joist-migration-utils 2.3.0-next.7 → 2.3.0-next.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/_virtual/_rolldown/runtime.cjs +23 -0
- package/build/_virtual/_rolldown/runtime.js +5 -0
- package/build/index.cjs +44 -0
- package/build/index.cjs.map +1 -0
- package/build/index.d.cts +7 -0
- package/build/index.d.cts.map +1 -0
- package/build/index.d.mts +7 -0
- package/build/index.d.mts.map +1 -0
- package/build/index.js +22 -39
- package/build/index.js.map +1 -1
- package/build/migrate.cjs +20 -0
- package/build/migrate.cjs.map +1 -0
- package/build/migrate.d.cts +6 -0
- package/build/migrate.d.cts.map +1 -0
- package/build/migrate.d.mts +6 -0
- package/build/migrate.d.mts.map +1 -0
- package/build/migrate.js +15 -17
- package/build/migrate.js.map +1 -1
- package/build/utils.cjs +272 -0
- package/build/utils.cjs.map +1 -0
- package/build/utils.d.cts +97 -0
- package/build/utils.d.cts.map +1 -0
- package/build/utils.d.mts +97 -0
- package/build/utils.d.mts.map +1 -0
- package/build/utils.js +192 -175
- package/build/utils.js.map +1 -1
- package/package.json +30 -13
- package/build/index.d.ts +0 -4
- package/build/index.d.ts.map +0 -1
- package/build/migrate.d.ts +0 -3
- package/build/migrate.d.ts.map +0 -1
- package/build/utils.d.ts +0 -95
- package/build/utils.d.ts.map +0 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ColumnDefinition, ColumnDefinitions, DropOptions, MigrationBuilder, TableOptions } from "node-pg-migrate";
|
|
2
|
+
//#region src/utils.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Creates an entity table with our conventions.
|
|
5
|
+
*
|
|
6
|
+
* Specifically an `id` auto-increment column (via a sequence) and `created_at` and `updated_at`
|
|
7
|
+
* columns.
|
|
8
|
+
*/
|
|
9
|
+
declare function createEntityTable(b: MigrationBuilder, tableName: string, columns: ColumnDefinitions): void;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a subtype table using class-per-table inheritance.
|
|
12
|
+
*
|
|
13
|
+
* The subtable will use the base table's id as its identity, and when loading rows of the base
|
|
14
|
+
* type, Joist will automatically stitch together rows across each table into a single instance.
|
|
15
|
+
*/
|
|
16
|
+
declare function createSubTable(b: MigrationBuilder, baseTableName: string, subTableName: string, columns: ColumnDefinitions): void;
|
|
17
|
+
declare function createEnumTable(b: MigrationBuilder, tableName: string, values: Array<[string, string]>): void;
|
|
18
|
+
declare function addEnumValue(b: MigrationBuilder, tableName: string, value: [string, string]): void;
|
|
19
|
+
declare function updateEnumValue(b: MigrationBuilder, tableName: string, previousCode: string, value: [string, string]): void;
|
|
20
|
+
/** Makes a trigger to update the `updated_at` column. */
|
|
21
|
+
declare function createTriggers(b: MigrationBuilder, tableName: string): void;
|
|
22
|
+
declare function createUpdatedAtFunction(b: MigrationBuilder): void;
|
|
23
|
+
/**
|
|
24
|
+
* Unnests a 2d array like `[[a, b], [c, d]]` into two rows of `[a, b]` and `[c, d]`.
|
|
25
|
+
*
|
|
26
|
+
* This custom function is a key part of Joist's bulk INSERT/UPDATE that sends "all firstNames as
|
|
27
|
+
* a single param", because it lets us support array columns as a single parameter (i.e. an array
|
|
28
|
+
* of arrays).
|
|
29
|
+
*
|
|
30
|
+
* Postgres's built-in `unnest` is too aggressive when flattening, and will unnest the 2d array
|
|
31
|
+
* of `[[a, b], [c, d]]` into four rows of `a`, `b`, `c`, and `d`, which is not what we want.
|
|
32
|
+
*
|
|
33
|
+
* Additionally, Postgres does not support "jagged arrays", i.e. 2D arrays where some
|
|
34
|
+
* sub-arrays are longer than others, e.g. `[[a, b], [c, d, e]]`. Instead, it treats arrays
|
|
35
|
+
* more as the mathematical concept of a matrix, than "a list of lists".
|
|
36
|
+
*
|
|
37
|
+
* To work around this, our PostgresDriver auto-pads all 2D arrays to the same length, by
|
|
38
|
+
* appending `NULL`, and then `unnest_arrays` prunes all `NULL`s from the resulting 1D arrays.
|
|
39
|
+
* See `fillArrayWithNulls`.
|
|
40
|
+
*
|
|
41
|
+
*
|
|
42
|
+
* This means we purposefully do not/cannot support `NULL` values _within_ array columns,
|
|
43
|
+
* i.e. `nick_names=[a, NULL, b]` (without some additional heuristics).
|
|
44
|
+
*
|
|
45
|
+
* That said, we do need to support nullable array columns, i.e. `nick_names=NULL`, which we
|
|
46
|
+
* detect by prefixing an additional marker element to each sub-array that indicates whether the
|
|
47
|
+
* array column is null or not, and then conditionally enabling this with the `nullable=true`
|
|
48
|
+
* param to `unnest_arrays`.
|
|
49
|
+
*/
|
|
50
|
+
declare const unnest_arrays = "\nCREATE OR REPLACE FUNCTION unnest_arrays(arr ANYARRAY, nullable BOOLEAN = false, OUT a ANYARRAY)\n RETURNS SETOF ANYARRAY\n LANGUAGE plpgsql IMMUTABLE STRICT AS\n$func$\nBEGIN\n FOREACH a SLICE 1 IN ARRAY arr LOOP\n IF nullable THEN\n IF a[1] IS NULL THEN a := NULL;\n ELSE a := a[2:array_length(a, 1)];\n END IF;\n END IF;\n a := array_remove(a, NULL);\n RETURN NEXT;\n END LOOP;\nEND\n$func$;\n";
|
|
51
|
+
declare function createUnnestArraysFunction(b: MigrationBuilder): void;
|
|
52
|
+
declare function createCreatedAtFunction(b: MigrationBuilder): void;
|
|
53
|
+
type FieldNameOverrides = {
|
|
54
|
+
fieldName?: string;
|
|
55
|
+
otherFieldName?: string;
|
|
56
|
+
};
|
|
57
|
+
type ForeignKeyOpts = Partial<ColumnDefinition> & Required<Pick<ColumnDefinition, "notNull">> & FieldNameOverrides;
|
|
58
|
+
declare function foreignKey(otherTable: string, opts: ForeignKeyOpts): ColumnDefinition;
|
|
59
|
+
type RenameRelationOpts = FieldNameOverrides & Pick<ColumnDefinition, "comment">;
|
|
60
|
+
declare function renameRelation(b: MigrationBuilder, tableName: string, columnName: string, opts: RenameRelationOpts): void;
|
|
61
|
+
declare function commentData(data: any, comment?: string | null): string;
|
|
62
|
+
declare function enumArrayColumn(enumTable: string, opts?: Pick<ColumnDefinition, "notNull">): ColumnDefinition;
|
|
63
|
+
type ManyToManyColumn = {
|
|
64
|
+
/** The target table for this m2m table, i.e. for a `books_to_tags`, this might be `books`. */
|
|
65
|
+
table: string;
|
|
66
|
+
/** The column name within the m2m table, i.e. for a `books_to_tags`, this might be `book_id`. */
|
|
67
|
+
column?: string;
|
|
68
|
+
/**
|
|
69
|
+
* The name of the collection that *points to* these rows, i.e. for `books_to_tags` and the `book_id`
|
|
70
|
+
* column, `collectionName=taggedBooks` b/c `t1.taggedBooks` does a `SELECT book_id WHERE tag_id=t:1`.
|
|
71
|
+
*
|
|
72
|
+
* For self-referential m2m tables, i.e. `author_to_mentors`, these names can be confusing, i.e.:
|
|
73
|
+
*
|
|
74
|
+
* ```
|
|
75
|
+
* createManyToManyTable(
|
|
76
|
+
* b,
|
|
77
|
+
* "author_to_mentors",
|
|
78
|
+
* // column=mentor_id, collectionName=mentors ==> `a.mentors` does `select mentor_id WHERE mentee_id=a:1`
|
|
79
|
+
* { table: "authors", column: "mentor_id", collectionName: "mentors" },
|
|
80
|
+
* // column=mentee_id, collectionName=mentees ==> `a.mentees` does `select mentee_id WHERE mentor_id=a:1`
|
|
81
|
+
* { table: "authors", column: "mentee_id", collectionName: "mentees" },
|
|
82
|
+
* );
|
|
83
|
+
* ````
|
|
84
|
+
*/
|
|
85
|
+
collectionName?: string;
|
|
86
|
+
};
|
|
87
|
+
/** Creates a many-to-many table between `table1` and `table2` with our conventions. */
|
|
88
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, table1: string, table2: string, options?: TableOptions & DropOptions): void;
|
|
89
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, column1: ManyToManyColumn, column2: ManyToManyColumn, options?: TableOptions & DropOptions): void;
|
|
90
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, table1: string, column2: ManyToManyColumn, options?: TableOptions & DropOptions): void;
|
|
91
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, column1: ManyToManyColumn, table2: string, options?: TableOptions & DropOptions): void;
|
|
92
|
+
/** Adds columns + auto-indexes any foreign keys. */
|
|
93
|
+
declare function addColumns(b: MigrationBuilder, tableName: string, columns: ColumnDefinitions): void;
|
|
94
|
+
declare function fail(message?: string): never;
|
|
95
|
+
//#endregion
|
|
96
|
+
export { FieldNameOverrides, RenameRelationOpts, addColumns, addEnumValue, commentData, createCreatedAtFunction, createEntityTable, createEnumTable, createManyToManyTable, createSubTable, createTriggers, createUnnestArraysFunction, createUpdatedAtFunction, enumArrayColumn, fail, foreignKey, renameRelation, unnest_arrays, updateEnumValue };
|
|
97
|
+
//# sourceMappingURL=utils.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.cts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;;;;;iBAkBgB,kBAAkB,GAAG,kBAAkB,mBAAmB,SAAS;;;;;;;iBAyBnE,eACd,GAAG,kBACH,uBACA,sBACA,SAAS;iBAsBK,gBAAgB,GAAG,kBAAkB,mBAAmB,QAAQ;iBAShE,aAAa,GAAG,kBAAkB,mBAAmB;iBAMrD,gBACd,GAAG,kBACH,mBACA,sBACA;;iBAcc,eAAe,GAAG,kBAAkB;iBAepC,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoC9B;iBAmBG,2BAA2B,GAAG;iBAI9B,wBAAwB,GAAG;KAS/B;EACV;EACA;;KAEG,iBAAiB,QAAQ,oBAAoB,SAAS,KAAK,gCAAgC;iBAChF,WAAW,oBAAoB,MAAM,iBAAiB;KAU1D,qBAAqB,qBAAqB,KAAK;iBAC3C,eAAe,GAAG,kBAAkB,mBAAmB,oBAAoB,MAAM;iBAIjF,YAAY,WAAW;iBAkBvB,gBAAgB,mBAAmB,OAAO,KAAK,+BAA+B;KAazF;;EAEH;;EAEA;;;;;;;;;;;;;;;;;;EAkBA;;;iBAac,sBACd,GAAG,kBACH,mBACA,gBACA,gBACA,UAAU,eAAe;iBAEX,sBACd,GAAG,kBACH,mBACA,SAAS,kBACT,SAAS,kBACT,UAAU,eAAe;iBAEX,sBACd,GAAG,kBACH,mBACA,gBACA,SAAS,kBACT,UAAU,eAAe;iBAEX,sBACd,GAAG,kBACH,mBACA,SAAS,kBACT,gBACA,UAAU,eAAe;;iBA0BX,WAAW,GAAG,kBAAkB,mBAAmB,SAAS;iBAS5D,KAAK"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ColumnDefinition, ColumnDefinitions, DropOptions, MigrationBuilder, TableOptions } from "node-pg-migrate";
|
|
2
|
+
//#region src/utils.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Creates an entity table with our conventions.
|
|
5
|
+
*
|
|
6
|
+
* Specifically an `id` auto-increment column (via a sequence) and `created_at` and `updated_at`
|
|
7
|
+
* columns.
|
|
8
|
+
*/
|
|
9
|
+
declare function createEntityTable(b: MigrationBuilder, tableName: string, columns: ColumnDefinitions): void;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a subtype table using class-per-table inheritance.
|
|
12
|
+
*
|
|
13
|
+
* The subtable will use the base table's id as its identity, and when loading rows of the base
|
|
14
|
+
* type, Joist will automatically stitch together rows across each table into a single instance.
|
|
15
|
+
*/
|
|
16
|
+
declare function createSubTable(b: MigrationBuilder, baseTableName: string, subTableName: string, columns: ColumnDefinitions): void;
|
|
17
|
+
declare function createEnumTable(b: MigrationBuilder, tableName: string, values: Array<[string, string]>): void;
|
|
18
|
+
declare function addEnumValue(b: MigrationBuilder, tableName: string, value: [string, string]): void;
|
|
19
|
+
declare function updateEnumValue(b: MigrationBuilder, tableName: string, previousCode: string, value: [string, string]): void;
|
|
20
|
+
/** Makes a trigger to update the `updated_at` column. */
|
|
21
|
+
declare function createTriggers(b: MigrationBuilder, tableName: string): void;
|
|
22
|
+
declare function createUpdatedAtFunction(b: MigrationBuilder): void;
|
|
23
|
+
/**
|
|
24
|
+
* Unnests a 2d array like `[[a, b], [c, d]]` into two rows of `[a, b]` and `[c, d]`.
|
|
25
|
+
*
|
|
26
|
+
* This custom function is a key part of Joist's bulk INSERT/UPDATE that sends "all firstNames as
|
|
27
|
+
* a single param", because it lets us support array columns as a single parameter (i.e. an array
|
|
28
|
+
* of arrays).
|
|
29
|
+
*
|
|
30
|
+
* Postgres's built-in `unnest` is too aggressive when flattening, and will unnest the 2d array
|
|
31
|
+
* of `[[a, b], [c, d]]` into four rows of `a`, `b`, `c`, and `d`, which is not what we want.
|
|
32
|
+
*
|
|
33
|
+
* Additionally, Postgres does not support "jagged arrays", i.e. 2D arrays where some
|
|
34
|
+
* sub-arrays are longer than others, e.g. `[[a, b], [c, d, e]]`. Instead, it treats arrays
|
|
35
|
+
* more as the mathematical concept of a matrix, than "a list of lists".
|
|
36
|
+
*
|
|
37
|
+
* To work around this, our PostgresDriver auto-pads all 2D arrays to the same length, by
|
|
38
|
+
* appending `NULL`, and then `unnest_arrays` prunes all `NULL`s from the resulting 1D arrays.
|
|
39
|
+
* See `fillArrayWithNulls`.
|
|
40
|
+
*
|
|
41
|
+
*
|
|
42
|
+
* This means we purposefully do not/cannot support `NULL` values _within_ array columns,
|
|
43
|
+
* i.e. `nick_names=[a, NULL, b]` (without some additional heuristics).
|
|
44
|
+
*
|
|
45
|
+
* That said, we do need to support nullable array columns, i.e. `nick_names=NULL`, which we
|
|
46
|
+
* detect by prefixing an additional marker element to each sub-array that indicates whether the
|
|
47
|
+
* array column is null or not, and then conditionally enabling this with the `nullable=true`
|
|
48
|
+
* param to `unnest_arrays`.
|
|
49
|
+
*/
|
|
50
|
+
declare const unnest_arrays = "\nCREATE OR REPLACE FUNCTION unnest_arrays(arr ANYARRAY, nullable BOOLEAN = false, OUT a ANYARRAY)\n RETURNS SETOF ANYARRAY\n LANGUAGE plpgsql IMMUTABLE STRICT AS\n$func$\nBEGIN\n FOREACH a SLICE 1 IN ARRAY arr LOOP\n IF nullable THEN\n IF a[1] IS NULL THEN a := NULL;\n ELSE a := a[2:array_length(a, 1)];\n END IF;\n END IF;\n a := array_remove(a, NULL);\n RETURN NEXT;\n END LOOP;\nEND\n$func$;\n";
|
|
51
|
+
declare function createUnnestArraysFunction(b: MigrationBuilder): void;
|
|
52
|
+
declare function createCreatedAtFunction(b: MigrationBuilder): void;
|
|
53
|
+
type FieldNameOverrides = {
|
|
54
|
+
fieldName?: string;
|
|
55
|
+
otherFieldName?: string;
|
|
56
|
+
};
|
|
57
|
+
type ForeignKeyOpts = Partial<ColumnDefinition> & Required<Pick<ColumnDefinition, "notNull">> & FieldNameOverrides;
|
|
58
|
+
declare function foreignKey(otherTable: string, opts: ForeignKeyOpts): ColumnDefinition;
|
|
59
|
+
type RenameRelationOpts = FieldNameOverrides & Pick<ColumnDefinition, "comment">;
|
|
60
|
+
declare function renameRelation(b: MigrationBuilder, tableName: string, columnName: string, opts: RenameRelationOpts): void;
|
|
61
|
+
declare function commentData(data: any, comment?: string | null): string;
|
|
62
|
+
declare function enumArrayColumn(enumTable: string, opts?: Pick<ColumnDefinition, "notNull">): ColumnDefinition;
|
|
63
|
+
type ManyToManyColumn = {
|
|
64
|
+
/** The target table for this m2m table, i.e. for a `books_to_tags`, this might be `books`. */
|
|
65
|
+
table: string;
|
|
66
|
+
/** The column name within the m2m table, i.e. for a `books_to_tags`, this might be `book_id`. */
|
|
67
|
+
column?: string;
|
|
68
|
+
/**
|
|
69
|
+
* The name of the collection that *points to* these rows, i.e. for `books_to_tags` and the `book_id`
|
|
70
|
+
* column, `collectionName=taggedBooks` b/c `t1.taggedBooks` does a `SELECT book_id WHERE tag_id=t:1`.
|
|
71
|
+
*
|
|
72
|
+
* For self-referential m2m tables, i.e. `author_to_mentors`, these names can be confusing, i.e.:
|
|
73
|
+
*
|
|
74
|
+
* ```
|
|
75
|
+
* createManyToManyTable(
|
|
76
|
+
* b,
|
|
77
|
+
* "author_to_mentors",
|
|
78
|
+
* // column=mentor_id, collectionName=mentors ==> `a.mentors` does `select mentor_id WHERE mentee_id=a:1`
|
|
79
|
+
* { table: "authors", column: "mentor_id", collectionName: "mentors" },
|
|
80
|
+
* // column=mentee_id, collectionName=mentees ==> `a.mentees` does `select mentee_id WHERE mentor_id=a:1`
|
|
81
|
+
* { table: "authors", column: "mentee_id", collectionName: "mentees" },
|
|
82
|
+
* );
|
|
83
|
+
* ````
|
|
84
|
+
*/
|
|
85
|
+
collectionName?: string;
|
|
86
|
+
};
|
|
87
|
+
/** Creates a many-to-many table between `table1` and `table2` with our conventions. */
|
|
88
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, table1: string, table2: string, options?: TableOptions & DropOptions): void;
|
|
89
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, column1: ManyToManyColumn, column2: ManyToManyColumn, options?: TableOptions & DropOptions): void;
|
|
90
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, table1: string, column2: ManyToManyColumn, options?: TableOptions & DropOptions): void;
|
|
91
|
+
declare function createManyToManyTable(b: MigrationBuilder, tableName: string, column1: ManyToManyColumn, table2: string, options?: TableOptions & DropOptions): void;
|
|
92
|
+
/** Adds columns + auto-indexes any foreign keys. */
|
|
93
|
+
declare function addColumns(b: MigrationBuilder, tableName: string, columns: ColumnDefinitions): void;
|
|
94
|
+
declare function fail(message?: string): never;
|
|
95
|
+
//#endregion
|
|
96
|
+
export { FieldNameOverrides, RenameRelationOpts, addColumns, addEnumValue, commentData, createCreatedAtFunction, createEntityTable, createEnumTable, createManyToManyTable, createSubTable, createTriggers, createUnnestArraysFunction, createUpdatedAtFunction, enumArrayColumn, fail, foreignKey, renameRelation, unnest_arrays, updateEnumValue };
|
|
97
|
+
//# sourceMappingURL=utils.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.mts","names":[],"sources":["../src/utils.ts"],"mappings":";;;;;;;;iBAkBgB,kBAAkB,GAAG,kBAAkB,mBAAmB,SAAS;;;;;;;iBAyBnE,eACd,GAAG,kBACH,uBACA,sBACA,SAAS;iBAsBK,gBAAgB,GAAG,kBAAkB,mBAAmB,QAAQ;iBAShE,aAAa,GAAG,kBAAkB,mBAAmB;iBAMrD,gBACd,GAAG,kBACH,mBACA,sBACA;;iBAcc,eAAe,GAAG,kBAAkB;iBAepC,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAoC9B;iBAmBG,2BAA2B,GAAG;iBAI9B,wBAAwB,GAAG;KAS/B;EACV;EACA;;KAEG,iBAAiB,QAAQ,oBAAoB,SAAS,KAAK,gCAAgC;iBAChF,WAAW,oBAAoB,MAAM,iBAAiB;KAU1D,qBAAqB,qBAAqB,KAAK;iBAC3C,eAAe,GAAG,kBAAkB,mBAAmB,oBAAoB,MAAM;iBAIjF,YAAY,WAAW;iBAkBvB,gBAAgB,mBAAmB,OAAO,KAAK,+BAA+B;KAazF;;EAEH;;EAEA;;;;;;;;;;;;;;;;;;EAkBA;;;iBAac,sBACd,GAAG,kBACH,mBACA,gBACA,gBACA,UAAU,eAAe;iBAEX,sBACd,GAAG,kBACH,mBACA,SAAS,kBACT,SAAS,kBACT,UAAU,eAAe;iBAEX,sBACd,GAAG,kBACH,mBACA,gBACA,SAAS,kBACT,UAAU,eAAe;iBAEX,sBACd,GAAG,kBACH,mBACA,SAAS,kBACT,gBACA,UAAU,eAAe;;iBA0BX,WAAW,GAAG,kBAAkB,mBAAmB,SAAS;iBAS5D,KAAK"}
|
package/build/utils.js
CHANGED
|
@@ -1,142 +1,132 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
exports.createSubTable = createSubTable;
|
|
6
|
-
exports.createEnumTable = createEnumTable;
|
|
7
|
-
exports.addEnumValue = addEnumValue;
|
|
8
|
-
exports.updateEnumValue = updateEnumValue;
|
|
9
|
-
exports.createTriggers = createTriggers;
|
|
10
|
-
exports.createUpdatedAtFunction = createUpdatedAtFunction;
|
|
11
|
-
exports.createUnnestArraysFunction = createUnnestArraysFunction;
|
|
12
|
-
exports.createCreatedAtFunction = createCreatedAtFunction;
|
|
13
|
-
exports.foreignKey = foreignKey;
|
|
14
|
-
exports.renameRelation = renameRelation;
|
|
15
|
-
exports.commentData = commentData;
|
|
16
|
-
exports.enumArrayColumn = enumArrayColumn;
|
|
17
|
-
exports.createManyToManyTable = createManyToManyTable;
|
|
18
|
-
exports.addColumns = addColumns;
|
|
19
|
-
exports.fail = fail;
|
|
20
|
-
const node_pg_migrate_1 = require("node-pg-migrate");
|
|
21
|
-
const pluralize_1 = require("pluralize");
|
|
1
|
+
import { PgLiteral } from "node-pg-migrate";
|
|
2
|
+
import pluralize from "pluralize";
|
|
3
|
+
//#region src/utils.ts
|
|
4
|
+
const { singular } = pluralize;
|
|
22
5
|
/**
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
6
|
+
* Creates an entity table with our conventions.
|
|
7
|
+
*
|
|
8
|
+
* Specifically an `id` auto-increment column (via a sequence) and `created_at` and `updated_at`
|
|
9
|
+
* columns.
|
|
10
|
+
*/
|
|
28
11
|
function createEntityTable(b, tableName, columns) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
12
|
+
b.createTable(tableName, {
|
|
13
|
+
id: "id",
|
|
14
|
+
...columns,
|
|
15
|
+
created_at: {
|
|
16
|
+
type: "timestamptz",
|
|
17
|
+
notNull: true
|
|
18
|
+
},
|
|
19
|
+
updated_at: {
|
|
20
|
+
type: "timestamptz",
|
|
21
|
+
notNull: true
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
Object.entries(columns).forEach(([name, def]) => {
|
|
25
|
+
if (typeof def === "object" && def.references) b.addIndex(tableName, [name], { method: "btree" });
|
|
26
|
+
});
|
|
27
|
+
createTriggers(b, tableName);
|
|
43
28
|
}
|
|
44
29
|
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
30
|
+
* Creates a subtype table using class-per-table inheritance.
|
|
31
|
+
*
|
|
32
|
+
* The subtable will use the base table's id as its identity, and when loading rows of the base
|
|
33
|
+
* type, Joist will automatically stitch together rows across each table into a single instance.
|
|
34
|
+
*/
|
|
50
35
|
function createSubTable(b, baseTableName, subTableName, columns) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (typeof def === "object" && def.references) {
|
|
66
|
-
b.addIndex(subTableName, [name], { method: "btree" });
|
|
67
|
-
}
|
|
68
|
-
});
|
|
36
|
+
b.createTable(subTableName, {
|
|
37
|
+
id: {
|
|
38
|
+
type: "int",
|
|
39
|
+
references: `${baseTableName}`,
|
|
40
|
+
primaryKey: true,
|
|
41
|
+
deferrable: true,
|
|
42
|
+
deferred: true,
|
|
43
|
+
onDelete: "CASCADE"
|
|
44
|
+
},
|
|
45
|
+
...columns
|
|
46
|
+
});
|
|
47
|
+
Object.entries(columns).forEach(([name, def]) => {
|
|
48
|
+
if (typeof def === "object" && def.references) b.addIndex(subTableName, [name], { method: "btree" });
|
|
49
|
+
});
|
|
69
50
|
}
|
|
70
51
|
function createEnumTable(b, tableName, values) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
52
|
+
b.createTable(tableName, {
|
|
53
|
+
id: "id",
|
|
54
|
+
code: {
|
|
55
|
+
type: "text",
|
|
56
|
+
notNull: true
|
|
57
|
+
},
|
|
58
|
+
name: {
|
|
59
|
+
type: "text",
|
|
60
|
+
notNull: true
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
b.addConstraint(tableName, `${tableName}_unique_enum_code_constraint`, "UNIQUE (code)");
|
|
64
|
+
values.forEach((value) => addEnumValue(b, tableName, value));
|
|
78
65
|
}
|
|
79
66
|
function addEnumValue(b, tableName, value) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
67
|
+
const [code, name] = value;
|
|
68
|
+
validateEnumCode(code);
|
|
69
|
+
b.sql(`INSERT INTO ${tableName} (code, name) VALUES ('${code}', '${name.replace("'", "''")}');`);
|
|
83
70
|
}
|
|
84
71
|
function updateEnumValue(b, tableName, previousCode, value) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
72
|
+
const [code, name] = value;
|
|
73
|
+
validateEnumCode(code);
|
|
74
|
+
b.sql(`UPDATE ${tableName} SET code ='${code}', name = '${name.replace("'", "''")}' WHERE code = '${previousCode}';`);
|
|
88
75
|
}
|
|
89
76
|
function validateEnumCode(code) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
throw `ERROR: Invalid enum code specified: ${code}. Codes must match the regex: ${codeRegex}`;
|
|
77
|
+
const codeRegex = /^[A-Z0-9_]+$/;
|
|
78
|
+
if (!codeRegex.test(code)) throw `ERROR: Invalid enum code specified: ${code}. Codes must match the regex: ${codeRegex}`;
|
|
93
79
|
}
|
|
94
80
|
/** Makes a trigger to update the `updated_at` column. */
|
|
95
81
|
function createTriggers(b, tableName) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
82
|
+
b.createTrigger(tableName, `${tableName}_created_at`, {
|
|
83
|
+
when: "BEFORE",
|
|
84
|
+
operation: "INSERT",
|
|
85
|
+
level: "ROW",
|
|
86
|
+
function: "trigger_maybe_set_created_at"
|
|
87
|
+
});
|
|
88
|
+
b.createTrigger(tableName, `${tableName}_updated_at`, {
|
|
89
|
+
when: "BEFORE",
|
|
90
|
+
operation: "UPDATE",
|
|
91
|
+
level: "ROW",
|
|
92
|
+
function: "trigger_maybe_set_updated_at"
|
|
93
|
+
});
|
|
108
94
|
}
|
|
109
95
|
function createUpdatedAtFunction(b) {
|
|
110
|
-
|
|
96
|
+
b.createFunction("trigger_maybe_set_updated_at", [], {
|
|
97
|
+
replace: true,
|
|
98
|
+
language: "plpgsql",
|
|
99
|
+
returns: "TRIGGER"
|
|
100
|
+
}, "BEGIN IF NEW.updated_at = OLD.updated_at THEN NEW.updated_at = NOW(); END IF; RETURN NEW; END;");
|
|
111
101
|
}
|
|
112
102
|
/**
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
103
|
+
* Unnests a 2d array like `[[a, b], [c, d]]` into two rows of `[a, b]` and `[c, d]`.
|
|
104
|
+
*
|
|
105
|
+
* This custom function is a key part of Joist's bulk INSERT/UPDATE that sends "all firstNames as
|
|
106
|
+
* a single param", because it lets us support array columns as a single parameter (i.e. an array
|
|
107
|
+
* of arrays).
|
|
108
|
+
*
|
|
109
|
+
* Postgres's built-in `unnest` is too aggressive when flattening, and will unnest the 2d array
|
|
110
|
+
* of `[[a, b], [c, d]]` into four rows of `a`, `b`, `c`, and `d`, which is not what we want.
|
|
111
|
+
*
|
|
112
|
+
* Additionally, Postgres does not support "jagged arrays", i.e. 2D arrays where some
|
|
113
|
+
* sub-arrays are longer than others, e.g. `[[a, b], [c, d, e]]`. Instead, it treats arrays
|
|
114
|
+
* more as the mathematical concept of a matrix, than "a list of lists".
|
|
115
|
+
*
|
|
116
|
+
* To work around this, our PostgresDriver auto-pads all 2D arrays to the same length, by
|
|
117
|
+
* appending `NULL`, and then `unnest_arrays` prunes all `NULL`s from the resulting 1D arrays.
|
|
118
|
+
* See `fillArrayWithNulls`.
|
|
119
|
+
*
|
|
120
|
+
*
|
|
121
|
+
* This means we purposefully do not/cannot support `NULL` values _within_ array columns,
|
|
122
|
+
* i.e. `nick_names=[a, NULL, b]` (without some additional heuristics).
|
|
123
|
+
*
|
|
124
|
+
* That said, we do need to support nullable array columns, i.e. `nick_names=NULL`, which we
|
|
125
|
+
* detect by prefixing an additional marker element to each sub-array that indicates whether the
|
|
126
|
+
* array column is null or not, and then conditionally enabling this with the `nullable=true`
|
|
127
|
+
* param to `unnest_arrays`.
|
|
128
|
+
*/
|
|
129
|
+
const unnest_arrays = `
|
|
140
130
|
CREATE OR REPLACE FUNCTION unnest_arrays(arr ANYARRAY, nullable BOOLEAN = false, OUT a ANYARRAY)
|
|
141
131
|
RETURNS SETOF ANYARRAY
|
|
142
132
|
LANGUAGE plpgsql IMMUTABLE STRICT AS
|
|
@@ -155,82 +145,109 @@ END
|
|
|
155
145
|
$func$;
|
|
156
146
|
`;
|
|
157
147
|
function createUnnestArraysFunction(b) {
|
|
158
|
-
|
|
148
|
+
b.sql(unnest_arrays);
|
|
159
149
|
}
|
|
160
150
|
function createCreatedAtFunction(b) {
|
|
161
|
-
|
|
151
|
+
b.createFunction("trigger_maybe_set_created_at", [], {
|
|
152
|
+
replace: true,
|
|
153
|
+
language: "plpgsql",
|
|
154
|
+
returns: "TRIGGER"
|
|
155
|
+
}, "BEGIN IF NEW.created_at IS NULL THEN NEW.created_at = NOW(); END IF; IF NEW.updated_at IS NULL THEN NEW.updated_at = NOW(); END IF; RETURN NEW; END;");
|
|
162
156
|
}
|
|
163
157
|
function foreignKey(otherTable, opts) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
158
|
+
return {
|
|
159
|
+
type: "integer",
|
|
160
|
+
references: otherTable,
|
|
161
|
+
deferrable: true,
|
|
162
|
+
deferred: true,
|
|
163
|
+
...foreignKeyOptsWithMaybeComment(opts)
|
|
164
|
+
};
|
|
171
165
|
}
|
|
172
166
|
function renameRelation(b, tableName, columnName, opts) {
|
|
173
|
-
|
|
167
|
+
b.alterColumn(tableName, columnName, foreignKeyOptsWithMaybeComment(opts));
|
|
174
168
|
}
|
|
175
169
|
function commentData(data, comment) {
|
|
176
|
-
|
|
170
|
+
return `${comment ?? ""}[pg-structure]${JSON.stringify(data)}[/pg-structure]`;
|
|
177
171
|
}
|
|
178
172
|
function foreignKeyOptsWithMaybeComment(opts) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
let { comment, fieldName, otherFieldName, ...rest } = opts;
|
|
174
|
+
if (fieldName || otherFieldName) {
|
|
175
|
+
const overrides = {
|
|
176
|
+
fieldName,
|
|
177
|
+
otherFieldName
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
...rest,
|
|
181
|
+
comment: commentData(overrides, comment)
|
|
182
|
+
};
|
|
183
|
+
} else if (comment) return {
|
|
184
|
+
...rest,
|
|
185
|
+
comment
|
|
186
|
+
};
|
|
187
|
+
else return rest;
|
|
190
188
|
}
|
|
191
189
|
function enumArrayColumn(enumTable, opts) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
default: node_pg_migrate_1.PgLiteral.create("array[]::integer[]"),
|
|
200
|
-
...opts,
|
|
201
|
-
};
|
|
190
|
+
return {
|
|
191
|
+
type: "integer[]",
|
|
192
|
+
comment: `enum=${enumTable}`,
|
|
193
|
+
notNull: false,
|
|
194
|
+
default: PgLiteral.create("array[]::integer[]"),
|
|
195
|
+
...opts
|
|
196
|
+
};
|
|
202
197
|
}
|
|
203
198
|
function maybeTableOrColumn(maybeTableOrColumn) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
199
|
+
if (typeof maybeTableOrColumn === "string") return [
|
|
200
|
+
maybeTableOrColumn,
|
|
201
|
+
`${singular(maybeTableOrColumn)}_id`,
|
|
202
|
+
void 0
|
|
203
|
+
];
|
|
204
|
+
else {
|
|
205
|
+
const { table, column, collectionName } = maybeTableOrColumn;
|
|
206
|
+
return [
|
|
207
|
+
table,
|
|
208
|
+
column ?? `${singular(table)}_id`,
|
|
209
|
+
collectionName
|
|
210
|
+
];
|
|
211
|
+
}
|
|
211
212
|
}
|
|
212
213
|
function createManyToManyTable(b, tableName, tableOrColumn1, tableOrColumn2, options) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
const [table1, column1, otherFieldName1] = maybeTableOrColumn(tableOrColumn1);
|
|
215
|
+
const [table2, column2, otherFieldName2] = maybeTableOrColumn(tableOrColumn2);
|
|
216
|
+
b.createTable(tableName, {
|
|
217
|
+
id: "id",
|
|
218
|
+
[column1]: foreignKey(table1, {
|
|
219
|
+
notNull: true,
|
|
220
|
+
onDelete: "CASCADE",
|
|
221
|
+
otherFieldName: otherFieldName1
|
|
222
|
+
}),
|
|
223
|
+
[column2]: foreignKey(table2, {
|
|
224
|
+
notNull: true,
|
|
225
|
+
onDelete: "CASCADE",
|
|
226
|
+
otherFieldName: otherFieldName2
|
|
227
|
+
}),
|
|
228
|
+
created_at: {
|
|
229
|
+
type: "timestamptz",
|
|
230
|
+
notNull: true,
|
|
231
|
+
default: b.func("NOW()")
|
|
232
|
+
}
|
|
233
|
+
}, options);
|
|
234
|
+
b.createIndex(tableName, [column1, column2], {
|
|
235
|
+
unique: true,
|
|
236
|
+
ifNotExists: options?.ifNotExists
|
|
237
|
+
});
|
|
238
|
+
b.createIndex(tableName, [column2]);
|
|
223
239
|
}
|
|
224
240
|
/** Adds columns + auto-indexes any foreign keys. */
|
|
225
241
|
function addColumns(b, tableName, columns) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
});
|
|
242
|
+
b.addColumns(tableName, columns);
|
|
243
|
+
Object.entries(columns).forEach(([name, def]) => {
|
|
244
|
+
if (typeof def === "object" && def.references) b.addIndex(tableName, [name], { method: "btree" });
|
|
245
|
+
});
|
|
232
246
|
}
|
|
233
247
|
function fail(message) {
|
|
234
|
-
|
|
248
|
+
throw new Error(message || "Failed");
|
|
235
249
|
}
|
|
250
|
+
//#endregion
|
|
251
|
+
export { addColumns, addEnumValue, commentData, createCreatedAtFunction, createEntityTable, createEnumTable, createManyToManyTable, createSubTable, createTriggers, createUnnestArraysFunction, createUpdatedAtFunction, enumArrayColumn, fail, foreignKey, renameRelation, unnest_arrays, updateEnumValue };
|
|
252
|
+
|
|
236
253
|
//# sourceMappingURL=utils.js.map
|