@pulse-js/core 0.1.9 → 0.2.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.
- package/dist/index.cjs +49 -70
- package/dist/index.d.cts +149 -142
- package/dist/index.d.ts +149 -142
- package/dist/index.js +49 -70
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -95,7 +95,7 @@ function guardFail(reason) {
|
|
|
95
95
|
function guardOk(value) {
|
|
96
96
|
return value;
|
|
97
97
|
}
|
|
98
|
-
function guard(nameOrFn, fn
|
|
98
|
+
function guard(nameOrFn, fn) {
|
|
99
99
|
const name = typeof nameOrFn === "string" ? nameOrFn : void 0;
|
|
100
100
|
const evaluator = typeof nameOrFn === "function" ? nameOrFn : fn;
|
|
101
101
|
if (!evaluator) {
|
|
@@ -259,11 +259,20 @@ function guard(nameOrFn, fn, _internalOffset = 3) {
|
|
|
259
259
|
lastDeps.forEach((dep) => {
|
|
260
260
|
const depName = dep._name || "unnamed";
|
|
261
261
|
const isG = "state" in dep;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
if (isG) {
|
|
263
|
+
const depState = dep.state();
|
|
264
|
+
deps.push({
|
|
265
|
+
name: depName,
|
|
266
|
+
type: "guard",
|
|
267
|
+
status: depState.status,
|
|
268
|
+
reason: depState.status === "fail" ? depState.reason : void 0
|
|
269
|
+
});
|
|
270
|
+
} else {
|
|
271
|
+
deps.push({
|
|
272
|
+
name: depName,
|
|
273
|
+
type: "source"
|
|
274
|
+
});
|
|
275
|
+
}
|
|
267
276
|
});
|
|
268
277
|
return {
|
|
269
278
|
name: name || "guard",
|
|
@@ -288,6 +297,13 @@ function guard(nameOrFn, fn, _internalOffset = 3) {
|
|
|
288
297
|
evaluate2();
|
|
289
298
|
return g;
|
|
290
299
|
}
|
|
300
|
+
guard.map = function(source2, mapper, name) {
|
|
301
|
+
const guardName = name || `map-${source2._name || "source"}`;
|
|
302
|
+
return guard(guardName, () => {
|
|
303
|
+
const value = source2();
|
|
304
|
+
return mapper(value);
|
|
305
|
+
});
|
|
306
|
+
};
|
|
291
307
|
|
|
292
308
|
// src/registry.ts
|
|
293
309
|
var Registry = class {
|
|
@@ -295,53 +311,23 @@ var Registry = class {
|
|
|
295
311
|
listeners = /* @__PURE__ */ new Set();
|
|
296
312
|
currentGeneration = 0;
|
|
297
313
|
cleanupScheduled = false;
|
|
298
|
-
autoNameCache = /* @__PURE__ */ new Map();
|
|
299
314
|
/**
|
|
300
|
-
*
|
|
301
|
-
* Uses file path and line number to ensure the same location always gets the same name.
|
|
302
|
-
* Cached to avoid repeated stack trace parsing.
|
|
303
|
-
*/
|
|
304
|
-
generateAutoName(type, offset = 3) {
|
|
305
|
-
const err = new Error();
|
|
306
|
-
const stack = err.stack?.split("\n") || [];
|
|
307
|
-
let callSite = stack[offset]?.trim() || "";
|
|
308
|
-
const cacheKey = `${type}:${callSite}`;
|
|
309
|
-
if (this.autoNameCache.has(cacheKey)) {
|
|
310
|
-
return this.autoNameCache.get(cacheKey);
|
|
311
|
-
}
|
|
312
|
-
const match = callSite.match(/([^/\\]+)\.(?:ts|tsx|js|jsx):(\d+):\d+/);
|
|
313
|
-
let name;
|
|
314
|
-
if (match) {
|
|
315
|
-
const filename = match[1];
|
|
316
|
-
const line = match[2];
|
|
317
|
-
if (filename && line) {
|
|
318
|
-
name = `${type}@${filename}:${line}`;
|
|
319
|
-
} else {
|
|
320
|
-
name = `${type}#${Math.random().toString(36).substring(2, 7)}`;
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
name = `${type}#${Math.random().toString(36).substring(2, 7)}`;
|
|
324
|
-
}
|
|
325
|
-
this.autoNameCache.set(cacheKey, name);
|
|
326
|
-
return name;
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Increments generation and schedules cleanup of old units.
|
|
330
|
-
* Called automatically when HMR is detected.
|
|
315
|
+
* Schedules cleanup of units that weren't re-registered (deleted from code).
|
|
331
316
|
*/
|
|
332
317
|
scheduleCleanup() {
|
|
333
318
|
if (this.cleanupScheduled) return;
|
|
334
319
|
this.cleanupScheduled = true;
|
|
335
|
-
this.currentGeneration++;
|
|
336
320
|
setTimeout(() => {
|
|
337
|
-
this.
|
|
321
|
+
this.cleanupDeadUnits();
|
|
338
322
|
this.cleanupScheduled = false;
|
|
339
323
|
}, 100);
|
|
340
324
|
}
|
|
341
325
|
/**
|
|
342
|
-
* Removes units
|
|
326
|
+
* Removes units that weren't re-registered in the current generation.
|
|
327
|
+
* Uses mark-and-sweep: units that were re-registered have current generation,
|
|
328
|
+
* units that weren't are from old generation and should be removed.
|
|
343
329
|
*/
|
|
344
|
-
|
|
330
|
+
cleanupDeadUnits() {
|
|
345
331
|
const toDelete = [];
|
|
346
332
|
this.units.forEach((unit, key) => {
|
|
347
333
|
const gen = unit._generation;
|
|
@@ -351,53 +337,46 @@ var Registry = class {
|
|
|
351
337
|
});
|
|
352
338
|
toDelete.forEach((key) => this.units.delete(key));
|
|
353
339
|
if (toDelete.length > 0) {
|
|
354
|
-
console.log(`[Pulse] Cleaned up ${toDelete.length}
|
|
340
|
+
console.log(`[Pulse] Cleaned up ${toDelete.length} deleted units after HMR`);
|
|
355
341
|
}
|
|
356
342
|
}
|
|
357
343
|
/**
|
|
358
|
-
* Registers a
|
|
359
|
-
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
344
|
+
* Registers a unit (only if it has an explicit name).
|
|
360
345
|
*/
|
|
361
|
-
register(unit
|
|
346
|
+
register(unit) {
|
|
362
347
|
const unitWithMetadata = unit;
|
|
363
|
-
|
|
348
|
+
const name = unitWithMetadata._name;
|
|
364
349
|
if (!name) {
|
|
365
|
-
|
|
366
|
-
name = this.generateAutoName(isGuard2 ? "guard" : "source", offset);
|
|
367
|
-
unitWithMetadata._name = name;
|
|
350
|
+
return;
|
|
368
351
|
}
|
|
369
|
-
|
|
370
|
-
if (
|
|
352
|
+
const existingUnit = this.units.get(name);
|
|
353
|
+
if (existingUnit) {
|
|
354
|
+
const existingGen = existingUnit?._generation;
|
|
355
|
+
if (existingGen === this.currentGeneration) {
|
|
356
|
+
unitWithMetadata._generation = this.currentGeneration;
|
|
357
|
+
this.units.set(name, unit);
|
|
358
|
+
this.listeners.forEach((l) => l(unit));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
this.currentGeneration++;
|
|
371
362
|
this.scheduleCleanup();
|
|
372
363
|
}
|
|
364
|
+
unitWithMetadata._generation = this.currentGeneration;
|
|
373
365
|
this.units.set(name, unit);
|
|
374
366
|
this.listeners.forEach((l) => l(unit));
|
|
375
367
|
}
|
|
376
|
-
/**
|
|
377
|
-
* Retrieves all registered units.
|
|
378
|
-
*/
|
|
379
368
|
getAll() {
|
|
380
369
|
return Array.from(this.units.values());
|
|
381
370
|
}
|
|
382
|
-
/**
|
|
383
|
-
* Subscribes to new unit registrations.
|
|
384
|
-
*
|
|
385
|
-
* @param listener - Callback receiving the newly registered unit.
|
|
386
|
-
* @returns Unsubscribe function.
|
|
387
|
-
*/
|
|
388
371
|
onRegister(listener) {
|
|
389
372
|
this.listeners.add(listener);
|
|
390
373
|
return () => {
|
|
391
374
|
this.listeners.delete(listener);
|
|
392
375
|
};
|
|
393
376
|
}
|
|
394
|
-
/**
|
|
395
|
-
* Clears all registered units.
|
|
396
|
-
*/
|
|
397
377
|
reset() {
|
|
398
378
|
this.units.clear();
|
|
399
379
|
this.currentGeneration = 0;
|
|
400
|
-
this.autoNameCache.clear();
|
|
401
380
|
}
|
|
402
381
|
};
|
|
403
382
|
var GLOBAL_KEY = "__PULSE_REGISTRY__";
|
|
@@ -408,7 +387,7 @@ if (!globalSymbols[GLOBAL_KEY]) {
|
|
|
408
387
|
var PulseRegistry = globalSymbols[GLOBAL_KEY];
|
|
409
388
|
|
|
410
389
|
// src/source.ts
|
|
411
|
-
function source(initialValue, options = {}
|
|
390
|
+
function source(initialValue, options = {}) {
|
|
412
391
|
let value = initialValue;
|
|
413
392
|
const subscribers = /* @__PURE__ */ new Set();
|
|
414
393
|
const dependents = /* @__PURE__ */ new Set();
|
|
@@ -447,7 +426,7 @@ function compute(name, dependencies, processor) {
|
|
|
447
426
|
return guard(name, () => {
|
|
448
427
|
const values = dependencies.map((dep) => typeof dep === "function" ? dep() : dep);
|
|
449
428
|
return processor(...values);
|
|
450
|
-
}
|
|
429
|
+
});
|
|
451
430
|
}
|
|
452
431
|
|
|
453
432
|
// src/composition.ts
|
|
@@ -470,7 +449,7 @@ function guardAll(nameOrGuards, maybeGuards) {
|
|
|
470
449
|
throw new Error(message);
|
|
471
450
|
}
|
|
472
451
|
return true;
|
|
473
|
-
}
|
|
452
|
+
});
|
|
474
453
|
}
|
|
475
454
|
function guardAny(nameOrGuards, maybeGuards) {
|
|
476
455
|
const name = typeof nameOrGuards === "string" ? nameOrGuards : void 0;
|
|
@@ -484,7 +463,7 @@ function guardAny(nameOrGuards, maybeGuards) {
|
|
|
484
463
|
allFails.push(message);
|
|
485
464
|
}
|
|
486
465
|
throw new Error(allFails.length > 0 ? allFails.join(" and ") : "no conditions met");
|
|
487
|
-
}
|
|
466
|
+
});
|
|
488
467
|
}
|
|
489
468
|
function guardNot(nameOrTarget, maybeTarget) {
|
|
490
469
|
const name = typeof nameOrTarget === "string" ? nameOrTarget : void 0;
|
|
@@ -494,7 +473,7 @@ function guardNot(nameOrTarget, maybeTarget) {
|
|
|
494
473
|
return !target.ok();
|
|
495
474
|
}
|
|
496
475
|
return !target();
|
|
497
|
-
}
|
|
476
|
+
});
|
|
498
477
|
}
|
|
499
478
|
var guardExtensions = {
|
|
500
479
|
all: guardAll,
|
package/dist/index.d.cts
CHANGED
|
@@ -41,6 +41,118 @@ declare function runInContext<T>(guard: GuardNode, fn: () => T): T;
|
|
|
41
41
|
*/
|
|
42
42
|
declare function getCurrentGuard(): GuardNode | null;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Options for configuring a Pulse Source.
|
|
46
|
+
*
|
|
47
|
+
* @template T - The type of value stored in the source.
|
|
48
|
+
*/
|
|
49
|
+
interface SourceOptions<T> {
|
|
50
|
+
/**
|
|
51
|
+
* A descriptive name for the source.
|
|
52
|
+
* Required for SSR hydration and highly recommended for debugging in DevTools.
|
|
53
|
+
*/
|
|
54
|
+
name?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Custom equality function to determine if a value has changed.
|
|
57
|
+
* By default, Pulse uses strict equality (`===`).
|
|
58
|
+
*
|
|
59
|
+
* @param a - The current value.
|
|
60
|
+
* @param b - The new value.
|
|
61
|
+
* @returns `true` if the values are considered equal, `false` otherwise.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const list = source([1], {
|
|
66
|
+
* equals: (a, b) => a.length === b.length
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
equals?: (a: T, b: T) => boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A Pulse Source is a reactive container for a value.
|
|
74
|
+
* It tracks which Guards read its value and notifies them when it changes.
|
|
75
|
+
*
|
|
76
|
+
* @template T The type of the value held by the source.
|
|
77
|
+
*/
|
|
78
|
+
interface Source<T> {
|
|
79
|
+
/**
|
|
80
|
+
* Returns the current value of the source.
|
|
81
|
+
* If called within a Guard evaluation, it automatically registers that Guard as a dependent.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const count = source(0);
|
|
86
|
+
* console.log(count()); // 0
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
(): T;
|
|
90
|
+
/**
|
|
91
|
+
* Updates the source with a new value.
|
|
92
|
+
* If the value is different (based on strict equality or `options.equals`),
|
|
93
|
+
* all dependent Guards and subscribers will be notified.
|
|
94
|
+
*
|
|
95
|
+
* @param value The new value to set.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const count = source(0);
|
|
100
|
+
* count.set(1); // Triggers re-evaluation of dependents
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @error
|
|
104
|
+
* Common error: Mutating an object property without setting a new object reference.
|
|
105
|
+
* Pulse uses reference equality by default. If you mutate a property, Pulse won't know it changed.
|
|
106
|
+
* Solution: Always provide a new object or implement a custom `equals`.
|
|
107
|
+
*/
|
|
108
|
+
set(value: T): void;
|
|
109
|
+
/**
|
|
110
|
+
* Updates the source value using a transformer function based on the current value.
|
|
111
|
+
* Useful for increments or toggles.
|
|
112
|
+
*
|
|
113
|
+
* @param updater A function that receives the current value and returns the new value.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const count = source(0);
|
|
118
|
+
* count.update(n => n + 1);
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
update(updater: (current: T) => T): void;
|
|
122
|
+
/**
|
|
123
|
+
* Manually subscribes to changes in the source value.
|
|
124
|
+
*
|
|
125
|
+
* @param listener A callback that receives the new value.
|
|
126
|
+
* @returns An unsubscription function.
|
|
127
|
+
*
|
|
128
|
+
* @note Most users should use `guard()` or `usePulse()` instead of manual subscriptions.
|
|
129
|
+
*/
|
|
130
|
+
subscribe(listener: Subscriber<T>): () => void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Creates a new Pulse Source.
|
|
134
|
+
*
|
|
135
|
+
* Sources are the fundamental building blocks of state in Pulse. They hold a value
|
|
136
|
+
* and track which Guards depend on them.
|
|
137
|
+
*
|
|
138
|
+
* @template T - The type of value to store.
|
|
139
|
+
* @param initialValue - The initial state.
|
|
140
|
+
* @param options - Configuration options (name, equality).
|
|
141
|
+
* @returns A reactive Pulse Source.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* const user = source({ name: 'Alice' }, { name: 'user_state' });
|
|
146
|
+
*
|
|
147
|
+
* // Read value (auto-tracks if inside a guard)
|
|
148
|
+
* console.log(user());
|
|
149
|
+
*
|
|
150
|
+
* // Update value
|
|
151
|
+
* user.set({ name: 'Bob' });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
declare function source<T>(initialValue: T, options?: SourceOptions<T>): Source<T>;
|
|
155
|
+
|
|
44
156
|
/**
|
|
45
157
|
* Status of a Pulse Guard evaluation.
|
|
46
158
|
* - 'pending': Async evaluation is in progress.
|
|
@@ -86,6 +198,7 @@ interface GuardExplanation {
|
|
|
86
198
|
name: string;
|
|
87
199
|
type: 'source' | 'guard';
|
|
88
200
|
status?: GuardStatus;
|
|
201
|
+
reason?: string | GuardReason;
|
|
89
202
|
}>;
|
|
90
203
|
}
|
|
91
204
|
/**
|
|
@@ -186,7 +299,10 @@ declare function guardFail(reason: string | GuardReason): never;
|
|
|
186
299
|
* Returns the value passed to it.
|
|
187
300
|
*/
|
|
188
301
|
declare function guardOk<T>(value: T): T;
|
|
189
|
-
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T
|
|
302
|
+
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T>): Guard<T>;
|
|
303
|
+
declare namespace guard {
|
|
304
|
+
var map: <T, U>(source: Source<T>, mapper: (value: T) => U | Promise<U>, name?: string) => Guard<U>;
|
|
305
|
+
}
|
|
190
306
|
|
|
191
307
|
/**
|
|
192
308
|
* Utility to transform reactive dependencies into a new derived value.
|
|
@@ -251,118 +367,6 @@ declare function guardAny(nameOrGuards: string | Guard<any>[], maybeGuards?: Gua
|
|
|
251
367
|
*/
|
|
252
368
|
declare function guardNot(nameOrTarget: string | Guard<any> | (() => any), maybeTarget?: Guard<any> | (() => any)): Guard<boolean>;
|
|
253
369
|
|
|
254
|
-
/**
|
|
255
|
-
* Options for configuring a Pulse Source.
|
|
256
|
-
*
|
|
257
|
-
* @template T - The type of value stored in the source.
|
|
258
|
-
*/
|
|
259
|
-
interface SourceOptions<T> {
|
|
260
|
-
/**
|
|
261
|
-
* A descriptive name for the source.
|
|
262
|
-
* Required for SSR hydration and highly recommended for debugging in DevTools.
|
|
263
|
-
*/
|
|
264
|
-
name?: string;
|
|
265
|
-
/**
|
|
266
|
-
* Custom equality function to determine if a value has changed.
|
|
267
|
-
* By default, Pulse uses strict equality (`===`).
|
|
268
|
-
*
|
|
269
|
-
* @param a - The current value.
|
|
270
|
-
* @param b - The new value.
|
|
271
|
-
* @returns `true` if the values are considered equal, `false` otherwise.
|
|
272
|
-
*
|
|
273
|
-
* @example
|
|
274
|
-
* ```ts
|
|
275
|
-
* const list = source([1], {
|
|
276
|
-
* equals: (a, b) => a.length === b.length
|
|
277
|
-
* });
|
|
278
|
-
* ```
|
|
279
|
-
*/
|
|
280
|
-
equals?: (a: T, b: T) => boolean;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* A Pulse Source is a reactive container for a value.
|
|
284
|
-
* It tracks which Guards read its value and notifies them when it changes.
|
|
285
|
-
*
|
|
286
|
-
* @template T The type of the value held by the source.
|
|
287
|
-
*/
|
|
288
|
-
interface Source<T> {
|
|
289
|
-
/**
|
|
290
|
-
* Returns the current value of the source.
|
|
291
|
-
* If called within a Guard evaluation, it automatically registers that Guard as a dependent.
|
|
292
|
-
*
|
|
293
|
-
* @example
|
|
294
|
-
* ```ts
|
|
295
|
-
* const count = source(0);
|
|
296
|
-
* console.log(count()); // 0
|
|
297
|
-
* ```
|
|
298
|
-
*/
|
|
299
|
-
(): T;
|
|
300
|
-
/**
|
|
301
|
-
* Updates the source with a new value.
|
|
302
|
-
* If the value is different (based on strict equality or `options.equals`),
|
|
303
|
-
* all dependent Guards and subscribers will be notified.
|
|
304
|
-
*
|
|
305
|
-
* @param value The new value to set.
|
|
306
|
-
*
|
|
307
|
-
* @example
|
|
308
|
-
* ```ts
|
|
309
|
-
* const count = source(0);
|
|
310
|
-
* count.set(1); // Triggers re-evaluation of dependents
|
|
311
|
-
* ```
|
|
312
|
-
*
|
|
313
|
-
* @error
|
|
314
|
-
* Common error: Mutating an object property without setting a new object reference.
|
|
315
|
-
* Pulse uses reference equality by default. If you mutate a property, Pulse won't know it changed.
|
|
316
|
-
* Solution: Always provide a new object or implement a custom `equals`.
|
|
317
|
-
*/
|
|
318
|
-
set(value: T): void;
|
|
319
|
-
/**
|
|
320
|
-
* Updates the source value using a transformer function based on the current value.
|
|
321
|
-
* Useful for increments or toggles.
|
|
322
|
-
*
|
|
323
|
-
* @param updater A function that receives the current value and returns the new value.
|
|
324
|
-
*
|
|
325
|
-
* @example
|
|
326
|
-
* ```ts
|
|
327
|
-
* const count = source(0);
|
|
328
|
-
* count.update(n => n + 1);
|
|
329
|
-
* ```
|
|
330
|
-
*/
|
|
331
|
-
update(updater: (current: T) => T): void;
|
|
332
|
-
/**
|
|
333
|
-
* Manually subscribes to changes in the source value.
|
|
334
|
-
*
|
|
335
|
-
* @param listener A callback that receives the new value.
|
|
336
|
-
* @returns An unsubscription function.
|
|
337
|
-
*
|
|
338
|
-
* @note Most users should use `guard()` or `usePulse()` instead of manual subscriptions.
|
|
339
|
-
*/
|
|
340
|
-
subscribe(listener: Subscriber<T>): () => void;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Creates a new Pulse Source.
|
|
344
|
-
*
|
|
345
|
-
* Sources are the fundamental building blocks of state in Pulse. They hold a value
|
|
346
|
-
* and track which Guards depend on them.
|
|
347
|
-
*
|
|
348
|
-
* @template T - The type of value to store.
|
|
349
|
-
* @param initialValue - The initial state.
|
|
350
|
-
* @param options - Configuration options (name, equality).
|
|
351
|
-
* @returns A reactive Pulse Source.
|
|
352
|
-
*
|
|
353
|
-
* @example
|
|
354
|
-
* ```ts
|
|
355
|
-
* const user = source({ name: 'Alice' }, { name: 'user_state' });
|
|
356
|
-
*
|
|
357
|
-
* // Read value (auto-tracks if inside a guard)
|
|
358
|
-
* console.log(user());
|
|
359
|
-
*
|
|
360
|
-
* // Update value
|
|
361
|
-
* user.set({ name: 'Bob' });
|
|
362
|
-
* ```
|
|
363
|
-
*/
|
|
364
|
-
declare function source<T>(initialValue: T, options?: SourceOptions<T>, _internalOffset?: number): Source<T>;
|
|
365
|
-
|
|
366
370
|
/**
|
|
367
371
|
* Serialized state of guards for transfer from server to client.
|
|
368
372
|
*/
|
|
@@ -416,53 +420,56 @@ type PulseUnit = Source<any> | Guard<any>;
|
|
|
416
420
|
/**
|
|
417
421
|
* Root Registry for Pulse.
|
|
418
422
|
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
423
|
+
* Tracks all registered Units (Sources and Guards) globally for DevTools.
|
|
424
|
+
*
|
|
425
|
+
* **IMPORTANT**: Only units with explicit names are registered and visible in DevTools.
|
|
426
|
+
* Unnamed units work perfectly but are not tracked to avoid HMR instability.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```ts
|
|
430
|
+
* // ✅ Visible in DevTools
|
|
431
|
+
* const count = source(0, { name: 'count' });
|
|
432
|
+
*
|
|
433
|
+
* // ❌ Not visible in DevTools (but works fine)
|
|
434
|
+
* const temp = source(0);
|
|
435
|
+
* ```
|
|
421
436
|
*/
|
|
422
437
|
declare class Registry {
|
|
423
438
|
private units;
|
|
424
439
|
private listeners;
|
|
425
440
|
private currentGeneration;
|
|
426
441
|
private cleanupScheduled;
|
|
427
|
-
private autoNameCache;
|
|
428
|
-
/**
|
|
429
|
-
* Generates a stable auto-name based on source code location.
|
|
430
|
-
* Uses file path and line number to ensure the same location always gets the same name.
|
|
431
|
-
* Cached to avoid repeated stack trace parsing.
|
|
432
|
-
*/
|
|
433
|
-
generateAutoName(type: 'source' | 'guard', offset?: number): string;
|
|
434
442
|
/**
|
|
435
|
-
*
|
|
436
|
-
* Called automatically when HMR is detected.
|
|
443
|
+
* Schedules cleanup of units that weren't re-registered (deleted from code).
|
|
437
444
|
*/
|
|
438
445
|
private scheduleCleanup;
|
|
439
446
|
/**
|
|
440
|
-
* Removes units
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Registers a new unit (Source or Guard).
|
|
445
|
-
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
447
|
+
* Removes units that weren't re-registered in the current generation.
|
|
448
|
+
* Uses mark-and-sweep: units that were re-registered have current generation,
|
|
449
|
+
* units that weren't are from old generation and should be removed.
|
|
446
450
|
*/
|
|
447
|
-
|
|
451
|
+
private cleanupDeadUnits;
|
|
448
452
|
/**
|
|
449
|
-
*
|
|
453
|
+
* Registers a unit (only if it has an explicit name).
|
|
450
454
|
*/
|
|
455
|
+
register(unit: PulseUnit): void;
|
|
451
456
|
getAll(): PulseUnit[];
|
|
452
|
-
/**
|
|
453
|
-
* Subscribes to new unit registrations.
|
|
454
|
-
*
|
|
455
|
-
* @param listener - Callback receiving the newly registered unit.
|
|
456
|
-
* @returns Unsubscribe function.
|
|
457
|
-
*/
|
|
458
457
|
onRegister(listener: (unit: PulseUnit) => void): () => void;
|
|
459
|
-
/**
|
|
460
|
-
* Clears all registered units.
|
|
461
|
-
*/
|
|
462
458
|
reset(): void;
|
|
463
459
|
}
|
|
464
460
|
declare const PulseRegistry: Registry;
|
|
465
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Extracts the result type T from a Guard<T>.
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* const authGuard = guard('auth', async () => fetchUser());
|
|
468
|
+
* type AuthUser = InferGuardType<typeof authGuard>; // User
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
type InferGuardType<T> = T extends Guard<infer U> ? U : never;
|
|
472
|
+
|
|
466
473
|
/**
|
|
467
474
|
* Pulse Guard with integrated Composition Helpers.
|
|
468
475
|
*
|
|
@@ -482,4 +489,4 @@ declare const extendedGuard: typeof guard & {
|
|
|
482
489
|
compute: typeof compute;
|
|
483
490
|
};
|
|
484
491
|
|
|
485
|
-
export { type Guard, type GuardExplanation, type GuardNode, type GuardReason, type GuardState, type GuardStatus, type HydrationState, PulseRegistry, type PulseUnit, type Source, type SourceOptions, type Subscriber, type Trackable, compute, evaluate, getCurrentGuard, extendedGuard as guard, guardFail, guardOk, hydrate, registerGuardForHydration, runInContext, source };
|
|
492
|
+
export { type Guard, type GuardExplanation, type GuardNode, type GuardReason, type GuardState, type GuardStatus, type HydrationState, type InferGuardType, PulseRegistry, type PulseUnit, type Source, type SourceOptions, type Subscriber, type Trackable, compute, evaluate, getCurrentGuard, extendedGuard as guard, guardFail, guardOk, hydrate, registerGuardForHydration, runInContext, source };
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,118 @@ declare function runInContext<T>(guard: GuardNode, fn: () => T): T;
|
|
|
41
41
|
*/
|
|
42
42
|
declare function getCurrentGuard(): GuardNode | null;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Options for configuring a Pulse Source.
|
|
46
|
+
*
|
|
47
|
+
* @template T - The type of value stored in the source.
|
|
48
|
+
*/
|
|
49
|
+
interface SourceOptions<T> {
|
|
50
|
+
/**
|
|
51
|
+
* A descriptive name for the source.
|
|
52
|
+
* Required for SSR hydration and highly recommended for debugging in DevTools.
|
|
53
|
+
*/
|
|
54
|
+
name?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Custom equality function to determine if a value has changed.
|
|
57
|
+
* By default, Pulse uses strict equality (`===`).
|
|
58
|
+
*
|
|
59
|
+
* @param a - The current value.
|
|
60
|
+
* @param b - The new value.
|
|
61
|
+
* @returns `true` if the values are considered equal, `false` otherwise.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const list = source([1], {
|
|
66
|
+
* equals: (a, b) => a.length === b.length
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
equals?: (a: T, b: T) => boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A Pulse Source is a reactive container for a value.
|
|
74
|
+
* It tracks which Guards read its value and notifies them when it changes.
|
|
75
|
+
*
|
|
76
|
+
* @template T The type of the value held by the source.
|
|
77
|
+
*/
|
|
78
|
+
interface Source<T> {
|
|
79
|
+
/**
|
|
80
|
+
* Returns the current value of the source.
|
|
81
|
+
* If called within a Guard evaluation, it automatically registers that Guard as a dependent.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const count = source(0);
|
|
86
|
+
* console.log(count()); // 0
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
(): T;
|
|
90
|
+
/**
|
|
91
|
+
* Updates the source with a new value.
|
|
92
|
+
* If the value is different (based on strict equality or `options.equals`),
|
|
93
|
+
* all dependent Guards and subscribers will be notified.
|
|
94
|
+
*
|
|
95
|
+
* @param value The new value to set.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const count = source(0);
|
|
100
|
+
* count.set(1); // Triggers re-evaluation of dependents
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @error
|
|
104
|
+
* Common error: Mutating an object property without setting a new object reference.
|
|
105
|
+
* Pulse uses reference equality by default. If you mutate a property, Pulse won't know it changed.
|
|
106
|
+
* Solution: Always provide a new object or implement a custom `equals`.
|
|
107
|
+
*/
|
|
108
|
+
set(value: T): void;
|
|
109
|
+
/**
|
|
110
|
+
* Updates the source value using a transformer function based on the current value.
|
|
111
|
+
* Useful for increments or toggles.
|
|
112
|
+
*
|
|
113
|
+
* @param updater A function that receives the current value and returns the new value.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const count = source(0);
|
|
118
|
+
* count.update(n => n + 1);
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
update(updater: (current: T) => T): void;
|
|
122
|
+
/**
|
|
123
|
+
* Manually subscribes to changes in the source value.
|
|
124
|
+
*
|
|
125
|
+
* @param listener A callback that receives the new value.
|
|
126
|
+
* @returns An unsubscription function.
|
|
127
|
+
*
|
|
128
|
+
* @note Most users should use `guard()` or `usePulse()` instead of manual subscriptions.
|
|
129
|
+
*/
|
|
130
|
+
subscribe(listener: Subscriber<T>): () => void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Creates a new Pulse Source.
|
|
134
|
+
*
|
|
135
|
+
* Sources are the fundamental building blocks of state in Pulse. They hold a value
|
|
136
|
+
* and track which Guards depend on them.
|
|
137
|
+
*
|
|
138
|
+
* @template T - The type of value to store.
|
|
139
|
+
* @param initialValue - The initial state.
|
|
140
|
+
* @param options - Configuration options (name, equality).
|
|
141
|
+
* @returns A reactive Pulse Source.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* const user = source({ name: 'Alice' }, { name: 'user_state' });
|
|
146
|
+
*
|
|
147
|
+
* // Read value (auto-tracks if inside a guard)
|
|
148
|
+
* console.log(user());
|
|
149
|
+
*
|
|
150
|
+
* // Update value
|
|
151
|
+
* user.set({ name: 'Bob' });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
declare function source<T>(initialValue: T, options?: SourceOptions<T>): Source<T>;
|
|
155
|
+
|
|
44
156
|
/**
|
|
45
157
|
* Status of a Pulse Guard evaluation.
|
|
46
158
|
* - 'pending': Async evaluation is in progress.
|
|
@@ -86,6 +198,7 @@ interface GuardExplanation {
|
|
|
86
198
|
name: string;
|
|
87
199
|
type: 'source' | 'guard';
|
|
88
200
|
status?: GuardStatus;
|
|
201
|
+
reason?: string | GuardReason;
|
|
89
202
|
}>;
|
|
90
203
|
}
|
|
91
204
|
/**
|
|
@@ -186,7 +299,10 @@ declare function guardFail(reason: string | GuardReason): never;
|
|
|
186
299
|
* Returns the value passed to it.
|
|
187
300
|
*/
|
|
188
301
|
declare function guardOk<T>(value: T): T;
|
|
189
|
-
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T
|
|
302
|
+
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T>): Guard<T>;
|
|
303
|
+
declare namespace guard {
|
|
304
|
+
var map: <T, U>(source: Source<T>, mapper: (value: T) => U | Promise<U>, name?: string) => Guard<U>;
|
|
305
|
+
}
|
|
190
306
|
|
|
191
307
|
/**
|
|
192
308
|
* Utility to transform reactive dependencies into a new derived value.
|
|
@@ -251,118 +367,6 @@ declare function guardAny(nameOrGuards: string | Guard<any>[], maybeGuards?: Gua
|
|
|
251
367
|
*/
|
|
252
368
|
declare function guardNot(nameOrTarget: string | Guard<any> | (() => any), maybeTarget?: Guard<any> | (() => any)): Guard<boolean>;
|
|
253
369
|
|
|
254
|
-
/**
|
|
255
|
-
* Options for configuring a Pulse Source.
|
|
256
|
-
*
|
|
257
|
-
* @template T - The type of value stored in the source.
|
|
258
|
-
*/
|
|
259
|
-
interface SourceOptions<T> {
|
|
260
|
-
/**
|
|
261
|
-
* A descriptive name for the source.
|
|
262
|
-
* Required for SSR hydration and highly recommended for debugging in DevTools.
|
|
263
|
-
*/
|
|
264
|
-
name?: string;
|
|
265
|
-
/**
|
|
266
|
-
* Custom equality function to determine if a value has changed.
|
|
267
|
-
* By default, Pulse uses strict equality (`===`).
|
|
268
|
-
*
|
|
269
|
-
* @param a - The current value.
|
|
270
|
-
* @param b - The new value.
|
|
271
|
-
* @returns `true` if the values are considered equal, `false` otherwise.
|
|
272
|
-
*
|
|
273
|
-
* @example
|
|
274
|
-
* ```ts
|
|
275
|
-
* const list = source([1], {
|
|
276
|
-
* equals: (a, b) => a.length === b.length
|
|
277
|
-
* });
|
|
278
|
-
* ```
|
|
279
|
-
*/
|
|
280
|
-
equals?: (a: T, b: T) => boolean;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* A Pulse Source is a reactive container for a value.
|
|
284
|
-
* It tracks which Guards read its value and notifies them when it changes.
|
|
285
|
-
*
|
|
286
|
-
* @template T The type of the value held by the source.
|
|
287
|
-
*/
|
|
288
|
-
interface Source<T> {
|
|
289
|
-
/**
|
|
290
|
-
* Returns the current value of the source.
|
|
291
|
-
* If called within a Guard evaluation, it automatically registers that Guard as a dependent.
|
|
292
|
-
*
|
|
293
|
-
* @example
|
|
294
|
-
* ```ts
|
|
295
|
-
* const count = source(0);
|
|
296
|
-
* console.log(count()); // 0
|
|
297
|
-
* ```
|
|
298
|
-
*/
|
|
299
|
-
(): T;
|
|
300
|
-
/**
|
|
301
|
-
* Updates the source with a new value.
|
|
302
|
-
* If the value is different (based on strict equality or `options.equals`),
|
|
303
|
-
* all dependent Guards and subscribers will be notified.
|
|
304
|
-
*
|
|
305
|
-
* @param value The new value to set.
|
|
306
|
-
*
|
|
307
|
-
* @example
|
|
308
|
-
* ```ts
|
|
309
|
-
* const count = source(0);
|
|
310
|
-
* count.set(1); // Triggers re-evaluation of dependents
|
|
311
|
-
* ```
|
|
312
|
-
*
|
|
313
|
-
* @error
|
|
314
|
-
* Common error: Mutating an object property without setting a new object reference.
|
|
315
|
-
* Pulse uses reference equality by default. If you mutate a property, Pulse won't know it changed.
|
|
316
|
-
* Solution: Always provide a new object or implement a custom `equals`.
|
|
317
|
-
*/
|
|
318
|
-
set(value: T): void;
|
|
319
|
-
/**
|
|
320
|
-
* Updates the source value using a transformer function based on the current value.
|
|
321
|
-
* Useful for increments or toggles.
|
|
322
|
-
*
|
|
323
|
-
* @param updater A function that receives the current value and returns the new value.
|
|
324
|
-
*
|
|
325
|
-
* @example
|
|
326
|
-
* ```ts
|
|
327
|
-
* const count = source(0);
|
|
328
|
-
* count.update(n => n + 1);
|
|
329
|
-
* ```
|
|
330
|
-
*/
|
|
331
|
-
update(updater: (current: T) => T): void;
|
|
332
|
-
/**
|
|
333
|
-
* Manually subscribes to changes in the source value.
|
|
334
|
-
*
|
|
335
|
-
* @param listener A callback that receives the new value.
|
|
336
|
-
* @returns An unsubscription function.
|
|
337
|
-
*
|
|
338
|
-
* @note Most users should use `guard()` or `usePulse()` instead of manual subscriptions.
|
|
339
|
-
*/
|
|
340
|
-
subscribe(listener: Subscriber<T>): () => void;
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Creates a new Pulse Source.
|
|
344
|
-
*
|
|
345
|
-
* Sources are the fundamental building blocks of state in Pulse. They hold a value
|
|
346
|
-
* and track which Guards depend on them.
|
|
347
|
-
*
|
|
348
|
-
* @template T - The type of value to store.
|
|
349
|
-
* @param initialValue - The initial state.
|
|
350
|
-
* @param options - Configuration options (name, equality).
|
|
351
|
-
* @returns A reactive Pulse Source.
|
|
352
|
-
*
|
|
353
|
-
* @example
|
|
354
|
-
* ```ts
|
|
355
|
-
* const user = source({ name: 'Alice' }, { name: 'user_state' });
|
|
356
|
-
*
|
|
357
|
-
* // Read value (auto-tracks if inside a guard)
|
|
358
|
-
* console.log(user());
|
|
359
|
-
*
|
|
360
|
-
* // Update value
|
|
361
|
-
* user.set({ name: 'Bob' });
|
|
362
|
-
* ```
|
|
363
|
-
*/
|
|
364
|
-
declare function source<T>(initialValue: T, options?: SourceOptions<T>, _internalOffset?: number): Source<T>;
|
|
365
|
-
|
|
366
370
|
/**
|
|
367
371
|
* Serialized state of guards for transfer from server to client.
|
|
368
372
|
*/
|
|
@@ -416,53 +420,56 @@ type PulseUnit = Source<any> | Guard<any>;
|
|
|
416
420
|
/**
|
|
417
421
|
* Root Registry for Pulse.
|
|
418
422
|
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
423
|
+
* Tracks all registered Units (Sources and Guards) globally for DevTools.
|
|
424
|
+
*
|
|
425
|
+
* **IMPORTANT**: Only units with explicit names are registered and visible in DevTools.
|
|
426
|
+
* Unnamed units work perfectly but are not tracked to avoid HMR instability.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```ts
|
|
430
|
+
* // ✅ Visible in DevTools
|
|
431
|
+
* const count = source(0, { name: 'count' });
|
|
432
|
+
*
|
|
433
|
+
* // ❌ Not visible in DevTools (but works fine)
|
|
434
|
+
* const temp = source(0);
|
|
435
|
+
* ```
|
|
421
436
|
*/
|
|
422
437
|
declare class Registry {
|
|
423
438
|
private units;
|
|
424
439
|
private listeners;
|
|
425
440
|
private currentGeneration;
|
|
426
441
|
private cleanupScheduled;
|
|
427
|
-
private autoNameCache;
|
|
428
|
-
/**
|
|
429
|
-
* Generates a stable auto-name based on source code location.
|
|
430
|
-
* Uses file path and line number to ensure the same location always gets the same name.
|
|
431
|
-
* Cached to avoid repeated stack trace parsing.
|
|
432
|
-
*/
|
|
433
|
-
generateAutoName(type: 'source' | 'guard', offset?: number): string;
|
|
434
442
|
/**
|
|
435
|
-
*
|
|
436
|
-
* Called automatically when HMR is detected.
|
|
443
|
+
* Schedules cleanup of units that weren't re-registered (deleted from code).
|
|
437
444
|
*/
|
|
438
445
|
private scheduleCleanup;
|
|
439
446
|
/**
|
|
440
|
-
* Removes units
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Registers a new unit (Source or Guard).
|
|
445
|
-
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
447
|
+
* Removes units that weren't re-registered in the current generation.
|
|
448
|
+
* Uses mark-and-sweep: units that were re-registered have current generation,
|
|
449
|
+
* units that weren't are from old generation and should be removed.
|
|
446
450
|
*/
|
|
447
|
-
|
|
451
|
+
private cleanupDeadUnits;
|
|
448
452
|
/**
|
|
449
|
-
*
|
|
453
|
+
* Registers a unit (only if it has an explicit name).
|
|
450
454
|
*/
|
|
455
|
+
register(unit: PulseUnit): void;
|
|
451
456
|
getAll(): PulseUnit[];
|
|
452
|
-
/**
|
|
453
|
-
* Subscribes to new unit registrations.
|
|
454
|
-
*
|
|
455
|
-
* @param listener - Callback receiving the newly registered unit.
|
|
456
|
-
* @returns Unsubscribe function.
|
|
457
|
-
*/
|
|
458
457
|
onRegister(listener: (unit: PulseUnit) => void): () => void;
|
|
459
|
-
/**
|
|
460
|
-
* Clears all registered units.
|
|
461
|
-
*/
|
|
462
458
|
reset(): void;
|
|
463
459
|
}
|
|
464
460
|
declare const PulseRegistry: Registry;
|
|
465
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Extracts the result type T from a Guard<T>.
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* const authGuard = guard('auth', async () => fetchUser());
|
|
468
|
+
* type AuthUser = InferGuardType<typeof authGuard>; // User
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
type InferGuardType<T> = T extends Guard<infer U> ? U : never;
|
|
472
|
+
|
|
466
473
|
/**
|
|
467
474
|
* Pulse Guard with integrated Composition Helpers.
|
|
468
475
|
*
|
|
@@ -482,4 +489,4 @@ declare const extendedGuard: typeof guard & {
|
|
|
482
489
|
compute: typeof compute;
|
|
483
490
|
};
|
|
484
491
|
|
|
485
|
-
export { type Guard, type GuardExplanation, type GuardNode, type GuardReason, type GuardState, type GuardStatus, type HydrationState, PulseRegistry, type PulseUnit, type Source, type SourceOptions, type Subscriber, type Trackable, compute, evaluate, getCurrentGuard, extendedGuard as guard, guardFail, guardOk, hydrate, registerGuardForHydration, runInContext, source };
|
|
492
|
+
export { type Guard, type GuardExplanation, type GuardNode, type GuardReason, type GuardState, type GuardStatus, type HydrationState, type InferGuardType, PulseRegistry, type PulseUnit, type Source, type SourceOptions, type Subscriber, type Trackable, compute, evaluate, getCurrentGuard, extendedGuard as guard, guardFail, guardOk, hydrate, registerGuardForHydration, runInContext, source };
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ function guardFail(reason) {
|
|
|
59
59
|
function guardOk(value) {
|
|
60
60
|
return value;
|
|
61
61
|
}
|
|
62
|
-
function guard(nameOrFn, fn
|
|
62
|
+
function guard(nameOrFn, fn) {
|
|
63
63
|
const name = typeof nameOrFn === "string" ? nameOrFn : void 0;
|
|
64
64
|
const evaluator = typeof nameOrFn === "function" ? nameOrFn : fn;
|
|
65
65
|
if (!evaluator) {
|
|
@@ -223,11 +223,20 @@ function guard(nameOrFn, fn, _internalOffset = 3) {
|
|
|
223
223
|
lastDeps.forEach((dep) => {
|
|
224
224
|
const depName = dep._name || "unnamed";
|
|
225
225
|
const isG = "state" in dep;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
if (isG) {
|
|
227
|
+
const depState = dep.state();
|
|
228
|
+
deps.push({
|
|
229
|
+
name: depName,
|
|
230
|
+
type: "guard",
|
|
231
|
+
status: depState.status,
|
|
232
|
+
reason: depState.status === "fail" ? depState.reason : void 0
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
deps.push({
|
|
236
|
+
name: depName,
|
|
237
|
+
type: "source"
|
|
238
|
+
});
|
|
239
|
+
}
|
|
231
240
|
});
|
|
232
241
|
return {
|
|
233
242
|
name: name || "guard",
|
|
@@ -252,6 +261,13 @@ function guard(nameOrFn, fn, _internalOffset = 3) {
|
|
|
252
261
|
evaluate2();
|
|
253
262
|
return g;
|
|
254
263
|
}
|
|
264
|
+
guard.map = function(source2, mapper, name) {
|
|
265
|
+
const guardName = name || `map-${source2._name || "source"}`;
|
|
266
|
+
return guard(guardName, () => {
|
|
267
|
+
const value = source2();
|
|
268
|
+
return mapper(value);
|
|
269
|
+
});
|
|
270
|
+
};
|
|
255
271
|
|
|
256
272
|
// src/registry.ts
|
|
257
273
|
var Registry = class {
|
|
@@ -259,53 +275,23 @@ var Registry = class {
|
|
|
259
275
|
listeners = /* @__PURE__ */ new Set();
|
|
260
276
|
currentGeneration = 0;
|
|
261
277
|
cleanupScheduled = false;
|
|
262
|
-
autoNameCache = /* @__PURE__ */ new Map();
|
|
263
278
|
/**
|
|
264
|
-
*
|
|
265
|
-
* Uses file path and line number to ensure the same location always gets the same name.
|
|
266
|
-
* Cached to avoid repeated stack trace parsing.
|
|
267
|
-
*/
|
|
268
|
-
generateAutoName(type, offset = 3) {
|
|
269
|
-
const err = new Error();
|
|
270
|
-
const stack = err.stack?.split("\n") || [];
|
|
271
|
-
let callSite = stack[offset]?.trim() || "";
|
|
272
|
-
const cacheKey = `${type}:${callSite}`;
|
|
273
|
-
if (this.autoNameCache.has(cacheKey)) {
|
|
274
|
-
return this.autoNameCache.get(cacheKey);
|
|
275
|
-
}
|
|
276
|
-
const match = callSite.match(/([^/\\]+)\.(?:ts|tsx|js|jsx):(\d+):\d+/);
|
|
277
|
-
let name;
|
|
278
|
-
if (match) {
|
|
279
|
-
const filename = match[1];
|
|
280
|
-
const line = match[2];
|
|
281
|
-
if (filename && line) {
|
|
282
|
-
name = `${type}@${filename}:${line}`;
|
|
283
|
-
} else {
|
|
284
|
-
name = `${type}#${Math.random().toString(36).substring(2, 7)}`;
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
287
|
-
name = `${type}#${Math.random().toString(36).substring(2, 7)}`;
|
|
288
|
-
}
|
|
289
|
-
this.autoNameCache.set(cacheKey, name);
|
|
290
|
-
return name;
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Increments generation and schedules cleanup of old units.
|
|
294
|
-
* Called automatically when HMR is detected.
|
|
279
|
+
* Schedules cleanup of units that weren't re-registered (deleted from code).
|
|
295
280
|
*/
|
|
296
281
|
scheduleCleanup() {
|
|
297
282
|
if (this.cleanupScheduled) return;
|
|
298
283
|
this.cleanupScheduled = true;
|
|
299
|
-
this.currentGeneration++;
|
|
300
284
|
setTimeout(() => {
|
|
301
|
-
this.
|
|
285
|
+
this.cleanupDeadUnits();
|
|
302
286
|
this.cleanupScheduled = false;
|
|
303
287
|
}, 100);
|
|
304
288
|
}
|
|
305
289
|
/**
|
|
306
|
-
* Removes units
|
|
290
|
+
* Removes units that weren't re-registered in the current generation.
|
|
291
|
+
* Uses mark-and-sweep: units that were re-registered have current generation,
|
|
292
|
+
* units that weren't are from old generation and should be removed.
|
|
307
293
|
*/
|
|
308
|
-
|
|
294
|
+
cleanupDeadUnits() {
|
|
309
295
|
const toDelete = [];
|
|
310
296
|
this.units.forEach((unit, key) => {
|
|
311
297
|
const gen = unit._generation;
|
|
@@ -315,53 +301,46 @@ var Registry = class {
|
|
|
315
301
|
});
|
|
316
302
|
toDelete.forEach((key) => this.units.delete(key));
|
|
317
303
|
if (toDelete.length > 0) {
|
|
318
|
-
console.log(`[Pulse] Cleaned up ${toDelete.length}
|
|
304
|
+
console.log(`[Pulse] Cleaned up ${toDelete.length} deleted units after HMR`);
|
|
319
305
|
}
|
|
320
306
|
}
|
|
321
307
|
/**
|
|
322
|
-
* Registers a
|
|
323
|
-
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
308
|
+
* Registers a unit (only if it has an explicit name).
|
|
324
309
|
*/
|
|
325
|
-
register(unit
|
|
310
|
+
register(unit) {
|
|
326
311
|
const unitWithMetadata = unit;
|
|
327
|
-
|
|
312
|
+
const name = unitWithMetadata._name;
|
|
328
313
|
if (!name) {
|
|
329
|
-
|
|
330
|
-
name = this.generateAutoName(isGuard2 ? "guard" : "source", offset);
|
|
331
|
-
unitWithMetadata._name = name;
|
|
314
|
+
return;
|
|
332
315
|
}
|
|
333
|
-
|
|
334
|
-
if (
|
|
316
|
+
const existingUnit = this.units.get(name);
|
|
317
|
+
if (existingUnit) {
|
|
318
|
+
const existingGen = existingUnit?._generation;
|
|
319
|
+
if (existingGen === this.currentGeneration) {
|
|
320
|
+
unitWithMetadata._generation = this.currentGeneration;
|
|
321
|
+
this.units.set(name, unit);
|
|
322
|
+
this.listeners.forEach((l) => l(unit));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
this.currentGeneration++;
|
|
335
326
|
this.scheduleCleanup();
|
|
336
327
|
}
|
|
328
|
+
unitWithMetadata._generation = this.currentGeneration;
|
|
337
329
|
this.units.set(name, unit);
|
|
338
330
|
this.listeners.forEach((l) => l(unit));
|
|
339
331
|
}
|
|
340
|
-
/**
|
|
341
|
-
* Retrieves all registered units.
|
|
342
|
-
*/
|
|
343
332
|
getAll() {
|
|
344
333
|
return Array.from(this.units.values());
|
|
345
334
|
}
|
|
346
|
-
/**
|
|
347
|
-
* Subscribes to new unit registrations.
|
|
348
|
-
*
|
|
349
|
-
* @param listener - Callback receiving the newly registered unit.
|
|
350
|
-
* @returns Unsubscribe function.
|
|
351
|
-
*/
|
|
352
335
|
onRegister(listener) {
|
|
353
336
|
this.listeners.add(listener);
|
|
354
337
|
return () => {
|
|
355
338
|
this.listeners.delete(listener);
|
|
356
339
|
};
|
|
357
340
|
}
|
|
358
|
-
/**
|
|
359
|
-
* Clears all registered units.
|
|
360
|
-
*/
|
|
361
341
|
reset() {
|
|
362
342
|
this.units.clear();
|
|
363
343
|
this.currentGeneration = 0;
|
|
364
|
-
this.autoNameCache.clear();
|
|
365
344
|
}
|
|
366
345
|
};
|
|
367
346
|
var GLOBAL_KEY = "__PULSE_REGISTRY__";
|
|
@@ -372,7 +351,7 @@ if (!globalSymbols[GLOBAL_KEY]) {
|
|
|
372
351
|
var PulseRegistry = globalSymbols[GLOBAL_KEY];
|
|
373
352
|
|
|
374
353
|
// src/source.ts
|
|
375
|
-
function source(initialValue, options = {}
|
|
354
|
+
function source(initialValue, options = {}) {
|
|
376
355
|
let value = initialValue;
|
|
377
356
|
const subscribers = /* @__PURE__ */ new Set();
|
|
378
357
|
const dependents = /* @__PURE__ */ new Set();
|
|
@@ -411,7 +390,7 @@ function compute(name, dependencies, processor) {
|
|
|
411
390
|
return guard(name, () => {
|
|
412
391
|
const values = dependencies.map((dep) => typeof dep === "function" ? dep() : dep);
|
|
413
392
|
return processor(...values);
|
|
414
|
-
}
|
|
393
|
+
});
|
|
415
394
|
}
|
|
416
395
|
|
|
417
396
|
// src/composition.ts
|
|
@@ -434,7 +413,7 @@ function guardAll(nameOrGuards, maybeGuards) {
|
|
|
434
413
|
throw new Error(message);
|
|
435
414
|
}
|
|
436
415
|
return true;
|
|
437
|
-
}
|
|
416
|
+
});
|
|
438
417
|
}
|
|
439
418
|
function guardAny(nameOrGuards, maybeGuards) {
|
|
440
419
|
const name = typeof nameOrGuards === "string" ? nameOrGuards : void 0;
|
|
@@ -448,7 +427,7 @@ function guardAny(nameOrGuards, maybeGuards) {
|
|
|
448
427
|
allFails.push(message);
|
|
449
428
|
}
|
|
450
429
|
throw new Error(allFails.length > 0 ? allFails.join(" and ") : "no conditions met");
|
|
451
|
-
}
|
|
430
|
+
});
|
|
452
431
|
}
|
|
453
432
|
function guardNot(nameOrTarget, maybeTarget) {
|
|
454
433
|
const name = typeof nameOrTarget === "string" ? nameOrTarget : void 0;
|
|
@@ -458,7 +437,7 @@ function guardNot(nameOrTarget, maybeTarget) {
|
|
|
458
437
|
return !target.ok();
|
|
459
438
|
}
|
|
460
439
|
return !target();
|
|
461
|
-
}
|
|
440
|
+
});
|
|
462
441
|
}
|
|
463
442
|
var guardExtensions = {
|
|
464
443
|
all: guardAll,
|