medusa-plugin-ordinant 0.1.1 → 0.1.2

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.
@@ -141,6 +141,57 @@ function formatValue(value) {
141
141
  }
142
142
  return labelValue(String(value));
143
143
  }
144
+ function CoveragePreview({ attributes }) {
145
+ const category = typeof attributes["category"] === "string" ? attributes["category"] : void 0;
146
+ const { data, isFetching, error } = reactQuery.useQuery({
147
+ queryKey: ["ordinant-coverage", JSON.stringify(attributes)],
148
+ queryFn: () => sdk.client.fetch("/admin/ordinant/coverage", {
149
+ method: "POST",
150
+ body: { attributes }
151
+ }),
152
+ enabled: category !== void 0,
153
+ staleTime: 5 * 60 * 1e3
154
+ });
155
+ if (category === void 0) {
156
+ return null;
157
+ }
158
+ if (isFetching && !data) {
159
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: "Checking where this configuration can ship…" });
160
+ }
161
+ if (error || !data) {
162
+ return null;
163
+ }
164
+ const blocked = data.blocked ?? [];
165
+ const conditional = data.conditional ?? [];
166
+ if (blocked.length === 0 && conditional.length === 0) {
167
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
168
+ "Checkout is allowed in all ",
169
+ data.total_states,
170
+ " modeled states with this configuration."
171
+ ] });
172
+ }
173
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 rounded-lg border p-3 bg-ui-bg-subtle", children: [
174
+ blocked.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
175
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-error", children: [
176
+ "With this configuration, shipping/checkout will be denied to",
177
+ " ",
178
+ blocked.length,
179
+ " ",
180
+ blocked.length === 1 ? "state" : "states",
181
+ ":"
182
+ ] }),
183
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: blocked.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "red", title: s.name, children: s.state }, s.state)) })
184
+ ] }),
185
+ conditional.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
186
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-subtle", children: [
187
+ "Requires buyer verification (FFL / permit / age) in ",
188
+ conditional.length,
189
+ ":"
190
+ ] }),
191
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: conditional.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "orange", title: s.name, children: s.state }, s.state)) })
192
+ ] })
193
+ ] });
194
+ }
144
195
  function AttributeEditor({
145
196
  value,
146
197
  onChange
@@ -184,17 +235,20 @@ function AttributeEditor({
184
235
  {
185
236
  def,
186
237
  value: value[def.key],
187
- onChange: (v) => set(def.key, v)
238
+ onChange: (v) => set(def.key, v),
239
+ extraHint: def.key === "nfa_class" && category === "suppressor_part" ? "For a suppressor component, this is what decides whether it can ship: choose “Suppressor” for the sound-reducing core (baffle, monocore, expansion chamber, outer tube). Choose “None” for external accessories that don't reduce sound (end cap, cover, mount, thread adapter)." : void 0
188
240
  },
189
241
  def.key
190
- ))
242
+ )),
243
+ category !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(CoveragePreview, { attributes: value })
191
244
  ] });
192
245
  }
193
246
  const NONE = "__none__";
194
247
  function AttributeField({
195
248
  def,
196
249
  value,
197
- onChange
250
+ onChange,
251
+ extraHint
198
252
  }) {
199
253
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
200
254
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
@@ -202,6 +256,7 @@ function AttributeField({
202
256
  def.hard_gate && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "orange", children: "required" })
203
257
  ] }),
204
258
  def.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: def.description }),
259
+ extraHint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: extraHint }),
205
260
  /* @__PURE__ */ jsxRuntime.jsx(Control, { def, value, onChange })
206
261
  ] });
207
262
  }
@@ -138,6 +138,57 @@ function formatValue(value) {
138
138
  }
139
139
  return labelValue(String(value));
140
140
  }
141
+ function CoveragePreview({ attributes }) {
142
+ const category = typeof attributes["category"] === "string" ? attributes["category"] : void 0;
143
+ const { data, isFetching, error } = useQuery({
144
+ queryKey: ["ordinant-coverage", JSON.stringify(attributes)],
145
+ queryFn: () => sdk.client.fetch("/admin/ordinant/coverage", {
146
+ method: "POST",
147
+ body: { attributes }
148
+ }),
149
+ enabled: category !== void 0,
150
+ staleTime: 5 * 60 * 1e3
151
+ });
152
+ if (category === void 0) {
153
+ return null;
154
+ }
155
+ if (isFetching && !data) {
156
+ return /* @__PURE__ */ jsx(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: "Checking where this configuration can ship…" });
157
+ }
158
+ if (error || !data) {
159
+ return null;
160
+ }
161
+ const blocked = data.blocked ?? [];
162
+ const conditional = data.conditional ?? [];
163
+ if (blocked.length === 0 && conditional.length === 0) {
164
+ return /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
165
+ "Checkout is allowed in all ",
166
+ data.total_states,
167
+ " modeled states with this configuration."
168
+ ] });
169
+ }
170
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-lg border p-3 bg-ui-bg-subtle", children: [
171
+ blocked.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
172
+ /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-error", children: [
173
+ "With this configuration, shipping/checkout will be denied to",
174
+ " ",
175
+ blocked.length,
176
+ " ",
177
+ blocked.length === 1 ? "state" : "states",
178
+ ":"
179
+ ] }),
180
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: blocked.map((s) => /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "red", title: s.name, children: s.state }, s.state)) })
181
+ ] }),
182
+ conditional.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
183
+ /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", weight: "plus", className: "text-ui-fg-subtle", children: [
184
+ "Requires buyer verification (FFL / permit / age) in ",
185
+ conditional.length,
186
+ ":"
187
+ ] }),
188
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 mt-1.5", children: conditional.map((s) => /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "orange", title: s.name, children: s.state }, s.state)) })
189
+ ] })
190
+ ] });
191
+ }
141
192
  function AttributeEditor({
142
193
  value,
143
194
  onChange
@@ -181,17 +232,20 @@ function AttributeEditor({
181
232
  {
182
233
  def,
183
234
  value: value[def.key],
184
- onChange: (v) => set(def.key, v)
235
+ onChange: (v) => set(def.key, v),
236
+ extraHint: def.key === "nfa_class" && category === "suppressor_part" ? "For a suppressor component, this is what decides whether it can ship: choose “Suppressor” for the sound-reducing core (baffle, monocore, expansion chamber, outer tube). Choose “None” for external accessories that don't reduce sound (end cap, cover, mount, thread adapter)." : void 0
185
237
  },
186
238
  def.key
187
- ))
239
+ )),
240
+ category !== void 0 && /* @__PURE__ */ jsx(CoveragePreview, { attributes: value })
188
241
  ] });
189
242
  }
190
243
  const NONE = "__none__";
191
244
  function AttributeField({
192
245
  def,
193
246
  value,
194
- onChange
247
+ onChange,
248
+ extraHint
195
249
  }) {
196
250
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
197
251
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -199,6 +253,7 @@ function AttributeField({
199
253
  def.hard_gate && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "orange", children: "required" })
200
254
  ] }),
201
255
  def.description && /* @__PURE__ */ jsx(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-muted", children: def.description }),
256
+ extraHint && /* @__PURE__ */ jsx(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: extraHint }),
202
257
  /* @__PURE__ */ jsx(Control, { def, value, onChange })
203
258
  ] });
204
259
  }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POST = POST;
4
+ const ordinant_1 = require("../../../../modules/ordinant");
5
+ /**
6
+ * POST /admin/ordinant/coverage
7
+ *
8
+ * Preview where a proposed classification would be denied. Given an attribute
9
+ * set, the engine sweeps every modeled jurisdiction and returns the states
10
+ * where checkout is BLOCKED vs. CONDITIONAL. Powers the editor's "with this
11
+ * configuration, shipping is denied to…" panel. Non-logging, advisory.
12
+ */
13
+ async function POST(req, res) {
14
+ const attributes = req.body?.attributes;
15
+ if (!attributes || Object.keys(attributes).length === 0) {
16
+ res.status(400).json({ message: "attributes are required to preview coverage" });
17
+ return;
18
+ }
19
+ const ordinant = req.scope.resolve(ordinant_1.ORDINANT_MODULE);
20
+ const coverage = await ordinant.client.coverage(attributes);
21
+ res.json(coverage);
22
+ }
23
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL2FkbWluL29yZGluYW50L2NvdmVyYWdlL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBY0Esb0JBWUM7QUF6QkQsMkRBQThEO0FBSzlEOzs7Ozs7O0dBT0c7QUFDSSxLQUFLLFVBQVUsSUFBSSxDQUN4QixHQUE2QyxFQUM3QyxHQUFtQjtJQUVuQixNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsSUFBSSxFQUFFLFVBQVUsQ0FBQTtJQUN2QyxJQUFJLENBQUMsVUFBVSxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3hELEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLDZDQUE2QyxFQUFFLENBQUMsQ0FBQTtRQUNoRixPQUFNO0lBQ1IsQ0FBQztJQUNELE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUF3QiwwQkFBZSxDQUFDLENBQUE7SUFDMUUsTUFBTSxRQUFRLEdBQUcsTUFBTSxRQUFRLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQTtJQUMzRCxHQUFHLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFBO0FBQ3BCLENBQUMifQ==
@@ -51,6 +51,16 @@ class OrdinantClient {
51
51
  }
52
52
  return this.call("POST", "/v1/decisions/preview", request, headers);
53
53
  }
54
+ /** Sweep a proposed attribute set across every modeled jurisdiction and
55
+ * report where checkout would be denied vs. conditionally allowed. Non-
56
+ * logging — powers the classification editor's "denied to" preview so a
57
+ * merchant sees the shipping impact of a classification before confirming. */
58
+ async coverage(attributes, buyer) {
59
+ return this.call("POST", "/v1/decisions/coverage", {
60
+ attributes,
61
+ buyer,
62
+ });
63
+ }
54
64
  async classifyCatalog(items) {
55
65
  // Classification makes one LLM call per item (6-way concurrent
56
66
  // server-side); a batch legitimately outlasts the checkout timeout.
@@ -133,4 +143,4 @@ class OrdinantClient {
133
143
  }
134
144
  }
135
145
  exports.OrdinantClient = OrdinantClient;
136
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7O0dBSUc7OztBQWtLSCxNQUFhLGdCQUFpQixTQUFRLEtBQUs7SUFJekMsWUFBWSxPQUFlLEVBQUUsSUFBWSxFQUFFLE1BQWU7UUFDeEQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2QsSUFBSSxDQUFDLElBQUksR0FBRyxrQkFBa0IsQ0FBQTtRQUM5QixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtRQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtJQUN0QixDQUFDO0NBQ0Y7QUFWRCw0Q0FVQztBQWlCRCxNQUFhLGNBQWM7SUFVekIsWUFBWSxPQUE4QjtRQUN4QyxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUM1RCxJQUFJLENBQUMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQzFELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUE7UUFDbEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQTtRQUMxQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixJQUFJLE9BQU8sQ0FBQTtJQUM3RCxDQUFDO0lBRUQsbUVBQW1FO0lBQ25FLEtBQUssQ0FBQyxVQUFVO1FBQ2QsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFhLEtBQUssRUFBRSxrQkFBa0IsQ0FBQyxDQUFBO0lBQ3pELENBQUM7SUFFRDtzQ0FDa0M7SUFDbEMsS0FBSyxDQUFDLGNBQWM7UUFDbEIs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
146
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9jbGllbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7O0dBSUc7OztBQWtLSCxNQUFhLGdCQUFpQixTQUFRLEtBQUs7SUFJekMsWUFBWSxPQUFlLEVBQUUsSUFBWSxFQUFFLE1BQWU7UUFDeEQsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ2QsSUFBSSxDQUFDLElBQUksR0FBRyxrQkFBa0IsQ0FBQTtRQUM5QixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtRQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtJQUN0QixDQUFDO0NBQ0Y7QUFWRCw0Q0FVQztBQW9DRCxNQUFhLGNBQWM7SUFVekIsWUFBWSxPQUE4QjtRQUN4QyxJQUFJLENBQUMsVUFBVSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUM1RCxJQUFJLENBQUMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQzFELElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUE7UUFDbEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQTtRQUMxQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixJQUFJLE9BQU8sQ0FBQTtJQUM3RCxDQUFDO0lBRUQsbUVBQW1FO0lBQ25FLEtBQUssQ0FBQyxVQUFVO1FBQ2QsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFhLEtBQUssRUFBRSxrQkFBa0IsQ0FBQyxDQUFBO0lBQ3pELENBQUM7SUFFRDtzQ0FDa0M7SUFDbEMsS0FBSyxDQUFDLGNBQWM7UUFDbEIs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
package/README.md CHANGED
@@ -248,6 +248,14 @@ dictionary change.
248
248
  Re-classifying a SKU (e.g. after a product edit) resets it to `proposed`: a
249
249
  stale attestation never survives changed product data.
250
250
 
251
+ **Live shipping-impact preview.** While editing a classification, the widget
252
+ shows *"With this configuration, shipping/checkout will be denied to: CA, NY, …"*
253
+ (and a separate "requires FFL/permit/age in …" list), swept across every modeled
254
+ jurisdiction — so you see exactly where a classification will and won't sell
255
+ **before** you attest to it. Fields with subtle legal meaning carry inline
256
+ guidance (e.g. for a suppressor part, `nfa_class` = *Suppressor* for the sound-
257
+ reducing core, *None* for an external accessory like an end cap).
258
+
251
259
  ## The checkout gate
252
260
 
253
261
  Consumes `completeCartWorkflow.hooks.validate`, which runs before any order
@@ -418,6 +426,52 @@ self-contained function — copy it from `src/storefront.ts` into your codebase
418
426
  instead. Either way, anything you don't wire is still caught by the completeCart
419
427
  gate; the preview is pure upside.
420
428
 
429
+ ### Also re-check when the shopper *enters* checkout (defense in depth)
430
+
431
+ The address-step check catches a bad address as it's entered. But a shopper can
432
+ set a *compliant* address, then add an item that's illegal to their state, and
433
+ walk up to the review step — where the block would otherwise only appear at
434
+ "Place Order". Close that gap by re-checking the **whole cart** when they land on
435
+ (or return to) the checkout page, and bouncing them back to the shipping step if
436
+ it's blocked.
437
+
438
+ The same preview does it — pass the `cartId` with **no `destination`** and it
439
+ evaluates the cart's saved address against its *current* items server-side:
440
+
441
+ ```ts
442
+ import { checkAddressCompliance } from "medusa-plugin-ordinant/storefront"
443
+
444
+ // on entering checkout (any storefront):
445
+ const block = await checkAddressCompliance(sdk, { cartId }) // no destination = full current cart
446
+ if (block) {
447
+ // send them back to the shipping step and show `block`
448
+ }
449
+ ```
450
+
451
+ In the Medusa **Next.js starter**, do it in the checkout page (a server
452
+ component) so the redirect happens before payment/review render:
453
+
454
+ ```tsx
455
+ // src/app/[countryCode]/(checkout)/checkout/page.tsx
456
+ import { getCartComplianceBlock } from "@lib/data/cart" // thin wrapper over the preview
457
+ import { redirect } from "next/navigation"
458
+
459
+ const complianceBlock = await getCartComplianceBlock(cart.id)
460
+ if (complianceBlock && step !== "address" && step !== "delivery") {
461
+ redirect(`/${countryCode}/checkout?step=delivery`) // bounce back to shipping
462
+ }
463
+ // ...and render `complianceBlock` as a banner above <CheckoutForm />.
464
+ ```
465
+
466
+ It only redirects when the shopper is *past* the shipping step, and it targets
467
+ the shipping step — so there's no redirect loop, and they can fix the address or
468
+ remove the item. Fail-open, same as the address check.
469
+
470
+ **The three layers together:** (1) the address step vets the address as it's
471
+ entered, (2) checkout entry re-checks the whole cart, (3) the completeCart gate
472
+ is the non-bypassable backstop at "Place Order". Wire 1 and 2, or neither — 3
473
+ always holds.
474
+
421
475
  ## Onboarding an existing catalog
422
476
 
423
477
  Products created **after** installation classify automatically. For the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medusa-plugin-ordinant",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Ordinant compliance for Medusa v2: catalog classification with merchant attestation and a deterministic, citation-bearing checkout gate. Compliance information and automation - not legal advice.",
5
5
  "author": "Ordinant (https://ordinant.com)",
6
6
  "license": "MIT",