Code Monkey home page Code Monkey logo

m3api's Introduction

m3api

npm documentation

m3api is a minimal, modern MediaWiki API client, a library for interacting with the MediaWiki Action API from JavaScript.

  • It is minimal: It wraps the MediaWiki API, without much more. This library does not have many abstractions above the API, as you might find in other libraries (“get page”, “make edit”); instead, these are left to § extension packages, see below. m3api itself is for people who are comfortable working with the API directly – you pass in the same parameters that you’d find in e.g. the API sandbox, and get back the original MediaWiki response, JSON-decoded. (And if you use the API sandbox, check out this user script!) As a general rule, this library only implements features that apply to more than one API module. (In this respect, m3api is similar to mediawiki-js.)

  • It is modern: It’s based on Promises, provides iterators, uses ES6 modules, and so on. (See § compatibility below for details.)

It supports both Node.js and browsers. The browser version has no external dependencies.

Usage

Here’s an example demonstrating some ways to use m3api:

// you may need to change the import path,
// e.g. ./node_modules/m3api/node.js if installed via npm
import Session, { set } from './node.js';

// note: this example uses top-level await for simplicity,
// you may need an async wrapper function

// create a session from a wiki domain (or full api.php URL)
const session = new Session( 'en.wikipedia.org', {
	// these default parameters will be added to all requests
	formatversion: 2,
	errorformat: 'plaintext',
}, {
	// these default options will apply to all requests
	userAgent: 'm3api-README-example',
} );

// a sample request to the siteinfo API
const siteinfoResponse = await session.request( {
	action: 'query',
	meta: 'siteinfo',
	// array parameters are automatically converted
	siprop: [ 'general', 'statistics' ],
} );
// one way to handle the response: destructure it
const { query: {
	general: { sitename },
	statistics: { edits },
} } = siteinfoResponse;
console.log( `Welcome to ${sitename}, home to ${edits} edits!` );

// another way to get the same result
async function getSiteName( session ) {
	const response = await session.request( {
		action: 'query',
		// set parameters may be combined with other requests
		meta: set( 'siteinfo' ),
		siprop: set( 'general' ),
	} );
	return response.query.general.sitename;
}
async function getSiteEdits( session ) {
	const response = await session.request( {
		action: 'query',
		meta: set( 'siteinfo' ),
		siprop: set( 'statistics' ),
	} );
	return response.query.statistics.edits;
}
// the following two concurrent API requests will be automatically combined,
// sending a single request with siprop=general|statistics,
// because they are compatible (set parameters get merged, others are equal)
const [ sitename_, edits_ ] = await Promise.all( [
	getSiteName( session ),
	getSiteEdits( session ),
] );
console.log( `Welcome back to ${sitename_}, home to ${edits_} edits!` );

// a slightly contrived example for continuation
console.log( 'Here are ten local file pages linking to web.archive.org:' );
// due to miser mode, each request may only return few results,
// so we need continuation in order to get ten results in total
let n = 0;
outer: for await ( const urlResponse of session.requestAndContinue( {
	// requestAndContinue returns an async iterable of responses
	action: 'query',
	list: set( 'exturlusage' ),
	euprotocol: 'https',
	euquery: 'web.archive.org',
	eunamespace: [ 6 ], // File:
	eulimit: 'max',
	euprop: set( 'title' ),
} ) ) {
	for ( const page of urlResponse.query.exturlusage ) {
		console.log( page.title );
		if ( ++n >= 10 ) {
			break outer;
			// once we stop iterating, no more requests are made
		}
	}
}

This code works in Node.js, but also in the browser with only two changes:

  • import browser.js instead of node.js

  • add origin: '*' to the default parameters (anonymous cross-site request)

Other features not demonstrated above:

  • m3api can automatically fetch and add tokens to requests, using the tokenType and tokenName request options. Example usage:

    await session.request( {
    	action: 'login',
    	lgname: 'username',
    	lgpassword: 'password',
    }, {
    	method: 'POST',
    	tokenType: 'login',
    	tokenName: 'lgtoken',
    } );
    session.tokens.clear(); // any cached tokens are invalid after login
    await session.request( {
    	action: 'edit',
    	title: 'Test page',
    	text: 'Test content',
    }, {
    	method: 'POST',
    	tokenType: 'csrf', // usual token type for most actions
    	// tokenName: 'token' is the default
    } );
  • m3api detects any error(s) returned by the API, and throws them as an ApiErrors instance (the class can be imported as a non-default export of the browser.js and node.js modules). The first error code is used as the message, and all the error objects can be accessed as .errors. (The shape of those objects will depend on the request errorformat.)

  • Any warnings returned by the API are also detected. By default, warnings are logged to the console; you can specify a custom warn handler function in the request options (this may be advisable for interactive CLI applications on Node.js, though you should make sure the warnings are still seen by developers somehow).

  • To make POST requests instead of GET requests, pass an object with a method value as the second parameter: e.g. request( { ... }, { method: 'POST' } ). (requestAndContinue also supports this.) In POST requests, Blob or File parameters are also supported. (Note that, in Node 18, these will trigger a warning from Node. If you are affected by this and cannot upgrade to Node 20 or later, you can suppress the warning by launching Node with --no-warnings=ExperimentalWarning.)

  • API requests will automatically be retried if necessary (if the response contains a Retry-After header, or either a maxlag or readonly error). m3api will wait for an appropriate amount of time, then repeat the request, for up to 65 seconds by default. You can change this with the maxRetriesSeconds request option: e.g. request( { ... }, { maxRetriesSeconds: 10 } ) to stop retrying sooner. Set maxRetriesSeconds to 0 to disable this feature entirely.

  • Apart from strings, numbers, and arrays and sets thereof, parameter values can also be booleans, null, or undefined. false, null and undefined parameters are omitted from the request, according to their standard meaning in the MediaWiki API. true, the empty string, the empty array and the empty set are all not omitted, but instead use the empty string as the parameter value; for example, you can use props: [] to override a nonempty default value.

  • The responseBoolean helper can be used to get a boolean from a response object. For example, responseBoolean( response.query.general.rtl ) returns true if response.query.general had rtl: "" (formatversion=1) or rtl: true (formatversion=2). This is mostly useful in library code, when you don’t know the formatversion of the response; you can import the helper from core.js (but not browser.js or node.js).

  • The authorization request option can be used to set the Authorization request header. You can use this directly with an owner-only OAuth 2.0 client, by setting the option to the string Bearer ACCESS_TOKEN (where ACCESS_TOKEN is the access token MediaWiki generated for you); to use a regular OAuth 2.0 client and make requests authenticated as another user, use the m3api-oauth2 extension package.

For more details, see also the code-level documentation (JSdoc comments).

Automatically combining requests

One m3api feature deserves a more detailed discussion: how it automatically combines concurrent, compatible API requests.

  • API requests are concurrent if they are made within the same JS call stack, or (in other words) in the same “callback”. Technically, as soon as m3api receives a request, it queues a microtask (using Promise.resolve()) to dispatch it, and only other requests which arrive before that microtask runs have a chance to be combined with it.

  • API requests are compatible if their parameters and options are compatible. The parameters as a whole are compatible if every parameter common to both requests is compatible, i.e. the parameter values are either identical (after simple transformations like 2"2") or are both sets, in which case they’re merged for the combined request. (The set( ... ) function, which can be imported from node.js and browser.js, is just a shortcut for new Set( [ ... ] ).) Options are mostly compatible if they’re identical for both requests, but some options have special handling so that requests can still be combined even if they specify different values for those options.

To take advantage of this feature, it’s recommended to use set( ... ) instead of [ ... ] for most “list-like” API paremeters, even if you’re only specifying a single set element, as long as that parameter is safe to merge with other requests. For example, consider this request from the usage example above:

session.requestAndContinue( {
	action: 'query',
	list: set( 'exturlusage' ),
	euprotocol: 'https',
	euquery: 'web.archive.org',
	eunamespace: [ 6 ], // File:
	eulimit: 'max',
	euprop: set( 'title' ),
} )

Let’s go through those parameters in turn:

  • action: 'query': There can only be one action at a time, so this parameter has a single value.

  • list: set( 'exturlusage' ): The query API supports multiple lists at once, and it doesn’t matter to us if there are other lists in the response (they’ll be tucked away under a different key in the result), so we specify this as a set. If another request has e.g. list: set( 'allpages' ), then the remaining parameters of that request will probably start with ap* (the list=allpages API parameter prefix), so they won’t conflict with our eu* parameters.

  • euprotocol: 'https': Must be a single value.

  • euquery: 'web.archive.org': Must be a single value.

  • eunamespace: [ 6 ]: Here we specify an array, not a set. The parameter can take multiple values in the API, but we want results limited to just the file namespace, and if this parameter was a set, we might get results from other namespaces due to merged requests. The alternative would be to specify this as eunamespace: set( 6 ), but then to check the namespace of each result we get, so that we skip results from other namespaces, and only process the ones we really wanted.

  • eulimit: 'max': Must be a single value.

  • euprop: set( 'title' ): Here we use a set again, because we don’t mind if other requests add extra properties to each result, as long as the title itself is included. Note that the default value for this parameter is ids|title|url, so if we didn’t specify it at all, we would probably get the data we need as well; however, if our request was then combined with another request, and that request had euprop: set( 'ids' ), then we wouldn’t get the title in our request.

The last point is worth elaborating on: don’t just rely on default parameter values if your requests may be combined with others. This is most important when you’re writing library code (similar to the getSiteName and getSiteEdits functions in the usage example above), where you don’t know which other requests may be made at any time; if you’re directly making API requests from an application, you may know that no other concurrent requests will be made at a certain time, and could get away with relying on default parameters.

To avoid just relying on default parameter values, you have several options:

  1. Explicitly specify a value for the parameter, either the default or (as with title vs. ids|title|url above) a part of it.

  2. Explicitly specify the parameter as null or undefined. This means that the parameter won’t be sent with the request (i.e. the server-side default will be used), but makes the request incompatible with any other request that has a different value for the parameter. (This is similar to using an array instead of a set, as we saw for eunamespace above: both strategies inhibit merging with some other requests.)

  3. Process the response in a way that works regardless of parameter value. This is not always possible, but as an example, with a bit of extra code, you may be able to process both formatversion=1 and formatversion=2 responses (see also the responseBoolean helper function).

Extension packages

While m3api itself aims to be a minimal library, its functionality can be extended by other packages, which make it easier to use certain APIs correctly. Available extension packages include:

If you create an additional extension package, feel free to submit a pull request to add it to this list. (Also, have a look at the guidelines below.)

Using extension packages

For the most part, m3api extension packages can be used like other packages: you install them using npm, import functions from them, etc.

However, they require some setup to be used in the browser. As they can’t import m3api using a relative path, and bare m3api imports only work out of the box in Node.js, something needs to resolve the imports for the browser. The most convenient way is to use a bundler or build system: for example, Vite has been tested and works out of the box.

Alternatively, you can specify an import map, like in this example:

<script type="importmap">
{
	"imports": {
		"m3api/": "./node_modules/m3api/",
		"m3api-query/": "./node_modules/m3api-query/"
	}
}
</script>
<script type="module">
	import Session, { set } from 'm3api/browser.js';
	import { queryFullPageByTitle } from 'm3api-query/index.js';
	// ...
</script>

Note that import maps are not as widely supported as ES6 modules in general.

Creating extension packages

Here are some guidelines or recommendations for creating m3api extension packages:

  • Combine your options with those from m3api. Functions that make requests should take a single (optional) options argument, including both options passed through to m3api and those for your package. The package’s options should be named beginning with the package name and a slash, e.g. somePkg/someOption or @someScope/somePkg/someOption. When reading the options, use the session’s defaultOptions and m3api’s DEFAULT_OPTIONS; you may add your options to the DEFAULT_OPTIONS at package load time. For example:

    import { DEFAULT_OPTIONS } from 'm3api';
    
    Object.assign( DEFAULT_OPTIONS, {
    	'somePkg/optionA': true,
    	'somePkg/optionB': false,
    } );
    
    function someFunction( session, options = {} ) {
    	const {
    		'somePkg/optionA': optionA,
    		'somePkg/optionB': optionB,
    	} = {
    		...DEFAULT_OPTIONS,
    		...session.defaultOptions,
    		...options,
    	};
    	// use optionA, optionB
    	session.request( ..., options );
    }
  • Functions that make requests or process responses should be able to deal with either formatversion, rather than forcing your users to use formatversion=2 (or even formatversion=1). The responseBoolean helper from core.js can be helpful.

  • If you need to import anything from m3api, import it from m3api/, not ../m3api/ or anything like that. (npm might move m3api further up the dependency tree.)

Compatibility

In Node.js, m3api is compatible with Node 18.2.0 or later. Among major browsers, m3api is compatible with Chrome 63, Firefox 60, Edge 79, Opera 50 (46 on Android), Safari 12, and Samsung Internet 8.0. The relevant browser requirements of m3api are:

  • Support for ES6 modules (import/export). Supported in Firefox since version 60. (Other browsers supported async generators before ES6 modules.)

  • Support for async generators (async function *, for await … of). Supported since Chrome 63, Edge 79, Opera 50 (46 on Android), Safari 12, Samsung Internet 8.0. (Firefox supported ES6 modules before async generators.)

The Node.js version requirement is based on fetch() being available and supported by the http-cookie-agent package. If you need support for earlier Node.js versions, try using m3api v0.7.3.

Other modern features used by m3api – destructuring assignment, spread syntax, default arguments, classes, etc. – are less recent than ES6 modules and async generators, and therefore aren’t expected to affect compatibility.

Using a combination of transpiling and polyfilling, it should be possible to use m3api on older platforms as well. If you try this, feel free to send a pull request updating this paragraph with your experience.

Stability

m3api follows a slightly modified version of semantic versioning. The public interface, which most users will use, is stable between minor versions (only changing incompatibly between major versions); however, the internal interface, which some extension packages may use, is only stable between patch versions, and may change incompatibly between minor versions. Most users are encouraged to use the “caret” operator in their m3api dependency (e.g. ^1), but extension packages depending on the internal interface should use the “tilde” operator (e.g. ~1.0), and list all m3api versions they’re compatible with (e.g. ~1.0||~1.1).

The stable, public interface comprises the following items:

  • The paths / existence of the core.js, node.js and browser.js files.

  • All exports of those files that have not been marked @protected or @private.

  • All members of those exports (class methods and properties) that have not been marked @protected or @private.

The internal interface additionally comprises the following items:

  • The paths / existence of the fetch.js, fetch-browser.js, fetch-node.js and combine.js files.

  • All exports of those files, or of files in the public interface, that have not been marked @private.

  • All members of those exports that have not been marked @private.

That is, public code only changes incompatibly between major versions, @protected code only changes incompatibly between minor versions, and @private code may change incompatibly at any time.

For methods, the stable interface only includes calling them; overriding them is part of the internal interface. (That is, changes that are compatible for callers but will require overriders to adjust may take place between minor versions.)

Incompatible changes to the stable interface will be mentioned in the changelog, always at the beginning of the entry for an release (before compatible changes in the same release), using the words “BREAKING CHANGE” (in all caps). Incompatible changes to the internal interface will be mentioned using the words “Internal Breaking Change”, not necessarily at the beginning of the entry.

The usual semver interpretation of pre-1.0 versions applies, i.e. in 0.x.y, x is the “major” version and y the “minor” one.

License

Published under the ISC License. By contributing to this software, you agree to publish your contribution under the same license.

m3api's People

Contributors

lucaswerkmeister avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

simon04

m3api's Issues

Create user script to format API sandbox calls for m3api

The API sandbox can show the request parameters in different formats; two (URL query string and JSON) are built-in, but this list is also extensible via the apisandbox.formatRequest JS hook. It would be nice to create a user script, which people can add to their global.js, which generates a snippet of m3api code to make the request.

Here’s a very quickly sketched version:

mw.hook( 'apisandbox.formatRequest' ).add( function ( items, displayParams, rawParams ) {
  items.push( new OO.ui.MenuOptionWidget( {
    label:'m3api',
    data: new mw.widgets.CopyTextLayout( {
      label: 'm3api call',
      copyText: `session.request( ${JSON.stringify( displayParams, null, '\t' )} );`,
      multiline: true,
      textInput: {
        classes: [ 'mw-apisandbox-textInputCode' ],
        autosize: true,
        maxRows: 6,
      },
    } ),
  } ) );
} );

Keep connections alive in axios backend

Apparently axios doesn’t do HTTP Connection: keep-alive by default. There seem to be two options:

  • native http.Agent:
    axios.create( {
      httpAgent: new http.Agent( { keepAlive: true } ),
      httpsAgent: new https.Agent( { keepAlive: true } ),
    } );
  • agentkeepalive package

I should check what the advantages and drawbacks of each approach are (mainly, if the extra dependency is worth it or not).

Link to other m3api packages in README

While m3api itself aims to be minimal, other packages can add useful functionality related to specific actions or modes of the API. I’m currently working on m3api-query (no initial release yet), and alluded to m3api-botpassword and m3api-oauth2 packages in #6 and elsewhere. (At least m3api-botpassword should be ready before m3api v1.0.0, and hopefully won’t take much work; m3api-oauth2 might be post-v1.0.0, depending on #9.) These should be added to the README, along with general guidelines for m3api “sidecar” packages.

So far, the only guideline for “sidecar” packages is a convention for option names I’ve arrived at in lucaswerkmeister/m3api-query#4: other packages are allowed to define their own request options, and are encouraged to name them using the pattern packageName/optionName (@scopeName/packageName/optionName for scoped packages). Note that this requires a stable way for these packages to access the default options, probably by declaring session.defaultOptions to be part of the stable interface (already planned in #6 (comment)).

Warn if user agent isn’t specified?

The Python mwapi library warns if no user agent is specified, before falling back to a default user agent. In m3api, we currently fall back to a default user agent without producing any warning. Should we add a warning?

And if yes, how should that warning work? mwapi warns using the standard Python logging module; the closest equivalent in JavaScript is console.warn(), but is it acceptable to write to that in a library?

If we want to add such a warning, we should probably do it in some pre-1.0.0 release, and then revisit the decision before the 1.0.0 release, checking if anybody complained about the warning.

Run browser tests in browser

We used to run test/integration/browser.test.js using mocha-headless, but that broke (and there’s no clear way to report it, since that package has no source code repository or issue tracker). I’m about to push a commit that, for now, runs the test in Node instead, using Node’s experimental fetch() support, to unbreak the tests; it would be nice to somehow run them in a browser again, though.

Change constructor signature

The signature of request() is nice and tidy at the moment: one object of params (sent to the API), and an optional object of options (controlling how the params are sent to the API: method, max retries, etc.).

The constructor is a bit more messy: it has the API URL, then an object of default params, and then the user agent, which logically belongs with the request options. There’s a hard split between the user agent and all the other request options: you can’t change the user agent for just one request() call, nor can you set any default request options in the constructor.

We probably want to change this, and we probably want to do that pre-1.0.0. The constructor should take the API URL, the default params, and then the default options; the user agent would be an option like any other.

Decide whether internal changes count as breaking or not

So far, several releases have included a “BREAKING CHANGE (internal)”, which is a breaking change to the private methods used to communicate between Session and its subclasses. Before the 1.0.0 release, we need to figure out if those kinds of changes are really considered breaking and require a major version bump in future, or if this whole interface is considered unstable and can be changed with only a minor (or even patch?) version bump.

Automatically combine compatible requests

The MediaWiki Action API, especially the “query” action, allows performing many different actions in a single API request. For example, you can get the categories, outgoing links, and recent revisions, of several pages, together with general site information, all at once:

session.request( {
    action: 'query',
    prop: [ 'categories', 'links', 'revisions' ],
    meta: 'siteinfo',
    titles: [ 'Page 1', 'Page 2' ],
} );

However, this requires that the request parameters are combined at some point. Usually, this requires some programmer effort: either you hard-code the request parameters directly, as above, or you encapsulate the request parts into several functions, but split each of them, and then have a phase where each function adds to the parameter set, then you make the request, and then you have another phase where each function extracts the relevant part out of the common response (example).

In JavaScript, we can do better. Since all API requests are asynchronous, we can postpone each request a tiny little bit, and, just before actually sending it, check if it can be combined with any other requests that haven’t yet been sent either. This can happen at the library level, so that application-level code doesn’t have to worry about it very much; for example:

async function getSiteName( session ) {
    const response = await session.request( {
        action: 'query',
        meta: new Set( [ 'siteinfo' ] ),
        siprop: new Set( [ 'general' ] ),
    } );
    return response.query.general.sitename;
}

async function getPageCount( session ) {
    const response = await session.request( {
        action: 'query',
        meta: new Set( [ 'siteinfo' ] ),
        siprop: new Set( [ 'statistics' ] ),
    } );
    return response.query.statistics.pages;
}

const [ siteName, pageCount ] = await Promise.all( [
    getSiteName( session ),
    getPageCount( session ),
] );

Here, m3api should detect that the two requests can be combined into a single request with siprop: new Set( [ 'general', 'statistics' ] ). Set will be used to distinguish multi-valued parameters that can be combined (and are order-insensitive) from ones that can’t be combined and are order-sensitive (arrays).

Publish documentation

So far, the two levels of documentation are “read the README” and “read the code”. We should have an in-between step of publishing the jsdoc-rendered code documentation somewhere, ideally for each release.

OAuth support

Currently, both backends theoretically support logging in and making authenticated edits, using regular cookie-based sessions. (Though for the browser backend, you’ll be subject to CORS restrictions, so the usefulness of this is fairly limited.) However, for the Node.js backend to be more useful, we should really figure out OAuth support. This will allow m3api to be used for tool backends.

Handle batchcomplete in continuation

I was initially under the impression that the batchcomplete field in responses is specific to the query API, while continuation is a more general concept, but in fact batchcomplete is also managed by the ApiContinuationManager. In light of this, I wonder if m3api (rather than m3api-query) should offer some functionality to work with this field, or its absence.

One thing I could imagine is a function called requestAndContinueBatch(), which, instead of returning a iterable, returns an iterable of iterables. Each inner iterable would yield responses up to and including a batchcomplete one; the outer iterable would produce inner iterables until continuation finishes (or iteration stops, of course). If each response has a complete batch, then each inner iterator would only yield a single response.

This would be more cumbersome to use, so I definitely wouldn’t want it to replace requestAndContinue(); it would just be available as an alternative API, which would allow you to write more correct / robust code when you think you need it (typically in a library).

Reconcile `core.js` exports with `browser.js` and `node.js` exports

Currently, browser.js and node.js re-export a subset of core.js’s exports: ApiErrors, ApiWarnings and set, but not e.g. DefaultUserAgentWarning or responseBoolean. I think this needs some kind of cleanup. Should they really re-export a subset, and if yes, how much? (I think makeWarnDroppingTruncatedResultWarning is an export that you’d exclude from any reasonable subset, but responseBoolean is arguably almost as useful as set.) Or should they re-export everything, or nothing?

Show warnings by default

Currently, the Node backend doesn’t show warnings by default, because I was worried about printing warnings in a CLI application; I had a vague idea to, some time after the 1.0 release, do a code search for m3api users, and consider showing warnings by default in a future 2.0 version if it looked like this wouldn’t cause much breakage.

After thinking this over for a bit longer, I think I’ve changed my mind: API warnings are important enough that they should be shown by default. Most users of the library won’t be CLI applications, and the benefit to those users of printing warnings by default should outweigh the risk of confusing users of CLI applications. Also, this will make example / model code for m3api shorter, since we won’t need to include warn: console.warn anymore, and it will simplify the internal code, since the default warn handler will no longer vary by backend.

Support specifying domain instead of full api.php URL

I think it would be convenient if we allowed specifying en.wikipedia.org in addition to https://en.wikipedia.org/w/api.php as the first constructor argument, since https:// is the most common protocol and /w/api.php the most common API path.

The logic would be that we add https://${…}/w/api.php if the argument contains no slashes at all. We would not support https://en.wikipedia.org as the argument. I don’t want to get too far into “cleverly” recognizing which parts of the URL have been specified and which should use a default – just two options, bare domain or full path.

Add separate repo with examples

I think I originally envisioned this as part of the wiki here, but a separate repo (lucaswerkmeister/m3api-examples) probably makes more sense?

Examples should include:

  • plain HTML+CSS+JS client-side web app with import maps
  • Vue+Vite app (probably same functionality as the previous one?)
  • server-side web app (express?)
  • bot
  • one-off script

Make retry customizable

Since I’m fairly sure I want to put OAuth support (#9) into an extension package and not into m3api core, but OAuth 2.0 access tokens are relatively short-lived (4 h) and need to be refreshed from time to time, m3api needs to let extension packages hook into the retry mechanism.

Clarify “modern” requirements

Prior to a 1.0 release, we should document what exactly m3api means by “modern”, somewhere in the README. The closest thing we have so far is the Node≥14 requirement in package.json.

Speaking of which – given that the latest Debian version is only 12, let’s see if it’s possible to use m3api under Debian Node. I think modules are the biggest incompatibility, and --experimental-modules might be sufficient to resolve that.

Decide whether to keep automatic retry

6e9b954 (first released in 0.2.0) makes m3api automatically retry API requests when receiving a Retry-After response header (i.e., if the request specified a maxlag parameter and database replication lag is currently above that). I’m not sure yet whether this is a good thing to do by default or not, and that decision should be made before the 1.0.0 release. If anyone has opinions on that, please leave them here!

Automatically get and add CSRF token

Many other MediaWiki API libraries support automatically adding a token parameter, transparently getting it from action=query&meta=tokens if it’s not known yet. We should add this to m3api – it’s useful for many different API modules.

The proposed interface is two new request options (i.e. the second request() parameter, after the params, alongside the method):

  • tokentype: No default. If present, use this token type. (csrf would be the most common type, but there are a few others.)
  • tokenname: Defaults to token. I’m not sure if any API modules use another name (I seem to dimly remember one existing, but can’t find anything in Wikidata paraminfo), but it doesn’t cost much to include. (We can always remove it later if it really turns out to be unnecessary.)

Example:

session.request( {
    action: 'edit',
    // ...
}, {
    method: 'POST',
    tokentype: 'csrf',
} );

Support non-200 HTTP status codes

I previously believed that the action API was always supposed to return HTTP 200 and any other status code indicated an unrecoverable internal error. But apparently it’s possible for API modules to return custom error codes (by calling $this->dieWithError( $msg, $code, $data, $httpCode ) with a custom $httpCode), and a few API modules make use of this. So I guess we need to find some way to distinguish these responses from genuine internal errors. (Check if the MediaWiki-API-Error response error is present, maybe?)

Support file parameters (upload)

I don’t think we support this yet, but we should. (I’ll need to look into what the API should look like – probably Blob as parameter type? But let’s see what e.g. fetch() accepts.)

Login failure does not result in exception being thrown

If login failed with the wrong password, I would expect an exception being thrown, similar to errors. However, the promise resolves and the code carries on.

This is the API response:

{
  login: {
    result: 'Failed',
    reason: {
      code: 'wrongpassword',
      text: 'Incorrect username or password entered. Please try again.'
    }
  }
}

Drop Node 12 support

m3api currently supports Node 12, even though it’s no longer supported by Node.js, because it’s the latest Node version in a stable Debian release. However, the upcoming Debian Bookworm (currently testing) already packages Node 18, so once that’s released (probably in Summer 2023), the next major m3api release (probably m3api v2) can drop Node 12 support. This should let us simplify the code in the following ways:

  • drop add-performance-global.js
  • drop the extra test job in the Test workflow in GitHub Actions
    • can’t happen unless we bump all the way to Node 18 (see aa6ccdb)
  • update http-cookie-agent and mocha to the latest versions
  • drop the --harmony-top-level-await option from test:readme
  • import fs/promises instead of fs in the integration tests
  • m3api-oauth2: always require crypto

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.