@stonecrop/schema 0.25.0 → 0.26.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/README.md CHANGED
@@ -305,6 +305,29 @@ stonecrop-schema generate -i introspection.json -o ./app/doctypes
305
305
  stonecrop-schema generate -s schema.graphql -o ./app/doctypes
306
306
  ```
307
307
 
308
+ ### What it writes
309
+
310
+ Each table yields two peer doctypes — the entity, carrying every column and backing the record
311
+ form, and its aggregate, the collection view — plus the `route` each one registers at:
312
+
313
+ ```jsonc
314
+ // order.json
315
+ { "name": "Order", "slug": "order", "route": "/order/:id" }
316
+ // orders.json
317
+ { "name": "Orders", "slug": "orders", "route": "/order" }
318
+ ```
319
+
320
+ The pair shares one URL segment, taken from the entity, so no URL ever carries a plural.
321
+
322
+ A doctype the schema shows as rows owned by another gets **no** `route`: it has no page, because
323
+ its records are edited inside their parent. Ownership is read from `ON DELETE CASCADE`, the only
324
+ place a database states it — a foreign key that cascades marks rows belonging to the parent, while
325
+ one that does not marks a reference to something outliving it. A doctype that anything links to
326
+ singly keeps its route regardless, so a link always has somewhere to navigate.
327
+
328
+ Every key is yours once written. Regeneration verifies an authored file and never overwrites it, so
329
+ a `route` you add, change or delete stays that way.
330
+
308
331
  ### Filtering types
309
332
 
310
333
  GraphQL schemas (especially PostGraphile) expose many internal types. Use `--include` to
package/dist/cli.js CHANGED
@@ -1,31 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync as c, existsSync as v, mkdirSync as O, writeFileSync as k } from "node:fs";
3
- import { resolve as d, join as C } from "node:path";
4
- import { parseArgs as P } from "node:util";
5
- import { getIntrospectionQuery as j } from "graphql";
6
- import { c as E, m as J, f as L, v as R } from "./validation-CQtfIFHQ.js";
7
- async function q(e, f) {
2
+ import { readFileSync as i, existsSync as y, mkdirSync as E, writeFileSync as C } from "node:fs";
3
+ import { resolve as u, join as A } from "node:path";
4
+ import { parseArgs as J } from "node:util";
5
+ import { getIntrospectionQuery as R } from "graphql";
6
+ import { c as F, a as G, p as L, m as q, f as D, s as T, v as U } from "./validation-DveCm7gu.js";
7
+ async function Q(e, w) {
8
8
  const o = await fetch(e, {
9
9
  method: "POST",
10
10
  headers: {
11
11
  "Content-Type": "application/json",
12
- ...f
12
+ ...w
13
13
  },
14
14
  body: JSON.stringify({
15
- query: j()
15
+ query: R()
16
16
  })
17
17
  });
18
18
  if (!o.ok)
19
19
  throw new Error(`Failed to fetch introspection: ${o.status} ${o.statusText}`);
20
- const p = await o.json();
21
- if (p.errors?.length)
22
- throw new Error(`GraphQL errors: ${p.errors.map((i) => i.message).join(", ")}`);
23
- if (!p.data)
20
+ const l = await o.json();
21
+ if (l.errors?.length)
22
+ throw new Error(`GraphQL errors: ${l.errors.map((r) => r.message).join(", ")}`);
23
+ if (!l.data)
24
24
  throw new Error("No data in introspection response");
25
- return p.data;
25
+ return l.data;
26
26
  }
27
- async function D() {
28
- const { values: e, positionals: f } = P({
27
+ async function W() {
28
+ const { values: e, positionals: w } = J({
29
29
  allowPositionals: !0,
30
30
  options: {
31
31
  endpoint: { type: "string", short: "e" },
@@ -37,72 +37,87 @@ async function D() {
37
37
  names: { type: "string" },
38
38
  "custom-scalars": { type: "string" },
39
39
  "include-unmapped": { type: "boolean", default: !1 },
40
+ "no-aggregates": { type: "boolean", default: !1 },
40
41
  check: { type: "boolean", default: !1 },
41
42
  help: { type: "boolean", short: "h" }
42
43
  }
43
- }), o = f[0];
44
- (e.help || !o) && (F(), process.exit(o ? 0 : 1)), o !== "generate" && (console.error(`Unknown command: ${o}`), console.error("Available commands: generate"), process.exit(1)), [e.endpoint, e.introspection, e.sdl].filter(Boolean).length !== 1 && (console.error("Exactly one of --endpoint, --introspection, or --sdl must be provided"), process.exit(1)), e.output || (console.error("--output <dir> is required"), process.exit(1));
45
- const i = d(e.output), a = {
44
+ }), o = w[0];
45
+ (e.help || !o) && (I(), process.exit(o ? 0 : 1)), o !== "generate" && (console.error(`Unknown command: ${o}`), console.error("Available commands: generate"), process.exit(1)), [e.endpoint, e.introspection, e.sdl].filter(Boolean).length !== 1 && (console.error("Exactly one of --endpoint, --introspection, or --sdl must be provided"), process.exit(1)), e.output || (console.error("--output <dir> is required"), process.exit(1));
46
+ const r = u(e.output), c = {
46
47
  includeUnmappedMeta: e["include-unmapped"]
47
48
  };
48
- if (e.include && (a.include = e.include.split(",").map((t) => t.trim())), e.exclude && (a.exclude = e.exclude.split(",").map((t) => t.trim())), e.names) {
49
- const t = d(e.names);
50
- a.doctypeNames = JSON.parse(c(t, "utf-8"));
49
+ if (e.include && (c.include = e.include.split(",").map((t) => t.trim())), e.exclude && (c.exclude = e.exclude.split(",").map((t) => t.trim())), e.names) {
50
+ const t = u(e.names);
51
+ c.doctypeNames = JSON.parse(i(t, "utf-8"));
51
52
  }
52
- if (a.onWarning = (t) => console.warn(` WARN: ${t}`), e["custom-scalars"]) {
53
- const t = d(e["custom-scalars"]), r = c(t, "utf-8");
54
- a.customScalars = JSON.parse(r);
53
+ if (c.onWarning = (t) => console.warn(` WARN: ${t}`), e["custom-scalars"]) {
54
+ const t = u(e["custom-scalars"]), n = i(t, "utf-8");
55
+ c.customScalars = JSON.parse(n);
55
56
  }
56
57
  let h;
57
58
  if (e.endpoint)
58
- console.log(`Fetching introspection from ${e.endpoint}...`), h = await q(e.endpoint);
59
+ console.log(`Fetching introspection from ${e.endpoint}...`), h = await Q(e.endpoint);
59
60
  else if (e.introspection) {
60
- const t = d(e.introspection), r = c(t, "utf-8"), s = JSON.parse(r);
61
- h = s.data ?? s;
61
+ const t = u(e.introspection), n = i(t, "utf-8"), a = JSON.parse(n);
62
+ h = a.data ?? a;
62
63
  } else {
63
- const t = d(e.sdl);
64
- h = c(t, "utf-8");
64
+ const t = u(e.sdl);
65
+ h = i(t, "utf-8");
65
66
  }
66
- const g = E(h, a);
67
- g.length === 0 && (console.warn("No entity types found in the schema. Check your include/exclude filters."), process.exit(0)), v(i) || O(i, { recursive: !0 });
68
- let y = 0, m = 0, u = 0;
69
- const w = [];
70
- for (const t of g) {
71
- const r = `${t.slug}.json`, s = C(i, r);
72
- let S = t;
73
- if (v(s)) {
74
- const { doctype: n, drift: l } = J(
75
- JSON.parse(c(s, "utf-8")),
76
- t
77
- );
78
- S = n, w.push(...L(l));
67
+ const v = F(h, c);
68
+ v.length === 0 && (console.warn("No entity types found in the schema. Check your include/exclude filters."), process.exit(0));
69
+ const O = {};
70
+ for (const t of v) {
71
+ const n = A(r, `${t.slug}.json`);
72
+ if (!y(n)) continue;
73
+ const a = G(JSON.parse(i(n, "utf-8")));
74
+ a !== void 0 && (O[t.name] = a);
75
+ }
76
+ const k = L(v, {
77
+ noAggregates: e["no-aggregates"],
78
+ identity: O,
79
+ onWarning: (t) => console.warn(` WARN: ${t}`)
80
+ });
81
+ y(r) || E(r, { recursive: !0 });
82
+ let S = 0, f = 0, m = 0;
83
+ const $ = [];
84
+ for (const { generated: t, basis: n, subset: a } of k) {
85
+ const x = `${t.slug}.json`, p = A(r, x);
86
+ let N = t;
87
+ if (y(p)) {
88
+ const { doctype: s, drift: d } = q(JSON.parse(i(p, "utf-8")), n, {
89
+ subset: a
90
+ });
91
+ N = s, $.push(...D(d));
79
92
  }
80
- const x = JSON.stringify(S, null, " ") + `
81
- `, $ = v(s) && c(s, "utf-8") === x;
82
- $ || u++, !e.check && !$ && k(s, x, "utf-8");
83
- const N = R(S);
84
- if (N.success) {
85
- const n = t.fields.filter((l) => l._unmapped);
86
- n.length > 0 && (y++, console.warn(
87
- ` WARN: ${r} has ${n.length} unmapped field(s): ${n.map((l) => l.fieldname).join(", ")}`
93
+ const g = { ...N };
94
+ Array.isArray(g.fields) && (g.fields = g.fields.map(T));
95
+ const b = JSON.stringify(g, null, " ") + `
96
+ `, j = y(p) && i(p, "utf-8") === b;
97
+ j || m++, !e.check && !j && C(p, b, "utf-8");
98
+ const P = U(N);
99
+ if (P.success) {
100
+ const s = t.fields.filter((d) => d._unmapped);
101
+ s.length > 0 && (S++, console.warn(
102
+ ` WARN: ${x} has ${s.length} unmapped field(s): ${s.map((d) => d.fieldname).join(", ")}`
88
103
  ));
89
104
  } else {
90
- m++, console.error(` ERROR: ${r} failed validation:`);
91
- for (const n of N.errors)
92
- console.error(` ${n.path.join(".")}: ${n.message}`);
105
+ f++, console.error(` ERROR: ${x} failed validation:`);
106
+ for (const s of P.errors)
107
+ console.error(` ${s.path.join(".")}: ${s.message}`);
93
108
  }
94
109
  }
95
- if (w.length > 0) {
110
+ if ($.length > 0) {
96
111
  console.log(`
97
112
  Drift between the authored doctypes and the schema (reported, not applied):`);
98
- for (const t of w) console.log(t);
113
+ for (const t of $) console.log(t);
99
114
  }
100
115
  console.log(
101
116
  `
102
- ${e.check ? "Checked" : "Generated"} ${g.length} doctype(s) in ${i}` + (u ? ` (${u} ${e.check ? "would change" : "written"})` : " (all up to date)") + (y ? ` (${y} with warnings)` : "") + (m ? ` (${m} with errors)` : "")
103
- ), (m > 0 || e.check && u > 0) && process.exit(1);
117
+ ${e.check ? "Checked" : "Generated"} ${k.length} doctype(s) in ${r}` + (m ? ` (${m} ${e.check ? "would change" : "written"})` : " (all up to date)") + (S ? ` (${S} with warnings)` : "") + (f ? ` (${f} with errors)` : "")
118
+ ), (f > 0 || e.check && m > 0) && process.exit(1);
104
119
  }
105
- function F() {
120
+ function I() {
106
121
  console.log(`
107
122
  stonecrop-schema - Convert GraphQL schemas to Stonecrop doctypes
108
123
 
@@ -123,9 +138,20 @@ OPTIONS:
123
138
  --names <file> JSON file mapping GraphQL type name to doctype name
124
139
  --custom-scalars <file> JSON file mapping custom scalar names to field templates
125
140
  --include-unmapped Include _graphqlType metadata on unmapped fields
141
+ --no-aggregates Emit only the entity doctype, not its aggregate
126
142
  --check Report drift and exit non-zero if anything would change; write nothing
127
143
  --help, -h Show this help message
128
144
 
145
+ Each table generates TWO doctypes: the entity (every column, backs the record form) and its
146
+ aggregate (the collection view, identity column only by default), named as simple plurals —
147
+ 'task.json' and 'tasks.json'. Widen an aggregate by adding fields to it — curation survives
148
+ regeneration. A doctype whose name is already plural gets no aggregate, because it would claim
149
+ its own name and its own file; the run says so and still writes the entity.
150
+
151
+ An aggregate needs an identity column. A table keyed on 'id' gives one up; for a natural key,
152
+ declare 'primaryKey' on the field in the entity's own file and re-run — the aggregate is keyed
153
+ on whatever that file declares.
154
+
129
155
  NOTE: an existing doctype file is the source of truth. Regeneration verifies it against the
130
156
  schema and adds 'source: introspected' markers; it reports disagreements rather than
131
157
  overwriting them, so hand-curation survives. Use --check in CI.
@@ -146,7 +172,7 @@ EXAMPLES:
146
172
  --include "User,Post,Comment"
147
173
  `);
148
174
  }
149
- D().catch((e) => {
175
+ W().catch((e) => {
150
176
  console.error("Error:", e.message), process.exit(1);
151
177
  });
152
178
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/* oxlint-disable no-console */\n\n/**\n * Stonecrop Schema CLI\n *\n * Converts GraphQL introspection results to Stonecrop doctype JSON schemas.\n *\n * Usage:\n * stonecrop-schema generate --endpoint <url> --output <dir>\n * stonecrop-schema generate --introspection <file.json> --output <dir>\n * stonecrop-schema generate --sdl <file.graphql> --output <dir>\n *\n * @packageDocumentation\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport { getIntrospectionQuery, type IntrospectionQuery } from 'graphql'\n\nimport { convertGraphQLSchema, formatDoctypeDrift, mergeIntrospectedDoctype } from './converter/index'\nimport { validateDoctype } from './validation'\nimport type { GraphQLConversionOptions } from './converter/types'\n\n/**\n * Fetch an introspection result from a live GraphQL endpoint.\n *\n * @param endpoint - The GraphQL endpoint URL\n * @param headers - Optional HTTP headers\n * @returns The introspection query result\n */\nasync function fetchIntrospection(endpoint: string, headers?: Record<string, string>): Promise<IntrospectionQuery> {\n\tconst response = await fetch(endpoint, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t\t...headers,\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tquery: getIntrospectionQuery(),\n\t\t}),\n\t})\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch introspection: ${response.status} ${response.statusText}`)\n\t}\n\n\tconst json: { data?: IntrospectionQuery; errors?: Array<{ message: string }> } = await response.json()\n\n\tif (json.errors?.length) {\n\t\tthrow new Error(`GraphQL errors: ${json.errors.map(e => e.message).join(', ')}`)\n\t}\n\n\tif (!json.data) {\n\t\tthrow new Error('No data in introspection response')\n\t}\n\n\treturn json.data\n}\n\nasync function main(): Promise<void> {\n\tconst { values, positionals } = parseArgs({\n\t\tallowPositionals: true,\n\t\toptions: {\n\t\t\tendpoint: { type: 'string', short: 'e' },\n\t\t\tintrospection: { type: 'string', short: 'i' },\n\t\t\tsdl: { type: 'string', short: 's' },\n\t\t\toutput: { type: 'string', short: 'o' },\n\t\t\tinclude: { type: 'string' },\n\t\t\texclude: { type: 'string' },\n\t\t\tnames: { type: 'string' },\n\t\t\t'custom-scalars': { type: 'string' },\n\t\t\t'include-unmapped': { type: 'boolean', default: false },\n\t\t\tcheck: { type: 'boolean', default: false },\n\t\t\thelp: { type: 'boolean', short: 'h' },\n\t\t},\n\t})\n\n\tconst command = positionals[0]\n\n\tif (values.help || !command) {\n\t\tprintHelp()\n\t\tprocess.exit(command ? 0 : 1)\n\t}\n\n\tif (command !== 'generate') {\n\t\tconsole.error(`Unknown command: ${command}`)\n\t\tconsole.error('Available commands: generate')\n\t\tprocess.exit(1)\n\t}\n\n\t// Determine source\n\tconst sourceCount = [values.endpoint, values.introspection, values.sdl].filter(Boolean).length\n\tif (sourceCount !== 1) {\n\t\tconsole.error('Exactly one of --endpoint, --introspection, or --sdl must be provided')\n\t\tprocess.exit(1)\n\t}\n\n\tif (!values.output) {\n\t\tconsole.error('--output <dir> is required')\n\t\tprocess.exit(1)\n\t}\n\n\tconst outputDir = resolve(values.output)\n\n\t// Build conversion options\n\tconst options: GraphQLConversionOptions = {\n\t\tincludeUnmappedMeta: values['include-unmapped'],\n\t}\n\n\tif (values.include) {\n\t\toptions.include = values.include.split(',').map(s => s.trim())\n\t}\n\n\tif (values.exclude) {\n\t\toptions.exclude = values.exclude.split(',').map(s => s.trim())\n\t}\n\n\tif (values.names) {\n\t\tconst namesPath = resolve(values.names)\n\t\toptions.doctypeNames = JSON.parse(readFileSync(namesPath, 'utf-8'))\n\t}\n\n\toptions.onWarning = message => console.warn(` WARN: ${message}`)\n\n\tif (values['custom-scalars']) {\n\t\tconst scalarsPath = resolve(values['custom-scalars'])\n\t\tconst scalarsContent = readFileSync(scalarsPath, 'utf-8')\n\t\toptions.customScalars = JSON.parse(scalarsContent)\n\t}\n\n\t// Resolve source\n\tlet source: IntrospectionQuery | string\n\n\tif (values.endpoint) {\n\t\tconsole.log(`Fetching introspection from ${values.endpoint}...`)\n\t\tsource = await fetchIntrospection(values.endpoint)\n\t} else if (values.introspection) {\n\t\tconst filePath = resolve(values.introspection)\n\t\tconst content = readFileSync(filePath, 'utf-8')\n\t\tconst parsed = JSON.parse(content)\n\t\t// Handle both { data: { __schema: ... } } and { __schema: ... } formats\n\t\tsource = parsed.data ?? parsed\n\t} else {\n\t\tconst filePath = resolve(values.sdl!)\n\t\tsource = readFileSync(filePath, 'utf-8')\n\t}\n\n\t// Convert\n\tconst doctypes = convertGraphQLSchema(source, options)\n\n\tif (doctypes.length === 0) {\n\t\tconsole.warn('No entity types found in the schema. Check your include/exclude filters.')\n\t\tprocess.exit(0)\n\t}\n\n\t// Write output\n\tif (!existsSync(outputDir)) {\n\t\tmkdirSync(outputDir, { recursive: true })\n\t}\n\n\tlet warnings = 0\n\tlet errors = 0\n\tlet changed = 0\n\tconst driftLines: string[] = []\n\n\tfor (const generated of doctypes) {\n\t\tconst fileName = `${generated.slug}.json`\n\t\tconst filePath = join(outputDir, fileName)\n\n\t\t// When a doctype already exists it is the source of truth: generation confirms it and adds\n\t\t// provenance markers, and reports anything it disagrees with rather than applying it. A\n\t\t// doctype legitimately declares identity the schema cannot express — most often a natural\n\t\t// key that is a UNIQUE constraint, not the table's PRIMARY KEY — and overwriting that would\n\t\t// silently re-key the doctype on every run. A first generation has nothing to merge into,\n\t\t// so converter output is written verbatim.\n\t\tlet output: object = generated\n\t\tif (existsSync(filePath)) {\n\t\t\tconst { doctype: merged, drift } = mergeIntrospectedDoctype(\n\t\t\t\tJSON.parse(readFileSync(filePath, 'utf-8')),\n\t\t\t\tgenerated\n\t\t\t)\n\t\t\toutput = merged\n\t\t\tdriftLines.push(...formatDoctypeDrift(drift))\n\t\t}\n\n\t\t// Serialize the merged object directly. Never round-trip it through the Zod parser first:\n\t\t// that runs in strip mode and would silently drop every key this package does not model,\n\t\t// `handler` on an action being the one consumers actually rely on.\n\t\tconst json = JSON.stringify(output, null, '\\t') + '\\n'\n\t\tconst unchanged = existsSync(filePath) && readFileSync(filePath, 'utf-8') === json\n\t\tif (!unchanged) changed++\n\n\t\tif (!values.check && !unchanged) {\n\t\t\twriteFileSync(filePath, json, 'utf-8')\n\t\t}\n\n\t\t// Validate the output\n\t\tconst validation = validateDoctype(output)\n\t\tif (!validation.success) {\n\t\t\terrors++\n\t\t\tconsole.error(` ERROR: ${fileName} failed validation:`)\n\t\t\tfor (const err of validation.errors) {\n\t\t\t\tconsole.error(` ${err.path.join('.')}: ${err.message}`)\n\t\t\t}\n\t\t} else {\n\t\t\t// Check for unmapped fields\n\t\t\tconst unmappedFields = generated.fields.filter((f: any) => f._unmapped)\n\t\t\tif (unmappedFields.length > 0) {\n\t\t\t\twarnings++\n\t\t\t\tconsole.warn(\n\t\t\t\t\t` WARN: ${fileName} has ${unmappedFields.length} unmapped field(s): ${unmappedFields\n\t\t\t\t\t\t.map((f: any) => f.fieldname)\n\t\t\t\t\t\t.join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (driftLines.length > 0) {\n\t\tconsole.log('\\nDrift between the authored doctypes and the schema (reported, not applied):')\n\t\tfor (const line of driftLines) console.log(line)\n\t}\n\n\tconsole.log(\n\t\t`\\n${values.check ? 'Checked' : 'Generated'} ${doctypes.length} doctype(s) in ${outputDir}` +\n\t\t\t(changed ? ` (${changed} ${values.check ? 'would change' : 'written'})` : ' (all up to date)') +\n\t\t\t(warnings ? ` (${warnings} with warnings)` : '') +\n\t\t\t(errors ? ` (${errors} with errors)` : '')\n\t)\n\n\tif (errors > 0 || (values.check && changed > 0)) {\n\t\tprocess.exit(1)\n\t}\n}\n\nfunction printHelp(): void {\n\tconsole.log(`\nstonecrop-schema - Convert GraphQL schemas to Stonecrop doctypes\n\nUSAGE:\n stonecrop-schema generate [options]\n\nSOURCE (exactly one required):\n --endpoint, -e <url> Fetch introspection from a live GraphQL endpoint\n --introspection, -i <file> Read from a saved introspection JSON file\n --sdl, -s <file> Read from a GraphQL SDL (.graphql) file\n\nOUTPUT:\n --output, -o <dir> Directory to write doctype JSON files (required)\n\nOPTIONS:\n --include <types> Comma-separated list of type names to include\n --exclude <types> Comma-separated list of type names to exclude\n --names <file> JSON file mapping GraphQL type name to doctype name\n --custom-scalars <file> JSON file mapping custom scalar names to field templates\n --include-unmapped Include _graphqlType metadata on unmapped fields\n --check Report drift and exit non-zero if anything would change; write nothing\n --help, -h Show this help message\n\nNOTE: an existing doctype file is the source of truth. Regeneration verifies it against the\nschema and adds 'source: introspected' markers; it reports disagreements rather than\noverwriting them, so hand-curation survives. Use --check in CI.\n\nEXAMPLES:\n # From a live PostGraphile server\n stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas\n\n # From a saved introspection result\n stonecrop-schema generate -i introspection.json -o ./schemas\n\n # From an SDL file with custom scalars\n stonecrop-schema generate -s schema.graphql -o ./schemas \\\\\n --custom-scalars custom-scalars.json\n\n # Only convert specific types\n stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas \\\\\n --include \"User,Post,Comment\"\n`)\n}\n\nmain().catch(err => {\n\tconsole.error('Error:', err.message)\n\tprocess.exit(1)\n})\n"],"names":["fetchIntrospection","endpoint","headers","response","getIntrospectionQuery","json","e","main","values","positionals","parseArgs","command","printHelp","outputDir","resolve","options","s","namesPath","readFileSync","message","scalarsPath","scalarsContent","source","filePath","content","parsed","doctypes","convertGraphQLSchema","existsSync","mkdirSync","warnings","errors","changed","driftLines","generated","fileName","join","output","merged","drift","mergeIntrospectedDoctype","formatDoctypeDrift","unchanged","writeFileSync","validation","validateDoctype","unmappedFields","f","err","line"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/* oxlint-disable no-console */\n\n/**\n * Stonecrop Schema CLI\n *\n * Converts GraphQL introspection results to Stonecrop doctype JSON schemas.\n *\n * Usage:\n * stonecrop-schema generate --endpoint <url> --output <dir>\n * stonecrop-schema generate --introspection <file.json> --output <dir>\n * stonecrop-schema generate --sdl <file.graphql> --output <dir>\n *\n * @packageDocumentation\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport { getIntrospectionQuery, type IntrospectionQuery } from 'graphql'\n\nimport { authoredPrimaryKey } from './converter/authored'\nimport { convertGraphQLSchema, formatDoctypeDrift, mergeIntrospectedDoctype, planGeneration } from './converter/index'\nimport { stripFieldKind } from './field'\nimport { validateDoctype } from './validation'\nimport type { GraphQLConversionOptions } from './converter/types'\n\n/**\n * Fetch an introspection result from a live GraphQL endpoint.\n *\n * @param endpoint - The GraphQL endpoint URL\n * @param headers - Optional HTTP headers\n * @returns The introspection query result\n */\nasync function fetchIntrospection(endpoint: string, headers?: Record<string, string>): Promise<IntrospectionQuery> {\n\tconst response = await fetch(endpoint, {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t\t...headers,\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tquery: getIntrospectionQuery(),\n\t\t}),\n\t})\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch introspection: ${response.status} ${response.statusText}`)\n\t}\n\n\tconst json: { data?: IntrospectionQuery; errors?: Array<{ message: string }> } = await response.json()\n\n\tif (json.errors?.length) {\n\t\tthrow new Error(`GraphQL errors: ${json.errors.map(e => e.message).join(', ')}`)\n\t}\n\n\tif (!json.data) {\n\t\tthrow new Error('No data in introspection response')\n\t}\n\n\treturn json.data\n}\n\nasync function main(): Promise<void> {\n\tconst { values, positionals } = parseArgs({\n\t\tallowPositionals: true,\n\t\toptions: {\n\t\t\tendpoint: { type: 'string', short: 'e' },\n\t\t\tintrospection: { type: 'string', short: 'i' },\n\t\t\tsdl: { type: 'string', short: 's' },\n\t\t\toutput: { type: 'string', short: 'o' },\n\t\t\tinclude: { type: 'string' },\n\t\t\texclude: { type: 'string' },\n\t\t\tnames: { type: 'string' },\n\t\t\t'custom-scalars': { type: 'string' },\n\t\t\t'include-unmapped': { type: 'boolean', default: false },\n\t\t\t'no-aggregates': { type: 'boolean', default: false },\n\t\t\tcheck: { type: 'boolean', default: false },\n\t\t\thelp: { type: 'boolean', short: 'h' },\n\t\t},\n\t})\n\n\tconst command = positionals[0]\n\n\tif (values.help || !command) {\n\t\tprintHelp()\n\t\tprocess.exit(command ? 0 : 1)\n\t}\n\n\tif (command !== 'generate') {\n\t\tconsole.error(`Unknown command: ${command}`)\n\t\tconsole.error('Available commands: generate')\n\t\tprocess.exit(1)\n\t}\n\n\t// Determine source\n\tconst sourceCount = [values.endpoint, values.introspection, values.sdl].filter(Boolean).length\n\tif (sourceCount !== 1) {\n\t\tconsole.error('Exactly one of --endpoint, --introspection, or --sdl must be provided')\n\t\tprocess.exit(1)\n\t}\n\n\tif (!values.output) {\n\t\tconsole.error('--output <dir> is required')\n\t\tprocess.exit(1)\n\t}\n\n\tconst outputDir = resolve(values.output)\n\n\t// Build conversion options\n\tconst options: GraphQLConversionOptions = {\n\t\tincludeUnmappedMeta: values['include-unmapped'],\n\t}\n\n\tif (values.include) {\n\t\toptions.include = values.include.split(',').map(s => s.trim())\n\t}\n\n\tif (values.exclude) {\n\t\toptions.exclude = values.exclude.split(',').map(s => s.trim())\n\t}\n\n\tif (values.names) {\n\t\tconst namesPath = resolve(values.names)\n\t\toptions.doctypeNames = JSON.parse(readFileSync(namesPath, 'utf-8'))\n\t}\n\n\toptions.onWarning = message => console.warn(` WARN: ${message}`)\n\n\tif (values['custom-scalars']) {\n\t\tconst scalarsPath = resolve(values['custom-scalars'])\n\t\tconst scalarsContent = readFileSync(scalarsPath, 'utf-8')\n\t\toptions.customScalars = JSON.parse(scalarsContent)\n\t}\n\n\t// Resolve source\n\tlet source: IntrospectionQuery | string\n\n\tif (values.endpoint) {\n\t\tconsole.log(`Fetching introspection from ${values.endpoint}...`)\n\t\tsource = await fetchIntrospection(values.endpoint)\n\t} else if (values.introspection) {\n\t\tconst filePath = resolve(values.introspection)\n\t\tconst content = readFileSync(filePath, 'utf-8')\n\t\tconst parsed = JSON.parse(content)\n\t\t// Handle both { data: { __schema: ... } } and { __schema: ... } formats\n\t\tsource = parsed.data ?? parsed\n\t} else {\n\t\tconst filePath = resolve(values.sdl!)\n\t\tsource = readFileSync(filePath, 'utf-8')\n\t}\n\n\t// Convert\n\tconst entities = convertGraphQLSchema(source, options)\n\n\tif (entities.length === 0) {\n\t\tconsole.warn('No entity types found in the schema. Check your include/exclude filters.')\n\t\tprocess.exit(0)\n\t}\n\n\t// Read the identity each authored doctype declares, before planning rather than during the write\n\t// loop below. SDL cannot express which UNIQUE column is a table's key, so for a natural-key\n\t// doctype the declaration in the file is the only answer that exists — and planning is what\n\t// decides whether the aggregate can be built at all. Reading it 20 lines later, as the loop\n\t// does, made the \"declare a primaryKey and re-run\" warning a dead end: re-running after\n\t// declaring one changed nothing.\n\tconst identity: Record<string, string> = {}\n\tfor (const entity of entities) {\n\t\tconst entityPath = join(outputDir, `${entity.slug}.json`)\n\t\tif (!existsSync(entityPath)) continue\n\t\tconst declared = authoredPrimaryKey(JSON.parse(readFileSync(entityPath, 'utf-8')))\n\t\tif (declared !== undefined) identity[entity.name] = declared\n\t}\n\n\t// Each table yields two doctypes — the entity and its aggregate — written as peers, one file\n\t// each, along with what each is verified against. See `planGeneration`.\n\tconst doctypes = planGeneration(entities, {\n\t\tnoAggregates: values['no-aggregates'],\n\t\tidentity,\n\t\tonWarning: message => console.warn(` WARN: ${message}`),\n\t})\n\n\t// Write output\n\tif (!existsSync(outputDir)) {\n\t\tmkdirSync(outputDir, { recursive: true })\n\t}\n\n\tlet warnings = 0\n\tlet errors = 0\n\tlet changed = 0\n\tconst driftLines: string[] = []\n\n\tfor (const { generated, basis, subset } of doctypes) {\n\t\tconst fileName = `${generated.slug}.json`\n\t\tconst filePath = join(outputDir, fileName)\n\n\t\t// When a doctype already exists it is the source of truth: generation confirms it and adds\n\t\t// provenance markers, and reports anything it disagrees with rather than applying it. A\n\t\t// doctype legitimately declares identity the schema cannot express — most often a natural\n\t\t// key that is a UNIQUE constraint, not the table's PRIMARY KEY — and overwriting that would\n\t\t// silently re-key the doctype on every run. A first generation has nothing to merge into,\n\t\t// so converter output is written verbatim.\n\t\tlet output: object = generated\n\t\tif (existsSync(filePath)) {\n\t\t\tconst { doctype: merged, drift } = mergeIntrospectedDoctype(JSON.parse(readFileSync(filePath, 'utf-8')), basis, {\n\t\t\t\tsubset,\n\t\t\t})\n\t\t\toutput = merged\n\t\t\tdriftLines.push(...formatDoctypeDrift(drift))\n\t\t}\n\n\t\t// Serialize the merged object directly. Never round-trip it through the Zod parser first:\n\t\t// that runs in strip mode and would silently drop every key this package does not model,\n\t\t// `handler` on an action being the one consumers actually rely on.\n\t\t//\n\t\t// `kind` is dropped on the way out because it is the discriminated-union tag the parser\n\t\t// synthesizes, not authored data — `injectKind` puts it back on every read. Writing it made\n\t\t// the generator and the docbuilder disagree with hand-authored files about what a doctype\n\t\t// looks like, and re-added the key on every regeneration.\n\t\tconst serializable: Record<string, unknown> = { ...output }\n\t\tif (Array.isArray(serializable.fields)) {\n\t\t\tserializable.fields = serializable.fields.map(stripFieldKind)\n\t\t}\n\t\tconst json = JSON.stringify(serializable, null, '\\t') + '\\n'\n\t\tconst unchanged = existsSync(filePath) && readFileSync(filePath, 'utf-8') === json\n\t\tif (!unchanged) changed++\n\n\t\tif (!values.check && !unchanged) {\n\t\t\twriteFileSync(filePath, json, 'utf-8')\n\t\t}\n\n\t\t// Validate the output\n\t\tconst validation = validateDoctype(output)\n\t\tif (!validation.success) {\n\t\t\terrors++\n\t\t\tconsole.error(` ERROR: ${fileName} failed validation:`)\n\t\t\tfor (const err of validation.errors) {\n\t\t\t\tconsole.error(` ${err.path.join('.')}: ${err.message}`)\n\t\t\t}\n\t\t} else {\n\t\t\t// Check for unmapped fields\n\t\t\tconst unmappedFields = generated.fields.filter((f: any) => f._unmapped)\n\t\t\tif (unmappedFields.length > 0) {\n\t\t\t\twarnings++\n\t\t\t\tconsole.warn(\n\t\t\t\t\t` WARN: ${fileName} has ${unmappedFields.length} unmapped field(s): ${unmappedFields\n\t\t\t\t\t\t.map((f: any) => f.fieldname)\n\t\t\t\t\t\t.join(', ')}`\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\tif (driftLines.length > 0) {\n\t\tconsole.log('\\nDrift between the authored doctypes and the schema (reported, not applied):')\n\t\tfor (const line of driftLines) console.log(line)\n\t}\n\n\tconsole.log(\n\t\t`\\n${values.check ? 'Checked' : 'Generated'} ${doctypes.length} doctype(s) in ${outputDir}` +\n\t\t\t(changed ? ` (${changed} ${values.check ? 'would change' : 'written'})` : ' (all up to date)') +\n\t\t\t(warnings ? ` (${warnings} with warnings)` : '') +\n\t\t\t(errors ? ` (${errors} with errors)` : '')\n\t)\n\n\tif (errors > 0 || (values.check && changed > 0)) {\n\t\tprocess.exit(1)\n\t}\n}\n\nfunction printHelp(): void {\n\tconsole.log(`\nstonecrop-schema - Convert GraphQL schemas to Stonecrop doctypes\n\nUSAGE:\n stonecrop-schema generate [options]\n\nSOURCE (exactly one required):\n --endpoint, -e <url> Fetch introspection from a live GraphQL endpoint\n --introspection, -i <file> Read from a saved introspection JSON file\n --sdl, -s <file> Read from a GraphQL SDL (.graphql) file\n\nOUTPUT:\n --output, -o <dir> Directory to write doctype JSON files (required)\n\nOPTIONS:\n --include <types> Comma-separated list of type names to include\n --exclude <types> Comma-separated list of type names to exclude\n --names <file> JSON file mapping GraphQL type name to doctype name\n --custom-scalars <file> JSON file mapping custom scalar names to field templates\n --include-unmapped Include _graphqlType metadata on unmapped fields\n --no-aggregates Emit only the entity doctype, not its aggregate\n --check Report drift and exit non-zero if anything would change; write nothing\n --help, -h Show this help message\n\nEach table generates TWO doctypes: the entity (every column, backs the record form) and its\naggregate (the collection view, identity column only by default), named as simple plurals —\n'task.json' and 'tasks.json'. Widen an aggregate by adding fields to it — curation survives\nregeneration. A doctype whose name is already plural gets no aggregate, because it would claim\nits own name and its own file; the run says so and still writes the entity.\n\nAn aggregate needs an identity column. A table keyed on 'id' gives one up; for a natural key,\ndeclare 'primaryKey' on the field in the entity's own file and re-run — the aggregate is keyed\non whatever that file declares.\n\nNOTE: an existing doctype file is the source of truth. Regeneration verifies it against the\nschema and adds 'source: introspected' markers; it reports disagreements rather than\noverwriting them, so hand-curation survives. Use --check in CI.\n\nEXAMPLES:\n # From a live PostGraphile server\n stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas\n\n # From a saved introspection result\n stonecrop-schema generate -i introspection.json -o ./schemas\n\n # From an SDL file with custom scalars\n stonecrop-schema generate -s schema.graphql -o ./schemas \\\\\n --custom-scalars custom-scalars.json\n\n # Only convert specific types\n stonecrop-schema generate -e http://localhost:5000/graphql -o ./schemas \\\\\n --include \"User,Post,Comment\"\n`)\n}\n\nmain().catch(err => {\n\tconsole.error('Error:', err.message)\n\tprocess.exit(1)\n})\n"],"names":["fetchIntrospection","endpoint","headers","response","getIntrospectionQuery","json","e","main","values","positionals","parseArgs","command","printHelp","outputDir","resolve","options","s","namesPath","readFileSync","message","scalarsPath","scalarsContent","source","filePath","content","parsed","entities","convertGraphQLSchema","identity","entity","entityPath","join","existsSync","declared","authoredPrimaryKey","doctypes","planGeneration","mkdirSync","warnings","errors","changed","driftLines","generated","basis","subset","fileName","output","merged","drift","mergeIntrospectedDoctype","formatDoctypeDrift","serializable","stripFieldKind","unchanged","writeFileSync","validation","validateDoctype","unmappedFields","f","err","line"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as E, D as N, F as h, G as _, I as p, a as j, L as x, T as P, b as R, d as B, V as w, W as M, e as k, g as G, h as K, i as V, j as W, k as Y, c as z, l as Q, n as q, o as J, f as X, p as H, q as U, r as Z, s as $, t as ee, u as re, m as ae, w as te, x as ne, y as ie, z as se, B as oe, C as le, E as ce, H as ue, v as de, J as fe } from "./validation-CQtfIFHQ.js";
1
+ import { A as L, D as p, F as E, G as h, I as _, b as P, L as R, T as j, d as x, e as B, V as M, W as w, g as k, h as K, i as G, j as V, k as W, l as Q, n as Y, o as z, c as q, q as J, r as X, t as H, f as U, u as Z, w as $, x as ee, y as re, z as ae, B as te, C as ne, E as ie, m as se, H as oe, J as le, K as ce, M as ue, p as de, N as fe, O as Ae, s as ye, P as ge, Q as be, v as Se, R as ve } from "./validation-DveCm7gu.js";
2
2
  const l = {
3
3
  ATextInput: "text",
4
4
  ATextboxInput: "text",
@@ -17,7 +17,7 @@ const l = {
17
17
  AQuantityInput: "quantity",
18
18
  ACurrencyInput: "currency"
19
19
  };
20
- function g(e) {
20
+ function S(e) {
21
21
  return e ? l[e] : void 0;
22
22
  }
23
23
  const c = {
@@ -53,7 +53,7 @@ function o(e) {
53
53
  function n(e) {
54
54
  return Array.isArray(e.choices);
55
55
  }
56
- function T(e) {
56
+ function D(e) {
57
57
  if (e === null || typeof e != "object" || Array.isArray(e)) return !1;
58
58
  const r = e;
59
59
  return !(typeof r.label != "string" || r.variant !== void 0 && !i(r.variant) || r.color !== void 0 && typeof r.color != "string");
@@ -70,7 +70,7 @@ function s(e) {
70
70
  function m(e) {
71
71
  return e === void 0 || Array.isArray(e) || !t(e) ? !1 : n(e);
72
72
  }
73
- function D(e) {
73
+ function F(e) {
74
74
  return e === void 0 ? [] : Array.isArray(e) ? e : t(e) ? n(e) ? e.choices : s(e) ? Object.keys(e) : [] : [];
75
75
  }
76
76
  function d(e, r) {
@@ -80,77 +80,83 @@ function d(e, r) {
80
80
  color: e.color
81
81
  } : { label: r };
82
82
  }
83
- function b(e, r) {
83
+ function g(e, r) {
84
84
  if (!(r === void 0 || r === "") && r in e)
85
85
  return d(e[r], r);
86
86
  }
87
- function S(e, r) {
87
+ function b(e, r) {
88
88
  if (r === void 0 || r === "") return;
89
89
  const a = e.badges;
90
90
  if (!(!a || !(r in a)))
91
91
  return d(a[r], r);
92
92
  }
93
- function F(e, r) {
93
+ function T(e, r) {
94
94
  if (!(e === void 0 || r === void 0 || r === "") && !Array.isArray(e) && t(e)) {
95
- if (n(e)) return S(e, r);
96
- if (s(e)) return b(e, r);
95
+ if (n(e)) return b(e, r);
96
+ if (s(e)) return g(e, r);
97
97
  }
98
98
  }
99
99
  function I(e) {
100
100
  return e === void 0 || Array.isArray(e) || !t(e) ? !1 : n(e) ? e.badges !== void 0 && Object.keys(e.badges).length > 0 : s(e);
101
101
  }
102
102
  export {
103
- E as ActionDefinition,
103
+ L as ActionDefinition,
104
104
  O as CANONICAL_COMPONENTS,
105
105
  l as COMPONENT_CATEGORY,
106
106
  c as COMPONENT_LINK_EXPANSION,
107
- N as DoctypeFieldSchema,
108
- h as FieldsetFieldSchema,
109
- _ as GQL_SCALAR_MAP,
110
- p as INTERNAL_SCALARS,
111
- j as INTROSPECTED_IDENTITY_PROPS,
112
- x as LINK_DISPLAY_SUFFIX,
113
- P as TableFieldSchema,
114
- R as TableViewConfig,
107
+ p as DoctypeFieldSchema,
108
+ E as FieldsetFieldSchema,
109
+ h as GQL_SCALAR_MAP,
110
+ _ as INTERNAL_SCALARS,
111
+ P as INTROSPECTED_IDENTITY_PROPS,
112
+ R as LINK_DISPLAY_SUFFIX,
113
+ j as TableFieldSchema,
114
+ x as TableViewConfig,
115
115
  B as TriggerDefinition,
116
- w as ValueFieldSchema,
117
- M as WELL_KNOWN_SCALARS,
116
+ M as ValueFieldSchema,
117
+ w as WELL_KNOWN_SCALARS,
118
118
  k as WorkflowLayout,
119
- G as WorkflowMeta,
120
- K as buildScalarMap,
121
- V as camelToLabel,
122
- W as camelToSnake,
123
- Y as classifyFieldType,
124
- g as componentCategory,
119
+ K as WorkflowMeta,
120
+ G as aggregateDoctypeName,
121
+ V as buildAggregateDoctype,
122
+ W as buildScalarMap,
123
+ Q as camelToLabel,
124
+ Y as camelToSnake,
125
+ z as classifyFieldType,
126
+ S as componentCategory,
125
127
  f as componentLinkExpansion,
126
- z as convertGraphQLSchema,
127
- Q as defaultIsEntityField,
128
- q as defaultIsEntityType,
129
- J as flattenFields,
130
- X as formatDoctypeDrift,
131
- H as getDisplayField,
132
- U as getPrimaryKeyField,
133
- Z as getRecordIdField,
134
- $ as getRecordIdentity,
128
+ q as convertGraphQLSchema,
129
+ J as defaultIsEntityField,
130
+ X as defaultIsEntityType,
131
+ H as flattenFields,
132
+ U as formatDoctypeDrift,
133
+ Z as getDisplayField,
134
+ $ as getDoctypeSlug,
135
+ ee as getPrimaryKeyField,
136
+ re as getRecordIdField,
137
+ ae as getRecordIdentity,
135
138
  I as hasBadgeOptions,
136
- ee as isActionAllowedInState,
137
- T as isBadgeDescriptor,
139
+ te as inferFieldKind,
140
+ ne as isActionAllowedInState,
141
+ D as isBadgeDescriptor,
138
142
  s as isSelectChoiceMap,
139
143
  m as isSelectOptions,
140
- re as linkDisplayFieldname,
141
- F as lookupBadge,
142
- ae as mergeIntrospectedDoctype,
143
- te as normalizeFieldKind,
144
- ne as parseDoctype,
145
- ie as parseField,
146
- se as pascalToSnake,
144
+ ie as linkDisplayFieldname,
145
+ T as lookupBadge,
146
+ se as mergeIntrospectedDoctype,
147
+ oe as normalizeFieldKind,
148
+ le as parseDoctype,
149
+ ce as parseField,
150
+ ue as pascalToSnake,
151
+ de as planGeneration,
147
152
  v as resolveLinkRenderMode,
148
- D as selectChoices,
149
- oe as snakeToCamel,
150
- le as snakeToLabel,
151
- ce as toPascalCase,
152
- ue as toSlug,
153
- de as validateDoctype,
154
- fe as validateField
153
+ F as selectChoices,
154
+ fe as snakeToCamel,
155
+ Ae as snakeToLabel,
156
+ ye as stripFieldKind,
157
+ ge as toPascalCase,
158
+ be as toSlug,
159
+ Se as validateDoctype,
160
+ ve as validateField
155
161
  };
156
162
  //# sourceMappingURL=index.js.map