@amalgm/live 0.2.27 → 0.2.28
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/PURPOSE.md +38 -25
- package/README.md +1 -2
- package/dist/contracts/index.d.ts +0 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +0 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/detection/record-proposal.d.ts +3 -2
- package/dist/detection/record-proposal.d.ts.map +1 -1
- package/dist/detection/record-proposal.js +9 -7
- package/dist/detection/record-proposal.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/machines/index.d.ts +0 -2
- package/dist/machines/index.d.ts.map +1 -1
- package/dist/machines/index.js +0 -2
- package/dist/machines/index.js.map +1 -1
- package/dist/records/authority.d.ts +23 -0
- package/dist/records/authority.d.ts.map +1 -0
- package/dist/records/authority.js +28 -0
- package/dist/records/authority.js.map +1 -0
- package/dist/records/codec.d.ts +6 -0
- package/dist/records/codec.d.ts.map +1 -0
- package/dist/records/codec.js +42 -0
- package/dist/records/codec.js.map +1 -0
- package/dist/records/contracts.d.ts +46 -0
- package/dist/records/contracts.d.ts.map +1 -0
- package/dist/records/contracts.js +114 -0
- package/dist/records/contracts.js.map +1 -0
- package/dist/records/inbox.d.ts +18 -0
- package/dist/records/inbox.d.ts.map +1 -0
- package/dist/records/inbox.js +34 -0
- package/dist/records/inbox.js.map +1 -0
- package/dist/records/index.d.ts +7 -0
- package/dist/records/index.d.ts.map +1 -0
- package/dist/records/index.js +7 -0
- package/dist/records/index.js.map +1 -0
- package/dist/records/outbox.d.ts +20 -0
- package/dist/records/outbox.d.ts.map +1 -0
- package/dist/records/outbox.js +44 -0
- package/dist/records/outbox.js.map +1 -0
- package/dist/registration/user-ground.d.ts +4 -4
- package/dist/registration/user-ground.d.ts.map +1 -1
- package/dist/registration/user-ground.js +8 -3
- package/dist/registration/user-ground.js.map +1 -1
- package/dist-cjs/contracts/binding.js +26 -0
- package/dist-cjs/contracts/binding.js.map +1 -0
- package/dist-cjs/contracts/content.js +99 -0
- package/dist-cjs/contracts/content.js.map +1 -0
- package/dist-cjs/contracts/docs.js +56 -0
- package/dist-cjs/contracts/docs.js.map +1 -0
- package/dist-cjs/contracts/events.js +44 -0
- package/dist-cjs/contracts/events.js.map +1 -0
- package/dist-cjs/contracts/frames/gateway.js +39 -0
- package/dist-cjs/contracts/frames/gateway.js.map +1 -0
- package/dist-cjs/contracts/frames/stream.js +20 -0
- package/dist-cjs/contracts/frames/stream.js.map +1 -0
- package/dist-cjs/contracts/frames/tunnel.js +60 -0
- package/dist-cjs/contracts/frames/tunnel.js.map +1 -0
- package/dist-cjs/contracts/index.js +34 -0
- package/dist-cjs/contracts/index.js.map +1 -0
- package/dist-cjs/contracts/presence.js +38 -0
- package/dist-cjs/contracts/presence.js.map +1 -0
- package/dist-cjs/contracts/resources.js +114 -0
- package/dist-cjs/contracts/resources.js.map +1 -0
- package/dist-cjs/contracts/snapshot.js +31 -0
- package/dist-cjs/contracts/snapshot.js.map +1 -0
- package/dist-cjs/contracts/validate.js +34 -0
- package/dist-cjs/contracts/validate.js.map +1 -0
- package/dist-cjs/detection/adapter-contract.js +168 -0
- package/dist-cjs/detection/adapter-contract.js.map +1 -0
- package/dist-cjs/detection/batch.js +71 -0
- package/dist-cjs/detection/batch.js.map +1 -0
- package/dist-cjs/detection/classify.js +48 -0
- package/dist-cjs/detection/classify.js.map +1 -0
- package/dist-cjs/detection/content.js +122 -0
- package/dist-cjs/detection/content.js.map +1 -0
- package/dist-cjs/detection/continuity.js +88 -0
- package/dist-cjs/detection/continuity.js.map +1 -0
- package/dist-cjs/detection/diff.js +193 -0
- package/dist-cjs/detection/diff.js.map +1 -0
- package/dist-cjs/detection/echo.js +25 -0
- package/dist-cjs/detection/echo.js.map +1 -0
- package/dist-cjs/detection/enrollment.js +189 -0
- package/dist-cjs/detection/enrollment.js.map +1 -0
- package/dist-cjs/detection/index.js +57 -0
- package/dist-cjs/detection/index.js.map +1 -0
- package/dist-cjs/detection/record-proposal.js +170 -0
- package/dist-cjs/detection/record-proposal.js.map +1 -0
- package/dist-cjs/detection/refusal.js +44 -0
- package/dist-cjs/detection/refusal.js.map +1 -0
- package/dist-cjs/detection/repository.js +67 -0
- package/dist-cjs/detection/repository.js.map +1 -0
- package/dist-cjs/detection/territory.js +82 -0
- package/dist-cjs/detection/territory.js.map +1 -0
- package/dist-cjs/detection/watch-boundary.js +51 -0
- package/dist-cjs/detection/watch-boundary.js.map +1 -0
- package/dist-cjs/entities/bytes.js +92 -0
- package/dist-cjs/entities/bytes.js.map +1 -0
- package/dist-cjs/entities/cloud.js +171 -0
- package/dist-cjs/entities/cloud.js.map +1 -0
- package/dist-cjs/entities/index.js +39 -0
- package/dist-cjs/entities/index.js.map +1 -0
- package/dist-cjs/entities/moves.js +85 -0
- package/dist-cjs/entities/moves.js.map +1 -0
- package/dist-cjs/entities/repo-states.js +256 -0
- package/dist-cjs/entities/repo-states.js.map +1 -0
- package/dist-cjs/entities/rules.js +216 -0
- package/dist-cjs/entities/rules.js.map +1 -0
- package/dist-cjs/entities/types.js +44 -0
- package/dist-cjs/entities/types.js.map +1 -0
- package/dist-cjs/files/commands.js +120 -0
- package/dist-cjs/files/commands.js.map +1 -0
- package/dist-cjs/files/index.js +19 -0
- package/dist-cjs/files/index.js.map +1 -0
- package/dist-cjs/index.js +39 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/machines/arbitration.js +41 -0
- package/dist-cjs/machines/arbitration.js.map +1 -0
- package/dist-cjs/machines/backoff.js +38 -0
- package/dist-cjs/machines/backoff.js.map +1 -0
- package/dist-cjs/machines/bootstrap.js +110 -0
- package/dist-cjs/machines/bootstrap.js.map +1 -0
- package/dist-cjs/machines/chunks.js +72 -0
- package/dist-cjs/machines/chunks.js.map +1 -0
- package/dist-cjs/machines/index.js +28 -0
- package/dist-cjs/machines/index.js.map +1 -0
- package/dist-cjs/machines/presence.js +53 -0
- package/dist-cjs/machines/presence.js.map +1 -0
- package/dist-cjs/merge/index.js +25 -0
- package/dist-cjs/merge/index.js.map +1 -0
- package/dist-cjs/merge/merge3.js +191 -0
- package/dist-cjs/merge/merge3.js.map +1 -0
- package/dist-cjs/package.json +1 -0
- package/dist-cjs/records/authority.js +31 -0
- package/dist-cjs/records/authority.js.map +1 -0
- package/dist-cjs/records/codec.js +48 -0
- package/dist-cjs/records/codec.js.map +1 -0
- package/dist-cjs/records/contracts.js +121 -0
- package/dist-cjs/records/contracts.js.map +1 -0
- package/dist-cjs/records/inbox.js +37 -0
- package/dist-cjs/records/inbox.js.map +1 -0
- package/dist-cjs/records/index.js +23 -0
- package/dist-cjs/records/index.js.map +1 -0
- package/dist-cjs/records/outbox.js +48 -0
- package/dist-cjs/records/outbox.js.map +1 -0
- package/dist-cjs/registration/index.js +19 -0
- package/dist-cjs/registration/index.js.map +1 -0
- package/dist-cjs/registration/user-ground.js +183 -0
- package/dist-cjs/registration/user-ground.js.map +1 -0
- package/dist-cjs/transfer/artifacts.js +75 -0
- package/dist-cjs/transfer/artifacts.js.map +1 -0
- package/dist-cjs/transfer/batches.js +44 -0
- package/dist-cjs/transfer/batches.js.map +1 -0
- package/dist-cjs/transfer/download.js +88 -0
- package/dist-cjs/transfer/download.js.map +1 -0
- package/dist-cjs/transfer/index.js +21 -0
- package/dist-cjs/transfer/index.js.map +1 -0
- package/dist-cjs/transfer/parallel.js +35 -0
- package/dist-cjs/transfer/parallel.js.map +1 -0
- package/dist-cjs/transfer/upload.js +161 -0
- package/dist-cjs/transfer/upload.js.map +1 -0
- package/dist-cjs/watching/coverage.js +54 -0
- package/dist-cjs/watching/coverage.js.map +1 -0
- package/dist-cjs/watching/index.js +24 -0
- package/dist-cjs/watching/index.js.map +1 -0
- package/dist-cjs/watching/suspicion.js +105 -0
- package/dist-cjs/watching/suspicion.js.map +1 -0
- package/dist-cjs/workspace/api.js +66 -0
- package/dist-cjs/workspace/api.js.map +1 -0
- package/dist-cjs/workspace/identity.js +32 -0
- package/dist-cjs/workspace/identity.js.map +1 -0
- package/dist-cjs/workspace/index.js +14 -0
- package/dist-cjs/workspace/index.js.map +1 -0
- package/dist-cjs/workspace/memory-store.js +21 -0
- package/dist-cjs/workspace/memory-store.js.map +1 -0
- package/dist-cjs/workspace/registry.js +98 -0
- package/dist-cjs/workspace/registry.js.map +1 -0
- package/dist-cjs/workspace/types.js +3 -0
- package/dist-cjs/workspace/types.js.map +1 -0
- package/package.json +30 -8
- package/dist/contracts/mutation.d.ts +0 -51
- package/dist/contracts/mutation.d.ts.map +0 -1
- package/dist/contracts/mutation.js +0 -55
- package/dist/contracts/mutation.js.map +0 -1
- package/dist/machines/journal.d.ts +0 -63
- package/dist/machines/journal.d.ts.map +0 -1
- package/dist/machines/journal.js +0 -71
- package/dist/machines/journal.js.map +0 -1
- package/dist/machines/ordering.d.ts +0 -53
- package/dist/machines/ordering.d.ts.map +0 -1
- package/dist/machines/ordering.js +0 -87
- package/dist/machines/ordering.js.map +0 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* State events — the unit of change on the local wire.
|
|
4
|
+
*
|
|
5
|
+
* Every change is an event (local-live-protocol.md §1). Tables and disk are
|
|
6
|
+
* truth; the event log is a bounded (~15 min) delivery buffer, so a client
|
|
7
|
+
* that falls off the buffer re-snapshots rather than asking for history.
|
|
8
|
+
*
|
|
9
|
+
* `seq` is a strictly increasing integer per runtime. The two optional
|
|
10
|
+
* Wave 3 fields (`mutationId`, `sharedResourceId`) are additive: legacy
|
|
11
|
+
* rows omit both keys exactly as before, and validation here accepts both
|
|
12
|
+
* generations (axiom: additive-only evolution).
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.isNewerThan = exports.isWireOp = exports.WIRE_OPS = void 0;
|
|
16
|
+
exports.checkStateEvent = checkStateEvent;
|
|
17
|
+
const validate_js_1 = require("./validate.js");
|
|
18
|
+
/** The shipped wire operations. `move`/`delta` (realtime-sdk.md §3) ride as contract-specific patches until a wire rev adds them. */
|
|
19
|
+
exports.WIRE_OPS = ['insert', 'update', 'delete', 'replace', 'invalidate'];
|
|
20
|
+
const EXAMPLE = '{ seq: 42, ts: 1719000000000, resource: "tasks", op: "update", id: "t1", patch: {...} }';
|
|
21
|
+
const isWireOp = (value) => typeof value === 'string' && exports.WIRE_OPS.includes(value);
|
|
22
|
+
exports.isWireOp = isWireOp;
|
|
23
|
+
/**
|
|
24
|
+
* Validate one wire event. Unknown extra keys are preserved untouched —
|
|
25
|
+
* a newer peer may be speaking an additive extension we do not know yet.
|
|
26
|
+
*/
|
|
27
|
+
function checkStateEvent(input) {
|
|
28
|
+
if (!(0, validate_js_1.isRecord)(input))
|
|
29
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `got ${input === null ? 'null' : typeof input}`);
|
|
30
|
+
if (!(0, validate_js_1.isSeq)(input.seq))
|
|
31
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `seq was ${JSON.stringify(input.seq)}, not a non-negative integer`);
|
|
32
|
+
if (typeof input.ts !== 'number')
|
|
33
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `ts was ${JSON.stringify(input.ts)}, not a number`);
|
|
34
|
+
if (!(0, validate_js_1.isNonEmptyString)(input.resource))
|
|
35
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `resource was ${JSON.stringify(input.resource)}, not a name`);
|
|
36
|
+
if (!(0, exports.isWireOp)(input.op)) {
|
|
37
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `op was ${JSON.stringify(input.op)}, not one of ${exports.WIRE_OPS.join('|')}`);
|
|
38
|
+
}
|
|
39
|
+
return (0, validate_js_1.pass)(input);
|
|
40
|
+
}
|
|
41
|
+
/** Events apply in seq order; equal or older seq means already applied (client obligation, protocol §6). */
|
|
42
|
+
const isNewerThan = (event, appliedSeq) => event.seq > appliedSeq;
|
|
43
|
+
exports.isNewerThan = isNewerThan;
|
|
44
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/contracts/events.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAsCH,0CASC;AA7CD,+CAAiG;AAGjG,qIAAqI;AACxH,QAAA,QAAQ,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAU,CAAC;AAuBzF,MAAM,OAAO,GAAG,yFAAyF,CAAC;AAEnG,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAmB,EAAE,CAC1D,OAAO,KAAK,KAAK,QAAQ,IAAK,gBAA8B,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AADlE,QAAA,QAAQ,YAC0D;AAE/E;;;GAGG;AACH,SAAgB,eAAe,CAAC,KAAc;IAC5C,IAAI,CAAC,IAAA,sBAAQ,EAAC,KAAK,CAAC;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IACjG,IAAI,CAAC,IAAA,mBAAK,EAAC,KAAK,CAAC,GAAG,CAAC;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,WAAW,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IACrH,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,UAAU,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAChH,IAAI,CAAC,IAAA,8BAAgB,EAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,gBAAgB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC/H,IAAI,CAAC,IAAA,gBAAQ,EAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;QACxB,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,UAAU,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,gBAAgB,gBAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpG,CAAC;IACD,OAAO,IAAA,kBAAI,EAAC,KAA8B,CAAC,CAAC;AAC9C,CAAC;AAED,4GAA4G;AACrG,MAAM,WAAW,GAAG,CAAC,KAAiB,EAAE,UAAkB,EAAW,EAAE,CAAC,KAAK,CAAC,GAAG,GAAG,UAAU,CAAC;AAAzF,QAAA,WAAW,eAA8E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gateway state socket — browser ⇄ Fly gateway
|
|
4
|
+
* (local-live-protocol.md §4).
|
|
5
|
+
*
|
|
6
|
+
* The barrier rule: in bootstrap mode the gateway MUST NOT send
|
|
7
|
+
* `state.connected` before `state.snapshot`. A browser treats
|
|
8
|
+
* `state.connected` as "delivery confirmed from here on" — and it is
|
|
9
|
+
* re-sent on every transport change, so `transport: 'poll'` is how the
|
|
10
|
+
* client learns it is degraded. Polling is a degraded state, never a
|
|
11
|
+
* destination: a polling session retries the runtime stream and promotes
|
|
12
|
+
* back.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.POLL_INTERVAL_MS = exports.POLL_PROMOTE_INTERVAL_MS = exports.HEARTBEAT_INTERVAL_MS = exports.COMMAND_LIMITS = exports.CAPABILITY_STATE_COMMAND = void 0;
|
|
16
|
+
exports.checkCommand = checkCommand;
|
|
17
|
+
const validate_js_1 = require("../validate.js");
|
|
18
|
+
/** A client uses socket commands only when `state.connected.capabilities` advertises this. */
|
|
19
|
+
exports.CAPABILITY_STATE_COMMAND = 'state.command.v1';
|
|
20
|
+
/** Commands are a small allowlist, validated and bounded — never an arbitrary method/path. */
|
|
21
|
+
exports.COMMAND_LIMITS = { maxBodyBytes: 4 * 1024 * 1024, maxInFlight: 16 };
|
|
22
|
+
exports.HEARTBEAT_INTERVAL_MS = 25_000;
|
|
23
|
+
/** A polling session retries the runtime stream at this cadence (env AMALGM_STATE_STREAM_PROMOTE_MS). */
|
|
24
|
+
exports.POLL_PROMOTE_INTERVAL_MS = 30_000;
|
|
25
|
+
exports.POLL_INTERVAL_MS = 1_000;
|
|
26
|
+
const COMMAND_EXAMPLE = '{ type: "state.command", requestId: "r1", command: "doc.update", payload: { path, updates } }';
|
|
27
|
+
function checkCommand(input) {
|
|
28
|
+
if (!(0, validate_js_1.isRecord)(input) || input.type !== 'state.command') {
|
|
29
|
+
return (0, validate_js_1.failShape)(COMMAND_EXAMPLE, `type was ${(0, validate_js_1.isRecord)(input) ? JSON.stringify(input.type) : typeof input}`);
|
|
30
|
+
}
|
|
31
|
+
if (!(0, validate_js_1.isNonEmptyString)(input.requestId)) {
|
|
32
|
+
return (0, validate_js_1.failShape)(COMMAND_EXAMPLE, `requestId was ${JSON.stringify(input.requestId)}, not a non-empty string`);
|
|
33
|
+
}
|
|
34
|
+
if (!(0, validate_js_1.isNonEmptyString)(input.command)) {
|
|
35
|
+
return (0, validate_js_1.failShape)(COMMAND_EXAMPLE, `command was ${JSON.stringify(input.command)}, not a non-empty string`);
|
|
36
|
+
}
|
|
37
|
+
return (0, validate_js_1.pass)(input);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../src/contracts/frames/gateway.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAuDH,oCAWC;AAhED,gDAA2F;AAI3F,8FAA8F;AACjF,QAAA,wBAAwB,GAAG,kBAAkB,CAAC;AAE3D,8FAA8F;AACjF,QAAA,cAAc,GAAG,EAAE,YAAY,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,WAAW,EAAE,EAAE,EAAW,CAAC;AAE7E,QAAA,qBAAqB,GAAG,MAAM,CAAC;AAC5C,yGAAyG;AAC5F,QAAA,wBAAwB,GAAG,MAAM,CAAC;AAClC,QAAA,gBAAgB,GAAG,KAAK,CAAC;AAsCtC,MAAM,eAAe,GAAG,+FAA+F,CAAC;AAExH,SAAgB,YAAY,CAAC,KAAc;IACzC,IAAI,CAAC,IAAA,sBAAQ,EAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QACvD,OAAO,IAAA,uBAAS,EAAC,eAAe,EAAE,YAAY,IAAA,sBAAQ,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,CAAC,IAAA,8BAAgB,EAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,OAAO,IAAA,uBAAS,EAAC,eAAe,EAAE,iBAAiB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;IAChH,CAAC;IACD,IAAI,CAAC,IAAA,8BAAgB,EAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,OAAO,IAAA,uBAAS,EAAC,eAAe,EAAE,eAAe,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,IAAA,kBAAI,EAAC,KAAoC,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime SSE stream frames — `GET /state/stream`
|
|
4
|
+
* (local-live-protocol.md §3).
|
|
5
|
+
*
|
|
6
|
+
* Two modes share one endpoint. Legacy: hello comment → backlog replay →
|
|
7
|
+
* live events, with the snapshot fetched separately (the race that creates
|
|
8
|
+
* is closed client-side by the full-snapshot barrier). Ordered-bootstrap
|
|
9
|
+
* (`bootstrap=1&resources=…`): the server registers the listener FIRST,
|
|
10
|
+
* then sends the snapshot as the first data frame, then flushes buffered
|
|
11
|
+
* events past its seq — the snapshot-buries-event race is impossible by
|
|
12
|
+
* construction because everything travels ordered on one channel.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.BOOTSTRAP_SNAPSHOT_TIMEOUT_MS = exports.PING_INTERVAL_MS = void 0;
|
|
16
|
+
/** Sent every 25 seconds. */
|
|
17
|
+
exports.PING_INTERVAL_MS = 25_000;
|
|
18
|
+
/** A bootstrap client falls back to the legacy path when no snapshot frame arrives in time. */
|
|
19
|
+
exports.BOOTSTRAP_SNAPSHOT_TIMEOUT_MS = 5_000;
|
|
20
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../../src/contracts/frames/stream.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AAgBH,6BAA6B;AAChB,QAAA,gBAAgB,GAAG,MAAM,CAAC;AACvC,+FAA+F;AAClF,QAAA,6BAA6B,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tunnel frames — runtime ⇄ gateway, protocol v2
|
|
4
|
+
* (local-live-protocol.md §5).
|
|
5
|
+
*
|
|
6
|
+
* One outbound WebSocket per runtime. v2 is negotiated per connection: the
|
|
7
|
+
* runtime's hello carries `protocol_version: 2`; no `hello_ack` means the
|
|
8
|
+
* peer is v1 and only v1 frames may be sent. Slot arbitration is
|
|
9
|
+
* hello-driven and identity-based — the decision function itself lives in
|
|
10
|
+
* `machines/arbitration.ts`; this file owns the wire shapes and constants
|
|
11
|
+
* both sides must agree on.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.hasUsableIdentity = exports.CHUNKING = exports.BACKPRESSURE = exports.CLOSE_CODES = exports.CAPABILITY_SHARED_MUTATION = exports.HELLO_INTERVAL_MS = exports.PROTOCOL_VERSION = void 0;
|
|
15
|
+
exports.checkHello = checkHello;
|
|
16
|
+
const validate_js_1 = require("../validate.js");
|
|
17
|
+
exports.PROTOCOL_VERSION = 2;
|
|
18
|
+
/** Sent on connect and every 15s. */
|
|
19
|
+
exports.HELLO_INTERVAL_MS = 15_000;
|
|
20
|
+
/** Advertised in hello acks; only then do upgraded runtimes use additive shared.resource.* / shared.mutation.* frames. */
|
|
21
|
+
exports.CAPABILITY_SHARED_MUTATION = 'shared.mutation.v1';
|
|
22
|
+
exports.CLOSE_CODES = {
|
|
23
|
+
/** The newcomer won the slot; the old connection is fenced before this close. */
|
|
24
|
+
replaced: 4000,
|
|
25
|
+
/** Refused: an older or equal boot_ts lost arbitration. Retry slowly, never stop. */
|
|
26
|
+
supersededInstance: 4009,
|
|
27
|
+
unauthorized: 4401,
|
|
28
|
+
};
|
|
29
|
+
exports.BACKPRESSURE = {
|
|
30
|
+
/** Frames above this queue behind bufferedAmount; control frames always send immediately. */
|
|
31
|
+
controlMaxBytes: 32 * 1024,
|
|
32
|
+
/** High-water mark on the socket buffer (AMALGM_TUNNEL_HWM_BYTES). */
|
|
33
|
+
highWaterMarkBytes: 1024 * 1024,
|
|
34
|
+
/** Bounded bulk queue (AMALGM_TUNNEL_BULK_MAX_BYTES); overflow drops the response as a 503 res frame — never a silent hang. */
|
|
35
|
+
bulkMaxBytes: 64 * 1024 * 1024,
|
|
36
|
+
};
|
|
37
|
+
exports.CHUNKING = {
|
|
38
|
+
/** Raw bodies above this are chunked to v2 peers. */
|
|
39
|
+
thresholdBytes: 256 * 1024,
|
|
40
|
+
/** ≤256KB raw per chunk, 0-based indexes. */
|
|
41
|
+
chunkMaxBytes: 256 * 1024,
|
|
42
|
+
};
|
|
43
|
+
const HELLO_EXAMPLE = '{ type: "hello", instance_id: "a1b2c3d4e5f60718", boot_ts: 1719000000000, runtime_version: "0.1.157", protocol_version: 2, runtime_label: "main" }';
|
|
44
|
+
function checkHello(input) {
|
|
45
|
+
if (!(0, validate_js_1.isRecord)(input) || input.type !== 'hello') {
|
|
46
|
+
return (0, validate_js_1.failShape)(HELLO_EXAMPLE, `type was ${(0, validate_js_1.isRecord)(input) ? JSON.stringify(input.type) : typeof input}`);
|
|
47
|
+
}
|
|
48
|
+
if (!(0, validate_js_1.isNonEmptyString)(input.runtime_label)) {
|
|
49
|
+
return (0, validate_js_1.failShape)(HELLO_EXAMPLE, `runtime_label was ${JSON.stringify(input.runtime_label)}, not a non-empty string`);
|
|
50
|
+
}
|
|
51
|
+
// Identity fields are additive v2: a v1 hello without them is still a valid hello.
|
|
52
|
+
return (0, validate_js_1.pass)(input);
|
|
53
|
+
}
|
|
54
|
+
/** Usable identity for arbitration: both fields present and well-formed. Missing/unusable identity falls back to legacy last-connect-wins. */
|
|
55
|
+
const hasUsableIdentity = (hello) => typeof hello.instance_id === 'string' &&
|
|
56
|
+
/^[0-9a-f]{16}$/.test(hello.instance_id) &&
|
|
57
|
+
typeof hello.boot_ts === 'number' &&
|
|
58
|
+
Number.isFinite(hello.boot_ts);
|
|
59
|
+
exports.hasUsableIdentity = hasUsableIdentity;
|
|
60
|
+
//# sourceMappingURL=tunnel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../../../src/contracts/frames/tunnel.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAgEH,gCASC;AAvED,gDAA2F;AAE9E,QAAA,gBAAgB,GAAG,CAAC,CAAC;AAClC,qCAAqC;AACxB,QAAA,iBAAiB,GAAG,MAAM,CAAC;AAExC,0HAA0H;AAC7G,QAAA,0BAA0B,GAAG,oBAAoB,CAAC;AAElD,QAAA,WAAW,GAAG;IACzB,iFAAiF;IACjF,QAAQ,EAAE,IAAI;IACd,qFAAqF;IACrF,kBAAkB,EAAE,IAAI;IACxB,YAAY,EAAE,IAAI;CACV,CAAC;AAEE,QAAA,YAAY,GAAG;IAC1B,6FAA6F;IAC7F,eAAe,EAAE,EAAE,GAAG,IAAI;IAC1B,sEAAsE;IACtE,kBAAkB,EAAE,IAAI,GAAG,IAAI;IAC/B,+HAA+H;IAC/H,YAAY,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;CACtB,CAAC;AAEE,QAAA,QAAQ,GAAG;IACtB,qDAAqD;IACrD,cAAc,EAAE,GAAG,GAAG,IAAI;IAC1B,6CAA6C;IAC7C,aAAa,EAAE,GAAG,GAAG,IAAI;CACjB,CAAC;AA4BX,MAAM,aAAa,GACjB,oJAAoJ,CAAC;AAEvJ,SAAgB,UAAU,CAAC,KAAc;IACvC,IAAI,CAAC,IAAA,sBAAQ,EAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC/C,OAAO,IAAA,uBAAS,EAAC,aAAa,EAAE,YAAY,IAAA,sBAAQ,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IAC7G,CAAC;IACD,IAAI,CAAC,IAAA,8BAAgB,EAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC3C,OAAO,IAAA,uBAAS,EAAC,aAAa,EAAE,qBAAqB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC;IACtH,CAAC;IACD,mFAAmF;IACnF,OAAO,IAAA,kBAAI,EAAC,KAA+B,CAAC,CAAC;AAC/C,CAAC;AAED,8IAA8I;AACvI,MAAM,iBAAiB,GAAG,CAAC,KAA2B,EAAW,EAAE,CACxE,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ;IACrC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;IACxC,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;IACjC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAJpB,QAAA,iBAAiB,qBAIG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The contracts layer: every wire shape, identity rule, and vocabulary the
|
|
4
|
+
* protocol documents define — as types, guards, and validators. Nothing in
|
|
5
|
+
* here performs I/O or holds state; the state machines live in
|
|
6
|
+
* `../machines`, and the ports a host injects live in `../adapters`.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
20
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
__exportStar(require("./validate.js"), exports);
|
|
24
|
+
__exportStar(require("./resources.js"), exports);
|
|
25
|
+
__exportStar(require("./events.js"), exports);
|
|
26
|
+
__exportStar(require("./snapshot.js"), exports);
|
|
27
|
+
__exportStar(require("./binding.js"), exports);
|
|
28
|
+
__exportStar(require("./presence.js"), exports);
|
|
29
|
+
__exportStar(require("./docs.js"), exports);
|
|
30
|
+
__exportStar(require("./content.js"), exports);
|
|
31
|
+
__exportStar(require("./frames/stream.js"), exports);
|
|
32
|
+
__exportStar(require("./frames/gateway.js"), exports);
|
|
33
|
+
__exportStar(require("./frames/tunnel.js"), exports);
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;AAEH,gDAA8B;AAC9B,iDAA+B;AAC/B,8CAA4B;AAC5B,gDAA8B;AAC9B,+CAA6B;AAC7B,gDAA8B;AAC9B,4CAA0B;AAC1B,+CAA6B;AAC7B,qDAAmC;AACnC,sDAAoC;AACpC,qDAAmC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ephemeral presence — the lane that is allowed to lose data.
|
|
4
|
+
*
|
|
5
|
+
* Presence is authenticated and permission-gated but best-effort
|
|
6
|
+
* (local-live-protocol.md §2, realtime-sdk.md §6): TTL-bound, never
|
|
7
|
+
* journaled, never replayed after a gap, safe to drop under backpressure.
|
|
8
|
+
* Re-publishing IS the heartbeat; absence is the default that publishing
|
|
9
|
+
* re-earns. Identity (`user`) is stamped server-side from the enrolled
|
|
10
|
+
* account and never read from the client body.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.MAX_BLOB_BYTES = exports.SERVER_TTL_MS = exports.PUBLISH_INTERVAL_MS = void 0;
|
|
14
|
+
exports.checkPresenceFrame = checkPresenceFrame;
|
|
15
|
+
const validate_js_1 = require("./validate.js");
|
|
16
|
+
/** Clients re-publish while bound and on every position change. */
|
|
17
|
+
exports.PUBLISH_INTERVAL_MS = 10_000;
|
|
18
|
+
/** The server expires any entry not re-published within this window (10s sweep). */
|
|
19
|
+
exports.SERVER_TTL_MS = 30_000;
|
|
20
|
+
/** The surface-defined opaque position blob is capped at 8KB. */
|
|
21
|
+
exports.MAX_BLOB_BYTES = 8 * 1024;
|
|
22
|
+
const EXAMPLE = '{ resource: "doc:...", peers: [{ session: "s1", user: "ada", at: 1719000000000 }] }';
|
|
23
|
+
function checkPresenceFrame(input) {
|
|
24
|
+
if (!(0, validate_js_1.isRecord)(input))
|
|
25
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `got ${input === null ? 'null' : typeof input}`);
|
|
26
|
+
if (!(0, validate_js_1.isNonEmptyString)(input.resource)) {
|
|
27
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `resource was ${JSON.stringify(input.resource)}, not a name`);
|
|
28
|
+
}
|
|
29
|
+
if (!Array.isArray(input.peers))
|
|
30
|
+
return (0, validate_js_1.failShape)(EXAMPLE, 'peers was not an array');
|
|
31
|
+
for (const peer of input.peers) {
|
|
32
|
+
if (!(0, validate_js_1.isRecord)(peer) || !(0, validate_js_1.isNonEmptyString)(peer.session) || !(0, validate_js_1.isNonEmptyString)(peer.user)) {
|
|
33
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `peer ${JSON.stringify(peer)} is missing session or user`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return (0, validate_js_1.pass)(input);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=presence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/contracts/presence.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AA2CH,gDAYC;AArDD,+CAA0F;AAE1F,mEAAmE;AACtD,QAAA,mBAAmB,GAAG,MAAM,CAAC;AAC1C,oFAAoF;AACvE,QAAA,aAAa,GAAG,MAAM,CAAC;AACpC,iEAAiE;AACpD,QAAA,cAAc,GAAG,CAAC,GAAG,IAAI,CAAC;AAgCvC,MAAM,OAAO,GAAG,qFAAqF,CAAC;AAEtG,SAAgB,kBAAkB,CAAC,KAAc;IAC/C,IAAI,CAAC,IAAA,sBAAQ,EAAC,KAAK,CAAC;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IACjG,IAAI,CAAC,IAAA,8BAAgB,EAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,gBAAgB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC1F,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,wBAAwB,CAAC,CAAC;IACrF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAA,sBAAQ,EAAC,IAAI,CAAC,IAAI,CAAC,IAAA,8BAAgB,EAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAA,8BAAgB,EAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvF,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IACD,OAAO,IAAA,kBAAI,EAAC,KAAiC,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Resource identity — the names of live channels.
|
|
4
|
+
*
|
|
5
|
+
* A resource is a named channel over the local wire
|
|
6
|
+
* (local-live-protocol.md §1). The shipped grammar:
|
|
7
|
+
*
|
|
8
|
+
* tasks a well-known singleton table
|
|
9
|
+
* app:<appId>:<name> a per-app named resource
|
|
10
|
+
* doc:<base64url(path)> a live document, addressed by local path
|
|
11
|
+
* files:<base64url(dir)> a directory listing channel
|
|
12
|
+
*
|
|
13
|
+
* A shared resource additionally receives a cloud id that never depends on
|
|
14
|
+
* any user's local path (realtime-sdk.md §2); path-derived ids are for
|
|
15
|
+
* unshared local resources only. Renaming a materialized file must not
|
|
16
|
+
* change a shared id — which is exactly why the shared id is not derived
|
|
17
|
+
* from the path.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.appResource = exports.filesResource = exports.docResource = void 0;
|
|
21
|
+
exports.parseResource = parseResource;
|
|
22
|
+
exports.encodeBase64Url = encodeBase64Url;
|
|
23
|
+
exports.decodeBase64Url = decodeBase64Url;
|
|
24
|
+
const validate_js_1 = require("./validate.js");
|
|
25
|
+
/** `doc:<base64url(path)>` for the document at a local path. */
|
|
26
|
+
const docResource = (path) => `doc:${encodeBase64Url(path)}`;
|
|
27
|
+
exports.docResource = docResource;
|
|
28
|
+
/** `files:<base64url(dir)>` for the listing channel of a directory. */
|
|
29
|
+
const filesResource = (dir) => `files:${encodeBase64Url(dir)}`;
|
|
30
|
+
exports.filesResource = filesResource;
|
|
31
|
+
/** `app:<appId>:<name>` for an app-scoped resource. */
|
|
32
|
+
const appResource = (appId, name) => `app:${appId}:${name}`;
|
|
33
|
+
exports.appResource = appResource;
|
|
34
|
+
/**
|
|
35
|
+
* Parse any shipped resource name into its addressed form.
|
|
36
|
+
*
|
|
37
|
+
* parseResource('doc:bm90ZXMubWQ') → { kind: 'doc', path: 'notes.md' }
|
|
38
|
+
* parseResource('app:canvas:board') → { kind: 'app', appId: 'canvas', name: 'board' }
|
|
39
|
+
* parseResource('tasks') → { kind: 'table', name: 'tasks' }
|
|
40
|
+
*/
|
|
41
|
+
function parseResource(resource) {
|
|
42
|
+
if (!(0, validate_js_1.isNonEmptyString)(resource)) {
|
|
43
|
+
return (0, validate_js_1.failShape)(`a resource name like "tasks" or "doc:${encodeBase64Url('notes.md')}"`, `got ${typeof resource}`);
|
|
44
|
+
}
|
|
45
|
+
const [prefix] = resource.split(':', 1);
|
|
46
|
+
switch (prefix) {
|
|
47
|
+
case 'doc': {
|
|
48
|
+
const path = decodeBase64Url(resource.slice('doc:'.length));
|
|
49
|
+
return path === null
|
|
50
|
+
? (0, validate_js_1.failShape)(`"doc:${encodeBase64Url('notes.md')}" (base64url of the path)`, `"${resource}" carries invalid base64url`)
|
|
51
|
+
: (0, validate_js_1.pass)({ kind: 'doc', path });
|
|
52
|
+
}
|
|
53
|
+
case 'files': {
|
|
54
|
+
const dir = decodeBase64Url(resource.slice('files:'.length));
|
|
55
|
+
return dir === null
|
|
56
|
+
? (0, validate_js_1.failShape)(`"files:${encodeBase64Url('notes')}" (base64url of the directory)`, `"${resource}" carries invalid base64url`)
|
|
57
|
+
: (0, validate_js_1.pass)({ kind: 'files', dir });
|
|
58
|
+
}
|
|
59
|
+
case 'app': {
|
|
60
|
+
const rest = resource.slice('app:'.length);
|
|
61
|
+
const divider = rest.indexOf(':');
|
|
62
|
+
if (divider <= 0 || divider === rest.length - 1) {
|
|
63
|
+
return (0, validate_js_1.failShape)('"app:<appId>:<name>", e.g. "app:canvas:board"', `"${resource}" is missing the appId or name segment`);
|
|
64
|
+
}
|
|
65
|
+
return (0, validate_js_1.pass)({ kind: 'app', appId: rest.slice(0, divider), name: rest.slice(divider + 1) });
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
return (0, validate_js_1.pass)({ kind: 'table', name: resource });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
72
|
+
/** UTF-8 → unpadded base64url. Portable: no Buffer, no btoa. */
|
|
73
|
+
function encodeBase64Url(text) {
|
|
74
|
+
const bytes = new TextEncoder().encode(text);
|
|
75
|
+
let out = '';
|
|
76
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
77
|
+
const a = bytes[i];
|
|
78
|
+
const b = bytes[i + 1];
|
|
79
|
+
const c = bytes[i + 2];
|
|
80
|
+
out += BASE64_ALPHABET[a >> 2];
|
|
81
|
+
out += BASE64_ALPHABET[((a & 0x03) << 4) | ((b ?? 0) >> 4)];
|
|
82
|
+
if (b !== undefined)
|
|
83
|
+
out += BASE64_ALPHABET[((b & 0x0f) << 2) | ((c ?? 0) >> 6)];
|
|
84
|
+
if (c !== undefined)
|
|
85
|
+
out += BASE64_ALPHABET[c & 0x3f];
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/** Unpadded base64url → UTF-8, or null when the input is not base64url. */
|
|
90
|
+
function decodeBase64Url(encoded) {
|
|
91
|
+
if (encoded.length % 4 === 1)
|
|
92
|
+
return null;
|
|
93
|
+
const bytes = [];
|
|
94
|
+
let buffer = 0;
|
|
95
|
+
let bits = 0;
|
|
96
|
+
for (const char of encoded) {
|
|
97
|
+
const value = BASE64_ALPHABET.indexOf(char);
|
|
98
|
+
if (value === -1)
|
|
99
|
+
return null;
|
|
100
|
+
buffer = (buffer << 6) | value;
|
|
101
|
+
bits += 6;
|
|
102
|
+
if (bits >= 8) {
|
|
103
|
+
bits -= 8;
|
|
104
|
+
bytes.push((buffer >> bits) & 0xff);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(new Uint8Array(bytes));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=resources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/contracts/resources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Snapshots — the resume point of every recovery path.
|
|
4
|
+
*
|
|
5
|
+
* A snapshot is the full value of each requested resource, consistent as of
|
|
6
|
+
* `seq` when `stable` is true (local-live-protocol.md §1). The builder may
|
|
7
|
+
* race writes; after 3 attempts it serves best-effort data with
|
|
8
|
+
* `stable: false`. Clients may treat an unstable snapshot like a stable one
|
|
9
|
+
* — the live stream repairs staleness — but must never cache it as a
|
|
10
|
+
* consistency proof.
|
|
11
|
+
*
|
|
12
|
+
* Recovery rule: every failure path (seq gap beyond the buffer, `reset`
|
|
13
|
+
* frame, failed backfill) converges on "fetch a snapshot and resume from
|
|
14
|
+
* its seq".
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.checkSnapshot = checkSnapshot;
|
|
18
|
+
const validate_js_1 = require("./validate.js");
|
|
19
|
+
const EXAMPLE = '{ seq: 42, stable: true, resources: { tasks: [...] } }';
|
|
20
|
+
function checkSnapshot(input) {
|
|
21
|
+
if (!(0, validate_js_1.isRecord)(input))
|
|
22
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `got ${input === null ? 'null' : typeof input}`);
|
|
23
|
+
if (!(0, validate_js_1.isSeq)(input.seq))
|
|
24
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `seq was ${JSON.stringify(input.seq)}, not a non-negative integer`);
|
|
25
|
+
if (!(0, validate_js_1.isRecord)(input.resources)) {
|
|
26
|
+
return (0, validate_js_1.failShape)(EXAMPLE, `resources was ${JSON.stringify(input.resources)}, not an object`);
|
|
27
|
+
}
|
|
28
|
+
const stable = input.stable !== false; // absent means stable: the field is an additive escape hatch
|
|
29
|
+
return (0, validate_js_1.pass)({ seq: input.seq, stable, resources: input.resources });
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/contracts/snapshot.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AAcH,sCAQC;AApBD,+CAA+E;AAU/E,MAAM,OAAO,GAAG,wDAAwD,CAAC;AAEzE,SAAgB,aAAa,CAAC,KAAc;IAC1C,IAAI,CAAC,IAAA,sBAAQ,EAAC,KAAK,CAAC;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IACjG,IAAI,CAAC,IAAA,mBAAK,EAAC,KAAK,CAAC,GAAG,CAAC;QAAE,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,WAAW,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IACrH,IAAI,CAAC,IAAA,sBAAQ,EAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAA,uBAAS,EAAC,OAAO,EAAE,iBAAiB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAC/F,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,6DAA6D;IACpG,OAAO,IAAA,kBAAI,EAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared validation vocabulary for every contract in this package.
|
|
4
|
+
*
|
|
5
|
+
* Validators never throw on bad wire data — a peer being wrong is an
|
|
6
|
+
* expected condition, not an exception (local-live-protocol.md: unknown
|
|
7
|
+
* frames mean "the other end is older"). They return a `Checked<T>` so
|
|
8
|
+
* callers must look at the outcome, and every failure message is
|
|
9
|
+
* example-first: it states what a correct value looks like before saying
|
|
10
|
+
* what was wrong (see PURPOSE.md axiom on error shape).
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.isSeq = exports.isNonEmptyString = exports.isRecord = exports.failShape = exports.fail = exports.pass = void 0;
|
|
14
|
+
const pass = (value) => ({ ok: true, value });
|
|
15
|
+
exports.pass = pass;
|
|
16
|
+
const fail = (error) => ({ ok: false, error });
|
|
17
|
+
exports.fail = fail;
|
|
18
|
+
/**
|
|
19
|
+
* Build an example-first failure: `expected` is a correct literal shape,
|
|
20
|
+
* `problem` names the specific field that broke it.
|
|
21
|
+
*
|
|
22
|
+
* failShape('{ seq: 42, resource: "tasks", op: "update" }',
|
|
23
|
+
* 'op was "patch", not one of insert|update|delete|replace|invalidate')
|
|
24
|
+
*/
|
|
25
|
+
const failShape = (expected, problem) => (0, exports.fail)(`expected ${expected} — ${problem}`);
|
|
26
|
+
exports.failShape = failShape;
|
|
27
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
exports.isRecord = isRecord;
|
|
29
|
+
const isNonEmptyString = (value) => typeof value === 'string' && value.length > 0;
|
|
30
|
+
exports.isNonEmptyString = isNonEmptyString;
|
|
31
|
+
/** A strictly increasing wire sequence number: a non-negative safe integer. */
|
|
32
|
+
const isSeq = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
33
|
+
exports.isSeq = isSeq;
|
|
34
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/contracts/validate.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAMI,MAAM,IAAI,GAAG,CAAI,KAAQ,EAAc,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAA1D,QAAA,IAAI,QAAsD;AAEhE,MAAM,IAAI,GAAG,CAAY,KAAa,EAAc,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AAAxE,QAAA,IAAI,QAAoE;AAErF;;;;;;GAMG;AACI,MAAM,SAAS,GAAG,CAAY,QAAgB,EAAE,OAAe,EAAc,EAAE,CACpF,IAAA,YAAI,EAAC,YAAY,QAAQ,MAAM,OAAO,EAAE,CAAC,CAAC;AAD/B,QAAA,SAAS,aACsB;AAErC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CAC3E,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAD1D,QAAA,QAAQ,YACkD;AAEhE,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAmB,EAAE,CAClE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AADnC,QAAA,gBAAgB,oBACmB;AAEhD,+EAA+E;AACxE,MAAM,KAAK,GAAG,CAAC,KAAc,EAAmB,EAAE,CACvD,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAD5D,QAAA,KAAK,SACuD"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The adapter contract: what it means to serve an entity type.
|
|
4
|
+
*
|
|
5
|
+
* An adapter is how a KIND of entity rides the seven realtime rails —
|
|
6
|
+
* watch, detect, record, send, receive, apply, verify. The entity's
|
|
7
|
+
* `type` selects its adapter; the rails themselves never change shape
|
|
8
|
+
* per type. Ratified by entity-model.md ("Type selects the complete
|
|
9
|
+
* adapter cycle") and entity-implementation.md.
|
|
10
|
+
*
|
|
11
|
+
* Axioms:
|
|
12
|
+
*
|
|
13
|
+
* 1. ONE ADAPTER PER TYPE, EVERY TYPE ASSIGNED. The alphabet belongs
|
|
14
|
+
* to the registry; the adapter map covers it exactly — no type
|
|
15
|
+
* unassigned, no type claimed twice. Assigned is not yet
|
|
16
|
+
* operational: operational is earned rail by rail, and absence is
|
|
17
|
+
* honest.
|
|
18
|
+
* 2. ADAPTERS OWN BEHAVIOR; THE REGISTRY OWNS IDENTITY. A lawful type
|
|
19
|
+
* transition that crosses adapters is a handoff on the same UUID,
|
|
20
|
+
* never a rebirth.
|
|
21
|
+
* 3. ONE SOCKET PER RAIL. A rail's callable surfaces are ratified
|
|
22
|
+
* HERE, once, for every adapter (`SOCKETS`). An adapter binds a
|
|
23
|
+
* rail's whole socket or binds the rail not at all; a rail whose
|
|
24
|
+
* socket is not yet ratified cannot be bound by anyone.
|
|
25
|
+
* 4. A TRUTH READ NEVER LIES. Every assigned type names its source of
|
|
26
|
+
* truth in one sentence, and reading it produces exactly one of
|
|
27
|
+
* three honest answers: present, absent, or unsettled.
|
|
28
|
+
* 5. RAILS ARE HONEST. An unbound rail is absent — never stubbed,
|
|
29
|
+
* never defaulted.
|
|
30
|
+
* 6. THE CONTRACT IS FROZEN GROUND. A descriptor is immutable from
|
|
31
|
+
* the moment it loads.
|
|
32
|
+
* 7. THE BASE ORCHESTRATES; THE ADAPTER SUPPLIES TYPE BEHAVIOR. A
|
|
33
|
+
* rail earns a socket only when a real consumer proves that
|
|
34
|
+
* behavior differs by type — never for symmetry.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.referenceAdapter = exports.lookFailed = exports.SOCKETS = exports.RAILS = void 0;
|
|
38
|
+
exports.assertAdapter = assertAdapter;
|
|
39
|
+
exports.buildTypeMap = buildTypeMap;
|
|
40
|
+
/** The seven rails, declared once, frozen, in journey order. */
|
|
41
|
+
exports.RAILS = Object.freeze([
|
|
42
|
+
'watch', 'detect', 'record', 'send', 'receive', 'apply', 'verify',
|
|
43
|
+
]);
|
|
44
|
+
/**
|
|
45
|
+
* The ratified sockets — for each rail that has one, the exact surfaces
|
|
46
|
+
* every binding adapter implements. Same names, same shapes, every
|
|
47
|
+
* type. ONE socket is ratified today (detect), derived from what
|
|
48
|
+
* production detection actually asks of a type. Watch and record earn
|
|
49
|
+
* none at this bar (adjudicated 2026-08-03 under axiom 7); apply and
|
|
50
|
+
* verify join only when the Send/Receive rails exist to derive their
|
|
51
|
+
* socket from. A socket is ratified from a real consumer or it is not
|
|
52
|
+
* ratified at all.
|
|
53
|
+
*/
|
|
54
|
+
exports.SOCKETS = Object.freeze({
|
|
55
|
+
detect: Object.freeze(['truth']),
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Convict a malformed adapter descriptor loudly; return it (typed)
|
|
59
|
+
* unchanged when lawful.
|
|
60
|
+
*/
|
|
61
|
+
function assertAdapter(adapter) {
|
|
62
|
+
const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
63
|
+
const where = isRecord(adapter) && typeof adapter['name'] === 'string' && adapter['name']
|
|
64
|
+
? `adapter ${JSON.stringify(adapter['name'])}` : 'adapter';
|
|
65
|
+
if (!isRecord(adapter) || !Object.isFrozen(adapter)) {
|
|
66
|
+
throw new Error(`${where}: a descriptor is a frozen object`);
|
|
67
|
+
}
|
|
68
|
+
if (typeof adapter['name'] !== 'string' || adapter['name'].length === 0) {
|
|
69
|
+
throw new Error('adapter: name is a non-empty string');
|
|
70
|
+
}
|
|
71
|
+
const { types, rails } = adapter;
|
|
72
|
+
if (!isRecord(types) || !Object.isFrozen(types) || Object.keys(types).length === 0) {
|
|
73
|
+
throw new Error(`${where}: types is a frozen, non-empty map of entity type → declared truth`);
|
|
74
|
+
}
|
|
75
|
+
for (const [type, truth] of Object.entries(types)) {
|
|
76
|
+
if (typeof truth !== 'string' || truth.length === 0) {
|
|
77
|
+
throw new Error(`${where}: type ${JSON.stringify(type)} must declare its source of truth in one sentence`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!isRecord(rails) || !Object.isFrozen(rails)) {
|
|
81
|
+
throw new Error(`${where}: rails is a frozen map (empty is honest; a stub is not)`);
|
|
82
|
+
}
|
|
83
|
+
for (const [rail, surfaces] of Object.entries(rails)) {
|
|
84
|
+
if (!exports.RAILS.includes(rail)) {
|
|
85
|
+
throw new Error(`${where}: ${JSON.stringify(rail)} is not a rail — the rails are ${exports.RAILS.join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
const socket = exports.SOCKETS[rail];
|
|
88
|
+
if (!socket) {
|
|
89
|
+
throw new Error(`${where}: rail ${rail} has no ratified socket yet — a socket is ratified in the contract before anyone binds it`);
|
|
90
|
+
}
|
|
91
|
+
if (!isRecord(surfaces) || !Object.isFrozen(surfaces)) {
|
|
92
|
+
throw new Error(`${where}: rail ${rail} must bind a frozen map of its socket's surfaces`);
|
|
93
|
+
}
|
|
94
|
+
const bound = Object.keys(surfaces).sort();
|
|
95
|
+
if (bound.join(',') !== [...socket].sort().join(',')) {
|
|
96
|
+
throw new Error(`${where}: rail ${rail} binds [${bound.join(', ')}] but its socket is [${socket.join(', ')}] — an adapter implements a rail's whole socket or binds the rail not at all`);
|
|
97
|
+
}
|
|
98
|
+
for (const [surface, fn] of Object.entries(surfaces)) {
|
|
99
|
+
if (typeof fn !== 'function') {
|
|
100
|
+
throw new Error(`${where}: ${rail}.${surface} must be a function — an unbuilt surface is left absent, never stubbed`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return adapter;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the one type → adapter map, convicting every violation of
|
|
108
|
+
* axiom 1: an adapter claiming a type outside the alphabet, two
|
|
109
|
+
* adapters claiming one type, or an alphabet type left unassigned.
|
|
110
|
+
* Pure — callers own where the alphabet and adapters come from.
|
|
111
|
+
*/
|
|
112
|
+
function buildTypeMap(adapters, alphabet) {
|
|
113
|
+
const known = new Set(alphabet);
|
|
114
|
+
const byType = new Map();
|
|
115
|
+
for (const candidate of adapters) {
|
|
116
|
+
const adapter = assertAdapter(candidate);
|
|
117
|
+
for (const type of Object.keys(adapter.types)) {
|
|
118
|
+
if (!known.has(type)) {
|
|
119
|
+
throw new Error(`adapter ${adapter.name} claims ${JSON.stringify(type)}, which is not in the registry's alphabet`);
|
|
120
|
+
}
|
|
121
|
+
const holder = byType.get(type);
|
|
122
|
+
if (holder) {
|
|
123
|
+
throw new Error(`entity type ${JSON.stringify(type)} is claimed by both ${holder.name} and ${adapter.name} — one adapter per type`);
|
|
124
|
+
}
|
|
125
|
+
byType.set(type, adapter);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const type of known) {
|
|
129
|
+
if (!byType.has(type)) {
|
|
130
|
+
throw new Error(`entity type ${JSON.stringify(type)} has no adapter — every type is assigned`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return byType;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The three-state vocabulary's one shared judgment: what a FAILED look
|
|
137
|
+
* at ground means. ENOENT and ENOTDIR are the filesystem itself saying
|
|
138
|
+
* "not there" — honest ABSENCE, a fact. Every other failure means the
|
|
139
|
+
* LOOK failed, not the ground — UNSETTLED: no conclusion, read again.
|
|
140
|
+
*
|
|
141
|
+
* Every truth reader classifies through this one function so no reader
|
|
142
|
+
* can invent its own idea of absence: mistaking "could not look" for
|
|
143
|
+
* "not there" is the exact lie axiom 4 forbids. Only present or absent
|
|
144
|
+
* may produce a conclusion.
|
|
145
|
+
*/
|
|
146
|
+
const lookFailed = (error) => error.code === 'ENOENT' || error.code === 'ENOTDIR'
|
|
147
|
+
? { truth: 'absent' }
|
|
148
|
+
: { truth: 'unsettled', reason: error.code };
|
|
149
|
+
exports.lookFailed = lookFailed;
|
|
150
|
+
/**
|
|
151
|
+
* The managed-reference adapter: Amalgm's OWN link kind, never
|
|
152
|
+
* conflated with a filesystem symlink. Its payload is the target
|
|
153
|
+
* entity's UUID — identity, not an address — and its local rendering as
|
|
154
|
+
* an ordinary symlink is DERIVED state (entity-model.md, symlink
|
|
155
|
+
* section). ASSIGNED, NOT YET OPERATIONAL — and that absence is the
|
|
156
|
+
* honest truth: no rail is bound because the projection lives inside
|
|
157
|
+
* the registration boundary today; its detect socket is ratified from
|
|
158
|
+
* that seam when the projection is extracted, never invented ahead of
|
|
159
|
+
* it.
|
|
160
|
+
*/
|
|
161
|
+
exports.referenceAdapter = Object.freeze({
|
|
162
|
+
name: 'reference',
|
|
163
|
+
types: Object.freeze({
|
|
164
|
+
reference: "its target entity's UUID — identity, not an address; the rendered symlink is derived state",
|
|
165
|
+
}),
|
|
166
|
+
rails: Object.freeze({}),
|
|
167
|
+
});
|
|
168
|
+
//# sourceMappingURL=adapter-contract.js.map
|