@rocicorp/zero 0.25.10-canary.7 → 0.25.10

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/out/zero/package.json.js +1 -1
  2. package/out/zero-cache/src/custom/fetch.d.ts +3 -3
  3. package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
  4. package/out/zero-cache/src/custom/fetch.js +116 -76
  5. package/out/zero-cache/src/custom/fetch.js.map +1 -1
  6. package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
  7. package/out/zero-cache/src/custom-queries/transform-query.js +0 -1
  8. package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
  9. package/out/zero-cache/src/server/anonymous-otel-start.d.ts.map +1 -1
  10. package/out/zero-cache/src/server/anonymous-otel-start.js +1 -0
  11. package/out/zero-cache/src/server/anonymous-otel-start.js.map +1 -1
  12. package/out/zero-cache/src/server/inspector-delegate.d.ts.map +1 -1
  13. package/out/zero-cache/src/server/inspector-delegate.js +2 -2
  14. package/out/zero-cache/src/server/inspector-delegate.js.map +1 -1
  15. package/out/zero-cache/src/server/priority-op.d.ts +8 -0
  16. package/out/zero-cache/src/server/priority-op.d.ts.map +1 -0
  17. package/out/zero-cache/src/server/priority-op.js +29 -0
  18. package/out/zero-cache/src/server/priority-op.js.map +1 -0
  19. package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
  20. package/out/zero-cache/src/server/syncer.js +9 -2
  21. package/out/zero-cache/src/server/syncer.js.map +1 -1
  22. package/out/zero-cache/src/services/analyze.js +1 -1
  23. package/out/zero-cache/src/services/analyze.js.map +1 -1
  24. package/out/zero-cache/src/services/change-source/replica-schema.d.ts.map +1 -1
  25. package/out/zero-cache/src/services/change-source/replica-schema.js +5 -1
  26. package/out/zero-cache/src/services/change-source/replica-schema.js.map +1 -1
  27. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts +1 -1
  28. package/out/zero-cache/src/services/change-streamer/backup-monitor.d.ts.map +1 -1
  29. package/out/zero-cache/src/services/change-streamer/backup-monitor.js +10 -6
  30. package/out/zero-cache/src/services/change-streamer/backup-monitor.js.map +1 -1
  31. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +2 -2
  32. package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
  33. package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
  34. package/out/zero-cache/src/services/mutagen/pusher.js +1 -3
  35. package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
  36. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  37. package/out/zero-cache/src/services/view-syncer/cvr-store.js +60 -22
  38. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  39. package/out/zero-cache/src/services/view-syncer/cvr.d.ts.map +1 -1
  40. package/out/zero-cache/src/services/view-syncer/cvr.js +2 -0
  41. package/out/zero-cache/src/services/view-syncer/cvr.js.map +1 -1
  42. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +1 -1
  43. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  44. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +2 -2
  45. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  46. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts +1 -1
  47. package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
  48. package/out/zero-cache/src/services/view-syncer/row-record-cache.js +22 -11
  49. package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
  50. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts +2 -1
  51. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  52. package/out/zero-cache/src/services/view-syncer/view-syncer.js +82 -52
  53. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  54. package/out/zero-cache/src/types/error-with-level.d.ts +1 -1
  55. package/out/zero-cache/src/types/error-with-level.d.ts.map +1 -1
  56. package/out/zero-cache/src/types/error-with-level.js +1 -1
  57. package/out/zero-cache/src/types/error-with-level.js.map +1 -1
  58. package/out/zero-client/src/client/connection-manager.d.ts +3 -0
  59. package/out/zero-client/src/client/connection-manager.d.ts.map +1 -1
  60. package/out/zero-client/src/client/connection-manager.js +10 -3
  61. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  62. package/out/zero-client/src/client/error.d.ts +5 -1
  63. package/out/zero-client/src/client/error.d.ts.map +1 -1
  64. package/out/zero-client/src/client/error.js +3 -3
  65. package/out/zero-client/src/client/error.js.map +1 -1
  66. package/out/zero-client/src/client/options.d.ts +1 -1
  67. package/out/zero-client/src/client/options.js.map +1 -1
  68. package/out/zero-client/src/client/version.js +1 -1
  69. package/out/zero-client/src/client/zero.d.ts +1 -1
  70. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  71. package/out/zero-client/src/client/zero.js +1 -1
  72. package/out/zero-client/src/client/zero.js.map +1 -1
  73. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- const version = "0.25.10-canary.7";
1
+ const version = "0.25.10";
2
2
  const packageJson = {
3
3
  version
4
4
  };
@@ -1,4 +1,4 @@
1
- import type { LogContext, LogLevel } from '@rocicorp/logger';
1
+ import type { LogContext } from '@rocicorp/logger';
2
2
  import 'urlpattern-polyfill';
3
3
  import type { ReadonlyJSONValue } from '../../../shared/src/json.ts';
4
4
  import { type Type } from '../../../shared/src/valita.ts';
@@ -19,8 +19,8 @@ export type HeaderOptions = {
19
19
  token?: string | undefined;
20
20
  cookie?: string | undefined;
21
21
  };
22
- export declare const getBodyPreview: (res: Response, lc: LogContext, level: LogLevel) => Promise<string | undefined>;
23
- export declare function fetchFromAPIServer<TValidator extends Type>(validator: TValidator, source: 'push' | 'transform', lc: LogContext, url: string, isUserUrl: boolean, allowedUrlPatterns: URLPattern[], shard: ShardID, headerOptions: HeaderOptions, body: ReadonlyJSONValue): Promise<import("../../../shared/src/valita.ts").Infer<TValidator>>;
22
+ export declare const getBodyPreview: (res: Response, lc: LogContext) => Promise<string | undefined>;
23
+ export declare function fetchFromAPIServer<TValidator extends Type>(validator: TValidator, source: 'push' | 'transform', lc: LogContext, url: string, allowedUrlPatterns: URLPattern[], shard: ShardID, headerOptions: HeaderOptions, body: ReadonlyJSONValue): Promise<import("../../../shared/src/valita.ts").Infer<TValidator>>;
24
24
  /**
25
25
  * Returns true if the url matches one of the allowedUrlPatterns.
26
26
  *
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/custom/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAE,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAC3D,OAAO,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAC,KAAK,IAAI,EAAC,MAAM,+BAA+B,CAAC;AAMxD,OAAO,EAAiB,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAIhE;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAQ7D;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,KAAK,QAAQ,EACb,IAAI,UAAU,EACd,OAAO,QAAQ,KACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAe5B,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,UAAU,SAAS,IAAI,EAC9D,SAAS,EAAE,UAAU,EACrB,MAAM,EAAE,MAAM,GAAG,WAAW,EAC5B,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,OAAO,EAClB,kBAAkB,EAAE,UAAU,EAAE,EAChC,KAAK,EAAE,OAAO,EACd,aAAa,EAAE,aAAa,EAC5B,IAAI,EAAE,iBAAiB,sEAqKxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,UAAU,EAAE,GAC/B,OAAO,CAOT"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/custom/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAW,MAAM,kBAAkB,CAAC;AAC3D,OAAO,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAEnE,OAAO,EAAC,KAAK,IAAI,EAAC,MAAM,+BAA+B,CAAC;AAMxD,OAAO,EAAiB,KAAK,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAKhE;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAQ7D;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACnD,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,KAAK,QAAQ,EACb,IAAI,UAAU,KACb,OAAO,CAAC,MAAM,GAAG,SAAS,CAkB5B,CAAC;AAIF,wBAAsB,kBAAkB,CAAC,UAAU,SAAS,IAAI,EAC9D,SAAS,EAAE,UAAU,EACrB,MAAM,EAAE,MAAM,GAAG,WAAW,EAC5B,EAAE,EAAE,UAAU,EACd,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,UAAU,EAAE,EAChC,KAAK,EAAE,OAAO,EACd,aAAa,EAAE,aAAa,EAC5B,IAAI,EAAE,iBAAiB,sEAuMxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,UAAU,EAAE,GAC/B,OAAO,CAOT"}
@@ -1,6 +1,7 @@
1
1
  import "urlpattern-polyfill";
2
- import { assert } from "../../../shared/src/asserts.js";
2
+ import { assert, unreachable } from "../../../shared/src/asserts.js";
3
3
  import { getErrorMessage } from "../../../shared/src/error.js";
4
+ import { sleep } from "../../../shared/src/sleep.js";
4
5
  import "../../../shared/src/valita.js";
5
6
  import { PushFailed, TransformFailed } from "../../../zero-protocol/src/error-kind-enum.js";
6
7
  import { ZeroCache } from "../../../zero-protocol/src/error-origin-enum.js";
@@ -8,6 +9,7 @@ import { Internal, HTTP, Parse } from "../../../zero-protocol/src/error-reason-e
8
9
  import { isProtocolError } from "../../../zero-protocol/src/error.js";
9
10
  import { ProtocolErrorWithLevel } from "../types/error-with-level.js";
10
11
  import { upstreamSchema } from "../types/shards.js";
12
+ import { randInt } from "../../../shared/src/rand.js";
11
13
  const reservedParams = ["schema", "appID"];
12
14
  function compileUrlPattern(pattern) {
13
15
  try {
@@ -18,7 +20,7 @@ function compileUrlPattern(pattern) {
18
20
  );
19
21
  }
20
22
  }
21
- const getBodyPreview = async (res, lc, level) => {
23
+ const getBodyPreview = async (res, lc) => {
22
24
  try {
23
25
  const body = await res.clone().text();
24
26
  if (body.length > 512) {
@@ -26,14 +28,20 @@ const getBodyPreview = async (res, lc, level) => {
26
28
  }
27
29
  return body;
28
30
  } catch (e) {
29
- lc[level]?.("failed to get body preview", {
30
- url: res.url,
31
- error: e instanceof Error ? e.message : String(e)
32
- });
31
+ lc.warn?.(
32
+ "failed to get body preview",
33
+ {
34
+ url: res.url
35
+ },
36
+ e
37
+ );
33
38
  }
34
39
  return void 0;
35
40
  };
36
- async function fetchFromAPIServer(validator, source, lc, url, isUserUrl, allowedUrlPatterns, shard, headerOptions, body) {
41
+ const MAX_ATTEMPTS = 4;
42
+ async function fetchFromAPIServer(validator, source, lc, url, allowedUrlPatterns, shard, headerOptions, body) {
43
+ const fetchFromAPIServerID = randInt(1, Number.MAX_SAFE_INTEGER).toString(36);
44
+ lc = lc.withContext("fetchFromAPIServerID", fetchFromAPIServerID);
37
45
  lc.debug?.("fetchFromAPIServer called", {
38
46
  url
39
47
  });
@@ -51,7 +59,8 @@ async function fetchFromAPIServer(validator, source, lc, url, isUserUrl, allowed
51
59
  reason: Internal,
52
60
  message: `URL "${url}" is not allowed by the ZERO_QUERY_URL configuration`,
53
61
  queryIDs: []
54
- }
62
+ },
63
+ "warn"
55
64
  );
56
65
  }
57
66
  const headers = {
@@ -81,93 +90,121 @@ async function fetchFromAPIServer(validator, source, lc, url, isUserUrl, allowed
81
90
  params.append("appID", shard.appID);
82
91
  urlObj.search = params.toString();
83
92
  const finalUrl = urlObj.toString();
84
- const errLevel = isUserUrl ? "warn" : "error";
85
- try {
86
- const response = await fetch(finalUrl, {
87
- method: "POST",
88
- headers,
89
- body: JSON.stringify(body)
90
- });
91
- if (!response.ok) {
92
- const bodyPreview = await getBodyPreview(response, lc, errLevel);
93
- const level = response.status === 502 || response.status === 504 ? errLevel : "info";
94
- lc[level]?.("fetch from API server returned non-OK status", {
95
- url: finalUrl,
96
- status: response.status,
97
- bodyPreview
93
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
94
+ lc = lc.withContext("fetchFromAPIServerAttempt", attempt);
95
+ lc.debug?.("fetch from API server attempt");
96
+ const shouldRetry = async () => {
97
+ if (attempt < MAX_ATTEMPTS) {
98
+ const delayMs = getBackoffDelayMs(attempt);
99
+ lc.debug?.(`fetch from API server retrying in ${delayMs} ms`);
100
+ await sleep(delayMs);
101
+ return true;
102
+ }
103
+ lc.debug?.("fetch from API server reached max attempts, not retrying");
104
+ return false;
105
+ };
106
+ try {
107
+ const response = await fetch(finalUrl, {
108
+ method: "POST",
109
+ headers,
110
+ body: JSON.stringify(body)
98
111
  });
99
- throw new ProtocolErrorWithLevel(
100
- source === "push" ? {
101
- kind: PushFailed,
102
- origin: ZeroCache,
103
- reason: HTTP,
112
+ if (!response.ok) {
113
+ const bodyPreview = await getBodyPreview(response, lc);
114
+ lc.warn?.("fetch from API server returned non-OK status", {
115
+ url: finalUrl,
104
116
  status: response.status,
105
- bodyPreview,
106
- message: `Fetch from API server returned non-OK status ${response.status}`,
107
- mutationIDs: []
108
- } : {
109
- kind: TransformFailed,
110
- origin: ZeroCache,
111
- reason: HTTP,
112
- status: response.status,
113
- bodyPreview,
114
- message: `Fetch from API server returned non-OK status ${response.status}`,
115
- queryIDs: []
117
+ bodyPreview
118
+ });
119
+ if ((response.status === 502 || response.status === 504) && await shouldRetry()) {
120
+ continue;
116
121
  }
117
- );
118
- }
119
- try {
120
- const json = await response.json();
121
- return validator.parse(json);
122
+ throw new ProtocolErrorWithLevel(
123
+ source === "push" ? {
124
+ kind: PushFailed,
125
+ origin: ZeroCache,
126
+ reason: HTTP,
127
+ status: response.status,
128
+ bodyPreview,
129
+ message: `Fetch from API server returned non-OK status ${response.status}`,
130
+ mutationIDs: []
131
+ } : {
132
+ kind: TransformFailed,
133
+ origin: ZeroCache,
134
+ reason: HTTP,
135
+ status: response.status,
136
+ bodyPreview,
137
+ message: `Fetch from API server returned non-OK status ${response.status}`,
138
+ queryIDs: []
139
+ },
140
+ "warn"
141
+ );
142
+ }
143
+ try {
144
+ const json = await response.json();
145
+ const result = validator.parse(json);
146
+ lc.debug?.("fetch from API server succeeded");
147
+ return result;
148
+ } catch (error) {
149
+ lc.warn?.(
150
+ "failed to parse response",
151
+ {
152
+ url: finalUrl
153
+ },
154
+ error
155
+ );
156
+ throw new ProtocolErrorWithLevel(
157
+ source === "push" ? {
158
+ kind: PushFailed,
159
+ origin: ZeroCache,
160
+ reason: Parse,
161
+ message: `Failed to parse response from API server: ${getErrorMessage(error)}`,
162
+ mutationIDs: []
163
+ } : {
164
+ kind: TransformFailed,
165
+ origin: ZeroCache,
166
+ reason: Parse,
167
+ message: `Failed to parse response from API server: ${getErrorMessage(error)}`,
168
+ queryIDs: []
169
+ },
170
+ "warn",
171
+ { cause: error }
172
+ );
173
+ }
122
174
  } catch (error) {
123
- lc[errLevel]?.("failed to parse response", {
124
- url: finalUrl,
175
+ if (isProtocolError(error)) {
176
+ throw error;
177
+ }
178
+ const isFetchFailed = error instanceof TypeError && error.message === "fetch failed";
179
+ let logLevel = isFetchFailed ? "warn" : "error";
180
+ lc[logLevel]?.(
181
+ "fetch from API server threw error",
182
+ { url: finalUrl },
125
183
  error
126
- });
184
+ );
185
+ if (isFetchFailed && await shouldRetry()) {
186
+ continue;
187
+ }
127
188
  throw new ProtocolErrorWithLevel(
128
189
  source === "push" ? {
129
190
  kind: PushFailed,
130
191
  origin: ZeroCache,
131
- reason: Parse,
132
- message: `Failed to parse response from API server: ${getErrorMessage(error)}`,
192
+ reason: Internal,
193
+ message: `Fetch from API server threw error: ${getErrorMessage(error)}`,
133
194
  mutationIDs: []
134
195
  } : {
135
196
  kind: TransformFailed,
136
197
  origin: ZeroCache,
137
- reason: Parse,
138
- message: `Failed to parse response from API server: ${getErrorMessage(error)}`,
198
+ reason: Internal,
199
+ message: `Fetch from API server threw error: ${getErrorMessage(error)}`,
139
200
  queryIDs: []
140
201
  },
141
- "error",
202
+ logLevel,
142
203
  { cause: error }
143
204
  );
144
205
  }
145
- } catch (error) {
146
- if (isProtocolError(error)) {
147
- throw error;
148
- }
149
- lc[errLevel]?.("failed to fetch from API server with unknown error", {
150
- url: finalUrl,
151
- error
152
- });
153
- throw new ProtocolErrorWithLevel(
154
- source === "push" ? {
155
- kind: PushFailed,
156
- origin: ZeroCache,
157
- reason: Internal,
158
- message: `Fetch from API server failed with unknown error: ${getErrorMessage(error)}`,
159
- mutationIDs: []
160
- } : {
161
- kind: TransformFailed,
162
- origin: ZeroCache,
163
- reason: Internal,
164
- message: `Fetch from API server failed with unknown error: ${getErrorMessage(error)}`,
165
- queryIDs: []
166
- },
167
- "error",
168
- { cause: error }
169
- );
170
206
  }
207
+ unreachable();
171
208
  }
172
209
  function urlMatch(url, allowedUrlPatterns) {
173
210
  for (const pattern of allowedUrlPatterns) {
@@ -177,6 +214,9 @@ function urlMatch(url, allowedUrlPatterns) {
177
214
  }
178
215
  return false;
179
216
  }
217
+ function getBackoffDelayMs(attempt) {
218
+ return Math.min(1e3, 100 * Math.pow(2, attempt - 1) + Math.random() * 100);
219
+ }
180
220
  export {
181
221
  compileUrlPattern,
182
222
  fetchFromAPIServer,
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sources":["../../../../../zero-cache/src/custom/fetch.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport 'urlpattern-polyfill';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {type Type} from '../../../shared/src/valita.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {isProtocolError} from '../../../zero-protocol/src/error.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../types/shards.ts';\n\nconst reservedParams = ['schema', 'appID'];\n\n/**\n * Compiles and validates a URLPattern from configuration.\n *\n * Patterns must be full URLs (e.g., \"https://api.example.com/endpoint\").\n * URLPattern automatically sets search and hash to wildcard ('*'),\n * which means query parameters and fragments are ignored during matching.\n *\n * @throws Error if the pattern is an invalid URLPattern\n */\nexport function compileUrlPattern(pattern: string): URLPattern {\n try {\n return new URLPattern(pattern);\n } catch (e) {\n throw new Error(\n `Invalid URLPattern in URL configuration: \"${pattern}\". Error: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n\nexport type HeaderOptions = {\n apiKey?: string | undefined;\n customHeaders?: Record<string, string> | undefined;\n token?: string | undefined;\n cookie?: string | undefined;\n};\n\nexport const getBodyPreview = async (\n res: Response,\n lc: LogContext,\n level: LogLevel,\n): Promise<string | undefined> => {\n try {\n const body = await res.clone().text();\n if (body.length > 512) {\n return body.slice(0, 512) + '...';\n }\n return body;\n } catch (e) {\n lc[level]?.('failed to get body preview', {\n url: res.url,\n error: e instanceof Error ? e.message : String(e),\n });\n }\n\n return undefined;\n};\n\nexport async function fetchFromAPIServer<TValidator extends Type>(\n validator: TValidator,\n source: 'push' | 'transform',\n lc: LogContext,\n url: string,\n isUserUrl: boolean,\n allowedUrlPatterns: URLPattern[],\n shard: ShardID,\n headerOptions: HeaderOptions,\n body: ReadonlyJSONValue,\n) {\n lc.debug?.('fetchFromAPIServer called', {\n url,\n });\n\n if (!urlMatch(url, allowedUrlPatterns)) {\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `URL \"${url}\" is not allowed by the ZERO_MUTATE_URL configuration`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `URL \"${url}\" is not allowed by the ZERO_QUERY_URL configuration`,\n queryIDs: [],\n },\n );\n }\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (headerOptions.apiKey) {\n headers['X-Api-Key'] = headerOptions.apiKey;\n }\n if (headerOptions.customHeaders) {\n Object.assign(headers, headerOptions.customHeaders);\n }\n if (headerOptions.token) {\n headers['Authorization'] = `Bearer ${headerOptions.token}`;\n }\n if (headerOptions.cookie) {\n headers['Cookie'] = headerOptions.cookie;\n }\n\n const urlObj = new URL(url);\n const params = new URLSearchParams(urlObj.search);\n\n for (const reserved of reservedParams) {\n assert(\n !params.has(reserved),\n `The push URL cannot contain the reserved query param \"${reserved}\"`,\n );\n }\n\n params.append('schema', upstreamSchema(shard));\n params.append('appID', shard.appID);\n\n urlObj.search = params.toString();\n\n const finalUrl = urlObj.toString();\n\n // Errors from a user-specified url are treated 4xx errors\n // (e.g. bad request) as they are developer driven and should not\n // trigger error-log based alerts.\n const errLevel: LogLevel = isUserUrl ? 'warn' : 'error';\n try {\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const bodyPreview = await getBodyPreview(response, lc, errLevel);\n\n // Bad Gateway or Gateway Timeout indicate the server was not reached\n const level =\n response.status === 502 || response.status === 504 ? errLevel : 'info';\n lc[level]?.('fetch from API server returned non-OK status', {\n url: finalUrl,\n status: response.status,\n bodyPreview,\n });\n\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n status: response.status,\n bodyPreview,\n message: `Fetch from API server returned non-OK status ${response.status}`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n status: response.status,\n bodyPreview,\n message: `Fetch from API server returned non-OK status ${response.status}`,\n queryIDs: [],\n },\n );\n }\n\n try {\n const json = await response.json();\n\n return validator.parse(json);\n } catch (error) {\n lc[errLevel]?.('failed to parse response', {\n url: finalUrl,\n error,\n });\n\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Parse,\n message: `Failed to parse response from API server: ${getErrorMessage(error)}`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Parse,\n message: `Failed to parse response from API server: ${getErrorMessage(error)}`,\n queryIDs: [],\n },\n 'error',\n {cause: error},\n );\n }\n } catch (error) {\n if (isProtocolError(error)) {\n throw error;\n }\n\n lc[errLevel]?.('failed to fetch from API server with unknown error', {\n url: finalUrl,\n error,\n });\n\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Fetch from API server failed with unknown error: ${getErrorMessage(error)}`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Fetch from API server failed with unknown error: ${getErrorMessage(error)}`,\n queryIDs: [],\n },\n 'error',\n {cause: error},\n );\n }\n}\n\n/**\n * Returns true if the url matches one of the allowedUrlPatterns.\n *\n * URLPattern automatically ignores query parameters and hash fragments during matching\n * because it sets search and hash to wildcard ('*') by default.\n *\n * Example URLPattern patterns:\n * - \"https://api.example.com/endpoint\" - Exact match for a specific URL\n * - \"https://*.example.com/endpoint\" - Matches any single subdomain (e.g., \"https://api.example.com/endpoint\")\n * - \"https://*.*.example.com/endpoint\" - Matches two subdomains (e.g., \"https://api.v1.example.com/endpoint\")\n * - \"https://api.example.com/*\" - Matches any path under /\n * - \"https://api.example.com/:version/endpoint\" - Matches with named parameter (e.g., \"https://api.example.com/v1/endpoint\")\n */\nexport function urlMatch(\n url: string,\n allowedUrlPatterns: URLPattern[],\n): boolean {\n for (const pattern of allowedUrlPatterns) {\n if (pattern.test(url)) {\n return true;\n }\n }\n return false;\n}\n"],"names":["ErrorKind.PushFailed","ErrorOrigin.ZeroCache","ErrorReason.Internal","ErrorKind.TransformFailed","ErrorReason.HTTP","ErrorReason.Parse"],"mappings":"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"}
1
+ {"version":3,"file":"fetch.js","sources":["../../../../../zero-cache/src/custom/fetch.ts"],"sourcesContent":["import type {LogContext, LogLevel} from '@rocicorp/logger';\nimport 'urlpattern-polyfill';\nimport {assert, unreachable} from '../../../shared/src/asserts.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport type {ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {sleep} from '../../../shared/src/sleep.ts';\nimport {type Type} from '../../../shared/src/valita.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {isProtocolError} from '../../../zero-protocol/src/error.ts';\nimport {ProtocolErrorWithLevel} from '../types/error-with-level.ts';\nimport {upstreamSchema, type ShardID} from '../types/shards.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\n\nconst reservedParams = ['schema', 'appID'];\n\n/**\n * Compiles and validates a URLPattern from configuration.\n *\n * Patterns must be full URLs (e.g., \"https://api.example.com/endpoint\").\n * URLPattern automatically sets search and hash to wildcard ('*'),\n * which means query parameters and fragments are ignored during matching.\n *\n * @throws Error if the pattern is an invalid URLPattern\n */\nexport function compileUrlPattern(pattern: string): URLPattern {\n try {\n return new URLPattern(pattern);\n } catch (e) {\n throw new Error(\n `Invalid URLPattern in URL configuration: \"${pattern}\". Error: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n\nexport type HeaderOptions = {\n apiKey?: string | undefined;\n customHeaders?: Record<string, string> | undefined;\n token?: string | undefined;\n cookie?: string | undefined;\n};\n\nexport const getBodyPreview = async (\n res: Response,\n lc: LogContext,\n): Promise<string | undefined> => {\n try {\n const body = await res.clone().text();\n if (body.length > 512) {\n return body.slice(0, 512) + '...';\n }\n return body;\n } catch (e) {\n lc.warn?.(\n 'failed to get body preview',\n {\n url: res.url,\n },\n e,\n );\n }\n\n return undefined;\n};\n\nconst MAX_ATTEMPTS = 4;\n\nexport async function fetchFromAPIServer<TValidator extends Type>(\n validator: TValidator,\n source: 'push' | 'transform',\n lc: LogContext,\n url: string,\n allowedUrlPatterns: URLPattern[],\n shard: ShardID,\n headerOptions: HeaderOptions,\n body: ReadonlyJSONValue,\n) {\n const fetchFromAPIServerID = randInt(1, Number.MAX_SAFE_INTEGER).toString(36);\n lc = lc.withContext('fetchFromAPIServerID', fetchFromAPIServerID);\n\n lc.debug?.('fetchFromAPIServer called', {\n url,\n });\n\n if (!urlMatch(url, allowedUrlPatterns)) {\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `URL \"${url}\" is not allowed by the ZERO_MUTATE_URL configuration`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `URL \"${url}\" is not allowed by the ZERO_QUERY_URL configuration`,\n queryIDs: [],\n },\n 'warn',\n );\n }\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n\n if (headerOptions.apiKey) {\n headers['X-Api-Key'] = headerOptions.apiKey;\n }\n if (headerOptions.customHeaders) {\n Object.assign(headers, headerOptions.customHeaders);\n }\n if (headerOptions.token) {\n headers['Authorization'] = `Bearer ${headerOptions.token}`;\n }\n if (headerOptions.cookie) {\n headers['Cookie'] = headerOptions.cookie;\n }\n\n const urlObj = new URL(url);\n const params = new URLSearchParams(urlObj.search);\n\n for (const reserved of reservedParams) {\n assert(\n !params.has(reserved),\n `The push URL cannot contain the reserved query param \"${reserved}\"`,\n );\n }\n\n params.append('schema', upstreamSchema(shard));\n params.append('appID', shard.appID);\n\n urlObj.search = params.toString();\n\n const finalUrl = urlObj.toString();\n\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n lc = lc.withContext('fetchFromAPIServerAttempt', attempt);\n lc.debug?.('fetch from API server attempt');\n const shouldRetry = async () => {\n if (attempt < MAX_ATTEMPTS) {\n const delayMs = getBackoffDelayMs(attempt);\n lc.debug?.(`fetch from API server retrying in ${delayMs} ms`);\n await sleep(delayMs);\n return true;\n }\n lc.debug?.('fetch from API server reached max attempts, not retrying');\n return false;\n };\n try {\n const response = await fetch(finalUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const bodyPreview = await getBodyPreview(response, lc);\n lc.warn?.('fetch from API server returned non-OK status', {\n url: finalUrl,\n status: response.status,\n bodyPreview,\n });\n // Bad Gateway or Gateway Timeout indicate the server was not reached\n // We retry these if we have retries remaining.\n if (\n (response.status === 502 || response.status === 504) &&\n (await shouldRetry())\n ) {\n continue;\n }\n\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n status: response.status,\n bodyPreview,\n message: `Fetch from API server returned non-OK status ${response.status}`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.HTTP,\n status: response.status,\n bodyPreview,\n message: `Fetch from API server returned non-OK status ${response.status}`,\n queryIDs: [],\n },\n 'warn',\n );\n }\n\n try {\n const json = await response.json();\n const result = validator.parse(json);\n lc.debug?.('fetch from API server succeeded');\n return result;\n } catch (error) {\n lc.warn?.(\n 'failed to parse response',\n {\n url: finalUrl,\n },\n error,\n );\n\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Parse,\n message: `Failed to parse response from API server: ${getErrorMessage(error)}`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Parse,\n message: `Failed to parse response from API server: ${getErrorMessage(error)}`,\n queryIDs: [],\n },\n 'warn',\n {cause: error},\n );\n }\n } catch (error) {\n if (isProtocolError(error)) {\n throw error;\n }\n\n const isFetchFailed =\n error instanceof TypeError && error.message === 'fetch failed';\n // unexpected/unknown errors should be logged at 'error' level so they\n // are investigated\n let logLevel: LogLevel = isFetchFailed ? 'warn' : 'error';\n lc[logLevel]?.(\n 'fetch from API server threw error',\n {url: finalUrl},\n error,\n );\n\n if (isFetchFailed && (await shouldRetry())) {\n continue;\n }\n\n throw new ProtocolErrorWithLevel(\n source === 'push'\n ? {\n kind: ErrorKind.PushFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Fetch from API server threw error: ${getErrorMessage(error)}`,\n mutationIDs: [],\n }\n : {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Fetch from API server threw error: ${getErrorMessage(error)}`,\n queryIDs: [],\n },\n logLevel,\n {cause: error},\n );\n }\n }\n unreachable();\n}\n\n/**\n * Returns true if the url matches one of the allowedUrlPatterns.\n *\n * URLPattern automatically ignores query parameters and hash fragments during matching\n * because it sets search and hash to wildcard ('*') by default.\n *\n * Example URLPattern patterns:\n * - \"https://api.example.com/endpoint\" - Exact match for a specific URL\n * - \"https://*.example.com/endpoint\" - Matches any single subdomain (e.g., \"https://api.example.com/endpoint\")\n * - \"https://*.*.example.com/endpoint\" - Matches two subdomains (e.g., \"https://api.v1.example.com/endpoint\")\n * - \"https://api.example.com/*\" - Matches any path under /\n * - \"https://api.example.com/:version/endpoint\" - Matches with named parameter (e.g., \"https://api.example.com/v1/endpoint\")\n */\nexport function urlMatch(\n url: string,\n allowedUrlPatterns: URLPattern[],\n): boolean {\n for (const pattern of allowedUrlPatterns) {\n if (pattern.test(url)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Returns the delay in milliseconds for the next retry attempt using exponential backoff with jitter.\n *\n * The delay assumes the first retry is attempt 1.\n * The formula is: `min(1000, 100 * 2^(attempt - 1) + jitter)` where jitter is between 0 and 100ms.\n */\nfunction getBackoffDelayMs(attempt: number): number {\n return Math.min(1000, 100 * Math.pow(2, attempt - 1) + Math.random() * 100);\n}\n"],"names":["ErrorKind.PushFailed","ErrorOrigin.ZeroCache","ErrorReason.Internal","ErrorKind.TransformFailed","ErrorReason.HTTP","ErrorReason.Parse"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"transform-query.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,EAEL,KAAK,YAAY,EAGlB,MAAM,8CAA8C,CAAC;AAItD,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,sBAAsB;;gBAW/B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,EAAE,CAAC;QACd,cAAc,EAAE,OAAO,CAAC;KACzB,EACD,KAAK,EAAE,OAAO;IASV,SAAS,CACb,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACpC,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,CAAC,oBAAoB,GAAG,YAAY,CAAC,EAAE,GAAG,mBAAmB,CAAC;CA8F1E"}
1
+ {"version":3,"file":"transform-query.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAIjD,OAAO,EAEL,KAAK,YAAY,EAGlB,MAAM,8CAA8C,CAAC;AAItD,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,sBAAsB;;gBAW/B,EAAE,EAAE,UAAU,EACd,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,EAAE,CAAC;QACd,cAAc,EAAE,OAAO,CAAC;KACzB,EACD,KAAK,EAAE,OAAO;IASV,SAAS,CACb,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACpC,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,CAAC,oBAAoB,GAAG,YAAY,CAAC,EAAE,GAAG,mBAAmB,CAAC;CA6F1E"}
@@ -57,7 +57,6 @@ class CustomQueryTransformer {
57
57
  this.#config.url[0],
58
58
  "A ZERO_QUERY_URL must be configured for custom queries"
59
59
  ),
60
- userQueryURL !== void 0,
61
60
  this.#urlPatterns,
62
61
  this.#shard,
63
62
  headerOptions,
@@ -1 +1 @@
1
- {"version":3,"file":"transform-query.js","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {\n transformResponseMessageSchema,\n type ErroredQuery,\n type TransformRequestBody,\n type TransformRequestMessage,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type TransformFailedBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {hashOfAST} from '../../../zero-protocol/src/query-hash.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {\n compileUrlPattern,\n fetchFromAPIServer,\n type HeaderOptions,\n} from '../custom/fetch.ts';\nimport type {CustomQueryRecord} from '../services/view-syncer/schema/types.ts';\nimport type {ShardID} from '../types/shards.ts';\n\n/**\n * Transforms a custom query by calling the user's API server.\n * Caches the transformed queries for 5 seconds to avoid unnecessary API calls.\n *\n * Error responses are not cached as the user may want to retry the query\n * and the error may be transient.\n *\n * The TTL was chosen to be 5 seconds since custom query requests come with\n * a token which itself may have a short TTL (e.g., 10 seconds).\n *\n * Token expiration isn't expected to be exact so this 5 second\n * caching shouldn't cause unexpected behavior. E.g., many JWT libraries\n * implement leeway for expiration checks: https://github.com/panva/jose/blob/main/docs/jwt/verify/interfaces/JWTVerifyOptions.md#clocktolerance\n *\n * The ViewSyncer will call the API server 3-4 times with the exact same queries\n * if we do not cache requests.\n *\n * Caching is safe here because the cache key encodes both\n * the user's cookies and auth token. A user cannot see another user's\n * transformed queries unless they share the same token and cookies.\n */\nexport class CustomQueryTransformer {\n readonly #shard: ShardID;\n readonly #cache: TimedCache<TransformedAndHashed>;\n readonly #config: {\n url: string[];\n forwardCookies: boolean;\n };\n readonly #urlPatterns: URLPattern[];\n readonly #lc: LogContext;\n\n constructor(\n lc: LogContext,\n config: {\n url: string[];\n forwardCookies: boolean;\n },\n shard: ShardID,\n ) {\n this.#config = config;\n this.#shard = shard;\n this.#lc = lc;\n this.#urlPatterns = config.url.map(compileUrlPattern);\n this.#cache = new TimedCache(5000); // 5 seconds cache TTL\n }\n\n async transform(\n headerOptions: HeaderOptions,\n queries: Iterable<CustomQueryRecord>,\n userQueryURL: string | undefined,\n ): Promise<(TransformedAndHashed | ErroredQuery)[] | TransformFailedBody> {\n const request: TransformRequestBody = [];\n const cachedResponses: TransformedAndHashed[] = [];\n\n if (!this.#config.forwardCookies && headerOptions.cookie) {\n headerOptions = {\n ...headerOptions,\n cookie: undefined, // remove cookies if not forwarded\n };\n }\n\n // split queries into cached and uncached\n for (const query of queries) {\n const cacheKey = getCacheKey(headerOptions, query.id);\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n cachedResponses.push(cached);\n } else {\n request.push({\n id: query.id,\n name: query.name,\n args: query.args,\n });\n }\n }\n\n if (request.length === 0) {\n return cachedResponses;\n }\n\n const queryIDs = request.map(r => r.id);\n\n try {\n const transformResponse = await fetchFromAPIServer(\n transformResponseMessageSchema,\n 'transform',\n this.#lc,\n userQueryURL ??\n must(\n this.#config.url[0],\n 'A ZERO_QUERY_URL must be configured for custom queries',\n ),\n userQueryURL !== undefined,\n this.#urlPatterns,\n this.#shard,\n headerOptions,\n ['transform', request] satisfies TransformRequestMessage,\n );\n\n if (transformResponse[0] === 'transformFailed') {\n return transformResponse[1];\n }\n\n const newResponses = transformResponse[1].map(transformed => {\n if ('error' in transformed) {\n return transformed;\n }\n return {\n id: transformed.id,\n transformedAst: transformed.ast,\n transformationHash: hashOfAST(transformed.ast),\n } satisfies TransformedAndHashed;\n });\n\n for (const transformed of newResponses) {\n if ('error' in transformed) {\n // do not cache error responses\n continue;\n }\n const cacheKey = getCacheKey(headerOptions, transformed.id);\n this.#cache.set(cacheKey, transformed);\n }\n\n return newResponses.concat(cachedResponses);\n } catch (e) {\n if (\n isProtocolError(e) &&\n e.errorBody.kind === ErrorKind.TransformFailed\n ) {\n return {\n ...e.errorBody,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Failed to transform queries: ${getErrorMessage(e)}`,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n }\n}\n\nfunction getCacheKey(headerOptions: HeaderOptions, queryID: string) {\n // For custom queries, queryID is a hash of the name + args.\n // the APIKey from headerOptions is static. Not needed for the cache key.\n // The token is used to identify the user and should be included in the cache key.\n return `${headerOptions.token}:${headerOptions.cookie}:${queryID}`;\n}\n"],"names":["ErrorKind.TransformFailed","ErrorOrigin.ZeroCache","ErrorReason.Internal"],"mappings":";;;;;;;;;;AAgDO,MAAM,uBAAuB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EAIA;AAAA,EACA;AAAA,EAET,YACE,IACA,QAIA,OACA;AACA,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,eAAe,OAAO,IAAI,IAAI,iBAAiB;AACpD,SAAK,SAAS,IAAI,WAAW,GAAI;AAAA,EACnC;AAAA,EAEA,MAAM,UACJ,eACA,SACA,cACwE;AACxE,UAAM,UAAgC,CAAA;AACtC,UAAM,kBAA0C,CAAA;AAEhD,QAAI,CAAC,KAAK,QAAQ,kBAAkB,cAAc,QAAQ;AACxD,sBAAgB;AAAA,QACd,GAAG;AAAA,QACH,QAAQ;AAAA;AAAA,MAAA;AAAA,IAEZ;AAGA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,YAAY,eAAe,MAAM,EAAE;AACpD,YAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,UAAI,QAAQ;AACV,wBAAgB,KAAK,MAAM;AAAA,MAC7B,OAAO;AACL,gBAAQ,KAAK;AAAA,UACX,IAAI,MAAM;AAAA,UACV,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM;AAAA,QAAA,CACb;AAAA,MACH;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,QAAQ,IAAI,CAAA,MAAK,EAAE,EAAE;AAEtC,QAAI;AACF,YAAM,oBAAoB,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL,gBACE;AAAA,UACE,KAAK,QAAQ,IAAI,CAAC;AAAA,UAClB;AAAA,QAAA;AAAA,QAEJ,iBAAiB;AAAA,QACjB,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,CAAC,aAAa,OAAO;AAAA,MAAA;AAGvB,UAAI,kBAAkB,CAAC,MAAM,mBAAmB;AAC9C,eAAO,kBAAkB,CAAC;AAAA,MAC5B;AAEA,YAAM,eAAe,kBAAkB,CAAC,EAAE,IAAI,CAAA,gBAAe;AAC3D,YAAI,WAAW,aAAa;AAC1B,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,UACL,IAAI,YAAY;AAAA,UAChB,gBAAgB,YAAY;AAAA,UAC5B,oBAAoB,UAAU,YAAY,GAAG;AAAA,QAAA;AAAA,MAEjD,CAAC;AAED,iBAAW,eAAe,cAAc;AACtC,YAAI,WAAW,aAAa;AAE1B;AAAA,QACF;AACA,cAAM,WAAW,YAAY,eAAe,YAAY,EAAE;AAC1D,aAAK,OAAO,IAAI,UAAU,WAAW;AAAA,MACvC;AAEA,aAAO,aAAa,OAAO,eAAe;AAAA,IAC5C,SAAS,GAAG;AACV,UACE,gBAAgB,CAAC,KACjB,EAAE,UAAU,SAASA,iBACrB;AACA,eAAO;AAAA,UACL,GAAG,EAAE;AAAA,UACL;AAAA,QAAA;AAAA,MAEJ;AAEA,aAAO;AAAA,QACL,MAAMA;AAAAA,QACN,QAAQC;AAAAA,QACR,QAAQC;AAAAA,QACR,SAAS,gCAAgC,gBAAgB,CAAC,CAAC;AAAA,QAC3D;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAEA,SAAS,YAAY,eAA8B,SAAiB;AAIlE,SAAO,GAAG,cAAc,KAAK,IAAI,cAAc,MAAM,IAAI,OAAO;AAClE;"}
1
+ {"version":3,"file":"transform-query.js","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {must} from '../../../shared/src/must.ts';\nimport {\n transformResponseMessageSchema,\n type ErroredQuery,\n type TransformRequestBody,\n type TransformRequestMessage,\n} from '../../../zero-protocol/src/custom-queries.ts';\nimport {ErrorKind} from '../../../zero-protocol/src/error-kind.ts';\nimport {ErrorOrigin} from '../../../zero-protocol/src/error-origin.ts';\nimport {ErrorReason} from '../../../zero-protocol/src/error-reason.ts';\nimport {\n isProtocolError,\n type TransformFailedBody,\n} from '../../../zero-protocol/src/error.ts';\nimport {hashOfAST} from '../../../zero-protocol/src/query-hash.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {\n compileUrlPattern,\n fetchFromAPIServer,\n type HeaderOptions,\n} from '../custom/fetch.ts';\nimport type {CustomQueryRecord} from '../services/view-syncer/schema/types.ts';\nimport type {ShardID} from '../types/shards.ts';\n\n/**\n * Transforms a custom query by calling the user's API server.\n * Caches the transformed queries for 5 seconds to avoid unnecessary API calls.\n *\n * Error responses are not cached as the user may want to retry the query\n * and the error may be transient.\n *\n * The TTL was chosen to be 5 seconds since custom query requests come with\n * a token which itself may have a short TTL (e.g., 10 seconds).\n *\n * Token expiration isn't expected to be exact so this 5 second\n * caching shouldn't cause unexpected behavior. E.g., many JWT libraries\n * implement leeway for expiration checks: https://github.com/panva/jose/blob/main/docs/jwt/verify/interfaces/JWTVerifyOptions.md#clocktolerance\n *\n * The ViewSyncer will call the API server 3-4 times with the exact same queries\n * if we do not cache requests.\n *\n * Caching is safe here because the cache key encodes both\n * the user's cookies and auth token. A user cannot see another user's\n * transformed queries unless they share the same token and cookies.\n */\nexport class CustomQueryTransformer {\n readonly #shard: ShardID;\n readonly #cache: TimedCache<TransformedAndHashed>;\n readonly #config: {\n url: string[];\n forwardCookies: boolean;\n };\n readonly #urlPatterns: URLPattern[];\n readonly #lc: LogContext;\n\n constructor(\n lc: LogContext,\n config: {\n url: string[];\n forwardCookies: boolean;\n },\n shard: ShardID,\n ) {\n this.#config = config;\n this.#shard = shard;\n this.#lc = lc;\n this.#urlPatterns = config.url.map(compileUrlPattern);\n this.#cache = new TimedCache(5000); // 5 seconds cache TTL\n }\n\n async transform(\n headerOptions: HeaderOptions,\n queries: Iterable<CustomQueryRecord>,\n userQueryURL: string | undefined,\n ): Promise<(TransformedAndHashed | ErroredQuery)[] | TransformFailedBody> {\n const request: TransformRequestBody = [];\n const cachedResponses: TransformedAndHashed[] = [];\n\n if (!this.#config.forwardCookies && headerOptions.cookie) {\n headerOptions = {\n ...headerOptions,\n cookie: undefined, // remove cookies if not forwarded\n };\n }\n\n // split queries into cached and uncached\n for (const query of queries) {\n const cacheKey = getCacheKey(headerOptions, query.id);\n const cached = this.#cache.get(cacheKey);\n if (cached) {\n cachedResponses.push(cached);\n } else {\n request.push({\n id: query.id,\n name: query.name,\n args: query.args,\n });\n }\n }\n\n if (request.length === 0) {\n return cachedResponses;\n }\n\n const queryIDs = request.map(r => r.id);\n\n try {\n const transformResponse = await fetchFromAPIServer(\n transformResponseMessageSchema,\n 'transform',\n this.#lc,\n userQueryURL ??\n must(\n this.#config.url[0],\n 'A ZERO_QUERY_URL must be configured for custom queries',\n ),\n this.#urlPatterns,\n this.#shard,\n headerOptions,\n ['transform', request] satisfies TransformRequestMessage,\n );\n\n if (transformResponse[0] === 'transformFailed') {\n return transformResponse[1];\n }\n\n const newResponses = transformResponse[1].map(transformed => {\n if ('error' in transformed) {\n return transformed;\n }\n return {\n id: transformed.id,\n transformedAst: transformed.ast,\n transformationHash: hashOfAST(transformed.ast),\n } satisfies TransformedAndHashed;\n });\n\n for (const transformed of newResponses) {\n if ('error' in transformed) {\n // do not cache error responses\n continue;\n }\n const cacheKey = getCacheKey(headerOptions, transformed.id);\n this.#cache.set(cacheKey, transformed);\n }\n\n return newResponses.concat(cachedResponses);\n } catch (e) {\n if (\n isProtocolError(e) &&\n e.errorBody.kind === ErrorKind.TransformFailed\n ) {\n return {\n ...e.errorBody,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n\n return {\n kind: ErrorKind.TransformFailed,\n origin: ErrorOrigin.ZeroCache,\n reason: ErrorReason.Internal,\n message: `Failed to transform queries: ${getErrorMessage(e)}`,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n }\n}\n\nfunction getCacheKey(headerOptions: HeaderOptions, queryID: string) {\n // For custom queries, queryID is a hash of the name + args.\n // the APIKey from headerOptions is static. Not needed for the cache key.\n // The token is used to identify the user and should be included in the cache key.\n return `${headerOptions.token}:${headerOptions.cookie}:${queryID}`;\n}\n"],"names":["ErrorKind.TransformFailed","ErrorOrigin.ZeroCache","ErrorReason.Internal"],"mappings":";;;;;;;;;;AAgDO,MAAM,uBAAuB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EAIA;AAAA,EACA;AAAA,EAET,YACE,IACA,QAIA,OACA;AACA,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,eAAe,OAAO,IAAI,IAAI,iBAAiB;AACpD,SAAK,SAAS,IAAI,WAAW,GAAI;AAAA,EACnC;AAAA,EAEA,MAAM,UACJ,eACA,SACA,cACwE;AACxE,UAAM,UAAgC,CAAA;AACtC,UAAM,kBAA0C,CAAA;AAEhD,QAAI,CAAC,KAAK,QAAQ,kBAAkB,cAAc,QAAQ;AACxD,sBAAgB;AAAA,QACd,GAAG;AAAA,QACH,QAAQ;AAAA;AAAA,MAAA;AAAA,IAEZ;AAGA,eAAW,SAAS,SAAS;AAC3B,YAAM,WAAW,YAAY,eAAe,MAAM,EAAE;AACpD,YAAM,SAAS,KAAK,OAAO,IAAI,QAAQ;AACvC,UAAI,QAAQ;AACV,wBAAgB,KAAK,MAAM;AAAA,MAC7B,OAAO;AACL,gBAAQ,KAAK;AAAA,UACX,IAAI,MAAM;AAAA,UACV,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM;AAAA,QAAA,CACb;AAAA,MACH;AAAA,IACF;AAEA,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,QAAQ,IAAI,CAAA,MAAK,EAAE,EAAE;AAEtC,QAAI;AACF,YAAM,oBAAoB,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL,gBACE;AAAA,UACE,KAAK,QAAQ,IAAI,CAAC;AAAA,UAClB;AAAA,QAAA;AAAA,QAEJ,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA,CAAC,aAAa,OAAO;AAAA,MAAA;AAGvB,UAAI,kBAAkB,CAAC,MAAM,mBAAmB;AAC9C,eAAO,kBAAkB,CAAC;AAAA,MAC5B;AAEA,YAAM,eAAe,kBAAkB,CAAC,EAAE,IAAI,CAAA,gBAAe;AAC3D,YAAI,WAAW,aAAa;AAC1B,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,UACL,IAAI,YAAY;AAAA,UAChB,gBAAgB,YAAY;AAAA,UAC5B,oBAAoB,UAAU,YAAY,GAAG;AAAA,QAAA;AAAA,MAEjD,CAAC;AAED,iBAAW,eAAe,cAAc;AACtC,YAAI,WAAW,aAAa;AAE1B;AAAA,QACF;AACA,cAAM,WAAW,YAAY,eAAe,YAAY,EAAE;AAC1D,aAAK,OAAO,IAAI,UAAU,WAAW;AAAA,MACvC;AAEA,aAAO,aAAa,OAAO,eAAe;AAAA,IAC5C,SAAS,GAAG;AACV,UACE,gBAAgB,CAAC,KACjB,EAAE,UAAU,SAASA,iBACrB;AACA,eAAO;AAAA,UACL,GAAG,EAAE;AAAA,UACL;AAAA,QAAA;AAAA,MAEJ;AAEA,aAAO;AAAA,QACL,MAAMA;AAAAA,QACN,QAAQC;AAAAA,QACR,QAAQC;AAAAA,QACR,SAAS,gCAAgC,gBAAgB,CAAC,CAAC;AAAA,QAC3D;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAEA,SAAS,YAAY,eAA8B,SAAiB;AAIlE,SAAO,GAAG,cAAc,KAAK,IAAI,cAAc,MAAM,IAAI,OAAO;AAClE;"}
@@ -1 +1 @@
1
- {"version":3,"file":"anonymous-otel-start.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/anonymous-otel-start.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAOjD,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,MAAM,WAAW,GAAG;IACxB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAwfF,eAAO,MAAM,uBAAuB,GAAI,KAAK,UAAU,EAAE,SAAS,UAAU,SAC/C,CAAC;AAC9B,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,GAAG,QAAQ,EAAE,cAAS,SAC1B,CAAC;AACxC,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,GAAG,QAAQ,EAAE,cAAS,SAC1B,CAAC;AACrC,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,SACX,CAAC;AACpC,eAAO,MAAM,uBAAuB,YACC,CAAC;AACtC,eAAO,MAAM,yBAAyB,YACC,CAAC;AACxC,eAAO,MAAM,2BAA2B,GAAI,QAAQ,MAAM,MAAM,SACjB,CAAC;AAChD,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,WAAW,SACtB,CAAC;AACzC,eAAO,MAAM,0BAA0B,YAA6B,CAAC"}
1
+ {"version":3,"file":"anonymous-otel-start.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/anonymous-otel-start.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAOjD,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,MAAM,WAAW,GAAG;IACxB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAyfF,eAAO,MAAM,uBAAuB,GAAI,KAAK,UAAU,EAAE,SAAS,UAAU,SAC/C,CAAC;AAC9B,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,GAAG,QAAQ,EAAE,cAAS,SAC1B,CAAC;AACxC,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,GAAG,QAAQ,EAAE,cAAS,SAC1B,CAAC;AACrC,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,SACX,CAAC;AACpC,eAAO,MAAM,uBAAuB,YACC,CAAC;AACtC,eAAO,MAAM,yBAAyB,YACC,CAAC;AACxC,eAAO,MAAM,2BAA2B,GAAI,QAAQ,MAAM,MAAM,SACjB,CAAC;AAChD,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,WAAW,SACtB,CAAC;AACzC,eAAO,MAAM,0BAA0B,YAA6B,CAAC"}
@@ -321,6 +321,7 @@ class AnonymousTelemetryManager {
321
321
  return this.#cachedAttributes;
322
322
  }
323
323
  #getPlatform() {
324
+ if (process.env.ZERO_ON_CLOUD_ZERO) return "cloudzero";
324
325
  if (process.env.FLY_APP_NAME || process.env.FLY_REGION) return "fly.io";
325
326
  if (process.env.ECS_CONTAINER_METADATA_URI_V4 || process.env.ECS_CONTAINER_METADATA_URI || process.env.AWS_EXECUTION_ENV)
326
327
  return "aws";
@@ -1 +1 @@
1
- {"version":3,"file":"anonymous-otel-start.js","sources":["../../../../../zero-cache/src/server/anonymous-otel-start.ts"],"sourcesContent":["import type {ObservableResult} from '@opentelemetry/api';\nimport {type Meter} from '@opentelemetry/api';\nimport {OTLPMetricExporter} from '@opentelemetry/exporter-metrics-otlp-http';\nimport {resourceFromAttributes} from '@opentelemetry/resources';\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from '@opentelemetry/sdk-metrics';\nimport type {LogContext} from '@rocicorp/logger';\nimport {execSync} from 'child_process';\nimport {randomUUID} from 'crypto';\nimport {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs';\nimport {homedir, platform} from 'os';\nimport {dirname, join} from 'path';\nimport {h64} from '../../../shared/src/hash.js';\nimport {\n getServerVersion,\n getZeroConfig,\n type ZeroConfig,\n} from '../config/zero-config.js';\nimport {setupOtelDiagnosticLogger} from './otel-diag-logger.js';\n\nexport type ActiveUsers = {\n active_users_last_day: number;\n users_1da: number;\n users_7da: number;\n users_30da: number;\n users_1da_legacy: number;\n users_7da_legacy: number;\n users_30da_legacy: number;\n};\n\nclass AnonymousTelemetryManager {\n static #instance: AnonymousTelemetryManager;\n #starting = false;\n #stopped = false;\n #meter!: Meter;\n #meterProvider!: MeterProvider;\n #totalCrudMutations = 0;\n #totalCustomMutations = 0;\n #totalCrudQueries = 0;\n #totalCustomQueries = 0;\n #totalRowsSynced = 0;\n #totalConnectionsSuccess = 0;\n #totalConnectionsAttempted = 0;\n #activeClientGroupsGetter: (() => number) | undefined;\n #activeUsersGetter: (() => ActiveUsers) | undefined;\n #lc: LogContext | undefined;\n #config: ZeroConfig | undefined;\n #processId: string;\n #cachedAttributes: Record<string, string> | undefined;\n #viewSyncerCount = 1;\n\n private constructor() {\n this.#processId = randomUUID();\n }\n\n static getInstance(): AnonymousTelemetryManager {\n if (!AnonymousTelemetryManager.#instance) {\n AnonymousTelemetryManager.#instance = new AnonymousTelemetryManager();\n }\n return AnonymousTelemetryManager.#instance;\n }\n\n start(lc?: LogContext, config?: ZeroConfig) {\n this.#lc = lc;\n\n // Set up OpenTelemetry diagnostic logger if not already configured\n setupOtelDiagnosticLogger(lc);\n\n if (!config) {\n try {\n config = getZeroConfig();\n } catch (e) {\n this.#lc?.info?.('telemetry: disabled - unable to parse config', e);\n return;\n }\n }\n\n if (process.env.DO_NOT_TRACK) {\n this.#lc?.info?.(\n 'telemetry: disabled - DO_NOT_TRACK environment variable is set',\n );\n return;\n }\n\n if (!config.enableTelemetry) {\n this.#lc?.info?.('telemetry: disabled - enableTelemetry is false');\n return;\n }\n\n if (this.#starting) {\n return;\n }\n\n this.#starting = true;\n this.#config = config;\n this.#viewSyncerCount = config.numSyncWorkers ?? 1;\n this.#cachedAttributes = undefined;\n\n this.#lc?.info?.(`telemetry: starting in 1 minute`);\n\n // Delay telemetry startup by 1 minute to avoid potential boot loop issues\n setTimeout(() => this.#run(), 60000);\n }\n\n #run() {\n if (this.#stopped) {\n return;\n }\n\n const resource = resourceFromAttributes(this.#getAttributes());\n\n // Add a random jitter to the export interval to avoid all view-syncers exporting at the same time\n const exportIntervalMillis =\n 60000 * this.#viewSyncerCount + Math.floor(Math.random() * 10000);\n const metricReader = new PeriodicExportingMetricReader({\n exportIntervalMillis,\n exporter: new OTLPMetricExporter({\n url: 'https://metrics.rocicorp.dev',\n timeoutMillis: 30000,\n }),\n });\n\n this.#meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n this.#meter = this.#meterProvider.getMeter('zero-anonymous-telemetry');\n\n this.#setupMetrics();\n this.#lc?.info?.(\n `telemetry: started (exports every ${exportIntervalMillis / 1000} seconds for ${this.#viewSyncerCount} view-syncers)`,\n );\n }\n\n #setupMetrics() {\n // Observable gauges\n const uptimeGauge = this.#meter.createObservableGauge('zero.uptime', {\n description: 'System uptime in seconds',\n unit: 'seconds',\n });\n\n // Observable counters\n const uptimeCounter = this.#meter.createObservableCounter(\n 'zero.uptime_counter',\n {\n description: 'System uptime in seconds',\n unit: 'seconds',\n },\n );\n const crudMutationsCounter = this.#meter.createObservableCounter(\n 'zero.crud_mutations_processed',\n {\n description: 'Total number of CRUD mutations processed',\n },\n );\n const customMutationsCounter = this.#meter.createObservableCounter(\n 'zero.custom_mutations_processed',\n {\n description: 'Total number of custom mutations processed',\n },\n );\n const totalMutationsCounter = this.#meter.createObservableCounter(\n 'zero.mutations_processed',\n {\n description: 'Total number of mutations processed',\n },\n );\n const crudQueriesCounter = this.#meter.createObservableCounter(\n 'zero.crud_queries_processed',\n {\n description: 'Total number of CRUD queries processed',\n },\n );\n const customQueriesCounter = this.#meter.createObservableCounter(\n 'zero.custom_queries_processed',\n {\n description: 'Total number of custom queries processed',\n },\n );\n const totalQueriesCounter = this.#meter.createObservableCounter(\n 'zero.queries_processed',\n {\n description: 'Total number of queries processed',\n },\n );\n const rowsSyncedCounter = this.#meter.createObservableCounter(\n 'zero.rows_synced',\n {\n description: 'Total number of rows synced',\n },\n );\n\n // Observable counters for connections\n const connectionsSuccessCounter = this.#meter.createObservableCounter(\n 'zero.connections_success',\n {\n description: 'Total number of successful connections',\n },\n );\n\n const connectionsAttemptedCounter = this.#meter.createObservableCounter(\n 'zero.connections_attempted',\n {\n description: 'Total number of attempted connections',\n },\n );\n\n const activeClientGroupsGauge = this.#meter.createObservableGauge(\n 'zero.gauge_active_client_groups',\n {\n description: 'Number of currently active client groups',\n },\n );\n\n const attrs = this.#getAttributes();\n const active =\n (metric: keyof ActiveUsers) => (result: ObservableResult) => {\n const actives = this.#activeUsersGetter?.();\n if (actives) {\n const value = actives[metric];\n result.observe(value, attrs);\n this.#lc?.debug?.(`telemetry: ${metric}=${value}`);\n } else {\n this.#lc?.debug?.(\n `telemetry: no actives available, skipping observation of ${metric}`,\n );\n }\n };\n this.#meter\n .createObservableGauge('zero.active_users_last_day', {\n description: 'Count of CVR instances active in the last 24h',\n })\n .addCallback(active('active_users_last_day'));\n this.#meter\n .createObservableGauge('zero.users_1da', {\n description: 'Count of 1-day active profiles',\n })\n .addCallback(active('users_1da'));\n this.#meter\n .createObservableGauge('zero.users_7da', {\n description: 'Count of 7-day active profiles',\n })\n .addCallback(active('users_7da'));\n this.#meter\n .createObservableGauge('zero.users_30da', {\n description: 'Count of 30-day active profiles',\n })\n .addCallback(active('users_30da'));\n this.#meter\n .createObservableGauge('zero.users_1da_legacy', {\n description: 'Count of 1-day active profiles with CVR fallback',\n })\n .addCallback(active('users_1da_legacy'));\n this.#meter\n .createObservableGauge('zero.users_7da_legacy', {\n description: 'Count of 7-day active profiles with CVR fallback',\n })\n .addCallback(active('users_7da_legacy'));\n this.#meter\n .createObservableGauge('zero.users_30da_legacy', {\n description: 'Count of 30-day active profiles with CVR fallback',\n })\n .addCallback(active('users_30da_legacy'));\n\n // Callbacks\n uptimeGauge.addCallback((result: ObservableResult) => {\n const uptimeSeconds = Math.floor(process.uptime());\n result.observe(uptimeSeconds, attrs);\n this.#lc?.debug?.(`telemetry: uptime=${uptimeSeconds}s`);\n });\n uptimeCounter.addCallback((result: ObservableResult) => {\n const uptimeSeconds = Math.floor(process.uptime());\n result.observe(uptimeSeconds, attrs);\n this.#lc?.debug?.(`telemetry: uptime_counter=${uptimeSeconds}s`);\n });\n crudMutationsCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCrudMutations, attrs);\n this.#lc?.debug?.(\n `telemetry: crud_mutations=${this.#totalCrudMutations}`,\n );\n });\n customMutationsCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCustomMutations, attrs);\n this.#lc?.debug?.(\n `telemetry: custom_mutations=${this.#totalCustomMutations}`,\n );\n });\n totalMutationsCounter.addCallback((result: ObservableResult) => {\n const totalMutations =\n this.#totalCrudMutations + this.#totalCustomMutations;\n result.observe(totalMutations, attrs);\n this.#lc?.debug?.(`telemetry: total_mutations=${totalMutations}`);\n });\n crudQueriesCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCrudQueries, attrs);\n this.#lc?.debug?.(`telemetry: crud_queries=${this.#totalCrudQueries}`);\n });\n customQueriesCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCustomQueries, attrs);\n this.#lc?.debug?.(\n `telemetry: custom_queries=${this.#totalCustomQueries}`,\n );\n });\n totalQueriesCounter.addCallback((result: ObservableResult) => {\n const totalQueries = this.#totalCrudQueries + this.#totalCustomQueries;\n result.observe(totalQueries, attrs);\n this.#lc?.debug?.(`telemetry: total_queries=${totalQueries}`);\n });\n rowsSyncedCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalRowsSynced, attrs);\n this.#lc?.debug?.(`telemetry: rows_synced=${this.#totalRowsSynced}`);\n });\n connectionsSuccessCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalConnectionsSuccess, attrs);\n this.#lc?.debug?.(\n `telemetry: connections_success=${this.#totalConnectionsSuccess}`,\n );\n });\n connectionsAttemptedCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalConnectionsAttempted, attrs);\n this.#lc?.debug?.(\n `telemetry: connections_attempted=${this.#totalConnectionsAttempted}`,\n );\n });\n activeClientGroupsGauge.addCallback((result: ObservableResult) => {\n const activeClientGroups = this.#activeClientGroupsGetter?.() ?? 0;\n result.observe(activeClientGroups, attrs);\n this.#lc?.debug?.(\n `telemetry: gauge_active_client_groups=${activeClientGroups}`,\n );\n });\n }\n\n recordMutation(type: 'crud' | 'custom', count = 1) {\n if (type === 'crud') {\n this.#totalCrudMutations += count;\n } else {\n this.#totalCustomMutations += count;\n }\n }\n\n recordQuery(type: 'crud' | 'custom', count = 1) {\n if (type === 'crud') {\n this.#totalCrudQueries += count;\n } else {\n this.#totalCustomQueries += count;\n }\n }\n\n recordRowsSynced(count: number) {\n this.#totalRowsSynced += count;\n }\n\n recordConnectionSuccess() {\n this.#totalConnectionsSuccess++;\n }\n\n recordConnectionAttempted() {\n this.#totalConnectionsAttempted++;\n }\n\n setActiveClientGroupsGetter(getter: () => number) {\n this.#activeClientGroupsGetter = getter;\n }\n\n setActiveUsersGetter(getter: () => ActiveUsers) {\n this.#activeUsersGetter = getter;\n }\n\n shutdown() {\n this.#stopped = true;\n if (this.#meterProvider) {\n this.#lc?.info?.('telemetry: shutting down');\n void this.#meterProvider.shutdown();\n }\n }\n\n #getAttributes() {\n if (!this.#cachedAttributes) {\n this.#cachedAttributes = {\n 'zero.app.id': h64(this.#config?.upstream.db || 'unknown').toString(),\n 'zero.machine.os': platform(),\n 'zero.telemetry.type': 'anonymous',\n 'zero.infra.platform': this.#getPlatform(),\n 'zero.version': getServerVersion(this.#config),\n 'zero.task.id': this.#config?.taskID || 'unknown',\n 'zero.project.id': this.#getGitProjectId(),\n 'zero.process.id': this.#processId,\n 'zero.fs.id': this.#getOrSetFsID(),\n };\n this.#lc?.debug?.(\n `telemetry: cached attributes=${JSON.stringify(this.#cachedAttributes)}`,\n );\n }\n return this.#cachedAttributes;\n }\n\n #getPlatform(): string {\n if (process.env.FLY_APP_NAME || process.env.FLY_REGION) return 'fly.io';\n if (\n process.env.ECS_CONTAINER_METADATA_URI_V4 ||\n process.env.ECS_CONTAINER_METADATA_URI ||\n process.env.AWS_EXECUTION_ENV\n )\n return 'aws';\n if (process.env.RAILWAY_ENV || process.env.RAILWAY_STATIC_URL)\n return 'railway';\n if (process.env.RENDER || process.env.RENDER_SERVICE_ID) return 'render';\n if (\n process.env.GCP_PROJECT ||\n process.env.GCLOUD_PROJECT ||\n process.env.GOOGLE_CLOUD_PROJECT\n )\n return 'gcp';\n if (process.env.COOLIFY_URL || process.env.COOLIFY_CONTAINER_NAME)\n return 'coolify';\n if (process.env.CONTAINER_APP_REVISION) return 'azure';\n if (process.env.FLIGHTCONTROL || process.env.FC_URL) return 'flightcontrol';\n return 'unknown';\n }\n\n #findUp(startDir: string, target: string): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, target))) return dir;\n dir = dirname(dir);\n }\n return null;\n }\n\n #getGitProjectId(): string {\n try {\n const cwd = process.cwd();\n const gitRoot = this.#findUp(cwd, '.git');\n if (!gitRoot) {\n return 'unknown';\n }\n\n const rootCommitHash = execSync('git rev-list --max-parents=0 HEAD -1', {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore'], // Suppress stderr\n }).trim();\n\n return rootCommitHash.length === 40 ? rootCommitHash : 'unknown';\n } catch (error) {\n this.#lc?.debug?.('telemetry: unable to get Git root commit:', error);\n return 'unknown';\n }\n }\n\n #getOrSetFsID(): string {\n try {\n if (this.#isInContainer()) {\n return 'container';\n }\n const fsidPath = join(homedir(), '.rocicorp', 'fsid');\n const fsidDir = dirname(fsidPath);\n\n mkdirSync(fsidDir, {recursive: true});\n\n // Always try atomic file creation first - this eliminates any race conditions\n const newId = randomUUID();\n try {\n writeFileSync(fsidPath, newId, {encoding: 'utf8', flag: 'wx'});\n return newId;\n } catch (writeError) {\n if ((writeError as NodeJS.ErrnoException).code === 'EEXIST') {\n const existingId = readFileSync(fsidPath, 'utf8').trim();\n return existingId;\n }\n throw writeError;\n }\n } catch (error) {\n this.#lc?.debug?.(\n 'telemetry: unable to get or set filesystem ID:',\n error,\n );\n return 'unknown';\n }\n }\n\n #isInContainer(): boolean {\n try {\n if (process.env.ZERO_IN_CONTAINER) {\n return true;\n }\n\n if (existsSync('/.dockerenv')) {\n return true;\n }\n\n if (existsSync('/usr/local/bin/docker-entrypoint.sh')) {\n return true;\n }\n\n if (process.env.KUBERNETES_SERVICE_HOST) {\n return true;\n }\n\n if (\n process.env.DOCKER_CONTAINER_ID ||\n process.env.HOSTNAME?.match(/^[a-f0-9]{12}$/)\n ) {\n return true;\n }\n\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (\n cgroup.includes('docker') ||\n cgroup.includes('kubepods') ||\n cgroup.includes('containerd')\n ) {\n return true;\n }\n }\n\n return false;\n } catch (error) {\n this.#lc?.debug?.(\n 'telemetry: unable to detect container environment:',\n error,\n );\n return false;\n }\n }\n}\n\nconst manager = () => AnonymousTelemetryManager.getInstance();\n\nexport const startAnonymousTelemetry = (lc?: LogContext, config?: ZeroConfig) =>\n manager().start(lc, config);\nexport const recordMutation = (type: 'crud' | 'custom', count = 1) =>\n manager().recordMutation(type, count);\nexport const recordQuery = (type: 'crud' | 'custom', count = 1) =>\n manager().recordQuery(type, count);\nexport const recordRowsSynced = (count: number) =>\n manager().recordRowsSynced(count);\nexport const recordConnectionSuccess = () =>\n manager().recordConnectionSuccess();\nexport const recordConnectionAttempted = () =>\n manager().recordConnectionAttempted();\nexport const setActiveClientGroupsGetter = (getter: () => number) =>\n manager().setActiveClientGroupsGetter(getter);\nexport const setActiveUsersGetter = (getter: () => ActiveUsers) =>\n manager().setActiveUsersGetter(getter);\nexport const shutdownAnonymousTelemetry = () => manager().shutdown();\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"anonymous-otel-start.js","sources":["../../../../../zero-cache/src/server/anonymous-otel-start.ts"],"sourcesContent":["import type {ObservableResult} from '@opentelemetry/api';\nimport {type Meter} from '@opentelemetry/api';\nimport {OTLPMetricExporter} from '@opentelemetry/exporter-metrics-otlp-http';\nimport {resourceFromAttributes} from '@opentelemetry/resources';\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from '@opentelemetry/sdk-metrics';\nimport type {LogContext} from '@rocicorp/logger';\nimport {execSync} from 'child_process';\nimport {randomUUID} from 'crypto';\nimport {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs';\nimport {homedir, platform} from 'os';\nimport {dirname, join} from 'path';\nimport {h64} from '../../../shared/src/hash.js';\nimport {\n getServerVersion,\n getZeroConfig,\n type ZeroConfig,\n} from '../config/zero-config.js';\nimport {setupOtelDiagnosticLogger} from './otel-diag-logger.js';\n\nexport type ActiveUsers = {\n active_users_last_day: number;\n users_1da: number;\n users_7da: number;\n users_30da: number;\n users_1da_legacy: number;\n users_7da_legacy: number;\n users_30da_legacy: number;\n};\n\nclass AnonymousTelemetryManager {\n static #instance: AnonymousTelemetryManager;\n #starting = false;\n #stopped = false;\n #meter!: Meter;\n #meterProvider!: MeterProvider;\n #totalCrudMutations = 0;\n #totalCustomMutations = 0;\n #totalCrudQueries = 0;\n #totalCustomQueries = 0;\n #totalRowsSynced = 0;\n #totalConnectionsSuccess = 0;\n #totalConnectionsAttempted = 0;\n #activeClientGroupsGetter: (() => number) | undefined;\n #activeUsersGetter: (() => ActiveUsers) | undefined;\n #lc: LogContext | undefined;\n #config: ZeroConfig | undefined;\n #processId: string;\n #cachedAttributes: Record<string, string> | undefined;\n #viewSyncerCount = 1;\n\n private constructor() {\n this.#processId = randomUUID();\n }\n\n static getInstance(): AnonymousTelemetryManager {\n if (!AnonymousTelemetryManager.#instance) {\n AnonymousTelemetryManager.#instance = new AnonymousTelemetryManager();\n }\n return AnonymousTelemetryManager.#instance;\n }\n\n start(lc?: LogContext, config?: ZeroConfig) {\n this.#lc = lc;\n\n // Set up OpenTelemetry diagnostic logger if not already configured\n setupOtelDiagnosticLogger(lc);\n\n if (!config) {\n try {\n config = getZeroConfig();\n } catch (e) {\n this.#lc?.info?.('telemetry: disabled - unable to parse config', e);\n return;\n }\n }\n\n if (process.env.DO_NOT_TRACK) {\n this.#lc?.info?.(\n 'telemetry: disabled - DO_NOT_TRACK environment variable is set',\n );\n return;\n }\n\n if (!config.enableTelemetry) {\n this.#lc?.info?.('telemetry: disabled - enableTelemetry is false');\n return;\n }\n\n if (this.#starting) {\n return;\n }\n\n this.#starting = true;\n this.#config = config;\n this.#viewSyncerCount = config.numSyncWorkers ?? 1;\n this.#cachedAttributes = undefined;\n\n this.#lc?.info?.(`telemetry: starting in 1 minute`);\n\n // Delay telemetry startup by 1 minute to avoid potential boot loop issues\n setTimeout(() => this.#run(), 60000);\n }\n\n #run() {\n if (this.#stopped) {\n return;\n }\n\n const resource = resourceFromAttributes(this.#getAttributes());\n\n // Add a random jitter to the export interval to avoid all view-syncers exporting at the same time\n const exportIntervalMillis =\n 60000 * this.#viewSyncerCount + Math.floor(Math.random() * 10000);\n const metricReader = new PeriodicExportingMetricReader({\n exportIntervalMillis,\n exporter: new OTLPMetricExporter({\n url: 'https://metrics.rocicorp.dev',\n timeoutMillis: 30000,\n }),\n });\n\n this.#meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n this.#meter = this.#meterProvider.getMeter('zero-anonymous-telemetry');\n\n this.#setupMetrics();\n this.#lc?.info?.(\n `telemetry: started (exports every ${exportIntervalMillis / 1000} seconds for ${this.#viewSyncerCount} view-syncers)`,\n );\n }\n\n #setupMetrics() {\n // Observable gauges\n const uptimeGauge = this.#meter.createObservableGauge('zero.uptime', {\n description: 'System uptime in seconds',\n unit: 'seconds',\n });\n\n // Observable counters\n const uptimeCounter = this.#meter.createObservableCounter(\n 'zero.uptime_counter',\n {\n description: 'System uptime in seconds',\n unit: 'seconds',\n },\n );\n const crudMutationsCounter = this.#meter.createObservableCounter(\n 'zero.crud_mutations_processed',\n {\n description: 'Total number of CRUD mutations processed',\n },\n );\n const customMutationsCounter = this.#meter.createObservableCounter(\n 'zero.custom_mutations_processed',\n {\n description: 'Total number of custom mutations processed',\n },\n );\n const totalMutationsCounter = this.#meter.createObservableCounter(\n 'zero.mutations_processed',\n {\n description: 'Total number of mutations processed',\n },\n );\n const crudQueriesCounter = this.#meter.createObservableCounter(\n 'zero.crud_queries_processed',\n {\n description: 'Total number of CRUD queries processed',\n },\n );\n const customQueriesCounter = this.#meter.createObservableCounter(\n 'zero.custom_queries_processed',\n {\n description: 'Total number of custom queries processed',\n },\n );\n const totalQueriesCounter = this.#meter.createObservableCounter(\n 'zero.queries_processed',\n {\n description: 'Total number of queries processed',\n },\n );\n const rowsSyncedCounter = this.#meter.createObservableCounter(\n 'zero.rows_synced',\n {\n description: 'Total number of rows synced',\n },\n );\n\n // Observable counters for connections\n const connectionsSuccessCounter = this.#meter.createObservableCounter(\n 'zero.connections_success',\n {\n description: 'Total number of successful connections',\n },\n );\n\n const connectionsAttemptedCounter = this.#meter.createObservableCounter(\n 'zero.connections_attempted',\n {\n description: 'Total number of attempted connections',\n },\n );\n\n const activeClientGroupsGauge = this.#meter.createObservableGauge(\n 'zero.gauge_active_client_groups',\n {\n description: 'Number of currently active client groups',\n },\n );\n\n const attrs = this.#getAttributes();\n const active =\n (metric: keyof ActiveUsers) => (result: ObservableResult) => {\n const actives = this.#activeUsersGetter?.();\n if (actives) {\n const value = actives[metric];\n result.observe(value, attrs);\n this.#lc?.debug?.(`telemetry: ${metric}=${value}`);\n } else {\n this.#lc?.debug?.(\n `telemetry: no actives available, skipping observation of ${metric}`,\n );\n }\n };\n this.#meter\n .createObservableGauge('zero.active_users_last_day', {\n description: 'Count of CVR instances active in the last 24h',\n })\n .addCallback(active('active_users_last_day'));\n this.#meter\n .createObservableGauge('zero.users_1da', {\n description: 'Count of 1-day active profiles',\n })\n .addCallback(active('users_1da'));\n this.#meter\n .createObservableGauge('zero.users_7da', {\n description: 'Count of 7-day active profiles',\n })\n .addCallback(active('users_7da'));\n this.#meter\n .createObservableGauge('zero.users_30da', {\n description: 'Count of 30-day active profiles',\n })\n .addCallback(active('users_30da'));\n this.#meter\n .createObservableGauge('zero.users_1da_legacy', {\n description: 'Count of 1-day active profiles with CVR fallback',\n })\n .addCallback(active('users_1da_legacy'));\n this.#meter\n .createObservableGauge('zero.users_7da_legacy', {\n description: 'Count of 7-day active profiles with CVR fallback',\n })\n .addCallback(active('users_7da_legacy'));\n this.#meter\n .createObservableGauge('zero.users_30da_legacy', {\n description: 'Count of 30-day active profiles with CVR fallback',\n })\n .addCallback(active('users_30da_legacy'));\n\n // Callbacks\n uptimeGauge.addCallback((result: ObservableResult) => {\n const uptimeSeconds = Math.floor(process.uptime());\n result.observe(uptimeSeconds, attrs);\n this.#lc?.debug?.(`telemetry: uptime=${uptimeSeconds}s`);\n });\n uptimeCounter.addCallback((result: ObservableResult) => {\n const uptimeSeconds = Math.floor(process.uptime());\n result.observe(uptimeSeconds, attrs);\n this.#lc?.debug?.(`telemetry: uptime_counter=${uptimeSeconds}s`);\n });\n crudMutationsCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCrudMutations, attrs);\n this.#lc?.debug?.(\n `telemetry: crud_mutations=${this.#totalCrudMutations}`,\n );\n });\n customMutationsCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCustomMutations, attrs);\n this.#lc?.debug?.(\n `telemetry: custom_mutations=${this.#totalCustomMutations}`,\n );\n });\n totalMutationsCounter.addCallback((result: ObservableResult) => {\n const totalMutations =\n this.#totalCrudMutations + this.#totalCustomMutations;\n result.observe(totalMutations, attrs);\n this.#lc?.debug?.(`telemetry: total_mutations=${totalMutations}`);\n });\n crudQueriesCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCrudQueries, attrs);\n this.#lc?.debug?.(`telemetry: crud_queries=${this.#totalCrudQueries}`);\n });\n customQueriesCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalCustomQueries, attrs);\n this.#lc?.debug?.(\n `telemetry: custom_queries=${this.#totalCustomQueries}`,\n );\n });\n totalQueriesCounter.addCallback((result: ObservableResult) => {\n const totalQueries = this.#totalCrudQueries + this.#totalCustomQueries;\n result.observe(totalQueries, attrs);\n this.#lc?.debug?.(`telemetry: total_queries=${totalQueries}`);\n });\n rowsSyncedCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalRowsSynced, attrs);\n this.#lc?.debug?.(`telemetry: rows_synced=${this.#totalRowsSynced}`);\n });\n connectionsSuccessCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalConnectionsSuccess, attrs);\n this.#lc?.debug?.(\n `telemetry: connections_success=${this.#totalConnectionsSuccess}`,\n );\n });\n connectionsAttemptedCounter.addCallback((result: ObservableResult) => {\n result.observe(this.#totalConnectionsAttempted, attrs);\n this.#lc?.debug?.(\n `telemetry: connections_attempted=${this.#totalConnectionsAttempted}`,\n );\n });\n activeClientGroupsGauge.addCallback((result: ObservableResult) => {\n const activeClientGroups = this.#activeClientGroupsGetter?.() ?? 0;\n result.observe(activeClientGroups, attrs);\n this.#lc?.debug?.(\n `telemetry: gauge_active_client_groups=${activeClientGroups}`,\n );\n });\n }\n\n recordMutation(type: 'crud' | 'custom', count = 1) {\n if (type === 'crud') {\n this.#totalCrudMutations += count;\n } else {\n this.#totalCustomMutations += count;\n }\n }\n\n recordQuery(type: 'crud' | 'custom', count = 1) {\n if (type === 'crud') {\n this.#totalCrudQueries += count;\n } else {\n this.#totalCustomQueries += count;\n }\n }\n\n recordRowsSynced(count: number) {\n this.#totalRowsSynced += count;\n }\n\n recordConnectionSuccess() {\n this.#totalConnectionsSuccess++;\n }\n\n recordConnectionAttempted() {\n this.#totalConnectionsAttempted++;\n }\n\n setActiveClientGroupsGetter(getter: () => number) {\n this.#activeClientGroupsGetter = getter;\n }\n\n setActiveUsersGetter(getter: () => ActiveUsers) {\n this.#activeUsersGetter = getter;\n }\n\n shutdown() {\n this.#stopped = true;\n if (this.#meterProvider) {\n this.#lc?.info?.('telemetry: shutting down');\n void this.#meterProvider.shutdown();\n }\n }\n\n #getAttributes() {\n if (!this.#cachedAttributes) {\n this.#cachedAttributes = {\n 'zero.app.id': h64(this.#config?.upstream.db || 'unknown').toString(),\n 'zero.machine.os': platform(),\n 'zero.telemetry.type': 'anonymous',\n 'zero.infra.platform': this.#getPlatform(),\n 'zero.version': getServerVersion(this.#config),\n 'zero.task.id': this.#config?.taskID || 'unknown',\n 'zero.project.id': this.#getGitProjectId(),\n 'zero.process.id': this.#processId,\n 'zero.fs.id': this.#getOrSetFsID(),\n };\n this.#lc?.debug?.(\n `telemetry: cached attributes=${JSON.stringify(this.#cachedAttributes)}`,\n );\n }\n return this.#cachedAttributes;\n }\n\n #getPlatform(): string {\n if (process.env.ZERO_ON_CLOUD_ZERO) return 'cloudzero';\n if (process.env.FLY_APP_NAME || process.env.FLY_REGION) return 'fly.io';\n if (\n process.env.ECS_CONTAINER_METADATA_URI_V4 ||\n process.env.ECS_CONTAINER_METADATA_URI ||\n process.env.AWS_EXECUTION_ENV\n )\n return 'aws';\n if (process.env.RAILWAY_ENV || process.env.RAILWAY_STATIC_URL)\n return 'railway';\n if (process.env.RENDER || process.env.RENDER_SERVICE_ID) return 'render';\n if (\n process.env.GCP_PROJECT ||\n process.env.GCLOUD_PROJECT ||\n process.env.GOOGLE_CLOUD_PROJECT\n )\n return 'gcp';\n if (process.env.COOLIFY_URL || process.env.COOLIFY_CONTAINER_NAME)\n return 'coolify';\n if (process.env.CONTAINER_APP_REVISION) return 'azure';\n if (process.env.FLIGHTCONTROL || process.env.FC_URL) return 'flightcontrol';\n return 'unknown';\n }\n\n #findUp(startDir: string, target: string): string | null {\n let dir = startDir;\n while (dir !== dirname(dir)) {\n if (existsSync(join(dir, target))) return dir;\n dir = dirname(dir);\n }\n return null;\n }\n\n #getGitProjectId(): string {\n try {\n const cwd = process.cwd();\n const gitRoot = this.#findUp(cwd, '.git');\n if (!gitRoot) {\n return 'unknown';\n }\n\n const rootCommitHash = execSync('git rev-list --max-parents=0 HEAD -1', {\n cwd: gitRoot,\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore'], // Suppress stderr\n }).trim();\n\n return rootCommitHash.length === 40 ? rootCommitHash : 'unknown';\n } catch (error) {\n this.#lc?.debug?.('telemetry: unable to get Git root commit:', error);\n return 'unknown';\n }\n }\n\n #getOrSetFsID(): string {\n try {\n if (this.#isInContainer()) {\n return 'container';\n }\n const fsidPath = join(homedir(), '.rocicorp', 'fsid');\n const fsidDir = dirname(fsidPath);\n\n mkdirSync(fsidDir, {recursive: true});\n\n // Always try atomic file creation first - this eliminates any race conditions\n const newId = randomUUID();\n try {\n writeFileSync(fsidPath, newId, {encoding: 'utf8', flag: 'wx'});\n return newId;\n } catch (writeError) {\n if ((writeError as NodeJS.ErrnoException).code === 'EEXIST') {\n const existingId = readFileSync(fsidPath, 'utf8').trim();\n return existingId;\n }\n throw writeError;\n }\n } catch (error) {\n this.#lc?.debug?.(\n 'telemetry: unable to get or set filesystem ID:',\n error,\n );\n return 'unknown';\n }\n }\n\n #isInContainer(): boolean {\n try {\n if (process.env.ZERO_IN_CONTAINER) {\n return true;\n }\n\n if (existsSync('/.dockerenv')) {\n return true;\n }\n\n if (existsSync('/usr/local/bin/docker-entrypoint.sh')) {\n return true;\n }\n\n if (process.env.KUBERNETES_SERVICE_HOST) {\n return true;\n }\n\n if (\n process.env.DOCKER_CONTAINER_ID ||\n process.env.HOSTNAME?.match(/^[a-f0-9]{12}$/)\n ) {\n return true;\n }\n\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (\n cgroup.includes('docker') ||\n cgroup.includes('kubepods') ||\n cgroup.includes('containerd')\n ) {\n return true;\n }\n }\n\n return false;\n } catch (error) {\n this.#lc?.debug?.(\n 'telemetry: unable to detect container environment:',\n error,\n );\n return false;\n }\n }\n}\n\nconst manager = () => AnonymousTelemetryManager.getInstance();\n\nexport const startAnonymousTelemetry = (lc?: LogContext, config?: ZeroConfig) =>\n manager().start(lc, config);\nexport const recordMutation = (type: 'crud' | 'custom', count = 1) =>\n manager().recordMutation(type, count);\nexport const recordQuery = (type: 'crud' | 'custom', count = 1) =>\n manager().recordQuery(type, count);\nexport const recordRowsSynced = (count: number) =>\n manager().recordRowsSynced(count);\nexport const recordConnectionSuccess = () =>\n manager().recordConnectionSuccess();\nexport const recordConnectionAttempted = () =>\n manager().recordConnectionAttempted();\nexport const setActiveClientGroupsGetter = (getter: () => number) =>\n manager().setActiveClientGroupsGetter(getter);\nexport const setActiveUsersGetter = (getter: () => ActiveUsers) =>\n manager().setActiveUsersGetter(getter);\nexport const shutdownAnonymousTelemetry = () => manager().shutdown();\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"inspector-delegate.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/inspector-delegate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAEnE,OAAO,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AACvD,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,mCAAmC,CAAC;AAE3D,OAAO,KAAK,EAAC,aAAa,IAAI,iBAAiB,EAAC,MAAM,4CAA4C,CAAC;AAEnG,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,eAAe,EACrB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,sCAAsC,CAAC;AACjF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAGtD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,8BAA8B,EAAE,OAAO,CAAC;IACxC,qBAAqB,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,KAAK,aAAa,GAAG,MAAM,CAAC;AAQ5B,qBAAa,iBAAkB,YAAW,eAAe;;gBAQ3C,sBAAsB,EAAE,sBAAsB,GAAG,SAAS;IAItE,SAAS,CAAC,CAAC,SAAS,MAAM,SAAS,EACjC,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,MAAM,EACb,GAAG,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GACpB,IAAI;IAeP,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAKjE,cAAc;;;;IAId,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAOhD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAalC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI;IAWrE;;;OAGG;IACH,eAAe,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO;IAMtD,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAIpD,kBAAkB,CAAC,aAAa,EAAE,aAAa;IAI/C;;;;OAIG;IACG,oBAAoB,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,iBAAiB,EAAE,EAClC,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,GAAG,CAAC;CA2ChB"}
1
+ {"version":3,"file":"inspector-delegate.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/inspector-delegate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAEnE,OAAO,EAAC,OAAO,EAAC,MAAM,gCAAgC,CAAC;AACvD,OAAO,KAAK,EAAC,GAAG,EAAC,MAAM,mCAAmC,CAAC;AAC3D,OAAO,KAAK,EAAC,aAAa,IAAI,iBAAiB,EAAC,MAAM,4CAA4C,CAAC;AAEnG,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,eAAe,EACrB,MAAM,4CAA4C,CAAC;AAEpD,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,sCAAsC,CAAC;AACjF,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAItD;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,8BAA8B,EAAE,OAAO,CAAC;IACxC,qBAAqB,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,KAAK,aAAa,GAAG,MAAM,CAAC;AAQ5B,qBAAa,iBAAkB,YAAW,eAAe;;gBAQ3C,sBAAsB,EAAE,sBAAsB,GAAG,SAAS;IAItE,SAAS,CAAC,CAAC,SAAS,MAAM,SAAS,EACjC,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,MAAM,EACb,GAAG,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GACpB,IAAI;IAeP,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAKjE,cAAc;;;;IAId,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS;IAOhD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAalC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI;IAWrE;;;OAGG;IACH,eAAe,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO;IAMtD,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,IAAI;IAIpD,kBAAkB,CAAC,aAAa,EAAE,aAAa;IAI/C;;;;OAIG;IACG,oBAAoB,CACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,iBAAiB,EAAE,EAClC,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,OAAO,CAAC,GAAG,CAAC;CA2ChB"}
@@ -1,10 +1,10 @@
1
1
  import { assert } from "../../../shared/src/asserts.js";
2
2
  import { mapValues } from "../../../shared/src/objects.js";
3
3
  import { TDigest } from "../../../shared/src/tdigest.js";
4
- import { ProtocolError } from "../../../zero-protocol/src/error.js";
5
4
  import { hashOfNameAndArgs } from "../../../zero-protocol/src/query-hash.js";
6
5
  import { isServerMetric } from "../../../zql/src/query/metrics-delegate.js";
7
6
  import { isDevelopmentMode } from "../config/normalize.js";
7
+ import { ProtocolErrorWithLevel } from "../types/error-with-level.js";
8
8
  const authenticatedClientGroupIDs = /* @__PURE__ */ new Set();
9
9
  class InspectorDelegate {
10
10
  #globalMetrics = newMetrics();
@@ -100,7 +100,7 @@ class InspectorDelegate {
100
100
  userQueryURL
101
101
  );
102
102
  if ("kind" in results) {
103
- throw new ProtocolError(results);
103
+ throw new ProtocolErrorWithLevel(results, "warn");
104
104
  }
105
105
  const result = results[0];
106
106
  if (!result) {