@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.
Files changed (41) hide show
  1. package/dist/additions/first-added.d.ts +220 -0
  2. package/dist/additions/first-added.d.ts.map +1 -0
  3. package/dist/additions/first-added.js +653 -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 +407 -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 +56 -0
  22. package/dist/additions/summary.d.ts.map +1 -0
  23. package/dist/additions/summary.js +114 -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,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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"}