@ultimat3/realtime 23.0.0 → 25.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +118 -126
- package/README.md +229 -54
- package/package.json +9 -6
- package/src/advisory-lock.ts +62 -0
- package/src/boot.ts +25 -15
- package/src/change-buffer.ts +96 -41
- package/src/changefeed-env.ts +9 -9
- package/src/changefeed.ts +81 -19
- package/src/channel-authz.ts +48 -17
- package/src/channel-describe.ts +1 -1
- package/src/channel-latch.ts +52 -0
- package/src/channel-logs.ts +36 -15
- package/src/channel-presence.ts +3 -3
- package/src/channel-records.ts +35 -4
- package/src/channel-ref.ts +2 -2
- package/src/channel-registry.ts +2 -2
- package/src/channel-sids.ts +37 -0
- package/src/channel.ts +148 -48
- package/src/client-channels.ts +12 -1
- package/src/client-contract.ts +4 -1
- package/src/client-frames.ts +16 -3
- package/src/client-heartbeat.ts +39 -9
- package/src/client.ts +30 -2
- package/src/cursor.ts +11 -0
- package/src/error-titles.ts +91 -0
- package/src/errors.ts +113 -105
- package/src/fanout.ts +13 -0
- package/src/first-paint-hold.ts +49 -0
- package/src/idb-types.ts +2 -2
- package/src/index.ts +25 -7
- package/src/json.ts +16 -3
- package/src/live-contract.ts +14 -0
- package/src/live-definition.ts +34 -9
- package/src/live-deliver.ts +55 -0
- package/src/live-fanout.ts +122 -29
- package/src/live-floor.ts +35 -0
- package/src/live-query-options.ts +44 -0
- package/src/live-query.ts +96 -175
- package/src/live-reauth.ts +127 -0
- package/src/live-replicator.ts +28 -8
- package/src/live-resume.ts +44 -8
- package/src/live-rows.ts +32 -3
- package/src/live-spend.ts +50 -0
- package/src/local-store-idb.ts +5 -0
- package/src/matcher-bridge.ts +11 -5
- package/src/nats-client.ts +37 -6
- package/src/nats-lib-client.ts +36 -7
- package/src/nats-subscriptions.ts +63 -0
- package/src/nats-transport.ts +149 -15
- package/src/offline-queue.ts +53 -5
- package/src/page-errors.ts +25 -9
- package/src/page-outbox.ts +125 -54
- package/src/page-runtime-wait.ts +72 -0
- package/src/page-runtime.ts +32 -0
- package/src/page-socket.ts +7 -16
- package/src/page-store.ts +83 -16
- package/src/pg-advisory-lock.ts +54 -5
- package/src/pg-auth.ts +9 -4
- package/src/pg-bytes.ts +4 -1
- package/src/pg-connection.ts +126 -22
- package/src/pg-entity-row.ts +16 -0
- package/src/pg-identity-tables.ts +25 -0
- package/src/pg-preflight.ts +50 -4
- package/src/pg-replication-row.ts +82 -0
- package/src/pg-replication.ts +50 -51
- package/src/pg-socket.ts +26 -7
- package/src/pg-tls.ts +18 -0
- package/src/pg-values.ts +29 -14
- package/src/pg-wire.ts +5 -3
- package/src/pgoutput.ts +19 -9
- package/src/policy-gate.ts +7 -7
- package/src/presence.ts +87 -10
- package/src/principal-sockets.ts +75 -0
- package/src/query-window.ts +17 -5
- package/src/record-persister.ts +7 -2
- package/src/record-publisher.ts +178 -0
- package/src/record-store.ts +25 -4
- package/src/record-synced.ts +43 -1
- package/src/replication-errors.ts +11 -7
- package/src/replicator-envelope.ts +160 -0
- package/src/replicator-row.ts +75 -0
- package/src/replicator.ts +313 -191
- package/src/server.ts +45 -23
- package/src/socket-defaults.ts +20 -0
- package/src/socket-engine.ts +14 -2
- package/src/socket-idle.ts +3 -1
- package/src/socket-routes.ts +8 -3
- package/src/socket.ts +14 -19
- package/src/subscriber-gate.ts +11 -1
- package/src/subscription-book.ts +129 -71
- package/src/subscription-count.ts +151 -0
- package/src/sync-actor-change.ts +56 -0
- package/src/sync-auth.ts +47 -2
- package/src/sync-bus-handlers.ts +131 -0
- package/src/sync-frames.ts +74 -21
- package/src/sync-node-bounds.ts +16 -1
- package/src/sync-node-contract.ts +36 -4
- package/src/sync-node.ts +163 -82
- package/src/sync-origin.ts +27 -12
- package/src/sync-protocol.ts +86 -16
- package/src/sync-upgrade.ts +115 -8
- package/src/type-pins.ts +9 -1
- package/src/use-channel.ts +19 -14
- package/src/use-connection.ts +3 -3
- package/src/use-mutation.ts +41 -20
- package/src/use-query.ts +11 -11
- package/src/use-record.ts +89 -10
- package/src/wire-channel.ts +18 -9
- package/src/wire-read.ts +2 -2
- package/src/idb-fake.ts +0 -133
- package/src/policy-fake.ts +0 -14
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Single responsibility: a change's rows across the replicator bus. The bus carries text, a row
|
|
2
|
+
// holds a `Date` and a `Uint8Array`, and `JSON.parse` returns neither — so the publisher writes a
|
|
3
|
+
// form JSON can carry and the sync node reads it back through the ENTITY's own columns.
|
|
4
|
+
|
|
5
|
+
import { entityForTable } from '@ultimat3/entity';
|
|
6
|
+
import type { Row } from './json';
|
|
7
|
+
|
|
8
|
+
const CHUNK = 0x8000;
|
|
9
|
+
|
|
10
|
+
/** Base64 of raw bytes. Chunked: one `String.fromCharCode(...bytes)` overflows the stack at ~100k. */
|
|
11
|
+
function toBase64(bytes: Uint8Array): string {
|
|
12
|
+
let binary = '';
|
|
13
|
+
for (let at = 0; at < bytes.length; at += CHUNK) {
|
|
14
|
+
binary += String.fromCharCode(...bytes.subarray(at, at + CHUNK));
|
|
15
|
+
}
|
|
16
|
+
return btoa(binary);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function fromBase64(text: string): Uint8Array {
|
|
20
|
+
const binary = atob(text);
|
|
21
|
+
const bytes = new Uint8Array(binary.length);
|
|
22
|
+
for (let at = 0; at < binary.length; at += 1) bytes[at] = binary.charCodeAt(at);
|
|
23
|
+
return bytes;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Publish side. A `Date` already serialises to the ISO text its column reads back; a `Uint8Array`
|
|
28
|
+
* serialises to `{"0":1,"1":2}`, which nothing reads back, so it crosses as base64. Only a
|
|
29
|
+
* top-level value can be one: `arrayOf()` refuses `bytes()` at declaration.
|
|
30
|
+
*/
|
|
31
|
+
export function busRow(row: Row | null): Row | null {
|
|
32
|
+
if (row === null) return null;
|
|
33
|
+
let out: Record<string, unknown> | undefined;
|
|
34
|
+
for (const [property, value] of Object.entries(row as Record<string, unknown>)) {
|
|
35
|
+
if (!(value instanceof Uint8Array)) continue;
|
|
36
|
+
out ??= { ...row };
|
|
37
|
+
out[property] = toBase64(value);
|
|
38
|
+
}
|
|
39
|
+
return out === undefined ? row : (out as unknown as Row);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Consume side: the row as the replicator held it before it was text. Without this every `Date`
|
|
44
|
+
* reached the matcher as an ISO string, `compareValues(Date, string)` fell to comparing their
|
|
45
|
+
* printed forms, and one edit to one column moved its row to the top of every window ordered by
|
|
46
|
+
* a timestamp — on every deployment with a bus, and in no in-process test, which hands the object
|
|
47
|
+
* over directly.
|
|
48
|
+
*
|
|
49
|
+
* Decided by the column that DECLARED the value, never by what the value looks like: a `text()`
|
|
50
|
+
* holding `2026-08-09T12:00:00.000Z` stays text. `table` is `ChangeEvent.table`, which on the WAL
|
|
51
|
+
* path is the relation name. A table with no entity here, a property with no column, and a value
|
|
52
|
+
* its column refuses are all handed back as they arrived — a change that crosses unrevived is the
|
|
53
|
+
* old behaviour; a change dropped is a window that silently diverges.
|
|
54
|
+
*/
|
|
55
|
+
export function reviveBusRow(table: string, row: Row | null): Row | null {
|
|
56
|
+
if (row === null) return null;
|
|
57
|
+
const entity = entityForTable(table);
|
|
58
|
+
if (entity === undefined) return row;
|
|
59
|
+
const out: Record<string, unknown> = {};
|
|
60
|
+
for (const [property, value] of Object.entries(row as Record<string, unknown>)) {
|
|
61
|
+
out[property] = value;
|
|
62
|
+
if (value === null || !Object.hasOwn(entity.$columns, property)) continue;
|
|
63
|
+
const column = entity.$columns[property];
|
|
64
|
+
if (column === undefined || column.$meta.sealed !== undefined) continue;
|
|
65
|
+
try {
|
|
66
|
+
out[property] =
|
|
67
|
+
column.$meta.kind === 'bytea' && typeof value === 'string'
|
|
68
|
+
? fromBase64(value)
|
|
69
|
+
: column.$parse(value);
|
|
70
|
+
} catch {
|
|
71
|
+
// Kept as it arrived — see the function comment.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out as unknown as Row;
|
|
75
|
+
}
|