@vesk/runtime 0.2.58 → 0.2.60
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/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +20 -1
- package/dist/path-store.d.ts +123 -0
- package/dist/path-store.d.ts.map +1 -0
- package/dist/path-store.js +966 -0
- package/dist/ripple-blocks.d.ts.map +1 -1
- package/dist/ripple-blocks.js +12 -1
- package/dist/ripple-runtime.d.ts +36 -0
- package/dist/ripple-runtime.d.ts.map +1 -1
- package/dist/ripple-runtime.js +120 -3
- package/dist/store-persistence.d.ts +90 -0
- package/dist/store-persistence.d.ts.map +1 -0
- package/dist/store-persistence.js +154 -0
- package/dist/store.d.ts +61 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +275 -0
- package/package.json +23 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ripple-blocks.d.ts","sourceRoot":"","sources":["../src/ripple-blocks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ripple-blocks.d.ts","sourceRoot":"","sources":["../src/ripple-blocks.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAW,MAAM,kCAAkC,CAAC;AAGlF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,KAAK,GAAG,IAAI,CAoBxD;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,KAAK,CAE5C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,KAAK,CAEhD;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,KAAK,CAEzF;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,EAAE,KAAK,SAAI,EAAE,KAAK,GAAE,OAAc,GAAG,KAAK,CAE/F;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,KAAK,CAEzD;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAExF;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,EAAE,KAAK,SAAI,EAAE,KAAK,GAAE,OAAc,GAAG,KAAK,CAElH;AAkBD,wBAAgB,KAAK,CACpB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,EAC/B,KAAK,GAAE,OAAc,EACrB,EAAE,CAAC,EAAE,SAAS,GACZ,KAAK,CAuCP;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,UAAQ,GAAG,IAAI,CAW9E;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,UAAQ,GAAG,IAAI,CAkBnF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAY/C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAe9C;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAiB/C;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,KAAK,GAAG,OAAO,CAezD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,UAAO,GAAG,IAAI,CA+BnE"}
|
package/dist/ripple-blocks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BLOCK_HAS_RUN, BRANCH_BLOCK, DERIVED, CONTAINS_TEARDOWN, DESTROYED, EFFECT_BLOCK, PAUSED, PRE_EFFECT_BLOCK, RENDER_BLOCK, ROOT_BLOCK, TRY_BLOCK, HEAD_BLOCK, DIRECT_CHILD_BLOCK, } from '@vesk/runtime/src/ripple-constants';
|
|
2
|
-
import { active_block, active_component, active_reaction, create_component_ctx, is_block_dirty, run_block, run_teardown, schedule_update, } from '@vesk/runtime/src/ripple-runtime';
|
|
2
|
+
import { active_block, active_component, active_reaction, create_component_ctx, get_orphan_root, is_block_dirty, run_block, run_teardown, schedule_update, unsubscribe_reaction, } from '@vesk/runtime/src/ripple-runtime';
|
|
3
3
|
export function user_effect(fn) {
|
|
4
4
|
if (active_block === null) {
|
|
5
5
|
throw new Error('effect() must be called within an active context, such as a component or effect');
|
|
@@ -69,6 +69,14 @@ export function block(flags, fn, state = null, co) {
|
|
|
69
69
|
if (active_block !== null) {
|
|
70
70
|
push_block(newBlock, active_block);
|
|
71
71
|
}
|
|
72
|
+
else {
|
|
73
|
+
// Nothing was active — hang it off the orphan root instead of leaving
|
|
74
|
+
// `p === null`, where scheduling it walks an empty chain and the update
|
|
75
|
+
// is discarded. `push_block` does not set `p` (it is always the active
|
|
76
|
+
// block on the normal path), so it is set here.
|
|
77
|
+
newBlock.p = get_orphan_root();
|
|
78
|
+
push_block(newBlock, newBlock.p);
|
|
79
|
+
}
|
|
72
80
|
if ((flags & EFFECT_BLOCK) !== 0) {
|
|
73
81
|
schedule_update(newBlock);
|
|
74
82
|
}
|
|
@@ -165,6 +173,9 @@ export function is_destroyed(target_block) {
|
|
|
165
173
|
}
|
|
166
174
|
export function destroy_block(block, remove_dom = true) {
|
|
167
175
|
block.f ^= DESTROYED;
|
|
176
|
+
// A destroyed reaction must stop being a reader of any ownerless cell
|
|
177
|
+
// (module-scope store) — nothing will ever dirty-check it again.
|
|
178
|
+
unsubscribe_reaction(block);
|
|
168
179
|
let removed = false;
|
|
169
180
|
const f = block.f;
|
|
170
181
|
if ((remove_dom && (f & (BRANCH_BLOCK | ROOT_BLOCK)) !== 0 && (f & TRY_BLOCK) === 0) ||
|
package/dist/ripple-runtime.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface Tracked<T = unknown> {
|
|
|
24
24
|
c: number;
|
|
25
25
|
d: DeferredTrackedEntry[] | null;
|
|
26
26
|
f: number;
|
|
27
|
+
r: OwnerlessSubscribers | null;
|
|
27
28
|
__v: unknown;
|
|
28
29
|
get(): T;
|
|
29
30
|
set(value: T): void;
|
|
@@ -39,11 +40,19 @@ export interface Derived<T = unknown> {
|
|
|
39
40
|
co: Component | null;
|
|
40
41
|
d: Dependency | null;
|
|
41
42
|
f: number;
|
|
43
|
+
r: OwnerlessSubscribers | null;
|
|
42
44
|
fn: () => unknown;
|
|
43
45
|
__v: unknown;
|
|
44
46
|
get(): T;
|
|
45
47
|
set(value: T): void;
|
|
46
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* A cell that has no owner block — a module-scope `track()` (the documented
|
|
51
|
+
* store pattern), or any cell created while no block was active. `set()` on one
|
|
52
|
+
* cannot walk up to a root block to schedule a flush, so its readers are kept
|
|
53
|
+
* here and invalidated directly. See `schedule_ownerless_update`.
|
|
54
|
+
*/
|
|
55
|
+
export type OwnerlessSubscribers = Set<Block | Derived>;
|
|
47
56
|
export interface Dependency {
|
|
48
57
|
c: number;
|
|
49
58
|
t: Tracked | Derived;
|
|
@@ -76,6 +85,27 @@ export declare let active_scope: Block | null;
|
|
|
76
85
|
export declare let active_component: Component | null;
|
|
77
86
|
export declare let active_namespace: keyof typeof NAMESPACE_URI;
|
|
78
87
|
export declare let is_mutating_allowed: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* The block every unparented block hangs off — a block created while no block
|
|
90
|
+
* was active (a component rendered outside a `root()`, a module-scope effect,
|
|
91
|
+
* a cell created at module scope).
|
|
92
|
+
*
|
|
93
|
+
* Without it `schedule_update()` walks an empty parent chain, pushes `null`
|
|
94
|
+
* into `queued_root_blocks`, and the update is dropped on the floor: the effect
|
|
95
|
+
* never runs, and a later write to its cell schedules nothing. Call sites used
|
|
96
|
+
* to each work around this privately (`runInBlockWindow` in the router,
|
|
97
|
+
* `runInHydrateBlock` in hydrate.ts) and every new one silently reintroduced the
|
|
98
|
+
* bug. The orphan root is the single place that makes "no active block" mean
|
|
99
|
+
* "rooted" instead of "disconnected".
|
|
100
|
+
*
|
|
101
|
+
* It is a plain object rather than a `block()` call because `block()` is what
|
|
102
|
+
* asks for it. It is never destroyed and never re-run: `is_block_dirty()`
|
|
103
|
+
* short-circuits on ROOT_BLOCK, so it only ever serves as the flush root that
|
|
104
|
+
* reaches its children.
|
|
105
|
+
*/
|
|
106
|
+
export declare function get_orphan_root(): Block;
|
|
107
|
+
/** True when `block` is a block with no real owner — it lives on the orphan root. */
|
|
108
|
+
export declare function is_orphan(block: Block | null | undefined): boolean;
|
|
79
109
|
export declare let disable_scoped_flush: boolean;
|
|
80
110
|
export declare let tracking: boolean;
|
|
81
111
|
export declare let teardown: boolean;
|
|
@@ -100,6 +130,12 @@ export declare function tick(): Promise<void>;
|
|
|
100
130
|
export declare function queue_microtask(fn?: () => void): void;
|
|
101
131
|
export declare function queue_post_block_flush_callback(fn: () => void): void;
|
|
102
132
|
export declare function schedule_update(block: Block | null): void;
|
|
133
|
+
/**
|
|
134
|
+
* Drop `reaction` from every ownerless cell it read last time round. Called
|
|
135
|
+
* before a reaction re-runs (and when it is destroyed) so a dependency the new
|
|
136
|
+
* run no longer takes is not left subscribed forever.
|
|
137
|
+
*/
|
|
138
|
+
export declare function unsubscribe_reaction(reaction: Block | Derived | null): void;
|
|
103
139
|
export declare function get_derived(computed: Derived): unknown;
|
|
104
140
|
export declare function get(t: unknown): unknown;
|
|
105
141
|
export declare function get_tracked(tracked: Tracked): unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ripple-runtime.d.ts","sourceRoot":"","sources":["../src/ripple-runtime.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ripple-runtime.d.ts","sourceRoot":"","sources":["../src/ripple-runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/ripple-runtime.js
CHANGED
|
@@ -11,6 +11,48 @@ export let active_component = null;
|
|
|
11
11
|
export let active_namespace = DEFAULT_NAMESPACE;
|
|
12
12
|
export let is_mutating_allowed = true;
|
|
13
13
|
const old_values = new Map();
|
|
14
|
+
let orphan_root = null;
|
|
15
|
+
/**
|
|
16
|
+
* The block every unparented block hangs off — a block created while no block
|
|
17
|
+
* was active (a component rendered outside a `root()`, a module-scope effect,
|
|
18
|
+
* a cell created at module scope).
|
|
19
|
+
*
|
|
20
|
+
* Without it `schedule_update()` walks an empty parent chain, pushes `null`
|
|
21
|
+
* into `queued_root_blocks`, and the update is dropped on the floor: the effect
|
|
22
|
+
* never runs, and a later write to its cell schedules nothing. Call sites used
|
|
23
|
+
* to each work around this privately (`runInBlockWindow` in the router,
|
|
24
|
+
* `runInHydrateBlock` in hydrate.ts) and every new one silently reintroduced the
|
|
25
|
+
* bug. The orphan root is the single place that makes "no active block" mean
|
|
26
|
+
* "rooted" instead of "disconnected".
|
|
27
|
+
*
|
|
28
|
+
* It is a plain object rather than a `block()` call because `block()` is what
|
|
29
|
+
* asks for it. It is never destroyed and never re-run: `is_block_dirty()`
|
|
30
|
+
* short-circuits on ROOT_BLOCK, so it only ever serves as the flush root that
|
|
31
|
+
* reaches its children.
|
|
32
|
+
*/
|
|
33
|
+
export function get_orphan_root() {
|
|
34
|
+
if (orphan_root === null) {
|
|
35
|
+
orphan_root = {
|
|
36
|
+
co: null,
|
|
37
|
+
d: null,
|
|
38
|
+
first: null,
|
|
39
|
+
f: ROOT_BLOCK,
|
|
40
|
+
fn: null,
|
|
41
|
+
last: null,
|
|
42
|
+
next: null,
|
|
43
|
+
p: null,
|
|
44
|
+
prev: null,
|
|
45
|
+
s: null,
|
|
46
|
+
t: null,
|
|
47
|
+
tc: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return orphan_root;
|
|
51
|
+
}
|
|
52
|
+
/** True when `block` is a block with no real owner — it lives on the orphan root. */
|
|
53
|
+
export function is_orphan(block) {
|
|
54
|
+
return block === null || block === undefined || block === orphan_root;
|
|
55
|
+
}
|
|
14
56
|
let scheduler_mode = FLUSH_MICROTASK;
|
|
15
57
|
let is_micro_task_queued = false;
|
|
16
58
|
let clock = 0;
|
|
@@ -164,6 +206,7 @@ export function run_block(block) {
|
|
|
164
206
|
try {
|
|
165
207
|
active_block = block;
|
|
166
208
|
active_reaction = block;
|
|
209
|
+
unsubscribe_reaction(block);
|
|
167
210
|
active_component = block.co;
|
|
168
211
|
destroy_non_branch_children(block);
|
|
169
212
|
run_teardown(block);
|
|
@@ -201,6 +244,7 @@ class TrackedValue {
|
|
|
201
244
|
c;
|
|
202
245
|
d;
|
|
203
246
|
f;
|
|
247
|
+
r;
|
|
204
248
|
__v;
|
|
205
249
|
constructor(v, block, a) {
|
|
206
250
|
this.a = a;
|
|
@@ -208,6 +252,7 @@ class TrackedValue {
|
|
|
208
252
|
this.c = 0;
|
|
209
253
|
this.d = null;
|
|
210
254
|
this.f = TRACKED;
|
|
255
|
+
this.r = null;
|
|
211
256
|
this.__v = v;
|
|
212
257
|
}
|
|
213
258
|
get() {
|
|
@@ -248,6 +293,7 @@ class DerivedValue {
|
|
|
248
293
|
d;
|
|
249
294
|
f;
|
|
250
295
|
fn;
|
|
296
|
+
r;
|
|
251
297
|
__v;
|
|
252
298
|
constructor(fn, block, a) {
|
|
253
299
|
this.a = a;
|
|
@@ -258,6 +304,7 @@ class DerivedValue {
|
|
|
258
304
|
this.d = null;
|
|
259
305
|
this.f = DERIVED;
|
|
260
306
|
this.fn = fn;
|
|
307
|
+
this.r = null;
|
|
261
308
|
this.__v = UNINITIALIZED;
|
|
262
309
|
}
|
|
263
310
|
get() {
|
|
@@ -290,10 +337,13 @@ class DerivedValue {
|
|
|
290
337
|
}
|
|
291
338
|
}
|
|
292
339
|
export function tracked(v, block, get, set) {
|
|
293
|
-
|
|
340
|
+
// `block || active_block!` used to store `null` here when nothing was
|
|
341
|
+
// active, which is what made a module-scope store cell unwritable: `set()`
|
|
342
|
+
// scheduled a `null` block and stopped.
|
|
343
|
+
return new TrackedValue(v, block || active_block || get_orphan_root(), get || set ? { get, set } : empty_get_set);
|
|
294
344
|
}
|
|
295
345
|
export function derived(fn, block, get, set) {
|
|
296
|
-
return new DerivedValue(fn, block || active_block, get || set ? { get, set } : empty_get_set);
|
|
346
|
+
return new DerivedValue(fn, block || active_block || get_orphan_root(), get || set ? { get, set } : empty_get_set);
|
|
297
347
|
}
|
|
298
348
|
export function track(v, b, get, set) {
|
|
299
349
|
if (is_ripple_object(v)) {
|
|
@@ -317,6 +367,14 @@ export function track(v, b, get, set) {
|
|
|
317
367
|
}
|
|
318
368
|
function create_dependency(tracked) {
|
|
319
369
|
const reaction = active_reaction;
|
|
370
|
+
// A cell on the orphan root is a shared one — the module-scope store
|
|
371
|
+
// pattern. Its readers can sit under ANY root (a component rendered inside
|
|
372
|
+
// the router's block window reads a store created at module scope), so
|
|
373
|
+
// walking up from the cell reaches nobody. They are recorded here and
|
|
374
|
+
// invalidated directly instead. Owned cells keep the scoped-flush path.
|
|
375
|
+
if (is_orphan(tracked.b)) {
|
|
376
|
+
(tracked.r ??= new Set()).add(reaction);
|
|
377
|
+
}
|
|
320
378
|
const existing = reaction.d;
|
|
321
379
|
if (existing !== null) {
|
|
322
380
|
reaction.d = existing.n;
|
|
@@ -499,6 +557,10 @@ export function queue_post_block_flush_callback(fn) {
|
|
|
499
557
|
export function schedule_update(block) {
|
|
500
558
|
if (block === null)
|
|
501
559
|
return;
|
|
560
|
+
// Skip initializing orphan-root-scheduled blocks on SSR to avoid running
|
|
561
|
+
// effects that access the browser (e.g., "document").
|
|
562
|
+
if (block.p === get_orphan_root() && globalThis.__vsk_ssr)
|
|
563
|
+
return;
|
|
502
564
|
if (scheduler_mode === FLUSH_MICROTASK) {
|
|
503
565
|
queue_microtask();
|
|
504
566
|
}
|
|
@@ -560,6 +622,52 @@ function register_dependency(tracked) {
|
|
|
560
622
|
current.n = dependency;
|
|
561
623
|
}
|
|
562
624
|
}
|
|
625
|
+
/**
|
|
626
|
+
* Drop `reaction` from every ownerless cell it read last time round. Called
|
|
627
|
+
* before a reaction re-runs (and when it is destroyed) so a dependency the new
|
|
628
|
+
* run no longer takes is not left subscribed forever.
|
|
629
|
+
*/
|
|
630
|
+
export function unsubscribe_reaction(reaction) {
|
|
631
|
+
if (reaction === null)
|
|
632
|
+
return;
|
|
633
|
+
let dep = reaction.d;
|
|
634
|
+
while (dep !== null) {
|
|
635
|
+
const t = dep.t;
|
|
636
|
+
if (is_orphan(t.b) && t.r !== null) {
|
|
637
|
+
t.r.delete(reaction);
|
|
638
|
+
}
|
|
639
|
+
dep = dep.n;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* `set()` on a cell with no owner block: there is no parent chain to walk, so
|
|
644
|
+
* schedule the readers themselves. A reader that is itself a derived value is
|
|
645
|
+
* followed through, because re-running the derived is what bumps its clock and
|
|
646
|
+
* marks the effects that read IT dirty — the same chain the scoped walk relies
|
|
647
|
+
* on. `visited` keeps a diamond in the graph from looping.
|
|
648
|
+
*/
|
|
649
|
+
function schedule_ownerless_update(tracked, visited) {
|
|
650
|
+
const subscribers = tracked.r;
|
|
651
|
+
if (subscribers === null)
|
|
652
|
+
return;
|
|
653
|
+
// Copy first: a reader that re-runs synchronously can re-subscribe.
|
|
654
|
+
for (const reaction of Array.from(subscribers)) {
|
|
655
|
+
if (visited !== null) {
|
|
656
|
+
if (visited.has(reaction))
|
|
657
|
+
continue;
|
|
658
|
+
visited.add(reaction);
|
|
659
|
+
}
|
|
660
|
+
if (reaction.f & DESTROYED) {
|
|
661
|
+
subscribers.delete(reaction);
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if ((reaction.f & DERIVED) !== 0) {
|
|
665
|
+
schedule_ownerless_update(reaction, visited ?? new Set());
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
schedule_update(reaction);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
563
671
|
export function get_derived(computed) {
|
|
564
672
|
update_derived(computed);
|
|
565
673
|
if (tracking) {
|
|
@@ -645,7 +753,16 @@ export function set(tracked, value) {
|
|
|
645
753
|
}
|
|
646
754
|
tracked.__v = value;
|
|
647
755
|
tracked.c = increment_clock();
|
|
648
|
-
|
|
756
|
+
if (!is_orphan(tracked_block)) {
|
|
757
|
+
schedule_update(tracked_block);
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
// A shared cell (module-scope store, or any cell created with no
|
|
761
|
+
// active block) has no chain to walk up, and its readers can live
|
|
762
|
+
// under a different root than it does — scheduling it alone would
|
|
763
|
+
// reach nobody. Invalidate the readers themselves.
|
|
764
|
+
schedule_ownerless_update(tracked, null);
|
|
765
|
+
}
|
|
649
766
|
}
|
|
650
767
|
}
|
|
651
768
|
export function untrack(fn) {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared SSR persistence plumbing for `@vesk/runtime/store` (`createStore`)
|
|
3
|
+
* and `@vesk/runtime/path-store` (`createPathStore`). Both stores read and
|
|
4
|
+
* write through the same hooks so the adapter and the blocking pre-paint
|
|
5
|
+
* script serve every persisted store identically.
|
|
6
|
+
*
|
|
7
|
+
* How SSR continuity works:
|
|
8
|
+
* - On the SERVER a persisted store reads its value through an
|
|
9
|
+
* adapter-installed hook (`installPersistedStoreServerHooks`), typically
|
|
10
|
+
* backed by a request cookie, and registers its `storageKey` so the adapter
|
|
11
|
+
* can emit a blocking pre-paint script right after the render.
|
|
12
|
+
* - The pre-paint script is delivered as an ORIGIN-SERVED JavaScript file
|
|
13
|
+
* (like the `/ssr-data.js` hydration handoff), never inlined into the HTML:
|
|
14
|
+
* strict CSP without `unsafe-inline` must not block it. The adapter stashes
|
|
15
|
+
* the payload built by `buildStorePrePaintPayload` under a CSPRNG token and
|
|
16
|
+
* injects a `<script src="/store-init.js?t=<token>">` tag into `<head>`; the
|
|
17
|
+
* server serves that file once per token. It populates
|
|
18
|
+
* `window.__VESK_STORE_INIT__` before first paint from localStorage (with
|
|
19
|
+
* the server-rendered, cookie-read value as the first-visit fallback).
|
|
20
|
+
* - On the CLIENT the store prefers `window.__VESK_STORE_INIT__[key]`, then
|
|
21
|
+
* falls back to localStorage. It mirrors every write to a same-site cookie so
|
|
22
|
+
* the NEXT server render can read the user's actual value back — no theme
|
|
23
|
+
* flash, no default-content first paint, SEO-correct.
|
|
24
|
+
*
|
|
25
|
+
* Registration is bucketed per request token (`globalThis.__vsk_ssr_token`,
|
|
26
|
+
* the compiler's AsyncLocalStorage accessor) so concurrent SSR renders never
|
|
27
|
+
* mix each other's persisted keys. The adapter captures the render token at
|
|
28
|
+
* its `renderPageStream` call site and passes it to the registry drain, so
|
|
29
|
+
* even a streamed body pumped after the scope settled reports into the right
|
|
30
|
+
* bucket. Renders that genuinely run outside any scope use the default bucket.
|
|
31
|
+
*
|
|
32
|
+
* SSR-safe: nothing here touches `document`/`localStorage` at module scope,
|
|
33
|
+
* and every browser access is guarded + wrapped in try/catch. No `batch`.
|
|
34
|
+
*/
|
|
35
|
+
export type PersistedStoreRead = (storageKey: string) => string | null | undefined;
|
|
36
|
+
export type PersistedStoreHooks = {
|
|
37
|
+
read?: PersistedStoreRead;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Install server-side persistence hooks (called by the adapter). `read`
|
|
41
|
+
* returns the persisted raw JSON string (typically from a request cookie) or
|
|
42
|
+
* null. The hook is a module-level singleton that reads request-scoped state
|
|
43
|
+
* itself (e.g. `globalThis.__vesk_request`), so installing it once per module
|
|
44
|
+
* load is safe under concurrent requests.
|
|
45
|
+
*/
|
|
46
|
+
export declare function installPersistedStoreServerHooks(hooks: PersistedStoreHooks): void;
|
|
47
|
+
/** The cookie name a persisted store is mirrored to, for SSR continuity. */
|
|
48
|
+
export declare function persistedStoreCookieName(storageKey: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* The storage keys of persisted stores constructed in this SSR render.
|
|
51
|
+
* `token` overrides the ambient request token for callers that drain the
|
|
52
|
+
* registry outside the render scope (the no-layout streaming path captures
|
|
53
|
+
* the render token at its `renderPageStream` call site and passes it here).
|
|
54
|
+
*/
|
|
55
|
+
export declare function getPersistedStoreKeys(token?: string): string[];
|
|
56
|
+
/** Clear the constructed-key registry (called per SSR render by the adapter). */
|
|
57
|
+
export declare function resetPersistedStoreKeys(token?: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Read a persisted store's raw JSON for the current environment:
|
|
60
|
+
* - server: register the key (so the adapter emits the pre-paint script) and
|
|
61
|
+
* ask the installed read hook,
|
|
62
|
+
* - client: consume `window.__VESK_STORE_INIT__[key]` if present (then write
|
|
63
|
+
* it back to localStorage so a later client pickup still works), else read
|
|
64
|
+
* localStorage.
|
|
65
|
+
*/
|
|
66
|
+
export declare function readPersistedStorage(storageKey: string): string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Persist a store's raw JSON on the client: localStorage plus a same-site
|
|
69
|
+
* cookie mirror (best effort, small stores) so the next server render sees it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function writePersistedStorage(storageKey: string, raw: string): void;
|
|
72
|
+
/** Remove a persisted store's localStorage record and expire its cookie mirror. */
|
|
73
|
+
export declare function clearPersistedStorage(storageKey: string): void;
|
|
74
|
+
/**
|
|
75
|
+
* Plain JS that copies each persisted store's localStorage value into
|
|
76
|
+
* `window.__VESK_STORE_INIT__` before first paint. `serverValues` maps
|
|
77
|
+
* storageKey → the value the server rendered with (cookie read), used as a
|
|
78
|
+
* fallback so the very first client visit does not flash defaults.
|
|
79
|
+
*
|
|
80
|
+
* This is served as an origin-hosted script file (`/store-init.js?t=<token>`),
|
|
81
|
+
* never inlined into the HTML, so strict CSP without `unsafe-inline` cannot
|
|
82
|
+
* block it. Because the file has no HTML context, JSON.stringify output can be
|
|
83
|
+
* embedded directly (JSON is a valid JS literal) — no escaping needed. Safe
|
|
84
|
+
* for `</script>`-style content precisely because it never sits inside a
|
|
85
|
+
* script element.
|
|
86
|
+
*/
|
|
87
|
+
export declare function buildStorePrePaintPayload(serverValues: Record<string, string | null>): string;
|
|
88
|
+
/** The `<script src>` reference for one store-init payload token. */
|
|
89
|
+
export declare function storeInitScriptTag(token: string): string;
|
|
90
|
+
//# sourceMappingURL=store-persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-persistence.d.ts","sourceRoot":"","sources":["../src/store-persistence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAEnF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CAAC,EAAE,kBAAkB,CAAC;CAC3B,CAAC;AAIF;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAEjF;AAQD,4EAA4E;AAC5E,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnE;AAYD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAG9D;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBtE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAoB3E;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAgB9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,CAY7F;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
let serverReadHook = null;
|
|
2
|
+
/**
|
|
3
|
+
* Install server-side persistence hooks (called by the adapter). `read`
|
|
4
|
+
* returns the persisted raw JSON string (typically from a request cookie) or
|
|
5
|
+
* null. The hook is a module-level singleton that reads request-scoped state
|
|
6
|
+
* itself (e.g. `globalThis.__vesk_request`), so installing it once per module
|
|
7
|
+
* load is safe under concurrent requests.
|
|
8
|
+
*/
|
|
9
|
+
export function installPersistedStoreServerHooks(hooks) {
|
|
10
|
+
serverReadHook = typeof hooks.read === 'function' ? hooks.read : null;
|
|
11
|
+
}
|
|
12
|
+
/** The request token scoping persisted-key registrations, or '' outside a render. */
|
|
13
|
+
function currentSsrToken() {
|
|
14
|
+
const g = globalThis;
|
|
15
|
+
return typeof g.__vsk_ssr_token === 'string' ? g.__vsk_ssr_token : '';
|
|
16
|
+
}
|
|
17
|
+
/** The cookie name a persisted store is mirrored to, for SSR continuity. */
|
|
18
|
+
export function persistedStoreCookieName(storageKey) {
|
|
19
|
+
return 'vsk_store_' + encodeURIComponent(storageKey);
|
|
20
|
+
}
|
|
21
|
+
const pendingKeys = new Map();
|
|
22
|
+
function bucketFor(token) {
|
|
23
|
+
const set = pendingKeys.get(token);
|
|
24
|
+
if (set)
|
|
25
|
+
return set;
|
|
26
|
+
const fresh = new Set();
|
|
27
|
+
pendingKeys.set(token, fresh);
|
|
28
|
+
return fresh;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The storage keys of persisted stores constructed in this SSR render.
|
|
32
|
+
* `token` overrides the ambient request token for callers that drain the
|
|
33
|
+
* registry outside the render scope (the no-layout streaming path captures
|
|
34
|
+
* the render token at its `renderPageStream` call site and passes it here).
|
|
35
|
+
*/
|
|
36
|
+
export function getPersistedStoreKeys(token) {
|
|
37
|
+
const set = pendingKeys.get(token ?? currentSsrToken());
|
|
38
|
+
return set ? Array.from(set) : [];
|
|
39
|
+
}
|
|
40
|
+
/** Clear the constructed-key registry (called per SSR render by the adapter). */
|
|
41
|
+
export function resetPersistedStoreKeys(token) {
|
|
42
|
+
pendingKeys.delete(token ?? currentSsrToken());
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read a persisted store's raw JSON for the current environment:
|
|
46
|
+
* - server: register the key (so the adapter emits the pre-paint script) and
|
|
47
|
+
* ask the installed read hook,
|
|
48
|
+
* - client: consume `window.__VESK_STORE_INIT__[key]` if present (then write
|
|
49
|
+
* it back to localStorage so a later client pickup still works), else read
|
|
50
|
+
* localStorage.
|
|
51
|
+
*/
|
|
52
|
+
export function readPersistedStorage(storageKey) {
|
|
53
|
+
if (!storageKey)
|
|
54
|
+
return null;
|
|
55
|
+
if (typeof window === 'undefined') {
|
|
56
|
+
bucketFor(currentSsrToken()).add(storageKey);
|
|
57
|
+
return serverReadHook ? (serverReadHook(storageKey) ?? null) : null;
|
|
58
|
+
}
|
|
59
|
+
const init = globalThis.__VESK_STORE_INIT__;
|
|
60
|
+
if (init && init[storageKey] !== undefined) {
|
|
61
|
+
const raw = init[storageKey];
|
|
62
|
+
delete init[storageKey];
|
|
63
|
+
try {
|
|
64
|
+
localStorage.setItem(storageKey, raw);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* storage blocked — the in-memory value still applies */
|
|
68
|
+
}
|
|
69
|
+
return raw;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return localStorage.getItem(storageKey);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Persist a store's raw JSON on the client: localStorage plus a same-site
|
|
80
|
+
* cookie mirror (best effort, small stores) so the next server render sees it.
|
|
81
|
+
*/
|
|
82
|
+
export function writePersistedStorage(storageKey, raw) {
|
|
83
|
+
if (!storageKey)
|
|
84
|
+
return;
|
|
85
|
+
if (typeof window !== 'undefined') {
|
|
86
|
+
try {
|
|
87
|
+
localStorage.setItem(storageKey, raw);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
/* storage blocked — the cookie mirror still applies */
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (typeof document !== 'undefined') {
|
|
94
|
+
try {
|
|
95
|
+
document.cookie =
|
|
96
|
+
persistedStoreCookieName(storageKey) +
|
|
97
|
+
'=' +
|
|
98
|
+
encodeURIComponent(raw) +
|
|
99
|
+
'; path=/; max-age=31536000; SameSite=Lax';
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* cookie blocked — client still persists via localStorage */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Remove a persisted store's localStorage record and expire its cookie mirror. */
|
|
107
|
+
export function clearPersistedStorage(storageKey) {
|
|
108
|
+
if (!storageKey)
|
|
109
|
+
return;
|
|
110
|
+
if (typeof window !== 'undefined') {
|
|
111
|
+
try {
|
|
112
|
+
localStorage.removeItem(storageKey);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* ignore */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (typeof document !== 'undefined') {
|
|
119
|
+
try {
|
|
120
|
+
document.cookie = persistedStoreCookieName(storageKey) + '=; path=/; max-age=0';
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* ignore */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Plain JS that copies each persisted store's localStorage value into
|
|
129
|
+
* `window.__VESK_STORE_INIT__` before first paint. `serverValues` maps
|
|
130
|
+
* storageKey → the value the server rendered with (cookie read), used as a
|
|
131
|
+
* fallback so the very first client visit does not flash defaults.
|
|
132
|
+
*
|
|
133
|
+
* This is served as an origin-hosted script file (`/store-init.js?t=<token>`),
|
|
134
|
+
* never inlined into the HTML, so strict CSP without `unsafe-inline` cannot
|
|
135
|
+
* block it. Because the file has no HTML context, JSON.stringify output can be
|
|
136
|
+
* embedded directly (JSON is a valid JS literal) — no escaping needed. Safe
|
|
137
|
+
* for `</script>`-style content precisely because it never sits inside a
|
|
138
|
+
* script element.
|
|
139
|
+
*/
|
|
140
|
+
export function buildStorePrePaintPayload(serverValues) {
|
|
141
|
+
return ('globalThis.__VESK_STORE_INIT__=(function(){' +
|
|
142
|
+
'var S=' +
|
|
143
|
+
JSON.stringify(serverValues) +
|
|
144
|
+
';' +
|
|
145
|
+
'var E={};' +
|
|
146
|
+
'for(var k in S){var v=null;try{v=localStorage.getItem(k);}catch(e){}' +
|
|
147
|
+
'if(v===null)v=S[k];if(v!=null)E[k]=v;}' +
|
|
148
|
+
'return E;' +
|
|
149
|
+
'})();\n');
|
|
150
|
+
}
|
|
151
|
+
/** The `<script src>` reference for one store-init payload token. */
|
|
152
|
+
export function storeInitScriptTag(token) {
|
|
153
|
+
return '<script src="/store-init.js?t=' + token + '" data-vesk-store-init></script>';
|
|
154
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { StoreMiddleware } from '@vesk/types';
|
|
2
|
+
/**
|
|
3
|
+
* @typedef Subscriber
|
|
4
|
+
* A callback that receives the current store state or derived value.
|
|
5
|
+
* @example
|
|
6
|
+
* const unsub = store.subscribe(state => console.log(state));
|
|
7
|
+
*/
|
|
8
|
+
type Subscriber<T> = (value: T) => void;
|
|
9
|
+
/**
|
|
10
|
+
* @typedef StoreOptions
|
|
11
|
+
* Options for creating a store.
|
|
12
|
+
* @property persist Whether to persist store in localStorage (default: false)
|
|
13
|
+
* @property storageKey The key to use in localStorage if persist is true
|
|
14
|
+
* @property devtools Whether to send devtools events (default: false)
|
|
15
|
+
*/
|
|
16
|
+
interface StoreOptions {
|
|
17
|
+
persist?: boolean;
|
|
18
|
+
storageKey?: string;
|
|
19
|
+
devtools?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @typedef Middleware
|
|
23
|
+
* A function that runs before or after each update.
|
|
24
|
+
* @param state Current state of the store
|
|
25
|
+
* @param action Optional string describing the event
|
|
26
|
+
* @param payload Optional data passed to update/set
|
|
27
|
+
*/
|
|
28
|
+
type Middleware<T> = StoreMiddleware<T>;
|
|
29
|
+
/**
|
|
30
|
+
* @function createStore
|
|
31
|
+
* Creates a reactive store with optional persistence and tools like watch & middleware.
|
|
32
|
+
*
|
|
33
|
+
* @param initial The initial state object
|
|
34
|
+
* @param options Optional store options
|
|
35
|
+
* @returns Store API with get, set, update, delete, subscribe, derive, watch, use, clear, batch, reset, rehydrate, toJSON
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const counterStore = createStore({ count: 0 });
|
|
39
|
+
*/
|
|
40
|
+
export declare function createStore<T extends object>(initial: T, options?: StoreOptions): {
|
|
41
|
+
get: () => T;
|
|
42
|
+
set: (next: T, action?: string) => void;
|
|
43
|
+
update: {
|
|
44
|
+
(partial: Partial<T>): void;
|
|
45
|
+
(partialFn: (prev: T) => Partial<T>): void;
|
|
46
|
+
};
|
|
47
|
+
delete: (keys: (keyof T) | (keyof T)[], action?: string) => void;
|
|
48
|
+
clear: () => void;
|
|
49
|
+
subscribe: (fn: Subscriber<T>, selector?: (state: T) => any, equals?: (a: any, b: any) => boolean) => () => boolean;
|
|
50
|
+
watch: <K>(selector: (s: T) => K, callback: (n: K, o: K) => void, equals?: (a: K, b: K) => boolean) => () => void;
|
|
51
|
+
use: (middleware: Middleware<T>) => () => void;
|
|
52
|
+
derive: <K>(selector: (s: T) => K, equals?: (a: K, b: K) => boolean) => {
|
|
53
|
+
subscribe(cb: (v: K) => void): () => boolean;
|
|
54
|
+
};
|
|
55
|
+
batch: <R>(fn: () => R) => R;
|
|
56
|
+
reset: () => void;
|
|
57
|
+
rehydrate: (newInitial: Partial<T>) => void;
|
|
58
|
+
toJSON: () => T;
|
|
59
|
+
};
|
|
60
|
+
export {};
|
|
61
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;;GAKG;AACH,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAExC;;;;;;GAMG;AACH,UAAU,YAAY;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,KAAK,UAAU,CAAC,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAC1C,OAAO,EAAE,CAAC,EACV,OAAO,GAAE,YAAiB;eAqFV,CAAC;gBAKE,CAAC;;kBAaK,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;oBACf,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;;mBAe/B,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE;;oBA2D3C,UAAU,CAAC,CAAC,CAAC,aACN,CAAC,KAAK,EAAE,CAAC,KAAK,GAAG,WACnB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,KAAK,OAAO;YAgCvB,CAAC,YACJ,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,YACX,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,WACrB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO;sBAoBT,UAAU,CAAC,CAAC,CAAC;aAgBtB,CAAC,YACL,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,WACZ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO;sBAGhB,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI;;YAtGjB,CAAC,MAAM,MAAM,CAAC,KAAG,CAAC;;4BAhBF,OAAO,CAAC,CAAC,CAAC;;EAoJ1C"}
|