@volynets/reflex-store 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -2
  3. package/SPECIFICATION.md +137 -0
  4. package/dist/advanced.d.ts +13 -0
  5. package/dist/advanced.js +9 -0
  6. package/dist/chunks/compiler-CW_P8WsZ.js +2526 -0
  7. package/dist/chunks/createStore-DHj16Ehk.js +12 -0
  8. package/dist/chunks/hydrate-Bdtl9XyA.js +137 -0
  9. package/dist/chunks/keyed-C-QvvUv4.js +647 -0
  10. package/dist/chunks/names-BF36AyM3.js +11 -0
  11. package/dist/chunks/shared-BHHficW8.js +202 -0
  12. package/dist/compiled-store.d.ts +4 -0
  13. package/dist/compiled-store.js +4 -0
  14. package/dist/compiler/swc.wasm +0 -0
  15. package/dist/index.d.ts +23 -0
  16. package/dist/index.js +7 -0
  17. package/dist/licenses/NOTICE.txt +1 -0
  18. package/dist/licenses/SWC-APACHE-2.0.txt +202 -0
  19. package/dist/runtime/core.d.ts +2 -0
  20. package/dist/runtime/core.js +1 -0
  21. package/dist/runtime/internal.d.ts +3 -0
  22. package/dist/runtime/internal.js +41 -0
  23. package/dist/runtime/types.d.ts +45 -0
  24. package/dist/runtime/types.js +1 -0
  25. package/dist/runtime.d.ts +25 -0
  26. package/dist/runtime.js +78 -0
  27. package/dist/selectors/index.d.ts +13 -0
  28. package/dist/selectors/index.js +15 -0
  29. package/dist/selectors/shared.d.ts +3 -0
  30. package/dist/selectors/shared.js +2 -0
  31. package/dist/store.d.ts +9 -0
  32. package/dist/store.js +4 -0
  33. package/dist/types/contracts-BCsRPdQb.d.ts +78 -0
  34. package/dist/types/createStore-BJ66-pfq.d.ts +43 -0
  35. package/dist/types/internal-BIzJSPwN.d.ts +18 -0
  36. package/dist/types/keyed-CdSYzIOc.d.ts +18 -0
  37. package/dist/types/shared-CdTKzvLe.d.ts +114 -0
  38. package/dist/vite.d.ts +68 -0
  39. package/dist/vite.js +87 -0
  40. package/examples/task-board/README.md +15 -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 +125 -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,240 @@
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 keeps the Reflex host and reactive kernel
5
+ as external imports. `@volynets/reflex`, `@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 @volynets/reflex-runtime @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 { createRuntime, effect } from "@volynets/reflex-store/runtime";
27
+ import {
28
+ createStore,
29
+ leaf,
30
+ derive,
31
+ selector,
32
+ reactiveMap,
33
+ action,
34
+ snapshot,
35
+ hydrate,
36
+ } from "@volynets/reflex-store";
37
+
38
+ createRuntime({ effectStrategy: "flush" });
39
+
40
+ export function createBoard() {
41
+ const board = createStore(
42
+ {
43
+ filter: { status: "all", query: "" },
44
+ selection: { taskId: "T-101" },
45
+ pinned: leaf<readonly string[]>([]),
46
+
47
+ get filterLabel() {
48
+ const query = this.filter.query.trim().toLowerCase();
49
+ return query ? this.filter.status + ":" + query : this.filter.status;
50
+ },
51
+
52
+ setFilter(status: string, query = "") {
53
+ this.filter.status = status;
54
+ this.filter.query = query;
55
+ },
56
+ },
57
+ { name: "Board" },
58
+ );
59
+
60
+ return board;
61
+ }
62
+
63
+ const board = createBoard();
64
+ const tasks = reactiveMap<string, { title: string; done: boolean }>();
65
+ const isSelected = selector(() => board.selection.taskId);
66
+ const summary = derive(
67
+ () => ({
68
+ total: tasks.size,
69
+ completed: [...tasks.values()].filter((task) => task.done).length,
70
+ }),
71
+ { name: "Summary" },
72
+ );
73
+
74
+ const addTasks = action(() => {
75
+ tasks.set("T-101", { title: "Design checkout", done: false });
76
+ tasks.set("T-102", { title: "Add audit log", done: true });
77
+ });
78
+
79
+ addTasks();
80
+ console.log(summary.completed); // 1; pulls current data immediately
81
+ const saved = snapshot(board); // frozen data; excludes methods and getters
82
+ hydrate(board, saved); // validates the entire shape, then writes in one action
83
+ ```
84
+
85
+ Every factory call creates independent state. Root methods become bound,
86
+ synchronous actions; getters become owned lazy computeds. Static data reads and
87
+ writes lower to direct cell calls. Getters must be pure and synchronous.
88
+
89
+ `derive` accepts a pure callback returning a plain object. Its property views
90
+ are read-only. Dynamic entity IDs belong in `reactiveMap`; map values are
91
+ reference boundaries, so replace an entry to publish an entity change.
92
+
93
+ `action` preserves callback parameters, return value and receiver. Related
94
+ writes run untracked inside one scheduler/runtime batch. Direct reads see writes
95
+ so far. Exceptions keep completed writes and close the batch; there is no rollback.
96
+
97
+ ## Compiler and Vite
98
+
99
+ `createStore` requires the transform. The runtime stub throws if it executes.
100
+
101
+ ```ts
102
+ import { defineConfig } from "vite";
103
+ import reflexStore from "@volynets/reflex-store/vite";
104
+
105
+ export default defineConfig({
106
+ plugins: [reflexStore({ eraseFacade: true })],
107
+ });
108
+ ```
109
+
110
+ The plugin transforms stores while preserving application package resolution.
111
+ It does not redirect host or kernel imports. `@volynets/reflex-store/runtime`,
112
+ `/runtime/core` and `/runtime/internal` re-export the shared peer APIs; they do not
113
+ contain a private host or kernel.
114
+
115
+ The standalone compiler is exported from `@volynets/reflex-store/store`.
116
+ It includes its portable WASM asset; installing a platform-specific native SWC
117
+ package is unnecessary. `eraseFacade` removes the object only when every use
118
+ lowers to data cells. Factories, methods, getters, disposal and data extraction
119
+ retain the required facade.
120
+
121
+ See [compiler rules](./src/store/TRANSFORM_SPEC.md).
122
+
123
+ ## Ownership
124
+
125
+ Compiled stores use `createStoreScope()` from `/runtime`. This thin compiler
126
+ target delegates ownership, cancellation and rollback to Framework's
127
+ `LifecycleScope`. It adds synchronous, untracked actions inside the host batch
128
+ and exposes disposal through `dispose()` and `Symbol.dispose`. Cells are created
129
+ and owned during setup; a failed initializer disposes earlier cells before
130
+ rethrowing its original error. Computed getter resources share the same lifetime.
131
+
132
+ Store compilation uses neither `createModel` nor `defineModel`. Models remain an
133
+ optional way for application code to own a store alongside other resources.
134
+
135
+ Use the shared host's model ownership to dispose resources together:
136
+
137
+ ```ts
138
+ import { createModel, own, signal } from "@volynets/reflex-store/runtime";
139
+ import { derive, reactiveMap, selector } from "@volynets/reflex-store";
140
+
141
+ const createPanel = createModel((ctx) => {
142
+ const tasks = own(ctx, reactiveMap<string, { done: boolean }>());
143
+ const selectedId = signal("");
144
+ return {
145
+ tasks,
146
+ selected: own(ctx, selector(selectedId)),
147
+ summary: own(
148
+ ctx,
149
+ derive(() => ({ total: tasks.size })),
150
+ ),
151
+ };
152
+ });
153
+ ```
154
+
155
+ Maps, sets, selectors, keyed projections, structured views and compiled stores
156
+ implement `Symbol.dispose`. They also support explicit terminal disposal. Compiled reads, methods, writes and
157
+ restoration reject use after disposal.
158
+ `collect` is available in the advanced API for long-lived structures with many
159
+ previously observed keys.
160
+
161
+ ## Reference and data boundaries
162
+
163
+ An object literal is a structural branch. `leaf(value)` is one replaceable
164
+ location. `opaque(resource)` retains an external reference.
165
+
166
+ ```ts
167
+ const state = createStore({
168
+ items: leaf<readonly Task[]>([]),
169
+ engine: opaque(new Engine()),
170
+ });
171
+ state.items = [...state.items, task];
172
+ ```
173
+
174
+ Compiled arrays use replacement semantics. Array mutation does not publish
175
+ fine-grained changes.
176
+
177
+ Snapshots copy and freeze plain structural data, preserving cycles and shared
178
+ references. Map/Set keys preserve identity. Opaque and foreign objects remain
179
+ external references. Applications define encoding/exclusion for JSON, SSR
180
+ persistence and external resources.
181
+
182
+ Compiled hydration requires the full exact data shape, including empty branches.
183
+ It rejects missing/extra fields and accessor properties before the first write,
184
+ clones structural leaf data, and restores all fields in one action. Schema
185
+ validation is structural; validate untrusted input value types in the application.
186
+
187
+ ## Advanced API
188
+
189
+ `@volynets/reflex-store/advanced` preserves projection controls, depth markers,
190
+ `raw`, `transaction`, lifecycle helpers and `createStoreCell`. It also exports:
191
+
192
+ ```ts
193
+ import { reactiveSet, getStoreName } from "@volynets/reflex-store/advanced";
194
+
195
+ const selectedIds = reactiveSet<string>([], { name: "Selected IDs" });
196
+ selectedIds.add("T-101");
197
+ console.log(getStoreName(selectedIds));
198
+ ```
199
+
200
+ `ReactiveSet` follows native Set membership and iteration semantics.
201
+ Resource names are optional store metadata and do not add runtime kernel hooks.
202
+ The existing draft and keyed projection APIs remain available here.
203
+
204
+ The [task board](./examples/task-board/README.md) demonstrates the basic API.
205
+ The normative contract is in [SPECIFICATION.md](./SPECIFICATION.md).
206
+
207
+ ## Checks
208
+
209
+ ```sh
210
+ pnpm --filter @volynets/reflex-store test
211
+ pnpm --filter @volynets/reflex-store test:dev
212
+ pnpm --filter @volynets/reflex-store typecheck
213
+ pnpm --filter @volynets/reflex-store lint
214
+ pnpm --filter @volynets/reflex-store test:packed-runtime
215
+ ```
216
+
217
+ The packed test installs the Store and its Reflex peer tarballs in an empty
218
+ offline project. It checks all published imports, WASM loading, one shared graph across entrypoints,
219
+ the Vite plugin, factory isolation, batching, snapshots/hydration and consumer
220
+ declarations with `skipLibCheck=false`.
221
+
222
+ ## Release preparation
223
+
224
+ Run from the repository root with workspace dependencies installed:
225
+
226
+ ```sh
227
+ pnpm --filter @volynets/reflex-store check:release
228
+ pnpm --dir packages/reflex-store pack --pack-destination ../../artifacts/npm
229
+ ```
230
+
231
+ `check:release` builds Store and its workspace dependencies, runs lint, production
232
+ and development tests, checks types and artifacts, and verifies an isolated
233
+ installation of the packed packages. `prepublishOnly` runs the same checks.
234
+ `prepack` rebuilds Store and checks its output before creating a tarball.
235
+
236
+ The package contains the MIT license and the Apache-2.0 license for the bundled
237
+ SWC compiler. Publish compatible versions of the required Reflex peers before
238
+ releasing Store. The repository release workflow uses `pnpm release:qualify`
239
+ and publishes the verified tarballs through `pnpm release:publish` with the
240
+ 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,13 @@
1
+ /// <reference lib="esnext.disposable" />
2
+ import type { Destructor, Accessor } from "./runtime/types.js";
3
+ 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';
4
+ export { action, derive, hydrate } from './index.js';
5
+ 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';
6
+ 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';
7
+ export { createProjection, createStoreProjection } from './selectors/index.js';
8
+ export { S as StoreCell, c as createStoreCell } from './types/internal-BIzJSPwN.js';
9
+ import '@volynets/reflex-runtime/internal';
10
+
11
+ declare function getStoreName(resource: object): string | undefined;
12
+
13
+ export { getStoreName };
@@ -0,0 +1,9 @@
1
+ export { createProjection } from './selectors/index.js';
2
+ export { R as ReactiveMap, b as ReactiveSet, d as createKeyedProjection, c as createReactiveMap, a as createSelector, e as createStoreProjection, c as reactiveMap, r as reactiveSet, a as selector, t as transaction } from './chunks/keyed-C-QvvUv4.js';
3
+ export { g as getStoreName } from './chunks/names-BF36AyM3.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/shared-BHHficW8.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-Bdtl9XyA.js';
7
+ export { c as createStore } from './chunks/createStore-DHj16Ehk.js';
8
+ import '@volynets/reflex-runtime/internal';
9
+ import '@volynets/reflex';