@opendatalabs/personal-server-ts-core 1.32.0 → 1.33.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.
Files changed (41) hide show
  1. package/dist/additions/first-added.d.ts +203 -0
  2. package/dist/additions/first-added.d.ts.map +1 -0
  3. package/dist/additions/first-added.js +647 -0
  4. package/dist/additions/first-added.js.map +1 -0
  5. package/dist/additions/index.d.ts +6 -0
  6. package/dist/additions/index.d.ts.map +1 -0
  7. package/dist/additions/index.js +6 -0
  8. package/dist/additions/index.js.map +1 -0
  9. package/dist/additions/ledger-store.d.ts +69 -0
  10. package/dist/additions/ledger-store.d.ts.map +1 -0
  11. package/dist/additions/ledger-store.js +343 -0
  12. package/dist/additions/ledger-store.js.map +1 -0
  13. package/dist/additions/record-rules.d.ts +54 -0
  14. package/dist/additions/record-rules.d.ts.map +1 -0
  15. package/dist/additions/record-rules.js +191 -0
  16. package/dist/additions/record-rules.js.map +1 -0
  17. package/dist/additions/scope-lock.d.ts +14 -0
  18. package/dist/additions/scope-lock.d.ts.map +1 -0
  19. package/dist/additions/scope-lock.js +32 -0
  20. package/dist/additions/scope-lock.js.map +1 -0
  21. package/dist/additions/summary.d.ts +48 -0
  22. package/dist/additions/summary.d.ts.map +1 -0
  23. package/dist/additions/summary.js +101 -0
  24. package/dist/additions/summary.js.map +1 -0
  25. package/dist/api/index.d.ts.map +1 -1
  26. package/dist/api/index.js +26 -0
  27. package/dist/api/index.js.map +1 -1
  28. package/dist/contracts/data.d.ts +19 -1
  29. package/dist/contracts/data.d.ts.map +1 -1
  30. package/dist/contracts/data.js +95 -23
  31. package/dist/contracts/data.js.map +1 -1
  32. package/dist/ports/index.d.ts +11 -0
  33. package/dist/ports/index.d.ts.map +1 -1
  34. package/dist/ports/index.js.map +1 -1
  35. package/dist/sync/workers/download.d.ts.map +1 -1
  36. package/dist/sync/workers/download.js +19 -0
  37. package/dist/sync/workers/download.js.map +1 -1
  38. package/dist/test-utils/memory-storage.d.ts.map +1 -1
  39. package/dist/test-utils/memory-storage.js +47 -9
  40. package/dist/test-utils/memory-storage.js.map +1 -1
  41. package/package.json +5 -1
@@ -0,0 +1,203 @@
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: 3;
51
+ scope: string;
52
+ /**
53
+ * collectedAt of the EARLIEST TRACKABLE version folded in (one with records
54
+ * that have ids, or an empty one): records first seen then predate tracking.
55
+ * Null until a trackable version exists, so a binary, over-cap or
56
+ * id-less first snapshot never makes everything after it look added.
57
+ */
58
+ baseline: string | null;
59
+ /** collectedAt of the NEWEST trackable version: a record is present iff last seen then. */
60
+ current: string | null;
61
+ /** collectedAt of the NEWEST version of any kind, and its record total (a binary file counts 1). */
62
+ latest: {
63
+ collectedAt: string;
64
+ total: number;
65
+ };
66
+ /**
67
+ * The ledger is known complete up to this collectedAt: every retained
68
+ * version at or before it has been folded. A write that folds only itself
69
+ * advances it only when no other version is newer than it, so the owner's
70
+ * `/additions` read knows what to catch up on.
71
+ */
72
+ through: string;
73
+ /**
74
+ * Versions that could not be read when they were folded, and when the last
75
+ * attempt was made. They are retried at most once per `RETRY_UNREADABLE_MS`
76
+ * and meanwhile the ledger is served as it is (the numbers of the newest
77
+ * readable version) without reading anything. `through` has already moved
78
+ * past them, so a poll with nothing new reads nothing.
79
+ */
80
+ retry?: {
81
+ versions: string[];
82
+ attemptedAt: string;
83
+ };
84
+ /** Set when no per-record keys are kept (see `LedgerSkipReason`); `records` is then empty. */
85
+ skipped?: LedgerSkipReason;
86
+ /**
87
+ * The ledger was rebuilt from only the newest retained versions (count or
88
+ * byte budget). A version older than `baseline` then never moves the
89
+ * baseline back over the versions that were not folded.
90
+ */
91
+ partial?: true;
92
+ /** record key -> [firstSeen collectedAt, lastSeen collectedAt] */
93
+ records: Record<string, [string, string]>;
94
+ }
95
+ /** A stored version to fold: its collectedAt and the envelope `data` as stored. */
96
+ export interface VersionToFold {
97
+ scope: string;
98
+ collectedAt: string;
99
+ data: Record<string, unknown>;
100
+ }
101
+ export interface RecordKeyExtraction {
102
+ /** De-duplicated keys of the records that have a usable id, in first-seen order. Empty when `tooMany`. */
103
+ keys: string[];
104
+ /** Records in the counted collections, tracked or not. */
105
+ total: number;
106
+ /** More than `MAX_TRACKED_KEYS` tracked keys: extraction stopped collecting them. */
107
+ tooMany?: true;
108
+ }
109
+ /**
110
+ * Stable keys for the records in an envelope `data` object, plus the number of
111
+ * records counted. Returns null when the data is not trackable (binary).
112
+ *
113
+ * Both stored forms of a scope give the same keys: the PDPP rows form
114
+ * `{ records: [row, ...] }` and the legacy keyed form `{ <collection>: [...] }`
115
+ * (a ruled scope names the id field per form, see `record-rules.ts`). A record
116
+ * is tracked only when it has a usable id; records without one count toward
117
+ * `total` but produce no key. Ids and collection names over 64 characters are
118
+ * hashed, so every key is at most about 200 characters.
119
+ */
120
+ export declare function extractRecordKeys(scope: string, data: Record<string, unknown>): Promise<RecordKeyExtraction | null>;
121
+ /**
122
+ * Folds stored versions into one ledger, one at a time, so a caller can read a
123
+ * version, `add` it and drop it before reading the next: only one parsed
124
+ * envelope is ever alive. Order independent and idempotent for versions within
125
+ * the 90-day pruning window of each other (pruning is relative to the newest
126
+ * tracked version, so folding versions further apart in a different order can
127
+ * re-date a record that was already pruned).
128
+ */
129
+ export declare class LedgerFold {
130
+ private scope;
131
+ private baseline;
132
+ private current;
133
+ private latest;
134
+ private through;
135
+ private retry;
136
+ private skipped;
137
+ private partial;
138
+ private records;
139
+ constructor(ledger: ScopeFirstSeenLedger | null, scope?: string);
140
+ add(version: VersionToFold): Promise<void>;
141
+ /**
142
+ * Insert the records this version brings that the ledger does not know.
143
+ * Capacity is decided ONCE: when they do not fit, absent records are evicted
144
+ * in a single pass (see `evict`), and whatever still does not fit is left
145
+ * out, smallest keys first, so the result never depends on the order of the
146
+ * ids in the version.
147
+ */
148
+ private insertFresh;
149
+ /**
150
+ * Make room for `need` records by dropping some that are absent: not in the
151
+ * newest tracked version, not in the version being folded, and not
152
+ * pre-tracking (first seen at or before the baseline). Pre-tracking records
153
+ * are never dropped. Dated records that are absent CAN be dropped (and are
154
+ * dated as new if they return): that needs a scope near the cap, and
155
+ * whoever can write the scope can provoke it. Among the evictable ones the
156
+ * most recently first seen go first. Returns how many were dropped.
157
+ */
158
+ private evict;
159
+ /** Drop records that are not present and were last seen over 90 days before `current`. */
160
+ private prune;
161
+ /**
162
+ * Prune and return the ledger, or null when nothing was ever folded.
163
+ * `partial` marks a ledger rebuilt from only the newest retained versions;
164
+ * `through` overrides the completeness marker (see `ScopeFirstSeenLedger`).
165
+ */
166
+ finish(options?: {
167
+ partial?: boolean;
168
+ through?: string;
169
+ /** `null` clears the retry list; undefined keeps it. */
170
+ retry?: ScopeFirstSeenLedger["retry"] | null;
171
+ }): Promise<ScopeFirstSeenLedger | null>;
172
+ }
173
+ /**
174
+ * Fold several stored versions into a ledger in one pass. Equivalent to
175
+ * calling `foldVersion` for each, but copies the ledger and prunes once.
176
+ * Returns null only when no version had a parseable `collectedAt` and there
177
+ * was no starting ledger.
178
+ */
179
+ export declare function foldVersions(ledger: ScopeFirstSeenLedger | null, versions: readonly VersionToFold[]): Promise<ScopeFirstSeenLedger | null>;
180
+ /**
181
+ * Fold one stored version into a ledger (or start one). A version whose
182
+ * `collectedAt` does not parse is ignored.
183
+ */
184
+ export declare function foldVersion(ledger: ScopeFirstSeenLedger | null, version: VersionToFold): Promise<ScopeFirstSeenLedger | null>;
185
+ /** True when the record was in the newest tracked version folded in. */
186
+ export declare function isPresent(ledger: ScopeFirstSeenLedger, key: string): boolean;
187
+ /** True when the record was first seen at or before the baseline (or there is no baseline). */
188
+ export declare function isPreTracking(ledger: ScopeFirstSeenLedger, key: string): boolean;
189
+ /**
190
+ * First-seen timestamps of the records that count as additions: present in
191
+ * the newest tracked version and first seen after the baseline. One per
192
+ * record. A skipped ledger, one with no baseline yet, and a scope whose newest
193
+ * version is not its newest tracked one (a binary file or an over-cap
194
+ * snapshot, whose `latest.total` does not describe those records) report none,
195
+ * so `added` can never exceed `total`.
196
+ */
197
+ export declare function listAddedTimestamps(ledger: ScopeFirstSeenLedger): string[];
198
+ /**
199
+ * The ledger in `value`, or null unless it is a well-formed version-2
200
+ * document (older versions are rejected and rebuilt). Never throws. The result is a fresh object.
201
+ */
202
+ export declare function readScopeFirstSeenLedger(value: unknown): ScopeFirstSeenLedger | null;
203
+ //# 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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"}