@the-package-labs/idocs-core 0.4.0 → 0.5.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/browser.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  createIconResolver,
7
7
  normalizeRoot,
8
8
  route
9
- } from "./chunk-S7M3BRFI.js";
10
- import "./chunk-ITBBPZE6.js";
9
+ } from "./chunk-Q2NHZ6TV.js";
10
+ import "./chunk-ZYFFV7Y7.js";
11
11
  export {
12
12
  KNOWN_KEYS,
13
13
  RootSchema,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  didYouMean
3
- } from "./chunk-ITBBPZE6.js";
3
+ } from "./chunk-ZYFFV7Y7.js";
4
4
 
5
5
  // src/nesting.ts
6
6
  function nestedDiagrams(all, name) {
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  BUILTIN_EDGE_KINDS,
3
3
  BUILTIN_KINDS,
4
+ DEFAULT_ENTITY_WIDTH,
4
5
  didYouMean,
5
6
  letterKey
6
- } from "./chunk-ITBBPZE6.js";
7
+ } from "./chunk-ZYFFV7Y7.js";
7
8
 
8
9
  // src/icon-resolver.ts
9
10
  import { getIconData, iconToSVG } from "@iconify/utils";
@@ -215,10 +216,28 @@ var ChipSchema = z.union([
215
216
  z.string(),
216
217
  z.strictObject({ label: z.string(), sub: z.string().optional() })
217
218
  ]);
219
+ var FieldProps = z.strictObject({
220
+ type: z.string().optional().describe("Its type: `keyword`, `uuid`, `string[]`."),
221
+ key: z.boolean().optional().describe("Part of the primary key. Shown with a `#` in front."),
222
+ ref: z.string().optional().describe(
223
+ "The `node` or `node.field` this one points at, like a foreign key. Draws a relation between the two cards."
224
+ ),
225
+ note: z.string().optional().describe("Shown on hover and in the drawer.")
226
+ });
227
+ var FieldSchema = z.union([
228
+ z.string().min(1),
229
+ z.record(z.string().min(1), z.union([z.string(), z.null(), FieldProps])).refine((o) => Object.keys(o).length === 1, {
230
+ message: "One field per list item: `- name: type`"
231
+ })
232
+ ]);
218
233
  var NodeSchema = z.strictObject({
219
234
  title: z.string().optional().describe("Display name. Defaults to the node id."),
220
235
  sub: z.string().optional().describe("One line under the title, rendered in mono (ports, stack, version)."),
236
+ pill: z.string().optional().describe("Short label in the card header, like `TABLE` or `ES INDEX`."),
221
237
  lines: z.array(z.string()).optional().describe("Short body lines inside the card."),
238
+ fields: z.array(FieldSchema).optional().describe(
239
+ "Makes the card an entity: a table of fields, each `name: type`. `ref` points a field at another entity."
240
+ ),
222
241
  icon: z.string().optional().describe("`postgresql`, `logos:redis`, `lucide:server` or ./path/to.svg."),
223
242
  kind: z.string().optional().describe("Gives a default icon and colour family. See `kinds`."),
224
243
  family: FamilySchema.optional().describe("Accent colour family; overrides the kind default."),
@@ -354,10 +373,31 @@ var ScenarioSchema = ScenarioBase.refine(
354
373
  (s) => (s.phases?.length ?? 0) + (s.steps?.length ?? 0) > 0,
355
374
  { message: "A scenario needs `steps` or `phases`" }
356
375
  );
376
+ var DefaultsSchema = z.strictObject({
377
+ view: Id.optional().describe(
378
+ "View shown first. Defaults to the view of `scenario`, or else the first view."
379
+ ),
380
+ scenario: Id.optional().describe(
381
+ "Scenario selected first. Without it the diagram opens on its overview. It does not play until a reader presses play, unless `autoplay` is on."
382
+ ),
383
+ mode: z.enum(["flow", "sequence", "story"]).optional().describe(
384
+ "How a scenario is drawn when it has no `mode` of its own: packets over the diagram, a sequence diagram, or a swimlane story."
385
+ ),
386
+ sidebar: z.boolean().optional().describe(
387
+ "Whether the panel over the picture starts open. By default it does when there is a scenario or another view to choose, and the frame is wide enough."
388
+ ),
389
+ subtitles: z.boolean().optional().describe("Words over the picture saying what the scenario is doing. On by default."),
390
+ autoplay: z.boolean().optional().describe(
391
+ "Play `scenario` as soon as the diagram is on screen. Needs `scenario`, and is ignored when the system asks for reduced motion."
392
+ )
393
+ });
357
394
  var RootSchema = z.strictObject({
358
395
  $schema: z.string().optional(),
359
396
  title: z.string(),
360
397
  description: z.string().optional().describe("Markdown shown above the first view."),
398
+ defaults: DefaultsSchema.optional().describe(
399
+ "How the diagram opens: its view, scenario and mode, and whether the panel and subtitles show. A page can still choose otherwise with the props of `<Diagram>`."
400
+ ),
361
401
  kinds: z.record(Id, z.strictObject({ icon: z.string().optional(), family: FamilySchema.optional() })).optional(),
362
402
  edgeKinds: z.record(
363
403
  Id,
@@ -380,7 +420,8 @@ var KNOWN_KEYS = {
380
420
  group: Object.keys(GroupSchema.shape),
381
421
  edge: Object.keys(EdgeSchema.shape),
382
422
  view: Object.keys(ViewSchema.shape),
383
- scenario: Object.keys(ScenarioBase.shape)
423
+ scenario: Object.keys(ScenarioBase.shape),
424
+ defaults: Object.keys(DefaultsSchema.shape)
384
425
  };
385
426
  function authoringJsonSchema() {
386
427
  const schema = z.toJSONSchema(RootSchema, { io: "input", unrepresentable: "any" });
@@ -589,6 +630,7 @@ function build(data, src, error, warn, { resolveIcon }) {
589
630
  const fixParents = Object.values(groups);
590
631
  for (const g of fixParents) g.style = g.parent ? "frame" : "zone";
591
632
  const nodes = {};
633
+ const refTexts = [];
592
634
  for (const [id, n] of Object.entries(data.nodes)) {
593
635
  if (n.kind && !kinds[n.kind]) {
594
636
  warn(
@@ -617,11 +659,26 @@ function build(data, src, error, warn, { resolveIcon }) {
617
659
  if (n.doc && docText === void 0)
618
660
  warn(`Doc file "${n.doc}" was not found`, ["nodes", id, "doc"]);
619
661
  const docHtml = [n.description, docText].filter((t) => !!t).map(renderMarkdown).join("");
662
+ const fields = (n.fields ?? []).map(toField);
663
+ const seenFields = /* @__PURE__ */ new Set();
664
+ fields.forEach((f, i) => {
665
+ if (seenFields.has(f.name)) {
666
+ error(`Field "${f.name}" appears twice in "${id}"`, ["nodes", id, "fields", i]);
667
+ }
668
+ seenFields.add(f.name);
669
+ });
670
+ n.fields?.forEach((item, i) => {
671
+ const ref = typeof item === "object" && item ? Object.values(item)[0] : void 0;
672
+ if (ref && typeof ref === "object" && ref.ref !== void 0)
673
+ refTexts.push({ node: id, index: i, text: ref.ref });
674
+ });
620
675
  nodes[id] = {
621
676
  id,
622
677
  title: n.title ?? id,
623
678
  sub: n.sub,
679
+ pill: n.pill,
624
680
  lines: n.lines ?? [],
681
+ fields,
625
682
  icon: iconFor(n.icon ?? kind?.icon, ["nodes", id, n.icon ? "icon" : "kind"]),
626
683
  kind: n.kind,
627
684
  family: n.family ?? kind?.family,
@@ -630,7 +687,7 @@ function build(data, src, error, warn, { resolveIcon }) {
630
687
  badge: n.badge === void 0 ? void 0 : String(n.badge),
631
688
  uses: n.uses ?? [],
632
689
  chips: (n.chips ?? []).map((c) => typeof c === "string" ? { label: c } : c),
633
- w: n.w,
690
+ w: n.w ?? (fields.length ? DEFAULT_ENTITY_WIDTH : void 0),
634
691
  docHtml: docHtml || void 0,
635
692
  links: n.links ?? [],
636
693
  refs: n.refs ?? [],
@@ -638,18 +695,39 @@ function build(data, src, error, warn, { resolveIcon }) {
638
695
  diagram: typeof n.diagram === "string" ? { name: n.diagram } : n.diagram
639
696
  };
640
697
  }
698
+ for (const { node: id, index, text } of refTexts) {
699
+ const found = resolveEnd(text, nodes);
700
+ if ("end" in found) {
701
+ nodes[id].fields[index].ref = found.end;
702
+ continue;
703
+ }
704
+ const name = nodes[id].fields[index].name;
705
+ error(
706
+ found.message ?? `Field "${name}" points at "${text}", which is not a node`,
707
+ ["nodes", id, "fields", index, name, "ref"],
708
+ found.hint ?? didYouMean(text, Object.keys(nodes))
709
+ );
710
+ }
641
711
  const edges = {};
642
712
  const nodeIds = Object.keys(nodes);
643
713
  (data.edges ?? []).forEach((e, i) => {
644
714
  const path = ["edges", i];
645
715
  let ok = true;
716
+ const ends = {};
646
717
  for (const end of ["from", "to"]) {
647
- if (!nodes[e[end]]) {
648
- error(`Edge ${end} "${e[end]}" is not a node`, [...path, end], didYouMean(e[end], nodeIds));
718
+ const found = resolveEnd(e[end], nodes);
719
+ if ("end" in found) ends[end] = found.end;
720
+ else {
721
+ error(
722
+ found.message ?? `Edge ${end} "${e[end]}" is not a node`,
723
+ [...path, end],
724
+ found.hint ?? didYouMean(e[end], nodeIds)
725
+ );
649
726
  ok = false;
650
727
  }
651
728
  }
652
- const kind = e.kind ?? "http";
729
+ const between = ends.from && ends.to && [ends.from, ends.to].every((x) => nodes[x.node].fields.length);
730
+ const kind = e.kind ?? (between ? "relation" : "http");
653
731
  if (!edgeKinds[kind]) {
654
732
  error(
655
733
  `Unknown edge kind "${kind}"`,
@@ -658,7 +736,7 @@ function build(data, src, error, warn, { resolveIcon }) {
658
736
  );
659
737
  ok = false;
660
738
  }
661
- if (e.from === e.to) {
739
+ if (ok && ends.from.node === ends.to.node) {
662
740
  warn("Edge connects a node to itself and is not drawn", path);
663
741
  ok = false;
664
742
  }
@@ -670,8 +748,10 @@ function build(data, src, error, warn, { resolveIcon }) {
670
748
  }
671
749
  edges[id] = {
672
750
  id,
673
- from: e.from,
674
- to: e.to,
751
+ from: ends.from.node,
752
+ to: ends.to.node,
753
+ fromField: ends.from.field,
754
+ toField: ends.to.field,
675
755
  label: e.label,
676
756
  kind,
677
757
  both: e.both ?? false,
@@ -684,6 +764,32 @@ function build(data, src, error, warn, { resolveIcon }) {
684
764
  noteHtml: e.note ? renderMarkdown(e.note) : void 0
685
765
  };
686
766
  });
767
+ const written = new Set(
768
+ Object.values(edges).flatMap((e) => {
769
+ const a = `${e.from}.${e.fromField ?? ""}`;
770
+ const b = `${e.to}.${e.toField ?? ""}`;
771
+ return [`${a}>${b}`, `${b}>${a}`];
772
+ })
773
+ );
774
+ for (const node of Object.values(nodes)) {
775
+ for (const f of node.fields) {
776
+ if (!f.ref || f.ref.node === node.id) continue;
777
+ const key = `${node.id}.${f.name}>${f.ref.node}.${f.ref.field ?? ""}`;
778
+ if (written.has(key)) continue;
779
+ const id = `ref:${node.id}.${f.name}`;
780
+ edges[id] = {
781
+ id,
782
+ from: node.id,
783
+ to: f.ref.node,
784
+ fromField: f.name,
785
+ toField: f.ref.field,
786
+ kind: "relation",
787
+ both: false,
788
+ hidden: false,
789
+ status: "built"
790
+ };
791
+ }
792
+ }
687
793
  const viewDefs = Object.keys(data.views ?? {}).length ? data.views : { main: { title: data.title } };
688
794
  const views = Object.entries(viewDefs).map(
689
795
  ([id, v]) => buildView(
@@ -701,10 +807,12 @@ function build(data, src, error, warn, { resolveIcon }) {
701
807
  const sc = buildScenario(id, s, views, nodes, edges, error, warn);
702
808
  if (sc) scenarios.push(sc);
703
809
  }
810
+ const defaults = buildDefaults(data.defaults, views, scenarios, error);
704
811
  return {
705
812
  name: src.name,
706
813
  title: data.title,
707
814
  descriptionHtml: data.description ? renderMarkdown(data.description) : void 0,
815
+ defaults,
708
816
  nodes,
709
817
  groups,
710
818
  edges,
@@ -713,6 +821,37 @@ function build(data, src, error, warn, { resolveIcon }) {
713
821
  scenarios
714
822
  };
715
823
  }
824
+ function toField(item) {
825
+ if (typeof item === "string") return { name: item };
826
+ const [name, value] = Object.entries(item)[0];
827
+ if (value == null) return { name };
828
+ if (typeof value === "string") return { name, type: value };
829
+ const { ref: _ref, ...rest } = value;
830
+ return { name, ...rest };
831
+ }
832
+ function resolveEnd(text, nodes) {
833
+ if (nodes[text]) return { end: { node: text } };
834
+ const matches = [];
835
+ let known;
836
+ for (let i = text.indexOf("."); i > 0; i = text.indexOf(".", i + 1)) {
837
+ const node = nodes[text.slice(0, i)];
838
+ if (!node) continue;
839
+ known ??= node;
840
+ const field2 = text.slice(i + 1);
841
+ if (node.fields.some((f) => f.name === field2)) matches.push({ node: node.id, field: field2 });
842
+ }
843
+ if (matches.length === 1) return { end: matches[0] };
844
+ if (matches.length > 1) {
845
+ return { message: `"${text}" could be more than one field`, hint: "Rename a node or a field." };
846
+ }
847
+ if (!known) return {};
848
+ const field = text.slice(known.id.length + 1);
849
+ const names = known.fields.map((f) => f.name);
850
+ return {
851
+ message: `Node "${known.id}" has no field "${field}"`,
852
+ hint: didYouMean(field, names) ?? (names.length ? `Fields: ${names.join(", ")}` : void 0)
853
+ };
854
+ }
716
855
  function buildView(id, v, diagramTitle, nodes, groups, edges, error) {
717
856
  const known = [...Object.keys(nodes), ...Object.keys(groups)];
718
857
  const members = (gid, out) => {
@@ -765,6 +904,38 @@ function buildView(id, v, diagramTitle, nodes, groups, edges, error) {
765
904
  interfaces: v.interfaces ?? false
766
905
  };
767
906
  }
907
+ function buildDefaults(d, views, scenarios, error) {
908
+ if (!d) return void 0;
909
+ const ids = (list) => list.map((x) => x.id);
910
+ const view = views.find((v) => v.id === d.view);
911
+ if (d.view !== void 0 && !view) {
912
+ error(
913
+ `defaults.view "${d.view}" is not a view`,
914
+ ["defaults", "view"],
915
+ didYouMean(d.view, ids(views))
916
+ );
917
+ }
918
+ const scenario = scenarios.find((s) => s.id === d.scenario);
919
+ if (d.scenario !== void 0 && !scenario) {
920
+ error(
921
+ `defaults.scenario "${d.scenario}" is not a scenario`,
922
+ ["defaults", "scenario"],
923
+ didYouMean(d.scenario, ids(scenarios))
924
+ );
925
+ }
926
+ if (view && scenario && scenario.view !== view.id) {
927
+ error(
928
+ `defaults.scenario "${scenario.id}" plays on view "${scenario.view}", not "${view.id}"`,
929
+ ["defaults", "scenario"],
930
+ `Drop \`view\` and the diagram opens on "${scenario.view}", or pick a scenario of "${view.id}".`
931
+ );
932
+ }
933
+ if (d.autoplay && d.scenario === void 0) {
934
+ error("defaults.autoplay needs a defaults.scenario to play", ["defaults", "autoplay"]);
935
+ }
936
+ const out = Object.fromEntries(Object.entries(d).filter(([, v]) => v !== void 0));
937
+ return Object.keys(out).length ? out : void 0;
938
+ }
768
939
  function buildScenario(id, s, views, nodes, edges, error, warn) {
769
940
  const viewId = s.view ?? views[0].id;
770
941
  const view = views.find((v) => v.id === viewId);
@@ -1042,6 +1213,7 @@ function describeIssue(issue) {
1042
1213
  function allowedKeysFor(path) {
1043
1214
  const [section] = path;
1044
1215
  if (path.length === 0) return KNOWN_KEYS.root;
1216
+ if (path.length === 1) return section === "defaults" ? KNOWN_KEYS.defaults : void 0;
1045
1217
  if (path.length !== 2) return void 0;
1046
1218
  switch (section) {
1047
1219
  case "nodes":
@@ -17,7 +17,8 @@ var BUILTIN_KINDS = {
17
17
  network: { icon: "lucide:network", family: "violet" },
18
18
  vm: { icon: "lucide:box", family: "violet" },
19
19
  auth: { icon: "lucide:key-round", family: "teal" },
20
- monitor: { icon: "lucide:activity", family: "teal" }
20
+ monitor: { icon: "lucide:activity", family: "teal" },
21
+ entity: { icon: "lucide:table-2", family: "blue" }
21
22
  };
22
23
  var BUILTIN_EDGE_KINDS = {
23
24
  http: { label: "HTTP request / response", color: "ink", width: 1.5 },
@@ -25,7 +26,8 @@ var BUILTIN_EDGE_KINDS = {
25
26
  tunnel: { label: "Overlay tunnel", color: "tunnel", width: 5 },
26
27
  queue: { label: "Queue / broker", color: "ok", width: 2, dash: "2 4" },
27
28
  ws: { label: "WebSocket", color: "ink", width: 1.6, dash: "7 4" },
28
- data: { label: "Data access", color: "muted", width: 1.3 }
29
+ data: { label: "Data access", color: "muted", width: 1.3 },
30
+ relation: { label: "Relation between entities", color: "muted", width: 1.4 }
29
31
  };
30
32
  var STEP_KIND_COLORS = {
31
33
  request: "accent",
@@ -35,6 +37,7 @@ var STEP_KIND_COLORS = {
35
37
  event: "warn"
36
38
  };
37
39
  var DEFAULT_NODE_WIDTH = 178;
40
+ var DEFAULT_ENTITY_WIDTH = 280;
38
41
 
39
42
  // src/geometry.ts
40
43
  function polylineLength(points) {
@@ -136,6 +139,7 @@ export {
136
139
  BUILTIN_EDGE_KINDS,
137
140
  STEP_KIND_COLORS,
138
141
  DEFAULT_NODE_WIDTH,
142
+ DEFAULT_ENTITY_WIDTH,
139
143
  polylineLength,
140
144
  dist,
141
145
  pointAt,
@@ -8,3 +8,5 @@ export declare const BUILTIN_KINDS: Record<string, KindDef>;
8
8
  export declare const BUILTIN_EDGE_KINDS: Record<string, EdgeKindDef>;
9
9
  export declare const STEP_KIND_COLORS: Record<string, string>;
10
10
  export declare const DEFAULT_NODE_WIDTH = 178;
11
+ /** Cards with fields need room for a name and a type side by side. */
12
+ export declare const DEFAULT_ENTITY_WIDTH = 280;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ export * from './nesting';
6
6
  export * from './outline';
7
7
  export * from './phases';
8
8
  export * from './route';
9
+ export * from './scrubber';
10
+ export * from './start';
9
11
  export * from './story';
10
12
  export * from './svg';
11
13
  export * from './timeline';
package/dist/index.js CHANGED
@@ -2,10 +2,11 @@ import {
2
2
  attachNested,
3
3
  linkDiagrams,
4
4
  nestedDiagrams
5
- } from "./chunk-3QBMUILO.js";
5
+ } from "./chunk-P2X2XSBM.js";
6
6
  import {
7
7
  BUILTIN_EDGE_KINDS,
8
8
  BUILTIN_KINDS,
9
+ DEFAULT_ENTITY_WIDTH,
9
10
  DEFAULT_NODE_WIDTH,
10
11
  STEP_KIND_COLORS,
11
12
  dist,
@@ -13,7 +14,7 @@ import {
13
14
  pointAt,
14
15
  polylineLength,
15
16
  roundedPath
16
- } from "./chunk-ITBBPZE6.js";
17
+ } from "./chunk-ZYFFV7Y7.js";
17
18
 
18
19
  // src/layout.ts
19
20
  import ELK from "elkjs/lib/elk.bundled.js";
@@ -960,9 +961,10 @@ function overlap(a, b, pad) {
960
961
  var INLINE_PAD = { top: 40, left: 4, right: 4, bottom: 4 };
961
962
  function openingView(diagram, ref) {
962
963
  const has = (id) => diagram.views.some((v) => v.id === id);
963
- if (has(ref.view)) return ref.view;
964
- const fromScenario = diagram.scenarios.find((s) => s.id === ref.scenario)?.view;
965
- return has(fromScenario) ? fromScenario : diagram.views[0].id;
964
+ const of = (scenario) => diagram.scenarios.find((s) => s.id === scenario)?.view;
965
+ return [ref.view, of(ref.scenario), of(diagram.defaults?.scenario), diagram.defaults?.view].find(
966
+ has
967
+ ) ?? diagram.views[0].id;
966
968
  }
967
969
  var canExpand = (ref, diagrams) => !!ref && ref.mode !== "open" && !!diagrams[ref.name];
968
970
  var canOpen = (ref, diagrams) => !!ref && ref.mode !== "expand" && !!diagrams[ref.name];
@@ -1186,6 +1188,81 @@ function phaseSpans(scenario) {
1186
1188
  return out;
1187
1189
  }
1188
1190
 
1191
+ // src/scrubber.ts
1192
+ function buildScrubber(tl) {
1193
+ const steps = tl.scenario.steps;
1194
+ const slots = [];
1195
+ let i = 0;
1196
+ while (i < steps.length) {
1197
+ let j = i + 1;
1198
+ const par = steps[i].par;
1199
+ if (par !== void 0) while (j < steps.length && steps[j].par === par) j++;
1200
+ const run = tl.steps.slice(i, j);
1201
+ slots.push({ from: i, to: j - 1, t0: Math.min(...run.map((s) => s.start)), t1: 0 });
1202
+ i = j;
1203
+ }
1204
+ slots.forEach((slot, k) => {
1205
+ slot.t1 = slots[k + 1]?.t0 ?? tl.duration;
1206
+ });
1207
+ if (slots[0]) slots[0].t0 = 0;
1208
+ const slotOf = (step) => slots.findIndex((s) => s.from <= step && step <= s.to);
1209
+ const sections = phaseSpans(tl.scenario).map((span) => ({
1210
+ phase: span.index,
1211
+ title: span.title,
1212
+ caption: span.caption,
1213
+ from: slotOf(span.from),
1214
+ to: slotOf(span.to)
1215
+ }));
1216
+ return { slots, sections, duration: tl.duration };
1217
+ }
1218
+ var clamp01 = (n) => Math.min(1, Math.max(0, n));
1219
+ function positionAt(s, t) {
1220
+ const n = s.slots.length;
1221
+ if (!n) return 0;
1222
+ let k = 0;
1223
+ s.slots.forEach((slot2, i) => {
1224
+ if (slot2.t0 <= t) k = i;
1225
+ });
1226
+ const slot = s.slots[k];
1227
+ const length = slot.t1 - slot.t0;
1228
+ return (k + (length > 0 ? clamp01((t - slot.t0) / length) : 0)) / n;
1229
+ }
1230
+ function timeAt(s, position) {
1231
+ const n = s.slots.length;
1232
+ if (!n) return 0;
1233
+ const x = clamp01(position) * n;
1234
+ const k = Math.min(n - 1, Math.floor(x));
1235
+ const slot = s.slots[k];
1236
+ return slot.t0 + (x - k) * (slot.t1 - slot.t0);
1237
+ }
1238
+ function slotAt(s, position) {
1239
+ const n = s.slots.length;
1240
+ return n ? Math.min(n - 1, Math.floor(clamp01(position) * n)) : -1;
1241
+ }
1242
+ function slotOfStep(s, index) {
1243
+ return s.slots.findIndex((slot) => slot.from <= index && index <= slot.to);
1244
+ }
1245
+
1246
+ // src/start.ts
1247
+ var defaultMode = (diagram) => diagram.defaults?.mode ?? "flow";
1248
+ function startOf(diagram, want = {}) {
1249
+ const d = diagram.defaults ?? {};
1250
+ const scenarioById = (id) => diagram.scenarios.find((s) => s.id === id);
1251
+ const hasView = (id) => diagram.views.some((v) => v.id === id);
1252
+ const first = diagram.views[0].id;
1253
+ let view;
1254
+ let scenario;
1255
+ if (want.view !== void 0 || want.scenario !== void 0) {
1256
+ view = [want.view, scenarioById(want.scenario)?.view].find(hasView) ?? first;
1257
+ scenario = scenarioById(want.scenario)?.id ?? (scenarioById(d.scenario)?.view === view ? d.scenario ?? null : null);
1258
+ } else {
1259
+ scenario = scenarioById(d.scenario)?.id ?? null;
1260
+ view = [scenarioById(scenario ?? void 0)?.view, d.view].find(hasView) ?? first;
1261
+ }
1262
+ const mode = want.mode ?? scenarioById(scenario ?? void 0)?.mode ?? defaultMode(diagram);
1263
+ return { view, scenario, mode };
1264
+ }
1265
+
1189
1266
  // src/story.ts
1190
1267
  function buildStory(diagram, scenario) {
1191
1268
  const steps = scenario.steps;
@@ -1484,16 +1561,19 @@ function runBounds(tl, index) {
1484
1561
  export {
1485
1562
  BUILTIN_EDGE_KINDS,
1486
1563
  BUILTIN_KINDS,
1564
+ DEFAULT_ENTITY_WIDTH,
1487
1565
  DEFAULT_NODE_WIDTH,
1488
1566
  INLINE_PAD,
1489
1567
  STEP_KIND_COLORS,
1490
1568
  attachNested,
1491
1569
  buildOutline,
1570
+ buildScrubber,
1492
1571
  buildStory,
1493
1572
  buildTimeline,
1494
1573
  canExpand,
1495
1574
  canOpen,
1496
1575
  defaultExpanded,
1576
+ defaultMode,
1497
1577
  dist,
1498
1578
  frameAt,
1499
1579
  hasPhases,
@@ -1511,12 +1591,17 @@ export {
1511
1591
  phaseSpans,
1512
1592
  pointAt,
1513
1593
  polylineLength,
1594
+ positionAt,
1514
1595
  rectOfPath,
1515
1596
  roundedPath,
1516
1597
  routeEdges,
1517
1598
  runBounds,
1518
1599
  scopeSvgIds,
1600
+ slotAt,
1601
+ slotOfStep,
1602
+ startOf,
1519
1603
  stepAt,
1520
1604
  stepEnd,
1521
- stepStart
1605
+ stepStart,
1606
+ timeAt
1522
1607
  };
@@ -25,7 +25,10 @@ export interface InnerLayout {
25
25
  layout: NestedLayout;
26
26
  }
27
27
  export type Diagrams = Record<string, CompiledDiagram | undefined>;
28
- /** The view a node's diagram shows when opened: the one named, the scenario's, or the first. */
28
+ /**
29
+ * The view a node's diagram shows when opened: the one the node names, its scenario's, the one the
30
+ * diagram opens on by itself, or the first.
31
+ */
29
32
  export declare function openingView(diagram: CompiledDiagram, ref: NodeDiagram): string;
30
33
  /** Whether a reader may show this node's diagram in place: it is at hand and not limited to `open`. */
31
34
  export declare const canExpand: (ref: NodeDiagram | undefined, diagrams: Diagrams) => boolean;
package/dist/node.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  attachNested,
3
3
  linkDiagrams,
4
4
  nestedDiagrams
5
- } from "./chunk-3QBMUILO.js";
5
+ } from "./chunk-P2X2XSBM.js";
6
6
  import {
7
7
  FamilySchema,
8
8
  KNOWN_KEYS,
@@ -11,8 +11,8 @@ import {
11
11
  compileDiagram,
12
12
  createIconResolver,
13
13
  renderMarkdown
14
- } from "./chunk-S7M3BRFI.js";
15
- import "./chunk-ITBBPZE6.js";
14
+ } from "./chunk-Q2NHZ6TV.js";
15
+ import "./chunk-ZYFFV7Y7.js";
16
16
 
17
17
  // src/node.ts
18
18
  import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
package/dist/schema.d.ts CHANGED
@@ -28,10 +28,24 @@ export declare const DirectionSchema: z.ZodEnum<{
28
28
  RIGHT: "RIGHT";
29
29
  UP: "UP";
30
30
  }>;
31
+ /** One field per list item: `- name`, `- name: type` or `- name: { type, key, ref, note }`. */
32
+ declare const FieldSchema: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNull, z.ZodObject<{
33
+ type: z.ZodOptional<z.ZodString>;
34
+ key: z.ZodOptional<z.ZodBoolean>;
35
+ ref: z.ZodOptional<z.ZodString>;
36
+ note: z.ZodOptional<z.ZodString>;
37
+ }, z.core.$strict>]>>]>;
31
38
  export declare const NodeSchema: z.ZodObject<{
32
39
  title: z.ZodOptional<z.ZodString>;
33
40
  sub: z.ZodOptional<z.ZodString>;
41
+ pill: z.ZodOptional<z.ZodString>;
34
42
  lines: z.ZodOptional<z.ZodArray<z.ZodString>>;
43
+ fields: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNull, z.ZodObject<{
44
+ type: z.ZodOptional<z.ZodString>;
45
+ key: z.ZodOptional<z.ZodBoolean>;
46
+ ref: z.ZodOptional<z.ZodString>;
47
+ note: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strict>]>>]>>>;
35
49
  icon: z.ZodOptional<z.ZodString>;
36
50
  kind: z.ZodOptional<z.ZodString>;
37
51
  family: z.ZodOptional<z.ZodEnum<{
@@ -440,10 +454,34 @@ export declare const ScenarioSchema: z.ZodObject<{
440
454
  }, z.core.$strict>], "type">>;
441
455
  }, z.core.$strict>], "type">>>;
442
456
  }, z.core.$strict>;
457
+ export declare const DefaultsSchema: z.ZodObject<{
458
+ view: z.ZodOptional<z.ZodString>;
459
+ scenario: z.ZodOptional<z.ZodString>;
460
+ mode: z.ZodOptional<z.ZodEnum<{
461
+ flow: "flow";
462
+ sequence: "sequence";
463
+ story: "story";
464
+ }>>;
465
+ sidebar: z.ZodOptional<z.ZodBoolean>;
466
+ subtitles: z.ZodOptional<z.ZodBoolean>;
467
+ autoplay: z.ZodOptional<z.ZodBoolean>;
468
+ }, z.core.$strict>;
443
469
  export declare const RootSchema: z.ZodObject<{
444
470
  $schema: z.ZodOptional<z.ZodString>;
445
471
  title: z.ZodString;
446
472
  description: z.ZodOptional<z.ZodString>;
473
+ defaults: z.ZodOptional<z.ZodObject<{
474
+ view: z.ZodOptional<z.ZodString>;
475
+ scenario: z.ZodOptional<z.ZodString>;
476
+ mode: z.ZodOptional<z.ZodEnum<{
477
+ flow: "flow";
478
+ sequence: "sequence";
479
+ story: "story";
480
+ }>>;
481
+ sidebar: z.ZodOptional<z.ZodBoolean>;
482
+ subtitles: z.ZodOptional<z.ZodBoolean>;
483
+ autoplay: z.ZodOptional<z.ZodBoolean>;
484
+ }, z.core.$strict>>;
447
485
  kinds: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
448
486
  icon: z.ZodOptional<z.ZodString>;
449
487
  family: z.ZodOptional<z.ZodEnum<{
@@ -498,7 +536,14 @@ export declare const RootSchema: z.ZodObject<{
498
536
  nodes: z.ZodRecord<z.ZodString, z.ZodObject<{
499
537
  title: z.ZodOptional<z.ZodString>;
500
538
  sub: z.ZodOptional<z.ZodString>;
539
+ pill: z.ZodOptional<z.ZodString>;
501
540
  lines: z.ZodOptional<z.ZodArray<z.ZodString>>;
541
+ fields: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNull, z.ZodObject<{
542
+ type: z.ZodOptional<z.ZodString>;
543
+ key: z.ZodOptional<z.ZodBoolean>;
544
+ ref: z.ZodOptional<z.ZodString>;
545
+ note: z.ZodOptional<z.ZodString>;
546
+ }, z.core.$strict>]>>]>>>;
502
547
  icon: z.ZodOptional<z.ZodString>;
503
548
  kind: z.ZodOptional<z.ZodString>;
504
549
  family: z.ZodOptional<z.ZodEnum<{
@@ -784,6 +829,7 @@ export declare const RootSchema: z.ZodObject<{
784
829
  }, z.core.$strict>>>;
785
830
  }, z.core.$strict>;
786
831
  export type RootInput = z.infer<typeof RootSchema>;
832
+ export type FieldInput = z.infer<typeof FieldSchema>;
787
833
  export type Step = z.infer<typeof StepSchema>;
788
834
  export declare const KNOWN_KEYS: {
789
835
  readonly root: string[];
@@ -792,6 +838,7 @@ export declare const KNOWN_KEYS: {
792
838
  readonly edge: string[];
793
839
  readonly view: string[];
794
840
  readonly scenario: string[];
841
+ readonly defaults: string[];
795
842
  };
796
843
  type Json = Record<string, unknown>;
797
844
  /**
@@ -0,0 +1,34 @@
1
+ import type { Timeline } from './timeline';
2
+ /** One stop of the slider: a step, or the steps of a `par` block that start together. */
3
+ export interface Slot {
4
+ /** First and last step of the slot, as indexes into `scenario.steps`. */
5
+ from: number;
6
+ to: number;
7
+ /** The time it covers. A slot runs until the next one starts, so pauses belong to the step before. */
8
+ t0: number;
9
+ t1: number;
10
+ }
11
+ /** A run of slots under one phase. A scenario without named phases is one section with no title. */
12
+ export interface Section {
13
+ /** Index in `scenario.phases`. */
14
+ phase: number;
15
+ title: string;
16
+ caption?: string;
17
+ /** First and last slot. */
18
+ from: number;
19
+ to: number;
20
+ }
21
+ export interface Scrubber {
22
+ slots: Slot[];
23
+ sections: Section[];
24
+ duration: number;
25
+ }
26
+ export declare function buildScrubber(tl: Timeline): Scrubber;
27
+ /** Where on the slider, 0 to 1, the moment `t` falls. */
28
+ export declare function positionAt(s: Scrubber, t: number): number;
29
+ /** The moment a place on the slider stands for. The inverse of `positionAt`. */
30
+ export declare function timeAt(s: Scrubber, position: number): number;
31
+ /** The slot a place on the slider falls in. */
32
+ export declare function slotAt(s: Scrubber, position: number): number;
33
+ /** The slot that holds step `index`, or -1. */
34
+ export declare function slotOfStep(s: Scrubber, index: number): number;
@@ -0,0 +1,22 @@
1
+ import type { CompiledDiagram, ScenarioMode } from './types';
2
+ /** What the page that shows a diagram asked for, if anything. Its choices win over the diagram's. */
3
+ export interface StartWant {
4
+ view?: string;
5
+ scenario?: string;
6
+ mode?: ScenarioMode;
7
+ }
8
+ export interface Start {
9
+ /** Always one of the diagram's views. */
10
+ view: string;
11
+ /** The scenario selected first, or null for the overview. */
12
+ scenario: string | null;
13
+ mode: ScenarioMode;
14
+ }
15
+ /** The mode a scenario is drawn in when neither it nor the page says: the diagram's, then `flow`. */
16
+ export declare const defaultMode: (diagram: CompiledDiagram) => ScenarioMode;
17
+ /**
18
+ * Where a diagram opens. The page's props come first, then what the diagram's author wrote under
19
+ * `defaults`, then the first view with nothing selected. The page choosing a scenario takes its
20
+ * view along, and one choosing a view leaves the default scenario out unless it plays there.
21
+ */
22
+ export declare function startOf(diagram: CompiledDiagram, want?: StartWant): Start;
package/dist/types.d.ts CHANGED
@@ -29,11 +29,27 @@ export interface NodeDiagram {
29
29
  /** Starts shown in place. */
30
30
  expanded?: boolean;
31
31
  }
32
+ /** A row of an entity card: one column, property or attribute of what the node stores. */
33
+ export interface CompiledField {
34
+ name: string;
35
+ type?: string;
36
+ /** Part of the primary key. */
37
+ key?: boolean;
38
+ /** The node (and field) this one points at, like a foreign key. Each one draws a relation. */
39
+ ref?: {
40
+ node: string;
41
+ field?: string;
42
+ };
43
+ note?: string;
44
+ }
32
45
  export interface CompiledNode {
33
46
  id: string;
34
47
  title: string;
35
48
  sub?: string;
49
+ /** A short label in the card header, like `TABLE` or `ES INDEX`. */
50
+ pill?: string;
36
51
  lines: string[];
52
+ fields: CompiledField[];
37
53
  icon?: IconData;
38
54
  kind?: string;
39
55
  family?: Family;
@@ -76,6 +92,9 @@ export interface CompiledEdge {
76
92
  id: string;
77
93
  from: string;
78
94
  to: string;
95
+ /** Set when the edge starts or ends on one field of an entity card. */
96
+ fromField?: string;
97
+ toField?: string;
79
98
  label?: string;
80
99
  kind: string;
81
100
  both: boolean;
@@ -147,10 +166,23 @@ export interface CompiledScenario {
147
166
  phases: CompiledPhase[];
148
167
  steps: CompiledStep[];
149
168
  }
169
+ /**
170
+ * How a diagram opens, as its author wrote it. A page's own choices (the props of `<Diagram>`) win
171
+ * over these. Validated by the compiler: the view and the scenario exist, and agree.
172
+ */
173
+ export interface DiagramDefaults {
174
+ view?: string;
175
+ scenario?: string;
176
+ mode?: ScenarioMode;
177
+ sidebar?: boolean;
178
+ subtitles?: boolean;
179
+ autoplay?: boolean;
180
+ }
150
181
  export interface CompiledDiagram {
151
182
  name: string;
152
183
  title: string;
153
184
  descriptionHtml?: string;
185
+ defaults?: DiagramDefaults;
154
186
  nodes: Record<string, CompiledNode>;
155
187
  groups: Record<string, CompiledGroup>;
156
188
  edges: Record<string, CompiledEdge>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-package-labs/idocs-core",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {