@open-mercato/shared 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7295.1.d0e0e33014
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 +2 -1
- package/AGENTS.md +6 -3
- package/build.mjs +4 -1
- package/dist/lib/crud/errors.js +6 -1
- package/dist/lib/crud/errors.js.map +2 -2
- package/dist/lib/crud/factory.js +22 -13
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/custom-fields/kinds.js +113 -0
- package/dist/lib/custom-fields/kinds.js.map +7 -0
- package/dist/lib/encryption/indexDoc.js +12 -9
- package/dist/lib/encryption/indexDoc.js.map +2 -2
- package/dist/lib/location/countries.generated.js +265 -0
- package/dist/lib/location/countries.generated.js.map +7 -0
- package/dist/lib/location/countries.js +2 -13
- package/dist/lib/location/countries.js.map +2 -2
- package/dist/lib/navigation/pageReload.js +11 -0
- package/dist/lib/navigation/pageReload.js.map +7 -0
- package/dist/lib/query/encrypted-sort.js +4 -1
- package/dist/lib/query/encrypted-sort.js.map +2 -2
- package/dist/lib/query/engine.js +97 -10
- package/dist/lib/query/engine.js.map +3 -3
- package/dist/lib/schedule/interval.js +33 -0
- package/dist/lib/schedule/interval.js.map +7 -0
- package/dist/lib/schedule/invalidScheduleValue.js +19 -0
- package/dist/lib/schedule/invalidScheduleValue.js.map +7 -0
- package/dist/lib/search/config.js.map +2 -2
- package/dist/lib/search/containment.js +32 -0
- package/dist/lib/search/containment.js.map +7 -0
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +3 -2
- package/scripts/generate-countries.mjs +75 -0
- package/src/lib/crud/__tests__/crud-factory.test.ts +177 -0
- package/src/lib/crud/__tests__/errors.test.ts +31 -1
- package/src/lib/crud/errors.ts +16 -0
- package/src/lib/crud/factory.ts +37 -15
- package/src/lib/custom-fields/__tests__/kinds.test.ts +208 -0
- package/src/lib/custom-fields/kinds.ts +205 -0
- package/src/lib/encryption/__tests__/indexDoc.custom-field-kinds.test.ts +127 -0
- package/src/lib/encryption/__tests__/indexDoc.test.ts +39 -0
- package/src/lib/encryption/indexDoc.ts +16 -7
- package/src/lib/location/__tests__/countries.test.ts +25 -0
- package/src/lib/location/countries.generated.ts +264 -0
- package/src/lib/location/countries.ts +2 -23
- package/src/lib/navigation/__tests__/pageReload.test.ts +58 -0
- package/src/lib/navigation/pageReload.ts +43 -0
- package/src/lib/query/__tests__/engine.test.ts +208 -16
- package/src/lib/query/encrypted-sort.ts +8 -1
- package/src/lib/query/engine.ts +172 -19
- package/src/lib/schedule/__tests__/interval.test.ts +37 -0
- package/src/lib/schedule/interval.ts +45 -0
- package/src/lib/schedule/invalidScheduleValue.ts +33 -0
- package/src/lib/search/__tests__/config.test.ts +27 -0
- package/src/lib/search/__tests__/containment.test.ts +61 -0
- package/src/lib/search/config.ts +28 -6
- package/src/lib/search/containment.ts +66 -0
package/.turbo/turbo-build.log
CHANGED
package/AGENTS.md
CHANGED
|
@@ -124,9 +124,12 @@ index stores hashes of the plaintext, so it keeps matching. Issue #2990.
|
|
|
124
124
|
- `matched: true` with `ids: []` is a real empty result.
|
|
125
125
|
- Queries that go through the query engine get this routing automatically; raw
|
|
126
126
|
`em.find` / Kysely list routes must wire it themselves. One carve-out: with
|
|
127
|
-
`OM_SEARCH_USE_ILIKE_FOR_NON_ENCRYPTED_FIELDS=true` (default
|
|
128
|
-
`like`/`ilike` on a **plaintext** column runs as
|
|
129
|
-
|
|
127
|
+
`OM_SEARCH_USE_ILIKE_FOR_NON_ENCRYPTED_FIELDS=true` (**off by default per #5383**, opt in ahead
|
|
128
|
+
of it to fix #5803), a base-column `like`/`ilike` on a **plaintext** column runs as SQL ILIKE —
|
|
129
|
+
one containment predicate per word of the term, ANDed (`lib/search/containment`), so word-order
|
|
130
|
+
independence survives the reroute — instead of the token rewrite; encrypted columns keep the
|
|
131
|
+
token path either way. Leaving the var unset keeps the legacy rewrite-everything behavior. When
|
|
132
|
+
the fallback would run
|
|
130
133
|
`ILIKE` against an encrypted column, both query engines now log a warning
|
|
131
134
|
(`lib/query/ciphertext-search-warning`) instead of degrading silently.
|
|
132
135
|
- The `…WithDecryption` helpers log the same warning outside production when the `where`
|
package/build.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
4
|
import { buildPackage } from '../../scripts/build-package.mjs'
|
|
5
5
|
import { buildVersionSource } from './scripts/versionSource.cjs'
|
|
6
6
|
|
|
@@ -22,4 +22,7 @@ const injectVersion = {
|
|
|
22
22
|
await buildPackage(packageDir, {
|
|
23
23
|
name: 'shared',
|
|
24
24
|
extraPlugins: [injectVersion],
|
|
25
|
+
async beforeBuild() {
|
|
26
|
+
await import(pathToFileURL(join(packageDir, 'scripts/generate-countries.mjs')).href)
|
|
27
|
+
},
|
|
25
28
|
})
|
package/dist/lib/crud/errors.js
CHANGED
|
@@ -45,6 +45,10 @@ function assertFound(value, message) {
|
|
|
45
45
|
if (!value) throw notFound(message);
|
|
46
46
|
return value;
|
|
47
47
|
}
|
|
48
|
+
function translateCrudErrorBody(body, translate) {
|
|
49
|
+
if (typeof body?.error !== "string") return body;
|
|
50
|
+
return { ...body, error: translate(body.error, body.error) };
|
|
51
|
+
}
|
|
48
52
|
export {
|
|
49
53
|
CrudHttpError,
|
|
50
54
|
assertFound,
|
|
@@ -53,6 +57,7 @@ export {
|
|
|
53
57
|
forbidden,
|
|
54
58
|
isCrudHttpError,
|
|
55
59
|
isUniqueViolation,
|
|
56
|
-
notFound
|
|
60
|
+
notFound,
|
|
61
|
+
translateCrudErrorBody
|
|
57
62
|
};
|
|
58
63
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/crud/errors.ts"],
|
|
4
|
-
"sourcesContent": ["// Use Symbol.for so the marker survives module duplication across bundle boundaries\n// (same behaviour as globalThis-based registries used for DI registrars)\nconst CRUD_HTTP_ERROR_MARKER = Symbol.for('@open-mercato/CrudHttpError')\n\nexport class CrudHttpError extends Error {\n readonly [CRUD_HTTP_ERROR_MARKER] = true\n status: number\n body: Record<string, any>\n\n constructor(\n status: number,\n body?: Record<string, any> | string,\n options?: { cause?: unknown },\n ) {\n const normalizedBody = typeof body === 'string' ? { error: body } : body ?? {}\n super(typeof body === 'string' ? body : normalizedBody.error ?? 'Request failed', options)\n this.status = status\n this.body = normalizedBody\n }\n}\n\n/**\n * Type-safe check for CrudHttpError that works across module/bundle boundaries.\n * Prefer this over `instanceof CrudHttpError` whenever the error may originate\n * from a different module bundle (e.g. enterprise packages, dynamic imports).\n */\nexport function isCrudHttpError(err: unknown): err is CrudHttpError {\n return !!err && typeof err === 'object' && (err as Record<symbol, unknown>)[CRUD_HTTP_ERROR_MARKER] === true\n}\n\nexport function badRequest(message: string): CrudHttpError {\n return new CrudHttpError(400, { error: message })\n}\n\nexport function forbidden(message = 'Forbidden'): CrudHttpError {\n return new CrudHttpError(403, { error: message })\n}\n\n/**\n * Builds the standardized 404 (`{ error: message }`) \u2014 use it instead of hand-rolling\n * `new CrudHttpError(404, { error: message })`. Reach for this in guard statements, where\n * the lookup spans several lines or the condition is compound\n * (`if (!entity || entity.tenantId !== auth.tenantId) throw notFound(msg)`); TypeScript\n * still narrows the value after the throw. For a plain inline lookup, `assertFound` is terser.\n *\n * Pass an already-translated message; this helper never derives one, so 404 copy\n * stays routed through i18n.\n */\nexport function notFound(message = 'Not found'): CrudHttpError {\n return new CrudHttpError(404, { error: message })\n}\n\nexport function conflict(message: string): CrudHttpError {\n return new CrudHttpError(409, { error: message })\n}\n\nconst POSTGRES_UNIQUE_VIOLATION = '23505'\n\n/**\n * Detects a Postgres unique-constraint violation (SQLSTATE 23505) on a thrown\n * error, looking through MikroORM's driver-error wrapping. Use this to map a DB\n * uniqueness race (e.g. a soft-deleted row the in-app check missed, or a\n * timestamp-precision mismatch) onto a clean 409 instead of a generic 500.\n */\nexport function isUniqueViolation(err: unknown, constraintName?: string): boolean {\n if (!err || typeof err !== 'object') return false\n const candidates: unknown[] = [err, (err as { cause?: unknown }).cause, (err as { previous?: unknown }).previous]\n for (const candidate of candidates) {\n if (!candidate || typeof candidate !== 'object') continue\n const record = candidate as Record<string, unknown>\n if (record.code === POSTGRES_UNIQUE_VIOLATION) {\n if (!constraintName) return true\n const constraint = typeof record.constraint === 'string' ? record.constraint : ''\n const detail = typeof record.detail === 'string' ? record.detail : ''\n const message = typeof record.message === 'string' ? record.message : ''\n return constraint === constraintName || detail.includes(constraintName) || message.includes(constraintName)\n }\n }\n return false\n}\n\n/**\n * The canonical \"entity must exist\" guard for API routes and commands: throws the\n * standardized 404 when the lookup came back empty, and returns the value narrowed\n * to `T` so callers drop the `| null` without a cast.\n *\n * const deal = assertFound(await em.findOne(Deal, { id }), translate('customers.errors.deal_not_found', 'Deal not found'))\n *\n * Use this instead of hand-rolling `if (!entity) throw new CrudHttpError(404, ...)`.\n * `message` is required and passed through verbatim, so it must already be translated.\n *\n * Treats every falsy value as missing, so it suits entity/object lookups \u2014 do not use\n * it to guard numbers or strings, where `0` and `''` are legitimate results.\n */\nexport function assertFound<T>(value: T | null | undefined, message: string): T {\n if (!value) throw notFound(message)\n return value\n}\n"],
|
|
5
|
-
"mappings": "AAAA;AAEA,MAAM,yBAAyB,uBAAO,IAAI,6BAA6B;AAEhE,MAAM,uBAAsB,YACvB,6BADuB,IAAM;AAAA,EAKvC,YACE,QACA,MACA,SACA;AACA,UAAM,iBAAiB,OAAO,SAAS,WAAW,EAAE,OAAO,KAAK,IAAI,QAAQ,CAAC;AAC7E,UAAM,OAAO,SAAS,WAAW,OAAO,eAAe,SAAS,kBAAkB,OAAO;AAV3F,SAAU,MAA0B;AAWlC,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAOO,SAAS,gBAAgB,KAAoC;AAClE,SAAO,CAAC,CAAC,OAAO,OAAO,QAAQ,YAAa,IAAgC,sBAAsB,MAAM;AAC1G;AAEO,SAAS,WAAW,SAAgC;AACzD,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAEO,SAAS,UAAU,UAAU,aAA4B;AAC9D,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAYO,SAAS,SAAS,UAAU,aAA4B;AAC7D,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAEO,SAAS,SAAS,SAAgC;AACvD,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAEA,MAAM,4BAA4B;AAQ3B,SAAS,kBAAkB,KAAc,gBAAkC;AAChF,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,aAAwB,CAAC,KAAM,IAA4B,OAAQ,IAA+B,QAAQ;AAChH,aAAW,aAAa,YAAY;AAClC,QAAI,CAAC,aAAa,OAAO,cAAc,SAAU;AACjD,UAAM,SAAS;AACf,QAAI,OAAO,SAAS,2BAA2B;AAC7C,UAAI,CAAC,eAAgB,QAAO;AAC5B,YAAM,aAAa,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa;AAC/E,YAAM,SAAS,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AACnE,YAAM,UAAU,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AACtE,aAAO,eAAe,kBAAkB,OAAO,SAAS,cAAc,KAAK,QAAQ,SAAS,cAAc;AAAA,IAC5G;AAAA,EACF;AACA,SAAO;AACT;AAeO,SAAS,YAAe,OAA6B,SAAoB;AAC9E,MAAI,CAAC,MAAO,OAAM,SAAS,OAAO;AAClC,SAAO;AACT;",
|
|
4
|
+
"sourcesContent": ["// Use Symbol.for so the marker survives module duplication across bundle boundaries\n// (same behaviour as globalThis-based registries used for DI registrars)\nconst CRUD_HTTP_ERROR_MARKER = Symbol.for('@open-mercato/CrudHttpError')\n\nexport class CrudHttpError extends Error {\n readonly [CRUD_HTTP_ERROR_MARKER] = true\n status: number\n body: Record<string, any>\n\n constructor(\n status: number,\n body?: Record<string, any> | string,\n options?: { cause?: unknown },\n ) {\n const normalizedBody = typeof body === 'string' ? { error: body } : body ?? {}\n super(typeof body === 'string' ? body : normalizedBody.error ?? 'Request failed', options)\n this.status = status\n this.body = normalizedBody\n }\n}\n\n/**\n * Type-safe check for CrudHttpError that works across module/bundle boundaries.\n * Prefer this over `instanceof CrudHttpError` whenever the error may originate\n * from a different module bundle (e.g. enterprise packages, dynamic imports).\n */\nexport function isCrudHttpError(err: unknown): err is CrudHttpError {\n return !!err && typeof err === 'object' && (err as Record<symbol, unknown>)[CRUD_HTTP_ERROR_MARKER] === true\n}\n\nexport function badRequest(message: string): CrudHttpError {\n return new CrudHttpError(400, { error: message })\n}\n\nexport function forbidden(message = 'Forbidden'): CrudHttpError {\n return new CrudHttpError(403, { error: message })\n}\n\n/**\n * Builds the standardized 404 (`{ error: message }`) \u2014 use it instead of hand-rolling\n * `new CrudHttpError(404, { error: message })`. Reach for this in guard statements, where\n * the lookup spans several lines or the condition is compound\n * (`if (!entity || entity.tenantId !== auth.tenantId) throw notFound(msg)`); TypeScript\n * still narrows the value after the throw. For a plain inline lookup, `assertFound` is terser.\n *\n * Pass an already-translated message; this helper never derives one, so 404 copy\n * stays routed through i18n.\n */\nexport function notFound(message = 'Not found'): CrudHttpError {\n return new CrudHttpError(404, { error: message })\n}\n\nexport function conflict(message: string): CrudHttpError {\n return new CrudHttpError(409, { error: message })\n}\n\nconst POSTGRES_UNIQUE_VIOLATION = '23505'\n\n/**\n * Detects a Postgres unique-constraint violation (SQLSTATE 23505) on a thrown\n * error, looking through MikroORM's driver-error wrapping. Use this to map a DB\n * uniqueness race (e.g. a soft-deleted row the in-app check missed, or a\n * timestamp-precision mismatch) onto a clean 409 instead of a generic 500.\n */\nexport function isUniqueViolation(err: unknown, constraintName?: string): boolean {\n if (!err || typeof err !== 'object') return false\n const candidates: unknown[] = [err, (err as { cause?: unknown }).cause, (err as { previous?: unknown }).previous]\n for (const candidate of candidates) {\n if (!candidate || typeof candidate !== 'object') continue\n const record = candidate as Record<string, unknown>\n if (record.code === POSTGRES_UNIQUE_VIOLATION) {\n if (!constraintName) return true\n const constraint = typeof record.constraint === 'string' ? record.constraint : ''\n const detail = typeof record.detail === 'string' ? record.detail : ''\n const message = typeof record.message === 'string' ? record.message : ''\n return constraint === constraintName || detail.includes(constraintName) || message.includes(constraintName)\n }\n }\n return false\n}\n\n/**\n * The canonical \"entity must exist\" guard for API routes and commands: throws the\n * standardized 404 when the lookup came back empty, and returns the value narrowed\n * to `T` so callers drop the `| null` without a cast.\n *\n * const deal = assertFound(await em.findOne(Deal, { id }), translate('customers.errors.deal_not_found', 'Deal not found'))\n *\n * Use this instead of hand-rolling `if (!entity) throw new CrudHttpError(404, ...)`.\n * `message` is required and passed through verbatim, so it must already be translated.\n *\n * Treats every falsy value as missing, so it suits entity/object lookups \u2014 do not use\n * it to guard numbers or strings, where `0` and `''` are legitimate results.\n */\nexport function assertFound<T>(value: T | null | undefined, message: string): T {\n if (!value) throw notFound(message)\n return value\n}\n\n/**\n * Translates a `CrudHttpError` body's `error` field before it reaches the client.\n * Some callers (command handlers, lib helpers reused by subscribers/CLI/workers) raise\n * `CrudHttpError` with a raw i18n key because they run without a request locale \u2014 a route\n * handler forwarding `err.body` verbatim would leak that key to the user. Call this at the\n * route boundary, passing the `translate` the route already resolved, instead of forwarding\n * `err.body` directly.\n */\nexport function translateCrudErrorBody<T extends Record<string, unknown>>(\n body: T,\n translate: (key: string, fallback?: string) => string,\n): T {\n if (typeof body?.error !== 'string') return body\n return { ...body, error: translate(body.error, body.error) }\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAEA,MAAM,yBAAyB,uBAAO,IAAI,6BAA6B;AAEhE,MAAM,uBAAsB,YACvB,6BADuB,IAAM;AAAA,EAKvC,YACE,QACA,MACA,SACA;AACA,UAAM,iBAAiB,OAAO,SAAS,WAAW,EAAE,OAAO,KAAK,IAAI,QAAQ,CAAC;AAC7E,UAAM,OAAO,SAAS,WAAW,OAAO,eAAe,SAAS,kBAAkB,OAAO;AAV3F,SAAU,MAA0B;AAWlC,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAOO,SAAS,gBAAgB,KAAoC;AAClE,SAAO,CAAC,CAAC,OAAO,OAAO,QAAQ,YAAa,IAAgC,sBAAsB,MAAM;AAC1G;AAEO,SAAS,WAAW,SAAgC;AACzD,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAEO,SAAS,UAAU,UAAU,aAA4B;AAC9D,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAYO,SAAS,SAAS,UAAU,aAA4B;AAC7D,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAEO,SAAS,SAAS,SAAgC;AACvD,SAAO,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AAClD;AAEA,MAAM,4BAA4B;AAQ3B,SAAS,kBAAkB,KAAc,gBAAkC;AAChF,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,aAAwB,CAAC,KAAM,IAA4B,OAAQ,IAA+B,QAAQ;AAChH,aAAW,aAAa,YAAY;AAClC,QAAI,CAAC,aAAa,OAAO,cAAc,SAAU;AACjD,UAAM,SAAS;AACf,QAAI,OAAO,SAAS,2BAA2B;AAC7C,UAAI,CAAC,eAAgB,QAAO;AAC5B,YAAM,aAAa,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa;AAC/E,YAAM,SAAS,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AACnE,YAAM,UAAU,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU;AACtE,aAAO,eAAe,kBAAkB,OAAO,SAAS,cAAc,KAAK,QAAQ,SAAS,cAAc;AAAA,IAC5G;AAAA,EACF;AACA,SAAO;AACT;AAeO,SAAS,YAAe,OAA6B,SAAoB;AAC9E,MAAI,CAAC,MAAO,OAAM,SAAS,OAAO;AAClC,SAAO;AACT;AAUO,SAAS,uBACd,MACA,WACG;AACH,MAAI,OAAO,MAAM,UAAU,SAAU,QAAO;AAC5C,SAAO,EAAE,GAAG,MAAM,OAAO,UAAU,KAAK,OAAO,KAAK,KAAK,EAAE;AAC7D;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/crud/factory.js
CHANGED
|
@@ -7,6 +7,7 @@ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/d
|
|
|
7
7
|
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
|
|
8
8
|
import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
|
|
9
9
|
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
|
|
10
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
10
11
|
import {
|
|
11
12
|
runMutationGuards,
|
|
12
13
|
bridgeLegacyGuard
|
|
@@ -26,7 +27,7 @@ import {
|
|
|
26
27
|
resolveCfDefIndexOrgCandidates
|
|
27
28
|
} from "./custom-field-definition-index.js";
|
|
28
29
|
import { serializeExport, normalizeExportFormat, defaultExportFilename, ensureColumns } from "./exporters.js";
|
|
29
|
-
import { isCrudHttpError } from "./errors.js";
|
|
30
|
+
import { isCrudHttpError, translateCrudErrorBody } from "./errors.js";
|
|
30
31
|
import {
|
|
31
32
|
buildCollectionTags,
|
|
32
33
|
buildRecordTag,
|
|
@@ -316,9 +317,12 @@ function classifyCrudError(err) {
|
|
|
316
317
|
}
|
|
317
318
|
return { code: "INTERNAL_ERROR", pgSqlState: null };
|
|
318
319
|
}
|
|
319
|
-
function handleError(err, request) {
|
|
320
|
+
async function handleError(err, request) {
|
|
320
321
|
if (err instanceof Response) return err;
|
|
321
|
-
if (isCrudHttpError(err))
|
|
322
|
+
if (isCrudHttpError(err)) {
|
|
323
|
+
const { translate } = await resolveTranslations();
|
|
324
|
+
return json(translateCrudErrorBody(err.body, translate), { status: err.status });
|
|
325
|
+
}
|
|
322
326
|
const interceptorRejection = getCommandInterceptorHttpRejection(err);
|
|
323
327
|
if (interceptorRejection) {
|
|
324
328
|
return json(interceptorRejection.body, { status: interceptorRejection.status });
|
|
@@ -1400,13 +1404,15 @@ function makeCrudRoute(opts) {
|
|
|
1400
1404
|
nextPage += 1;
|
|
1401
1405
|
}
|
|
1402
1406
|
}
|
|
1403
|
-
const prepared = exportFullRequested ? { columns: ensureColumns(exportItems), rows: exportItems } : prepareExportData(exportItems, opts.list, validated, ctx);
|
|
1404
|
-
const fallbackBase = `${opts.events?.entity || resourceKind || "list"}${exportFullRequested ? "_full" : ""}`;
|
|
1405
|
-
const filename = finalizeExportFilename(opts.list, requestedExport, fallbackBase);
|
|
1406
|
-
const serialized = serializeExport(prepared, requestedExport);
|
|
1407
1407
|
const exportPayload = { items: exportItems, total, page: 1, pageSize: exportItems.length, totalPages: 1, ...res.meta ? { meta: res.meta } : {} };
|
|
1408
1408
|
await opts.hooks?.afterList?.(exportPayload, { ...ctx, query: validated });
|
|
1409
1409
|
profiler.mark("after_list_hook");
|
|
1410
|
+
const hookExportItems = Array.isArray(exportPayload.items) ? exportPayload.items : exportItems;
|
|
1411
|
+
const finalExportItems = exportFullRequested ? hookExportItems.map(normalizeFullRecordForExport) : hookExportItems;
|
|
1412
|
+
const prepared = exportFullRequested ? { columns: ensureColumns(finalExportItems), rows: finalExportItems } : prepareExportData(finalExportItems, opts.list, validated, ctx);
|
|
1413
|
+
const fallbackBase = `${opts.events?.entity || resourceKind || "list"}${exportFullRequested ? "_full" : ""}`;
|
|
1414
|
+
const filename = finalizeExportFilename(opts.list, requestedExport, fallbackBase);
|
|
1415
|
+
const serialized = serializeExport(prepared, requestedExport);
|
|
1410
1416
|
const response3 = new Response(serialized.body, {
|
|
1411
1417
|
headers: {
|
|
1412
1418
|
"content-type": serialized.contentType,
|
|
@@ -1429,7 +1435,7 @@ function makeCrudRoute(opts) {
|
|
|
1429
1435
|
finishProfile({
|
|
1430
1436
|
result: "export",
|
|
1431
1437
|
cacheStatus,
|
|
1432
|
-
itemCount:
|
|
1438
|
+
itemCount: finalExportItems.length,
|
|
1433
1439
|
total
|
|
1434
1440
|
});
|
|
1435
1441
|
return response3;
|
|
@@ -1585,12 +1591,15 @@ function makeCrudRoute(opts) {
|
|
|
1585
1591
|
profiler.mark("access_logged", accessLogResult);
|
|
1586
1592
|
if (exportRequested && requestedExport) {
|
|
1587
1593
|
const exportItems = exportFullRequested ? list.map(normalizeFullRecordForExport) : list;
|
|
1588
|
-
const
|
|
1594
|
+
const exportPayload = { items: exportItems, total: exportItems.length, page: 1, pageSize: exportItems.length, totalPages: 1 };
|
|
1595
|
+
await opts.hooks?.afterList?.(exportPayload, { ...ctx, query: validated });
|
|
1596
|
+
profiler.mark("after_list_hook");
|
|
1597
|
+
const hookExportItems = Array.isArray(exportPayload.items) ? exportPayload.items : exportItems;
|
|
1598
|
+
const finalExportItems = exportFullRequested ? hookExportItems.map(normalizeFullRecordForExport) : hookExportItems;
|
|
1599
|
+
const prepared = exportFullRequested ? { columns: ensureColumns(finalExportItems), rows: finalExportItems } : prepareExportData(finalExportItems, opts.list, validated, ctx);
|
|
1589
1600
|
const fallbackBase = `${opts.events?.entity || resourceKind || "list"}${exportFullRequested ? "_full" : ""}`;
|
|
1590
1601
|
const filename = finalizeExportFilename(opts.list, requestedExport, fallbackBase);
|
|
1591
1602
|
const serialized = serializeExport(prepared, requestedExport);
|
|
1592
|
-
await opts.hooks?.afterList?.({ items: exportItems, total: exportItems.length, page: 1, pageSize: exportItems.length, totalPages: 1 }, { ...ctx, query: validated });
|
|
1593
|
-
profiler.mark("after_list_hook");
|
|
1594
1603
|
const response2 = new Response(serialized.body, {
|
|
1595
1604
|
headers: {
|
|
1596
1605
|
"content-type": serialized.contentType,
|
|
@@ -1600,8 +1609,8 @@ function makeCrudRoute(opts) {
|
|
|
1600
1609
|
finishProfile({
|
|
1601
1610
|
result: "export",
|
|
1602
1611
|
cacheStatus,
|
|
1603
|
-
itemCount:
|
|
1604
|
-
total:
|
|
1612
|
+
itemCount: finalExportItems.length,
|
|
1613
|
+
total: finalExportItems.length,
|
|
1605
1614
|
branch: "fallback"
|
|
1606
1615
|
});
|
|
1607
1616
|
return response2;
|