@rotorsoft/act 0.6.28 → 0.6.29

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.
@@ -11,51 +11,402 @@ import type { Patch, Schema } from "./types/index.js";
11
11
  * - Use `sleep()` for async delays.
12
12
  */
13
13
  /**
14
- * Immutably copies state with patches recursively.
14
+ * Immutably applies patches to a state object, creating a new copy.
15
15
  *
16
- * Keys with `undefined` or `null` values in patch are deleted.
16
+ * This function performs deep merging for plain objects while preserving
17
+ * immutability. Special types (Arrays, Dates, Maps, etc.) are replaced
18
+ * entirely rather than merged. Setting a property to `undefined` or `null`
19
+ * removes it from the resulting object.
17
20
  *
18
- * @param original The original state object
19
- * @param patches The patches to merge
20
- * @returns A new patched state
21
+ * Used internally by the framework to apply event patches to state, but
22
+ * can also be used directly for state transformations.
21
23
  *
22
- * @example
23
- * const newState = patch(oldState, { count: 5 });
24
+ * **Merging rules:**
25
+ * - Plain objects: Deep merge recursively
26
+ * - Arrays, Dates, RegExp, Maps, Sets, TypedArrays: Replace entirely
27
+ * - `undefined` or `null` values: Delete the property
28
+ * - Primitives: Replace with patch value
29
+ *
30
+ * @param original - The original state object to patch
31
+ * @param patches - The patches to apply (partial state)
32
+ * @returns A new state object with patches applied
33
+ *
34
+ * @example Simple property update
35
+ * ```typescript
36
+ * import { patch } from "@rotorsoft/act";
37
+ *
38
+ * const state = { count: 0, name: "Alice" };
39
+ * const updated = patch(state, { count: 5 });
40
+ * // Result: { count: 5, name: "Alice" }
41
+ * // Original unchanged: { count: 0, name: "Alice" }
42
+ * ```
43
+ *
44
+ * @example Nested object patching
45
+ * ```typescript
46
+ * const state = {
47
+ * user: { id: 1, name: "Alice", email: "alice@example.com" },
48
+ * settings: { theme: "dark" }
49
+ * };
50
+ *
51
+ * const updated = patch(state, {
52
+ * user: { email: "newemail@example.com" }
53
+ * });
54
+ * // Result: {
55
+ * // user: { id: 1, name: "Alice", email: "newemail@example.com" },
56
+ * // settings: { theme: "dark" }
57
+ * // }
58
+ * ```
59
+ *
60
+ * @example Property deletion
61
+ * ```typescript
62
+ * const state = { count: 5, temp: "value", flag: true };
63
+ *
64
+ * const updated = patch(state, {
65
+ * temp: undefined, // Delete temp
66
+ * flag: null // Delete flag
67
+ * });
68
+ * // Result: { count: 5 }
69
+ * ```
70
+ *
71
+ * @example Array replacement (not merged)
72
+ * ```typescript
73
+ * const state = { items: [1, 2, 3], meta: { count: 3 } };
74
+ *
75
+ * const updated = patch(state, {
76
+ * items: [4, 5] // Arrays are replaced, not merged
77
+ * });
78
+ * // Result: { items: [4, 5], meta: { count: 3 } }
79
+ * ```
80
+ *
81
+ * @example In event handlers
82
+ * ```typescript
83
+ * import { state } from "@rotorsoft/act";
84
+ * import { z } from "zod";
85
+ *
86
+ * const Counter = state("Counter", z.object({ count: z.number() }))
87
+ * .init(() => ({ count: 0 }))
88
+ * .emits({ Incremented: z.object({ by: z.number() }) })
89
+ * .patch({
90
+ * Incremented: (event, state) => {
91
+ * // patch() is called internally here
92
+ * return { count: state.count + event.data.by };
93
+ * }
94
+ * });
95
+ * ```
96
+ *
97
+ * @see {@link Patch} for the patch type definition
24
98
  */
25
99
  export declare const patch: <S extends Schema>(original: Readonly<S>, patches: Readonly<Patch<S>>) => Readonly<S>;
26
100
  /**
27
- * Validates a payload against a Zod schema, throwing a ValidationError on failure.
101
+ * Validates a payload against a Zod schema.
102
+ *
103
+ * This is the primary validation function used throughout the Act framework.
104
+ * It parses the payload using the provided Zod schema and throws a
105
+ * {@link ValidationError} with detailed error information if validation fails.
106
+ *
107
+ * When no schema is provided, the payload is returned as-is without validation.
108
+ * This allows for optional validation in the framework.
109
+ *
110
+ * The framework automatically calls this function when:
111
+ * - Actions are invoked via `app.do()`
112
+ * - Events are emitted from action handlers
113
+ * - States are initialized
28
114
  *
29
- * @param target The name of the target (for error reporting)
30
- * @param payload The payload to validate
31
- * @param schema (Optional) The Zod schema to validate against
32
- * @returns The validated payload
33
- * @throws ValidationError if validation fails
115
+ * @param target - Name of the target being validated (used in error messages)
116
+ * @param payload - The data to validate
117
+ * @param schema - Optional Zod schema to validate against
118
+ * @returns The validated and type-safe payload
119
+ * @throws {@link ValidationError} if validation fails with detailed error info
34
120
  *
35
- * @example
36
- * const valid = validate("User", userPayload, userSchema);
121
+ * @example Basic validation
122
+ * ```typescript
123
+ * import { validate } from "@rotorsoft/act";
124
+ * import { z } from "zod";
125
+ *
126
+ * const UserSchema = z.object({
127
+ * email: z.string().email(),
128
+ * age: z.number().min(0)
129
+ * });
130
+ *
131
+ * const user = validate("User", { email: "alice@example.com", age: 30 }, UserSchema);
132
+ * // Returns: { email: "alice@example.com", age: 30 }
133
+ * ```
134
+ *
135
+ * @example Handling validation errors
136
+ * ```typescript
137
+ * import { validate, ValidationError } from "@rotorsoft/act";
138
+ * import { z } from "zod";
139
+ *
140
+ * const schema = z.object({
141
+ * email: z.string().email(),
142
+ * age: z.number().min(18)
143
+ * });
144
+ *
145
+ * try {
146
+ * validate("User", { email: "invalid", age: 15 }, schema);
147
+ * } catch (error) {
148
+ * if (error instanceof ValidationError) {
149
+ * console.error("Target:", error.target); // "User"
150
+ * console.error("Payload:", error.payload); // { email: "invalid", age: 15 }
151
+ * console.error("Details:", error.details); // Prettified Zod errors
152
+ * // Details shows: email must be valid, age must be >= 18
153
+ * }
154
+ * }
155
+ * ```
156
+ *
157
+ * @example Optional validation
158
+ * ```typescript
159
+ * // When schema is undefined, payload is returned as-is
160
+ * const data = validate("Data", { any: "value" });
161
+ * // Returns: { any: "value" } without validation
162
+ * ```
163
+ *
164
+ * @example In action definitions
165
+ * ```typescript
166
+ * import { state } from "@rotorsoft/act";
167
+ * import { z } from "zod";
168
+ *
169
+ * const Counter = state("Counter", z.object({ count: z.number() }))
170
+ * .init(() => ({ count: 0 }))
171
+ * .emits({ Incremented: z.object({ by: z.number().positive() }) })
172
+ * .on("increment", z.object({ by: z.number() }))
173
+ * .emit((action) => {
174
+ * // validate() is called automatically before this runs
175
+ * // action.by is guaranteed to be a number
176
+ * return ["Incremented", { by: action.by }];
177
+ * })
178
+ * .build();
179
+ * ```
180
+ *
181
+ * @example Custom validation in application code
182
+ * ```typescript
183
+ * import { validate } from "@rotorsoft/act";
184
+ * import { z } from "zod";
185
+ *
186
+ * const ConfigSchema = z.object({
187
+ * apiKey: z.string().min(32),
188
+ * timeout: z.number().positive(),
189
+ * retries: z.number().int().min(0).max(10)
190
+ * });
191
+ *
192
+ * function loadConfig(raw: unknown) {
193
+ * return validate("AppConfig", raw, ConfigSchema);
194
+ * }
195
+ * ```
196
+ *
197
+ * @see {@link ValidationError} for error handling
198
+ * @see {@link https://zod.dev | Zod documentation} for schema definition
37
199
  */
38
200
  export declare const validate: <S>(target: string, payload: Readonly<S>, schema?: ZodType<S>) => Readonly<S>;
39
201
  /**
40
- * Extends the target payload with the source payload after validating the source.
202
+ * Validates and merges configuration objects.
203
+ *
204
+ * This function first validates the source object against a Zod schema using
205
+ * {@link validate}, then merges it with an optional target object. The source
206
+ * properties override target properties in the result.
207
+ *
208
+ * Primarily used for configuration management where you want to:
209
+ * 1. Define default configuration values
210
+ * 2. Load environment-specific overrides
211
+ * 3. Validate the final configuration
212
+ *
213
+ * The framework uses this internally for the {@link config} function.
41
214
  *
42
- * @param source The source object to validate and merge
43
- * @param schema The Zod schema for the source
44
- * @param target (Optional) The target object to extend
45
- * @returns The merged and validated object
215
+ * @template S - Source object type (must be a record)
216
+ * @template T - Target object type (must be a record)
217
+ * @param source - The source object to validate and use as overrides
218
+ * @param schema - Zod schema to validate the source against
219
+ * @param target - Optional target object with default values
220
+ * @returns Merged object with validated source overriding target
221
+ * @throws {@link ValidationError} if source fails schema validation
46
222
  *
47
- * @example
48
- * const config = extend(envConfig, configSchema, defaultConfig);
223
+ * @example Basic configuration merging
224
+ * ```typescript
225
+ * import { extend } from "@rotorsoft/act";
226
+ * import { z } from "zod";
227
+ *
228
+ * const ConfigSchema = z.object({
229
+ * host: z.string(),
230
+ * port: z.number(),
231
+ * debug: z.boolean()
232
+ * });
233
+ *
234
+ * const defaults = { host: "localhost", port: 3000, debug: false };
235
+ * const overrides = { port: 8080, debug: true };
236
+ *
237
+ * const config = extend(overrides, ConfigSchema, defaults);
238
+ * // Result: { host: "localhost", port: 8080, debug: true }
239
+ * ```
240
+ *
241
+ * @example Environment-based configuration
242
+ * ```typescript
243
+ * import { extend } from "@rotorsoft/act";
244
+ * import { z } from "zod";
245
+ *
246
+ * const DbConfigSchema = z.object({
247
+ * host: z.string(),
248
+ * port: z.number(),
249
+ * database: z.string(),
250
+ * user: z.string(),
251
+ * password: z.string()
252
+ * });
253
+ *
254
+ * const defaults = {
255
+ * host: "localhost",
256
+ * port: 5432,
257
+ * database: "myapp_dev",
258
+ * user: "postgres",
259
+ * password: "dev"
260
+ * };
261
+ *
262
+ * const envConfig = {
263
+ * host: process.env.DB_HOST || "localhost",
264
+ * port: parseInt(process.env.DB_PORT || "5432"),
265
+ * database: process.env.DB_NAME || "myapp_dev",
266
+ * user: process.env.DB_USER || "postgres",
267
+ * password: process.env.DB_PASSWORD || "dev"
268
+ * };
269
+ *
270
+ * // Validates environment config and merges with defaults
271
+ * const dbConfig = extend(envConfig, DbConfigSchema, defaults);
272
+ * ```
273
+ *
274
+ * @example Framework usage
275
+ * ```typescript
276
+ * // This is how Act's config() function uses extend internally:
277
+ * import { extend } from "@rotorsoft/act";
278
+ * import { z } from "zod";
279
+ *
280
+ * const BaseSchema = z.object({
281
+ * env: z.enum(["development", "test", "staging", "production"]),
282
+ * logLevel: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]),
283
+ * sleepMs: z.number().int().min(0).max(5000)
284
+ * });
285
+ *
286
+ * const packageData = { name: "my-app", version: "1.0.0" };
287
+ * const runtimeConfig = { env: "production", logLevel: "info", sleepMs: 100 };
288
+ *
289
+ * const config = extend(
290
+ * { ...packageData, ...runtimeConfig },
291
+ * BaseSchema,
292
+ * packageData
293
+ * );
294
+ * ```
295
+ *
296
+ * @example With validation error handling
297
+ * ```typescript
298
+ * import { extend, ValidationError } from "@rotorsoft/act";
299
+ * import { z } from "zod";
300
+ *
301
+ * const schema = z.object({
302
+ * apiKey: z.string().min(32),
303
+ * timeout: z.number().positive()
304
+ * });
305
+ *
306
+ * try {
307
+ * const config = extend(
308
+ * { apiKey: "short", timeout: -1 },
309
+ * schema
310
+ * );
311
+ * } catch (error) {
312
+ * if (error instanceof ValidationError) {
313
+ * console.error("Invalid configuration:", error.details);
314
+ * }
315
+ * }
316
+ * ```
317
+ *
318
+ * @see {@link validate} for validation details
319
+ * @see {@link config} for framework configuration
320
+ * @see {@link ValidationError} for error handling
49
321
  */
50
322
  export declare const extend: <S extends Record<string, unknown>, T extends Record<string, unknown>>(source: Readonly<S>, schema: ZodType<S>, target?: Readonly<T>) => Readonly<S & T>;
51
323
  /**
52
- * Async helper to pause execution for a given number of milliseconds.
324
+ * Pauses async execution for a specified duration.
325
+ *
326
+ * This is a simple async utility for adding delays in your code. When called
327
+ * without arguments, it uses the configured sleep duration from `config().sleepMs`,
328
+ * which defaults to 100ms in development and 0ms in test environments.
329
+ *
330
+ * The framework uses this internally in store adapters to simulate I/O delays
331
+ * in the {@link InMemoryStore}.
332
+ *
333
+ * **Note:** In test environments (NODE_ENV=test), the default sleep duration is
334
+ * 0ms to keep tests fast.
335
+ *
336
+ * @param ms - Optional duration in milliseconds (defaults to config().sleepMs)
337
+ * @returns Promise that resolves after the specified delay
338
+ *
339
+ * @example Using default sleep duration
340
+ * ```typescript
341
+ * import { sleep } from "@rotorsoft/act";
342
+ *
343
+ * async function processWithDelay() {
344
+ * console.log("Starting...");
345
+ * await sleep(); // Uses config().sleepMs (100ms in dev, 0ms in test)
346
+ * console.log("Continued after delay");
347
+ * }
348
+ * ```
349
+ *
350
+ * @example Custom sleep duration
351
+ * ```typescript
352
+ * import { sleep } from "@rotorsoft/act";
353
+ *
354
+ * async function retryWithBackoff(fn: () => Promise<void>, retries = 3) {
355
+ * for (let i = 0; i < retries; i++) {
356
+ * try {
357
+ * await fn();
358
+ * return;
359
+ * } catch (error) {
360
+ * if (i < retries - 1) {
361
+ * const delay = Math.pow(2, i) * 1000; // Exponential backoff
362
+ * console.log(`Retrying in ${delay}ms...`);
363
+ * await sleep(delay);
364
+ * } else {
365
+ * throw error;
366
+ * }
367
+ * }
368
+ * }
369
+ * }
370
+ * ```
371
+ *
372
+ * @example Rate limiting
373
+ * ```typescript
374
+ * import { sleep } from "@rotorsoft/act";
375
+ *
376
+ * async function processItems(items: string[]) {
377
+ * for (const item of items) {
378
+ * await processItem(item);
379
+ * await sleep(500); // 500ms between items
380
+ * }
381
+ * }
382
+ * ```
383
+ *
384
+ * @example Framework internal usage
385
+ * ```typescript
386
+ * // InMemoryStore uses sleep to simulate async I/O
387
+ * class InMemoryStore implements Store {
388
+ * async query(...) {
389
+ * await sleep(); // Simulate database latency
390
+ * // ... query logic
391
+ * }
392
+ *
393
+ * async commit(...) {
394
+ * await sleep(); // Simulate write latency
395
+ * // ... commit logic
396
+ * }
397
+ * }
398
+ * ```
399
+ *
400
+ * @example Configuring default sleep duration
401
+ * ```bash
402
+ * # Set custom default sleep duration via environment variable
403
+ * SLEEP_MS=50 npm start
53
404
  *
54
- * @param ms (Optional) Milliseconds to sleep (defaults to config().sleepMs)
55
- * @returns Promise that resolves after the delay
405
+ * # In tests, it's automatically 0
406
+ * NODE_ENV=test npm test
407
+ * ```
56
408
  *
57
- * @example
58
- * await sleep(1000); // sleep for 1 second
409
+ * @see {@link config} for sleep duration configuration
59
410
  */
60
411
  export declare function sleep(ms?: number): Promise<unknown>;
61
412
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,OAAO,EAAiB,MAAM,KAAK,CAAC;AAEjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA+BtD;;;;;;;;;GASG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,SAAS,MAAM,EACpC,UAAU,QAAQ,CAAC,CAAC,CAAC,EACrB,SAAS,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAC1B,QAAQ,CAAC,CAAC,CAgBZ,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,QAAQ,MAAM,EACd,SAAS,QAAQ,CAAC,CAAC,CAAC,EACpB,SAAS,OAAO,CAAC,CAAC,CAAC,KAClB,QAAQ,CAAC,CAAC,CAaZ,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,GACjB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjC,QAAQ,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,OAAO,CAAC,CAAC,CAAC,EAClB,SAAS,QAAQ,CAAC,CAAC,CAAC,KACnB,QAAQ,CAAC,CAAC,GAAG,CAAC,CAGhB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,oBAEtC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,OAAO,EAAiB,MAAM,KAAK,CAAC;AAEjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA+BtD;;;;;;;;;GASG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,SAAS,MAAM,EACpC,UAAU,QAAQ,CAAC,CAAC,CAAC,EACrB,SAAS,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAC1B,QAAQ,CAAC,CAAC,CAgBZ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,QAAQ,MAAM,EACd,SAAS,QAAQ,CAAC,CAAC,CAAC,EACpB,SAAS,OAAO,CAAC,CAAC,CAAC,KAClB,QAAQ,CAAC,CAAC,CAaZ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwHG;AACH,eAAO,MAAM,MAAM,GACjB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjC,QAAQ,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,OAAO,CAAC,CAAC,CAAC,EAClB,SAAS,QAAQ,CAAC,CAAC,CAAC,KACnB,QAAQ,CAAC,CAAC,GAAG,CAAC,CAGhB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AACH,wBAAsB,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,oBAEtC"}