@tiempo/sdk 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +269 -2
  3. package/dist/cjs/client.d.ts +24 -0
  4. package/dist/cjs/client.js +42 -0
  5. package/dist/cjs/core.d.ts +72 -0
  6. package/dist/cjs/core.js +187 -0
  7. package/dist/cjs/crypto.d.ts +5 -0
  8. package/dist/cjs/crypto.js +29 -0
  9. package/dist/cjs/errors.d.ts +86 -0
  10. package/dist/cjs/errors.js +134 -0
  11. package/dist/cjs/events.d.ts +23 -0
  12. package/dist/cjs/events.js +10 -0
  13. package/dist/cjs/generated/events.d.ts +51 -0
  14. package/dist/cjs/generated/events.js +51 -0
  15. package/dist/cjs/generated/meta.d.ts +4 -0
  16. package/dist/cjs/generated/meta.js +8 -0
  17. package/dist/cjs/generated/openapi.d.ts +3721 -0
  18. package/dist/cjs/generated/openapi.js +2 -0
  19. package/dist/cjs/generated/resources.d.ts +416 -0
  20. package/dist/cjs/generated/resources.js +621 -0
  21. package/dist/cjs/generated/types.d.ts +134 -0
  22. package/dist/cjs/generated/types.js +3 -0
  23. package/dist/cjs/index.d.ts +16 -0
  24. package/dist/cjs/index.js +49 -0
  25. package/dist/cjs/list.d.ts +43 -0
  26. package/dist/cjs/list.js +78 -0
  27. package/dist/cjs/package.json +1 -0
  28. package/dist/cjs/query.d.ts +8 -0
  29. package/dist/cjs/query.js +47 -0
  30. package/dist/cjs/shared.d.ts +7 -0
  31. package/dist/cjs/shared.js +2 -0
  32. package/dist/cjs/webhooks.d.ts +34 -0
  33. package/dist/cjs/webhooks.js +115 -0
  34. package/dist/esm/client.d.ts +24 -0
  35. package/dist/esm/client.js +38 -0
  36. package/dist/esm/core.d.ts +72 -0
  37. package/dist/esm/core.js +181 -0
  38. package/dist/esm/crypto.d.ts +5 -0
  39. package/dist/esm/crypto.js +26 -0
  40. package/dist/esm/errors.d.ts +86 -0
  41. package/dist/esm/errors.js +120 -0
  42. package/dist/esm/events.d.ts +23 -0
  43. package/dist/esm/events.js +8 -0
  44. package/dist/esm/generated/events.d.ts +51 -0
  45. package/dist/esm/generated/events.js +48 -0
  46. package/dist/esm/generated/meta.d.ts +4 -0
  47. package/dist/esm/generated/meta.js +5 -0
  48. package/dist/esm/generated/openapi.d.ts +3721 -0
  49. package/dist/esm/generated/openapi.js +1 -0
  50. package/dist/esm/generated/resources.d.ts +416 -0
  51. package/dist/esm/generated/resources.js +603 -0
  52. package/dist/esm/generated/types.d.ts +134 -0
  53. package/dist/esm/generated/types.js +2 -0
  54. package/dist/esm/index.d.ts +16 -0
  55. package/dist/esm/index.js +10 -0
  56. package/dist/esm/list.d.ts +43 -0
  57. package/dist/esm/list.js +74 -0
  58. package/dist/esm/package.json +1 -0
  59. package/dist/esm/query.d.ts +8 -0
  60. package/dist/esm/query.js +43 -0
  61. package/dist/esm/shared.d.ts +7 -0
  62. package/dist/esm/shared.js +1 -0
  63. package/dist/esm/webhooks.d.ts +34 -0
  64. package/dist/esm/webhooks.js +109 -0
  65. package/package.json +74 -4
@@ -0,0 +1,72 @@
1
+ import { ListPromise } from './list.js';
2
+ /** Client options. */
3
+ export interface TiempoConfig {
4
+ /**
5
+ * Where the API lives. Defaults to {@link DEFAULT_BASE_URL}, Tiempo itself; set it for the test
6
+ * deployment (`https://api.uat.tiempo.one`) or a local one (`http://localhost:8080`).
7
+ */
8
+ baseUrl?: string;
9
+ /**
10
+ * The `Tiempo-Version` to send. Defaults to {@link API_VERSION}, the version
11
+ * this SDK's types describe; change it only if you know the shapes match.
12
+ */
13
+ apiVersion?: string;
14
+ /** Act for this business (`acct_…`) on every request: sends `Tiempo-Account`. */
15
+ account?: string;
16
+ /** Retries after the first attempt for retryable failures. Default 2. */
17
+ maxRetries?: number;
18
+ /** Per-attempt timeout in milliseconds. Default 30000. */
19
+ timeout?: number;
20
+ /** A `fetch` implementation. Defaults to the global one. */
21
+ fetch?: typeof fetch;
22
+ }
23
+ /** Per-request options; the last argument of every method. */
24
+ export interface RequestOptions {
25
+ /** Act for this business (`acct_…`), overriding the client's account. */
26
+ account?: string;
27
+ /** Idempotency key for a POST. Default: a fresh UUID per call, reused across its retries. */
28
+ idempotencyKey?: string;
29
+ /** Overrides the client's `maxRetries` for this call. */
30
+ maxRetries?: number;
31
+ /** Overrides the client's per-attempt `timeout` (ms) for this call. */
32
+ timeout?: number;
33
+ /** Aborts the call (including any pending retry). The promise rejects with the signal's reason. */
34
+ signal?: AbortSignal;
35
+ }
36
+ /** An HTTP call, as generated resource methods describe it. */
37
+ export interface RequestSpec {
38
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
39
+ path: string;
40
+ query?: object | undefined;
41
+ body?: unknown;
42
+ options?: RequestOptions | undefined;
43
+ /** A POST that only reads (availability search): sent without an Idempotency-Key. */
44
+ readOnly?: boolean;
45
+ }
46
+ /** What generated resource classes call. */
47
+ export interface Core {
48
+ request<T>(spec: RequestSpec): Promise<T>;
49
+ list<T>(spec: RequestSpec, paginates: boolean): ListPromise<T>;
50
+ }
51
+ export interface ResolvedConfig {
52
+ apiKey: string;
53
+ baseUrl: string;
54
+ apiVersion: string;
55
+ account: string | undefined;
56
+ maxRetries: number;
57
+ timeout: number;
58
+ fetch: typeof fetch | undefined;
59
+ }
60
+ /** Tiempo's API, where the SDK points unless `baseUrl` says otherwise. */
61
+ export declare const DEFAULT_BASE_URL = "https://api.tiempo.one";
62
+ /** Backoff before retry `attempt` (0-based): 0.5s × 2^n, capped at 8s, jittered to 50–100%. */
63
+ export declare function backoffMs(attempt: number, random?: () => number): number;
64
+ /** Parses `Retry-After` (seconds or an HTTP date) into ms; undefined if absent or unparseable. */
65
+ export declare function retryAfterMs(header: string | null | undefined, now?: number): number | undefined;
66
+ export declare class HttpClient implements Core {
67
+ readonly config: ResolvedConfig;
68
+ constructor(config: ResolvedConfig);
69
+ static resolve(apiKey: string, config: TiempoConfig | undefined): ResolvedConfig;
70
+ list<T>(spec: RequestSpec, paginates: boolean): ListPromise<T>;
71
+ request<T>(spec: RequestSpec): Promise<T>;
72
+ }
@@ -0,0 +1,181 @@
1
+ import { webCrypto } from './crypto.js';
2
+ import { TiempoAPIError, TiempoConnectionError, errorFromResponse } from './errors.js';
3
+ import { API_VERSION, SDK_VERSION } from './generated/meta.js';
4
+ import { ListPromise } from './list.js';
5
+ import { encodeQuery } from './query.js';
6
+ /** Tiempo's API, where the SDK points unless `baseUrl` says otherwise. */
7
+ export const DEFAULT_BASE_URL = 'https://api.tiempo.one';
8
+ const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);
9
+ const MAX_RETRY_AFTER_SECONDS = 60;
10
+ /** Backoff before retry `attempt` (0-based): 0.5s × 2^n, capped at 8s, jittered to 50–100%. */
11
+ export function backoffMs(attempt, random = Math.random) {
12
+ const base = Math.min(8000, 500 * 2 ** attempt);
13
+ return Math.round(base * (0.5 + random() * 0.5));
14
+ }
15
+ /** Parses `Retry-After` (seconds or an HTTP date) into ms; undefined if absent or unparseable. */
16
+ export function retryAfterMs(header, now = Date.now()) {
17
+ if (!header)
18
+ return undefined;
19
+ const trimmed = header.trim();
20
+ let seconds;
21
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
22
+ seconds = Number(trimmed);
23
+ }
24
+ else {
25
+ const at = Date.parse(trimmed);
26
+ if (Number.isNaN(at))
27
+ return undefined;
28
+ seconds = Math.max(0, (at - now) / 1000);
29
+ }
30
+ return Math.round(Math.min(seconds, MAX_RETRY_AFTER_SECONDS) * 1000);
31
+ }
32
+ function headersToRecord(headers) {
33
+ const out = {};
34
+ headers.forEach((value, key) => {
35
+ out[key.toLowerCase()] = value;
36
+ });
37
+ return out;
38
+ }
39
+ function sleep(ms, signal) {
40
+ return new Promise((resolve, reject) => {
41
+ if (signal?.aborted)
42
+ return reject(signal.reason);
43
+ const timer = setTimeout(() => {
44
+ signal?.removeEventListener('abort', onAbort);
45
+ resolve();
46
+ }, ms);
47
+ const onAbort = () => {
48
+ clearTimeout(timer);
49
+ reject(signal?.reason);
50
+ };
51
+ signal?.addEventListener('abort', onAbort, { once: true });
52
+ });
53
+ }
54
+ const isBrowser = typeof globalThis.window !== 'undefined';
55
+ export class HttpClient {
56
+ config;
57
+ constructor(config) {
58
+ this.config = config;
59
+ }
60
+ static resolve(apiKey, config) {
61
+ if (typeof apiKey !== 'string' || apiKey.trim() === '') {
62
+ throw new TypeError('Tiempo: an API key is required, e.g. new Tiempo(process.env.TIEMPO_API_KEY)');
63
+ }
64
+ const baseUrl = config?.baseUrl ?? DEFAULT_BASE_URL;
65
+ if (typeof baseUrl !== 'string' || !/^https?:\/\//.test(baseUrl)) {
66
+ throw new TypeError("Tiempo: `baseUrl` must be an http(s) URL, e.g. new Tiempo(key, { baseUrl: 'http://localhost:8080' })");
67
+ }
68
+ return {
69
+ apiKey,
70
+ baseUrl: baseUrl.replace(/\/+$/, '').replace(/\/v1$/, ''),
71
+ apiVersion: config?.apiVersion ?? API_VERSION,
72
+ account: config?.account,
73
+ maxRetries: config?.maxRetries ?? 2,
74
+ timeout: config?.timeout ?? 30_000,
75
+ fetch: config?.fetch,
76
+ };
77
+ }
78
+ list(spec, paginates) {
79
+ return new ListPromise((params) => this.request({ ...spec, query: params }), { ...spec.query }, paginates);
80
+ }
81
+ async request(spec) {
82
+ const opts = spec.options ?? {};
83
+ const cfg = this.config;
84
+ const doFetch = cfg.fetch ?? globalThis.fetch;
85
+ if (typeof doFetch !== 'function') {
86
+ throw new TypeError('Tiempo: no global fetch; pass one with { fetch } (Node 18+ has it built in)');
87
+ }
88
+ const maxRetries = Math.max(0, opts.maxRetries ?? cfg.maxRetries);
89
+ const timeout = opts.timeout ?? cfg.timeout;
90
+ const account = opts.account ?? cfg.account;
91
+ const url = cfg.baseUrl + spec.path + encodeQuery(spec.query);
92
+ const headers = {
93
+ Accept: 'application/json',
94
+ Authorization: `Bearer ${cfg.apiKey}`,
95
+ 'Tiempo-Version': cfg.apiVersion,
96
+ };
97
+ if (!isBrowser)
98
+ headers['User-Agent'] = `tiempo-typescript/${SDK_VERSION}`;
99
+ if (account)
100
+ headers['Tiempo-Account'] = account;
101
+ let body;
102
+ if (spec.body !== undefined) {
103
+ headers['Content-Type'] = 'application/json';
104
+ body = JSON.stringify(spec.body);
105
+ }
106
+ if (spec.method === 'POST' && !spec.readOnly) {
107
+ headers['Idempotency-Key'] = opts.idempotencyKey ?? (await webCrypto()).randomUUID();
108
+ }
109
+ for (let attempt = 0;; attempt++) {
110
+ if (opts.signal?.aborted)
111
+ throw opts.signal.reason;
112
+ const canRetry = attempt < maxRetries;
113
+ let response;
114
+ let text;
115
+ const controller = new AbortController();
116
+ let timedOut = false;
117
+ const timer = setTimeout(() => {
118
+ timedOut = true;
119
+ controller.abort();
120
+ }, timeout);
121
+ const onAbort = () => controller.abort(opts.signal?.reason);
122
+ opts.signal?.addEventListener('abort', onAbort, { once: true });
123
+ try {
124
+ const init = { method: spec.method, headers, signal: controller.signal };
125
+ if (body !== undefined)
126
+ init.body = body;
127
+ response = await doFetch(url, init);
128
+ text = await response.text();
129
+ }
130
+ catch (err) {
131
+ if (opts.signal?.aborted)
132
+ throw opts.signal.reason;
133
+ if (canRetry) {
134
+ await sleep(backoffMs(attempt), opts.signal);
135
+ continue;
136
+ }
137
+ throw new TiempoConnectionError({
138
+ message: timedOut
139
+ ? `Request to Tiempo timed out after ${timeout}ms (${spec.method} ${spec.path})`
140
+ : `Could not connect to Tiempo (${spec.method} ${spec.path}): ${err?.message ?? err}`,
141
+ cause: err,
142
+ });
143
+ }
144
+ finally {
145
+ clearTimeout(timer);
146
+ opts.signal?.removeEventListener('abort', onAbort);
147
+ }
148
+ const responseHeaders = headersToRecord(response.headers);
149
+ let parsed = text;
150
+ if (text.length > 0) {
151
+ try {
152
+ parsed = JSON.parse(text);
153
+ }
154
+ catch {
155
+ // keep text
156
+ }
157
+ }
158
+ if (response.ok) {
159
+ if (typeof parsed === 'string' && text.length > 0) {
160
+ // A 2xx that wasn't JSON: don't hand back a string typed as an object.
161
+ throw new TiempoAPIError({
162
+ message: `Expected a JSON response from Tiempo, got: ${text.slice(0, 200)}`,
163
+ statusCode: response.status,
164
+ headers: responseHeaders,
165
+ raw: text,
166
+ });
167
+ }
168
+ return (text.length > 0 ? parsed : undefined);
169
+ }
170
+ const error = errorFromResponse(response.status, responseHeaders, parsed);
171
+ const retryable = RETRYABLE_STATUS.has(response.status) ||
172
+ (response.status === 409 && error.code === 'idempotency_key_in_use');
173
+ if (retryable && canRetry) {
174
+ const delay = retryAfterMs(response.headers.get('retry-after')) ?? backoffMs(attempt);
175
+ await sleep(delay, opts.signal);
176
+ continue;
177
+ }
178
+ throw error;
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Web Crypto: the global `crypto` (Node 19+, browsers, edge runtimes), else
3
+ * `node:crypto`'s `webcrypto` (Node 18).
4
+ */
5
+ export declare function webCrypto(): Promise<Crypto>;
@@ -0,0 +1,26 @@
1
+ let cached;
2
+ /**
3
+ * Web Crypto: the global `crypto` (Node 19+, browsers, edge runtimes), else
4
+ * `node:crypto`'s `webcrypto` (Node 18).
5
+ */
6
+ export async function webCrypto() {
7
+ if (cached)
8
+ return cached;
9
+ const g = globalThis.crypto;
10
+ if (g?.subtle && typeof g.randomUUID === 'function') {
11
+ cached = g;
12
+ return g;
13
+ }
14
+ const specifier = 'node:crypto';
15
+ try {
16
+ const mod = (await import(specifier));
17
+ if (mod.webcrypto?.subtle) {
18
+ cached = mod.webcrypto;
19
+ return cached;
20
+ }
21
+ }
22
+ catch {
23
+ // fall through
24
+ }
25
+ throw new Error('Tiempo: Web Crypto is not available in this runtime (need Node 18+ or a runtime with globalThis.crypto).');
26
+ }
@@ -0,0 +1,86 @@
1
+ import type { Conflict } from './generated/types.js';
2
+ /**
3
+ * The `error` object in Tiempo's error envelope. Besides these four, an error can carry fields of
4
+ * its own, merged straight in: `conflicts` on `booking_conflict` and `session_conflicted`, `occurrences` on
5
+ * `series_update_conflict`, `seats_available` on `seats_unavailable`.
6
+ */
7
+ export interface TiempoErrorBody {
8
+ type?: string;
9
+ code?: string;
10
+ message?: string;
11
+ param?: string | null;
12
+ [key: string]: unknown;
13
+ }
14
+ /** A series occurrence that doesn't fit an edit (`series_update_conflict`). */
15
+ export interface UnplaceableOccurrence {
16
+ booking: string;
17
+ occurrence_key: string;
18
+ conflicts: Conflict[];
19
+ }
20
+ export interface TiempoErrorInit {
21
+ message: string;
22
+ type?: string | undefined;
23
+ code?: string | undefined;
24
+ param?: string | undefined;
25
+ details?: Record<string, unknown> | undefined;
26
+ statusCode?: number | undefined;
27
+ headers?: Record<string, string> | undefined;
28
+ raw?: unknown;
29
+ cause?: unknown;
30
+ }
31
+ /** Base class for every error the SDK throws. */
32
+ export declare class TiempoError extends Error {
33
+ /** The server's error `type`, for example `invalid_request_error`. */
34
+ readonly type: string | undefined;
35
+ /** A machine-readable code, for example `parameter_invalid`. */
36
+ readonly code: string | undefined;
37
+ /** The request parameter the error is about, when there is one. */
38
+ readonly param: string | undefined;
39
+ /**
40
+ * The error's own fields beyond type, code, message and param, for example `{ conflicts: [...] }`.
41
+ * Empty when there are none. {@link TiempoConflictError} has typed shortcuts.
42
+ */
43
+ readonly details: Record<string, unknown>;
44
+ /** HTTP status, when the server answered. */
45
+ readonly statusCode: number | undefined;
46
+ /** Response headers (lower-case names), when the server answered. */
47
+ readonly headers: Record<string, string>;
48
+ /** The parsed response body (or text), when the server answered. */
49
+ readonly raw: unknown;
50
+ constructor(init: TiempoErrorInit);
51
+ }
52
+ /** 400/404/422: the request was invalid (bad params, unknown id, …). */
53
+ export declare class TiempoInvalidRequestError extends TiempoError {
54
+ }
55
+ /** 401: missing or bad API key. */
56
+ export declare class TiempoAuthenticationError extends TiempoError {
57
+ }
58
+ /** 403: the key can't do this (for example live mode not activated). */
59
+ export declare class TiempoPermissionError extends TiempoError {
60
+ }
61
+ /** 409: the request conflicts with current state (slot taken, already cancelled, …). */
62
+ export declare class TiempoConflictError extends TiempoError {
63
+ /** Why the time isn't available, per segment (`booking_conflict`, `session_conflicted`). Empty for other conflicts. */
64
+ get conflicts(): Conflict[];
65
+ /** Occurrences a series edit couldn't place (`series_update_conflict`). */
66
+ get occurrences(): UnplaceableOccurrence[];
67
+ /** Seats still free in the session (`seats_unavailable`). */
68
+ get seatsAvailable(): number | undefined;
69
+ }
70
+ /** An `Idempotency-Key` was reused with different params, or is still in flight. */
71
+ export declare class TiempoIdempotencyError extends TiempoError {
72
+ }
73
+ /** 429: too many requests. */
74
+ export declare class TiempoRateLimitError extends TiempoError {
75
+ }
76
+ /** 5xx: something went wrong on Tiempo's side. */
77
+ export declare class TiempoAPIError extends TiempoError {
78
+ }
79
+ /** The request never got a response: network failure or timeout. */
80
+ export declare class TiempoConnectionError extends TiempoError {
81
+ }
82
+ /** A webhook's `Tiempo-Signature` didn't verify. */
83
+ export declare class TiempoSignatureVerificationError extends TiempoError {
84
+ }
85
+ /** Builds the right error subclass from an HTTP error response. */
86
+ export declare function errorFromResponse(statusCode: number, headers: Record<string, string>, raw: unknown): TiempoError;
@@ -0,0 +1,120 @@
1
+ /** Base class for every error the SDK throws. */
2
+ export class TiempoError extends Error {
3
+ /** The server's error `type`, for example `invalid_request_error`. */
4
+ type;
5
+ /** A machine-readable code, for example `parameter_invalid`. */
6
+ code;
7
+ /** The request parameter the error is about, when there is one. */
8
+ param;
9
+ /**
10
+ * The error's own fields beyond type, code, message and param, for example `{ conflicts: [...] }`.
11
+ * Empty when there are none. {@link TiempoConflictError} has typed shortcuts.
12
+ */
13
+ details;
14
+ /** HTTP status, when the server answered. */
15
+ statusCode;
16
+ /** Response headers (lower-case names), when the server answered. */
17
+ headers;
18
+ /** The parsed response body (or text), when the server answered. */
19
+ raw;
20
+ constructor(init) {
21
+ super(init.message, init.cause !== undefined ? { cause: init.cause } : undefined);
22
+ this.name = new.target.name;
23
+ this.type = init.type;
24
+ this.code = init.code;
25
+ this.param = init.param;
26
+ this.details = init.details ?? {};
27
+ this.statusCode = init.statusCode;
28
+ this.headers = init.headers ?? {};
29
+ this.raw = init.raw;
30
+ }
31
+ }
32
+ /** 400/404/422: the request was invalid (bad params, unknown id, …). */
33
+ export class TiempoInvalidRequestError extends TiempoError {
34
+ }
35
+ /** 401: missing or bad API key. */
36
+ export class TiempoAuthenticationError extends TiempoError {
37
+ }
38
+ /** 403: the key can't do this (for example live mode not activated). */
39
+ export class TiempoPermissionError extends TiempoError {
40
+ }
41
+ /** 409: the request conflicts with current state (slot taken, already cancelled, …). */
42
+ export class TiempoConflictError extends TiempoError {
43
+ /** Why the time isn't available, per segment (`booking_conflict`, `session_conflicted`). Empty for other conflicts. */
44
+ get conflicts() {
45
+ return Array.isArray(this.details.conflicts) ? this.details.conflicts : [];
46
+ }
47
+ /** Occurrences a series edit couldn't place (`series_update_conflict`). */
48
+ get occurrences() {
49
+ return Array.isArray(this.details.occurrences) ? this.details.occurrences : [];
50
+ }
51
+ /** Seats still free in the session (`seats_unavailable`). */
52
+ get seatsAvailable() {
53
+ return typeof this.details.seats_available === 'number' ? this.details.seats_available : undefined;
54
+ }
55
+ }
56
+ /** An `Idempotency-Key` was reused with different params, or is still in flight. */
57
+ export class TiempoIdempotencyError extends TiempoError {
58
+ }
59
+ /** 429: too many requests. */
60
+ export class TiempoRateLimitError extends TiempoError {
61
+ }
62
+ /** 5xx: something went wrong on Tiempo's side. */
63
+ export class TiempoAPIError extends TiempoError {
64
+ }
65
+ /** The request never got a response: network failure or timeout. */
66
+ export class TiempoConnectionError extends TiempoError {
67
+ }
68
+ /** A webhook's `Tiempo-Signature` didn't verify. */
69
+ export class TiempoSignatureVerificationError extends TiempoError {
70
+ }
71
+ const BY_TYPE = {
72
+ invalid_request_error: TiempoInvalidRequestError,
73
+ authentication_error: TiempoAuthenticationError,
74
+ permission_error: TiempoPermissionError,
75
+ conflict_error: TiempoConflictError,
76
+ idempotency_error: TiempoIdempotencyError,
77
+ rate_limit_error: TiempoRateLimitError,
78
+ api_error: TiempoAPIError,
79
+ internal_error: TiempoAPIError,
80
+ };
81
+ function byStatus(status) {
82
+ if (status === 401)
83
+ return TiempoAuthenticationError;
84
+ if (status === 403)
85
+ return TiempoPermissionError;
86
+ if (status === 409)
87
+ return TiempoConflictError;
88
+ if (status === 429)
89
+ return TiempoRateLimitError;
90
+ if (status >= 400 && status < 500)
91
+ return TiempoInvalidRequestError;
92
+ return TiempoAPIError;
93
+ }
94
+ /** Builds the right error subclass from an HTTP error response. */
95
+ export function errorFromResponse(statusCode, headers, raw) {
96
+ const body = raw && typeof raw === 'object' && 'error' in raw && raw.error && typeof raw.error === 'object'
97
+ ? raw.error
98
+ : undefined;
99
+ const Cls = (body?.type && BY_TYPE[body.type]) || byStatus(statusCode);
100
+ const message = body?.message ?? (typeof raw === 'string' && raw.length > 0 && raw.length < 500 ? raw : `HTTP ${statusCode}`);
101
+ return new Cls({
102
+ message,
103
+ type: body?.type,
104
+ code: body?.code,
105
+ param: body?.param ?? undefined,
106
+ details: body ? extraFields(body) : undefined,
107
+ statusCode,
108
+ headers,
109
+ raw,
110
+ });
111
+ }
112
+ const ENVELOPE_FIELDS = new Set(['type', 'code', 'message', 'param']);
113
+ function extraFields(body) {
114
+ const out = {};
115
+ for (const [key, value] of Object.entries(body)) {
116
+ if (!ENVELOPE_FIELDS.has(key))
117
+ out[key] = value;
118
+ }
119
+ return out;
120
+ }
@@ -0,0 +1,23 @@
1
+ import { EVENT_TYPES, type EventObjectMap, type TiempoEventType } from './generated/events.js';
2
+ import type { components } from './generated/openapi.js';
3
+ export { EVENT_TYPES };
4
+ export type { EventObjectMap, TiempoEventType };
5
+ /** The object an event of type `T` carries in `data.object`. */
6
+ export type EventObject<T extends TiempoEventType> = EventObjectMap[T];
7
+ type EventEnvelope = Omit<components['schemas']['Event'], 'type' | 'data'>;
8
+ /** One event of a specific type. */
9
+ export interface TiempoEventOf<T extends TiempoEventType> extends EventEnvelope {
10
+ type: T;
11
+ data: {
12
+ /** The object the event is about, as it was just after the change. */
13
+ object: EventObject<T>;
14
+ /** On `*.updated`: the changed fields' previous values. Null otherwise. */
15
+ previous_attributes: Partial<EventObject<T>> | null;
16
+ };
17
+ }
18
+ /** Any Tiempo event. Narrow on `type` to get a typed `data.object`. */
19
+ export type TiempoEvent = {
20
+ [T in TiempoEventType]: TiempoEventOf<T>;
21
+ }[TiempoEventType];
22
+ /** True if `type` is one this SDK knows about. */
23
+ export declare function isKnownEventType(type: string): type is TiempoEventType;
@@ -0,0 +1,8 @@
1
+ import { EVENT_TYPES } from './generated/events.js';
2
+ // The event list and each type's object come from the spec (Event.type and its
3
+ // x-tiempo-event-objects map) via `npm run generate`; see src/generated/events.ts.
4
+ export { EVENT_TYPES };
5
+ /** True if `type` is one this SDK knows about. */
6
+ export function isKnownEventType(type) {
7
+ return EVENT_TYPES.includes(type);
8
+ }
@@ -0,0 +1,51 @@
1
+ import type { Account, Attendee, Block, Booking, Location, Resource, Schedule, ScheduleOverride, Series, SkippedOccurrence } from './types.js';
2
+ /** Every event type Tiempo sends (from the spec's Event.type). */
3
+ export declare const EVENT_TYPES: readonly ["account.created", "account.updated", "account.deleted", "location.created", "location.updated", "location.deleted", "resource.created", "resource.updated", "resource.archived", "resource.unarchived", "resource.deleted", "schedule.created", "schedule.updated", "schedule.deleted", "schedule_override.created", "schedule_override.updated", "schedule_override.deleted", "block.created", "block.updated", "block.deleted", "booking.created", "booking.confirmed", "booking.rescheduled", "booking.cancelled", "booking.expired", "booking.completed", "booking.no_show", "booking.updated", "booking.conflicted", "booking.conflict_resolved", "booking.imported", "attendee.created", "attendee.confirmed", "attendee.cancelled", "attendee.expired", "attendee.completed", "attendee.no_show", "attendee.updated", "series.created", "series.updated", "series.split", "series.cancelled", "series.ended", "series.occurrence_skipped"];
4
+ export type TiempoEventType = (typeof EVENT_TYPES)[number];
5
+ /** The object each event type carries in `data.object`. */
6
+ export interface EventObjectMap {
7
+ "account.created": Account;
8
+ "account.updated": Account;
9
+ "account.deleted": Account;
10
+ "location.created": Location;
11
+ "location.updated": Location;
12
+ "location.deleted": Location;
13
+ "resource.created": Resource;
14
+ "resource.updated": Resource;
15
+ "resource.archived": Resource;
16
+ "resource.unarchived": Resource;
17
+ "resource.deleted": Resource;
18
+ "schedule.created": Schedule;
19
+ "schedule.updated": Schedule;
20
+ "schedule.deleted": Schedule;
21
+ "schedule_override.created": ScheduleOverride;
22
+ "schedule_override.updated": ScheduleOverride;
23
+ "schedule_override.deleted": ScheduleOverride;
24
+ "block.created": Block;
25
+ "block.updated": Block;
26
+ "block.deleted": Block;
27
+ "booking.created": Booking;
28
+ "booking.confirmed": Booking;
29
+ "booking.rescheduled": Booking;
30
+ "booking.cancelled": Booking;
31
+ "booking.expired": Booking;
32
+ "booking.completed": Booking;
33
+ "booking.no_show": Booking;
34
+ "booking.updated": Booking;
35
+ "booking.conflicted": Booking;
36
+ "booking.conflict_resolved": Booking;
37
+ "booking.imported": Booking;
38
+ "attendee.created": Attendee;
39
+ "attendee.confirmed": Attendee;
40
+ "attendee.cancelled": Attendee;
41
+ "attendee.expired": Attendee;
42
+ "attendee.completed": Attendee;
43
+ "attendee.no_show": Attendee;
44
+ "attendee.updated": Attendee;
45
+ "series.created": Series;
46
+ "series.updated": Series;
47
+ "series.split": Series;
48
+ "series.cancelled": Series;
49
+ "series.ended": Series;
50
+ "series.occurrence_skipped": SkippedOccurrence;
51
+ }
@@ -0,0 +1,48 @@
1
+ // Code generated by scripts/generate.mjs from openapi.json. DO NOT EDIT.
2
+ /** Every event type Tiempo sends (from the spec's Event.type). */
3
+ export const EVENT_TYPES = [
4
+ "account.created",
5
+ "account.updated",
6
+ "account.deleted",
7
+ "location.created",
8
+ "location.updated",
9
+ "location.deleted",
10
+ "resource.created",
11
+ "resource.updated",
12
+ "resource.archived",
13
+ "resource.unarchived",
14
+ "resource.deleted",
15
+ "schedule.created",
16
+ "schedule.updated",
17
+ "schedule.deleted",
18
+ "schedule_override.created",
19
+ "schedule_override.updated",
20
+ "schedule_override.deleted",
21
+ "block.created",
22
+ "block.updated",
23
+ "block.deleted",
24
+ "booking.created",
25
+ "booking.confirmed",
26
+ "booking.rescheduled",
27
+ "booking.cancelled",
28
+ "booking.expired",
29
+ "booking.completed",
30
+ "booking.no_show",
31
+ "booking.updated",
32
+ "booking.conflicted",
33
+ "booking.conflict_resolved",
34
+ "booking.imported",
35
+ "attendee.created",
36
+ "attendee.confirmed",
37
+ "attendee.cancelled",
38
+ "attendee.expired",
39
+ "attendee.completed",
40
+ "attendee.no_show",
41
+ "attendee.updated",
42
+ "series.created",
43
+ "series.updated",
44
+ "series.split",
45
+ "series.cancelled",
46
+ "series.ended",
47
+ "series.occurrence_skipped",
48
+ ];
@@ -0,0 +1,4 @@
1
+ /** The dated API version this SDK's types describe. Sent as `Tiempo-Version` on every request. */
2
+ export declare const API_VERSION = "2026-10-16";
3
+ /** This package's version. */
4
+ export declare const SDK_VERSION = "0.1.1";
@@ -0,0 +1,5 @@
1
+ // Code generated by scripts/generate.mjs from openapi.json. DO NOT EDIT.
2
+ /** The dated API version this SDK's types describe. Sent as `Tiempo-Version` on every request. */
3
+ export const API_VERSION = "2026-10-16";
4
+ /** This package's version. */
5
+ export const SDK_VERSION = "0.1.1";