@furystack/entity-sync 3.0.3 → 3.0.4

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/README.md CHANGED
@@ -1,83 +1,83 @@
1
- # @furystack/entity-sync
2
-
3
- Shared protocol types for FuryStack entity synchronization over WebSocket.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @furystack/entity-sync
9
- # or
10
- yarn add @furystack/entity-sync
11
- ```
12
-
13
- ## Overview
14
-
15
- This package defines the wire protocol used between `@furystack/entity-sync-client` and `@furystack/entity-sync-service`. It exports only TypeScript types -- there is no runtime code.
16
-
17
- ### Protocol Flow
18
-
19
- ```
20
- Client Server
21
- │ │
22
- │──── subscribe-entity / subscribe-collection ──▶│
23
- │ │
24
- │◀──── subscribed (snapshot or delta) ──────────│
25
- │ │
26
- │◀──── entity-added / entity-updated / ─────────│
27
- │ entity-removed (incremental) │
28
- │ │
29
- │──── unsubscribe ──────────────────────────────▶│
30
- ```
31
-
32
- ## Types
33
-
34
- ### `SyncVersion`
35
-
36
- Version metadata included in all server sync messages:
37
-
38
- ```ts
39
- type SyncVersion = {
40
- seq: number // Monotonically increasing sequence number per model
41
- timestamp: string // ISO 8601 timestamp
42
- }
43
- ```
44
-
45
- ### `SyncChangeEntry`
46
-
47
- A single entry in a delta replay, representing one change to an entity:
48
-
49
- ```ts
50
- type SyncChangeEntry =
51
- | { type: 'added'; entity: unknown; version: SyncVersion }
52
- | { type: 'updated'; id: unknown; change: Record<string, unknown>; version: SyncVersion }
53
- | { type: 'removed'; id: unknown; version: SyncVersion }
54
- ```
55
-
56
- ### `ClientSyncMessage`
57
-
58
- Messages sent from client to server:
59
-
60
- - `subscribe-entity` -- subscribe to a single entity by primary key (with optional `lastSeq` for delta sync)
61
- - `subscribe-collection` -- subscribe to a filtered, ordered collection (with optional `lastSeq`)
62
- - `unsubscribe` -- remove a subscription by ID
63
-
64
- ### `ServerSyncMessage`
65
-
66
- Messages sent from server to client:
67
-
68
- - `subscribed` -- subscription confirmation with either a full `snapshot` or a `delta` (array of `SyncChangeEntry`)
69
- - `entity-added` / `entity-updated` / `entity-removed` -- incremental changes pushed to active subscriptions
70
- - `subscription-error` -- error response for a subscription request
71
-
72
- ### `SyncState<T>`
73
-
74
- Client-side discriminated union representing the current state of a subscription:
75
-
76
- ```ts
77
- type SyncState<T> =
78
- | { status: 'connecting' }
79
- | { status: 'cached'; data: T }
80
- | { status: 'suspended'; data: T }
81
- | { status: 'synced'; data: T }
82
- | { status: 'error'; error: string }
83
- ```
1
+ # @furystack/entity-sync
2
+
3
+ Shared protocol types for FuryStack entity synchronization over WebSocket.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @furystack/entity-sync
9
+ # or
10
+ yarn add @furystack/entity-sync
11
+ ```
12
+
13
+ ## Overview
14
+
15
+ This package defines the wire protocol used between `@furystack/entity-sync-client` and `@furystack/entity-sync-service`. It exports only TypeScript types -- there is no runtime code.
16
+
17
+ ### Protocol Flow
18
+
19
+ ```
20
+ Client Server
21
+ │ │
22
+ │──── subscribe-entity / subscribe-collection ──▶│
23
+ │ │
24
+ │◀──── subscribed (snapshot or delta) ──────────│
25
+ │ │
26
+ │◀──── entity-added / entity-updated / ─────────│
27
+ │ entity-removed (incremental) │
28
+ │ │
29
+ │──── unsubscribe ──────────────────────────────▶│
30
+ ```
31
+
32
+ ## Types
33
+
34
+ ### `SyncVersion`
35
+
36
+ Version metadata included in all server sync messages:
37
+
38
+ ```ts
39
+ type SyncVersion = {
40
+ seq: number // Monotonically increasing sequence number per model
41
+ timestamp: string // ISO 8601 timestamp
42
+ }
43
+ ```
44
+
45
+ ### `SyncChangeEntry`
46
+
47
+ A single entry in a delta replay, representing one change to an entity:
48
+
49
+ ```ts
50
+ type SyncChangeEntry =
51
+ | { type: 'added'; entity: unknown; version: SyncVersion }
52
+ | { type: 'updated'; id: unknown; change: Record<string, unknown>; version: SyncVersion }
53
+ | { type: 'removed'; id: unknown; version: SyncVersion }
54
+ ```
55
+
56
+ ### `ClientSyncMessage`
57
+
58
+ Messages sent from client to server:
59
+
60
+ - `subscribe-entity` -- subscribe to a single entity by primary key (with optional `lastSeq` for delta sync)
61
+ - `subscribe-collection` -- subscribe to a filtered, ordered collection (with optional `lastSeq`)
62
+ - `unsubscribe` -- remove a subscription by ID
63
+
64
+ ### `ServerSyncMessage`
65
+
66
+ Messages sent from server to client:
67
+
68
+ - `subscribed` -- subscription confirmation with either a full `snapshot` or a `delta` (array of `SyncChangeEntry`)
69
+ - `entity-added` / `entity-updated` / `entity-removed` -- incremental changes pushed to active subscriptions
70
+ - `subscription-error` -- error response for a subscription request
71
+
72
+ ### `SyncState<T>`
73
+
74
+ Client-side discriminated union representing the current state of a subscription:
75
+
76
+ ```ts
77
+ type SyncState<T> =
78
+ | { status: 'connecting' }
79
+ | { status: 'cached'; data: T }
80
+ | { status: 'suspended'; data: T }
81
+ | { status: 'synced'; data: T }
82
+ | { status: 'error'; error: string }
83
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@furystack/entity-sync",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "Shared protocol types for FuryStack entity synchronization",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "homepage": "https://github.com/furystack/furystack",
39
39
  "dependencies": {
40
- "@furystack/core": "^17.1.3"
40
+ "@furystack/core": "^17.1.4"
41
41
  },
42
42
  "devDependencies": {
43
43
  "typescript": "^7.0.2"
@@ -1,28 +1,28 @@
1
- import type { FilterType } from '@furystack/core'
2
-
3
- /**
4
- * Messages sent from client to server over the WebSocket sync protocol.
5
- * The `model` field is derived from `constructor.name` on both sides.
6
- */
7
- export type ClientSyncMessage =
8
- | {
9
- type: 'subscribe-entity'
10
- requestId: string
11
- model: string
12
- key: unknown
13
- lastSeq?: string
14
- }
15
- | {
16
- type: 'subscribe-collection'
17
- requestId: string
18
- model: string
19
- filter?: FilterType<unknown>
20
- top?: number
21
- skip?: number
22
- order?: Record<string, 'ASC' | 'DESC'>
23
- lastSeq?: string
24
- }
25
- | {
26
- type: 'unsubscribe'
27
- subscriptionId: string
28
- }
1
+ import type { FilterType } from '@furystack/core'
2
+
3
+ /**
4
+ * Messages sent from client to server over the WebSocket sync protocol.
5
+ * The `model` field is derived from `constructor.name` on both sides.
6
+ */
7
+ export type ClientSyncMessage =
8
+ | {
9
+ type: 'subscribe-entity'
10
+ requestId: string
11
+ model: string
12
+ key: unknown
13
+ lastSeq?: string
14
+ }
15
+ | {
16
+ type: 'subscribe-collection'
17
+ requestId: string
18
+ model: string
19
+ filter?: FilterType<unknown>
20
+ top?: number
21
+ skip?: number
22
+ order?: Record<string, 'ASC' | 'DESC'>
23
+ lastSeq?: string
24
+ }
25
+ | {
26
+ type: 'unsubscribe'
27
+ subscriptionId: string
28
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- export type * from './sync-version.js'
2
- export type * from './sync-change-entry.js'
3
- export type * from './client-sync-message.js'
4
- export type * from './server-sync-message.js'
5
- export type * from './sync-state.js'
6
- export type { FilterType } from '@furystack/core'
1
+ export type * from './sync-version.js'
2
+ export type * from './sync-change-entry.js'
3
+ export type * from './client-sync-message.js'
4
+ export type * from './server-sync-message.js'
5
+ export type * from './sync-state.js'
6
+ export type { FilterType } from '@furystack/core'
@@ -1,64 +1,64 @@
1
- import type { SyncChangeEntry } from './sync-change-entry.js'
2
- import type { SyncVersion } from './sync-version.js'
3
-
4
- /**
5
- * Messages sent from server to client over the WebSocket sync protocol
6
- */
7
- export type ServerSyncMessage =
8
- | {
9
- type: 'subscribed'
10
- requestId: string
11
- subscriptionId: string
12
- model: string
13
- /** The primary key field name (included for collection subscriptions) */
14
- primaryKey?: string
15
- mode: 'snapshot'
16
- data: unknown
17
- /** Total count of matching entities (ignoring top/skip). Included for collection subscriptions. */
18
- totalCount?: number
19
- version: SyncVersion
20
- }
21
- | {
22
- type: 'subscribed'
23
- requestId: string
24
- subscriptionId: string
25
- model: string
26
- /** The primary key field name (included for collection subscriptions) */
27
- primaryKey?: string
28
- mode: 'delta'
29
- changes: SyncChangeEntry[]
30
- /** Total count of matching entities (ignoring top/skip). Included for collection subscriptions. */
31
- totalCount?: number
32
- version: SyncVersion
33
- }
34
- | {
35
- type: 'entity-added'
36
- subscriptionId: string
37
- entity: unknown
38
- version: SyncVersion
39
- }
40
- | {
41
- type: 'entity-updated'
42
- subscriptionId: string
43
- id: unknown
44
- change: Record<string, unknown>
45
- version: SyncVersion
46
- }
47
- | {
48
- type: 'entity-removed'
49
- subscriptionId: string
50
- id: unknown
51
- version: SyncVersion
52
- }
53
- | {
54
- type: 'collection-snapshot'
55
- subscriptionId: string
56
- data: unknown[]
57
- totalCount: number
58
- version: SyncVersion
59
- }
60
- | {
61
- type: 'subscription-error'
62
- requestId: string
63
- error: string
64
- }
1
+ import type { SyncChangeEntry } from './sync-change-entry.js'
2
+ import type { SyncVersion } from './sync-version.js'
3
+
4
+ /**
5
+ * Messages sent from server to client over the WebSocket sync protocol
6
+ */
7
+ export type ServerSyncMessage =
8
+ | {
9
+ type: 'subscribed'
10
+ requestId: string
11
+ subscriptionId: string
12
+ model: string
13
+ /** The primary key field name (included for collection subscriptions) */
14
+ primaryKey?: string
15
+ mode: 'snapshot'
16
+ data: unknown
17
+ /** Total count of matching entities (ignoring top/skip). Included for collection subscriptions. */
18
+ totalCount?: number
19
+ version: SyncVersion
20
+ }
21
+ | {
22
+ type: 'subscribed'
23
+ requestId: string
24
+ subscriptionId: string
25
+ model: string
26
+ /** The primary key field name (included for collection subscriptions) */
27
+ primaryKey?: string
28
+ mode: 'delta'
29
+ changes: SyncChangeEntry[]
30
+ /** Total count of matching entities (ignoring top/skip). Included for collection subscriptions. */
31
+ totalCount?: number
32
+ version: SyncVersion
33
+ }
34
+ | {
35
+ type: 'entity-added'
36
+ subscriptionId: string
37
+ entity: unknown
38
+ version: SyncVersion
39
+ }
40
+ | {
41
+ type: 'entity-updated'
42
+ subscriptionId: string
43
+ id: unknown
44
+ change: Record<string, unknown>
45
+ version: SyncVersion
46
+ }
47
+ | {
48
+ type: 'entity-removed'
49
+ subscriptionId: string
50
+ id: unknown
51
+ version: SyncVersion
52
+ }
53
+ | {
54
+ type: 'collection-snapshot'
55
+ subscriptionId: string
56
+ data: unknown[]
57
+ totalCount: number
58
+ version: SyncVersion
59
+ }
60
+ | {
61
+ type: 'subscription-error'
62
+ requestId: string
63
+ error: string
64
+ }
@@ -1,9 +1,9 @@
1
- import type { SyncVersion } from './sync-version.js'
2
-
3
- /**
4
- * A single entry in a delta replay, representing one change to an entity
5
- */
6
- export type SyncChangeEntry =
7
- | { type: 'added'; entity: unknown; version: SyncVersion }
8
- | { type: 'updated'; id: unknown; change: Record<string, unknown>; version: SyncVersion }
9
- | { type: 'removed'; id: unknown; version: SyncVersion }
1
+ import type { SyncVersion } from './sync-version.js'
2
+
3
+ /**
4
+ * A single entry in a delta replay, representing one change to an entity
5
+ */
6
+ export type SyncChangeEntry =
7
+ | { type: 'added'; entity: unknown; version: SyncVersion }
8
+ | { type: 'updated'; id: unknown; change: Record<string, unknown>; version: SyncVersion }
9
+ | { type: 'removed'; id: unknown; version: SyncVersion }
package/src/sync-state.ts CHANGED
@@ -1,20 +1,20 @@
1
- /**
2
- * Discriminated union over the lifecycle of a subscribed entity or
3
- * collection on the client:
4
- *
5
- * - `connecting` — handshake in progress, no `data` yet (or first load).
6
- * - `cached` — local cache hit; rendering immediately while a `delta` sync
7
- * confirms freshness.
8
- * - `suspended` — connection dropped; the last-known `data` is still
9
- * served while reconnect attempts run in the background.
10
- * - `synced` — server confirmed our seq matches; `data` is authoritative.
11
- * - `error` — terminal: subscription rejected; `error` carries the reason.
12
- *
13
- * Discriminate on `status` to narrow.
14
- */
15
- export type SyncState<T> =
16
- | { status: 'connecting' }
17
- | { status: 'cached'; data: T }
18
- | { status: 'suspended'; data: T }
19
- | { status: 'synced'; data: T }
20
- | { status: 'error'; error: string }
1
+ /**
2
+ * Discriminated union over the lifecycle of a subscribed entity or
3
+ * collection on the client:
4
+ *
5
+ * - `connecting` — handshake in progress, no `data` yet (or first load).
6
+ * - `cached` — local cache hit; rendering immediately while a `delta` sync
7
+ * confirms freshness.
8
+ * - `suspended` — connection dropped; the last-known `data` is still
9
+ * served while reconnect attempts run in the background.
10
+ * - `synced` — server confirmed our seq matches; `data` is authoritative.
11
+ * - `error` — terminal: subscription rejected; `error` carries the reason.
12
+ *
13
+ * Discriminate on `status` to narrow.
14
+ */
15
+ export type SyncState<T> =
16
+ | { status: 'connecting' }
17
+ | { status: 'cached'; data: T }
18
+ | { status: 'suspended'; data: T }
19
+ | { status: 'synced'; data: T }
20
+ | { status: 'error'; error: string }
@@ -1,18 +1,18 @@
1
- /**
2
- * Version metadata included in all server sync messages.
3
- *
4
- * `seq` is an **opaque, ordered token** assigned by the server's underlying
5
- * cross-node bus adapter — the broadcaster never invents the value, it only
6
- * forwards what the bus stamps on each message. Clients **must not** compare
7
- * seqs lexicographically or numerically: the only safe operations are
8
- * round-tripping the value back to the server (via `lastSeq` on
9
- * `ClientSyncMessage`) and equality. The string encoding lets adapters use
10
- * native id formats — e.g. integer counters in-process, `<ms>-<n>` Redis
11
- * Stream ids — without a translation layer.
12
- */
13
- export type SyncVersion = {
14
- /** Adapter-assigned, opaque, ordered per-topic identifier. */
15
- seq: string
16
- /** ISO 8601 timestamp, used for time-based changelog pruning */
17
- timestamp: string
18
- }
1
+ /**
2
+ * Version metadata included in all server sync messages.
3
+ *
4
+ * `seq` is an **opaque, ordered token** assigned by the server's underlying
5
+ * cross-node bus adapter — the broadcaster never invents the value, it only
6
+ * forwards what the bus stamps on each message. Clients **must not** compare
7
+ * seqs lexicographically or numerically: the only safe operations are
8
+ * round-tripping the value back to the server (via `lastSeq` on
9
+ * `ClientSyncMessage`) and equality. The string encoding lets adapters use
10
+ * native id formats — e.g. integer counters in-process, `<ms>-<n>` Redis
11
+ * Stream ids — without a translation layer.
12
+ */
13
+ export type SyncVersion = {
14
+ /** Adapter-assigned, opaque, ordered per-topic identifier. */
15
+ seq: string
16
+ /** ISO 8601 timestamp, used for time-based changelog pruning */
17
+ timestamp: string
18
+ }