@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,134 @@
1
+ import type { components, operations } from './openapi.js';
2
+ import type { WithMetadataFilter } from '../shared.js';
3
+ export type { components, operations };
4
+ export type Account = components["schemas"]["Account"];
5
+ export type Attendee = components["schemas"]["Attendee"];
6
+ export type AvailabilityCalendarBlocked = components["schemas"]["AvailabilityCalendarBlocked"];
7
+ export type AvailabilityCalendar = components["schemas"]["AvailabilityCalendarCalendar"];
8
+ export type AvailabilityCalendarDay = components["schemas"]["AvailabilityCalendarDay"];
9
+ export type AvailabilityCalendarDayReason = components["schemas"]["AvailabilityCalendarDayReason"];
10
+ export type AvailabilityCalendarFree = components["schemas"]["AvailabilityCalendarFree"];
11
+ export type AvailabilityCalendarResourceCalendar = components["schemas"]["AvailabilityCalendarResourceCalendar"];
12
+ export type AvailabilityCalendarWorking = components["schemas"]["AvailabilityCalendarWorking"];
13
+ export type Availability = components["schemas"]["AvailabilitySearchAvailability"];
14
+ export type AvailabilitySegmentQuery = components["schemas"]["AvailabilitySearchSegmentQuery"];
15
+ export type AvailabilitySlot = components["schemas"]["AvailabilitySearchSlot"];
16
+ export type AvailabilitySlotSegment = components["schemas"]["AvailabilitySearchSlotSegment"];
17
+ export type Block = components["schemas"]["Block"];
18
+ export type Booking = components["schemas"]["Booking"];
19
+ export type BookingSegment = components["schemas"]["BookingSegment"];
20
+ export type BookingsImportedBooking = components["schemas"]["BookingsImportedBooking"];
21
+ export type BookingsImportedSegment = components["schemas"]["BookingsImportedSegment"];
22
+ export type BookingSegmentInput = components["schemas"]["BookingsSegmentInput"];
23
+ export type CandidateConflict = components["schemas"]["CandidateConflict"];
24
+ export type Conflict = components["schemas"]["Conflict"];
25
+ export type ConflictPreview = components["schemas"]["ConflictPreview"];
26
+ export type ConflictPreviewAffected = components["schemas"]["ConflictPreviewAffected"];
27
+ export type DeletedObject = components["schemas"]["DeletedObject"];
28
+ export type Location = components["schemas"]["Location"];
29
+ export type Resource = components["schemas"]["Resource"];
30
+ export type Schedule = components["schemas"]["Schedule"];
31
+ export type ScheduleInterval = components["schemas"]["ScheduleInterval"];
32
+ export type ScheduleIntervalInput = components["schemas"]["ScheduleIntervalInput"];
33
+ export type ScheduleOverride = components["schemas"]["ScheduleOverride"];
34
+ export type Series = components["schemas"]["Series"];
35
+ export type SeriesTemplate = components["schemas"]["SeriesTemplate"];
36
+ export type SeriesTemplateInput = components["schemas"]["SeriesTemplateInput"];
37
+ export type SeriesTemplateSegment = components["schemas"]["SeriesTemplateSegment"];
38
+ export type SeriesTemplateSegmentInput = components["schemas"]["SeriesTemplateSegmentInput"];
39
+ export type SkippedOccurrence = components["schemas"]["SkippedOccurrence"];
40
+ export type AccountUsage = components["schemas"]["UsageAccountUsage"];
41
+ export type UsageStatement = components["schemas"]["UsageStatement"];
42
+ export type UsageSummary = components["schemas"]["UsageSummary"];
43
+ export type WebhookDelivery = components["schemas"]["WebhookDelivery"];
44
+ export type WebhookDeliveryAttempt = components["schemas"]["WebhookDeliveryAttempt"];
45
+ export type WebhookEndpoint = components["schemas"]["WebhookEndpoint"];
46
+ export type WebhookEndpointResendResult = components["schemas"]["WebhookResendsEndpointResent"];
47
+ /** Params for `list` on `accounts` (GET /v1/accounts). */
48
+ export type AccountListParams = WithMetadataFilter<NonNullable<operations["accounts.list"]["parameters"]["query"]>>;
49
+ /** Params for `create` on `accounts` (POST /v1/accounts). */
50
+ export type AccountCreateParams = NonNullable<operations["accounts.create"]["requestBody"]>["content"]["application/json"];
51
+ /** Params for `update` on `accounts` (POST /v1/accounts/{id}). */
52
+ export type AccountUpdateParams = NonNullable<operations["accounts.update"]["requestBody"]>["content"]["application/json"];
53
+ /** Params for `update` on `attendees` (POST /v1/attendees/{id}). */
54
+ export type AttendeeUpdateParams = NonNullable<operations["attendees.updateAttendee"]["requestBody"]>["content"]["application/json"];
55
+ /** Params for `cancel` on `attendees` (POST /v1/attendees/{id}/cancel). */
56
+ export type AttendeeCancelParams = NonNullable<operations["attendees.cancelAttendee"]["requestBody"]>["content"]["application/json"];
57
+ /** Params for `search` on `availability` (POST /v1/availability). */
58
+ export type AvailabilitySearchParams = NonNullable<operations["availability.search"]["requestBody"]>["content"]["application/json"];
59
+ /** Params for `calendar` on `availability` (GET /v1/availability/calendar). */
60
+ export type AvailabilityCalendarParams = NonNullable<operations["availability.calendar"]["parameters"]["query"]>;
61
+ /** Params for `list` on `blocks` (GET /v1/blocks). */
62
+ export type BlockListParams = WithMetadataFilter<NonNullable<operations["blocks.list"]["parameters"]["query"]>>;
63
+ /** Params for `create` on `blocks` (POST /v1/blocks). */
64
+ export type BlockCreateParams = NonNullable<operations["blocks.create"]["requestBody"]>["content"]["application/json"];
65
+ /** Params for `update` on `blocks` (POST /v1/blocks/{id}). */
66
+ export type BlockUpdateParams = NonNullable<operations["blocks.update"]["requestBody"]>["content"]["application/json"];
67
+ /** Params for `list` on `bookings` (GET /v1/bookings). */
68
+ export type BookingListParams = WithMetadataFilter<NonNullable<operations["bookings.list"]["parameters"]["query"]>>;
69
+ /** Params for `create` on `bookings` (POST /v1/bookings). */
70
+ export type BookingCreateParams = NonNullable<operations["bookings.create"]["requestBody"]>["content"]["application/json"];
71
+ /** Params for `import` on `bookings` (POST /v1/bookings/import). */
72
+ export type BookingImportParams = NonNullable<operations["bookings.importBookings"]["requestBody"]>["content"]["application/json"];
73
+ /** Params for `update` on `bookings` (POST /v1/bookings/{id}). */
74
+ export type BookingUpdateParams = NonNullable<operations["bookings.update"]["requestBody"]>["content"]["application/json"];
75
+ /** Params for `list` on `attendees` (GET /v1/bookings/{id}/attendees). */
76
+ export type AttendeeListParams = WithMetadataFilter<NonNullable<operations["attendees.listAttendees"]["parameters"]["query"]>>;
77
+ /** Params for `create` on `attendees` (POST /v1/bookings/{id}/attendees). */
78
+ export type AttendeeCreateParams = NonNullable<operations["attendees.createAttendee"]["requestBody"]>["content"]["application/json"];
79
+ /** Params for `cancel` on `bookings` (POST /v1/bookings/{id}/cancel). */
80
+ export type BookingCancelParams = NonNullable<operations["bookings.cancel"]["requestBody"]>["content"]["application/json"];
81
+ /** Params for `reschedule` on `bookings` (POST /v1/bookings/{id}/reschedule). */
82
+ export type BookingRescheduleParams = NonNullable<operations["bookings.reschedule"]["requestBody"]>["content"]["application/json"];
83
+ /** Params for `create` on `conflictPreviews` (POST /v1/conflict_previews). */
84
+ export type ConflictPreviewCreateParams = NonNullable<operations["conflict_previews.create"]["requestBody"]>["content"]["application/json"];
85
+ /** Params for `list` on `events` (GET /v1/events). */
86
+ export type EventListParams = NonNullable<operations["events.list"]["parameters"]["query"]>;
87
+ /** Params for `resend` on `events` (POST /v1/events/{id}/resend). */
88
+ export type EventResendParams = NonNullable<operations["events.resend"]["requestBody"]>["content"]["application/json"];
89
+ /** Params for `list` on `locations` (GET /v1/locations). */
90
+ export type LocationListParams = WithMetadataFilter<NonNullable<operations["locations.list"]["parameters"]["query"]>>;
91
+ /** Params for `create` on `locations` (POST /v1/locations). */
92
+ export type LocationCreateParams = NonNullable<operations["locations.create"]["requestBody"]>["content"]["application/json"];
93
+ /** Params for `update` on `locations` (POST /v1/locations/{id}). */
94
+ export type LocationUpdateParams = NonNullable<operations["locations.update"]["requestBody"]>["content"]["application/json"];
95
+ /** Params for `list` on `resources` (GET /v1/resources). */
96
+ export type ResourceListParams = WithMetadataFilter<NonNullable<operations["resources.list"]["parameters"]["query"]>>;
97
+ /** Params for `create` on `resources` (POST /v1/resources). */
98
+ export type ResourceCreateParams = NonNullable<operations["resources.create"]["requestBody"]>["content"]["application/json"];
99
+ /** Params for `update` on `resources` (POST /v1/resources/{id}). */
100
+ export type ResourceUpdateParams = NonNullable<operations["resources.update"]["requestBody"]>["content"]["application/json"];
101
+ /** Params for `list` on `scheduleOverrides` (GET /v1/schedule_overrides). */
102
+ export type ScheduleOverrideListParams = WithMetadataFilter<NonNullable<operations["schedule_overrides.listOverrides"]["parameters"]["query"]>>;
103
+ /** Params for `create` on `scheduleOverrides` (POST /v1/schedule_overrides). */
104
+ export type ScheduleOverrideCreateParams = NonNullable<operations["schedule_overrides.createOverride"]["requestBody"]>["content"]["application/json"];
105
+ /** Params for `update` on `scheduleOverrides` (POST /v1/schedule_overrides/{id}). */
106
+ export type ScheduleOverrideUpdateParams = NonNullable<operations["schedule_overrides.updateOverride"]["requestBody"]>["content"]["application/json"];
107
+ /** Params for `list` on `schedules` (GET /v1/schedules). */
108
+ export type ScheduleListParams = WithMetadataFilter<NonNullable<operations["schedules.list"]["parameters"]["query"]>>;
109
+ /** Params for `create` on `schedules` (POST /v1/schedules). */
110
+ export type ScheduleCreateParams = NonNullable<operations["schedules.create"]["requestBody"]>["content"]["application/json"];
111
+ /** Params for `update` on `schedules` (POST /v1/schedules/{id}). */
112
+ export type ScheduleUpdateParams = NonNullable<operations["schedules.update"]["requestBody"]>["content"]["application/json"];
113
+ /** Params for `list` on `series` (GET /v1/series). */
114
+ export type SeriesListParams = WithMetadataFilter<NonNullable<operations["series.list"]["parameters"]["query"]>>;
115
+ /** Params for `create` on `series` (POST /v1/series). */
116
+ export type SeriesCreateParams = NonNullable<operations["series.create"]["requestBody"]>["content"]["application/json"];
117
+ /** Params for `update` on `series` (POST /v1/series/{id}). */
118
+ export type SeriesUpdateParams = NonNullable<operations["series.update"]["requestBody"]>["content"]["application/json"];
119
+ /** Params for `cancel` on `series` (POST /v1/series/{id}/cancel). */
120
+ export type SeriesCancelParams = NonNullable<operations["series.cancel"]["requestBody"]>["content"]["application/json"];
121
+ /** Params for `split` on `series` (POST /v1/series/{id}/split). */
122
+ export type SeriesSplitParams = NonNullable<operations["series.split"]["requestBody"]>["content"]["application/json"];
123
+ /** Params for `get` on `usage` (GET /v1/usage). */
124
+ export type UsageGetParams = NonNullable<operations["usage.get"]["parameters"]["query"]>;
125
+ /** Params for `list` on `webhookEndpoints` (GET /v1/webhook_endpoints). */
126
+ export type WebhookEndpointListParams = WithMetadataFilter<NonNullable<operations["webhook_endpoints.list"]["parameters"]["query"]>>;
127
+ /** Params for `create` on `webhookEndpoints` (POST /v1/webhook_endpoints). */
128
+ export type WebhookEndpointCreateParams = NonNullable<operations["webhook_endpoints.create"]["requestBody"]>["content"]["application/json"];
129
+ /** Params for `update` on `webhookEndpoints` (POST /v1/webhook_endpoints/{id}). */
130
+ export type WebhookEndpointUpdateParams = NonNullable<operations["webhook_endpoints.update"]["requestBody"]>["content"]["application/json"];
131
+ /** Params for `deliveries` on `webhookEndpoints` (GET /v1/webhook_endpoints/{id}/deliveries). */
132
+ export type WebhookEndpointDeliveriesParams = NonNullable<operations["webhook_endpoints.deliveries"]["parameters"]["query"]>;
133
+ /** Params for `resend` on `webhookEndpoints` (POST /v1/webhook_endpoints/{id}/resend). */
134
+ export type WebhookEndpointResendParams = NonNullable<operations["webhook_endpoints.resend"]["requestBody"]>["content"]["application/json"];
@@ -0,0 +1,2 @@
1
+ // Code generated by scripts/generate.mjs from openapi.json. DO NOT EDIT.
2
+ export {};
@@ -0,0 +1,16 @@
1
+ export { Tiempo } from './client.js';
2
+ export { Tiempo as default } from './client.js';
3
+ export { DEFAULT_BASE_URL } from './core.js';
4
+ export type { TiempoConfig, RequestOptions } from './core.js';
5
+ export { ListPromise } from './list.js';
6
+ export type { ListPage, PageParams } from './list.js';
7
+ export { TiempoError, TiempoInvalidRequestError, TiempoAuthenticationError, TiempoPermissionError, TiempoConflictError, TiempoIdempotencyError, TiempoRateLimitError, TiempoAPIError, TiempoConnectionError, TiempoSignatureVerificationError, } from './errors.js';
8
+ export type { TiempoErrorBody } from './errors.js';
9
+ export { EVENT_TYPES, isKnownEventType } from './events.js';
10
+ export type { TiempoEvent, TiempoEventOf, TiempoEventType, EventObject, EventObjectMap } from './events.js';
11
+ export { constructEvent, generateTestHeader, Webhooks, SIGNATURE_HEADER } from './webhooks.js';
12
+ export type { ConstructEventOptions, GenerateTestHeaderOptions } from './webhooks.js';
13
+ export { API_VERSION, SDK_VERSION } from './generated/meta.js';
14
+ export type { WithMetadataFilter } from './shared.js';
15
+ export * from './generated/types.js';
16
+ export * from './generated/resources.js';
@@ -0,0 +1,10 @@
1
+ export { Tiempo } from './client.js';
2
+ export { Tiempo as default } from './client.js';
3
+ export { DEFAULT_BASE_URL } from './core.js';
4
+ export { ListPromise } from './list.js';
5
+ export { TiempoError, TiempoInvalidRequestError, TiempoAuthenticationError, TiempoPermissionError, TiempoConflictError, TiempoIdempotencyError, TiempoRateLimitError, TiempoAPIError, TiempoConnectionError, TiempoSignatureVerificationError, } from './errors.js';
6
+ export { EVENT_TYPES, isKnownEventType } from './events.js';
7
+ export { constructEvent, generateTestHeader, Webhooks, SIGNATURE_HEADER } from './webhooks.js';
8
+ export { API_VERSION, SDK_VERSION } from './generated/meta.js';
9
+ export * from './generated/types.js';
10
+ export * from './generated/resources.js';
@@ -0,0 +1,43 @@
1
+ /** One page of a list endpoint. */
2
+ export interface ListPage<T> {
3
+ object: 'list';
4
+ data: T[];
5
+ has_more: boolean;
6
+ }
7
+ /** Query params every paginating list accepts. */
8
+ export interface PageParams {
9
+ limit?: number;
10
+ starting_after?: string;
11
+ ending_before?: string;
12
+ [key: string]: unknown;
13
+ }
14
+ type FetchPage<T> = (params: PageParams) => Promise<ListPage<T>>;
15
+ /**
16
+ * What a list method returns. `await` it for one page; `for await` over it to
17
+ * walk every item across pages.
18
+ *
19
+ * Iteration moves forward with `starting_after` (the last item's id). If you
20
+ * passed `ending_before`, it moves backwards with `ending_before` (the first
21
+ * item's id) and yields each page's items last to first, so you walk away from
22
+ * your cursor.
23
+ *
24
+ * The first page is fetched once, on first use, and shared between `await` and
25
+ * iteration.
26
+ */
27
+ export declare class ListPromise<T> implements PromiseLike<ListPage<T>>, AsyncIterable<T> {
28
+ private readonly fetchPage;
29
+ private readonly params;
30
+ private readonly paginates;
31
+ private first;
32
+ constructor(fetchPage: FetchPage<T>, params: PageParams, paginates?: boolean);
33
+ private firstPage;
34
+ then<R1 = ListPage<T>, R2 = never>(onfulfilled?: ((value: ListPage<T>) => R1 | PromiseLike<R1>) | null, onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): Promise<R1 | R2>;
35
+ catch<R = never>(onrejected?: ((reason: unknown) => R | PromiseLike<R>) | null): Promise<ListPage<T> | R>;
36
+ finally(onfinally?: (() => void) | null): Promise<ListPage<T>>;
37
+ [Symbol.asyncIterator](): AsyncIterator<T>;
38
+ /** Collects items into an array, stopping after `limit` (required, to avoid surprises). */
39
+ toArray({ limit }: {
40
+ limit: number;
41
+ }): Promise<T[]>;
42
+ }
43
+ export {};
@@ -0,0 +1,74 @@
1
+ function idOf(item) {
2
+ const id = item && typeof item === 'object' ? item.id : undefined;
3
+ if (typeof id !== 'string')
4
+ throw new TypeError('Cannot paginate: list item has no string `id`');
5
+ return id;
6
+ }
7
+ /**
8
+ * What a list method returns. `await` it for one page; `for await` over it to
9
+ * walk every item across pages.
10
+ *
11
+ * Iteration moves forward with `starting_after` (the last item's id). If you
12
+ * passed `ending_before`, it moves backwards with `ending_before` (the first
13
+ * item's id) and yields each page's items last to first, so you walk away from
14
+ * your cursor.
15
+ *
16
+ * The first page is fetched once, on first use, and shared between `await` and
17
+ * iteration.
18
+ */
19
+ export class ListPromise {
20
+ fetchPage;
21
+ params;
22
+ paginates;
23
+ first;
24
+ constructor(fetchPage, params, paginates = true) {
25
+ this.fetchPage = fetchPage;
26
+ this.params = params;
27
+ this.paginates = paginates;
28
+ }
29
+ firstPage() {
30
+ this.first ??= this.fetchPage(this.params);
31
+ return this.first;
32
+ }
33
+ then(onfulfilled, onrejected) {
34
+ return this.firstPage().then(onfulfilled, onrejected);
35
+ }
36
+ catch(onrejected) {
37
+ return this.firstPage().catch(onrejected);
38
+ }
39
+ finally(onfinally) {
40
+ return this.firstPage().finally(onfinally);
41
+ }
42
+ async *[Symbol.asyncIterator]() {
43
+ const backwards = this.params.ending_before !== undefined && this.params.starting_after === undefined;
44
+ let page = await this.firstPage();
45
+ for (;;) {
46
+ const items = backwards ? [...page.data].reverse() : page.data;
47
+ for (const item of items)
48
+ yield item;
49
+ if (!this.paginates || !page.has_more || page.data.length === 0)
50
+ return;
51
+ const next = { ...this.params };
52
+ if (backwards) {
53
+ next.ending_before = idOf(page.data[0]);
54
+ }
55
+ else {
56
+ delete next.ending_before;
57
+ next.starting_after = idOf(page.data[page.data.length - 1]);
58
+ }
59
+ page = await this.fetchPage(next);
60
+ }
61
+ }
62
+ /** Collects items into an array, stopping after `limit` (required, to avoid surprises). */
63
+ async toArray({ limit }) {
64
+ const out = [];
65
+ if (limit <= 0)
66
+ return out;
67
+ for await (const item of this) {
68
+ out.push(item);
69
+ if (out.length >= limit)
70
+ break;
71
+ }
72
+ return out;
73
+ }
74
+ }
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,8 @@
1
+ /** Encodes a path segment. */
2
+ export declare function encodePath(value: string): string;
3
+ /**
4
+ * Encodes a query object: undefined and null are skipped, booleans become
5
+ * `true`/`false`, arrays repeat the key, and objects (like `metadata`) become
6
+ * `key[sub]=value`.
7
+ */
8
+ export declare function encodeQuery(query: object | undefined): string;
@@ -0,0 +1,43 @@
1
+ /** Encodes a path segment. */
2
+ export function encodePath(value) {
3
+ if (typeof value !== 'string' || value === '') {
4
+ throw new TypeError(`Expected a non-empty id string, got ${JSON.stringify(value)}`);
5
+ }
6
+ return encodeURIComponent(value);
7
+ }
8
+ function scalar(value) {
9
+ if (value instanceof Date)
10
+ return value.toISOString();
11
+ if (typeof value === 'boolean')
12
+ return value ? 'true' : 'false';
13
+ return String(value);
14
+ }
15
+ function add(out, key, value) {
16
+ if (value === undefined || value === null)
17
+ return;
18
+ if (Array.isArray(value)) {
19
+ for (const v of value)
20
+ add(out, key, v);
21
+ return;
22
+ }
23
+ if (typeof value === 'object' && !(value instanceof Date)) {
24
+ for (const [k, v] of Object.entries(value))
25
+ add(out, `${key}[${k}]`, v);
26
+ return;
27
+ }
28
+ out.append(key, scalar(value));
29
+ }
30
+ /**
31
+ * Encodes a query object: undefined and null are skipped, booleans become
32
+ * `true`/`false`, arrays repeat the key, and objects (like `metadata`) become
33
+ * `key[sub]=value`.
34
+ */
35
+ export function encodeQuery(query) {
36
+ if (!query)
37
+ return '';
38
+ const out = new URLSearchParams();
39
+ for (const [k, v] of Object.entries(query))
40
+ add(out, k, v);
41
+ const s = out.toString();
42
+ return s ? `?${s}` : '';
43
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Replaces the spec's single `metadata[key]` query param with a map. Each entry
3
+ * is sent as `metadata[k]=v`; several entries must all match.
4
+ */
5
+ export type WithMetadataFilter<Q> = Omit<Q, 'metadata[key]'> & {
6
+ metadata?: Record<string, string>;
7
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import type { TiempoEvent } from './events.js';
2
+ /** The header Tiempo signs webhook deliveries with. */
3
+ export declare const SIGNATURE_HEADER = "Tiempo-Signature";
4
+ export interface ConstructEventOptions {
5
+ /** Maximum age (and clock skew) of the signature timestamp, in seconds. Default 300. */
6
+ tolerance?: number;
7
+ /** The current time, as a `Date` or unix seconds. Default: now. */
8
+ now?: Date | number;
9
+ }
10
+ export interface GenerateTestHeaderOptions {
11
+ /** The exact body you will pass to `constructEvent`. */
12
+ payload: string | Uint8Array;
13
+ /** The endpoint's `tpw_…` secret. */
14
+ secret: string;
15
+ /** Unix seconds. Default: now. */
16
+ timestamp?: number;
17
+ }
18
+ /**
19
+ * Verifies a webhook delivery and returns its event.
20
+ *
21
+ * Pass the raw request body exactly as received (not re-serialised JSON), the
22
+ * `Tiempo-Signature` header, and the endpoint's `tpw_…` secret. Throws
23
+ * {@link TiempoSignatureVerificationError} if the signature doesn't match or
24
+ * the timestamp is outside `tolerance`.
25
+ */
26
+ export declare function constructEvent(payload: string | Uint8Array, signatureHeader: string | null | undefined, secret: string, options?: ConstructEventOptions): Promise<TiempoEvent>;
27
+ /** Builds a valid `Tiempo-Signature` header for a payload, for your own tests. */
28
+ export declare function generateTestHeader({ payload, secret, timestamp }: GenerateTestHeaderOptions): Promise<string>;
29
+ /** `tiempo.webhooks`: the same functions, on the client. */
30
+ export declare class Webhooks {
31
+ readonly constructEvent: typeof constructEvent;
32
+ readonly generateTestHeader: typeof generateTestHeader;
33
+ readonly signatureHeader = "Tiempo-Signature";
34
+ }
@@ -0,0 +1,109 @@
1
+ import { webCrypto } from './crypto.js';
2
+ import { TiempoSignatureVerificationError } from './errors.js';
3
+ /** The header Tiempo signs webhook deliveries with. */
4
+ export const SIGNATURE_HEADER = 'Tiempo-Signature';
5
+ const encoder = new TextEncoder();
6
+ function fail(message) {
7
+ throw new TiempoSignatureVerificationError({ message });
8
+ }
9
+ function bytesOf(payload) {
10
+ return typeof payload === 'string' ? encoder.encode(payload) : payload;
11
+ }
12
+ function hex(buf) {
13
+ return Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, '0')).join('');
14
+ }
15
+ /** Constant-time string comparison (for equal-length hex digests). */
16
+ function timingSafeEqual(a, b) {
17
+ if (a.length !== b.length)
18
+ return false;
19
+ let diff = 0;
20
+ for (let i = 0; i < a.length; i++)
21
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
22
+ return diff === 0;
23
+ }
24
+ async function sign(secret, timestamp, payload) {
25
+ const { subtle } = await webCrypto();
26
+ const key = await subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
27
+ const prefix = encoder.encode(`${timestamp}.`);
28
+ const message = new Uint8Array(prefix.length + payload.length);
29
+ message.set(prefix, 0);
30
+ message.set(payload, prefix.length);
31
+ return hex(await subtle.sign('HMAC', key, message));
32
+ }
33
+ function parseHeader(header) {
34
+ let timestamp;
35
+ const signatures = [];
36
+ for (const part of header.split(',')) {
37
+ const eq = part.indexOf('=');
38
+ if (eq < 0)
39
+ continue;
40
+ const key = part.slice(0, eq).trim();
41
+ const value = part.slice(eq + 1).trim();
42
+ if (key === 't' && /^\d+$/.test(value))
43
+ timestamp = Number(value);
44
+ else if (key === 'v1' && value)
45
+ signatures.push(value.toLowerCase());
46
+ }
47
+ if (timestamp === undefined)
48
+ fail('Tiempo-Signature header has no timestamp (t=…)');
49
+ if (signatures.length === 0)
50
+ fail('Tiempo-Signature header has no v1 signature');
51
+ return { timestamp, signatures };
52
+ }
53
+ /**
54
+ * Verifies a webhook delivery and returns its event.
55
+ *
56
+ * Pass the raw request body exactly as received (not re-serialised JSON), the
57
+ * `Tiempo-Signature` header, and the endpoint's `tpw_…` secret. Throws
58
+ * {@link TiempoSignatureVerificationError} if the signature doesn't match or
59
+ * the timestamp is outside `tolerance`.
60
+ */
61
+ export async function constructEvent(payload, signatureHeader, secret, options = {}) {
62
+ if (typeof payload !== 'string' && !(payload instanceof Uint8Array)) {
63
+ fail('Webhook payload must be the raw body as a string or bytes, not a parsed object (use express.raw()).');
64
+ }
65
+ if (typeof signatureHeader !== 'string' || signatureHeader === '')
66
+ fail('Missing Tiempo-Signature header');
67
+ if (typeof secret !== 'string' || secret === '')
68
+ fail('Missing webhook signing secret (tpw_…)');
69
+ const tolerance = options.tolerance ?? 300;
70
+ const nowSeconds = options.now instanceof Date
71
+ ? Math.floor(options.now.getTime() / 1000)
72
+ : options.now ?? Math.floor(Date.now() / 1000);
73
+ const { timestamp, signatures } = parseHeader(signatureHeader);
74
+ const bytes = bytesOf(payload);
75
+ const expected = await sign(secret, timestamp, bytes);
76
+ let matched = false;
77
+ for (const candidate of signatures) {
78
+ if (timingSafeEqual(candidate, expected))
79
+ matched = true;
80
+ }
81
+ if (!matched)
82
+ fail('No v1 signature in Tiempo-Signature matches the payload. Check the secret and that the body is unmodified.');
83
+ if (Math.abs(nowSeconds - timestamp) > tolerance) {
84
+ fail(`Tiempo-Signature timestamp ${timestamp} is outside the ${tolerance}s tolerance`);
85
+ }
86
+ const text = typeof payload === 'string' ? payload : new TextDecoder().decode(payload);
87
+ let event;
88
+ try {
89
+ event = JSON.parse(text);
90
+ }
91
+ catch {
92
+ fail('Webhook payload is not valid JSON');
93
+ }
94
+ if (!event || typeof event !== 'object' || event.object !== 'event') {
95
+ fail('Webhook payload is not a Tiempo event');
96
+ }
97
+ return event;
98
+ }
99
+ /** Builds a valid `Tiempo-Signature` header for a payload, for your own tests. */
100
+ export async function generateTestHeader({ payload, secret, timestamp }) {
101
+ const t = timestamp ?? Math.floor(Date.now() / 1000);
102
+ return `t=${t},v1=${await sign(secret, t, bytesOf(payload))}`;
103
+ }
104
+ /** `tiempo.webhooks`: the same functions, on the client. */
105
+ export class Webhooks {
106
+ constructEvent = constructEvent;
107
+ generateTestHeader = generateTestHeader;
108
+ signatureHeader = SIGNATURE_HEADER;
109
+ }
package/package.json CHANGED
@@ -1,6 +1,76 @@
1
1
  {
2
2
  "name": "@tiempo/sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "TypeScript client for Tiempo, the scheduling API for booking platforms: availability search, holds and bookings that never double-book, classes, recurring series and signed webhooks.",
5
+ "keywords": [
6
+ "tiempo",
7
+ "scheduling",
8
+ "scheduling-api",
9
+ "booking",
10
+ "booking-api",
11
+ "booking-engine",
12
+ "appointments",
13
+ "availability",
14
+ "reservations",
15
+ "double-booking",
16
+ "calendar",
17
+ "classes",
18
+ "webhooks",
19
+ "api",
20
+ "sdk"
21
+ ],
22
+ "homepage": "https://tiempo.one/docs",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/tiempo-one/tiempo-node.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/tiempo-one/tiempo-node/issues"
29
+ },
30
+ "license": "MIT",
31
+ "type": "module",
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "main": "./dist/cjs/index.js",
36
+ "module": "./dist/esm/index.js",
37
+ "types": "./dist/cjs/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "import": {
41
+ "types": "./dist/esm/index.d.ts",
42
+ "default": "./dist/esm/index.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/cjs/index.d.ts",
46
+ "default": "./dist/cjs/index.js"
47
+ }
48
+ },
49
+ "./package.json": "./package.json"
50
+ },
51
+ "files": [
52
+ "dist",
53
+ "README.md",
54
+ "LICENSE"
55
+ ],
56
+ "sideEffects": false,
57
+ "scripts": {
58
+ "generate": "node scripts/generate.mjs",
59
+ "build": "node scripts/build.mjs",
60
+ "test": "vitest run",
61
+ "typecheck": "tsc -p tsconfig.json --noEmit",
62
+ "smoke": "npm run build && node scripts/smoke.mjs",
63
+ "prepare": "npm run build",
64
+ "traffic": "npm run build && node scripts/traffic.mjs",
65
+ "prepublishOnly": "npm run typecheck && npm test"
66
+ },
67
+ "devDependencies": {
68
+ "@types/node": "^22.20.5",
69
+ "openapi-typescript": "^7.13.0",
70
+ "typescript": "~5.9.3",
71
+ "vitest": "^4.1.11"
72
+ },
73
+ "publishConfig": {
74
+ "access": "public"
75
+ }
76
+ }