@memrosetta/sync-client 0.1.0 → 0.1.1

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