@g9tm/syncdata 1.5.19 → 1.5.20

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/PARITY.md CHANGED
@@ -48,6 +48,8 @@ it). This file maps every .NET piece to its twin and lists every deliberate diff
48
48
  | A value its type cannot carry refuses only its row, on the device (LES-0088) | `findUnencodable` | `FindUnencodable`: the writer's conversions run before a row enters the batch; a failure is `G9S-REJ-MALFORMED` with the column, counted in `rejectedLocally`; `MalformedLocalValueTests` / `test/malformedLocalValue.test.ts` |
49
49
  | Lost = pushed columns only; timestamps as instants (LES-0086) | `pushedMask`, `sameValue(…, type)` / `instant` | `PushedMask` / `SameValue(type, …)` / `Instant`: the dirty mask restricted to the batch's columns; DateTime/DateTimeOffset text in either form equal within one microsecond (TS: no BigInt - milliseconds plus 100 ns units); `HeldValueTests` / `test/heldValues.test.ts` |
50
50
  | `SaveHelloAsync(hello, localMidpointUtc, ct)` (LES-0075) | `saveHello(hello, signal, localMidpointMs)` | the engine measures the Hello round trip in both clients and stores `serverTime - midpoint` |
51
+ | `RefetchRowsAsync(table, keys?)` (1.5.19, LES-0090) | `refetchRows(table, keys?)` | the same rows (clean, held: `dirty = 0 AND rejected = 0 AND deleted = 0 AND subs <> 0`) get `server_ver = -1`, their holders NeedsReconcile; the holder bitmask of .NET is one `EXISTS … (subs >> slot) & 1` statement; keys canonicalised, pages of 5000; `test/refetchAndRelease.test.ts` |
52
+ | `ReleaseUnclaimedRowsAsync(table, keys?, includePending)` (1.5.19) | `releaseUnclaimedRows(table, keys?, includePending)` | the same selection (`subs = 0 AND server_ver = 0`, not revoked / quarantined / ephemeral / in a pending command; pending and refused only with `includePending`), read whole before the disposal (`temp.g9_unclaimed`, key order), capture off, reason `G9ERevocationReason.Unclaimed` (4), `changedKeys` capped by `maxChangedKeysPerTable`, RowsApplied (PostStep) when anything left; `test/refetchAndRelease.test.ts`, `test/engine.test.ts`. Until 1.5.20 both were .NET only (1.5.19 shipped without the twins) |
51
53
  | `G9CSqliteException` (`IG9SyncLocalStoreFailure`) | `G9SqliteStoreError` (`localStoreFailure`) | classified from the driver's code |
52
54
  | `G9CSignalRSyncTransport`, options | `G9SignalRSyncTransport` (`@g9tm/syncdata/signalr`) | over `G9Client` of `@g9tm/signalr-supernetcore-client`; Lynx: `createLynxSyncTransport`. Limits (ADR-0074): `CallTimeout` / `PlanTimeout` / `PushTimeout` / `StreamIdleTimeout` = `callTimeoutMs` / `planTimeoutMs` / `pushTimeoutMs` / `streamIdleTimeoutMs`, same defaults, same retryable `G9S-NET-FAILURE`, same connection drop (`SignalRTimeoutTests` / `test/transportTimeouts.test.ts`). After an automatic reconnect both resend the last accepted Hello so the new connection rejoins its poke groups (LES-0087: `OnReconnectedAsync` / `_rejoin`; `SignalRTransportTests.After_the_connection_reconnects_by_itself_…` / `test/transportRejoin.test.ts`) |
53
55
  | `G9CInMemorySyncTransport`, `G9CTransportFaults` | `G9FaultInjectingTransport`, `G9TransportFaults` (`@g9tm/syncdata/testing`) | wraps a real transport (no in-process server engine in JavaScript) |
@@ -2,7 +2,7 @@ import { G9SyncParameters } from '../abstractions/parameters.js';
2
2
  import { type G9DtHelloResult, type G9DtNegotiatedTable, type G9DtSubscriptionState } from '../protocol/dtos.js';
3
3
  import type { G9SyncClientModel } from './model.js';
4
4
  import { G9SyncRun, type G9RowsAppliedEvent, type G9SubscriptionOptions, type G9SyncEngineOptions, type G9SyncHealth, type G9SyncRequest, type G9SyncRunResult } from './runs.js';
5
- import { type G9ChangeFeedEntry, type G9ChangeFeedKeyFilter, type G9LocalCommand, type G9LocalCommandWriter, type G9ELocalCommandState, type G9PendingSummary, type G9QuarantinedRow, type G9RetainedRow, type G9SyncClientStore, type G9SyncRejection, type G9SyncSubscriptionRecord } from './storeTypes.js';
5
+ import { G9ApplyReport, type G9ChangeFeedEntry, type G9ChangeFeedKeyFilter, type G9LocalCommand, type G9LocalCommandWriter, type G9ELocalCommandState, type G9PendingSummary, type G9QuarantinedRow, type G9RetainedRow, type G9SyncClientStore, type G9SyncRejection, type G9SyncSubscriptionRecord } from './storeTypes.js';
6
6
  import type { G9SyncTransport } from './transport.js';
7
7
  /**
8
8
  * The client sync engine (`G9CSyncEngine`): one per local database. Owns a prioritised run queue, so status queries
@@ -91,6 +91,21 @@ export declare class G9SyncEngine {
91
91
  getChangeFeedFloor(signal?: AbortSignal): Promise<number>;
92
92
  adoptExistingRows(signal?: AbortSignal): Promise<number>;
93
93
  markDirty(localTable: string, keys: readonly string[], signal?: AbortSignal): Promise<number>;
94
+ /**
95
+ * `RefetchRowsAsync` (1.5.19): has the next sync fetch these rows again and overwrite their local values with the server's
96
+ * (all of a table, or `keys`) - for a server whose READ of a column changed while the rows did not, e.g. a column it now
97
+ * serves empty (a reconcile compares key + version, never values). Only clean rows a subscription holds; a row with a pending
98
+ * change, a rejection or a delete is left alone. Nothing is uploaded. Their subscriptions become NeedsReconcile, so the next
99
+ * sync of any kind does it, offline in between or not. Returns the rows marked.
100
+ */
101
+ refetchRows(localTable: string, keys?: readonly string[] | null, signal?: AbortSignal): Promise<number>;
102
+ /**
103
+ * `ReleaseUnclaimedRowsAsync` (1.5.19): removes rows of one table (all, or `keys`) that no subscription holds and the server
104
+ * never claimed (`server_ver = 0`: adopted from the device's own table, never confirmed since). Locally only: capture is off,
105
+ * nothing is uploaded. A row with a pending (or refused) change stays unless `includePending` - then the revocation policy
106
+ * decides (by default quarantined, the refusal with it). Raises RowsApplied (PostStep) when anything left.
107
+ */
108
+ releaseUnclaimedRows(localTable: string, keys?: readonly string[] | null, includePending?: boolean, signal?: AbortSignal): Promise<G9ApplyReport>;
94
109
  getHealth(signal?: AbortSignal): Promise<G9SyncHealth>;
95
110
  dispose(): Promise<void>;
96
111
  private _signal;
@@ -276,6 +276,34 @@ export class G9SyncEngine {
276
276
  await this.initialize(signal);
277
277
  return this._store.markDirty(localTable, keys, signal);
278
278
  }
279
+ /**
280
+ * `RefetchRowsAsync` (1.5.19): has the next sync fetch these rows again and overwrite their local values with the server's
281
+ * (all of a table, or `keys`) - for a server whose READ of a column changed while the rows did not, e.g. a column it now
282
+ * serves empty (a reconcile compares key + version, never values). Only clean rows a subscription holds; a row with a pending
283
+ * change, a rejection or a delete is left alone. Nothing is uploaded. Their subscriptions become NeedsReconcile, so the next
284
+ * sync of any kind does it, offline in between or not. Returns the rows marked.
285
+ */
286
+ async refetchRows(localTable, keys = null, signal) {
287
+ if (!localTable || !localTable.trim())
288
+ throw new Error('localTable is required');
289
+ await this.initialize(signal);
290
+ return this._store.refetchRows(localTable, keys, signal);
291
+ }
292
+ /**
293
+ * `ReleaseUnclaimedRowsAsync` (1.5.19): removes rows of one table (all, or `keys`) that no subscription holds and the server
294
+ * never claimed (`server_ver = 0`: adopted from the device's own table, never confirmed since). Locally only: capture is off,
295
+ * nothing is uploaded. A row with a pending (or refused) change stays unless `includePending` - then the revocation policy
296
+ * decides (by default quarantined, the refusal with it). Raises RowsApplied (PostStep) when anything left.
297
+ */
298
+ async releaseUnclaimedRows(localTable, keys = null, includePending = false, signal) {
299
+ if (!localTable || !localTable.trim())
300
+ throw new Error('localTable is required');
301
+ await this.initialize(signal);
302
+ const report = await this._store.releaseUnclaimedRows(localTable, keys, includePending, signal);
303
+ if (report.revokedDeleted + report.revokedQuarantined + report.revokedRetained > 0)
304
+ this._raiseRowsApplied(report, G9ESyncPhase.PostStep);
305
+ return report;
306
+ }
279
307
  async getHealth(signal) {
280
308
  await this.initialize(signal);
281
309
  const state = await this._store.getServerState(signal);