orez-lite 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/cf-do/application-sql.d.ts +10 -3
- package/dist/cf-do/application-sql.d.ts.map +1 -1
- package/dist/cf-do/application-sql.js.map +1 -1
- package/dist/cf-do/lite-data-worker.d.ts.map +1 -1
- package/dist/cf-do/lite-data-worker.js +10 -1
- package/dist/cf-do/lite-data-worker.js.map +1 -1
- package/dist/cf-do/worker.d.ts +1 -1
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +3 -0
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/realtime.d.ts +18 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +18 -0
- package/dist/realtime.js.map +1 -0
- package/package.json +6 -6
- package/dist/realtime/host.d.ts +0 -7
- package/dist/realtime/host.d.ts.map +0 -1
- package/dist/realtime/host.js +0 -77
- package/dist/realtime/host.js.map +0 -1
- package/dist/realtime/hub.d.ts +0 -67
- package/dist/realtime/hub.d.ts.map +0 -1
- package/dist/realtime/hub.js +0 -456
- package/dist/realtime/hub.js.map +0 -1
- package/dist/realtime/index.d.ts +0 -24
- package/dist/realtime/index.d.ts.map +0 -1
- package/dist/realtime/index.js +0 -27
- package/dist/realtime/index.js.map +0 -1
- package/dist/realtime/local.d.ts +0 -15
- package/dist/realtime/local.d.ts.map +0 -1
- package/dist/realtime/local.js +0 -63
- package/dist/realtime/local.js.map +0 -1
- package/dist/realtime/manifest.d.ts +0 -51
- package/dist/realtime/manifest.d.ts.map +0 -1
- package/dist/realtime/manifest.js +0 -110
- package/dist/realtime/manifest.js.map +0 -1
- package/dist/realtime/message-port.d.ts +0 -34
- package/dist/realtime/message-port.d.ts.map +0 -1
- package/dist/realtime/message-port.js +0 -141
- package/dist/realtime/message-port.js.map +0 -1
- package/dist/realtime/producer-socket.d.ts +0 -10
- package/dist/realtime/producer-socket.d.ts.map +0 -1
- package/dist/realtime/producer-socket.js +0 -90
- package/dist/realtime/producer-socket.js.map +0 -1
- package/dist/realtime/producer.d.ts +0 -16
- package/dist/realtime/producer.d.ts.map +0 -1
- package/dist/realtime/producer.js +0 -51
- package/dist/realtime/producer.js.map +0 -1
- package/dist/realtime/protocol.d.ts +0 -99
- package/dist/realtime/protocol.d.ts.map +0 -1
- package/dist/realtime/protocol.js +0 -191
- package/dist/realtime/protocol.js.map +0 -1
- package/dist/realtime/publisher.d.ts +0 -28
- package/dist/realtime/publisher.d.ts.map +0 -1
- package/dist/realtime/publisher.js +0 -309
- package/dist/realtime/publisher.js.map +0 -1
- package/dist/realtime/socket-host.d.ts +0 -25
- package/dist/realtime/socket-host.d.ts.map +0 -1
- package/dist/realtime/socket-host.js +0 -105
- package/dist/realtime/socket-host.js.map +0 -1
- package/dist/realtime/store.d.ts +0 -28
- package/dist/realtime/store.d.ts.map +0 -1
- package/dist/realtime/store.js +0 -0
- package/dist/realtime/store.js.map +0 -1
- package/dist/realtime/writer.d.ts +0 -17
- package/dist/realtime/writer.d.ts.map +0 -1
- package/dist/realtime/writer.js +0 -138
- package/dist/realtime/writer.js.map +0 -1
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
export type RealtimeKeyValue = string | number | boolean | null;
|
|
2
|
-
export type RealtimeTopic = {
|
|
3
|
-
readonly table: string;
|
|
4
|
-
readonly key: Readonly<Record<string, RealtimeKeyValue>>;
|
|
5
|
-
readonly field: string;
|
|
6
|
-
};
|
|
7
|
-
export declare function canonicalPrimaryKey(primaryKey: readonly string[], key: Readonly<Record<string, RealtimeKeyValue>>): string;
|
|
8
|
-
export declare const TOPIC_SEPARATOR = "\u001F";
|
|
9
|
-
export declare function canonicalTopic(primaryKey: readonly string[], topic: RealtimeTopic): string;
|
|
10
|
-
export type SubscribeFrame = readonly ['subscribe', {
|
|
11
|
-
readonly topic: RealtimeTopic;
|
|
12
|
-
}];
|
|
13
|
-
export type UnsubscribeFrame = readonly ['unsubscribe', {
|
|
14
|
-
readonly topic: RealtimeTopic;
|
|
15
|
-
}];
|
|
16
|
-
export type ClientFrame = SubscribeFrame | UnsubscribeFrame;
|
|
17
|
-
export type BeginFrame = readonly [
|
|
18
|
-
'begin',
|
|
19
|
-
{
|
|
20
|
-
readonly topic: RealtimeTopic;
|
|
21
|
-
readonly streamID: string;
|
|
22
|
-
}
|
|
23
|
-
];
|
|
24
|
-
export type PublishFrame = readonly ['publish', {
|
|
25
|
-
readonly update: FieldUpdate;
|
|
26
|
-
}];
|
|
27
|
-
export type ProducerFrame = BeginFrame | PublishFrame;
|
|
28
|
-
export type BeginResultFrame = readonly [
|
|
29
|
-
'begin-result',
|
|
30
|
-
{
|
|
31
|
-
readonly streamID: string;
|
|
32
|
-
readonly topic: string | null;
|
|
33
|
-
readonly reason: string | null;
|
|
34
|
-
}
|
|
35
|
-
];
|
|
36
|
-
export type PublishRejectedFrame = readonly [
|
|
37
|
-
'publish-rejected',
|
|
38
|
-
{
|
|
39
|
-
readonly topic: string;
|
|
40
|
-
readonly streamID: string;
|
|
41
|
-
readonly reason: string;
|
|
42
|
-
}
|
|
43
|
-
];
|
|
44
|
-
export type SupersededFrame = readonly [
|
|
45
|
-
'superseded',
|
|
46
|
-
{
|
|
47
|
-
readonly topic: string;
|
|
48
|
-
readonly streamID: string;
|
|
49
|
-
}
|
|
50
|
-
];
|
|
51
|
-
export type ProducerHostFrame = BeginResultFrame | PublishRejectedFrame | SupersededFrame;
|
|
52
|
-
export type SubscribedFrame = readonly [
|
|
53
|
-
'subscribed',
|
|
54
|
-
{
|
|
55
|
-
readonly topic: string;
|
|
56
|
-
readonly status: 'active' | 'pending';
|
|
57
|
-
}
|
|
58
|
-
];
|
|
59
|
-
export type SubscribeErrorFrame = readonly [
|
|
60
|
-
'subscribe-error',
|
|
61
|
-
{
|
|
62
|
-
readonly topic: string;
|
|
63
|
-
readonly reason: string;
|
|
64
|
-
}
|
|
65
|
-
];
|
|
66
|
-
export type FieldUpdate = {
|
|
67
|
-
readonly topic: string;
|
|
68
|
-
readonly streamID: string;
|
|
69
|
-
readonly seq: number;
|
|
70
|
-
readonly op: 'snapshot';
|
|
71
|
-
readonly value: unknown;
|
|
72
|
-
} | {
|
|
73
|
-
readonly topic: string;
|
|
74
|
-
readonly streamID: string;
|
|
75
|
-
readonly seq: number;
|
|
76
|
-
readonly op: 'append';
|
|
77
|
-
readonly text: string;
|
|
78
|
-
} | {
|
|
79
|
-
readonly topic: string;
|
|
80
|
-
readonly streamID: string;
|
|
81
|
-
readonly seq: number;
|
|
82
|
-
readonly op: 'end';
|
|
83
|
-
} | {
|
|
84
|
-
readonly topic: string;
|
|
85
|
-
readonly streamID: string;
|
|
86
|
-
readonly seq: number;
|
|
87
|
-
readonly op: 'abort';
|
|
88
|
-
};
|
|
89
|
-
export type FieldFrame = readonly ['field', {
|
|
90
|
-
readonly updates: readonly FieldUpdate[];
|
|
91
|
-
}];
|
|
92
|
-
export type WakeFrame = readonly ['wake', Record<string, never>];
|
|
93
|
-
export type HostFrame = FieldFrame | SubscribeErrorFrame | SubscribedFrame | WakeFrame;
|
|
94
|
-
export declare const LEGACY_WAKE_FRAME = "wake";
|
|
95
|
-
export declare function isLegacyWake(raw: string): boolean;
|
|
96
|
-
export type AnyFrame = ClientFrame | HostFrame | ProducerFrame | ProducerHostFrame;
|
|
97
|
-
export declare function encodeFrame(frame: AnyFrame): string;
|
|
98
|
-
export declare function decodeFrame(raw: string): AnyFrame | undefined;
|
|
99
|
-
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/realtime/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
// Wire protocol for orez realtime field streaming.
|
|
2
|
-
//
|
|
3
|
-
// Field values are ephemeral: they never enter the Zero store, never advance a
|
|
4
|
-
// cookie, and never survive a reload. The durable value is whatever the
|
|
5
|
-
// ordinary Zero row says. A stream is a presentation-latency optimization over
|
|
6
|
-
// that row, so a dropped frame costs latency and can never cost correctness.
|
|
7
|
-
//
|
|
8
|
-
// Two publish modes, declared per field in the manifest and fixed for the life
|
|
9
|
-
// of the field, so no host ever branches on frame shape at runtime:
|
|
10
|
-
//
|
|
11
|
-
// - `append` (string columns): a frame carries only the new characters. Cost
|
|
12
|
-
// per frame stays flat as the value grows, which is the whole point for token
|
|
13
|
-
// streams. The host accumulates, so a subscriber that arrives at token 900
|
|
14
|
-
// gets one snapshot and then appends.
|
|
15
|
-
// - `replace` (every other column type): a frame carries the complete value.
|
|
16
|
-
// Correct for any JSON-representable column, and the right cost model for
|
|
17
|
-
// fields that change at semantic-event frequency rather than per token.
|
|
18
|
-
//
|
|
19
|
-
// Recovery is the host's accumulator, not a client-side replay buffer. Frames
|
|
20
|
-
// travel over one WebSocket, so they arrive in order or the socket is gone; a
|
|
21
|
-
// reconnect resubscribes and gets a fresh snapshot. `seq` therefore only has to
|
|
22
|
-
// fence a superseded generation and mark where a snapshot ended.
|
|
23
|
-
// The canonical primary-key encoding, mirroring sync_core::value::canonical_pk
|
|
24
|
-
// byte for byte: primary-key columns in the schema's declared order, each key
|
|
25
|
-
// and value serialized as JSON. Both sides derive topics independently and must
|
|
26
|
-
// land on the same string, so harness/fixtures/canonical-pk-vectors.json is
|
|
27
|
-
// asserted here as well as in the native and wasm builds.
|
|
28
|
-
export function canonicalPrimaryKey(primaryKey, key) {
|
|
29
|
-
let out = '{';
|
|
30
|
-
for (let index = 0; index < primaryKey.length; index++) {
|
|
31
|
-
const column = primaryKey[index];
|
|
32
|
-
if (index > 0)
|
|
33
|
-
out += ',';
|
|
34
|
-
out += `${JSON.stringify(column)}:${JSON.stringify(key[column] ?? null)}`;
|
|
35
|
-
}
|
|
36
|
-
return `${out}}`;
|
|
37
|
-
}
|
|
38
|
-
// ASCII unit separator. A Zero table or column name cannot contain it, and the
|
|
39
|
-
// encoded primary key is JSON where it would appear escaped, so the three parts
|
|
40
|
-
// of a topic can never run together ambiguously. Written as an escape rather
|
|
41
|
-
// than a literal so it stays visible in source.
|
|
42
|
-
export const TOPIC_SEPARATOR = '\u001f';
|
|
43
|
-
// A topic's wire identity. Field frames carry this string rather than the
|
|
44
|
-
// structured topic: it is the map key on both sides, and re-deriving it per
|
|
45
|
-
// frame would be pure overhead.
|
|
46
|
-
export function canonicalTopic(primaryKey, topic) {
|
|
47
|
-
const key = canonicalPrimaryKey(primaryKey, topic.key);
|
|
48
|
-
return `${topic.table}${TOPIC_SEPARATOR}${topic.field}${TOPIC_SEPARATOR}${key}`;
|
|
49
|
-
}
|
|
50
|
-
// The legacy notification-only channel sent the bare text "wake". It stays
|
|
51
|
-
// accepted so an old client against a new host, or a new client against the
|
|
52
|
-
// /wake route during migration, still pulls.
|
|
53
|
-
export const LEGACY_WAKE_FRAME = 'wake';
|
|
54
|
-
export function isLegacyWake(raw) {
|
|
55
|
-
return raw === LEGACY_WAKE_FRAME;
|
|
56
|
-
}
|
|
57
|
-
export function encodeFrame(frame) {
|
|
58
|
-
return JSON.stringify(frame);
|
|
59
|
-
}
|
|
60
|
-
// Parsing is total: a malformed frame returns undefined rather than throwing,
|
|
61
|
-
// because a socket message is attacker-influenced input on the host side and a
|
|
62
|
-
// throw inside a message handler would tear down an otherwise healthy socket.
|
|
63
|
-
export function decodeFrame(raw) {
|
|
64
|
-
if (isLegacyWake(raw))
|
|
65
|
-
return ['wake', {}];
|
|
66
|
-
let parsed;
|
|
67
|
-
try {
|
|
68
|
-
parsed = JSON.parse(raw);
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
if (!Array.isArray(parsed) || parsed.length !== 2)
|
|
74
|
-
return undefined;
|
|
75
|
-
const [kind, body] = parsed;
|
|
76
|
-
if (typeof kind !== 'string' || !body || typeof body !== 'object')
|
|
77
|
-
return undefined;
|
|
78
|
-
switch (kind) {
|
|
79
|
-
case 'subscribe':
|
|
80
|
-
case 'unsubscribe': {
|
|
81
|
-
const topic = body.topic;
|
|
82
|
-
return isTopic(topic) ? [kind, { topic }] : undefined;
|
|
83
|
-
}
|
|
84
|
-
case 'subscribed': {
|
|
85
|
-
const { topic, status } = body;
|
|
86
|
-
if (typeof topic !== 'string')
|
|
87
|
-
return undefined;
|
|
88
|
-
if (status !== 'active' && status !== 'pending')
|
|
89
|
-
return undefined;
|
|
90
|
-
return ['subscribed', { topic, status }];
|
|
91
|
-
}
|
|
92
|
-
case 'subscribe-error': {
|
|
93
|
-
const { topic, reason } = body;
|
|
94
|
-
if (typeof topic !== 'string' || typeof reason !== 'string')
|
|
95
|
-
return undefined;
|
|
96
|
-
return ['subscribe-error', { topic, reason }];
|
|
97
|
-
}
|
|
98
|
-
case 'field': {
|
|
99
|
-
const updates = body.updates;
|
|
100
|
-
if (!Array.isArray(updates))
|
|
101
|
-
return undefined;
|
|
102
|
-
const decoded = [];
|
|
103
|
-
for (const update of updates) {
|
|
104
|
-
const valid = decodeUpdate(update);
|
|
105
|
-
if (!valid)
|
|
106
|
-
return undefined;
|
|
107
|
-
decoded.push(valid);
|
|
108
|
-
}
|
|
109
|
-
return ['field', { updates: decoded }];
|
|
110
|
-
}
|
|
111
|
-
case 'begin': {
|
|
112
|
-
const { topic, streamID } = body;
|
|
113
|
-
if (!isTopic(topic) || typeof streamID !== 'string' || !streamID)
|
|
114
|
-
return undefined;
|
|
115
|
-
return ['begin', { topic, streamID }];
|
|
116
|
-
}
|
|
117
|
-
case 'publish': {
|
|
118
|
-
const update = decodeUpdate(body.update);
|
|
119
|
-
return update ? ['publish', { update }] : undefined;
|
|
120
|
-
}
|
|
121
|
-
case 'begin-result': {
|
|
122
|
-
const { streamID, topic, reason } = body;
|
|
123
|
-
if (typeof streamID !== 'string')
|
|
124
|
-
return undefined;
|
|
125
|
-
if (topic !== null && typeof topic !== 'string')
|
|
126
|
-
return undefined;
|
|
127
|
-
if (reason !== null && typeof reason !== 'string')
|
|
128
|
-
return undefined;
|
|
129
|
-
return ['begin-result', { streamID, topic, reason }];
|
|
130
|
-
}
|
|
131
|
-
case 'publish-rejected': {
|
|
132
|
-
const { topic, streamID, reason } = body;
|
|
133
|
-
if (typeof topic !== 'string' || typeof streamID !== 'string')
|
|
134
|
-
return undefined;
|
|
135
|
-
if (typeof reason !== 'string')
|
|
136
|
-
return undefined;
|
|
137
|
-
return ['publish-rejected', { topic, streamID, reason }];
|
|
138
|
-
}
|
|
139
|
-
case 'superseded': {
|
|
140
|
-
const { topic, streamID } = body;
|
|
141
|
-
if (typeof topic !== 'string' || typeof streamID !== 'string')
|
|
142
|
-
return undefined;
|
|
143
|
-
return ['superseded', { topic, streamID }];
|
|
144
|
-
}
|
|
145
|
-
case 'wake':
|
|
146
|
-
return ['wake', {}];
|
|
147
|
-
default:
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function decodeUpdate(update) {
|
|
152
|
-
if (!update || typeof update !== 'object')
|
|
153
|
-
return undefined;
|
|
154
|
-
const { topic, streamID, seq, op } = update;
|
|
155
|
-
if (typeof topic !== 'string' || typeof streamID !== 'string')
|
|
156
|
-
return undefined;
|
|
157
|
-
if (typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < 0)
|
|
158
|
-
return undefined;
|
|
159
|
-
switch (op) {
|
|
160
|
-
case 'snapshot':
|
|
161
|
-
return { topic, streamID, seq, op, value: update.value };
|
|
162
|
-
case 'append': {
|
|
163
|
-
const text = update.text;
|
|
164
|
-
return typeof text === 'string' ? { topic, streamID, seq, op, text } : undefined;
|
|
165
|
-
}
|
|
166
|
-
case 'abort':
|
|
167
|
-
case 'end':
|
|
168
|
-
return { topic, streamID, seq, op };
|
|
169
|
-
default:
|
|
170
|
-
return undefined;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
function isTopic(value) {
|
|
174
|
-
if (!value || typeof value !== 'object')
|
|
175
|
-
return false;
|
|
176
|
-
const { table, key, field } = value;
|
|
177
|
-
if (typeof table !== 'string' || !table)
|
|
178
|
-
return false;
|
|
179
|
-
if (typeof field !== 'string' || !field)
|
|
180
|
-
return false;
|
|
181
|
-
if (!key || typeof key !== 'object' || Array.isArray(key))
|
|
182
|
-
return false;
|
|
183
|
-
for (const entry of Object.values(key)) {
|
|
184
|
-
const type = typeof entry;
|
|
185
|
-
if (entry !== null && type !== 'string' && type !== 'number' && type !== 'boolean') {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return true;
|
|
190
|
-
}
|
|
191
|
-
//# sourceMappingURL=protocol.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/realtime/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { StreamingManifest } from './manifest.js';
|
|
2
|
-
import type { FieldUpdate, RealtimeTopic } from './protocol.js';
|
|
3
|
-
export type PublisherTransport = {
|
|
4
|
-
readonly publish: (update: FieldUpdate) => Promise<void> | void;
|
|
5
|
-
readonly begin: (topic: RealtimeTopic, streamID: string) => Promise<void> | void;
|
|
6
|
-
readonly end: (streamID: string) => Promise<void> | void;
|
|
7
|
-
};
|
|
8
|
-
export type BeginOptions = {
|
|
9
|
-
readonly namespace: string;
|
|
10
|
-
readonly key: Readonly<Record<string, boolean | null | number | string>>;
|
|
11
|
-
};
|
|
12
|
-
export type StreamSession<Value> = {
|
|
13
|
-
readonly streamID: string;
|
|
14
|
-
readonly topic: RealtimeTopic;
|
|
15
|
-
set(value: Value): void;
|
|
16
|
-
flush(): Promise<void>;
|
|
17
|
-
finish(value: Value, commit: () => Promise<void>): Promise<void>;
|
|
18
|
-
abort(): Promise<void>;
|
|
19
|
-
};
|
|
20
|
-
export declare class RealtimePublisher {
|
|
21
|
-
#private;
|
|
22
|
-
constructor(transport: PublisherTransport, manifest: StreamingManifest, options?: {
|
|
23
|
-
readonly now?: () => number;
|
|
24
|
-
readonly randomID?: () => string;
|
|
25
|
-
});
|
|
26
|
-
begin<Value = string>(table: string, field: string, options: BeginOptions): Promise<StreamSession<Value>>;
|
|
27
|
-
}
|
|
28
|
-
//# sourceMappingURL=publisher.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../src/realtime/publisher.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAsB,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAE/D,MAAM,MAAM,kBAAkB,GAAG;IAG/B,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAG/D,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChF,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACzD,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,CAAA;CACzE,CAAA;AAED,MAAM,MAAM,aAAa,CAAC,KAAK,IAAI;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;IAI7B,GAAG,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IAEvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB,CAAA;AAcD,qBAAa,iBAAiB;;gBAO1B,SAAS,EAAE,kBAAkB,EAC7B,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE;QAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,MAAM,CAAA;KAAO;IAQ3E,KAAK,CAAC,KAAK,GAAG,MAAM,EACxB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;CAyBjC"}
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
// The producer side of a streaming field.
|
|
2
|
-
//
|
|
3
|
-
// A producer opens one generation per field, pushes values, and finishes by
|
|
4
|
-
// running the application's authoritative durable write. Orez never performs
|
|
5
|
-
// that write: in a delegated-push deployment the application worker is the
|
|
6
|
-
// write authority, so the commit is a callback the application supplies.
|
|
7
|
-
//
|
|
8
|
-
// The transport is injected. Trusted server code gets an in-process publisher
|
|
9
|
-
// on the native and local hosts; on Cloudflare the same interface is backed by
|
|
10
|
-
// one private service-bound producer WebSocket per generation.
|
|
11
|
-
import { canonicalTopic } from './protocol.js';
|
|
12
|
-
export class RealtimePublisher {
|
|
13
|
-
#transport;
|
|
14
|
-
#manifest;
|
|
15
|
-
#now;
|
|
16
|
-
#randomID;
|
|
17
|
-
constructor(transport, manifest, options = {}) {
|
|
18
|
-
this.#transport = transport;
|
|
19
|
-
this.#manifest = manifest;
|
|
20
|
-
this.#now = options.now ?? (() => Date.now());
|
|
21
|
-
this.#randomID = options.randomID ?? (() => crypto.randomUUID());
|
|
22
|
-
}
|
|
23
|
-
async begin(table, field, options) {
|
|
24
|
-
const spec = this.#manifest.fields.get(`${table}.${field}`);
|
|
25
|
-
if (!spec) {
|
|
26
|
-
throw new TypeError(`'${table}.${field}' is not a streaming field in this manifest`);
|
|
27
|
-
}
|
|
28
|
-
const topic = { table, key: options.key, field };
|
|
29
|
-
for (const column of spec.primaryKey) {
|
|
30
|
-
if (options.key[column] === undefined) {
|
|
31
|
-
throw new TypeError(`streaming topic '${table}.${field}' is missing primary key column '${column}'`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const streamID = this.#randomID();
|
|
35
|
-
await this.#transport.begin(topic, streamID);
|
|
36
|
-
return new Session(this.#transport, spec, topic, canonicalTopic(spec.primaryKey, topic), streamID, this.#now);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
class Session {
|
|
40
|
-
streamID;
|
|
41
|
-
topic;
|
|
42
|
-
#transport;
|
|
43
|
-
#spec;
|
|
44
|
-
#id;
|
|
45
|
-
#now;
|
|
46
|
-
#seq = 0;
|
|
47
|
-
// what the subscribers have been told, so append mode can ship the suffix
|
|
48
|
-
#delivered = '';
|
|
49
|
-
#started = false;
|
|
50
|
-
#closed = false;
|
|
51
|
-
#pending;
|
|
52
|
-
#inFlight = Promise.resolve();
|
|
53
|
-
// A background flush cannot reject into the caller's stack, so a transport
|
|
54
|
-
// failure (most often: this generation was superseded by a retry) is held
|
|
55
|
-
// here and raised on the next set/finish. Without this the failure would
|
|
56
|
-
// surface as an unhandled rejection and the producer would keep writing into
|
|
57
|
-
// a stream nobody accepts.
|
|
58
|
-
#failure;
|
|
59
|
-
constructor(transport, spec, topic, id, streamID, now) {
|
|
60
|
-
this.#transport = transport;
|
|
61
|
-
this.#spec = spec;
|
|
62
|
-
this.topic = topic;
|
|
63
|
-
this.#id = id;
|
|
64
|
-
this.streamID = streamID;
|
|
65
|
-
this.#now = now;
|
|
66
|
-
this.#pending = {
|
|
67
|
-
value: undefined,
|
|
68
|
-
windowBytes: 0,
|
|
69
|
-
windowUpdates: 0,
|
|
70
|
-
windowStartedAt: now(),
|
|
71
|
-
timer: undefined,
|
|
72
|
-
flushing: false,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
set(value) {
|
|
76
|
-
this.#throwIfUnusable();
|
|
77
|
-
this.#validate(value);
|
|
78
|
-
this.#pending.value = value;
|
|
79
|
-
this.#schedule();
|
|
80
|
-
}
|
|
81
|
-
// Deliver whatever is pending now, waiting only as long as the manifest's
|
|
82
|
-
// rate bounds require. `set` is fire-and-forget by design so a token loop
|
|
83
|
-
// never awaits; this is for the places a producer needs the value to have
|
|
84
|
-
// actually left: a semantic checkpoint, or a deterministic test.
|
|
85
|
-
//
|
|
86
|
-
// It waits rather than bypassing the bound, so a producer calling flush in a
|
|
87
|
-
// loop still cannot exceed its declared maxUpdatesPerSecond.
|
|
88
|
-
async flush() {
|
|
89
|
-
this.#throwIfUnusable();
|
|
90
|
-
if (this.#pending.value === undefined)
|
|
91
|
-
return;
|
|
92
|
-
const delay = this.#delayUntilAllowed();
|
|
93
|
-
if (delay > 0)
|
|
94
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
95
|
-
// A scheduled flush can fire during that wait, and if it failed then this
|
|
96
|
-
// generation is dead and its pending value was never delivered. Re-checking
|
|
97
|
-
// is what keeps flush() honest: it either delivered, or it throws. Without
|
|
98
|
-
// it the scheduled flush's suffix bookkeeping makes the retry here a no-op
|
|
99
|
-
// and the producer is told its value went out when it did not.
|
|
100
|
-
this.#throwIfUnusable();
|
|
101
|
-
this.#cancelTimer();
|
|
102
|
-
await this.#flush(false);
|
|
103
|
-
}
|
|
104
|
-
async finish(value, commit) {
|
|
105
|
-
this.#throwIfUnusable();
|
|
106
|
-
this.#validate(value);
|
|
107
|
-
this.#pending.value = value;
|
|
108
|
-
this.#cancelTimer();
|
|
109
|
-
// 1. flush the final value so subscribers see it before the commit round-trip
|
|
110
|
-
await this.#flush(true);
|
|
111
|
-
// 2. the application's authoritative durable write. Orez cannot do this
|
|
112
|
-
// generically: in a delegated-push deployment the application worker is
|
|
113
|
-
// the write authority.
|
|
114
|
-
await commit();
|
|
115
|
-
// 3. `end` puts every subscriber in `committing`, holding the final overlay
|
|
116
|
-
// until their own Zero query produces the committed value. No waiting on
|
|
117
|
-
// replication here: the client's durable-equality check is what ends the
|
|
118
|
-
// overlay, and blocking the generation on replica observation would
|
|
119
|
-
// couple producer lifetime to pull lag for no extra safety.
|
|
120
|
-
this.#closed = true;
|
|
121
|
-
await this.#emit({
|
|
122
|
-
topic: this.#id,
|
|
123
|
-
streamID: this.streamID,
|
|
124
|
-
seq: ++this.#seq,
|
|
125
|
-
op: 'end',
|
|
126
|
-
});
|
|
127
|
-
await this.#transport.end(this.streamID);
|
|
128
|
-
}
|
|
129
|
-
async abort() {
|
|
130
|
-
if (this.#closed)
|
|
131
|
-
return;
|
|
132
|
-
this.#closed = true;
|
|
133
|
-
this.#cancelTimer();
|
|
134
|
-
await this.#emit({
|
|
135
|
-
topic: this.#id,
|
|
136
|
-
streamID: this.streamID,
|
|
137
|
-
seq: ++this.#seq,
|
|
138
|
-
op: 'abort',
|
|
139
|
-
});
|
|
140
|
-
await this.#transport.end(this.streamID);
|
|
141
|
-
}
|
|
142
|
-
#throwIfUnusable() {
|
|
143
|
-
if (this.#failure)
|
|
144
|
-
throw this.#failure;
|
|
145
|
-
if (this.#closed)
|
|
146
|
-
throw new Error('streaming field generation is already closed');
|
|
147
|
-
}
|
|
148
|
-
#validate(value) {
|
|
149
|
-
if (this.#spec.mode === 'append' && typeof value !== 'string') {
|
|
150
|
-
throw new TypeError(`streaming field '${this.#spec.table}.${this.#spec.field}' is append mode and takes a string`);
|
|
151
|
-
}
|
|
152
|
-
if (this.#spec.validate && !this.#spec.validate(value)) {
|
|
153
|
-
throw new TypeError(`streaming field '${this.#spec.table}.${this.#spec.field}' rejected a value in validate()`);
|
|
154
|
-
}
|
|
155
|
-
const bytes = byteLength(typeof value === 'string' ? value : JSON.stringify(value ?? null));
|
|
156
|
-
if (bytes > this.#spec.maxBytes) {
|
|
157
|
-
throw new RangeError(`streaming field '${this.#spec.table}.${this.#spec.field}' value is ${bytes} bytes, over its ${this.#spec.maxBytes} maxBytes`);
|
|
158
|
-
}
|
|
159
|
-
if (this.#spec.mode === 'append' && this.#started) {
|
|
160
|
-
// append ships suffixes, so the value must only ever grow. A producer that
|
|
161
|
-
// rewrites history needs `mode: 'replace'`; silently shipping a bogus
|
|
162
|
-
// suffix would leave every subscriber with a corrupted value.
|
|
163
|
-
const next = value;
|
|
164
|
-
if (!next.startsWith(this.#delivered)) {
|
|
165
|
-
// A text column holding JSON is the common way to hit this: the
|
|
166
|
-
// manifest infers append from the Zero column type, but the producer
|
|
167
|
-
// replaces the value rather than extending it. Naming the fix here
|
|
168
|
-
// saves the reader from inferring it from a corrupted overlay.
|
|
169
|
-
throw new TypeError(`streaming field '${this.#spec.table}.${this.#spec.field}' is append mode, but its new value is not an extension of what was already sent. ` +
|
|
170
|
-
`Append ships only the added suffix, so the value must grow. If this field is replaced rather than extended (a JSON payload in a text column, for example), declare mode: 'replace' for it in the manifest.`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
#schedule() {
|
|
175
|
-
if (this.#pending.timer || this.#pending.flushing || this.#failure)
|
|
176
|
-
return;
|
|
177
|
-
const delay = this.#delayUntilAllowed();
|
|
178
|
-
if (delay === 0) {
|
|
179
|
-
void this.#flush(false).catch((error) => this.#fail(error));
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
this.#pending.timer = setTimeout(() => {
|
|
183
|
-
this.#pending.timer = undefined;
|
|
184
|
-
void this.#flush(false).catch((error) => this.#fail(error));
|
|
185
|
-
}, delay);
|
|
186
|
-
}
|
|
187
|
-
// How long before another update may go out, honouring BOTH manifest bounds.
|
|
188
|
-
// In append mode the byte budget is charged against new bytes only, which is
|
|
189
|
-
// what keeps a long generation's update rate flat instead of degrading as the
|
|
190
|
-
// accumulated value grows.
|
|
191
|
-
#delayUntilAllowed() {
|
|
192
|
-
const now = this.#now();
|
|
193
|
-
const elapsed = now - this.#pending.windowStartedAt;
|
|
194
|
-
if (elapsed >= 1000)
|
|
195
|
-
return 0;
|
|
196
|
-
const newBytes = this.#pendingBytes();
|
|
197
|
-
const overUpdates = this.#pending.windowUpdates >= this.#spec.maxUpdatesPerSecond;
|
|
198
|
-
const overBytes = this.#pending.windowBytes + newBytes > this.#spec.maxBytesPerSecond;
|
|
199
|
-
if (!overUpdates && !overBytes) {
|
|
200
|
-
// spread updates evenly rather than bursting then starving
|
|
201
|
-
const minGap = Math.floor(1000 / this.#spec.maxUpdatesPerSecond);
|
|
202
|
-
const sinceWindowStart = elapsed;
|
|
203
|
-
const expected = this.#pending.windowUpdates * minGap;
|
|
204
|
-
return Math.max(0, expected - sinceWindowStart);
|
|
205
|
-
}
|
|
206
|
-
return 1000 - elapsed;
|
|
207
|
-
}
|
|
208
|
-
#pendingBytes() {
|
|
209
|
-
const value = this.#pending.value;
|
|
210
|
-
if (this.#spec.mode === 'append') {
|
|
211
|
-
const text = value ?? '';
|
|
212
|
-
return byteLength(text.slice(this.#delivered.length));
|
|
213
|
-
}
|
|
214
|
-
return byteLength(JSON.stringify(value ?? null));
|
|
215
|
-
}
|
|
216
|
-
async #flush(final) {
|
|
217
|
-
const value = this.#pending.value;
|
|
218
|
-
if (value === undefined)
|
|
219
|
-
return;
|
|
220
|
-
this.#pending.flushing = true;
|
|
221
|
-
try {
|
|
222
|
-
const now = this.#now();
|
|
223
|
-
if (now - this.#pending.windowStartedAt >= 1000) {
|
|
224
|
-
this.#pending.windowStartedAt = now;
|
|
225
|
-
this.#pending.windowBytes = 0;
|
|
226
|
-
this.#pending.windowUpdates = 0;
|
|
227
|
-
}
|
|
228
|
-
let update;
|
|
229
|
-
if (this.#spec.mode === 'append' && this.#started) {
|
|
230
|
-
const text = value.slice(this.#delivered.length);
|
|
231
|
-
if (!text && !final)
|
|
232
|
-
return;
|
|
233
|
-
update = {
|
|
234
|
-
topic: this.#id,
|
|
235
|
-
streamID: this.streamID,
|
|
236
|
-
seq: ++this.#seq,
|
|
237
|
-
op: 'append',
|
|
238
|
-
text,
|
|
239
|
-
};
|
|
240
|
-
this.#delivered = value;
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
update = {
|
|
244
|
-
topic: this.#id,
|
|
245
|
-
streamID: this.streamID,
|
|
246
|
-
seq: ++this.#seq,
|
|
247
|
-
op: 'snapshot',
|
|
248
|
-
value,
|
|
249
|
-
};
|
|
250
|
-
this.#started = true;
|
|
251
|
-
this.#delivered = this.#spec.mode === 'append' ? value : '';
|
|
252
|
-
}
|
|
253
|
-
this.#pending.windowBytes += this.#pendingBytes();
|
|
254
|
-
this.#pending.windowUpdates++;
|
|
255
|
-
await this.#emit(update);
|
|
256
|
-
}
|
|
257
|
-
finally {
|
|
258
|
-
this.#pending.flushing = false;
|
|
259
|
-
}
|
|
260
|
-
// a value that arrived while this flush was in flight still needs sending
|
|
261
|
-
if (!final && this.#pendingHasNewBytes())
|
|
262
|
-
this.#schedule();
|
|
263
|
-
}
|
|
264
|
-
#pendingHasNewBytes() {
|
|
265
|
-
if (this.#spec.mode !== 'append')
|
|
266
|
-
return false;
|
|
267
|
-
const text = this.#pending.value ?? '';
|
|
268
|
-
return text.length > this.#delivered.length;
|
|
269
|
-
}
|
|
270
|
-
#cancelTimer() {
|
|
271
|
-
if (this.#pending.timer) {
|
|
272
|
-
clearTimeout(this.#pending.timer);
|
|
273
|
-
this.#pending.timer = undefined;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
// Serialize every emit for the generation. WebSocket ordering owns frame
|
|
277
|
-
// order on the wire, but two overlapping flushes could otherwise hand the
|
|
278
|
-
// transport its frames out of sequence.
|
|
279
|
-
#emit(update) {
|
|
280
|
-
this.#inFlight = this.#inFlight.then(() => this.#transport.publish(update));
|
|
281
|
-
return this.#inFlight;
|
|
282
|
-
}
|
|
283
|
-
// Record a background failure and stop the generation. `finish` and `abort`
|
|
284
|
-
// await their emits directly, so they still reject into the caller; only the
|
|
285
|
-
// scheduled flushes route through here.
|
|
286
|
-
#fail(error) {
|
|
287
|
-
this.#failure = error instanceof Error ? error : new Error(String(error));
|
|
288
|
-
this.#closed = true;
|
|
289
|
-
this.#cancelTimer();
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
function byteLength(value) {
|
|
293
|
-
let bytes = 0;
|
|
294
|
-
for (let index = 0; index < value.length; index++) {
|
|
295
|
-
const code = value.charCodeAt(index);
|
|
296
|
-
if (code < 0x80)
|
|
297
|
-
bytes += 1;
|
|
298
|
-
else if (code < 0x800)
|
|
299
|
-
bytes += 2;
|
|
300
|
-
else if (code >= 0xd800 && code < 0xdc00) {
|
|
301
|
-
bytes += 4;
|
|
302
|
-
index++;
|
|
303
|
-
}
|
|
304
|
-
else
|
|
305
|
-
bytes += 3;
|
|
306
|
-
}
|
|
307
|
-
return bytes;
|
|
308
|
-
}
|
|
309
|
-
//# sourceMappingURL=publisher.js.map
|