joist-codegen 0.1.536 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/EntityDbMetadata.d.ts +71 -23
- package/build/EntityDbMetadata.js +229 -63
- package/build/EntityDbMetadata.js.map +1 -1
- package/build/EntityDbMetadata.test.js +5 -5
- package/build/EntityDbMetadata.test.js.map +1 -1
- package/build/assignTags.d.ts +1 -1
- package/build/assignTags.js +2 -2
- package/build/assignTags.js.map +1 -1
- package/build/config.d.ts +8 -1
- package/build/config.js +25 -13
- package/build/config.js.map +1 -1
- package/build/config.test.d.ts +1 -0
- package/build/config.test.js +37 -0
- package/build/config.test.js.map +1 -0
- package/build/generateEntitiesFile.d.ts +2 -1
- package/build/generateEntitiesFile.js +6 -3
- package/build/generateEntitiesFile.js.map +1 -1
- package/build/generateEntityCodegenFile.d.ts +2 -4
- package/build/generateEntityCodegenFile.js +278 -90
- package/build/generateEntityCodegenFile.js.map +1 -1
- package/build/generateEnumFile.d.ts +2 -3
- package/build/generateEnumFile.js +15 -10
- package/build/generateEnumFile.js.map +1 -1
- package/build/generateFactoriesFiles.js +4 -4
- package/build/generateFactoriesFiles.js.map +1 -1
- package/build/generateInitialEntityFile.js +7 -2
- package/build/generateInitialEntityFile.js.map +1 -1
- package/build/generateMetadataFile.js +96 -69
- package/build/generateMetadataFile.js.map +1 -1
- package/build/generatePgEnumFile.d.ts +4 -0
- package/build/generatePgEnumFile.js +18 -0
- package/build/generatePgEnumFile.js.map +1 -0
- package/build/index.d.ts +24 -8
- package/build/index.js +116 -40
- package/build/index.js.map +1 -1
- package/build/symbols.d.ts +62 -51
- package/build/symbols.js +64 -52
- package/build/symbols.js.map +1 -1
- package/build/utils.d.ts +4 -2
- package/build/utils.js +21 -11
- package/build/utils.js.map +1 -1
- package/package.json +26 -14
- package/jest.config.js +0 -10
- package/package.json.bak +0 -28
- package/src/EntityDbMetadata.test.ts +0 -42
- package/src/EntityDbMetadata.ts +0 -322
- package/src/assignTags.ts +0 -45
- package/src/config.ts +0 -82
- package/src/generateEntitiesFile.ts +0 -26
- package/src/generateEntityCodegenFile.ts +0 -414
- package/src/generateEnumFile.ts +0 -63
- package/src/generateFactoriesFiles.ts +0 -29
- package/src/generateInitialEntityFile.ts +0 -12
- package/src/generateMetadataFile.ts +0 -175
- package/src/index.ts +0 -180
- package/src/symbols.ts +0 -53
- package/src/utils.ts +0 -87
- package/tsconfig.json +0 -21
- package/tsconfig.tsbuildinfo +0 -3377
package/build/utils.js
CHANGED
|
@@ -3,10 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.sortKeys = exports.fail = exports.trueIfResolved = exports.merge = exports.
|
|
6
|
+
exports.sortKeys = exports.fail = exports.trueIfResolved = exports.merge = exports.mapSimpleDbTypeToTypescriptType = exports.tableToEntityName = exports.isJoinTable = exports.isEnumTable = exports.isEntityTable = exports.assertNever = void 0;
|
|
7
|
+
const change_case_1 = require("change-case");
|
|
7
8
|
const is_plain_object_1 = __importDefault(require("is-plain-object"));
|
|
8
9
|
const pluralize_1 = __importDefault(require("pluralize"));
|
|
9
|
-
|
|
10
|
+
function assertNever(x) {
|
|
11
|
+
throw new Error("Unexpected object: " + x);
|
|
12
|
+
}
|
|
13
|
+
exports.assertNever = assertNever;
|
|
10
14
|
function isEntityTable(t) {
|
|
11
15
|
const columnNames = t.columns.map((c) => c.name);
|
|
12
16
|
return includesAllOf(columnNames, ["id", "created_at", "updated_at"]);
|
|
@@ -31,35 +35,41 @@ function includesAllOf(set, subset) {
|
|
|
31
35
|
}
|
|
32
36
|
/** Converts `projects` to `Project`. */
|
|
33
37
|
function tableToEntityName(config, table) {
|
|
34
|
-
let entityName = config.__tableToEntityName[table.name];
|
|
38
|
+
let entityName = config.__tableToEntityName?.[table.name];
|
|
35
39
|
if (!entityName) {
|
|
36
40
|
const configEntityName = Object.entries(config.entities)
|
|
37
41
|
.filter(([, conf]) => conf.tableName === table.name)
|
|
38
42
|
.map(([entityName]) => entityName)[0];
|
|
39
|
-
entityName = configEntityName || change_case_1.pascalCase(pluralize_1.default.singular(table.name));
|
|
40
|
-
config.__tableToEntityName[table.name] = entityName;
|
|
43
|
+
entityName = configEntityName || (0, change_case_1.pascalCase)(pluralize_1.default.singular(table.name));
|
|
44
|
+
(config.__tableToEntityName ?? (config.__tableToEntityName = {}))[table.name] = entityName;
|
|
41
45
|
}
|
|
42
46
|
return entityName;
|
|
43
47
|
}
|
|
44
48
|
exports.tableToEntityName = tableToEntityName;
|
|
45
49
|
/** Maps db types, i.e. `int`, to JS types, i.e. `number`. */
|
|
46
|
-
function
|
|
50
|
+
function mapSimpleDbTypeToTypescriptType(dbType) {
|
|
47
51
|
switch (dbType) {
|
|
48
|
-
case "
|
|
52
|
+
case "boolean":
|
|
49
53
|
return "boolean";
|
|
50
54
|
case "int":
|
|
55
|
+
case "numeric":
|
|
51
56
|
return "number";
|
|
52
57
|
case "text":
|
|
58
|
+
case "citext":
|
|
59
|
+
case "character varying":
|
|
53
60
|
case "varchar":
|
|
61
|
+
case "uuid":
|
|
54
62
|
return "string";
|
|
55
|
-
case "
|
|
63
|
+
case "timestamp with time zone":
|
|
56
64
|
case "date":
|
|
57
65
|
return "Date";
|
|
66
|
+
case "jsonb":
|
|
67
|
+
return "Object";
|
|
58
68
|
default:
|
|
59
|
-
|
|
69
|
+
assertNever(dbType);
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
|
-
exports.
|
|
72
|
+
exports.mapSimpleDbTypeToTypescriptType = mapSimpleDbTypeToTypescriptType;
|
|
63
73
|
function merge(a, b) {
|
|
64
74
|
return [...a, ...b];
|
|
65
75
|
}
|
|
@@ -78,7 +88,7 @@ function sortKeys(o) {
|
|
|
78
88
|
.sort()
|
|
79
89
|
.reduce((acc, key) => {
|
|
80
90
|
const value = o[key];
|
|
81
|
-
const newValue = typeof value === "object" && is_plain_object_1.default(value) ? sortKeys(value) : value;
|
|
91
|
+
const newValue = typeof value === "object" && (0, is_plain_object_1.default)(value) ? sortKeys(value) : value;
|
|
82
92
|
acc[key] = newValue;
|
|
83
93
|
return acc;
|
|
84
94
|
}, {});
|
package/build/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAAA,sEAA4C;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAAA,6CAAyC;AACzC,sEAA4C;AAE5C,0DAAkC;AAIlC,SAAgB,WAAW,CAAC,CAAQ;IAClC,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC;AAFD,kCAEC;AAED,SAAgB,aAAa,CAAC,CAAQ;IACpC,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,aAAa,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC,CAAC;AACxE,CAAC;AAHD,sCAGC;AAED,SAAgB,WAAW,CAAC,CAAQ;IAClC,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,aAAa,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AACjF,CAAC;AAHD,kCAGC;AAED,SAAgB,WAAW,CAAC,CAAQ;IAClC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACtB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IACrE,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IAC7C,MAAM,4BAA4B,GAChC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;IACtF,OAAO,QAAQ,IAAI,SAAS,IAAI,CAAC,eAAe,IAAI,4BAA4B,CAAC,CAAC;AACpF,CAAC;AARD,kCAQC;AAED,SAAS,aAAa,CAAC,GAAa,EAAE,MAAgB;IACpD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;AAC5D,CAAC;AAED,wCAAwC;AACxC,SAAgB,iBAAiB,CAAC,MAAc,EAAE,KAAY;IAC5D,IAAI,UAAU,GAAG,MAAM,CAAC,mBAAmB,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,CAAC,UAAU,EAAE;QACf,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;aACrD,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,IAAI,CAAC;aACnD,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,UAAU,GAAG,gBAAgB,IAAI,IAAA,wBAAU,EAAC,mBAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5E,CAAC,MAAM,CAAC,mBAAmB,KAA1B,MAAM,CAAC,mBAAmB,GAAK,EAAE,EAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC;KAC9D;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAVD,8CAUC;AAED,6DAA6D;AAC7D,SAAgB,+BAA+B,CAAC,MAA0B;IACxE,QAAQ,MAAM,EAAE;QACd,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,KAAK,CAAC;QACX,KAAK,SAAS;YACZ,OAAO,QAAQ,CAAC;QAClB,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,mBAAmB,CAAC;QACzB,KAAK,SAAS,CAAC;QACf,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC;QAClB,KAAK,0BAA0B,CAAC;QAChC,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,OAAO;YACV,OAAO,QAAQ,CAAC;QAClB;YACE,WAAW,CAAC,MAAM,CAAC,CAAC;KACvB;AACH,CAAC;AArBD,0EAqBC;AAED,SAAgB,KAAK,CAAI,CAAM,EAAE,CAAM;IACrC,OAAO,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AACtB,CAAC;AAFD,sBAEC;AAED,0EAA0E;AACnE,KAAK,UAAU,cAAc,CAAC,CAAmB;IACtD,OAAO,CAAC,CAAC,IAAI,CACX,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACZ,CAAC;AACJ,CAAC;AALD,wCAKC;AAED,SAAgB,IAAI,CAAC,OAAgB;IACnC,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,QAAQ,CAAC,CAAC;AACvC,CAAC;AAFD,oBAEC;AAED,SAAgB,QAAQ,CAAmB,CAAI;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;SAClB,IAAI,EAAE;SACN,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,KAAK,GAAG,CAAC,CAAC,GAAc,CAAC,CAAC;QAChC,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAA,yBAAa,EAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAsB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC9G,GAAG,CAAC,GAAc,CAAC,GAAG,QAAe,CAAC;QACtC,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,EAAc,CAAC,CAAC;AACvB,CAAC;AATD,4BASC"}
|
package/package.json
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joist-codegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/stephenh/joist-ts.git",
|
|
8
|
+
"directory": "packages/codegen"
|
|
9
|
+
},
|
|
5
10
|
"main": "build/index.js",
|
|
6
11
|
"types": "build/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"format": "prettier --write '{schema,migrations,src}/**/*.{ts,js,tsx,jsx,graphql}'"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"build"
|
|
17
|
+
],
|
|
7
18
|
"dependencies": {
|
|
8
19
|
"@types/pluralize": "0.0.29",
|
|
9
20
|
"change-case": "^4.1.1",
|
|
10
|
-
"knex": "^0.21.4",
|
|
11
21
|
"is-plain-object": "^3.0.1",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
22
|
+
"joist-utils": "1.0.0-bump",
|
|
23
|
+
"knex": "^0.95.9",
|
|
24
|
+
"pg": "^8.7.1",
|
|
25
|
+
"pg-structure": "^7.13.0",
|
|
14
26
|
"pluralize": "^8.0.0",
|
|
15
|
-
"ts-poet": "^
|
|
16
|
-
"joist-utils": "0.1.536"
|
|
27
|
+
"ts-poet": "^4.8.0"
|
|
17
28
|
},
|
|
18
29
|
"devDependencies": {
|
|
19
|
-
"@
|
|
20
|
-
"@types/
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
30
|
+
"@swc/jest": "^0.2.16",
|
|
31
|
+
"@types/jest": "^26.0.24",
|
|
32
|
+
"@types/node": "^16.4.10",
|
|
33
|
+
"jest": "^27.0.6",
|
|
34
|
+
"prettier": "^2.5.1",
|
|
35
|
+
"prettier-plugin-organize-imports": "^2.3.4",
|
|
36
|
+
"ts-node-dev": "^1.1.8",
|
|
37
|
+
"tsconfig-paths": "^3.10.1",
|
|
38
|
+
"typescript": "^4.5.2"
|
|
27
39
|
}
|
|
28
40
|
}
|
package/jest.config.js
DELETED
package/package.json.bak
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "joist-codegen",
|
|
3
|
-
"version": "0.1.0-bump",
|
|
4
|
-
"license": "MIT",
|
|
5
|
-
"main": "build/index.js",
|
|
6
|
-
"types": "build/index.d.ts",
|
|
7
|
-
"dependencies": {
|
|
8
|
-
"@types/pluralize": "0.0.29",
|
|
9
|
-
"change-case": "^4.1.1",
|
|
10
|
-
"knex": "^0.21.4",
|
|
11
|
-
"is-plain-object": "^3.0.1",
|
|
12
|
-
"pg": "^8.3.0",
|
|
13
|
-
"pg-structure": "^5.10.13",
|
|
14
|
-
"pluralize": "^8.0.0",
|
|
15
|
-
"ts-poet": "^3.2.2",
|
|
16
|
-
"joist-utils": "0.1.0-bump"
|
|
17
|
-
},
|
|
18
|
-
"devDependencies": {
|
|
19
|
-
"@types/jest": "^25.2.1",
|
|
20
|
-
"@types/node": "^13.13.4",
|
|
21
|
-
"jest": "^26.1.0",
|
|
22
|
-
"prettier": "^2.0.5",
|
|
23
|
-
"ts-jest": "^26.1.1",
|
|
24
|
-
"ts-node-dev": "^1.0.0-pre.50",
|
|
25
|
-
"tsconfig-paths": "^3.9.0",
|
|
26
|
-
"typescript": "^3.9.5"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { defaultConfig } from "./config";
|
|
2
|
-
import { collectionName, EntityDbMetadata, makeEntity } from "./EntityDbMetadata";
|
|
3
|
-
import { tableToEntityName } from "./utils";
|
|
4
|
-
|
|
5
|
-
const relationDummy: any = { targetTable: { m2oRelations: [] } };
|
|
6
|
-
const configDummy: any = { relationNameOverrides: {} };
|
|
7
|
-
|
|
8
|
-
describe("EntityDbMetadata", () => {
|
|
9
|
-
describe("tableToEntityName", () => {
|
|
10
|
-
it("singularizes the table names", () => {
|
|
11
|
-
expect(tableToEntityName(defaultConfig, { name: "authors" } as any)).toEqual("Author");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("uses config for the table names", () => {
|
|
15
|
-
const config = {
|
|
16
|
-
...defaultConfig,
|
|
17
|
-
entities: { Author: { tag: "a", tableName: "TBL_ATHR" } },
|
|
18
|
-
};
|
|
19
|
-
expect(tableToEntityName(config, { name: "TBL_ATHR" } as any)).toEqual("Author");
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("collectionName", () => {
|
|
24
|
-
it("handles base case", () => {
|
|
25
|
-
expect(collectionName(configDummy, makeEntity("Author"), makeEntity("Book"), relationDummy).fieldName).toEqual(
|
|
26
|
-
"books",
|
|
27
|
-
);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("handles author/mentor", () => {
|
|
31
|
-
expect(collectionName(configDummy, makeEntity("Author"), makeEntity("Author"), relationDummy).fieldName).toEqual(
|
|
32
|
-
"authors",
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("handles book/book review", () => {
|
|
37
|
-
expect(
|
|
38
|
-
collectionName(configDummy, makeEntity("Book"), makeEntity("BookReview"), relationDummy).fieldName,
|
|
39
|
-
).toEqual("reviews");
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
});
|
package/src/EntityDbMetadata.ts
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { camelCase } from "change-case";
|
|
2
|
-
import { Column, M2MRelation, M2ORelation, O2MRelation, Table } from "pg-structure";
|
|
3
|
-
import { plural, singular } from "pluralize";
|
|
4
|
-
import { imp } from "ts-poet";
|
|
5
|
-
import { SymbolSpec } from "ts-poet/build/SymbolSpecs";
|
|
6
|
-
import { Config, isAsyncDerived, isDerived, isProtected, ormMaintainedFields, relationName } from "./config";
|
|
7
|
-
import { ColumnMetaData } from "./generateEntityCodegenFile";
|
|
8
|
-
import { isEnumTable, isJoinTable, mapSimpleDbType, tableToEntityName } from "./utils";
|
|
9
|
-
|
|
10
|
-
// TODO Populate from config
|
|
11
|
-
const columnCustomizations: Record<string, ColumnMetaData> = {};
|
|
12
|
-
|
|
13
|
-
/** Codegen-time metadata about a given domain entity. */
|
|
14
|
-
export type Entity = {
|
|
15
|
-
name: string;
|
|
16
|
-
/** The symbol pointing to the entity itself. */
|
|
17
|
-
type: SymbolSpec;
|
|
18
|
-
/** The name of the entity's runtime metadata const. */
|
|
19
|
-
metaName: string;
|
|
20
|
-
/** The symbol pointing to the entity's runtime metadata const. */
|
|
21
|
-
metaType: SymbolSpec;
|
|
22
|
-
/** The symbol pointing to the entity's EntityId type. */
|
|
23
|
-
idType: SymbolSpec;
|
|
24
|
-
/** The symbol pointing to the entity's Order type. */
|
|
25
|
-
orderType: SymbolSpec;
|
|
26
|
-
/** The symbol pointing to the entity's config const. */
|
|
27
|
-
configConst: SymbolSpec;
|
|
28
|
-
optsType: SymbolSpec;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type PrimitiveField = {
|
|
32
|
-
fieldName: string;
|
|
33
|
-
columnName: string;
|
|
34
|
-
columnType: string;
|
|
35
|
-
columnDefault: number | boolean | string | null;
|
|
36
|
-
fieldType: string | SymbolSpec;
|
|
37
|
-
notNull: boolean;
|
|
38
|
-
derived: "orm" | "sync" | "async" | false;
|
|
39
|
-
protected: boolean;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export type EnumField = {
|
|
43
|
-
fieldName: string;
|
|
44
|
-
columnName: string;
|
|
45
|
-
enumName: string;
|
|
46
|
-
enumType: SymbolSpec;
|
|
47
|
-
enumDetailType: SymbolSpec;
|
|
48
|
-
notNull: boolean;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** I.e. a `Book.author` reference pointing to an `Author`. */
|
|
52
|
-
export type ManyToOneField = {
|
|
53
|
-
fieldName: string;
|
|
54
|
-
columnName: string;
|
|
55
|
-
otherFieldName: string;
|
|
56
|
-
otherEntity: Entity;
|
|
57
|
-
notNull: boolean;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/** I.e. a `Author.books` collection. */
|
|
61
|
-
export type OneToManyField = {
|
|
62
|
-
fieldName: string;
|
|
63
|
-
singularName: string;
|
|
64
|
-
otherEntity: Entity;
|
|
65
|
-
otherFieldName: string;
|
|
66
|
-
otherColumnName: string;
|
|
67
|
-
otherColumnNotNull: boolean;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/** I.e. a `Author.image` reference when `image.author_id` is unique. */
|
|
71
|
-
export type OneToOneField = {
|
|
72
|
-
fieldName: string;
|
|
73
|
-
otherEntity: Entity;
|
|
74
|
-
otherFieldName: string;
|
|
75
|
-
otherColumnName: string;
|
|
76
|
-
otherColumnNotNull: boolean;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export type ManyToManyField = {
|
|
80
|
-
joinTableName: string;
|
|
81
|
-
fieldName: string;
|
|
82
|
-
singularName: string;
|
|
83
|
-
columnName: string;
|
|
84
|
-
otherEntity: Entity;
|
|
85
|
-
otherFieldName: string;
|
|
86
|
-
otherColumnName: string;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
/** Adapts the generally-great pg-structure metadata into our specific ORM types. */
|
|
90
|
-
export class EntityDbMetadata {
|
|
91
|
-
entity: Entity;
|
|
92
|
-
primitives: PrimitiveField[];
|
|
93
|
-
enums: EnumField[];
|
|
94
|
-
manyToOnes: ManyToOneField[];
|
|
95
|
-
oneToManys: OneToManyField[];
|
|
96
|
-
oneToOnes: OneToOneField[];
|
|
97
|
-
manyToManys: ManyToManyField[];
|
|
98
|
-
tableName: string;
|
|
99
|
-
|
|
100
|
-
constructor(config: Config, table: Table) {
|
|
101
|
-
this.entity = makeEntity(tableToEntityName(config, table));
|
|
102
|
-
this.primitives = table.columns
|
|
103
|
-
.filter((c) => !c.isPrimaryKey && !c.isForeignKey)
|
|
104
|
-
.map((column) => newPrimitive(config, this.entity, column, table));
|
|
105
|
-
this.enums = table.m2oRelations.filter((r) => isEnumTable(r.targetTable)).map((r) => newEnumField(config, r));
|
|
106
|
-
this.manyToOnes = table.m2oRelations
|
|
107
|
-
.filter((r) => !isEnumTable(r.targetTable))
|
|
108
|
-
.filter((r) => !isMultiColumnForeignKey(r))
|
|
109
|
-
.map((r) => newManyToOneField(config, this.entity, r));
|
|
110
|
-
this.oneToManys = table.o2mRelations
|
|
111
|
-
// ManyToMany join tables also show up as OneToMany tables in pg-structure
|
|
112
|
-
.filter((r) => !isJoinTable(r.targetTable))
|
|
113
|
-
.filter((r) => !isMultiColumnForeignKey(r))
|
|
114
|
-
.filter((r) => !isOneToOneRelation(r))
|
|
115
|
-
.map((r) => newOneToMany(config, this.entity, r));
|
|
116
|
-
this.oneToOnes = table.o2mRelations
|
|
117
|
-
// ManyToMany join tables also show up as OneToMany tables in pg-structure
|
|
118
|
-
.filter((r) => !isJoinTable(r.targetTable))
|
|
119
|
-
.filter((r) => !isMultiColumnForeignKey(r))
|
|
120
|
-
.filter((r) => isOneToOneRelation(r))
|
|
121
|
-
.map((r) => newOneToOne(config, this.entity, r));
|
|
122
|
-
this.manyToManys = table.m2mRelations
|
|
123
|
-
// pg-structure is really loose on what it considers a m2m relationship, i.e. any entity
|
|
124
|
-
// that has a foreign key to us, and a foreign key to something else, is automatically
|
|
125
|
-
// considered as a join table/m2m between "us" and "something else". Filter these out
|
|
126
|
-
// by looking for only true join tables, i.e. tables with only id, fk1, and fk2.
|
|
127
|
-
.filter((r) => isJoinTable(r.joinTable))
|
|
128
|
-
.filter((r) => !isMultiColumnForeignKey(r))
|
|
129
|
-
.map((r) => newManyToManyField(config, this.entity, r));
|
|
130
|
-
this.tableName = table.name;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
get name(): string {
|
|
134
|
-
return this.entity.name;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function isMultiColumnForeignKey(r: M2ORelation) {
|
|
139
|
-
return r.foreignKey.columns.length > 1;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function isOneToOneRelation(r: O2MRelation) {
|
|
143
|
-
// otherColumn will be the images.book_id in the other table
|
|
144
|
-
const otherColumn = r.foreignKey.columns[0];
|
|
145
|
-
// r.foreignKey.index is the index on _us_ (i.e. our Book primary key), so look up indexes in the target table
|
|
146
|
-
const indexes = r.targetTable.columns.find((c) => c.name == otherColumn.name)?.uniqueIndexes || [];
|
|
147
|
-
// If the column is the only column in an unique index, it's a one-to-one
|
|
148
|
-
return indexes.find((i) => i.columns.length === 1) !== undefined;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function newPrimitive(config: Config, entity: Entity, column: Column, table: Table): PrimitiveField {
|
|
152
|
-
const fieldName = camelCase(column.name);
|
|
153
|
-
const columnName = column.name;
|
|
154
|
-
return {
|
|
155
|
-
fieldName,
|
|
156
|
-
columnName,
|
|
157
|
-
columnType: column.type.shortName || column.type.name,
|
|
158
|
-
fieldType: mapType(table.name, columnName, column.type.shortName || column.type.name).fieldType,
|
|
159
|
-
notNull: column.notNull,
|
|
160
|
-
columnDefault: column.default,
|
|
161
|
-
derived: fieldDerived(config, entity, fieldName),
|
|
162
|
-
protected: isProtected(config, entity, fieldName),
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function fieldDerived(config: Config, entity: Entity, fieldName: string): PrimitiveField["derived"] {
|
|
167
|
-
if (ormMaintainedFields.includes(fieldName)) {
|
|
168
|
-
return "orm";
|
|
169
|
-
} else if (isDerived(config, entity, fieldName)) {
|
|
170
|
-
return "sync";
|
|
171
|
-
} else if (isAsyncDerived(config, entity, fieldName)) {
|
|
172
|
-
return "async";
|
|
173
|
-
} else {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function newEnumField(config: Config, r: M2ORelation): EnumField {
|
|
179
|
-
const column = r.foreignKey.columns[0];
|
|
180
|
-
const columnName = column.name;
|
|
181
|
-
const fieldName = camelCase(column.name.replace("_id", ""));
|
|
182
|
-
const enumName = tableToEntityName(config, r.targetTable);
|
|
183
|
-
const enumType = imp(`${enumName}@./entities`);
|
|
184
|
-
const enumDetailType = imp(`${plural(enumName)}@./entities`);
|
|
185
|
-
const notNull = column.notNull;
|
|
186
|
-
return { fieldName, columnName, enumName, enumType, enumDetailType, notNull };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function newManyToOneField(config: Config, entity: Entity, r: M2ORelation): ManyToOneField {
|
|
190
|
-
const column = r.foreignKey.columns[0];
|
|
191
|
-
const columnName = column.name;
|
|
192
|
-
const fieldName = referenceName(config, entity, r);
|
|
193
|
-
const otherEntity = makeEntity(tableToEntityName(config, r.targetTable));
|
|
194
|
-
const isOneToOne = column.uniqueIndexes.find((i) => i.columns.length === 1) !== undefined;
|
|
195
|
-
const otherFieldName = isOneToOne
|
|
196
|
-
? oneToOneName(config, otherEntity, entity)
|
|
197
|
-
: collectionName(config, otherEntity, entity, r).fieldName;
|
|
198
|
-
const notNull = column.notNull;
|
|
199
|
-
return { fieldName, columnName, otherEntity, otherFieldName, notNull };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function newOneToMany(config: Config, entity: Entity, r: O2MRelation): OneToManyField {
|
|
203
|
-
const column = r.foreignKey.columns[0];
|
|
204
|
-
// source == parent i.e. the reference of the foreign key column
|
|
205
|
-
// target == child i.e. the table with the foreign key column in it
|
|
206
|
-
const otherEntity = makeEntity(tableToEntityName(config, r.targetTable));
|
|
207
|
-
const { singularName, fieldName } = collectionName(config, entity, otherEntity, r);
|
|
208
|
-
const otherFieldName = referenceName(config, otherEntity, r);
|
|
209
|
-
const otherColumnName = column.name;
|
|
210
|
-
const otherColumnNotNull = column.notNull;
|
|
211
|
-
return { fieldName, singularName, otherEntity, otherFieldName, otherColumnName, otherColumnNotNull };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function newOneToOne(config: Config, entity: Entity, r: O2MRelation): OneToOneField {
|
|
215
|
-
const column = r.foreignKey.columns[0];
|
|
216
|
-
// source == parent i.e. the reference of the foreign key column
|
|
217
|
-
// target == child i.e. the table with the foreign key column in it
|
|
218
|
-
const otherEntity = makeEntity(tableToEntityName(config, r.targetTable));
|
|
219
|
-
const fieldName = oneToOneName(config, entity, otherEntity);
|
|
220
|
-
const otherFieldName = referenceName(config, otherEntity, r);
|
|
221
|
-
const otherColumnName = column.name;
|
|
222
|
-
const otherColumnNotNull = column.notNull;
|
|
223
|
-
return { fieldName, otherEntity, otherFieldName, otherColumnName, otherColumnNotNull };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function newManyToManyField(config: Config, entity: Entity, r: M2MRelation): ManyToManyField {
|
|
227
|
-
const { foreignKey, targetForeignKey, targetTable } = r;
|
|
228
|
-
const joinTableName = r.joinTable.name;
|
|
229
|
-
const otherEntity = makeEntity(tableToEntityName(config, targetTable));
|
|
230
|
-
const fieldName = relationName(
|
|
231
|
-
config,
|
|
232
|
-
entity,
|
|
233
|
-
camelCase(plural(targetForeignKey.columns[0].name.replace("_id", ""))),
|
|
234
|
-
);
|
|
235
|
-
const otherFieldName = relationName(
|
|
236
|
-
config,
|
|
237
|
-
otherEntity,
|
|
238
|
-
camelCase(plural(foreignKey.columns[0].name.replace("_id", ""))),
|
|
239
|
-
);
|
|
240
|
-
const columnName = foreignKey.columns[0].name;
|
|
241
|
-
const otherColumnName = targetForeignKey.columns[0].name;
|
|
242
|
-
const singularName = singular(fieldName);
|
|
243
|
-
return { joinTableName, fieldName, singularName, columnName, otherEntity, otherFieldName, otherColumnName };
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export function oneToOneName(config: Config, entity: Entity, otherEntity: Entity): string {
|
|
247
|
-
return relationName(config, entity, camelCase(otherEntity.name));
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export function referenceName(config: Config, entity: Entity, r: M2ORelation | O2MRelation): string {
|
|
251
|
-
const column = r.foreignKey.columns[0];
|
|
252
|
-
const fieldName = camelCase(column.name.replace("_id", ""));
|
|
253
|
-
return relationName(config, entity, fieldName);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/** Returns the collection name to use on `entity` when referring to `otherEntity`s. */
|
|
257
|
-
export function collectionName(
|
|
258
|
-
config: Config,
|
|
259
|
-
entity: Entity,
|
|
260
|
-
otherEntity: Entity,
|
|
261
|
-
r: M2ORelation | O2MRelation,
|
|
262
|
-
): { fieldName: string; singularName: string } {
|
|
263
|
-
// TODO Handle conflicts in names
|
|
264
|
-
// I.e. if the other side is `child.project_id`, use `children`.
|
|
265
|
-
let fieldName = otherEntity.name;
|
|
266
|
-
// check if we have multiple FKs from otherEntity --> entity and prefix with FK name if so
|
|
267
|
-
const sourceTable = r instanceof M2ORelation ? r.sourceTable : r.targetTable;
|
|
268
|
-
const targetTable = r instanceof M2ORelation ? r.targetTable : r.sourceTable;
|
|
269
|
-
if (sourceTable.m2oRelations.filter((r) => r.targetTable === targetTable).length > 1) {
|
|
270
|
-
fieldName = `${r.foreignKey.columns[0].name.replace("_id", "")}_${fieldName}`;
|
|
271
|
-
}
|
|
272
|
-
// If the other side is `book_reviews.book_id`, use `reviews`.
|
|
273
|
-
if (fieldName.length > entity.name.length) {
|
|
274
|
-
fieldName = fieldName.replace(entity.name, "");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// camelize the name
|
|
278
|
-
let singularName = camelCase(fieldName);
|
|
279
|
-
fieldName = camelCase(plural(fieldName));
|
|
280
|
-
|
|
281
|
-
// If the name is overridden, use that, but also singularize it
|
|
282
|
-
const maybeOverriddenName = relationName(config, entity, fieldName);
|
|
283
|
-
if (maybeOverriddenName !== fieldName) {
|
|
284
|
-
singularName = singular(maybeOverriddenName);
|
|
285
|
-
fieldName = maybeOverriddenName;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return { singularName, fieldName };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function makeEntity(entityName: string): Entity {
|
|
292
|
-
return {
|
|
293
|
-
name: entityName,
|
|
294
|
-
type: entityType(entityName),
|
|
295
|
-
metaName: metaName(entityName),
|
|
296
|
-
metaType: metaType(entityName),
|
|
297
|
-
idType: imp(`${entityName}Id@./entities`, { definedIn: `./${entityName}Codegen` }),
|
|
298
|
-
orderType: imp(`${entityName}Order@./entities`, { definedIn: `./${entityName}Codegen` }),
|
|
299
|
-
optsType: imp(`${entityName}Opts@./entities`, { definedIn: `./${entityName}Codegen` }),
|
|
300
|
-
configConst: imp(`${camelCase(entityName)}Config@./entities`, { definedIn: `./${entityName}Codegen` }),
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function metaName(entityName: string): string {
|
|
305
|
-
return `${camelCase(entityName)}Meta`;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function metaType(entityName: string): SymbolSpec {
|
|
309
|
-
return imp(`${metaName(entityName)}@./entities`);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function entityType(entityName: string): SymbolSpec {
|
|
313
|
-
return imp(`${entityName}@./entities`);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function mapType(tableName: string, columnName: string, dbColumnType: string): ColumnMetaData {
|
|
317
|
-
return (
|
|
318
|
-
columnCustomizations[`${tableName}.${columnName}`] || {
|
|
319
|
-
fieldType: mapSimpleDbType(dbColumnType),
|
|
320
|
-
}
|
|
321
|
-
);
|
|
322
|
-
}
|
package/src/assignTags.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { snakeCase, camelCase } from "change-case";
|
|
2
|
-
import { DbMetadata } from "./index";
|
|
3
|
-
import { Config } from "./config";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Looks for any entities that don't have tags in `config` yet, and guesses at what a good tag would be.
|
|
7
|
-
*
|
|
8
|
-
* The current guess is `BookReview` -> `br`. If that guess is already taken, we use the full entity name,
|
|
9
|
-
* i.e. `bookReview`. The user can then customize the tags as they want directly in `joist-codegen.json`.
|
|
10
|
-
*
|
|
11
|
-
* We also mutate `config` by putting the new tag into the `config`'s entity entry.
|
|
12
|
-
*/
|
|
13
|
-
export function assignTags(config: Config, dbMetadata: DbMetadata): void {
|
|
14
|
-
const existingTags = Object.fromEntries(
|
|
15
|
-
Object.entries(config.entities).map(([name, conf]) => {
|
|
16
|
-
return [name, conf.tag];
|
|
17
|
-
}),
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
const existingTagNames = Object.values(existingTags);
|
|
21
|
-
|
|
22
|
-
dbMetadata.entities
|
|
23
|
-
.filter((e) => !existingTags[e.name])
|
|
24
|
-
.forEach((e) => {
|
|
25
|
-
const abbreviatedTag = guessTagName(e.name);
|
|
26
|
-
const tagName = existingTagNames.includes(abbreviatedTag) ? camelCase(e.name) : abbreviatedTag;
|
|
27
|
-
const oc = config.entities[e.name];
|
|
28
|
-
if (!oc) {
|
|
29
|
-
config.entities[e.name] = { tag: tagName };
|
|
30
|
-
} else {
|
|
31
|
-
oc.tag = tagName;
|
|
32
|
-
}
|
|
33
|
-
existingTagNames.push(tagName);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
// TODO ensure tags are unique
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Abbreviates `BookReview` -> `book_review` -> `br`. */
|
|
40
|
-
function guessTagName(name: string): string {
|
|
41
|
-
return snakeCase(name)
|
|
42
|
-
.split("_")
|
|
43
|
-
.map((w) => w[0])
|
|
44
|
-
.join("");
|
|
45
|
-
}
|