@memrosetta/sync-client 0.1.0 → 0.1.2

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/index.d.ts CHANGED
@@ -7,6 +7,7 @@ declare class Outbox {
7
7
  constructor(db: Database.Database);
8
8
  addOp(op: SyncOp): void;
9
9
  getPending(): readonly SyncOp[];
10
+ countPending(): number;
10
11
  markPushed(opIds: readonly string[]): void;
11
12
  }
12
13
 
@@ -27,12 +28,27 @@ interface SyncClientConfig {
27
28
  readonly serverUrl: string;
28
29
  readonly apiKey: string;
29
30
  readonly deviceId: string;
31
+ readonly userId: string;
30
32
  }
31
33
  interface SyncClientPushResponse {
32
34
  readonly pushed: number;
33
35
  readonly results: readonly SyncPushResult[];
34
36
  readonly highWatermark: number;
35
37
  }
38
+ interface SyncStatusTimestamps {
39
+ readonly attemptAt: string | null;
40
+ readonly successAt: string | null;
41
+ }
42
+ interface SyncClientStatus {
43
+ readonly enabled: true;
44
+ readonly serverUrl: string;
45
+ readonly userId: string;
46
+ readonly deviceId: string;
47
+ readonly pendingOps: number;
48
+ readonly lastPush: SyncStatusTimestamps;
49
+ readonly lastPull: SyncStatusTimestamps;
50
+ readonly cursor: number;
51
+ }
36
52
  declare class SyncClient {
37
53
  private readonly db;
38
54
  private readonly config;
@@ -42,12 +58,15 @@ declare class SyncClient {
42
58
  initialize(): void;
43
59
  getOutbox(): Outbox;
44
60
  getInbox(): Inbox;
61
+ getStatus(): SyncClientStatus;
45
62
  push(): Promise<SyncClientPushResponse>;
46
63
  pull(): Promise<number>;
47
64
  getState(key: string): string | null;
48
65
  setState(key: string, value: string): void;
66
+ private getCursor;
67
+ private setCursor;
49
68
  }
50
69
 
51
70
  declare function ensureSyncSchema(db: Database.Database): void;
52
71
 
53
- export { Inbox, Outbox, SyncClient, ensureSyncSchema };
72
+ export { Inbox, Outbox, SyncClient, type SyncClientConfig, type SyncClientPushResponse, type SyncClientStatus, type SyncStatusTimestamps, ensureSyncSchema };
package/dist/index.js CHANGED
@@ -56,6 +56,10 @@ var Outbox = class {
56
56
  const rows = this.db.prepare("SELECT * FROM sync_outbox WHERE pushed_at IS NULL ORDER BY created_at ASC").all();
57
57
  return rows.map(rowToSyncOp);
58
58
  }
59
+ countPending() {
60
+ const row = this.db.prepare("SELECT COUNT(*) as count FROM sync_outbox WHERE pushed_at IS NULL").get();
61
+ return row.count;
62
+ }
59
63
  markPushed(opIds) {
60
64
  if (opIds.length === 0) return;
61
65
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -129,13 +133,33 @@ var SyncClient = class {
129
133
  getInbox() {
130
134
  return this.inbox;
131
135
  }
136
+ getStatus() {
137
+ return {
138
+ enabled: true,
139
+ serverUrl: this.config.serverUrl,
140
+ userId: this.config.userId,
141
+ deviceId: this.config.deviceId,
142
+ pendingOps: this.outbox.countPending(),
143
+ lastPush: {
144
+ attemptAt: this.getState("last_push_attempt_at"),
145
+ successAt: this.getState("last_push_success_at")
146
+ },
147
+ lastPull: {
148
+ attemptAt: this.getState("last_pull_attempt_at"),
149
+ successAt: this.getState("last_pull_success_at")
150
+ },
151
+ cursor: this.getCursor()
152
+ };
153
+ }
132
154
  async push() {
155
+ const now = (/* @__PURE__ */ new Date()).toISOString();
156
+ this.setState("last_push_attempt_at", now);
133
157
  const pending = this.outbox.getPending();
134
158
  if (pending.length === 0) {
159
+ this.setState("last_push_success_at", now);
135
160
  return { pushed: 0, results: [], highWatermark: 0 };
136
161
  }
137
- const baseCursorStr = this.getState("pull_cursor");
138
- const baseCursor = baseCursorStr ? parseInt(baseCursorStr, 10) : 0;
162
+ const baseCursor = this.getCursor();
139
163
  const wireOps = pending.map((op) => ({
140
164
  ...op,
141
165
  payload: typeof op.payload === "string" ? JSON.parse(op.payload) : op.payload
@@ -160,7 +184,8 @@ var SyncClient = class {
160
184
  const { results, highWatermark } = body.data;
161
185
  const pushedIds = results.filter((r) => r.status === "accepted" || r.status === "duplicate").map((r) => r.opId);
162
186
  this.outbox.markPushed(pushedIds);
163
- this.setState("pull_cursor", String(highWatermark));
187
+ this.setCursor(highWatermark);
188
+ this.setState("last_push_success_at", (/* @__PURE__ */ new Date()).toISOString());
164
189
  return {
165
190
  pushed: pushedIds.length,
166
191
  results,
@@ -168,10 +193,11 @@ var SyncClient = class {
168
193
  };
169
194
  }
170
195
  async pull() {
171
- const cursorStr = this.getState("pull_cursor");
172
- const since = cursorStr ? parseInt(cursorStr, 10) : 0;
196
+ this.setState("last_pull_attempt_at", (/* @__PURE__ */ new Date()).toISOString());
197
+ const since = this.getCursor();
173
198
  const params = new URLSearchParams({
174
- since: String(since)
199
+ since: String(since),
200
+ userId: this.config.userId
175
201
  });
176
202
  const url = `${this.config.serverUrl}/sync/pull?${params.toString()}`;
177
203
  const response = await fetch(url, {
@@ -188,7 +214,8 @@ var SyncClient = class {
188
214
  if (ops.length > 0) {
189
215
  this.inbox.addOps(ops);
190
216
  }
191
- this.setState("pull_cursor", String(nextCursor));
217
+ this.setCursor(nextCursor);
218
+ this.setState("last_pull_success_at", (/* @__PURE__ */ new Date()).toISOString());
192
219
  return ops.length;
193
220
  }
194
221
  getState(key) {
@@ -198,6 +225,15 @@ var SyncClient = class {
198
225
  setState(key, value) {
199
226
  this.db.prepare("INSERT OR REPLACE INTO sync_state (key, value) VALUES (?, ?)").run(key, value);
200
227
  }
228
+ getCursor() {
229
+ const cursorStr = this.getState("last_cursor") ?? this.getState("pull_cursor");
230
+ return cursorStr ? parseInt(cursorStr, 10) : 0;
231
+ }
232
+ setCursor(cursor) {
233
+ const value = String(cursor);
234
+ this.setState("last_cursor", value);
235
+ this.setState("pull_cursor", value);
236
+ }
201
237
  };
202
238
  export {
203
239
  Inbox,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memrosetta/sync-client",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Local-first sync client for MemRosetta (outbox/inbox, push/pull)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",