@almadar/std 4.0.0 → 5.0.0

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.js CHANGED
@@ -4785,19 +4785,14 @@ function buildTrait(c) {
4785
4785
  const actionEvents = /* @__PURE__ */ new Set();
4786
4786
  for (const a of c.headerActions) actionEvents.add(a.event);
4787
4787
  for (const a of c.itemActions) actionEvents.add(a.event);
4788
- for (const re of c.refreshEvents) actionEvents.add(re);
4789
4788
  const events = [
4790
4789
  { key: "INIT", name: "Initialize" },
4791
4790
  ...Array.from(actionEvents).map((e) => {
4792
4791
  const needsId = c.itemActions.some((a) => a.event === e);
4793
- const isRefresh = c.refreshEvents.includes(e);
4794
- if (isRefresh) {
4795
- return { key: e, name: e, payload: [{ name: "data", type: "object", required: true }] };
4796
- }
4797
4792
  return needsId ? { key: e, name: e, payload: [{ name: "id", type: "string", required: true }, { name: "row", type: "object" }] } : { key: e, name: e };
4798
4793
  })
4799
4794
  ];
4800
- const listensDecl = c.refreshEvents.length > 0 ? c.refreshEvents.map((evt) => ({ event: evt, triggers: evt })) : void 0;
4795
+ const listensDecl = c.refreshEvents.length > 0 ? c.refreshEvents.map((evt) => ({ event: evt, triggers: "INIT" })) : void 0;
4801
4796
  return {
4802
4797
  name: c.traitName,
4803
4798
  linkedEntity: entityName,
@@ -4834,14 +4829,7 @@ function buildTrait(c) {
4834
4829
  ]
4835
4830
  }]
4836
4831
  ]
4837
- },
4838
- // Refresh self-loops: when modal atoms fire SAVE etc., re-fetch data
4839
- ...c.refreshEvents.map((evt) => ({
4840
- from: "browsing",
4841
- to: "browsing",
4842
- event: evt,
4843
- effects: [["ref", entityName]]
4844
- }))
4832
+ }
4845
4833
  ]
4846
4834
  }
4847
4835
  };
@@ -4899,6 +4887,8 @@ function resolve2(params) {
4899
4887
  }
4900
4888
  ]
4901
4889
  };
4890
+ const saveEvent = params.saveEvent ?? "SAVE";
4891
+ const emitOnSave = params.emitOnSave ?? saveEvent;
4902
4892
  return {
4903
4893
  entityName,
4904
4894
  fields,
@@ -4912,9 +4902,9 @@ function resolve2(params) {
4912
4902
  openPayload: params.openPayload ?? [],
4913
4903
  closeEvent: params.closeEvent ?? "CLOSE",
4914
4904
  openEffects: params.openEffects ?? [],
4915
- saveEvent: params.saveEvent ?? null,
4905
+ saveEvent,
4916
4906
  saveEffects: params.saveEffects ?? [],
4917
- emitOnSave: params.emitOnSave ?? null,
4907
+ emitOnSave,
4918
4908
  standalone: params.standalone ?? true,
4919
4909
  pageName: params.pageName ?? `${entityName}ModalPage`,
4920
4910
  pagePath: params.pagePath ?? `/${p.toLowerCase()}/modal`,
@@ -4928,11 +4918,9 @@ function buildTrait2(c) {
4928
4918
  const events = [
4929
4919
  { key: "INIT", name: "Initialize" },
4930
4920
  { key: c.openEvent, name: "Open", ...c.openPayload.length > 0 ? { payload: c.openPayload } : {} },
4931
- { key: c.closeEvent, name: "Close" }
4921
+ { key: c.closeEvent, name: "Close" },
4922
+ { key: c.saveEvent, name: "Save", payload: [{ name: "data", type: "object", required: true }] }
4932
4923
  ];
4933
- if (c.saveEvent) {
4934
- events.push({ key: c.saveEvent, name: "Save", payload: [{ name: "data", type: "object", required: true }] });
4935
- }
4936
4924
  const transitions = [
4937
4925
  // INIT: closed → closed
4938
4926
  {
@@ -4985,41 +4973,44 @@ function buildTrait2(c) {
4985
4973
  }]] : []
4986
4974
  ] }
4987
4975
  ];
4988
- if (c.saveEvent) {
4989
- const mainRefresh = c.standalone ? [["ref", c.entityName], ["render-ui", "main", {
4990
- type: "stack",
4991
- direction: "vertical",
4992
- gap: "lg",
4993
- children: [
4994
- { type: "stack", direction: "horizontal", gap: "md", justify: "space-between", children: [
4995
- { type: "stack", direction: "horizontal", gap: "md", children: [
4996
- { type: "icon", name: c.headerIcon, size: "lg" },
4997
- { type: "typography", content: c.modalTitle, variant: "h2" }
4998
- ] },
4999
- { type: "button", label: "Open", event: c.openEvent, variant: "primary", icon: c.headerIcon }
4976
+ const mainRefresh = c.standalone ? [["ref", c.entityName], ["render-ui", "main", {
4977
+ type: "stack",
4978
+ direction: "vertical",
4979
+ gap: "lg",
4980
+ children: [
4981
+ { type: "stack", direction: "horizontal", gap: "md", justify: "space-between", children: [
4982
+ { type: "stack", direction: "horizontal", gap: "md", children: [
4983
+ { type: "icon", name: c.headerIcon, size: "lg" },
4984
+ { type: "typography", content: c.modalTitle, variant: "h2" }
5000
4985
  ] },
5001
- { type: "divider" },
5002
- { type: "empty-state", icon: c.headerIcon, title: "Nothing open", description: "Click Open to view details in a modal overlay." }
5003
- ]
5004
- }]] : [];
5005
- transitions.push({
5006
- from: "open",
5007
- to: "closed",
5008
- event: c.saveEvent,
5009
- effects: [
5010
- ...c.saveEffects,
5011
- ["render-ui", "modal", null],
5012
- // Emit after persist succeeds so browse traits can fetch fresh data
5013
- ...c.emitOnSave ? [["emit", c.emitOnSave]] : [],
5014
- ...mainRefresh
5015
- ]
5016
- });
5017
- }
4986
+ { type: "button", label: "Open", event: c.openEvent, variant: "primary", icon: c.headerIcon }
4987
+ ] },
4988
+ { type: "divider" },
4989
+ { type: "empty-state", icon: c.headerIcon, title: "Nothing open", description: "Click Open to view details in a modal overlay." }
4990
+ ]
4991
+ }]] : [];
4992
+ transitions.push({
4993
+ from: "open",
4994
+ to: "closed",
4995
+ event: c.saveEvent,
4996
+ effects: [
4997
+ ...c.saveEffects,
4998
+ ["render-ui", "modal", null],
4999
+ // Emit after persist succeeds so browse traits can fetch fresh data.
5000
+ // Skip the emit when emitOnSave equals saveEvent — that's a self-emit
5001
+ // that the runtime would short-circuit anyway, and avoids double
5002
+ // dispatch on every save.
5003
+ ...c.emitOnSave !== c.saveEvent ? [["emit", c.emitOnSave]] : [],
5004
+ ...mainRefresh
5005
+ ]
5006
+ });
5018
5007
  return {
5019
5008
  name: c.traitName,
5020
5009
  linkedEntity: c.entityName,
5021
5010
  category: "interaction",
5022
- ...c.emitOnSave ? { emits: [{ event: c.emitOnSave }] } : {},
5011
+ // Phase F.10: emits[] is always populated (default emitOnSave = saveEvent).
5012
+ // If a molecule supplies a distinct emitOnSave, declare both events.
5013
+ emits: c.emitOnSave === c.saveEvent ? [{ event: c.saveEvent }] : [{ event: c.saveEvent }, { event: c.emitOnSave }],
5023
5014
  stateMachine: {
5024
5015
  states: [{ name: "closed", isInitial: true }, { name: "open" }],
5025
5016
  events,
@@ -5048,6 +5039,8 @@ function resolve3(params) {
5048
5039
  const fields = ensureIdField(params.fields);
5049
5040
  const nonIdFields = fields.filter((f) => f.name !== "id");
5050
5041
  const p = plural(entityName);
5042
+ const confirmEvent = params.confirmEvent ?? "CONFIRM";
5043
+ const emitOnConfirm = params.emitOnConfirm ?? confirmEvent;
5051
5044
  return {
5052
5045
  entityName,
5053
5046
  fields,
@@ -5061,9 +5054,9 @@ function resolve3(params) {
5061
5054
  cancelLabel: params.cancelLabel ?? "Cancel",
5062
5055
  headerIcon: params.headerIcon ?? "shield-check",
5063
5056
  requestEvent: params.requestEvent ?? "REQUEST",
5064
- confirmEvent: params.confirmEvent ?? "CONFIRM",
5057
+ confirmEvent,
5065
5058
  confirmEffects: params.confirmEffects ?? [],
5066
- emitOnConfirm: params.emitOnConfirm ?? null,
5059
+ emitOnConfirm,
5067
5060
  standalone: params.standalone ?? true,
5068
5061
  pageName: params.pageName ?? `${entityName}ConfirmPage`,
5069
5062
  pagePath: params.pagePath ?? `/${p.toLowerCase()}/confirm`,
@@ -5143,7 +5136,9 @@ function buildTrait3(c) {
5143
5136
  name: c.traitName,
5144
5137
  linkedEntity: entityName,
5145
5138
  category: "interaction",
5146
- ...c.emitOnConfirm ? { emits: [{ event: c.emitOnConfirm }] } : {},
5139
+ // Phase F.10: emits[] always populated. When emitOnConfirm equals
5140
+ // confirmEvent, declare just the one. When they differ, declare both.
5141
+ emits: c.emitOnConfirm === c.confirmEvent ? [{ event: c.confirmEvent }] : [{ event: c.confirmEvent }, { event: c.emitOnConfirm }],
5147
5142
  stateMachine: {
5148
5143
  states: [
5149
5144
  { name: "idle", isInitial: true },
@@ -5180,7 +5175,9 @@ function buildTrait3(c) {
5180
5175
  effects: [
5181
5176
  ...c.confirmEffects,
5182
5177
  ...dismissAndRefresh,
5183
- ...c.emitOnConfirm ? [["emit", c.emitOnConfirm]] : []
5178
+ // Skip self-emit when emitOnConfirm == confirmEvent (the runtime
5179
+ // would short-circuit anyway).
5180
+ ...c.emitOnConfirm !== c.confirmEvent ? [["emit", c.emitOnConfirm]] : []
5184
5181
  ]
5185
5182
  },
5186
5183
  {
@@ -13039,6 +13036,7 @@ function stdList(params) {
13039
13036
  const UPPER = entityName.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
13040
13037
  const CREATED = `${UPPER}_CREATED`;
13041
13038
  const UPDATED = `${UPPER}_UPDATED`;
13039
+ const DELETED = `${UPPER}_DELETED`;
13042
13040
  const browseTrait = extractTrait(stdBrowse({
13043
13041
  entityName,
13044
13042
  fields,
@@ -13059,7 +13057,7 @@ function stdList(params) {
13059
13057
  { label: "Edit", event: "EDIT" },
13060
13058
  { label: "Delete", event: "DELETE", variant: "danger" }
13061
13059
  ],
13062
- refreshEvents: [CREATED, UPDATED]
13060
+ refreshEvents: [CREATED, UPDATED, DELETED]
13063
13061
  }));
13064
13062
  const createTrait = extractTrait(stdModal({
13065
13063
  standalone: false,
@@ -13105,62 +13103,20 @@ function stdList(params) {
13105
13103
  closeEvent: "CLOSE",
13106
13104
  openEffects: [["fetch", entityName, { id: "@payload.id" }]]
13107
13105
  }));
13108
- const sm = browseTrait.stateMachine;
13109
- sm.states.push({ name: "deleting" });
13110
- const deleteEvent = sm.events.find((e) => e.key === "DELETE");
13111
- if (deleteEvent && !deleteEvent.payload) {
13112
- deleteEvent.payload = [{ name: "id", type: "string" }, { name: "row", type: "object" }];
13113
- }
13114
- const existingKeys = new Set(sm.events.map((e) => e.key));
13115
- if (!existingKeys.has("CONFIRM_DELETE")) sm.events.push({ key: "CONFIRM_DELETE", name: "Confirm Delete" });
13116
- if (!existingKeys.has("CANCEL")) sm.events.push({ key: "CANCEL", name: "Cancel" });
13117
- if (!existingKeys.has("CLOSE")) sm.events.push({ key: "CLOSE", name: "Close" });
13118
- const initTransition = sm.transitions[0];
13119
- const initRenderEffect = initTransition.effects.find(
13120
- (e) => Array.isArray(e) && e[0] === "render-ui" && e[1] === "main"
13121
- );
13122
- const browseMainView = initRenderEffect ? initRenderEffect[2] : null;
13123
- sm.transitions.push(
13124
- // DELETE: browsing → deleting (fetch entity by ID, show confirmation modal)
13125
- { from: "browsing", to: "deleting", event: "DELETE", effects: [
13126
- ["fetch", entityName, { id: "@payload.id" }],
13127
- ["render-ui", "modal", {
13128
- type: "stack",
13129
- direction: "vertical",
13130
- gap: "md",
13131
- children: [
13132
- { type: "stack", direction: "horizontal", gap: "sm", children: [
13133
- { type: "icon", name: "trash-2", size: "md" },
13134
- { type: "typography", content: `Delete ${entityName}`, variant: "h3" }
13135
- ] },
13136
- { type: "divider" },
13137
- { type: "typography", content: `@entity.${c.nonIdFields[0]?.name ?? "name"}`, variant: "h4" },
13138
- { type: "typography", content: c.deleteMessage, variant: "body" },
13139
- { type: "stack", direction: "horizontal", gap: "sm", justify: "end", children: [
13140
- { type: "button", label: "Cancel", event: "CANCEL", variant: "ghost" },
13141
- { type: "button", label: "Delete", event: "CONFIRM_DELETE", variant: "danger", icon: "trash" }
13142
- ] }
13143
- ]
13144
- }]
13145
- ] },
13146
- // CONFIRM_DELETE: deleting → browsing (persist delete, dismiss modal, re-render main)
13147
- { from: "deleting", to: "browsing", event: "CONFIRM_DELETE", effects: [
13148
- ["persist", "delete", entityName, "@entity.id"],
13149
- ["render-ui", "modal", null],
13150
- ["render-ui", "main", browseMainView]
13151
- ] },
13152
- // CANCEL/CLOSE from deleting (dismiss modal, re-render main)
13153
- { from: "deleting", to: "browsing", event: "CANCEL", effects: [
13154
- ["render-ui", "modal", null],
13155
- ["fetch", entityName],
13156
- ["render-ui", "main", browseMainView]
13157
- ] },
13158
- { from: "deleting", to: "browsing", event: "CLOSE", effects: [
13159
- ["render-ui", "modal", null],
13160
- ["fetch", entityName],
13161
- ["render-ui", "main", browseMainView]
13162
- ] }
13163
- );
13106
+ const deleteTrait = extractTrait(stdConfirmation({
13107
+ standalone: false,
13108
+ entityName,
13109
+ fields,
13110
+ traitName: `${entityName}Delete`,
13111
+ confirmTitle: `Delete ${entityName}`,
13112
+ confirmMessage: c.deleteMessage,
13113
+ confirmLabel: "Delete",
13114
+ headerIcon: "trash-2",
13115
+ requestEvent: "DELETE",
13116
+ confirmEvent: "CONFIRM_DELETE",
13117
+ confirmEffects: [["persist", "delete", entityName, "@entity.pendingId"]],
13118
+ emitOnConfirm: DELETED
13119
+ }));
13164
13120
  const entity = makeEntity({ name: entityName, fields, persistence: c.persistence, collection: c.collection });
13165
13121
  const page = {
13166
13122
  name: c.pageName,
@@ -13170,13 +13126,14 @@ function stdList(params) {
13170
13126
  { ref: browseTrait.name },
13171
13127
  { ref: createTrait.name },
13172
13128
  { ref: editTrait.name },
13173
- { ref: viewTrait.name }
13129
+ { ref: viewTrait.name },
13130
+ { ref: deleteTrait.name }
13174
13131
  ]
13175
13132
  };
13176
13133
  return {
13177
13134
  name: `${entityName}Orbital`,
13178
13135
  entity,
13179
- traits: [browseTrait, createTrait, editTrait, viewTrait],
13136
+ traits: [browseTrait, createTrait, editTrait, viewTrait, deleteTrait],
13180
13137
  pages: [page]
13181
13138
  };
13182
13139
  }
@@ -13664,17 +13621,9 @@ function stdInventory(params) {
13664
13621
  headerIcon: "trash-2",
13665
13622
  requestEvent: "DROP",
13666
13623
  confirmEvent: "CONFIRM_DROP",
13667
- confirmEffects: [["persist", "delete", entityName, "@payload.id"]],
13624
+ confirmEffects: [["persist", "delete", entityName, "@entity.pendingId"]],
13668
13625
  emitOnConfirm: "CONFIRM_DROP"
13669
13626
  }));
13670
- const dropSm = dropTrait.stateMachine;
13671
- if (dropSm && "events" in dropSm) {
13672
- const events = dropSm.events;
13673
- const confirmDropEvt = events.find((e) => e.key === "CONFIRM_DROP");
13674
- if (confirmDropEvt && !confirmDropEvt.payload) {
13675
- confirmDropEvt.payload = [{ name: "id", type: "string", required: true }];
13676
- }
13677
- }
13678
13627
  const instances = [
13679
13628
  { id: "item-1", name: "Health Potion", description: "Restores 50 HP", status: "active", pendingId: "" },
13680
13629
  { id: "item-2", name: "Iron Sword", description: "A sturdy blade", status: "active", pendingId: "" },