visigraph 0.0.6 → 0.0.7

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/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # visigraph
2
2
 
3
3
  Command line for [VisiGraph](https://github.com/textologylabs/visigraph):
4
- architecture models you can play through.
4
+ bringing your architecture to life.
5
5
 
6
- Early, pre-1.0. Today it validates a model. Next it builds a static,
7
- playable site from one: no server, works as a file, a GitLab Pages site or
8
- a Confluence embed.
6
+ Early, pre-1.0. Today it validates a model. Next it builds a static site
7
+ from one: an explorable model you can zoom, drill into and follow, with
8
+ journeys you can play on top. No server; works as a file, a GitLab Pages
9
+ site or a Confluence embed.
9
10
 
10
11
  ```sh
11
12
  npm install -g visigraph
package/dist/main.js CHANGED
@@ -2856,10 +2856,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2856
2856
  * @param {string} flag
2857
2857
  * @private
2858
2858
  */
2859
- unknownOption(flag) {
2859
+ unknownOption(flag2) {
2860
2860
  if (this._allowUnknownOption) return;
2861
2861
  let suggestion = "";
2862
- if (flag.startsWith("--") && this._showSuggestionAfterError) {
2862
+ if (flag2.startsWith("--") && this._showSuggestionAfterError) {
2863
2863
  let candidateFlags = [];
2864
2864
  let command = this;
2865
2865
  do {
@@ -2867,9 +2867,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2867
2867
  candidateFlags = candidateFlags.concat(moreFlags);
2868
2868
  command = command.parent;
2869
2869
  } while (command && !command._enablePositionalOptions);
2870
- suggestion = suggestSimilar(flag, candidateFlags);
2870
+ suggestion = suggestSimilar(flag2, candidateFlags);
2871
2871
  }
2872
- const message = `error: unknown option '${flag}'${suggestion}`;
2872
+ const message = `error: unknown option '${flag2}'${suggestion}`;
2873
2873
  this.error(message, { code: "commander.unknownOption" });
2874
2874
  }
2875
2875
  /**
@@ -10807,16 +10807,86 @@ function lineOf(range, lines) {
10807
10807
  return `line ${line}, column ${col}`;
10808
10808
  }
10809
10809
 
10810
+ // ../core/dist/kinds.js
10811
+ var FAMILIES = ["container", "endpoint", "entity"];
10812
+ var KindRegistry = class _KindRegistry {
10813
+ families = /* @__PURE__ */ new Map();
10814
+ static of(...extensions) {
10815
+ const r = new _KindRegistry();
10816
+ for (const e of extensions)
10817
+ r.register(e);
10818
+ return r;
10819
+ }
10820
+ register(extension) {
10821
+ for (const definition of extension.kinds) {
10822
+ if (!FAMILIES.includes(definition.family)) {
10823
+ throw new Error(`extension '${extension.name}': unknown family '${definition.family}'`);
10824
+ }
10825
+ const table = this.families.get(definition.family) ?? /* @__PURE__ */ new Map();
10826
+ const existing = table.get(definition.kind);
10827
+ if (existing) {
10828
+ throw new Error(`extension '${extension.name}': ${definition.family} kind '${definition.kind}' is already registered by '${existing.extension}'`);
10829
+ }
10830
+ table.set(definition.kind, { definition, extension: extension.name });
10831
+ this.families.set(definition.family, table);
10832
+ }
10833
+ return this;
10834
+ }
10835
+ /** True when at least one kind is registered for the family, so kinds in it are checked. */
10836
+ governs(family) {
10837
+ return (this.families.get(family)?.size ?? 0) > 0;
10838
+ }
10839
+ get(family, kind) {
10840
+ return this.families.get(family)?.get(kind)?.definition;
10841
+ }
10842
+ kinds(family) {
10843
+ return [...this.families.get(family)?.keys() ?? []].sort();
10844
+ }
10845
+ };
10846
+ function checkKind(registry, family, element) {
10847
+ if (!registry || !registry.governs(family))
10848
+ return [];
10849
+ const kind = element.kind;
10850
+ if (kind === void 0)
10851
+ return [];
10852
+ if (typeof kind !== "string")
10853
+ return [["kind", `${family} kind must be a string`]];
10854
+ const def = registry.get(family, kind);
10855
+ if (!def)
10856
+ return [["kind", `unknown ${family} kind '${kind}'; registered: ${registry.kinds(family).join(", ")}`]];
10857
+ const out = [];
10858
+ for (const [name, decl] of Object.entries(def.properties)) {
10859
+ const value = element[name];
10860
+ if (value === void 0) {
10861
+ if (decl.required)
10862
+ out.push([name, `${family} kind '${kind}' requires '${name}'`]);
10863
+ continue;
10864
+ }
10865
+ if (typeof value !== decl.type) {
10866
+ out.push([name, `'${name}' on ${family} kind '${kind}' must be a ${decl.type}`]);
10867
+ continue;
10868
+ }
10869
+ if (decl.enum && !decl.enum.includes(value)) {
10870
+ out.push([name, `'${name}' on ${family} kind '${kind}' must be one of ${decl.enum.join(", ")}; got '${String(value)}'`]);
10871
+ }
10872
+ }
10873
+ return out;
10874
+ }
10875
+
10810
10876
  // ../core/dist/resolve.js
10811
10877
  function resolve(model, options = {}) {
10812
- const r = new Resolver();
10878
+ const r = new Resolver(options.kinds);
10813
10879
  const root = r.declare(model, model.system, void 0, { file: options.file ?? "", prefix: "" });
10814
10880
  r.bind(root, void 0);
10815
10881
  r.check(root);
10816
10882
  return { root, diagnostics: r.diagnostics };
10817
10883
  }
10818
10884
  var Resolver = class {
10885
+ kinds;
10819
10886
  diagnostics = [];
10887
+ constructor(kinds2) {
10888
+ this.kinds = kinds2;
10889
+ }
10820
10890
  /** Scopes by interior identity, so a shared file is one scope. */
10821
10891
  scopes = /* @__PURE__ */ new Map();
10822
10892
  locs = /* @__PURE__ */ new Map();
@@ -10855,6 +10925,8 @@ var Resolver = class {
10855
10925
  return;
10856
10926
  }
10857
10927
  scope.entities.set(entity.id, entity);
10928
+ for (const [suffix2, message] of checkKind(this.kinds, "entity", entity))
10929
+ err(`${path}.${suffix2}`, message);
10858
10930
  });
10859
10931
  eachListed(node.journeys, "journeys", err, (journey, path) => {
10860
10932
  if (scope.journeys.has(journey.id)) {
@@ -10868,6 +10940,8 @@ var Resolver = class {
10868
10940
  err(`${path}.id`, `duplicate container id '${child.id}'`);
10869
10941
  return;
10870
10942
  }
10943
+ for (const [suffix2, message] of checkKind(this.kinds, "container", child))
10944
+ err(`${path}.${suffix2}`, message);
10871
10945
  const link = child[LINKED];
10872
10946
  const childLoc = link ? { file: link.file, prefix: "" } : { file: loc.file, prefix: `${loc.prefix}${path}.` };
10873
10947
  scope.children.set(child.id, this.declare(child, child.id, child, childLoc, scope.path));
@@ -10881,6 +10955,8 @@ var Resolver = class {
10881
10955
  err(`${path}.id`, `duplicate endpoint id '${endpoint.id}' in container '${container.id}'`);
10882
10956
  }
10883
10957
  endpointIds.add(endpoint.id);
10958
+ for (const [suffix2, message] of checkKind(this.kinds, "endpoint", endpoint))
10959
+ err(`${path}.${suffix2}`, message);
10884
10960
  });
10885
10961
  const stateIds = /* @__PURE__ */ new Set();
10886
10962
  eachListed(container.state, "state", err, (state, path) => {
@@ -10937,6 +11013,21 @@ var Resolver = class {
10937
11013
  const loc = this.locs.get(scope);
10938
11014
  const err = (path, message) => this.err(loc, path, message);
10939
11015
  const node = scope.container ?? this.rootNode(scope);
11016
+ eachListed(node.containers, "containers", () => {
11017
+ }, (container, cPath) => {
11018
+ if (container[LINKED])
11019
+ return;
11020
+ eachListed(container.endpoints, `${cPath}.endpoints`, () => {
11021
+ }, (endpoint, ePath) => {
11022
+ for (const key of ["accepts", "returns"]) {
11023
+ if (endpoint[key] === void 0)
11024
+ continue;
11025
+ const entity = ref(endpoint[key], `${ePath}.${key}`, err);
11026
+ if (entity)
11027
+ this.lookupMember(scope, entity, "entity", `${ePath}.${key}`, err);
11028
+ }
11029
+ });
11030
+ });
10940
11031
  eachListed(node.journeys, "journeys", err, (journey, path) => {
10941
11032
  const hasSteps = journey.steps !== void 0;
10942
11033
  const hasScenarios = journey.scenarios !== void 0;
@@ -10985,20 +11076,30 @@ var Resolver = class {
10985
11076
  this.lookupContainer(scope, from, `${p}.from`, err);
10986
11077
  const to = ref(step.to, `${p}.to`, err);
10987
11078
  const target = to ? this.lookupContainer(scope, to, `${p}.to`, err) : void 0;
11079
+ let endpointDecl;
10988
11080
  if (step.endpoint !== void 0) {
10989
11081
  const endpoint = ref(step.endpoint, `${p}.endpoint`, err);
10990
11082
  if (endpoint && target) {
10991
11083
  if (endpoint.includes(".")) {
10992
11084
  err(`${p}.endpoint`, `endpoint '${endpoint}' must be a bare id on the 'to' container, not a path`);
10993
- } else if (!(target.container?.endpoints ?? []).some((e) => e.id === endpoint)) {
10994
- err(`${p}.endpoint`, `container '${to}' has no endpoint '${endpoint}'`);
11085
+ } else {
11086
+ endpointDecl = (target.container?.endpoints ?? []).find((e) => e.id === endpoint);
11087
+ if (!endpointDecl)
11088
+ err(`${p}.endpoint`, `container '${to}' has no endpoint '${endpoint}'`);
10995
11089
  }
10996
11090
  }
10997
11091
  }
10998
11092
  if (step.payload !== void 0) {
10999
- const entity = isRecord(step.payload) ? ref(step.payload.entity, `${p}.payload.entity`, err) : ref(step.payload, `${p}.payload`, err);
11000
- if (entity)
11001
- this.lookupMember(scope, entity, "entity", `${p}.payload`, err);
11093
+ const entityRef = isRecord(step.payload) ? ref(step.payload.entity, `${p}.payload.entity`, err) : ref(step.payload, `${p}.payload`, err);
11094
+ const payload = entityRef ? this.lookupMember(scope, entityRef, "entity", `${p}.payload`, err) : void 0;
11095
+ if (payload && endpointDecl?.accepts !== void 0 && target) {
11096
+ const owner = this.ownerScope(target);
11097
+ const accepted = owner ? this.lookupMember(owner, endpointDecl.accepts, "entity", `${p}.payload`, () => {
11098
+ }) : void 0;
11099
+ if (accepted && accepted !== payload) {
11100
+ err(`${p}.payload`, `endpoint '${step.endpoint}' on '${to}' accepts '${endpointDecl.accepts}', not '${entityRef}'`);
11101
+ }
11102
+ }
11002
11103
  }
11003
11104
  if (!STEP_KINDS.includes(step.kind)) {
11004
11105
  err(`${p}.kind`, `kind must be one of ${STEP_KINDS.join(", ")}; got '${String(step.kind)}'`);
@@ -11029,19 +11130,27 @@ var Resolver = class {
11029
11130
  }
11030
11131
  return cur;
11031
11132
  }
11032
- /** Resolve `a.b.name` as container path `a.b` plus a member id. */
11033
11133
  lookupMember(scope, pathStr, kind, at, err) {
11034
11134
  const segments = pathStr.split(".");
11035
11135
  const name = segments.pop();
11036
11136
  const owner = segments.length === 0 ? scope : this.lookupContainer(scope, segments.join("."), at, err);
11037
11137
  if (!owner)
11038
- return false;
11039
- const table = kind === "entity" ? owner.entities : owner.journeys;
11040
- if (!table.has(name)) {
11138
+ return void 0;
11139
+ const found = kind === "entity" ? owner.entities.get(name) : owner.journeys.get(name);
11140
+ if (!found) {
11041
11141
  err(at, segments.length === 0 ? `unknown ${kind} '${name}'` : `unknown ${kind} '${name}' in '${segments.join(".")}'`);
11042
- return false;
11142
+ return void 0;
11043
11143
  }
11044
- return true;
11144
+ return found;
11145
+ }
11146
+ /** The scope a container is declared in: where its endpoint contracts resolve from. */
11147
+ ownerScope(target) {
11148
+ for (const s of this.scopes.values()) {
11149
+ for (const child of s.children.values())
11150
+ if (child === target)
11151
+ return s;
11152
+ }
11153
+ return void 0;
11045
11154
  }
11046
11155
  };
11047
11156
  function loadedFile(req, loc) {
@@ -11098,6 +11207,36 @@ var EXIT = {
11098
11207
  usage: 2
11099
11208
  };
11100
11209
 
11210
+ // ../ext-http/dist/index.js
11211
+ var http = {
11212
+ name: "@visigraph/ext-http",
11213
+ kinds: [
11214
+ {
11215
+ family: "endpoint",
11216
+ kind: "http",
11217
+ description: "An HTTP endpoint: a method and a path on a container.",
11218
+ properties: {
11219
+ method: {
11220
+ type: "string",
11221
+ tier: "advanced",
11222
+ required: true,
11223
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
11224
+ category: "transport"
11225
+ },
11226
+ path: {
11227
+ type: "string",
11228
+ tier: "advanced",
11229
+ required: true,
11230
+ category: "transport"
11231
+ }
11232
+ }
11233
+ }
11234
+ ]
11235
+ };
11236
+
11237
+ // src/kinds.ts
11238
+ var kinds = KindRegistry.of(http);
11239
+
11101
11240
  // src/commands/validate.ts
11102
11241
  function registerValidate(program3) {
11103
11242
  program3.command("validate").argument("<model>", "path to a model file (YAML)").description("check a model and every model it links or requires; every identifier must resolve").addHelpText(
@@ -11115,7 +11254,7 @@ async function validate(file) {
11115
11254
  const parsed = await loadModel(file);
11116
11255
  let diagnostics = parsed.diagnostics;
11117
11256
  if (parsed.model) {
11118
- diagnostics = diagnostics.concat(resolve(parsed.model, { file }).diagnostics);
11257
+ diagnostics = diagnostics.concat(resolve(parsed.model, { file, kinds }).diagnostics);
11119
11258
  }
11120
11259
  for (const d of diagnostics) {
11121
11260
  const where = d.path ? `${d.file ?? file}:${d.path}` : d.file ?? file;
@@ -11388,11 +11527,21 @@ function relaunch() {
11388
11527
  }
11389
11528
 
11390
11529
  // src/main.ts
11530
+ for (const stream of [process.stdout, process.stderr]) {
11531
+ stream.on("error", (e) => {
11532
+ if (e.code === "EPIPE") process.exit(process.exitCode ?? 0);
11533
+ throw e;
11534
+ });
11535
+ }
11391
11536
  var program2 = new Command();
11392
11537
  program2.name("visigraph").addHelpText("before", `${DESCRIPTION}
11393
11538
  `).version(VERSION, "-v, --version", "print the version and exit").helpOption("-h, --help", "print help and exit").helpCommand("help [command]", "print help for a command").showHelpAfterError("(run with --help for usage)").exitOverride();
11394
11539
  registerValidate(program2);
11395
11540
  var INSTANT = /* @__PURE__ */ new Set(["-v", "--version", "-h", "--help", "help"]);
11541
+ var [flag, maybeCommand] = process.argv.slice(2);
11542
+ if ((flag === "-h" || flag === "--help") && maybeCommand && program2.commands.some((c) => c.name() === maybeCommand)) {
11543
+ process.argv.splice(2, 2, "help", maybeCommand);
11544
+ }
11396
11545
  try {
11397
11546
  if (process.argv.length <= 2) {
11398
11547
  program2.outputHelp();