@skuio/storefront 0.6.1 → 0.7.1
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.
- package/README.md +2 -2
- package/db/migrations/0008_quotes.sql +59 -0
- package/dist/api.d.ts +79 -5
- package/dist/api.js +130 -8
- package/dist/api.js.map +1 -1
- package/dist/{chunk-UCREHLZH.js → chunk-BD274TXU.js} +84 -2
- package/dist/chunk-BD274TXU.js.map +1 -0
- package/dist/{chunk-56ODALDY.js → chunk-NM5YP764.js} +1 -1
- package/dist/chunk-NM5YP764.js.map +1 -0
- package/dist/{chunk-X7VXO4QU.js → chunk-RUUUZ2EL.js} +4 -3
- package/dist/chunk-RUUUZ2EL.js.map +1 -0
- package/dist/{config-C4YCi7m4.d.ts → config-CCxTka_z.d.ts} +78 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/mail.d.ts +10 -2
- package/dist/mail.js +9 -1
- package/dist/payments/bitcoin.d.ts +1 -1
- package/dist/payments/btcpay.d.ts +1 -1
- package/dist/payments/manual.d.ts +1 -1
- package/dist/payments/manual.js +1 -1
- package/dist/payments/paypal.d.ts +1 -1
- package/dist/payments/stripe.d.ts +1 -1
- package/dist/payments.d.ts +1 -1
- package/dist/store.d.ts +163 -8
- package/dist/store.js +516 -50
- package/dist/store.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-56ODALDY.js.map +0 -1
- package/dist/chunk-UCREHLZH.js.map +0 -1
- package/dist/chunk-X7VXO4QU.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @skuio/storefront
|
|
2
2
|
|
|
3
|
-
Typed
|
|
3
|
+
Typed catalog, checkout and order primitives for storefronts on the SKU.io
|
|
4
4
|
storefront kit. Framework-agnostic: takes and returns plain data.
|
|
5
5
|
|
|
6
6
|
```ts
|
|
@@ -17,7 +17,7 @@ import { createStripeCheckout, parseStripeWebhook } from '@skuio/storefront/stri
|
|
|
17
17
|
Storefront API: live availability, checkout created by SKU.io on the
|
|
18
18
|
tenant\'s processor, orders born in SKU.io).
|
|
19
19
|
- Stripe helpers behind a separate entry point so non-Stripe stores never
|
|
20
|
-
bundle it. Prices are only ever read from the
|
|
20
|
+
bundle it. Prices are only ever read from the catalog on the server.
|
|
21
21
|
- Cart functions — pure, browser-agnostic, never trusted for money.
|
|
22
22
|
|
|
23
23
|
Start a store with `npx @skuio/cli create my-store`. Kit and docs:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
-- Quotes (request for quote): a buyer — signed in or a guest who never makes an account — asks for a price on a
|
|
2
|
+
-- list of products; the operations system prices it through the Store API; the buyer accepts it, which places an
|
|
3
|
+
-- order at exactly the quoted prices, or declines it. Money is integer minor units; the priced columns stay NULL
|
|
4
|
+
-- until an offer exists.
|
|
5
|
+
CREATE TABLE quotes (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
number INTEGER NOT NULL UNIQUE,
|
|
8
|
+
status TEXT NOT NULL, -- requested | quoted | accepted | declined | expired | cancelled
|
|
9
|
+
customer_id TEXT NOT NULL REFERENCES customers (id),
|
|
10
|
+
email TEXT NOT NULL,
|
|
11
|
+
company TEXT NULL,
|
|
12
|
+
message TEXT NULL,
|
|
13
|
+
shipping_address_json TEXT NULL,
|
|
14
|
+
currency TEXT NOT NULL,
|
|
15
|
+
subtotal INTEGER NULL,
|
|
16
|
+
shipping INTEGER NULL,
|
|
17
|
+
tax INTEGER NULL,
|
|
18
|
+
total INTEGER NULL,
|
|
19
|
+
offer_note TEXT NULL,
|
|
20
|
+
expires_at TEXT NULL,
|
|
21
|
+
external_reference TEXT NULL,
|
|
22
|
+
order_id TEXT NULL REFERENCES orders (id),
|
|
23
|
+
decline_reason TEXT NULL,
|
|
24
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
25
|
+
access_key TEXT NOT NULL,
|
|
26
|
+
created_at TEXT NOT NULL,
|
|
27
|
+
updated_at TEXT NOT NULL,
|
|
28
|
+
quoted_at TEXT NULL,
|
|
29
|
+
accepted_at TEXT NULL,
|
|
30
|
+
declined_at TEXT NULL,
|
|
31
|
+
cancelled_at TEXT NULL
|
|
32
|
+
);
|
|
33
|
+
CREATE INDEX quotes_updated ON quotes (updated_at, id);
|
|
34
|
+
CREATE INDEX quotes_customer ON quotes (customer_id, created_at);
|
|
35
|
+
CREATE INDEX quotes_email ON quotes (email, created_at);
|
|
36
|
+
CREATE INDEX quotes_status ON quotes (status, expires_at);
|
|
37
|
+
|
|
38
|
+
CREATE TABLE quote_lines (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
quote_id TEXT NOT NULL REFERENCES quotes (id) ON DELETE CASCADE,
|
|
41
|
+
sku TEXT NOT NULL,
|
|
42
|
+
name TEXT NOT NULL,
|
|
43
|
+
quantity INTEGER NOT NULL,
|
|
44
|
+
list_amount INTEGER NULL, -- the catalog price when the buyer asked; for reference only
|
|
45
|
+
unit_amount INTEGER NULL, -- the offered price; NULL until quoted
|
|
46
|
+
total_amount INTEGER NULL,
|
|
47
|
+
image_url TEXT NULL,
|
|
48
|
+
position INTEGER NOT NULL DEFAULT 0
|
|
49
|
+
);
|
|
50
|
+
CREATE INDEX quote_lines_quote ON quote_lines (quote_id, position);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE quote_events (
|
|
53
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
54
|
+
quote_id TEXT NOT NULL REFERENCES quotes (id) ON DELETE CASCADE,
|
|
55
|
+
type TEXT NOT NULL,
|
|
56
|
+
data_json TEXT NULL,
|
|
57
|
+
created_at TEXT NOT NULL
|
|
58
|
+
);
|
|
59
|
+
CREATE INDEX quote_events_quote ON quote_events (quote_id, id);
|
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CategoryRecord, ProductRecord, Connection, Store } from './store.js';
|
|
2
|
-
import { O as Order, F as Fulfillment } from './config-
|
|
2
|
+
import { O as Order, Q as QuoteStatus, F as Fulfillment, S as StoreQuote } from './config-CCxTka_z.js';
|
|
3
3
|
import './driver-Cr4l6GgG.js';
|
|
4
4
|
import './types-CWreCyxc.js';
|
|
5
5
|
|
|
@@ -79,6 +79,48 @@ interface ApiOrder {
|
|
|
79
79
|
paid_at: string | null;
|
|
80
80
|
cancelled_at: string | null;
|
|
81
81
|
}
|
|
82
|
+
interface ApiQuote {
|
|
83
|
+
id: string;
|
|
84
|
+
/** With the store's quote prefix: "Q1001". */
|
|
85
|
+
number: string;
|
|
86
|
+
status: QuoteStatus;
|
|
87
|
+
currency: string;
|
|
88
|
+
customer: {
|
|
89
|
+
id: string;
|
|
90
|
+
email: string;
|
|
91
|
+
name: string | null;
|
|
92
|
+
phone: string | null;
|
|
93
|
+
};
|
|
94
|
+
company: string | null;
|
|
95
|
+
message: string | null;
|
|
96
|
+
shipping_address: ApiAddress | null;
|
|
97
|
+
lines: {
|
|
98
|
+
id: string;
|
|
99
|
+
sku: string;
|
|
100
|
+
name: string;
|
|
101
|
+
quantity: number;
|
|
102
|
+
list_amount: ApiMoney | null;
|
|
103
|
+
unit_amount: ApiMoney | null;
|
|
104
|
+
total_amount: ApiMoney | null;
|
|
105
|
+
}[];
|
|
106
|
+
subtotal: ApiMoney | null;
|
|
107
|
+
shipping: ApiMoney | null;
|
|
108
|
+
tax: ApiMoney | null;
|
|
109
|
+
total: ApiMoney | null;
|
|
110
|
+
note: string | null;
|
|
111
|
+
expires_at: string | null;
|
|
112
|
+
external_reference: string | null;
|
|
113
|
+
/** The order the buyer's acceptance placed; it also arrives through GET /orders with `metadata.quote_id`. */
|
|
114
|
+
order_id: string | null;
|
|
115
|
+
decline_reason: string | null;
|
|
116
|
+
metadata: Record<string, string>;
|
|
117
|
+
created_at: string;
|
|
118
|
+
updated_at: string;
|
|
119
|
+
quoted_at: string | null;
|
|
120
|
+
accepted_at: string | null;
|
|
121
|
+
declined_at: string | null;
|
|
122
|
+
cancelled_at: string | null;
|
|
123
|
+
}
|
|
82
124
|
interface ApiProduct {
|
|
83
125
|
sku: string;
|
|
84
126
|
name: string;
|
|
@@ -126,8 +168,31 @@ declare function serializeFulfillment(f: Fulfillment): ApiFulfillment;
|
|
|
126
168
|
* sees, with the store's prefix (VLD4200), so the consumer shows the same number.
|
|
127
169
|
*/
|
|
128
170
|
declare function serializeOrder(o: Order, numberPrefix?: string): ApiOrder;
|
|
129
|
-
/** The Store API's view of a
|
|
130
|
-
declare function
|
|
171
|
+
/** The Store API's view of a quote. `access_key` never leaves the store. */
|
|
172
|
+
declare function serializeQuote(q: StoreQuote, numberPrefix?: string): ApiQuote;
|
|
173
|
+
/**
|
|
174
|
+
* Maps a stored image src to the public URL a consumer can load. Return null
|
|
175
|
+
* (or undefined) to fall back to the default: an absolute URL as-is, anything
|
|
176
|
+
* else resolved against the request origin.
|
|
177
|
+
*/
|
|
178
|
+
type ImageUrlResolver = (src: string) => string | null | undefined;
|
|
179
|
+
interface SerializeProductOptions {
|
|
180
|
+
/** The store's own image resolver; see StoreApiOptions.imageUrl. */
|
|
181
|
+
imageUrl?: ImageUrlResolver;
|
|
182
|
+
/** The origin relative srcs resolve against when the resolver has no answer ("https://store.example"). */
|
|
183
|
+
origin?: string;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The absolute URL the Store API reports for an image. The store's resolver
|
|
187
|
+
* wins; without one (or when it returns null) an absolute URL passes through
|
|
188
|
+
* unchanged and a relative path or bare filename is resolved against the
|
|
189
|
+
* request origin. Resolving a bare filename only produces a loadable URL when
|
|
190
|
+
* the store serves the file at that path — stores that fingerprint their
|
|
191
|
+
* assets must pass a resolver.
|
|
192
|
+
*/
|
|
193
|
+
declare function absoluteImageUrl(src: string, options?: SerializeProductOptions): string;
|
|
194
|
+
/** The Store API's view of a catalog product — the stored row, not the shopper projection. */
|
|
195
|
+
declare function serializeProduct(p: ProductRecord, options?: SerializeProductOptions): ApiProduct;
|
|
131
196
|
declare function serializeCategory(c: CategoryRecord): ApiCategory;
|
|
132
197
|
|
|
133
198
|
declare class ValidationError extends Error {
|
|
@@ -145,6 +210,15 @@ interface StoreApiOptions {
|
|
|
145
210
|
apiKey: string | undefined;
|
|
146
211
|
/** Where the API is mounted in the request URL. */
|
|
147
212
|
basePath?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Maps a stored product image src (a bare filename, a path, or an absolute
|
|
215
|
+
* URL) to the absolute URL the store actually serves it at — e.g. the
|
|
216
|
+
* fingerprinted `/_astro/<name>.<hash>.jpg` a template's `resolveImage()`
|
|
217
|
+
* finds, made absolute against the site URL. Return null to fall back to the
|
|
218
|
+
* default: an absolute src as-is, a relative one resolved against the
|
|
219
|
+
* request origin. `images[].url` in every response is always absolute.
|
|
220
|
+
*/
|
|
221
|
+
imageUrl?: ImageUrlResolver;
|
|
148
222
|
}
|
|
149
223
|
/** Who a request authenticated as: the environment key, or an approved connection. */
|
|
150
224
|
type ApiCaller = {
|
|
@@ -171,7 +245,7 @@ declare function serializeConnection(c: Connection): {
|
|
|
171
245
|
id: string;
|
|
172
246
|
name: string;
|
|
173
247
|
tenant: string | null;
|
|
174
|
-
scopes: ("orders" | "payments" | "fulfillments" | "inventory" | "catalog")[];
|
|
248
|
+
scopes: ("orders" | "payments" | "fulfillments" | "inventory" | "catalog" | "quotes")[];
|
|
175
249
|
key_prefix: string | null;
|
|
176
250
|
created_at: string;
|
|
177
251
|
issued_at: string | null;
|
|
@@ -179,4 +253,4 @@ declare function serializeConnection(c: Connection): {
|
|
|
179
253
|
revoked_at: string | null;
|
|
180
254
|
};
|
|
181
255
|
|
|
182
|
-
export { type ApiCaller, type ApiCategory, type ApiFulfillment, type ApiOrder, type ApiProduct, STORE_API_BASE, STORE_API_BASES, type StoreApiOptions, ValidationError, handleStoreApi, serializeCategory, serializeConnection, serializeFulfillment, serializeOrder, serializeProduct };
|
|
256
|
+
export { type ApiCaller, type ApiCategory, type ApiFulfillment, type ApiOrder, type ApiProduct, type ApiQuote, type ImageUrlResolver, STORE_API_BASE, STORE_API_BASES, type SerializeProductOptions, type StoreApiOptions, ValidationError, absoluteImageUrl, handleStoreApi, serializeCategory, serializeConnection, serializeFulfillment, serializeOrder, serializeProduct, serializeQuote };
|
package/dist/api.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
API_KEY_PREFIX,
|
|
3
3
|
StoreError,
|
|
4
4
|
secretsEqual
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RUUUZ2EL.js";
|
|
6
6
|
|
|
7
7
|
// src/api/serialize.ts
|
|
8
8
|
var money = (m) => ({ amount: m.amount, currency: m.currency.toUpperCase() });
|
|
@@ -64,7 +64,55 @@ function serializeOrder(o, numberPrefix = "") {
|
|
|
64
64
|
cancelled_at: o.cancelledAt
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
|
|
67
|
+
var maybeMoney = (m) => m ? money(m) : null;
|
|
68
|
+
function serializeQuote(q, numberPrefix = "") {
|
|
69
|
+
return {
|
|
70
|
+
id: q.id,
|
|
71
|
+
number: `${numberPrefix}${q.number}`,
|
|
72
|
+
status: q.status,
|
|
73
|
+
currency: q.currency.toUpperCase(),
|
|
74
|
+
customer: { id: q.customer.id, email: q.customer.email, name: q.customer.name, phone: q.customer.phone },
|
|
75
|
+
company: q.company,
|
|
76
|
+
message: q.message,
|
|
77
|
+
shipping_address: address(q.shippingAddress),
|
|
78
|
+
lines: q.lines.map((l) => ({
|
|
79
|
+
id: l.id,
|
|
80
|
+
sku: l.sku,
|
|
81
|
+
name: l.name,
|
|
82
|
+
quantity: l.quantity,
|
|
83
|
+
list_amount: maybeMoney(l.listAmount),
|
|
84
|
+
unit_amount: maybeMoney(l.unitAmount),
|
|
85
|
+
total_amount: maybeMoney(l.totalAmount)
|
|
86
|
+
})),
|
|
87
|
+
subtotal: maybeMoney(q.subtotal),
|
|
88
|
+
shipping: maybeMoney(q.shipping),
|
|
89
|
+
tax: maybeMoney(q.tax),
|
|
90
|
+
total: maybeMoney(q.total),
|
|
91
|
+
note: q.offerNote,
|
|
92
|
+
expires_at: q.expiresAt,
|
|
93
|
+
external_reference: q.externalReference,
|
|
94
|
+
order_id: q.orderId,
|
|
95
|
+
decline_reason: q.declineReason,
|
|
96
|
+
metadata: q.metadata,
|
|
97
|
+
created_at: q.createdAt,
|
|
98
|
+
updated_at: q.updatedAt,
|
|
99
|
+
quoted_at: q.quotedAt,
|
|
100
|
+
accepted_at: q.acceptedAt,
|
|
101
|
+
declined_at: q.declinedAt,
|
|
102
|
+
cancelled_at: q.cancelledAt
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
var ABSOLUTE = /^[a-z][a-z0-9+.-]*:/i;
|
|
106
|
+
function absoluteImageUrl(src, options = {}) {
|
|
107
|
+
const url = options.imageUrl?.(src) || src;
|
|
108
|
+
if (ABSOLUTE.test(url) || !options.origin) return url;
|
|
109
|
+
try {
|
|
110
|
+
return new URL(url, `${options.origin.replace(/\/+$/, "")}/`).href;
|
|
111
|
+
} catch {
|
|
112
|
+
return url;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function serializeProduct(p, options = {}) {
|
|
68
116
|
return {
|
|
69
117
|
sku: p.sku,
|
|
70
118
|
name: p.name,
|
|
@@ -78,7 +126,7 @@ function serializeProduct(p) {
|
|
|
78
126
|
position: p.position,
|
|
79
127
|
legacy_path: p.legacyPath,
|
|
80
128
|
categories: p.categories.map((c) => ({ id: c.id, slug: c.slug, name: c.name })),
|
|
81
|
-
images: p.images.map((i) => ({ url: i.url, alt: i.alt, position: i.position })),
|
|
129
|
+
images: p.images.map((i) => ({ url: absoluteImageUrl(i.url, options), alt: i.alt, position: i.position })),
|
|
82
130
|
inventory: p.inventory ? { available: p.inventory.available, allow_backorder: p.inventory.allowBackorder, updated_at: p.inventory.updatedAt } : null,
|
|
83
131
|
created_at: p.createdAt,
|
|
84
132
|
updated_at: p.updatedAt
|
|
@@ -370,6 +418,55 @@ function validateOrderPatch(body) {
|
|
|
370
418
|
finish(details, "Invalid order patch");
|
|
371
419
|
return out;
|
|
372
420
|
}
|
|
421
|
+
function validateQuotePatch(body) {
|
|
422
|
+
const details = {};
|
|
423
|
+
const out = {};
|
|
424
|
+
if ("external_reference" in body) {
|
|
425
|
+
const ref = str(details, body, "external_reference", "external_reference", { nullable: true, max: 64 });
|
|
426
|
+
if (ref !== void 0) out.external_reference = ref;
|
|
427
|
+
}
|
|
428
|
+
if (body.status !== void 0) {
|
|
429
|
+
if (body.status !== "cancelled") details.status = 'can only be set to "cancelled"';
|
|
430
|
+
else out.status = "cancelled";
|
|
431
|
+
}
|
|
432
|
+
if (body.offer !== void 0) {
|
|
433
|
+
if (!isObject(body.offer)) details.offer = "must be an object";
|
|
434
|
+
else {
|
|
435
|
+
const o = body.offer;
|
|
436
|
+
const lines = [];
|
|
437
|
+
if (!Array.isArray(o.lines) || o.lines.length === 0) details["offer.lines"] = "must be a non-empty array";
|
|
438
|
+
else
|
|
439
|
+
o.lines.forEach((raw, i) => {
|
|
440
|
+
if (!isObject(raw)) {
|
|
441
|
+
details[`offer.lines.${i}`] = "must be an object";
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
const sku = str(details, raw, "sku", `offer.lines.${i}.sku`, { required: true, max: 100 });
|
|
445
|
+
const name = str(details, raw, "name", `offer.lines.${i}.name`, { nullable: true, max: 300 });
|
|
446
|
+
if (!isInt(raw.quantity) || raw.quantity < 1) details[`offer.lines.${i}.quantity`] = "must be an integer of at least 1";
|
|
447
|
+
const unit = moneyField(details, raw.unit_amount, `offer.lines.${i}.unit_amount`, { required: true });
|
|
448
|
+
if (sku && isInt(raw.quantity) && raw.quantity >= 1 && unit) lines.push({ sku: sku.trim(), name: name ?? null, quantity: raw.quantity, unit_amount: unit });
|
|
449
|
+
});
|
|
450
|
+
const shipping = moneyField(details, o.shipping, "offer.shipping", { nullable: true });
|
|
451
|
+
const tax = moneyField(details, o.tax, "offer.tax", { nullable: true });
|
|
452
|
+
const expiresAt = isoDate(details, o.expires_at, "offer.expires_at");
|
|
453
|
+
const note = str(details, o, "note", "offer.note", { nullable: true, max: 4e3 });
|
|
454
|
+
if (o.notify_customer !== void 0 && typeof o.notify_customer !== "boolean") details["offer.notify_customer"] = "must be a boolean";
|
|
455
|
+
out.offer = {
|
|
456
|
+
lines,
|
|
457
|
+
shipping: shipping ?? null,
|
|
458
|
+
tax: tax ?? null,
|
|
459
|
+
expires_at: expiresAt ?? null,
|
|
460
|
+
note: note ?? null,
|
|
461
|
+
notify_customer: o.notify_customer === void 0 ? true : o.notify_customer
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (out.status && out.offer) details.status = "cannot be combined with an offer";
|
|
466
|
+
if (!("external_reference" in body) && body.status === void 0 && body.offer === void 0) details.body = "must include external_reference, status or offer";
|
|
467
|
+
finish(details, "Invalid quote patch");
|
|
468
|
+
return out;
|
|
469
|
+
}
|
|
373
470
|
|
|
374
471
|
// src/api/index.ts
|
|
375
472
|
var STORE_API_BASES = ["/api/store/v1", "/api/skuio/v1"];
|
|
@@ -395,7 +492,8 @@ async function handleStoreApi(request, options) {
|
|
|
395
492
|
if (scope && caller.kind === "connection" && !caller.connection.scopes.includes(scope)) {
|
|
396
493
|
return error(403, "forbidden", `This connection was not approved for "${scope}"; reconnect and approve it at /admin/connect`);
|
|
397
494
|
}
|
|
398
|
-
|
|
495
|
+
const images = { imageUrl: options.imageUrl, origin: url.origin };
|
|
496
|
+
return await route(method, path, url, request, options.store, caller, images);
|
|
399
497
|
} catch (e) {
|
|
400
498
|
if (e instanceof ValidationError) return error(422, "validation_failed", e.message, e.details);
|
|
401
499
|
if (e instanceof StoreError) return error(e.status, e.code, e.message, e.details);
|
|
@@ -448,9 +546,10 @@ function scopeFor(path) {
|
|
|
448
546
|
if (first === "orders") return /^\/orders\/[^/]+\/fulfillments$/.test(path) ? "fulfillments" : "orders";
|
|
449
547
|
if (first === "inventory") return "inventory";
|
|
450
548
|
if (first === "products" || first === "categories") return "catalog";
|
|
549
|
+
if (first === "quotes") return "quotes";
|
|
451
550
|
return null;
|
|
452
551
|
}
|
|
453
|
-
async function route(method, path, url, request, store, caller) {
|
|
552
|
+
async function route(method, path, url, request, store, caller, images) {
|
|
454
553
|
const segments = path.split("/").filter(Boolean);
|
|
455
554
|
if (path === "/health" && method === "GET") {
|
|
456
555
|
const health = await store.health();
|
|
@@ -498,6 +597,27 @@ async function route(method, path, url, request, store, caller) {
|
|
|
498
597
|
return json({ data: serializeFulfillment(result.fulfillment) }, result.created ? 201 : 200);
|
|
499
598
|
}
|
|
500
599
|
}
|
|
600
|
+
if (segments[0] === "quotes") {
|
|
601
|
+
const prefix = store.config.quotes.numberPrefix;
|
|
602
|
+
if (segments.length === 1 && method === "GET") {
|
|
603
|
+
const query = validateListQuery(url.searchParams, { defaultLimit: 50, maxLimit: 200, allowUpdatedSince: true });
|
|
604
|
+
await store.quotes.expireStale();
|
|
605
|
+
const page = await store.quotes.changedSince(query.updatedSince, query.cursor, query.limit);
|
|
606
|
+
return json({ data: page.quotes.map((q) => serializeQuote(q, prefix)), next_cursor: page.nextCursor });
|
|
607
|
+
}
|
|
608
|
+
const id = segments[1] ? decodeURIComponent(segments[1]) : "";
|
|
609
|
+
if (segments.length === 2 && id) {
|
|
610
|
+
if (method === "GET") return json({ data: serializeQuote(await store.quotes.require(id), prefix) });
|
|
611
|
+
if (method === "PATCH") {
|
|
612
|
+
const patch = validateQuotePatch(await readJson(request));
|
|
613
|
+
let quote = await store.quotes.require(id);
|
|
614
|
+
if (patch.external_reference !== void 0) quote = await store.quotes.setExternalReference(id, patch.external_reference);
|
|
615
|
+
if (patch.offer) quote = await store.quotes.offer(id, patch.offer);
|
|
616
|
+
if (patch.status === "cancelled") quote = await store.quotes.cancel(id, "Cancelled by the operations system");
|
|
617
|
+
return json({ data: serializeQuote(quote, prefix) });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
501
621
|
if (path === "/inventory" && method === "PUT") {
|
|
502
622
|
const items = validateInventory(await readJson(request));
|
|
503
623
|
return json({ data: await store.inventory.set(items) });
|
|
@@ -509,11 +629,11 @@ async function route(method, path, url, request, store, caller) {
|
|
|
509
629
|
if (path === "/products" && method === "GET") {
|
|
510
630
|
const query = validateListQuery(url.searchParams, { defaultLimit: 50, maxLimit: 200, allowUpdatedSince: true, allowActive: true });
|
|
511
631
|
const page = await store.catalog.listProducts(query);
|
|
512
|
-
return json({ data: page.products.map(serializeProduct), next_cursor: page.nextCursor });
|
|
632
|
+
return json({ data: page.products.map((p) => serializeProduct(p, images)), next_cursor: page.nextCursor });
|
|
513
633
|
}
|
|
514
634
|
if (segments[0] === "products" && segments.length === 2) {
|
|
515
635
|
const sku = decodeURIComponent(segments[1]);
|
|
516
|
-
if (method === "GET") return json({ data: serializeProduct(await store.catalog.requireProduct(sku)) });
|
|
636
|
+
if (method === "GET") return json({ data: serializeProduct(await store.catalog.requireProduct(sku), images) });
|
|
517
637
|
if (method === "DELETE") {
|
|
518
638
|
const done = await store.catalog.deactivateProduct(sku);
|
|
519
639
|
if (!done) return error(404, "not_found", `Product ${sku} not found`);
|
|
@@ -535,11 +655,13 @@ export {
|
|
|
535
655
|
STORE_API_BASE,
|
|
536
656
|
STORE_API_BASES,
|
|
537
657
|
ValidationError,
|
|
658
|
+
absoluteImageUrl,
|
|
538
659
|
handleStoreApi,
|
|
539
660
|
serializeCategory,
|
|
540
661
|
serializeConnection,
|
|
541
662
|
serializeFulfillment,
|
|
542
663
|
serializeOrder,
|
|
543
|
-
serializeProduct
|
|
664
|
+
serializeProduct,
|
|
665
|
+
serializeQuote
|
|
544
666
|
};
|
|
545
667
|
//# sourceMappingURL=api.js.map
|