@open-mercato/cli 0.7.0 → 0.7.1-develop.7102.1.b41f7e3e51

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 (184) hide show
  1. package/.turbo/turbo-build.log +7 -1
  2. package/dist/agentic/guides/module-facts.json +5509 -599
  3. package/dist/agentic/guides/module-facts.v2.json +5595 -599
  4. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys/di-registrations-rich.md +11 -0
  7. package/dist/agentic/guides/modules/api_keys/di-service-tokens.md +11 -0
  8. package/dist/agentic/guides/modules/api_keys/exact-override-targets.md +1 -0
  9. package/dist/agentic/guides/modules/api_keys/index.md +4 -2
  10. package/dist/agentic/guides/modules/attachments/di-registrations-rich.md +6 -5
  11. package/dist/agentic/guides/modules/attachments/di-service-tokens.md +5 -4
  12. package/dist/agentic/guides/modules/attachments/exact-override-targets.md +6 -5
  13. package/dist/agentic/guides/modules/attachments/index.md +2 -2
  14. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  15. package/dist/agentic/guides/modules/auth/di-registrations-rich.md +3 -2
  16. package/dist/agentic/guides/modules/auth/di-service-tokens.md +3 -2
  17. package/dist/agentic/guides/modules/auth/exact-override-targets.md +3 -2
  18. package/dist/agentic/guides/modules/auth/index.md +1 -1
  19. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  20. package/dist/agentic/guides/modules/catalog/incoming-installed-contributions.md +3 -0
  21. package/dist/agentic/guides/modules/catalog/index.md +2 -2
  22. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_discord/acl-features.md +13 -0
  24. package/dist/agentic/guides/modules/channel_discord/ai-agents.md +11 -0
  25. package/dist/agentic/guides/modules/channel_discord/backend-pages.md +11 -0
  26. package/dist/agentic/guides/modules/channel_discord/cli-commands.md +13 -0
  27. package/dist/agentic/guides/modules/channel_discord/contribution-resolutions.md +14 -0
  28. package/dist/agentic/guides/modules/channel_discord/di-registrations-rich.md +12 -0
  29. package/dist/agentic/guides/modules/channel_discord/exact-override-targets.md +26 -0
  30. package/dist/agentic/guides/modules/channel_discord/index.md +22 -0
  31. package/dist/agentic/guides/modules/channel_discord/owned-contract-module-metadata.md +11 -0
  32. package/dist/agentic/guides/modules/channel_discord/setup.md +11 -0
  33. package/dist/agentic/guides/modules/channel_discord/umes-contributions.md +14 -0
  34. package/dist/agentic/guides/modules/channel_discord/workers.md +12 -0
  35. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  36. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  37. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  38. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  39. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  40. package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +9 -8
  41. package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +9 -8
  42. package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +2 -0
  43. package/dist/agentic/guides/modules/communication_channels/index.md +3 -3
  44. package/dist/agentic/guides/modules/configs/index.md +1 -1
  45. package/dist/agentic/guides/modules/content/index.md +1 -1
  46. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  47. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  48. package/dist/agentic/guides/modules/customers/active-extension-bindings.md +6 -3
  49. package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
  50. package/dist/agentic/guides/modules/customers/events.md +2 -1
  51. package/dist/agentic/guides/modules/customers/incoming-installed-contributions.md +7 -0
  52. package/dist/agentic/guides/modules/customers/index.md +4 -4
  53. package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
  54. package/dist/agentic/guides/modules/customers/umes-hosts.md +1 -0
  55. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  56. package/dist/agentic/guides/modules/data_sync/exact-override-targets.md +2 -2
  57. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  58. package/dist/agentic/guides/modules/data_sync/workers.md +2 -2
  59. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  60. package/dist/agentic/guides/modules/devices/index.md +1 -1
  61. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  62. package/dist/agentic/guides/modules/directory/di-registrations-rich.md +12 -0
  63. package/dist/agentic/guides/modules/directory/di-service-tokens.md +12 -0
  64. package/dist/agentic/guides/modules/directory/exact-override-targets.md +2 -0
  65. package/dist/agentic/guides/modules/directory/index.md +5 -3
  66. package/dist/agentic/guides/modules/documents/acl-features.md +17 -0
  67. package/dist/agentic/guides/modules/documents/backend-pages.md +13 -0
  68. package/dist/agentic/guides/modules/documents/contribution-resolutions.md +27 -0
  69. package/dist/agentic/guides/modules/documents/di-registrations-rich.md +21 -0
  70. package/dist/agentic/guides/modules/documents/domain-commands.md +37 -0
  71. package/dist/agentic/guides/modules/documents/encryption.md +11 -0
  72. package/dist/agentic/guides/modules/documents/entities.md +21 -0
  73. package/dist/agentic/guides/modules/documents/events.md +25 -0
  74. package/dist/agentic/guides/modules/documents/exact-override-targets.md +39 -0
  75. package/dist/agentic/guides/modules/documents/index.md +25 -0
  76. package/dist/agentic/guides/modules/documents/notifications.md +13 -0
  77. package/dist/agentic/guides/modules/documents/owned-contract-module-metadata.md +11 -0
  78. package/dist/agentic/guides/modules/documents/setup.md +11 -0
  79. package/dist/agentic/guides/modules/documents/umes-contributions.md +27 -0
  80. package/dist/agentic/guides/modules/documents/umes-hosts.md +36 -0
  81. package/dist/agentic/guides/modules/entities/index.md +1 -1
  82. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  83. package/dist/agentic/guides/modules/events/index.md +1 -1
  84. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  85. package/dist/agentic/guides/modules/gateway_stripe/exact-override-targets.md +1 -1
  86. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  87. package/dist/agentic/guides/modules/gateway_stripe/workers.md +1 -1
  88. package/dist/agentic/guides/modules/generators/index.md +1 -1
  89. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  90. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  91. package/dist/agentic/guides/modules/messages/index.md +1 -1
  92. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  93. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  94. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  95. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  96. package/dist/agentic/guides/modules/planner/index.md +1 -1
  97. package/dist/agentic/guides/modules/portal/index.md +1 -1
  98. package/dist/agentic/guides/modules/progress/index.md +1 -1
  99. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  100. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  101. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  102. package/dist/agentic/guides/modules/resources/index.md +1 -1
  103. package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -0
  104. package/dist/agentic/guides/modules/sales/contribution-resolutions.md +1 -0
  105. package/dist/agentic/guides/modules/sales/exact-override-targets.md +5 -4
  106. package/dist/agentic/guides/modules/sales/host-extension-points.md +1 -1
  107. package/dist/agentic/guides/modules/sales/incoming-installed-contributions.md +2 -0
  108. package/dist/agentic/guides/modules/sales/index.md +3 -3
  109. package/dist/agentic/guides/modules/sales/setup.md +1 -1
  110. package/dist/agentic/guides/modules/sales/umes-contributions.md +1 -0
  111. package/dist/agentic/guides/modules/sales/umes-diagnostics.md +1 -0
  112. package/dist/agentic/guides/modules/sales/umes-hosts.md +9 -0
  113. package/dist/agentic/guides/modules/scheduler/cli-commands.md +1 -0
  114. package/dist/agentic/guides/modules/scheduler/exact-override-targets.md +3 -1
  115. package/dist/agentic/guides/modules/scheduler/index.md +2 -2
  116. package/dist/agentic/guides/modules/scheduler/workers.md +2 -1
  117. package/dist/agentic/guides/modules/search/index.md +1 -1
  118. package/dist/agentic/guides/modules/security/index.md +1 -1
  119. package/dist/agentic/guides/modules/security/owned-contract-module-metadata.md +1 -1
  120. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  121. package/dist/agentic/guides/modules/sso/index.md +1 -1
  122. package/dist/agentic/guides/modules/staff/index.md +1 -1
  123. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  124. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  125. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  126. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  127. package/dist/agentic/guides/modules/translations/index.md +1 -1
  128. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  129. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  130. package/dist/agentic/guides/modules/wms/index.md +1 -1
  131. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  132. package/dist/agentic/guides/reference-module-facts.json +2 -2
  133. package/dist/agentic/guides/reference-modules/example/index.md +1 -1
  134. package/dist/agentic/guides/upstream/manifest.json +1 -1
  135. package/dist/agentic/shared/AGENTS.md.template +3 -3
  136. package/dist/agentic/shared/ai/harness/README.md +2 -2
  137. package/dist/agentic/shared/ai/harness/RELEASE.md +8 -6
  138. package/dist/agentic/shared/ai/harness/cases.json +83 -0
  139. package/dist/agentic/shared/ai/harness/cases.schema.json +4 -4
  140. package/dist/agentic/shared/ai/harness/validators.json +1 -1
  141. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +37 -11
  142. package/dist/lib/generators/ast/index.js +4 -1
  143. package/dist/lib/generators/ast/index.js.map +2 -2
  144. package/dist/lib/generators/ast/source-file.js +8 -1
  145. package/dist/lib/generators/ast/source-file.js.map +2 -2
  146. package/dist/lib/generators/ast/writers.js +9 -1
  147. package/dist/lib/generators/ast/writers.js.map +2 -2
  148. package/dist/lib/generators/module-registry.js +424 -189
  149. package/dist/lib/generators/module-registry.js.map +2 -2
  150. package/dist/lib/generators/route-manifest-shards.js +4 -1
  151. package/dist/lib/generators/route-manifest-shards.js.map +2 -2
  152. package/dist/lib/spawn.js +3 -2
  153. package/dist/lib/spawn.js.map +2 -2
  154. package/dist/lib/testing/integration.js +162 -24
  155. package/dist/lib/testing/integration.js.map +2 -2
  156. package/dist/mercato.js +14 -0
  157. package/dist/mercato.js.map +2 -2
  158. package/package.json +8 -7
  159. package/src/__tests__/resolve-queue-abandon-hook.test.ts +39 -0
  160. package/src/lib/__tests__/spawn.test.ts +23 -0
  161. package/src/lib/generators/__tests__/__snapshots__/bootstrap-registrations.test.ts.snap +14 -0
  162. package/src/lib/generators/__tests__/__snapshots__/command-loaders.test.ts.snap +23 -0
  163. package/src/lib/generators/__tests__/ast-writers-escaping.test.ts +64 -0
  164. package/src/lib/generators/__tests__/bootstrap-registrations.test.ts +176 -0
  165. package/src/lib/generators/__tests__/command-loaders.test.ts +239 -0
  166. package/src/lib/generators/__tests__/generated-registry-imports.test.ts +128 -0
  167. package/src/lib/generators/__tests__/metadata-extraction.test.ts +44 -0
  168. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +32 -8
  169. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +4 -1
  170. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +1 -1
  171. package/src/lib/generators/__tests__/module-subset.test.ts +96 -72
  172. package/src/lib/generators/__tests__/output-snapshots.test.ts +1 -1
  173. package/src/lib/generators/__tests__/registry-variant-parity.test.ts +258 -0
  174. package/src/lib/generators/__tests__/route-manifest-shards.test.ts +15 -0
  175. package/src/lib/generators/__tests__/structural-contracts.test.ts +7 -3
  176. package/src/lib/generators/ast/index.ts +2 -1
  177. package/src/lib/generators/ast/source-file.ts +13 -0
  178. package/src/lib/generators/ast/writers.ts +26 -1
  179. package/src/lib/generators/module-registry.ts +534 -227
  180. package/src/lib/generators/route-manifest-shards.ts +8 -1
  181. package/src/lib/spawn.ts +5 -4
  182. package/src/lib/testing/__tests__/integration.test.ts +435 -0
  183. package/src/lib/testing/integration.ts +254 -32
  184. package/src/mercato.ts +27 -0
@@ -28,6 +28,7 @@ import {
28
28
  conditionalExpression,
29
29
  createGeneratedSourceFile,
30
30
  elementAccess,
31
+ escapeGeneratedJsonLiteral,
31
32
  returnStatement,
32
33
  getSourceText,
33
34
  identifier,
@@ -40,6 +41,7 @@ import {
40
41
  optionalPropertyAccess,
41
42
  parenthesized,
42
43
  propertyAccess,
44
+ renderGeneratedValue,
43
45
  spreadExpression,
44
46
  variableStatement,
45
47
  writeArrayLiteral,
@@ -320,6 +322,38 @@ function extractObjectPropertiesFromAst(sourceFile, exportName) {
320
322
  }
321
323
  return Object.keys(result).length > 0 ? result : null;
322
324
  }
325
+ function namedObjectLiteralDeclaresProperty(sourceFile, exportName, propertyName) {
326
+ let source = "";
327
+ try {
328
+ source = fs.readFileSync(sourceFile, "utf8");
329
+ } catch {
330
+ return false;
331
+ }
332
+ const parsed = ts.createSourceFile(sourceFile, source, ts.ScriptTarget.Latest, true, inferScriptKind(sourceFile));
333
+ const exportedNames = /* @__PURE__ */ new Set();
334
+ for (const statement of parsed.statements) {
335
+ if (ts.isExportDeclaration(statement) && statement.exportClause && ts.isNamedExports(statement.exportClause) && !statement.moduleSpecifier) {
336
+ for (const element of statement.exportClause.elements) exportedNames.add(element.name.text);
337
+ }
338
+ }
339
+ for (const statement of parsed.statements) {
340
+ if (!ts.isVariableStatement(statement)) continue;
341
+ const exportsDirectly = (ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : void 0)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
342
+ for (const declaration of statement.declarationList.declarations) {
343
+ if (!ts.isIdentifier(declaration.name) || declaration.name.text !== exportName) continue;
344
+ if (!exportsDirectly && !exportedNames.has(exportName)) continue;
345
+ const objectLiteral2 = unwrapObjectLiteralExpression(declaration.initializer);
346
+ if (!objectLiteral2) return false;
347
+ return objectLiteral2.properties.some((property) => {
348
+ const name = property.name;
349
+ if (!name) return false;
350
+ const key = ts.isIdentifier(name) ? name.text : ts.isStringLiteral(name) ? name.text : null;
351
+ return key === propertyName;
352
+ });
353
+ }
354
+ }
355
+ return false;
356
+ }
323
357
  function extractNamedObjectLiteralExport(sourceFile, exportName) {
324
358
  const literal = extractNamedObjectLiteralSource(sourceFile, exportName);
325
359
  if (literal) {
@@ -743,7 +777,7 @@ function requiresRuntimePageMetadataFromSourceFile(sourceFile) {
743
777
  return !/^['"`]/.test(iconMatch[1].trim());
744
778
  }
745
779
  function toLiteral(value) {
746
- return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
780
+ return escapeGeneratedJsonLiteral(JSON.stringify(value));
747
781
  }
748
782
  const GENERATED_MODULE_RELATIVE_PREFIXES = ["@/", "../../src/modules/", "./"];
749
783
  const GENERATED_MODULE_SPECIFIER_SEGMENT = /^[A-Za-z0-9_.\-[\]()']+$/;
@@ -932,28 +966,66 @@ function collectCommandLoaderEntries(roots, imps, modId, quiet = false) {
932
966
  }
933
967
  function renderCommandLoadersFile(entries) {
934
968
  const seenCommandIds = /* @__PURE__ */ new Map();
935
- const rendered = [];
969
+ const loaderEntries = [];
936
970
  for (const entry of entries) {
937
- const loadExpr = `() => ${buildDynamicImportExpression(entry.importPath)}`;
971
+ const loadValue = arrowFunction({
972
+ body: importExpression(sanitizeGeneratedModuleSpecifier(entry.importPath))
973
+ });
938
974
  for (const id of entry.ids) {
939
975
  const previous = seenCommandIds.get(id);
940
976
  if (previous && previous !== entry.key) {
941
977
  throw new Error(`[generate] Duplicate command id "${id}" discovered in "${previous}" and "${entry.key}"`);
942
978
  }
943
979
  seenCommandIds.set(id, entry.key);
944
- rendered.push(` { moduleId: ${toLiteral(entry.moduleId)}, id: ${toLiteral(id)}, key: ${toLiteral(entry.key)}, load: ${loadExpr} },`);
980
+ loaderEntries.push(objectLiteral([
981
+ { name: "moduleId", value: entry.moduleId },
982
+ { name: "id", value: id },
983
+ { name: "key", value: entry.key },
984
+ { name: "load", value: loadValue }
985
+ ]));
945
986
  }
946
- rendered.push(` { moduleId: ${toLiteral(entry.moduleId)}, key: ${toLiteral(entry.key)}, load: ${loadExpr} },`);
987
+ loaderEntries.push(objectLiteral([
988
+ { name: "moduleId", value: entry.moduleId },
989
+ { name: "key", value: entry.key },
990
+ { name: "load", value: loadValue }
991
+ ]));
947
992
  }
948
- return `// AUTO-GENERATED by mercato generate command-loaders
949
- import type { CommandLoader } from '@open-mercato/shared/lib/commands'
950
-
951
- export const commandLoaderEntries: CommandLoader[] = [
952
- ${rendered.join("\n")}
953
- ]
954
-
955
- export default commandLoaderEntries
956
- `;
993
+ const sourceFile = createGeneratedSourceFile("command-loaders.generated.ts");
994
+ addAutoGeneratedComment(sourceFile, "command-loaders");
995
+ addImportSpec(sourceFile, {
996
+ moduleSpecifier: "@open-mercato/shared/lib/commands",
997
+ isTypeOnly: true,
998
+ namedImports: [{ name: "CommandLoader" }]
999
+ });
1000
+ sourceFile.addVariableStatement({
1001
+ declarationKind: VariableDeclarationKind.Const,
1002
+ isExported: true,
1003
+ declarations: [
1004
+ {
1005
+ name: "commandLoaderEntries",
1006
+ type: "CommandLoader[]",
1007
+ initializer: arrayLiteral(loaderEntries, writeValue)
1008
+ }
1009
+ ]
1010
+ });
1011
+ sourceFile.addExportAssignment({
1012
+ isExportEquals: false,
1013
+ expression: "commandLoaderEntries"
1014
+ });
1015
+ return getSourceText(sourceFile);
1016
+ }
1017
+ function renderBootstrapRegistrationsFile(options) {
1018
+ const uniqueImports = [.../* @__PURE__ */ new Set([...options.entryImports, ...options.registrationImports])];
1019
+ const sourceFile = createGeneratedSourceFile("bootstrap-registrations.generated.ts");
1020
+ addAutoGeneratedComment(sourceFile, "registry");
1021
+ addImportStatements(sourceFile, uniqueImports);
1022
+ sourceFile.addFunction({
1023
+ name: "runBootstrapRegistrations",
1024
+ isExported: true,
1025
+ returnType: "void",
1026
+ statements: options.calls
1027
+ });
1028
+ return getSourceText(sourceFile);
957
1029
  }
958
1030
  function serializeGeneratedImport(statement) {
959
1031
  if (typeof statement === "string") {
@@ -1136,7 +1208,35 @@ function buildPageRouteProps(metaExpr, routePath) {
1136
1208
  return `pattern: ${toLiteral(routePath || "/")}, requireAuth: (${metaExpr})?.requireAuth, requireRoles: (${metaExpr})?.requireRoles, requireFeatures: (${metaExpr})?.requireFeatures, requireCustomerAuth: (${metaExpr})?.requireCustomerAuth, requireCustomerFeatures: (${metaExpr})?.requireCustomerFeatures, nav: (${metaExpr})?.nav, title: (${metaExpr})?.pageTitle ?? (${metaExpr})?.title, titleKey: (${metaExpr})?.pageTitleKey ?? (${metaExpr})?.titleKey, group: (${metaExpr})?.pageGroup ?? (${metaExpr})?.group, groupKey: (${metaExpr})?.pageGroupKey ?? (${metaExpr})?.groupKey, icon: (${metaExpr})?.icon, order: (${metaExpr})?.pageOrder ?? (${metaExpr})?.order, priority: (${metaExpr})?.pagePriority ?? (${metaExpr})?.priority, navHidden: (${metaExpr})?.navHidden, visible: (${metaExpr})?.visible, enabled: (${metaExpr})?.enabled, breadcrumb: (${metaExpr})?.breadcrumb, pageContext: (${metaExpr})?.pageContext, placement: (${metaExpr})?.placement`;
1137
1209
  }
1138
1210
  function buildPageRouteManifestSpread(metaExpr, routePath) {
1139
- return `...resolvePageRouteMetadata(${toLiteral(routePath || "/")}, ${metaExpr})`;
1211
+ return callExpression(identifier("resolvePageRouteMetadata"), [routePath || "/", identifier(metaExpr)]);
1212
+ }
1213
+ function buildManifestRouteLoader(importPath) {
1214
+ return arrowFunction({
1215
+ async: true,
1216
+ body: block((writer) => {
1217
+ writeStatements(writer, [
1218
+ variableStatement({
1219
+ name: "mod",
1220
+ initializer: awaitExpression(importExpression(sanitizeGeneratedModuleSpecifier(importPath)))
1221
+ }),
1222
+ returnStatement(
1223
+ asExpression(
1224
+ parenthesized(nullishCoalesce([
1225
+ propertyAccess(identifier("mod"), "default"),
1226
+ identifier("mod")
1227
+ ])),
1228
+ "any"
1229
+ )
1230
+ )
1231
+ ]);
1232
+ })
1233
+ });
1234
+ }
1235
+ function buildManifestApiLoader(importPath) {
1236
+ return arrowFunction({
1237
+ async: true,
1238
+ body: importExpression(sanitizeGeneratedModuleSpecifier(importPath))
1239
+ });
1140
1240
  }
1141
1241
  function normalizeBreadcrumb(raw) {
1142
1242
  if (!Array.isArray(raw)) return void 0;
@@ -1238,6 +1338,14 @@ function normalizeWorkerMetadata(raw) {
1238
1338
  if (typeof source.concurrency === "number") normalized.concurrency = source.concurrency;
1239
1339
  if (typeof source.lockDuration === "number") normalized.lockDuration = source.lockDuration;
1240
1340
  if (typeof source.maxStalledCount === "number") normalized.maxStalledCount = source.maxStalledCount;
1341
+ if (typeof source.onJobAbandoned === "function") normalized.hasJobAbandonedHook = true;
1342
+ if (source.schedulerSafe === true) normalized.schedulerSafe = true;
1343
+ if (Array.isArray(source.schedulerRequiredFeatures)) {
1344
+ const features = source.schedulerRequiredFeatures.filter(
1345
+ (feature) => typeof feature === "string" && feature.length > 0
1346
+ );
1347
+ if (features.length > 0) normalized.schedulerRequiredFeatures = features;
1348
+ }
1241
1349
  return Object.keys(normalized).length > 0 ? normalized : null;
1242
1350
  }
1243
1351
  async function loadSubscriberMetadata(sourceFile) {
@@ -1246,7 +1354,12 @@ async function loadSubscriberMetadata(sourceFile) {
1246
1354
  }
1247
1355
  async function loadWorkerMetadata(sourceFile) {
1248
1356
  const sourceModule = await loadModuleExportsFromSource(sourceFile);
1249
- return normalizeWorkerMetadata(sourceModule?.metadata) ?? normalizeWorkerMetadata(extractNamedObjectLiteralExport(sourceFile, "metadata"));
1357
+ const metadata = normalizeWorkerMetadata(sourceModule?.metadata) ?? normalizeWorkerMetadata(extractNamedObjectLiteralExport(sourceFile, "metadata"));
1358
+ if (!metadata) return null;
1359
+ if (!metadata.hasJobAbandonedHook && namedObjectLiteralDeclaresProperty(sourceFile, "metadata", "onJobAbandoned")) {
1360
+ metadata.hasJobAbandonedHook = true;
1361
+ }
1362
+ return metadata;
1250
1363
  }
1251
1364
  async function discoverSubscribers(roots, imps, modId) {
1252
1365
  const subscribers = [];
@@ -1457,18 +1570,25 @@ async function processPageFiles(options) {
1457
1570
  const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath);
1458
1571
  eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`);
1459
1572
  runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`);
1460
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps} }`;
1461
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`;
1462
- manifestRoutes.push(manifestDeclaration);
1573
+ const metadataEntries = [
1574
+ { name: "moduleId", value: modId },
1575
+ { kind: "spread", value: manifestBaseProps }
1576
+ ];
1577
+ const manifestEntry = objectLiteral([
1578
+ ...metadataEntries,
1579
+ { name: "load", value: buildManifestRouteLoader(importPath) }
1580
+ ]);
1581
+ const shardImports = manifestImportStatement ? [manifestImportStatement] : [];
1582
+ manifestRoutes.push(manifestEntry);
1463
1583
  manifestShardRoutes.push({
1464
1584
  path: routePath,
1465
- declaration: manifestDeclaration,
1466
- imports: manifestImportStatement ? [manifestImportStatement] : []
1585
+ declaration: renderGeneratedValue(manifestEntry),
1586
+ imports: shardImports
1467
1587
  });
1468
1588
  metadataRoutes.push({
1469
1589
  path: routePath,
1470
- declaration: metadataDeclaration,
1471
- imports: manifestImportStatement ? [manifestImportStatement] : []
1590
+ declaration: renderGeneratedValue(objectLiteral(metadataEntries)),
1591
+ imports: shardImports
1472
1592
  });
1473
1593
  routePatterns.push(routePath);
1474
1594
  }
@@ -1542,18 +1662,25 @@ async function processPageFiles(options) {
1542
1662
  const manifestBaseProps = buildPageRouteManifestSpread(manifestMetaExpr, routePath);
1543
1663
  eagerRoutes.push(`{ ${baseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`);
1544
1664
  runtimeRoutes.push(`{ ${runtimeBaseProps}, Component: ${buildLazyRouteComponentExpression(importPath)} }`);
1545
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps} }`;
1546
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, ${manifestBaseProps}, load: async () => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod.default ?? mod) as any } }`;
1547
- manifestRoutes.push(manifestDeclaration);
1665
+ const metadataEntries = [
1666
+ { name: "moduleId", value: modId },
1667
+ { kind: "spread", value: manifestBaseProps }
1668
+ ];
1669
+ const manifestEntry = objectLiteral([
1670
+ ...metadataEntries,
1671
+ { name: "load", value: buildManifestRouteLoader(importPath) }
1672
+ ]);
1673
+ const shardImports = manifestImportStatement ? [manifestImportStatement] : [];
1674
+ manifestRoutes.push(manifestEntry);
1548
1675
  manifestShardRoutes.push({
1549
1676
  path: routePath,
1550
- declaration: manifestDeclaration,
1551
- imports: manifestImportStatement ? [manifestImportStatement] : []
1677
+ declaration: renderGeneratedValue(manifestEntry),
1678
+ imports: shardImports
1552
1679
  });
1553
1680
  metadataRoutes.push({
1554
1681
  path: routePath,
1555
- declaration: metadataDeclaration,
1556
- imports: manifestImportStatement ? [manifestImportStatement] : []
1682
+ declaration: renderGeneratedValue(objectLiteral(metadataEntries)),
1683
+ imports: shardImports
1557
1684
  });
1558
1685
  routePatterns.push(routePath);
1559
1686
  }
@@ -1606,11 +1733,19 @@ async function processApiRoutes(options) {
1606
1733
  eagerImports.push(buildImportStatement(`* as ${importName}`, importPath));
1607
1734
  eagerApis.push(`{ path: ((${importName} as any).metadata?.path ?? ${toLiteral(routePath)}), metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`);
1608
1735
  runtimeApis.push(`{ path: ${toLiteral(resolvedPath)}, metadata: ${metadataLiteral}, handlers: { ${exportedMethods.map((method) => `${method}: async (req: Request, ctx?: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod as any).${method}(req, ctx) }`).join(", ")} } }`);
1609
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral("route-file")}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((method) => toLiteral(method)).join(", ")}] }`;
1610
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral("route-file")}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((method) => toLiteral(method)).join(", ")}], load: async () => ${buildDynamicImportExpression(importPath)} }`;
1611
- manifestApis.push(manifestDeclaration);
1612
- manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration });
1613
- metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration });
1736
+ const metadataEntries = [
1737
+ { name: "moduleId", value: modId },
1738
+ { name: "kind", value: "route-file" },
1739
+ { name: "path", value: resolvedPath },
1740
+ { name: "methods", value: arrayLiteral(exportedMethods, writeValue) }
1741
+ ];
1742
+ const manifestEntry = objectLiteral([
1743
+ ...metadataEntries,
1744
+ { name: "load", value: buildManifestApiLoader(importPath) }
1745
+ ]);
1746
+ manifestApis.push(manifestEntry);
1747
+ manifestShardApis.push({ path: resolvedPath, declaration: renderGeneratedValue(manifestEntry) });
1748
+ metadataApis.push({ path: resolvedPath, declaration: renderGeneratedValue(objectLiteral(metadataEntries)) });
1614
1749
  }
1615
1750
  const plainFiles = scanModuleDir(roots, SCAN_CONFIGS.apiPlainFiles);
1616
1751
  for (const { relPath, fromApp } of plainFiles) {
@@ -1640,11 +1775,19 @@ async function processApiRoutes(options) {
1640
1775
  eagerImports.push(buildImportStatement(`* as ${importName}`, importPath));
1641
1776
  eagerApis.push(`{ path: ((${importName} as any).metadata?.path ?? ${toLiteral(routePath)}), metadata: (${importName} as any).metadata, handlers: ${importName} as any${docsPart} }`);
1642
1777
  runtimeApis.push(`{ path: ${toLiteral(resolvedPath)}, metadata: ${metadataLiteral}, handlers: { ${exportedMethods.map((entryMethod) => `${entryMethod}: async (req: Request, ctx?: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; return (mod as any).${entryMethod}(req, ctx) }`).join(", ")} } }`);
1643
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral("route-file")}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((entryMethod) => toLiteral(entryMethod)).join(", ")}] }`;
1644
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral("route-file")}, path: ${toLiteral(resolvedPath)}, methods: [${exportedMethods.map((entryMethod) => toLiteral(entryMethod)).join(", ")}], load: async () => ${buildDynamicImportExpression(importPath)} }`;
1645
- manifestApis.push(manifestDeclaration);
1646
- manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration });
1647
- metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration });
1778
+ const metadataEntries = [
1779
+ { name: "moduleId", value: modId },
1780
+ { name: "kind", value: "route-file" },
1781
+ { name: "path", value: resolvedPath },
1782
+ { name: "methods", value: arrayLiteral(exportedMethods, writeValue) }
1783
+ ];
1784
+ const manifestEntry = objectLiteral([
1785
+ ...metadataEntries,
1786
+ { name: "load", value: buildManifestApiLoader(importPath) }
1787
+ ]);
1788
+ manifestApis.push(manifestEntry);
1789
+ manifestShardApis.push({ path: resolvedPath, declaration: renderGeneratedValue(manifestEntry) });
1790
+ metadataApis.push({ path: resolvedPath, declaration: renderGeneratedValue(objectLiteral(metadataEntries)) });
1648
1791
  }
1649
1792
  const methods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
1650
1793
  for (const method of methods) {
@@ -1683,11 +1826,20 @@ async function processApiRoutes(options) {
1683
1826
  eagerImports.push(buildImportStatement(`${importName}, * as ${metaName}`, importPath));
1684
1827
  eagerApis.push(`{ method: ${toLiteral(method)}, path: (${metaName}.metadata?.path ?? ${toLiteral(routePath)}), handler: ${importName}, metadata: ${metaName}.metadata${docsPart} }`);
1685
1828
  runtimeApis.push(`{ method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, handler: async (req: Request, ctx?: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; const handler = ((mod as any).default ?? (mod as any).${method} ?? (mod as any).handler) as any; return handler(req, ctx) }, metadata: ${metadataLiteral} }`);
1686
- const metadataDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral("legacy")}, method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, methods: [${toLiteral(method)}] }`;
1687
- const manifestDeclaration = `{ moduleId: ${toLiteral(modId)}, kind: ${toLiteral("legacy")}, method: ${toLiteral(method)}, path: ${toLiteral(resolvedPath)}, methods: [${toLiteral(method)}], load: async () => ${buildDynamicImportExpression(importPath)} }`;
1688
- manifestApis.push(manifestDeclaration);
1689
- manifestShardApis.push({ path: resolvedPath, declaration: manifestDeclaration });
1690
- metadataApis.push({ path: resolvedPath, declaration: metadataDeclaration });
1829
+ const metadataEntries = [
1830
+ { name: "moduleId", value: modId },
1831
+ { name: "kind", value: "legacy" },
1832
+ { name: "method", value: method },
1833
+ { name: "path", value: resolvedPath },
1834
+ { name: "methods", value: arrayLiteral([method], writeValue) }
1835
+ ];
1836
+ const manifestEntry = objectLiteral([
1837
+ ...metadataEntries,
1838
+ { name: "load", value: buildManifestApiLoader(importPath) }
1839
+ ]);
1840
+ manifestApis.push(manifestEntry);
1841
+ manifestShardApis.push({ path: resolvedPath, declaration: renderGeneratedValue(manifestEntry) });
1842
+ metadataApis.push({ path: resolvedPath, declaration: renderGeneratedValue(objectLiteral(metadataEntries)) });
1691
1843
  }
1692
1844
  }
1693
1845
  return {
@@ -1698,53 +1850,9 @@ async function processApiRoutes(options) {
1698
1850
  metadataApis
1699
1851
  };
1700
1852
  }
1701
- function processSubscribers(discovered) {
1702
- const subscribers = [];
1703
- for (const { id, importPath, metadata } of discovered) {
1704
- const subscriberId = metadata?.id ?? id;
1705
- subscribers.push(
1706
- `{ id: ${toLiteral(subscriberId)}, event: ${toLiteral(metadata?.event ?? "")}, persistent: ${metadata?.persistent === void 0 ? "undefined" : toLiteral(metadata.persistent)}, sync: ${metadata?.sync === void 0 ? "undefined" : toLiteral(metadata.sync)}, priority: ${metadata?.priority === void 0 ? "undefined" : toLiteral(metadata.priority)}, handler: createLazyModuleSubscriber(() => ${buildDynamicImportExpression(importPath)}, ${toLiteral(subscriberId)}) }`
1707
- );
1708
- }
1709
- return subscribers;
1710
- }
1711
- function processWorkers(discovered) {
1712
- const workers = [];
1713
- for (const { id, importPath, metadata } of discovered) {
1714
- const workerId = metadata.id ?? id;
1715
- workers.push(
1716
- `{ id: ${toLiteral(workerId)}, queue: ${toLiteral(metadata.queue)}, concurrency: ${toLiteral(metadata.concurrency ?? 1)}${metadata.lockDuration === void 0 ? "" : `, lockDuration: ${toLiteral(metadata.lockDuration)}`}${metadata.maxStalledCount === void 0 ? "" : `, maxStalledCount: ${toLiteral(metadata.maxStalledCount)}`}, handler: createLazyModuleWorker(() => ${buildDynamicImportExpression(importPath)}, ${toLiteral(workerId)}) }`
1717
- );
1718
- }
1719
- return workers;
1720
- }
1721
- function processTranslations(options) {
1722
- const { discovered, modId, appImportBase, pkgImportBase, imports, extraImports } = options;
1723
- const translations = [];
1724
- for (const { locale, coreHas, appHas } of discovered) {
1725
- if (coreHas && appHas) {
1726
- const cName = `T_${toVar(modId)}_${toVar(locale)}_C`;
1727
- const aName = `T_${toVar(modId)}_${toVar(locale)}_A`;
1728
- imports.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`));
1729
- imports.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`));
1730
- extraImports?.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`));
1731
- extraImports?.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`));
1732
- translations.push(
1733
- `'${locale}': { ...( ${cName} as unknown as Record<string,string> ), ...( ${aName} as unknown as Record<string,string> ) }`
1734
- );
1735
- } else if (appHas) {
1736
- const aName = `T_${toVar(modId)}_${toVar(locale)}_A`;
1737
- imports.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`));
1738
- extraImports?.push(buildImportStatement(aName, `${appImportBase}/i18n/${locale}.json`));
1739
- translations.push(`'${locale}': ${aName} as unknown as Record<string,string>`);
1740
- } else if (coreHas) {
1741
- const cName = `T_${toVar(modId)}_${toVar(locale)}_C`;
1742
- imports.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`));
1743
- extraImports?.push(buildImportStatement(cName, `${pkgImportBase}/i18n/${locale}.json`));
1744
- translations.push(`'${locale}': ${cName} as unknown as Record<string,string>`);
1745
- }
1746
- }
1747
- return translations;
1853
+ const ABANDON_HOOK_FACTORY = "createLazyModuleWorkerAbandonHook";
1854
+ function workersDeclareAbandonHook(discovered) {
1855
+ return discovered.some(({ metadata }) => metadata.hasJobAbandonedHook === true);
1748
1856
  }
1749
1857
  function processStandaloneConfig(options) {
1750
1858
  const {
@@ -1870,6 +1978,37 @@ function buildModuleDashboardWidgetsValue(entries) {
1870
1978
  writeValue
1871
1979
  );
1872
1980
  }
1981
+ function preRenderedExpressionList(expressions) {
1982
+ return arrayLiteral(expressions.map((expression) => identifier(expression)), writeValue);
1983
+ }
1984
+ function legacyNamespaceMemberFallback(importName, members) {
1985
+ return parenthesized(nullishCoalesce([
1986
+ propertyAccess(identifier(importName), members[0]),
1987
+ propertyAccess(identifier(importName), members[1])
1988
+ ]));
1989
+ }
1990
+ function buildLegacyModuleListValue(options) {
1991
+ return logicalOr([
1992
+ asExpression(legacyNamespaceMemberFallback(options.importName, options.members), options.castType),
1993
+ emptyArray()
1994
+ ]);
1995
+ }
1996
+ function buildLegacyModuleSetupValue(importName) {
1997
+ return logicalOr([
1998
+ legacyNamespaceMemberFallback(importName, ["default", "setup"]),
1999
+ identifier("undefined")
2000
+ ]);
2001
+ }
2002
+ function buildLegacyIntegrationListValue(importName, pluralMember, singularMember, castType) {
2003
+ const singular = propertyAccess(identifier(importName), singularMember);
2004
+ return asExpression(
2005
+ parenthesized(nullishCoalesce([
2006
+ propertyAccess(identifier(importName), pluralMember),
2007
+ parenthesized(conditionalExpression(singular, arrayLiteral([singular], writeValue), emptyArray()))
2008
+ ])),
2009
+ castType
2010
+ );
2011
+ }
1873
2012
  function buildModuleIntegrationListValue(importName, pluralMember, singularMember, castType) {
1874
2013
  const plural = namespaceLookup(importName, pluralMember);
1875
2014
  const singular = namespaceLookup(importName, singularMember);
@@ -1920,10 +2059,11 @@ function renderAstModuleRegistryFile(options) {
1920
2059
  namedImports: [
1921
2060
  { name: "createLazyModuleSubscriber" },
1922
2061
  { name: "createLazyModuleWorker" },
2062
+ ...options.includeAbandonHookImport ? [{ name: ABANDON_HOOK_FACTORY }] : [],
1923
2063
  { name: "Module", isTypeOnly: true }
1924
2064
  ]
1925
2065
  });
1926
- addImportStatements(sourceFile, options.imports);
2066
+ addImportStatements(sourceFile, options.imports.map((entry) => serializeGeneratedImport(entry)));
1927
2067
  sourceFile.addVariableStatement({
1928
2068
  declarationKind: VariableDeclarationKind.Const,
1929
2069
  isExported: true,
@@ -1951,6 +2091,39 @@ function renderAstModuleRegistryFile(options) {
1951
2091
  });
1952
2092
  return getSourceText(sourceFile);
1953
2093
  }
2094
+ function renderAstManifestFile(options) {
2095
+ const sourceFile = createGeneratedSourceFile(options.fileName);
2096
+ addAutoGeneratedComment(sourceFile, "registry");
2097
+ const usesPageRouteMetadataHelper = options.typeName === "FrontendRouteManifestEntry" || options.typeName === "BackendRouteManifestEntry";
2098
+ addImportSpec(sourceFile, usesPageRouteMetadataHelper ? {
2099
+ moduleSpecifier: "@open-mercato/shared/modules/registry",
2100
+ namedImports: [
2101
+ { name: "resolvePageRouteMetadata" },
2102
+ { name: options.typeName, isTypeOnly: true }
2103
+ ]
2104
+ } : {
2105
+ moduleSpecifier: "@open-mercato/shared/modules/registry",
2106
+ isTypeOnly: true,
2107
+ namedImports: [{ name: options.typeName }]
2108
+ });
2109
+ addImportStatements(sourceFile, options.imports ?? []);
2110
+ sourceFile.addVariableStatement({
2111
+ declarationKind: VariableDeclarationKind.Const,
2112
+ isExported: true,
2113
+ declarations: [
2114
+ {
2115
+ name: options.exportName,
2116
+ type: `${options.typeName}[]`,
2117
+ initializer: arrayLiteral(options.entries, writeValue)
2118
+ }
2119
+ ]
2120
+ });
2121
+ sourceFile.addExportAssignment({
2122
+ isExportEquals: false,
2123
+ expression: options.exportName
2124
+ });
2125
+ return getSourceText(sourceFile);
2126
+ }
1954
2127
  function renderAstLegacyAliasFile(options) {
1955
2128
  const sourceFile = createGeneratedSourceFile(options.fileName);
1956
2129
  addAutoGeneratedComment(sourceFile, "registry");
@@ -2001,42 +2174,9 @@ function renderEnabledModuleIdsFile(moduleIds) {
2001
2174
  });
2002
2175
  return getSourceText(sourceFile);
2003
2176
  }
2004
- function renderLegacyCompatibleArray(entries) {
2005
- return `[
2006
- ${entries.join(",\n ")}
2007
- ]`;
2008
- }
2009
2177
  function buildLazyRouteComponentExpression(importPath) {
2010
2178
  return `async (props: any) => { const mod = await ${buildDynamicImportExpression(importPath)}; const Component = (mod.default ?? mod) as any; return createElement(Component, props) }`;
2011
2179
  }
2012
- function renderAstLegacyModuleRegistryOutput(options) {
2013
- const importSection = [
2014
- ...options.includeCreateElementImport ? ["import { createElement } from 'react'"] : [],
2015
- "import { createLazyModuleSubscriber, createLazyModuleWorker, type Module } from '@open-mercato/shared/modules/registry'",
2016
- ...options.imports.map((entry) => serializeGeneratedImport(entry))
2017
- ].join("\n");
2018
- return `// AUTO-GENERATED by mercato generate registry
2019
- ${importSection}
2020
-
2021
- export const modules: Module[] = ${renderLegacyCompatibleArray(options.moduleEntries)}
2022
- export const modulesInfo = modules.map(m => ({ id: m.id, ...(m.info || {}) }))
2023
- export default modules
2024
- `;
2025
- }
2026
- function renderAstLegacyManifestOutput(options) {
2027
- const usesPageRouteMetadataHelper = options.typeName === "FrontendRouteManifestEntry" || options.typeName === "BackendRouteManifestEntry";
2028
- const importSection = [
2029
- usesPageRouteMetadataHelper ? `import { resolvePageRouteMetadata, type ${options.typeName} } from '@open-mercato/shared/modules/registry'` : `import type { ${options.typeName} } from '@open-mercato/shared/modules/registry'`,
2030
- ...(options.imports ?? []).map((entry) => serializeGeneratedImport(entry))
2031
- ].join("\n");
2032
- return `// AUTO-GENERATED by mercato generate registry
2033
- ${importSection}
2034
-
2035
- export const ${options.exportName}: ${options.typeName}[] = ${renderLegacyCompatibleArray(options.entries)}
2036
-
2037
- export default ${options.exportName}
2038
- `;
2039
- }
2040
2180
  function buildRuntimeRouteComponent(importPath) {
2041
2181
  return arrowFunction({
2042
2182
  async: true,
@@ -2277,8 +2417,17 @@ function processWorkersAst(discovered) {
2277
2417
  { name: "id", value: workerId },
2278
2418
  { name: "queue", value: metadata.queue },
2279
2419
  { name: "concurrency", value: metadata.concurrency ?? 1 },
2420
+ ...metadata.schedulerSafe === true ? [{ name: "schedulerSafe", value: true }] : [],
2421
+ ...metadata.schedulerRequiredFeatures ? [{ name: "schedulerRequiredFeatures", value: arrayLiteral(metadata.schedulerRequiredFeatures, writeValue) }] : [],
2280
2422
  ...metadata.lockDuration === void 0 ? [] : [{ name: "lockDuration", value: metadata.lockDuration }],
2281
2423
  ...metadata.maxStalledCount === void 0 ? [] : [{ name: "maxStalledCount", value: metadata.maxStalledCount }],
2424
+ ...metadata.hasJobAbandonedHook ? [{
2425
+ name: "onJobAbandoned",
2426
+ value: callExpression(identifier(ABANDON_HOOK_FACTORY), [
2427
+ arrowFunction({ body: importExpression(importPath) }),
2428
+ workerId
2429
+ ])
2430
+ }] : [],
2282
2431
  {
2283
2432
  name: "handler",
2284
2433
  value: callExpression(identifier("createLazyModuleWorker"), [
@@ -2588,6 +2737,7 @@ async function generateModuleRegistryFromDiscovery(options) {
2588
2737
  const requiresByModule = /* @__PURE__ */ new Map();
2589
2738
  const commandLoaderEntries = [];
2590
2739
  let hasRouteComponents = false;
2740
+ let hasAbandonHookWorkers = false;
2591
2741
  const umesConflictSources = [];
2592
2742
  const seenBackendRoutePatterns = /* @__PURE__ */ new Map();
2593
2743
  for (const discovered of discovery.modules) {
@@ -2782,7 +2932,7 @@ async function generateModuleRegistryFromDiscovery(options) {
2782
2932
  cliImportName = importName;
2783
2933
  }
2784
2934
  }
2785
- translations.push(...processTranslations({
2935
+ translations.push(...processTranslationsAst({
2786
2936
  discovered: discovered.translations,
2787
2937
  modId,
2788
2938
  appImportBase,
@@ -2790,8 +2940,10 @@ async function generateModuleRegistryFromDiscovery(options) {
2790
2940
  imports,
2791
2941
  extraImports: runtimeImports
2792
2942
  }));
2793
- subscribers.push(...processSubscribers(await discovered.getSubscribers()));
2794
- workers.push(...processWorkers(await discovered.getWorkers()));
2943
+ subscribers.push(...processSubscribersAst(await discovered.getSubscribers()));
2944
+ const discoveredWorkersForModule = await discovered.getWorkers();
2945
+ if (workersDeclareAbandonHook(discoveredWorkersForModule)) hasAbandonHookWorkers = true;
2946
+ workers.push(...processWorkersAst(discoveredWorkersForModule));
2795
2947
  {
2796
2948
  const parts = [];
2797
2949
  if (fieldsImportName)
@@ -2809,44 +2961,116 @@ async function generateModuleRegistryFromDiscovery(options) {
2809
2961
  );
2810
2962
  }
2811
2963
  }
2812
- moduleDecls.push(`{
2813
- id: ${toLiteral(modId)},
2814
- ${infoImportName ? `info: ${infoImportName}.metadata,` : ""}
2815
- ${frontendRoutes.length ? `frontendRoutes: [${frontendRoutes.join(", ")}],` : ""}
2816
- ${backendRoutes.length ? `backendRoutes: [${backendRoutes.join(", ")}],` : ""}
2817
- ${apis.length ? `apis: [${apis.join(", ")}],` : ""}
2818
- ${cliImportName ? `cli: ${cliImportName},` : ""}
2819
- ${translations.length ? `translations: { ${translations.join(", ")} },` : ""}
2820
- ${subscribers.length ? `subscribers: [${subscribers.join(", ")}],` : ""}
2821
- ${workers.length ? `workers: [${workers.join(", ")}],` : ""}
2822
- ${extensionsImportName ? `entityExtensions: ((${extensionsImportName}.default ?? ${extensionsImportName}.extensions) as import('@open-mercato/shared/modules/entities').EntityExtension[]) || [],` : ""}
2823
- customFieldSets: ${customFieldSetsExpr},
2824
- ${featuresImportName ? `features: ((${featuresImportName}.default ?? ${featuresImportName}.features) as any) || [],` : ""}
2825
- ${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ""}
2826
- ${dashboardWidgets.length ? `dashboardWidgets: [${dashboardWidgets.join(", ")}],` : ""}
2827
- ${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ""}
2828
- ${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ""}
2829
- ${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ""}
2830
- ${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ""}
2831
- }`);
2832
- runtimeModuleDecls.push(`{
2833
- id: ${toLiteral(modId)},
2834
- ${infoImportName ? `info: ${infoImportName}.metadata,` : ""}
2835
- ${runtimeFrontendRoutes.length ? `frontendRoutes: [${runtimeFrontendRoutes.join(", ")}],` : ""}
2836
- ${runtimeBackendRoutes.length ? `backendRoutes: [${runtimeBackendRoutes.join(", ")}],` : ""}
2837
- ${runtimeApis.length ? `apis: [${runtimeApis.join(", ")}],` : ""}
2838
- ${translations.length ? `translations: { ${translations.join(", ")} },` : ""}
2839
- ${subscribers.length ? `subscribers: [${subscribers.join(", ")}],` : ""}
2840
- ${workers.length ? `workers: [${workers.join(", ")}],` : ""}
2841
- ${extensionsImportName ? `entityExtensions: ((${extensionsImportName}.default ?? ${extensionsImportName}.extensions) as import('@open-mercato/shared/modules/entities').EntityExtension[]) || [],` : ""}
2842
- customFieldSets: ${customFieldSetsExpr},
2843
- ${featuresImportName ? `features: ((${featuresImportName}.default ?? ${featuresImportName}.features) as any) || [],` : ""}
2844
- ${customEntitiesImportName ? `customEntities: ((${customEntitiesImportName}.default ?? ${customEntitiesImportName}.entities) as any) || [],` : ""}
2845
- ${setupImportName ? `setup: (${setupImportName}.default ?? ${setupImportName}.setup) || undefined,` : ""}
2846
- ${encryptionImportName ? `defaultEncryptionMaps: ((${encryptionImportName}.default ?? ${encryptionImportName}.defaultEncryptionMaps) as import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]) || [],` : ""}
2847
- ${integrationExports ? `integrations: ((${integrationExports}.integrations ?? (${integrationExports}.integration ? [${integrationExports}.integration] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]),` : ""}
2848
- ${integrationExports ? `bundles: ((${integrationExports}.bundles ?? (${integrationExports}.bundle ? [${integrationExports}.bundle] : [])) as import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]),` : ""}
2849
- }`);
2964
+ const identityEntries = [{ name: "id", value: modId }];
2965
+ if (infoImportName) {
2966
+ identityEntries.push({ name: "info", value: propertyAccess(identifier(infoImportName), "metadata") });
2967
+ }
2968
+ const sharedTailEntries = [];
2969
+ if (translations.length > 0) {
2970
+ sharedTailEntries.push({ name: "translations", value: objectLiteral(translations) });
2971
+ }
2972
+ if (subscribers.length > 0) {
2973
+ sharedTailEntries.push({ name: "subscribers", value: arrayLiteral(subscribers, writeValue) });
2974
+ }
2975
+ if (workers.length > 0) {
2976
+ sharedTailEntries.push({ name: "workers", value: arrayLiteral(workers, writeValue) });
2977
+ }
2978
+ if (extensionsImportName) {
2979
+ sharedTailEntries.push({
2980
+ name: "entityExtensions",
2981
+ value: buildLegacyModuleListValue({
2982
+ importName: extensionsImportName,
2983
+ members: ["default", "extensions"],
2984
+ castType: "import('@open-mercato/shared/modules/entities').EntityExtension[]"
2985
+ })
2986
+ });
2987
+ }
2988
+ sharedTailEntries.push({ name: "customFieldSets", value: identifier(customFieldSetsExpr) });
2989
+ if (featuresImportName) {
2990
+ sharedTailEntries.push({
2991
+ name: "features",
2992
+ value: buildLegacyModuleListValue({
2993
+ importName: featuresImportName,
2994
+ members: ["default", "features"],
2995
+ castType: "any"
2996
+ })
2997
+ });
2998
+ }
2999
+ if (customEntitiesImportName) {
3000
+ sharedTailEntries.push({
3001
+ name: "customEntities",
3002
+ value: buildLegacyModuleListValue({
3003
+ importName: customEntitiesImportName,
3004
+ members: ["default", "entities"],
3005
+ castType: "any"
3006
+ })
3007
+ });
3008
+ }
3009
+ const setupAndBeyondEntries = [];
3010
+ if (setupImportName) {
3011
+ setupAndBeyondEntries.push({ name: "setup", value: buildLegacyModuleSetupValue(setupImportName) });
3012
+ }
3013
+ if (encryptionImportName) {
3014
+ setupAndBeyondEntries.push({
3015
+ name: "defaultEncryptionMaps",
3016
+ value: buildLegacyModuleListValue({
3017
+ importName: encryptionImportName,
3018
+ members: ["default", "defaultEncryptionMaps"],
3019
+ castType: "import('@open-mercato/shared/modules/encryption').ModuleEncryptionMap[]"
3020
+ })
3021
+ });
3022
+ }
3023
+ if (integrationExports) {
3024
+ setupAndBeyondEntries.push({
3025
+ name: "integrations",
3026
+ value: buildLegacyIntegrationListValue(
3027
+ integrationExports,
3028
+ "integrations",
3029
+ "integration",
3030
+ "import('@open-mercato/shared/modules/integrations/types').IntegrationDefinition[]"
3031
+ )
3032
+ });
3033
+ setupAndBeyondEntries.push({
3034
+ name: "bundles",
3035
+ value: buildLegacyIntegrationListValue(
3036
+ integrationExports,
3037
+ "bundles",
3038
+ "bundle",
3039
+ "import('@open-mercato/shared/modules/integrations/types').IntegrationBundle[]"
3040
+ )
3041
+ });
3042
+ }
3043
+ const eagerEntries = [...identityEntries];
3044
+ if (frontendRoutes.length > 0) {
3045
+ eagerEntries.push({ name: "frontendRoutes", value: preRenderedExpressionList(frontendRoutes) });
3046
+ }
3047
+ if (backendRoutes.length > 0) {
3048
+ eagerEntries.push({ name: "backendRoutes", value: preRenderedExpressionList(backendRoutes) });
3049
+ }
3050
+ if (apis.length > 0) {
3051
+ eagerEntries.push({ name: "apis", value: preRenderedExpressionList(apis) });
3052
+ }
3053
+ if (cliImportName) {
3054
+ eagerEntries.push({ name: "cli", value: identifier(cliImportName) });
3055
+ }
3056
+ eagerEntries.push(...sharedTailEntries);
3057
+ if (dashboardWidgets.length > 0) {
3058
+ eagerEntries.push({ name: "dashboardWidgets", value: preRenderedExpressionList(dashboardWidgets) });
3059
+ }
3060
+ eagerEntries.push(...setupAndBeyondEntries);
3061
+ moduleDecls.push(objectLiteral(eagerEntries));
3062
+ const runtimeEntries = [...identityEntries];
3063
+ if (runtimeFrontendRoutes.length > 0) {
3064
+ runtimeEntries.push({ name: "frontendRoutes", value: preRenderedExpressionList(runtimeFrontendRoutes) });
3065
+ }
3066
+ if (runtimeBackendRoutes.length > 0) {
3067
+ runtimeEntries.push({ name: "backendRoutes", value: preRenderedExpressionList(runtimeBackendRoutes) });
3068
+ }
3069
+ if (runtimeApis.length > 0) {
3070
+ runtimeEntries.push({ name: "apis", value: preRenderedExpressionList(runtimeApis) });
3071
+ }
3072
+ runtimeEntries.push(...sharedTailEntries, ...setupAndBeyondEntries);
3073
+ runtimeModuleDecls.push(objectLiteral(runtimeEntries));
2850
3074
  }
2851
3075
  {
2852
3076
  const { detectConflicts } = await import("@open-mercato/shared/lib/umes/conflict-detection");
@@ -2939,33 +3163,37 @@ async function generateModuleRegistryFromDiscovery(options) {
2939
3163
  );
2940
3164
  }
2941
3165
  }
2942
- const output = renderAstLegacyModuleRegistryOutput({
3166
+ const output = renderAstModuleRegistryFile({
2943
3167
  fileName: "modules.generated.ts",
3168
+ generator: "registry",
2944
3169
  imports,
2945
3170
  moduleEntries: moduleDecls,
2946
- includeCreateElementImport: hasRouteComponents
3171
+ includeCreateElementImport: hasRouteComponents,
3172
+ includeAbandonHookImport: hasAbandonHookWorkers
2947
3173
  });
2948
- const runtimeOutput = renderAstLegacyModuleRegistryOutput({
3174
+ const runtimeOutput = renderAstModuleRegistryFile({
2949
3175
  fileName: "modules.runtime.generated.ts",
3176
+ generator: "registry",
2950
3177
  imports: runtimeImports,
2951
3178
  moduleEntries: runtimeModuleDecls,
2952
- includeCreateElementImport: true
3179
+ includeCreateElementImport: true,
3180
+ includeAbandonHookImport: hasAbandonHookWorkers
2953
3181
  });
2954
- const frontendRoutesOutput = renderAstLegacyManifestOutput({
3182
+ const frontendRoutesOutput = renderAstManifestFile({
2955
3183
  fileName: "frontend-routes.generated.ts",
2956
3184
  typeName: "FrontendRouteManifestEntry",
2957
3185
  exportName: "frontendRoutes",
2958
3186
  imports: frontendRouteManifestImports,
2959
3187
  entries: frontendRouteManifestDecls
2960
3188
  });
2961
- const backendRoutesOutput = renderAstLegacyManifestOutput({
3189
+ const backendRoutesOutput = renderAstManifestFile({
2962
3190
  fileName: "backend-routes.generated.ts",
2963
3191
  typeName: "BackendRouteManifestEntry",
2964
3192
  exportName: "backendRoutes",
2965
3193
  imports: backendRouteManifestImports,
2966
3194
  entries: backendRouteManifestDecls
2967
3195
  });
2968
- const apiRoutesOutput = renderAstLegacyManifestOutput({
3196
+ const apiRoutesOutput = renderAstManifestFile({
2969
3197
  fileName: "api-routes.generated.ts",
2970
3198
  typeName: "ApiRouteManifestEntry",
2971
3199
  exportName: "apiRoutes",
@@ -3092,16 +3320,11 @@ async function generateModuleRegistryFromDiscovery(options) {
3092
3320
  allRegImports.push(...reg.registrationImports);
3093
3321
  allCalls.push(reg.buildCall(reg.entriesExportName));
3094
3322
  }
3095
- const uniqueImports = [.../* @__PURE__ */ new Set([...allEntryImports, ...allRegImports])];
3096
- const importSection = uniqueImports.join("\n");
3097
- const body = ` ${allCalls.join("\n ")}`;
3098
- const bootstrapRegsOutput = `// AUTO-GENERATED by mercato generate registry
3099
- ${importSection ? `${importSection}
3100
- ` : ""}
3101
- export function runBootstrapRegistrations(): void {
3102
- ${body}
3103
- }
3104
- `;
3323
+ const bootstrapRegsOutput = renderBootstrapRegistrationsFile({
3324
+ entryImports: allEntryImports,
3325
+ registrationImports: allRegImports,
3326
+ calls: allCalls
3327
+ });
3105
3328
  writeGeneratedFile({ outFile: bootstrapRegsOutFile, checksumFile: bootstrapRegsChecksumFile, content: bootstrapRegsOutput, structureChecksum, result, quiet });
3106
3329
  }
3107
3330
  return result;
@@ -3137,6 +3360,7 @@ async function generateModuleRegistryAppFromDiscovery(options) {
3137
3360
  const requiresByModule = /* @__PURE__ */ new Map();
3138
3361
  const commandLoaderEntries = [];
3139
3362
  let hasRouteComponents = false;
3363
+ let hasAbandonHookWorkers = false;
3140
3364
  const seenBackendRoutePatterns = /* @__PURE__ */ new Map();
3141
3365
  for (const discovered of discovery.modules) {
3142
3366
  const { modId, roots, imps, appImportBase } = discovered;
@@ -3282,7 +3506,9 @@ async function generateModuleRegistryAppFromDiscovery(options) {
3282
3506
  i18nLocaleShards.set(locale, shard);
3283
3507
  }
3284
3508
  subscribers.push(...processSubscribersAst(await discovered.getSubscribers()));
3285
- workers.push(...processWorkersAst(await discovered.getWorkers()));
3509
+ const discoveredWorkersForModule = await discovered.getWorkers();
3510
+ if (workersDeclareAbandonHook(discoveredWorkersForModule)) hasAbandonHookWorkers = true;
3511
+ workers.push(...processWorkersAst(discoveredWorkersForModule));
3286
3512
  {
3287
3513
  dashboardWidgetsValue = buildModuleDashboardWidgetsValue(discovered.dashboardWidgetEntries);
3288
3514
  }
@@ -3399,19 +3625,22 @@ async function generateModuleRegistryAppFromDiscovery(options) {
3399
3625
  generator: "registry (app version)",
3400
3626
  imports,
3401
3627
  moduleEntries: moduleDecls,
3402
- includeCreateElementImport: hasRouteComponents
3628
+ includeCreateElementImport: hasRouteComponents,
3629
+ includeAbandonHookImport: hasAbandonHookWorkers
3403
3630
  });
3404
3631
  const bootstrapOutput = renderAstModuleRegistryFile({
3405
3632
  fileName: "modules.bootstrap.generated.ts",
3406
3633
  generator: "registry (bootstrap version)",
3407
3634
  imports: bootstrapImports,
3408
- moduleEntries: bootstrapModuleDecls
3635
+ moduleEntries: bootstrapModuleDecls,
3636
+ includeAbandonHookImport: hasAbandonHookWorkers
3409
3637
  });
3410
3638
  const i18nOutput = renderAstModuleRegistryFile({
3411
3639
  fileName: "modules.i18n.generated.ts",
3412
3640
  generator: "registry (i18n version)",
3413
3641
  imports: i18nImports,
3414
- moduleEntries: i18nModuleDecls
3642
+ moduleEntries: i18nModuleDecls,
3643
+ includeAbandonHookImport: hasAbandonHookWorkers
3415
3644
  });
3416
3645
  const legacyOutput = renderAstLegacyAliasFile({
3417
3646
  fileName: "bootstrap-modules.generated.ts",
@@ -3486,6 +3715,7 @@ async function generateModuleRegistryAppFromDiscovery(options) {
3486
3715
  return result;
3487
3716
  }
3488
3717
  async function generateModuleRegistryCliFromDiscovery(options) {
3718
+ let hasAbandonHookWorkers = false;
3489
3719
  const { resolver, quiet = false } = options;
3490
3720
  const { discovery } = options;
3491
3721
  const result = createGeneratorResult();
@@ -3604,6 +3834,7 @@ async function generateModuleRegistryCliFromDiscovery(options) {
3604
3834
  subscribers.push(...processSubscribersAst(await discovered.getSubscribers()));
3605
3835
  const discoveredWorkers = await discovered.getWorkers();
3606
3836
  const workerGenerationEntries = collectWorkerGenerationEntries(discoveredWorkers, modId);
3837
+ if (workersDeclareAbandonHook(discoveredWorkers)) hasAbandonHookWorkers = true;
3607
3838
  workers.push(...processWorkersAst(discoveredWorkers));
3608
3839
  devSupervisorWorkers.push(...workerGenerationEntries.map(({ importPath: _importPath, ...worker }) => worker));
3609
3840
  {
@@ -3720,7 +3951,8 @@ async function generateModuleRegistryCliFromDiscovery(options) {
3720
3951
  fileName: "modules.cli.generated.ts",
3721
3952
  generator: "registry (CLI version)",
3722
3953
  imports,
3723
- moduleEntries: moduleDecls
3954
+ moduleEntries: moduleDecls,
3955
+ includeAbandonHookImport: hasAbandonHookWorkers
3724
3956
  });
3725
3957
  const legacyOutput = renderAstLegacyAliasFile({
3726
3958
  fileName: "cli-modules.generated.ts",
@@ -3806,6 +4038,9 @@ export {
3806
4038
  generateModuleRegistryApp,
3807
4039
  generateModuleRegistryCli,
3808
4040
  hasNamedExport,
4041
+ namedObjectLiteralDeclaresProperty,
4042
+ renderBootstrapRegistrationsFile,
4043
+ renderCommandLoadersFile,
3809
4044
  resetConventionWarnings,
3810
4045
  resolveNamedObjectExport,
3811
4046
  warnIfPageMetaMissingMetadataExport,