@almadar/std 3.14.1 → 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.
Files changed (43) hide show
  1. package/behaviors/exports/validation-report.json +2 -2
  2. package/dist/behaviors/exports/validation-report.json +2 -2
  3. package/dist/behaviors/exports-reader.js +71 -122
  4. package/dist/behaviors/exports-reader.js.map +1 -1
  5. package/dist/behaviors/functions/index.d.ts +10 -4
  6. package/dist/behaviors/functions/index.js +71 -122
  7. package/dist/behaviors/functions/index.js.map +1 -1
  8. package/dist/behaviors/index.js +71 -122
  9. package/dist/behaviors/index.js.map +1 -1
  10. package/dist/behaviors/query.js +71 -122
  11. package/dist/behaviors/query.js.map +1 -1
  12. package/dist/exports/validation-report.json +2 -2
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.js +176 -125
  15. package/dist/index.js.map +1 -1
  16. package/dist/modules/agent.d.ts +1 -1
  17. package/dist/modules/array.d.ts +1 -1
  18. package/dist/modules/async.d.ts +1 -1
  19. package/dist/modules/composition.d.ts +27 -0
  20. package/dist/modules/composition.js +98 -0
  21. package/dist/modules/composition.js.map +1 -0
  22. package/dist/modules/contract.d.ts +1 -1
  23. package/dist/modules/data.d.ts +1 -1
  24. package/dist/modules/format.d.ts +1 -1
  25. package/dist/modules/graph.d.ts +1 -1
  26. package/dist/modules/index.d.ts +2 -1
  27. package/dist/modules/index.js +96 -1
  28. package/dist/modules/index.js.map +1 -1
  29. package/dist/modules/math.d.ts +1 -1
  30. package/dist/modules/nn.d.ts +1 -1
  31. package/dist/modules/object.d.ts +1 -1
  32. package/dist/modules/os.d.ts +1 -1
  33. package/dist/modules/prob.d.ts +1 -1
  34. package/dist/modules/str.d.ts +1 -1
  35. package/dist/modules/tensor.d.ts +1 -1
  36. package/dist/modules/time.d.ts +1 -1
  37. package/dist/modules/train.d.ts +1 -1
  38. package/dist/modules/validate.d.ts +1 -1
  39. package/dist/registry.d.ts +1 -1
  40. package/dist/registry.js +98 -3
  41. package/dist/registry.js.map +1 -1
  42. package/dist/{types-BjP5nVQd.d.ts → types-BGtQuBge.d.ts} +5 -3
  43. package/package.json +1 -1
@@ -1715,10 +1715,16 @@ declare function stdGameCanvas3d(params: StdGameCanvas3dParams): OrbitalDefiniti
1715
1715
  * CRUD list molecule. Composes atoms via shared event bus:
1716
1716
  * - stdBrowse: data-grid with item actions (fires CREATE, VIEW, EDIT, DELETE)
1717
1717
  * - stdModal (x3): create form, edit form, detail view (responds to matching events)
1718
- * - stdConfirmation: delete confirmation (responds to DELETE)
1719
- *
1720
- * No emits/listens wiring. Traits on the same page share the event bus.
1721
- * Only the trait with a matching transition from its current state responds.
1718
+ * - stdConfirmation: delete confirmation (responds to DELETE → CONFIRM_DELETE)
1719
+ *
1720
+ * Phase F.10: previously the delete confirmation was inlined into the browse
1721
+ * trait via post-processing (added a `deleting` state, three new transitions,
1722
+ * and patched the events array). That made std-list un-liftable AND prevented
1723
+ * any organism from composing it as a clean molecule. The refactored version
1724
+ * has 5 separate traits, all from extractTrait, no post-processing — so the
1725
+ * converter lifts every trait into a reference and the molecule itself
1726
+ * becomes a first-class composable unit that organisms can extend with the
1727
+ * same override surface molecules use over atoms.
1722
1728
  *
1723
1729
  * @level molecule
1724
1730
  * @family crud
@@ -166,19 +166,14 @@ function buildTrait(c) {
166
166
  const actionEvents = /* @__PURE__ */ new Set();
167
167
  for (const a of c.headerActions) actionEvents.add(a.event);
168
168
  for (const a of c.itemActions) actionEvents.add(a.event);
169
- for (const re of c.refreshEvents) actionEvents.add(re);
170
169
  const events = [
171
170
  { key: "INIT", name: "Initialize" },
172
171
  ...Array.from(actionEvents).map((e) => {
173
172
  const needsId = c.itemActions.some((a) => a.event === e);
174
- const isRefresh = c.refreshEvents.includes(e);
175
- if (isRefresh) {
176
- return { key: e, name: e, payload: [{ name: "data", type: "object", required: true }] };
177
- }
178
173
  return needsId ? { key: e, name: e, payload: [{ name: "id", type: "string", required: true }, { name: "row", type: "object" }] } : { key: e, name: e };
179
174
  })
180
175
  ];
181
- const listensDecl = c.refreshEvents.length > 0 ? c.refreshEvents.map((evt) => ({ event: evt, triggers: evt })) : void 0;
176
+ const listensDecl = c.refreshEvents.length > 0 ? c.refreshEvents.map((evt) => ({ event: evt, triggers: "INIT" })) : void 0;
182
177
  return {
183
178
  name: c.traitName,
184
179
  linkedEntity: entityName,
@@ -215,14 +210,7 @@ function buildTrait(c) {
215
210
  ]
216
211
  }]
217
212
  ]
218
- },
219
- // Refresh self-loops: when modal atoms fire SAVE etc., re-fetch data
220
- ...c.refreshEvents.map((evt) => ({
221
- from: "browsing",
222
- to: "browsing",
223
- event: evt,
224
- effects: [["ref", entityName]]
225
- }))
213
+ }
226
214
  ]
227
215
  }
228
216
  };
@@ -280,6 +268,8 @@ function resolve2(params) {
280
268
  }
281
269
  ]
282
270
  };
271
+ const saveEvent = params.saveEvent ?? "SAVE";
272
+ const emitOnSave = params.emitOnSave ?? saveEvent;
283
273
  return {
284
274
  entityName,
285
275
  fields,
@@ -293,9 +283,9 @@ function resolve2(params) {
293
283
  openPayload: params.openPayload ?? [],
294
284
  closeEvent: params.closeEvent ?? "CLOSE",
295
285
  openEffects: params.openEffects ?? [],
296
- saveEvent: params.saveEvent ?? null,
286
+ saveEvent,
297
287
  saveEffects: params.saveEffects ?? [],
298
- emitOnSave: params.emitOnSave ?? null,
288
+ emitOnSave,
299
289
  standalone: params.standalone ?? true,
300
290
  pageName: params.pageName ?? `${entityName}ModalPage`,
301
291
  pagePath: params.pagePath ?? `/${p.toLowerCase()}/modal`,
@@ -309,11 +299,9 @@ function buildTrait2(c) {
309
299
  const events = [
310
300
  { key: "INIT", name: "Initialize" },
311
301
  { key: c.openEvent, name: "Open", ...c.openPayload.length > 0 ? { payload: c.openPayload } : {} },
312
- { key: c.closeEvent, name: "Close" }
302
+ { key: c.closeEvent, name: "Close" },
303
+ { key: c.saveEvent, name: "Save", payload: [{ name: "data", type: "object", required: true }] }
313
304
  ];
314
- if (c.saveEvent) {
315
- events.push({ key: c.saveEvent, name: "Save", payload: [{ name: "data", type: "object", required: true }] });
316
- }
317
305
  const transitions = [
318
306
  // INIT: closed → closed
319
307
  {
@@ -366,41 +354,44 @@ function buildTrait2(c) {
366
354
  }]] : []
367
355
  ] }
368
356
  ];
369
- if (c.saveEvent) {
370
- const mainRefresh = c.standalone ? [["ref", c.entityName], ["render-ui", "main", {
371
- type: "stack",
372
- direction: "vertical",
373
- gap: "lg",
374
- children: [
375
- { type: "stack", direction: "horizontal", gap: "md", justify: "space-between", children: [
376
- { type: "stack", direction: "horizontal", gap: "md", children: [
377
- { type: "icon", name: c.headerIcon, size: "lg" },
378
- { type: "typography", content: c.modalTitle, variant: "h2" }
379
- ] },
380
- { type: "button", label: "Open", event: c.openEvent, variant: "primary", icon: c.headerIcon }
357
+ const mainRefresh = c.standalone ? [["ref", c.entityName], ["render-ui", "main", {
358
+ type: "stack",
359
+ direction: "vertical",
360
+ gap: "lg",
361
+ children: [
362
+ { type: "stack", direction: "horizontal", gap: "md", justify: "space-between", children: [
363
+ { type: "stack", direction: "horizontal", gap: "md", children: [
364
+ { type: "icon", name: c.headerIcon, size: "lg" },
365
+ { type: "typography", content: c.modalTitle, variant: "h2" }
381
366
  ] },
382
- { type: "divider" },
383
- { type: "empty-state", icon: c.headerIcon, title: "Nothing open", description: "Click Open to view details in a modal overlay." }
384
- ]
385
- }]] : [];
386
- transitions.push({
387
- from: "open",
388
- to: "closed",
389
- event: c.saveEvent,
390
- effects: [
391
- ...c.saveEffects,
392
- ["render-ui", "modal", null],
393
- // Emit after persist succeeds so browse traits can fetch fresh data
394
- ...c.emitOnSave ? [["emit", c.emitOnSave]] : [],
395
- ...mainRefresh
396
- ]
397
- });
398
- }
367
+ { type: "button", label: "Open", event: c.openEvent, variant: "primary", icon: c.headerIcon }
368
+ ] },
369
+ { type: "divider" },
370
+ { type: "empty-state", icon: c.headerIcon, title: "Nothing open", description: "Click Open to view details in a modal overlay." }
371
+ ]
372
+ }]] : [];
373
+ transitions.push({
374
+ from: "open",
375
+ to: "closed",
376
+ event: c.saveEvent,
377
+ effects: [
378
+ ...c.saveEffects,
379
+ ["render-ui", "modal", null],
380
+ // Emit after persist succeeds so browse traits can fetch fresh data.
381
+ // Skip the emit when emitOnSave equals saveEvent — that's a self-emit
382
+ // that the runtime would short-circuit anyway, and avoids double
383
+ // dispatch on every save.
384
+ ...c.emitOnSave !== c.saveEvent ? [["emit", c.emitOnSave]] : [],
385
+ ...mainRefresh
386
+ ]
387
+ });
399
388
  return {
400
389
  name: c.traitName,
401
390
  linkedEntity: c.entityName,
402
391
  category: "interaction",
403
- ...c.emitOnSave ? { emits: [{ event: c.emitOnSave }] } : {},
392
+ // Phase F.10: emits[] is always populated (default emitOnSave = saveEvent).
393
+ // If a molecule supplies a distinct emitOnSave, declare both events.
394
+ emits: c.emitOnSave === c.saveEvent ? [{ event: c.saveEvent }] : [{ event: c.saveEvent }, { event: c.emitOnSave }],
404
395
  stateMachine: {
405
396
  states: [{ name: "closed", isInitial: true }, { name: "open" }],
406
397
  events,
@@ -429,6 +420,8 @@ function resolve3(params) {
429
420
  const fields = ensureIdField(params.fields);
430
421
  const nonIdFields = fields.filter((f) => f.name !== "id");
431
422
  const p = plural(entityName);
423
+ const confirmEvent = params.confirmEvent ?? "CONFIRM";
424
+ const emitOnConfirm = params.emitOnConfirm ?? confirmEvent;
432
425
  return {
433
426
  entityName,
434
427
  fields,
@@ -442,9 +435,9 @@ function resolve3(params) {
442
435
  cancelLabel: params.cancelLabel ?? "Cancel",
443
436
  headerIcon: params.headerIcon ?? "shield-check",
444
437
  requestEvent: params.requestEvent ?? "REQUEST",
445
- confirmEvent: params.confirmEvent ?? "CONFIRM",
438
+ confirmEvent,
446
439
  confirmEffects: params.confirmEffects ?? [],
447
- emitOnConfirm: params.emitOnConfirm ?? null,
440
+ emitOnConfirm,
448
441
  standalone: params.standalone ?? true,
449
442
  pageName: params.pageName ?? `${entityName}ConfirmPage`,
450
443
  pagePath: params.pagePath ?? `/${p.toLowerCase()}/confirm`,
@@ -524,7 +517,9 @@ function buildTrait3(c) {
524
517
  name: c.traitName,
525
518
  linkedEntity: entityName,
526
519
  category: "interaction",
527
- ...c.emitOnConfirm ? { emits: [{ event: c.emitOnConfirm }] } : {},
520
+ // Phase F.10: emits[] always populated. When emitOnConfirm equals
521
+ // confirmEvent, declare just the one. When they differ, declare both.
522
+ emits: c.emitOnConfirm === c.confirmEvent ? [{ event: c.confirmEvent }] : [{ event: c.confirmEvent }, { event: c.emitOnConfirm }],
528
523
  stateMachine: {
529
524
  states: [
530
525
  { name: "idle", isInitial: true },
@@ -561,7 +556,9 @@ function buildTrait3(c) {
561
556
  effects: [
562
557
  ...c.confirmEffects,
563
558
  ...dismissAndRefresh,
564
- ...c.emitOnConfirm ? [["emit", c.emitOnConfirm]] : []
559
+ // Skip self-emit when emitOnConfirm == confirmEvent (the runtime
560
+ // would short-circuit anyway).
561
+ ...c.emitOnConfirm !== c.confirmEvent ? [["emit", c.emitOnConfirm]] : []
565
562
  ]
566
563
  },
567
564
  {
@@ -8420,6 +8417,7 @@ function stdList(params) {
8420
8417
  const UPPER = entityName.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase();
8421
8418
  const CREATED = `${UPPER}_CREATED`;
8422
8419
  const UPDATED = `${UPPER}_UPDATED`;
8420
+ const DELETED = `${UPPER}_DELETED`;
8423
8421
  const browseTrait = extractTrait(stdBrowse({
8424
8422
  entityName,
8425
8423
  fields,
@@ -8440,7 +8438,7 @@ function stdList(params) {
8440
8438
  { label: "Edit", event: "EDIT" },
8441
8439
  { label: "Delete", event: "DELETE", variant: "danger" }
8442
8440
  ],
8443
- refreshEvents: [CREATED, UPDATED]
8441
+ refreshEvents: [CREATED, UPDATED, DELETED]
8444
8442
  }));
8445
8443
  const createTrait = extractTrait(stdModal({
8446
8444
  standalone: false,
@@ -8486,62 +8484,20 @@ function stdList(params) {
8486
8484
  closeEvent: "CLOSE",
8487
8485
  openEffects: [["fetch", entityName, { id: "@payload.id" }]]
8488
8486
  }));
8489
- const sm = browseTrait.stateMachine;
8490
- sm.states.push({ name: "deleting" });
8491
- const deleteEvent = sm.events.find((e) => e.key === "DELETE");
8492
- if (deleteEvent && !deleteEvent.payload) {
8493
- deleteEvent.payload = [{ name: "id", type: "string" }, { name: "row", type: "object" }];
8494
- }
8495
- const existingKeys = new Set(sm.events.map((e) => e.key));
8496
- if (!existingKeys.has("CONFIRM_DELETE")) sm.events.push({ key: "CONFIRM_DELETE", name: "Confirm Delete" });
8497
- if (!existingKeys.has("CANCEL")) sm.events.push({ key: "CANCEL", name: "Cancel" });
8498
- if (!existingKeys.has("CLOSE")) sm.events.push({ key: "CLOSE", name: "Close" });
8499
- const initTransition = sm.transitions[0];
8500
- const initRenderEffect = initTransition.effects.find(
8501
- (e) => Array.isArray(e) && e[0] === "render-ui" && e[1] === "main"
8502
- );
8503
- const browseMainView = initRenderEffect ? initRenderEffect[2] : null;
8504
- sm.transitions.push(
8505
- // DELETE: browsing → deleting (fetch entity by ID, show confirmation modal)
8506
- { from: "browsing", to: "deleting", event: "DELETE", effects: [
8507
- ["fetch", entityName, { id: "@payload.id" }],
8508
- ["render-ui", "modal", {
8509
- type: "stack",
8510
- direction: "vertical",
8511
- gap: "md",
8512
- children: [
8513
- { type: "stack", direction: "horizontal", gap: "sm", children: [
8514
- { type: "icon", name: "trash-2", size: "md" },
8515
- { type: "typography", content: `Delete ${entityName}`, variant: "h3" }
8516
- ] },
8517
- { type: "divider" },
8518
- { type: "typography", content: `@entity.${c.nonIdFields[0]?.name ?? "name"}`, variant: "h4" },
8519
- { type: "typography", content: c.deleteMessage, variant: "body" },
8520
- { type: "stack", direction: "horizontal", gap: "sm", justify: "end", children: [
8521
- { type: "button", label: "Cancel", event: "CANCEL", variant: "ghost" },
8522
- { type: "button", label: "Delete", event: "CONFIRM_DELETE", variant: "danger", icon: "trash" }
8523
- ] }
8524
- ]
8525
- }]
8526
- ] },
8527
- // CONFIRM_DELETE: deleting → browsing (persist delete, dismiss modal, re-render main)
8528
- { from: "deleting", to: "browsing", event: "CONFIRM_DELETE", effects: [
8529
- ["persist", "delete", entityName, "@entity.id"],
8530
- ["render-ui", "modal", null],
8531
- ["render-ui", "main", browseMainView]
8532
- ] },
8533
- // CANCEL/CLOSE from deleting (dismiss modal, re-render main)
8534
- { from: "deleting", to: "browsing", event: "CANCEL", effects: [
8535
- ["render-ui", "modal", null],
8536
- ["fetch", entityName],
8537
- ["render-ui", "main", browseMainView]
8538
- ] },
8539
- { from: "deleting", to: "browsing", event: "CLOSE", effects: [
8540
- ["render-ui", "modal", null],
8541
- ["fetch", entityName],
8542
- ["render-ui", "main", browseMainView]
8543
- ] }
8544
- );
8487
+ const deleteTrait = extractTrait(stdConfirmation({
8488
+ standalone: false,
8489
+ entityName,
8490
+ fields,
8491
+ traitName: `${entityName}Delete`,
8492
+ confirmTitle: `Delete ${entityName}`,
8493
+ confirmMessage: c.deleteMessage,
8494
+ confirmLabel: "Delete",
8495
+ headerIcon: "trash-2",
8496
+ requestEvent: "DELETE",
8497
+ confirmEvent: "CONFIRM_DELETE",
8498
+ confirmEffects: [["persist", "delete", entityName, "@entity.pendingId"]],
8499
+ emitOnConfirm: DELETED
8500
+ }));
8545
8501
  const entity = makeEntity({ name: entityName, fields, persistence: c.persistence, collection: c.collection });
8546
8502
  const page = {
8547
8503
  name: c.pageName,
@@ -8551,13 +8507,14 @@ function stdList(params) {
8551
8507
  { ref: browseTrait.name },
8552
8508
  { ref: createTrait.name },
8553
8509
  { ref: editTrait.name },
8554
- { ref: viewTrait.name }
8510
+ { ref: viewTrait.name },
8511
+ { ref: deleteTrait.name }
8555
8512
  ]
8556
8513
  };
8557
8514
  return {
8558
8515
  name: `${entityName}Orbital`,
8559
8516
  entity,
8560
- traits: [browseTrait, createTrait, editTrait, viewTrait],
8517
+ traits: [browseTrait, createTrait, editTrait, viewTrait, deleteTrait],
8561
8518
  pages: [page]
8562
8519
  };
8563
8520
  }
@@ -9045,17 +9002,9 @@ function stdInventory(params) {
9045
9002
  headerIcon: "trash-2",
9046
9003
  requestEvent: "DROP",
9047
9004
  confirmEvent: "CONFIRM_DROP",
9048
- confirmEffects: [["persist", "delete", entityName, "@payload.id"]],
9005
+ confirmEffects: [["persist", "delete", entityName, "@entity.pendingId"]],
9049
9006
  emitOnConfirm: "CONFIRM_DROP"
9050
9007
  }));
9051
- const dropSm = dropTrait.stateMachine;
9052
- if (dropSm && "events" in dropSm) {
9053
- const events = dropSm.events;
9054
- const confirmDropEvt = events.find((e) => e.key === "CONFIRM_DROP");
9055
- if (confirmDropEvt && !confirmDropEvt.payload) {
9056
- confirmDropEvt.payload = [{ name: "id", type: "string", required: true }];
9057
- }
9058
- }
9059
9008
  const instances = [
9060
9009
  { id: "item-1", name: "Health Potion", description: "Restores 50 HP", status: "active", pendingId: "" },
9061
9010
  { id: "item-2", name: "Iron Sword", description: "A sturdy blade", status: "active", pendingId: "" },