@recordtimelabel/core 0.3.2 → 0.4.0
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 +72 -2
- package/package.json +5 -2
- package/src/compat.js +8 -0
- package/src/firestore-v2.js +27 -0
- package/src/index.js +1345 -59
- package/src/protocol.js +128 -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
|
|
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 durable identity and explicit entrypoint contract is prepared in package version `0.4.0` (publish it before updating consumers):
|
|
21
21
|
|
|
22
22
|
```json
|
|
23
|
-
"@recordtimelabel/core": "0.
|
|
23
|
+
"@recordtimelabel/core": "0.4.0"
|
|
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.
|
|
@@ -32,6 +32,9 @@ If this checkout's `package.json` is ahead of the published version, publish the
|
|
|
32
32
|
- `mergeLocalRemote({ localState, remoteState, pendingOps, clientId, now })`
|
|
33
33
|
- `applyOperation(state, operation)`
|
|
34
34
|
- `applyRecordTimeLabelOperation(state, operation)`
|
|
35
|
+
- `createRecordTimeLabelSyncEngine({ storage, cloud, session, client, clock, logger })`
|
|
36
|
+
- `normalizeRecordTimeLabelOperationResults(operations, results)`
|
|
37
|
+
- `normalizeRecordTimeLabelEnvelopeResponse(operations, response)`
|
|
35
38
|
- `createSyncEngine({ storageAdapter, cloudAdapter, clientId, clock, logger })`
|
|
36
39
|
- `createRecordTimeLabelController({ storageAdapter, cloudAdapter, clientId, settingKeys, clock, logger })`
|
|
37
40
|
- `buildRecordTimeLabelSyncPayload({ data, previousCloudData, pendingOps, syncMode, clientId, now, groupOrderNormalizer })`
|
|
@@ -55,6 +58,8 @@ If this checkout's `package.json` is ahead of the published version, publish the
|
|
|
55
58
|
- `RTL_SYNC_PROTOCOL_VERSION`
|
|
56
59
|
- `hasMeaningfulRecordTimeLabelCloudState(data, options)`
|
|
57
60
|
- `buildRecordTimeLabelContentFingerprint(data)`
|
|
61
|
+
- `normalizeRecordTimeLabelDomainState(input)`
|
|
62
|
+
- `migrateRecordTimeLabelExpandedGroups({ pendingOperations, currentView })`
|
|
58
63
|
- `buildMigratedRecordTimeLabelV2State(legacyState, options)`
|
|
59
64
|
- `RECORD_TIMELABEL_CLOUD_SCHEMAS`
|
|
60
65
|
- `RECORD_TIMELABEL_SYNC_MODES`
|
|
@@ -63,6 +68,71 @@ If this checkout's `package.json` is ahead of the published version, publish the
|
|
|
63
68
|
- `getActiveTrashEntries(entries, now)`
|
|
64
69
|
- `RTL_TRASH_RETENTION_MS`
|
|
65
70
|
|
|
71
|
+
The package exports four intentional entrypoints. The root (`@recordtimelabel/core`)
|
|
72
|
+
keeps the complete backwards-compatible surface, `/protocol` contains only shared
|
|
73
|
+
acknowledgement protocol helpers, `/firestore-v2` contains platform-neutral document
|
|
74
|
+
and operation planners, and `/compat` contains the legacy `createSyncEngine` and
|
|
75
|
+
`createRecordTimeLabelController` APIs.
|
|
76
|
+
|
|
77
|
+
### Durable sync engine
|
|
78
|
+
|
|
79
|
+
`createRecordTimeLabelSyncEngine` is the platform-neutral durable workspace API. Its storage port
|
|
80
|
+
has `load()` and `save(workspace)` methods (adapters may consume the optional second
|
|
81
|
+
`save(workspace, fenceContext)` argument to enforce the session/epoch atomically), and its cloud port has `bootstrap(context)`,
|
|
82
|
+
`applyOperations(envelope, context)`, and `subscribe(listener, context)` methods. The session port
|
|
83
|
+
provides `current()`, `subscribe(listener)`, and `isCurrent(sessionToken, uid, workspaceEpoch)`.
|
|
84
|
+
|
|
85
|
+
The persisted workspace is versioned and contains only durable data:
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
{
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
ownerUid: 'user-id',
|
|
91
|
+
workspaceEpoch: 3,
|
|
92
|
+
remoteBaseline: { state, revision: 12, changeCursor: 'cursor-12' },
|
|
93
|
+
pendingOperations: [],
|
|
94
|
+
rejectedOperations: {},
|
|
95
|
+
syncMeta: {}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Every normalized pending operation also carries `ownerUid` and a non-negative
|
|
100
|
+
`workspaceEpoch`. Dispatch stamps these fields from the captured session; legacy
|
|
101
|
+
operations missing them inherit the migrated workspace identity. An operation whose
|
|
102
|
+
explicit identity does not match the current workspace is moved to
|
|
103
|
+
`rejectedOperations` with a stable identity-mismatch reason before any cloud apply.
|
|
104
|
+
|
|
105
|
+
The engine exposes `init()`, `dispatch(operations)`, `sync(reason)`, `getSnapshot()`,
|
|
106
|
+
`subscribe(listener)`, and `destroy()`. `getSnapshot().state` is derived by replaying pending
|
|
107
|
+
operations over `remoteBaseline.state`; rejected operations are kept in diagnostics and are not
|
|
108
|
+
replayed. Session tokens are used for fencing but are never persisted. The legacy
|
|
109
|
+
`createSyncEngine` and app adapters remain available and are not implicitly migrated by this API.
|
|
110
|
+
|
|
111
|
+
`expandedGroups` is a local view projection, not durable cloud domain state. Durable workspaces,
|
|
112
|
+
content fingerprints, and Firestore v2 root documents exclude it. The migration helper consumes
|
|
113
|
+
legacy `expandedGroups.update` operations in order, returns the resulting view projection, and is
|
|
114
|
+
idempotent; legacy root fields are preserved verbatim when a compatibility document is rewritten
|
|
115
|
+
so older clients can continue reading the view until they migrate.
|
|
116
|
+
|
|
117
|
+
### Operation-result protocol
|
|
118
|
+
|
|
119
|
+
The root package and `@recordtimelabel/core/protocol` export the shared acknowledgement
|
|
120
|
+
normalizers. `normalizeRecordTimeLabelOperationResults(operations, results)` always returns one
|
|
121
|
+
positional result per submitted operation. The only normalized statuses are `applied`, `noop`,
|
|
122
|
+
`rejected`, and `retryable`; `retryable` is derived from that status, while the legacy `id` and
|
|
123
|
+
`applied` fields remain available alongside `operationId`, `reason`, and `retryAfterMs`.
|
|
124
|
+
|
|
125
|
+
`normalizeRecordTimeLabelEnvelopeResponse(operations, response)` prefers an explicit
|
|
126
|
+
`operationResults` array. An explicitly provided non-array or mismatched array, and a failed
|
|
127
|
+
envelope without results, throw an error whose `code` is `operation_result_count_mismatch`, so
|
|
128
|
+
callers can reject the entire acknowledgement before changing durable state.
|
|
129
|
+
|
|
130
|
+
During the compatibility window, a successful legacy envelope that omits the
|
|
131
|
+
`operationResults` property acknowledges every submitted operation as `applied`. Failed envelopes
|
|
132
|
+
and envelopes that explicitly provide a malformed result value never receive that fallback. Keep
|
|
133
|
+
the fallback until all deployed gateways and clients send and consume the explicit array; removing
|
|
134
|
+
it requires a coordinated breaking release.
|
|
135
|
+
|
|
66
136
|
## Firestore v1 Compatibility
|
|
67
137
|
|
|
68
138
|
Phase 1 keeps the existing `users/{uid}` document shape:
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recordtimelabel/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared RecordTimeLabel data model, merge logic, operations, and sync engine.",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./src/index.js"
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./protocol": "./src/protocol.js",
|
|
10
|
+
"./firestore-v2": "./src/firestore-v2.js",
|
|
11
|
+
"./compat": "./src/compat.js"
|
|
9
12
|
},
|
|
10
13
|
"scripts": {
|
|
11
14
|
"test": "node --test tests/*.test.mjs"
|
package/src/compat.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Compatibility surface for the pre-durable sync/controller APIs. Keeping
|
|
2
|
+
// these exports explicit lets adapters migrate without pulling in unrelated
|
|
3
|
+
// Firestore-v2 planners.
|
|
4
|
+
export {createRecordTimeLabelController, createSyncEngine} from './index.js';
|
|
5
|
+
|
|
6
|
+
import {createRecordTimeLabelController, createSyncEngine} from './index.js';
|
|
7
|
+
|
|
8
|
+
export default {createSyncEngine, createRecordTimeLabelController};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Platform-neutral Firestore v2 planning and document helpers. Runtime
|
|
2
|
+
// adapters (Firebase SDKs, browser storage, and UI bindings) belong outside
|
|
3
|
+
// the core package and should import this explicit surface.
|
|
4
|
+
export {
|
|
5
|
+
FIRESTORE_V2_SETTINGS_DOC_ID,
|
|
6
|
+
OPERATION_TYPES,
|
|
7
|
+
RECORD_TIMELABEL_CLOUD_SCHEMAS,
|
|
8
|
+
RECORD_TIMELABEL_OPERATION_RESULT_STATUSES,
|
|
9
|
+
RTL_MAX_OPERATIONS_PER_REQUEST,
|
|
10
|
+
RTL_MAX_REQUEST_BYTES,
|
|
11
|
+
RTL_MAX_TARGET_WRITES,
|
|
12
|
+
RTL_SYNC_PROTOCOL_VERSION,
|
|
13
|
+
buildFirestoreV2DocumentChangeSet,
|
|
14
|
+
buildFirestoreV2DocumentsFromState,
|
|
15
|
+
buildFirestoreV2LogicalPaths,
|
|
16
|
+
buildFirestoreV2OperationReadPlan,
|
|
17
|
+
buildOperationsFromSnapshotDiff,
|
|
18
|
+
buildStateFromFirestoreV2Documents,
|
|
19
|
+
estimateFirestoreV2WriteUnits,
|
|
20
|
+
extendFirestoreV2OperationReadPlanWithRecords,
|
|
21
|
+
extendFirestoreV2OperationReadPlanWithTrash,
|
|
22
|
+
normalizeRecordTimeLabelEnvelopeResponse,
|
|
23
|
+
normalizeRecordTimeLabelOperationResults,
|
|
24
|
+
planFirestoreV2OperationChanges,
|
|
25
|
+
normalizeRecordTimeLabelDomainState,
|
|
26
|
+
validateRecordTimeLabelOperationBatch
|
|
27
|
+
} from './index.js';
|