@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.
- package/dist/backend-authentication/generators/authentication-service.generator.js +1 -1
- package/dist/backend-core/backend.generator.js +0 -4
- package/dist/backend-core/backend.generator.js.map +1 -1
- package/dist/backend-core/modules/backend-module-xlport.generator.js +1 -1
- package/dist/backend-excel-io/excel-io.generator.d.ts +19 -0
- package/dist/backend-excel-io/excel-io.generator.js +106 -0
- package/dist/backend-excel-io/excel-io.generator.js.map +1 -0
- package/dist/backend-excel-io/generators/excel-io-service.generator.d.ts +9 -0
- package/dist/backend-excel-io/generators/excel-io-service.generator.js +680 -0
- package/dist/backend-excel-io/generators/excel-io-service.generator.js.map +1 -0
- package/dist/backend-excel-io/index.d.ts +2 -0
- package/dist/backend-excel-io/index.js +22 -0
- package/dist/backend-excel-io/index.js.map +1 -0
- package/dist/backend-excel-io/template/README.md +24 -0
- package/dist/backend-excel-io/template/excel-io.controller.ts +195 -0
- package/dist/backend-excel-io/template/excel-io.module.ts +17 -0
- package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js +148 -13
- package/dist/backend-import/generators/detect-delta/detect-delta-functions.generator.js.map +1 -1
- package/dist/backend-import/generators/filter-error-rows.generator.d.ts +2 -0
- package/dist/backend-import/generators/filter-error-rows.generator.js +28 -0
- package/dist/backend-import/generators/filter-error-rows.generator.js.map +1 -0
- package/dist/backend-import/generators/import-service.generator.js +126 -2
- package/dist/backend-import/generators/import-service.generator.js.map +1 -1
- package/dist/backend-import/import.generator.js +2 -0
- package/dist/backend-import/import.generator.js.map +1 -1
- package/dist/backend-repositories/generators/model-repository.generator.js +17 -2
- package/dist/backend-repositories/generators/model-repository.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/app-routes.generator.js +5 -0
- package/dist/backend-router-trpc/generators/app-routes.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/excel-io-route.generator.d.ts +4 -0
- package/dist/backend-router-trpc/generators/excel-io-route.generator.js +35 -0
- package/dist/backend-router-trpc/generators/excel-io-route.generator.js.map +1 -0
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js +6 -0
- package/dist/backend-router-trpc/generators/trpc-plugin.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js +8 -1
- package/dist/backend-router-trpc/generators/trpc-router-module.generator.js.map +1 -1
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js +9 -0
- package/dist/backend-router-trpc/generators/trpc-shared.generator.js.map +1 -1
- package/dist/backend-router-trpc/router-trpc.generator.d.ts +2 -1
- package/dist/backend-router-trpc/router-trpc.generator.js +4 -0
- package/dist/backend-router-trpc/router-trpc.generator.js.map +1 -1
- package/dist/backend-update/model-update-service.generator.js +54 -0
- package/dist/backend-update/model-update-service.generator.js.map +1 -1
- package/dist/backend-update/update-actions.decoders.d.ts +2 -0
- package/dist/backend-update/update-actions.decoders.js +2 -0
- package/dist/backend-update/update-actions.decoders.js.map +1 -1
- package/dist/backend-update/update.generator.js +6 -0
- package/dist/backend-update/update.generator.js.map +1 -1
- package/dist/base/base.generator.js +0 -4
- package/dist/base/base.generator.js.map +1 -1
- package/dist/decoders/datamodel-decoder.generator.js +91 -1
- package/dist/decoders/datamodel-decoder.generator.js.map +1 -1
- package/dist/decoders/decoders.generator.d.ts +9 -0
- package/dist/decoders/decoders.generator.js +15 -0
- package/dist/decoders/decoders.generator.js.map +1 -1
- package/dist/decoders/discriminated-union.decoder.generator.js +4 -3
- package/dist/decoders/discriminated-union.decoder.generator.js.map +1 -1
- package/dist/devops/devops.generator.d.ts +5 -1
- package/dist/devops/devops.generator.js +5 -4
- package/dist/devops/devops.generator.js.map +1 -1
- package/dist/devops/generators/docker-compose-yml.generator.d.ts +1 -1
- package/dist/devops/generators/docker-compose-yml.generator.js +16 -1
- package/dist/devops/generators/docker-compose-yml.generator.js.map +1 -1
- package/dist/e2e/template/e2e/specs/example.spec.ts-snapshots/Navigate-to-homepage-and-take-snapshot-1-chromium-linux.png +0 -0
- package/dist/frontend-actions/generators/filter-utils.generator.js +1 -1
- package/dist/frontend-admin/admin.generator.d.ts +2 -0
- package/dist/frontend-admin/admin.generator.js +28 -0
- package/dist/frontend-admin/admin.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-global-actions.generator.js +78 -0
- package/dist/frontend-admin/generators/admin-global-actions.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-overview-page.generator.js +21 -1
- package/dist/frontend-admin/generators/admin-overview-page.generator.js.map +1 -1
- package/dist/frontend-admin/generators/admin-sidebar.generator.js +20 -0
- package/dist/frontend-admin/generators/admin-sidebar.generator.js.map +1 -1
- package/dist/frontend-admin/generators/excel-io-page.generator.d.ts +4 -0
- package/dist/frontend-admin/generators/excel-io-page.generator.js +258 -0
- package/dist/frontend-admin/generators/excel-io-page.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page-result-stage.generator.d.ts +1 -0
- package/dist/frontend-admin/generators/import-review-page-result-stage.generator.js +104 -0
- package/dist/frontend-admin/generators/import-review-page-result-stage.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page-review-stage.generator.d.ts +1 -0
- package/dist/frontend-admin/generators/import-review-page-review-stage.generator.js +1031 -0
- package/dist/frontend-admin/generators/import-review-page-review-stage.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.d.ts +1 -0
- package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.js +77 -0
- package/dist/frontend-admin/generators/import-review-page-upload-stage.generator.js.map +1 -0
- package/dist/frontend-admin/generators/import-review-page.generator.d.ts +7 -0
- package/dist/frontend-admin/generators/import-review-page.generator.js +180 -0
- package/dist/frontend-admin/generators/import-review-page.generator.js.map +1 -0
- package/dist/frontend-admin/generators/model-admin-page.generator.js +60 -56
- package/dist/frontend-admin/generators/model-admin-page.generator.js.map +1 -1
- package/dist/frontend-admin/utils.js +25 -33
- package/dist/frontend-admin/utils.js.map +1 -1
- package/dist/frontend-core/frontend.generator.js +1 -2
- package/dist/frontend-core/frontend.generator.js.map +1 -1
- package/dist/frontend-core/template/src/components/admin/excel-io-actions.tsx +64 -0
- package/dist/frontend-core/template/src/components/admin/table-view-panel.tsx +41 -3
- package/dist/frontend-core/template/src/hooks/use-excel-io.ts +137 -0
- package/dist/frontend-core/template/src/hooks/use-import-review.ts +143 -0
- package/dist/frontend-core/template/src/lib/excel-download.ts +28 -0
- package/dist/frontend-core/types/hook.d.ts +1 -1
- package/dist/frontend-tables/generators/model-table.generator.js +21 -13
- package/dist/frontend-tables/generators/model-table.generator.js.map +1 -1
- package/dist/frontend-trpc-client/generators/model-hook.generator.js +4 -0
- package/dist/frontend-trpc-client/generators/model-hook.generator.js.map +1 -1
- package/dist/frontend-trpc-client/trpc-client.generator.d.ts +4 -0
- package/dist/frontend-trpc-client/trpc-client.generator.js +1 -0
- package/dist/frontend-trpc-client/trpc-client.generator.js.map +1 -1
- package/dist/generators.js +2 -0
- package/dist/generators.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/seed-data/seed-data.generator.d.ts +3 -0
- package/dist/seed-data/seed-data.generator.js +45 -1
- package/dist/seed-data/seed-data.generator.js.map +1 -1
- 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
|
-
*
|
|
175
|
-
*
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,
|
|
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,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"}
|