graphile-settings 5.15.1 → 5.17.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/esm/plugins/meta-schema/encoding-meta-builders.d.ts +2 -0
- package/esm/plugins/meta-schema/encoding-meta-builders.js +84 -0
- package/esm/plugins/meta-schema/graphql-meta-field.js +81 -50
- package/esm/plugins/meta-schema/scope-meta-builders.d.ts +15 -0
- package/esm/plugins/meta-schema/scope-meta-builders.js +40 -0
- package/esm/plugins/meta-schema/table-meta-builder.js +11 -8
- package/esm/plugins/meta-schema/type-mappings.js +2 -0
- package/esm/plugins/meta-schema/types.d.ts +45 -0
- package/esm/plugins/meta-schema.d.ts +3 -0
- package/esm/plugins/meta-schema.js +3 -0
- package/package.json +15 -15
- package/plugins/meta-schema/encoding-meta-builders.d.ts +2 -0
- package/plugins/meta-schema/encoding-meta-builders.js +87 -0
- package/plugins/meta-schema/graphql-meta-field.js +80 -49
- package/plugins/meta-schema/scope-meta-builders.d.ts +15 -0
- package/plugins/meta-schema/scope-meta-builders.js +43 -0
- package/plugins/meta-schema/table-meta-builder.js +7 -4
- package/plugins/meta-schema/type-mappings.js +2 -0
- package/plugins/meta-schema/types.d.ts +45 -0
- package/plugins/meta-schema.d.ts +3 -0
- package/plugins/meta-schema.js +3 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scalar encoding contracts — the one piece of field metadata standard GraphQL
|
|
3
|
+
* introspection can't provide.
|
|
4
|
+
*
|
|
5
|
+
* Introspection tells a client a field's scalar *name* (`GeoJSON`, `LTree`,
|
|
6
|
+
* `Interval`, `BigInt`, `vector`, …) but not how to serialize/parse it or its
|
|
7
|
+
* structural parameters (geometry subtype/SRID, vector dimensions, ltree
|
|
8
|
+
* dot-path semantics). Without that, generated clients treat these as opaque
|
|
9
|
+
* strings / `any` and mis-encode inputs or fail to parse outputs.
|
|
10
|
+
*
|
|
11
|
+
* `kind` is derived deterministically from the *underlying* codec (array and
|
|
12
|
+
* domain wrappers unwrapped first, first match wins) so exactly one kind is
|
|
13
|
+
* ever assigned and nothing can clobber it. Structural extras (subtype, SRID,
|
|
14
|
+
* dimensions) are read verbatim from attribute extensions — never guessed, and
|
|
15
|
+
* `null` when absent.
|
|
16
|
+
*/
|
|
17
|
+
/** pg type name → encoding kind. Plain scalars are intentionally absent → null. */
|
|
18
|
+
const KIND_BY_PG_TYPE = {
|
|
19
|
+
int8: 'bigint',
|
|
20
|
+
bigint: 'bigint',
|
|
21
|
+
numeric: 'bigint',
|
|
22
|
+
timestamptz: 'datetime',
|
|
23
|
+
timestamp: 'datetime',
|
|
24
|
+
date: 'date',
|
|
25
|
+
time: 'time',
|
|
26
|
+
timetz: 'time',
|
|
27
|
+
interval: 'interval',
|
|
28
|
+
uuid: 'uuid',
|
|
29
|
+
geometry: 'geojson',
|
|
30
|
+
geography: 'geojson',
|
|
31
|
+
point: 'point',
|
|
32
|
+
inet: 'inet',
|
|
33
|
+
cidr: 'inet',
|
|
34
|
+
ltree: 'ltree',
|
|
35
|
+
vector: 'vector',
|
|
36
|
+
bytea: 'bytea'
|
|
37
|
+
};
|
|
38
|
+
/** Unwrap array-of and domain-of wrappers to the underlying scalar/record codec. */
|
|
39
|
+
function underlyingCodec(codec) {
|
|
40
|
+
let current = codec ?? null;
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
while (current && !seen.has(current) && (current.arrayOfCodec || current.domainOfCodec)) {
|
|
43
|
+
seen.add(current);
|
|
44
|
+
current = current.arrayOfCodec || current.domainOfCodec || null;
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
/** True when a codec is a composite/record type (has named attributes). */
|
|
49
|
+
function isCompositeCodec(codec) {
|
|
50
|
+
return !!codec?.attributes && Object.keys(codec.attributes).length > 0;
|
|
51
|
+
}
|
|
52
|
+
export function buildScalarEncoding(attr) {
|
|
53
|
+
const codec = underlyingCodec(attr?.codec);
|
|
54
|
+
if (!codec)
|
|
55
|
+
return null;
|
|
56
|
+
const ext = attr?.extensions ?? undefined;
|
|
57
|
+
const kind = KIND_BY_PG_TYPE[codec.name];
|
|
58
|
+
if (!kind) {
|
|
59
|
+
// Composite/record types are the only non-mapped scalars worth describing;
|
|
60
|
+
// everything else (text, int, bool, json, enum, …) is fully covered by gqlType.
|
|
61
|
+
if (isCompositeCodec(codec)) {
|
|
62
|
+
return { kind: 'composite', fields: null };
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
switch (kind) {
|
|
67
|
+
case 'geojson':
|
|
68
|
+
return {
|
|
69
|
+
kind,
|
|
70
|
+
geometrySubtype: typeof ext?.geometrySubtype === 'string' ? ext.geometrySubtype : null,
|
|
71
|
+
srid: typeof ext?.geometrySrid === 'number' ? ext.geometrySrid : null
|
|
72
|
+
};
|
|
73
|
+
case 'vector':
|
|
74
|
+
return {
|
|
75
|
+
kind,
|
|
76
|
+
elementType: 'float',
|
|
77
|
+
dimensions: typeof ext?.vectorDimensions === 'number' ? ext.vectorDimensions : null
|
|
78
|
+
};
|
|
79
|
+
case 'ltree':
|
|
80
|
+
return { kind, dotPath: true };
|
|
81
|
+
default:
|
|
82
|
+
return { kind };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GraphQLBoolean, GraphQLFloat, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString
|
|
1
|
+
import { GraphQLBoolean, GraphQLFloat, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
|
|
2
2
|
function nn(type) {
|
|
3
3
|
return new GraphQLNonNull(type);
|
|
4
4
|
}
|
|
@@ -6,6 +6,21 @@ function nnList(type) {
|
|
|
6
6
|
return nn(new GraphQLList(nn(type)));
|
|
7
7
|
}
|
|
8
8
|
function createMetaSchemaType() {
|
|
9
|
+
const MetaScalarEncodingType = new GraphQLObjectType({
|
|
10
|
+
name: 'MetaScalarEncoding',
|
|
11
|
+
description: 'How a client must serialize/parse a scalar — the one field-type detail standard GraphQL introspection cannot describe. Null for plain scalars whose wire format is obvious from gqlType.',
|
|
12
|
+
fields: () => ({
|
|
13
|
+
kind: {
|
|
14
|
+
type: nn(GraphQLString),
|
|
15
|
+
description: "Machine kind: bigint, datetime, date, time, interval, uuid, geojson, point, inet, ltree, vector, bytea, or composite."
|
|
16
|
+
},
|
|
17
|
+
elementType: { type: GraphQLString, description: "For 'vector': element scalar (e.g. 'float')." },
|
|
18
|
+
dimensions: { type: GraphQLInt, description: "For 'vector': declared length, else null." },
|
|
19
|
+
geometrySubtype: { type: GraphQLString, description: "For 'geojson': Point/LineString/Polygon/…, else null." },
|
|
20
|
+
srid: { type: GraphQLInt, description: "For 'geojson': spatial reference id, else null." },
|
|
21
|
+
dotPath: { type: GraphQLBoolean, description: "For 'ltree': values are dot-separated path strings." }
|
|
22
|
+
})
|
|
23
|
+
});
|
|
9
24
|
const MetaTypeType = new GraphQLObjectType({
|
|
10
25
|
name: 'MetaType',
|
|
11
26
|
description: 'Information about a PostgreSQL type',
|
|
@@ -16,15 +31,19 @@ function createMetaSchemaType() {
|
|
|
16
31
|
isNotNull: { type: GraphQLBoolean },
|
|
17
32
|
hasDefault: { type: GraphQLBoolean },
|
|
18
33
|
subtype: { type: GraphQLString },
|
|
19
|
-
|
|
34
|
+
encoding: {
|
|
35
|
+
type: MetaScalarEncodingType,
|
|
36
|
+
description: 'Scalar serialization contract (null for plain scalars)'
|
|
37
|
+
}
|
|
38
|
+
})
|
|
20
39
|
});
|
|
21
40
|
const MetaEnumType = new GraphQLObjectType({
|
|
22
41
|
name: 'MetaEnum',
|
|
23
42
|
description: 'Information about a PostgreSQL enum type',
|
|
24
43
|
fields: () => ({
|
|
25
44
|
name: { type: nn(GraphQLString), description: 'The PostgreSQL enum type name' },
|
|
26
|
-
values: { type: nnList(GraphQLString), description: 'Allowed values for this enum' }
|
|
27
|
-
})
|
|
45
|
+
values: { type: nnList(GraphQLString), description: 'Allowed values for this enum' }
|
|
46
|
+
})
|
|
28
47
|
});
|
|
29
48
|
const MetaFieldType = new GraphQLObjectType({
|
|
30
49
|
name: 'MetaField',
|
|
@@ -37,8 +56,8 @@ function createMetaSchemaType() {
|
|
|
37
56
|
isPrimaryKey: { type: nn(GraphQLBoolean) },
|
|
38
57
|
isForeignKey: { type: nn(GraphQLBoolean) },
|
|
39
58
|
description: { type: GraphQLString },
|
|
40
|
-
enumValues: { type: MetaEnumType, description: 'Enum metadata if this field has an enum type' }
|
|
41
|
-
})
|
|
59
|
+
enumValues: { type: MetaEnumType, description: 'Enum metadata if this field has an enum type' }
|
|
60
|
+
})
|
|
42
61
|
});
|
|
43
62
|
const MetaIndexType = new GraphQLObjectType({
|
|
44
63
|
name: 'MetaIndex',
|
|
@@ -48,31 +67,31 @@ function createMetaSchemaType() {
|
|
|
48
67
|
isUnique: { type: nn(GraphQLBoolean) },
|
|
49
68
|
isPrimary: { type: nn(GraphQLBoolean) },
|
|
50
69
|
columns: { type: nnList(GraphQLString) },
|
|
51
|
-
fields: { type: new GraphQLList(nn(MetaFieldType)) }
|
|
52
|
-
})
|
|
70
|
+
fields: { type: new GraphQLList(nn(MetaFieldType)) }
|
|
71
|
+
})
|
|
53
72
|
});
|
|
54
73
|
const MetaPrimaryKeyConstraintType = new GraphQLObjectType({
|
|
55
74
|
name: 'MetaPrimaryKeyConstraint',
|
|
56
75
|
description: 'Information about a primary key constraint',
|
|
57
76
|
fields: () => ({
|
|
58
77
|
name: { type: nn(GraphQLString) },
|
|
59
|
-
fields: { type: nnList(MetaFieldType) }
|
|
60
|
-
})
|
|
78
|
+
fields: { type: nnList(MetaFieldType) }
|
|
79
|
+
})
|
|
61
80
|
});
|
|
62
81
|
const MetaUniqueConstraintType = new GraphQLObjectType({
|
|
63
82
|
name: 'MetaUniqueConstraint',
|
|
64
83
|
description: 'Information about a unique constraint',
|
|
65
84
|
fields: () => ({
|
|
66
85
|
name: { type: nn(GraphQLString) },
|
|
67
|
-
fields: { type: nnList(MetaFieldType) }
|
|
68
|
-
})
|
|
86
|
+
fields: { type: nnList(MetaFieldType) }
|
|
87
|
+
})
|
|
69
88
|
});
|
|
70
89
|
const MetaRefTableType = new GraphQLObjectType({
|
|
71
90
|
name: 'MetaRefTable',
|
|
72
91
|
description: 'Reference to a related table',
|
|
73
92
|
fields: () => ({
|
|
74
|
-
name: { type: nn(GraphQLString) }
|
|
75
|
-
})
|
|
93
|
+
name: { type: nn(GraphQLString) }
|
|
94
|
+
})
|
|
76
95
|
});
|
|
77
96
|
const MetaForeignKeyConstraintType = new GraphQLObjectType({
|
|
78
97
|
name: 'MetaForeignKeyConstraint',
|
|
@@ -83,8 +102,8 @@ function createMetaSchemaType() {
|
|
|
83
102
|
referencedTable: { type: nn(GraphQLString) },
|
|
84
103
|
referencedFields: { type: nnList(GraphQLString) },
|
|
85
104
|
refFields: { type: new GraphQLList(nn(MetaFieldType)) },
|
|
86
|
-
refTable: { type: MetaRefTableType }
|
|
87
|
-
})
|
|
105
|
+
refTable: { type: MetaRefTableType }
|
|
106
|
+
})
|
|
88
107
|
});
|
|
89
108
|
const MetaConstraintsType = new GraphQLObjectType({
|
|
90
109
|
name: 'MetaConstraints',
|
|
@@ -92,8 +111,8 @@ function createMetaSchemaType() {
|
|
|
92
111
|
fields: () => ({
|
|
93
112
|
primaryKey: { type: MetaPrimaryKeyConstraintType },
|
|
94
113
|
unique: { type: nnList(MetaUniqueConstraintType) },
|
|
95
|
-
foreignKey: { type: nnList(MetaForeignKeyConstraintType) }
|
|
96
|
-
})
|
|
114
|
+
foreignKey: { type: nnList(MetaForeignKeyConstraintType) }
|
|
115
|
+
})
|
|
97
116
|
});
|
|
98
117
|
const MetaInflectionType = new GraphQLObjectType({
|
|
99
118
|
name: 'MetaInflection',
|
|
@@ -110,8 +129,8 @@ function createMetaSchemaType() {
|
|
|
110
129
|
createInputType: { type: nn(GraphQLString) },
|
|
111
130
|
createPayloadType: { type: nn(GraphQLString) },
|
|
112
131
|
updatePayloadType: { type: GraphQLString },
|
|
113
|
-
deletePayloadType: { type: nn(GraphQLString) }
|
|
114
|
-
})
|
|
132
|
+
deletePayloadType: { type: nn(GraphQLString) }
|
|
133
|
+
})
|
|
115
134
|
});
|
|
116
135
|
const MetaQueryType = new GraphQLObjectType({
|
|
117
136
|
name: 'MetaQuery',
|
|
@@ -121,8 +140,8 @@ function createMetaSchemaType() {
|
|
|
121
140
|
one: { type: GraphQLString },
|
|
122
141
|
create: { type: GraphQLString },
|
|
123
142
|
update: { type: GraphQLString },
|
|
124
|
-
delete: { type: GraphQLString }
|
|
125
|
-
})
|
|
143
|
+
delete: { type: GraphQLString }
|
|
144
|
+
})
|
|
126
145
|
});
|
|
127
146
|
const MetaBelongsToRelationType = new GraphQLObjectType({
|
|
128
147
|
name: 'MetaBelongsToRelation',
|
|
@@ -132,8 +151,8 @@ function createMetaSchemaType() {
|
|
|
132
151
|
isUnique: { type: nn(GraphQLBoolean) },
|
|
133
152
|
type: { type: GraphQLString },
|
|
134
153
|
keys: { type: nnList(MetaFieldType) },
|
|
135
|
-
references: { type: nn(MetaRefTableType) }
|
|
136
|
-
})
|
|
154
|
+
references: { type: nn(MetaRefTableType) }
|
|
155
|
+
})
|
|
137
156
|
});
|
|
138
157
|
const MetaHasRelationType = new GraphQLObjectType({
|
|
139
158
|
name: 'MetaHasRelation',
|
|
@@ -143,8 +162,8 @@ function createMetaSchemaType() {
|
|
|
143
162
|
isUnique: { type: nn(GraphQLBoolean) },
|
|
144
163
|
type: { type: GraphQLString },
|
|
145
164
|
keys: { type: nnList(MetaFieldType) },
|
|
146
|
-
referencedBy: { type: nn(MetaRefTableType) }
|
|
147
|
-
})
|
|
165
|
+
referencedBy: { type: nn(MetaRefTableType) }
|
|
166
|
+
})
|
|
148
167
|
});
|
|
149
168
|
const MetaManyToManyRelationType = new GraphQLObjectType({
|
|
150
169
|
name: 'MetaManyToManyRelation',
|
|
@@ -159,8 +178,8 @@ function createMetaSchemaType() {
|
|
|
159
178
|
junctionRightKeyAttributes: { type: nnList(MetaFieldType) },
|
|
160
179
|
leftKeyAttributes: { type: nnList(MetaFieldType) },
|
|
161
180
|
rightKeyAttributes: { type: nnList(MetaFieldType) },
|
|
162
|
-
rightTable: { type: nn(MetaRefTableType) }
|
|
163
|
-
})
|
|
181
|
+
rightTable: { type: nn(MetaRefTableType) }
|
|
182
|
+
})
|
|
164
183
|
});
|
|
165
184
|
const MetaRelationsType = new GraphQLObjectType({
|
|
166
185
|
name: 'MetaRelations',
|
|
@@ -170,16 +189,16 @@ function createMetaSchemaType() {
|
|
|
170
189
|
has: { type: nnList(MetaHasRelationType) },
|
|
171
190
|
hasOne: { type: nnList(MetaHasRelationType) },
|
|
172
191
|
hasMany: { type: nnList(MetaHasRelationType) },
|
|
173
|
-
manyToMany: { type: nnList(MetaManyToManyRelationType) }
|
|
174
|
-
})
|
|
192
|
+
manyToMany: { type: nnList(MetaManyToManyRelationType) }
|
|
193
|
+
})
|
|
175
194
|
});
|
|
176
195
|
const MetaStorageType = new GraphQLObjectType({
|
|
177
196
|
name: 'MetaStorage',
|
|
178
197
|
description: 'Storage metadata for a table',
|
|
179
198
|
fields: () => ({
|
|
180
199
|
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
|
-
})
|
|
200
|
+
isBucketsTable: { type: nn(GraphQLBoolean), description: 'Whether this table is a storage buckets table' }
|
|
201
|
+
})
|
|
183
202
|
});
|
|
184
203
|
const MetaSearchConfigType = new GraphQLObjectType({
|
|
185
204
|
name: 'MetaSearchConfig',
|
|
@@ -190,20 +209,20 @@ function createMetaSchemaType() {
|
|
|
190
209
|
description: 'JSON-encoded per-adapter score weights',
|
|
191
210
|
resolve(source) {
|
|
192
211
|
return source.weights ? JSON.stringify(source.weights) : null;
|
|
193
|
-
}
|
|
212
|
+
}
|
|
194
213
|
},
|
|
195
214
|
boostRecent: { type: nn(GraphQLBoolean), description: 'Whether recency boosting is enabled' },
|
|
196
215
|
boostRecencyField: { type: GraphQLString, description: 'Field used for recency decay' },
|
|
197
|
-
boostRecencyDecay: { type: GraphQLFloat, description: 'Exponential decay factor per day' }
|
|
198
|
-
})
|
|
216
|
+
boostRecencyDecay: { type: GraphQLFloat, description: 'Exponential decay factor per day' }
|
|
217
|
+
})
|
|
199
218
|
});
|
|
200
219
|
const MetaSearchColumnType = new GraphQLObjectType({
|
|
201
220
|
name: 'MetaSearchColumn',
|
|
202
221
|
description: 'A searchable column with its algorithm',
|
|
203
222
|
fields: () => ({
|
|
204
223
|
name: { type: nn(GraphQLString), description: 'Column name (camelCase)' },
|
|
205
|
-
algorithm: { type: nn(GraphQLString), description: 'Search algorithm: tsvector, bm25, trgm, or vector' }
|
|
206
|
-
})
|
|
224
|
+
algorithm: { type: nn(GraphQLString), description: 'Search algorithm: tsvector, bm25, trgm, or vector' }
|
|
225
|
+
})
|
|
207
226
|
});
|
|
208
227
|
const MetaSearchType = new GraphQLObjectType({
|
|
209
228
|
name: 'MetaSearch',
|
|
@@ -212,31 +231,42 @@ function createMetaSchemaType() {
|
|
|
212
231
|
algorithms: { type: nnList(GraphQLString), description: 'Active search algorithms on this table' },
|
|
213
232
|
columns: { type: nnList(MetaSearchColumnType), description: 'Searchable columns with their algorithm' },
|
|
214
233
|
hasUnifiedSearch: { type: nn(GraphQLBoolean), description: 'Whether unifiedSearch composite filter is available' },
|
|
215
|
-
config: { type: MetaSearchConfigType, description: 'Per-table search configuration' }
|
|
216
|
-
})
|
|
234
|
+
config: { type: MetaSearchConfigType, description: 'Per-table search configuration' }
|
|
235
|
+
})
|
|
217
236
|
});
|
|
218
237
|
const MetaI18nFieldType = new GraphQLObjectType({
|
|
219
238
|
name: 'MetaI18nField',
|
|
220
239
|
description: 'A translatable field',
|
|
221
240
|
fields: () => ({
|
|
222
241
|
name: { type: nn(GraphQLString), description: 'GraphQL field name' },
|
|
223
|
-
type: { type: nn(GraphQLString), description: 'PostgreSQL column type (text, citext)' }
|
|
224
|
-
})
|
|
242
|
+
type: { type: nn(GraphQLString), description: 'PostgreSQL column type (text, citext)' }
|
|
243
|
+
})
|
|
225
244
|
});
|
|
226
245
|
const MetaI18nType = new GraphQLObjectType({
|
|
227
246
|
name: 'MetaI18n',
|
|
228
247
|
description: 'i18n metadata for a table with @i18n tag',
|
|
229
248
|
fields: () => ({
|
|
230
249
|
translationTable: { type: nn(GraphQLString), description: 'Name of the translation table' },
|
|
231
|
-
translatableFields: { type: nnList(MetaI18nFieldType), description: 'Fields that are translatable' }
|
|
232
|
-
})
|
|
250
|
+
translatableFields: { type: nnList(MetaI18nFieldType), description: 'Fields that are translatable' }
|
|
251
|
+
})
|
|
233
252
|
});
|
|
234
253
|
const MetaRealtimeType = new GraphQLObjectType({
|
|
235
254
|
name: 'MetaRealtime',
|
|
236
255
|
description: 'Realtime metadata for a table with @realtime tag',
|
|
237
256
|
fields: () => ({
|
|
238
|
-
subscriptionFieldName: { type: nn(GraphQLString), description: 'The generated subscription field name (e.g. onPostChanged)' }
|
|
239
|
-
})
|
|
257
|
+
subscriptionFieldName: { type: nn(GraphQLString), description: 'The generated subscription field name (e.g. onPostChanged)' }
|
|
258
|
+
})
|
|
259
|
+
});
|
|
260
|
+
const MetaScopeType = new GraphQLObjectType({
|
|
261
|
+
name: 'MetaScope',
|
|
262
|
+
description: 'Provisioning scope metadata for a table',
|
|
263
|
+
fields: () => ({
|
|
264
|
+
scope: { type: nn(GraphQLString), description: "Provisioning scope: 'platform', 'app', 'database', or an entity scope (e.g. 'org')" },
|
|
265
|
+
tier: { type: nn(GraphQLString), description: "Coarse bucket: 'global', 'database', or 'entity'" },
|
|
266
|
+
keyColumn: { type: GraphQLString, description: 'Inflected scope key column (e.g. databaseId, orgId), null for global tiers' },
|
|
267
|
+
entityTable: { type: GraphQLString, description: 'SQL name of the entity table for entity scopes, else null' },
|
|
268
|
+
source: { type: nn(GraphQLString), description: "Provenance of the scope metadata (always 'smartTag')" }
|
|
269
|
+
})
|
|
240
270
|
});
|
|
241
271
|
const MetaTableType = new GraphQLObjectType({
|
|
242
272
|
name: 'MetaTable',
|
|
@@ -257,14 +287,15 @@ function createMetaSchemaType() {
|
|
|
257
287
|
search: { type: MetaSearchType, description: 'Search metadata (null if no search configured)' },
|
|
258
288
|
i18n: { type: MetaI18nType, description: 'i18n metadata (null if no @i18n tag)' },
|
|
259
289
|
realtime: { type: MetaRealtimeType, description: 'Realtime metadata (null if no @realtime tag)' },
|
|
260
|
-
|
|
290
|
+
scope: { type: MetaScopeType, description: 'Provisioning scope metadata (null if no @scope tag)' }
|
|
291
|
+
})
|
|
261
292
|
});
|
|
262
293
|
return new GraphQLObjectType({
|
|
263
294
|
name: 'MetaSchema',
|
|
264
295
|
description: 'Root meta schema type',
|
|
265
296
|
fields: () => ({
|
|
266
|
-
tables: { type: nnList(MetaTableType) }
|
|
267
|
-
})
|
|
297
|
+
tables: { type: nnList(MetaTableType) }
|
|
298
|
+
})
|
|
268
299
|
});
|
|
269
300
|
}
|
|
270
301
|
export function extendQueryWithMetaField(fields, tablesMeta) {
|
|
@@ -274,10 +305,10 @@ export function extendQueryWithMetaField(fields, tablesMeta) {
|
|
|
274
305
|
description: 'Metadata about the database schema, including tables, fields, indexes, and constraints. Useful for code generation tools.',
|
|
275
306
|
resolve() {
|
|
276
307
|
return { tables: tablesMeta };
|
|
277
|
-
}
|
|
308
|
+
}
|
|
278
309
|
};
|
|
279
310
|
return {
|
|
280
311
|
...fields,
|
|
281
|
-
_meta: metaField
|
|
312
|
+
_meta: metaField
|
|
282
313
|
};
|
|
283
314
|
}
|
|
@@ -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
|
|
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
|
|
4
|
-
import {
|
|
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) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildScalarEncoding } from './encoding-meta-builders';
|
|
1
2
|
const PG_TO_GQL_TYPE = {
|
|
2
3
|
text: 'String',
|
|
3
4
|
varchar: 'String',
|
|
@@ -87,6 +88,7 @@ export function buildFieldMeta(name, attr, build, options) {
|
|
|
87
88
|
isNotNull,
|
|
88
89
|
hasDefault,
|
|
89
90
|
subtype,
|
|
91
|
+
encoding: buildScalarEncoding(attr),
|
|
90
92
|
},
|
|
91
93
|
isNotNull,
|
|
92
94
|
hasDefault,
|
|
@@ -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 */
|
|
@@ -86,6 +101,33 @@ export interface TypeMeta {
|
|
|
86
101
|
isNotNull?: boolean;
|
|
87
102
|
hasDefault?: boolean;
|
|
88
103
|
subtype?: string | null;
|
|
104
|
+
/**
|
|
105
|
+
* How the client must serialize/parse this scalar. `null` for plain scalars
|
|
106
|
+
* (String/Int/Float/Boolean/JSON/enum) whose wire format is obvious from
|
|
107
|
+
* `gqlType` — populated only for scalars introspection can't fully describe.
|
|
108
|
+
*/
|
|
109
|
+
encoding?: ScalarEncodingMeta | null;
|
|
110
|
+
}
|
|
111
|
+
/** The machine kind a scalar marshals as on the client. */
|
|
112
|
+
export type ScalarEncodingKind = 'bigint' | 'datetime' | 'date' | 'time' | 'interval' | 'uuid' | 'geojson' | 'point' | 'inet' | 'ltree' | 'vector' | 'bytea' | 'composite';
|
|
113
|
+
export interface ScalarEncodingMeta {
|
|
114
|
+
/** Deterministic kind derived from the underlying (unwrapped) codec. */
|
|
115
|
+
kind: ScalarEncodingKind;
|
|
116
|
+
/** For 'vector': element scalar (e.g. 'float'), else null. */
|
|
117
|
+
elementType?: string | null;
|
|
118
|
+
/** For 'vector': declared length from typmod, else null. */
|
|
119
|
+
dimensions?: number | null;
|
|
120
|
+
/** For 'geojson': geometry subtype (Point/LineString/Polygon/…), else null. */
|
|
121
|
+
geometrySubtype?: string | null;
|
|
122
|
+
/** For 'geojson': spatial reference id, else null. */
|
|
123
|
+
srid?: number | null;
|
|
124
|
+
/** For 'ltree': values are dot-separated path strings ('a.b.c'). */
|
|
125
|
+
dotPath?: boolean;
|
|
126
|
+
/** For 'composite': nested attribute encodings. */
|
|
127
|
+
fields?: {
|
|
128
|
+
name: string;
|
|
129
|
+
type: TypeMeta;
|
|
130
|
+
}[] | null;
|
|
89
131
|
}
|
|
90
132
|
export interface IndexMeta {
|
|
91
133
|
name: string;
|
|
@@ -183,6 +225,7 @@ export interface PgCodec {
|
|
|
183
225
|
name: string;
|
|
184
226
|
attributes?: Record<string, PgAttribute>;
|
|
185
227
|
arrayOfCodec?: PgCodec | null;
|
|
228
|
+
domainOfCodec?: PgCodec | null;
|
|
186
229
|
isAnonymous?: boolean;
|
|
187
230
|
extensions?: {
|
|
188
231
|
pg?: {
|
|
@@ -192,6 +235,8 @@ export interface PgCodec {
|
|
|
192
235
|
}
|
|
193
236
|
export interface PgAttributeExtensions extends Record<string, unknown> {
|
|
194
237
|
geometrySubtype?: string | null;
|
|
238
|
+
geometrySrid?: number | null;
|
|
239
|
+
vectorDimensions?: number | null;
|
|
195
240
|
}
|
|
196
241
|
export interface PgAttribute {
|
|
197
242
|
codec?: PgCodec | null;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { GraphileConfig } from 'graphile-config';
|
|
8
8
|
import { cachedTablesMeta } from './meta-schema/cache';
|
|
9
|
+
import { buildScalarEncoding } from './meta-schema/encoding-meta-builders';
|
|
9
10
|
import { MetaSchemaPlugin } from './meta-schema/plugin';
|
|
10
11
|
import { buildFieldMeta, pgTypeToGqlType } from './meta-schema/type-mappings';
|
|
11
12
|
export { MetaSchemaPlugin };
|
|
@@ -16,4 +17,6 @@ export { pgTypeToGqlType as _pgTypeToGqlType };
|
|
|
16
17
|
export { buildFieldMeta as _buildFieldMeta };
|
|
17
18
|
/** @internal Exported for testing only */
|
|
18
19
|
export { cachedTablesMeta as _cachedTablesMeta };
|
|
20
|
+
/** @internal Exported for testing only */
|
|
21
|
+
export { buildScalarEncoding as _buildScalarEncoding };
|
|
19
22
|
export default MetaSchemaPlugin;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* constraints, indexes, and relations for code generation tooling.
|
|
6
6
|
*/
|
|
7
7
|
import { cachedTablesMeta } from './meta-schema/cache';
|
|
8
|
+
import { buildScalarEncoding } from './meta-schema/encoding-meta-builders';
|
|
8
9
|
import { MetaSchemaPlugin } from './meta-schema/plugin';
|
|
9
10
|
import { buildFieldMeta, pgTypeToGqlType } from './meta-schema/type-mappings';
|
|
10
11
|
export { MetaSchemaPlugin };
|
|
@@ -17,4 +18,6 @@ export { pgTypeToGqlType as _pgTypeToGqlType };
|
|
|
17
18
|
export { buildFieldMeta as _buildFieldMeta };
|
|
18
19
|
/** @internal Exported for testing only */
|
|
19
20
|
export { cachedTablesMeta as _cachedTablesMeta };
|
|
21
|
+
/** @internal Exported for testing only */
|
|
22
|
+
export { buildScalarEncoding as _buildScalarEncoding };
|
|
20
23
|
export default MetaSchemaPlugin;
|