orez-sync-executor 0.11.1 → 0.11.2
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/realtime/host.d.ts +7 -0
- package/dist/realtime/host.d.ts.map +1 -0
- package/dist/realtime/host.js +77 -0
- package/dist/realtime/host.js.map +1 -0
- package/dist/realtime/hub.d.ts +67 -0
- package/dist/realtime/hub.d.ts.map +1 -0
- package/dist/realtime/hub.js +456 -0
- package/dist/realtime/hub.js.map +1 -0
- package/dist/realtime/index.d.ts +24 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +27 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/realtime/local.d.ts +15 -0
- package/dist/realtime/local.d.ts.map +1 -0
- package/dist/realtime/local.js +63 -0
- package/dist/realtime/local.js.map +1 -0
- package/dist/realtime/manifest.d.ts +51 -0
- package/dist/realtime/manifest.d.ts.map +1 -0
- package/dist/realtime/manifest.js +110 -0
- package/dist/realtime/manifest.js.map +1 -0
- package/dist/realtime/message-port.d.ts +34 -0
- package/dist/realtime/message-port.d.ts.map +1 -0
- package/dist/realtime/message-port.js +141 -0
- package/dist/realtime/message-port.js.map +1 -0
- package/dist/realtime/producer-socket.d.ts +10 -0
- package/dist/realtime/producer-socket.d.ts.map +1 -0
- package/dist/realtime/producer-socket.js +90 -0
- package/dist/realtime/producer-socket.js.map +1 -0
- package/dist/realtime/producer.d.ts +16 -0
- package/dist/realtime/producer.d.ts.map +1 -0
- package/dist/realtime/producer.js +51 -0
- package/dist/realtime/producer.js.map +1 -0
- package/dist/realtime/protocol.d.ts +99 -0
- package/dist/realtime/protocol.d.ts.map +1 -0
- package/dist/realtime/protocol.js +191 -0
- package/dist/realtime/protocol.js.map +1 -0
- package/dist/realtime/publisher.d.ts +28 -0
- package/dist/realtime/publisher.d.ts.map +1 -0
- package/dist/realtime/publisher.js +309 -0
- package/dist/realtime/publisher.js.map +1 -0
- package/dist/realtime/socket-host.d.ts +25 -0
- package/dist/realtime/socket-host.d.ts.map +1 -0
- package/dist/realtime/socket-host.js +105 -0
- package/dist/realtime/socket-host.js.map +1 -0
- package/dist/realtime/store.d.ts +28 -0
- package/dist/realtime/store.d.ts.map +1 -0
- package/dist/realtime/store.js +0 -0
- package/dist/realtime/store.js.map +1 -0
- package/dist/realtime/writer.d.ts +17 -0
- package/dist/realtime/writer.d.ts.map +1 -0
- package/dist/realtime/writer.js +138 -0
- package/dist/realtime/writer.js.map +1 -0
- package/package.json +5 -1
- package/dist/adapters.d.ts +0 -25
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -116
- package/dist/adapters.js.map +0 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { defineStreamingFields, resolveTopic } from './manifest.js';
|
|
2
|
+
export type { FieldMode, StreamingFieldDeclaration, StreamingFieldOptions, StreamingFieldHandle, StreamingFieldRef, StreamingFieldSpec, StreamingManifest, ZeroSchemaShape, } from './manifest.js';
|
|
3
|
+
export { canonicalPrimaryKey, canonicalTopic, decodeFrame, encodeFrame, isLegacyWake, LEGACY_WAKE_FRAME, TOPIC_SEPARATOR, } from './protocol.js';
|
|
4
|
+
export type { AnyFrame, ClientFrame, FieldUpdate, HostFrame, ProducerFrame, ProducerHostFrame, RealtimeKeyValue, RealtimeTopic, } from './protocol.js';
|
|
5
|
+
export { canonicalEncode, RealtimeStore } from './store.js';
|
|
6
|
+
export type { RealtimeStoreOptions, StreamingFieldState, StreamingPhase, } from './store.js';
|
|
7
|
+
export { RealtimePublisher } from './publisher.js';
|
|
8
|
+
export type { BeginOptions, PublisherTransport, StreamSession } from './publisher.js';
|
|
9
|
+
export { DEFAULT_HUB_LIMITS, RealtimeHub } from './hub.js';
|
|
10
|
+
export type { HubConnection, HubLimits, HubOptions, HubProducer, RealtimeIdentity, SubscribeAuthorization, } from './hub.js';
|
|
11
|
+
export { FieldWriter } from './writer.js';
|
|
12
|
+
export type { FieldWriterOptions } from './writer.js';
|
|
13
|
+
export { createLocalRealtime } from './local.js';
|
|
14
|
+
export type { LocalRealtime, LocalRealtimeOptions } from './local.js';
|
|
15
|
+
export { BrowserRealtime, connectRealtimePort } from './message-port.js';
|
|
16
|
+
export type { BrowserRealtimeOptions, MembershipReader } from './message-port.js';
|
|
17
|
+
export { applyClientFrame, applyHostFrame, applyProducerFrame } from './host.js';
|
|
18
|
+
export { createProducer, inProcessTransport } from './producer.js';
|
|
19
|
+
export type { ProducerOptions, RealtimeProducer } from './producer.js';
|
|
20
|
+
export { createSocketHost } from './socket-host.js';
|
|
21
|
+
export type { HostConnection, HostSocket, RealtimeSocketHost } from './socket-host.js';
|
|
22
|
+
export { createSocketProducer } from './producer-socket.js';
|
|
23
|
+
export type { ProducerSocket, SocketProducer } from './producer-socket.js';
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/realtime/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACnE,YAAY,EACV,SAAS,EACT,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,QAAQ,EACR,WAAW,EACX,WAAW,EACX,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC3D,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAErF,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAC1D,YAAY,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAChD,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAErE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AACxE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAGjF,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAGhF,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAGtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAItF,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Streaming fields: an ephemeral, typed field overlay beside Zero.
|
|
2
|
+
//
|
|
3
|
+
// The ordinary Zero row stays the durable truth. During a stream orez
|
|
4
|
+
// broadcasts field values without writing the application row or advancing the
|
|
5
|
+
// Zero cookie, and the application commits the final value through its existing
|
|
6
|
+
// write path. See plans/streaming-fields.md for the design and its rejected
|
|
7
|
+
// alternatives.
|
|
8
|
+
export { defineStreamingFields, resolveTopic } from './manifest.js';
|
|
9
|
+
export { canonicalPrimaryKey, canonicalTopic, decodeFrame, encodeFrame, isLegacyWake, LEGACY_WAKE_FRAME, TOPIC_SEPARATOR, } from './protocol.js';
|
|
10
|
+
export { canonicalEncode, RealtimeStore } from './store.js';
|
|
11
|
+
export { RealtimePublisher } from './publisher.js';
|
|
12
|
+
export { DEFAULT_HUB_LIMITS, RealtimeHub } from './hub.js';
|
|
13
|
+
export { FieldWriter } from './writer.js';
|
|
14
|
+
export { createLocalRealtime } from './local.js';
|
|
15
|
+
export { BrowserRealtime, connectRealtimePort } from './message-port.js';
|
|
16
|
+
// Frame routing. One applier per direction; every surface is built from these.
|
|
17
|
+
export { applyClientFrame, applyHostFrame, applyProducerFrame } from './host.js';
|
|
18
|
+
// The producer role, identical on every surface
|
|
19
|
+
export { createProducer, inProcessTransport } from './producer.js';
|
|
20
|
+
// The host role over sockets, for any runtime that has them
|
|
21
|
+
export { createSocketHost } from './socket-host.js';
|
|
22
|
+
// A producer that reaches the hub over a socket: an application server
|
|
23
|
+
// generating values while the subscribers are browsers elsewhere
|
|
24
|
+
export { createSocketProducer } from './producer-socket.js';
|
|
25
|
+
// The React binding lives in on-zero, which already owns the React peer
|
|
26
|
+
// dependency. orez-lite stays framework-free: see on-zero's useStreamingField.
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/realtime/index.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,sEAAsE;AACtE,+EAA+E;AAC/E,gFAAgF;AAChF,4EAA4E;AAC5E,gBAAgB;AAEhB,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAYnE,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,eAAe,GAChB,MAAM,eAAe,CAAA;AAYtB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAO3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAGlD,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAU1D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAGhD,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAGxE,+EAA+E;AAC/E,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAEhF,gDAAgD;AAChD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAGlE,4DAA4D;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAGnD,uEAAuE;AACvE,iEAAiE;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAG3D,wEAAwE;AACxE,+EAA+E"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RealtimeStore } from './store.js';
|
|
2
|
+
import type { StreamingManifest } from './manifest.js';
|
|
3
|
+
import type { RealtimeProducer } from './producer.js';
|
|
4
|
+
export type LocalRealtimeOptions = {
|
|
5
|
+
readonly manifest: StreamingManifest;
|
|
6
|
+
readonly staleAfterMs?: number;
|
|
7
|
+
readonly onError?: (message: string) => void;
|
|
8
|
+
};
|
|
9
|
+
export type LocalRealtime = RealtimeProducer & {
|
|
10
|
+
readonly store: RealtimeStore;
|
|
11
|
+
readonly flush: () => void;
|
|
12
|
+
readonly close: () => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function createLocalRealtime(options: LocalRealtimeOptions): LocalRealtime;
|
|
15
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/realtime/local.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAGrD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IAEpC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7C,CAAA;AAID,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG;IAE7C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;IAG7B,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CA0ChF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Single-client realtime: a hub, a publisher, and a store wired together in
|
|
2
|
+
// one JavaScript context, with no socket, no host, and no authorization.
|
|
3
|
+
//
|
|
4
|
+
// This is the shape an application needs when the thing producing the values
|
|
5
|
+
// and the thing displaying them are the same client. Soot's agent loop runs in
|
|
6
|
+
// the browser: the tab running the agent is the tab rendering its output, so
|
|
7
|
+
// routing those values through a server and back would add latency and an auth
|
|
8
|
+
// surface to reach the component sitting next to the producer.
|
|
9
|
+
//
|
|
10
|
+
// It is the same RealtimeHub every other host runs, so a stream behaves
|
|
11
|
+
// identically here and over a socket: same generations, same append
|
|
12
|
+
// accumulation, same durable handoff, same manifest bounds. Moving an
|
|
13
|
+
// application from local to a shared host changes which realtime it constructs
|
|
14
|
+
// and nothing about its components or its producer code.
|
|
15
|
+
//
|
|
16
|
+
// What it deliberately does NOT do is reach other clients. Values live in this
|
|
17
|
+
// context only. The durable Zero row remains the cross-client story, which is
|
|
18
|
+
// exactly the guarantee the overlay already makes: a reload, another tab, or
|
|
19
|
+
// another user sees the committed value, never the in-flight one.
|
|
20
|
+
import { applyClientFrame, applyHostFrame } from './host.js';
|
|
21
|
+
import { RealtimeHub } from './hub.js';
|
|
22
|
+
import { createProducer, inProcessTransport } from './producer.js';
|
|
23
|
+
import { RealtimeStore } from './store.js';
|
|
24
|
+
export function createLocalRealtime(options) {
|
|
25
|
+
// Every row is authorized: there is one client, it is the producer, and it
|
|
26
|
+
// already holds the data it is about to display. There is nothing to
|
|
27
|
+
// authorize against and nobody to withhold it from.
|
|
28
|
+
const hub = new RealtimeHub({
|
|
29
|
+
manifest: options.manifest,
|
|
30
|
+
authorizeSubscribe: () => ({ status: 'active' }),
|
|
31
|
+
// No batching: the window exists to collapse several updates into one
|
|
32
|
+
// WebSocket frame, and there is no socket here. Deferring an in-process
|
|
33
|
+
// call would only add a tick of latency between the producer and the
|
|
34
|
+
// component next to it.
|
|
35
|
+
scheduleFlush: (flush) => flush(),
|
|
36
|
+
});
|
|
37
|
+
const store = new RealtimeStore({
|
|
38
|
+
send: (frame) => void applyClientFrame(hub, connection, frame),
|
|
39
|
+
staleAfterMs: options.staleAfterMs,
|
|
40
|
+
onError: options.onError,
|
|
41
|
+
});
|
|
42
|
+
const connection = {
|
|
43
|
+
id: 'local',
|
|
44
|
+
identity: { userID: 'local', clientID: 'local', clientGroupID: 'local' },
|
|
45
|
+
// Frames are handed straight to the store, not serialized: this is one
|
|
46
|
+
// context, and a JSON round trip per token would be pure cost. The routing
|
|
47
|
+
// is still the shared one, so a frame means the same thing here as it does
|
|
48
|
+
// over a socket.
|
|
49
|
+
send: (frame) => applyHostFrame(store, frame),
|
|
50
|
+
};
|
|
51
|
+
const producerHandle = { id: 'local-producer', send: () => { } };
|
|
52
|
+
const producer = createProducer(inProcessTransport(hub, producerHandle), options);
|
|
53
|
+
return {
|
|
54
|
+
store,
|
|
55
|
+
...producer,
|
|
56
|
+
flush: () => hub.flush(),
|
|
57
|
+
close: () => {
|
|
58
|
+
hub.dropProducer(producerHandle.id);
|
|
59
|
+
hub.dropConnection(connection.id);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/realtime/local.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,yEAAyE;AACzE,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,+DAA+D;AAC/D,EAAE;AACF,wEAAwE;AACxE,oEAAoE;AACpE,sEAAsE;AACtE,+EAA+E;AAC/E,yDAAyD;AACzD,EAAE;AACF,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,kEAAkE;AAElE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAyB1C,MAAM,UAAU,mBAAmB,CAAC,OAA6B;IAC/D,2EAA2E;IAC3E,qEAAqE;IACrE,oDAAoD;IACpD,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,kBAAkB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAChD,sEAAsE;QACtE,wEAAwE;QACxE,qEAAqE;QACrE,wBAAwB;QACxB,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE;KAClC,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC;QAC9B,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,gBAAgB,CAAC,GAAG,EAAE,UAAU,EAAE,KAAoB,CAAC;QAC7E,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CAAA;IAEF,MAAM,UAAU,GAAkB;QAChC,EAAE,EAAE,OAAO;QACX,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE;QACxE,uEAAuE;QACvE,2EAA2E;QAC3E,2EAA2E;QAC3E,iBAAiB;QACjB,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,KAAkB,CAAC;KAC3D,CAAA;IAED,MAAM,cAAc,GAAgB,EAAE,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAA;IAC5E,MAAM,QAAQ,GAAG,cAAc,CAAC,kBAAkB,CAAC,GAAG,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAA;IAEjF,OAAO;QACL,KAAK;QACL,GAAG,QAAQ;QACX,KAAK,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE;QACxB,KAAK,EAAE,GAAG,EAAE;YACV,GAAG,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC,CAAA;YACnC,GAAG,CAAC,cAAc,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;QACnC,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { RealtimeKeyValue, RealtimeTopic } from './protocol.js';
|
|
2
|
+
export type ZeroSchemaShape = {
|
|
3
|
+
readonly tables: Readonly<Record<string, {
|
|
4
|
+
readonly columns: Readonly<Record<string, {
|
|
5
|
+
readonly type: string;
|
|
6
|
+
}>>;
|
|
7
|
+
readonly primaryKey: readonly string[];
|
|
8
|
+
}>>;
|
|
9
|
+
};
|
|
10
|
+
export type FieldMode = 'append' | 'replace';
|
|
11
|
+
export type StreamingFieldOptions = {
|
|
12
|
+
readonly maxBytes: number;
|
|
13
|
+
readonly maxUpdatesPerSecond: number;
|
|
14
|
+
readonly maxBytesPerSecond: number;
|
|
15
|
+
readonly mode?: FieldMode;
|
|
16
|
+
readonly validate?: (value: unknown) => boolean;
|
|
17
|
+
};
|
|
18
|
+
export type StreamingFieldSpec = StreamingFieldOptions & {
|
|
19
|
+
readonly table: string;
|
|
20
|
+
readonly field: string;
|
|
21
|
+
readonly mode: FieldMode;
|
|
22
|
+
readonly columnType: string;
|
|
23
|
+
readonly primaryKey: readonly string[];
|
|
24
|
+
};
|
|
25
|
+
export type StreamingManifest = {
|
|
26
|
+
readonly fields: ReadonlyMap<string, StreamingFieldSpec>;
|
|
27
|
+
readonly schemaKey: string;
|
|
28
|
+
};
|
|
29
|
+
export type StreamingFieldHandle = {
|
|
30
|
+
readonly topic: RealtimeTopic;
|
|
31
|
+
readonly spec: StreamingFieldSpec;
|
|
32
|
+
};
|
|
33
|
+
export type StreamingFieldRef<Key, Value> = ((key: Key) => StreamingFieldHandle) & {
|
|
34
|
+
readonly spec: StreamingFieldSpec;
|
|
35
|
+
readonly __value?: Value;
|
|
36
|
+
};
|
|
37
|
+
export type StreamingFieldDeclaration = Readonly<Record<string, Readonly<Record<string, StreamingFieldOptions>>>>;
|
|
38
|
+
export declare function defineStreamingFields<Declaration extends StreamingFieldDeclaration>(schema: ZeroSchemaShape, declaration: Declaration): {
|
|
39
|
+
readonly manifest: StreamingManifest;
|
|
40
|
+
} & {
|
|
41
|
+
readonly [Table in keyof Declaration]: {
|
|
42
|
+
readonly [Field in keyof Declaration[Table]]: StreamingFieldRef<Record<string, RealtimeKeyValue>, unknown>;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export declare function resolveTopic(manifest: StreamingManifest, topic: RealtimeTopic): {
|
|
46
|
+
readonly spec: StreamingFieldSpec;
|
|
47
|
+
readonly id: string;
|
|
48
|
+
} | {
|
|
49
|
+
readonly reason: string;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/realtime/manifest.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CACvB,MAAM,CACJ,MAAM,EACN;QACE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC,CAAA;QACrE,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;KACvC,CACF,CACF,CAAA;CACF,CAAA;AAOD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAA;AAE5C,MAAM,MAAM,qBAAqB,GAAG;IAElC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IAEzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IAGpC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAElC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;IAKzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,qBAAqB,GAAG;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;IAIxD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B,CAAA;AAMD,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;IAC7B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;CAClC,CAAA;AAGD,MAAM,MAAM,iBAAiB,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,oBAAoB,CAAC,GAAG;IACjF,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;IAEjC,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAA;CACzB,CAAA;AAoBD,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAC9C,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAChE,CAAA;AAED,wBAAgB,qBAAqB,CAAC,WAAW,SAAS,yBAAyB,EACjF,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,WAAW,GACvB;IACD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;CACrC,GAAG;IACF,QAAQ,EAAE,KAAK,IAAI,MAAM,WAAW,GAAG;QACrC,QAAQ,EAAE,KAAK,IAAI,MAAM,WAAW,CAAC,KAAK,CAAC,GAAG,iBAAiB,CAC7D,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAChC,OAAO,CACR;KACF;CACF,CA0EA;AAuBD,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,iBAAiB,EAC3B,KAAK,EAAE,aAAa,GAElB;IAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAU9B"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// The streaming-field manifest: which columns may stream, in what mode, under
|
|
2
|
+
// what bounds.
|
|
3
|
+
//
|
|
4
|
+
// It stays PARALLEL to the Zero schema rather than being a property on it.
|
|
5
|
+
// Zero derives its client schema and compatibility hash from the stock column
|
|
6
|
+
// shape, so an orez-specific property there would change Zero's schema identity
|
|
7
|
+
// or depend on how its runtime treats unknown keys. The manifest takes the
|
|
8
|
+
// schema as input, validates every table and column against it, and produces a
|
|
9
|
+
// separate object.
|
|
10
|
+
import { canonicalTopic } from './protocol.js';
|
|
11
|
+
const APPENDABLE_TYPES = new Set(['string']);
|
|
12
|
+
const VALIDATOR_REQUIRED_TYPES = new Set(['json']);
|
|
13
|
+
function schemaIdentity(schema, fields) {
|
|
14
|
+
// identity covers exactly what a mismatch would break: the streamed columns,
|
|
15
|
+
// their type, their mode, and the key ordering topics are built from.
|
|
16
|
+
return fields
|
|
17
|
+
.map((spec) => {
|
|
18
|
+
const table = schema.tables[spec.table];
|
|
19
|
+
return `${spec.table}.${spec.field}:${spec.columnType}:${spec.mode}:${table.primaryKey.join(',')}`;
|
|
20
|
+
})
|
|
21
|
+
.sort()
|
|
22
|
+
.join('|');
|
|
23
|
+
}
|
|
24
|
+
export function defineStreamingFields(schema, declaration) {
|
|
25
|
+
const specs = [];
|
|
26
|
+
const tables = {};
|
|
27
|
+
for (const [table, fields] of Object.entries(declaration)) {
|
|
28
|
+
const schemaTable = schema.tables[table];
|
|
29
|
+
if (!schemaTable) {
|
|
30
|
+
throw new TypeError(`streaming field table '${table}' is not in the Zero schema`);
|
|
31
|
+
}
|
|
32
|
+
if (schemaTable.primaryKey.length === 0) {
|
|
33
|
+
throw new TypeError(`streaming field table '${table}' has no primary key`);
|
|
34
|
+
}
|
|
35
|
+
const refs = {};
|
|
36
|
+
for (const [field, options] of Object.entries(fields)) {
|
|
37
|
+
const column = schemaTable.columns[field];
|
|
38
|
+
if (!column) {
|
|
39
|
+
throw new TypeError(`streaming field '${table}.${field}' is not in the Zero schema`);
|
|
40
|
+
}
|
|
41
|
+
if (schemaTable.primaryKey.includes(field)) {
|
|
42
|
+
throw new TypeError(`streaming field '${table}.${field}' is a primary key column; a streamed key would change the row's identity mid-generation`);
|
|
43
|
+
}
|
|
44
|
+
validateBounds(table, field, options);
|
|
45
|
+
const mode = options.mode ?? (APPENDABLE_TYPES.has(column.type) ? 'append' : 'replace');
|
|
46
|
+
if (mode === 'append' && !APPENDABLE_TYPES.has(column.type)) {
|
|
47
|
+
throw new TypeError(`streaming field '${table}.${field}' is a ${column.type} column; append mode concatenates and only applies to string columns`);
|
|
48
|
+
}
|
|
49
|
+
if (VALIDATOR_REQUIRED_TYPES.has(column.type) && !options.validate) {
|
|
50
|
+
throw new TypeError(`streaming field '${table}.${field}' is a ${column.type} column and must supply validate(); Zero carries no runtime validator for it and realtime values bypass PayloadCodec.decodePull`);
|
|
51
|
+
}
|
|
52
|
+
const spec = {
|
|
53
|
+
...options,
|
|
54
|
+
table,
|
|
55
|
+
field,
|
|
56
|
+
mode,
|
|
57
|
+
columnType: column.type,
|
|
58
|
+
primaryKey: schemaTable.primaryKey,
|
|
59
|
+
};
|
|
60
|
+
specs.push(spec);
|
|
61
|
+
const ref = ((key) => {
|
|
62
|
+
for (const column of spec.primaryKey) {
|
|
63
|
+
if (key[column] === undefined) {
|
|
64
|
+
throw new TypeError(`streaming topic '${table}.${field}' is missing primary key column '${column}'`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { topic: { table, key, field }, spec };
|
|
68
|
+
});
|
|
69
|
+
Object.defineProperty(ref, 'spec', { value: spec, enumerable: true });
|
|
70
|
+
refs[field] = ref;
|
|
71
|
+
}
|
|
72
|
+
tables[table] = refs;
|
|
73
|
+
}
|
|
74
|
+
const fieldMap = new Map();
|
|
75
|
+
for (const spec of specs)
|
|
76
|
+
fieldMap.set(`${spec.table}.${spec.field}`, spec);
|
|
77
|
+
return {
|
|
78
|
+
...tables,
|
|
79
|
+
manifest: { fields: fieldMap, schemaKey: schemaIdentity(schema, specs) },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function validateBounds(table, field, options) {
|
|
83
|
+
const bounds = [
|
|
84
|
+
['maxBytes', options.maxBytes],
|
|
85
|
+
['maxUpdatesPerSecond', options.maxUpdatesPerSecond],
|
|
86
|
+
['maxBytesPerSecond', options.maxBytesPerSecond],
|
|
87
|
+
];
|
|
88
|
+
for (const [name, value] of bounds) {
|
|
89
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
90
|
+
throw new TypeError(`streaming field '${table}.${field}' needs a positive ${name}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Host-side lookup: resolve a topic against the manifest, returning the spec
|
|
95
|
+
// and the canonical wire identity, or a reason the topic is not streamable.
|
|
96
|
+
// Every host runs this before it will fan anything out, so a producer bug or a
|
|
97
|
+
// crafted subscribe frame cannot create a topic outside the manifest.
|
|
98
|
+
export function resolveTopic(manifest, topic) {
|
|
99
|
+
const spec = manifest.fields.get(`${topic.table}.${topic.field}`);
|
|
100
|
+
if (!spec)
|
|
101
|
+
return { reason: `'${topic.table}.${topic.field}' is not a streaming field` };
|
|
102
|
+
for (const column of spec.primaryKey) {
|
|
103
|
+
const value = topic.key[column];
|
|
104
|
+
if (value === undefined) {
|
|
105
|
+
return { reason: `topic is missing primary key column '${column}'` };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { spec, id: canonicalTopic(spec.primaryKey, topic) };
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/realtime/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RealtimeHub } from './hub.js';
|
|
2
|
+
import { RealtimeStore } from './store.js';
|
|
3
|
+
import type { RealtimeIdentity } from './hub.js';
|
|
4
|
+
import type { StreamingManifest } from './manifest.js';
|
|
5
|
+
import type { RealtimeTopic } from './protocol.js';
|
|
6
|
+
import type { RealtimePublisher } from './publisher.js';
|
|
7
|
+
import type { FieldWriter } from './writer.js';
|
|
8
|
+
export type MembershipReader = (identity: RealtimeIdentity, table: string, key: Readonly<Record<string, boolean | null | number | string>>) => Promise<{
|
|
9
|
+
readonly ownsGroup: boolean;
|
|
10
|
+
readonly authorized: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
export type BrowserRealtimeOptions = {
|
|
13
|
+
readonly manifest: StreamingManifest;
|
|
14
|
+
readonly readMembership: MembershipReader;
|
|
15
|
+
readonly onError?: (message: string) => void;
|
|
16
|
+
};
|
|
17
|
+
export declare class BrowserRealtime {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(options: BrowserRealtimeOptions);
|
|
20
|
+
get fields(): FieldWriter;
|
|
21
|
+
get publisher(): RealtimePublisher;
|
|
22
|
+
connect(port: MessagePort, identity: RealtimeIdentity): () => void;
|
|
23
|
+
revokeMembership(clientGroupID: string, removed: readonly RealtimeTopic[]): void;
|
|
24
|
+
flush(): void;
|
|
25
|
+
get hub(): RealtimeHub;
|
|
26
|
+
}
|
|
27
|
+
export declare function connectRealtimePort(port: MessagePort, options?: {
|
|
28
|
+
readonly staleAfterMs?: number;
|
|
29
|
+
readonly onError?: (message: string) => void;
|
|
30
|
+
}): {
|
|
31
|
+
readonly store: RealtimeStore;
|
|
32
|
+
readonly close: () => void;
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=message-port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-port.d.ts","sourceRoot":"","sources":["../../src/realtime/message-port.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAGtC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,OAAO,KAAK,EAGV,gBAAgB,EAEjB,MAAM,UAAU,CAAA;AACjB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAEtD,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,eAAe,CAAA;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAI9C,MAAM,MAAM,gBAAgB,GAAG,CAC7B,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,KAC5D,OAAO,CAAC;IAAE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAE3E,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAA;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7C,CAAA;AAED,qBAAa,eAAe;;gBAMd,OAAO,EAAE,sBAAsB;IAqC3C,IAAI,MAAM,IAAI,WAAW,CAExB;IAID,IAAI,SAAS,IAAI,iBAAiB,CAEjC;IAKD,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAqClE,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI;IAIhF,KAAK,IAAI,IAAI;IAIb,IAAI,GAAG,IAAI,WAAW,CAErB;CACF;AAKD,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE;IACP,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,GACL;IAAE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAAE,CAiC/D"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// MessagePort realtime adapter: the browser-worker host's transport.
|
|
2
|
+
//
|
|
3
|
+
// The hub state machine, publisher, and protocol are shared with every other
|
|
4
|
+
// host (the realtime hub). This file is only the browser's socket lifecycle:
|
|
5
|
+
// a MessagePort instead of a WebSocket, and an authorizer that reads the same
|
|
6
|
+
// durable query membership the pull path maintains.
|
|
7
|
+
//
|
|
8
|
+
// The worker and its producer share one process lifecycle, so subscriptions and
|
|
9
|
+
// stream generations are memory-only here. Nothing survives a worker restart,
|
|
10
|
+
// which is correct: the durable value is whatever the client's next Zero pull
|
|
11
|
+
// produces.
|
|
12
|
+
import { applyClientFrame, applyHostFrame } from './host.js';
|
|
13
|
+
import { RealtimeHub } from './hub.js';
|
|
14
|
+
import { createProducer, inProcessTransport } from './producer.js';
|
|
15
|
+
import { decodeFrame, encodeFrame } from './protocol.js';
|
|
16
|
+
import { RealtimeStore } from './store.js';
|
|
17
|
+
export class BrowserRealtime {
|
|
18
|
+
#hub;
|
|
19
|
+
#producer;
|
|
20
|
+
#producerCounter = 0;
|
|
21
|
+
#connectionCounter = 0;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.#hub = new RealtimeHub({
|
|
24
|
+
manifest: options.manifest,
|
|
25
|
+
authorizeSubscribe: async (identity, topic) => {
|
|
26
|
+
const { ownsGroup, authorized } = await options.readMembership(identity, topic.table, topic.key);
|
|
27
|
+
// A client group id is not a bearer token: not owning it is a denial,
|
|
28
|
+
// and the reason deliberately does not say whether the row exists.
|
|
29
|
+
if (!ownsGroup) {
|
|
30
|
+
return { status: 'denied', reason: 'client group does not belong to this user' };
|
|
31
|
+
}
|
|
32
|
+
// Owning the group without membership is the optimistic-row race: the
|
|
33
|
+
// client holds a row from its own mutation that the server has not
|
|
34
|
+
// recorded yet. It retries after its next pull.
|
|
35
|
+
return authorized ? { status: 'active' } : { status: 'pending' };
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// There is no producer socket in the browser: the code running the model
|
|
39
|
+
// loop and the hub share one JavaScript context, so begin/publish are
|
|
40
|
+
// direct calls. The producer ROLE is still the shared one, so a loop
|
|
41
|
+
// written here runs unchanged against a socket host.
|
|
42
|
+
const producerHandle = {
|
|
43
|
+
id: `browser-producer-${++this.#producerCounter}`,
|
|
44
|
+
send: () => { },
|
|
45
|
+
};
|
|
46
|
+
this.#producer = createProducer(inProcessTransport(this.#hub, producerHandle), options);
|
|
47
|
+
}
|
|
48
|
+
// Imperative writing: set a row's field to its current value, generations
|
|
49
|
+
// managed for you. Trusted by construction, this is the worker's own code.
|
|
50
|
+
get fields() {
|
|
51
|
+
return this.#producer.fields;
|
|
52
|
+
}
|
|
53
|
+
// The explicit generation API underneath, for a producer that controls begin,
|
|
54
|
+
// commit, and end itself.
|
|
55
|
+
get publisher() {
|
|
56
|
+
return this.#producer.publisher;
|
|
57
|
+
}
|
|
58
|
+
// Attach a port. The identity comes from the host's authenticated pull path,
|
|
59
|
+
// never from the port itself, so a page cannot claim another user's client
|
|
60
|
+
// group by asserting one over the channel.
|
|
61
|
+
connect(port, identity) {
|
|
62
|
+
const connection = {
|
|
63
|
+
id: `browser-connection-${++this.#connectionCounter}`,
|
|
64
|
+
identity,
|
|
65
|
+
send: (frame) => {
|
|
66
|
+
port.postMessage({ event: 'realtime', frame: encodeFrame(frame) });
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
const onMessage = (event) => {
|
|
70
|
+
const message = event.data;
|
|
71
|
+
if (!message ||
|
|
72
|
+
typeof message !== 'object' ||
|
|
73
|
+
message.event !== 'realtime') {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const raw = message.frame;
|
|
77
|
+
if (typeof raw !== 'string')
|
|
78
|
+
return;
|
|
79
|
+
const frame = decodeFrame(raw);
|
|
80
|
+
if (!frame)
|
|
81
|
+
return;
|
|
82
|
+
// A subscriber channel is never a publish channel. Only subscribe and
|
|
83
|
+
// unsubscribe are accepted here, whatever else a port sends.
|
|
84
|
+
if (frame[0] !== 'subscribe' && frame[0] !== 'unsubscribe')
|
|
85
|
+
return;
|
|
86
|
+
void applyClientFrame(this.#hub, connection, frame);
|
|
87
|
+
};
|
|
88
|
+
port.addEventListener('message', onMessage);
|
|
89
|
+
return () => {
|
|
90
|
+
port.removeEventListener('message', onMessage);
|
|
91
|
+
this.#hub.dropConnection(connection.id);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// A pull removed rows from a client group's membership, so its field
|
|
95
|
+
// subscriptions for those rows go with them.
|
|
96
|
+
revokeMembership(clientGroupID, removed) {
|
|
97
|
+
this.#hub.revokeMembership(clientGroupID, removed);
|
|
98
|
+
}
|
|
99
|
+
flush() {
|
|
100
|
+
this.#hub.flush();
|
|
101
|
+
}
|
|
102
|
+
get hub() {
|
|
103
|
+
return this.#hub;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// The page-side half of the port transport. Owns a RealtimeStore and pumps
|
|
107
|
+
// frames both ways, so `useStreamingField` sees the same store it would against
|
|
108
|
+
// a WebSocket host.
|
|
109
|
+
export function connectRealtimePort(port, options = {}) {
|
|
110
|
+
const store = new RealtimeStore({
|
|
111
|
+
send: (frame) => {
|
|
112
|
+
port.postMessage({ event: 'realtime', frame: encodeFrame(frame) });
|
|
113
|
+
},
|
|
114
|
+
staleAfterMs: options.staleAfterMs,
|
|
115
|
+
onError: options.onError,
|
|
116
|
+
});
|
|
117
|
+
const onMessage = (event) => {
|
|
118
|
+
const message = event.data;
|
|
119
|
+
if (!message ||
|
|
120
|
+
typeof message !== 'object' ||
|
|
121
|
+
message.event !== 'realtime') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const raw = message.frame;
|
|
125
|
+
if (typeof raw !== 'string')
|
|
126
|
+
return;
|
|
127
|
+
const frame = decodeFrame(raw);
|
|
128
|
+
if (frame)
|
|
129
|
+
applyHostFrame(store, frame);
|
|
130
|
+
};
|
|
131
|
+
port.addEventListener('message', onMessage);
|
|
132
|
+
port.start();
|
|
133
|
+
return {
|
|
134
|
+
store,
|
|
135
|
+
close: () => {
|
|
136
|
+
port.removeEventListener('message', onMessage);
|
|
137
|
+
store.handleDisconnect();
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=message-port.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-port.js","sourceRoot":"","sources":["../../src/realtime/message-port.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ProducerOptions, RealtimeProducer } from './producer.js';
|
|
2
|
+
export type ProducerSocket = {
|
|
3
|
+
send(data: string): void;
|
|
4
|
+
};
|
|
5
|
+
export type SocketProducer = RealtimeProducer & {
|
|
6
|
+
handleMessage(raw: string): void;
|
|
7
|
+
fail(reason: string): void;
|
|
8
|
+
};
|
|
9
|
+
export declare function createSocketProducer(socket: ProducerSocket, options: ProducerOptions): SocketProducer;
|
|
10
|
+
//# sourceMappingURL=producer-socket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"producer-socket.d.ts","sourceRoot":"","sources":["../../src/realtime/producer-socket.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAItE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG;IAE9C,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAIhC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,eAAe,GACvB,cAAc,CA2EhB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// A PublisherTransport backed by a socket, for a producer that is not in the
|
|
2
|
+
// same process as the hub.
|
|
3
|
+
//
|
|
4
|
+
// This is the shape soot does NOT need and the shape agentbus and chat do: an
|
|
5
|
+
// application server generates the values, and the subscribers are browsers
|
|
6
|
+
// somewhere else, so the updates have to cross a network to reach the hub that
|
|
7
|
+
// fans them out.
|
|
8
|
+
//
|
|
9
|
+
// One socket carries every generation the producer has open. A server writing
|
|
10
|
+
// twenty agent sessions holds one connection, and the hub releases all twenty
|
|
11
|
+
// leases when it drops.
|
|
12
|
+
//
|
|
13
|
+
// Connecting, authenticating, and reconnecting are the caller's: it knows its
|
|
14
|
+
// own deployment. This owns only the frame bookkeeping on top of a socket that
|
|
15
|
+
// is already open.
|
|
16
|
+
import { createProducer } from './producer.js';
|
|
17
|
+
import { decodeFrame, encodeFrame } from './protocol.js';
|
|
18
|
+
export function createSocketProducer(socket, options) {
|
|
19
|
+
const pendingBegins = new Map();
|
|
20
|
+
// streamID -> why it is unusable. Set by a host rejection or supersession, and
|
|
21
|
+
// raised on the producer's next publish for that stream.
|
|
22
|
+
const failures = new Map();
|
|
23
|
+
// The socket itself died. This is deliberately not per-stream: once the
|
|
24
|
+
// connection is gone every generation on it is gone, including ones that
|
|
25
|
+
// began successfully and are therefore absent from `failures`.
|
|
26
|
+
let socketFailure;
|
|
27
|
+
const transport = {
|
|
28
|
+
begin(topic, streamID) {
|
|
29
|
+
if (socketFailure)
|
|
30
|
+
return Promise.reject(new Error(socketFailure));
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
pendingBegins.set(streamID, { resolve, reject });
|
|
33
|
+
socket.send(encodeFrame(['begin', { topic, streamID }]));
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
publish(update) {
|
|
37
|
+
if (socketFailure)
|
|
38
|
+
throw new Error(socketFailure);
|
|
39
|
+
const failure = failures.get(update.streamID);
|
|
40
|
+
if (failure)
|
|
41
|
+
throw new Error(failure);
|
|
42
|
+
socket.send(encodeFrame(['publish', { update }]));
|
|
43
|
+
},
|
|
44
|
+
end(streamID) {
|
|
45
|
+
failures.delete(streamID);
|
|
46
|
+
pendingBegins.delete(streamID);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
...createProducer(transport, options),
|
|
51
|
+
handleMessage(raw) {
|
|
52
|
+
const frame = decodeFrame(raw);
|
|
53
|
+
if (!frame)
|
|
54
|
+
return;
|
|
55
|
+
switch (frame[0]) {
|
|
56
|
+
case 'begin-result': {
|
|
57
|
+
const { streamID, reason } = frame[1];
|
|
58
|
+
const pending = pendingBegins.get(streamID);
|
|
59
|
+
if (!pending)
|
|
60
|
+
return;
|
|
61
|
+
pendingBegins.delete(streamID);
|
|
62
|
+
if (reason === null)
|
|
63
|
+
pending.resolve();
|
|
64
|
+
else
|
|
65
|
+
pending.reject(new Error(reason));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
case 'publish-rejected': {
|
|
69
|
+
const { streamID, reason } = frame[1];
|
|
70
|
+
failures.set(streamID, reason);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
case 'superseded': {
|
|
74
|
+
const { topic, streamID } = frame[1];
|
|
75
|
+
failures.set(streamID, `generation for '${topic}' was superseded by a newer one`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
fail(reason) {
|
|
83
|
+
socketFailure = reason;
|
|
84
|
+
for (const pending of pendingBegins.values())
|
|
85
|
+
pending.reject(new Error(reason));
|
|
86
|
+
pendingBegins.clear();
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=producer-socket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"producer-socket.js","sourceRoot":"","sources":["../../src/realtime/producer-socket.ts"],"names":[],"mappings":"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"}
|