@kontsedal/olas-core 0.0.3 → 0.0.5

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.
@@ -251,6 +251,13 @@ type AsyncState<T> = {
251
251
  refetch: () => Promise<T>;
252
252
  reset: () => void;
253
253
  firstValue: () => Promise<T>;
254
+ /**
255
+ * Alias of `firstValue()` — clearer name for Suspense / `React.use(...)`
256
+ * use cases. Resolves with `data` on first success (short-circuits if
257
+ * already settled), rejects with `error` on the first failure. Use this
258
+ * to suspend a React tree until the query lands its first value.
259
+ */
260
+ promise: () => Promise<T>;
254
261
  };
255
262
  /**
256
263
  * Returned by `query.setData(...)` or `localCache.setData(...)`. Used by
@@ -580,7 +587,67 @@ type MutationSpec<V, R> = {
580
587
  concurrency?: MutationConcurrency;
581
588
  retry?: RetryPolicy;
582
589
  retryDelay?: RetryDelay;
590
+ /**
591
+ * Stable identifier used by the mutation-queue plugin
592
+ * (`@kontsedal/olas-mutation-queue`) to route persistable runs across a
593
+ * page reload. REQUIRED when `persist: true`. Recommended even without
594
+ * `persist` if you want devtools to group runs across mutation instances
595
+ * — same shape as `defineQuery({ queryId })`.
596
+ *
597
+ * Don't auto-derive from `name` or function identity; both are fragile
598
+ * under minification.
599
+ */
600
+ mutationId?: string;
601
+ /**
602
+ * Opt this mutation into durable persistence. When `true`, the runner
603
+ * emits `onMutationEnqueue` to plugins before the user's `mutate` runs
604
+ * and `onMutationSettle` after retries exhaust. Requires `mutationId`.
605
+ * SPEC §13.3.
606
+ */
607
+ persist?: boolean;
608
+ };
609
+ /**
610
+ * Module-scope handle for a persistable mutation. Returned by
611
+ * `defineMutation(...)`. Pass it to `ctx.mutation(...)` (spread or as-is)
612
+ * so per-controller lifecycle hooks (`onSuccess` / `onError` / ...) can be
613
+ * layered on top.
614
+ *
615
+ * Registering at module import time means the mutation-queue plugin can
616
+ * replay pending runs from durable storage during `init` — before any
617
+ * controller reconstructs.
618
+ */
619
+ type MutationDef<V, R> = MutationSpec<V, R> & {
620
+ readonly __olas: 'mutation';
621
+ readonly mutationId: string;
583
622
  };
623
+ /**
624
+ * Register a persistable mutation at module scope. Returns the spec
625
+ * unchanged (with a `__olas: 'mutation'` brand) so consumers can pass it
626
+ * to `ctx.mutation(...)`, optionally spreading per-controller hooks on
627
+ * top:
628
+ *
629
+ * ```ts
630
+ * // module-scope
631
+ * export const createOrder = defineMutation({
632
+ * mutationId: 'order/create',
633
+ * mutate: async (vars: OrderInput, { signal }) => api.createOrder(vars, { signal }),
634
+ * })
635
+ *
636
+ * // controller
637
+ * const m = ctx.mutation({
638
+ * ...createOrder,
639
+ * onSuccess: () => toast('Order placed'),
640
+ * })
641
+ * ```
642
+ *
643
+ * The `mutate` function MUST NOT close over controller-instance state — on
644
+ * replay there is no controller. Module-level dependencies (a shared `api`
645
+ * client, etc.) are fine.
646
+ */
647
+ declare function defineMutation<V, R>(spec: MutationSpec<V, R> & {
648
+ mutationId: string;
649
+ persist?: boolean;
650
+ }): MutationDef<V, R>;
584
651
  /**
585
652
  * A running mutation. Created via `ctx.mutation(spec)` — the controller owns
586
653
  * its lifetime. Each `run(vars)` returns a Promise; the four signals reflect
@@ -722,6 +789,33 @@ type GcEvent = {
722
789
  keyArgs: readonly unknown[];
723
790
  kind: 'data' | 'infinite';
724
791
  };
792
+ /**
793
+ * Emitted when a persistable mutation (`spec.persist === true`) starts
794
+ * executing — before the user's `mutate` is invoked. Plugins use this to
795
+ * persist the variables to durable storage; if the page reloads mid-run,
796
+ * the queue replays from these entries.
797
+ *
798
+ * `runId` is unique per execution (a single `mutation.run(...)` call OR a
799
+ * replay attempt). `attempt` counts retry passes within a single runId.
800
+ */
801
+ type MutationEnqueueEvent = {
802
+ mutationId: string;
803
+ runId: string;
804
+ variables: unknown;
805
+ attempt: number;
806
+ };
807
+ /**
808
+ * Emitted after a persistable mutation settles. Plugins use this to drop
809
+ * the run from the durable queue (on `'success'` or `'error'` after retries
810
+ * exhaust), or to leave it pending (on `'cancelled'` — e.g. parent dispose
811
+ * mid-flight).
812
+ */
813
+ type MutationSettleEvent = {
814
+ mutationId: string;
815
+ runId: string;
816
+ outcome: 'success' | 'error' | 'cancelled'; /** Only present on `'error'` — the final thrown value after retries. */
817
+ error?: unknown;
818
+ };
725
819
  /**
726
820
  * Plugin contract. Every hook is optional. Hooks are wrapped in try/catch
727
821
  * by `QueryClient`; thrown exceptions are routed through the root's
@@ -730,12 +824,23 @@ type GcEvent = {
730
824
  type QueryClientPlugin = {
731
825
  /**
732
826
  * Called once after the `QueryClient` is constructed. Use it to wire up
733
- * transport listeners and capture the `QueryClientPluginApi`.
827
+ * transport listeners and capture the `QueryClientPluginApi`. SPEC §13.2.
828
+ *
829
+ * Persistable-mutation replay typically happens HERE — module-scope
830
+ * `defineMutation(...)` calls have already registered their handlers by
831
+ * the time `createRoot(...)` runs, so `init` can walk durable storage
832
+ * and re-invoke registered mutates for any pending entries.
734
833
  */
735
834
  init?(api: QueryClientPluginApi): void;
736
835
  onSetData?(event: SetDataEvent): void;
737
836
  onInvalidate?(event: InvalidateEvent): void;
738
- onGc?(event: GcEvent): void; /** Called from `QueryClient.dispose`. Tear down transports / listeners here. */
837
+ onGc?(event: GcEvent): void;
838
+ /**
839
+ * Fired when a persistable mutation (`spec.persist === true`) starts
840
+ * executing. SPEC §13.3.
841
+ */
842
+ onMutationEnqueue?(event: MutationEnqueueEvent): void; /** Fired after a persistable mutation settles. SPEC §13.3. */
843
+ onMutationSettle?(event: MutationSettleEvent): void; /** Called from `QueryClient.dispose`. Tear down transports / listeners here. */
739
844
  dispose?(): void;
740
845
  };
741
846
  /**
@@ -755,6 +860,33 @@ type RegisteredQuery = {
755
860
  * in the receiving tab).
756
861
  */
757
862
  declare function lookupRegisteredQuery(queryId: string): RegisteredQuery | undefined;
863
+ /**
864
+ * Shape stored in the `mutationId → handler` registry. Only the `mutate`
865
+ * function and the id are needed for replay — lifecycle hooks like
866
+ * `onSuccess` / `onError` are per-controller and can't be safely replayed
867
+ * across page reloads (the controller doesn't exist yet).
868
+ */
869
+ type RegisteredMutation = {
870
+ readonly mutationId: string;
871
+ /**
872
+ * Replay-safe `mutate`. Matches `MutationSpec.mutate` 1:1 — receives the
873
+ * variables and an `AbortSignal`. The mutation-queue plugin invokes this
874
+ * directly on replay, so the implementation MUST NOT close over
875
+ * controller-instance state. Module-level deps (a shared `api` client,
876
+ * etc.) are fine.
877
+ */
878
+ readonly mutate: (vars: unknown, signal: AbortSignal) => Promise<unknown>;
879
+ };
880
+ /**
881
+ * Look up a registered mutation by id. Returns `undefined` when no
882
+ * mutation with that id has been defined — typical when a queue entry
883
+ * references a mutation whose module hasn't been imported (e.g. a
884
+ * code-split route boundary). The plugin should leave such entries in
885
+ * place and retry once the module loads.
886
+ */
887
+ declare function lookupRegisteredMutation(mutationId: string): RegisteredMutation | undefined;
888
+ /** Test-only — drop a registered mutation. Not exported from the package. */
889
+ declare function _unregisterMutationById(mutationId: string): void;
758
890
  //#endregion
759
891
  //#region src/scope.d.ts
760
892
  /**
@@ -941,13 +1073,13 @@ type Ctx<TDeps = AmbientDeps> = {
941
1073
  keepPreviousData?: boolean;
942
1074
  initialData?: T | undefined;
943
1075
  }): LocalCache<T>;
944
- use<Args extends unknown[], T>(source: Query<Args, T>, keyOrOptions?: (() => Args) | UseOptions<Args>): QuerySubscription<T>;
945
- use<Args extends unknown[], TPage, TItem>(source: InfiniteQuery<Args, TPage, TItem>, keyOrOptions?: (() => Args) | UseOptions<Args>): InfiniteQuerySubscription<TPage, TItem>;
946
1076
  use<Args extends unknown[], T, U>(source: Query<Args, T>, options: {
947
- key?: () => Args;
1077
+ key?: () => readonly [...Args];
948
1078
  enabled?: () => boolean;
949
1079
  select: (data: T) => U;
950
1080
  }): QuerySubscription<U>;
1081
+ use<Args extends unknown[], T>(source: Query<Args, T>, keyOrOptions?: (() => readonly [...Args]) | UseOptions<Args>): QuerySubscription<T>;
1082
+ use<Args extends unknown[], TPage, TItem>(source: InfiniteQuery<Args, TPage, TItem>, keyOrOptions?: (() => readonly [...Args]) | UseOptions<Args>): InfiniteQuerySubscription<TPage, TItem>;
951
1083
  mutation<V, R>(spec: MutationSpec<V, R>): Mutation<V, R>;
952
1084
  emitter<T = void>(): Emitter<T>;
953
1085
  field<T>(initial: T, validators?: ReadonlyArray<Validator<T>>): Field<T>;
@@ -1072,5 +1204,5 @@ type Root<Api> = Api & {
1072
1204
  readonly __debug: DebugBus;
1073
1205
  };
1074
1206
  //#endregion
1075
- export { FormErrors as $, DebugEvent as A, QuerySpec as B, SetDataEvent as C, MutationSpec as D, MutationConcurrency as E, AsyncStatus as F, UseOptions as G, RetryDelay as H, DehydratedEntry as I, FieldArrayItemErrors as J, DeepPartial as K, DehydratedState as L, InfiniteQuerySpec as M, InfiniteQuerySubscription as N, DebugBus as O, AsyncState as P, Form as Q, LocalCache as R, RegisteredQuery as S, Mutation as T, RetryPolicy as U, QuerySubscription as V, Snapshot as W, FieldArrayValidator as X, FieldArrayOptions as Y, FieldArrayValue as Z, defineScope as _, CollectionFactoryResult as a, Validator as at, QueryClientPlugin as b, CtrlApi as c, Signal as ct, Field as d, EmitterErrorReporter as dt, FormOptions as et, LazyChild as f, createEmitter as ft, ScopeOptions as g, Scope as h, CollectionFactoryOptions as i, ItemInitial as it, InfiniteQuery as j, DebugCacheEntry as k, CtrlProps as l, ErrorContext as lt, RootOptions as m, Collection as n, FormValidator as nt, CollectionHomogeneousOptions as o, Computed as ot, Root as p, FieldArray as q, CollectionFactoryApi as r, FormValue as rt, ControllerDef as s, ReadSignal as st, AmbientDeps as t, FormSchema as tt, Ctx as u, Emitter as ut, GcEvent as v, lookupRegisteredQuery as w, QueryClientPluginApi as x, InvalidateEvent as y, Query as z };
1076
- //# sourceMappingURL=types-r_TVaRkD.d.cts.map
1207
+ export { DeepPartial as $, Mutation as A, InfiniteQuerySubscription as B, QueryClientPluginApi as C, _unregisterMutationById as D, SetDataEvent as E, DebugBus as F, LocalCache as G, AsyncStatus as H, DebugCacheEntry as I, QuerySubscription as J, Query as K, DebugEvent as L, MutationDef as M, MutationSpec as N, lookupRegisteredMutation as O, defineMutation as P, UseOptions as Q, InfiniteQuery as R, QueryClientPlugin as S, RegisteredQuery as T, DehydratedEntry as U, AsyncState as V, DehydratedState as W, RetryPolicy as X, RetryDelay as Y, Snapshot as Z, defineScope as _, Emitter as _t, CollectionFactoryResult as a, Form as at, MutationEnqueueEvent as b, CtrlApi as c, FormSchema as ct, Field as d, ItemInitial as dt, FieldArray as et, LazyChild as f, Validator as ft, ScopeOptions as g, ErrorContext as gt, Scope as h, Signal as ht, CollectionFactoryOptions as i, FieldArrayValue as it, MutationConcurrency as j, lookupRegisteredQuery as k, CtrlProps as l, FormValidator as lt, RootOptions as m, ReadSignal as mt, Collection as n, FieldArrayOptions as nt, CollectionHomogeneousOptions as o, FormErrors as ot, Root as p, Computed as pt, QuerySpec as q, CollectionFactoryApi as r, FieldArrayValidator as rt, ControllerDef as s, FormOptions as st, AmbientDeps as t, FieldArrayItemErrors as tt, Ctx as u, FormValue as ut, GcEvent as v, EmitterErrorReporter as vt, RegisteredMutation as w, MutationSettleEvent as x, InvalidateEvent as y, createEmitter as yt, InfiniteQuerySpec as z };
1208
+ //# sourceMappingURL=types-C4Vtkxbn.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-C4Vtkxbn.d.cts","names":[],"sources":["../src/emitter.ts","../src/errors.ts","../src/signals/types.ts","../src/forms/types.ts","../src/forms/form-types.ts","../src/query/types.ts","../src/query/infinite.ts","../src/devtools.ts","../src/query/mutation.ts","../src/query/plugin.ts","../src/scope.ts","../src/controller/types.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontsedal/olas-core",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Olas core — controller-tree state management with signals, queries, mutations, and forms. Framework-agnostic.",
5
5
  "keywords": [
6
6
  "olas",
@@ -340,12 +340,22 @@ export class ControllerInstance {
340
340
  },
341
341
 
342
342
  mutation<V, R>(spec: MutationSpec<V, R>): Mutation<V, R> {
343
+ const queryClient = self.rootShared.queryClient
343
344
  const m = createMutation<V, R>(
344
345
  spec,
345
346
  self.rootShared.onError,
346
347
  self.path,
347
- self.rootShared.queryClient.mutationsInflight$,
348
+ queryClient.mutationsInflight$,
348
349
  self.rootShared.devtools,
350
+ // Lifecycle hooks for persistable mutations — only wired when
351
+ // `spec.persist === true`. `createMutation` validates the
352
+ // `mutationId` requirement before construction.
353
+ spec.persist === true
354
+ ? {
355
+ emitEnqueue: (ev) => queryClient.emitMutationEnqueue(ev),
356
+ emitSettle: (ev) => queryClient.emitMutationSettle(ev),
357
+ }
358
+ : undefined,
349
359
  )
350
360
  self.entries.push({ kind: 'cleanup', dispose: () => m.dispose() })
351
361
  return m
@@ -185,21 +185,25 @@ export type Ctx<TDeps = AmbientDeps> = {
185
185
  },
186
186
  ): LocalCache<T>
187
187
 
188
+ // Select-projecting overload — picked when the options object has a
189
+ // required `select` field. `key`'s return is `readonly [...Args]` so
190
+ // callers writing `() => [id] as const` flow through cleanly.
191
+ use<Args extends unknown[], T, U>(
192
+ source: Query<Args, T>,
193
+ options: {
194
+ key?: () => readonly [...Args]
195
+ enabled?: () => boolean
196
+ select: (data: T) => U
197
+ },
198
+ ): QuerySubscription<U>
188
199
  use<Args extends unknown[], T>(
189
200
  source: Query<Args, T>,
190
- keyOrOptions?: (() => Args) | UseOptions<Args>,
201
+ keyOrOptions?: (() => readonly [...Args]) | UseOptions<Args>,
191
202
  ): QuerySubscription<T>
192
203
  use<Args extends unknown[], TPage, TItem>(
193
204
  source: InfiniteQuery<Args, TPage, TItem>,
194
- keyOrOptions?: (() => Args) | UseOptions<Args>,
205
+ keyOrOptions?: (() => readonly [...Args]) | UseOptions<Args>,
195
206
  ): InfiniteQuerySubscription<TPage, TItem>
196
- // Overload — `select` projects T → U; the returned subscription's `data`
197
- // is `U | undefined`. The required `select` field is the discriminator
198
- // that picks this overload over the plain-key one above.
199
- use<Args extends unknown[], T, U>(
200
- source: Query<Args, T>,
201
- options: { key?: () => Args; enabled?: () => boolean; select: (data: T) => U },
202
- ): QuerySubscription<U>
203
207
 
204
208
  mutation<V, R>(spec: MutationSpec<V, R>): Mutation<V, R>
205
209
 
package/src/index.ts CHANGED
@@ -68,18 +68,27 @@ export { stableHash } from './query/keys'
68
68
  export type {
69
69
  Mutation,
70
70
  MutationConcurrency,
71
+ MutationDef,
71
72
  MutationSpec,
72
73
  } from './query/mutation'
73
- // Query-client plugins (§13.2)
74
+ export { defineMutation } from './query/mutation'
75
+ // Query-client plugins (§13.2 / §13.3)
74
76
  export type {
75
77
  GcEvent,
76
78
  InvalidateEvent,
79
+ MutationEnqueueEvent,
80
+ MutationSettleEvent,
77
81
  QueryClientPlugin,
78
82
  QueryClientPluginApi,
83
+ RegisteredMutation,
79
84
  RegisteredQuery,
80
85
  SetDataEvent,
81
86
  } from './query/plugin'
82
- export { lookupRegisteredQuery } from './query/plugin'
87
+ export {
88
+ _unregisterMutationById,
89
+ lookupRegisteredMutation,
90
+ lookupRegisteredQuery,
91
+ } from './query/plugin'
83
92
  // Query primitives
84
93
  export type {
85
94
  AsyncState,
@@ -520,6 +520,42 @@ export class QueryClient {
520
520
  }
521
521
  }
522
522
 
523
+ /**
524
+ * Fan out a `MutationEnqueueEvent` to every installed plugin. Called from
525
+ * `MutationImpl.executeRun` when `spec.persist === true`. Plugins use this
526
+ * to write the run to durable storage; the queue replays on reload. SPEC §13.3.
527
+ */
528
+ emitMutationEnqueue(event: {
529
+ mutationId: string
530
+ runId: string
531
+ variables: unknown
532
+ attempt: number
533
+ }): void {
534
+ if (this.plugins.length === 0) return
535
+ for (const plugin of this.plugins) {
536
+ if (plugin.onMutationEnqueue) {
537
+ const cb = plugin.onMutationEnqueue
538
+ this.callPlugin(() => cb.call(plugin, event))
539
+ }
540
+ }
541
+ }
542
+
543
+ /** Fan out a `MutationSettleEvent` to every installed plugin. SPEC §13.3. */
544
+ emitMutationSettle(event: {
545
+ mutationId: string
546
+ runId: string
547
+ outcome: 'success' | 'error' | 'cancelled'
548
+ error?: unknown
549
+ }): void {
550
+ if (this.plugins.length === 0) return
551
+ for (const plugin of this.plugins) {
552
+ if (plugin.onMutationSettle) {
553
+ const cb = plugin.onMutationSettle
554
+ this.callPlugin(() => cb.call(plugin, event))
555
+ }
556
+ }
557
+ }
558
+
523
559
  /** Resolve `queryId → live entry-map keys`. Empty array when unknown. */
524
560
  private subscribedKeysFor(queryId: string): readonly (readonly unknown[])[] {
525
561
  // Defer the registry lookup to avoid an eager circular import — `define.ts`
@@ -83,6 +83,7 @@ class LocalCacheImpl<T> implements LocalCache<T> {
83
83
  refetch = (): Promise<T> => this.entry.refetch()
84
84
  reset = (): void => this.entry.reset()
85
85
  firstValue = (): Promise<T> => this.entry.firstValue()
86
+ promise = (): Promise<T> => this.entry.firstValue()
86
87
  invalidate = (): void => {
87
88
  this.entry.invalidate().catch(() => {})
88
89
  }
@@ -3,6 +3,7 @@ import { dispatchError, type ErrorHandler } from '../errors'
3
3
  import { batch, type Signal, signal } from '../signals'
4
4
  import type { ReadSignal } from '../signals/types'
5
5
  import { abortableSleep, isAbortError } from '../utils'
6
+ import { registerMutationById } from './plugin'
6
7
  import type { RetryDelay, RetryPolicy, Snapshot } from './types'
7
8
 
8
9
  /**
@@ -40,6 +41,83 @@ export type MutationSpec<V, R> = {
40
41
  concurrency?: MutationConcurrency
41
42
  retry?: RetryPolicy
42
43
  retryDelay?: RetryDelay
44
+ /**
45
+ * Stable identifier used by the mutation-queue plugin
46
+ * (`@kontsedal/olas-mutation-queue`) to route persistable runs across a
47
+ * page reload. REQUIRED when `persist: true`. Recommended even without
48
+ * `persist` if you want devtools to group runs across mutation instances
49
+ * — same shape as `defineQuery({ queryId })`.
50
+ *
51
+ * Don't auto-derive from `name` or function identity; both are fragile
52
+ * under minification.
53
+ */
54
+ mutationId?: string
55
+ /**
56
+ * Opt this mutation into durable persistence. When `true`, the runner
57
+ * emits `onMutationEnqueue` to plugins before the user's `mutate` runs
58
+ * and `onMutationSettle` after retries exhaust. Requires `mutationId`.
59
+ * SPEC §13.3.
60
+ */
61
+ persist?: boolean
62
+ }
63
+
64
+ /**
65
+ * Module-scope handle for a persistable mutation. Returned by
66
+ * `defineMutation(...)`. Pass it to `ctx.mutation(...)` (spread or as-is)
67
+ * so per-controller lifecycle hooks (`onSuccess` / `onError` / ...) can be
68
+ * layered on top.
69
+ *
70
+ * Registering at module import time means the mutation-queue plugin can
71
+ * replay pending runs from durable storage during `init` — before any
72
+ * controller reconstructs.
73
+ */
74
+ export type MutationDef<V, R> = MutationSpec<V, R> & {
75
+ readonly __olas: 'mutation'
76
+ readonly mutationId: string
77
+ }
78
+
79
+ /**
80
+ * Register a persistable mutation at module scope. Returns the spec
81
+ * unchanged (with a `__olas: 'mutation'` brand) so consumers can pass it
82
+ * to `ctx.mutation(...)`, optionally spreading per-controller hooks on
83
+ * top:
84
+ *
85
+ * ```ts
86
+ * // module-scope
87
+ * export const createOrder = defineMutation({
88
+ * mutationId: 'order/create',
89
+ * mutate: async (vars: OrderInput, { signal }) => api.createOrder(vars, { signal }),
90
+ * })
91
+ *
92
+ * // controller
93
+ * const m = ctx.mutation({
94
+ * ...createOrder,
95
+ * onSuccess: () => toast('Order placed'),
96
+ * })
97
+ * ```
98
+ *
99
+ * The `mutate` function MUST NOT close over controller-instance state — on
100
+ * replay there is no controller. Module-level dependencies (a shared `api`
101
+ * client, etc.) are fine.
102
+ */
103
+ export function defineMutation<V, R>(
104
+ spec: MutationSpec<V, R> & { mutationId: string; persist?: boolean },
105
+ ): MutationDef<V, R> {
106
+ if (typeof spec.mutationId !== 'string' || spec.mutationId.length === 0) {
107
+ throw new Error('[olas] defineMutation requires a non-empty `mutationId`.')
108
+ }
109
+ // Default `persist: true` for defined mutations — that's the whole point
110
+ // of using the module-scope helper. Consumers who want a non-persistable
111
+ // module-scope handle can override with `persist: false`.
112
+ const persistSpec: MutationSpec<V, R> = { ...spec, persist: spec.persist ?? true }
113
+ registerMutationById(spec.mutationId, {
114
+ mutationId: spec.mutationId,
115
+ mutate: spec.mutate as (vars: unknown, signal: AbortSignal) => Promise<unknown>,
116
+ })
117
+ return Object.assign(persistSpec, {
118
+ __olas: 'mutation' as const,
119
+ mutationId: spec.mutationId,
120
+ })
43
121
  }
44
122
 
45
123
  /**
@@ -88,6 +166,26 @@ type SerialEntry<V, R> = {
88
166
  reject: (err: unknown) => void
89
167
  }
90
168
 
169
+ /**
170
+ * Hooks for emitting persistable-mutation lifecycle events back to the
171
+ * `QueryClient`. Wired from `createMutation` when `spec.persist === true`.
172
+ * Internal — not part of any public surface.
173
+ */
174
+ export type MutationLifecycleHooks = {
175
+ emitEnqueue(event: {
176
+ mutationId: string
177
+ runId: string
178
+ variables: unknown
179
+ attempt: number
180
+ }): void
181
+ emitSettle(event: {
182
+ mutationId: string
183
+ runId: string
184
+ outcome: 'success' | 'error' | 'cancelled'
185
+ error?: unknown
186
+ }): void
187
+ }
188
+
91
189
  class MutationImpl<V, R> implements Mutation<V, R> {
92
190
  readonly data: Signal<R | undefined> = signal(undefined)
93
191
  readonly error: Signal<unknown | undefined> = signal(undefined)
@@ -107,8 +205,18 @@ class MutationImpl<V, R> implements Mutation<V, R> {
107
205
  update(fn: (n: number) => number): void
108
206
  },
109
207
  private readonly devtools?: DevtoolsEmitter,
208
+ private readonly lifecycle?: MutationLifecycleHooks,
110
209
  ) {}
111
210
 
211
+ /**
212
+ * True iff this mutation should emit persistable-lifecycle events.
213
+ * Validated at construction time (in `createMutation`) so any malformed
214
+ * `persist: true`-without-`mutationId` config surfaces early.
215
+ */
216
+ private get isPersistable(): boolean {
217
+ return this.spec.persist === true && this.lifecycle !== undefined
218
+ }
219
+
112
220
  private emit(event: { type: 'mutation:run'; vars: unknown }): void
113
221
  private emit(event: { type: 'mutation:success'; result: unknown }): void
114
222
  private emit(event: { type: 'mutation:error'; error: unknown }): void
@@ -204,10 +312,30 @@ class MutationImpl<V, R> implements Mutation<V, R> {
204
312
 
205
313
  if (__DEV__) this.emit({ type: 'mutation:run', vars })
206
314
 
315
+ // Persistable mutations emit an enqueue event BEFORE the user's `mutate`
316
+ // runs. If the page reloads mid-mutation, the queue plugin replays from
317
+ // this entry. `runId` is unique per `executeRun` invocation; retries
318
+ // within `runWithRetry` reuse it via `attempt` bumps inside that loop.
319
+ const runId = this.isPersistable ? makeRunId() : ''
320
+ const mutationId = this.spec.mutationId
321
+ if (this.isPersistable && mutationId !== undefined) {
322
+ try {
323
+ this.lifecycle?.emitEnqueue({ mutationId, runId, variables: vars, attempt: 0 })
324
+ } catch (err) {
325
+ dispatchError(this.onError, err, {
326
+ kind: 'plugin',
327
+ controllerPath: this.controllerPath,
328
+ })
329
+ }
330
+ }
331
+
207
332
  try {
208
333
  const result = await raceAbort(this.runWithRetry(vars, abort.signal), abort.signal)
209
334
  if (abort.signal.aborted || this.disposed) {
210
335
  snapshot?.rollback()
336
+ if (this.isPersistable && mutationId !== undefined) {
337
+ this.safeEmitSettle({ mutationId, runId, outcome: 'cancelled' })
338
+ }
211
339
  throw new DOMException('Superseded', 'AbortError')
212
340
  }
213
341
  batch(() => {
@@ -221,10 +349,16 @@ class MutationImpl<V, R> implements Mutation<V, R> {
221
349
  // Spec §6.4.
222
350
  snapshot?.finalize()
223
351
  this.safeCall(() => this.spec.onSettled?.(result, undefined, vars), 'mutation')
352
+ if (this.isPersistable && mutationId !== undefined) {
353
+ this.safeEmitSettle({ mutationId, runId, outcome: 'success' })
354
+ }
224
355
  return result
225
356
  } catch (err) {
226
357
  if (isAbortError(err) || abort.signal.aborted) {
227
358
  snapshot?.rollback()
359
+ if (this.isPersistable && mutationId !== undefined) {
360
+ this.safeEmitSettle({ mutationId, runId, outcome: 'cancelled' })
361
+ }
228
362
  // Reserve `error` signal for genuine failures.
229
363
  throw err
230
364
  }
@@ -236,6 +370,9 @@ class MutationImpl<V, R> implements Mutation<V, R> {
236
370
  // turns the auto-call into a no-op. Spec §6.4.
237
371
  snapshot?.rollback()
238
372
  this.safeCall(() => this.spec.onSettled?.(undefined, err, vars), 'mutation')
373
+ if (this.isPersistable && mutationId !== undefined) {
374
+ this.safeEmitSettle({ mutationId, runId, outcome: 'error', error: err })
375
+ }
239
376
  throw err
240
377
  } finally {
241
378
  this.inflight.delete(handle)
@@ -246,6 +383,22 @@ class MutationImpl<V, R> implements Mutation<V, R> {
246
383
  }
247
384
  }
248
385
 
386
+ private safeEmitSettle(event: {
387
+ mutationId: string
388
+ runId: string
389
+ outcome: 'success' | 'error' | 'cancelled'
390
+ error?: unknown
391
+ }): void {
392
+ try {
393
+ this.lifecycle?.emitSettle(event)
394
+ } catch (err) {
395
+ dispatchError(this.onError, err, {
396
+ kind: 'plugin',
397
+ controllerPath: this.controllerPath,
398
+ })
399
+ }
400
+ }
401
+
249
402
  // Wrap so any rollback / finalize path runs the raw operation at most
250
403
  // once. The mutation auto-finalizes on success and auto-rolls-back on
251
404
  // error; user code may also call rollback() from onError. Whichever
@@ -334,8 +487,32 @@ export function createMutation<V, R>(
334
487
  controllerPath: readonly string[],
335
488
  inflightCounter?: { update(fn: (n: number) => number): void },
336
489
  devtools?: DevtoolsEmitter,
490
+ lifecycle?: MutationLifecycleHooks,
337
491
  ): Mutation<V, R> {
338
- return new MutationImpl<V, R>(spec, onError, controllerPath, inflightCounter, devtools)
492
+ // Validate persistable-mutation config at construction time so misconfig
493
+ // surfaces synchronously rather than on first `run()`.
494
+ if (spec.persist === true) {
495
+ if (typeof spec.mutationId !== 'string' || spec.mutationId.length === 0) {
496
+ throw new Error(
497
+ '[olas] ctx.mutation({ persist: true, ... }) requires a non-empty `mutationId`.',
498
+ )
499
+ }
500
+ }
501
+ return new MutationImpl<V, R>(spec, onError, controllerPath, inflightCounter, devtools, lifecycle)
502
+ }
503
+
504
+ /**
505
+ * Generate a unique-enough run id for the persistable-mutation lifecycle.
506
+ * Uses `crypto.randomUUID` where available (Node 19+, modern browsers),
507
+ * with a timestamp+random fallback for older runtimes. Collisions only
508
+ * affect dedup at the plugin layer, not correctness, so the fallback's
509
+ * weakness is acceptable.
510
+ */
511
+ function makeRunId(): string {
512
+ const g = globalThis as { crypto?: { randomUUID?: () => string } }
513
+ if (typeof g.crypto?.randomUUID === 'function') return g.crypto.randomUUID()
514
+ const rand = Math.random().toString(36).slice(2, 12)
515
+ return `${Date.now().toString(36)}-${rand}`
339
516
  }
340
517
 
341
518
  /**