@reopt-ai/data-contract 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +11 -4
  2. package/dist/{chunk-2LJ7JLDX.js → chunk-2LZBZXBF.js} +9 -3
  3. package/dist/chunk-2LZBZXBF.js.map +1 -0
  4. package/dist/{chunk-ATPS4UV6.cjs → chunk-4B4AY44F.cjs} +33 -7
  5. package/dist/chunk-4B4AY44F.cjs.map +1 -0
  6. package/dist/{chunk-J4VD3TLV.js → chunk-AHXPQQKO.js} +30 -4
  7. package/dist/chunk-AHXPQQKO.js.map +1 -0
  8. package/dist/{chunk-55IWO4S3.js → chunk-BCGR27G2.js} +123 -11
  9. package/dist/chunk-BCGR27G2.js.map +1 -0
  10. package/dist/{chunk-3A7WZ7L6.js → chunk-MFZEIH5Z.js} +5 -4
  11. package/dist/chunk-MFZEIH5Z.js.map +1 -0
  12. package/dist/{chunk-KXEQGD3E.cjs → chunk-OLSMH4RY.cjs} +9 -3
  13. package/dist/chunk-OLSMH4RY.cjs.map +1 -0
  14. package/dist/{chunk-I2T4HRBM.cjs → chunk-VQ2CD5MG.cjs} +131 -19
  15. package/dist/chunk-VQ2CD5MG.cjs.map +1 -0
  16. package/dist/{chunk-RDPPPFGO.cjs → chunk-XI3VP5DN.cjs} +5 -4
  17. package/dist/chunk-XI3VP5DN.cjs.map +1 -0
  18. package/dist/client.cjs +83 -43
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +6 -2
  21. package/dist/client.d.ts +6 -2
  22. package/dist/client.js +45 -5
  23. package/dist/client.js.map +1 -1
  24. package/dist/control.cjs +14 -4
  25. package/dist/control.cjs.map +1 -1
  26. package/dist/control.d.cts +49 -7
  27. package/dist/control.d.ts +49 -7
  28. package/dist/control.js +15 -5
  29. package/dist/index.cjs +2 -2
  30. package/dist/index.d.cts +3 -3
  31. package/dist/index.d.ts +3 -3
  32. package/dist/index.js +1 -1
  33. package/dist/ingest.cjs +2 -2
  34. package/dist/ingest.d.cts +6 -6
  35. package/dist/ingest.d.ts +6 -6
  36. package/dist/ingest.js +1 -1
  37. package/dist/query.cjs +27 -3
  38. package/dist/query.cjs.map +1 -1
  39. package/dist/query.d.cts +302 -22
  40. package/dist/query.d.ts +302 -22
  41. package/dist/query.js +28 -4
  42. package/dist/report.cjs +85 -0
  43. package/dist/report.cjs.map +1 -0
  44. package/dist/report.d.cts +167 -0
  45. package/dist/report.d.ts +167 -0
  46. package/dist/report.js +85 -0
  47. package/dist/report.js.map +1 -0
  48. package/package.json +7 -2
  49. package/dist/chunk-2LJ7JLDX.js.map +0 -1
  50. package/dist/chunk-3A7WZ7L6.js.map +0 -1
  51. package/dist/chunk-55IWO4S3.js.map +0 -1
  52. package/dist/chunk-ATPS4UV6.cjs.map +0 -1
  53. package/dist/chunk-I2T4HRBM.cjs.map +0 -1
  54. package/dist/chunk-J4VD3TLV.js.map +0 -1
  55. package/dist/chunk-KXEQGD3E.cjs.map +0 -1
  56. package/dist/chunk-RDPPPFGO.cjs.map +0 -1
package/README.md CHANGED
@@ -71,18 +71,25 @@ const { data, meta } = await client.query.eventsTimeseries({
71
71
  endDate: "2026-08-23",
72
72
  granularity: "day",
73
73
  eventName: "checkout_completed",
74
+ // Top-level event properties. Multiple filters are combined with AND.
75
+ propertyFilters: [{ key: "surface", operator: "eq", value: "brandfront" }],
76
+ breakdown: { kind: "property", key: "page_id", topN: 100 },
74
77
  });
75
78
  ```
76
79
 
80
+ `propertyFilters` currently supports exact string equality only. Breakdown
81
+ values are capped at 100 so page-scale tables stay bounded.
82
+
77
83
  `meta` reports staleness as two separate numbers, never one sum, because they
78
84
  have different remedies:
79
85
 
80
86
  - `cacheAgeSeconds` — how old the returned computation is. Resolves itself at
81
87
  the next TTL.
82
- - `ingestLagSeconds` — how far behind materialization was: the age of the oldest
83
- raw event not yet materialized, or `0` when nothing is pending. A non-zero
84
- value means the read model is genuinely behind, which is an "aggregating"
85
- state, not a stale-cache state.
88
+ - `ingestLagSeconds` — how far behind live materialization was. `0` means no
89
+ live event is pending; `null` plus `ingestLagStatus: "unknown"` means the
90
+ measurement failed and must not be shown as fresh.
91
+ - `ingestDeadLetterCount` pending events older than the live replay window.
92
+ These remain visible without pegging the live "aggregating" indicator.
86
93
 
87
94
  Not `now - max(events.created_at)`: that conflates a lagging pipeline with a
88
95
  quiet project and grows without bound while a project sits idle.
@@ -1,13 +1,19 @@
1
1
  // src/index.ts
2
2
  import { z } from "zod";
3
- var CONTRACT_VERSION = "0.3.0";
3
+ var CONTRACT_VERSION = "0.5.0";
4
4
  var CONTRACT_VERSION_HEADER = "x-reopt-contract-version";
5
5
  var CLIENT_ID_HEADER = "reopt-client-id";
6
6
  var CLIENT_SECRET_HEADER = "reopt-client-secret";
7
7
  var WRITE_KEY_HEADER = "reopt-write-key";
8
8
  var DEVICE_ID_HEADER = "reopt-device-id";
9
9
  var REQUEST_ID_HEADER = "x-request-id";
10
- var zDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD (UTC)");
10
+ var zDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD").refine(isRealCalendarDate, "date does not exist");
11
+ function isRealCalendarDate(value) {
12
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
13
+ const [year, month, day] = value.split("-").map(Number);
14
+ const instant = new Date(Date.UTC(year, month - 1, day));
15
+ return instant.getUTCFullYear() === year && instant.getUTCMonth() === month - 1 && instant.getUTCDate() === day;
16
+ }
11
17
  var zInstant = z.string().min(20);
12
18
  var DataApiError = class _DataApiError extends Error {
13
19
  /**
@@ -58,4 +64,4 @@ export {
58
64
  zInstant,
59
65
  DataApiError
60
66
  };
61
- //# sourceMappingURL=chunk-2LJ7JLDX.js.map
67
+ //# sourceMappingURL=chunk-2LZBZXBF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@reopt-ai/data-contract` — the wire contract between reopt-data and its\n * server-credential consumers (RFC-0023).\n *\n * Subpaths:\n * `@reopt-ai/data-contract` this module — version, shared primitives, errors\n * `@reopt-ai/data-contract/ingest` POST /api/track request + response schemas\n * `@reopt-ai/data-contract/query` POST /api/v1/query/* request + response schemas\n * `@reopt-ai/data-contract/control` provisioning request + response schemas\n * `@reopt-ai/data-contract/client` createDataClient() — typed fetch over all\n *\n * Only `/client` touches `fetch`; everything else is pure zod. The single\n * runtime dependency is zod, deliberately — consumers bundle this.\n */\nimport { z } from \"zod\";\n\n/**\n * Contract version. Bumped on any breaking change to a schema in this package.\n * The server echoes the version it was built against in `x-reopt-contract-version`;\n * `createDataClient` surfaces a mismatch rather than guessing.\n */\nexport const CONTRACT_VERSION = \"0.5.0\";\n\n/** Response header carrying the server's contract version. */\nexport const CONTRACT_VERSION_HEADER = \"x-reopt-contract-version\";\n\n/** Request header names shared by the ingest and query planes. */\nexport const CLIENT_ID_HEADER = \"reopt-client-id\";\nexport const CLIENT_SECRET_HEADER = \"reopt-client-secret\";\nexport const WRITE_KEY_HEADER = \"reopt-write-key\";\nexport const DEVICE_ID_HEADER = \"reopt-device-id\";\nexport const REQUEST_ID_HEADER = \"x-request-id\";\n\n/**\n * A real calendar date. The query's resolved IANA zone decides which instants\n * its inclusive day covers.\n */\nexport const zDate = z\n .string()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, \"expected YYYY-MM-DD\")\n .refine(isRealCalendarDate, \"date does not exist\");\n\nfunction isRealCalendarDate(value: string): boolean {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return false;\n const [year, month, day] = value.split(\"-\").map(Number);\n const instant = new Date(Date.UTC(year!, month! - 1, day!));\n return instant.getUTCFullYear() === year && instant.getUTCMonth() === month! - 1 && instant.getUTCDate() === day;\n}\n\n/** ISO-8601 instant, as produced by `Date#toISOString()`. */\nexport const zInstant = z.string().min(20);\n\n/**\n * Client credentials. The same pair authenticates both planes; what a given\n * client may reach is decided server-side by `Client.scopes`\n * (`\"ingest\"` / `\"query\"` / `\"provision\"`).\n */\nexport interface ClientCredentials {\n clientId: string;\n clientSecret: string;\n}\n\n/**\n * Credentials are optional on the client itself because the provisioning\n * surface does not use them — it authenticates per call with a platform or\n * organization key. A caller that only provisions should not have to invent an\n * empty client id to satisfy a constructor.\n *\n * Reaching ingest or query without them fails with `credentials_missing`\n * rather than sending an unauthenticated request and reporting the 401 as a\n * server problem.\n */\nexport type OptionalClientCredentials = Partial<ClientCredentials>;\n\n/**\n * Every non-2xx response from either plane, plus the two failures the client\n * itself raises (`contract_mismatch`, `network_error`).\n */\nexport class DataApiError extends Error {\n /**\n * Brand for {@link DataApiError.is}. A bundler that inlines this module into\n * more than one entry point produces two distinct classes, and `instanceof`\n * silently stops matching — the brand survives that, and realm boundaries.\n */\n readonly isDataApiError = true as const;\n\n readonly status: number;\n readonly code: string;\n readonly requestId?: string;\n /** Parsed from `Retry-After`, in milliseconds. Only set on 429. */\n readonly retryAfterMs?: number;\n /** zod issues for `validation_failed`, or the raw body when unparseable. */\n readonly details?: unknown;\n\n constructor(init: {\n message: string;\n status: number;\n code: string;\n requestId?: string;\n retryAfterMs?: number;\n details?: unknown;\n }) {\n super(init.message);\n this.name = \"DataApiError\";\n this.status = init.status;\n this.code = init.code;\n this.requestId = init.requestId;\n this.retryAfterMs = init.retryAfterMs;\n this.details = init.details;\n }\n\n /** 429 and 5xx are worth retrying; 4xx are the caller's fault and are not. */\n get retryable(): boolean {\n return this.status === 429 || this.status >= 500;\n }\n\n /**\n * Prefer this over `instanceof` in a `catch`. It holds even when the class\n * has been duplicated — across CJS entry points, across bundler chunks, or\n * across realms — where `instanceof` quietly returns false.\n */\n static is(value: unknown): value is DataApiError {\n return (\n value instanceof DataApiError ||\n (typeof value === \"object\" && value !== null && (value as { isDataApiError?: unknown }).isDataApiError === true)\n );\n }\n}\n"],"mappings":";AAcA,SAAS,SAAS;AAOX,IAAM,mBAAmB;AAGzB,IAAM,0BAA0B;AAGhC,IAAM,mBAAmB;AACzB,IAAM,uBAAuB;AAC7B,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAM1B,IAAM,QAAQ,EAClB,OAAO,EACP,MAAM,uBAAuB,qBAAqB,EAClD,OAAO,oBAAoB,qBAAqB;AAEnD,SAAS,mBAAmB,OAAwB;AAClD,MAAI,CAAC,sBAAsB,KAAK,KAAK,EAAG,QAAO;AAC/C,QAAM,CAAC,MAAM,OAAO,GAAG,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM;AACtD,QAAM,UAAU,IAAI,KAAK,KAAK,IAAI,MAAO,QAAS,GAAG,GAAI,CAAC;AAC1D,SAAO,QAAQ,eAAe,MAAM,QAAQ,QAAQ,YAAY,MAAM,QAAS,KAAK,QAAQ,WAAW,MAAM;AAC/G;AAGO,IAAM,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE;AA4BlC,IAAM,eAAN,MAAM,sBAAqB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM7B,iBAAiB;AAAA,EAEjB;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,MAOT;AACD,UAAM,KAAK,OAAO;AAClB,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AACjB,SAAK,YAAY,KAAK;AACtB,SAAK,eAAe,KAAK;AACzB,SAAK,UAAU,KAAK;AAAA,EACtB;AAAA;AAAA,EAGA,IAAI,YAAqB;AACvB,WAAO,KAAK,WAAW,OAAO,KAAK,UAAU;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,GAAG,OAAuC;AAC/C,WACE,iBAAiB,iBAChB,OAAO,UAAU,YAAY,UAAU,QAAS,MAAuC,mBAAmB;AAAA,EAE/G;AACF;","names":[]}
@@ -1,9 +1,9 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkI2T4HRBMcjs = require('./chunk-I2T4HRBM.cjs');
3
+ var _chunkVQ2CD5MGcjs = require('./chunk-VQ2CD5MG.cjs');
4
4
 
5
5
 
6
- var _chunkKXEQGD3Ecjs = require('./chunk-KXEQGD3E.cjs');
6
+ var _chunkOLSMH4RYcjs = require('./chunk-OLSMH4RY.cjs');
7
7
 
8
8
  // src/control.ts
9
9
  var _zod = require('zod');
@@ -56,7 +56,7 @@ var zCreateProjectInput = _zod.z.object({
56
56
  name: _zod.z.string().min(1).max(200),
57
57
  domain: _zod.z.string().max(253).optional(),
58
58
  /** Becomes the project's default for every query that omits one. */
59
- timezone: _chunkI2T4HRBMcjs.zTimezone.optional(),
59
+ timezone: _chunkVQ2CD5MGcjs.zTimezone.optional(),
60
60
  retentionDays: _zod.z.number().int().min(1).max(3650).optional()
61
61
  });
62
62
  var zProject = _zod.z.object({
@@ -112,7 +112,27 @@ var zDeleteProjectResponse = _zod.z.object({
112
112
  /** Idempotent, but it still says which of the two happened. */
113
113
  purge: _zod.z.enum(["queued", "already-absent"]),
114
114
  /** When the purge was marked. Absent for `already-absent`. */
115
- purgingAt: _chunkKXEQGD3Ecjs.zInstant.optional()
115
+ purgingAt: _chunkOLSMH4RYcjs.zInstant.optional()
116
+ });
117
+ var EVENT_META_STATUSES = ["active", "deprecated", "archived", "internal"];
118
+ var zEventMetaStatus = _zod.z.enum(EVENT_META_STATUSES);
119
+ var zEventName = _zod.z.string().min(1).max(200);
120
+ var zUpsertEventMetaInput = _zod.z.object({
121
+ conversion: _zod.z.boolean().optional(),
122
+ /** `internal` events are left out of every metric unless a query opts in. */
123
+ status: zEventMetaStatus.optional(),
124
+ /** Property keys the hourly rollups break this event down by. */
125
+ rollupProperties: _zod.z.array(_zod.z.string().min(1).max(200)).max(20).optional()
126
+ }).strict();
127
+ var zEventMetaResponse = _zod.z.object({
128
+ projectId: _zod.z.string(),
129
+ name: _zod.z.string(),
130
+ conversion: _zod.z.boolean(),
131
+ status: zEventMetaStatus,
132
+ rollupProperties: _zod.z.array(_zod.z.string()),
133
+ /** Fields this call actually changed; empty on a repeat. */
134
+ changed: _zod.z.array(_zod.z.string()),
135
+ updatedAt: _chunkOLSMH4RYcjs.zInstant
116
136
  });
117
137
  var CONTROL_ERROR_CODES = [
118
138
  /** 401 — missing or invalid platform key / organization key. */
@@ -151,7 +171,8 @@ var CONTROL_API_PATHS = {
151
171
  organizationKeys: (id) => `/api/v1/organizations/${encodeURIComponent(id)}/keys`,
152
172
  projects: "/api/v1/projects",
153
173
  project: (id) => `/api/v1/projects/${encodeURIComponent(id)}`,
154
- clientRotate: (projectId, clientId) => `/api/v1/projects/${encodeURIComponent(projectId)}/clients/${encodeURIComponent(clientId)}/rotate`
174
+ clientRotate: (projectId, clientId) => `/api/v1/projects/${encodeURIComponent(projectId)}/clients/${encodeURIComponent(clientId)}/rotate`,
175
+ projectEvent: (projectId, name) => `/api/v1/projects/${encodeURIComponent(projectId)}/events/${encodeURIComponent(name)}`
155
176
  };
156
177
 
157
178
 
@@ -174,5 +195,10 @@ var CONTROL_API_PATHS = {
174
195
 
175
196
 
176
197
 
177
- exports.PLATFORM_KEY_HEADER = PLATFORM_KEY_HEADER; exports.ORG_KEY_HEADER = ORG_KEY_HEADER; exports.zExternalId = zExternalId; exports.zCreateOrganizationInput = zCreateOrganizationInput; exports.zOrganization = zOrganization; exports.zCreateOrganizationResponse = zCreateOrganizationResponse; exports.zRotateOrganizationKeyResponse = zRotateOrganizationKeyResponse; exports.zUpdateOrganizationQuotaInput = zUpdateOrganizationQuotaInput; exports.zUpdateOrganizationQuotaResponse = zUpdateOrganizationQuotaResponse; exports.zCreateProjectInput = zCreateProjectInput; exports.zProject = zProject; exports.zCreateProjectResponse = zCreateProjectResponse; exports.zRotateClientSecretResponse = zRotateClientSecretResponse; exports.zDeleteProjectInput = zDeleteProjectInput; exports.zDeleteProjectResponse = zDeleteProjectResponse; exports.CONTROL_ERROR_CODES = CONTROL_ERROR_CODES; exports.zControlErrorCode = zControlErrorCode; exports.zControlError = zControlError; exports.CONTROL_API_PATHS = CONTROL_API_PATHS;
178
- //# sourceMappingURL=chunk-ATPS4UV6.cjs.map
198
+
199
+
200
+
201
+
202
+
203
+ exports.PLATFORM_KEY_HEADER = PLATFORM_KEY_HEADER; exports.ORG_KEY_HEADER = ORG_KEY_HEADER; exports.zExternalId = zExternalId; exports.zCreateOrganizationInput = zCreateOrganizationInput; exports.zOrganization = zOrganization; exports.zCreateOrganizationResponse = zCreateOrganizationResponse; exports.zRotateOrganizationKeyResponse = zRotateOrganizationKeyResponse; exports.zUpdateOrganizationQuotaInput = zUpdateOrganizationQuotaInput; exports.zUpdateOrganizationQuotaResponse = zUpdateOrganizationQuotaResponse; exports.zCreateProjectInput = zCreateProjectInput; exports.zProject = zProject; exports.zCreateProjectResponse = zCreateProjectResponse; exports.zRotateClientSecretResponse = zRotateClientSecretResponse; exports.zDeleteProjectInput = zDeleteProjectInput; exports.zDeleteProjectResponse = zDeleteProjectResponse; exports.EVENT_META_STATUSES = EVENT_META_STATUSES; exports.zEventMetaStatus = zEventMetaStatus; exports.zEventName = zEventName; exports.zUpsertEventMetaInput = zUpsertEventMetaInput; exports.zEventMetaResponse = zEventMetaResponse; exports.CONTROL_ERROR_CODES = CONTROL_ERROR_CODES; exports.zControlErrorCode = zControlErrorCode; exports.zControlError = zControlError; exports.CONTROL_API_PATHS = CONTROL_API_PATHS;
204
+ //# sourceMappingURL=chunk-4B4AY44F.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-4B4AY44F.cjs","../src/control.ts"],"names":[],"mappings":"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","file":"/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-4B4AY44F.cjs","sourcesContent":[null,"/**\n * `POST /api/v1/organizations` · `/api/v1/projects` and friends — the\n * provisioning contract.\n *\n * A project-scoped client credential cannot authenticate the creation of the\n * project it is scoped to, and an organization key cannot authenticate the\n * creation of its own organization. So provisioning runs on two credentials\n * above the client one: a platform key that creates organizations and writes\n * quotas, and an organization key that creates and deletes projects inside one\n * organization.\n *\n * Quota writes sit with the platform key on purpose. Organization keys are held\n * one-per-workspace by the caller and so have a wide exposure surface; a leaked\n * one that could raise its own limits would turn a credential leak into an\n * unbounded bill.\n */\nimport { z } from \"zod\";\nimport { zInstant } from \"./index.js\";\nimport { zTimezone } from \"./query.js\";\n\nexport const PLATFORM_KEY_HEADER = \"reopt-platform-key\";\nexport const ORG_KEY_HEADER = \"reopt-org-key\";\n\n/**\n * The caller's own id for the thing being provisioned.\n *\n * Preferred over an `Idempotency-Key` header because this domain has a real\n * natural key — the caller already has a stable id for the workspace or brand —\n * and a header would make the caller store a second one just to retry safely.\n */\nexport const zExternalId = z.string().min(1).max(191);\n\n// ─── Organizations ──────────────────────────────────────────────────────────\n\nexport const zCreateOrganizationInput = z.object({\n externalId: zExternalId,\n name: z.string().min(1).max(200),\n slug: z\n .string()\n .min(1)\n .max(100)\n .regex(/^[a-z0-9-]+$/, \"lowercase letters, digits and hyphens only\")\n .optional(),\n monthlyEventQuota: z.number().int().nonnegative().optional(),\n monthlyCreditQuota: z.number().int().nonnegative().optional(),\n});\nexport type CreateOrganizationInput = z.input<typeof zCreateOrganizationInput>;\n\nexport const zOrganization = z.object({\n id: z.string(),\n externalId: zExternalId,\n name: z.string(),\n slug: z.string(),\n monthlyEventQuota: z.number().int(),\n monthlyCreditQuota: z.number().int(),\n});\n\nexport const zCreateOrganizationResponse = z.object({\n /** `false` when this externalId already had an organization. */\n created: z.boolean(),\n organization: zOrganization,\n /**\n * Plaintext, returned here and nowhere else.\n *\n * Absent when `created` is `false`: re-issuing on every retry would let a\n * retry quietly multiply live credentials. A caller that lost the key rotates\n * instead — see `POST /api/v1/organizations/{id}/keys`.\n */\n orgKey: z.string().optional(),\n});\nexport type CreateOrganizationResponse = z.infer<typeof zCreateOrganizationResponse>;\n\nexport const zRotateOrganizationKeyResponse = z.object({\n organizationId: z.string(),\n keyPrefix: z.string(),\n /** Plaintext, returned once. Every previously issued key is revoked immediately. */\n orgKey: z.string(),\n});\nexport type RotateOrganizationKeyResponse = z.infer<typeof zRotateOrganizationKeyResponse>;\n\nexport const zUpdateOrganizationQuotaInput = z\n .object({\n monthlyEventQuota: z.number().int().nonnegative().optional(),\n monthlyCreditQuota: z.number().int().nonnegative().optional(),\n })\n .refine((value) => value.monthlyEventQuota !== undefined || value.monthlyCreditQuota !== undefined, {\n message: \"at least one quota must be given\",\n });\nexport type UpdateOrganizationQuotaInput = z.input<typeof zUpdateOrganizationQuotaInput>;\n\nexport const zUpdateOrganizationQuotaResponse = z.object({ organization: zOrganization });\nexport type UpdateOrganizationQuotaResponse = z.infer<typeof zUpdateOrganizationQuotaResponse>;\n\n// ─── Projects ───────────────────────────────────────────────────────────────\n\nexport const zCreateProjectInput = z.object({\n externalId: zExternalId,\n name: z.string().min(1).max(200),\n domain: z.string().max(253).optional(),\n /** Becomes the project's default for every query that omits one. */\n timezone: zTimezone.optional(),\n retentionDays: z.number().int().min(1).max(3650).optional(),\n});\nexport type CreateProjectInput = z.input<typeof zCreateProjectInput>;\n\nexport const zProject = z.object({\n id: z.string(),\n externalId: zExternalId,\n organizationId: z.string(),\n name: z.string(),\n /** The applied value — `\"UTC\"` when none was given. */\n timezone: z.string(),\n retentionDays: z.number().int().nullable(),\n});\n\nexport const zCreateProjectResponse = z.object({\n created: z.boolean(),\n project: zProject,\n /**\n * Two credentials, not one. A key embedded in a browser bundle must not also\n * be able to read the project's analytics, so the browser write key and the\n * server secret are separate rows with different reach.\n */\n clients: z.object({\n browser: z.object({\n id: z.string(),\n /** Public by design, and recoverable — it ships in page source anyway. */\n writeKey: z.string(),\n scopes: z.array(z.string()),\n }),\n server: z.object({\n id: z.string(),\n /** Plaintext, returned once. Absent when `created` is `false`. */\n clientSecret: z.string().optional(),\n scopes: z.array(z.string()),\n }),\n }),\n});\nexport type CreateProjectResponse = z.infer<typeof zCreateProjectResponse>;\n\nexport const zRotateClientSecretResponse = z.object({\n projectId: z.string(),\n clientId: z.string(),\n /** Plaintext, returned once. The previous secret stops working immediately. */\n clientSecret: z.string(),\n});\nexport type RotateClientSecretResponse = z.infer<typeof zRotateClientSecretResponse>;\n\nexport const zDeleteProjectInput = z.object({\n /**\n * A field in the body rather than `?purge=true`. This deletes the project's\n * Postgres rows and every analytics row behind it, with no undo — and a query\n * parameter is far too easy to fire from shell history or a mis-copied URL.\n */\n purge: z.literal(true),\n /** Checked against the project on record; a mismatch is refused. */\n externalId: zExternalId,\n});\nexport type DeleteProjectInput = z.input<typeof zDeleteProjectInput>;\n\nexport const zDeleteProjectResponse = z.object({\n projectId: z.string(),\n /** Idempotent, but it still says which of the two happened. */\n purge: z.enum([\"queued\", \"already-absent\"]),\n /** When the purge was marked. Absent for `already-absent`. */\n purgingAt: zInstant.optional(),\n});\nexport type DeleteProjectResponse = z.infer<typeof zDeleteProjectResponse>;\n\n// ─── Errors ─────────────────────────────────────────────────────────────────\n\n// ─── Event metadata ─────────────────────────────────────────────────────────\n\n/**\n * `PUT /api/v1/projects/{projectId}/events/{name}` — catalogue settings for\n * one event name. Provisioning calls it to mark `form.submitted` a conversion\n * and an operational probe `internal`; the call is idempotent so a retried\n * provisioning run is harmless.\n */\nexport const EVENT_META_STATUSES = [\"active\", \"deprecated\", \"archived\", \"internal\"] as const;\nexport const zEventMetaStatus = z.enum(EVENT_META_STATUSES);\nexport type EventMetaStatus = z.infer<typeof zEventMetaStatus>;\n\nexport const zEventName = z.string().min(1).max(200);\n\nexport const zUpsertEventMetaInput = z\n .object({\n conversion: z.boolean().optional(),\n /** `internal` events are left out of every metric unless a query opts in. */\n status: zEventMetaStatus.optional(),\n /** Property keys the hourly rollups break this event down by. */\n rollupProperties: z.array(z.string().min(1).max(200)).max(20).optional(),\n })\n .strict();\nexport type UpsertEventMetaInput = z.infer<typeof zUpsertEventMetaInput>;\n\nexport const zEventMetaResponse = z.object({\n projectId: z.string(),\n name: z.string(),\n conversion: z.boolean(),\n status: zEventMetaStatus,\n rollupProperties: z.array(z.string()),\n /** Fields this call actually changed; empty on a repeat. */\n changed: z.array(z.string()),\n updatedAt: zInstant,\n});\nexport type EventMetaResponse = z.infer<typeof zEventMetaResponse>;\n\nexport const CONTROL_ERROR_CODES = [\n /** 401 — missing or invalid platform key / organization key. */\n \"unauthorized\",\n /** 403 — the organization key belongs to a different organization. */\n \"organization_scope_mismatch\",\n /**\n * 409 — this externalId exists with a materially different configuration.\n * Only fields whose change alters results are compared (see the docs); a\n * renamed brand is not a conflict.\n */\n \"external_id_conflict\",\n /** 409 — the externalId in a delete body does not match the project's. */\n \"project_external_id_mismatch\",\n /** 404 — no such organization or project, on an endpoint that is not idempotent. */\n \"not_found\",\n /** 400 — input failed the schema. */\n \"validation_failed\",\n /** 429 — per-credential rate limit. */\n \"rate_limited\",\n /** 500 — unexpected server failure. */\n \"internal_error\",\n] as const;\nexport const zControlErrorCode = z.enum(CONTROL_ERROR_CODES);\nexport type ControlErrorCode = z.infer<typeof zControlErrorCode>;\n\nexport const zControlError = z.object({\n status: z.number().int(),\n code: zControlErrorCode,\n error: z.string(),\n message: z.string().optional(),\n errors: z.unknown().optional(),\n requestId: z.string().optional(),\n});\nexport type ControlError = z.infer<typeof zControlError>;\n\n/** Paths, so the client and the route handlers cannot drift apart. */\nexport const CONTROL_API_PATHS = {\n organizations: \"/api/v1/organizations\",\n organization: (id: string) => `/api/v1/organizations/${encodeURIComponent(id)}`,\n organizationKeys: (id: string) => `/api/v1/organizations/${encodeURIComponent(id)}/keys`,\n projects: \"/api/v1/projects\",\n project: (id: string) => `/api/v1/projects/${encodeURIComponent(id)}`,\n clientRotate: (projectId: string, clientId: string) =>\n `/api/v1/projects/${encodeURIComponent(projectId)}/clients/${encodeURIComponent(clientId)}/rotate`,\n projectEvent: (projectId: string, name: string) =>\n `/api/v1/projects/${encodeURIComponent(projectId)}/events/${encodeURIComponent(name)}`,\n} as const;\n"]}
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  zTimezone
3
- } from "./chunk-55IWO4S3.js";
3
+ } from "./chunk-BCGR27G2.js";
4
4
  import {
5
5
  zInstant
6
- } from "./chunk-2LJ7JLDX.js";
6
+ } from "./chunk-2LZBZXBF.js";
7
7
 
8
8
  // src/control.ts
9
9
  import { z } from "zod";
@@ -114,6 +114,26 @@ var zDeleteProjectResponse = z.object({
114
114
  /** When the purge was marked. Absent for `already-absent`. */
115
115
  purgingAt: zInstant.optional()
116
116
  });
117
+ var EVENT_META_STATUSES = ["active", "deprecated", "archived", "internal"];
118
+ var zEventMetaStatus = z.enum(EVENT_META_STATUSES);
119
+ var zEventName = z.string().min(1).max(200);
120
+ var zUpsertEventMetaInput = z.object({
121
+ conversion: z.boolean().optional(),
122
+ /** `internal` events are left out of every metric unless a query opts in. */
123
+ status: zEventMetaStatus.optional(),
124
+ /** Property keys the hourly rollups break this event down by. */
125
+ rollupProperties: z.array(z.string().min(1).max(200)).max(20).optional()
126
+ }).strict();
127
+ var zEventMetaResponse = z.object({
128
+ projectId: z.string(),
129
+ name: z.string(),
130
+ conversion: z.boolean(),
131
+ status: zEventMetaStatus,
132
+ rollupProperties: z.array(z.string()),
133
+ /** Fields this call actually changed; empty on a repeat. */
134
+ changed: z.array(z.string()),
135
+ updatedAt: zInstant
136
+ });
117
137
  var CONTROL_ERROR_CODES = [
118
138
  /** 401 — missing or invalid platform key / organization key. */
119
139
  "unauthorized",
@@ -151,7 +171,8 @@ var CONTROL_API_PATHS = {
151
171
  organizationKeys: (id) => `/api/v1/organizations/${encodeURIComponent(id)}/keys`,
152
172
  projects: "/api/v1/projects",
153
173
  project: (id) => `/api/v1/projects/${encodeURIComponent(id)}`,
154
- clientRotate: (projectId, clientId) => `/api/v1/projects/${encodeURIComponent(projectId)}/clients/${encodeURIComponent(clientId)}/rotate`
174
+ clientRotate: (projectId, clientId) => `/api/v1/projects/${encodeURIComponent(projectId)}/clients/${encodeURIComponent(clientId)}/rotate`,
175
+ projectEvent: (projectId, name) => `/api/v1/projects/${encodeURIComponent(projectId)}/events/${encodeURIComponent(name)}`
155
176
  };
156
177
 
157
178
  export {
@@ -170,9 +191,14 @@ export {
170
191
  zRotateClientSecretResponse,
171
192
  zDeleteProjectInput,
172
193
  zDeleteProjectResponse,
194
+ EVENT_META_STATUSES,
195
+ zEventMetaStatus,
196
+ zEventName,
197
+ zUpsertEventMetaInput,
198
+ zEventMetaResponse,
173
199
  CONTROL_ERROR_CODES,
174
200
  zControlErrorCode,
175
201
  zControlError,
176
202
  CONTROL_API_PATHS
177
203
  };
178
- //# sourceMappingURL=chunk-J4VD3TLV.js.map
204
+ //# sourceMappingURL=chunk-AHXPQQKO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/control.ts"],"sourcesContent":["/**\n * `POST /api/v1/organizations` · `/api/v1/projects` and friends — the\n * provisioning contract.\n *\n * A project-scoped client credential cannot authenticate the creation of the\n * project it is scoped to, and an organization key cannot authenticate the\n * creation of its own organization. So provisioning runs on two credentials\n * above the client one: a platform key that creates organizations and writes\n * quotas, and an organization key that creates and deletes projects inside one\n * organization.\n *\n * Quota writes sit with the platform key on purpose. Organization keys are held\n * one-per-workspace by the caller and so have a wide exposure surface; a leaked\n * one that could raise its own limits would turn a credential leak into an\n * unbounded bill.\n */\nimport { z } from \"zod\";\nimport { zInstant } from \"./index.js\";\nimport { zTimezone } from \"./query.js\";\n\nexport const PLATFORM_KEY_HEADER = \"reopt-platform-key\";\nexport const ORG_KEY_HEADER = \"reopt-org-key\";\n\n/**\n * The caller's own id for the thing being provisioned.\n *\n * Preferred over an `Idempotency-Key` header because this domain has a real\n * natural key — the caller already has a stable id for the workspace or brand —\n * and a header would make the caller store a second one just to retry safely.\n */\nexport const zExternalId = z.string().min(1).max(191);\n\n// ─── Organizations ──────────────────────────────────────────────────────────\n\nexport const zCreateOrganizationInput = z.object({\n externalId: zExternalId,\n name: z.string().min(1).max(200),\n slug: z\n .string()\n .min(1)\n .max(100)\n .regex(/^[a-z0-9-]+$/, \"lowercase letters, digits and hyphens only\")\n .optional(),\n monthlyEventQuota: z.number().int().nonnegative().optional(),\n monthlyCreditQuota: z.number().int().nonnegative().optional(),\n});\nexport type CreateOrganizationInput = z.input<typeof zCreateOrganizationInput>;\n\nexport const zOrganization = z.object({\n id: z.string(),\n externalId: zExternalId,\n name: z.string(),\n slug: z.string(),\n monthlyEventQuota: z.number().int(),\n monthlyCreditQuota: z.number().int(),\n});\n\nexport const zCreateOrganizationResponse = z.object({\n /** `false` when this externalId already had an organization. */\n created: z.boolean(),\n organization: zOrganization,\n /**\n * Plaintext, returned here and nowhere else.\n *\n * Absent when `created` is `false`: re-issuing on every retry would let a\n * retry quietly multiply live credentials. A caller that lost the key rotates\n * instead — see `POST /api/v1/organizations/{id}/keys`.\n */\n orgKey: z.string().optional(),\n});\nexport type CreateOrganizationResponse = z.infer<typeof zCreateOrganizationResponse>;\n\nexport const zRotateOrganizationKeyResponse = z.object({\n organizationId: z.string(),\n keyPrefix: z.string(),\n /** Plaintext, returned once. Every previously issued key is revoked immediately. */\n orgKey: z.string(),\n});\nexport type RotateOrganizationKeyResponse = z.infer<typeof zRotateOrganizationKeyResponse>;\n\nexport const zUpdateOrganizationQuotaInput = z\n .object({\n monthlyEventQuota: z.number().int().nonnegative().optional(),\n monthlyCreditQuota: z.number().int().nonnegative().optional(),\n })\n .refine((value) => value.monthlyEventQuota !== undefined || value.monthlyCreditQuota !== undefined, {\n message: \"at least one quota must be given\",\n });\nexport type UpdateOrganizationQuotaInput = z.input<typeof zUpdateOrganizationQuotaInput>;\n\nexport const zUpdateOrganizationQuotaResponse = z.object({ organization: zOrganization });\nexport type UpdateOrganizationQuotaResponse = z.infer<typeof zUpdateOrganizationQuotaResponse>;\n\n// ─── Projects ───────────────────────────────────────────────────────────────\n\nexport const zCreateProjectInput = z.object({\n externalId: zExternalId,\n name: z.string().min(1).max(200),\n domain: z.string().max(253).optional(),\n /** Becomes the project's default for every query that omits one. */\n timezone: zTimezone.optional(),\n retentionDays: z.number().int().min(1).max(3650).optional(),\n});\nexport type CreateProjectInput = z.input<typeof zCreateProjectInput>;\n\nexport const zProject = z.object({\n id: z.string(),\n externalId: zExternalId,\n organizationId: z.string(),\n name: z.string(),\n /** The applied value — `\"UTC\"` when none was given. */\n timezone: z.string(),\n retentionDays: z.number().int().nullable(),\n});\n\nexport const zCreateProjectResponse = z.object({\n created: z.boolean(),\n project: zProject,\n /**\n * Two credentials, not one. A key embedded in a browser bundle must not also\n * be able to read the project's analytics, so the browser write key and the\n * server secret are separate rows with different reach.\n */\n clients: z.object({\n browser: z.object({\n id: z.string(),\n /** Public by design, and recoverable — it ships in page source anyway. */\n writeKey: z.string(),\n scopes: z.array(z.string()),\n }),\n server: z.object({\n id: z.string(),\n /** Plaintext, returned once. Absent when `created` is `false`. */\n clientSecret: z.string().optional(),\n scopes: z.array(z.string()),\n }),\n }),\n});\nexport type CreateProjectResponse = z.infer<typeof zCreateProjectResponse>;\n\nexport const zRotateClientSecretResponse = z.object({\n projectId: z.string(),\n clientId: z.string(),\n /** Plaintext, returned once. The previous secret stops working immediately. */\n clientSecret: z.string(),\n});\nexport type RotateClientSecretResponse = z.infer<typeof zRotateClientSecretResponse>;\n\nexport const zDeleteProjectInput = z.object({\n /**\n * A field in the body rather than `?purge=true`. This deletes the project's\n * Postgres rows and every analytics row behind it, with no undo — and a query\n * parameter is far too easy to fire from shell history or a mis-copied URL.\n */\n purge: z.literal(true),\n /** Checked against the project on record; a mismatch is refused. */\n externalId: zExternalId,\n});\nexport type DeleteProjectInput = z.input<typeof zDeleteProjectInput>;\n\nexport const zDeleteProjectResponse = z.object({\n projectId: z.string(),\n /** Idempotent, but it still says which of the two happened. */\n purge: z.enum([\"queued\", \"already-absent\"]),\n /** When the purge was marked. Absent for `already-absent`. */\n purgingAt: zInstant.optional(),\n});\nexport type DeleteProjectResponse = z.infer<typeof zDeleteProjectResponse>;\n\n// ─── Errors ─────────────────────────────────────────────────────────────────\n\n// ─── Event metadata ─────────────────────────────────────────────────────────\n\n/**\n * `PUT /api/v1/projects/{projectId}/events/{name}` — catalogue settings for\n * one event name. Provisioning calls it to mark `form.submitted` a conversion\n * and an operational probe `internal`; the call is idempotent so a retried\n * provisioning run is harmless.\n */\nexport const EVENT_META_STATUSES = [\"active\", \"deprecated\", \"archived\", \"internal\"] as const;\nexport const zEventMetaStatus = z.enum(EVENT_META_STATUSES);\nexport type EventMetaStatus = z.infer<typeof zEventMetaStatus>;\n\nexport const zEventName = z.string().min(1).max(200);\n\nexport const zUpsertEventMetaInput = z\n .object({\n conversion: z.boolean().optional(),\n /** `internal` events are left out of every metric unless a query opts in. */\n status: zEventMetaStatus.optional(),\n /** Property keys the hourly rollups break this event down by. */\n rollupProperties: z.array(z.string().min(1).max(200)).max(20).optional(),\n })\n .strict();\nexport type UpsertEventMetaInput = z.infer<typeof zUpsertEventMetaInput>;\n\nexport const zEventMetaResponse = z.object({\n projectId: z.string(),\n name: z.string(),\n conversion: z.boolean(),\n status: zEventMetaStatus,\n rollupProperties: z.array(z.string()),\n /** Fields this call actually changed; empty on a repeat. */\n changed: z.array(z.string()),\n updatedAt: zInstant,\n});\nexport type EventMetaResponse = z.infer<typeof zEventMetaResponse>;\n\nexport const CONTROL_ERROR_CODES = [\n /** 401 — missing or invalid platform key / organization key. */\n \"unauthorized\",\n /** 403 — the organization key belongs to a different organization. */\n \"organization_scope_mismatch\",\n /**\n * 409 — this externalId exists with a materially different configuration.\n * Only fields whose change alters results are compared (see the docs); a\n * renamed brand is not a conflict.\n */\n \"external_id_conflict\",\n /** 409 — the externalId in a delete body does not match the project's. */\n \"project_external_id_mismatch\",\n /** 404 — no such organization or project, on an endpoint that is not idempotent. */\n \"not_found\",\n /** 400 — input failed the schema. */\n \"validation_failed\",\n /** 429 — per-credential rate limit. */\n \"rate_limited\",\n /** 500 — unexpected server failure. */\n \"internal_error\",\n] as const;\nexport const zControlErrorCode = z.enum(CONTROL_ERROR_CODES);\nexport type ControlErrorCode = z.infer<typeof zControlErrorCode>;\n\nexport const zControlError = z.object({\n status: z.number().int(),\n code: zControlErrorCode,\n error: z.string(),\n message: z.string().optional(),\n errors: z.unknown().optional(),\n requestId: z.string().optional(),\n});\nexport type ControlError = z.infer<typeof zControlError>;\n\n/** Paths, so the client and the route handlers cannot drift apart. */\nexport const CONTROL_API_PATHS = {\n organizations: \"/api/v1/organizations\",\n organization: (id: string) => `/api/v1/organizations/${encodeURIComponent(id)}`,\n organizationKeys: (id: string) => `/api/v1/organizations/${encodeURIComponent(id)}/keys`,\n projects: \"/api/v1/projects\",\n project: (id: string) => `/api/v1/projects/${encodeURIComponent(id)}`,\n clientRotate: (projectId: string, clientId: string) =>\n `/api/v1/projects/${encodeURIComponent(projectId)}/clients/${encodeURIComponent(clientId)}/rotate`,\n projectEvent: (projectId: string, name: string) =>\n `/api/v1/projects/${encodeURIComponent(projectId)}/events/${encodeURIComponent(name)}`,\n} as const;\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  zDate,
3
3
  zInstant
4
- } from "./chunk-2LJ7JLDX.js";
4
+ } from "./chunk-2LZBZXBF.js";
5
5
 
6
6
  // src/query.ts
7
7
  import { z } from "zod";
@@ -22,6 +22,7 @@ var zChannelFilter = z.object({
22
22
  utmMediums: z.array(z.string()).optional(),
23
23
  utmCampaigns: z.array(z.string()).optional()
24
24
  }).optional();
25
+ var zQuerySource = z.enum(["rollup", "raw", "mixed"]);
25
26
  var zQueryMeta = z.object({
26
27
  projectId: z.string(),
27
28
  /** When the underlying computation actually ran. On a cache hit, when the cached value was computed. */
@@ -29,15 +30,23 @@ var zQueryMeta = z.object({
29
30
  /** Age of the returned computation, in seconds. `0` on a cache miss. */
30
31
  cacheAgeSeconds: z.number().int().nonnegative(),
31
32
  /**
32
- * How far behind materialization was when the computation ran, in seconds:
33
- * the age of the oldest raw event not yet materialized, or `0` when nothing
34
- * is pending.
33
+ * How far behind live materialization was when the computation ran, in
34
+ * seconds. `0` means no live event is pending; `null` means measurement
35
+ * failed and must never be interpreted as fresh.
35
36
  *
36
37
  * Deliberately not `now - max(events.created_at)` — that conflates "the
37
38
  * pipeline is behind" with "this project is quiet", and grows without bound
38
39
  * on an idle project.
39
40
  */
40
- ingestLagSeconds: z.number().int().nonnegative(),
41
+ ingestLagSeconds: z.number().int().nonnegative().nullable(),
42
+ /** Whether the freshness measurement itself succeeded. */
43
+ ingestLagStatus: z.enum(["known", "unknown"]),
44
+ /**
45
+ * Pending events older than the live replay window. Kept separate so an old
46
+ * permanently failing event neither pegs live lag nor disappears behind 0.
47
+ * `null` means the measurement failed.
48
+ */
49
+ ingestDeadLetterCount: z.number().int().nonnegative().nullable(),
41
50
  cached: z.boolean(),
42
51
  /**
43
52
  * The time zone actually used to place bucket and window boundaries — the
@@ -46,6 +55,14 @@ var zQueryMeta = z.object({
46
55
  * that is exactly the case a consumer needs to notice.
47
56
  */
48
57
  timezone: z.string(),
58
+ /**
59
+ * Where the numbers came from. `rollup`: pre-aggregated hourly tables;
60
+ * `raw`: a scan of the event/session tables; `mixed`: finished hours from
61
+ * the rollup, the hour still in progress from raw. Diagnostic — the values
62
+ * agree either way — but a consumer that expected `rollup` and sees `raw`
63
+ * has asked for a combination the rollups cannot serve.
64
+ */
65
+ source: zQuerySource,
49
66
  requestId: z.string()
50
67
  });
51
68
  function zQueryEnvelope(data) {
@@ -54,7 +71,12 @@ function zQueryEnvelope(data) {
54
71
  var zTimeseriesBreakdown = z.object({
55
72
  kind: z.literal("property"),
56
73
  key: z.string().min(1).max(200),
57
- topN: z.number().int().min(1).max(10).default(5)
74
+ topN: z.number().int().min(1).max(100).default(5)
75
+ });
76
+ var zEventPropertyFilter = z.object({
77
+ key: z.string().min(1).max(200),
78
+ operator: z.literal("eq").default("eq"),
79
+ value: z.string().min(1).max(500)
58
80
  });
59
81
  var zEventsTimeseriesInput = z.object({
60
82
  projectId: z.string().min(1),
@@ -63,6 +85,8 @@ var zEventsTimeseriesInput = z.object({
63
85
  granularity: zGranularity.default("day"),
64
86
  /** Omit to count every event in the window. */
65
87
  eventName: z.string().min(1).max(200).optional(),
88
+ /** Exact matches against top-level `properties` keys. Every item must match. */
89
+ propertyFilters: z.array(zEventPropertyFilter).max(10).optional(),
66
90
  breakdown: zTimeseriesBreakdown.optional(),
67
91
  /**
68
92
  * Cannot be combined with `breakdown` — the backend's breakdown time series
@@ -71,7 +95,15 @@ var zEventsTimeseriesInput = z.object({
71
95
  */
72
96
  segmentId: z.string().min(1).optional(),
73
97
  /** Omit to fall back to the project's configured zone, then to UTC. */
74
- timezone: zTimezone.optional()
98
+ timezone: zTimezone.optional(),
99
+ /**
100
+ * Events whose catalogue status is `internal` (operational probes) are
101
+ * left out by default. A probe that needs to find its own event sets this.
102
+ */
103
+ includeInternal: z.boolean().default(false)
104
+ }).refine((input) => input.startDate <= input.endDate, {
105
+ message: "endDate must be on or after startDate",
106
+ path: ["endDate"]
75
107
  });
76
108
  var zBucketStart = z.string().regex(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, "expected 'YYYY-MM-DD HH:MM:SS'");
77
109
  var zTimeseriesPoint = z.object({
@@ -108,7 +140,12 @@ var zFunnelInput = z.object({
108
140
  * this only moves the window — but a nine-hour window shift changes who is
109
141
  * in the funnel.
110
142
  */
111
- timezone: zTimezone.optional()
143
+ timezone: zTimezone.optional(),
144
+ /** Accepted for symmetry with events.timeseries; funnel steps are named explicitly, so nothing is filtered. */
145
+ includeInternal: z.boolean().default(false)
146
+ }).refine((input) => input.startDate <= input.endDate, {
147
+ message: "endDate must be on or after startDate",
148
+ path: ["endDate"]
112
149
  });
113
150
  var zFunnelStep = z.object({
114
151
  step: z.number().int().min(1),
@@ -156,7 +193,12 @@ var zRetentionInput = z.object({
156
193
  * cut on UTC days and cannot be re-cut after the fact — so expect a slower
157
194
  * query in exchange for cohorts that match the reader's calendar.
158
195
  */
159
- timezone: zTimezone.optional()
196
+ timezone: zTimezone.optional(),
197
+ /** Accepted for symmetry with events.timeseries; retention reads named or all-event activity and does not yet apply the internal filter. */
198
+ includeInternal: z.boolean().default(false)
199
+ }).refine((input) => input.startDate <= input.endDate, {
200
+ message: "endDate must be on or after startDate",
201
+ path: ["endDate"]
160
202
  });
161
203
  var zRetentionCell = z.object({
162
204
  /** `0` is the cohort's own period. */
@@ -184,6 +226,62 @@ var zRetentionData = z.object({
184
226
  timezone: z.string()
185
227
  });
186
228
  var zRetentionResponse = zQueryEnvelope(zRetentionData);
229
+ var zTrafficOverviewInput = z.object({
230
+ projectId: z.string().min(1),
231
+ startDate: zDate,
232
+ endDate: zDate,
233
+ granularity: zGranularity.default("day"),
234
+ timezone: zTimezone.optional()
235
+ }).refine((input) => input.startDate <= input.endDate, {
236
+ message: "endDate must be on or after startDate",
237
+ path: ["endDate"]
238
+ });
239
+ var zTrafficTotals = z.object({
240
+ sessions: z.number().int().nonnegative(),
241
+ pageviews: z.number().int().nonnegative(),
242
+ newVisitors: z.number().int().nonnegative(),
243
+ returningVisitors: z.number().int().nonnegative(),
244
+ /** Percent of sessions that bounced, two decimals. */
245
+ bounceRate: z.number()
246
+ });
247
+ var zTrafficSeriesPoint = zTrafficTotals.extend({ date: zBucketStart });
248
+ var zTrafficOverviewData = z.object({
249
+ granularity: zGranularity,
250
+ timezone: z.string(),
251
+ totals: zTrafficTotals,
252
+ series: z.array(zTrafficSeriesPoint)
253
+ });
254
+ var zTrafficOverviewResponse = zQueryEnvelope(zTrafficOverviewData);
255
+ var zTrafficDimension = z.enum(["channel", "referrer", "utm"]);
256
+ var zTrafficSourcesInput = z.object({
257
+ projectId: z.string().min(1),
258
+ startDate: zDate,
259
+ endDate: zDate,
260
+ /** `channel`: channel_type · `referrer`: referrer name+type · `utm`: source/medium/campaign. */
261
+ dimension: zTrafficDimension,
262
+ limit: z.number().int().min(1).max(100).default(10),
263
+ timezone: zTimezone.optional()
264
+ }).refine((input) => input.startDate <= input.endDate, {
265
+ message: "endDate must be on or after startDate",
266
+ path: ["endDate"]
267
+ });
268
+ var zTrafficSourceRow = zTrafficTotals.extend({
269
+ /** Display key for the row: the channel, the referrer name, or `source / medium / campaign`. */
270
+ key: z.string(),
271
+ channelType: z.string().optional(),
272
+ referrerName: z.string().optional(),
273
+ referrerType: z.string().optional(),
274
+ utmSource: z.string().optional(),
275
+ utmMedium: z.string().optional(),
276
+ utmCampaign: z.string().optional()
277
+ });
278
+ var zTrafficSourcesData = z.object({
279
+ dimension: zTrafficDimension,
280
+ timezone: z.string(),
281
+ /** Ordered by sessions, descending. */
282
+ rows: z.array(zTrafficSourceRow)
283
+ });
284
+ var zTrafficSourcesResponse = zQueryEnvelope(zTrafficSourcesData);
187
285
  var QUERY_ERROR_CODES = [
188
286
  /** 401 — unknown or mismatched credentials. */
189
287
  "unauthorized",
@@ -216,7 +314,9 @@ var zQueryError = z.object({
216
314
  var QUERY_API_PATHS = {
217
315
  eventsTimeseries: "/api/v1/query/events/timeseries",
218
316
  funnel: "/api/v1/query/funnel",
219
- retention: "/api/v1/query/retention"
317
+ retention: "/api/v1/query/retention",
318
+ trafficOverview: "/api/v1/query/traffic/overview",
319
+ trafficSources: "/api/v1/query/traffic/sources"
220
320
  };
221
321
 
222
322
  export {
@@ -224,9 +324,11 @@ export {
224
324
  zTimezone,
225
325
  isValidTimeZone,
226
326
  zChannelFilter,
327
+ zQuerySource,
227
328
  zQueryMeta,
228
329
  zQueryEnvelope,
229
330
  zTimeseriesBreakdown,
331
+ zEventPropertyFilter,
230
332
  zEventsTimeseriesInput,
231
333
  zBucketStart,
232
334
  zTimeseriesPoint,
@@ -241,9 +343,19 @@ export {
241
343
  zRetentionCell,
242
344
  zRetentionData,
243
345
  zRetentionResponse,
346
+ zTrafficOverviewInput,
347
+ zTrafficTotals,
348
+ zTrafficSeriesPoint,
349
+ zTrafficOverviewData,
350
+ zTrafficOverviewResponse,
351
+ zTrafficDimension,
352
+ zTrafficSourcesInput,
353
+ zTrafficSourceRow,
354
+ zTrafficSourcesData,
355
+ zTrafficSourcesResponse,
244
356
  QUERY_ERROR_CODES,
245
357
  zQueryErrorCode,
246
358
  zQueryError,
247
359
  QUERY_API_PATHS
248
360
  };
249
- //# sourceMappingURL=chunk-55IWO4S3.js.map
361
+ //# sourceMappingURL=chunk-BCGR27G2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/query.ts"],"sourcesContent":["/**\n * `POST /api/v1/query/*` — the read contract.\n *\n * Server credentials, project-scoped: the authenticated client's projectId must\n * equal the `projectId` in the body, or the request is refused. Reads are POSTs\n * because the inputs nest (funnel steps, channel filters) and would not survive\n * a query string; consumers cache on their own side, so HTTP cache semantics\n * buy nothing here.\n *\n * Response shapes mirror the existing dashboard computations exactly — this\n * plane re-exposes them, it does not add new math.\n */\nimport { z } from \"zod\";\nimport { zDate, zInstant } from \"./index.js\";\n\nexport { zDate, zInstant };\n\n/** Bucket width for a time series. */\nexport const zGranularity = z.enum([\"hour\", \"day\", \"week\", \"month\"]);\nexport type Granularity = z.infer<typeof zGranularity>;\n\n/**\n * IANA time zone name, e.g. `Asia/Seoul`. Decides two things at once: where a\n * calendar day starts, and therefore which bucket an event falls in.\n *\n * A Korean brand reading \"23 Aug: 120 visits\" means 23 Aug in Seoul. Computed\n * in UTC, that headline silently covers 23 Aug 09:00 through 24 Aug 09:00 —\n * wrong by nine hours in a way nothing on the page reveals.\n *\n * Validated against the runtime's own zone database rather than a regex, so an\n * unknown name fails here with a clear message instead of deep inside a query.\n */\nexport const zTimezone = z.string().min(1).max(64).refine(isValidTimeZone, { message: \"unknown IANA time zone\" });\n\nexport function isValidTimeZone(value: string): boolean {\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone: value });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Acquisition-channel filter. Every field is a set — an event matches when its\n * value is in the set; omitted fields do not constrain. Mirrors the dashboard's\n * `channelFilterSchema` field-for-field.\n */\nexport const zChannelFilter = z\n .object({\n referrerTypes: z.array(z.string()).optional(),\n channelTypes: z.array(z.string()).optional(),\n utmSources: z.array(z.string()).optional(),\n utmMediums: z.array(z.string()).optional(),\n utmCampaigns: z.array(z.string()).optional(),\n })\n .optional();\nexport type ChannelFilter = z.infer<typeof zChannelFilter>;\n\n// ─── Envelope ───────────────────────────────────────────────────────────────\n\n/**\n * Staleness is reported as two independent numbers, never as one sum, because\n * the two have different remedies: a stale cache resolves itself at the next\n * TTL, while ingest lag means the read model is genuinely behind and the right\n * response is an \"aggregating\" badge, not a refresh.\n */\nexport const zQuerySource = z.enum([\"rollup\", \"raw\", \"mixed\"]);\nexport type QuerySource = z.infer<typeof zQuerySource>;\n\nexport const zQueryMeta = z.object({\n projectId: z.string(),\n /** When the underlying computation actually ran. On a cache hit, when the cached value was computed. */\n computedAt: zInstant,\n /** Age of the returned computation, in seconds. `0` on a cache miss. */\n cacheAgeSeconds: z.number().int().nonnegative(),\n /**\n * How far behind live materialization was when the computation ran, in\n * seconds. `0` means no live event is pending; `null` means measurement\n * failed and must never be interpreted as fresh.\n *\n * Deliberately not `now - max(events.created_at)` — that conflates \"the\n * pipeline is behind\" with \"this project is quiet\", and grows without bound\n * on an idle project.\n */\n ingestLagSeconds: z.number().int().nonnegative().nullable(),\n /** Whether the freshness measurement itself succeeded. */\n ingestLagStatus: z.enum([\"known\", \"unknown\"]),\n /**\n * Pending events older than the live replay window. Kept separate so an old\n * permanently failing event neither pegs live lag nor disappears behind 0.\n * `null` means the measurement failed.\n */\n ingestDeadLetterCount: z.number().int().nonnegative().nullable(),\n cached: z.boolean(),\n /**\n * The time zone actually used to place bucket and window boundaries — the\n * resolved value, not the requested one. Read it rather than assuming the\n * request was honoured: when no zone is given anywhere it is `\"UTC\"`, and\n * that is exactly the case a consumer needs to notice.\n */\n timezone: z.string(),\n /**\n * Where the numbers came from. `rollup`: pre-aggregated hourly tables;\n * `raw`: a scan of the event/session tables; `mixed`: finished hours from\n * the rollup, the hour still in progress from raw. Diagnostic — the values\n * agree either way — but a consumer that expected `rollup` and sees `raw`\n * has asked for a combination the rollups cannot serve.\n */\n source: zQuerySource,\n requestId: z.string(),\n});\nexport type QueryMeta = z.infer<typeof zQueryMeta>;\n\n/** Every query response is `{ data, meta }`. */\nexport function zQueryEnvelope<T extends z.ZodTypeAny>(data: T) {\n return z.object({ data, meta: zQueryMeta });\n}\n\n// ─── events.timeseries ──────────────────────────────────────────────────────\n\n/**\n * v1 breaks down by `properties` JSON keys only. Built-in columns\n * (country / os / browser / utm_*) have a totals breakdown in the backend but\n * no *time series* variant, and this plane does not add new computations.\n */\nexport const zTimeseriesBreakdown = z.object({\n kind: z.literal(\"property\"),\n key: z.string().min(1).max(200),\n topN: z.number().int().min(1).max(100).default(5),\n});\nexport type TimeseriesBreakdown = z.infer<typeof zTimeseriesBreakdown>;\n\n/** Exact-match filter for a top-level event `properties` key. Multiple filters are ANDed. */\nexport const zEventPropertyFilter = z.object({\n key: z.string().min(1).max(200),\n operator: z.literal(\"eq\").default(\"eq\"),\n value: z.string().min(1).max(500),\n});\nexport type EventPropertyFilter = z.infer<typeof zEventPropertyFilter>;\n\nexport const zEventsTimeseriesInput = z\n .object({\n projectId: z.string().min(1),\n startDate: zDate,\n endDate: zDate,\n granularity: zGranularity.default(\"day\"),\n /** Omit to count every event in the window. */\n eventName: z.string().min(1).max(200).optional(),\n /** Exact matches against top-level `properties` keys. Every item must match. */\n propertyFilters: z.array(zEventPropertyFilter).max(10).optional(),\n breakdown: zTimeseriesBreakdown.optional(),\n /**\n * Cannot be combined with `breakdown` — the backend's breakdown time series\n * takes no segment. Sending both is refused with `unsupported_combination`\n * rather than silently ignoring one of them.\n */\n segmentId: z.string().min(1).optional(),\n /** Omit to fall back to the project's configured zone, then to UTC. */\n timezone: zTimezone.optional(),\n /**\n * Events whose catalogue status is `internal` (operational probes) are\n * left out by default. A probe that needs to find its own event sets this.\n */\n includeInternal: z.boolean().default(false),\n })\n .refine((input) => input.startDate <= input.endDate, {\n message: \"endDate must be on or after startDate\",\n path: [\"endDate\"],\n });\nexport type EventsTimeseriesInput = z.input<typeof zEventsTimeseriesInput>;\n\n/**\n * Bucket start as wall-clock time in `meta.timezone`, `YYYY-MM-DD HH:MM:SS`.\n *\n * Pinned rather than left to the backend, because a consumer keys its chart on\n * this string: an unspecified format is one where two backends, or two\n * granularities, can quietly disagree and produce two different keys for the\n * same bucket.\n *\n * It is wall time, not an instant — for `Asia/Seoul` a day bucket reads\n * `2026-08-24 00:00:00`, which is `2026-08-23T15:00:00Z`. Day, week and month\n * buckets always carry `00:00:00`, so slicing the first ten characters is a\n * safe way to get a date key.\n */\nexport const zBucketStart = z.string().regex(/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$/, \"expected 'YYYY-MM-DD HH:MM:SS'\");\n\nexport const zTimeseriesPoint = z.object({\n date: zBucketStart,\n count: z.number().int().nonnegative(),\n});\nexport type TimeseriesPoint = z.infer<typeof zTimeseriesPoint>;\n\nexport const zEventsTimeseriesData = z.object({\n granularity: zGranularity,\n /** Zone the buckets were cut in. Mirrors `meta.timezone`. */\n timezone: z.string(),\n /** Total per bucket. Always present, with or without a breakdown. */\n series: z.array(zTimeseriesPoint),\n /** Present only when `breakdown` was requested: the top-N values, each with its own series. */\n breakdown: z\n .array(\n z.object({\n value: z.string(),\n points: z.array(zTimeseriesPoint),\n })\n )\n .optional(),\n});\nexport type EventsTimeseriesData = z.infer<typeof zEventsTimeseriesData>;\n\nexport const zEventsTimeseriesResponse = zQueryEnvelope(zEventsTimeseriesData);\nexport type EventsTimeseriesResponse = z.infer<typeof zEventsTimeseriesResponse>;\n\n// ─── funnel ─────────────────────────────────────────────────────────────────\n\nexport const zFunnelInput = z\n .object({\n projectId: z.string().min(1),\n /** Ordered event names. A user counts at step k if they reached at least step k. */\n steps: z.array(z.string().min(1).max(200)).min(2).max(10),\n startDate: zDate,\n endDate: zDate,\n /** Max seconds between the first and last step for a conversion to count. Default 24h. */\n windowSeconds: z.number().int().min(60).max(2_592_000).default(86_400),\n segmentId: z.string().min(1).optional(),\n channelFilter: zChannelFilter,\n /**\n * Decides where `startDate` / `endDate` fall. A funnel has no buckets, so\n * this only moves the window — but a nine-hour window shift changes who is\n * in the funnel.\n */\n timezone: zTimezone.optional(),\n /** Accepted for symmetry with events.timeseries; funnel steps are named explicitly, so nothing is filtered. */\n includeInternal: z.boolean().default(false),\n })\n .refine((input) => input.startDate <= input.endDate, {\n message: \"endDate must be on or after startDate\",\n path: [\"endDate\"],\n });\nexport type FunnelInput = z.input<typeof zFunnelInput>;\n\nexport const zFunnelStep = z.object({\n step: z.number().int().min(1),\n name: z.string(),\n users: z.number().int().nonnegative(),\n /** Percent of the previous step that reached this one. `100` for step 1. */\n conversionRate: z.number(),\n /** Percent of the previous step that did not. `0` for step 1. */\n dropoffRate: z.number(),\n dropoffUsers: z.number().int().nonnegative(),\n});\n\nexport const zFunnelData = z.object({\n steps: z.array(zFunnelStep),\n totalUsers: z.number().int().nonnegative(),\n /** Percent of step-1 users that reached the last step. */\n overallConversionRate: z.number(),\n});\nexport type FunnelData = z.infer<typeof zFunnelData>;\n\nexport const zFunnelResponse = zQueryEnvelope(zFunnelData);\nexport type FunnelResponse = z.infer<typeof zFunnelResponse>;\n\n// ─── retention ──────────────────────────────────────────────────────────────\n\nexport const zRetentionInterval = z.enum([\"day\", \"week\", \"month\"]);\n\nexport const zRetentionInput = z\n .object({\n projectId: z.string().min(1),\n startDate: zDate,\n endDate: zDate,\n interval: zRetentionInterval.default(\"day\"),\n periods: z.number().int().min(1).max(30).default(7),\n startEvent: z.string().min(1).max(200).optional(),\n returnEvent: z.string().min(1).max(200).optional(),\n /** `bounded`: returned in exactly period N. `unbounded`: returned in period N or later. */\n retentionType: z.enum([\"bounded\", \"unbounded\"]).default(\"bounded\"),\n /**\n * `first-ever` (default): the cohort is everyone whose true first activity\n * landed in the window, read from the first-seen aggregate. `startEvent`,\n * `segmentId` and `channelFilter` then constrain only the *returning* side.\n * `first-in-window`: the cohort is selected by scanning events in the window\n * with all filters applied. Slower, but filters apply to cohort selection.\n */\n cohortMode: z.enum([\"first-ever\", \"first-in-window\"]).default(\"first-ever\"),\n segmentId: z.string().min(1).optional(),\n channelFilter: zChannelFilter,\n /**\n * Decides where a cohort day starts, and so which period a return falls in.\n *\n * Honoured under both cohort modes. Note that a non-UTC zone makes\n * `first-ever` read raw events instead of its daily rollup — the rollup is\n * cut on UTC days and cannot be re-cut after the fact — so expect a slower\n * query in exchange for cohorts that match the reader's calendar.\n */\n timezone: zTimezone.optional(),\n /** Accepted for symmetry with events.timeseries; retention reads named or all-event activity and does not yet apply the internal filter. */\n includeInternal: z.boolean().default(false),\n })\n .refine((input) => input.startDate <= input.endDate, {\n message: \"endDate must be on or after startDate\",\n path: [\"endDate\"],\n });\nexport type RetentionInput = z.input<typeof zRetentionInput>;\n\nexport const zRetentionCell = z.object({\n /** `0` is the cohort's own period. */\n period: z.number().int().nonnegative(),\n users: z.number().int().nonnegative(),\n /** Percent of `cohortSize`. */\n rate: z.number(),\n});\n\nexport const zRetentionData = z.object({\n matrix: z.array(\n z.object({\n /** Cohort bucket start, same shape and zone as {@link zBucketStart}. */\n cohortDate: zBucketStart,\n cohortSize: z.number().int().nonnegative(),\n /** Length is `periods + 1`, index-aligned to `period`. */\n retention: z.array(zRetentionCell),\n })\n ),\n /** Unweighted mean rate per period across cohorts. */\n averageRetention: z.array(z.object({ period: z.number().int().nonnegative(), rate: z.number() })),\n interval: zRetentionInterval,\n periods: z.number().int(),\n retentionType: z.enum([\"bounded\", \"unbounded\"]),\n /** Zone the cohort days were cut in. Mirrors `meta.timezone`. */\n timezone: z.string(),\n});\nexport type RetentionData = z.infer<typeof zRetentionData>;\n\nexport const zRetentionResponse = zQueryEnvelope(zRetentionData);\nexport type RetentionResponse = z.infer<typeof zRetentionResponse>;\n\n// ─── traffic ────────────────────────────────────────────────────────────────\n\n/**\n * Session-level traffic for a brand site, served from the hourly rollups.\n * `newVisitors` counts sessions whose device was first seen inside them;\n * `returningVisitors` is the rest. Both are session counts, not people —\n * \"visitor\" is the vocabulary the dashboards already use.\n */\nexport const zTrafficOverviewInput = z\n .object({\n projectId: z.string().min(1),\n startDate: zDate,\n endDate: zDate,\n granularity: zGranularity.default(\"day\"),\n timezone: zTimezone.optional(),\n })\n .refine((input) => input.startDate <= input.endDate, {\n message: \"endDate must be on or after startDate\",\n path: [\"endDate\"],\n });\nexport type TrafficOverviewInput = z.input<typeof zTrafficOverviewInput>;\n\nexport const zTrafficTotals = z.object({\n sessions: z.number().int().nonnegative(),\n pageviews: z.number().int().nonnegative(),\n newVisitors: z.number().int().nonnegative(),\n returningVisitors: z.number().int().nonnegative(),\n /** Percent of sessions that bounced, two decimals. */\n bounceRate: z.number(),\n});\nexport type TrafficTotals = z.infer<typeof zTrafficTotals>;\n\nexport const zTrafficSeriesPoint = zTrafficTotals.extend({ date: zBucketStart });\n\nexport const zTrafficOverviewData = z.object({\n granularity: zGranularity,\n timezone: z.string(),\n totals: zTrafficTotals,\n series: z.array(zTrafficSeriesPoint),\n});\nexport type TrafficOverviewData = z.infer<typeof zTrafficOverviewData>;\nexport const zTrafficOverviewResponse = zQueryEnvelope(zTrafficOverviewData);\nexport type TrafficOverviewResponse = z.infer<typeof zTrafficOverviewResponse>;\n\nexport const zTrafficDimension = z.enum([\"channel\", \"referrer\", \"utm\"]);\nexport type TrafficDimension = z.infer<typeof zTrafficDimension>;\n\nexport const zTrafficSourcesInput = z\n .object({\n projectId: z.string().min(1),\n startDate: zDate,\n endDate: zDate,\n /** `channel`: channel_type · `referrer`: referrer name+type · `utm`: source/medium/campaign. */\n dimension: zTrafficDimension,\n limit: z.number().int().min(1).max(100).default(10),\n timezone: zTimezone.optional(),\n })\n .refine((input) => input.startDate <= input.endDate, {\n message: \"endDate must be on or after startDate\",\n path: [\"endDate\"],\n });\nexport type TrafficSourcesInput = z.input<typeof zTrafficSourcesInput>;\n\nexport const zTrafficSourceRow = zTrafficTotals.extend({\n /** Display key for the row: the channel, the referrer name, or `source / medium / campaign`. */\n key: z.string(),\n channelType: z.string().optional(),\n referrerName: z.string().optional(),\n referrerType: z.string().optional(),\n utmSource: z.string().optional(),\n utmMedium: z.string().optional(),\n utmCampaign: z.string().optional(),\n});\nexport type TrafficSourceRow = z.infer<typeof zTrafficSourceRow>;\n\nexport const zTrafficSourcesData = z.object({\n dimension: zTrafficDimension,\n timezone: z.string(),\n /** Ordered by sessions, descending. */\n rows: z.array(zTrafficSourceRow),\n});\nexport type TrafficSourcesData = z.infer<typeof zTrafficSourcesData>;\nexport const zTrafficSourcesResponse = zQueryEnvelope(zTrafficSourcesData);\nexport type TrafficSourcesResponse = z.infer<typeof zTrafficSourcesResponse>;\n\n// ─── Errors ─────────────────────────────────────────────────────────────────\n\nexport const QUERY_ERROR_CODES = [\n /** 401 — unknown or mismatched credentials. */\n \"unauthorized\",\n /** 403 — the credential's project is not the project asked about. */\n \"project_scope_mismatch\",\n /** 403 — the credential lacks the `query` scope. Provision a query-scoped client. */\n \"query_scope_missing\",\n /** 400 — input failed the schema. `errors` carries the zod issues. */\n \"validation_failed\",\n /** 400 — inputs are individually valid but cannot be served together (e.g. breakdown + segmentId). */\n \"unsupported_combination\",\n /** 410 — the project is being deleted. Permanent; stop asking. */\n \"project_purging\",\n /** 429 — per-credential query rate limit. `Retry-After` set. */\n \"rate_limited\",\n /** 504 — the analytics backend exceeded its statement timeout. Narrow the window. */\n \"query_timeout\",\n /** 500 — unexpected server failure. */\n \"internal_error\",\n] as const;\nexport const zQueryErrorCode = z.enum(QUERY_ERROR_CODES);\nexport type QueryErrorCode = z.infer<typeof zQueryErrorCode>;\n\nexport const zQueryError = z.object({\n status: z.number().int(),\n code: zQueryErrorCode,\n error: z.string(),\n message: z.string().optional(),\n errors: z.unknown().optional(),\n requestId: z.string().optional(),\n});\nexport type QueryError = z.infer<typeof zQueryError>;\n\n/** Paths, so the client and the route handlers cannot drift apart. */\nexport const QUERY_API_PATHS = {\n eventsTimeseries: \"/api/v1/query/events/timeseries\",\n funnel: \"/api/v1/query/funnel\",\n retention: \"/api/v1/query/retention\",\n trafficOverview: \"/api/v1/query/traffic/overview\",\n trafficSources: \"/api/v1/query/traffic/sources\",\n} as const;\n"],"mappings":"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","names":[]}
@@ -4,9 +4,10 @@ var RESERVED_EVENT_NAMES = ["session_start", "session_end", "screen_view"];
4
4
  var MAX_TRACK_PAYLOAD_BYTES = 512e3;
5
5
  var zClientEventMetadata = {
6
6
  /**
7
- * Client-generated UUID. This is the idempotency key: it becomes the
8
- * `RawEvent` primary key, scoped to the project. Re-sending the same
9
- * eventId is always safe and always reported as a duplicate.
7
+ * Client-generated UUID. This is the project-scoped idempotency key stored as
8
+ * `RawEvent.clientEventId`; the server uses a separate globally unique raw
9
+ * event id internally. Re-sending the same eventId to the same project is
10
+ * always safe and always reported as a duplicate.
10
11
  */
11
12
  eventId: z.uuid(),
12
13
  /** Event time, epoch milliseconds. */
@@ -150,4 +151,4 @@ export {
150
151
  zIngestErrorCode,
151
152
  zIngestError
152
153
  };
153
- //# sourceMappingURL=chunk-3A7WZ7L6.js.map
154
+ //# sourceMappingURL=chunk-MFZEIH5Z.js.map