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

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
@@ -281,6 +281,11 @@ export interface ModelSourceChangedAction extends Action {
281
281
  * created with `sendModel`). An iStar extra; a mirror uses this to know the exact state.
282
282
  */
283
283
  readonly canRedo?: boolean;
284
+ /**
285
+ * The `origin` the causing `dispatch` was given, echoed back; absent for a change with none
286
+ * (a host's direct `store.load`, for instance). See `ActionDispatcher.dispatch`.
287
+ */
288
+ readonly origin?: string;
284
289
  }
285
290
  /** Saves the model through the host's `save` callback. */
286
291
  export interface SaveModelAction extends Action {
@@ -293,6 +298,8 @@ export interface SetDirtyStateAction extends Action {
293
298
  readonly kind: "setDirtyState";
294
299
  readonly isDirty: boolean;
295
300
  readonly reason?: DirtyStateChangeReason;
301
+ /** The `origin` of the dispatch that caused this dirty-state change, echoed back; see `ModelSourceChangedAction.origin`. */
302
+ readonly origin?: string;
296
303
  }
297
304
  export interface UndoAction extends Action {
298
305
  readonly kind: "glspUndo";
@@ -489,8 +496,31 @@ export type ActionListener<EK extends string = ElementKind, LK extends string =
489
496
  * Sends actions and hears what the other side sends back. Implemented in-process by
490
497
  * `createActionDispatcher`; a transport implements it over a message port.
491
498
  */
499
+ /** `dispatch`'s second argument. */
500
+ export interface DispatchOptions {
501
+ /**
502
+ * Echoed back as `origin` on every `ModelSourceChangedAction` and `SetDirtyStateAction` this
503
+ * dispatch causes (each change of a compound, both events of an undo or redo), so a listener
504
+ * can tell which surface or client made a change without tracking it itself. Carried through
505
+ * `createRemoteDispatcher`/`connectDispatcher` and by `createMirrorStore`'s dispatcher, so it
506
+ * survives a round trip to a remote host. Optional: a change with no origin (a host's direct
507
+ * `store.load`, for instance) has none.
508
+ */
509
+ readonly origin?: string;
510
+ }
492
511
  export interface ActionDispatcher<EK extends string = ElementKind, LK extends string = LinkKind> {
493
- dispatch<A extends IstarAction<EK, LK>>(action: A): Promise<ActionResult<ResponseFor<A>>>;
512
+ /**
513
+ * For a **local** dispatcher (`createActionDispatcher`), an operation, `glspUndo`/`glspRedo`,
514
+ * and the guards and postGuards around them, are applied to the store and every listener
515
+ * (`subscribe`, the store's own) is notified synchronously, before this call's promise is
516
+ * created to resolve — only `saveModel` awaits. Code that needs to see the result of an action
517
+ * before its own next statement (not just before `await dispatch(...)` resolves) can rely on
518
+ * this for a local dispatcher; it does **not** hold across a `createRemoteDispatcher`/
519
+ * `connectDispatcher` transport or a `createMirrorStore` mirror, where a change always arrives
520
+ * after a message round trip. Use `origin` ({@link DispatchOptions}) instead where that
521
+ * portability matters — it is carried through both.
522
+ */
523
+ dispatch<A extends IstarAction<EK, LK>>(action: A, options?: DispatchOptions): Promise<ActionResult<ResponseFor<A>>>;
494
524
  /**
495
525
  * Hears the actions the dispatcher sends: `ModelSourceChangedAction` after every model change,
496
526
  * `SetDirtyStateAction` when the dirty state flips, and the notifications dispatched to it.
@@ -724,6 +754,8 @@ export interface JsonRpcRequest {
724
754
  readonly id: JsonRpcId;
725
755
  readonly method: string;
726
756
  readonly params?: unknown;
757
+ /** The dispatch's `origin` (`DispatchOptions.origin`), carried to the server's `dispatch`. */
758
+ readonly origin?: string;
727
759
  }
728
760
  /** A subscriber event, sent with no `id`: it expects no response. */
729
761
  export interface JsonRpcNotification {
package/dist/index.d.ts CHANGED
@@ -281,6 +281,11 @@ export interface ModelSourceChangedAction extends Action {
281
281
  * created with `sendModel`). An iStar extra; a mirror uses this to know the exact state.
282
282
  */
283
283
  readonly canRedo?: boolean;
284
+ /**
285
+ * The `origin` the causing `dispatch` was given, echoed back; absent for a change with none
286
+ * (a host's direct `store.load`, for instance). See `ActionDispatcher.dispatch`.
287
+ */
288
+ readonly origin?: string;
284
289
  }
285
290
  /** Saves the model through the host's `save` callback. */
286
291
  export interface SaveModelAction extends Action {
@@ -293,6 +298,8 @@ export interface SetDirtyStateAction extends Action {
293
298
  readonly kind: "setDirtyState";
294
299
  readonly isDirty: boolean;
295
300
  readonly reason?: DirtyStateChangeReason;
301
+ /** The `origin` of the dispatch that caused this dirty-state change, echoed back; see `ModelSourceChangedAction.origin`. */
302
+ readonly origin?: string;
296
303
  }
297
304
  export interface UndoAction extends Action {
298
305
  readonly kind: "glspUndo";
@@ -489,8 +496,31 @@ export type ActionListener<EK extends string = ElementKind, LK extends string =
489
496
  * Sends actions and hears what the other side sends back. Implemented in-process by
490
497
  * `createActionDispatcher`; a transport implements it over a message port.
491
498
  */
499
+ /** `dispatch`'s second argument. */
500
+ export interface DispatchOptions {
501
+ /**
502
+ * Echoed back as `origin` on every `ModelSourceChangedAction` and `SetDirtyStateAction` this
503
+ * dispatch causes (each change of a compound, both events of an undo or redo), so a listener
504
+ * can tell which surface or client made a change without tracking it itself. Carried through
505
+ * `createRemoteDispatcher`/`connectDispatcher` and by `createMirrorStore`'s dispatcher, so it
506
+ * survives a round trip to a remote host. Optional: a change with no origin (a host's direct
507
+ * `store.load`, for instance) has none.
508
+ */
509
+ readonly origin?: string;
510
+ }
492
511
  export interface ActionDispatcher<EK extends string = ElementKind, LK extends string = LinkKind> {
493
- dispatch<A extends IstarAction<EK, LK>>(action: A): Promise<ActionResult<ResponseFor<A>>>;
512
+ /**
513
+ * For a **local** dispatcher (`createActionDispatcher`), an operation, `glspUndo`/`glspRedo`,
514
+ * and the guards and postGuards around them, are applied to the store and every listener
515
+ * (`subscribe`, the store's own) is notified synchronously, before this call's promise is
516
+ * created to resolve — only `saveModel` awaits. Code that needs to see the result of an action
517
+ * before its own next statement (not just before `await dispatch(...)` resolves) can rely on
518
+ * this for a local dispatcher; it does **not** hold across a `createRemoteDispatcher`/
519
+ * `connectDispatcher` transport or a `createMirrorStore` mirror, where a change always arrives
520
+ * after a message round trip. Use `origin` ({@link DispatchOptions}) instead where that
521
+ * portability matters — it is carried through both.
522
+ */
523
+ dispatch<A extends IstarAction<EK, LK>>(action: A, options?: DispatchOptions): Promise<ActionResult<ResponseFor<A>>>;
494
524
  /**
495
525
  * Hears the actions the dispatcher sends: `ModelSourceChangedAction` after every model change,
496
526
  * `SetDirtyStateAction` when the dirty state flips, and the notifications dispatched to it.
@@ -724,6 +754,8 @@ export interface JsonRpcRequest {
724
754
  readonly id: JsonRpcId;
725
755
  readonly method: string;
726
756
  readonly params?: unknown;
757
+ /** The dispatch's `origin` (`DispatchOptions.origin`), carried to the server's `dispatch`. */
758
+ readonly origin?: string;
727
759
  }
728
760
  /** A subscriber event, sent with no `id`: it expects no response. */
729
761
  export interface JsonRpcNotification {
package/dist/index.js CHANGED
@@ -315,17 +315,25 @@ function createActionDispatcher(store, options = {}) {
315
315
  const listeners = /* @__PURE__ */ new Set();
316
316
  let saved = s.getModel();
317
317
  let dirty = false;
318
+ /**
319
+ * The origin of the dispatch currently applying synchronously (set by `dispatch`, around the
320
+ * synchronous part of `run`): what the store's subscription below echoes on
321
+ * `sourceModelChanged`/`setDirtyState`. `undefined` for a change not caused by a dispatch (a
322
+ * host's direct `store.load`) or once `run`'s synchronous part has returned.
323
+ */
324
+ let currentOrigin;
318
325
  const emit = (action) => {
319
326
  for (const listener of listeners) listener(action);
320
327
  };
321
- const updateDirty = (reason) => {
328
+ const updateDirty = (reason, origin) => {
322
329
  const next = s.getModel() !== saved;
323
330
  if (next === dirty) return;
324
331
  dirty = next;
325
332
  emit({
326
333
  kind: "setDirtyState",
327
334
  isDirty: next,
328
- reason
335
+ reason,
336
+ ...origin === void 0 ? {} : { origin }
329
337
  });
330
338
  };
331
339
  const REASONS = {
@@ -345,9 +353,10 @@ function createActionDispatcher(store, options = {}) {
345
353
  ...options.sendModel ? {
346
354
  canUndo: s.canUndo(),
347
355
  canRedo: s.canRedo()
348
- } : {}
356
+ } : {},
357
+ ...currentOrigin === void 0 ? {} : { origin: currentOrigin }
349
358
  });
350
- updateDirty(REASONS[event.source]);
359
+ updateDirty(REASONS[event.source], currentOrigin);
351
360
  });
352
361
  const requireElement = (id) => s.getModel().elements.get(id) ?? reject(`unknown element "${id}"`, id, "unknown-element");
353
362
  const runGuards = (operation, warnings) => {
@@ -744,7 +753,7 @@ function createActionDispatcher(store, options = {}) {
744
753
  default: return;
745
754
  }
746
755
  };
747
- const run = async (action) => {
756
+ const run = async (action, origin) => {
748
757
  if (isOperation(action)) return runOperation(action);
749
758
  if (isRequestAction(action)) return {
750
759
  ok: true,
@@ -769,7 +778,7 @@ function createActionDispatcher(store, options = {}) {
769
778
  const model = s.getModel();
770
779
  await options.save(toPistarObject(model, { saveDate: /* @__PURE__ */ new Date() }), action);
771
780
  saved = model;
772
- updateDirty("save");
781
+ updateDirty("save", origin);
773
782
  return {
774
783
  ok: true,
775
784
  createdIds: []
@@ -789,9 +798,17 @@ function createActionDispatcher(store, options = {}) {
789
798
  }
790
799
  };
791
800
  return {
792
- async dispatch(action) {
801
+ async dispatch(action, dispatchOptions) {
802
+ const origin = dispatchOptions?.origin;
803
+ currentOrigin = origin;
804
+ let pending;
805
+ try {
806
+ pending = run(action, origin);
807
+ } finally {
808
+ currentOrigin = void 0;
809
+ }
793
810
  try {
794
- return await run(action);
811
+ return await pending;
795
812
  } catch (error) {
796
813
  if (error instanceof Rejection) return {
797
814
  ok: false,
@@ -1015,8 +1032,8 @@ function createMirrorStore(remote, options = {}) {
1015
1032
  return {
1016
1033
  ready,
1017
1034
  dispatcher: {
1018
- async dispatch(action) {
1019
- const result = await r.dispatch(action);
1035
+ async dispatch(action, options) {
1036
+ const result = await r.dispatch(action, options);
1020
1037
  await tail;
1021
1038
  return result;
1022
1039
  },
@@ -1102,8 +1119,9 @@ function connectDispatcher(port, dispatcher) {
1102
1119
  port.onmessage = (ev) => {
1103
1120
  const message = ev.data;
1104
1121
  if (!isRecord(message) || message.jsonrpc !== JSONRPC_VERSION) return;
1105
- const { id, method, params } = message;
1122
+ const { id, method, params, origin } = message;
1106
1123
  const hasId = isJsonRpcId(id);
1124
+ const dispatchOptions = typeof origin === "string" ? { origin } : void 0;
1107
1125
  if (typeof method !== "string" || method.length === 0) {
1108
1126
  if (hasId) port.postMessage(errorMessage(id, -32601, "method not found"));
1109
1127
  return;
@@ -1114,10 +1132,10 @@ function connectDispatcher(port, dispatcher) {
1114
1132
  }
1115
1133
  const action = params;
1116
1134
  if (!hasId) {
1117
- dispatcher.dispatch(action);
1135
+ dispatcher.dispatch(action, dispatchOptions);
1118
1136
  return;
1119
1137
  }
1120
- dispatcher.dispatch(action).then((result) => {
1138
+ dispatcher.dispatch(action, dispatchOptions).then((result) => {
1121
1139
  port.postMessage({
1122
1140
  jsonrpc: JSONRPC_VERSION,
1123
1141
  id,
@@ -1161,7 +1179,7 @@ function createRemoteDispatcher(port) {
1161
1179
  }
1162
1180
  };
1163
1181
  return {
1164
- dispatch(action) {
1182
+ dispatch(action, dispatchOptions) {
1165
1183
  const id = nextId++;
1166
1184
  return new Promise((resolve, reject) => {
1167
1185
  pending.set(id, {
@@ -1172,7 +1190,8 @@ function createRemoteDispatcher(port) {
1172
1190
  jsonrpc: JSONRPC_VERSION,
1173
1191
  id,
1174
1192
  method: action.kind,
1175
- params: action
1193
+ params: action,
1194
+ ...dispatchOptions?.origin === void 0 ? {} : { origin: dispatchOptions.origin }
1176
1195
  });
1177
1196
  });
1178
1197
  },