@volynets/reflex-store 0.0.0-stage → 0.0.2

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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +237 -2
  3. package/SPECIFICATION.md +137 -0
  4. package/dist/advanced.d.ts +12 -0
  5. package/dist/advanced.js +10 -0
  6. package/dist/chunks/collections-yHy0wH0f.js +268 -0
  7. package/dist/chunks/compiler-Dsb3E2ck.js +2516 -0
  8. package/dist/chunks/createStore-DHj16Ehk.js +12 -0
  9. package/dist/chunks/hydrate-DMnHCI5q.js +138 -0
  10. package/dist/chunks/keyed-CQKtL8a7.js +383 -0
  11. package/dist/chunks/names-Cx4YcvsH.js +11 -0
  12. package/dist/chunks/values-DCB6zyMz.js +142 -0
  13. package/dist/compiled-store.d.ts +4 -0
  14. package/dist/compiled-store.js +4 -0
  15. package/dist/compiler/swc.wasm +0 -0
  16. package/dist/index.d.ts +23 -0
  17. package/dist/index.js +8 -0
  18. package/dist/licenses/NOTICE.txt +1 -0
  19. package/dist/licenses/SWC-APACHE-2.0.txt +202 -0
  20. package/dist/runtime/core.d.ts +2 -0
  21. package/dist/runtime/core.js +1 -0
  22. package/dist/runtime/internal.d.ts +3 -0
  23. package/dist/runtime/internal.js +41 -0
  24. package/dist/runtime.d.ts +29 -0
  25. package/dist/runtime.js +88 -0
  26. package/dist/selectors/index.d.ts +13 -0
  27. package/dist/selectors/index.js +16 -0
  28. package/dist/selectors/shared.d.ts +3 -0
  29. package/dist/selectors/shared.js +64 -0
  30. package/dist/store.d.ts +9 -0
  31. package/dist/store.js +4 -0
  32. package/dist/types/contracts-BCsRPdQb.d.ts +78 -0
  33. package/dist/types/createStore-BJ66-pfq.d.ts +43 -0
  34. package/dist/types/internal-BIzJSPwN.d.ts +18 -0
  35. package/dist/types/keyed-CdSYzIOc.d.ts +18 -0
  36. package/dist/types/shared-CdTKzvLe.d.ts +114 -0
  37. package/dist/vite.d.ts +68 -0
  38. package/dist/vite.js +87 -0
  39. package/examples/task-board/README.md +15 -0
  40. package/examples/task-board/src/reactivity.ts +33 -0
  41. package/examples/task-board/src/task-board.ts +159 -0
  42. package/examples/task-board/vite.config.ts +6 -0
  43. package/package.json +119 -4
  44. package/src/store/README.md +59 -0
  45. package/src/store/TRANSFORM_SPEC.md +85 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Andrii Volynets
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,238 @@
1
- # Temporary Holding Version
1
+ # Reflex Store
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Structured state, derived views and keyed collections with a compiler for static
4
+ store shapes. The library distribution uses the shared reactive kernel and
5
+ lifecycle primitives. `@volynets/reflex-runtime` and
6
+ `@volynets/reflex-framework` are peer dependencies. Framework supplies the
7
+ shared lifecycle primitives, while Store, DOM and Async share the application's
8
+ kernel module identity.
9
+ The distribution includes the portable SWC WebAssembly compiler and Vite plugin.
10
+
11
+ ```sh
12
+ pnpm add @volynets/reflex-store @volynets/reflex-runtime@^1.0.1 @volynets/reflex-framework
13
+ ```
14
+
15
+ Requires Node.js 20.19 or newer for the compiler and Vite plugin. Published
16
+ entrypoints use ES modules. The compiler and its SWC WASM asset are included;
17
+ consumers do not need to install SWC separately.
18
+
19
+ ## Application API
20
+
21
+ Start with `createStore`, `derive`, `selector`, `reactiveMap`, `action` and
22
+ `snapshot`. Use `leaf`/`opaque` for reference boundaries and `hydrate` for
23
+ restoring compiled data.
24
+
25
+ ```ts
26
+ import {
27
+ createStore,
28
+ leaf,
29
+ derive,
30
+ selector,
31
+ reactiveMap,
32
+ action,
33
+ snapshot,
34
+ hydrate,
35
+ } from "@volynets/reflex-store";
36
+
37
+ export function createBoard() {
38
+ const board = createStore(
39
+ {
40
+ filter: { status: "all", query: "" },
41
+ selection: { taskId: "T-101" },
42
+ pinned: leaf<readonly string[]>([]),
43
+
44
+ get filterLabel() {
45
+ const query = this.filter.query.trim().toLowerCase();
46
+ return query ? this.filter.status + ":" + query : this.filter.status;
47
+ },
48
+
49
+ setFilter(status: string, query = "") {
50
+ this.filter.status = status;
51
+ this.filter.query = query;
52
+ },
53
+ },
54
+ { name: "Board" },
55
+ );
56
+
57
+ return board;
58
+ }
59
+
60
+ const board = createBoard();
61
+ const tasks = reactiveMap<string, { title: string; done: boolean }>();
62
+ const isSelected = selector(() => board.selection.taskId);
63
+ const summary = derive(
64
+ () => ({
65
+ total: tasks.size,
66
+ completed: [...tasks.values()].filter((task) => task.done).length,
67
+ }),
68
+ { name: "Summary" },
69
+ );
70
+
71
+ const addTasks = action(() => {
72
+ tasks.set("T-101", { title: "Design checkout", done: false });
73
+ tasks.set("T-102", { title: "Add audit log", done: true });
74
+ });
75
+
76
+ addTasks();
77
+ console.log(summary.completed); // 1; pulls current data immediately
78
+ const saved = snapshot(board); // frozen data; excludes methods and getters
79
+ hydrate(board, saved); // validates the entire shape, then writes in one action
80
+ ```
81
+
82
+ Every factory call creates independent state. Root methods become bound,
83
+ synchronous actions; getters become owned lazy computeds. Static data reads and
84
+ writes lower to direct cell calls. Getters must be pure and synchronous.
85
+
86
+ `derive` accepts a pure callback returning a plain object. Its property views
87
+ are read-only. Dynamic entity IDs belong in `reactiveMap`; map values are
88
+ reference boundaries, so replace an entry to publish an entity change.
89
+
90
+ `action` preserves callback parameters, return value and receiver. Related
91
+ writes run untracked inside one scheduler/runtime batch. Direct reads see writes
92
+ so far. Exceptions keep completed writes and close the batch; there is no rollback.
93
+
94
+ ## Compiler and Vite
95
+
96
+ `createStore` requires the transform. The runtime stub throws if it executes.
97
+
98
+ ```ts
99
+ import { defineConfig } from "vite";
100
+ import reflexStore from "@volynets/reflex-store/vite";
101
+
102
+ export default defineConfig({
103
+ plugins: [reflexStore({ eraseFacade: true })],
104
+ });
105
+ ```
106
+
107
+ The plugin transforms stores while preserving application package resolution.
108
+ `@volynets/reflex-store/runtime` exports the compiler's cell, scope and owned
109
+ computed primitives. `/runtime/core` and `/runtime/internal` re-export the shared
110
+ kernel. Application runtime setup and effect scheduling belong to the consumer;
111
+ the task-board example includes explicit kernel/scheduler wiring.
112
+
113
+ The standalone compiler is exported from `@volynets/reflex-store/store`.
114
+ It includes its portable WASM asset; installing a platform-specific native SWC
115
+ package is unnecessary. `eraseFacade` removes the object only when every use
116
+ lowers to data cells. Factories, methods, getters, disposal and data extraction
117
+ retain the required facade.
118
+
119
+ See [compiler rules](./src/store/TRANSFORM_SPEC.md).
120
+
121
+ ## Ownership
122
+
123
+ Compiled stores use `createStoreScope()` from `/runtime`. This thin compiler
124
+ target delegates ownership, cancellation and rollback to Framework's
125
+ `LifecycleScope`. It adds synchronous, untracked actions inside the host batch
126
+ and exposes disposal through `dispose()` and `Symbol.dispose`. Cells are created
127
+ and owned during setup; a failed initializer disposes earlier cells before
128
+ rethrowing its original error. Computed getter resources share the same lifetime.
129
+
130
+ Store compilation uses store scopes for ownership and disposal.
131
+
132
+ Use a store scope to dispose resources together:
133
+
134
+ ```ts
135
+ import {
136
+ createStoreScope,
137
+ createStoreCell,
138
+ } from "@volynets/reflex-store/runtime";
139
+ import { derive, reactiveMap, selector } from "@volynets/reflex-store";
140
+
141
+ const createPanel = () =>
142
+ createStoreScope((ctx) => {
143
+ const tasks = ctx.own(reactiveMap<string, { done: boolean }>());
144
+ const selectedId = ctx.own(createStoreCell(""));
145
+ return {
146
+ tasks,
147
+ selected: ctx.own(selector(selectedId)),
148
+ summary: ctx.own(derive(() => ({ total: tasks.size }))),
149
+ };
150
+ });
151
+ ```
152
+
153
+ Maps, sets, selectors, keyed projections, structured views and compiled stores
154
+ implement `Symbol.dispose`. They also support explicit terminal disposal. Compiled reads, methods, writes and
155
+ restoration reject use after disposal.
156
+ `collect` is available in the advanced API for long-lived structures with many
157
+ previously observed keys.
158
+
159
+ ## Reference and data boundaries
160
+
161
+ An object literal is a structural branch. `leaf(value)` is one replaceable
162
+ location. `opaque(resource)` retains an external reference.
163
+
164
+ ```ts
165
+ const state = createStore({
166
+ items: leaf<readonly Task[]>([]),
167
+ engine: opaque(new Engine()),
168
+ });
169
+ state.items = [...state.items, task];
170
+ ```
171
+
172
+ Compiled arrays use replacement semantics. Array mutation does not publish
173
+ fine-grained changes.
174
+
175
+ Snapshots copy and freeze plain structural data, preserving cycles and shared
176
+ references. Map/Set keys preserve identity. Opaque and foreign objects remain
177
+ external references. Applications define encoding/exclusion for JSON, SSR
178
+ persistence and external resources.
179
+
180
+ Compiled hydration requires the full exact data shape, including empty branches.
181
+ It rejects missing/extra fields and accessor properties before the first write,
182
+ clones structural leaf data, and restores all fields in one action. Schema
183
+ validation is structural; validate untrusted input value types in the application.
184
+
185
+ ## Advanced API
186
+
187
+ `@volynets/reflex-store/advanced` preserves projection controls, depth markers,
188
+ `raw`, `transaction`, lifecycle helpers and `createStoreCell`. It also exports:
189
+
190
+ ```ts
191
+ import { reactiveSet, getStoreName } from "@volynets/reflex-store/advanced";
192
+
193
+ const selectedIds = reactiveSet<string>([], { name: "Selected IDs" });
194
+ selectedIds.add("T-101");
195
+ console.log(getStoreName(selectedIds));
196
+ ```
197
+
198
+ `ReactiveSet` follows native Set membership and iteration semantics.
199
+ Resource names are optional store metadata and do not add runtime kernel hooks.
200
+ The existing draft and keyed projection APIs remain available here.
201
+
202
+ The [task board](./examples/task-board/README.md) demonstrates the basic API.
203
+ The normative contract is in [SPECIFICATION.md](./SPECIFICATION.md).
204
+
205
+ ## Checks
206
+
207
+ ```sh
208
+ pnpm --filter @volynets/reflex-store test
209
+ pnpm --filter @volynets/reflex-store test:dev
210
+ pnpm --filter @volynets/reflex-store typecheck
211
+ pnpm --filter @volynets/reflex-store lint
212
+ pnpm --filter @volynets/reflex-store test:packed-runtime
213
+ ```
214
+
215
+ The packed test installs the Store and its Reflex peer tarballs in an empty
216
+ offline project. It checks all published imports, WASM loading, one shared graph across entrypoints,
217
+ the Vite plugin, factory isolation, batching, snapshots/hydration and consumer
218
+ declarations with `skipLibCheck=false`.
219
+
220
+ ## Release preparation
221
+
222
+ Run from the repository root with workspace dependencies installed:
223
+
224
+ ```sh
225
+ pnpm --filter @volynets/reflex-store check:release
226
+ pnpm --dir packages/reflex-store pack --pack-destination ../../artifacts/npm
227
+ ```
228
+
229
+ `check:release` builds Store and its workspace dependencies, runs lint, production
230
+ and development tests, checks types and artifacts, and verifies an isolated
231
+ installation of the packed packages. `prepublishOnly` runs the same checks.
232
+ `prepack` rebuilds Store and checks its output before creating a tarball.
233
+
234
+ The package contains the MIT license and the Apache-2.0 license for the bundled
235
+ SWC compiler. Publish compatible versions of the required Reflex peers before
236
+ releasing Store. The repository release workflow uses `pnpm release:qualify`
237
+ and publishes the verified tarballs through `pnpm release:publish` with the
238
+ qualification and quality-summary paths required by that command.
@@ -0,0 +1,137 @@
1
+ # Reflex Store Specification
2
+
3
+ **Revision:** 2026-10-07
4
+ **Scope:** compiled static state and runtime structured views/collections.
5
+
6
+ ## Package boundary
7
+
8
+ The published package MUST declare no dependencies, peer dependencies or optional
9
+ dependencies. Runtime, host, scheduler, compiler and WASM assets MUST be included.
10
+ All runtime entrypoints MUST share the same kernel and host bindings.
11
+
12
+ The basic root exports createStore, derive, selector, reactiveMap, action,
13
+ snapshot, leaf, opaque and hydrate. Advanced projections, Set, lifecycle controls,
14
+ names and compiler primitives belong to /advanced. Headless host APIs belong to
15
+ /runtime; compiler and Vite entrypoints belong to /store and /vite.
16
+
17
+ Kernel read/write/link/unlink code MUST remain independent from store ownership,
18
+ names and compiler metadata. This package makes no kernel source changes.
19
+
20
+ ## Static stores
21
+
22
+ createStore is compile-only and MUST throw if its source stub executes.
23
+ Declarations may appear at module scope or inside lexical function/block bodies.
24
+ Binding identity MUST distinguish aliases, shadowing and separate factory calls.
25
+ Loop-header declarations and arbitrary inline factories remain unsupported.
26
+
27
+ A literal object property defines a structural branch. Other accepted initial
28
+ values define leaves. leaf(value) forces one replaceable leaf. opaque(value)
29
+ retains an external resource/reference boundary. Marker aliases MUST resolve by
30
+ import binding identity.
31
+
32
+ Root methods become synchronous bound actions. Their this references to the
33
+ store are lowered to the same static locations; nested ordinary functions
34
+ retain their own this. Async/generator store methods are rejected.
35
+ Root getters become lazy disposable computeds. They MUST be pure, synchronous,
36
+ memoize clean reads and validate dependencies on pull. Store writes in getters
37
+ are rejected. Setters and nested methods/getters remain unsupported.
38
+
39
+ Static leaf reads/writes lower to callable cells and action writers. Data cells
40
+ create their producer only on the first tracked read. Initializers MUST run once
41
+ per declaration execution. Compound assignment and update lowering MUST preserve
42
+ JavaScript evaluation order, single RHS evaluation, ToNumeric and exceptions.
43
+
44
+ Dynamic root paths, branch aliases, structural replacement, delete, optional
45
+ chaining and general root reflection are diagnosed. Returning a facade from a
46
+ factory and snapshot/hydrate data intrinsics are supported escape boundaries.
47
+
48
+ Optional static name options label the store and its cells. Optional eraseFacade
49
+ may remove a facade only when every use lowers directly to declared data leaves.
50
+ Escapes, methods, getters and lifecycle references MUST keep it.
51
+
52
+ ## Collections
53
+
54
+ ReactiveMap uses native SameValueZero keys and Object.is value equality by
55
+ default. get, has, size, key iteration and value iteration observe different
56
+ semantic locations. Replacing an existing value MUST not invalidate key-only
57
+ consumers. Untracked lookups MUST not allocate key observation producers.
58
+
59
+ ReactiveSet follows native Set membership, insertion order, deletion, clear,
60
+ iteration and forEach semantics. It uses keyed membership observations.
61
+ No-op add/delete operations MUST not notify consumers.
62
+
63
+ Collection values are reference boundaries. In-place mutation of an entity or
64
+ array is not a reactive publication. Optional initializers materialize backing
65
+ data lazily. Reads after terminal disposal MUST fail.
66
+
67
+ ## Derivations and selectors
68
+
69
+ derive accepts a pure-return callback producing a plain object. Structured
70
+ projections use lazy Demand and semantic path observations; compatible plain
71
+ objects and arrays are traversed by default. Foreign/ref/opaque values retain
72
+ reference semantics. Returned structured views are read-only.
73
+
74
+ Selectors route old/new semantic keys with configurable equality and preserve
75
+ Object.is behavior for signed zero/NaN. Keyed projections represent the active
76
+ source key; they are not arbitrary entity lookup. ReactiveMap provides that lookup.
77
+
78
+ Advanced draft projections, clone/equality/depth controls and keyed projections
79
+ remain available. Clean reads MUST memoize and dirty reads MUST pull fresh data
80
+ without waiting for an effect flush. Equal projected results MUST cut off
81
+ downstream user computation.
82
+
83
+ ## Actions and delivery
84
+
85
+ action runs the synchronous callback untracked within one scheduler and reactive
86
+ batch. Nested actions compose. The callback receiver, arguments and return value
87
+ MUST be preserved. Reads inside an action see writes so far. Completed writes
88
+ survive exceptions; all batch boundaries MUST close.
89
+
90
+ The active host controls flush/eager/SAB delivery. Runtime and scheduler batching
91
+ MUST compose so one logical multi-write action never publishes an intermediate
92
+ state to eager effects.
93
+
94
+ ## Extraction, hydration and SSR
95
+
96
+ snapshot is untracked. Compiled stores expose statically generated data extraction
97
+ callbacks, including nested/empty branches and excluding methods/getters.
98
+ Plain snapshots MUST be independent and frozen, preserving cycles, shared
99
+ references, symbols, enumerable descriptors and sparse array structure.
100
+ Map and Set representations preserve key identity and disable public mutators.
101
+
102
+ Opaque/foreign values remain external references. No arbitrary JSON or
103
+ structured-clone guarantee is made. Applications define serialization policies
104
+ for cycles, BigInt, symbols, functions, external resources and Map/Set keys.
105
+
106
+ hydrate requires a complete compiled data schema. It MUST validate all structural
107
+ fields before writing: exact branch keys, required fields and own data-property
108
+ descriptors. Structural values MUST be cloned with shared references preserved.
109
+ All writes MUST occur in one logical batch. Field value types are checked by
110
+ TypeScript/application validation, not inferred from initial primitive values.
111
+
112
+ Each SSR request creates its own store factory instance. Snapshot/restore operate
113
+ on those instances and never require module-global application state.
114
+
115
+ ## Ownership and diagnostic names
116
+
117
+ Maps, Sets, selectors, keyed projections, structured projection views and compiled
118
+ stores MUST implement Symbol.dispose and work with own(ctx, resource).
119
+ Disposal is terminal and idempotent. Compiled data reads/writes, getters, methods,
120
+ extraction and hydration MUST reject use after disposal. Collecting unused observations MUST preserve
121
+ nodes that still have outgoing consumer links. Owners stop consumers before
122
+ collection and collect derived layers from downstream to upstream.
123
+
124
+ Names are optional resource metadata exposed through getStoreName. Looking up a
125
+ name MUST not compute a lazy projection or create reactive observations. Names
126
+ MUST not add metadata or hooks to runtime kernel hot paths.
127
+
128
+ ## Conformance evidence
129
+
130
+ Tests cover native collection differential behavior, compiler/JavaScript
131
+ differential behavior, all scheduler strategies, getter caching, coherent methods
132
+ and hydration, factory isolation, ownership and erasure safety.
133
+
134
+ The packed-install check MUST install only the local tarball in a clean offline
135
+ project, reject external package imports in JS/declarations, load WASM, validate
136
+ shared graph behavior and Vite integration, and typecheck a consumer without
137
+ skipLibCheck. Build-only tooling belongs in devDependencies.
@@ -0,0 +1,12 @@
1
+ /// <reference lib="esnext.disposable" />
2
+ export { C as CompiledStore, S as StoreData, a as StoreOptions, b as StoreShape, c as createStore, l as leaf } from './types/createStore-BJ66-pfq.js';
3
+ export { action, derive, hydrate } from './index.js';
4
+ export { A as Accessor, D as Destructor, a as DisposableAccessor, S as StoreDisposable, c as createKeyedProjection, b as createSelector, b as selector } from './types/keyed-CdSYzIOc.js';
5
+ export { D as Depth, K as KeyedOptions, P as ProjectionOptions, R as ReactiveMap, a as ReactiveMapOptions, b as ReactiveSet, S as Snapshot, c as StoreProjectionOptions, d as collectStore, e as createReactiveMap, f as deep, g as disposeStore, o as opaque, r as raw, e as reactiveMap, h as reactiveSet, i as ref, s as shallow, j as snapshot, t as transaction } from './types/shared-CdTKzvLe.js';
6
+ export { createProjection, createStoreProjection } from './selectors/index.js';
7
+ export { S as StoreCell, c as createStoreCell } from './types/internal-BIzJSPwN.js';
8
+ import '@volynets/reflex-runtime/internal';
9
+
10
+ declare function getStoreName(resource: object): string | undefined;
11
+
12
+ export { getStoreName };
@@ -0,0 +1,10 @@
1
+ export { createProjection } from './selectors/index.js';
2
+ export { R as ReactiveMap, a as ReactiveSet, c as createReactiveMap, c as reactiveMap, r as reactiveSet, t as transaction } from './chunks/collections-yHy0wH0f.js';
3
+ export { g as getStoreName } from './chunks/names-Cx4YcvsH.js';
4
+ export { c as collectStore, d as deep, a as disposeStore, o as opaque, r as raw, b as ref, e as shallow, s as snapshot } from './chunks/values-DCB6zyMz.js';
5
+ export { createStoreCell } from './runtime/internal.js';
6
+ export { a as action, d as derive, h as hydrate, l as leaf } from './chunks/hydrate-DMnHCI5q.js';
7
+ export { a as createKeyedProjection, c as createSelector, b as createStoreProjection, c as selector } from './chunks/keyed-CQKtL8a7.js';
8
+ export { c as createStore } from './chunks/createStore-DHj16Ehk.js';
9
+ import '@volynets/reflex-runtime/internal';
10
+ import './selectors/shared.js';
@@ -0,0 +1,268 @@
1
+ import { untracked, currentConsumer, readProducer, createProducer, disposeNode, getActiveRuntimeContext, enterReactiveBatch, leaveReactiveBatch, writeProducer } from '@volynets/reflex-runtime/internal';
2
+ import { g as storeControls } from './values-DCB6zyMz.js';
3
+ import { s as setStoreName } from './names-Cx4YcvsH.js';
4
+
5
+ let transactionContext;
6
+ /** Publish a synchronous logical action at one runtime batch boundary. */ function transaction(action) {
7
+ if (transactionContext !== undefined && transactionContext === getActiveRuntimeContext()) return action();
8
+ const previous = transactionContext;
9
+ transactionContext = getActiveRuntimeContext();
10
+ enterReactiveBatch();
11
+ try {
12
+ return action();
13
+ } finally{
14
+ transactionContext = previous;
15
+ leaveReactiveBatch();
16
+ }
17
+ }
18
+ class Cells {
19
+ read(key, value) {
20
+ if (currentConsumer === null) return value;
21
+ let entry = this.nodes.get(key);
22
+ if (!entry) {
23
+ entry = {
24
+ node: createProducer(0),
25
+ value
26
+ };
27
+ this.nodes.set(key, entry);
28
+ }
29
+ readProducer(entry.node);
30
+ return value;
31
+ }
32
+ write(key, value) {
33
+ const entry = this.nodes.get(key);
34
+ if (!entry || Object.is(entry.value, value)) return;
35
+ entry.value = value;
36
+ writeProducer(entry.node, entry.node.payload + 1);
37
+ }
38
+ clear(value) {
39
+ for (const entry of this.nodes.values()){
40
+ if (Object.is(entry.value, value)) continue;
41
+ entry.value = value;
42
+ writeProducer(entry.node, entry.node.payload + 1);
43
+ }
44
+ }
45
+ collect() {
46
+ for (const [key, { node }] of this.nodes){
47
+ if (node.firstOut !== null) continue;
48
+ this.nodes.delete(key);
49
+ disposeNode(node);
50
+ }
51
+ }
52
+ dispose() {
53
+ for (const { node } of this.nodes.values())disposeNode(node);
54
+ this.nodes.clear();
55
+ }
56
+ constructor(){
57
+ // Values live outside producer payloads so foreign objects remain valid even
58
+ // when runtime dev validation accepts only primitive/plain payloads.
59
+ this.nodes = new Map();
60
+ }
61
+ }
62
+ class ReactiveMap {
63
+ constructor(initial, options = {}){
64
+ this.initial = initial;
65
+ this.options = options;
66
+ this.disposed = false;
67
+ this.valuesByKey = new Cells();
68
+ this.existsByKey = new Cells();
69
+ setStoreName(this, options.name);
70
+ storeControls.set(this, {
71
+ raw: ()=>this.backing(),
72
+ collect: ()=>this.collect(),
73
+ dispose: ()=>this.dispose()
74
+ });
75
+ }
76
+ backing() {
77
+ if (this.disposed) throw new Error("Cannot use a disposed reactive map");
78
+ if (!this.data) {
79
+ this.data = untracked(()=>new Map(typeof this.initial === "function" ? this.initial() : this.initial));
80
+ this.initial = undefined;
81
+ }
82
+ return this.data;
83
+ }
84
+ get size() {
85
+ const size = this.backing().size;
86
+ if (currentConsumer !== null) readProducer(this.sizeNode ??= createProducer(size));
87
+ return size;
88
+ }
89
+ get [Symbol.toStringTag]() {
90
+ return "ReactiveMap";
91
+ }
92
+ get(key) {
93
+ return this.valuesByKey.read(key, this.backing().get(key));
94
+ }
95
+ has(key) {
96
+ return this.existsByKey.read(key, this.backing().has(key));
97
+ }
98
+ set(key, value) {
99
+ const data = this.backing();
100
+ const previous = data.get(key);
101
+ const existed = previous !== undefined || data.has(key);
102
+ if (existed) {
103
+ const equals = this.options.equals;
104
+ if (equals == null || equals === Object.is ? Object.is(previous, value) : untracked(()=>equals(previous, value))) return this;
105
+ }
106
+ this.commitSet(data, key, value, existed);
107
+ return this;
108
+ }
109
+ commitSet(data, key, value, existed) {
110
+ transaction(()=>{
111
+ data.set(key, value);
112
+ this.valuesByKey.write(key, value);
113
+ if (!existed) {
114
+ this.existsByKey.write(key, true);
115
+ if (this.sizeNode) writeProducer(this.sizeNode, data.size);
116
+ if (this.keysNode) writeProducer(this.keysNode, this.keysNode.payload + 1);
117
+ }
118
+ if (this.valuesNode) writeProducer(this.valuesNode, this.valuesNode.payload + 1);
119
+ });
120
+ }
121
+ delete(key) {
122
+ const data = this.backing();
123
+ if (!data.has(key)) return false;
124
+ transaction(()=>{
125
+ data.delete(key);
126
+ this.valuesByKey.write(key, undefined);
127
+ this.existsByKey.write(key, false);
128
+ if (this.sizeNode) writeProducer(this.sizeNode, data.size);
129
+ if (this.keysNode) writeProducer(this.keysNode, this.keysNode.payload + 1);
130
+ if (this.valuesNode) writeProducer(this.valuesNode, this.valuesNode.payload + 1);
131
+ });
132
+ return true;
133
+ }
134
+ clear() {
135
+ const data = this.backing();
136
+ if (data.size === 0) return;
137
+ transaction(()=>{
138
+ data.clear();
139
+ // Only materialized semantic locations need invalidation, even for a large map.
140
+ this.valuesByKey.clear(undefined);
141
+ this.existsByKey.clear(false);
142
+ if (this.sizeNode) writeProducer(this.sizeNode, 0);
143
+ if (this.keysNode) writeProducer(this.keysNode, this.keysNode.payload + 1);
144
+ if (this.valuesNode) writeProducer(this.valuesNode, this.valuesNode.payload + 1);
145
+ });
146
+ }
147
+ keys() {
148
+ const data = this.backing();
149
+ if (currentConsumer !== null) readProducer(this.keysNode ??= createProducer(0));
150
+ return data.keys();
151
+ }
152
+ values() {
153
+ const data = this.backing();
154
+ if (currentConsumer !== null) readProducer(this.valuesNode ??= createProducer(0));
155
+ return data.values();
156
+ }
157
+ entries() {
158
+ const data = this.backing();
159
+ if (currentConsumer !== null) readProducer(this.valuesNode ??= createProducer(0));
160
+ return data.entries();
161
+ }
162
+ [Symbol.iterator]() {
163
+ return this.entries();
164
+ }
165
+ forEach(callback, thisArg) {
166
+ const data = this.backing();
167
+ if (currentConsumer !== null) readProducer(this.valuesNode ??= createProducer(0));
168
+ data.forEach((value, key)=>callback.call(thisArg, value, key, this));
169
+ }
170
+ collect() {
171
+ this.valuesByKey.collect();
172
+ this.existsByKey.collect();
173
+ if (this.sizeNode?.firstOut === null) {
174
+ disposeNode(this.sizeNode);
175
+ this.sizeNode = undefined;
176
+ }
177
+ if (this.keysNode?.firstOut === null) {
178
+ disposeNode(this.keysNode);
179
+ this.keysNode = undefined;
180
+ }
181
+ if (this.valuesNode?.firstOut === null) {
182
+ disposeNode(this.valuesNode);
183
+ this.valuesNode = undefined;
184
+ }
185
+ }
186
+ [Symbol.dispose]() {
187
+ this.dispose();
188
+ }
189
+ dispose() {
190
+ this.disposed = true;
191
+ this.valuesByKey.dispose();
192
+ this.existsByKey.dispose();
193
+ if (this.sizeNode) disposeNode(this.sizeNode);
194
+ if (this.keysNode) disposeNode(this.keysNode);
195
+ if (this.valuesNode) disposeNode(this.valuesNode);
196
+ this.sizeNode = this.keysNode = this.valuesNode = undefined;
197
+ this.data = undefined;
198
+ this.initial = undefined;
199
+ }
200
+ }
201
+ /** A native Set-shaped membership collection over keyed reactive observations. */ class ReactiveSet {
202
+ constructor(initial, options = {}){
203
+ this.members = new ReactiveMap(()=>Array.from(typeof initial === "function" ? initial() : initial ?? [], (value)=>[
204
+ value,
205
+ true
206
+ ]));
207
+ setStoreName(this, options.name);
208
+ storeControls.set(this, {
209
+ raw: ()=>new Set(this.members.keys()),
210
+ collect: ()=>this.collect(),
211
+ dispose: ()=>this.dispose()
212
+ });
213
+ }
214
+ get size() {
215
+ return this.members.size;
216
+ }
217
+ get [Symbol.toStringTag]() {
218
+ return "ReactiveSet";
219
+ }
220
+ has(value) {
221
+ return this.members.has(value);
222
+ }
223
+ add(value) {
224
+ this.members.set(value, true);
225
+ return this;
226
+ }
227
+ delete(value) {
228
+ return this.members.delete(value);
229
+ }
230
+ clear() {
231
+ this.members.clear();
232
+ }
233
+ values() {
234
+ return this.members.keys();
235
+ }
236
+ keys() {
237
+ return this.values();
238
+ }
239
+ *entries() {
240
+ for (const value of this.values())yield [
241
+ value,
242
+ value
243
+ ];
244
+ }
245
+ [Symbol.iterator]() {
246
+ return this.values();
247
+ }
248
+ forEach(callback, thisArg) {
249
+ for (const value of this.values())callback.call(thisArg, value, value, this);
250
+ }
251
+ collect() {
252
+ this.members.collect();
253
+ }
254
+ dispose() {
255
+ this.members.dispose();
256
+ }
257
+ [Symbol.dispose]() {
258
+ this.dispose();
259
+ }
260
+ }
261
+ function reactiveSet(initial, options) {
262
+ return new ReactiveSet(initial, options);
263
+ }
264
+ function createReactiveMap(initial, options) {
265
+ return new ReactiveMap(initial, options);
266
+ }
267
+
268
+ export { ReactiveMap as R, ReactiveSet as a, createReactiveMap as c, reactiveSet as r, transaction as t };