@recordtimelabel/core 0.6.5 → 0.6.7
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 +4 -2
- package/package.json +1 -1
- package/src/index.js +49 -2
- package/src/protocol.js +34 -0
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.
|
|
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.7`; verify that its registry tarball and lockfile integrity are available before updating consumers:
|
|
21
21
|
|
|
22
22
|
```json
|
|
23
|
-
"@recordtimelabel/core": "0.6.
|
|
23
|
+
"@recordtimelabel/core": "0.6.7"
|
|
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.
|
|
@@ -38,7 +38,9 @@ If this checkout's `package.json` is ahead of the published version, publish the
|
|
|
38
38
|
- `RECORD_TIMELABEL_DURABLE_ENGINE_CAPABILITIES`
|
|
39
39
|
- `RECORD_TIMELABEL_CAPABILITY_CLOUD_FAILURE_STATE`
|
|
40
40
|
- `RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES`
|
|
41
|
+
- `RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS`
|
|
41
42
|
- `normalizeRecordTimeLabelCloudFailure(input)`
|
|
43
|
+
- `classifyRecordTimeLabelCloudRecovery(input)`
|
|
42
44
|
- `toRecordTimeLabelCloudFailureError(input)`
|
|
43
45
|
- `RECORD_TIMELABEL_PROTOCOL_CAPABILITIES`
|
|
44
46
|
- `toRecordTimeLabelWireOperation(operation)`
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
RECORD_TIMELABEL_CAPABILITY_STRICT_READINESS,
|
|
9
9
|
RECORD_TIMELABEL_CLOUD_FAILURE_SCHEMA_VERSION,
|
|
10
10
|
RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES,
|
|
11
|
+
RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS,
|
|
11
12
|
RECORD_TIMELABEL_OPERATION_RESULT_STATUSES,
|
|
12
13
|
RECORD_TIMELABEL_PROTOCOL_CAPABILITIES,
|
|
13
14
|
buildRecordTimeLabelRequestId,
|
|
14
15
|
createRecordTimeLabelTransportFailureResults,
|
|
15
16
|
isRecordTimeLabelOperationConflictFailure,
|
|
16
17
|
normalizeRecordTimeLabelCloudFailure,
|
|
18
|
+
classifyRecordTimeLabelCloudRecovery,
|
|
17
19
|
normalizeRecordTimeLabelEnvelopeResponse,
|
|
18
20
|
normalizeRecordTimeLabelOperationResults,
|
|
19
21
|
normalizeRecordTimeLabelImmutableId,
|
|
@@ -32,12 +34,14 @@ export {
|
|
|
32
34
|
RECORD_TIMELABEL_CAPABILITY_STRICT_READINESS,
|
|
33
35
|
RECORD_TIMELABEL_CLOUD_FAILURE_SCHEMA_VERSION,
|
|
34
36
|
RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES,
|
|
37
|
+
RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS,
|
|
35
38
|
RECORD_TIMELABEL_OPERATION_RESULT_STATUSES,
|
|
36
39
|
RECORD_TIMELABEL_PROTOCOL_CAPABILITIES,
|
|
37
40
|
buildRecordTimeLabelRequestId,
|
|
38
41
|
createRecordTimeLabelTransportFailureResults,
|
|
39
42
|
isRecordTimeLabelOperationConflictFailure,
|
|
40
43
|
normalizeRecordTimeLabelCloudFailure,
|
|
44
|
+
classifyRecordTimeLabelCloudRecovery,
|
|
41
45
|
normalizeRecordTimeLabelEnvelopeResponse,
|
|
42
46
|
normalizeRecordTimeLabelOperationResults,
|
|
43
47
|
normalizeRecordTimeLabelImmutableId,
|
|
@@ -141,7 +145,7 @@ export {
|
|
|
141
145
|
selectBestMatchingTwitchVod
|
|
142
146
|
};
|
|
143
147
|
|
|
144
|
-
export const RECORD_TIMELABEL_CORE_VERSION = '0.6.
|
|
148
|
+
export const RECORD_TIMELABEL_CORE_VERSION = '0.6.7';
|
|
145
149
|
export const RTL_SYNC_PROTOCOL_VERSION = 2;
|
|
146
150
|
export const RECORD_TIMELABEL_DURABLE_ENGINE_CAPABILITIES = Object.freeze([
|
|
147
151
|
'fifo-retry-fence',
|
|
@@ -1145,6 +1149,10 @@ export const applyOperation = (state = {}, operation = {}) => {
|
|
|
1145
1149
|
originalRecordIndex: Number.isInteger(payload.originalRecordIndex)
|
|
1146
1150
|
? payload.originalRecordIndex
|
|
1147
1151
|
: Math.max(0, Number(existingEntry?.index || 0)),
|
|
1152
|
+
originalGroupId: String(payload.originalGroupId || '').trim(),
|
|
1153
|
+
originalGroupOrderIndex: Number.isInteger(payload.originalGroupOrderIndex)
|
|
1154
|
+
? payload.originalGroupOrderIndex
|
|
1155
|
+
: -1,
|
|
1148
1156
|
lifecycleGeneration,
|
|
1149
1157
|
deletedAt,
|
|
1150
1158
|
purgeAt: toFiniteTimestamp(payload.purgeAt) || deletedAt + RTL_TRASH_RETENTION_MS,
|
|
@@ -1206,6 +1214,16 @@ export const applyOperation = (state = {}, operation = {}) => {
|
|
|
1206
1214
|
);
|
|
1207
1215
|
targetRecords.splice(restoreIndex, 0, restoredRecord);
|
|
1208
1216
|
nextState.records[targetFolderId] = targetRecords;
|
|
1217
|
+
const originalGroupId = String(trashEntry.originalGroupId || '').trim();
|
|
1218
|
+
const originalGroupOrderIndex = Number.isInteger(trashEntry.originalGroupOrderIndex)
|
|
1219
|
+
? trashEntry.originalGroupOrderIndex
|
|
1220
|
+
: -1;
|
|
1221
|
+
if (originalGroupId && originalGroupOrderIndex >= 0 &&
|
|
1222
|
+
!nextState.groupOrder.includes(originalGroupId)) {
|
|
1223
|
+
const groupOrder = [...nextState.groupOrder];
|
|
1224
|
+
groupOrder.splice(Math.min(originalGroupOrderIndex, groupOrder.length), 0, originalGroupId);
|
|
1225
|
+
nextState.groupOrder = groupOrder;
|
|
1226
|
+
}
|
|
1209
1227
|
delete nextState.trashEntries[trashEntryId];
|
|
1210
1228
|
delete nextState.deletedRecordTombstones[recordId];
|
|
1211
1229
|
break;
|
|
@@ -6232,6 +6250,19 @@ const rtlPayloadOrder = (operation, keys) => {
|
|
|
6232
6250
|
}
|
|
6233
6251
|
return [];
|
|
6234
6252
|
};
|
|
6253
|
+
// Partial delete reads omit sibling records, so folder.recordOrder is the live slot.
|
|
6254
|
+
const rtlResolveOriginalRecordIndex = (payload, existingRecord, folders) => {
|
|
6255
|
+
if (Number.isInteger(payload?.originalRecordIndex) && payload.originalRecordIndex >= 0) {
|
|
6256
|
+
return payload.originalRecordIndex;
|
|
6257
|
+
}
|
|
6258
|
+
const recordId = normalizeId(existingRecord?.id);
|
|
6259
|
+
const folderId = safeFolderId(existingRecord?.folderId || payload?.folderId);
|
|
6260
|
+
const recordOrder = rtlMergeOrder(
|
|
6261
|
+
rtlOwn(folders, folderId) ? folders[folderId]?.recordOrder : []
|
|
6262
|
+
);
|
|
6263
|
+
const orderIndex = recordId ? recordOrder.indexOf(recordId) : -1;
|
|
6264
|
+
return orderIndex >= 0 ? orderIndex : 0;
|
|
6265
|
+
};
|
|
6235
6266
|
const rtlHasInvalidPayloadValue = (value, depth = 0) => {
|
|
6236
6267
|
if (depth > 20) return true;
|
|
6237
6268
|
if (typeof value === 'number') return !Number.isFinite(value);
|
|
@@ -6863,7 +6894,21 @@ export const planFirestoreV2OperationChanges = ({
|
|
|
6863
6894
|
break;
|
|
6864
6895
|
}
|
|
6865
6896
|
const sourceFolderId = safeFolderId(existingRecord.folderId);
|
|
6866
|
-
applied = rtlApplyOperationToPartialDocuments({
|
|
6897
|
+
applied = rtlApplyOperationToPartialDocuments({
|
|
6898
|
+
...nextDocuments,
|
|
6899
|
+
operation: {
|
|
6900
|
+
...operation,
|
|
6901
|
+
payload: {
|
|
6902
|
+
...payload,
|
|
6903
|
+
originalRecordIndex: rtlResolveOriginalRecordIndex(
|
|
6904
|
+
payload,
|
|
6905
|
+
existingRecord,
|
|
6906
|
+
nextDocuments.folders
|
|
6907
|
+
)
|
|
6908
|
+
}
|
|
6909
|
+
},
|
|
6910
|
+
now: operationNow
|
|
6911
|
+
});
|
|
6867
6912
|
rtlSetRoot(nextDocuments.root, applied.documents.root);
|
|
6868
6913
|
const trashDocument = rtlOwn(applied.documents.trash, operationTrashEntryId)
|
|
6869
6914
|
? applied.documents.trash[operationTrashEntryId]
|
|
@@ -7286,8 +7331,10 @@ export default {
|
|
|
7286
7331
|
RECORD_TIMELABEL_CAPABILITY_STRICT_READINESS,
|
|
7287
7332
|
RECORD_TIMELABEL_CLOUD_FAILURE_SCHEMA_VERSION,
|
|
7288
7333
|
RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES,
|
|
7334
|
+
RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS,
|
|
7289
7335
|
isRecordTimeLabelOperationConflictFailure,
|
|
7290
7336
|
normalizeRecordTimeLabelCloudFailure,
|
|
7337
|
+
classifyRecordTimeLabelCloudRecovery,
|
|
7291
7338
|
toRecordTimeLabelCloudFailureError,
|
|
7292
7339
|
RECORD_TIMELABEL_OPERATION_RESULT_STATUSES,
|
|
7293
7340
|
RECORD_TIMELABEL_PROTOCOL_CAPABILITIES,
|
package/src/protocol.js
CHANGED
|
@@ -73,6 +73,16 @@ export const RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES = Object.freeze({
|
|
|
73
73
|
STALE_SESSION: 'stale-session'
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
// Hosts must map class to these actions. They may choose the platform I/O
|
|
77
|
+
// (alarm, timer, Web Lock, Main process), but they must not invent a fourth
|
|
78
|
+
// strategy such as "rebuild the owner on 429".
|
|
79
|
+
export const RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS = Object.freeze({
|
|
80
|
+
IGNORE: 'ignore',
|
|
81
|
+
WAIT: 'wait',
|
|
82
|
+
BOOTSTRAP: 'bootstrap',
|
|
83
|
+
LOCK: 'lock'
|
|
84
|
+
});
|
|
85
|
+
|
|
76
86
|
const CLOUD_FAILURE_CLASS_VALUES = new Set(Object.values(RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES));
|
|
77
87
|
|
|
78
88
|
// Keep operation-conflict aliases in one wire-compatibility table. The
|
|
@@ -580,6 +590,28 @@ export const toRecordTimeLabelCloudFailureError = (input = {}) => {
|
|
|
580
590
|
return error;
|
|
581
591
|
};
|
|
582
592
|
|
|
593
|
+
/**
|
|
594
|
+
* Shared recovery mapping for cloud-failure-state-v1. Core owns the class →
|
|
595
|
+
* action table so Extension and Flowmoor cannot fork "retry" into bootstrap.
|
|
596
|
+
*
|
|
597
|
+
* `wait` keeps the workspace, outbox, and cursor/attempt and honors
|
|
598
|
+
* `retryAfterMs`. It is not permission to rebuild an owner or start a page-1
|
|
599
|
+
* walk. `bootstrap` is the only action that may start a fresh hydration.
|
|
600
|
+
*/
|
|
601
|
+
export const classifyRecordTimeLabelCloudRecovery = (input = {}) => {
|
|
602
|
+
const failure = normalizeRecordTimeLabelCloudFailure(input);
|
|
603
|
+
if (failure.class === RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.STALE_SESSION) {
|
|
604
|
+
return {action: RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS.IGNORE, failure};
|
|
605
|
+
}
|
|
606
|
+
if (failure.class === RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.TRANSIENT) {
|
|
607
|
+
return {action: RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS.WAIT, failure};
|
|
608
|
+
}
|
|
609
|
+
if (failure.class === RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES.BOOTSTRAP_REQUIRED) {
|
|
610
|
+
return {action: RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS.BOOTSTRAP, failure};
|
|
611
|
+
}
|
|
612
|
+
return {action: RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS.LOCK, failure};
|
|
613
|
+
};
|
|
614
|
+
|
|
583
615
|
const ALLOWED_OPERATION_RESULT_STATUSES = new Set(
|
|
584
616
|
Object.values(RECORD_TIMELABEL_OPERATION_RESULT_STATUSES)
|
|
585
617
|
);
|
|
@@ -833,6 +865,7 @@ export default {
|
|
|
833
865
|
RECORD_TIMELABEL_CAPABILITY_LIFECYCLE_GENERATION_FENCE_V1,
|
|
834
866
|
RECORD_TIMELABEL_CLOUD_FAILURE_SCHEMA_VERSION,
|
|
835
867
|
RECORD_TIMELABEL_CLOUD_FAILURE_CLASSES,
|
|
868
|
+
RECORD_TIMELABEL_CLOUD_RECOVERY_ACTIONS,
|
|
836
869
|
RECORD_TIMELABEL_PROTOCOL_CAPABILITIES,
|
|
837
870
|
toRecordTimeLabelWireOperation,
|
|
838
871
|
buildRecordTimeLabelRequestId,
|
|
@@ -840,6 +873,7 @@ export default {
|
|
|
840
873
|
normalizeRecordTimeLabelOperationResults,
|
|
841
874
|
normalizeRecordTimeLabelEnvelopeResponse,
|
|
842
875
|
normalizeRecordTimeLabelCloudFailure,
|
|
876
|
+
classifyRecordTimeLabelCloudRecovery,
|
|
843
877
|
toRecordTimeLabelCloudFailureError,
|
|
844
878
|
isRecordTimeLabelOperationConflictFailure,
|
|
845
879
|
normalizeRecordTimeLabelImmutableId,
|