@prisma/streams-server 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CONTRIBUTING.md +8 -0
  2. package/package.json +2 -1
  3. package/src/app.ts +290 -17
  4. package/src/app_core.ts +1833 -698
  5. package/src/app_local.ts +144 -4
  6. package/src/auto_tune.ts +62 -0
  7. package/src/bootstrap.ts +159 -1
  8. package/src/concurrency_gate.ts +108 -0
  9. package/src/config.ts +116 -14
  10. package/src/db/db.ts +1201 -131
  11. package/src/db/schema.ts +308 -8
  12. package/src/foreground_activity.ts +55 -0
  13. package/src/index/indexer.ts +254 -124
  14. package/src/index/lexicon_file_cache.ts +261 -0
  15. package/src/index/lexicon_format.ts +93 -0
  16. package/src/index/lexicon_indexer.ts +789 -0
  17. package/src/index/secondary_indexer.ts +824 -0
  18. package/src/index/secondary_schema.ts +105 -0
  19. package/src/ingest.ts +10 -12
  20. package/src/manifest.ts +143 -8
  21. package/src/memory.ts +183 -8
  22. package/src/metrics.ts +15 -29
  23. package/src/metrics_emitter.ts +26 -3
  24. package/src/notifier.ts +121 -5
  25. package/src/objectstore/accounting.ts +92 -0
  26. package/src/objectstore/mock_r2.ts +1 -1
  27. package/src/objectstore/r2.ts +17 -1
  28. package/src/profiles/evlog/schema.ts +234 -0
  29. package/src/profiles/evlog.ts +299 -0
  30. package/src/profiles/generic.ts +47 -0
  31. package/src/profiles/index.ts +205 -0
  32. package/src/profiles/metrics/block_format.ts +109 -0
  33. package/src/profiles/metrics/normalize.ts +366 -0
  34. package/src/profiles/metrics/schema.ts +319 -0
  35. package/src/profiles/metrics.ts +85 -0
  36. package/src/profiles/profile.ts +225 -0
  37. package/src/{touch/engine.ts → profiles/stateProtocol/changes.ts} +3 -20
  38. package/src/profiles/stateProtocol/routes.ts +389 -0
  39. package/src/profiles/stateProtocol/types.ts +6 -0
  40. package/src/profiles/stateProtocol/validation.ts +51 -0
  41. package/src/profiles/stateProtocol.ts +100 -0
  42. package/src/read_filter.ts +468 -0
  43. package/src/reader.ts +2151 -164
  44. package/src/runtime/host_runtime.ts +5 -0
  45. package/src/runtime_memory.ts +200 -0
  46. package/src/runtime_memory_sampler.ts +235 -0
  47. package/src/schema/read_json.ts +43 -0
  48. package/src/schema/registry.ts +563 -59
  49. package/src/search/agg_format.ts +638 -0
  50. package/src/search/aggregate.ts +389 -0
  51. package/src/search/binary/codec.ts +162 -0
  52. package/src/search/binary/docset.ts +67 -0
  53. package/src/search/binary/restart_strings.ts +181 -0
  54. package/src/search/binary/varint.ts +34 -0
  55. package/src/search/bitset.ts +19 -0
  56. package/src/search/col_format.ts +382 -0
  57. package/src/search/col_runtime.ts +59 -0
  58. package/src/search/column_encoding.ts +43 -0
  59. package/src/search/companion_file_cache.ts +319 -0
  60. package/src/search/companion_format.ts +313 -0
  61. package/src/search/companion_manager.ts +1086 -0
  62. package/src/search/companion_plan.ts +218 -0
  63. package/src/search/fts_format.ts +423 -0
  64. package/src/search/fts_runtime.ts +333 -0
  65. package/src/search/query.ts +875 -0
  66. package/src/search/schema.ts +245 -0
  67. package/src/segment/cache.ts +93 -2
  68. package/src/segment/cached_segment.ts +89 -0
  69. package/src/segment/format.ts +108 -36
  70. package/src/segment/segmenter.ts +79 -5
  71. package/src/segment/segmenter_worker.ts +35 -6
  72. package/src/segment/segmenter_workers.ts +42 -12
  73. package/src/server.ts +150 -36
  74. package/src/sqlite/adapter.ts +185 -14
  75. package/src/sqlite/runtime_stats.ts +163 -0
  76. package/src/stats.ts +3 -3
  77. package/src/stream_size_reconciler.ts +100 -0
  78. package/src/touch/canonical_change.ts +7 -0
  79. package/src/touch/live_metrics.ts +94 -64
  80. package/src/touch/live_templates.ts +15 -1
  81. package/src/touch/manager.ts +166 -88
  82. package/src/touch/{interpreter_worker.ts → processor_worker.ts} +19 -14
  83. package/src/touch/spec.ts +95 -92
  84. package/src/touch/touch_journal.ts +4 -0
  85. package/src/touch/worker_pool.ts +8 -14
  86. package/src/touch/worker_protocol.ts +3 -3
  87. package/src/uploader.ts +77 -6
  88. package/src/util/bloom256.ts +2 -2
  89. package/src/util/byte_lru.ts +73 -0
  90. package/src/util/lru.ts +8 -0
  91. package/src/util/stream_paths.ts +19 -0
@@ -0,0 +1,366 @@
1
+ import { Result } from "better-result";
2
+ import type { AggSummaryState } from "../../search/agg_format";
3
+ import type { NormalizedMetricsRecord } from "../profile";
4
+ import { expectPlainObjectResult, isPlainObject } from "../profile";
5
+
6
+ type PrimitiveAttribute = string | number | boolean | bigint;
7
+
8
+ type HistogramMap = Record<string, number>;
9
+
10
+ function normalizeString(value: unknown): string | null {
11
+ if (typeof value !== "string") return null;
12
+ const trimmed = value.trim();
13
+ return trimmed === "" ? null : trimmed;
14
+ }
15
+
16
+ function normalizeFiniteNumber(value: unknown): number | null {
17
+ if (typeof value === "number" && Number.isFinite(value)) return value;
18
+ if (typeof value === "bigint") return Number(value);
19
+ if (typeof value === "string" && value.trim() !== "") {
20
+ const parsed = Number(value);
21
+ if (Number.isFinite(parsed)) return parsed;
22
+ }
23
+ return null;
24
+ }
25
+
26
+ function normalizeInteger(value: unknown): number | null {
27
+ const numeric = normalizeFiniteNumber(value);
28
+ if (numeric == null) return null;
29
+ return Math.trunc(numeric);
30
+ }
31
+
32
+ function normalizeHistogram(value: unknown): HistogramMap | undefined {
33
+ if (!isPlainObject(value)) return undefined;
34
+ const out: HistogramMap = {};
35
+ for (const [bucket, raw] of Object.entries(value)) {
36
+ const count = normalizeFiniteNumber(raw);
37
+ if (count == null || count <= 0) continue;
38
+ out[String(bucket)] = (out[String(bucket)] ?? 0) + count;
39
+ }
40
+ return Object.keys(out).length > 0 ? out : undefined;
41
+ }
42
+
43
+ function histogramPercentile(histogram: HistogramMap | undefined, percentile: number): number | null {
44
+ if (!histogram) return null;
45
+ const entries = Object.entries(histogram)
46
+ .map(([bucket, count]) => ({ bucket: Number(bucket), count }))
47
+ .filter((entry) => Number.isFinite(entry.bucket) && Number.isFinite(entry.count) && entry.count > 0)
48
+ .sort((a, b) => a.bucket - b.bucket);
49
+ if (entries.length === 0) return null;
50
+ const total = entries.reduce((sum, entry) => sum + entry.count, 0);
51
+ if (total <= 0) return null;
52
+ const threshold = total * percentile;
53
+ let seen = 0;
54
+ for (const entry of entries) {
55
+ seen += entry.count;
56
+ if (seen >= threshold) return entry.bucket;
57
+ }
58
+ return entries[entries.length - 1]?.bucket ?? null;
59
+ }
60
+
61
+ function normalizeAttributes(value: unknown): Record<string, string> {
62
+ if (!isPlainObject(value)) return {};
63
+ const out: Record<string, string> = {};
64
+ for (const [key, raw] of Object.entries(value)) {
65
+ if (typeof raw === "string") out[key] = raw;
66
+ else if (typeof raw === "number" && Number.isFinite(raw)) out[key] = String(raw);
67
+ else if (typeof raw === "boolean") out[key] = raw ? "true" : "false";
68
+ else if (typeof raw === "bigint") out[key] = raw.toString();
69
+ }
70
+ return Object.fromEntries(Object.entries(out).sort((a, b) => a[0].localeCompare(b[0])));
71
+ }
72
+
73
+ function buildDimensionPairs(attributes: Record<string, string>): string[] {
74
+ return Object.entries(attributes)
75
+ .map(([key, value]) => `${key}=${value}`)
76
+ .sort((a, b) => a.localeCompare(b));
77
+ }
78
+
79
+ function buildSeriesKey(args: {
80
+ metricKind: string;
81
+ temporality: string;
82
+ metric: string;
83
+ unit: string;
84
+ stream: string | null;
85
+ instance: string | null;
86
+ dimensionKey: string | null;
87
+ }): string {
88
+ return [
89
+ args.metricKind,
90
+ args.temporality,
91
+ args.metric,
92
+ args.unit,
93
+ args.stream ?? "",
94
+ args.instance ?? "",
95
+ args.dimensionKey ?? "",
96
+ ].join("|");
97
+ }
98
+
99
+ function normalizeSummaryResult(value: Record<string, unknown>): Result<AggSummaryState, { message: string }> {
100
+ const summaryObject = isPlainObject(value.summary) ? value.summary : null;
101
+ const histogram =
102
+ normalizeHistogram(value.buckets) ??
103
+ normalizeHistogram(summaryObject?.histogram ?? undefined);
104
+ const count =
105
+ normalizeFiniteNumber(value.count) ??
106
+ normalizeFiniteNumber(summaryObject?.count);
107
+ const sum =
108
+ normalizeFiniteNumber(value.sum) ??
109
+ normalizeFiniteNumber(summaryObject?.sum);
110
+ const min =
111
+ normalizeFiniteNumber(value.min) ??
112
+ normalizeFiniteNumber(summaryObject?.min);
113
+ const max =
114
+ normalizeFiniteNumber(value.max) ??
115
+ normalizeFiniteNumber(summaryObject?.max);
116
+
117
+ if (count == null || count < 0) return Result.err({ message: "metrics interval requires count" });
118
+ if (sum == null) return Result.err({ message: "metrics interval requires sum" });
119
+
120
+ return Result.ok({
121
+ count,
122
+ sum,
123
+ min: count === 0 ? null : min ?? 0,
124
+ max: count === 0 ? null : max ?? 0,
125
+ histogram,
126
+ });
127
+ }
128
+
129
+ export function normalizeMetricsRecordResult(value: unknown): Result<NormalizedMetricsRecord, { message: string }> {
130
+ const objRes = expectPlainObjectResult(value, "metrics record");
131
+ if (Result.isError(objRes)) return objRes;
132
+ const input = objRes.value;
133
+
134
+ const kind = normalizeString(input.kind) ?? "interval";
135
+ if (kind !== "interval") return Result.err({ message: "metrics record.kind must be interval" });
136
+
137
+ const metric = normalizeString(input.metric);
138
+ if (!metric) return Result.err({ message: "metrics record.metric must be a non-empty string" });
139
+
140
+ const unit = normalizeString(input.unit);
141
+ if (!unit) return Result.err({ message: "metrics record.unit must be a non-empty string" });
142
+
143
+ const windowStart = normalizeInteger(input.windowStart);
144
+ const windowEnd = normalizeInteger(input.windowEnd);
145
+ if (windowStart == null || windowEnd == null) {
146
+ return Result.err({ message: "metrics record.windowStart and windowEnd must be integers" });
147
+ }
148
+ if (windowEnd < windowStart) return Result.err({ message: "metrics record.windowEnd must be >= windowStart" });
149
+
150
+ const intervalMs = normalizeInteger(input.intervalMs) ?? (windowEnd - windowStart);
151
+ if (intervalMs < 0) return Result.err({ message: "metrics record.intervalMs must be >= 0" });
152
+
153
+ const metricKind = normalizeString(input.metricKind) ?? "summary";
154
+ const temporality = normalizeString(input.temporality) ?? "delta";
155
+ const stream = normalizeString(input.stream);
156
+ const instance = normalizeString(input.instance);
157
+ const attributes = normalizeAttributes(input.attributes ?? input.tags);
158
+ const dimensionPairs = buildDimensionPairs(attributes);
159
+ const dimensionKey = dimensionPairs.length > 0 ? dimensionPairs.join("\u0000") : null;
160
+ const seriesKey = buildSeriesKey({
161
+ metricKind,
162
+ temporality,
163
+ metric,
164
+ unit,
165
+ stream,
166
+ instance,
167
+ dimensionKey,
168
+ });
169
+
170
+ const summaryRes = normalizeSummaryResult(input);
171
+ if (Result.isError(summaryRes)) return summaryRes;
172
+ const summary = summaryRes.value;
173
+ const avg = normalizeFiniteNumber(input.avg) ?? (summary.count > 0 ? summary.sum / summary.count : 0);
174
+ const p50 = normalizeFiniteNumber(input.p50) ?? histogramPercentile(summary.histogram, 0.5) ?? 0;
175
+ const p95 = normalizeFiniteNumber(input.p95) ?? histogramPercentile(summary.histogram, 0.95) ?? 0;
176
+ const p99 = normalizeFiniteNumber(input.p99) ?? histogramPercentile(summary.histogram, 0.99) ?? 0;
177
+
178
+ const normalizedValue: Record<string, unknown> = {
179
+ apiVersion: "durable.streams/metrics/v1",
180
+ kind: "interval",
181
+ metric,
182
+ unit,
183
+ metricKind,
184
+ temporality,
185
+ windowStart,
186
+ windowEnd,
187
+ intervalMs,
188
+ instance,
189
+ stream,
190
+ tags: attributes,
191
+ attributes,
192
+ dimensionPairs,
193
+ dimensionKey,
194
+ seriesKey,
195
+ count: summary.count,
196
+ sum: summary.sum,
197
+ min: summary.min,
198
+ max: summary.max,
199
+ avg,
200
+ p50,
201
+ p95,
202
+ p99,
203
+ buckets: summary.histogram ?? {},
204
+ summary: {
205
+ count: summary.count,
206
+ sum: summary.sum,
207
+ min: summary.min,
208
+ max: summary.max,
209
+ histogram: summary.histogram ?? {},
210
+ },
211
+ };
212
+
213
+ return Result.ok({
214
+ value: normalizedValue,
215
+ routingKey: seriesKey,
216
+ companion: {
217
+ metric,
218
+ unit,
219
+ metricKind,
220
+ temporality,
221
+ windowStartMs: windowStart,
222
+ windowEndMs: windowEnd,
223
+ intervalMs,
224
+ stream,
225
+ instance,
226
+ attributes,
227
+ dimensionPairs,
228
+ dimensionKey,
229
+ seriesKey,
230
+ summary,
231
+ },
232
+ });
233
+ }
234
+
235
+ export type MetricsBlockRecord = {
236
+ doc_id: number;
237
+ metric: string;
238
+ unit: string;
239
+ metricKind: string;
240
+ temporality: string;
241
+ windowStartMs: number;
242
+ windowEndMs: number;
243
+ intervalMs: number;
244
+ stream: string | null;
245
+ instance: string | null;
246
+ attributes: Record<string, string>;
247
+ dimensionPairs: string[];
248
+ dimensionKey: string | null;
249
+ seriesKey: string;
250
+ summary: AggSummaryState;
251
+ };
252
+
253
+ export function buildMetricsBlockRecord(docId: number, value: unknown): Result<MetricsBlockRecord, { message: string }> {
254
+ const normalizedRes = normalizeMetricsRecordResult(value);
255
+ if (Result.isError(normalizedRes)) return normalizedRes;
256
+ return Result.ok({
257
+ doc_id: docId,
258
+ ...normalizedRes.value.companion,
259
+ });
260
+ }
261
+
262
+ export function materializeMetricsBlockRecord(record: MetricsBlockRecord): Record<string, unknown> {
263
+ const histogram = record.summary.histogram ?? {};
264
+ const avg = record.summary.count > 0 ? record.summary.sum / record.summary.count : 0;
265
+ return {
266
+ apiVersion: "durable.streams/metrics/v1",
267
+ kind: "interval",
268
+ metric: record.metric,
269
+ unit: record.unit,
270
+ metricKind: record.metricKind,
271
+ temporality: record.temporality,
272
+ windowStart: record.windowStartMs,
273
+ windowEnd: record.windowEndMs,
274
+ intervalMs: record.intervalMs,
275
+ instance: record.instance,
276
+ stream: record.stream,
277
+ tags: { ...record.attributes },
278
+ attributes: { ...record.attributes },
279
+ dimensionPairs: [...record.dimensionPairs],
280
+ dimensionKey: record.dimensionKey,
281
+ seriesKey: record.seriesKey,
282
+ count: record.summary.count,
283
+ sum: record.summary.sum,
284
+ min: record.summary.min,
285
+ max: record.summary.max,
286
+ avg,
287
+ p50: histogramPercentile(histogram, 0.5) ?? 0,
288
+ p95: histogramPercentile(histogram, 0.95) ?? 0,
289
+ p99: histogramPercentile(histogram, 0.99) ?? 0,
290
+ buckets: { ...histogram },
291
+ summary: {
292
+ count: record.summary.count,
293
+ sum: record.summary.sum,
294
+ min: record.summary.min,
295
+ max: record.summary.max,
296
+ histogram: { ...histogram },
297
+ },
298
+ };
299
+ }
300
+
301
+ export function buildInternalMetricsRecord(args: {
302
+ metric: string;
303
+ unit: string;
304
+ windowStart: number;
305
+ windowEnd: number;
306
+ intervalMs: number;
307
+ instance: string;
308
+ stream?: string;
309
+ tags?: Record<string, PrimitiveAttribute>;
310
+ count: number;
311
+ sum: number;
312
+ min: number;
313
+ max: number;
314
+ avg: number;
315
+ p50: number;
316
+ p95: number;
317
+ p99: number;
318
+ buckets: Record<string, number>;
319
+ }): Record<string, unknown> {
320
+ const attributes = normalizeAttributes(args.tags);
321
+ const dimensionPairs = buildDimensionPairs(attributes);
322
+ const dimensionKey = dimensionPairs.length > 0 ? dimensionPairs.join("\u0000") : null;
323
+ const seriesKey = buildSeriesKey({
324
+ metricKind: "summary",
325
+ temporality: "delta",
326
+ metric: args.metric,
327
+ unit: args.unit,
328
+ stream: args.stream ?? null,
329
+ instance: args.instance,
330
+ dimensionKey,
331
+ });
332
+ return {
333
+ apiVersion: "durable.streams/metrics/v1",
334
+ kind: "interval",
335
+ metric: args.metric,
336
+ unit: args.unit,
337
+ metricKind: "summary",
338
+ temporality: "delta",
339
+ windowStart: args.windowStart,
340
+ windowEnd: args.windowEnd,
341
+ intervalMs: args.intervalMs,
342
+ instance: args.instance,
343
+ stream: args.stream ?? null,
344
+ tags: attributes,
345
+ attributes,
346
+ dimensionPairs,
347
+ dimensionKey,
348
+ seriesKey,
349
+ count: args.count,
350
+ sum: args.sum,
351
+ min: args.count === 0 ? null : args.min,
352
+ max: args.count === 0 ? null : args.max,
353
+ avg: args.avg,
354
+ p50: args.p50,
355
+ p95: args.p95,
356
+ p99: args.p99,
357
+ buckets: { ...args.buckets },
358
+ summary: {
359
+ count: args.count,
360
+ sum: args.sum,
361
+ min: args.count === 0 ? null : args.min,
362
+ max: args.count === 0 ? null : args.max,
363
+ histogram: { ...args.buckets },
364
+ },
365
+ };
366
+ }
@@ -0,0 +1,319 @@
1
+ import {
2
+ SCHEMA_REGISTRY_API_VERSION,
3
+ type SchemaRegistry,
4
+ type SearchConfig,
5
+ } from "../../schema/registry";
6
+
7
+ export const METRICS_CANONICAL_SCHEMA = {
8
+ type: "object",
9
+ additionalProperties: false,
10
+ properties: {
11
+ apiVersion: { type: "string", const: "durable.streams/metrics/v1" },
12
+ kind: { type: "string", const: "interval" },
13
+ metric: { type: "string" },
14
+ unit: { type: "string" },
15
+ metricKind: { type: "string" },
16
+ temporality: { type: "string" },
17
+ windowStart: { type: "integer" },
18
+ windowEnd: { type: "integer" },
19
+ intervalMs: { type: "integer" },
20
+ instance: { type: ["string", "null"] },
21
+ stream: { type: ["string", "null"] },
22
+ tags: {
23
+ type: "object",
24
+ additionalProperties: { type: "string" },
25
+ },
26
+ attributes: {
27
+ type: "object",
28
+ additionalProperties: { type: "string" },
29
+ },
30
+ dimensionPairs: {
31
+ type: "array",
32
+ items: { type: "string" },
33
+ },
34
+ dimensionKey: { type: ["string", "null"] },
35
+ seriesKey: { type: "string" },
36
+ count: { type: "number" },
37
+ sum: { type: "number" },
38
+ min: { type: ["number", "null"] },
39
+ max: { type: ["number", "null"] },
40
+ avg: { type: "number" },
41
+ p50: { type: "number" },
42
+ p95: { type: "number" },
43
+ p99: { type: "number" },
44
+ buckets: {
45
+ type: "object",
46
+ additionalProperties: { type: "number" },
47
+ },
48
+ summary: {
49
+ type: "object",
50
+ additionalProperties: false,
51
+ properties: {
52
+ count: { type: "number" },
53
+ sum: { type: "number" },
54
+ min: { type: ["number", "null"] },
55
+ max: { type: ["number", "null"] },
56
+ histogram: {
57
+ type: "object",
58
+ additionalProperties: { type: "number" },
59
+ },
60
+ },
61
+ required: ["count", "sum", "min", "max", "histogram"],
62
+ },
63
+ },
64
+ required: [
65
+ "apiVersion",
66
+ "kind",
67
+ "metric",
68
+ "unit",
69
+ "metricKind",
70
+ "temporality",
71
+ "windowStart",
72
+ "windowEnd",
73
+ "intervalMs",
74
+ "instance",
75
+ "stream",
76
+ "tags",
77
+ "attributes",
78
+ "dimensionPairs",
79
+ "dimensionKey",
80
+ "seriesKey",
81
+ "count",
82
+ "sum",
83
+ "min",
84
+ "max",
85
+ "avg",
86
+ "p50",
87
+ "p95",
88
+ "p99",
89
+ "buckets",
90
+ "summary",
91
+ ],
92
+ } as const;
93
+
94
+ export const METRICS_DEFAULT_SEARCH_CONFIG: SearchConfig = {
95
+ profile: "metrics",
96
+ primaryTimestampField: "windowStart",
97
+ aliases: {
98
+ ts: "windowStart",
99
+ time: "windowStart",
100
+ name: "metric",
101
+ dims: "dimensionKey",
102
+ series: "seriesKey",
103
+ },
104
+ defaultFields: [
105
+ { field: "metric", boost: 2 },
106
+ { field: "stream", boost: 1.25 },
107
+ { field: "dimensionPairs", boost: 1 },
108
+ ],
109
+ fields: {
110
+ windowStart: {
111
+ kind: "date",
112
+ bindings: [{ version: 1, jsonPointer: "/windowStart" }],
113
+ exact: true,
114
+ column: true,
115
+ exists: true,
116
+ sortable: true,
117
+ aggregatable: true,
118
+ },
119
+ windowEnd: {
120
+ kind: "date",
121
+ bindings: [{ version: 1, jsonPointer: "/windowEnd" }],
122
+ exact: true,
123
+ column: true,
124
+ exists: true,
125
+ sortable: true,
126
+ },
127
+ intervalMs: {
128
+ kind: "integer",
129
+ bindings: [{ version: 1, jsonPointer: "/intervalMs" }],
130
+ exact: true,
131
+ column: true,
132
+ exists: true,
133
+ sortable: true,
134
+ aggregatable: true,
135
+ },
136
+ metric: {
137
+ kind: "keyword",
138
+ bindings: [{ version: 1, jsonPointer: "/metric" }],
139
+ normalizer: "lowercase_v1",
140
+ exact: true,
141
+ prefix: true,
142
+ exists: true,
143
+ sortable: true,
144
+ aggregatable: true,
145
+ },
146
+ unit: {
147
+ kind: "keyword",
148
+ bindings: [{ version: 1, jsonPointer: "/unit" }],
149
+ normalizer: "lowercase_v1",
150
+ exact: true,
151
+ prefix: true,
152
+ exists: true,
153
+ aggregatable: true,
154
+ },
155
+ metricKind: {
156
+ kind: "keyword",
157
+ bindings: [{ version: 1, jsonPointer: "/metricKind" }],
158
+ normalizer: "lowercase_v1",
159
+ exact: true,
160
+ prefix: true,
161
+ exists: true,
162
+ aggregatable: true,
163
+ },
164
+ temporality: {
165
+ kind: "keyword",
166
+ bindings: [{ version: 1, jsonPointer: "/temporality" }],
167
+ normalizer: "lowercase_v1",
168
+ exact: true,
169
+ prefix: true,
170
+ exists: true,
171
+ aggregatable: true,
172
+ },
173
+ stream: {
174
+ kind: "keyword",
175
+ bindings: [{ version: 1, jsonPointer: "/stream" }],
176
+ normalizer: "lowercase_v1",
177
+ exact: true,
178
+ prefix: true,
179
+ exists: true,
180
+ sortable: true,
181
+ aggregatable: true,
182
+ },
183
+ instance: {
184
+ kind: "keyword",
185
+ bindings: [{ version: 1, jsonPointer: "/instance" }],
186
+ exact: true,
187
+ prefix: true,
188
+ exists: true,
189
+ sortable: true,
190
+ aggregatable: true,
191
+ },
192
+ seriesKey: {
193
+ kind: "keyword",
194
+ bindings: [{ version: 1, jsonPointer: "/seriesKey" }],
195
+ exact: true,
196
+ exists: true,
197
+ sortable: true,
198
+ },
199
+ dimensionKey: {
200
+ kind: "keyword",
201
+ bindings: [{ version: 1, jsonPointer: "/dimensionKey" }],
202
+ exact: true,
203
+ prefix: true,
204
+ exists: true,
205
+ sortable: true,
206
+ aggregatable: true,
207
+ },
208
+ dimensionPairs: {
209
+ kind: "keyword",
210
+ bindings: [{ version: 1, jsonPointer: "/dimensionPairs" }],
211
+ exact: true,
212
+ prefix: true,
213
+ exists: true,
214
+ },
215
+ count: {
216
+ kind: "integer",
217
+ bindings: [{ version: 1, jsonPointer: "/count" }],
218
+ exact: true,
219
+ column: true,
220
+ exists: true,
221
+ sortable: true,
222
+ aggregatable: true,
223
+ },
224
+ sum: {
225
+ kind: "float",
226
+ bindings: [{ version: 1, jsonPointer: "/sum" }],
227
+ exact: true,
228
+ column: true,
229
+ exists: true,
230
+ sortable: true,
231
+ aggregatable: true,
232
+ },
233
+ min: {
234
+ kind: "float",
235
+ bindings: [{ version: 1, jsonPointer: "/min" }],
236
+ exact: true,
237
+ column: true,
238
+ exists: true,
239
+ sortable: true,
240
+ },
241
+ max: {
242
+ kind: "float",
243
+ bindings: [{ version: 1, jsonPointer: "/max" }],
244
+ exact: true,
245
+ column: true,
246
+ exists: true,
247
+ sortable: true,
248
+ },
249
+ avg: {
250
+ kind: "float",
251
+ bindings: [{ version: 1, jsonPointer: "/avg" }],
252
+ exact: true,
253
+ column: true,
254
+ exists: true,
255
+ sortable: true,
256
+ },
257
+ p95: {
258
+ kind: "float",
259
+ bindings: [{ version: 1, jsonPointer: "/p95" }],
260
+ exact: true,
261
+ column: true,
262
+ exists: true,
263
+ sortable: true,
264
+ },
265
+ p99: {
266
+ kind: "float",
267
+ bindings: [{ version: 1, jsonPointer: "/p99" }],
268
+ exact: true,
269
+ column: true,
270
+ exists: true,
271
+ sortable: true,
272
+ },
273
+ },
274
+ rollups: {
275
+ metrics: {
276
+ timestampField: "windowStart",
277
+ dimensions: ["metric", "unit", "stream", "instance", "dimensionKey", "metricKind", "temporality"],
278
+ intervals: ["10s", "1m", "5m", "1h"],
279
+ measures: {
280
+ value: {
281
+ kind: "summary_parts",
282
+ countJsonPointer: "/count",
283
+ sumJsonPointer: "/sum",
284
+ minJsonPointer: "/min",
285
+ maxJsonPointer: "/max",
286
+ histogramJsonPointer: "/buckets",
287
+ },
288
+ },
289
+ },
290
+ },
291
+ };
292
+
293
+ export function buildMetricsDefaultRegistry(stream: string): SchemaRegistry {
294
+ return {
295
+ apiVersion: SCHEMA_REGISTRY_API_VERSION,
296
+ schema: stream,
297
+ currentVersion: 1,
298
+ routingKey: { jsonPointer: "/seriesKey", required: true },
299
+ search: structuredClone(METRICS_DEFAULT_SEARCH_CONFIG),
300
+ boundaries: [{ offset: 0, version: 1 }],
301
+ schemas: {
302
+ "1": structuredClone(METRICS_CANONICAL_SCHEMA),
303
+ },
304
+ lenses: {},
305
+ };
306
+ }
307
+
308
+ export function buildInternalMetricsRegistry(stream: string): SchemaRegistry {
309
+ return {
310
+ apiVersion: SCHEMA_REGISTRY_API_VERSION,
311
+ schema: stream,
312
+ currentVersion: 1,
313
+ boundaries: [{ offset: 0, version: 1 }],
314
+ schemas: {
315
+ "1": structuredClone(METRICS_CANONICAL_SCHEMA),
316
+ },
317
+ lenses: {},
318
+ };
319
+ }