node-type-registry 0.7.1 → 0.8.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 +10 -5
- package/blueprint-types.generated.d.ts +86 -22
- package/blueprint-types.generated.js +1 -1
- package/codegen/generate-seed.js +1 -1
- package/codegen/generate-types.d.ts +1 -1
- package/codegen/generate-types.js +62 -24
- package/esm/blueprint-types.generated.d.ts +86 -22
- package/esm/blueprint-types.generated.js +1 -1
- package/esm/codegen/generate-seed.js +1 -1
- package/esm/codegen/generate-types.d.ts +1 -1
- package/esm/codegen/generate-types.js +62 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,7 +31,6 @@ import type {
|
|
|
31
31
|
const definition: BlueprintDefinition = {
|
|
32
32
|
tables: [
|
|
33
33
|
{
|
|
34
|
-
ref: 'tasks',
|
|
35
34
|
table_name: 'tasks',
|
|
36
35
|
nodes: [
|
|
37
36
|
'DataId',
|
|
@@ -48,11 +47,17 @@ const definition: BlueprintDefinition = {
|
|
|
48
47
|
relations: [
|
|
49
48
|
{
|
|
50
49
|
$type: 'RelationBelongsTo',
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
source_table: 'tasks',
|
|
51
|
+
target_table: 'projects',
|
|
53
52
|
delete_action: 'c',
|
|
54
53
|
},
|
|
55
54
|
],
|
|
55
|
+
unique_constraints: [
|
|
56
|
+
{
|
|
57
|
+
table_name: 'tasks',
|
|
58
|
+
columns: ['title', 'owner_id'],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
56
61
|
};
|
|
57
62
|
```
|
|
58
63
|
|
|
@@ -61,7 +66,7 @@ const definition: BlueprintDefinition = {
|
|
|
61
66
|
When node type definitions are added or modified, regenerate with:
|
|
62
67
|
|
|
63
68
|
```bash
|
|
64
|
-
cd
|
|
69
|
+
cd graphql/node-type-registry && pnpm generate:types
|
|
65
70
|
```
|
|
66
71
|
|
|
67
72
|
This produces `src/blueprint-types.generated.ts` from the TS node type source of truth.
|
|
@@ -71,7 +76,7 @@ This produces `src/blueprint-types.generated.ts` from the TS node type source of
|
|
|
71
76
|
Generate SQL seed scripts for `node_type_registry` table:
|
|
72
77
|
|
|
73
78
|
```bash
|
|
74
|
-
cd
|
|
79
|
+
cd graphql/node-type-registry && pnpm generate:seed --pgpm ../../constructive-db/packages/metaschema
|
|
75
80
|
```
|
|
76
81
|
|
|
77
82
|
---
|
|
@@ -444,18 +444,18 @@ export interface BlueprintField {
|
|
|
444
444
|
/** Comment/description for this field. */
|
|
445
445
|
description?: string;
|
|
446
446
|
}
|
|
447
|
-
/** An RLS policy entry for a blueprint table. */
|
|
447
|
+
/** An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention. */
|
|
448
448
|
export interface BlueprintPolicy {
|
|
449
449
|
/** Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll"). */
|
|
450
|
-
|
|
450
|
+
$type: "AuthzDirectOwner" | "AuthzDirectOwnerAny" | "AuthzMembership" | "AuthzEntityMembership" | "AuthzRelatedEntityMembership" | "AuthzOrgHierarchy" | "AuthzTemporal" | "AuthzPublishable" | "AuthzMemberList" | "AuthzRelatedMemberList" | "AuthzAllowAll" | "AuthzDenyAll" | "AuthzComposite" | "AuthzPeerOwnership" | "AuthzRelatedPeerOwnership";
|
|
451
|
+
/** Privileges this policy applies to (e.g., ["select"], ["insert", "update", "delete"]). */
|
|
452
|
+
privileges?: string[];
|
|
453
|
+
/** Whether this policy is permissive (true) or restrictive (false). Defaults to true. */
|
|
454
|
+
permissive?: boolean;
|
|
451
455
|
/** Role for this policy. Defaults to "authenticated". */
|
|
452
456
|
policy_role?: string;
|
|
453
|
-
/** Whether this policy is permissive (true) or restrictive (false). */
|
|
454
|
-
permissive?: boolean;
|
|
455
457
|
/** Optional custom name for this policy. */
|
|
456
458
|
policy_name?: string;
|
|
457
|
-
/** Privileges this policy applies to. */
|
|
458
|
-
privileges?: string[];
|
|
459
459
|
/** Policy-specific data (structure varies by policy type). */
|
|
460
460
|
data?: Record<string, unknown>;
|
|
461
461
|
}
|
|
@@ -468,19 +468,49 @@ export interface BlueprintFtsSource {
|
|
|
468
468
|
/** Language for text search. Defaults to "english". */
|
|
469
469
|
lang?: string;
|
|
470
470
|
}
|
|
471
|
-
/** A full-text search configuration for a blueprint table. */
|
|
471
|
+
/** A full-text search configuration for a blueprint table (top-level, requires table_name). */
|
|
472
472
|
export interface BlueprintFullTextSearch {
|
|
473
|
-
/**
|
|
474
|
-
|
|
473
|
+
/** Table name this full-text search belongs to. */
|
|
474
|
+
table_name: string;
|
|
475
|
+
/** Optional schema name for disambiguation (falls back to top-level default). */
|
|
476
|
+
schema_name?: string;
|
|
477
|
+
/** Name of the tsvector field on the table. */
|
|
478
|
+
field: string;
|
|
479
|
+
/** Source fields that feed into this tsvector. */
|
|
480
|
+
sources: BlueprintFtsSource[];
|
|
481
|
+
}
|
|
482
|
+
/** A full-text search configuration nested inside a table definition (table_name not required). */
|
|
483
|
+
export interface BlueprintTableFullTextSearch {
|
|
475
484
|
/** Name of the tsvector field on the table. */
|
|
476
485
|
field: string;
|
|
477
486
|
/** Source fields that feed into this tsvector. */
|
|
478
487
|
sources: BlueprintFtsSource[];
|
|
488
|
+
/** Optional schema name override. */
|
|
489
|
+
schema_name?: string;
|
|
479
490
|
}
|
|
480
|
-
/** An index definition within a blueprint. */
|
|
491
|
+
/** An index definition within a blueprint (top-level, requires table_name). */
|
|
481
492
|
export interface BlueprintIndex {
|
|
482
|
-
/**
|
|
483
|
-
|
|
493
|
+
/** Table name this index belongs to. */
|
|
494
|
+
table_name: string;
|
|
495
|
+
/** Optional schema name for disambiguation (falls back to top-level default). */
|
|
496
|
+
schema_name?: string;
|
|
497
|
+
/** Single column name for the index. */
|
|
498
|
+
column?: string;
|
|
499
|
+
/** Array of column names for a multi-column index. */
|
|
500
|
+
columns?: string[];
|
|
501
|
+
/** Index access method (e.g., "BTREE", "GIN", "GIST", "HNSW", "BM25"). */
|
|
502
|
+
access_method: string;
|
|
503
|
+
/** Whether this is a unique index. */
|
|
504
|
+
is_unique?: boolean;
|
|
505
|
+
/** Optional custom name for the index. */
|
|
506
|
+
name?: string;
|
|
507
|
+
/** Operator classes for the index columns. */
|
|
508
|
+
op_classes?: string[];
|
|
509
|
+
/** Additional index-specific options. */
|
|
510
|
+
options?: Record<string, unknown>;
|
|
511
|
+
}
|
|
512
|
+
/** An index definition nested inside a table definition (table_name not required). */
|
|
513
|
+
export interface BlueprintTableIndex {
|
|
484
514
|
/** Single column name for the index. */
|
|
485
515
|
column?: string;
|
|
486
516
|
/** Array of column names for a multi-column index. */
|
|
@@ -495,6 +525,24 @@ export interface BlueprintIndex {
|
|
|
495
525
|
op_classes?: string[];
|
|
496
526
|
/** Additional index-specific options. */
|
|
497
527
|
options?: Record<string, unknown>;
|
|
528
|
+
/** Optional schema name override. */
|
|
529
|
+
schema_name?: string;
|
|
530
|
+
}
|
|
531
|
+
/** A unique constraint definition within a blueprint (top-level, requires table_name). */
|
|
532
|
+
export interface BlueprintUniqueConstraint {
|
|
533
|
+
/** Table name this unique constraint belongs to. */
|
|
534
|
+
table_name: string;
|
|
535
|
+
/** Optional schema name for disambiguation (falls back to top-level default). */
|
|
536
|
+
schema_name?: string;
|
|
537
|
+
/** Column names that form the unique constraint. */
|
|
538
|
+
columns: string[];
|
|
539
|
+
}
|
|
540
|
+
/** A unique constraint nested inside a table definition (table_name not required). */
|
|
541
|
+
export interface BlueprintTableUniqueConstraint {
|
|
542
|
+
/** Column names that form the unique constraint. */
|
|
543
|
+
columns: string[];
|
|
544
|
+
/** Optional schema name override. */
|
|
545
|
+
schema_name?: string;
|
|
498
546
|
}
|
|
499
547
|
/** String shorthand -- just the node type name. */
|
|
500
548
|
export type BlueprintNodeShorthand = "AuthzDirectOwner" | "AuthzDirectOwnerAny" | "AuthzMembership" | "AuthzEntityMembership" | "AuthzRelatedEntityMembership" | "AuthzOrgHierarchy" | "AuthzTemporal" | "AuthzPublishable" | "AuthzMemberList" | "AuthzRelatedMemberList" | "AuthzAllowAll" | "AuthzDenyAll" | "AuthzComposite" | "AuthzPeerOwnership" | "AuthzRelatedPeerOwnership" | "DataId" | "DataDirectOwner" | "DataEntityMembership" | "DataOwnershipInEntity" | "DataTimestamps" | "DataPeoplestamps" | "DataPublishable" | "DataSoftDelete" | "DataEmbedding" | "DataFullTextSearch" | "DataBm25" | "DataSearch" | "DataPostGIS" | "DataPostGISAggregate" | "DataJobTrigger" | "DataTags" | "DataStatusField" | "DataJsonb" | "DataTrgm" | "DataSlug" | "DataInflection" | "DataOwnedFields" | "DataInheritFromParent" | "DataForceCurrentUser" | "DataImmutableFields" | "TableUserProfiles" | "TableOrganizationSettings" | "TableUserSettings";
|
|
@@ -634,27 +682,35 @@ export type BlueprintNode = BlueprintNodeShorthand | BlueprintNodeObject;
|
|
|
634
682
|
/** A relation entry in a blueprint definition. */
|
|
635
683
|
export type BlueprintRelation = {
|
|
636
684
|
$type: "RelationBelongsTo";
|
|
637
|
-
|
|
638
|
-
|
|
685
|
+
source_table: string;
|
|
686
|
+
target_table: string;
|
|
687
|
+
source_schema_name?: string;
|
|
688
|
+
target_schema_name?: string;
|
|
639
689
|
} & Partial<RelationBelongsToParams> | {
|
|
640
690
|
$type: "RelationHasOne";
|
|
641
|
-
|
|
642
|
-
|
|
691
|
+
source_table: string;
|
|
692
|
+
target_table: string;
|
|
693
|
+
source_schema_name?: string;
|
|
694
|
+
target_schema_name?: string;
|
|
643
695
|
} & Partial<RelationHasOneParams> | {
|
|
644
696
|
$type: "RelationHasMany";
|
|
645
|
-
|
|
646
|
-
|
|
697
|
+
source_table: string;
|
|
698
|
+
target_table: string;
|
|
699
|
+
source_schema_name?: string;
|
|
700
|
+
target_schema_name?: string;
|
|
647
701
|
} & Partial<RelationHasManyParams> | {
|
|
648
702
|
$type: "RelationManyToMany";
|
|
649
|
-
|
|
650
|
-
|
|
703
|
+
source_table: string;
|
|
704
|
+
target_table: string;
|
|
705
|
+
source_schema_name?: string;
|
|
706
|
+
target_schema_name?: string;
|
|
651
707
|
} & Partial<RelationManyToManyParams>;
|
|
652
708
|
/** A table definition within a blueprint. */
|
|
653
709
|
export interface BlueprintTable {
|
|
654
|
-
/** Local reference key for this table (used by relations, indexes, fts). */
|
|
655
|
-
ref: string;
|
|
656
710
|
/** The PostgreSQL table name to create. */
|
|
657
711
|
table_name: string;
|
|
712
|
+
/** Optional schema name (falls back to top-level default). */
|
|
713
|
+
schema_name?: string;
|
|
658
714
|
/** Array of node type entries that define the table's behavior. */
|
|
659
715
|
nodes: BlueprintNode[];
|
|
660
716
|
/** Custom fields (columns) to add to the table. */
|
|
@@ -667,6 +723,12 @@ export interface BlueprintTable {
|
|
|
667
723
|
grants?: unknown[];
|
|
668
724
|
/** Whether to enable RLS on this table. Defaults to true. */
|
|
669
725
|
use_rls?: boolean;
|
|
726
|
+
/** Table-level indexes (table_name inherited from parent). */
|
|
727
|
+
indexes?: BlueprintTableIndex[];
|
|
728
|
+
/** Table-level full-text search configurations (table_name inherited from parent). */
|
|
729
|
+
full_text_searches?: BlueprintTableFullTextSearch[];
|
|
730
|
+
/** Table-level unique constraints (table_name inherited from parent). */
|
|
731
|
+
unique_constraints?: BlueprintTableUniqueConstraint[];
|
|
670
732
|
}
|
|
671
733
|
/** The complete blueprint definition -- the JSONB shape accepted by construct_blueprint(). */
|
|
672
734
|
export interface BlueprintDefinition {
|
|
@@ -678,4 +740,6 @@ export interface BlueprintDefinition {
|
|
|
678
740
|
indexes?: BlueprintIndex[];
|
|
679
741
|
/** Full-text search configurations. */
|
|
680
742
|
full_text_searches?: BlueprintFullTextSearch[];
|
|
743
|
+
/** Unique constraints on table columns. */
|
|
744
|
+
unique_constraints?: BlueprintUniqueConstraint[];
|
|
681
745
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// GENERATED FILE — DO NOT EDIT
|
|
3
3
|
//
|
|
4
4
|
// Regenerate with:
|
|
5
|
-
// cd
|
|
5
|
+
// cd graphql/node-type-registry && pnpm generate:types
|
|
6
6
|
//
|
|
7
7
|
// These types match the JSONB shape expected by construct_blueprint().
|
|
8
8
|
// All field names are snake_case to match the SQL convention.
|
package/codegen/generate-seed.js
CHANGED
|
@@ -128,7 +128,7 @@ const GENERATED_HEADER = [
|
|
|
128
128
|
'-- GENERATED FILE — DO NOT EDIT',
|
|
129
129
|
'--',
|
|
130
130
|
'-- Regenerate with:',
|
|
131
|
-
'-- cd
|
|
131
|
+
'-- cd graphql/node-type-registry && pnpm generate:seed --pgpm ../../constructive-db/packages/metaschema',
|
|
132
132
|
'--',
|
|
133
133
|
'',
|
|
134
134
|
].join('\n');
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - Per-node-type parameter interfaces (via schema-typescript)
|
|
8
8
|
* - BlueprintNode -- discriminated union of all non-relation node types
|
|
9
|
-
* - BlueprintRelation -- typed relation entries with $type,
|
|
9
|
+
* - BlueprintRelation -- typed relation entries with $type, source_table, target_table
|
|
10
10
|
* - BlueprintTable, BlueprintField, BlueprintPolicy, BlueprintIndex, etc.
|
|
11
11
|
* - BlueprintDefinition -- the top-level type matching the JSONB shape
|
|
12
12
|
*
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - Per-node-type parameter interfaces (via schema-typescript)
|
|
9
9
|
* - BlueprintNode -- discriminated union of all non-relation node types
|
|
10
|
-
* - BlueprintRelation -- typed relation entries with $type,
|
|
10
|
+
* - BlueprintRelation -- typed relation entries with $type, source_table, target_table
|
|
11
11
|
* - BlueprintTable, BlueprintField, BlueprintPolicy, BlueprintIndex, etc.
|
|
12
12
|
* - BlueprintDefinition -- the top-level type matching the JSONB shape
|
|
13
13
|
*
|
|
@@ -246,28 +246,22 @@ function buildBlueprintField(meta) {
|
|
|
246
246
|
addJSDoc(optionalProp('description', t.tsStringKeyword()), 'Comment/description for this field.'),
|
|
247
247
|
]), 'A custom field (column) to add to a blueprint table.');
|
|
248
248
|
}
|
|
249
|
-
function buildBlueprintPolicy(authzNodes,
|
|
249
|
+
function buildBlueprintPolicy(authzNodes, _meta) {
|
|
250
|
+
// BlueprintPolicy represents the blueprint JSON shape (not the DB table).
|
|
251
|
+
// The SQL procedure reads $type (not policy_type) from the JSONB:
|
|
252
|
+
// v_policy_type := v_policy_entry->>'$type';
|
|
253
|
+
// So we always use the static definition with $type.
|
|
250
254
|
const policyTypeAnnotation = authzNodes.length > 0
|
|
251
255
|
? strUnion(authzNodes.map((nt) => nt.name))
|
|
252
256
|
: t.tsStringKeyword();
|
|
253
|
-
const table = meta && findTable(meta, 'metaschema_public', 'policy');
|
|
254
|
-
if (table) {
|
|
255
|
-
return deriveInterfaceFromTable(table, 'BlueprintPolicy', 'An RLS policy entry for a blueprint table. Derived from _meta.', {
|
|
256
|
-
// policy_type gets a typed union of known Authz* node names
|
|
257
|
-
policy_type: policyTypeAnnotation,
|
|
258
|
-
// data is untyped JSONB — use Record<string, unknown>
|
|
259
|
-
data: recordType(t.tsStringKeyword(), t.tsUnknownKeyword()),
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
// Static fallback
|
|
263
257
|
return addJSDoc(exportInterface('BlueprintPolicy', [
|
|
264
|
-
addJSDoc(requiredProp('
|
|
258
|
+
addJSDoc(requiredProp('$type', policyTypeAnnotation), 'Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll").'),
|
|
259
|
+
addJSDoc(optionalProp('privileges', t.tsArrayType(t.tsStringKeyword())), 'Privileges this policy applies to (e.g., ["select"], ["insert", "update", "delete"]).'),
|
|
260
|
+
addJSDoc(optionalProp('permissive', t.tsBooleanKeyword()), 'Whether this policy is permissive (true) or restrictive (false). Defaults to true.'),
|
|
265
261
|
addJSDoc(optionalProp('policy_role', t.tsStringKeyword()), 'Role for this policy. Defaults to "authenticated".'),
|
|
266
|
-
addJSDoc(optionalProp('permissive', t.tsBooleanKeyword()), 'Whether this policy is permissive (true) or restrictive (false).'),
|
|
267
262
|
addJSDoc(optionalProp('policy_name', t.tsStringKeyword()), 'Optional custom name for this policy.'),
|
|
268
|
-
addJSDoc(optionalProp('privileges', t.tsArrayType(t.tsStringKeyword())), 'Privileges this policy applies to.'),
|
|
269
263
|
addJSDoc(optionalProp('data', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Policy-specific data (structure varies by policy type).'),
|
|
270
|
-
]), 'An RLS policy entry for a blueprint table.');
|
|
264
|
+
]), 'An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention.');
|
|
271
265
|
}
|
|
272
266
|
function buildBlueprintFtsSource() {
|
|
273
267
|
return addJSDoc(exportInterface('BlueprintFtsSource', [
|
|
@@ -278,10 +272,18 @@ function buildBlueprintFtsSource() {
|
|
|
278
272
|
}
|
|
279
273
|
function buildBlueprintFullTextSearch() {
|
|
280
274
|
return addJSDoc(exportInterface('BlueprintFullTextSearch', [
|
|
281
|
-
addJSDoc(requiredProp('
|
|
275
|
+
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this full-text search belongs to.'),
|
|
276
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
|
|
282
277
|
addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Name of the tsvector field on the table.'),
|
|
283
278
|
addJSDoc(requiredProp('sources', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFtsSource')))), 'Source fields that feed into this tsvector.'),
|
|
284
|
-
]), 'A full-text search configuration for a blueprint table.');
|
|
279
|
+
]), 'A full-text search configuration for a blueprint table (top-level, requires table_name).');
|
|
280
|
+
}
|
|
281
|
+
function buildBlueprintTableFullTextSearch() {
|
|
282
|
+
return addJSDoc(exportInterface('BlueprintTableFullTextSearch', [
|
|
283
|
+
addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Name of the tsvector field on the table.'),
|
|
284
|
+
addJSDoc(requiredProp('sources', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFtsSource')))), 'Source fields that feed into this tsvector.'),
|
|
285
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
|
|
286
|
+
]), 'A full-text search configuration nested inside a table definition (table_name not required).');
|
|
285
287
|
}
|
|
286
288
|
function buildBlueprintIndex(meta) {
|
|
287
289
|
const table = meta && findTable(meta, 'metaschema_public', 'index');
|
|
@@ -295,7 +297,8 @@ function buildBlueprintIndex(meta) {
|
|
|
295
297
|
}
|
|
296
298
|
// Static fallback
|
|
297
299
|
return addJSDoc(exportInterface('BlueprintIndex', [
|
|
298
|
-
addJSDoc(requiredProp('
|
|
300
|
+
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this index belongs to.'),
|
|
301
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
|
|
299
302
|
addJSDoc(optionalProp('column', t.tsStringKeyword()), 'Single column name for the index.'),
|
|
300
303
|
addJSDoc(optionalProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Array of column names for a multi-column index.'),
|
|
301
304
|
addJSDoc(requiredProp('access_method', t.tsStringKeyword()), 'Index access method (e.g., "BTREE", "GIN", "GIST", "HNSW", "BM25").'),
|
|
@@ -303,7 +306,19 @@ function buildBlueprintIndex(meta) {
|
|
|
303
306
|
addJSDoc(optionalProp('name', t.tsStringKeyword()), 'Optional custom name for the index.'),
|
|
304
307
|
addJSDoc(optionalProp('op_classes', t.tsArrayType(t.tsStringKeyword())), 'Operator classes for the index columns.'),
|
|
305
308
|
addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.'),
|
|
306
|
-
]), 'An index definition within a blueprint.');
|
|
309
|
+
]), 'An index definition within a blueprint (top-level, requires table_name).');
|
|
310
|
+
}
|
|
311
|
+
function buildBlueprintTableIndex() {
|
|
312
|
+
return addJSDoc(exportInterface('BlueprintTableIndex', [
|
|
313
|
+
addJSDoc(optionalProp('column', t.tsStringKeyword()), 'Single column name for the index.'),
|
|
314
|
+
addJSDoc(optionalProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Array of column names for a multi-column index.'),
|
|
315
|
+
addJSDoc(requiredProp('access_method', t.tsStringKeyword()), 'Index access method (e.g., "BTREE", "GIN", "GIST", "HNSW", "BM25").'),
|
|
316
|
+
addJSDoc(optionalProp('is_unique', t.tsBooleanKeyword()), 'Whether this is a unique index.'),
|
|
317
|
+
addJSDoc(optionalProp('name', t.tsStringKeyword()), 'Optional custom name for the index.'),
|
|
318
|
+
addJSDoc(optionalProp('op_classes', t.tsArrayType(t.tsStringKeyword())), 'Operator classes for the index columns.'),
|
|
319
|
+
addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.'),
|
|
320
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
|
|
321
|
+
]), 'An index definition nested inside a table definition (table_name not required).');
|
|
307
322
|
}
|
|
308
323
|
// ---------------------------------------------------------------------------
|
|
309
324
|
// Node type discriminated unions
|
|
@@ -339,8 +354,10 @@ function buildRelationTypes(relationNodes) {
|
|
|
339
354
|
const relationMembers = relationNodes.map((nt) => {
|
|
340
355
|
const baseType = t.tsTypeLiteral([
|
|
341
356
|
requiredProp('$type', strLit(nt.name)),
|
|
342
|
-
requiredProp('
|
|
343
|
-
requiredProp('
|
|
357
|
+
requiredProp('source_table', t.tsStringKeyword()),
|
|
358
|
+
requiredProp('target_table', t.tsStringKeyword()),
|
|
359
|
+
optionalProp('source_schema_name', t.tsStringKeyword()),
|
|
360
|
+
optionalProp('target_schema_name', t.tsStringKeyword()),
|
|
344
361
|
]);
|
|
345
362
|
return t.tsIntersectionType([
|
|
346
363
|
baseType,
|
|
@@ -354,16 +371,32 @@ function buildRelationTypes(relationNodes) {
|
|
|
354
371
|
// ---------------------------------------------------------------------------
|
|
355
372
|
// BlueprintTable and BlueprintDefinition
|
|
356
373
|
// ---------------------------------------------------------------------------
|
|
374
|
+
function buildBlueprintUniqueConstraint() {
|
|
375
|
+
return addJSDoc(exportInterface('BlueprintUniqueConstraint', [
|
|
376
|
+
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this unique constraint belongs to.'),
|
|
377
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
|
|
378
|
+
addJSDoc(requiredProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Column names that form the unique constraint.'),
|
|
379
|
+
]), 'A unique constraint definition within a blueprint (top-level, requires table_name).');
|
|
380
|
+
}
|
|
381
|
+
function buildBlueprintTableUniqueConstraint() {
|
|
382
|
+
return addJSDoc(exportInterface('BlueprintTableUniqueConstraint', [
|
|
383
|
+
addJSDoc(requiredProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Column names that form the unique constraint.'),
|
|
384
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
|
|
385
|
+
]), 'A unique constraint nested inside a table definition (table_name not required).');
|
|
386
|
+
}
|
|
357
387
|
function buildBlueprintTable() {
|
|
358
388
|
return addJSDoc(exportInterface('BlueprintTable', [
|
|
359
|
-
addJSDoc(requiredProp('ref', t.tsStringKeyword()), 'Local reference key for this table (used by relations, indexes, fts).'),
|
|
360
389
|
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'The PostgreSQL table name to create.'),
|
|
390
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name (falls back to top-level default).'),
|
|
361
391
|
addJSDoc(requiredProp('nodes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintNode')))), "Array of node type entries that define the table's behavior."),
|
|
362
392
|
addJSDoc(optionalProp('fields', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintField')))), 'Custom fields (columns) to add to the table.'),
|
|
363
393
|
addJSDoc(optionalProp('policies', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy')))), 'RLS policies for this table.'),
|
|
364
394
|
addJSDoc(optionalProp('grant_roles', t.tsArrayType(t.tsStringKeyword())), 'Database roles to grant privileges to. Defaults to ["authenticated"].'),
|
|
365
395
|
addJSDoc(optionalProp('grants', t.tsArrayType(t.tsUnknownKeyword())), 'Privilege grants as [verb, column] tuples or objects. Defaults to empty (no grants — callers must explicitly specify).'),
|
|
366
396
|
addJSDoc(optionalProp('use_rls', t.tsBooleanKeyword()), 'Whether to enable RLS on this table. Defaults to true.'),
|
|
397
|
+
addJSDoc(optionalProp('indexes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableIndex')))), 'Table-level indexes (table_name inherited from parent).'),
|
|
398
|
+
addJSDoc(optionalProp('full_text_searches', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableFullTextSearch')))), 'Table-level full-text search configurations (table_name inherited from parent).'),
|
|
399
|
+
addJSDoc(optionalProp('unique_constraints', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableUniqueConstraint')))), 'Table-level unique constraints (table_name inherited from parent).'),
|
|
367
400
|
]), 'A table definition within a blueprint.');
|
|
368
401
|
}
|
|
369
402
|
function buildBlueprintDefinition() {
|
|
@@ -372,6 +405,7 @@ function buildBlueprintDefinition() {
|
|
|
372
405
|
addJSDoc(optionalProp('relations', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintRelation')))), 'Relations between tables.'),
|
|
373
406
|
addJSDoc(optionalProp('indexes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintIndex')))), 'Indexes on table columns.'),
|
|
374
407
|
addJSDoc(optionalProp('full_text_searches', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFullTextSearch')))), 'Full-text search configurations.'),
|
|
408
|
+
addJSDoc(optionalProp('unique_constraints', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintUniqueConstraint')))), 'Unique constraints on table columns.'),
|
|
375
409
|
]), 'The complete blueprint definition -- the JSONB shape accepted by construct_blueprint().');
|
|
376
410
|
}
|
|
377
411
|
// ---------------------------------------------------------------------------
|
|
@@ -420,7 +454,11 @@ function buildProgram(meta) {
|
|
|
420
454
|
statements.push(buildBlueprintPolicy(authzNodes, meta));
|
|
421
455
|
statements.push(buildBlueprintFtsSource());
|
|
422
456
|
statements.push(buildBlueprintFullTextSearch());
|
|
457
|
+
statements.push(buildBlueprintTableFullTextSearch());
|
|
423
458
|
statements.push(buildBlueprintIndex(meta));
|
|
459
|
+
statements.push(buildBlueprintTableIndex());
|
|
460
|
+
statements.push(buildBlueprintUniqueConstraint());
|
|
461
|
+
statements.push(buildBlueprintTableUniqueConstraint());
|
|
424
462
|
// -- Node types discriminated union --
|
|
425
463
|
statements.push(sectionComment('Node types -- discriminated union for nodes[] entries'));
|
|
426
464
|
statements.push(...buildNodeTypes(dataNodes));
|
|
@@ -438,7 +476,7 @@ function buildProgram(meta) {
|
|
|
438
476
|
'// GENERATED FILE \u2014 DO NOT EDIT',
|
|
439
477
|
'//',
|
|
440
478
|
'// Regenerate with:',
|
|
441
|
-
'// cd
|
|
479
|
+
'// cd graphql/node-type-registry && pnpm generate:types',
|
|
442
480
|
'//',
|
|
443
481
|
'// These types match the JSONB shape expected by construct_blueprint().',
|
|
444
482
|
'// All field names are snake_case to match the SQL convention.',
|
|
@@ -444,18 +444,18 @@ export interface BlueprintField {
|
|
|
444
444
|
/** Comment/description for this field. */
|
|
445
445
|
description?: string;
|
|
446
446
|
}
|
|
447
|
-
/** An RLS policy entry for a blueprint table. */
|
|
447
|
+
/** An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention. */
|
|
448
448
|
export interface BlueprintPolicy {
|
|
449
449
|
/** Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll"). */
|
|
450
|
-
|
|
450
|
+
$type: "AuthzDirectOwner" | "AuthzDirectOwnerAny" | "AuthzMembership" | "AuthzEntityMembership" | "AuthzRelatedEntityMembership" | "AuthzOrgHierarchy" | "AuthzTemporal" | "AuthzPublishable" | "AuthzMemberList" | "AuthzRelatedMemberList" | "AuthzAllowAll" | "AuthzDenyAll" | "AuthzComposite" | "AuthzPeerOwnership" | "AuthzRelatedPeerOwnership";
|
|
451
|
+
/** Privileges this policy applies to (e.g., ["select"], ["insert", "update", "delete"]). */
|
|
452
|
+
privileges?: string[];
|
|
453
|
+
/** Whether this policy is permissive (true) or restrictive (false). Defaults to true. */
|
|
454
|
+
permissive?: boolean;
|
|
451
455
|
/** Role for this policy. Defaults to "authenticated". */
|
|
452
456
|
policy_role?: string;
|
|
453
|
-
/** Whether this policy is permissive (true) or restrictive (false). */
|
|
454
|
-
permissive?: boolean;
|
|
455
457
|
/** Optional custom name for this policy. */
|
|
456
458
|
policy_name?: string;
|
|
457
|
-
/** Privileges this policy applies to. */
|
|
458
|
-
privileges?: string[];
|
|
459
459
|
/** Policy-specific data (structure varies by policy type). */
|
|
460
460
|
data?: Record<string, unknown>;
|
|
461
461
|
}
|
|
@@ -468,19 +468,49 @@ export interface BlueprintFtsSource {
|
|
|
468
468
|
/** Language for text search. Defaults to "english". */
|
|
469
469
|
lang?: string;
|
|
470
470
|
}
|
|
471
|
-
/** A full-text search configuration for a blueprint table. */
|
|
471
|
+
/** A full-text search configuration for a blueprint table (top-level, requires table_name). */
|
|
472
472
|
export interface BlueprintFullTextSearch {
|
|
473
|
-
/**
|
|
474
|
-
|
|
473
|
+
/** Table name this full-text search belongs to. */
|
|
474
|
+
table_name: string;
|
|
475
|
+
/** Optional schema name for disambiguation (falls back to top-level default). */
|
|
476
|
+
schema_name?: string;
|
|
477
|
+
/** Name of the tsvector field on the table. */
|
|
478
|
+
field: string;
|
|
479
|
+
/** Source fields that feed into this tsvector. */
|
|
480
|
+
sources: BlueprintFtsSource[];
|
|
481
|
+
}
|
|
482
|
+
/** A full-text search configuration nested inside a table definition (table_name not required). */
|
|
483
|
+
export interface BlueprintTableFullTextSearch {
|
|
475
484
|
/** Name of the tsvector field on the table. */
|
|
476
485
|
field: string;
|
|
477
486
|
/** Source fields that feed into this tsvector. */
|
|
478
487
|
sources: BlueprintFtsSource[];
|
|
488
|
+
/** Optional schema name override. */
|
|
489
|
+
schema_name?: string;
|
|
479
490
|
}
|
|
480
|
-
/** An index definition within a blueprint. */
|
|
491
|
+
/** An index definition within a blueprint (top-level, requires table_name). */
|
|
481
492
|
export interface BlueprintIndex {
|
|
482
|
-
/**
|
|
483
|
-
|
|
493
|
+
/** Table name this index belongs to. */
|
|
494
|
+
table_name: string;
|
|
495
|
+
/** Optional schema name for disambiguation (falls back to top-level default). */
|
|
496
|
+
schema_name?: string;
|
|
497
|
+
/** Single column name for the index. */
|
|
498
|
+
column?: string;
|
|
499
|
+
/** Array of column names for a multi-column index. */
|
|
500
|
+
columns?: string[];
|
|
501
|
+
/** Index access method (e.g., "BTREE", "GIN", "GIST", "HNSW", "BM25"). */
|
|
502
|
+
access_method: string;
|
|
503
|
+
/** Whether this is a unique index. */
|
|
504
|
+
is_unique?: boolean;
|
|
505
|
+
/** Optional custom name for the index. */
|
|
506
|
+
name?: string;
|
|
507
|
+
/** Operator classes for the index columns. */
|
|
508
|
+
op_classes?: string[];
|
|
509
|
+
/** Additional index-specific options. */
|
|
510
|
+
options?: Record<string, unknown>;
|
|
511
|
+
}
|
|
512
|
+
/** An index definition nested inside a table definition (table_name not required). */
|
|
513
|
+
export interface BlueprintTableIndex {
|
|
484
514
|
/** Single column name for the index. */
|
|
485
515
|
column?: string;
|
|
486
516
|
/** Array of column names for a multi-column index. */
|
|
@@ -495,6 +525,24 @@ export interface BlueprintIndex {
|
|
|
495
525
|
op_classes?: string[];
|
|
496
526
|
/** Additional index-specific options. */
|
|
497
527
|
options?: Record<string, unknown>;
|
|
528
|
+
/** Optional schema name override. */
|
|
529
|
+
schema_name?: string;
|
|
530
|
+
}
|
|
531
|
+
/** A unique constraint definition within a blueprint (top-level, requires table_name). */
|
|
532
|
+
export interface BlueprintUniqueConstraint {
|
|
533
|
+
/** Table name this unique constraint belongs to. */
|
|
534
|
+
table_name: string;
|
|
535
|
+
/** Optional schema name for disambiguation (falls back to top-level default). */
|
|
536
|
+
schema_name?: string;
|
|
537
|
+
/** Column names that form the unique constraint. */
|
|
538
|
+
columns: string[];
|
|
539
|
+
}
|
|
540
|
+
/** A unique constraint nested inside a table definition (table_name not required). */
|
|
541
|
+
export interface BlueprintTableUniqueConstraint {
|
|
542
|
+
/** Column names that form the unique constraint. */
|
|
543
|
+
columns: string[];
|
|
544
|
+
/** Optional schema name override. */
|
|
545
|
+
schema_name?: string;
|
|
498
546
|
}
|
|
499
547
|
/** String shorthand -- just the node type name. */
|
|
500
548
|
export type BlueprintNodeShorthand = "AuthzDirectOwner" | "AuthzDirectOwnerAny" | "AuthzMembership" | "AuthzEntityMembership" | "AuthzRelatedEntityMembership" | "AuthzOrgHierarchy" | "AuthzTemporal" | "AuthzPublishable" | "AuthzMemberList" | "AuthzRelatedMemberList" | "AuthzAllowAll" | "AuthzDenyAll" | "AuthzComposite" | "AuthzPeerOwnership" | "AuthzRelatedPeerOwnership" | "DataId" | "DataDirectOwner" | "DataEntityMembership" | "DataOwnershipInEntity" | "DataTimestamps" | "DataPeoplestamps" | "DataPublishable" | "DataSoftDelete" | "DataEmbedding" | "DataFullTextSearch" | "DataBm25" | "DataSearch" | "DataPostGIS" | "DataPostGISAggregate" | "DataJobTrigger" | "DataTags" | "DataStatusField" | "DataJsonb" | "DataTrgm" | "DataSlug" | "DataInflection" | "DataOwnedFields" | "DataInheritFromParent" | "DataForceCurrentUser" | "DataImmutableFields" | "TableUserProfiles" | "TableOrganizationSettings" | "TableUserSettings";
|
|
@@ -634,27 +682,35 @@ export type BlueprintNode = BlueprintNodeShorthand | BlueprintNodeObject;
|
|
|
634
682
|
/** A relation entry in a blueprint definition. */
|
|
635
683
|
export type BlueprintRelation = {
|
|
636
684
|
$type: "RelationBelongsTo";
|
|
637
|
-
|
|
638
|
-
|
|
685
|
+
source_table: string;
|
|
686
|
+
target_table: string;
|
|
687
|
+
source_schema_name?: string;
|
|
688
|
+
target_schema_name?: string;
|
|
639
689
|
} & Partial<RelationBelongsToParams> | {
|
|
640
690
|
$type: "RelationHasOne";
|
|
641
|
-
|
|
642
|
-
|
|
691
|
+
source_table: string;
|
|
692
|
+
target_table: string;
|
|
693
|
+
source_schema_name?: string;
|
|
694
|
+
target_schema_name?: string;
|
|
643
695
|
} & Partial<RelationHasOneParams> | {
|
|
644
696
|
$type: "RelationHasMany";
|
|
645
|
-
|
|
646
|
-
|
|
697
|
+
source_table: string;
|
|
698
|
+
target_table: string;
|
|
699
|
+
source_schema_name?: string;
|
|
700
|
+
target_schema_name?: string;
|
|
647
701
|
} & Partial<RelationHasManyParams> | {
|
|
648
702
|
$type: "RelationManyToMany";
|
|
649
|
-
|
|
650
|
-
|
|
703
|
+
source_table: string;
|
|
704
|
+
target_table: string;
|
|
705
|
+
source_schema_name?: string;
|
|
706
|
+
target_schema_name?: string;
|
|
651
707
|
} & Partial<RelationManyToManyParams>;
|
|
652
708
|
/** A table definition within a blueprint. */
|
|
653
709
|
export interface BlueprintTable {
|
|
654
|
-
/** Local reference key for this table (used by relations, indexes, fts). */
|
|
655
|
-
ref: string;
|
|
656
710
|
/** The PostgreSQL table name to create. */
|
|
657
711
|
table_name: string;
|
|
712
|
+
/** Optional schema name (falls back to top-level default). */
|
|
713
|
+
schema_name?: string;
|
|
658
714
|
/** Array of node type entries that define the table's behavior. */
|
|
659
715
|
nodes: BlueprintNode[];
|
|
660
716
|
/** Custom fields (columns) to add to the table. */
|
|
@@ -667,6 +723,12 @@ export interface BlueprintTable {
|
|
|
667
723
|
grants?: unknown[];
|
|
668
724
|
/** Whether to enable RLS on this table. Defaults to true. */
|
|
669
725
|
use_rls?: boolean;
|
|
726
|
+
/** Table-level indexes (table_name inherited from parent). */
|
|
727
|
+
indexes?: BlueprintTableIndex[];
|
|
728
|
+
/** Table-level full-text search configurations (table_name inherited from parent). */
|
|
729
|
+
full_text_searches?: BlueprintTableFullTextSearch[];
|
|
730
|
+
/** Table-level unique constraints (table_name inherited from parent). */
|
|
731
|
+
unique_constraints?: BlueprintTableUniqueConstraint[];
|
|
670
732
|
}
|
|
671
733
|
/** The complete blueprint definition -- the JSONB shape accepted by construct_blueprint(). */
|
|
672
734
|
export interface BlueprintDefinition {
|
|
@@ -678,4 +740,6 @@ export interface BlueprintDefinition {
|
|
|
678
740
|
indexes?: BlueprintIndex[];
|
|
679
741
|
/** Full-text search configurations. */
|
|
680
742
|
full_text_searches?: BlueprintFullTextSearch[];
|
|
743
|
+
/** Unique constraints on table columns. */
|
|
744
|
+
unique_constraints?: BlueprintUniqueConstraint[];
|
|
681
745
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GENERATED FILE — DO NOT EDIT
|
|
2
2
|
//
|
|
3
3
|
// Regenerate with:
|
|
4
|
-
// cd
|
|
4
|
+
// cd graphql/node-type-registry && pnpm generate:types
|
|
5
5
|
//
|
|
6
6
|
// These types match the JSONB shape expected by construct_blueprint().
|
|
7
7
|
// All field names are snake_case to match the SQL convention.
|
|
@@ -126,7 +126,7 @@ const GENERATED_HEADER = [
|
|
|
126
126
|
'-- GENERATED FILE — DO NOT EDIT',
|
|
127
127
|
'--',
|
|
128
128
|
'-- Regenerate with:',
|
|
129
|
-
'-- cd
|
|
129
|
+
'-- cd graphql/node-type-registry && pnpm generate:seed --pgpm ../../constructive-db/packages/metaschema',
|
|
130
130
|
'--',
|
|
131
131
|
'',
|
|
132
132
|
].join('\n');
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - Per-node-type parameter interfaces (via schema-typescript)
|
|
8
8
|
* - BlueprintNode -- discriminated union of all non-relation node types
|
|
9
|
-
* - BlueprintRelation -- typed relation entries with $type,
|
|
9
|
+
* - BlueprintRelation -- typed relation entries with $type, source_table, target_table
|
|
10
10
|
* - BlueprintTable, BlueprintField, BlueprintPolicy, BlueprintIndex, etc.
|
|
11
11
|
* - BlueprintDefinition -- the top-level type matching the JSONB shape
|
|
12
12
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - Per-node-type parameter interfaces (via schema-typescript)
|
|
8
8
|
* - BlueprintNode -- discriminated union of all non-relation node types
|
|
9
|
-
* - BlueprintRelation -- typed relation entries with $type,
|
|
9
|
+
* - BlueprintRelation -- typed relation entries with $type, source_table, target_table
|
|
10
10
|
* - BlueprintTable, BlueprintField, BlueprintPolicy, BlueprintIndex, etc.
|
|
11
11
|
* - BlueprintDefinition -- the top-level type matching the JSONB shape
|
|
12
12
|
*
|
|
@@ -211,28 +211,22 @@ function buildBlueprintField(meta) {
|
|
|
211
211
|
addJSDoc(optionalProp('description', t.tsStringKeyword()), 'Comment/description for this field.'),
|
|
212
212
|
]), 'A custom field (column) to add to a blueprint table.');
|
|
213
213
|
}
|
|
214
|
-
function buildBlueprintPolicy(authzNodes,
|
|
214
|
+
function buildBlueprintPolicy(authzNodes, _meta) {
|
|
215
|
+
// BlueprintPolicy represents the blueprint JSON shape (not the DB table).
|
|
216
|
+
// The SQL procedure reads $type (not policy_type) from the JSONB:
|
|
217
|
+
// v_policy_type := v_policy_entry->>'$type';
|
|
218
|
+
// So we always use the static definition with $type.
|
|
215
219
|
const policyTypeAnnotation = authzNodes.length > 0
|
|
216
220
|
? strUnion(authzNodes.map((nt) => nt.name))
|
|
217
221
|
: t.tsStringKeyword();
|
|
218
|
-
const table = meta && findTable(meta, 'metaschema_public', 'policy');
|
|
219
|
-
if (table) {
|
|
220
|
-
return deriveInterfaceFromTable(table, 'BlueprintPolicy', 'An RLS policy entry for a blueprint table. Derived from _meta.', {
|
|
221
|
-
// policy_type gets a typed union of known Authz* node names
|
|
222
|
-
policy_type: policyTypeAnnotation,
|
|
223
|
-
// data is untyped JSONB — use Record<string, unknown>
|
|
224
|
-
data: recordType(t.tsStringKeyword(), t.tsUnknownKeyword()),
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
// Static fallback
|
|
228
222
|
return addJSDoc(exportInterface('BlueprintPolicy', [
|
|
229
|
-
addJSDoc(requiredProp('
|
|
223
|
+
addJSDoc(requiredProp('$type', policyTypeAnnotation), 'Authz* policy type name (e.g., "AuthzDirectOwner", "AuthzAllowAll").'),
|
|
224
|
+
addJSDoc(optionalProp('privileges', t.tsArrayType(t.tsStringKeyword())), 'Privileges this policy applies to (e.g., ["select"], ["insert", "update", "delete"]).'),
|
|
225
|
+
addJSDoc(optionalProp('permissive', t.tsBooleanKeyword()), 'Whether this policy is permissive (true) or restrictive (false). Defaults to true.'),
|
|
230
226
|
addJSDoc(optionalProp('policy_role', t.tsStringKeyword()), 'Role for this policy. Defaults to "authenticated".'),
|
|
231
|
-
addJSDoc(optionalProp('permissive', t.tsBooleanKeyword()), 'Whether this policy is permissive (true) or restrictive (false).'),
|
|
232
227
|
addJSDoc(optionalProp('policy_name', t.tsStringKeyword()), 'Optional custom name for this policy.'),
|
|
233
|
-
addJSDoc(optionalProp('privileges', t.tsArrayType(t.tsStringKeyword())), 'Privileges this policy applies to.'),
|
|
234
228
|
addJSDoc(optionalProp('data', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Policy-specific data (structure varies by policy type).'),
|
|
235
|
-
]), 'An RLS policy entry for a blueprint table.');
|
|
229
|
+
]), 'An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention.');
|
|
236
230
|
}
|
|
237
231
|
function buildBlueprintFtsSource() {
|
|
238
232
|
return addJSDoc(exportInterface('BlueprintFtsSource', [
|
|
@@ -243,10 +237,18 @@ function buildBlueprintFtsSource() {
|
|
|
243
237
|
}
|
|
244
238
|
function buildBlueprintFullTextSearch() {
|
|
245
239
|
return addJSDoc(exportInterface('BlueprintFullTextSearch', [
|
|
246
|
-
addJSDoc(requiredProp('
|
|
240
|
+
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this full-text search belongs to.'),
|
|
241
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
|
|
247
242
|
addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Name of the tsvector field on the table.'),
|
|
248
243
|
addJSDoc(requiredProp('sources', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFtsSource')))), 'Source fields that feed into this tsvector.'),
|
|
249
|
-
]), 'A full-text search configuration for a blueprint table.');
|
|
244
|
+
]), 'A full-text search configuration for a blueprint table (top-level, requires table_name).');
|
|
245
|
+
}
|
|
246
|
+
function buildBlueprintTableFullTextSearch() {
|
|
247
|
+
return addJSDoc(exportInterface('BlueprintTableFullTextSearch', [
|
|
248
|
+
addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Name of the tsvector field on the table.'),
|
|
249
|
+
addJSDoc(requiredProp('sources', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFtsSource')))), 'Source fields that feed into this tsvector.'),
|
|
250
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
|
|
251
|
+
]), 'A full-text search configuration nested inside a table definition (table_name not required).');
|
|
250
252
|
}
|
|
251
253
|
function buildBlueprintIndex(meta) {
|
|
252
254
|
const table = meta && findTable(meta, 'metaschema_public', 'index');
|
|
@@ -260,7 +262,8 @@ function buildBlueprintIndex(meta) {
|
|
|
260
262
|
}
|
|
261
263
|
// Static fallback
|
|
262
264
|
return addJSDoc(exportInterface('BlueprintIndex', [
|
|
263
|
-
addJSDoc(requiredProp('
|
|
265
|
+
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this index belongs to.'),
|
|
266
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
|
|
264
267
|
addJSDoc(optionalProp('column', t.tsStringKeyword()), 'Single column name for the index.'),
|
|
265
268
|
addJSDoc(optionalProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Array of column names for a multi-column index.'),
|
|
266
269
|
addJSDoc(requiredProp('access_method', t.tsStringKeyword()), 'Index access method (e.g., "BTREE", "GIN", "GIST", "HNSW", "BM25").'),
|
|
@@ -268,7 +271,19 @@ function buildBlueprintIndex(meta) {
|
|
|
268
271
|
addJSDoc(optionalProp('name', t.tsStringKeyword()), 'Optional custom name for the index.'),
|
|
269
272
|
addJSDoc(optionalProp('op_classes', t.tsArrayType(t.tsStringKeyword())), 'Operator classes for the index columns.'),
|
|
270
273
|
addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.'),
|
|
271
|
-
]), 'An index definition within a blueprint.');
|
|
274
|
+
]), 'An index definition within a blueprint (top-level, requires table_name).');
|
|
275
|
+
}
|
|
276
|
+
function buildBlueprintTableIndex() {
|
|
277
|
+
return addJSDoc(exportInterface('BlueprintTableIndex', [
|
|
278
|
+
addJSDoc(optionalProp('column', t.tsStringKeyword()), 'Single column name for the index.'),
|
|
279
|
+
addJSDoc(optionalProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Array of column names for a multi-column index.'),
|
|
280
|
+
addJSDoc(requiredProp('access_method', t.tsStringKeyword()), 'Index access method (e.g., "BTREE", "GIN", "GIST", "HNSW", "BM25").'),
|
|
281
|
+
addJSDoc(optionalProp('is_unique', t.tsBooleanKeyword()), 'Whether this is a unique index.'),
|
|
282
|
+
addJSDoc(optionalProp('name', t.tsStringKeyword()), 'Optional custom name for the index.'),
|
|
283
|
+
addJSDoc(optionalProp('op_classes', t.tsArrayType(t.tsStringKeyword())), 'Operator classes for the index columns.'),
|
|
284
|
+
addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.'),
|
|
285
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
|
|
286
|
+
]), 'An index definition nested inside a table definition (table_name not required).');
|
|
272
287
|
}
|
|
273
288
|
// ---------------------------------------------------------------------------
|
|
274
289
|
// Node type discriminated unions
|
|
@@ -304,8 +319,10 @@ function buildRelationTypes(relationNodes) {
|
|
|
304
319
|
const relationMembers = relationNodes.map((nt) => {
|
|
305
320
|
const baseType = t.tsTypeLiteral([
|
|
306
321
|
requiredProp('$type', strLit(nt.name)),
|
|
307
|
-
requiredProp('
|
|
308
|
-
requiredProp('
|
|
322
|
+
requiredProp('source_table', t.tsStringKeyword()),
|
|
323
|
+
requiredProp('target_table', t.tsStringKeyword()),
|
|
324
|
+
optionalProp('source_schema_name', t.tsStringKeyword()),
|
|
325
|
+
optionalProp('target_schema_name', t.tsStringKeyword()),
|
|
309
326
|
]);
|
|
310
327
|
return t.tsIntersectionType([
|
|
311
328
|
baseType,
|
|
@@ -319,16 +336,32 @@ function buildRelationTypes(relationNodes) {
|
|
|
319
336
|
// ---------------------------------------------------------------------------
|
|
320
337
|
// BlueprintTable and BlueprintDefinition
|
|
321
338
|
// ---------------------------------------------------------------------------
|
|
339
|
+
function buildBlueprintUniqueConstraint() {
|
|
340
|
+
return addJSDoc(exportInterface('BlueprintUniqueConstraint', [
|
|
341
|
+
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this unique constraint belongs to.'),
|
|
342
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
|
|
343
|
+
addJSDoc(requiredProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Column names that form the unique constraint.'),
|
|
344
|
+
]), 'A unique constraint definition within a blueprint (top-level, requires table_name).');
|
|
345
|
+
}
|
|
346
|
+
function buildBlueprintTableUniqueConstraint() {
|
|
347
|
+
return addJSDoc(exportInterface('BlueprintTableUniqueConstraint', [
|
|
348
|
+
addJSDoc(requiredProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Column names that form the unique constraint.'),
|
|
349
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
|
|
350
|
+
]), 'A unique constraint nested inside a table definition (table_name not required).');
|
|
351
|
+
}
|
|
322
352
|
function buildBlueprintTable() {
|
|
323
353
|
return addJSDoc(exportInterface('BlueprintTable', [
|
|
324
|
-
addJSDoc(requiredProp('ref', t.tsStringKeyword()), 'Local reference key for this table (used by relations, indexes, fts).'),
|
|
325
354
|
addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'The PostgreSQL table name to create.'),
|
|
355
|
+
addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name (falls back to top-level default).'),
|
|
326
356
|
addJSDoc(requiredProp('nodes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintNode')))), "Array of node type entries that define the table's behavior."),
|
|
327
357
|
addJSDoc(optionalProp('fields', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintField')))), 'Custom fields (columns) to add to the table.'),
|
|
328
358
|
addJSDoc(optionalProp('policies', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy')))), 'RLS policies for this table.'),
|
|
329
359
|
addJSDoc(optionalProp('grant_roles', t.tsArrayType(t.tsStringKeyword())), 'Database roles to grant privileges to. Defaults to ["authenticated"].'),
|
|
330
360
|
addJSDoc(optionalProp('grants', t.tsArrayType(t.tsUnknownKeyword())), 'Privilege grants as [verb, column] tuples or objects. Defaults to empty (no grants — callers must explicitly specify).'),
|
|
331
361
|
addJSDoc(optionalProp('use_rls', t.tsBooleanKeyword()), 'Whether to enable RLS on this table. Defaults to true.'),
|
|
362
|
+
addJSDoc(optionalProp('indexes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableIndex')))), 'Table-level indexes (table_name inherited from parent).'),
|
|
363
|
+
addJSDoc(optionalProp('full_text_searches', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableFullTextSearch')))), 'Table-level full-text search configurations (table_name inherited from parent).'),
|
|
364
|
+
addJSDoc(optionalProp('unique_constraints', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableUniqueConstraint')))), 'Table-level unique constraints (table_name inherited from parent).'),
|
|
332
365
|
]), 'A table definition within a blueprint.');
|
|
333
366
|
}
|
|
334
367
|
function buildBlueprintDefinition() {
|
|
@@ -337,6 +370,7 @@ function buildBlueprintDefinition() {
|
|
|
337
370
|
addJSDoc(optionalProp('relations', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintRelation')))), 'Relations between tables.'),
|
|
338
371
|
addJSDoc(optionalProp('indexes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintIndex')))), 'Indexes on table columns.'),
|
|
339
372
|
addJSDoc(optionalProp('full_text_searches', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFullTextSearch')))), 'Full-text search configurations.'),
|
|
373
|
+
addJSDoc(optionalProp('unique_constraints', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintUniqueConstraint')))), 'Unique constraints on table columns.'),
|
|
340
374
|
]), 'The complete blueprint definition -- the JSONB shape accepted by construct_blueprint().');
|
|
341
375
|
}
|
|
342
376
|
// ---------------------------------------------------------------------------
|
|
@@ -385,7 +419,11 @@ function buildProgram(meta) {
|
|
|
385
419
|
statements.push(buildBlueprintPolicy(authzNodes, meta));
|
|
386
420
|
statements.push(buildBlueprintFtsSource());
|
|
387
421
|
statements.push(buildBlueprintFullTextSearch());
|
|
422
|
+
statements.push(buildBlueprintTableFullTextSearch());
|
|
388
423
|
statements.push(buildBlueprintIndex(meta));
|
|
424
|
+
statements.push(buildBlueprintTableIndex());
|
|
425
|
+
statements.push(buildBlueprintUniqueConstraint());
|
|
426
|
+
statements.push(buildBlueprintTableUniqueConstraint());
|
|
389
427
|
// -- Node types discriminated union --
|
|
390
428
|
statements.push(sectionComment('Node types -- discriminated union for nodes[] entries'));
|
|
391
429
|
statements.push(...buildNodeTypes(dataNodes));
|
|
@@ -403,7 +441,7 @@ function buildProgram(meta) {
|
|
|
403
441
|
'// GENERATED FILE \u2014 DO NOT EDIT',
|
|
404
442
|
'//',
|
|
405
443
|
'// Regenerate with:',
|
|
406
|
-
'// cd
|
|
444
|
+
'// cd graphql/node-type-registry && pnpm generate:types',
|
|
407
445
|
'//',
|
|
408
446
|
'// These types match the JSONB shape expected by construct_blueprint().',
|
|
409
447
|
'// All field names are snake_case to match the SQL convention.',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-type-registry",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Node type definitions for the Constructive blueprint system. Single source of truth for all Authz*, Data*, Relation*, and View* node types.",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"main": "index.js",
|
|
@@ -48,5 +48,5 @@
|
|
|
48
48
|
"registry",
|
|
49
49
|
"graphile"
|
|
50
50
|
],
|
|
51
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "091694a30420500790004997a61770972e158d82"
|
|
52
52
|
}
|