@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.
- package/LICENSE +21 -0
- package/README.md +237 -2
- package/SPECIFICATION.md +137 -0
- package/dist/advanced.d.ts +12 -0
- package/dist/advanced.js +10 -0
- package/dist/chunks/collections-yHy0wH0f.js +268 -0
- package/dist/chunks/compiler-Dsb3E2ck.js +2516 -0
- package/dist/chunks/createStore-DHj16Ehk.js +12 -0
- package/dist/chunks/hydrate-DMnHCI5q.js +138 -0
- package/dist/chunks/keyed-CQKtL8a7.js +383 -0
- package/dist/chunks/names-Cx4YcvsH.js +11 -0
- package/dist/chunks/values-DCB6zyMz.js +142 -0
- package/dist/compiled-store.d.ts +4 -0
- package/dist/compiled-store.js +4 -0
- package/dist/compiler/swc.wasm +0 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +8 -0
- package/dist/licenses/NOTICE.txt +1 -0
- package/dist/licenses/SWC-APACHE-2.0.txt +202 -0
- package/dist/runtime/core.d.ts +2 -0
- package/dist/runtime/core.js +1 -0
- package/dist/runtime/internal.d.ts +3 -0
- package/dist/runtime/internal.js +41 -0
- package/dist/runtime.d.ts +29 -0
- package/dist/runtime.js +88 -0
- package/dist/selectors/index.d.ts +13 -0
- package/dist/selectors/index.js +16 -0
- package/dist/selectors/shared.d.ts +3 -0
- package/dist/selectors/shared.js +64 -0
- package/dist/store.d.ts +9 -0
- package/dist/store.js +4 -0
- package/dist/types/contracts-BCsRPdQb.d.ts +78 -0
- package/dist/types/createStore-BJ66-pfq.d.ts +43 -0
- package/dist/types/internal-BIzJSPwN.d.ts +18 -0
- package/dist/types/keyed-CdSYzIOc.d.ts +18 -0
- package/dist/types/shared-CdTKzvLe.d.ts +114 -0
- package/dist/vite.d.ts +68 -0
- package/dist/vite.js +87 -0
- package/examples/task-board/README.md +15 -0
- package/examples/task-board/src/reactivity.ts +33 -0
- package/examples/task-board/src/task-board.ts +159 -0
- package/examples/task-board/vite.config.ts +6 -0
- package/package.json +119 -4
- package/src/store/README.md +59 -0
- package/src/store/TRANSFORM_SPEC.md +85 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference lib="esnext.disposable" />
|
|
2
|
+
import '@volynets/reflex-runtime/internal';
|
|
3
|
+
|
|
4
|
+
interface StoreCell<T> {
|
|
5
|
+
(): T;
|
|
6
|
+
set(value: T): void;
|
|
7
|
+
peek(): T;
|
|
8
|
+
collect(): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
[Symbol.dispose](): void;
|
|
11
|
+
}
|
|
12
|
+
/** Compiler target: a direct leaf address, materialized only by a tracked read. */
|
|
13
|
+
declare function createStoreCell<T>(initial: T, options?: {
|
|
14
|
+
name?: string;
|
|
15
|
+
}): StoreCell<T>;
|
|
16
|
+
|
|
17
|
+
export { createStoreCell as c };
|
|
18
|
+
export type { StoreCell as S };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference lib="esnext.disposable" />
|
|
2
|
+
import { K as KeyedOptions, P as ProjectionOptions } from './shared-CdTKzvLe.js';
|
|
3
|
+
|
|
4
|
+
interface StoreDisposable {
|
|
5
|
+
[Symbol.dispose](): void;
|
|
6
|
+
}
|
|
7
|
+
type Destructor = () => void;
|
|
8
|
+
type Accessor<T> = () => T;
|
|
9
|
+
type DisposableAccessor<K, V> = ((key: K) => V) & StoreDisposable & {
|
|
10
|
+
collect(): void;
|
|
11
|
+
dispose(): void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
declare function createSelector<T>(source: Accessor<T>, options?: KeyedOptions<T>): DisposableAccessor<T, boolean>;
|
|
15
|
+
declare function createKeyedProjection<T, K, R>(source: Accessor<T>, keyOf: (value: T) => K, project: (value: T) => R, options?: ProjectionOptions<K, R>): DisposableAccessor<K, R | undefined>;
|
|
16
|
+
|
|
17
|
+
export { createSelector as b, createKeyedProjection as c };
|
|
18
|
+
export type { Accessor as A, Destructor as D, StoreDisposable as S, DisposableAccessor as a };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/// <reference lib="esnext.disposable" />
|
|
2
|
+
import { d as StoreBoundary, b as StoreShape, C as CompiledStore, S as StoreData } from './createStore-BJ66-pfq.js';
|
|
3
|
+
|
|
4
|
+
/** Publish a synchronous logical action at one runtime batch boundary. */
|
|
5
|
+
declare function transaction<T>(action: () => T): T;
|
|
6
|
+
interface ReactiveMapOptions<V> {
|
|
7
|
+
name?: string;
|
|
8
|
+
/** Values are reference boundaries; equivalent replacements retain the old value. */
|
|
9
|
+
equals?: (previous: V, next: V) => boolean;
|
|
10
|
+
}
|
|
11
|
+
declare class ReactiveMap<K, V> implements Iterable<[K, V]> {
|
|
12
|
+
private initial?;
|
|
13
|
+
private readonly options;
|
|
14
|
+
private data?;
|
|
15
|
+
private disposed;
|
|
16
|
+
private readonly valuesByKey;
|
|
17
|
+
private readonly existsByKey;
|
|
18
|
+
private sizeNode?;
|
|
19
|
+
private keysNode?;
|
|
20
|
+
private valuesNode?;
|
|
21
|
+
constructor(initial?: (Iterable<readonly [K, V]> | (() => Iterable<readonly [K, V]>)) | undefined, options?: ReactiveMapOptions<V>);
|
|
22
|
+
private backing;
|
|
23
|
+
get size(): number;
|
|
24
|
+
get [Symbol.toStringTag](): string;
|
|
25
|
+
get(key: K): V | undefined;
|
|
26
|
+
has(key: K): boolean;
|
|
27
|
+
set(key: K, value: V): this;
|
|
28
|
+
private commitSet;
|
|
29
|
+
delete(key: K): boolean;
|
|
30
|
+
clear(): void;
|
|
31
|
+
keys(): MapIterator<K>;
|
|
32
|
+
values(): MapIterator<V>;
|
|
33
|
+
entries(): MapIterator<[K, V]>;
|
|
34
|
+
[Symbol.iterator](): MapIterator<[K, V]>;
|
|
35
|
+
forEach(callback: (value: V, key: K, map: ReactiveMap<K, V>) => void, thisArg?: unknown): void;
|
|
36
|
+
collect(): void;
|
|
37
|
+
[Symbol.dispose](): void;
|
|
38
|
+
dispose(): void;
|
|
39
|
+
}
|
|
40
|
+
/** A native Set-shaped membership collection over keyed reactive observations. */
|
|
41
|
+
declare class ReactiveSet<T> implements Iterable<T> {
|
|
42
|
+
private readonly members;
|
|
43
|
+
constructor(initial?: Iterable<T> | (() => Iterable<T>), options?: {
|
|
44
|
+
name?: string;
|
|
45
|
+
});
|
|
46
|
+
get size(): number;
|
|
47
|
+
get [Symbol.toStringTag](): string;
|
|
48
|
+
has(value: T): boolean;
|
|
49
|
+
add(value: T): this;
|
|
50
|
+
delete(value: T): boolean;
|
|
51
|
+
clear(): void;
|
|
52
|
+
values(): MapIterator<T>;
|
|
53
|
+
keys(): MapIterator<T>;
|
|
54
|
+
entries(): IterableIterator<[T, T]>;
|
|
55
|
+
[Symbol.iterator](): MapIterator<T>;
|
|
56
|
+
forEach(callback: (value: T, key: T, set: ReactiveSet<T>) => void, thisArg?: unknown): void;
|
|
57
|
+
collect(): void;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
[Symbol.dispose](): void;
|
|
60
|
+
}
|
|
61
|
+
declare function reactiveSet<T>(initial?: Iterable<T> | (() => Iterable<T>), options?: {
|
|
62
|
+
name?: string;
|
|
63
|
+
}): ReactiveSet<T>;
|
|
64
|
+
declare function createReactiveMap<K, V>(initial?: Iterable<readonly [K, V]> | (() => Iterable<readonly [K, V]>), options?: ReactiveMapOptions<V>): ReactiveMap<K, V>;
|
|
65
|
+
|
|
66
|
+
type Depth = "deep" | "shallow" | "ref" | "opaque";
|
|
67
|
+
declare const deep: <T extends object>(value: T) => T;
|
|
68
|
+
declare const shallow: <T extends object>(value: T) => T;
|
|
69
|
+
declare const ref: <T extends object>(value: T) => T;
|
|
70
|
+
declare const opaque: <T extends object>(value: T) => StoreBoundary<T>;
|
|
71
|
+
/** Copy structural data, retaining explicit reference boundaries and foreign objects. */
|
|
72
|
+
declare function cloneValue<T>(value: T, seen?: Map<object, unknown>): T;
|
|
73
|
+
/** An untracked backing view. Mutating it bypasses reactivity and is unsupported. */
|
|
74
|
+
declare function raw<K, V>(store: ReactiveMap<K, V>): Map<K, V>;
|
|
75
|
+
declare function raw<T>(store: ReactiveSet<T>): Set<T>;
|
|
76
|
+
declare function raw<T>(store: T): T;
|
|
77
|
+
type Snapshot<T> = T extends ReactiveSet<infer V> ? ReadonlySet<V> : T extends Set<infer V> ? ReadonlySet<V> : T extends ReactiveMap<infer K, infer V> ? ReadonlyMap<K, Snapshot<V>> : T extends Map<infer K, infer V> ? ReadonlyMap<K, Snapshot<V>> : T extends readonly (infer V)[] ? readonly Snapshot<V>[] : T extends object ? {
|
|
78
|
+
readonly [K in keyof T]: Snapshot<T[K]>;
|
|
79
|
+
} : T;
|
|
80
|
+
/** An untracked point-in-time copy. Opaque/foreign values remain by reference. */
|
|
81
|
+
declare function snapshot<T extends StoreShape>(store: CompiledStore<T>): Snapshot<StoreData<T>>;
|
|
82
|
+
declare function snapshot<T>(store: T): Snapshot<T>;
|
|
83
|
+
declare function disposeStore(store: object): void;
|
|
84
|
+
/** Reclaim locations with no consumers at an explicit owner lifecycle boundary. */
|
|
85
|
+
declare function collectStore(store: object): void;
|
|
86
|
+
|
|
87
|
+
declare const MISSING: unique symbol;
|
|
88
|
+
type AnyRecord = Record<PropertyKey, unknown>;
|
|
89
|
+
type Missing = typeof MISSING;
|
|
90
|
+
interface KeyedOptions<T> {
|
|
91
|
+
name?: string;
|
|
92
|
+
equals?: (prev: T, next: T) => boolean;
|
|
93
|
+
}
|
|
94
|
+
interface ProjectionOptions<K, R> {
|
|
95
|
+
name?: string;
|
|
96
|
+
keyEquals?: (prev: K, next: K) => boolean;
|
|
97
|
+
equals?: (prev: R, next: R) => boolean;
|
|
98
|
+
fallback?: R;
|
|
99
|
+
}
|
|
100
|
+
interface StoreProjectionOptions<T extends object> {
|
|
101
|
+
name?: string;
|
|
102
|
+
clone?: (value: T) => T;
|
|
103
|
+
equals?: (previous: T, next: T) => boolean;
|
|
104
|
+
depth?: "deep" | "shallow";
|
|
105
|
+
}
|
|
106
|
+
declare function sameValue<T>(prev: T, next: T): boolean;
|
|
107
|
+
declare function getMissing(): Missing;
|
|
108
|
+
declare function isObject(value: unknown): value is AnyRecord;
|
|
109
|
+
|
|
110
|
+
declare function readProjectionPath(value: unknown, path: readonly PropertyKey[]): unknown;
|
|
111
|
+
declare function createStoreReaderProxy<T extends object>(readState: () => T, path?: readonly PropertyKey[]): T;
|
|
112
|
+
|
|
113
|
+
export { ReactiveMap as R, ReactiveSet as b, collectStore as d, createReactiveMap as e, deep as f, disposeStore as g, reactiveSet as h, ref as i, snapshot as j, cloneValue as k, createStoreReaderProxy as l, getMissing as m, isObject as n, opaque as o, readProjectionPath as p, sameValue as q, raw as r, shallow as s, transaction as t };
|
|
114
|
+
export type { Depth as D, KeyedOptions as K, Missing as M, ProjectionOptions as P, Snapshot as S, ReactiveMapOptions as a, StoreProjectionOptions as c };
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/// <reference lib="esnext.disposable" />
|
|
2
|
+
import { d as CompiledStoreLoweringTargetOptions } from './types/contracts-BCsRPdQb.js';
|
|
3
|
+
|
|
4
|
+
type TransformResult = {
|
|
5
|
+
code: string;
|
|
6
|
+
map: null | {
|
|
7
|
+
version: 3;
|
|
8
|
+
sources: string[];
|
|
9
|
+
names: string[];
|
|
10
|
+
mappings: string;
|
|
11
|
+
sourcesContent?: (string | null)[];
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
interface ReflexStorePlugin {
|
|
15
|
+
name: string;
|
|
16
|
+
enforce: "pre";
|
|
17
|
+
transform(this: DiagnosticReporter, code: string, id: string): TransformResult | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Selector = string | RegExp | readonly (string | RegExp)[];
|
|
21
|
+
type DiagnosticMode = "error" | "warn" | "collect";
|
|
22
|
+
type DiagnosticReporter = {
|
|
23
|
+
warn(warning: string): void;
|
|
24
|
+
error(error: string): never;
|
|
25
|
+
};
|
|
26
|
+
interface ReflexStoreVitePluginOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Files eligible for the compiled-store transform.
|
|
29
|
+
*
|
|
30
|
+
* @default /\.[cm]?[jt]sx?$/
|
|
31
|
+
*/
|
|
32
|
+
include?: Selector;
|
|
33
|
+
/**
|
|
34
|
+
* Files excluded from the compiled-store transform.
|
|
35
|
+
*
|
|
36
|
+
* @default /\/node_modules\//
|
|
37
|
+
*/
|
|
38
|
+
exclude?: Selector;
|
|
39
|
+
/**
|
|
40
|
+
* Runtime module used by generated code.
|
|
41
|
+
*
|
|
42
|
+
* @default "@volynets/reflex-store/runtime"
|
|
43
|
+
*/
|
|
44
|
+
runtimeModule?: string;
|
|
45
|
+
eraseFacade?: boolean;
|
|
46
|
+
/** Customize the runtime primitives and every generated identifier. */
|
|
47
|
+
loweringTarget?: CompiledStoreLoweringTargetOptions;
|
|
48
|
+
/**
|
|
49
|
+
* Compile bare `createStore({ ... })` calls even when the file does not
|
|
50
|
+
* import `createStore` from `@volynets/reflex-store`.
|
|
51
|
+
*
|
|
52
|
+
* The default keeps the plugin conservative and avoids touching unrelated
|
|
53
|
+
* libraries that also expose a `createStore` function.
|
|
54
|
+
*
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
compileBareCreateStore?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* How unsupported phase-1 syntax is surfaced through Vite.
|
|
60
|
+
*
|
|
61
|
+
* @default "error"
|
|
62
|
+
*/
|
|
63
|
+
diagnostics?: DiagnosticMode;
|
|
64
|
+
}
|
|
65
|
+
declare function reflexStoreVitePlugin(options?: ReflexStoreVitePluginOptions): ReflexStorePlugin;
|
|
66
|
+
|
|
67
|
+
export { reflexStoreVitePlugin as default, reflexStoreVitePlugin as reflexStore, reflexStoreVitePlugin };
|
|
68
|
+
export type { ReflexStorePlugin, ReflexStoreVitePluginOptions };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { c as compileStore, C as CompiledStoreTransformError } from './chunks/compiler-Dsb3E2ck.js';
|
|
2
|
+
import 'util';
|
|
3
|
+
import 'fs';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_INCLUDE = /\.[cm]?[jt]sx?$/;
|
|
6
|
+
const DEFAULT_EXCLUDE = /\/node_modules\//;
|
|
7
|
+
const STORE_IMPORT_RE = /from\s*["'](?:@volynets\/reflex-store|@reflex\/store)(?:\/(?:store|compiled-store|advanced))?["']/;
|
|
8
|
+
function reflexStoreVitePlugin(options = {}) {
|
|
9
|
+
const include = options.include ?? DEFAULT_INCLUDE;
|
|
10
|
+
const exclude = options.exclude ?? DEFAULT_EXCLUDE;
|
|
11
|
+
const diagnostics = options.diagnostics ?? "error";
|
|
12
|
+
return {
|
|
13
|
+
name: "reflex-store",
|
|
14
|
+
enforce: "pre",
|
|
15
|
+
transform (code, id) {
|
|
16
|
+
const normalizedId = normalizeId(id);
|
|
17
|
+
if (!matchesSelector(include, normalizedId)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (matchesSelector(exclude, normalizedId)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (!shouldCompileCode(code, options.compileBareCreateStore ?? false)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const result = compileStore(code, normalizedId, {
|
|
28
|
+
importRuntime: true,
|
|
29
|
+
eraseFacade: options.eraseFacade,
|
|
30
|
+
onDiagnostic: diagnostics === "error" ? "throw" : "collect",
|
|
31
|
+
loweringTarget: options.loweringTarget,
|
|
32
|
+
runtimeModule: options.runtimeModule
|
|
33
|
+
});
|
|
34
|
+
reportDiagnostics(this, result.diagnostics, diagnostics);
|
|
35
|
+
return {
|
|
36
|
+
code: result.code,
|
|
37
|
+
map: parseSourceMap(result.map)
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof CompiledStoreTransformError) {
|
|
41
|
+
this.error(formatDiagnostics(error.diagnostics));
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function shouldCompileCode(code, compileBareCreateStore) {
|
|
49
|
+
if (!code.includes("createStore")) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return compileBareCreateStore || STORE_IMPORT_RE.test(code);
|
|
53
|
+
}
|
|
54
|
+
function normalizeId(id) {
|
|
55
|
+
return id.split("?")[0].replace(/\\/g, "/");
|
|
56
|
+
}
|
|
57
|
+
function matchesSelector(selector, id) {
|
|
58
|
+
if (isSelectorArray(selector)) {
|
|
59
|
+
return selector.some((item)=>matchesSelector(item, id));
|
|
60
|
+
}
|
|
61
|
+
if (typeof selector === "string") {
|
|
62
|
+
return id.includes(selector);
|
|
63
|
+
}
|
|
64
|
+
return selector.test(id);
|
|
65
|
+
}
|
|
66
|
+
function isSelectorArray(selector) {
|
|
67
|
+
return Array.isArray(selector);
|
|
68
|
+
}
|
|
69
|
+
function parseSourceMap(map) {
|
|
70
|
+
return map === null ? null : JSON.parse(map);
|
|
71
|
+
}
|
|
72
|
+
function reportDiagnostics(context, diagnostics, mode) {
|
|
73
|
+
if (diagnostics.length === 0 || mode === "collect") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const message = formatDiagnostics(diagnostics);
|
|
77
|
+
if (mode === "warn") {
|
|
78
|
+
context.warn(message);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
context.error(message);
|
|
82
|
+
}
|
|
83
|
+
function formatDiagnostics(diagnostics) {
|
|
84
|
+
return diagnostics.map((diagnostic)=>diagnostic.message).join("\n");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { reflexStoreVitePlugin as default, reflexStoreVitePlugin as reflexStore, reflexStoreVitePlugin };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Task board example
|
|
2
|
+
|
|
3
|
+
The example uses reactiveMap for dynamic task IDs, createStore for closed UI
|
|
4
|
+
state, selector for selection observations, derive for summary fields and action
|
|
5
|
+
for coherent mutations. The Vite plugin enables compilation.
|
|
6
|
+
|
|
7
|
+
The implementation is [src/task-board.ts](./src/task-board.ts). Run its contracts:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm --filter @volynets/reflex-store test:integration
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The tests cover summary-field precision, keyed map reads, selection locality,
|
|
14
|
+
batching, pull freshness, ownership and no-op moves. The package also includes
|
|
15
|
+
production contracts for compiled methods/getters and snapshot/hydration.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Application wiring for this example; the store does not own a host runtime.
|
|
2
|
+
import {
|
|
3
|
+
createRuntimeContext,
|
|
4
|
+
createWatcher,
|
|
5
|
+
disposeWatcher,
|
|
6
|
+
runWatcher,
|
|
7
|
+
switchRuntimeContext,
|
|
8
|
+
} from "@volynets/reflex-runtime/internal";
|
|
9
|
+
import {
|
|
10
|
+
createRuntimeSchedulerBinding,
|
|
11
|
+
resolveEffectSchedulerMode,
|
|
12
|
+
type EffectStrategy,
|
|
13
|
+
} from "@volynets/reflex-scheduler";
|
|
14
|
+
|
|
15
|
+
export function createRuntime({
|
|
16
|
+
effectStrategy = "eager",
|
|
17
|
+
}: { effectStrategy?: EffectStrategy } = {}) {
|
|
18
|
+
const context = createRuntimeContext();
|
|
19
|
+
const scheduler = createRuntimeSchedulerBinding(
|
|
20
|
+
resolveEffectSchedulerMode(effectStrategy),
|
|
21
|
+
context,
|
|
22
|
+
);
|
|
23
|
+
context.nodeInvalidatedHook = scheduler.onNodeInvalidated;
|
|
24
|
+
context.hostFlushHook = scheduler.onHostFlush;
|
|
25
|
+
switchRuntimeContext(context);
|
|
26
|
+
return { flush: scheduler.flush };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function effect(callback: () => void | (() => void)): () => void {
|
|
30
|
+
const node = createWatcher(callback);
|
|
31
|
+
runWatcher(node);
|
|
32
|
+
return () => disposeWatcher(node);
|
|
33
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createRuntime, effect } from "./reactivity";
|
|
2
|
+
import {
|
|
3
|
+
action,
|
|
4
|
+
createStore,
|
|
5
|
+
derive,
|
|
6
|
+
reactiveMap,
|
|
7
|
+
selector,
|
|
8
|
+
} from "@volynets/reflex-store";
|
|
9
|
+
|
|
10
|
+
export type TaskStatus = "backlog" | "active" | "done";
|
|
11
|
+
|
|
12
|
+
export interface Task {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
assignee: string;
|
|
16
|
+
status: TaskStatus;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BoardSummary {
|
|
20
|
+
total: number;
|
|
21
|
+
visible: number;
|
|
22
|
+
completed: number;
|
|
23
|
+
activeTitle: string;
|
|
24
|
+
filterLabel: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const taskBoardRuntime = createRuntime({ effectStrategy: "flush" });
|
|
28
|
+
|
|
29
|
+
const INITIAL_TASKS: readonly Task[] = [
|
|
30
|
+
{ id: "T-101", title: "Design checkout", assignee: "Ada", status: "active" },
|
|
31
|
+
{ id: "T-102", title: "Add audit log", assignee: "Lin", status: "backlog" },
|
|
32
|
+
{ id: "T-103", title: "Ship billing", assignee: "Ada", status: "done" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const tasks = reactiveMap<string, Task>(
|
|
36
|
+
INITIAL_TASKS.map((task) => [task.id, task] as const),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const ui = createStore({
|
|
40
|
+
filter: {
|
|
41
|
+
status: "all" as TaskStatus | "all",
|
|
42
|
+
query: "",
|
|
43
|
+
},
|
|
44
|
+
selection: {
|
|
45
|
+
taskId: "T-101",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const EMPTY_TASK: Readonly<Task> = Object.freeze({
|
|
50
|
+
id: "",
|
|
51
|
+
title: "No task selected",
|
|
52
|
+
assignee: "",
|
|
53
|
+
status: "backlog",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const isTaskSelected = selector(() => ui.selection.taskId);
|
|
57
|
+
|
|
58
|
+
export const taskById = (taskId: string): Task | undefined => tasks.get(taskId);
|
|
59
|
+
|
|
60
|
+
export const summary = derive<BoardSummary>(() => {
|
|
61
|
+
const allTasks = [...tasks.values()];
|
|
62
|
+
const status = ui.filter.status;
|
|
63
|
+
const normalizedQuery = ui.filter.query.trim().toLocaleLowerCase();
|
|
64
|
+
const selectedTask = tasks.get(ui.selection.taskId) ?? EMPTY_TASK;
|
|
65
|
+
|
|
66
|
+
let completed = 0;
|
|
67
|
+
let visible = 0;
|
|
68
|
+
|
|
69
|
+
for (const task of allTasks) {
|
|
70
|
+
if (task.status === "done") completed++;
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
(status === "all" || task.status === status) &&
|
|
74
|
+
(normalizedQuery === "" ||
|
|
75
|
+
task.title.toLocaleLowerCase().includes(normalizedQuery))
|
|
76
|
+
) {
|
|
77
|
+
visible++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
total: allTasks.length,
|
|
83
|
+
completed,
|
|
84
|
+
visible,
|
|
85
|
+
activeTitle: selectedTask.title,
|
|
86
|
+
filterLabel:
|
|
87
|
+
normalizedQuery === "" ? status : status + ":" + normalizedQuery,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const resetBoard = action(() => {
|
|
92
|
+
tasks.clear();
|
|
93
|
+
for (const task of INITIAL_TASKS) tasks.set(task.id, task);
|
|
94
|
+
ui.filter.status = "all";
|
|
95
|
+
ui.filter.query = "";
|
|
96
|
+
ui.selection.taskId = "T-101";
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const boardActions = {
|
|
100
|
+
select: action((taskId: string) => {
|
|
101
|
+
if (ui.selection.taskId !== taskId) ui.selection.taskId = taskId;
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
setFilter: action((status: TaskStatus | "all", query = "") => {
|
|
105
|
+
if (ui.filter.status !== status) ui.filter.status = status;
|
|
106
|
+
if (ui.filter.query !== query) ui.filter.query = query;
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
move: action((taskId: string, status: TaskStatus) => {
|
|
110
|
+
const task = tasks.get(taskId);
|
|
111
|
+
if (!task || task.status === status) return;
|
|
112
|
+
tasks.set(taskId, { ...task, status });
|
|
113
|
+
}),
|
|
114
|
+
|
|
115
|
+
reset() {
|
|
116
|
+
resetBoard();
|
|
117
|
+
taskBoardRuntime.flush();
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
flush() {
|
|
121
|
+
taskBoardRuntime.flush();
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export function runTaskBoardScenario() {
|
|
126
|
+
const renders: string[] = [];
|
|
127
|
+
|
|
128
|
+
const stop = effect(() => {
|
|
129
|
+
renders.push(
|
|
130
|
+
summary.filterLabel +
|
|
131
|
+
"|" +
|
|
132
|
+
summary.visible +
|
|
133
|
+
"|" +
|
|
134
|
+
summary.completed +
|
|
135
|
+
"|" +
|
|
136
|
+
summary.activeTitle,
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
boardActions.setFilter("active");
|
|
141
|
+
boardActions.select("T-102");
|
|
142
|
+
taskBoardRuntime.flush();
|
|
143
|
+
|
|
144
|
+
boardActions.move("T-102", "active");
|
|
145
|
+
taskBoardRuntime.flush();
|
|
146
|
+
|
|
147
|
+
stop();
|
|
148
|
+
|
|
149
|
+
const activeTask = taskById("T-102");
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
activeTask: activeTask === undefined ? undefined : activeTask.title,
|
|
153
|
+
completed: summary.completed,
|
|
154
|
+
isSelected: isTaskSelected("T-102"),
|
|
155
|
+
renders,
|
|
156
|
+
total: summary.total,
|
|
157
|
+
visible: summary.visible,
|
|
158
|
+
};
|
|
159
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,121 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volynets/reflex-store",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "State management primitives for Reflex",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./advanced": {
|
|
16
|
+
"types": "./dist/advanced.d.ts",
|
|
17
|
+
"import": "./dist/advanced.js"
|
|
18
|
+
},
|
|
19
|
+
"./selectors": {
|
|
20
|
+
"types": "./dist/selectors/index.d.ts",
|
|
21
|
+
"import": "./dist/selectors/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./selectors/shared": {
|
|
24
|
+
"types": "./dist/selectors/shared.d.ts",
|
|
25
|
+
"import": "./dist/selectors/shared.js"
|
|
26
|
+
},
|
|
27
|
+
"./store": {
|
|
28
|
+
"types": "./dist/store.d.ts",
|
|
29
|
+
"import": "./dist/store.js"
|
|
30
|
+
},
|
|
31
|
+
"./compiled-store": {
|
|
32
|
+
"types": "./dist/compiled-store.d.ts",
|
|
33
|
+
"import": "./dist/compiled-store.js"
|
|
34
|
+
},
|
|
35
|
+
"./vite": {
|
|
36
|
+
"types": "./dist/vite.d.ts",
|
|
37
|
+
"import": "./dist/vite.js"
|
|
38
|
+
},
|
|
39
|
+
"./runtime": {
|
|
40
|
+
"types": "./dist/runtime.d.ts",
|
|
41
|
+
"import": "./dist/runtime.js"
|
|
42
|
+
},
|
|
43
|
+
"./runtime/internal": {
|
|
44
|
+
"types": "./dist/runtime/internal.d.ts",
|
|
45
|
+
"import": "./dist/runtime/internal.js"
|
|
46
|
+
},
|
|
47
|
+
"./runtime/core": {
|
|
48
|
+
"types": "./dist/runtime/core.d.ts",
|
|
49
|
+
"import": "./dist/runtime/core.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"examples/task-board",
|
|
55
|
+
"src/store/README.md",
|
|
56
|
+
"src/store/TRANSFORM_SPEC.md",
|
|
57
|
+
"SPECIFICATION.md"
|
|
58
|
+
],
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"clean": "node ../../tooling/build/clean.mjs .",
|
|
64
|
+
"build": "node scripts/build.mjs",
|
|
65
|
+
"bench": "vitest bench --config vite.config.ts bench/semantic-workloads.bench.ts",
|
|
66
|
+
"bench:full": "vitest bench --mode full --config vite.config.ts bench/semantic-workloads.bench.ts",
|
|
67
|
+
"bench:hot-paths": "node --expose-gc bench/hot-paths.node.mjs",
|
|
68
|
+
"bench:metrics": "vitest run --config vite.metrics.config.ts tests/semantic-workloads.metrics.test.ts",
|
|
69
|
+
"lint": "eslint .",
|
|
70
|
+
"lint:fix": "eslint . --fix",
|
|
71
|
+
"test": "vitest run --config vite.config.ts",
|
|
72
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc -p tsconfig.example.json",
|
|
73
|
+
"prepublishOnly": "pnpm check:release",
|
|
74
|
+
"check:release": "pnpm build:all && pnpm lint && pnpm test && pnpm test:dev && pnpm typecheck && pnpm check:artifacts && node scripts/packed-runtime-identity.mjs",
|
|
75
|
+
"test:dev": "vitest run --config vite.dev.config.ts",
|
|
76
|
+
"test:differential": "vitest run --config vite.config.ts tests/compiler.differential.test.ts tests/runtime.differential.test.ts",
|
|
77
|
+
"test:integration": "vitest run --config vite.config.ts tests/store.integration.test.ts tests/vite-user-dx.test.ts tests/task-board",
|
|
78
|
+
"test:packed-runtime": "pnpm build:all && node scripts/packed-runtime-identity.mjs",
|
|
79
|
+
"build:all": "node ../../tooling/build/run.mjs @volynets/reflex-store",
|
|
80
|
+
"check:artifacts": "node ../../tooling/build/verify-artifacts.mjs --directory .",
|
|
81
|
+
"prepack": "pnpm build && pnpm check:artifacts"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@rollup/plugin-commonjs": "^29.0.3",
|
|
85
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
86
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
87
|
+
"@rollup/plugin-swc": "^0.4.0",
|
|
88
|
+
"@swc/core": "^1.15.18",
|
|
89
|
+
"@swc/wasm": "^1.16.13",
|
|
90
|
+
"@types/node": "^26.1.2",
|
|
91
|
+
"@volynets/reflex-runtime": "workspace:*",
|
|
92
|
+
"@volynets/reflex-scheduler": "workspace:^",
|
|
93
|
+
"rollup": "^4.54.0",
|
|
94
|
+
"rollup-plugin-dts": "^6.4.1",
|
|
95
|
+
"@volynets/reflex-framework": "workspace:*"
|
|
96
|
+
},
|
|
97
|
+
"keywords": [
|
|
98
|
+
"reflex",
|
|
99
|
+
"state",
|
|
100
|
+
"store",
|
|
101
|
+
"selector",
|
|
102
|
+
"reactivity"
|
|
103
|
+
],
|
|
104
|
+
"repository": {
|
|
105
|
+
"type": "git",
|
|
106
|
+
"url": "git+https://github.com/volynetstyle/Reflex.git",
|
|
107
|
+
"directory": "packages/reflex-store"
|
|
108
|
+
},
|
|
109
|
+
"homepage": "https://github.com/volynetstyle/Reflex/tree/main/packages/reflex-store",
|
|
110
|
+
"bugs": {
|
|
111
|
+
"url": "https://github.com/volynetstyle/Reflex/issues"
|
|
112
|
+
},
|
|
113
|
+
"engines": {
|
|
114
|
+
"node": ">=20.19.0"
|
|
115
|
+
},
|
|
116
|
+
"peerDependencies": {
|
|
117
|
+
"@volynets/reflex-runtime": "^1.0.1",
|
|
118
|
+
"@volynets/reflex-framework": "^0.1.4"
|
|
119
|
+
},
|
|
120
|
+
"private": false
|
|
121
|
+
}
|