@syncular/client 0.0.2-2 → 0.0.3-6
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/client.d.ts +65 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +96 -0
- package/dist/client.js.map +1 -1
- package/dist/engine/SyncEngine.d.ts +51 -1
- package/dist/engine/SyncEngine.d.ts.map +1 -1
- package/dist/engine/SyncEngine.js +654 -9
- package/dist/engine/SyncEngine.js.map +1 -1
- package/dist/engine/types.d.ts +100 -2
- package/dist/engine/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/subscription-state.d.ts +46 -0
- package/dist/subscription-state.d.ts.map +1 -0
- package/dist/subscription-state.js +185 -0
- package/dist/subscription-state.js.map +1 -0
- package/dist/utils/id.d.ts +9 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +27 -0
- package/dist/utils/id.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +146 -0
- package/src/engine/SyncEngine.ts +836 -21
- package/src/engine/types.ts +132 -1
- package/src/index.ts +1 -0
- package/src/subscription-state.ts +259 -0
- package/src/utils/id.ts +35 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/client - Subscription state helpers
|
|
3
|
+
*
|
|
4
|
+
* Stable accessors for sync subscription metadata.
|
|
5
|
+
*/
|
|
6
|
+
import { isRecord } from '@syncular/core';
|
|
7
|
+
import { sql } from 'kysely';
|
|
8
|
+
export const DEFAULT_SYNC_STATE_ID = 'default';
|
|
9
|
+
function isScopeValues(value) {
|
|
10
|
+
if (!isRecord(value))
|
|
11
|
+
return false;
|
|
12
|
+
for (const entry of Object.values(value)) {
|
|
13
|
+
if (typeof entry === 'string')
|
|
14
|
+
continue;
|
|
15
|
+
if (Array.isArray(entry) && entry.every((v) => typeof v === 'string')) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
export function parseBootstrapState(value) {
|
|
23
|
+
if (!value)
|
|
24
|
+
return null;
|
|
25
|
+
try {
|
|
26
|
+
const parsed = typeof value === 'string' ? JSON.parse(value) : value;
|
|
27
|
+
if (!isRecord(parsed))
|
|
28
|
+
return null;
|
|
29
|
+
if (typeof parsed.asOfCommitSeq !== 'number')
|
|
30
|
+
return null;
|
|
31
|
+
if (!Array.isArray(parsed.tables))
|
|
32
|
+
return null;
|
|
33
|
+
if (!parsed.tables.every((table) => typeof table === 'string'))
|
|
34
|
+
return null;
|
|
35
|
+
if (typeof parsed.tableIndex !== 'number')
|
|
36
|
+
return null;
|
|
37
|
+
if (parsed.rowCursor !== null && typeof parsed.rowCursor !== 'string') {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
asOfCommitSeq: parsed.asOfCommitSeq,
|
|
42
|
+
tables: parsed.tables,
|
|
43
|
+
tableIndex: parsed.tableIndex,
|
|
44
|
+
rowCursor: parsed.rowCursor,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function parseScopes(value) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(value);
|
|
54
|
+
return isScopeValues(parsed) ? parsed : {};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function parseParams(value) {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(value);
|
|
63
|
+
return isRecord(parsed) ? parsed : {};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function mapSubscriptionState(row) {
|
|
70
|
+
return {
|
|
71
|
+
stateId: row.state_id,
|
|
72
|
+
subscriptionId: row.subscription_id,
|
|
73
|
+
table: row.table,
|
|
74
|
+
scopes: parseScopes(row.scopes_json),
|
|
75
|
+
params: parseParams(row.params_json),
|
|
76
|
+
cursor: row.cursor,
|
|
77
|
+
bootstrapState: parseBootstrapState(row.bootstrap_state_json),
|
|
78
|
+
status: row.status,
|
|
79
|
+
createdAt: row.created_at,
|
|
80
|
+
updatedAt: row.updated_at,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export async function listSubscriptionStates(db, options = {}) {
|
|
84
|
+
const filters = [];
|
|
85
|
+
if (options.stateId) {
|
|
86
|
+
filters.push(sql `${sql.ref('state_id')} = ${sql.val(options.stateId)}`);
|
|
87
|
+
}
|
|
88
|
+
if (options.table) {
|
|
89
|
+
filters.push(sql `${sql.ref('table')} = ${sql.val(options.table)}`);
|
|
90
|
+
}
|
|
91
|
+
if (options.status) {
|
|
92
|
+
filters.push(sql `${sql.ref('status')} = ${sql.val(options.status)}`);
|
|
93
|
+
}
|
|
94
|
+
const whereClause = filters.length > 0 ? sql `where ${sql.join(filters, sql ` and `)}` : sql ``;
|
|
95
|
+
const rows = await sql `
|
|
96
|
+
select
|
|
97
|
+
${sql.ref('state_id')},
|
|
98
|
+
${sql.ref('subscription_id')},
|
|
99
|
+
${sql.ref('table')},
|
|
100
|
+
${sql.ref('scopes_json')},
|
|
101
|
+
${sql.ref('params_json')},
|
|
102
|
+
${sql.ref('cursor')},
|
|
103
|
+
${sql.ref('bootstrap_state_json')},
|
|
104
|
+
${sql.ref('status')},
|
|
105
|
+
${sql.ref('created_at')},
|
|
106
|
+
${sql.ref('updated_at')}
|
|
107
|
+
from ${sql.table('sync_subscription_state')}
|
|
108
|
+
${whereClause}
|
|
109
|
+
order by ${sql.ref('state_id')} asc, ${sql.ref('subscription_id')} asc
|
|
110
|
+
`.execute(db);
|
|
111
|
+
return rows.rows.map((row) => mapSubscriptionState(row));
|
|
112
|
+
}
|
|
113
|
+
export async function getSubscriptionState(db, options) {
|
|
114
|
+
const stateId = options.stateId ?? DEFAULT_SYNC_STATE_ID;
|
|
115
|
+
const rows = await sql `
|
|
116
|
+
select
|
|
117
|
+
${sql.ref('state_id')},
|
|
118
|
+
${sql.ref('subscription_id')},
|
|
119
|
+
${sql.ref('table')},
|
|
120
|
+
${sql.ref('scopes_json')},
|
|
121
|
+
${sql.ref('params_json')},
|
|
122
|
+
${sql.ref('cursor')},
|
|
123
|
+
${sql.ref('bootstrap_state_json')},
|
|
124
|
+
${sql.ref('status')},
|
|
125
|
+
${sql.ref('created_at')},
|
|
126
|
+
${sql.ref('updated_at')}
|
|
127
|
+
from ${sql.table('sync_subscription_state')}
|
|
128
|
+
where
|
|
129
|
+
${sql.ref('state_id')} = ${sql.val(stateId)}
|
|
130
|
+
and ${sql.ref('subscription_id')} = ${sql.val(options.subscriptionId)}
|
|
131
|
+
limit 1
|
|
132
|
+
`.execute(db);
|
|
133
|
+
const row = rows.rows[0];
|
|
134
|
+
return row ? mapSubscriptionState(row) : null;
|
|
135
|
+
}
|
|
136
|
+
export async function upsertSubscriptionState(db, input) {
|
|
137
|
+
const now = input.nowMs ?? Date.now();
|
|
138
|
+
const stateId = input.stateId ?? DEFAULT_SYNC_STATE_ID;
|
|
139
|
+
const bootstrapStateJson = input.bootstrapState === null || input.bootstrapState === undefined
|
|
140
|
+
? null
|
|
141
|
+
: JSON.stringify(input.bootstrapState);
|
|
142
|
+
await sql `
|
|
143
|
+
insert into ${sql.table('sync_subscription_state')} (
|
|
144
|
+
${sql.ref('state_id')},
|
|
145
|
+
${sql.ref('subscription_id')},
|
|
146
|
+
${sql.ref('table')},
|
|
147
|
+
${sql.ref('scopes_json')},
|
|
148
|
+
${sql.ref('params_json')},
|
|
149
|
+
${sql.ref('cursor')},
|
|
150
|
+
${sql.ref('bootstrap_state_json')},
|
|
151
|
+
${sql.ref('status')},
|
|
152
|
+
${sql.ref('created_at')},
|
|
153
|
+
${sql.ref('updated_at')}
|
|
154
|
+
) values (
|
|
155
|
+
${sql.val(stateId)},
|
|
156
|
+
${sql.val(input.subscriptionId)},
|
|
157
|
+
${sql.val(input.table)},
|
|
158
|
+
${sql.val(JSON.stringify(input.scopes ?? {}))},
|
|
159
|
+
${sql.val(JSON.stringify(input.params ?? {}))},
|
|
160
|
+
${sql.val(input.cursor)},
|
|
161
|
+
${sql.val(bootstrapStateJson)},
|
|
162
|
+
${sql.val(input.status ?? 'active')},
|
|
163
|
+
${sql.val(now)},
|
|
164
|
+
${sql.val(now)}
|
|
165
|
+
)
|
|
166
|
+
on conflict (${sql.join([sql.ref('state_id'), sql.ref('subscription_id')])})
|
|
167
|
+
do update set
|
|
168
|
+
${sql.ref('table')} = ${sql.val(input.table)},
|
|
169
|
+
${sql.ref('scopes_json')} = ${sql.val(JSON.stringify(input.scopes ?? {}))},
|
|
170
|
+
${sql.ref('params_json')} = ${sql.val(JSON.stringify(input.params ?? {}))},
|
|
171
|
+
${sql.ref('cursor')} = ${sql.val(input.cursor)},
|
|
172
|
+
${sql.ref('bootstrap_state_json')} = ${sql.val(bootstrapStateJson)},
|
|
173
|
+
${sql.ref('status')} = ${sql.val(input.status ?? 'active')},
|
|
174
|
+
${sql.ref('updated_at')} = ${sql.val(now)}
|
|
175
|
+
`.execute(db);
|
|
176
|
+
const next = await getSubscriptionState(db, {
|
|
177
|
+
stateId,
|
|
178
|
+
subscriptionId: input.subscriptionId,
|
|
179
|
+
});
|
|
180
|
+
if (!next) {
|
|
181
|
+
throw new Error(`[subscription-state] Failed to load upserted state for "${input.subscriptionId}"`);
|
|
182
|
+
}
|
|
183
|
+
return next;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=subscription-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-state.js","sourceRoot":"","sources":["../src/subscription-state.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/id.d.ts
CHANGED
|
@@ -5,4 +5,13 @@
|
|
|
5
5
|
* Generate a random UUID v4
|
|
6
6
|
*/
|
|
7
7
|
export declare function randomUUID(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Build a stable state id from meaningful segments.
|
|
10
|
+
* Empty/undefined segments are ignored.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildStateId(...segments: Array<string | null | undefined>): string;
|
|
13
|
+
/**
|
|
14
|
+
* Create a deterministic fingerprint string for scope values.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createScopeFingerprint(scopes: Record<string, string | string[]>): string;
|
|
8
17
|
//# sourceMappingURL=id.d.ts.map
|
package/dist/utils/id.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAYnC"}
|
|
1
|
+
{"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAYnC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,QAAQ,EAAE,KAAK,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAC5C,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GACxC,MAAM,CAaR"}
|
package/dist/utils/id.js
CHANGED
|
@@ -16,4 +16,31 @@ export function randomUUID() {
|
|
|
16
16
|
return v.toString(16);
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Build a stable state id from meaningful segments.
|
|
21
|
+
* Empty/undefined segments are ignored.
|
|
22
|
+
*/
|
|
23
|
+
export function buildStateId(...segments) {
|
|
24
|
+
const normalized = segments
|
|
25
|
+
.map((segment) => segment?.trim())
|
|
26
|
+
.filter((segment) => !!segment && segment.length > 0);
|
|
27
|
+
if (normalized.length === 0)
|
|
28
|
+
return 'default';
|
|
29
|
+
return normalized.join(':');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a deterministic fingerprint string for scope values.
|
|
33
|
+
*/
|
|
34
|
+
export function createScopeFingerprint(scopes) {
|
|
35
|
+
const entries = Object.entries(scopes)
|
|
36
|
+
.map(([key, value]) => {
|
|
37
|
+
const encodedValues = (Array.isArray(value) ? [...value] : [value])
|
|
38
|
+
.map((item) => item.trim())
|
|
39
|
+
.filter((item) => item.length > 0)
|
|
40
|
+
.sort();
|
|
41
|
+
return `${key}:${encodedValues.join('|')}`;
|
|
42
|
+
})
|
|
43
|
+
.sort();
|
|
44
|
+
return entries.join(';');
|
|
45
|
+
}
|
|
19
46
|
//# sourceMappingURL=id.js.map
|
package/dist/utils/id.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,UAAU,UAAU,GAAW;IACnC,kEAAkE;IAClE,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACvD,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IAED,0BAA0B;IAC1B,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1C,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAAA,CACvB,CAAC,CAAC;AAAA,CACJ"}
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,UAAU,UAAU,GAAW;IACnC,kEAAkE;IAClE,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACvD,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;IAED,0BAA0B;IAC1B,OAAO,sCAAsC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAC1C,OAAO,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAAA,CACvB,CAAC,CAAC;AAAA,CACJ;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAG,QAA0C,EACrC;IACR,MAAM,UAAU,GAAG,QAAQ;SACxB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;SACjC,MAAM,CAAC,CAAC,OAAO,EAAqB,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE3E,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAAA,CAC7B;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAyC,EACjC;IACR,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QACrB,MAAM,aAAa,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;aAChE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;aACjC,IAAI,EAAE,CAAC;QAEV,OAAO,GAAG,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAAA,CAC5C,CAAC;SACD,IAAI,EAAE,CAAC;IAEV,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAAA,CAC1B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/client",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3-6",
|
|
4
4
|
"description": "Client-side sync engine with offline-first support, outbox, and conflict resolution",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"release": "bunx syncular-publish"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@syncular/core": "0.0.
|
|
50
|
-
"@syncular/transport-http": "0.0.
|
|
49
|
+
"@syncular/core": "0.0.3-6",
|
|
50
|
+
"@syncular/transport-http": "0.0.3-6"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"kysely": "*"
|
package/src/client.ts
CHANGED
|
@@ -23,8 +23,17 @@ import type {
|
|
|
23
23
|
ConflictInfo,
|
|
24
24
|
OutboxStats,
|
|
25
25
|
PresenceEntry,
|
|
26
|
+
SubscriptionProgress,
|
|
27
|
+
SyncAwaitBootstrapOptions,
|
|
28
|
+
SyncAwaitPhaseOptions,
|
|
29
|
+
SyncDiagnostics,
|
|
26
30
|
SyncEngineState,
|
|
31
|
+
SyncProgress,
|
|
32
|
+
SyncRepairOptions,
|
|
33
|
+
SyncResetOptions,
|
|
34
|
+
SyncResetResult,
|
|
27
35
|
SyncResult,
|
|
36
|
+
TransportHealth,
|
|
28
37
|
} from './engine/types';
|
|
29
38
|
import type { ClientTableRegistry } from './handlers/registry';
|
|
30
39
|
import { ensureClientSyncSchema } from './migrate';
|
|
@@ -35,6 +44,7 @@ import {
|
|
|
35
44
|
} from './mutations';
|
|
36
45
|
import type { SyncClientPlugin } from './plugins/types';
|
|
37
46
|
import type { SyncClientDb } from './schema';
|
|
47
|
+
import type { SubscriptionState } from './subscription-state';
|
|
38
48
|
|
|
39
49
|
// ============================================================================
|
|
40
50
|
// Types
|
|
@@ -216,7 +226,11 @@ export interface MigrationInfo {
|
|
|
216
226
|
type ClientEventType =
|
|
217
227
|
| 'sync:start'
|
|
218
228
|
| 'sync:complete'
|
|
229
|
+
| 'sync:live'
|
|
219
230
|
| 'sync:error'
|
|
231
|
+
| 'bootstrap:start'
|
|
232
|
+
| 'bootstrap:progress'
|
|
233
|
+
| 'bootstrap:complete'
|
|
220
234
|
| 'connection:change'
|
|
221
235
|
| 'data:change'
|
|
222
236
|
| 'outbox:change'
|
|
@@ -229,7 +243,25 @@ type ClientEventType =
|
|
|
229
243
|
type ClientEventPayloads = {
|
|
230
244
|
'sync:start': { timestamp: number };
|
|
231
245
|
'sync:complete': SyncResult;
|
|
246
|
+
'sync:live': { timestamp: number };
|
|
232
247
|
'sync:error': { code: string; message: string };
|
|
248
|
+
'bootstrap:start': {
|
|
249
|
+
timestamp: number;
|
|
250
|
+
stateId: string;
|
|
251
|
+
subscriptionId: string;
|
|
252
|
+
};
|
|
253
|
+
'bootstrap:progress': {
|
|
254
|
+
timestamp: number;
|
|
255
|
+
stateId: string;
|
|
256
|
+
subscriptionId: string;
|
|
257
|
+
progress: SubscriptionProgress;
|
|
258
|
+
};
|
|
259
|
+
'bootstrap:complete': {
|
|
260
|
+
timestamp: number;
|
|
261
|
+
stateId: string;
|
|
262
|
+
subscriptionId: string;
|
|
263
|
+
durationMs: number;
|
|
264
|
+
};
|
|
233
265
|
'connection:change': { previous: string; current: string };
|
|
234
266
|
'data:change': { scopes: string[]; timestamp: number };
|
|
235
267
|
'outbox:change': OutboxStats;
|
|
@@ -464,6 +496,29 @@ export class Client<DB extends SyncClientDb = SyncClientDb> {
|
|
|
464
496
|
}));
|
|
465
497
|
}
|
|
466
498
|
|
|
499
|
+
/**
|
|
500
|
+
* List persisted subscription metadata rows.
|
|
501
|
+
*/
|
|
502
|
+
async listSubscriptionStates(args?: {
|
|
503
|
+
stateId?: string;
|
|
504
|
+
table?: string;
|
|
505
|
+
status?: 'active' | 'revoked';
|
|
506
|
+
}): Promise<SubscriptionState[]> {
|
|
507
|
+
if (!this.engine) return [];
|
|
508
|
+
return this.engine.listSubscriptionStates(args);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Read one persisted subscription metadata row.
|
|
513
|
+
*/
|
|
514
|
+
async getSubscriptionState(
|
|
515
|
+
subscriptionId: string,
|
|
516
|
+
options?: { stateId?: string }
|
|
517
|
+
): Promise<SubscriptionState | null> {
|
|
518
|
+
if (!this.engine) return null;
|
|
519
|
+
return this.engine.getSubscriptionState(subscriptionId, options);
|
|
520
|
+
}
|
|
521
|
+
|
|
467
522
|
// ===========================================================================
|
|
468
523
|
// State
|
|
469
524
|
// ===========================================================================
|
|
@@ -488,6 +543,81 @@ export class Client<DB extends SyncClientDb = SyncClientDb> {
|
|
|
488
543
|
};
|
|
489
544
|
}
|
|
490
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Get current transport health details.
|
|
548
|
+
*/
|
|
549
|
+
getTransportHealth(): TransportHealth | null {
|
|
550
|
+
if (!this.engine) return null;
|
|
551
|
+
return this.engine.getTransportHealth();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Get computed sync progress across subscriptions.
|
|
556
|
+
*/
|
|
557
|
+
async getProgress(): Promise<SyncProgress | null> {
|
|
558
|
+
if (!this.engine) return null;
|
|
559
|
+
return this.engine.getProgress();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Get a diagnostics snapshot for support/debug flows.
|
|
564
|
+
*/
|
|
565
|
+
async getDiagnostics(): Promise<SyncDiagnostics | null> {
|
|
566
|
+
if (!this.engine) return null;
|
|
567
|
+
return this.engine.getDiagnostics();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Reset local sync metadata (and optionally synced app rows/outbox/conflicts).
|
|
572
|
+
*/
|
|
573
|
+
async reset(options: SyncResetOptions): Promise<SyncResetResult> {
|
|
574
|
+
if (!this.engine) {
|
|
575
|
+
return {
|
|
576
|
+
deletedSubscriptionStates: 0,
|
|
577
|
+
deletedOutboxCommits: 0,
|
|
578
|
+
deletedConflicts: 0,
|
|
579
|
+
clearedTables: [],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return this.engine.reset(options);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Run a built-in repair flow for common corruption scenarios.
|
|
587
|
+
*/
|
|
588
|
+
async repair(options: SyncRepairOptions): Promise<SyncResetResult> {
|
|
589
|
+
if (!this.engine) {
|
|
590
|
+
return {
|
|
591
|
+
deletedSubscriptionStates: 0,
|
|
592
|
+
deletedOutboxCommits: 0,
|
|
593
|
+
deletedConflicts: 0,
|
|
594
|
+
clearedTables: [],
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return this.engine.repair(options);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Wait until the channel reaches a target phase.
|
|
602
|
+
*/
|
|
603
|
+
async awaitPhase(
|
|
604
|
+
phase: SyncProgress['channelPhase'],
|
|
605
|
+
options: SyncAwaitPhaseOptions = {}
|
|
606
|
+
): Promise<SyncProgress | null> {
|
|
607
|
+
if (!this.engine) return null;
|
|
608
|
+
return this.engine.awaitPhase(phase, options);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Wait until bootstrap completes for the default state or a specific subscription.
|
|
613
|
+
*/
|
|
614
|
+
async awaitBootstrapComplete(
|
|
615
|
+
options: SyncAwaitBootstrapOptions = {}
|
|
616
|
+
): Promise<SyncProgress | null> {
|
|
617
|
+
if (!this.engine) return null;
|
|
618
|
+
return this.engine.awaitBootstrapComplete(options);
|
|
619
|
+
}
|
|
620
|
+
|
|
491
621
|
/**
|
|
492
622
|
* Subscribe to state changes (for useSyncExternalStore).
|
|
493
623
|
*/
|
|
@@ -783,6 +913,10 @@ export class Client<DB extends SyncClientDb = SyncClientDb> {
|
|
|
783
913
|
});
|
|
784
914
|
});
|
|
785
915
|
|
|
916
|
+
this.engine.on('sync:live', (payload) => {
|
|
917
|
+
this.emit('sync:live', payload);
|
|
918
|
+
});
|
|
919
|
+
|
|
786
920
|
this.engine.on('sync:error', (error) => {
|
|
787
921
|
this.emit('sync:error', { code: error.code, message: error.message });
|
|
788
922
|
|
|
@@ -790,6 +924,18 @@ export class Client<DB extends SyncClientDb = SyncClientDb> {
|
|
|
790
924
|
this.checkForNewConflicts();
|
|
791
925
|
});
|
|
792
926
|
|
|
927
|
+
this.engine.on('bootstrap:start', (payload) => {
|
|
928
|
+
this.emit('bootstrap:start', payload);
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
this.engine.on('bootstrap:progress', (payload) => {
|
|
932
|
+
this.emit('bootstrap:progress', payload);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
this.engine.on('bootstrap:complete', (payload) => {
|
|
936
|
+
this.emit('bootstrap:complete', payload);
|
|
937
|
+
});
|
|
938
|
+
|
|
793
939
|
this.engine.on('connection:change', (payload) => {
|
|
794
940
|
this.emit('connection:change', payload);
|
|
795
941
|
});
|