@open-mercato/shared 0.6.8-develop.7099.1.7ba2771d06 → 0.7.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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +1 -4
- package/dist/lib/auth/jwt.js +0 -6
- package/dist/lib/auth/jwt.js.map +2 -2
- package/dist/lib/auth/organizationAccess.js +4 -7
- package/dist/lib/auth/organizationAccess.js.map +2 -2
- package/dist/lib/auth/server.js +7 -38
- package/dist/lib/auth/server.js.map +2 -2
- package/dist/lib/commands/command-bus.js +1 -6
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/crud/factory.js +8 -24
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/data/engine.js +2 -8
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/location/countries.js +0 -12
- package/dist/lib/location/countries.js.map +2 -2
- package/dist/lib/openapi/crud.js +1 -4
- package/dist/lib/openapi/crud.js.map +2 -2
- package/dist/lib/query/engine.js +34 -270
- package/dist/lib/query/engine.js.map +3 -3
- package/dist/lib/query/types.js.map +1 -1
- package/dist/lib/search/config.js +0 -1
- package/dist/lib/search/config.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/events/factory.js +15 -69
- package/dist/modules/events/factory.js.map +2 -2
- package/dist/modules/registry.js +0 -15
- package/dist/modules/registry.js.map +2 -2
- package/dist/modules/widgets/component-registry.js.map +2 -2
- package/package.json +3 -10
- package/src/lib/auth/__tests__/jwt.test.ts +0 -13
- package/src/lib/auth/__tests__/organizationAccess.test.ts +1 -36
- package/src/lib/auth/__tests__/server.apiKeyCache.test.ts +0 -324
- package/src/lib/auth/__tests__/server.test.ts +0 -104
- package/src/lib/auth/jwt.ts +0 -17
- package/src/lib/auth/organizationAccess.ts +3 -11
- package/src/lib/auth/server.ts +8 -78
- package/src/lib/commands/__tests__/command-bus.test.ts +0 -31
- package/src/lib/commands/command-bus.ts +1 -8
- package/src/lib/crud/__tests__/crud-factory.test.ts +0 -165
- package/src/lib/crud/factory.ts +7 -33
- package/src/lib/data/__tests__/engine.event-validation.test.ts +1 -9
- package/src/lib/data/engine.ts +1 -7
- package/src/lib/location/__tests__/countries.test.ts +0 -15
- package/src/lib/location/countries.ts +0 -17
- package/src/lib/openapi/crud.ts +0 -3
- package/src/lib/query/__tests__/engine.count-distinct.test.ts +15 -162
- package/src/lib/query/__tests__/engine.scope-and-or.test.ts +1 -11
- package/src/lib/query/__tests__/engine.test.ts +7 -445
- package/src/lib/query/engine.ts +54 -434
- package/src/lib/query/types.ts +0 -15
- package/src/lib/search/config.ts +0 -10
- package/src/modules/events/factory.ts +19 -111
- package/src/modules/events/types.ts +0 -17
- package/src/modules/registry.ts +0 -40
- package/src/modules/widgets/component-registry.ts +0 -14
- package/dist/lib/auth/mfaPendingAccess.js +0 -42
- package/dist/lib/auth/mfaPendingAccess.js.map +0 -7
- package/dist/lib/auth/principal-service.js +0 -1
- package/dist/lib/auth/principal-service.js.map +0 -7
- package/dist/lib/html/htmlToPlainText.js +0 -16
- package/dist/lib/html/htmlToPlainText.js.map +0 -7
- package/dist/lib/query/count-cap.js +0 -11
- package/dist/lib/query/count-cap.js.map +0 -7
- package/dist/lib/queue/dispatchOrigin.js +0 -20
- package/dist/lib/queue/dispatchOrigin.js.map +0 -7
- package/dist/lib/search/entityAccess.js +0 -44
- package/dist/lib/search/entityAccess.js.map +0 -7
- package/src/lib/auth/__tests__/mfaPendingAccess.test.ts +0 -69
- package/src/lib/auth/__tests__/principalServiceExport.test.ts +0 -67
- package/src/lib/auth/mfaPendingAccess.ts +0 -70
- package/src/lib/auth/principal-service.ts +0 -110
- package/src/lib/html/__tests__/htmlToPlainText.test.ts +0 -59
- package/src/lib/html/htmlToPlainText.ts +0 -17
- package/src/lib/query/__tests__/count-cap-plan.test.ts +0 -240
- package/src/lib/query/__tests__/count-cap.test.ts +0 -41
- package/src/lib/query/count-cap.ts +0 -19
- package/src/lib/queue/dispatchOrigin.ts +0 -35
- package/src/lib/search/entityAccess.ts +0 -132
- package/src/modules/events/__tests__/factory.test.ts +0 -88
|
@@ -49,37 +49,6 @@ describe('CommandBus', () => {
|
|
|
49
49
|
expect(logEntry).toEqual({ id: 'log-entry' })
|
|
50
50
|
})
|
|
51
51
|
|
|
52
|
-
it('records the system actor marker when a trusted command has no auth actor', async () => {
|
|
53
|
-
const logMock = jest.fn(async () => ({ id: 'system-log-entry' }))
|
|
54
|
-
registerCommand({
|
|
55
|
-
id: 'test.system-command',
|
|
56
|
-
execute: jest.fn(async () => ({ ok: true })),
|
|
57
|
-
buildLog: jest.fn(() => ({ actionLabel: 'System test', resourceKind: 'test', resourceId: 'system-123' })),
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
|
|
61
|
-
container.register({ actionLogService: asValue({ log: logMock }) })
|
|
62
|
-
|
|
63
|
-
const bus = new CommandBus()
|
|
64
|
-
const ctx = {
|
|
65
|
-
container,
|
|
66
|
-
auth: null,
|
|
67
|
-
organizationScope: null,
|
|
68
|
-
selectedOrganizationId: null,
|
|
69
|
-
organizationIds: null,
|
|
70
|
-
systemActor: true,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
await bus.execute('test.system-command', { input: {}, ctx })
|
|
74
|
-
|
|
75
|
-
expect(logMock).toHaveBeenCalledWith(
|
|
76
|
-
expect.objectContaining({
|
|
77
|
-
actorUserId: undefined,
|
|
78
|
-
context: { systemActor: 'system:command' },
|
|
79
|
-
})
|
|
80
|
-
)
|
|
81
|
-
})
|
|
82
|
-
|
|
83
52
|
it('passes captureAfter snapshot to buildLog as snapshots.after', async () => {
|
|
84
53
|
const logMock = jest.fn(async () => ({ id: 'log-entry-2' }))
|
|
85
54
|
const buildLogMock = jest.fn(() => ({
|
|
@@ -583,9 +583,6 @@ export class CommandBus {
|
|
|
583
583
|
const organizationId =
|
|
584
584
|
metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null
|
|
585
585
|
const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null
|
|
586
|
-
const systemActorContext = !actorUserId && options.ctx.systemActor === true
|
|
587
|
-
? { systemActor: 'system:command' }
|
|
588
|
-
: null
|
|
589
586
|
const payload: Record<string, unknown> = {
|
|
590
587
|
tenantId: tenantId ?? undefined,
|
|
591
588
|
organizationId: organizationId ?? undefined,
|
|
@@ -606,11 +603,7 @@ export class CommandBus {
|
|
|
606
603
|
if ('snapshotBefore' in metadata && metadata.snapshotBefore !== undefined) payload.snapshotBefore = metadata.snapshotBefore
|
|
607
604
|
if ('snapshotAfter' in metadata && metadata.snapshotAfter !== undefined) payload.snapshotAfter = metadata.snapshotAfter
|
|
608
605
|
if ('changes' in metadata && metadata.changes !== undefined && metadata.changes !== null) payload.changes = metadata.changes
|
|
609
|
-
if ('context' in metadata && metadata.context !== undefined && metadata.context !== null)
|
|
610
|
-
payload.context = { ...(systemActorContext ?? {}), ...metadata.context }
|
|
611
|
-
} else if (systemActorContext) {
|
|
612
|
-
payload.context = systemActorContext
|
|
613
|
-
}
|
|
606
|
+
if ('context' in metadata && metadata.context !== undefined && metadata.context !== null) payload.context = metadata.context
|
|
614
607
|
}
|
|
615
608
|
|
|
616
609
|
const redoEnvelope = wrapRedoPayload('commandPayload' in payload ? (payload.commandPayload as unknown) : undefined, options.input)
|
|
@@ -12,16 +12,6 @@ import {
|
|
|
12
12
|
import { loadCustomFieldDefinitionIndex } from '@open-mercato/shared/lib/crud/custom-fields'
|
|
13
13
|
import { registerMutationGuards } from '@open-mercato/shared/lib/crud/mutation-guard-store'
|
|
14
14
|
import { CommandInterceptorError } from '@open-mercato/shared/lib/commands/errors'
|
|
15
|
-
import {
|
|
16
|
-
registerLoggerExtension,
|
|
17
|
-
resetLoggerExtension,
|
|
18
|
-
type LoggerExtensionRecord,
|
|
19
|
-
} from '@open-mercato/shared/lib/logger'
|
|
20
|
-
import {
|
|
21
|
-
registerTelemetryRuntime,
|
|
22
|
-
resetTelemetryRuntime,
|
|
23
|
-
type TelemetryRuntime,
|
|
24
|
-
} from '@open-mercato/shared/lib/telemetry/runtime'
|
|
25
15
|
import { z } from 'zod'
|
|
26
16
|
|
|
27
17
|
// Keep the real custom-field helpers but spy on the definition loader so we can
|
|
@@ -278,28 +268,6 @@ describe('CRUD Factory', () => {
|
|
|
278
268
|
}))
|
|
279
269
|
})
|
|
280
270
|
|
|
281
|
-
it('GET spreads totalIsCapped only when the engine reports a capped count', async () => {
|
|
282
|
-
queryEngine.query.mockResolvedValueOnce({
|
|
283
|
-
items: [{ id: 'id-1', title: 'A', is_done: false }],
|
|
284
|
-
total: 10_000,
|
|
285
|
-
page: 1,
|
|
286
|
-
pageSize: 10,
|
|
287
|
-
meta: { listCountCapWarning: { entity: 'example.todo', cap: 10_000 } },
|
|
288
|
-
})
|
|
289
|
-
const res = await route.GET(new Request('http://x/api/example/todos?page=1&pageSize=10&sortField=id&sortDir=asc'))
|
|
290
|
-
expect(res.status).toBe(200)
|
|
291
|
-
const body = await res.json()
|
|
292
|
-
expect(body.total).toBe(10_000)
|
|
293
|
-
expect(body.totalIsCapped).toBe(true)
|
|
294
|
-
expect(body.meta.listCountCapWarning).toEqual({ entity: 'example.todo', cap: 10_000 })
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
it('GET omits totalIsCapped entirely for exact totals', async () => {
|
|
298
|
-
const res = await route.GET(new Request('http://x/api/example/todos?page=1&pageSize=10&sortField=id&sortDir=asc'))
|
|
299
|
-
const body = await res.json()
|
|
300
|
-
expect('totalIsCapped' in body).toBe(false)
|
|
301
|
-
})
|
|
302
|
-
|
|
303
271
|
const makeDecoratedRoute = () => makeCrudRoute({
|
|
304
272
|
metadata: { GET: { requireAuth: true } },
|
|
305
273
|
orm: { entity: Todo, idField: 'id', orgField: 'organizationId', tenantField: 'tenantId', softDeleteField: 'deletedAt' },
|
|
@@ -1086,7 +1054,6 @@ describe('CRUD Factory', () => {
|
|
|
1086
1054
|
await expect(res.json()).resolves.toEqual({
|
|
1087
1055
|
error: 'Internal server error',
|
|
1088
1056
|
message: 'Something went wrong. Please try again later.',
|
|
1089
|
-
requestId: expect.any(String),
|
|
1090
1057
|
})
|
|
1091
1058
|
})
|
|
1092
1059
|
|
|
@@ -1125,138 +1092,6 @@ describe('CRUD Factory', () => {
|
|
|
1125
1092
|
await expect(res.json()).resolves.toEqual({
|
|
1126
1093
|
error: 'Internal server error',
|
|
1127
1094
|
message: 'Something went wrong. Please try again later.',
|
|
1128
|
-
requestId: expect.any(String),
|
|
1129
|
-
})
|
|
1130
|
-
})
|
|
1131
|
-
|
|
1132
|
-
// Issue #5608 — a generic 500 must carry a requestId the client/support can cite, and
|
|
1133
|
-
// that same id must appear on the server log line so the two can be correlated.
|
|
1134
|
-
describe('generic 500 requestId correlation', () => {
|
|
1135
|
-
const logRecords: LoggerExtensionRecord[] = []
|
|
1136
|
-
const reportError = jest.fn()
|
|
1137
|
-
|
|
1138
|
-
const postWithRequestId = (requestId: string) => interceptorErrorRoute().POST(
|
|
1139
|
-
new Request('http://x/api/example/todos/command', {
|
|
1140
|
-
method: 'POST',
|
|
1141
|
-
body: JSON.stringify({ title: 'A' }),
|
|
1142
|
-
headers: { 'content-type': 'application/json', 'x-request-id': requestId },
|
|
1143
|
-
}),
|
|
1144
|
-
)
|
|
1145
|
-
|
|
1146
|
-
beforeEach(() => {
|
|
1147
|
-
logRecords.length = 0
|
|
1148
|
-
reportError.mockClear()
|
|
1149
|
-
registerLoggerExtension({ emit: (record) => logRecords.push(record) })
|
|
1150
|
-
registerTelemetryRuntime({
|
|
1151
|
-
canUseGlobalTracePropagation: () => false,
|
|
1152
|
-
captureTraceContext: () => ({}),
|
|
1153
|
-
continueTrace: (_carrier, _name, fn) => fn(),
|
|
1154
|
-
recordHttpDuration: () => {},
|
|
1155
|
-
reportError,
|
|
1156
|
-
shutdown: async () => {},
|
|
1157
|
-
} satisfies TelemetryRuntime)
|
|
1158
|
-
})
|
|
1159
|
-
|
|
1160
|
-
afterEach(() => {
|
|
1161
|
-
resetLoggerExtension()
|
|
1162
|
-
resetTelemetryRuntime()
|
|
1163
|
-
})
|
|
1164
|
-
|
|
1165
|
-
it('includes a requestId in the body that matches the server log line', async () => {
|
|
1166
|
-
commandBus.execute.mockRejectedValue(new Error('boom'))
|
|
1167
|
-
|
|
1168
|
-
const res = await postInterceptorErrorRequest(interceptorErrorRoute())
|
|
1169
|
-
const body = await res.json()
|
|
1170
|
-
|
|
1171
|
-
expect(res.status).toBe(500)
|
|
1172
|
-
expect(typeof body.requestId).toBe('string')
|
|
1173
|
-
expect(body.requestId.length).toBeGreaterThan(0)
|
|
1174
|
-
|
|
1175
|
-
const logRecord = logRecords.find((record) => record.message === 'Unexpected CRUD error')
|
|
1176
|
-
expect(logRecord?.fields.requestId).toBe(body.requestId)
|
|
1177
|
-
})
|
|
1178
|
-
|
|
1179
|
-
it('echoes the requestId on an x-request-id response header', async () => {
|
|
1180
|
-
commandBus.execute.mockRejectedValue(new Error('boom'))
|
|
1181
|
-
|
|
1182
|
-
const res = await postInterceptorErrorRequest(interceptorErrorRoute())
|
|
1183
|
-
const body = await res.json()
|
|
1184
|
-
|
|
1185
|
-
expect(res.headers.get('x-request-id')).toBe(body.requestId)
|
|
1186
|
-
})
|
|
1187
|
-
|
|
1188
|
-
it('reuses an inbound x-request-id header instead of generating a new one', async () => {
|
|
1189
|
-
commandBus.execute.mockRejectedValue(new Error('boom'))
|
|
1190
|
-
|
|
1191
|
-
const res = await postWithRequestId('req-fixed-123')
|
|
1192
|
-
const body = await res.json()
|
|
1193
|
-
|
|
1194
|
-
expect(res.status).toBe(500)
|
|
1195
|
-
expect(body.requestId).toBe('req-fixed-123')
|
|
1196
|
-
const logRecord = logRecords.find((record) => record.message === 'Unexpected CRUD error')
|
|
1197
|
-
expect(logRecord?.fields.requestId).toBe('req-fixed-123')
|
|
1198
|
-
})
|
|
1199
|
-
|
|
1200
|
-
// `Headers.get()` returns '' for an empty or whitespace-only header, which a plain
|
|
1201
|
-
// `?? randomUUID()` would hand straight through as a blank correlation id.
|
|
1202
|
-
it.each([
|
|
1203
|
-
['an empty inbound header', ''],
|
|
1204
|
-
['a whitespace-only inbound header', ' '],
|
|
1205
|
-
])('generates a fresh id for %s', async (_label, inbound) => {
|
|
1206
|
-
commandBus.execute.mockRejectedValue(new Error('boom'))
|
|
1207
|
-
|
|
1208
|
-
const res = await postWithRequestId(inbound)
|
|
1209
|
-
const body = await res.json()
|
|
1210
|
-
|
|
1211
|
-
expect(res.status).toBe(500)
|
|
1212
|
-
expect(typeof body.requestId).toBe('string')
|
|
1213
|
-
expect(body.requestId.length).toBeGreaterThan(0)
|
|
1214
|
-
const logRecord = logRecords.find((record) => record.message === 'Unexpected CRUD error')
|
|
1215
|
-
expect(logRecord?.fields.requestId).toBe(body.requestId)
|
|
1216
|
-
})
|
|
1217
|
-
|
|
1218
|
-
// A caller-controlled id lands verbatim in the unquoted `key=value` log line, so an
|
|
1219
|
-
// over-long one or one carrying spaces/`=` is discarded rather than echoed.
|
|
1220
|
-
it.each([
|
|
1221
|
-
['a value carrying log-field separators', 'a=1 tenantId=victim'],
|
|
1222
|
-
['an over-long value', 'x'.repeat(129)],
|
|
1223
|
-
])('discards %s in favor of a generated id', async (_label, inbound) => {
|
|
1224
|
-
commandBus.execute.mockRejectedValue(new Error('boom'))
|
|
1225
|
-
|
|
1226
|
-
const res = await postWithRequestId(inbound)
|
|
1227
|
-
const body = await res.json()
|
|
1228
|
-
|
|
1229
|
-
expect(res.status).toBe(500)
|
|
1230
|
-
expect(body.requestId).not.toBe(inbound)
|
|
1231
|
-
expect(body.requestId).toMatch(/^[A-Za-z0-9-]{36}$/)
|
|
1232
|
-
})
|
|
1233
|
-
|
|
1234
|
-
it('reports the error to telemetry with the same requestId', async () => {
|
|
1235
|
-
commandBus.execute.mockRejectedValue(new Error('boom'))
|
|
1236
|
-
|
|
1237
|
-
const res = await postWithRequestId('req-fixed-123')
|
|
1238
|
-
const body = await res.json()
|
|
1239
|
-
|
|
1240
|
-
expect(body.requestId).toBe('req-fixed-123')
|
|
1241
|
-
expect(reportError).toHaveBeenCalledTimes(1)
|
|
1242
|
-
expect(reportError).toHaveBeenCalledWith(
|
|
1243
|
-
expect.any(Error),
|
|
1244
|
-
{ module: 'crud', attributes: { requestId: 'req-fixed-123', errorName: 'Error' } },
|
|
1245
|
-
)
|
|
1246
|
-
})
|
|
1247
|
-
|
|
1248
|
-
// The 503/422 branches deliberately stay outside this change (issue #5608) — lock that
|
|
1249
|
-
// in so a later refactor cannot quietly widen the correlation id across every branch.
|
|
1250
|
-
it('leaves the interceptor-rejection branch without a requestId', async () => {
|
|
1251
|
-
commandBus.execute.mockRejectedValue(
|
|
1252
|
-
new CommandInterceptorError('Missing required fields: VAT id', { status: 422 }),
|
|
1253
|
-
)
|
|
1254
|
-
|
|
1255
|
-
const res = await postWithRequestId('req-fixed-123')
|
|
1256
|
-
|
|
1257
|
-
expect(res.status).toBe(422)
|
|
1258
|
-
await expect(res.json()).resolves.toEqual({ error: 'Missing required fields: VAT id' })
|
|
1259
|
-
expect(res.headers.get('x-request-id')).toBeNull()
|
|
1260
1095
|
})
|
|
1261
1096
|
})
|
|
1262
1097
|
|
package/src/lib/crud/factory.ts
CHANGED
|
@@ -75,8 +75,6 @@ import { createGenericOptimisticLockReader } from './optimistic-lock'
|
|
|
75
75
|
import { registerOptimisticLockReaderIfAbsent } from './optimistic-lock-store'
|
|
76
76
|
import { createLogger } from '../logger'
|
|
77
77
|
import { isTransientDbError } from '../db/pg-errors'
|
|
78
|
-
import { getTelemetryRuntime } from '../telemetry/runtime'
|
|
79
|
-
import { randomUUID } from 'node:crypto'
|
|
80
78
|
|
|
81
79
|
type RbacServiceLike = {
|
|
82
80
|
getGrantedFeatures: (userId: string, opts: { tenantId: string | null; organizationId: string | null }) => Promise<string[]>
|
|
@@ -596,19 +594,7 @@ function attachOperationHeader(res: Response, logEntry: any) {
|
|
|
596
594
|
return res
|
|
597
595
|
}
|
|
598
596
|
|
|
599
|
-
|
|
600
|
-
// looks like an id. `Headers.get()` yields '' for an empty or whitespace-only header —
|
|
601
|
-
// which `??` would not replace — and an unbounded value carrying spaces or `=` would
|
|
602
|
-
// forge fields in the unquoted `key=value` log line this id exists to be read from.
|
|
603
|
-
const INBOUND_REQUEST_ID_PATTERN = /^[A-Za-z0-9._-]{1,128}$/
|
|
604
|
-
|
|
605
|
-
function resolveRequestId(request?: Request): string {
|
|
606
|
-
const inbound = request?.headers.get('x-request-id')?.trim()
|
|
607
|
-
if (inbound && INBOUND_REQUEST_ID_PATTERN.test(inbound)) return inbound
|
|
608
|
-
return randomUUID()
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
function handleError(err: unknown, request?: Request): Response {
|
|
597
|
+
function handleError(err: unknown): Response {
|
|
612
598
|
if (err instanceof Response) return err
|
|
613
599
|
if (isCrudHttpError(err)) return json(err.body, { status: err.status })
|
|
614
600
|
// A command interceptor that blocked with an explicit status is a deliberate business
|
|
@@ -632,25 +618,14 @@ function handleError(err: unknown, request?: Request): Response {
|
|
|
632
618
|
)
|
|
633
619
|
}
|
|
634
620
|
|
|
635
|
-
// Unexpected exceptions still collapse into a generic 500 for the client (no internal
|
|
636
|
-
// detail leaked), but a requestId ties that response to this log line and to whatever
|
|
637
|
-
// reaches APM, so a client/support ticket citing it can be correlated with server-side
|
|
638
|
-
// detail (issue #5608).
|
|
639
621
|
const message = err instanceof Error ? err.message : undefined
|
|
640
622
|
const stack = err instanceof Error ? err.stack : undefined
|
|
641
|
-
|
|
642
|
-
const requestId = resolveRequestId(request)
|
|
643
|
-
logger.error('Unexpected CRUD error', { message, stack, err, requestId })
|
|
644
|
-
getTelemetryRuntime()?.reportError(err, {
|
|
645
|
-
module: 'crud',
|
|
646
|
-
attributes: { requestId, errorName },
|
|
647
|
-
})
|
|
623
|
+
logger.error('Unexpected CRUD error', { message, stack, err })
|
|
648
624
|
const body: Record<string, unknown> = {
|
|
649
625
|
error: 'Internal server error',
|
|
650
626
|
message: 'Something went wrong. Please try again later.',
|
|
651
|
-
requestId,
|
|
652
627
|
}
|
|
653
|
-
return json(body, { status: 500
|
|
628
|
+
return json(body, { status: 500 })
|
|
654
629
|
}
|
|
655
630
|
|
|
656
631
|
const LIFECYCLE_ACTION_MAP: Record<string, { before: string; after: string }> = {
|
|
@@ -1966,7 +1941,6 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
|
|
|
1966
1941
|
page: page.page || requestedPage,
|
|
1967
1942
|
pageSize: page.pageSize || requestedPageSize,
|
|
1968
1943
|
totalPages: Math.ceil(res.total / (Number(page.pageSize) || 1)),
|
|
1969
|
-
...(res.meta?.listCountCapWarning ? { totalIsCapped: true } : {}),
|
|
1970
1944
|
...(res.meta ? { meta: res.meta } : {}),
|
|
1971
1945
|
}
|
|
1972
1946
|
await opts.hooks?.afterList?.(payload, { ...ctx, query: validated as any })
|
|
@@ -2188,7 +2162,7 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
|
|
|
2188
2162
|
return response
|
|
2189
2163
|
} catch (e) {
|
|
2190
2164
|
finishProfile({ result: 'error' })
|
|
2191
|
-
return handleError(e
|
|
2165
|
+
return handleError(e)
|
|
2192
2166
|
}
|
|
2193
2167
|
}
|
|
2194
2168
|
|
|
@@ -2502,7 +2476,7 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
|
|
|
2502
2476
|
payload = await enrichSingleRecord(payload, ctx)
|
|
2503
2477
|
return json(payload, { status: 201 })
|
|
2504
2478
|
} catch (e) {
|
|
2505
|
-
return handleError(e
|
|
2479
|
+
return handleError(e)
|
|
2506
2480
|
}
|
|
2507
2481
|
}
|
|
2508
2482
|
|
|
@@ -2840,7 +2814,7 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
|
|
|
2840
2814
|
}
|
|
2841
2815
|
return json(payload)
|
|
2842
2816
|
} catch (e) {
|
|
2843
|
-
return handleError(e
|
|
2817
|
+
return handleError(e)
|
|
2844
2818
|
}
|
|
2845
2819
|
}
|
|
2846
2820
|
|
|
@@ -3129,7 +3103,7 @@ export function makeCrudRoute<TCreate = any, TUpdate = any, TList = any>(opts: C
|
|
|
3129
3103
|
}
|
|
3130
3104
|
return json(payload)
|
|
3131
3105
|
} catch (e) {
|
|
3132
|
-
return handleError(e
|
|
3106
|
+
return handleError(e)
|
|
3133
3107
|
}
|
|
3134
3108
|
}
|
|
3135
3109
|
|
|
@@ -68,15 +68,7 @@ describe('DataEngine event contract validation (issue #1421)', () => {
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
expect(emitted).toEqual([
|
|
71
|
-
expect.objectContaining({
|
|
72
|
-
name: 'issue1421_test.widget.created',
|
|
73
|
-
options: {
|
|
74
|
-
persistent: false,
|
|
75
|
-
tenantId: 'tenant-1',
|
|
76
|
-
organizationId: 'org-1',
|
|
77
|
-
emitterModuleId: 'issue1421_test',
|
|
78
|
-
},
|
|
79
|
-
}),
|
|
71
|
+
expect.objectContaining({ name: 'issue1421_test.widget.created' }),
|
|
80
72
|
])
|
|
81
73
|
expect(loggerWarn).not.toHaveBeenCalled()
|
|
82
74
|
} finally {
|
package/src/lib/data/engine.ts
CHANGED
|
@@ -226,12 +226,7 @@ export class DefaultDataEngine implements DataEngine {
|
|
|
226
226
|
const eventName = `${mod}.${ent}.updated`
|
|
227
227
|
warnIfUndeclaredEvent(eventName, 'setCustomFields')
|
|
228
228
|
try {
|
|
229
|
-
await bus.emitEvent(eventName, { id: recordId, organizationId, tenantId }, {
|
|
230
|
-
persistent: true,
|
|
231
|
-
tenantId,
|
|
232
|
-
organizationId,
|
|
233
|
-
emitterModuleId: mod,
|
|
234
|
-
})
|
|
229
|
+
await bus.emitEvent(eventName, { id: recordId, organizationId, tenantId }, { persistent: true })
|
|
235
230
|
} catch {
|
|
236
231
|
// non-blocking
|
|
237
232
|
}
|
|
@@ -632,7 +627,6 @@ export class DefaultDataEngine implements DataEngine {
|
|
|
632
627
|
persistent: !!events.persistent,
|
|
633
628
|
tenantId: ctx.identifiers.tenantId ?? null,
|
|
634
629
|
organizationId: ctx.identifiers.organizationId ?? null,
|
|
635
|
-
emitterModuleId: events.module,
|
|
636
630
|
})
|
|
637
631
|
} catch {
|
|
638
632
|
// non-blocking
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
COUNTRY_PRIORITY,
|
|
3
3
|
ISO_COUNTRIES,
|
|
4
4
|
buildCountryOptions,
|
|
5
|
-
matchCountryCodes,
|
|
6
5
|
resolveCountryName,
|
|
7
6
|
} from '../countries'
|
|
8
7
|
|
|
@@ -81,17 +80,3 @@ describe('buildCountryOptions', () => {
|
|
|
81
80
|
expect(options).toEqual(expect.arrayContaining([{ code: 'XK', label: 'Kosovo (XK)' }]))
|
|
82
81
|
})
|
|
83
82
|
})
|
|
84
|
-
|
|
85
|
-
describe('matchCountryCodes', () => {
|
|
86
|
-
it('maps English country names to ISO codes so list search can find stored PL rows', () => {
|
|
87
|
-
expect(matchCountryCodes('Poland')).toContain('PL')
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('maps localized names when extra locales are provided', () => {
|
|
91
|
-
expect(matchCountryCodes('Polska', { locales: ['pl'] })).toContain('PL')
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('returns nothing for blank search terms', () => {
|
|
95
|
-
expect(matchCountryCodes(' ')).toEqual([])
|
|
96
|
-
})
|
|
97
|
-
})
|
|
@@ -106,20 +106,3 @@ export function buildCountryOptions(options: {
|
|
|
106
106
|
|
|
107
107
|
return [...prioritized, ...remaining]
|
|
108
108
|
}
|
|
109
|
-
|
|
110
|
-
export function matchCountryCodes(
|
|
111
|
-
term: string,
|
|
112
|
-
options: { locales?: string[] } = {},
|
|
113
|
-
): string[] {
|
|
114
|
-
const needle = term.trim().toLowerCase()
|
|
115
|
-
if (!needle) return []
|
|
116
|
-
const searchLocales = options.locales?.length ? options.locales : ['en']
|
|
117
|
-
return ISO_COUNTRIES
|
|
118
|
-
.filter((entry) => {
|
|
119
|
-
if (entry.name.toLowerCase().includes(needle)) return true
|
|
120
|
-
return searchLocales.some((locale) =>
|
|
121
|
-
resolveCountryName(entry.code, { locale }).toLowerCase().includes(needle),
|
|
122
|
-
)
|
|
123
|
-
})
|
|
124
|
-
.map((entry) => entry.code)
|
|
125
|
-
}
|
package/src/lib/openapi/crud.ts
CHANGED
|
@@ -17,9 +17,6 @@ export function createPagedListResponseSchema(itemSchema: ZodTypeAny, options: P
|
|
|
17
17
|
page: paginationMetaOptional ? z.number().optional() : z.number(),
|
|
18
18
|
pageSize: paginationMetaOptional ? z.number().optional() : z.number(),
|
|
19
19
|
totalPages: z.number(),
|
|
20
|
-
// Present (true) only when the list count was bounded at OM_LIST_COUNT_CAP:
|
|
21
|
-
// `total` is then a floor, not an exact value.
|
|
22
|
-
totalIsCapped: z.boolean().optional(),
|
|
23
20
|
})
|
|
24
21
|
}
|
|
25
22
|
|