@pulse-js/core 0.1.8 → 0.1.9
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/README.md +19 -29
- package/dist/index.cjs +95 -11
- package/dist/index.d.cts +26 -4
- package/dist/index.d.ts +26 -4
- package/dist/index.js +95 -11
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
<img width="200" height="200" alt="logo" src="https://raw.githubusercontent.com/ZtaMDev/Pulse/refs/heads/main/pulse.svg" />
|
|
4
4
|
|
|
5
|
-
# Pulse
|
|
5
|
+
# Pulse-JS
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@pulse-js/core)
|
|
6
8
|
|
|
7
9
|
> A semantic reactivity system for modern applications. Separate reactive data (sources) from business conditions (guards) with a declarative, composable, and observable approach.
|
|
8
10
|
|
|
11
|
+
Official [Documentation](https://pulse-js.vercel.app)
|
|
12
|
+
|
|
9
13
|
Pulse differs from traditional signals or state managers by treating `Conditions` as first-class citizens. Instead of embedding complex boolean logic inside components or selectors, you define **Semantic Guards** that can be observed, composed, and debugged independently.
|
|
10
14
|
|
|
11
15
|
</div>
|
|
@@ -146,37 +150,23 @@ Compare Pulse primitives:
|
|
|
146
150
|
| **Guard** | ✅ | ✅ | ✅ | Business rules (conditioned truths). |
|
|
147
151
|
| **Compute** | ❌ | ❌ | ✅ | Pure transformations (derivations). |
|
|
148
152
|
|
|
149
|
-
##
|
|
150
|
-
|
|
151
|
-
### `source<T>(initialValue: T, options?: SourceOptions)`
|
|
152
|
-
|
|
153
|
-
Creates a reactive source.
|
|
154
|
-
|
|
155
|
-
- `options.name`: Unique string name (highly recommended for debugging).
|
|
156
|
-
- `options.equals`: Custom equality function `(prev, next) => boolean`.
|
|
157
|
-
|
|
158
|
-
Methods:
|
|
159
|
-
|
|
160
|
-
- `.set(value: T)`: Updates the value.
|
|
161
|
-
- `.update(fn: (current: T) => T)`: Updates value using a transform.
|
|
162
|
-
- `.subscribe(fn: (value: T) => void)`: Manual subscription.
|
|
163
|
-
|
|
164
|
-
### `guard<T>(name: string, evaluator: () => T | Promise<T>)`
|
|
153
|
+
## Framework Integrations
|
|
165
154
|
|
|
166
|
-
|
|
155
|
+
Pulse provides official adapters for major frameworks to ensure seamless integration.
|
|
167
156
|
|
|
168
|
-
|
|
157
|
+
| Framework | Package | Documentation |
|
|
158
|
+
| :--------- | :----------------- | :----------------------------------------------------------- |
|
|
159
|
+
| **React** | `@pulse-js/react` | [Read Docs](https://pulse-js.vercel.app/integrations/react) |
|
|
160
|
+
| **Vue** | `@pulse-js/vue` | [Read Docs](https://pulse-js.vercel.app/integrations/vue) |
|
|
161
|
+
| **Svelte** | `@pulse-js/svelte` | [Read Docs](https://pulse-js.vercel.app/integrations/svelte) |
|
|
169
162
|
|
|
170
|
-
|
|
171
|
-
- `.fail()`: Returns true if status is 'fail'.
|
|
172
|
-
- `.pending()`: Returns true if evaluating async.
|
|
173
|
-
- `.reason()`: Returns the failure message.
|
|
174
|
-
- `.state()`: Returns full `{ status, value, reason }` object.
|
|
175
|
-
- `.subscribe(fn: (state: GuardState) => void)`: Manual subscription.
|
|
163
|
+
## Developer Tools
|
|
176
164
|
|
|
177
|
-
|
|
165
|
+
Debug your reactive graph with **[Pulse Tools](https://pulse-js.vercel.app/guides/devtools/)**, a powerful framework-agnostic inspector.
|
|
178
166
|
|
|
179
|
-
|
|
167
|
+
### Features
|
|
180
168
|
|
|
181
|
-
-
|
|
182
|
-
-
|
|
169
|
+
- **Component Tree**: Visualize your entire guard dependency graph.
|
|
170
|
+
- **Editable Logic**: Update source values directly from the UI to test logic branches.
|
|
171
|
+
- **Time Travel**: (Coming Soon) Replay state changes.
|
|
172
|
+
- **Zero Config**: Works out of the box with `@pulse-js/tools`.
|
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, _internalOffset = 3) {
|
|
99
99
|
const name = typeof nameOrFn === "string" ? nameOrFn : void 0;
|
|
100
100
|
const evaluator = typeof nameOrFn === "function" ? nameOrFn : fn;
|
|
101
101
|
if (!evaluator) {
|
|
@@ -293,13 +293,84 @@ function guard(nameOrFn, fn) {
|
|
|
293
293
|
var Registry = class {
|
|
294
294
|
units = /* @__PURE__ */ new Map();
|
|
295
295
|
listeners = /* @__PURE__ */ new Set();
|
|
296
|
+
currentGeneration = 0;
|
|
297
|
+
cleanupScheduled = false;
|
|
298
|
+
autoNameCache = /* @__PURE__ */ new Map();
|
|
299
|
+
/**
|
|
300
|
+
* Generates a stable auto-name based on source code location.
|
|
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.
|
|
331
|
+
*/
|
|
332
|
+
scheduleCleanup() {
|
|
333
|
+
if (this.cleanupScheduled) return;
|
|
334
|
+
this.cleanupScheduled = true;
|
|
335
|
+
this.currentGeneration++;
|
|
336
|
+
setTimeout(() => {
|
|
337
|
+
this.cleanupOldGenerations();
|
|
338
|
+
this.cleanupScheduled = false;
|
|
339
|
+
}, 100);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Removes units from old generations (likely orphaned by HMR).
|
|
343
|
+
*/
|
|
344
|
+
cleanupOldGenerations() {
|
|
345
|
+
const toDelete = [];
|
|
346
|
+
this.units.forEach((unit, key) => {
|
|
347
|
+
const gen = unit._generation;
|
|
348
|
+
if (gen !== void 0 && gen < this.currentGeneration) {
|
|
349
|
+
toDelete.push(key);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
toDelete.forEach((key) => this.units.delete(key));
|
|
353
|
+
if (toDelete.length > 0) {
|
|
354
|
+
console.log(`[Pulse] Cleaned up ${toDelete.length} stale units after HMR`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
296
357
|
/**
|
|
297
358
|
* Registers a new unit (Source or Guard).
|
|
298
|
-
*
|
|
359
|
+
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
299
360
|
*/
|
|
300
|
-
register(unit) {
|
|
301
|
-
const
|
|
302
|
-
|
|
361
|
+
register(unit, offset = 3) {
|
|
362
|
+
const unitWithMetadata = unit;
|
|
363
|
+
let name = unitWithMetadata._name;
|
|
364
|
+
if (!name) {
|
|
365
|
+
const isGuard2 = "state" in unit;
|
|
366
|
+
name = this.generateAutoName(isGuard2 ? "guard" : "source", offset);
|
|
367
|
+
unitWithMetadata._name = name;
|
|
368
|
+
}
|
|
369
|
+
unitWithMetadata._generation = this.currentGeneration;
|
|
370
|
+
if (this.units.has(name)) {
|
|
371
|
+
this.scheduleCleanup();
|
|
372
|
+
}
|
|
373
|
+
this.units.set(name, unit);
|
|
303
374
|
this.listeners.forEach((l) => l(unit));
|
|
304
375
|
}
|
|
305
376
|
/**
|
|
@@ -320,11 +391,24 @@ var Registry = class {
|
|
|
320
391
|
this.listeners.delete(listener);
|
|
321
392
|
};
|
|
322
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Clears all registered units.
|
|
396
|
+
*/
|
|
397
|
+
reset() {
|
|
398
|
+
this.units.clear();
|
|
399
|
+
this.currentGeneration = 0;
|
|
400
|
+
this.autoNameCache.clear();
|
|
401
|
+
}
|
|
323
402
|
};
|
|
324
|
-
var
|
|
403
|
+
var GLOBAL_KEY = "__PULSE_REGISTRY__";
|
|
404
|
+
var globalSymbols = globalThis;
|
|
405
|
+
if (!globalSymbols[GLOBAL_KEY]) {
|
|
406
|
+
globalSymbols[GLOBAL_KEY] = new Registry();
|
|
407
|
+
}
|
|
408
|
+
var PulseRegistry = globalSymbols[GLOBAL_KEY];
|
|
325
409
|
|
|
326
410
|
// src/source.ts
|
|
327
|
-
function source(initialValue, options = {}) {
|
|
411
|
+
function source(initialValue, options = {}, _internalOffset = 3) {
|
|
328
412
|
let value = initialValue;
|
|
329
413
|
const subscribers = /* @__PURE__ */ new Set();
|
|
330
414
|
const dependents = /* @__PURE__ */ new Set();
|
|
@@ -363,7 +447,7 @@ function compute(name, dependencies, processor) {
|
|
|
363
447
|
return guard(name, () => {
|
|
364
448
|
const values = dependencies.map((dep) => typeof dep === "function" ? dep() : dep);
|
|
365
449
|
return processor(...values);
|
|
366
|
-
});
|
|
450
|
+
}, 4);
|
|
367
451
|
}
|
|
368
452
|
|
|
369
453
|
// src/composition.ts
|
|
@@ -386,7 +470,7 @@ function guardAll(nameOrGuards, maybeGuards) {
|
|
|
386
470
|
throw new Error(message);
|
|
387
471
|
}
|
|
388
472
|
return true;
|
|
389
|
-
});
|
|
473
|
+
}, 4);
|
|
390
474
|
}
|
|
391
475
|
function guardAny(nameOrGuards, maybeGuards) {
|
|
392
476
|
const name = typeof nameOrGuards === "string" ? nameOrGuards : void 0;
|
|
@@ -400,7 +484,7 @@ function guardAny(nameOrGuards, maybeGuards) {
|
|
|
400
484
|
allFails.push(message);
|
|
401
485
|
}
|
|
402
486
|
throw new Error(allFails.length > 0 ? allFails.join(" and ") : "no conditions met");
|
|
403
|
-
});
|
|
487
|
+
}, 4);
|
|
404
488
|
}
|
|
405
489
|
function guardNot(nameOrTarget, maybeTarget) {
|
|
406
490
|
const name = typeof nameOrTarget === "string" ? nameOrTarget : void 0;
|
|
@@ -410,7 +494,7 @@ function guardNot(nameOrTarget, maybeTarget) {
|
|
|
410
494
|
return !target.ok();
|
|
411
495
|
}
|
|
412
496
|
return !target();
|
|
413
|
-
});
|
|
497
|
+
}, 4);
|
|
414
498
|
}
|
|
415
499
|
var guardExtensions = {
|
|
416
500
|
all: guardAll,
|
package/dist/index.d.cts
CHANGED
|
@@ -186,7 +186,7 @@ declare function guardFail(reason: string | GuardReason): never;
|
|
|
186
186
|
* Returns the value passed to it.
|
|
187
187
|
*/
|
|
188
188
|
declare function guardOk<T>(value: T): T;
|
|
189
|
-
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T
|
|
189
|
+
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T>, _internalOffset?: number): Guard<T>;
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
192
|
* Utility to transform reactive dependencies into a new derived value.
|
|
@@ -361,7 +361,7 @@ interface Source<T> {
|
|
|
361
361
|
* user.set({ name: 'Bob' });
|
|
362
362
|
* ```
|
|
363
363
|
*/
|
|
364
|
-
declare function source<T>(initialValue: T, options?: SourceOptions<T
|
|
364
|
+
declare function source<T>(initialValue: T, options?: SourceOptions<T>, _internalOffset?: number): Source<T>;
|
|
365
365
|
|
|
366
366
|
/**
|
|
367
367
|
* Serialized state of guards for transfer from server to client.
|
|
@@ -422,11 +422,29 @@ type PulseUnit = Source<any> | Guard<any>;
|
|
|
422
422
|
declare class Registry {
|
|
423
423
|
private units;
|
|
424
424
|
private listeners;
|
|
425
|
+
private currentGeneration;
|
|
426
|
+
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
|
+
/**
|
|
435
|
+
* Increments generation and schedules cleanup of old units.
|
|
436
|
+
* Called automatically when HMR is detected.
|
|
437
|
+
*/
|
|
438
|
+
private scheduleCleanup;
|
|
439
|
+
/**
|
|
440
|
+
* Removes units from old generations (likely orphaned by HMR).
|
|
441
|
+
*/
|
|
442
|
+
private cleanupOldGenerations;
|
|
425
443
|
/**
|
|
426
444
|
* Registers a new unit (Source or Guard).
|
|
427
|
-
*
|
|
445
|
+
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
428
446
|
*/
|
|
429
|
-
register(unit: PulseUnit): void;
|
|
447
|
+
register(unit: PulseUnit, offset?: number): void;
|
|
430
448
|
/**
|
|
431
449
|
* Retrieves all registered units.
|
|
432
450
|
*/
|
|
@@ -438,6 +456,10 @@ declare class Registry {
|
|
|
438
456
|
* @returns Unsubscribe function.
|
|
439
457
|
*/
|
|
440
458
|
onRegister(listener: (unit: PulseUnit) => void): () => void;
|
|
459
|
+
/**
|
|
460
|
+
* Clears all registered units.
|
|
461
|
+
*/
|
|
462
|
+
reset(): void;
|
|
441
463
|
}
|
|
442
464
|
declare const PulseRegistry: Registry;
|
|
443
465
|
|
package/dist/index.d.ts
CHANGED
|
@@ -186,7 +186,7 @@ declare function guardFail(reason: string | GuardReason): never;
|
|
|
186
186
|
* Returns the value passed to it.
|
|
187
187
|
*/
|
|
188
188
|
declare function guardOk<T>(value: T): T;
|
|
189
|
-
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T
|
|
189
|
+
declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T>, _internalOffset?: number): Guard<T>;
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
192
|
* Utility to transform reactive dependencies into a new derived value.
|
|
@@ -361,7 +361,7 @@ interface Source<T> {
|
|
|
361
361
|
* user.set({ name: 'Bob' });
|
|
362
362
|
* ```
|
|
363
363
|
*/
|
|
364
|
-
declare function source<T>(initialValue: T, options?: SourceOptions<T
|
|
364
|
+
declare function source<T>(initialValue: T, options?: SourceOptions<T>, _internalOffset?: number): Source<T>;
|
|
365
365
|
|
|
366
366
|
/**
|
|
367
367
|
* Serialized state of guards for transfer from server to client.
|
|
@@ -422,11 +422,29 @@ type PulseUnit = Source<any> | Guard<any>;
|
|
|
422
422
|
declare class Registry {
|
|
423
423
|
private units;
|
|
424
424
|
private listeners;
|
|
425
|
+
private currentGeneration;
|
|
426
|
+
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
|
+
/**
|
|
435
|
+
* Increments generation and schedules cleanup of old units.
|
|
436
|
+
* Called automatically when HMR is detected.
|
|
437
|
+
*/
|
|
438
|
+
private scheduleCleanup;
|
|
439
|
+
/**
|
|
440
|
+
* Removes units from old generations (likely orphaned by HMR).
|
|
441
|
+
*/
|
|
442
|
+
private cleanupOldGenerations;
|
|
425
443
|
/**
|
|
426
444
|
* Registers a new unit (Source or Guard).
|
|
427
|
-
*
|
|
445
|
+
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
428
446
|
*/
|
|
429
|
-
register(unit: PulseUnit): void;
|
|
447
|
+
register(unit: PulseUnit, offset?: number): void;
|
|
430
448
|
/**
|
|
431
449
|
* Retrieves all registered units.
|
|
432
450
|
*/
|
|
@@ -438,6 +456,10 @@ declare class Registry {
|
|
|
438
456
|
* @returns Unsubscribe function.
|
|
439
457
|
*/
|
|
440
458
|
onRegister(listener: (unit: PulseUnit) => void): () => void;
|
|
459
|
+
/**
|
|
460
|
+
* Clears all registered units.
|
|
461
|
+
*/
|
|
462
|
+
reset(): void;
|
|
441
463
|
}
|
|
442
464
|
declare const PulseRegistry: Registry;
|
|
443
465
|
|
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, _internalOffset = 3) {
|
|
63
63
|
const name = typeof nameOrFn === "string" ? nameOrFn : void 0;
|
|
64
64
|
const evaluator = typeof nameOrFn === "function" ? nameOrFn : fn;
|
|
65
65
|
if (!evaluator) {
|
|
@@ -257,13 +257,84 @@ function guard(nameOrFn, fn) {
|
|
|
257
257
|
var Registry = class {
|
|
258
258
|
units = /* @__PURE__ */ new Map();
|
|
259
259
|
listeners = /* @__PURE__ */ new Set();
|
|
260
|
+
currentGeneration = 0;
|
|
261
|
+
cleanupScheduled = false;
|
|
262
|
+
autoNameCache = /* @__PURE__ */ new Map();
|
|
263
|
+
/**
|
|
264
|
+
* Generates a stable auto-name based on source code location.
|
|
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.
|
|
295
|
+
*/
|
|
296
|
+
scheduleCleanup() {
|
|
297
|
+
if (this.cleanupScheduled) return;
|
|
298
|
+
this.cleanupScheduled = true;
|
|
299
|
+
this.currentGeneration++;
|
|
300
|
+
setTimeout(() => {
|
|
301
|
+
this.cleanupOldGenerations();
|
|
302
|
+
this.cleanupScheduled = false;
|
|
303
|
+
}, 100);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Removes units from old generations (likely orphaned by HMR).
|
|
307
|
+
*/
|
|
308
|
+
cleanupOldGenerations() {
|
|
309
|
+
const toDelete = [];
|
|
310
|
+
this.units.forEach((unit, key) => {
|
|
311
|
+
const gen = unit._generation;
|
|
312
|
+
if (gen !== void 0 && gen < this.currentGeneration) {
|
|
313
|
+
toDelete.push(key);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
toDelete.forEach((key) => this.units.delete(key));
|
|
317
|
+
if (toDelete.length > 0) {
|
|
318
|
+
console.log(`[Pulse] Cleaned up ${toDelete.length} stale units after HMR`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
260
321
|
/**
|
|
261
322
|
* Registers a new unit (Source or Guard).
|
|
262
|
-
*
|
|
323
|
+
* Auto-assigns stable names to unnamed units for HMR stability.
|
|
263
324
|
*/
|
|
264
|
-
register(unit) {
|
|
265
|
-
const
|
|
266
|
-
|
|
325
|
+
register(unit, offset = 3) {
|
|
326
|
+
const unitWithMetadata = unit;
|
|
327
|
+
let name = unitWithMetadata._name;
|
|
328
|
+
if (!name) {
|
|
329
|
+
const isGuard2 = "state" in unit;
|
|
330
|
+
name = this.generateAutoName(isGuard2 ? "guard" : "source", offset);
|
|
331
|
+
unitWithMetadata._name = name;
|
|
332
|
+
}
|
|
333
|
+
unitWithMetadata._generation = this.currentGeneration;
|
|
334
|
+
if (this.units.has(name)) {
|
|
335
|
+
this.scheduleCleanup();
|
|
336
|
+
}
|
|
337
|
+
this.units.set(name, unit);
|
|
267
338
|
this.listeners.forEach((l) => l(unit));
|
|
268
339
|
}
|
|
269
340
|
/**
|
|
@@ -284,11 +355,24 @@ var Registry = class {
|
|
|
284
355
|
this.listeners.delete(listener);
|
|
285
356
|
};
|
|
286
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Clears all registered units.
|
|
360
|
+
*/
|
|
361
|
+
reset() {
|
|
362
|
+
this.units.clear();
|
|
363
|
+
this.currentGeneration = 0;
|
|
364
|
+
this.autoNameCache.clear();
|
|
365
|
+
}
|
|
287
366
|
};
|
|
288
|
-
var
|
|
367
|
+
var GLOBAL_KEY = "__PULSE_REGISTRY__";
|
|
368
|
+
var globalSymbols = globalThis;
|
|
369
|
+
if (!globalSymbols[GLOBAL_KEY]) {
|
|
370
|
+
globalSymbols[GLOBAL_KEY] = new Registry();
|
|
371
|
+
}
|
|
372
|
+
var PulseRegistry = globalSymbols[GLOBAL_KEY];
|
|
289
373
|
|
|
290
374
|
// src/source.ts
|
|
291
|
-
function source(initialValue, options = {}) {
|
|
375
|
+
function source(initialValue, options = {}, _internalOffset = 3) {
|
|
292
376
|
let value = initialValue;
|
|
293
377
|
const subscribers = /* @__PURE__ */ new Set();
|
|
294
378
|
const dependents = /* @__PURE__ */ new Set();
|
|
@@ -327,7 +411,7 @@ function compute(name, dependencies, processor) {
|
|
|
327
411
|
return guard(name, () => {
|
|
328
412
|
const values = dependencies.map((dep) => typeof dep === "function" ? dep() : dep);
|
|
329
413
|
return processor(...values);
|
|
330
|
-
});
|
|
414
|
+
}, 4);
|
|
331
415
|
}
|
|
332
416
|
|
|
333
417
|
// src/composition.ts
|
|
@@ -350,7 +434,7 @@ function guardAll(nameOrGuards, maybeGuards) {
|
|
|
350
434
|
throw new Error(message);
|
|
351
435
|
}
|
|
352
436
|
return true;
|
|
353
|
-
});
|
|
437
|
+
}, 4);
|
|
354
438
|
}
|
|
355
439
|
function guardAny(nameOrGuards, maybeGuards) {
|
|
356
440
|
const name = typeof nameOrGuards === "string" ? nameOrGuards : void 0;
|
|
@@ -364,7 +448,7 @@ function guardAny(nameOrGuards, maybeGuards) {
|
|
|
364
448
|
allFails.push(message);
|
|
365
449
|
}
|
|
366
450
|
throw new Error(allFails.length > 0 ? allFails.join(" and ") : "no conditions met");
|
|
367
|
-
});
|
|
451
|
+
}, 4);
|
|
368
452
|
}
|
|
369
453
|
function guardNot(nameOrTarget, maybeTarget) {
|
|
370
454
|
const name = typeof nameOrTarget === "string" ? nameOrTarget : void 0;
|
|
@@ -374,7 +458,7 @@ function guardNot(nameOrTarget, maybeTarget) {
|
|
|
374
458
|
return !target.ok();
|
|
375
459
|
}
|
|
376
460
|
return !target();
|
|
377
|
-
});
|
|
461
|
+
}, 4);
|
|
378
462
|
}
|
|
379
463
|
var guardExtensions = {
|
|
380
464
|
all: guardAll,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-js/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"module": "dist/index.js",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"description": "A semantic reactivity system for modern applications. Separate reactive data (sources) from business conditions (guards) with a declarative, composable, and observable approach.",
|
|
19
19
|
"workspaces": [
|
|
20
20
|
"packages/*",
|
|
21
|
-
"tests"
|
|
21
|
+
"tests",
|
|
22
|
+
"tests/*"
|
|
22
23
|
],
|
|
23
24
|
"keywords": [
|
|
24
25
|
"reactivity",
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"directory": "packages/core"
|
|
38
39
|
},
|
|
39
40
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/ZtaMDev/pulse/issues"
|
|
41
|
+
"url": "https://github.com/ZtaMDev/pulse-js/issues"
|
|
41
42
|
},
|
|
42
43
|
"license": "MIT",
|
|
43
44
|
"scripts": {
|