pure-effect 0.18.0 → 0.20.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.
Files changed (5) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +678 -306
  3. package/index.d.ts +82 -22
  4. package/index.js +625 -48
  5. package/package.json +2 -1
package/index.d.ts CHANGED
@@ -35,7 +35,7 @@ export type AskState<T, E = unknown, Ctx = unknown> = {
35
35
  // Every optional option and hook takes `undefined`, which keeps its default, under `exactOptionalPropertyTypes` too.
36
36
  export type RetryOptions = {
37
37
  /** Tries after the first, so `2` makes at most 3 calls. A positive integer, 3 by default. */
38
- attempts?: number | undefined;
38
+ retries?: number | undefined;
39
39
  /** Milliseconds before the first retry, 100 by default. */
40
40
  delay?: number | undefined;
41
41
  /** Multiplies the wait before each later retry, so `2` doubles it. 1 by default, the same wait each time. */
@@ -58,8 +58,8 @@ export type RetryState<T, E = unknown, Ctx = unknown, R = T> = {
58
58
  export type RetryExhaustedError<Thrown = unknown> = {
59
59
  retryExhausted: true;
60
60
  lastError: Thrown;
61
- /** The `attempts` option: tries after the first, so the retried steps ran one more time than this. */
62
- attempts: number;
61
+ /** The `retries` option, so the retried steps ran one more time than this. */
62
+ retries: number;
63
63
  };
64
64
 
65
65
  export type ParallelOptions = {
@@ -82,7 +82,7 @@ export type ParallelState<
82
82
  effects: { [K in keyof T]: Effect<T[K], BranchError, Ctx> };
83
83
  next(values: [...V]): Effect<R, E, Ctx>;
84
84
  /** A frozen copy of the options the `Parallel` was built with. */
85
- options?: Readonly<ParallelOptions>;
85
+ options: Readonly<ParallelOptions>;
86
86
  };
87
87
 
88
88
  export type Effect<T, E = unknown, Ctx = unknown> =
@@ -194,6 +194,9 @@ export declare function Command<R, T = R, E = unknown, Ctx = unknown>(
194
194
  /** The name a Command is known by in traces, replays and spans: `meta.name`, else `cmd.name`, else `'anonymous'`. */
195
195
  export declare function commandName(command: CommandState<any, any, any, any>): string;
196
196
 
197
+ /** Reads the context passed to `runEffect`. With no argument it succeeds with the context; name its type as `Ask<AppCtx>()`. */
198
+ export declare function Ask<Ctx = unknown>(): AskState<Ctx, never, Ctx>;
199
+
197
200
  /** Reads the context passed to `runEffect`. Give the value, error and context types, or none. */
198
201
  export declare function Ask<T = never, E = never, Ctx = unknown, N extends AnyEffect = Effect<T, E, Ctx>>(
199
202
  next: (context: Ctx) => N
@@ -1245,23 +1248,70 @@ export declare function effectPipe<
1245
1248
  >;
1246
1249
  // END effectPipe overloads.
1247
1250
 
1251
+ // The symbol and the iterator's yield type exist only in the declarations, never at runtime: together they are how
1252
+ // `effectGen` reads each step's Effect type out of the body's yield type.
1253
+ declare const stepEffect: unique symbol;
1254
+ declare const stepContext: unique symbol;
1255
+
1256
+ /** A step for `yield*` inside `effectGen`, which gives back the Effect's value. Using it any other way throws. */
1257
+ // The context is also a function parameter, which TypeScript compares the opposite way to the Effect, so of two steps
1258
+ // it merges in a generator's yield type it can only drop the one that reads less context.
1259
+ export type Step<X extends AnyEffect, C = EffectContext<X>> = {
1260
+ readonly [stepEffect]?: X;
1261
+ readonly [stepContext]?: (context: C) => void;
1262
+ [Symbol.iterator](): Generator<Step<X, C>, EffectValue<X>, unknown>;
1263
+ };
1264
+
1265
+ /** Wraps an Effect for `yield*` inside `effectGen`: `const user = yield* step(findUser(email))`. */
1266
+ export declare function step<X extends AnyEffect>(effect: X): Step<X>;
1267
+
1268
+ type StepEffectOf<Y> = Y extends Step<infer X, any> ? X : never;
1269
+ // An `any` return would match both branches and turn the error union into `any`, so it counts as a plain value.
1270
+ type GenIsAny<R> = 0 extends 1 & R ? true : false;
1271
+ type ReturnedEffect<R> = GenIsAny<R> extends true ? never : R extends AnyEffect ? R : never;
1272
+ type GenValue<R> = GenIsAny<R> extends true ? any : R extends AnyEffect ? EffectValue<R> : R;
1273
+ type GenError<Y, R> = EffectError<StepEffectOf<Y>> | EffectError<ReturnedEffect<R>>;
1274
+ // A step that reads no context gives `unknown`, which would erase the others in an intersection, so it is dropped. Each
1275
+ // context is boxed, so one that is itself a union is intersected whole rather than member by member.
1276
+ type GenNeeds<X> = X extends AnyEffect
1277
+ ? EffectContext<X> extends infer C
1278
+ ? unknown extends C
1279
+ ? never
1280
+ : { context: C }
1281
+ : never
1282
+ : never;
1283
+ type GenIntersection<U> = (U extends any ? (u: U) => void : never) extends (i: infer I) => void ? I : never;
1284
+ type GenContext<Y, R> = [GenNeeds<StepEffectOf<Y> | ReturnedEffect<R>>] extends [never]
1285
+ ? unknown
1286
+ : GenIntersection<GenNeeds<StepEffectOf<Y> | ReturnedEffect<R>>> extends { context: infer C }
1287
+ ? C
1288
+ : unknown;
1289
+
1290
+ /**
1291
+ * Builds a flow function from a generator function, written with `yield* step(effect)` where `async` code writes
1292
+ * `await`. A plain return value is a `Success`; return a `Failure` to stop.
1293
+ */
1294
+ export declare function effectGen<I, Y extends Step<any, any>, R>(
1295
+ body: (input: I) => Generator<Y, R, unknown>
1296
+ ): (input: I) => Effect<GenValue<R>, GenError<Y, R>, GenContext<Y, R>>;
1297
+
1248
1298
  /**
1249
1299
  * Wraps each Command, and each `Parallel`. Call `op` and return its result; a hook that calls another passes `path` on.
1250
1300
  */
1251
1301
  export type StepRunner = (
1252
1302
  name: string,
1253
- type: string,
1303
+ type: 'Command' | 'Parallel',
1254
1304
  op: (decision?: ParallelDecision) => Promise<unknown>,
1255
1305
  path: string
1256
1306
  ) => Promise<unknown>;
1257
1307
 
1258
1308
  /**
1259
- * Which branch, if any, cancelled a `Parallel`, and how many branches it had; `branch: null` means an enclosing one
1260
- * did. A replay refuses a `Parallel` whose count differs, and a decision without one is not checked.
1309
+ * Which branch, if any, cancelled a `Parallel`, whether it threw, and how many branches it had; `branch: null` means an
1310
+ * enclosing one did. A replay refuses a `Parallel` whose count differs, and a decision without one is not checked.
1261
1311
  */
1262
1312
  export type ParallelDecision =
1263
1313
  | { cancelled: false; branches?: number | undefined }
1264
- | { cancelled: true; branch: number | null; branches?: number | undefined };
1314
+ | { cancelled: true; branch: number | null; branches?: number | undefined; threw?: true | undefined };
1265
1315
 
1266
1316
  /**
1267
1317
  * `flowName` is `context.flowName`, or `''` when the context has none. `initialInput` is what the flow was called with
@@ -1317,7 +1367,7 @@ export type ReplayStep = {
1317
1367
  /** `meta.name`, `cmd.name`, or 'anonymous'; 'Parallel' for a `Parallel`'s step. */
1318
1368
  name: string;
1319
1369
  /** `'Command'`, or `'Parallel'` for a `Parallel`'s decision. */
1320
- type: string;
1370
+ type: 'Command' | 'Parallel';
1321
1371
  /** The step's position in the flow, the same in every run; a Resolver should key off it. */
1322
1372
  path: string;
1323
1373
  };
@@ -1392,16 +1442,15 @@ export type RecordOptions<Ctx = unknown> = RecorderOptions & {
1392
1442
  };
1393
1443
 
1394
1444
  /**
1395
- * Runs a flow for real and returns its outcome with a trace, which keeps the input's type for the replay. That type is
1396
- * wrong when `redact` changes the input, or the trace marks it `unrecorded`.
1445
+ * Runs a flow for real and returns its outcome with a trace. The trace holds the flow's input when `effectPipe` or
1446
+ * `effectGen` built it.
1397
1447
  */
1398
- export declare function recordEffect<I, T, E = unknown, Ctx = unknown>(
1399
- flowFn: (input: I) => Effect<T, E, Ctx>,
1400
- initialInput: I,
1448
+ export declare function recordEffect<T, E = unknown, Ctx = unknown>(
1449
+ effect: Effect<T, E, Ctx>,
1401
1450
  ...options: unknown extends Ctx
1402
1451
  ? [options?: RecordOptions<CheckedContext<Ctx>>]
1403
1452
  : [options: RecordOptions<CheckedContext<Ctx>> & { context: RunContext<CheckedContext<Ctx>> }]
1404
- ): Promise<{ result: SuccessState<T> | FailureState<E>; trace: TraceLog<I, Ctx> & { initialInput: I } }>;
1453
+ ): Promise<{ result: SuccessState<T> | FailureState<E>; trace: TraceLog<unknown, Ctx> }>;
1405
1454
 
1406
1455
  export interface ReplayOptions<Ctx = unknown> {
1407
1456
  /** Context for `Ask`; defaults to the one the trace recorded. */
@@ -1448,13 +1497,24 @@ export declare function replayEffect<T, E = unknown, Ctx = unknown>(
1448
1497
  : [options: ReplayOptions<CheckedContext<Ctx>> & { context: RunContext<CheckedContext<Ctx>> }]
1449
1498
  ): Promise<Omit<Replay<T, E>, 'unreached'> & { unreached?: TraceEntry[] }>;
1450
1499
 
1451
- /** Replays a trace and logs each step with its recorded timing, warning about what will not replay as recorded. */
1500
+ export interface TimeTravelOptions<Ctx = unknown> {
1501
+ /** Receives each narrated line; `console.log` by default. */
1502
+ log?: ((...args: any[]) => void) | undefined;
1503
+ /** Context for `Ask`; defaults to the one the trace recorded. */
1504
+ context?: RunContext<Ctx> | undefined;
1505
+ /** The build replaying the trace; a trace recorded at another build gets a warning. */
1506
+ version?: string | undefined;
1507
+ }
1508
+
1509
+ /**
1510
+ * Replays a trace and logs each step with its recorded timing, warning about what will not replay as recorded. Returns
1511
+ * what `replayEffect` returns for a trace.
1512
+ */
1452
1513
  export declare function timeTravel<T, E = unknown, Ctx = unknown>(
1453
1514
  flowFn: (input: any) => Effect<T, E, Ctx>,
1454
1515
  traceLog: TraceLog,
1455
- options?: {
1456
- log?: ((...args: any[]) => void) | undefined;
1457
- context?: RunContext<CheckedContext<Ctx>> | undefined;
1458
- version?: string | undefined;
1459
- }
1460
- ): Promise<SuccessState<T> | FailureState<E>>;
1516
+ options?: TimeTravelOptions<CheckedContext<Ctx>>
1517
+ ): Promise<Replay<T, E>>;
1518
+
1519
+ // Only the declarations marked `export` are public; without this, a declaration file exports every top-level one.
1520
+ export {};