@rljson/rljson 0.0.74 → 0.0.76

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.
@@ -1,7 +1,400 @@
1
- # @rljson/
1
+ <!--
2
+ @license
3
+ Copyright (c) 2025 Rljson
2
4
 
3
- Todo: Add description here
5
+ Use of this source code is governed by terms that can be
6
+ found in the LICENSE file in the root of this package.
7
+ -->
4
8
 
5
- ## Example
9
+ # @rljson/rljson
6
10
 
7
- [src/example.ts](src/example.ts)
11
+ Core types, validation, and sync protocol for the RLJSON data format.
12
+
13
+ ## Table of Contents <!-- omit in toc -->
14
+
15
+ - [Installation](#installation)
16
+ - [Overview](#overview)
17
+ - [Data Model Types](#data-model-types)
18
+ - [Components](#components)
19
+ - [SliceIds](#sliceids)
20
+ - [Layers](#layers)
21
+ - [Cakes](#cakes)
22
+ - [Buffets](#buffets)
23
+ - [Trees](#trees)
24
+ - [Schema System](#schema-system)
25
+ - [TableCfg](#tablecfg)
26
+ - [Validation](#validation)
27
+ - [Edit Protocol](#edit-protocol)
28
+ - [Insert](#insert)
29
+ - [InsertHistory](#inserthistory)
30
+ - [Edits, MultiEdits, EditHistory](#edits-multiedits-edithistory)
31
+ - [Routing](#routing)
32
+ - [Sync Protocol](#sync-protocol)
33
+ - [ConnectorPayload](#connectorpayload)
34
+ - [AckPayload](#ackpayload)
35
+ - [GapFill](#gapfill)
36
+ - [SyncConfig](#syncconfig)
37
+ - [SyncEventNames](#synceventnames)
38
+ - [ClientId](#clientid)
39
+ - [Utilities](#utilities)
40
+ - [TimeId](#timeid)
41
+ - [RemoveDuplicates](#removeduplicates)
42
+ - [Ecosystem](#ecosystem)
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pnpm add @rljson/rljson
48
+ ```
49
+
50
+ ## Overview
51
+
52
+ `@rljson/rljson` is the foundational types package for the RLJSON ecosystem. It
53
+ defines the data format specification — a JSON-based, relational, normalized,
54
+ deeply-hashed exchange format designed for efficient synchronization of large
55
+ datasets.
56
+
57
+ ```typescript
58
+ import {
59
+ Rljson,
60
+ Route,
61
+ ConnectorPayload,
62
+ SyncConfig,
63
+ timeId,
64
+ } from '@rljson/rljson';
65
+ ```
66
+
67
+ ## Data Model Types
68
+
69
+ ### Components
70
+
71
+ The fundamental data unit. A `ComponentsTable` contains key-value rows with an
72
+ auto-generated `_hash` serving as the primary key.
73
+
74
+ ```typescript
75
+ import { ComponentsTable } from '@rljson/rljson';
76
+
77
+ const ingredients: ComponentsTable = {
78
+ _type: 'components',
79
+ _data: [
80
+ { id: 'flour', amountUnit: 'g', _hash: 'A5d...' },
81
+ { id: 'sugar', amountUnit: 'g', _hash: 'B7f...' },
82
+ ],
83
+ _hash: 't5o...',
84
+ };
85
+ ```
86
+
87
+ ### SliceIds
88
+
89
+ For efficient management of large layers, slice IDs are separated from their
90
+ data. This allows fetching IDs first and retrieving details on demand.
91
+
92
+ ```typescript
93
+ import { SliceIds, SliceIdsTable } from '@rljson/rljson';
94
+
95
+ const slices: SliceIdsTable = {
96
+ _type: 'sliceIds',
97
+ _data: [
98
+ { add: ['slice0', 'slice1'], remove: [], _hash: 'wyY...' },
99
+ ],
100
+ _hash: 'cnG...',
101
+ };
102
+ ```
103
+
104
+ Derived sets can modify an existing set using `base`, `add`, and `remove`.
105
+
106
+ ### Layers
107
+
108
+ Layers assign components to slices.
109
+
110
+ ```typescript
111
+ import { Layer, LayersTable } from '@rljson/rljson';
112
+ ```
113
+
114
+ A layer references a `componentsTable` and a `sliceIdsTable`, then maps each
115
+ slice ID to a component hash in its `add` block.
116
+
117
+ ### Cakes
118
+
119
+ A `Cake` is a stack of layers sharing the same slice IDs. Each layer assigns
120
+ different component values to the shared slices.
121
+
122
+ ```typescript
123
+ import { Cake, CakesTable } from '@rljson/rljson';
124
+ ```
125
+
126
+ ### Buffets
127
+
128
+ A `Buffet` is a heterogeneous collection of related items — references to rows
129
+ in any other table (cakes, layers, components, etc.).
130
+
131
+ ```typescript
132
+ import { Buffet, BuffetsTable } from '@rljson/rljson';
133
+ ```
134
+
135
+ ### Trees
136
+
137
+ Hierarchical tree structures with content-addressed nodes. Each `Tree` has an
138
+ optional `id` (unique among siblings), an `isParent` flag, a `meta` field
139
+ (`Json | null`), and a `children` array of child hashes (or `null` for leaves).
140
+
141
+ ```typescript
142
+ import { Tree, TreesTable, treeFromObject } from '@rljson/rljson';
143
+
144
+ // Convert a plain object into hashed tree nodes
145
+ const nodes = treeFromObject({ src: { 'index.ts': 'console.log("hello")' } });
146
+ ```
147
+
148
+ ## Schema System
149
+
150
+ ### TableCfg
151
+
152
+ `TableCfg` defines the schema for any table — its key, content type, column
153
+ definitions, and metadata flags.
154
+
155
+ ```typescript
156
+ import { TableCfg, ColumnCfg, throwOnInvalidTableCfg } from '@rljson/rljson';
157
+
158
+ const cfg: TableCfg = {
159
+ key: 'ingredients',
160
+ type: 'components',
161
+ columns: [
162
+ { key: '_hash', type: 'string', titleLong: 'Hash', titleShort: 'Hash' },
163
+ { key: 'name', type: 'string', titleLong: 'Name', titleShort: 'Name' },
164
+ ],
165
+ isHead: false,
166
+ isRoot: false,
167
+ isShared: false,
168
+ };
169
+
170
+ throwOnInvalidTableCfg(cfg); // throws on invalid config
171
+ ```
172
+
173
+ Column types: `string`, `number`, `boolean`, `json`, `jsonArray`.
174
+
175
+ Columns can reference other tables via the `ref` property on `ColumnCfgWithRef`.
176
+
177
+ ### Validation
178
+
179
+ The `Validate` class coordinates multiple validators to check entire RLJSON
180
+ objects — naming conventions, hash integrity, reference validity, tree
181
+ structure, layer/cake/buffet consistency.
182
+
183
+ ```typescript
184
+ import { Validate, BaseValidator } from '@rljson/rljson';
185
+
186
+ const validate = new Validate();
187
+ validate.addValidator(new BaseValidator());
188
+ const errors = await validate.run(myRljsonData);
189
+ // errors: { base: { hasErrors: false } }
190
+ ```
191
+
192
+ The `BaseValidator` checks all core rules. Custom validators can be added by
193
+ implementing the `Validator` interface.
194
+
195
+ ## Edit Protocol
196
+
197
+ ### Insert
198
+
199
+ An `Insert` describes a data modification operation with a command (`add`),
200
+ a value, a route, and an optional origin.
201
+
202
+ ```typescript
203
+ import { Insert, validateInsert } from '@rljson/rljson';
204
+
205
+ const insert: Insert<any> = {
206
+ route: '/ingredients',
207
+ command: 'add',
208
+ value: { name: 'butter', amountUnit: 'g' },
209
+ };
210
+
211
+ const errors = validateInsert(insert);
212
+ ```
213
+
214
+ ### InsertHistory
215
+
216
+ `InsertHistoryRow` tracks each insert operation with a unique `timeId`, the
217
+ `route` that was modified, and optional `origin`, `previous` (causal
218
+ predecessors), and `clientTimestamp`.
219
+
220
+ ```typescript
221
+ import { InsertHistoryRow, InsertHistoryTimeId } from '@rljson/rljson';
222
+
223
+ const row: InsertHistoryRow<'ingredients'> = {
224
+ ingredientsRef: 'A5d...',
225
+ timeId: '1700000000000:AbCd',
226
+ route: '/ingredients',
227
+ origin: 'client_ExAmPlE12345',
228
+ previous: ['1699999999999:ZzZz'],
229
+ clientTimestamp: 1700000000000,
230
+ };
231
+ ```
232
+
233
+ ### Edits, MultiEdits, EditHistory
234
+
235
+ - **Edit**: A named action with a type and data payload (`EditAction`)
236
+ - **MultiEdit**: Chains edits into a linked list via `previous` ref
237
+ - **EditHistory**: Tracks the full chain of multi-edits with `timeId` timestamps
238
+
239
+ ## Routing
240
+
241
+ The `Route` class parses and builds hierarchical data paths used throughout
242
+ the RLJSON ecosystem.
243
+
244
+ ```typescript
245
+ import { Route } from '@rljson/rljson';
246
+
247
+ // Parse a flat route string
248
+ const route = Route.fromFlat('/ingredients@A5d.../nutritionalValues');
249
+
250
+ // Navigate route segments
251
+ route.top; // first segment: { tableKey: 'ingredients', ... }
252
+ route.root; // last segment: { tableKey: 'nutritionalValues' }
253
+ route.segment(0); // segment by index
254
+ route.flat; // serialized string
255
+ ```
256
+
257
+ Routes support references (`@hash`), slice IDs (`(id1,id2)`), and
258
+ InsertHistory refs (`@timestamp:unique`).
259
+
260
+ ## Sync Protocol
261
+
262
+ The `sync/` module defines wire-protocol types for the messaging hardening
263
+ system used by `@rljson/db` (Connector) and `@rljson/server`.
264
+
265
+ ### ConnectorPayload
266
+
267
+ The payload transmitted between Connector and Server on the wire.
268
+
269
+ ```typescript
270
+ import { ConnectorPayload } from '@rljson/rljson';
271
+
272
+ // Minimal (backward-compatible)
273
+ const legacy: ConnectorPayload = { o: 'origin', r: 'ref' };
274
+
275
+ // Fully enriched
276
+ const enriched: ConnectorPayload = {
277
+ o: 'origin', // ephemeral origin (self-echo filter)
278
+ r: 'ref', // the ref being announced
279
+ c: 'client_abc123...', // stable client identity
280
+ t: Date.now(), // client-side timestamp
281
+ seq: 42, // monotonic sequence number
282
+ p: ['prev-timeId'], // causal predecessors
283
+ cksum: 'sha256:...', // content checksum
284
+ };
285
+ ```
286
+
287
+ ### AckPayload
288
+
289
+ Server → Client acknowledgment that all (or some) receivers got a ref.
290
+
291
+ ```typescript
292
+ import { AckPayload } from '@rljson/rljson';
293
+
294
+ const ack: AckPayload = {
295
+ r: 'ref',
296
+ ok: true,
297
+ receivedBy: 3,
298
+ totalClients: 3,
299
+ };
300
+ ```
301
+
302
+ ### GapFill
303
+
304
+ Types for requesting and receiving missing refs when a sequence gap is
305
+ detected.
306
+
307
+ ```typescript
308
+ import { GapFillRequest, GapFillResponse } from '@rljson/rljson';
309
+
310
+ const request: GapFillRequest = {
311
+ route: '/sharedTree',
312
+ afterSeq: 5,
313
+ };
314
+ ```
315
+
316
+ ### SyncConfig
317
+
318
+ Feature flags to opt into hardened sync behavior. All flags are optional and
319
+ default to off.
320
+
321
+ ```typescript
322
+ import { SyncConfig } from '@rljson/rljson';
323
+
324
+ const config: SyncConfig = {
325
+ causalOrdering: true, // track predecessors + detect gaps
326
+ requireAck: true, // wait for server ACK after send
327
+ ackTimeoutMs: 5_000, // ACK timeout
328
+ includeClientIdentity: true, // attach clientId + timestamp
329
+ maxDedupSetSize: 10_000, // max refs per dedup generation (default: 10 000)
330
+ bootstrapHeartbeatMs: 30_000, // periodic bootstrap heartbeat interval (optional)
331
+ };
332
+ ```
333
+
334
+ ### SyncEventNames
335
+
336
+ Helper to generate typed, route-specific socket event names.
337
+
338
+ ```typescript
339
+ import { syncEvents } from '@rljson/rljson';
340
+
341
+ const events = syncEvents('/sharedTree');
342
+ // events.ref → '/sharedTree'
343
+ // events.ack → '/sharedTree:ack'
344
+ // events.ackClient → '/sharedTree:ack:client'
345
+ // events.gapFillReq → '/sharedTree:gapfill:req'
346
+ // events.gapFillRes → '/sharedTree:gapfill:res'
347
+ // events.bootstrap → '/sharedTree:bootstrap'
348
+ ```
349
+
350
+ ### ClientId
351
+
352
+ A stable client identity that persists across reconnections (unlike the
353
+ ephemeral Connector origin).
354
+
355
+ ```typescript
356
+ import { clientId, isClientId } from '@rljson/rljson';
357
+
358
+ const id = clientId(); // 'client_V1StGXR8_Z5j'
359
+ isClientId(id); // true
360
+ isClientId('not-a-client-id'); // false
361
+ ```
362
+
363
+ ## Utilities
364
+
365
+ ### TimeId
366
+
367
+ A unique, time-based identifier in the format `"timestamp:xxxx"`.
368
+
369
+ ```typescript
370
+ import { timeId, isTimeId, getTimeIdTimestamp } from '@rljson/rljson';
371
+
372
+ const id = timeId(); // '1700000000000:AbCd'
373
+ isTimeId(id); // true
374
+ getTimeIdTimestamp(id); // 1700000000000
375
+ ```
376
+
377
+ ### RemoveDuplicates
378
+
379
+ Deduplicates rows (by `_hash`) across all tables in an Rljson object.
380
+
381
+ ```typescript
382
+ import { removeDuplicates } from '@rljson/rljson';
383
+
384
+ const deduped = removeDuplicates(myRljsonData);
385
+ ```
386
+
387
+ ## Ecosystem
388
+
389
+ `@rljson/rljson` is the foundational layer of the RLJSON ecosystem:
390
+
391
+ | Package | Purpose |
392
+ | ------------------ | ----------------------------------- |
393
+ | `@rljson/rljson` | Core types & validation (this) |
394
+ | `@rljson/hash` | Deep hashing for RLJSON data |
395
+ | `@rljson/json` | JSON type definitions |
396
+ | `@rljson/io` | Data transport (Io, Socket, IoPeer) |
397
+ | `@rljson/bs` | Blob storage (Bs, BsPeer) |
398
+ | `@rljson/db` | Database pipeline (Db, Connector) |
399
+ | `@rljson/server` | Server relay & Client setup |
400
+ | `@rljson/fs-agent` | Filesystem ↔ database sync |
@@ -1,5 +1,9 @@
1
1
  <!--
2
+ @license
3
+ Copyright (c) 2025 Rljson
2
4
 
5
+ Use of this source code is governed by terms that can be
6
+ found in the LICENSE file in the root of this package.
3
7
  -->
4
8
 
5
9
  # Trouble shooting
@@ -47,6 +47,7 @@ export declare const exampleTreesTable: () => TreesTable;
47
47
  /**
48
48
  * Converts a plain object into a tree structure
49
49
  * @param obj - The plain object to convert
50
+ * @param skipRootCreation - If true, skips creating an automatic root node (default: false)
50
51
  * @returns An array of Tree nodes representing the tree structure
51
52
  */
52
- export declare const treeFromObject: (obj: any) => TreeWithHash[];
53
+ export declare const treeFromObject: (obj: any, skipRootCreation?: boolean) => TreeWithHash[];
package/dist/index.d.ts CHANGED
@@ -16,6 +16,12 @@ export * from './insert/insert.ts';
16
16
  export * from './insertHistory/insertHistory.ts';
17
17
  export * from './rljson.ts';
18
18
  export * from './route/route.ts';
19
+ export * from './sync/ack-payload.ts';
20
+ export * from './sync/client-id.ts';
21
+ export * from './sync/connector-payload.ts';
22
+ export * from './sync/gap-fill.ts';
23
+ export * from './sync/sync-config.ts';
24
+ export * from './sync/sync-events.ts';
19
25
  export * from './tools/remove-duplicates.ts';
20
26
  export * from './tools/time-id.ts';
21
27
  export * from './typedefs.ts';
@@ -1,6 +1,7 @@
1
1
  import { TableCfg } from '../content/table-cfg.ts';
2
2
  import { RljsonTable } from '../rljson.ts';
3
3
  import { RouteRef } from '../route/route.ts';
4
+ import { ClientId } from '../sync/client-id.ts';
4
5
  import { Ref } from '../typedefs.ts';
5
6
  export type InsertHistoryTimeId = string;
6
7
  export type InsertHistoryRow<Str extends string> = {
@@ -8,8 +9,9 @@ export type InsertHistoryRow<Str extends string> = {
8
9
  } & {
9
10
  timeId: InsertHistoryTimeId;
10
11
  route: RouteRef;
11
- origin?: Ref;
12
+ origin?: ClientId | Ref;
12
13
  previous?: InsertHistoryTimeId[];
14
+ clientTimestamp?: number;
13
15
  };
14
16
  export type InsertHistoryTable<Str extends string> = RljsonTable<InsertHistoryRow<Str>, 'insertHistory'>;
15
17
  /**
package/dist/rljson.js CHANGED
@@ -643,7 +643,7 @@ const createTreesTableCfg = (treesTableKey) => ({
643
643
  isShared: true
644
644
  });
645
645
  const exampleTreesTable = () => bakeryExample().recipesTreeTable;
646
- const treeFromObject = (obj) => {
646
+ const treeFromObject = (obj, skipRootCreation = false) => {
647
647
  const result = [];
648
648
  const processedIds = /* @__PURE__ */ new Set();
649
649
  const idToHashMap = /* @__PURE__ */ new Map();
@@ -725,13 +725,26 @@ const treeFromObject = (obj) => {
725
725
  result.push(hashedNode);
726
726
  }
727
727
  };
728
+ const topLevelIds = [];
728
729
  if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
729
730
  for (const key in obj) {
730
731
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
732
+ topLevelIds.push(key);
731
733
  processNode(obj[key], key);
732
734
  }
733
735
  }
734
736
  }
737
+ if (!skipRootCreation && topLevelIds.length > 0) {
738
+ const rootNode = {
739
+ id: "root",
740
+ isParent: true,
741
+ meta: null,
742
+ children: topLevelIds.map((id) => idToHashMap.get(id))
743
+ };
744
+ const hashedRootNode = hip(rootNode);
745
+ idToHashMap.set("root", hashedRootNode._hash);
746
+ result.push(hashedRootNode);
747
+ }
735
748
  return result;
736
749
  };
737
750
  class Example {
@@ -1913,6 +1926,12 @@ const createInsertHistoryTableCfg = (tableCfg) => ({
1913
1926
  type: "jsonArray",
1914
1927
  titleLong: "Previous",
1915
1928
  titleShort: "Previous"
1929
+ },
1930
+ {
1931
+ key: "clientTimestamp",
1932
+ type: "number",
1933
+ titleLong: "Client Timestamp",
1934
+ titleShort: "Timestamp"
1916
1935
  }
1917
1936
  ],
1918
1937
  isHead: false,
@@ -1959,6 +1978,71 @@ const iterateTables = async (rljson, callback) => {
1959
1978
  throw errors;
1960
1979
  }
1961
1980
  };
1981
+ const ackPayloadExample = () => ({
1982
+ r: "1700000000001:EfGh",
1983
+ ok: true,
1984
+ receivedBy: 3,
1985
+ totalClients: 3
1986
+ });
1987
+ const ackPayloadPartialExample = () => ({
1988
+ r: "1700000000001:EfGh",
1989
+ ok: false,
1990
+ receivedBy: 1,
1991
+ totalClients: 3
1992
+ });
1993
+ const clientId = () => {
1994
+ return "client_" + nanoid(12);
1995
+ };
1996
+ const isClientId = (id) => {
1997
+ if (!id.startsWith("client_")) return false;
1998
+ const suffix = id.slice("client_".length);
1999
+ return suffix.length === 12;
2000
+ };
2001
+ const clientIdExample = () => "client_ExAmPlE12345";
2002
+ const connectorPayloadExample = () => ({
2003
+ o: "1700000000000:AbCd",
2004
+ r: "1700000000001:EfGh"
2005
+ });
2006
+ const connectorPayloadFullExample = () => ({
2007
+ o: "1700000000000:AbCd",
2008
+ r: "1700000000001:EfGh",
2009
+ c: "client_ExAmPlE12345",
2010
+ t: 1700000000001,
2011
+ seq: 42,
2012
+ p: ["1700000000000:XyZw"],
2013
+ cksum: "sha256:abc123def456"
2014
+ });
2015
+ const gapFillRequestExample = () => ({
2016
+ route: "/sharedTree",
2017
+ afterSeq: 5,
2018
+ afterTimeId: "1700000000000:AbCd"
2019
+ });
2020
+ const gapFillResponseExample = () => ({
2021
+ route: "/sharedTree",
2022
+ refs: [
2023
+ { o: "1700000000000:AbCd", r: "1700000000006:MnOp", seq: 6 },
2024
+ { o: "1700000000000:AbCd", r: "1700000000007:QrSt", seq: 7 }
2025
+ ]
2026
+ });
2027
+ const syncConfigDefault = () => ({});
2028
+ const syncConfigFullExample = () => ({
2029
+ causalOrdering: true,
2030
+ requireAck: true,
2031
+ ackTimeoutMs: 5e3,
2032
+ includeClientIdentity: true,
2033
+ maxDedupSetSize: 1e4
2034
+ });
2035
+ const syncConfigCausalOnlyExample = () => ({
2036
+ causalOrdering: true
2037
+ });
2038
+ const syncEvents = (route) => ({
2039
+ ref: route,
2040
+ ack: `${route}:ack`,
2041
+ ackClient: `${route}:ack:client`,
2042
+ gapFillReq: `${route}:gapfill:req`,
2043
+ gapFillRes: `${route}:gapfill:res`,
2044
+ bootstrap: `${route}:bootstrap`
2045
+ });
1962
2046
  const removeDuplicates = (rljson) => {
1963
2047
  const result = {};
1964
2048
  for (const key in rljson) {
@@ -2958,8 +3042,14 @@ export {
2958
3042
  InsertValidator,
2959
3043
  Route,
2960
3044
  Validate,
3045
+ ackPayloadExample,
3046
+ ackPayloadPartialExample,
2961
3047
  addColumnsToTableCfg,
2962
3048
  bakeryExample,
3049
+ clientId,
3050
+ clientIdExample,
3051
+ connectorPayloadExample,
3052
+ connectorPayloadFullExample,
2963
3053
  contentTypes,
2964
3054
  createCakeTableCfg,
2965
3055
  createEditHistoryTableCfg,
@@ -2982,8 +3072,11 @@ export {
2982
3072
  exampleTableCfgTable,
2983
3073
  exampleTreesTable,
2984
3074
  exampleTypedefs,
3075
+ gapFillRequestExample,
3076
+ gapFillResponseExample,
2985
3077
  getTimeIdTimestamp,
2986
3078
  getTimeIdUniquePart,
3079
+ isClientId,
2987
3080
  isTimeId,
2988
3081
  isValidFieldName,
2989
3082
  iterateTables,
@@ -2994,6 +3087,10 @@ export {
2994
3087
  routeRefSeperator,
2995
3088
  routeSliceIdIndicators,
2996
3089
  routeSliceIdSeperator,
3090
+ syncConfigCausalOnlyExample,
3091
+ syncConfigDefault,
3092
+ syncConfigFullExample,
3093
+ syncEvents,
2997
3094
  throwOnInvalidTableCfg,
2998
3095
  timeId,
2999
3096
  treeFromObject,
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Server → Client acknowledgment that all (or some) receivers
3
+ * successfully received and processed a given ref.
4
+ *
5
+ * Sent on the `${route}:ack` event after the server has collected
6
+ * individual client ACKs (or after a timeout).
7
+ */
8
+ export type AckPayload = {
9
+ /** The ref being acknowledged. */
10
+ r: string;
11
+ /** `true` if all connected clients confirmed receipt; `false` on timeout / partial. */
12
+ ok: boolean;
13
+ /** Number of clients that confirmed receipt. */
14
+ receivedBy?: number;
15
+ /** Total number of receiver clients at the time of broadcast. */
16
+ totalClients?: number;
17
+ };
18
+ /**
19
+ * Returns an example AckPayload where all clients confirmed receipt.
20
+ */
21
+ export declare const ackPayloadExample: () => AckPayload;
22
+ /**
23
+ * Returns an example AckPayload for a partial / timed-out acknowledgment.
24
+ */
25
+ export declare const ackPayloadPartialExample: () => AckPayload;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A stable client identity that persists across reconnections.
3
+ *
4
+ * Unlike a Connector's ephemeral `origin` (which changes on every
5
+ * instantiation), a `ClientId` should be generated once and stored
6
+ * (e.g. in local storage) so that it can be reused across sessions.
7
+ */
8
+ export type ClientId = string;
9
+ /**
10
+ * Generates a new ClientId.
11
+ * A ClientId is a 12-character nanoid, prefixed with `"client_"` for
12
+ * easy visual identification in logs and debugging.
13
+ * @returns A new ClientId string (e.g. `"client_V1StGXR8_Z5j"`)
14
+ */
15
+ export declare const clientId: () => ClientId;
16
+ /**
17
+ * Checks whether a given string is a valid ClientId.
18
+ * A valid ClientId starts with `"client_"` followed by exactly 12
19
+ * characters.
20
+ * @param id - The string to check
21
+ * @returns `true` if the string matches the ClientId format
22
+ */
23
+ export declare const isClientId: (id: string) => boolean;
24
+ /**
25
+ * Returns an example ClientId for documentation and testing.
26
+ */
27
+ export declare const clientIdExample: () => ClientId;