@istar-ts/actions 0.15.0-beta.2 → 0.15.0-beta.4

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/dist/index.d.cts CHANGED
@@ -2,6 +2,10 @@ import { AnyIstarModel, AnyMetamodel, AnyPaletteDescription, CustomProperties, D
2
2
  //#region src/actions.d.ts
3
3
  /** GLSP's custom arguments: a flat map of primitives. */
4
4
  export type Args = Readonly<Record<string, string | number | boolean>>;
5
+ /** A value that survives a JSON round trip, for `istar.updateModelExtra`'s `set`. */
6
+ export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
7
+ readonly [key: string]: JsonValue;
8
+ };
5
9
  export interface Point {
6
10
  readonly x: number;
7
11
  readonly y: number;
@@ -243,8 +247,35 @@ export interface SetFileMetamodelOperation extends Operation {
243
247
  readonly kind: "istar.setFileMetamodel";
244
248
  readonly block: FileMetamodel | null;
245
249
  }
250
+ /**
251
+ * Sets, removes or reads back the model's own top-level keys that core keeps in `model.extra`
252
+ * (e.g. a dialect's `metamodelVersion`): `set`'s keys are written (other extra keys and their
253
+ * order are kept; a new key is appended where `toPistar` would place it), `remove`'s keys are
254
+ * deleted. `tool` sets (or, with `null`, removes) the model's own `tool` field in the same undo
255
+ * step — `set`/`remove` reject it, and every key core models itself (`actors`, `orphans`,
256
+ * `dependencies`, `links`, `display`, `istar`, `saveDate`, `diagram`), and `"metamodel"` (use
257
+ * `istar.setFileMetamodel` for that one). One undo step.
258
+ */
259
+ export interface UpdateModelExtraOperation extends Operation {
260
+ readonly kind: "istar.updateModelExtra";
261
+ readonly set?: Readonly<Record<string, JsonValue>>;
262
+ readonly remove?: readonly string[];
263
+ readonly tool?: string | null;
264
+ }
265
+ /**
266
+ * Replaces the whole model from a piStar save file, as one undo step: ids, order and extra keys
267
+ * come from `model`, so a parse → `toPistar` round trip is byte-identical to it. Parsed with
268
+ * `parsePistar` under the dispatcher's own metamodel (`fileMetamodel` as in `parsePistar`); a
269
+ * parse error rejects with a typed diagnostic, the model untouched. Pre-guards and postGuards
270
+ * run against this operation, the model before and the model after, like any other operation.
271
+ */
272
+ export interface ReplaceModelOperation extends Operation {
273
+ readonly kind: "istar.replaceModel";
274
+ readonly model: PistarFile;
275
+ readonly fileMetamodel?: boolean;
276
+ }
246
277
  /** Every operation, keyed by `kind`. */
247
- export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation;
278
+ export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation | UpdateModelExtraOperation | ReplaceModelOperation;
248
279
  export interface RequestModelAction extends RequestAction {
249
280
  readonly kind: "requestModel";
250
281
  readonly options?: Args;
@@ -281,6 +312,11 @@ export interface ModelSourceChangedAction extends Action {
281
312
  * created with `sendModel`). An iStar extra; a mirror uses this to know the exact state.
282
313
  */
283
314
  readonly canRedo?: boolean;
315
+ /**
316
+ * The `origin` the causing `dispatch` was given, echoed back; absent for a change with none
317
+ * (a host's direct `store.load`, for instance). See `ActionDispatcher.dispatch`.
318
+ */
319
+ readonly origin?: string;
284
320
  }
285
321
  /** Saves the model through the host's `save` callback. */
286
322
  export interface SaveModelAction extends Action {
@@ -293,6 +329,8 @@ export interface SetDirtyStateAction extends Action {
293
329
  readonly kind: "setDirtyState";
294
330
  readonly isDirty: boolean;
295
331
  readonly reason?: DirtyStateChangeReason;
332
+ /** The `origin` of the dispatch that caused this dirty-state change, echoed back; see `ModelSourceChangedAction.origin`. */
333
+ readonly origin?: string;
296
334
  }
297
335
  export interface UndoAction extends Action {
298
336
  readonly kind: "glspUndo";
@@ -489,8 +527,31 @@ export type ActionListener<EK extends string = ElementKind, LK extends string =
489
527
  * Sends actions and hears what the other side sends back. Implemented in-process by
490
528
  * `createActionDispatcher`; a transport implements it over a message port.
491
529
  */
530
+ /** `dispatch`'s second argument. */
531
+ export interface DispatchOptions {
532
+ /**
533
+ * Echoed back as `origin` on every `ModelSourceChangedAction` and `SetDirtyStateAction` this
534
+ * dispatch causes (each change of a compound, both events of an undo or redo), so a listener
535
+ * can tell which surface or client made a change without tracking it itself. Carried through
536
+ * `createRemoteDispatcher`/`connectDispatcher` and by `createMirrorStore`'s dispatcher, so it
537
+ * survives a round trip to a remote host. Optional: a change with no origin (a host's direct
538
+ * `store.load`, for instance) has none.
539
+ */
540
+ readonly origin?: string;
541
+ }
492
542
  export interface ActionDispatcher<EK extends string = ElementKind, LK extends string = LinkKind> {
493
- dispatch<A extends IstarAction<EK, LK>>(action: A): Promise<ActionResult<ResponseFor<A>>>;
543
+ /**
544
+ * For a **local** dispatcher (`createActionDispatcher`), an operation, `glspUndo`/`glspRedo`,
545
+ * and the guards and postGuards around them, are applied to the store and every listener
546
+ * (`subscribe`, the store's own) is notified synchronously, before this call's promise is
547
+ * created to resolve — only `saveModel` awaits. Code that needs to see the result of an action
548
+ * before its own next statement (not just before `await dispatch(...)` resolves) can rely on
549
+ * this for a local dispatcher; it does **not** hold across a `createRemoteDispatcher`/
550
+ * `connectDispatcher` transport or a `createMirrorStore` mirror, where a change always arrives
551
+ * after a message round trip. Use `origin` ({@link DispatchOptions}) instead where that
552
+ * portability matters — it is carried through both.
553
+ */
554
+ dispatch<A extends IstarAction<EK, LK>>(action: A, options?: DispatchOptions): Promise<ActionResult<ResponseFor<A>>>;
494
555
  /**
495
556
  * Hears the actions the dispatcher sends: `ModelSourceChangedAction` after every model change,
496
557
  * `SetDirtyStateAction` when the dirty state flips, and the notifications dispatched to it.
@@ -724,6 +785,8 @@ export interface JsonRpcRequest {
724
785
  readonly id: JsonRpcId;
725
786
  readonly method: string;
726
787
  readonly params?: unknown;
788
+ /** The dispatch's `origin` (`DispatchOptions.origin`), carried to the server's `dispatch`. */
789
+ readonly origin?: string;
727
790
  }
728
791
  /** A subscriber event, sent with no `id`: it expects no response. */
729
792
  export interface JsonRpcNotification {
package/dist/index.d.ts CHANGED
@@ -2,6 +2,10 @@ import { AnyIstarModel, AnyMetamodel, AnyPaletteDescription, CustomProperties, D
2
2
  //#region src/actions.d.ts
3
3
  /** GLSP's custom arguments: a flat map of primitives. */
4
4
  export type Args = Readonly<Record<string, string | number | boolean>>;
5
+ /** A value that survives a JSON round trip, for `istar.updateModelExtra`'s `set`. */
6
+ export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
7
+ readonly [key: string]: JsonValue;
8
+ };
5
9
  export interface Point {
6
10
  readonly x: number;
7
11
  readonly y: number;
@@ -243,8 +247,35 @@ export interface SetFileMetamodelOperation extends Operation {
243
247
  readonly kind: "istar.setFileMetamodel";
244
248
  readonly block: FileMetamodel | null;
245
249
  }
250
+ /**
251
+ * Sets, removes or reads back the model's own top-level keys that core keeps in `model.extra`
252
+ * (e.g. a dialect's `metamodelVersion`): `set`'s keys are written (other extra keys and their
253
+ * order are kept; a new key is appended where `toPistar` would place it), `remove`'s keys are
254
+ * deleted. `tool` sets (or, with `null`, removes) the model's own `tool` field in the same undo
255
+ * step — `set`/`remove` reject it, and every key core models itself (`actors`, `orphans`,
256
+ * `dependencies`, `links`, `display`, `istar`, `saveDate`, `diagram`), and `"metamodel"` (use
257
+ * `istar.setFileMetamodel` for that one). One undo step.
258
+ */
259
+ export interface UpdateModelExtraOperation extends Operation {
260
+ readonly kind: "istar.updateModelExtra";
261
+ readonly set?: Readonly<Record<string, JsonValue>>;
262
+ readonly remove?: readonly string[];
263
+ readonly tool?: string | null;
264
+ }
265
+ /**
266
+ * Replaces the whole model from a piStar save file, as one undo step: ids, order and extra keys
267
+ * come from `model`, so a parse → `toPistar` round trip is byte-identical to it. Parsed with
268
+ * `parsePistar` under the dispatcher's own metamodel (`fileMetamodel` as in `parsePistar`); a
269
+ * parse error rejects with a typed diagnostic, the model untouched. Pre-guards and postGuards
270
+ * run against this operation, the model before and the model after, like any other operation.
271
+ */
272
+ export interface ReplaceModelOperation extends Operation {
273
+ readonly kind: "istar.replaceModel";
274
+ readonly model: PistarFile;
275
+ readonly fileMetamodel?: boolean;
276
+ }
246
277
  /** Every operation, keyed by `kind`. */
247
- export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation;
278
+ export type IstarOperation<EK extends string = ElementKind, LK extends string = LinkKind> = CreateNodeOperation<EK> | CreateEdgeOperation<EK, LK> | DeleteElementOperation | ChangeBoundsOperation | ChangeContainerOperation | ReconnectEdgeOperation | ApplyLabelEditOperation | CompoundOperation<EK, LK> | AddLegOperation | RemoveLegOperation | SetCollapsedOperation | UpdateDiagramOperation | UpdatePropertiesOperation | UpdateDisplayOperation | SetLinkValueOperation | UpdateLinkDisplayOperation | ChangeLinkKindOperation<LK> | SetFileMetamodelOperation | UpdateModelExtraOperation | ReplaceModelOperation;
248
279
  export interface RequestModelAction extends RequestAction {
249
280
  readonly kind: "requestModel";
250
281
  readonly options?: Args;
@@ -281,6 +312,11 @@ export interface ModelSourceChangedAction extends Action {
281
312
  * created with `sendModel`). An iStar extra; a mirror uses this to know the exact state.
282
313
  */
283
314
  readonly canRedo?: boolean;
315
+ /**
316
+ * The `origin` the causing `dispatch` was given, echoed back; absent for a change with none
317
+ * (a host's direct `store.load`, for instance). See `ActionDispatcher.dispatch`.
318
+ */
319
+ readonly origin?: string;
284
320
  }
285
321
  /** Saves the model through the host's `save` callback. */
286
322
  export interface SaveModelAction extends Action {
@@ -293,6 +329,8 @@ export interface SetDirtyStateAction extends Action {
293
329
  readonly kind: "setDirtyState";
294
330
  readonly isDirty: boolean;
295
331
  readonly reason?: DirtyStateChangeReason;
332
+ /** The `origin` of the dispatch that caused this dirty-state change, echoed back; see `ModelSourceChangedAction.origin`. */
333
+ readonly origin?: string;
296
334
  }
297
335
  export interface UndoAction extends Action {
298
336
  readonly kind: "glspUndo";
@@ -489,8 +527,31 @@ export type ActionListener<EK extends string = ElementKind, LK extends string =
489
527
  * Sends actions and hears what the other side sends back. Implemented in-process by
490
528
  * `createActionDispatcher`; a transport implements it over a message port.
491
529
  */
530
+ /** `dispatch`'s second argument. */
531
+ export interface DispatchOptions {
532
+ /**
533
+ * Echoed back as `origin` on every `ModelSourceChangedAction` and `SetDirtyStateAction` this
534
+ * dispatch causes (each change of a compound, both events of an undo or redo), so a listener
535
+ * can tell which surface or client made a change without tracking it itself. Carried through
536
+ * `createRemoteDispatcher`/`connectDispatcher` and by `createMirrorStore`'s dispatcher, so it
537
+ * survives a round trip to a remote host. Optional: a change with no origin (a host's direct
538
+ * `store.load`, for instance) has none.
539
+ */
540
+ readonly origin?: string;
541
+ }
492
542
  export interface ActionDispatcher<EK extends string = ElementKind, LK extends string = LinkKind> {
493
- dispatch<A extends IstarAction<EK, LK>>(action: A): Promise<ActionResult<ResponseFor<A>>>;
543
+ /**
544
+ * For a **local** dispatcher (`createActionDispatcher`), an operation, `glspUndo`/`glspRedo`,
545
+ * and the guards and postGuards around them, are applied to the store and every listener
546
+ * (`subscribe`, the store's own) is notified synchronously, before this call's promise is
547
+ * created to resolve — only `saveModel` awaits. Code that needs to see the result of an action
548
+ * before its own next statement (not just before `await dispatch(...)` resolves) can rely on
549
+ * this for a local dispatcher; it does **not** hold across a `createRemoteDispatcher`/
550
+ * `connectDispatcher` transport or a `createMirrorStore` mirror, where a change always arrives
551
+ * after a message round trip. Use `origin` ({@link DispatchOptions}) instead where that
552
+ * portability matters — it is carried through both.
553
+ */
554
+ dispatch<A extends IstarAction<EK, LK>>(action: A, options?: DispatchOptions): Promise<ActionResult<ResponseFor<A>>>;
494
555
  /**
495
556
  * Hears the actions the dispatcher sends: `ModelSourceChangedAction` after every model change,
496
557
  * `SetDirtyStateAction` when the dirty state flips, and the notifications dispatched to it.
@@ -724,6 +785,8 @@ export interface JsonRpcRequest {
724
785
  readonly id: JsonRpcId;
725
786
  readonly method: string;
726
787
  readonly params?: unknown;
788
+ /** The dispatch's `origin` (`DispatchOptions.origin`), carried to the server's `dispatch`. */
789
+ readonly origin?: string;
727
790
  }
728
791
  /** A subscriber event, sent with no `id`: it expects no response. */
729
792
  export interface JsonRpcNotification {
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DIAGNOSTIC_SEVERITIES, FILE_METAMODEL_KEY, LINK_SELECTOR, MetamodelError, ModelOperationError, canLink, connectResultToDiagnostic, createEmptyModel, dependencyEnds, effectiveLinkKind, elementBehavesLike, inheritMetamodel, inheritSourceLayout, linkBehavesLike, metamodelOf, parsePistar, toPistarObject, validateModel, validateModelProperties, withFileMetamodel } from "@istar-ts/core";
1
+ import { DIAGNOSTIC_SEVERITIES, FILE_METAMODEL_KEY, LINK_SELECTOR, MetamodelError, ModelOperationError, PistarParseError, TOP_LEVEL_KEYS, canLink, connectResultToDiagnostic, createEmptyModel, dependencyEnds, effectiveLinkKind, elementBehavesLike, hostMetamodelOf, inheritMetamodel, inheritSourceLayout, linkBehavesLike, metamodelOf, parsePistar, toPistarObject, validateModel, validateModelProperties, withFileMetamodel } from "@istar-ts/core";
2
2
  //#region src/actions.ts
3
3
  const OPERATION_KIND_LIST = [
4
4
  "createNode",
@@ -18,7 +18,9 @@ const OPERATION_KIND_LIST = [
18
18
  "istar.setLinkValue",
19
19
  "istar.updateLinkDisplay",
20
20
  "istar.changeLinkKind",
21
- "istar.setFileMetamodel"
21
+ "istar.setFileMetamodel",
22
+ "istar.updateModelExtra",
23
+ "istar.replaceModel"
22
24
  ];
23
25
  const REQUEST_KIND_LIST = [
24
26
  "requestModel",
@@ -275,6 +277,11 @@ function rejectCheck(check, endpoints) {
275
277
  function metaOf(model) {
276
278
  return metamodelOf(model);
277
279
  }
280
+ /**
281
+ * Keys `istar.updateModelExtra`'s `set`/`remove` must not touch: every key core models itself
282
+ * (`TOP_LEVEL_KEYS`, which includes `tool`), and `"metamodel"` (`istar.setFileMetamodel`'s own).
283
+ */
284
+ const RESERVED_EXTRA_KEYS = /* @__PURE__ */ new Set([...TOP_LEVEL_KEYS, FILE_METAMODEL_KEY]);
278
285
  /** A model with `links` changed, as the core operations derive one. */
279
286
  function withLinks(model, links) {
280
287
  return inheritMetamodel(model, inheritSourceLayout(model, {
@@ -315,17 +322,25 @@ function createActionDispatcher(store, options = {}) {
315
322
  const listeners = /* @__PURE__ */ new Set();
316
323
  let saved = s.getModel();
317
324
  let dirty = false;
325
+ /**
326
+ * The origin of the dispatch currently applying synchronously (set by `dispatch`, around the
327
+ * synchronous part of `run`): what the store's subscription below echoes on
328
+ * `sourceModelChanged`/`setDirtyState`. `undefined` for a change not caused by a dispatch (a
329
+ * host's direct `store.load`) or once `run`'s synchronous part has returned.
330
+ */
331
+ let currentOrigin;
318
332
  const emit = (action) => {
319
333
  for (const listener of listeners) listener(action);
320
334
  };
321
- const updateDirty = (reason) => {
335
+ const updateDirty = (reason, origin) => {
322
336
  const next = s.getModel() !== saved;
323
337
  if (next === dirty) return;
324
338
  dirty = next;
325
339
  emit({
326
340
  kind: "setDirtyState",
327
341
  isDirty: next,
328
- reason
342
+ reason,
343
+ ...origin === void 0 ? {} : { origin }
329
344
  });
330
345
  };
331
346
  const REASONS = {
@@ -345,9 +360,10 @@ function createActionDispatcher(store, options = {}) {
345
360
  ...options.sendModel ? {
346
361
  canUndo: s.canUndo(),
347
362
  canRedo: s.canRedo()
348
- } : {}
363
+ } : {},
364
+ ...currentOrigin === void 0 ? {} : { origin: currentOrigin }
349
365
  });
350
- updateDirty(REASONS[event.source]);
366
+ updateDirty(REASONS[event.source], currentOrigin);
351
367
  });
352
368
  const requireElement = (id) => s.getModel().elements.get(id) ?? reject(`unknown element "${id}"`, id, "unknown-element");
353
369
  const runGuards = (operation, warnings) => {
@@ -572,6 +588,48 @@ function createActionDispatcher(store, options = {}) {
572
588
  s.replace(next);
573
589
  return;
574
590
  }
591
+ case "istar.updateModelExtra": {
592
+ const { set, remove, tool } = operation;
593
+ for (const key of [...Object.keys(set ?? {}), ...remove ?? []]) {
594
+ if (!RESERVED_EXTRA_KEYS.has(key)) continue;
595
+ if (key === "tool") reject(`"tool" is a modelled field: set it with this operation's own "tool"`, "", "reserved-key");
596
+ if (key === FILE_METAMODEL_KEY) reject(`"${key}" is set with istar.setFileMetamodel, not updateModelExtra`, "", "reserved-key");
597
+ reject(`"${key}" is modelled by the file itself and cannot be an extra key`, "", "reserved-key");
598
+ }
599
+ let withTool = model;
600
+ if (tool === null) {
601
+ const { tool: _tool, ...rest } = withTool;
602
+ withTool = rest;
603
+ } else if (tool !== void 0) withTool = {
604
+ ...withTool,
605
+ tool
606
+ };
607
+ const extra = { ...withTool.extra };
608
+ if (set) for (const [key, value] of Object.entries(set)) extra[key] = value;
609
+ if (remove) for (const key of remove) delete extra[key];
610
+ const { extra: _extra, ...rest } = withTool;
611
+ const next = {
612
+ ...rest,
613
+ ...Object.keys(extra).length > 0 ? { extra } : {}
614
+ };
615
+ s.replace(inheritMetamodel(model, inheritSourceLayout(model, next)));
616
+ return;
617
+ }
618
+ case "istar.replaceModel": {
619
+ const host = hostMetamodelOf(model) ?? meta;
620
+ let next;
621
+ try {
622
+ next = parsePistar(operation.model, {
623
+ metamodel: host,
624
+ fileMetamodel: operation.fileMetamodel ?? false
625
+ });
626
+ } catch (error) {
627
+ if (error instanceof PistarParseError) reject(error.message, "", "parse-error");
628
+ throw error;
629
+ }
630
+ s.replace(next);
631
+ return;
632
+ }
575
633
  default: reject(`unknown operation "${operation.kind}"`, "", "unknown-action");
576
634
  }
577
635
  };
@@ -584,7 +642,8 @@ function createActionDispatcher(store, options = {}) {
584
642
  for (const [key, value] of Object.entries(properties)) if (value === null) delete next[key];
585
643
  else if (typeof value === "string") next[key] = value;
586
644
  else reject(`the value of "${key}" must be a string or null`, id, "invalid-value");
587
- const customProperties = Object.keys(next).length > 0 ? next : null;
645
+ if (Object.keys(next).length === 0 && owner.customProperties === void 0) return;
646
+ const customProperties = next;
588
647
  if (model.elements.has(id)) s.updateElement(id, { customProperties });
589
648
  else s.updateLink(id, { customProperties });
590
649
  };
@@ -744,7 +803,7 @@ function createActionDispatcher(store, options = {}) {
744
803
  default: return;
745
804
  }
746
805
  };
747
- const run = async (action) => {
806
+ const run = async (action, origin) => {
748
807
  if (isOperation(action)) return runOperation(action);
749
808
  if (isRequestAction(action)) return {
750
809
  ok: true,
@@ -769,7 +828,7 @@ function createActionDispatcher(store, options = {}) {
769
828
  const model = s.getModel();
770
829
  await options.save(toPistarObject(model, { saveDate: /* @__PURE__ */ new Date() }), action);
771
830
  saved = model;
772
- updateDirty("save");
831
+ updateDirty("save", origin);
773
832
  return {
774
833
  ok: true,
775
834
  createdIds: []
@@ -789,9 +848,17 @@ function createActionDispatcher(store, options = {}) {
789
848
  }
790
849
  };
791
850
  return {
792
- async dispatch(action) {
851
+ async dispatch(action, dispatchOptions) {
852
+ const origin = dispatchOptions?.origin;
853
+ currentOrigin = origin;
854
+ let pending;
855
+ try {
856
+ pending = run(action, origin);
857
+ } finally {
858
+ currentOrigin = void 0;
859
+ }
793
860
  try {
794
- return await run(action);
861
+ return await pending;
795
862
  } catch (error) {
796
863
  if (error instanceof Rejection) return {
797
864
  ok: false,
@@ -1015,8 +1082,8 @@ function createMirrorStore(remote, options = {}) {
1015
1082
  return {
1016
1083
  ready,
1017
1084
  dispatcher: {
1018
- async dispatch(action) {
1019
- const result = await r.dispatch(action);
1085
+ async dispatch(action, options) {
1086
+ const result = await r.dispatch(action, options);
1020
1087
  await tail;
1021
1088
  return result;
1022
1089
  },
@@ -1102,8 +1169,9 @@ function connectDispatcher(port, dispatcher) {
1102
1169
  port.onmessage = (ev) => {
1103
1170
  const message = ev.data;
1104
1171
  if (!isRecord(message) || message.jsonrpc !== JSONRPC_VERSION) return;
1105
- const { id, method, params } = message;
1172
+ const { id, method, params, origin } = message;
1106
1173
  const hasId = isJsonRpcId(id);
1174
+ const dispatchOptions = typeof origin === "string" ? { origin } : void 0;
1107
1175
  if (typeof method !== "string" || method.length === 0) {
1108
1176
  if (hasId) port.postMessage(errorMessage(id, -32601, "method not found"));
1109
1177
  return;
@@ -1114,10 +1182,10 @@ function connectDispatcher(port, dispatcher) {
1114
1182
  }
1115
1183
  const action = params;
1116
1184
  if (!hasId) {
1117
- dispatcher.dispatch(action);
1185
+ dispatcher.dispatch(action, dispatchOptions);
1118
1186
  return;
1119
1187
  }
1120
- dispatcher.dispatch(action).then((result) => {
1188
+ dispatcher.dispatch(action, dispatchOptions).then((result) => {
1121
1189
  port.postMessage({
1122
1190
  jsonrpc: JSONRPC_VERSION,
1123
1191
  id,
@@ -1161,7 +1229,7 @@ function createRemoteDispatcher(port) {
1161
1229
  }
1162
1230
  };
1163
1231
  return {
1164
- dispatch(action) {
1232
+ dispatch(action, dispatchOptions) {
1165
1233
  const id = nextId++;
1166
1234
  return new Promise((resolve, reject) => {
1167
1235
  pending.set(id, {
@@ -1172,7 +1240,8 @@ function createRemoteDispatcher(port) {
1172
1240
  jsonrpc: JSONRPC_VERSION,
1173
1241
  id,
1174
1242
  method: action.kind,
1175
- params: action
1243
+ params: action,
1244
+ ...dispatchOptions?.origin === void 0 ? {} : { origin: dispatchOptions.origin }
1176
1245
  });
1177
1246
  });
1178
1247
  },