joist-codegen 0.1.538 → 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 +20 -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 -88
- package/tsconfig.json +0 -21
- package/tsconfig.tsbuildinfo +0 -3377
package/src/config.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Entity } from "EntityDbMetadata";
|
|
2
|
-
import { promises as fs } from "fs";
|
|
3
|
-
import prettier, { resolveConfig } from "prettier";
|
|
4
|
-
import { sortKeys, trueIfResolved } from "./utils";
|
|
5
|
-
|
|
6
|
-
export interface FieldConfig {
|
|
7
|
-
derived?: "sync" | "async";
|
|
8
|
-
protected?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface RelationConfig {
|
|
12
|
-
name?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface EntityConfig {
|
|
16
|
-
tag: string;
|
|
17
|
-
tableName?: string;
|
|
18
|
-
fields?: Record<string, FieldConfig>;
|
|
19
|
-
relations?: Record<string, RelationConfig>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface Config {
|
|
23
|
-
contextType?: string;
|
|
24
|
-
entitiesDirectory: string;
|
|
25
|
-
codegenPlugins: string[];
|
|
26
|
-
entities: Record<string, EntityConfig>;
|
|
27
|
-
// We don't persist this, and instead just use it as a cache
|
|
28
|
-
__tableToEntityName: Record<string, string>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const defaultConfig: Config = {
|
|
32
|
-
entitiesDirectory: "./src/entities",
|
|
33
|
-
codegenPlugins: [],
|
|
34
|
-
entities: {},
|
|
35
|
-
__tableToEntityName: {},
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export const ormMaintainedFields = ["createdAt", "updatedAt"];
|
|
39
|
-
|
|
40
|
-
export function isDerived(config: Config, entity: Entity, fieldName: string): boolean {
|
|
41
|
-
return config.entities[entity.name]?.fields?.[fieldName]?.derived === "sync";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function isAsyncDerived(config: Config, entity: Entity, fieldName: string): boolean {
|
|
45
|
-
return config.entities[entity.name]?.fields?.[fieldName]?.derived === "async";
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function isProtected(config: Config, entity: Entity, fieldName: string): boolean {
|
|
49
|
-
return config.entities[entity.name]?.fields?.[fieldName]?.protected === true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function relationName(config: Config, entity: Entity, relationName: string): string {
|
|
53
|
-
return config.entities?.[entity.name]?.relations?.[relationName]?.name ?? relationName;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const configPath = "./joist-codegen.json";
|
|
57
|
-
export async function loadConfig(): Promise<Config> {
|
|
58
|
-
const exists = await trueIfResolved(fs.access(configPath));
|
|
59
|
-
if (exists) {
|
|
60
|
-
const content = await fs.readFile(configPath);
|
|
61
|
-
return {
|
|
62
|
-
...defaultConfig,
|
|
63
|
-
...(JSON.parse(content.toString()) as Config),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return defaultConfig;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Writes the potentially-updated config entry back to `joist-codegen.json`.
|
|
71
|
-
*
|
|
72
|
-
* We format the output with prettier so it should both a) look nice and b) be deterministic,
|
|
73
|
-
* such that no changes to the config show up as noops to the scm.
|
|
74
|
-
*/
|
|
75
|
-
export async function writeConfig(config: Config): Promise<void> {
|
|
76
|
-
const prettierConfig = await resolveConfig("./");
|
|
77
|
-
const sorted = sortKeys(config);
|
|
78
|
-
delete sorted.__tableToEntityName;
|
|
79
|
-
const input = JSON.stringify(sorted);
|
|
80
|
-
const content = prettier.format(input.trim(), { parser: "json", ...prettierConfig });
|
|
81
|
-
await fs.writeFile(configPath, content);
|
|
82
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Table } from "pg-structure";
|
|
2
|
-
import { code, Code } from "ts-poet";
|
|
3
|
-
import { Config } from "./config";
|
|
4
|
-
import { EntityDbMetadata } from "./EntityDbMetadata";
|
|
5
|
-
import { tableToEntityName } from "./utils";
|
|
6
|
-
|
|
7
|
-
export function generateEntitiesFile(config: Config, entities: EntityDbMetadata[], enums: Table[]): Code {
|
|
8
|
-
return code`
|
|
9
|
-
// organize-imports-ignore
|
|
10
|
-
|
|
11
|
-
// This file drives our import order to avoid undefined errors
|
|
12
|
-
// when the subclasses extend the base classes, see:
|
|
13
|
-
// https://medium.com/visual-development/how-to-fix-nasty-circular-dependency-issues-once-and-for-all-in-javascript-typescript-a04c987cf0de
|
|
14
|
-
${enums.map((table) => {
|
|
15
|
-
return `export * from "./${tableToEntityName(config, table)}";`;
|
|
16
|
-
})}
|
|
17
|
-
${entities.map((meta) => {
|
|
18
|
-
return `export * from "./${meta.entity.name}Codegen";`;
|
|
19
|
-
})}
|
|
20
|
-
${entities.map((meta) => {
|
|
21
|
-
return `export * from "./${meta.entity.name}";`;
|
|
22
|
-
})}
|
|
23
|
-
export * from "./factories";
|
|
24
|
-
export * from "./metadata";
|
|
25
|
-
`;
|
|
26
|
-
}
|
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
import { camelCase, pascalCase } from "change-case";
|
|
2
|
-
import { code, Code, imp } from "ts-poet";
|
|
3
|
-
import { SymbolSpec } from "ts-poet/build/SymbolSpecs";
|
|
4
|
-
import { Config } from "./config";
|
|
5
|
-
import { EntityDbMetadata, PrimitiveField } from "./EntityDbMetadata";
|
|
6
|
-
import {
|
|
7
|
-
BaseEntity,
|
|
8
|
-
BooleanFilter,
|
|
9
|
-
BooleanGraphQLFilter,
|
|
10
|
-
Changes,
|
|
11
|
-
Collection,
|
|
12
|
-
ConfigApi,
|
|
13
|
-
EntityFilter,
|
|
14
|
-
EntityGraphQLFilter,
|
|
15
|
-
EntityManager,
|
|
16
|
-
EnumGraphQLFilter,
|
|
17
|
-
FilterOf,
|
|
18
|
-
Flavor,
|
|
19
|
-
getEm,
|
|
20
|
-
GraphQLFilterOf,
|
|
21
|
-
hasMany,
|
|
22
|
-
hasManyToMany,
|
|
23
|
-
hasOne,
|
|
24
|
-
hasOneToOne,
|
|
25
|
-
Lens,
|
|
26
|
-
Loaded,
|
|
27
|
-
LoadHint,
|
|
28
|
-
loadLens,
|
|
29
|
-
newChangesProxy,
|
|
30
|
-
newRequiredRule,
|
|
31
|
-
OptsOf,
|
|
32
|
-
OrderBy,
|
|
33
|
-
PartialOrNull,
|
|
34
|
-
Reference,
|
|
35
|
-
setField,
|
|
36
|
-
setOpts,
|
|
37
|
-
ValueFilter,
|
|
38
|
-
ValueGraphQLFilter,
|
|
39
|
-
} from "./symbols";
|
|
40
|
-
|
|
41
|
-
export interface ColumnMetaData {
|
|
42
|
-
typeConverter?: SymbolSpec;
|
|
43
|
-
fieldType: SymbolSpec | string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Creates the base class with the boilerplate annotations. */
|
|
47
|
-
export function generateEntityCodegenFile(config: Config, meta: EntityDbMetadata): Code {
|
|
48
|
-
const { entity } = meta;
|
|
49
|
-
const entityName = entity.name;
|
|
50
|
-
|
|
51
|
-
// Add the primitives
|
|
52
|
-
const primitives = meta.primitives.map((p) => {
|
|
53
|
-
const { fieldName, fieldType, notNull } = p;
|
|
54
|
-
const maybeOptional = notNull ? "" : " | undefined";
|
|
55
|
-
|
|
56
|
-
let getter: Code;
|
|
57
|
-
if (p.derived === "async") {
|
|
58
|
-
getter = code`
|
|
59
|
-
get ${fieldName}(): ${fieldType}${maybeOptional} {
|
|
60
|
-
if (!("${fieldName}" in this.__orm.data)) {
|
|
61
|
-
throw new Error("${fieldName} has not been derived yet");
|
|
62
|
-
}
|
|
63
|
-
return this.__orm.data["${fieldName}"];
|
|
64
|
-
}
|
|
65
|
-
`;
|
|
66
|
-
} else if (p.derived === "sync") {
|
|
67
|
-
getter = code`
|
|
68
|
-
abstract get ${fieldName}(): ${fieldType}${maybeOptional};
|
|
69
|
-
`;
|
|
70
|
-
} else {
|
|
71
|
-
getter = code`
|
|
72
|
-
get ${fieldName}(): ${fieldType}${maybeOptional} {
|
|
73
|
-
return this.__orm.data["${fieldName}"];
|
|
74
|
-
}
|
|
75
|
-
`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let setter: Code | string;
|
|
79
|
-
if (p.protected) {
|
|
80
|
-
// TODO Allow making the getter to be protected as well. And so probably remove it
|
|
81
|
-
// from the Opts as well. Wonder how that works for required protected fields?
|
|
82
|
-
//
|
|
83
|
-
// We have to use a method of `set${fieldName}` because TS enforces getters/setters to have
|
|
84
|
-
// same access level and currently we're leaving the getter as public.
|
|
85
|
-
setter = code`
|
|
86
|
-
protected set${pascalCase(fieldName)}(${fieldName}: ${fieldType}${maybeOptional}) {
|
|
87
|
-
${setField}(this, "${fieldName}", ${fieldName});
|
|
88
|
-
}
|
|
89
|
-
`;
|
|
90
|
-
} else if (p.derived) {
|
|
91
|
-
setter = "";
|
|
92
|
-
} else {
|
|
93
|
-
setter = code`
|
|
94
|
-
set ${fieldName}(${fieldName}: ${fieldType}${maybeOptional}) {
|
|
95
|
-
${setField}(this, "${fieldName}", ${fieldName});
|
|
96
|
-
}
|
|
97
|
-
`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return code`${getter} ${setter}`;
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Add ManyToOne enums
|
|
104
|
-
meta.enums.forEach((e) => {
|
|
105
|
-
const { fieldName, enumType, notNull } = e;
|
|
106
|
-
const maybeOptional = notNull ? "" : " | undefined";
|
|
107
|
-
const getter = code`
|
|
108
|
-
get ${fieldName}(): ${enumType}${maybeOptional} {
|
|
109
|
-
return this.__orm.data["${fieldName}"];
|
|
110
|
-
}
|
|
111
|
-
`;
|
|
112
|
-
const setter = code`
|
|
113
|
-
set ${fieldName}(${fieldName}: ${enumType}${maybeOptional}) {
|
|
114
|
-
${setField}(this, "${fieldName}", ${fieldName});
|
|
115
|
-
}
|
|
116
|
-
`;
|
|
117
|
-
// Group enums as primitives
|
|
118
|
-
primitives.push(getter);
|
|
119
|
-
primitives.push(setter);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Add ManyToOne entities
|
|
123
|
-
const m2o = meta.manyToOnes.map((m2o) => {
|
|
124
|
-
const { fieldName, otherEntity, otherFieldName, notNull } = m2o;
|
|
125
|
-
const maybeOptional = notNull ? "never" : "undefined";
|
|
126
|
-
return code`
|
|
127
|
-
readonly ${fieldName}: ${Reference}<${entity.type}, ${otherEntity.type}, ${maybeOptional}> =
|
|
128
|
-
${hasOne}(
|
|
129
|
-
${otherEntity.metaType},
|
|
130
|
-
"${fieldName}",
|
|
131
|
-
"${otherFieldName}",
|
|
132
|
-
);
|
|
133
|
-
`;
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// Add OneToMany
|
|
137
|
-
const o2m = meta.oneToManys.map((o2m) => {
|
|
138
|
-
const { fieldName, otherFieldName, otherColumnName, otherEntity } = o2m;
|
|
139
|
-
return code`
|
|
140
|
-
readonly ${fieldName}: ${Collection}<${entity.type}, ${otherEntity.type}> = ${hasMany}(
|
|
141
|
-
${otherEntity.metaType},
|
|
142
|
-
"${fieldName}",
|
|
143
|
-
"${otherFieldName}",
|
|
144
|
-
"${otherColumnName}"
|
|
145
|
-
);
|
|
146
|
-
`;
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Add OneToOne
|
|
150
|
-
const o2o = meta.oneToOnes.map((o2o) => {
|
|
151
|
-
const { fieldName, otherEntity, otherFieldName } = o2o;
|
|
152
|
-
return code`
|
|
153
|
-
readonly ${fieldName}: ${Reference}<${entity.type}, ${otherEntity.type}, undefined> =
|
|
154
|
-
${hasOneToOne}(
|
|
155
|
-
${otherEntity.metaType},
|
|
156
|
-
"${fieldName}",
|
|
157
|
-
"${otherFieldName}",
|
|
158
|
-
);
|
|
159
|
-
`;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Add ManyToMany
|
|
163
|
-
const m2m = meta.manyToManys.map((m2m) => {
|
|
164
|
-
const { joinTableName, fieldName, columnName, otherEntity, otherFieldName, otherColumnName } = m2m;
|
|
165
|
-
return code`
|
|
166
|
-
readonly ${fieldName}: ${Collection}<${entity.type}, ${otherEntity.type}> = ${hasManyToMany}(
|
|
167
|
-
"${joinTableName}",
|
|
168
|
-
"${fieldName}",
|
|
169
|
-
"${columnName}",
|
|
170
|
-
${otherEntity.metaType},
|
|
171
|
-
"${otherFieldName}",
|
|
172
|
-
"${otherColumnName}",
|
|
173
|
-
);
|
|
174
|
-
`;
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const configName = `${camelCase(entityName)}Config`;
|
|
178
|
-
const metadata = imp(`${camelCase(entityName)}Meta@./entities`);
|
|
179
|
-
|
|
180
|
-
const defaultValues = generateDefaultValues(config, meta);
|
|
181
|
-
const hasDefaultValues = defaultValues.length > 0;
|
|
182
|
-
const defaultValuesName = `${camelCase(entityName)}DefaultValues`;
|
|
183
|
-
|
|
184
|
-
const contextType = config.contextType ? imp(config.contextType) : "{}";
|
|
185
|
-
const factoryMethod = imp(`new${entity.name}@./entities`);
|
|
186
|
-
|
|
187
|
-
return code`
|
|
188
|
-
export type ${entityName}Id = ${Flavor}<string, "${entityName}">;
|
|
189
|
-
|
|
190
|
-
export interface ${entityName}Opts {
|
|
191
|
-
${generateOptsFields(config, meta)}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export interface ${entityName}IdsOpts {
|
|
195
|
-
${generateOptIdsFields(config, meta)}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export interface ${entityName}Filter {
|
|
199
|
-
id?: ${ValueFilter}<${entityName}Id, never>;
|
|
200
|
-
${generateFilterFields(meta)}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export interface ${entityName}GraphQLFilter {
|
|
204
|
-
id?: ${ValueGraphQLFilter}<${entityName}Id>;
|
|
205
|
-
${generateGraphQLFilterFields(meta)}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export interface ${entityName}Order {
|
|
209
|
-
id?: ${OrderBy};
|
|
210
|
-
${generateOrderFields(meta)}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
${hasDefaultValues ? code`export const ${defaultValuesName} = { ${defaultValues} };` : ""}
|
|
214
|
-
|
|
215
|
-
export const ${configName} = new ${ConfigApi}<${entity.type}, ${contextType}>();
|
|
216
|
-
|
|
217
|
-
${generateDefaultValidationRules(meta, configName)}
|
|
218
|
-
|
|
219
|
-
export abstract class ${entityName}Codegen extends ${BaseEntity} {
|
|
220
|
-
readonly __types: {
|
|
221
|
-
filterType: ${entityName}Filter;
|
|
222
|
-
gqlFilterType: ${entityName}GraphQLFilter;
|
|
223
|
-
orderType: ${entityName}Order;
|
|
224
|
-
optsType: ${entityName}Opts;
|
|
225
|
-
optIdsType: ${entityName}IdsOpts;
|
|
226
|
-
factoryOptsType: Parameters<typeof ${factoryMethod}>[1];
|
|
227
|
-
} = null!;
|
|
228
|
-
${[o2m, m2o, o2o, m2m]}
|
|
229
|
-
|
|
230
|
-
constructor(em: ${EntityManager}, opts: ${entityName}Opts) {
|
|
231
|
-
super(em, ${metadata}, ${hasDefaultValues ? `${defaultValuesName}` : "{}"}, opts);
|
|
232
|
-
${setOpts}(this as any as ${entityName}, opts, { calledFromConstructor: true });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
get id(): ${entityName}Id | undefined {
|
|
236
|
-
return this.__orm.data["id"];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
${primitives}
|
|
240
|
-
|
|
241
|
-
set(opts: Partial<${entityName}Opts>): void {
|
|
242
|
-
${setOpts}(this as any as ${entityName}, opts);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
setPartial(opts: ${PartialOrNull}<${entityName}Opts>): void {
|
|
246
|
-
${setOpts}(this as any as ${entityName}, opts as ${OptsOf}<${entityName}>, { partial: true });
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
get changes(): ${Changes}<${entityName}> {
|
|
250
|
-
return ${newChangesProxy}(this as any as ${entityName});
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async load<U, V>(fn: (lens: ${Lens}<${entity.type}>) => ${Lens}<U, V>): Promise<V> {
|
|
254
|
-
return ${loadLens}(this as any as ${entityName}, fn);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async populate<H extends ${LoadHint}<${entityName}>>(hint: H): Promise<${Loaded}<${entityName}, H>> {
|
|
258
|
-
return ${getEm}(this).populate(this as any as ${entityName}, hint);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
`;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function fieldHasDefaultValue(config: Config, meta: EntityDbMetadata, field: PrimitiveField): boolean {
|
|
265
|
-
let { fieldName, columnDefault, columnType } = field;
|
|
266
|
-
|
|
267
|
-
// if there's no default at all, return false
|
|
268
|
-
if (columnDefault === null) {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// even though default is defined as a number | boolean | string | null in reality pg-structure
|
|
273
|
-
// only ever returns a string | null so we make sure of that here
|
|
274
|
-
columnDefault = columnDefault.toString();
|
|
275
|
-
|
|
276
|
-
// if this value should be set elsewhere, return false
|
|
277
|
-
if (field.derived !== false) {
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// try to validate that we actually got a primitive value and not arbitrary SQL
|
|
282
|
-
return (
|
|
283
|
-
(["smallint", "int", "bigint"].includes(columnType) && !isNaN(parseInt(columnDefault))) ||
|
|
284
|
-
(["varchar", "text"].includes(columnType) && /^'.*'$/.test(columnDefault)) ||
|
|
285
|
-
("bool" === columnType && ["true", "false"].includes(columnDefault))
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function generateDefaultValues(config: Config, meta: EntityDbMetadata): Code[] {
|
|
290
|
-
return meta.primitives
|
|
291
|
-
.filter((field) => fieldHasDefaultValue(config, meta, field))
|
|
292
|
-
.map(({ fieldName, columnDefault }) => {
|
|
293
|
-
return code`${fieldName}: ${columnDefault},`;
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function generateDefaultValidationRules(meta: EntityDbMetadata, configName: string): Code[] {
|
|
298
|
-
const fields = [...meta.primitives, ...meta.enums, ...meta.manyToOnes];
|
|
299
|
-
return fields
|
|
300
|
-
.filter((p) => p.notNull)
|
|
301
|
-
.map(({ fieldName }) => {
|
|
302
|
-
return code`${configName}.addRule(${newRequiredRule}("${fieldName}"));`;
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Make our opts type
|
|
307
|
-
function generateOptsFields(config: Config, meta: EntityDbMetadata): Code[] {
|
|
308
|
-
const primitives = meta.primitives.map((field) => {
|
|
309
|
-
const { fieldName, fieldType, notNull, derived } = field;
|
|
310
|
-
if (derived) {
|
|
311
|
-
return code``;
|
|
312
|
-
}
|
|
313
|
-
return code`${fieldName}${maybeOptional(
|
|
314
|
-
notNull && !fieldHasDefaultValue(config, meta, field),
|
|
315
|
-
)}: ${fieldType}${maybeUnionNull(notNull)};`;
|
|
316
|
-
});
|
|
317
|
-
const enums = meta.enums.map(({ fieldName, enumType, notNull }) => {
|
|
318
|
-
return code`${fieldName}${maybeOptional(notNull)}: ${enumType}${maybeUnionNull(notNull)};`;
|
|
319
|
-
});
|
|
320
|
-
const m2o = meta.manyToOnes.map(({ fieldName, otherEntity, notNull }) => {
|
|
321
|
-
return code`${fieldName}${maybeOptional(notNull)}: ${otherEntity.type}${maybeUnionNull(notNull)};`;
|
|
322
|
-
});
|
|
323
|
-
const o2o = meta.oneToOnes.map(({ fieldName, otherEntity }) => {
|
|
324
|
-
return code`${fieldName}?: ${otherEntity.type} | null;`;
|
|
325
|
-
});
|
|
326
|
-
const o2m = meta.oneToManys.map(({ fieldName, otherEntity }) => {
|
|
327
|
-
return code`${fieldName}?: ${otherEntity.type}[];`;
|
|
328
|
-
});
|
|
329
|
-
const m2m = meta.manyToManys.map(({ fieldName, otherEntity }) => {
|
|
330
|
-
return code`${fieldName}?: ${otherEntity.type}[];`;
|
|
331
|
-
});
|
|
332
|
-
return [...primitives, ...enums, ...m2o, ...o2o, ...o2m, ...m2m];
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// We know the OptIds types are only used in partials, so we make everything optional.
|
|
336
|
-
// This especially needs to be the case b/c both `book: ...` and `bookId: ...` will be
|
|
337
|
-
// in the partial type and of course the caller will only be setting one.
|
|
338
|
-
function generateOptIdsFields(config: Config, meta: EntityDbMetadata): Code[] {
|
|
339
|
-
const m2o = meta.manyToOnes.map(({ fieldName, otherEntity, notNull }) => {
|
|
340
|
-
return code`${fieldName}Id?: ${otherEntity.idType} | null;`;
|
|
341
|
-
});
|
|
342
|
-
const o2o = meta.oneToOnes.map(({ fieldName, otherEntity }) => {
|
|
343
|
-
return code`${fieldName}Id?: ${otherEntity.idType} | null;`;
|
|
344
|
-
});
|
|
345
|
-
const o2m = meta.oneToManys.map(({ singularName, otherEntity }) => {
|
|
346
|
-
return code`${singularName}Ids?: ${otherEntity.idType}[] | null;`;
|
|
347
|
-
});
|
|
348
|
-
const m2m = meta.manyToManys.map(({ singularName, otherEntity }) => {
|
|
349
|
-
return code`${singularName}Ids?: ${otherEntity.idType}[] | null;`;
|
|
350
|
-
});
|
|
351
|
-
return [...m2o, ...o2o, ...o2m, ...m2m];
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function generateFilterFields(meta: EntityDbMetadata): Code[] {
|
|
355
|
-
const primitives = meta.primitives.map(({ fieldName, fieldType, notNull }) => {
|
|
356
|
-
if (fieldType === "boolean") {
|
|
357
|
-
return code`${fieldName}?: ${BooleanFilter}<${nullOrNever(notNull)}>;`;
|
|
358
|
-
} else {
|
|
359
|
-
return code`${fieldName}?: ${ValueFilter}<${fieldType}, ${nullOrNever(notNull)}>;`;
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
const enums = meta.enums.map(({ fieldName, enumType, notNull }) => {
|
|
363
|
-
return code`${fieldName}?: ${ValueFilter}<${enumType}, ${nullOrNever(notNull)}>;`;
|
|
364
|
-
});
|
|
365
|
-
const m2o = meta.manyToOnes.map(({ fieldName, otherEntity, notNull }) => {
|
|
366
|
-
return code`${fieldName}?: ${EntityFilter}<${otherEntity.type}, ${otherEntity.idType}, ${FilterOf}<${
|
|
367
|
-
otherEntity.type
|
|
368
|
-
}>, ${nullOrNever(notNull)}>;`;
|
|
369
|
-
});
|
|
370
|
-
return [...primitives, ...enums, ...m2o];
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function generateGraphQLFilterFields(meta: EntityDbMetadata): Code[] {
|
|
374
|
-
const primitives = meta.primitives.map(({ fieldName, fieldType, notNull }) => {
|
|
375
|
-
if (fieldType === "boolean") {
|
|
376
|
-
return code`${fieldName}?: ${BooleanGraphQLFilter};`;
|
|
377
|
-
} else {
|
|
378
|
-
return code`${fieldName}?: ${ValueGraphQLFilter}<${fieldType}>;`;
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
const enums = meta.enums.map(({ fieldName, enumType, notNull }) => {
|
|
382
|
-
return code`${fieldName}?: ${EnumGraphQLFilter}<${enumType}>;`;
|
|
383
|
-
});
|
|
384
|
-
const m2o = meta.manyToOnes.map(({ fieldName, otherEntity, notNull }) => {
|
|
385
|
-
return code`${fieldName}?: ${EntityGraphQLFilter}<${otherEntity.type}, ${otherEntity.idType}, ${GraphQLFilterOf}<${otherEntity.type}>>;`;
|
|
386
|
-
});
|
|
387
|
-
return [...primitives, ...enums, ...m2o];
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function generateOrderFields(meta: EntityDbMetadata): Code[] {
|
|
391
|
-
// Make our opts type
|
|
392
|
-
const primitives = meta.primitives.map(({ fieldName }) => {
|
|
393
|
-
return code`${fieldName}?: ${OrderBy};`;
|
|
394
|
-
});
|
|
395
|
-
const enums = meta.enums.map(({ fieldName }) => {
|
|
396
|
-
return code`${fieldName}?: ${OrderBy};`;
|
|
397
|
-
});
|
|
398
|
-
const m2o = meta.manyToOnes.map(({ fieldName, otherEntity, notNull }) => {
|
|
399
|
-
return code`${fieldName}?: ${otherEntity.orderType};`;
|
|
400
|
-
});
|
|
401
|
-
return [...primitives, ...enums, ...m2o];
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function maybeOptional(notNull: boolean): string {
|
|
405
|
-
return notNull ? "" : "?";
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function maybeUnionNull(notNull: boolean): string {
|
|
409
|
-
return notNull ? "" : " | null";
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function nullOrNever(notNull: boolean): string {
|
|
413
|
-
return notNull ? "never" : " null | undefined";
|
|
414
|
-
}
|
package/src/generateEnumFile.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { pascalCase } from "change-case";
|
|
2
|
-
import { Table } from "pg-structure";
|
|
3
|
-
import pluralize from "pluralize";
|
|
4
|
-
import { code, Code } from "ts-poet";
|
|
5
|
-
import { Config } from "./config";
|
|
6
|
-
import { EntityDbMetadata, EnumRows } from "./index";
|
|
7
|
-
|
|
8
|
-
export function generateEnumFile(config: Config, table: Table, enumRows: EnumRows, enumName: string): Code {
|
|
9
|
-
const rows = enumRows[table.name];
|
|
10
|
-
const detailsName = `${enumName}Details`;
|
|
11
|
-
// We're not really an entity, but appropriate EntityDbMetadata's `primitives` filtering
|
|
12
|
-
const extraPrimitives = new EntityDbMetadata(config, table).primitives.filter(
|
|
13
|
-
(p) => !["code", "name"].includes(p.fieldName),
|
|
14
|
-
);
|
|
15
|
-
const detailsDefinition = [
|
|
16
|
-
"id: number;",
|
|
17
|
-
`code: ${enumName};`,
|
|
18
|
-
"name: string;",
|
|
19
|
-
...extraPrimitives.map((primitive) => `${primitive.fieldName}: ${primitive.fieldType};`),
|
|
20
|
-
].join(" ");
|
|
21
|
-
return code`
|
|
22
|
-
export enum ${enumName} {
|
|
23
|
-
${rows.map((row) => `${pascalCase(row.code)} = '${row.code}'`).join(",\n")}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type ${detailsName} = {${detailsDefinition}};
|
|
27
|
-
|
|
28
|
-
const details: Record<${enumName}, ${detailsName}> = {
|
|
29
|
-
${rows
|
|
30
|
-
.map((row) => {
|
|
31
|
-
const code = pascalCase(row.code);
|
|
32
|
-
const safeName = row.name.replace(/(["'])/g, "\\$1");
|
|
33
|
-
const extras = extraPrimitives
|
|
34
|
-
.map((p) => `${p.fieldName}: ${JSON.stringify((row as any)[p.columnName])}`)
|
|
35
|
-
.join(", ");
|
|
36
|
-
return `[${enumName}.${code}]: { id: ${row.id}, code: ${enumName}.${code}, name: '${safeName}', ${extras} }`;
|
|
37
|
-
})
|
|
38
|
-
.join(",")}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export const ${pluralize(enumName)} = {
|
|
42
|
-
getByCode(code: ${enumName}): ${detailsName} {
|
|
43
|
-
return details[code];
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
findByCode(code: string): ${detailsName} | undefined {
|
|
47
|
-
return details[code as ${enumName}];
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
findById(id: number): ${detailsName} | undefined {
|
|
51
|
-
return Object.values(details).find(d => d.id === id);
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
getValues(): ReadonlyArray<${enumName}> {
|
|
55
|
-
return Object.values(${enumName});
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
getDetails(): ReadonlyArray<${detailsName}> {
|
|
59
|
-
return Object.values(details);
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
`;
|
|
63
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { pascalCase } from "change-case";
|
|
2
|
-
import { code } from "ts-poet";
|
|
3
|
-
import { EntityDbMetadata } from "./EntityDbMetadata";
|
|
4
|
-
import { CodeGenFile } from "./index";
|
|
5
|
-
import { EntityManager, FactoryOpts, New, newTestInstance } from "./symbols";
|
|
6
|
-
|
|
7
|
-
export function generateFactoriesFiles(entities: EntityDbMetadata[]): CodeGenFile[] {
|
|
8
|
-
// One-time create an Author.factories.ts for each entity
|
|
9
|
-
const entityFiles = entities.map(({ entity }) => {
|
|
10
|
-
const name = pascalCase(entity.name);
|
|
11
|
-
const contents = code`
|
|
12
|
-
export function new${name}(
|
|
13
|
-
em: ${EntityManager},
|
|
14
|
-
opts?: ${FactoryOpts}<${entity.type}>
|
|
15
|
-
): ${New}<${entity.type}> {
|
|
16
|
-
return ${newTestInstance}(em, ${entity.type}, opts);
|
|
17
|
-
}`;
|
|
18
|
-
return { name: `./${entity.name}.factories.ts`, contents, overwrite: false };
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// Everytime create a factories.ts that exports the others
|
|
22
|
-
const factoriesFile = {
|
|
23
|
-
name: "./factories.ts",
|
|
24
|
-
contents: code`${entities.map(({ entity }) => code`export * from "./${entity.name}.factories";`)}`,
|
|
25
|
-
overwrite: true,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return [...entityFiles, factoriesFile];
|
|
29
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { code, Code, imp } from "ts-poet";
|
|
2
|
-
import { EntityManager } from "./symbols";
|
|
3
|
-
import { EntityDbMetadata } from "./EntityDbMetadata";
|
|
4
|
-
|
|
5
|
-
/** Creates the placeholder file for our per-entity custom business logic in. */
|
|
6
|
-
export function generateInitialEntityFile(meta: EntityDbMetadata): Code {
|
|
7
|
-
const entityName = meta.entity.name;
|
|
8
|
-
const codegenClass = imp(`${entityName}Codegen@./entities`);
|
|
9
|
-
return code`
|
|
10
|
-
export class ${entityName} extends ${codegenClass} {}
|
|
11
|
-
`;
|
|
12
|
-
}
|