@open-mercato/shared 0.6.7 → 0.6.8-canary.320.1.bf889f49e4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +62 -6
  3. package/build.mjs +2 -4
  4. package/dist/lib/ai/safety-identifier.js +32 -0
  5. package/dist/lib/ai/safety-identifier.js.map +7 -0
  6. package/dist/lib/auth/organizationScope.js +34 -0
  7. package/dist/lib/auth/organizationScope.js.map +7 -0
  8. package/dist/lib/auth/server.js +15 -2
  9. package/dist/lib/auth/server.js.map +2 -2
  10. package/dist/lib/bootstrap/clientOnlyModules.js +55 -0
  11. package/dist/lib/bootstrap/clientOnlyModules.js.map +7 -0
  12. package/dist/lib/bootstrap/dynamicLoader.js +190 -70
  13. package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
  14. package/dist/lib/bootstrap/factory.js +18 -13
  15. package/dist/lib/bootstrap/factory.js.map +2 -2
  16. package/dist/lib/commands/command-bus.js +9 -3
  17. package/dist/lib/commands/command-bus.js.map +2 -2
  18. package/dist/lib/commands/command-interceptor-runner.js +9 -4
  19. package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
  20. package/dist/lib/commands/errors.js +25 -4
  21. package/dist/lib/commands/errors.js.map +2 -2
  22. package/dist/lib/commands/index.js +7 -1
  23. package/dist/lib/commands/index.js.map +2 -2
  24. package/dist/lib/crud/enricher-runner.js +2 -11
  25. package/dist/lib/crud/enricher-runner.js.map +2 -2
  26. package/dist/lib/crud/factory.js +104 -23
  27. package/dist/lib/crud/factory.js.map +2 -2
  28. package/dist/lib/crud/interceptor-runner.js +2 -2
  29. package/dist/lib/crud/interceptor-runner.js.map +2 -2
  30. package/dist/lib/crud/mutation-guard-registry.js +2 -2
  31. package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
  32. package/dist/lib/crud/types.js +4 -0
  33. package/dist/lib/crud/types.js.map +3 -3
  34. package/dist/lib/data/consistency.js +19 -0
  35. package/dist/lib/data/consistency.js.map +7 -0
  36. package/dist/lib/data/engine.js +37 -11
  37. package/dist/lib/data/engine.js.map +2 -2
  38. package/dist/lib/db/entityMetadata.js +26 -0
  39. package/dist/lib/db/entityMetadata.js.map +7 -0
  40. package/dist/lib/db/pg-errors.js +42 -0
  41. package/dist/lib/db/pg-errors.js.map +2 -2
  42. package/dist/lib/di/container.js +48 -6
  43. package/dist/lib/di/container.js.map +2 -2
  44. package/dist/lib/email/send.js +56 -0
  45. package/dist/lib/email/send.js.map +2 -2
  46. package/dist/lib/encryption/find.js +22 -0
  47. package/dist/lib/encryption/find.js.map +2 -2
  48. package/dist/lib/encryption/likeFilterWarning.js +66 -0
  49. package/dist/lib/encryption/likeFilterWarning.js.map +7 -0
  50. package/dist/lib/encryption/subscriber.js +2 -10
  51. package/dist/lib/encryption/subscriber.js.map +2 -2
  52. package/dist/lib/encryption/tenantDataEncryptionService.js +52 -17
  53. package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
  54. package/dist/lib/i18n/config.js +1 -1
  55. package/dist/lib/i18n/config.js.map +2 -2
  56. package/dist/lib/i18n/dictionary-cache.js +5 -0
  57. package/dist/lib/i18n/dictionary-cache.js.map +2 -2
  58. package/dist/lib/i18n/server.js +4 -4
  59. package/dist/lib/i18n/server.js.map +3 -3
  60. package/dist/lib/logger/extension.js +29 -0
  61. package/dist/lib/logger/extension.js.map +7 -0
  62. package/dist/lib/logger/index.js +47 -3
  63. package/dist/lib/logger/index.js.map +3 -3
  64. package/dist/lib/modules/registry.js +36 -6
  65. package/dist/lib/modules/registry.js.map +2 -2
  66. package/dist/lib/modules/surfaceFingerprint.js +47 -0
  67. package/dist/lib/modules/surfaceFingerprint.js.map +7 -0
  68. package/dist/lib/query/ciphertext-search-warning.js +51 -0
  69. package/dist/lib/query/ciphertext-search-warning.js.map +7 -0
  70. package/dist/lib/query/engine.js +60 -43
  71. package/dist/lib/query/engine.js.map +2 -2
  72. package/dist/lib/query/types.js.map +1 -1
  73. package/dist/lib/redis/connection.js +7 -2
  74. package/dist/lib/redis/connection.js.map +2 -2
  75. package/dist/lib/search/auto-indexing.js +14 -0
  76. package/dist/lib/search/auto-indexing.js.map +7 -0
  77. package/dist/lib/search/availability.js +107 -0
  78. package/dist/lib/search/availability.js.map +7 -0
  79. package/dist/lib/search/config.js +61 -2
  80. package/dist/lib/search/config.js.map +2 -2
  81. package/dist/lib/search/tokenLookup.js +46 -0
  82. package/dist/lib/search/tokenLookup.js.map +7 -0
  83. package/dist/lib/search/tokenize.js +23 -11
  84. package/dist/lib/search/tokenize.js.map +2 -2
  85. package/dist/lib/telemetry/runtime.js +35 -0
  86. package/dist/lib/telemetry/runtime.js.map +7 -0
  87. package/dist/lib/version.js +1 -1
  88. package/dist/lib/version.js.map +1 -1
  89. package/dist/lib/webhooks/body.js +57 -0
  90. package/dist/lib/webhooks/body.js.map +7 -0
  91. package/dist/lib/webhooks/inbound-types.js +1 -0
  92. package/dist/lib/webhooks/inbound-types.js.map +7 -0
  93. package/dist/lib/webhooks/index.js +13 -1
  94. package/dist/lib/webhooks/index.js.map +2 -2
  95. package/dist/lib/webhooks/verify.js +12 -7
  96. package/dist/lib/webhooks/verify.js.map +2 -2
  97. package/dist/modules/analytics.js.map +2 -2
  98. package/dist/modules/overrides.js +50 -1
  99. package/dist/modules/overrides.js.map +2 -2
  100. package/dist/modules/payment_gateways/types.js +8 -1
  101. package/dist/modules/payment_gateways/types.js.map +2 -2
  102. package/dist/modules/registry.js +1 -0
  103. package/dist/modules/registry.js.map +2 -2
  104. package/dist/modules/widgets/extension-points.js +96 -0
  105. package/dist/modules/widgets/extension-points.js.map +7 -0
  106. package/dist/security/enabledModulesRegistry.js +56 -11
  107. package/dist/security/enabledModulesRegistry.js.map +2 -2
  108. package/dist/security/featurePolicy.js +62 -0
  109. package/dist/security/featurePolicy.js.map +7 -0
  110. package/jest.config.cjs +20 -10
  111. package/package.json +19 -9
  112. package/scripts/versionSource.cjs +65 -0
  113. package/src/lib/__tests__/versionSource.test.ts +64 -0
  114. package/src/lib/ai/__tests__/llm-provider-contract.test.ts +59 -0
  115. package/src/lib/ai/__tests__/safety-identifier.test.ts +71 -0
  116. package/src/lib/ai/llm-provider.ts +33 -0
  117. package/src/lib/ai/safety-identifier.ts +73 -0
  118. package/src/lib/auth/__tests__/organizationScope.test.ts +107 -0
  119. package/src/lib/auth/__tests__/server.test.ts +23 -0
  120. package/src/lib/auth/organizationScope.ts +65 -0
  121. package/src/lib/auth/server.ts +23 -2
  122. package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +189 -0
  123. package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +98 -0
  124. package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +211 -0
  125. package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +63 -8
  126. package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +256 -0
  127. package/src/lib/bootstrap/__tests__/dynamicLoader.test.ts +126 -0
  128. package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +21 -1
  129. package/src/lib/bootstrap/__tests__/factory.test.ts +78 -0
  130. package/src/lib/bootstrap/clientOnlyModules.ts +85 -0
  131. package/src/lib/bootstrap/dynamicLoader.ts +346 -96
  132. package/src/lib/bootstrap/factory.ts +21 -15
  133. package/src/lib/bootstrap/types.ts +6 -0
  134. package/src/lib/commands/__tests__/command-bus.test.ts +63 -0
  135. package/src/lib/commands/__tests__/command-interceptor-error.test.ts +91 -1
  136. package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +101 -0
  137. package/src/lib/commands/command-bus.ts +9 -3
  138. package/src/lib/commands/command-interceptor-runner.ts +27 -6
  139. package/src/lib/commands/command-interceptor.ts +8 -0
  140. package/src/lib/commands/errors.ts +67 -2
  141. package/src/lib/commands/index.ts +7 -1
  142. package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +171 -0
  143. package/src/lib/crud/__tests__/crud-factory.test.ts +564 -0
  144. package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +26 -0
  145. package/src/lib/crud/enricher-runner.ts +2 -11
  146. package/src/lib/crud/factory.ts +157 -22
  147. package/src/lib/crud/interceptor-runner.ts +2 -2
  148. package/src/lib/crud/mutation-guard-registry.ts +2 -2
  149. package/src/lib/crud/types.ts +3 -0
  150. package/src/lib/data/__tests__/consistency.test.ts +38 -0
  151. package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +18 -3
  152. package/src/lib/data/consistency.ts +17 -0
  153. package/src/lib/data/engine.ts +50 -16
  154. package/src/lib/db/__tests__/entityMetadata.test.ts +38 -0
  155. package/src/lib/db/__tests__/pg-errors.test.ts +48 -0
  156. package/src/lib/db/entityMetadata.ts +43 -0
  157. package/src/lib/db/pg-errors.ts +58 -0
  158. package/src/lib/di/__tests__/container-app-di-absent.test.ts +155 -0
  159. package/src/lib/di/__tests__/container-app-di.test.ts +173 -0
  160. package/src/lib/di/__tests__/registrar-error-log.test.ts +154 -0
  161. package/src/lib/di/container.ts +72 -8
  162. package/src/lib/email/__tests__/send.test.ts +37 -1
  163. package/src/lib/email/send.ts +79 -0
  164. package/src/lib/encryption/__tests__/likeFilterWarning.test.ts +167 -0
  165. package/src/lib/encryption/__tests__/subscriber.test.ts +54 -2
  166. package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +58 -0
  167. package/src/lib/encryption/find.ts +22 -0
  168. package/src/lib/encryption/likeFilterWarning.ts +102 -0
  169. package/src/lib/encryption/subscriber.ts +2 -14
  170. package/src/lib/encryption/tenantDataEncryptionService.ts +64 -18
  171. package/src/lib/i18n/__tests__/config.test.ts +8 -0
  172. package/src/lib/i18n/__tests__/dictionary-cache.test.ts +23 -0
  173. package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +30 -0
  174. package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +29 -0
  175. package/src/lib/i18n/config.ts +2 -2
  176. package/src/lib/i18n/dictionary-cache.ts +5 -0
  177. package/src/lib/i18n/server.ts +6 -2
  178. package/src/lib/logger/__tests__/logger.test.ts +77 -0
  179. package/src/lib/logger/extension.ts +59 -0
  180. package/src/lib/logger/index.ts +59 -1
  181. package/src/lib/modules/__tests__/registry.test.ts +26 -0
  182. package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +122 -0
  183. package/src/lib/modules/registry.ts +46 -7
  184. package/src/lib/modules/surfaceFingerprint.ts +87 -0
  185. package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +178 -0
  186. package/src/lib/query/__tests__/engine.scope-and-or.test.ts +67 -0
  187. package/src/lib/query/__tests__/engine.test.ts +213 -13
  188. package/src/lib/query/ciphertext-search-warning.ts +106 -0
  189. package/src/lib/query/engine.ts +102 -61
  190. package/src/lib/query/types.ts +4 -5
  191. package/src/lib/redis/__tests__/connection.test.ts +16 -0
  192. package/src/lib/redis/connection.ts +10 -7
  193. package/src/lib/search/__tests__/availability.test.ts +211 -0
  194. package/src/lib/search/__tests__/config.test.ts +177 -0
  195. package/src/lib/search/__tests__/tokenLookup.test.ts +206 -0
  196. package/src/lib/search/__tests__/tokenize.test.ts +49 -0
  197. package/src/lib/search/auto-indexing.ts +22 -0
  198. package/src/lib/search/availability.ts +232 -0
  199. package/src/lib/search/config.ts +106 -8
  200. package/src/lib/search/tokenLookup.ts +133 -0
  201. package/src/lib/search/tokenize.ts +33 -11
  202. package/src/lib/telemetry/runtime.ts +72 -0
  203. package/src/lib/webhooks/__tests__/body.test.ts +95 -0
  204. package/src/lib/webhooks/__tests__/verify.test.ts +20 -1
  205. package/src/lib/webhooks/body.ts +70 -0
  206. package/src/lib/webhooks/inbound-types.ts +125 -0
  207. package/src/lib/webhooks/index.ts +19 -1
  208. package/src/lib/webhooks/verify.ts +17 -9
  209. package/src/modules/__tests__/nav-group-order-override.test.ts +183 -0
  210. package/src/modules/__tests__/registry.test.ts +10 -2
  211. package/src/modules/analytics.ts +7 -0
  212. package/src/modules/customer-auth.ts +1 -0
  213. package/src/modules/encryption.ts +3 -0
  214. package/src/modules/entities.ts +15 -0
  215. package/src/modules/navigation/backendChrome.ts +15 -0
  216. package/src/modules/overrides.ts +103 -0
  217. package/src/modules/payment_gateways/__tests__/types.test.ts +37 -0
  218. package/src/modules/payment_gateways/types.ts +41 -0
  219. package/src/modules/registry.ts +3 -0
  220. package/src/modules/widgets/__tests__/extension-points.test.ts +101 -0
  221. package/src/modules/widgets/extension-points.ts +524 -0
  222. package/src/security/__tests__/featurePolicy.test.ts +166 -0
  223. package/src/security/enabledModulesRegistry.ts +64 -12
  224. package/src/security/featurePolicy.ts +89 -0
@@ -5,6 +5,7 @@ import { getAuthFromCookies, getAuthFromRequest } from "@open-mercato/shared/lib
5
5
  import { SortDir } from "@open-mercato/shared/lib/query/types";
6
6
  import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
7
7
  import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
8
+ import { getCommandInterceptorHttpRejection } from "@open-mercato/shared/lib/commands/errors";
8
9
  import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
9
10
  import {
10
11
  runMutationGuards,
@@ -38,6 +39,7 @@ import {
38
39
  isCrudCacheEnabled,
39
40
  normalizeIdentifierValue,
40
41
  normalizeTagSegment,
42
+ pickFirstIdentifier,
41
43
  resolveCrudCache
42
44
  } from "./cache.js";
43
45
  import { deriveCrudSegmentTag } from "./cache-stats.js";
@@ -51,11 +53,13 @@ import { parseExtensionHeaders } from "../umes/extension-headers.js";
51
53
  import { createGenericOptimisticLockReader } from "./optimistic-lock.js";
52
54
  import { registerOptimisticLockReaderIfAbsent } from "./optimistic-lock-store.js";
53
55
  import { createLogger } from "../logger/index.js";
56
+ import { isTransientDbError } from "../db/pg-errors.js";
54
57
  const logger = createLogger("shared").child({ component: "crud" });
55
- function resolveSortParams(queryParams) {
56
- const rawSortField = queryParams.sortField ?? queryParams.sort ?? "id";
57
- const rawSortDir = queryParams.sortDir ?? queryParams.order ?? "asc";
58
- const sortField = typeof rawSortField === "string" && rawSortField.trim().length > 0 ? rawSortField.trim() : "id";
58
+ function resolveSortParams(queryParams, defaultSort) {
59
+ const rawSortField = queryParams.sortField ?? queryParams.sort;
60
+ const requestedSortField = typeof rawSortField === "string" && rawSortField.trim().length > 0 ? rawSortField.trim() : null;
61
+ const sortField = requestedSortField ?? defaultSort?.field ?? "id";
62
+ const rawSortDir = queryParams.sortDir ?? queryParams.order ?? (requestedSortField ? "asc" : defaultSort?.dir ?? "asc");
59
63
  const normalizedDir = typeof rawSortDir === "string" ? rawSortDir.trim().toLowerCase() : "asc";
60
64
  const sortDir = normalizedDir === "desc" ? SortDir.Desc : SortDir.Asc;
61
65
  return { sortField, sortDir };
@@ -95,6 +99,7 @@ const DEFAULT_EXPORT_FORMATS = ["csv", "json", "xml", "markdown"];
95
99
  const DEFAULT_EXPORT_BATCH_SIZE = 1e3;
96
100
  const MIN_EXPORT_BATCH_SIZE = 100;
97
101
  const MAX_EXPORT_BATCH_SIZE = 1e4;
102
+ const EXPORT_MAX_PAGES = 1e3;
98
103
  function resolveAvailableExportFormats(list) {
99
104
  if (!list) return [];
100
105
  if (list.export?.enabled === false) return [];
@@ -143,14 +148,15 @@ function buildExportFromColumns(items, columnsConfig) {
143
148
  rows
144
149
  };
145
150
  }
146
- function buildExportFromCsv(items, csv) {
151
+ function buildExportFromCsv(items, csv, query, ctx) {
147
152
  const used = /* @__PURE__ */ new Set();
148
- const columns = csv.headers.map((header, idx) => ({
153
+ const resolvedHeaders = typeof csv.headers === "function" ? csv.headers(query, ctx) : csv.headers;
154
+ const columns = resolvedHeaders.map((header, idx) => ({
149
155
  field: sanitizeFieldName(header || `column_${idx + 1}`, used, idx),
150
156
  header: header || `Column ${idx + 1}`
151
157
  }));
152
158
  const rows = items.map((item) => {
153
- const values = csv.row(item) || [];
159
+ const values = csv.row(item, ctx) || [];
154
160
  const row = {};
155
161
  columns.forEach((column, idx) => {
156
162
  row[column.field] = values[idx];
@@ -171,12 +177,12 @@ function buildDefaultExport(items) {
171
177
  rows
172
178
  };
173
179
  }
174
- function prepareExportData(items, list) {
180
+ function prepareExportData(items, list, query, ctx) {
175
181
  if (list.export?.columns && list.export.columns.length > 0) {
176
182
  return buildExportFromColumns(items, list.export.columns);
177
183
  }
178
184
  if (list.csv) {
179
- return buildExportFromCsv(items, list.csv);
185
+ return buildExportFromCsv(items, list.csv, query, ctx);
180
186
  }
181
187
  const prepared = buildDefaultExport(items);
182
188
  return {
@@ -295,7 +301,20 @@ function attachOperationHeader(res, logEntry) {
295
301
  function handleError(err) {
296
302
  if (err instanceof Response) return err;
297
303
  if (isCrudHttpError(err)) return json(err.body, { status: err.status });
304
+ const interceptorRejection = getCommandInterceptorHttpRejection(err);
305
+ if (interceptorRejection) {
306
+ return json(interceptorRejection.body, { status: interceptorRejection.status });
307
+ }
298
308
  if (err instanceof z.ZodError) return json({ error: "Invalid input", details: err.issues }, { status: 400 });
309
+ if (isTransientDbError(err)) {
310
+ logger.warn("Transient DB failure during CRUD handler", {
311
+ message: err instanceof Error ? err.message : void 0
312
+ });
313
+ return json(
314
+ { error: "Service temporarily unavailable" },
315
+ { status: 503, headers: { "Retry-After": "2" } }
316
+ );
317
+ }
299
318
  const message = err instanceof Error ? err.message : void 0;
300
319
  const stack = err instanceof Error ? err.stack : void 0;
301
320
  logger.error("Unexpected CRUD error", { message, stack, err });
@@ -560,6 +579,12 @@ function buildCrudCacheKey(resource, request, ctx, enricherSignature = "") {
560
579
  `tenant:${normalizeTagSegment(ctx.auth?.tenantId ?? null)}`,
561
580
  `selectedOrg:${normalizeTagSegment(ctx.selectedOrganizationId ?? null)}`,
562
581
  `scope:${scopeSegment}`,
582
+ // List payloads can vary per caller identity beyond tenant/org scope:
583
+ // buildFilters may narrow by ctx.auth (e.g. ?mine=true), before-interceptor
584
+ // query rewrites are feature-gated per user, and afterList/after-interceptor
585
+ // output is embedded in the stored payload — so entries MUST be partitioned
586
+ // per actor (API key or user), never shared across identities.
587
+ `user:${normalizeTagSegment(ctx.auth?.keyId ?? ctx.auth?.sub ?? null)}`,
563
588
  `query:${serializeSearchParams(url.searchParams)}`
564
589
  ];
565
590
  if (enricherSignature) {
@@ -1149,10 +1174,21 @@ function makeCrudRoute(opts) {
1149
1174
  profiler.mark("query_engine_prepare");
1150
1175
  const qe = ctx.container.resolve("queryEngine");
1151
1176
  profiler.mark("query_engine_resolved");
1152
- const { sortField: sortFieldRaw, sortDir: sortDirRaw } = resolveSortParams(queryParams);
1153
- const mappedSortField = opts.list.sortFieldMap && opts.list.sortFieldMap[sortFieldRaw] || sortFieldRaw;
1154
- const sortField = typeof mappedSortField === "string" ? normalizeSortFieldSelector(mappedSortField) : mappedSortField;
1177
+ const sortFieldMap = opts.list.sortFieldMap;
1178
+ const resolveSortSelector = (field) => {
1179
+ const mapped = sortFieldMap && sortFieldMap[field] || field;
1180
+ return typeof mapped === "string" ? normalizeSortFieldSelector(mapped) : mapped;
1181
+ };
1182
+ const { sortField: sortFieldRaw, sortDir: sortDirRaw } = resolveSortParams(
1183
+ queryParams,
1184
+ opts.list.defaultSort
1185
+ );
1186
+ const sortField = resolveSortSelector(sortFieldRaw);
1155
1187
  const sort = [{ field: sortField, dir: sortDirRaw }];
1188
+ if (opts.list.tiebreakSortField) {
1189
+ const tiebreakField = resolveSortSelector(opts.list.tiebreakSortField);
1190
+ if (tiebreakField !== sortField) sort.push({ field: tiebreakField, dir: SortDir.Asc });
1191
+ }
1156
1192
  const page = exportRequested ? { page: 1, pageSize: exportPageSize } : { page: requestedPage, pageSize: requestedPageSize };
1157
1193
  const baseFilters = exportFullRequested ? {} : opts.list.buildFilters ? await opts.list.buildFilters(validated, ctx) : {};
1158
1194
  const filters = exportFullRequested ? baseFilters : mergeAdvancedFilters(baseFilters, validated);
@@ -1262,13 +1298,13 @@ function makeCrudRoute(opts) {
1262
1298
  if (exportRequested && requestedExport) {
1263
1299
  const total = typeof res.total === "number" ? res.total : rawItems.length;
1264
1300
  const initialExportItems = exportFullRequested ? rawItems.map(normalizeFullRecordForExport) : transformedItems;
1265
- let exportItems = [...initialExportItems];
1266
- if (total > exportItems.length) {
1267
- const exportPageSizeNumber = typeof page.pageSize === "number" ? page.pageSize : exportPageSize;
1301
+ const exportItems = [...initialExportItems];
1302
+ const exportPageSizeNumber = typeof page.pageSize === "number" ? page.pageSize : exportPageSize;
1303
+ if (rawItems.length >= exportPageSizeNumber) {
1268
1304
  const queryBase = { ...queryOpts };
1269
1305
  delete queryBase.page;
1270
1306
  let nextPage = 2;
1271
- while (exportItems.length < total) {
1307
+ for (; ; ) {
1272
1308
  profiler.mark("export_next_page_request", { page: nextPage });
1273
1309
  const nextRes = await qe.query(opts.list.entityId, {
1274
1310
  ...queryBase,
@@ -1281,11 +1317,14 @@ function makeCrudRoute(opts) {
1281
1317
  nextTransformed = await decorateItemsWithCustomFields(nextTransformed, ctx, nextRes.customFieldDefinitions);
1282
1318
  const nextExportItems = exportFullRequested ? nextItemsRaw.map(normalizeFullRecordForExport) : nextTransformed;
1283
1319
  exportItems.push(...nextExportItems);
1284
- if (nextExportItems.length < exportPageSizeNumber) break;
1320
+ if (nextItemsRaw.length < exportPageSizeNumber) break;
1321
+ if (nextPage >= EXPORT_MAX_PAGES) {
1322
+ throw new Error(`[internal] export exceeded ${EXPORT_MAX_PAGES} pages; refusing to return a partial export`);
1323
+ }
1285
1324
  nextPage += 1;
1286
1325
  }
1287
1326
  }
1288
- const prepared = exportFullRequested ? { columns: ensureColumns(exportItems), rows: exportItems } : prepareExportData(exportItems, opts.list);
1327
+ const prepared = exportFullRequested ? { columns: ensureColumns(exportItems), rows: exportItems } : prepareExportData(exportItems, opts.list, validated, ctx);
1289
1328
  const fallbackBase = `${opts.events?.entity || resourceKind || "list"}${exportFullRequested ? "_full" : ""}`;
1290
1329
  const filename = finalizeExportFilename(opts.list, requestedExport, fallbackBase);
1291
1330
  const serialized = serializeExport(prepared, requestedExport);
@@ -1469,7 +1508,7 @@ function makeCrudRoute(opts) {
1469
1508
  profiler.mark("access_logged", accessLogResult);
1470
1509
  if (exportRequested && requestedExport) {
1471
1510
  const exportItems = exportFullRequested ? list.map(normalizeFullRecordForExport) : list;
1472
- const prepared = exportFullRequested ? { columns: ensureColumns(exportItems), rows: exportItems } : prepareExportData(exportItems, opts.list);
1511
+ const prepared = exportFullRequested ? { columns: ensureColumns(exportItems), rows: exportItems } : prepareExportData(exportItems, opts.list, validated, ctx);
1473
1512
  const fallbackBase = `${opts.events?.entity || resourceKind || "list"}${exportFullRequested ? "_full" : ""}`;
1474
1513
  const filename = finalizeExportFilename(opts.list, requestedExport, fallbackBase);
1475
1514
  const serialized = serializeExport(prepared, requestedExport);
@@ -1589,6 +1628,29 @@ function makeCrudRoute(opts) {
1589
1628
  }
1590
1629
  }
1591
1630
  }
1631
+ const createCmdUserFeatures = await resolveUserFeatures(ctx);
1632
+ const { allGuards: createCmdAllGuards } = collectAndRunGuards(ctx.container);
1633
+ let createCmdGuardAfterCallbacks = [];
1634
+ if (createCmdAllGuards.length && ctx.auth.tenantId) {
1635
+ const guardResult = await runMutationGuards(createCmdAllGuards, {
1636
+ tenantId: ctx.auth.tenantId,
1637
+ organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId ?? null,
1638
+ userId: ctx.auth.sub,
1639
+ resourceKind,
1640
+ resourceId: null,
1641
+ operation: "create",
1642
+ requestMethod: request.method,
1643
+ requestHeaders: request.headers,
1644
+ mutationPayload: input2 && typeof input2 === "object" ? input2 : null
1645
+ }, { userFeatures: createCmdUserFeatures ?? [] });
1646
+ if (!guardResult.ok) {
1647
+ return json(guardResult.errorBody ?? { error: "Operation blocked by guard" }, { status: guardResult.errorStatus ?? 422 });
1648
+ }
1649
+ if (guardResult.modifiedPayload && typeof input2 === "object" && input2) {
1650
+ input2 = { ...input2, ...guardResult.modifiedPayload };
1651
+ }
1652
+ createCmdGuardAfterCallbacks = guardResult.afterSuccessCallbacks;
1653
+ }
1592
1654
  const baseMetadata = {
1593
1655
  tenantId: ctx.auth?.tenantId ?? null,
1594
1656
  organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId ?? null,
@@ -1628,6 +1690,22 @@ function makeCrudRoute(opts) {
1628
1690
  const status = action.status ?? 201;
1629
1691
  const response = json(resolvedPayload, { status });
1630
1692
  attachOperationHeader(response, logEntry);
1693
+ const commandResultId = pickFirstIdentifier(
1694
+ result?.id,
1695
+ resolvedPayload?.id
1696
+ );
1697
+ if (createCmdGuardAfterCallbacks.length && ctx.auth.tenantId && commandResultId) {
1698
+ await runGuardAfterSuccessCallbacks(createCmdGuardAfterCallbacks, {
1699
+ tenantId: ctx.auth.tenantId,
1700
+ organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId ?? null,
1701
+ userId: ctx.auth.sub,
1702
+ resourceKind,
1703
+ resourceId: commandResultId,
1704
+ operation: "create",
1705
+ requestMethod: request.method,
1706
+ requestHeaders: request.headers
1707
+ });
1708
+ }
1631
1709
  return response;
1632
1710
  }
1633
1711
  const createConfig = opts.create;
@@ -1714,7 +1792,8 @@ function makeCrudRoute(opts) {
1714
1792
  recordId: String(created[ormCfg.idField]),
1715
1793
  organizationId: targetOrgId,
1716
1794
  tenantId: writeTenantId,
1717
- values
1795
+ values,
1796
+ notify: false
1718
1797
  });
1719
1798
  }
1720
1799
  }
@@ -2017,7 +2096,8 @@ function makeCrudRoute(opts) {
2017
2096
  recordId: String(updated[ormCfg.idField]),
2018
2097
  organizationId: targetOrgId,
2019
2098
  tenantId: writeTenantId,
2020
- values
2099
+ values,
2100
+ notify: false
2021
2101
  });
2022
2102
  }
2023
2103
  }
@@ -2113,7 +2193,8 @@ function makeCrudRoute(opts) {
2113
2193
  ctx,
2114
2194
  request,
2115
2195
  method: "DELETE",
2116
- body: interceptorInput
2196
+ body: interceptorInput,
2197
+ query: raw.query
2117
2198
  });
2118
2199
  if (beforeInterceptors2.errorResponse) return beforeInterceptors2.errorResponse;
2119
2200
  interceptorRequestPayload = beforeInterceptors2.requestPayload;
@@ -2224,7 +2305,7 @@ function makeCrudRoute(opts) {
2224
2305
  request,
2225
2306
  method: "DELETE",
2226
2307
  body: idFrom === "query" ? void 0 : { id },
2227
- query: idFrom === "query" ? { id } : void 0
2308
+ query: idFrom === "query" ? Object.fromEntries(url.searchParams.entries()) : void 0
2228
2309
  });
2229
2310
  if (beforeInterceptors.errorResponse) return beforeInterceptors.errorResponse;
2230
2311
  interceptorRequestPayload = beforeInterceptors.requestPayload;