@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.cjs CHANGED
@@ -19,7 +19,9 @@ const OPERATION_KIND_LIST = [
19
19
  "istar.setLinkValue",
20
20
  "istar.updateLinkDisplay",
21
21
  "istar.changeLinkKind",
22
- "istar.setFileMetamodel"
22
+ "istar.setFileMetamodel",
23
+ "istar.updateModelExtra",
24
+ "istar.replaceModel"
23
25
  ];
24
26
  const REQUEST_KIND_LIST = [
25
27
  "requestModel",
@@ -276,6 +278,11 @@ function rejectCheck(check, endpoints) {
276
278
  function metaOf(model) {
277
279
  return (0, _istar_ts_core.metamodelOf)(model);
278
280
  }
281
+ /**
282
+ * Keys `istar.updateModelExtra`'s `set`/`remove` must not touch: every key core models itself
283
+ * (`TOP_LEVEL_KEYS`, which includes `tool`), and `"metamodel"` (`istar.setFileMetamodel`'s own).
284
+ */
285
+ const RESERVED_EXTRA_KEYS = /* @__PURE__ */ new Set([..._istar_ts_core.TOP_LEVEL_KEYS, _istar_ts_core.FILE_METAMODEL_KEY]);
279
286
  /** A model with `links` changed, as the core operations derive one. */
280
287
  function withLinks(model, links) {
281
288
  return (0, _istar_ts_core.inheritMetamodel)(model, (0, _istar_ts_core.inheritSourceLayout)(model, {
@@ -316,17 +323,25 @@ function createActionDispatcher(store, options = {}) {
316
323
  const listeners = /* @__PURE__ */ new Set();
317
324
  let saved = s.getModel();
318
325
  let dirty = false;
326
+ /**
327
+ * The origin of the dispatch currently applying synchronously (set by `dispatch`, around the
328
+ * synchronous part of `run`): what the store's subscription below echoes on
329
+ * `sourceModelChanged`/`setDirtyState`. `undefined` for a change not caused by a dispatch (a
330
+ * host's direct `store.load`) or once `run`'s synchronous part has returned.
331
+ */
332
+ let currentOrigin;
319
333
  const emit = (action) => {
320
334
  for (const listener of listeners) listener(action);
321
335
  };
322
- const updateDirty = (reason) => {
336
+ const updateDirty = (reason, origin) => {
323
337
  const next = s.getModel() !== saved;
324
338
  if (next === dirty) return;
325
339
  dirty = next;
326
340
  emit({
327
341
  kind: "setDirtyState",
328
342
  isDirty: next,
329
- reason
343
+ reason,
344
+ ...origin === void 0 ? {} : { origin }
330
345
  });
331
346
  };
332
347
  const REASONS = {
@@ -346,9 +361,10 @@ function createActionDispatcher(store, options = {}) {
346
361
  ...options.sendModel ? {
347
362
  canUndo: s.canUndo(),
348
363
  canRedo: s.canRedo()
349
- } : {}
364
+ } : {},
365
+ ...currentOrigin === void 0 ? {} : { origin: currentOrigin }
350
366
  });
351
- updateDirty(REASONS[event.source]);
367
+ updateDirty(REASONS[event.source], currentOrigin);
352
368
  });
353
369
  const requireElement = (id) => s.getModel().elements.get(id) ?? reject(`unknown element "${id}"`, id, "unknown-element");
354
370
  const runGuards = (operation, warnings) => {
@@ -573,6 +589,48 @@ function createActionDispatcher(store, options = {}) {
573
589
  s.replace(next);
574
590
  return;
575
591
  }
592
+ case "istar.updateModelExtra": {
593
+ const { set, remove, tool } = operation;
594
+ for (const key of [...Object.keys(set ?? {}), ...remove ?? []]) {
595
+ if (!RESERVED_EXTRA_KEYS.has(key)) continue;
596
+ if (key === "tool") reject(`"tool" is a modelled field: set it with this operation's own "tool"`, "", "reserved-key");
597
+ if (key === _istar_ts_core.FILE_METAMODEL_KEY) reject(`"${key}" is set with istar.setFileMetamodel, not updateModelExtra`, "", "reserved-key");
598
+ reject(`"${key}" is modelled by the file itself and cannot be an extra key`, "", "reserved-key");
599
+ }
600
+ let withTool = model;
601
+ if (tool === null) {
602
+ const { tool: _tool, ...rest } = withTool;
603
+ withTool = rest;
604
+ } else if (tool !== void 0) withTool = {
605
+ ...withTool,
606
+ tool
607
+ };
608
+ const extra = { ...withTool.extra };
609
+ if (set) for (const [key, value] of Object.entries(set)) extra[key] = value;
610
+ if (remove) for (const key of remove) delete extra[key];
611
+ const { extra: _extra, ...rest } = withTool;
612
+ const next = {
613
+ ...rest,
614
+ ...Object.keys(extra).length > 0 ? { extra } : {}
615
+ };
616
+ s.replace((0, _istar_ts_core.inheritMetamodel)(model, (0, _istar_ts_core.inheritSourceLayout)(model, next)));
617
+ return;
618
+ }
619
+ case "istar.replaceModel": {
620
+ const host = (0, _istar_ts_core.hostMetamodelOf)(model) ?? meta;
621
+ let next;
622
+ try {
623
+ next = (0, _istar_ts_core.parsePistar)(operation.model, {
624
+ metamodel: host,
625
+ fileMetamodel: operation.fileMetamodel ?? false
626
+ });
627
+ } catch (error) {
628
+ if (error instanceof _istar_ts_core.PistarParseError) reject(error.message, "", "parse-error");
629
+ throw error;
630
+ }
631
+ s.replace(next);
632
+ return;
633
+ }
576
634
  default: reject(`unknown operation "${operation.kind}"`, "", "unknown-action");
577
635
  }
578
636
  };
@@ -585,7 +643,8 @@ function createActionDispatcher(store, options = {}) {
585
643
  for (const [key, value] of Object.entries(properties)) if (value === null) delete next[key];
586
644
  else if (typeof value === "string") next[key] = value;
587
645
  else reject(`the value of "${key}" must be a string or null`, id, "invalid-value");
588
- const customProperties = Object.keys(next).length > 0 ? next : null;
646
+ if (Object.keys(next).length === 0 && owner.customProperties === void 0) return;
647
+ const customProperties = next;
589
648
  if (model.elements.has(id)) s.updateElement(id, { customProperties });
590
649
  else s.updateLink(id, { customProperties });
591
650
  };
@@ -745,7 +804,7 @@ function createActionDispatcher(store, options = {}) {
745
804
  default: return;
746
805
  }
747
806
  };
748
- const run = async (action) => {
807
+ const run = async (action, origin) => {
749
808
  if (isOperation(action)) return runOperation(action);
750
809
  if (isRequestAction(action)) return {
751
810
  ok: true,
@@ -770,7 +829,7 @@ function createActionDispatcher(store, options = {}) {
770
829
  const model = s.getModel();
771
830
  await options.save((0, _istar_ts_core.toPistarObject)(model, { saveDate: /* @__PURE__ */ new Date() }), action);
772
831
  saved = model;
773
- updateDirty("save");
832
+ updateDirty("save", origin);
774
833
  return {
775
834
  ok: true,
776
835
  createdIds: []
@@ -790,9 +849,17 @@ function createActionDispatcher(store, options = {}) {
790
849
  }
791
850
  };
792
851
  return {
793
- async dispatch(action) {
852
+ async dispatch(action, dispatchOptions) {
853
+ const origin = dispatchOptions?.origin;
854
+ currentOrigin = origin;
855
+ let pending;
856
+ try {
857
+ pending = run(action, origin);
858
+ } finally {
859
+ currentOrigin = void 0;
860
+ }
794
861
  try {
795
- return await run(action);
862
+ return await pending;
796
863
  } catch (error) {
797
864
  if (error instanceof Rejection) return {
798
865
  ok: false,
@@ -1016,8 +1083,8 @@ function createMirrorStore(remote, options = {}) {
1016
1083
  return {
1017
1084
  ready,
1018
1085
  dispatcher: {
1019
- async dispatch(action) {
1020
- const result = await r.dispatch(action);
1086
+ async dispatch(action, options) {
1087
+ const result = await r.dispatch(action, options);
1021
1088
  await tail;
1022
1089
  return result;
1023
1090
  },
@@ -1103,8 +1170,9 @@ function connectDispatcher(port, dispatcher) {
1103
1170
  port.onmessage = (ev) => {
1104
1171
  const message = ev.data;
1105
1172
  if (!isRecord(message) || message.jsonrpc !== JSONRPC_VERSION) return;
1106
- const { id, method, params } = message;
1173
+ const { id, method, params, origin } = message;
1107
1174
  const hasId = isJsonRpcId(id);
1175
+ const dispatchOptions = typeof origin === "string" ? { origin } : void 0;
1108
1176
  if (typeof method !== "string" || method.length === 0) {
1109
1177
  if (hasId) port.postMessage(errorMessage(id, -32601, "method not found"));
1110
1178
  return;
@@ -1115,10 +1183,10 @@ function connectDispatcher(port, dispatcher) {
1115
1183
  }
1116
1184
  const action = params;
1117
1185
  if (!hasId) {
1118
- dispatcher.dispatch(action);
1186
+ dispatcher.dispatch(action, dispatchOptions);
1119
1187
  return;
1120
1188
  }
1121
- dispatcher.dispatch(action).then((result) => {
1189
+ dispatcher.dispatch(action, dispatchOptions).then((result) => {
1122
1190
  port.postMessage({
1123
1191
  jsonrpc: JSONRPC_VERSION,
1124
1192
  id,
@@ -1162,7 +1230,7 @@ function createRemoteDispatcher(port) {
1162
1230
  }
1163
1231
  };
1164
1232
  return {
1165
- dispatch(action) {
1233
+ dispatch(action, dispatchOptions) {
1166
1234
  const id = nextId++;
1167
1235
  return new Promise((resolve, reject) => {
1168
1236
  pending.set(id, {
@@ -1173,7 +1241,8 @@ function createRemoteDispatcher(port) {
1173
1241
  jsonrpc: JSONRPC_VERSION,
1174
1242
  id,
1175
1243
  method: action.kind,
1176
- params: action
1244
+ params: action,
1245
+ ...dispatchOptions?.origin === void 0 ? {} : { origin: dispatchOptions.origin }
1177
1246
  });
1178
1247
  });
1179
1248
  },