@legenki/print2medusa 0.3.0 → 0.5.0

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.
@@ -4,6 +4,13 @@ const adminSdk = require("@medusajs/admin-sdk");
4
4
  const ui = require("@medusajs/ui");
5
5
  const react = require("react");
6
6
  const ATTENTION_STATES = /* @__PURE__ */ new Set(["failed", "canceled", "onhold"]);
7
+ const formatMinor = (amount, currency) => {
8
+ if (typeof amount !== "number" || !Number.isFinite(amount)) {
9
+ return null;
10
+ }
11
+ const code = typeof currency === "string" ? currency.toUpperCase() : "";
12
+ return `${(amount / 100).toFixed(2)}${code ? ` ${code}` : ""}`;
13
+ };
7
14
  const PrintfulOrderWidget = ({ data }) => {
8
15
  const metadata = data.metadata ?? {};
9
16
  const printfulOrderId = metadata.printful_order_id;
@@ -14,6 +21,19 @@ const PrintfulOrderWidget = ({ data }) => {
14
21
  const statusUpdatedAt = metadata.printful_status_updated_at;
15
22
  const shipments = metadata.printful_shipments ?? [];
16
23
  const isAttention = status ? ATTENTION_STATES.has(status) : false;
24
+ const costTotal = formatMinor(
25
+ metadata.printful_cost_total,
26
+ metadata.printful_cost_currency
27
+ );
28
+ const retailTotal = formatMinor(
29
+ metadata.printful_retail_total,
30
+ metadata.printful_retail_currency
31
+ );
32
+ const margin = formatMinor(
33
+ metadata.printful_margin,
34
+ metadata.printful_cost_currency
35
+ );
36
+ const currencyMismatch = costTotal !== null && retailTotal !== null && margin === null;
17
37
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
18
38
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
19
39
  /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Printful" }),
@@ -49,7 +69,23 @@ const PrintfulOrderWidget = ({ data }) => {
49
69
  "Last synced: ",
50
70
  new Date(statusUpdatedAt).toLocaleString()
51
71
  ] }) : null
52
- ] })
72
+ ] }),
73
+ costTotal ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: "Economics" }),
75
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
76
+ "Printful cost: ",
77
+ costTotal
78
+ ] }),
79
+ retailTotal ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
80
+ "Retail: ",
81
+ retailTotal
82
+ ] }) : null,
83
+ margin ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", children: [
84
+ "Margin: ",
85
+ margin
86
+ ] }) : null,
87
+ currencyMismatch ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Margin unavailable — Printful billed in a different currency than the order." }) : null
88
+ ] }) : null
53
89
  ] });
54
90
  };
55
91
  adminSdk.defineWidgetConfig({
@@ -100,6 +136,13 @@ const PrintfulSyncWidget = () => {
100
136
  void loadStatus();
101
137
  void loadWebhook();
102
138
  }, []);
139
+ react.useEffect(() => {
140
+ if (!(status == null ? void 0 : status.running)) {
141
+ return;
142
+ }
143
+ const t = setInterval(() => void loadStatus(), 3e3);
144
+ return () => clearInterval(t);
145
+ }, [status == null ? void 0 : status.running]);
103
146
  const onRegisterWebhook = async () => {
104
147
  setRegistering(true);
105
148
  try {
@@ -126,7 +169,6 @@ const PrintfulSyncWidget = () => {
126
169
  }
127
170
  };
128
171
  const onSync = async () => {
129
- var _a, _b, _c;
130
172
  setSyncing(true);
131
173
  try {
132
174
  const res = await fetch("/admin/printful/sync", {
@@ -135,13 +177,20 @@ const PrintfulSyncWidget = () => {
135
177
  headers: { "Content-Type": "application/json" },
136
178
  body: JSON.stringify({})
137
179
  });
180
+ if (res.status === 409) {
181
+ const body = await res.json();
182
+ ui.toast.info("A Printful sync is already running", {
183
+ description: body.started_at ? `Started ${new Date(body.started_at).toLocaleString()}` : void 0
184
+ });
185
+ await loadStatus();
186
+ return;
187
+ }
138
188
  if (!res.ok) {
139
189
  const text = await res.text();
140
190
  throw new Error(text || `Sync failed (${res.status})`);
141
191
  }
142
- const data = await res.json();
143
- ui.toast.success("Printful sync finished", {
144
- description: `Created ${((_a = data.counters) == null ? void 0 : _a.created) ?? 0}, updated ${((_b = data.counters) == null ? void 0 : _b.updated) ?? 0}, failed ${((_c = data.counters) == null ? void 0 : _c.failed) ?? 0}`
192
+ ui.toast.success("Printful sync started", {
193
+ description: "Progress will appear below as products are processed."
145
194
  });
146
195
  await loadStatus();
147
196
  } catch (e) {
@@ -153,6 +202,7 @@ const PrintfulSyncWidget = () => {
153
202
  }
154
203
  };
155
204
  const latest = status == null ? void 0 : status.latest_sync;
205
+ const running = (status == null ? void 0 : status.running) === true;
156
206
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
157
207
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
158
208
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
@@ -164,9 +214,9 @@ const PrintfulSyncWidget = () => {
164
214
  {
165
215
  size: "small",
166
216
  onClick: () => void onSync(),
167
- isLoading: syncing,
168
- disabled: loading || syncing,
169
- children: "Sync Now"
217
+ isLoading: syncing || running,
218
+ disabled: loading || syncing || running,
219
+ children: running ? "Syncing…" : "Sync Now"
170
220
  }
171
221
  )
172
222
  ] }),
@@ -180,7 +230,14 @@ const PrintfulSyncWidget = () => {
180
230
  new Date(latest.started_at).toLocaleString(),
181
231
  latest.finished_at ? ` · Finished: ${new Date(latest.finished_at).toLocaleString()}` : ""
182
232
  ] }),
183
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
233
+ running ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
234
+ "Processed ",
235
+ latest.products_processed ?? 0,
236
+ " of",
237
+ " ",
238
+ latest.products_total ?? 0,
239
+ " products"
240
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
184
241
  "Created ",
185
242
  latest.products_created ?? 0,
186
243
  " · Updated",
@@ -3,6 +3,13 @@ import { defineWidgetConfig } from "@medusajs/admin-sdk";
3
3
  import { Container, Heading, Badge, Text, Button, toast } from "@medusajs/ui";
4
4
  import { useState, useEffect } from "react";
5
5
  const ATTENTION_STATES = /* @__PURE__ */ new Set(["failed", "canceled", "onhold"]);
6
+ const formatMinor = (amount, currency) => {
7
+ if (typeof amount !== "number" || !Number.isFinite(amount)) {
8
+ return null;
9
+ }
10
+ const code = typeof currency === "string" ? currency.toUpperCase() : "";
11
+ return `${(amount / 100).toFixed(2)}${code ? ` ${code}` : ""}`;
12
+ };
6
13
  const PrintfulOrderWidget = ({ data }) => {
7
14
  const metadata = data.metadata ?? {};
8
15
  const printfulOrderId = metadata.printful_order_id;
@@ -13,6 +20,19 @@ const PrintfulOrderWidget = ({ data }) => {
13
20
  const statusUpdatedAt = metadata.printful_status_updated_at;
14
21
  const shipments = metadata.printful_shipments ?? [];
15
22
  const isAttention = status ? ATTENTION_STATES.has(status) : false;
23
+ const costTotal = formatMinor(
24
+ metadata.printful_cost_total,
25
+ metadata.printful_cost_currency
26
+ );
27
+ const retailTotal = formatMinor(
28
+ metadata.printful_retail_total,
29
+ metadata.printful_retail_currency
30
+ );
31
+ const margin = formatMinor(
32
+ metadata.printful_margin,
33
+ metadata.printful_cost_currency
34
+ );
35
+ const currencyMismatch = costTotal !== null && retailTotal !== null && margin === null;
16
36
  return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
17
37
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
18
38
  /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Printful" }),
@@ -48,7 +68,23 @@ const PrintfulOrderWidget = ({ data }) => {
48
68
  "Last synced: ",
49
69
  new Date(statusUpdatedAt).toLocaleString()
50
70
  ] }) : null
51
- ] })
71
+ ] }),
72
+ costTotal ? /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 flex flex-col gap-2", children: [
73
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: "Economics" }),
74
+ /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
75
+ "Printful cost: ",
76
+ costTotal
77
+ ] }),
78
+ retailTotal ? /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
79
+ "Retail: ",
80
+ retailTotal
81
+ ] }) : null,
82
+ margin ? /* @__PURE__ */ jsxs(Text, { size: "small", children: [
83
+ "Margin: ",
84
+ margin
85
+ ] }) : null,
86
+ currencyMismatch ? /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: "Margin unavailable — Printful billed in a different currency than the order." }) : null
87
+ ] }) : null
52
88
  ] });
53
89
  };
54
90
  defineWidgetConfig({
@@ -99,6 +135,13 @@ const PrintfulSyncWidget = () => {
99
135
  void loadStatus();
100
136
  void loadWebhook();
101
137
  }, []);
138
+ useEffect(() => {
139
+ if (!(status == null ? void 0 : status.running)) {
140
+ return;
141
+ }
142
+ const t = setInterval(() => void loadStatus(), 3e3);
143
+ return () => clearInterval(t);
144
+ }, [status == null ? void 0 : status.running]);
102
145
  const onRegisterWebhook = async () => {
103
146
  setRegistering(true);
104
147
  try {
@@ -125,7 +168,6 @@ const PrintfulSyncWidget = () => {
125
168
  }
126
169
  };
127
170
  const onSync = async () => {
128
- var _a, _b, _c;
129
171
  setSyncing(true);
130
172
  try {
131
173
  const res = await fetch("/admin/printful/sync", {
@@ -134,13 +176,20 @@ const PrintfulSyncWidget = () => {
134
176
  headers: { "Content-Type": "application/json" },
135
177
  body: JSON.stringify({})
136
178
  });
179
+ if (res.status === 409) {
180
+ const body = await res.json();
181
+ toast.info("A Printful sync is already running", {
182
+ description: body.started_at ? `Started ${new Date(body.started_at).toLocaleString()}` : void 0
183
+ });
184
+ await loadStatus();
185
+ return;
186
+ }
137
187
  if (!res.ok) {
138
188
  const text = await res.text();
139
189
  throw new Error(text || `Sync failed (${res.status})`);
140
190
  }
141
- const data = await res.json();
142
- toast.success("Printful sync finished", {
143
- description: `Created ${((_a = data.counters) == null ? void 0 : _a.created) ?? 0}, updated ${((_b = data.counters) == null ? void 0 : _b.updated) ?? 0}, failed ${((_c = data.counters) == null ? void 0 : _c.failed) ?? 0}`
191
+ toast.success("Printful sync started", {
192
+ description: "Progress will appear below as products are processed."
144
193
  });
145
194
  await loadStatus();
146
195
  } catch (e) {
@@ -152,6 +201,7 @@ const PrintfulSyncWidget = () => {
152
201
  }
153
202
  };
154
203
  const latest = status == null ? void 0 : status.latest_sync;
204
+ const running = (status == null ? void 0 : status.running) === true;
155
205
  return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
156
206
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
157
207
  /* @__PURE__ */ jsxs("div", { children: [
@@ -163,9 +213,9 @@ const PrintfulSyncWidget = () => {
163
213
  {
164
214
  size: "small",
165
215
  onClick: () => void onSync(),
166
- isLoading: syncing,
167
- disabled: loading || syncing,
168
- children: "Sync Now"
216
+ isLoading: syncing || running,
217
+ disabled: loading || syncing || running,
218
+ children: running ? "Syncing…" : "Sync Now"
169
219
  }
170
220
  )
171
221
  ] }),
@@ -179,7 +229,14 @@ const PrintfulSyncWidget = () => {
179
229
  new Date(latest.started_at).toLocaleString(),
180
230
  latest.finished_at ? ` · Finished: ${new Date(latest.finished_at).toLocaleString()}` : ""
181
231
  ] }),
182
- /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
232
+ running ? /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
233
+ "Processed ",
234
+ latest.products_processed ?? 0,
235
+ " of",
236
+ " ",
237
+ latest.products_total ?? 0,
238
+ " products"
239
+ ] }) : /* @__PURE__ */ jsxs(Text, { size: "small", className: "text-ui-fg-subtle", children: [
183
240
  "Created ",
184
241
  latest.products_created ?? 0,
185
242
  " · Updated",
@@ -4,22 +4,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.POST = POST;
7
+ const utils_1 = require("@medusajs/framework/utils");
8
+ const printful_1 = require("../../../../modules/printful");
7
9
  const sync_products_1 = __importDefault(require("../../../../workflows/sync-products"));
10
+ /**
11
+ * Start a catalog sync.
12
+ *
13
+ * The claim completes before this responds. Doing it inside the background
14
+ * step would let two requests both answer 202 before either reached the
15
+ * insert, which is exactly the race the unique index exists to prevent.
16
+ */
8
17
  async function POST(req, res) {
18
+ const printful = req.scope.resolve(printful_1.PRINTFUL_MODULE);
19
+ const logger = req.scope.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
20
+ const options = await printful.getOptions();
9
21
  const limit = typeof req.body === "object" &&
10
22
  req.body &&
11
23
  "limit" in req.body &&
12
24
  req.body.limit != null
13
25
  ? Number(req.body.limit)
14
26
  : undefined;
15
- const { result } = await (0, sync_products_1.default)(req.scope).run({
27
+ const claim = await printful.claimSyncLog(options.syncStaleMinutes ?? 60);
28
+ if (!claim) {
29
+ const running = await printful.getRunningSyncLog();
30
+ res.status(409).json({
31
+ message: "A Printful sync is already running",
32
+ running_sync_id: running?.id,
33
+ started_at: running?.started_at,
34
+ heartbeat_at: running?.heartbeat_at,
35
+ });
36
+ return;
37
+ }
38
+ // Deliberately not awaited: the step is backgroundExecution, and the point
39
+ // of this release is that the admin gets an answer immediately rather than
40
+ // holding the request open for a 500-product catalog. The scheduled job in
41
+ // src/jobs/sync-products.ts DOES await the same workflow — it serves no HTTP
42
+ // response and wants the counters to log.
43
+ void (0, sync_products_1.default)(req.scope)
44
+ .run({
16
45
  input: {
46
+ sync_log_id: claim.id,
17
47
  limit: Number.isFinite(limit) ? limit : undefined,
18
48
  },
49
+ })
50
+ .catch((err) => {
51
+ logger.error(`Printful sync ${claim.id} failed to start: ${err instanceof Error ? err.message : String(err)}`);
19
52
  });
20
- res.status(200).json({
21
- sync_log: result.sync_log,
22
- counters: result.counters,
23
- });
53
+ res.status(202).json({ sync_id: claim.id });
24
54
  }
25
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL3N5bmMvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFHQSxvQkFtQkM7QUFyQkQsd0ZBQXNFO0FBRS9ELEtBQUssVUFBVSxJQUFJLENBQUMsR0FBa0IsRUFBRSxHQUFtQjtJQUNoRSxNQUFNLEtBQUssR0FDVCxPQUFPLEdBQUcsQ0FBQyxJQUFJLEtBQUssUUFBUTtRQUM1QixHQUFHLENBQUMsSUFBSTtRQUNSLE9BQU8sSUFBSSxHQUFHLENBQUMsSUFBSTtRQUNuQixHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJO1FBQ3BCLENBQUMsQ0FBQyxNQUFNLENBQUUsR0FBRyxDQUFDLElBQTJCLENBQUMsS0FBSyxDQUFDO1FBQ2hELENBQUMsQ0FBQyxTQUFTLENBQUE7SUFFZixNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxJQUFBLHVCQUFvQixFQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUM7UUFDM0QsS0FBSyxFQUFFO1lBQ0wsS0FBSyxFQUFFLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsU0FBUztTQUNsRDtLQUNGLENBQUMsQ0FBQTtJQUVGLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQ25CLFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUTtRQUN6QixRQUFRLEVBQUUsTUFBTSxDQUFDLFFBQVE7S0FDMUIsQ0FBQyxDQUFBO0FBQ0osQ0FBQyJ9
55
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL3ByaW50ZnVsL3N5bmMvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFhQSxvQkFpREM7QUE3REQscURBQXFFO0FBQ3JFLDJEQUE4RDtBQUU5RCx3RkFBc0U7QUFFdEU7Ozs7OztHQU1HO0FBQ0ksS0FBSyxVQUFVLElBQUksQ0FBQyxHQUFrQixFQUFFLEdBQW1CO0lBQ2hFLE1BQU0sUUFBUSxHQUEwQixHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQywwQkFBZSxDQUFDLENBQUE7SUFDMUUsTUFBTSxNQUFNLEdBQXFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUNoRSxpQ0FBeUIsQ0FBQyxNQUFNLENBQ2pDLENBQUE7SUFDRCxNQUFNLE9BQU8sR0FBRyxNQUFNLFFBQVEsQ0FBQyxVQUFVLEVBQUUsQ0FBQTtJQUUzQyxNQUFNLEtBQUssR0FDVCxPQUFPLEdBQUcsQ0FBQyxJQUFJLEtBQUssUUFBUTtRQUM1QixHQUFHLENBQUMsSUFBSTtRQUNSLE9BQU8sSUFBSSxHQUFHLENBQUMsSUFBSTtRQUNuQixHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxJQUFJO1FBQ3BCLENBQUMsQ0FBQyxNQUFNLENBQUUsR0FBRyxDQUFDLElBQTJCLENBQUMsS0FBSyxDQUFDO1FBQ2hELENBQUMsQ0FBQyxTQUFTLENBQUE7SUFFZixNQUFNLEtBQUssR0FBRyxNQUFNLFFBQVEsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLGdCQUFnQixJQUFJLEVBQUUsQ0FBQyxDQUFBO0lBRXpFLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNYLE1BQU0sT0FBTyxHQUFHLE1BQU0sUUFBUSxDQUFDLGlCQUFpQixFQUFFLENBQUE7UUFDbEQsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUM7WUFDbkIsT0FBTyxFQUFFLG9DQUFvQztZQUM3QyxlQUFlLEVBQUUsT0FBTyxFQUFFLEVBQUU7WUFDNUIsVUFBVSxFQUFFLE9BQU8sRUFBRSxVQUFVO1lBQy9CLFlBQVksRUFBRSxPQUFPLEVBQUUsWUFBWTtTQUNwQyxDQUFDLENBQUE7UUFDRixPQUFNO0lBQ1IsQ0FBQztJQUVELDJFQUEyRTtJQUMzRSwyRUFBMkU7SUFDM0UsMkVBQTJFO0lBQzNFLDZFQUE2RTtJQUM3RSwwQ0FBMEM7SUFDMUMsS0FBSyxJQUFBLHVCQUFvQixFQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUM7U0FDakMsR0FBRyxDQUFDO1FBQ0gsS0FBSyxFQUFFO1lBQ0wsV0FBVyxFQUFFLEtBQUssQ0FBQyxFQUFFO1lBQ3JCLEtBQUssRUFBRSxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDbEQ7S0FDRixDQUFDO1NBQ0QsS0FBSyxDQUFDLENBQUMsR0FBRyxFQUFFLEVBQUU7UUFDYixNQUFNLENBQUMsS0FBSyxDQUNWLGlCQUFpQixLQUFLLENBQUMsRUFBRSxxQkFDdkIsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FDakQsRUFBRSxDQUNILENBQUE7SUFDSCxDQUFDLENBQUMsQ0FBQTtJQUVKLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0FBQzdDLENBQUMifQ==
@@ -5,18 +5,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.config = void 0;
7
7
  exports.default = syncPrintfulProductsJob;
8
+ const utils_1 = require("@medusajs/framework/utils");
8
9
  const sync_products_1 = __importDefault(require("../workflows/sync-products"));
10
+ const printful_1 = require("../modules/printful");
9
11
  /**
10
12
  * Optional scheduled full sync.
11
13
  * Enable by ensuring jobs are loaded (default in plugins).
12
14
  * Schedule: daily at 03:00 UTC — adjust as needed.
13
15
  */
14
16
  async function syncPrintfulProductsJob(container) {
15
- const logger = container.resolve("logger");
17
+ const logger = container.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
18
+ const printful = container.resolve(printful_1.PRINTFUL_MODULE);
19
+ const options = await printful.getOptions();
20
+ const claim = await printful.claimSyncLog(options.syncStaleMinutes ?? 60);
21
+ if (!claim) {
22
+ // A manual sync is already covering the catalog. Not a problem.
23
+ logger.info("Printful scheduled sync skipped: another sync is running");
24
+ return;
25
+ }
16
26
  logger.info("Printful scheduled sync starting");
17
27
  try {
28
+ // Awaited on purpose, unlike the admin route in
29
+ // src/api/admin/printful/sync/route.ts. That route must answer 202 without
30
+ // waiting for a 500-product catalog; this job serves no HTTP response, and
31
+ // awaiting is what gives it counters worth logging.
18
32
  const { result } = await (0, sync_products_1.default)(container).run({
19
- input: {},
33
+ input: { sync_log_id: claim.id },
20
34
  });
21
35
  logger.info(`Printful scheduled sync done: created=${result.counters.created} updated=${result.counters.updated} failed=${result.counters.failed}`);
22
36
  }
@@ -28,4 +42,4 @@ exports.config = {
28
42
  name: "printful-sync-products",
29
43
  schedule: "0 3 * * *",
30
44
  };
31
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1wcm9kdWN0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9qb2JzL3N5bmMtcHJvZHVjdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBUUEsMENBdUJDO0FBOUJELCtFQUE2RDtBQUU3RDs7OztHQUlHO0FBQ1ksS0FBSyxVQUFVLHVCQUF1QixDQUNuRCxTQUEwQjtJQUUxQixNQUFNLE1BQU0sR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FHeEMsQ0FBQTtJQUVELE1BQU0sQ0FBQyxJQUFJLENBQUMsa0NBQWtDLENBQUMsQ0FBQTtJQUMvQyxJQUFJLENBQUM7UUFDSCxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsTUFBTSxJQUFBLHVCQUFvQixFQUFDLFNBQVMsQ0FBQyxDQUFDLEdBQUcsQ0FBQztZQUMzRCxLQUFLLEVBQUUsRUFBRTtTQUNWLENBQUMsQ0FBQTtRQUNGLE1BQU0sQ0FBQyxJQUFJLENBQ1QseUNBQXlDLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxZQUFZLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxXQUFXLE1BQU0sQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLENBQ3ZJLENBQUE7SUFDSCxDQUFDO0lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztRQUNiLE1BQU0sQ0FBQyxLQUFLLENBQ1YsbUNBQ0UsR0FBRyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FDakQsRUFBRSxDQUNILENBQUE7SUFDSCxDQUFDO0FBQ0gsQ0FBQztBQUVZLFFBQUEsTUFBTSxHQUFHO0lBQ3BCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsUUFBUSxFQUFFLFdBQVc7Q0FDdEIsQ0FBQSJ9
45
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3luYy1wcm9kdWN0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9qb2JzL3N5bmMtcHJvZHVjdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBV0EsMENBcUNDO0FBL0NELHFEQUFxRTtBQUNyRSwrRUFBNkQ7QUFDN0Qsa0RBQXFEO0FBR3JEOzs7O0dBSUc7QUFDWSxLQUFLLFVBQVUsdUJBQXVCLENBQ25ELFNBQTBCO0lBRTFCLE1BQU0sTUFBTSxHQUdSLFNBQVMsQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsTUFBTSxDQUFDLENBQUE7SUFFdkQsTUFBTSxRQUFRLEdBQTBCLFNBQVMsQ0FBQyxPQUFPLENBQUMsMEJBQWUsQ0FBQyxDQUFBO0lBQzFFLE1BQU0sT0FBTyxHQUFHLE1BQU0sUUFBUSxDQUFDLFVBQVUsRUFBRSxDQUFBO0lBRTNDLE1BQU0sS0FBSyxHQUFHLE1BQU0sUUFBUSxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsZ0JBQWdCLElBQUksRUFBRSxDQUFDLENBQUE7SUFDekUsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ1gsZ0VBQWdFO1FBQ2hFLE1BQU0sQ0FBQyxJQUFJLENBQUMsMERBQTBELENBQUMsQ0FBQTtRQUN2RSxPQUFNO0lBQ1IsQ0FBQztJQUVELE1BQU0sQ0FBQyxJQUFJLENBQUMsa0NBQWtDLENBQUMsQ0FBQTtJQUMvQyxJQUFJLENBQUM7UUFDSCxnREFBZ0Q7UUFDaEQsMkVBQTJFO1FBQzNFLDJFQUEyRTtRQUMzRSxvREFBb0Q7UUFDcEQsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLE1BQU0sSUFBQSx1QkFBb0IsRUFBQyxTQUFTLENBQUMsQ0FBQyxHQUFHLENBQUM7WUFDM0QsS0FBSyxFQUFFLEVBQUUsV0FBVyxFQUFFLEtBQUssQ0FBQyxFQUFFLEVBQUU7U0FDakMsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxDQUFDLElBQUksQ0FDVCx5Q0FBeUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUFPLFlBQVksTUFBTSxDQUFDLFFBQVEsQ0FBQyxPQUFPLFdBQVcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FDdkksQ0FBQTtJQUNILENBQUM7SUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1FBQ2IsTUFBTSxDQUFDLEtBQUssQ0FDVixtQ0FDRSxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUNqRCxFQUFFLENBQ0gsQ0FBQTtJQUNILENBQUM7QUFDSCxDQUFDO0FBRVksUUFBQSxNQUFNLEdBQUc7SUFDcEIsSUFBSSxFQUFFLHdCQUF3QjtJQUM5QixRQUFRLEVBQUUsV0FBVztDQUN0QixDQUFBIn0=
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Migration20260802000000 = void 0;
4
+ const migrations_1 = require("@medusajs/framework/mikro-orm/migrations");
5
+ class Migration20260802000000 extends migrations_1.Migration {
6
+ async up() {
7
+ this.addSql(`
8
+ alter table "printful_sync_log"
9
+ add column if not exists "heartbeat_at" timestamptz null,
10
+ add column if not exists "products_processed" integer not null default 0,
11
+ add column if not exists "products_total" integer not null default 0;
12
+ `);
13
+ // At most one running sync, whatever the state of finished ones. Indexing
14
+ // the constant `true` under a status predicate is what makes the claim
15
+ // atomic: a second concurrent claim collides here rather than racing
16
+ // between a read and a write.
17
+ this.addSql(`
18
+ create unique index if not exists "IDX_printful_sync_log_one_running"
19
+ on "printful_sync_log" ((true))
20
+ where status = 'running' and deleted_at is null;
21
+ `);
22
+ this.addSql(`
23
+ create index if not exists "IDX_printful_sync_log_heartbeat"
24
+ on "printful_sync_log" ("heartbeat_at")
25
+ where status = 'running' and deleted_at is null;
26
+ `);
27
+ }
28
+ async down() {
29
+ this.addSql(`drop index if exists "IDX_printful_sync_log_heartbeat";`);
30
+ this.addSql(`drop index if exists "IDX_printful_sync_log_one_running";`);
31
+ this.addSql(`
32
+ alter table "printful_sync_log"
33
+ drop column if exists "products_total",
34
+ drop column if exists "products_processed",
35
+ drop column if exists "heartbeat_at";
36
+ `);
37
+ }
38
+ }
39
+ exports.Migration20260802000000 = Migration20260802000000;
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNjA4MDIwMDAwMDAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9wcmludGZ1bC9taWdyYXRpb25zL01pZ3JhdGlvbjIwMjYwODAyMDAwMDAwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHlFQUFvRTtBQUVwRSxNQUFhLHVCQUF3QixTQUFRLHNCQUFTO0lBQzNDLEtBQUssQ0FBQyxFQUFFO1FBQ2YsSUFBSSxDQUFDLE1BQU0sQ0FBQzs7Ozs7S0FLWCxDQUFDLENBQUE7UUFFRiwwRUFBMEU7UUFDMUUsdUVBQXVFO1FBQ3ZFLHFFQUFxRTtRQUNyRSw4QkFBOEI7UUFDOUIsSUFBSSxDQUFDLE1BQU0sQ0FBQzs7OztLQUlYLENBQUMsQ0FBQTtRQUVGLElBQUksQ0FBQyxNQUFNLENBQUM7Ozs7S0FJWCxDQUFDLENBQUE7SUFDSixDQUFDO0lBRVEsS0FBSyxDQUFDLElBQUk7UUFDakIsSUFBSSxDQUFDLE1BQU0sQ0FBQyx5REFBeUQsQ0FBQyxDQUFBO1FBQ3RFLElBQUksQ0FBQyxNQUFNLENBQUMsMkRBQTJELENBQUMsQ0FBQTtRQUN4RSxJQUFJLENBQUMsTUFBTSxDQUFDOzs7OztLQUtYLENBQUMsQ0FBQTtJQUNKLENBQUM7Q0FDRjtBQXBDRCwwREFvQ0MifQ==
@@ -10,6 +10,9 @@ const PrintfulSyncLog = utils_1.model.define("printful_sync_log", {
10
10
  products_updated: utils_1.model.number().default(0),
11
11
  products_failed: utils_1.model.number().default(0),
12
12
  error_message: utils_1.model.text().nullable(),
13
+ heartbeat_at: utils_1.model.dateTime().nullable(),
14
+ products_processed: utils_1.model.number().default(0),
15
+ products_total: utils_1.model.number().default(0),
13
16
  });
14
17
  exports.default = PrintfulSyncLog;
15
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbnRmdWwtc3luYy1sb2cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9wcmludGZ1bC9tb2RlbHMvcHJpbnRmdWwtc3luYy1sb2cudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxREFBaUQ7QUFFakQsTUFBTSxlQUFlLEdBQUcsYUFBSyxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsRUFBRTtJQUN4RCxFQUFFLEVBQUUsYUFBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLFVBQVUsRUFBRTtJQUMzQixNQUFNLEVBQUUsYUFBSyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUM7SUFDdkMsVUFBVSxFQUFFLGFBQUssQ0FBQyxRQUFRLEVBQUU7SUFDNUIsV0FBVyxFQUFFLGFBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDeEMsZ0JBQWdCLEVBQUUsYUFBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDM0MsZ0JBQWdCLEVBQUUsYUFBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDM0MsZUFBZSxFQUFFLGFBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQzFDLGFBQWEsRUFBRSxhQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsUUFBUSxFQUFFO0NBQ3ZDLENBQUMsQ0FBQTtBQUVGLGtCQUFlLGVBQWUsQ0FBQSJ9
18
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbnRmdWwtc3luYy1sb2cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9wcmludGZ1bC9tb2RlbHMvcHJpbnRmdWwtc3luYy1sb2cudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxxREFBaUQ7QUFFakQsTUFBTSxlQUFlLEdBQUcsYUFBSyxDQUFDLE1BQU0sQ0FBQyxtQkFBbUIsRUFBRTtJQUN4RCxFQUFFLEVBQUUsYUFBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLFVBQVUsRUFBRTtJQUMzQixNQUFNLEVBQUUsYUFBSyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUM7SUFDdkMsVUFBVSxFQUFFLGFBQUssQ0FBQyxRQUFRLEVBQUU7SUFDNUIsV0FBVyxFQUFFLGFBQUssQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDeEMsZ0JBQWdCLEVBQUUsYUFBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDM0MsZ0JBQWdCLEVBQUUsYUFBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDM0MsZUFBZSxFQUFFLGFBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQzFDLGFBQWEsRUFBRSxhQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQ3RDLFlBQVksRUFBRSxhQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQ3pDLGtCQUFrQixFQUFFLGFBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQzdDLGNBQWMsRUFBRSxhQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQztDQUMxQyxDQUFDLENBQUE7QUFFRixrQkFBZSxlQUFlLENBQUEifQ==
@@ -241,6 +241,72 @@ class PrintfulModuleService extends (0, utils_1.MedusaService)({
241
241
  const logs = await this.listPrintfulSyncLogs({}, { take: 1, order: { started_at: "DESC" } });
242
242
  return logs[0] ?? null;
243
243
  }
244
+ /**
245
+ * Mark abandoned running syncs as failed.
246
+ *
247
+ * One statement whose predicate does the selecting, so there is no
248
+ * read-then-decide window. A row is abandoned when its heartbeat is older
249
+ * than the stale window, or when it never got one.
250
+ */
251
+ async reapStaleSyncLogs(staleMinutes) {
252
+ const cutoff = new Date(Date.now() - staleMinutes * 60 * 1000);
253
+ const stale = await this.listPrintfulSyncLogs({
254
+ status: "running",
255
+ $or: [{ heartbeat_at: { $lt: cutoff } }, { heartbeat_at: null }],
256
+ });
257
+ for (const log of stale) {
258
+ await this.updatePrintfulSyncLogs({
259
+ id: log.id,
260
+ status: "failed",
261
+ error_message: "stale_running",
262
+ finished_at: new Date(),
263
+ });
264
+ }
265
+ return stale.length;
266
+ }
267
+ /**
268
+ * Claim the right to run a sync.
269
+ *
270
+ * Returns the new log on success, or null when another sync already holds
271
+ * the claim. The unique index on `status = 'running'` is what makes this
272
+ * atomic — a second concurrent claim collides in the database rather than
273
+ * racing between a read and a write.
274
+ */
275
+ async claimSyncLog(staleMinutes) {
276
+ await this.reapStaleSyncLogs(staleMinutes);
277
+ const now = new Date();
278
+ try {
279
+ return await this.createPrintfulSyncLogs({
280
+ status: "running",
281
+ started_at: now,
282
+ heartbeat_at: now,
283
+ products_created: 0,
284
+ products_updated: 0,
285
+ products_failed: 0,
286
+ products_processed: 0,
287
+ products_total: 0,
288
+ });
289
+ }
290
+ catch (err) {
291
+ if (isUniqueViolation(err)) {
292
+ return null;
293
+ }
294
+ throw err;
295
+ }
296
+ }
297
+ /** The sync currently holding the claim, for a 409 body. */
298
+ async getRunningSyncLog() {
299
+ const [log] = await this.listPrintfulSyncLogs({ status: "running" });
300
+ return log ?? null;
301
+ }
302
+ /** Refresh the claim so it is not reaped, and report progress. */
303
+ async heartbeatSyncLog(id, progress = {}) {
304
+ return this.updatePrintfulSyncLogs({
305
+ id,
306
+ heartbeat_at: new Date(),
307
+ ...progress,
308
+ });
309
+ }
244
310
  }
245
311
  exports.default = PrintfulModuleService;
246
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL3ByaW50ZnVsL3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBZ0RBLDRDQUlDO0FBYUQsZ0NBR0M7QUFpQkQsOENBMkJDO0FBaEhELHFEQUF5RDtBQUN6RCxtQ0FBbUM7QUFDbkMsaUVBQTREO0FBRTVELHVGQUE0RDtBQUM1RCwyRkFBZ0U7QUFDaEUsbUZBQXdEO0FBQ3hELDJGQUFnRTtBQUNoRSw2RkFBa0U7QUEyQmxFOzs7Ozs7R0FNRztBQUNVLFFBQUEseUJBQXlCLEdBQUcsU0FBUyxDQUFBO0FBRWxELGdFQUFnRTtBQUNuRCxRQUFBLG9CQUFvQixHQUFHLEVBQUUsQ0FBQTtBQUV0QyxrRUFBa0U7QUFDbEUsU0FBZ0IsZ0JBQWdCLENBQUMsUUFBZ0I7SUFDL0MsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUE7SUFDbEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFBO0lBQ3BDLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsUUFBUSxDQUFDLEVBQUUsU0FBUyxDQUFDLENBQUE7QUFDbEUsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFnQixVQUFVLENBQUMsZUFBdUI7SUFDaEQsTUFBTSxNQUFNLEdBQUcsSUFBQSxtQkFBVSxFQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQTtJQUM1RSxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDOUIsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsU0FBZ0IsaUJBQWlCLENBQUMsR0FBWTtJQUM1QyxJQUFJLENBQUMsR0FBRyxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ3BDLE9BQU8sS0FBSyxDQUFBO0lBQ2QsQ0FBQztJQUNELE1BQU0sQ0FBQyxHQUFHLEdBS1QsQ0FBQTtJQUNELElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyxPQUFPLEVBQUUsQ0FBQztRQUN2QixPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFDRCxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sS0FBSyxRQUFRLElBQUksQ0FBQyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNqRSxPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7SUFDRCxJQUNFLENBQUMsQ0FBQyxJQUFJLEtBQUssY0FBYztRQUN6QixPQUFPLENBQUMsQ0FBQyxPQUFPLEtBQUssUUFBUTtRQUM3QixxQkFBcUIsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxFQUM1QyxDQUFDO1FBQ0QsT0FBTyxJQUFJLENBQUE7SUFDYixDQUFDO0lBQ0QsSUFBSSxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQyxLQUFLLEtBQUssR0FBRyxFQUFFLENBQUM7UUFDL0IsT0FBTyxpQkFBaUIsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDbkMsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFBO0FBQ2QsQ0FBQztBQUVELE1BQU0scUJBQXNCLFNBQVEsSUFBQSxxQkFBYSxFQUFDO0lBQ2hELG1CQUFtQixFQUFuQiwrQkFBbUI7SUFDbkIsbUJBQW1CLEVBQW5CLCtCQUFtQjtJQUNuQixlQUFlLEVBQWYsMkJBQWU7SUFDZixpQkFBaUIsRUFBakIsNkJBQWlCO0lBQ2pCLG9CQUFvQixFQUFwQixnQ0FBb0I7Q0FDckIsQ0FBQztJQU1BLFlBQ0UsU0FBK0IsRUFDL0IsT0FBK0I7UUFFL0Isc0NBQXNDO1FBQ3RDLEtBQUssQ0FBQyxHQUFHLFNBQVMsQ0FBQyxDQUFBO1FBVFgsWUFBTyxHQUEwQixJQUFJLENBQUE7UUFVN0MsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLElBQUssRUFBNEIsQ0FBQTtJQUMxRCxDQUFDO0lBRUQsS0FBSyxDQUFDLFVBQVU7UUFDZCxPQUFPLElBQUksQ0FBQyxRQUFRLENBQUE7SUFDdEIsQ0FBQztJQUVELEtBQUssQ0FBQyxTQUFTO1FBQ2IsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNsQixJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxRQUFRLEVBQUUsQ0FBQztnQkFDN0IsTUFBTSxJQUFJLEtBQUssQ0FDYiw4RUFBOEUsQ0FDL0UsQ0FBQTtZQUNILENBQUM7WUFDRCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksZ0NBQWMsQ0FBQztnQkFDaEMsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUTtnQkFDaEMsT0FBTyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTzthQUMvQixDQUFDLENBQUE7UUFDSixDQUFDO1FBQ0QsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3JCLENBQUM7SUFFRCxLQUFLLENBQUMsVUFBVTtRQUNkLE9BQU8sSUFBSSxDQUFDLFFBQVEsRUFBRSxPQUFPLElBQUksU0FBUyxDQUFBO0lBQzVDLENBQUM7SUFFRCxLQUFLLENBQUMsZUFBZSxDQUFDLGFBQXFCO1FBQ3pDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFBO1FBQ3ZDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FBQztZQUNqRCxpQkFBaUIsRUFBRSxPQUFPO1lBQzFCLHdCQUF3QixFQUFFLE1BQU0sQ0FBQyxhQUFhLENBQUM7U0FDaEQsQ0FBQyxDQUFBO1FBQ0YsT0FBTyxJQUFJLElBQUksSUFBSSxDQUFBO0lBQ3JCLENBQUM7SUFFRCxLQUFLLENBQUMsZUFBZSxDQUFDLGFBQXFCO1FBQ3pDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFBO1FBQ3ZDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxNQUFNLElBQUksQ0FBQyx3QkFBd0IsQ0FBQztZQUNqRCxpQkFBaUIsRUFBRSxPQUFPO1lBQzFCLHdCQUF3QixFQUFFLE1BQU0sQ0FBQyxhQUFhLENBQUM7U0FDaEQsQ0FBQyxDQUFBO1FBQ0YsT0FBTyxJQUFJLElBQUksSUFBSSxDQUFBO0lBQ3JCLENBQUM7SUFFRCxLQUFLLENBQUMseUJBQXlCLENBQUMsZUFBdUI7UUFDckQsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUFDO1lBQ2pELGlCQUFpQixFQUFFLGVBQWU7U0FDbkMsQ0FBQyxDQUFBO1FBQ0YsT0FBTyxJQUFJLElBQUksSUFBSSxDQUFBO0lBQ3JCLENBQUM7SUFFRCxLQUFLLENBQUMsYUFBYSxDQUFDLGFBQXFCO1FBQ3ZDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxNQUFNLElBQUksQ0FBQyxzQkFBc0IsQ0FBQztZQUMvQyxlQUFlLEVBQUUsYUFBYTtTQUMvQixDQUFDLENBQUE7UUFDRixPQUFPLElBQUksSUFBSSxJQUFJLENBQUE7SUFDckIsQ0FBQztJQUVELDhFQUE4RTtJQUM5RSxLQUFLLENBQUMseUJBQXlCLENBQUMsZUFBdUI7UUFDckQsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLE1BQU0sSUFBSSxDQUFDLHNCQUFzQixDQUFDO1lBQy9DLGlCQUFpQixFQUFFLE1BQU0sQ0FBQyxlQUFlLENBQUM7U0FDM0MsQ0FBQyxDQUFBO1FBQ0YsT0FBTyxJQUFJLElBQUksSUFBSSxDQUFBO0lBQ3JCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsa0JBQWtCLENBQUMsS0FPeEI7UUFDQyxJQUFJLENBQUM7WUFDSCxPQUFPLE1BQU0sSUFBSSxDQUFDLDJCQUEyQixDQUFDO2dCQUM1QyxRQUFRLEVBQUUsS0FBSyxDQUFDLFFBQVE7Z0JBQ3hCLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSTtnQkFDaEIsaUJBQWlCLEVBQUUsS0FBSyxDQUFDLGlCQUFpQjtnQkFDMUMsb0JBQW9CLEVBQUUsS0FBSyxDQUFDLG9CQUFvQixJQUFJLElBQUk7Z0JBQ3hELE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTztnQkFDdEIsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLElBQUksVUFBVTtnQkFDbEMsUUFBUSxFQUFFLENBQUM7Z0JBQ1gsYUFBYSxFQUFFLElBQUksSUFBSSxFQUFFO2FBQzFCLENBQUMsQ0FBQTtRQUNKLENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IsSUFBSSxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUMzQixPQUFPLElBQUksQ0FBQTtZQUNiLENBQUM7WUFDRCxNQUFNLEdBQUcsQ0FBQTtRQUNYLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLGdCQUFnQixDQUFDLE9BQWU7UUFDcEMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLE1BQU0sSUFBSSxDQUFDLHlCQUF5QixDQUFDLEVBQUUsUUFBUSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUE7UUFDM0UsT0FBTyxLQUFLLElBQUksSUFBSSxDQUFBO0lBQ3RCLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsS0FBSyxDQUFDLG9CQUFvQixDQUFDLEtBQUssR0FBRyxFQUFFO1FBQ25DLE9BQU8sSUFBSSxDQUFDLHlCQUF5QixDQUNuQztZQUNFLE1BQU0sRUFBRSxDQUFDLFVBQVUsRUFBRSxVQUFVLENBQUM7WUFDaEMsYUFBYSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksSUFBSSxFQUFFLEVBQUU7WUFDbkMsUUFBUSxFQUFFLEVBQUUsR0FBRyxFQUFFLDRCQUFvQixFQUFFO1NBQ3hDLEVBQ0QsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxFQUFFLGFBQWEsRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUNqRCxDQUFBO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7Ozs7O09BZ0JHO0lBQ0gsS0FBSyxDQUFDLGFBQWEsQ0FDakIsZUFBdUIsRUFDdkIsRUFBb0I7UUFFcEIsTUFBTSxHQUFHLEdBQUcsVUFBVSxDQUFDLGVBQWUsQ0FBQyxDQUFBO1FBQ3ZDLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBQyxXQUFXLENBQ3JDLEtBQUssRUFBRSxTQUF5QixFQUFFLEVBQUU7WUFDbEMsTUFBTSxTQUFTLENBQUMsT0FBTyxDQUFDLGlDQUFpQyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQTtZQUNqRSxPQUFPLE1BQU0sRUFBRSxFQUFFLENBQUE7UUFDbkIsQ0FBQyxDQUNGLENBQUE7SUFDSCxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxjQUFjLENBQUMsYUFBcUI7UUFJeEMsSUFBSSxDQUFDO1lBQ0gsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsd0JBQXdCLENBQUM7Z0JBQy9DLGVBQWUsRUFBRSxhQUFhO2dCQUM5QixpQkFBaUIsRUFBRSxpQ0FBeUI7Z0JBQzVDLFdBQVcsRUFBRSxhQUFhO2dCQUMxQixNQUFNLEVBQUUsU0FBUzthQUNsQixDQUFDLENBQUE7WUFDRixPQUFPLElBQUksQ0FBQTtRQUNiLENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IsSUFBSSxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUMzQixPQUFPLElBQUksQ0FBQTtZQUNiLENBQUM7WUFDRCxNQUFNLEdBQUcsQ0FBQTtRQUNYLENBQUM7SUFDSCxDQUFDO0lBRUQsS0FBSyxDQUFDLGdCQUFnQjtRQUNwQixNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxvQkFBb0IsQ0FDMUMsRUFBRSxFQUNGLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxLQUFLLEVBQUUsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLEVBQUUsQ0FDM0MsQ0FBQTtRQUNELE9BQU8sSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQTtJQUN4QixDQUFDO0NBQ0Y7QUFFRCxrQkFBZSxxQkFBcUIsQ0FBQSJ9
312
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL3ByaW50ZnVsL3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBZ0RBLDRDQUlDO0FBYUQsZ0NBR0M7QUFpQkQsOENBMkJDO0FBaEhELHFEQUF5RDtBQUN6RCxtQ0FBbUM7QUFDbkMsaUVBQTREO0FBRTVELHVGQUE0RDtBQUM1RCwyRkFBZ0U7QUFDaEUsbUZBQXdEO0FBQ3hELDJGQUFnRTtBQUNoRSw2RkFBa0U7QUEyQmxFOzs7Ozs7R0FNRztBQUNVLFFBQUEseUJBQXlCLEdBQUcsU0FBUyxDQUFBO0FBRWxELGdFQUFnRTtBQUNuRCxRQUFBLG9CQUFvQixHQUFHLEVBQUUsQ0FBQTtBQUV0QyxrRUFBa0U7QUFDbEUsU0FBZ0IsZ0JBQWdCLENBQUMsUUFBZ0I7SUFDL0MsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUE7SUFDbEMsTUFBTSxTQUFTLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFBO0lBQ3BDLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsUUFBUSxDQUFDLEVBQUUsU0FBUyxDQUFDLENBQUE7QUFDbEUsQ0FBQztBQUVEOzs7Ozs7Ozs7O0dBVUc7QUFDSCxTQUFnQixVQUFVLENBQUMsZUFBdUI7SUFDaEQsTUFBTSxNQUFNLEdBQUcsSUFBQSxtQkFBVSxFQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQTtJQUM1RSxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUE7QUFDOUIsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsU0FBZ0IsaUJBQWlCLENBQUMsR0FB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