@parlor/sync 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/channel.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Principal } from "@parlor/core";
|
|
2
2
|
export interface ServerChannel {
|
|
3
3
|
readonly event: string;
|
|
4
|
+
/**
|
|
5
|
+
* Also joins the streams of the users' computers (host principals), which carry no other
|
|
6
|
+
* channel; such a channel keeps what it sends a computer apart from what it sends tabs.
|
|
7
|
+
*/
|
|
8
|
+
readonly hosts?: boolean | undefined;
|
|
4
9
|
connect(connection: ChannelConnection): () => void;
|
|
5
10
|
}
|
|
6
11
|
export interface ChannelConnection {
|
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC3C,sFAAsF;IACtF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,IAAI,IAAI,CAAC;CAChB"}
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACrC,OAAO,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC3C,sFAAsF;IACtF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,IAAI,IAAI,CAAC;CAChB"}
|
package/dist/channel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"","sourcesContent":["import type { Principal } from \"@parlor/core\";\n\nexport interface ServerChannel {\n readonly event: string;\n connect(connection: ChannelConnection): () => void;\n}\n\nexport interface ChannelConnection {\n readonly clientId: string | undefined;\n readonly scope: string;\n /** Who holds the stream; absent when the server resolves no identity. */\n readonly principal?: Principal | undefined;\n /** Aborts when the stream ends, including when its holder's credential is revoked. */\n readonly closed?: AbortSignal | undefined;\n send(data: string): void;\n}\n\nexport interface ClientChannel {\n readonly event: string;\n opened(): void;\n receive(data: string): void;\n closed(): void;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"","sourcesContent":["import type { Principal } from \"@parlor/core\";\n\nexport interface ServerChannel {\n readonly event: string;\n /**\n * Also joins the streams of the users' computers (host principals), which carry no other\n * channel; such a channel keeps what it sends a computer apart from what it sends tabs.\n */\n readonly hosts?: boolean | undefined;\n connect(connection: ChannelConnection): () => void;\n}\n\nexport interface ChannelConnection {\n readonly clientId: string | undefined;\n readonly scope: string;\n /** Who holds the stream; absent when the server resolves no identity. */\n readonly principal?: Principal | undefined;\n /** Aborts when the stream ends, including when its holder's credential is revoked. */\n readonly closed?: AbortSignal | undefined;\n send(data: string): void;\n}\n\nexport interface ClientChannel {\n readonly event: string;\n opened(): void;\n receive(data: string): void;\n closed(): void;\n}\n"]}
|
package/dist/server/routes.d.ts
CHANGED
|
@@ -28,7 +28,8 @@ export interface SyncRouteOptions {
|
|
|
28
28
|
export interface StreamAuthority {
|
|
29
29
|
readonly principal: Principal;
|
|
30
30
|
readonly revoked?: AbortSignal | undefined;
|
|
31
|
-
|
|
31
|
+
/** Which channels the stream carries: all, none, or those the predicate admits. */
|
|
32
|
+
readonly channels: boolean | ((channel: ServerChannel) => boolean);
|
|
32
33
|
}
|
|
33
34
|
export declare const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
34
35
|
export declare function transportFromEnv(value?: string | undefined): SyncTransport | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAA+B,KAAK,EAAE,EAAgB,MAAM,IAAI,CAAC;AAExE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAYnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC,GAAG,SAAS,CAAC;CACnF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAA+B,KAAK,EAAE,EAAgB,MAAM,IAAI,CAAC;AAExE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAYnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGxC,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;AAE3C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/D;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,aAAa,EAAE,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC,GAAG,SAAS,CAAC;CACnF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC;CACpE;AAED,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,wBAAgB,gBAAgB,CAC9B,KAAK,GAAE,MAAM,GAAG,SAA6C,GAC5D,aAAa,GAAG,SAAS,CAE3B;AAwBD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAiH3E"}
|
package/dist/server/routes.js
CHANGED
|
@@ -33,7 +33,6 @@ export function registerSyncRoutes(app, options) {
|
|
|
33
33
|
const keepAliveMs = options.keepAliveMs ?? DEFAULT_KEEP_ALIVE_MS;
|
|
34
34
|
const db = () => data.read();
|
|
35
35
|
const channels = options.channels ?? [];
|
|
36
|
-
const capabilities = channels.map((channel) => channel.event);
|
|
37
36
|
app.post(`${basePath}/sync/token`, () => ({
|
|
38
37
|
protocol: SYNC_PROTOCOL_VERSION,
|
|
39
38
|
transport,
|
|
@@ -49,6 +48,8 @@ export function registerSyncRoutes(app, options) {
|
|
|
49
48
|
const streams = await streamsOf(event.req);
|
|
50
49
|
const currentScope = streams[0];
|
|
51
50
|
const authority = await options.authority?.(event.req);
|
|
51
|
+
const admitted = authority?.channels ?? true;
|
|
52
|
+
const carried = channels.filter((channel) => typeof admitted === "function" ? admitted(channel) : admitted);
|
|
52
53
|
await hub.start();
|
|
53
54
|
const stream = createEventStream(event);
|
|
54
55
|
const send = (payload) => void stream.push(JSON.stringify(payload));
|
|
@@ -87,12 +88,12 @@ export function registerSyncRoutes(app, options) {
|
|
|
87
88
|
event: "handshake",
|
|
88
89
|
data: JSON.stringify({
|
|
89
90
|
protocol: SYNC_PROTOCOL_VERSION,
|
|
90
|
-
capabilities:
|
|
91
|
+
capabilities: carried.map((channel) => channel.event),
|
|
91
92
|
keepAliveMs,
|
|
92
93
|
}),
|
|
93
94
|
});
|
|
94
95
|
const clientId = options.clientId?.(event.req);
|
|
95
|
-
for (const channel of
|
|
96
|
+
for (const channel of carried) {
|
|
96
97
|
disconnects.push(channel.connect({
|
|
97
98
|
clientId,
|
|
98
99
|
scope: currentScope,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAyB,MAAM,IAAI,CAAC;AAGxE,OAAO,EACL,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,qBAAqB,EACrB,YAAY,EAIZ,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAiChF,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAE7C,MAAM,UAAU,gBAAgB,CAC9B,KAAK,GAAuB,OAAO,CAAC,GAAG,CAAC,qBAAqB;IAE7D,OAAO,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC;AAED,SAAS,KAAK,CAAC,MAAmB,EAAE,KAAa;IAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;AACzE,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,EAAgB,EAChB,OAA0B,EAC1B,KAAc;IAEd,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,KAAK,KAAK,GAAG,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAO,EAAE,OAAyB;IACnE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAC/C,MAAM,SAAS,GAAG,KAAK,EAAE,OAAgB,EAA8B,EAAE;QACvE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC;QACnD,OAAO,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC9E,CAAC,CAAC;IACF,MAAM,SAAS,GACb,OAAO,CAAC,SAAS,IAAI,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5E,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC;IAC1E,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,qBAAqB,CAAC;IACjE,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAkB,CAAC;IAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;IACxC,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAE9D,GAAG,CAAC,IAAI,CAAC,GAAG,QAAQ,aAAa,EAAE,GAAoB,EAAE,CAAC,CAAC;QACzD,QAAQ,EAAE,qBAAqB;QAC/B,SAAS;QACT,QAAQ,EAAE,GAAG,QAAQ,cAAc;QACnC,cAAc;KACf,CAAC,CAAC,CAAC;IAEJ,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,OAAO,EAAE,KAAK,EAAE,KAAc,EAAE,EAAE;QACnD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1E,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,GAAG,QAAQ,cAAc,EAAE,KAAK,EAAE,KAAc,EAAE,EAAE;QAC1D,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QACjC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACvD,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,CAAC,OAAkB,EAAE,EAAE,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/E,MAAM,QAAQ,GAAG,CAAC,SAAoB,EAAE,EAAE,CACxC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;QAE3E,IAAI,oBAAoB,GAA4B,EAAE,CAAC;QACvD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACxC,GAAG,CAAC,SAAS,CACX,IAAI,EACJ,CAAC,SAAS,EAAE,EAAE;YACZ,IAAI,oBAAoB;gBAAE,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;;gBAC1D,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC,EACD,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,CAC1B,CACF,CAAC;QACF,MAAM,SAAS,GAAG,WAAW,CAC3B,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,WAAW,CAAC,YAAY,CAAC,EAC3C,WAAW,CACZ,CAAC;QACF,MAAM,WAAW,GAAmB,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;QACpC,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,MAAM,OAAO,GAAG,SAAS,EAAE,OAAO,CAAC;QACnC,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;QAC/C,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,aAAa,CAAC,SAAS,CAAC,CAAC;YACzB,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YACpD,KAAK,MAAM,WAAW,IAAI,YAAY;gBAAE,WAAW,EAAE,CAAC;YACtD,KAAK,MAAM,UAAU,IAAI,WAAW;gBAAE,UAAU,EAAE,CAAC;QACrD,CAAC,CAAC;QACF,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACxB,IAAI,OAAO,EAAE,OAAO;YAAE,YAAY,EAAE,CAAC;;YAChC,OAAO,EAAE,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtE,KAAK,MAAM,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,QAAQ,EAAE,qBAAqB;gBAC/B,YAAY,EAAE,SAAS,EAAE,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY;gBAC/D,WAAW;aACZ,CAAC;SACH,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/C,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YACpE,WAAW,CAAC,IAAI,CACd,OAAO,CAAC,OAAO,CAAC;gBACd,QAAQ;gBACR,KAAK,EAAE,YAAY;gBACnB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE;oBACd,IAAI,CAAC,QAAQ;wBAAE,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;gBACzE,CAAC;aACF,CAAC,CACH,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC9E,MAAM,EAAE,CAAC;YACT,MAAM,KAAK,CAAC;QACd,CAAC,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK;YAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,oBAAoB;aACjC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;aAC1E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,oBAAoB,GAAG,SAAS,CAAC;QACjC,KAAK,MAAM,SAAS,IAAI,OAAO;YAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,YAAY,EAAE,CAAC;YACzD,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { DataProvider, Principal } from \"@parlor/core\";\nimport { createEventStream, getQuery, type H3, type H3Event } from \"h3\";\n\nimport type { ServerChannel } from \"../channel.js\";\nimport {\n compareCursors,\n cursorOf,\n decodeCursor,\n DEFAULT_KEEP_ALIVE_MS,\n encodeCursor,\n type SyncBatch,\n type SyncEvent,\n type SyncNegotiation,\n SYNC_PROTOCOL_VERSION,\n} from \"../protocol.js\";\nimport type { SyncHub } from \"./hub.js\";\nimport { readSyncBacklog, readSyncEvents, type SyncDatabase } from \"./store.js\";\n\nexport type SyncTransport = \"sse\" | \"poll\";\n\nexport interface SyncRouteOptions {\n readonly basePath: string;\n readonly data: DataProvider;\n readonly hub: SyncHub;\n readonly scope: (request: Request) => string | Promise<string>;\n /**\n * The caller's own stream inside its scope, for events only its user should hear (an\n * action with `notifies: \"user\"`); read and pushed alongside the scope's.\n */\n readonly audience?: ((request: Request) => string | Promise<string>) | undefined;\n readonly transport?: SyncTransport | undefined;\n readonly pollIntervalMs?: number | undefined;\n readonly keepAliveMs?: number | undefined;\n readonly channels?: readonly ServerChannel[] | undefined;\n readonly clientId?: ((request: Request) => string | undefined) | undefined;\n /**\n * Who holds a stream and until when. A stream closes when `revoked` aborts, and a\n * stream without `channels` carries sync events only. Refusing a caller happens before\n * these routes run.\n */\n readonly authority?: ((request: Request) => Promise<StreamAuthority>) | undefined;\n}\n\nexport interface StreamAuthority {\n readonly principal: Principal;\n readonly revoked?: AbortSignal | undefined;\n readonly channels: boolean;\n}\n\nexport const DEFAULT_POLL_INTERVAL_MS = 2000;\n\nexport function transportFromEnv(\n value: string | undefined = process.env.PARLOR_SYNC_TRANSPORT,\n): SyncTransport | undefined {\n return value === \"sse\" || value === \"poll\" ? value : undefined;\n}\n\nfunction batch(events: SyncEvent[], since: string): SyncBatch {\n const last = events.at(-1);\n return { events, cursor: last ? encodeCursor(cursorOf(last)) : since };\n}\n\nasync function missedSince(\n db: SyncDatabase,\n streams: readonly string[],\n event: H3Event,\n): Promise<SyncBatch> {\n const query = getQuery(event);\n const since = String(query.since ?? \"\");\n if (query.reset !== \"1\") {\n return batch(await readSyncEvents(db, streams, decodeCursor(since)), since);\n }\n const backlog = await readSyncBacklog(db, streams, decodeCursor(since));\n if (backlog.resetTo) {\n return { events: [], cursor: encodeCursor(backlog.resetTo), reset: true };\n }\n return batch(backlog.events, since);\n}\n\nexport function registerSyncRoutes(app: H3, options: SyncRouteOptions): void {\n const { basePath, data, hub, scope } = options;\n const streamsOf = async (request: Request): Promise<readonly string[]> => {\n const own = await scope(request);\n const audience = await options.audience?.(request);\n return audience === undefined || audience === own ? [own] : [own, audience];\n };\n const transport: SyncTransport =\n options.transport ?? transportFromEnv() ?? (hub.canPush ? \"sse\" : \"poll\");\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const keepAliveMs = options.keepAliveMs ?? DEFAULT_KEEP_ALIVE_MS;\n const db = () => data.read() as SyncDatabase;\n const channels = options.channels ?? [];\n const capabilities = channels.map((channel) => channel.event);\n\n app.post(`${basePath}/sync/token`, (): SyncNegotiation => ({\n protocol: SYNC_PROTOCOL_VERSION,\n transport,\n endpoint: `${basePath}/sync/stream`,\n pollIntervalMs,\n }));\n\n app.get(`${basePath}/sync`, async (event: H3Event) => {\n const missed = await missedSince(db(), await streamsOf(event.req), event);\n event.res.headers.set(\"cache-control\", \"no-store\");\n return missed;\n });\n\n app.get(`${basePath}/sync/stream`, async (event: H3Event) => {\n const streams = await streamsOf(event.req);\n const currentScope = streams[0]!;\n const authority = await options.authority?.(event.req);\n await hub.start();\n const stream = createEventStream(event);\n const send = (payload: SyncBatch) => void stream.push(JSON.stringify(payload));\n const sendLive = (syncEvent: SyncEvent) =>\n send({ events: [syncEvent], cursor: encodeCursor(cursorOf(syncEvent)) });\n\n let arrivedDuringCatchUp: SyncEvent[] | undefined = [];\n const unsubscribes = streams.map((name) =>\n hub.subscribe(\n name,\n (syncEvent) => {\n if (arrivedDuringCatchUp) arrivedDuringCatchUp.push(syncEvent);\n else sendLive(syncEvent);\n },\n () => void stream.close(),\n ),\n );\n const keepAlive = setInterval(\n () => void stream.pushComment(\"keep-alive\"),\n keepAliveMs,\n );\n const disconnects: (() => void)[] = [];\n const ended = new AbortController();\n let detached = false;\n const revoked = authority?.revoked;\n const closeRevoked = () => void stream.close();\n const detach = () => {\n if (detached) return;\n detached = true;\n ended.abort();\n clearInterval(keepAlive);\n revoked?.removeEventListener(\"abort\", closeRevoked);\n for (const unsubscribe of unsubscribes) unsubscribe();\n for (const disconnect of disconnects) disconnect();\n };\n stream.onClosed(detach);\n if (revoked?.aborted) closeRevoked();\n else revoked?.addEventListener(\"abort\", closeRevoked, { once: true });\n\n void stream.push({\n event: \"handshake\",\n data: JSON.stringify({\n protocol: SYNC_PROTOCOL_VERSION,\n capabilities: authority?.channels === false ? [] : capabilities,\n keepAliveMs,\n }),\n });\n const clientId = options.clientId?.(event.req);\n for (const channel of authority?.channels === false ? [] : channels) {\n disconnects.push(\n channel.connect({\n clientId,\n scope: currentScope,\n ...(authority ? { principal: authority.principal } : {}),\n closed: ended.signal,\n send: (frame) => {\n if (!detached) void stream.push({ event: channel.event, data: frame });\n },\n }),\n );\n }\n const missed = await missedSince(db(), streams, event).catch((error: unknown) => {\n detach();\n throw error;\n });\n if (missed.events.length > 0 || missed.reset) send(missed);\n const caughtUpTo = decodeCursor(missed.cursor);\n const pending = arrivedDuringCatchUp\n .filter((syncEvent) => compareCursors(cursorOf(syncEvent), caughtUpTo) > 0)\n .sort((a, b) => compareCursors(cursorOf(a), cursorOf(b)));\n arrivedDuringCatchUp = undefined;\n for (const syncEvent of pending) sendLive(syncEvent);\n const body = await stream.send();\n if (event.req.headers.get(\"connection\") === \"keep-alive\") {\n event.res.headers.set(\"connection\", \"close\");\n }\n return body;\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DataProvider, Principal } from \"@parlor/core\";\nimport { createEventStream, getQuery, type H3, type H3Event } from \"h3\";\n\nimport type { ServerChannel } from \"../channel.js\";\nimport {\n compareCursors,\n cursorOf,\n decodeCursor,\n DEFAULT_KEEP_ALIVE_MS,\n encodeCursor,\n type SyncBatch,\n type SyncEvent,\n type SyncNegotiation,\n SYNC_PROTOCOL_VERSION,\n} from \"../protocol.js\";\nimport type { SyncHub } from \"./hub.js\";\nimport { readSyncBacklog, readSyncEvents, type SyncDatabase } from \"./store.js\";\n\nexport type SyncTransport = \"sse\" | \"poll\";\n\nexport interface SyncRouteOptions {\n readonly basePath: string;\n readonly data: DataProvider;\n readonly hub: SyncHub;\n readonly scope: (request: Request) => string | Promise<string>;\n /**\n * The caller's own stream inside its scope, for events only its user should hear (an\n * action with `notifies: \"user\"`); read and pushed alongside the scope's.\n */\n readonly audience?: ((request: Request) => string | Promise<string>) | undefined;\n readonly transport?: SyncTransport | undefined;\n readonly pollIntervalMs?: number | undefined;\n readonly keepAliveMs?: number | undefined;\n readonly channels?: readonly ServerChannel[] | undefined;\n readonly clientId?: ((request: Request) => string | undefined) | undefined;\n /**\n * Who holds a stream and until when. A stream closes when `revoked` aborts, and a\n * stream without `channels` carries sync events only. Refusing a caller happens before\n * these routes run.\n */\n readonly authority?: ((request: Request) => Promise<StreamAuthority>) | undefined;\n}\n\nexport interface StreamAuthority {\n readonly principal: Principal;\n readonly revoked?: AbortSignal | undefined;\n /** Which channels the stream carries: all, none, or those the predicate admits. */\n readonly channels: boolean | ((channel: ServerChannel) => boolean);\n}\n\nexport const DEFAULT_POLL_INTERVAL_MS = 2000;\n\nexport function transportFromEnv(\n value: string | undefined = process.env.PARLOR_SYNC_TRANSPORT,\n): SyncTransport | undefined {\n return value === \"sse\" || value === \"poll\" ? value : undefined;\n}\n\nfunction batch(events: SyncEvent[], since: string): SyncBatch {\n const last = events.at(-1);\n return { events, cursor: last ? encodeCursor(cursorOf(last)) : since };\n}\n\nasync function missedSince(\n db: SyncDatabase,\n streams: readonly string[],\n event: H3Event,\n): Promise<SyncBatch> {\n const query = getQuery(event);\n const since = String(query.since ?? \"\");\n if (query.reset !== \"1\") {\n return batch(await readSyncEvents(db, streams, decodeCursor(since)), since);\n }\n const backlog = await readSyncBacklog(db, streams, decodeCursor(since));\n if (backlog.resetTo) {\n return { events: [], cursor: encodeCursor(backlog.resetTo), reset: true };\n }\n return batch(backlog.events, since);\n}\n\nexport function registerSyncRoutes(app: H3, options: SyncRouteOptions): void {\n const { basePath, data, hub, scope } = options;\n const streamsOf = async (request: Request): Promise<readonly string[]> => {\n const own = await scope(request);\n const audience = await options.audience?.(request);\n return audience === undefined || audience === own ? [own] : [own, audience];\n };\n const transport: SyncTransport =\n options.transport ?? transportFromEnv() ?? (hub.canPush ? \"sse\" : \"poll\");\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const keepAliveMs = options.keepAliveMs ?? DEFAULT_KEEP_ALIVE_MS;\n const db = () => data.read() as SyncDatabase;\n const channels = options.channels ?? [];\n\n app.post(`${basePath}/sync/token`, (): SyncNegotiation => ({\n protocol: SYNC_PROTOCOL_VERSION,\n transport,\n endpoint: `${basePath}/sync/stream`,\n pollIntervalMs,\n }));\n\n app.get(`${basePath}/sync`, async (event: H3Event) => {\n const missed = await missedSince(db(), await streamsOf(event.req), event);\n event.res.headers.set(\"cache-control\", \"no-store\");\n return missed;\n });\n\n app.get(`${basePath}/sync/stream`, async (event: H3Event) => {\n const streams = await streamsOf(event.req);\n const currentScope = streams[0]!;\n const authority = await options.authority?.(event.req);\n const admitted = authority?.channels ?? true;\n const carried = channels.filter((channel) =>\n typeof admitted === \"function\" ? admitted(channel) : admitted,\n );\n await hub.start();\n const stream = createEventStream(event);\n const send = (payload: SyncBatch) => void stream.push(JSON.stringify(payload));\n const sendLive = (syncEvent: SyncEvent) =>\n send({ events: [syncEvent], cursor: encodeCursor(cursorOf(syncEvent)) });\n\n let arrivedDuringCatchUp: SyncEvent[] | undefined = [];\n const unsubscribes = streams.map((name) =>\n hub.subscribe(\n name,\n (syncEvent) => {\n if (arrivedDuringCatchUp) arrivedDuringCatchUp.push(syncEvent);\n else sendLive(syncEvent);\n },\n () => void stream.close(),\n ),\n );\n const keepAlive = setInterval(\n () => void stream.pushComment(\"keep-alive\"),\n keepAliveMs,\n );\n const disconnects: (() => void)[] = [];\n const ended = new AbortController();\n let detached = false;\n const revoked = authority?.revoked;\n const closeRevoked = () => void stream.close();\n const detach = () => {\n if (detached) return;\n detached = true;\n ended.abort();\n clearInterval(keepAlive);\n revoked?.removeEventListener(\"abort\", closeRevoked);\n for (const unsubscribe of unsubscribes) unsubscribe();\n for (const disconnect of disconnects) disconnect();\n };\n stream.onClosed(detach);\n if (revoked?.aborted) closeRevoked();\n else revoked?.addEventListener(\"abort\", closeRevoked, { once: true });\n\n void stream.push({\n event: \"handshake\",\n data: JSON.stringify({\n protocol: SYNC_PROTOCOL_VERSION,\n capabilities: carried.map((channel) => channel.event),\n keepAliveMs,\n }),\n });\n const clientId = options.clientId?.(event.req);\n for (const channel of carried) {\n disconnects.push(\n channel.connect({\n clientId,\n scope: currentScope,\n ...(authority ? { principal: authority.principal } : {}),\n closed: ended.signal,\n send: (frame) => {\n if (!detached) void stream.push({ event: channel.event, data: frame });\n },\n }),\n );\n }\n const missed = await missedSince(db(), streams, event).catch((error: unknown) => {\n detach();\n throw error;\n });\n if (missed.events.length > 0 || missed.reset) send(missed);\n const caughtUpTo = decodeCursor(missed.cursor);\n const pending = arrivedDuringCatchUp\n .filter((syncEvent) => compareCursors(cursorOf(syncEvent), caughtUpTo) > 0)\n .sort((a, b) => compareCursors(cursorOf(a), cursorOf(b)));\n arrivedDuringCatchUp = undefined;\n for (const syncEvent of pending) sendLive(syncEvent);\n const body = await stream.send();\n if (event.req.headers.get(\"connection\") === \"keep-alive\") {\n event.res.headers.set(\"connection\", \"close\");\n }\n return body;\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parlor/sync",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"homepage": "https://cychien.github.io/parlor",
|
|
5
5
|
"bugs": "https://github.com/cychien/parlor/issues",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"drizzle-orm": "^0.45.4",
|
|
32
32
|
"h3": "^2.0.1",
|
|
33
|
-
"@parlor/core": "0.3.
|
|
33
|
+
"@parlor/core": "0.3.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^24.14.0",
|
|
37
37
|
"typescript": "^7.0.2",
|
|
38
38
|
"vitest": "^5.0.3",
|
|
39
|
-
"@parlor/data": "0.3.
|
|
39
|
+
"@parlor/data": "0.3.1"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -p tsconfig.json",
|