@brass-build/client 0.1.0 → 0.3.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/AGENTS.md +72 -63
- package/CHANGELOG.md +97 -0
- package/README.md +325 -235
- package/dist/auth-flow.d.ts +10 -1
- package/dist/auth-flow.d.ts.map +1 -1
- package/dist/auth-flow.js +64 -20
- package/dist/auth-flow.js.map +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +245 -76
- package/dist/client.js.map +1 -1
- package/dist/document-checkpoints.d.ts +1 -0
- package/dist/document-checkpoints.d.ts.map +1 -1
- package/dist/document-checkpoints.js +27 -0
- package/dist/document-checkpoints.js.map +1 -1
- package/dist/document-engine.d.ts +4 -0
- package/dist/document-engine.d.ts.map +1 -0
- package/dist/document-engine.js +9 -0
- package/dist/document-engine.js.map +1 -0
- package/dist/document-signals.d.ts +31 -1
- package/dist/document-signals.d.ts.map +1 -1
- package/dist/document-signals.js +59 -0
- package/dist/document-signals.js.map +1 -1
- package/dist/document-streams.d.ts +8 -1
- package/dist/document-streams.d.ts.map +1 -1
- package/dist/document-streams.js +79 -8
- package/dist/document-streams.js.map +1 -1
- package/dist/document-thumbnail-capture.d.ts +20 -0
- package/dist/document-thumbnail-capture.d.ts.map +1 -1
- package/dist/document-thumbnail-capture.js +247 -9
- package/dist/document-thumbnail-capture.js.map +1 -1
- package/dist/documents-namespace.d.ts.map +1 -1
- package/dist/documents-namespace.js +22 -3
- package/dist/documents-namespace.js.map +1 -1
- package/dist/documents.d.ts +49 -16
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +981 -237
- package/dist/documents.js.map +1 -1
- package/dist/embed-protocol.d.ts +3 -0
- package/dist/embed-protocol.d.ts.map +1 -0
- package/dist/embed-protocol.js +26 -0
- package/dist/embed-protocol.js.map +1 -0
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +8 -1
- package/dist/emitter.js.map +1 -1
- package/dist/export-guest.d.ts.map +1 -1
- package/dist/export-guest.js +14 -6
- package/dist/export-guest.js.map +1 -1
- package/dist/fetch-deadline.d.ts +22 -0
- package/dist/fetch-deadline.d.ts.map +1 -0
- package/dist/fetch-deadline.js +96 -0
- package/dist/fetch-deadline.js.map +1 -0
- package/dist/fetch-retry.d.ts +1 -0
- package/dist/fetch-retry.d.ts.map +1 -1
- package/dist/fetch-retry.js +12 -5
- package/dist/fetch-retry.js.map +1 -1
- package/dist/handle-types.d.ts +150 -0
- package/dist/handle-types.d.ts.map +1 -0
- package/dist/handle-types.js +8 -0
- package/dist/handle-types.js.map +1 -0
- package/dist/handles.d.ts +48 -48
- package/dist/handles.d.ts.map +1 -1
- package/dist/handles.js +3122 -501
- package/dist/handles.js.map +1 -1
- package/dist/hosted-runtime.d.ts.map +1 -1
- package/dist/hosted-runtime.js +8 -12
- package/dist/hosted-runtime.js.map +1 -1
- package/dist/hosted-ui.d.ts +0 -1
- package/dist/hosted-ui.d.ts.map +1 -1
- package/dist/hosted-ui.js +29 -24
- package/dist/hosted-ui.js.map +1 -1
- package/dist/importer.d.ts.map +1 -1
- package/dist/importer.js +204 -8
- package/dist/importer.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/join-coverage.d.ts +12 -0
- package/dist/join-coverage.d.ts.map +1 -0
- package/dist/join-coverage.js +37 -0
- package/dist/join-coverage.js.map +1 -0
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +5 -1
- package/dist/jwt.js.map +1 -1
- package/dist/launch-context.js +2 -2
- package/dist/launch-context.js.map +1 -1
- package/dist/mutate-batching.d.ts +25 -2
- package/dist/mutate-batching.d.ts.map +1 -1
- package/dist/mutate-batching.js +57 -10
- package/dist/mutate-batching.js.map +1 -1
- package/dist/open-document.d.ts +30 -1
- package/dist/open-document.d.ts.map +1 -1
- package/dist/open-document.js +1905 -322
- package/dist/open-document.js.map +1 -1
- package/dist/presence.d.ts +28 -0
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +149 -7
- package/dist/presence.js.map +1 -1
- package/dist/record-paging.d.ts +335 -71
- package/dist/record-paging.d.ts.map +1 -1
- package/dist/record-paging.js +590 -127
- package/dist/record-paging.js.map +1 -1
- package/dist/selection-persistence.d.ts.map +1 -1
- package/dist/selection-persistence.js +11 -1
- package/dist/selection-persistence.js.map +1 -1
- package/dist/serializer.d.ts +30 -1
- package/dist/serializer.d.ts.map +1 -1
- package/dist/session-persistence.d.ts.map +1 -1
- package/dist/session-persistence.js +9 -8
- package/dist/session-persistence.js.map +1 -1
- package/dist/session-toolbar.d.ts +0 -1
- package/dist/session-toolbar.d.ts.map +1 -1
- package/dist/session-toolbar.js +356 -83
- package/dist/session-toolbar.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +87 -18
- package/dist/session.js.map +1 -1
- package/dist/tail-ops.d.ts +14 -4
- package/dist/tail-ops.d.ts.map +1 -1
- package/dist/tail-ops.js +75 -15
- package/dist/tail-ops.js.map +1 -1
- package/dist/topic-lifecycle.d.ts +4 -0
- package/dist/topic-lifecycle.d.ts.map +1 -1
- package/dist/topic-lifecycle.js +25 -1
- package/dist/topic-lifecycle.js.map +1 -1
- package/dist/types.d.ts +47 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/undo.d.ts +2 -3
- package/dist/undo.d.ts.map +1 -1
- package/dist/undo.js +11 -0
- package/dist/undo.js.map +1 -1
- package/dist/unload-guard.d.ts +5 -0
- package/dist/unload-guard.d.ts.map +1 -0
- package/dist/unload-guard.js +88 -0
- package/dist/unload-guard.js.map +1 -0
- package/dist/version-bar.d.ts +4 -3
- package/dist/version-bar.d.ts.map +1 -1
- package/dist/version-bar.js +162 -20
- package/dist/version-bar.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/dist/vite.d.ts +18 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +133 -8
- package/dist/vite.js.map +1 -1
- package/dist/when-saved.d.ts +2 -0
- package/dist/when-saved.d.ts.map +1 -0
- package/dist/when-saved.js +70 -0
- package/dist/when-saved.js.map +1 -0
- package/dist/ws-client.d.ts +27 -3
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +408 -89
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-frame-limit.d.ts.map +1 -1
- package/dist/ws-frame-limit.js +23 -17
- package/dist/ws-frame-limit.js.map +1 -1
- package/docs/app-manifest.md +7 -8
- package/docs/custom-chrome.md +21 -22
- package/docs/hosted-ui-advanced.md +14 -17
- package/docs/imported-documents.md +53 -197
- package/docs/presence.md +45 -21
- package/docs/schemas.md +12 -17
- package/docs/streams.md +55 -57
- package/package.json +4 -3
- package/src/auth-flow.ts +90 -23
- package/src/client.ts +262 -80
- package/src/document-checkpoints.ts +30 -0
- package/src/document-engine.ts +15 -0
- package/src/document-signals.ts +120 -8
- package/src/document-streams.ts +95 -9
- package/src/document-thumbnail-capture.ts +253 -9
- package/src/documents-namespace.ts +38 -13
- package/src/documents.ts +1103 -290
- package/src/embed-protocol.ts +25 -0
- package/src/emitter.ts +8 -1
- package/src/export-guest.ts +15 -9
- package/src/fetch-deadline.ts +119 -0
- package/src/fetch-retry.ts +14 -5
- package/src/handle-types.ts +303 -0
- package/src/handles.ts +3407 -683
- package/src/hosted-runtime.ts +8 -12
- package/src/hosted-ui.ts +28 -32
- package/src/importer.ts +216 -8
- package/src/index.ts +29 -18
- package/src/internal.ts +7 -0
- package/src/join-coverage.ts +42 -0
- package/src/jwt.ts +8 -1
- package/src/launch-context.ts +2 -2
- package/src/mutate-batching.ts +60 -11
- package/src/open-document.ts +2441 -502
- package/src/presence.ts +210 -11
- package/src/record-paging.ts +686 -137
- package/src/selection-persistence.ts +10 -1
- package/src/serializer.ts +33 -1
- package/src/session-persistence.ts +9 -8
- package/src/session-toolbar.ts +387 -89
- package/src/session.ts +91 -17
- package/src/tail-ops.ts +76 -10
- package/src/topic-lifecycle.ts +31 -1
- package/src/types.ts +56 -4
- package/src/undo.ts +13 -5
- package/src/unload-guard.ts +91 -0
- package/src/version-bar.ts +183 -26
- package/src/version.ts +1 -1
- package/src/vite.ts +175 -10
- package/src/when-saved.ts +66 -0
- package/src/ws-client.ts +461 -92
- package/src/ws-frame-limit.ts +23 -17
package/dist/record-paging.d.ts
CHANGED
|
@@ -6,69 +6,222 @@ export declare const BODY_FIELD = "params";
|
|
|
6
6
|
export declare const META_KEY = "meta";
|
|
7
7
|
export declare const RECORDS_KEY = "instances";
|
|
8
8
|
export declare const ORDER_KEY = "instance_order";
|
|
9
|
-
/** `meta` key
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
9
|
+
/** Prefix on the `meta` key marking ONE root map paged (`meta` holds
|
|
10
|
+
* `paged:instances` -> `true`). A root whose key is absent, or holds anything
|
|
11
|
+
* but `true`, is unpaged; a document with no paged root carries no such key.
|
|
12
|
+
*
|
|
13
|
+
* Per root rather than one flag for the document, so paging is a property of
|
|
14
|
+
* a collection rather than of a reserved name. One flag would force the read
|
|
15
|
+
* side to hardcode WHICH root it described, since a document-wide answer
|
|
16
|
+
* handed to every root would make `meta` and `header` read as paged the
|
|
17
|
+
* moment the records did. Keyed by root, a root with no key is simply
|
|
18
|
+
* unpaged, which is the answer those roots want anyway.
|
|
19
|
+
*
|
|
20
|
+
* A key per root rather than one entry naming them all. A map entry is
|
|
21
|
+
* written whole, so an entry holding every paged root is read-modify-write:
|
|
22
|
+
* two peers crossing the threshold on two different roots at once each write
|
|
23
|
+
* the whole entry, the two resolve last-writer-wins, and the root the losing
|
|
24
|
+
* write named is left unmarked. Nothing recovers it. Every reader then takes
|
|
25
|
+
* the unpaged path over a map holding nothing but pages, so the store's own
|
|
26
|
+
* bookkeeping keys read back as the records and the records reach nobody,
|
|
27
|
+
* while the raw entry count sits far below the threshold that would page the
|
|
28
|
+
* root again. A key per root means two roots are two keys, which merge.
|
|
29
|
+
*
|
|
30
|
+
* A flag rather than a page count. Pages are keyed by writing session and
|
|
31
|
+
* index (`brass:p:<peer>:<n>`) and a reader locates a record through the index
|
|
32
|
+
* it builds from the pages' `keys` fields, so no reader needs to know how many
|
|
33
|
+
* pages there are; a stored count would be a number that drifts as the store
|
|
34
|
+
* grows and that nothing checks.
|
|
35
|
+
*
|
|
36
|
+
* A plain value rather than a container: it is read on the hot path
|
|
37
|
+
* (`_isPaged` on every keyed read and edit), where one `get` of a scalar is
|
|
38
|
+
* the cheapest answer available. */
|
|
39
|
+
export declare const PAGED_ROOT_PREFIX = "paged:";
|
|
40
|
+
/** The `meta` key marking `rootKey` paged. */
|
|
41
|
+
export declare function pagedRootKey(rootKey: string): string;
|
|
42
|
+
/** Whether a value read from `meta` at `pagedRootKey(root)` marks that root
|
|
43
|
+
* paged. The one place the marker's stored form is interpreted, so the SDK's
|
|
44
|
+
* handle path and the store readers cannot disagree about what marks a root. */
|
|
45
|
+
export declare function isPagedMarker(marker: unknown): boolean;
|
|
35
46
|
/** Above this many records the store is written paged; at or below it it keeps
|
|
36
|
-
* the one-container-per-record shape.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
*
|
|
47
|
+
* the one-container-per-record shape.
|
|
48
|
+
*
|
|
49
|
+
* Set where paging starts paying for itself on every axis but one. Measured
|
|
50
|
+
* on the two shapes at equal record counts (`scripts/bench/paging-threshold.mts`),
|
|
51
|
+
* the paged store writes ~3x faster, opens ~5x faster, and stores ~1.7x
|
|
52
|
+
* smaller here, against ~1.4x on a single edit, and that last penalty
|
|
53
|
+
* inverts by 25,000 records. Below a few hundred records the paged store is
|
|
54
|
+
* actually LARGER, since per-page gzip overhead outweighs what it saves. */
|
|
55
|
+
export declare const UNPAGED_MAX_RECORDS = 5000;
|
|
56
|
+
/** The tag a page container carries in place of a record tag, which labels it
|
|
57
|
+
* in a raw dump of the stored document. A READER never decides from it: what
|
|
58
|
+
* makes an entry a page is its key (`SESSION_PAGE_PREFIX`), so a record whose
|
|
59
|
+
* own tag happens to be this string is still a record. */
|
|
41
60
|
export declare const PAGE_TAG = "__page__";
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
61
|
+
/** Prefix on every key the record store writes for its own bookkeeping: the
|
|
62
|
+
* pages, a record's own container, a delete marker, a patch.
|
|
63
|
+
*
|
|
64
|
+
* A record and the bookkeeping about it share one map, so the two key spaces
|
|
65
|
+
* have to be told apart by something an app cannot produce. The map handle
|
|
66
|
+
* refuses a write under this prefix (`assertUsableRecordKey`), which makes the
|
|
67
|
+
* space exclusive: every key outside it is a record key, and every key inside
|
|
68
|
+
* it is the store's own. Nothing here inspects an app's VALUE to make that
|
|
69
|
+
* call, so no record can be mistaken for a page or a tombstone whatever it
|
|
70
|
+
* holds. */
|
|
71
|
+
export declare const RESERVED_KEY_PREFIX = "brass:";
|
|
72
|
+
/** Whether `key` names one of the store's own entries rather than a record. */
|
|
73
|
+
export declare function isReservedKey(key: string): boolean;
|
|
74
|
+
/** Refuse a record key the store cannot carry, at the moment an app writes it:
|
|
75
|
+
* one inside the reserved space, and `__proto__`.
|
|
76
|
+
*
|
|
77
|
+
* Rejecting the reserved space is what lets every reader decide what an entry
|
|
78
|
+
* is from the key alone. The alternative, escaping, would put the escape on
|
|
79
|
+
* the read of every key of every map, paged or not, and a store that pages
|
|
80
|
+
* later cannot escape what it already holds. */
|
|
81
|
+
export declare function assertUsableRecordKey(key: string): void;
|
|
82
|
+
/** The prefix on a record written as its OWN container in a paged store,
|
|
83
|
+
* which shadows the page's copy on read. A page body is one value, so an
|
|
84
|
+
* edit that rewrote its page would make the page the merge unit; a record in
|
|
85
|
+
* its own container merges on its own. */
|
|
86
|
+
export declare const OVERLAY_PREFIX = "brass:r:";
|
|
87
|
+
/** Pack one stored value into a page triple. A `null` tag is what says the
|
|
88
|
+
* body IS the record: an app's map holds whatever the app assigned, which has
|
|
89
|
+
* no tag to split off. A typed record arrives already split, and its tag is a
|
|
90
|
+
* string, so the two forms cannot be confused whatever the tag says. */
|
|
91
|
+
export declare function packWholeValue<B>(key: string, value: B): PackedRecord<B>;
|
|
92
|
+
/** Re-pack `value` in the same triple shape `packed` already uses, so a page
|
|
93
|
+
* rewritten in place keeps the form its readers expect: a typed record stays
|
|
94
|
+
* a tag beside a body, and an app's stays one whole value. A streaming
|
|
95
|
+
* read visits a page's triples without unpacking them, so a rewrite that
|
|
96
|
+
* changed the shape would hand it the wrong half of the record. */
|
|
97
|
+
export declare function repackRecord(packed: PackedRecord<unknown>, value: unknown): PackedRecord<unknown>;
|
|
98
|
+
/** The stored value a page triple carries: the value itself for a whole-value
|
|
99
|
+
* record, else the `{ type, params }` pair a typed one was split into. */
|
|
100
|
+
export declare function unpackRecord(packed: PackedRecord<unknown>): unknown;
|
|
101
|
+
/** The tag + body a page triple names, for a reader that walks the two
|
|
102
|
+
* separately, or null when the triple packs a whole value that is not a
|
|
103
|
+
* record.
|
|
104
|
+
*
|
|
105
|
+
* A page holds both forms: a typed record is packed as the tag and body it
|
|
106
|
+
* was already split into, and a map an app writes packs whatever the app
|
|
107
|
+
* assigned as one whole value. Both forms occur in ONE store, because an
|
|
108
|
+
* app's bulk add to a store of typed records seals its own pages, so a reader
|
|
109
|
+
* that took `packed[1]` for the tag would report every record those pages
|
|
110
|
+
* hold as having no type at all. */
|
|
111
|
+
export declare function unpackRecordPair(packed: PackedRecord<unknown>): {
|
|
112
|
+
tag: string;
|
|
113
|
+
body: unknown;
|
|
114
|
+
} | null;
|
|
115
|
+
/** The container key a record is written under, beside the pages. */
|
|
116
|
+
export declare function overlayKeyFor(recordKey: string): string;
|
|
117
|
+
/** The marker key standing for a record deleted out of a paged store. */
|
|
118
|
+
export declare function tombstoneKeyFor(recordKey: string): string;
|
|
119
|
+
/** The prefix on a page written by ONE editing session, which fills its pages
|
|
120
|
+
* in creation order.
|
|
121
|
+
*
|
|
122
|
+
* An app fills a map by appending, so a rule that scattered one mutation's
|
|
123
|
+
* records across every page would add a record or two to each of thousands of
|
|
124
|
+
* pages and rewrite every one of them again on the next mutation, which costs
|
|
125
|
+
* more than the per-record containers it replaces. Filling in creation order
|
|
126
|
+
* packs each mutation's records into its own dense pages, written complete
|
|
127
|
+
* and never rewritten.
|
|
128
|
+
*
|
|
129
|
+
* The token makes the key space exclusive to one writer. A page body is a
|
|
130
|
+
* single value, so two writers appending to the same page container resolve
|
|
131
|
+
* last-writer-wins and one writer's records are lost; with a per-session
|
|
132
|
+
* token no two writers ever address the same page. It is also what makes
|
|
133
|
+
* filling in creation order safe on reopen: a session never inherits, and so
|
|
134
|
+
* never rewrites, a page an earlier session sealed. */
|
|
135
|
+
export declare const SESSION_PAGE_PREFIX = "brass:p:";
|
|
136
|
+
/** The container key for page `index` of the session identified by `token`. */
|
|
137
|
+
export declare function sessionPageKey(token: string, index: number): string;
|
|
138
|
+
/** The `{ token, index }` a session page key names, or null for any other key.
|
|
139
|
+
* Used to seed a session's next page index above what the store already
|
|
140
|
+
* holds, so a reopened session cannot overwrite its own sealed pages. */
|
|
141
|
+
export declare function parseSessionPageKey(key: string): {
|
|
142
|
+
token: string;
|
|
143
|
+
index: number;
|
|
144
|
+
} | null;
|
|
145
|
+
/** A record packed inside a page: `[key, tag, body]`, where a `null` tag means
|
|
146
|
+
* the body is the record's WHOLE stored value (`packWholeValue`). The tuple
|
|
147
|
+
* form keeps a paged snapshot compact. It is how a reader HOLDS a page's
|
|
148
|
+
* records; a stored page splits the key off into its own field
|
|
149
|
+
* (`PAGE_KEYS_FIELD`). */
|
|
150
|
+
export type PackedRecord<B> = [key: string, tag: string | null, body: B];
|
|
151
|
+
/** Field on a page container holding the keys of the records its body packs,
|
|
152
|
+
* in the same order, so the two are zipped by index.
|
|
153
|
+
*
|
|
154
|
+
* The keys live apart from the bodies because finding a record in a page
|
|
155
|
+
* filled in creation order (`SESSION_PAGE_PREFIX`) means learning which page
|
|
156
|
+
* holds which key, and that question is answered by the keys alone. Packed
|
|
157
|
+
* beside the bodies, answering it costs decompressing and parsing every
|
|
158
|
+
* record in the store: measured at 250,000 records, 1.85 s against 0.24 s for
|
|
159
|
+
* the keys on their own. Splitting them costs no bytes, since the keys are
|
|
160
|
+
* stored once either way and compress better grouped than interleaved with
|
|
161
|
+
* the bodies. */
|
|
162
|
+
export declare const PAGE_KEYS_FIELD = "keys";
|
|
163
|
+
/** A page's two stored strings: the record bodies and the keys they belong
|
|
164
|
+
* to. */
|
|
165
|
+
export interface EncodedPage {
|
|
166
|
+
body: string;
|
|
167
|
+
keys: string;
|
|
168
|
+
/** The body's size BEFORE gzip, so a writer can charge a page in the same
|
|
169
|
+
* unit an ordinary map assignment is charged in. Taken here because the
|
|
170
|
+
* uncompressed string is built here and nothing else holds it. */
|
|
171
|
+
chars: number;
|
|
172
|
+
}
|
|
173
|
+
/** Encode a page's records as the two strings it stores. Each is JSON,
|
|
174
|
+
* gzipped, held as a latin1 string. A STRING avoids loro's superlinear
|
|
175
|
+
* per-value conversion of a nested array; gzipping keeps a heavy file in
|
|
176
|
+
* memory + export budget. (Full rationale in the substrate's page notes.) */
|
|
177
|
+
export declare function encodePage<B>(records: PackedRecord<B>[]): EncodedPage;
|
|
178
|
+
/** Encode one record's `[tag, body]` pair as the JSON string it occupies
|
|
179
|
+
* inside a page body. Split from page assembly so a large write can
|
|
52
180
|
* stringify each record as it buckets and release the parsed body before the
|
|
53
|
-
* gzip loop
|
|
54
|
-
*
|
|
55
|
-
export declare function encodeRecordForPage<B>(
|
|
56
|
-
/** Assemble a page
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* is unchanged; the split exists only so the writer frees each parsed body
|
|
181
|
+
* gzip loop: holding a whole parsed model live through that loop makes GC
|
|
182
|
+
* marking scale with the live set and turns the encode into a wall. */
|
|
183
|
+
export declare function encodeRecordForPage<B>(tag: string | null, body: B): string;
|
|
184
|
+
/** Assemble a page from bodies already JSON-encoded by `encodeRecordForPage`
|
|
185
|
+
* and the keys they belong to. Byte-identical to `encodePage` over the same
|
|
186
|
+
* records; the split exists only so the writer frees each parsed body
|
|
60
187
|
* first. */
|
|
61
|
-
export declare function
|
|
62
|
-
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
export declare function
|
|
66
|
-
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
188
|
+
export declare function encodePageFromEncoded(encoded: string[], keys: string[]): EncodedPage;
|
|
189
|
+
/** The record keys a page holds, from the page's own `keys` string. Reading
|
|
190
|
+
* this field alone is what makes learning a store's key locations cost the
|
|
191
|
+
* keys rather than every record body. */
|
|
192
|
+
export declare function decodePageKeys(keysField: unknown): string[];
|
|
193
|
+
/** A page's records, zipped back into `[key, tag, body]` triples from the two
|
|
194
|
+
* strings the page stores. A key with no body (or a body with no key) is
|
|
195
|
+
* dropped: the two arrays are written together and index each other, so a
|
|
196
|
+
* length mismatch means a page nothing can read as records. */
|
|
197
|
+
export declare function decodePageRecords(page: unknown): PackedRecord<unknown>[];
|
|
198
|
+
/** Mean JSON bytes of a record body, over a stride sample of at most
|
|
199
|
+
* `WIDTH_SAMPLE` records, which is what `recordsPerPage` sizes a page
|
|
200
|
+
* against. `bodyOf` returns the value the caller will pack as the triple's
|
|
201
|
+
* body: a typed record carries a tag beside a body, and an app's map packs
|
|
202
|
+
* its whole stored value (`packWholeValue`), so each caller weighs the bytes
|
|
203
|
+
* its own pages will hold.
|
|
204
|
+
*
|
|
205
|
+
* Returns 0, which keeps `recordsPerPage`'s plain record-count rule, for an
|
|
206
|
+
* empty population and for a body `JSON.stringify` refuses. Such a body fails
|
|
207
|
+
* the write regardless (a paged write stringifies each record, and loro
|
|
208
|
+
* rejects a non-JSON value), so the catch is not there to rescue it; it is
|
|
209
|
+
* there so that a page-size ESTIMATE is never what decides a write fails, and
|
|
210
|
+
* the failure stays with the code that owns the body. */
|
|
211
|
+
export declare function sampleBodyBytes<T>(records: readonly T[], bodyOf: (record: T) => unknown): number;
|
|
212
|
+
/** Records one page holds: `TARGET_PAGE_SIZE`, or fewer when that many bodies
|
|
213
|
+
* would exceed `TARGET_PAGE_BYTES`. `avgBodyBytes` of 0 or absent (a caller
|
|
214
|
+
* with no estimate) keeps the count rule. */
|
|
215
|
+
export declare function recordsPerPage(avgBodyBytes?: number): number;
|
|
216
|
+
/** How many pages a record population occupies: 0 (unpaged) at or below
|
|
217
|
+
* `UNPAGED_MAX_RECORDS`, else one page per `recordsPerPage(avgBodyBytes)`
|
|
218
|
+
* records.
|
|
219
|
+
*
|
|
220
|
+
* A projection, not a decision the write path takes. Pages are filled in
|
|
221
|
+
* creation order and allocated as a write needs them, so this is what
|
|
222
|
+
* `scripts/bench` uses to price container counts and what the hash-page
|
|
223
|
+
* fixtures use as their bucket modulus. */
|
|
224
|
+
export declare function pageCountFor(recordCount: number, avgBodyBytes?: number): number;
|
|
72
225
|
/** A stored container's tag (`type`), or null for any value not in the
|
|
73
226
|
* `{ type, params }` shape, so a reader skips a corrupt entry. A page carries
|
|
74
227
|
* `PAGE_TAG`. */
|
|
@@ -79,21 +232,132 @@ export declare function decodeRecordValue(value: unknown): {
|
|
|
79
232
|
tag: string;
|
|
80
233
|
body: unknown;
|
|
81
234
|
} | null;
|
|
82
|
-
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
|
|
235
|
+
/** Prefix of a RECORD PATCH: the fields one edit changed, applied over the
|
|
236
|
+
* record's page copy on read.
|
|
237
|
+
*
|
|
238
|
+
* Giving an edited record a container of its own instead restores the
|
|
239
|
+
* per-container cost paging exists to remove, so a store whose records are
|
|
240
|
+
* all eventually edited ends up slower and larger than one that never paged.
|
|
241
|
+
* A patch keeps the record packed and costs one entry per edit rather than a
|
|
242
|
+
* container per edited record.
|
|
243
|
+
*
|
|
244
|
+
* The key carries the writing peer and a counter. The peer is what lets the
|
|
245
|
+
* patches be FOLDED back into their pages later without a lock: a CRDT delete
|
|
246
|
+
* only defeats a CONCURRENT write to the same key, and no two writers can
|
|
247
|
+
* name the same key while the peer is in it, so a peer editing during
|
|
248
|
+
* another's fold writes a key that fold never saw. A writer that re-mints a
|
|
249
|
+
* key it folded away itself is ordered after its own delete, so that write is
|
|
250
|
+
* not the race either.
|
|
251
|
+
*
|
|
252
|
+
* The counter orders the patches on ONE record, and is minted one above every
|
|
253
|
+
* counter that record already carries. That is what makes the fold causal: a
|
|
254
|
+
* patch written by someone who had already merged an earlier patch sorts
|
|
255
|
+
* above it, whoever wrote which. A counter that instead counted the WRITER's
|
|
256
|
+
* edits would put a busy session's first-ever edit of a record above a later
|
|
257
|
+
* session's, and the later edit would vanish as it was made.
|
|
258
|
+
*
|
|
259
|
+
* The record key goes last because it may contain a colon; the peer and
|
|
260
|
+
* counter may not.
|
|
261
|
+
*
|
|
262
|
+
* Each reserved space differs from its siblings at the character after
|
|
263
|
+
* `RESERVED_KEY_PREFIX`, so `classifyStoreKey` decides between them with one
|
|
264
|
+
* test each and no ordering to get right. */
|
|
265
|
+
export declare const PATCH_PREFIX = "brass:q:";
|
|
266
|
+
export declare function patchKeyFor(recordKey: string, peer: string, counter: number): string;
|
|
267
|
+
export interface ParsedPatchKey {
|
|
268
|
+
recordKey: string;
|
|
269
|
+
peer: string;
|
|
270
|
+
counter: string;
|
|
271
|
+
}
|
|
272
|
+
/** What one entry of a paged map is, decided from its key alone.
|
|
273
|
+
*
|
|
274
|
+
* `record` is the ordinary case: every key outside the reserved space names a
|
|
275
|
+
* record, whatever its value holds. `unknown` is a reserved key this build
|
|
276
|
+
* does not recognise, which a reader skips rather than reporting as a record,
|
|
277
|
+
* so a store written by a later build degrades to missing entries instead of
|
|
278
|
+
* entries named after the store's own bookkeeping. */
|
|
279
|
+
export type StoreEntryKind = {
|
|
280
|
+
kind: 'record';
|
|
281
|
+
recordKey: string;
|
|
282
|
+
} | {
|
|
283
|
+
kind: 'page';
|
|
284
|
+
} | {
|
|
285
|
+
kind: 'overlay';
|
|
286
|
+
recordKey: string;
|
|
287
|
+
} | {
|
|
288
|
+
kind: 'tombstone';
|
|
289
|
+
recordKey: string;
|
|
290
|
+
} | {
|
|
291
|
+
kind: 'patch';
|
|
292
|
+
parsed: ParsedPatchKey;
|
|
293
|
+
} | {
|
|
294
|
+
kind: 'unknown';
|
|
295
|
+
};
|
|
296
|
+
/** Classify one key of a paged map. The single place the key spaces are told
|
|
297
|
+
* apart, so a keyed read, a whole-store read, a streaming read, and the page
|
|
298
|
+
* index cannot disagree about which entry is which. */
|
|
299
|
+
export declare function classifyStoreKey(key: string): StoreEntryKind;
|
|
300
|
+
/** The parts of a patch key, or null when `key` is not one.
|
|
301
|
+
*
|
|
302
|
+
* The record key runs to the end of the string and may be empty, which is a
|
|
303
|
+
* record key like any other: the two colons after the prefix delimit the peer
|
|
304
|
+
* and the counter, so the tail needs no length of its own to be
|
|
305
|
+
* unambiguous. Reading an empty tail as "not a patch" instead leaves the
|
|
306
|
+
* entry classified `unknown`, which every reader skips, so the edit stays in
|
|
307
|
+
* the document and reaches nobody. */
|
|
308
|
+
export declare function parsePatchKey(key: string): ParsedPatchKey | null;
|
|
309
|
+
/** One patch: the fields an edit changed, plus the pair that orders it. */
|
|
310
|
+
export interface RecordPatch {
|
|
311
|
+
peer: string;
|
|
312
|
+
counter: string;
|
|
313
|
+
fields: unknown;
|
|
314
|
+
}
|
|
315
|
+
/** Apply `patches` over `base`, in the order every reader must agree on: by
|
|
316
|
+
* counter, then by peer id. The counter is per record and minted one above
|
|
317
|
+
* what that record already carries, so a patch whose writer had merged an
|
|
318
|
+
* earlier one applies over it. Peer id breaks the tie between two writers who
|
|
319
|
+
* had not seen each other, so both reach the same answer. */
|
|
320
|
+
export declare function applyRecordPatches(base: unknown, patches: RecordPatch[]): unknown;
|
|
321
|
+
/**
|
|
322
|
+
* Apply patches to a record a streaming read carries both as its whole stored
|
|
323
|
+
* value and as the `(tag, body)` pair split out of it, and return all three
|
|
324
|
+
* after the fold.
|
|
325
|
+
*
|
|
326
|
+
* A patch names the record's own fields, which for a typed record are
|
|
327
|
+
* `type` and `params` together, so a reader that walks tag and body
|
|
328
|
+
* separately has to re-split the result. Both readers of a paged store
|
|
329
|
+
* (the flattening one and the streaming one) resolve patches through this
|
|
330
|
+
* module, so an edit cannot read one way in the editor and another in a
|
|
331
|
+
* re-emitted file. The fold runs over the whole value for the same reason the
|
|
332
|
+
* streaming read carries one: a field beyond the pair is a field of the record
|
|
333
|
+
* and an edit may name it.
|
|
334
|
+
*/
|
|
335
|
+
export declare function applyPatchesToRecord(record: unknown, tag: string, body: unknown, patches: RecordPatch[]): {
|
|
336
|
+
tag: string;
|
|
337
|
+
body: unknown;
|
|
338
|
+
record: unknown;
|
|
339
|
+
};
|
|
340
|
+
/** Where a paged store keeps each record, reported as a flatten resolves it.
|
|
341
|
+
* A reader that has to locate records by key (a keyed read over pages filled
|
|
342
|
+
* in creation order) takes its index from the walk that decodes the pages
|
|
343
|
+
* anyway, rather than decoding them a second time of its own. */
|
|
344
|
+
export interface RecordLocationSink {
|
|
345
|
+
/** `recordKey` is packed in the body of page `pageKey`. */
|
|
346
|
+
page(recordKey: string, pageKey: string): void;
|
|
347
|
+
/** A container of its own answers for `recordKey`, outranking any page. */
|
|
348
|
+
contained(recordKey: string): void;
|
|
349
|
+
/** A tombstone removes `recordKey`, whatever a page still holds. */
|
|
350
|
+
removed(recordKey: string): void;
|
|
351
|
+
}
|
|
86
352
|
/** Flatten a possibly-PAGED `instances` lookup to the flat
|
|
87
353
|
* `key -> { type, params }` lookup a reader consumes. An unpaged lookup is
|
|
88
354
|
* returned unchanged (no allocation). The single place paging is undone on the
|
|
89
355
|
* read side.
|
|
90
356
|
*
|
|
91
|
-
* `paged` is the AUTHORITATIVE page state, read from `meta
|
|
92
|
-
* caller (`
|
|
93
|
-
* on
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
|
|
97
|
-
* a crafted page-shaped body could forge records into the lookup. */
|
|
98
|
-
export declare function flattenPagedLookup(lookup: Record<string, unknown>, paged: boolean): Record<string, unknown>;
|
|
357
|
+
* `paged` is the AUTHORITATIVE page state, read from the root's own `meta`
|
|
358
|
+
* key by the caller (`pagedRootKey` / `isPagedMarker`). Page interpretation
|
|
359
|
+
* MUST be gated on it: in an UNPAGED store every key is a record key,
|
|
360
|
+
* including one that looks reserved, because nothing refused it before the
|
|
361
|
+
* store was written. */
|
|
362
|
+
export declare function flattenPagedLookup(lookup: Record<string, unknown>, paged: boolean, into?: RecordLocationSink): Record<string, unknown>;
|
|
99
363
|
//# sourceMappingURL=record-paging.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-paging.d.ts","sourceRoot":"","sources":["../src/record-paging.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"record-paging.d.ts","sourceRoot":"","sources":["../src/record-paging.ts"],"names":[],"mappings":"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"}
|