@recordtimelabel/core 0.6.15 → 0.6.17

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/README.md CHANGED
@@ -17,10 +17,10 @@ During local development an app can consume a sibling checkout with:
17
17
  "@recordtimelabel/core": "file:../recordtimelabel-core"
18
18
  ```
19
19
 
20
- For release builds, consume a fixed npm package, git tag, or private registry version so builds do not depend on a sibling folder path. The release target is `0.6.15`; verify that its registry tarball and lockfile integrity are available before updating consumers:
20
+ For release builds, consume a fixed npm package, git tag, or private registry version so builds do not depend on a sibling folder path. The release target is `0.6.17`; verify that its registry tarball and lockfile integrity are available before updating consumers:
21
21
 
22
22
  ```json
23
- "@recordtimelabel/core": "0.6.15"
23
+ "@recordtimelabel/core": "0.6.17"
24
24
  ```
25
25
 
26
26
  If this checkout's `package.json` is ahead of the published version, publish the new package before updating consumers to that version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recordtimelabel/core",
3
- "version": "0.6.15",
3
+ "version": "0.6.17",
4
4
  "type": "module",
5
5
  "description": "Shared RecordTimeLabel data model, merge logic, operations, and sync engine.",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -147,7 +147,7 @@ export {
147
147
  selectBestMatchingTwitchVod
148
148
  };
149
149
 
150
- export const RECORD_TIMELABEL_CORE_VERSION = '0.6.15';
150
+ export const RECORD_TIMELABEL_CORE_VERSION = '0.6.17';
151
151
  export const RTL_SYNC_PROTOCOL_VERSION = 2;
152
152
  export const RECORD_TIMELABEL_DURABLE_ENGINE_CAPABILITIES = Object.freeze([
153
153
  'fifo-retry-fence',
@@ -5356,6 +5356,8 @@ export const createRecordTimeLabelSyncEngine = ({
5356
5356
  let firstRootObservation = null;
5357
5357
  let latestRootObservationRevision = null;
5358
5358
  let adapterCatchUpProof = null;
5359
+ let processedRootRevisionProof = null;
5360
+ let inFlightRemoteCallbacks = 0;
5359
5361
  const settleReadyIfProven = () => {
5360
5362
  if (!isCurrentRemoteReady(readyState) || readyState.settled ||
5361
5363
  !firstRootObservation || (adapterReadyExpected && !adapterCatchUpProof)) {
@@ -5365,12 +5367,32 @@ export const createRecordTimeLabelSyncEngine = ({
5365
5367
  ? workspace.remoteBaseline.revision
5366
5368
  : 0;
5367
5369
  const proof = adapterCatchUpProof || {};
5368
- const revision = Number.isSafeInteger(Number(proof.revision))
5370
+ const proofRev = proof.revision !== undefined ? Number(proof.revision) : null;
5371
+ const proofCaughtUp = proof.caughtUpToRevision !== undefined ? Number(proof.caughtUpToRevision) : null;
5372
+ if (
5373
+ (proofRev !== null && (!Number.isSafeInteger(proofRev) || proofRev < 0)) ||
5374
+ (proofCaughtUp !== null && (!Number.isSafeInteger(proofCaughtUp) || proofCaughtUp < 0))
5375
+ ) {
5376
+ readyState.reject(toRecordTimeLabelCloudFailureError({
5377
+ class: RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL,
5378
+ code: 'invalid_remote_ready_revision',
5379
+ reason: 'invalid_remote_ready_revision',
5380
+ message: 'invalid_remote_ready_revision'
5381
+ }));
5382
+ return;
5383
+ }
5384
+ const rawRevision = Number.isSafeInteger(Number(proof.revision))
5369
5385
  ? Number(proof.revision)
5370
5386
  : baselineRevision;
5371
- const caughtUpToRevision = Number.isSafeInteger(Number(proof.caughtUpToRevision))
5387
+ const rawCaughtUpToRevision = Number.isSafeInteger(Number(proof.caughtUpToRevision))
5372
5388
  ? Number(proof.caughtUpToRevision)
5373
- : revision;
5389
+ : rawRevision;
5390
+ const revision = Number.isSafeInteger(processedRootRevisionProof)
5391
+ ? Math.max(rawRevision, processedRootRevisionProof)
5392
+ : rawRevision;
5393
+ const caughtUpToRevision = Number.isSafeInteger(processedRootRevisionProof)
5394
+ ? Math.max(rawCaughtUpToRevision, processedRootRevisionProof)
5395
+ : rawCaughtUpToRevision;
5374
5396
  if (revision < 0 || caughtUpToRevision < 0) {
5375
5397
  readyState.reject(toRecordTimeLabelCloudFailureError({
5376
5398
  class: RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL,
@@ -5385,6 +5407,7 @@ export const createRecordTimeLabelSyncEngine = ({
5385
5407
  : baselineRevision;
5386
5408
  const requiredRevision = Math.max(baselineRevision, observedRevision);
5387
5409
  if (revision < requiredRevision || caughtUpToRevision < requiredRevision) {
5410
+ if (inFlightRemoteCallbacks > 0) return;
5388
5411
  readyState.reject(toRecordTimeLabelCloudFailureError({
5389
5412
  class: RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL,
5390
5413
  code: 'stale_remote_ready_revision',
@@ -5420,6 +5443,13 @@ export const createRecordTimeLabelSyncEngine = ({
5420
5443
  return;
5421
5444
  }
5422
5445
  firstRootObservation = result || {success: true};
5446
+ const rootPersistedRevision = Number(workspace?.remoteBaseline?.revision);
5447
+ if (Number.isSafeInteger(rootPersistedRevision)) {
5448
+ processedRootRevisionProof = Math.max(
5449
+ processedRootRevisionProof ?? 0,
5450
+ rootPersistedRevision
5451
+ );
5452
+ }
5423
5453
  if (Number.isSafeInteger(observedRevision) && observedRevision >= 0) {
5424
5454
  latestRootObservationRevision = Math.max(
5425
5455
  latestRootObservationRevision ?? 0,
@@ -5461,14 +5491,21 @@ export const createRecordTimeLabelSyncEngine = ({
5461
5491
  const observedRevision = Number.isSafeInteger(observedRevisionValue) && observedRevisionValue >= 0
5462
5492
  ? observedRevisionValue
5463
5493
  : null;
5494
+ inFlightRemoteCallbacks += 1;
5464
5495
  const callbackResult = enqueue(() => processRemote(remoteValue, captured));
5465
5496
  if (!firstCallbackResult) firstCallbackResult = callbackResult;
5466
- callbackResult.then((result) => handleRootObservation(result, observedRevision)).catch((error) => {
5467
- if (isCurrentRemoteReady(readyState) && !readyState.settled) {
5468
- readyState.reject(error);
5469
- }
5470
- logger?.error?.('[RecordTimeLabelCore] durable remote callback failed', error);
5471
- });
5497
+ callbackResult
5498
+ .then((result) => handleRootObservation(result, observedRevision))
5499
+ .catch((error) => {
5500
+ if (isCurrentRemoteReady(readyState) && !readyState.settled) {
5501
+ readyState.reject(error);
5502
+ }
5503
+ logger?.error?.('[RecordTimeLabelCore] durable remote callback failed', error);
5504
+ })
5505
+ .finally(() => {
5506
+ inFlightRemoteCallbacks -= 1;
5507
+ settleReadyIfProven();
5508
+ });
5472
5509
  return callbackResult;
5473
5510
  };
5474
5511
  let disposer;
@@ -5513,19 +5550,40 @@ export const createRecordTimeLabelSyncEngine = ({
5513
5550
  }
5514
5551
  ));
5515
5552
  } else {
5553
+ const rawRev = result?.revision;
5554
+ const rawCaughtUp = result?.caughtUpToRevision;
5555
+ if (
5556
+ (rawRev !== undefined && (!Number.isSafeInteger(Number(rawRev)) || Number(rawRev) < 0)) ||
5557
+ (rawCaughtUp !== undefined && (!Number.isSafeInteger(Number(rawCaughtUp)) || Number(rawCaughtUp) < 0))
5558
+ ) {
5559
+ readyState.reject(toRecordTimeLabelCloudFailureError({
5560
+ class: RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL,
5561
+ code: 'invalid_remote_ready_revision',
5562
+ reason: 'invalid_remote_ready_revision',
5563
+ message: 'invalid_remote_ready_revision'
5564
+ }));
5565
+ return;
5566
+ }
5516
5567
  const nextProof = result && typeof result === 'object'
5517
5568
  ? {...result, success: true}
5518
5569
  : {success: true};
5519
5570
  const currentRevision = Number(adapterCatchUpProof?.revision);
5520
5571
  const currentCaughtUp = Number(adapterCatchUpProof?.caughtUpToRevision);
5572
+ const provedRevision = Number.isSafeInteger(processedRootRevisionProof)
5573
+ ? processedRootRevisionProof
5574
+ : null;
5521
5575
  adapterCatchUpProof = {
5522
5576
  ...nextProof,
5523
- ...(Number.isSafeInteger(currentRevision) && Number.isSafeInteger(Number(nextProof.revision))
5524
- ? {revision: Math.max(currentRevision, Number(nextProof.revision))}
5525
- : {}),
5526
- ...(Number.isSafeInteger(currentCaughtUp) && Number.isSafeInteger(Number(nextProof.caughtUpToRevision))
5527
- ? {caughtUpToRevision: Math.max(currentCaughtUp, Number(nextProof.caughtUpToRevision))}
5528
- : {})
5577
+ revision: Math.max(
5578
+ Number.isSafeInteger(currentRevision) ? currentRevision : 0,
5579
+ Number.isSafeInteger(Number(nextProof.revision)) ? Number(nextProof.revision) : 0,
5580
+ provedRevision ?? 0
5581
+ ),
5582
+ caughtUpToRevision: Math.max(
5583
+ Number.isSafeInteger(currentCaughtUp) ? currentCaughtUp : 0,
5584
+ Number.isSafeInteger(Number(nextProof.caughtUpToRevision)) ? Number(nextProof.caughtUpToRevision) : 0,
5585
+ provedRevision ?? 0
5586
+ )
5529
5587
  };
5530
5588
  settleReadyIfProven();
5531
5589
  }
package/src/protocol.js CHANGED
@@ -177,6 +177,8 @@ const CLOUD_FAILURE_CODE_CLASSES = new Map([
177
177
  ['operation_rejected', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],
178
178
  ['protocol_error', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],
179
179
  ['operation_result_count_mismatch', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],
180
+ ['operation_ack_incomplete', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],
181
+ ['bulk_authoritative_ack_missing', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],
180
182
  ['lifecycle_conflict', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],
181
183
  ['lifecycle_generation_required', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],
182
184
  ['record_not_found', RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TERMINAL],