@drawbridge/drawbridge-utils 0.0.176 → 0.0.178

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/admin-B9ZaLvan.d.cts +697 -0
  2. package/dist/admin-C3HtEM6h.d.ts +697 -0
  3. package/dist/billing-Bc4yo9XG.d.cts +175 -0
  4. package/dist/billing-mNsKflmQ.d.ts +175 -0
  5. package/dist/billing.d.cts +1 -1
  6. package/dist/billing.d.ts +1 -1
  7. package/dist/connections/index.cjs +3391 -271
  8. package/dist/connections/index.d.cts +13 -4
  9. package/dist/connections/index.d.ts +13 -4
  10. package/dist/connections/index.js +3388 -270
  11. package/dist/features.cjs +3080 -241
  12. package/dist/features.d.cts +13 -4
  13. package/dist/features.d.ts +13 -4
  14. package/dist/features.js +3120 -275
  15. package/dist/http.cjs +10 -1
  16. package/dist/http.d.cts +10 -1
  17. package/dist/http.d.ts +10 -1
  18. package/dist/http.js +10 -1
  19. package/dist/{index-qY18QITf.d.cts → index-C2rxasGZ.d.cts} +2670 -354
  20. package/dist/{index-B8JhYfvU.d.ts → index-DOYCXtd7.d.ts} +2670 -354
  21. package/dist/oauth/index.d.cts +1 -1
  22. package/dist/oauth/index.d.ts +1 -1
  23. package/dist/oauth-BJDh0sdM.d.cts +527 -0
  24. package/dist/oauth-DveZMLHx.d.ts +527 -0
  25. package/dist/partner-BOZltuh2.d.ts +94 -0
  26. package/dist/partner-ed2OfW1J.d.cts +94 -0
  27. package/dist/plans.cjs +3079 -240
  28. package/dist/plans.d.cts +13 -4
  29. package/dist/plans.d.ts +13 -4
  30. package/dist/plans.js +3120 -275
  31. package/dist/pricing.cjs +3112 -273
  32. package/dist/pricing.d.cts +13 -4
  33. package/dist/pricing.d.ts +13 -4
  34. package/dist/pricing.js +3117 -272
  35. package/dist/providers.cjs +3082 -262
  36. package/dist/providers.d.cts +12 -3
  37. package/dist/providers.d.ts +12 -3
  38. package/dist/providers.js +3086 -260
  39. package/dist/sendgrid.cjs +10 -1
  40. package/dist/sendgrid.js +10 -1
  41. package/dist/shopify/admin.cjs +562 -0
  42. package/dist/shopify/admin.d.cts +3 -0
  43. package/dist/shopify/admin.d.ts +3 -0
  44. package/dist/shopify/admin.js +528 -0
  45. package/dist/shopify/billing.cjs +166 -0
  46. package/dist/shopify/billing.d.cts +3 -0
  47. package/dist/shopify/billing.d.ts +3 -0
  48. package/dist/shopify/billing.js +140 -0
  49. package/dist/shopify/constants.cjs +63 -0
  50. package/dist/shopify/constants.d.cts +58 -0
  51. package/dist/shopify/constants.d.ts +58 -0
  52. package/dist/shopify/constants.js +32 -0
  53. package/dist/shopify/oauth.cjs +509 -0
  54. package/dist/shopify/oauth.d.cts +7 -0
  55. package/dist/shopify/oauth.d.ts +7 -0
  56. package/dist/shopify/oauth.js +466 -0
  57. package/dist/shopify/partner.cjs +156 -0
  58. package/dist/shopify/partner.d.cts +3 -0
  59. package/dist/shopify/partner.d.ts +3 -0
  60. package/dist/shopify/partner.js +130 -0
  61. package/dist/shopify/storefront.cjs +611 -0
  62. package/dist/shopify/storefront.d.cts +3 -0
  63. package/dist/shopify/storefront.d.ts +3 -0
  64. package/dist/shopify/storefront.js +576 -0
  65. package/dist/storefront-C8FKOGeD.d.cts +659 -0
  66. package/dist/storefront-DJFGLqPl.d.ts +659 -0
  67. package/dist/twilio.cjs +10 -1
  68. package/dist/twilio.js +10 -1
  69. package/package.json +98 -68
@@ -0,0 +1,528 @@
1
+ // lib/http.js
2
+ var DEFAULT_TIMEOUT_MS = 15e3;
3
+ var request = async ({
4
+ body,
5
+ // THE TRANSPORT, injectable and defaulted to the real one.
6
+ //
7
+ // Every other vendor client in this package takes a `fetcher` — it is how
8
+ // klaviyo, mailchimp and attentive are tested without a network, and it is a
9
+ // declared HOOK_OPTION. The Shopify client had no seam at all, so the only way
10
+ // to test a hook that used it was to inject the whole SDK namespace; that
11
+ // injection existed to work around an import cycle, and when the cycle went
12
+ // the tests lost their only hold. This is the seam they should have had.
13
+ fetcher = fetch,
14
+ headers = {},
15
+ method = "GET",
16
+ query,
17
+ timeout = DEFAULT_TIMEOUT_MS,
18
+ type = "json",
19
+ url
20
+ }) => {
21
+ const fullUrl = new URL(url);
22
+ if (query) {
23
+ Object.entries(query).forEach(([k, v]) => fullUrl.searchParams.set(k, v));
24
+ }
25
+ ;
26
+ const isForm = type === "form";
27
+ const response = await fetcher(fullUrl.toString(), {
28
+ method,
29
+ headers: {
30
+ "Content-Type": isForm ? "application/x-www-form-urlencoded" : "application/json",
31
+ ...headers
32
+ },
33
+ signal: AbortSignal.timeout(timeout),
34
+ ...body !== void 0 && {
35
+ body: isForm ? new URLSearchParams(body).toString() : JSON.stringify(body)
36
+ }
37
+ });
38
+ if (!response.ok) {
39
+ const text2 = await response.text().catch(() => "");
40
+ const error = new Error(text2 || response.statusText);
41
+ error.status = response.status;
42
+ throw error;
43
+ }
44
+ ;
45
+ const text = await response.text();
46
+ try {
47
+ return text ? JSON.parse(text) : null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ };
52
+
53
+ // lib/shopify/constants.js
54
+ var SHOPIFY_ADMIN_API_VERSION = "2026-04";
55
+ var REFRESH_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1e3;
56
+ var ACCESS_TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
57
+ var PARTNER_API_VERSION = process.env.SHOPIFY_PARTNER_API_VERSION || "2026-07";
58
+
59
+ // lib/shopify/admin.js
60
+ var adminUrl = (domain) => `https://${domain}/admin/api/${SHOPIFY_ADMIN_API_VERSION}`;
61
+ var adminFetch = async ({ adminAccessToken, domain, fetcher, query, variables }) => {
62
+ var _a;
63
+ const { data, errors } = await request({
64
+ fetcher,
65
+ method: "POST",
66
+ url: adminUrl(domain) + "/graphql.json",
67
+ headers: {
68
+ "X-Shopify-Access-Token": adminAccessToken
69
+ },
70
+ body: {
71
+ query,
72
+ ...variables && { variables }
73
+ }
74
+ });
75
+ if (errors) {
76
+ throw new Error("Shopify GraphQL error: " + ((_a = errors[0]) == null ? void 0 : _a.message));
77
+ }
78
+ ;
79
+ return data;
80
+ };
81
+ var DISCOUNT_FIELDS = `
82
+ title
83
+ status
84
+ startsAt
85
+ endsAt
86
+ usageLimit
87
+ asyncUsageCount
88
+ appliesOncePerCustomer
89
+ context { __typename }
90
+ `;
91
+ var discountsQuery = (search, cursor, limit) => `{
92
+ codeDiscountNodes(first: ${limit}${search ? `, query: "title:*${search}*"` : ""}${cursor ? `, after: "${cursor}"` : ""}) {
93
+ edges {
94
+ node {
95
+ id
96
+ codeDiscount {
97
+ ... on DiscountCodeBasic { ${DISCOUNT_FIELDS} }
98
+ ... on DiscountCodeBxgy { ${DISCOUNT_FIELDS} }
99
+ ... on DiscountCodeFreeShipping { ${DISCOUNT_FIELDS} }
100
+ ... on DiscountCodeApp { ${DISCOUNT_FIELDS} }
101
+ }
102
+ }
103
+ }
104
+ pageInfo {
105
+ endCursor
106
+ hasNextPage
107
+ hasPreviousPage
108
+ startCursor
109
+ }
110
+ }
111
+ }`;
112
+ var getDiscounts = async ({ fetcher, adminAccessToken, domain, search, cursor, limit }) => {
113
+ const data = await adminFetch({
114
+ fetcher,
115
+ adminAccessToken,
116
+ domain,
117
+ query: discountsQuery(search, cursor, limit)
118
+ });
119
+ return data == null ? void 0 : data.codeDiscountNodes;
120
+ };
121
+ var discountGid = (id) => String(id).startsWith("gid://") ? String(id) : "gid://shopify/DiscountCodeNode/" + id;
122
+ var createDiscountCode = async ({ fetcher, adminAccessToken, domain, discountId, code }) => {
123
+ var _a, _b, _c, _d, _e, _f, _g, _h;
124
+ const added = await adminFetch({
125
+ fetcher,
126
+ adminAccessToken,
127
+ domain,
128
+ query: `
129
+ mutation DiscountRedeemCodeBulkAdd($discountId: ID!, $codes: [DiscountRedeemCodeInput!]!) {
130
+ discountRedeemCodeBulkAdd(discountId: $discountId, codes: $codes) {
131
+ bulkCreation {
132
+ id
133
+ }
134
+ userErrors {
135
+ field
136
+ message
137
+ }
138
+ }
139
+ }
140
+ `,
141
+ variables: {
142
+ codes: [{ code }],
143
+ discountId: discountGid(discountId)
144
+ }
145
+ });
146
+ const addErrors = (_a = added == null ? void 0 : added.discountRedeemCodeBulkAdd) == null ? void 0 : _a.userErrors;
147
+ if (addErrors == null ? void 0 : addErrors.length) {
148
+ throw new Error("Shopify discount code create failed: " + addErrors[0].message);
149
+ }
150
+ ;
151
+ const bulkCreationId = (_c = (_b = added == null ? void 0 : added.discountRedeemCodeBulkAdd) == null ? void 0 : _b.bulkCreation) == null ? void 0 : _c.id;
152
+ if (!bulkCreationId) {
153
+ throw new Error("Shopify discount code create failed: no bulk creation id returned");
154
+ }
155
+ ;
156
+ for (let attempt = 0; attempt < 15; attempt++) {
157
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
158
+ const polled = await adminFetch({
159
+ fetcher,
160
+ adminAccessToken,
161
+ domain,
162
+ query: `
163
+ query DiscountRedeemCodeBulkCreationPoll($id: ID!) {
164
+ discountRedeemCodeBulkCreation(id: $id) {
165
+ done
166
+ codes(first: 1) {
167
+ nodes {
168
+ code
169
+ discountRedeemCode {
170
+ id
171
+ }
172
+ errors {
173
+ message
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ `,
180
+ variables: {
181
+ id: bulkCreationId
182
+ }
183
+ });
184
+ const creation = polled == null ? void 0 : polled.discountRedeemCodeBulkCreation;
185
+ if (!(creation == null ? void 0 : creation.done)) continue;
186
+ const node = (_e = (_d = creation.codes) == null ? void 0 : _d.nodes) == null ? void 0 : _e[0];
187
+ if (!((_f = node == null ? void 0 : node.discountRedeemCode) == null ? void 0 : _f.id)) {
188
+ throw new Error("Shopify discount code create failed: " + (((_h = (_g = node == null ? void 0 : node.errors) == null ? void 0 : _g[0]) == null ? void 0 : _h.message) || "code was not created"));
189
+ }
190
+ ;
191
+ return {
192
+ code: node.code,
193
+ // Numeric tail keeps the stored shopifyDiscountId in the shape the
194
+ // legacy REST endpoint returned — it feeds a merchant-visible
195
+ // workflow template variable, so no gid:// leakage.
196
+ id: node.discountRedeemCode.id.split("/").pop()
197
+ };
198
+ }
199
+ ;
200
+ throw new Error("Shopify discount code create timed out waiting for bulk creation " + bulkCreationId);
201
+ };
202
+ var getOrCreateCustomer = async ({ fetcher, adminAccessToken, domain, email, firstName, lastName, source }) => {
203
+ var _a, _b, _c, _d, _e, _f, _g, _h;
204
+ const lookupQuery = `
205
+ query customerByEmail( $query : String! ) {
206
+ customers( first : 1, query : $query ) {
207
+ edges {
208
+ node {
209
+ id
210
+ }
211
+ }
212
+ }
213
+ }
214
+ `;
215
+ const data = await adminFetch({
216
+ fetcher,
217
+ adminAccessToken,
218
+ domain,
219
+ query: lookupQuery,
220
+ variables: { query: "email:" + email }
221
+ });
222
+ const existing = (_c = (_b = (_a = data == null ? void 0 : data.customers) == null ? void 0 : _a.edges) == null ? void 0 : _b[0]) == null ? void 0 : _c.node;
223
+ if (existing) return existing;
224
+ const input = { email };
225
+ if (firstName) input.firstName = firstName;
226
+ if (lastName) input.lastName = lastName;
227
+ if (source) {
228
+ input.metafields = [{
229
+ namespace: "drwbrdg",
230
+ key: "source",
231
+ type: "single_line_text_field",
232
+ value: source
233
+ }];
234
+ }
235
+ ;
236
+ const result = await adminFetch({
237
+ fetcher,
238
+ adminAccessToken,
239
+ domain,
240
+ query: `
241
+ mutation customerCreate( $input : CustomerInput! ) {
242
+ customerCreate( input : $input ) {
243
+ customer {
244
+ id
245
+ }
246
+ userErrors {
247
+ field
248
+ message
249
+ }
250
+ }
251
+ }
252
+ `,
253
+ variables: { input }
254
+ });
255
+ const userErrors = (_d = result == null ? void 0 : result.customerCreate) == null ? void 0 : _d.userErrors;
256
+ if (userErrors == null ? void 0 : userErrors.length) {
257
+ const emailTaken = userErrors.some((e) => {
258
+ var _a2;
259
+ return (_a2 = e.message) == null ? void 0 : _a2.includes("Email has already been taken");
260
+ });
261
+ if (emailTaken) {
262
+ const retry = await adminFetch({
263
+ fetcher,
264
+ adminAccessToken,
265
+ domain,
266
+ query: lookupQuery,
267
+ variables: { query: "email:" + email }
268
+ });
269
+ const found = (_g = (_f = (_e = retry == null ? void 0 : retry.customers) == null ? void 0 : _e.edges) == null ? void 0 : _f[0]) == null ? void 0 : _g.node;
270
+ if (found) return found;
271
+ }
272
+ ;
273
+ throw new Error("Shopify customer create failed: " + userErrors[0].message);
274
+ }
275
+ ;
276
+ return (_h = result == null ? void 0 : result.customerCreate) == null ? void 0 : _h.customer;
277
+ };
278
+ var getActiveAppSubscriptions = async ({ fetcher, adminAccessToken, domain }) => {
279
+ var _a, _b;
280
+ const data = await adminFetch({
281
+ fetcher,
282
+ adminAccessToken,
283
+ domain,
284
+ query: `{
285
+ currentAppInstallation {
286
+ activeSubscriptions {
287
+ id
288
+ name
289
+ status
290
+ test
291
+ lineItems {
292
+ id
293
+ plan {
294
+ pricingDetails {
295
+ __typename
296
+ }
297
+ }
298
+ }
299
+ }
300
+ }
301
+ shop {
302
+ myshopifyDomain
303
+ }
304
+ }`
305
+ });
306
+ return {
307
+ shopDomain: ((_a = data == null ? void 0 : data.shop) == null ? void 0 : _a.myshopifyDomain) || null,
308
+ subscriptions: (((_b = data == null ? void 0 : data.currentAppInstallation) == null ? void 0 : _b.activeSubscriptions) || []).map((subscription) => {
309
+ var _a2;
310
+ return {
311
+ ...subscription,
312
+ metered: ((subscription == null ? void 0 : subscription.lineItems) || []).some(
313
+ (item) => {
314
+ var _a3, _b2;
315
+ return ((_b2 = (_a3 = item == null ? void 0 : item.plan) == null ? void 0 : _a3.pricingDetails) == null ? void 0 : _b2.__typename) === "AppUsagePricing";
316
+ }
317
+ ),
318
+ // The usage line's own id — the address appUsageRecordCreate charges
319
+ // against. Null when the approval carries no usage component.
320
+ usageLineItemId: ((_a2 = ((subscription == null ? void 0 : subscription.lineItems) || []).find(
321
+ (item) => {
322
+ var _a3, _b2;
323
+ return ((_b2 = (_a3 = item == null ? void 0 : item.plan) == null ? void 0 : _a3.pricingDetails) == null ? void 0 : _b2.__typename) === "AppUsagePricing";
324
+ }
325
+ )) == null ? void 0 : _a2.id) || null
326
+ };
327
+ })
328
+ };
329
+ };
330
+ var createUsageSubscription = async ({ fetcher, adminAccessToken, cappedAmount, currencyCode = "USD", domain, name, recurringPrice = 0, returnUrl, terms, test = false }) => {
331
+ if (!name || !returnUrl || !terms || !(Number(cappedAmount) > 0)) {
332
+ throw new Error("createUsageSubscription requires name, returnUrl, terms, and a positive cappedAmount");
333
+ }
334
+ ;
335
+ const data = await adminFetch({
336
+ fetcher,
337
+ adminAccessToken,
338
+ domain,
339
+ query: `mutation createUsageSubscription( $lineItems : [AppSubscriptionLineItemInput!]!, $name : String!, $returnUrl : URL!, $test : Boolean ) {
340
+ appSubscriptionCreate( lineItems : $lineItems, name : $name, returnUrl : $returnUrl, test : $test ) {
341
+ appSubscription { id name status test lineItems { id plan { pricingDetails { __typename } } } }
342
+ confirmationUrl
343
+ userErrors { field message }
344
+ }
345
+ }`,
346
+ variables: {
347
+ lineItems: [
348
+ {
349
+ plan: {
350
+ appRecurringPricingDetails: {
351
+ interval: "EVERY_30_DAYS",
352
+ price: { amount: Number(recurringPrice), currencyCode }
353
+ }
354
+ }
355
+ },
356
+ {
357
+ plan: {
358
+ appUsagePricingDetails: {
359
+ cappedAmount: { amount: Number(cappedAmount), currencyCode },
360
+ terms
361
+ }
362
+ }
363
+ }
364
+ ],
365
+ name,
366
+ returnUrl,
367
+ test
368
+ }
369
+ });
370
+ const payload = data == null ? void 0 : data.appSubscriptionCreate;
371
+ const userErrors = (payload == null ? void 0 : payload.userErrors) || [];
372
+ if (userErrors.length) {
373
+ throw new Error("Shopify subscription create failed: " + userErrors.map((entry) => entry.message).join("; "));
374
+ }
375
+ ;
376
+ if (!(payload == null ? void 0 : payload.confirmationUrl)) {
377
+ throw new Error("Shopify subscription create failed: no confirmation url returned");
378
+ }
379
+ ;
380
+ return {
381
+ confirmationUrl: payload.confirmationUrl,
382
+ subscription: payload.appSubscription
383
+ };
384
+ };
385
+ var createUsageRecord = async ({ fetcher, adminAccessToken, amount, currencyCode = "USD", description, domain, idempotencyKey, subscriptionLineItemId }) => {
386
+ var _a;
387
+ if (!subscriptionLineItemId || !description || !(Number(amount) > 0)) {
388
+ throw new Error("createUsageRecord requires subscriptionLineItemId, description, and a positive amount");
389
+ }
390
+ ;
391
+ const data = await adminFetch({
392
+ fetcher,
393
+ adminAccessToken,
394
+ domain,
395
+ query: `mutation createUsageRecord( $description : String!, $idempotencyKey : String, $price : MoneyInput!, $subscriptionLineItemId : ID! ) {
396
+ appUsageRecordCreate( description : $description, idempotencyKey : $idempotencyKey, price : $price, subscriptionLineItemId : $subscriptionLineItemId ) {
397
+ appUsageRecord { id }
398
+ userErrors { field message }
399
+ }
400
+ }`,
401
+ variables: {
402
+ description,
403
+ idempotencyKey: idempotencyKey ? String(idempotencyKey) : null,
404
+ price: { amount: Number(amount), currencyCode },
405
+ subscriptionLineItemId
406
+ }
407
+ });
408
+ const payload = data == null ? void 0 : data.appUsageRecordCreate;
409
+ const userErrors = (payload == null ? void 0 : payload.userErrors) || [];
410
+ if (userErrors.length || !((_a = payload == null ? void 0 : payload.appUsageRecord) == null ? void 0 : _a.id)) {
411
+ throw new Error("Shopify usage record failed: " + (userErrors.map((entry) => entry.message).join("; ") || "no record returned"));
412
+ }
413
+ ;
414
+ return { id: payload.appUsageRecord.id };
415
+ };
416
+ var getProductInventory = async ({ fetcher, adminAccessToken, domain, productId }) => {
417
+ var _a, _b;
418
+ const data = await adminFetch({
419
+ fetcher,
420
+ adminAccessToken,
421
+ domain,
422
+ query: `{
423
+ product( id : "${productId}" ) {
424
+ variants( first : 100 ) {
425
+ edges {
426
+ node {
427
+ id
428
+ inventoryQuantity
429
+ }
430
+ }
431
+ }
432
+ }
433
+ }`
434
+ });
435
+ const edges = ((_b = (_a = data == null ? void 0 : data.product) == null ? void 0 : _a.variants) == null ? void 0 : _b.edges) || [];
436
+ return edges.reduce((accumulator, edge) => {
437
+ var _a2, _b2;
438
+ const id = (_a2 = edge == null ? void 0 : edge.node) == null ? void 0 : _a2.id;
439
+ const quantity = (_b2 = edge == null ? void 0 : edge.node) == null ? void 0 : _b2.inventoryQuantity;
440
+ if (id) {
441
+ accumulator[id] = Number.isFinite(quantity) ? quantity : 0;
442
+ }
443
+ ;
444
+ return accumulator;
445
+ }, {});
446
+ };
447
+ var getProductUpdatedAt = async ({ adminAccessToken, domain, fetcher, productId }) => {
448
+ var _a;
449
+ const data = await adminFetch({
450
+ fetcher,
451
+ adminAccessToken,
452
+ domain,
453
+ query: `
454
+ query ProductUpdatedAt($id: ID!) {
455
+ product(id: $id) {
456
+ updatedAt
457
+ }
458
+ }
459
+ `,
460
+ variables: { id: productId }
461
+ });
462
+ return ((_a = data == null ? void 0 : data.product) == null ? void 0 : _a.updatedAt) || null;
463
+ };
464
+ var NOT_ON_CHANNEL = "not available to the channel";
465
+ var OUTDATED_FEEDBACK = "later version";
466
+ var sendProductResourceFeedback = async ({ fetcher, adminAccessToken, domain, productId, state, messages = [] }) => {
467
+ var _a, _b, _c;
468
+ const productUpdatedAt = await getProductUpdatedAt({ adminAccessToken, domain, fetcher, productId });
469
+ if (!productUpdatedAt) return null;
470
+ const now = (/* @__PURE__ */ new Date()).toISOString();
471
+ const submit = async (version) => {
472
+ const data = await adminFetch({
473
+ fetcher,
474
+ adminAccessToken,
475
+ domain,
476
+ query: `
477
+ mutation ProductFeedback($feedbackInput: [ProductResourceFeedbackInput!]!) {
478
+ bulkProductResourceFeedbackCreate(feedbackInput: $feedbackInput) {
479
+ feedback {
480
+ productId
481
+ state
482
+ }
483
+ userErrors {
484
+ field
485
+ message
486
+ }
487
+ }
488
+ }
489
+ `,
490
+ variables: {
491
+ feedbackInput: [
492
+ {
493
+ productId,
494
+ state,
495
+ feedbackGeneratedAt: now,
496
+ productUpdatedAt: version,
497
+ messages
498
+ }
499
+ ]
500
+ }
501
+ });
502
+ return data == null ? void 0 : data.bulkProductResourceFeedbackCreate;
503
+ };
504
+ let result = await submit(productUpdatedAt);
505
+ if ((((_b = (_a = result == null ? void 0 : result.userErrors) == null ? void 0 : _a[0]) == null ? void 0 : _b.message) || "").toLowerCase().includes(OUTDATED_FEEDBACK)) {
506
+ result = await submit(now);
507
+ }
508
+ ;
509
+ const feedbackErrors = result == null ? void 0 : result.userErrors;
510
+ if (feedbackErrors == null ? void 0 : feedbackErrors.length) {
511
+ const message = feedbackErrors[0].message || "";
512
+ if (message.toLowerCase().includes(NOT_ON_CHANNEL)) return null;
513
+ throw new Error("Shopify product feedback failed: " + message);
514
+ }
515
+ ;
516
+ return ((_c = result == null ? void 0 : result.feedback) == null ? void 0 : _c[0]) || null;
517
+ };
518
+ export {
519
+ adminFetch,
520
+ createDiscountCode,
521
+ createUsageRecord,
522
+ createUsageSubscription,
523
+ getActiveAppSubscriptions,
524
+ getDiscounts,
525
+ getOrCreateCustomer,
526
+ getProductInventory,
527
+ sendProductResourceFeedback
528
+ };
@@ -0,0 +1,166 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // lib/shopify/billing.js
20
+ var billing_exports = {};
21
+ __export(billing_exports, {
22
+ sendAppEvent: () => sendAppEvent
23
+ });
24
+ module.exports = __toCommonJS(billing_exports);
25
+
26
+ // lib/http.js
27
+ var DEFAULT_TIMEOUT_MS = 15e3;
28
+ var request = async ({
29
+ body,
30
+ // THE TRANSPORT, injectable and defaulted to the real one.
31
+ //
32
+ // Every other vendor client in this package takes a `fetcher` — it is how
33
+ // klaviyo, mailchimp and attentive are tested without a network, and it is a
34
+ // declared HOOK_OPTION. The Shopify client had no seam at all, so the only way
35
+ // to test a hook that used it was to inject the whole SDK namespace; that
36
+ // injection existed to work around an import cycle, and when the cycle went
37
+ // the tests lost their only hold. This is the seam they should have had.
38
+ fetcher = fetch,
39
+ headers = {},
40
+ method = "GET",
41
+ query,
42
+ timeout = DEFAULT_TIMEOUT_MS,
43
+ type = "json",
44
+ url
45
+ }) => {
46
+ const fullUrl = new URL(url);
47
+ if (query) {
48
+ Object.entries(query).forEach(([k, v]) => fullUrl.searchParams.set(k, v));
49
+ }
50
+ ;
51
+ const isForm = type === "form";
52
+ const response = await fetcher(fullUrl.toString(), {
53
+ method,
54
+ headers: {
55
+ "Content-Type": isForm ? "application/x-www-form-urlencoded" : "application/json",
56
+ ...headers
57
+ },
58
+ signal: AbortSignal.timeout(timeout),
59
+ ...body !== void 0 && {
60
+ body: isForm ? new URLSearchParams(body).toString() : JSON.stringify(body)
61
+ }
62
+ });
63
+ if (!response.ok) {
64
+ const text2 = await response.text().catch(() => "");
65
+ const error = new Error(text2 || response.statusText);
66
+ error.status = response.status;
67
+ throw error;
68
+ }
69
+ ;
70
+ const text = await response.text();
71
+ try {
72
+ return text ? JSON.parse(text) : null;
73
+ } catch {
74
+ return null;
75
+ }
76
+ };
77
+
78
+ // lib/shopify/constants.js
79
+ var SHOPIFY_APP_API_VERSION = "unstable";
80
+ var REFRESH_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1e3;
81
+ var ACCESS_TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
82
+ var PARTNER_API_VERSION = process.env.SHOPIFY_PARTNER_API_VERSION || "2026-07";
83
+
84
+ // lib/shopify/billing.js
85
+ var APP_API = "https://api.shopify.com";
86
+ var APP_API_VERSION = process.env.SHOPIFY_APP_API_VERSION || SHOPIFY_APP_API_VERSION;
87
+ var cachedTokens = {};
88
+ var fetchAppToken = async ({ clientId, clientSecret, fetcher }) => {
89
+ const data = await request({
90
+ fetcher,
91
+ method: "POST",
92
+ url: APP_API + "/auth/access_token",
93
+ body: {
94
+ client_id: clientId,
95
+ client_secret: clientSecret,
96
+ grant_type: "client_credentials"
97
+ }
98
+ });
99
+ if (!(data == null ? void 0 : data.access_token)) {
100
+ throw new Error("Shopify app token request failed: no access token returned");
101
+ }
102
+ ;
103
+ const ttl = data.expires_in ? data.expires_in * 1e3 : 60 * 60 * 1e3;
104
+ cachedTokens[clientId] = {
105
+ expiresAt: Date.now() + ttl - 60 * 1e3,
106
+ token: data.access_token
107
+ };
108
+ return data.access_token;
109
+ };
110
+ var getAppToken = async ({ clientId, clientSecret, fetcher }) => {
111
+ const cached = cachedTokens[clientId];
112
+ if (cached && cached.expiresAt > Date.now()) {
113
+ return cached.token;
114
+ }
115
+ ;
116
+ return fetchAppToken({ clientId, clientSecret, fetcher });
117
+ };
118
+ var toShopGid = (shopId) => String(shopId).startsWith("gid://") ? String(shopId) : "gid://shopify/Shop/" + shopId;
119
+ var sendAppEvent = async ({ fetcher, clientId, clientSecret, eventHandle, idempotencyKey, reference, revision, shopId, timestamp, value }) => {
120
+ if (!shopId || !eventHandle || !(Number(value) > 0)) {
121
+ throw new Error("sendAppEvent requires shopId, eventHandle, and a positive value");
122
+ }
123
+ ;
124
+ if (!clientId || !clientSecret) {
125
+ throw new Error("sendAppEvent requires clientId and clientSecret");
126
+ }
127
+ ;
128
+ const suffix = revision ? ".r" + revision : "";
129
+ const key = idempotencyKey && String(idempotencyKey).slice(0, Math.max(0, 64 - suffix.length)) + suffix;
130
+ const body = {
131
+ attributes: {
132
+ value: Number(value),
133
+ ...reference && {
134
+ reference: String(reference).slice(0, 128)
135
+ }
136
+ },
137
+ event_handle: eventHandle,
138
+ shop_id: toShopGid(shopId),
139
+ timestamp: timestamp || (/* @__PURE__ */ new Date()).toISOString(),
140
+ ...key && {
141
+ idempotency_key: key
142
+ }
143
+ };
144
+ const send = async (token) => request({
145
+ method: "POST",
146
+ url: APP_API + "/app/" + APP_API_VERSION + "/events",
147
+ headers: {
148
+ "Authorization": "Bearer " + token
149
+ },
150
+ body
151
+ });
152
+ try {
153
+ return await send(await getAppToken({ clientId, clientSecret, fetcher }));
154
+ } catch (error) {
155
+ if ((error == null ? void 0 : error.status) === 401) {
156
+ delete cachedTokens[clientId];
157
+ return send(await fetchAppToken({ clientId, clientSecret, fetcher }));
158
+ }
159
+ ;
160
+ throw error;
161
+ }
162
+ };
163
+ // Annotate the CommonJS export names for ESM import in node:
164
+ 0 && (module.exports = {
165
+ sendAppEvent
166
+ });
@@ -0,0 +1,3 @@
1
+ import '../http.cjs';
2
+ import './constants.cjs';
3
+ export { s as sendAppEvent } from '../billing-Bc4yo9XG.cjs';
@@ -0,0 +1,3 @@
1
+ import '../http.js';
2
+ import './constants.js';
3
+ export { s as sendAppEvent } from '../billing-mNsKflmQ.js';