@skrybe/sdk 1.0.0 → 2.0.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/LICENSE +21 -0
- package/README.md +184 -103
- package/dist/index.cjs +1235 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +764 -0
- package/dist/index.d.ts +737 -25
- package/dist/index.js +1194 -88
- package/dist/index.js.map +1 -0
- package/package.json +48 -22
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/parse.ts","../src/resources/autoresponders.ts","../src/resources/shared.ts","../src/resources/campaigns.ts","../src/resources/emails.ts","../src/resources/lists.ts","../src/resources/misc.ts","../src/resources/subscribers.ts","../src/resources/templates.ts","../src/version.ts","../src/transport.ts","../src/client.ts"],"sourcesContent":["/**\n * The Skrybe API answers most failures with HTTP 200 and a prose sentence.\n * Only api/emails/send.php, api/emails/send-transactional.php and\n * api/stats/emails-sent.php use real status codes.\n *\n * Everything here exists to turn that into something code can branch on: every\n * failure is a `SkrybeError` with a stable `code`, and its subclass says what\n * kind of failure it is.\n */\n\n/**\n * - `authentication`: the API key is missing or wrong.\n * - `invalid_request`: the request itself is wrong; retrying it unchanged will fail again.\n * - `rate_limit`: a quota or rate limit was hit.\n * - `connection`: the API could not be reached, or did not answer in time.\n * - `api`: anything else the API refused or got wrong.\n */\nexport type SkrybeErrorType = 'authentication' | 'invalid_request' | 'rate_limit' | 'connection' | 'api'\n\nexport interface SkrybeErrorOptions {\n /** What to do about it, when we know. */\n hint?: string\n /** HTTP status of the response, when there was one. */\n status?: number\n /** Raw response body, kept for debugging odd endpoints. */\n raw?: string\n cause?: unknown\n}\n\nexport class SkrybeError extends Error {\n /** Stable machine-readable slug, e.g. `list_not_found`. */\n readonly code: string\n readonly type: SkrybeErrorType\n readonly hint?: string\n readonly status?: number\n readonly raw?: string\n\n constructor(type: SkrybeErrorType, code: string, message: string, opts: SkrybeErrorOptions = {}) {\n super(message, opts.cause === undefined ? undefined : { cause: opts.cause })\n this.name = 'SkrybeError'\n this.type = type\n this.code = code\n this.hint = opts.hint\n this.status = opts.status\n this.raw = opts.raw\n }\n}\n\nexport class AuthenticationError extends SkrybeError {\n constructor(code: string, message: string, opts?: SkrybeErrorOptions) {\n super('authentication', code, message, opts)\n this.name = 'AuthenticationError'\n }\n}\n\nexport class InvalidRequestError extends SkrybeError {\n constructor(code: string, message: string, opts?: SkrybeErrorOptions) {\n super('invalid_request', code, message, opts)\n this.name = 'InvalidRequestError'\n }\n}\n\nexport class RateLimitError extends SkrybeError {\n constructor(code: string, message: string, opts?: SkrybeErrorOptions) {\n super('rate_limit', code, message, opts)\n this.name = 'RateLimitError'\n }\n}\n\nexport class ConnectionError extends SkrybeError {\n constructor(code: string, message: string, opts?: SkrybeErrorOptions) {\n super('connection', code, message, opts)\n this.name = 'ConnectionError'\n }\n}\n\nexport class ApiError extends SkrybeError {\n constructor(code: string, message: string, opts?: SkrybeErrorOptions) {\n super('api', code, message, opts)\n this.name = 'ApiError'\n }\n}\n\nexport function createError(\n type: SkrybeErrorType,\n code: string,\n message: string,\n opts?: SkrybeErrorOptions,\n): SkrybeError {\n switch (type) {\n case 'authentication':\n return new AuthenticationError(code, message, opts)\n case 'invalid_request':\n return new InvalidRequestError(code, message, opts)\n case 'rate_limit':\n return new RateLimitError(code, message, opts)\n case 'connection':\n return new ConnectionError(code, message, opts)\n default:\n return new ApiError(code, message, opts)\n }\n}\n\n/**\n * Render an unexpected response body for an error message: whitespace collapsed\n * so a multi-line PHP warning stays on one line, and truncated so a stray HTML\n * page does not swamp a log. The untruncated body stays on `SkrybeError.raw`.\n */\nexport function excerpt(body: string, limit = 200): string {\n const flat = body.replace(/\\s+/g, ' ').trim()\n if (flat === '') return '(an empty response)'\n return flat.length > limit ? `${flat.slice(0, limit)}…` : flat\n}\n\ninterface ProseRule {\n code: string\n type: SkrybeErrorType\n hint?: string\n}\n\nconst LIST_IDS_HINT = 'Call lists.list() to see the IDs available to this API key.'\nconst UNDER_REVIEW_HINT =\n 'Sending is paused while the brand is reviewed for a high bounce rate. Contact Skrybe support.'\nconst DOMAIN_HINT = \"Verify the From domain for this brand in the Skrybe dashboard.\"\n\n/**\n * Prose returned by the legacy endpoints, mapped to a stable code.\n * Matched case-insensitively against the trimmed body.\n *\n * Kept in step with skrybe-cli's src/api/errors.ts, which documents where each\n * one comes from.\n */\nconst PROSE_ERRORS: Record<string, ProseRule> = {\n // Authentication\n 'api key not passed': { code: 'api_key_missing', type: 'authentication' },\n 'invalid api key': {\n code: 'invalid_api_key',\n type: 'authentication',\n hint: 'Generate a key in the Skrybe dashboard under Settings. Keys are cleared for every login of a brand placed under review.',\n },\n\n // Request shape\n 'no data passed': { code: 'no_data_passed', type: 'invalid_request' },\n 'some fields are missing.': { code: 'missing_fields', type: 'invalid_request' },\n\n // Brands and lists\n 'brand id not passed': { code: 'brand_id_missing', type: 'invalid_request' },\n // Also the answer to the install's main account key, which no brand is tied to.\n 'brand does not exist': {\n code: 'brand_not_found',\n type: 'api',\n hint: 'Use an API key from a brand login; the main account key is not tied to a brand.',\n },\n 'list does not exist': { code: 'list_not_found', type: 'api', hint: LIST_IDS_HINT },\n 'invalid list id.': { code: 'list_not_found', type: 'api', hint: LIST_IDS_HINT },\n // Also what the server produces from an id it cannot decrypt, so this is\n // the answer to a malformed id as much as to a missing one.\n 'list id not passed': {\n code: 'list_id_invalid',\n type: 'invalid_request',\n hint: 'A list ID is the encrypted value lists.list() returns, not the number in the dashboard URL.',\n },\n\n // Autoresponders\n 'autoresponder id not passed': { code: 'autoresponder_id_missing', type: 'invalid_request' },\n 'autoresponder does not exist': { code: 'autoresponder_not_found', type: 'api' },\n 'email id not passed': { code: 'email_id_missing', type: 'invalid_request' },\n 'autoresponder email does not exist': { code: 'autoresponder_email_not_found', type: 'api' },\n 'autoresponder name not passed': { code: 'autoresponder_name_missing', type: 'invalid_request' },\n 'type must be drip, annually or on_date': { code: 'invalid_autoresponder_type', type: 'invalid_request' },\n 'list already has a drip autoresponder': {\n code: 'drip_exists',\n type: 'api',\n hint: 'A list can have one drip autoresponder. Add emails to the existing one.',\n },\n 'custom_field not passed': {\n code: 'custom_field_missing',\n type: 'invalid_request',\n hint: 'annually and on_date autoresponders send on a Date custom field of the list.',\n },\n 'field is not a date field': { code: 'field_not_date', type: 'invalid_request' },\n 'field already has an autoresponder': { code: 'field_has_autoresponder', type: 'api' },\n 'subject not passed': { code: 'subject_missing', type: 'invalid_request' },\n 'a drip email cannot go before signup': { code: 'invalid_when', type: 'invalid_request' },\n 'when must be immediately, or a delay such as +3 days': { code: 'invalid_when', type: 'invalid_request' },\n 'when must be on, or an offset such as +9 hours or -1 days': { code: 'invalid_when', type: 'invalid_request' },\n\n // Templates\n 'template id not passed': { code: 'template_id_missing', type: 'invalid_request' },\n 'template does not exist': { code: 'template_not_found', type: 'api' },\n 'template name not passed': { code: 'template_name_missing', type: 'invalid_request' },\n 'template name cannot be empty': { code: 'template_name_missing', type: 'invalid_request' },\n 'html not passed': { code: 'html_missing', type: 'invalid_request' },\n 'editor must be html or dragdrop': { code: 'invalid_editor', type: 'invalid_request' },\n\n // List management\n 'list name not passed': { code: 'list_name_missing', type: 'invalid_request' },\n 'list name is too long': {\n code: 'list_name_too_long',\n type: 'invalid_request',\n hint: 'A list name can be at most 100 characters.',\n },\n 'opt_in must be single or double': { code: 'invalid_opt_in', type: 'invalid_request' },\n 'nothing to update': { code: 'nothing_to_update', type: 'invalid_request', hint: 'Pass at least one field to change.' },\n 'list is used by a scheduled or sending campaign': {\n code: 'list_in_use',\n type: 'api',\n hint: 'Unschedule the campaign, or wait for it to finish sending, then delete the list.',\n },\n\n // Custom fields\n 'field name not passed': { code: 'field_name_missing', type: 'invalid_request' },\n 'invalid field name': {\n code: 'invalid_field_name',\n type: 'invalid_request',\n hint: 'Up to 100 characters, without : % [ ] , \" < or >.',\n },\n 'name and email are built-in fields': { code: 'reserved_field_name', type: 'invalid_request' },\n 'type must be text or date': { code: 'invalid_field_type', type: 'invalid_request' },\n 'field already exists': { code: 'field_exists', type: 'api', hint: 'Field names are case-insensitive.' },\n 'field does not exist': { code: 'field_not_found', type: 'api' },\n 'field is used by an autoresponder': {\n code: 'field_in_use',\n type: 'api',\n hint: 'Change the autoresponder in the Skrybe dashboard first.',\n },\n 'field is used by a segment': {\n code: 'field_in_use',\n type: 'api',\n hint: 'Remove it from the segment in the Skrybe dashboard first.',\n },\n 'list was changed by another request. try again.': { code: 'conflict', type: 'api' },\n\n // Campaigns\n 'campaign id not passed': { code: 'campaign_id_missing', type: 'invalid_request' },\n 'campaign does not exist': { code: 'campaign_not_found', type: 'api' },\n 'invalid status': { code: 'invalid_status', type: 'invalid_request' },\n 'invalid page': { code: 'invalid_page', type: 'invalid_request' },\n 'invalid limit': { code: 'invalid_limit', type: 'invalid_request' },\n 'invalid type': { code: 'invalid_type', type: 'invalid_request' },\n\n // Sending a campaign\n 'list or segment id(s) not passed': {\n code: 'recipients_missing',\n type: 'invalid_request',\n hint: 'Pass at least one list or segment ID.',\n },\n 'one or more list ids are invalid': { code: 'list_not_found', type: 'api', hint: LIST_IDS_HINT },\n 'one or more segment ids are invalid': { code: 'segment_not_found', type: 'api' },\n 'campaign has already been sent': {\n code: 'campaign_not_draft',\n type: 'api',\n hint: 'It has already started sending, so it can no longer be sent, scheduled or edited.',\n },\n 'campaign is scheduled': {\n code: 'campaign_scheduled',\n type: 'api',\n hint: 'Unschedule it first, or leave it to send at its scheduled time.',\n },\n 'campaign is not scheduled': { code: 'campaign_not_scheduled', type: 'api' },\n\n // Editing and deleting campaigns\n 'campaign is sending': {\n code: 'campaign_sending',\n type: 'api',\n hint: 'Stop it in the Skrybe dashboard first, or wait for it to finish.',\n },\n 'subject cannot be empty': { code: 'subject_empty', type: 'invalid_request' },\n 'html cannot be empty': { code: 'html_empty', type: 'invalid_request' },\n 'invalid from_email': { code: 'invalid_from_email', type: 'invalid_request' },\n 'invalid reply_to': { code: 'invalid_reply_to', type: 'invalid_request' },\n 'track_opens and track_clicks must be 0, 1 or 2': {\n code: 'invalid_tracking',\n type: 'invalid_request',\n hint: '0 off, 1 on, 2 anonymous.',\n },\n 'campaign was changed by another request. try again.': { code: 'conflict', type: 'api' },\n\n // Scheduling\n 'schedule_date_time not passed': { code: 'schedule_missing', type: 'invalid_request' },\n 'schedule_date_time is invalid': {\n code: 'invalid_schedule',\n type: 'invalid_request',\n hint: 'Use a date and time such as \"2027-06-15 18:05\" or \"June 15, 2027 6:05pm\".',\n },\n 'schedule_date_time is in the past': {\n code: 'schedule_in_past',\n type: 'invalid_request',\n hint: \"The time is read in the timezone given, or the account's timezone if none is.\",\n },\n 'schedule_timezone is invalid': {\n code: 'invalid_timezone',\n type: 'invalid_request',\n hint: 'Use an IANA name such as Africa/Lagos or America/New_York.',\n },\n\n // Test sends\n 'email addresses not passed': { code: 'emails_missing', type: 'invalid_request' },\n 'too many email addresses': {\n code: 'too_many_emails',\n type: 'invalid_request',\n hint: 'A test send goes to at most 5 addresses.',\n },\n 'quota exceeded. please upgrade your plan.': { code: 'quota_exceeded', type: 'rate_limit' },\n 'no active subscribers to send to': {\n code: 'no_recipients',\n type: 'api',\n hint: 'Every subscriber on those lists is unsubscribed, bounced, unconfirmed or excluded.',\n },\n\n // Subscribers\n 'subscriber does not exist': { code: 'subscriber_not_found', type: 'api' },\n 'email does not exist in list': { code: 'subscriber_not_found', type: 'api' },\n 'email not passed': { code: 'email_missing', type: 'invalid_request' },\n 'email address not passed': { code: 'email_missing', type: 'invalid_request' },\n 'invalid email address.': { code: 'invalid_email', type: 'invalid_request' },\n 'bounced email address.': { code: 'bounced_email', type: 'api' },\n 'email is suppressed.': { code: 'suppressed_email', type: 'api' },\n // unsubscribe.php's wording for an address that is not on the list.\n 'email does not exist.': { code: 'subscriber_not_found', type: 'api' },\n 'ip address is invalid.': { code: 'invalid_ip_address', type: 'invalid_request' },\n 'country must be a valid 2 letter country code': {\n code: 'invalid_country',\n type: 'invalid_request',\n hint: 'Use a two-letter ISO 3166-1 code, e.g. NG.',\n },\n 'referrer is not a valid url': { code: 'invalid_referrer', type: 'invalid_request' },\n 'consent not given.': {\n code: 'consent_not_given',\n type: 'invalid_request',\n hint: 'The list requires GDPR consent; pass gdpr: true.',\n },\n 'failed recaptcha test.': {\n code: 'recaptcha_failed',\n type: 'api',\n hint: 'The list has reCAPTCHA enabled on its subscribe form, which an API call cannot satisfy.',\n },\n\n // Sending\n 'unauthorised from email domain': {\n code: 'unauthorised_from_domain',\n type: 'api',\n hint: 'The From domain must be verified for this brand, or listed in its allowed domains.',\n },\n}\n\n/** Prose with a variable part, matched by pattern after the exact table. */\nconst PROSE_PATTERN_ERRORS: [RegExp, ProseRule][] = [\n [/^error: this campaign would exceed/, { code: 'quota_exceeded', type: 'rate_limit' }],\n [/^account under review:/, { code: 'account_under_review', type: 'api', hint: UNDER_REVIEW_HINT }],\n [/^brand is under review/, { code: 'account_under_review', type: 'api', hint: UNDER_REVIEW_HINT }],\n [/^a field is too long/, { code: 'field_too_long', type: 'invalid_request' }],\n [\n /^test send rate limit reached/,\n {\n code: 'rate_limited',\n type: 'rate_limit',\n hint: 'Test sends are limited to 20 an hour per brand, shared with the Skrybe dashboard.',\n },\n ],\n [/^domain not verified in ses/, { code: 'domain_not_verified', type: 'api', hint: DOMAIN_HINT }],\n [/^domain \".*\" is not verified/, { code: 'domain_not_verified', type: 'api', hint: DOMAIN_HINT }],\n]\n\n/**\n * Bodies that mean \"nothing to return\" rather than a failure: these endpoints\n * answer with prose instead of an empty object when the brand has no rows.\n */\nconst PROSE_EMPTY = new Set(['no lists found', 'no brands found', 'no campaigns found'])\n\nexport function isProseEmpty(body: string): boolean {\n return PROSE_EMPTY.has(body.trim().toLowerCase())\n}\n\n/** Bodies that mean success even though they are bare prose. */\nconst PROSE_OK = new Set(['1', 'true', 'already subscribed.'])\n\nexport function isProseSuccess(body: string): boolean {\n return PROSE_OK.has(body.trim().toLowerCase())\n}\n\n/**\n * Classify a legacy plain-text body. Returns null when the body is not a\n * recognised error, which callers should treat as data.\n */\nexport function classifyProse(body: string, status?: number): SkrybeError | null {\n const trimmed = body.trim()\n const key = trimmed.toLowerCase()\n\n const rule = PROSE_ERRORS[key] ?? PROSE_PATTERN_ERRORS.find(([pattern]) => pattern.test(key))?.[1]\n if (rule) return createError(rule.type, rule.code, trimmed, { raw: body, status, hint: rule.hint })\n\n // A family of \"Unable to ...\" server-side failures not worth enumerating.\n if (key.startsWith('unable to')) return new ApiError('operation_failed', trimmed, { raw: body, status })\n\n return null\n}\n\n/** The error type for an HTTP status from one of the JSON endpoints. */\nexport function typeForStatus(status: number): SkrybeErrorType {\n if (status === 401 || status === 403) return 'authentication'\n if (status === 402 || status === 429) return 'rate_limit'\n if (status === 400 || status === 405 || status === 422) return 'invalid_request'\n return 'api'\n}\n\nexport function codeForStatus(status: number): string {\n switch (status) {\n case 400:\n return 'invalid_request'\n case 401:\n return 'unauthenticated'\n case 402:\n return 'quota_exceeded'\n case 403:\n return 'forbidden'\n case 404:\n return 'not_found'\n case 405:\n return 'method_not_allowed'\n case 429:\n return 'rate_limited'\n default:\n return status >= 500 ? 'server_error' : 'api_error'\n }\n}\n\n/** A body that is neither a known success nor a known failure. */\nexport function unexpectedResponse(operation: string, body: string): ApiError {\n return new ApiError('unexpected_response', `Unexpected response from ${operation}: ${excerpt(body)}`, { raw: body })\n}\n","/**\n * `api/lists/get-lists.php` and `api/brands/get-brands.php` build their payload\n * by string concatenation:\n *\n * $output .= '\"list'.$i.'\": { \"id\": \"'.encrypt_val($id).'\", \"name\": \"'.$name.'\" },'\n *\n * Consequences we have to live with until those endpoints use json_encode():\n * - no Content-Type header\n * - entries are keyed `list1`, `list2`, ... rather than being an array\n * - the name is NOT escaped, so a list called `Q4 \"Big\" Push` emits invalid JSON\n *\n * The id is safe to match on: encrypt_val() base64s and then substitutes\n * `/` -> 892 and `+` -> 763 and strips `=`, so it never contains a quote.\n * That gives us a reliable structural anchor for the fallback parser.\n */\n\nimport { ApiError, excerpt } from './errors.js'\n\nexport interface NamedEntity {\n id: string\n name: string\n}\n\n/**\n * Structural fallback for payloads JSON.parse rejects.\n *\n * The trailing lookahead is what makes an embedded quote survive: the lazy\n * name match can only stop at a `\"` that is followed by the closing brace of\n * the entry AND either the next `listN`/`brandN` key or the end of the object.\n */\nfunction extractByStructure(body: string, prefix: string): NamedEntity[] {\n const entry = new RegExp(\n String.raw`\"${prefix}\\d+\"\\s*:\\s*\\{\\s*` +\n String.raw`\"id\"\\s*:\\s*\"([^\"]*)\"\\s*,\\s*` +\n String.raw`\"name\"\\s*:\\s*\"([\\s\\S]*?)\"\\s*\\}` +\n String.raw`(?=\\s*,\\s*\"${prefix}\\d+\"\\s*:|\\s*\\}\\s*$)`,\n 'g',\n )\n\n const out: NamedEntity[] = []\n for (const match of body.matchAll(entry)) {\n out.push({ id: match[1] ?? '', name: match[2] ?? '' })\n }\n return out\n}\n\n/**\n * Parse a `{\"list1\": {...}, \"list2\": {...}}` payload into an ordered array.\n *\n * @param prefix the key prefix the endpoint uses — `list` or `brand`\n * @throws if the body is neither valid JSON nor structurally recognisable\n */\nexport function parseNumberedObject(body: string, prefix: 'list' | 'brand'): NamedEntity[] {\n const trimmed = body.trim()\n if (trimmed === '') return []\n\n // Happy path, and the path this takes once the endpoints use json_encode().\n try {\n const parsed: unknown = JSON.parse(trimmed)\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {\n const out: NamedEntity[] = []\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (!key.startsWith(prefix)) continue\n if (value === null || typeof value !== 'object') continue\n const row = value as Record<string, unknown>\n out.push({ id: String(row.id ?? ''), name: String(row.name ?? '') })\n }\n return sortByNumericSuffix(out, parsed as Record<string, unknown>, prefix)\n }\n } catch {\n // Fall through — almost certainly an unescaped quote in a name.\n }\n\n const recovered = extractByStructure(trimmed, prefix)\n if (recovered.length > 0) return recovered\n\n // The body is the whole diagnosis here — it is usually a PHP warning or an\n // interstitial from something in front of the install — so show it rather\n // than describing it. `raw` keeps the untruncated version for callers.\n throw new ApiError(\n 'malformed_response',\n `Could not parse the ${prefix} payload returned by the API.`,\n { raw: body, hint: `The server sent: ${excerpt(trimmed)}` },\n )\n}\n\n/**\n * Object key order in PHP's output is already list1..listN, and V8 preserves\n * insertion order for string keys, so this is belt-and-braces for the case\n * where a future endpoint emits them out of order.\n */\nfunction sortByNumericSuffix(\n rows: NamedEntity[],\n source: Record<string, unknown>,\n prefix: string,\n): NamedEntity[] {\n const keys = Object.keys(source).filter((k) => k.startsWith(prefix))\n const order = new Map<number, number>()\n keys.forEach((key, index) => {\n const n = Number.parseInt(key.slice(prefix.length), 10)\n if (!Number.isNaN(n)) order.set(index, n)\n })\n return rows\n .map((row, index) => ({ row, sort: order.get(index) ?? index }))\n .sort((a, b) => a.sort - b.sort)\n .map((x) => x.row)\n}\n\n/**\n * Parse a numbered payload from an endpoint that builds it with json_encode()\n * (`api/campaigns/get-campaigns.php`), so the rows are trusted as-is rather\n * than cut down to `{id, name}`.\n *\n * @param prefix the key prefix the endpoint uses, e.g. `campaign`\n * @throws if the body is not a JSON object\n */\nexport function parseNumberedRecords<T>(body: string, prefix: string): T[] {\n const parsed = parseJsonObject<Record<string, unknown>>(body, `${prefix} payload`)\n const key = new RegExp(`^${prefix}(\\\\d+)$`)\n\n return Object.entries(parsed)\n .map(([name, value]) => ({ match: key.exec(name), value }))\n .filter((entry): entry is { match: RegExpExecArray; value: unknown } =>\n entry.match !== null && entry.value !== null && typeof entry.value === 'object',\n )\n .sort((a, b) => Number(a.match[1]) - Number(b.match[1]))\n .map((entry) => entry.value as T)\n}\n\n/**\n * Parse a body that must be a JSON object, e.g. `api/campaigns/stats.php`.\n * Anything else is almost always a PHP warning or a proxy page, so the body\n * itself goes in the hint.\n */\nexport function parseJsonObject<T>(body: string, what: string): T {\n const trimmed = body.trim()\n try {\n const parsed: unknown = JSON.parse(trimmed)\n if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed as T\n } catch {\n // Reported below with the body intact.\n }\n throw new ApiError(\n 'malformed_response',\n `Could not parse the ${what} returned by the API.`,\n { raw: body, hint: `The server sent: ${excerpt(trimmed)}` },\n )\n}\n","import { parseJsonObject } from '../parse.js'\nimport type { Transport } from '../transport.js'\nimport type { EngagementStats, Tracking } from './shared.js'\n\n/**\n * `drip` sends each email a set time after someone joins; `annually` and\n * `on_date` send on a Date custom field, every year or once.\n */\nexport type AutoresponderType = 'drip' | 'annually' | 'on_date'\n\nexport interface Autoresponder {\n id: number\n name: string\n /** `unknown` for a type this SDK predates. */\n type: AutoresponderType | 'unknown'\n /** Encrypted, like the ids `lists.list()` returns. */\n list_id: string\n /** The date field an `annually` or `on_date` autoresponder sends on. */\n custom_field: string | null\n emails: number\n enabled_emails: number\n}\n\nexport interface AutoresponderEmail {\n id: number\n autoresponder_id: number\n subject: string\n from_name: string\n from_email: string\n /** As stored: \"immediately\", \"+3 days\", \"-1 days\", or \"\" for the date itself. */\n when: string\n enabled: boolean\n recipients: number\n /** Unix seconds. */\n created_at: number | null\n}\n\nexport interface AutoresponderWithEmails extends Autoresponder {\n email_list: AutoresponderEmail[]\n}\n\nexport interface AutoresponderEmailStats extends EngagementStats {\n id: number\n autoresponder_id: number\n}\n\nexport interface CreateAutoresponderParams {\n name: string\n type: AutoresponderType\n /** The encrypted list id. A list can have one drip autoresponder. */\n listId: string\n /** Required for `annually` and `on_date`: a Date custom field of the list, one autoresponder per field. */\n customField?: string\n}\n\nexport interface CreateAutoresponderEmailParams {\n subject: string\n htmlText: string\n plainText?: string\n preheader?: string\n /**\n * When it goes. For a drip, \"immediately\" (the default) or a delay such as\n * \"+3 days\"; for annually and on_date, \"on\" (the default, the date itself)\n * or an offset such as \"+9 hours\" or \"-1 days\". Units: minute, hour, day,\n * week, month.\n */\n when?: string\n /** Each falls back to the brand's sender. */\n fromName?: string\n fromEmail?: string\n replyTo?: string\n queryString?: string\n trackOpens?: Tracking\n trackClicks?: Tracking\n /** Emails are created off, as the dashboard creates them, unless this is true. */\n enabled?: boolean\n}\n\nexport class Autoresponders {\n constructor(private readonly transport: Transport) {}\n\n /** The brand's autoresponders, or one list's. */\n async list(opts: { listId?: string } = {}): Promise<Autoresponder[]> {\n const body = await this.transport.text({\n path: 'api/autoresponders/get-autoresponders.php',\n body: { list_id: opts.listId },\n retryable: true,\n })\n return parseJsonObject<{ autoresponders: Autoresponder[] }>(body, 'autoresponders').autoresponders\n }\n\n /** One autoresponder with its emails. */\n async get(autoresponderId: number): Promise<AutoresponderWithEmails> {\n const body = await this.transport.text({\n path: 'api/autoresponders/get-autoresponder-emails.php',\n body: { autoresponder_id: autoresponderId },\n retryable: true,\n })\n return parseJsonObject<AutoresponderWithEmails>(body, 'autoresponder')\n }\n\n async emailStats(emailId: number): Promise<AutoresponderEmailStats> {\n const body = await this.transport.text({\n path: 'api/autoresponders/autoresponder-stats.php',\n body: { email_id: emailId },\n retryable: true,\n })\n return parseJsonObject<AutoresponderEmailStats>(body, 'autoresponder email stats')\n }\n\n /** Creates an autoresponder with no emails; add them with `createEmail()`. Not retried. */\n async create(params: CreateAutoresponderParams): Promise<Autoresponder> {\n const body = await this.transport.text({\n path: 'api/autoresponders/create-autoresponder.php',\n body: { name: params.name, type: params.type, list_id: params.listId, custom_field: params.customField },\n })\n return parseJsonObject<Autoresponder>(body, 'autoresponder')\n }\n\n /** Adds an email to an autoresponder. Not retried: a repeat would add it twice. */\n async createEmail(autoresponderId: number, params: CreateAutoresponderEmailParams): Promise<AutoresponderEmail> {\n const body = await this.transport.text({\n path: 'api/autoresponders/create-autoresponder-email.php',\n body: {\n autoresponder_id: autoresponderId,\n subject: params.subject,\n html_text: params.htmlText,\n plain_text: params.plainText,\n preheader: params.preheader,\n when: params.when,\n from_name: params.fromName,\n from_email: params.fromEmail,\n reply_to: params.replyTo,\n query_string: params.queryString,\n track_opens: params.trackOpens,\n track_clicks: params.trackClicks,\n enabled: params.enabled ? 1 : undefined,\n },\n })\n return parseJsonObject<AutoresponderEmail>(body, 'autoresponder email')\n }\n}\n","/** 0 off, 1 on, 2 anonymous. */\nexport type Tracking = 0 | 1 | 2\n\n/** Opens, clicks, bounces and the rest, as campaign and autoresponder stats report them. */\nexport interface EngagementStats {\n recipients: number\n /** `rate` is a percentage of delivered (recipients less hard bounces). */\n opens: { total: number; unique: number; rate: number }\n /** `rate` is a percentage of recipients. */\n clicks: { total: number; unique: number; rate: number }\n bounces: { hard: number; soft: number }\n complaints: number\n unsubscribes: number\n links: { url: string; clicks: number; unique_clicks: number }[]\n}\n\n/** Comma-joined, or undefined for an empty or missing list so the field is not sent. */\nexport function joinIds(ids?: readonly (string | number)[]): string | undefined {\n return ids && ids.length > 0 ? ids.join(',') : undefined\n}\n","import { excerpt, isProseEmpty, ApiError } from '../errors.js'\nimport { parseJsonObject, parseNumberedRecords } from '../parse.js'\nimport type { Transport } from '../transport.js'\nimport { joinIds, type EngagementStats, type Tracking } from './shared.js'\n\nexport interface CreateCampaignParams {\n fromName: string\n fromEmail: string\n replyTo: string\n title: string\n subject: string\n htmlText: string\n plainText?: string\n /** Encrypted list ids. Needed, with `segmentIds`, only to send or schedule. */\n listIds?: string[]\n segmentIds?: (string | number)[]\n excludeListIds?: string[]\n excludeSegmentIds?: (string | number)[]\n queryString?: string\n trackOpens?: Tracking\n trackClicks?: Tracking\n /** Send immediately once created. */\n send?: boolean\n /** Schedule instead, e.g. \"June 15, 2027 6:05pm\". Minutes must be a multiple of 5. */\n scheduleDateTime?: string\n /** e.g. \"America/New_York\". Falls back to the brand's timezone. */\n scheduleTimezone?: string\n}\n\nexport interface CreateCampaignResult {\n outcome: 'created' | 'sending' | 'scheduled'\n /**\n * Only for a plain create: the sending and scheduling paths of create.php\n * answer with bare prose and no id.\n */\n campaignId?: string\n}\n\n/** As the brand's campaign list labels them. */\nexport const CAMPAIGN_STATUSES = ['draft', 'scheduled', 'preparing', 'sending', 'sent', 'paused'] as const\nexport type CampaignStatus = (typeof CAMPAIGN_STATUSES)[number]\n\n/** One row of `campaigns.list()`. */\nexport interface Campaign {\n id: number\n title: string\n subject: string\n status: CampaignStatus\n from_name: string\n from_email: string\n reply_to: string\n recipients: number\n to_send: number\n /** Unix seconds. */\n sent_at: number | null\n /** Unix seconds, only while scheduled. */\n scheduled_at: number | null\n timezone: string | null\n /** `ui`, or `api` for campaigns created by `emails.send()`. */\n source: string\n}\n\nexport interface CampaignDetail extends Campaign {\n preheader: string\n query_string: string\n track_opens: Tracking\n track_clicks: Tracking\n /** Encrypted, like the ids `lists.list()` returns. */\n list_ids: string[]\n exclude_list_ids: string[]\n segment_ids: number[]\n exclude_segment_ids: number[]\n web_version: string\n /** Only with `includeContent`. */\n html_text?: string\n plain_text?: string\n}\n\nexport interface CampaignStats extends EngagementStats {\n id: number\n status: CampaignStatus\n}\n\nexport interface ListCampaignsParams {\n /** 1-based. */\n page?: number\n /** At most 100. */\n limit?: number\n status?: CampaignStatus\n}\n\nexport const MAX_CAMPAIGNS_PAGE = 100\n\nexport interface Recipients {\n /** Encrypted list ids. At least one list or segment is required. */\n listIds?: string[]\n segmentIds?: (string | number)[]\n excludeListIds?: string[]\n excludeSegmentIds?: (string | number)[]\n /** Run every check and count the recipients, but send nothing. */\n dryRun?: boolean\n}\n\nexport interface SendCampaignResult {\n status: 'sending' | 'dry_run'\n campaign_id: number\n /** Unique active subscribers the campaign goes (or would go) to. */\n recipients: number\n}\n\nexport interface ScheduleCampaignParams extends Recipients {\n /** Anything PHP's strtotime() reads, e.g. \"2027-06-15 18:05\" or \"June 15, 2027 6:05pm\". */\n at: string\n /** e.g. \"Africa/Lagos\". Defaults to the account's timezone. */\n timezone?: string\n}\n\nexport interface ScheduleCampaignResult {\n status: 'scheduled' | 'dry_run'\n campaign_id: number\n recipients: number\n /** Unix seconds. */\n scheduled_at: number\n timezone: string\n}\n\n/** test-send.php refuses more than this many addresses per request. */\nexport const MAX_TEST_EMAILS = 5\n\nexport interface TestSendResult {\n campaign_id: number\n /** One per address, in the order sent. */\n results: { email: string; ok: boolean; error: string | null }[]\n}\n\n/** Fields `campaigns.update()` can change. Only those given change. */\nexport interface UpdateCampaignParams {\n title?: string\n subject?: string\n preheader?: string\n fromName?: string\n fromEmail?: string\n replyTo?: string\n htmlText?: string\n plainText?: string\n queryString?: string\n trackOpens?: Tracking\n trackClicks?: Tracking\n}\n\nexport const ACTIVITY_TYPES = ['opens', 'clicks', 'bounces', 'complaints', 'unsubscribes'] as const\nexport type ActivityType = (typeof ACTIVITY_TYPES)[number]\n\n/** One subscriber's part in a campaign. `opens`/`country` only for opens, `clicks`/`links` only for clicks. */\nexport interface CampaignActivity {\n email: string\n name: string\n /** Encrypted, like the ids `lists.list()` returns. */\n list_id: string\n opens?: number\n /** Where they first opened it, when known. */\n country?: string | null\n clicks?: number\n links?: string[]\n}\n\nexport interface CampaignActivityPage {\n campaign_id: number\n type: ActivityType\n page: number\n limit: number\n /** Subscribers across all pages. */\n total: number\n activity: CampaignActivity[]\n}\n\nexport interface CampaignActivityParams {\n type?: ActivityType\n page?: number\n /** At most 1000. */\n limit?: number\n}\n\nexport const MAX_ACTIVITY_PAGE = 1000\n\nexport class Campaigns {\n constructor(private readonly transport: Transport) {}\n\n /**\n * `api/campaigns/create.php` creates a draft, and optionally sends or\n * schedules it in the same call. To send a draft that already exists, see\n * `send()`.\n */\n async create(params: CreateCampaignParams): Promise<CreateCampaignResult> {\n const text = await this.transport.text({\n path: 'api/campaigns/create.php',\n body: {\n from_name: params.fromName,\n from_email: params.fromEmail,\n reply_to: params.replyTo,\n title: params.title,\n subject: params.subject,\n html_text: params.htmlText,\n plain_text: params.plainText,\n list_ids: joinIds(params.listIds),\n segment_ids: joinIds(params.segmentIds),\n exclude_list_ids: joinIds(params.excludeListIds),\n exclude_segments_ids: joinIds(params.excludeSegmentIds),\n query_string: params.queryString,\n track_opens: params.trackOpens,\n track_clicks: params.trackClicks,\n send_campaign: params.send ? 1 : undefined,\n schedule_date_time: params.scheduleDateTime,\n schedule_timezone: params.scheduleTimezone,\n // Asks for `{\"status\": ..., \"campaign_id\": ...}` instead of bare prose.\n json: 1,\n },\n })\n const trimmed = text.trim()\n\n if (trimmed === 'Campaign scheduled') return { outcome: 'scheduled' }\n if (trimmed === 'Campaign created and now sending') return { outcome: 'sending' }\n if (trimmed === 'Campaign created') return { outcome: 'created' }\n\n // The json=1 shape, built by hand server-side, so parsed leniently.\n if (trimmed.startsWith('{')) {\n try {\n const parsed = JSON.parse(trimmed) as { status?: string; campaign_id?: string }\n if (typeof parsed.status === 'string' && parsed.status.startsWith('Campaign created')) {\n return { outcome: 'created', campaignId: parsed.campaign_id }\n }\n } catch {\n // Reported below with the body intact.\n }\n }\n\n throw new ApiError('unexpected_response', `Unexpected response from campaigns/create: ${excerpt(trimmed)}`, {\n raw: text,\n })\n }\n\n /** One page, newest first. An empty page is an empty array, not an error. */\n async list(params: ListCampaignsParams = {}): Promise<Campaign[]> {\n const body = await this.transport.text({\n path: 'api/campaigns/get-campaigns.php',\n body: { page: params.page, limit: params.limit, status: params.status },\n retryable: true,\n })\n if (isProseEmpty(body)) return []\n return parseNumberedRecords<Campaign>(body, 'campaign')\n }\n\n /** Every page, fetched in order at the largest page size. */\n async listAll(params: Omit<ListCampaignsParams, 'page' | 'limit'> = {}): Promise<Campaign[]> {\n const all: Campaign[] = []\n for (let page = 1; ; page++) {\n const rows = await this.list({ ...params, page, limit: MAX_CAMPAIGNS_PAGE })\n all.push(...rows)\n if (rows.length < MAX_CAMPAIGNS_PAGE) return all\n }\n }\n\n async get(campaignId: number, opts: { includeContent?: boolean } = {}): Promise<CampaignDetail> {\n const body = await this.transport.text({\n path: 'api/campaigns/get-campaign.php',\n body: { campaign_id: campaignId, include_content: opts.includeContent ? 'yes' : undefined },\n retryable: true,\n })\n return parseJsonObject<CampaignDetail>(body, 'campaign')\n }\n\n async stats(campaignId: number): Promise<CampaignStats> {\n const body = await this.transport.text({\n path: 'api/campaigns/stats.php',\n body: { campaign_id: campaignId },\n retryable: true,\n })\n return parseJsonObject<CampaignStats>(body, 'campaign stats')\n }\n\n /**\n * `api/campaigns/send.php` sends an existing draft. It only ever claims a\n * draft, so a repeat after a lost response answers \"Campaign has already been\n * sent\" rather than sending twice. It is still not retried: that answer\n * would turn a success into a reported failure.\n */\n async send(campaignId: number, recipients: Recipients): Promise<SendCampaignResult> {\n const body = await this.transport.text({\n path: 'api/campaigns/send.php',\n body: { campaign_id: campaignId, ...recipientFields(recipients) },\n })\n return parseJsonObject<SendCampaignResult>(body, 'send result')\n }\n\n /**\n * Sends the campaign as a test to up to 5 addresses. Each request counts\n * against a per-brand limit shared with the dashboard (20 an hour), so it is\n * never retried.\n */\n async testSend(campaignId: number, emails: string[]): Promise<TestSendResult> {\n const body = await this.transport.text({\n path: 'api/campaigns/test-send.php',\n body: { campaign_id: campaignId, emails: emails.join(',') },\n })\n return parseJsonObject<TestSendResult>(body, 'test send result')\n }\n\n /**\n * Schedules a draft, or moves a scheduled campaign. The recipients are\n * reserved against the quota now and handed back by `unschedule()`.\n */\n async schedule(campaignId: number, params: ScheduleCampaignParams): Promise<ScheduleCampaignResult> {\n const body = await this.transport.text({\n path: 'api/campaigns/schedule.php',\n body: {\n campaign_id: campaignId,\n ...recipientFields(params),\n schedule_date_time: params.at,\n schedule_timezone: params.timezone,\n },\n })\n return parseJsonObject<ScheduleCampaignResult>(body, 'schedule result')\n }\n\n /** Back to a draft, with its quota reservation refunded. */\n async unschedule(campaignId: number): Promise<{ status: 'draft'; campaign_id: number }> {\n const body = await this.transport.text({ path: 'api/campaigns/unschedule.php', body: { campaign_id: campaignId } })\n return parseJsonObject<{ status: 'draft'; campaign_id: number }>(body, 'unschedule result')\n }\n\n /** Edits a draft or scheduled campaign and returns it as `get()` would. */\n async update(campaignId: number, changes: UpdateCampaignParams): Promise<CampaignDetail> {\n const body = await this.transport.text({\n path: 'api/campaigns/update-campaign.php',\n body: {\n campaign_id: campaignId,\n title: changes.title,\n subject: changes.subject,\n preheader: changes.preheader,\n from_name: changes.fromName,\n from_email: changes.fromEmail,\n reply_to: changes.replyTo,\n html_text: changes.htmlText,\n plain_text: changes.plainText,\n query_string: changes.queryString,\n track_opens: changes.trackOpens,\n track_clicks: changes.trackClicks,\n },\n })\n return parseJsonObject<CampaignDetail>(body, 'campaign')\n }\n\n /** A new draft copied from the campaign, in the same brand. */\n async duplicate(campaignId: number, opts: { title?: string } = {}): Promise<CampaignDetail> {\n const body = await this.transport.text({\n path: 'api/campaigns/duplicate-campaign.php',\n body: { campaign_id: campaignId, title: opts.title },\n })\n return parseJsonObject<CampaignDetail>(body, 'campaign')\n }\n\n /**\n * Deletes the campaign with its links and attachments; a sent one loses its\n * report. Refused while the campaign is preparing or sending.\n */\n async delete(campaignId: number): Promise<{ campaign_id: number; deleted: true }> {\n const body = await this.transport.text({ path: 'api/campaigns/delete-campaign.php', body: { campaign_id: campaignId } })\n return parseJsonObject<{ campaign_id: number; deleted: true }>(body, 'delete result')\n }\n\n /**\n * A page of who opened, clicked, bounced, complained or unsubscribed. Opens\n * and clicks are in the order subscribers first did them.\n */\n async activity(campaignId: number, params: CampaignActivityParams = {}): Promise<CampaignActivityPage> {\n const body = await this.transport.text({\n path: 'api/campaigns/get-activity.php',\n body: { campaign_id: campaignId, type: params.type, page: params.page, limit: params.limit },\n retryable: true,\n })\n return parseJsonObject<CampaignActivityPage>(body, 'campaign activity')\n }\n\n /** Every page of one activity type, at the largest page size. */\n async activityAll(campaignId: number, type: ActivityType): Promise<CampaignActivity[]> {\n const all: CampaignActivity[] = []\n for (let page = 1; ; page++) {\n const r = await this.activity(campaignId, { type, page, limit: MAX_ACTIVITY_PAGE })\n all.push(...r.activity)\n if (r.activity.length === 0 || page * MAX_ACTIVITY_PAGE >= r.total) return all\n }\n }\n}\n\nfunction recipientFields(r: Recipients) {\n return {\n list_ids: joinIds(r.listIds),\n segment_ids: joinIds(r.segmentIds),\n exclude_list_ids: joinIds(r.excludeListIds),\n exclude_segment_ids: joinIds(r.excludeSegmentIds),\n dry_run: r.dryRun ? 1 : undefined,\n }\n}\n","import { InvalidRequestError } from '../errors.js'\nimport type { Transport } from '../transport.js'\nimport type { Tracking } from './shared.js'\n\n/** send.php refuses more than this, and says so with a 400. */\nexport const MAX_RECIPIENTS = 250\n\nexport interface SendEmailParams {\n fromName: string\n fromEmail: string\n replyTo: string\n subject: string\n htmlText: string\n plainText?: string\n /** Addresses to send to, at most 250. Without `listIds` these are added to a new list. */\n to?: string[]\n /** Per-recipient values, referenced in the body as `[recipient.first]`. */\n recipientVariables?: Record<string, Record<string, unknown>>\n /** Encrypted list ids, as `lists.list()` returns them. */\n listIds?: string[]\n queryString?: string\n trackOpens?: Tracking\n trackClicks?: Tracking\n /** e.g. \"June 15, 2027 6:05pm\". */\n scheduleDateTime?: string\n /** e.g. \"Africa/Lagos\". Falls back to the brand's timezone. */\n scheduleTimezone?: string\n}\n\nexport interface SendEmailResult {\n message: string\n /** The campaign the send created behind the scenes. */\n campaignId?: string\n}\n\nexport interface SendTransactionalParams {\n toEmail: string\n subject: string\n /** At least one of `htmlBody` and `plainBody` is required. */\n htmlBody?: string\n plainBody?: string\n /** Each falls back to the brand's configured value when omitted. */\n fromName?: string\n fromEmail?: string\n replyTo?: string\n}\n\nexport interface SendTransactionalResult {\n message: string\n messageId?: string\n}\n\nexport class Emails {\n constructor(private readonly transport: Transport) {}\n\n /**\n * `api/emails/send.php`: a bulk send to addresses or lists. It creates a\n * campaign behind the scenes; the subject doubles as its title.\n */\n async send(params: SendEmailParams): Promise<SendEmailResult> {\n const to = params.to ?? []\n if (to.length === 0 && (params.listIds ?? []).length === 0) {\n throw new InvalidRequestError('recipients_missing', 'Nothing to send to.', {\n hint: 'Pass `to` addresses, or `listIds`.',\n })\n }\n // Checked here so a 251-address send fails before it is transmitted.\n if (to.length > MAX_RECIPIENTS) {\n throw new InvalidRequestError(\n 'too_many_recipients',\n `${to.length} recipients is more than the ${MAX_RECIPIENTS} this endpoint accepts.`,\n { hint: 'Split the send, or put the addresses on a list and pass `listIds`.' },\n )\n }\n\n const data = await this.transport.json<{ message?: string; data?: { campaign_id?: string } | null }>({\n path: 'api/emails/send.php',\n body: {\n from_name: params.fromName,\n from_email: params.fromEmail,\n reply_to: params.replyTo,\n subject: params.subject,\n html_text: params.htmlText,\n plain_text: params.plainText,\n // Both go over the wire as JSON strings inside a form body.\n to: to.length > 0 ? JSON.stringify(to) : undefined,\n 'recipient-variables': params.recipientVariables ? JSON.stringify(params.recipientVariables) : undefined,\n list_ids: params.listIds?.join(','),\n query_string: params.queryString,\n track_opens: params.trackOpens,\n track_clicks: params.trackClicks,\n schedule_date_time: params.scheduleDateTime,\n schedule_timezone: params.scheduleTimezone,\n },\n })\n return { message: data.message ?? 'Sent.', campaignId: data.data?.campaign_id }\n }\n\n /**\n * `api/emails/send-transactional.php`: one email, delivered immediately with\n * nothing queued. For OTPs, password resets and receipts.\n */\n async sendTransactional(params: SendTransactionalParams): Promise<SendTransactionalResult> {\n if (!params.htmlBody && !params.plainBody) {\n throw new InvalidRequestError('body_missing', 'A message body is required.', {\n hint: 'Pass `htmlBody`, `plainBody`, or both.',\n })\n }\n\n // The one endpoint that reads php://input: a form post gets \"Invalid JSON payload.\"\n const data = await this.transport.json<{ message?: string; message_id?: string }>({\n path: 'api/emails/send-transactional.php',\n encoding: 'json',\n body: {\n to_email: params.toEmail,\n subject: params.subject,\n html_body: params.htmlBody,\n plain_body: params.plainBody,\n from_name: params.fromName,\n from_email: params.fromEmail,\n reply_to: params.replyTo,\n },\n })\n return { message: data.message ?? 'Email sent successfully.', messageId: data.message_id }\n }\n}\n","import { ApiError, excerpt, isProseEmpty } from '../errors.js'\nimport { parseJsonObject, parseNumberedObject } from '../parse.js'\nimport type { Transport } from '../transport.js'\n\nexport interface List {\n /**\n * An encrypted value, not the number in the dashboard URL. Pass it back as\n * given wherever a list id is wanted.\n */\n id: string\n name: string\n}\n\nexport type OptIn = 'single' | 'double'\n\nexport type CustomFieldType = 'text' | 'date'\n\nexport interface CustomField {\n /** As used in personalization tags: `[Name,fallback=]`. */\n name: string\n /** As the API stores it. */\n type: 'Text' | 'Date'\n}\n\n/** One list from `get()`, `create()`, `update()` or the custom-field methods. */\nexport interface ListDetail extends List {\n opt_in: OptIn\n /** `active` is who a campaign goes to: confirmed, and not unsubscribed, bounced or complained. */\n subscribers: { active: number; unconfirmed: number; unsubscribed: number; bounced: number; complained: number }\n /** In the order subscribers' values are stored. */\n custom_fields: CustomField[]\n}\n\nexport class Lists {\n constructor(private readonly transport: Transport) {}\n\n /** The brand's lists. An empty brand is an empty array, not an error. */\n async list(opts: { includeHidden?: boolean } = {}): Promise<List[]> {\n const body = await this.transport.text({\n path: 'api/lists/get-lists.php',\n body: { include_hidden: opts.includeHidden ? 'yes' : 'no' },\n retryable: true,\n })\n if (isProseEmpty(body)) return []\n return parseNumberedObject(body, 'list')\n }\n\n async get(listId: string): Promise<ListDetail> {\n const body = await this.transport.text({ path: 'api/lists/get-list.php', body: { list_id: listId }, retryable: true })\n return parseJsonObject<ListDetail>(body, 'list')\n }\n\n /** Not retried: a repeat after a lost response would create a second list. */\n async create(params: { name: string; optIn?: OptIn }): Promise<ListDetail> {\n const body = await this.transport.text({\n path: 'api/lists/create.php',\n body: { name: params.name, opt_in: params.optIn },\n })\n return parseJsonObject<ListDetail>(body, 'list')\n }\n\n async update(listId: string, changes: { name?: string; optIn?: OptIn }): Promise<ListDetail> {\n const body = await this.transport.text({\n path: 'api/lists/update-list.php',\n body: { list_id: listId, name: changes.name, opt_in: changes.optIn },\n })\n return parseJsonObject<ListDetail>(body, 'list')\n }\n\n /**\n * Deletes the list with its subscribers, segments, autoresponders and rules.\n * Refused while a scheduled or sending campaign is going to it.\n */\n async delete(listId: string): Promise<{ id: string; deleted: true }> {\n const body = await this.transport.text({ path: 'api/lists/delete.php', body: { list_id: listId } })\n return parseJsonObject<{ id: string; deleted: true }>(body, 'delete result')\n }\n\n /**\n * Subscribers a campaign to this list would reach.\n *\n * Anything but a bare integer is a failure and is thrown, never reported as\n * 0: \"this list is empty\" is the one wrong answer a caller cannot detect.\n */\n async activeSubscriberCount(listId: string): Promise<number> {\n const body = await this.transport.text({\n path: 'api/subscribers/active-subscriber-count.php',\n body: { list_id: listId },\n retryable: true,\n })\n const trimmed = body.trim()\n if (!/^\\d+$/.test(trimmed)) {\n throw new ApiError('malformed_response', `Expected a subscriber count for list ${listId}, got: ${excerpt(trimmed)}`, {\n raw: body,\n })\n }\n return Number.parseInt(trimmed, 10)\n }\n\n /** Adds a field; every subscriber gets an empty value for it. Returns the list with its fields. */\n async addCustomField(listId: string, params: { name: string; type?: CustomFieldType }): Promise<ListDetail> {\n const body = await this.transport.text({\n path: 'api/lists/add-custom-field.php',\n body: { list_id: listId, name: params.name, type: params.type },\n })\n return parseJsonObject<ListDetail>(body, 'list')\n }\n\n /**\n * Renames a field, keeping subscribers' values. The autoresponders and\n * segment conditions that use it follow; tags already in campaigns keep the\n * old name.\n */\n async renameCustomField(listId: string, name: string, newName: string): Promise<ListDetail> {\n const body = await this.transport.text({\n path: 'api/lists/rename-custom-field.php',\n body: { list_id: listId, name, new_name: newName },\n })\n return parseJsonObject<ListDetail>(body, 'list')\n }\n\n /** Deletes a field and every subscriber's value for it. Refused while an autoresponder or segment uses it. */\n async deleteCustomField(listId: string, name: string): Promise<ListDetail> {\n const body = await this.transport.text({\n path: 'api/lists/delete-custom-field.php',\n body: { list_id: listId, name },\n })\n return parseJsonObject<ListDetail>(body, 'list')\n }\n}\n","import { isProseEmpty } from '../errors.js'\nimport { parseJsonObject, parseNumberedObject } from '../parse.js'\nimport type { Transport } from '../transport.js'\n\nexport interface Brand {\n id: string\n name: string\n}\n\nexport class Brands {\n constructor(private readonly transport: Transport) {}\n\n /**\n * The key's own brand: a brand key gets exactly one row. Empty, not an error,\n * when there is none.\n */\n async list(): Promise<Brand[]> {\n const body = await this.transport.text({ path: 'api/brands/get-brands.php', retryable: true })\n if (isProseEmpty(body)) return []\n return parseNumberedObject(body, 'brand')\n }\n}\n\nexport class Stats {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Emails sent across the whole installation (campaigns, autoresponders and\n * transactional), not a per-brand figure. Public and cached for 5 minutes.\n */\n async totalEmailsSent(): Promise<number> {\n const data = await this.transport.json<{ total_emails_sent?: number }>({\n path: 'api/stats/emails-sent.php',\n method: 'GET',\n anonymous: true,\n retryable: true,\n })\n return data.total_emails_sent ?? 0\n }\n}\n","import { isProseSuccess, unexpectedResponse } from '../errors.js'\nimport { parseJsonObject } from '../parse.js'\nimport type { Transport } from '../transport.js'\n\nexport interface SubscriberRef {\n /** The encrypted list id, as `lists.list()` returns it. */\n listId: string\n email: string\n}\n\nexport interface SubscribeParams extends SubscriberRef {\n name?: string\n /** Two-letter ISO 3166-1 code, e.g. NG. */\n country?: string\n ipAddress?: string\n referrer?: string\n /** Record GDPR consent for an EU signup. */\n gdpr?: boolean\n /** Add to a double opt-in list as single opt-in, skipping confirmation. */\n silent?: boolean\n /**\n * Custom fields, keyed by the name the list shows, e.g. `Birthday` for a\n * field whose tag is `[Birthday,fallback=]`. Spaces are taken out before\n * sending, as the server matches on.\n */\n fields?: Record<string, string>\n}\n\nexport type SubscribeOutcome = 'subscribed' | 'already_subscribed'\n\n/** Every subscriber is in exactly one of these; `active` is who a campaign sends to. */\nexport const SUBSCRIBER_STATUSES = ['active', 'unconfirmed', 'unsubscribed', 'bounced', 'complained'] as const\nexport type SubscriberStatus = (typeof SUBSCRIBER_STATUSES)[number]\n\nexport interface Subscriber {\n email: string\n name: string\n status: SubscriberStatus\n /** Unix seconds. */\n joined_at: number | null\n /** By field name, in the list's order. Dates are YYYY-MM-DD; empty values are null. */\n custom_fields: Record<string, string | null>\n}\n\nexport interface SubscribersPage {\n list_id: string\n status: SubscriberStatus | null\n page: number\n limit: number\n /** Subscribers matching the status filter, across all pages. */\n total: number\n subscribers: Subscriber[]\n}\n\nexport interface ListSubscribersParams {\n status?: SubscriberStatus\n /** 1-based. */\n page?: number\n /** At most 1000. */\n limit?: number\n}\n\nexport const MAX_SUBSCRIBERS_PAGE = 1000\n\nexport class Subscribers {\n constructor(private readonly transport: Transport) {}\n\n /**\n * Adds an address to a list. Posting one that is already there updates its\n * name and custom fields, and resolves `already_subscribed` rather than\n * throwing, so re-running an import is safe.\n */\n async subscribe(params: SubscribeParams): Promise<SubscribeOutcome> {\n const body: Record<string, string> = {}\n for (const [name, value] of Object.entries(params.fields ?? {})) body[name.replace(/ /g, '')] = value\n\n // Written after the custom fields so the documented names win a collision.\n body.email = params.email\n body.list = params.listId\n // Switches subscribe.php from rendering an HTML page to answering in plain text.\n body.boolean = 'true'\n if (params.name !== undefined) body.name = params.name\n if (params.country !== undefined) body.country = params.country\n if (params.ipAddress !== undefined) body.ipaddress = params.ipAddress\n if (params.referrer !== undefined) body.referrer = params.referrer\n if (params.gdpr) body.gdpr = 'true'\n if (params.silent) body.silent = 'true'\n\n // At the install root, not under api/.\n const text = await this.transport.text({ path: 'subscribe', body })\n if (text.trim().toLowerCase() === 'already subscribed.') return 'already_subscribed'\n if (isProseSuccess(text)) return 'subscribed'\n throw unexpectedResponse('subscribe', text)\n }\n\n /** Marks the address unsubscribed from the list. */\n async unsubscribe(ref: SubscriberRef): Promise<void> {\n const text = await this.transport.text({\n path: 'unsubscribe',\n body: { email: ref.email, list: ref.listId, boolean: 'true' },\n })\n if (!isProseSuccess(text)) throw unexpectedResponse('unsubscribe', text)\n }\n\n /** Removes the subscriber outright, rather than marking it unsubscribed. */\n async delete(ref: SubscriberRef): Promise<void> {\n const text = await this.transport.text({\n path: 'api/subscribers/delete.php',\n body: { list_id: ref.listId, email: ref.email },\n })\n if (!isProseSuccess(text)) throw unexpectedResponse('delete', text)\n }\n\n /**\n * One of Subscribed, Unsubscribed, Unconfirmed, Bounced, Soft bounced or\n * Complained. Returned as given, so a status added server-side surfaces\n * rather than being rejected.\n */\n async status(ref: SubscriberRef): Promise<string> {\n const text = await this.transport.text({\n path: 'api/subscribers/subscription-status.php',\n body: { email: ref.email, list_id: ref.listId },\n retryable: true,\n })\n const status = text.trim()\n if (status === '') throw unexpectedResponse('subscription-status', text)\n return status\n }\n\n /** One page of a list's subscribers, oldest first. */\n async list(listId: string, params: ListSubscribersParams = {}): Promise<SubscribersPage> {\n const body = await this.transport.text({\n path: 'api/subscribers/get-subscribers.php',\n body: { list_id: listId, status: params.status, page: params.page, limit: params.limit },\n retryable: true,\n })\n return parseJsonObject<SubscribersPage>(body, 'subscribers page')\n }\n\n /**\n * Every page, at the largest page size. Oldest first, so someone joining\n * mid-way lands on a later page instead of shifting the ones already read.\n */\n async listAll(listId: string, params: { status?: SubscriberStatus } = {}): Promise<Subscriber[]> {\n const all: Subscriber[] = []\n for (let page = 1; ; page++) {\n const r = await this.list(listId, { ...params, page, limit: MAX_SUBSCRIBERS_PAGE })\n all.push(...r.subscribers)\n if (r.subscribers.length < MAX_SUBSCRIBERS_PAGE) return all\n }\n }\n}\n","import { parseJsonObject } from '../parse.js'\nimport type { Transport } from '../transport.js'\n\n/** The editor the Skrybe dashboard opens a template in. */\nexport type TemplateEditor = 'html' | 'dragdrop'\n\nexport interface Template {\n id: number\n name: string\n from_name: string\n from_email: string\n reply_to: string\n editor: TemplateEditor\n /** Only from `get()` with `includeContent`, and from create and update. */\n html_text?: string\n plain_text?: string\n}\n\nexport interface TemplateFields {\n name?: string\n htmlText?: string\n plainText?: string\n fromName?: string\n fromEmail?: string\n replyTo?: string\n editor?: TemplateEditor\n}\n\nconst fieldsBody = (fields: TemplateFields) => ({\n name: fields.name,\n html_text: fields.htmlText,\n plain_text: fields.plainText,\n from_name: fields.fromName,\n from_email: fields.fromEmail,\n reply_to: fields.replyTo,\n editor: fields.editor,\n})\n\nexport class Templates {\n constructor(private readonly transport: Transport) {}\n\n /** The brand's templates by name, without their HTML. */\n async list(): Promise<Template[]> {\n const text = await this.transport.text({ path: 'api/templates/get-templates.php', retryable: true })\n return parseJsonObject<{ templates: Template[] }>(text, 'templates').templates\n }\n\n async get(templateId: number, opts: { includeContent?: boolean } = {}): Promise<Template> {\n const text = await this.transport.text({\n path: 'api/templates/get-template.php',\n body: { template_id: templateId, include_content: opts.includeContent ? 'yes' : undefined },\n retryable: true,\n })\n return parseJsonObject<Template>(text, 'template')\n }\n\n /** Not retried: a repeat would create a second template. */\n async create(fields: TemplateFields & { name: string; htmlText: string }): Promise<Template> {\n const text = await this.transport.text({ path: 'api/templates/create-template.php', body: fieldsBody(fields) })\n return parseJsonObject<Template>(text, 'template')\n }\n\n /** Only the fields given change. */\n async update(templateId: number, fields: TemplateFields): Promise<Template> {\n const text = await this.transport.text({\n path: 'api/templates/update-template.php',\n body: { template_id: templateId, ...fieldsBody(fields) },\n })\n return parseJsonObject<Template>(text, 'template')\n }\n\n /** Campaigns already made from it are unaffected. */\n async delete(templateId: number): Promise<{ template_id: number; deleted: true }> {\n const text = await this.transport.text({\n path: 'api/templates/delete-template.php',\n body: { template_id: templateId },\n })\n return parseJsonObject<{ template_id: number; deleted: true }>(text, 'delete result')\n }\n}\n","/**\n * Sent in the User-Agent. A constant rather than read from package.json, which\n * not every runtime can load; scripts/release.mjs keeps the two in step and a\n * test fails if they drift.\n */\nexport const VERSION = '2.0.0'\n","import {\n ApiError,\n ConnectionError,\n classifyProse,\n codeForStatus,\n createError,\n typeForStatus,\n type SkrybeError,\n} from './errors.js'\nimport { VERSION } from './version.js'\n\nexport type FetchLike = (input: string, init: RequestInit) => Promise<Response>\n\nexport interface TransportOptions {\n apiKey: string\n baseUrl: string\n timeoutMs: number\n maxRetries: number\n fetch: FetchLike\n}\n\nexport type Fields = Record<string, string | number | undefined>\n\nexport interface RequestOptions {\n /** Path relative to the install root, e.g. `api/lists/get-lists.php`. */\n path: string\n /** Form fields. `api_key` is added automatically unless `anonymous`. */\n body?: Fields\n /**\n * `form` posts application/x-www-form-urlencoded, which is what every\n * endpoint wants except api/emails/send-transactional.php, which reads\n * php://input and needs a raw JSON body.\n */\n encoding?: 'form' | 'json'\n method?: 'GET' | 'POST'\n /** Skip the api_key: only api/stats/emails-sent.php is public. */\n anonymous?: boolean\n /**\n * Opt in to retrying this request. Off by default because most endpoints\n * here create or send something, and none of them take an idempotency key:\n * a 504 from a send that actually landed becomes a second campaign on\n * retry. A dropped connection cannot be told apart from a response we never\n * saw, so only reads set this.\n */\n retryable?: boolean\n}\n\nconst RETRYABLE_STATUS = new Set([429, 502, 503, 504])\n\nexport class Transport {\n private readonly opts: TransportOptions\n\n constructor(opts: TransportOptions) {\n this.opts = { ...opts, baseUrl: opts.baseUrl.replace(/\\/+$/, '') }\n }\n\n /**\n * Perform a request and return the body as text.\n *\n * Legacy endpoints answer HTTP 200 with a prose error, so a 2xx here is not\n * proof of success: recognised prose errors are thrown, and anything else is\n * handed back for the caller to parse.\n */\n async text(opts: RequestOptions): Promise<string> {\n const response = await this.send(opts)\n const text = await response.text()\n\n if (!response.ok) throw errorFromResponse(response, text)\n\n const prose = classifyProse(text, response.status)\n if (prose) throw prose\n\n return text\n }\n\n /** Perform a request against one of the endpoints that returns real JSON. */\n async json<T>(opts: RequestOptions): Promise<T> {\n const response = await this.send(opts)\n const text = await response.text()\n\n if (!response.ok) throw errorFromResponse(response, text)\n\n try {\n return JSON.parse(text) as T\n } catch {\n throw new ApiError('malformed_response', 'The API returned a response that was not valid JSON.', {\n raw: text,\n status: response.status,\n })\n }\n }\n\n private async send(opts: RequestOptions): Promise<Response> {\n const method = opts.method ?? 'POST'\n const target = `${this.opts.baseUrl}/${opts.path.replace(/^\\/+/, '')}`\n\n const fields: Record<string, string> = {}\n for (const [key, value] of Object.entries(opts.body ?? {})) {\n if (value !== undefined) fields[key] = String(value)\n }\n if (!opts.anonymous) fields.api_key = this.opts.apiKey\n\n const headers: Record<string, string> = { 'User-Agent': `skrybe-sdk-ts/${VERSION}` }\n let url = target\n let body: string | undefined\n if (method === 'GET') {\n const query = new URLSearchParams(fields).toString()\n if (query !== '') url = `${target}?${query}`\n } else if (opts.encoding === 'json') {\n headers['Content-Type'] = 'application/json'\n body = JSON.stringify(fields)\n } else {\n headers['Content-Type'] = 'application/x-www-form-urlencoded'\n body = new URLSearchParams(fields).toString()\n }\n\n const attempts = opts.retryable ? this.opts.maxRetries + 1 : 1\n let lastError: unknown\n for (let attempt = 1; attempt <= attempts; attempt++) {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.opts.timeoutMs)\n\n try {\n const response = await this.opts.fetch(url, { method, headers, body, signal: controller.signal })\n if (RETRYABLE_STATUS.has(response.status) && attempt < attempts) {\n // Drain it so the connection can be reused.\n await response.text().catch(() => undefined)\n await delay(backoffMs(attempt, response.headers.get('retry-after')))\n continue\n }\n return response\n } catch (err) {\n lastError = err\n if (isAbort(err)) {\n throw new ConnectionError('timeout', `Request to ${target} timed out after ${this.opts.timeoutMs}ms.`, {\n cause: err,\n })\n }\n if (attempt < attempts) {\n await delay(backoffMs(attempt, null))\n continue\n }\n } finally {\n clearTimeout(timer)\n }\n }\n\n throw new ConnectionError(\n 'network_error',\n `Could not reach ${this.opts.baseUrl}: ${(lastError as Error | undefined)?.message ?? 'unknown error'}`,\n { cause: lastError },\n )\n }\n}\n\n/**\n * Build an error from a non-2xx response. The JSON endpoints use\n * `{ok:false,error:...}` (api/emails/send.php) or\n * `{status:\"error\",message:...}` (api/emails/send-transactional.php).\n */\nfunction errorFromResponse(response: Response, text: string): SkrybeError {\n let message = text.trim()\n try {\n const parsed = JSON.parse(text) as { error?: unknown; message?: unknown }\n const extracted = parsed.error ?? parsed.message\n if (typeof extracted === 'string' && extracted !== '') message = extracted\n } catch {\n // Not JSON: the prose body is already the best message we have.\n }\n if (message === '') message = `HTTP ${response.status} from the Skrybe API.`\n\n return createError(typeForStatus(response.status), codeForStatus(response.status), message, {\n raw: text,\n status: response.status,\n })\n}\n\n/** Honour Retry-After when present, else exponential backoff with jitter. */\nfunction backoffMs(attempt: number, retryAfter: string | null): number {\n if (retryAfter) {\n const seconds = Number.parseInt(retryAfter, 10)\n if (!Number.isNaN(seconds) && seconds >= 0) return Math.min(seconds * 1000, 30_000)\n }\n return 2 ** (attempt - 1) * 500 + Math.floor(Math.random() * 250)\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nfunction isAbort(err: unknown): boolean {\n return err instanceof Error && err.name === 'AbortError'\n}\n","import { InvalidRequestError } from './errors.js'\nimport { Autoresponders } from './resources/autoresponders.js'\nimport { Campaigns } from './resources/campaigns.js'\nimport { Emails } from './resources/emails.js'\nimport { Lists } from './resources/lists.js'\nimport { Brands, Stats } from './resources/misc.js'\nimport { Subscribers } from './resources/subscribers.js'\nimport { Templates } from './resources/templates.js'\nimport { Transport, type FetchLike } from './transport.js'\n\nexport const DEFAULT_BASE_URL = 'https://dashboard.skry.be'\n\nexport interface SkrybeOptions {\n /** A brand's API key, from Settings in the Skrybe dashboard. Keep it server-side. */\n apiKey: string\n /** Defaults to https://dashboard.skry.be. */\n baseUrl?: string\n /** Per attempt. Defaults to 30 seconds. */\n timeoutMs?: number\n /**\n * Extra attempts for reads after a dropped connection or a 429/502/503/504.\n * Defaults to 2. Writes are never retried: the API has no idempotency keys,\n * so a retried send could go out twice.\n */\n maxRetries?: number\n /**\n * The fetch to use, defaulting to the global one. Pass your own to route\n * through a proxy or trust a corporate CA, e.g. undici's fetch with a\n * dispatcher. There is deliberately no option to skip TLS verification.\n */\n fetch?: FetchLike\n}\n\nexport class Skrybe {\n readonly emails: Emails\n readonly campaigns: Campaigns\n readonly lists: Lists\n readonly subscribers: Subscribers\n readonly templates: Templates\n readonly autoresponders: Autoresponders\n readonly brands: Brands\n readonly stats: Stats\n\n constructor(options: SkrybeOptions) {\n if (!options?.apiKey) {\n throw new InvalidRequestError('api_key_missing', 'An apiKey is required.', {\n hint: 'Generate one in the Skrybe dashboard under Settings.',\n })\n }\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (typeof fetchImpl !== 'function') {\n throw new InvalidRequestError('fetch_unavailable', 'No global fetch is available in this runtime.', {\n hint: 'Use Node.js 18 or newer, or pass a fetch implementation as `fetch`.',\n })\n }\n\n const transport = new Transport({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,\n timeoutMs: options.timeoutMs ?? 30_000,\n maxRetries: Math.max(0, options.maxRetries ?? 2),\n // Bound so a fetch that relies on `this` being globalThis still works.\n fetch: options.fetch ?? ((input, init) => globalThis.fetch(input, init)),\n })\n\n this.emails = new Emails(transport)\n this.campaigns = new Campaigns(transport)\n this.lists = new Lists(transport)\n this.subscribers = new Subscribers(transport)\n this.templates = new Templates(transport)\n this.autoresponders = new Autoresponders(transport)\n this.brands = new Brands(transport)\n this.stats = new Stats(transport)\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,14 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skrybe/sdk",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Official TypeScript/JavaScript SDK for Skrybe API",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Official TypeScript/JavaScript SDK for the Skrybe email marketing API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
7
31
|
"scripts": {
|
|
8
|
-
"build": "
|
|
9
|
-
"
|
|
10
|
-
"test": "
|
|
11
|
-
"prepublishOnly": "npm run build"
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
34
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
35
|
+
"prepublishOnly": "npm run build",
|
|
36
|
+
"release:prepare": "node scripts/release.mjs prepare"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.0.0",
|
|
40
|
+
"tsup": "^8.3.5",
|
|
41
|
+
"tsx": "^4.23.13",
|
|
42
|
+
"typescript": "^5.6.3"
|
|
12
43
|
},
|
|
13
44
|
"keywords": [
|
|
14
45
|
"skrybe",
|
|
@@ -19,22 +50,17 @@
|
|
|
19
50
|
],
|
|
20
51
|
"author": "Skrybe",
|
|
21
52
|
"license": "MIT",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/skry-be/skrybe-sdk-ts.git",
|
|
56
|
+
"directory": "typescript"
|
|
24
57
|
},
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
"
|
|
58
|
+
"homepage": "https://github.com/skry-be/skrybe-sdk-ts/tree/main/typescript#readme",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/skry-be/skrybe-sdk-ts/issues"
|
|
28
61
|
},
|
|
29
|
-
"files": [
|
|
30
|
-
"dist",
|
|
31
|
-
"README.md"
|
|
32
|
-
],
|
|
33
62
|
"publishConfig": {
|
|
34
|
-
"access": "public"
|
|
35
|
-
|
|
36
|
-
"repository": {
|
|
37
|
-
"type": "git",
|
|
38
|
-
"url": "https://github.com/skrybe/sdk"
|
|
63
|
+
"access": "public",
|
|
64
|
+
"provenance": true
|
|
39
65
|
}
|
|
40
66
|
}
|