@doany-ai/sdk 0.2.9-alpha.0 → 0.3.0-alpha.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/dist/client.js CHANGED
@@ -13,6 +13,9 @@ import { createAgentsModule } from "./modules/agents.js";
13
13
  import { createAiGatewayModule } from "./modules/ai-gateway.js";
14
14
  import { createAppLogsModule } from "./modules/app-logs.js";
15
15
  import { createUsersModule } from "./modules/users.js";
16
+ import { createContactsModule } from "./modules/contacts.js";
17
+ import { createEventsModule } from "./modules/events.js";
18
+ import { createProjectScope } from "./modules/project.js";
16
19
  import { RoomsSocket } from "./utils/socket-utils.js";
17
20
  import { createAnalyticsModule } from "./modules/analytics.js";
18
21
  /**
@@ -54,7 +57,10 @@ import { createAnalyticsModule } from "./modules/analytics.js";
54
57
  */
55
58
  export function createClient(config) {
56
59
  var _a, _b;
57
- const { serverUrl = "https://api.doany.ai", appId, token, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders, } = config;
60
+ const { serverUrl = "https://api.doany.ai", appId, token, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders, projectId, platformToken, operatorUserId, } = config;
61
+ if (platformToken && !projectId) {
62
+ throw new Error("createClient({ platformToken }) needs projectId: a platform client names the business it works on");
63
+ }
58
64
  // Normalize appBaseUrl to always be a string (empty if not provided or invalid)
59
65
  const normalizedAppBaseUrl = typeof appBaseUrl === "string" ? appBaseUrl : "";
60
66
  const socketConfig = {
@@ -83,9 +89,17 @@ export function createClient(config) {
83
89
  "Doany-Functions-Version": functionsVersion,
84
90
  }
85
91
  : headers;
92
+ // The platform's credential rides on the client's own requests only: not on
93
+ // function calls, and never on the service-role client, which is the app key's.
94
+ const platformHeaders = platformToken
95
+ ? {
96
+ "x-doany-service-token": platformToken,
97
+ ...(operatorUserId ? { "x-doany-operator-user-id": operatorUserId } : {}),
98
+ }
99
+ : {};
86
100
  const axiosClient = createAxiosClient({
87
101
  baseURL: `${serverUrl}/api`,
88
- headers,
102
+ headers: { ...headers, ...platformHeaders },
89
103
  token,
90
104
  onError: options === null || options === void 0 ? void 0 : options.onError,
91
105
  });
@@ -120,6 +134,12 @@ export function createClient(config) {
120
134
  token: serviceToken,
121
135
  interceptResponses: false,
122
136
  });
137
+ // The business the site belongs to, for the /projects/{project_id}/...
138
+ // modules: as given, or read once from the site's public settings. Each
139
+ // client reads them with its own credential: a login-gated site refuses the
140
+ // read without one, and a backend function may hold only the service token.
141
+ const project = createProjectScope(axiosClient, appId, projectId);
142
+ const serviceProject = createProjectScope(serviceRoleAxiosClient, appId, projectId);
123
143
  const userAuthModule = createAuthModule(axiosClient, functionsAxiosClient, appId, {
124
144
  appBaseUrl: normalizedAppBaseUrl,
125
145
  serverUrl,
@@ -141,9 +161,14 @@ export function createClient(config) {
141
161
  getSocket,
142
162
  }),
143
163
  integrations: createIntegrationsModule(axiosClient, appId),
144
- payments: createPaymentsModule(axiosClient, appId),
145
- catalog: createCatalogModule(axiosClient, appId),
146
- orders: createOrdersModule(axiosClient, appId),
164
+ app: project.app,
165
+ payments: createPaymentsModule(axiosClient, appId, project),
166
+ catalog: createCatalogModule(axiosClient, appId, project),
167
+ // A platform client runs on a server for many operators: like the service
168
+ // role, it keeps no retry state between calls.
169
+ orders: createOrdersModule(axiosClient, appId, project, { rememberAttempts: !platformToken }),
170
+ contacts: createContactsModule(axiosClient, project),
171
+ events: createEventsModule(axiosClient, project),
147
172
  connectors: createUserConnectorsModule(axiosClient, appId),
148
173
  auth: userAuthModule,
149
174
  functions: createFunctionsModule(functionsAxiosClient, appId, {
@@ -167,7 +192,7 @@ export function createClient(config) {
167
192
  }),
168
193
  aiGateway: createAiGatewayModule({ serverUrl, token, appId }),
169
194
  appLogs: createAppLogsModule(axiosClient, appId),
170
- users: createUsersModule(axiosClient, appId),
195
+ users: createUsersModule(axiosClient, appId, project),
171
196
  analytics: createAnalyticsModule({
172
197
  axiosClient,
173
198
  serverUrl,
@@ -202,15 +227,34 @@ export function createClient(config) {
202
227
  // actually there, and a function naming its own mode could charge a real
203
228
  // card from a preview.
204
229
  payments: (() => {
205
- const full = createPaymentsModule(serviceRoleAxiosClient, appId);
230
+ const full = createPaymentsModule(serviceRoleAxiosClient, appId, serviceProject);
206
231
  return {
207
232
  getCheckoutSession: full.getCheckoutSession.bind(full),
208
233
  getSubscription: full.getSubscription.bind(full),
209
234
  // Reading the products is not a decision for any browser, and a
210
235
  // fulfilment function often needs what it just sold.
211
236
  products: full.products,
237
+ // An order's checkout (e.g. a payment link to send) and the payment
238
+ // records. Not getForOrder: a service caller may not use it
239
+ // (permissions `payments:get_for_order`); it reads payments with `get`.
240
+ checkout: full.checkout.bind(full),
241
+ list: full.list.bind(full),
242
+ get: full.get.bind(full),
212
243
  };
213
244
  })(),
245
+ catalog: createCatalogModule(serviceRoleAxiosClient, appId, serviceProject),
246
+ orders: createOrdersModule(serviceRoleAxiosClient, appId, serviceProject, { rememberAttempts: false }),
247
+ contacts: (() => {
248
+ // `me` is a signed-in account's own contact; the app key has none.
249
+ const { me: _me, updateMe: _updateMe, ...rest } = createContactsModule(serviceRoleAxiosClient, serviceProject);
250
+ return rest;
251
+ })(),
252
+ events: createEventsModule(serviceRoleAxiosClient, serviceProject),
253
+ users: (() => {
254
+ // The invite route requires a signed-in account; the app key alone gets a 401.
255
+ const { inviteUser: _invite, ...rest } = createUsersModule(serviceRoleAxiosClient, appId, serviceProject);
256
+ return rest;
257
+ })(),
214
258
  functions: createFunctionsModule(serviceRoleFunctionsAxiosClient, appId, {
215
259
  getAuthHeaders: () => {
216
260
  const headers = {};
@@ -11,6 +11,10 @@ import type { AgentsModule } from "./modules/agents.types.js";
11
11
  import type { AiGatewayModule } from "./modules/ai-gateway.types.js";
12
12
  import type { AppLogsModule } from "./modules/app-logs.types.js";
13
13
  import type { AnalyticsModule } from "./modules/analytics.types.js";
14
+ import type { ContactsModule } from "./modules/contacts.types.js";
15
+ import type { EventsModule } from "./modules/events.types.js";
16
+ import type { UsersModule } from "./modules/users.types.js";
17
+ import type { AppModule } from "./modules/project.types.js";
14
18
  /**
15
19
  * Options for creating a Doany client.
16
20
  */
@@ -46,6 +50,12 @@ export interface CreateClientConfig {
46
50
  * It's the string between `/apps/` and `/editor/`.
47
51
  */
48
52
  appId: string;
53
+ /**
54
+ * The business (project) the app belongs to, for the catalog, orders,
55
+ * payments, contacts, events and users modules. Optional: left out, the SDK
56
+ * reads it once from the app's public settings.
57
+ */
58
+ projectId?: string;
49
59
  /**
50
60
  * User authentication token. Used to authenticate as a specific user.
51
61
  *
@@ -57,6 +67,18 @@ export interface CreateClientConfig {
57
67
  * @internal
58
68
  */
59
69
  serviceToken?: string;
70
+ /**
71
+ * The Doany platform's own credential (the platform website's server,
72
+ * Annie's tools), sent as `x-doany-service-token` on the client's requests.
73
+ * Needs `projectId`: a platform client names the business it works on.
74
+ */
75
+ platformToken?: string;
76
+ /**
77
+ * The platform account a `platformToken` call is made for, sent as
78
+ * `x-doany-operator-user-id`: it must be allowed on the project, and it is
79
+ * who the business's history records.
80
+ */
81
+ operatorUserId?: string;
60
82
  /**
61
83
  * Whether authentication is required. If true, redirects to login if not authenticated.
62
84
  * @internal
@@ -105,8 +127,16 @@ export interface DoanyClient {
105
127
  payments: PaymentsModule;
106
128
  /** {@link CatalogModule | Catalog module} for reading what the business offers. */
107
129
  catalog: CatalogModule;
108
- /** {@link OrdersModule | Orders module} for placing and reading orders. */
130
+ /** {@link OrdersModule | Orders module} for placing and managing orders. */
109
131
  orders: OrdersModule;
132
+ /** {@link ContactsModule | Contacts module} for the people the business knows. */
133
+ contacts: ContactsModule;
134
+ /** {@link EventsModule | Events module} for the business's timeline. */
135
+ events: EventsModule;
136
+ /** {@link UsersModule | Users module} for inviting and managing the site's accounts. */
137
+ users: UsersModule;
138
+ /** {@link AppModule | App module} for the site's public settings. */
139
+ app: AppModule;
110
140
  /** Cleanup function to disconnect WebSocket connections. Call when you're done with the client. */
111
141
  cleanup: () => void;
112
142
  /**
@@ -156,11 +186,31 @@ export interface DoanyClient {
156
186
  * service-role caller, so the ordinary client cannot resolve a mode and
157
187
  * the read fails whatever the function forwards.
158
188
  *
159
- * Only the reads are here — the two above and the products. There
160
- * is no service-role checkout: opening one is a decision that belongs to
161
- * the browser that is actually there.
189
+ * The reads are here — the two above and the products — and, for
190
+ * orders, `checkout` (e.g. a payment link to send), `list` and `get`.
191
+ * Not `getForOrder`: a service caller reads payments with `get`.
192
+ */
193
+ payments: Pick<PaymentsModule, "getCheckoutSession" | "getSubscription" | "products" | "checkout" | "list" | "get">;
194
+ /** {@link CatalogModule | Catalog module} with the service credential. */
195
+ catalog: CatalogModule;
196
+ /**
197
+ * {@link OrdersModule | Orders module} with the service credential. Unlike
198
+ * a page's client, nothing is remembered between calls: pass
199
+ * `idempotencyKey` to make a retry answer with the first order.
200
+ */
201
+ orders: OrdersModule;
202
+ /**
203
+ * {@link ContactsModule | Contacts module} with the service credential. No
204
+ * `me` / `updateMe`: those are a signed-in account's own contact.
205
+ */
206
+ contacts: Omit<ContactsModule, "me" | "updateMe">;
207
+ /** {@link EventsModule | Events module} with the service credential. */
208
+ events: EventsModule;
209
+ /**
210
+ * {@link UsersModule | Users module} with the service credential. No `inviteUser`: an invitation is sent by a
211
+ * signed-in account (the invite route requires one), not by the app key.
162
212
  */
163
- payments: Pick<PaymentsModule, "getCheckoutSession" | "getSubscription" | "products">;
213
+ users: Omit<UsersModule, "inviteUser">;
164
214
  /** {@link SsoModule | SSO module} for generating SSO tokens.
165
215
  * @internal
166
216
  */
package/dist/index.d.ts CHANGED
@@ -5,15 +5,19 @@ export { createClient, createClientFromRequest, DoanyError, getAccessToken, save
5
5
  export type { DoanyClient, CreateClientConfig, CreateClientOptions, DoanyErrorJSON, };
6
6
  export * from "./types.js";
7
7
  export type { DeleteManyResult, DeleteResult, EntitiesModule, EntityFilterOperators, EntityFilterQuery, EntityFilterValue, EntityHandler, EntityRecord, EntityTypeRegistry, ImportResult, RealtimeEventType, RealtimeEvent, RealtimeCallback, SortField, UpdateManyResult, } from "./modules/entities.types.js";
8
- export type { AuthModule, LoginResponse, RegisterParams, VerifyOtpParams, ChangePasswordParams, ResetPasswordParams, User, } from "./modules/auth.types.js";
8
+ export type { AuthModule, LoginResponse, LoginSession, RegisterParams, VerifyOtpParams, ChangePasswordParams, ResetPasswordParams, User, } from "./modules/auth.types.js";
9
9
  export type { IntegrationsModule, IntegrationEndpointFunction, CoreIntegrations, InvokeLLMParams, GenerateImageParams, GenerateImageResult, UploadFileParams, UploadFileResult, SendEmailParams, SendEmailResult, ExtractDataFromUploadedFileParams, ExtractDataFromUploadedFileResult, UploadPrivateFileParams, UploadPrivateFileResult, CreateFileSignedUrlParams, CreateFileSignedUrlResult, } from "./modules/integrations.types.js";
10
10
  export type { FunctionsModule, FunctionName, FunctionNameRegistry, } from "./modules/functions.types.js";
11
11
  export type { AgentsModule, AgentName, AgentNameRegistry, AgentConversation, AgentMessage, AgentMessageReasoning, AgentMessageToolCall, AgentMessageUsage, AgentMessageCustomContext, AgentMessageMetadata, CreateConversationParams, } from "./modules/agents.types.js";
12
12
  export type { AiGatewayModule, AiGatewayConnection, } from "./modules/ai-gateway.types.js";
13
13
  export type { AppLogsModule } from "./modules/app-logs.types.js";
14
- export type { PaymentsModule, ProductsModule, Product, ProductQuery, ProductSort, CheckoutLineItem, CreateCheckoutParams, CreateCheckoutResult, CreateEmbeddedCheckoutResult, CheckoutSession, SubscriptionState, BillingPortalParams, } from "./modules/payments.types.js";
15
- export type { CatalogModule, CatalogItem, CatalogVariant, CatalogListParams, } from "./modules/catalog.types.js";
16
- export type { OrdersModule, Order, OrderItem, OrderBuyer, OrderSummary, OrderStatus, FulfillmentStatus, CreateOrderParams, CreateOrderOptions, CreateOrderResult, OrderListParams, OrderAccessOptions, } from "./modules/orders.types.js";
14
+ export type { PaymentsModule, ProductsModule, Product, ProductQuery, ProductSort, CheckoutLineItem, CreateCheckoutParams, CreateCheckoutResult, CreateEmbeddedCheckoutResult, CheckoutSession, SubscriptionState, BillingPortalParams, OrderCheckoutParams, OrderCheckout, OrderPayments, Payment, PaymentStatus, PaymentListParams, Refund, RefundStatus, } from "./modules/payments.types.js";
15
+ export type { CatalogModule, CatalogItem, CatalogItemKind, CatalogItemStatus, CatalogVariant, CatalogVariantStatus, CatalogListParams, CatalogVariantInput, CreateCatalogItemParams, UpdateCatalogItemParams, UpdateCatalogVariantParams, } from "./modules/catalog.types.js";
16
+ export type { OrdersModule, Order, OrderItem, OrderBuyer, OrderChannel, OrderSummary, OrderStatus, FulfillmentStatus, ShippingAddress, CreateOrderParams, CreateOrderOptions, CreateOrderResult, OrderListParams, OrderAccessOptions, UpdateOrderParams, } from "./modules/orders.types.js";
17
+ export type { ContactsModule, Contact, ContactDetail, ContactSource, ContactUserLink, ContactListParams, CreateContactParams, UpdateContactParams, } from "./modules/contacts.types.js";
18
+ export type { EventsModule, ProjectEvent, EventSubjectType, EventListParams, } from "./modules/events.types.js";
19
+ export type { UsersModule, UserRecord, UserListParams, } from "./modules/users.types.js";
20
+ export type { AppModule, AppPublicSettings, Address, Page, PageParams, VersionInput, CreateOptions, } from "./modules/project.types.js";
17
21
  export type { SsoModule, SsoAccessTokenResponse } from "./modules/sso.types.js";
18
22
  export type { ConnectorsModule, UserConnectorsModule, ConnectorApiRequest, ConnectorApiResponse, ConnectorProxyRawResponse, } from "./modules/connectors.types.js";
19
23
  export type { CustomIntegrationsModule, CustomIntegrationCallParams, CustomIntegrationCallResponse, } from "./modules/custom-integrations.types.js";
@@ -211,6 +211,12 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
211
211
  otp_code: otpCode,
212
212
  });
213
213
  },
214
+ // Exchange the one-time code a sign-in redirect brought back for a token
215
+ async exchangeLoginCode(code) {
216
+ return (await axios.post(`/apps/${appId}/auth/session`, {
217
+ code,
218
+ }));
219
+ },
214
220
  // Resend an OTP code to the user's email
215
221
  resendOtp(email) {
216
222
  return axios.post(`/apps/${appId}/auth/resend-otp`, { email });
@@ -42,6 +42,21 @@ export interface LoginResponse {
42
42
  /** User information. */
43
43
  user: User;
44
44
  }
45
+ /** What the session endpoints answer with: the token and the account as the site sees it. */
46
+ export interface LoginSession {
47
+ access_token: string;
48
+ user: {
49
+ id: string;
50
+ email: string;
51
+ full_name: string | null;
52
+ role: string;
53
+ verified: boolean;
54
+ created_date: string | null;
55
+ updated_date: string | null;
56
+ /** The account's own custom fields. */
57
+ [field: string]: unknown;
58
+ };
59
+ }
45
60
  /**
46
61
  * Payload for user registration.
47
62
  */
@@ -514,4 +529,19 @@ export interface AuthModule {
514
529
  * ```
515
530
  */
516
531
  changePassword(params: ChangePasswordParams): Promise<any>;
532
+ /**
533
+ * Exchanges the one-time code a sign-in redirect (Google, SSO) brought back
534
+ * in the URL for an access token. The code is single-use and lives about a
535
+ * minute. Does not set the token: pass it to `doany.setToken()`.
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * const code = new URLSearchParams(window.location.search).get('code');
540
+ * if (code) {
541
+ * const { access_token } = await doany.auth.exchangeLoginCode(code);
542
+ * doany.setToken(access_token);
543
+ * }
544
+ * ```
545
+ */
546
+ exchangeLoginCode(code: string): Promise<LoginSession>;
517
547
  }
@@ -1,8 +1,9 @@
1
1
  import { AxiosInstance } from "axios";
2
2
  import { CatalogModule } from "./catalog.types";
3
+ import { ProjectScope } from "./project.js";
3
4
  /**
4
- * Creates the catalog module for the Doany SDK.
5
+ * Creates the catalog module: `/projects/{project_id}/catalog/...`.
5
6
  *
6
7
  * @internal
7
8
  */
8
- export declare function createCatalogModule(axios: AxiosInstance, appId: string): CatalogModule;
9
+ export declare function createCatalogModule(axios: AxiosInstance, appId: string, project: ProjectScope): CatalogModule;
@@ -1,29 +1,62 @@
1
+ import { idempotencyHeaders, queryOf, seg } from "./project.js";
1
2
  /**
2
- * Creates the catalog module for the Doany SDK.
3
+ * Creates the catalog module: `/projects/{project_id}/catalog/...`.
3
4
  *
4
5
  * @internal
5
6
  */
6
- export function createCatalogModule(axios, appId) {
7
- const baseURL = `/apps/${appId}/catalog/items`;
7
+ export function createCatalogModule(axios, appId, project) {
8
+ const items = (suffix = "") => project.path(`/catalog/items${suffix}`);
9
+ const variants = (suffix) => project.path(`/catalog/variants${suffix}`);
10
+ const post = async (url, data, headers) => (await axios.request({ method: "POST", url: await url, data, headers }));
8
11
  return {
9
12
  async list(params = {}) {
10
- const query = {};
11
- if (params.category)
12
- query.category = params.category;
13
- if (params.purchasable)
14
- query.purchasable = "true";
15
- if (params.limit)
16
- query.limit = params.limit;
17
- if (params.skip)
18
- query.skip = params.skip;
19
- if (params.sort)
20
- query.sort = params.sort;
21
- const data = await axios.get(baseURL, { params: query });
22
- return data;
23
- },
24
- async get(itemId) {
25
- const data = await axios.get(`${baseURL}/${encodeURIComponent(itemId)}`);
26
- return data;
13
+ // A site lists what it sells unless told otherwise.
14
+ const query = queryOf({ ...params, app_id: params.app_id === undefined ? appId : params.app_id });
15
+ return (await axios.get(await items(), { params: query }));
16
+ },
17
+ async get(itemId, params = {}) {
18
+ return (await axios.get(await items(`/${seg(itemId)}`), {
19
+ params: queryOf(params),
20
+ }));
21
+ },
22
+ create(params, options) {
23
+ return post(items(), params, idempotencyHeaders(options === null || options === void 0 ? void 0 : options.idempotencyKey));
24
+ },
25
+ async update(itemId, params) {
26
+ return (await axios.patch(await items(`/${seg(itemId)}`), params));
27
+ },
28
+ publish(itemId, { version }) {
29
+ return post(items(`/${seg(itemId)}/publish`), { version });
30
+ },
31
+ archive(itemId, { version }) {
32
+ return post(items(`/${seg(itemId)}/archive`), { version });
33
+ },
34
+ restore(itemId, { version }) {
35
+ return post(items(`/${seg(itemId)}/restore`), { version });
36
+ },
37
+ async setDirectPurchase(itemId, { enabled, version }) {
38
+ return (await axios.put(await items(`/${seg(itemId)}/direct-purchase`), {
39
+ enabled,
40
+ version,
41
+ }));
42
+ },
43
+ async delete(itemId) {
44
+ await axios.delete(await items(`/${seg(itemId)}`));
45
+ },
46
+ createVariant(itemId, params, options) {
47
+ return post(items(`/${seg(itemId)}/variants`), params, idempotencyHeaders(options === null || options === void 0 ? void 0 : options.idempotencyKey));
48
+ },
49
+ async updateVariant(variantId, params) {
50
+ return (await axios.patch(await variants(`/${seg(variantId)}`), params));
51
+ },
52
+ archiveVariant(variantId, { version }) {
53
+ return post(variants(`/${seg(variantId)}/archive`), { version });
54
+ },
55
+ restoreVariant(variantId, { version }) {
56
+ return post(variants(`/${seg(variantId)}/restore`), { version });
57
+ },
58
+ async deleteVariant(variantId) {
59
+ await axios.delete(await variants(`/${seg(variantId)}`));
27
60
  },
28
61
  };
29
62
  }
@@ -1,9 +1,17 @@
1
+ import type { CreateOptions, Page, PageParams, VersionInput } from "./project.types";
2
+ export type CatalogItemKind = "service" | "physical" | "digital";
3
+ export type CatalogItemStatus = "draft" | "active" | "archived";
4
+ export type CatalogVariantStatus = "active" | "archived";
1
5
  /**
2
6
  * One way an item is sold: a name, a price and, for services, a duration.
3
7
  */
4
8
  export interface CatalogVariant {
5
9
  id: string;
10
+ catalog_item_id: string;
11
+ status: CatalogVariantStatus;
6
12
  name: string;
13
+ /** The business's own stock-keeping code. */
14
+ sku: string | null;
7
15
  /** In the currency's smallest unit (cents). `null` when no price is set; `0` is free. */
8
16
  price_amount: number | null;
9
17
  /** Three upper-case letters, e.g. `"USD"`. `null` exactly when `price_amount` is. */
@@ -12,52 +20,144 @@ export interface CatalogVariant {
12
20
  /** The variant to preselect. */
13
21
  is_default: boolean;
14
22
  sort_order: number;
23
+ version: number;
24
+ created_at: string;
25
+ updated_at: string;
15
26
  }
16
27
  /**
17
28
  * Something the business offers, with the variants it is sold in.
18
29
  */
19
30
  export interface CatalogItem {
20
31
  id: string;
32
+ kind: CatalogItemKind;
33
+ /** `active` is shown; `draft` and `archived` are not. */
34
+ status: CatalogItemStatus;
35
+ /** The site that sells it; `null` when every site of the business does. */
36
+ app_id: string | null;
21
37
  name: string;
22
38
  description: string | null;
23
39
  category: string | null;
24
40
  image_url: string | null;
25
41
  direct_purchase_enabled: boolean;
26
42
  /**
27
- * Whether it can be ordered right now with `doany.orders.create`. An item
28
- * that is shown but not purchasable is for display only.
43
+ * Whether it can be ordered right now with `doany.orders.create`: published,
44
+ * direct purchase on, and at least one active variant with a price.
29
45
  */
30
46
  purchasable: boolean;
31
47
  sort_order: number;
32
48
  /** In display order. */
33
49
  variants: CatalogVariant[];
50
+ version: number;
51
+ created_at: string;
52
+ updated_at: string;
34
53
  }
35
- export interface CatalogListParams {
36
- category?: string;
54
+ export interface CatalogListParams extends PageParams {
55
+ /** `active` (default), `draft`, `archived` or `all`. A visitor only ever sees `active`. */
56
+ status?: CatalogItemStatus | "all";
57
+ /**
58
+ * The items a site sells: its own and those every site sells. Defaults to
59
+ * this client's app; `null` lists every item of the business.
60
+ */
61
+ app_id?: string | null;
37
62
  /** Only items that can be ordered right now. */
38
63
  purchasable?: boolean;
39
- limit?: number;
40
- skip?: number;
41
- /** A field name, `-` in front for descending: `sort_order` (default), `name`, `category`, `created_at`. */
42
- sort?: string;
64
+ /** Which variants come with each item: `active` (default) or `all`. */
65
+ variant_status?: "active" | "all";
66
+ /** Part of the name. */
67
+ q?: string;
68
+ category?: string;
69
+ kind?: CatalogItemKind;
70
+ }
71
+ export interface CatalogVariantInput {
72
+ name: string;
73
+ sku?: string | null;
74
+ price_amount?: number | null;
75
+ currency?: string | null;
76
+ duration_minutes?: number | null;
77
+ /** At most one per item; with none, the first variant is the default. */
78
+ is_default?: boolean;
79
+ sort_order?: number;
80
+ }
81
+ export interface CreateCatalogItemParams {
82
+ name: string;
83
+ /** `service` (default), `physical` or `digital`. Cannot change later. */
84
+ kind?: CatalogItemKind;
85
+ description?: string | null;
86
+ category?: string | null;
87
+ /** An `http(s)://` URL. */
88
+ image_url?: string | null;
89
+ sort_order?: number;
90
+ /** The one site that sells it; left out or `null`, every site does. */
91
+ app_id?: string | null;
92
+ variants?: CatalogVariantInput[];
93
+ }
94
+ export interface UpdateCatalogItemParams extends VersionInput {
95
+ name?: string;
96
+ description?: string | null;
97
+ category?: string | null;
98
+ image_url?: string | null;
99
+ sort_order?: number;
100
+ app_id?: string | null;
101
+ }
102
+ export interface UpdateCatalogVariantParams extends VersionInput {
103
+ name?: string;
104
+ sku?: string | null;
105
+ price_amount?: number | null;
106
+ currency?: string | null;
107
+ duration_minutes?: number | null;
108
+ /** `true` also unsets the item's previous default. */
109
+ is_default?: boolean;
110
+ sort_order?: number;
43
111
  }
44
112
  /**
45
- * What the business offers. Read-only: the catalog is managed by the business
46
- * owner in Doany, not from the site.
113
+ * What the business offers: items and the variants they are sold in.
114
+ *
115
+ * Everyone can read what is published. Changing the catalog takes the site's
116
+ * admin account or a service credential (`asServiceRole`); others are refused
117
+ * with 403 (401 when nobody is signed in).
118
+ *
119
+ * Every change sends back the `version` it is based on; a stale one is
120
+ * refused with 409 `VERSION_CONFLICT` and `details.version` is the current one.
121
+ * A change that would leave a published, directly purchasable item without a
122
+ * priced variant, or mix currencies, is refused with 409 `NOT_PURCHASABLE`
123
+ * (`details.reason`).
47
124
  */
48
125
  export interface CatalogModule {
49
126
  /**
50
- * The published items.
127
+ * The items this site sells.
51
128
  *
52
129
  * @example
53
130
  * ```typescript
54
- * const items = await doany.catalog.list({ purchasable: true });
131
+ * const { data: items } = await doany.catalog.list({ purchasable: true });
55
132
  * ```
56
133
  */
57
- list(params?: CatalogListParams): Promise<CatalogItem[]>;
58
- /**
59
- * One published item. Rejects with status 404 when there is no such item or
60
- * it is not published.
61
- */
62
- get(itemId: string): Promise<CatalogItem>;
134
+ list(params?: CatalogListParams): Promise<Page<CatalogItem>>;
135
+ /** One item. Rejects with 404 when there is none, or the caller may not see it. */
136
+ get(itemId: string, params?: {
137
+ variant_status?: "active" | "all";
138
+ }): Promise<CatalogItem>;
139
+ /** A new item, as a `draft` with direct purchase off. */
140
+ create(params: CreateCatalogItemParams, options?: CreateOptions): Promise<CatalogItem>;
141
+ /** Changes the item's details. `status` and direct purchase have their own calls. */
142
+ update(itemId: string, params: UpdateCatalogItemParams): Promise<CatalogItem>;
143
+ /** `draft` → `active` (shown). */
144
+ publish(itemId: string, params: VersionInput): Promise<CatalogItem>;
145
+ /** → `archived`: no longer shown or sold; existing orders are unaffected. */
146
+ archive(itemId: string, params: VersionInput): Promise<CatalogItem>;
147
+ /** `archived` → `draft`, to be checked and published again. */
148
+ restore(itemId: string, params: VersionInput): Promise<CatalogItem>;
149
+ /** Turns ordering the item directly on the site on or off. */
150
+ setDirectPurchase(itemId: string, params: VersionInput & {
151
+ enabled: boolean;
152
+ }): Promise<CatalogItem>;
153
+ /** Deletes an item nothing refers to; otherwise 409 `IN_USE` (archive it instead). */
154
+ delete(itemId: string): Promise<void>;
155
+ /** A new variant of an item. */
156
+ createVariant(itemId: string, params: CatalogVariantInput, options?: CreateOptions): Promise<CatalogVariant>;
157
+ /** Changes a variant. A new price applies to orders placed from now on. */
158
+ updateVariant(variantId: string, params: UpdateCatalogVariantParams): Promise<CatalogVariant>;
159
+ archiveVariant(variantId: string, params: VersionInput): Promise<CatalogVariant>;
160
+ restoreVariant(variantId: string, params: VersionInput): Promise<CatalogVariant>;
161
+ /** Deletes a variant nothing refers to; otherwise 409 `IN_USE`. */
162
+ deleteVariant(variantId: string): Promise<void>;
63
163
  }
@@ -0,0 +1,9 @@
1
+ import { AxiosInstance } from "axios";
2
+ import { ContactsModule } from "./contacts.types";
3
+ import { ProjectScope } from "./project.js";
4
+ /**
5
+ * Creates the contacts module: `/projects/{project_id}/contacts/...`.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare function createContactsModule(axios: AxiosInstance, project: ProjectScope): ContactsModule;
@@ -0,0 +1,42 @@
1
+ import { idempotencyHeaders, queryOf, seg } from "./project.js";
2
+ /**
3
+ * Creates the contacts module: `/projects/{project_id}/contacts/...`.
4
+ *
5
+ * @internal
6
+ */
7
+ export function createContactsModule(axios, project) {
8
+ const contacts = (suffix = "") => project.path(`/contacts${suffix}`);
9
+ const post = async (suffix, data, headers) => (await axios.request({ method: "POST", url: await contacts(suffix), data, headers }));
10
+ return {
11
+ async list(params = {}) {
12
+ return (await axios.get(await contacts(), { params: queryOf(params) }));
13
+ },
14
+ async get(contactId) {
15
+ return (await axios.get(await contacts(`/${seg(contactId)}`)));
16
+ },
17
+ async me() {
18
+ return (await axios.get(await contacts("/me")));
19
+ },
20
+ create(params, options) {
21
+ return post("", params, idempotencyHeaders(options === null || options === void 0 ? void 0 : options.idempotencyKey));
22
+ },
23
+ async update(contactId, params) {
24
+ return (await axios.patch(await contacts(`/${seg(contactId)}`), params));
25
+ },
26
+ async updateMe(params) {
27
+ return (await axios.patch(await contacts("/me"), params));
28
+ },
29
+ archive(contactId, { version }) {
30
+ return post(`/${seg(contactId)}/archive`, { version });
31
+ },
32
+ restore(contactId, { version }) {
33
+ return post(`/${seg(contactId)}/restore`, { version });
34
+ },
35
+ merge(contactId, { into_contact_id, version }) {
36
+ return post(`/${seg(contactId)}/merge`, { into_contact_id, version });
37
+ },
38
+ async delete(contactId) {
39
+ await axios.delete(await contacts(`/${seg(contactId)}`));
40
+ },
41
+ };
42
+ }