@rocicorp/zero 1.8.0-canary.4 → 1.8.0-canary.6
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.
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero-cache/src/custom/fetch.d.ts +6 -1
- package/out/zero-cache/src/custom/fetch.d.ts.map +1 -1
- package/out/zero-cache/src/custom/fetch.js +255 -119
- package/out/zero-cache/src/custom/fetch.js.map +1 -1
- package/out/zero-cache/src/custom/metrics.d.ts +31 -0
- package/out/zero-cache/src/custom/metrics.d.ts.map +1 -0
- package/out/zero-cache/src/custom/metrics.js +47 -0
- package/out/zero-cache/src/custom/metrics.js.map +1 -0
- package/out/zero-cache/src/custom-queries/transform-query.d.ts.map +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js +1 -1
- package/out/zero-cache/src/custom-queries/transform-query.js.map +1 -1
- package/out/zero-cache/src/db/migration-lite.d.ts +0 -6
- package/out/zero-cache/src/db/migration-lite.d.ts.map +1 -1
- package/out/zero-cache/src/db/migration-lite.js +2 -22
- package/out/zero-cache/src/db/migration-lite.js.map +1 -1
- package/out/zero-cache/src/observability/metrics.d.ts +3 -0
- package/out/zero-cache/src/observability/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/observability/metrics.js +21 -1
- package/out/zero-cache/src/observability/metrics.js.map +1 -1
- package/out/zero-cache/src/server/anonymous-otel-start.js +1 -1
- package/out/zero-cache/src/server/main.d.ts.map +1 -1
- package/out/zero-cache/src/server/main.js +4 -2
- package/out/zero-cache/src/server/main.js.map +1 -1
- package/out/zero-cache/src/server/otel-start.d.ts.map +1 -1
- package/out/zero-cache/src/server/otel-start.js +7 -0
- package/out/zero-cache/src/server/otel-start.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +2 -2
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/change-source.js +4 -0
- package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.d.ts +15 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js +72 -0
- package/out/zero-cache/src/services/change-source/pg/replication-slot-health.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +2 -0
- package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/broadcast.js +11 -4
- package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/forwarder.js +43 -2
- package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +8 -2
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +112 -12
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/life-cycle.d.ts +1 -0
- package/out/zero-cache/src/services/life-cycle.d.ts.map +1 -1
- package/out/zero-cache/src/services/life-cycle.js +41 -1
- package/out/zero-cache/src/services/life-cycle.js.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/commands.js +0 -2
- package/out/zero-cache/src/services/litestream/commands.js.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.d.ts.map +1 -1
- package/out/zero-cache/src/services/litestream/metrics.js +14 -29
- package/out/zero-cache/src/services/litestream/metrics.js.map +1 -1
- package/out/zero-cache/src/services/litestream/vfs-watermark-reader.js +2 -2
- package/out/zero-cache/src/services/mutagen/pusher.d.ts.map +1 -1
- package/out/zero-cache/src/services/mutagen/pusher.js +9 -3
- package/out/zero-cache/src/services/mutagen/pusher.js.map +1 -1
- package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js +10 -6
- package/out/zero-cache/src/services/shadow-sync/shadow-sync-service.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/cvr-store.js +48 -11
- package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js +12 -0
- package/out/zero-cache/src/services/view-syncer/row-record-cache.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +10 -3
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/connection.d.ts.map +1 -1
- package/out/zero-cache/src/workers/connection.js +12 -0
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +72 -22
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-protocol/src/error.js +1 -1
- package/out/zqlite/src/db.js +2 -2
- package/out/zqlite/src/sqlite-cost-model.js +5 -5
- package/out/zqlite/src/table-source.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/custom/metrics.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,eAAe,CAAC;AAC5E,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,YAAY,CAAC;IACxB,YAAY,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,YAAY,GACZ,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,cAAc,CAAC;AAEnB,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,YAAY,GACZ,aAAa,GACb,aAAa,CAAC;AAElB,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,GAAG;IACvD,MAAM,EAAE,gBAAgB,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iBAAiB,CAAC,EAAE,GAAG,MAAM,IAAI,GAAG,SAAS,CAAC;IAC9C,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,GAAG;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iBAAiB,CAAC,EAAE,GAAG,MAAM,IAAI,GAAG,SAAS,CAAC;IAC9C,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,CAAC;AAOF,wBAAgB,WAAW,kFAM1B;AAED,wBAAgB,kBAAkB,2DAMjC;AAED,wBAAgB,WAAW,kFAM1B;AAED,wBAAgB,kBAAkB,2DAMjC;AAED,wBAAgB,WAAW,wFAM1B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getOrCreateCounter, getOrCreateHistogram, getOrCreateUpDownCounter } from "../observability/metrics.js";
|
|
2
|
+
//#region ../zero-cache/src/custom/metrics.ts
|
|
3
|
+
var API_DURATION_HISTOGRAM_BOUNDARIES_S = [
|
|
4
|
+
.001,
|
|
5
|
+
.002,
|
|
6
|
+
.005,
|
|
7
|
+
.01,
|
|
8
|
+
.02,
|
|
9
|
+
.05,
|
|
10
|
+
.1,
|
|
11
|
+
.2,
|
|
12
|
+
.5,
|
|
13
|
+
1,
|
|
14
|
+
2,
|
|
15
|
+
5,
|
|
16
|
+
10,
|
|
17
|
+
30,
|
|
18
|
+
60,
|
|
19
|
+
120
|
|
20
|
+
];
|
|
21
|
+
function apiRequests() {
|
|
22
|
+
return getOrCreateCounter("server", "api.requests", "API requests, labeled by operation and result.");
|
|
23
|
+
}
|
|
24
|
+
function apiRequestDuration() {
|
|
25
|
+
return getOrCreateHistogram("server", "api.request_duration", {
|
|
26
|
+
description: "End-to-end API request duration, including retries.",
|
|
27
|
+
unit: "s",
|
|
28
|
+
bucketBoundaries: API_DURATION_HISTOGRAM_BOUNDARIES_S
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function apiAttempts() {
|
|
32
|
+
return getOrCreateCounter("server", "api.attempts", "API HTTP fetch attempts");
|
|
33
|
+
}
|
|
34
|
+
function apiAttemptDuration() {
|
|
35
|
+
return getOrCreateHistogram("server", "api.attempt_duration", {
|
|
36
|
+
description: "API HTTP fetch attempt duration, excluding retry sleep.",
|
|
37
|
+
unit: "s",
|
|
38
|
+
bucketBoundaries: API_DURATION_HISTOGRAM_BOUNDARIES_S
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function apiInFlight() {
|
|
42
|
+
return getOrCreateUpDownCounter("server", "api.in_flight", "API requests currently in flight.");
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { apiAttemptDuration, apiAttempts, apiInFlight, apiRequestDuration, apiRequests };
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","names":[],"sources":["../../../../../zero-cache/src/custom/metrics.ts"],"sourcesContent":["import {\n getOrCreateCounter,\n getOrCreateHistogram,\n getOrCreateUpDownCounter,\n} from '../observability/metrics.ts';\n\nexport type ApiOperation = 'mutate' | 'query' | 'cleanup' | 'validate_auth';\nexport type ApiCleanupType = 'single' | 'bulk';\n\nexport type ApiMetricBaseAttrs = {\n operation: ApiOperation;\n cleanup_type?: ApiCleanupType | undefined;\n};\n\nexport type ApiRequestResult =\n | 'success'\n | 'api_error'\n | 'http_error'\n | 'parse_error'\n | 'fetch_error'\n | 'url_not_allowed'\n | 'config_error';\n\nexport type ApiAttemptResult =\n | 'success'\n | 'api_error'\n | 'http_error'\n | 'parse_error'\n | 'fetch_error';\n\nexport type ApiRequestMetricAttrs = ApiMetricBaseAttrs & {\n result: ApiRequestResult;\n attempt_count: number;\n http_status_code?: number | undefined;\n http_status_class?: `${string}xx` | undefined;\n error_kind?: string | undefined;\n error_reason?: string | undefined;\n};\n\nexport type ApiAttemptMetricAttrs = ApiMetricBaseAttrs & {\n attempt: number;\n result: ApiAttemptResult;\n will_retry: boolean;\n http_status_code?: number | undefined;\n http_status_class?: `${string}xx` | undefined;\n error_kind?: string | undefined;\n error_reason?: string | undefined;\n};\n\nconst API_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60,\n 120,\n];\n\nexport function apiRequests() {\n return getOrCreateCounter(\n 'server',\n 'api.requests',\n 'API requests, labeled by operation and result.',\n );\n}\n\nexport function apiRequestDuration() {\n return getOrCreateHistogram('server', 'api.request_duration', {\n description: 'End-to-end API request duration, including retries.',\n unit: 's',\n bucketBoundaries: API_DURATION_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nexport function apiAttempts() {\n return getOrCreateCounter(\n 'server',\n 'api.attempts',\n 'API HTTP fetch attempts',\n );\n}\n\nexport function apiAttemptDuration() {\n return getOrCreateHistogram('server', 'api.attempt_duration', {\n description: 'API HTTP fetch attempt duration, excluding retry sleep.',\n unit: 's',\n bucketBoundaries: API_DURATION_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nexport function apiInFlight() {\n return getOrCreateUpDownCounter(\n 'server',\n 'api.in_flight',\n 'API requests currently in flight.',\n );\n}\n"],"mappings":";;AAiDA,IAAM,sCAAsC;CAC1C;CAAO;CAAO;CAAO;CAAM;CAAM;CAAM;CAAK;CAAK;CAAK;CAAG;CAAG;CAAG;CAAI;CAAI;CACvE;AACF;AAEA,SAAgB,cAAc;CAC5B,OAAO,mBACL,UACA,gBACA,gDACF;AACF;AAEA,SAAgB,qBAAqB;CACnC,OAAO,qBAAqB,UAAU,wBAAwB;EAC5D,aAAa;EACb,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH;AAEA,SAAgB,cAAc;CAC5B,OAAO,mBACL,UACA,gBACA,yBACF;AACF;AAEA,SAAgB,qBAAqB;CACnC,OAAO,qBAAqB,UAAU,wBAAwB;EAC5D,aAAa;EACb,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH;AAEA,SAAgB,cAAc;CAC5B,OAAO,yBACL,UACA,iBACA,mCACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-query.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAKjD,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,8CAA8C,CAAC;AAItD,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,4BAA4B,CAAC;AAErE,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAIhD,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,oBAAoB,CAAC;CAClC,GACD,mBAAmB,CAAC;AAExB,MAAM,MAAM,uBAAuB,GAC/B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,mBAAmB,CAAC;CAC7B,GACD,CAAC;IACC,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,CAAC,oBAAoB,GAAG,YAAY,CAAC,EAAE,CAAC;CACjD,GAAG,CACA;IACE,MAAM,EAAE,IAAI,CAAC;CACd,GACD;IACE,MAAM,EAAE,KAAK,CAAC;IACd,UAAU,EAAE,oBAAoB,CAAC;CAClC,CACJ,CAAC,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,sBAAsB;;gBAKrB,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO;IAM1C;;;;;;;;OAQG;IACG,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAM5D,SAAS,CACb,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GACnC,OAAO,CAAC,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"transform-query.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAKjD,OAAO,EACL,KAAK,YAAY,EAGlB,MAAM,8CAA8C,CAAC;AAItD,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,4CAA4C,CAAC;AACpD,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,4BAA4B,CAAC;AAErE,OAAO,KAAK,EACV,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,uDAAuD,CAAC;AAC/D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAIhD,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,UAAU,EAAE,oBAAoB,CAAC;CAClC,GACD,mBAAmB,CAAC;AAExB,MAAM,MAAM,uBAAuB,GAC/B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,mBAAmB,CAAC;CAC7B,GACD,CAAC;IACC,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,CAAC,oBAAoB,GAAG,YAAY,CAAC,EAAE,CAAC;CACjD,GAAG,CACA;IACE,MAAM,EAAE,IAAI,CAAC;CACd,GACD;IACE,MAAM,EAAE,KAAK,CAAC;IACd,UAAU,EAAE,oBAAoB,CAAC;CAClC,CACJ,CAAC,CAAC;AAEP;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,sBAAsB;;gBAKrB,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO;IAM1C;;;;;;;;OAQG;IACG,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAM5D,SAAS,CACb,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GACnC,OAAO,CAAC,uBAAuB,CAAC;CAyIpC"}
|
|
@@ -103,7 +103,7 @@ var CustomQueryTransformer = class {
|
|
|
103
103
|
async #requestTransform(ctx, request, operation) {
|
|
104
104
|
const queryIDs = request.map(({ id }) => id);
|
|
105
105
|
try {
|
|
106
|
-
const transformResponse = await startAsyncSpan(tracer, "customQueryTransformer.fetchFromAPIServer", () => fetchFromAPIServer(queryResponseSchema, "transform", this.#lc, ctx, this.#shard, ["transform", request]));
|
|
106
|
+
const transformResponse = await startAsyncSpan(tracer, "customQueryTransformer.fetchFromAPIServer", () => fetchFromAPIServer(queryResponseSchema, "transform", this.#lc, ctx, this.#shard, ["transform", request], { operation: operation === "validate" ? "validate_auth" : "query" }));
|
|
107
107
|
if ("kind" in transformResponse) return transformResponse.kind === "QueryResponse" ? {
|
|
108
108
|
kind: "QueryResponse",
|
|
109
109
|
queries: transformResponse.queries,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-query.js","names":["#shard","#cache","#lc","#requestTransform"],"sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {startAsyncSpan} from '../../../otel/src/span.ts';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {sortedEntries} from '../../../shared/src/sorted-entries.ts';\nimport {\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 {\n queryResponseSchema,\n type QueryResponseBody,\n} from '../../../zero-protocol/src/query-server.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {fetchFromAPIServer} from '../custom/fetch.ts';\nimport type {\n ConnectionContext,\n ConnectionValidation,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport type {CustomQueryRecord} from '../services/view-syncer/schema/types.ts';\nimport type {ShardID} from '../types/shards.ts';\n\nconst tracer = trace.getTracer('custom-query-transformer');\n\nexport type TransformResponse =\n | {\n kind: 'QueryResponse';\n queries: QueryResponseBody;\n validation: ConnectionValidation;\n }\n | TransformFailedBody;\n\nexport type HashedTransformResponse =\n | {\n kind: 'failed';\n result: TransformFailedBody;\n }\n | ({\n kind: 'success';\n result: (TransformedAndHashed | ErroredQuery)[];\n } & (\n | {\n cached: true;\n }\n | {\n cached: false;\n validation: ConnectionValidation;\n }\n ));\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 the effective request\n * identity used for `/query`: auth, userID, forwarded cookies, origin,\n * custom headers, target URL, and the query hash itself.\n */\nexport class CustomQueryTransformer {\n readonly #shard: ShardID;\n readonly #cache: TimedCache<TransformedAndHashed>;\n readonly #lc: LogContext;\n\n constructor(lc: LogContext, shard: ShardID) {\n this.#shard = shard;\n this.#lc = lc;\n this.#cache = new TimedCache(5000); // 5 seconds cache TTL\n }\n\n /**\n * Forces the empty `/query` validation request used by auth maintenance.\n *\n * This stays separate from `transform()` because `transform([], ...)`\n * short-circuits locally and never hits the API server, while validation\n * still needs to make the request so auth failures are surfaced.\n * Successful validation is intentionally opaque because callers only care\n * whether the request succeeded or failed.\n */\n async validate(ctx: ConnectionContext): Promise<TransformResponse> {\n const response = await this.#requestTransform(ctx, [], 'validate');\n\n return response;\n }\n\n async transform(\n ctx: ConnectionContext,\n queries: Iterable<CustomQueryRecord>,\n ): Promise<HashedTransformResponse> {\n const request: TransformRequestBody = [];\n const cachedResponses: TransformedAndHashed[] = [];\n\n // split queries into cached and uncached\n for (const query of queries) {\n const cacheKey = getCacheKey(ctx, 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 let cached = true;\n\n if (request.length === 0) {\n return {\n kind: 'success',\n result: cachedResponses,\n cached: true,\n };\n } else {\n // we are hitting the server with at least one uncached query\n cached = false;\n }\n\n const response = await this.#requestTransform(ctx, request, 'transform');\n if (response.kind === 'TransformFailed') {\n return {\n kind: 'failed',\n result: response,\n };\n }\n\n const newResponses = response.queries.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(ctx, transformed.id);\n this.#cache.set(cacheKey, transformed);\n }\n\n return {\n kind: 'success',\n result: [...newResponses, ...cachedResponses],\n validation: response.validation,\n cached,\n };\n }\n\n async #requestTransform(\n ctx: ConnectionContext,\n request: TransformRequestBody,\n operation: 'validate' | 'transform',\n ): Promise<TransformResponse> {\n const queryIDs = request.map(({id}) => id);\n\n try {\n const transformResponse = await startAsyncSpan(\n tracer,\n 'customQueryTransformer.fetchFromAPIServer',\n () =>\n fetchFromAPIServer(\n queryResponseSchema,\n 'transform',\n this.#lc,\n ctx,\n this.#shard,\n ['transform', request] satisfies TransformRequestMessage,\n ),\n );\n\n if ('kind' in transformResponse) {\n return transformResponse.kind === 'QueryResponse'\n ? {\n kind: 'QueryResponse',\n queries: transformResponse.queries,\n validation:\n transformResponse.userID !== undefined\n ? {\n kind: 'server-validated',\n validatedUserID: transformResponse.userID,\n }\n : {kind: 'client-fallback'},\n }\n : transformResponse;\n }\n\n if (transformResponse[0] === 'transformed') {\n // Legacy query response, so we use client fallback\n return {\n kind: 'QueryResponse',\n validation: {kind: 'client-fallback'},\n queries: transformResponse[1],\n };\n }\n\n return transformResponse[1];\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 ${operation} queries: ${getErrorMessage(e)}`,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n }\n}\n\nfunction getCacheKey(ctx: ConnectionContext, queryID: string) {\n // For custom queries, queryID is a hash of the name + args.\n // The apiKey is static for a given transformer instance.\n return JSON.stringify({\n queryID,\n token: ctx.auth?.raw,\n cookie: ctx.queryContext.headerOptions.cookie,\n origin: ctx.queryContext.headerOptions.origin,\n userID: ctx.user,\n url: ctx.queryContext.url,\n customHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.customHeaders,\n ),\n requestHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.requestHeaders,\n ),\n });\n}\n\nfunction normalizedHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n) {\n if (!headers) {\n return undefined;\n }\n\n const forwardedHeaders = sortedEntries(headers);\n\n return forwardedHeaders.length === 0\n ? undefined\n : JSON.stringify(forwardedHeaders);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transform-query.js","names":["#shard","#cache","#lc","#requestTransform"],"sources":["../../../../../zero-cache/src/custom-queries/transform-query.ts"],"sourcesContent":["import {trace} from '@opentelemetry/api';\nimport type {LogContext} from '@rocicorp/logger';\nimport {startAsyncSpan} from '../../../otel/src/span.ts';\nimport {TimedCache} from '../../../shared/src/cache.ts';\nimport {getErrorMessage} from '../../../shared/src/error.ts';\nimport {sortedEntries} from '../../../shared/src/sorted-entries.ts';\nimport {\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 {\n queryResponseSchema,\n type QueryResponseBody,\n} from '../../../zero-protocol/src/query-server.ts';\nimport type {TransformedAndHashed} from '../auth/read-authorizer.ts';\nimport {fetchFromAPIServer} from '../custom/fetch.ts';\nimport type {\n ConnectionContext,\n ConnectionValidation,\n} from '../services/view-syncer/connection-context-manager.ts';\nimport type {CustomQueryRecord} from '../services/view-syncer/schema/types.ts';\nimport type {ShardID} from '../types/shards.ts';\n\nconst tracer = trace.getTracer('custom-query-transformer');\n\nexport type TransformResponse =\n | {\n kind: 'QueryResponse';\n queries: QueryResponseBody;\n validation: ConnectionValidation;\n }\n | TransformFailedBody;\n\nexport type HashedTransformResponse =\n | {\n kind: 'failed';\n result: TransformFailedBody;\n }\n | ({\n kind: 'success';\n result: (TransformedAndHashed | ErroredQuery)[];\n } & (\n | {\n cached: true;\n }\n | {\n cached: false;\n validation: ConnectionValidation;\n }\n ));\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 the effective request\n * identity used for `/query`: auth, userID, forwarded cookies, origin,\n * custom headers, target URL, and the query hash itself.\n */\nexport class CustomQueryTransformer {\n readonly #shard: ShardID;\n readonly #cache: TimedCache<TransformedAndHashed>;\n readonly #lc: LogContext;\n\n constructor(lc: LogContext, shard: ShardID) {\n this.#shard = shard;\n this.#lc = lc;\n this.#cache = new TimedCache(5000); // 5 seconds cache TTL\n }\n\n /**\n * Forces the empty `/query` validation request used by auth maintenance.\n *\n * This stays separate from `transform()` because `transform([], ...)`\n * short-circuits locally and never hits the API server, while validation\n * still needs to make the request so auth failures are surfaced.\n * Successful validation is intentionally opaque because callers only care\n * whether the request succeeded or failed.\n */\n async validate(ctx: ConnectionContext): Promise<TransformResponse> {\n const response = await this.#requestTransform(ctx, [], 'validate');\n\n return response;\n }\n\n async transform(\n ctx: ConnectionContext,\n queries: Iterable<CustomQueryRecord>,\n ): Promise<HashedTransformResponse> {\n const request: TransformRequestBody = [];\n const cachedResponses: TransformedAndHashed[] = [];\n\n // split queries into cached and uncached\n for (const query of queries) {\n const cacheKey = getCacheKey(ctx, 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 let cached = true;\n\n if (request.length === 0) {\n return {\n kind: 'success',\n result: cachedResponses,\n cached: true,\n };\n } else {\n // we are hitting the server with at least one uncached query\n cached = false;\n }\n\n const response = await this.#requestTransform(ctx, request, 'transform');\n if (response.kind === 'TransformFailed') {\n return {\n kind: 'failed',\n result: response,\n };\n }\n\n const newResponses = response.queries.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(ctx, transformed.id);\n this.#cache.set(cacheKey, transformed);\n }\n\n return {\n kind: 'success',\n result: [...newResponses, ...cachedResponses],\n validation: response.validation,\n cached,\n };\n }\n\n async #requestTransform(\n ctx: ConnectionContext,\n request: TransformRequestBody,\n operation: 'validate' | 'transform',\n ): Promise<TransformResponse> {\n const queryIDs = request.map(({id}) => id);\n\n try {\n const transformResponse = await startAsyncSpan(\n tracer,\n 'customQueryTransformer.fetchFromAPIServer',\n () =>\n fetchFromAPIServer(\n queryResponseSchema,\n 'transform',\n this.#lc,\n ctx,\n this.#shard,\n ['transform', request] satisfies TransformRequestMessage,\n {operation: operation === 'validate' ? 'validate_auth' : 'query'},\n ),\n );\n\n if ('kind' in transformResponse) {\n return transformResponse.kind === 'QueryResponse'\n ? {\n kind: 'QueryResponse',\n queries: transformResponse.queries,\n validation:\n transformResponse.userID !== undefined\n ? {\n kind: 'server-validated',\n validatedUserID: transformResponse.userID,\n }\n : {kind: 'client-fallback'},\n }\n : transformResponse;\n }\n\n if (transformResponse[0] === 'transformed') {\n // Legacy query response, so we use client fallback\n return {\n kind: 'QueryResponse',\n validation: {kind: 'client-fallback'},\n queries: transformResponse[1],\n };\n }\n\n return transformResponse[1];\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 ${operation} queries: ${getErrorMessage(e)}`,\n queryIDs,\n } as const satisfies TransformFailedBody;\n }\n }\n}\n\nfunction getCacheKey(ctx: ConnectionContext, queryID: string) {\n // For custom queries, queryID is a hash of the name + args.\n // The apiKey is static for a given transformer instance.\n return JSON.stringify({\n queryID,\n token: ctx.auth?.raw,\n cookie: ctx.queryContext.headerOptions.cookie,\n origin: ctx.queryContext.headerOptions.origin,\n userID: ctx.user,\n url: ctx.queryContext.url,\n customHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.customHeaders,\n ),\n requestHeaders: normalizedHeaders(\n ctx.queryContext.headerOptions.requestHeaders,\n ),\n });\n}\n\nfunction normalizedHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n) {\n if (!headers) {\n return undefined;\n }\n\n const forwardedHeaders = sortedEntries(headers);\n\n return forwardedHeaders.length === 0\n ? undefined\n : JSON.stringify(forwardedHeaders);\n}\n"],"mappings":"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"}
|
|
@@ -48,12 +48,6 @@ export type IncrementalMigrationMap = {
|
|
|
48
48
|
* migrating the schema if necessary.
|
|
49
49
|
*/
|
|
50
50
|
export declare function runSchemaMigrations(log: LogContext, debugName: string, dbPath: string, setupMigration: Migration, incrementalMigrationMap: IncrementalMigrationMap): Promise<void>;
|
|
51
|
-
export declare class DatabaseIntegrityError extends Error {
|
|
52
|
-
readonly name = "DatabaseIntegrityError";
|
|
53
|
-
readonly issues: readonly string[];
|
|
54
|
-
constructor(debugName: string, issues: readonly string[]);
|
|
55
|
-
}
|
|
56
|
-
export declare function assertDatabaseIntegrity(log: LogContext, debugName: string, db: Db): void;
|
|
57
51
|
export declare const versionHistory: v.ObjectType<{
|
|
58
52
|
/**
|
|
59
53
|
* The `schemaVersion` is highest code version that has ever been run
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration-lite.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/db/migration-lite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,KAAK,EAAC,QAAQ,IAAI,EAAE,EAAC,MAAM,2BAA2B,CAAC;AAG9D,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,UAAU,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,SAAS,EACzB,uBAAuB,EAAE,uBAAuB,GAC/C,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"migration-lite.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/db/migration-lite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAGjD,OAAO,KAAK,CAAC,MAAM,+BAA+B,CAAC;AACnD,OAAO,KAAK,EAAC,QAAQ,IAAI,EAAE,EAAC,MAAM,2BAA2B,CAAC;AAG9D,KAAK,UAAU,GAAG,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEpE;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,UAAU,CAAC;IAE3B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,UAAU,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,SAAS,EACzB,uBAAuB,EAAE,uBAAuB,GAC/C,OAAO,CAAC,IAAI,CAAC,CAmHf;AAaD,eAAO,MAAM,cAAc;IACzB;;;;;;OAMG;;IAGH;;;;;;OAMG;;IAGH;;;;OAIG;;aAEH,CAAC;AAGH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAG5D,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,GAAG,cAAc,CAmBxD"}
|
|
@@ -26,7 +26,6 @@ async function runSchemaMigrations(log, debugName, dbPath, setupMigration, incre
|
|
|
26
26
|
}
|
|
27
27
|
return versions;
|
|
28
28
|
});
|
|
29
|
-
const initialDataVersion = versions.dataVersion;
|
|
30
29
|
if (versions.dataVersion < codeVersion) {
|
|
31
30
|
db.unsafeMode(true);
|
|
32
31
|
db.pragma("locking_mode = EXCLUSIVE");
|
|
@@ -48,11 +47,7 @@ async function runSchemaMigrations(log, debugName, dbPath, setupMigration, incre
|
|
|
48
47
|
}
|
|
49
48
|
db.exec("ANALYZE main");
|
|
50
49
|
log.info?.("ANALYZE completed");
|
|
51
|
-
|
|
52
|
-
} else {
|
|
53
|
-
db.pragma("optimize = 0x10002");
|
|
54
|
-
assertDatabaseIntegrity(log, debugName, db);
|
|
55
|
-
}
|
|
50
|
+
} else db.pragma("optimize = 0x10002");
|
|
56
51
|
db.pragma("synchronous = NORMAL");
|
|
57
52
|
db.unsafeMode(false);
|
|
58
53
|
assert(versions.dataVersion === codeVersion, () => `Final dataVersion (${versions.dataVersion}) does not match codeVersion (${codeVersion})`);
|
|
@@ -65,21 +60,6 @@ async function runSchemaMigrations(log, debugName, dbPath, setupMigration, incre
|
|
|
65
60
|
log.flush();
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
|
-
var DatabaseIntegrityError = class extends Error {
|
|
69
|
-
name = "DatabaseIntegrityError";
|
|
70
|
-
issues;
|
|
71
|
-
constructor(debugName, issues) {
|
|
72
|
-
super(`SQLite quick_check failed for ${debugName}: ${issues.join("; ")}`);
|
|
73
|
-
this.issues = issues;
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
function assertDatabaseIntegrity(log, debugName, db) {
|
|
77
|
-
const start = Date.now();
|
|
78
|
-
const rows = db.pragma("quick_check");
|
|
79
|
-
const issues = rows.length === 0 ? ["PRAGMA quick_check returned no rows"] : rows.map((row) => row.quick_check).filter((issue) => issue !== "ok");
|
|
80
|
-
if (issues.length > 0) throw new DatabaseIntegrityError(debugName, issues);
|
|
81
|
-
log.info?.(`quick_check completed for ${debugName} (${Date.now() - start} ms)`);
|
|
82
|
-
}
|
|
83
63
|
function sorted(incrementalMigrationMap) {
|
|
84
64
|
const versionMigrations = [];
|
|
85
65
|
for (const [v, m] of Object.entries(incrementalMigrationMap)) versionMigrations.push([Number(v), m]);
|
|
@@ -181,6 +161,6 @@ async function runTransaction(log, db, tx) {
|
|
|
181
161
|
}
|
|
182
162
|
}
|
|
183
163
|
//#endregion
|
|
184
|
-
export {
|
|
164
|
+
export { runSchemaMigrations };
|
|
185
165
|
|
|
186
166
|
//# sourceMappingURL=migration-lite.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration-lite.js","names":[],"sources":["../../../../../zero-cache/src/db/migration-lite.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Database as Db} from '../../../zqlite/src/db.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\n\ntype Operations = (log: LogContext, tx: Db) => Promise<void> | void;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n dbPath: string,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n const start = Date.now();\n log = log.withContext(\n 'initSchema',\n randInt(0, Number.MAX_SAFE_INTEGER).toString(36),\n );\n const db = new Database(log, dbPath);\n\n try {\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify a at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n let versions = await runTransaction(log, db, tx => {\n const versions = getVersionHistory(tx);\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n return updateVersionHistory(log, tx, versions, codeVersion);\n }\n return versions;\n });\n const initialDataVersion = versions.dataVersion;\n\n if (versions.dataVersion < codeVersion) {\n db.unsafeMode(true); // Enables journal_mode = OFF\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('foreign_keys = OFF');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n // Unfortunately, AUTO_VACUUM is not compatible with BEGIN CONCURRENT,\n // so it is not an option for the replica file.\n // https://sqlite.org/forum/forumpost/25f183416a\n // db.pragma('auto_vacuum = INCREMENTAL');\n\n const migrations =\n versions.dataVersion === 0\n ? // For the empty database v0, only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush(); // Flush logs before each migration to help debug crash-y migrations.\n\n versions = await runTransaction(log, db, async tx => {\n // Fetch meta from within the transaction to make the migration atomic.\n let versions = getVersionHistory(tx);\n if (versions.dataVersion < dest) {\n versions = await runMigration(log, tx, versions, dest, migration);\n assert(\n versions.dataVersion === dest,\n () =>\n `Migration did not reach target version: expected ${dest}, got ${versions.dataVersion}`,\n );\n }\n return versions;\n });\n }\n }\n\n db.exec('ANALYZE main');\n log.info?.('ANALYZE completed');\n\n if (initialDataVersion > 0) {\n assertDatabaseIntegrity(log, debugName, db);\n }\n } else {\n // Run optimize whenever opening an sqlite db file as recommended in\n // https://www.sqlite.org/pragma.html#pragma_optimize\n // It is important to run the same initialization steps as is done\n // in the view-syncer (i.e. when preparing database for serving\n // replication) so that any corruption detected in the view-syncer is\n // similarly detected in the change-streamer, facilitating an eventual\n // recovery by resyncing the replica anew.\n db.pragma('optimize = 0x10002');\n assertDatabaseIntegrity(log, debugName, db);\n }\n\n db.pragma('synchronous = NORMAL');\n db.unsafeMode(false);\n\n assert(\n versions.dataVersion === codeVersion,\n () =>\n `Final dataVersion (${versions.dataVersion}) does not match codeVersion (${codeVersion})`,\n );\n log.info?.(\n `Running ${debugName} at schema v${codeVersion} (${\n Date.now() - start\n } ms)`,\n );\n } catch (e) {\n log.error?.('Error in ensureSchemaMigrated', e);\n throw e;\n } finally {\n db.close();\n void log.flush(); // Flush the logs but do not block server progress on it.\n }\n}\n\nexport class DatabaseIntegrityError extends Error {\n readonly name = 'DatabaseIntegrityError';\n readonly issues: readonly string[];\n\n constructor(debugName: string, issues: readonly string[]) {\n super(`SQLite quick_check failed for ${debugName}: ${issues.join('; ')}`);\n this.issues = issues;\n }\n}\n\nexport function assertDatabaseIntegrity(\n log: LogContext,\n debugName: string,\n db: Db,\n) {\n const start = Date.now();\n const rows = db.pragma<{quick_check: string}>('quick_check');\n const issues =\n rows.length === 0\n ? ['PRAGMA quick_check returned no rows']\n : rows.map(row => row.quick_check).filter(issue => issue !== 'ok');\n\n if (issues.length > 0) {\n throw new DatabaseIntegrityError(debugName, issues);\n }\n\n log.info?.(\n `quick_check completed for ${debugName} (${Date.now() - start} ms)`,\n );\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests\nexport function getVersionHistory(db: Db): VersionHistory {\n // Note: The `lock` column transparently ensures that at most one row exists.\n db.prepare(\n `\n CREATE TABLE IF NOT EXISTS \"_zero.versionHistory\" (\n dataVersion INTEGER NOT NULL,\n schemaVersion INTEGER NOT NULL,\n minSafeVersion INTEGER NOT NULL,\n\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n `,\n ).run();\n const result = db\n .prepare(\n 'SELECT dataVersion, schemaVersion, minSafeVersion FROM \"_zero.versionHistory\"',\n )\n .get() as VersionHistory;\n return result ?? {dataVersion: 0, schemaVersion: 0, minSafeVersion: 0};\n}\n\nfunction updateVersionHistory(\n log: LogContext,\n db: Db,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): VersionHistory {\n assert(newVersion > 0, 'newVersion must be positive');\n const meta = {\n ...prev,\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n db.prepare(\n `\n INSERT INTO \"_zero.versionHistory\" (dataVersion, schemaVersion, minSafeVersion, lock)\n VALUES (@dataVersion, @schemaVersion, @minSafeVersion, 1)\n ON CONFLICT (lock) DO UPDATE\n SET dataVersion=@dataVersion,\n schemaVersion=@schemaVersion,\n minSafeVersion=@minSafeVersion\n `,\n ).run(meta);\n\n return meta;\n}\n\nasync function runMigration(\n log: LogContext,\n tx: Db,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n\n// Note: We use a custom transaction wrapper (instead of db.begin(...)) in order\n// to support async operations within the transaction.\nasync function runTransaction<T>(\n log: LogContext,\n db: Db,\n tx: (db: Db) => Promise<T> | T,\n): Promise<T> {\n db.prepare('BEGIN EXCLUSIVE').run();\n try {\n const result = await tx(db);\n db.prepare('COMMIT').run();\n return result;\n } catch (e) {\n log.error?.('Aborted transaction due to error', e);\n try {\n db.prepare('ROLLBACK').run();\n } catch (rollbackError) {\n log.error?.('Unable to rollback transaction', rollbackError);\n const combinedError = new Error(\n `Transaction failed and rollback also failed: operation error = ${String(\n e,\n )}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = e;\n throw combinedError;\n }\n throw e;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"migration-lite.js","names":[],"sources":["../../../../../zero-cache/src/db/migration-lite.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {assert} from '../../../shared/src/asserts.ts';\nimport {randInt} from '../../../shared/src/rand.ts';\nimport * as v from '../../../shared/src/valita.ts';\nimport type {Database as Db} from '../../../zqlite/src/db.ts';\nimport {Database} from '../../../zqlite/src/db.ts';\n\ntype Operations = (log: LogContext, tx: Db) => Promise<void> | void;\n\n/**\n * Encapsulates the logic for setting up or upgrading to a new schema. After the\n * Migration code successfully completes, {@link runSchemaMigrations}\n * will update the schema version and commit the transaction.\n */\nexport type Migration = {\n /**\n * Perform database operations that create or alter table structure. This is\n * called at most once during lifetime of the application. If a `migrateData()`\n * operation is defined, that will be performed after `migrateSchema()` succeeds.\n */\n migrateSchema?: Operations;\n\n /**\n * Perform database operations to migrate data to the new schema. This is\n * called after `migrateSchema()` (if defined), and may be called again\n * to re-migrate data after the server was rolled back to an earlier version,\n * and rolled forward again.\n *\n * Consequently, the logic in `migrateData()` must be idempotent.\n */\n migrateData?: Operations;\n\n /**\n * Sets the `minSafeVersion` to the specified value, prohibiting running\n * any earlier code versions.\n */\n minSafeVersion?: number;\n};\n\n/**\n * Mapping of incremental migrations to move from the previous old code\n * version to next one. Versions must be non-zero.\n *\n * The schema resulting from performing incremental migrations should be\n * equivalent to that of the `setupMigration` on a blank database.\n *\n * The highest destinationVersion of this map denotes the current\n * \"code version\", and is also used as the destination version when\n * running the initial setup migration on a blank database.\n */\nexport type IncrementalMigrationMap = {\n [destinationVersion: number]: Migration;\n};\n\n/**\n * Ensures that the schema is compatible with the current code, updating and\n * migrating the schema if necessary.\n */\nexport async function runSchemaMigrations(\n log: LogContext,\n debugName: string,\n dbPath: string,\n setupMigration: Migration,\n incrementalMigrationMap: IncrementalMigrationMap,\n): Promise<void> {\n const start = Date.now();\n log = log.withContext(\n 'initSchema',\n randInt(0, Number.MAX_SAFE_INTEGER).toString(36),\n );\n const db = new Database(log, dbPath);\n\n try {\n const versionMigrations = sorted(incrementalMigrationMap);\n assert(\n versionMigrations.length,\n `Must specify a at least one version migration`,\n );\n assert(\n versionMigrations[0][0] > 0,\n `Versions must be non-zero positive numbers`,\n );\n // oxlint-disable-next-line typescript/no-non-null-assertion\n const codeVersion = versionMigrations.at(-1)![0];\n log.info?.(\n `Checking schema for compatibility with ${debugName} at schema v${codeVersion}`,\n );\n\n let versions = await runTransaction(log, db, tx => {\n const versions = getVersionHistory(tx);\n if (codeVersion < versions.minSafeVersion) {\n throw new Error(\n `Cannot run ${debugName} at schema v${codeVersion} because rollback limit is v${versions.minSafeVersion}`,\n );\n }\n\n if (versions.dataVersion > codeVersion) {\n log.info?.(\n `Data is at v${versions.dataVersion}. Resetting to v${codeVersion}`,\n );\n return updateVersionHistory(log, tx, versions, codeVersion);\n }\n return versions;\n });\n if (versions.dataVersion < codeVersion) {\n db.unsafeMode(true); // Enables journal_mode = OFF\n db.pragma('locking_mode = EXCLUSIVE');\n db.pragma('foreign_keys = OFF');\n db.pragma('journal_mode = OFF');\n db.pragma('synchronous = OFF');\n // Unfortunately, AUTO_VACUUM is not compatible with BEGIN CONCURRENT,\n // so it is not an option for the replica file.\n // https://sqlite.org/forum/forumpost/25f183416a\n // db.pragma('auto_vacuum = INCREMENTAL');\n\n const migrations =\n versions.dataVersion === 0\n ? // For the empty database v0, only run the setup migration.\n ([[codeVersion, setupMigration]] as const)\n : versionMigrations;\n\n for (const [dest, migration] of migrations) {\n if (versions.dataVersion < dest) {\n log.info?.(\n `Migrating schema from v${versions.dataVersion} to v${dest}`,\n );\n void log.flush(); // Flush logs before each migration to help debug crash-y migrations.\n\n versions = await runTransaction(log, db, async tx => {\n // Fetch meta from within the transaction to make the migration atomic.\n let versions = getVersionHistory(tx);\n if (versions.dataVersion < dest) {\n versions = await runMigration(log, tx, versions, dest, migration);\n assert(\n versions.dataVersion === dest,\n () =>\n `Migration did not reach target version: expected ${dest}, got ${versions.dataVersion}`,\n );\n }\n return versions;\n });\n }\n }\n\n db.exec('ANALYZE main');\n log.info?.('ANALYZE completed');\n } else {\n // Run optimize whenever opening an sqlite db file as recommended in\n // https://www.sqlite.org/pragma.html#pragma_optimize\n // It is important to run the same initialization steps as is done\n // in the view-syncer (i.e. when preparing database for serving\n // replication) so that any corruption detected in the view-syncer is\n // similarly detected in the change-streamer, facilitating an eventual\n // recovery by resyncing the replica anew.\n db.pragma('optimize = 0x10002');\n // Do not run `quick_check` here. It scans the full database and can\n // dominate startup time for large replicas or slower disks.\n }\n\n db.pragma('synchronous = NORMAL');\n db.unsafeMode(false);\n\n assert(\n versions.dataVersion === codeVersion,\n () =>\n `Final dataVersion (${versions.dataVersion}) does not match codeVersion (${codeVersion})`,\n );\n log.info?.(\n `Running ${debugName} at schema v${codeVersion} (${\n Date.now() - start\n } ms)`,\n );\n } catch (e) {\n log.error?.('Error in ensureSchemaMigrated', e);\n throw e;\n } finally {\n db.close();\n void log.flush(); // Flush the logs but do not block server progress on it.\n }\n}\n\nfunction sorted(\n incrementalMigrationMap: IncrementalMigrationMap,\n): [number, Migration][] {\n const versionMigrations: [number, Migration][] = [];\n for (const [v, m] of Object.entries(incrementalMigrationMap)) {\n versionMigrations.push([Number(v), m]);\n }\n return versionMigrations.sort(([a], [b]) => a - b);\n}\n\n// Exposed for tests.\nexport const versionHistory = v.object({\n /**\n * The `schemaVersion` is highest code version that has ever been run\n * on the database, and is used to delineate the structure of the tables\n * in the database. A schemaVersion only moves forward; rolling back to\n * an earlier (safe) code version does not revert schema changes that\n * have already been applied.\n */\n schemaVersion: v.number(),\n\n /**\n * The data version is the code version of the latest server that ran.\n * Note that this may be less than the schemaVersion in the case that\n * a server is rolled back to an earlier version after a schema change.\n * In such a case, data (but not schema), may need to be re-migrated\n * when rolling forward again.\n */\n dataVersion: v.number(),\n\n /**\n * The minimum code version that is safe to run. This is used when\n * a schema migration is not backwards compatible with an older version\n * of the code.\n */\n minSafeVersion: v.number(),\n});\n\n// Exposed for tests.\nexport type VersionHistory = v.Infer<typeof versionHistory>;\n\n// Exposed for tests\nexport function getVersionHistory(db: Db): VersionHistory {\n // Note: The `lock` column transparently ensures that at most one row exists.\n db.prepare(\n `\n CREATE TABLE IF NOT EXISTS \"_zero.versionHistory\" (\n dataVersion INTEGER NOT NULL,\n schemaVersion INTEGER NOT NULL,\n minSafeVersion INTEGER NOT NULL,\n\n lock INTEGER PRIMARY KEY DEFAULT 1 CHECK (lock=1)\n );\n `,\n ).run();\n const result = db\n .prepare(\n 'SELECT dataVersion, schemaVersion, minSafeVersion FROM \"_zero.versionHistory\"',\n )\n .get() as VersionHistory;\n return result ?? {dataVersion: 0, schemaVersion: 0, minSafeVersion: 0};\n}\n\nfunction updateVersionHistory(\n log: LogContext,\n db: Db,\n prev: VersionHistory,\n newVersion: number,\n minSafeVersion?: number,\n): VersionHistory {\n assert(newVersion > 0, 'newVersion must be positive');\n const meta = {\n ...prev,\n dataVersion: newVersion,\n // The schemaVersion never moves backwards.\n schemaVersion: Math.max(newVersion, prev.schemaVersion),\n minSafeVersion: getMinSafeVersion(log, prev, minSafeVersion),\n } satisfies VersionHistory;\n\n db.prepare(\n `\n INSERT INTO \"_zero.versionHistory\" (dataVersion, schemaVersion, minSafeVersion, lock)\n VALUES (@dataVersion, @schemaVersion, @minSafeVersion, 1)\n ON CONFLICT (lock) DO UPDATE\n SET dataVersion=@dataVersion,\n schemaVersion=@schemaVersion,\n minSafeVersion=@minSafeVersion\n `,\n ).run(meta);\n\n return meta;\n}\n\nasync function runMigration(\n log: LogContext,\n tx: Db,\n versions: VersionHistory,\n destinationVersion: number,\n migration: Migration,\n): Promise<VersionHistory> {\n if (versions.schemaVersion < destinationVersion) {\n await migration.migrateSchema?.(log, tx);\n }\n if (versions.dataVersion < destinationVersion) {\n await migration.migrateData?.(log, tx);\n }\n return updateVersionHistory(\n log,\n tx,\n versions,\n destinationVersion,\n migration.minSafeVersion,\n );\n}\n\n/**\n * Bumps the rollback limit [[toAtLeast]] the specified version.\n * Leaves the rollback limit unchanged if it is equal or greater.\n */\nfunction getMinSafeVersion(\n log: LogContext,\n current: VersionHistory,\n proposedSafeVersion?: number,\n): number {\n if (proposedSafeVersion === undefined) {\n return current.minSafeVersion;\n }\n if (current.minSafeVersion >= proposedSafeVersion) {\n // The rollback limit must never move backwards.\n log.debug?.(\n `rollback limit is already at ${current.minSafeVersion}, ` +\n `don't need to bump to ${proposedSafeVersion}`,\n );\n return current.minSafeVersion;\n }\n log.info?.(\n `bumping rollback limit from ${current.minSafeVersion} to ${proposedSafeVersion}`,\n );\n return proposedSafeVersion;\n}\n\n// Note: We use a custom transaction wrapper (instead of db.begin(...)) in order\n// to support async operations within the transaction.\nasync function runTransaction<T>(\n log: LogContext,\n db: Db,\n tx: (db: Db) => Promise<T> | T,\n): Promise<T> {\n db.prepare('BEGIN EXCLUSIVE').run();\n try {\n const result = await tx(db);\n db.prepare('COMMIT').run();\n return result;\n } catch (e) {\n log.error?.('Aborted transaction due to error', e);\n try {\n db.prepare('ROLLBACK').run();\n } catch (rollbackError) {\n log.error?.('Unable to rollback transaction', rollbackError);\n const combinedError = new Error(\n `Transaction failed and rollback also failed: operation error = ${String(\n e,\n )}; rollback error = ${String(rollbackError)}`,\n );\n combinedError.cause = e;\n throw combinedError;\n }\n throw e;\n }\n}\n"],"mappings":"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"}
|
|
@@ -6,6 +6,8 @@ type Options = MetricOptions & {
|
|
|
6
6
|
type HistogramOptions = Options & {
|
|
7
7
|
bucketBoundaries: number[];
|
|
8
8
|
};
|
|
9
|
+
export declare const NATIVE_HISTOGRAM_INSTRUMENT_NAMES: readonly ["zero.sync.view_syncer_lag", "zero.sync.view_syncer_hydration"];
|
|
10
|
+
export declare const LONG_DURATION_HISTOGRAM_BOUNDARIES_S: number[];
|
|
9
11
|
export declare function getOrCreateUpDownCounter(category: Category, name: string, description: string): UpDownCounter;
|
|
10
12
|
export declare function getOrCreateUpDownCounter(category: Category, name: string, opts: Options): UpDownCounter;
|
|
11
13
|
/**
|
|
@@ -26,6 +28,7 @@ export type LatencyHistogram = {
|
|
|
26
28
|
recordMs(durationMs: number, attributes?: Parameters<Histogram['record']>[1]): void;
|
|
27
29
|
};
|
|
28
30
|
export declare function getOrCreateHistogram(category: Category, name: string, opts: HistogramOptions): LatencyHistogram;
|
|
31
|
+
export declare function getOrCreateNativeHistogram(category: Category, name: string, opts: Options): LatencyHistogram;
|
|
29
32
|
/**
|
|
30
33
|
* Creates (or retrieves) a latency histogram for the given metric.
|
|
31
34
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/observability/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,CAAC;AAIb,KAAK,OAAO,GAAG,aAAa,GAAG;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,OAAO,GAAG;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/observability/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EAET,aAAa,EACb,eAAe,EACf,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,SAAS,GACT,MAAM,GACN,UAAU,GACV,QAAQ,CAAC;AAIb,KAAK,OAAO,GAAG,aAAa,GAAG;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,OAAO,GAAG;IAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAC,CAAC;AAE/D,eAAO,MAAM,iCAAiC,2EAGpC,CAAC;AAEX,eAAO,MAAM,oCAAoC,UAEhD,CAAC;AA4BF,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,aAAa,CAAC;AACjB,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,aAAa,CAAC;AAcjB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;;;;OAMG;IACH,QAAQ,CACN,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAC9C,IAAI,CAAC;CACT,CAAC;AAoBF,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,gBAAgB,GACrB,gBAAgB,CAelB;AAKD,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,gBAAgB,CAQlB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,gBAAgB,CAMlB;AAID,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;AACX,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC;AAgBX,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,GAClB,eAAe,CAAC;AACnB,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GACZ,eAAe,CAAC"}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { metrics } from "@opentelemetry/api";
|
|
2
2
|
//#region ../zero-cache/src/observability/metrics.ts
|
|
3
3
|
var meter;
|
|
4
|
+
var NATIVE_HISTOGRAM_INSTRUMENT_NAMES = ["zero.sync.view_syncer_lag", "zero.sync.view_syncer_hydration"];
|
|
5
|
+
var LONG_DURATION_HISTOGRAM_BOUNDARIES_S = [
|
|
6
|
+
1,
|
|
7
|
+
2,
|
|
8
|
+
5,
|
|
9
|
+
10,
|
|
10
|
+
30,
|
|
11
|
+
60,
|
|
12
|
+
120,
|
|
13
|
+
300,
|
|
14
|
+
600,
|
|
15
|
+
1200,
|
|
16
|
+
2400,
|
|
17
|
+
3600,
|
|
18
|
+
7200
|
|
19
|
+
];
|
|
4
20
|
function getMeter() {
|
|
5
21
|
if (!meter) meter = metrics.getMeter("zero");
|
|
6
22
|
return meter;
|
|
@@ -59,6 +75,10 @@ function getOrCreateHistogram(category, name, opts) {
|
|
|
59
75
|
}));
|
|
60
76
|
return { recordMs: (durationMs, attributes) => h.record(durationMs / 1e3, attributes) };
|
|
61
77
|
}
|
|
78
|
+
function getOrCreateNativeHistogram(category, name, opts) {
|
|
79
|
+
const h = histograms(name, (name) => getMeter().createHistogram(`zero.${category}.${name}`, opts));
|
|
80
|
+
return { recordMs: (durationMs, attributes) => h.record(durationMs / 1e3, attributes) };
|
|
81
|
+
}
|
|
62
82
|
/**
|
|
63
83
|
* Creates (or retrieves) a latency histogram for the given metric.
|
|
64
84
|
*
|
|
@@ -93,6 +113,6 @@ function getOrCreateGauge(category, name, opts) {
|
|
|
93
113
|
return gauges(name, (name) => getMeter().createObservableGauge(`zero.${category}.${name}`, typeof opts === "string" ? { description: opts } : opts));
|
|
94
114
|
}
|
|
95
115
|
//#endregion
|
|
96
|
-
export { getOrCreateCounter, getOrCreateGauge, getOrCreateHistogram, getOrCreateLatencyHistogram, getOrCreateUpDownCounter };
|
|
116
|
+
export { LONG_DURATION_HISTOGRAM_BOUNDARIES_S, NATIVE_HISTOGRAM_INSTRUMENT_NAMES, getOrCreateCounter, getOrCreateGauge, getOrCreateHistogram, getOrCreateLatencyHistogram, getOrCreateNativeHistogram, getOrCreateUpDownCounter };
|
|
97
117
|
|
|
98
118
|
//# sourceMappingURL=metrics.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","names":[],"sources":["../../../../../zero-cache/src/observability/metrics.ts"],"sourcesContent":["import type {\n Counter,\n Histogram,\n Meter,\n MetricOptions,\n ObservableGauge,\n UpDownCounter,\n} from '@opentelemetry/api';\nimport {metrics} from '@opentelemetry/api';\n\n// intentional lazy initialization so it is not started before the SDK is started.\n\nexport type Category =\n | 'replication' // postgres to replica\n | 'replica' // health of replica and litestream backup\n | 'sync' // replica to client\n | 'mutation'\n | 'server';\n\nlet meter: Meter | undefined;\n\ntype Options = MetricOptions & {description: string};\ntype HistogramOptions = Options & {bucketBoundaries: number[]};\n\nfunction getMeter() {\n if (!meter) {\n meter = metrics.getMeter('zero');\n }\n return meter;\n}\n\nfunction cache<TRet>(): (\n name: string,\n creator: (name: string) => TRet,\n) => TRet {\n const instruments = new Map<string, TRet>();\n return (name: string, creator: (name: string) => TRet) => {\n const existing = instruments.get(name);\n if (existing) {\n return existing;\n }\n\n const ret = creator(name);\n instruments.set(name, ret);\n return ret;\n };\n}\n\nconst upDownCounters = cache<UpDownCounter>();\n\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n description: string,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: Options,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): UpDownCounter {\n return upDownCounters(name, name =>\n getMeter().createUpDownCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\n/**\n * A latency histogram whose {@link recordMs} method accepts raw millisecond\n * durations and converts them to seconds internally.\n *\n * Use {@link getOrCreateLatencyHistogram} to create one — the unit (`'s'`),\n * bucket boundaries, and ms→s conversion are all baked in\n */\nexport type LatencyHistogram = {\n /**\n * Record a duration. Pass the raw elapsed milliseconds — the conversion to\n * seconds (required by the `unit: 's'` OTel histogram) is handled internally.\n *\n * @param durationMs Elapsed time in **milliseconds** (do NOT pre-divide).\n * @param attributes Optional OTel attributes to attach to the observation.\n */\n recordMs(\n durationMs: number,\n attributes?: Parameters<Histogram['record']>[1],\n ): void;\n};\n\n/**\n * Bucket boundaries (in seconds) for zero's latency histograms.\n *\n * The operational range is 1 ms – 5,000 ms (including customers actively\n * tuning queries). ~2× logarithmic steps give proportionally consistent\n * `histogram_quantile` accuracy regardless of where values cluster within\n * that range. 10,000 ms and 30,000 ms are overflow catchers for truly broken\n * states.\n *\n * 1 ms, 2 ms, 5 ms, 10 ms, 20 ms, 50 ms, 100 ms, 200 ms, 500 ms,\n * 1 s, 2 s, 5 s, 10 s, 30 s\n */\nconst LATENCY_HISTOGRAM_BOUNDARIES_S = [\n 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30,\n];\n\nconst histograms = cache<Histogram>();\n\nexport function getOrCreateHistogram(\n category: Category,\n name: string,\n opts: HistogramOptions,\n): LatencyHistogram {\n const {bucketBoundaries, ...metricOptions} = opts;\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, {\n ...metricOptions,\n advice: {\n ...metricOptions.advice,\n explicitBucketBoundaries: bucketBoundaries,\n },\n }),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n/**\n * Creates (or retrieves) a latency histogram for the given metric.\n *\n * - `unit` is always `'s'` (seconds), matching the OTel convention.\n * - Bucket boundaries are pre-set for zero's typical operation range\n * (1 ms – 5 s); see {@link LATENCY_HISTOGRAM_BOUNDARIES_S}.\n * - The returned {@link LatencyHistogram} accepts **milliseconds** via\n * `recordMs()`, so callers never need to divide by 1000.\n *\n * @example\n * ```ts\n * readonly #hydrationTime = getOrCreateLatencyHistogram(\n * 'sync', 'hydration-time', 'Time to hydrate a query.',\n * );\n * // ...\n * this.#hydrationTime.recordMs(performance.now() - start);\n * ```\n */\nexport function getOrCreateLatencyHistogram(\n category: Category,\n name: string,\n description: string,\n): LatencyHistogram {\n return getOrCreateHistogram(category, name, {\n description,\n unit: 's',\n bucketBoundaries: LATENCY_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nconst counters = cache<Counter>();\n\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n description: string,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: Options,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): Counter {\n return counters(name, name =>\n getMeter().createCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\nconst gauges = cache<ObservableGauge>();\n\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n description: string,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: Options,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: string | Options,\n): ObservableGauge {\n return gauges(name, name =>\n getMeter().createObservableGauge(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n"],"mappings":";;AAmBA,IAAI;AAKJ,SAAS,WAAW;CAClB,IAAI,CAAC,OACH,QAAQ,QAAQ,SAAS,MAAM;CAEjC,OAAO;AACT;AAEA,SAAS,QAGC;CACR,MAAM,8BAAc,IAAI,IAAkB;CAC1C,QAAQ,MAAc,YAAoC;EACxD,MAAM,WAAW,YAAY,IAAI,IAAI;EACrC,IAAI,UACF,OAAO;EAGT,MAAM,MAAM,QAAQ,IAAI;EACxB,YAAY,IAAI,MAAM,GAAG;EACzB,OAAO;CACT;AACF;AAEA,IAAM,iBAAiB,MAAqB;AAY5C,SAAgB,yBACd,UACA,MACA,MACe;CACf,OAAO,eAAe,OAAM,SAC1B,SAAS,EAAE,oBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;;;;;;;;;;;;;AAmCA,IAAM,iCAAiC;CACrC;CAAO;CAAO;CAAO;CAAM;CAAM;CAAM;CAAK;CAAK;CAAK;CAAG;CAAG;CAAG;CAAI;AACrE;AAEA,IAAM,aAAa,MAAiB;AAEpC,SAAgB,qBACd,UACA,MACA,MACkB;CAClB,MAAM,EAAC,kBAAkB,GAAG,kBAAiB;CAC7C,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ;EACrD,GAAG;EACH,QAAQ;GACN,GAAG,cAAc;GACjB,0BAA0B;EAC5B;CACF,CAAC,CACH;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,4BACd,UACA,MACA,aACkB;CAClB,OAAO,qBAAqB,UAAU,MAAM;EAC1C;EACA,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH;AAEA,IAAM,WAAW,MAAe;AAYhC,SAAgB,mBACd,UACA,MACA,MACS;CACT,OAAO,SAAS,OAAM,SACpB,SAAS,EAAE,cACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;AAEA,IAAM,SAAS,MAAuB;AAYtC,SAAgB,iBACd,UACA,MACA,MACiB;CACjB,OAAO,OAAO,OAAM,SAClB,SAAS,EAAE,sBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"metrics.js","names":[],"sources":["../../../../../zero-cache/src/observability/metrics.ts"],"sourcesContent":["import type {\n Counter,\n Histogram,\n Meter,\n MetricOptions,\n ObservableGauge,\n UpDownCounter,\n} from '@opentelemetry/api';\nimport {metrics} from '@opentelemetry/api';\n\n// intentional lazy initialization so it is not started before the SDK is started.\n\nexport type Category =\n | 'replication' // postgres to replica\n | 'replica' // health of replica and litestream backup\n | 'sync' // replica to client\n | 'mutation'\n | 'server';\n\nlet meter: Meter | undefined;\n\ntype Options = MetricOptions & {description: string};\ntype HistogramOptions = Options & {bucketBoundaries: number[]};\n\nexport const NATIVE_HISTOGRAM_INSTRUMENT_NAMES = [\n 'zero.sync.view_syncer_lag',\n 'zero.sync.view_syncer_hydration',\n] as const;\n\nexport const LONG_DURATION_HISTOGRAM_BOUNDARIES_S = [\n 1, 2, 5, 10, 30, 60, 120, 300, 600, 1200, 2400, 3600, 7200,\n];\n\nfunction getMeter() {\n if (!meter) {\n meter = metrics.getMeter('zero');\n }\n return meter;\n}\n\nfunction cache<TRet>(): (\n name: string,\n creator: (name: string) => TRet,\n) => TRet {\n const instruments = new Map<string, TRet>();\n return (name: string, creator: (name: string) => TRet) => {\n const existing = instruments.get(name);\n if (existing) {\n return existing;\n }\n\n const ret = creator(name);\n instruments.set(name, ret);\n return ret;\n };\n}\n\nconst upDownCounters = cache<UpDownCounter>();\n\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n description: string,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: Options,\n): UpDownCounter;\nexport function getOrCreateUpDownCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): UpDownCounter {\n return upDownCounters(name, name =>\n getMeter().createUpDownCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\n/**\n * A latency histogram whose {@link recordMs} method accepts raw millisecond\n * durations and converts them to seconds internally.\n *\n * Use {@link getOrCreateLatencyHistogram} to create one — the unit (`'s'`),\n * bucket boundaries, and ms→s conversion are all baked in\n */\nexport type LatencyHistogram = {\n /**\n * Record a duration. Pass the raw elapsed milliseconds — the conversion to\n * seconds (required by the `unit: 's'` OTel histogram) is handled internally.\n *\n * @param durationMs Elapsed time in **milliseconds** (do NOT pre-divide).\n * @param attributes Optional OTel attributes to attach to the observation.\n */\n recordMs(\n durationMs: number,\n attributes?: Parameters<Histogram['record']>[1],\n ): void;\n};\n\n/**\n * Bucket boundaries (in seconds) for zero's latency histograms.\n *\n * The operational range is 1 ms – 5,000 ms (including customers actively\n * tuning queries). ~2× logarithmic steps give proportionally consistent\n * `histogram_quantile` accuracy regardless of where values cluster within\n * that range. 10,000 ms and 30,000 ms are overflow catchers for truly broken\n * states.\n *\n * 1 ms, 2 ms, 5 ms, 10 ms, 20 ms, 50 ms, 100 ms, 200 ms, 500 ms,\n * 1 s, 2 s, 5 s, 10 s, 30 s\n */\nconst LATENCY_HISTOGRAM_BOUNDARIES_S = [\n 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30,\n];\n\nconst histograms = cache<Histogram>();\n\nexport function getOrCreateHistogram(\n category: Category,\n name: string,\n opts: HistogramOptions,\n): LatencyHistogram {\n const {bucketBoundaries, ...metricOptions} = opts;\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, {\n ...metricOptions,\n advice: {\n ...metricOptions.advice,\n explicitBucketBoundaries: bucketBoundaries,\n },\n }),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n// Add instruments created through this helper to\n// NATIVE_HISTOGRAM_INSTRUMENT_NAMES so the SDK exports them as exponential\n// histograms.\nexport function getOrCreateNativeHistogram(\n category: Category,\n name: string,\n opts: Options,\n): LatencyHistogram {\n const h = histograms(name, name =>\n getMeter().createHistogram(`zero.${category}.${name}`, opts),\n );\n return {\n recordMs: (durationMs, attributes) =>\n h.record(durationMs / 1000, attributes),\n };\n}\n\n/**\n * Creates (or retrieves) a latency histogram for the given metric.\n *\n * - `unit` is always `'s'` (seconds), matching the OTel convention.\n * - Bucket boundaries are pre-set for zero's typical operation range\n * (1 ms – 5 s); see {@link LATENCY_HISTOGRAM_BOUNDARIES_S}.\n * - The returned {@link LatencyHistogram} accepts **milliseconds** via\n * `recordMs()`, so callers never need to divide by 1000.\n *\n * @example\n * ```ts\n * readonly #hydrationTime = getOrCreateLatencyHistogram(\n * 'sync', 'hydration-time', 'Time to hydrate a query.',\n * );\n * // ...\n * this.#hydrationTime.recordMs(performance.now() - start);\n * ```\n */\nexport function getOrCreateLatencyHistogram(\n category: Category,\n name: string,\n description: string,\n): LatencyHistogram {\n return getOrCreateHistogram(category, name, {\n description,\n unit: 's',\n bucketBoundaries: LATENCY_HISTOGRAM_BOUNDARIES_S,\n });\n}\n\nconst counters = cache<Counter>();\n\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n description: string,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: Options,\n): Counter;\nexport function getOrCreateCounter(\n category: Category,\n name: string,\n opts: string | Options,\n): Counter {\n return counters(name, name =>\n getMeter().createCounter(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n\nconst gauges = cache<ObservableGauge>();\n\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n description: string,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: Options,\n): ObservableGauge;\nexport function getOrCreateGauge(\n category: Category,\n name: string,\n opts: string | Options,\n): ObservableGauge {\n return gauges(name, name =>\n getMeter().createObservableGauge(\n `zero.${category}.${name}`,\n typeof opts === 'string' ? {description: opts} : opts,\n ),\n );\n}\n"],"mappings":";;AAmBA,IAAI;AAKJ,IAAa,oCAAoC,CAC/C,6BACA,iCACF;AAEA,IAAa,uCAAuC;CAClD;CAAG;CAAG;CAAG;CAAI;CAAI;CAAI;CAAK;CAAK;CAAK;CAAM;CAAM;CAAM;AACxD;AAEA,SAAS,WAAW;CAClB,IAAI,CAAC,OACH,QAAQ,QAAQ,SAAS,MAAM;CAEjC,OAAO;AACT;AAEA,SAAS,QAGC;CACR,MAAM,8BAAc,IAAI,IAAkB;CAC1C,QAAQ,MAAc,YAAoC;EACxD,MAAM,WAAW,YAAY,IAAI,IAAI;EACrC,IAAI,UACF,OAAO;EAGT,MAAM,MAAM,QAAQ,IAAI;EACxB,YAAY,IAAI,MAAM,GAAG;EACzB,OAAO;CACT;AACF;AAEA,IAAM,iBAAiB,MAAqB;AAY5C,SAAgB,yBACd,UACA,MACA,MACe;CACf,OAAO,eAAe,OAAM,SAC1B,SAAS,EAAE,oBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;;;;;;;;;;;;;AAmCA,IAAM,iCAAiC;CACrC;CAAO;CAAO;CAAO;CAAM;CAAM;CAAM;CAAK;CAAK;CAAK;CAAG;CAAG;CAAG;CAAI;AACrE;AAEA,IAAM,aAAa,MAAiB;AAEpC,SAAgB,qBACd,UACA,MACA,MACkB;CAClB,MAAM,EAAC,kBAAkB,GAAG,kBAAiB;CAC7C,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ;EACrD,GAAG;EACH,QAAQ;GACN,GAAG,cAAc;GACjB,0BAA0B;EAC5B;CACF,CAAC,CACH;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;AAKA,SAAgB,2BACd,UACA,MACA,MACkB;CAClB,MAAM,IAAI,WAAW,OAAM,SACzB,SAAS,EAAE,gBAAgB,QAAQ,SAAS,GAAG,QAAQ,IAAI,CAC7D;CACA,OAAO,EACL,WAAW,YAAY,eACrB,EAAE,OAAO,aAAa,KAAM,UAAU,EAC1C;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,4BACd,UACA,MACA,aACkB;CAClB,OAAO,qBAAqB,UAAU,MAAM;EAC1C;EACA,MAAM;EACN,kBAAkB;CACpB,CAAC;AACH;AAEA,IAAM,WAAW,MAAe;AAYhC,SAAgB,mBACd,UACA,MACA,MACS;CACT,OAAO,SAAS,OAAM,SACpB,SAAS,EAAE,cACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF;AAEA,IAAM,SAAS,MAAuB;AAYtC,SAAgB,iBACd,UACA,MACA,MACiB;CACjB,OAAO,OAAO,OAAM,SAClB,SAAS,EAAE,sBACT,QAAQ,SAAS,GAAG,QACpB,OAAO,SAAS,WAAW,EAAC,aAAa,KAAI,IAAI,IACnD,CACF;AACF"}
|
|
@@ -5,11 +5,11 @@ import { homedir, platform } from "os";
|
|
|
5
5
|
import "@opentelemetry/api";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
7
7
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
8
|
+
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
8
9
|
import { execSync } from "child_process";
|
|
9
10
|
import { randomUUID } from "crypto";
|
|
10
11
|
import { dirname, join } from "path";
|
|
11
12
|
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
12
|
-
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
13
13
|
//#region ../zero-cache/src/server/anonymous-otel-start.ts
|
|
14
14
|
var hostNameRe = /^[a-f0-9]{12}$/;
|
|
15
15
|
var AnonymousTelemetryManager = class AnonymousTelemetryManager {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/main.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/main.ts"],"names":[],"mappings":"AAiBA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,uBAAuB,CAAC;AAwB/B,wBAA8B,SAAS,CACrC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAAC,IAAI,CAAC,CA+Kf"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { must } from "../../../shared/src/must.js";
|
|
2
2
|
import { childWorker, parentWorker, singleProcessMode } from "../types/processes.js";
|
|
3
3
|
import { getNormalizedZeroConfig } from "../config/zero-config.js";
|
|
4
|
-
import { ProcessManager, exitAfter, runUntilKilled } from "../services/life-cycle.js";
|
|
4
|
+
import { ProcessManager, exitAfter, recordStartupDurationMs, runUntilKilled } from "../services/life-cycle.js";
|
|
5
5
|
import { createLogContext } from "./logging.js";
|
|
6
6
|
import { CHANGE_STREAMER_URL, REAPER_URL, REPLICATOR_URL, SHADOW_SYNCER_URL, SYNCER_URL } from "./worker-urls.js";
|
|
7
7
|
import { initEventSink } from "../observability/events.js";
|
|
@@ -83,7 +83,9 @@ async function runWorker(parent, env) {
|
|
|
83
83
|
const logWaiting = setInterval(() => lc.info?.(`still waiting for ${processes.initializing().join(", ")}`), 1e4);
|
|
84
84
|
await processes.allWorkersReady();
|
|
85
85
|
clearInterval(logWaiting);
|
|
86
|
-
|
|
86
|
+
const startupDurationMs = Date.now() - startMs;
|
|
87
|
+
lc.info?.(`all workers ready (${startupDurationMs} ms)`);
|
|
88
|
+
recordStartupDurationMs(startupDurationMs);
|
|
87
89
|
parent.send(["ready", { ready: true }]);
|
|
88
90
|
try {
|
|
89
91
|
await runUntilKilled(lc, parent, new WorkerDispatcher(lc, taskID, parent, syncers, mutator, changeStreamer));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","names":[],"sources":["../../../../../zero-cache/src/server/main.ts"],"sourcesContent":["import path from 'node:path';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {\n exitAfter,\n ProcessManager,\n runUntilKilled,\n type WorkerType,\n} from '../services/life-cycle.ts';\nimport {\n restoreReplica,\n startReplicaBackupProcess,\n} from '../services/litestream/commands.ts';\nimport {\n childWorker,\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {\n createNotifierFrom,\n handleSubscriptionsFrom,\n type ReplicaFileMode,\n subscribeTo,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {WorkerDispatcher} from './worker-dispatcher.ts';\nimport {\n CHANGE_STREAMER_URL,\n MUTATOR_URL,\n REAPER_URL,\n REPLICATOR_URL,\n SHADOW_SYNCER_URL,\n SYNCER_URL,\n} from './worker-urls.ts';\n\nconst clientConnectionBifurcated = false;\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n): Promise<void> {\n const startMs = Date.now();\n const config = getNormalizedZeroConfig({env});\n\n startOtelAuto(\n createLogContext(config, 'dispatcher', 0, false),\n 'dispatcher',\n 0,\n );\n lc = createLogContext(config, 'dispatcher');\n initEventSink(lc, config);\n\n const processes = new ProcessManager(lc, parent);\n\n const {numSyncWorkers: numSyncers} = config;\n if (config.enableCrudMutations && config.upstream.maxConns < numSyncers) {\n throw new Error(\n `Insufficient upstream connections (${config.upstream.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_UPSTREAM_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n if (config.cvr.maxConns < numSyncers) {\n throw new Error(\n `Insufficient cvr connections (${config.cvr.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_CVR_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n\n const internalFlags: string[] =\n numSyncers === 0\n ? []\n : [\n '--upstream-max-conns-per-worker',\n String(Math.floor(config.upstream.maxConns / numSyncers)),\n '--cvr-max-conns-per-worker',\n String(Math.floor(config.cvr.maxConns / numSyncers)),\n ];\n\n function loadWorker(\n moduleUrl: URL,\n type: WorkerType,\n id?: string | number,\n ...args: string[]\n ): Worker {\n const worker = childWorker(moduleUrl, env, ...args, ...internalFlags);\n const name = path.basename(moduleUrl.pathname) + (id ? ` (${id})` : '');\n return processes.addWorker(worker, type, name);\n }\n\n const {\n taskID,\n changeStreamer: {mode: changeStreamerMode, uri: changeStreamerURI},\n litestream,\n } = config;\n const runChangeStreamer =\n changeStreamerMode === 'dedicated' && changeStreamerURI === undefined;\n\n let changeStreamer: Worker | undefined;\n\n if (!runChangeStreamer) {\n changeStreamer = undefined;\n if (litestream.executable) {\n // For view-syncers, the backup is restored here. For the replication-manager,\n // the backup is restored in the change-streamer worker.\n await restoreReplica(lc, config, null);\n }\n } else {\n const {promise: changeStreamerReady, resolve: changeStreamerStarted} =\n resolver();\n changeStreamer = loadWorker(CHANGE_STREAMER_URL, 'supporting').once(\n 'message',\n changeStreamerStarted,\n );\n\n // Wait for the change-streamer to be ready to guarantee that a replica\n // file is present.\n await changeStreamerReady;\n\n if (litestream.backupURL) {\n // Start a backup replicator and corresponding litestream backup process.\n const {promise: backupReady, resolve} = resolver();\n const mode: ReplicaFileMode = 'backup';\n loadWorker(REPLICATOR_URL, 'supporting', mode, mode).once(\n // Wait for the Replicator's first message (i.e. \"ready\") before starting\n // litestream backup in order to avoid contending on the lock when the\n // replicator first prepares the db file.\n 'message',\n () => {\n processes.addSubprocess(\n startReplicaBackupProcess(lc, config),\n 'supporting',\n 'litestream',\n );\n resolve();\n },\n );\n await backupReady;\n }\n }\n\n if (numSyncers > 0) {\n const {promise: reaperReady, resolve: reaperStarted} = resolver();\n loadWorker(REAPER_URL, 'supporting').once('message', reaperStarted);\n // Before starting the view-syncers, ensure that the reaper has started\n // up, indicating that any CVR db migrations have been performed.\n await reaperReady;\n }\n\n // Only run the shadow-sync canary on the replication-manager (or in\n // single-node mode, where it also owns upstream). Running on every\n // view-syncer would hammer the upstream with N redundant canaries.\n if (config.shadowSync.enabled && runChangeStreamer) {\n const {promise: shadowReady, resolve: shadowStarted} = resolver();\n loadWorker(SHADOW_SYNCER_URL, 'supporting').once('message', shadowStarted);\n await shadowReady;\n }\n\n const syncers: Worker[] = [];\n if (numSyncers) {\n const mode: ReplicaFileMode =\n runChangeStreamer && litestream.backupURL ? 'serving-copy' : 'serving';\n const {promise: replicaReady, resolve} = resolver();\n const replicator = loadWorker(\n REPLICATOR_URL,\n 'supporting',\n mode,\n mode,\n ).once('message', () => {\n subscribeTo(lc, replicator);\n resolve();\n });\n await replicaReady;\n\n const notifier = createNotifierFrom(lc, replicator);\n for (let i = 0; i < numSyncers; i++) {\n syncers.push(loadWorker(SYNCER_URL, 'user-facing', i, mode, String(i)));\n }\n syncers.forEach(syncer => handleSubscriptionsFrom(lc, syncer, notifier));\n }\n let mutator: Worker | undefined;\n if (clientConnectionBifurcated) {\n mutator = loadWorker(MUTATOR_URL, 'supporting', 'mutator');\n }\n\n lc.info?.('waiting for workers to be ready ...');\n const logWaiting = setInterval(\n () => lc.info?.(`still waiting for ${processes.initializing().join(', ')}`),\n 10_000,\n );\n await processes.allWorkersReady();\n clearInterval(logWaiting);\n lc.info?.(`all workers ready (${Date.now() - startMs} ms)`);\n\n parent.send(['ready', {ready: true}]);\n\n try {\n await runUntilKilled(\n lc,\n parent,\n new WorkerDispatcher(\n lc,\n taskID,\n parent,\n syncers,\n mutator,\n changeStreamer,\n ),\n );\n } catch (err) {\n processes.logErrorAndExit(err, 'dispatcher');\n }\n\n await processes.done();\n}\n\nif (!singleProcessMode()) {\n void exitAfter(lc, () => runWorker(must(parentWorker), process.env));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"main.js","names":[],"sources":["../../../../../zero-cache/src/server/main.ts"],"sourcesContent":["import path from 'node:path';\nimport {consoleLogSink, LogContext} from '@rocicorp/logger';\nimport {resolver} from '@rocicorp/resolver';\nimport {must} from '../../../shared/src/must.ts';\nimport {getNormalizedZeroConfig} from '../config/zero-config.ts';\nimport {initEventSink} from '../observability/events.ts';\nimport {\n exitAfter,\n ProcessManager,\n recordStartupDurationMs,\n runUntilKilled,\n type WorkerType,\n} from '../services/life-cycle.ts';\nimport {\n restoreReplica,\n startReplicaBackupProcess,\n} from '../services/litestream/commands.ts';\nimport {\n childWorker,\n parentWorker,\n singleProcessMode,\n type Worker,\n} from '../types/processes.ts';\nimport {\n createNotifierFrom,\n handleSubscriptionsFrom,\n type ReplicaFileMode,\n subscribeTo,\n} from '../workers/replicator.ts';\nimport {createLogContext} from './logging.ts';\nimport {startOtelAuto} from './otel-start.ts';\nimport {WorkerDispatcher} from './worker-dispatcher.ts';\nimport {\n CHANGE_STREAMER_URL,\n MUTATOR_URL,\n REAPER_URL,\n REPLICATOR_URL,\n SHADOW_SYNCER_URL,\n SYNCER_URL,\n} from './worker-urls.ts';\n\nconst clientConnectionBifurcated = false;\n\n// Default LogContext, overridden in runWorker\nlet lc = new LogContext('info', {}, consoleLogSink);\n\nexport default async function runWorker(\n parent: Worker,\n env: NodeJS.ProcessEnv,\n): Promise<void> {\n const startMs = Date.now();\n const config = getNormalizedZeroConfig({env});\n\n startOtelAuto(\n createLogContext(config, 'dispatcher', 0, false),\n 'dispatcher',\n 0,\n );\n lc = createLogContext(config, 'dispatcher');\n initEventSink(lc, config);\n\n const processes = new ProcessManager(lc, parent);\n\n const {numSyncWorkers: numSyncers} = config;\n if (config.enableCrudMutations && config.upstream.maxConns < numSyncers) {\n throw new Error(\n `Insufficient upstream connections (${config.upstream.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_UPSTREAM_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n if (config.cvr.maxConns < numSyncers) {\n throw new Error(\n `Insufficient cvr connections (${config.cvr.maxConns}) for ${numSyncers} syncers.` +\n `Increase ZERO_CVR_MAX_CONNS or decrease ZERO_NUM_SYNC_WORKERS (which defaults to available cores).`,\n );\n }\n\n const internalFlags: string[] =\n numSyncers === 0\n ? []\n : [\n '--upstream-max-conns-per-worker',\n String(Math.floor(config.upstream.maxConns / numSyncers)),\n '--cvr-max-conns-per-worker',\n String(Math.floor(config.cvr.maxConns / numSyncers)),\n ];\n\n function loadWorker(\n moduleUrl: URL,\n type: WorkerType,\n id?: string | number,\n ...args: string[]\n ): Worker {\n const worker = childWorker(moduleUrl, env, ...args, ...internalFlags);\n const name = path.basename(moduleUrl.pathname) + (id ? ` (${id})` : '');\n return processes.addWorker(worker, type, name);\n }\n\n const {\n taskID,\n changeStreamer: {mode: changeStreamerMode, uri: changeStreamerURI},\n litestream,\n } = config;\n const runChangeStreamer =\n changeStreamerMode === 'dedicated' && changeStreamerURI === undefined;\n\n let changeStreamer: Worker | undefined;\n\n if (!runChangeStreamer) {\n changeStreamer = undefined;\n if (litestream.executable) {\n // For view-syncers, the backup is restored here. For the replication-manager,\n // the backup is restored in the change-streamer worker.\n await restoreReplica(lc, config, null);\n }\n } else {\n const {promise: changeStreamerReady, resolve: changeStreamerStarted} =\n resolver();\n changeStreamer = loadWorker(CHANGE_STREAMER_URL, 'supporting').once(\n 'message',\n changeStreamerStarted,\n );\n\n // Wait for the change-streamer to be ready to guarantee that a replica\n // file is present.\n await changeStreamerReady;\n\n if (litestream.backupURL) {\n // Start a backup replicator and corresponding litestream backup process.\n const {promise: backupReady, resolve} = resolver();\n const mode: ReplicaFileMode = 'backup';\n loadWorker(REPLICATOR_URL, 'supporting', mode, mode).once(\n // Wait for the Replicator's first message (i.e. \"ready\") before starting\n // litestream backup in order to avoid contending on the lock when the\n // replicator first prepares the db file.\n 'message',\n () => {\n processes.addSubprocess(\n startReplicaBackupProcess(lc, config),\n 'supporting',\n 'litestream',\n );\n resolve();\n },\n );\n await backupReady;\n }\n }\n\n if (numSyncers > 0) {\n const {promise: reaperReady, resolve: reaperStarted} = resolver();\n loadWorker(REAPER_URL, 'supporting').once('message', reaperStarted);\n // Before starting the view-syncers, ensure that the reaper has started\n // up, indicating that any CVR db migrations have been performed.\n await reaperReady;\n }\n\n // Only run the shadow-sync canary on the replication-manager (or in\n // single-node mode, where it also owns upstream). Running on every\n // view-syncer would hammer the upstream with N redundant canaries.\n if (config.shadowSync.enabled && runChangeStreamer) {\n const {promise: shadowReady, resolve: shadowStarted} = resolver();\n loadWorker(SHADOW_SYNCER_URL, 'supporting').once('message', shadowStarted);\n await shadowReady;\n }\n\n const syncers: Worker[] = [];\n if (numSyncers) {\n const mode: ReplicaFileMode =\n runChangeStreamer && litestream.backupURL ? 'serving-copy' : 'serving';\n const {promise: replicaReady, resolve} = resolver();\n const replicator = loadWorker(\n REPLICATOR_URL,\n 'supporting',\n mode,\n mode,\n ).once('message', () => {\n subscribeTo(lc, replicator);\n resolve();\n });\n await replicaReady;\n\n const notifier = createNotifierFrom(lc, replicator);\n for (let i = 0; i < numSyncers; i++) {\n syncers.push(loadWorker(SYNCER_URL, 'user-facing', i, mode, String(i)));\n }\n syncers.forEach(syncer => handleSubscriptionsFrom(lc, syncer, notifier));\n }\n let mutator: Worker | undefined;\n if (clientConnectionBifurcated) {\n mutator = loadWorker(MUTATOR_URL, 'supporting', 'mutator');\n }\n\n lc.info?.('waiting for workers to be ready ...');\n const logWaiting = setInterval(\n () => lc.info?.(`still waiting for ${processes.initializing().join(', ')}`),\n 10_000,\n );\n await processes.allWorkersReady();\n clearInterval(logWaiting);\n const startupDurationMs = Date.now() - startMs;\n lc.info?.(`all workers ready (${startupDurationMs} ms)`);\n recordStartupDurationMs(startupDurationMs);\n\n parent.send(['ready', {ready: true}]);\n\n try {\n await runUntilKilled(\n lc,\n parent,\n new WorkerDispatcher(\n lc,\n taskID,\n parent,\n syncers,\n mutator,\n changeStreamer,\n ),\n );\n } catch (err) {\n processes.logErrorAndExit(err, 'dispatcher');\n }\n\n await processes.done();\n}\n\nif (!singleProcessMode()) {\n void exitAfter(lc, () => runWorker(must(parentWorker), process.env));\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otel-start.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/otel-start.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"otel-start.d.ts","sourceRoot":"","sources":["../../../../../zero-cache/src/server/otel-start.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAgGjD,eAAO,MAAM,aAAa,GACxB,IAAI,UAAU,GAAG,SAAS,EAC1B,YAAY,MAAM,EAClB,aAAa,MAAM,SACoD,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { NATIVE_HISTOGRAM_INSTRUMENT_NAMES } from "../observability/metrics.js";
|
|
1
2
|
import { otelEnabled, otelLogsEnabled, otelMetricsEnabled, otelTracesEnabled } from "../../../otel/src/enabled.js";
|
|
2
3
|
import { setupOtelDiagnosticLogger } from "./otel-diag-logger.js";
|
|
3
4
|
import { logs } from "@opentelemetry/api-logs";
|
|
4
5
|
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
5
6
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
7
|
+
import { AggregationType, InstrumentType } from "@opentelemetry/sdk-metrics";
|
|
6
8
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
7
9
|
import { ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
8
10
|
//#region ../zero-cache/src/server/otel-start.ts
|
|
@@ -32,6 +34,11 @@ var OtelManager = class OtelManager {
|
|
|
32
34
|
const sdk = new NodeSDK({
|
|
33
35
|
resource,
|
|
34
36
|
autoDetectResources: true,
|
|
37
|
+
views: NATIVE_HISTOGRAM_INSTRUMENT_NAMES.map((instrumentName) => ({
|
|
38
|
+
instrumentName,
|
|
39
|
+
instrumentType: InstrumentType.HISTOGRAM,
|
|
40
|
+
aggregation: { type: AggregationType.EXPONENTIAL_HISTOGRAM }
|
|
41
|
+
})),
|
|
35
42
|
instrumentations: process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS || process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS ? [getNodeAutoInstrumentations()] : []
|
|
36
43
|
});
|
|
37
44
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otel-start.js","names":["#instance","#started"],"sources":["../../../../../zero-cache/src/server/otel-start.ts"],"sourcesContent":["import {logs} from '@opentelemetry/api-logs';\nimport {getNodeAutoInstrumentations} from '@opentelemetry/auto-instrumentations-node';\nimport {resourceFromAttributes} from '@opentelemetry/resources';\nimport {NodeSDK} from '@opentelemetry/sdk-node';\nimport {ATTR_SERVICE_VERSION} from '@opentelemetry/semantic-conventions';\nimport type {LogContext} from '@rocicorp/logger';\nimport {\n otelEnabled,\n otelLogsEnabled,\n otelMetricsEnabled,\n otelTracesEnabled,\n} from '../../../otel/src/enabled.ts';\nimport {setupOtelDiagnosticLogger} from './otel-diag-logger.ts';\n\nclass OtelManager {\n static #instance: OtelManager;\n #started = false;\n\n private constructor() {}\n\n static getInstance(): OtelManager {\n if (!OtelManager.#instance) {\n OtelManager.#instance = new OtelManager();\n }\n return OtelManager.#instance;\n }\n\n startOtelAuto(\n lc: LogContext | undefined,\n workerName: string,\n workerIndex: number,\n ) {\n if (this.#started || !otelEnabled()) {\n return;\n }\n this.#started = true;\n\n // Store and temporarily remove OTEL_LOG_LEVEL to prevent NodeSDK from setting its own logger\n const otelLogLevel = process.env.OTEL_LOG_LEVEL;\n delete process.env.OTEL_LOG_LEVEL;\n\n // Use exponential histograms by default to reduce cardinality from auto-instrumentation\n // This affects HTTP server/client and other auto-instrumented histogram metrics\n // Exponential histograms automatically adjust bucket boundaries and use fewer buckets\n process.env.OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ??=\n 'base2_exponential_bucket_histogram';\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_VERSION]: process.env.ZERO_SERVER_VERSION ?? 'unknown',\n // Tag every metric/trace/log with the worker name and index so each\n // worker process in a multi-worker pod is distinguishable. Without\n // this, N syncer workers sharing the same pod labels clobber each\n // other in the OTel collector on every scrape interval.\n // These mirror the 'worker' and 'workerIndex' keys in every log\n // context so logs and metrics can be correlated on the same fields.\n // Using a stable index instead of PID avoids label churn in Prometheus.\n 'process.worker': workerName,\n 'process.worker_index': workerIndex,\n });\n\n // Set defaults to be backwards compatible with the previously\n // hard-coded exporters\n process.env.OTEL_EXPORTER_OTLP_PROTOCOL ??= 'http/json';\n process.env.OTEL_METRICS_EXPORTER ??= otelMetricsEnabled()\n ? 'otlp'\n : 'none';\n process.env.OTEL_TRACES_EXPORTER ??= otelTracesEnabled() ? 'otlp' : 'none';\n process.env.OTEL_LOGS_EXPORTER ??= otelLogsEnabled() ? 'otlp' : 'none';\n\n const sdk = new NodeSDK({\n resource,\n autoDetectResources: true,\n instrumentations:\n process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS ||\n process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS\n ? [getNodeAutoInstrumentations()]\n : [],\n });\n\n try {\n sdk.start();\n } finally {\n if (otelLogLevel) {\n process.env.OTEL_LOG_LEVEL = otelLogLevel;\n }\n }\n setupOtelDiagnosticLogger(lc, true);\n\n logs.getLogger('zero-cache').emit({\n severityText: 'INFO',\n body: 'OpenTelemetry SDK started successfully',\n });\n }\n}\n\nexport const startOtelAuto = (\n lc: LogContext | undefined,\n workerName: string,\n workerIndex: number,\n) => OtelManager.getInstance().startOtelAuto(lc, workerName, workerIndex);\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"otel-start.js","names":["#instance","#started"],"sources":["../../../../../zero-cache/src/server/otel-start.ts"],"sourcesContent":["import {logs} from '@opentelemetry/api-logs';\nimport {getNodeAutoInstrumentations} from '@opentelemetry/auto-instrumentations-node';\nimport {resourceFromAttributes} from '@opentelemetry/resources';\nimport {AggregationType, InstrumentType} from '@opentelemetry/sdk-metrics';\nimport {NodeSDK} from '@opentelemetry/sdk-node';\nimport {ATTR_SERVICE_VERSION} from '@opentelemetry/semantic-conventions';\nimport type {LogContext} from '@rocicorp/logger';\nimport {\n otelEnabled,\n otelLogsEnabled,\n otelMetricsEnabled,\n otelTracesEnabled,\n} from '../../../otel/src/enabled.ts';\nimport {NATIVE_HISTOGRAM_INSTRUMENT_NAMES} from '../observability/metrics.ts';\nimport {setupOtelDiagnosticLogger} from './otel-diag-logger.ts';\n\nclass OtelManager {\n static #instance: OtelManager;\n #started = false;\n\n private constructor() {}\n\n static getInstance(): OtelManager {\n if (!OtelManager.#instance) {\n OtelManager.#instance = new OtelManager();\n }\n return OtelManager.#instance;\n }\n\n startOtelAuto(\n lc: LogContext | undefined,\n workerName: string,\n workerIndex: number,\n ) {\n if (this.#started || !otelEnabled()) {\n return;\n }\n this.#started = true;\n\n // Store and temporarily remove OTEL_LOG_LEVEL to prevent NodeSDK from setting its own logger\n const otelLogLevel = process.env.OTEL_LOG_LEVEL;\n delete process.env.OTEL_LOG_LEVEL;\n\n // Use exponential histograms by default to reduce cardinality from auto-instrumentation\n // This affects HTTP server/client and other auto-instrumented histogram metrics\n // Exponential histograms automatically adjust bucket boundaries and use fewer buckets\n process.env.OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION ??=\n 'base2_exponential_bucket_histogram';\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_VERSION]: process.env.ZERO_SERVER_VERSION ?? 'unknown',\n // Tag every metric/trace/log with the worker name and index so each\n // worker process in a multi-worker pod is distinguishable. Without\n // this, N syncer workers sharing the same pod labels clobber each\n // other in the OTel collector on every scrape interval.\n // These mirror the 'worker' and 'workerIndex' keys in every log\n // context so logs and metrics can be correlated on the same fields.\n // Using a stable index instead of PID avoids label churn in Prometheus.\n 'process.worker': workerName,\n 'process.worker_index': workerIndex,\n });\n\n // Set defaults to be backwards compatible with the previously\n // hard-coded exporters\n process.env.OTEL_EXPORTER_OTLP_PROTOCOL ??= 'http/json';\n process.env.OTEL_METRICS_EXPORTER ??= otelMetricsEnabled()\n ? 'otlp'\n : 'none';\n process.env.OTEL_TRACES_EXPORTER ??= otelTracesEnabled() ? 'otlp' : 'none';\n process.env.OTEL_LOGS_EXPORTER ??= otelLogsEnabled() ? 'otlp' : 'none';\n\n const sdk = new NodeSDK({\n resource,\n autoDetectResources: true,\n views: NATIVE_HISTOGRAM_INSTRUMENT_NAMES.map(instrumentName => ({\n instrumentName,\n instrumentType: InstrumentType.HISTOGRAM,\n aggregation: {type: AggregationType.EXPONENTIAL_HISTOGRAM},\n })),\n instrumentations:\n process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS ||\n process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS\n ? [getNodeAutoInstrumentations()]\n : [],\n });\n\n try {\n sdk.start();\n } finally {\n if (otelLogLevel) {\n process.env.OTEL_LOG_LEVEL = otelLogLevel;\n }\n }\n setupOtelDiagnosticLogger(lc, true);\n\n logs.getLogger('zero-cache').emit({\n severityText: 'INFO',\n body: 'OpenTelemetry SDK started successfully',\n });\n }\n}\n\nexport const startOtelAuto = (\n lc: LogContext | undefined,\n workerName: string,\n workerIndex: number,\n) => OtelManager.getInstance().startOtelAuto(lc, workerName, workerIndex);\n"],"mappings":";;;;;;;;;;AAgBA,IAAM,cAAN,MAAM,YAAY;CAChB,OAAOA;CACP,WAAW;CAEX,cAAsB,CAAC;CAEvB,OAAO,cAA2B;EAChC,IAAI,CAAC,YAAYA,WACf,YAAYA,YAAY,IAAI,YAAY;EAE1C,OAAO,YAAYA;CACrB;CAEA,cACE,IACA,YACA,aACA;EACA,IAAI,KAAKC,YAAY,CAAC,YAAY,GAChC;EAEF,KAAKA,WAAW;EAGhB,MAAM,eAAe,QAAQ,IAAI;EACjC,OAAO,QAAQ,IAAI;EAKnB,QAAQ,IAAI,6DACV;EAEF,MAAM,WAAW,uBAAuB;IACrC,uBAAuB,QAAQ,IAAI,uBAAuB;GAQ3D,kBAAkB;GAClB,wBAAwB;EAC1B,CAAC;EAID,QAAQ,IAAI,gCAAgC;EAC5C,QAAQ,IAAI,0BAA0B,mBAAmB,IACrD,SACA;EACJ,QAAQ,IAAI,yBAAyB,kBAAkB,IAAI,SAAS;EACpE,QAAQ,IAAI,uBAAuB,gBAAgB,IAAI,SAAS;EAEhE,MAAM,MAAM,IAAI,QAAQ;GACtB;GACA,qBAAqB;GACrB,OAAO,kCAAkC,KAAI,oBAAmB;IAC9D;IACA,gBAAgB,eAAe;IAC/B,aAAa,EAAC,MAAM,gBAAgB,sBAAqB;GAC3D,EAAE;GACF,kBACE,QAAQ,IAAI,sCACZ,QAAQ,IAAI,sCACR,CAAC,4BAA4B,CAAC,IAC9B,CAAC;EACT,CAAC;EAED,IAAI;GACF,IAAI,MAAM;EACZ,UAAU;GACR,IAAI,cACF,QAAQ,IAAI,iBAAiB;EAEjC;EACA,0BAA0B,IAAI,IAAI;EAElC,KAAK,UAAU,YAAY,EAAE,KAAK;GAChC,cAAc;GACd,MAAM;EACR,CAAC;CACH;AACF;AAEA,IAAa,iBACX,IACA,YACA,gBACG,YAAY,YAAY,EAAE,cAAc,IAAI,YAAY,WAAW"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replica-schema.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/common/replica-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,iCAAiC,CAAC;AAE9D,OAAO,
|
|
1
|
+
{"version":3,"file":"replica-schema.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/common/replica-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,iCAAiC,CAAC;AAE9D,OAAO,EAEL,KAAK,uBAAuB,EAE7B,MAAM,+BAA+B,CAAC;AAQvC,wBAAsB,WAAW,CAC/B,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,GAC3D,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,iBAoBf;AAYD,eAAO,MAAM,+BAA+B,0UAW3C,CAAC;AAEF,eAAO,MAAM,oBAAoB,8XAUhC,CAAC;AAEF,eAAO,MAAM,8BAA8B,sMAO1C,CAAC;AAEF,eAAO,MAAM,yBAAyB,EAAE,uBA8HvC,CAAC;AAGF,eAAO,MAAM,sBAAsB,QAEwB,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { populateFromExistingTables } from "../../replicator/schema/column-metadata.js";
|
|
2
2
|
import { listTables } from "../../../db/lite-tables.js";
|
|
3
3
|
import { CREATE_RUNTIME_EVENTS_TABLE, recordEvent } from "../../replicator/schema/replication-state.js";
|
|
4
|
-
import {
|
|
4
|
+
import { runSchemaMigrations } from "../../../db/migration-lite.js";
|
|
5
5
|
import { AutoResetSignal } from "../../change-streamer/schema/tables.js";
|
|
6
6
|
import { SqliteError } from "@rocicorp/zero-sqlite3";
|
|
7
7
|
//#region ../zero-cache/src/services/change-source/common/replica-schema.ts
|
|
@@ -26,7 +26,7 @@ async function upgradeReplica(log, debugName, dbPath) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
function throwAutoResetForCorruption(e) {
|
|
29
|
-
if (e instanceof
|
|
29
|
+
if (e instanceof SqliteError && e.code === "SQLITE_CORRUPT") throw new AutoResetSignal(`replica database appears corrupt: ${String(e)}`, { cause: e });
|
|
30
30
|
throw e;
|
|
31
31
|
}
|
|
32
32
|
var CREATE_V6_COLUMN_METADATA_TABLE = `
|