@pulse-js/core 0.1.9 → 0.2.0

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 CHANGED
@@ -95,7 +95,7 @@ function guardFail(reason) {
95
95
  function guardOk(value) {
96
96
  return value;
97
97
  }
98
- function guard(nameOrFn, fn, _internalOffset = 3) {
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) {
@@ -295,53 +295,23 @@ var Registry = class {
295
295
  listeners = /* @__PURE__ */ new Set();
296
296
  currentGeneration = 0;
297
297
  cleanupScheduled = false;
298
- autoNameCache = /* @__PURE__ */ new Map();
299
298
  /**
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.
299
+ * Schedules cleanup of units that weren't re-registered (deleted from code).
331
300
  */
332
301
  scheduleCleanup() {
333
302
  if (this.cleanupScheduled) return;
334
303
  this.cleanupScheduled = true;
335
- this.currentGeneration++;
336
304
  setTimeout(() => {
337
- this.cleanupOldGenerations();
305
+ this.cleanupDeadUnits();
338
306
  this.cleanupScheduled = false;
339
307
  }, 100);
340
308
  }
341
309
  /**
342
- * Removes units from old generations (likely orphaned by HMR).
310
+ * Removes units that weren't re-registered in the current generation.
311
+ * Uses mark-and-sweep: units that were re-registered have current generation,
312
+ * units that weren't are from old generation and should be removed.
343
313
  */
344
- cleanupOldGenerations() {
314
+ cleanupDeadUnits() {
345
315
  const toDelete = [];
346
316
  this.units.forEach((unit, key) => {
347
317
  const gen = unit._generation;
@@ -351,53 +321,46 @@ var Registry = class {
351
321
  });
352
322
  toDelete.forEach((key) => this.units.delete(key));
353
323
  if (toDelete.length > 0) {
354
- console.log(`[Pulse] Cleaned up ${toDelete.length} stale units after HMR`);
324
+ console.log(`[Pulse] Cleaned up ${toDelete.length} deleted units after HMR`);
355
325
  }
356
326
  }
357
327
  /**
358
- * Registers a new unit (Source or Guard).
359
- * Auto-assigns stable names to unnamed units for HMR stability.
328
+ * Registers a unit (only if it has an explicit name).
360
329
  */
361
- register(unit, offset = 3) {
330
+ register(unit) {
362
331
  const unitWithMetadata = unit;
363
- let name = unitWithMetadata._name;
332
+ const name = unitWithMetadata._name;
364
333
  if (!name) {
365
- const isGuard2 = "state" in unit;
366
- name = this.generateAutoName(isGuard2 ? "guard" : "source", offset);
367
- unitWithMetadata._name = name;
334
+ return;
368
335
  }
369
- unitWithMetadata._generation = this.currentGeneration;
370
- if (this.units.has(name)) {
336
+ const existingUnit = this.units.get(name);
337
+ if (existingUnit) {
338
+ const existingGen = existingUnit?._generation;
339
+ if (existingGen === this.currentGeneration) {
340
+ unitWithMetadata._generation = this.currentGeneration;
341
+ this.units.set(name, unit);
342
+ this.listeners.forEach((l) => l(unit));
343
+ return;
344
+ }
345
+ this.currentGeneration++;
371
346
  this.scheduleCleanup();
372
347
  }
348
+ unitWithMetadata._generation = this.currentGeneration;
373
349
  this.units.set(name, unit);
374
350
  this.listeners.forEach((l) => l(unit));
375
351
  }
376
- /**
377
- * Retrieves all registered units.
378
- */
379
352
  getAll() {
380
353
  return Array.from(this.units.values());
381
354
  }
382
- /**
383
- * Subscribes to new unit registrations.
384
- *
385
- * @param listener - Callback receiving the newly registered unit.
386
- * @returns Unsubscribe function.
387
- */
388
355
  onRegister(listener) {
389
356
  this.listeners.add(listener);
390
357
  return () => {
391
358
  this.listeners.delete(listener);
392
359
  };
393
360
  }
394
- /**
395
- * Clears all registered units.
396
- */
397
361
  reset() {
398
362
  this.units.clear();
399
363
  this.currentGeneration = 0;
400
- this.autoNameCache.clear();
401
364
  }
402
365
  };
403
366
  var GLOBAL_KEY = "__PULSE_REGISTRY__";
@@ -408,7 +371,7 @@ if (!globalSymbols[GLOBAL_KEY]) {
408
371
  var PulseRegistry = globalSymbols[GLOBAL_KEY];
409
372
 
410
373
  // src/source.ts
411
- function source(initialValue, options = {}, _internalOffset = 3) {
374
+ function source(initialValue, options = {}) {
412
375
  let value = initialValue;
413
376
  const subscribers = /* @__PURE__ */ new Set();
414
377
  const dependents = /* @__PURE__ */ new Set();
@@ -447,7 +410,7 @@ function compute(name, dependencies, processor) {
447
410
  return guard(name, () => {
448
411
  const values = dependencies.map((dep) => typeof dep === "function" ? dep() : dep);
449
412
  return processor(...values);
450
- }, 4);
413
+ });
451
414
  }
452
415
 
453
416
  // src/composition.ts
@@ -470,7 +433,7 @@ function guardAll(nameOrGuards, maybeGuards) {
470
433
  throw new Error(message);
471
434
  }
472
435
  return true;
473
- }, 4);
436
+ });
474
437
  }
475
438
  function guardAny(nameOrGuards, maybeGuards) {
476
439
  const name = typeof nameOrGuards === "string" ? nameOrGuards : void 0;
@@ -484,7 +447,7 @@ function guardAny(nameOrGuards, maybeGuards) {
484
447
  allFails.push(message);
485
448
  }
486
449
  throw new Error(allFails.length > 0 ? allFails.join(" and ") : "no conditions met");
487
- }, 4);
450
+ });
488
451
  }
489
452
  function guardNot(nameOrTarget, maybeTarget) {
490
453
  const name = typeof nameOrTarget === "string" ? nameOrTarget : void 0;
@@ -494,7 +457,7 @@ function guardNot(nameOrTarget, maybeTarget) {
494
457
  return !target.ok();
495
458
  }
496
459
  return !target();
497
- }, 4);
460
+ });
498
461
  }
499
462
  var guardExtensions = {
500
463
  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>, _internalOffset?: number): Guard<T>;
189
+ declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T>): 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>, _internalOffset?: number): Source<T>;
364
+ declare function source<T>(initialValue: T, options?: SourceOptions<T>): Source<T>;
365
365
 
366
366
  /**
367
367
  * Serialized state of guards for transfer from server to client.
@@ -416,49 +416,41 @@ type PulseUnit = Source<any> | Guard<any>;
416
416
  /**
417
417
  * Root Registry for Pulse.
418
418
  *
419
- * It tracks all registered Units (Sources and Guards) globally, providing
420
- * the data source for DevTools and HMR stability.
419
+ * Tracks all registered Units (Sources and Guards) globally for DevTools.
420
+ *
421
+ * **IMPORTANT**: Only units with explicit names are registered and visible in DevTools.
422
+ * Unnamed units work perfectly but are not tracked to avoid HMR instability.
423
+ *
424
+ * @example
425
+ * ```ts
426
+ * // ✅ Visible in DevTools
427
+ * const count = source(0, { name: 'count' });
428
+ *
429
+ * // ❌ Not visible in DevTools (but works fine)
430
+ * const temp = source(0);
431
+ * ```
421
432
  */
422
433
  declare class Registry {
423
434
  private units;
424
435
  private listeners;
425
436
  private currentGeneration;
426
437
  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
438
  /**
435
- * Increments generation and schedules cleanup of old units.
436
- * Called automatically when HMR is detected.
439
+ * Schedules cleanup of units that weren't re-registered (deleted from code).
437
440
  */
438
441
  private scheduleCleanup;
439
442
  /**
440
- * Removes units from old generations (likely orphaned by HMR).
443
+ * Removes units that weren't re-registered in the current generation.
444
+ * Uses mark-and-sweep: units that were re-registered have current generation,
445
+ * units that weren't are from old generation and should be removed.
441
446
  */
442
- private cleanupOldGenerations;
447
+ private cleanupDeadUnits;
443
448
  /**
444
- * Registers a new unit (Source or Guard).
445
- * Auto-assigns stable names to unnamed units for HMR stability.
446
- */
447
- register(unit: PulseUnit, offset?: number): void;
448
- /**
449
- * Retrieves all registered units.
449
+ * Registers a unit (only if it has an explicit name).
450
450
  */
451
+ register(unit: PulseUnit): void;
451
452
  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
453
  onRegister(listener: (unit: PulseUnit) => void): () => void;
459
- /**
460
- * Clears all registered units.
461
- */
462
454
  reset(): void;
463
455
  }
464
456
  declare const PulseRegistry: Registry;
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>, _internalOffset?: number): Guard<T>;
189
+ declare function guard<T = boolean>(nameOrFn?: string | (() => T | Promise<T>), fn?: () => T | Promise<T>): 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>, _internalOffset?: number): Source<T>;
364
+ declare function source<T>(initialValue: T, options?: SourceOptions<T>): Source<T>;
365
365
 
366
366
  /**
367
367
  * Serialized state of guards for transfer from server to client.
@@ -416,49 +416,41 @@ type PulseUnit = Source<any> | Guard<any>;
416
416
  /**
417
417
  * Root Registry for Pulse.
418
418
  *
419
- * It tracks all registered Units (Sources and Guards) globally, providing
420
- * the data source for DevTools and HMR stability.
419
+ * Tracks all registered Units (Sources and Guards) globally for DevTools.
420
+ *
421
+ * **IMPORTANT**: Only units with explicit names are registered and visible in DevTools.
422
+ * Unnamed units work perfectly but are not tracked to avoid HMR instability.
423
+ *
424
+ * @example
425
+ * ```ts
426
+ * // ✅ Visible in DevTools
427
+ * const count = source(0, { name: 'count' });
428
+ *
429
+ * // ❌ Not visible in DevTools (but works fine)
430
+ * const temp = source(0);
431
+ * ```
421
432
  */
422
433
  declare class Registry {
423
434
  private units;
424
435
  private listeners;
425
436
  private currentGeneration;
426
437
  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
438
  /**
435
- * Increments generation and schedules cleanup of old units.
436
- * Called automatically when HMR is detected.
439
+ * Schedules cleanup of units that weren't re-registered (deleted from code).
437
440
  */
438
441
  private scheduleCleanup;
439
442
  /**
440
- * Removes units from old generations (likely orphaned by HMR).
443
+ * Removes units that weren't re-registered in the current generation.
444
+ * Uses mark-and-sweep: units that were re-registered have current generation,
445
+ * units that weren't are from old generation and should be removed.
441
446
  */
442
- private cleanupOldGenerations;
447
+ private cleanupDeadUnits;
443
448
  /**
444
- * Registers a new unit (Source or Guard).
445
- * Auto-assigns stable names to unnamed units for HMR stability.
446
- */
447
- register(unit: PulseUnit, offset?: number): void;
448
- /**
449
- * Retrieves all registered units.
449
+ * Registers a unit (only if it has an explicit name).
450
450
  */
451
+ register(unit: PulseUnit): void;
451
452
  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
453
  onRegister(listener: (unit: PulseUnit) => void): () => void;
459
- /**
460
- * Clears all registered units.
461
- */
462
454
  reset(): void;
463
455
  }
464
456
  declare const PulseRegistry: Registry;
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, _internalOffset = 3) {
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) {
@@ -259,53 +259,23 @@ var Registry = class {
259
259
  listeners = /* @__PURE__ */ new Set();
260
260
  currentGeneration = 0;
261
261
  cleanupScheduled = false;
262
- autoNameCache = /* @__PURE__ */ new Map();
263
262
  /**
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.
263
+ * Schedules cleanup of units that weren't re-registered (deleted from code).
295
264
  */
296
265
  scheduleCleanup() {
297
266
  if (this.cleanupScheduled) return;
298
267
  this.cleanupScheduled = true;
299
- this.currentGeneration++;
300
268
  setTimeout(() => {
301
- this.cleanupOldGenerations();
269
+ this.cleanupDeadUnits();
302
270
  this.cleanupScheduled = false;
303
271
  }, 100);
304
272
  }
305
273
  /**
306
- * Removes units from old generations (likely orphaned by HMR).
274
+ * Removes units that weren't re-registered in the current generation.
275
+ * Uses mark-and-sweep: units that were re-registered have current generation,
276
+ * units that weren't are from old generation and should be removed.
307
277
  */
308
- cleanupOldGenerations() {
278
+ cleanupDeadUnits() {
309
279
  const toDelete = [];
310
280
  this.units.forEach((unit, key) => {
311
281
  const gen = unit._generation;
@@ -315,53 +285,46 @@ var Registry = class {
315
285
  });
316
286
  toDelete.forEach((key) => this.units.delete(key));
317
287
  if (toDelete.length > 0) {
318
- console.log(`[Pulse] Cleaned up ${toDelete.length} stale units after HMR`);
288
+ console.log(`[Pulse] Cleaned up ${toDelete.length} deleted units after HMR`);
319
289
  }
320
290
  }
321
291
  /**
322
- * Registers a new unit (Source or Guard).
323
- * Auto-assigns stable names to unnamed units for HMR stability.
292
+ * Registers a unit (only if it has an explicit name).
324
293
  */
325
- register(unit, offset = 3) {
294
+ register(unit) {
326
295
  const unitWithMetadata = unit;
327
- let name = unitWithMetadata._name;
296
+ const name = unitWithMetadata._name;
328
297
  if (!name) {
329
- const isGuard2 = "state" in unit;
330
- name = this.generateAutoName(isGuard2 ? "guard" : "source", offset);
331
- unitWithMetadata._name = name;
298
+ return;
332
299
  }
333
- unitWithMetadata._generation = this.currentGeneration;
334
- if (this.units.has(name)) {
300
+ const existingUnit = this.units.get(name);
301
+ if (existingUnit) {
302
+ const existingGen = existingUnit?._generation;
303
+ if (existingGen === this.currentGeneration) {
304
+ unitWithMetadata._generation = this.currentGeneration;
305
+ this.units.set(name, unit);
306
+ this.listeners.forEach((l) => l(unit));
307
+ return;
308
+ }
309
+ this.currentGeneration++;
335
310
  this.scheduleCleanup();
336
311
  }
312
+ unitWithMetadata._generation = this.currentGeneration;
337
313
  this.units.set(name, unit);
338
314
  this.listeners.forEach((l) => l(unit));
339
315
  }
340
- /**
341
- * Retrieves all registered units.
342
- */
343
316
  getAll() {
344
317
  return Array.from(this.units.values());
345
318
  }
346
- /**
347
- * Subscribes to new unit registrations.
348
- *
349
- * @param listener - Callback receiving the newly registered unit.
350
- * @returns Unsubscribe function.
351
- */
352
319
  onRegister(listener) {
353
320
  this.listeners.add(listener);
354
321
  return () => {
355
322
  this.listeners.delete(listener);
356
323
  };
357
324
  }
358
- /**
359
- * Clears all registered units.
360
- */
361
325
  reset() {
362
326
  this.units.clear();
363
327
  this.currentGeneration = 0;
364
- this.autoNameCache.clear();
365
328
  }
366
329
  };
367
330
  var GLOBAL_KEY = "__PULSE_REGISTRY__";
@@ -372,7 +335,7 @@ if (!globalSymbols[GLOBAL_KEY]) {
372
335
  var PulseRegistry = globalSymbols[GLOBAL_KEY];
373
336
 
374
337
  // src/source.ts
375
- function source(initialValue, options = {}, _internalOffset = 3) {
338
+ function source(initialValue, options = {}) {
376
339
  let value = initialValue;
377
340
  const subscribers = /* @__PURE__ */ new Set();
378
341
  const dependents = /* @__PURE__ */ new Set();
@@ -411,7 +374,7 @@ function compute(name, dependencies, processor) {
411
374
  return guard(name, () => {
412
375
  const values = dependencies.map((dep) => typeof dep === "function" ? dep() : dep);
413
376
  return processor(...values);
414
- }, 4);
377
+ });
415
378
  }
416
379
 
417
380
  // src/composition.ts
@@ -434,7 +397,7 @@ function guardAll(nameOrGuards, maybeGuards) {
434
397
  throw new Error(message);
435
398
  }
436
399
  return true;
437
- }, 4);
400
+ });
438
401
  }
439
402
  function guardAny(nameOrGuards, maybeGuards) {
440
403
  const name = typeof nameOrGuards === "string" ? nameOrGuards : void 0;
@@ -448,7 +411,7 @@ function guardAny(nameOrGuards, maybeGuards) {
448
411
  allFails.push(message);
449
412
  }
450
413
  throw new Error(allFails.length > 0 ? allFails.join(" and ") : "no conditions met");
451
- }, 4);
414
+ });
452
415
  }
453
416
  function guardNot(nameOrTarget, maybeTarget) {
454
417
  const name = typeof nameOrTarget === "string" ? nameOrTarget : void 0;
@@ -458,7 +421,7 @@ function guardNot(nameOrTarget, maybeTarget) {
458
421
  return !target.ok();
459
422
  }
460
423
  return !target();
461
- }, 4);
424
+ });
462
425
  }
463
426
  var guardExtensions = {
464
427
  all: guardAll,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-js/core",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "module": "dist/index.js",
5
5
  "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",