node-type-registry 0.3.1 → 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.
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ // GENERATED FILE — DO NOT EDIT
3
+ //
4
+ // Regenerate with:
5
+ // cd graphile/node-type-registry && pnpm generate:types
6
+ //
7
+ // These types match the JSONB shape expected by construct_blueprint().
8
+ // All field names are snake_case to match the SQL convention.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ /**
11
+ * ===========================================================================
12
+ * Data node type parameters
13
+ * ===========================================================================
14
+ */
15
+ ;
16
+ /**
17
+ * ===========================================================================
18
+ * Authz node type parameters
19
+ * ===========================================================================
20
+ */
21
+ ;
22
+ /**
23
+ * ===========================================================================
24
+ * Relation node type parameters
25
+ * ===========================================================================
26
+ */
27
+ ;
28
+ /**
29
+ * ===========================================================================
30
+ * View node type parameters
31
+ * ===========================================================================
32
+ */
33
+ ;
34
+ /**
35
+ * ===========================================================================
36
+ * Static structural types
37
+ * ===========================================================================
38
+ */
39
+ ;
40
+ /**
41
+ * ===========================================================================
42
+ * Node types -- discriminated union for nodes[] entries
43
+ * ===========================================================================
44
+ */
45
+ ;
46
+ /**
47
+ * ===========================================================================
48
+ * Relation types
49
+ * ===========================================================================
50
+ */
51
+ ;
52
+ /**
53
+ * ===========================================================================
54
+ * Blueprint table and definition
55
+ * ===========================================================================
56
+ */
57
+ ;
@@ -5,10 +5,16 @@
5
5
  * suitable for use as separate pgpm migration files.
6
6
  *
7
7
  * Usage:
8
- * npx ts-node src/codegen/generate-seed.ts [--outdir <dir>] [--single]
8
+ * npx ts-node src/codegen/generate-seed.ts [--outdir <dir>] [--single] [--pgpm <dir>]
9
9
  *
10
10
  * --outdir <dir> Directory to write individual SQL files (default: stdout)
11
11
  * --single Emit a single combined seed.sql instead of per-node files
12
+ * --pgpm <dir> Generate deploy/revert/verify files in pgpm package layout.
13
+ * <dir> is the pgpm package root (e.g. packages/metaschema).
14
+ * Files are written relative to this root at:
15
+ * deploy/schemas/metaschema_public/tables/node_type_registry/data/seed.sql
16
+ * revert/schemas/metaschema_public/tables/node_type_registry/data/seed.sql
17
+ * verify/schemas/metaschema_public/tables/node_type_registry/data/seed.sql
12
18
  *
13
19
  * Examples:
14
20
  * # Print all INSERT statements to stdout
@@ -19,5 +25,8 @@
19
25
  *
20
26
  * # Generate a single combined seed file
21
27
  * npx ts-node src/codegen/generate-seed.ts --single --outdir ./deploy
28
+ *
29
+ * # Generate pgpm deploy/revert/verify files
30
+ * npx ts-node src/codegen/generate-seed.ts --pgpm ../../constructive-db/packages/metaschema
22
31
  */
23
32
  export {};
@@ -6,10 +6,16 @@
6
6
  * suitable for use as separate pgpm migration files.
7
7
  *
8
8
  * Usage:
9
- * npx ts-node src/codegen/generate-seed.ts [--outdir <dir>] [--single]
9
+ * npx ts-node src/codegen/generate-seed.ts [--outdir <dir>] [--single] [--pgpm <dir>]
10
10
  *
11
11
  * --outdir <dir> Directory to write individual SQL files (default: stdout)
12
12
  * --single Emit a single combined seed.sql instead of per-node files
13
+ * --pgpm <dir> Generate deploy/revert/verify files in pgpm package layout.
14
+ * <dir> is the pgpm package root (e.g. packages/metaschema).
15
+ * Files are written relative to this root at:
16
+ * deploy/schemas/metaschema_public/tables/node_type_registry/data/seed.sql
17
+ * revert/schemas/metaschema_public/tables/node_type_registry/data/seed.sql
18
+ * verify/schemas/metaschema_public/tables/node_type_registry/data/seed.sql
13
19
  *
14
20
  * Examples:
15
21
  * # Print all INSERT statements to stdout
@@ -20,6 +26,9 @@
20
26
  *
21
27
  * # Generate a single combined seed file
22
28
  * npx ts-node src/codegen/generate-seed.ts --single --outdir ./deploy
29
+ *
30
+ * # Generate pgpm deploy/revert/verify files
31
+ * npx ts-node src/codegen/generate-seed.ts --pgpm ../../constructive-db/packages/metaschema
23
32
  */
24
33
  Object.defineProperty(exports, "__esModule", { value: true });
25
34
  const fs_1 = require("fs");
@@ -28,6 +37,10 @@ const pgsql_deparser_1 = require("pgsql-deparser");
28
37
  const utils_1 = require("@pgsql/utils");
29
38
  const index_1 = require("../index");
30
39
  // ---------------------------------------------------------------------------
40
+ // Constants
41
+ // ---------------------------------------------------------------------------
42
+ const MIGRATION_PATH = 'schemas/metaschema_public/tables/node_type_registry/data/seed';
43
+ // ---------------------------------------------------------------------------
31
44
  // AST helpers
32
45
  // ---------------------------------------------------------------------------
33
46
  const astr = (val) => utils_1.nodes.aConst({ sval: utils_1.ast.string({ sval: val }) });
@@ -109,17 +122,101 @@ function buildInsertStmt(nt) {
109
122
  };
110
123
  }
111
124
  // ---------------------------------------------------------------------------
125
+ // pgpm file generators
126
+ // ---------------------------------------------------------------------------
127
+ const GENERATED_HEADER = [
128
+ '-- GENERATED FILE — DO NOT EDIT',
129
+ '--',
130
+ '-- Regenerate with:',
131
+ '-- cd graphile/node-type-registry && pnpm generate:seed --pgpm ../../constructive-db/packages/metaschema',
132
+ '--',
133
+ '',
134
+ ].join('\n');
135
+ async function buildDeploySql() {
136
+ const header = [
137
+ `-- Deploy ${MIGRATION_PATH} to pg`,
138
+ '',
139
+ GENERATED_HEADER,
140
+ '-- requires: schemas/metaschema_public/tables/node_type_registry/table',
141
+ '',
142
+ ].join('\n');
143
+ const stmts = index_1.allNodeTypes.map(buildInsertStmt);
144
+ const body = await (0, pgsql_deparser_1.deparse)(stmts);
145
+ return header + body + '\n';
146
+ }
147
+ function buildRevertSql() {
148
+ const names = index_1.allNodeTypes.map((nt) => ` '${nt.name}'`);
149
+ // Wrap names at ~4 per line for readability
150
+ const chunks = [];
151
+ for (let i = 0; i < names.length; i += 4) {
152
+ chunks.push(names.slice(i, i + 4).join(', '));
153
+ }
154
+ return [
155
+ `-- Revert ${MIGRATION_PATH} from pg`,
156
+ '',
157
+ GENERATED_HEADER,
158
+ 'DELETE FROM metaschema_public.node_type_registry',
159
+ 'WHERE name IN (',
160
+ chunks.join(',\n'),
161
+ ');',
162
+ '',
163
+ ].join('\n');
164
+ }
165
+ function buildVerifySql() {
166
+ // Pick one representative from each category
167
+ const categories = new Map();
168
+ for (const nt of index_1.allNodeTypes) {
169
+ if (!categories.has(nt.category)) {
170
+ categories.set(nt.category, nt);
171
+ }
172
+ }
173
+ const checks = Array.from(categories.values()).map((nt) => `SELECT 1 FROM metaschema_public.node_type_registry WHERE name = '${nt.name}';`);
174
+ return [
175
+ `-- Verify ${MIGRATION_PATH} on pg`,
176
+ '',
177
+ GENERATED_HEADER,
178
+ ...checks,
179
+ '',
180
+ ].join('\n');
181
+ }
182
+ // ---------------------------------------------------------------------------
183
+ // File writer helper
184
+ // ---------------------------------------------------------------------------
185
+ function writeFile(filePath, content) {
186
+ const dir = (0, path_1.join)(filePath, '..');
187
+ if (!(0, fs_1.existsSync)(dir))
188
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
189
+ (0, fs_1.writeFileSync)(filePath, content);
190
+ }
191
+ // ---------------------------------------------------------------------------
112
192
  // CLI
113
193
  // ---------------------------------------------------------------------------
114
- function main() {
194
+ async function main() {
115
195
  const args = process.argv.slice(2);
116
196
  const outdirIdx = args.indexOf('--outdir');
117
197
  const outdir = outdirIdx !== -1 ? args[outdirIdx + 1] : undefined;
118
198
  const single = args.includes('--single');
199
+ const pgpmIdx = args.indexOf('--pgpm');
200
+ const pgpmRoot = pgpmIdx !== -1 ? args[pgpmIdx + 1] : undefined;
201
+ // --pgpm mode: generate deploy/revert/verify in pgpm package layout
202
+ if (pgpmRoot) {
203
+ const relPath = 'schemas/metaschema_public/tables/node_type_registry/data/seed.sql';
204
+ const deployPath = (0, path_1.join)(pgpmRoot, 'deploy', relPath);
205
+ const revertPath = (0, path_1.join)(pgpmRoot, 'revert', relPath);
206
+ const verifyPath = (0, path_1.join)(pgpmRoot, 'verify', relPath);
207
+ writeFile(deployPath, await buildDeploySql());
208
+ writeFile(revertPath, buildRevertSql());
209
+ writeFile(verifyPath, buildVerifySql());
210
+ console.log(`Wrote ${index_1.allNodeTypes.length} node types to pgpm layout:`);
211
+ console.log(` deploy: ${deployPath}`);
212
+ console.log(` revert: ${revertPath}`);
213
+ console.log(` verify: ${verifyPath}`);
214
+ return;
215
+ }
119
216
  if (single) {
120
217
  // Emit all INSERT statements as a single SQL string
121
218
  const stmts = index_1.allNodeTypes.map(buildInsertStmt);
122
- const sql = (0, pgsql_deparser_1.deparse)(stmts);
219
+ const sql = await (0, pgsql_deparser_1.deparse)(stmts);
123
220
  if (outdir) {
124
221
  if (!(0, fs_1.existsSync)(outdir))
125
222
  (0, fs_1.mkdirSync)(outdir, { recursive: true });
@@ -132,10 +229,10 @@ function main() {
132
229
  return;
133
230
  }
134
231
  // Emit individual SQL files per node type
135
- const stmts = index_1.allNodeTypes.map((nt) => ({
232
+ const stmts = await Promise.all(index_1.allNodeTypes.map(async (nt) => ({
136
233
  nt,
137
- sql: (0, pgsql_deparser_1.deparse)([buildInsertStmt(nt)]),
138
- }));
234
+ sql: await (0, pgsql_deparser_1.deparse)([buildInsertStmt(nt)]),
235
+ })));
139
236
  if (outdir) {
140
237
  if (!(0, fs_1.existsSync)(outdir))
141
238
  (0, fs_1.mkdirSync)(outdir, { recursive: true });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Generate TypeScript types for blueprint definitions from node type registry.
3
+ *
4
+ * Uses @babel/types AST nodes + schema-typescript for JSON Schema -> TS
5
+ * conversion. Produces a `blueprint-types.generated.ts` file with:
6
+ *
7
+ * - Per-node-type parameter interfaces (via schema-typescript)
8
+ * - BlueprintNode -- discriminated union of all non-relation node types
9
+ * - BlueprintRelation -- typed relation entries with $type, source_ref, target_ref
10
+ * - BlueprintTable, BlueprintField, BlueprintPolicy, BlueprintIndex, etc.
11
+ * - BlueprintDefinition -- the top-level type matching the JSONB shape
12
+ *
13
+ * These types are client-side only -- they provide autocomplete and type safety
14
+ * when building blueprint JSON. The API itself accepts plain JSONB.
15
+ *
16
+ * Usage:
17
+ * npx ts-node src/codegen/generate-types.ts [--outdir <dir>]
18
+ */
19
+ export {};
@@ -0,0 +1,373 @@
1
+ "use strict";
2
+ /**
3
+ * Generate TypeScript types for blueprint definitions from node type registry.
4
+ *
5
+ * Uses @babel/types AST nodes + schema-typescript for JSON Schema -> TS
6
+ * conversion. Produces a `blueprint-types.generated.ts` file with:
7
+ *
8
+ * - Per-node-type parameter interfaces (via schema-typescript)
9
+ * - BlueprintNode -- discriminated union of all non-relation node types
10
+ * - BlueprintRelation -- typed relation entries with $type, source_ref, target_ref
11
+ * - BlueprintTable, BlueprintField, BlueprintPolicy, BlueprintIndex, etc.
12
+ * - BlueprintDefinition -- the top-level type matching the JSONB shape
13
+ *
14
+ * These types are client-side only -- they provide autocomplete and type safety
15
+ * when building blueprint JSON. The API itself accepts plain JSONB.
16
+ *
17
+ * Usage:
18
+ * npx ts-node src/codegen/generate-types.ts [--outdir <dir>]
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
55
+ const generate = require('@babel/generator').default ?? require('@babel/generator');
56
+ const t = __importStar(require("@babel/types"));
57
+ const fs_1 = require("fs");
58
+ const path_1 = require("path");
59
+ const schema_typescript_1 = require("schema-typescript");
60
+ const index_1 = require("../index");
61
+ // ---------------------------------------------------------------------------
62
+ // Helpers
63
+ // ---------------------------------------------------------------------------
64
+ /** Attach a JSDoc-style leading comment to an AST node. */
65
+ function addJSDoc(node, description) {
66
+ node.leadingComments = [
67
+ { type: 'CommentBlock', value: `* ${description} ` },
68
+ ];
69
+ return node;
70
+ }
71
+ /** Create an optional TSPropertySignature. */
72
+ function optionalProp(name, typeAnnotation) {
73
+ const prop = t.tsPropertySignature(t.identifier(name), t.tsTypeAnnotation(typeAnnotation));
74
+ prop.optional = true;
75
+ return prop;
76
+ }
77
+ /** Create a required TSPropertySignature. */
78
+ function requiredProp(name, typeAnnotation) {
79
+ return t.tsPropertySignature(t.identifier(name), t.tsTypeAnnotation(typeAnnotation));
80
+ }
81
+ /** Create an exported interface declaration. */
82
+ function exportInterface(name, members) {
83
+ return t.exportNamedDeclaration(t.tsInterfaceDeclaration(t.identifier(name), null, [], t.tsInterfaceBody(members)));
84
+ }
85
+ /** Create an exported type alias. */
86
+ function exportTypeAlias(name, type) {
87
+ return t.exportNamedDeclaration(t.tsTypeAliasDeclaration(t.identifier(name), null, type));
88
+ }
89
+ /** Create a string literal type. */
90
+ function strLit(value) {
91
+ return t.tsLiteralType(t.stringLiteral(value));
92
+ }
93
+ /** Create a union of string literals. */
94
+ function strUnion(values) {
95
+ if (values.length === 1)
96
+ return strLit(values[0]);
97
+ return t.tsUnionType(values.map(strLit));
98
+ }
99
+ /** Create Record<K, V> type reference. */
100
+ function recordType(keyType, valueType) {
101
+ return t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([keyType, valueType]));
102
+ }
103
+ /** Create Partial<T> type reference. */
104
+ function partialOf(inner) {
105
+ return t.tsTypeReference(t.identifier('Partial'), t.tsTypeParameterInstantiation([inner]));
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // Schema sanitizer — ensures array types have an items spec (schema-typescript
109
+ // throws without one). Numeric/boolean enum handling is fixed upstream as of
110
+ // schema-typescript@0.14.2.
111
+ // ---------------------------------------------------------------------------
112
+ function ensureArrayItems(schema) {
113
+ const out = { ...schema };
114
+ // Ensure arrays have an items spec (schema-typescript throws without one)
115
+ if (out.type === 'array' && !out.items) {
116
+ out.items = { type: 'string' };
117
+ }
118
+ // Recurse into properties
119
+ if (out.properties && typeof out.properties === 'object') {
120
+ const props = {};
121
+ for (const [k, v] of Object.entries(out.properties)) {
122
+ props[k] = v && typeof v === 'object' ? ensureArrayItems(v) : v;
123
+ }
124
+ out.properties = props;
125
+ }
126
+ // Recurse into items
127
+ if (out.items && typeof out.items === 'object') {
128
+ out.items = ensureArrayItems(out.items);
129
+ }
130
+ // Recurse into composition keywords
131
+ for (const keyword of ['oneOf', 'anyOf', 'allOf']) {
132
+ if (Array.isArray(out[keyword])) {
133
+ out[keyword] = out[keyword].map((s) => s && typeof s === 'object' ? ensureArrayItems(s) : s);
134
+ }
135
+ }
136
+ return out;
137
+ }
138
+ // ---------------------------------------------------------------------------
139
+ // Generate per-node-type parameter interfaces via schema-typescript
140
+ // ---------------------------------------------------------------------------
141
+ function generateParamsInterfaces(nodeTypes) {
142
+ const results = [];
143
+ for (const nt of nodeTypes) {
144
+ const schema = nt.parameter_schema;
145
+ const typeName = `${nt.name}Params`;
146
+ const sanitized = ensureArrayItems({ ...schema, title: typeName });
147
+ const astNodes = (0, schema_typescript_1.generateTypeScriptTypes)(sanitized, {
148
+ includePropertyComments: true,
149
+ includeTypeComments: false,
150
+ strictTypeSafety: true,
151
+ });
152
+ if (astNodes.length > 0) {
153
+ // The last node is the main interface for the title
154
+ const mainNode = astNodes[astNodes.length - 1];
155
+ addJSDoc(mainNode, nt.description);
156
+ for (const node of astNodes) {
157
+ results.push(node);
158
+ }
159
+ }
160
+ else {
161
+ // Fallback: empty interface for node types with no properties
162
+ results.push(addJSDoc(exportInterface(typeName, []), nt.description));
163
+ }
164
+ }
165
+ return results;
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Static structural types (BlueprintField, BlueprintPolicy, etc.)
169
+ // ---------------------------------------------------------------------------
170
+ function buildBlueprintField() {
171
+ return addJSDoc(exportInterface('BlueprintField', [
172
+ addJSDoc(requiredProp('name', t.tsStringKeyword()), 'The column name.'),
173
+ addJSDoc(requiredProp('type', t.tsStringKeyword()), 'The PostgreSQL type (e.g., "text", "integer", "boolean", "uuid").'),
174
+ addJSDoc(optionalProp('is_not_null', t.tsBooleanKeyword()), 'Whether the column has a NOT NULL constraint.'),
175
+ addJSDoc(optionalProp('default_value', t.tsStringKeyword()), 'SQL default value expression (e.g., "true", "now()").'),
176
+ addJSDoc(optionalProp('description', t.tsStringKeyword()), 'Comment/description for this field.'),
177
+ ]), 'A custom field (column) to add to a blueprint table.');
178
+ }
179
+ function buildBlueprintPolicy(authzNodes) {
180
+ const policyTypeAnnotation = authzNodes.length > 0
181
+ ? strUnion(authzNodes.map((nt) => nt.name))
182
+ : t.tsStringKeyword();
183
+ return addJSDoc(exportInterface('BlueprintPolicy', [
184
+ addJSDoc(requiredProp('$type', policyTypeAnnotation), 'Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll").'),
185
+ addJSDoc(optionalProp('policy_role', t.tsStringKeyword()), 'Role for this policy. Defaults to "authenticated".'),
186
+ addJSDoc(optionalProp('permissive', t.tsBooleanKeyword()), 'Whether this policy is permissive (true) or restrictive (false).'),
187
+ addJSDoc(optionalProp('policy_name', t.tsStringKeyword()), 'Optional custom name for this policy.'),
188
+ addJSDoc(optionalProp('privileges', t.tsArrayType(t.tsStringKeyword())), 'Privileges this policy applies to.'),
189
+ addJSDoc(optionalProp('data', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Policy-specific data (structure varies by policy type).'),
190
+ ]), 'An RLS policy entry for a blueprint table.');
191
+ }
192
+ function buildBlueprintFtsSource() {
193
+ return addJSDoc(exportInterface('BlueprintFtsSource', [
194
+ addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Column name of the source field.'),
195
+ addJSDoc(requiredProp('weight', t.tsStringKeyword()), 'TSVector weight: "A", "B", "C", or "D".'),
196
+ addJSDoc(optionalProp('lang', t.tsStringKeyword()), 'Language for text search. Defaults to "english".'),
197
+ ]), 'A source field contributing to a full-text search tsvector column.');
198
+ }
199
+ function buildBlueprintFullTextSearch() {
200
+ return addJSDoc(exportInterface('BlueprintFullTextSearch', [
201
+ addJSDoc(requiredProp('table_ref', t.tsStringKeyword()), 'Reference key of the table this full-text search belongs to.'),
202
+ addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Name of the tsvector field on the table.'),
203
+ addJSDoc(requiredProp('sources', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFtsSource')))), 'Source fields that feed into this tsvector.'),
204
+ ]), 'A full-text search configuration for a blueprint table.');
205
+ }
206
+ function buildBlueprintIndex() {
207
+ return addJSDoc(exportInterface('BlueprintIndex', [
208
+ addJSDoc(requiredProp('table_ref', t.tsStringKeyword()), 'Reference key of the table this index belongs to.'),
209
+ addJSDoc(optionalProp('column', t.tsStringKeyword()), 'Single column name for the index. Mutually exclusive with "columns".'),
210
+ addJSDoc(optionalProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Array of column names for a multi-column index. Mutually exclusive with "column".'),
211
+ addJSDoc(requiredProp('access_method', t.tsStringKeyword()), 'Index access method (e.g., "BTREE", "GIN", "GIST", "HNSW", "BM25").'),
212
+ addJSDoc(optionalProp('is_unique', t.tsBooleanKeyword()), 'Whether this is a unique index.'),
213
+ addJSDoc(optionalProp('name', t.tsStringKeyword()), 'Optional custom name for the index.'),
214
+ addJSDoc(optionalProp('op_classes', t.tsArrayType(t.tsStringKeyword())), 'Operator classes for the index columns.'),
215
+ addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.'),
216
+ ]), 'An index definition within a blueprint.');
217
+ }
218
+ // ---------------------------------------------------------------------------
219
+ // Node type discriminated unions
220
+ // ---------------------------------------------------------------------------
221
+ function buildNodeTypes(dataNodes) {
222
+ const results = [];
223
+ // BlueprintNodeShorthand -- union of string literals
224
+ results.push(addJSDoc(exportTypeAlias('BlueprintNodeShorthand', strUnion(dataNodes.map((nt) => nt.name))), 'String shorthand -- just the node type name.'));
225
+ // BlueprintNodeObject -- discriminated union of { $type, data } objects
226
+ const objectMembers = dataNodes.map((nt) => {
227
+ const hasParams = nt.parameter_schema.properties &&
228
+ Object.keys(nt.parameter_schema.properties).length > 0;
229
+ const dataType = hasParams
230
+ ? t.tsTypeReference(t.identifier(`${nt.name}Params`))
231
+ : recordType(t.tsStringKeyword(), t.tsNeverKeyword());
232
+ const dataProp = hasParams
233
+ ? requiredProp('data', dataType)
234
+ : optionalProp('data', dataType);
235
+ return t.tsTypeLiteral([requiredProp('$type', strLit(nt.name)), dataProp]);
236
+ });
237
+ results.push(addJSDoc(exportTypeAlias('BlueprintNodeObject', t.tsUnionType(objectMembers)), 'Object form -- { $type, data } with typed parameters.'));
238
+ // BlueprintNode -- shorthand | object
239
+ results.push(addJSDoc(exportTypeAlias('BlueprintNode', t.tsUnionType([
240
+ t.tsTypeReference(t.identifier('BlueprintNodeShorthand')),
241
+ t.tsTypeReference(t.identifier('BlueprintNodeObject')),
242
+ ])), 'A node entry in a blueprint table. Either a string shorthand or a typed object.'));
243
+ return results;
244
+ }
245
+ // ---------------------------------------------------------------------------
246
+ // Relation types
247
+ // ---------------------------------------------------------------------------
248
+ function buildRelationTypes(relationNodes) {
249
+ const relationMembers = relationNodes.map((nt) => {
250
+ const baseType = t.tsTypeLiteral([
251
+ requiredProp('$type', strLit(nt.name)),
252
+ requiredProp('source_ref', t.tsStringKeyword()),
253
+ requiredProp('target_ref', t.tsStringKeyword()),
254
+ ]);
255
+ return t.tsIntersectionType([
256
+ baseType,
257
+ partialOf(t.tsTypeReference(t.identifier(`${nt.name}Params`))),
258
+ ]);
259
+ });
260
+ return [
261
+ addJSDoc(exportTypeAlias('BlueprintRelation', t.tsUnionType(relationMembers)), 'A relation entry in a blueprint definition.'),
262
+ ];
263
+ }
264
+ // ---------------------------------------------------------------------------
265
+ // BlueprintTable and BlueprintDefinition
266
+ // ---------------------------------------------------------------------------
267
+ function buildBlueprintTable() {
268
+ return addJSDoc(exportInterface('BlueprintTable', [
269
+ addJSDoc(requiredProp('ref', t.tsStringKeyword()), 'Local reference key for this table (used by relations, indexes, fts).'),
270
+ addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'The PostgreSQL table name to create.'),
271
+ addJSDoc(requiredProp('nodes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintNode')))), "Array of node type entries that define the table's behavior."),
272
+ addJSDoc(optionalProp('fields', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintField')))), 'Custom fields (columns) to add to the table.'),
273
+ addJSDoc(optionalProp('policies', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy')))), 'RLS policies for this table.'),
274
+ addJSDoc(optionalProp('grant_roles', t.tsArrayType(t.tsStringKeyword())), 'Database roles to grant privileges to. Defaults to ["authenticated"].'),
275
+ addJSDoc(optionalProp('grants', t.tsArrayType(t.tsUnknownKeyword())), 'Privilege grants as [verb, column] tuples or objects.'),
276
+ addJSDoc(optionalProp('use_rls', t.tsBooleanKeyword()), 'Whether to enable RLS on this table. Defaults to true.'),
277
+ ]), 'A table definition within a blueprint.');
278
+ }
279
+ function buildBlueprintDefinition() {
280
+ return addJSDoc(exportInterface('BlueprintDefinition', [
281
+ addJSDoc(requiredProp('tables', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTable')))), 'Tables to create.'),
282
+ addJSDoc(optionalProp('relations', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintRelation')))), 'Relations between tables.'),
283
+ addJSDoc(optionalProp('indexes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintIndex')))), 'Indexes on table columns.'),
284
+ addJSDoc(optionalProp('full_text_searches', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFullTextSearch')))), 'Full-text search configurations.'),
285
+ ]), 'The complete blueprint definition -- the JSONB shape accepted by construct_blueprint().');
286
+ }
287
+ // ---------------------------------------------------------------------------
288
+ // Section comment helper
289
+ // ---------------------------------------------------------------------------
290
+ function sectionComment(title) {
291
+ const empty = t.emptyStatement();
292
+ empty.leadingComments = [
293
+ {
294
+ type: 'CommentBlock',
295
+ value: `*\n * ===========================================================================\n * ${title}\n * ===========================================================================\n `,
296
+ },
297
+ ];
298
+ return empty;
299
+ }
300
+ // ---------------------------------------------------------------------------
301
+ // Main generator
302
+ // ---------------------------------------------------------------------------
303
+ function buildProgram() {
304
+ const statements = [];
305
+ // Group node types by category
306
+ const categories = new Map();
307
+ for (const nt of index_1.allNodeTypes) {
308
+ const list = categories.get(nt.category) ?? [];
309
+ list.push(nt);
310
+ categories.set(nt.category, list);
311
+ }
312
+ const dataNodes = index_1.allNodeTypes.filter((nt) => nt.category !== 'relation' && nt.category !== 'view');
313
+ const relationNodes = index_1.allNodeTypes.filter((nt) => nt.category === 'relation');
314
+ const authzNodes = index_1.allNodeTypes.filter((nt) => nt.category === 'authz');
315
+ // -- Parameter interfaces grouped by category --
316
+ const categoryOrder = ['data', 'authz', 'relation', 'view'];
317
+ for (const cat of categoryOrder) {
318
+ const nts = categories.get(cat);
319
+ if (!nts || nts.length === 0)
320
+ continue;
321
+ statements.push(sectionComment(`${cat.charAt(0).toUpperCase() + cat.slice(1)} node type parameters`));
322
+ statements.push(...generateParamsInterfaces(nts));
323
+ }
324
+ // -- Static structural types --
325
+ statements.push(sectionComment('Static structural types'));
326
+ statements.push(buildBlueprintField());
327
+ statements.push(buildBlueprintPolicy(authzNodes));
328
+ statements.push(buildBlueprintFtsSource());
329
+ statements.push(buildBlueprintFullTextSearch());
330
+ statements.push(buildBlueprintIndex());
331
+ // -- Node types discriminated union --
332
+ statements.push(sectionComment('Node types -- discriminated union for nodes[] entries'));
333
+ statements.push(...buildNodeTypes(dataNodes));
334
+ // -- Relation types --
335
+ statements.push(sectionComment('Relation types'));
336
+ statements.push(...buildRelationTypes(relationNodes));
337
+ // -- Blueprint table and definition --
338
+ statements.push(sectionComment('Blueprint table and definition'));
339
+ statements.push(buildBlueprintTable());
340
+ statements.push(buildBlueprintDefinition());
341
+ // Build the full AST and render to code
342
+ const program = t.program(statements);
343
+ const file = t.file(program);
344
+ const header = [
345
+ '// GENERATED FILE \u2014 DO NOT EDIT',
346
+ '//',
347
+ '// Regenerate with:',
348
+ '// cd graphile/node-type-registry && pnpm generate:types',
349
+ '//',
350
+ '// These types match the JSONB shape expected by construct_blueprint().',
351
+ '// All field names are snake_case to match the SQL convention.',
352
+ '',
353
+ '',
354
+ ].join('\n');
355
+ const output = generate(file, { comments: true });
356
+ return header + output.code + '\n';
357
+ }
358
+ // ---------------------------------------------------------------------------
359
+ // CLI
360
+ // ---------------------------------------------------------------------------
361
+ function main() {
362
+ const args = process.argv.slice(2);
363
+ const outdirIdx = args.indexOf('--outdir');
364
+ const outdir = outdirIdx !== -1 ? args[outdirIdx + 1] : (0, path_1.join)(__dirname, '..');
365
+ const content = buildProgram();
366
+ const filename = 'blueprint-types.generated.ts';
367
+ const filepath = (0, path_1.join)(outdir, filename);
368
+ if (!(0, fs_1.existsSync)(outdir))
369
+ (0, fs_1.mkdirSync)(outdir, { recursive: true });
370
+ (0, fs_1.writeFileSync)(filepath, content);
371
+ console.log(`Generated ${filepath} (${index_1.allNodeTypes.length} node types)`);
372
+ }
373
+ main();