@pgpmjs/export 0.20.0 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/esm/export-graphql-meta.js +106 -8
- package/esm/export-meta.js +51 -13
- package/esm/export-utils.js +161 -1070
- package/esm/graphql-client.js +43 -0
- package/esm/graphql-naming.js +53 -22
- package/esm/index.js +4 -2
- package/esm/interval-utils.js +70 -0
- package/esm/type-map.js +52 -0
- package/export-graphql-meta.js +105 -7
- package/export-meta.js +50 -12
- package/export-utils.d.ts +16 -9
- package/export-utils.js +162 -1070
- package/graphql-client.d.ts +9 -0
- package/graphql-client.js +43 -0
- package/graphql-naming.d.ts +38 -5
- package/graphql-naming.js +58 -24
- package/index.d.ts +4 -2
- package/index.js +12 -2
- package/interval-utils.d.ts +33 -0
- package/interval-utils.js +75 -0
- package/package.json +3 -3
- package/type-map.d.ts +45 -0
- package/type-map.js +57 -0
package/esm/graphql-client.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Simple GraphQL HTTP client with pagination and authentication support.
|
|
3
3
|
* Used by the GraphQL export flow to fetch data from the Constructive GraphQL API.
|
|
4
4
|
*/
|
|
5
|
+
import { unwrapGraphQLType } from './graphql-naming';
|
|
5
6
|
export class GraphQLClient {
|
|
6
7
|
endpoint;
|
|
7
8
|
defaultHeaders;
|
|
@@ -136,4 +137,46 @@ export class GraphQLClient {
|
|
|
136
137
|
}
|
|
137
138
|
return parts.length > 0 ? `(${parts.join(', ')})` : '';
|
|
138
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Introspect a GraphQL type to discover its fields and their types.
|
|
142
|
+
* Used by the dynamic export flow to discover what fields are available
|
|
143
|
+
* instead of hardcoding them in META_TABLE_CONFIG.
|
|
144
|
+
*
|
|
145
|
+
* Returns a Map of camelCase field name → { typeName, list, nonNull }.
|
|
146
|
+
*/
|
|
147
|
+
async introspectType(typeName) {
|
|
148
|
+
const result = await this.query(`
|
|
149
|
+
query IntrospectType($typeName: String!) {
|
|
150
|
+
__type(name: $typeName) {
|
|
151
|
+
fields {
|
|
152
|
+
name
|
|
153
|
+
type {
|
|
154
|
+
name
|
|
155
|
+
kind
|
|
156
|
+
ofType {
|
|
157
|
+
name
|
|
158
|
+
kind
|
|
159
|
+
ofType {
|
|
160
|
+
name
|
|
161
|
+
kind
|
|
162
|
+
ofType {
|
|
163
|
+
name
|
|
164
|
+
kind
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
`, { typeName });
|
|
173
|
+
const fields = new Map();
|
|
174
|
+
if (result.__type?.fields) {
|
|
175
|
+
for (const field of result.__type.fields) {
|
|
176
|
+
const typeInfo = unwrapGraphQLType(field.type);
|
|
177
|
+
fields.set(field.name, typeInfo);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return fields;
|
|
181
|
+
}
|
|
139
182
|
}
|
package/esm/graphql-naming.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* column database_id -> databaseId
|
|
15
15
|
*/
|
|
16
16
|
import { toCamelCase, toPascalCase, toSnakeCase, distinctPluralize, singularizeLast } from 'inflekt';
|
|
17
|
+
import { lookupByGqlType } from './type-map';
|
|
17
18
|
/**
|
|
18
19
|
* Get the GraphQL query field name for a given Postgres table name.
|
|
19
20
|
* Mirrors the PostGraphile InflektPlugin's allRowsConnection inflector:
|
|
@@ -36,28 +37,7 @@ export const graphqlRowToPostgresRow = (row) => {
|
|
|
36
37
|
}
|
|
37
38
|
return result;
|
|
38
39
|
};
|
|
39
|
-
|
|
40
|
-
* Convert a PostgreSQL interval object (from GraphQL Interval type) back to a Postgres interval string.
|
|
41
|
-
* e.g. { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 } -> '1 hour 30 minutes'
|
|
42
|
-
*/
|
|
43
|
-
export const intervalToPostgres = (interval) => {
|
|
44
|
-
if (!interval)
|
|
45
|
-
return null;
|
|
46
|
-
const parts = [];
|
|
47
|
-
if (interval.years)
|
|
48
|
-
parts.push(`${interval.years} year${interval.years !== 1 ? 's' : ''}`);
|
|
49
|
-
if (interval.months)
|
|
50
|
-
parts.push(`${interval.months} mon${interval.months !== 1 ? 's' : ''}`);
|
|
51
|
-
if (interval.days)
|
|
52
|
-
parts.push(`${interval.days} day${interval.days !== 1 ? 's' : ''}`);
|
|
53
|
-
if (interval.hours)
|
|
54
|
-
parts.push(`${interval.hours}:${String(interval.minutes ?? 0).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
|
|
55
|
-
else if (interval.minutes)
|
|
56
|
-
parts.push(`00:${String(interval.minutes).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
|
|
57
|
-
else if (interval.seconds)
|
|
58
|
-
parts.push(`00:00:${String(interval.seconds).padStart(2, '0')}`);
|
|
59
|
-
return parts.length > 0 ? parts.join(' ') : '00:00:00';
|
|
60
|
-
};
|
|
40
|
+
export { intervalToPostgres } from './interval-utils';
|
|
61
41
|
/**
|
|
62
42
|
* Convert an array of Postgres field names (with optional type hints) to a GraphQL fields fragment.
|
|
63
43
|
* Handles composite types like 'interval' by expanding them into subfield selections.
|
|
@@ -74,3 +54,54 @@ export const buildFieldsFragment = (pgFieldNames, fieldTypes) => {
|
|
|
74
54
|
return camel;
|
|
75
55
|
}).join('\n ');
|
|
76
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Unwrap a GraphQL introspection type reference into its leaf type name and list status.
|
|
59
|
+
* PostGraphile wraps types like: { kind: NON_NULL, name: null, ofType: { kind: LIST, name: null, ofType: { kind: SCALAR, name: "UUID" } } }
|
|
60
|
+
* This function recursively unwraps ofType layers, detecting LIST wrappers via the `kind` field.
|
|
61
|
+
*/
|
|
62
|
+
export const unwrapGraphQLType = (typeRef, parentKind) => {
|
|
63
|
+
if (!typeRef)
|
|
64
|
+
return { typeName: 'Unknown', kind: 'UNKNOWN', nonNull: false, list: false };
|
|
65
|
+
// If the type has a name, it's the leaf type
|
|
66
|
+
if (typeRef.name) {
|
|
67
|
+
const isList = parentKind === 'LIST';
|
|
68
|
+
return { typeName: typeRef.name, kind: typeRef.kind ?? 'UNKNOWN', nonNull: parentKind === 'NON_NULL', list: isList };
|
|
69
|
+
}
|
|
70
|
+
// If it has ofType, it's a wrapper (NON_NULL or LIST)
|
|
71
|
+
if (typeRef.ofType) {
|
|
72
|
+
return unwrapGraphQLType(typeRef.ofType, typeRef.kind ?? undefined);
|
|
73
|
+
}
|
|
74
|
+
return { typeName: 'Unknown', kind: 'UNKNOWN', nonNull: false, list: false };
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Map GraphQL scalar/type names to FieldType values.
|
|
78
|
+
* Delegates to the canonical PG_TYPE_MAP in type-map.ts.
|
|
79
|
+
*/
|
|
80
|
+
export const mapGraphQLTypeToFieldType = (gqlTypeName, isList = false) => {
|
|
81
|
+
// Handle list types — map to the array variants that exist in FieldType
|
|
82
|
+
if (isList) {
|
|
83
|
+
const inner = mapGraphQLTypeToFieldType(gqlTypeName, false);
|
|
84
|
+
// Only these array types exist in FieldType: uuid[], text[], jsonb[]
|
|
85
|
+
switch (inner) {
|
|
86
|
+
case 'uuid': return 'uuid[]';
|
|
87
|
+
case 'text': return 'text[]';
|
|
88
|
+
case 'jsonb': return 'jsonb[]';
|
|
89
|
+
default: return 'text'; // safe fallback for unsupported array types
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ID is a GraphQL-only type (relay-style) that maps to uuid;
|
|
93
|
+
// it has no direct PG udt_name counterpart in PG_TYPE_MAP.
|
|
94
|
+
if (gqlTypeName === 'ID')
|
|
95
|
+
return 'uuid';
|
|
96
|
+
const entry = lookupByGqlType(gqlTypeName);
|
|
97
|
+
return entry?.fieldType ?? 'text';
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Derive the GraphQL type name (PascalCase singular) from a PostgreSQL table name.
|
|
101
|
+
* Mirrors PostGraphile's InflektPlugin type inflector:
|
|
102
|
+
* singularizeLast(toPascalCase(pgTableName))
|
|
103
|
+
* e.g. "user_auth_module" → "UserAuthModule"
|
|
104
|
+
*/
|
|
105
|
+
export const getGraphQLTypeName = (pgTableName) => {
|
|
106
|
+
return singularizeLast(toPascalCase(pgTableName));
|
|
107
|
+
};
|
package/esm/index.js
CHANGED
|
@@ -3,5 +3,7 @@ export * from './export-migrations';
|
|
|
3
3
|
export * from './export-graphql';
|
|
4
4
|
export * from './export-graphql-meta';
|
|
5
5
|
export { GraphQLClient } from './graphql-client';
|
|
6
|
-
export { getGraphQLQueryName, graphqlRowToPostgresRow, buildFieldsFragment,
|
|
7
|
-
export { DB_REQUIRED_EXTENSIONS, SERVICE_REQUIRED_EXTENSIONS, META_COMMON_HEADER, META_COMMON_FOOTER, META_TABLE_ORDER, META_TABLE_CONFIG, makeReplacer, preparePackage, normalizeOutdir, detectMissingModules, installMissingModules } from './export-utils';
|
|
6
|
+
export { getGraphQLQueryName, getGraphQLTypeName, graphqlRowToPostgresRow, buildFieldsFragment, mapGraphQLTypeToFieldType, unwrapGraphQLType } from './graphql-naming';
|
|
7
|
+
export { DB_REQUIRED_EXTENSIONS, SERVICE_REQUIRED_EXTENSIONS, META_COMMON_HEADER, META_COMMON_FOOTER, META_TABLE_ORDER, META_TABLE_CONFIG, mapPgTypeToFieldType, makeReplacer, preparePackage, normalizeOutdir, detectMissingModules, installMissingModules } from './export-utils';
|
|
8
|
+
export { PG_TYPE_MAP, lookupByPgUdt, lookupByGqlType } from './type-map';
|
|
9
|
+
export { intervalToPostgres, parsePgInterval } from './interval-utils';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interval conversion utilities shared between graphql-naming.ts and tests.
|
|
3
|
+
*
|
|
4
|
+
* NOTE: @pgpmjs/csv-to-pg has its own formatInterval() in parse.ts which
|
|
5
|
+
* performs the same object→string conversion. That is a separate package with
|
|
6
|
+
* its own release cycle, so we do not cross-reference it here. If interval
|
|
7
|
+
* handling needs to be unified across packages, that would require an
|
|
8
|
+
* architectural change (shared dependency or monorepo util package).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Convert a PostgreSQL interval object (from GraphQL Interval type) back to a
|
|
12
|
+
* Postgres interval string.
|
|
13
|
+
* e.g. { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 } → '1:30:00'
|
|
14
|
+
*/
|
|
15
|
+
export const intervalToPostgres = (interval) => {
|
|
16
|
+
if (!interval)
|
|
17
|
+
return null;
|
|
18
|
+
const parts = [];
|
|
19
|
+
if (interval.years)
|
|
20
|
+
parts.push(`${interval.years} year${interval.years !== 1 ? 's' : ''}`);
|
|
21
|
+
if (interval.months)
|
|
22
|
+
parts.push(`${interval.months} mon${interval.months !== 1 ? 's' : ''}`);
|
|
23
|
+
if (interval.days)
|
|
24
|
+
parts.push(`${interval.days} day${interval.days !== 1 ? 's' : ''}`);
|
|
25
|
+
if (interval.hours)
|
|
26
|
+
parts.push(`${interval.hours}:${String(interval.minutes ?? 0).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
|
|
27
|
+
else if (interval.minutes)
|
|
28
|
+
parts.push(`00:${String(interval.minutes).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
|
|
29
|
+
else if (interval.seconds)
|
|
30
|
+
parts.push(`00:00:${String(interval.seconds).padStart(2, '0')}`);
|
|
31
|
+
return parts.length > 0 ? parts.join(' ') : '00:00:00';
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Parse a PostgreSQL interval string into the object shape that PostGraphile's
|
|
35
|
+
* Interval type returns: { years, months, days, hours, minutes, seconds }.
|
|
36
|
+
*
|
|
37
|
+
* Handles formats like:
|
|
38
|
+
* '30 days' → { years: 0, months: 0, days: 30, hours: 0, minutes: 0, seconds: 0 }
|
|
39
|
+
* '1:30:00' → { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 }
|
|
40
|
+
*/
|
|
41
|
+
export const parsePgInterval = (value) => {
|
|
42
|
+
const result = { years: 0, months: 0, days: 0, hours: 0, minutes: 0, seconds: 0 };
|
|
43
|
+
// Try HH:MM:SS format
|
|
44
|
+
const timeMatch = value.match(/^(\d+):(\d+):(\d+)/);
|
|
45
|
+
if (timeMatch) {
|
|
46
|
+
result.hours = parseInt(timeMatch[1], 10);
|
|
47
|
+
result.minutes = parseInt(timeMatch[2], 10);
|
|
48
|
+
result.seconds = parseInt(timeMatch[3], 10);
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
// Try descriptive format: 'N unit N unit ...'
|
|
52
|
+
const parts = value.trim().split(/\s+/);
|
|
53
|
+
for (let i = 0; i < parts.length - 1; i += 2) {
|
|
54
|
+
const num = parseInt(parts[i], 10);
|
|
55
|
+
const unit = parts[i + 1].toLowerCase();
|
|
56
|
+
if (unit.startsWith('year'))
|
|
57
|
+
result.years = num;
|
|
58
|
+
else if (unit.startsWith('mon'))
|
|
59
|
+
result.months = num;
|
|
60
|
+
else if (unit.startsWith('day'))
|
|
61
|
+
result.days = num;
|
|
62
|
+
else if (unit.startsWith('hour'))
|
|
63
|
+
result.hours = num;
|
|
64
|
+
else if (unit.startsWith('minute'))
|
|
65
|
+
result.minutes = num;
|
|
66
|
+
else if (unit.startsWith('second'))
|
|
67
|
+
result.seconds = num;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
};
|
package/esm/type-map.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical PG → GraphQL → FieldType mapping table.
|
|
3
|
+
* Aligned with PostGraphile v5's PgCodecsPlugin type assignments:
|
|
4
|
+
* - int2, int4 → Int
|
|
5
|
+
* - int8 (bigint) → BigInt
|
|
6
|
+
* - numeric → BigFloat
|
|
7
|
+
* - float4, float8 → Float
|
|
8
|
+
* - interval → Interval (OBJECT kind, not SCALAR)
|
|
9
|
+
* - timestamptz, timestamp → Datetime
|
|
10
|
+
*/
|
|
11
|
+
export const PG_TYPE_MAP = [
|
|
12
|
+
{ pgUdtNames: ['uuid'], gqlTypeName: 'UUID', fieldType: 'uuid', gqlKind: 'SCALAR' },
|
|
13
|
+
{ pgUdtNames: ['_uuid'], gqlTypeName: 'UUID', fieldType: 'uuid[]', gqlKind: 'SCALAR', isArray: true },
|
|
14
|
+
{ pgUdtNames: ['text', 'varchar', 'bpchar', 'name', 'citext'], gqlTypeName: 'String', fieldType: 'text', gqlKind: 'SCALAR' },
|
|
15
|
+
{ pgUdtNames: ['_text', '_varchar', '_citext'], gqlTypeName: 'String', fieldType: 'text[]', gqlKind: 'SCALAR', isArray: true },
|
|
16
|
+
{ pgUdtNames: ['bool'], gqlTypeName: 'Boolean', fieldType: 'boolean', gqlKind: 'SCALAR' },
|
|
17
|
+
{ pgUdtNames: ['jsonb', 'json'], gqlTypeName: 'JSON', fieldType: 'jsonb', gqlKind: 'SCALAR' },
|
|
18
|
+
{ pgUdtNames: ['_jsonb'], gqlTypeName: 'JSON', fieldType: 'jsonb[]', gqlKind: 'SCALAR', isArray: true },
|
|
19
|
+
{ pgUdtNames: ['int2', 'int4'], gqlTypeName: 'Int', fieldType: 'int', gqlKind: 'SCALAR' },
|
|
20
|
+
{ pgUdtNames: ['int8'], gqlTypeName: 'BigInt', fieldType: 'int', gqlKind: 'SCALAR' },
|
|
21
|
+
{ pgUdtNames: ['numeric'], gqlTypeName: 'BigFloat', fieldType: 'int', gqlKind: 'SCALAR' },
|
|
22
|
+
{ pgUdtNames: ['float4', 'float8'], gqlTypeName: 'Float', fieldType: 'int', gqlKind: 'SCALAR' },
|
|
23
|
+
{ pgUdtNames: ['interval'], gqlTypeName: 'Interval', fieldType: 'interval', gqlKind: 'OBJECT' },
|
|
24
|
+
{ pgUdtNames: ['timestamptz', 'timestamp'], gqlTypeName: 'Datetime', fieldType: 'timestamptz', gqlKind: 'SCALAR' },
|
|
25
|
+
];
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Lookup indices (built once at module load)
|
|
28
|
+
// =============================================================================
|
|
29
|
+
/** Reverse index: pgUdtName → TypeMapEntry */
|
|
30
|
+
const pgUdtIndex = new Map();
|
|
31
|
+
for (const entry of PG_TYPE_MAP) {
|
|
32
|
+
for (const udt of entry.pgUdtNames) {
|
|
33
|
+
pgUdtIndex.set(udt, entry);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Reverse index: gqlTypeName → TypeMapEntry (first match wins) */
|
|
37
|
+
const gqlTypeIndex = new Map();
|
|
38
|
+
for (const entry of PG_TYPE_MAP) {
|
|
39
|
+
if (!gqlTypeIndex.has(entry.gqlTypeName)) {
|
|
40
|
+
gqlTypeIndex.set(entry.gqlTypeName, entry);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Look up a TypeMapEntry by PostgreSQL udt_name.
|
|
45
|
+
* Returns undefined for unknown types (callers should fall back to 'text').
|
|
46
|
+
*/
|
|
47
|
+
export const lookupByPgUdt = (udtName) => pgUdtIndex.get(udtName);
|
|
48
|
+
/**
|
|
49
|
+
* Look up a TypeMapEntry by GraphQL type name.
|
|
50
|
+
* Returns undefined for unknown types (callers should fall back to 'text').
|
|
51
|
+
*/
|
|
52
|
+
export const lookupByGqlType = (gqlTypeName) => gqlTypeIndex.get(gqlTypeName);
|
package/export-graphql-meta.js
CHANGED
|
@@ -9,8 +9,70 @@ exports.exportGraphQLMeta = void 0;
|
|
|
9
9
|
* generate SQL INSERT statements.
|
|
10
10
|
*/
|
|
11
11
|
const csv_to_pg_1 = require("csv-to-pg");
|
|
12
|
+
const inflekt_1 = require("inflekt");
|
|
12
13
|
const export_utils_1 = require("./export-utils");
|
|
13
14
|
const graphql_naming_1 = require("./graphql-naming");
|
|
15
|
+
const type_map_1 = require("./type-map");
|
|
16
|
+
/**
|
|
17
|
+
* Discover fields dynamically from a GraphQL type via introspection.
|
|
18
|
+
* Queries `__type` to enumerate fields and infer their `FieldType` via
|
|
19
|
+
* `mapGraphQLTypeToFieldType`. Tracks ENUM fields separately so callers
|
|
20
|
+
* can normalize their CONSTANT_CASE values back to lowercase.
|
|
21
|
+
*
|
|
22
|
+
* `typeOverrides` from the config are applied on top for special types
|
|
23
|
+
* (image, upload, url) that cannot be inferred from the GraphQL type alone.
|
|
24
|
+
*/
|
|
25
|
+
const buildDynamicFieldsFromGraphQL = async (client, tableConfig) => {
|
|
26
|
+
const emptyResult = { fields: {}, enumFields: new Set() };
|
|
27
|
+
const typeName = tableConfig.gqlTypeName || (0, graphql_naming_1.getGraphQLTypeName)(tableConfig.table);
|
|
28
|
+
try {
|
|
29
|
+
const introspectedFields = await client.introspectType(typeName);
|
|
30
|
+
const dynamicFields = {};
|
|
31
|
+
const enumFields = new Set();
|
|
32
|
+
for (const [camelName, typeInfo] of introspectedFields) {
|
|
33
|
+
const snakeName = (0, inflekt_1.toSnakeCase)(camelName);
|
|
34
|
+
// Skip internal GraphQL fields
|
|
35
|
+
if (camelName.startsWith('__'))
|
|
36
|
+
continue;
|
|
37
|
+
// Track enum fields for lowercase normalization (custom inflector uppercases them)
|
|
38
|
+
if (typeInfo.kind === 'ENUM') {
|
|
39
|
+
enumFields.add(snakeName);
|
|
40
|
+
}
|
|
41
|
+
// Skip non-scalar fields (relations/computed columns like "database" of type Database)
|
|
42
|
+
// Only SCALAR and ENUM kinds can be selected without sub-field selections
|
|
43
|
+
// EXCEPTION: types registered as non-SCALAR in PG_TYPE_MAP (e.g. Interval=OBJECT)
|
|
44
|
+
// are handled via buildFieldsFragment sub-selections and intervalToPostgres conversion
|
|
45
|
+
if (typeInfo.kind !== 'SCALAR' && typeInfo.kind !== 'ENUM') {
|
|
46
|
+
const mapEntry = (0, type_map_1.lookupByGqlType)(typeInfo.typeName);
|
|
47
|
+
if (mapEntry && mapEntry.gqlKind !== 'SCALAR') {
|
|
48
|
+
dynamicFields[snakeName] = mapEntry.fieldType;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
dynamicFields[snakeName] = (0, graphql_naming_1.mapGraphQLTypeToFieldType)(typeInfo.typeName, typeInfo.list);
|
|
54
|
+
}
|
|
55
|
+
// Apply type overrides (e.g., image, upload, url)
|
|
56
|
+
if (tableConfig.typeOverrides) {
|
|
57
|
+
for (const [fieldName, fieldType] of Object.entries(tableConfig.typeOverrides)) {
|
|
58
|
+
if (dynamicFields[fieldName]) {
|
|
59
|
+
dynamicFields[fieldName] = fieldType;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { fields: dynamicFields, enumFields };
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
67
|
+
if (message.includes('Cannot query field') ||
|
|
68
|
+
message.includes('is not defined by type') ||
|
|
69
|
+
message.includes('Unknown field')) {
|
|
70
|
+
// Type not available in the GraphQL schema — return empty
|
|
71
|
+
return emptyResult;
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
14
76
|
/**
|
|
15
77
|
* Fetch metadata via GraphQL and generate SQL INSERT statements.
|
|
16
78
|
* This is the GraphQL equivalent of exportMeta() in export-meta.ts.
|
|
@@ -21,8 +83,12 @@ const exportGraphQLMeta = async ({ client, database_id }) => {
|
|
|
21
83
|
const tableConfig = export_utils_1.META_TABLE_CONFIG[key];
|
|
22
84
|
if (!tableConfig)
|
|
23
85
|
return;
|
|
24
|
-
|
|
25
|
-
const
|
|
86
|
+
// Build fields dynamically: either from hardcoded config or via introspection
|
|
87
|
+
const { fields: configFields, enumFields } = await buildDynamicFieldsFromGraphQL(client, tableConfig);
|
|
88
|
+
if (Object.keys(configFields).length === 0)
|
|
89
|
+
return;
|
|
90
|
+
const pgFieldNames = Object.keys(configFields);
|
|
91
|
+
const graphqlFieldsFragment = (0, graphql_naming_1.buildFieldsFragment)(pgFieldNames, configFields);
|
|
26
92
|
const graphqlQueryName = (0, graphql_naming_1.getGraphQLQueryName)(tableConfig.table);
|
|
27
93
|
// The 'database' table is fetched by id, not by database_id
|
|
28
94
|
const condition = key === 'database'
|
|
@@ -33,13 +99,30 @@ const exportGraphQLMeta = async ({ client, database_id }) => {
|
|
|
33
99
|
if (rows.length > 0) {
|
|
34
100
|
// Convert camelCase GraphQL keys back to snake_case for the Parser
|
|
35
101
|
// Also convert interval objects back to Postgres interval strings
|
|
102
|
+
// and normalize enum values from CONSTANT_CASE back to lowercase
|
|
36
103
|
const pgRows = rows.map(row => {
|
|
37
104
|
const pgRow = (0, graphql_naming_1.graphqlRowToPostgresRow)(row);
|
|
38
|
-
|
|
39
|
-
|
|
105
|
+
for (const [fieldName, fieldType] of Object.entries(configFields)) {
|
|
106
|
+
// Convert interval fields from {seconds, minutes, ...} objects to strings
|
|
40
107
|
if (fieldType === 'interval' && pgRow[fieldName] && typeof pgRow[fieldName] === 'object') {
|
|
41
108
|
pgRow[fieldName] = (0, graphql_naming_1.intervalToPostgres)(pgRow[fieldName]);
|
|
42
109
|
}
|
|
110
|
+
// Truncate timestamptz to second precision for parity with SQL flow
|
|
111
|
+
// PostGraphile's Datetime scalar preserves full millisecond precision,
|
|
112
|
+
// but the pg driver + our SQL flow truncates to .000Z via Date rounding
|
|
113
|
+
if (fieldType === 'timestamptz' && typeof pgRow[fieldName] === 'string') {
|
|
114
|
+
const d = new Date(pgRow[fieldName]);
|
|
115
|
+
if (!isNaN(d.getTime())) {
|
|
116
|
+
pgRow[fieldName] = new Date(Math.floor(d.getTime() / 1000) * 1000).toISOString();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Normalize enum values: custom inflector uppercases them to CONSTANT_CASE,
|
|
121
|
+
// but PostgreSQL stores them in lowercase — convert back for parity with SQL flow
|
|
122
|
+
for (const fieldName of enumFields) {
|
|
123
|
+
if (typeof pgRow[fieldName] === 'string') {
|
|
124
|
+
pgRow[fieldName] = pgRow[fieldName].toLowerCase();
|
|
125
|
+
}
|
|
43
126
|
}
|
|
44
127
|
return pgRow;
|
|
45
128
|
});
|
|
@@ -52,7 +135,7 @@ const exportGraphQLMeta = async ({ client, database_id }) => {
|
|
|
52
135
|
}
|
|
53
136
|
}
|
|
54
137
|
const dynamicFields = {};
|
|
55
|
-
for (const [fieldName, fieldType] of Object.entries(
|
|
138
|
+
for (const [fieldName, fieldType] of Object.entries(configFields)) {
|
|
56
139
|
if (returnedKeys.has(fieldName)) {
|
|
57
140
|
dynamicFields[fieldName] = fieldType;
|
|
58
141
|
}
|
|
@@ -136,6 +219,7 @@ const exportGraphQLMeta = async ({ client, database_id }) => {
|
|
|
136
219
|
queryAndParse('permissions_module'),
|
|
137
220
|
queryAndParse('limits_module'),
|
|
138
221
|
queryAndParse('levels_module'),
|
|
222
|
+
queryAndParse('events_module'),
|
|
139
223
|
queryAndParse('users_module'),
|
|
140
224
|
queryAndParse('hierarchy_module'),
|
|
141
225
|
queryAndParse('membership_types_module'),
|
|
@@ -145,13 +229,14 @@ const exportGraphQLMeta = async ({ client, database_id }) => {
|
|
|
145
229
|
queryAndParse('user_state_module'),
|
|
146
230
|
queryAndParse('profiles_module'),
|
|
147
231
|
queryAndParse('config_secrets_user_module'),
|
|
232
|
+
queryAndParse('user_credentials_module'),
|
|
233
|
+
queryAndParse('user_settings_module'),
|
|
148
234
|
queryAndParse('connected_accounts_module'),
|
|
149
235
|
queryAndParse('phone_numbers_module'),
|
|
150
236
|
queryAndParse('crypto_addresses_module'),
|
|
151
237
|
queryAndParse('crypto_auth_module'),
|
|
152
238
|
queryAndParse('field_module'),
|
|
153
239
|
queryAndParse('table_module'),
|
|
154
|
-
queryAndParse('table_template_module'),
|
|
155
240
|
queryAndParse('secure_table_provision'),
|
|
156
241
|
queryAndParse('uuid_module'),
|
|
157
242
|
queryAndParse('default_ids_module'),
|
|
@@ -169,8 +254,21 @@ const exportGraphQLMeta = async ({ client, database_id }) => {
|
|
|
169
254
|
queryAndParse('realtime_module'),
|
|
170
255
|
queryAndParse('session_secrets_module'),
|
|
171
256
|
queryAndParse('config_secrets_org_module'),
|
|
257
|
+
queryAndParse('config_secrets_module'),
|
|
258
|
+
queryAndParse('i18n_module'),
|
|
259
|
+
queryAndParse('agent_module'),
|
|
260
|
+
queryAndParse('function_module'),
|
|
261
|
+
queryAndParse('namespace_module'),
|
|
262
|
+
queryAndParse('merkle_store_module'),
|
|
263
|
+
queryAndParse('graph_module'),
|
|
264
|
+
queryAndParse('compute_log_module'),
|
|
265
|
+
queryAndParse('db_usage_module'),
|
|
266
|
+
queryAndParse('storage_log_module'),
|
|
267
|
+
queryAndParse('transfer_log_module'),
|
|
172
268
|
queryAndParse('webauthn_auth_module'),
|
|
173
|
-
queryAndParse('webauthn_credentials_module')
|
|
269
|
+
queryAndParse('webauthn_credentials_module'),
|
|
270
|
+
queryAndParse('inference_log_module'),
|
|
271
|
+
queryAndParse('rate_limit_meters_module')
|
|
174
272
|
]);
|
|
175
273
|
return sql;
|
|
176
274
|
};
|
package/export-meta.js
CHANGED
|
@@ -22,10 +22,10 @@ const getTableColumns = async (pool, schemaName, tableName) => {
|
|
|
22
22
|
return columns;
|
|
23
23
|
};
|
|
24
24
|
/**
|
|
25
|
-
* Build dynamic fields config
|
|
26
|
-
* -
|
|
27
|
-
* -
|
|
28
|
-
*
|
|
25
|
+
* Build dynamic fields config from the database via information_schema.
|
|
26
|
+
* - All fields are derived from `information_schema.columns` + `mapPgTypeToFieldType`.
|
|
27
|
+
* - `typeOverrides` from the config are applied on top for special types
|
|
28
|
+
* (image, upload, url) that cannot be inferred from PG types alone.
|
|
29
29
|
*/
|
|
30
30
|
const buildDynamicFields = async (pool, tableConfig) => {
|
|
31
31
|
const actualColumns = await getTableColumns(pool, tableConfig.schema, tableConfig.table);
|
|
@@ -34,13 +34,17 @@ const buildDynamicFields = async (pool, tableConfig) => {
|
|
|
34
34
|
return {};
|
|
35
35
|
}
|
|
36
36
|
const dynamicFields = {};
|
|
37
|
-
//
|
|
38
|
-
for (const [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
// Derive all fields from information_schema
|
|
38
|
+
for (const [columnName, udtName] of actualColumns) {
|
|
39
|
+
dynamicFields[columnName] = (0, export_utils_1.mapPgTypeToFieldType)(udtName);
|
|
40
|
+
}
|
|
41
|
+
// Apply type overrides (image, upload, url)
|
|
42
|
+
if (tableConfig.typeOverrides) {
|
|
43
|
+
for (const [fieldName, fieldType] of Object.entries(tableConfig.typeOverrides)) {
|
|
44
|
+
if (dynamicFields[fieldName]) {
|
|
45
|
+
dynamicFields[fieldName] = fieldType;
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
|
-
// If column doesn't exist in database, skip it (this fixes the bug)
|
|
44
48
|
}
|
|
45
49
|
return dynamicFields;
|
|
46
50
|
};
|
|
@@ -50,8 +54,9 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
|
|
|
50
54
|
database: dbname
|
|
51
55
|
});
|
|
52
56
|
const sql = {};
|
|
53
|
-
// Cache for dynamically built parsers
|
|
57
|
+
// Cache for dynamically built parsers and their field configs
|
|
54
58
|
const parsers = {};
|
|
59
|
+
const parserFields = {};
|
|
55
60
|
// Build parser dynamically by querying actual columns from the database
|
|
56
61
|
const getParser = async (key) => {
|
|
57
62
|
if (parsers[key]) {
|
|
@@ -67,6 +72,7 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
|
|
|
67
72
|
// No columns found (table doesn't exist or no matching columns)
|
|
68
73
|
return null;
|
|
69
74
|
}
|
|
75
|
+
parserFields[key] = dynamicFields;
|
|
70
76
|
const parser = new csv_to_pg_1.Parser({
|
|
71
77
|
schema: tableConfig.schema,
|
|
72
78
|
table: tableConfig.table,
|
|
@@ -84,6 +90,23 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
|
|
|
84
90
|
}
|
|
85
91
|
const result = await pool.query(query, [database_id]);
|
|
86
92
|
if (result.rows.length) {
|
|
93
|
+
// Truncate timestamptz to second precision to match PostGraphile's Datetime scalar
|
|
94
|
+
// which truncates milliseconds in the GraphQL flow
|
|
95
|
+
const fields = parserFields[key];
|
|
96
|
+
if (fields) {
|
|
97
|
+
for (const row of result.rows) {
|
|
98
|
+
for (const [fieldName, fieldType] of Object.entries(fields)) {
|
|
99
|
+
if (fieldType === 'timestamptz') {
|
|
100
|
+
const val = row[fieldName];
|
|
101
|
+
if (val instanceof Date) {
|
|
102
|
+
// Truncate to second precision and convert to ISO string
|
|
103
|
+
// so both SQL and GraphQL flows pass the same value type to the Parser
|
|
104
|
+
row[fieldName] = new Date(Math.floor(val.getTime() / 1000) * 1000).toISOString();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
87
110
|
const parsed = await parser.parse(result.rows);
|
|
88
111
|
if (parsed) {
|
|
89
112
|
sql[key] = parsed;
|
|
@@ -148,6 +171,7 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
|
|
|
148
171
|
await queryAndParse('permissions_module', `SELECT * FROM metaschema_modules_public.permissions_module WHERE database_id = $1 ORDER BY id`);
|
|
149
172
|
await queryAndParse('limits_module', `SELECT * FROM metaschema_modules_public.limits_module WHERE database_id = $1 ORDER BY id`);
|
|
150
173
|
await queryAndParse('levels_module', `SELECT * FROM metaschema_modules_public.levels_module WHERE database_id = $1 ORDER BY id`);
|
|
174
|
+
await queryAndParse('events_module', `SELECT * FROM metaschema_modules_public.events_module WHERE database_id = $1 ORDER BY id`);
|
|
151
175
|
await queryAndParse('users_module', `SELECT * FROM metaschema_modules_public.users_module WHERE database_id = $1 ORDER BY id`);
|
|
152
176
|
await queryAndParse('hierarchy_module', `SELECT * FROM metaschema_modules_public.hierarchy_module WHERE database_id = $1 ORDER BY id`);
|
|
153
177
|
await queryAndParse('membership_types_module', `SELECT * FROM metaschema_modules_public.membership_types_module WHERE database_id = $1 ORDER BY id`);
|
|
@@ -157,13 +181,14 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
|
|
|
157
181
|
await queryAndParse('user_state_module', `SELECT * FROM metaschema_modules_public.user_state_module WHERE database_id = $1 ORDER BY id`);
|
|
158
182
|
await queryAndParse('profiles_module', `SELECT * FROM metaschema_modules_public.profiles_module WHERE database_id = $1 ORDER BY id`);
|
|
159
183
|
await queryAndParse('config_secrets_user_module', `SELECT * FROM metaschema_modules_public.config_secrets_user_module WHERE database_id = $1 ORDER BY id`);
|
|
184
|
+
await queryAndParse('user_credentials_module', `SELECT * FROM metaschema_modules_public.user_credentials_module WHERE database_id = $1 ORDER BY id`);
|
|
185
|
+
await queryAndParse('user_settings_module', `SELECT * FROM metaschema_modules_public.user_settings_module WHERE database_id = $1 ORDER BY id`);
|
|
160
186
|
await queryAndParse('connected_accounts_module', `SELECT * FROM metaschema_modules_public.connected_accounts_module WHERE database_id = $1 ORDER BY id`);
|
|
161
187
|
await queryAndParse('phone_numbers_module', `SELECT * FROM metaschema_modules_public.phone_numbers_module WHERE database_id = $1 ORDER BY id`);
|
|
162
188
|
await queryAndParse('crypto_addresses_module', `SELECT * FROM metaschema_modules_public.crypto_addresses_module WHERE database_id = $1 ORDER BY id`);
|
|
163
189
|
await queryAndParse('crypto_auth_module', `SELECT * FROM metaschema_modules_public.crypto_auth_module WHERE database_id = $1 ORDER BY id`);
|
|
164
190
|
await queryAndParse('field_module', `SELECT * FROM metaschema_modules_public.field_module WHERE database_id = $1 ORDER BY id`);
|
|
165
191
|
await queryAndParse('table_module', `SELECT * FROM metaschema_modules_public.table_module WHERE database_id = $1 ORDER BY id`);
|
|
166
|
-
await queryAndParse('table_template_module', `SELECT * FROM metaschema_modules_public.table_template_module WHERE database_id = $1 ORDER BY id`);
|
|
167
192
|
await queryAndParse('secure_table_provision', `SELECT * FROM metaschema_modules_public.secure_table_provision WHERE database_id = $1 ORDER BY id`);
|
|
168
193
|
await queryAndParse('uuid_module', `SELECT * FROM metaschema_modules_public.uuid_module WHERE database_id = $1 ORDER BY id`);
|
|
169
194
|
await queryAndParse('default_ids_module', `SELECT * FROM metaschema_modules_public.default_ids_module WHERE database_id = $1 ORDER BY id`);
|
|
@@ -181,8 +206,21 @@ const exportMeta = async ({ opts, dbname, database_id }) => {
|
|
|
181
206
|
await queryAndParse('realtime_module', `SELECT * FROM metaschema_modules_public.realtime_module WHERE database_id = $1 ORDER BY id`);
|
|
182
207
|
await queryAndParse('session_secrets_module', `SELECT * FROM metaschema_modules_public.session_secrets_module WHERE database_id = $1 ORDER BY id`);
|
|
183
208
|
await queryAndParse('config_secrets_org_module', `SELECT * FROM metaschema_modules_public.config_secrets_org_module WHERE database_id = $1 ORDER BY id`);
|
|
209
|
+
await queryAndParse('config_secrets_module', `SELECT * FROM metaschema_modules_public.config_secrets_module WHERE database_id = $1 ORDER BY id`);
|
|
210
|
+
await queryAndParse('i18n_module', `SELECT * FROM metaschema_modules_public.i18n_module WHERE database_id = $1 ORDER BY id`);
|
|
211
|
+
await queryAndParse('agent_module', `SELECT * FROM metaschema_modules_public.agent_module WHERE database_id = $1 ORDER BY id`);
|
|
212
|
+
await queryAndParse('function_module', `SELECT * FROM metaschema_modules_public.function_module WHERE database_id = $1 ORDER BY id`);
|
|
213
|
+
await queryAndParse('namespace_module', `SELECT * FROM metaschema_modules_public.namespace_module WHERE database_id = $1 ORDER BY id`);
|
|
214
|
+
await queryAndParse('merkle_store_module', `SELECT * FROM metaschema_modules_public.merkle_store_module WHERE database_id = $1 ORDER BY id`);
|
|
215
|
+
await queryAndParse('graph_module', `SELECT * FROM metaschema_modules_public.graph_module WHERE database_id = $1 ORDER BY id`);
|
|
216
|
+
await queryAndParse('compute_log_module', `SELECT * FROM metaschema_modules_public.compute_log_module WHERE database_id = $1 ORDER BY id`);
|
|
217
|
+
await queryAndParse('db_usage_module', `SELECT * FROM metaschema_modules_public.db_usage_module WHERE database_id = $1 ORDER BY id`);
|
|
218
|
+
await queryAndParse('storage_log_module', `SELECT * FROM metaschema_modules_public.storage_log_module WHERE database_id = $1 ORDER BY id`);
|
|
219
|
+
await queryAndParse('transfer_log_module', `SELECT * FROM metaschema_modules_public.transfer_log_module WHERE database_id = $1 ORDER BY id`);
|
|
184
220
|
await queryAndParse('webauthn_auth_module', `SELECT * FROM metaschema_modules_public.webauthn_auth_module WHERE database_id = $1 ORDER BY id`);
|
|
185
221
|
await queryAndParse('webauthn_credentials_module', `SELECT * FROM metaschema_modules_public.webauthn_credentials_module WHERE database_id = $1 ORDER BY id`);
|
|
222
|
+
await queryAndParse('inference_log_module', `SELECT * FROM metaschema_modules_public.inference_log_module WHERE database_id = $1 ORDER BY id`);
|
|
223
|
+
await queryAndParse('rate_limit_meters_module', `SELECT * FROM metaschema_modules_public.rate_limit_meters_module WHERE database_id = $1 ORDER BY id`);
|
|
186
224
|
return sql;
|
|
187
225
|
};
|
|
188
226
|
exports.exportMeta = exportMeta;
|
package/export-utils.d.ts
CHANGED
|
@@ -4,7 +4,13 @@ import { PgpmPackage } from '@pgpmjs/core';
|
|
|
4
4
|
* Required extensions for database schema exports.
|
|
5
5
|
* Includes native PostgreSQL extensions and pgpm modules.
|
|
6
6
|
*/
|
|
7
|
-
export declare const DB_REQUIRED_EXTENSIONS: readonly ["plpgsql", "uuid-ossp", "citext", "pgcrypto", "btree_gin", "btree_gist", "pg_textsearch", "pg_trgm", "postgis", "hstore", "vector", "ltree", "metaschema-schema", "pgpm-inflection", "pgpm-utils", "pgpm-database-jobs", "pgpm-jwt-claims", "pgpm-stamps", "pgpm-base32", "pgpm-totp", "pgpm-types", "pgpm-ltree-helpers", "pgpm-partman"];
|
|
7
|
+
export declare const DB_REQUIRED_EXTENSIONS: readonly ["plpgsql", "uuid-ossp", "citext", "pgcrypto", "btree_gin", "btree_gist", "pg_textsearch", "pg_trgm", "postgis", "hstore", "vector", "ltree", "metaschema-schema", "pgpm-inflection", "pgpm-uuid", "pgpm-utils", "pgpm-database-jobs", "pgpm-jwt-claims", "pgpm-stamps", "pgpm-base32", "pgpm-totp", "pgpm-types", "pgpm-ltree-helpers", "pgpm-partman"];
|
|
8
|
+
/**
|
|
9
|
+
* Map PostgreSQL data types to FieldType values.
|
|
10
|
+
* Uses udt_name from information_schema which gives the base type name.
|
|
11
|
+
* Delegates to the canonical PG_TYPE_MAP in type-map.ts.
|
|
12
|
+
*/
|
|
13
|
+
export declare const mapPgTypeToFieldType: (udtName: string) => FieldType;
|
|
8
14
|
/**
|
|
9
15
|
* Required extensions for service/meta exports.
|
|
10
16
|
* Includes native PostgreSQL extensions and pgpm modules for metadata management.
|
|
@@ -23,24 +29,25 @@ export declare const META_COMMON_FOOTER = "\nSET session_replication_role TO DEF
|
|
|
23
29
|
* Ordered list of meta tables for export.
|
|
24
30
|
* Tables are processed in this order to satisfy foreign key dependencies.
|
|
25
31
|
*/
|
|
26
|
-
export declare const META_TABLE_ORDER: readonly ["database", "schema", "function", "table", "field", "spatial_relation", "policy", "index", "trigger", "trigger_function", "rls_function", "foreign_key_constraint", "primary_key_constraint", "unique_constraint", "check_constraint", "full_text_search", "schema_grant", "table_grant", "default_privilege", "domains", "sites", "apis", "apps", "site_modules", "site_themes", "site_metadata", "api_modules", "api_extensions", "api_schemas", "database_settings", "api_settings", "rls_settings", "cors_settings", "pubkey_settings", "webauthn_settings", "rls_module", "user_auth_module", "memberships_module", "permissions_module", "limits_module", "events_module", "users_module", "hierarchy_module", "membership_types_module", "invites_module", "emails_module", "sessions_module", "user_state_module", "profiles_module", "config_secrets_user_module", "connected_accounts_module", "phone_numbers_module", "crypto_addresses_module", "crypto_auth_module", "field_module", "table_module", "secure_table_provision", "uuid_module", "default_ids_module", "denormalized_table_field", "relation_provision", "entity_type_provision", "rate_limits_module", "storage_module", "billing_module", "billing_provider_module", "devices_module", "identity_providers_module", "notifications_module", "plans_module", "realtime_module", "session_secrets_module", "config_secrets_org_module", "webauthn_auth_module", "webauthn_credentials_module", "inference_log_module", "rate_limit_meters_module"];
|
|
32
|
+
export declare const META_TABLE_ORDER: readonly ["database", "schema", "function", "table", "field", "spatial_relation", "policy", "index", "trigger", "trigger_function", "rls_function", "foreign_key_constraint", "primary_key_constraint", "unique_constraint", "check_constraint", "full_text_search", "schema_grant", "table_grant", "default_privilege", "domains", "sites", "apis", "apps", "site_modules", "site_themes", "site_metadata", "api_modules", "api_extensions", "api_schemas", "database_settings", "api_settings", "rls_settings", "cors_settings", "pubkey_settings", "webauthn_settings", "rls_module", "user_auth_module", "memberships_module", "permissions_module", "limits_module", "levels_module", "events_module", "users_module", "hierarchy_module", "membership_types_module", "invites_module", "emails_module", "sessions_module", "user_state_module", "profiles_module", "config_secrets_user_module", "user_credentials_module", "user_settings_module", "connected_accounts_module", "phone_numbers_module", "crypto_addresses_module", "crypto_auth_module", "field_module", "table_module", "secure_table_provision", "uuid_module", "default_ids_module", "denormalized_table_field", "relation_provision", "entity_type_provision", "rate_limits_module", "storage_module", "billing_module", "billing_provider_module", "devices_module", "identity_providers_module", "notifications_module", "plans_module", "realtime_module", "session_secrets_module", "config_secrets_org_module", "config_secrets_module", "i18n_module", "agent_module", "function_module", "namespace_module", "merkle_store_module", "graph_module", "compute_log_module", "db_usage_module", "storage_log_module", "transfer_log_module", "webauthn_auth_module", "webauthn_credentials_module", "inference_log_module", "rate_limit_meters_module"];
|
|
27
33
|
export type FieldType = 'uuid' | 'uuid[]' | 'text' | 'text[]' | 'boolean' | 'image' | 'upload' | 'url' | 'jsonb' | 'jsonb[]' | 'int' | 'interval' | 'timestamptz';
|
|
28
34
|
export interface TableConfig {
|
|
29
35
|
schema: string;
|
|
30
36
|
table: string;
|
|
31
37
|
conflictDoNothing?: boolean;
|
|
32
|
-
|
|
38
|
+
typeOverrides?: Record<string, FieldType>;
|
|
39
|
+
gqlTypeName?: string;
|
|
33
40
|
}
|
|
34
41
|
/**
|
|
35
42
|
* Shared metadata table configuration.
|
|
36
43
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
44
|
+
* Fields are discovered dynamically at runtime via introspection:
|
|
45
|
+
* - SQL flow: uses information_schema.columns + mapPgTypeToFieldType()
|
|
46
|
+
* - GraphQL flow: uses __type introspection + mapGraphQLTypeToFieldType()
|
|
47
|
+
*
|
|
48
|
+
* Only `typeOverrides` are hardcoded for special types (image, upload, url)
|
|
49
|
+
* that cannot be inferred from database/GraphQL types alone.
|
|
42
50
|
*
|
|
43
|
-
* Adding a field here that doesn't exist in a particular environment is safe.
|
|
44
51
|
*/
|
|
45
52
|
export declare const META_TABLE_CONFIG: Record<string, TableConfig>;
|
|
46
53
|
export interface Schema {
|