@postxl/generator 0.56.6 → 0.56.8

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.
@@ -1,54 +1,59 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.generateModelBusinessLogicUpdate = void 0;
4
27
  const imports_1 = require("../../lib/imports");
5
28
  const meta_1 = require("../../lib/meta");
6
29
  const fields_1 = require("../../lib/schema/fields");
7
- const types_1 = require("../../lib/schema/types");
30
+ const Types = __importStar(require("../../lib/schema/types"));
8
31
  const zod_1 = require("../../lib/schema/zod");
9
32
  const jsdoc_1 = require("../../lib/utils/jsdoc");
33
+ const string_1 = require("../../lib/utils/string");
10
34
  /**
11
35
  * Generates update business logic for a given model.
12
36
  * The update logic handles all Create/Update/Delete/Upsert operations. See template's readme for more info.
13
37
  */
14
38
  function generateModelBusinessLogicUpdate({ model, meta }) {
15
39
  const schemaMeta = (0, meta_1.getSchemaMetadata)({ config: model.schemaConfig });
16
- /**
17
- * Shorthand variable for relevant metadata.
18
- */
19
40
  const m = {
20
- /**
21
- * The name of the interface that handles the action execution.
22
- */
23
41
  iExecution: schemaMeta.actions.execution.interface,
24
- /**
25
- * The name of the type that represents a fully typed, flat object, e.g. `Aggregation`
26
- */
27
42
  typeName: meta.types.typeName,
28
- /**
29
- * Type of the ID field that is specific to this model, e.g. `AggregationId`
30
- */
31
43
  brandedId: model.brandedIdType,
32
- /**
33
- * Internal type name for create payload, e.g. `CreateAggregation`
34
- */
35
44
  createType: `Create${meta.internalSingularNameCapitalized}`,
36
- /**
37
- * Internal type name for update payload, e.g. `UpdateAggregation`
38
- */
39
45
  updateType: `Update${meta.internalSingularNameCapitalized}`,
40
- /**
41
- * Internal type name for upsert payload, e.g. `UpsertAggregation`
42
- */
43
46
  upsertType: `Upsert${meta.internalSingularNameCapitalized}`,
47
+ cloneType: `Clone${meta.internalSingularNameCapitalized}`,
44
48
  };
45
49
  const imports = imports_1.ImportsGenerator.from(meta.businessLogic.update.serviceFilePath);
46
50
  imports.addImports({
47
51
  [meta.data.importPath]: meta.data.repository.className,
48
52
  [meta.types.importPath]: [
49
- (0, types_1.toAnnotatedTypeName)(m.brandedId),
50
- (0, types_1.toAnnotatedTypeName)(m.typeName),
53
+ Types.toAnnotatedTypeName(m.brandedId),
54
+ Types.toAnnotatedTypeName(m.typeName),
51
55
  meta.types.toBrandedIdTypeFnName,
56
+ Types.toFunctionName(`omitId`),
52
57
  ],
53
58
  [meta.businessLogic.view.serviceFilePath]: [meta.businessLogic.view.serviceClassName],
54
59
  [schemaMeta.actions.importPath]: [schemaMeta.actions.execution.interface, schemaMeta.actions.dispatcher.definition],
@@ -66,6 +71,9 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
66
71
  from: refMeta.types.importPath,
67
72
  });
68
73
  }
74
+ for (const { referencingModel } of model.references) {
75
+ imports.addTypeImport({ items: [referencingModel.brandedIdType], from: meta.types.importPath });
76
+ }
69
77
  /**
70
78
  * The name of the variable that holds the repository instance for the current model
71
79
  * (e.g. when we generate business logic service for Aggregation, the AggregationRepository
@@ -77,7 +85,7 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
77
85
  `@Inject(forwardRef(() => ${schemaMeta.businessLogic.update.serviceClassName})) private readonly updateService: ${schemaMeta.businessLogic.update.serviceClassName}`,
78
86
  `@Inject(forwardRef(() => ${schemaMeta.businessLogic.view.serviceClassName})) private readonly viewService: ${schemaMeta.businessLogic.view.serviceClassName}`,
79
87
  ];
80
- const { zodCreateObject, zodUpdateObject, zodUpsertObject } = meta.businessLogic.update;
88
+ const { zodCreateObject, zodUpdateObject, zodUpsertObject, zodCloneObject } = meta.businessLogic.update;
81
89
  const { view, update } = meta.businessLogic;
82
90
  /* prettier-ignore */
83
91
  return /* ts */ `
@@ -136,6 +144,12 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
136
144
  payload: ${m.brandedId}[]
137
145
  result: ${m.brandedId}[]
138
146
  }
147
+
148
+ ${(0, jsdoc_1.toJsDocComment)([`Clones a ${meta.userFriendlyName} and returns the clone.`])}
149
+ clone: {
150
+ payload: ${m.cloneType}
151
+ result: ${m.typeName}
152
+ }
139
153
  }
140
154
 
141
155
  /**
@@ -169,6 +183,18 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
169
183
 
170
184
  type ${m.upsertType} = z.infer<typeof ${zodUpsertObject}>
171
185
 
186
+ /**
187
+ * Zod decoder for validating the clone input of a ${meta.userFriendlyName} .
188
+ */
189
+ export const ${zodCloneObject} = z.object({
190
+ ${model.fields
191
+ .filter((f) => !f.attributes.isReadonly || f.kind === "id")
192
+ .map((f) => `${f.name}: z.${(0, zod_1.getZodDecoderDefinition)({ field: f, allowAnyOptionalField: f.kind !== 'id' })}`)
193
+ .join(',')}
194
+ })
195
+
196
+ type ${m.cloneType} = z.infer<typeof ${zodCloneObject}>
197
+
172
198
  export type ${update.serviceInterfaceName} = ${schemaMeta.actions.dispatcher.definition}<Actions>
173
199
 
174
200
  @Injectable()
@@ -213,16 +239,122 @@ function generateModelBusinessLogicUpdate({ model, meta }) {
213
239
  return this.data.upsertMany({ items: data, execution })
214
240
  }
215
241
 
216
- ${(0, jsdoc_1.toJsDocComment)([`Deletes a ${meta.userFriendlyName} and returns its id.`])}
217
- public async delete({ data, execution }: { data: ${m.brandedId}; execution: ${m.iExecution} }): Promise<${m.brandedId}> {
218
- return this.data.delete({ id: data, execution })
219
- }
242
+
243
+ ${generateDeleteFunction({ model, meta, m })}
220
244
 
221
- ${(0, jsdoc_1.toJsDocComment)([`Deletes multiple ${meta.userFriendlyNamePlural} and returns their ids.`])}
222
- public async deleteMany({ data, execution }: { data: ${m.brandedId}[]; execution: ${m.iExecution} }): Promise<${m.brandedId}[]> {
223
- return this.data.deleteMany({ ids: data, execution })
224
- }
245
+ ${generateDeleteManyMethod({ model, meta, m })}
246
+
247
+ ${generateCloneMethod({ model, meta, m })}
225
248
  }
226
249
  `;
227
250
  }
228
251
  exports.generateModelBusinessLogicUpdate = generateModelBusinessLogicUpdate;
252
+ /**
253
+ * Returns a function implementation that deletes a single entity and all its related entities.
254
+ */
255
+ function generateDeleteFunction({ model, meta, m }) {
256
+ const backReferenceDelete = [];
257
+ const backReferenceNames = [];
258
+ for (const { referencingField, referencingModel } of model.references) {
259
+ const refModelMeta = (0, meta_1.getModelMetadata)({ model: referencingModel });
260
+ const refFieldMeta = (0, meta_1.getFieldMetadata)({ field: referencingField });
261
+ const ids = `${refModelMeta.internalSingularName}${(0, string_1.capitalize)(referencingField.name)}s`;
262
+ backReferenceNames.push(`${refModelMeta.userFriendlyNamePlural}.${referencingField.name}`);
263
+ backReferenceDelete.push(`
264
+ // ${referencingModel.name}.${referencingField.name}
265
+ const ${ids} = await this.viewService.${refModelMeta.businessLogic.view.serviceVariableName}.data.${refFieldMeta.getByForeignKeyIdsMethodFnName}(data)
266
+ await this.updateService.${refModelMeta.businessLogic.update.serviceVariableName}.deleteMany({ data: ${ids}, execution })
267
+ `);
268
+ }
269
+ return `
270
+ ${(0, jsdoc_1.toJsDocComment)([`Deletes a ${meta.userFriendlyName} instance and returns its id.`])}
271
+ public async delete(
272
+ { data, execution }: { data: ${m.brandedId}; execution: ${m.iExecution} }
273
+ ): Promise<${m.brandedId}> {
274
+ ${backReferenceDelete.join('\n')}
275
+ return this.data.delete({ id: data, execution })
276
+ }
277
+ `;
278
+ }
279
+ /**
280
+ * Returns a function that deletes multiple entities and all their related entities.
281
+ */
282
+ function generateDeleteManyMethod({ model, meta, m }) {
283
+ const idArrays = [];
284
+ const idAssignments = [];
285
+ const deleteCalls = [];
286
+ for (const { referencingField, referencingModel } of model.references) {
287
+ const refModelMeta = (0, meta_1.getModelMetadata)({ model: referencingModel });
288
+ const refFieldMeta = (0, meta_1.getFieldMetadata)({ field: referencingField });
289
+ const idArray = `${refModelMeta.internalSingularName}${(0, string_1.capitalize)(referencingField.name)}s`;
290
+ idArrays.push(`const ${idArray}: ${referencingModel.brandedIdType}[] = []`);
291
+ idAssignments.push(`
292
+ {
293
+ // ${referencingModel.name}.${referencingField.name}
294
+ const _ids = await this.viewService.${refModelMeta.businessLogic.view.serviceVariableName}.data.${refFieldMeta.getByForeignKeyIdsMethodFnName}(id)
295
+ ${idArray}.push(..._ids)
296
+ }
297
+ `);
298
+ deleteCalls.push(`await this.updateService.${refModelMeta.businessLogic.update.serviceVariableName}.deleteMany({ data: ${idArray}, execution })`);
299
+ }
300
+ let relatedEntities = '';
301
+ if (model.references.length > 0) {
302
+ relatedEntities = `
303
+ ${idArrays.join('\n')}
304
+
305
+ for (const id of ids) {
306
+ ${idAssignments.join('\n')}
307
+ }
308
+
309
+ ${deleteCalls.join('\n')}
310
+ `;
311
+ }
312
+ return `
313
+ ${(0, jsdoc_1.toJsDocComment)([`Deletes multiple ${meta.userFriendlyNamePlural} and returns their ids.`])}
314
+ public async deleteMany(
315
+ { data: ids, execution }: { data: ${m.brandedId}[]; execution: ${m.iExecution} }
316
+ ): Promise<${m.brandedId}[]> {
317
+ if (ids.length === 0) {
318
+ return []
319
+ }
320
+
321
+ ${relatedEntities}
322
+
323
+ return this.data.deleteMany({ ids, execution })
324
+ }`;
325
+ }
326
+ /**
327
+ * Returns a function that deep clones a given entity and all its related entities.
328
+ */
329
+ function generateCloneMethod({ model, meta, m }) {
330
+ const backReferenceCloning = [];
331
+ const backReferenceNames = [];
332
+ for (const { referencingField, referencingModel } of model.references) {
333
+ const refModelMeta = (0, meta_1.getModelMetadata)({ model: referencingModel });
334
+ const refFieldMeta = (0, meta_1.getFieldMetadata)({ field: referencingField });
335
+ backReferenceNames.push(`${refModelMeta.userFriendlyNamePlural}.${referencingField.name}`);
336
+ const { view, update } = refModelMeta.businessLogic;
337
+ backReferenceCloning.push(`
338
+ // ${referencingModel.name}.${referencingField.name}
339
+ for (const childId of await this.viewService.${view.serviceVariableName}.data.${refFieldMeta.getByForeignKeyIdsMethodFnName}(id)) {
340
+ await this.updateService.${update.serviceVariableName}.clone({ data: { id: childId, ${referencingField.name}: clone.id }, execution })
341
+ }
342
+ `);
343
+ }
344
+ return `
345
+ ${(0, jsdoc_1.toJsDocComment)([`Creates a new ${meta.userFriendlyName} deep clone and returns it.`])}
346
+ public async clone(
347
+ { data: { id, ...data }, execution }: { data: ${m.cloneType}; execution: ${m.iExecution} }
348
+ ): Promise<${m.typeName}> {
349
+ const source = await this.view.get(id)
350
+ if (!source) {
351
+ throw new Error(\`${meta.userFriendlyName} with id \${id} not found\`)
352
+ }
353
+
354
+ const clone = await this.data.create({ item: { ...omitId(source), ...data }, execution })
355
+
356
+ ${backReferenceCloning.join('\n')}
357
+
358
+ return clone
359
+ }`;
360
+ }
@@ -12,14 +12,14 @@ function generateRoute({ model, meta }) {
12
12
  const defaultValueMethod = `
13
13
  getDefault: procedure.query(({ ctx }) => ctx.view.${meta.data.dataServiceName}.${dataRepositoryVariableName}.defaultValue),
14
14
  `;
15
- const { zodCreateObject, zodUpdateObject, zodUpsertObject } = meta.businessLogic.update;
15
+ const { zodCreateObject, zodUpdateObject, zodUpsertObject, zodCloneObject } = meta.businessLogic.update;
16
16
  const imports = imports_1.ImportsGenerator.from(meta.trpc.routerFilePath).addImports({
17
17
  [meta.types.importPath]: [
18
18
  (0, types_1.toAnnotatedTypeName)(model.typeName),
19
19
  meta.types.toBrandedIdTypeFnName,
20
20
  meta.types.zodDecoderFnNames.id,
21
21
  ],
22
- [meta.businessLogic.update.serviceFilePath]: [zodCreateObject, zodUpdateObject, zodUpsertObject],
22
+ [meta.businessLogic.update.serviceFilePath]: [zodCreateObject, zodUpdateObject, zodUpsertObject, zodCloneObject],
23
23
  });
24
24
  return /* ts */ `
25
25
  import { z } from 'zod'
@@ -91,6 +91,10 @@ export const ${meta.trpc.routerName} = router({
91
91
  deleteMany: procedure
92
92
  .input(z.array(${meta.types.zodDecoderFnNames.id}))
93
93
  .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "deleteMany", payload: input})),
94
+
95
+ clone: procedure
96
+ .input(${zodCloneObject})
97
+ .mutation(({ input, ctx }) => ctx.dispatch({scope: "${scopeName}", type: "clone", payload: input})),
94
98
  })
95
99
  `;
96
100
  }
@@ -954,6 +954,10 @@ export type ModelMetaData = {
954
954
  * The name of the function that decodes an Upsert object to a fully typed object, e.g. `aggregationUpsertDecoder`.
955
955
  */
956
956
  zodUpsertObject: Types.FunctionName;
957
+ /**
958
+ * The name of the function that clones an object, e.g. `aggregationCloneDecoder`.
959
+ */
960
+ zodCloneObject: Types.FunctionName;
957
961
  };
958
962
  /**
959
963
  * Name by which the business logic service exposes the data service.
package/dist/lib/meta.js CHANGED
@@ -346,6 +346,7 @@ function getModelMetadata({ model }) {
346
346
  zodCreateObject: Types.toFunctionName(`${camelCase}CreateDecoder`),
347
347
  zodUpdateObject: Types.toFunctionName(`${camelCase}UpdateDecoder`),
348
348
  zodUpsertObject: Types.toFunctionName(`${camelCase}UpsertDecoder`),
349
+ zodCloneObject: Types.toFunctionName(`${camelCase}CloneDecoder`),
349
350
  },
350
351
  dataRepositoryVariableName: Types.toVariableName(`data`),
351
352
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "0.56.6",
3
+ "version": "0.56.8",
4
4
  "main": "./dist/generator.js",
5
5
  "typings": "./dist/generator.d.ts",
6
6
  "bin": {