medusa-plugin-ordinant 0.1.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.
Files changed (30) hide show
  1. package/.medusa/server/src/admin/index.js +1636 -0
  2. package/.medusa/server/src/admin/index.mjs +1635 -0
  3. package/.medusa/server/src/api/admin/ordinant/classifications/[sku]/confirm/route.js +25 -0
  4. package/.medusa/server/src/api/admin/ordinant/classifications/bulk-confirm/route.js +29 -0
  5. package/.medusa/server/src/api/admin/ordinant/classifications/route.js +23 -0
  6. package/.medusa/server/src/api/admin/ordinant/classify-all/route.js +108 -0
  7. package/.medusa/server/src/api/admin/ordinant/dictionary/route.js +17 -0
  8. package/.medusa/server/src/api/admin/ordinant/generate-all-skus/route.js +31 -0
  9. package/.medusa/server/src/api/admin/ordinant/overview/route.js +69 -0
  10. package/.medusa/server/src/api/admin/ordinant/products/[id]/classify/route.js +18 -0
  11. package/.medusa/server/src/api/admin/ordinant/products/[id]/generate-skus/route.js +34 -0
  12. package/.medusa/server/src/api/admin/ordinant/products/[id]/overview/route.js +59 -0
  13. package/.medusa/server/src/api/admin/ordinant/stats/route.js +17 -0
  14. package/.medusa/server/src/api/admin/ordinant/status/route.js +52 -0
  15. package/.medusa/server/src/api/middlewares.js +14 -0
  16. package/.medusa/server/src/api/store/ordinant/preview/route.js +110 -0
  17. package/.medusa/server/src/api/store/ordinant/preview/validators.js +34 -0
  18. package/.medusa/server/src/lib/client.js +136 -0
  19. package/.medusa/server/src/modules/ordinant/index.js +14 -0
  20. package/.medusa/server/src/modules/ordinant/service.js +27 -0
  21. package/.medusa/server/src/storefront.d.ts +61 -0
  22. package/.medusa/server/src/storefront.js +61 -0
  23. package/.medusa/server/src/subscribers/product-upsert.js +52 -0
  24. package/.medusa/server/src/workflows/bulk-operations.js +59 -0
  25. package/.medusa/server/src/workflows/classify-product.js +80 -0
  26. package/.medusa/server/src/workflows/confirm-classification.js +20 -0
  27. package/.medusa/server/src/workflows/generate-skus.js +81 -0
  28. package/.medusa/server/src/workflows/hooks/validate-cart-completion.js +127 -0
  29. package/README.md +495 -0
  30. package/package.json +92 -0
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const ordinant_1 = require("../../../../modules/ordinant");
6
+ /** A "nothing to check yet" answer — advisory previews always resolve to a
7
+ * clear result rather than an error, so a checkout step never breaks on them. */
8
+ const clear = (skipped) => ({
9
+ preview: true,
10
+ disposition: "ALLOW",
11
+ items: [],
12
+ ...(skipped ? { skipped } : {}),
13
+ });
14
+ /**
15
+ * POST /store/ordinant/preview
16
+ *
17
+ * Advisory, non-logging compliance preview for the storefront. Given a cart id
18
+ * (and any buyer assertions gathered so far), it resolves the cart's items and
19
+ * destination server-side and returns what the checkout gate WOULD decide — so
20
+ * the UI can warn early (e.g. when the shipping state is entered) instead of
21
+ * only at "Place Order".
22
+ *
23
+ * `destination` may be passed to check a PROPOSED address that hasn't been
24
+ * saved to the cart yet — this lets the storefront reject a blocked address
25
+ * before it's ever persisted. When omitted, the cart's saved shipping address
26
+ * is used.
27
+ *
28
+ * Opt-in and advisory: it never blocks and fails open. The authoritative,
29
+ * non-bypassable gate is the completeCart hook (validate-cart-completion).
30
+ */
31
+ async function POST(req, res) {
32
+ const { cart_id, buyer, destination } = req.validatedBody;
33
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
34
+ if (!ordinant.configured) {
35
+ res.json(clear("not_configured"));
36
+ return;
37
+ }
38
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
39
+ const { data } = await query.graph({
40
+ entity: "cart",
41
+ filters: { id: cart_id },
42
+ fields: [
43
+ "id",
44
+ "metadata",
45
+ "shipping_address.country_code",
46
+ "shipping_address.province",
47
+ "shipping_address.postal_code",
48
+ "shipping_address.city",
49
+ "shipping_address.address_1",
50
+ "items.variant_sku",
51
+ "items.quantity",
52
+ ],
53
+ });
54
+ const cart = data?.[0];
55
+ if (!cart) {
56
+ res.json(clear("cart_not_found"));
57
+ return;
58
+ }
59
+ // Prefer the proposed destination (a not-yet-saved address) over the cart's
60
+ // saved one, so the storefront can vet an address before persisting it.
61
+ const sa = cart.shipping_address;
62
+ const country = (destination?.country_code ?? sa?.country_code ?? "").toLowerCase();
63
+ const state = (destination?.state ?? sa?.province ?? "").toUpperCase();
64
+ // US-only model; nothing to preview until a state is known.
65
+ if (country && country !== "us") {
66
+ res.json(clear("non_us"));
67
+ return;
68
+ }
69
+ if (!state) {
70
+ res.json(clear("no_state"));
71
+ return;
72
+ }
73
+ const items = (cart.items ?? [])
74
+ .filter((i) => i?.variant_sku)
75
+ .map((i) => ({
76
+ sku: i.variant_sku,
77
+ quantity: i.quantity ?? 1,
78
+ attributes: {},
79
+ }));
80
+ if (items.length === 0) {
81
+ res.json(clear());
82
+ return;
83
+ }
84
+ // Assertions already on the cart, overlaid with whatever the caller just
85
+ // gathered this step.
86
+ const cartBuyer = (cart.metadata?.ordinant_buyer ?? {});
87
+ const mergedBuyer = { ...cartBuyer, ...(buyer ?? {}) };
88
+ try {
89
+ const decision = await ordinant.client.previewDecision({
90
+ destination: {
91
+ state,
92
+ zip: destination?.zip ?? sa?.postal_code ?? undefined,
93
+ city: destination?.city ?? sa?.city ?? undefined,
94
+ line1: destination?.line1 ?? sa?.address_1 ?? undefined,
95
+ },
96
+ buyer: mergedBuyer,
97
+ items,
98
+ },
99
+ // Keyed per cart so a blocked attempt is counted once for the merchant's
100
+ // stopped-orders metric, no matter how many times the address step
101
+ // re-previews it.
102
+ `preview-block:${cart_id}`);
103
+ res.json(decision);
104
+ }
105
+ catch {
106
+ // Advisory only: never fail a checkout step because the preview is down.
107
+ res.json(clear("preview_unavailable"));
108
+ }
109
+ }
110
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL29yZGluYW50L3ByZXZpZXcvcm91dGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFpQ0Esb0JBc0ZDO0FBdEhELHFEQUFxRTtBQUNyRSwyREFBOEQ7QUFLOUQ7aUZBQ2lGO0FBQ2pGLE1BQU0sS0FBSyxHQUFHLENBQUMsT0FBZ0IsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUNuQyxPQUFPLEVBQUUsSUFBYTtJQUN0QixXQUFXLEVBQUUsT0FBZ0I7SUFDN0IsS0FBSyxFQUFFLEVBQWU7SUFDdEIsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0NBQ2hDLENBQUMsQ0FBQTtBQUVGOzs7Ozs7Ozs7Ozs7Ozs7O0dBZ0JHO0FBQ0ksS0FBSyxVQUFVLElBQUksQ0FBQyxHQUFpQyxFQUFFLEdBQW1CO0lBQy9FLE1BQU0sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRSxHQUFHLEdBQUcsQ0FBQyxhQUFhLENBQUE7SUFDekQsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQXdCLDBCQUFlLENBQUMsQ0FBQTtJQUUxRSxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ3pCLEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQTtRQUNqQyxPQUFNO0lBQ1IsQ0FBQztJQUVELE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFBO0lBQ2hFLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxNQUFNLEtBQUssQ0FBQyxLQUFLLENBQUM7UUFDakMsTUFBTSxFQUFFLE1BQU07UUFDZCxPQUFPLEVB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
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PreviewSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /** Body for POST /store/ordinant/preview. `cart_id` identifies the cart to
6
+ * check; `buyer` carries any verification assertions gathered so far (merged
7
+ * over whatever is already on the cart). */
8
+ exports.PreviewSchema = zod_1.z.object({
9
+ cart_id: zod_1.z.string().min(1),
10
+ /** A proposed destination to check before it's saved to the cart. When
11
+ * omitted, the cart's saved shipping address is used. */
12
+ destination: zod_1.z
13
+ .object({
14
+ state: zod_1.z.string(),
15
+ country_code: zod_1.z.string(),
16
+ zip: zod_1.z.string(),
17
+ city: zod_1.z.string(),
18
+ line1: zod_1.z.string(),
19
+ })
20
+ .partial()
21
+ .optional(),
22
+ buyer: zod_1.z
23
+ .object({
24
+ age_verified: zod_1.z.boolean(),
25
+ verified_age: zod_1.z.number(),
26
+ ffl_verified: zod_1.z.boolean(),
27
+ sot_verified: zod_1.z.boolean(),
28
+ permits: zod_1.z.array(zod_1.z.string()),
29
+ fulfillment_method: zod_1.z.enum(["ship", "in_store_pickup"]),
30
+ })
31
+ .partial()
32
+ .optional(),
33
+ });
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9ycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uLy4uLy4uL3NyYy9hcGkvc3RvcmUvb3JkaW5hbnQvcHJldmlldy92YWxpZGF0b3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDZCQUF1QjtBQUV2Qjs7NENBRTRDO0FBQy9CLFFBQUEsYUFBYSxHQUFHLE9BQUMsQ0FBQyxNQUFNLENBQUM7SUFDcEMsT0FBTyxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQzFCOzZEQUN5RDtJQUN6RCxXQUFXLEVBQUUsT0FBQztTQUNYLE1BQU0sQ0FBQztRQUNOLEtBQUssRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFO1FBQ2pCLFlBQVksRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFO1FBQ3hCLEdBQUcsRUFBRSxPQUFDLENBQUMsTUFBTSxFQUFFO1FBQ2YsSUFBSSxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUU7UUFDaEIsS0FBSyxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUU7S0FDbEIsQ0FBQztTQUNELE9BQU8sRUFBRTtTQUNULFFBQVEsRUFBRTtJQUNiLEtBQUssRUFBRSxPQUFDO1NBQ0wsTUFBTSxDQUFDO1FBQ04sWUFBWSxFQUFFLE9BQUMsQ0FBQyxPQUFPLEVBQUU7UUFDekIsWUFBWSxFQUFFLE9BQUMsQ0FBQyxNQUFNLEVBQUU7UUFDeEIsWUFBWSxFQUFFLE9BQUMsQ0FBQyxPQUFPLEVBQUU7UUFDekIsWUFBWSxFQUFFLE9BQUMsQ0FBQyxPQUFPLEVBQUU7UUFDekIsT0FBTyxFQUFFLE9BQUMsQ0FBQyxLQUFLLENBQUMsT0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQzVCLGtCQUFrQixFQUFFLE9BQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxNQUFNLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztLQUN4RCxDQUFDO1NBQ0QsT0FBTyxFQUFFO1NBQ1QsUUFBUSxFQUFFO0NBQ2QsQ0FBQyxDQUFBIn0=
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ /**
3
+ * Thin typed client for the Ordinant decision API. The plugin never
4
+ * interprets compliance logic itself — determinism lives server-side; this
5
+ * client just carries requests and citations.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.OrdinantClient = exports.OrdinantApiError = void 0;
9
+ class OrdinantApiError extends Error {
10
+ constructor(message, code, status) {
11
+ super(message);
12
+ this.name = "OrdinantApiError";
13
+ this.code = code;
14
+ this.status = status;
15
+ }
16
+ }
17
+ exports.OrdinantApiError = OrdinantApiError;
18
+ class OrdinantClient {
19
+ constructor(options) {
20
+ this.configured = Boolean(options.baseUrl && options.apiKey);
21
+ this.baseUrl = (options.baseUrl ?? "").replace(/\/+$/, "");
22
+ this.apiKey = options.apiKey ?? "";
23
+ this.timeoutMs = options.timeoutMs ?? 5000;
24
+ this.catalogTimeoutMs = options.catalogTimeoutMs ?? 120_000;
25
+ }
26
+ /** Blocked-order metrics for the merchant tied to this API key. */
27
+ async blockStats() {
28
+ return this.call("GET", "/v1/stats/blocks");
29
+ }
30
+ /** Current ruleset summary — a cheap authenticated probe used by the
31
+ * dashboard's readiness check. */
32
+ async currentRuleset() {
33
+ return this.call("GET", "/v1/rulesets/current");
34
+ }
35
+ async decide(request, idempotencyKey) {
36
+ const headers = {};
37
+ if (idempotencyKey) {
38
+ headers["Idempotency-Key"] = idempotencyKey;
39
+ }
40
+ return this.call("POST", "/v1/decisions", request, headers);
41
+ }
42
+ /** Preview a decision without logging it — for surfacing compliance early in
43
+ * checkout (advisory). Never use this to gate an order; the authoritative,
44
+ * logged decision is `decide()`. Passing `idempotencyKey` (keyed per cart)
45
+ * records a BLOCK once, so a stopped attempt counts toward the merchant's
46
+ * blocked-order metric without repeat previews inflating it. */
47
+ async previewDecision(request, idempotencyKey) {
48
+ const headers = {};
49
+ if (idempotencyKey) {
50
+ headers["Idempotency-Key"] = idempotencyKey;
51
+ }
52
+ return this.call("POST", "/v1/decisions/preview", request, headers);
53
+ }
54
+ async classifyCatalog(items) {
55
+ // Classification makes one LLM call per item (6-way concurrent
56
+ // server-side); a batch legitimately outlasts the checkout timeout.
57
+ return this.call("POST", "/v1/catalog/classify", { items }, {}, this.catalogTimeoutMs);
58
+ }
59
+ /** The attribute dictionary the current ruleset decides against — the
60
+ * schema the classification editor renders its dropdowns from. */
61
+ async getDictionary() {
62
+ const snapshot = await this.call("GET", "/v1/rulesets/current/full");
63
+ return snapshot.dictionary;
64
+ }
65
+ async listClassifications(status, limit) {
66
+ const params = new URLSearchParams();
67
+ if (status) {
68
+ params.set("status", status);
69
+ }
70
+ if (limit) {
71
+ params.set("limit", String(limit));
72
+ }
73
+ const qs = params.size > 0 ? `?${params}` : "";
74
+ return this.call("GET", `/v1/catalog/classifications${qs}`);
75
+ }
76
+ async confirmClassification(sku, attributes) {
77
+ return this.call("POST", `/v1/catalog/classifications/${encodeURIComponent(sku)}/confirm`, { attributes });
78
+ }
79
+ async call(method, path, body, extraHeaders = {}, timeoutMs = this.timeoutMs) {
80
+ if (!this.configured) {
81
+ throw new OrdinantApiError("Ordinant is not configured — set ORDINANT_BASE_URL and ORDINANT_API_KEY", "not_configured");
82
+ }
83
+ const controller = new AbortController();
84
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
85
+ let res;
86
+ try {
87
+ res = await fetch(this.baseUrl + path, {
88
+ method,
89
+ headers: {
90
+ authorization: `Bearer ${this.apiKey}`,
91
+ "content-type": "application/json",
92
+ ...extraHeaders,
93
+ },
94
+ body: body === undefined ? null : JSON.stringify(body),
95
+ signal: controller.signal,
96
+ });
97
+ }
98
+ catch (err) {
99
+ const aborted = err instanceof Error && err.name === "AbortError";
100
+ throw new OrdinantApiError(aborted
101
+ ? `Ordinant request timed out after ${timeoutMs}ms`
102
+ : `Ordinant transport error: ${String(err)}`, aborted ? "timeout" : "transport");
103
+ }
104
+ finally {
105
+ clearTimeout(timer);
106
+ }
107
+ const text = await res.text();
108
+ if (!res.ok) {
109
+ let code = "http_error";
110
+ let message = `HTTP ${res.status}`;
111
+ try {
112
+ const parsed = JSON.parse(text);
113
+ if (parsed.error?.code) {
114
+ code = parsed.error.code;
115
+ }
116
+ if (parsed.error?.message) {
117
+ message = parsed.error.message;
118
+ }
119
+ }
120
+ catch {
121
+ if (text) {
122
+ message = `${message}: ${text.slice(0, 200)}`;
123
+ }
124
+ }
125
+ throw new OrdinantApiError(message, code, res.status);
126
+ }
127
+ try {
128
+ return JSON.parse(text);
129
+ }
130
+ catch {
131
+ throw new OrdinantApiError("invalid JSON from Ordinant", "bad_response", res.status);
132
+ }
133
+ }
134
+ }
135
+ exports.OrdinantClient = OrdinantClient;
136
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7O0dBSUc7OztBQWtLSCxNQUFhLGdCQUFpQixTQUFRLEtBQUs7SUFJekMsWUFBWSxPQUFlLEVBQUUsSUFBWSxFQUFFLE1BQWU7UUFDeEQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2QsSUFBSSxDQUFDLElBQUksR0FBRyxrQkFBa0IsQ0FBQTtRQUM5QixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtRQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtJQUN0QixDQUFDO0NBQ0Y7QUFWRCw0Q0FVQztBQWlCRCxNQUFhLGNBQWM7SUFVekIsWUFBWSxPQUE4QjtRQUN4QyxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUM1RCxJQUFJLENBQUMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQzFELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUE7UUFDbEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQTtRQUMxQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixJQUFJLE9BQU8sQ0FBQTtJQUM3RCxDQUFDO0lBRUQsbUVBQW1FO0lBQ25FLEtBQUssQ0FBQyxVQUFVO1FBQ2QsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFhLEtBQUssRUFBRSxrQkFBa0IsQ0FBQyxDQUFBO0lBQ3pELENBQUM7SUFFRDtzQ0FDa0M7SUFDbEMsS0FBSyxDQUFDLGNBQWM7UUFDbEIs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
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OrdinantModuleService = exports.ORDINANT_MODULE = void 0;
7
+ const utils_1 = require("@medusajs/framework/utils");
8
+ const service_1 = __importDefault(require("./service"));
9
+ exports.OrdinantModuleService = service_1.default;
10
+ exports.ORDINANT_MODULE = "ordinant";
11
+ exports.default = (0, utils_1.Module)(exports.ORDINANT_MODULE, {
12
+ service: service_1.default,
13
+ });
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9vcmRpbmFudC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSxxREFBa0Q7QUFDbEQsd0RBQTZDO0FBUXBDLGdDQVJGLGlCQUFxQixDQVFFO0FBTmpCLFFBQUEsZUFBZSxHQUFHLFVBQVUsQ0FBQTtBQUV6QyxrQkFBZSxJQUFBLGNBQU0sRUFBQyx1QkFBZSxFQUFFO0lBQ3JDLE9BQU8sRUFBRSxpQkFBcUI7Q0FDL0IsQ0FBQyxDQUFBIn0=
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const client_1 = require("../../lib/client");
4
+ /**
5
+ * OrdinantModuleService wraps the Ordinant API client so workflows,
6
+ * subscribers, and API routes resolve one configured instance from the
7
+ * container. It holds no state and performs no compliance logic — decisions
8
+ * are made (and logged, and replayable) server-side.
9
+ */
10
+ class OrdinantModuleService {
11
+ constructor(_container, options) {
12
+ this.client = new client_1.OrdinantClient({
13
+ baseUrl: options?.baseUrl ?? "",
14
+ apiKey: options?.apiKey ?? "",
15
+ timeoutMs: options?.timeoutMs,
16
+ });
17
+ this.failMode = options?.failMode === "open" ? "open" : "closed";
18
+ this.skuMode = options?.skuMode === "allow" ? "allow" : "block";
19
+ }
20
+ /** Whether the merchant has activated Ordinant (URL + API key set). The
21
+ * admin dashboard gates itself on this. */
22
+ get configured() {
23
+ return this.client.configured;
24
+ }
25
+ }
26
+ exports.default = OrdinantModuleService;
27
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL29yZGluYW50L3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSw2Q0FBaUQ7QUFpQ2pEOzs7OztHQUtHO0FBQ0gsTUFBcUIscUJBQXFCO0lBS3hDLFlBQVksVUFBbUMsRUFBRSxPQUErQjtRQUM5RSxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksdUJBQWMsQ0FBQztZQUMvQixPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sSUFBSSxFQUFFO1lBQy9CLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxJQUFJLEVBQUU7WUFDN0IsU0FBUyxFQUFFLE9BQU8sRUFBRSxTQUFTO1NBQzlCLENBQUMsQ0FBQTtRQUNGLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxFQUFFLFFBQVEsS0FBSyxNQUFNLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFBO1FBQ2hFLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxFQUFFLE9BQU8sS0FBSyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFBO0lBQ2pFLENBQUM7SUFFRDsrQ0FDMkM7SUFDM0MsSUFBSSxVQUFVO1FBQ1osT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVUsQ0FBQTtJQUMvQixDQUFDO0NBQ0Y7QUFwQkQsd0NBb0JDIn0=
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Storefront helper for the Ordinant early compliance preview.
3
+ *
4
+ * Import it in your checkout code:
5
+ *
6
+ * import { checkAddressCompliance } from "medusa-plugin-ordinant/storefront"
7
+ *
8
+ * // when the shipping address is submitted, BEFORE you save it:
9
+ * const block = await checkAddressCompliance(sdk, { cartId, destination })
10
+ * if (block) return block // show `block`; don't save the address
11
+ *
12
+ * It's framework-agnostic (pass your Medusa JS SDK, or anything with a
13
+ * `client.fetch`) and FAIL-OPEN: any error returns null so it can never break
14
+ * checkout. It returns a human-readable message (with statute citations) only
15
+ * for a hard BLOCK; CONDITIONAL requirements (age/FFL not yet gathered) return
16
+ * null and surface at their own later steps. The authoritative, non-bypassable
17
+ * gate still runs at cart completion — this is purely the early-warning UX.
18
+ *
19
+ * Checking the *proposed* destination before persisting it means a blocked
20
+ * address is never written to the cart.
21
+ */
22
+ export type ComplianceDestination = {
23
+ state?: string;
24
+ country_code?: string;
25
+ zip?: string;
26
+ city?: string;
27
+ line1?: string;
28
+ };
29
+ export type ComplianceBuyer = {
30
+ age_verified?: boolean;
31
+ verified_age?: number;
32
+ ffl_verified?: boolean;
33
+ sot_verified?: boolean;
34
+ permits?: string[];
35
+ fulfillment_method?: "ship" | "in_store_pickup";
36
+ };
37
+ /** The slice of the Medusa JS SDK this helper needs. */
38
+ export type ComplianceFetcher = {
39
+ client: {
40
+ fetch: <T>(path: string, init: {
41
+ method: string;
42
+ body?: unknown;
43
+ }) => Promise<T>;
44
+ };
45
+ };
46
+ export type CheckAddressComplianceOptions = {
47
+ /** The cart being checked out. */
48
+ cartId: string;
49
+ /** The PROPOSED address to vet before it's saved. Omit to use the cart's
50
+ * already-saved shipping address. */
51
+ destination?: ComplianceDestination;
52
+ /** Verification assertions gathered so far (age, FFL, …), if any. */
53
+ buyer?: ComplianceBuyer;
54
+ /** Message used when a block has no itemized reason. */
55
+ fallbackMessage?: string;
56
+ };
57
+ /**
58
+ * Returns a block message when the destination is prohibited, or null when it's
59
+ * fine (or when anything goes wrong — fail-open). Never throws.
60
+ */
61
+ export declare function checkAddressCompliance(sdk: ComplianceFetcher, options: CheckAddressComplianceOptions): Promise<string | null>;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * Storefront helper for the Ordinant early compliance preview.
4
+ *
5
+ * Import it in your checkout code:
6
+ *
7
+ * import { checkAddressCompliance } from "medusa-plugin-ordinant/storefront"
8
+ *
9
+ * // when the shipping address is submitted, BEFORE you save it:
10
+ * const block = await checkAddressCompliance(sdk, { cartId, destination })
11
+ * if (block) return block // show `block`; don't save the address
12
+ *
13
+ * It's framework-agnostic (pass your Medusa JS SDK, or anything with a
14
+ * `client.fetch`) and FAIL-OPEN: any error returns null so it can never break
15
+ * checkout. It returns a human-readable message (with statute citations) only
16
+ * for a hard BLOCK; CONDITIONAL requirements (age/FFL not yet gathered) return
17
+ * null and surface at their own later steps. The authoritative, non-bypassable
18
+ * gate still runs at cart completion — this is purely the early-warning UX.
19
+ *
20
+ * Checking the *proposed* destination before persisting it means a blocked
21
+ * address is never written to the cart.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.checkAddressCompliance = checkAddressCompliance;
25
+ const DEFAULT_MESSAGE = "This order can’t be completed to that address.";
26
+ /**
27
+ * Returns a block message when the destination is prohibited, or null when it's
28
+ * fine (or when anything goes wrong — fail-open). Never throws.
29
+ */
30
+ async function checkAddressCompliance(sdk, options) {
31
+ const { cartId, destination, buyer, fallbackMessage } = options;
32
+ try {
33
+ const preview = await sdk.client.fetch("/store/ordinant/preview", {
34
+ method: "POST",
35
+ body: { cart_id: cartId, destination, buyer },
36
+ });
37
+ if (preview.disposition !== "BLOCK") {
38
+ return null;
39
+ }
40
+ const reasons = [];
41
+ for (const item of preview.items ?? []) {
42
+ for (const eff of item.effects ?? []) {
43
+ if (eff.satisfied || eff.effect?.type !== "BLOCK" || !eff.description) {
44
+ continue;
45
+ }
46
+ const cite = (eff.citations ?? [])
47
+ .map((c) => c.source)
48
+ .filter(Boolean)
49
+ .join("; ");
50
+ reasons.push(cite ? `${eff.description} [${cite}]` : eff.description);
51
+ }
52
+ }
53
+ const base = fallbackMessage ?? DEFAULT_MESSAGE;
54
+ return reasons.length ? `${base} ${reasons.join(" ")}` : base;
55
+ }
56
+ catch {
57
+ // Fail-open: never break checkout because the advisory preview is down.
58
+ return null;
59
+ }
60
+ }
61
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmVmcm9udC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zdG9yZWZyb250LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FvQkc7O0FBd0RILHdEQWdDQztBQXRDRCxNQUFNLGVBQWUsR0FBRyxnREFBZ0QsQ0FBQTtBQUV4RTs7O0dBR0c7QUFDSSxLQUFLLFVBQVUsc0JBQXNCLENBQzFDLEdBQXNCLEVBQ3RCLE9BQXNDO0lBRXRDLE1BQU0sRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxlQUFlLEVBQUUsR0FBRyxPQUFPLENBQUE7SUFDL0QsSUFBSSxDQUFDO1FBQ0gsTUFBTSxPQUFPLEdBQUcsTUFBTSxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBa0IseUJBQXlCLEVBQUU7WUFDakYsTUFBTSxFQUFFLE1BQU07WUFDZCxJQUFJLEVBQUUsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUU7U0FDOUMsQ0FBQyxDQUFBO1FBQ0YsSUFBSSxPQUFPLENBQUMsV0FBVyxLQUFLLE9BQU8sRUFBRSxDQUFDO1lBQ3BDLE9BQU8sSUFBSSxDQUFBO1FBQ2IsQ0FBQztRQUNELE1BQU0sT0FBTyxHQUFhLEVBQUUsQ0FBQTtRQUM1QixLQUFLLE1BQU0sSUFBSSxJQUFJLE9BQU8sQ0FBQyxLQUFLLElBQUksRUFBRSxFQUFFLENBQUM7WUFDdkMsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLEVBQUUsRUFBRSxDQUFDO2dCQUNyQyxJQUFJLEdBQUcsQ0FBQyxTQUFTLElBQUksR0FBRyxDQUFDLE1BQU0sRUFBRSxJQUFJLEtBQUssT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO29CQUN0RSxTQUFRO2dCQUNWLENBQUM7Z0JBQ0QsTUFBTSxJQUFJLEdBQUcsQ0FBQyxHQUFHLENBQUMsU0FBUyxJQUFJLEVBQUUsQ0FBQztxQkFDL0IsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDO3FCQUNwQixNQUFNLENBQUMsT0FBTyxDQUFDO3FCQUNmLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtnQkFDYixPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsV0FBVyxLQUFLLElBQUksR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUE7WUFDdkUsQ0FBQztRQUNILENBQUM7UUFDRCxNQUFNLElBQUksR0FBRyxlQUFlLElBQUksZUFBZSxDQUFBO1FBQy9DLE9BQU8sT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7SUFDL0QsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLHdFQUF3RTtRQUN4RSxPQUFPLElBQUksQ0FBQTtJQUNiLENBQUM7QUFDSCxDQUFDIn0=
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.config = void 0;
4
+ exports.default = ordinantProductUpsertHandler;
5
+ const classify_product_1 = require("../workflows/classify-product");
6
+ /**
7
+ * When a product is created or updated, send its variants to Ordinant for
8
+ * attribute classification. This produces PROPOSALS the merchant confirms
9
+ * in the product page's Ordinant widget — nothing is auto-attested, and a
10
+ * classification failure never blocks catalog work (the SKU simply stays
11
+ * unclassified, which fails closed at checkout).
12
+ *
13
+ * Firing on `product.updated` is safe because Ordinant dedups on a content
14
+ * signature of the classification-relevant fields (title, description, type,
15
+ * specs). Medusa's `product.updated` fires for price/inventory/image/status
16
+ * edits too, but those reach Ordinant with an unchanged signature and are a
17
+ * true no-op — a confirmed attestation is never reset by an edit that didn't
18
+ * change what the product legally is. Only a title/description/spec change
19
+ * re-classifies and (correctly) invalidates the prior attestation.
20
+ */
21
+ async function ordinantProductUpsertHandler({ event: { name: eventName, data }, container, }) {
22
+ const logger = container.resolve("logger");
23
+ try {
24
+ const { result } = await (0, classify_product_1.classifyProductWorkflow)(container).run({
25
+ input: { product_id: data.id },
26
+ });
27
+ const failed = result.filter((r) => r.error).length;
28
+ logger.info(`[ordinant] ${eventName}: proposed classifications for product ${data.id} ` +
29
+ `(${result.length - failed} proposed, ${failed} failed)`);
30
+ }
31
+ catch (error) {
32
+ // Workflow failures can surface as plain objects; stringify for a
33
+ // readable log line either way.
34
+ const message = error instanceof Error
35
+ ? error.message
36
+ : (() => {
37
+ try {
38
+ return JSON.stringify(error);
39
+ }
40
+ catch {
41
+ return String(error);
42
+ }
43
+ })();
44
+ logger.error(`[ordinant] ${eventName}: classification failed for product ${data.id}: ${message}`);
45
+ // Swallow: subscribers must not disrupt product management. Unclassified
46
+ // SKUs fail closed at checkout, so nothing unsafe slips through.
47
+ }
48
+ }
49
+ exports.config = {
50
+ event: ["product.created", "product.updated"],
51
+ };
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvZHVjdC11cHNlcnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvc3Vic2NyaWJlcnMvcHJvZHVjdC11cHNlcnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBa0JBLCtDQWtDQztBQW5ERCxvRUFBdUU7QUFFdkU7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDWSxLQUFLLFVBQVUsNEJBQTRCLENBQUMsRUFDekQsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsRUFDaEMsU0FBUyxHQUNzQjtJQUMvQixNQUFNLE1BQU0sR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0lBRTFDLElBQUksQ0FBQztRQUNILE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxNQUFNLElBQUEsMENBQXVCLEVBQUMsU0FBUyxDQUFDLENBQUMsR0FBRyxDQUFDO1lBQzlELEtBQUssRUFBRSxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFO1NBQy9CLENBQUMsQ0FBQTtRQUNGLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUE7UUFDbkQsTUFBTSxDQUFDLElBQUksQ0FDVCxjQUFjLFNBQVMsMENBQTBDLElBQUksQ0FBQyxFQUFFLEdBQUc7WUFDekUsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLE1BQU0sY0FBYyxNQUFNLFVBQVUsQ0FDM0QsQ0FBQTtJQUNILENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2Ysa0VBQWtFO1FBQ2xFLGdDQUFnQztRQUNoQyxNQUFNLE9BQU8sR0FDWCxLQUFLLFlBQVksS0FBSztZQUNwQixDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU87WUFDZixDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUU7Z0JBQ0osSUFBSSxDQUFDO29CQUNILE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQTtnQkFDOUIsQ0FBQztnQkFBQyxNQUFNLENBQUM7b0JBQ1AsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUE7Z0JBQ3RCLENBQUM7WUFDSCxDQUFDLENBQUMsRUFBRSxDQUFBO1FBQ1YsTUFBTSxDQUFDLEtBQUssQ0FDVixjQUFjLFNBQVMsdUNBQXVDLElBQUksQ0FBQyxFQUFFLEtBQUssT0FBTyxFQUFFLENBQ3BGLENBQUE7UUFDRCx5RUFBeUU7UUFDekUsaUVBQWlFO0lBQ25FLENBQUM7QUFDSCxDQUFDO0FBRVksUUFBQSxNQUFNLEdBQXFCO0lBQ3RDLEtBQUssRUFBRSxDQUFDLGlCQUFpQixFQUFFLGlCQUFpQixDQUFDO0NBQzlDLENBQUEifQ==
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bulkConfirmWorkflow = exports.bulkConfirmStep = exports.bulkClassifyWorkflow = void 0;
4
+ const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
5
+ const ordinant_1 = require("../modules/ordinant");
6
+ const classify_product_1 = require("./classify-product");
7
+ /**
8
+ * bulkClassifyWorkflow submits a pre-gathered batch of catalog items for
9
+ * classification (the Ordinant admin page's "Classify unclassified"
10
+ * action). Batches are capped by the API route; the page loops until the
11
+ * backlog is empty.
12
+ */
13
+ exports.bulkClassifyWorkflow = (0, workflows_sdk_1.createWorkflow)("ordinant-bulk-classify", function (input) {
14
+ const results = (0, classify_product_1.classifyItemsStep)(input.items);
15
+ return new workflows_sdk_1.WorkflowResponse(results);
16
+ });
17
+ /**
18
+ * bulkConfirmStep confirms each SKU's classification AS PROPOSED — the
19
+ * merchant reviewed the summary in the admin page and is attesting to the
20
+ * model's proposals in bulk. SKUs that are not in `proposed` status are
21
+ * skipped, and per-SKU failures never abort the batch.
22
+ */
23
+ exports.bulkConfirmStep = (0, workflows_sdk_1.createStep)("ordinant-bulk-confirm", async (input, { container }) => {
24
+ const ordinant = container.resolve(ordinant_1.ORDINANT_MODULE);
25
+ const { items } = await ordinant.client.listClassifications("proposed", 5000);
26
+ const proposals = new Map(items.map((c) => [c.sku, c]));
27
+ const results = [];
28
+ for (const sku of input.skus) {
29
+ const proposal = proposals.get(sku);
30
+ if (!proposal) {
31
+ results.push({
32
+ sku,
33
+ status: "skipped",
34
+ detail: "no proposal awaiting confirmation",
35
+ });
36
+ continue;
37
+ }
38
+ try {
39
+ await ordinant.client.confirmClassification(sku, proposal.attributes);
40
+ results.push({ sku, status: "confirmed" });
41
+ }
42
+ catch (error) {
43
+ results.push({
44
+ sku,
45
+ status: "failed",
46
+ detail: error instanceof Error ? error.message : String(error),
47
+ });
48
+ }
49
+ }
50
+ return new workflows_sdk_1.StepResponse(results);
51
+ });
52
+ /**
53
+ * bulkConfirmWorkflow records bulk attestations from the admin page.
54
+ */
55
+ exports.bulkConfirmWorkflow = (0, workflows_sdk_1.createWorkflow)("ordinant-bulk-confirm", function (input) {
56
+ const results = (0, exports.bulkConfirmStep)(input);
57
+ return new workflows_sdk_1.WorkflowResponse(results);
58
+ });
59
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVsay1vcGVyYXRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3dvcmtmbG93cy9idWxrLW9wZXJhdGlvbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEscUVBSzBDO0FBQzFDLGtEQUFxRDtBQUdyRCx5REFBc0Q7QUFNdEQ7Ozs7O0dBS0c7QUFDVSxRQUFBLG9CQUFvQixHQUFHLElBQUEsOEJBQWMsRUFDaEQsd0JBQXdCLEVBQ3hCLFVBQVUsS0FBd0I7SUFDaEMsTUFBTSxPQUFPLEdBQUcsSUFBQSxvQ0FBaUIsRUFBQyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUE7SUFDOUMsT0FBTyxJQUFJLGdDQUFnQixDQUFDLE9BQU8sQ0FBQyxDQUFBO0FBQ3RDLENBQUMsQ0FDRixDQUFBO0FBWUQ7Ozs7O0dBS0c7QUFDVSxRQUFBLGVBQWUsR0FBRyxJQUFBLDBCQUFVLEVBQ3ZDLHVCQUF1QixFQUN2QixLQUFLLEVBQUUsS0FBdUIsRUFBRSxFQUFFLFNBQVMsRUFBRSxFQUFFLEVBQUU7SUFDL0MsTUFBTSxRQUFRLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBd0IsMEJBQWUsQ0FBQyxDQUFBO0lBQzFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsR0FBRyxNQUFNLFFBQVEsQ0FBQyxNQUFNLENBQUMsbUJBQW1CLENBQUMsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFBO0lBQzdFLE1BQU0sU0FBUyxHQUFHLElBQUksR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFFdkQsTUFBTSxPQUFPLEdBQXdCLEVBQUUsQ0FBQTtJQUN2QyxLQUFLLE1BQU0sR0FBRyxJQUFJLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUM3QixNQUFNLFFBQVEsR0FBRyxTQUFTLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO1FBQ25DLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNkLE9BQU8sQ0FBQyxJQUFJLENBQUM7Z0JBQ1gsR0FBRztnQkFDSCxNQUFNLEVBQUUsU0FBUztnQkFDakIsTUFBTSxFQUFFLG1DQUFtQzthQUM1QyxDQUFDLENBQUE7WUFDRixTQUFRO1FBQ1YsQ0FBQztRQUNELElBQUksQ0FBQztZQUNILE1BQU0sUUFBUSxDQUFDLE1BQU0sQ0FBQyxxQkFBcUIsQ0FBQyxHQUFHLEVBQUUsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFBO1lBQ3JFLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLFdBQVcsRUFBRSxDQUFDLENBQUE7UUFDNUMsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsSUFBSSxDQUFDO2dCQUNYLEdBQUc7Z0JBQ0gsTUFBTSxFQUFFLFFBQVE7Z0JBQ2hCLE1BQU0sRUFBRSxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDO2FBQy9ELENBQUMsQ0FBQTtRQUNKLENBQUM7SUFDSCxDQUFDO0lBQ0QsT0FBTyxJQUFJLDRCQUFZLENBQUMsT0FBTyxDQUFDLENBQUE7QUFDbEMsQ0FBQyxDQUNGLENBQUE7QUFFRDs7R0FFRztBQUNVLFFBQUEsbUJBQW1CLEdBQUcsSUFBQSw4QkFBYyxFQUMvQyx1QkFBdUIsRUFDdkIsVUFBVSxLQUF1QjtJQUMvQixNQUFNLE9BQU8sR0FBRyxJQUFBLHVCQUFlLEVBQUMsS0FBSyxDQUFDLENBQUE7SUFDdEMsT0FBTyxJQUFJLGdDQUFnQixDQUFDLE9BQU8sQ0FBQyxDQUFBO0FBQ3RDLENBQUMsQ0FDRixDQUFBIn0=