@reopt-ai/data-contract 0.5.0 → 0.6.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 (73) hide show
  1. package/dist/{chunk-2LZBZXBF.js → chunk-33BCA2LH.js} +1 -15
  2. package/dist/chunk-33BCA2LH.js.map +1 -0
  3. package/dist/chunk-3EEANNYN.cjs +62 -0
  4. package/dist/chunk-3EEANNYN.cjs.map +1 -0
  5. package/dist/{chunk-MFZEIH5Z.js → chunk-EZ2TA4S6.js} +31 -6
  6. package/dist/chunk-EZ2TA4S6.js.map +1 -0
  7. package/dist/chunk-FP2X3PRK.js +62 -0
  8. package/dist/chunk-FP2X3PRK.js.map +1 -0
  9. package/dist/{chunk-VQ2CD5MG.cjs → chunk-FUGY5ANJ.cjs} +13 -13
  10. package/dist/{chunk-VQ2CD5MG.cjs.map → chunk-FUGY5ANJ.cjs.map} +1 -1
  11. package/dist/chunk-GCQZNME3.js +153 -0
  12. package/dist/chunk-GCQZNME3.js.map +1 -0
  13. package/dist/chunk-HLQAZ6JW.js +19 -0
  14. package/dist/chunk-HLQAZ6JW.js.map +1 -0
  15. package/dist/{chunk-AHXPQQKO.js → chunk-IY4MXH35.js} +3 -3
  16. package/dist/chunk-JB4MJQX7.cjs +19 -0
  17. package/dist/chunk-JB4MJQX7.cjs.map +1 -0
  18. package/dist/{chunk-BCGR27G2.js → chunk-L3WJGIKD.js} +2 -2
  19. package/dist/{chunk-4B4AY44F.cjs → chunk-LA5F5KR6.cjs} +6 -6
  20. package/dist/{chunk-4B4AY44F.cjs.map → chunk-LA5F5KR6.cjs.map} +1 -1
  21. package/dist/chunk-PHPTCLRU.cjs +153 -0
  22. package/dist/chunk-PHPTCLRU.cjs.map +1 -0
  23. package/dist/{chunk-XI3VP5DN.cjs → chunk-VGB2VJO4.cjs} +34 -9
  24. package/dist/chunk-VGB2VJO4.cjs.map +1 -0
  25. package/dist/{chunk-OLSMH4RY.cjs → chunk-YSAEOYPD.cjs} +2 -16
  26. package/dist/chunk-YSAEOYPD.cjs.map +1 -0
  27. package/dist/client.cjs +53 -49
  28. package/dist/client.cjs.map +1 -1
  29. package/dist/client.d.cts +2 -0
  30. package/dist/client.d.ts +2 -0
  31. package/dist/client.js +9 -5
  32. package/dist/client.js.map +1 -1
  33. package/dist/control.cjs +5 -4
  34. package/dist/control.cjs.map +1 -1
  35. package/dist/control.d.cts +6 -6
  36. package/dist/control.d.ts +6 -6
  37. package/dist/control.js +4 -3
  38. package/dist/events.cjs +17 -0
  39. package/dist/events.cjs.map +1 -0
  40. package/dist/events.d.cts +59 -0
  41. package/dist/events.d.ts +59 -0
  42. package/dist/events.js +17 -0
  43. package/dist/events.js.map +1 -0
  44. package/dist/identity.cjs +57 -0
  45. package/dist/identity.cjs.map +1 -0
  46. package/dist/identity.d.cts +119 -0
  47. package/dist/identity.d.ts +119 -0
  48. package/dist/identity.js +57 -0
  49. package/dist/identity.js.map +1 -0
  50. package/dist/index.cjs +4 -2
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +5 -15
  53. package/dist/index.d.ts +5 -15
  54. package/dist/index.js +7 -5
  55. package/dist/ingest.cjs +18 -2
  56. package/dist/ingest.cjs.map +1 -1
  57. package/dist/ingest.d.cts +39 -9
  58. package/dist/ingest.d.ts +39 -9
  59. package/dist/ingest.js +18 -2
  60. package/dist/query.cjs +4 -3
  61. package/dist/query.cjs.map +1 -1
  62. package/dist/query.d.cts +17 -16
  63. package/dist/query.d.ts +17 -16
  64. package/dist/query.js +3 -2
  65. package/dist/version-BzeRTpta.d.cts +26 -0
  66. package/dist/version-BzeRTpta.d.ts +26 -0
  67. package/package.json +13 -2
  68. package/dist/chunk-2LZBZXBF.js.map +0 -1
  69. package/dist/chunk-MFZEIH5Z.js.map +0 -1
  70. package/dist/chunk-OLSMH4RY.cjs.map +0 -1
  71. package/dist/chunk-XI3VP5DN.cjs.map +0 -1
  72. /package/dist/{chunk-AHXPQQKO.js.map → chunk-IY4MXH35.js.map} +0 -0
  73. /package/dist/{chunk-BCGR27G2.js.map → chunk-L3WJGIKD.js.map} +0 -0
@@ -1,12 +1,5 @@
1
1
  // src/index.ts
2
2
  import { z } from "zod";
3
- var CONTRACT_VERSION = "0.5.0";
4
- var CONTRACT_VERSION_HEADER = "x-reopt-contract-version";
5
- var CLIENT_ID_HEADER = "reopt-client-id";
6
- var CLIENT_SECRET_HEADER = "reopt-client-secret";
7
- var WRITE_KEY_HEADER = "reopt-write-key";
8
- var DEVICE_ID_HEADER = "reopt-device-id";
9
- var REQUEST_ID_HEADER = "x-request-id";
10
3
  var zDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected YYYY-MM-DD").refine(isRealCalendarDate, "date does not exist");
11
4
  function isRealCalendarDate(value) {
12
5
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
@@ -53,15 +46,8 @@ var DataApiError = class _DataApiError extends Error {
53
46
  };
54
47
 
55
48
  export {
56
- CONTRACT_VERSION,
57
- CONTRACT_VERSION_HEADER,
58
- CLIENT_ID_HEADER,
59
- CLIENT_SECRET_HEADER,
60
- WRITE_KEY_HEADER,
61
- DEVICE_ID_HEADER,
62
- REQUEST_ID_HEADER,
63
49
  zDate,
64
50
  zInstant,
65
51
  DataApiError
66
52
  };
67
- //# sourceMappingURL=chunk-2LZBZXBF.js.map
53
+ //# sourceMappingURL=chunk-33BCA2LH.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 * `@reopt-ai/data-contract/events` event names + property keys (no zod)\n * `@reopt-ai/data-contract/identity` cookie/header names and encodings shared by\n * the SDKs, the Next.js proxy and ingest\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\nexport {\n CONTRACT_VERSION,\n CONTRACT_VERSION_HEADER,\n CLIENT_ID_HEADER,\n CLIENT_SECRET_HEADER,\n WRITE_KEY_HEADER,\n DEVICE_ID_HEADER,\n REQUEST_ID_HEADER,\n} from \"./version.js\";\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":";AAiBA,SAAS,SAAS;AAgBX,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":[]}
@@ -0,0 +1,62 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/events.ts
2
+ var RESERVED_EVENT_NAMES = ["session_start", "session_end", "screen_view"];
3
+ var AUTO_EVENT_NAMES = {
4
+ pageview: "$pageview",
5
+ screenView: "$screen_view",
6
+ pageleave: "$pageleave",
7
+ webVitals: "$web_vitals",
8
+ exception: "$exception"
9
+ };
10
+ var AUTO_EVENT_PROPERTIES = {
11
+ [AUTO_EVENT_NAMES.pageview]: [
12
+ "path",
13
+ "origin",
14
+ "title",
15
+ "referrer",
16
+ "search",
17
+ "utm_source",
18
+ "utm_medium",
19
+ "utm_campaign",
20
+ "utm_term",
21
+ "utm_content"
22
+ ],
23
+ [AUTO_EVENT_NAMES.screenView]: ["screen_name"],
24
+ [AUTO_EVENT_NAMES.pageleave]: ["path", "origin", "duration", "scroll_depth", "scroll_depth_bucket"],
25
+ [AUTO_EVENT_NAMES.webVitals]: ["metric_name", "metric_id", "value", "delta", "rating", "navigation_type", "path"],
26
+ // `$`-prefixed on purpose: these are reserved, SDK-owned keys with fixed
27
+ // semantics, distinguished from a host's own snake_case vocabulary the
28
+ // same way the event names are. Not a typo — do not strip the prefix.
29
+ [AUTO_EVENT_NAMES.exception]: [
30
+ "$exception_type",
31
+ "$exception_message",
32
+ "$exception_stack",
33
+ "$exception_source",
34
+ "$exception_handled",
35
+ "path"
36
+ ]
37
+ };
38
+ var AUTO_EVENT_ROLLUP_KEYS = {
39
+ [AUTO_EVENT_NAMES.pageview]: ["path", "origin", "referrer", "utm_source", "utm_medium", "utm_campaign"],
40
+ [AUTO_EVENT_NAMES.screenView]: ["screen_name"],
41
+ [AUTO_EVENT_NAMES.pageleave]: ["path", "origin", "scroll_depth_bucket"],
42
+ [AUTO_EVENT_NAMES.webVitals]: ["metric_name", "rating", "navigation_type", "path"],
43
+ [AUTO_EVENT_NAMES.exception]: ["$exception_type", "$exception_source", "$exception_handled", "path"]
44
+ };
45
+ var SCROLL_DEPTH_BUCKETS = [25, 50, 75, 100];
46
+ function scrollDepthBucket(depth) {
47
+ let reached = "0";
48
+ for (const threshold of SCROLL_DEPTH_BUCKETS) {
49
+ if (depth >= threshold) reached = `${threshold}`;
50
+ }
51
+ return reached;
52
+ }
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+ exports.RESERVED_EVENT_NAMES = RESERVED_EVENT_NAMES; exports.AUTO_EVENT_NAMES = AUTO_EVENT_NAMES; exports.AUTO_EVENT_PROPERTIES = AUTO_EVENT_PROPERTIES; exports.AUTO_EVENT_ROLLUP_KEYS = AUTO_EVENT_ROLLUP_KEYS; exports.SCROLL_DEPTH_BUCKETS = SCROLL_DEPTH_BUCKETS; exports.scrollDepthBucket = scrollDepthBucket;
62
+ //# sourceMappingURL=chunk-3EEANNYN.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-3EEANNYN.cjs","../src/events.ts"],"names":[],"mappings":"AAAA;ACOO,IAAM,qBAAA,EAAuB,CAAC,eAAA,EAAiB,aAAA,EAAe,aAAa,CAAA;AAQ3E,IAAM,iBAAA,EAAmB;AAAA,EAC9B,QAAA,EAAU,WAAA;AAAA,EACV,UAAA,EAAY,cAAA;AAAA,EACZ,SAAA,EAAW,YAAA;AAAA,EACX,SAAA,EAAW,aAAA;AAAA,EACX,SAAA,EAAW;AACb,CAAA;AASO,IAAM,sBAAA,EAAwB;AAAA,EACnC,CAAC,gBAAA,CAAiB,QAAQ,CAAA,EAAG;AAAA,IAC3B,MAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,EACF,CAAA;AAAA,EACA,CAAC,gBAAA,CAAiB,UAAU,CAAA,EAAG,CAAC,aAAa,CAAA;AAAA,EAC7C,CAAC,gBAAA,CAAiB,SAAS,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,UAAA,EAAY,cAAA,EAAgB,qBAAqB,CAAA;AAAA,EAClG,CAAC,gBAAA,CAAiB,SAAS,CAAA,EAAG,CAAC,aAAA,EAAe,WAAA,EAAa,OAAA,EAAS,OAAA,EAAS,QAAA,EAAU,iBAAA,EAAmB,MAAM,CAAA;AAAA;AAAA;AAAA;AAAA,EAIhH,CAAC,gBAAA,CAAiB,SAAS,CAAA,EAAG;AAAA,IAC5B,iBAAA;AAAA,IACA,oBAAA;AAAA,IACA,kBAAA;AAAA,IACA,mBAAA;AAAA,IACA,oBAAA;AAAA,IACA;AAAA,EACF;AACF,CAAA;AAUO,IAAM,uBAAA,EAAyB;AAAA,EACpC,CAAC,gBAAA,CAAiB,QAAQ,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,UAAA,EAAY,YAAA,EAAc,YAAA,EAAc,cAAc,CAAA;AAAA,EACtG,CAAC,gBAAA,CAAiB,UAAU,CAAA,EAAG,CAAC,aAAa,CAAA;AAAA,EAC7C,CAAC,gBAAA,CAAiB,SAAS,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAA,EAAU,qBAAqB,CAAA;AAAA,EACtE,CAAC,gBAAA,CAAiB,SAAS,CAAA,EAAG,CAAC,aAAA,EAAe,QAAA,EAAU,iBAAA,EAAmB,MAAM,CAAA;AAAA,EACjF,CAAC,gBAAA,CAAiB,SAAS,CAAA,EAAG,CAAC,iBAAA,EAAmB,mBAAA,EAAqB,oBAAA,EAAsB,MAAM;AACrG,CAAA;AAOO,IAAM,qBAAA,EAAuB,CAAC,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,GAAG,CAAA;AAG7C,SAAS,iBAAA,CAAkB,KAAA,EAAkC;AAClE,EAAA,IAAI,QAAA,EAA6B,GAAA;AACjC,EAAA,IAAA,CAAA,MAAW,UAAA,GAAa,oBAAA,EAAsB;AAC5C,IAAA,GAAA,CAAI,MAAA,GAAS,SAAA,EAAW,QAAA,EAAU,CAAA,EAAA;AACpC,EAAA;AACO,EAAA;AACT;ADrCuC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-3EEANNYN.cjs","sourcesContent":[null,"/**\n * `@reopt-ai/data-contract/events` — event names and property keys, with no\n * zod. Browser SDKs import this so their production bundles stay free of\n * schema code; `./ingest` re-exports everything here.\n */\n\n/** Event names the pipeline reserves for its own session bookkeeping. */\nexport const RESERVED_EVENT_NAMES = [\"session_start\", \"session_end\", \"screen_view\"] as const;\n\n/**\n * Events the SDKs emit on their own. One declaration, so a consumer that\n * registers them in its event catalogue (to make them roll up) cannot drift\n * from what the SDK actually sends — a drifted name silently falls back to\n * raw queries, which is slower but not visibly wrong.\n */\nexport const AUTO_EVENT_NAMES = {\n pageview: \"$pageview\",\n screenView: \"$screen_view\",\n pageleave: \"$pageleave\",\n webVitals: \"$web_vitals\",\n exception: \"$exception\",\n} as const;\nexport type AutoEventName = (typeof AUTO_EVENT_NAMES)[keyof typeof AUTO_EVENT_NAMES];\n\n/**\n * Property keys each automatic event carries. Consumers derive their\n * breakdown / rollup property lists from here rather than retyping them.\n * `path`, `origin`, `referrer` and `utm_*` are the keys the ingest pipeline\n * reads into session and event columns.\n */\nexport const AUTO_EVENT_PROPERTIES = {\n [AUTO_EVENT_NAMES.pageview]: [\n \"path\",\n \"origin\",\n \"title\",\n \"referrer\",\n \"search\",\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_term\",\n \"utm_content\",\n ],\n [AUTO_EVENT_NAMES.screenView]: [\"screen_name\"],\n [AUTO_EVENT_NAMES.pageleave]: [\"path\", \"origin\", \"duration\", \"scroll_depth\", \"scroll_depth_bucket\"],\n [AUTO_EVENT_NAMES.webVitals]: [\"metric_name\", \"metric_id\", \"value\", \"delta\", \"rating\", \"navigation_type\", \"path\"],\n // `$`-prefixed on purpose: these are reserved, SDK-owned keys with fixed\n // semantics, distinguished from a host's own snake_case vocabulary the\n // same way the event names are. Not a typo — do not strip the prefix.\n [AUTO_EVENT_NAMES.exception]: [\n \"$exception_type\",\n \"$exception_message\",\n \"$exception_stack\",\n \"$exception_source\",\n \"$exception_handled\",\n \"path\",\n ],\n} as const satisfies Record<AutoEventName, readonly string[]>;\n\n/**\n * The subset of {@link AUTO_EVENT_PROPERTIES} that is a sensible rollup\n * dimension: low cardinality, categorical. Per-measurement ids and\n * continuous values (`metric_id`, `value`, `delta`, `duration`,\n * `scroll_depth`) are deliberately absent — as dimensions they would make\n * the rollup larger than the raw table it summarises. Derive\n * `rollupProperties` from here, not from the full property list.\n */\nexport const AUTO_EVENT_ROLLUP_KEYS = {\n [AUTO_EVENT_NAMES.pageview]: [\"path\", \"origin\", \"referrer\", \"utm_source\", \"utm_medium\", \"utm_campaign\"],\n [AUTO_EVENT_NAMES.screenView]: [\"screen_name\"],\n [AUTO_EVENT_NAMES.pageleave]: [\"path\", \"origin\", \"scroll_depth_bucket\"],\n [AUTO_EVENT_NAMES.webVitals]: [\"metric_name\", \"rating\", \"navigation_type\", \"path\"],\n [AUTO_EVENT_NAMES.exception]: [\"$exception_type\", \"$exception_source\", \"$exception_handled\", \"path\"],\n} as const satisfies Record<AutoEventName, readonly string[]>;\n\n/**\n * `$pageleave.scroll_depth` is the exact 0–100 percentage; `scroll_depth_bucket`\n * is the deepest of these thresholds reached, as a string — the value to\n * break down by. `\"0\"` means the visitor never reached the first threshold.\n */\nexport const SCROLL_DEPTH_BUCKETS = [25, 50, 75, 100] as const;\nexport type ScrollDepthBucket = \"0\" | `${(typeof SCROLL_DEPTH_BUCKETS)[number]}`;\n\nexport function scrollDepthBucket(depth: number): ScrollDepthBucket {\n let reached: ScrollDepthBucket = \"0\";\n for (const threshold of SCROLL_DEPTH_BUCKETS) {\n if (depth >= threshold) reached = `${threshold}`;\n }\n return reached;\n}\n"]}
@@ -1,6 +1,12 @@
1
+ import {
2
+ IDENTITY_ID_PATTERN
3
+ } from "./chunk-GCQZNME3.js";
4
+ import {
5
+ RESERVED_EVENT_NAMES
6
+ } from "./chunk-FP2X3PRK.js";
7
+
1
8
  // src/ingest.ts
2
9
  import { z } from "zod";
3
- var RESERVED_EVENT_NAMES = ["session_start", "session_end", "screen_view"];
4
10
  var MAX_TRACK_PAYLOAD_BYTES = 512e3;
5
11
  var zClientEventMetadata = {
6
12
  /**
@@ -11,7 +17,20 @@ var zClientEventMetadata = {
11
17
  */
12
18
  eventId: z.uuid(),
13
19
  /** Event time, epoch milliseconds. */
14
- timestamp: z.number().int().nonnegative()
20
+ timestamp: z.number().int().nonnegative(),
21
+ /**
22
+ * The browser device this event belongs to, for events a server records
23
+ * on a visitor's behalf (a form submission, an order). Lets one server
24
+ * batch carry events from many visitors — a forwarder cannot split its
25
+ * batches per device without breaking its failure model and its
26
+ * `accepted + duplicates + rejected === sent` invariant.
27
+ *
28
+ * Honoured in `server` mode only, where it takes precedence over the
29
+ * `reopt-device-id` request header. Ignored in `browser` mode: there the
30
+ * header is authoritative and a body field would be trivially forged.
31
+ * Same character set as every identity id.
32
+ */
33
+ deviceId: z.string().regex(IDENTITY_ID_PATTERN, "expected an identity id").optional()
15
34
  };
16
35
  var zTrackPayload = z.object({
17
36
  name: z.string().min(1).max(200),
@@ -76,12 +95,17 @@ var ingestCounts = {
76
95
  /** Always `[]` in `browser` mode, where a bad row fails the whole batch. */
77
96
  rejected: z.array(zIngestRejection)
78
97
  };
98
+ var zSessionCredential = z.object({
99
+ id: z.string().regex(IDENTITY_ID_PATTERN),
100
+ token: z.string().regex(/^[A-Za-z0-9_-]{43}$/)
101
+ });
79
102
  var zIngestOkResponse = z.object({
80
103
  status: z.literal("ok"),
81
104
  requestId: z.string(),
82
105
  mode: zIngestMode,
83
106
  ...ingestCounts,
84
- queued: z.boolean()
107
+ queued: z.boolean(),
108
+ session: zSessionCredential.optional()
85
109
  });
86
110
  var zIngestAcceptedResponse = z.object({
87
111
  status: z.literal("accepted"),
@@ -89,7 +113,8 @@ var zIngestAcceptedResponse = z.object({
89
113
  mode: zIngestMode,
90
114
  ...ingestCounts,
91
115
  backgroundQueued: z.literal(false),
92
- replay: z.literal("unmaterialized-raw-events")
116
+ replay: z.literal("unmaterialized-raw-events"),
117
+ session: zSessionCredential.optional()
93
118
  });
94
119
  var zIngestResponse = z.discriminatedUnion("status", [zIngestOkResponse, zIngestAcceptedResponse]);
95
120
  function reconcileIngestResponse(response, sentCount) {
@@ -131,7 +156,6 @@ var zIngestError = z.object({
131
156
  });
132
157
 
133
158
  export {
134
- RESERVED_EVENT_NAMES,
135
159
  MAX_TRACK_PAYLOAD_BYTES,
136
160
  zTrackPayload,
137
161
  zIdentifyPayload,
@@ -143,6 +167,7 @@ export {
143
167
  INGEST_REJECTION_REASONS,
144
168
  zIngestRejectionReason,
145
169
  zIngestRejection,
170
+ zSessionCredential,
146
171
  zIngestOkResponse,
147
172
  zIngestAcceptedResponse,
148
173
  zIngestResponse,
@@ -151,4 +176,4 @@ export {
151
176
  zIngestErrorCode,
152
177
  zIngestError
153
178
  };
154
- //# sourceMappingURL=chunk-MFZEIH5Z.js.map
179
+ //# sourceMappingURL=chunk-EZ2TA4S6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ingest.ts"],"sourcesContent":["/**\n * `POST /api/track` — the ingest contract.\n *\n * The request shape is shared by both credential modes. The *response* shape\n * is where server-ingest differs: it reports per-event rejections instead of\n * failing a whole batch, because a forwarder that stalls on one malformed row\n * stops forwarding forever.\n *\n * Absorbed from the former internal `@reopt/ingest-contract`; that package is\n * now a deprecated re-export of this module.\n */\nimport { z } from \"zod\";\nimport { IDENTITY_ID_PATTERN } from \"./identity.js\";\n\nexport {\n AUTO_EVENT_NAMES,\n AUTO_EVENT_PROPERTIES,\n AUTO_EVENT_ROLLUP_KEYS,\n RESERVED_EVENT_NAMES,\n SCROLL_DEPTH_BUCKETS,\n scrollDepthBucket,\n type AutoEventName,\n type ScrollDepthBucket,\n} from \"./events.js\";\nimport { RESERVED_EVENT_NAMES } from \"./events.js\";\n\n/** Hard cap enforced by the route before the body is even parsed. */\nexport const MAX_TRACK_PAYLOAD_BYTES = 512_000;\n\nconst zClientEventMetadata = {\n /**\n * Client-generated UUID. This is the project-scoped idempotency key stored as\n * `RawEvent.clientEventId`; the server uses a separate globally unique raw\n * event id internally. Re-sending the same eventId to the same project is\n * always safe and always reported as a duplicate.\n */\n eventId: z.uuid(),\n /** Event time, epoch milliseconds. */\n timestamp: z.number().int().nonnegative(),\n /**\n * The browser device this event belongs to, for events a server records\n * on a visitor's behalf (a form submission, an order). Lets one server\n * batch carry events from many visitors — a forwarder cannot split its\n * batches per device without breaking its failure model and its\n * `accepted + duplicates + rejected === sent` invariant.\n *\n * Honoured in `server` mode only, where it takes precedence over the\n * `reopt-device-id` request header. Ignored in `browser` mode: there the\n * header is authoritative and a body field would be trivially forged.\n * Same character set as every identity id.\n */\n deviceId: z.string().regex(IDENTITY_ID_PATTERN, \"expected an identity id\").optional(),\n};\n\nexport const zTrackPayload = z\n .object({\n name: z.string().min(1).max(200),\n properties: z.record(z.string(), z.unknown()).optional(),\n profileId: z.string().max(500).or(z.number()).optional(),\n })\n .refine((data) => !RESERVED_EVENT_NAMES.includes(data.name as never), {\n message: `Event name cannot be one of the reserved names: ${RESERVED_EVENT_NAMES.join(\", \")}`,\n path: [\"name\"],\n });\n\nexport const zIdentifyPayload = z.object({\n profileId: z.string().min(1).max(500).or(z.number()),\n firstName: z.string().max(200).optional(),\n lastName: z.string().max(200).optional(),\n email: z.email().max(320).optional(),\n avatar: z.url().max(2000).optional(),\n properties: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport const zIncrementPayload = z.object({\n profileId: z.string().min(1).max(500).or(z.number()),\n property: z.string().min(1).max(200),\n value: z.number().positive().optional(),\n});\n\nexport const zDecrementPayload = z.object({\n profileId: z.string().min(1).max(500).or(z.number()),\n property: z.string().min(1).max(200),\n value: z.number().positive().optional(),\n});\n\nexport const zTrackHandlerPayload = z.discriminatedUnion(\"type\", [\n z.object({ type: z.literal(\"track\"), payload: zTrackPayload, ...zClientEventMetadata }),\n z.object({ type: z.literal(\"identify\"), payload: zIdentifyPayload, ...zClientEventMetadata }),\n z.object({ type: z.literal(\"increment\"), payload: zIncrementPayload, ...zClientEventMetadata }),\n z.object({ type: z.literal(\"decrement\"), payload: zDecrementPayload, ...zClientEventMetadata }),\n]);\n\nexport type ITrackPayload = z.infer<typeof zTrackPayload>;\nexport type IIdentifyPayload = z.infer<typeof zIdentifyPayload>;\nexport type IIncrementPayload = z.infer<typeof zIncrementPayload>;\nexport type IDecrementPayload = z.infer<typeof zDecrementPayload>;\nexport type ITrackHandlerPayload = z.infer<typeof zTrackHandlerPayload>;\n\n// ─── Ingest mode ────────────────────────────────────────────────────────────\n\n/**\n * Decided by the credential presented, never by a header the caller sets:\n * `reopt-write-key` alone → `browser`, `reopt-client-id` + `reopt-client-secret`\n * → `server`. A separate mode header could disagree with the credential; this\n * cannot.\n *\n * `browser` keeps the 5-second click dedup and device-scoped sessions.\n * `server` drops both and relies solely on `eventId` idempotency — a server\n * batch legitimately contains the same event name many times within one second,\n * and its \"device\" is a process, not a person.\n */\nexport const INGEST_MODES = [\"browser\", \"server\"] as const;\nexport const zIngestMode = z.enum(INGEST_MODES);\nexport type IngestMode = z.infer<typeof zIngestMode>;\n\n// ─── Response ───────────────────────────────────────────────────────────────\n\n/**\n * Why a single event in a batch was dropped. These are permanent: re-sending\n * the same row produces the same rejection, so a forwarder should count it as\n * skipped and advance its cursor rather than retry.\n */\nexport const INGEST_REJECTION_REASONS = [\n /** Failed the zod schema (bad eventId, missing name, wrong types…). */\n \"validation_failed\",\n /** Used one of RESERVED_EVENT_NAMES. */\n \"reserved_name\",\n /** Another row earlier in the same batch already claimed this eventId. */\n \"duplicate_in_batch\",\n] as const;\nexport const zIngestRejectionReason = z.enum(INGEST_REJECTION_REASONS);\nexport type IngestRejectionReason = z.infer<typeof zIngestRejectionReason>;\n\nexport const zIngestRejection = z.object({\n /** May be absent or malformed on `validation_failed` — hence `string`, not `uuid`. */\n eventId: z.string(),\n /** Position in the submitted batch. The only reliable identifier when eventId itself is bad. */\n index: z.number().int().nonnegative(),\n reason: zIngestRejectionReason,\n message: z.string().optional(),\n});\nexport type IngestRejection = z.infer<typeof zIngestRejection>;\n\nconst ingestCounts = {\n /** Raw events newly persisted by this request. */\n accepted: z.number().int().nonnegative(),\n /**\n * Events the server already had. In `server` mode this is exactly the\n * eventId-idempotency count; in `browser` mode it also includes the\n * 5-second click dedup.\n */\n duplicates: z.number().int().nonnegative(),\n /** Always `[]` in `browser` mode, where a bad row fails the whole batch. */\n rejected: z.array(zIngestRejection),\n};\n\n/**\n * The session ingest filed this batch under, with the token that lets the\n * browser (or a server acting for it) join the same session later with\n * authority — see `signSessionToken` in `./identity`. Browser mode only;\n * absent when the batch carried no sessionable event.\n */\nexport const zSessionCredential = z.object({\n id: z.string().regex(IDENTITY_ID_PATTERN),\n token: z.string().regex(/^[A-Za-z0-9_-]{43}$/),\n});\nexport type SessionCredential = z.infer<typeof zSessionCredential>;\n\n/** 200 — accepted and handed to the materialize queue. */\nexport const zIngestOkResponse = z.object({\n status: z.literal(\"ok\"),\n requestId: z.string(),\n mode: zIngestMode,\n ...ingestCounts,\n queued: z.boolean(),\n session: zSessionCredential.optional(),\n});\n\n/**\n * 202 — raw events landed, but enqueueing the materialize task failed. No data\n * is lost: the `replay-unmaterialized` cron picks these up. A forwarder should\n * treat this as success and advance its cursor. Carries the same counts as 200\n * so the reconciliation below works here too.\n */\nexport const zIngestAcceptedResponse = z.object({\n status: z.literal(\"accepted\"),\n requestId: z.string(),\n mode: zIngestMode,\n ...ingestCounts,\n backgroundQueued: z.literal(false),\n replay: z.literal(\"unmaterialized-raw-events\"),\n session: zSessionCredential.optional(),\n});\n\nexport const zIngestResponse = z.discriminatedUnion(\"status\", [zIngestOkResponse, zIngestAcceptedResponse]);\n\nexport type IngestOkResponse = z.infer<typeof zIngestOkResponse>;\nexport type IngestAcceptedResponse = z.infer<typeof zIngestAcceptedResponse>;\nexport type IngestResponse = z.infer<typeof zIngestResponse>;\n\n/**\n * The reconciliation a forwarder runs on every 2xx:\n *\n * accepted + duplicates + rejected.length === events sent\n *\n * A mismatch means the server silently dropped something, which is exactly the\n * failure mode this contract exists to make impossible to miss.\n */\nexport function reconcileIngestResponse(response: IngestResponse, sentCount: number): boolean {\n return response.accepted + response.duplicates + response.rejected.length === sentCount;\n}\n\n// ─── Errors ─────────────────────────────────────────────────────────────────\n\n/**\n * Stable machine-readable codes. Branch on these, never on `error`/`message`,\n * which are prose and may be reworded.\n */\nexport const INGEST_ERROR_CODES = [\n /** 401 — unknown or mismatched credentials. Stop and alert; do not retry. */\n \"unauthorized\",\n /** 400 — body was not valid JSON. */\n \"invalid_json\",\n /** 400 — request exceeded MAX_TRACK_PAYLOAD_BYTES. Split the batch. */\n \"payload_too_large\",\n /** 400 — zero events submitted. */\n \"empty_batch\",\n /** 400 — batch contained a `type: \"alias\"` event, which is not supported. */\n \"alias_unsupported\",\n /** 400 — the project has no organization; it cannot be billed or quota-checked. */\n \"project_without_organization\",\n /** 400 — `browser` mode only: a row failed validation. `server` mode reports these in `rejected[]`. */\n \"validation_failed\",\n /** 429 — per-project request rate limit. `Retry-After` set. Pause this tick, keep the cursor. */\n \"rate_limited\",\n /** 429 — the organization's monthly event quota is exhausted. `Retry-After` set (capped at 1h). Alert: a human must raise the limit. */\n \"quota_exceeded\",\n /** 410 — the project is being deleted. Permanent; stop sending. */\n \"project_purging\",\n /** 500 — unexpected server failure. Back off and retry. */\n \"internal_error\",\n] as const;\nexport const zIngestErrorCode = z.enum(INGEST_ERROR_CODES);\nexport type IngestErrorCode = z.infer<typeof zIngestErrorCode>;\n\nexport const zIngestError = z.object({\n status: z.number().int(),\n code: zIngestErrorCode,\n error: z.string(),\n message: z.string().optional(),\n /** zod issues, when `code === \"validation_failed\"`. */\n errors: z.unknown().optional(),\n requestId: z.string().optional(),\n});\nexport type IngestError = z.infer<typeof zIngestError>;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,62 @@
1
+ // src/events.ts
2
+ var RESERVED_EVENT_NAMES = ["session_start", "session_end", "screen_view"];
3
+ var AUTO_EVENT_NAMES = {
4
+ pageview: "$pageview",
5
+ screenView: "$screen_view",
6
+ pageleave: "$pageleave",
7
+ webVitals: "$web_vitals",
8
+ exception: "$exception"
9
+ };
10
+ var AUTO_EVENT_PROPERTIES = {
11
+ [AUTO_EVENT_NAMES.pageview]: [
12
+ "path",
13
+ "origin",
14
+ "title",
15
+ "referrer",
16
+ "search",
17
+ "utm_source",
18
+ "utm_medium",
19
+ "utm_campaign",
20
+ "utm_term",
21
+ "utm_content"
22
+ ],
23
+ [AUTO_EVENT_NAMES.screenView]: ["screen_name"],
24
+ [AUTO_EVENT_NAMES.pageleave]: ["path", "origin", "duration", "scroll_depth", "scroll_depth_bucket"],
25
+ [AUTO_EVENT_NAMES.webVitals]: ["metric_name", "metric_id", "value", "delta", "rating", "navigation_type", "path"],
26
+ // `$`-prefixed on purpose: these are reserved, SDK-owned keys with fixed
27
+ // semantics, distinguished from a host's own snake_case vocabulary the
28
+ // same way the event names are. Not a typo — do not strip the prefix.
29
+ [AUTO_EVENT_NAMES.exception]: [
30
+ "$exception_type",
31
+ "$exception_message",
32
+ "$exception_stack",
33
+ "$exception_source",
34
+ "$exception_handled",
35
+ "path"
36
+ ]
37
+ };
38
+ var AUTO_EVENT_ROLLUP_KEYS = {
39
+ [AUTO_EVENT_NAMES.pageview]: ["path", "origin", "referrer", "utm_source", "utm_medium", "utm_campaign"],
40
+ [AUTO_EVENT_NAMES.screenView]: ["screen_name"],
41
+ [AUTO_EVENT_NAMES.pageleave]: ["path", "origin", "scroll_depth_bucket"],
42
+ [AUTO_EVENT_NAMES.webVitals]: ["metric_name", "rating", "navigation_type", "path"],
43
+ [AUTO_EVENT_NAMES.exception]: ["$exception_type", "$exception_source", "$exception_handled", "path"]
44
+ };
45
+ var SCROLL_DEPTH_BUCKETS = [25, 50, 75, 100];
46
+ function scrollDepthBucket(depth) {
47
+ let reached = "0";
48
+ for (const threshold of SCROLL_DEPTH_BUCKETS) {
49
+ if (depth >= threshold) reached = `${threshold}`;
50
+ }
51
+ return reached;
52
+ }
53
+
54
+ export {
55
+ RESERVED_EVENT_NAMES,
56
+ AUTO_EVENT_NAMES,
57
+ AUTO_EVENT_PROPERTIES,
58
+ AUTO_EVENT_ROLLUP_KEYS,
59
+ SCROLL_DEPTH_BUCKETS,
60
+ scrollDepthBucket
61
+ };
62
+ //# sourceMappingURL=chunk-FP2X3PRK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/events.ts"],"sourcesContent":["/**\n * `@reopt-ai/data-contract/events` — event names and property keys, with no\n * zod. Browser SDKs import this so their production bundles stay free of\n * schema code; `./ingest` re-exports everything here.\n */\n\n/** Event names the pipeline reserves for its own session bookkeeping. */\nexport const RESERVED_EVENT_NAMES = [\"session_start\", \"session_end\", \"screen_view\"] as const;\n\n/**\n * Events the SDKs emit on their own. One declaration, so a consumer that\n * registers them in its event catalogue (to make them roll up) cannot drift\n * from what the SDK actually sends — a drifted name silently falls back to\n * raw queries, which is slower but not visibly wrong.\n */\nexport const AUTO_EVENT_NAMES = {\n pageview: \"$pageview\",\n screenView: \"$screen_view\",\n pageleave: \"$pageleave\",\n webVitals: \"$web_vitals\",\n exception: \"$exception\",\n} as const;\nexport type AutoEventName = (typeof AUTO_EVENT_NAMES)[keyof typeof AUTO_EVENT_NAMES];\n\n/**\n * Property keys each automatic event carries. Consumers derive their\n * breakdown / rollup property lists from here rather than retyping them.\n * `path`, `origin`, `referrer` and `utm_*` are the keys the ingest pipeline\n * reads into session and event columns.\n */\nexport const AUTO_EVENT_PROPERTIES = {\n [AUTO_EVENT_NAMES.pageview]: [\n \"path\",\n \"origin\",\n \"title\",\n \"referrer\",\n \"search\",\n \"utm_source\",\n \"utm_medium\",\n \"utm_campaign\",\n \"utm_term\",\n \"utm_content\",\n ],\n [AUTO_EVENT_NAMES.screenView]: [\"screen_name\"],\n [AUTO_EVENT_NAMES.pageleave]: [\"path\", \"origin\", \"duration\", \"scroll_depth\", \"scroll_depth_bucket\"],\n [AUTO_EVENT_NAMES.webVitals]: [\"metric_name\", \"metric_id\", \"value\", \"delta\", \"rating\", \"navigation_type\", \"path\"],\n // `$`-prefixed on purpose: these are reserved, SDK-owned keys with fixed\n // semantics, distinguished from a host's own snake_case vocabulary the\n // same way the event names are. Not a typo — do not strip the prefix.\n [AUTO_EVENT_NAMES.exception]: [\n \"$exception_type\",\n \"$exception_message\",\n \"$exception_stack\",\n \"$exception_source\",\n \"$exception_handled\",\n \"path\",\n ],\n} as const satisfies Record<AutoEventName, readonly string[]>;\n\n/**\n * The subset of {@link AUTO_EVENT_PROPERTIES} that is a sensible rollup\n * dimension: low cardinality, categorical. Per-measurement ids and\n * continuous values (`metric_id`, `value`, `delta`, `duration`,\n * `scroll_depth`) are deliberately absent — as dimensions they would make\n * the rollup larger than the raw table it summarises. Derive\n * `rollupProperties` from here, not from the full property list.\n */\nexport const AUTO_EVENT_ROLLUP_KEYS = {\n [AUTO_EVENT_NAMES.pageview]: [\"path\", \"origin\", \"referrer\", \"utm_source\", \"utm_medium\", \"utm_campaign\"],\n [AUTO_EVENT_NAMES.screenView]: [\"screen_name\"],\n [AUTO_EVENT_NAMES.pageleave]: [\"path\", \"origin\", \"scroll_depth_bucket\"],\n [AUTO_EVENT_NAMES.webVitals]: [\"metric_name\", \"rating\", \"navigation_type\", \"path\"],\n [AUTO_EVENT_NAMES.exception]: [\"$exception_type\", \"$exception_source\", \"$exception_handled\", \"path\"],\n} as const satisfies Record<AutoEventName, readonly string[]>;\n\n/**\n * `$pageleave.scroll_depth` is the exact 0–100 percentage; `scroll_depth_bucket`\n * is the deepest of these thresholds reached, as a string — the value to\n * break down by. `\"0\"` means the visitor never reached the first threshold.\n */\nexport const SCROLL_DEPTH_BUCKETS = [25, 50, 75, 100] as const;\nexport type ScrollDepthBucket = \"0\" | `${(typeof SCROLL_DEPTH_BUCKETS)[number]}`;\n\nexport function scrollDepthBucket(depth: number): ScrollDepthBucket {\n let reached: ScrollDepthBucket = \"0\";\n for (const threshold of SCROLL_DEPTH_BUCKETS) {\n if (depth >= threshold) reached = `${threshold}`;\n }\n return reached;\n}\n"],"mappings":";AAOO,IAAM,uBAAuB,CAAC,iBAAiB,eAAe,aAAa;AAQ3E,IAAM,mBAAmB;AAAA,EAC9B,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AACb;AASO,IAAM,wBAAwB;AAAA,EACnC,CAAC,iBAAiB,QAAQ,GAAG;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,CAAC,iBAAiB,UAAU,GAAG,CAAC,aAAa;AAAA,EAC7C,CAAC,iBAAiB,SAAS,GAAG,CAAC,QAAQ,UAAU,YAAY,gBAAgB,qBAAqB;AAAA,EAClG,CAAC,iBAAiB,SAAS,GAAG,CAAC,eAAe,aAAa,SAAS,SAAS,UAAU,mBAAmB,MAAM;AAAA;AAAA;AAAA;AAAA,EAIhH,CAAC,iBAAiB,SAAS,GAAG;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAUO,IAAM,yBAAyB;AAAA,EACpC,CAAC,iBAAiB,QAAQ,GAAG,CAAC,QAAQ,UAAU,YAAY,cAAc,cAAc,cAAc;AAAA,EACtG,CAAC,iBAAiB,UAAU,GAAG,CAAC,aAAa;AAAA,EAC7C,CAAC,iBAAiB,SAAS,GAAG,CAAC,QAAQ,UAAU,qBAAqB;AAAA,EACtE,CAAC,iBAAiB,SAAS,GAAG,CAAC,eAAe,UAAU,mBAAmB,MAAM;AAAA,EACjF,CAAC,iBAAiB,SAAS,GAAG,CAAC,mBAAmB,qBAAqB,sBAAsB,MAAM;AACrG;AAOO,IAAM,uBAAuB,CAAC,IAAI,IAAI,IAAI,GAAG;AAG7C,SAAS,kBAAkB,OAAkC;AAClE,MAAI,UAA6B;AACjC,aAAW,aAAa,sBAAsB;AAC5C,QAAI,SAAS,UAAW,WAAU,GAAG,SAAS;AAAA,EAChD;AACA,SAAO;AACT;","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
 
4
- var _chunkOLSMH4RYcjs = require('./chunk-OLSMH4RY.cjs');
4
+ var _chunkYSAEOYPDcjs = require('./chunk-YSAEOYPD.cjs');
5
5
 
6
6
  // src/query.ts
7
7
  var _zod = require('zod');
@@ -26,7 +26,7 @@ var zQuerySource = _zod.z.enum(["rollup", "raw", "mixed"]);
26
26
  var zQueryMeta = _zod.z.object({
27
27
  projectId: _zod.z.string(),
28
28
  /** When the underlying computation actually ran. On a cache hit, when the cached value was computed. */
29
- computedAt: _chunkOLSMH4RYcjs.zInstant,
29
+ computedAt: _chunkYSAEOYPDcjs.zInstant,
30
30
  /** Age of the returned computation, in seconds. `0` on a cache miss. */
31
31
  cacheAgeSeconds: _zod.z.number().int().nonnegative(),
32
32
  /**
@@ -80,8 +80,8 @@ var zEventPropertyFilter = _zod.z.object({
80
80
  });
81
81
  var zEventsTimeseriesInput = _zod.z.object({
82
82
  projectId: _zod.z.string().min(1),
83
- startDate: _chunkOLSMH4RYcjs.zDate,
84
- endDate: _chunkOLSMH4RYcjs.zDate,
83
+ startDate: _chunkYSAEOYPDcjs.zDate,
84
+ endDate: _chunkYSAEOYPDcjs.zDate,
85
85
  granularity: zGranularity.default("day"),
86
86
  /** Omit to count every event in the window. */
87
87
  eventName: _zod.z.string().min(1).max(200).optional(),
@@ -129,8 +129,8 @@ var zFunnelInput = _zod.z.object({
129
129
  projectId: _zod.z.string().min(1),
130
130
  /** Ordered event names. A user counts at step k if they reached at least step k. */
131
131
  steps: _zod.z.array(_zod.z.string().min(1).max(200)).min(2).max(10),
132
- startDate: _chunkOLSMH4RYcjs.zDate,
133
- endDate: _chunkOLSMH4RYcjs.zDate,
132
+ startDate: _chunkYSAEOYPDcjs.zDate,
133
+ endDate: _chunkYSAEOYPDcjs.zDate,
134
134
  /** Max seconds between the first and last step for a conversion to count. Default 24h. */
135
135
  windowSeconds: _zod.z.number().int().min(60).max(2592e3).default(86400),
136
136
  segmentId: _zod.z.string().min(1).optional(),
@@ -167,8 +167,8 @@ var zFunnelResponse = zQueryEnvelope(zFunnelData);
167
167
  var zRetentionInterval = _zod.z.enum(["day", "week", "month"]);
168
168
  var zRetentionInput = _zod.z.object({
169
169
  projectId: _zod.z.string().min(1),
170
- startDate: _chunkOLSMH4RYcjs.zDate,
171
- endDate: _chunkOLSMH4RYcjs.zDate,
170
+ startDate: _chunkYSAEOYPDcjs.zDate,
171
+ endDate: _chunkYSAEOYPDcjs.zDate,
172
172
  interval: zRetentionInterval.default("day"),
173
173
  periods: _zod.z.number().int().min(1).max(30).default(7),
174
174
  startEvent: _zod.z.string().min(1).max(200).optional(),
@@ -228,8 +228,8 @@ var zRetentionData = _zod.z.object({
228
228
  var zRetentionResponse = zQueryEnvelope(zRetentionData);
229
229
  var zTrafficOverviewInput = _zod.z.object({
230
230
  projectId: _zod.z.string().min(1),
231
- startDate: _chunkOLSMH4RYcjs.zDate,
232
- endDate: _chunkOLSMH4RYcjs.zDate,
231
+ startDate: _chunkYSAEOYPDcjs.zDate,
232
+ endDate: _chunkYSAEOYPDcjs.zDate,
233
233
  granularity: zGranularity.default("day"),
234
234
  timezone: zTimezone.optional()
235
235
  }).refine((input) => input.startDate <= input.endDate, {
@@ -255,8 +255,8 @@ var zTrafficOverviewResponse = zQueryEnvelope(zTrafficOverviewData);
255
255
  var zTrafficDimension = _zod.z.enum(["channel", "referrer", "utm"]);
256
256
  var zTrafficSourcesInput = _zod.z.object({
257
257
  projectId: _zod.z.string().min(1),
258
- startDate: _chunkOLSMH4RYcjs.zDate,
259
- endDate: _chunkOLSMH4RYcjs.zDate,
258
+ startDate: _chunkYSAEOYPDcjs.zDate,
259
+ endDate: _chunkYSAEOYPDcjs.zDate,
260
260
  /** `channel`: channel_type · `referrer`: referrer name+type · `utm`: source/medium/campaign. */
261
261
  dimension: zTrafficDimension,
262
262
  limit: _zod.z.number().int().min(1).max(100).default(10),
@@ -358,4 +358,4 @@ var QUERY_API_PATHS = {
358
358
 
359
359
 
360
360
  exports.zGranularity = zGranularity; exports.zTimezone = zTimezone; exports.isValidTimeZone = isValidTimeZone; exports.zChannelFilter = zChannelFilter; exports.zQuerySource = zQuerySource; exports.zQueryMeta = zQueryMeta; exports.zQueryEnvelope = zQueryEnvelope; exports.zTimeseriesBreakdown = zTimeseriesBreakdown; exports.zEventPropertyFilter = zEventPropertyFilter; exports.zEventsTimeseriesInput = zEventsTimeseriesInput; exports.zBucketStart = zBucketStart; exports.zTimeseriesPoint = zTimeseriesPoint; exports.zEventsTimeseriesData = zEventsTimeseriesData; exports.zEventsTimeseriesResponse = zEventsTimeseriesResponse; exports.zFunnelInput = zFunnelInput; exports.zFunnelStep = zFunnelStep; exports.zFunnelData = zFunnelData; exports.zFunnelResponse = zFunnelResponse; exports.zRetentionInterval = zRetentionInterval; exports.zRetentionInput = zRetentionInput; exports.zRetentionCell = zRetentionCell; exports.zRetentionData = zRetentionData; exports.zRetentionResponse = zRetentionResponse; exports.zTrafficOverviewInput = zTrafficOverviewInput; exports.zTrafficTotals = zTrafficTotals; exports.zTrafficSeriesPoint = zTrafficSeriesPoint; exports.zTrafficOverviewData = zTrafficOverviewData; exports.zTrafficOverviewResponse = zTrafficOverviewResponse; exports.zTrafficDimension = zTrafficDimension; exports.zTrafficSourcesInput = zTrafficSourcesInput; exports.zTrafficSourceRow = zTrafficSourceRow; exports.zTrafficSourcesData = zTrafficSourcesData; exports.zTrafficSourcesResponse = zTrafficSourcesResponse; exports.QUERY_ERROR_CODES = QUERY_ERROR_CODES; exports.zQueryErrorCode = zQueryErrorCode; exports.zQueryError = zQueryError; exports.QUERY_API_PATHS = QUERY_API_PATHS;
361
- //# sourceMappingURL=chunk-VQ2CD5MG.cjs.map
361
+ //# sourceMappingURL=chunk-FUGY5ANJ.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-VQ2CD5MG.cjs","../src/query.ts"],"names":[],"mappings":"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","file":"/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-VQ2CD5MG.cjs","sourcesContent":[null,"/**\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"]}
1
+ {"version":3,"sources":["/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-FUGY5ANJ.cjs","../src/query.ts"],"names":[],"mappings":"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","file":"/Users/eric/reopt-ai/reopt-data/packages/data-contract/dist/chunk-FUGY5ANJ.cjs","sourcesContent":[null,"/**\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"]}