@opendatalabs/personal-server-ts-core 1.32.0 → 1.33.1
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/dist/additions/first-added.d.ts +220 -0
- package/dist/additions/first-added.d.ts.map +1 -0
- package/dist/additions/first-added.js +653 -0
- package/dist/additions/first-added.js.map +1 -0
- package/dist/additions/index.d.ts +6 -0
- package/dist/additions/index.d.ts.map +1 -0
- package/dist/additions/index.js +6 -0
- package/dist/additions/index.js.map +1 -0
- package/dist/additions/ledger-store.d.ts +69 -0
- package/dist/additions/ledger-store.d.ts.map +1 -0
- package/dist/additions/ledger-store.js +407 -0
- package/dist/additions/ledger-store.js.map +1 -0
- package/dist/additions/record-rules.d.ts +54 -0
- package/dist/additions/record-rules.d.ts.map +1 -0
- package/dist/additions/record-rules.js +191 -0
- package/dist/additions/record-rules.js.map +1 -0
- package/dist/additions/scope-lock.d.ts +14 -0
- package/dist/additions/scope-lock.d.ts.map +1 -0
- package/dist/additions/scope-lock.js +32 -0
- package/dist/additions/scope-lock.js.map +1 -0
- package/dist/additions/summary.d.ts +56 -0
- package/dist/additions/summary.d.ts.map +1 -0
- package/dist/additions/summary.js +114 -0
- package/dist/additions/summary.js.map +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +26 -0
- package/dist/api/index.js.map +1 -1
- package/dist/contracts/data.d.ts +19 -1
- package/dist/contracts/data.d.ts.map +1 -1
- package/dist/contracts/data.js +95 -23
- package/dist/contracts/data.js.map +1 -1
- package/dist/ports/index.d.ts +11 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/sync/workers/download.d.ts.map +1 -1
- package/dist/sync/workers/download.js +19 -0
- package/dist/sync/workers/download.js.map +1 -1
- package/dist/test-utils/memory-storage.d.ts.map +1 -1
- package/dist/test-utils/memory-storage.js +47 -9
- package/dist/test-utils/memory-storage.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-seen ledger: when each record of a scope was first seen.
|
|
3
|
+
*
|
|
4
|
+
* The Personal Server stores each data import as one envelope per scope,
|
|
5
|
+
* `{ scope, collectedAt, data }`, and a re-import replaces the whole scope
|
|
6
|
+
* with a fresh full snapshot. Nothing in that shape records when an
|
|
7
|
+
* individual record first appeared, so this module derives a per-record
|
|
8
|
+
* first-seen ledger by folding the scope's stored versions.
|
|
9
|
+
*
|
|
10
|
+
* The ledger is a SIDECAR kept beside the data (see `ledger-store.ts`). It
|
|
11
|
+
* is never stamped into a stored envelope, never synced as data and never
|
|
12
|
+
* served to grantees: it is a cache that can always be rebuilt from the
|
|
13
|
+
* retained versions.
|
|
14
|
+
*
|
|
15
|
+
* Everything here is pure: it never reads or writes a store and never
|
|
16
|
+
* mutates the objects it is handed. Folding is order independent and
|
|
17
|
+
* idempotent, so two devices that hold the same versions compute the same
|
|
18
|
+
* ledger, and a version folded twice changes nothing.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* At most this many tracked keys per scope snapshot AND in the ledger (the
|
|
22
|
+
* union over time). A snapshot over it is untrackable; when the union passes
|
|
23
|
+
* it, keys absent from the newest tracked version are dropped, oldest first.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MAX_TRACKED_KEYS = 200000;
|
|
26
|
+
/** Ids longer than this are replaced by a hash so keys stay bounded. */
|
|
27
|
+
export declare const MAX_ID_LENGTH = 64;
|
|
28
|
+
/** Collection names longer than this are replaced by a hash (they can be caller-controlled). */
|
|
29
|
+
export declare const MAX_COLLECTION_LENGTH = 64;
|
|
30
|
+
/** A record absent from the newest tracked version is dropped after this long. */
|
|
31
|
+
export declare const PRUNE_AFTER_MS: number;
|
|
32
|
+
/** An unreadable version is read again at most this often. */
|
|
33
|
+
export declare const RETRY_UNREADABLE_MS: number;
|
|
34
|
+
/** At most this many unreadable versions are remembered for retry. */
|
|
35
|
+
export declare const MAX_RETRY_VERSIONS = 20;
|
|
36
|
+
/** Items processed between two yields to the event loop. */
|
|
37
|
+
export declare const YIELD_EVERY = 2000;
|
|
38
|
+
/** Run the rest of an async loop in a later macrotask so other requests are served. */
|
|
39
|
+
export declare function yieldToEventLoop(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Why a ledger holds no per-record keys: a rebuild could not read any version
|
|
42
|
+
* within its byte budget (`too_large`), or none could be read (`unreadable`).
|
|
43
|
+
* A negative result persisted so it is not retried on every request; a newer
|
|
44
|
+
* retained version retries it. (A snapshot over the key cap is not a state of
|
|
45
|
+
* the ledger: that one version is simply untrackable, see `isTrackable`.)
|
|
46
|
+
*/
|
|
47
|
+
export type LedgerSkipReason = "too_large" | "unreadable";
|
|
48
|
+
/** One document per scope, stored as a sidecar beside the data. */
|
|
49
|
+
export interface ScopeFirstSeenLedger {
|
|
50
|
+
version: 4;
|
|
51
|
+
scope: string;
|
|
52
|
+
/**
|
|
53
|
+
* collectedAt of the EARLIEST TRACKABLE version folded in (one with records
|
|
54
|
+
* that have ids, or an empty one). Its records count as added on its date
|
|
55
|
+
* only when the baseline is the scope's oldest retained version AND that
|
|
56
|
+
* version is the scope's first (see `partial`); otherwise their real date is
|
|
57
|
+
* unknown. Null until a trackable version exists, so a binary, over-cap or
|
|
58
|
+
* id-less first snapshot never becomes the baseline.
|
|
59
|
+
*/
|
|
60
|
+
baseline: string | null;
|
|
61
|
+
/** collectedAt of the NEWEST trackable version: a record is present iff last seen then. */
|
|
62
|
+
current: string | null;
|
|
63
|
+
/** collectedAt of the NEWEST version of any kind, and its record total (a binary file counts 1). */
|
|
64
|
+
latest: {
|
|
65
|
+
collectedAt: string;
|
|
66
|
+
total: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The ledger is known complete up to this collectedAt: every retained
|
|
70
|
+
* version at or before it has been folded. A write that folds only itself
|
|
71
|
+
* advances it only when no other version is newer than it, so the owner's
|
|
72
|
+
* `/additions` read knows what to catch up on.
|
|
73
|
+
*/
|
|
74
|
+
through: string;
|
|
75
|
+
/**
|
|
76
|
+
* Versions that could not be read when they were folded, and when the last
|
|
77
|
+
* attempt was made. They are retried at most once per `RETRY_UNREADABLE_MS`
|
|
78
|
+
* and meanwhile the ledger is served as it is (the numbers of the newest
|
|
79
|
+
* readable version) without reading anything. `through` has already moved
|
|
80
|
+
* past them, so a poll with nothing new reads nothing.
|
|
81
|
+
*/
|
|
82
|
+
retry?: {
|
|
83
|
+
versions: string[];
|
|
84
|
+
attemptedAt: string;
|
|
85
|
+
};
|
|
86
|
+
/** Set when no per-record keys are kept (see `LedgerSkipReason`); `records` is then empty. */
|
|
87
|
+
skipped?: LedgerSkipReason;
|
|
88
|
+
/**
|
|
89
|
+
* The rebuild or catch-up folded only the newest retained versions (the
|
|
90
|
+
* count window or the byte budget cut off older ones). A version older than
|
|
91
|
+
* `baseline` then never moves the baseline back over versions that were not
|
|
92
|
+
* folded. Persisted.
|
|
93
|
+
*/
|
|
94
|
+
truncated?: true;
|
|
95
|
+
/**
|
|
96
|
+
* DERIVED, never persisted: set by `ensureScopeLedger` on the ledger it
|
|
97
|
+
* returns when the baseline is not known to be the scope's first version.
|
|
98
|
+
* The owner's read decides it from the index (the oldest retained version,
|
|
99
|
+
* its number, whether it follows a deletion) plus `truncated`, so it follows
|
|
100
|
+
* later changes of version numbers and deletions. Records first seen at the
|
|
101
|
+
* baseline then keep an unknown date and are not counted as added.
|
|
102
|
+
*/
|
|
103
|
+
partial?: true;
|
|
104
|
+
/** record key -> [firstSeen collectedAt, lastSeen collectedAt] */
|
|
105
|
+
records: Record<string, [string, string]>;
|
|
106
|
+
}
|
|
107
|
+
/** A stored version to fold: its collectedAt and the envelope `data` as stored. */
|
|
108
|
+
export interface VersionToFold {
|
|
109
|
+
scope: string;
|
|
110
|
+
collectedAt: string;
|
|
111
|
+
data: Record<string, unknown>;
|
|
112
|
+
}
|
|
113
|
+
export interface RecordKeyExtraction {
|
|
114
|
+
/** De-duplicated keys of the records that have a usable id, in first-seen order. Empty when `tooMany`. */
|
|
115
|
+
keys: string[];
|
|
116
|
+
/** Records in the counted collections, tracked or not. */
|
|
117
|
+
total: number;
|
|
118
|
+
/** More than `MAX_TRACKED_KEYS` tracked keys: extraction stopped collecting them. */
|
|
119
|
+
tooMany?: true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Stable keys for the records in an envelope `data` object, plus the number of
|
|
123
|
+
* records counted. Returns null when the data is not trackable (binary).
|
|
124
|
+
*
|
|
125
|
+
* Both stored forms of a scope give the same keys: the PDPP rows form
|
|
126
|
+
* `{ records: [row, ...] }` and the legacy keyed form `{ <collection>: [...] }`
|
|
127
|
+
* (a ruled scope names the id field per form, see `record-rules.ts`). A record
|
|
128
|
+
* is tracked only when it has a usable id; records without one count toward
|
|
129
|
+
* `total` but produce no key. Ids and collection names over 64 characters are
|
|
130
|
+
* hashed, so every key is at most about 200 characters.
|
|
131
|
+
*/
|
|
132
|
+
export declare function extractRecordKeys(scope: string, data: Record<string, unknown>): Promise<RecordKeyExtraction | null>;
|
|
133
|
+
/**
|
|
134
|
+
* Folds stored versions into one ledger, one at a time, so a caller can read a
|
|
135
|
+
* version, `add` it and drop it before reading the next: only one parsed
|
|
136
|
+
* envelope is ever alive. Order independent and idempotent for versions within
|
|
137
|
+
* the 90-day pruning window of each other (pruning is relative to the newest
|
|
138
|
+
* tracked version, so folding versions further apart in a different order can
|
|
139
|
+
* re-date a record that was already pruned).
|
|
140
|
+
*/
|
|
141
|
+
export declare class LedgerFold {
|
|
142
|
+
private scope;
|
|
143
|
+
private baseline;
|
|
144
|
+
private current;
|
|
145
|
+
private latest;
|
|
146
|
+
private through;
|
|
147
|
+
private retry;
|
|
148
|
+
private skipped;
|
|
149
|
+
private truncated;
|
|
150
|
+
private records;
|
|
151
|
+
constructor(ledger: ScopeFirstSeenLedger | null, scope?: string);
|
|
152
|
+
add(version: VersionToFold): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* Insert the records this version brings that the ledger does not know.
|
|
155
|
+
* Capacity is decided ONCE: when they do not fit, absent records are evicted
|
|
156
|
+
* in a single pass (see `evict`), and whatever still does not fit is left
|
|
157
|
+
* out, smallest keys first, so the result never depends on the order of the
|
|
158
|
+
* ids in the version.
|
|
159
|
+
*/
|
|
160
|
+
private insertFresh;
|
|
161
|
+
/**
|
|
162
|
+
* Make room for `need` records by dropping some that are absent: not in the
|
|
163
|
+
* newest tracked version, not in the version being folded, and not
|
|
164
|
+
* baseline records (first seen at or before the baseline). Baseline records
|
|
165
|
+
* are never dropped. Other absent records CAN be dropped (and are
|
|
166
|
+
* dated as new if they return): that needs a scope near the cap, and
|
|
167
|
+
* whoever can write the scope can provoke it. Among the evictable ones the
|
|
168
|
+
* most recently first seen go first. Returns how many were dropped.
|
|
169
|
+
*/
|
|
170
|
+
private evict;
|
|
171
|
+
/** Drop records that are not present and were last seen over 90 days before `current`. */
|
|
172
|
+
private prune;
|
|
173
|
+
/**
|
|
174
|
+
* Prune and return the ledger, or null when nothing was ever folded.
|
|
175
|
+
* `truncated` marks a ledger rebuilt from only the newest retained versions;
|
|
176
|
+
* `through` overrides the completeness marker (see `ScopeFirstSeenLedger`).
|
|
177
|
+
*/
|
|
178
|
+
finish(options?: {
|
|
179
|
+
truncated?: boolean;
|
|
180
|
+
through?: string;
|
|
181
|
+
/** `null` clears the retry list; undefined keeps it. */
|
|
182
|
+
retry?: ScopeFirstSeenLedger["retry"] | null;
|
|
183
|
+
}): Promise<ScopeFirstSeenLedger | null>;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Fold several stored versions into a ledger in one pass. Equivalent to
|
|
187
|
+
* calling `foldVersion` for each, but copies the ledger and prunes once.
|
|
188
|
+
* Returns null only when no version had a parseable `collectedAt` and there
|
|
189
|
+
* was no starting ledger.
|
|
190
|
+
*/
|
|
191
|
+
export declare function foldVersions(ledger: ScopeFirstSeenLedger | null, versions: readonly VersionToFold[]): Promise<ScopeFirstSeenLedger | null>;
|
|
192
|
+
/**
|
|
193
|
+
* Fold one stored version into a ledger (or start one). A version whose
|
|
194
|
+
* `collectedAt` does not parse is ignored.
|
|
195
|
+
*/
|
|
196
|
+
export declare function foldVersion(ledger: ScopeFirstSeenLedger | null, version: VersionToFold): Promise<ScopeFirstSeenLedger | null>;
|
|
197
|
+
/** True when the record was in the newest tracked version folded in. */
|
|
198
|
+
export declare function isPresent(ledger: ScopeFirstSeenLedger, key: string): boolean;
|
|
199
|
+
/**
|
|
200
|
+
* True when the record's first-seen date is unknown: there is no baseline, or
|
|
201
|
+
* the ledger is partial and the record was first seen at or before the
|
|
202
|
+
* baseline. In a complete ledger the baseline's records are dated by it.
|
|
203
|
+
*/
|
|
204
|
+
export declare function isPreTracking(ledger: ScopeFirstSeenLedger, key: string): boolean;
|
|
205
|
+
/**
|
|
206
|
+
* First-seen timestamps of the records that count as additions: present in
|
|
207
|
+
* the newest tracked version. Records first seen at the baseline count too
|
|
208
|
+
* (they entered the server then) unless the ledger is `partial`, where their
|
|
209
|
+
* date is unknown. One per record. A skipped ledger, one with no baseline yet, and a scope whose newest
|
|
210
|
+
* version is not its newest tracked one (a binary file or an over-cap
|
|
211
|
+
* snapshot, whose `latest.total` does not describe those records) report none,
|
|
212
|
+
* so `added` can never exceed `total`.
|
|
213
|
+
*/
|
|
214
|
+
export declare function listAddedTimestamps(ledger: ScopeFirstSeenLedger): string[];
|
|
215
|
+
/**
|
|
216
|
+
* The ledger in `value`, or null unless it is a well-formed version-2
|
|
217
|
+
* document (older versions are rejected and rebuilt). Never throws. The result is a fresh object.
|
|
218
|
+
*/
|
|
219
|
+
export declare function readScopeFirstSeenLedger(value: unknown): ScopeFirstSeenLedger | null;
|
|
220
|
+
//# sourceMappingURL=first-added.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"first-added.d.ts","sourceRoot":"","sources":["../../src/additions/first-added.ts"],"names":[],"mappings":"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"}
|