@postxl/generators 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/backend-authentication/generators/authentication-service.generator.js +1 -1
  2. package/dist/backend-core/backend.generator.js +0 -4
  3. package/dist/backend-core/backend.generator.js.map +1 -1
  4. package/dist/backend-core/modules/backend-module-xlport.generator.js +1 -1
  5. package/dist/backend-excel-io/excel-io.generator.d.ts +19 -0
  6. package/dist/backend-excel-io/excel-io.generator.js +106 -0
  7. package/dist/backend-excel-io/excel-io.generator.js.map +1 -0
  8. package/dist/backend-excel-io/generators/excel-io-service.generator.d.ts +9 -0
  9. package/dist/backend-excel-io/generators/excel-io-service.generator.js +680 -0
  10. package/dist/backend-excel-io/generators/excel-io-service.generator.js.map +1 -0
  11. package/dist/backend-excel-io/index.d.ts +2 -0
  12. package/dist/backend-excel-io/index.js +22 -0
  13. package/dist/backend-excel-io/index.js.map +1 -0
  14. package/dist/backend-excel-io/template/README.md +24 -0
  15. package/dist/backend-excel-io/template/excel-io.controller.ts +195 -0
  16. package/dist/backend-excel-io/template/excel-io.module.ts +17 -0
  17. package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js +148 -13
  18. package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js.map +1 -1
  19. package/dist/backend-import/generators/filter-error-rows.generator.d.ts +2 -0
  20. package/dist/backend-import/generators/filter-error-rows.generator.js +28 -0
  21. package/dist/backend-import/generators/filter-error-rows.generator.js.map +1 -0
  22. package/dist/backend-import/generators/import-service.generator.js +126 -2
  23. package/dist/backend-import/generators/import-service.generator.js.map +1 -1
  24. package/dist/backend-import/import.generator.js +2 -0
  25. package/dist/backend-import/import.generator.js.map +1 -1
  26. package/dist/backend-repositories/generators/model-repository.generator.js +17 -2
  27. package/dist/backend-repositories/generators/model-repository.generator.js.map +1 -1
  28. package/dist/backend-router-trpc/generators/app-routes.generator.js +5 -0
  29. package/dist/backend-router-trpc/generators/app-routes.generator.js.map +1 -1
  30. package/dist/backend-router-trpc/generators/excel-io-route.generator.d.ts +4 -0
  31. package/dist/backend-router-trpc/generators/excel-io-route.generator.js +35 -0
  32. package/dist/backend-router-trpc/generators/excel-io-route.generator.js.map +1 -0
  33. package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +6 -0
  34. package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
  35. package/dist/backend-router-trpc/generators/trpc-router-module.generator.js +8 -1
  36. package/dist/backend-router-trpc/generators/trpc-router-module.generator.js.map +1 -1
  37. package/dist/backend-router-trpc/generators/trpc-shared.generator.js +9 -0
  38. package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
  39. package/dist/backend-router-trpc/router-trpc.generator.d.ts +2 -1
  40. package/dist/backend-router-trpc/router-trpc.generator.js +4 -0
  41. package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
  42. package/dist/backend-update/model-update-service.generator.js +54 -0
  43. package/dist/backend-update/model-update-service.generator.js.map +1 -1
  44. package/dist/backend-update/update-actions.decoders.d.ts +2 -0
  45. package/dist/backend-update/update-actions.decoders.js +2 -0
  46. package/dist/backend-update/update-actions.decoders.js.map +1 -1
  47. package/dist/backend-update/update.generator.js +6 -0
  48. package/dist/backend-update/update.generator.js.map +1 -1
  49. package/dist/base/base.generator.js +0 -4
  50. package/dist/base/base.generator.js.map +1 -1
  51. package/dist/decoders/datamodel-decoder.generator.js +91 -1
  52. package/dist/decoders/datamodel-decoder.generator.js.map +1 -1
  53. package/dist/decoders/decoders.generator.d.ts +9 -0
  54. package/dist/decoders/decoders.generator.js +15 -0
  55. package/dist/decoders/decoders.generator.js.map +1 -1
  56. package/dist/decoders/discriminated-union.decoder.generator.js +4 -3
  57. package/dist/decoders/discriminated-union.decoder.generator.js.map +1 -1
  58. package/dist/devops/devops.generator.d.ts +5 -1
  59. package/dist/devops/devops.generator.js +5 -4
  60. package/dist/devops/devops.generator.js.map +1 -1
  61. package/dist/devops/generators/docker-compose-yml.generator.d.ts +1 -1
  62. package/dist/devops/generators/docker-compose-yml.generator.js +16 -1
  63. package/dist/devops/generators/docker-compose-yml.generator.js.map +1 -1
  64. package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
  65. package/dist/frontend-actions/generators/filter-utils.generator.js +1 -1
  66. package/dist/frontend-admin/admin.generator.d.ts +2 -0
  67. package/dist/frontend-admin/admin.generator.js +28 -0
  68. package/dist/frontend-admin/admin.generator.js.map +1 -1
  69. package/dist/frontend-admin/generators/admin-global-actions.generator.js +78 -0
  70. package/dist/frontend-admin/generators/admin-global-actions.generator.js.map +1 -1
  71. package/dist/frontend-admin/generators/admin-overview-page.generator.js +21 -1
  72. package/dist/frontend-admin/generators/admin-overview-page.generator.js.map +1 -1
  73. package/dist/frontend-admin/generators/admin-sidebar.generator.js +20 -0
  74. package/dist/frontend-admin/generators/admin-sidebar.generator.js.map +1 -1
  75. package/dist/frontend-admin/generators/excel-io-page.generator.d.ts +4 -0
  76. package/dist/frontend-admin/generators/excel-io-page.generator.js +258 -0
  77. package/dist/frontend-admin/generators/excel-io-page.generator.js.map +1 -0
  78. package/dist/frontend-admin/generators/import-review-page-result-stage.generator.d.ts +1 -0
  79. package/dist/frontend-admin/generators/import-review-page-result-stage.generator.js +104 -0
  80. package/dist/frontend-admin/generators/import-review-page-result-stage.generator.js.map +1 -0
  81. package/dist/frontend-admin/generators/import-review-page-review-stage.generator.d.ts +1 -0
  82. package/dist/frontend-admin/generators/import-review-page-review-stage.generator.js +1031 -0
  83. package/dist/frontend-admin/generators/import-review-page-review-stage.generator.js.map +1 -0
  84. package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.d.ts +1 -0
  85. package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.js +77 -0
  86. package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.js.map +1 -0
  87. package/dist/frontend-admin/generators/import-review-page.generator.d.ts +7 -0
  88. package/dist/frontend-admin/generators/import-review-page.generator.js +180 -0
  89. package/dist/frontend-admin/generators/import-review-page.generator.js.map +1 -0
  90. package/dist/frontend-admin/generators/model-admin-page.generator.js +60 -56
  91. package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
  92. package/dist/frontend-admin/utils.js +25 -33
  93. package/dist/frontend-admin/utils.js.map +1 -1
  94. package/dist/frontend-core/frontend.generator.js +1 -2
  95. package/dist/frontend-core/frontend.generator.js.map +1 -1
  96. package/dist/frontend-core/template/src/components/admin/excel-io-actions.tsx +64 -0
  97. package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +41 -3
  98. package/dist/frontend-core/template/src/hooks/use-excel-io.ts +137 -0
  99. package/dist/frontend-core/template/src/hooks/use-import-review.ts +143 -0
  100. package/dist/frontend-core/template/src/lib/excel-download.ts +28 -0
  101. package/dist/frontend-core/types/hook.d.ts +1 -1
  102. package/dist/frontend-tables/generators/model-table.generator.js +21 -13
  103. package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
  104. package/dist/frontend-trpc-client/generators/model-hook.generator.js +4 -0
  105. package/dist/frontend-trpc-client/generators/model-hook.generator.js.map +1 -1
  106. package/dist/frontend-trpc-client/trpc-client.generator.d.ts +4 -0
  107. package/dist/frontend-trpc-client/trpc-client.generator.js +1 -0
  108. package/dist/frontend-trpc-client/trpc-client.generator.js.map +1 -1
  109. package/dist/generators.js +2 -0
  110. package/dist/generators.js.map +1 -1
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.js +5 -2
  113. package/dist/index.js.map +1 -1
  114. package/dist/seed-data/seed-data.generator.d.ts +3 -0
  115. package/dist/seed-data/seed-data.generator.js +45 -1
  116. package/dist/seed-data/seed-data.generator.js.map +1 -1
  117. package/package.json +3 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend-excel-io/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,uDAAoC;AACpC,2DAAqH;AAA5G,6HAAA,SAAS,OAA2B;AAAE,+HAAA,WAAW,OAA6B"}
@@ -0,0 +1,24 @@
1
+ # PXL Backend Excel I/O
2
+
3
+ The Excel I/O module uses a two-phase import flow:
4
+
5
+ 1. `POST /excel-io/import/preview` (REST multipart upload)
6
+ returns only a `previewSessionId`.
7
+ 2. `excelIo.getPreview` (tRPC query)
8
+ returns the fully typed preview for that `previewSessionId`.
9
+ 3. `excelIo.execute` (tRPC mutation)
10
+ executes the reviewed import for that `previewSessionId`.
11
+
12
+ ## Why this split exists
13
+
14
+ - File uploads are handled in REST because tRPC does not handle multipart uploads directly.
15
+ - All post-upload operations are handled via tRPC to keep type-safe request/response contracts.
16
+ - Preview data is stored server-side in an in-memory preview store with TTL.
17
+
18
+ ## Session lifecycle
19
+
20
+ - Upload creates and stores preview data and returns `previewSessionId`.
21
+ - `getPreview` reads the stored preview by `previewSessionId`.
22
+ - `execute` validates flags (`ignoreErrors`, `confirmDeletes`) and applies changes.
23
+ - Successful execution removes the stored preview session.
24
+ - Expired preview sessions are cleaned up automatically.
@@ -0,0 +1,195 @@
1
+ import { AuthGuard, type Viewer } from '@authentication/auth.guard'
2
+ import { ViewerParam } from '@authentication/viewer.decorator'
3
+ import { Controller, Get, Logger, Param, Post, Query, Req, Res, UseGuards } from '@nestjs/common'
4
+
5
+ import { FastifyReply, FastifyRequest } from 'fastify'
6
+
7
+ import { ExcelIoService } from './excel-io.service'
8
+
9
+ const MAX_IMPORT_FILE_SIZE_BYTES = 20 * 1024 * 1024 // 20 MB
10
+
11
+ /**
12
+ * Security note:
13
+ * This controller is always registered and guarded with AuthGuard.
14
+ * In local dev with AUTH=false, mock auth still provides a viewer context.
15
+ */
16
+ @UseGuards(AuthGuard)
17
+ @Controller('excel-io')
18
+ export class ExcelIoController {
19
+ private readonly logger = new Logger(ExcelIoController.name)
20
+
21
+ constructor(private readonly excelIoService: ExcelIoService) {}
22
+
23
+ @Get('export/all')
24
+ async exportAll(@ViewerParam() viewer: Viewer, @Res({ passthrough: true }) res: FastifyReply): Promise<void> {
25
+ try {
26
+ const { filename, file } = await this.excelIoService.exportAllModelsToExcel(viewer.user)
27
+ const disposition = createContentDisposition(filename)
28
+
29
+ res.type('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
30
+ res.header('Content-Disposition', disposition)
31
+ res.header('Access-Control-Expose-Headers', 'Content-Disposition')
32
+ res.send(file)
33
+ } catch (error) {
34
+ this.logger.error('Error exporting all models to Excel', error)
35
+ res.status(500).send({ error: error instanceof Error ? error.message : String(error) })
36
+ }
37
+ }
38
+
39
+ @Get('export/:model')
40
+ async exportModel(
41
+ @Param('model') model: string,
42
+ @ViewerParam() viewer: Viewer,
43
+ @Res({ passthrough: true }) res: FastifyReply,
44
+ @Query('filters') filters?: string,
45
+ @Query('sort') sort?: string,
46
+ ): Promise<void> {
47
+ try {
48
+ const parsedModel = this.excelIoService.parseModelName(model)
49
+ const rawFilters = parseJsonQuery(filters)
50
+ const rawSort = parseJsonQuery(sort)
51
+
52
+ const { filename, file } = await this.excelIoService.exportModelToExcel({
53
+ model: parsedModel,
54
+ rawFilters,
55
+ rawSort,
56
+ user: viewer.user,
57
+ })
58
+ const disposition = createContentDisposition(filename)
59
+
60
+ res.type('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
61
+ res.header('Content-Disposition', disposition)
62
+ res.header('Access-Control-Expose-Headers', 'Content-Disposition')
63
+ res.send(file)
64
+ } catch (error) {
65
+ this.logger.error(`Error exporting model ${model} to Excel`, error)
66
+ res.status(500).send({ error: error instanceof Error ? error.message : String(error) })
67
+ }
68
+ }
69
+
70
+ @Post('import')
71
+ async importExcel(
72
+ @ViewerParam() viewer: Viewer,
73
+ @Req() req: FastifyRequest,
74
+ @Res({ passthrough: true }) res: FastifyReply,
75
+ ): Promise<void> {
76
+ try {
77
+ const data = await req.file()
78
+ if (!data) {
79
+ throw new Error('No file uploaded')
80
+ }
81
+
82
+ const fileBuffer = await toBufferWithLimit(data.file, MAX_IMPORT_FILE_SIZE_BYTES)
83
+ if (fileBuffer.length === 0) {
84
+ throw new Error('Uploaded file is empty')
85
+ }
86
+ const result = await this.excelIoService.importExcel({
87
+ data: fileBuffer,
88
+ filename: data.filename,
89
+ user: viewer.user,
90
+ })
91
+
92
+ res.status(200).send({ success: true, result })
93
+ } catch (error) {
94
+ this.logger.error('Error importing Excel file', error)
95
+ res.status(500).send({ success: false, error: error instanceof Error ? error.message : String(error) })
96
+ }
97
+ }
98
+
99
+ @Post('import/preview')
100
+ async previewImportExcel(
101
+ @ViewerParam() _viewer: Viewer,
102
+ @Req() req: FastifyRequest,
103
+ @Res({ passthrough: true }) res: FastifyReply,
104
+ ): Promise<void> {
105
+ try {
106
+ const data = await req.file()
107
+ if (!data) {
108
+ throw new Error('No file uploaded')
109
+ }
110
+
111
+ const fileBuffer = await toBufferWithLimit(data.file, MAX_IMPORT_FILE_SIZE_BYTES)
112
+ if (fileBuffer.length === 0) {
113
+ throw new Error('Uploaded file is empty')
114
+ }
115
+
116
+ const result = await this.excelIoService.previewImportExcel({
117
+ data: fileBuffer,
118
+ filename: data.filename,
119
+ includeDetailedUnchangedRecords: parseMultipartBooleanField(
120
+ data.fields,
121
+ 'includeDetailedUnchangedRecords',
122
+ false,
123
+ ),
124
+ })
125
+
126
+ res.status(200).send({ success: true, result })
127
+ } catch (error) {
128
+ this.logger.error('Error previewing Excel import', error)
129
+ res.status(500).send({ success: false, error: error instanceof Error ? error.message : String(error) })
130
+ }
131
+ }
132
+ }
133
+
134
+ async function toBufferWithLimit(stream: AsyncIterable<unknown>, maxBytes: number): Promise<Buffer> {
135
+ const chunks: Buffer[] = []
136
+ let size = 0
137
+
138
+ for await (const chunk of stream) {
139
+ const buffer = toBuffer(chunk)
140
+ size += buffer.length
141
+ if (size > maxBytes) {
142
+ throw new Error(`Uploaded file exceeds ${Math.floor(maxBytes / (1024 * 1024))} MB limit`)
143
+ }
144
+ chunks.push(buffer)
145
+ }
146
+
147
+ return Buffer.concat(chunks)
148
+ }
149
+
150
+ function toBuffer(chunk: unknown): Buffer {
151
+ if (Buffer.isBuffer(chunk)) {
152
+ return chunk
153
+ }
154
+ if (chunk instanceof Uint8Array) {
155
+ return Buffer.from(chunk)
156
+ }
157
+ return Buffer.from(String(chunk))
158
+ }
159
+
160
+ function parseJsonQuery(value?: string): unknown {
161
+ if (!value) {
162
+ return undefined
163
+ }
164
+
165
+ try {
166
+ return JSON.parse(value)
167
+ } catch (error) {
168
+ throw new Error(`Invalid JSON query parameter: ${error instanceof Error ? error.message : String(error)}`)
169
+ }
170
+ }
171
+
172
+ function createContentDisposition(filename: string): string {
173
+ const asciiFilename = filename.replaceAll(/[^A-Za-z0-9._-]/g, '_')
174
+ const encodedFilename = encodeURIComponent(filename)
175
+
176
+ return `attachment; filename="${asciiFilename}"; filename*=UTF-8''${encodedFilename}`
177
+ }
178
+
179
+ function parseMultipartBooleanField(fields: unknown, key: string, defaultValue: boolean): boolean {
180
+ if (!fields || typeof fields !== 'object') {
181
+ return defaultValue
182
+ }
183
+
184
+ const fieldValue = (fields as Record<string, unknown>)[key]
185
+ if (!fieldValue || typeof fieldValue !== 'object') {
186
+ return defaultValue
187
+ }
188
+
189
+ const value = 'value' in fieldValue ? (fieldValue as { value?: unknown }).value : undefined
190
+ if (typeof value !== 'string') {
191
+ return defaultValue
192
+ }
193
+
194
+ return value.toLowerCase() === 'true'
195
+ }
@@ -0,0 +1,17 @@
1
+ import { ActionsModule } from '@actions/actions.module'
2
+ import { AuthGuard } from '@authentication/auth.guard'
3
+ import { ImportModule } from '@import/import.module'
4
+ import { forwardRef, Module } from '@nestjs/common'
5
+ import { ViewModule } from '@view/view.module'
6
+ import { XlPortModule } from '@xlport/xlport.module'
7
+
8
+ import { ExcelIoController } from './excel-io.controller'
9
+ import { ExcelIoService } from './excel-io.service'
10
+
11
+ @Module({
12
+ imports: [ViewModule, XlPortModule, forwardRef(() => ActionsModule), forwardRef(() => ImportModule)],
13
+ controllers: [ExcelIoController],
14
+ providers: [ExcelIoService, AuthGuard],
15
+ exports: [ExcelIoService],
16
+ })
17
+ export class ExcelIoModule {}
@@ -171,8 +171,9 @@ export async function ${detectDelta._utils.detectModelDelta.name}<Model extends
171
171
 
172
172
  /**
173
173
  * Generic function to determine what action (create/update/delete/unchanged) to take with an item.
174
- * When both id and keyField are provided, id takes precedence for looking up existing records.
175
- * Falls back to keyField when id is not provided.
174
+ * For models with a non-id keyField (e.g. slug), keyField lookup is preferred and item.id is
175
+ * synchronized to the matched existing entity.
176
+ * For id-keyed models, id lookup is used as primary strategy.
176
177
  *
177
178
  * In case the type is update it also includes the delta for a given item. In case of delete, it also includes
178
179
  * the existing item.
@@ -180,7 +181,7 @@ export async function ${detectDelta._utils.detectModelDelta.name}<Model extends
180
181
  * **Important**: The function does not implement any logic to automatically determine if an item should be deleted.
181
182
  * Implement this logic based on the business requirements!
182
183
  */
183
- async function determineDeltaResult<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}, KeyFieldName extends keyof Model>({
184
+ async function determineDeltaResult<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.idType.name}, KeyFieldName extends keyof Model>({ // NOSONAR
184
185
  item,
185
186
  keyFieldName,
186
187
  getById,
@@ -193,7 +194,25 @@ async function determineDeltaResult<Model extends ${dto.genericModel.name}<ID>,
193
194
  getByKey: (key: Model[KeyFieldName]) => Promise<Model | null>
194
195
  getDelta: ({ item, existingItem }: { item: Model; existingItem: Model }) => ${delta_Fields.name}<Model, ID>
195
196
  }): Promise<Delta_Result<Model, ID>> {
196
- // First, check if id is provided - it takes precedence over keyField
197
+ const keyValue = normalizeKeyValue(item[keyFieldName])
198
+ const isIdKeyField = keyFieldName === ('id' as KeyFieldName)
199
+
200
+ // For non-id key fields (e.g. slug), prefer key lookup to avoid stale/generated IDs
201
+ // forcing false create behavior.
202
+ if (!isIdKeyField && keyValue !== undefined && keyValue !== null && keyValue !== '') {
203
+ const existingItemByKey = await findExistingByKeyValue({ keyValue, getByKey })
204
+ if (existingItemByKey) {
205
+ item.id = existingItemByKey.id
206
+ const delta = getDelta({ item, existingItem: existingItemByKey })
207
+ if (Object.keys(delta).length > 0) {
208
+ return { type: '${update.discriminant}', delta, existingItem: existingItemByKey, updatedItem: item }
209
+ }
210
+ return { type: '${unchanged.discriminant}' }
211
+ }
212
+ }
213
+
214
+ // Check if id is provided - this is the primary lookup for id-keyed models
215
+ // and the fallback for non-id key models.
197
216
  const idValue = item.id
198
217
  if (idValue !== undefined && idValue !== null && idValue !== '') {
199
218
  const existingItemById = await getById(idValue)
@@ -204,12 +223,25 @@ async function determineDeltaResult<Model extends ${dto.genericModel.name}<ID>,
204
223
  }
205
224
  return { type: '${unchanged.discriminant}' }
206
225
  }
207
- // If id is provided but not found, treat as create (id might be pre-assigned)
226
+ // If id is provided but not found, fall back to keyField lookup.
227
+ // This prevents false creates when imported files contain stale/generated IDs
228
+ // but a stable keyField (e.g. slug) still matches an existing record.
229
+ if (keyValue !== undefined && keyValue !== null && keyValue !== '') {
230
+ const existingItemByKey = await findExistingByKeyValue({ keyValue, getByKey })
231
+ if (existingItemByKey) {
232
+ item.id = existingItemByKey.id
233
+ const delta = getDelta({ item, existingItem: existingItemByKey })
234
+ if (Object.keys(delta).length > 0) {
235
+ return { type: '${update.discriminant}', delta, existingItem: existingItemByKey, updatedItem: item }
236
+ }
237
+ return { type: '${unchanged.discriminant}' }
238
+ }
239
+ }
240
+ // No item found by id or keyField: treat as create (id might be pre-assigned).
208
241
  return { type: '${create.discriminant}', newItem: item }
209
242
  }
210
243
 
211
244
  // Fall back to keyField lookup when id is not provided
212
- const keyValue = item[keyFieldName]
213
245
 
214
246
  // If the key field value is also missing or empty, treat as a new record to create
215
247
  if (keyValue === undefined || keyValue === null || keyValue === '') {
@@ -217,11 +249,12 @@ async function determineDeltaResult<Model extends ${dto.genericModel.name}<ID>,
217
249
  }
218
250
 
219
251
  // Look up existing record by key field value
220
- const existingItem = await getByKey(keyValue)
252
+ const existingItem = await findExistingByKeyValue({ keyValue, getByKey })
221
253
 
222
254
  if (!existingItem) {
223
255
  return { type: '${create.discriminant}', newItem: item }
224
256
  }
257
+ item.id = existingItem.id
225
258
 
226
259
  const delta = getDelta({ item, existingItem })
227
260
 
@@ -270,12 +303,55 @@ function addDelta<Model extends ${dto.genericModel.name}<ID>, ID extends ${dto.i
270
303
  item: Model
271
304
  property: keyof Omit<Model, 'id'>
272
305
  }): void {
273
- if (existingItem[property] === item[property]) {
306
+ // createdAt is system-managed and should not trigger user-facing update deltas during import preview.
307
+ if (property === 'createdAt') {
308
+ return
309
+ }
310
+ if (areValuesEqual(existingItem[property], item[property])) {
274
311
  return
275
312
  }
276
313
  delta[property] = { old: existingItem[property], new: item[property] }
277
314
  }
278
315
 
316
+ function areValuesEqual(left: unknown, right: unknown): boolean {
317
+ if (left === right) {
318
+ return true
319
+ }
320
+
321
+ if (left === null || left === undefined || right === null || right === undefined) {
322
+ return false
323
+ }
324
+
325
+ if (left instanceof Date && right instanceof Date) {
326
+ return left.getTime() === right.getTime()
327
+ }
328
+
329
+ if (Array.isArray(left) || Array.isArray(right)) {
330
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
331
+ return false
332
+ }
333
+ return left.every((item, index) => areValuesEqual(item, right[index]))
334
+ }
335
+
336
+ if (isPlainObject(left) || isPlainObject(right)) {
337
+ if (!isPlainObject(left) || !isPlainObject(right)) {
338
+ return false
339
+ }
340
+ const leftKeys = Object.keys(left)
341
+ const rightKeys = Object.keys(right)
342
+ if (leftKeys.length !== rightKeys.length) {
343
+ return false
344
+ }
345
+ return leftKeys.every((key) => areValuesEqual(left[key], right[key]))
346
+ }
347
+
348
+ return false
349
+ }
350
+
351
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
352
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
353
+ }
354
+
279
355
  /**
280
356
  * Converts a Delta to an UpdateDto.
281
357
  */
@@ -318,7 +394,7 @@ function getValidKeyValue<Model, KeyFieldName extends keyof Model>(
318
394
  item: Model,
319
395
  keyFieldName: KeyFieldName,
320
396
  ): string | number | null {
321
- const keyValue = item[keyFieldName] as string | number | undefined | null
397
+ const keyValue = normalizeKeyValue(item[keyFieldName]) as string | number | undefined | null
322
398
 
323
399
  if (keyValue === undefined || keyValue === null || keyValue === '') {
324
400
  return null
@@ -327,6 +403,13 @@ function getValidKeyValue<Model, KeyFieldName extends keyof Model>(
327
403
  return keyValue
328
404
  }
329
405
 
406
+ function normalizeKeyValue<Key>(value: Key): Key {
407
+ if (typeof value === 'string') {
408
+ return value.trim() as Key
409
+ }
410
+ return value
411
+ }
412
+
330
413
  /**
331
414
  * Assigns an ID to an item by either finding existing entity or generating new ID
332
415
  */
@@ -348,10 +431,58 @@ async function assignIdToItem<Model extends ${dto.genericModel.name}<ID>, ID ext
348
431
  }
349
432
 
350
433
  // Try to find existing entity by key
351
- const existingItem = await getByKey(String(keyValue))
434
+ const existingItem = await findByKeyWithNormalization({ keyValue, getByKey })
352
435
  item.id = existingItem ? existingItem.id : generateId()
353
436
  }
354
437
 
438
+ async function findByKeyWithNormalization<Model>({
439
+ keyValue,
440
+ getByKey,
441
+ }: {
442
+ keyValue: string | number
443
+ getByKey: (key: string) => Promise<Model | null>
444
+ }): Promise<Model | null> {
445
+ for (const key of buildKeyCandidates(keyValue)) {
446
+ const existingItem = await getByKey(key)
447
+ if (existingItem) {
448
+ return existingItem
449
+ }
450
+ }
451
+ return null
452
+ }
453
+
454
+ async function findExistingByKeyValue<Model, Key>({
455
+ keyValue,
456
+ getByKey,
457
+ }: {
458
+ keyValue: Key
459
+ getByKey: (key: Key) => Promise<Model | null>
460
+ }): Promise<Model | null> {
461
+ if (typeof keyValue === 'string') {
462
+ for (const key of buildKeyCandidates(keyValue)) {
463
+ const existingItem = await getByKey(key as Key)
464
+ if (existingItem) {
465
+ return existingItem
466
+ }
467
+ }
468
+ return null
469
+ }
470
+
471
+ return getByKey(keyValue)
472
+ }
473
+
474
+ function buildKeyCandidates(keyValue: string | number): string[] {
475
+ const value = String(keyValue)
476
+ const candidates = [
477
+ value,
478
+ value.trim(),
479
+ value.trim().normalize('NFKC'),
480
+ value.trim().normalize('NFKC').replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, ''),
481
+ value.trim().normalize('NFKC').replaceAll(/[\\u200B-\\u200D\\uFEFF]/g, '').toLowerCase(),
482
+ ]
483
+ return [...new Set(candidates.filter((candidate) => candidate !== ''))]
484
+ }
485
+
355
486
  /**
356
487
  * Adds an item to the ID and key lookup maps
357
488
  */
@@ -422,9 +553,13 @@ export async function ${detectDelta._utils.ensureItemsHaveIds.name}<
422
553
  }
423
554
 
424
555
  for (const item of items) {
425
- // Assign ID if needed
426
- if (needsIdAssignment(item.id)) {
427
- const keyValue = getValidKeyValue(item, keyFieldName)
556
+ const keyValue = getValidKeyValue(item, keyFieldName)
557
+ const isIdKeyField = keyFieldName === ('id' as KeyFieldName)
558
+
559
+ // For non-id key fields (e.g. slug), always reconcile ID from key lookup.
560
+ // This prevents stale/random IDs from forcing incorrect create behavior.
561
+ if ((!isIdKeyField && keyValue !== null) || needsIdAssignment(item.id)) {
562
+ // For non-id key fields always reconcile from key; otherwise only when ID is missing.
428
563
  await assignIdToItem({ item, keyValue, getByKey, generateId })
429
564
  }
430
565
 
@@ -1 +1 @@
1
- {"version":3,"file":"detect-delta-functions.generator.js","sourceRoot":"","sources":["../../../../src/backend-import/generators/detect-delta/detect-delta-functions.generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"detect-delta-functions.generator.js","sourceRoot":"","sources":["../../../../src/backend-import/generators/detect-delta/detect-delta-functions.generator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { ContextResult } from '../import.generator';
2
+ export declare function generateFilterErrorRows(context: ContextResult): string;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateFilterErrorRows = generateFilterErrorRows;
4
+ function generateFilterErrorRows(context) {
5
+ const deltaTypeName = context.import.detectDelta.allModels.type.name;
6
+ return `
7
+ import type { ${deltaTypeName} } from './detect-delta/models.detect-delta'
8
+
9
+ /**
10
+ * Returns a new Delta with all 'errors' rows removed from each model.
11
+ * Used when ignoreErrors is enabled to skip errored rows during execution.
12
+ */
13
+ export function filterErrorRows(delta: ${deltaTypeName}): ${deltaTypeName} {
14
+ const result: ${deltaTypeName} = {}
15
+
16
+ for (const [key, items] of Object.entries(delta) as [keyof ${deltaTypeName}, ${deltaTypeName}[keyof ${deltaTypeName}]][]) {
17
+ if (!items) continue
18
+ const filtered = items.filter((item: { type: string }) => item.type !== 'errors')
19
+ if (filtered.length > 0) {
20
+ ;(result as Record<string, unknown>)[key] = filtered
21
+ }
22
+ }
23
+
24
+ return result
25
+ }
26
+ `;
27
+ }
28
+ //# sourceMappingURL=filter-error-rows.generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-error-rows.generator.js","sourceRoot":"","sources":["../../../src/backend-import/generators/filter-error-rows.generator.ts"],"names":[],"mappings":";;AAEA,0DAwBC;AAxBD,SAAgB,uBAAuB,CAAC,OAAsB;IAC5D,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAA;IAEpE,OAAO;gBACO,aAAa;;;;;;yCAMY,aAAa,MAAM,aAAa;kBACvD,aAAa;;+DAEgC,aAAa,KAAK,aAAa,UAAU,aAAa;;;;;;;;;;CAUpH,CAAA;AACD,CAAC"}