@bradensbay/globals-core 0.2.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 (98) hide show
  1. package/README.md +69 -0
  2. package/dist/src/allocator.d.ts +78 -0
  3. package/dist/src/allocator.d.ts.map +1 -0
  4. package/dist/src/allocator.js +211 -0
  5. package/dist/src/allocator.js.map +1 -0
  6. package/dist/src/arena.d.ts +81 -0
  7. package/dist/src/arena.d.ts.map +1 -0
  8. package/dist/src/arena.js +224 -0
  9. package/dist/src/arena.js.map +1 -0
  10. package/dist/src/checksum.d.ts +5 -0
  11. package/dist/src/checksum.d.ts.map +1 -0
  12. package/dist/src/checksum.js +35 -0
  13. package/dist/src/checksum.js.map +1 -0
  14. package/dist/src/draft.d.ts +48 -0
  15. package/dist/src/draft.d.ts.map +1 -0
  16. package/dist/src/draft.js +427 -0
  17. package/dist/src/draft.js.map +1 -0
  18. package/dist/src/errors.d.ts +56 -0
  19. package/dist/src/errors.d.ts.map +1 -0
  20. package/dist/src/errors.js +69 -0
  21. package/dist/src/errors.js.map +1 -0
  22. package/dist/src/external.d.ts +52 -0
  23. package/dist/src/external.d.ts.map +1 -0
  24. package/dist/src/external.js +68 -0
  25. package/dist/src/external.js.map +1 -0
  26. package/dist/src/hamt.d.ts +87 -0
  27. package/dist/src/hamt.d.ts.map +1 -0
  28. package/dist/src/hamt.js +643 -0
  29. package/dist/src/hamt.js.map +1 -0
  30. package/dist/src/history.d.ts +45 -0
  31. package/dist/src/history.d.ts.map +1 -0
  32. package/dist/src/history.js +49 -0
  33. package/dist/src/history.js.map +1 -0
  34. package/dist/src/index.d.ts +55 -0
  35. package/dist/src/index.d.ts.map +1 -0
  36. package/dist/src/index.js +38 -0
  37. package/dist/src/index.js.map +1 -0
  38. package/dist/src/inspect.d.ts +74 -0
  39. package/dist/src/inspect.d.ts.map +1 -0
  40. package/dist/src/inspect.js +127 -0
  41. package/dist/src/inspect.js.map +1 -0
  42. package/dist/src/layout.d.ts +130 -0
  43. package/dist/src/layout.d.ts.map +1 -0
  44. package/dist/src/layout.js +149 -0
  45. package/dist/src/layout.js.map +1 -0
  46. package/dist/src/liveness.d.ts +48 -0
  47. package/dist/src/liveness.d.ts.map +1 -0
  48. package/dist/src/liveness.js +92 -0
  49. package/dist/src/liveness.js.map +1 -0
  50. package/dist/src/owner.d.ts +130 -0
  51. package/dist/src/owner.d.ts.map +1 -0
  52. package/dist/src/owner.js +368 -0
  53. package/dist/src/owner.js.map +1 -0
  54. package/dist/src/reader.d.ts +122 -0
  55. package/dist/src/reader.d.ts.map +1 -0
  56. package/dist/src/reader.js +325 -0
  57. package/dist/src/reader.js.map +1 -0
  58. package/dist/src/readers.d.ts +60 -0
  59. package/dist/src/readers.d.ts.map +1 -0
  60. package/dist/src/readers.js +122 -0
  61. package/dist/src/readers.js.map +1 -0
  62. package/dist/src/retained.d.ts +39 -0
  63. package/dist/src/retained.d.ts.map +1 -0
  64. package/dist/src/retained.js +99 -0
  65. package/dist/src/retained.js.map +1 -0
  66. package/dist/src/schema.d.ts +82 -0
  67. package/dist/src/schema.d.ts.map +1 -0
  68. package/dist/src/schema.js +18 -0
  69. package/dist/src/schema.js.map +1 -0
  70. package/dist/src/store.d.ts +83 -0
  71. package/dist/src/store.d.ts.map +1 -0
  72. package/dist/src/store.js +127 -0
  73. package/dist/src/store.js.map +1 -0
  74. package/dist/src/strings.d.ts +37 -0
  75. package/dist/src/strings.d.ts.map +1 -0
  76. package/dist/src/strings.js +144 -0
  77. package/dist/src/strings.js.map +1 -0
  78. package/dist/src/tags.d.ts +47 -0
  79. package/dist/src/tags.d.ts.map +1 -0
  80. package/dist/src/tags.js +53 -0
  81. package/dist/src/tags.js.map +1 -0
  82. package/dist/src/values.d.ts +90 -0
  83. package/dist/src/values.d.ts.map +1 -0
  84. package/dist/src/values.js +458 -0
  85. package/dist/src/values.js.map +1 -0
  86. package/dist/src/vector.d.ts +64 -0
  87. package/dist/src/vector.d.ts.map +1 -0
  88. package/dist/src/vector.js +387 -0
  89. package/dist/src/vector.js.map +1 -0
  90. package/dist/src/verify.d.ts +38 -0
  91. package/dist/src/verify.d.ts.map +1 -0
  92. package/dist/src/verify.js +179 -0
  93. package/dist/src/verify.js.map +1 -0
  94. package/dist/src/view.d.ts +66 -0
  95. package/dist/src/view.d.ts.map +1 -0
  96. package/dist/src/view.js +278 -0
  97. package/dist/src/view.js.map +1 -0
  98. package/package.json +28 -0
@@ -0,0 +1,99 @@
1
+ import { Header, RETAINED_SLOT_WORDS, RetainedSlot, RetainedState, WORD, } from "./layout.js";
2
+ /**
3
+ * The retained version ring.
4
+ *
5
+ * The owner appends one entry per commit. Readers only read it, and they read it for one
6
+ * reason: to prove that the version they are pinned to is still live before they decode
7
+ * anything through it.
8
+ *
9
+ * The ring is bounded on purpose. A reader that stops advancing cannot pin memory forever,
10
+ * because once the ring wraps the owner force reclaims the oldest entry and that reader
11
+ * fails closed on its next decode. Unbounded retention would be the alternative, and it
12
+ * turns one frozen window into an out of memory crash for the whole application.
13
+ */
14
+ export class RetainedRing {
15
+ #arena;
16
+ constructor(arena) {
17
+ this.#arena = arena;
18
+ }
19
+ get capacity() {
20
+ return this.#arena.geometry.retainedCapacity;
21
+ }
22
+ #wordIndex(index, field) {
23
+ return (this.#arena.geometry.retainedRingOffset / WORD + index * RETAINED_SLOT_WORDS + field);
24
+ }
25
+ #indexFor(versionId) {
26
+ // Version ids start at 1 and never wrap in practice: at one commit per millisecond a
27
+ // 32 bit counter lasts 24 days, and the owner refuses to commit past the limit rather
28
+ // than wrapping into ambiguity.
29
+ return (versionId - 1) % this.capacity;
30
+ }
31
+ /** Owner only. Publish a version into the ring, overwriting the entry it wraps onto. */
32
+ publish(versionId, rootTag, rootPayload) {
33
+ const words = this.#arena.words;
34
+ const index = this.#indexFor(versionId);
35
+ // Order matters: clear the state first so a reader cannot observe a new version id
36
+ // beside the previous entry's root.
37
+ Atomics.store(words, this.#wordIndex(index, RetainedSlot.State), RetainedState.Empty);
38
+ Atomics.store(words, this.#wordIndex(index, RetainedSlot.RootTag), rootTag);
39
+ Atomics.store(words, this.#wordIndex(index, RetainedSlot.RootPayload), rootPayload);
40
+ Atomics.store(words, this.#wordIndex(index, RetainedSlot.VersionId), versionId);
41
+ Atomics.store(words, this.#wordIndex(index, RetainedSlot.State), RetainedState.Live);
42
+ }
43
+ /** Owner only. Mark a version reclaimed. `forced` records that a reader was still pinned. */
44
+ retire(versionId, forced) {
45
+ const index = this.#indexFor(versionId);
46
+ const words = this.#arena.words;
47
+ if (Atomics.load(words, this.#wordIndex(index, RetainedSlot.VersionId)) !== versionId)
48
+ return;
49
+ Atomics.store(words, this.#wordIndex(index, RetainedSlot.State), forced ? RetainedState.ForceReclaimed : RetainedState.Reclaimed);
50
+ }
51
+ /**
52
+ * The check every reader runs after publishing its epoch. Returns true only when the
53
+ * version is still live in the ring, which proves the owner has not reclaimed the memory
54
+ * beneath it.
55
+ */
56
+ isLive(versionId) {
57
+ if (versionId <= 0)
58
+ return false;
59
+ const words = this.#arena.words;
60
+ const index = this.#indexFor(versionId);
61
+ return (Atomics.load(words, this.#wordIndex(index, RetainedSlot.VersionId)) === versionId &&
62
+ Atomics.load(words, this.#wordIndex(index, RetainedSlot.State)) === RetainedState.Live);
63
+ }
64
+ read(versionId) {
65
+ const words = this.#arena.words;
66
+ const index = this.#indexFor(versionId);
67
+ if (Atomics.load(words, this.#wordIndex(index, RetainedSlot.VersionId)) !== versionId) {
68
+ return undefined;
69
+ }
70
+ return {
71
+ versionId,
72
+ rootTag: Atomics.load(words, this.#wordIndex(index, RetainedSlot.RootTag)),
73
+ rootPayload: Atomics.load(words, this.#wordIndex(index, RetainedSlot.RootPayload)),
74
+ state: Atomics.load(words, this.#wordIndex(index, RetainedSlot.State)),
75
+ };
76
+ }
77
+ /** Live versions, oldest first. Diagnostics and the debug panel use this. */
78
+ live() {
79
+ const words = this.#arena.words;
80
+ const out = [];
81
+ for (let index = 0; index < this.capacity; index += 1) {
82
+ if (Atomics.load(words, this.#wordIndex(index, RetainedSlot.State)) !== RetainedState.Live) {
83
+ continue;
84
+ }
85
+ out.push({
86
+ versionId: Atomics.load(words, this.#wordIndex(index, RetainedSlot.VersionId)),
87
+ rootTag: Atomics.load(words, this.#wordIndex(index, RetainedSlot.RootTag)),
88
+ rootPayload: Atomics.load(words, this.#wordIndex(index, RetainedSlot.RootPayload)),
89
+ state: RetainedState.Live,
90
+ });
91
+ }
92
+ out.sort((a, b) => a.versionId - b.versionId);
93
+ return out;
94
+ }
95
+ reclaimFloor() {
96
+ return this.#arena.loadHeader(Header.ReclaimFloor);
97
+ }
98
+ }
99
+ //# sourceMappingURL=retained.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retained.js","sourceRoot":"","sources":["../../src/retained.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import type { Snapshot } from "./reader.js";
2
+ /**
3
+ * Types.
4
+ *
5
+ * Two jobs, and the second one matters more than the first.
6
+ *
7
+ * The first is inference: declare the shape once and get typed reads without casting at
8
+ * every call site.
9
+ *
10
+ * The second is making the contract visible in the types. A synchronous read and an
11
+ * asynchronous write must not look alike, because the entire class of bug this library
12
+ * invites is reading straight after writing and expecting the new value. So a write returns
13
+ * a promise, always, even where an implementation could resolve it immediately.
14
+ */
15
+ /** The values the type ladder can encode. Anything else needs the asynchronous tier. */
16
+ export type Encodable = string | number | boolean | null | undefined | bigint | Date | RegExp | Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array | Float32Array | Float64Array | Encodable[] | {
17
+ [key: string]: Encodable;
18
+ } | Map<string | number | boolean | null, Encodable> | Set<string | number | boolean | null>;
19
+ /** A state shape this library can hold. */
20
+ export type StateShape = {
21
+ [key: string]: Encodable;
22
+ };
23
+ /** Deeply readonly, which is what a snapshot is. */
24
+ export type Immutable<T> = T extends (infer Element)[] ? readonly Immutable<Element>[] : T extends Map<infer K, infer V> ? ReadonlyMap<K, Immutable<V>> : T extends Set<infer M> ? ReadonlySet<M> : T extends Date | RegExp | ArrayBufferView ? T : T extends object ? {
25
+ readonly [K in keyof T]: Immutable<T[K]>;
26
+ } : T;
27
+ /** Mutable, which is what a draft is. */
28
+ export type Draft<T> = T extends (infer Element)[] ? Draft<Element>[] : T extends Map<infer K, infer V> ? Map<K, Draft<V>> : T extends Set<infer M> ? Set<M> : T extends Date | RegExp | ArrayBufferView ? T : T extends object ? {
29
+ -readonly [K in keyof T]: Draft<T[K]>;
30
+ } : T;
31
+ /**
32
+ * A typed read side store.
33
+ *
34
+ * Every method here is synchronous, and there is no method that writes. A window holding one
35
+ * of these cannot write by accident, because there is nothing to call.
36
+ */
37
+ export interface TypedReadableStore<State extends StateShape> {
38
+ get(): Immutable<State>;
39
+ select<T>(path: readonly (string | number)[]): T | undefined;
40
+ snapshot(): Snapshot;
41
+ subscribe(listener: () => void): () => void;
42
+ readonly version: number;
43
+ }
44
+ /**
45
+ * A typed write side store.
46
+ *
47
+ * `update` and `set` return promises. That is not an implementation detail leaking into the
48
+ * type: it is the contract. A write is serialised by the owner, so the value is not
49
+ * observable on the next line, and a signature that returned a version synchronously would
50
+ * be a lie that costs somebody a day.
51
+ */
52
+ export interface TypedWritableStore<State extends StateShape> extends TypedReadableStore<State> {
53
+ update(recipe: (draft: Draft<State>) => void): Promise<number>;
54
+ set(value: State): Promise<number>;
55
+ }
56
+ /**
57
+ * A named write, as a window sends it.
58
+ *
59
+ * Functions cannot cross a process boundary, so a window sends the name of an operation and
60
+ * a payload. Declaring the operations as a type gives the dispatch call site the same
61
+ * checking a direct call would have had.
62
+ */
63
+ export type OperationMap<State extends StateShape> = Record<string, (draft: Draft<State>, payload: never) => void>;
64
+ export type PayloadOf<Operations extends OperationMap<StateShape>, Name extends keyof Operations> = Operations[Name] extends (draft: never, payload: infer Payload) => void ? Payload : never;
65
+ /** A dispatcher typed against the operations the owner registered. */
66
+ export interface TypedDispatcher<Operations extends OperationMap<StateShape>> {
67
+ <Name extends keyof Operations & string>(operation: Name, payload: PayloadOf<Operations, Name>): Promise<number>;
68
+ }
69
+ /**
70
+ * Declare a schema.
71
+ *
72
+ * Purely a type level helper: it returns its argument and exists so a state shape and its
73
+ * operations can be declared in one place and referenced by type everywhere else.
74
+ *
75
+ * const schema = defineSchema<AppState>()({
76
+ * increment: (draft, payload: { by: number }) => { draft.count += payload.by; },
77
+ * });
78
+ *
79
+ * type Dispatch = TypedDispatcher<typeof schema>;
80
+ */
81
+ export declare function defineSchema<State extends StateShape>(): <Operations extends OperationMap<State>>(definitions: Operations) => Operations;
82
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Declare a schema.
3
+ *
4
+ * Purely a type level helper: it returns its argument and exists so a state shape and its
5
+ * operations can be declared in one place and referenced by type everywhere else.
6
+ *
7
+ * const schema = defineSchema<AppState>()({
8
+ * increment: (draft, payload: { by: number }) => { draft.count += payload.by; },
9
+ * });
10
+ *
11
+ * type Dispatch = TypedDispatcher<typeof schema>;
12
+ */
13
+ export function defineSchema() {
14
+ return function operations(definitions) {
15
+ return definitions;
16
+ };
17
+ }
18
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/schema.ts"],"names":[],"mappings":"AAwHA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,SAAS,UAAU,CACxB,WAAuB;QAEvB,OAAO,WAAW,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,83 @@
1
+ import { ArenaOwner, type OwnerOptions, type OwnerStats } from "./owner.js";
2
+ import { ArenaReader, type ReaderOptions, type Snapshot } from "./reader.js";
3
+ /**
4
+ * The pair of interfaces the framework bindings and the Electron integration are written
5
+ * against.
6
+ *
7
+ * Splitting reading from writing in the type system is the point. A window holds a
8
+ * `ReadableStore`, which has no way to write, and the owner holds a `WritableStore`. Code
9
+ * that wants to write has to say so, and the asynchrony is in the return type where the
10
+ * caller can see it.
11
+ */
12
+ export interface ReadableStore {
13
+ /** The current committed value, synchronously. No await, no round trip. */
14
+ get(): unknown;
15
+ /** Read one path without materialising the nodes along it. */
16
+ select(path: readonly (string | number)[]): unknown;
17
+ /** Pin the current version. Release it when you are finished, or hold it for a render. */
18
+ snapshot(): Snapshot;
19
+ /** Called after every commit. Returns an unsubscribe function. */
20
+ subscribe(listener: () => void): () => void;
21
+ /** The version currently published. */
22
+ readonly version: number;
23
+ }
24
+ export interface WritableStore extends ReadableStore {
25
+ /**
26
+ * Apply a recipe to a draft and commit the result.
27
+ *
28
+ * The promise resolves once the write is committed and observable. Reading before it
29
+ * resolves may return the old value, which is the contract rather than a defect.
30
+ */
31
+ update<T>(recipe: (draft: T) => void): Promise<number>;
32
+ /** Replace the root outright. Asynchronous for the same reason update is. */
33
+ set(value: unknown): Promise<number>;
34
+ }
35
+ /**
36
+ * The owner side store.
37
+ *
38
+ * Writes are queued and applied in order, and the promise resolves after the commit is
39
+ * published. Queuing rather than applying inline means a recipe cannot observe a half
40
+ * applied earlier write, and it makes the total order the contract promises explicit rather
41
+ * than incidental.
42
+ */
43
+ export declare class OwnerStore implements WritableStore {
44
+ #private;
45
+ readonly owner: ArenaOwner;
46
+ private constructor();
47
+ static create(initial: unknown, options?: OwnerOptions): OwnerStore;
48
+ get buffer(): SharedArrayBuffer;
49
+ get version(): number;
50
+ get(): unknown;
51
+ select(path: readonly (string | number)[]): unknown;
52
+ snapshot(): Snapshot;
53
+ update<T>(recipe: (draft: T) => void): Promise<number>;
54
+ set(value: unknown): Promise<number>;
55
+ subscribe(listener: () => void): () => void;
56
+ close(): void;
57
+ }
58
+ /**
59
+ * The reader side store.
60
+ *
61
+ * It has no write method at all. A window that needs to write does it through the intent
62
+ * channel the integration supplies, which is asynchronous by construction.
63
+ */
64
+ export declare class ReaderStore implements ReadableStore {
65
+ #private;
66
+ readonly reader: ArenaReader;
67
+ constructor(buffer: SharedArrayBuffer, options?: ReaderOptions);
68
+ get version(): number;
69
+ get(): unknown;
70
+ select(path: readonly (string | number)[]): unknown;
71
+ snapshot(): Snapshot;
72
+ subscribe(listener: () => void): () => void;
73
+ /**
74
+ * Tell the store a commit happened.
75
+ *
76
+ * The integration calls this when the owner notifies it. Deliberately not a poll: nothing
77
+ * on the read path may depend on a message arriving, so this only drives rerenders.
78
+ */
79
+ notify(): void;
80
+ close(): void;
81
+ }
82
+ export type { OwnerStats, Snapshot };
83
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE7E;;;;;;;;GAQG;AAEH,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,GAAG,IAAI,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAAC;IACpD,0FAA0F;IAC1F,QAAQ,IAAI,QAAQ,CAAC;IACrB,kEAAkE;IAClE,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAc,SAAQ,aAAa;IAClD;;;;;OAKG;IACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,6EAA6E;IAC7E,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,qBAAa,UAAW,YAAW,aAAa;;IAC9C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAI3B,OAAO,eAEN;IAED,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,YAAiB,GAAG,UAAU,CAItE;IAED,IAAI,MAAM,IAAI,iBAAiB,CAE9B;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,GAAG,IAAI,OAAO,CAEb;IAED,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAElD;IAED,QAAQ,IAAI,QAAQ,CAEnB;IAaD,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAErD;IAED,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnC;IAED,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG1C;IAkBD,KAAK,IAAI,IAAI,CAIZ;CACF;AAED;;;;;GAKG;AACH,qBAAa,WAAY,YAAW,aAAa;;IAC/C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAI7B,YAAY,MAAM,EAAE,iBAAiB,EAAE,OAAO,GAAE,aAAkB,EAGjE;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,GAAG,IAAI,OAAO,CAEb;IAED,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,OAAO,CAElD;IAED,QAAQ,IAAI,QAAQ,CAEnB;IAED,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG1C;IAED;;;;;OAKG;IACH,MAAM,IAAI,IAAI,CAKb;IAED,KAAK,IAAI,IAAI,CAGZ;CACF;AAED,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,127 @@
1
+ import { ArenaOwner } from "./owner.js";
2
+ import { ArenaReader } from "./reader.js";
3
+ /**
4
+ * The owner side store.
5
+ *
6
+ * Writes are queued and applied in order, and the promise resolves after the commit is
7
+ * published. Queuing rather than applying inline means a recipe cannot observe a half
8
+ * applied earlier write, and it makes the total order the contract promises explicit rather
9
+ * than incidental.
10
+ */
11
+ export class OwnerStore {
12
+ owner;
13
+ #listeners = new Set();
14
+ #queue = Promise.resolve();
15
+ constructor(owner) {
16
+ this.owner = owner;
17
+ }
18
+ static create(initial, options = {}) {
19
+ const owner = ArenaOwner.create(options);
20
+ owner.commit(initial);
21
+ return new OwnerStore(owner);
22
+ }
23
+ get buffer() {
24
+ return this.owner.buffer;
25
+ }
26
+ get version() {
27
+ return this.owner.versionId;
28
+ }
29
+ get() {
30
+ return this.snapshot().value;
31
+ }
32
+ select(path) {
33
+ return this.snapshot().get(path);
34
+ }
35
+ snapshot() {
36
+ return this.#reader().acquire();
37
+ }
38
+ #ownReader;
39
+ #reader() {
40
+ if (this.#ownReader === undefined) {
41
+ // The owner reads through an ordinary reader, on the same code path every window uses.
42
+ // A separate owner only read path would be a second implementation to keep correct.
43
+ this.#ownReader = ArenaReader.attach(this.owner.buffer);
44
+ }
45
+ return this.#ownReader;
46
+ }
47
+ update(recipe) {
48
+ return this.#enqueue(() => this.owner.update(recipe));
49
+ }
50
+ set(value) {
51
+ return this.#enqueue(() => this.owner.commit(value));
52
+ }
53
+ subscribe(listener) {
54
+ this.#listeners.add(listener);
55
+ return () => this.#listeners.delete(listener);
56
+ }
57
+ #enqueue(work) {
58
+ const result = this.#queue.then(() => {
59
+ const version = work();
60
+ this.#notify();
61
+ return version;
62
+ });
63
+ // Keep the chain alive after a rejection, or one failed write would stall every later
64
+ // one behind it.
65
+ this.#queue = result.catch(() => undefined);
66
+ return result;
67
+ }
68
+ #notify() {
69
+ for (const listener of this.#listeners)
70
+ listener();
71
+ }
72
+ close() {
73
+ this.#ownReader?.detach();
74
+ this.#ownReader = undefined;
75
+ this.#listeners.clear();
76
+ }
77
+ }
78
+ /**
79
+ * The reader side store.
80
+ *
81
+ * It has no write method at all. A window that needs to write does it through the intent
82
+ * channel the integration supplies, which is asynchronous by construction.
83
+ */
84
+ export class ReaderStore {
85
+ reader;
86
+ #listeners = new Set();
87
+ #lastSeen;
88
+ constructor(buffer, options = {}) {
89
+ this.reader = ArenaReader.attach(buffer, options);
90
+ this.#lastSeen = this.reader.publishedVersion();
91
+ }
92
+ get version() {
93
+ return this.reader.publishedVersion();
94
+ }
95
+ get() {
96
+ return this.reader.acquire().value;
97
+ }
98
+ select(path) {
99
+ return this.reader.acquire().get(path);
100
+ }
101
+ snapshot() {
102
+ return this.reader.acquire();
103
+ }
104
+ subscribe(listener) {
105
+ this.#listeners.add(listener);
106
+ return () => this.#listeners.delete(listener);
107
+ }
108
+ /**
109
+ * Tell the store a commit happened.
110
+ *
111
+ * The integration calls this when the owner notifies it. Deliberately not a poll: nothing
112
+ * on the read path may depend on a message arriving, so this only drives rerenders.
113
+ */
114
+ notify() {
115
+ const version = this.reader.publishedVersion();
116
+ if (version === this.#lastSeen)
117
+ return;
118
+ this.#lastSeen = version;
119
+ for (const listener of this.#listeners)
120
+ listener();
121
+ }
122
+ close() {
123
+ this.reader.detach();
124
+ this.#listeners.clear();
125
+ }
126
+ }
127
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { SharedArena } from "./arena.js";
2
+ import type { Allocator } from "./allocator.js";
3
+ export declare function stringRecordBytes(length: number): number;
4
+ /** Owner side. Holds the intern map, which never leaves the writer process. */
5
+ export declare class StringTable {
6
+ #private;
7
+ constructor(arena: SharedArena, allocator: Allocator);
8
+ get size(): number;
9
+ /**
10
+ * Return the offset of `text`, writing it into the arena the first time it is seen.
11
+ *
12
+ * When a journal is supplied, strings this call newly interned are recorded on it. A commit
13
+ * that fails part way passes the journal to `forget` so the records it created are
14
+ * released. Without that, a rejected write would consume arena permanently, and a window
15
+ * that could request writes could exhaust the arena with rejected ones.
16
+ */
17
+ intern(text: string, journal?: string[]): number;
18
+ /**
19
+ * Release strings interned during a commit that failed.
20
+ *
21
+ * Safe only for strings no published version references, which is exactly what the journal
22
+ * records: entries this commit created and no earlier one did.
23
+ */
24
+ forget(texts: readonly string[]): void;
25
+ /** Bytes held by interned strings. Reported by the soak harness. */
26
+ byteSize(): number;
27
+ }
28
+ /**
29
+ * Decode a string record. Every field is validated before it is used, so a wild offset
30
+ * produces an ArenaCorruptError rather than a very long string built from arbitrary bytes.
31
+ */
32
+ export declare function decodeString(arena: SharedArena, offset: number): string;
33
+ /** The stored hash, for callers comparing keys without materialising the string. */
34
+ export declare function stringHash(arena: SharedArena, offset: number): number;
35
+ /** Compare an interned record against a JavaScript string without allocating. */
36
+ export declare function stringEquals(arena: SharedArena, offset: number, text: string): boolean;
37
+ //# sourceMappingURL=strings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/strings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AA8BhD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,+EAA+E;AAC/E,qBAAa,WAAW;;IAMtB,YAAY,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAGnD;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAmB/C;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAOrC;IAED,oEAAoE;IACpE,QAAQ,IAAI,MAAM,CAIjB;CACF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA0BvE;AAED,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAiBtF"}
@@ -0,0 +1,144 @@
1
+ import { ArenaCorruptError } from "./errors.js";
2
+ import { WORD, align } from "./layout.js";
3
+ import { hashString } from "./checksum.js";
4
+ /**
5
+ * Interned strings.
6
+ *
7
+ * Record layout, at the payload offset returned by the allocator:
8
+ *
9
+ * word 0 hash, so a comparison can reject in one integer compare
10
+ * word 1 length in UTF-16 code units
11
+ * word 2.. the code units, packed two per word
12
+ *
13
+ * Strings are stored as UTF-16 rather than UTF-8 because JavaScript strings are UTF-16 and
14
+ * the decode path is the hot one. Encoding to UTF-8 would halve the size of ASCII text and
15
+ * add a conversion to every read, which is the wrong trade for this library.
16
+ *
17
+ * The table is append only. A string is never freed while the arena lives, which is the
18
+ * documented cost of interning: equal keys share one record and comparison is an integer
19
+ * compare, at the price of holding every distinct string ever written. Workloads that churn
20
+ * through unbounded distinct strings should keep them in the asynchronous tier. Compaction
21
+ * is a phase 5 question, and the soak harness reports the table size so the decision has
22
+ * data behind it.
23
+ */
24
+ const HEADER_WORDS = 2;
25
+ const HASH_WORD = 0;
26
+ const LENGTH_WORD = 1;
27
+ export function stringRecordBytes(length) {
28
+ return align(HEADER_WORDS * WORD + length * 2);
29
+ }
30
+ /** Owner side. Holds the intern map, which never leaves the writer process. */
31
+ export class StringTable {
32
+ #arena;
33
+ #allocator;
34
+ /** Interning map, keyed by the string itself. Only the owner has it. */
35
+ #interned = new Map();
36
+ constructor(arena, allocator) {
37
+ this.#arena = arena;
38
+ this.#allocator = allocator;
39
+ }
40
+ get size() {
41
+ return this.#interned.size;
42
+ }
43
+ /**
44
+ * Return the offset of `text`, writing it into the arena the first time it is seen.
45
+ *
46
+ * When a journal is supplied, strings this call newly interned are recorded on it. A commit
47
+ * that fails part way passes the journal to `forget` so the records it created are
48
+ * released. Without that, a rejected write would consume arena permanently, and a window
49
+ * that could request writes could exhaust the arena with rejected ones.
50
+ */
51
+ intern(text, journal) {
52
+ const existing = this.#interned.get(text);
53
+ if (existing !== undefined)
54
+ return existing;
55
+ journal?.push(text);
56
+ const offset = this.#allocator.allocate(stringRecordBytes(text.length));
57
+ const words = this.#arena.words;
58
+ const base = offset / WORD;
59
+ words[base + HASH_WORD] = hashString(text);
60
+ words[base + LENGTH_WORD] = text.length;
61
+ const units = this.#arena.units;
62
+ const unitBase = (offset + HEADER_WORDS * WORD) / 2;
63
+ for (let i = 0; i < text.length; i += 1) {
64
+ units[unitBase + i] = text.charCodeAt(i);
65
+ }
66
+ this.#interned.set(text, offset);
67
+ return offset;
68
+ }
69
+ /**
70
+ * Release strings interned during a commit that failed.
71
+ *
72
+ * Safe only for strings no published version references, which is exactly what the journal
73
+ * records: entries this commit created and no earlier one did.
74
+ */
75
+ forget(texts) {
76
+ for (const text of texts) {
77
+ const offset = this.#interned.get(text);
78
+ if (offset === undefined)
79
+ continue;
80
+ this.#interned.delete(text);
81
+ this.#allocator.free(offset);
82
+ }
83
+ }
84
+ /** Bytes held by interned strings. Reported by the soak harness. */
85
+ byteSize() {
86
+ let total = 0;
87
+ for (const text of this.#interned.keys())
88
+ total += stringRecordBytes(text.length);
89
+ return total;
90
+ }
91
+ }
92
+ /**
93
+ * Decode a string record. Every field is validated before it is used, so a wild offset
94
+ * produces an ArenaCorruptError rather than a very long string built from arbitrary bytes.
95
+ */
96
+ export function decodeString(arena, offset) {
97
+ const byteSize = arena.checkBlock(offset, "string");
98
+ const words = arena.words;
99
+ const base = offset / WORD;
100
+ const length = words[base + LENGTH_WORD];
101
+ if (length < 0 || stringRecordBytes(length) > byteSize) {
102
+ throw new ArenaCorruptError(`string at ${offset} claims ${length} code units, which does not fit in ${byteSize} bytes`, { offset, actual: length });
103
+ }
104
+ const units = arena.units;
105
+ const unitBase = (offset + HEADER_WORDS * WORD) / 2;
106
+ // fromCharCode with a subarray is measurably faster than a loop for typical key lengths,
107
+ // and the chunking keeps the argument count away from the engine limit for long text.
108
+ if (length <= 2048) {
109
+ return String.fromCharCode(...units.subarray(unitBase, unitBase + length));
110
+ }
111
+ let out = "";
112
+ for (let start = 0; start < length; start += 2048) {
113
+ const end = Math.min(start + 2048, length);
114
+ out += String.fromCharCode(...units.subarray(unitBase + start, unitBase + end));
115
+ }
116
+ return out;
117
+ }
118
+ /** The stored hash, for callers comparing keys without materialising the string. */
119
+ export function stringHash(arena, offset) {
120
+ arena.checkBlock(offset, "string hash");
121
+ return arena.words[offset / WORD + HASH_WORD];
122
+ }
123
+ /** Compare an interned record against a JavaScript string without allocating. */
124
+ export function stringEquals(arena, offset, text) {
125
+ const byteSize = arena.checkBlock(offset, "string compare");
126
+ const words = arena.words;
127
+ const base = offset / WORD;
128
+ const length = words[base + LENGTH_WORD];
129
+ if (length !== text.length)
130
+ return false;
131
+ if (stringRecordBytes(length) > byteSize) {
132
+ throw new ArenaCorruptError(`string at ${offset} has an impossible length`, { offset });
133
+ }
134
+ if (words[base + HASH_WORD] !== hashString(text))
135
+ return false;
136
+ const units = arena.units;
137
+ const unitBase = (offset + HEADER_WORDS * WORD) / 2;
138
+ for (let i = 0; i < length; i += 1) {
139
+ if (units[unitBase + i] !== text.charCodeAt(i))
140
+ return false;
141
+ }
142
+ return true;
143
+ }
144
+ //# sourceMappingURL=strings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings.js","sourceRoot":"","sources":["../../src/strings.ts"],"names":[],"mappings":"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"}