@keystrokehq/segment 0.0.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 (74) hide show
  1. package/README.md +232 -0
  2. package/dist/_official/index.d.mts +2 -0
  3. package/dist/_official/index.mjs +3 -0
  4. package/dist/_runtime/index.d.mts +1 -0
  5. package/dist/_runtime/index.mjs +1 -0
  6. package/dist/audiences.d.mts +282 -0
  7. package/dist/audiences.mjs +205 -0
  8. package/dist/client.d.mts +90 -0
  9. package/dist/client.mjs +337 -0
  10. package/dist/common-CdGiJbjq.mjs +56 -0
  11. package/dist/computed-traits.d.mts +215 -0
  12. package/dist/computed-traits.mjs +149 -0
  13. package/dist/connection.d.mts +2 -0
  14. package/dist/connection.mjs +3 -0
  15. package/dist/crud-SWa_79Hg.mjs +140 -0
  16. package/dist/deletion-suppression.d.mts +191 -0
  17. package/dist/deletion-suppression.mjs +103 -0
  18. package/dist/delivery-overview.d.mts +79 -0
  19. package/dist/delivery-overview.mjs +54 -0
  20. package/dist/destination-filters.d.mts +190 -0
  21. package/dist/destination-filters.mjs +115 -0
  22. package/dist/destinations.d.mts +473 -0
  23. package/dist/destinations.mjs +257 -0
  24. package/dist/edge-functions.d.mts +168 -0
  25. package/dist/edge-functions.mjs +72 -0
  26. package/dist/errors-4FGnrowW.mjs +27 -0
  27. package/dist/events-catalog.d.mts +111 -0
  28. package/dist/events-catalog.mjs +65 -0
  29. package/dist/events.d.mts +167 -0
  30. package/dist/events.mjs +134 -0
  31. package/dist/factory-CvfKfzMq.mjs +8 -0
  32. package/dist/functions.d.mts +347 -0
  33. package/dist/functions.mjs +180 -0
  34. package/dist/iam-groups.d.mts +284 -0
  35. package/dist/iam-groups.mjs +144 -0
  36. package/dist/iam-users.d.mts +205 -0
  37. package/dist/iam-users.mjs +91 -0
  38. package/dist/index.d.mts +1 -0
  39. package/dist/index.mjs +1 -0
  40. package/dist/integration-B5zYasBZ.mjs +25 -0
  41. package/dist/integration-D2yRaKFR.d.mts +67 -0
  42. package/dist/labels.d.mts +100 -0
  43. package/dist/labels.mjs +58 -0
  44. package/dist/monitoring.d.mts +182 -0
  45. package/dist/monitoring.mjs +88 -0
  46. package/dist/profiles-sync.d.mts +127 -0
  47. package/dist/profiles-sync.mjs +89 -0
  48. package/dist/profiles.d.mts +197 -0
  49. package/dist/profiles.mjs +137 -0
  50. package/dist/reverse-etl.d.mts +448 -0
  51. package/dist/reverse-etl.mjs +228 -0
  52. package/dist/roles.d.mts +48 -0
  53. package/dist/roles.mjs +25 -0
  54. package/dist/schemas/index.d.mts +57 -0
  55. package/dist/schemas/index.mjs +3 -0
  56. package/dist/sources.d.mts +560 -0
  57. package/dist/sources.mjs +259 -0
  58. package/dist/tracking-plans.d.mts +351 -0
  59. package/dist/tracking-plans.mjs +160 -0
  60. package/dist/tracking.d.mts +499 -0
  61. package/dist/tracking.mjs +255 -0
  62. package/dist/transformations.d.mts +188 -0
  63. package/dist/transformations.mjs +83 -0
  64. package/dist/triggers.d.mts +54 -0
  65. package/dist/triggers.mjs +341 -0
  66. package/dist/usage.d.mts +70 -0
  67. package/dist/usage.mjs +55 -0
  68. package/dist/verification.d.mts +17 -0
  69. package/dist/verification.mjs +51 -0
  70. package/dist/warehouses.d.mts +341 -0
  71. package/dist/warehouses.mjs +176 -0
  72. package/dist/workspaces.d.mts +95 -0
  73. package/dist/workspaces.mjs +67 -0
  74. package/package.json +188 -0
@@ -0,0 +1,205 @@
1
+ import { createSegmentClient } from "./client.mjs";
2
+ import { a as segmentLooseObjectSchema, n as segmentIdSchema } from "./common-CdGiJbjq.mjs";
3
+ import { t as segmentOperation } from "./factory-CvfKfzMq.mjs";
4
+ import { z } from "zod";
5
+
6
+ //#region src/audiences.ts
7
+ /**
8
+ * segment/audiences.ts — Engage audiences (space-scoped). 8 actions total
9
+ * (PLAN.md § 6.7).
10
+ */
11
+ const audienceSchema = segmentLooseObjectSchema({
12
+ id: segmentIdSchema,
13
+ key: z.string().optional(),
14
+ name: z.string().optional(),
15
+ description: z.string().optional(),
16
+ definition: z.record(z.string(), z.unknown()).optional(),
17
+ enabled: z.boolean().optional()
18
+ });
19
+ const membershipSchema = segmentLooseObjectSchema({
20
+ profileId: z.string().optional(),
21
+ isMember: z.boolean().optional(),
22
+ enteredAt: z.string().optional(),
23
+ exitedAt: z.string().optional()
24
+ });
25
+ const memberSchema = segmentLooseObjectSchema({
26
+ profileId: z.string(),
27
+ traits: z.record(z.string(), z.unknown()).optional(),
28
+ enteredAt: z.string().optional()
29
+ });
30
+ const previewResultSchema = segmentLooseObjectSchema({
31
+ count: z.number().optional(),
32
+ sample: z.array(z.record(z.string(), z.unknown())).optional()
33
+ });
34
+ const listAudiences = segmentOperation({
35
+ id: "segment.audiences.list",
36
+ name: "List audiences",
37
+ description: "List audiences defined in an Engage space.",
38
+ input: z.object({
39
+ spaceId: segmentIdSchema,
40
+ pageSize: z.number().int().positive().max(200).optional(),
41
+ pageToken: z.string().optional()
42
+ }),
43
+ output: z.object({
44
+ items: z.array(audienceSchema),
45
+ nextPageToken: z.string().optional()
46
+ }),
47
+ run: async (input, credentials) => {
48
+ const result = await createSegmentClient(credentials).publicApi.list(`/spaces/${encodeURIComponent(input.spaceId)}/audiences`, {
49
+ itemsKey: "audiences",
50
+ items: audienceSchema
51
+ }, { query: {
52
+ count: input.pageSize,
53
+ cursor: input.pageToken
54
+ } });
55
+ return {
56
+ items: [...result.items],
57
+ ...result.nextPageToken !== void 0 ? { nextPageToken: result.nextPageToken } : {}
58
+ };
59
+ }
60
+ });
61
+ const getAudience = segmentOperation({
62
+ id: "segment.audiences.get",
63
+ name: "Get audience",
64
+ description: "Fetch an audience in an Engage space.",
65
+ input: z.object({
66
+ spaceId: segmentIdSchema,
67
+ id: segmentIdSchema
68
+ }),
69
+ output: audienceSchema,
70
+ run: async (input, credentials) => {
71
+ const body = await createSegmentClient(credentials).publicApi.request(`/spaces/${encodeURIComponent(input.spaceId)}/audiences/${encodeURIComponent(input.id)}`);
72
+ const rec = body;
73
+ return audienceSchema.parse(rec.data?.audience ?? body);
74
+ }
75
+ });
76
+ const createAudience = segmentOperation({
77
+ id: "segment.audiences.create",
78
+ name: "Create audience",
79
+ description: "Create a new audience.",
80
+ input: z.object({
81
+ spaceId: segmentIdSchema,
82
+ name: z.string().min(1),
83
+ description: z.string().optional(),
84
+ definition: z.record(z.string(), z.unknown()),
85
+ enabled: z.boolean().optional()
86
+ }),
87
+ output: audienceSchema,
88
+ needsApproval: true,
89
+ run: async (input, credentials) => {
90
+ const client = createSegmentClient(credentials);
91
+ const { spaceId, ...body } = input;
92
+ const response = await client.publicApi.request(`/spaces/${encodeURIComponent(spaceId)}/audiences`, {
93
+ method: "POST",
94
+ body
95
+ });
96
+ const rec = response;
97
+ return audienceSchema.parse(rec.data?.audience ?? response);
98
+ }
99
+ });
100
+ const updateAudience = segmentOperation({
101
+ id: "segment.audiences.update",
102
+ name: "Update audience",
103
+ description: "Patch an audience definition.",
104
+ input: z.object({
105
+ spaceId: segmentIdSchema,
106
+ id: segmentIdSchema,
107
+ name: z.string().optional(),
108
+ description: z.string().optional(),
109
+ definition: z.record(z.string(), z.unknown()).optional(),
110
+ enabled: z.boolean().optional()
111
+ }),
112
+ output: audienceSchema,
113
+ needsApproval: true,
114
+ run: async (input, credentials) => {
115
+ const client = createSegmentClient(credentials);
116
+ const { spaceId, id, ...body } = input;
117
+ const response = await client.publicApi.request(`/spaces/${encodeURIComponent(spaceId)}/audiences/${encodeURIComponent(id)}`, {
118
+ method: "PATCH",
119
+ body
120
+ });
121
+ const rec = response;
122
+ return audienceSchema.parse(rec.data?.audience ?? response);
123
+ }
124
+ });
125
+ const deleteAudience = segmentOperation({
126
+ id: "segment.audiences.delete",
127
+ name: "Delete audience",
128
+ description: "Delete an audience.",
129
+ input: z.object({
130
+ spaceId: segmentIdSchema,
131
+ id: segmentIdSchema
132
+ }),
133
+ output: z.object({ deleted: z.literal(true) }),
134
+ needsApproval: true,
135
+ run: async (input, credentials) => {
136
+ await createSegmentClient(credentials).publicApi.request(`/spaces/${encodeURIComponent(input.spaceId)}/audiences/${encodeURIComponent(input.id)}`, { method: "DELETE" });
137
+ return { deleted: true };
138
+ }
139
+ });
140
+ const previewAudience = segmentOperation({
141
+ id: "segment.audiences.preview",
142
+ name: "Preview audience",
143
+ description: "Run a preview compute over an audience definition without saving.",
144
+ input: z.object({
145
+ spaceId: segmentIdSchema,
146
+ definition: z.record(z.string(), z.unknown())
147
+ }),
148
+ output: previewResultSchema,
149
+ needsApproval: true,
150
+ run: async (input, credentials) => {
151
+ const body = await createSegmentClient(credentials).publicApi.request(`/spaces/${encodeURIComponent(input.spaceId)}/audiences/previews`, {
152
+ method: "POST",
153
+ body: { definition: input.definition }
154
+ });
155
+ const rec = body;
156
+ return previewResultSchema.parse(rec.data?.preview ?? body);
157
+ }
158
+ });
159
+ const getAudienceMembership = segmentOperation({
160
+ id: "segment.audiences.getMembership",
161
+ name: "Get audience membership",
162
+ description: "Check whether a specific profile is a member of an audience.",
163
+ input: z.object({
164
+ spaceId: segmentIdSchema,
165
+ id: segmentIdSchema,
166
+ profileId: segmentIdSchema
167
+ }),
168
+ output: membershipSchema,
169
+ run: async (input, credentials) => {
170
+ const body = await createSegmentClient(credentials).publicApi.request(`/spaces/${encodeURIComponent(input.spaceId)}/audiences/${encodeURIComponent(input.id)}/membership/${encodeURIComponent(input.profileId)}`);
171
+ const rec = body;
172
+ return membershipSchema.parse(rec.data ?? body);
173
+ }
174
+ });
175
+ const listAudienceMembers = segmentOperation({
176
+ id: "segment.audiences.listMembers",
177
+ name: "List audience members",
178
+ description: "List the profiles currently in an audience.",
179
+ input: z.object({
180
+ spaceId: segmentIdSchema,
181
+ id: segmentIdSchema,
182
+ pageSize: z.number().int().positive().max(200).optional(),
183
+ pageToken: z.string().optional()
184
+ }),
185
+ output: z.object({
186
+ items: z.array(memberSchema),
187
+ nextPageToken: z.string().optional()
188
+ }),
189
+ run: async (input, credentials) => {
190
+ const result = await createSegmentClient(credentials).publicApi.list(`/spaces/${encodeURIComponent(input.spaceId)}/audiences/${encodeURIComponent(input.id)}/members`, {
191
+ itemsKey: "members",
192
+ items: memberSchema
193
+ }, { query: {
194
+ count: input.pageSize,
195
+ cursor: input.pageToken
196
+ } });
197
+ return {
198
+ items: [...result.items],
199
+ ...result.nextPageToken !== void 0 ? { nextPageToken: result.nextPageToken } : {}
200
+ };
201
+ }
202
+ });
203
+
204
+ //#endregion
205
+ export { audienceSchema, createAudience, deleteAudience, getAudience, getAudienceMembership, listAudienceMembers, listAudiences, previewAudience, updateAudience };
@@ -0,0 +1,90 @@
1
+ import { n as SegmentRegion, t as SegmentCredentials } from "./integration-D2yRaKFR.mjs";
2
+ import { z } from "zod";
3
+
4
+ //#region src/client.d.ts
5
+ declare const PUBLIC_API_HOSTS: Readonly<Record<SegmentRegion, string>>;
6
+ declare const TRACKING_API_HOSTS: Readonly<Record<SegmentRegion, string>>;
7
+ declare const PROFILE_API_HOST = "https://profiles.segment.com/v1";
8
+ /** Segment-enforced hard limit for a single event body (Track/Identify/…). */
9
+ declare const MAX_EVENT_BYTES: number;
10
+ /** Segment-enforced hard limit for a `/v1/batch` envelope. */
11
+ declare const MAX_BATCH_BYTES: number;
12
+ type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
13
+ interface CreateSegmentClientOptions {
14
+ /** Override the global `fetch` (for tests, custom timeouts, etc.). */
15
+ readonly fetch?: FetchLike;
16
+ /** Maximum number of retries on 429/5xx. Defaults to 3. */
17
+ readonly maxRetries?: number;
18
+ /** Maximum overall backoff between retries in ms. Defaults to 10_000. */
19
+ readonly maxBackoffMs?: number;
20
+ /** Sleep function (override for tests). */
21
+ readonly sleep?: (ms: number) => Promise<void>;
22
+ }
23
+ type QueryPrimitive = string | number | boolean;
24
+ type QueryValue = QueryPrimitive | readonly QueryPrimitive[] | undefined;
25
+ interface PublicRequestOptions {
26
+ readonly method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
27
+ readonly query?: Readonly<Record<string, QueryValue>>;
28
+ readonly body?: unknown;
29
+ readonly timeoutMs?: number;
30
+ }
31
+ interface ListOptions<TOut> {
32
+ readonly itemsKey: string;
33
+ readonly items: z.ZodType<TOut>;
34
+ }
35
+ interface ListResult<T> {
36
+ readonly items: readonly T[];
37
+ readonly nextPageToken?: string;
38
+ readonly totalEntries?: number;
39
+ }
40
+ interface BatchSplitChunk<T> {
41
+ readonly events: readonly T[];
42
+ readonly bytes: number;
43
+ }
44
+ interface BatchSplitResult<T> {
45
+ readonly chunks: readonly BatchSplitChunk<T>[];
46
+ readonly rejected: readonly {
47
+ readonly event: T;
48
+ readonly bytes: number;
49
+ }[];
50
+ }
51
+ declare function splitBatch<T>(events: readonly T[], options?: {
52
+ readonly maxBatchBytes?: number;
53
+ readonly maxEventBytes?: number;
54
+ }): BatchSplitResult<T>;
55
+ interface PublicApiClient {
56
+ readonly baseUrl: string;
57
+ readonly request: <T>(path: string, options?: PublicRequestOptions) => Promise<T>;
58
+ readonly list: <T>(path: string, listOpts: ListOptions<T>, requestOpts?: PublicRequestOptions) => Promise<ListResult<T>>;
59
+ }
60
+ interface TrackingApiClient {
61
+ readonly baseUrl: string;
62
+ /** Send a single tracking event. `method` defaults to the endpoint path
63
+ * (e.g. `identify`, `track`). `sourceId` selects which write key to use. */
64
+ readonly send: (method: TrackingMethod, payload: Record<string, unknown>, options?: {
65
+ readonly sourceId?: string;
66
+ }) => Promise<void>;
67
+ readonly batch: (events: readonly Record<string, unknown>[], options?: {
68
+ readonly sourceId?: string;
69
+ }) => Promise<{
70
+ readonly chunksSent: number;
71
+ readonly rejected: readonly Record<string, unknown>[];
72
+ }>;
73
+ }
74
+ interface ProfileApiClient {
75
+ readonly baseUrl: string;
76
+ readonly request: <T>(path: string, options?: PublicRequestOptions & {
77
+ readonly spaceId?: string;
78
+ }) => Promise<T>;
79
+ }
80
+ type TrackingMethod = 'identify' | 'track' | 'page' | 'screen' | 'group' | 'alias' | 'batch';
81
+ interface SegmentClient {
82
+ readonly credentials: SegmentCredentials;
83
+ readonly region: SegmentRegion;
84
+ readonly publicApi: PublicApiClient;
85
+ readonly tracking: TrackingApiClient;
86
+ readonly profiles: ProfileApiClient;
87
+ }
88
+ declare function createSegmentClient(credentials: SegmentCredentials, options?: CreateSegmentClientOptions): SegmentClient;
89
+ //#endregion
90
+ export { BatchSplitChunk, BatchSplitResult, CreateSegmentClientOptions, FetchLike, ListOptions, ListResult, MAX_BATCH_BYTES, MAX_EVENT_BYTES, PROFILE_API_HOST, PUBLIC_API_HOSTS, ProfileApiClient, PublicApiClient, PublicRequestOptions, SegmentClient, TRACKING_API_HOSTS, TrackingApiClient, TrackingMethod, createSegmentClient, splitBatch };
@@ -0,0 +1,337 @@
1
+ import { t as SegmentApiError } from "./errors-4FGnrowW.mjs";
2
+ import { z } from "zod";
3
+
4
+ //#region src/client.ts
5
+ /**
6
+ * segment/client.ts
7
+ *
8
+ * Typed `fetch`-based client for all three Segment authentication surfaces:
9
+ *
10
+ * - **Public API** (`api.segmentapis.com` US / `eu1.api.segmentapis.com`
11
+ * EU) — workspace-scoped Bearer token. Cursor pagination, retry on 429
12
+ * (respects `Retry-After`) and 5xx.
13
+ * - **Tracking API** (`api.segment.io` US / `events.eu1.segmentapis.com`
14
+ * EU) — HTTP Basic with per-source write key as username; no password.
15
+ * Enforces 500 KB per batch and 32 KB per event client-side and exposes a
16
+ * `splitBatch` helper for chunking.
17
+ * - **Profile API** (`profiles.segment.com`) — HTTP Basic with per-space
18
+ * access token as username.
19
+ *
20
+ * The official `@segment/public-api-sdk-typescript` package was considered and
21
+ * rejected (see `IMPLEMENTATION_NOTES.md` § 3): it depends on the deprecated
22
+ * `request` library and `bluebird`, neither of which is ESM-friendly or
23
+ * usable in Cloudflare Workers / V8 isolates (our executor tier).
24
+ */
25
+ const PUBLIC_API_HOSTS = {
26
+ us: "https://api.segmentapis.com",
27
+ eu: "https://eu1.api.segmentapis.com"
28
+ };
29
+ const TRACKING_API_HOSTS = {
30
+ us: "https://api.segment.io/v1",
31
+ eu: "https://events.eu1.segmentapis.com/v1"
32
+ };
33
+ const PROFILE_API_HOST = "https://profiles.segment.com/v1";
34
+ /** Segment-enforced hard limit for a single event body (Track/Identify/…). */
35
+ const MAX_EVENT_BYTES = 32 * 1024;
36
+ /** Segment-enforced hard limit for a `/v1/batch` envelope. */
37
+ const MAX_BATCH_BYTES = 500 * 1024;
38
+ function splitBatch(events, options) {
39
+ const maxBatch = options?.maxBatchBytes ?? MAX_BATCH_BYTES;
40
+ const maxEvent = options?.maxEventBytes ?? MAX_EVENT_BYTES;
41
+ const chunks = [];
42
+ const rejected = [];
43
+ let current = [];
44
+ let currentBytes = envelopeBytes(0);
45
+ for (const event of events) {
46
+ const bytes = jsonByteLength(event);
47
+ if (bytes > maxEvent) {
48
+ rejected.push({
49
+ event,
50
+ bytes
51
+ });
52
+ continue;
53
+ }
54
+ const addition = bytes + 1;
55
+ if (currentBytes + addition > maxBatch && current.length > 0) {
56
+ chunks.push({
57
+ events: current,
58
+ bytes: currentBytes
59
+ });
60
+ current = [];
61
+ currentBytes = envelopeBytes(0);
62
+ }
63
+ current.push(event);
64
+ currentBytes += addition;
65
+ }
66
+ if (current.length > 0) chunks.push({
67
+ events: current,
68
+ bytes: currentBytes
69
+ });
70
+ return {
71
+ chunks,
72
+ rejected
73
+ };
74
+ }
75
+ function envelopeBytes(eventsBytes) {
76
+ return 12 + eventsBytes;
77
+ }
78
+ function jsonByteLength(value) {
79
+ return new TextEncoder().encode(JSON.stringify(value ?? null)).length;
80
+ }
81
+ const writeKeyMapSchema = z.record(z.string().min(1), z.string().min(1));
82
+ function resolveWriteKey(credentials, context) {
83
+ const map = parseWriteKeyMap(credentials.SEGMENT_WRITE_KEYS_JSON);
84
+ if (context.sourceId && map?.[context.sourceId]) return map[context.sourceId];
85
+ const fallback = credentials.SEGMENT_DEFAULT_WRITE_KEY;
86
+ if (fallback) return fallback;
87
+ throw new SegmentApiError({
88
+ kind: "missing_credential",
89
+ message: context.sourceId ? `No Segment write key is configured for source "${context.sourceId}". Provide it in SEGMENT_WRITE_KEYS_JSON or set SEGMENT_DEFAULT_WRITE_KEY.` : "No Segment write key is configured. Pass a sourceId resolvable from SEGMENT_WRITE_KEYS_JSON, or set SEGMENT_DEFAULT_WRITE_KEY."
90
+ });
91
+ }
92
+ function parseWriteKeyMap(raw) {
93
+ if (!raw) return void 0;
94
+ let parsed;
95
+ try {
96
+ parsed = JSON.parse(raw);
97
+ } catch (cause) {
98
+ throw new SegmentApiError({
99
+ kind: "validation",
100
+ message: "SEGMENT_WRITE_KEYS_JSON is not valid JSON.",
101
+ cause
102
+ });
103
+ }
104
+ const result = writeKeyMapSchema.safeParse(parsed);
105
+ if (!result.success) throw new SegmentApiError({
106
+ kind: "validation",
107
+ message: "SEGMENT_WRITE_KEYS_JSON must be a JSON object of { sourceId: writeKey }.",
108
+ cause: result.error
109
+ });
110
+ return result.data;
111
+ }
112
+ async function runRequest(opts, runtime) {
113
+ const url = new URL(`${opts.baseUrl}${opts.path}`);
114
+ if (opts.query) appendQuery(url, opts.query);
115
+ const init = {
116
+ method: opts.method ?? "GET",
117
+ headers: {
118
+ Authorization: opts.authHeader,
119
+ Accept: opts.acceptHeader ?? "application/json",
120
+ ...opts.body !== void 0 ? { "Content-Type": opts.contentType ?? "application/json" } : {}
121
+ },
122
+ ...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {}
123
+ };
124
+ for (let attempt = 0;; attempt += 1) {
125
+ let response;
126
+ try {
127
+ response = await runtime.fetch(url, init);
128
+ } catch (cause) {
129
+ if (attempt < runtime.maxRetries) {
130
+ await runtime.sleep(computeBackoff(attempt, runtime.maxBackoffMs));
131
+ continue;
132
+ }
133
+ throw new SegmentApiError({
134
+ kind: "network",
135
+ message: `Segment request failed (${opts.method ?? "GET"} ${opts.path}): ${stringifyError(cause)}`,
136
+ cause
137
+ });
138
+ }
139
+ if (response.status === 204) return;
140
+ const body = (response.headers.get("content-type") ?? "").includes("application/json") ? await response.json().catch(() => void 0) : await response.text().catch(() => "");
141
+ if (response.ok) return body;
142
+ if (response.status === 429 && attempt < runtime.maxRetries) {
143
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
144
+ await runtime.sleep(retryAfter ?? computeBackoff(attempt, runtime.maxBackoffMs));
145
+ continue;
146
+ }
147
+ if (response.status >= 500 && response.status < 600 && attempt < runtime.maxRetries) {
148
+ await runtime.sleep(computeBackoff(attempt, runtime.maxBackoffMs));
149
+ continue;
150
+ }
151
+ throw toSegmentError(response, body, opts);
152
+ }
153
+ }
154
+ function toSegmentError(response, body, opts) {
155
+ const kind = response.status === 401 || response.status === 403 ? "auth" : response.status === 413 ? "batch_too_large" : response.status === 429 ? "rate_limit" : "http";
156
+ const message = extractMessage(body) ?? `${opts.method ?? "GET"} ${opts.path} -> ${response.status}`;
157
+ const code = extractCode(body);
158
+ const requestId = response.headers.get("x-request-id") ?? void 0;
159
+ return new SegmentApiError({
160
+ kind,
161
+ status: response.status,
162
+ code,
163
+ requestId,
164
+ body,
165
+ message: `Segment API ${opts.method ?? "GET"} ${opts.path} failed (${response.status}): ${message}`
166
+ });
167
+ }
168
+ function extractMessage(body) {
169
+ if (!body || typeof body !== "object") return typeof body === "string" ? body : void 0;
170
+ const record = body;
171
+ if (typeof record.message === "string") return record.message;
172
+ if (typeof record.error === "string") return record.error;
173
+ }
174
+ function extractCode(body) {
175
+ if (!body || typeof body !== "object") return void 0;
176
+ const record = body;
177
+ if (typeof record.code === "string") return record.code;
178
+ if (typeof record.errorCode === "string") return record.errorCode;
179
+ }
180
+ function parseRetryAfter(value) {
181
+ if (!value) return void 0;
182
+ const seconds = Number(value);
183
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
184
+ const dateMs = Date.parse(value);
185
+ if (!Number.isNaN(dateMs)) {
186
+ const delta = dateMs - Date.now();
187
+ return delta > 0 ? delta : 0;
188
+ }
189
+ }
190
+ function computeBackoff(attempt, maxBackoffMs) {
191
+ const base = Math.min(maxBackoffMs, 500 * 2 ** attempt);
192
+ const jitter = Math.random() * base * .25;
193
+ return Math.floor(base + jitter);
194
+ }
195
+ function stringifyError(err) {
196
+ if (err instanceof Error) return err.message;
197
+ return String(err);
198
+ }
199
+ function appendQuery(url, query) {
200
+ for (const [key, value] of Object.entries(query)) {
201
+ if (value === void 0) continue;
202
+ if (Array.isArray(value)) {
203
+ for (const entry of value) url.searchParams.append(key, String(entry));
204
+ continue;
205
+ }
206
+ url.searchParams.set(key, String(value));
207
+ }
208
+ }
209
+ function basicAuthHeader(usernameWithNoPassword) {
210
+ return `Basic ${typeof globalThis.btoa === "function" ? globalThis.btoa(`${usernameWithNoPassword}:`) : Buffer.from(`${usernameWithNoPassword}:`, "utf8").toString("base64")}`;
211
+ }
212
+ function createSegmentClient(credentials, options = {}) {
213
+ const region = credentials.SEGMENT_REGION ?? "us";
214
+ const runtime = {
215
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
216
+ maxRetries: options.maxRetries ?? 3,
217
+ maxBackoffMs: options.maxBackoffMs ?? 1e4,
218
+ sleep: options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)))
219
+ };
220
+ const publicBase = PUBLIC_API_HOSTS[region];
221
+ const trackingBase = TRACKING_API_HOSTS[region];
222
+ const bearer = `Bearer ${credentials.SEGMENT_PUBLIC_API_TOKEN}`;
223
+ return {
224
+ credentials,
225
+ region,
226
+ publicApi: {
227
+ baseUrl: publicBase,
228
+ request: (path, opts = {}) => runRequest({
229
+ ...opts,
230
+ baseUrl: publicBase,
231
+ path,
232
+ authHeader: bearer,
233
+ acceptHeader: "application/vnd.segment.v1+json"
234
+ }, runtime),
235
+ list: async (path, listOpts, requestOpts = {}) => {
236
+ return parseListResult(await runRequest({
237
+ ...requestOpts,
238
+ baseUrl: publicBase,
239
+ path,
240
+ authHeader: bearer,
241
+ acceptHeader: "application/vnd.segment.v1+json"
242
+ }, runtime), listOpts);
243
+ }
244
+ },
245
+ tracking: {
246
+ baseUrl: trackingBase,
247
+ send: async (method, payload, opts = {}) => {
248
+ if (method === "batch") throw new SegmentApiError({
249
+ kind: "validation",
250
+ message: "Use client.tracking.batch(...) for batched events, not send(\"batch\", ...)."
251
+ });
252
+ const writeKey = resolveWriteKey(credentials, { sourceId: opts.sourceId });
253
+ const bytes = jsonByteLength(payload);
254
+ if (bytes > MAX_EVENT_BYTES) throw new SegmentApiError({
255
+ kind: "event_too_large",
256
+ message: `Segment tracking event is ${bytes} bytes; limit is ${MAX_EVENT_BYTES} bytes per request.`
257
+ });
258
+ await runRequest({
259
+ baseUrl: trackingBase,
260
+ path: `/${method}`,
261
+ method: "POST",
262
+ body: payload,
263
+ authHeader: basicAuthHeader(writeKey)
264
+ }, runtime);
265
+ },
266
+ batch: async (events, opts = {}) => {
267
+ const writeKey = resolveWriteKey(credentials, { sourceId: opts.sourceId });
268
+ const { chunks, rejected } = splitBatch(events);
269
+ for (const chunk of chunks) await runRequest({
270
+ baseUrl: trackingBase,
271
+ path: "/batch",
272
+ method: "POST",
273
+ body: { batch: chunk.events },
274
+ authHeader: basicAuthHeader(writeKey)
275
+ }, runtime);
276
+ return {
277
+ chunksSent: chunks.length,
278
+ rejected: rejected.map((r) => r.event)
279
+ };
280
+ }
281
+ },
282
+ profiles: {
283
+ baseUrl: PROFILE_API_HOST,
284
+ request: async (path, opts = {}) => {
285
+ const token = credentials.SEGMENT_PROFILE_API_TOKEN;
286
+ if (!token) throw new SegmentApiError({
287
+ kind: "missing_credential",
288
+ message: "Profile API access requires SEGMENT_PROFILE_API_TOKEN. Generate a per-space access token in Segment Engage settings and add it to the Keystroke connection."
289
+ });
290
+ const spaceId = opts.spaceId ?? credentials.SEGMENT_PROFILE_SPACE_ID;
291
+ if (!spaceId) throw new SegmentApiError({
292
+ kind: "missing_credential",
293
+ message: "Profile API calls require a spaceId — pass one as input, or set SEGMENT_PROFILE_SPACE_ID on the connection."
294
+ });
295
+ return runRequest({
296
+ ...opts,
297
+ baseUrl: PROFILE_API_HOST,
298
+ path: path.replace("{spaceId}", encodeURIComponent(spaceId)),
299
+ authHeader: basicAuthHeader(token)
300
+ }, runtime);
301
+ }
302
+ }
303
+ };
304
+ }
305
+ function parseListResult(raw, opts) {
306
+ const body = unwrapListBody(raw);
307
+ const container = body.data ?? body;
308
+ const items = extractItems(container, opts.itemsKey).map((entry) => opts.items.parse(entry));
309
+ const pagination = extractPagination(container);
310
+ return {
311
+ items,
312
+ ...pagination.next !== void 0 ? { nextPageToken: pagination.next } : {},
313
+ ...pagination.totalEntries !== void 0 ? { totalEntries: pagination.totalEntries } : {}
314
+ };
315
+ }
316
+ function unwrapListBody(raw) {
317
+ if (!raw || typeof raw !== "object") return {};
318
+ return raw;
319
+ }
320
+ function extractItems(container, key) {
321
+ const value = container[key];
322
+ return Array.isArray(value) ? value : [];
323
+ }
324
+ function extractPagination(container) {
325
+ const pagination = container.pagination;
326
+ if (!pagination || typeof pagination !== "object") return {};
327
+ const record = pagination;
328
+ const next = typeof record.next === "string" ? record.next : void 0;
329
+ const totalEntries = typeof record.totalEntries === "number" ? record.totalEntries : void 0;
330
+ return {
331
+ ...next !== void 0 ? { next } : {},
332
+ ...totalEntries !== void 0 ? { totalEntries } : {}
333
+ };
334
+ }
335
+
336
+ //#endregion
337
+ export { MAX_BATCH_BYTES, MAX_EVENT_BYTES, PROFILE_API_HOST, PUBLIC_API_HOSTS, TRACKING_API_HOSTS, createSegmentClient, splitBatch };
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/schemas/common.ts
4
+ /**
5
+ * segment/schemas/common.ts
6
+ *
7
+ * Zod v4 primitives shared across every Segment domain. Each domain file
8
+ * defines its own resource-shape schemas inline (AUTHORING.md § "inline
9
+ * single-use schemas") and reuses these primitives.
10
+ */
11
+ /** Non-empty trimmed string. */
12
+ const segmentIdSchema = z.string().trim().min(1);
13
+ /** ISO-8601 timestamp as emitted by Segment. Accept either a real ISO-8601
14
+ * datetime or any non-empty string (Segment occasionally returns
15
+ * human-readable dates for some `finishedAt` fields). */
16
+ const segmentTimestampSchema = z.union([z.iso.datetime({ offset: true }), segmentIdSchema]);
17
+ /** Forward-only cursor token the Public API returns on list endpoints under
18
+ * `pagination.next`. */
19
+ const segmentCursorSchema = z.string().min(1);
20
+ /** A permissively-typed object. Used for provider fields we pass through
21
+ * untouched (e.g. destination settings blobs whose shape depends on which
22
+ * destination is configured). Parsed so we never leak unknown values into
23
+ * typed outputs, but we accept any keys. */
24
+ function segmentLooseObjectSchema(shape = {}) {
25
+ return z.object(shape).catchall(z.unknown());
26
+ }
27
+ /** Segment Public API pagination envelope. */
28
+ const segmentPaginationSchema = z.object({
29
+ current: segmentCursorSchema.optional(),
30
+ next: segmentCursorSchema.optional(),
31
+ previous: segmentCursorSchema.optional(),
32
+ totalEntries: z.number().int().nonnegative().optional()
33
+ }).partial();
34
+ /** Common pagination input fields accepted by every list op. */
35
+ const segmentPaginationInputSchema = z.object({
36
+ pageSize: z.number().int().positive().max(200).optional(),
37
+ pageToken: segmentCursorSchema.optional()
38
+ });
39
+ /** Segment label — `key:value`. */
40
+ const segmentLabelSchema = z.object({
41
+ key: segmentIdSchema,
42
+ value: segmentIdSchema,
43
+ description: z.string().optional()
44
+ });
45
+ /** Envelope for a Public API list response. The actual payload key is the
46
+ * resource name (`sources`, `destinations`, …); callers wrap with the
47
+ * resource-specific schema. */
48
+ function segmentListEnvelopeSchema(itemsKey, item) {
49
+ return segmentLooseObjectSchema({
50
+ [itemsKey]: z.array(item),
51
+ pagination: segmentPaginationSchema.optional()
52
+ });
53
+ }
54
+
55
+ //#endregion
56
+ export { segmentLooseObjectSchema as a, segmentTimestampSchema as c, segmentListEnvelopeSchema as i, segmentIdSchema as n, segmentPaginationInputSchema as o, segmentLabelSchema as r, segmentPaginationSchema as s, segmentCursorSchema as t };