@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.
@@ -978,6 +978,25 @@ function registerQueryById(queryId, query) {
978
978
  function lookupRegisteredQuery(queryId) {
979
979
  return queryRegistry.get(queryId);
980
980
  }
981
+ const mutationRegistry = /* @__PURE__ */ new Map();
982
+ /** Register a mutation by its `mutationId`. Internal — called from `defineMutation`. */
983
+ function registerMutationById(mutationId, entry) {
984
+ mutationRegistry.set(mutationId, entry);
985
+ }
986
+ /**
987
+ * Look up a registered mutation by id. Returns `undefined` when no
988
+ * mutation with that id has been defined — typical when a queue entry
989
+ * references a mutation whose module hasn't been imported (e.g. a
990
+ * code-split route boundary). The plugin should leave such entries in
991
+ * place and retry once the module loads.
992
+ */
993
+ function lookupRegisteredMutation(mutationId) {
994
+ return mutationRegistry.get(mutationId);
995
+ }
996
+ /** Test-only — drop a registered mutation. Not exported from the package. */
997
+ function _unregisterMutationById(mutationId) {
998
+ mutationRegistry.delete(mutationId);
999
+ }
981
1000
  //#endregion
982
1001
  //#region src/query/client.ts
983
1002
  const DEFAULT_GC_TIME = 5 * 6e4;
@@ -1342,6 +1361,26 @@ var QueryClient = class {
1342
1361
  this.callPlugin(() => cb.call(plugin, event));
1343
1362
  }
1344
1363
  }
1364
+ /**
1365
+ * Fan out a `MutationEnqueueEvent` to every installed plugin. Called from
1366
+ * `MutationImpl.executeRun` when `spec.persist === true`. Plugins use this
1367
+ * to write the run to durable storage; the queue replays on reload. SPEC §13.3.
1368
+ */
1369
+ emitMutationEnqueue(event) {
1370
+ if (this.plugins.length === 0) return;
1371
+ for (const plugin of this.plugins) if (plugin.onMutationEnqueue) {
1372
+ const cb = plugin.onMutationEnqueue;
1373
+ this.callPlugin(() => cb.call(plugin, event));
1374
+ }
1375
+ }
1376
+ /** Fan out a `MutationSettleEvent` to every installed plugin. SPEC §13.3. */
1377
+ emitMutationSettle(event) {
1378
+ if (this.plugins.length === 0) return;
1379
+ for (const plugin of this.plugins) if (plugin.onMutationSettle) {
1380
+ const cb = plugin.onMutationSettle;
1381
+ this.callPlugin(() => cb.call(plugin, event));
1382
+ }
1383
+ }
1345
1384
  /** Resolve `queryId → live entry-map keys`. Empty array when unknown. */
1346
1385
  subscribedKeysFor(queryId) {
1347
1386
  const query = lookupRegisteredQuery(queryId);
@@ -2761,6 +2800,7 @@ var LocalCacheImpl = class {
2761
2800
  refetch = () => this.entry.refetch();
2762
2801
  reset = () => this.entry.reset();
2763
2802
  firstValue = () => this.entry.firstValue();
2803
+ promise = () => this.entry.firstValue();
2764
2804
  invalidate = () => {
2765
2805
  this.entry.invalidate().catch(() => {});
2766
2806
  };
@@ -2783,12 +2823,52 @@ function arraysEqual(a, b) {
2783
2823
  }
2784
2824
  //#endregion
2785
2825
  //#region src/query/mutation.ts
2826
+ /**
2827
+ * Register a persistable mutation at module scope. Returns the spec
2828
+ * unchanged (with a `__olas: 'mutation'` brand) so consumers can pass it
2829
+ * to `ctx.mutation(...)`, optionally spreading per-controller hooks on
2830
+ * top:
2831
+ *
2832
+ * ```ts
2833
+ * // module-scope
2834
+ * export const createOrder = defineMutation({
2835
+ * mutationId: 'order/create',
2836
+ * mutate: async (vars: OrderInput, { signal }) => api.createOrder(vars, { signal }),
2837
+ * })
2838
+ *
2839
+ * // controller
2840
+ * const m = ctx.mutation({
2841
+ * ...createOrder,
2842
+ * onSuccess: () => toast('Order placed'),
2843
+ * })
2844
+ * ```
2845
+ *
2846
+ * The `mutate` function MUST NOT close over controller-instance state — on
2847
+ * replay there is no controller. Module-level dependencies (a shared `api`
2848
+ * client, etc.) are fine.
2849
+ */
2850
+ function defineMutation(spec) {
2851
+ if (typeof spec.mutationId !== "string" || spec.mutationId.length === 0) throw new Error("[olas] defineMutation requires a non-empty `mutationId`.");
2852
+ const persistSpec = {
2853
+ ...spec,
2854
+ persist: spec.persist ?? true
2855
+ };
2856
+ registerMutationById(spec.mutationId, {
2857
+ mutationId: spec.mutationId,
2858
+ mutate: spec.mutate
2859
+ });
2860
+ return Object.assign(persistSpec, {
2861
+ __olas: "mutation",
2862
+ mutationId: spec.mutationId
2863
+ });
2864
+ }
2786
2865
  var MutationImpl = class {
2787
2866
  spec;
2788
2867
  onError;
2789
2868
  controllerPath;
2790
2869
  inflightCounter;
2791
2870
  devtools;
2871
+ lifecycle;
2792
2872
  data = signal(void 0);
2793
2873
  error = signal(void 0);
2794
2874
  isPending = signal(false);
@@ -2797,12 +2877,21 @@ var MutationImpl = class {
2797
2877
  serialQueue = [];
2798
2878
  serialActive = false;
2799
2879
  disposed = false;
2800
- constructor(spec, onError, controllerPath, inflightCounter, devtools) {
2880
+ constructor(spec, onError, controllerPath, inflightCounter, devtools, lifecycle) {
2801
2881
  this.spec = spec;
2802
2882
  this.onError = onError;
2803
2883
  this.controllerPath = controllerPath;
2804
2884
  this.inflightCounter = inflightCounter;
2805
2885
  this.devtools = devtools;
2886
+ this.lifecycle = lifecycle;
2887
+ }
2888
+ /**
2889
+ * True iff this mutation should emit persistable-lifecycle events.
2890
+ * Validated at construction time (in `createMutation`) so any malformed
2891
+ * `persist: true`-without-`mutationId` config surfaces early.
2892
+ */
2893
+ get isPersistable() {
2894
+ return this.spec.persist === true && this.lifecycle !== void 0;
2806
2895
  }
2807
2896
  emit(event) {}
2808
2897
  run = ((vars = void 0) => {
@@ -2866,10 +2955,30 @@ var MutationImpl = class {
2866
2955
  this.isPending.set(true);
2867
2956
  this.lastVariables.set(vars);
2868
2957
  });
2958
+ const runId = this.isPersistable ? makeRunId() : "";
2959
+ const mutationId = this.spec.mutationId;
2960
+ if (this.isPersistable && mutationId !== void 0) try {
2961
+ this.lifecycle?.emitEnqueue({
2962
+ mutationId,
2963
+ runId,
2964
+ variables: vars,
2965
+ attempt: 0
2966
+ });
2967
+ } catch (err) {
2968
+ dispatchError(this.onError, err, {
2969
+ kind: "plugin",
2970
+ controllerPath: this.controllerPath
2971
+ });
2972
+ }
2869
2973
  try {
2870
2974
  const result = await raceAbort(this.runWithRetry(vars, abort.signal), abort.signal);
2871
2975
  if (abort.signal.aborted || this.disposed) {
2872
2976
  snapshot?.rollback();
2977
+ if (this.isPersistable && mutationId !== void 0) this.safeEmitSettle({
2978
+ mutationId,
2979
+ runId,
2980
+ outcome: "cancelled"
2981
+ });
2873
2982
  throw new DOMException("Superseded", "AbortError");
2874
2983
  }
2875
2984
  batch(() => {
@@ -2879,16 +2988,32 @@ var MutationImpl = class {
2879
2988
  this.safeCall(() => this.spec.onSuccess?.(result, vars), "mutation");
2880
2989
  snapshot?.finalize();
2881
2990
  this.safeCall(() => this.spec.onSettled?.(result, void 0, vars), "mutation");
2991
+ if (this.isPersistable && mutationId !== void 0) this.safeEmitSettle({
2992
+ mutationId,
2993
+ runId,
2994
+ outcome: "success"
2995
+ });
2882
2996
  return result;
2883
2997
  } catch (err) {
2884
2998
  if (isAbortError(err) || abort.signal.aborted) {
2885
2999
  snapshot?.rollback();
3000
+ if (this.isPersistable && mutationId !== void 0) this.safeEmitSettle({
3001
+ mutationId,
3002
+ runId,
3003
+ outcome: "cancelled"
3004
+ });
2886
3005
  throw err;
2887
3006
  }
2888
3007
  this.error.set(err);
2889
3008
  this.safeCall(() => this.spec.onError?.(err, vars, snapshot), "mutation");
2890
3009
  snapshot?.rollback();
2891
3010
  this.safeCall(() => this.spec.onSettled?.(void 0, err, vars), "mutation");
3011
+ if (this.isPersistable && mutationId !== void 0) this.safeEmitSettle({
3012
+ mutationId,
3013
+ runId,
3014
+ outcome: "error",
3015
+ error: err
3016
+ });
2892
3017
  throw err;
2893
3018
  } finally {
2894
3019
  this.inflight.delete(handle);
@@ -2896,6 +3021,16 @@ var MutationImpl = class {
2896
3021
  if (this.inflight.size === 0) this.isPending.set(false);
2897
3022
  }
2898
3023
  }
3024
+ safeEmitSettle(event) {
3025
+ try {
3026
+ this.lifecycle?.emitSettle(event);
3027
+ } catch (err) {
3028
+ dispatchError(this.onError, err, {
3029
+ kind: "plugin",
3030
+ controllerPath: this.controllerPath
3031
+ });
3032
+ }
3033
+ }
2899
3034
  wrapSnapshot(raw) {
2900
3035
  let consumed = false;
2901
3036
  return {
@@ -2959,8 +3094,24 @@ var MutationImpl = class {
2959
3094
  this.serialQueue.length = 0;
2960
3095
  }
2961
3096
  };
2962
- function createMutation(spec, onError, controllerPath, inflightCounter, devtools) {
2963
- return new MutationImpl(spec, onError, controllerPath, inflightCounter, devtools);
3097
+ function createMutation(spec, onError, controllerPath, inflightCounter, devtools, lifecycle) {
3098
+ if (spec.persist === true) {
3099
+ if (typeof spec.mutationId !== "string" || spec.mutationId.length === 0) throw new Error("[olas] ctx.mutation({ persist: true, ... }) requires a non-empty `mutationId`.");
3100
+ }
3101
+ return new MutationImpl(spec, onError, controllerPath, inflightCounter, devtools, lifecycle);
3102
+ }
3103
+ /**
3104
+ * Generate a unique-enough run id for the persistable-mutation lifecycle.
3105
+ * Uses `crypto.randomUUID` where available (Node 19+, modern browsers),
3106
+ * with a timestamp+random fallback for older runtimes. Collisions only
3107
+ * affect dedup at the plugin layer, not correctness, so the fallback's
3108
+ * weakness is acceptable.
3109
+ */
3110
+ function makeRunId() {
3111
+ const g = globalThis;
3112
+ if (typeof g.crypto?.randomUUID === "function") return g.crypto.randomUUID();
3113
+ const rand = Math.random().toString(36).slice(2, 12);
3114
+ return `${Date.now().toString(36)}-${rand}`;
2964
3115
  }
2965
3116
  /**
2966
3117
  * Race a promise against an AbortSignal. If the signal fires before the
@@ -3056,6 +3207,7 @@ var SubscriptionImpl = class {
3056
3207
  if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
3057
3208
  return cur.entry.firstValue().then((v) => this.project(v));
3058
3209
  };
3210
+ promise = () => this.firstValue();
3059
3211
  project(v) {
3060
3212
  return this.select === void 0 ? v : this.select(v);
3061
3213
  }
@@ -3215,6 +3367,7 @@ var InfiniteSubscriptionImpl = class {
3215
3367
  if (!cur) return Promise.reject(/* @__PURE__ */ new Error("[olas] no active subscription"));
3216
3368
  return cur.entry.firstValue();
3217
3369
  };
3370
+ promise = () => this.firstValue();
3218
3371
  fetchNextPage = () => {
3219
3372
  const cur = this.current$.peek();
3220
3373
  if (!cur) return Promise.resolve();
@@ -3494,7 +3647,11 @@ var ControllerInstance = class ControllerInstance {
3494
3647
  return handle.subscription;
3495
3648
  },
3496
3649
  mutation(spec) {
3497
- const m = createMutation(spec, self.rootShared.onError, self.path, self.rootShared.queryClient.mutationsInflight$, self.rootShared.devtools);
3650
+ const queryClient = self.rootShared.queryClient;
3651
+ const m = createMutation(spec, self.rootShared.onError, self.path, queryClient.mutationsInflight$, self.rootShared.devtools, spec.persist === true ? {
3652
+ emitEnqueue: (ev) => queryClient.emitMutationEnqueue(ev),
3653
+ emitSettle: (ev) => queryClient.emitMutationSettle(ev)
3654
+ } : void 0);
3498
3655
  self.entries.push({
3499
3656
  kind: "cleanup",
3500
3657
  dispose: () => m.dispose()
@@ -4096,6 +4253,12 @@ function createRoot(def, options) {
4096
4253
  return createRootWithProps(def, void 0, options);
4097
4254
  }
4098
4255
  //#endregion
4256
+ Object.defineProperty(exports, "_unregisterMutationById", {
4257
+ enumerable: true,
4258
+ get: function() {
4259
+ return _unregisterMutationById;
4260
+ }
4261
+ });
4099
4262
  Object.defineProperty(exports, "batch", {
4100
4263
  enumerable: true,
4101
4264
  get: function() {
@@ -4138,6 +4301,12 @@ Object.defineProperty(exports, "defineController", {
4138
4301
  return defineController;
4139
4302
  }
4140
4303
  });
4304
+ Object.defineProperty(exports, "defineMutation", {
4305
+ enumerable: true,
4306
+ get: function() {
4307
+ return defineMutation;
4308
+ }
4309
+ });
4141
4310
  Object.defineProperty(exports, "effect", {
4142
4311
  enumerable: true,
4143
4312
  get: function() {
@@ -4150,6 +4319,12 @@ Object.defineProperty(exports, "isAbortError", {
4150
4319
  return isAbortError;
4151
4320
  }
4152
4321
  });
4322
+ Object.defineProperty(exports, "lookupRegisteredMutation", {
4323
+ enumerable: true,
4324
+ get: function() {
4325
+ return lookupRegisteredMutation;
4326
+ }
4327
+ });
4153
4328
  Object.defineProperty(exports, "lookupRegisteredQuery", {
4154
4329
  enumerable: true,
4155
4330
  get: function() {
@@ -4181,4 +4356,4 @@ Object.defineProperty(exports, "untracked", {
4181
4356
  }
4182
4357
  });
4183
4358
 
4184
- //# sourceMappingURL=root-D_xAdcom.cjs.map
4359
+ //# sourceMappingURL=root-lBp7qziQ.cjs.map