joist-codegen 2.3.0-next.99 → 2.4.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.
Files changed (67) hide show
  1. package/build/EntityDbMetadata.cjs +43 -13
  2. package/build/EntityDbMetadata.cjs.map +1 -1
  3. package/build/EntityDbMetadata.d.cts +4 -2
  4. package/build/EntityDbMetadata.d.cts.map +1 -1
  5. package/build/EntityDbMetadata.d.mts +4 -2
  6. package/build/EntityDbMetadata.d.mts.map +1 -1
  7. package/build/EntityDbMetadata.js +41 -11
  8. package/build/EntityDbMetadata.js.map +1 -1
  9. package/build/generateEntitiesFile.cjs +1 -1
  10. package/build/generateEntitiesFile.cjs.map +1 -1
  11. package/build/generateEntitiesFile.d.cts +1 -1
  12. package/build/generateEntitiesFile.d.mts +1 -1
  13. package/build/generateEntitiesFile.js +1 -1
  14. package/build/generateEntitiesFile.js.map +1 -1
  15. package/build/generateEntityCodegenFile.cjs +1 -0
  16. package/build/generateEntityCodegenFile.cjs.map +1 -1
  17. package/build/generateEntityCodegenFile.d.cts.map +1 -1
  18. package/build/generateEntityCodegenFile.d.mts.map +1 -1
  19. package/build/generateEntityCodegenFile.js +1 -0
  20. package/build/generateEntityCodegenFile.js.map +1 -1
  21. package/build/generateMetadataFile.cjs +23 -17
  22. package/build/generateMetadataFile.cjs.map +1 -1
  23. package/build/generateMetadataFile.d.cts.map +1 -1
  24. package/build/generateMetadataFile.d.mts.map +1 -1
  25. package/build/generateMetadataFile.js +23 -17
  26. package/build/generateMetadataFile.js.map +1 -1
  27. package/build/index.cjs +6 -6
  28. package/build/index.cjs.map +1 -1
  29. package/build/index.d.cts.map +1 -1
  30. package/build/index.d.mts.map +1 -1
  31. package/build/index.js +7 -6
  32. package/build/index.js.map +1 -1
  33. package/build/inheritance.cjs +1 -0
  34. package/build/inheritance.cjs.map +1 -1
  35. package/build/inheritance.d.cts.map +1 -1
  36. package/build/inheritance.d.mts.map +1 -1
  37. package/build/inheritance.js +1 -0
  38. package/build/inheritance.js.map +1 -1
  39. package/build/loadMetadata.cjs +3 -3
  40. package/build/loadMetadata.cjs.map +1 -1
  41. package/build/loadMetadata.d.cts +1 -1
  42. package/build/loadMetadata.d.mts +1 -1
  43. package/build/loadMetadata.js +2 -2
  44. package/build/loadMetadata.js.map +1 -1
  45. package/build/pgMetadata.cjs +332 -0
  46. package/build/pgMetadata.cjs.map +1 -0
  47. package/build/pgMetadata.d.cts +128 -0
  48. package/build/pgMetadata.d.cts.map +1 -0
  49. package/build/pgMetadata.d.mts +128 -0
  50. package/build/pgMetadata.d.mts.map +1 -0
  51. package/build/pgMetadata.js +320 -0
  52. package/build/pgMetadata.js.map +1 -0
  53. package/build/utils.cjs +3 -1
  54. package/build/utils.cjs.map +1 -1
  55. package/build/utils.d.cts +4 -2
  56. package/build/utils.d.cts.map +1 -1
  57. package/build/utils.d.mts +4 -2
  58. package/build/utils.d.mts.map +1 -1
  59. package/build/utils.js +3 -2
  60. package/build/utils.js.map +1 -1
  61. package/package.json +12 -3
  62. package/skills/joist-docs/SKILL.md +1 -1
  63. package/skills/joist-em-basics/SKILL.md +2 -2
  64. package/skills/joist-em-execute/SKILL.md +35 -0
  65. package/skills/joist-em-find/SKILL.md +27 -0
  66. package/skills/joist-em-query/SKILL.md +32 -0
  67. package/skills/joist-reactive-hints/SKILL.md +2 -2
@@ -0,0 +1,320 @@
1
+ import pluralize from "pluralize";
2
+ //#region src/pgMetadata.ts
3
+ /** The small part of PostgreSQL's catalog model used by Joist codegen. */
4
+ var Items = class extends Array {
5
+ byName;
6
+ /** Looks up a column by name without scanning the table on every call. */
7
+ get(name) {
8
+ this.byName ??= new Map(this.map((item) => [item.name, item]));
9
+ const item = this.byName.get(name);
10
+ if (!item) throw new Error(`No item named ${name}`);
11
+ return item;
12
+ }
13
+ /** Keep the name index current if more catalog rows arrive after a lookup. */
14
+ push(...items) {
15
+ const length = super.push(...items);
16
+ if (this.byName) for (const item of items) this.byName.set(item.name, item);
17
+ return length;
18
+ }
19
+ };
20
+ var EnumType = class {
21
+ name;
22
+ values;
23
+ shortName;
24
+ constructor(name, values) {
25
+ this.name = name;
26
+ this.values = values;
27
+ }
28
+ };
29
+ var Column = class {
30
+ name;
31
+ type;
32
+ notNull;
33
+ defaultValue;
34
+ isGenerated;
35
+ arrayDimension;
36
+ comment;
37
+ foreignKeys = [];
38
+ uniqueIndexes = [];
39
+ isPrimaryKey = false;
40
+ constructor(name, type, notNull, defaultValue, isGenerated, arrayDimension, comment) {
41
+ this.name = name;
42
+ this.type = type;
43
+ this.notNull = notNull;
44
+ this.defaultValue = defaultValue;
45
+ this.isGenerated = isGenerated;
46
+ this.arrayDimension = arrayDimension;
47
+ this.comment = comment;
48
+ }
49
+ get isForeignKey() {
50
+ return this.foreignKeys.length > 0;
51
+ }
52
+ get default() {
53
+ return this.defaultValue?.replace(/^('.*?')::.+$/, "$1") ?? null;
54
+ }
55
+ get commentData() {
56
+ const data = this.comment?.match(/\[pg-structure\]([\s\S]*?)\[\/pg-structure\]/)?.[1];
57
+ return data ? JSON.parse(data) : void 0;
58
+ }
59
+ };
60
+ var Index = class {
61
+ name;
62
+ columns;
63
+ isUnique;
64
+ isPartial;
65
+ constructor(name, columns, isUnique, isPartial) {
66
+ this.name = name;
67
+ this.columns = columns;
68
+ this.isUnique = isUnique;
69
+ this.isPartial = isPartial;
70
+ }
71
+ };
72
+ var ForeignKey = class {
73
+ name;
74
+ table;
75
+ referencedTable;
76
+ columns;
77
+ isDeferred;
78
+ isDeferrable;
79
+ onDelete;
80
+ constructor(name, table, referencedTable, columns, isDeferred, isDeferrable, onDelete) {
81
+ this.name = name;
82
+ this.table = table;
83
+ this.referencedTable = referencedTable;
84
+ this.columns = columns;
85
+ this.isDeferred = isDeferred;
86
+ this.isDeferrable = isDeferrable;
87
+ this.onDelete = onDelete;
88
+ }
89
+ };
90
+ var M2ORelation = class {
91
+ foreignKey;
92
+ type = "m2o";
93
+ constructor(foreignKey) {
94
+ this.foreignKey = foreignKey;
95
+ }
96
+ get sourceTable() {
97
+ return this.foreignKey.table;
98
+ }
99
+ get targetTable() {
100
+ return this.foreignKey.referencedTable;
101
+ }
102
+ /** Uses the old short relation name so M2O metadata keeps its factory evaluation order. */
103
+ get name() {
104
+ const { foreignKey, sourceTable, targetTable } = this;
105
+ const hasOtherIncomingFKs = sourceTable.foreignKeys.some((fk) => fk !== foreignKey && fk.referencedTable === targetTable);
106
+ return pluralize.singular(hasOtherIncomingFKs ? foreignKey.columns[0].name.replace(/_id$/, "") : targetTable.name);
107
+ }
108
+ };
109
+ var O2MRelation = class {
110
+ foreignKey;
111
+ type = "o2m";
112
+ constructor(foreignKey) {
113
+ this.foreignKey = foreignKey;
114
+ }
115
+ get sourceTable() {
116
+ return this.foreignKey.referencedTable;
117
+ }
118
+ get targetTable() {
119
+ return this.foreignKey.table;
120
+ }
121
+ /** Keeps inverse relations in the same short-name order as pg-structure. */
122
+ get name() {
123
+ const { foreignKey, sourceTable, targetTable } = this;
124
+ if (!targetTable.foreignKeys.some((fk) => fk !== foreignKey && fk.referencedTable === sourceTable)) return pluralize.plural(targetTable.name);
125
+ const columnName = foreignKey.columns[0].name.replace(/_id$/, "");
126
+ const sourceName = pluralize.singular(sourceTable.name);
127
+ const adjective = columnName.endsWith(`_${sourceName}`) ? columnName.slice(0, -sourceName.length - 1) : "";
128
+ if (sourceTable === targetTable) return pluralize.plural(adjective || targetTable.name);
129
+ return pluralize.plural(`${adjective || sourceTable.name}_${targetTable.name}`);
130
+ }
131
+ };
132
+ var M2MRelation = class {
133
+ foreignKey;
134
+ targetForeignKey;
135
+ type = "m2m";
136
+ constructor(foreignKey, targetForeignKey) {
137
+ this.foreignKey = foreignKey;
138
+ this.targetForeignKey = targetForeignKey;
139
+ }
140
+ get sourceTable() {
141
+ return this.foreignKey.referencedTable;
142
+ }
143
+ get targetTable() {
144
+ return this.targetForeignKey.referencedTable;
145
+ }
146
+ get joinTable() {
147
+ return this.foreignKey.table;
148
+ }
149
+ /** Adds the join-table qualifier when more than one join path reaches the same target. */
150
+ get name() {
151
+ const { sourceTable, joinTable, targetTable } = this;
152
+ const joinTables = new Set(sourceTable.foreignKeysToThis.filter((fk) => fk.table.foreignKeys.some((other) => other !== fk && other.referencedTable === targetTable)).map((fk) => fk.table));
153
+ const targetName = pluralize.plural(targetTable.name);
154
+ if (joinTables.size < 2) return targetName;
155
+ return `${joinTable.name.replace(sourceTable.name, "").replace(targetTable.name, "").replace(/^_+|_+$/g, "")}_${targetName}`;
156
+ }
157
+ };
158
+ var Table = class {
159
+ name;
160
+ schema;
161
+ columns = new Items();
162
+ indexes = new Items();
163
+ foreignKeys = [];
164
+ foreignKeysToThis = [];
165
+ uniqueConstraints = [];
166
+ primaryKey;
167
+ constructor(name, schema) {
168
+ this.name = name;
169
+ this.schema = schema;
170
+ }
171
+ get m2oRelations() {
172
+ return this.foreignKeys.map((fk) => new M2ORelation(fk)).sort((a, b) => compareNames(a.name, b.name));
173
+ }
174
+ get o2mRelations() {
175
+ return this.foreignKeysToThis.map((fk) => new O2MRelation(fk)).sort((a, b) => compareNames(a.name.toUpperCase(), b.name.toUpperCase()));
176
+ }
177
+ get m2mRelations() {
178
+ return this.foreignKeysToThis.flatMap((fk) => fk.table.foreignKeys.filter((other) => other !== fk).map((other) => new M2MRelation(fk, other))).sort((a, b) => compareNames(a.name.toUpperCase(), b.name.toUpperCase()));
179
+ }
180
+ };
181
+ /**
182
+ * Reads only the PostgreSQL catalog data used by codegen.
183
+ *
184
+ * Load tables and columns before constraints so each foreign key can link the same Table and Column
185
+ * objects used by entity fields. For example, books.author_id and authors.id are joined through
186
+ * the foreign key, while a trigger calling cyanaudit in another schema is irrelevant to codegen.
187
+ */
188
+ async function loadPgMetadata(client) {
189
+ const tables = new Items();
190
+ const byOid = /* @__PURE__ */ new Map();
191
+ const columns = /* @__PURE__ */ new Map();
192
+ const tableRows = await client.query(`
193
+ SELECT c.oid, c.relname AS name, n.nspname AS schema
194
+ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
195
+ WHERE c.relkind IN ('r', 'p') AND n.nspname <> 'information_schema'
196
+ AND n.nspname NOT LIKE 'pg_%' AND NOT pg_is_other_temp_schema(n.oid)
197
+ ORDER BY n.nspname, c.relname
198
+ `);
199
+ for (const row of tableRows.rows) {
200
+ const table = new Table(row.name, { name: row.schema });
201
+ tables.push(table);
202
+ byOid.set(row.oid, table);
203
+ columns.set(row.oid, /* @__PURE__ */ new Map());
204
+ }
205
+ const enumRows = await client.query(`
206
+ SELECT t.oid, t.typname AS name, e.enumlabel AS value
207
+ FROM pg_type t JOIN pg_enum e ON e.enumtypid = t.oid
208
+ JOIN pg_namespace n ON n.oid = t.typnamespace
209
+ WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg_%'
210
+ ORDER BY t.oid, e.enumsortorder
211
+ `);
212
+ const enums = /* @__PURE__ */ new Map();
213
+ for (const row of enumRows.rows) {
214
+ if (!enums.has(row.oid)) enums.set(row.oid, new EnumType(row.name, []));
215
+ enums.get(row.oid).values.push(row.value);
216
+ }
217
+ const columnRows = await client.query(`
218
+ SELECT a.attrelid AS "tableOid", a.attnum AS position, a.attname AS name,
219
+ COALESCE(NULLIF(t.typelem, 0), t.oid) AS "typeOid",
220
+ base.typname AS "typeName", a.attnotnull AS "notNull",
221
+ pg_get_expr(d.adbin, d.adrelid) AS "defaultValue", a.attgenerated AS generated,
222
+ a.attndims AS "arrayDimension", col_description(a.attrelid, a.attnum) AS comment
223
+ FROM pg_attribute a JOIN pg_type t ON t.oid = a.atttypid
224
+ JOIN pg_type base ON base.oid = COALESCE(NULLIF(t.typelem, 0), t.oid)
225
+ LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
226
+ WHERE a.attrelid = ANY($1::oid[]) AND a.attnum > 0 AND NOT a.attisdropped
227
+ ORDER BY a.attrelid, a.attnum
228
+ `, [Array.from(byOid.keys())]);
229
+ for (const row of columnRows.rows) {
230
+ const type = enums.get(row.typeOid) ?? sqlType(row.typeName);
231
+ const column = new Column(row.name, type, row.notNull, row.defaultValue, row.generated !== "", row.arrayDimension, row.comment);
232
+ byOid.get(row.tableOid).columns.push(column);
233
+ columns.get(row.tableOid).set(row.position, column);
234
+ }
235
+ const indexRows = await client.query(`
236
+ SELECT i.indrelid AS "tableOid", i.indexrelid AS "indexOid", c.relname AS name,
237
+ i.indkey::int2[] AS positions, i.indisunique AS unique, i.indpred IS NOT NULL AS partial
238
+ FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid
239
+ WHERE i.indrelid = ANY($1::oid[])
240
+ `, [Array.from(byOid.keys())]);
241
+ const indexes = /* @__PURE__ */ new Map();
242
+ for (const row of indexRows.rows) {
243
+ const table = byOid.get(row.tableOid);
244
+ const index = new Index(row.name, row.positions.flatMap((pos) => columns.get(row.tableOid).get(pos) ?? []), row.unique, row.partial);
245
+ table.indexes.push(index);
246
+ indexes.set(row.indexOid, index);
247
+ if (row.unique) for (const column of index.columns) column.uniqueIndexes.push(index);
248
+ }
249
+ const constraintRows = await client.query(`
250
+ SELECT conrelid AS "tableOid", confrelid AS "targetOid", conindid AS "indexOid",
251
+ conname AS name, contype AS kind, conkey AS positions,
252
+ condeferred AS deferred, condeferrable AS deferrable, confdeltype AS "onDelete"
253
+ FROM pg_constraint WHERE conrelid = ANY($1::oid[]) AND contype IN ('p', 'u', 'f')
254
+ ORDER BY conrelid, conname
255
+ `, [Array.from(byOid.keys())]);
256
+ for (const row of constraintRows.rows) {
257
+ const table = byOid.get(row.tableOid);
258
+ const keys = (row.positions ?? []).map((pos) => columns.get(row.tableOid).get(pos));
259
+ if (row.kind === "p") {
260
+ table.primaryKey = { columns: keys };
261
+ for (const column of keys) column.isPrimaryKey = true;
262
+ } else if (row.kind === "u") table.uniqueConstraints.push({
263
+ columns: keys,
264
+ index: indexes.get(row.indexOid)
265
+ });
266
+ else {
267
+ const target = byOid.get(row.targetOid);
268
+ if (!target) continue;
269
+ const fk = new ForeignKey(row.name, table, target, keys, row.deferred, row.deferrable, deleteAction(row.onDelete));
270
+ table.foreignKeys.push(fk);
271
+ target.foreignKeysToThis.push(fk);
272
+ for (const column of keys) column.foreignKeys.push(fk);
273
+ }
274
+ }
275
+ return {
276
+ tables,
277
+ types: Array.from(enums.values())
278
+ };
279
+ }
280
+ /** Matches the type names codegen expects from PostgreSQL's builtin aliases. */
281
+ function sqlType(name) {
282
+ switch (name) {
283
+ case "int2": return { name: "smallint" };
284
+ case "int4": return {
285
+ name: "integer",
286
+ shortName: "int"
287
+ };
288
+ case "int8": return { name: "bigint" };
289
+ case "bool": return { name: "boolean" };
290
+ case "float4": return { name: "real" };
291
+ case "float8": return { name: "double precision" };
292
+ case "varchar": return { name: "character varying" };
293
+ case "timestamptz": return { name: "timestamp with time zone" };
294
+ case "timestamp": return { name: "timestamp without time zone" };
295
+ case "time": return { name: "time without time zone" };
296
+ default: return {
297
+ name,
298
+ shortName: name
299
+ };
300
+ }
301
+ }
302
+ /** Decodes the pg_constraint foreign-key delete action. */
303
+ function deleteAction(code) {
304
+ switch (code) {
305
+ case "a": return "NO ACTION";
306
+ case "r": return "RESTRICT";
307
+ case "c": return "CASCADE";
308
+ case "n": return "SET NULL";
309
+ case "d": return "SET DEFAULT";
310
+ default: throw new Error(`Unknown PostgreSQL delete action: ${code}`);
311
+ }
312
+ }
313
+ /** Compares PostgreSQL relation names by code point, independent of the host locale. */
314
+ function compareNames(a, b) {
315
+ return a < b ? -1 : a > b ? 1 : 0;
316
+ }
317
+ //#endregion
318
+ export { Column, EnumType, ForeignKey, Index, Items, M2MRelation, M2ORelation, O2MRelation, Table, loadPgMetadata };
319
+
320
+ //# sourceMappingURL=pgMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pgMetadata.js","names":[],"sources":["../src/pgMetadata.ts"],"sourcesContent":["import type { Client } from \"pg\";\nimport pluralize from \"pluralize\";\n\n/** The small part of PostgreSQL's catalog model used by Joist codegen. */\nexport class Items<T extends { name: string }> extends Array<T> {\n private byName?: Map<string, T>;\n\n /** Looks up a column by name without scanning the table on every call. */\n get(name: string): T {\n // Catalog rows are appended before codegen reads them. Build the index on first lookup\n // so derived arrays from filter/map can still be indexed without special handling.\n this.byName ??= new Map(this.map((item) => [item.name, item]));\n const item = this.byName.get(name);\n if (!item) throw new Error(`No item named ${name}`);\n return item;\n }\n\n /** Keep the name index current if more catalog rows arrive after a lookup. */\n push(...items: T[]): number {\n const length = super.push(...items);\n if (this.byName) for (const item of items) this.byName.set(item.name, item);\n return length;\n }\n}\n\nexport type Action = \"NO ACTION\" | \"RESTRICT\" | \"CASCADE\" | \"SET NULL\" | \"SET DEFAULT\";\nexport type JSONData = string | number | boolean | null | JSONData[] | { [key: string]: JSONData };\n\nexport class EnumType {\n readonly shortName?: string;\n constructor(\n public name: string,\n public values: string[],\n ) {}\n}\n\nexport class Column {\n readonly foreignKeys: ForeignKey[] = [];\n readonly uniqueIndexes: Index[] = [];\n isPrimaryKey = false;\n\n constructor(\n public name: string,\n public type: { name: string; shortName?: string } | EnumType,\n public notNull: boolean,\n public defaultValue: string | null,\n public isGenerated: boolean,\n public arrayDimension: number,\n public comment: string | null,\n ) {}\n\n get isForeignKey(): boolean {\n return this.foreignKeys.length > 0;\n }\n\n get default(): string | null {\n return this.defaultValue?.replace(/^('.*?')::.+$/, \"$1\") ?? null;\n }\n\n get commentData(): JSONData | undefined {\n const data = this.comment?.match(/\\[pg-structure\\]([\\s\\S]*?)\\[\\/pg-structure\\]/)?.[1];\n return data ? (JSON.parse(data) as JSONData) : undefined;\n }\n}\n\nexport class Index {\n constructor(\n public name: string,\n public columns: Column[],\n public isUnique: boolean,\n public isPartial: boolean,\n ) {}\n}\n\nexport class ForeignKey {\n constructor(\n public name: string,\n public table: Table,\n public referencedTable: Table,\n public columns: Column[],\n public isDeferred: boolean,\n public isDeferrable: boolean,\n public onDelete: Action,\n ) {}\n}\n\nexport class M2ORelation {\n readonly type = \"m2o\";\n\n constructor(public foreignKey: ForeignKey) {}\n\n get sourceTable(): Table {\n return this.foreignKey.table;\n }\n\n get targetTable(): Table {\n return this.foreignKey.referencedTable;\n }\n\n /** Uses the old short relation name so M2O metadata keeps its factory evaluation order. */\n get name(): string {\n const { foreignKey, sourceTable, targetTable } = this;\n // I.e. books.author_id and books.reviewer_id both target authors, so sort them as author and reviewer.\n const hasOtherIncomingFKs = sourceTable.foreignKeys.some(\n (fk) => fk !== foreignKey && fk.referencedTable === targetTable,\n );\n\n return pluralize.singular(hasOtherIncomingFKs ? foreignKey.columns[0].name.replace(/_id$/, \"\") : targetTable.name);\n }\n}\n\nexport class O2MRelation {\n readonly type = \"o2m\";\n\n constructor(public foreignKey: ForeignKey) {}\n\n get sourceTable(): Table {\n return this.foreignKey.referencedTable;\n }\n\n get targetTable(): Table {\n return this.foreignKey.table;\n }\n\n /** Keeps inverse relations in the same short-name order as pg-structure. */\n get name(): string {\n const { foreignKey, sourceTable, targetTable } = this;\n // I.e. books.author_id and books.reviewer_id are both incoming FKs to authors.\n const hasOtherIncomingFKs = targetTable.foreignKeys.some(\n (fk) => fk !== foreignKey && fk.referencedTable === sourceTable,\n );\n if (!hasOtherIncomingFKs) return pluralize.plural(targetTable.name);\n\n const columnName = foreignKey.columns[0].name.replace(/_id$/, \"\");\n const sourceName = pluralize.singular(sourceTable.name);\n const adjective = columnName.endsWith(`_${sourceName}`) ? columnName.slice(0, -sourceName.length - 1) : \"\";\n\n if (sourceTable === targetTable) return pluralize.plural(adjective || targetTable.name);\n return pluralize.plural(`${adjective || sourceTable.name}_${targetTable.name}`);\n }\n}\n\nexport class M2MRelation {\n readonly type = \"m2m\";\n\n constructor(\n public foreignKey: ForeignKey,\n public targetForeignKey: ForeignKey,\n ) {}\n\n get sourceTable(): Table {\n return this.foreignKey.referencedTable;\n }\n\n get targetTable(): Table {\n return this.targetForeignKey.referencedTable;\n }\n\n get joinTable(): Table {\n return this.foreignKey.table;\n }\n\n /** Adds the join-table qualifier when more than one join path reaches the same target. */\n get name(): string {\n const { sourceTable, joinTable, targetTable } = this;\n const joinTables = new Set(\n sourceTable.foreignKeysToThis\n .filter((fk) => fk.table.foreignKeys.some((other) => other !== fk && other.referencedTable === targetTable))\n .map((fk) => fk.table),\n );\n\n const targetName = pluralize.plural(targetTable.name);\n if (joinTables.size < 2) return targetName;\n\n const qualifier = joinTable.name\n .replace(sourceTable.name, \"\")\n .replace(targetTable.name, \"\")\n .replace(/^_+|_+$/g, \"\");\n return `${qualifier}_${targetName}`;\n }\n}\n\nexport class Table {\n readonly columns = new Items<Column>();\n readonly indexes = new Items<Index>();\n readonly foreignKeys: ForeignKey[] = [];\n readonly foreignKeysToThis: ForeignKey[] = [];\n readonly uniqueConstraints: { columns: Column[]; index: Index }[] = [];\n primaryKey?: { columns: Column[] };\n\n constructor(\n public name: string,\n public schema: { name: string },\n ) {}\n\n get m2oRelations(): M2ORelation[] {\n return this.foreignKeys.map((fk) => new M2ORelation(fk)).sort((a, b) => compareNames(a.name, b.name));\n }\n\n get o2mRelations(): O2MRelation[] {\n return this.foreignKeysToThis\n .map((fk) => new O2MRelation(fk))\n .sort((a, b) => compareNames(a.name.toUpperCase(), b.name.toUpperCase()));\n }\n\n get m2mRelations(): M2MRelation[] {\n return this.foreignKeysToThis\n .flatMap((fk) => fk.table.foreignKeys.filter((other) => other !== fk).map((other) => new M2MRelation(fk, other)))\n .sort((a, b) => compareNames(a.name.toUpperCase(), b.name.toUpperCase()));\n }\n}\n\nexport type Db = { tables: Items<Table>; types: EnumType[] };\n\n/**\n * Reads only the PostgreSQL catalog data used by codegen.\n *\n * Load tables and columns before constraints so each foreign key can link the same Table and Column\n * objects used by entity fields. For example, books.author_id and authors.id are joined through\n * the foreign key, while a trigger calling cyanaudit in another schema is irrelevant to codegen.\n */\nexport async function loadPgMetadata(client: Client): Promise<Db> {\n const tables = new Items<Table>();\n const byOid = new Map<number, Table>();\n const columns = new Map<number, Map<number, Column>>();\n\n const tableRows = await client.query<{ oid: number; name: string; schema: string }>(`\n SELECT c.oid, c.relname AS name, n.nspname AS schema\n FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE c.relkind IN ('r', 'p') AND n.nspname <> 'information_schema'\n AND n.nspname NOT LIKE 'pg_%' AND NOT pg_is_other_temp_schema(n.oid)\n ORDER BY n.nspname, c.relname\n `);\n for (const row of tableRows.rows) {\n const table = new Table(row.name, { name: row.schema });\n tables.push(table);\n byOid.set(row.oid, table);\n columns.set(row.oid, new Map());\n }\n\n const enumRows = await client.query<{ oid: number; name: string; value: string }>(`\n SELECT t.oid, t.typname AS name, e.enumlabel AS value\n FROM pg_type t JOIN pg_enum e ON e.enumtypid = t.oid\n JOIN pg_namespace n ON n.oid = t.typnamespace\n WHERE n.nspname <> 'information_schema' AND n.nspname NOT LIKE 'pg_%'\n ORDER BY t.oid, e.enumsortorder\n `);\n const enums = new Map<number, EnumType>();\n for (const row of enumRows.rows) {\n if (!enums.has(row.oid)) enums.set(row.oid, new EnumType(row.name, []));\n enums.get(row.oid)!.values.push(row.value);\n }\n\n const columnRows = await client.query<{\n tableOid: number;\n position: number;\n name: string;\n typeOid: number;\n typeName: string;\n notNull: boolean;\n defaultValue: string | null;\n generated: string;\n arrayDimension: number;\n comment: string | null;\n }>(\n `\n SELECT a.attrelid AS \"tableOid\", a.attnum AS position, a.attname AS name,\n COALESCE(NULLIF(t.typelem, 0), t.oid) AS \"typeOid\",\n base.typname AS \"typeName\", a.attnotnull AS \"notNull\",\n pg_get_expr(d.adbin, d.adrelid) AS \"defaultValue\", a.attgenerated AS generated,\n a.attndims AS \"arrayDimension\", col_description(a.attrelid, a.attnum) AS comment\n FROM pg_attribute a JOIN pg_type t ON t.oid = a.atttypid\n JOIN pg_type base ON base.oid = COALESCE(NULLIF(t.typelem, 0), t.oid)\n LEFT JOIN pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum\n WHERE a.attrelid = ANY($1::oid[]) AND a.attnum > 0 AND NOT a.attisdropped\n ORDER BY a.attrelid, a.attnum\n `,\n [Array.from(byOid.keys())],\n );\n for (const row of columnRows.rows) {\n const type = enums.get(row.typeOid) ?? sqlType(row.typeName);\n const column = new Column(\n row.name,\n type,\n row.notNull,\n row.defaultValue,\n row.generated !== \"\",\n row.arrayDimension,\n row.comment,\n );\n byOid.get(row.tableOid)!.columns.push(column);\n columns.get(row.tableOid)!.set(row.position, column);\n }\n\n const indexRows = await client.query<{\n tableOid: number;\n indexOid: number;\n name: string;\n positions: number[];\n unique: boolean;\n partial: boolean;\n }>(\n `\n SELECT i.indrelid AS \"tableOid\", i.indexrelid AS \"indexOid\", c.relname AS name,\n i.indkey::int2[] AS positions, i.indisunique AS unique, i.indpred IS NOT NULL AS partial\n FROM pg_index i JOIN pg_class c ON c.oid = i.indexrelid\n WHERE i.indrelid = ANY($1::oid[])\n `,\n [Array.from(byOid.keys())],\n );\n const indexes = new Map<number, Index>();\n for (const row of indexRows.rows) {\n const table = byOid.get(row.tableOid)!;\n const index = new Index(\n row.name,\n row.positions.flatMap((pos) => columns.get(row.tableOid)!.get(pos) ?? []),\n row.unique,\n row.partial,\n );\n table.indexes.push(index);\n indexes.set(row.indexOid, index);\n if (row.unique) for (const column of index.columns) column.uniqueIndexes.push(index);\n }\n\n const constraintRows = await client.query<{\n tableOid: number;\n targetOid: number;\n indexOid: number;\n name: string;\n kind: string;\n positions: number[] | null;\n deferred: boolean;\n deferrable: boolean;\n onDelete: string;\n }>(\n `\n SELECT conrelid AS \"tableOid\", confrelid AS \"targetOid\", conindid AS \"indexOid\",\n conname AS name, contype AS kind, conkey AS positions,\n condeferred AS deferred, condeferrable AS deferrable, confdeltype AS \"onDelete\"\n FROM pg_constraint WHERE conrelid = ANY($1::oid[]) AND contype IN ('p', 'u', 'f')\n ORDER BY conrelid, conname\n `,\n [Array.from(byOid.keys())],\n );\n for (const row of constraintRows.rows) {\n const table = byOid.get(row.tableOid)!;\n const keys = (row.positions ?? []).map((pos) => columns.get(row.tableOid)!.get(pos)!);\n if (row.kind === \"p\") {\n table.primaryKey = { columns: keys };\n for (const column of keys) column.isPrimaryKey = true;\n } else if (row.kind === \"u\") {\n table.uniqueConstraints.push({ columns: keys, index: indexes.get(row.indexOid)! });\n } else {\n const target = byOid.get(row.targetOid);\n if (!target) continue;\n const fk = new ForeignKey(\n row.name,\n table,\n target,\n keys,\n row.deferred,\n row.deferrable,\n deleteAction(row.onDelete),\n );\n table.foreignKeys.push(fk);\n target.foreignKeysToThis.push(fk);\n for (const column of keys) column.foreignKeys.push(fk);\n }\n }\n\n return { tables, types: Array.from(enums.values()) };\n}\n\n/** Matches the type names codegen expects from PostgreSQL's builtin aliases. */\nfunction sqlType(name: string): { name: string; shortName?: string } {\n switch (name) {\n case \"int2\":\n return { name: \"smallint\" };\n case \"int4\":\n return { name: \"integer\", shortName: \"int\" };\n case \"int8\":\n return { name: \"bigint\" };\n case \"bool\":\n return { name: \"boolean\" };\n case \"float4\":\n return { name: \"real\" };\n case \"float8\":\n return { name: \"double precision\" };\n case \"varchar\":\n return { name: \"character varying\" };\n case \"timestamptz\":\n return { name: \"timestamp with time zone\" };\n case \"timestamp\":\n return { name: \"timestamp without time zone\" };\n case \"time\":\n return { name: \"time without time zone\" };\n default:\n // Foreign-key serdes need shortName even for types with no PostgreSQL alias, such as uuid.\n return { name, shortName: name };\n }\n}\n\n/** Decodes the pg_constraint foreign-key delete action. */\nfunction deleteAction(code: string): Action {\n switch (code) {\n case \"a\":\n return \"NO ACTION\";\n case \"r\":\n return \"RESTRICT\";\n case \"c\":\n return \"CASCADE\";\n case \"n\":\n return \"SET NULL\";\n case \"d\":\n return \"SET DEFAULT\";\n default:\n throw new Error(`Unknown PostgreSQL delete action: ${code}`);\n }\n}\n\n/** Compares PostgreSQL relation names by code point, independent of the host locale. */\nfunction compareNames(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"],"mappings":"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"}
package/build/utils.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
3
+ require("./pgMetadata.cjs");
3
4
  const require_config = require("./config.cjs");
4
5
  require("./EntityDbMetadata.cjs");
5
6
  let change_case = require("change-case");
6
7
  let joist_utils = require("joist-utils");
7
- require("pg-structure");
8
8
  let pluralize = require("pluralize");
9
9
  pluralize = require_runtime.__toESM(pluralize, 1);
10
10
  let ts_poet = require("ts-poet");
@@ -145,6 +145,7 @@ function uncapitalize(s) {
145
145
  function isIgnored(config, t) {
146
146
  return (config.ignoredTables || ["migrations", "pgmigrations"]).includes(t.name) || !shouldIncludeSchema(config, t);
147
147
  }
148
+ /** Uses the same schema selection for metadata and test database cleanup. */
148
149
  function shouldIncludeSchema(config, t) {
149
150
  return config.schemas ? config.schemas.includes(t.schema.name) : t.schema.name === "public";
150
151
  }
@@ -164,6 +165,7 @@ exports.plainDateCode = plainDateCode;
164
165
  exports.plainDateTimeCode = plainDateTimeCode;
165
166
  exports.plainTimeCode = plainTimeCode;
166
167
  exports.q = q;
168
+ exports.shouldIncludeSchema = shouldIncludeSchema;
167
169
  exports.sortKeys = sortKeys;
168
170
  exports.tableToEntityName = tableToEntityName;
169
171
  exports.trueIfResolved = trueIfResolved;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","names":["getTimestampConfig","pascalCase","imp","isPlainObject"],"sources":["../src/utils.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { isPlainObject } from \"joist-utils\";\nimport { type Table } from \"pg-structure\";\nimport pluralize from \"pluralize\";\nimport { code, imp } from \"ts-poet\";\n\nimport { type Config, getTimestampConfig } from \"./config.ts\";\nimport { type DatabaseColumnType, type PrimitiveTypescriptType } from \"./EntityDbMetadata.ts\";\n\nexport function assertNever(x: never): never {\n throw new Error(\"Unexpected object: \" + x);\n}\n\nexport function isEntityTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t) || isJoinTable(config, t)) {\n return false;\n }\n\n // Depending on the config settings, we may/may not require the presence of timestamp\n // fields to determine whether a table is an entity.\n const columnNames = t.columns.map((c) => c.name);\n const { updatedAtConf, createdAtConf } = getTimestampConfig(config);\n const hasCreatedAt = createdAtConf.names.some((c) => columnNames.includes(c));\n const hasUpdatedAt = updatedAtConf.names.some((c) => columnNames.includes(c));\n\n // Entities always require an id column\n const hasIdColumn = columnNames.includes(\"id\");\n if (!hasIdColumn) return false;\n\n // If we have no timestamp columns, but do have `name` and `code`, we're probably an enum.\n if (hasIdColumn && !hasCreatedAt && !hasUpdatedAt) {\n if ([\"name\", \"code\"].every((c) => columnNames.includes(c))) {\n return false;\n }\n }\n\n // If the ID column is a FK then we're a subclass\n if (isSubClassTable(t)) return true;\n\n // Otherwise respect the created_at/updated_at required rules\n return hasIdColumn && (hasCreatedAt || !createdAtConf.required) && (hasUpdatedAt || !updatedAtConf.required);\n}\n\nexport function isSubClassTable(t: Table): boolean {\n // Note that `.get(\"id\")` will blow up if there is no id column\n return t.columns.get(\"id\").foreignKeys.length === 1;\n}\n\nexport function isEnumTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n const columnNames = t.columns.map((c) => c.name);\n return [\"id\", \"code\", \"name\"].every((c) => columnNames.includes(c)) && !isEntityTable(config, t);\n}\n\n// Oddly enough isJoinTable showed up as a hot spot, so cache its calculation.\nconst joinTables: Map<string, boolean> = new Map();\n\nexport function isJoinTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n return getOrElse(joinTables, t.name, () => {\n const { columns } = t;\n const fks = columns.filter((c) => c.isForeignKey);\n const pks = columns.filter((c) => c.isPrimaryKey);\n // Every join table is two FKs, plus either a surrogate `id` PK (\"id-ful\") or no surrogate\n // at all, in which case the two FKs are themselves the composite PK (\"id-less\").\n if (fks.length !== 2) return false;\n if (!joinTableHasId(t) && !(pks.length === 2 && pks.every((c) => c.isForeignKey))) return false;\n // Any column beyond the two FKs and the optional `id` PK must be a single created_at.\n const others = columns.length - fks.length - (joinTableHasId(t) ? 1 : 0);\n const createdAts = columns.filter((c) => c.name === \"created_at\" || c.name === \"createdAt\").length;\n return others === 0 || (others === 1 && createdAts === 1);\n });\n}\n\n/** Whether a join table has a surrogate `id` primary key (vs. an id-less FK-pair composite PK). */\nexport function joinTableHasId(t: Table): boolean {\n const pks = t.columns.filter((c) => c.isPrimaryKey);\n return pks.length === 1 && !pks[0].isForeignKey;\n}\n\nfunction getOrElse<K, V>(map: Map<K, V>, key: K, fn: () => V): V {\n let value = map.get(key);\n if (value === undefined) {\n value = fn();\n map.set(key, value);\n }\n return value;\n}\n\n/** Converts `projects` to `Project`. */\nexport function tableToEntityName(config: Config, table: Table): string {\n let entityName = config.__tableToEntityName?.[table.name];\n if (!entityName) {\n const configEntityName = Object.entries(config.entities)\n .filter(([, conf]) => conf.tableName === table.name)\n .map(([entityName]) => entityName)[0];\n entityName = configEntityName || pascalCase(pluralize.singular(table.name));\n (config.__tableToEntityName ??= {})[table.name] = entityName;\n }\n return entityName;\n}\n\nexport const dateCode = code`Date`;\nexport const plainDateCode = code`${imp(\"Temporal@joist-orm\")}.PlainDate`;\nexport const plainTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainTime`;\nexport const plainDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainDateTime`;\nexport const zonedDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.ZonedDateTime`;\n\n/** Maps db types, i.e. `int`, to JS types, i.e. `number`. */\nexport function mapSimpleDbTypeToTypescriptType(config: Config, dbType: DatabaseColumnType): PrimitiveTypescriptType {\n switch (dbType) {\n case \"boolean\":\n return \"boolean\";\n case \"int\":\n case \"numeric\":\n case \"smallint\":\n case \"integer\":\n case \"decimal\":\n case \"real\":\n case \"smallserial\":\n case \"serial\":\n case \"double precision\":\n return \"number\";\n case \"bigserial\":\n case \"bigint\":\n return \"bigint\";\n case \"text\":\n case \"citext\":\n case \"character varying\":\n case \"varchar\":\n case \"uuid\":\n case \"tsvector\":\n case \"tstzrange\":\n return \"string\";\n case \"time without time zone\":\n return config.temporal\n ? plainTimeCode\n : fail(\"Joist doesn't support 'time without time zone' unless temporal is enabled\");\n case \"timestamp with time zone\":\n return config.temporal ? zonedDateTimeCode : dateCode;\n case \"timestamp without time zone\":\n return config.temporal ? plainDateTimeCode : dateCode;\n case \"date\":\n return config.temporal ? plainDateCode : dateCode;\n case \"jsonb\":\n return \"Object\";\n case \"bytea\":\n return \"Uint8Array\";\n default:\n assertNever(dbType);\n }\n}\n\nexport function merge<T>(a: T[], b: T[]): T[] {\n return [...a, ...b];\n}\n\n/** Returns true if `p` is resolved, otherwise false if it is rejected. */\nexport async function trueIfResolved(p: Promise<unknown>): Promise<boolean> {\n return p.then(\n () => true,\n () => false,\n );\n}\n\nexport function fail(message?: string): never {\n throw new Error(message || \"Failed\");\n}\n\nexport function sortKeys<T extends object>(o: T): T {\n return Object.keys(o)\n .sort()\n .reduce(\n (acc, key) => {\n const value = o[key as keyof T];\n const newValue = typeof value === \"object\" && isPlainObject(value) ? sortKeys(value as any as object) : value;\n acc[key as keyof T] = newValue as any;\n return acc;\n },\n {} as any as T,\n );\n}\n\nexport function parseOrder(order: string | undefined): { field: string; direction: \"ASC\" | \"DESC\" } | undefined {\n if (!order) {\n return undefined;\n }\n const [field, direction = \"ASC\"] = order.split(\" \");\n if (direction !== \"ASC\" && direction !== \"DESC\") {\n throw new Error(`Invalid direction: ${direction} (must be \"ASC\" or \"DESC\")`);\n }\n return { field, direction };\n}\n\nexport function q(s: string | undefined): string {\n return s === undefined ? \"undefined\" : `\"${s}\"`;\n}\n\nexport function uncapitalize(s: string): string {\n return s.charAt(0).toLowerCase() + s.slice(1);\n}\n\nfunction isIgnored(config: Config, t: Table): boolean {\n return (config.ignoredTables || [\"migrations\", \"pgmigrations\"]).includes(t.name) || !shouldIncludeSchema(config, t);\n}\n\nfunction shouldIncludeSchema(config: Config, t: Table): boolean {\n return config.schemas ? config.schemas.includes(t.schema.name) : t.schema.name === \"public\";\n}\n"],"mappings":";;;;;;;;;;;AASA,SAAgB,YAAY,GAAiB;CAC3C,MAAM,IAAI,MAAM,wBAAwB,CAAC;AAC3C;AAEA,SAAgB,cAAc,QAAgB,GAAmB;CAC/D,IAAI,UAAU,QAAQ,CAAC,KAAK,YAAY,QAAQ,CAAC,GAC/C,OAAO;CAKT,MAAM,cAAc,EAAE,QAAQ,KAAK,MAAM,EAAE,IAAI;CAC/C,MAAM,EAAE,eAAe,kBAAkBA,eAAAA,mBAAmB,MAAM;CAClE,MAAM,eAAe,cAAc,MAAM,MAAM,MAAM,YAAY,SAAS,CAAC,CAAC;CAC5E,MAAM,eAAe,cAAc,MAAM,MAAM,MAAM,YAAY,SAAS,CAAC,CAAC;CAG5E,MAAM,cAAc,YAAY,SAAS,IAAI;CAC7C,IAAI,CAAC,aAAa,OAAO;CAGzB,IAAI,eAAe,CAAC,gBAAgB,CAAC,cAC/B;MAAA,CAAC,QAAQ,MAAM,CAAC,CAAC,OAAO,MAAM,YAAY,SAAS,CAAC,CAAC,GACvD,OAAO;CAAA;CAKX,IAAI,gBAAgB,CAAC,GAAG,OAAO;CAG/B,OAAO,gBAAgB,gBAAgB,CAAC,cAAc,cAAc,gBAAgB,CAAC,cAAc;AACrG;AAEA,SAAgB,gBAAgB,GAAmB;CAEjD,OAAO,EAAE,QAAQ,IAAI,IAAI,CAAC,CAAC,YAAY,WAAW;AACpD;AAEA,SAAgB,YAAY,QAAgB,GAAmB;CAC7D,IAAI,UAAU,QAAQ,CAAC,GACrB,OAAO;CAET,MAAM,cAAc,EAAE,QAAQ,KAAK,MAAM,EAAE,IAAI;CAC/C,OAAO;EAAC;EAAM;EAAQ;CAAM,CAAC,CAAC,OAAO,MAAM,YAAY,SAAS,CAAC,CAAC,KAAK,CAAC,cAAc,QAAQ,CAAC;AACjG;AAGA,MAAM,6BAAmC,IAAI,IAAI;AAEjD,SAAgB,YAAY,QAAgB,GAAmB;CAC7D,IAAI,UAAU,QAAQ,CAAC,GACrB,OAAO;CAET,OAAO,UAAU,YAAY,EAAE,YAAY;EACzC,MAAM,EAAE,YAAY;EACpB,MAAM,MAAM,QAAQ,QAAQ,MAAM,EAAE,YAAY;EAChD,MAAM,MAAM,QAAQ,QAAQ,MAAM,EAAE,YAAY;EAGhD,IAAI,IAAI,WAAW,GAAG,OAAO;EAC7B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,WAAW,KAAK,IAAI,OAAO,MAAM,EAAE,YAAY,IAAI,OAAO;EAE1F,MAAM,SAAS,QAAQ,SAAS,IAAI,UAAU,eAAe,CAAC,IAAI,IAAI;EACtE,MAAM,aAAa,QAAQ,QAAQ,MAAM,EAAE,SAAS,gBAAgB,EAAE,SAAS,WAAW,CAAC,CAAC;EAC5F,OAAO,WAAW,KAAM,WAAW,KAAK,eAAe;CACzD,CAAC;AACH;;AAGA,SAAgB,eAAe,GAAmB;CAChD,MAAM,MAAM,EAAE,QAAQ,QAAQ,MAAM,EAAE,YAAY;CAClD,OAAO,IAAI,WAAW,KAAK,CAAC,IAAI,EAAE,CAAC;AACrC;AAEA,SAAS,UAAgB,KAAgB,KAAQ,IAAgB;CAC/D,IAAI,QAAQ,IAAI,IAAI,GAAG;CACvB,IAAI,UAAU,KAAA,GAAW;EACvB,QAAQ,GAAG;EACX,IAAI,IAAI,KAAK,KAAK;CACpB;CACA,OAAO;AACT;;AAGA,SAAgB,kBAAkB,QAAgB,OAAsB;CACtE,IAAI,aAAa,OAAO,sBAAsB,MAAM;CACpD,IAAI,CAAC,YAAY;EAIf,aAHyB,OAAO,QAAQ,OAAO,QAAQ,CAAC,CACrD,QAAQ,GAAG,UAAU,KAAK,cAAc,MAAM,IAAI,CAAC,CACnD,KAAK,CAAC,gBAAgB,UAAU,CAAC,CAAC,OAAA,GACJC,YAAAA,WAAAA,CAAW,UAAA,QAAU,SAAS,MAAM,IAAI,CAAC;EAC1E,CAAC,OAAO,wBAAwB,CAAC,EAAA,CAAG,MAAM,QAAQ;CACpD;CACA,OAAO;AACT;AAEA,MAAa,WAAW,QAAA,IAAI;AAC5B,MAAa,gBAAgB,QAAA,IAAI,IAAA,GAAGC,QAAAA,IAAAA,CAAI,oBAAoB,EAAE;AAC9D,MAAa,gBAAgB,QAAA,IAAI,IAAA,GAAGA,QAAAA,IAAAA,CAAI,oBAAoB,EAAE;AAC9D,MAAa,oBAAoB,QAAA,IAAI,IAAA,GAAGA,QAAAA,IAAAA,CAAI,oBAAoB,EAAE;AAClE,MAAa,oBAAoB,QAAA,IAAI,IAAA,GAAGA,QAAAA,IAAAA,CAAI,oBAAoB,EAAE;;AAGlE,SAAgB,gCAAgC,QAAgB,QAAqD;CACnH,QAAQ,QAAR;EACE,KAAK,WACH,OAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,oBACH,OAAO;EACT,KAAK;EACL,KAAK,UACH,OAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,aACH,OAAO;EACT,KAAK,0BACH,OAAO,OAAO,WACV,gBACA,KAAK,2EAA2E;EACtF,KAAK,4BACH,OAAO,OAAO,WAAW,oBAAoB;EAC/C,KAAK,+BACH,OAAO,OAAO,WAAW,oBAAoB;EAC/C,KAAK,QACH,OAAO,OAAO,WAAW,gBAAgB;EAC3C,KAAK,SACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,SACE,YAAY,MAAM;CACtB;AACF;AAEA,SAAgB,MAAS,GAAQ,GAAa;CAC5C,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;AACpB;;AAGA,eAAsB,eAAe,GAAuC;CAC1E,OAAO,EAAE,WACD,YACA,KACR;AACF;AAEA,SAAgB,KAAK,SAAyB;CAC5C,MAAM,IAAI,MAAM,WAAW,QAAQ;AACrC;AAEA,SAAgB,SAA2B,GAAS;CAClD,OAAO,OAAO,KAAK,CAAC,CAAC,CAClB,KAAK,CAAC,CACN,QACE,KAAK,QAAQ;EACZ,MAAM,QAAQ,EAAE;EAEhB,IAAI,OADa,OAAO,UAAU,aAAA,GAAYC,YAAAA,cAAAA,CAAc,KAAK,IAAI,SAAS,KAAsB,IAAI;EAExG,OAAO;CACT,GACA,CAAC,CACH;AACJ;AAEA,SAAgB,WAAW,OAAqF;CAC9G,IAAI,CAAC,OACH;CAEF,MAAM,CAAC,OAAO,YAAY,SAAS,MAAM,MAAM,GAAG;CAClD,IAAI,cAAc,SAAS,cAAc,QACvC,MAAM,IAAI,MAAM,sBAAsB,UAAU,2BAA2B;CAE7E,OAAO;EAAE;EAAO;CAAU;AAC5B;AAEA,SAAgB,EAAE,GAA+B;CAC/C,OAAO,MAAM,KAAA,IAAY,cAAc,IAAI,EAAE;AAC/C;AAEA,SAAgB,aAAa,GAAmB;CAC9C,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAE,MAAM,CAAC;AAC9C;AAEA,SAAS,UAAU,QAAgB,GAAmB;CACpD,QAAQ,OAAO,iBAAiB,CAAC,cAAc,cAAc,EAAA,CAAG,SAAS,EAAE,IAAI,KAAK,CAAC,oBAAoB,QAAQ,CAAC;AACpH;AAEA,SAAS,oBAAoB,QAAgB,GAAmB;CAC9D,OAAO,OAAO,UAAU,OAAO,QAAQ,SAAS,EAAE,OAAO,IAAI,IAAI,EAAE,OAAO,SAAS;AACrF"}
1
+ {"version":3,"file":"utils.cjs","names":["getTimestampConfig","pascalCase","imp","isPlainObject"],"sources":["../src/utils.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { isPlainObject } from \"joist-utils\";\nimport pluralize from \"pluralize\";\nimport { code, imp } from \"ts-poet\";\n\nimport { type Config, getTimestampConfig } from \"./config.ts\";\nimport { type DatabaseColumnType, type PrimitiveTypescriptType } from \"./EntityDbMetadata.ts\";\nimport { type Table } from \"./pgMetadata.ts\";\n\nexport function assertNever(x: never): never {\n throw new Error(\"Unexpected object: \" + x);\n}\n\nexport function isEntityTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t) || isJoinTable(config, t)) {\n return false;\n }\n\n // Depending on the config settings, we may/may not require the presence of timestamp\n // fields to determine whether a table is an entity.\n const columnNames = t.columns.map((c) => c.name);\n const { updatedAtConf, createdAtConf } = getTimestampConfig(config);\n const hasCreatedAt = createdAtConf.names.some((c) => columnNames.includes(c));\n const hasUpdatedAt = updatedAtConf.names.some((c) => columnNames.includes(c));\n\n // Entities always require an id column\n const hasIdColumn = columnNames.includes(\"id\");\n if (!hasIdColumn) return false;\n\n // If we have no timestamp columns, but do have `name` and `code`, we're probably an enum.\n if (hasIdColumn && !hasCreatedAt && !hasUpdatedAt) {\n if ([\"name\", \"code\"].every((c) => columnNames.includes(c))) {\n return false;\n }\n }\n\n // If the ID column is a FK then we're a subclass\n if (isSubClassTable(t)) return true;\n\n // Otherwise respect the created_at/updated_at required rules\n return hasIdColumn && (hasCreatedAt || !createdAtConf.required) && (hasUpdatedAt || !updatedAtConf.required);\n}\n\nexport function isSubClassTable(t: Table): boolean {\n // Note that `.get(\"id\")` will blow up if there is no id column\n return t.columns.get(\"id\").foreignKeys.length === 1;\n}\n\nexport function isEnumTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n const columnNames = t.columns.map((c) => c.name);\n return [\"id\", \"code\", \"name\"].every((c) => columnNames.includes(c)) && !isEntityTable(config, t);\n}\n\n// Oddly enough isJoinTable showed up as a hot spot, so cache its calculation.\nconst joinTables: Map<string, boolean> = new Map();\n\nexport function isJoinTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n return getOrElse(joinTables, t.name, () => {\n const { columns } = t;\n const fks = columns.filter((c) => c.isForeignKey);\n const pks = columns.filter((c) => c.isPrimaryKey);\n // Every join table is two FKs, plus either a surrogate `id` PK (\"id-ful\") or no surrogate\n // at all, in which case the two FKs are themselves the composite PK (\"id-less\").\n if (fks.length !== 2) return false;\n if (!joinTableHasId(t) && !(pks.length === 2 && pks.every((c) => c.isForeignKey))) return false;\n // Any column beyond the two FKs and the optional `id` PK must be a single created_at.\n const others = columns.length - fks.length - (joinTableHasId(t) ? 1 : 0);\n const createdAts = columns.filter((c) => c.name === \"created_at\" || c.name === \"createdAt\").length;\n return others === 0 || (others === 1 && createdAts === 1);\n });\n}\n\n/** Whether a join table has a surrogate `id` primary key (vs. an id-less FK-pair composite PK). */\nexport function joinTableHasId(t: Table): boolean {\n const pks = t.columns.filter((c) => c.isPrimaryKey);\n return pks.length === 1 && !pks[0].isForeignKey;\n}\n\nfunction getOrElse<K, V>(map: Map<K, V>, key: K, fn: () => V): V {\n let value = map.get(key);\n if (value === undefined) {\n value = fn();\n map.set(key, value);\n }\n return value;\n}\n\n/** Converts `projects` to `Project`. */\nexport function tableToEntityName(config: Config, table: Table): string {\n let entityName = config.__tableToEntityName?.[table.name];\n if (!entityName) {\n const configEntityName = Object.entries(config.entities)\n .filter(([, conf]) => conf.tableName === table.name)\n .map(([entityName]) => entityName)[0];\n entityName = configEntityName || pascalCase(pluralize.singular(table.name));\n (config.__tableToEntityName ??= {})[table.name] = entityName;\n }\n return entityName;\n}\n\nexport const dateCode = code`Date`;\nexport const plainDateCode = code`${imp(\"Temporal@joist-orm\")}.PlainDate`;\nexport const plainTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainTime`;\nexport const plainDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainDateTime`;\nexport const zonedDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.ZonedDateTime`;\n\n/** Maps db types, i.e. `int`, to JS types, i.e. `number`. */\nexport function mapSimpleDbTypeToTypescriptType(config: Config, dbType: DatabaseColumnType): PrimitiveTypescriptType {\n switch (dbType) {\n case \"boolean\":\n return \"boolean\";\n case \"int\":\n case \"numeric\":\n case \"smallint\":\n case \"integer\":\n case \"decimal\":\n case \"real\":\n case \"smallserial\":\n case \"serial\":\n case \"double precision\":\n return \"number\";\n case \"bigserial\":\n case \"bigint\":\n return \"bigint\";\n case \"text\":\n case \"citext\":\n case \"character varying\":\n case \"varchar\":\n case \"uuid\":\n case \"tsvector\":\n case \"tstzrange\":\n return \"string\";\n case \"time without time zone\":\n return config.temporal\n ? plainTimeCode\n : fail(\"Joist doesn't support 'time without time zone' unless temporal is enabled\");\n case \"timestamp with time zone\":\n return config.temporal ? zonedDateTimeCode : dateCode;\n case \"timestamp without time zone\":\n return config.temporal ? plainDateTimeCode : dateCode;\n case \"date\":\n return config.temporal ? plainDateCode : dateCode;\n case \"jsonb\":\n return \"Object\";\n case \"bytea\":\n return \"Uint8Array\";\n default:\n assertNever(dbType);\n }\n}\n\nexport function merge<T>(a: T[], b: T[]): T[] {\n return [...a, ...b];\n}\n\n/** Returns true if `p` is resolved, otherwise false if it is rejected. */\nexport async function trueIfResolved(p: Promise<unknown>): Promise<boolean> {\n return p.then(\n () => true,\n () => false,\n );\n}\n\nexport function fail(message?: string): never {\n throw new Error(message || \"Failed\");\n}\n\nexport function sortKeys<T extends object>(o: T): T {\n return Object.keys(o)\n .sort()\n .reduce(\n (acc, key) => {\n const value = o[key as keyof T];\n const newValue = typeof value === \"object\" && isPlainObject(value) ? sortKeys(value as any as object) : value;\n acc[key as keyof T] = newValue as any;\n return acc;\n },\n {} as any as T,\n );\n}\n\nexport function parseOrder(order: string | undefined): { field: string; direction: \"ASC\" | \"DESC\" } | undefined {\n if (!order) {\n return undefined;\n }\n const [field, direction = \"ASC\"] = order.split(\" \");\n if (direction !== \"ASC\" && direction !== \"DESC\") {\n throw new Error(`Invalid direction: ${direction} (must be \"ASC\" or \"DESC\")`);\n }\n return { field, direction };\n}\n\nexport function q(s: string | undefined): string {\n return s === undefined ? \"undefined\" : `\"${s}\"`;\n}\n\nexport function uncapitalize(s: string): string {\n return s.charAt(0).toLowerCase() + s.slice(1);\n}\n\nfunction isIgnored(config: Config, t: Table): boolean {\n return (config.ignoredTables || [\"migrations\", \"pgmigrations\"]).includes(t.name) || !shouldIncludeSchema(config, t);\n}\n\n/** Uses the same schema selection for metadata and test database cleanup. */\nexport function shouldIncludeSchema(config: Config, t: Table): boolean {\n return config.schemas ? config.schemas.includes(t.schema.name) : t.schema.name === \"public\";\n}\n"],"mappings":"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"}
package/build/utils.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Config } from "./config.cjs";
2
+ import { Table } from "./pgMetadata.cjs";
2
3
  import { DatabaseColumnType, PrimitiveTypescriptType } from "./EntityDbMetadata.cjs";
3
- import { Table } from "pg-structure";
4
4
  //#region src/utils.d.ts
5
5
  declare function assertNever(x: never): never;
6
6
  declare function isEntityTable(config: Config, t: Table): boolean;
@@ -29,6 +29,8 @@ declare function parseOrder(order: string | undefined): {
29
29
  } | undefined;
30
30
  declare function q(s: string | undefined): string;
31
31
  declare function uncapitalize(s: string): string;
32
+ /** Uses the same schema selection for metadata and test database cleanup. */
33
+ declare function shouldIncludeSchema(config: Config, t: Table): boolean;
32
34
  //#endregion
33
- export { assertNever, dateCode, fail, isEntityTable, isEnumTable, isJoinTable, isSubClassTable, joinTableHasId, mapSimpleDbTypeToTypescriptType, merge, parseOrder, plainDateCode, plainDateTimeCode, plainTimeCode, q, sortKeys, tableToEntityName, trueIfResolved, uncapitalize, zonedDateTimeCode };
35
+ export { assertNever, dateCode, fail, isEntityTable, isEnumTable, isJoinTable, isSubClassTable, joinTableHasId, mapSimpleDbTypeToTypescriptType, merge, parseOrder, plainDateCode, plainDateTimeCode, plainTimeCode, q, shouldIncludeSchema, sortKeys, tableToEntityName, trueIfResolved, uncapitalize, zonedDateTimeCode };
34
36
  //# sourceMappingURL=utils.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.cts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;iBASgB,YAAY;iBAIZ,cAAc,QAAQ,QAAQ,GAAG;iBA8BjC,gBAAgB,GAAG;iBAKnB,YAAY,QAAQ,QAAQ,GAAG;iBAW/B,YAAY,QAAQ,QAAQ,GAAG;;iBAoB/B,eAAe,GAAG;;iBAelB,kBAAkB,QAAQ,QAAQ,OAAO;cAY5C,4BAAQ;cACR,iCAAa;cACb,iCAAa;cACb,qCAAiB;cACjB,qCAAiB;;iBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;iBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;iBAKpB,eAAe,GAAG,mBAAmB;iBAO3C,KAAK;iBAIL,SAAS,kBAAkB,GAAG,IAAI;iBAclC,WAAW;EAA8B;EAAe;;iBAWxD,EAAE;iBAIF,aAAa"}
1
+ {"version":3,"file":"utils.d.cts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;iBASgB,YAAY;iBAIZ,cAAc,QAAQ,QAAQ,GAAG;iBA8BjC,gBAAgB,GAAG;iBAKnB,YAAY,QAAQ,QAAQ,GAAG;iBAW/B,YAAY,QAAQ,QAAQ,GAAG;;iBAoB/B,eAAe,GAAG;;iBAelB,kBAAkB,QAAQ,QAAQ,OAAO;cAY5C,4BAAQ;cACR,iCAAa;cACb,iCAAa;cACb,qCAAiB;cACjB,qCAAiB;;iBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;iBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;iBAKpB,eAAe,GAAG,mBAAmB;iBAO3C,KAAK;iBAIL,SAAS,kBAAkB,GAAG,IAAI;iBAclC,WAAW;EAA8B;EAAe;;iBAWxD,EAAE;iBAIF,aAAa;;iBASb,oBAAoB,QAAQ,QAAQ,GAAG"}
package/build/utils.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Config } from "./config.mjs";
2
+ import { Table } from "./pgMetadata.mjs";
2
3
  import { DatabaseColumnType, PrimitiveTypescriptType } from "./EntityDbMetadata.mjs";
3
- import { Table } from "pg-structure";
4
4
  //#region src/utils.d.ts
5
5
  declare function assertNever(x: never): never;
6
6
  declare function isEntityTable(config: Config, t: Table): boolean;
@@ -29,6 +29,8 @@ declare function parseOrder(order: string | undefined): {
29
29
  } | undefined;
30
30
  declare function q(s: string | undefined): string;
31
31
  declare function uncapitalize(s: string): string;
32
+ /** Uses the same schema selection for metadata and test database cleanup. */
33
+ declare function shouldIncludeSchema(config: Config, t: Table): boolean;
32
34
  //#endregion
33
- export { assertNever, dateCode, fail, isEntityTable, isEnumTable, isJoinTable, isSubClassTable, joinTableHasId, mapSimpleDbTypeToTypescriptType, merge, parseOrder, plainDateCode, plainDateTimeCode, plainTimeCode, q, sortKeys, tableToEntityName, trueIfResolved, uncapitalize, zonedDateTimeCode };
35
+ export { assertNever, dateCode, fail, isEntityTable, isEnumTable, isJoinTable, isSubClassTable, joinTableHasId, mapSimpleDbTypeToTypescriptType, merge, parseOrder, plainDateCode, plainDateTimeCode, plainTimeCode, q, shouldIncludeSchema, sortKeys, tableToEntityName, trueIfResolved, uncapitalize, zonedDateTimeCode };
34
36
  //# sourceMappingURL=utils.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;iBASgB,YAAY;iBAIZ,cAAc,QAAQ,QAAQ,GAAG;iBA8BjC,gBAAgB,GAAG;iBAKnB,YAAY,QAAQ,QAAQ,GAAG;iBAW/B,YAAY,QAAQ,QAAQ,GAAG;;iBAoB/B,eAAe,GAAG;;iBAelB,kBAAkB,QAAQ,QAAQ,OAAO;cAY5C,4BAAQ;cACR,iCAAa;cACb,iCAAa;cACb,qCAAiB;cACjB,qCAAiB;;iBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;iBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;iBAKpB,eAAe,GAAG,mBAAmB;iBAO3C,KAAK;iBAIL,SAAS,kBAAkB,GAAG,IAAI;iBAclC,WAAW;EAA8B;EAAe;;iBAWxD,EAAE;iBAIF,aAAa"}
1
+ {"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;iBASgB,YAAY;iBAIZ,cAAc,QAAQ,QAAQ,GAAG;iBA8BjC,gBAAgB,GAAG;iBAKnB,YAAY,QAAQ,QAAQ,GAAG;iBAW/B,YAAY,QAAQ,QAAQ,GAAG;;iBAoB/B,eAAe,GAAG;;iBAelB,kBAAkB,QAAQ,QAAQ,OAAO;cAY5C,4BAAQ;cACR,iCAAa;cACb,iCAAa;cACb,qCAAiB;cACjB,qCAAiB;;iBAGd,gCAAgC,QAAQ,QAAQ,QAAQ,qBAAqB;iBA4C7E,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM;;iBAKpB,eAAe,GAAG,mBAAmB;iBAO3C,KAAK;iBAIL,SAAS,kBAAkB,GAAG,IAAI;iBAclC,WAAW;EAA8B;EAAe;;iBAWxD,EAAE;iBAIF,aAAa;;iBASb,oBAAoB,QAAQ,QAAQ,GAAG"}
package/build/utils.js CHANGED
@@ -1,8 +1,8 @@
1
+ import "./pgMetadata.js";
1
2
  import { getTimestampConfig } from "./config.js";
2
3
  import "./EntityDbMetadata.js";
3
4
  import { pascalCase } from "change-case";
4
5
  import { isPlainObject } from "joist-utils";
5
- import "pg-structure";
6
6
  import pluralize from "pluralize";
7
7
  import { code, imp } from "ts-poet";
8
8
  //#region src/utils.ts
@@ -142,10 +142,11 @@ function uncapitalize(s) {
142
142
  function isIgnored(config, t) {
143
143
  return (config.ignoredTables || ["migrations", "pgmigrations"]).includes(t.name) || !shouldIncludeSchema(config, t);
144
144
  }
145
+ /** Uses the same schema selection for metadata and test database cleanup. */
145
146
  function shouldIncludeSchema(config, t) {
146
147
  return config.schemas ? config.schemas.includes(t.schema.name) : t.schema.name === "public";
147
148
  }
148
149
  //#endregion
149
- export { assertNever, dateCode, fail, isEntityTable, isEnumTable, isJoinTable, isSubClassTable, joinTableHasId, mapSimpleDbTypeToTypescriptType, merge, parseOrder, plainDateCode, plainDateTimeCode, plainTimeCode, q, sortKeys, tableToEntityName, trueIfResolved, uncapitalize, zonedDateTimeCode };
150
+ export { assertNever, dateCode, fail, isEntityTable, isEnumTable, isJoinTable, isSubClassTable, joinTableHasId, mapSimpleDbTypeToTypescriptType, merge, parseOrder, plainDateCode, plainDateTimeCode, plainTimeCode, q, shouldIncludeSchema, sortKeys, tableToEntityName, trueIfResolved, uncapitalize, zonedDateTimeCode };
150
151
 
151
152
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { isPlainObject } from \"joist-utils\";\nimport { type Table } from \"pg-structure\";\nimport pluralize from \"pluralize\";\nimport { code, imp } from \"ts-poet\";\n\nimport { type Config, getTimestampConfig } from \"./config.ts\";\nimport { type DatabaseColumnType, type PrimitiveTypescriptType } from \"./EntityDbMetadata.ts\";\n\nexport function assertNever(x: never): never {\n throw new Error(\"Unexpected object: \" + x);\n}\n\nexport function isEntityTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t) || isJoinTable(config, t)) {\n return false;\n }\n\n // Depending on the config settings, we may/may not require the presence of timestamp\n // fields to determine whether a table is an entity.\n const columnNames = t.columns.map((c) => c.name);\n const { updatedAtConf, createdAtConf } = getTimestampConfig(config);\n const hasCreatedAt = createdAtConf.names.some((c) => columnNames.includes(c));\n const hasUpdatedAt = updatedAtConf.names.some((c) => columnNames.includes(c));\n\n // Entities always require an id column\n const hasIdColumn = columnNames.includes(\"id\");\n if (!hasIdColumn) return false;\n\n // If we have no timestamp columns, but do have `name` and `code`, we're probably an enum.\n if (hasIdColumn && !hasCreatedAt && !hasUpdatedAt) {\n if ([\"name\", \"code\"].every((c) => columnNames.includes(c))) {\n return false;\n }\n }\n\n // If the ID column is a FK then we're a subclass\n if (isSubClassTable(t)) return true;\n\n // Otherwise respect the created_at/updated_at required rules\n return hasIdColumn && (hasCreatedAt || !createdAtConf.required) && (hasUpdatedAt || !updatedAtConf.required);\n}\n\nexport function isSubClassTable(t: Table): boolean {\n // Note that `.get(\"id\")` will blow up if there is no id column\n return t.columns.get(\"id\").foreignKeys.length === 1;\n}\n\nexport function isEnumTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n const columnNames = t.columns.map((c) => c.name);\n return [\"id\", \"code\", \"name\"].every((c) => columnNames.includes(c)) && !isEntityTable(config, t);\n}\n\n// Oddly enough isJoinTable showed up as a hot spot, so cache its calculation.\nconst joinTables: Map<string, boolean> = new Map();\n\nexport function isJoinTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n return getOrElse(joinTables, t.name, () => {\n const { columns } = t;\n const fks = columns.filter((c) => c.isForeignKey);\n const pks = columns.filter((c) => c.isPrimaryKey);\n // Every join table is two FKs, plus either a surrogate `id` PK (\"id-ful\") or no surrogate\n // at all, in which case the two FKs are themselves the composite PK (\"id-less\").\n if (fks.length !== 2) return false;\n if (!joinTableHasId(t) && !(pks.length === 2 && pks.every((c) => c.isForeignKey))) return false;\n // Any column beyond the two FKs and the optional `id` PK must be a single created_at.\n const others = columns.length - fks.length - (joinTableHasId(t) ? 1 : 0);\n const createdAts = columns.filter((c) => c.name === \"created_at\" || c.name === \"createdAt\").length;\n return others === 0 || (others === 1 && createdAts === 1);\n });\n}\n\n/** Whether a join table has a surrogate `id` primary key (vs. an id-less FK-pair composite PK). */\nexport function joinTableHasId(t: Table): boolean {\n const pks = t.columns.filter((c) => c.isPrimaryKey);\n return pks.length === 1 && !pks[0].isForeignKey;\n}\n\nfunction getOrElse<K, V>(map: Map<K, V>, key: K, fn: () => V): V {\n let value = map.get(key);\n if (value === undefined) {\n value = fn();\n map.set(key, value);\n }\n return value;\n}\n\n/** Converts `projects` to `Project`. */\nexport function tableToEntityName(config: Config, table: Table): string {\n let entityName = config.__tableToEntityName?.[table.name];\n if (!entityName) {\n const configEntityName = Object.entries(config.entities)\n .filter(([, conf]) => conf.tableName === table.name)\n .map(([entityName]) => entityName)[0];\n entityName = configEntityName || pascalCase(pluralize.singular(table.name));\n (config.__tableToEntityName ??= {})[table.name] = entityName;\n }\n return entityName;\n}\n\nexport const dateCode = code`Date`;\nexport const plainDateCode = code`${imp(\"Temporal@joist-orm\")}.PlainDate`;\nexport const plainTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainTime`;\nexport const plainDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainDateTime`;\nexport const zonedDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.ZonedDateTime`;\n\n/** Maps db types, i.e. `int`, to JS types, i.e. `number`. */\nexport function mapSimpleDbTypeToTypescriptType(config: Config, dbType: DatabaseColumnType): PrimitiveTypescriptType {\n switch (dbType) {\n case \"boolean\":\n return \"boolean\";\n case \"int\":\n case \"numeric\":\n case \"smallint\":\n case \"integer\":\n case \"decimal\":\n case \"real\":\n case \"smallserial\":\n case \"serial\":\n case \"double precision\":\n return \"number\";\n case \"bigserial\":\n case \"bigint\":\n return \"bigint\";\n case \"text\":\n case \"citext\":\n case \"character varying\":\n case \"varchar\":\n case \"uuid\":\n case \"tsvector\":\n case \"tstzrange\":\n return \"string\";\n case \"time without time zone\":\n return config.temporal\n ? plainTimeCode\n : fail(\"Joist doesn't support 'time without time zone' unless temporal is enabled\");\n case \"timestamp with time zone\":\n return config.temporal ? zonedDateTimeCode : dateCode;\n case \"timestamp without time zone\":\n return config.temporal ? plainDateTimeCode : dateCode;\n case \"date\":\n return config.temporal ? plainDateCode : dateCode;\n case \"jsonb\":\n return \"Object\";\n case \"bytea\":\n return \"Uint8Array\";\n default:\n assertNever(dbType);\n }\n}\n\nexport function merge<T>(a: T[], b: T[]): T[] {\n return [...a, ...b];\n}\n\n/** Returns true if `p` is resolved, otherwise false if it is rejected. */\nexport async function trueIfResolved(p: Promise<unknown>): Promise<boolean> {\n return p.then(\n () => true,\n () => false,\n );\n}\n\nexport function fail(message?: string): never {\n throw new Error(message || \"Failed\");\n}\n\nexport function sortKeys<T extends object>(o: T): T {\n return Object.keys(o)\n .sort()\n .reduce(\n (acc, key) => {\n const value = o[key as keyof T];\n const newValue = typeof value === \"object\" && isPlainObject(value) ? sortKeys(value as any as object) : value;\n acc[key as keyof T] = newValue as any;\n return acc;\n },\n {} as any as T,\n );\n}\n\nexport function parseOrder(order: string | undefined): { field: string; direction: \"ASC\" | \"DESC\" } | undefined {\n if (!order) {\n return undefined;\n }\n const [field, direction = \"ASC\"] = order.split(\" \");\n if (direction !== \"ASC\" && direction !== \"DESC\") {\n throw new Error(`Invalid direction: ${direction} (must be \"ASC\" or \"DESC\")`);\n }\n return { field, direction };\n}\n\nexport function q(s: string | undefined): string {\n return s === undefined ? \"undefined\" : `\"${s}\"`;\n}\n\nexport function uncapitalize(s: string): string {\n return s.charAt(0).toLowerCase() + s.slice(1);\n}\n\nfunction isIgnored(config: Config, t: Table): boolean {\n return (config.ignoredTables || [\"migrations\", \"pgmigrations\"]).includes(t.name) || !shouldIncludeSchema(config, t);\n}\n\nfunction shouldIncludeSchema(config: Config, t: Table): boolean {\n return config.schemas ? config.schemas.includes(t.schema.name) : t.schema.name === \"public\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { pascalCase } from \"change-case\";\nimport { isPlainObject } from \"joist-utils\";\nimport pluralize from \"pluralize\";\nimport { code, imp } from \"ts-poet\";\n\nimport { type Config, getTimestampConfig } from \"./config.ts\";\nimport { type DatabaseColumnType, type PrimitiveTypescriptType } from \"./EntityDbMetadata.ts\";\nimport { type Table } from \"./pgMetadata.ts\";\n\nexport function assertNever(x: never): never {\n throw new Error(\"Unexpected object: \" + x);\n}\n\nexport function isEntityTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t) || isJoinTable(config, t)) {\n return false;\n }\n\n // Depending on the config settings, we may/may not require the presence of timestamp\n // fields to determine whether a table is an entity.\n const columnNames = t.columns.map((c) => c.name);\n const { updatedAtConf, createdAtConf } = getTimestampConfig(config);\n const hasCreatedAt = createdAtConf.names.some((c) => columnNames.includes(c));\n const hasUpdatedAt = updatedAtConf.names.some((c) => columnNames.includes(c));\n\n // Entities always require an id column\n const hasIdColumn = columnNames.includes(\"id\");\n if (!hasIdColumn) return false;\n\n // If we have no timestamp columns, but do have `name` and `code`, we're probably an enum.\n if (hasIdColumn && !hasCreatedAt && !hasUpdatedAt) {\n if ([\"name\", \"code\"].every((c) => columnNames.includes(c))) {\n return false;\n }\n }\n\n // If the ID column is a FK then we're a subclass\n if (isSubClassTable(t)) return true;\n\n // Otherwise respect the created_at/updated_at required rules\n return hasIdColumn && (hasCreatedAt || !createdAtConf.required) && (hasUpdatedAt || !updatedAtConf.required);\n}\n\nexport function isSubClassTable(t: Table): boolean {\n // Note that `.get(\"id\")` will blow up if there is no id column\n return t.columns.get(\"id\").foreignKeys.length === 1;\n}\n\nexport function isEnumTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n const columnNames = t.columns.map((c) => c.name);\n return [\"id\", \"code\", \"name\"].every((c) => columnNames.includes(c)) && !isEntityTable(config, t);\n}\n\n// Oddly enough isJoinTable showed up as a hot spot, so cache its calculation.\nconst joinTables: Map<string, boolean> = new Map();\n\nexport function isJoinTable(config: Config, t: Table): boolean {\n if (isIgnored(config, t)) {\n return false;\n }\n return getOrElse(joinTables, t.name, () => {\n const { columns } = t;\n const fks = columns.filter((c) => c.isForeignKey);\n const pks = columns.filter((c) => c.isPrimaryKey);\n // Every join table is two FKs, plus either a surrogate `id` PK (\"id-ful\") or no surrogate\n // at all, in which case the two FKs are themselves the composite PK (\"id-less\").\n if (fks.length !== 2) return false;\n if (!joinTableHasId(t) && !(pks.length === 2 && pks.every((c) => c.isForeignKey))) return false;\n // Any column beyond the two FKs and the optional `id` PK must be a single created_at.\n const others = columns.length - fks.length - (joinTableHasId(t) ? 1 : 0);\n const createdAts = columns.filter((c) => c.name === \"created_at\" || c.name === \"createdAt\").length;\n return others === 0 || (others === 1 && createdAts === 1);\n });\n}\n\n/** Whether a join table has a surrogate `id` primary key (vs. an id-less FK-pair composite PK). */\nexport function joinTableHasId(t: Table): boolean {\n const pks = t.columns.filter((c) => c.isPrimaryKey);\n return pks.length === 1 && !pks[0].isForeignKey;\n}\n\nfunction getOrElse<K, V>(map: Map<K, V>, key: K, fn: () => V): V {\n let value = map.get(key);\n if (value === undefined) {\n value = fn();\n map.set(key, value);\n }\n return value;\n}\n\n/** Converts `projects` to `Project`. */\nexport function tableToEntityName(config: Config, table: Table): string {\n let entityName = config.__tableToEntityName?.[table.name];\n if (!entityName) {\n const configEntityName = Object.entries(config.entities)\n .filter(([, conf]) => conf.tableName === table.name)\n .map(([entityName]) => entityName)[0];\n entityName = configEntityName || pascalCase(pluralize.singular(table.name));\n (config.__tableToEntityName ??= {})[table.name] = entityName;\n }\n return entityName;\n}\n\nexport const dateCode = code`Date`;\nexport const plainDateCode = code`${imp(\"Temporal@joist-orm\")}.PlainDate`;\nexport const plainTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainTime`;\nexport const plainDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.PlainDateTime`;\nexport const zonedDateTimeCode = code`${imp(\"Temporal@joist-orm\")}.ZonedDateTime`;\n\n/** Maps db types, i.e. `int`, to JS types, i.e. `number`. */\nexport function mapSimpleDbTypeToTypescriptType(config: Config, dbType: DatabaseColumnType): PrimitiveTypescriptType {\n switch (dbType) {\n case \"boolean\":\n return \"boolean\";\n case \"int\":\n case \"numeric\":\n case \"smallint\":\n case \"integer\":\n case \"decimal\":\n case \"real\":\n case \"smallserial\":\n case \"serial\":\n case \"double precision\":\n return \"number\";\n case \"bigserial\":\n case \"bigint\":\n return \"bigint\";\n case \"text\":\n case \"citext\":\n case \"character varying\":\n case \"varchar\":\n case \"uuid\":\n case \"tsvector\":\n case \"tstzrange\":\n return \"string\";\n case \"time without time zone\":\n return config.temporal\n ? plainTimeCode\n : fail(\"Joist doesn't support 'time without time zone' unless temporal is enabled\");\n case \"timestamp with time zone\":\n return config.temporal ? zonedDateTimeCode : dateCode;\n case \"timestamp without time zone\":\n return config.temporal ? plainDateTimeCode : dateCode;\n case \"date\":\n return config.temporal ? plainDateCode : dateCode;\n case \"jsonb\":\n return \"Object\";\n case \"bytea\":\n return \"Uint8Array\";\n default:\n assertNever(dbType);\n }\n}\n\nexport function merge<T>(a: T[], b: T[]): T[] {\n return [...a, ...b];\n}\n\n/** Returns true if `p` is resolved, otherwise false if it is rejected. */\nexport async function trueIfResolved(p: Promise<unknown>): Promise<boolean> {\n return p.then(\n () => true,\n () => false,\n );\n}\n\nexport function fail(message?: string): never {\n throw new Error(message || \"Failed\");\n}\n\nexport function sortKeys<T extends object>(o: T): T {\n return Object.keys(o)\n .sort()\n .reduce(\n (acc, key) => {\n const value = o[key as keyof T];\n const newValue = typeof value === \"object\" && isPlainObject(value) ? sortKeys(value as any as object) : value;\n acc[key as keyof T] = newValue as any;\n return acc;\n },\n {} as any as T,\n );\n}\n\nexport function parseOrder(order: string | undefined): { field: string; direction: \"ASC\" | \"DESC\" } | undefined {\n if (!order) {\n return undefined;\n }\n const [field, direction = \"ASC\"] = order.split(\" \");\n if (direction !== \"ASC\" && direction !== \"DESC\") {\n throw new Error(`Invalid direction: ${direction} (must be \"ASC\" or \"DESC\")`);\n }\n return { field, direction };\n}\n\nexport function q(s: string | undefined): string {\n return s === undefined ? \"undefined\" : `\"${s}\"`;\n}\n\nexport function uncapitalize(s: string): string {\n return s.charAt(0).toLowerCase() + s.slice(1);\n}\n\nfunction isIgnored(config: Config, t: Table): boolean {\n return (config.ignoredTables || [\"migrations\", \"pgmigrations\"]).includes(t.name) || !shouldIncludeSchema(config, t);\n}\n\n/** Uses the same schema selection for metadata and test database cleanup. */\nexport function shouldIncludeSchema(config: Config, t: Table): boolean {\n return config.schemas ? config.schemas.includes(t.schema.name) : t.schema.name === \"public\";\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joist-codegen",
3
- "version": "2.3.0-next.99",
3
+ "version": "2.4.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -30,6 +30,16 @@
30
30
  "default": "./build/index.cjs"
31
31
  },
32
32
  "default": "./build/index.js"
33
+ },
34
+ "./pgMetadata": {
35
+ "import": {
36
+ "types": "./build/pgMetadata.d.mts",
37
+ "default": "./build/pgMetadata.js"
38
+ },
39
+ "require": {
40
+ "types": "./build/pgMetadata.d.cts",
41
+ "default": "./build/pgMetadata.cjs"
42
+ }
33
43
  }
34
44
  },
35
45
  "bin": "build/index.cjs",
@@ -38,7 +48,7 @@
38
48
  "skills"
39
49
  ],
40
50
  "peerDependencies": {
41
- "joist-utils": "2.3.0-next.99",
51
+ "joist-utils": "2.4.0",
42
52
  "knex": "^3.1.0",
43
53
  "pg": "^8.23.0"
44
54
  },
@@ -52,7 +62,6 @@
52
62
  "fast-glob": "^3.3.3",
53
63
  "jscodeshift": "^17.4.0",
54
64
  "jsonc-parser": "^3.3.1",
55
- "pg-structure": "^7.15.3",
56
65
  "pluralize": "^8.0.0",
57
66
  "prettier": "^3.9.6",
58
67
  "ts-poet": "^6.12.0",
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: entity-docs
2
+ name: joist-docs
3
3
  description: Read the `src/entities/<Entity>.md` business-docs when working on an entity, its resolvers, jobs, or tests. Add only durable, non-obvious domain knowledge that helps readers understand a business rule or avoid a likely mistake; prioritize succinctness, clarity, and discretion. Also covers how `## Overview` and `## Fields`/`### fieldName` sections sync to JSDocs via `yarn joist-codegen`.
4
4
  ---
5
5