graphile-settings 5.15.0 → 5.16.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.
@@ -1,4 +1,4 @@
1
- import { GraphQLBoolean, GraphQLFloat, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString, } from 'graphql';
1
+ import { GraphQLBoolean, GraphQLFloat, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
2
2
  function nn(type) {
3
3
  return new GraphQLNonNull(type);
4
4
  }
@@ -15,16 +15,16 @@ function createMetaSchemaType() {
15
15
  isArray: { type: nn(GraphQLBoolean) },
16
16
  isNotNull: { type: GraphQLBoolean },
17
17
  hasDefault: { type: GraphQLBoolean },
18
- subtype: { type: GraphQLString },
19
- }),
18
+ subtype: { type: GraphQLString }
19
+ })
20
20
  });
21
21
  const MetaEnumType = new GraphQLObjectType({
22
22
  name: 'MetaEnum',
23
23
  description: 'Information about a PostgreSQL enum type',
24
24
  fields: () => ({
25
25
  name: { type: nn(GraphQLString), description: 'The PostgreSQL enum type name' },
26
- values: { type: nnList(GraphQLString), description: 'Allowed values for this enum' },
27
- }),
26
+ values: { type: nnList(GraphQLString), description: 'Allowed values for this enum' }
27
+ })
28
28
  });
29
29
  const MetaFieldType = new GraphQLObjectType({
30
30
  name: 'MetaField',
@@ -37,8 +37,8 @@ function createMetaSchemaType() {
37
37
  isPrimaryKey: { type: nn(GraphQLBoolean) },
38
38
  isForeignKey: { type: nn(GraphQLBoolean) },
39
39
  description: { type: GraphQLString },
40
- enumValues: { type: MetaEnumType, description: 'Enum metadata if this field has an enum type' },
41
- }),
40
+ enumValues: { type: MetaEnumType, description: 'Enum metadata if this field has an enum type' }
41
+ })
42
42
  });
43
43
  const MetaIndexType = new GraphQLObjectType({
44
44
  name: 'MetaIndex',
@@ -48,31 +48,31 @@ function createMetaSchemaType() {
48
48
  isUnique: { type: nn(GraphQLBoolean) },
49
49
  isPrimary: { type: nn(GraphQLBoolean) },
50
50
  columns: { type: nnList(GraphQLString) },
51
- fields: { type: new GraphQLList(nn(MetaFieldType)) },
52
- }),
51
+ fields: { type: new GraphQLList(nn(MetaFieldType)) }
52
+ })
53
53
  });
54
54
  const MetaPrimaryKeyConstraintType = new GraphQLObjectType({
55
55
  name: 'MetaPrimaryKeyConstraint',
56
56
  description: 'Information about a primary key constraint',
57
57
  fields: () => ({
58
58
  name: { type: nn(GraphQLString) },
59
- fields: { type: nnList(MetaFieldType) },
60
- }),
59
+ fields: { type: nnList(MetaFieldType) }
60
+ })
61
61
  });
62
62
  const MetaUniqueConstraintType = new GraphQLObjectType({
63
63
  name: 'MetaUniqueConstraint',
64
64
  description: 'Information about a unique constraint',
65
65
  fields: () => ({
66
66
  name: { type: nn(GraphQLString) },
67
- fields: { type: nnList(MetaFieldType) },
68
- }),
67
+ fields: { type: nnList(MetaFieldType) }
68
+ })
69
69
  });
70
70
  const MetaRefTableType = new GraphQLObjectType({
71
71
  name: 'MetaRefTable',
72
72
  description: 'Reference to a related table',
73
73
  fields: () => ({
74
- name: { type: nn(GraphQLString) },
75
- }),
74
+ name: { type: nn(GraphQLString) }
75
+ })
76
76
  });
77
77
  const MetaForeignKeyConstraintType = new GraphQLObjectType({
78
78
  name: 'MetaForeignKeyConstraint',
@@ -83,8 +83,8 @@ function createMetaSchemaType() {
83
83
  referencedTable: { type: nn(GraphQLString) },
84
84
  referencedFields: { type: nnList(GraphQLString) },
85
85
  refFields: { type: new GraphQLList(nn(MetaFieldType)) },
86
- refTable: { type: MetaRefTableType },
87
- }),
86
+ refTable: { type: MetaRefTableType }
87
+ })
88
88
  });
89
89
  const MetaConstraintsType = new GraphQLObjectType({
90
90
  name: 'MetaConstraints',
@@ -92,8 +92,8 @@ function createMetaSchemaType() {
92
92
  fields: () => ({
93
93
  primaryKey: { type: MetaPrimaryKeyConstraintType },
94
94
  unique: { type: nnList(MetaUniqueConstraintType) },
95
- foreignKey: { type: nnList(MetaForeignKeyConstraintType) },
96
- }),
95
+ foreignKey: { type: nnList(MetaForeignKeyConstraintType) }
96
+ })
97
97
  });
98
98
  const MetaInflectionType = new GraphQLObjectType({
99
99
  name: 'MetaInflection',
@@ -110,8 +110,8 @@ function createMetaSchemaType() {
110
110
  createInputType: { type: nn(GraphQLString) },
111
111
  createPayloadType: { type: nn(GraphQLString) },
112
112
  updatePayloadType: { type: GraphQLString },
113
- deletePayloadType: { type: nn(GraphQLString) },
114
- }),
113
+ deletePayloadType: { type: nn(GraphQLString) }
114
+ })
115
115
  });
116
116
  const MetaQueryType = new GraphQLObjectType({
117
117
  name: 'MetaQuery',
@@ -121,8 +121,8 @@ function createMetaSchemaType() {
121
121
  one: { type: GraphQLString },
122
122
  create: { type: GraphQLString },
123
123
  update: { type: GraphQLString },
124
- delete: { type: GraphQLString },
125
- }),
124
+ delete: { type: GraphQLString }
125
+ })
126
126
  });
127
127
  const MetaBelongsToRelationType = new GraphQLObjectType({
128
128
  name: 'MetaBelongsToRelation',
@@ -132,8 +132,8 @@ function createMetaSchemaType() {
132
132
  isUnique: { type: nn(GraphQLBoolean) },
133
133
  type: { type: GraphQLString },
134
134
  keys: { type: nnList(MetaFieldType) },
135
- references: { type: nn(MetaRefTableType) },
136
- }),
135
+ references: { type: nn(MetaRefTableType) }
136
+ })
137
137
  });
138
138
  const MetaHasRelationType = new GraphQLObjectType({
139
139
  name: 'MetaHasRelation',
@@ -143,8 +143,8 @@ function createMetaSchemaType() {
143
143
  isUnique: { type: nn(GraphQLBoolean) },
144
144
  type: { type: GraphQLString },
145
145
  keys: { type: nnList(MetaFieldType) },
146
- referencedBy: { type: nn(MetaRefTableType) },
147
- }),
146
+ referencedBy: { type: nn(MetaRefTableType) }
147
+ })
148
148
  });
149
149
  const MetaManyToManyRelationType = new GraphQLObjectType({
150
150
  name: 'MetaManyToManyRelation',
@@ -159,8 +159,8 @@ function createMetaSchemaType() {
159
159
  junctionRightKeyAttributes: { type: nnList(MetaFieldType) },
160
160
  leftKeyAttributes: { type: nnList(MetaFieldType) },
161
161
  rightKeyAttributes: { type: nnList(MetaFieldType) },
162
- rightTable: { type: nn(MetaRefTableType) },
163
- }),
162
+ rightTable: { type: nn(MetaRefTableType) }
163
+ })
164
164
  });
165
165
  const MetaRelationsType = new GraphQLObjectType({
166
166
  name: 'MetaRelations',
@@ -170,16 +170,16 @@ function createMetaSchemaType() {
170
170
  has: { type: nnList(MetaHasRelationType) },
171
171
  hasOne: { type: nnList(MetaHasRelationType) },
172
172
  hasMany: { type: nnList(MetaHasRelationType) },
173
- manyToMany: { type: nnList(MetaManyToManyRelationType) },
174
- }),
173
+ manyToMany: { type: nnList(MetaManyToManyRelationType) }
174
+ })
175
175
  });
176
176
  const MetaStorageType = new GraphQLObjectType({
177
177
  name: 'MetaStorage',
178
178
  description: 'Storage metadata for a table',
179
179
  fields: () => ({
180
180
  isFilesTable: { type: nn(GraphQLBoolean), description: 'Whether this table is a storage files table' },
181
- isBucketsTable: { type: nn(GraphQLBoolean), description: 'Whether this table is a storage buckets table' },
182
- }),
181
+ isBucketsTable: { type: nn(GraphQLBoolean), description: 'Whether this table is a storage buckets table' }
182
+ })
183
183
  });
184
184
  const MetaSearchConfigType = new GraphQLObjectType({
185
185
  name: 'MetaSearchConfig',
@@ -190,20 +190,20 @@ function createMetaSchemaType() {
190
190
  description: 'JSON-encoded per-adapter score weights',
191
191
  resolve(source) {
192
192
  return source.weights ? JSON.stringify(source.weights) : null;
193
- },
193
+ }
194
194
  },
195
195
  boostRecent: { type: nn(GraphQLBoolean), description: 'Whether recency boosting is enabled' },
196
196
  boostRecencyField: { type: GraphQLString, description: 'Field used for recency decay' },
197
- boostRecencyDecay: { type: GraphQLFloat, description: 'Exponential decay factor per day' },
198
- }),
197
+ boostRecencyDecay: { type: GraphQLFloat, description: 'Exponential decay factor per day' }
198
+ })
199
199
  });
200
200
  const MetaSearchColumnType = new GraphQLObjectType({
201
201
  name: 'MetaSearchColumn',
202
202
  description: 'A searchable column with its algorithm',
203
203
  fields: () => ({
204
204
  name: { type: nn(GraphQLString), description: 'Column name (camelCase)' },
205
- algorithm: { type: nn(GraphQLString), description: 'Search algorithm: tsvector, bm25, trgm, or vector' },
206
- }),
205
+ algorithm: { type: nn(GraphQLString), description: 'Search algorithm: tsvector, bm25, trgm, or vector' }
206
+ })
207
207
  });
208
208
  const MetaSearchType = new GraphQLObjectType({
209
209
  name: 'MetaSearch',
@@ -212,31 +212,42 @@ function createMetaSchemaType() {
212
212
  algorithms: { type: nnList(GraphQLString), description: 'Active search algorithms on this table' },
213
213
  columns: { type: nnList(MetaSearchColumnType), description: 'Searchable columns with their algorithm' },
214
214
  hasUnifiedSearch: { type: nn(GraphQLBoolean), description: 'Whether unifiedSearch composite filter is available' },
215
- config: { type: MetaSearchConfigType, description: 'Per-table search configuration' },
216
- }),
215
+ config: { type: MetaSearchConfigType, description: 'Per-table search configuration' }
216
+ })
217
217
  });
218
218
  const MetaI18nFieldType = new GraphQLObjectType({
219
219
  name: 'MetaI18nField',
220
220
  description: 'A translatable field',
221
221
  fields: () => ({
222
222
  name: { type: nn(GraphQLString), description: 'GraphQL field name' },
223
- type: { type: nn(GraphQLString), description: 'PostgreSQL column type (text, citext)' },
224
- }),
223
+ type: { type: nn(GraphQLString), description: 'PostgreSQL column type (text, citext)' }
224
+ })
225
225
  });
226
226
  const MetaI18nType = new GraphQLObjectType({
227
227
  name: 'MetaI18n',
228
228
  description: 'i18n metadata for a table with @i18n tag',
229
229
  fields: () => ({
230
230
  translationTable: { type: nn(GraphQLString), description: 'Name of the translation table' },
231
- translatableFields: { type: nnList(MetaI18nFieldType), description: 'Fields that are translatable' },
232
- }),
231
+ translatableFields: { type: nnList(MetaI18nFieldType), description: 'Fields that are translatable' }
232
+ })
233
233
  });
234
234
  const MetaRealtimeType = new GraphQLObjectType({
235
235
  name: 'MetaRealtime',
236
236
  description: 'Realtime metadata for a table with @realtime tag',
237
237
  fields: () => ({
238
- subscriptionFieldName: { type: nn(GraphQLString), description: 'The generated subscription field name (e.g. onPostChanged)' },
239
- }),
238
+ subscriptionFieldName: { type: nn(GraphQLString), description: 'The generated subscription field name (e.g. onPostChanged)' }
239
+ })
240
+ });
241
+ const MetaScopeType = new GraphQLObjectType({
242
+ name: 'MetaScope',
243
+ description: 'Provisioning scope metadata for a table',
244
+ fields: () => ({
245
+ scope: { type: nn(GraphQLString), description: "Provisioning scope: 'platform', 'app', 'database', or an entity scope (e.g. 'org')" },
246
+ tier: { type: nn(GraphQLString), description: "Coarse bucket: 'global', 'database', or 'entity'" },
247
+ keyColumn: { type: GraphQLString, description: 'Inflected scope key column (e.g. databaseId, orgId), null for global tiers' },
248
+ entityTable: { type: GraphQLString, description: 'SQL name of the entity table for entity scopes, else null' },
249
+ source: { type: nn(GraphQLString), description: "Provenance of the scope metadata (always 'smartTag')" }
250
+ })
240
251
  });
241
252
  const MetaTableType = new GraphQLObjectType({
242
253
  name: 'MetaTable',
@@ -257,14 +268,15 @@ function createMetaSchemaType() {
257
268
  search: { type: MetaSearchType, description: 'Search metadata (null if no search configured)' },
258
269
  i18n: { type: MetaI18nType, description: 'i18n metadata (null if no @i18n tag)' },
259
270
  realtime: { type: MetaRealtimeType, description: 'Realtime metadata (null if no @realtime tag)' },
260
- }),
271
+ scope: { type: MetaScopeType, description: 'Provisioning scope metadata (null if no @scope tag)' }
272
+ })
261
273
  });
262
274
  return new GraphQLObjectType({
263
275
  name: 'MetaSchema',
264
276
  description: 'Root meta schema type',
265
277
  fields: () => ({
266
- tables: { type: nnList(MetaTableType) },
267
- }),
278
+ tables: { type: nnList(MetaTableType) }
279
+ })
268
280
  });
269
281
  }
270
282
  export function extendQueryWithMetaField(fields, tablesMeta) {
@@ -274,10 +286,10 @@ export function extendQueryWithMetaField(fields, tablesMeta) {
274
286
  description: 'Metadata about the database schema, including tables, fields, indexes, and constraints. Useful for code generation tools.',
275
287
  resolve() {
276
288
  return { tables: tablesMeta };
277
- },
289
+ }
278
290
  };
279
291
  return {
280
292
  ...fields,
281
- _meta: metaField,
293
+ _meta: metaField
282
294
  };
283
295
  }
@@ -0,0 +1,15 @@
1
+ import type { PgCodec, ScopeMeta } from './types';
2
+ /**
3
+ * Detect provisioning scope metadata for a table.
4
+ *
5
+ * The `@scope` smart tag emitted by the constructive-db generators is the sole
6
+ * source of truth. `apply_scope_fields` knows the exact scope, tier, key column,
7
+ * and entity table, and emits them via
8
+ * `metaschema.append_table_smart_tags(id, { scope, scopeTier, scopeKey, scopeEntityTable })`.
9
+ * PostGraphile surfaces these as `codec.extensions.tags.*`.
10
+ *
11
+ * There is NO inference from column names — a table without the tag returns
12
+ * `null` (the same convention as every other nullable metadata block). This
13
+ * avoids guessing key columns, whose names are chosen freely by each generator.
14
+ */
15
+ export declare function buildScopeMeta(codec: PgCodec, inflectAttr: (attrName: string, codec: PgCodec) => string): ScopeMeta | null;
@@ -0,0 +1,40 @@
1
+ const VALID_TIERS = new Set(['global', 'database', 'entity']);
2
+ function readString(value) {
3
+ return typeof value === 'string' && value.length > 0 ? value : null;
4
+ }
5
+ /**
6
+ * Detect provisioning scope metadata for a table.
7
+ *
8
+ * The `@scope` smart tag emitted by the constructive-db generators is the sole
9
+ * source of truth. `apply_scope_fields` knows the exact scope, tier, key column,
10
+ * and entity table, and emits them via
11
+ * `metaschema.append_table_smart_tags(id, { scope, scopeTier, scopeKey, scopeEntityTable })`.
12
+ * PostGraphile surfaces these as `codec.extensions.tags.*`.
13
+ *
14
+ * There is NO inference from column names — a table without the tag returns
15
+ * `null` (the same convention as every other nullable metadata block). This
16
+ * avoids guessing key columns, whose names are chosen freely by each generator.
17
+ */
18
+ export function buildScopeMeta(codec, inflectAttr) {
19
+ const tags = codec.extensions?.tags;
20
+ const scope = readString(tags?.scope);
21
+ if (!scope)
22
+ return null;
23
+ const rawTier = readString(tags.scopeTier);
24
+ const tier = rawTier && VALID_TIERS.has(rawTier)
25
+ ? rawTier
26
+ : null;
27
+ if (!tier) {
28
+ throw new Error(`@scope tag on codec "${codec.name}" is missing a valid scopeTier ` +
29
+ `(expected one of global|database|entity, got ${JSON.stringify(rawTier)})`);
30
+ }
31
+ const keyColumn = readString(tags.scopeKey);
32
+ const entityTable = readString(tags.scopeEntityTable);
33
+ return {
34
+ scope,
35
+ tier,
36
+ keyColumn: keyColumn ? inflectAttr(keyColumn, codec) : null,
37
+ entityTable,
38
+ source: 'smartTag'
39
+ };
40
+ }
@@ -1,10 +1,11 @@
1
- import { buildForeignKeyConstraints, buildIndexes, buildPrimaryKey, buildUniqueConstraints, } from './constraint-meta-builders';
1
+ import { buildForeignKeyConstraints, buildIndexes, buildPrimaryKey, buildUniqueConstraints } from './constraint-meta-builders';
2
2
  import { buildInflectionMeta, buildQueryMeta, resolveTableType } from './name-meta-builders';
3
- import { buildBelongsToRelations, buildManyToManyRelations, buildReverseRelations, } from './relation-meta-builders';
4
- import { buildStorageMeta, buildSearchMeta, buildI18nMeta, buildRealtimeMeta } from './storage-search-meta-builders';
3
+ import { buildBelongsToRelations, buildManyToManyRelations, buildReverseRelations } from './relation-meta-builders';
4
+ import { buildScopeMeta } from './scope-meta-builders';
5
+ import { buildI18nMeta, buildRealtimeMeta, buildSearchMeta, buildStorageMeta } from './storage-search-meta-builders';
6
+ import { createBuildContext } from './table-meta-context';
7
+ import { getConfiguredSchemas, getRelations, getSchemaName, getUniques, isTableResource } from './table-resource-utils';
5
8
  import { buildFieldMeta } from './type-mappings';
6
- import { createBuildContext, } from './table-meta-context';
7
- import { getConfiguredSchemas, getRelations, getSchemaName, getUniques, isTableResource, } from './table-resource-utils';
8
9
  function buildTableMeta(resource, schemaName, context) {
9
10
  const codec = resource.codec;
10
11
  const attributes = codec.attributes;
@@ -27,7 +28,7 @@ function buildTableMeta(resource, schemaName, context) {
27
28
  }
28
29
  const fields = Object.entries(attributes).map(([attrName, attr]) => buildFieldMeta(context.inflectAttr(attrName, codec), attr, context.build, {
29
30
  isPrimaryKey: pkAttrNames.has(attrName),
30
- isForeignKey: fkAttrNames.has(attrName),
31
+ isForeignKey: fkAttrNames.has(attrName)
31
32
  }));
32
33
  const indexes = buildIndexes(codec, attributes, uniques, context);
33
34
  const primaryKey = buildPrimaryKey(codec, attributes, uniques, context);
@@ -36,7 +37,7 @@ function buildTableMeta(resource, schemaName, context) {
36
37
  const constraints = {
37
38
  primaryKey,
38
39
  unique: uniqueConstraints,
39
- foreignKey: foreignKeyConstraints,
40
+ foreignKey: foreignKeyConstraints
40
41
  };
41
42
  const belongsTo = buildBelongsToRelations(codec, attributes, uniques, relations, context);
42
43
  const { hasOne, hasMany } = buildReverseRelations(codec, attributes, relations, context);
@@ -46,13 +47,14 @@ function buildTableMeta(resource, schemaName, context) {
46
47
  has: [...hasOne, ...hasMany],
47
48
  hasOne,
48
49
  hasMany,
49
- manyToMany,
50
+ manyToMany
50
51
  };
51
52
  const tableType = resolveTableType(context.build, codec);
52
53
  const storage = buildStorageMeta(codec);
53
54
  const search = buildSearchMeta(codec, context.build, context.inflectAttr);
54
55
  const i18n = buildI18nMeta(codec, context.build, context.inflectAttr);
55
56
  const realtime = buildRealtimeMeta(codec, context.build);
57
+ const scope = buildScopeMeta(codec, context.inflectAttr);
56
58
  return {
57
59
  name: tableType,
58
60
  schemaName,
@@ -69,6 +71,7 @@ function buildTableMeta(resource, schemaName, context) {
69
71
  search,
70
72
  i18n,
71
73
  realtime,
74
+ scope
72
75
  };
73
76
  }
74
77
  export function collectTablesMeta(build) {
@@ -14,6 +14,21 @@ export interface TableMeta {
14
14
  search: SearchMeta | null;
15
15
  i18n: I18nMeta | null;
16
16
  realtime: RealtimeMeta | null;
17
+ scope: ScopeMeta | null;
18
+ }
19
+ /** Coarse provisioning tier a table's scope belongs to. */
20
+ export type ScopeTier = 'global' | 'database' | 'entity';
21
+ export interface ScopeMeta {
22
+ /** The provisioning scope, e.g. 'platform', 'app', 'database', 'org', 'user' */
23
+ scope: string;
24
+ /** Coarse bucket: global (platform/app), database, or entity */
25
+ tier: ScopeTier;
26
+ /** Inflected scope key column (e.g. 'databaseId', 'orgId'), or null for global tiers */
27
+ keyColumn: string | null;
28
+ /** SQL name of the entity table for entity scopes, else null */
29
+ entityTable: string | null;
30
+ /** Provenance of the scope metadata. Always the @scope smart tag. */
31
+ source: 'smartTag';
17
32
  }
18
33
  export interface StorageMeta {
19
34
  /** Whether this table is tagged as a storage files table */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-settings",
3
- "version": "5.15.0",
3
+ "version": "5.16.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "graphile settings",
6
6
  "main": "index.js",
@@ -31,16 +31,16 @@
31
31
  "dependencies": {
32
32
  "@aws-sdk/client-s3": "^3.1052.0",
33
33
  "@constructive-io/bucket-provisioner": "^0.14.0",
34
- "@constructive-io/graphql-env": "^3.16.0",
35
- "@constructive-io/graphql-types": "^3.14.0",
36
- "@constructive-io/s3-streamer": "^2.29.0",
34
+ "@constructive-io/graphql-env": "^3.16.2",
35
+ "@constructive-io/graphql-types": "^3.14.2",
36
+ "@constructive-io/s3-streamer": "^2.29.2",
37
37
  "@constructive-io/s3-utils": "^2.20.0",
38
38
  "@constructive-io/upload-names": "^2.19.0",
39
39
  "@dataplan/json": "1.0.0",
40
40
  "@dataplan/pg": "1.0.3",
41
41
  "@graphile-contrib/pg-many-to-many": "2.0.0-rc.2",
42
42
  "@pgpmjs/logger": "^2.14.0",
43
- "@pgpmjs/types": "^2.34.0",
43
+ "@pgpmjs/types": "^2.35.1",
44
44
  "@pgsql/quotes": "^17.1.0",
45
45
  "cors": "^2.8.6",
46
46
  "express": "^5.2.1",
@@ -49,17 +49,17 @@
49
49
  "graphile-bucket-provisioner-plugin": "0.14.0",
50
50
  "graphile-build": "5.0.2",
51
51
  "graphile-build-pg": "5.0.2",
52
- "graphile-bulk-mutations": "^0.9.0",
52
+ "graphile-bulk-mutations": "^0.9.2",
53
53
  "graphile-config": "1.0.1",
54
- "graphile-connection-filter": "^1.16.0",
55
- "graphile-i18n": "^1.6.0",
56
- "graphile-llm": "^0.19.0",
57
- "graphile-ltree": "^1.14.0",
58
- "graphile-pg-aggregates": "^1.10.0",
59
- "graphile-postgis": "^2.23.0",
60
- "graphile-presigned-url-plugin": "^0.22.0",
54
+ "graphile-connection-filter": "^1.16.2",
55
+ "graphile-i18n": "^1.6.2",
56
+ "graphile-llm": "^0.19.2",
57
+ "graphile-ltree": "^1.14.2",
58
+ "graphile-pg-aggregates": "^1.10.2",
59
+ "graphile-postgis": "^2.23.2",
60
+ "graphile-presigned-url-plugin": "^0.22.1",
61
61
  "graphile-realtime-subscriptions": "^0.10.0",
62
- "graphile-search": "^1.21.0",
62
+ "graphile-search": "^1.21.2",
63
63
  "graphile-upload-plugin": "^2.14.0",
64
64
  "graphile-utils": "5.0.1",
65
65
  "graphql": "16.13.0",
@@ -77,7 +77,7 @@
77
77
  "@types/express": "^5.0.6",
78
78
  "@types/pg": "^8.20.0",
79
79
  "@types/request-ip": "^0.0.41",
80
- "graphile-test": "^4.21.0",
80
+ "graphile-test": "^4.21.2",
81
81
  "makage": "^0.3.0",
82
82
  "nodemon": "^3.1.14",
83
83
  "ts-node": "^10.9.2"
@@ -89,5 +89,5 @@
89
89
  "constructive",
90
90
  "graphql"
91
91
  ],
92
- "gitHead": "ff6c442b789cbe8803f33036110f6e09124e65df"
92
+ "gitHead": "e53a570a4a70987e82dc074bd73501971fd82828"
93
93
  }
@@ -18,16 +18,16 @@ function createMetaSchemaType() {
18
18
  isArray: { type: nn(graphql_1.GraphQLBoolean) },
19
19
  isNotNull: { type: graphql_1.GraphQLBoolean },
20
20
  hasDefault: { type: graphql_1.GraphQLBoolean },
21
- subtype: { type: graphql_1.GraphQLString },
22
- }),
21
+ subtype: { type: graphql_1.GraphQLString }
22
+ })
23
23
  });
24
24
  const MetaEnumType = new graphql_1.GraphQLObjectType({
25
25
  name: 'MetaEnum',
26
26
  description: 'Information about a PostgreSQL enum type',
27
27
  fields: () => ({
28
28
  name: { type: nn(graphql_1.GraphQLString), description: 'The PostgreSQL enum type name' },
29
- values: { type: nnList(graphql_1.GraphQLString), description: 'Allowed values for this enum' },
30
- }),
29
+ values: { type: nnList(graphql_1.GraphQLString), description: 'Allowed values for this enum' }
30
+ })
31
31
  });
32
32
  const MetaFieldType = new graphql_1.GraphQLObjectType({
33
33
  name: 'MetaField',
@@ -40,8 +40,8 @@ function createMetaSchemaType() {
40
40
  isPrimaryKey: { type: nn(graphql_1.GraphQLBoolean) },
41
41
  isForeignKey: { type: nn(graphql_1.GraphQLBoolean) },
42
42
  description: { type: graphql_1.GraphQLString },
43
- enumValues: { type: MetaEnumType, description: 'Enum metadata if this field has an enum type' },
44
- }),
43
+ enumValues: { type: MetaEnumType, description: 'Enum metadata if this field has an enum type' }
44
+ })
45
45
  });
46
46
  const MetaIndexType = new graphql_1.GraphQLObjectType({
47
47
  name: 'MetaIndex',
@@ -51,31 +51,31 @@ function createMetaSchemaType() {
51
51
  isUnique: { type: nn(graphql_1.GraphQLBoolean) },
52
52
  isPrimary: { type: nn(graphql_1.GraphQLBoolean) },
53
53
  columns: { type: nnList(graphql_1.GraphQLString) },
54
- fields: { type: new graphql_1.GraphQLList(nn(MetaFieldType)) },
55
- }),
54
+ fields: { type: new graphql_1.GraphQLList(nn(MetaFieldType)) }
55
+ })
56
56
  });
57
57
  const MetaPrimaryKeyConstraintType = new graphql_1.GraphQLObjectType({
58
58
  name: 'MetaPrimaryKeyConstraint',
59
59
  description: 'Information about a primary key constraint',
60
60
  fields: () => ({
61
61
  name: { type: nn(graphql_1.GraphQLString) },
62
- fields: { type: nnList(MetaFieldType) },
63
- }),
62
+ fields: { type: nnList(MetaFieldType) }
63
+ })
64
64
  });
65
65
  const MetaUniqueConstraintType = new graphql_1.GraphQLObjectType({
66
66
  name: 'MetaUniqueConstraint',
67
67
  description: 'Information about a unique constraint',
68
68
  fields: () => ({
69
69
  name: { type: nn(graphql_1.GraphQLString) },
70
- fields: { type: nnList(MetaFieldType) },
71
- }),
70
+ fields: { type: nnList(MetaFieldType) }
71
+ })
72
72
  });
73
73
  const MetaRefTableType = new graphql_1.GraphQLObjectType({
74
74
  name: 'MetaRefTable',
75
75
  description: 'Reference to a related table',
76
76
  fields: () => ({
77
- name: { type: nn(graphql_1.GraphQLString) },
78
- }),
77
+ name: { type: nn(graphql_1.GraphQLString) }
78
+ })
79
79
  });
80
80
  const MetaForeignKeyConstraintType = new graphql_1.GraphQLObjectType({
81
81
  name: 'MetaForeignKeyConstraint',
@@ -86,8 +86,8 @@ function createMetaSchemaType() {
86
86
  referencedTable: { type: nn(graphql_1.GraphQLString) },
87
87
  referencedFields: { type: nnList(graphql_1.GraphQLString) },
88
88
  refFields: { type: new graphql_1.GraphQLList(nn(MetaFieldType)) },
89
- refTable: { type: MetaRefTableType },
90
- }),
89
+ refTable: { type: MetaRefTableType }
90
+ })
91
91
  });
92
92
  const MetaConstraintsType = new graphql_1.GraphQLObjectType({
93
93
  name: 'MetaConstraints',
@@ -95,8 +95,8 @@ function createMetaSchemaType() {
95
95
  fields: () => ({
96
96
  primaryKey: { type: MetaPrimaryKeyConstraintType },
97
97
  unique: { type: nnList(MetaUniqueConstraintType) },
98
- foreignKey: { type: nnList(MetaForeignKeyConstraintType) },
99
- }),
98
+ foreignKey: { type: nnList(MetaForeignKeyConstraintType) }
99
+ })
100
100
  });
101
101
  const MetaInflectionType = new graphql_1.GraphQLObjectType({
102
102
  name: 'MetaInflection',
@@ -113,8 +113,8 @@ function createMetaSchemaType() {
113
113
  createInputType: { type: nn(graphql_1.GraphQLString) },
114
114
  createPayloadType: { type: nn(graphql_1.GraphQLString) },
115
115
  updatePayloadType: { type: graphql_1.GraphQLString },
116
- deletePayloadType: { type: nn(graphql_1.GraphQLString) },
117
- }),
116
+ deletePayloadType: { type: nn(graphql_1.GraphQLString) }
117
+ })
118
118
  });
119
119
  const MetaQueryType = new graphql_1.GraphQLObjectType({
120
120
  name: 'MetaQuery',
@@ -124,8 +124,8 @@ function createMetaSchemaType() {
124
124
  one: { type: graphql_1.GraphQLString },
125
125
  create: { type: graphql_1.GraphQLString },
126
126
  update: { type: graphql_1.GraphQLString },
127
- delete: { type: graphql_1.GraphQLString },
128
- }),
127
+ delete: { type: graphql_1.GraphQLString }
128
+ })
129
129
  });
130
130
  const MetaBelongsToRelationType = new graphql_1.GraphQLObjectType({
131
131
  name: 'MetaBelongsToRelation',
@@ -135,8 +135,8 @@ function createMetaSchemaType() {
135
135
  isUnique: { type: nn(graphql_1.GraphQLBoolean) },
136
136
  type: { type: graphql_1.GraphQLString },
137
137
  keys: { type: nnList(MetaFieldType) },
138
- references: { type: nn(MetaRefTableType) },
139
- }),
138
+ references: { type: nn(MetaRefTableType) }
139
+ })
140
140
  });
141
141
  const MetaHasRelationType = new graphql_1.GraphQLObjectType({
142
142
  name: 'MetaHasRelation',
@@ -146,8 +146,8 @@ function createMetaSchemaType() {
146
146
  isUnique: { type: nn(graphql_1.GraphQLBoolean) },
147
147
  type: { type: graphql_1.GraphQLString },
148
148
  keys: { type: nnList(MetaFieldType) },
149
- referencedBy: { type: nn(MetaRefTableType) },
150
- }),
149
+ referencedBy: { type: nn(MetaRefTableType) }
150
+ })
151
151
  });
152
152
  const MetaManyToManyRelationType = new graphql_1.GraphQLObjectType({
153
153
  name: 'MetaManyToManyRelation',
@@ -162,8 +162,8 @@ function createMetaSchemaType() {
162
162
  junctionRightKeyAttributes: { type: nnList(MetaFieldType) },
163
163
  leftKeyAttributes: { type: nnList(MetaFieldType) },
164
164
  rightKeyAttributes: { type: nnList(MetaFieldType) },
165
- rightTable: { type: nn(MetaRefTableType) },
166
- }),
165
+ rightTable: { type: nn(MetaRefTableType) }
166
+ })
167
167
  });
168
168
  const MetaRelationsType = new graphql_1.GraphQLObjectType({
169
169
  name: 'MetaRelations',
@@ -173,16 +173,16 @@ function createMetaSchemaType() {
173
173
  has: { type: nnList(MetaHasRelationType) },
174
174
  hasOne: { type: nnList(MetaHasRelationType) },
175
175
  hasMany: { type: nnList(MetaHasRelationType) },
176
- manyToMany: { type: nnList(MetaManyToManyRelationType) },
177
- }),
176
+ manyToMany: { type: nnList(MetaManyToManyRelationType) }
177
+ })
178
178
  });
179
179
  const MetaStorageType = new graphql_1.GraphQLObjectType({
180
180
  name: 'MetaStorage',
181
181
  description: 'Storage metadata for a table',
182
182
  fields: () => ({
183
183
  isFilesTable: { type: nn(graphql_1.GraphQLBoolean), description: 'Whether this table is a storage files table' },
184
- isBucketsTable: { type: nn(graphql_1.GraphQLBoolean), description: 'Whether this table is a storage buckets table' },
185
- }),
184
+ isBucketsTable: { type: nn(graphql_1.GraphQLBoolean), description: 'Whether this table is a storage buckets table' }
185
+ })
186
186
  });
187
187
  const MetaSearchConfigType = new graphql_1.GraphQLObjectType({
188
188
  name: 'MetaSearchConfig',
@@ -193,20 +193,20 @@ function createMetaSchemaType() {
193
193
  description: 'JSON-encoded per-adapter score weights',
194
194
  resolve(source) {
195
195
  return source.weights ? JSON.stringify(source.weights) : null;
196
- },
196
+ }
197
197
  },
198
198
  boostRecent: { type: nn(graphql_1.GraphQLBoolean), description: 'Whether recency boosting is enabled' },
199
199
  boostRecencyField: { type: graphql_1.GraphQLString, description: 'Field used for recency decay' },
200
- boostRecencyDecay: { type: graphql_1.GraphQLFloat, description: 'Exponential decay factor per day' },
201
- }),
200
+ boostRecencyDecay: { type: graphql_1.GraphQLFloat, description: 'Exponential decay factor per day' }
201
+ })
202
202
  });
203
203
  const MetaSearchColumnType = new graphql_1.GraphQLObjectType({
204
204
  name: 'MetaSearchColumn',
205
205
  description: 'A searchable column with its algorithm',
206
206
  fields: () => ({
207
207
  name: { type: nn(graphql_1.GraphQLString), description: 'Column name (camelCase)' },
208
- algorithm: { type: nn(graphql_1.GraphQLString), description: 'Search algorithm: tsvector, bm25, trgm, or vector' },
209
- }),
208
+ algorithm: { type: nn(graphql_1.GraphQLString), description: 'Search algorithm: tsvector, bm25, trgm, or vector' }
209
+ })
210
210
  });
211
211
  const MetaSearchType = new graphql_1.GraphQLObjectType({
212
212
  name: 'MetaSearch',
@@ -215,31 +215,42 @@ function createMetaSchemaType() {
215
215
  algorithms: { type: nnList(graphql_1.GraphQLString), description: 'Active search algorithms on this table' },
216
216
  columns: { type: nnList(MetaSearchColumnType), description: 'Searchable columns with their algorithm' },
217
217
  hasUnifiedSearch: { type: nn(graphql_1.GraphQLBoolean), description: 'Whether unifiedSearch composite filter is available' },
218
- config: { type: MetaSearchConfigType, description: 'Per-table search configuration' },
219
- }),
218
+ config: { type: MetaSearchConfigType, description: 'Per-table search configuration' }
219
+ })
220
220
  });
221
221
  const MetaI18nFieldType = new graphql_1.GraphQLObjectType({
222
222
  name: 'MetaI18nField',
223
223
  description: 'A translatable field',
224
224
  fields: () => ({
225
225
  name: { type: nn(graphql_1.GraphQLString), description: 'GraphQL field name' },
226
- type: { type: nn(graphql_1.GraphQLString), description: 'PostgreSQL column type (text, citext)' },
227
- }),
226
+ type: { type: nn(graphql_1.GraphQLString), description: 'PostgreSQL column type (text, citext)' }
227
+ })
228
228
  });
229
229
  const MetaI18nType = new graphql_1.GraphQLObjectType({
230
230
  name: 'MetaI18n',
231
231
  description: 'i18n metadata for a table with @i18n tag',
232
232
  fields: () => ({
233
233
  translationTable: { type: nn(graphql_1.GraphQLString), description: 'Name of the translation table' },
234
- translatableFields: { type: nnList(MetaI18nFieldType), description: 'Fields that are translatable' },
235
- }),
234
+ translatableFields: { type: nnList(MetaI18nFieldType), description: 'Fields that are translatable' }
235
+ })
236
236
  });
237
237
  const MetaRealtimeType = new graphql_1.GraphQLObjectType({
238
238
  name: 'MetaRealtime',
239
239
  description: 'Realtime metadata for a table with @realtime tag',
240
240
  fields: () => ({
241
- subscriptionFieldName: { type: nn(graphql_1.GraphQLString), description: 'The generated subscription field name (e.g. onPostChanged)' },
242
- }),
241
+ subscriptionFieldName: { type: nn(graphql_1.GraphQLString), description: 'The generated subscription field name (e.g. onPostChanged)' }
242
+ })
243
+ });
244
+ const MetaScopeType = new graphql_1.GraphQLObjectType({
245
+ name: 'MetaScope',
246
+ description: 'Provisioning scope metadata for a table',
247
+ fields: () => ({
248
+ scope: { type: nn(graphql_1.GraphQLString), description: "Provisioning scope: 'platform', 'app', 'database', or an entity scope (e.g. 'org')" },
249
+ tier: { type: nn(graphql_1.GraphQLString), description: "Coarse bucket: 'global', 'database', or 'entity'" },
250
+ keyColumn: { type: graphql_1.GraphQLString, description: 'Inflected scope key column (e.g. databaseId, orgId), null for global tiers' },
251
+ entityTable: { type: graphql_1.GraphQLString, description: 'SQL name of the entity table for entity scopes, else null' },
252
+ source: { type: nn(graphql_1.GraphQLString), description: "Provenance of the scope metadata (always 'smartTag')" }
253
+ })
243
254
  });
244
255
  const MetaTableType = new graphql_1.GraphQLObjectType({
245
256
  name: 'MetaTable',
@@ -260,14 +271,15 @@ function createMetaSchemaType() {
260
271
  search: { type: MetaSearchType, description: 'Search metadata (null if no search configured)' },
261
272
  i18n: { type: MetaI18nType, description: 'i18n metadata (null if no @i18n tag)' },
262
273
  realtime: { type: MetaRealtimeType, description: 'Realtime metadata (null if no @realtime tag)' },
263
- }),
274
+ scope: { type: MetaScopeType, description: 'Provisioning scope metadata (null if no @scope tag)' }
275
+ })
264
276
  });
265
277
  return new graphql_1.GraphQLObjectType({
266
278
  name: 'MetaSchema',
267
279
  description: 'Root meta schema type',
268
280
  fields: () => ({
269
- tables: { type: nnList(MetaTableType) },
270
- }),
281
+ tables: { type: nnList(MetaTableType) }
282
+ })
271
283
  });
272
284
  }
273
285
  function extendQueryWithMetaField(fields, tablesMeta) {
@@ -277,10 +289,10 @@ function extendQueryWithMetaField(fields, tablesMeta) {
277
289
  description: 'Metadata about the database schema, including tables, fields, indexes, and constraints. Useful for code generation tools.',
278
290
  resolve() {
279
291
  return { tables: tablesMeta };
280
- },
292
+ }
281
293
  };
282
294
  return {
283
295
  ...fields,
284
- _meta: metaField,
296
+ _meta: metaField
285
297
  };
286
298
  }
@@ -0,0 +1,15 @@
1
+ import type { PgCodec, ScopeMeta } from './types';
2
+ /**
3
+ * Detect provisioning scope metadata for a table.
4
+ *
5
+ * The `@scope` smart tag emitted by the constructive-db generators is the sole
6
+ * source of truth. `apply_scope_fields` knows the exact scope, tier, key column,
7
+ * and entity table, and emits them via
8
+ * `metaschema.append_table_smart_tags(id, { scope, scopeTier, scopeKey, scopeEntityTable })`.
9
+ * PostGraphile surfaces these as `codec.extensions.tags.*`.
10
+ *
11
+ * There is NO inference from column names — a table without the tag returns
12
+ * `null` (the same convention as every other nullable metadata block). This
13
+ * avoids guessing key columns, whose names are chosen freely by each generator.
14
+ */
15
+ export declare function buildScopeMeta(codec: PgCodec, inflectAttr: (attrName: string, codec: PgCodec) => string): ScopeMeta | null;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildScopeMeta = buildScopeMeta;
4
+ const VALID_TIERS = new Set(['global', 'database', 'entity']);
5
+ function readString(value) {
6
+ return typeof value === 'string' && value.length > 0 ? value : null;
7
+ }
8
+ /**
9
+ * Detect provisioning scope metadata for a table.
10
+ *
11
+ * The `@scope` smart tag emitted by the constructive-db generators is the sole
12
+ * source of truth. `apply_scope_fields` knows the exact scope, tier, key column,
13
+ * and entity table, and emits them via
14
+ * `metaschema.append_table_smart_tags(id, { scope, scopeTier, scopeKey, scopeEntityTable })`.
15
+ * PostGraphile surfaces these as `codec.extensions.tags.*`.
16
+ *
17
+ * There is NO inference from column names — a table without the tag returns
18
+ * `null` (the same convention as every other nullable metadata block). This
19
+ * avoids guessing key columns, whose names are chosen freely by each generator.
20
+ */
21
+ function buildScopeMeta(codec, inflectAttr) {
22
+ const tags = codec.extensions?.tags;
23
+ const scope = readString(tags?.scope);
24
+ if (!scope)
25
+ return null;
26
+ const rawTier = readString(tags.scopeTier);
27
+ const tier = rawTier && VALID_TIERS.has(rawTier)
28
+ ? rawTier
29
+ : null;
30
+ if (!tier) {
31
+ throw new Error(`@scope tag on codec "${codec.name}" is missing a valid scopeTier ` +
32
+ `(expected one of global|database|entity, got ${JSON.stringify(rawTier)})`);
33
+ }
34
+ const keyColumn = readString(tags.scopeKey);
35
+ const entityTable = readString(tags.scopeEntityTable);
36
+ return {
37
+ scope,
38
+ tier,
39
+ keyColumn: keyColumn ? inflectAttr(keyColumn, codec) : null,
40
+ entityTable,
41
+ source: 'smartTag'
42
+ };
43
+ }
@@ -4,10 +4,11 @@ exports.collectTablesMeta = collectTablesMeta;
4
4
  const constraint_meta_builders_1 = require("./constraint-meta-builders");
5
5
  const name_meta_builders_1 = require("./name-meta-builders");
6
6
  const relation_meta_builders_1 = require("./relation-meta-builders");
7
+ const scope_meta_builders_1 = require("./scope-meta-builders");
7
8
  const storage_search_meta_builders_1 = require("./storage-search-meta-builders");
8
- const type_mappings_1 = require("./type-mappings");
9
9
  const table_meta_context_1 = require("./table-meta-context");
10
10
  const table_resource_utils_1 = require("./table-resource-utils");
11
+ const type_mappings_1 = require("./type-mappings");
11
12
  function buildTableMeta(resource, schemaName, context) {
12
13
  const codec = resource.codec;
13
14
  const attributes = codec.attributes;
@@ -30,7 +31,7 @@ function buildTableMeta(resource, schemaName, context) {
30
31
  }
31
32
  const fields = Object.entries(attributes).map(([attrName, attr]) => (0, type_mappings_1.buildFieldMeta)(context.inflectAttr(attrName, codec), attr, context.build, {
32
33
  isPrimaryKey: pkAttrNames.has(attrName),
33
- isForeignKey: fkAttrNames.has(attrName),
34
+ isForeignKey: fkAttrNames.has(attrName)
34
35
  }));
35
36
  const indexes = (0, constraint_meta_builders_1.buildIndexes)(codec, attributes, uniques, context);
36
37
  const primaryKey = (0, constraint_meta_builders_1.buildPrimaryKey)(codec, attributes, uniques, context);
@@ -39,7 +40,7 @@ function buildTableMeta(resource, schemaName, context) {
39
40
  const constraints = {
40
41
  primaryKey,
41
42
  unique: uniqueConstraints,
42
- foreignKey: foreignKeyConstraints,
43
+ foreignKey: foreignKeyConstraints
43
44
  };
44
45
  const belongsTo = (0, relation_meta_builders_1.buildBelongsToRelations)(codec, attributes, uniques, relations, context);
45
46
  const { hasOne, hasMany } = (0, relation_meta_builders_1.buildReverseRelations)(codec, attributes, relations, context);
@@ -49,13 +50,14 @@ function buildTableMeta(resource, schemaName, context) {
49
50
  has: [...hasOne, ...hasMany],
50
51
  hasOne,
51
52
  hasMany,
52
- manyToMany,
53
+ manyToMany
53
54
  };
54
55
  const tableType = (0, name_meta_builders_1.resolveTableType)(context.build, codec);
55
56
  const storage = (0, storage_search_meta_builders_1.buildStorageMeta)(codec);
56
57
  const search = (0, storage_search_meta_builders_1.buildSearchMeta)(codec, context.build, context.inflectAttr);
57
58
  const i18n = (0, storage_search_meta_builders_1.buildI18nMeta)(codec, context.build, context.inflectAttr);
58
59
  const realtime = (0, storage_search_meta_builders_1.buildRealtimeMeta)(codec, context.build);
60
+ const scope = (0, scope_meta_builders_1.buildScopeMeta)(codec, context.inflectAttr);
59
61
  return {
60
62
  name: tableType,
61
63
  schemaName,
@@ -72,6 +74,7 @@ function buildTableMeta(resource, schemaName, context) {
72
74
  search,
73
75
  i18n,
74
76
  realtime,
77
+ scope
75
78
  };
76
79
  }
77
80
  function collectTablesMeta(build) {
@@ -14,6 +14,21 @@ export interface TableMeta {
14
14
  search: SearchMeta | null;
15
15
  i18n: I18nMeta | null;
16
16
  realtime: RealtimeMeta | null;
17
+ scope: ScopeMeta | null;
18
+ }
19
+ /** Coarse provisioning tier a table's scope belongs to. */
20
+ export type ScopeTier = 'global' | 'database' | 'entity';
21
+ export interface ScopeMeta {
22
+ /** The provisioning scope, e.g. 'platform', 'app', 'database', 'org', 'user' */
23
+ scope: string;
24
+ /** Coarse bucket: global (platform/app), database, or entity */
25
+ tier: ScopeTier;
26
+ /** Inflected scope key column (e.g. 'databaseId', 'orgId'), or null for global tiers */
27
+ keyColumn: string | null;
28
+ /** SQL name of the entity table for entity scopes, else null */
29
+ entityTable: string | null;
30
+ /** Provenance of the scope metadata. Always the @scope smart tag. */
31
+ source: 'smartTag';
17
32
  }
18
33
  export interface StorageMeta {
19
34
  /** Whether this table is tagged as a storage files table */