@alxia/context-storage 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +3 -3
- package/dist/storage.d.ts +4 -6
- package/dist/storage.d.ts.map +1 -1
- package/docs/guide.md +9 -13
- package/docs/roadmap.md +6 -6
- package/docs/troubleshooting.md +15 -15
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -80,8 +80,8 @@ or where the middleware did not run (a route declared before it),
|
|
|
80
80
|
a request that reached no route, `NOT_ROUTED`.
|
|
81
81
|
|
|
82
82
|
Pass it to `app.use` called: `use(contextStorage)`, uncalled, is refused by
|
|
83
|
-
`tsc` (`
|
|
84
|
-
([troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/context-storage/docs/troubleshooting.md#
|
|
83
|
+
`tsc` (`TS2345`), and throws where it is declared
|
|
84
|
+
([troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/context-storage/docs/troubleshooting.md#use-argument-1-looks-like-a-factory-contextstorage-call-it-usecontextstorage)).
|
|
85
85
|
|
|
86
86
|
## API
|
|
87
87
|
|
|
@@ -90,7 +90,6 @@ Pass it to `app.use` called: `use(contextStorage)`, uncalled, is refused by
|
|
|
90
90
|
| `contextStorage<App>()` | the middleware, given to `app.use`, with `context()` and `tryContext()` typed by `App` — by default the app `@alxia/core`'s `Register` names, `BaseContext` when none — and required of the app that mounts it |
|
|
91
91
|
| `StoredContext<App>` | what `context()` returns: `ContextOf<App>`, or `BaseContext` when `App` is no app |
|
|
92
92
|
| `ContextStorageMiddleware<App>` | its type: a middleware with `context()` and `tryContext()` |
|
|
93
|
-
| `ContextStoragePlugin<App>` | deprecated: the former name of `ContextStorageMiddleware` |
|
|
94
93
|
| `getContext`, `tryGetContext`, `getRequestContext`, `tryGetRequestContext`, `runWithContext` | the store, untyped |
|
|
95
94
|
| `ContextStorageError`, `ContextStorageErrorCode` | why there is no context |
|
|
96
95
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ContextStorageError, type ContextStorageErrorCode, type ContextStorageMiddleware,
|
|
1
|
+
export { ContextStorageError, type ContextStorageErrorCode, type ContextStorageMiddleware, contextStorage, getContext, getRequestContext, runWithContext, type StoredContext, tryGetContext, tryGetRequestContext, } from './storage';
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,mBAAmB,EACnB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,KAAK,aAAa,EAClB,aAAa,EACb,oBAAoB,GACpB,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
import {
|
|
4
4
|
defineMiddleware,
|
|
5
|
+
markFactory,
|
|
5
6
|
settle
|
|
6
7
|
} from "@alxia/core";
|
|
7
8
|
|
|
@@ -41,7 +42,7 @@ function contextStorage(...uncalled) {
|
|
|
41
42
|
if (uncalled.length > 0) {
|
|
42
43
|
throw new TypeError("contextStorage is a factory: use(contextStorage()), not use(contextStorage)");
|
|
43
44
|
}
|
|
44
|
-
const middleware = defineMiddleware((ctx, next)
|
|
45
|
+
const middleware = defineMiddleware(function contextStorage(ctx, next) {
|
|
45
46
|
const routed = ctx.route === undefined ? undefined : ctx;
|
|
46
47
|
const current = storage.getStore();
|
|
47
48
|
if (current !== undefined && current.request.url === ctx.url) {
|
|
@@ -55,6 +56,7 @@ function contextStorage(...uncalled) {
|
|
|
55
56
|
tryContext: () => tryGetContext()
|
|
56
57
|
});
|
|
57
58
|
}
|
|
59
|
+
markFactory(contextStorage);
|
|
58
60
|
export {
|
|
59
61
|
ContextStorageError,
|
|
60
62
|
contextStorage,
|
|
@@ -65,5 +67,5 @@ export {
|
|
|
65
67
|
tryGetRequestContext
|
|
66
68
|
};
|
|
67
69
|
|
|
68
|
-
//# debugId=
|
|
70
|
+
//# debugId=C6E5225783F4C54464756E2164756E21
|
|
69
71
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/storage.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n\ttype BaseContext,\n\ttype ContextOf,\n\tdefineMiddleware,\n\ttype Empty,\n\ttype Middleware,\n\ttype
|
|
5
|
+
"import { AsyncLocalStorage } from 'node:async_hooks';\nimport {\n\ttype BaseContext,\n\ttype ContextOf,\n\tdefineMiddleware,\n\ttype Empty,\n\ttype Middleware,\n\ttype Mounted,\n\tmarkFactory,\n\ttype RegisteredBase,\n\ttype RequestContext,\n\ttype RequiresOf,\n\tsettle,\n} from '@alxia/core';\n\n/** Why there is no context to read. */\nexport type ContextStorageErrorCode =\n\t/** Called outside any request: at startup, in a job, after the response. */\n\t| 'OUTSIDE_REQUEST'\n\t/** In a request `contextStorage()` ran on, but that reached no route: a 404, a 405. A route it did not run on is `OUTSIDE_REQUEST`. */\n\t| 'NOT_ROUTED';\n\nexport class ContextStorageError extends Error {\n\toverride readonly name = 'ContextStorageError';\n\treadonly code: ContextStorageErrorCode;\n\n\tconstructor(code: ContextStorageErrorCode) {\n\t\tsuper(\n\t\t\tcode === 'OUTSIDE_REQUEST'\n\t\t\t\t? 'getContext(): called outside a request — use tryGetContext(), or runWithContext() in a job or a test'\n\t\t\t\t: 'getContext(): this request reached no route declared after contextStorage() — use it earlier, or getRequestContext()',\n\t\t);\n\t\tthis.code = code;\n\t}\n}\n\ninterface Holder {\n\treadonly request: RequestContext;\n\tctx: BaseContext | undefined;\n}\n\n/**\n * One store for the process: every `contextStorage()` writes to it, and\n * `getContext()` reads it wherever it is called — the way `hono/context-storage`\n * works, so code written against one ports to the other.\n */\nconst storage = new AsyncLocalStorage<Holder>();\n\n/**\n * The context of the route the current request reached: what its handler\n * reads — the request, `set`, `reply`, and what every hook before it added.\n * Throws a `ContextStorageError` outside a route declared after\n * `contextStorage()`.\n *\n * `Ctx` types what the hooks added; prefer the typed `context()` of the middleware\n * itself, typed by the app.\n */\nexport function getContext<Ctx extends object = Empty>(): BaseContext & Ctx {\n\tconst holder = storage.getStore();\n\tif (holder === undefined) throw new ContextStorageError('OUTSIDE_REQUEST');\n\tif (holder.ctx === undefined) throw new ContextStorageError('NOT_ROUTED');\n\treturn holder.ctx as BaseContext & Ctx;\n}\n\n/** `getContext()`, or `undefined` where it would throw: code that runs in and out of requests. */\nexport function tryGetContext<Ctx extends object = Empty>():\n\t| (BaseContext & Ctx)\n\t| undefined {\n\treturn storage.getStore()?.ctx as (BaseContext & Ctx) | undefined;\n}\n\n/**\n * The request as a middleware sees it — in a 404 too — with the route it\n * reached and the error it failed with.\n */\nexport function getRequestContext(): RequestContext {\n\tconst holder = storage.getStore();\n\tif (holder === undefined) throw new ContextStorageError('OUTSIDE_REQUEST');\n\treturn holder.request;\n}\n\n/** `getRequestContext()`, or `undefined` outside a request. */\nexport function tryGetRequestContext(): RequestContext | undefined {\n\treturn storage.getStore()?.request;\n}\n\n/**\n * Runs `work` with `ctx` as the current context: a job, a queue consumer,\n * a test calling a service that reads `getContext()`.\n */\nexport function runWithContext<T>(ctx: BaseContext, work: () => T): T {\n\treturn storage.run({ request: ctx, ctx }, work);\n}\n\n/**\n * The middleware, and its context typed by the app it follows. It\n * requires that context of the app that uses it, beyond the base context:\n * `app.use` on an app that does not give it is a compile error.\n */\nexport type ContextStorageMiddleware<App> = Middleware<\n\tRequiresOf<StoredContext<App>, 'context'>,\n\tPromise<Response>\n> & {\n\t/** `getContext()`, typed by `App`. */\n\tcontext(): StoredContext<App>;\n\t/** `tryGetContext()`, typed by `App`. */\n\ttryContext(): StoredContext<App> | undefined;\n};\n\n/** What `context()` reads: the context of `App`, or the base context when `App` is no app. */\nexport type StoredContext<App> = [ContextOf<App>] extends [never]\n\t? BaseContext\n\t: Mounted<ContextOf<App>>;\n\n/**\n * The request's context, anywhere it runs, as a middleware: from the\n * routes declared after it, every function their handlers call — however\n * deep, through every `await` and timer — reads it with `getContext()`,\n * without it being passed down; the answer to an error too, a try/catch\n * middleware's included. Give it to `use` before the middlewares whose errors\n * your own middleware answers: what the rest throws is answered inside\n * it, as the route would.\n *\n * Typed by the app it is used on: give the plugin that app's type, and its\n * `context()` returns what its routes read — the `user` a session derived, the\n * `db` decorated. Given none, the app `Register` names in `@alxia/core`\n * (`BaseContext` when nothing is registered). Either way the app that uses\n * it must give that context: using it before is a compile error.\n *\n * ```ts\n * const base = alxia().decorate({ db }).use(session(auth, { required: true }));\n * export const requestContext = contextStorage<typeof base>();\n * const app = base.use(requestContext).get('/orders', ({ reply }) => reply(200, listOrders()));\n *\n * // orders.ts — no context passed\n * export const listOrders = () => {\n * const { db, user } = requestContext.context();\n * return db.orders.forUser(user.id);\n * };\n * ```\n */\nexport function contextStorage<App = RegisteredBase>(\n\t...uncalled: readonly never[]\n): ContextStorageMiddleware<App> {\n\tif (uncalled.length > 0) {\n\t\t// `use(contextStorage)`: the factory runs as the middleware, handed\n\t\t// each request's context, and would store none of them.\n\t\tthrow new TypeError(\n\t\t\t'contextStorage is a factory: use(contextStorage()), not use(contextStorage)',\n\t\t);\n\t}\n\tconst middleware = defineMiddleware(function contextStorage(ctx, next) {\n\t\t// A route's context; none for a request no route matches.\n\t\tconst routed = ctx.route === undefined ? undefined : ctx;\n\t\tconst current = storage.getStore();\n\t\t// A second one, on the same request: the context it reaches is the route's.\n\t\tif (current !== undefined && current.request.url === ctx.url) {\n\t\t\tcurrent.ctx = routed ?? current.ctx;\n\t\t\treturn settle(ctx, next());\n\t\t}\n\t\treturn storage.run({ request: ctx, ctx: routed }, () =>\n\t\t\tsettle(ctx, next()),\n\t\t);\n\t});\n\treturn Object.assign(middleware, {\n\t\tcontext: () => getContext(),\n\t\ttryContext: () => tryGetContext(),\n\t}) as unknown as ContextStorageMiddleware<App>;\n}\n\nmarkFactory(contextStorage);\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AAqBO,MAAM,4BAA4B,MAAM;AAAA,EAC5B,OAAO;AAAA,EAChB;AAAA,EAET,WAAW,CAAC,MAA+B;AAAA,IAC1C,MACC,SAAS,oBACN,yGACA,sHACJ;AAAA,IACA,KAAK,OAAO;AAAA;AAEd;AAYA,IAAM,UAAU,IAAI;AAWb,SAAS,UAAsC,GAAsB;AAAA,EAC3E,MAAM,SAAS,QAAQ,SAAS;AAAA,EAChC,IAAI,WAAW;AAAA,IAAW,MAAM,IAAI,oBAAoB,iBAAiB;AAAA,EACzE,IAAI,OAAO,QAAQ;AAAA,IAAW,MAAM,IAAI,oBAAoB,YAAY;AAAA,EACxE,OAAO,OAAO;AAAA;AAIR,SAAS,aAAyC,GAE5C;AAAA,EACZ,OAAO,QAAQ,SAAS,GAAG;AAAA;AAOrB,SAAS,iBAAiB,GAAmB;AAAA,EACnD,MAAM,SAAS,QAAQ,SAAS;AAAA,EAChC,IAAI,WAAW;AAAA,IAAW,MAAM,IAAI,oBAAoB,iBAAiB;AAAA,EACzE,OAAO,OAAO;AAAA;AAIR,SAAS,oBAAoB,GAA+B;AAAA,EAClE,OAAO,QAAQ,SAAS,GAAG;AAAA;AAOrB,SAAS,cAAiB,CAAC,KAAkB,MAAkB;AAAA,EACrE,OAAO,QAAQ,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;AAAA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBO,MAAM,4BAA4B,MAAM;AAAA,EAC5B,OAAO;AAAA,EAChB;AAAA,EAET,WAAW,CAAC,MAA+B;AAAA,IAC1C,MACC,SAAS,oBACN,yGACA,sHACJ;AAAA,IACA,KAAK,OAAO;AAAA;AAEd;AAYA,IAAM,UAAU,IAAI;AAWb,SAAS,UAAsC,GAAsB;AAAA,EAC3E,MAAM,SAAS,QAAQ,SAAS;AAAA,EAChC,IAAI,WAAW;AAAA,IAAW,MAAM,IAAI,oBAAoB,iBAAiB;AAAA,EACzE,IAAI,OAAO,QAAQ;AAAA,IAAW,MAAM,IAAI,oBAAoB,YAAY;AAAA,EACxE,OAAO,OAAO;AAAA;AAIR,SAAS,aAAyC,GAE5C;AAAA,EACZ,OAAO,QAAQ,SAAS,GAAG;AAAA;AAOrB,SAAS,iBAAiB,GAAmB;AAAA,EACnD,MAAM,SAAS,QAAQ,SAAS;AAAA,EAChC,IAAI,WAAW;AAAA,IAAW,MAAM,IAAI,oBAAoB,iBAAiB;AAAA,EACzE,OAAO,OAAO;AAAA;AAIR,SAAS,oBAAoB,GAA+B;AAAA,EAClE,OAAO,QAAQ,SAAS,GAAG;AAAA;AAOrB,SAAS,cAAiB,CAAC,KAAkB,MAAkB;AAAA,EACrE,OAAO,QAAQ,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;AAAA;AAkDxC,SAAS,cAAoC,IAChD,UAC6B;AAAA,EAChC,IAAI,SAAS,SAAS,GAAG;AAAA,IAGxB,MAAM,IAAI,UACT,6EACD;AAAA,EACD;AAAA,EACA,MAAM,aAAa,iBAAiB,SAAS,cAAc,CAAC,KAAK,MAAM;AAAA,IAEtE,MAAM,SAAS,IAAI,UAAU,YAAY,YAAY;AAAA,IACrD,MAAM,UAAU,QAAQ,SAAS;AAAA,IAEjC,IAAI,YAAY,aAAa,QAAQ,QAAQ,QAAQ,IAAI,KAAK;AAAA,MAC7D,QAAQ,MAAM,UAAU,QAAQ;AAAA,MAChC,OAAO,OAAO,KAAK,KAAK,CAAC;AAAA,IAC1B;AAAA,IACA,OAAO,QAAQ,IAAI,EAAE,SAAS,KAAK,KAAK,OAAO,GAAG,MACjD,OAAO,KAAK,KAAK,CAAC,CACnB;AAAA,GACA;AAAA,EACD,OAAO,OAAO,OAAO,YAAY;AAAA,IAChC,SAAS,MAAM,WAAW;AAAA,IAC1B,YAAY,MAAM,cAAc;AAAA,EACjC,CAAC;AAAA;AAGF,YAAY,cAAc;",
|
|
8
|
+
"debugId": "C6E5225783F4C54464756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BaseContext, type ContextOf, type Empty, type Middleware, type
|
|
1
|
+
import { type BaseContext, type ContextOf, type Empty, type Middleware, type Mounted, type RegisteredBase, type RequestContext, type RequiresOf } from '@alxia/core';
|
|
2
2
|
/** Why there is no context to read. */
|
|
3
3
|
export type ContextStorageErrorCode =
|
|
4
4
|
/** Called outside any request: at startup, in a job, after the response. */
|
|
@@ -39,22 +39,20 @@ export declare function runWithContext<T>(ctx: BaseContext, work: () => T): T;
|
|
|
39
39
|
* requires that context of the app that uses it, beyond the base context:
|
|
40
40
|
* `app.use` on an app that does not give it is a compile error.
|
|
41
41
|
*/
|
|
42
|
-
export type ContextStorageMiddleware<App> = Middleware<RequiresOf<StoredContext<App>, 'context'>, Promise<Response>> &
|
|
42
|
+
export type ContextStorageMiddleware<App> = Middleware<RequiresOf<StoredContext<App>, 'context'>, Promise<Response>> & {
|
|
43
43
|
/** `getContext()`, typed by `App`. */
|
|
44
44
|
context(): StoredContext<App>;
|
|
45
45
|
/** `tryGetContext()`, typed by `App`. */
|
|
46
46
|
tryContext(): StoredContext<App> | undefined;
|
|
47
47
|
};
|
|
48
|
-
/** @deprecated Renamed `ContextStorageMiddleware`: it is a middleware. */
|
|
49
|
-
export type ContextStoragePlugin<App> = ContextStorageMiddleware<App>;
|
|
50
48
|
/** What `context()` reads: the context of `App`, or the base context when `App` is no app. */
|
|
51
49
|
export type StoredContext<App> = [ContextOf<App>] extends [never] ? BaseContext : Mounted<ContextOf<App>>;
|
|
52
50
|
/**
|
|
53
51
|
* The request's context, anywhere it runs, as a middleware: from the
|
|
54
52
|
* routes declared after it, every function their handlers call — however
|
|
55
53
|
* deep, through every `await` and timer — reads it with `getContext()`,
|
|
56
|
-
* without it being passed down; the answer to an error too,
|
|
57
|
-
*
|
|
54
|
+
* without it being passed down; the answer to an error too, a try/catch
|
|
55
|
+
* middleware's included. Give it to `use` before the middlewares whose errors
|
|
58
56
|
* your own middleware answers: what the rest throws is answered inside
|
|
59
57
|
* it, as the route would.
|
|
60
58
|
*
|
package/dist/storage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,SAAS,EAEd,KAAK,KAAK,EACV,KAAK,UAAU,EACf,KAAK,
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,SAAS,EAEd,KAAK,KAAK,EACV,KAAK,UAAU,EACf,KAAK,OAAO,EAEZ,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EAEf,MAAM,aAAa,CAAC;AAErB,uCAAuC;AACvC,MAAM,MAAM,uBAAuB;AAClC,4EAA4E;AAC1E,iBAAiB;AACnB,uIAAuI;GACrI,YAAY,CAAC;AAEhB,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,SAAkB,IAAI,yBAAyB;IAC/C,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;gBAE3B,IAAI,EAAE,uBAAuB;CAQzC;AAcD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,SAAS,MAAM,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG,CAK1E;AAED,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,GAAG,SAAS,MAAM,GAAG,KAAK,KACrD,CAAC,WAAW,GAAG,GAAG,CAAC,GACnB,SAAS,CAEX;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAIlD;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,IAAI,cAAc,GAAG,SAAS,CAEjE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAEpE;AAED;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,CAAC,GAAG,IAAI,UAAU,CACrD,UAAU,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,EACzC,OAAO,CAAC,QAAQ,CAAC,CACjB,GAAG;IACH,sCAAsC;IACtC,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;IAC9B,yCAAyC;IACzC,UAAU,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C,CAAC;AAEF,8FAA8F;AAC9F,MAAM,MAAM,aAAa,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC9D,WAAW,GACX,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAG,cAAc,EAClD,GAAG,QAAQ,EAAE,SAAS,KAAK,EAAE,GAC3B,wBAAwB,CAAC,GAAG,CAAC,CAyB/B"}
|
package/docs/guide.md
CHANGED
|
@@ -37,15 +37,13 @@ context of the request that called it, and never another's.
|
|
|
37
37
|
function contextStorage<App = RegisteredBase>(...uncalled: readonly never[]): ContextStorageMiddleware<App>;
|
|
38
38
|
|
|
39
39
|
// A middleware: it requires `App`'s context of the app that mounts it.
|
|
40
|
-
// `ContextStoragePlugin<App>`, its name in 0.3, is a deprecated alias.
|
|
41
40
|
type ContextStorageMiddleware<App> = Middleware<
|
|
42
41
|
RequiresOf<StoredContext<App>, 'context'>,
|
|
43
42
|
Promise<Response>
|
|
44
|
-
> &
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
43
|
+
> & {
|
|
44
|
+
context(): StoredContext<App>;
|
|
45
|
+
tryContext(): StoredContext<App> | undefined;
|
|
46
|
+
};
|
|
49
47
|
|
|
50
48
|
// What `context()` returns: `App`'s context, or `BaseContext` when `App` is no app
|
|
51
49
|
type StoredContext<App> = [ContextOf<App>] extends [never] ? BaseContext : Mounted<ContextOf<App>>;
|
|
@@ -64,11 +62,11 @@ class ContextStorageError extends Error {
|
|
|
64
62
|
type ContextStorageErrorCode = 'OUTSIDE_REQUEST' | 'NOT_ROUTED';
|
|
65
63
|
```
|
|
66
64
|
|
|
67
|
-
`contextStorage()` returns a middleware: pass it to `app.use`, called — `use(contextStorage)` fails `tsc` with `
|
|
65
|
+
`contextStorage()` returns a middleware: pass it to `app.use`, called — `use(contextStorage)` fails `tsc` with `TS2345`, and throws where it is declared ([troubleshooting](troubleshooting.md#use-argument-1-looks-like-a-factory-contextstorage-call-it-usecontextstorage)). It adds
|
|
68
66
|
nothing to the app's type, but it requires `StoredContext<App>` of the app
|
|
69
67
|
that mounts it: `app.use` on an app that does not give that context is a compile
|
|
70
68
|
error. `BaseContext`, `RequestContext`, `ContextOf`, `RegisteredBase`,
|
|
71
|
-
`Middleware`, `
|
|
69
|
+
`Middleware`, `RequiresOf` and `Mounted` come from `@alxia/core`.
|
|
72
70
|
|
|
73
71
|
| Export | Returns | Where it would have nothing |
|
|
74
72
|
| --- | --- | --- |
|
|
@@ -101,14 +99,12 @@ relative to it:
|
|
|
101
99
|
| the handler of a route the middleware ran on, and everything it calls | the request | the same | the context the handler receives, with what the middlewares added | the same |
|
|
102
100
|
| a middleware that catches an error, after it | the request, with `route` and `error` | the same | the same context | the same |
|
|
103
101
|
| a route declared **before** it, or outside the `group` it is used in | throws `OUTSIDE_REQUEST` | `undefined` | throws `OUTSIDE_REQUEST` | `undefined` |
|
|
104
|
-
| the deprecated `onRequest` and `onResponse` hooks, which run outside the chain | throws `OUTSIDE_REQUEST` | `undefined` | throws `OUTSIDE_REQUEST` | `undefined` |
|
|
105
|
-
| the deprecated `onError` and `onRefusal` hooks, which answer at the route boundary | the request, with `route` and `error` | the same | the same context | the same |
|
|
106
102
|
| a socket's handlers (`open`, `message`, `close`) | throws `OUTSIDE_REQUEST` | `undefined` | throws `OUTSIDE_REQUEST` | `undefined` |
|
|
107
103
|
| startup, a job, a timer started at startup | throws `OUTSIDE_REQUEST` | `undefined` | throws `OUTSIDE_REQUEST` | `undefined` |
|
|
108
104
|
|
|
109
105
|
`contextStorage()` settles `next()`: an error the rest of the chain throws
|
|
110
|
-
is answered inside the store, as the route would answer it, so a
|
|
111
|
-
|
|
106
|
+
is answered inside the store, as the route would answer it, so a
|
|
107
|
+
middleware that reads the context while answering an error still finds it.
|
|
112
108
|
|
|
113
109
|
The two errors carry these messages:
|
|
114
110
|
|
|
@@ -323,7 +319,7 @@ answers needs the context; put it after the observers (`logger`,
|
|
|
323
319
|
|
|
324
320
|
`contextStorage()` is a middleware, not an app: declare routes on the app,
|
|
325
321
|
and pass `contextStorage()` to `app.use` called — the uncalled form is
|
|
326
|
-
[refused](troubleshooting.md#
|
|
322
|
+
[refused](troubleshooting.md#use-argument-1-looks-like-a-factory-contextstorage-call-it-usecontextstorage).
|
|
327
323
|
|
|
328
324
|
## What `AsyncLocalStorage` carries
|
|
329
325
|
|
package/docs/roadmap.md
CHANGED
|
@@ -7,7 +7,7 @@ only number on it. Every release, with each change it made, is in
|
|
|
7
7
|
|
|
8
8
|
## Now
|
|
9
9
|
|
|
10
|
-
- **A middleware, not a plugin (0.4).** `app.use(contextStorage())` opens the store on every request, a 404 included: `getRequestContext()` works in every middleware after it, and an error is answered inside it, so
|
|
10
|
+
- **A middleware, not a plugin (0.4).** `app.use(contextStorage())` opens the store on every request, a 404 included: `getRequestContext()` works in every middleware after it, and an error is answered inside it, so a middleware that catches it still reads the context. `app.plugin(contextStorage())`, the deprecated plugin form, was removed with alxia 0.5: give it to `use`.
|
|
11
11
|
- **Typed by `Register`.** With `@alxia/core`'s `Register` naming the
|
|
12
12
|
base, `contextStorage()` needs no type argument: `context()` reads the
|
|
13
13
|
registered context. Typed either way, the middleware requires that context
|
|
@@ -37,9 +37,9 @@ Nothing scheduled yet.
|
|
|
37
37
|
every `await`, timer and promise, and never another request's.
|
|
38
38
|
- **Typed by the app.** `contextStorage<typeof base>()` gives `context()` and
|
|
39
39
|
`tryContext()` what a route declared next on `base` reads: the values its
|
|
40
|
-
`decorate` and `derive`
|
|
41
|
-
- **The request
|
|
42
|
-
before routing
|
|
40
|
+
`decorate` and `derive` added.
|
|
41
|
+
- **The request before routing.** `getRequestContext()` reads the request
|
|
42
|
+
before routing and in a 404, with the route it
|
|
43
43
|
reached and the error it failed with; `tryGetRequestContext()` returns
|
|
44
44
|
`undefined` outside a request instead of throwing.
|
|
45
45
|
- **Jobs and tests.** `runWithContext(ctx, work)` runs code that reads the
|
|
@@ -47,8 +47,8 @@ Nothing scheduled yet.
|
|
|
47
47
|
- **A refusal that says why.** Where there is no context, `getContext()`
|
|
48
48
|
throws a `ContextStorageError` coded `OUTSIDE_REQUEST` or `NOT_ROUTED`,
|
|
49
49
|
and `tryGetContext()` returns `undefined`.
|
|
50
|
-
`use(contextStorage)`, the factory uncalled, fails `tsc` and throws
|
|
51
|
-
|
|
50
|
+
`use(contextStorage)`, the factory uncalled, fails `tsc` and throws
|
|
51
|
+
where it is declared (since alxia 0.5), rather than answering its routes 500.
|
|
52
52
|
- **Ported from `hono/context-storage`.** One store, and `getContext()`
|
|
53
53
|
read wherever it is called, as Hono's is: code written against one ports
|
|
54
54
|
to the other.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -7,7 +7,7 @@ behaviour that prints nothing, or an error from `tsc`. A
|
|
|
7
7
|
|
|
8
8
|
**Runtime**
|
|
9
9
|
|
|
10
|
-
- [`
|
|
10
|
+
- [`use(): argument 1 looks like a factory (contextStorage): call it, use(contextStorage())`](#use-argument-1-looks-like-a-factory-contextstorage-call-it-usecontextstorage), and `TypeError: contextStorage is a factory: use(contextStorage()), not use(contextStorage)`
|
|
11
11
|
- [`ContextStorageError: getContext(): called outside a request — use tryGetContext(), or runWithContext() in a job or a test`](#contextstorageerror-getcontext-called-outside-a-request--use-trygetcontext-or-runwithcontext-in-a-job-or-a-test)
|
|
12
12
|
- [`ContextStorageError: getContext(): this request reached no route declared after contextStorage() — use it earlier, or getRequestContext()`](#contextstorageerror-getcontext-this-request-reached-no-route-declared-after-contextstorage--use-it-earlier-or-getrequestcontext)
|
|
13
13
|
- [A header set from a timer never reaches the response](#a-header-set-from-a-timer-never-reaches-the-response)
|
|
@@ -22,25 +22,26 @@ behaviour that prints nothing, or an error from `tsc`. A
|
|
|
22
22
|
|
|
23
23
|
## Runtime
|
|
24
24
|
|
|
25
|
-
### `
|
|
25
|
+
### `use(): argument 1 looks like a factory (contextStorage): call it, use(contextStorage())`
|
|
26
26
|
|
|
27
27
|
`tsc` reports the same mistake first:
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
|
-
error
|
|
31
|
-
…
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Type 'BaseContext & Empty' is not assignable to type 'never'.
|
|
30
|
+
error TS2345: Argument of type '<App = Alxia<Empty, "">>(...uncalled: readonly never[]) => ContextStorageMiddleware<App>' is not assignable to parameter of type '…'.
|
|
31
|
+
Type '<App = Alxia<Empty, "">>(...uncalled: readonly never[]) => ContextStorageMiddleware<App>' is not assignable to type '(ctx: never, next: NextFunction) => … & "this looks like a factory given uncalled: call it, as use(cors()) and not use(cors)"'.
|
|
32
|
+
Types of parameters 'uncalled' and 'next' are incompatible.
|
|
33
|
+
Type 'NextFunction' is not assignable to type 'never'.
|
|
35
34
|
```
|
|
36
35
|
|
|
37
|
-
**When:**
|
|
38
|
-
|
|
36
|
+
**When:** `.use(contextStorage)`, the factory given to `app.use` without
|
|
37
|
+
being called. It throws where the app is declared, since alxia 0.5; before,
|
|
38
|
+
each request the routes after it answered was a 500, with
|
|
39
|
+
`TypeError: contextStorage is a factory: use(contextStorage()), not use(contextStorage)`
|
|
40
|
+
in the server log, which a factory run past `use` still throws.
|
|
39
41
|
|
|
40
|
-
**Why:** `
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
argument instead.
|
|
42
|
+
**Why:** `contextStorage` is marked as a factory, so `use`, a route and
|
|
43
|
+
`plugin` refuse it. Run as a middleware, it would make a new middleware
|
|
44
|
+
each time and store nothing.
|
|
44
45
|
|
|
45
46
|
**Fix:** call it, once, and keep the result:
|
|
46
47
|
|
|
@@ -63,8 +64,7 @@ and `getRequestContext()` all throw it.
|
|
|
63
64
|
and run later by such a timer;
|
|
64
65
|
- in a route declared **before** `use(requestContext)`, or outside the
|
|
65
66
|
`group` it is used in: the middleware never ran on that request;
|
|
66
|
-
- in a middleware declared before it,
|
|
67
|
-
`onResponse` hooks, which run outside the chain;
|
|
67
|
+
- in a middleware declared before it, which runs outside the store;
|
|
68
68
|
- in a WebSocket's `open`, `message` or `close`, since a socket's handlers
|
|
69
69
|
run outside the chain;
|
|
70
70
|
- with two copies of `@alxia/context-storage` installed: each has its own
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alxia/context-storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "The request's context anywhere it runs — a service, a repository, a logger — through AsyncLocalStorage: alxia's hono/context-storage, typed by the app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
]
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@alxia/core": "^0.
|
|
44
|
+
"@alxia/core": "^0.5.0",
|
|
45
45
|
"@types/bun": "^1.4.2",
|
|
46
46
|
"zod": "^4.6.5"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@alxia/core": "^0.
|
|
49
|
+
"@alxia/core": "^0.5.0",
|
|
50
50
|
"typescript": "^6.0.3 || ^7.0.0"
|
|
51
51
|
}
|
|
52
52
|
}
|