@smart-data-engines/sde 0.1.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +13 -0
  3. package/README.md +153 -0
  4. package/bin/weather.mjs +32 -0
  5. package/dist/_usage.d.ts +30 -0
  6. package/dist/_usage.js +194 -0
  7. package/dist/_usage.js.map +1 -0
  8. package/dist/bulk.d.ts +9 -0
  9. package/dist/bulk.js +83 -0
  10. package/dist/bulk.js.map +1 -0
  11. package/dist/canonical.d.ts +46 -0
  12. package/dist/canonical.js +150 -0
  13. package/dist/canonical.js.map +1 -0
  14. package/dist/capabilities.d.ts +48 -0
  15. package/dist/capabilities.js +62 -0
  16. package/dist/capabilities.js.map +1 -0
  17. package/dist/cutover.d.ts +36 -0
  18. package/dist/cutover.js +219 -0
  19. package/dist/cutover.js.map +1 -0
  20. package/dist/demo/model.d.ts +28 -0
  21. package/dist/demo/model.js +40 -0
  22. package/dist/demo/model.js.map +1 -0
  23. package/dist/demo/project.d.ts +19 -0
  24. package/dist/demo/project.js +128 -0
  25. package/dist/demo/project.js.map +1 -0
  26. package/dist/demo/weather.d.ts +73 -0
  27. package/dist/demo/weather.js +334 -0
  28. package/dist/demo/weather.js.map +1 -0
  29. package/dist/engines/_clickhouse-connection.d.ts +17 -0
  30. package/dist/engines/_clickhouse-connection.js +182 -0
  31. package/dist/engines/_clickhouse-connection.js.map +1 -0
  32. package/dist/engines/_tls-peer-identity.d.ts +2 -0
  33. package/dist/engines/_tls-peer-identity.js +23 -0
  34. package/dist/engines/_tls-peer-identity.js.map +1 -0
  35. package/dist/engines/_write-fences.d.ts +51 -0
  36. package/dist/engines/_write-fences.js +189 -0
  37. package/dist/engines/_write-fences.js.map +1 -0
  38. package/dist/engines/clickhouse.d.ts +193 -0
  39. package/dist/engines/clickhouse.js +899 -0
  40. package/dist/engines/clickhouse.js.map +1 -0
  41. package/dist/engines/postgres.d.ts +293 -0
  42. package/dist/engines/postgres.js +981 -0
  43. package/dist/engines/postgres.js.map +1 -0
  44. package/dist/errors.d.ts +89 -0
  45. package/dist/errors.js +90 -0
  46. package/dist/errors.js.map +1 -0
  47. package/dist/frozen-verification.d.ts +26 -0
  48. package/dist/frozen-verification.js +67 -0
  49. package/dist/frozen-verification.js.map +1 -0
  50. package/dist/generation.d.ts +34 -0
  51. package/dist/generation.js +81 -0
  52. package/dist/generation.js.map +1 -0
  53. package/dist/groups.d.ts +17 -0
  54. package/dist/groups.js +66 -0
  55. package/dist/groups.js.map +1 -0
  56. package/dist/hashing.d.ts +68 -0
  57. package/dist/hashing.js +146 -0
  58. package/dist/hashing.js.map +1 -0
  59. package/dist/in-place-index.d.ts +43 -0
  60. package/dist/in-place-index.js +272 -0
  61. package/dist/in-place-index.js.map +1 -0
  62. package/dist/index.d.ts +79 -0
  63. package/dist/index.js +64 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/inspection.d.ts +19 -0
  66. package/dist/inspection.js +31 -0
  67. package/dist/inspection.js.map +1 -0
  68. package/dist/internal.d.ts +42 -0
  69. package/dist/internal.js +56 -0
  70. package/dist/internal.js.map +1 -0
  71. package/dist/layout.d.ts +36 -0
  72. package/dist/layout.js +62 -0
  73. package/dist/layout.js.map +1 -0
  74. package/dist/migration.d.ts +197 -0
  75. package/dist/migration.js +592 -0
  76. package/dist/migration.js.map +1 -0
  77. package/dist/model.d.ts +93 -0
  78. package/dist/model.js +313 -0
  79. package/dist/model.js.map +1 -0
  80. package/dist/physical.d.ts +128 -0
  81. package/dist/physical.js +421 -0
  82. package/dist/physical.js.map +1 -0
  83. package/dist/placement.d.ts +157 -0
  84. package/dist/placement.js +651 -0
  85. package/dist/placement.js.map +1 -0
  86. package/dist/provisioning.d.ts +6 -0
  87. package/dist/provisioning.js +45 -0
  88. package/dist/provisioning.js.map +1 -0
  89. package/dist/query.d.ts +68 -0
  90. package/dist/query.js +340 -0
  91. package/dist/query.js.map +1 -0
  92. package/dist/routing.d.ts +25 -0
  93. package/dist/routing.js +35 -0
  94. package/dist/routing.js.map +1 -0
  95. package/dist/schema.d.ts +110 -0
  96. package/dist/schema.js +337 -0
  97. package/dist/schema.js.map +1 -0
  98. package/dist/session.d.ts +195 -0
  99. package/dist/session.js +870 -0
  100. package/dist/session.js.map +1 -0
  101. package/dist/shapes.d.ts +30 -0
  102. package/dist/shapes.js +112 -0
  103. package/dist/shapes.js.map +1 -0
  104. package/dist/staging.d.ts +29 -0
  105. package/dist/staging.js +214 -0
  106. package/dist/staging.js.map +1 -0
  107. package/dist/telemetry.d.ts +468 -0
  108. package/dist/telemetry.js +872 -0
  109. package/dist/telemetry.js.map +1 -0
  110. package/dist/testing/loader.d.ts +38 -0
  111. package/dist/testing/loader.js +86 -0
  112. package/dist/testing/loader.js.map +1 -0
  113. package/dist/testing/memory.d.ts +131 -0
  114. package/dist/testing/memory.js +311 -0
  115. package/dist/testing/memory.js.map +1 -0
  116. package/dist/timestamp.d.ts +20 -0
  117. package/dist/timestamp.js +89 -0
  118. package/dist/timestamp.js.map +1 -0
  119. package/dist/types.d.ts +79 -0
  120. package/dist/types.js +100 -0
  121. package/dist/types.js.map +1 -0
  122. package/dist/verification.d.ts +41 -0
  123. package/dist/verification.js +169 -0
  124. package/dist/verification.js.map +1 -0
  125. package/dist/watermark.d.ts +103 -0
  126. package/dist/watermark.js +170 -0
  127. package/dist/watermark.js.map +1 -0
  128. package/dist/write-fence.d.ts +58 -0
  129. package/dist/write-fence.js +225 -0
  130. package/dist/write-fence.js.map +1 -0
  131. package/package.json +86 -0
@@ -0,0 +1,468 @@
1
+ /**
2
+ * Measuring what the application actually does, without ever seeing what it does it to.
3
+ *
4
+ * This is the input a placement decision is made from, so its shape matters more than its
5
+ * precision. Three constraints shaped everything here.
6
+ *
7
+ * **It carries no values.** A record is keyed by an operation shape, which is assembled from the
8
+ * structure of a call and never sees its arguments. There is no code path by which a customer's row
9
+ * reaches a telemetry record, which is why this file can be read by a client and believed.
10
+ *
11
+ * **It cannot cost anything.** Routing already has a one percent budget for the whole library and
12
+ * recording happens on the same path. So: no string formatting per record, no stack walking, and a
13
+ * histogram rather than a list of samples. There is no lock, and that is not a shortcut - this
14
+ * runtime has one thread per record, so the trade the reference implementation makes (a lock taken
15
+ * only when a shape is first seen) has nothing to buy here.
16
+ *
17
+ * **It cannot fail the caller.** Every entry point goes through `guard`. A bug in an aggregation
18
+ * counter must not take down somebody's request - and the failure is counted, so it is not
19
+ * invisible either.
20
+ *
21
+ * The histogram deserves a word, because it is the one deliberate loss of precision. Latency lands
22
+ * in exponential buckets, so a percentile read out of it is approximate - within one bucket width,
23
+ * which is a factor of two at the extremes. That is ample for the decision it feeds: a planner
24
+ * cares whether a group's reads are microseconds or milliseconds, not whether p99 is 412 or 431
25
+ * microseconds.
26
+ */
27
+ import type { Group } from './groups.js';
28
+ import type { LogicalModel } from './model.js';
29
+ import type { ShapeKind } from './shapes.js';
30
+ /** 1 microsecond to about 17 s, doubling. Enough to tell a cache hit from a full scan. */
31
+ export declare const BUCKET_COUNT = 25;
32
+ export declare const BUCKET_BASE_NS = 1000;
33
+ /** Exponential-bucket histogram. Fixed memory, O(1) record, approximate percentiles. */
34
+ export declare class Histogram {
35
+ readonly buckets: number[];
36
+ count: number;
37
+ total: number;
38
+ /**
39
+ * Put one duration in its bucket. **Integer arithmetic only, and that is the point.**
40
+ *
41
+ * The obvious form is `Math.floor(Math.log2(ns / BUCKET_BASE_NS)) + 1`, which is the same
42
+ * function and the wrong way to compute it in a library that has to agree with another
43
+ * implementation. `log2` is not required by IEEE 754 to be correctly rounded, so two runtimes may
44
+ * differ in the last bit - and one bit at a power-of-two boundary is a different bucket, which is
45
+ * a different p99 for identical traffic, in a number a placement decision is made from.
46
+ *
47
+ * The bit length of the integer quotient is exact everywhere. `Math.clz32` is defined on the
48
+ * 32-bit value, and the quotient is below 2^24 in every case that is not clamped, so the clamp is
49
+ * checked first rather than relying on the coercion.
50
+ *
51
+ * **The vectors cannot see the difference, and that is worth saying rather than implying.**
52
+ * Measured: the logarithm form passes every vector in `telemetry/`, because glibc's `log2` and
53
+ * V8's are both exact at a power of two - the two runtimes we have agree, and the hazard is a
54
+ * *third* libm that does not. A property no output can distinguish on the machines available is
55
+ * not one a vector can hold, so it is held statically: `telemetry.test.ts` refuses a logarithm in
56
+ * this file.
57
+ */
58
+ record(nanoseconds: number): void;
59
+ /**
60
+ * Approximate percentile in milliseconds, or null if nothing was recorded.
61
+ *
62
+ * Returns the *upper* edge of the bucket the percentile falls in. Rounding up rather than
63
+ * interpolating is deliberate: a placement decision made on an optimistic latency figure is the
64
+ * wrong kind of wrong.
65
+ */
66
+ percentileMs(fraction: number): number | null;
67
+ merge(other: Histogram): void;
68
+ }
69
+ /** What a read filtered on: the fields compared by equality and the field a range bounded. */
70
+ export interface ReadPredicates {
71
+ readonly equal: readonly string[];
72
+ readonly ranged: string;
73
+ }
74
+ /** What was observed for one operation shape. No values, by construction. */
75
+ export declare class ShapeStats {
76
+ readonly shapeId: string;
77
+ readonly group: string;
78
+ readonly entity: string;
79
+ readonly kind: ShapeKind;
80
+ calls: number;
81
+ rows: number;
82
+ errors: number;
83
+ readonly latency: Histogram;
84
+ /**
85
+ * Calls by what they filtered on - equality fields and the bounded field (`''` for none) - names
86
+ * only, never values. Two reads of one shape can want different key orders; only an operation
87
+ * that takes a `where` reports this, so writes and point reads leave it empty.
88
+ */
89
+ readonly filtered: Map<string, {
90
+ predicates: ReadPredicates;
91
+ calls: number;
92
+ }>;
93
+ constructor(shapeId: string, group: string, entity: string, kind: ShapeKind);
94
+ record(nanoseconds: number, rows: number, failed: boolean, predicates?: ReadPredicates): void;
95
+ }
96
+ /**
97
+ * What was observed writing one row to one derived copy. No values, by construction.
98
+ *
99
+ * Deliberately **not** a `ShapeStats`, and that is the load-bearing decision. A fan-out is not an
100
+ * operation the application asked for - it is the library keeping a copy current - so recording it
101
+ * as a shape would add a write to the very counters a placement is scored on: `read_write_ratio`
102
+ * would move because a copy exists, and a group with one copy would look twice as write-heavy as
103
+ * the same group without one. It is also not part of `GroupFeatures`: a copy's freshness does not
104
+ * score a placement, it reports the health of one already made.
105
+ */
106
+ export declare class FanOutStats {
107
+ readonly group: string;
108
+ readonly materialization: string;
109
+ writes: number;
110
+ /** Rows that did not reach the copy. Absence, not lateness - see `CopyFreshness`. */
111
+ failures: number;
112
+ readonly latency: Histogram;
113
+ constructor(group: string, materialization: string);
114
+ record(nanoseconds: number, failed: boolean): void;
115
+ }
116
+ /**
117
+ * How far behind one derived copy is, measured.
118
+ *
119
+ * A derived copy in this library is maintained by the fan-out in a write, in the client's own
120
+ * process, straight after the source. There is no asynchronous replication anywhere, so there is no
121
+ * queue to fall behind in. Two things can therefore be true of a copy, and only two: it is **late**
122
+ * by at most the duration of that one write, or the write **failed** and the row is absent rather
123
+ * than late. Both are reported, because a copy missing a thousand rows can have an excellent p99
124
+ * and a client told only the percentile reads "0.9 ms behind" off a copy that is missing yesterday.
125
+ */
126
+ export interface CopyFreshness {
127
+ readonly group: string;
128
+ readonly materialization: string;
129
+ readonly writes: number;
130
+ readonly failures: number;
131
+ readonly lagP50Ms: number | null;
132
+ readonly lagP99Ms: number | null;
133
+ /** Whether every write reached the copy in this window. */
134
+ readonly complete: boolean;
135
+ }
136
+ export declare function copyFreshnessRecord(copy: CopyFreshness): Record<string, unknown>;
137
+ /**
138
+ * The contract between telemetry and the planner.
139
+ *
140
+ * `null` means *unknown*, which is not zero and is treated differently by the planner. Anything
141
+ * unknown also appears in `missing`, so a reader never has to infer absence from a null.
142
+ */
143
+ export interface GroupFeatures {
144
+ /**
145
+ * How many operations were observed. A count, never a value.
146
+ *
147
+ * Present because a planner comparing two groups has to know which one carries the traffic:
148
+ * without it, an idle group and the group serving every request are equally important. It is also
149
+ * evidence about the features themselves - a read/write ratio derived from twelve calls is
150
+ * arithmetic, not a measurement.
151
+ */
152
+ readonly calls: number;
153
+ readonly readWriteRatio: number | null;
154
+ readonly shapeMix: Readonly<Record<string, number>>;
155
+ readonly latencyP50Ms: number | null;
156
+ readonly latencyP99Ms: number | null;
157
+ readonly resultCardinalityP50: number | null;
158
+ readonly resultCardinalityP99: number | null;
159
+ readonly totalBytes: number | null;
160
+ readonly dailyGrowthBytes: number | null;
161
+ readonly indexToTableRatio: number | null;
162
+ readonly pkAccessShare: number | null;
163
+ readonly hasTimeDimension: boolean;
164
+ readonly timeFilteredShare: number | null;
165
+ readonly distinctShapes: number;
166
+ readonly writeBurstiness: number | null;
167
+ readonly errorShare: number | null;
168
+ readonly missing: readonly string[];
169
+ readonly complete: boolean;
170
+ }
171
+ /**
172
+ * The document key for every measured field, and the property that carries it.
173
+ *
174
+ * Two spellings because two things are being named: the **document** is the format, shared with
175
+ * every other implementation and therefore `snake_case`; the **property** is this language's, and a
176
+ * library that reads like transliterated Python is a worse library in TypeScript and no better an
177
+ * SDE one.
178
+ *
179
+ * The reference implementation derives its equivalent from the dataclass at runtime, which is not
180
+ * available here - types are erased before the code runs. So the list is the source and the type is
181
+ * checked against it, which is the same guarantee in the other direction and is enforced by the
182
+ * compiler rather than by a test: see `FIELD_LIST_IS_TOTAL`.
183
+ */
184
+ export declare const MEASURED_FIELDS: readonly [readonly ["calls", "calls"], readonly ["read_write_ratio", "readWriteRatio"], readonly ["shape_mix", "shapeMix"], readonly ["latency_p50_ms", "latencyP50Ms"], readonly ["latency_p99_ms", "latencyP99Ms"], readonly ["result_cardinality_p50", "resultCardinalityP50"], readonly ["result_cardinality_p99", "resultCardinalityP99"], readonly ["total_bytes", "totalBytes"], readonly ["daily_growth_bytes", "dailyGrowthBytes"], readonly ["index_to_table_ratio", "indexToTableRatio"], readonly ["pk_access_share", "pkAccessShare"], readonly ["has_time_dimension", "hasTimeDimension"], readonly ["time_filtered_share", "timeFilteredShare"], readonly ["distinct_shapes", "distinctShapes"], readonly ["write_burstiness", "writeBurstiness"], readonly ["error_share", "errorShare"]];
185
+ type MeasuredProperty = (typeof MEASURED_FIELDS)[number][1];
186
+ type Bookkeeping = 'missing' | 'complete';
187
+ /**
188
+ * A compile-time ratchet in both directions.
189
+ *
190
+ * Add a field to `GroupFeatures` and forget the list, and the second element stops being
191
+ * assignable; put a name in the list that is not a field, and the first does. `tsc` fails either
192
+ * way, which is where this belongs: the control plane's reader had a hand-written field list
193
+ * against a dataclass where every field has a default, so the next field added to the library would
194
+ * have been recorded as *measured* rather than missing. That defect had to be found by mutation.
195
+ * This one cannot exist.
196
+ */
197
+ export declare const FIELD_LIST_IS_TOTAL: [
198
+ MeasuredProperty extends keyof GroupFeatures ? true : never,
199
+ Exclude<keyof GroupFeatures, Bookkeeping> extends MeasuredProperty ? true : never
200
+ ];
201
+ /**
202
+ * The feature vector as the document that crosses the boundary to the control plane.
203
+ *
204
+ * **A field with no value is omitted, and `missing` is what says so.** Emitting a null would work
205
+ * too - the reader treats absent and null alike - but omitting is the honest spelling of "not
206
+ * measured", and it keeps this function from having an opinion about what a null means.
207
+ */
208
+ export declare function featuresRecord(features: GroupFeatures): Record<string, unknown>;
209
+ /** The bucket a write's rows are counted in for `write_burstiness`. */
210
+ export declare const SECOND_NS = 1000000000;
211
+ /** The kinds of read that take a `where` and so report what they filtered on (`filtered_on`). */
212
+ export declare const FILTERED_KINDS: ReadonlySet<string>;
213
+ /**
214
+ * The shortest span a daily growth is projected from.
215
+ *
216
+ * A day projected from a few seconds of writes is a number with no basis: a demonstration writing a
217
+ * hundred rows in two seconds would claim gigabytes a day. An hour is where the projection
218
+ * multiplies a measurement by 24 rather than by thousands.
219
+ */
220
+ export declare const GROWTH_MIN_NS = 3600000000000;
221
+ /** The unit growth is projected to, and how long the recorder keeps a group's storage samples. */
222
+ export declare const DAY_NS = 86400000000000;
223
+ /**
224
+ * One group's bytes on its source materialisation at one moment, from the engine's catalogue.
225
+ *
226
+ * Numbers only. `totalBytes` is everything the group's tables occupy, indexes included;
227
+ * `secondaryIndexBytes` is the part that is indexes other than the one enforcing the key - the part
228
+ * a physical design adds and can remove. `atNs` is the recorder's clock.
229
+ */
230
+ export interface StorageSample {
231
+ readonly group: string;
232
+ readonly atNs: number;
233
+ readonly totalBytes: number;
234
+ readonly secondaryIndexBytes: number;
235
+ }
236
+ /** One group's size on its source materialisation, as the engine's catalogue gave it. */
237
+ export interface StorageSize {
238
+ readonly group: string;
239
+ readonly materialization: string;
240
+ readonly engine: string;
241
+ readonly totalBytes: number;
242
+ readonly secondaryIndexBytes: number;
243
+ }
244
+ /**
245
+ * Why a group's size stayed unknown: its engine's adapter has no catalogue to read, a table the map
246
+ * names does not exist, the catalogue refused the login (ClickHouse without the `system.parts`
247
+ * grant) or the read failed otherwise.
248
+ */
249
+ export declare const STORAGE_UNAVAILABLE: readonly ["failed", "missing_table", "refused", "unsupported"];
250
+ export type StorageUnavailable = (typeof STORAGE_UNAVAILABLE)[number];
251
+ /**
252
+ * What `Session.measureStorage` measured, and why the rest stayed unknown - a class of reason per
253
+ * group, never the engine's message.
254
+ */
255
+ export interface StorageMeasurement {
256
+ readonly sizes: readonly StorageSize[];
257
+ readonly unavailable: Readonly<Record<string, StorageUnavailable>>;
258
+ }
259
+ /**
260
+ * A catalogue size as an exact safe integer, or a refusal. Drivers hand a 64-bit count over as text;
261
+ * a size past 2^53 bytes would round, and a rounded size is a wrong one.
262
+ */
263
+ export declare function exactBytes(value: unknown): number;
264
+ /**
265
+ * One aggregation period, ready to send.
266
+ *
267
+ * `complete` is false when the application could not collect part of the period, or when the buffer
268
+ * dropped windows. A window that is not complete is still sent - the planner needs to know traffic
269
+ * existed - but it may not be used to justify a migration.
270
+ */
271
+ export interface Window {
272
+ readonly modelVersion: string;
273
+ readonly startedNs: number;
274
+ readonly endedNs: number;
275
+ readonly shapes: readonly ShapeStats[];
276
+ readonly complete: boolean;
277
+ readonly droppedWindows: number;
278
+ /**
279
+ * What the fan-out to each derived copy did. Empty when the group has no copy, which is the
280
+ * ordinary case - a copy exists during a migration and while a derived materialisation is in the
281
+ * map, not otherwise.
282
+ */
283
+ readonly fanned: readonly FanOutStats[];
284
+ /**
285
+ * The storage samples taken during this window - between the roll that opened it and the one
286
+ * that closed it, by the recorder's own state rather than by comparing clock readings, so a
287
+ * sample taken at the instant of a roll belongs to exactly one window.
288
+ */
289
+ readonly storage: readonly StorageSample[];
290
+ /**
291
+ * Every storage sample the recorder kept when this window closed: this window's and earlier ones
292
+ * up to a day old, which a daily growth is projected against.
293
+ */
294
+ readonly storageHistory: readonly StorageSample[];
295
+ /** Rows written by successful writes, per group, per whole second of the window from its start. */
296
+ readonly writeSeconds: ReadonlyMap<string, ReadonlyMap<number, number>>;
297
+ }
298
+ /**
299
+ * How far behind each of this group's derived copies ran, sorted by materialisation.
300
+ *
301
+ * Read out of the histogram rather than stored, like every other percentile here. A stored
302
+ * percentile is a second copy of a fact that changes when the samples do.
303
+ */
304
+ export declare function windowCopies(window: Window, group: string): CopyFreshness[];
305
+ /**
306
+ * What each operation shape of one group measured, in the model's enumeration order.
307
+ *
308
+ * The evidence a physical design can point at. A group's features say that 62% of its calls were
309
+ * range reads; only this says *which field* they ranged over, which is the fact a key order or a
310
+ * partition is chosen from. Still no values: a shape is the structure of a call, and the fields it
311
+ * names are the model's field names (digests, when the client hashes them).
312
+ *
313
+ * The descriptor - entity, kind, fields, target - comes from the model's own enumeration by
314
+ * identifier, never from the recorder, and `target` appears only on a relation walk. A record for an
315
+ * identifier the model does not enumerate is refused rather than described by guesswork. Every
316
+ * number is an integer count or a bucket edge divided by a million, like the rest of the document.
317
+ */
318
+ export declare function windowShapes(window: Window, model: LogicalModel, group: string): Record<string, unknown>[];
319
+ export interface FeatureOptions {
320
+ readonly hasTimeDimension?: boolean;
321
+ /**
322
+ * Per entity, the fields of a time type (`timeFields`), which `time_filtered_share` is counted
323
+ * against. Without it that share stays unknown: which fields are times is a fact about the model,
324
+ * and the window does not guess it.
325
+ */
326
+ readonly timeFields?: ReadonlyMap<string, ReadonlySet<string>>;
327
+ /**
328
+ * A range read's shape identifier and the field its shape ranges over, which settles a range read
329
+ * whose filters were not reported.
330
+ */
331
+ readonly rangeFields?: ReadonlyMap<string, string>;
332
+ }
333
+ /** Fold this window's records for one group into the planner's feature vector. */
334
+ export declare function windowFeatures(window: Window, group: string, options?: FeatureOptions): GroupFeatures;
335
+ /**
336
+ * This window as the document the control plane reads. Numbers, never rows.
337
+ *
338
+ * The model is required and it is checked. Only one fact is read from it - whether a group carries a
339
+ * time dimension, which is decided by declared *type* and never by a field's name - but a window
340
+ * serialised against the wrong model would attach that fact to the wrong groups and claim
341
+ * `has_time_dimension: false` for a group that has one. False is a claim; a measurement this
342
+ * library cannot make has to be absent.
343
+ *
344
+ * **This document is deliberately not canonical, and that needs saying because section 1 of the
345
+ * format contract rejects floating point outright.** Its reason is that a float's textual form
346
+ * differs between languages, and almost every number here is a float. This document is not signed,
347
+ * not hashed and never compared for equality, so the rule it breaks does not apply - but the thing
348
+ * that makes the `telemetry/` family checkable is narrower and worth stating: every number here is
349
+ * either **a ratio of two integers** or **a bucket edge divided by a million**, and IEEE 754
350
+ * requires division to be correctly rounded. So two languages compute the same double from the same
351
+ * traffic even where they would print it differently, which is why those vectors compare numbers
352
+ * rather than bytes.
353
+ */
354
+ export declare function windowRecord(window: Window, model: LogicalModel): Record<string, unknown>;
355
+ /**
356
+ * Does any entity in this group carry a time dimension?
357
+ *
358
+ * Decided by the declared type and never by the field's name. A name is not evidence: `created_at`
359
+ * typed as a string is a string, and treating it as a timestamp would have the planner recommend
360
+ * time partitioning on a column no engine can range-scan usefully. And a client may hash identifier
361
+ * names so that we never see them - a derivation that read names would silently produce different
362
+ * answers with hashing on and off, which is the property the hashed-model vectors forbid.
363
+ */
364
+ export declare function hasTimeDimension(model: LogicalModel, group: Group): boolean;
365
+ /**
366
+ * Each entity of this group and its fields of a time type - by type, never by name.
367
+ *
368
+ * What `time_filtered_share` counts against, for the reasons `hasTimeDimension` gives: a
369
+ * `created_at` typed as a string is not a time, and a hashed model must answer the same.
370
+ */
371
+ export declare function timeFields(model: LogicalModel, group: Group): Map<string, Set<string>>;
372
+ export interface StorageOptions {
373
+ readonly group: string;
374
+ readonly totalBytes: number;
375
+ readonly secondaryIndexBytes: number;
376
+ }
377
+ export interface RecordOptions {
378
+ readonly shapeId: string;
379
+ readonly group: string;
380
+ readonly entity: string;
381
+ readonly kind: ShapeKind;
382
+ readonly nanoseconds: number;
383
+ readonly rows?: number;
384
+ readonly failed?: boolean;
385
+ /**
386
+ * For an operation that takes a `where`: the fields it compared by equality. Absent for one that
387
+ * does not filter at all, and then nothing about filters is recorded.
388
+ */
389
+ readonly equal?: readonly string[];
390
+ /** The field a range bounded, for a filtered read. */
391
+ readonly ranged?: string | null;
392
+ }
393
+ export interface FanOutOptions {
394
+ readonly group: string;
395
+ readonly materialization: string;
396
+ readonly nanoseconds: number;
397
+ readonly failed?: boolean;
398
+ }
399
+ /**
400
+ * Accumulates records, rolls windows, and drops telemetry rather than anything else.
401
+ *
402
+ * No lock, because this runtime does not need one - and that is worth stating rather than leaving
403
+ * as an absence. The reference implementation takes one only when a shape is first seen or a window
404
+ * rolls, and accepts that two threads racing on the same shape can lose a call from a count. Here
405
+ * there is nothing to race: the cost this class is allowed is zero either way.
406
+ */
407
+ export declare class Recorder {
408
+ private readonly modelVersion;
409
+ private readonly maxWindows;
410
+ private readonly clock;
411
+ private current;
412
+ private fanned;
413
+ private writes;
414
+ private storage;
415
+ private storageWindow;
416
+ private startedNs;
417
+ private windows;
418
+ private dropped;
419
+ private incomplete;
420
+ /**
421
+ * `clock` returns nanoseconds and defaults to a monotonic one; tests and the conformance vectors
422
+ * pass their own, because a write's second and a sample's age are read from it.
423
+ */
424
+ constructor(modelVersion: string, maxWindows?: number, clock?: () => number);
425
+ /** Record one operation. Never throws. */
426
+ record(options: RecordOptions): void;
427
+ /**
428
+ * Record one group's size, as the engine's catalogue reported it. Never throws.
429
+ *
430
+ * `Session.measureStorage` calls this for every group it could measure. A sample is kept for a
431
+ * day, across windows, because growth is a property of time rather than of one window; a sample
432
+ * that is not two non-negative safe integers with the index part inside the total is dropped,
433
+ * never guessed into shape.
434
+ */
435
+ recordStorage(options: StorageOptions): void;
436
+ /**
437
+ * Record one write to one derived copy. Never throws.
438
+ *
439
+ * A separate entry point from `record` rather than a shape kind, because a fan-out is not an
440
+ * operation the application asked for - see `FanOutStats`.
441
+ */
442
+ recordFanOut(options: FanOutOptions): void;
443
+ /**
444
+ * Close the current period and queue it.
445
+ *
446
+ * Returns the window, or undefined if nothing was recorded in it.
447
+ */
448
+ roll(): Window | undefined;
449
+ pending(): readonly Window[];
450
+ /**
451
+ * Called by the application when part of the current period was not recorded.
452
+ *
453
+ * The flag travels in `Window.complete` and the planner reads it, because a window missing a
454
+ * slice of the traffic must not be scored as though it were the whole period. Nothing here
455
+ * decides when that happened: the application collects these windows and hands them over, and
456
+ * only it knows whether a collection was skipped.
457
+ */
458
+ markIncomplete(): void;
459
+ /**
460
+ * Drop the oldest `count` windows once the application has taken them.
461
+ *
462
+ * The delivering party is the client's own process, and there is no other. This library never
463
+ * opens a connection to us - the buffer is read with `pending`, written wherever the application
464
+ * writes it, and that file is what we are handed.
465
+ */
466
+ acknowledge(count: number): void;
467
+ }
468
+ export {};