@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.
@@ -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 { GraphQLTypeInfo } from './graphql-naming';
5
6
  interface GraphQLClientOptions {
6
7
  endpoint: string;
7
8
  token?: string;
@@ -21,5 +22,13 @@ export declare class GraphQLClient {
21
22
  */
22
23
  fetchAllNodes<T = Record<string, unknown>>(queryFieldName: string, fieldsFragment: string, condition?: Record<string, unknown>, pageSize?: number, orderBy?: string): Promise<T[]>;
23
24
  private buildConnectionArgs;
25
+ /**
26
+ * Introspect a GraphQL type to discover its fields and their types.
27
+ * Used by the dynamic export flow to discover what fields are available
28
+ * instead of hardcoding them in META_TABLE_CONFIG.
29
+ *
30
+ * Returns a Map of camelCase field name → { typeName, list, nonNull }.
31
+ */
32
+ introspectType(typeName: string): Promise<Map<string, GraphQLTypeInfo>>;
24
33
  }
25
34
  export {};
package/graphql-client.js CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.GraphQLClient = void 0;
8
+ const graphql_naming_1 = require("./graphql-naming");
8
9
  class GraphQLClient {
9
10
  endpoint;
10
11
  defaultHeaders;
@@ -139,5 +140,47 @@ class GraphQLClient {
139
140
  }
140
141
  return parts.length > 0 ? `(${parts.join(', ')})` : '';
141
142
  }
143
+ /**
144
+ * Introspect a GraphQL type to discover its fields and their types.
145
+ * Used by the dynamic export flow to discover what fields are available
146
+ * instead of hardcoding them in META_TABLE_CONFIG.
147
+ *
148
+ * Returns a Map of camelCase field name → { typeName, list, nonNull }.
149
+ */
150
+ async introspectType(typeName) {
151
+ const result = await this.query(`
152
+ query IntrospectType($typeName: String!) {
153
+ __type(name: $typeName) {
154
+ fields {
155
+ name
156
+ type {
157
+ name
158
+ kind
159
+ ofType {
160
+ name
161
+ kind
162
+ ofType {
163
+ name
164
+ kind
165
+ ofType {
166
+ name
167
+ kind
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ `, { typeName });
176
+ const fields = new Map();
177
+ if (result.__type?.fields) {
178
+ for (const field of result.__type.fields) {
179
+ const typeInfo = (0, graphql_naming_1.unwrapGraphQLType)(field.type);
180
+ fields.set(field.name, typeInfo);
181
+ }
182
+ }
183
+ return fields;
184
+ }
142
185
  }
143
186
  exports.GraphQLClient = GraphQLClient;
@@ -1,3 +1,4 @@
1
+ import { FieldType } from './export-utils';
1
2
  /**
2
3
  * Get the GraphQL query field name for a given Postgres table name.
3
4
  * Mirrors the PostGraphile InflektPlugin's allRowsConnection inflector:
@@ -10,11 +11,7 @@ export declare const getGraphQLQueryName: (pgTableName: string) => string;
10
11
  * Only transforms top-level keys — nested objects (e.g. JSONB values) are left intact.
11
12
  */
12
13
  export declare const graphqlRowToPostgresRow: (row: Record<string, unknown>) => Record<string, unknown>;
13
- /**
14
- * Convert a PostgreSQL interval object (from GraphQL Interval type) back to a Postgres interval string.
15
- * e.g. { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 } -> '1 hour 30 minutes'
16
- */
17
- export declare const intervalToPostgres: (interval: Record<string, number | null> | null) => string | null;
14
+ export { intervalToPostgres } from './interval-utils';
18
15
  /**
19
16
  * Convert an array of Postgres field names (with optional type hints) to a GraphQL fields fragment.
20
17
  * Handles composite types like 'interval' by expanding them into subfield selections.
@@ -22,3 +19,39 @@ export declare const intervalToPostgres: (interval: Record<string, number | null
22
19
  * 'id\nsessionsDefaultExpiration { seconds minutes hours days months years }'
23
20
  */
24
21
  export declare const buildFieldsFragment: (pgFieldNames: string[], fieldTypes?: Record<string, string>) => string;
22
+ /**
23
+ * Represents the unwrapped type info from a GraphQL introspection field.
24
+ * PostGraphile wraps types in NON_NULL and LIST layers via nested `ofType`.
25
+ */
26
+ export interface GraphQLTypeInfo {
27
+ /** The leaf/nullable type name (e.g. "UUID", "String", "Interval") */
28
+ typeName: string;
29
+ /** The leaf type kind (e.g. "SCALAR", "OBJECT", "ENUM") */
30
+ kind: string;
31
+ /** Whether the outermost wrapper is NON_NULL */
32
+ nonNull: boolean;
33
+ /** Whether the type is a list */
34
+ list: boolean;
35
+ }
36
+ /**
37
+ * Unwrap a GraphQL introspection type reference into its leaf type name and list status.
38
+ * PostGraphile wraps types like: { kind: NON_NULL, name: null, ofType: { kind: LIST, name: null, ofType: { kind: SCALAR, name: "UUID" } } }
39
+ * This function recursively unwraps ofType layers, detecting LIST wrappers via the `kind` field.
40
+ */
41
+ export declare const unwrapGraphQLType: (typeRef: {
42
+ name: string | null;
43
+ kind?: string;
44
+ ofType?: any;
45
+ } | null, parentKind?: string) => GraphQLTypeInfo;
46
+ /**
47
+ * Map GraphQL scalar/type names to FieldType values.
48
+ * Delegates to the canonical PG_TYPE_MAP in type-map.ts.
49
+ */
50
+ export declare const mapGraphQLTypeToFieldType: (gqlTypeName: string, isList?: boolean) => FieldType;
51
+ /**
52
+ * Derive the GraphQL type name (PascalCase singular) from a PostgreSQL table name.
53
+ * Mirrors PostGraphile's InflektPlugin type inflector:
54
+ * singularizeLast(toPascalCase(pgTableName))
55
+ * e.g. "user_auth_module" → "UserAuthModule"
56
+ */
57
+ export declare const getGraphQLTypeName: (pgTableName: string) => string;
package/graphql-naming.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildFieldsFragment = exports.intervalToPostgres = exports.graphqlRowToPostgresRow = exports.getGraphQLQueryName = void 0;
3
+ exports.getGraphQLTypeName = exports.mapGraphQLTypeToFieldType = exports.unwrapGraphQLType = exports.buildFieldsFragment = exports.intervalToPostgres = exports.graphqlRowToPostgresRow = exports.getGraphQLQueryName = void 0;
4
4
  /**
5
5
  * Helpers for mapping between PostgreSQL names and PostGraphile GraphQL names.
6
6
  *
@@ -17,6 +17,7 @@ exports.buildFieldsFragment = exports.intervalToPostgres = exports.graphqlRowToP
17
17
  * column database_id -> databaseId
18
18
  */
19
19
  const inflekt_1 = require("inflekt");
20
+ const type_map_1 = require("./type-map");
20
21
  /**
21
22
  * Get the GraphQL query field name for a given Postgres table name.
22
23
  * Mirrors the PostGraphile InflektPlugin's allRowsConnection inflector:
@@ -41,29 +42,8 @@ const graphqlRowToPostgresRow = (row) => {
41
42
  return result;
42
43
  };
43
44
  exports.graphqlRowToPostgresRow = graphqlRowToPostgresRow;
44
- /**
45
- * Convert a PostgreSQL interval object (from GraphQL Interval type) back to a Postgres interval string.
46
- * e.g. { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 } -> '1 hour 30 minutes'
47
- */
48
- const intervalToPostgres = (interval) => {
49
- if (!interval)
50
- return null;
51
- const parts = [];
52
- if (interval.years)
53
- parts.push(`${interval.years} year${interval.years !== 1 ? 's' : ''}`);
54
- if (interval.months)
55
- parts.push(`${interval.months} mon${interval.months !== 1 ? 's' : ''}`);
56
- if (interval.days)
57
- parts.push(`${interval.days} day${interval.days !== 1 ? 's' : ''}`);
58
- if (interval.hours)
59
- parts.push(`${interval.hours}:${String(interval.minutes ?? 0).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
60
- else if (interval.minutes)
61
- parts.push(`00:${String(interval.minutes).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
62
- else if (interval.seconds)
63
- parts.push(`00:00:${String(interval.seconds).padStart(2, '0')}`);
64
- return parts.length > 0 ? parts.join(' ') : '00:00:00';
65
- };
66
- exports.intervalToPostgres = intervalToPostgres;
45
+ var interval_utils_1 = require("./interval-utils");
46
+ Object.defineProperty(exports, "intervalToPostgres", { enumerable: true, get: function () { return interval_utils_1.intervalToPostgres; } });
67
47
  /**
68
48
  * Convert an array of Postgres field names (with optional type hints) to a GraphQL fields fragment.
69
49
  * Handles composite types like 'interval' by expanding them into subfield selections.
@@ -81,3 +61,57 @@ const buildFieldsFragment = (pgFieldNames, fieldTypes) => {
81
61
  }).join('\n ');
82
62
  };
83
63
  exports.buildFieldsFragment = buildFieldsFragment;
64
+ /**
65
+ * Unwrap a GraphQL introspection type reference into its leaf type name and list status.
66
+ * PostGraphile wraps types like: { kind: NON_NULL, name: null, ofType: { kind: LIST, name: null, ofType: { kind: SCALAR, name: "UUID" } } }
67
+ * This function recursively unwraps ofType layers, detecting LIST wrappers via the `kind` field.
68
+ */
69
+ const unwrapGraphQLType = (typeRef, parentKind) => {
70
+ if (!typeRef)
71
+ return { typeName: 'Unknown', kind: 'UNKNOWN', nonNull: false, list: false };
72
+ // If the type has a name, it's the leaf type
73
+ if (typeRef.name) {
74
+ const isList = parentKind === 'LIST';
75
+ return { typeName: typeRef.name, kind: typeRef.kind ?? 'UNKNOWN', nonNull: parentKind === 'NON_NULL', list: isList };
76
+ }
77
+ // If it has ofType, it's a wrapper (NON_NULL or LIST)
78
+ if (typeRef.ofType) {
79
+ return (0, exports.unwrapGraphQLType)(typeRef.ofType, typeRef.kind ?? undefined);
80
+ }
81
+ return { typeName: 'Unknown', kind: 'UNKNOWN', nonNull: false, list: false };
82
+ };
83
+ exports.unwrapGraphQLType = unwrapGraphQLType;
84
+ /**
85
+ * Map GraphQL scalar/type names to FieldType values.
86
+ * Delegates to the canonical PG_TYPE_MAP in type-map.ts.
87
+ */
88
+ const mapGraphQLTypeToFieldType = (gqlTypeName, isList = false) => {
89
+ // Handle list types — map to the array variants that exist in FieldType
90
+ if (isList) {
91
+ const inner = (0, exports.mapGraphQLTypeToFieldType)(gqlTypeName, false);
92
+ // Only these array types exist in FieldType: uuid[], text[], jsonb[]
93
+ switch (inner) {
94
+ case 'uuid': return 'uuid[]';
95
+ case 'text': return 'text[]';
96
+ case 'jsonb': return 'jsonb[]';
97
+ default: return 'text'; // safe fallback for unsupported array types
98
+ }
99
+ }
100
+ // ID is a GraphQL-only type (relay-style) that maps to uuid;
101
+ // it has no direct PG udt_name counterpart in PG_TYPE_MAP.
102
+ if (gqlTypeName === 'ID')
103
+ return 'uuid';
104
+ const entry = (0, type_map_1.lookupByGqlType)(gqlTypeName);
105
+ return entry?.fieldType ?? 'text';
106
+ };
107
+ exports.mapGraphQLTypeToFieldType = mapGraphQLTypeToFieldType;
108
+ /**
109
+ * Derive the GraphQL type name (PascalCase singular) from a PostgreSQL table name.
110
+ * Mirrors PostGraphile's InflektPlugin type inflector:
111
+ * singularizeLast(toPascalCase(pgTableName))
112
+ * e.g. "user_auth_module" → "UserAuthModule"
113
+ */
114
+ const getGraphQLTypeName = (pgTableName) => {
115
+ return (0, inflekt_1.singularizeLast)((0, inflekt_1.toPascalCase)(pgTableName));
116
+ };
117
+ exports.getGraphQLTypeName = getGraphQLTypeName;
package/index.d.ts CHANGED
@@ -3,6 +3,8 @@ 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, intervalToPostgres } from './graphql-naming';
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, GraphQLTypeInfo } 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
8
  export type { FieldType, TableConfig, Schema, MakeReplacerOptions, ReplacerResult, PreparePackageOptions, MissingModulesResult } from './export-utils';
9
+ export { PG_TYPE_MAP, TypeMapEntry, lookupByPgUdt, lookupByGqlType } from './type-map';
10
+ export { intervalToPostgres, parsePgInterval, PgInterval } from './interval-utils';
package/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.installMissingModules = exports.detectMissingModules = exports.normalizeOutdir = exports.preparePackage = exports.makeReplacer = exports.META_TABLE_CONFIG = exports.META_TABLE_ORDER = exports.META_COMMON_FOOTER = exports.META_COMMON_HEADER = exports.SERVICE_REQUIRED_EXTENSIONS = exports.DB_REQUIRED_EXTENSIONS = exports.intervalToPostgres = exports.buildFieldsFragment = exports.graphqlRowToPostgresRow = exports.getGraphQLQueryName = exports.GraphQLClient = void 0;
17
+ exports.parsePgInterval = exports.intervalToPostgres = exports.lookupByGqlType = exports.lookupByPgUdt = exports.PG_TYPE_MAP = exports.installMissingModules = exports.detectMissingModules = exports.normalizeOutdir = exports.preparePackage = exports.makeReplacer = exports.mapPgTypeToFieldType = exports.META_TABLE_CONFIG = exports.META_TABLE_ORDER = exports.META_COMMON_FOOTER = exports.META_COMMON_HEADER = exports.SERVICE_REQUIRED_EXTENSIONS = exports.DB_REQUIRED_EXTENSIONS = exports.unwrapGraphQLType = exports.mapGraphQLTypeToFieldType = exports.buildFieldsFragment = exports.graphqlRowToPostgresRow = exports.getGraphQLTypeName = exports.getGraphQLQueryName = exports.GraphQLClient = void 0;
18
18
  __exportStar(require("./export-meta"), exports);
19
19
  __exportStar(require("./export-migrations"), exports);
20
20
  __exportStar(require("./export-graphql"), exports);
@@ -23,9 +23,11 @@ var graphql_client_1 = require("./graphql-client");
23
23
  Object.defineProperty(exports, "GraphQLClient", { enumerable: true, get: function () { return graphql_client_1.GraphQLClient; } });
24
24
  var graphql_naming_1 = require("./graphql-naming");
25
25
  Object.defineProperty(exports, "getGraphQLQueryName", { enumerable: true, get: function () { return graphql_naming_1.getGraphQLQueryName; } });
26
+ Object.defineProperty(exports, "getGraphQLTypeName", { enumerable: true, get: function () { return graphql_naming_1.getGraphQLTypeName; } });
26
27
  Object.defineProperty(exports, "graphqlRowToPostgresRow", { enumerable: true, get: function () { return graphql_naming_1.graphqlRowToPostgresRow; } });
27
28
  Object.defineProperty(exports, "buildFieldsFragment", { enumerable: true, get: function () { return graphql_naming_1.buildFieldsFragment; } });
28
- Object.defineProperty(exports, "intervalToPostgres", { enumerable: true, get: function () { return graphql_naming_1.intervalToPostgres; } });
29
+ Object.defineProperty(exports, "mapGraphQLTypeToFieldType", { enumerable: true, get: function () { return graphql_naming_1.mapGraphQLTypeToFieldType; } });
30
+ Object.defineProperty(exports, "unwrapGraphQLType", { enumerable: true, get: function () { return graphql_naming_1.unwrapGraphQLType; } });
29
31
  var export_utils_1 = require("./export-utils");
30
32
  Object.defineProperty(exports, "DB_REQUIRED_EXTENSIONS", { enumerable: true, get: function () { return export_utils_1.DB_REQUIRED_EXTENSIONS; } });
31
33
  Object.defineProperty(exports, "SERVICE_REQUIRED_EXTENSIONS", { enumerable: true, get: function () { return export_utils_1.SERVICE_REQUIRED_EXTENSIONS; } });
@@ -33,8 +35,16 @@ Object.defineProperty(exports, "META_COMMON_HEADER", { enumerable: true, get: fu
33
35
  Object.defineProperty(exports, "META_COMMON_FOOTER", { enumerable: true, get: function () { return export_utils_1.META_COMMON_FOOTER; } });
34
36
  Object.defineProperty(exports, "META_TABLE_ORDER", { enumerable: true, get: function () { return export_utils_1.META_TABLE_ORDER; } });
35
37
  Object.defineProperty(exports, "META_TABLE_CONFIG", { enumerable: true, get: function () { return export_utils_1.META_TABLE_CONFIG; } });
38
+ Object.defineProperty(exports, "mapPgTypeToFieldType", { enumerable: true, get: function () { return export_utils_1.mapPgTypeToFieldType; } });
36
39
  Object.defineProperty(exports, "makeReplacer", { enumerable: true, get: function () { return export_utils_1.makeReplacer; } });
37
40
  Object.defineProperty(exports, "preparePackage", { enumerable: true, get: function () { return export_utils_1.preparePackage; } });
38
41
  Object.defineProperty(exports, "normalizeOutdir", { enumerable: true, get: function () { return export_utils_1.normalizeOutdir; } });
39
42
  Object.defineProperty(exports, "detectMissingModules", { enumerable: true, get: function () { return export_utils_1.detectMissingModules; } });
40
43
  Object.defineProperty(exports, "installMissingModules", { enumerable: true, get: function () { return export_utils_1.installMissingModules; } });
44
+ var type_map_1 = require("./type-map");
45
+ Object.defineProperty(exports, "PG_TYPE_MAP", { enumerable: true, get: function () { return type_map_1.PG_TYPE_MAP; } });
46
+ Object.defineProperty(exports, "lookupByPgUdt", { enumerable: true, get: function () { return type_map_1.lookupByPgUdt; } });
47
+ Object.defineProperty(exports, "lookupByGqlType", { enumerable: true, get: function () { return type_map_1.lookupByGqlType; } });
48
+ var interval_utils_1 = require("./interval-utils");
49
+ Object.defineProperty(exports, "intervalToPostgres", { enumerable: true, get: function () { return interval_utils_1.intervalToPostgres; } });
50
+ Object.defineProperty(exports, "parsePgInterval", { enumerable: true, get: function () { return interval_utils_1.parsePgInterval; } });
@@ -0,0 +1,33 @@
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
+ /** Shape of a PostGraphile Interval object (OBJECT type in introspection). */
11
+ export interface PgInterval {
12
+ years: number | null;
13
+ months: number | null;
14
+ days: number | null;
15
+ hours: number | null;
16
+ minutes: number | null;
17
+ seconds: number | null;
18
+ }
19
+ /**
20
+ * Convert a PostgreSQL interval object (from GraphQL Interval type) back to a
21
+ * Postgres interval string.
22
+ * e.g. { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 } → '1:30:00'
23
+ */
24
+ export declare const intervalToPostgres: (interval: Record<string, number | null> | null) => string | null;
25
+ /**
26
+ * Parse a PostgreSQL interval string into the object shape that PostGraphile's
27
+ * Interval type returns: { years, months, days, hours, minutes, seconds }.
28
+ *
29
+ * Handles formats like:
30
+ * '30 days' → { years: 0, months: 0, days: 30, hours: 0, minutes: 0, seconds: 0 }
31
+ * '1:30:00' → { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 }
32
+ */
33
+ export declare const parsePgInterval: (value: string) => Record<string, number>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ /**
3
+ * Interval conversion utilities shared between graphql-naming.ts and tests.
4
+ *
5
+ * NOTE: @pgpmjs/csv-to-pg has its own formatInterval() in parse.ts which
6
+ * performs the same object→string conversion. That is a separate package with
7
+ * its own release cycle, so we do not cross-reference it here. If interval
8
+ * handling needs to be unified across packages, that would require an
9
+ * architectural change (shared dependency or monorepo util package).
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.parsePgInterval = exports.intervalToPostgres = void 0;
13
+ /**
14
+ * Convert a PostgreSQL interval object (from GraphQL Interval type) back to a
15
+ * Postgres interval string.
16
+ * e.g. { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 } → '1:30:00'
17
+ */
18
+ const intervalToPostgres = (interval) => {
19
+ if (!interval)
20
+ return null;
21
+ const parts = [];
22
+ if (interval.years)
23
+ parts.push(`${interval.years} year${interval.years !== 1 ? 's' : ''}`);
24
+ if (interval.months)
25
+ parts.push(`${interval.months} mon${interval.months !== 1 ? 's' : ''}`);
26
+ if (interval.days)
27
+ parts.push(`${interval.days} day${interval.days !== 1 ? 's' : ''}`);
28
+ if (interval.hours)
29
+ parts.push(`${interval.hours}:${String(interval.minutes ?? 0).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
30
+ else if (interval.minutes)
31
+ parts.push(`00:${String(interval.minutes).padStart(2, '0')}:${String(interval.seconds ?? 0).padStart(2, '0')}`);
32
+ else if (interval.seconds)
33
+ parts.push(`00:00:${String(interval.seconds).padStart(2, '0')}`);
34
+ return parts.length > 0 ? parts.join(' ') : '00:00:00';
35
+ };
36
+ exports.intervalToPostgres = intervalToPostgres;
37
+ /**
38
+ * Parse a PostgreSQL interval string into the object shape that PostGraphile's
39
+ * Interval type returns: { years, months, days, hours, minutes, seconds }.
40
+ *
41
+ * Handles formats like:
42
+ * '30 days' → { years: 0, months: 0, days: 30, hours: 0, minutes: 0, seconds: 0 }
43
+ * '1:30:00' → { years: 0, months: 0, days: 0, hours: 1, minutes: 30, seconds: 0 }
44
+ */
45
+ const parsePgInterval = (value) => {
46
+ const result = { years: 0, months: 0, days: 0, hours: 0, minutes: 0, seconds: 0 };
47
+ // Try HH:MM:SS format
48
+ const timeMatch = value.match(/^(\d+):(\d+):(\d+)/);
49
+ if (timeMatch) {
50
+ result.hours = parseInt(timeMatch[1], 10);
51
+ result.minutes = parseInt(timeMatch[2], 10);
52
+ result.seconds = parseInt(timeMatch[3], 10);
53
+ return result;
54
+ }
55
+ // Try descriptive format: 'N unit N unit ...'
56
+ const parts = value.trim().split(/\s+/);
57
+ for (let i = 0; i < parts.length - 1; i += 2) {
58
+ const num = parseInt(parts[i], 10);
59
+ const unit = parts[i + 1].toLowerCase();
60
+ if (unit.startsWith('year'))
61
+ result.years = num;
62
+ else if (unit.startsWith('mon'))
63
+ result.months = num;
64
+ else if (unit.startsWith('day'))
65
+ result.days = num;
66
+ else if (unit.startsWith('hour'))
67
+ result.hours = num;
68
+ else if (unit.startsWith('minute'))
69
+ result.minutes = num;
70
+ else if (unit.startsWith('second'))
71
+ result.seconds = num;
72
+ }
73
+ return result;
74
+ };
75
+ exports.parsePgInterval = parsePgInterval;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/export",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM export tools for SQL and GraphQL database migration extraction",
6
6
  "main": "index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@pgpmjs/core": "^6.21.0",
48
- "@pgpmjs/migrate-client": "^0.15.0",
48
+ "@pgpmjs/migrate-client": "^0.15.1",
49
49
  "@pgpmjs/types": "^2.29.0",
50
50
  "csv-to-pg": "^3.18.0",
51
51
  "glob": "^13.0.6",
@@ -55,5 +55,5 @@
55
55
  "pg-cache": "^3.12.0",
56
56
  "pg-env": "^1.16.0"
57
57
  },
58
- "gitHead": "0503916f414a3b8a6f6cbe46e03a59988b2f3cb5"
58
+ "gitHead": "8a39d7f293a1128aed84a3bc6a7a79c32d27759d"
59
59
  }
package/type-map.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Single-source type mapping between PostgreSQL, PostGraphile GraphQL, and FieldType.
3
+ *
4
+ * This is the canonical mapping table. All other mappers and tests derive from it:
5
+ * - `mapPgTypeToFieldType` in export-utils.ts
6
+ * - `mapGraphQLTypeToFieldType` in graphql-naming.ts
7
+ * - `pgUdtToGraphQLType` / `pgUdtToGraphQLKind` in cross-flow-parity test
8
+ * - parity table in dynamic-fields test
9
+ *
10
+ * When a new type needs to be supported, add it here and all consumers update automatically.
11
+ */
12
+ import { FieldType } from './export-utils';
13
+ export interface TypeMapEntry {
14
+ /** PostgreSQL udt_name values from information_schema (e.g. ['int4', 'int2']) */
15
+ pgUdtNames: string[];
16
+ /** PostGraphile v5 GraphQL type name (e.g. 'Int', 'BigInt', 'Datetime') */
17
+ gqlTypeName: string;
18
+ /** FieldType used by csv-to-pg Parser (e.g. 'int', 'timestamptz') */
19
+ fieldType: FieldType;
20
+ /** GraphQL kind that PostGraphile reports via introspection */
21
+ gqlKind: 'SCALAR' | 'OBJECT' | 'ENUM';
22
+ /** Whether this is a PostgreSQL array type (e.g. _uuid, _text, _jsonb) */
23
+ isArray?: boolean;
24
+ }
25
+ /**
26
+ * Canonical PG → GraphQL → FieldType mapping table.
27
+ * Aligned with PostGraphile v5's PgCodecsPlugin type assignments:
28
+ * - int2, int4 → Int
29
+ * - int8 (bigint) → BigInt
30
+ * - numeric → BigFloat
31
+ * - float4, float8 → Float
32
+ * - interval → Interval (OBJECT kind, not SCALAR)
33
+ * - timestamptz, timestamp → Datetime
34
+ */
35
+ export declare const PG_TYPE_MAP: TypeMapEntry[];
36
+ /**
37
+ * Look up a TypeMapEntry by PostgreSQL udt_name.
38
+ * Returns undefined for unknown types (callers should fall back to 'text').
39
+ */
40
+ export declare const lookupByPgUdt: (udtName: string) => TypeMapEntry | undefined;
41
+ /**
42
+ * Look up a TypeMapEntry by GraphQL type name.
43
+ * Returns undefined for unknown types (callers should fall back to 'text').
44
+ */
45
+ export declare const lookupByGqlType: (gqlTypeName: string) => TypeMapEntry | undefined;
package/type-map.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lookupByGqlType = exports.lookupByPgUdt = exports.PG_TYPE_MAP = void 0;
4
+ /**
5
+ * Canonical PG → GraphQL → FieldType mapping table.
6
+ * Aligned with PostGraphile v5's PgCodecsPlugin type assignments:
7
+ * - int2, int4 → Int
8
+ * - int8 (bigint) → BigInt
9
+ * - numeric → BigFloat
10
+ * - float4, float8 → Float
11
+ * - interval → Interval (OBJECT kind, not SCALAR)
12
+ * - timestamptz, timestamp → Datetime
13
+ */
14
+ exports.PG_TYPE_MAP = [
15
+ { pgUdtNames: ['uuid'], gqlTypeName: 'UUID', fieldType: 'uuid', gqlKind: 'SCALAR' },
16
+ { pgUdtNames: ['_uuid'], gqlTypeName: 'UUID', fieldType: 'uuid[]', gqlKind: 'SCALAR', isArray: true },
17
+ { pgUdtNames: ['text', 'varchar', 'bpchar', 'name', 'citext'], gqlTypeName: 'String', fieldType: 'text', gqlKind: 'SCALAR' },
18
+ { pgUdtNames: ['_text', '_varchar', '_citext'], gqlTypeName: 'String', fieldType: 'text[]', gqlKind: 'SCALAR', isArray: true },
19
+ { pgUdtNames: ['bool'], gqlTypeName: 'Boolean', fieldType: 'boolean', gqlKind: 'SCALAR' },
20
+ { pgUdtNames: ['jsonb', 'json'], gqlTypeName: 'JSON', fieldType: 'jsonb', gqlKind: 'SCALAR' },
21
+ { pgUdtNames: ['_jsonb'], gqlTypeName: 'JSON', fieldType: 'jsonb[]', gqlKind: 'SCALAR', isArray: true },
22
+ { pgUdtNames: ['int2', 'int4'], gqlTypeName: 'Int', fieldType: 'int', gqlKind: 'SCALAR' },
23
+ { pgUdtNames: ['int8'], gqlTypeName: 'BigInt', fieldType: 'int', gqlKind: 'SCALAR' },
24
+ { pgUdtNames: ['numeric'], gqlTypeName: 'BigFloat', fieldType: 'int', gqlKind: 'SCALAR' },
25
+ { pgUdtNames: ['float4', 'float8'], gqlTypeName: 'Float', fieldType: 'int', gqlKind: 'SCALAR' },
26
+ { pgUdtNames: ['interval'], gqlTypeName: 'Interval', fieldType: 'interval', gqlKind: 'OBJECT' },
27
+ { pgUdtNames: ['timestamptz', 'timestamp'], gqlTypeName: 'Datetime', fieldType: 'timestamptz', gqlKind: 'SCALAR' },
28
+ ];
29
+ // =============================================================================
30
+ // Lookup indices (built once at module load)
31
+ // =============================================================================
32
+ /** Reverse index: pgUdtName → TypeMapEntry */
33
+ const pgUdtIndex = new Map();
34
+ for (const entry of exports.PG_TYPE_MAP) {
35
+ for (const udt of entry.pgUdtNames) {
36
+ pgUdtIndex.set(udt, entry);
37
+ }
38
+ }
39
+ /** Reverse index: gqlTypeName → TypeMapEntry (first match wins) */
40
+ const gqlTypeIndex = new Map();
41
+ for (const entry of exports.PG_TYPE_MAP) {
42
+ if (!gqlTypeIndex.has(entry.gqlTypeName)) {
43
+ gqlTypeIndex.set(entry.gqlTypeName, entry);
44
+ }
45
+ }
46
+ /**
47
+ * Look up a TypeMapEntry by PostgreSQL udt_name.
48
+ * Returns undefined for unknown types (callers should fall back to 'text').
49
+ */
50
+ const lookupByPgUdt = (udtName) => pgUdtIndex.get(udtName);
51
+ exports.lookupByPgUdt = lookupByPgUdt;
52
+ /**
53
+ * Look up a TypeMapEntry by GraphQL type name.
54
+ * Returns undefined for unknown types (callers should fall back to 'text').
55
+ */
56
+ const lookupByGqlType = (gqlTypeName) => gqlTypeIndex.get(gqlTypeName);
57
+ exports.lookupByGqlType = lookupByGqlType;