@01.software/sdk 0.29.0 → 0.31.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.
- package/README.md +331 -77
- package/dist/analytics/react.cjs +4 -1
- package/dist/analytics/react.cjs.map +1 -1
- package/dist/analytics/react.js +4 -1
- package/dist/analytics/react.js.map +1 -1
- package/dist/analytics.cjs +4 -1
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.js +4 -1
- package/dist/analytics.js.map +1 -1
- package/dist/client.cjs +1541 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +28 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +1518 -0
- package/dist/client.js.map +1 -0
- package/dist/collection-client-ByzY3hWK.d.ts +218 -0
- package/dist/collection-client-DFXXz0vk.d.cts +218 -0
- package/dist/{const-DAjQYNuM.d.ts → const-AytzliEu.d.cts} +5 -7
- package/dist/{const-Dsixdi6z.d.cts → const-BGCP-OJL.d.ts} +5 -7
- package/dist/index-BGEhoDUs.d.cts +106 -0
- package/dist/index-BGEhoDUs.d.ts +106 -0
- package/dist/index.cjs +1006 -1615
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -115
- package/dist/index.d.ts +11 -115
- package/dist/index.js +932 -1559
- package/dist/index.js.map +1 -1
- package/dist/metadata.cjs +91 -0
- package/dist/metadata.cjs.map +1 -0
- package/dist/metadata.d.cts +58 -0
- package/dist/metadata.d.ts +58 -0
- package/dist/metadata.js +68 -0
- package/dist/metadata.js.map +1 -0
- package/dist/{payload-types-Ci-ZA7aM.d.cts → payload-types-Wa4-eC6x.d.cts} +794 -532
- package/dist/{payload-types-Ci-ZA7aM.d.ts → payload-types-Wa4-eC6x.d.ts} +794 -532
- package/dist/query.cjs +1841 -0
- package/dist/query.cjs.map +1 -0
- package/dist/query.d.cts +244 -0
- package/dist/query.d.ts +244 -0
- package/dist/query.js +1836 -0
- package/dist/query.js.map +1 -0
- package/dist/realtime.cjs +4 -1
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js +4 -1
- package/dist/realtime.js.map +1 -1
- package/dist/{server-BINWywT8.d.cts → server-CrsPyqEc.d.cts} +14 -31
- package/dist/{server-BINWywT8.d.ts → server-CrsPyqEc.d.ts} +14 -31
- package/dist/server.cjs +430 -846
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +137 -7
- package/dist/server.d.ts +137 -7
- package/dist/server.js +430 -864
- package/dist/server.js.map +1 -1
- package/dist/{server-Cv0Q4dPQ.d.ts → types-BX2mqDf6.d.ts} +270 -743
- package/dist/{types-BWq_WlbB.d.ts → types-CVA10VC-.d.ts} +6 -2
- package/dist/{types-zKjATmDK.d.cts → types-CmLG-7RL.d.cts} +6 -2
- package/dist/{server-C0C8dtms.d.cts → types-DChFjQGz.d.cts} +270 -743
- package/dist/ui/canvas/server.cjs +7 -6
- package/dist/ui/canvas/server.cjs.map +1 -1
- package/dist/ui/canvas/server.d.cts +1 -3
- package/dist/ui/canvas/server.d.ts +1 -3
- package/dist/ui/canvas/server.js +7 -6
- package/dist/ui/canvas/server.js.map +1 -1
- package/dist/ui/canvas.cjs +11 -10
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.d.cts +29 -6
- package/dist/ui/canvas.d.ts +29 -6
- package/dist/ui/canvas.js +11 -10
- package/dist/ui/canvas.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.d.cts +3 -3
- package/dist/webhook.d.ts +3 -3
- package/package.json +84 -15
package/dist/index.js
CHANGED
|
@@ -1,242 +1,3 @@
|
|
|
1
|
-
// src/utils/types.ts
|
|
2
|
-
var resolveRelation = (ref) => {
|
|
3
|
-
if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
|
|
4
|
-
return null;
|
|
5
|
-
return ref;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
// src/core/metadata/index.ts
|
|
9
|
-
function extractSeo(doc) {
|
|
10
|
-
const seo = doc.seo ?? {};
|
|
11
|
-
const og = seo.openGraph ?? {};
|
|
12
|
-
return {
|
|
13
|
-
title: seo.title ?? doc.title ?? null,
|
|
14
|
-
description: seo.description ?? null,
|
|
15
|
-
noIndex: seo.noIndex ?? null,
|
|
16
|
-
canonical: seo.canonical ?? null,
|
|
17
|
-
openGraph: {
|
|
18
|
-
title: og.title ?? null,
|
|
19
|
-
description: og.description ?? null,
|
|
20
|
-
image: og.image ?? null
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function generateMetadata(input, options) {
|
|
25
|
-
const title = input.title ?? void 0;
|
|
26
|
-
const description = input.description ?? void 0;
|
|
27
|
-
const ogTitle = input.openGraph?.title ?? title;
|
|
28
|
-
const ogDescription = input.openGraph?.description ?? description;
|
|
29
|
-
const image = resolveMetaImage(input.openGraph?.image);
|
|
30
|
-
return {
|
|
31
|
-
title,
|
|
32
|
-
description,
|
|
33
|
-
...input.noIndex && { robots: { index: false, follow: false } },
|
|
34
|
-
...input.canonical && { alternates: { canonical: input.canonical } },
|
|
35
|
-
openGraph: {
|
|
36
|
-
...ogTitle && { title: ogTitle },
|
|
37
|
-
...ogDescription && { description: ogDescription },
|
|
38
|
-
...options?.siteName && { siteName: options.siteName },
|
|
39
|
-
...image && { images: [image] }
|
|
40
|
-
},
|
|
41
|
-
twitter: {
|
|
42
|
-
card: image ? "summary_large_image" : "summary",
|
|
43
|
-
...ogTitle && { title: ogTitle },
|
|
44
|
-
...ogDescription && { description: ogDescription },
|
|
45
|
-
...image && { images: [image.url] }
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function resolveMetaImage(ref) {
|
|
50
|
-
const image = resolveRelation(ref);
|
|
51
|
-
if (!image) return null;
|
|
52
|
-
const sized = image.sizes?.["1536"];
|
|
53
|
-
const url = sized?.url || image.url;
|
|
54
|
-
if (!url) return null;
|
|
55
|
-
const width = sized?.url ? sized.width : image.width;
|
|
56
|
-
const height = sized?.url ? sized.height : image.height;
|
|
57
|
-
return {
|
|
58
|
-
url,
|
|
59
|
-
...width && { width },
|
|
60
|
-
...height && { height },
|
|
61
|
-
...image.alt && { alt: image.alt }
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// src/core/collection/query-builder.ts
|
|
66
|
-
var ReadOnlyCollectionQueryBuilder = class {
|
|
67
|
-
constructor(api, collection) {
|
|
68
|
-
this.api = api;
|
|
69
|
-
this.collection = collection;
|
|
70
|
-
}
|
|
71
|
-
async find(options) {
|
|
72
|
-
return this.api.requestFind(
|
|
73
|
-
`/api/${String(this.collection)}`,
|
|
74
|
-
options
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
async findById(id, options) {
|
|
78
|
-
return this.api.requestFindById(
|
|
79
|
-
`/api/${String(this.collection)}/${String(id)}`,
|
|
80
|
-
options
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
async count(options) {
|
|
84
|
-
return this.api.requestCount(
|
|
85
|
-
`/api/${String(this.collection)}/count`,
|
|
86
|
-
options
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
async findMetadata(options, metadataOptions) {
|
|
90
|
-
const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
|
|
91
|
-
const doc = docs[0];
|
|
92
|
-
if (!doc) return null;
|
|
93
|
-
return generateMetadata(
|
|
94
|
-
extractSeo(doc),
|
|
95
|
-
metadataOptions
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
async findMetadataById(id, metadataOptions) {
|
|
99
|
-
const doc = await this.findById(id, { depth: 1 });
|
|
100
|
-
return generateMetadata(
|
|
101
|
-
extractSeo(doc),
|
|
102
|
-
metadataOptions
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
var CollectionQueryBuilder = class {
|
|
107
|
-
constructor(api, collection) {
|
|
108
|
-
this.api = api;
|
|
109
|
-
this.collection = collection;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Find documents (list query)
|
|
113
|
-
* GET /api/{collection}
|
|
114
|
-
* @returns Payload CMS find response with docs array and pagination
|
|
115
|
-
*/
|
|
116
|
-
async find(options) {
|
|
117
|
-
return this.api.requestFind(
|
|
118
|
-
`/api/${String(this.collection)}`,
|
|
119
|
-
options
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Find document by ID
|
|
124
|
-
* GET /api/{collection}/{id}
|
|
125
|
-
* @returns Document object directly (no wrapper)
|
|
126
|
-
*/
|
|
127
|
-
async findById(id, options) {
|
|
128
|
-
return this.api.requestFindById(
|
|
129
|
-
`/api/${String(this.collection)}/${String(id)}`,
|
|
130
|
-
options
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Create a new document
|
|
135
|
-
* POST /api/{collection}
|
|
136
|
-
* @returns Payload CMS mutation response with doc and message
|
|
137
|
-
*/
|
|
138
|
-
async create(data, options) {
|
|
139
|
-
const endpoint = `/api/${String(this.collection)}`;
|
|
140
|
-
if (options?.file) {
|
|
141
|
-
return this.api.requestCreateWithFile(
|
|
142
|
-
endpoint,
|
|
143
|
-
data,
|
|
144
|
-
options.file,
|
|
145
|
-
options.filename
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
return this.api.requestCreate(endpoint, data);
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Update a document by ID
|
|
152
|
-
* PATCH /api/{collection}/{id}
|
|
153
|
-
* @returns Payload CMS mutation response with doc and message
|
|
154
|
-
*/
|
|
155
|
-
async update(id, data, options) {
|
|
156
|
-
const endpoint = `/api/${String(this.collection)}/${String(id)}`;
|
|
157
|
-
if (options?.file) {
|
|
158
|
-
return this.api.requestUpdateWithFile(
|
|
159
|
-
endpoint,
|
|
160
|
-
data,
|
|
161
|
-
options.file,
|
|
162
|
-
options.filename
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
return this.api.requestUpdate(endpoint, data);
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Count documents
|
|
169
|
-
* GET /api/{collection}/count
|
|
170
|
-
* @returns Count response with totalDocs
|
|
171
|
-
*/
|
|
172
|
-
async count(options) {
|
|
173
|
-
return this.api.requestCount(
|
|
174
|
-
`/api/${String(this.collection)}/count`,
|
|
175
|
-
options
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Find first matching document and return its Next.js Metadata.
|
|
180
|
-
* Applies depth: 1 (SEO image populate) and limit: 1 automatically.
|
|
181
|
-
* @returns Metadata or null if no document matches
|
|
182
|
-
*/
|
|
183
|
-
async findMetadata(options, metadataOptions) {
|
|
184
|
-
const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
|
|
185
|
-
const doc = docs[0];
|
|
186
|
-
if (!doc) return null;
|
|
187
|
-
return generateMetadata(
|
|
188
|
-
extractSeo(doc),
|
|
189
|
-
metadataOptions
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Find document by ID and return its Next.js Metadata.
|
|
194
|
-
* Applies depth: 1 (SEO image populate) automatically.
|
|
195
|
-
* @returns Metadata (throws on 404)
|
|
196
|
-
*/
|
|
197
|
-
async findMetadataById(id, metadataOptions) {
|
|
198
|
-
const doc = await this.findById(id, { depth: 1 });
|
|
199
|
-
return generateMetadata(
|
|
200
|
-
extractSeo(doc),
|
|
201
|
-
metadataOptions
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Update multiple documents (bulk update)
|
|
206
|
-
* PATCH /api/{collection}
|
|
207
|
-
* @returns Payload CMS find response with updated docs
|
|
208
|
-
*/
|
|
209
|
-
async updateMany(where, data) {
|
|
210
|
-
return this.api.requestUpdateMany(
|
|
211
|
-
`/api/${String(this.collection)}`,
|
|
212
|
-
{ where, data }
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Delete a document by ID
|
|
217
|
-
* DELETE /api/{collection}/{id}
|
|
218
|
-
* @returns Deleted document object directly (no wrapper)
|
|
219
|
-
*/
|
|
220
|
-
async remove(id) {
|
|
221
|
-
return this.api.requestDelete(
|
|
222
|
-
`/api/${String(this.collection)}/${String(id)}`
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Delete multiple documents (bulk delete)
|
|
227
|
-
* DELETE /api/{collection}
|
|
228
|
-
* @returns Payload CMS find response with deleted docs
|
|
229
|
-
*/
|
|
230
|
-
async removeMany(where) {
|
|
231
|
-
return this.api.requestDeleteMany(
|
|
232
|
-
`/api/${String(this.collection)}`,
|
|
233
|
-
{ where }
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
var ServerCollectionQueryBuilder = class extends CollectionQueryBuilder {
|
|
238
|
-
};
|
|
239
|
-
|
|
240
1
|
// src/core/collection/http-client.ts
|
|
241
2
|
import { stringify } from "qs-esm";
|
|
242
3
|
|
|
@@ -284,8 +45,16 @@ var ValidationError = class extends SDKError {
|
|
|
284
45
|
}
|
|
285
46
|
};
|
|
286
47
|
var ApiError = class extends SDKError {
|
|
287
|
-
constructor(message, status, details, userMessage, suggestion) {
|
|
288
|
-
super(
|
|
48
|
+
constructor(message, status, details, userMessage, suggestion, requestId) {
|
|
49
|
+
super(
|
|
50
|
+
"API_ERROR",
|
|
51
|
+
message,
|
|
52
|
+
status,
|
|
53
|
+
details,
|
|
54
|
+
userMessage,
|
|
55
|
+
suggestion,
|
|
56
|
+
requestId
|
|
57
|
+
);
|
|
289
58
|
this.name = "ApiError";
|
|
290
59
|
}
|
|
291
60
|
};
|
|
@@ -336,19 +105,43 @@ var UsageLimitError = class extends SDKError {
|
|
|
336
105
|
};
|
|
337
106
|
var AuthError = class extends SDKError {
|
|
338
107
|
constructor(message, details, userMessage, suggestion, requestId) {
|
|
339
|
-
super(
|
|
108
|
+
super(
|
|
109
|
+
"auth_error",
|
|
110
|
+
message,
|
|
111
|
+
401,
|
|
112
|
+
details,
|
|
113
|
+
userMessage,
|
|
114
|
+
suggestion,
|
|
115
|
+
requestId
|
|
116
|
+
);
|
|
340
117
|
this.name = "AuthError";
|
|
341
118
|
}
|
|
342
119
|
};
|
|
343
120
|
var PermissionError = class extends SDKError {
|
|
344
121
|
constructor(message, details, userMessage, suggestion, requestId) {
|
|
345
|
-
super(
|
|
122
|
+
super(
|
|
123
|
+
"permission_error",
|
|
124
|
+
message,
|
|
125
|
+
403,
|
|
126
|
+
details,
|
|
127
|
+
userMessage,
|
|
128
|
+
suggestion,
|
|
129
|
+
requestId
|
|
130
|
+
);
|
|
346
131
|
this.name = "PermissionError";
|
|
347
132
|
}
|
|
348
133
|
};
|
|
349
134
|
var NotFoundError = class extends SDKError {
|
|
350
135
|
constructor(message, details, userMessage, suggestion, requestId) {
|
|
351
|
-
super(
|
|
136
|
+
super(
|
|
137
|
+
"not_found",
|
|
138
|
+
message,
|
|
139
|
+
404,
|
|
140
|
+
details,
|
|
141
|
+
userMessage,
|
|
142
|
+
suggestion,
|
|
143
|
+
requestId
|
|
144
|
+
);
|
|
352
145
|
this.name = "NotFoundError";
|
|
353
146
|
}
|
|
354
147
|
};
|
|
@@ -360,7 +153,15 @@ var ConflictError = class extends SDKError {
|
|
|
360
153
|
};
|
|
361
154
|
var RateLimitError = class extends SDKError {
|
|
362
155
|
constructor(message, retryAfter, details, userMessage, suggestion, requestId) {
|
|
363
|
-
super(
|
|
156
|
+
super(
|
|
157
|
+
"rate_limit_exceeded",
|
|
158
|
+
message,
|
|
159
|
+
429,
|
|
160
|
+
details,
|
|
161
|
+
userMessage,
|
|
162
|
+
suggestion,
|
|
163
|
+
requestId
|
|
164
|
+
);
|
|
364
165
|
this.name = "RateLimitError";
|
|
365
166
|
this.retryAfter = retryAfter;
|
|
366
167
|
}
|
|
@@ -409,7 +210,7 @@ function isRateLimitError(error) {
|
|
|
409
210
|
}
|
|
410
211
|
var createNetworkError = (message, status, details, userMessage, suggestion) => new NetworkError(message, status, details, userMessage, suggestion);
|
|
411
212
|
var createValidationError = (message, details, userMessage, suggestion, status) => new ValidationError(message, details, userMessage, suggestion, status);
|
|
412
|
-
var createApiError = (message, status, details, userMessage, suggestion) => new ApiError(message, status, details, userMessage, suggestion);
|
|
213
|
+
var createApiError = (message, status, details, userMessage, suggestion, requestId) => new ApiError(message, status, details, userMessage, suggestion, requestId);
|
|
413
214
|
var createConfigError = (message, details, userMessage, suggestion) => new ConfigError(message, details, userMessage, suggestion);
|
|
414
215
|
var createTimeoutError = (message, details, userMessage, suggestion) => new TimeoutError(message, details, userMessage, suggestion);
|
|
415
216
|
var createUsageLimitError = (message, usage, details, userMessage, suggestion) => new UsageLimitError(message, usage, details, userMessage, suggestion);
|
|
@@ -417,7 +218,14 @@ var createAuthError = (message, details, userMessage, suggestion, requestId) =>
|
|
|
417
218
|
var createPermissionError = (message, details, userMessage, suggestion, requestId) => new PermissionError(message, details, userMessage, suggestion, requestId);
|
|
418
219
|
var createNotFoundError = (message, details, userMessage, suggestion, requestId) => new NotFoundError(message, details, userMessage, suggestion, requestId);
|
|
419
220
|
var createConflictError = (message, details, userMessage, suggestion, requestId) => new ConflictError(message, details, userMessage, suggestion, requestId);
|
|
420
|
-
var createRateLimitError = (message, retryAfter, details, userMessage, suggestion, requestId) => new RateLimitError(
|
|
221
|
+
var createRateLimitError = (message, retryAfter, details, userMessage, suggestion, requestId) => new RateLimitError(
|
|
222
|
+
message,
|
|
223
|
+
retryAfter,
|
|
224
|
+
details,
|
|
225
|
+
userMessage,
|
|
226
|
+
suggestion,
|
|
227
|
+
requestId
|
|
228
|
+
);
|
|
421
229
|
|
|
422
230
|
// src/core/internal/utils/credentials.ts
|
|
423
231
|
function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
|
|
@@ -430,7 +238,10 @@ function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
|
|
|
430
238
|
}
|
|
431
239
|
|
|
432
240
|
// src/core/client/types.ts
|
|
433
|
-
function resolveApiUrl() {
|
|
241
|
+
function resolveApiUrl(apiUrl) {
|
|
242
|
+
if (apiUrl) {
|
|
243
|
+
return apiUrl.replace(/\/$/, "");
|
|
244
|
+
}
|
|
434
245
|
if (typeof process !== "undefined" && process.env) {
|
|
435
246
|
const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
|
|
436
247
|
if (envUrl) {
|
|
@@ -454,6 +265,22 @@ function debugLog(debug, type, message, data) {
|
|
|
454
265
|
console.groupEnd();
|
|
455
266
|
}
|
|
456
267
|
}
|
|
268
|
+
function redactSensitiveHeader(value) {
|
|
269
|
+
const prefix = value.toLowerCase().startsWith("bearer ") ? "Bearer " : "";
|
|
270
|
+
return value.length > 20 ? `${prefix}...****${value.slice(-8)}` : "****";
|
|
271
|
+
}
|
|
272
|
+
function redactSensitiveHeaders(headers) {
|
|
273
|
+
const redacted = Object.fromEntries(headers.entries());
|
|
274
|
+
if (redacted.authorization) {
|
|
275
|
+
redacted.authorization = redactSensitiveHeader(redacted.authorization);
|
|
276
|
+
}
|
|
277
|
+
if (redacted["x-preview-token"]) {
|
|
278
|
+
redacted["x-preview-token"] = redactSensitiveHeader(
|
|
279
|
+
redacted["x-preview-token"]
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return redacted;
|
|
283
|
+
}
|
|
457
284
|
function getErrorSuggestion(status) {
|
|
458
285
|
if (status === 400)
|
|
459
286
|
return "The request data failed validation. Check field values and types.";
|
|
@@ -528,6 +355,12 @@ async function parseErrorBody(response) {
|
|
|
528
355
|
return fallback;
|
|
529
356
|
}
|
|
530
357
|
}
|
|
358
|
+
function getParsedErrorSuggestion(status, parsed) {
|
|
359
|
+
if (status === 403 && parsed.reason === "origin_not_allowed") {
|
|
360
|
+
return "Add the request origin to the tenant Browser API origins, then retry the browser request.";
|
|
361
|
+
}
|
|
362
|
+
return getErrorSuggestion(status);
|
|
363
|
+
}
|
|
531
364
|
async function delay(ms) {
|
|
532
365
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
533
366
|
}
|
|
@@ -541,7 +374,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
|
|
|
541
374
|
...parsed.errors && { errors: parsed.errors },
|
|
542
375
|
...parsed.body && { body: parsed.body }
|
|
543
376
|
};
|
|
544
|
-
const suggestion =
|
|
377
|
+
const suggestion = getParsedErrorSuggestion(status, parsed);
|
|
545
378
|
if (status === 400 || status === 422) {
|
|
546
379
|
return attachRequestId(
|
|
547
380
|
createValidationError(
|
|
@@ -611,6 +444,7 @@ function createHttpStatusError(status, parsed, details, requestId) {
|
|
|
611
444
|
}
|
|
612
445
|
async function httpFetch(url, options) {
|
|
613
446
|
const {
|
|
447
|
+
apiUrl,
|
|
614
448
|
publishableKey,
|
|
615
449
|
secretKey,
|
|
616
450
|
customerToken,
|
|
@@ -620,7 +454,7 @@ async function httpFetch(url, options) {
|
|
|
620
454
|
onUnauthorized,
|
|
621
455
|
...requestInit
|
|
622
456
|
} = options || {};
|
|
623
|
-
const baseUrl = resolveApiUrl();
|
|
457
|
+
const baseUrl = resolveApiUrl(apiUrl);
|
|
624
458
|
const retryConfig = {
|
|
625
459
|
maxRetries: retry?.maxRetries ?? 3,
|
|
626
460
|
retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,
|
|
@@ -646,11 +480,7 @@ async function httpFetch(url, options) {
|
|
|
646
480
|
if (!headers.has("Content-Type") && requestInit.body && !(requestInit.body instanceof FormData)) {
|
|
647
481
|
headers.set("Content-Type", "application/json");
|
|
648
482
|
}
|
|
649
|
-
const redactedHeaders =
|
|
650
|
-
if (redactedHeaders["authorization"]) {
|
|
651
|
-
const token = redactedHeaders["authorization"];
|
|
652
|
-
redactedHeaders["authorization"] = token.length > 20 ? `Bearer ...****${token.slice(-8)}` : "****";
|
|
653
|
-
}
|
|
483
|
+
const redactedHeaders = redactSensitiveHeaders(headers);
|
|
654
484
|
debugLog(debug, "request", url, {
|
|
655
485
|
method: requestInit.method || "GET",
|
|
656
486
|
headers: redactedHeaders,
|
|
@@ -668,7 +498,7 @@ async function httpFetch(url, options) {
|
|
|
668
498
|
debugLog(debug, "response", url, {
|
|
669
499
|
status: response.status,
|
|
670
500
|
statusText: response.statusText,
|
|
671
|
-
headers:
|
|
501
|
+
headers: redactSensitiveHeaders(response.headers)
|
|
672
502
|
});
|
|
673
503
|
if (!response.ok) {
|
|
674
504
|
if (isUsageLimitExceededResponse(response)) {
|
|
@@ -809,7 +639,7 @@ async function httpFetch(url, options) {
|
|
|
809
639
|
|
|
810
640
|
// src/core/collection/http-client.ts
|
|
811
641
|
var HttpClient = class {
|
|
812
|
-
constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId) {
|
|
642
|
+
constructor(publishableKey, secretKey, getCustomerToken, onUnauthorized, onRequestId, apiUrl) {
|
|
813
643
|
this.publishableKey = requirePublishableKeyForSecret(
|
|
814
644
|
"CollectionClient",
|
|
815
645
|
publishableKey,
|
|
@@ -819,9 +649,11 @@ var HttpClient = class {
|
|
|
819
649
|
this.getCustomerToken = getCustomerToken;
|
|
820
650
|
this.onUnauthorized = onUnauthorized;
|
|
821
651
|
this.onRequestId = onRequestId;
|
|
652
|
+
this.apiUrl = apiUrl;
|
|
822
653
|
}
|
|
823
654
|
get defaultOptions() {
|
|
824
655
|
const opts = {
|
|
656
|
+
apiUrl: this.apiUrl,
|
|
825
657
|
publishableKey: this.publishableKey,
|
|
826
658
|
secretKey: this.secretKey
|
|
827
659
|
};
|
|
@@ -939,159 +771,113 @@ var HttpClient = class {
|
|
|
939
771
|
}
|
|
940
772
|
};
|
|
941
773
|
|
|
942
|
-
// src/
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
774
|
+
// src/utils/types.ts
|
|
775
|
+
var resolveRelation = (ref) => {
|
|
776
|
+
if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
|
|
777
|
+
return null;
|
|
778
|
+
return ref;
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// src/core/metadata/index.ts
|
|
782
|
+
function extractSeo(doc) {
|
|
783
|
+
const seo = doc.seo ?? {};
|
|
784
|
+
const og = seo.openGraph ?? {};
|
|
785
|
+
return {
|
|
786
|
+
title: seo.title ?? doc.title ?? null,
|
|
787
|
+
description: seo.description ?? null,
|
|
788
|
+
noIndex: seo.noIndex ?? null,
|
|
789
|
+
canonical: seo.canonical ?? null,
|
|
790
|
+
openGraph: {
|
|
791
|
+
title: og.title ?? null,
|
|
792
|
+
description: og.description ?? null,
|
|
793
|
+
image: og.image ?? null
|
|
794
|
+
}
|
|
795
|
+
};
|
|
950
796
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
...
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
*/
|
|
998
|
-
async requestCreate(endpoint, data) {
|
|
999
|
-
const response = await this.fetchWithTracking(endpoint, {
|
|
1000
|
-
...this.defaultOptions,
|
|
1001
|
-
method: "POST",
|
|
1002
|
-
body: data ? JSON.stringify(data) : void 0
|
|
1003
|
-
});
|
|
1004
|
-
return this.parseMutationResponse(response);
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Update document
|
|
1008
|
-
* PATCH /api/{collection}/{id}
|
|
1009
|
-
*/
|
|
1010
|
-
async requestUpdate(endpoint, data) {
|
|
1011
|
-
const response = await this.fetchWithTracking(endpoint, {
|
|
1012
|
-
...this.defaultOptions,
|
|
1013
|
-
method: "PATCH",
|
|
1014
|
-
body: data ? JSON.stringify(data) : void 0
|
|
1015
|
-
});
|
|
1016
|
-
return this.parseMutationResponse(response);
|
|
1017
|
-
}
|
|
1018
|
-
/**
|
|
1019
|
-
* Count documents
|
|
1020
|
-
* GET /api/{collection}/count
|
|
1021
|
-
*/
|
|
1022
|
-
async requestCount(endpoint, options) {
|
|
1023
|
-
const url = this.buildUrl(endpoint, options);
|
|
1024
|
-
const response = await this.fetchWithTracking(url, {
|
|
1025
|
-
...this.defaultOptions,
|
|
1026
|
-
method: "GET"
|
|
1027
|
-
});
|
|
1028
|
-
return this.parseDocumentResponse(response);
|
|
1029
|
-
}
|
|
1030
|
-
/**
|
|
1031
|
-
* Update multiple documents (bulk update)
|
|
1032
|
-
* PATCH /api/{collection}
|
|
1033
|
-
*/
|
|
1034
|
-
async requestUpdateMany(endpoint, data) {
|
|
1035
|
-
const response = await this.fetchWithTracking(endpoint, {
|
|
1036
|
-
...this.defaultOptions,
|
|
1037
|
-
method: "PATCH",
|
|
1038
|
-
body: JSON.stringify(data)
|
|
1039
|
-
});
|
|
1040
|
-
return this.parseFindResponse(response);
|
|
797
|
+
function generateMetadata(input, options) {
|
|
798
|
+
const title = input.title ?? void 0;
|
|
799
|
+
const description = input.description ?? void 0;
|
|
800
|
+
const ogTitle = input.openGraph?.title ?? title;
|
|
801
|
+
const ogDescription = input.openGraph?.description ?? description;
|
|
802
|
+
const image = resolveMetaImage(input.openGraph?.image);
|
|
803
|
+
return {
|
|
804
|
+
title,
|
|
805
|
+
description,
|
|
806
|
+
...input.noIndex && { robots: { index: false, follow: false } },
|
|
807
|
+
...input.canonical && { alternates: { canonical: input.canonical } },
|
|
808
|
+
openGraph: {
|
|
809
|
+
...ogTitle && { title: ogTitle },
|
|
810
|
+
...ogDescription && { description: ogDescription },
|
|
811
|
+
...options?.siteName && { siteName: options.siteName },
|
|
812
|
+
...image && { images: [image] }
|
|
813
|
+
},
|
|
814
|
+
twitter: {
|
|
815
|
+
card: image ? "summary_large_image" : "summary",
|
|
816
|
+
...ogTitle && { title: ogTitle },
|
|
817
|
+
...ogDescription && { description: ogDescription },
|
|
818
|
+
...image && { images: [image.url] }
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function resolveMetaImage(ref) {
|
|
823
|
+
const image = resolveRelation(ref);
|
|
824
|
+
if (!image) return null;
|
|
825
|
+
const sized = image.sizes?.["1536"];
|
|
826
|
+
const url = sized?.url || image.url;
|
|
827
|
+
if (!url) return null;
|
|
828
|
+
const width = sized?.url ? sized.width : image.width;
|
|
829
|
+
const height = sized?.url ? sized.height : image.height;
|
|
830
|
+
return {
|
|
831
|
+
url,
|
|
832
|
+
...width && { width },
|
|
833
|
+
...height && { height },
|
|
834
|
+
...image.alt && { alt: image.alt }
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/core/collection/query-builder.ts
|
|
839
|
+
var ReadOnlyCollectionQueryBuilder = class {
|
|
840
|
+
constructor(api, collection) {
|
|
841
|
+
this.api = api;
|
|
842
|
+
this.collection = collection;
|
|
1041
843
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const response = await this.fetchWithTracking(endpoint, {
|
|
1048
|
-
...this.defaultOptions,
|
|
1049
|
-
method: "DELETE"
|
|
1050
|
-
});
|
|
1051
|
-
return this.parseDocumentResponse(response);
|
|
844
|
+
async find(options) {
|
|
845
|
+
return this.api.requestFind(
|
|
846
|
+
`/api/${String(this.collection)}`,
|
|
847
|
+
options
|
|
848
|
+
);
|
|
1052
849
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const response = await this.fetchWithTracking(endpoint, {
|
|
1059
|
-
...this.defaultOptions,
|
|
1060
|
-
method: "DELETE",
|
|
1061
|
-
body: JSON.stringify(data)
|
|
1062
|
-
});
|
|
1063
|
-
return this.parseFindResponse(response);
|
|
850
|
+
async findById(id, options) {
|
|
851
|
+
return this.api.requestFindById(
|
|
852
|
+
`/api/${String(this.collection)}/${String(id)}`,
|
|
853
|
+
options
|
|
854
|
+
);
|
|
1064
855
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
const response = await this.fetchWithTracking(endpoint, {
|
|
1071
|
-
...this.defaultOptions,
|
|
1072
|
-
method: "POST",
|
|
1073
|
-
body: buildPayloadFormData(data, file, filename)
|
|
1074
|
-
});
|
|
1075
|
-
return this.parseMutationResponse(response);
|
|
856
|
+
async count(options) {
|
|
857
|
+
return this.api.requestCount(
|
|
858
|
+
`/api/${String(this.collection)}/count`,
|
|
859
|
+
options
|
|
860
|
+
);
|
|
1076
861
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
body: buildPayloadFormData(data, file, filename)
|
|
1086
|
-
});
|
|
1087
|
-
return this.parseMutationResponse(response);
|
|
862
|
+
async findMetadata(options, metadataOptions) {
|
|
863
|
+
const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
|
|
864
|
+
const doc = docs[0];
|
|
865
|
+
if (!doc) return null;
|
|
866
|
+
return generateMetadata(
|
|
867
|
+
extractSeo(doc),
|
|
868
|
+
metadataOptions
|
|
869
|
+
);
|
|
1088
870
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
871
|
+
async findMetadataById(id, metadataOptions) {
|
|
872
|
+
const doc = await this.findById(id, { depth: 1 });
|
|
873
|
+
return generateMetadata(
|
|
874
|
+
extractSeo(doc),
|
|
875
|
+
metadataOptions
|
|
876
|
+
);
|
|
1093
877
|
}
|
|
1094
878
|
};
|
|
879
|
+
|
|
880
|
+
// src/core/collection/collection-client.ts
|
|
1095
881
|
var ReadOnlyCollectionClient = class extends HttpClient {
|
|
1096
882
|
from(collection) {
|
|
1097
883
|
return new ReadOnlyCollectionQueryBuilder(this, collection);
|
|
@@ -1122,126 +908,6 @@ var ReadOnlyCollectionClient = class extends HttpClient {
|
|
|
1122
908
|
}
|
|
1123
909
|
};
|
|
1124
910
|
|
|
1125
|
-
// src/core/collection/const.ts
|
|
1126
|
-
var INTERNAL_COLLECTIONS = [
|
|
1127
|
-
"users",
|
|
1128
|
-
"payload-kv",
|
|
1129
|
-
"payload-locked-documents",
|
|
1130
|
-
"payload-preferences",
|
|
1131
|
-
"payload-migrations",
|
|
1132
|
-
"payload-folders",
|
|
1133
|
-
"field-configs",
|
|
1134
|
-
"system-media",
|
|
1135
|
-
"track-assets",
|
|
1136
|
-
"audiences",
|
|
1137
|
-
"email-logs",
|
|
1138
|
-
"api-usage",
|
|
1139
|
-
"tenant-analytics-daily",
|
|
1140
|
-
"tenant-web-analytics-config",
|
|
1141
|
-
"analytics-event-schemas",
|
|
1142
|
-
"subscriptions",
|
|
1143
|
-
"billing-history",
|
|
1144
|
-
"inventory-reservations",
|
|
1145
|
-
"order-status-logs",
|
|
1146
|
-
"api-keys",
|
|
1147
|
-
"personal-access-tokens",
|
|
1148
|
-
"tenant-entitlements",
|
|
1149
|
-
"tenant-purge-jobs",
|
|
1150
|
-
"direct-upload-sessions",
|
|
1151
|
-
"webhook-events",
|
|
1152
|
-
"webhook-deliveries",
|
|
1153
|
-
"audit-logs",
|
|
1154
|
-
"plans",
|
|
1155
|
-
"webhooks",
|
|
1156
|
-
"event-registrations"
|
|
1157
|
-
];
|
|
1158
|
-
var COLLECTIONS = [
|
|
1159
|
-
"tenants",
|
|
1160
|
-
"tenant-metadata",
|
|
1161
|
-
"tenant-logos",
|
|
1162
|
-
"products",
|
|
1163
|
-
"product-variants",
|
|
1164
|
-
"product-options",
|
|
1165
|
-
"product-option-values",
|
|
1166
|
-
"product-categories",
|
|
1167
|
-
"product-tags",
|
|
1168
|
-
"product-collections",
|
|
1169
|
-
"brands",
|
|
1170
|
-
"brand-logos",
|
|
1171
|
-
"orders",
|
|
1172
|
-
"order-items",
|
|
1173
|
-
"returns",
|
|
1174
|
-
"return-items",
|
|
1175
|
-
"fulfillments",
|
|
1176
|
-
"fulfillment-items",
|
|
1177
|
-
"transactions",
|
|
1178
|
-
"customers",
|
|
1179
|
-
"customer-profiles",
|
|
1180
|
-
"customer-profile-lists",
|
|
1181
|
-
"customer-addresses",
|
|
1182
|
-
"carts",
|
|
1183
|
-
"cart-items",
|
|
1184
|
-
"discounts",
|
|
1185
|
-
"shipping-policies",
|
|
1186
|
-
"shipping-zones",
|
|
1187
|
-
"documents",
|
|
1188
|
-
"document-categories",
|
|
1189
|
-
"document-types",
|
|
1190
|
-
"articles",
|
|
1191
|
-
"article-authors",
|
|
1192
|
-
"article-categories",
|
|
1193
|
-
"article-tags",
|
|
1194
|
-
"playlists",
|
|
1195
|
-
"playlist-categories",
|
|
1196
|
-
"playlist-tags",
|
|
1197
|
-
"tracks",
|
|
1198
|
-
"track-categories",
|
|
1199
|
-
"track-tags",
|
|
1200
|
-
"galleries",
|
|
1201
|
-
"gallery-categories",
|
|
1202
|
-
"gallery-tags",
|
|
1203
|
-
"gallery-items",
|
|
1204
|
-
"links",
|
|
1205
|
-
"link-categories",
|
|
1206
|
-
"link-tags",
|
|
1207
|
-
"canvases",
|
|
1208
|
-
"canvas-node-types",
|
|
1209
|
-
"canvas-edge-types",
|
|
1210
|
-
"canvas-categories",
|
|
1211
|
-
"canvas-tags",
|
|
1212
|
-
"canvas-nodes",
|
|
1213
|
-
"canvas-edges",
|
|
1214
|
-
"videos",
|
|
1215
|
-
"video-categories",
|
|
1216
|
-
"video-tags",
|
|
1217
|
-
"live-streams",
|
|
1218
|
-
"images",
|
|
1219
|
-
"forms",
|
|
1220
|
-
"form-submissions",
|
|
1221
|
-
// Community
|
|
1222
|
-
"posts",
|
|
1223
|
-
"comments",
|
|
1224
|
-
"reactions",
|
|
1225
|
-
"reaction-types",
|
|
1226
|
-
"bookmarks",
|
|
1227
|
-
"post-categories",
|
|
1228
|
-
// Events
|
|
1229
|
-
"event-calendars",
|
|
1230
|
-
"events",
|
|
1231
|
-
"event-categories",
|
|
1232
|
-
"event-occurrences",
|
|
1233
|
-
"event-tags"
|
|
1234
|
-
];
|
|
1235
|
-
var SERVER_ONLY_COLLECTIONS = [
|
|
1236
|
-
"customer-groups",
|
|
1237
|
-
"reports",
|
|
1238
|
-
"community-bans"
|
|
1239
|
-
];
|
|
1240
|
-
var SERVER_COLLECTIONS = [
|
|
1241
|
-
...COLLECTIONS,
|
|
1242
|
-
...SERVER_ONLY_COLLECTIONS
|
|
1243
|
-
];
|
|
1244
|
-
|
|
1245
911
|
// src/core/api/parse-response.ts
|
|
1246
912
|
async function parseApiResponse(response, endpoint) {
|
|
1247
913
|
let data;
|
|
@@ -1265,7 +931,7 @@ async function parseApiResponse(response, endpoint) {
|
|
|
1265
931
|
if (reason === "validation_failed") {
|
|
1266
932
|
throw attachRequestId(createValidationError(errorMessage, data, errorMessage), requestId);
|
|
1267
933
|
}
|
|
1268
|
-
if (reason === "token_expired" || reason === "token_invalid" || reason === "key_invalid" || reason === "key_revoked") {
|
|
934
|
+
if (reason === "token_expired" || reason === "token_invalid" || reason === "preview_token_invalid" || reason === "preview_token_required" || reason === "key_invalid" || reason === "key_revoked") {
|
|
1269
935
|
throw attachRequestId(createAuthError(errorMessage, data, errorMessage), requestId);
|
|
1270
936
|
}
|
|
1271
937
|
if (reason === "forbidden") {
|
|
@@ -1297,6 +963,7 @@ var CommunityClient = class {
|
|
|
1297
963
|
options.secretKey
|
|
1298
964
|
);
|
|
1299
965
|
this.secretKey = options.secretKey;
|
|
966
|
+
this.apiUrl = options.apiUrl;
|
|
1300
967
|
this.customerToken = options.customerToken;
|
|
1301
968
|
this.onUnauthorized = options.onUnauthorized;
|
|
1302
969
|
this.onRequestId = options.onRequestId;
|
|
@@ -1311,6 +978,7 @@ var CommunityClient = class {
|
|
|
1311
978
|
try {
|
|
1312
979
|
const response = await httpFetch(endpoint, {
|
|
1313
980
|
method,
|
|
981
|
+
apiUrl: this.apiUrl,
|
|
1314
982
|
publishableKey: this.publishableKey,
|
|
1315
983
|
secretKey: this.secretKey,
|
|
1316
984
|
customerToken: token ?? void 0,
|
|
@@ -1459,53 +1127,6 @@ var CommunityClient = class {
|
|
|
1459
1127
|
}
|
|
1460
1128
|
};
|
|
1461
1129
|
|
|
1462
|
-
// src/core/api/base-api.ts
|
|
1463
|
-
var BaseApi = class {
|
|
1464
|
-
constructor(apiName, options) {
|
|
1465
|
-
if (!options.secretKey) {
|
|
1466
|
-
throw createConfigError(`secretKey is required for ${apiName}.`);
|
|
1467
|
-
}
|
|
1468
|
-
this.publishableKey = requirePublishableKeyForSecret(
|
|
1469
|
-
apiName,
|
|
1470
|
-
options.publishableKey,
|
|
1471
|
-
options.secretKey
|
|
1472
|
-
);
|
|
1473
|
-
this.secretKey = options.secretKey;
|
|
1474
|
-
this.onRequestId = options.onRequestId;
|
|
1475
|
-
}
|
|
1476
|
-
async request(endpoint, body, options) {
|
|
1477
|
-
const method = options?.method ?? "POST";
|
|
1478
|
-
try {
|
|
1479
|
-
const response = await httpFetch(endpoint, {
|
|
1480
|
-
method,
|
|
1481
|
-
publishableKey: this.publishableKey,
|
|
1482
|
-
secretKey: this.secretKey,
|
|
1483
|
-
...body !== void 0 && { body: JSON.stringify(body) },
|
|
1484
|
-
...options?.headers && { headers: options.headers }
|
|
1485
|
-
});
|
|
1486
|
-
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1487
|
-
return parseApiResponse(response, endpoint);
|
|
1488
|
-
} catch (err) {
|
|
1489
|
-
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
1490
|
-
this.onRequestId?.(id);
|
|
1491
|
-
throw err;
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
};
|
|
1495
|
-
|
|
1496
|
-
// src/core/community/moderation-api.ts
|
|
1497
|
-
var ModerationApi = class extends BaseApi {
|
|
1498
|
-
constructor(options) {
|
|
1499
|
-
super("ModerationApi", options);
|
|
1500
|
-
}
|
|
1501
|
-
banCustomer(params) {
|
|
1502
|
-
return this.request("/api/community-bans/ban", params);
|
|
1503
|
-
}
|
|
1504
|
-
unbanCustomer(params) {
|
|
1505
|
-
return this.request("/api/community-bans/unban", params);
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
1130
|
// src/core/customer/customer-auth.ts
|
|
1510
1131
|
var DEFAULT_TIMEOUT2 = 15e3;
|
|
1511
1132
|
function safeGetItem(key) {
|
|
@@ -1516,10 +1137,10 @@ function safeGetItem(key) {
|
|
|
1516
1137
|
}
|
|
1517
1138
|
}
|
|
1518
1139
|
var CustomerAuth = class {
|
|
1519
|
-
constructor(publishableKey, options) {
|
|
1140
|
+
constructor(publishableKey, options, apiUrl) {
|
|
1520
1141
|
this.refreshPromise = null;
|
|
1521
1142
|
this.publishableKey = publishableKey;
|
|
1522
|
-
this.baseUrl = resolveApiUrl();
|
|
1143
|
+
this.baseUrl = resolveApiUrl(apiUrl);
|
|
1523
1144
|
const persist = options?.persist ?? true;
|
|
1524
1145
|
if (persist) {
|
|
1525
1146
|
const key = typeof persist === "string" ? persist : "customer-token";
|
|
@@ -1750,8 +1371,8 @@ var CustomerAuth = class {
|
|
|
1750
1371
|
|
|
1751
1372
|
// src/core/customer/customer-namespace.ts
|
|
1752
1373
|
var CustomerNamespace = class {
|
|
1753
|
-
constructor(publishableKey, options) {
|
|
1754
|
-
this.auth = new CustomerAuth(publishableKey, options);
|
|
1374
|
+
constructor(publishableKey, options, apiUrl) {
|
|
1375
|
+
this.auth = new CustomerAuth(publishableKey, options, apiUrl);
|
|
1755
1376
|
}
|
|
1756
1377
|
};
|
|
1757
1378
|
|
|
@@ -1769,6 +1390,7 @@ var CartApi = class {
|
|
|
1769
1390
|
options.secretKey
|
|
1770
1391
|
);
|
|
1771
1392
|
this.secretKey = options.secretKey;
|
|
1393
|
+
this.apiUrl = options.apiUrl;
|
|
1772
1394
|
this.customerToken = options.customerToken;
|
|
1773
1395
|
this.onUnauthorized = options.onUnauthorized;
|
|
1774
1396
|
this.onRequestId = options.onRequestId;
|
|
@@ -1778,6 +1400,7 @@ var CartApi = class {
|
|
|
1778
1400
|
try {
|
|
1779
1401
|
const response = await httpFetch(endpoint, {
|
|
1780
1402
|
method,
|
|
1403
|
+
apiUrl: this.apiUrl,
|
|
1781
1404
|
publishableKey: this.publishableKey,
|
|
1782
1405
|
secretKey: this.secretKey,
|
|
1783
1406
|
customerToken: token ?? void 0,
|
|
@@ -1828,6 +1451,7 @@ var CommerceClient = class {
|
|
|
1828
1451
|
constructor(options) {
|
|
1829
1452
|
const cartApi = new CartApi({
|
|
1830
1453
|
publishableKey: options.publishableKey,
|
|
1454
|
+
apiUrl: options.apiUrl,
|
|
1831
1455
|
customerToken: options.customerToken,
|
|
1832
1456
|
onUnauthorized: options.onUnauthorized,
|
|
1833
1457
|
onRequestId: options.onRequestId
|
|
@@ -1837,6 +1461,7 @@ var CommerceClient = class {
|
|
|
1837
1461
|
try {
|
|
1838
1462
|
const response = await httpFetch(endpoint, {
|
|
1839
1463
|
method: "POST",
|
|
1464
|
+
apiUrl: options.apiUrl,
|
|
1840
1465
|
publishableKey: options.publishableKey,
|
|
1841
1466
|
customerToken: token ?? void 0,
|
|
1842
1467
|
...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
|
|
@@ -1884,68 +1509,105 @@ var CommerceClient = class {
|
|
|
1884
1509
|
}
|
|
1885
1510
|
};
|
|
1886
1511
|
|
|
1887
|
-
// src/core/
|
|
1888
|
-
var
|
|
1512
|
+
// src/core/client/client.ts
|
|
1513
|
+
var Client = class {
|
|
1889
1514
|
constructor(options) {
|
|
1890
|
-
|
|
1515
|
+
this.lastRequestId = null;
|
|
1516
|
+
const publishableKey = options.publishableKey;
|
|
1517
|
+
if (!publishableKey) {
|
|
1518
|
+
throw createConfigError("publishableKey is required.");
|
|
1519
|
+
}
|
|
1520
|
+
this.config = { ...options, publishableKey };
|
|
1521
|
+
const metadata = {
|
|
1522
|
+
timestamp: Date.now(),
|
|
1523
|
+
userAgent: typeof window !== "undefined" ? window.navigator?.userAgent : "Node.js"
|
|
1524
|
+
};
|
|
1525
|
+
this.state = { metadata };
|
|
1526
|
+
this.customer = new CustomerNamespace(
|
|
1527
|
+
this.config.publishableKey,
|
|
1528
|
+
options.customer,
|
|
1529
|
+
this.config.apiUrl
|
|
1530
|
+
);
|
|
1531
|
+
const onUnauthorized = async () => {
|
|
1532
|
+
try {
|
|
1533
|
+
const result = await this.customer.auth.refreshToken();
|
|
1534
|
+
return result.token ?? null;
|
|
1535
|
+
} catch {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
const onRequestId = (id) => {
|
|
1540
|
+
this.lastRequestId = id;
|
|
1541
|
+
};
|
|
1542
|
+
this.commerce = new CommerceClient({
|
|
1543
|
+
publishableKey: this.config.publishableKey,
|
|
1544
|
+
apiUrl: this.config.apiUrl,
|
|
1545
|
+
customerToken: () => this.customer.auth.getToken(),
|
|
1546
|
+
onUnauthorized,
|
|
1547
|
+
onRequestId,
|
|
1548
|
+
customerAuth: this.customer.auth
|
|
1549
|
+
});
|
|
1550
|
+
this.community = new CommunityClient({
|
|
1551
|
+
publishableKey: this.config.publishableKey,
|
|
1552
|
+
apiUrl: this.config.apiUrl,
|
|
1553
|
+
customerToken: () => this.customer.auth.getToken(),
|
|
1554
|
+
onUnauthorized,
|
|
1555
|
+
onRequestId
|
|
1556
|
+
});
|
|
1557
|
+
this.collections = new ReadOnlyCollectionClient(
|
|
1558
|
+
this.config.publishableKey,
|
|
1559
|
+
void 0,
|
|
1560
|
+
() => this.customer.auth.getToken(),
|
|
1561
|
+
onUnauthorized,
|
|
1562
|
+
onRequestId,
|
|
1563
|
+
this.config.apiUrl
|
|
1564
|
+
);
|
|
1891
1565
|
}
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
* Results reflect available stock at the moment of the call and are not guaranteed
|
|
1895
|
-
* to remain available by the time an order is placed.
|
|
1896
|
-
*/
|
|
1897
|
-
stockCheck(params) {
|
|
1898
|
-
return this.request("/api/products/stock-check", params);
|
|
1566
|
+
getState() {
|
|
1567
|
+
return { ...this.state };
|
|
1899
1568
|
}
|
|
1900
|
-
|
|
1901
|
-
return this.
|
|
1902
|
-
|
|
1903
|
-
|
|
1569
|
+
getConfig() {
|
|
1570
|
+
return { ...this.config };
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
function createClient(options) {
|
|
1574
|
+
return new Client(options);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// src/core/api/base-api.ts
|
|
1578
|
+
var BaseApi = class {
|
|
1579
|
+
constructor(apiName, options) {
|
|
1580
|
+
if (!options.secretKey) {
|
|
1581
|
+
throw createConfigError(`secretKey is required for ${apiName}.`);
|
|
1582
|
+
}
|
|
1583
|
+
this.publishableKey = requirePublishableKeyForSecret(
|
|
1584
|
+
apiName,
|
|
1585
|
+
options.publishableKey,
|
|
1586
|
+
options.secretKey
|
|
1904
1587
|
);
|
|
1588
|
+
this.secretKey = options.secretKey;
|
|
1589
|
+
this.apiUrl = options.apiUrl;
|
|
1590
|
+
this.onRequestId = options.onRequestId;
|
|
1905
1591
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
* Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
|
|
1909
|
-
* `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
|
|
1910
|
-
* inspect `client.lastRequestId` against backend logs.
|
|
1911
|
-
*/
|
|
1912
|
-
async detail(params) {
|
|
1592
|
+
async request(endpoint, body, options) {
|
|
1593
|
+
const method = options?.method ?? "POST";
|
|
1913
1594
|
try {
|
|
1914
|
-
|
|
1595
|
+
const response = await httpFetch(endpoint, {
|
|
1596
|
+
method,
|
|
1597
|
+
apiUrl: this.apiUrl,
|
|
1598
|
+
publishableKey: this.publishableKey,
|
|
1599
|
+
secretKey: this.secretKey,
|
|
1600
|
+
...body !== void 0 && { body: JSON.stringify(body) },
|
|
1601
|
+
...options?.headers && { headers: options.headers }
|
|
1602
|
+
});
|
|
1603
|
+
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1604
|
+
return parseApiResponse(response, endpoint);
|
|
1915
1605
|
} catch (err) {
|
|
1916
|
-
|
|
1606
|
+
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
1607
|
+
this.onRequestId?.(id);
|
|
1917
1608
|
throw err;
|
|
1918
1609
|
}
|
|
1919
1610
|
}
|
|
1920
|
-
/**
|
|
1921
|
-
* Atomically create or update a product together with its options,
|
|
1922
|
-
* option-values, and variants in a single transaction. Mirrors Shopify's
|
|
1923
|
-
* `productSet` shape and is the canonical write path for the MCP
|
|
1924
|
-
* `product-upsert` tool.
|
|
1925
|
-
*/
|
|
1926
|
-
upsert(params) {
|
|
1927
|
-
return this.request("/api/products/upsert", params);
|
|
1928
|
-
}
|
|
1929
|
-
};
|
|
1930
|
-
|
|
1931
|
-
// src/core/api/discount-api.ts
|
|
1932
|
-
var DiscountApi = class extends BaseApi {
|
|
1933
|
-
constructor(options) {
|
|
1934
|
-
super("DiscountApi", options);
|
|
1935
|
-
}
|
|
1936
|
-
validate(params) {
|
|
1937
|
-
return this.request("/api/discounts/validate", params);
|
|
1938
|
-
}
|
|
1939
|
-
};
|
|
1940
|
-
|
|
1941
|
-
// src/core/api/shipping-api.ts
|
|
1942
|
-
var ShippingApi = class extends BaseApi {
|
|
1943
|
-
constructor(options) {
|
|
1944
|
-
super("ShippingApi", options);
|
|
1945
|
-
}
|
|
1946
|
-
calculate(params) {
|
|
1947
|
-
return this.request("/api/shipping-policies/calculate", params);
|
|
1948
|
-
}
|
|
1949
1611
|
};
|
|
1950
1612
|
|
|
1951
1613
|
// src/core/api/order-api.ts
|
|
@@ -1998,770 +1660,308 @@ var OrderApi = class extends BaseApi {
|
|
|
1998
1660
|
}
|
|
1999
1661
|
};
|
|
2000
1662
|
|
|
2001
|
-
// src/core/
|
|
2002
|
-
var
|
|
1663
|
+
// src/core/api/discount-api.ts
|
|
1664
|
+
var DiscountApi = class extends BaseApi {
|
|
2003
1665
|
constructor(options) {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
);
|
|
2009
|
-
const serverOptions = {
|
|
2010
|
-
publishableKey,
|
|
2011
|
-
secretKey: options.secretKey,
|
|
2012
|
-
onRequestId: options.onRequestId
|
|
2013
|
-
};
|
|
2014
|
-
const productApi = new ProductApi(serverOptions);
|
|
2015
|
-
const cartApi = new CartApi(serverOptions);
|
|
2016
|
-
const discountApi = new DiscountApi(serverOptions);
|
|
2017
|
-
const shippingApi = new ShippingApi(serverOptions);
|
|
2018
|
-
const orderApi = new OrderApi(serverOptions);
|
|
2019
|
-
this.product = {
|
|
2020
|
-
stockCheck: productApi.stockCheck.bind(productApi),
|
|
2021
|
-
listingGroups: productApi.listingGroups.bind(productApi),
|
|
2022
|
-
detail: productApi.detail.bind(productApi),
|
|
2023
|
-
upsert: productApi.upsert.bind(productApi)
|
|
2024
|
-
};
|
|
2025
|
-
this.cart = {
|
|
2026
|
-
get: cartApi.getCart.bind(cartApi),
|
|
2027
|
-
addItem: cartApi.addItem.bind(cartApi),
|
|
2028
|
-
updateItem: cartApi.updateItem.bind(cartApi),
|
|
2029
|
-
removeItem: cartApi.removeItem.bind(cartApi),
|
|
2030
|
-
applyDiscount: cartApi.applyDiscount.bind(cartApi),
|
|
2031
|
-
removeDiscount: cartApi.removeDiscount.bind(cartApi),
|
|
2032
|
-
clear: cartApi.clearCart.bind(cartApi)
|
|
2033
|
-
};
|
|
2034
|
-
this.orders = {
|
|
2035
|
-
checkout: orderApi.checkout.bind(orderApi),
|
|
2036
|
-
create: orderApi.createOrder.bind(orderApi),
|
|
2037
|
-
update: orderApi.updateOrder.bind(orderApi),
|
|
2038
|
-
updateTransaction: orderApi.updateTransaction.bind(orderApi),
|
|
2039
|
-
confirmPayment: orderApi.confirmPayment.bind(orderApi),
|
|
2040
|
-
createFulfillment: orderApi.createFulfillment.bind(orderApi),
|
|
2041
|
-
updateFulfillment: orderApi.updateFulfillment.bind(orderApi),
|
|
2042
|
-
bulkImportFulfillments: orderApi.bulkImportFulfillments.bind(orderApi),
|
|
2043
|
-
createReturn: orderApi.createReturn.bind(orderApi),
|
|
2044
|
-
updateReturn: orderApi.updateReturn.bind(orderApi),
|
|
2045
|
-
returnWithRefund: orderApi.returnWithRefund.bind(orderApi)
|
|
2046
|
-
};
|
|
2047
|
-
this.discounts = {
|
|
2048
|
-
validate: discountApi.validate.bind(discountApi)
|
|
2049
|
-
};
|
|
2050
|
-
this.shipping = {
|
|
2051
|
-
calculate: shippingApi.calculate.bind(shippingApi)
|
|
2052
|
-
};
|
|
1666
|
+
super("DiscountApi", options);
|
|
1667
|
+
}
|
|
1668
|
+
validate(params) {
|
|
1669
|
+
return this.request("/api/discounts/validate", params);
|
|
2053
1670
|
}
|
|
2054
1671
|
};
|
|
2055
1672
|
|
|
2056
|
-
// src/core/
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
defaultShouldDehydrateQuery
|
|
2061
|
-
} from "@tanstack/react-query";
|
|
2062
|
-
function makeQueryClient() {
|
|
2063
|
-
return new QueryClient({
|
|
2064
|
-
defaultOptions: {
|
|
2065
|
-
queries: {
|
|
2066
|
-
// Infinite staleTime: server-fetched data persists until explicitly invalidated.
|
|
2067
|
-
// For browser clients needing fresher data, override per-query:
|
|
2068
|
-
// useQuery({ ..., staleTime: 5 * 60 * 1000 })
|
|
2069
|
-
staleTime: Number.POSITIVE_INFINITY,
|
|
2070
|
-
refetchOnWindowFocus: false
|
|
2071
|
-
},
|
|
2072
|
-
dehydrate: {
|
|
2073
|
-
shouldDehydrateQuery: (query) => defaultShouldDehydrateQuery(query) || query.state.status === "pending",
|
|
2074
|
-
shouldRedactErrors: () => false
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
});
|
|
2078
|
-
}
|
|
2079
|
-
var browserQueryClient;
|
|
2080
|
-
function getQueryClient() {
|
|
2081
|
-
if (isServer) {
|
|
2082
|
-
return makeQueryClient();
|
|
1673
|
+
// src/core/api/shipping-api.ts
|
|
1674
|
+
var ShippingApi = class extends BaseApi {
|
|
1675
|
+
constructor(options) {
|
|
1676
|
+
super("ShippingApi", options);
|
|
2083
1677
|
}
|
|
2084
|
-
|
|
2085
|
-
|
|
1678
|
+
calculate(params) {
|
|
1679
|
+
return this.request("/api/shipping-policies/calculate", params);
|
|
2086
1680
|
}
|
|
2087
|
-
return browserQueryClient;
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
|
-
// src/core/query/query-hooks.ts
|
|
2091
|
-
import {
|
|
2092
|
-
useInfiniteQuery as useInfiniteQueryOriginal2,
|
|
2093
|
-
useQuery as useQueryOriginal3,
|
|
2094
|
-
useSuspenseInfiniteQuery as useSuspenseInfiniteQueryOriginal2,
|
|
2095
|
-
useSuspenseQuery as useSuspenseQueryOriginal2
|
|
2096
|
-
} from "@tanstack/react-query";
|
|
2097
|
-
|
|
2098
|
-
// src/core/query/collection-hooks.ts
|
|
2099
|
-
import {
|
|
2100
|
-
useQuery as useQueryOriginal,
|
|
2101
|
-
useSuspenseQuery as useSuspenseQueryOriginal,
|
|
2102
|
-
useInfiniteQuery as useInfiniteQueryOriginal,
|
|
2103
|
-
useSuspenseInfiniteQuery as useSuspenseInfiniteQueryOriginal,
|
|
2104
|
-
useMutation as useMutationOriginal
|
|
2105
|
-
} from "@tanstack/react-query";
|
|
2106
|
-
|
|
2107
|
-
// src/core/query/query-keys.ts
|
|
2108
|
-
function collectionKeys(collection) {
|
|
2109
|
-
return {
|
|
2110
|
-
all: [collection],
|
|
2111
|
-
lists: () => [collection, "list"],
|
|
2112
|
-
list: (options) => [collection, "list", options],
|
|
2113
|
-
details: () => [collection, "detail"],
|
|
2114
|
-
detail: (id, options) => [collection, "detail", id, options],
|
|
2115
|
-
infinites: () => [collection, "infinite"],
|
|
2116
|
-
infinite: (options) => [collection, "infinite", options]
|
|
2117
|
-
};
|
|
2118
|
-
}
|
|
2119
|
-
var customerKeys = {
|
|
2120
|
-
all: ["customer"],
|
|
2121
|
-
me: () => ["customer", "me"]
|
|
2122
|
-
};
|
|
2123
|
-
var productKeys = {
|
|
2124
|
-
listingGroups: (options) => ["products", "listing-groups", "list", options],
|
|
2125
|
-
listingGroupsInfinite: (options) => ["products", "listing-groups", "infinite", options],
|
|
2126
|
-
detail: (params) => ["products", "detail", params],
|
|
2127
|
-
detailAll: () => ["products", "detail"]
|
|
2128
1681
|
};
|
|
2129
1682
|
|
|
2130
|
-
// src/core/
|
|
2131
|
-
var
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
"product-options",
|
|
2135
|
-
"product-option-values",
|
|
2136
|
-
"product-categories",
|
|
2137
|
-
"product-tags",
|
|
2138
|
-
"product-collections",
|
|
2139
|
-
"brands",
|
|
2140
|
-
"brand-logos",
|
|
2141
|
-
"images"
|
|
2142
|
-
]);
|
|
2143
|
-
var DEFAULT_PAGE_SIZE = 20;
|
|
2144
|
-
var CollectionHooks = class {
|
|
2145
|
-
constructor(queryClient, collectionClient) {
|
|
2146
|
-
this.queryClient = queryClient;
|
|
2147
|
-
this.collectionClient = collectionClient;
|
|
2148
|
-
}
|
|
2149
|
-
// ===== useQuery =====
|
|
2150
|
-
useQuery(params, options) {
|
|
2151
|
-
const { collection, options: queryOptions } = params;
|
|
2152
|
-
const { placeholderData, ...restOptions } = options ?? {};
|
|
2153
|
-
return useQueryOriginal({
|
|
2154
|
-
queryKey: collectionKeys(collection).list(queryOptions),
|
|
2155
|
-
queryFn: async () => {
|
|
2156
|
-
return await this.collectionClient.from(collection).find(queryOptions);
|
|
2157
|
-
},
|
|
2158
|
-
...restOptions,
|
|
2159
|
-
// NonFunctionGuard<T> incompatible with generic union types — safe cast
|
|
2160
|
-
...placeholderData !== void 0 && {
|
|
2161
|
-
placeholderData
|
|
2162
|
-
}
|
|
2163
|
-
});
|
|
1683
|
+
// src/core/api/product-api.ts
|
|
1684
|
+
var ProductApi = class extends BaseApi {
|
|
1685
|
+
constructor(options) {
|
|
1686
|
+
super("ProductApi", options);
|
|
2164
1687
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
},
|
|
2173
|
-
...options
|
|
2174
|
-
});
|
|
1688
|
+
/**
|
|
1689
|
+
* Check point-in-time stock availability for one or more product variants.
|
|
1690
|
+
* Results reflect available stock at the moment of the call and are not guaranteed
|
|
1691
|
+
* to remain available by the time an order is placed.
|
|
1692
|
+
*/
|
|
1693
|
+
stockCheck(params) {
|
|
1694
|
+
return this.request("/api/products/stock-check", params);
|
|
2175
1695
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
return useQueryOriginal({
|
|
2181
|
-
queryKey: collectionKeys(collection).detail(id, queryOptions),
|
|
2182
|
-
queryFn: async () => {
|
|
2183
|
-
return await this.collectionClient.from(collection).findById(id, queryOptions);
|
|
2184
|
-
},
|
|
2185
|
-
...restOptions,
|
|
2186
|
-
// NonFunctionGuard<T> incompatible with generic union types — safe cast
|
|
2187
|
-
...placeholderData !== void 0 && {
|
|
2188
|
-
placeholderData
|
|
2189
|
-
}
|
|
2190
|
-
});
|
|
2191
|
-
}
|
|
2192
|
-
// ===== useSuspenseQueryById =====
|
|
2193
|
-
useSuspenseQueryById(params, options) {
|
|
2194
|
-
const { collection, id, options: queryOptions } = params;
|
|
2195
|
-
return useSuspenseQueryOriginal({
|
|
2196
|
-
queryKey: collectionKeys(collection).detail(id, queryOptions),
|
|
2197
|
-
queryFn: async () => {
|
|
2198
|
-
return await this.collectionClient.from(collection).findById(id, queryOptions);
|
|
2199
|
-
},
|
|
2200
|
-
...options
|
|
2201
|
-
});
|
|
2202
|
-
}
|
|
2203
|
-
// ===== useInfiniteQuery =====
|
|
2204
|
-
useInfiniteQuery(params, options) {
|
|
2205
|
-
const {
|
|
2206
|
-
collection,
|
|
2207
|
-
options: queryOptions,
|
|
2208
|
-
pageSize = DEFAULT_PAGE_SIZE
|
|
2209
|
-
} = params;
|
|
2210
|
-
return useInfiniteQueryOriginal({
|
|
2211
|
-
queryKey: collectionKeys(collection).infinite(queryOptions),
|
|
2212
|
-
queryFn: async ({ pageParam }) => {
|
|
2213
|
-
const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
|
|
2214
|
-
return response;
|
|
2215
|
-
},
|
|
2216
|
-
initialPageParam: 1,
|
|
2217
|
-
getNextPageParam: (lastPage) => {
|
|
2218
|
-
return lastPage.hasNextPage ? lastPage.nextPage : void 0;
|
|
2219
|
-
},
|
|
2220
|
-
...options
|
|
2221
|
-
});
|
|
2222
|
-
}
|
|
2223
|
-
// ===== useSuspenseInfiniteQuery =====
|
|
2224
|
-
useSuspenseInfiniteQuery(params, options) {
|
|
2225
|
-
const {
|
|
2226
|
-
collection,
|
|
2227
|
-
options: queryOptions,
|
|
2228
|
-
pageSize = DEFAULT_PAGE_SIZE
|
|
2229
|
-
} = params;
|
|
2230
|
-
return useSuspenseInfiniteQueryOriginal({
|
|
2231
|
-
queryKey: collectionKeys(collection).infinite(queryOptions),
|
|
2232
|
-
queryFn: async ({ pageParam }) => {
|
|
2233
|
-
const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
|
|
2234
|
-
return response;
|
|
2235
|
-
},
|
|
2236
|
-
initialPageParam: 1,
|
|
2237
|
-
getNextPageParam: (lastPage) => {
|
|
2238
|
-
return lastPage.hasNextPage ? lastPage.nextPage : void 0;
|
|
2239
|
-
},
|
|
2240
|
-
...options
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2243
|
-
// ===== prefetchQuery =====
|
|
2244
|
-
async prefetchQuery(params, options) {
|
|
2245
|
-
const { collection, options: queryOptions } = params;
|
|
2246
|
-
return this.queryClient.prefetchQuery({
|
|
2247
|
-
queryKey: collectionKeys(collection).list(queryOptions),
|
|
2248
|
-
queryFn: async () => {
|
|
2249
|
-
return await this.collectionClient.from(collection).find(queryOptions);
|
|
2250
|
-
},
|
|
2251
|
-
...options
|
|
2252
|
-
});
|
|
2253
|
-
}
|
|
2254
|
-
// ===== prefetchQueryById =====
|
|
2255
|
-
async prefetchQueryById(params, options) {
|
|
2256
|
-
const { collection, id, options: queryOptions } = params;
|
|
2257
|
-
return this.queryClient.prefetchQuery({
|
|
2258
|
-
queryKey: collectionKeys(collection).detail(id, queryOptions),
|
|
2259
|
-
queryFn: async () => {
|
|
2260
|
-
return await this.collectionClient.from(collection).findById(id, queryOptions);
|
|
2261
|
-
},
|
|
2262
|
-
...options
|
|
2263
|
-
});
|
|
2264
|
-
}
|
|
2265
|
-
// ===== prefetchInfiniteQuery =====
|
|
2266
|
-
async prefetchInfiniteQuery(params, options) {
|
|
2267
|
-
const {
|
|
2268
|
-
collection,
|
|
2269
|
-
options: queryOptions,
|
|
2270
|
-
pageSize = DEFAULT_PAGE_SIZE
|
|
2271
|
-
} = params;
|
|
2272
|
-
return this.queryClient.prefetchInfiniteQuery({
|
|
2273
|
-
queryKey: collectionKeys(collection).infinite(queryOptions),
|
|
2274
|
-
queryFn: async ({ pageParam }) => {
|
|
2275
|
-
const response = await this.collectionClient.from(collection).find({ ...queryOptions, page: pageParam, limit: pageSize });
|
|
2276
|
-
return response;
|
|
2277
|
-
},
|
|
2278
|
-
initialPageParam: 1,
|
|
2279
|
-
getNextPageParam: (lastPage) => {
|
|
2280
|
-
return lastPage.hasNextPage ? lastPage.nextPage : void 0;
|
|
2281
|
-
},
|
|
2282
|
-
pages: options?.pages ?? 1,
|
|
2283
|
-
staleTime: options?.staleTime
|
|
2284
|
-
});
|
|
2285
|
-
}
|
|
2286
|
-
// ===== Mutation Hooks =====
|
|
2287
|
-
useCreate(params, options) {
|
|
2288
|
-
const { collection } = params;
|
|
2289
|
-
return useMutationOriginal({
|
|
2290
|
-
mutationFn: async (variables) => {
|
|
2291
|
-
return await this.collectionClient.from(collection).create(
|
|
2292
|
-
variables.data,
|
|
2293
|
-
variables.file ? { file: variables.file, filename: variables.filename } : void 0
|
|
2294
|
-
);
|
|
2295
|
-
},
|
|
2296
|
-
onSuccess: (data) => {
|
|
2297
|
-
this.queryClient.invalidateQueries({
|
|
2298
|
-
queryKey: collectionKeys(collection).all
|
|
2299
|
-
});
|
|
2300
|
-
if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
|
|
2301
|
-
this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
|
|
2302
|
-
}
|
|
2303
|
-
options?.onSuccess?.(data);
|
|
2304
|
-
},
|
|
2305
|
-
onError: options?.onError,
|
|
2306
|
-
onSettled: options?.onSettled
|
|
2307
|
-
});
|
|
2308
|
-
}
|
|
2309
|
-
useUpdate(params, options) {
|
|
2310
|
-
const { collection } = params;
|
|
2311
|
-
return useMutationOriginal({
|
|
2312
|
-
mutationFn: async (variables) => {
|
|
2313
|
-
return await this.collectionClient.from(collection).update(
|
|
2314
|
-
variables.id,
|
|
2315
|
-
variables.data,
|
|
2316
|
-
variables.file ? { file: variables.file, filename: variables.filename } : void 0
|
|
2317
|
-
);
|
|
2318
|
-
},
|
|
2319
|
-
onSuccess: (data) => {
|
|
2320
|
-
this.queryClient.invalidateQueries({
|
|
2321
|
-
queryKey: collectionKeys(collection).all
|
|
2322
|
-
});
|
|
2323
|
-
if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
|
|
2324
|
-
this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
|
|
2325
|
-
}
|
|
2326
|
-
options?.onSuccess?.(data);
|
|
2327
|
-
},
|
|
2328
|
-
onError: options?.onError,
|
|
2329
|
-
onSettled: options?.onSettled
|
|
2330
|
-
});
|
|
2331
|
-
}
|
|
2332
|
-
useRemove(params, options) {
|
|
2333
|
-
const { collection } = params;
|
|
2334
|
-
return useMutationOriginal({
|
|
2335
|
-
mutationFn: async (id) => {
|
|
2336
|
-
return await this.collectionClient.from(collection).remove(id);
|
|
2337
|
-
},
|
|
2338
|
-
onSuccess: (data) => {
|
|
2339
|
-
this.queryClient.invalidateQueries({
|
|
2340
|
-
queryKey: collectionKeys(collection).all
|
|
2341
|
-
});
|
|
2342
|
-
if (PRODUCT_DETAIL_INVALIDATING_COLLECTIONS.has(collection)) {
|
|
2343
|
-
this.queryClient.invalidateQueries({ queryKey: ["products", "detail"] });
|
|
2344
|
-
}
|
|
2345
|
-
options?.onSuccess?.(data);
|
|
2346
|
-
},
|
|
2347
|
-
onError: options?.onError,
|
|
2348
|
-
onSettled: options?.onSettled
|
|
2349
|
-
});
|
|
2350
|
-
}
|
|
2351
|
-
// ===== Cache Utilities =====
|
|
2352
|
-
invalidateQueries(collection, type) {
|
|
2353
|
-
const queryKey = type ? [collection, type] : [collection];
|
|
2354
|
-
return this.queryClient.invalidateQueries({ queryKey });
|
|
2355
|
-
}
|
|
2356
|
-
getQueryData(collection, type, idOrOptions, options) {
|
|
2357
|
-
if (type === "list") {
|
|
2358
|
-
return this.queryClient.getQueryData(
|
|
2359
|
-
collectionKeys(collection).list(idOrOptions)
|
|
2360
|
-
);
|
|
2361
|
-
}
|
|
2362
|
-
return this.queryClient.getQueryData(
|
|
2363
|
-
collectionKeys(collection).detail(idOrOptions, options)
|
|
1696
|
+
listingGroups(params) {
|
|
1697
|
+
return this.request(
|
|
1698
|
+
"/api/products/listing-groups",
|
|
1699
|
+
params
|
|
2364
1700
|
);
|
|
2365
1701
|
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
);
|
|
1702
|
+
/**
|
|
1703
|
+
* Fetch full product detail by slug or id.
|
|
1704
|
+
* Returns `null` on 404 regardless of reason (`not_found` / `not_published` /
|
|
1705
|
+
* `tenant_mismatch` / `feature_disabled`). For the reason behind a null,
|
|
1706
|
+
* inspect `client.lastRequestId` against backend logs.
|
|
1707
|
+
*/
|
|
1708
|
+
async detail(params) {
|
|
1709
|
+
try {
|
|
1710
|
+
return await this.request("/api/products/detail", params);
|
|
1711
|
+
} catch (err) {
|
|
1712
|
+
if (err instanceof NotFoundError) return null;
|
|
1713
|
+
throw err;
|
|
2377
1714
|
}
|
|
2378
1715
|
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Atomically create or update a product together with its options,
|
|
1718
|
+
* option-values, and variants in a single transaction. Mirrors Shopify's
|
|
1719
|
+
* `productSet` shape and is the canonical write path for the MCP
|
|
1720
|
+
* `product-upsert` tool.
|
|
1721
|
+
*/
|
|
1722
|
+
upsert(params) {
|
|
1723
|
+
return this.request("/api/products/upsert", params);
|
|
1724
|
+
}
|
|
2379
1725
|
};
|
|
2380
1726
|
|
|
2381
|
-
// src/core/
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
function createMutation(mutationFn, callbacks, onSuccessExtra) {
|
|
2387
|
-
return useMutationOriginal2({
|
|
2388
|
-
mutationFn,
|
|
2389
|
-
onSuccess: (data) => {
|
|
2390
|
-
onSuccessExtra?.(data);
|
|
2391
|
-
callbacks?.onSuccess?.(data);
|
|
2392
|
-
},
|
|
2393
|
-
onError: callbacks?.onError,
|
|
2394
|
-
onSettled: callbacks?.onSettled
|
|
2395
|
-
});
|
|
1727
|
+
// src/core/webhook/index.ts
|
|
1728
|
+
function isValidWebhookEvent(data) {
|
|
1729
|
+
if (typeof data !== "object" || data === null) return false;
|
|
1730
|
+
const obj = data;
|
|
1731
|
+
return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
|
|
2396
1732
|
}
|
|
2397
|
-
var
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
1733
|
+
var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
|
|
1734
|
+
function isRecord(value) {
|
|
1735
|
+
return typeof value === "object" && value !== null;
|
|
1736
|
+
}
|
|
1737
|
+
function hasString(value, key) {
|
|
1738
|
+
return typeof value[key] === "string";
|
|
1739
|
+
}
|
|
1740
|
+
function hasStringOrNumber(value, key) {
|
|
1741
|
+
return typeof value[key] === "string" || typeof value[key] === "number";
|
|
1742
|
+
}
|
|
1743
|
+
function isCustomerPasswordResetWebhookEvent(event) {
|
|
1744
|
+
if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
|
|
1745
|
+
return false;
|
|
2404
1746
|
}
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
1747
|
+
return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
|
|
1748
|
+
}
|
|
1749
|
+
function createCustomerAuthWebhookHandler(handlers) {
|
|
1750
|
+
return async (event) => {
|
|
1751
|
+
if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
|
|
1752
|
+
await handlers.passwordReset(event.data, event);
|
|
1753
|
+
return;
|
|
2410
1754
|
}
|
|
2411
|
-
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
useCustomerLogin(options) {
|
|
2426
|
-
return createMutation(
|
|
2427
|
-
(data) => this.ensureCustomerAuth().login(data),
|
|
2428
|
-
options,
|
|
2429
|
-
this.invalidateMe
|
|
2430
|
-
);
|
|
2431
|
-
}
|
|
2432
|
-
useCustomerRegister(options) {
|
|
2433
|
-
return createMutation(
|
|
2434
|
-
(data) => this.ensureCustomerAuth().register(data),
|
|
2435
|
-
options
|
|
2436
|
-
);
|
|
2437
|
-
}
|
|
2438
|
-
useCustomerLogout(options) {
|
|
2439
|
-
return useMutationOriginal2({
|
|
2440
|
-
mutationFn: async () => {
|
|
2441
|
-
this.ensureCustomerAuth().logout();
|
|
2442
|
-
},
|
|
2443
|
-
onSuccess: () => {
|
|
2444
|
-
this.queryClient.removeQueries({ queryKey: customerKeys.all });
|
|
2445
|
-
options?.onSuccess?.();
|
|
2446
|
-
},
|
|
2447
|
-
onError: options?.onError,
|
|
2448
|
-
onSettled: options?.onSettled
|
|
2449
|
-
});
|
|
2450
|
-
}
|
|
2451
|
-
useCustomerForgotPassword(options) {
|
|
2452
|
-
return createMutation(
|
|
2453
|
-
(email) => this.ensureCustomerAuth().forgotPassword(email).then(() => {
|
|
2454
|
-
}),
|
|
2455
|
-
options
|
|
2456
|
-
);
|
|
2457
|
-
}
|
|
2458
|
-
useCustomerResetPassword(options) {
|
|
2459
|
-
return createMutation(
|
|
2460
|
-
(data) => this.ensureCustomerAuth().resetPassword(data.token, data.password).then(() => {
|
|
2461
|
-
}),
|
|
2462
|
-
options
|
|
2463
|
-
);
|
|
2464
|
-
}
|
|
2465
|
-
useCustomerRefreshToken(options) {
|
|
2466
|
-
return createMutation(
|
|
2467
|
-
() => this.ensureCustomerAuth().refreshToken(),
|
|
2468
|
-
options,
|
|
2469
|
-
this.invalidateMe
|
|
2470
|
-
);
|
|
2471
|
-
}
|
|
2472
|
-
useCustomerUpdateProfile(options) {
|
|
2473
|
-
return createMutation(
|
|
2474
|
-
(data) => this.ensureCustomerAuth().updateProfile(data),
|
|
2475
|
-
options,
|
|
2476
|
-
this.invalidateMe
|
|
2477
|
-
);
|
|
2478
|
-
}
|
|
2479
|
-
useCustomerChangePassword(options) {
|
|
2480
|
-
return createMutation(
|
|
2481
|
-
(data) => this.ensureCustomerAuth().changePassword(data.currentPassword, data.newPassword).then(() => {
|
|
2482
|
-
}),
|
|
2483
|
-
options
|
|
2484
|
-
);
|
|
2485
|
-
}
|
|
2486
|
-
// ===== Customer Cache Utilities =====
|
|
2487
|
-
invalidateCustomerQueries() {
|
|
2488
|
-
return this.queryClient.invalidateQueries({ queryKey: customerKeys.all });
|
|
2489
|
-
}
|
|
2490
|
-
getCustomerData() {
|
|
2491
|
-
return this.queryClient.getQueryData(customerKeys.me());
|
|
2492
|
-
}
|
|
2493
|
-
setCustomerData(data) {
|
|
2494
|
-
this.queryClient.setQueryData(customerKeys.me(), data);
|
|
1755
|
+
await handlers.unhandled?.(event);
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
|
|
1759
|
+
const encoder = new TextEncoder();
|
|
1760
|
+
const key = await crypto.subtle.importKey(
|
|
1761
|
+
"raw",
|
|
1762
|
+
encoder.encode(secret),
|
|
1763
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
1764
|
+
false,
|
|
1765
|
+
["verify"]
|
|
1766
|
+
);
|
|
1767
|
+
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
1768
|
+
return false;
|
|
2495
1769
|
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
)
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
1770
|
+
const sigBytes = new Uint8Array(
|
|
1771
|
+
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
1772
|
+
);
|
|
1773
|
+
return crypto.subtle.verify(
|
|
1774
|
+
"HMAC",
|
|
1775
|
+
key,
|
|
1776
|
+
sigBytes,
|
|
1777
|
+
encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
function timestampIsFresh(timestamp, toleranceSeconds) {
|
|
1781
|
+
if (!/^\d+$/.test(timestamp)) return false;
|
|
1782
|
+
const timestampMs = Number(timestamp);
|
|
1783
|
+
if (!Number.isFinite(timestampMs)) return false;
|
|
1784
|
+
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
1785
|
+
return skewMs <= toleranceSeconds * 1e3;
|
|
1786
|
+
}
|
|
1787
|
+
async function handleWebhook(request, handler, options) {
|
|
1788
|
+
try {
|
|
1789
|
+
const rawBody = await request.text();
|
|
1790
|
+
if (options?.secret) {
|
|
1791
|
+
const signature = request.headers.get("x-webhook-signature") || "";
|
|
1792
|
+
const timestamp = request.headers.get("x-webhook-timestamp") || "";
|
|
1793
|
+
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
1794
|
+
const toleranceSeconds = options.toleranceSeconds ?? 300;
|
|
1795
|
+
const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
|
|
1796
|
+
rawBody,
|
|
1797
|
+
options.secret,
|
|
1798
|
+
signature,
|
|
1799
|
+
timestamp,
|
|
1800
|
+
deliveryId
|
|
1801
|
+
);
|
|
1802
|
+
if (!valid) {
|
|
1803
|
+
return new Response(
|
|
1804
|
+
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
1805
|
+
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
1806
|
+
);
|
|
2531
1807
|
}
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
return useSuspenseQueryOriginal2({
|
|
2537
|
-
queryKey: productKeys.listingGroups(queryOptions),
|
|
2538
|
-
queryFn: async () => this.collectionClient.requestFindEndpoint(
|
|
2539
|
-
"/api/products/listing-groups/query",
|
|
2540
|
-
{ options: queryOptions }
|
|
2541
|
-
),
|
|
2542
|
-
...options
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2545
|
-
useInfiniteProductListingGroupsQuery(params, options) {
|
|
2546
|
-
const {
|
|
2547
|
-
options: queryOptions,
|
|
2548
|
-
pageSize = 20
|
|
2549
|
-
} = params;
|
|
2550
|
-
return useInfiniteQueryOriginal2({
|
|
2551
|
-
queryKey: productKeys.listingGroupsInfinite(queryOptions),
|
|
2552
|
-
queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
|
|
2553
|
-
"/api/products/listing-groups/query",
|
|
2554
|
-
{
|
|
2555
|
-
options: { ...queryOptions, page: pageParam, limit: pageSize }
|
|
2556
|
-
}
|
|
2557
|
-
),
|
|
2558
|
-
initialPageParam: 1,
|
|
2559
|
-
getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
|
|
2560
|
-
...options
|
|
2561
|
-
});
|
|
2562
|
-
}
|
|
2563
|
-
useSuspenseInfiniteProductListingGroupsQuery(params, options) {
|
|
2564
|
-
const {
|
|
2565
|
-
options: queryOptions,
|
|
2566
|
-
pageSize = 20
|
|
2567
|
-
} = params;
|
|
2568
|
-
return useSuspenseInfiniteQueryOriginal2({
|
|
2569
|
-
queryKey: productKeys.listingGroupsInfinite(queryOptions),
|
|
2570
|
-
queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
|
|
2571
|
-
"/api/products/listing-groups/query",
|
|
2572
|
-
{
|
|
2573
|
-
options: { ...queryOptions, page: pageParam, limit: pageSize }
|
|
2574
|
-
}
|
|
2575
|
-
),
|
|
2576
|
-
initialPageParam: 1,
|
|
2577
|
-
getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
|
|
2578
|
-
...options
|
|
2579
|
-
});
|
|
2580
|
-
}
|
|
2581
|
-
async prefetchProductListingGroupsQuery(params, options) {
|
|
2582
|
-
const queryOptions = params.options;
|
|
2583
|
-
return this.queryClient.prefetchQuery({
|
|
2584
|
-
queryKey: productKeys.listingGroups(queryOptions),
|
|
2585
|
-
queryFn: async () => this.collectionClient.requestFindEndpoint(
|
|
2586
|
-
"/api/products/listing-groups/query",
|
|
2587
|
-
{ options: queryOptions }
|
|
2588
|
-
),
|
|
2589
|
-
...options
|
|
2590
|
-
});
|
|
2591
|
-
}
|
|
2592
|
-
async prefetchInfiniteProductListingGroupsQuery(params, options) {
|
|
2593
|
-
const {
|
|
2594
|
-
options: queryOptions,
|
|
2595
|
-
pageSize = 20
|
|
2596
|
-
} = params;
|
|
2597
|
-
return this.queryClient.prefetchInfiniteQuery({
|
|
2598
|
-
queryKey: productKeys.listingGroupsInfinite(queryOptions),
|
|
2599
|
-
queryFn: async ({ pageParam }) => this.collectionClient.requestFindEndpoint(
|
|
2600
|
-
"/api/products/listing-groups/query",
|
|
2601
|
-
{
|
|
2602
|
-
options: { ...queryOptions, page: pageParam, limit: pageSize }
|
|
2603
|
-
}
|
|
2604
|
-
),
|
|
2605
|
-
initialPageParam: 1,
|
|
2606
|
-
getNextPageParam: (lastPage) => lastPage.hasNextPage ? lastPage.nextPage : void 0,
|
|
2607
|
-
pages: options?.pages ?? 1,
|
|
2608
|
-
staleTime: options?.staleTime
|
|
2609
|
-
});
|
|
2610
|
-
}
|
|
2611
|
-
useProductDetail(params, options) {
|
|
2612
|
-
const discriminator = "slug" in params ? params.slug : params.id;
|
|
2613
|
-
const enabled = options?.enabled !== false && Boolean(discriminator);
|
|
2614
|
-
return useQueryOriginal3({
|
|
2615
|
-
queryKey: productKeys.detail(params),
|
|
2616
|
-
queryFn: () => this._commerce.product.detail(params),
|
|
2617
|
-
enabled
|
|
2618
|
-
});
|
|
2619
|
-
}
|
|
2620
|
-
useProductDetailBySlug(slug, options) {
|
|
2621
|
-
return this.useProductDetail({ slug }, options);
|
|
2622
|
-
}
|
|
2623
|
-
useProductDetailById(id, options) {
|
|
2624
|
-
return this.useProductDetail({ id }, options);
|
|
2625
|
-
}
|
|
2626
|
-
};
|
|
2627
|
-
|
|
2628
|
-
// src/core/client/client.ts
|
|
2629
|
-
var Client = class {
|
|
2630
|
-
constructor(options) {
|
|
2631
|
-
this.lastRequestId = null;
|
|
2632
|
-
const publishableKey = options.publishableKey;
|
|
2633
|
-
if (!publishableKey) {
|
|
2634
|
-
throw createConfigError("publishableKey is required.");
|
|
1808
|
+
} else {
|
|
1809
|
+
console.warn(
|
|
1810
|
+
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
1811
|
+
);
|
|
2635
1812
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
1813
|
+
const body = JSON.parse(rawBody);
|
|
1814
|
+
if (!isValidWebhookEvent(body)) {
|
|
1815
|
+
return new Response(
|
|
1816
|
+
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
1817
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
await handler(body);
|
|
1821
|
+
return new Response(
|
|
1822
|
+
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
1823
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
2646
1824
|
);
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
return null;
|
|
2653
|
-
}
|
|
2654
|
-
};
|
|
2655
|
-
const onRequestId = (id) => {
|
|
2656
|
-
this.lastRequestId = id;
|
|
2657
|
-
};
|
|
2658
|
-
this.commerce = new CommerceClient({
|
|
2659
|
-
publishableKey: this.config.publishableKey,
|
|
2660
|
-
customerToken: () => this.customer.auth.getToken(),
|
|
2661
|
-
onUnauthorized,
|
|
2662
|
-
onRequestId,
|
|
2663
|
-
customerAuth: this.customer.auth
|
|
2664
|
-
});
|
|
2665
|
-
this.community = new CommunityClient({
|
|
2666
|
-
publishableKey: this.config.publishableKey,
|
|
2667
|
-
customerToken: () => this.customer.auth.getToken(),
|
|
2668
|
-
onUnauthorized,
|
|
2669
|
-
onRequestId
|
|
1825
|
+
} catch (error) {
|
|
1826
|
+
console.error("Webhook processing error:", error);
|
|
1827
|
+
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
1828
|
+
status: 500,
|
|
1829
|
+
headers: { "Content-Type": "application/json" }
|
|
2670
1830
|
});
|
|
2671
|
-
const collectionClient = new CollectionClient(
|
|
2672
|
-
this.config.publishableKey,
|
|
2673
|
-
void 0,
|
|
2674
|
-
() => this.customer.auth.getToken(),
|
|
2675
|
-
onUnauthorized,
|
|
2676
|
-
onRequestId
|
|
2677
|
-
);
|
|
2678
|
-
this.collections = new ReadOnlyCollectionClient(
|
|
2679
|
-
this.config.publishableKey,
|
|
2680
|
-
void 0,
|
|
2681
|
-
() => this.customer.auth.getToken(),
|
|
2682
|
-
onUnauthorized,
|
|
2683
|
-
onRequestId
|
|
2684
|
-
);
|
|
2685
|
-
this.query = new QueryHooks(
|
|
2686
|
-
this.queryClient,
|
|
2687
|
-
collectionClient,
|
|
2688
|
-
this.customer.auth,
|
|
2689
|
-
this.commerce
|
|
2690
|
-
);
|
|
2691
|
-
}
|
|
2692
|
-
getState() {
|
|
2693
|
-
return { ...this.state };
|
|
2694
|
-
}
|
|
2695
|
-
getConfig() {
|
|
2696
|
-
return { ...this.config };
|
|
2697
1831
|
}
|
|
2698
|
-
};
|
|
2699
|
-
function createClient(options) {
|
|
2700
|
-
return new Client(options);
|
|
2701
1832
|
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
if (typeof window !== "undefined") {
|
|
2708
|
-
throw createConfigError(
|
|
2709
|
-
"ServerClient must not be used in a browser environment. This risks exposing your secretKey in client bundles. Use createClient() for browser code instead."
|
|
2710
|
-
);
|
|
2711
|
-
}
|
|
2712
|
-
if (!options.secretKey) {
|
|
2713
|
-
throw createConfigError("secretKey is required.");
|
|
2714
|
-
}
|
|
2715
|
-
if (!options.publishableKey) {
|
|
2716
|
-
throw createConfigError(
|
|
2717
|
-
"publishableKey is required. It is used for rate limiting and monthly quota enforcement via the X-Publishable-Key header. Get it from Console > Settings > API Keys."
|
|
1833
|
+
function createTypedWebhookHandler(collection, handler) {
|
|
1834
|
+
return async (event) => {
|
|
1835
|
+
if (event.collection !== collection) {
|
|
1836
|
+
throw new Error(
|
|
1837
|
+
`Expected collection "${collection}", got "${event.collection}"`
|
|
2718
1838
|
);
|
|
2719
1839
|
}
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
timestamp: Date.now(),
|
|
2723
|
-
userAgent: "Node.js"
|
|
2724
|
-
};
|
|
2725
|
-
this.state = { metadata };
|
|
2726
|
-
const onRequestId = (id) => {
|
|
2727
|
-
this.lastRequestId = id;
|
|
2728
|
-
};
|
|
2729
|
-
const serverOptions = {
|
|
2730
|
-
publishableKey: this.config.publishableKey,
|
|
2731
|
-
secretKey: this.config.secretKey,
|
|
2732
|
-
onRequestId
|
|
2733
|
-
};
|
|
2734
|
-
this.commerce = new ServerCommerceClient(serverOptions);
|
|
2735
|
-
const communityClient = new CommunityClient(serverOptions);
|
|
2736
|
-
const moderationApi = new ModerationApi(serverOptions);
|
|
2737
|
-
this.community = Object.assign(communityClient, {
|
|
2738
|
-
moderation: {
|
|
2739
|
-
banCustomer: moderationApi.banCustomer.bind(moderationApi),
|
|
2740
|
-
unbanCustomer: moderationApi.unbanCustomer.bind(moderationApi)
|
|
2741
|
-
}
|
|
2742
|
-
});
|
|
2743
|
-
this.collections = new ServerCollectionClient(
|
|
2744
|
-
this.config.publishableKey,
|
|
2745
|
-
this.config.secretKey,
|
|
2746
|
-
void 0,
|
|
2747
|
-
void 0,
|
|
2748
|
-
onRequestId
|
|
2749
|
-
);
|
|
2750
|
-
this.queryClient = getQueryClient();
|
|
2751
|
-
this.query = new QueryHooks(this.queryClient, this.collections, void 0, this.commerce);
|
|
2752
|
-
}
|
|
2753
|
-
getState() {
|
|
2754
|
-
return { ...this.state };
|
|
2755
|
-
}
|
|
2756
|
-
getConfig() {
|
|
2757
|
-
const { secretKey: _, ...safeConfig } = this.config;
|
|
2758
|
-
return safeConfig;
|
|
2759
|
-
}
|
|
2760
|
-
};
|
|
2761
|
-
function createServerClient(options) {
|
|
2762
|
-
return new ServerClient(options);
|
|
1840
|
+
return handler(event);
|
|
1841
|
+
};
|
|
2763
1842
|
}
|
|
2764
1843
|
|
|
1844
|
+
// src/core/collection/const.ts
|
|
1845
|
+
var INTERNAL_COLLECTIONS = [
|
|
1846
|
+
"users",
|
|
1847
|
+
"payload-kv",
|
|
1848
|
+
"payload-locked-documents",
|
|
1849
|
+
"payload-preferences",
|
|
1850
|
+
"payload-migrations",
|
|
1851
|
+
"payload-folders",
|
|
1852
|
+
"field-configs",
|
|
1853
|
+
"system-media",
|
|
1854
|
+
"track-assets",
|
|
1855
|
+
"audiences",
|
|
1856
|
+
"email-logs",
|
|
1857
|
+
"api-usage",
|
|
1858
|
+
"tenant-analytics-daily",
|
|
1859
|
+
"tenant-web-analytics-config",
|
|
1860
|
+
"analytics-event-schemas",
|
|
1861
|
+
"subscriptions",
|
|
1862
|
+
"billing-history",
|
|
1863
|
+
"inventory-reservations",
|
|
1864
|
+
"product-collection-items",
|
|
1865
|
+
"order-status-logs",
|
|
1866
|
+
"api-keys",
|
|
1867
|
+
"personal-access-tokens",
|
|
1868
|
+
"tenant-entitlements",
|
|
1869
|
+
"tenant-purge-jobs",
|
|
1870
|
+
"direct-upload-sessions",
|
|
1871
|
+
"webhook-events",
|
|
1872
|
+
"webhook-deliveries",
|
|
1873
|
+
"audit-logs",
|
|
1874
|
+
"plans",
|
|
1875
|
+
"webhooks",
|
|
1876
|
+
"event-registrations"
|
|
1877
|
+
];
|
|
1878
|
+
var COLLECTIONS = [
|
|
1879
|
+
"tenants",
|
|
1880
|
+
"tenant-metadata",
|
|
1881
|
+
"tenant-logos",
|
|
1882
|
+
"products",
|
|
1883
|
+
"product-variants",
|
|
1884
|
+
"product-options",
|
|
1885
|
+
"product-option-values",
|
|
1886
|
+
"product-categories",
|
|
1887
|
+
"product-tags",
|
|
1888
|
+
"product-collections",
|
|
1889
|
+
"brands",
|
|
1890
|
+
"brand-logos",
|
|
1891
|
+
"orders",
|
|
1892
|
+
"order-items",
|
|
1893
|
+
"returns",
|
|
1894
|
+
"return-items",
|
|
1895
|
+
"fulfillments",
|
|
1896
|
+
"fulfillment-items",
|
|
1897
|
+
"transactions",
|
|
1898
|
+
"customers",
|
|
1899
|
+
"customer-profiles",
|
|
1900
|
+
"customer-addresses",
|
|
1901
|
+
"carts",
|
|
1902
|
+
"cart-items",
|
|
1903
|
+
"discounts",
|
|
1904
|
+
"shipping-policies",
|
|
1905
|
+
"shipping-zones",
|
|
1906
|
+
"documents",
|
|
1907
|
+
"document-categories",
|
|
1908
|
+
"document-types",
|
|
1909
|
+
"articles",
|
|
1910
|
+
"article-authors",
|
|
1911
|
+
"article-categories",
|
|
1912
|
+
"article-tags",
|
|
1913
|
+
"playlists",
|
|
1914
|
+
"playlist-categories",
|
|
1915
|
+
"playlist-tags",
|
|
1916
|
+
"tracks",
|
|
1917
|
+
"track-categories",
|
|
1918
|
+
"track-tags",
|
|
1919
|
+
"galleries",
|
|
1920
|
+
"gallery-categories",
|
|
1921
|
+
"gallery-tags",
|
|
1922
|
+
"gallery-items",
|
|
1923
|
+
"links",
|
|
1924
|
+
"link-categories",
|
|
1925
|
+
"link-tags",
|
|
1926
|
+
"canvases",
|
|
1927
|
+
"canvas-node-types",
|
|
1928
|
+
"canvas-edge-types",
|
|
1929
|
+
"canvas-categories",
|
|
1930
|
+
"canvas-tags",
|
|
1931
|
+
"canvas-nodes",
|
|
1932
|
+
"canvas-edges",
|
|
1933
|
+
"videos",
|
|
1934
|
+
"video-categories",
|
|
1935
|
+
"video-tags",
|
|
1936
|
+
"live-streams",
|
|
1937
|
+
"images",
|
|
1938
|
+
"forms",
|
|
1939
|
+
"form-submissions",
|
|
1940
|
+
// Community
|
|
1941
|
+
"posts",
|
|
1942
|
+
"comments",
|
|
1943
|
+
"reactions",
|
|
1944
|
+
"reaction-types",
|
|
1945
|
+
"bookmarks",
|
|
1946
|
+
"post-categories",
|
|
1947
|
+
"customer-profile-lists",
|
|
1948
|
+
// Events
|
|
1949
|
+
"event-calendars",
|
|
1950
|
+
"events",
|
|
1951
|
+
"event-categories",
|
|
1952
|
+
"event-occurrences",
|
|
1953
|
+
"event-tags"
|
|
1954
|
+
];
|
|
1955
|
+
var SERVER_ONLY_COLLECTIONS = [
|
|
1956
|
+
"customer-groups",
|
|
1957
|
+
"reports",
|
|
1958
|
+
"community-bans"
|
|
1959
|
+
];
|
|
1960
|
+
var SERVER_COLLECTIONS = [
|
|
1961
|
+
...COLLECTIONS,
|
|
1962
|
+
...SERVER_ONLY_COLLECTIONS
|
|
1963
|
+
];
|
|
1964
|
+
|
|
2765
1965
|
// src/core/query/realtime.ts
|
|
2766
1966
|
var INITIAL_RECONNECT_DELAY = 1e3;
|
|
2767
1967
|
var MAX_RECONNECT_DELAY = 3e4;
|
|
@@ -2907,123 +2107,6 @@ var RealtimeConnection = class {
|
|
|
2907
2107
|
}
|
|
2908
2108
|
};
|
|
2909
2109
|
|
|
2910
|
-
// src/core/webhook/index.ts
|
|
2911
|
-
function isValidWebhookEvent(data) {
|
|
2912
|
-
if (typeof data !== "object" || data === null) return false;
|
|
2913
|
-
const obj = data;
|
|
2914
|
-
return typeof obj.collection === "string" && typeof obj.operation === "string" && obj.operation.length > 0 && typeof obj.data === "object" && obj.data !== null;
|
|
2915
|
-
}
|
|
2916
|
-
var CUSTOMER_PASSWORD_RESET_OPERATION = "password-reset";
|
|
2917
|
-
function isRecord(value) {
|
|
2918
|
-
return typeof value === "object" && value !== null;
|
|
2919
|
-
}
|
|
2920
|
-
function hasString(value, key) {
|
|
2921
|
-
return typeof value[key] === "string";
|
|
2922
|
-
}
|
|
2923
|
-
function hasStringOrNumber(value, key) {
|
|
2924
|
-
return typeof value[key] === "string" || typeof value[key] === "number";
|
|
2925
|
-
}
|
|
2926
|
-
function isCustomerPasswordResetWebhookEvent(event) {
|
|
2927
|
-
if (event.collection !== "customers" || event.operation !== CUSTOMER_PASSWORD_RESET_OPERATION || !isRecord(event.data)) {
|
|
2928
|
-
return false;
|
|
2929
|
-
}
|
|
2930
|
-
return hasStringOrNumber(event.data, "customerId") && hasString(event.data, "email") && hasString(event.data, "name") && hasString(event.data, "resetPasswordToken") && hasString(event.data, "resetPasswordExpiresAt");
|
|
2931
|
-
}
|
|
2932
|
-
function createCustomerAuthWebhookHandler(handlers) {
|
|
2933
|
-
return async (event) => {
|
|
2934
|
-
if (isCustomerPasswordResetWebhookEvent(event) && handlers.passwordReset) {
|
|
2935
|
-
await handlers.passwordReset(event.data, event);
|
|
2936
|
-
return;
|
|
2937
|
-
}
|
|
2938
|
-
await handlers.unhandled?.(event);
|
|
2939
|
-
};
|
|
2940
|
-
}
|
|
2941
|
-
async function verifySignature(payload, secret, signature, timestamp, deliveryId) {
|
|
2942
|
-
const encoder = new TextEncoder();
|
|
2943
|
-
const key = await crypto.subtle.importKey(
|
|
2944
|
-
"raw",
|
|
2945
|
-
encoder.encode(secret),
|
|
2946
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
2947
|
-
false,
|
|
2948
|
-
["verify"]
|
|
2949
|
-
);
|
|
2950
|
-
if (signature.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(signature)) {
|
|
2951
|
-
return false;
|
|
2952
|
-
}
|
|
2953
|
-
const sigBytes = new Uint8Array(
|
|
2954
|
-
(signature.match(/.{2}/g) ?? []).map((byte) => parseInt(byte, 16))
|
|
2955
|
-
);
|
|
2956
|
-
return crypto.subtle.verify(
|
|
2957
|
-
"HMAC",
|
|
2958
|
-
key,
|
|
2959
|
-
sigBytes,
|
|
2960
|
-
encoder.encode(`${timestamp}.${deliveryId}.${payload}`)
|
|
2961
|
-
);
|
|
2962
|
-
}
|
|
2963
|
-
function timestampIsFresh(timestamp, toleranceSeconds) {
|
|
2964
|
-
if (!/^\d+$/.test(timestamp)) return false;
|
|
2965
|
-
const timestampMs = Number(timestamp);
|
|
2966
|
-
if (!Number.isFinite(timestampMs)) return false;
|
|
2967
|
-
const skewMs = Math.abs(Date.now() - timestampMs);
|
|
2968
|
-
return skewMs <= toleranceSeconds * 1e3;
|
|
2969
|
-
}
|
|
2970
|
-
async function handleWebhook(request, handler, options) {
|
|
2971
|
-
try {
|
|
2972
|
-
const rawBody = await request.text();
|
|
2973
|
-
if (options?.secret) {
|
|
2974
|
-
const signature = request.headers.get("x-webhook-signature") || "";
|
|
2975
|
-
const timestamp = request.headers.get("x-webhook-timestamp") || "";
|
|
2976
|
-
const deliveryId = request.headers.get("x-webhook-delivery-id") || "";
|
|
2977
|
-
const toleranceSeconds = options.toleranceSeconds ?? 300;
|
|
2978
|
-
const valid = Boolean(timestamp && deliveryId) && timestampIsFresh(timestamp, toleranceSeconds) && await verifySignature(
|
|
2979
|
-
rawBody,
|
|
2980
|
-
options.secret,
|
|
2981
|
-
signature,
|
|
2982
|
-
timestamp,
|
|
2983
|
-
deliveryId
|
|
2984
|
-
);
|
|
2985
|
-
if (!valid) {
|
|
2986
|
-
return new Response(
|
|
2987
|
-
JSON.stringify({ error: "Invalid webhook signature" }),
|
|
2988
|
-
{ status: 401, headers: { "Content-Type": "application/json" } }
|
|
2989
|
-
);
|
|
2990
|
-
}
|
|
2991
|
-
} else {
|
|
2992
|
-
console.warn(
|
|
2993
|
-
"[@01.software/sdk] Webhook signature verification is disabled. Set { secret } in handleWebhook() options to enable HMAC-SHA256 verification."
|
|
2994
|
-
);
|
|
2995
|
-
}
|
|
2996
|
-
const body = JSON.parse(rawBody);
|
|
2997
|
-
if (!isValidWebhookEvent(body)) {
|
|
2998
|
-
return new Response(
|
|
2999
|
-
JSON.stringify({ error: "Invalid webhook event format" }),
|
|
3000
|
-
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
3001
|
-
);
|
|
3002
|
-
}
|
|
3003
|
-
await handler(body);
|
|
3004
|
-
return new Response(
|
|
3005
|
-
JSON.stringify({ success: true, message: "Webhook processed" }),
|
|
3006
|
-
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
3007
|
-
);
|
|
3008
|
-
} catch (error) {
|
|
3009
|
-
console.error("Webhook processing error:", error);
|
|
3010
|
-
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
|
3011
|
-
status: 500,
|
|
3012
|
-
headers: { "Content-Type": "application/json" }
|
|
3013
|
-
});
|
|
3014
|
-
}
|
|
3015
|
-
}
|
|
3016
|
-
function createTypedWebhookHandler(collection, handler) {
|
|
3017
|
-
return async (event) => {
|
|
3018
|
-
if (event.collection !== collection) {
|
|
3019
|
-
throw new Error(
|
|
3020
|
-
`Expected collection "${collection}", got "${event.collection}"`
|
|
3021
|
-
);
|
|
3022
|
-
}
|
|
3023
|
-
return handler(event);
|
|
3024
|
-
};
|
|
3025
|
-
}
|
|
3026
|
-
|
|
3027
2110
|
// src/utils/ecommerce.ts
|
|
3028
2111
|
var ProductSelectionCodecError = class extends Error {
|
|
3029
2112
|
constructor(message) {
|
|
@@ -3308,38 +2391,45 @@ function hasExplicitSelection(selection) {
|
|
|
3308
2391
|
);
|
|
3309
2392
|
}
|
|
3310
2393
|
function assignSelectedValue(matrix, selectedByOptionId, optionId, valueId) {
|
|
3311
|
-
if (valueId == null) return;
|
|
2394
|
+
if (valueId == null) return false;
|
|
3312
2395
|
const normalizedValueId = String(valueId);
|
|
3313
|
-
if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return;
|
|
2396
|
+
if (matrix.valueToOptionId.get(normalizedValueId) !== optionId) return false;
|
|
3314
2397
|
selectedByOptionId.set(optionId, normalizedValueId);
|
|
2398
|
+
return true;
|
|
3315
2399
|
}
|
|
3316
2400
|
function assignSelectedValueSlugByOptionId(matrix, selectedByOptionId, optionId, valueSlug) {
|
|
3317
|
-
if (!valueSlug) return;
|
|
2401
|
+
if (!valueSlug) return false;
|
|
3318
2402
|
const option = matrix.optionById.get(optionId);
|
|
3319
|
-
if (!option) return;
|
|
3320
|
-
const
|
|
3321
|
-
|
|
2403
|
+
if (!option) return false;
|
|
2404
|
+
const values = option.values.filter(
|
|
2405
|
+
(candidate) => candidate.slug === valueSlug
|
|
2406
|
+
);
|
|
2407
|
+
if (values.length > 1) {
|
|
2408
|
+
throw new ProductSelectionCodecError(
|
|
2409
|
+
`Ambiguous product selection value slug "${valueSlug}" for option "${optionId}". Use opt.<optionId>=<valueId>.`
|
|
2410
|
+
);
|
|
2411
|
+
}
|
|
2412
|
+
const value = values[0];
|
|
2413
|
+
if (!value) return false;
|
|
3322
2414
|
selectedByOptionId.set(optionId, value.id);
|
|
2415
|
+
return true;
|
|
3323
2416
|
}
|
|
3324
2417
|
function assignSelectedValueSlugByOptionSlug(matrix, selectedByOptionId, optionSlug, valueSlug) {
|
|
3325
|
-
if (!valueSlug) return;
|
|
2418
|
+
if (!valueSlug) return false;
|
|
3326
2419
|
const option = matrix.optionBySlug.get(optionSlug);
|
|
3327
|
-
if (!option) return;
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
selectedByOptionId.set(option.id, value.id);
|
|
3331
|
-
}
|
|
3332
|
-
function requireValueSlug(value) {
|
|
3333
|
-
if (value.slug) return value.slug;
|
|
3334
|
-
throw new ProductSelectionCodecError(
|
|
3335
|
-
`Option value "${value.id}" does not have a slug and cannot be used in product selection URLs.`
|
|
3336
|
-
);
|
|
3337
|
-
}
|
|
3338
|
-
function requireOptionSlug(option) {
|
|
3339
|
-
if (option.slug) return option.slug;
|
|
3340
|
-
throw new ProductSelectionCodecError(
|
|
3341
|
-
`Option "${option.id}" does not have a slug and cannot be used in product selection URLs.`
|
|
2420
|
+
if (!option) return false;
|
|
2421
|
+
const values = option.values.filter(
|
|
2422
|
+
(candidate) => candidate.slug === valueSlug
|
|
3342
2423
|
);
|
|
2424
|
+
if (values.length > 1) {
|
|
2425
|
+
throw new ProductSelectionCodecError(
|
|
2426
|
+
`Ambiguous product selection value slug "${valueSlug}" for option "${optionSlug}". Use opt.<optionId>=<valueId>.`
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
const value = values[0];
|
|
2430
|
+
if (!value) return false;
|
|
2431
|
+
selectedByOptionId.set(option.id, value.id);
|
|
2432
|
+
return true;
|
|
3343
2433
|
}
|
|
3344
2434
|
function toSearchParams(search) {
|
|
3345
2435
|
if (!search) return new URLSearchParams();
|
|
@@ -3363,6 +2453,15 @@ function slugLike(value) {
|
|
|
3363
2453
|
}
|
|
3364
2454
|
function assertNoAmbiguousSelectionParams(matrix, params) {
|
|
3365
2455
|
const knownSelectionKeys = /* @__PURE__ */ new Set();
|
|
2456
|
+
const hasVariantParam = params.has("variant");
|
|
2457
|
+
const hasOptionParams = Array.from(params.keys()).some(
|
|
2458
|
+
(key) => key.startsWith("opt.")
|
|
2459
|
+
);
|
|
2460
|
+
if (hasVariantParam && hasOptionParams) {
|
|
2461
|
+
throw new ProductSelectionCodecError(
|
|
2462
|
+
"Product selection URL cannot mix variant=<variantId> with opt.<optionId>=<valueId> params."
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
3366
2465
|
for (const option of matrix.options) {
|
|
3367
2466
|
knownSelectionKeys.add(slugLike(option.slug));
|
|
3368
2467
|
knownSelectionKeys.add(slugLike(option.title));
|
|
@@ -3375,12 +2474,25 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
|
|
|
3375
2474
|
const optionToken = key.slice(4);
|
|
3376
2475
|
if (!optionToken || !matrix.optionBySlug.has(optionToken) && !matrix.optionById.has(optionToken)) {
|
|
3377
2476
|
throw new ProductSelectionCodecError(
|
|
3378
|
-
`Unknown product selection query parameter "${key}". Use opt.<
|
|
2477
|
+
`Unknown product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
if (!value) {
|
|
2481
|
+
throw new ProductSelectionCodecError(
|
|
2482
|
+
`Product selection query parameter "${key}" requires a value ID or compatibility value slug.`
|
|
3379
2483
|
);
|
|
3380
2484
|
}
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
if (key === "variant") {
|
|
3381
2488
|
if (!value) {
|
|
3382
2489
|
throw new ProductSelectionCodecError(
|
|
3383
|
-
|
|
2490
|
+
'Product selection query parameter "variant" requires a variant ID.'
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
if (!getVariantSelection(matrix, value)) {
|
|
2494
|
+
throw new ProductSelectionCodecError(
|
|
2495
|
+
`Unknown product selection variant "${value}".`
|
|
3384
2496
|
);
|
|
3385
2497
|
}
|
|
3386
2498
|
continue;
|
|
@@ -3388,55 +2500,97 @@ function assertNoAmbiguousSelectionParams(matrix, params) {
|
|
|
3388
2500
|
const keyToken = slugLike(key);
|
|
3389
2501
|
if (knownSelectionKeys.has(keyToken) || value === "" && knownSelectionKeys.has(keyToken)) {
|
|
3390
2502
|
throw new ProductSelectionCodecError(
|
|
3391
|
-
`Ambiguous product selection query parameter "${key}". Use opt.<
|
|
2503
|
+
`Ambiguous product selection query parameter "${key}". Use opt.<optionId>=<valueId>.`
|
|
3392
2504
|
);
|
|
3393
2505
|
}
|
|
3394
2506
|
}
|
|
3395
2507
|
}
|
|
3396
|
-
function
|
|
2508
|
+
function emitCompatibilityOptionIdParam(options, event) {
|
|
3397
2509
|
try {
|
|
2510
|
+
if (options?.onCompatibilityOptionIdParam) {
|
|
2511
|
+
options.onCompatibilityOptionIdParam(event);
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
3398
2514
|
options?.onLegacyOptionIdParam?.(event);
|
|
3399
2515
|
} catch {
|
|
3400
2516
|
}
|
|
3401
2517
|
}
|
|
3402
2518
|
function assignSearchSelection(matrix, selectedByOptionId, search, options) {
|
|
3403
|
-
if (!search) return;
|
|
2519
|
+
if (!search) return null;
|
|
3404
2520
|
const params = toSearchParams(search);
|
|
3405
2521
|
assertNoAmbiguousSelectionParams(matrix, params);
|
|
3406
|
-
|
|
2522
|
+
const variantParam = params.get("variant");
|
|
2523
|
+
if (variantParam != null) {
|
|
2524
|
+
const variantSelection = getVariantSelection(matrix, variantParam);
|
|
2525
|
+
if (!variantSelection) {
|
|
2526
|
+
throw new ProductSelectionCodecError(
|
|
2527
|
+
`Unknown product selection variant "${variantParam}".`
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
for (const [optionId, valueId] of variantSelection.optionValueByOptionId) {
|
|
2531
|
+
selectedByOptionId.set(optionId, valueId);
|
|
2532
|
+
}
|
|
2533
|
+
return variantSelection.id;
|
|
2534
|
+
}
|
|
2535
|
+
for (const [key, valueToken] of params.entries()) {
|
|
3407
2536
|
if (!key.startsWith("opt.")) continue;
|
|
3408
2537
|
const optionToken = key.slice(4);
|
|
2538
|
+
const optionById = matrix.optionById.get(optionToken);
|
|
2539
|
+
if (optionById) {
|
|
2540
|
+
if (assignSelectedValue(
|
|
2541
|
+
matrix,
|
|
2542
|
+
selectedByOptionId,
|
|
2543
|
+
optionById.id,
|
|
2544
|
+
valueToken
|
|
2545
|
+
)) {
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
2548
|
+
const before = selectedByOptionId.get(optionById.id);
|
|
2549
|
+
if (assignSelectedValueSlugByOptionId(
|
|
2550
|
+
matrix,
|
|
2551
|
+
selectedByOptionId,
|
|
2552
|
+
optionById.id,
|
|
2553
|
+
valueToken
|
|
2554
|
+
) && selectedByOptionId.get(optionById.id) !== before) {
|
|
2555
|
+
emitCompatibilityOptionIdParam(options, {
|
|
2556
|
+
optionId: optionById.id,
|
|
2557
|
+
optionSlug: optionById.slug,
|
|
2558
|
+
valueSlug: valueToken,
|
|
2559
|
+
searchParam: key
|
|
2560
|
+
});
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
throw new ProductSelectionCodecError(
|
|
2564
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
3409
2567
|
const optionBySlug = matrix.optionBySlug.get(optionToken);
|
|
3410
2568
|
if (optionBySlug) {
|
|
3411
|
-
assignSelectedValueSlugByOptionSlug(
|
|
2569
|
+
if (assignSelectedValueSlugByOptionSlug(
|
|
3412
2570
|
matrix,
|
|
3413
2571
|
selectedByOptionId,
|
|
3414
2572
|
optionBySlug.slug,
|
|
3415
|
-
|
|
2573
|
+
valueToken
|
|
2574
|
+
)) {
|
|
2575
|
+
continue;
|
|
2576
|
+
}
|
|
2577
|
+
if (matrix.valueById.has(valueToken)) {
|
|
2578
|
+
throw new ProductSelectionCodecError(
|
|
2579
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionId>=<valueId>.`
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
throw new ProductSelectionCodecError(
|
|
2583
|
+
`Unknown product selection value "${valueToken}" for option "${optionToken}". Use opt.<optionSlug>=<valueSlug> for compatibility URLs.`
|
|
3416
2584
|
);
|
|
3417
|
-
continue;
|
|
3418
|
-
}
|
|
3419
|
-
const legacyOption = matrix.optionById.get(optionToken);
|
|
3420
|
-
if (!legacyOption) continue;
|
|
3421
|
-
const before = selectedByOptionId.get(legacyOption.id);
|
|
3422
|
-
assignSelectedValueSlugByOptionId(
|
|
3423
|
-
matrix,
|
|
3424
|
-
selectedByOptionId,
|
|
3425
|
-
legacyOption.id,
|
|
3426
|
-
valueSlug
|
|
3427
|
-
);
|
|
3428
|
-
if (selectedByOptionId.get(legacyOption.id) !== before) {
|
|
3429
|
-
emitLegacyOptionIdParam(options, {
|
|
3430
|
-
optionId: legacyOption.id,
|
|
3431
|
-
optionSlug: legacyOption.slug,
|
|
3432
|
-
valueSlug,
|
|
3433
|
-
searchParam: key
|
|
3434
|
-
});
|
|
3435
2585
|
}
|
|
3436
2586
|
}
|
|
2587
|
+
return null;
|
|
3437
2588
|
}
|
|
3438
2589
|
function normalizeProductSelection(detail, selection = {}, options) {
|
|
3439
2590
|
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
2591
|
+
return normalizeProductSelectionFromMatrix(matrix, selection, options);
|
|
2592
|
+
}
|
|
2593
|
+
function normalizeProductSelectionFromMatrix(matrix, selection = {}, options) {
|
|
3440
2594
|
const selectedByOptionId = /* @__PURE__ */ new Map();
|
|
3441
2595
|
const variantSelection = getVariantSelection(matrix, selection.variantId);
|
|
3442
2596
|
const variantId = variantSelection?.id ?? null;
|
|
@@ -3452,7 +2606,12 @@ function normalizeProductSelection(detail, selection = {}, options) {
|
|
|
3452
2606
|
if (!optionId) continue;
|
|
3453
2607
|
selectedByOptionId.set(optionId, valueId);
|
|
3454
2608
|
}
|
|
3455
|
-
|
|
2609
|
+
const searchVariantId = assignSearchSelection(
|
|
2610
|
+
matrix,
|
|
2611
|
+
selectedByOptionId,
|
|
2612
|
+
selection.search,
|
|
2613
|
+
options
|
|
2614
|
+
);
|
|
3456
2615
|
for (const [rawOptionId, rawSelection] of Object.entries(
|
|
3457
2616
|
selection.byOptionId ?? {}
|
|
3458
2617
|
)) {
|
|
@@ -3529,7 +2688,7 @@ function normalizeProductSelection(detail, selection = {}, options) {
|
|
|
3529
2688
|
byOptionSlug,
|
|
3530
2689
|
byOptionId,
|
|
3531
2690
|
valueIds: matrix.optionIds.map((optionId) => byOptionId[optionId]).filter((valueId) => Boolean(valueId)),
|
|
3532
|
-
variantId
|
|
2691
|
+
variantId: searchVariantId ?? variantId
|
|
3533
2692
|
};
|
|
3534
2693
|
}
|
|
3535
2694
|
function parseProductSelection(detail, search, options) {
|
|
@@ -3537,15 +2696,31 @@ function parseProductSelection(detail, search, options) {
|
|
|
3537
2696
|
}
|
|
3538
2697
|
function stringifyProductSelection(detail, selection = {}, options) {
|
|
3539
2698
|
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
3540
|
-
const normalized =
|
|
2699
|
+
const normalized = normalizeProductSelectionFromMatrix(
|
|
2700
|
+
matrix,
|
|
2701
|
+
selection,
|
|
2702
|
+
options
|
|
2703
|
+
);
|
|
3541
2704
|
const params = new URLSearchParams();
|
|
2705
|
+
if (hasExplicitSelection(selection)) {
|
|
2706
|
+
const matchingVariants = getMatchingVariantEntries(matrix, normalized);
|
|
2707
|
+
const exactVariant = getExactSelectedVariantEntry(
|
|
2708
|
+
matrix,
|
|
2709
|
+
normalized,
|
|
2710
|
+
matchingVariants
|
|
2711
|
+
);
|
|
2712
|
+
if (exactVariant) {
|
|
2713
|
+
params.set("variant", exactVariant.id);
|
|
2714
|
+
return params.toString();
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
3542
2717
|
for (const optionId of matrix.optionIds) {
|
|
3543
2718
|
const valueId = normalized.byOptionId[optionId];
|
|
3544
2719
|
if (!valueId) continue;
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
params.append(`opt.${
|
|
2720
|
+
if (!matrix.optionById.has(optionId) || !matrix.valueById.has(valueId)) {
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2723
|
+
params.append(`opt.${optionId}`, valueId);
|
|
3549
2724
|
}
|
|
3550
2725
|
return params.toString();
|
|
3551
2726
|
}
|
|
@@ -3610,17 +2785,21 @@ function mediaArray(values) {
|
|
|
3610
2785
|
return values.filter(isPresentMedia);
|
|
3611
2786
|
}
|
|
3612
2787
|
function buildSelectionMedia(detail, selectedVariant, matchingVariants, selectedValues) {
|
|
2788
|
+
const selectedVariantImages = mediaArray(selectedVariant?.images);
|
|
3613
2789
|
const selectedValueImages = selectedValues.flatMap(
|
|
3614
2790
|
(value) => mediaArray(value.images)
|
|
3615
2791
|
);
|
|
2792
|
+
const matchingVariantImages = matchingVariants.flatMap(
|
|
2793
|
+
(variant) => mediaArray(variant.images)
|
|
2794
|
+
);
|
|
3616
2795
|
const selectedValuePrimary = selectedValues.map((value) => firstMedia(value.thumbnail) ?? firstMedia(value.images)).find((value) => value != null) ?? null;
|
|
3617
2796
|
const selectedVariantPrimary = firstMedia(selectedVariant?.thumbnail) ?? firstMedia(selectedVariant?.images);
|
|
3618
2797
|
const matchingVariantPrimary = matchingVariants.map(
|
|
3619
2798
|
(variant) => firstMedia(variant.thumbnail) ?? firstMedia(variant.images)
|
|
3620
2799
|
).find((value) => value != null) ?? null;
|
|
3621
2800
|
const detailImages = mediaArray(detail.images);
|
|
3622
|
-
const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? firstMedia(detail.listing.primaryImage) ??
|
|
3623
|
-
const images =
|
|
2801
|
+
const primaryImage = selectedVariantPrimary ?? selectedValuePrimary ?? matchingVariantPrimary ?? firstMedia(detail.listing.primaryImage) ?? firstMedia(detailImages);
|
|
2802
|
+
const images = selectedVariantImages.length > 0 ? selectedVariantImages : selectedValueImages.length > 0 ? selectedValueImages : matchingVariantImages.length > 0 ? matchingVariantImages : detailImages;
|
|
3624
2803
|
return {
|
|
3625
2804
|
primaryImage,
|
|
3626
2805
|
images
|
|
@@ -3646,11 +2825,39 @@ function buildSelectionStock(selectedVariant, matchingVariants) {
|
|
|
3646
2825
|
availableStock: null
|
|
3647
2826
|
};
|
|
3648
2827
|
}
|
|
3649
|
-
function
|
|
3650
|
-
const
|
|
3651
|
-
const
|
|
3652
|
-
const
|
|
3653
|
-
|
|
2828
|
+
function buildAvailableValueStock(variants) {
|
|
2829
|
+
const activeVariants = variants.filter((variant) => variant.isActive !== false);
|
|
2830
|
+
const isUnlimited = activeVariants.some((variant) => variant.isUnlimited);
|
|
2831
|
+
const availableStock = isUnlimited ? null : activeVariants.reduce(
|
|
2832
|
+
(sum, variant) => sum + Math.max(0, variant.stock - variant.reservedStock),
|
|
2833
|
+
0
|
|
2834
|
+
);
|
|
2835
|
+
return {
|
|
2836
|
+
availableForSale: activeVariants.some(isVariantAvailableForSale),
|
|
2837
|
+
isUnlimited,
|
|
2838
|
+
availableStock
|
|
2839
|
+
};
|
|
2840
|
+
}
|
|
2841
|
+
function getCandidateVariantsForValue(matrix, optionId, valueId, selectedValueIds) {
|
|
2842
|
+
const selectedByOption = getSelectedValueByOptionId(matrix, selectedValueIds);
|
|
2843
|
+
selectedByOption.set(optionId, valueId);
|
|
2844
|
+
return matrix.variants.filter(
|
|
2845
|
+
(variant) => Array.from(selectedByOption.entries()).every(
|
|
2846
|
+
([selectedOptionId, selectedValueId]) => variant.optionValueByOptionId.get(selectedOptionId) === selectedValueId
|
|
2847
|
+
)
|
|
2848
|
+
).map((variant) => variant.source);
|
|
2849
|
+
}
|
|
2850
|
+
function getResolutionContext(context) {
|
|
2851
|
+
return {
|
|
2852
|
+
images: context?.images ?? context?.detail?.images ?? [],
|
|
2853
|
+
listing: context?.listing ?? context?.detail?.listing ?? {}
|
|
2854
|
+
};
|
|
2855
|
+
}
|
|
2856
|
+
function resolveProductSelectionFromMatrix(matrix, selection = {}, options, context) {
|
|
2857
|
+
const { images, listing } = getResolutionContext(context);
|
|
2858
|
+
const effectiveSelection = hasExplicitSelection(selection) || listing.selectionHintVariant == null ? selection : { ...selection, variantId: listing.selectionHintVariant };
|
|
2859
|
+
const normalizedSelection = normalizeProductSelectionFromMatrix(
|
|
2860
|
+
matrix,
|
|
3654
2861
|
effectiveSelection,
|
|
3655
2862
|
options
|
|
3656
2863
|
);
|
|
@@ -3685,9 +2892,17 @@ function resolveProductSelection(detail, selection = {}, options) {
|
|
|
3685
2892
|
option.values.map((value) => ({
|
|
3686
2893
|
valueId: value.id,
|
|
3687
2894
|
value: value.label,
|
|
3688
|
-
slug:
|
|
2895
|
+
slug: value.slug ?? "",
|
|
3689
2896
|
selected: normalizedSelection.byOptionId[option.id] === value.id,
|
|
3690
2897
|
available: availableValueIds.has(value.id),
|
|
2898
|
+
...buildAvailableValueStock(
|
|
2899
|
+
getCandidateVariantsForValue(
|
|
2900
|
+
matrix,
|
|
2901
|
+
option.id,
|
|
2902
|
+
value.id,
|
|
2903
|
+
normalizedSelection.valueIds
|
|
2904
|
+
)
|
|
2905
|
+
),
|
|
3691
2906
|
swatchColor: value.swatchColor ?? null,
|
|
3692
2907
|
thumbnail: value.thumbnail ?? null,
|
|
3693
2908
|
images: value.images ?? null
|
|
@@ -3715,7 +2930,12 @@ function resolveProductSelection(detail, selection = {}, options) {
|
|
|
3715
2930
|
allOptionsSelected,
|
|
3716
2931
|
price: buildSelectionPrice(priceVariants),
|
|
3717
2932
|
media: buildSelectionMedia(
|
|
3718
|
-
|
|
2933
|
+
{
|
|
2934
|
+
images,
|
|
2935
|
+
listing: {
|
|
2936
|
+
primaryImage: listing.primaryImage ?? null
|
|
2937
|
+
}
|
|
2938
|
+
},
|
|
3719
2939
|
selectedVariant,
|
|
3720
2940
|
matchingVariants,
|
|
3721
2941
|
selectedValues
|
|
@@ -3723,6 +2943,100 @@ function resolveProductSelection(detail, selection = {}, options) {
|
|
|
3723
2943
|
stock: buildSelectionStock(selectedVariant, matchingVariants)
|
|
3724
2944
|
};
|
|
3725
2945
|
}
|
|
2946
|
+
function resolveProductSelection(detail, selection = {}, options) {
|
|
2947
|
+
const matrix = buildProductOptionMatrixFromDetail(detail);
|
|
2948
|
+
return resolveProductSelectionFromMatrix(matrix, selection, options, {
|
|
2949
|
+
detail
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
function isProductDetailImageMedia(value) {
|
|
2953
|
+
return typeof value === "object" && value !== null;
|
|
2954
|
+
}
|
|
2955
|
+
function mediaDedupeKey(value) {
|
|
2956
|
+
if (value.id != null) return `id:${String(value.id)}`;
|
|
2957
|
+
if (value.url) return `url:${value.url}`;
|
|
2958
|
+
return null;
|
|
2959
|
+
}
|
|
2960
|
+
function getProductSelectionImages(resolution) {
|
|
2961
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2962
|
+
const images = [];
|
|
2963
|
+
const candidates = [
|
|
2964
|
+
resolution.media.primaryImage,
|
|
2965
|
+
...resolution.media.images
|
|
2966
|
+
].filter(isProductDetailImageMedia);
|
|
2967
|
+
for (const candidate of candidates) {
|
|
2968
|
+
const key = mediaDedupeKey(candidate);
|
|
2969
|
+
if (key && seen.has(key)) continue;
|
|
2970
|
+
if (key) seen.add(key);
|
|
2971
|
+
images.push(candidate);
|
|
2972
|
+
}
|
|
2973
|
+
return images;
|
|
2974
|
+
}
|
|
2975
|
+
function getProductHrefSlug(product) {
|
|
2976
|
+
if ("product" in product && product.product?.slug) {
|
|
2977
|
+
return product.product.slug;
|
|
2978
|
+
}
|
|
2979
|
+
if ("slug" in product && product.slug) return product.slug;
|
|
2980
|
+
throw new ProductSelectionCodecError(
|
|
2981
|
+
"Product slug is required to build a product href."
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
function joinProductPath(basePath, slug, trailingSlash) {
|
|
2985
|
+
const base = basePath.replace(/\/+$/, "");
|
|
2986
|
+
const encodedSlug = encodeURIComponent(slug);
|
|
2987
|
+
return `${base}/${encodedSlug}${trailingSlash ? "/" : ""}`;
|
|
2988
|
+
}
|
|
2989
|
+
function getProductHrefGroupSelection(group, matrix) {
|
|
2990
|
+
if (!group) return null;
|
|
2991
|
+
if (group.variantId != null) return { variantId: group.variantId };
|
|
2992
|
+
const listingVariantId = group.listing?.selectionHintVariant;
|
|
2993
|
+
const optionId = group.optionId != null ? String(group.optionId) : group.optionSlug ? matrix?.optionBySlug.get(group.optionSlug)?.id : void 0;
|
|
2994
|
+
if (!optionId) {
|
|
2995
|
+
return listingVariantId != null ? { variantId: listingVariantId } : null;
|
|
2996
|
+
}
|
|
2997
|
+
const option = matrix?.optionById.get(optionId);
|
|
2998
|
+
const optionValueId = group.optionValueId != null ? String(group.optionValueId) : group.optionValueSlug && option ? option.values.find((value) => value.slug === group.optionValueSlug)?.id : void 0;
|
|
2999
|
+
if (!optionValueId) {
|
|
3000
|
+
return listingVariantId != null ? { variantId: listingVariantId } : null;
|
|
3001
|
+
}
|
|
3002
|
+
return { byOptionId: { [optionId]: optionValueId } };
|
|
3003
|
+
}
|
|
3004
|
+
function buildProductHref(product, group, options = {}) {
|
|
3005
|
+
const path = joinProductPath(
|
|
3006
|
+
options.basePath ?? "/products",
|
|
3007
|
+
getProductHrefSlug(product),
|
|
3008
|
+
options.trailingSlash ?? false
|
|
3009
|
+
);
|
|
3010
|
+
const params = new URLSearchParams();
|
|
3011
|
+
if (options.detail && options.selection) {
|
|
3012
|
+
const selection = stringifyProductSelection(options.detail, options.selection);
|
|
3013
|
+
return selection ? `${path}?${selection}` : path;
|
|
3014
|
+
}
|
|
3015
|
+
const groupSelection = getProductHrefGroupSelection(group, options.matrix);
|
|
3016
|
+
if (groupSelection) {
|
|
3017
|
+
if (options.detail) {
|
|
3018
|
+
const selection = stringifyProductSelection(options.detail, groupSelection);
|
|
3019
|
+
return selection ? `${path}?${selection}` : path;
|
|
3020
|
+
}
|
|
3021
|
+
if (groupSelection.variantId != null) {
|
|
3022
|
+
params.set("variant", String(groupSelection.variantId));
|
|
3023
|
+
return `${path}?${params.toString()}`;
|
|
3024
|
+
}
|
|
3025
|
+
const [selectionEntry] = Object.entries(groupSelection.byOptionId ?? {});
|
|
3026
|
+
if (selectionEntry) {
|
|
3027
|
+
const [optionId, valueId] = selectionEntry;
|
|
3028
|
+
params.set(`opt.${optionId}`, String(valueId));
|
|
3029
|
+
return `${path}?${params.toString()}`;
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
if (group?.optionValueSlug) {
|
|
3033
|
+
const optionSlug = group.optionSlug ?? (group.optionId != null ? options.matrix?.optionById.get(String(group.optionId))?.slug : void 0);
|
|
3034
|
+
if (optionSlug) {
|
|
3035
|
+
params.set(`opt.${optionSlug}`, group.optionValueSlug);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
return params.size > 0 ? `${path}?${params.toString()}` : path;
|
|
3039
|
+
}
|
|
3726
3040
|
function compareVariantOrder(a, b) {
|
|
3727
3041
|
const aOrder = Number(a._order ?? Number.MAX_SAFE_INTEGER);
|
|
3728
3042
|
const bOrder = Number(b._order ?? Number.MAX_SAFE_INTEGER);
|
|
@@ -3776,6 +3090,72 @@ function buildProductListingProjection(product, variants) {
|
|
|
3776
3090
|
availableForSale: availableVariants.length > 0
|
|
3777
3091
|
};
|
|
3778
3092
|
}
|
|
3093
|
+
function buildProductListingCard(item, options = {}) {
|
|
3094
|
+
const product = item.product;
|
|
3095
|
+
const groups = item.groups;
|
|
3096
|
+
const primaryImage = firstMedia(product.thumbnail) ?? firstMedia(product.images ?? null) ?? null;
|
|
3097
|
+
const priceRange = aggregateListingPriceRange(groups);
|
|
3098
|
+
const availableForSale = groups.some(
|
|
3099
|
+
(group) => group.listing.availableForSale
|
|
3100
|
+
);
|
|
3101
|
+
const swatches = groups.length > 1 ? groups.map((group) => buildListingSwatch(product, group, options)) : [];
|
|
3102
|
+
return {
|
|
3103
|
+
id: String(product.id),
|
|
3104
|
+
href: buildProductHref({ slug: product.slug }, void 0, options),
|
|
3105
|
+
title: product.title,
|
|
3106
|
+
primaryImage,
|
|
3107
|
+
priceRange,
|
|
3108
|
+
availableForSale,
|
|
3109
|
+
swatches
|
|
3110
|
+
};
|
|
3111
|
+
}
|
|
3112
|
+
function aggregateListingPriceRange(groups) {
|
|
3113
|
+
const minPrice = minOfNullable(groups.map((g) => g.listing.minPrice));
|
|
3114
|
+
const maxPrice = maxOfNullable(groups.map((g) => g.listing.maxPrice));
|
|
3115
|
+
const minCompareAtPrice = minOfNullable(
|
|
3116
|
+
groups.map((g) => g.listing.minCompareAtPrice)
|
|
3117
|
+
);
|
|
3118
|
+
const maxCompareAtPrice = maxOfNullable(
|
|
3119
|
+
groups.map((g) => g.listing.maxCompareAtPrice)
|
|
3120
|
+
);
|
|
3121
|
+
const isPriceRange = minPrice !== null && maxPrice !== null && minPrice !== maxPrice;
|
|
3122
|
+
return {
|
|
3123
|
+
minPrice,
|
|
3124
|
+
maxPrice,
|
|
3125
|
+
minCompareAtPrice,
|
|
3126
|
+
maxCompareAtPrice,
|
|
3127
|
+
isPriceRange
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
function buildListingSwatch(product, group, options) {
|
|
3131
|
+
const thumbnail = firstMedia(group.optionValueThumbnail) ?? firstMedia(group.optionValueImages) ?? null;
|
|
3132
|
+
return {
|
|
3133
|
+
optionId: group.optionId,
|
|
3134
|
+
optionValueId: group.optionValueId,
|
|
3135
|
+
label: group.optionValueLabel,
|
|
3136
|
+
swatchColor: group.optionValueSwatchColor ?? null,
|
|
3137
|
+
thumbnail,
|
|
3138
|
+
href: buildProductHref(
|
|
3139
|
+
{ slug: product.slug },
|
|
3140
|
+
{
|
|
3141
|
+
optionId: group.optionId,
|
|
3142
|
+
optionValueId: group.optionValueId,
|
|
3143
|
+
optionValueSlug: group.optionValueSlug,
|
|
3144
|
+
listing: group.listing
|
|
3145
|
+
},
|
|
3146
|
+
options
|
|
3147
|
+
),
|
|
3148
|
+
availableForSale: group.listing.availableForSale
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
3151
|
+
function minOfNullable(values) {
|
|
3152
|
+
const numbers = values.filter((v) => v !== null);
|
|
3153
|
+
return numbers.length === 0 ? null : Math.min(...numbers);
|
|
3154
|
+
}
|
|
3155
|
+
function maxOfNullable(values) {
|
|
3156
|
+
const numbers = values.filter((v) => v !== null);
|
|
3157
|
+
return numbers.length === 0 ? null : Math.max(...numbers);
|
|
3158
|
+
}
|
|
3779
3159
|
function buildProductListingGroupsByOption(args) {
|
|
3780
3160
|
const primaryOptionId = args.primaryOptionId ?? void 0;
|
|
3781
3161
|
if (!primaryOptionId) return [];
|
|
@@ -4102,6 +3482,11 @@ function createAnalytics(config) {
|
|
|
4102
3482
|
}
|
|
4103
3483
|
};
|
|
4104
3484
|
}
|
|
3485
|
+
|
|
3486
|
+
// src/index.ts
|
|
3487
|
+
function createClient2(options) {
|
|
3488
|
+
return createClient(options);
|
|
3489
|
+
}
|
|
4105
3490
|
export {
|
|
4106
3491
|
ApiError,
|
|
4107
3492
|
AuthError,
|
|
@@ -4109,61 +3494,48 @@ export {
|
|
|
4109
3494
|
COLLECTIONS,
|
|
4110
3495
|
CUSTOMER_PASSWORD_RESET_OPERATION,
|
|
4111
3496
|
CartApi,
|
|
4112
|
-
Client,
|
|
4113
|
-
CollectionClient,
|
|
4114
|
-
CollectionHooks,
|
|
4115
|
-
CollectionQueryBuilder,
|
|
4116
3497
|
CommerceClient,
|
|
4117
3498
|
CommunityClient,
|
|
4118
3499
|
ConfigError,
|
|
4119
3500
|
ConflictError,
|
|
4120
3501
|
CustomerAuth,
|
|
4121
|
-
CustomerHooks,
|
|
4122
3502
|
CustomerNamespace,
|
|
4123
3503
|
DiscountApi,
|
|
4124
3504
|
GoneError,
|
|
4125
3505
|
IMAGE_SIZES,
|
|
4126
3506
|
INTERNAL_COLLECTIONS,
|
|
4127
|
-
ModerationApi,
|
|
4128
3507
|
NetworkError,
|
|
4129
3508
|
NotFoundError,
|
|
4130
3509
|
OrderApi,
|
|
4131
3510
|
PermissionError,
|
|
4132
3511
|
ProductApi,
|
|
4133
3512
|
ProductSelectionCodecError,
|
|
4134
|
-
QueryHooks,
|
|
4135
3513
|
RateLimitError,
|
|
4136
|
-
ReadOnlyCollectionClient,
|
|
4137
3514
|
RealtimeConnection,
|
|
4138
3515
|
SDKError,
|
|
4139
3516
|
SERVER_COLLECTIONS,
|
|
4140
3517
|
SERVER_ONLY_COLLECTIONS,
|
|
4141
|
-
ServerClient,
|
|
4142
|
-
ServerCollectionClient,
|
|
4143
|
-
ServerCollectionQueryBuilder,
|
|
4144
|
-
ServerCommerceClient,
|
|
4145
3518
|
ServiceUnavailableError,
|
|
4146
3519
|
ShippingApi,
|
|
4147
3520
|
TimeoutError,
|
|
4148
3521
|
UsageLimitError,
|
|
4149
3522
|
ValidationError,
|
|
3523
|
+
buildProductHref,
|
|
3524
|
+
buildProductListingCard,
|
|
4150
3525
|
buildProductListingGroupsByOption,
|
|
4151
3526
|
buildProductListingProjection,
|
|
4152
3527
|
buildProductOptionMatrix,
|
|
4153
3528
|
buildProductOptionMatrixFromDetail,
|
|
4154
|
-
collectionKeys,
|
|
4155
3529
|
createAnalytics,
|
|
4156
3530
|
createAuthError,
|
|
4157
|
-
createClient,
|
|
3531
|
+
createClient2 as createClient,
|
|
4158
3532
|
createConflictError,
|
|
4159
3533
|
createCustomerAuthWebhookHandler,
|
|
4160
3534
|
createNotFoundError,
|
|
4161
3535
|
createPermissionError,
|
|
4162
3536
|
createProductSelectionCodec,
|
|
4163
3537
|
createRateLimitError,
|
|
4164
|
-
createServerClient,
|
|
4165
3538
|
createTypedWebhookHandler,
|
|
4166
|
-
customerKeys,
|
|
4167
3539
|
formatOrderName,
|
|
4168
3540
|
generateOrderNumber,
|
|
4169
3541
|
getAvailableOptionValues,
|
|
@@ -4172,7 +3544,7 @@ export {
|
|
|
4172
3544
|
getImagePlaceholderStyle,
|
|
4173
3545
|
getImageSrcSet,
|
|
4174
3546
|
getImageUrl,
|
|
4175
|
-
|
|
3547
|
+
getProductSelectionImages,
|
|
4176
3548
|
getSelectedValueByOptionId,
|
|
4177
3549
|
getVideoGif,
|
|
4178
3550
|
getVideoMp4Url,
|
|
@@ -4197,10 +3569,11 @@ export {
|
|
|
4197
3569
|
isValidWebhookEvent,
|
|
4198
3570
|
isValidationError,
|
|
4199
3571
|
normalizeProductSelection,
|
|
3572
|
+
normalizeProductSelectionFromMatrix,
|
|
4200
3573
|
normalizeSelectedValueIds,
|
|
4201
3574
|
parseProductSelection,
|
|
4202
|
-
productKeys,
|
|
4203
3575
|
resolveProductSelection,
|
|
3576
|
+
resolveProductSelectionFromMatrix,
|
|
4204
3577
|
resolveRelation,
|
|
4205
3578
|
resolveVariantForSelection,
|
|
4206
3579
|
stringifyProductSelection
|