@open-mercato/shared 0.6.1-develop.3090.06ab462170 → 0.6.1-develop.3110.780b0b618f
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/lib/crud/custom-fields.js +12 -0
- package/dist/lib/crud/custom-fields.js.map +2 -2
- package/dist/lib/crud/factory.js +4 -6
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +5 -5
- package/src/lib/crud/__tests__/custom-fields.test.ts +57 -0
- package/src/lib/crud/custom-fields.ts +26 -0
- package/src/lib/crud/factory.ts +12 -6
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.1-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.1-develop.3110.780b0b618f'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.6.1-develop.
|
|
3
|
+
"version": "0.6.1-develop.3110.780b0b618f",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
}
|
|
90
90
|
},
|
|
91
91
|
"dependencies": {
|
|
92
|
-
"@mikro-orm/core": "^7.0.
|
|
93
|
-
"@mikro-orm/decorators": "^7.0.
|
|
94
|
-
"@mikro-orm/postgresql": "^7.0.
|
|
95
|
-
"@open-mercato/cache": "0.6.1-develop.
|
|
92
|
+
"@mikro-orm/core": "^7.0.14",
|
|
93
|
+
"@mikro-orm/decorators": "^7.0.14",
|
|
94
|
+
"@mikro-orm/postgresql": "^7.0.14",
|
|
95
|
+
"@open-mercato/cache": "0.6.1-develop.3110.780b0b618f",
|
|
96
96
|
"dotenv": "^17.4.2",
|
|
97
97
|
"rate-limiter-flexible": "^11.0.1",
|
|
98
98
|
"reflect-metadata": "^0.2.2",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
applyCustomFieldsNormalization,
|
|
2
3
|
buildCustomFieldFiltersFromQuery,
|
|
3
4
|
decorateRecordWithCustomFields,
|
|
4
5
|
extractAllCustomFieldEntries,
|
|
@@ -298,6 +299,62 @@ describe('decorateRecordWithCustomFields', () => {
|
|
|
298
299
|
})
|
|
299
300
|
})
|
|
300
301
|
|
|
302
|
+
describe('applyCustomFieldsNormalization', () => {
|
|
303
|
+
const definitionIndex: CustomFieldDefinitionIndex = new Map([
|
|
304
|
+
[
|
|
305
|
+
'priority',
|
|
306
|
+
[
|
|
307
|
+
{
|
|
308
|
+
key: 'priority',
|
|
309
|
+
label: 'Priority',
|
|
310
|
+
kind: 'integer',
|
|
311
|
+
multi: false,
|
|
312
|
+
organizationId: null,
|
|
313
|
+
tenantId: null,
|
|
314
|
+
priority: 0,
|
|
315
|
+
updatedAt: 1,
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
],
|
|
319
|
+
])
|
|
320
|
+
|
|
321
|
+
it('preserves cf_* keys by default for backward compatibility', () => {
|
|
322
|
+
const record = { id: 'r-1', name: 'Item', cf_priority: 5, 'cf:priority': 5 }
|
|
323
|
+
const decorated = decorateRecordWithCustomFields(record, definitionIndex, {})
|
|
324
|
+
const result = applyCustomFieldsNormalization(record, decorated)
|
|
325
|
+
|
|
326
|
+
expect(result.id).toBe('r-1')
|
|
327
|
+
expect(result.cf_priority).toBe(5)
|
|
328
|
+
expect(result['cf:priority']).toBe(5)
|
|
329
|
+
expect(result.customValues).toEqual({ priority: 5 })
|
|
330
|
+
expect(Array.isArray(result.customFields)).toBe(true)
|
|
331
|
+
expect((result.customFields as any[])[0]).toMatchObject({ key: 'priority', value: 5 })
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
it('strips cf_* and cf:* keys when stripPrefixedKeys is enabled (issue #1769)', () => {
|
|
335
|
+
const record = { id: 'r-1', name: 'Item', cf_priority: 5, 'cf:priority': 5 }
|
|
336
|
+
const decorated = decorateRecordWithCustomFields(record, definitionIndex, {})
|
|
337
|
+
const result = applyCustomFieldsNormalization(record, decorated, { stripPrefixedKeys: true })
|
|
338
|
+
|
|
339
|
+
expect(result.id).toBe('r-1')
|
|
340
|
+
expect(result.name).toBe('Item')
|
|
341
|
+
expect('cf_priority' in result).toBe(false)
|
|
342
|
+
expect('cf:priority' in result).toBe(false)
|
|
343
|
+
expect(result.customValues).toEqual({ priority: 5 })
|
|
344
|
+
expect(Array.isArray(result.customFields)).toBe(true)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('emits null customValues when no active definitions match', () => {
|
|
348
|
+
const record = { id: 'r-1', cf_unknown: 'leftover' }
|
|
349
|
+
const decorated = decorateRecordWithCustomFields(record, new Map(), {})
|
|
350
|
+
const result = applyCustomFieldsNormalization(record, decorated, { stripPrefixedKeys: true })
|
|
351
|
+
|
|
352
|
+
expect(result.customValues).toBeNull()
|
|
353
|
+
expect(result.customFields).toEqual([])
|
|
354
|
+
expect('cf_unknown' in result).toBe(false)
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
301
358
|
describe('loadCustomFieldDefinitionIndex', () => {
|
|
302
359
|
it('filters definition summaries by selected fieldset membership', async () => {
|
|
303
360
|
const em = mockEntityManager([
|
|
@@ -406,6 +406,32 @@ export async function loadCustomFieldDefinitionIndex(opts: {
|
|
|
406
406
|
return index
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
export type ApplyCustomFieldsNormalizationOptions = {
|
|
410
|
+
/**
|
|
411
|
+
* When true, removes raw `cf_*` and `cf:*` keys from the record once they
|
|
412
|
+
* have been extracted into `customValues` / `customFields`. Produces a single
|
|
413
|
+
* canonical response shape (issue #1769). Defaults to `false` to preserve the
|
|
414
|
+
* existing wire format for callers that read `cf_*` from the top level.
|
|
415
|
+
*/
|
|
416
|
+
stripPrefixedKeys?: boolean
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function applyCustomFieldsNormalization(
|
|
420
|
+
record: Record<string, unknown>,
|
|
421
|
+
decorated: CustomFieldDisplayPayload,
|
|
422
|
+
options: ApplyCustomFieldsNormalizationOptions = {},
|
|
423
|
+
): Record<string, unknown> {
|
|
424
|
+
const stripPrefixedKeys = options.stripPrefixedKeys === true
|
|
425
|
+
const base: Record<string, unknown> = {}
|
|
426
|
+
for (const [key, value] of Object.entries(record)) {
|
|
427
|
+
if (stripPrefixedKeys && (key.startsWith('cf_') || key.startsWith('cf:'))) continue
|
|
428
|
+
base[key] = value
|
|
429
|
+
}
|
|
430
|
+
base.customValues = decorated.customValues
|
|
431
|
+
base.customFields = decorated.customFields
|
|
432
|
+
return base
|
|
433
|
+
}
|
|
434
|
+
|
|
409
435
|
export function decorateRecordWithCustomFields(
|
|
410
436
|
record: Record<string, unknown>,
|
|
411
437
|
definitions: CustomFieldDefinitionIndex,
|
package/src/lib/crud/factory.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
extractCustomFieldValuesFromPayload,
|
|
31
31
|
extractAllCustomFieldEntries,
|
|
32
32
|
decorateRecordWithCustomFields,
|
|
33
|
+
applyCustomFieldsNormalization,
|
|
33
34
|
loadCustomFieldDefinitionIndex,
|
|
34
35
|
} from './custom-fields'
|
|
35
36
|
import { serializeExport, normalizeExportFormat, defaultExportFilename, ensureColumns, type CrudExportFormat, type PreparedExport } from './exporters'
|
|
@@ -162,6 +163,14 @@ export type CustomFieldsConfig =
|
|
|
162
163
|
export type CrudListCustomFieldDecorator = {
|
|
163
164
|
entityIds: EntityId | EntityId[]
|
|
164
165
|
resolveContext?: (item: any, ctx: CrudCtx) => { organizationId?: string | null; tenantId?: string | null }
|
|
166
|
+
/**
|
|
167
|
+
* When true, the factory removes raw `cf_*` and `cf:*` keys from each list
|
|
168
|
+
* item after extracting them into `customValues` / `customFields`. Recommended
|
|
169
|
+
* for new modules — produces the single canonical response shape requested in
|
|
170
|
+
* #1769. Defaults to `false` so existing callers that read `cf_*` from the
|
|
171
|
+
* top level keep working until they migrate.
|
|
172
|
+
*/
|
|
173
|
+
stripPrefixedKeys?: boolean
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
export type ListConfig<TList> = {
|
|
@@ -924,12 +933,9 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
|
|
|
924
933
|
organizationId: organizationId ?? null,
|
|
925
934
|
tenantId: tenantId ?? null,
|
|
926
935
|
})
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
customFields: decorated.customFields,
|
|
931
|
-
}
|
|
932
|
-
return output
|
|
936
|
+
return applyCustomFieldsNormalization(item, decorated, {
|
|
937
|
+
stripPrefixedKeys: listCustomFieldDecorator.stripPrefixedKeys === true,
|
|
938
|
+
})
|
|
933
939
|
})
|
|
934
940
|
cfProfiler.mark('decorate_complete', { itemCount: decoratedItems.length })
|
|
935
941
|
endProfile({
|