@pulse-js/core 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +54 -15
- package/dist/index.d.cts +134 -119
- package/dist/index.d.ts +134 -119
- package/dist/index.js +54 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Pulse-JS
|
|
6
6
|
|
|
7
|
-
[](https://www.npmjs.com/package/@pulse-js/core)
|
|
7
|
+
[](https://www.npmjs.com/package/@pulse-js/core)
|
|
8
8
|
|
|
9
9
|
> A semantic reactivity system for modern applications. Separate reactive data (sources) from business conditions (guards) with a declarative, composable, and observable approach.
|
|
10
10
|
|
package/dist/index.cjs
CHANGED
|
@@ -138,7 +138,12 @@ function guard(nameOrFn, fn) {
|
|
|
138
138
|
if (currentId === evaluationId) {
|
|
139
139
|
persistDependencies();
|
|
140
140
|
if (resolved === false) {
|
|
141
|
-
const
|
|
141
|
+
const message = name ? `${name} failed` : "condition failed";
|
|
142
|
+
const reason = {
|
|
143
|
+
code: "GUARD_FAIL",
|
|
144
|
+
message,
|
|
145
|
+
toString: () => message
|
|
146
|
+
};
|
|
142
147
|
state = { status: "fail", reason, lastReason: reason, updatedAt: Date.now() };
|
|
143
148
|
} else if (resolved === void 0) {
|
|
144
149
|
state = { ...state, status: "pending", updatedAt: Date.now() };
|
|
@@ -150,13 +155,22 @@ function guard(nameOrFn, fn) {
|
|
|
150
155
|
}).catch((err) => {
|
|
151
156
|
if (currentId === evaluationId) {
|
|
152
157
|
persistDependencies();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
let reason;
|
|
159
|
+
if (err && err._pulseFail) {
|
|
160
|
+
reason = err._reason;
|
|
161
|
+
} else {
|
|
162
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
163
|
+
reason = err && err.meta ? {
|
|
164
|
+
code: err.code || "ERROR",
|
|
165
|
+
message,
|
|
166
|
+
meta: err.meta,
|
|
167
|
+
toString: () => message
|
|
168
|
+
} : {
|
|
169
|
+
code: "ERROR",
|
|
170
|
+
message,
|
|
171
|
+
toString: () => message
|
|
172
|
+
};
|
|
173
|
+
}
|
|
160
174
|
state = {
|
|
161
175
|
status: "fail",
|
|
162
176
|
reason,
|
|
@@ -169,7 +183,12 @@ function guard(nameOrFn, fn) {
|
|
|
169
183
|
} else {
|
|
170
184
|
persistDependencies();
|
|
171
185
|
if (result === false) {
|
|
172
|
-
const
|
|
186
|
+
const message = name ? `${name} failed` : "condition failed";
|
|
187
|
+
const reason = {
|
|
188
|
+
code: "GUARD_FAIL",
|
|
189
|
+
message,
|
|
190
|
+
toString: () => message
|
|
191
|
+
};
|
|
173
192
|
state = { status: "fail", reason, lastReason: reason, updatedAt: Date.now() };
|
|
174
193
|
} else if (result === void 0) {
|
|
175
194
|
state = { ...state, status: "pending", updatedAt: Date.now() };
|
|
@@ -198,7 +217,11 @@ function guard(nameOrFn, fn) {
|
|
|
198
217
|
message,
|
|
199
218
|
meta: err.meta,
|
|
200
219
|
toString: () => message
|
|
201
|
-
} :
|
|
220
|
+
} : {
|
|
221
|
+
code: "ERROR",
|
|
222
|
+
message,
|
|
223
|
+
toString: () => message
|
|
224
|
+
};
|
|
202
225
|
state = {
|
|
203
226
|
status: "fail",
|
|
204
227
|
reason,
|
|
@@ -259,11 +282,20 @@ function guard(nameOrFn, fn) {
|
|
|
259
282
|
lastDeps.forEach((dep) => {
|
|
260
283
|
const depName = dep._name || "unnamed";
|
|
261
284
|
const isG = "state" in dep;
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
285
|
+
if (isG) {
|
|
286
|
+
const depState = dep.state();
|
|
287
|
+
deps.push({
|
|
288
|
+
name: depName,
|
|
289
|
+
type: "guard",
|
|
290
|
+
status: depState.status,
|
|
291
|
+
reason: depState.status === "fail" ? depState.reason : void 0
|
|
292
|
+
});
|
|
293
|
+
} else {
|
|
294
|
+
deps.push({
|
|
295
|
+
name: depName,
|
|
296
|
+
type: "source"
|
|
297
|
+
});
|
|
298
|
+
}
|
|
267
299
|
});
|
|
268
300
|
return {
|
|
269
301
|
name: name || "guard",
|
|
@@ -288,6 +320,13 @@ function guard(nameOrFn, fn) {
|
|
|
288
320
|
evaluate2();
|
|
289
321
|
return g;
|
|
290
322
|
}
|
|
323
|
+
guard.map = function(source2, mapper, name) {
|
|
324
|
+
const guardName = name || `map-${source2._name || "source"}`;
|
|
325
|
+
return guard(guardName, () => {
|
|
326
|
+
const value = source2();
|
|
327
|
+
return mapper(value);
|
|
328
|
+
});
|
|
329
|
+
};
|
|
291
330
|
|
|
292
331
|
// src/registry.ts
|
|
293
332
|
var Registry = class {
|
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.
|
|
@@ -67,9 +179,9 @@ interface GuardState<T> {
|
|
|
67
179
|
/** The value returned by the evaluator (only if status is 'ok'). */
|
|
68
180
|
value?: T;
|
|
69
181
|
/** The reason why the guard failed. */
|
|
70
|
-
reason?:
|
|
182
|
+
reason?: GuardReason;
|
|
71
183
|
/** The last known failure reason, persisted even during 'pending'. */
|
|
72
|
-
lastReason?:
|
|
184
|
+
lastReason?: GuardReason;
|
|
73
185
|
/** The timestamp when the status last changed. */
|
|
74
186
|
updatedAt?: number;
|
|
75
187
|
}
|
|
@@ -79,13 +191,14 @@ interface GuardState<T> {
|
|
|
79
191
|
interface GuardExplanation {
|
|
80
192
|
name: string;
|
|
81
193
|
status: GuardStatus;
|
|
82
|
-
reason?:
|
|
83
|
-
lastReason?:
|
|
194
|
+
reason?: GuardReason;
|
|
195
|
+
lastReason?: GuardReason;
|
|
84
196
|
value?: any;
|
|
85
197
|
dependencies: Array<{
|
|
86
198
|
name: string;
|
|
87
199
|
type: 'source' | 'guard';
|
|
88
200
|
status?: GuardStatus;
|
|
201
|
+
reason?: GuardReason;
|
|
89
202
|
}>;
|
|
90
203
|
}
|
|
91
204
|
/**
|
|
@@ -124,9 +237,9 @@ interface Guard<T = boolean> {
|
|
|
124
237
|
* Returns the failure reason message if the guard is in the 'fail' state.
|
|
125
238
|
* Useful for displaying semantic error messages in the UI.
|
|
126
239
|
*
|
|
127
|
-
* @returns The error message or undefined.
|
|
240
|
+
* @returns The error message object or undefined.
|
|
128
241
|
*/
|
|
129
|
-
reason():
|
|
242
|
+
reason(): GuardReason | undefined;
|
|
130
243
|
/**
|
|
131
244
|
* Returns a snapshot of the full internal state of the guard.
|
|
132
245
|
* Useful for adapters (like React) to synchronize with the guard.
|
|
@@ -187,6 +300,9 @@ declare function guardFail(reason: string | GuardReason): never;
|
|
|
187
300
|
*/
|
|
188
301
|
declare function guardOk<T>(value: T): T;
|
|
189
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>): Source<T>;
|
|
365
|
-
|
|
366
370
|
/**
|
|
367
371
|
* Serialized state of guards for transfer from server to client.
|
|
368
372
|
*/
|
|
@@ -455,6 +459,17 @@ declare class Registry {
|
|
|
455
459
|
}
|
|
456
460
|
declare const PulseRegistry: Registry;
|
|
457
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
|
+
|
|
458
473
|
/**
|
|
459
474
|
* Pulse Guard with integrated Composition Helpers.
|
|
460
475
|
*
|
|
@@ -474,4 +489,4 @@ declare const extendedGuard: typeof guard & {
|
|
|
474
489
|
compute: typeof compute;
|
|
475
490
|
};
|
|
476
491
|
|
|
477
|
-
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.
|
|
@@ -67,9 +179,9 @@ interface GuardState<T> {
|
|
|
67
179
|
/** The value returned by the evaluator (only if status is 'ok'). */
|
|
68
180
|
value?: T;
|
|
69
181
|
/** The reason why the guard failed. */
|
|
70
|
-
reason?:
|
|
182
|
+
reason?: GuardReason;
|
|
71
183
|
/** The last known failure reason, persisted even during 'pending'. */
|
|
72
|
-
lastReason?:
|
|
184
|
+
lastReason?: GuardReason;
|
|
73
185
|
/** The timestamp when the status last changed. */
|
|
74
186
|
updatedAt?: number;
|
|
75
187
|
}
|
|
@@ -79,13 +191,14 @@ interface GuardState<T> {
|
|
|
79
191
|
interface GuardExplanation {
|
|
80
192
|
name: string;
|
|
81
193
|
status: GuardStatus;
|
|
82
|
-
reason?:
|
|
83
|
-
lastReason?:
|
|
194
|
+
reason?: GuardReason;
|
|
195
|
+
lastReason?: GuardReason;
|
|
84
196
|
value?: any;
|
|
85
197
|
dependencies: Array<{
|
|
86
198
|
name: string;
|
|
87
199
|
type: 'source' | 'guard';
|
|
88
200
|
status?: GuardStatus;
|
|
201
|
+
reason?: GuardReason;
|
|
89
202
|
}>;
|
|
90
203
|
}
|
|
91
204
|
/**
|
|
@@ -124,9 +237,9 @@ interface Guard<T = boolean> {
|
|
|
124
237
|
* Returns the failure reason message if the guard is in the 'fail' state.
|
|
125
238
|
* Useful for displaying semantic error messages in the UI.
|
|
126
239
|
*
|
|
127
|
-
* @returns The error message or undefined.
|
|
240
|
+
* @returns The error message object or undefined.
|
|
128
241
|
*/
|
|
129
|
-
reason():
|
|
242
|
+
reason(): GuardReason | undefined;
|
|
130
243
|
/**
|
|
131
244
|
* Returns a snapshot of the full internal state of the guard.
|
|
132
245
|
* Useful for adapters (like React) to synchronize with the guard.
|
|
@@ -187,6 +300,9 @@ declare function guardFail(reason: string | GuardReason): never;
|
|
|
187
300
|
*/
|
|
188
301
|
declare function guardOk<T>(value: T): T;
|
|
189
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>): Source<T>;
|
|
365
|
-
|
|
366
370
|
/**
|
|
367
371
|
* Serialized state of guards for transfer from server to client.
|
|
368
372
|
*/
|
|
@@ -455,6 +459,17 @@ declare class Registry {
|
|
|
455
459
|
}
|
|
456
460
|
declare const PulseRegistry: Registry;
|
|
457
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
|
+
|
|
458
473
|
/**
|
|
459
474
|
* Pulse Guard with integrated Composition Helpers.
|
|
460
475
|
*
|
|
@@ -474,4 +489,4 @@ declare const extendedGuard: typeof guard & {
|
|
|
474
489
|
compute: typeof compute;
|
|
475
490
|
};
|
|
476
491
|
|
|
477
|
-
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
|
@@ -102,7 +102,12 @@ function guard(nameOrFn, fn) {
|
|
|
102
102
|
if (currentId === evaluationId) {
|
|
103
103
|
persistDependencies();
|
|
104
104
|
if (resolved === false) {
|
|
105
|
-
const
|
|
105
|
+
const message = name ? `${name} failed` : "condition failed";
|
|
106
|
+
const reason = {
|
|
107
|
+
code: "GUARD_FAIL",
|
|
108
|
+
message,
|
|
109
|
+
toString: () => message
|
|
110
|
+
};
|
|
106
111
|
state = { status: "fail", reason, lastReason: reason, updatedAt: Date.now() };
|
|
107
112
|
} else if (resolved === void 0) {
|
|
108
113
|
state = { ...state, status: "pending", updatedAt: Date.now() };
|
|
@@ -114,13 +119,22 @@ function guard(nameOrFn, fn) {
|
|
|
114
119
|
}).catch((err) => {
|
|
115
120
|
if (currentId === evaluationId) {
|
|
116
121
|
persistDependencies();
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
let reason;
|
|
123
|
+
if (err && err._pulseFail) {
|
|
124
|
+
reason = err._reason;
|
|
125
|
+
} else {
|
|
126
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
127
|
+
reason = err && err.meta ? {
|
|
128
|
+
code: err.code || "ERROR",
|
|
129
|
+
message,
|
|
130
|
+
meta: err.meta,
|
|
131
|
+
toString: () => message
|
|
132
|
+
} : {
|
|
133
|
+
code: "ERROR",
|
|
134
|
+
message,
|
|
135
|
+
toString: () => message
|
|
136
|
+
};
|
|
137
|
+
}
|
|
124
138
|
state = {
|
|
125
139
|
status: "fail",
|
|
126
140
|
reason,
|
|
@@ -133,7 +147,12 @@ function guard(nameOrFn, fn) {
|
|
|
133
147
|
} else {
|
|
134
148
|
persistDependencies();
|
|
135
149
|
if (result === false) {
|
|
136
|
-
const
|
|
150
|
+
const message = name ? `${name} failed` : "condition failed";
|
|
151
|
+
const reason = {
|
|
152
|
+
code: "GUARD_FAIL",
|
|
153
|
+
message,
|
|
154
|
+
toString: () => message
|
|
155
|
+
};
|
|
137
156
|
state = { status: "fail", reason, lastReason: reason, updatedAt: Date.now() };
|
|
138
157
|
} else if (result === void 0) {
|
|
139
158
|
state = { ...state, status: "pending", updatedAt: Date.now() };
|
|
@@ -162,7 +181,11 @@ function guard(nameOrFn, fn) {
|
|
|
162
181
|
message,
|
|
163
182
|
meta: err.meta,
|
|
164
183
|
toString: () => message
|
|
165
|
-
} :
|
|
184
|
+
} : {
|
|
185
|
+
code: "ERROR",
|
|
186
|
+
message,
|
|
187
|
+
toString: () => message
|
|
188
|
+
};
|
|
166
189
|
state = {
|
|
167
190
|
status: "fail",
|
|
168
191
|
reason,
|
|
@@ -223,11 +246,20 @@ function guard(nameOrFn, fn) {
|
|
|
223
246
|
lastDeps.forEach((dep) => {
|
|
224
247
|
const depName = dep._name || "unnamed";
|
|
225
248
|
const isG = "state" in dep;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
if (isG) {
|
|
250
|
+
const depState = dep.state();
|
|
251
|
+
deps.push({
|
|
252
|
+
name: depName,
|
|
253
|
+
type: "guard",
|
|
254
|
+
status: depState.status,
|
|
255
|
+
reason: depState.status === "fail" ? depState.reason : void 0
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
deps.push({
|
|
259
|
+
name: depName,
|
|
260
|
+
type: "source"
|
|
261
|
+
});
|
|
262
|
+
}
|
|
231
263
|
});
|
|
232
264
|
return {
|
|
233
265
|
name: name || "guard",
|
|
@@ -252,6 +284,13 @@ function guard(nameOrFn, fn) {
|
|
|
252
284
|
evaluate2();
|
|
253
285
|
return g;
|
|
254
286
|
}
|
|
287
|
+
guard.map = function(source2, mapper, name) {
|
|
288
|
+
const guardName = name || `map-${source2._name || "source"}`;
|
|
289
|
+
return guard(guardName, () => {
|
|
290
|
+
const value = source2();
|
|
291
|
+
return mapper(value);
|
|
292
|
+
});
|
|
293
|
+
};
|
|
255
294
|
|
|
256
295
|
// src/registry.ts
|
|
257
296
|
var Registry = class {
|