node-type-registry 0.17.0 → 0.18.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.
@@ -19,7 +19,7 @@
19
19
  // eslint-disable-next-line @typescript-eslint/no-var-requires
20
20
  const generate = require('@babel/generator').default ?? require('@babel/generator');
21
21
  import * as t from '@babel/types';
22
- import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs';
22
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
23
23
  import { join } from 'path';
24
24
  import { generateTypeScriptTypes } from 'schema-typescript';
25
25
  import { allNodeTypes } from '../index';
@@ -29,7 +29,7 @@ import { allNodeTypes } from '../index';
29
29
  /** Attach a JSDoc-style leading comment to an AST node. */
30
30
  function addJSDoc(node, description) {
31
31
  node.leadingComments = [
32
- { type: 'CommentBlock', value: `* ${description} ` },
32
+ { type: 'CommentBlock', value: `* ${description} ` }
33
33
  ];
34
34
  return node;
35
35
  }
@@ -112,7 +112,7 @@ function generateParamsInterfaces(nodeTypes) {
112
112
  const astNodes = generateTypeScriptTypes(sanitized, {
113
113
  includePropertyComments: true,
114
114
  includeTypeComments: false,
115
- strictTypeSafety: true,
115
+ strictTypeSafety: true
116
116
  });
117
117
  if (astNodes.length > 0) {
118
118
  // The last node is the main interface for the title
@@ -208,7 +208,7 @@ function buildBlueprintField(meta) {
208
208
  addJSDoc(requiredProp('type', t.tsStringKeyword()), 'The PostgreSQL type (e.g., "text", "integer", "boolean", "uuid").'),
209
209
  addJSDoc(optionalProp('is_required', t.tsBooleanKeyword()), 'Whether the column has a NOT NULL constraint.'),
210
210
  addJSDoc(optionalProp('default_value', t.tsStringKeyword()), 'SQL default value expression (e.g., "true", "now()").'),
211
- addJSDoc(optionalProp('description', t.tsStringKeyword()), 'Comment/description for this field.'),
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
214
  function buildBlueprintPolicy(authzNodes, _meta) {
@@ -225,14 +225,14 @@ function buildBlueprintPolicy(authzNodes, _meta) {
225
225
  addJSDoc(optionalProp('permissive', t.tsBooleanKeyword()), 'Whether this policy is permissive (true) or restrictive (false). Defaults to true.'),
226
226
  addJSDoc(optionalProp('policy_role', t.tsStringKeyword()), 'Role for this policy. Defaults to "authenticated".'),
227
227
  addJSDoc(optionalProp('policy_name', t.tsStringKeyword()), 'Optional custom name for this policy.'),
228
- addJSDoc(optionalProp('data', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Policy-specific data (structure varies by policy type).'),
228
+ addJSDoc(optionalProp('data', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Policy-specific data (structure varies by policy type).')
229
229
  ]), 'An RLS policy entry for a blueprint table. Uses $type to match the blueprint JSON convention.');
230
230
  }
231
231
  function buildBlueprintFtsSource() {
232
232
  return addJSDoc(exportInterface('BlueprintFtsSource', [
233
233
  addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Column name of the source field.'),
234
234
  addJSDoc(requiredProp('weight', t.tsStringKeyword()), 'TSVector weight: "A", "B", "C", or "D".'),
235
- addJSDoc(optionalProp('lang', t.tsStringKeyword()), 'Language for text search. Defaults to "english".'),
235
+ addJSDoc(optionalProp('lang', t.tsStringKeyword()), 'Language for text search. Defaults to "english".')
236
236
  ]), 'A source field contributing to a full-text search tsvector column.');
237
237
  }
238
238
  function buildBlueprintFullTextSearch() {
@@ -240,14 +240,14 @@ function buildBlueprintFullTextSearch() {
240
240
  addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this full-text search belongs to.'),
241
241
  addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
242
242
  addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Name of the tsvector field on the table.'),
243
- addJSDoc(requiredProp('sources', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFtsSource')))), 'Source fields that feed into this tsvector.'),
243
+ addJSDoc(requiredProp('sources', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFtsSource')))), 'Source fields that feed into this tsvector.')
244
244
  ]), 'A full-text search configuration for a blueprint table (top-level, requires table_name).');
245
245
  }
246
246
  function buildBlueprintTableFullTextSearch() {
247
247
  return addJSDoc(exportInterface('BlueprintTableFullTextSearch', [
248
248
  addJSDoc(requiredProp('field', t.tsStringKeyword()), 'Name of the tsvector field on the table.'),
249
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.'),
250
+ addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.')
251
251
  ]), 'A full-text search configuration nested inside a table definition (table_name not required).');
252
252
  }
253
253
  function buildBlueprintIndex(meta) {
@@ -257,7 +257,7 @@ function buildBlueprintIndex(meta) {
257
257
  // JSONB columns get Record<string, unknown> instead of the default
258
258
  index_params: recordType(t.tsStringKeyword(), t.tsUnknownKeyword()),
259
259
  where_clause: recordType(t.tsStringKeyword(), t.tsUnknownKeyword()),
260
- options: recordType(t.tsStringKeyword(), t.tsUnknownKeyword()),
260
+ options: recordType(t.tsStringKeyword(), t.tsUnknownKeyword())
261
261
  });
262
262
  }
263
263
  // Static fallback
@@ -270,7 +270,7 @@ function buildBlueprintIndex(meta) {
270
270
  addJSDoc(optionalProp('is_unique', t.tsBooleanKeyword()), 'Whether this is a unique index.'),
271
271
  addJSDoc(optionalProp('name', t.tsStringKeyword()), 'Optional custom name for the index.'),
272
272
  addJSDoc(optionalProp('op_classes', t.tsArrayType(t.tsStringKeyword())), 'Operator classes for the index columns.'),
273
- addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.'),
273
+ addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.')
274
274
  ]), 'An index definition within a blueprint (top-level, requires table_name).');
275
275
  }
276
276
  function buildBlueprintTableIndex() {
@@ -282,7 +282,7 @@ function buildBlueprintTableIndex() {
282
282
  addJSDoc(optionalProp('name', t.tsStringKeyword()), 'Optional custom name for the index.'),
283
283
  addJSDoc(optionalProp('op_classes', t.tsArrayType(t.tsStringKeyword())), 'Operator classes for the index columns.'),
284
284
  addJSDoc(optionalProp('options', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Additional index-specific options.'),
285
- addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
285
+ addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.')
286
286
  ]), 'An index definition nested inside a table definition (table_name not required).');
287
287
  }
288
288
  // ---------------------------------------------------------------------------
@@ -308,7 +308,7 @@ function buildNodeTypes(dataNodes) {
308
308
  // BlueprintNode -- shorthand | object
309
309
  results.push(addJSDoc(exportTypeAlias('BlueprintNode', t.tsUnionType([
310
310
  t.tsTypeReference(t.identifier('BlueprintNodeShorthand')),
311
- t.tsTypeReference(t.identifier('BlueprintNodeObject')),
311
+ t.tsTypeReference(t.identifier('BlueprintNodeObject'))
312
312
  ])), 'A node entry in a blueprint table. Either a string shorthand or a typed object.'));
313
313
  return results;
314
314
  }
@@ -322,7 +322,7 @@ function buildRelationTypes(relationNodes) {
322
322
  requiredProp('source_table', t.tsStringKeyword()),
323
323
  requiredProp('target_table', t.tsStringKeyword()),
324
324
  optionalProp('source_schema_name', t.tsStringKeyword()),
325
- optionalProp('target_schema_name', t.tsStringKeyword()),
325
+ optionalProp('target_schema_name', t.tsStringKeyword())
326
326
  ];
327
327
  // RelationSpatial is the only relation type that references *existing*
328
328
  // columns rather than creating FK/junction fields. Its blueprint JSON
@@ -336,11 +336,11 @@ function buildRelationTypes(relationNodes) {
336
336
  const baseType = t.tsTypeLiteral(baseMembers);
337
337
  return t.tsIntersectionType([
338
338
  baseType,
339
- partialOf(t.tsTypeReference(t.identifier(`${nt.name}Params`))),
339
+ partialOf(t.tsTypeReference(t.identifier(`${nt.name}Params`)))
340
340
  ]);
341
341
  });
342
342
  return [
343
- addJSDoc(exportTypeAlias('BlueprintRelation', t.tsUnionType(relationMembers)), 'A relation entry in a blueprint definition.'),
343
+ addJSDoc(exportTypeAlias('BlueprintRelation', t.tsUnionType(relationMembers)), 'A relation entry in a blueprint definition.')
344
344
  ];
345
345
  }
346
346
  // ---------------------------------------------------------------------------
@@ -350,23 +350,70 @@ function buildBlueprintUniqueConstraint() {
350
350
  return addJSDoc(exportInterface('BlueprintUniqueConstraint', [
351
351
  addJSDoc(requiredProp('table_name', t.tsStringKeyword()), 'Table name this unique constraint belongs to.'),
352
352
  addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name for disambiguation (falls back to top-level default).'),
353
- addJSDoc(requiredProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Column names that form the unique constraint.'),
353
+ addJSDoc(requiredProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Column names that form the unique constraint.')
354
354
  ]), 'A unique constraint definition within a blueprint (top-level, requires table_name).');
355
355
  }
356
356
  function buildBlueprintTableUniqueConstraint() {
357
357
  return addJSDoc(exportInterface('BlueprintTableUniqueConstraint', [
358
358
  addJSDoc(requiredProp('columns', t.tsArrayType(t.tsStringKeyword())), 'Column names that form the unique constraint.'),
359
- addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.'),
359
+ addJSDoc(optionalProp('schema_name', t.tsStringKeyword()), 'Optional schema name override.')
360
360
  ]), 'A unique constraint nested inside a table definition (table_name not required).');
361
361
  }
362
+ /**
363
+ * Build the BlueprintStoragePolicy interface.
364
+ *
365
+ * Matches the jsonb policy objects accepted by apply_storage_security():
366
+ * { "$type": "AuthzPublishable", "privileges": ["select"], "data": {...}, "tables": [...], "policy_name": "pub" }
367
+ */
368
+ function buildBlueprintStoragePolicy() {
369
+ return addJSDoc(exportInterface('BlueprintStoragePolicy', [
370
+ addJSDoc(requiredProp('$type', t.tsStringKeyword()), 'Authz* policy generator type (e.g., "AuthzPublishable", "AuthzDirectOwner", "AuthzEntityMembership").'),
371
+ addJSDoc(requiredProp('privileges', t.tsArrayType(t.tsStringKeyword())), 'Privilege array (e.g., ["select", "insert", "update", "delete"]). Intersected with each storage table\'s supported operations.'),
372
+ addJSDoc(optionalProp('data', recordType(t.tsStringKeyword(), t.tsUnknownKeyword())), 'Policy data config. Auto-derived from $type when omitted (e.g., AuthzPublishable defaults to {"is_published_field": "is_public", "require_published_at": false}).'),
373
+ addJSDoc(optionalProp('tables', t.tsArrayType(strUnion(['buckets', 'files', 'upload_requests']))), 'Which storage tables to apply this policy to. Defaults to all three when omitted. Uses logical names (not prefixed).'),
374
+ addJSDoc(optionalProp('policy_name', t.tsStringKeyword()), 'Custom RLS policy name suffix. Auto-derived from $type when omitted (pub/own/mem).')
375
+ ]), 'A storage-specific RLS policy object for apply_storage_security(). Each entry defines an Authz* policy with explicit privileges, scoped to specific storage tables.');
376
+ }
377
+ /**
378
+ * Build the BlueprintBucketSeed interface.
379
+ *
380
+ * Matches the bucket entries in storage_config.buckets[].
381
+ */
382
+ function buildBlueprintBucketSeed() {
383
+ return addJSDoc(exportInterface('BlueprintBucketSeed', [
384
+ addJSDoc(requiredProp('name', t.tsStringKeyword()), 'Bucket key name (e.g., "avatars", "documents"). Becomes the key column value.'),
385
+ addJSDoc(optionalProp('description', t.tsStringKeyword()), 'Human-readable description of this bucket.'),
386
+ addJSDoc(optionalProp('is_public', t.tsBooleanKeyword()), 'Whether the bucket is publicly readable. Defaults to false.'),
387
+ addJSDoc(optionalProp('allowed_mime_types', t.tsArrayType(t.tsStringKeyword())), 'MIME type allowlist (e.g., ["image/png", "image/jpeg"]). NULL means all types allowed.'),
388
+ addJSDoc(optionalProp('max_file_size', t.tsNumberKeyword()), 'Maximum file size in bytes for this bucket. NULL means no limit.'),
389
+ addJSDoc(optionalProp('allowed_origins', t.tsArrayType(t.tsStringKeyword())), 'CORS allowed origins for this bucket.')
390
+ ]), 'A bucket seed entry for storage_config.buckets[]. Creates an initial bucket row in the {prefix}_buckets table during entity type provisioning. Only used for app-level storage (not entity-scoped).');
391
+ }
392
+ /**
393
+ * Build the BlueprintStorageConfig interface.
394
+ *
395
+ * Matches the jsonb shape accepted by storage_config on entity_type_provision.
396
+ */
397
+ function buildBlueprintStorageConfig() {
398
+ return addJSDoc(exportInterface('BlueprintStorageConfig', [
399
+ addJSDoc(optionalProp('policies', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintStoragePolicy')))), 'Custom RLS policies for storage tables. When provided, replaces the default policy set (AuthzPublishable + membership + AuthzDirectOwner). Each entry is a policy object with $type, privileges, and optional data/tables/policy_name.'),
400
+ addJSDoc(optionalProp('buckets', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintBucketSeed')))), 'Initial bucket seed entries. Each creates a row in {prefix}_buckets during provisioning. Only used for app-level storage (not entity-scoped).'),
401
+ addJSDoc(optionalProp('upload_url_expiry_seconds', t.tsNumberKeyword()), 'Override for presigned upload URL expiry time in seconds.'),
402
+ addJSDoc(optionalProp('download_url_expiry_seconds', t.tsNumberKeyword()), 'Override for presigned download URL expiry time in seconds.'),
403
+ addJSDoc(optionalProp('default_max_file_size', t.tsNumberKeyword()), 'Default maximum file size in bytes for the storage module.'),
404
+ addJSDoc(optionalProp('allowed_origins', t.tsArrayType(t.tsStringKeyword())), 'CORS allowed origins for the storage module.')
405
+ ]), 'Storage configuration for an entity type. Controls RLS policies on storage tables, seeds initial buckets, and overrides module-level settings (expiry times, file size limits, CORS).');
406
+ }
362
407
  function buildBlueprintEntityTableProvision() {
363
408
  return addJSDoc(exportInterface('BlueprintEntityTableProvision', [
364
409
  addJSDoc(optionalProp('use_rls', t.tsBooleanKeyword()), 'Whether to enable RLS on the entity table. Forwarded to secure_table_provision. Defaults to true.'),
365
410
  addJSDoc(optionalProp('nodes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintNode')))), 'Node objects applied to the entity table for field creation (e.g., DataTimestamps, DataPeoplestamps). Forwarded to secure_table_provision as-is.'),
366
411
  addJSDoc(optionalProp('fields', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintField')))), 'Custom fields (columns) to add to the entity table. Forwarded to secure_table_provision as-is.'),
367
- addJSDoc(optionalProp('grant_privileges', t.tsArrayType(t.tsUnknownKeyword())), 'Privilege grants for the entity table as [verb, columns] tuples (e.g. [["select","*"],["insert","*"]]). Forwarded to secure_table_provision as-is.'),
368
- addJSDoc(optionalProp('grant_roles', t.tsArrayType(t.tsStringKeyword())), 'Database roles to grant privileges to. Forwarded to secure_table_provision as-is. Defaults to ["authenticated"].'),
369
- addJSDoc(optionalProp('policies', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy')))), 'RLS policies for the entity table. When present, these policies fully replace the five default entity-table policies (is_visible becomes a no-op).'),
412
+ addJSDoc(optionalProp('grants', t.tsArrayType(t.tsTypeLiteral([
413
+ requiredProp('roles', t.tsArrayType(t.tsStringKeyword())),
414
+ requiredProp('privileges', t.tsArrayType(t.tsUnknownKeyword()))
415
+ ]))), 'Unified grant objects for the entity table. Each entry is { roles: string[], privileges: unknown[] } where privileges are [verb, columns] tuples. Forwarded to secure_table_provision as-is. Defaults to [].'),
416
+ addJSDoc(optionalProp('policies', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy')))), 'RLS policies for the entity table. When present, these policies fully replace the five default entity-table policies (is_visible becomes a no-op).')
370
417
  ]), 'Override object for the entity table created by a BlueprintMembershipType. Shape mirrors BlueprintTable / secure_table_provision vocabulary. When supplied, policies[] replaces the default entity-table policies entirely.');
371
418
  }
372
419
  function buildBlueprintMembershipType() {
@@ -380,8 +427,10 @@ function buildBlueprintMembershipType() {
380
427
  addJSDoc(optionalProp('has_limits', t.tsBooleanKeyword()), 'Whether to provision a limits module for this entity type. Defaults to false.'),
381
428
  addJSDoc(optionalProp('has_profiles', t.tsBooleanKeyword()), 'Whether to provision a profiles module for this entity type. Defaults to false.'),
382
429
  addJSDoc(optionalProp('has_levels', t.tsBooleanKeyword()), 'Whether to provision a levels module for this entity type. Defaults to false.'),
430
+ addJSDoc(optionalProp('has_storage', t.tsBooleanKeyword()), 'Whether to provision a storage module (buckets, files, upload_requests tables) for this entity type. Defaults to false.'),
383
431
  addJSDoc(optionalProp('skip_entity_policies', t.tsBooleanKeyword()), 'Escape hatch: when true AND table_provision is NULL, zero policies are provisioned on the entity table. Defaults to false.'),
384
432
  addJSDoc(optionalProp('table_provision', t.tsTypeReference(t.identifier('BlueprintEntityTableProvision'))), 'Override for the entity table. Shape mirrors BlueprintTable / secure_table_provision vocabulary. When supplied, its policies[] replaces the five default entity-table policies; is_visible becomes a no-op. When NULL (default), the five default policies are applied (gated by is_visible).'),
433
+ addJSDoc(optionalProp('storage', t.tsTypeReference(t.identifier('BlueprintStorageConfig'))), 'Storage configuration. Only used when has_storage is true. Controls RLS policies on storage tables, seeds initial buckets, and overrides module-level settings (expiry times, file size limits, CORS).')
385
434
  ]), 'A membership type entry for Phase 0 of construct_blueprint(). Provisions a full entity type with its own entity table, membership modules, and security policies via entity_type_provision.');
386
435
  }
387
436
  function buildBlueprintTable() {
@@ -391,12 +440,14 @@ function buildBlueprintTable() {
391
440
  addJSDoc(requiredProp('nodes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintNode')))), "Array of node type entries that define the table's behavior."),
392
441
  addJSDoc(optionalProp('fields', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintField')))), 'Custom fields (columns) to add to the table.'),
393
442
  addJSDoc(optionalProp('policies', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintPolicy')))), 'RLS policies for this table.'),
394
- addJSDoc(optionalProp('grant_roles', t.tsArrayType(t.tsStringKeyword())), 'Database roles to grant privileges to. Defaults to ["authenticated"].'),
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).'),
443
+ addJSDoc(optionalProp('grants', t.tsArrayType(t.tsTypeLiteral([
444
+ requiredProp('roles', t.tsArrayType(t.tsStringKeyword())),
445
+ requiredProp('privileges', t.tsArrayType(t.tsUnknownKeyword()))
446
+ ]))), 'Unified grant objects. Each entry is { roles: string[], privileges: unknown[] } where privileges are [verb, columns] tuples (e.g. [["select","*"]]). Enables per-role targeting. Defaults to [].'),
396
447
  addJSDoc(optionalProp('use_rls', t.tsBooleanKeyword()), 'Whether to enable RLS on this table. Defaults to true.'),
397
448
  addJSDoc(optionalProp('indexes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableIndex')))), 'Table-level indexes (table_name inherited from parent).'),
398
449
  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).'),
450
+ addJSDoc(optionalProp('unique_constraints', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintTableUniqueConstraint')))), 'Table-level unique constraints (table_name inherited from parent).')
400
451
  ]), 'A table definition within a blueprint.');
401
452
  }
402
453
  function buildBlueprintDefinition() {
@@ -406,7 +457,7 @@ function buildBlueprintDefinition() {
406
457
  addJSDoc(optionalProp('indexes', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintIndex')))), 'Indexes on table columns.'),
407
458
  addJSDoc(optionalProp('full_text_searches', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintFullTextSearch')))), 'Full-text search configurations.'),
408
459
  addJSDoc(optionalProp('unique_constraints', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintUniqueConstraint')))), 'Unique constraints on table columns.'),
409
- addJSDoc(optionalProp('membership_types', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintMembershipType')))), 'Entity types to provision in Phase 0 (before tables). Each entry creates an entity table with membership modules and security.'),
460
+ addJSDoc(optionalProp('membership_types', t.tsArrayType(t.tsTypeReference(t.identifier('BlueprintMembershipType')))), 'Entity types to provision in Phase 0 (before tables). Each entry creates an entity table with membership modules and security.')
410
461
  ]), 'The complete blueprint definition -- the JSONB shape accepted by construct_blueprint().');
411
462
  }
412
463
  // ---------------------------------------------------------------------------
@@ -417,8 +468,8 @@ function sectionComment(title) {
417
468
  empty.leadingComments = [
418
469
  {
419
470
  type: 'CommentBlock',
420
- value: `*\n * ===========================================================================\n * ${title}\n * ===========================================================================\n `,
421
- },
471
+ value: `*\n * ===========================================================================\n * ${title}\n * ===========================================================================\n `
472
+ }
422
473
  ];
423
474
  return empty;
424
475
  }
@@ -460,6 +511,9 @@ function buildProgram(meta) {
460
511
  statements.push(buildBlueprintTableIndex());
461
512
  statements.push(buildBlueprintUniqueConstraint());
462
513
  statements.push(buildBlueprintTableUniqueConstraint());
514
+ statements.push(buildBlueprintStoragePolicy());
515
+ statements.push(buildBlueprintBucketSeed());
516
+ statements.push(buildBlueprintStorageConfig());
463
517
  statements.push(buildBlueprintEntityTableProvision());
464
518
  statements.push(buildBlueprintMembershipType());
465
519
  // -- Node types discriminated union --
@@ -477,6 +531,7 @@ function buildProgram(meta) {
477
531
  const file = t.file(program);
478
532
  const header = [
479
533
  '// GENERATED FILE \u2014 DO NOT EDIT',
534
+ '/* eslint-disable @typescript-eslint/no-empty-object-type */',
480
535
  '//',
481
536
  '// Regenerate with:',
482
537
  '// cd graphql/node-type-registry && pnpm generate:types',
@@ -484,9 +539,9 @@ function buildProgram(meta) {
484
539
  '// These types match the JSONB shape expected by construct_blueprint().',
485
540
  '// All field names are snake_case to match the SQL convention.',
486
541
  '',
487
- '',
542
+ ''
488
543
  ].join('\n');
489
- const output = generate(file, { comments: true });
544
+ const output = generate(file, { comments: true, jsescOption: { quotes: 'single' } });
490
545
  return header + output.code + '\n';
491
546
  }
492
547
  // ---------------------------------------------------------------------------
@@ -46,46 +46,33 @@ export const RelationManyToMany = {
46
46
  },
47
47
  "description": "Array of node objects for field creation on junction table. Each object has a $type key (e.g. DataId, DataEntityMembership) and optional data keys. Forwarded to secure_table_provision as-is. Empty array means no additional fields."
48
48
  },
49
- "grant_roles": {
49
+ "grants": {
50
50
  "type": "array",
51
51
  "items": {
52
- "type": "string"
52
+ "type": "object",
53
+ "properties": {
54
+ "roles": { "type": "array", "items": { "type": "string" } },
55
+ "privileges": { "type": "array", "items": { "type": "array", "items": { "type": "string" } } }
56
+ },
57
+ "required": ["roles", "privileges"]
53
58
  },
54
- "description": "Database roles to grant privileges to. Forwarded to secure_table_provision as-is. Default: [authenticated]"
59
+ "description": "Unified grant objects for the junction table. Each entry is { roles: string[], privileges: string[][] }. Forwarded to secure_table_provision as-is. Default: []"
55
60
  },
56
- "grant_privileges": {
61
+ "policies": {
57
62
  "type": "array",
58
63
  "items": {
59
- "type": "array",
60
- "items": {
61
- "type": "string"
62
- }
64
+ "type": "object",
65
+ "properties": {
66
+ "$type": { "type": "string" },
67
+ "data": { "type": "object" },
68
+ "privileges": { "type": "array", "items": { "type": "string" } },
69
+ "policy_role": { "type": "string" },
70
+ "permissive": { "type": "boolean" },
71
+ "policy_name": { "type": "string" }
72
+ },
73
+ "required": ["$type"]
63
74
  },
64
- "description": "Privilege grants for the junction table as [verb, columns] tuples (e.g. [['select','*'],['insert','*']]). Forwarded to secure_table_provision as-is. Default: select/insert/delete for all columns"
65
- },
66
- "policy_type": {
67
- "type": "string",
68
- "description": "RLS policy type for the junction table. Forwarded to secure_table_provision as-is. NULL means no policy."
69
- },
70
- "policy_privileges": {
71
- "type": "array",
72
- "items": {
73
- "type": "string"
74
- },
75
- "description": "Privileges the policy applies to. Forwarded to secure_table_provision as-is. NULL means derived from grant_privileges verbs."
76
- },
77
- "policy_role": {
78
- "type": "string",
79
- "description": "Database role the policy targets. Forwarded to secure_table_provision as-is. NULL means falls back to first grant_role."
80
- },
81
- "policy_permissive": {
82
- "type": "boolean",
83
- "description": "Whether the policy is PERMISSIVE (true) or RESTRICTIVE (false). Forwarded to secure_table_provision as-is.",
84
- "default": true
85
- },
86
- "policy_data": {
87
- "type": "object",
88
- "description": "Policy configuration forwarded to secure_table_provision as-is. Structure varies by policy_type."
75
+ "description": "RLS policy objects for the junction table. Each entry has $type (Authz* generator), optional data, privileges, policy_role, permissive, policy_name. Forwarded to secure_table_provision as-is. Default: []"
89
76
  }
90
77
  },
91
78
  "required": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-type-registry",
3
- "version": "0.17.0",
3
+ "version": "0.18.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",
@@ -47,5 +47,5 @@
47
47
  "registry",
48
48
  "graphile"
49
49
  ],
50
- "gitHead": "4988b64539a61786647412a456c56cb486722e18"
50
+ "gitHead": "058b8200e99eb505477d1599ee0d5ab795aa0123"
51
51
  }
@@ -49,46 +49,33 @@ exports.RelationManyToMany = {
49
49
  },
50
50
  "description": "Array of node objects for field creation on junction table. Each object has a $type key (e.g. DataId, DataEntityMembership) and optional data keys. Forwarded to secure_table_provision as-is. Empty array means no additional fields."
51
51
  },
52
- "grant_roles": {
52
+ "grants": {
53
53
  "type": "array",
54
54
  "items": {
55
- "type": "string"
55
+ "type": "object",
56
+ "properties": {
57
+ "roles": { "type": "array", "items": { "type": "string" } },
58
+ "privileges": { "type": "array", "items": { "type": "array", "items": { "type": "string" } } }
59
+ },
60
+ "required": ["roles", "privileges"]
56
61
  },
57
- "description": "Database roles to grant privileges to. Forwarded to secure_table_provision as-is. Default: [authenticated]"
62
+ "description": "Unified grant objects for the junction table. Each entry is { roles: string[], privileges: string[][] }. Forwarded to secure_table_provision as-is. Default: []"
58
63
  },
59
- "grant_privileges": {
64
+ "policies": {
60
65
  "type": "array",
61
66
  "items": {
62
- "type": "array",
63
- "items": {
64
- "type": "string"
65
- }
67
+ "type": "object",
68
+ "properties": {
69
+ "$type": { "type": "string" },
70
+ "data": { "type": "object" },
71
+ "privileges": { "type": "array", "items": { "type": "string" } },
72
+ "policy_role": { "type": "string" },
73
+ "permissive": { "type": "boolean" },
74
+ "policy_name": { "type": "string" }
75
+ },
76
+ "required": ["$type"]
66
77
  },
67
- "description": "Privilege grants for the junction table as [verb, columns] tuples (e.g. [['select','*'],['insert','*']]). Forwarded to secure_table_provision as-is. Default: select/insert/delete for all columns"
68
- },
69
- "policy_type": {
70
- "type": "string",
71
- "description": "RLS policy type for the junction table. Forwarded to secure_table_provision as-is. NULL means no policy."
72
- },
73
- "policy_privileges": {
74
- "type": "array",
75
- "items": {
76
- "type": "string"
77
- },
78
- "description": "Privileges the policy applies to. Forwarded to secure_table_provision as-is. NULL means derived from grant_privileges verbs."
79
- },
80
- "policy_role": {
81
- "type": "string",
82
- "description": "Database role the policy targets. Forwarded to secure_table_provision as-is. NULL means falls back to first grant_role."
83
- },
84
- "policy_permissive": {
85
- "type": "boolean",
86
- "description": "Whether the policy is PERMISSIVE (true) or RESTRICTIVE (false). Forwarded to secure_table_provision as-is.",
87
- "default": true
88
- },
89
- "policy_data": {
90
- "type": "object",
91
- "description": "Policy configuration forwarded to secure_table_provision as-is. Structure varies by policy_type."
78
+ "description": "RLS policy objects for the junction table. Each entry has $type (Authz* generator), optional data, privileges, policy_role, permissive, policy_name. Forwarded to secure_table_provision as-is. Default: []"
92
79
  }
93
80
  },
94
81
  "required": [