@open-mercato/core 0.6.5-develop.4498.1.55dc06a57c → 0.6.5-develop.4534.1.b459babe6d

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 (105) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/step_instance/index.js +2 -0
  3. package/dist/generated/entities/step_instance/index.js.map +2 -2
  4. package/dist/generated/entities/user_task/index.js +2 -0
  5. package/dist/generated/entities/user_task/index.js.map +2 -2
  6. package/dist/generated/entities/workflow_branch_instance/index.js +39 -0
  7. package/dist/generated/entities/workflow_branch_instance/index.js.map +7 -0
  8. package/dist/generated/entities/workflow_event/index.js +2 -0
  9. package/dist/generated/entities/workflow_event/index.js.map +2 -2
  10. package/dist/generated/entities/workflow_instance/index.js +2 -0
  11. package/dist/generated/entities/workflow_instance/index.js.map +2 -2
  12. package/dist/generated/entities.ids.generated.js +1 -0
  13. package/dist/generated/entities.ids.generated.js.map +2 -2
  14. package/dist/generated/entity-fields-registry.js +24 -0
  15. package/dist/generated/entity-fields-registry.js.map +2 -2
  16. package/dist/helpers/integration/currenciesFixtures.js +51 -1
  17. package/dist/helpers/integration/currenciesFixtures.js.map +2 -2
  18. package/dist/modules/progress/api/jobs/[id]/route.js +7 -1
  19. package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
  20. package/dist/modules/shipping_carriers/api/cancel/route.js +2 -2
  21. package/dist/modules/shipping_carriers/api/cancel/route.js.map +2 -2
  22. package/dist/modules/shipping_carriers/lib/status-sync.js +8 -1
  23. package/dist/modules/shipping_carriers/lib/status-sync.js.map +2 -2
  24. package/dist/modules/workflows/components/NodeEditDialog.js +3 -1
  25. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  26. package/dist/modules/workflows/components/WorkflowGraphImpl.js +4 -2
  27. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  28. package/dist/modules/workflows/components/nodes/ParallelForkNode.js +49 -0
  29. package/dist/modules/workflows/components/nodes/ParallelForkNode.js.map +7 -0
  30. package/dist/modules/workflows/components/nodes/ParallelJoinNode.js +49 -0
  31. package/dist/modules/workflows/components/nodes/ParallelJoinNode.js.map +7 -0
  32. package/dist/modules/workflows/components/nodes/index.js +4 -0
  33. package/dist/modules/workflows/components/nodes/index.js.map +2 -2
  34. package/dist/modules/workflows/data/entities.js +81 -0
  35. package/dist/modules/workflows/data/entities.js.map +2 -2
  36. package/dist/modules/workflows/data/validators.js +146 -1
  37. package/dist/modules/workflows/data/validators.js.map +2 -2
  38. package/dist/modules/workflows/events.js +7 -1
  39. package/dist/modules/workflows/events.js.map +2 -2
  40. package/dist/modules/workflows/lib/activity-executor.js +4 -2
  41. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  42. package/dist/modules/workflows/lib/activity-queue-types.js.map +2 -2
  43. package/dist/modules/workflows/lib/event-logger.js +2 -0
  44. package/dist/modules/workflows/lib/event-logger.js.map +2 -2
  45. package/dist/modules/workflows/lib/execution-token.js +98 -0
  46. package/dist/modules/workflows/lib/execution-token.js.map +7 -0
  47. package/dist/modules/workflows/lib/node-type-icons.js +14 -5
  48. package/dist/modules/workflows/lib/node-type-icons.js.map +2 -2
  49. package/dist/modules/workflows/lib/parallel-handler.js +364 -0
  50. package/dist/modules/workflows/lib/parallel-handler.js.map +7 -0
  51. package/dist/modules/workflows/lib/signal-handler.js +63 -1
  52. package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
  53. package/dist/modules/workflows/lib/step-handler.js +74 -30
  54. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  55. package/dist/modules/workflows/lib/task-handler.js +26 -0
  56. package/dist/modules/workflows/lib/task-handler.js.map +2 -2
  57. package/dist/modules/workflows/lib/timer-handler.js +26 -1
  58. package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
  59. package/dist/modules/workflows/lib/transition-handler.js +33 -21
  60. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  61. package/dist/modules/workflows/lib/workflow-executor.js +39 -1
  62. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  63. package/dist/modules/workflows/migrations/Migration20260602120000.js +24 -0
  64. package/dist/modules/workflows/migrations/Migration20260602120000.js.map +7 -0
  65. package/dist/modules/workflows/workers/workflow-activities.worker.js +8 -4
  66. package/dist/modules/workflows/workers/workflow-activities.worker.js.map +2 -2
  67. package/generated/entities/step_instance/index.ts +1 -0
  68. package/generated/entities/user_task/index.ts +1 -0
  69. package/generated/entities/workflow_branch_instance/index.ts +18 -0
  70. package/generated/entities/workflow_event/index.ts +1 -0
  71. package/generated/entities/workflow_instance/index.ts +1 -0
  72. package/generated/entities.ids.generated.ts +1 -0
  73. package/generated/entity-fields-registry.ts +24 -0
  74. package/package.json +7 -7
  75. package/src/helpers/integration/currenciesFixtures.ts +59 -0
  76. package/src/modules/progress/api/jobs/[id]/route.ts +7 -0
  77. package/src/modules/shipping_carriers/api/cancel/route.ts +2 -2
  78. package/src/modules/shipping_carriers/lib/status-sync.ts +19 -0
  79. package/src/modules/workflows/components/NodeEditDialog.tsx +2 -0
  80. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +3 -1
  81. package/src/modules/workflows/components/nodes/ParallelForkNode.tsx +66 -0
  82. package/src/modules/workflows/components/nodes/ParallelJoinNode.tsx +66 -0
  83. package/src/modules/workflows/components/nodes/index.ts +6 -0
  84. package/src/modules/workflows/data/entities.ts +109 -0
  85. package/src/modules/workflows/data/validators.ts +223 -0
  86. package/src/modules/workflows/events.ts +7 -0
  87. package/src/modules/workflows/i18n/de.json +12 -0
  88. package/src/modules/workflows/i18n/en.json +12 -0
  89. package/src/modules/workflows/i18n/es.json +12 -0
  90. package/src/modules/workflows/i18n/pl.json +12 -0
  91. package/src/modules/workflows/lib/activity-executor.ts +8 -2
  92. package/src/modules/workflows/lib/activity-queue-types.ts +3 -0
  93. package/src/modules/workflows/lib/event-logger.ts +3 -0
  94. package/src/modules/workflows/lib/execution-token.ts +166 -0
  95. package/src/modules/workflows/lib/node-type-icons.ts +11 -2
  96. package/src/modules/workflows/lib/parallel-handler.ts +575 -0
  97. package/src/modules/workflows/lib/signal-handler.ts +72 -1
  98. package/src/modules/workflows/lib/step-handler.ts +94 -34
  99. package/src/modules/workflows/lib/task-handler.ts +32 -0
  100. package/src/modules/workflows/lib/timer-handler.ts +30 -1
  101. package/src/modules/workflows/lib/transition-handler.ts +56 -24
  102. package/src/modules/workflows/lib/workflow-executor.ts +53 -1
  103. package/src/modules/workflows/migrations/.snapshot-open-mercato.json +263 -0
  104. package/src/modules/workflows/migrations/Migration20260602120000.ts +25 -0
  105. package/src/modules/workflows/workers/workflow-activities.worker.ts +9 -4
@@ -12,6 +12,54 @@ async function createCurrencyFixture(request, token, input) {
12
12
  expect(typeof body.id === "string" && body.id.length > 0).toBeTruthy();
13
13
  return body.id;
14
14
  }
15
+ const SEEDED_CURRENCY_CODES = /* @__PURE__ */ new Set([
16
+ "USD",
17
+ "EUR",
18
+ "JPY",
19
+ "GBP",
20
+ "CHF",
21
+ "CAD",
22
+ "AUD",
23
+ "CNY",
24
+ "CNH",
25
+ "PLN"
26
+ ]);
27
+ const reservedCurrencyCodes = /* @__PURE__ */ new Set();
28
+ function generateUniqueCurrencyCode() {
29
+ const letter = () => String.fromCharCode(65 + Math.floor(Math.random() * 26));
30
+ for (let attempt = 0; attempt < 200; attempt += 1) {
31
+ const code = `${letter()}${letter()}${letter()}`;
32
+ if (!SEEDED_CURRENCY_CODES.has(code) && !reservedCurrencyCodes.has(code)) {
33
+ reservedCurrencyCodes.add(code);
34
+ return code;
35
+ }
36
+ }
37
+ throw new Error("[internal] exhausted unique currency code space");
38
+ }
39
+ async function createRandomCurrencyFixture(request, token, input) {
40
+ const { organizationId, tenantId } = getTokenContext(token);
41
+ let lastStatus = 0;
42
+ for (let attempt = 0; attempt < 8; attempt += 1) {
43
+ const code = generateUniqueCurrencyCode();
44
+ const data = {
45
+ organizationId,
46
+ tenantId,
47
+ code,
48
+ name: input.name,
49
+ symbol: input.symbol ?? null
50
+ };
51
+ if (typeof input.isActive === "boolean") data.isActive = input.isActive;
52
+ const response = await apiRequest(request, "POST", "/api/currencies/currencies", { token, data });
53
+ if (response.status() === 201) {
54
+ const body = await response.json();
55
+ if (typeof body.id === "string" && body.id.length > 0) {
56
+ return { id: body.id, code };
57
+ }
58
+ }
59
+ lastStatus = response.status();
60
+ }
61
+ throw new Error(`[internal] failed to create currency fixture after retries (last status ${lastStatus})`);
62
+ }
15
63
  async function createFetchConfigFixture(request, token, input) {
16
64
  const response = await apiRequest(request, "POST", "/api/currencies/fetch-configs", {
17
65
  token,
@@ -34,6 +82,8 @@ async function deleteCurrenciesEntityIfExists(request, token, path, id) {
34
82
  export {
35
83
  createCurrencyFixture,
36
84
  createFetchConfigFixture,
37
- deleteCurrenciesEntityIfExists
85
+ createRandomCurrencyFixture,
86
+ deleteCurrenciesEntityIfExists,
87
+ generateUniqueCurrencyCode
38
88
  };
39
89
  //# sourceMappingURL=currenciesFixtures.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/helpers/integration/currenciesFixtures.ts"],
4
- "sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\nimport { getTokenContext } from './generalFixtures';\n\nexport async function createCurrencyFixture(\n request: APIRequestContext,\n token: string,\n input: { code: string; name: string; symbol?: string },\n): Promise<string> {\n const { organizationId, tenantId } = getTokenContext(token);\n const response = await apiRequest(request, 'POST', '/api/currencies/currencies', {\n token,\n data: { organizationId, tenantId, code: input.code, name: input.name, symbol: input.symbol ?? null },\n });\n expect(response.ok(), `Failed to create currency fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { id?: string };\n expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy();\n return body.id as string;\n}\n\nexport async function createFetchConfigFixture(\n request: APIRequestContext,\n token: string,\n input: { provider: string; isEnabled: boolean },\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/currencies/fetch-configs', {\n token,\n data: input,\n });\n expect(response.ok(), `Failed to create fetch config fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { config?: { id?: string } };\n const id = body.config?.id;\n expect(typeof id === 'string' && id.length > 0).toBeTruthy();\n return id as string;\n}\n\nexport async function deleteCurrenciesEntityIfExists(\n request: APIRequestContext,\n token: string | null,\n path: string,\n id: string | null,\n): Promise<void> {\n if (!token || !id) return;\n try {\n await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });\n } catch {\n return;\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,uBAAuB;AAEhC,eAAsB,sBACpB,SACA,OACA,OACiB;AACjB,QAAM,EAAE,gBAAgB,SAAS,IAAI,gBAAgB,KAAK;AAC1D,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,8BAA8B;AAAA,IAC/E;AAAA,IACA,MAAM,EAAE,gBAAgB,UAAU,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,MAAM,UAAU,KAAK;AAAA,EACrG,CAAC;AACD,SAAO,SAAS,GAAG,GAAG,sCAAsC,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC5F,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,SAAS,CAAC,EAAE,WAAW;AACrE,SAAO,KAAK;AACd;AAEA,eAAsB,yBACpB,SACA,OACA,OACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,iCAAiC;AAAA,IAClF;AAAA,IACA,MAAM;AAAA,EACR,CAAC;AACD,SAAO,SAAS,GAAG,GAAG,0CAA0C,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAChG,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,QAAM,KAAK,KAAK,QAAQ;AACxB,SAAO,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC,EAAE,WAAW;AAC3D,SAAO;AACT;AAEA,eAAsB,+BACpB,SACA,OACA,MACA,IACe;AACf,MAAI,CAAC,SAAS,CAAC,GAAI;AACnB,MAAI;AACF,UAAM,WAAW,SAAS,UAAU,GAAG,IAAI,OAAO,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EACvF,QAAQ;AACN;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\nimport { getTokenContext } from './generalFixtures';\n\nexport async function createCurrencyFixture(\n request: APIRequestContext,\n token: string,\n input: { code: string; name: string; symbol?: string },\n): Promise<string> {\n const { organizationId, tenantId } = getTokenContext(token);\n const response = await apiRequest(request, 'POST', '/api/currencies/currencies', {\n token,\n data: { organizationId, tenantId, code: input.code, name: input.name, symbol: input.symbol ?? null },\n });\n expect(response.ok(), `Failed to create currency fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { id?: string };\n expect(typeof body.id === 'string' && body.id.length > 0).toBeTruthy();\n return body.id as string;\n}\n\n// Codes seeded by the currencies module (seedExampleCurrencies). Generated test\n// codes avoid these so fixtures never collide with seeded rows.\nconst SEEDED_CURRENCY_CODES = new Set([\n 'USD', 'EUR', 'JPY', 'GBP', 'CHF', 'CAD', 'AUD', 'CNY', 'CNH', 'PLN',\n]);\n// Reserved across the worker so two fixtures never draw the same code in one run.\nconst reservedCurrencyCodes = new Set<string>();\n\n/** Draws an ISO-style three-letter code unused by seeds or earlier fixtures. */\nexport function generateUniqueCurrencyCode(): string {\n const letter = () => String.fromCharCode(65 + Math.floor(Math.random() * 26));\n for (let attempt = 0; attempt < 200; attempt += 1) {\n const code = `${letter()}${letter()}${letter()}`;\n if (!SEEDED_CURRENCY_CODES.has(code) && !reservedCurrencyCodes.has(code)) {\n reservedCurrencyCodes.add(code);\n return code;\n }\n }\n throw new Error('[internal] exhausted unique currency code space');\n}\n\n/**\n * Creates a currency with a generated unique code and returns its id and code.\n *\n * Currency DELETE is a soft delete, but the (organization, tenant, code) unique\n * constraint still counts soft-deleted rows \u2014 re-using a code an earlier test\n * soft-deleted makes the create fail. Drawing from the full three-letter space\n * (minus seeds) and retrying with a fresh code on an accidental collision keeps\n * fixture setup deterministic across runs that share a database.\n */\nexport async function createRandomCurrencyFixture(\n request: APIRequestContext,\n token: string,\n input: { name: string; symbol?: string; isActive?: boolean },\n): Promise<{ id: string; code: string }> {\n const { organizationId, tenantId } = getTokenContext(token);\n let lastStatus = 0;\n for (let attempt = 0; attempt < 8; attempt += 1) {\n const code = generateUniqueCurrencyCode();\n const data: Record<string, unknown> = {\n organizationId,\n tenantId,\n code,\n name: input.name,\n symbol: input.symbol ?? null,\n };\n if (typeof input.isActive === 'boolean') data.isActive = input.isActive;\n const response = await apiRequest(request, 'POST', '/api/currencies/currencies', { token, data });\n if (response.status() === 201) {\n const body = (await response.json()) as { id?: string };\n if (typeof body.id === 'string' && body.id.length > 0) {\n return { id: body.id, code };\n }\n }\n lastStatus = response.status();\n }\n throw new Error(`[internal] failed to create currency fixture after retries (last status ${lastStatus})`);\n}\n\nexport async function createFetchConfigFixture(\n request: APIRequestContext,\n token: string,\n input: { provider: string; isEnabled: boolean },\n): Promise<string> {\n const response = await apiRequest(request, 'POST', '/api/currencies/fetch-configs', {\n token,\n data: input,\n });\n expect(response.ok(), `Failed to create fetch config fixture: ${response.status()}`).toBeTruthy();\n const body = (await response.json()) as { config?: { id?: string } };\n const id = body.config?.id;\n expect(typeof id === 'string' && id.length > 0).toBeTruthy();\n return id as string;\n}\n\nexport async function deleteCurrenciesEntityIfExists(\n request: APIRequestContext,\n token: string | null,\n path: string,\n id: string | null,\n): Promise<void> {\n if (!token || !id) return;\n try {\n await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });\n } catch {\n return;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -61,6 +61,11 @@ async function PUT(req, { params }) {
61
61
  return NextResponse.json({ error: "Invalid input", details: parsed.error.flatten() }, { status: 400 });
62
62
  }
63
63
  const container = await createRequestContainer();
64
+ const em = container.resolve("em");
65
+ const existing = await em.findOne(ProgressJob, { id: params.id, tenantId: auth.tenantId });
66
+ if (!existing) {
67
+ return NextResponse.json({ error: "Not found" }, { status: 404 });
68
+ }
64
69
  const progressService = container.resolve("progressService");
65
70
  const job = await progressService.updateProgress(params.id, parsed.data, {
66
71
  tenantId: auth.tenantId,
@@ -103,7 +108,8 @@ const openApi = {
103
108
  tags: ["Progress"],
104
109
  responses: {
105
110
  200: { description: "Progress updated" },
106
- 400: { description: "Invalid input" }
111
+ 400: { description: "Invalid input" },
112
+ 404: { description: "Job not found" }
107
113
  }
108
114
  },
109
115
  DELETE: {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/progress/api/jobs/%5Bid%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ProgressJob } from '../../../data/entities'\nimport { updateProgressSchema } from '../../../data/validators'\nimport type { ProgressService } from '../../../lib/progressService'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n PUT: { requireAuth: true, requireFeatures: ['progress.update'] },\n DELETE: { requireAuth: true, requireFeatures: ['progress.cancel'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const job = await em.findOne(ProgressJob, {\n id: params.id,\n tenantId: auth.tenantId,\n })\n\n if (!job) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta,\n resultSummary: job.resultSummary,\n errorMessage: job.errorMessage,\n startedByUserId: job.startedByUserId,\n startedAt: job.startedAt?.toISOString() ?? null,\n heartbeatAt: job.heartbeatAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n parentJobId: job.parentJobId,\n partitionIndex: job.partitionIndex,\n partitionCount: job.partitionCount,\n createdAt: job.createdAt.toISOString(),\n updatedAt: job.updatedAt.toISOString(),\n tenantId: job.tenantId,\n organizationId: job.organizationId,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = updateProgressSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as ProgressService\n\n const job = await progressService.updateProgress(params.id, parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ ok: true, progressPercent: job.progressPercent })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as ProgressService\n\n try {\n await progressService.cancelJob(params.id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n return NextResponse.json({ ok: true })\n } catch {\n return NextResponse.json({ error: 'Cannot cancel this job' }, { status: 400 })\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get progress job details',\n description: 'Returns full details of a specific progress job by ID.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress job details' },\n 404: { description: 'Job not found' },\n },\n },\n PUT: {\n summary: 'Update progress job',\n description: 'Updates progress metrics and heartbeat for a running job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress updated' },\n 400: { description: 'Invalid input' },\n },\n },\n DELETE: {\n summary: 'Cancel progress job',\n description: 'Requests cancellation of a running or pending job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Cancellation requested' },\n 400: { description: 'Cannot cancel this job' },\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ProgressJob } from '../../../data/entities'\nimport { updateProgressSchema } from '../../../data/validators'\nimport type { ProgressService } from '../../../lib/progressService'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n PUT: { requireAuth: true, requireFeatures: ['progress.update'] },\n DELETE: { requireAuth: true, requireFeatures: ['progress.cancel'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const job = await em.findOne(ProgressJob, {\n id: params.id,\n tenantId: auth.tenantId,\n })\n\n if (!job) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta,\n resultSummary: job.resultSummary,\n errorMessage: job.errorMessage,\n startedByUserId: job.startedByUserId,\n startedAt: job.startedAt?.toISOString() ?? null,\n heartbeatAt: job.heartbeatAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n parentJobId: job.parentJobId,\n partitionIndex: job.partitionIndex,\n partitionCount: job.partitionCount,\n createdAt: job.createdAt.toISOString(),\n updatedAt: job.updatedAt.toISOString(),\n tenantId: job.tenantId,\n organizationId: job.organizationId,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = updateProgressSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const existing = await em.findOne(ProgressJob, { id: params.id, tenantId: auth.tenantId })\n if (!existing) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n const progressService = container.resolve('progressService') as ProgressService\n\n const job = await progressService.updateProgress(params.id, parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ ok: true, progressPercent: job.progressPercent })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as ProgressService\n\n try {\n await progressService.cancelJob(params.id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n return NextResponse.json({ ok: true })\n } catch {\n return NextResponse.json({ error: 'Cannot cancel this job' }, { status: 400 })\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get progress job details',\n description: 'Returns full details of a specific progress job by ID.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress job details' },\n 404: { description: 'Job not found' },\n },\n },\n PUT: {\n summary: 'Update progress job',\n description: 'Updates progress metrics and heartbeat for a running job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress updated' },\n 400: { description: 'Invalid input' },\n 404: { description: 'Job not found' },\n },\n },\n DELETE: {\n summary: 'Cancel progress job',\n description: 'Requests cancellation of a running or pending job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Cancellation requested' },\n 400: { description: 'Cannot cancel this job' },\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
2
2
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
3
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
4
  import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
5
- import { ShipmentCancelNotAllowedError } from "../../lib/status-sync.js";
5
+ import { isShipmentCancelNotAllowedError } from "../../lib/status-sync.js";
6
6
  import { cancelShipmentSchema } from "../../data/validators.js";
7
7
  import { shippingCarriersTag } from "../openapi.js";
8
8
  const metadata = {
@@ -29,7 +29,7 @@ async function POST(req) {
29
29
  });
30
30
  return NextResponse.json(result);
31
31
  } catch (error) {
32
- if (error instanceof ShipmentCancelNotAllowedError) {
32
+ if (isShipmentCancelNotAllowedError(error)) {
33
33
  return NextResponse.json({ error: error.message }, { status: 422 });
34
34
  }
35
35
  const message = error instanceof Error ? error.message : "Failed to cancel shipment";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/shipping_carriers/api/cancel/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { ShippingCarrierService } from '../../lib/shipping-service'\nimport { ShipmentCancelNotAllowedError } from '../../lib/status-sync'\nimport { cancelShipmentSchema } from '../../data/validators'\nimport { shippingCarriersTag } from '../openapi'\n\nexport const metadata = {\n path: '/shipping-carriers/cancel',\n POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const payload = await readJsonSafe<unknown>(req)\n const parsed = cancelShipmentSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n const container = await createRequestContainer()\n const service = container.resolve('shippingCarrierService') as ShippingCarrierService\n try {\n const result = await service.cancelShipment({\n ...parsed.data,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n return NextResponse.json(result)\n } catch (error: unknown) {\n if (error instanceof ShipmentCancelNotAllowedError) {\n return NextResponse.json({ error: error.message }, { status: 422 })\n }\n const message = error instanceof Error ? error.message : 'Failed to cancel shipment'\n return NextResponse.json({ error: message }, { status: 502 })\n }\n}\n\nexport const openApi = {\n tags: [shippingCarriersTag],\n summary: 'Cancel shipment',\n methods: {\n POST: {\n summary: 'Cancel shipment',\n tags: [shippingCarriersTag],\n responses: [\n { status: 200, description: 'Shipment cancelled' },\n { status: 422, description: 'Validation failed or shipment cannot be cancelled in its current status' },\n { status: 502, description: 'Provider upstream error' },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,SAAS,qCAAqC;AAC9C,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,0BAA0B,EAAE;AAC3E;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AACA,QAAM,UAAU,MAAM,aAAsB,GAAG;AAC/C,QAAM,SAAS,qBAAqB,UAAU,OAAO;AACrD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzG;AACA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,UAAU,UAAU,QAAQ,wBAAwB;AAC1D,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,eAAe;AAAA,MAC1C,GAAG,OAAO;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,IACjB,CAAC;AACD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,OAAgB;AACvB,QAAI,iBAAiB,+BAA+B;AAClD,aAAO,aAAa,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AACA,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,mBAAmB;AAAA,EAC1B,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,MAAM,CAAC,mBAAmB;AAAA,MAC1B,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,qBAAqB;AAAA,QACjD,EAAE,QAAQ,KAAK,aAAa,0EAA0E;AAAA,QACtG,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { ShippingCarrierService } from '../../lib/shipping-service'\nimport { isShipmentCancelNotAllowedError } from '../../lib/status-sync'\nimport { cancelShipmentSchema } from '../../data/validators'\nimport { shippingCarriersTag } from '../openapi'\n\nexport const metadata = {\n path: '/shipping-carriers/cancel',\n POST: { requireAuth: true, requireFeatures: ['shipping_carriers.manage'] },\n}\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n const payload = await readJsonSafe<unknown>(req)\n const parsed = cancelShipmentSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })\n }\n const container = await createRequestContainer()\n const service = container.resolve('shippingCarrierService') as ShippingCarrierService\n try {\n const result = await service.cancelShipment({\n ...parsed.data,\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n return NextResponse.json(result)\n } catch (error: unknown) {\n if (isShipmentCancelNotAllowedError(error)) {\n return NextResponse.json({ error: error.message }, { status: 422 })\n }\n const message = error instanceof Error ? error.message : 'Failed to cancel shipment'\n return NextResponse.json({ error: message }, { status: 502 })\n }\n}\n\nexport const openApi = {\n tags: [shippingCarriersTag],\n summary: 'Cancel shipment',\n methods: {\n POST: {\n summary: 'Cancel shipment',\n tags: [shippingCarriersTag],\n responses: [\n { status: 200, description: 'Shipment cancelled' },\n { status: 422, description: 'Validation failed or shipment cannot be cancelled in its current status' },\n { status: 502, description: 'Provider upstream error' },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,SAAS,uCAAuC;AAChD,SAAS,4BAA4B;AACrC,SAAS,2BAA2B;AAE7B,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,0BAA0B,EAAE;AAC3E;AAEA,eAAsB,KAAK,KAAc;AACvC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AACA,QAAM,UAAU,MAAM,aAAsB,GAAG;AAC/C,QAAM,SAAS,qBAAqB,UAAU,OAAO;AACrD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzG;AACA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,UAAU,UAAU,QAAQ,wBAAwB;AAC1D,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ,eAAe;AAAA,MAC1C,GAAG,OAAO;AAAA,MACV,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,IACjB,CAAC;AACD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,OAAgB;AACvB,QAAI,gCAAgC,KAAK,GAAG;AAC1C,aAAO,aAAa,KAAK,EAAE,OAAO,MAAM,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpE;AACA,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,QAAQ,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9D;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,mBAAmB;AAAA,EAC1B,SAAS;AAAA,EACT,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,MAAM,CAAC,mBAAmB;AAAA,MAC1B,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,qBAAqB;AAAA,QACjD,EAAE,QAAQ,KAAK,aAAa,0EAA0E;AAAA,QACtG,EAAE,QAAQ,KAAK,aAAa,0BAA0B;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,15 @@
1
- class ShipmentCancelNotAllowedError extends Error {
1
+ var _a, _b;
2
+ const SHIPMENT_CANCEL_NOT_ALLOWED_MARKER = /* @__PURE__ */ Symbol.for("@open-mercato/shipping_carriers/ShipmentCancelNotAllowedError");
3
+ class ShipmentCancelNotAllowedError extends (_b = Error, _a = SHIPMENT_CANCEL_NOT_ALLOWED_MARKER, _b) {
2
4
  constructor(status) {
3
5
  super(`Shipment cannot be cancelled in its current status: ${status}`);
6
+ this[_a] = true;
4
7
  this.name = "ShipmentCancelNotAllowedError";
5
8
  }
6
9
  }
10
+ function isShipmentCancelNotAllowedError(error) {
11
+ return !!error && typeof error === "object" && error[SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] === true;
12
+ }
7
13
  const VALID_SHIPPING_TRANSITIONS = {
8
14
  label_created: ["picked_up", "in_transit", "cancelled"],
9
15
  picked_up: ["in_transit", "cancelled"],
@@ -38,6 +44,7 @@ export {
38
44
  ShipmentCancelNotAllowedError,
39
45
  TERMINAL_SHIPPING_STATUSES,
40
46
  getTerminalShippingEvent,
47
+ isShipmentCancelNotAllowedError,
41
48
  isValidShippingTransition,
42
49
  syncShipmentStatus
43
50
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/shipping_carriers/lib/status-sync.ts"],
4
- "sourcesContent": ["import type { UnifiedShipmentStatus } from './adapter'\nimport type { CarrierShipment } from '../data/entities'\nimport type { ShippingEventId } from '../events'\n\nexport class ShipmentCancelNotAllowedError extends Error {\n constructor(status: string) {\n super(`Shipment cannot be cancelled in its current status: ${status}`)\n this.name = 'ShipmentCancelNotAllowedError'\n }\n}\n\nconst VALID_SHIPPING_TRANSITIONS: Record<string, UnifiedShipmentStatus[]> = {\n label_created: ['picked_up', 'in_transit', 'cancelled'],\n picked_up: ['in_transit', 'cancelled'],\n in_transit: ['out_for_delivery', 'delivered', 'returned', 'failed_delivery'],\n out_for_delivery: ['delivered', 'returned', 'failed_delivery'],\n failed_delivery: ['in_transit', 'out_for_delivery', 'delivered', 'returned', 'cancelled'],\n}\n\nexport const TERMINAL_SHIPPING_STATUSES: Set<UnifiedShipmentStatus> = new Set([\n 'delivered',\n 'returned',\n 'cancelled',\n])\n\nexport function isValidShippingTransition(from: UnifiedShipmentStatus, to: UnifiedShipmentStatus): boolean {\n if (from === to) return false\n const allowed = VALID_SHIPPING_TRANSITIONS[from]\n if (!allowed) return false\n return allowed.includes(to)\n}\n\nexport function syncShipmentStatus(shipment: CarrierShipment, newStatus: UnifiedShipmentStatus): boolean {\n const currentStatus = shipment.unifiedStatus as UnifiedShipmentStatus\n if (!isValidShippingTransition(currentStatus, newStatus)) return false\n shipment.unifiedStatus = newStatus\n return true\n}\n\nexport function getTerminalShippingEvent(status: UnifiedShipmentStatus): ShippingEventId | null {\n if (status === 'delivered') return 'shipping_carriers.shipment.delivered'\n if (status === 'returned') return 'shipping_carriers.shipment.returned'\n if (status === 'cancelled') return 'shipping_carriers.shipment.cancelled'\n return null\n}\n"],
5
- "mappings": "AAIO,MAAM,sCAAsC,MAAM;AAAA,EACvD,YAAY,QAAgB;AAC1B,UAAM,uDAAuD,MAAM,EAAE;AACrE,SAAK,OAAO;AAAA,EACd;AACF;AAEA,MAAM,6BAAsE;AAAA,EAC1E,eAAe,CAAC,aAAa,cAAc,WAAW;AAAA,EACtD,WAAW,CAAC,cAAc,WAAW;AAAA,EACrC,YAAY,CAAC,oBAAoB,aAAa,YAAY,iBAAiB;AAAA,EAC3E,kBAAkB,CAAC,aAAa,YAAY,iBAAiB;AAAA,EAC7D,iBAAiB,CAAC,cAAc,oBAAoB,aAAa,YAAY,WAAW;AAC1F;AAEO,MAAM,6BAAyD,oBAAI,IAAI;AAAA,EAC5E;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,0BAA0B,MAA6B,IAAoC;AACzG,MAAI,SAAS,GAAI,QAAO;AACxB,QAAM,UAAU,2BAA2B,IAAI;AAC/C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,SAAS,EAAE;AAC5B;AAEO,SAAS,mBAAmB,UAA2B,WAA2C;AACvG,QAAM,gBAAgB,SAAS;AAC/B,MAAI,CAAC,0BAA0B,eAAe,SAAS,EAAG,QAAO;AACjE,WAAS,gBAAgB;AACzB,SAAO;AACT;AAEO,SAAS,yBAAyB,QAAuD;AAC9F,MAAI,WAAW,YAAa,QAAO;AACnC,MAAI,WAAW,WAAY,QAAO;AAClC,MAAI,WAAW,YAAa,QAAO;AACnC,SAAO;AACT;",
4
+ "sourcesContent": ["import type { UnifiedShipmentStatus } from './adapter'\nimport type { CarrierShipment } from '../data/entities'\nimport type { ShippingEventId } from '../events'\n\n// Use Symbol.for so the marker survives module duplication across bundle\n// boundaries \u2014 production builds can split this class into separate chunks,\n// which breaks `instanceof` (see isCrudHttpError in\n// @open-mercato/shared/lib/crud/errors for the same pattern).\nconst SHIPMENT_CANCEL_NOT_ALLOWED_MARKER = Symbol.for('@open-mercato/shipping_carriers/ShipmentCancelNotAllowedError')\n\nexport class ShipmentCancelNotAllowedError extends Error {\n readonly [SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] = true\n\n constructor(status: string) {\n super(`Shipment cannot be cancelled in its current status: ${status}`)\n this.name = 'ShipmentCancelNotAllowedError'\n }\n}\n\n/**\n * Type-safe check that works across module/bundle boundaries. Prefer this over\n * `instanceof ShipmentCancelNotAllowedError` in route handlers, where the thrown\n * error may originate from a different bundle than the one performing the check.\n */\nexport function isShipmentCancelNotAllowedError(error: unknown): error is ShipmentCancelNotAllowedError {\n return !!error\n && typeof error === 'object'\n && (error as Record<symbol, unknown>)[SHIPMENT_CANCEL_NOT_ALLOWED_MARKER] === true\n}\n\nconst VALID_SHIPPING_TRANSITIONS: Record<string, UnifiedShipmentStatus[]> = {\n label_created: ['picked_up', 'in_transit', 'cancelled'],\n picked_up: ['in_transit', 'cancelled'],\n in_transit: ['out_for_delivery', 'delivered', 'returned', 'failed_delivery'],\n out_for_delivery: ['delivered', 'returned', 'failed_delivery'],\n failed_delivery: ['in_transit', 'out_for_delivery', 'delivered', 'returned', 'cancelled'],\n}\n\nexport const TERMINAL_SHIPPING_STATUSES: Set<UnifiedShipmentStatus> = new Set([\n 'delivered',\n 'returned',\n 'cancelled',\n])\n\nexport function isValidShippingTransition(from: UnifiedShipmentStatus, to: UnifiedShipmentStatus): boolean {\n if (from === to) return false\n const allowed = VALID_SHIPPING_TRANSITIONS[from]\n if (!allowed) return false\n return allowed.includes(to)\n}\n\nexport function syncShipmentStatus(shipment: CarrierShipment, newStatus: UnifiedShipmentStatus): boolean {\n const currentStatus = shipment.unifiedStatus as UnifiedShipmentStatus\n if (!isValidShippingTransition(currentStatus, newStatus)) return false\n shipment.unifiedStatus = newStatus\n return true\n}\n\nexport function getTerminalShippingEvent(status: UnifiedShipmentStatus): ShippingEventId | null {\n if (status === 'delivered') return 'shipping_carriers.shipment.delivered'\n if (status === 'returned') return 'shipping_carriers.shipment.returned'\n if (status === 'cancelled') return 'shipping_carriers.shipment.cancelled'\n return null\n}\n"],
5
+ "mappings": "AAAA;AAQA,MAAM,qCAAqC,uBAAO,IAAI,+DAA+D;AAE9G,MAAM,uCAAsC,YACvC,yCADuC,IAAM;AAAA,EAGvD,YAAY,QAAgB;AAC1B,UAAM,uDAAuD,MAAM,EAAE;AAHvE,SAAU,MAAsC;AAI9C,SAAK,OAAO;AAAA,EACd;AACF;AAOO,SAAS,gCAAgC,OAAwD;AACtG,SAAO,CAAC,CAAC,SACJ,OAAO,UAAU,YAChB,MAAkC,kCAAkC,MAAM;AAClF;AAEA,MAAM,6BAAsE;AAAA,EAC1E,eAAe,CAAC,aAAa,cAAc,WAAW;AAAA,EACtD,WAAW,CAAC,cAAc,WAAW;AAAA,EACrC,YAAY,CAAC,oBAAoB,aAAa,YAAY,iBAAiB;AAAA,EAC3E,kBAAkB,CAAC,aAAa,YAAY,iBAAiB;AAAA,EAC7D,iBAAiB,CAAC,cAAc,oBAAoB,aAAa,YAAY,WAAW;AAC1F;AAEO,MAAM,6BAAyD,oBAAI,IAAI;AAAA,EAC5E;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,0BAA0B,MAA6B,IAAoC;AACzG,MAAI,SAAS,GAAI,QAAO;AACxB,QAAM,UAAU,2BAA2B,IAAI;AAC/C,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,SAAS,EAAE;AAC5B;AAEO,SAAS,mBAAmB,UAA2B,WAA2C;AACvG,QAAM,gBAAgB,SAAS;AAC/B,MAAI,CAAC,0BAA0B,eAAe,SAAS,EAAG,QAAO;AACjE,WAAS,gBAAgB;AACzB,SAAO;AACT;AAEO,SAAS,yBAAyB,QAAuD;AAC9F,MAAI,WAAW,YAAa,QAAO;AACnC,MAAI,WAAW,WAAY,QAAO;AAClC,MAAI,WAAW,YAAa,QAAO;AACnC,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -361,7 +361,9 @@ function NodeEditDialog({ node, isOpen, onClose, onSave, onDelete }) {
361
361
  decision: t("workflows.nodeTypes.decision"),
362
362
  waitForSignal: t("workflows.nodeTypes.waitForSignal"),
363
363
  waitForTimer: t("workflows.nodeTypes.waitForTimer"),
364
- subWorkflow: t("workflows.nodeTypes.subWorkflow")
364
+ subWorkflow: t("workflows.nodeTypes.subWorkflow"),
365
+ parallelFork: t("workflows.nodeTypes.parallelFork"),
366
+ parallelJoin: t("workflows.nodeTypes.parallelJoin")
365
367
  }[node.type || "automated"];
366
368
  const isEditable = node.type !== "end";
367
369
  const isStartNode = node.type === "start";