@superbee/server 0.1.6 → 0.2.0-pre.10
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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/legacy-router.d.ts +11 -2
- package/dist/legacy-router.d.ts.map +1 -1
- package/dist/legacy-router.js +10 -6
- package/dist/legacy-router.js.map +1 -1
- package/dist/operation-outcomes.d.ts +98 -0
- package/dist/operation-outcomes.d.ts.map +1 -0
- package/dist/operation-outcomes.js +122 -0
- package/dist/operation-outcomes.js.map +1 -0
- package/dist/router.d.ts +32 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +448 -24
- package/dist/router.js.map +1 -1
- package/dist/serve.d.ts +12 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +24 -4
- package/dist/serve.js.map +1 -1
- package/package.json +7 -3
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
10
|
export { createRouter, createRouterForBackend } from "./legacy-router.js";
|
|
11
|
+
export type { LegacyRouterOptions } from "./legacy-router.js";
|
|
12
|
+
export { MemoryOperationOutcomeStore } from "./operation-outcomes.js";
|
|
13
|
+
export type { MemoryOperationOutcomeStoreOptions, OperationClaim, OperationOutcomeStore, RecordedOperation, RecordedOutcome, RecordedResponse, } from "./operation-outcomes.js";
|
|
11
14
|
export { WIRE_ENDPOINTS } from "./router.js";
|
|
12
15
|
export { RequestBodyTooLargeError, serve, requestFromIncomingMessage, writeResponseToServerResponse, } from "./serve.js";
|
|
13
16
|
export type { RequestAdapterOptions, ServeOptions, ServerHandle, } from "./serve.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,qBAAqB,EACrB,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,YAAY,EACV,kCAAkC,EAClC,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,qBAAqB,EACrB,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
10
|
export { createRouter, createRouterForBackend } from "./legacy-router.js";
|
|
11
|
+
export { MemoryOperationOutcomeStore } from "./operation-outcomes.js";
|
|
11
12
|
export { WIRE_ENDPOINTS } from "./router.js";
|
|
12
13
|
export { RequestBodyTooLargeError, serve, requestFromIncomingMessage, writeResponseToServerResponse, } from "./serve.js";
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAStE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,wBAAwB,EACxB,KAAK,EACL,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,YAAY,CAAC"}
|
package/dist/legacy-router.d.ts
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/** Node-specific compatibility adapter for the historical single-backend package root. */
|
|
2
2
|
import { type Bundle, type StorageBackend } from "@superbee/core";
|
|
3
|
+
import { type OperationOutcomeStore } from "./operation-outcomes.js";
|
|
4
|
+
export interface LegacyRouterOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The store behind identified document writes. Omitted, the reference router gets a fresh
|
|
7
|
+
* in-memory store so identity works out of the box; `null` builds a router without one, which
|
|
8
|
+
* refuses identity and reports `operations: false`.
|
|
9
|
+
*/
|
|
10
|
+
outcomes?: OperationOutcomeStore | null;
|
|
11
|
+
}
|
|
3
12
|
/** Historical explicit-backend entry point retained at the Node package root. */
|
|
4
|
-
export declare function createRouterForBackend(backend: StorageBackend): (request: Request) => Promise<Response>;
|
|
13
|
+
export declare function createRouterForBackend(backend: StorageBackend, options?: LegacyRouterOptions): (request: Request) => Promise<Response>;
|
|
5
14
|
/** Historical Bundle entry point retained at the Node package root, including `/bundles/default`. */
|
|
6
|
-
export declare function createRouter(bundle: Bundle): (request: Request) => Promise<Response>;
|
|
15
|
+
export declare function createRouter(bundle: Bundle, options?: LegacyRouterOptions): (request: Request) => Promise<Response>;
|
|
7
16
|
//# sourceMappingURL=legacy-router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy-router.d.ts","sourceRoot":"","sources":["../src/legacy-router.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,
|
|
1
|
+
{"version":3,"file":"legacy-router.d.ts","sourceRoot":"","sources":["../src/legacy-router.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,EAEL,KAAK,MAAM,EACX,KAAK,cAAc,EAEpB,MAAM,gBAAgB,CAAC;AAIxB,OAAO,EAA+B,KAAK,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAQlG,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACzC;AA4CD,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,GAAE,mBAAwB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAE1I;AAED,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAEvH"}
|
package/dist/legacy-router.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Node-specific compatibility adapter for the historical single-backend package root. */
|
|
2
|
-
import {
|
|
2
|
+
import { MemoryBackend, } from "@superbee/core";
|
|
3
|
+
import { backendFor } from "@superbee/core/bundle-ops";
|
|
4
|
+
import { MemoryOperationOutcomeStore } from "./operation-outcomes.js";
|
|
3
5
|
import { createRouter as createWorkerRouter, } from "./router.js";
|
|
4
6
|
const LEGACY_INTERNAL_BUNDLE_ID = "bnd_00000000000000000000000000000000";
|
|
5
7
|
function capabilitiesForBackend(backend) {
|
|
@@ -30,19 +32,21 @@ function canonicalizeLegacyBundleRoute(request) {
|
|
|
30
32
|
url.pathname = `/v0/bundles/${LEGACY_INTERNAL_BUNDLE_ID}/${match[2]}`;
|
|
31
33
|
return new Request(url, request);
|
|
32
34
|
}
|
|
33
|
-
function buildLegacyRouter(backend) {
|
|
35
|
+
function buildLegacyRouter(backend, options) {
|
|
36
|
+
const outcomes = options.outcomes === undefined ? new MemoryOperationOutcomeStore() : options.outcomes;
|
|
34
37
|
const workerRouter = createWorkerRouter({
|
|
35
38
|
capabilities: capabilitiesForBackend(backend),
|
|
39
|
+
...(outcomes === null ? {} : { outcomes }),
|
|
36
40
|
resolveContext: (request) => ({ backend, attribution: legacyAttribution(request) }),
|
|
37
41
|
});
|
|
38
42
|
return (request) => workerRouter(canonicalizeLegacyBundleRoute(request));
|
|
39
43
|
}
|
|
40
44
|
/** Historical explicit-backend entry point retained at the Node package root. */
|
|
41
|
-
export function createRouterForBackend(backend) {
|
|
42
|
-
return buildLegacyRouter(backend);
|
|
45
|
+
export function createRouterForBackend(backend, options = {}) {
|
|
46
|
+
return buildLegacyRouter(backend, options);
|
|
43
47
|
}
|
|
44
48
|
/** Historical Bundle entry point retained at the Node package root, including `/bundles/default`. */
|
|
45
|
-
export function createRouter(bundle) {
|
|
46
|
-
return buildLegacyRouter(
|
|
49
|
+
export function createRouter(bundle, options = {}) {
|
|
50
|
+
return buildLegacyRouter(backendFor(bundle), options);
|
|
47
51
|
}
|
|
48
52
|
//# sourceMappingURL=legacy-router.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy-router.js","sourceRoot":"","sources":["../src/legacy-router.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"legacy-router.js","sourceRoot":"","sources":["../src/legacy-router.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,EACL,aAAa,GAId,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAAE,2BAA2B,EAA8B,MAAM,yBAAyB,CAAC;AAClG,OAAO,EACL,YAAY,IAAI,kBAAkB,GAEnC,MAAM,aAAa,CAAC;AAErB,MAAM,yBAAyB,GAAG,sCAAsC,CAAC;AAWzE,SAAS,sBAAsB,CAAC,OAAuB;IACrD,OAAO,CACL,OAAO,CAAC,YAAY,EAAE,EAAE,IAAI;QAC1B,YAAY,EAAE,OAAO,YAAY,aAAa;QAC9C,KAAK,EAAE,IAAI;QACX,WAAW,EAAE,IAAI;QACjB,SAAS,EAAE,KAAK;KACjB,CACF,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAgB;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC;IACrD,OAAO;QACL,KAAK,EAAE,KAAK,IAAI,SAAS;QACzB,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,6BAA6B,CAAC,OAAgB;IACrD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClE,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC;IAC3B,GAAG,CAAC,QAAQ,GAAG,eAAe,yBAAyB,IAAI,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC;IACvE,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAuB,EAAE,OAA4B;IAC9E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,2BAA2B,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACvG,MAAM,YAAY,GAAG,kBAAkB,CAAC;QACtC,YAAY,EAAE,sBAAsB,CAAC,OAAO,CAAC;QAC7C,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QAC1C,cAAc,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAAE,CAAC;KACpF,CAAC,CAAC;IACH,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,6BAA6B,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,sBAAsB,CAAC,OAAuB,EAAE,UAA+B,EAAE;IAC/F,OAAO,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,UAA+B,EAAE;IAC5E,OAAO,iBAAiB,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recorded outcomes of identified writes: the store behind the wire's `Idempotency-Key` header
|
|
3
|
+
* and `GET /operations/{key}` route (`docs/WIRE-PROTOCOL.md`, "Identified writes").
|
|
4
|
+
*
|
|
5
|
+
* A key is claimed before its write is applied. The claim answers in one of three ways: the
|
|
6
|
+
* key already has a recorded operation, so the caller replays it; another application of the
|
|
7
|
+
* same key is in progress, so the caller waits for it; or the key is now the caller's, who
|
|
8
|
+
* either records the response it produced or releases the claim when the application threw
|
|
9
|
+
* before any response existed. Recording binds the outcome to the method and document id it
|
|
10
|
+
* was produced for, so a resubmission under the same key that names a different operation can
|
|
11
|
+
* be refused instead of replayed.
|
|
12
|
+
*
|
|
13
|
+
* Keys are scoped: the reference router scopes by canonical bundle id, so two bundles cannot
|
|
14
|
+
* share or collide on a key. The memory store keeps each record for a retention window and
|
|
15
|
+
* forgets it lazily on access, pruning on every record so the map stays bounded by what was
|
|
16
|
+
* written inside one window. The module imports no Node builtin: the Worker-safe router
|
|
17
|
+
* subpath consumes it unchanged.
|
|
18
|
+
*/
|
|
19
|
+
import type { Outcome } from "@superbee/core/storage";
|
|
20
|
+
/** The outcome kinds an authority can record; `unknown` is the client's word, never the store's. */
|
|
21
|
+
export type RecordedOutcome = Exclude<Outcome, {
|
|
22
|
+
kind: "unknown";
|
|
23
|
+
}>;
|
|
24
|
+
/** The parts of a response that replay verbatim to a duplicate submission. */
|
|
25
|
+
export interface RecordedResponse {
|
|
26
|
+
status: number;
|
|
27
|
+
headers: Array<[string, string]>;
|
|
28
|
+
body: string;
|
|
29
|
+
}
|
|
30
|
+
/** One identified write the authority applied, as recorded under its key. */
|
|
31
|
+
export interface RecordedOperation {
|
|
32
|
+
/** The HTTP method the outcome was recorded for. */
|
|
33
|
+
method: string;
|
|
34
|
+
/** The decoded document id the outcome was recorded for. */
|
|
35
|
+
id: string;
|
|
36
|
+
response: RecordedResponse;
|
|
37
|
+
outcome: RecordedOutcome;
|
|
38
|
+
/** Milliseconds since the epoch on the store's clock; retention counts from here. */
|
|
39
|
+
recordedAt: number;
|
|
40
|
+
}
|
|
41
|
+
/** What a caller learns when it claims a key. */
|
|
42
|
+
export type OperationClaim = {
|
|
43
|
+
kind: "recorded";
|
|
44
|
+
operation: RecordedOperation;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "in_progress";
|
|
47
|
+
/** Resolves with the record once the other application records, or `null` if it released instead. */
|
|
48
|
+
settled: Promise<RecordedOperation | null>;
|
|
49
|
+
} | {
|
|
50
|
+
kind: "claimed";
|
|
51
|
+
/**
|
|
52
|
+
* Record the application's response under the key. The router awaits completion before
|
|
53
|
+
* returning it, and does not release the claim if recording fails after application.
|
|
54
|
+
* Durable stores must reconcile a possibly committed mutation before allowing reapplication.
|
|
55
|
+
*/
|
|
56
|
+
record(operation: Omit<RecordedOperation, "recordedAt">): RecordedOperation | Promise<RecordedOperation>;
|
|
57
|
+
/**
|
|
58
|
+
* Settle an application failure before the router returns it. A durable store must check
|
|
59
|
+
* for a committed mutation before making the key available for a fresh application.
|
|
60
|
+
*/
|
|
61
|
+
release(): void | Promise<void>;
|
|
62
|
+
};
|
|
63
|
+
export interface OperationOutcomeStore {
|
|
64
|
+
/** Claim `key` within `scope` ahead of applying the write it identifies. */
|
|
65
|
+
claim(scope: string, key: string): Promise<OperationClaim>;
|
|
66
|
+
/** The record under `key` within `scope`, or `null` when nothing is recorded (never, or no longer). */
|
|
67
|
+
lookup(scope: string, key: string): Promise<RecordedOperation | null>;
|
|
68
|
+
}
|
|
69
|
+
export interface MemoryOperationOutcomeStoreOptions {
|
|
70
|
+
/** How long a record stays answerable after it is recorded. Default 24 hours. */
|
|
71
|
+
retentionMs?: number;
|
|
72
|
+
/** The clock retention is measured on; injectable so a test can expire records deterministically. */
|
|
73
|
+
now?: () => number;
|
|
74
|
+
}
|
|
75
|
+
/** The reference store: one map per process, bounded by the retention window. */
|
|
76
|
+
export declare class MemoryOperationOutcomeStore implements OperationOutcomeStore {
|
|
77
|
+
private readonly recorded;
|
|
78
|
+
private readonly inProgress;
|
|
79
|
+
private readonly retentionMs;
|
|
80
|
+
private readonly now;
|
|
81
|
+
constructor(options?: MemoryOperationOutcomeStoreOptions);
|
|
82
|
+
/** Records currently held, expired ones included until they are next touched. */
|
|
83
|
+
get size(): number;
|
|
84
|
+
claim(scope: string, key: string): Promise<OperationClaim>;
|
|
85
|
+
lookup(scope: string, key: string): Promise<RecordedOperation | null>;
|
|
86
|
+
/** The record at `slot` if it is inside the window; an expired record is dropped on the way. */
|
|
87
|
+
private live;
|
|
88
|
+
private expired;
|
|
89
|
+
/**
|
|
90
|
+
* Drop expired records from the front of the map. Records are inserted in clock order (a
|
|
91
|
+
* record always lands as a fresh key, since `live` removes an expired one before a new claim
|
|
92
|
+
* can be handed out), so the scan stops at the first live record and each record is visited
|
|
93
|
+
* once over its life. A clock that moves backwards can leave an expired record behind a live
|
|
94
|
+
* one; `live` still drops it on its next touch, so correctness never depends on this scan.
|
|
95
|
+
*/
|
|
96
|
+
private prune;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=operation-outcomes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-outcomes.d.ts","sourceRoot":"","sources":["../src/operation-outcomes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEtD,oGAAoG;AACpG,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AAEpE,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,eAAe,CAAC;IACzB,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iDAAiD;AACjD,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,iBAAiB,CAAA;CAAE,GAClD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,qGAAqG;IACrG,OAAO,EAAE,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CAC5C,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzG;;;OAGG;IACH,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,CAAC;AAEN,MAAM,WAAW,qBAAqB;IACpC,4EAA4E;IAC5E,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3D,uGAAuG;IACvG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,kCAAkC;IACjD,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qGAAqG;IACrG,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AASD,iFAAiF;AACjF,qBAAa,2BAA4B,YAAW,qBAAqB;IACvE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IACjE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiC;IAC5D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,OAAO,GAAE,kCAAuC;IAK5D,iFAAiF;IACjF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAEK,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgD1D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAI3E,gGAAgG;IAChG,OAAO,CAAC,IAAI;IAUZ,OAAO,CAAC,OAAO;IAIf;;;;;;OAMG;IACH,OAAO,CAAC,KAAK;CAMd"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recorded outcomes of identified writes: the store behind the wire's `Idempotency-Key` header
|
|
3
|
+
* and `GET /operations/{key}` route (`docs/WIRE-PROTOCOL.md`, "Identified writes").
|
|
4
|
+
*
|
|
5
|
+
* A key is claimed before its write is applied. The claim answers in one of three ways: the
|
|
6
|
+
* key already has a recorded operation, so the caller replays it; another application of the
|
|
7
|
+
* same key is in progress, so the caller waits for it; or the key is now the caller's, who
|
|
8
|
+
* either records the response it produced or releases the claim when the application threw
|
|
9
|
+
* before any response existed. Recording binds the outcome to the method and document id it
|
|
10
|
+
* was produced for, so a resubmission under the same key that names a different operation can
|
|
11
|
+
* be refused instead of replayed.
|
|
12
|
+
*
|
|
13
|
+
* Keys are scoped: the reference router scopes by canonical bundle id, so two bundles cannot
|
|
14
|
+
* share or collide on a key. The memory store keeps each record for a retention window and
|
|
15
|
+
* forgets it lazily on access, pruning on every record so the map stays bounded by what was
|
|
16
|
+
* written inside one window. The module imports no Node builtin: the Worker-safe router
|
|
17
|
+
* subpath consumes it unchanged.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
20
|
+
/** The reference store: one map per process, bounded by the retention window. */
|
|
21
|
+
export class MemoryOperationOutcomeStore {
|
|
22
|
+
recorded = new Map();
|
|
23
|
+
inProgress = new Map();
|
|
24
|
+
retentionMs;
|
|
25
|
+
now;
|
|
26
|
+
constructor(options = {}) {
|
|
27
|
+
this.retentionMs = options.retentionMs ?? DEFAULT_RETENTION_MS;
|
|
28
|
+
this.now = options.now ?? (() => Date.now());
|
|
29
|
+
}
|
|
30
|
+
/** Records currently held, expired ones included until they are next touched. */
|
|
31
|
+
get size() {
|
|
32
|
+
return this.recorded.size;
|
|
33
|
+
}
|
|
34
|
+
async claim(scope, key) {
|
|
35
|
+
// No await precedes the map reads and writes below, so a claim is atomic against every
|
|
36
|
+
// other claim in this process: two concurrent submissions cannot both be handed the key.
|
|
37
|
+
const slot = slotKey(scope, key);
|
|
38
|
+
const existing = this.live(slot);
|
|
39
|
+
if (existing)
|
|
40
|
+
return { kind: "recorded", operation: existing };
|
|
41
|
+
const running = this.inProgress.get(slot);
|
|
42
|
+
if (running)
|
|
43
|
+
return { kind: "in_progress", settled: running.settled };
|
|
44
|
+
let resolve;
|
|
45
|
+
const settled = new Promise((done) => {
|
|
46
|
+
resolve = done;
|
|
47
|
+
});
|
|
48
|
+
const entry = { settled, resolve };
|
|
49
|
+
this.inProgress.set(slot, entry);
|
|
50
|
+
let open = true;
|
|
51
|
+
const assertOpen = () => {
|
|
52
|
+
if (!open)
|
|
53
|
+
throw new Error(`operation outcome for '${key}' was already recorded or released`);
|
|
54
|
+
};
|
|
55
|
+
const finish = (value) => {
|
|
56
|
+
assertOpen();
|
|
57
|
+
open = false;
|
|
58
|
+
if (this.inProgress.get(slot) === entry)
|
|
59
|
+
this.inProgress.delete(slot);
|
|
60
|
+
entry.resolve(value);
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
kind: "claimed",
|
|
64
|
+
record: (operation) => {
|
|
65
|
+
// A settled claim is refused before any map is touched, so a second record cannot land
|
|
66
|
+
// a row the caller was told was rejected. The clock is injected, so it can throw; a
|
|
67
|
+
// record that never lands still gives the key back, or every waiter on this claim would
|
|
68
|
+
// hang and the key would stay unusable until the process restarts.
|
|
69
|
+
assertOpen();
|
|
70
|
+
try {
|
|
71
|
+
const record = { ...operation, recordedAt: this.now() };
|
|
72
|
+
this.prune();
|
|
73
|
+
this.recorded.set(slot, record);
|
|
74
|
+
finish(record);
|
|
75
|
+
return record;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (open)
|
|
79
|
+
finish(null);
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
release: () => finish(null),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async lookup(scope, key) {
|
|
87
|
+
return this.live(slotKey(scope, key));
|
|
88
|
+
}
|
|
89
|
+
/** The record at `slot` if it is inside the window; an expired record is dropped on the way. */
|
|
90
|
+
live(slot) {
|
|
91
|
+
const record = this.recorded.get(slot);
|
|
92
|
+
if (!record)
|
|
93
|
+
return null;
|
|
94
|
+
if (this.expired(record)) {
|
|
95
|
+
this.recorded.delete(slot);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return record;
|
|
99
|
+
}
|
|
100
|
+
expired(record) {
|
|
101
|
+
return this.now() - record.recordedAt >= this.retentionMs;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Drop expired records from the front of the map. Records are inserted in clock order (a
|
|
105
|
+
* record always lands as a fresh key, since `live` removes an expired one before a new claim
|
|
106
|
+
* can be handed out), so the scan stops at the first live record and each record is visited
|
|
107
|
+
* once over its life. A clock that moves backwards can leave an expired record behind a live
|
|
108
|
+
* one; `live` still drops it on its next touch, so correctness never depends on this scan.
|
|
109
|
+
*/
|
|
110
|
+
prune() {
|
|
111
|
+
for (const [slot, record] of this.recorded) {
|
|
112
|
+
if (!this.expired(record))
|
|
113
|
+
break;
|
|
114
|
+
this.recorded.delete(slot);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Scope and key joined on a separator no key can contain (keys are printable ASCII, so no NUL). */
|
|
119
|
+
function slotKey(scope, key) {
|
|
120
|
+
return `${scope}\u0000${key}`;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=operation-outcomes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operation-outcomes.js","sourceRoot":"","sources":["../src/operation-outcomes.ts"],"names":[],"mappings":"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"}
|
package/dist/router.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* endpoint set and any recorded deviations from the draft.
|
|
18
18
|
*/
|
|
19
19
|
import { type BlobKey, type ConceptId, type ReservedFilename, type StorageBackend, type StorageCapabilities } from "@superbee/core/storage";
|
|
20
|
+
import type { OperationOutcomeStore } from "./operation-outcomes.js";
|
|
20
21
|
/** Canonical remote bundle id: 16 random bytes rendered as lowercase hexadecimal. */
|
|
21
22
|
export type BundleId = `bnd_${string}`;
|
|
22
23
|
/** Access needed before a registered endpoint may dispatch. */
|
|
@@ -44,6 +45,18 @@ export declare const WIRE_ENDPOINTS: readonly [{
|
|
|
44
45
|
readonly method: "POST";
|
|
45
46
|
readonly path: "/v0/bundles/{bundle}/docs:read-many";
|
|
46
47
|
readonly accessClass: "read";
|
|
48
|
+
}, {
|
|
49
|
+
readonly id: "docs-heads";
|
|
50
|
+
readonly resource: "docs-heads";
|
|
51
|
+
readonly method: "GET";
|
|
52
|
+
readonly path: "/v0/bundles/{bundle}/heads";
|
|
53
|
+
readonly accessClass: "read";
|
|
54
|
+
}, {
|
|
55
|
+
readonly id: "docs-snapshot";
|
|
56
|
+
readonly resource: "docs-snapshot";
|
|
57
|
+
readonly method: "GET";
|
|
58
|
+
readonly path: "/v0/bundles/{bundle}/snapshot";
|
|
59
|
+
readonly accessClass: "read";
|
|
47
60
|
}, {
|
|
48
61
|
readonly id: "doc-read";
|
|
49
62
|
readonly resource: "doc";
|
|
@@ -116,6 +129,12 @@ export declare const WIRE_ENDPOINTS: readonly [{
|
|
|
116
129
|
readonly method: "DELETE";
|
|
117
130
|
readonly path: "/v0/bundles/{bundle}/blobs/{key...}";
|
|
118
131
|
readonly accessClass: "write";
|
|
132
|
+
}, {
|
|
133
|
+
readonly id: "operation-lookup";
|
|
134
|
+
readonly resource: "operation";
|
|
135
|
+
readonly method: "GET";
|
|
136
|
+
readonly path: "/v0/bundles/{bundle}/operations/{key}";
|
|
137
|
+
readonly accessClass: "write";
|
|
119
138
|
}];
|
|
120
139
|
export type WireEndpointId = (typeof WIRE_ENDPOINTS)[number]["id"];
|
|
121
140
|
export type ResolvedWireResource = {
|
|
@@ -124,6 +143,10 @@ export type ResolvedWireResource = {
|
|
|
124
143
|
kind: "docs";
|
|
125
144
|
} | {
|
|
126
145
|
kind: "docs-read-many";
|
|
146
|
+
} | {
|
|
147
|
+
kind: "docs-heads";
|
|
148
|
+
} | {
|
|
149
|
+
kind: "docs-snapshot";
|
|
127
150
|
} | {
|
|
128
151
|
kind: "doc";
|
|
129
152
|
id: ConceptId;
|
|
@@ -139,6 +162,9 @@ export type ResolvedWireResource = {
|
|
|
139
162
|
} | {
|
|
140
163
|
kind: "blob";
|
|
141
164
|
key: BlobKey;
|
|
165
|
+
} | {
|
|
166
|
+
kind: "operation";
|
|
167
|
+
key: string;
|
|
142
168
|
};
|
|
143
169
|
interface ResolvedWireRouteBase {
|
|
144
170
|
endpointId: WireEndpointId;
|
|
@@ -174,6 +200,12 @@ export interface TrustedRouterContext {
|
|
|
174
200
|
}
|
|
175
201
|
export interface RouterOptions {
|
|
176
202
|
capabilities: StorageCapabilities;
|
|
203
|
+
/**
|
|
204
|
+
* Where identified document writes record their outcomes, scoped by canonical bundle id.
|
|
205
|
+
* Without one the router refuses every request that carries `Idempotency-Key` and the lookup
|
|
206
|
+
* route with `400 USAGE`, and `GET /v0/capabilities` reports `operations: false`.
|
|
207
|
+
*/
|
|
208
|
+
outcomes?: OperationOutcomeStore;
|
|
177
209
|
resolveContext(request: Request, route: ResolvedBundleWireRoute): TrustedRouterContext | Response | Promise<TrustedRouterContext | Response>;
|
|
178
210
|
}
|
|
179
211
|
/** Typed resolution failure retained as a stable host-side rejection seam. */
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|