@amalgm/live 0.2.29 → 0.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PURPOSE.md +8 -2
- package/dist/records/index.d.ts +2 -0
- package/dist/records/index.d.ts.map +1 -1
- package/dist/records/index.js +2 -0
- package/dist/records/index.js.map +1 -1
- package/dist/records/rail.d.ts +95 -0
- package/dist/records/rail.d.ts.map +1 -0
- package/dist/records/rail.js +244 -0
- package/dist/records/rail.js.map +1 -0
- package/dist/records/wire.d.ts +63 -0
- package/dist/records/wire.d.ts.map +1 -0
- package/dist/records/wire.js +86 -0
- package/dist/records/wire.js.map +1 -0
- package/dist-cjs/records/index.js +2 -0
- package/dist-cjs/records/index.js.map +1 -1
- package/dist-cjs/records/rail.js +248 -0
- package/dist-cjs/records/rail.js.map +1 -0
- package/dist-cjs/records/wire.js +91 -0
- package/dist-cjs/records/wire.js.map +1 -0
- package/package.json +1 -1
package/PURPOSE.md
CHANGED
|
@@ -217,6 +217,12 @@ its cutover.
|
|
|
217
217
|
`basedOnState`, `resultState`, and the last official sequence for that
|
|
218
218
|
entity known at Record time. The authority preserves those facts and never
|
|
219
219
|
uses them to reject or reorder an otherwise valid write.
|
|
220
|
+
38. **Realtime wakes; cursors recover.** An accepted entity-record insert
|
|
221
|
+
emits a small availability signal naming its authority. A connected rail
|
|
222
|
+
reacts immediately and never polls.
|
|
223
|
+
Startup and reconnect subscribe before reading the durable authority tail,
|
|
224
|
+
so a delayed, repeated, or missed signal cannot create a gap; Postgres and
|
|
225
|
+
the local cursors remain truth, while realtime is only the doorbell.
|
|
220
226
|
|
|
221
227
|
## Five surfaces, one behavior
|
|
222
228
|
|
|
@@ -238,8 +244,8 @@ src/contracts/ value types + validators: events, snapshots, resource ids,
|
|
|
238
244
|
content manifests, frames (SSE /
|
|
239
245
|
gateway socket / tunnel), doc-write merge outcomes,
|
|
240
246
|
presence.
|
|
241
|
-
src/records/ entity record contracts
|
|
242
|
-
|
|
247
|
+
src/records/ entity record contracts, the event-driven Send/Receive rail,
|
|
248
|
+
and the local-outbox, authority, and per-entity inbox laws.
|
|
243
249
|
src/machines/ transport-neutral state machines: bootstrap barrier, presence
|
|
244
250
|
TTL, slot arbitration, chunk reassembly, backoff.
|
|
245
251
|
src/entities/ the entity model's rule surface: the six-field record,
|
package/dist/records/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/records/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/records/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
package/dist/records/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/records/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/records/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** Portable, event-driven Send and Receive orchestration. Hosts inject effects. */
|
|
2
|
+
import { type OutboxRow } from './outbox.js';
|
|
3
|
+
import type { AcceptedEntityRecord, LocalEntityRecord } from './contracts.js';
|
|
4
|
+
export declare const ENTITY_RECORD_DELIVERY_LIMIT = 256;
|
|
5
|
+
export declare const ENTITY_RECORD_RETRY_BASE_MS = 1000;
|
|
6
|
+
export declare const ENTITY_RECORD_RETRY_MAX_MS = 30000;
|
|
7
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
8
|
+
export type PendingEntityRecord = Extract<OutboxRow, {
|
|
9
|
+
readonly status: 'pending';
|
|
10
|
+
}> & {
|
|
11
|
+
readonly localSequence: number;
|
|
12
|
+
};
|
|
13
|
+
export interface DeliveredEntityRecord {
|
|
14
|
+
readonly deliverySequence: number;
|
|
15
|
+
readonly record: AcceptedEntityRecord;
|
|
16
|
+
}
|
|
17
|
+
export interface EntityRecordAvailability {
|
|
18
|
+
readonly authorityId: string;
|
|
19
|
+
}
|
|
20
|
+
export interface EntityRecordGap {
|
|
21
|
+
readonly entityId: string;
|
|
22
|
+
readonly after: number;
|
|
23
|
+
readonly received: number;
|
|
24
|
+
}
|
|
25
|
+
export type ReceiveEntityDeliveriesResult = {
|
|
26
|
+
readonly kind: 'stored';
|
|
27
|
+
readonly deliveryCursor: number;
|
|
28
|
+
} | {
|
|
29
|
+
readonly kind: 'gap';
|
|
30
|
+
readonly gap: EntityRecordGap;
|
|
31
|
+
};
|
|
32
|
+
/** One persistent host-owned transaction boundary for the two local queues. */
|
|
33
|
+
export interface EntityRecordStorePort {
|
|
34
|
+
readPending(): MaybePromise<readonly PendingEntityRecord[]>;
|
|
35
|
+
stampAccepted(row: PendingEntityRecord, accepted: AcceptedEntityRecord): MaybePromise<void>;
|
|
36
|
+
readDeliveryCursor(authorityId: string): MaybePromise<number>;
|
|
37
|
+
receive(input: {
|
|
38
|
+
readonly authorityId: string;
|
|
39
|
+
readonly deliveries: readonly DeliveredEntityRecord[];
|
|
40
|
+
/** False only while repairing one entity gap. */
|
|
41
|
+
readonly advanceDeliveryCursor: boolean;
|
|
42
|
+
}): MaybePromise<ReceiveEntityDeliveriesResult>;
|
|
43
|
+
}
|
|
44
|
+
export interface EntityRecordCargoPort {
|
|
45
|
+
ensure(record: LocalEntityRecord): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
/** Authenticated authority effects. `subscribe` registers before it reads. */
|
|
48
|
+
export interface EntityRecordAuthorityPort {
|
|
49
|
+
submit(record: LocalEntityRecord): Promise<AcceptedEntityRecord>;
|
|
50
|
+
subscribe(input: {
|
|
51
|
+
readonly authorityId: string;
|
|
52
|
+
readonly afterDeliverySequence: number;
|
|
53
|
+
readonly limit: number;
|
|
54
|
+
}): Promise<readonly DeliveredEntityRecord[]>;
|
|
55
|
+
backfill(input: {
|
|
56
|
+
readonly authorityId: string;
|
|
57
|
+
readonly entityId: string;
|
|
58
|
+
readonly afterGlobalSequence: number;
|
|
59
|
+
readonly throughGlobalSequence: number;
|
|
60
|
+
}): Promise<readonly DeliveredEntityRecord[]>;
|
|
61
|
+
onAvailable(listener: (signal: EntityRecordAvailability) => void): () => void;
|
|
62
|
+
onDisconnect(listener: (error: unknown) => void): () => void;
|
|
63
|
+
}
|
|
64
|
+
export interface EntityRecordSchedulePort {
|
|
65
|
+
schedule(delayMs: number, callback: () => void): {
|
|
66
|
+
cancel(): void;
|
|
67
|
+
};
|
|
68
|
+
random(): number;
|
|
69
|
+
}
|
|
70
|
+
export interface EntityRecordRailOptions {
|
|
71
|
+
readonly authorityId: string;
|
|
72
|
+
readonly store: EntityRecordStorePort;
|
|
73
|
+
readonly cargo: EntityRecordCargoPort;
|
|
74
|
+
readonly authority: EntityRecordAuthorityPort;
|
|
75
|
+
readonly schedule: EntityRecordSchedulePort;
|
|
76
|
+
readonly onBackgroundError?: (error: unknown) => void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* One rail has only two reasons to run: local work exists, or authority work
|
|
80
|
+
* may exist. Repeated reasons coalesce while the current run is in flight.
|
|
81
|
+
*/
|
|
82
|
+
export declare class EntityRecordRail {
|
|
83
|
+
#private;
|
|
84
|
+
constructor(options: EntityRecordRailOptions);
|
|
85
|
+
/** Subscribe first, catch up from the durable cursor, then drain local work. */
|
|
86
|
+
start(): Promise<void>;
|
|
87
|
+
/** Record calls this only after its local outbox transaction commits. */
|
|
88
|
+
localRecordsAvailable(): void;
|
|
89
|
+
/** Await all work known at this boundary; it never creates an idle query. */
|
|
90
|
+
flush(): Promise<void>;
|
|
91
|
+
/** Stop accepting events. Closing the injected wire may interrupt a request. */
|
|
92
|
+
stop(): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
export {};
|
|
95
|
+
//# sourceMappingURL=rail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rail.d.ts","sourceRoot":"","sources":["../../src/records/rail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/** Portable, event-driven Send and Receive orchestration. Hosts inject effects. */
|
|
2
|
+
import { nextEntitySends } from './outbox.js';
|
|
3
|
+
export const ENTITY_RECORD_DELIVERY_LIMIT = 256;
|
|
4
|
+
export const ENTITY_RECORD_RETRY_BASE_MS = 1_000;
|
|
5
|
+
export const ENTITY_RECORD_RETRY_MAX_MS = 30_000;
|
|
6
|
+
/**
|
|
7
|
+
* One rail has only two reasons to run: local work exists, or authority work
|
|
8
|
+
* may exist. Repeated reasons coalesce while the current run is in flight.
|
|
9
|
+
*/
|
|
10
|
+
export class EntityRecordRail {
|
|
11
|
+
#authorityId;
|
|
12
|
+
#store;
|
|
13
|
+
#cargo;
|
|
14
|
+
#authority;
|
|
15
|
+
#schedule;
|
|
16
|
+
#onBackgroundError;
|
|
17
|
+
#started = false;
|
|
18
|
+
#stopped = false;
|
|
19
|
+
#subscribed = false;
|
|
20
|
+
#needsSubscribe = false;
|
|
21
|
+
#needsSend = false;
|
|
22
|
+
#running = null;
|
|
23
|
+
#retry = null;
|
|
24
|
+
#retryAttempt = 0;
|
|
25
|
+
#removeAvailable = null;
|
|
26
|
+
#removeDisconnect = null;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
if (!options.authorityId.trim())
|
|
29
|
+
throw new Error('entity record authority is required');
|
|
30
|
+
this.#authorityId = options.authorityId;
|
|
31
|
+
this.#store = options.store;
|
|
32
|
+
this.#cargo = options.cargo;
|
|
33
|
+
this.#authority = options.authority;
|
|
34
|
+
this.#schedule = options.schedule;
|
|
35
|
+
this.#onBackgroundError = options.onBackgroundError ?? (() => undefined);
|
|
36
|
+
}
|
|
37
|
+
/** Subscribe first, catch up from the durable cursor, then drain local work. */
|
|
38
|
+
async start() {
|
|
39
|
+
if (this.#started)
|
|
40
|
+
throw new Error('entity record rail already started');
|
|
41
|
+
if (this.#stopped)
|
|
42
|
+
throw new Error('entity record rail is stopped');
|
|
43
|
+
this.#started = true;
|
|
44
|
+
this.#removeAvailable = this.#authority.onAvailable((signal) => {
|
|
45
|
+
if (signal.authorityId !== this.#authorityId || this.#stopped)
|
|
46
|
+
return;
|
|
47
|
+
this.#needsSubscribe = true;
|
|
48
|
+
this.#kick();
|
|
49
|
+
});
|
|
50
|
+
this.#removeDisconnect = this.#authority.onDisconnect(() => {
|
|
51
|
+
if (this.#stopped)
|
|
52
|
+
return;
|
|
53
|
+
this.#subscribed = false;
|
|
54
|
+
this.#needsSubscribe = true;
|
|
55
|
+
this.#scheduleRetry();
|
|
56
|
+
});
|
|
57
|
+
this.#needsSubscribe = true;
|
|
58
|
+
this.#needsSend = true;
|
|
59
|
+
await this.#runExplicitly();
|
|
60
|
+
}
|
|
61
|
+
/** Record calls this only after its local outbox transaction commits. */
|
|
62
|
+
localRecordsAvailable() {
|
|
63
|
+
if (this.#stopped)
|
|
64
|
+
return;
|
|
65
|
+
this.#needsSend = true;
|
|
66
|
+
if (this.#started)
|
|
67
|
+
this.#kick();
|
|
68
|
+
}
|
|
69
|
+
/** Await all work known at this boundary; it never creates an idle query. */
|
|
70
|
+
async flush() {
|
|
71
|
+
if (!this.#started)
|
|
72
|
+
throw new Error('entity record rail has not started');
|
|
73
|
+
if (this.#stopped)
|
|
74
|
+
return;
|
|
75
|
+
this.#needsSend = true;
|
|
76
|
+
if (!this.#subscribed)
|
|
77
|
+
this.#needsSubscribe = true;
|
|
78
|
+
await this.#runExplicitly();
|
|
79
|
+
}
|
|
80
|
+
/** Stop accepting events. Closing the injected wire may interrupt a request. */
|
|
81
|
+
async stop() {
|
|
82
|
+
if (this.#stopped)
|
|
83
|
+
return;
|
|
84
|
+
this.#stopped = true;
|
|
85
|
+
this.#cancelRetry();
|
|
86
|
+
this.#removeAvailable?.();
|
|
87
|
+
this.#removeAvailable = null;
|
|
88
|
+
this.#removeDisconnect?.();
|
|
89
|
+
this.#removeDisconnect = null;
|
|
90
|
+
await this.#running?.catch(() => undefined);
|
|
91
|
+
}
|
|
92
|
+
async #runExplicitly() {
|
|
93
|
+
this.#cancelRetry();
|
|
94
|
+
try {
|
|
95
|
+
await this.#ensureRun();
|
|
96
|
+
this.#retryAttempt = 0;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.#scheduleRetry();
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
#kick() {
|
|
104
|
+
if (!this.#started || this.#stopped)
|
|
105
|
+
return;
|
|
106
|
+
this.#cancelRetry();
|
|
107
|
+
void this.#ensureRun().then(() => {
|
|
108
|
+
this.#retryAttempt = 0;
|
|
109
|
+
}, (error) => {
|
|
110
|
+
this.#onBackgroundError(error);
|
|
111
|
+
this.#scheduleRetry();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
#ensureRun() {
|
|
115
|
+
if (this.#running)
|
|
116
|
+
return this.#running;
|
|
117
|
+
const running = this.#drain();
|
|
118
|
+
this.#running = running;
|
|
119
|
+
void running.then(() => {
|
|
120
|
+
if (this.#running === running)
|
|
121
|
+
this.#running = null;
|
|
122
|
+
if (!this.#stopped && (this.#needsSubscribe || this.#needsSend))
|
|
123
|
+
this.#kick();
|
|
124
|
+
}, () => {
|
|
125
|
+
if (this.#running === running)
|
|
126
|
+
this.#running = null;
|
|
127
|
+
});
|
|
128
|
+
return running;
|
|
129
|
+
}
|
|
130
|
+
async #drain() {
|
|
131
|
+
while (!this.#stopped && (this.#needsSubscribe || this.#needsSend)) {
|
|
132
|
+
if (this.#needsSubscribe) {
|
|
133
|
+
this.#needsSubscribe = false;
|
|
134
|
+
try {
|
|
135
|
+
await this.#catchUp();
|
|
136
|
+
this.#subscribed = true;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
this.#subscribed = false;
|
|
140
|
+
this.#needsSubscribe = true;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (this.#stopped)
|
|
145
|
+
return;
|
|
146
|
+
if (this.#needsSend) {
|
|
147
|
+
this.#needsSend = false;
|
|
148
|
+
try {
|
|
149
|
+
if (await this.#sendPending())
|
|
150
|
+
this.#needsSubscribe = true;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
this.#needsSend = true;
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async #catchUp() {
|
|
160
|
+
let after = await this.#store.readDeliveryCursor(this.#authorityId);
|
|
161
|
+
while (!this.#stopped) {
|
|
162
|
+
const deliveries = await this.#authority.subscribe({
|
|
163
|
+
authorityId: this.#authorityId,
|
|
164
|
+
afterDeliverySequence: after,
|
|
165
|
+
limit: ENTITY_RECORD_DELIVERY_LIMIT,
|
|
166
|
+
});
|
|
167
|
+
if (deliveries.length === 0)
|
|
168
|
+
return;
|
|
169
|
+
let stored = await this.#store.receive({
|
|
170
|
+
authorityId: this.#authorityId,
|
|
171
|
+
deliveries,
|
|
172
|
+
advanceDeliveryCursor: true,
|
|
173
|
+
});
|
|
174
|
+
if (stored.kind === 'gap') {
|
|
175
|
+
const missing = await this.#authority.backfill({
|
|
176
|
+
authorityId: this.#authorityId,
|
|
177
|
+
entityId: stored.gap.entityId,
|
|
178
|
+
afterGlobalSequence: stored.gap.after,
|
|
179
|
+
throughGlobalSequence: stored.gap.received,
|
|
180
|
+
});
|
|
181
|
+
if (missing.length === 0) {
|
|
182
|
+
throw new Error(`entity ${stored.gap.entityId} is missing sequence ${stored.gap.after + 1}`);
|
|
183
|
+
}
|
|
184
|
+
const repaired = await this.#store.receive({
|
|
185
|
+
authorityId: this.#authorityId,
|
|
186
|
+
deliveries: missing,
|
|
187
|
+
advanceDeliveryCursor: false,
|
|
188
|
+
});
|
|
189
|
+
if (repaired.kind === 'gap') {
|
|
190
|
+
throw new Error(`entity ${repaired.gap.entityId} history remained gapped after backfill`);
|
|
191
|
+
}
|
|
192
|
+
stored = await this.#store.receive({
|
|
193
|
+
authorityId: this.#authorityId,
|
|
194
|
+
deliveries,
|
|
195
|
+
advanceDeliveryCursor: true,
|
|
196
|
+
});
|
|
197
|
+
if (stored.kind === 'gap') {
|
|
198
|
+
throw new Error(`entity ${stored.gap.entityId} history did not reach ${stored.gap.received}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
after = stored.deliveryCursor;
|
|
202
|
+
if (deliveries.length < ENTITY_RECORD_DELIVERY_LIMIT)
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async #sendPending() {
|
|
207
|
+
let sent = false;
|
|
208
|
+
while (!this.#stopped) {
|
|
209
|
+
const pending = await this.#store.readPending();
|
|
210
|
+
const selected = nextEntitySends(pending);
|
|
211
|
+
if (selected.length === 0)
|
|
212
|
+
return sent;
|
|
213
|
+
const results = await Promise.allSettled(selected.map(async (row) => {
|
|
214
|
+
await this.#cargo.ensure(row.record);
|
|
215
|
+
if (this.#stopped)
|
|
216
|
+
return;
|
|
217
|
+
const accepted = await this.#authority.submit(row.record);
|
|
218
|
+
await this.#store.stampAccepted(row, accepted);
|
|
219
|
+
sent = true;
|
|
220
|
+
}));
|
|
221
|
+
const failure = results.find((result) => result.status === 'rejected');
|
|
222
|
+
if (failure)
|
|
223
|
+
throw failure.reason;
|
|
224
|
+
}
|
|
225
|
+
return sent;
|
|
226
|
+
}
|
|
227
|
+
#scheduleRetry() {
|
|
228
|
+
if (this.#stopped || this.#retry)
|
|
229
|
+
return;
|
|
230
|
+
const base = Math.min(ENTITY_RECORD_RETRY_MAX_MS, ENTITY_RECORD_RETRY_BASE_MS * (2 ** Math.min(this.#retryAttempt, 16)));
|
|
231
|
+
const random = Math.max(0, Math.min(1, this.#schedule.random()));
|
|
232
|
+
const delay = Math.round(base * (0.5 + random));
|
|
233
|
+
this.#retryAttempt += 1;
|
|
234
|
+
this.#retry = this.#schedule.schedule(delay, () => {
|
|
235
|
+
this.#retry = null;
|
|
236
|
+
this.#kick();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
#cancelRetry() {
|
|
240
|
+
this.#retry?.cancel();
|
|
241
|
+
this.#retry = null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=rail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rail.js","sourceRoot":"","sources":["../../src/records/rail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Exact wire for the entity Record/Send/Receive rail. */
|
|
2
|
+
import type { DeliveredEntityRecord, EntityRecordAuthorityPort, EntityRecordAvailability } from './rail.js';
|
|
3
|
+
export interface EntityRecordSubmitFrame {
|
|
4
|
+
readonly type: 'entity.record.submit';
|
|
5
|
+
readonly request_id: string;
|
|
6
|
+
readonly record_json: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EntityRecordSubscribeFrame {
|
|
9
|
+
readonly type: 'entity.record.subscribe';
|
|
10
|
+
readonly request_id: string;
|
|
11
|
+
readonly authority_id: string;
|
|
12
|
+
readonly after_delivery_sequence: number;
|
|
13
|
+
readonly limit: number;
|
|
14
|
+
}
|
|
15
|
+
export interface EntityRecordBackfillFrame {
|
|
16
|
+
readonly type: 'entity.record.backfill';
|
|
17
|
+
readonly request_id: string;
|
|
18
|
+
readonly authority_id: string;
|
|
19
|
+
readonly entity_id: string;
|
|
20
|
+
readonly after_global_sequence: number;
|
|
21
|
+
readonly through_global_sequence: number;
|
|
22
|
+
}
|
|
23
|
+
export type EntityRecordRuntimeFrame = EntityRecordSubmitFrame | EntityRecordSubscribeFrame | EntityRecordBackfillFrame;
|
|
24
|
+
export interface EntityRecordAckFrame {
|
|
25
|
+
readonly type: 'entity.record.ack';
|
|
26
|
+
readonly request_id: string;
|
|
27
|
+
readonly record_json: string;
|
|
28
|
+
readonly duplicate: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface EntityRecordDeliveryFrame {
|
|
31
|
+
readonly delivery_sequence: number;
|
|
32
|
+
readonly record_json: string;
|
|
33
|
+
}
|
|
34
|
+
export interface EntityRecordSubscribedFrame {
|
|
35
|
+
readonly type: 'entity.record.subscribed';
|
|
36
|
+
readonly request_id: string;
|
|
37
|
+
readonly records: readonly EntityRecordDeliveryFrame[];
|
|
38
|
+
}
|
|
39
|
+
export interface EntityRecordBackfillResultFrame {
|
|
40
|
+
readonly type: 'entity.record.backfill-result';
|
|
41
|
+
readonly request_id: string;
|
|
42
|
+
readonly records: readonly EntityRecordDeliveryFrame[];
|
|
43
|
+
}
|
|
44
|
+
export interface EntityRecordAvailableFrame {
|
|
45
|
+
readonly type: 'entity.record.available';
|
|
46
|
+
readonly authority_id: string;
|
|
47
|
+
}
|
|
48
|
+
export type EntityRecordAuthorityFrame = EntityRecordAckFrame | EntityRecordSubscribedFrame | EntityRecordBackfillResultFrame | EntityRecordAvailableFrame;
|
|
49
|
+
type WithoutRequestId<T> = T extends {
|
|
50
|
+
readonly request_id: string;
|
|
51
|
+
} ? Omit<T, 'request_id'> : T;
|
|
52
|
+
export type EntityRecordWireRequest = WithoutRequestId<EntityRecordRuntimeFrame>;
|
|
53
|
+
export interface EntityRecordWirePort {
|
|
54
|
+
request(frame: EntityRecordWireRequest, acceptedTypes: readonly string[]): Promise<unknown>;
|
|
55
|
+
onFrame(listener: (frame: unknown) => void): () => void;
|
|
56
|
+
onDisconnect(listener: (error: unknown) => void): () => void;
|
|
57
|
+
}
|
|
58
|
+
export declare function decodeEntityRecordDeliveries(value: unknown): readonly DeliveredEntityRecord[];
|
|
59
|
+
export declare function decodeEntityRecordAvailability(value: unknown): EntityRecordAvailability;
|
|
60
|
+
/** Map one authenticated request/event wire onto the portable authority port. */
|
|
61
|
+
export declare function createEntityRecordAuthorityPort(wire: EntityRecordWirePort): EntityRecordAuthorityPort;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../../src/records/wire.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Exact wire for the entity Record/Send/Receive rail. */
|
|
2
|
+
import { isRecord } from '../contracts/validate.js';
|
|
3
|
+
import { decodeAcceptedEntityRecord, encodeEntityRecord } from './codec.js';
|
|
4
|
+
const positiveSequence = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
5
|
+
export function decodeEntityRecordDeliveries(value) {
|
|
6
|
+
if (!Array.isArray(value))
|
|
7
|
+
throw new Error('entity record authority did not return a records array');
|
|
8
|
+
return Object.freeze(value.map((item) => {
|
|
9
|
+
if (!isRecord(item)
|
|
10
|
+
|| !positiveSequence(item.delivery_sequence)
|
|
11
|
+
|| typeof item.record_json !== 'string') {
|
|
12
|
+
throw new Error('entity record authority returned an invalid delivery');
|
|
13
|
+
}
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
deliverySequence: item.delivery_sequence,
|
|
16
|
+
record: decodeAcceptedEntityRecord(item.record_json),
|
|
17
|
+
});
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
export function decodeEntityRecordAvailability(value) {
|
|
21
|
+
if (!isRecord(value)
|
|
22
|
+
|| value.type !== 'entity.record.available'
|
|
23
|
+
|| typeof value.authority_id !== 'string'
|
|
24
|
+
|| !value.authority_id) {
|
|
25
|
+
throw new Error('entity record availability signal is invalid');
|
|
26
|
+
}
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
authorityId: value.authority_id,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Map one authenticated request/event wire onto the portable authority port. */
|
|
32
|
+
export function createEntityRecordAuthorityPort(wire) {
|
|
33
|
+
return {
|
|
34
|
+
async submit(record) {
|
|
35
|
+
const frame = await wire.request({
|
|
36
|
+
type: 'entity.record.submit',
|
|
37
|
+
record_json: encodeEntityRecord(record),
|
|
38
|
+
}, ['entity.record.ack']);
|
|
39
|
+
if (!isRecord(frame) || frame.type !== 'entity.record.ack' || typeof frame.record_json !== 'string') {
|
|
40
|
+
throw new Error('entity record acknowledgement is invalid');
|
|
41
|
+
}
|
|
42
|
+
return decodeAcceptedEntityRecord(frame.record_json);
|
|
43
|
+
},
|
|
44
|
+
async subscribe(input) {
|
|
45
|
+
const frame = await wire.request({
|
|
46
|
+
type: 'entity.record.subscribe',
|
|
47
|
+
authority_id: input.authorityId,
|
|
48
|
+
after_delivery_sequence: input.afterDeliverySequence,
|
|
49
|
+
limit: input.limit,
|
|
50
|
+
}, ['entity.record.subscribed']);
|
|
51
|
+
if (!isRecord(frame) || frame.type !== 'entity.record.subscribed') {
|
|
52
|
+
throw new Error('entity record subscription response is invalid');
|
|
53
|
+
}
|
|
54
|
+
return decodeEntityRecordDeliveries(frame.records);
|
|
55
|
+
},
|
|
56
|
+
async backfill(input) {
|
|
57
|
+
const frame = await wire.request({
|
|
58
|
+
type: 'entity.record.backfill',
|
|
59
|
+
authority_id: input.authorityId,
|
|
60
|
+
entity_id: input.entityId,
|
|
61
|
+
after_global_sequence: input.afterGlobalSequence,
|
|
62
|
+
through_global_sequence: input.throughGlobalSequence,
|
|
63
|
+
}, ['entity.record.backfill-result']);
|
|
64
|
+
if (!isRecord(frame) || frame.type !== 'entity.record.backfill-result') {
|
|
65
|
+
throw new Error('entity record backfill response is invalid');
|
|
66
|
+
}
|
|
67
|
+
return decodeEntityRecordDeliveries(frame.records);
|
|
68
|
+
},
|
|
69
|
+
onAvailable(listener) {
|
|
70
|
+
return wire.onFrame((frame) => {
|
|
71
|
+
if (!isRecord(frame) || frame.type !== 'entity.record.available')
|
|
72
|
+
return;
|
|
73
|
+
try {
|
|
74
|
+
listener(decodeEntityRecordAvailability(frame));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// A malformed unsolicited frame is not authority evidence.
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
onDisconnect(listener) {
|
|
82
|
+
return wire.onDisconnect(listener);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.js","sourceRoot":"","sources":["../../src/records/wire.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,4 +20,6 @@ __exportStar(require("./codec.js"), exports);
|
|
|
20
20
|
__exportStar(require("./contracts.js"), exports);
|
|
21
21
|
__exportStar(require("./inbox.js"), exports);
|
|
22
22
|
__exportStar(require("./outbox.js"), exports);
|
|
23
|
+
__exportStar(require("./rail.js"), exports);
|
|
24
|
+
__exportStar(require("./wire.js"), exports);
|
|
23
25
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/records/index.ts"],"names":[],"mappings":";AAAA,2EAA2E;;;;;;;;;;;;;;;;AAE3E,iDAA+B;AAC/B,6CAA2B;AAC3B,iDAA+B;AAC/B,6CAA2B;AAC3B,8CAA4B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/records/index.ts"],"names":[],"mappings":";AAAA,2EAA2E;;;;;;;;;;;;;;;;AAE3E,iDAA+B;AAC/B,6CAA2B;AAC3B,iDAA+B;AAC/B,6CAA2B;AAC3B,8CAA4B;AAC5B,4CAA0B;AAC1B,4CAA0B"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Portable, event-driven Send and Receive orchestration. Hosts inject effects. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.EntityRecordRail = exports.ENTITY_RECORD_RETRY_MAX_MS = exports.ENTITY_RECORD_RETRY_BASE_MS = exports.ENTITY_RECORD_DELIVERY_LIMIT = void 0;
|
|
5
|
+
const outbox_js_1 = require("./outbox.js");
|
|
6
|
+
exports.ENTITY_RECORD_DELIVERY_LIMIT = 256;
|
|
7
|
+
exports.ENTITY_RECORD_RETRY_BASE_MS = 1_000;
|
|
8
|
+
exports.ENTITY_RECORD_RETRY_MAX_MS = 30_000;
|
|
9
|
+
/**
|
|
10
|
+
* One rail has only two reasons to run: local work exists, or authority work
|
|
11
|
+
* may exist. Repeated reasons coalesce while the current run is in flight.
|
|
12
|
+
*/
|
|
13
|
+
class EntityRecordRail {
|
|
14
|
+
#authorityId;
|
|
15
|
+
#store;
|
|
16
|
+
#cargo;
|
|
17
|
+
#authority;
|
|
18
|
+
#schedule;
|
|
19
|
+
#onBackgroundError;
|
|
20
|
+
#started = false;
|
|
21
|
+
#stopped = false;
|
|
22
|
+
#subscribed = false;
|
|
23
|
+
#needsSubscribe = false;
|
|
24
|
+
#needsSend = false;
|
|
25
|
+
#running = null;
|
|
26
|
+
#retry = null;
|
|
27
|
+
#retryAttempt = 0;
|
|
28
|
+
#removeAvailable = null;
|
|
29
|
+
#removeDisconnect = null;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
if (!options.authorityId.trim())
|
|
32
|
+
throw new Error('entity record authority is required');
|
|
33
|
+
this.#authorityId = options.authorityId;
|
|
34
|
+
this.#store = options.store;
|
|
35
|
+
this.#cargo = options.cargo;
|
|
36
|
+
this.#authority = options.authority;
|
|
37
|
+
this.#schedule = options.schedule;
|
|
38
|
+
this.#onBackgroundError = options.onBackgroundError ?? (() => undefined);
|
|
39
|
+
}
|
|
40
|
+
/** Subscribe first, catch up from the durable cursor, then drain local work. */
|
|
41
|
+
async start() {
|
|
42
|
+
if (this.#started)
|
|
43
|
+
throw new Error('entity record rail already started');
|
|
44
|
+
if (this.#stopped)
|
|
45
|
+
throw new Error('entity record rail is stopped');
|
|
46
|
+
this.#started = true;
|
|
47
|
+
this.#removeAvailable = this.#authority.onAvailable((signal) => {
|
|
48
|
+
if (signal.authorityId !== this.#authorityId || this.#stopped)
|
|
49
|
+
return;
|
|
50
|
+
this.#needsSubscribe = true;
|
|
51
|
+
this.#kick();
|
|
52
|
+
});
|
|
53
|
+
this.#removeDisconnect = this.#authority.onDisconnect(() => {
|
|
54
|
+
if (this.#stopped)
|
|
55
|
+
return;
|
|
56
|
+
this.#subscribed = false;
|
|
57
|
+
this.#needsSubscribe = true;
|
|
58
|
+
this.#scheduleRetry();
|
|
59
|
+
});
|
|
60
|
+
this.#needsSubscribe = true;
|
|
61
|
+
this.#needsSend = true;
|
|
62
|
+
await this.#runExplicitly();
|
|
63
|
+
}
|
|
64
|
+
/** Record calls this only after its local outbox transaction commits. */
|
|
65
|
+
localRecordsAvailable() {
|
|
66
|
+
if (this.#stopped)
|
|
67
|
+
return;
|
|
68
|
+
this.#needsSend = true;
|
|
69
|
+
if (this.#started)
|
|
70
|
+
this.#kick();
|
|
71
|
+
}
|
|
72
|
+
/** Await all work known at this boundary; it never creates an idle query. */
|
|
73
|
+
async flush() {
|
|
74
|
+
if (!this.#started)
|
|
75
|
+
throw new Error('entity record rail has not started');
|
|
76
|
+
if (this.#stopped)
|
|
77
|
+
return;
|
|
78
|
+
this.#needsSend = true;
|
|
79
|
+
if (!this.#subscribed)
|
|
80
|
+
this.#needsSubscribe = true;
|
|
81
|
+
await this.#runExplicitly();
|
|
82
|
+
}
|
|
83
|
+
/** Stop accepting events. Closing the injected wire may interrupt a request. */
|
|
84
|
+
async stop() {
|
|
85
|
+
if (this.#stopped)
|
|
86
|
+
return;
|
|
87
|
+
this.#stopped = true;
|
|
88
|
+
this.#cancelRetry();
|
|
89
|
+
this.#removeAvailable?.();
|
|
90
|
+
this.#removeAvailable = null;
|
|
91
|
+
this.#removeDisconnect?.();
|
|
92
|
+
this.#removeDisconnect = null;
|
|
93
|
+
await this.#running?.catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
async #runExplicitly() {
|
|
96
|
+
this.#cancelRetry();
|
|
97
|
+
try {
|
|
98
|
+
await this.#ensureRun();
|
|
99
|
+
this.#retryAttempt = 0;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
this.#scheduleRetry();
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
#kick() {
|
|
107
|
+
if (!this.#started || this.#stopped)
|
|
108
|
+
return;
|
|
109
|
+
this.#cancelRetry();
|
|
110
|
+
void this.#ensureRun().then(() => {
|
|
111
|
+
this.#retryAttempt = 0;
|
|
112
|
+
}, (error) => {
|
|
113
|
+
this.#onBackgroundError(error);
|
|
114
|
+
this.#scheduleRetry();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
#ensureRun() {
|
|
118
|
+
if (this.#running)
|
|
119
|
+
return this.#running;
|
|
120
|
+
const running = this.#drain();
|
|
121
|
+
this.#running = running;
|
|
122
|
+
void running.then(() => {
|
|
123
|
+
if (this.#running === running)
|
|
124
|
+
this.#running = null;
|
|
125
|
+
if (!this.#stopped && (this.#needsSubscribe || this.#needsSend))
|
|
126
|
+
this.#kick();
|
|
127
|
+
}, () => {
|
|
128
|
+
if (this.#running === running)
|
|
129
|
+
this.#running = null;
|
|
130
|
+
});
|
|
131
|
+
return running;
|
|
132
|
+
}
|
|
133
|
+
async #drain() {
|
|
134
|
+
while (!this.#stopped && (this.#needsSubscribe || this.#needsSend)) {
|
|
135
|
+
if (this.#needsSubscribe) {
|
|
136
|
+
this.#needsSubscribe = false;
|
|
137
|
+
try {
|
|
138
|
+
await this.#catchUp();
|
|
139
|
+
this.#subscribed = true;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
this.#subscribed = false;
|
|
143
|
+
this.#needsSubscribe = true;
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (this.#stopped)
|
|
148
|
+
return;
|
|
149
|
+
if (this.#needsSend) {
|
|
150
|
+
this.#needsSend = false;
|
|
151
|
+
try {
|
|
152
|
+
if (await this.#sendPending())
|
|
153
|
+
this.#needsSubscribe = true;
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.#needsSend = true;
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async #catchUp() {
|
|
163
|
+
let after = await this.#store.readDeliveryCursor(this.#authorityId);
|
|
164
|
+
while (!this.#stopped) {
|
|
165
|
+
const deliveries = await this.#authority.subscribe({
|
|
166
|
+
authorityId: this.#authorityId,
|
|
167
|
+
afterDeliverySequence: after,
|
|
168
|
+
limit: exports.ENTITY_RECORD_DELIVERY_LIMIT,
|
|
169
|
+
});
|
|
170
|
+
if (deliveries.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
let stored = await this.#store.receive({
|
|
173
|
+
authorityId: this.#authorityId,
|
|
174
|
+
deliveries,
|
|
175
|
+
advanceDeliveryCursor: true,
|
|
176
|
+
});
|
|
177
|
+
if (stored.kind === 'gap') {
|
|
178
|
+
const missing = await this.#authority.backfill({
|
|
179
|
+
authorityId: this.#authorityId,
|
|
180
|
+
entityId: stored.gap.entityId,
|
|
181
|
+
afterGlobalSequence: stored.gap.after,
|
|
182
|
+
throughGlobalSequence: stored.gap.received,
|
|
183
|
+
});
|
|
184
|
+
if (missing.length === 0) {
|
|
185
|
+
throw new Error(`entity ${stored.gap.entityId} is missing sequence ${stored.gap.after + 1}`);
|
|
186
|
+
}
|
|
187
|
+
const repaired = await this.#store.receive({
|
|
188
|
+
authorityId: this.#authorityId,
|
|
189
|
+
deliveries: missing,
|
|
190
|
+
advanceDeliveryCursor: false,
|
|
191
|
+
});
|
|
192
|
+
if (repaired.kind === 'gap') {
|
|
193
|
+
throw new Error(`entity ${repaired.gap.entityId} history remained gapped after backfill`);
|
|
194
|
+
}
|
|
195
|
+
stored = await this.#store.receive({
|
|
196
|
+
authorityId: this.#authorityId,
|
|
197
|
+
deliveries,
|
|
198
|
+
advanceDeliveryCursor: true,
|
|
199
|
+
});
|
|
200
|
+
if (stored.kind === 'gap') {
|
|
201
|
+
throw new Error(`entity ${stored.gap.entityId} history did not reach ${stored.gap.received}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
after = stored.deliveryCursor;
|
|
205
|
+
if (deliveries.length < exports.ENTITY_RECORD_DELIVERY_LIMIT)
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async #sendPending() {
|
|
210
|
+
let sent = false;
|
|
211
|
+
while (!this.#stopped) {
|
|
212
|
+
const pending = await this.#store.readPending();
|
|
213
|
+
const selected = (0, outbox_js_1.nextEntitySends)(pending);
|
|
214
|
+
if (selected.length === 0)
|
|
215
|
+
return sent;
|
|
216
|
+
const results = await Promise.allSettled(selected.map(async (row) => {
|
|
217
|
+
await this.#cargo.ensure(row.record);
|
|
218
|
+
if (this.#stopped)
|
|
219
|
+
return;
|
|
220
|
+
const accepted = await this.#authority.submit(row.record);
|
|
221
|
+
await this.#store.stampAccepted(row, accepted);
|
|
222
|
+
sent = true;
|
|
223
|
+
}));
|
|
224
|
+
const failure = results.find((result) => result.status === 'rejected');
|
|
225
|
+
if (failure)
|
|
226
|
+
throw failure.reason;
|
|
227
|
+
}
|
|
228
|
+
return sent;
|
|
229
|
+
}
|
|
230
|
+
#scheduleRetry() {
|
|
231
|
+
if (this.#stopped || this.#retry)
|
|
232
|
+
return;
|
|
233
|
+
const base = Math.min(exports.ENTITY_RECORD_RETRY_MAX_MS, exports.ENTITY_RECORD_RETRY_BASE_MS * (2 ** Math.min(this.#retryAttempt, 16)));
|
|
234
|
+
const random = Math.max(0, Math.min(1, this.#schedule.random()));
|
|
235
|
+
const delay = Math.round(base * (0.5 + random));
|
|
236
|
+
this.#retryAttempt += 1;
|
|
237
|
+
this.#retry = this.#schedule.schedule(delay, () => {
|
|
238
|
+
this.#retry = null;
|
|
239
|
+
this.#kick();
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
#cancelRetry() {
|
|
243
|
+
this.#retry?.cancel();
|
|
244
|
+
this.#retry = null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
exports.EntityRecordRail = EntityRecordRail;
|
|
248
|
+
//# sourceMappingURL=rail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rail.js","sourceRoot":"","sources":["../../src/records/rail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Exact wire for the entity Record/Send/Receive rail. */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.decodeEntityRecordDeliveries = decodeEntityRecordDeliveries;
|
|
5
|
+
exports.decodeEntityRecordAvailability = decodeEntityRecordAvailability;
|
|
6
|
+
exports.createEntityRecordAuthorityPort = createEntityRecordAuthorityPort;
|
|
7
|
+
const validate_js_1 = require("../contracts/validate.js");
|
|
8
|
+
const codec_js_1 = require("./codec.js");
|
|
9
|
+
const positiveSequence = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
10
|
+
function decodeEntityRecordDeliveries(value) {
|
|
11
|
+
if (!Array.isArray(value))
|
|
12
|
+
throw new Error('entity record authority did not return a records array');
|
|
13
|
+
return Object.freeze(value.map((item) => {
|
|
14
|
+
if (!(0, validate_js_1.isRecord)(item)
|
|
15
|
+
|| !positiveSequence(item.delivery_sequence)
|
|
16
|
+
|| typeof item.record_json !== 'string') {
|
|
17
|
+
throw new Error('entity record authority returned an invalid delivery');
|
|
18
|
+
}
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
deliverySequence: item.delivery_sequence,
|
|
21
|
+
record: (0, codec_js_1.decodeAcceptedEntityRecord)(item.record_json),
|
|
22
|
+
});
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
function decodeEntityRecordAvailability(value) {
|
|
26
|
+
if (!(0, validate_js_1.isRecord)(value)
|
|
27
|
+
|| value.type !== 'entity.record.available'
|
|
28
|
+
|| typeof value.authority_id !== 'string'
|
|
29
|
+
|| !value.authority_id) {
|
|
30
|
+
throw new Error('entity record availability signal is invalid');
|
|
31
|
+
}
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
authorityId: value.authority_id,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Map one authenticated request/event wire onto the portable authority port. */
|
|
37
|
+
function createEntityRecordAuthorityPort(wire) {
|
|
38
|
+
return {
|
|
39
|
+
async submit(record) {
|
|
40
|
+
const frame = await wire.request({
|
|
41
|
+
type: 'entity.record.submit',
|
|
42
|
+
record_json: (0, codec_js_1.encodeEntityRecord)(record),
|
|
43
|
+
}, ['entity.record.ack']);
|
|
44
|
+
if (!(0, validate_js_1.isRecord)(frame) || frame.type !== 'entity.record.ack' || typeof frame.record_json !== 'string') {
|
|
45
|
+
throw new Error('entity record acknowledgement is invalid');
|
|
46
|
+
}
|
|
47
|
+
return (0, codec_js_1.decodeAcceptedEntityRecord)(frame.record_json);
|
|
48
|
+
},
|
|
49
|
+
async subscribe(input) {
|
|
50
|
+
const frame = await wire.request({
|
|
51
|
+
type: 'entity.record.subscribe',
|
|
52
|
+
authority_id: input.authorityId,
|
|
53
|
+
after_delivery_sequence: input.afterDeliverySequence,
|
|
54
|
+
limit: input.limit,
|
|
55
|
+
}, ['entity.record.subscribed']);
|
|
56
|
+
if (!(0, validate_js_1.isRecord)(frame) || frame.type !== 'entity.record.subscribed') {
|
|
57
|
+
throw new Error('entity record subscription response is invalid');
|
|
58
|
+
}
|
|
59
|
+
return decodeEntityRecordDeliveries(frame.records);
|
|
60
|
+
},
|
|
61
|
+
async backfill(input) {
|
|
62
|
+
const frame = await wire.request({
|
|
63
|
+
type: 'entity.record.backfill',
|
|
64
|
+
authority_id: input.authorityId,
|
|
65
|
+
entity_id: input.entityId,
|
|
66
|
+
after_global_sequence: input.afterGlobalSequence,
|
|
67
|
+
through_global_sequence: input.throughGlobalSequence,
|
|
68
|
+
}, ['entity.record.backfill-result']);
|
|
69
|
+
if (!(0, validate_js_1.isRecord)(frame) || frame.type !== 'entity.record.backfill-result') {
|
|
70
|
+
throw new Error('entity record backfill response is invalid');
|
|
71
|
+
}
|
|
72
|
+
return decodeEntityRecordDeliveries(frame.records);
|
|
73
|
+
},
|
|
74
|
+
onAvailable(listener) {
|
|
75
|
+
return wire.onFrame((frame) => {
|
|
76
|
+
if (!(0, validate_js_1.isRecord)(frame) || frame.type !== 'entity.record.available')
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
listener(decodeEntityRecordAvailability(frame));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// A malformed unsolicited frame is not authority evidence.
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
onDisconnect(listener) {
|
|
87
|
+
return wire.onDisconnect(listener);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.js","sourceRoot":"","sources":["../../src/records/wire.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/live",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.30",
|
|
4
4
|
"description": "Portable realtime SDK for Amalgm — wire contracts, mutation discipline, and delivery state machines behind host-injected adapters.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist-cjs/index.js",
|