@g9tm/syncdata 1.5.20 → 1.5.21

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
@@ -79,6 +79,13 @@ it). This file maps every .NET piece to its twin and lists every deliberate diff
79
79
  would hand out, in model order) so its triggers are written in the same batch. Same objects, rows, tids and counter
80
80
  (`test/provision.test.ts` compares both paths on fresh, reopened, evolved and repaired databases). AgriPad's 47 tables:
81
81
  14 driver calls on every start instead of 296, 17 instead of 534 on the first.
82
+ - **Hello sends the model once per connection (1.5.21).** `G9CSignalRSyncTransport.HelloAsync` / `G9SignalRSyncTransport.hello`
83
+ strip the tables on a later Hello of the same connection (`_modelOnConnection`, keyed by the connection id and the engine's one
84
+ model list/array) and resend them inside the call on `ModelRequired`; the engines keep the last answer in memory
85
+ (`G9CSyncEngine.HelloCache` / `_lastAnswer`), send `KnownTablesHash` / `KnownDefinitionsHash` and put back what an "unchanged"
86
+ answer left out (`RestoreUnchanged` / `restoreUnchanged`); both stores skip `definitions_json`, `tables_json` and the per-table
87
+ updates of an unchanged answer. Tests: `HelloModelOnceTests` / `test/transportModelOnce.test.ts`; the golden Hello samples carry
88
+ the six new fields.
82
89
  - **Per-run bookkeeping.** `saveHello` writes the meta rows, the definitions' move-out policies and every negotiated
83
90
  table (with its capture triggers) in ONE batch, after one read for the only per-table question (does a table with
84
91
  newly negotiated columns hold rows?); the rows an apply protects (`readRowsChangedAfter`) are read with one
@@ -32,6 +32,13 @@ export declare class G9SyncEngine {
32
32
  private _disposed;
33
33
  /** The model problems last logged, so the warning is said once per distinct set (ADR-0061). */
34
34
  private _reportedModelProblems;
35
+ /**
36
+ * What Hello does not send again (1.5.21, TR-0069; twin of .NET `G9CSyncEngine.HelloCache`): the last negotiated tables and definitions,
37
+ * with the server's ids for them. In memory only: the first Hello of a process gets them in full.
38
+ */
39
+ private _lastAnswer;
40
+ /** The model as Hello sends it, built once: the transport recognises an unchanged model by this very array. */
41
+ private _wireTables;
35
42
  constructor(store: G9SyncClientStore, transport: G9SyncTransport, model: G9SyncClientModel, options?: G9SyncEngineOptions);
36
43
  get clientId(): string;
37
44
  get isRunning(): boolean;
@@ -42,6 +42,13 @@ export class G9SyncEngine {
42
42
  this._disposed = false;
43
43
  /** The model problems last logged, so the warning is said once per distinct set (ADR-0061). */
44
44
  this._reportedModelProblems = null;
45
+ /**
46
+ * What Hello does not send again (1.5.21, TR-0069; twin of .NET `G9CSyncEngine.HelloCache`): the last negotiated tables and definitions,
47
+ * with the server's ids for them. In memory only: the first Hello of a process gets them in full.
48
+ */
49
+ this._lastAnswer = null;
50
+ /** The model as Hello sends it, built once: the transport recognises an unchanged model by this very array. */
51
+ this._wireTables = null;
45
52
  if (!store)
46
53
  throw new TypeError('A store is required.');
47
54
  if (!transport)
@@ -459,6 +466,7 @@ export class G9SyncEngine {
459
466
  }
460
467
  // ================================================================ execution
461
468
  async _executeRun(run) {
469
+ var _a, _b, _c;
462
470
  const request = run.request;
463
471
  const startedUtcMs = Date.now();
464
472
  const changes = new G9ApplyReport();
@@ -493,10 +501,19 @@ export class G9SyncEngine {
493
501
  hostScope = await this._options.hooks.beginRun(run, ct);
494
502
  // ---------------------------------------------------------------- 1. connect + hello
495
503
  tracker.enter(G9ESyncPhase.Connecting);
504
+ // Where the Connecting phase goes (AgriPad TR-0069, 2026-11-13: «Connecting takes long»): each step is timed and the
505
+ // split logged once per run (event 20030) — the transport, the ephemeral sweep, reading the subscriptions, the
506
+ // Hello round trip and the save of its answer are separate costs with separate cures.
507
+ const connectT0 = Date.now();
496
508
  await this._ensureConnected(ct);
509
+ const connectT1 = Date.now();
497
510
  await this._store.collectExpiredEphemeral(Date.now(), ct);
511
+ const connectT2 = Date.now();
498
512
  const subscriptions = await this._store.getSubscriptions(ct);
513
+ const connectT3 = Date.now();
499
514
  let helloMidpointMs = Date.now();
515
+ const known = this._lastAnswer;
516
+ const wireTables = (_a = this._wireTables) !== null && _a !== void 0 ? _a : (this._wireTables = this.model.tables.map((t) => t.toWire()));
500
517
  const hello = await this._withRetry(async (token) => {
501
518
  // The round trip of the attempt that answered: its midpoint is the local time of the server's clock reading (LES-0075).
502
519
  const sent = Date.now();
@@ -504,15 +521,34 @@ export class G9SyncEngine {
504
521
  clientId: this._clientId,
505
522
  appVersion: this._options.appVersion,
506
523
  protocolVersion: 1,
507
- tables: this.model.tables.map((t) => t.toWire()),
524
+ tables: wireTables,
508
525
  subscriptions: subscriptions.map(toSubscriptionRef),
526
+ knownTablesHash: known ? known.tablesHash : null,
527
+ knownDefinitionsHash: known ? known.definitionsHash : null,
509
528
  }, token);
510
529
  helloMidpointMs = sent + (Date.now() - sent) / 2;
511
530
  return answer;
512
531
  }, ct);
513
532
  if (hello.error)
514
533
  throw syncErrorToException(hello.error);
534
+ const unchanged = (hello.tablesUnchanged ? 'tables' : '') + (hello.definitionsUnchanged ? (hello.tablesUnchanged ? '+' : '') + 'definitions' : '');
535
+ restoreUnchanged(hello, known);
536
+ const connectT4 = Date.now();
515
537
  await this._store.saveHello(hello, ct, helloMidpointMs);
538
+ this._lastAnswer =
539
+ hello.tablesHash || hello.definitionsHash
540
+ ? {
541
+ tablesHash: (_b = hello.tablesHash) !== null && _b !== void 0 ? _b : null,
542
+ tables: hello.tables,
543
+ unknownTables: hello.unknownTables,
544
+ definitionsHash: (_c = hello.definitionsHash) !== null && _c !== void 0 ? _c : null,
545
+ definitions: hello.definitions,
546
+ }
547
+ : null;
548
+ const connectT5 = Date.now();
549
+ this._log('information', 20030, `Connecting ${connectT5 - connectT0} ms: transport ${connectT1 - connectT0}, ephemeral sweep ${connectT2 - connectT1}, ` +
550
+ `subscriptions ${connectT3 - connectT2} (${subscriptions.length}), hello ${connectT4 - connectT3} ` +
551
+ `(${this.model.tables.length} tables; unchanged: ${unchanged || 'none'}), save ${connectT5 - connectT4}`);
516
552
  if (hello.unknownTables.length > 0) {
517
553
  this._log('warning', 20016, 'The server does not know these client tables (they will not sync): ' + hello.unknownTables.join(', '));
518
554
  }
@@ -1258,4 +1294,21 @@ async function* toChunks(batch) {
1258
1294
  for (const frame of batch.frames)
1259
1295
  yield { sequence: sequence++, payload: frame, rowCount: 0, isFinal: false };
1260
1296
  }
1297
+ /**
1298
+ * Puts back what an "unchanged" Hello answer left out (1.5.21; twin of .NET `RestoreUnchanged`), so the rest of a run reads a complete
1299
+ * answer either way. An answer that says unchanged for something this client never held is a server fault.
1300
+ */
1301
+ function restoreUnchanged(hello, known) {
1302
+ if (hello.tablesUnchanged) {
1303
+ if (!known)
1304
+ throw new G9SyncException(G9SyncErrorCodes.ProtocolMismatch, "The server answered 'tables unchanged' to a client that holds none.");
1305
+ hello.tables = known.tables;
1306
+ hello.unknownTables = known.unknownTables;
1307
+ }
1308
+ if (hello.definitionsUnchanged) {
1309
+ if (!known)
1310
+ throw new G9SyncException(G9SyncErrorCodes.ProtocolMismatch, "The server answered 'definitions unchanged' to a client that holds none.");
1311
+ hello.definitions = known.definitions;
1312
+ }
1313
+ }
1261
1314
  //# sourceMappingURL=engine.js.map