@jarwizz/create-jarshop 0.1.4 → 0.1.6

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 (20) hide show
  1. package/dist/template/src/email-smoke.ts +1 -0
  2. package/dist/template/src/migrations/1787800000000-jarshop-order-email-event-key.ts +60 -0
  3. package/dist/template/src/migrations/1787900000000-jarshop-email-brand-configuration.ts +32 -0
  4. package/dist/template/src/migrations/1788000000000-jarshop-email-brand-schema-parity.ts +21 -0
  5. package/dist/template/src/plugins/jarshop-checkout/dashboard/index.tsx +213 -32
  6. package/dist/template/src/plugins/jarshop-checkout/dashboard/order-email-delivery-status.ts +17 -0
  7. package/dist/template/src/plugins/jarshop-checkout/index.ts +49 -1
  8. package/dist/template/src/plugins/jarshop-checkout/jarshop-checkout.plugin.ts +146 -5
  9. package/dist/template/src/plugins/jarshop-checkout/order-email-configuration.entity.ts +61 -0
  10. package/dist/template/src/plugins/jarshop-checkout/order-email-configuration.ts +190 -0
  11. package/dist/template/src/plugins/jarshop-checkout/order-email-delivery-core.ts +321 -13
  12. package/dist/template/src/plugins/jarshop-checkout/order-email-delivery-status.ts +3 -1
  13. package/dist/template/src/plugins/jarshop-checkout/order-email-delivery.entity.ts +15 -4
  14. package/dist/template/src/plugins/jarshop-checkout/order-email-lifecycle.ts +355 -0
  15. package/dist/template/src/plugins/jarshop-checkout/order-email-outbox.ts +308 -15
  16. package/dist/template/src/plugins/jarshop-checkout/order-email-worker.ts +86 -8
  17. package/dist/template/src/plugins/jarshop-checkout/order-lifecycle-core.ts +152 -0
  18. package/dist/template/src/plugins/jarshop-checkout/order-lifecycle-process.ts +102 -0
  19. package/dist/template-manifest.json +1 -1
  20. package/package.json +1 -1
@@ -14,6 +14,7 @@ async function main(): Promise<void> {
14
14
  "This is a controlled JarShop SMTP transport smoke message.",
15
15
  "No customer order, bearer token, or checkout data is included.",
16
16
  ].join("\n"),
17
+ html: "<p>This is a controlled JarShop SMTP transport smoke message. No customer order, bearer token, or checkout data is included.</p>",
17
18
  },
18
19
  });
19
20
  console.log("SMTP smoke email was accepted by the configured transport.");
@@ -0,0 +1,60 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /**
4
+ * Generalizes confirmation-only deduplication without replacing or deleting
5
+ * existing delivery history. New lifecycle events may omit checkoutAttemptId.
6
+ */
7
+ export class JarShopOrderEmailEventKey1787800000000 implements MigrationInterface {
8
+ name = "JarShopOrderEmailEventKey1787800000000";
9
+
10
+ public async up(queryRunner: QueryRunner): Promise<void> {
11
+ await queryRunner.query(
12
+ `ALTER TABLE "jarshop_order_email_delivery" ADD "eventKey" character varying(255)`,
13
+ );
14
+ await queryRunner.query(`
15
+ UPDATE "jarshop_order_email_delivery"
16
+ SET "eventKey" = 'order:' || "orderId"::text || ':' || "kind"
17
+ WHERE "eventKey" IS NULL
18
+ `);
19
+ await queryRunner.query(
20
+ `ALTER TABLE "jarshop_order_email_delivery" ALTER COLUMN "eventKey" SET NOT NULL`,
21
+ );
22
+ await queryRunner.query(
23
+ `ALTER TABLE "jarshop_order_email_delivery" ALTER COLUMN "checkoutAttemptId" DROP NOT NULL`,
24
+ );
25
+ await queryRunner.query(
26
+ `ALTER TABLE "jarshop_order_email_delivery" DROP CONSTRAINT "UQ_jarshop_order_email_delivery_order_kind"`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "jarshop_order_email_delivery" ADD CONSTRAINT "UQ_jarshop_order_email_delivery_event_key" UNIQUE ("eventKey")`,
30
+ );
31
+ }
32
+
33
+ public async down(queryRunner: QueryRunner): Promise<void> {
34
+ await queryRunner.query(`
35
+ DO $$
36
+ BEGIN
37
+ IF EXISTS (
38
+ SELECT 1
39
+ FROM "jarshop_order_email_delivery"
40
+ WHERE "checkoutAttemptId" IS NULL OR "kind" <> 'order-confirmation'
41
+ ) THEN
42
+ RAISE EXCEPTION 'cannot restore confirmation-only order email schema while lifecycle deliveries exist';
43
+ END IF;
44
+ END
45
+ $$
46
+ `);
47
+ await queryRunner.query(
48
+ `ALTER TABLE "jarshop_order_email_delivery" DROP CONSTRAINT "UQ_jarshop_order_email_delivery_event_key"`,
49
+ );
50
+ await queryRunner.query(
51
+ `ALTER TABLE "jarshop_order_email_delivery" ALTER COLUMN "checkoutAttemptId" SET NOT NULL`,
52
+ );
53
+ await queryRunner.query(
54
+ `ALTER TABLE "jarshop_order_email_delivery" ADD CONSTRAINT "UQ_jarshop_order_email_delivery_order_kind" UNIQUE ("orderId", "kind")`,
55
+ );
56
+ await queryRunner.query(
57
+ `ALTER TABLE "jarshop_order_email_delivery" DROP COLUMN "eventKey"`,
58
+ );
59
+ }
60
+ }
@@ -0,0 +1,32 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class JarShopEmailBrandConfiguration1787900000000 implements MigrationInterface {
4
+ name = "JarShopEmailBrandConfiguration1787900000000";
5
+
6
+ async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`
8
+ CREATE TABLE "jarshop_email_brand_configuration" (
9
+ "id" SERIAL NOT NULL,
10
+ "channelId" integer NOT NULL,
11
+ "brandName" character varying(120) NOT NULL,
12
+ "logoUrl" character varying(2048),
13
+ "primaryColor" character varying(7) NOT NULL,
14
+ "websiteUrl" character varying(2048) NOT NULL,
15
+ "supportEmail" character varying(320) NOT NULL,
16
+ "companyName" character varying(160) NOT NULL,
17
+ "companyAddress" text NOT NULL,
18
+ "enabledEvents" jsonb NOT NULL DEFAULT '[]'::jsonb,
19
+ "shippingMethods" jsonb NOT NULL DEFAULT '[]'::jsonb,
20
+ "createdAt" TIMESTAMP NOT NULL DEFAULT now(),
21
+ "updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
22
+ CONSTRAINT "PK_jarshop_email_brand_configuration" PRIMARY KEY ("id"),
23
+ CONSTRAINT "UQ_jarshop_email_brand_configuration_channel" UNIQUE ("channelId"),
24
+ CONSTRAINT "FK_jarshop_email_brand_configuration_channel" FOREIGN KEY ("channelId") REFERENCES "channel"("id") ON DELETE CASCADE ON UPDATE CASCADE
25
+ )
26
+ `);
27
+ }
28
+
29
+ async down(queryRunner: QueryRunner): Promise<void> {
30
+ await queryRunner.query(`DROP TABLE "jarshop_email_brand_configuration"`);
31
+ }
32
+ }
@@ -0,0 +1,21 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class JarShopEmailBrandSchemaParity1788000000000 implements MigrationInterface {
4
+ name = "JarShopEmailBrandSchemaParity1788000000000";
5
+
6
+ async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`
8
+ ALTER TABLE "jarshop_email_brand_configuration"
9
+ DROP CONSTRAINT IF EXISTS "FK_jarshop_email_brand_configuration_channel"
10
+ `);
11
+ }
12
+
13
+ async down(queryRunner: QueryRunner): Promise<void> {
14
+ await queryRunner.query(`
15
+ ALTER TABLE "jarshop_email_brand_configuration"
16
+ ADD CONSTRAINT "FK_jarshop_email_brand_configuration_channel"
17
+ FOREIGN KEY ("channelId") REFERENCES "channel"("id")
18
+ ON DELETE CASCADE ON UPDATE CASCADE
19
+ `);
20
+ }
21
+ }
@@ -1,13 +1,16 @@
1
1
  import { api, defineDashboardExtension, useQuery } from "@vendure/dashboard";
2
2
  import { graphql } from "@/gql";
3
+ import { useState, type FormEvent } from "react";
3
4
  import {
4
5
  getDashboardOrderId,
5
- toOrderEmailDeliveryStatusPanel,
6
+ toOrderEmailDeliveryTimeline,
6
7
  } from "./order-email-delivery-status.js";
7
8
 
8
- const orderEmailDeliveryStatusDocument = graphql(`
9
- query JarShopOrderEmailDeliveryStatus($orderId: ID!) {
10
- jarShopOrderEmailDeliveryStatus(orderId: $orderId) {
9
+ const orderEmailDeliveriesDocument = graphql(`
10
+ query JarShopOrderEmailDeliveries($orderId: ID!) {
11
+ jarShopOrderEmailDeliveries(orderId: $orderId) {
12
+ deliveryId
13
+ kind
11
14
  status
12
15
  attemptCount
13
16
  nextAttemptAt
@@ -17,6 +20,37 @@ const orderEmailDeliveryStatusDocument = graphql(`
17
20
  }
18
21
  `);
19
22
 
23
+ const emailBrandConfigurationDocument = graphql(`
24
+ query JarShopEmailBrandConfiguration {
25
+ jarShopEmailBrandConfiguration {
26
+ brandName
27
+ logoUrl
28
+ primaryColor
29
+ websiteUrl
30
+ supportEmail
31
+ companyName
32
+ companyAddress
33
+ enabledEvents
34
+ shippingMethods {
35
+ code
36
+ pickupDetails
37
+ trackingUrlTemplate
38
+ }
39
+ }
40
+ }
41
+ `);
42
+
43
+ const updateEmailBrandConfigurationDocument = graphql(`
44
+ mutation UpdateJarShopEmailBrandConfiguration(
45
+ $input: JarShopEmailBrandConfigurationInput!
46
+ ) {
47
+ updateJarShopEmailBrandConfiguration(input: $input) {
48
+ brandName
49
+ enabledEvents
50
+ }
51
+ }
52
+ `);
53
+
20
54
  function formatDateTime(value: string): string {
21
55
  return new Intl.DateTimeFormat(undefined, {
22
56
  dateStyle: "medium",
@@ -30,7 +64,7 @@ function OrderEmailDeliveryStatusBlock({ context }: { context: unknown }) {
30
64
  queryKey: ["jarshop-order-email-delivery-status", orderId],
31
65
  enabled: orderId !== undefined,
32
66
  queryFn: () =>
33
- api.query(orderEmailDeliveryStatusDocument, {
67
+ api.query(orderEmailDeliveriesDocument, {
34
68
  orderId: orderId ?? "",
35
69
  }),
36
70
  });
@@ -49,10 +83,10 @@ function OrderEmailDeliveryStatusBlock({ context }: { context: unknown }) {
49
83
  );
50
84
  }
51
85
 
52
- const panel = toOrderEmailDeliveryStatusPanel(
53
- query.data?.jarShopOrderEmailDeliveryStatus,
86
+ const timeline = toOrderEmailDeliveryTimeline(
87
+ query.data?.jarShopOrderEmailDeliveries ?? [],
54
88
  );
55
- if (panel.kind === "unavailable") {
89
+ if (timeline.length === 0) {
56
90
  return (
57
91
  <p className="text-sm text-muted-foreground">
58
92
  No JarShop order email is queued for this order.
@@ -61,32 +95,168 @@ function OrderEmailDeliveryStatusBlock({ context }: { context: unknown }) {
61
95
  }
62
96
 
63
97
  return (
64
- <dl className="grid gap-2 text-sm">
65
- <div className="flex justify-between gap-4">
66
- <dt className="text-muted-foreground">Status</dt>
67
- <dd className="font-medium capitalize">{panel.status}</dd>
68
- </div>
69
- <div className="flex justify-between gap-4">
70
- <dt className="text-muted-foreground">Attempts</dt>
71
- <dd>{panel.attemptCount}</dd>
98
+ <ol className="grid gap-4 text-sm">
99
+ {timeline.map((delivery) => (
100
+ <li key={delivery.deliveryId} className="border-l-2 pl-3">
101
+ <div className="flex justify-between gap-4">
102
+ <strong className="capitalize">{delivery.label}</strong>
103
+ <span className="capitalize">{delivery.status}</span>
104
+ </div>
105
+ <p className="text-muted-foreground">
106
+ Attempts: {delivery.attemptCount} · Next:{" "}
107
+ {formatDateTime(delivery.nextAttemptAt)}
108
+ </p>
109
+ {delivery.sentAt ? (
110
+ <p>Sent: {formatDateTime(delivery.sentAt)}</p>
111
+ ) : null}
112
+ {delivery.lastErrorCode ? (
113
+ <p className="font-mono text-xs">{delivery.lastErrorCode}</p>
114
+ ) : null}
115
+ </li>
116
+ ))}
117
+ </ol>
118
+ );
119
+ }
120
+
121
+ function EmailBrandConfigurationBlock() {
122
+ const [message, setMessage] = useState<string>();
123
+ const query = useQuery({
124
+ queryKey: ["jarshop-email-brand-configuration"],
125
+ queryFn: () => api.query(emailBrandConfigurationDocument),
126
+ });
127
+ if (query.isPending) return <p>Loading email configuration…</p>;
128
+ if (query.isError)
129
+ return (
130
+ <p className="text-destructive">Email configuration is unavailable.</p>
131
+ );
132
+
133
+ const configuration = query.data?.jarShopEmailBrandConfiguration;
134
+ const methods = new Map(
135
+ configuration?.shippingMethods.map((method) => [method.code, method]),
136
+ );
137
+ const enabled = new Set(configuration?.enabledEvents ?? []);
138
+ const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
139
+ event.preventDefault();
140
+ setMessage(undefined);
141
+ const form = new FormData(event.currentTarget);
142
+ try {
143
+ await api.mutate(updateEmailBrandConfigurationDocument, {
144
+ input: {
145
+ brandName: String(form.get("brandName") ?? ""),
146
+ logoUrl: String(form.get("logoUrl") ?? "") || null,
147
+ primaryColor: String(form.get("primaryColor") ?? ""),
148
+ websiteUrl: String(form.get("websiteUrl") ?? ""),
149
+ supportEmail: String(form.get("supportEmail") ?? ""),
150
+ companyName: String(form.get("companyName") ?? ""),
151
+ companyAddress: String(form.get("companyAddress") ?? ""),
152
+ enabledEvents: form.getAll("enabledEvents").map(String),
153
+ shippingMethods: [
154
+ {
155
+ code: "pickup",
156
+ pickupDetails: String(form.get("pickupDetails") ?? ""),
157
+ trackingUrlTemplate: "",
158
+ },
159
+ {
160
+ code: "courier",
161
+ pickupDetails: "",
162
+ trackingUrlTemplate: String(
163
+ form.get("trackingUrlTemplate") ?? "",
164
+ ),
165
+ },
166
+ ],
167
+ },
168
+ });
169
+ setMessage("Email configuration saved.");
170
+ await query.refetch();
171
+ } catch {
172
+ setMessage("Email configuration could not be saved.");
173
+ }
174
+ };
175
+
176
+ return (
177
+ <form
178
+ key={JSON.stringify(configuration)}
179
+ onSubmit={handleSubmit}
180
+ className="grid gap-4 text-sm"
181
+ >
182
+ <div className="grid grid-cols-2 gap-3">
183
+ {[
184
+ ["brandName", "Brand name", configuration?.brandName],
185
+ [
186
+ "primaryColor",
187
+ "Primary color",
188
+ configuration?.primaryColor ?? "#111827",
189
+ ],
190
+ ["websiteUrl", "Website URL", configuration?.websiteUrl],
191
+ ["logoUrl", "Logo URL", configuration?.logoUrl],
192
+ ["supportEmail", "Support email", configuration?.supportEmail],
193
+ ["companyName", "Company name", configuration?.companyName],
194
+ ].map(([name, label, value]) => (
195
+ <label key={name} className="grid gap-1">
196
+ <span>{label}</span>
197
+ <input
198
+ className="rounded border bg-background px-3 py-2"
199
+ name={name}
200
+ defaultValue={value ?? ""}
201
+ />
202
+ </label>
203
+ ))}
72
204
  </div>
73
- <div className="flex justify-between gap-4">
74
- <dt className="text-muted-foreground">Next attempt</dt>
75
- <dd>{formatDateTime(panel.nextAttemptAt)}</dd>
205
+ <label className="grid gap-1">
206
+ <span>Company address</span>
207
+ <textarea
208
+ className="rounded border bg-background px-3 py-2"
209
+ name="companyAddress"
210
+ defaultValue={configuration?.companyAddress ?? ""}
211
+ />
212
+ </label>
213
+ <label className="grid gap-1">
214
+ <span>Pickup details</span>
215
+ <textarea
216
+ className="rounded border bg-background px-3 py-2"
217
+ name="pickupDetails"
218
+ defaultValue={methods.get("pickup")?.pickupDetails ?? ""}
219
+ />
220
+ </label>
221
+ <label className="grid gap-1">
222
+ <span>Courier tracking URL template</span>
223
+ <input
224
+ className="rounded border bg-background px-3 py-2"
225
+ name="trackingUrlTemplate"
226
+ placeholder="https://carrier.example/track/{trackingCode}"
227
+ defaultValue={methods.get("courier")?.trackingUrlTemplate ?? ""}
228
+ />
229
+ </label>
230
+ <fieldset className="grid grid-cols-2 gap-2">
231
+ <legend className="mb-2 font-medium">Enabled lifecycle events</legend>
232
+ {[
233
+ "pickup-ready",
234
+ "pickup-picked-up",
235
+ "shipment-shipped",
236
+ "shipment-delivered",
237
+ "order-cancelled",
238
+ ].map((kind) => (
239
+ <label key={kind} className="flex gap-2">
240
+ <input
241
+ type="checkbox"
242
+ name="enabledEvents"
243
+ value={kind}
244
+ defaultChecked={enabled.has(kind)}
245
+ />
246
+ <span>{kind}</span>
247
+ </label>
248
+ ))}
249
+ </fieldset>
250
+ <div className="flex items-center gap-3">
251
+ <button
252
+ type="submit"
253
+ className="rounded bg-primary px-4 py-2 text-primary-foreground"
254
+ >
255
+ Save email configuration
256
+ </button>
257
+ {message ? <span>{message}</span> : null}
76
258
  </div>
77
- {panel.sentAt ? (
78
- <div className="flex justify-between gap-4">
79
- <dt className="text-muted-foreground">Sent</dt>
80
- <dd>{formatDateTime(panel.sentAt)}</dd>
81
- </div>
82
- ) : null}
83
- {panel.lastErrorCode ? (
84
- <div className="flex justify-between gap-4">
85
- <dt className="text-muted-foreground">Error code</dt>
86
- <dd className="font-mono text-xs">{panel.lastErrorCode}</dd>
87
- </div>
88
- ) : null}
89
- </dl>
259
+ </form>
90
260
  );
91
261
  }
92
262
 
@@ -103,5 +273,16 @@ defineDashboardExtension({
103
273
  component: OrderEmailDeliveryStatusBlock,
104
274
  requiresPermission: "ReadOrder",
105
275
  },
276
+ {
277
+ id: "jarshop-email-brand-configuration",
278
+ title: "JarShop lifecycle email",
279
+ location: {
280
+ pageId: "channel-detail",
281
+ column: "main",
282
+ position: { blockId: "channel-defaults", order: "after" },
283
+ },
284
+ component: EmailBrandConfigurationBlock,
285
+ requiresPermission: "UpdateSettings",
286
+ },
106
287
  ],
107
288
  });
@@ -41,3 +41,20 @@ export function toOrderEmailDeliveryStatusPanel(
41
41
  lastErrorCode: delivery.lastErrorCode,
42
42
  };
43
43
  }
44
+
45
+ export function toOrderEmailDeliveryTimeline(
46
+ deliveries: ReadonlyArray<{
47
+ deliveryId: string;
48
+ kind: string;
49
+ status: "pending" | "processing" | "sent" | "failed";
50
+ attemptCount: number;
51
+ nextAttemptAt: string;
52
+ sentAt: string | null;
53
+ lastErrorCode: string | null;
54
+ }>,
55
+ ) {
56
+ return deliveries.map((delivery) => ({
57
+ ...delivery,
58
+ label: delivery.kind.replaceAll("-", " "),
59
+ }));
60
+ }
@@ -13,14 +13,50 @@ export {
13
13
  } from "./confirmation.js";
14
14
  export { JarShopLegalConfiguration } from "./legal.entity.js";
15
15
  export { JarShopOrderEmailDelivery } from "./order-email-delivery.entity.js";
16
- export type { OrderEmailDeliveryStatus } from "./order-email-delivery.entity.js";
16
+ export type {
17
+ OrderEmailDeliveryKind,
18
+ OrderEmailDeliveryStatus,
19
+ } from "./order-email-delivery.entity.js";
20
+ export { JarShopEmailBrandConfiguration } from "./order-email-configuration.entity.js";
21
+ export type {
22
+ ConfigurableOrderEmailKind,
23
+ OrderEmailShippingMethodConfiguration,
24
+ } from "./order-email-configuration.entity.js";
25
+ export {
26
+ createTrackingUrl,
27
+ isLifecycleEmailEnabled,
28
+ normalizeOrderEmailBrandConfiguration,
29
+ resolveShippingEmailConfiguration,
30
+ toOrderEmailBrandConfiguration,
31
+ validateOrderEmailBrandConfiguration,
32
+ validateTrackingUrlTemplate,
33
+ } from "./order-email-configuration.js";
34
+ export type { OrderEmailBrandConfigurationInput } from "./order-email-configuration.js";
17
35
  export {
18
36
  assertOrderEmailOutboxEncryptionConfiguration,
37
+ createPendingCancellationEmailDelivery,
38
+ createPendingFulfillmentEmailDelivery,
39
+ createOrderConfirmationEventKey,
19
40
  createPendingOrderEmailDelivery,
20
41
  decryptOrderEmailOutboxPayload,
21
42
  encryptOrderEmailOutboxPayload,
43
+ isLegacyOrderConfirmationOutboxPayload,
44
+ isSnapshotOrderEmailOutboxPayload,
22
45
  parseOrderEmailEncryptionKey,
23
46
  } from "./order-email-outbox.js";
47
+ export type {
48
+ FulfillmentLifecycleEmailKind,
49
+ FulfillmentLifecycleEmailSnapshot,
50
+ LegacyOrderConfirmationOutboxPayload,
51
+ OrderCancellationEmailSnapshot,
52
+ OrderConfirmationEmailSnapshot,
53
+ OrderEmailLineSnapshot,
54
+ OrderEmailOutboxPayload,
55
+ } from "./order-email-outbox.js";
56
+ export {
57
+ JarShopOrderEmailLifecycleOutbox,
58
+ toEmailLineSnapshots,
59
+ } from "./order-email-lifecycle.js";
24
60
  export {
25
61
  assertOrderEmailDeliveryConfiguration,
26
62
  createFileOrderEmailTransport,
@@ -29,6 +65,7 @@ export {
29
65
  readOrderEmailTransportConfiguration,
30
66
  registerUniqueScheduledTask,
31
67
  renderOrderConfirmationEmail,
68
+ renderOrderLifecycleEmail,
32
69
  scheduleOrderEmailRetry,
33
70
  sanitizeFileTransportEmail,
34
71
  writeSanitizedFileTransport,
@@ -36,6 +73,7 @@ export {
36
73
  export type {
37
74
  OrderConfirmationEmail,
38
75
  OrderConfirmationEmailInput,
76
+ OrderLifecycleEmailInput,
39
77
  OrderEmailLanguageCode,
40
78
  OrderEmailTransport,
41
79
  OrderEmailTransportConfiguration,
@@ -48,4 +86,14 @@ export {
48
86
  registerJarShopOrderEmailWorkerTask,
49
87
  } from "./order-email-worker.js";
50
88
  export { JarShopOrderConsent } from "./order-consent.entity.js";
89
+ export {
90
+ getCourierOrderAlignmentState,
91
+ resolveShippingMethodCodeForOrderLines,
92
+ toFulfillmentLifecycleEmailKind,
93
+ validateFulfillmentTransition,
94
+ } from "./order-lifecycle-core.js";
95
+ export {
96
+ jarShopFulfillmentProcess,
97
+ registerJarShopOrderLifecycle,
98
+ } from "./order-lifecycle-process.js";
51
99
  export { JarShopCheckoutPlugin } from "./jarshop-checkout.plugin.js";