@zappie/sdk 0.3.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @zappie/sdk
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a5bd08f: `@zappie/sdk/account` reads the signed-in shopper's account through the same endpoint and session as `createCustomer`. `createAccount()` returns `wallets()`, `cards()`, `removeCard(id)`, `payments({ limit, cursor })` and `payment(id)`. A call the shopper's sign-in doesn't cover rejects with `ScopeRequiredError`, which names the scope, and a card or payment that isn't theirs rejects with `NotFoundError`. Both are a `CustomerError`. `accountsUrl(path, environment, { returnTo })` builds a link to a page on the Zappie accounts portal, `/account`, `/account/profile`, `/account/payouts`, `/account/withdraw` or `/account/activity`, with the page to come back to. It refuses a path the way `checkoutUrl` does. `@zappie/sdk/testing` adds `mockAccountFetch()` and `accountFixtures`, which answer the account endpoints as the API does, including a removed card and a missing scope.
8
+
9
+ `@zappie/foundations/account` adds headless account parts that load only on account pages: `Wallets`, `Cards`, `RemoveCard` (a confirmation before a card is removed), `Payments`, `Receipt` and `ScopeMissing`, which offers to sign in again. `SignInOptions.scopes` accepts `wallet`, `cards` and `payments:read`.
10
+
11
+ `no-offsite-navigation` trusts the staging accounts portal and `accountsUrl` from `@zappie/sdk/account`, and `no-direct-api` points a written-out accounts address to `accountsUrl`.
12
+
13
+ ## 0.4.0
14
+
15
+ ### Minor Changes
16
+
17
+ - b884697: `zappieSupportUrl(context)` in `@zappie/foundations` builds a WhatsApp link to Zappie support for a shopper with a problem such as a payment. Its message starts with "Hi Zappie support," and names the store, the branch when the store has more than one, the order and the page, as the classic storefront does. `ZAPPIE_SUPPORT_WHATSAPP` in `@zappie/sdk/services` is the number it opens.
18
+
3
19
  ## 0.3.0
4
20
 
5
21
  ### Minor Changes
package/README.md CHANGED
@@ -50,21 +50,22 @@ Prices always come from Zappie. The cart shows a local estimate while it waits,
50
50
 
51
51
  ## Entry points
52
52
 
53
- | Import | For |
54
- | -------------------------------------------- | ---------------------------------------------------------------------------------------- |
55
- | `@zappie/sdk` | The client, the cart, money formatting and errors |
56
- | `@zappie/sdk/react` | `ZappieProvider` and hooks such as `useStore`, `useMenu` and `useCart` |
57
- | `@zappie/sdk/checkout` | Opening Zappie's hosted checkout and checking it completed |
58
- | `@zappie/sdk/checkout-defaults` | The phone, address, region and time a signed-in checkout starts from |
59
- | `@zappie/sdk/customer` and `/customer/react` | Signed-in customers, their orders and saved addresses |
60
- | `@zappie/sdk/integrations` | Public integration IDs and browser keys |
61
- | `@zappie/sdk/places` | Address search and delivery-zone checks |
62
- | `@zappie/sdk/pricing` | An item's starting price and whether to show it as "From" |
63
- | `@zappie/sdk/server` | Server-side reads, storefront URLs, `verifyWebhook()`, menu snapshots and sign-in crypto |
64
- | `@zappie/sdk/template` and `/template/react` | Template settings and responsive images for template authors |
65
- | `@zappie/sdk/testing` | A mock client for your tests |
66
- | `@zappie/sdk/url` | Storefront URL parsing and canonical redirects |
67
- | `@zappie/sdk/redirect` | Same-origin validation for untrusted redirect targets |
53
+ | Import | For |
54
+ | -------------------------------------------- | ------------------------------------------------------------------------------------------ |
55
+ | `@zappie/sdk` | The client, the cart, money formatting and errors |
56
+ | `@zappie/sdk/react` | `ZappieProvider` and hooks such as `useStore`, `useMenu` and `useCart` |
57
+ | `@zappie/sdk/checkout` | Opening Zappie's hosted checkout and checking it completed |
58
+ | `@zappie/sdk/checkout-defaults` | The phone, address, region and time a signed-in checkout starts from |
59
+ | `@zappie/sdk/customer` and `/customer/react` | Signed-in customers, their orders and saved addresses |
60
+ | `@zappie/sdk/account` | The shopper's wallet, saved cards, payments and receipts, and links to the accounts portal |
61
+ | `@zappie/sdk/integrations` | Public integration IDs and browser keys |
62
+ | `@zappie/sdk/places` | Address search and delivery-zone checks |
63
+ | `@zappie/sdk/pricing` | An item's starting price and whether to show it as "From" |
64
+ | `@zappie/sdk/server` | Server-side reads, storefront URLs, `verifyWebhook()`, menu snapshots and sign-in crypto |
65
+ | `@zappie/sdk/template` and `/template/react` | Template settings and responsive images for template authors |
66
+ | `@zappie/sdk/testing` | A mock client and mock account endpoints for your tests |
67
+ | `@zappie/sdk/url` | Storefront URL parsing and canonical redirects |
68
+ | `@zappie/sdk/redirect` | Same-origin validation for untrusted redirect targets |
68
69
 
69
70
  Customer sign-in runs through Zappie's site host, so `@zappie/sdk/customer` works on sites Zappie hosts. Sign-in for a site you host yourself isn't available yet.
70
71
 
@@ -0,0 +1,68 @@
1
+ // src/account/client.ts
2
+ import { CustomerError } from "../customer/error.js";
3
+ import { createCustomerRequest } from "../customer/request.js";
4
+ import { NotFoundError, ScopeRequiredError } from "./errors.js";
5
+ function createAccount(options = {}) {
6
+ const base = (options.customerBase ?? "/_zappie/api").replace(/\/+$/, "");
7
+ const request = createCustomerRequest(
8
+ base,
9
+ options.fetch ?? globalThis.fetch,
10
+ () => {
11
+ void options.customer?.session().catch(() => void 0);
12
+ }
13
+ );
14
+ const call = async (scope, path, method = "GET") => {
15
+ try {
16
+ return await request(path, { method });
17
+ } catch (error) {
18
+ if (error instanceof CustomerError) {
19
+ if (error.apiCode === "scope_required") {
20
+ throw new ScopeRequiredError(scope, error.message);
21
+ }
22
+ if (error.status === 404) {
23
+ throw new NotFoundError(error.message);
24
+ }
25
+ }
26
+ throw error;
27
+ }
28
+ };
29
+ return {
30
+ /** The shopper's Zappie wallet, one balance per currency, naira first. Needs the `wallet` scope. */
31
+ async wallets() {
32
+ return (await call("wallet", "/me/wallets")).wallets;
33
+ },
34
+ /** The cards the shopper saved when they paid. Needs the `cards` scope. */
35
+ async cards() {
36
+ return (await call("cards", "/me/cards")).cards;
37
+ },
38
+ /** Remove one of the shopper's saved cards. Needs the `cards` scope. */
39
+ removeCard: (id) => call("cards", `/me/cards/${encodeURIComponent(id)}`, "DELETE"),
40
+ /**
41
+ * One page of the shopper's payments at this business, newest first. Needs the `payments:read` scope.
42
+ *
43
+ * @param page - Up to 50 payments a page, 20 by default, and the next_cursor of the previous page.
44
+ */
45
+ payments(page = {}) {
46
+ const query = new URLSearchParams();
47
+ if (page.limit !== void 0) {
48
+ query.set("limit", String(page.limit));
49
+ }
50
+ if (page.cursor !== void 0) {
51
+ query.set("cursor", page.cursor);
52
+ }
53
+ return call(
54
+ "payments:read",
55
+ `/me/payments${query.size > 0 ? `?${query}` : ""}`
56
+ );
57
+ },
58
+ /** One payment with what its receipt shows. Needs the `payments:read` scope. */
59
+ payment: (id) => call(
60
+ "payments:read",
61
+ `/me/payments/${encodeURIComponent(id)}`
62
+ )
63
+ };
64
+ }
65
+ export {
66
+ createAccount
67
+ };
68
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/account/client.ts"],
4
+ "mappings": ";AAAA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,eAAe,0BAA0B;AAmB3C,SAAS,cAAc,UAAgC,CAAC,GAAG;AAChE,QAAM,QAAQ,QAAQ,gBAAgB,gBAAgB,QAAQ,QAAQ,EAAE;AACxE,QAAM,UAAU;AAAA,IACd;AAAA,IACA,QAAQ,SAAS,WAAW;AAAA,IAC5B,MAAM;AACJ,WAAK,QAAQ,UAAU,QAAQ,EAAE,MAAM,MAAM,MAAS;AAAA,IACxD;AAAA,EACF;AACA,QAAM,OAAO,OACX,OACA,MACA,SAAS,UACM;AACf,QAAI;AACF,aAAO,MAAM,QAAW,MAAM,EAAE,OAAO,CAAC;AAAA,IAC1C,SAAS,OAAO;AACd,UAAI,iBAAiB,eAAe;AAClC,YAAI,MAAM,YAAY,kBAAkB;AACtC,gBAAM,IAAI,mBAAmB,OAAO,MAAM,OAAO;AAAA,QACnD;AACA,YAAI,MAAM,WAAW,KAAK;AACxB,gBAAM,IAAI,cAAc,MAAM,OAAO;AAAA,QACvC;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO;AAAA;AAAA,IAEL,MAAM,UAA6B;AACjC,cAAQ,MAAM,KAA4B,UAAU,aAAa,GAC9D;AAAA,IACL;AAAA;AAAA,IAEA,MAAM,QAA8B;AAClC,cAAQ,MAAM,KAA6B,SAAS,WAAW,GAAG;AAAA,IACpE;AAAA;AAAA,IAEA,YAAY,CAAC,OACX,KAAK,SAAS,aAAa,mBAAmB,EAAE,CAAC,IAAI,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAM/D,SAAS,OAA4C,CAAC,GAAG;AACvD,YAAM,QAAQ,IAAI,gBAAgB;AAClC,UAAI,KAAK,UAAU,QAAW;AAC5B,cAAM,IAAI,SAAS,OAAO,KAAK,KAAK,CAAC;AAAA,MACvC;AACA,UAAI,KAAK,WAAW,QAAW;AAC7B,cAAM,IAAI,UAAU,KAAK,MAAM;AAAA,MACjC;AACA,aAAO;AAAA,QACL;AAAA,QACA,eAAe,MAAM,OAAO,IAAI,IAAI,KAAK,KAAK,EAAE;AAAA,MAClD;AAAA,IACF;AAAA;AAAA,IAEA,SAAS,CAAC,OACR;AAAA,MACE;AAAA,MACA,gBAAgB,mBAAmB,EAAE,CAAC;AAAA,IACxC;AAAA,EACJ;AACF;",
5
+ "names": []
6
+ }
@@ -0,0 +1,20 @@
1
+ // src/account/errors.ts
2
+ import { CustomerError } from "../customer/error.js";
3
+ var ScopeRequiredError = class extends CustomerError {
4
+ constructor(scope, message) {
5
+ super(403, "scope_required", message);
6
+ this.scope = scope;
7
+ this.name = "ScopeRequiredError";
8
+ }
9
+ };
10
+ var NotFoundError = class extends CustomerError {
11
+ constructor(message) {
12
+ super(404, "not_found", message);
13
+ this.name = "NotFoundError";
14
+ }
15
+ };
16
+ export {
17
+ NotFoundError,
18
+ ScopeRequiredError
19
+ };
20
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/account/errors.ts"],
4
+ "mappings": ";AAAA,SAAS,qBAAqB;AAOvB,IAAM,qBAAN,cAAiC,cAAc;AAAA,EACpD,YAEW,OACT,SACA;AACA,UAAM,KAAK,kBAAkB,OAAO;AAH3B;AAIT,SAAK,OAAO;AAAA,EACd;AACF;AAGO,IAAM,gBAAN,cAA4B,cAAc;AAAA,EAC/C,YAAY,SAAiB;AAC3B,UAAM,KAAK,aAAa,OAAO;AAC/B,SAAK,OAAO;AAAA,EACd;AACF;",
5
+ "names": []
6
+ }
@@ -0,0 +1,150 @@
1
+ import { ZappieError } from '@zappie/sdk';
2
+
3
+ /**
4
+ * A customer API amount in minor currency units, paired with its currency code.
5
+ */
6
+ type Money = {
7
+ amount_minor: number;
8
+ currency: string;
9
+ };
10
+
11
+ /** A scope the account calls need. A token without it gets `scope_required`. */
12
+ type AccountScope = "wallet" | "cards" | "payments:read";
13
+ /** One balance in the shopper's Zappie wallet, which is the same at every business. */
14
+ type Wallet = {
15
+ currency: string;
16
+ balance: Money;
17
+ };
18
+ /** A card the shopper saved when they paid. It never carries the full card number. */
19
+ type SavedCard = {
20
+ id: string;
21
+ /** The card scheme in lower case, such as `mastercard`. */
22
+ brand: string;
23
+ last4: string;
24
+ exp_month: number;
25
+ exp_year: number;
26
+ };
27
+ /** A payment the shopper made at this business. */
28
+ type CustomerPayment = {
29
+ id: string;
30
+ status: "successful" | "failed" | "refunded";
31
+ /** What the payment was for. */
32
+ amount: Money;
33
+ /** What the shopper paid, fees included. */
34
+ paid: Money;
35
+ reference: string;
36
+ branch_id: string;
37
+ /** The order the payment paid for, or null when it wasn't a delivery or pickup order. */
38
+ order_id: string | null;
39
+ created_at: string;
40
+ paid_at: string | null;
41
+ };
42
+ /** A page of payments and the cursor for the next page, or null at the end. */
43
+ type PaymentsPage = {
44
+ payments: CustomerPayment[];
45
+ next_cursor: string | null;
46
+ };
47
+ /** How the shopper paid. A card shows its brand and last four digits, a bank transfer its bank and account name. */
48
+ type PaymentMethodDetails = {
49
+ type: "card" | "card_terminal" | "bank_transfer" | "wallet" | "apple_pay" | "google_pay" | "store_credit" | "loyalty_points" | "mobile_money" | "other";
50
+ card_brand?: string;
51
+ card_last4?: string;
52
+ bank_name?: string;
53
+ account_name?: string;
54
+ };
55
+ /** A payment with what its receipt shows: the branch's name and how the shopper paid. */
56
+ type PaymentReceipt = CustomerPayment & {
57
+ branch_name: string;
58
+ method: PaymentMethodDetails;
59
+ };
60
+ /**
61
+ * Where the account calls go and how. They share the customer client's defaults, so a site that
62
+ * changed `customerBase` or `fetch` for createCustomer passes the same values here.
63
+ */
64
+ type AccountClientOptions = {
65
+ customerBase?: string;
66
+ fetch?: typeof fetch;
67
+ /** The site's customer client. A call the session no longer allows re-checks it, so the site sees the shopper signed out. */
68
+ customer?: {
69
+ session(): Promise<unknown>;
70
+ };
71
+ };
72
+
73
+ /**
74
+ * Create a client for the shopper's wallet, saved cards and payments at this business, through the
75
+ * same endpoint and session as createCustomer. Load it only on account pages.
76
+ * A call the shopper's sign-in doesn't allow rejects with ScopeRequiredError. A card or payment that
77
+ * isn't theirs rejects with NotFoundError. Any other failure is a CustomerError.
78
+ *
79
+ * @param options - Endpoint and fetch overrides, and the site's customer client.
80
+ * @returns The account calls.
81
+ */
82
+ declare function createAccount(options?: AccountClientOptions): {
83
+ /** The shopper's Zappie wallet, one balance per currency, naira first. Needs the `wallet` scope. */
84
+ wallets(): Promise<Wallet[]>;
85
+ /** The cards the shopper saved when they paid. Needs the `cards` scope. */
86
+ cards(): Promise<SavedCard[]>;
87
+ /** Remove one of the shopper's saved cards. Needs the `cards` scope. */
88
+ removeCard: (id: string) => Promise<void>;
89
+ /**
90
+ * One page of the shopper's payments at this business, newest first. Needs the `payments:read` scope.
91
+ *
92
+ * @param page - Up to 50 payments a page, 20 by default, and the next_cursor of the previous page.
93
+ */
94
+ payments(page?: {
95
+ limit?: number;
96
+ cursor?: string;
97
+ }): Promise<PaymentsPage>;
98
+ /** One payment with what its receipt shows. Needs the `payments:read` scope. */
99
+ payment: (id: string) => Promise<PaymentReceipt>;
100
+ };
101
+ /** The wallet, card and payment calls returned by createAccount. */
102
+ type AccountClient = ReturnType<typeof createAccount>;
103
+
104
+ /**
105
+ * A customer API failure with its HTTP status, API code and optional validation issues.
106
+ * Statuses 429 and 5xx are marked retryable.
107
+ */
108
+ declare class CustomerError extends ZappieError {
109
+ readonly apiCode: string;
110
+ readonly issues?: unknown;
111
+ constructor(status: number, apiCode: string, message: string, param?: string, issues?: unknown);
112
+ }
113
+
114
+ /**
115
+ * The shopper's sign-in doesn't include the scope this call needs, usually because they signed in
116
+ * before the site asked for it. Offer to sign in again. It doesn't sign the shopper out.
117
+ */
118
+ declare class ScopeRequiredError extends CustomerError {
119
+ /** The scope the call needs. */
120
+ readonly scope: AccountScope;
121
+ constructor(
122
+ /** The scope the call needs. */
123
+ scope: AccountScope, message: string);
124
+ }
125
+ /** The card or payment isn't the shopper's, or isn't at this business. */
126
+ declare class NotFoundError extends CustomerError {
127
+ constructor(message: string);
128
+ }
129
+
130
+ /** The Zappie environment a service URL points at. */
131
+ type ZappieEnvironment = "production" | "staging";
132
+
133
+ /** A page on the Zappie accounts portal. */
134
+ type AccountsPage = "/account" | "/account/profile" | "/account/payouts" | "/account/withdraw" | "/account/activity";
135
+ /**
136
+ * Build a link to a page on the Zappie accounts portal, where the shopper changes their name or
137
+ * email, adds a payout account, withdraws from their wallet or sees all their activity. The portal
138
+ * links back to `returnTo` when it is a page on a site registered with Zappie.
139
+ *
140
+ * @param path - The portal page, such as `/account/withdraw`.
141
+ * @param environment - `staging` for a test host. Defaults to `production`.
142
+ * @param options - `returnTo`, the page to come back to. A path resolves against the current page.
143
+ * @returns The absolute portal URL. A path that could leave the portal, or a `returnTo` that isn't an http or https page, throws a TypeError.
144
+ */
145
+ declare function accountsUrl(path: AccountsPage, environment?: ZappieEnvironment, options?: {
146
+ returnTo?: string | URL;
147
+ }): string;
148
+
149
+ export { NotFoundError, ScopeRequiredError, accountsUrl, createAccount };
150
+ export type { AccountClient, AccountClientOptions, AccountScope, AccountsPage, CustomerPayment, PaymentMethodDetails, PaymentReceipt, PaymentsPage, SavedCard, Wallet };
@@ -0,0 +1,11 @@
1
+ // src/account/index.ts
2
+ import { createAccount } from "./client.js";
3
+ import { NotFoundError, ScopeRequiredError } from "./errors.js";
4
+ import { accountsUrl } from "./url.js";
5
+ export {
6
+ NotFoundError,
7
+ ScopeRequiredError,
8
+ accountsUrl,
9
+ createAccount
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/account/index.ts"],
4
+ "mappings": ";AAAA,SAAS,qBAAqB;AAE9B,SAAS,eAAe,0BAA0B;AAElD,SAAS,mBAAmB;",
5
+ "names": []
6
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "mappings": "",
5
+ "names": []
6
+ }
@@ -0,0 +1,24 @@
1
+ // src/account/url.ts
2
+ import { serviceUrl } from "../internal/service-url.js";
3
+ var ACCOUNTS = {
4
+ production: "https://accounts.zappie.ai",
5
+ staging: "https://accounts.stg.zappie.ai"
6
+ };
7
+ function accountsUrl(path, environment = "production", options = {}) {
8
+ const url = new URL(serviceUrl(ACCOUNTS, path, environment));
9
+ if (options.returnTo !== void 0) {
10
+ const back = new URL(
11
+ options.returnTo,
12
+ typeof location === "undefined" ? void 0 : location.href
13
+ );
14
+ if (back.protocol !== "https:" && back.protocol !== "http:") {
15
+ throw new TypeError("Pass an http or https page to return to");
16
+ }
17
+ url.searchParams.set("return", back.href);
18
+ }
19
+ return url.href;
20
+ }
21
+ export {
22
+ accountsUrl
23
+ };
24
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/account/url.ts"],
4
+ "mappings": ";AAAA,SAAS,kBAA0C;AAEnD,IAAM,WAAW;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AACX;AAoBO,SAAS,YACd,MACA,cAAiC,cACjC,UAAuC,CAAC,GAChC;AACR,QAAM,MAAM,IAAI,IAAI,WAAW,UAAU,MAAM,WAAW,CAAC;AAC3D,MAAI,QAAQ,aAAa,QAAW;AAClC,UAAM,OAAO,IAAI;AAAA,MACf,QAAQ;AAAA,MACR,OAAO,aAAa,cAAc,SAAY,SAAS;AAAA,IACzD;AACA,QAAI,KAAK,aAAa,YAAY,KAAK,aAAa,SAAS;AAC3D,YAAM,IAAI,UAAU,yCAAyC;AAAA,IAC/D;AACA,QAAI,aAAa,IAAI,UAAU,KAAK,IAAI;AAAA,EAC1C;AACA,SAAO,IAAI;AACb;",
5
+ "names": []
6
+ }
package/dist/debug.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/debug.ts
2
- var VERSION = true ? "0.3.0" : "0.0.0-development";
2
+ var VERSION = true ? "0.5.0" : "0.0.0-development";
3
3
  function installDebug(client) {
4
4
  if (typeof window === "undefined" || client.mode !== "test") {
5
5
  return;
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { ZappieError, isZappieError } from "./errors.js";
10
10
  export * from "./models.js";
11
11
  import { format, money } from "./money.js";
12
12
  import { RestTransport } from "./transport/rest.js";
13
- var VERSION = true ? "0.3.0" : "0.0.0-development";
13
+ var VERSION = true ? "0.5.0" : "0.0.0-development";
14
14
  export {
15
15
  API_VERSION,
16
16
  RestTransport,
@@ -0,0 +1,11 @@
1
+ // src/internal/service-url.ts
2
+ var serviceUrl = (origins, path, environment) => {
3
+ if (environment !== "production" && environment !== "staging" || !/^\/(?!\/)[^\u0000- \u007f\\]*$/u.test(path)) {
4
+ throw new TypeError("Pass a path that starts with one slash");
5
+ }
6
+ return origins[environment] + path;
7
+ };
8
+ export {
9
+ serviceUrl
10
+ };
11
+ //# sourceMappingURL=service-url.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/internal/service-url.ts"],
4
+ "mappings": ";AAIO,IAAM,aAAa,CACxB,SACA,MACA,gBACW;AACX,MACG,gBAAgB,gBAAgB,gBAAgB,aACjD,CAAC,kCAAkC,KAAK,IAAI,GAC5C;AACA,UAAM,IAAI,UAAU,wCAAwC;AAAA,EAC9D;AACA,SAAO,QAAQ,WAAW,IAAI;AAChC;",
5
+ "names": []
6
+ }
@@ -1,5 +1,6 @@
1
1
  /** The Zappie environment a service URL points at. */
2
2
  type ZappieEnvironment = "production" | "staging";
3
+
3
4
  /**
4
5
  * Build a URL on Zappie's checkout, such as an old payment link's new home.
5
6
  *
@@ -16,6 +17,8 @@ declare const checkoutUrl: (path: string, environment?: ZappieEnvironment) => st
16
17
  * @returns The absolute app URL. A path that could leave the app throws a TypeError.
17
18
  */
18
19
  declare const appUrl: (path: string, environment?: ZappieEnvironment) => string;
20
+ /** Zappie's support WhatsApp number, the same one the classic storefront opens. */
21
+ declare const ZAPPIE_SUPPORT_WHATSAPP = "15558956471";
19
22
 
20
- export { appUrl, checkoutUrl };
23
+ export { ZAPPIE_SUPPORT_WHATSAPP, appUrl, checkoutUrl };
21
24
  export type { ZappieEnvironment };
@@ -1,23 +1,18 @@
1
1
  // src/services/index.ts
2
- var ORIGINS = {
3
- checkout: {
4
- production: "https://checkout.zappie.ai",
5
- staging: "https://checkout.stg.zappie.ai"
6
- },
7
- app: {
8
- production: "https://app.zappie.ai",
9
- staging: "https://app.stg.zappie.ai"
10
- }
2
+ import { serviceUrl } from "../internal/service-url.js";
3
+ var CHECKOUT = {
4
+ production: "https://checkout.zappie.ai",
5
+ staging: "https://checkout.stg.zappie.ai"
11
6
  };
12
- var serviceUrl = (service, path, environment) => {
13
- if (environment !== "production" && environment !== "staging" || !/^\/(?!\/)[^\u0000- \u007f\\]*$/u.test(path)) {
14
- throw new TypeError("Pass a path that starts with one slash");
15
- }
16
- return ORIGINS[service][environment] + path;
7
+ var APP = {
8
+ production: "https://app.zappie.ai",
9
+ staging: "https://app.stg.zappie.ai"
17
10
  };
18
- var checkoutUrl = (path, environment = "production") => serviceUrl("checkout", path, environment);
19
- var appUrl = (path, environment = "production") => serviceUrl("app", path, environment);
11
+ var checkoutUrl = (path, environment = "production") => serviceUrl(CHECKOUT, path, environment);
12
+ var appUrl = (path, environment = "production") => serviceUrl(APP, path, environment);
13
+ var ZAPPIE_SUPPORT_WHATSAPP = "15558956471";
20
14
  export {
15
+ ZAPPIE_SUPPORT_WHATSAPP,
21
16
  appUrl,
22
17
  checkoutUrl
23
18
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/services/index.ts"],
4
- "mappings": ";AAIA,IAAM,UAAU;AAAA,EACd,UAAU;AAAA,IACR,YAAY;AAAA,IACZ,SAAS;AAAA,EACX;AAAA,EACA,KAAK;AAAA,IACH,YAAY;AAAA,IACZ,SAAS;AAAA,EACX;AACF;AAGA,IAAM,aAAa,CACjB,SACA,MACA,gBACW;AACX,MACG,gBAAgB,gBAAgB,gBAAgB,aACjD,CAAC,kCAAkC,KAAK,IAAI,GAC5C;AACA,UAAM,IAAI,UAAU,wCAAwC;AAAA,EAC9D;AACA,SAAO,QAAQ,OAAO,EAAE,WAAW,IAAI;AACzC;AASO,IAAM,cAAc,CACzB,MACA,cAAiC,iBACtB,WAAW,YAAY,MAAM,WAAW;AAS9C,IAAM,SAAS,CACpB,MACA,cAAiC,iBACtB,WAAW,OAAO,MAAM,WAAW;",
4
+ "mappings": ";AAAA,SAAS,kBAA0C;AAKnD,IAAM,WAAW;AAAA,EACf,YAAY;AAAA,EACZ,SAAS;AACX;AACA,IAAM,MAAM;AAAA,EACV,YAAY;AAAA,EACZ,SAAS;AACX;AASO,IAAM,cAAc,CACzB,MACA,cAAiC,iBACtB,WAAW,UAAU,MAAM,WAAW;AAS5C,IAAM,SAAS,CACpB,MACA,cAAiC,iBACtB,WAAW,KAAK,MAAM,WAAW;AAGvC,IAAM,0BAA0B;",
5
5
  "names": []
6
6
  }
@@ -0,0 +1,126 @@
1
+ // src/testing/account.ts
2
+ var ngn = (amount_minor) => ({ amount_minor, currency: "NGN" });
3
+ var receipt = (id, status, amount, at, method) => ({
4
+ id,
5
+ status,
6
+ amount: ngn(amount),
7
+ paid: ngn(amount),
8
+ reference: `PAY_${id.slice(4).toUpperCase()}`,
9
+ branch_id: "br_fixture",
10
+ branch_name: "Main",
11
+ order_id: status === "failed" ? null : `ord_${id.slice(4)}`,
12
+ created_at: at,
13
+ paid_at: status === "failed" ? null : at,
14
+ method
15
+ });
16
+ var accountFixtures = {
17
+ wallets: [
18
+ { currency: "NGN", balance: ngn(25e5) },
19
+ { currency: "USD", balance: { amount_minor: 1250, currency: "USD" } }
20
+ ],
21
+ cards: [
22
+ {
23
+ id: "card_fixture1",
24
+ brand: "mastercard",
25
+ last4: "4321",
26
+ exp_month: 4,
27
+ exp_year: 2029
28
+ },
29
+ {
30
+ id: "card_fixture2",
31
+ brand: "visa",
32
+ last4: "1881",
33
+ exp_month: 11,
34
+ exp_year: 2027
35
+ }
36
+ ],
37
+ payments: [
38
+ receipt("pay_fixture3", "successful", 105e4, "2026-10-12T12:00:00Z", {
39
+ type: "card",
40
+ card_brand: "mastercard",
41
+ card_last4: "4321"
42
+ }),
43
+ receipt("pay_fixture2", "refunded", 45e4, "2026-10-11T09:30:00Z", {
44
+ type: "bank_transfer",
45
+ bank_name: "Example Bank",
46
+ account_name: "Ada Obi"
47
+ }),
48
+ receipt("pay_fixture1", "failed", 2e5, "2026-10-10T18:15:00Z", {
49
+ type: "wallet"
50
+ })
51
+ ]
52
+ };
53
+ var SCOPES = ["wallet", "cards", "payments:read"];
54
+ function mockAccountFetch(fixtures = accountFixtures, options = {}) {
55
+ const data = structuredClone(fixtures);
56
+ const granted = options.scopes ?? SCOPES;
57
+ const error = (status, code, message) => Response.json({ error: { code, message } }, { status });
58
+ return async (input, init) => {
59
+ const request = new Request(
60
+ new URL(String(input), "http://mock.test"),
61
+ init
62
+ );
63
+ const url = new URL(request.url);
64
+ const [, resource, id, extra] = /\/me\/(wallets|cards|payments)(?:\/([^/]+))?(\/.*)?$/.exec(
65
+ url.pathname
66
+ ) ?? [];
67
+ if (!resource || extra !== void 0) {
68
+ return error(404, "not_found", "No such endpoint.");
69
+ }
70
+ const scope = resource === "wallets" ? "wallet" : resource === "cards" ? "cards" : "payments:read";
71
+ if (!granted.includes(scope)) {
72
+ return error(
73
+ 403,
74
+ "scope_required",
75
+ `The customer did not grant the ${scope} scope.`
76
+ );
77
+ }
78
+ const method = request.method;
79
+ if (resource === "wallets" && id === void 0 && method === "GET") {
80
+ return Response.json({ wallets: data.wallets });
81
+ }
82
+ if (resource === "cards" && id === void 0 && method === "GET") {
83
+ return Response.json({ cards: data.cards });
84
+ }
85
+ if (resource === "cards" && id !== void 0 && method === "DELETE") {
86
+ const cardId = decodeURIComponent(id);
87
+ if (!/^card_\w+$/.test(cardId)) {
88
+ return error(400, "invalid_id", "That isn't a card ID.");
89
+ }
90
+ const index = data.cards.findIndex((card) => card.id === cardId);
91
+ if (index < 0) {
92
+ return error(404, "not_found", "No such card.");
93
+ }
94
+ data.cards.splice(index, 1);
95
+ return new Response(null, { status: 204 });
96
+ }
97
+ if (resource === "payments" && method === "GET") {
98
+ if (id !== void 0) {
99
+ const found = data.payments.find(
100
+ (payment) => payment.id === decodeURIComponent(id)
101
+ );
102
+ return found ? Response.json(found) : error(404, "not_found", "No such payment.");
103
+ }
104
+ const limit = Number(url.searchParams.get("limit") ?? 20);
105
+ const cursor = url.searchParams.get("cursor");
106
+ const start = cursor === null ? 0 : data.payments.findIndex((payment) => payment.id === cursor);
107
+ if (!Number.isInteger(limit) || limit < 1 || start < 0) {
108
+ return error(400, "parameter_invalid", "Check limit and cursor.");
109
+ }
110
+ const page = data.payments.slice(start, start + Math.min(limit, 50));
111
+ const next = data.payments[start + page.length];
112
+ return Response.json({
113
+ payments: page.map(
114
+ ({ branch_name: _name, method: _method, ...payment }) => payment
115
+ ),
116
+ next_cursor: next?.id ?? null
117
+ });
118
+ }
119
+ return error(405, "method_not_allowed", "Method not allowed.");
120
+ };
121
+ }
122
+ export {
123
+ accountFixtures,
124
+ mockAccountFetch
125
+ };
126
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/testing/account.ts"],
4
+ "mappings": ";AAmBA,IAAM,MAAM,CAAC,kBAA0B,EAAE,cAAc,UAAU,MAAM;AACvE,IAAM,UAAU,CACd,IACA,QACA,QACA,IACA,YACoB;AAAA,EACpB;AAAA,EACA;AAAA,EACA,QAAQ,IAAI,MAAM;AAAA,EAClB,MAAM,IAAI,MAAM;AAAA,EAChB,WAAW,OAAO,GAAG,MAAM,CAAC,EAAE,YAAY,CAAC;AAAA,EAC3C,WAAW;AAAA,EACX,aAAa;AAAA,EACb,UAAU,WAAW,WAAW,OAAO,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,EACzD,YAAY;AAAA,EACZ,SAAS,WAAW,WAAW,OAAO;AAAA,EACtC;AACF;AAGO,IAAM,kBAAuC;AAAA,EAClD,SAAS;AAAA,IACP,EAAE,UAAU,OAAO,SAAS,IAAI,IAAS,EAAE;AAAA,IAC3C,EAAE,UAAU,OAAO,SAAS,EAAE,cAAc,MAAO,UAAU,MAAM,EAAE;AAAA,EACvE;AAAA,EACA,OAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,WAAW;AAAA,MACX,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,WAAW;AAAA,MACX,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,UAAU;AAAA,IACR,QAAQ,gBAAgB,cAAc,OAAW,wBAAwB;AAAA,MACvE,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,YAAY;AAAA,IACd,CAAC;AAAA,IACD,QAAQ,gBAAgB,YAAY,MAAS,wBAAwB;AAAA,MACnE,MAAM;AAAA,MACN,WAAW;AAAA,MACX,cAAc;AAAA,IAChB,CAAC;AAAA,IACD,QAAQ,gBAAgB,UAAU,KAAS,wBAAwB;AAAA,MACjE,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AACF;AAEA,IAAM,SAAyB,CAAC,UAAU,SAAS,eAAe;AAa3D,SAAS,iBACd,WAAgC,iBAChC,UAA8B,CAAC,GACjB;AACd,QAAM,OAAO,gBAAgB,QAAQ;AACrC,QAAM,UAAU,QAAQ,UAAU;AAClC,QAAM,QAAQ,CAAC,QAAgB,MAAc,YAC3C,SAAS,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC;AACxD,SAAO,OAAO,OAAO,SAAS;AAC5B,UAAM,UAAU,IAAI;AAAA,MAClB,IAAI,IAAI,OAAO,KAAK,GAAG,kBAAkB;AAAA,MACzC;AAAA,IACF;AACA,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,CAAC,EAAE,UAAU,IAAI,KAAK,IAC1B,uDAAuD;AAAA,MACrD,IAAI;AAAA,IACN,KAAK,CAAC;AACR,QAAI,CAAC,YAAY,UAAU,QAAW;AACpC,aAAO,MAAM,KAAK,aAAa,mBAAmB;AAAA,IACpD;AACA,UAAM,QACJ,aAAa,YACT,WACA,aAAa,UACX,UACA;AACR,QAAI,CAAC,QAAQ,SAAS,KAAK,GAAG;AAC5B,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,kCAAkC,KAAK;AAAA,MACzC;AAAA,IACF;AACA,UAAM,SAAS,QAAQ;AACvB,QAAI,aAAa,aAAa,OAAO,UAAa,WAAW,OAAO;AAClE,aAAO,SAAS,KAAK,EAAE,SAAS,KAAK,QAAQ,CAAC;AAAA,IAChD;AACA,QAAI,aAAa,WAAW,OAAO,UAAa,WAAW,OAAO;AAChE,aAAO,SAAS,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;AAAA,IAC5C;AACA,QAAI,aAAa,WAAW,OAAO,UAAa,WAAW,UAAU;AACnE,YAAM,SAAS,mBAAmB,EAAE;AACpC,UAAI,CAAC,aAAa,KAAK,MAAM,GAAG;AAC9B,eAAO,MAAM,KAAK,cAAc,uBAAuB;AAAA,MACzD;AACA,YAAM,QAAQ,KAAK,MAAM,UAAU,CAAC,SAAS,KAAK,OAAO,MAAM;AAC/D,UAAI,QAAQ,GAAG;AACb,eAAO,MAAM,KAAK,aAAa,eAAe;AAAA,MAChD;AACA,WAAK,MAAM,OAAO,OAAO,CAAC;AAC1B,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC3C;AACA,QAAI,aAAa,cAAc,WAAW,OAAO;AAC/C,UAAI,OAAO,QAAW;AACpB,cAAM,QAAQ,KAAK,SAAS;AAAA,UAC1B,CAAC,YAAY,QAAQ,OAAO,mBAAmB,EAAE;AAAA,QACnD;AACA,eAAO,QACH,SAAS,KAAK,KAAK,IACnB,MAAM,KAAK,aAAa,kBAAkB;AAAA,MAChD;AACA,YAAM,QAAQ,OAAO,IAAI,aAAa,IAAI,OAAO,KAAK,EAAE;AACxD,YAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;AAC5C,YAAM,QACJ,WAAW,OACP,IACA,KAAK,SAAS,UAAU,CAAC,YAAY,QAAQ,OAAO,MAAM;AAChE,UAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,GAAG;AACtD,eAAO,MAAM,KAAK,qBAAqB,yBAAyB;AAAA,MAClE;AACA,YAAM,OAAO,KAAK,SAAS,MAAM,OAAO,QAAQ,KAAK,IAAI,OAAO,EAAE,CAAC;AACnE,YAAM,OAAO,KAAK,SAAS,QAAQ,KAAK,MAAM;AAC9C,aAAO,SAAS,KAAK;AAAA,QACnB,UAAU,KAAK;AAAA,UACb,CAAC,EAAE,aAAa,OAAO,QAAQ,SAAS,GAAG,QAAQ,MAAM;AAAA,QAC3D;AAAA,QACA,aAAa,MAAM,MAAM;AAAA,MAC3B,CAAC;AAAA,IACH;AACA,WAAO,MAAM,KAAK,sBAAsB,qBAAqB;AAAA,EAC/D;AACF;",
5
+ "names": []
6
+ }
@@ -4,6 +4,85 @@ import { Store, Menu, Formatters } from '../index.js';
4
4
  export { VERSION } from '../index.js';
5
5
  import { CartModule, CartLineInput } from '../index.js';
6
6
 
7
+ /**
8
+ * A customer API amount in minor currency units, paired with its currency code.
9
+ */
10
+ type Money = {
11
+ amount_minor: number;
12
+ currency: string;
13
+ };
14
+
15
+ /** A scope the account calls need. A token without it gets `scope_required`. */
16
+ type AccountScope = "wallet" | "cards" | "payments:read";
17
+ /** One balance in the shopper's Zappie wallet, which is the same at every business. */
18
+ type Wallet = {
19
+ currency: string;
20
+ balance: Money;
21
+ };
22
+ /** A card the shopper saved when they paid. It never carries the full card number. */
23
+ type SavedCard = {
24
+ id: string;
25
+ /** The card scheme in lower case, such as `mastercard`. */
26
+ brand: string;
27
+ last4: string;
28
+ exp_month: number;
29
+ exp_year: number;
30
+ };
31
+ /** A payment the shopper made at this business. */
32
+ type CustomerPayment = {
33
+ id: string;
34
+ status: "successful" | "failed" | "refunded";
35
+ /** What the payment was for. */
36
+ amount: Money;
37
+ /** What the shopper paid, fees included. */
38
+ paid: Money;
39
+ reference: string;
40
+ branch_id: string;
41
+ /** The order the payment paid for, or null when it wasn't a delivery or pickup order. */
42
+ order_id: string | null;
43
+ created_at: string;
44
+ paid_at: string | null;
45
+ };
46
+ /** How the shopper paid. A card shows its brand and last four digits, a bank transfer its bank and account name. */
47
+ type PaymentMethodDetails = {
48
+ type: "card" | "card_terminal" | "bank_transfer" | "wallet" | "apple_pay" | "google_pay" | "store_credit" | "loyalty_points" | "mobile_money" | "other";
49
+ card_brand?: string;
50
+ card_last4?: string;
51
+ bank_name?: string;
52
+ account_name?: string;
53
+ };
54
+ /** A payment with what its receipt shows: the branch's name and how the shopper paid. */
55
+ type PaymentReceipt = CustomerPayment & {
56
+ branch_name: string;
57
+ method: PaymentMethodDetails;
58
+ };
59
+
60
+ /** The wallets, saved cards and payment receipts the mock account endpoints answer with. */
61
+ type MockAccountFixtures = {
62
+ wallets: Wallet[];
63
+ cards: SavedCard[];
64
+ /** Newest first, as the API lists them. */
65
+ payments: PaymentReceipt[];
66
+ };
67
+ /** The scopes the mock shopper granted. Leave it out to grant all three. */
68
+ type MockAccountOptions = {
69
+ scopes?: AccountScope[];
70
+ };
71
+ /** Sample account data with no real cards or people: two balances, two cards and three payments. */
72
+ declare const accountFixtures: MockAccountFixtures;
73
+ /**
74
+ * A fetch that answers the account endpoints `/me/wallets`, `/me/cards`, `/me/cards/{card_id}`,
75
+ * `/me/payments` and `/me/payments/{payment_id}` as the API does, from fixtures and in memory.
76
+ * Pass it as `fetch` to createAccount, or call it from a mock API server. A removed card stays
77
+ * removed for this fetch. A scope left out of `scopes` answers 403 `scope_required`. Any other
78
+ * path answers 404.
79
+ *
80
+ * @param fixtures - The account data. Defaults to accountFixtures.
81
+ * @param options - The scopes the shopper granted.
82
+ * @returns A fetch implementation for the account endpoints.
83
+ */
84
+ declare function mockAccountFetch(fixtures?: MockAccountFixtures, options?: MockAccountOptions): typeof fetch;
85
+
7
86
  /** Store and branch menus supplied to the deterministic mock transport. */
8
87
  type MockFixtures = {
9
88
  store: Store;
@@ -64,5 +143,5 @@ declare const fixtures: {
64
143
  };
65
144
  };
66
145
 
67
- export { createMockZappie, fixtures };
68
- export type { MockFixtures, MockOptions };
146
+ export { accountFixtures, createMockZappie, fixtures, mockAccountFetch };
147
+ export type { MockAccountFixtures, MockAccountOptions, MockFixtures, MockOptions };
@@ -7,6 +7,7 @@ import { delay, throwIfAborted } from "../internal/abort.js";
7
7
  import { multiplyMoney } from "../internal/money-math.js";
8
8
  import { money } from "../money.js";
9
9
  import { VERSION } from "../index.js";
10
+ import { accountFixtures, mockAccountFetch } from "./account.js";
10
11
  function createMockZappie(input, opts = {}) {
11
12
  const fixtures2 = structuredClone(input);
12
13
  const before = async (method, ctx) => {
@@ -452,6 +453,8 @@ var fixtures = {
452
453
  };
453
454
  export {
454
455
  VERSION,
456
+ accountFixtures,
455
457
  createMockZappie,
456
- fixtures
458
+ fixtures,
459
+ mockAccountFetch
457
460
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zappie/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Typed, framework-agnostic client for Zappie menus, carts, checkout, auth and events.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -73,6 +73,10 @@
73
73
  "types": "./dist/customer/react.d.ts",
74
74
  "import": "./dist/customer/react.js"
75
75
  },
76
+ "./account": {
77
+ "types": "./dist/account/index.d.ts",
78
+ "import": "./dist/account/index.js"
79
+ },
76
80
  "./places": {
77
81
  "types": "./dist/places/index.d.ts",
78
82
  "import": "./dist/places/index.js"
@@ -148,7 +152,7 @@
148
152
  "vite": "8.3.5"
149
153
  },
150
154
  "scripts": {
151
- "build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.checkout-defaults.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.redirect.config.ts && tsup --config tsup.services.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs --no-sources-content --unmapped=studio/index.js --unmapped=testing/index.js --unmapped=vite/index.js",
155
+ "build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.checkout-defaults.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.account.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.redirect.config.ts && tsup --config tsup.services.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs --no-sources-content --unmapped=studio/index.js --unmapped=testing/index.js --unmapped=vite/index.js",
152
156
  "test": "vitest run --root ../.. packages/sdk/test",
153
157
  "size": "node ../../scripts/release-packages/size-sdk.mjs && size-limit"
154
158
  }