@amalgm/live 0.1.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/PURPOSE.md +103 -0
- package/README.md +45 -0
- package/dist/contracts/binding.d.ts +49 -0
- package/dist/contracts/binding.d.ts.map +1 -0
- package/dist/contracts/binding.js +23 -0
- package/dist/contracts/binding.js.map +1 -0
- package/dist/contracts/content.d.ts +42 -0
- package/dist/contracts/content.d.ts.map +1 -0
- package/dist/contracts/content.js +72 -0
- package/dist/contracts/content.js.map +1 -0
- package/dist/contracts/docs.d.ts +64 -0
- package/dist/contracts/docs.d.ts.map +1 -0
- package/dist/contracts/docs.js +51 -0
- package/dist/contracts/docs.js.map +1 -0
- package/dist/contracts/events.d.ts +45 -0
- package/dist/contracts/events.d.ts.map +1 -0
- package/dist/contracts/events.js +38 -0
- package/dist/contracts/events.js.map +1 -0
- package/dist/contracts/frames/gateway.d.ts +83 -0
- package/dist/contracts/frames/gateway.d.ts.map +1 -0
- package/dist/contracts/frames/gateway.js +35 -0
- package/dist/contracts/frames/gateway.js.map +1 -0
- package/dist/contracts/frames/stream.d.ts +59 -0
- package/dist/contracts/frames/stream.d.ts.map +1 -0
- package/dist/contracts/frames/stream.js +17 -0
- package/dist/contracts/frames/stream.js.map +1 -0
- package/dist/contracts/frames/tunnel.d.ts +78 -0
- package/dist/contracts/frames/tunnel.d.ts.map +1 -0
- package/dist/contracts/frames/tunnel.js +55 -0
- package/dist/contracts/frames/tunnel.js.map +1 -0
- package/dist/contracts/index.d.ts +19 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +19 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/mutation.d.ts +51 -0
- package/dist/contracts/mutation.d.ts.map +1 -0
- package/dist/contracts/mutation.js +55 -0
- package/dist/contracts/mutation.js.map +1 -0
- package/dist/contracts/presence.d.ts +46 -0
- package/dist/contracts/presence.d.ts.map +1 -0
- package/dist/contracts/presence.js +34 -0
- package/dist/contracts/presence.js.map +1 -0
- package/dist/contracts/resources.d.ts +52 -0
- package/dist/contracts/resources.d.ts.map +1 -0
- package/dist/contracts/resources.js +105 -0
- package/dist/contracts/resources.js.map +1 -0
- package/dist/contracts/snapshot.d.ts +24 -0
- package/dist/contracts/snapshot.d.ts.map +1 -0
- package/dist/contracts/snapshot.js +28 -0
- package/dist/contracts/snapshot.js.map +1 -0
- package/dist/contracts/validate.d.ts +32 -0
- package/dist/contracts/validate.d.ts.map +1 -0
- package/dist/contracts/validate.js +25 -0
- package/dist/contracts/validate.js.map +1 -0
- package/dist/detection/adapter-contract.d.ts +134 -0
- package/dist/detection/adapter-contract.d.ts.map +1 -0
- package/dist/detection/adapter-contract.js +162 -0
- package/dist/detection/adapter-contract.js.map +1 -0
- package/dist/detection/classify.d.ts +31 -0
- package/dist/detection/classify.d.ts.map +1 -0
- package/dist/detection/classify.js +28 -0
- package/dist/detection/classify.js.map +1 -0
- package/dist/detection/continuity.d.ts +54 -0
- package/dist/detection/continuity.d.ts.map +1 -0
- package/dist/detection/continuity.js +85 -0
- package/dist/detection/continuity.js.map +1 -0
- package/dist/detection/coverage.d.ts +43 -0
- package/dist/detection/coverage.d.ts.map +1 -0
- package/dist/detection/coverage.js +60 -0
- package/dist/detection/coverage.js.map +1 -0
- package/dist/detection/diff.d.ts +144 -0
- package/dist/detection/diff.d.ts.map +1 -0
- package/dist/detection/diff.js +187 -0
- package/dist/detection/diff.js.map +1 -0
- package/dist/detection/enrollment.d.ts +16 -0
- package/dist/detection/enrollment.d.ts.map +1 -0
- package/dist/detection/enrollment.js +70 -0
- package/dist/detection/enrollment.js.map +1 -0
- package/dist/detection/index.d.ts +29 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +29 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/detection/refusal.d.ts +33 -0
- package/dist/detection/refusal.d.ts.map +1 -0
- package/dist/detection/refusal.js +40 -0
- package/dist/detection/refusal.js.map +1 -0
- package/dist/entities/bytes.d.ts +18 -0
- package/dist/entities/bytes.d.ts.map +1 -0
- package/dist/entities/bytes.js +61 -0
- package/dist/entities/bytes.js.map +1 -0
- package/dist/entities/cloud.d.ts +82 -0
- package/dist/entities/cloud.d.ts.map +1 -0
- package/dist/entities/cloud.js +136 -0
- package/dist/entities/cloud.js.map +1 -0
- package/dist/entities/index.d.ts +23 -0
- package/dist/entities/index.d.ts.map +1 -0
- package/dist/entities/index.js +23 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/moves.d.ts +42 -0
- package/dist/entities/moves.d.ts.map +1 -0
- package/dist/entities/moves.js +82 -0
- package/dist/entities/moves.js.map +1 -0
- package/dist/entities/repo-states.d.ts +121 -0
- package/dist/entities/repo-states.d.ts.map +1 -0
- package/dist/entities/repo-states.js +127 -0
- package/dist/entities/repo-states.js.map +1 -0
- package/dist/entities/rules.d.ts +104 -0
- package/dist/entities/rules.d.ts.map +1 -0
- package/dist/entities/rules.js +200 -0
- package/dist/entities/rules.js.map +1 -0
- package/dist/entities/types.d.ts +94 -0
- package/dist/entities/types.d.ts.map +1 -0
- package/dist/entities/types.js +40 -0
- package/dist/entities/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/machines/arbitration.d.ts +36 -0
- package/dist/machines/arbitration.d.ts.map +1 -0
- package/dist/machines/arbitration.js +37 -0
- package/dist/machines/arbitration.js.map +1 -0
- package/dist/machines/backoff.d.ts +36 -0
- package/dist/machines/backoff.d.ts.map +1 -0
- package/dist/machines/backoff.js +34 -0
- package/dist/machines/backoff.js.map +1 -0
- package/dist/machines/bootstrap.d.ts +65 -0
- package/dist/machines/bootstrap.d.ts.map +1 -0
- package/dist/machines/bootstrap.js +105 -0
- package/dist/machines/bootstrap.js.map +1 -0
- package/dist/machines/chunks.d.ts +51 -0
- package/dist/machines/chunks.d.ts.map +1 -0
- package/dist/machines/chunks.js +67 -0
- package/dist/machines/chunks.js.map +1 -0
- package/dist/machines/index.d.ts +14 -0
- package/dist/machines/index.d.ts.map +1 -0
- package/dist/machines/index.js +14 -0
- package/dist/machines/index.js.map +1 -0
- package/dist/machines/journal.d.ts +63 -0
- package/dist/machines/journal.d.ts.map +1 -0
- package/dist/machines/journal.js +71 -0
- package/dist/machines/journal.js.map +1 -0
- package/dist/machines/ordering.d.ts +53 -0
- package/dist/machines/ordering.d.ts.map +1 -0
- package/dist/machines/ordering.js +87 -0
- package/dist/machines/ordering.js.map +1 -0
- package/dist/machines/presence.d.ts +26 -0
- package/dist/machines/presence.d.ts.map +1 -0
- package/dist/machines/presence.js +49 -0
- package/dist/machines/presence.js.map +1 -0
- package/dist/merge/index.d.ts +9 -0
- package/dist/merge/index.d.ts.map +1 -0
- package/dist/merge/index.js +9 -0
- package/dist/merge/index.js.map +1 -0
- package/dist/merge/merge3.d.ts +39 -0
- package/dist/merge/merge3.d.ts.map +1 -0
- package/dist/merge/merge3.js +188 -0
- package/dist/merge/merge3.js.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway state socket — browser ⇄ Fly gateway
|
|
3
|
+
* (local-live-protocol.md §4).
|
|
4
|
+
*
|
|
5
|
+
* The barrier rule: in bootstrap mode the gateway MUST NOT send
|
|
6
|
+
* `state.connected` before `state.snapshot`. A browser treats
|
|
7
|
+
* `state.connected` as "delivery confirmed from here on" — and it is
|
|
8
|
+
* re-sent on every transport change, so `transport: 'poll'` is how the
|
|
9
|
+
* client learns it is degraded. Polling is a degraded state, never a
|
|
10
|
+
* destination: a polling session retries the runtime stream and promotes
|
|
11
|
+
* back.
|
|
12
|
+
*/
|
|
13
|
+
import { type Checked } from '../validate.js';
|
|
14
|
+
import type { StateEvent } from '../events.js';
|
|
15
|
+
import type { ResourceName } from '../resources.js';
|
|
16
|
+
/** A client uses socket commands only when `state.connected.capabilities` advertises this. */
|
|
17
|
+
export declare const CAPABILITY_STATE_COMMAND = "state.command.v1";
|
|
18
|
+
/** Commands are a small allowlist, validated and bounded — never an arbitrary method/path. */
|
|
19
|
+
export declare const COMMAND_LIMITS: {
|
|
20
|
+
readonly maxBodyBytes: number;
|
|
21
|
+
readonly maxInFlight: 16;
|
|
22
|
+
};
|
|
23
|
+
export declare const HEARTBEAT_INTERVAL_MS = 25000;
|
|
24
|
+
/** A polling session retries the runtime stream at this cadence (env AMALGM_STATE_STREAM_PROMOTE_MS). */
|
|
25
|
+
export declare const POLL_PROMOTE_INTERVAL_MS = 30000;
|
|
26
|
+
export declare const POLL_INTERVAL_MS = 1000;
|
|
27
|
+
export type GatewayToBrowser =
|
|
28
|
+
/** Relayed bootstrap snapshot; always precedes state.connected in bootstrap mode. */
|
|
29
|
+
{
|
|
30
|
+
readonly type: 'state.snapshot';
|
|
31
|
+
readonly seq: number;
|
|
32
|
+
readonly resources: Readonly<Record<ResourceName, unknown>>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The delivery barrier: sent only when the runtime stream is confirmed
|
|
36
|
+
* (or poll fallback engaged). Version fields exist only on this
|
|
37
|
+
* gateway/WebSocket path against v2 servers; the client keeps last-known
|
|
38
|
+
* values across reconnects until the next confirmation overwrites them.
|
|
39
|
+
*/
|
|
40
|
+
| {
|
|
41
|
+
readonly type: 'state.connected';
|
|
42
|
+
readonly seq: number;
|
|
43
|
+
readonly transport: 'stream' | 'poll';
|
|
44
|
+
readonly runtimeVersion?: string;
|
|
45
|
+
readonly runtimeInstanceId?: string;
|
|
46
|
+
readonly gatewayVersion?: string;
|
|
47
|
+
readonly capabilities?: readonly string[];
|
|
48
|
+
} | {
|
|
49
|
+
readonly type: 'state.event';
|
|
50
|
+
readonly event: StateEvent;
|
|
51
|
+
} | {
|
|
52
|
+
readonly type: 'state.events';
|
|
53
|
+
readonly events: readonly StateEvent[];
|
|
54
|
+
} | {
|
|
55
|
+
readonly type: 'state.heartbeat';
|
|
56
|
+
readonly t: number;
|
|
57
|
+
}
|
|
58
|
+
/** The client must re-snapshot. */
|
|
59
|
+
| {
|
|
60
|
+
readonly type: 'state.reset';
|
|
61
|
+
}
|
|
62
|
+
/** Advisory, not terminal by itself: the socket stays open unless the gateway follows with a close. */
|
|
63
|
+
| {
|
|
64
|
+
readonly type: 'state.error';
|
|
65
|
+
readonly status?: number;
|
|
66
|
+
readonly message: string;
|
|
67
|
+
}
|
|
68
|
+
/** Acknowledgement from the real runtime handler for a browser command. */
|
|
69
|
+
| {
|
|
70
|
+
readonly type: 'state.command.result';
|
|
71
|
+
readonly requestId: string;
|
|
72
|
+
readonly status: number;
|
|
73
|
+
readonly body: unknown;
|
|
74
|
+
};
|
|
75
|
+
export type BrowserToGateway = {
|
|
76
|
+
readonly type: 'state.command';
|
|
77
|
+
readonly requestId: string;
|
|
78
|
+
/** First command: `doc.update` with `{ path, updates: [base64Yjs] }`. A repeated Yjs update is idempotent, so retrying a lost ack cannot duplicate text. */
|
|
79
|
+
readonly command: string;
|
|
80
|
+
readonly payload: unknown;
|
|
81
|
+
};
|
|
82
|
+
export declare function checkCommand(input: unknown): Checked<BrowserToGateway>;
|
|
83
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../src/contracts/frames/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,OAAO,EAA+C,MAAM,gBAAgB,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAE3D,8FAA8F;AAC9F,eAAO,MAAM,cAAc;;;CAA8D,CAAC;AAE1F,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAC5C,yGAAyG;AACzG,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AAEtC,MAAM,MAAM,gBAAgB;AAC1B,qFAAqF;AACnF;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;CAAE;AACxH;;;;;GAKG;GACD;IACE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,QAAQ,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAA;CAAE,GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE;AAC1D,mCAAmC;GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE;AAClC,uGAAuG;GACrG;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACtF,2EAA2E;GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3H,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4JAA4J;IAC5J,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B,CAAC;AAIF,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAWtE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway state socket — browser ⇄ Fly gateway
|
|
3
|
+
* (local-live-protocol.md §4).
|
|
4
|
+
*
|
|
5
|
+
* The barrier rule: in bootstrap mode the gateway MUST NOT send
|
|
6
|
+
* `state.connected` before `state.snapshot`. A browser treats
|
|
7
|
+
* `state.connected` as "delivery confirmed from here on" — and it is
|
|
8
|
+
* re-sent on every transport change, so `transport: 'poll'` is how the
|
|
9
|
+
* client learns it is degraded. Polling is a degraded state, never a
|
|
10
|
+
* destination: a polling session retries the runtime stream and promotes
|
|
11
|
+
* back.
|
|
12
|
+
*/
|
|
13
|
+
import { pass, failShape, isRecord, isNonEmptyString } from '../validate.js';
|
|
14
|
+
/** A client uses socket commands only when `state.connected.capabilities` advertises this. */
|
|
15
|
+
export const CAPABILITY_STATE_COMMAND = 'state.command.v1';
|
|
16
|
+
/** Commands are a small allowlist, validated and bounded — never an arbitrary method/path. */
|
|
17
|
+
export const COMMAND_LIMITS = { maxBodyBytes: 4 * 1024 * 1024, maxInFlight: 16 };
|
|
18
|
+
export const HEARTBEAT_INTERVAL_MS = 25_000;
|
|
19
|
+
/** A polling session retries the runtime stream at this cadence (env AMALGM_STATE_STREAM_PROMOTE_MS). */
|
|
20
|
+
export const POLL_PROMOTE_INTERVAL_MS = 30_000;
|
|
21
|
+
export const POLL_INTERVAL_MS = 1_000;
|
|
22
|
+
const COMMAND_EXAMPLE = '{ type: "state.command", requestId: "r1", command: "doc.update", payload: { path, updates } }';
|
|
23
|
+
export function checkCommand(input) {
|
|
24
|
+
if (!isRecord(input) || input.type !== 'state.command') {
|
|
25
|
+
return failShape(COMMAND_EXAMPLE, `type was ${isRecord(input) ? JSON.stringify(input.type) : typeof input}`);
|
|
26
|
+
}
|
|
27
|
+
if (!isNonEmptyString(input.requestId)) {
|
|
28
|
+
return failShape(COMMAND_EXAMPLE, `requestId was ${JSON.stringify(input.requestId)}, not a non-empty string`);
|
|
29
|
+
}
|
|
30
|
+
if (!isNonEmptyString(input.command)) {
|
|
31
|
+
return failShape(COMMAND_EXAMPLE, `command was ${JSON.stringify(input.command)}, not a non-empty string`);
|
|
32
|
+
}
|
|
33
|
+
return pass(input);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../src/contracts/frames/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAgB,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAI3F,8FAA8F;AAC9F,MAAM,CAAC,MAAM,wBAAwB,GAAG,kBAAkB,CAAC;AAE3D,8FAA8F;AAC9F,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,YAAY,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,WAAW,EAAE,EAAE,EAAW,CAAC;AAE1F,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAC5C,yGAAyG;AACzG,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAsCtC,MAAM,eAAe,GAAG,+FAA+F,CAAC;AAExH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QACvD,OAAO,SAAS,CAAC,eAAe,EAAE,YAAY,QAAQ,CAAC,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,gBAAgB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,OAAO,SAAS,CAAC,eAAe,EAAE,iBAAiB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;IAChH,CAAC;IACD,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,OAAO,SAAS,CAAC,eAAe,EAAE,eAAe,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,IAAI,CAAC,KAAoC,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime SSE stream frames — `GET /state/stream`
|
|
3
|
+
* (local-live-protocol.md §3).
|
|
4
|
+
*
|
|
5
|
+
* Two modes share one endpoint. Legacy: hello comment → backlog replay →
|
|
6
|
+
* live events, with the snapshot fetched separately (the race that creates
|
|
7
|
+
* is closed client-side by the full-snapshot barrier). Ordered-bootstrap
|
|
8
|
+
* (`bootstrap=1&resources=…`): the server registers the listener FIRST,
|
|
9
|
+
* then sends the snapshot as the first data frame, then flushes buffered
|
|
10
|
+
* events past its seq — the snapshot-buries-event race is impossible by
|
|
11
|
+
* construction because everything travels ordered on one channel.
|
|
12
|
+
*/
|
|
13
|
+
import type { StateEvent } from '../events.js';
|
|
14
|
+
import type { PresenceFrame } from '../presence.js';
|
|
15
|
+
import type { ResourceName } from '../resources.js';
|
|
16
|
+
export interface StreamRequest {
|
|
17
|
+
/** Replay events with seq > after, then go live. Ignored in bootstrap mode — the snapshot IS the resume point. */
|
|
18
|
+
readonly after?: number;
|
|
19
|
+
/** Bootstrap snapshot scope. */
|
|
20
|
+
readonly resources?: readonly ResourceName[];
|
|
21
|
+
readonly bootstrap?: boolean;
|
|
22
|
+
/** Ties this connection to a presence binding session: teardown evicts its presence everywhere. */
|
|
23
|
+
readonly session?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Sent every 25 seconds. */
|
|
26
|
+
export declare const PING_INTERVAL_MS = 25000;
|
|
27
|
+
/** A bootstrap client falls back to the legacy path when no snapshot frame arrives in time. */
|
|
28
|
+
export declare const BOOTSTRAP_SNAPSHOT_TIMEOUT_MS = 5000;
|
|
29
|
+
export type StreamFrame =
|
|
30
|
+
/** First data frame in bootstrap mode; `{ seq, resources }`. */
|
|
31
|
+
{
|
|
32
|
+
readonly type: 'snapshot';
|
|
33
|
+
readonly seq: number;
|
|
34
|
+
readonly resources: Readonly<Record<ResourceName, unknown>>;
|
|
35
|
+
readonly stable?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/** One event per frame. */
|
|
38
|
+
| {
|
|
39
|
+
readonly type: 'state';
|
|
40
|
+
readonly event: StateEvent;
|
|
41
|
+
} | {
|
|
42
|
+
readonly type: 'ping';
|
|
43
|
+
}
|
|
44
|
+
/** Replay window exceeded — the client must re-snapshot. */
|
|
45
|
+
| {
|
|
46
|
+
readonly type: 'reset';
|
|
47
|
+
}
|
|
48
|
+
/** Bootstrap snapshot build failed; the stream ends and the client falls back to the REST path. */
|
|
49
|
+
| {
|
|
50
|
+
readonly type: 'error';
|
|
51
|
+
readonly status?: number;
|
|
52
|
+
readonly error?: string;
|
|
53
|
+
}
|
|
54
|
+
/** Ephemeral peer state — no seq, ignored by legacy clients by construction. */
|
|
55
|
+
| {
|
|
56
|
+
readonly type: 'presence';
|
|
57
|
+
readonly frame: PresenceFrame;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../../src/contracts/frames/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,WAAW,aAAa;IAC5B,kHAAkH;IAClH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,gCAAgC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,mGAAmG;IACnG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,6BAA6B;AAC7B,eAAO,MAAM,gBAAgB,QAAS,CAAC;AACvC,+FAA+F;AAC/F,eAAO,MAAM,6BAA6B,OAAQ,CAAC;AAEnD,MAAM,MAAM,WAAW;AACrB,gEAAgE;AAC9D;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE;AAC7I,2BAA2B;GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE;AAC3B,4DAA4D;GAC1D;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE;AAC5B,mGAAmG;GACjG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE;AAC/E,gFAAgF;GAC9E;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime SSE stream frames — `GET /state/stream`
|
|
3
|
+
* (local-live-protocol.md §3).
|
|
4
|
+
*
|
|
5
|
+
* Two modes share one endpoint. Legacy: hello comment → backlog replay →
|
|
6
|
+
* live events, with the snapshot fetched separately (the race that creates
|
|
7
|
+
* is closed client-side by the full-snapshot barrier). Ordered-bootstrap
|
|
8
|
+
* (`bootstrap=1&resources=…`): the server registers the listener FIRST,
|
|
9
|
+
* then sends the snapshot as the first data frame, then flushes buffered
|
|
10
|
+
* events past its seq — the snapshot-buries-event race is impossible by
|
|
11
|
+
* construction because everything travels ordered on one channel.
|
|
12
|
+
*/
|
|
13
|
+
/** Sent every 25 seconds. */
|
|
14
|
+
export const PING_INTERVAL_MS = 25_000;
|
|
15
|
+
/** A bootstrap client falls back to the legacy path when no snapshot frame arrives in time. */
|
|
16
|
+
export const BOOTSTRAP_SNAPSHOT_TIMEOUT_MS = 5_000;
|
|
17
|
+
//# 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;AAC7B,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AACvC,+FAA+F;AAC/F,MAAM,CAAC,MAAM,6BAA6B,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel frames — runtime ⇄ gateway, protocol v2
|
|
3
|
+
* (local-live-protocol.md §5).
|
|
4
|
+
*
|
|
5
|
+
* One outbound WebSocket per runtime. v2 is negotiated per connection: the
|
|
6
|
+
* runtime's hello carries `protocol_version: 2`; no `hello_ack` means the
|
|
7
|
+
* peer is v1 and only v1 frames may be sent. Slot arbitration is
|
|
8
|
+
* hello-driven and identity-based — the decision function itself lives in
|
|
9
|
+
* `machines/arbitration.ts`; this file owns the wire shapes and constants
|
|
10
|
+
* both sides must agree on.
|
|
11
|
+
*/
|
|
12
|
+
import { type Checked } from '../validate.js';
|
|
13
|
+
export declare const PROTOCOL_VERSION = 2;
|
|
14
|
+
/** Sent on connect and every 15s. */
|
|
15
|
+
export declare const HELLO_INTERVAL_MS = 15000;
|
|
16
|
+
/** Advertised in hello acks; only then do upgraded runtimes use additive shared.resource.* / shared.mutation.* frames. */
|
|
17
|
+
export declare const CAPABILITY_SHARED_MUTATION = "shared.mutation.v1";
|
|
18
|
+
export declare const CLOSE_CODES: {
|
|
19
|
+
/** The newcomer won the slot; the old connection is fenced before this close. */
|
|
20
|
+
readonly replaced: 4000;
|
|
21
|
+
/** Refused: an older or equal boot_ts lost arbitration. Retry slowly, never stop. */
|
|
22
|
+
readonly supersededInstance: 4009;
|
|
23
|
+
readonly unauthorized: 4401;
|
|
24
|
+
};
|
|
25
|
+
export declare const BACKPRESSURE: {
|
|
26
|
+
/** Frames above this queue behind bufferedAmount; control frames always send immediately. */
|
|
27
|
+
readonly controlMaxBytes: number;
|
|
28
|
+
/** High-water mark on the socket buffer (AMALGM_TUNNEL_HWM_BYTES). */
|
|
29
|
+
readonly highWaterMarkBytes: number;
|
|
30
|
+
/** Bounded bulk queue (AMALGM_TUNNEL_BULK_MAX_BYTES); overflow drops the response as a 503 res frame — never a silent hang. */
|
|
31
|
+
readonly bulkMaxBytes: number;
|
|
32
|
+
};
|
|
33
|
+
export declare const CHUNKING: {
|
|
34
|
+
/** Raw bodies above this are chunked to v2 peers. */
|
|
35
|
+
readonly thresholdBytes: number;
|
|
36
|
+
/** ≤256KB raw per chunk, 0-based indexes. */
|
|
37
|
+
readonly chunkMaxBytes: number;
|
|
38
|
+
};
|
|
39
|
+
/** Identity fields are per PROCESS: instance_id is minted once at init, boot_ts is the init timestamp. */
|
|
40
|
+
export interface TunnelHello {
|
|
41
|
+
readonly type: 'hello';
|
|
42
|
+
readonly instance_id: string;
|
|
43
|
+
readonly boot_ts: number;
|
|
44
|
+
readonly runtime_version: string;
|
|
45
|
+
readonly protocol_version: number;
|
|
46
|
+
readonly runtime_label: string;
|
|
47
|
+
readonly branch?: string;
|
|
48
|
+
/** Tunnel-forwarded HTTP targets this port; the MCP port is the fallback when absent. */
|
|
49
|
+
readonly runtime_gateway_port?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface TunnelHelloAck {
|
|
52
|
+
readonly type: 'hello_ack';
|
|
53
|
+
readonly protocol_version: number;
|
|
54
|
+
readonly capabilities?: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
/** Chunked response train: res_start → res_chunk* → res_end. Control frames may interleave — that is the point. */
|
|
57
|
+
export type ChunkedResponseFrame = {
|
|
58
|
+
readonly type: 'res_start';
|
|
59
|
+
readonly req_id: string;
|
|
60
|
+
readonly status: number;
|
|
61
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
62
|
+
readonly body_bytes: number;
|
|
63
|
+
} | {
|
|
64
|
+
readonly type: 'res_chunk';
|
|
65
|
+
readonly req_id: string;
|
|
66
|
+
readonly index: number;
|
|
67
|
+
readonly body_b64: string;
|
|
68
|
+
}
|
|
69
|
+
/** `chunks` must match the count received, else the whole response is a 502. */
|
|
70
|
+
| {
|
|
71
|
+
readonly type: 'res_end';
|
|
72
|
+
readonly req_id: string;
|
|
73
|
+
readonly chunks: number;
|
|
74
|
+
};
|
|
75
|
+
export declare function checkHello(input: unknown): Checked<TunnelHello>;
|
|
76
|
+
/** Usable identity for arbitration: both fields present and well-formed. Missing/unusable identity falls back to legacy last-connect-wins. */
|
|
77
|
+
export declare const hasUsableIdentity: (hello: Partial<TunnelHello>) => boolean;
|
|
78
|
+
//# sourceMappingURL=tunnel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../../src/contracts/frames/tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,OAAO,EAA+C,MAAM,gBAAgB,CAAC;AAE3F,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,qCAAqC;AACrC,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC,0HAA0H;AAC1H,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAE/D,eAAO,MAAM,WAAW;IACtB,iFAAiF;;IAEjF,qFAAqF;;;CAG7E,CAAC;AAEX,eAAO,MAAM,YAAY;IACvB,6FAA6F;;IAE7F,sEAAsE;;IAEtE,+HAA+H;;CAEvH,CAAC;AAEX,eAAO,MAAM,QAAQ;IACnB,qDAAqD;;IAErD,6CAA6C;;CAErC,CAAC;AAEX,0GAA0G;AAC1G,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,mHAAmH;AACnH,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACjK;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AAC5G,gFAAgF;GAC9E;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAKnF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CAS/D;AAED,8IAA8I;AAC9I,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,CAAC,WAAW,CAAC,KAAG,OAIhC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel frames — runtime ⇄ gateway, protocol v2
|
|
3
|
+
* (local-live-protocol.md §5).
|
|
4
|
+
*
|
|
5
|
+
* One outbound WebSocket per runtime. v2 is negotiated per connection: the
|
|
6
|
+
* runtime's hello carries `protocol_version: 2`; no `hello_ack` means the
|
|
7
|
+
* peer is v1 and only v1 frames may be sent. Slot arbitration is
|
|
8
|
+
* hello-driven and identity-based — the decision function itself lives in
|
|
9
|
+
* `machines/arbitration.ts`; this file owns the wire shapes and constants
|
|
10
|
+
* both sides must agree on.
|
|
11
|
+
*/
|
|
12
|
+
import { pass, failShape, isRecord, isNonEmptyString } from '../validate.js';
|
|
13
|
+
export const PROTOCOL_VERSION = 2;
|
|
14
|
+
/** Sent on connect and every 15s. */
|
|
15
|
+
export const HELLO_INTERVAL_MS = 15_000;
|
|
16
|
+
/** Advertised in hello acks; only then do upgraded runtimes use additive shared.resource.* / shared.mutation.* frames. */
|
|
17
|
+
export const CAPABILITY_SHARED_MUTATION = 'shared.mutation.v1';
|
|
18
|
+
export const CLOSE_CODES = {
|
|
19
|
+
/** The newcomer won the slot; the old connection is fenced before this close. */
|
|
20
|
+
replaced: 4000,
|
|
21
|
+
/** Refused: an older or equal boot_ts lost arbitration. Retry slowly, never stop. */
|
|
22
|
+
supersededInstance: 4009,
|
|
23
|
+
unauthorized: 4401,
|
|
24
|
+
};
|
|
25
|
+
export const BACKPRESSURE = {
|
|
26
|
+
/** Frames above this queue behind bufferedAmount; control frames always send immediately. */
|
|
27
|
+
controlMaxBytes: 32 * 1024,
|
|
28
|
+
/** High-water mark on the socket buffer (AMALGM_TUNNEL_HWM_BYTES). */
|
|
29
|
+
highWaterMarkBytes: 1024 * 1024,
|
|
30
|
+
/** Bounded bulk queue (AMALGM_TUNNEL_BULK_MAX_BYTES); overflow drops the response as a 503 res frame — never a silent hang. */
|
|
31
|
+
bulkMaxBytes: 64 * 1024 * 1024,
|
|
32
|
+
};
|
|
33
|
+
export const CHUNKING = {
|
|
34
|
+
/** Raw bodies above this are chunked to v2 peers. */
|
|
35
|
+
thresholdBytes: 256 * 1024,
|
|
36
|
+
/** ≤256KB raw per chunk, 0-based indexes. */
|
|
37
|
+
chunkMaxBytes: 256 * 1024,
|
|
38
|
+
};
|
|
39
|
+
const HELLO_EXAMPLE = '{ type: "hello", instance_id: "a1b2c3d4e5f60718", boot_ts: 1719000000000, runtime_version: "0.1.157", protocol_version: 2, runtime_label: "main" }';
|
|
40
|
+
export function checkHello(input) {
|
|
41
|
+
if (!isRecord(input) || input.type !== 'hello') {
|
|
42
|
+
return failShape(HELLO_EXAMPLE, `type was ${isRecord(input) ? JSON.stringify(input.type) : typeof input}`);
|
|
43
|
+
}
|
|
44
|
+
if (!isNonEmptyString(input.runtime_label)) {
|
|
45
|
+
return failShape(HELLO_EXAMPLE, `runtime_label was ${JSON.stringify(input.runtime_label)}, not a non-empty string`);
|
|
46
|
+
}
|
|
47
|
+
// Identity fields are additive v2: a v1 hello without them is still a valid hello.
|
|
48
|
+
return pass(input);
|
|
49
|
+
}
|
|
50
|
+
/** Usable identity for arbitration: both fields present and well-formed. Missing/unusable identity falls back to legacy last-connect-wins. */
|
|
51
|
+
export const hasUsableIdentity = (hello) => typeof hello.instance_id === 'string' &&
|
|
52
|
+
/^[0-9a-f]{16}$/.test(hello.instance_id) &&
|
|
53
|
+
typeof hello.boot_ts === 'number' &&
|
|
54
|
+
Number.isFinite(hello.boot_ts);
|
|
55
|
+
//# sourceMappingURL=tunnel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../../../src/contracts/frames/tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAgB,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAE3F,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,qCAAqC;AACrC,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAExC,0HAA0H;AAC1H,MAAM,CAAC,MAAM,0BAA0B,GAAG,oBAAoB,CAAC;AAE/D,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,iFAAiF;IACjF,QAAQ,EAAE,IAAI;IACd,qFAAqF;IACrF,kBAAkB,EAAE,IAAI;IACxB,YAAY,EAAE,IAAI;CACV,CAAC;AAEX,MAAM,CAAC,MAAM,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;AAEX,MAAM,CAAC,MAAM,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,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC/C,OAAO,SAAS,CAAC,aAAa,EAAE,YAAY,QAAQ,CAAC,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,gBAAgB,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC,aAAa,EAAE,qBAAqB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC;IACtH,CAAC;IACD,mFAAmF;IACnF,OAAO,IAAI,CAAC,KAA+B,CAAC,CAAC;AAC/C,CAAC;AAED,8IAA8I;AAC9I,MAAM,CAAC,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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contracts layer: every wire shape, identity rule, and vocabulary the
|
|
3
|
+
* protocol documents define — as types, guards, and validators. Nothing in
|
|
4
|
+
* here performs I/O or holds state; the state machines live in
|
|
5
|
+
* `../machines`, and the ports a host injects live in `../adapters`.
|
|
6
|
+
*/
|
|
7
|
+
export * from './validate.js';
|
|
8
|
+
export * from './resources.js';
|
|
9
|
+
export * from './events.js';
|
|
10
|
+
export * from './snapshot.js';
|
|
11
|
+
export * from './mutation.js';
|
|
12
|
+
export * from './binding.js';
|
|
13
|
+
export * from './presence.js';
|
|
14
|
+
export * from './docs.js';
|
|
15
|
+
export * from './content.js';
|
|
16
|
+
export * from './frames/stream.js';
|
|
17
|
+
export * from './frames/gateway.js';
|
|
18
|
+
export * from './frames/tunnel.js';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contracts layer: every wire shape, identity rule, and vocabulary the
|
|
3
|
+
* protocol documents define — as types, guards, and validators. Nothing in
|
|
4
|
+
* here performs I/O or holds state; the state machines live in
|
|
5
|
+
* `../machines`, and the ports a host injects live in `../adapters`.
|
|
6
|
+
*/
|
|
7
|
+
export * from './validate.js';
|
|
8
|
+
export * from './resources.js';
|
|
9
|
+
export * from './events.js';
|
|
10
|
+
export * from './snapshot.js';
|
|
11
|
+
export * from './mutation.js';
|
|
12
|
+
export * from './binding.js';
|
|
13
|
+
export * from './presence.js';
|
|
14
|
+
export * from './docs.js';
|
|
15
|
+
export * from './content.js';
|
|
16
|
+
export * from './frames/stream.js';
|
|
17
|
+
export * from './frames/gateway.js';
|
|
18
|
+
export * from './frames/tunnel.js';
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contracts/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable mutations — one edit, one identity, forever.
|
|
3
|
+
*
|
|
4
|
+
* The hard product invariant (realtime-sdk.md §4, §5): one surface edit
|
|
5
|
+
* creates one logical mutation with one `mutationId`, stable across every
|
|
6
|
+
* retry, journaled before local materialization, and deduplicated by
|
|
7
|
+
* `(resourceId, mutationId)` at every door. Transport may pack, compress,
|
|
8
|
+
* or chunk envelopes — it may never merge identities.
|
|
9
|
+
*
|
|
10
|
+
* After acceptance the authority adds server-derived fields. A
|
|
11
|
+
* client-supplied actor, role, official version, or commit time is never
|
|
12
|
+
* trusted (realtime-sdk.md §5).
|
|
13
|
+
*/
|
|
14
|
+
import { type Checked } from './validate.js';
|
|
15
|
+
export interface LiveMutation<Operation = unknown> {
|
|
16
|
+
readonly resourceId: string;
|
|
17
|
+
/** Fences writes across authority handoffs: a stale epoch is refused. */
|
|
18
|
+
readonly authorityEpoch: number;
|
|
19
|
+
/** Stable across retries. A retry never manufactures a second edit. */
|
|
20
|
+
readonly mutationId: string;
|
|
21
|
+
readonly deviceId: string;
|
|
22
|
+
/** The official per-resource version this edit was built against. */
|
|
23
|
+
readonly baseVersion: number;
|
|
24
|
+
/** Data contract of the operation, e.g. "text-yjs@1". */
|
|
25
|
+
readonly contract: string;
|
|
26
|
+
readonly operation: Operation;
|
|
27
|
+
}
|
|
28
|
+
/** Server-derived facts stamped by the authority on acceptance. */
|
|
29
|
+
export interface AcceptedMutation<Operation = unknown> extends LiveMutation<Operation> {
|
|
30
|
+
readonly actorId: string;
|
|
31
|
+
/** Official per-resource version assigned by the authority. */
|
|
32
|
+
readonly version: number;
|
|
33
|
+
readonly committedAt: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Durable mutation state, separate from connection state
|
|
37
|
+
* (realtime-sdk.md §7). Terminal states preserve local work: `rejected`
|
|
38
|
+
* and `conflicted` never discard the losing side (axiom: never a silent
|
|
39
|
+
* winner).
|
|
40
|
+
*/
|
|
41
|
+
export declare const MUTATION_STATES: readonly ["saving-local", "saved-local", "syncing", "synced", "rejected", "conflicted"];
|
|
42
|
+
export type MutationState = (typeof MUTATION_STATES)[number];
|
|
43
|
+
/**
|
|
44
|
+
* The cloud dedupes both entry doors on this key; repeating an accepted
|
|
45
|
+
* mutation returns the original ack. NUL-separated: ids may contain any
|
|
46
|
+
* printable character, so a printable separator could collide two
|
|
47
|
+
* different identities.
|
|
48
|
+
*/
|
|
49
|
+
export declare const mutationKey: (m: Pick<LiveMutation, "resourceId" | "mutationId">) => string;
|
|
50
|
+
export declare function checkLiveMutation(input: unknown): Checked<LiveMutation>;
|
|
51
|
+
//# sourceMappingURL=mutation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation.d.ts","sourceRoot":"","sources":["../../src/contracts/mutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAsD,MAAM,eAAe,CAAC;AAEjG,MAAM,WAAW,YAAY,CAAC,SAAS,GAAG,OAAO;IAC/C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB,CAAC,SAAS,GAAG,OAAO,CAAE,SAAQ,YAAY,CAAC,SAAS,CAAC;IACpF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,eAAe,yFAOlB,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,GAAG,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,YAAY,CAAC,KAAG,MACzC,CAAC;AAKzC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAevE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable mutations — one edit, one identity, forever.
|
|
3
|
+
*
|
|
4
|
+
* The hard product invariant (realtime-sdk.md §4, §5): one surface edit
|
|
5
|
+
* creates one logical mutation with one `mutationId`, stable across every
|
|
6
|
+
* retry, journaled before local materialization, and deduplicated by
|
|
7
|
+
* `(resourceId, mutationId)` at every door. Transport may pack, compress,
|
|
8
|
+
* or chunk envelopes — it may never merge identities.
|
|
9
|
+
*
|
|
10
|
+
* After acceptance the authority adds server-derived fields. A
|
|
11
|
+
* client-supplied actor, role, official version, or commit time is never
|
|
12
|
+
* trusted (realtime-sdk.md §5).
|
|
13
|
+
*/
|
|
14
|
+
import { pass, failShape, isRecord, isNonEmptyString, isSeq } from './validate.js';
|
|
15
|
+
/**
|
|
16
|
+
* Durable mutation state, separate from connection state
|
|
17
|
+
* (realtime-sdk.md §7). Terminal states preserve local work: `rejected`
|
|
18
|
+
* and `conflicted` never discard the losing side (axiom: never a silent
|
|
19
|
+
* winner).
|
|
20
|
+
*/
|
|
21
|
+
export const MUTATION_STATES = [
|
|
22
|
+
'saving-local', // selected journal is persisting the edit
|
|
23
|
+
'saved-local', // journal contains it; runtime-routed edits are also materialized
|
|
24
|
+
'syncing', // leased/pending in the outbox view
|
|
25
|
+
'synced', // cloud accepted and assigned the official version
|
|
26
|
+
'rejected', // cloud refused (permission/schema); local work preserved
|
|
27
|
+
'conflicted', // contract requires an explicit merge decision; both sides survive
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* The cloud dedupes both entry doors on this key; repeating an accepted
|
|
31
|
+
* mutation returns the original ack. NUL-separated: ids may contain any
|
|
32
|
+
* printable character, so a printable separator could collide two
|
|
33
|
+
* different identities.
|
|
34
|
+
*/
|
|
35
|
+
export const mutationKey = (m) => `${m.resourceId}\u0000${m.mutationId}`;
|
|
36
|
+
const EXAMPLE = '{ resourceId: "r1", authorityEpoch: 1, mutationId: "m-7", deviceId: "d1", baseVersion: 12, contract: "text-yjs@1", operation: {...} }';
|
|
37
|
+
export function checkLiveMutation(input) {
|
|
38
|
+
if (!isRecord(input))
|
|
39
|
+
return failShape(EXAMPLE, `got ${input === null ? 'null' : typeof input}`);
|
|
40
|
+
for (const field of ['resourceId', 'mutationId', 'deviceId', 'contract']) {
|
|
41
|
+
if (!isNonEmptyString(input[field])) {
|
|
42
|
+
return failShape(EXAMPLE, `${field} was ${JSON.stringify(input[field])}, not a non-empty string`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!isSeq(input.authorityEpoch)) {
|
|
46
|
+
return failShape(EXAMPLE, `authorityEpoch was ${JSON.stringify(input.authorityEpoch)}, not a non-negative integer`);
|
|
47
|
+
}
|
|
48
|
+
if (!isSeq(input.baseVersion)) {
|
|
49
|
+
return failShape(EXAMPLE, `baseVersion was ${JSON.stringify(input.baseVersion)}, not a non-negative integer`);
|
|
50
|
+
}
|
|
51
|
+
if (!('operation' in input))
|
|
52
|
+
return failShape(EXAMPLE, 'operation was missing');
|
|
53
|
+
return pass(input);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=mutation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutation.js","sourceRoot":"","sources":["../../src/contracts/mutation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAgB,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAwBjG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,cAAc,EAAE,0CAA0C;IAC1D,aAAa,EAAE,kEAAkE;IACjF,SAAS,EAAE,oCAAoC;IAC/C,QAAQ,EAAE,mDAAmD;IAC7D,UAAU,EAAE,0DAA0D;IACtE,YAAY,EAAE,mEAAmE;CACzE,CAAC;AAGX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAkD,EAAU,EAAE,CACxF,GAAG,CAAC,CAAC,UAAU,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC;AAEzC,MAAM,OAAO,GACX,uIAAuI,CAAC;AAE1I,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IACjG,KAAK,MAAM,KAAK,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,CAAU,EAAE,CAAC;QAClF,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YACpC,OAAO,SAAS,CAAC,OAAO,EAAE,GAAG,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,0BAA0B,CAAC,CAAC;QACpG,CAAC;IACH,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QACjC,OAAO,SAAS,CAAC,OAAO,EAAE,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,8BAA8B,CAAC,CAAC;IACtH,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC,OAAO,EAAE,mBAAmB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC,8BAA8B,CAAC,CAAC;IAChH,CAAC;IACD,IAAI,CAAC,CAAC,WAAW,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC;IAChF,OAAO,IAAI,CAAC,KAAgC,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral presence — the lane that is allowed to lose data.
|
|
3
|
+
*
|
|
4
|
+
* Presence is authenticated and permission-gated but best-effort
|
|
5
|
+
* (local-live-protocol.md §2, realtime-sdk.md §6): TTL-bound, never
|
|
6
|
+
* journaled, never replayed after a gap, safe to drop under backpressure.
|
|
7
|
+
* Re-publishing IS the heartbeat; absence is the default that publishing
|
|
8
|
+
* re-earns. Identity (`user`) is stamped server-side from the enrolled
|
|
9
|
+
* account and never read from the client body.
|
|
10
|
+
*/
|
|
11
|
+
import { type Checked } from './validate.js';
|
|
12
|
+
/** Clients re-publish while bound and on every position change. */
|
|
13
|
+
export declare const PUBLISH_INTERVAL_MS = 10000;
|
|
14
|
+
/** The server expires any entry not re-published within this window (10s sweep). */
|
|
15
|
+
export declare const SERVER_TTL_MS = 30000;
|
|
16
|
+
/** The surface-defined opaque position blob is capped at 8KB. */
|
|
17
|
+
export declare const MAX_BLOB_BYTES: number;
|
|
18
|
+
/** `POST /state/presence` body. `session` is a client-minted binding id; `leave: true` is the clean goodbye. */
|
|
19
|
+
export interface PresencePublish {
|
|
20
|
+
readonly resource: string;
|
|
21
|
+
readonly session: string;
|
|
22
|
+
/** Surface-defined opaque position: a text caret, canvas x/y… */
|
|
23
|
+
readonly blob?: unknown;
|
|
24
|
+
readonly active?: boolean;
|
|
25
|
+
readonly leave?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface PresencePeer {
|
|
28
|
+
readonly session: string;
|
|
29
|
+
/** Stamped server-side; never client-supplied. */
|
|
30
|
+
readonly user: string;
|
|
31
|
+
readonly blob?: unknown;
|
|
32
|
+
readonly at: number;
|
|
33
|
+
readonly activeAt?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The `presence` stream frame: the FULL peer list for one resource.
|
|
37
|
+
* Receivers replace, never merge — removal is implicit (a peer absent from
|
|
38
|
+
* the list is gone). Presence frames carry no seq and never enter the
|
|
39
|
+
* event log, snapshots, or backfill.
|
|
40
|
+
*/
|
|
41
|
+
export interface PresenceFrame {
|
|
42
|
+
readonly resource: string;
|
|
43
|
+
readonly peers: readonly PresencePeer[];
|
|
44
|
+
}
|
|
45
|
+
export declare function checkPresenceFrame(input: unknown): Checked<PresenceFrame>;
|
|
46
|
+
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/contracts/presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,OAAO,EAA+C,MAAM,eAAe,CAAC;AAE1F,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAC1C,oFAAoF;AACpF,eAAO,MAAM,aAAa,QAAS,CAAC;AACpC,iEAAiE;AACjE,eAAO,MAAM,cAAc,QAAW,CAAC;AAEvC,gHAAgH;AAChH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAC;CACzC;AAID,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAYzE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral presence — the lane that is allowed to lose data.
|
|
3
|
+
*
|
|
4
|
+
* Presence is authenticated and permission-gated but best-effort
|
|
5
|
+
* (local-live-protocol.md §2, realtime-sdk.md §6): TTL-bound, never
|
|
6
|
+
* journaled, never replayed after a gap, safe to drop under backpressure.
|
|
7
|
+
* Re-publishing IS the heartbeat; absence is the default that publishing
|
|
8
|
+
* re-earns. Identity (`user`) is stamped server-side from the enrolled
|
|
9
|
+
* account and never read from the client body.
|
|
10
|
+
*/
|
|
11
|
+
import { pass, failShape, isRecord, isNonEmptyString } from './validate.js';
|
|
12
|
+
/** Clients re-publish while bound and on every position change. */
|
|
13
|
+
export const PUBLISH_INTERVAL_MS = 10_000;
|
|
14
|
+
/** The server expires any entry not re-published within this window (10s sweep). */
|
|
15
|
+
export const SERVER_TTL_MS = 30_000;
|
|
16
|
+
/** The surface-defined opaque position blob is capped at 8KB. */
|
|
17
|
+
export const MAX_BLOB_BYTES = 8 * 1024;
|
|
18
|
+
const EXAMPLE = '{ resource: "doc:...", peers: [{ session: "s1", user: "ada", at: 1719000000000 }] }';
|
|
19
|
+
export function checkPresenceFrame(input) {
|
|
20
|
+
if (!isRecord(input))
|
|
21
|
+
return failShape(EXAMPLE, `got ${input === null ? 'null' : typeof input}`);
|
|
22
|
+
if (!isNonEmptyString(input.resource)) {
|
|
23
|
+
return failShape(EXAMPLE, `resource was ${JSON.stringify(input.resource)}, not a name`);
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(input.peers))
|
|
26
|
+
return failShape(EXAMPLE, 'peers was not an array');
|
|
27
|
+
for (const peer of input.peers) {
|
|
28
|
+
if (!isRecord(peer) || !isNonEmptyString(peer.session) || !isNonEmptyString(peer.user)) {
|
|
29
|
+
return failShape(EXAMPLE, `peer ${JSON.stringify(peer)} is missing session or user`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return pass(input);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=presence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/contracts/presence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAgB,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE1F,mEAAmE;AACnE,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAC1C,oFAAoF;AACpF,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC;AACpC,iEAAiE;AACjE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,CAAC;AAgCvC,MAAM,OAAO,GAAG,qFAAqF,CAAC;AAEtG,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;IACjG,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,SAAS,CAAC,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,SAAS,CAAC,OAAO,EAAE,wBAAwB,CAAC,CAAC;IACrF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvF,OAAO,SAAS,CAAC,OAAO,EAAE,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,KAAiC,CAAC,CAAC;AACjD,CAAC"}
|