@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.
- package/LICENSE +201 -0
- package/NOTICE +13 -0
- package/README.md +153 -0
- package/bin/weather.mjs +32 -0
- package/dist/_usage.d.ts +30 -0
- package/dist/_usage.js +194 -0
- package/dist/_usage.js.map +1 -0
- package/dist/bulk.d.ts +9 -0
- package/dist/bulk.js +83 -0
- package/dist/bulk.js.map +1 -0
- package/dist/canonical.d.ts +46 -0
- package/dist/canonical.js +150 -0
- package/dist/canonical.js.map +1 -0
- package/dist/capabilities.d.ts +48 -0
- package/dist/capabilities.js +62 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cutover.d.ts +36 -0
- package/dist/cutover.js +219 -0
- package/dist/cutover.js.map +1 -0
- package/dist/demo/model.d.ts +28 -0
- package/dist/demo/model.js +40 -0
- package/dist/demo/model.js.map +1 -0
- package/dist/demo/project.d.ts +19 -0
- package/dist/demo/project.js +128 -0
- package/dist/demo/project.js.map +1 -0
- package/dist/demo/weather.d.ts +73 -0
- package/dist/demo/weather.js +334 -0
- package/dist/demo/weather.js.map +1 -0
- package/dist/engines/_clickhouse-connection.d.ts +17 -0
- package/dist/engines/_clickhouse-connection.js +182 -0
- package/dist/engines/_clickhouse-connection.js.map +1 -0
- package/dist/engines/_tls-peer-identity.d.ts +2 -0
- package/dist/engines/_tls-peer-identity.js +23 -0
- package/dist/engines/_tls-peer-identity.js.map +1 -0
- package/dist/engines/_write-fences.d.ts +51 -0
- package/dist/engines/_write-fences.js +189 -0
- package/dist/engines/_write-fences.js.map +1 -0
- package/dist/engines/clickhouse.d.ts +193 -0
- package/dist/engines/clickhouse.js +899 -0
- package/dist/engines/clickhouse.js.map +1 -0
- package/dist/engines/postgres.d.ts +293 -0
- package/dist/engines/postgres.js +981 -0
- package/dist/engines/postgres.js.map +1 -0
- package/dist/errors.d.ts +89 -0
- package/dist/errors.js +90 -0
- package/dist/errors.js.map +1 -0
- package/dist/frozen-verification.d.ts +26 -0
- package/dist/frozen-verification.js +67 -0
- package/dist/frozen-verification.js.map +1 -0
- package/dist/generation.d.ts +34 -0
- package/dist/generation.js +81 -0
- package/dist/generation.js.map +1 -0
- package/dist/groups.d.ts +17 -0
- package/dist/groups.js +66 -0
- package/dist/groups.js.map +1 -0
- package/dist/hashing.d.ts +68 -0
- package/dist/hashing.js +146 -0
- package/dist/hashing.js.map +1 -0
- package/dist/in-place-index.d.ts +43 -0
- package/dist/in-place-index.js +272 -0
- package/dist/in-place-index.js.map +1 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection.d.ts +19 -0
- package/dist/inspection.js +31 -0
- package/dist/inspection.js.map +1 -0
- package/dist/internal.d.ts +42 -0
- package/dist/internal.js +56 -0
- package/dist/internal.js.map +1 -0
- package/dist/layout.d.ts +36 -0
- package/dist/layout.js +62 -0
- package/dist/layout.js.map +1 -0
- package/dist/migration.d.ts +197 -0
- package/dist/migration.js +592 -0
- package/dist/migration.js.map +1 -0
- package/dist/model.d.ts +93 -0
- package/dist/model.js +313 -0
- package/dist/model.js.map +1 -0
- package/dist/physical.d.ts +128 -0
- package/dist/physical.js +421 -0
- package/dist/physical.js.map +1 -0
- package/dist/placement.d.ts +157 -0
- package/dist/placement.js +651 -0
- package/dist/placement.js.map +1 -0
- package/dist/provisioning.d.ts +6 -0
- package/dist/provisioning.js +45 -0
- package/dist/provisioning.js.map +1 -0
- package/dist/query.d.ts +68 -0
- package/dist/query.js +340 -0
- package/dist/query.js.map +1 -0
- package/dist/routing.d.ts +25 -0
- package/dist/routing.js +35 -0
- package/dist/routing.js.map +1 -0
- package/dist/schema.d.ts +110 -0
- package/dist/schema.js +337 -0
- package/dist/schema.js.map +1 -0
- package/dist/session.d.ts +195 -0
- package/dist/session.js +870 -0
- package/dist/session.js.map +1 -0
- package/dist/shapes.d.ts +30 -0
- package/dist/shapes.js +112 -0
- package/dist/shapes.js.map +1 -0
- package/dist/staging.d.ts +29 -0
- package/dist/staging.js +214 -0
- package/dist/staging.js.map +1 -0
- package/dist/telemetry.d.ts +468 -0
- package/dist/telemetry.js +872 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/testing/loader.d.ts +38 -0
- package/dist/testing/loader.js +86 -0
- package/dist/testing/loader.js.map +1 -0
- package/dist/testing/memory.d.ts +131 -0
- package/dist/testing/memory.js +311 -0
- package/dist/testing/memory.js.map +1 -0
- package/dist/timestamp.d.ts +20 -0
- package/dist/timestamp.js +89 -0
- package/dist/timestamp.js.map +1 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.js +100 -0
- package/dist/types.js.map +1 -0
- package/dist/verification.d.ts +41 -0
- package/dist/verification.js +169 -0
- package/dist/verification.js.map +1 -0
- package/dist/watermark.d.ts +103 -0
- package/dist/watermark.js +170 -0
- package/dist/watermark.js.map +1 -0
- package/dist/write-fence.d.ts +58 -0
- package/dist/write-fence.js +225 -0
- package/dist/write-fence.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { Group } from './groups.js';
|
|
2
|
+
import type { NameMap } from './hashing.js';
|
|
3
|
+
import type { LogicalModel } from './model.js';
|
|
4
|
+
import type { NumericSummary, ReadOptions, ScanPage } from './query.js';
|
|
5
|
+
import type { PhysicalLayout, PlacementMap } from './placement.js';
|
|
6
|
+
import type { Recorder, StorageMeasurement } from './telemetry.js';
|
|
7
|
+
import type { WatermarkCheck } from './watermark.js';
|
|
8
|
+
import type { PhysicalFinding } from './physical.js';
|
|
9
|
+
export type Row = Record<string, unknown>;
|
|
10
|
+
/** What an adapter has to offer for a session to route to it. */
|
|
11
|
+
export interface Engine {
|
|
12
|
+
readonly dialect: string;
|
|
13
|
+
/**
|
|
14
|
+
* Create what is missing and return how existing tables differ from the declared physical
|
|
15
|
+
* design. `void` remains acceptable from an adapter written before findings existed.
|
|
16
|
+
*/
|
|
17
|
+
ensureSchema(layout: PhysicalLayout, options: {
|
|
18
|
+
readonly keys: Readonly<Record<string, readonly string[]>>;
|
|
19
|
+
}): Promise<readonly PhysicalFinding[] | void>;
|
|
20
|
+
insert(table: string, values: Readonly<Row>): Promise<void>;
|
|
21
|
+
get(table: string, key: Readonly<Row>): Promise<Row | null>;
|
|
22
|
+
/**
|
|
23
|
+
* One engine, one transaction, that engine's semantics.
|
|
24
|
+
*
|
|
25
|
+
* A callback rather than a pair of begin/commit calls, so a caller cannot leave one open. There
|
|
26
|
+
* is no distributed transaction here and there will not be one: a client needing two entities to
|
|
27
|
+
* commit together declares that, the planner puts them in the same group and therefore the same
|
|
28
|
+
* engine, and the requirement turns into a placement constraint instead of a two-phase commit.
|
|
29
|
+
*/
|
|
30
|
+
transaction<T>(body: () => Promise<T>): Promise<T>;
|
|
31
|
+
}
|
|
32
|
+
export type ScanOptions = Omit<ReadOptions, 'paginate'> & {
|
|
33
|
+
readonly fresh?: boolean;
|
|
34
|
+
};
|
|
35
|
+
export type CountOptions = Pick<ScanOptions, 'where' | 'bounds' | 'fresh'>;
|
|
36
|
+
export type SummaryOptions = CountOptions & {
|
|
37
|
+
readonly meanScale?: number;
|
|
38
|
+
};
|
|
39
|
+
export interface SessionOptions {
|
|
40
|
+
readonly projectId?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Telemetry is optional and off by default. A library that starts measuring the moment it is
|
|
43
|
+
* imported is a library people are right to be suspicious of; measurement begins when a recorder
|
|
44
|
+
* is handed in, which is a visible line in the client's code.
|
|
45
|
+
*/
|
|
46
|
+
readonly recorder?: Recorder;
|
|
47
|
+
/** The name map from {@link hashIdentifiers}, when the client hashes their identifiers. */
|
|
48
|
+
readonly names?: NameMap;
|
|
49
|
+
}
|
|
50
|
+
export interface ManagedEngine extends Engine {
|
|
51
|
+
connect(): Promise<void>;
|
|
52
|
+
close(): Promise<void> | void;
|
|
53
|
+
}
|
|
54
|
+
export declare class Session {
|
|
55
|
+
readonly model: LogicalModel;
|
|
56
|
+
readonly placement: PlacementMap;
|
|
57
|
+
private readonly engines;
|
|
58
|
+
private readonly recorder;
|
|
59
|
+
private readonly names;
|
|
60
|
+
/**
|
|
61
|
+
* Whether an older map could be loaded over this one, and why.
|
|
62
|
+
*
|
|
63
|
+
* Public because a protection whose state cannot be read is a protection taken on trust. It has
|
|
64
|
+
* three values and the middle one matters: `enforced`, `unavailable` - no engine in this map can
|
|
65
|
+
* keep the bookkeeping - and `not_applicable` for an unsigned map, which is the client's own
|
|
66
|
+
* document.
|
|
67
|
+
*/
|
|
68
|
+
readonly rollbackProtection: WatermarkCheck;
|
|
69
|
+
readonly projectId: string | undefined;
|
|
70
|
+
private readonly shapes;
|
|
71
|
+
private readonly inputFields;
|
|
72
|
+
private readonly groups;
|
|
73
|
+
private readonly reverseFields;
|
|
74
|
+
private readonly declared;
|
|
75
|
+
private inWriteTransaction;
|
|
76
|
+
private deferred;
|
|
77
|
+
private readonly usage;
|
|
78
|
+
private ownedEngines;
|
|
79
|
+
private closing;
|
|
80
|
+
private physicalFindings;
|
|
81
|
+
private constructor();
|
|
82
|
+
/**
|
|
83
|
+
* Open a session, refusing a map this set of engines cannot serve and one that goes backwards.
|
|
84
|
+
*
|
|
85
|
+
* Both checks are here rather than in a method somebody has to remember to call, and here rather
|
|
86
|
+
* than in `ensureSchema`, which a deployment past its first release skips. A rolled-back map file
|
|
87
|
+
* is read at process start, so the check has to be on the path every start takes.
|
|
88
|
+
*/
|
|
89
|
+
static open(model: LogicalModel, placement: PlacementMap, engines: Readonly<Record<string, Engine>>, options?: SessionOptions): Promise<Session>;
|
|
90
|
+
/** Create and own fresh adapters, including cleanup when only part of startup succeeded. */
|
|
91
|
+
static connect(model: LogicalModel, placement: PlacementMap, factories: Readonly<Record<string, () => ManagedEngine | Promise<ManagedEngine>>>, options?: SessionOptions): Promise<Session>;
|
|
92
|
+
/** Close only adapters created by Session.connect; borrowed adapters remain the caller's. */
|
|
93
|
+
close(): Promise<void>;
|
|
94
|
+
/** The client's entity name, as the model knows it. */
|
|
95
|
+
private entityName;
|
|
96
|
+
private fieldsIn;
|
|
97
|
+
private fieldsOut;
|
|
98
|
+
private clientNames;
|
|
99
|
+
/**
|
|
100
|
+
* The adapter registered under this name, refusing an unknown one.
|
|
101
|
+
*
|
|
102
|
+
* Exposed for the migration module, which needs all three of what a session holds and is
|
|
103
|
+
* deliberately a set of free functions rather than methods here: a call that copies a table for
|
|
104
|
+
* an hour has no business sitting in autocomplete next to `save`. Reaching into a private field
|
|
105
|
+
* from a sibling module would have worked and would have made this class a friend of that one,
|
|
106
|
+
* which is a worse arrangement than admitting what a session holds.
|
|
107
|
+
*/
|
|
108
|
+
engineNamed(name: string): Engine;
|
|
109
|
+
/** The adapters, by the names the map uses. A copy; the session keeps its own. */
|
|
110
|
+
engineNames(): readonly string[];
|
|
111
|
+
groupOf(entity: string): Group;
|
|
112
|
+
private shapeFor;
|
|
113
|
+
private target;
|
|
114
|
+
/**
|
|
115
|
+
* How existing tables differ from the physical design the map declares, if at all.
|
|
116
|
+
*
|
|
117
|
+
* Reported, never refused, because the difference is performance: a table whose sort key,
|
|
118
|
+
* partition or index is not the declared one still stores and returns exactly the same rows, and
|
|
119
|
+
* turning that into an outage would make this library the thing that broke production
|
|
120
|
+
* (requirement 3.6). `prepareSchema` refuses the same differences, which is where a person can
|
|
121
|
+
* act on them. This runtime has no log channel, so the property is the whole report.
|
|
122
|
+
*/
|
|
123
|
+
get physical(): readonly PhysicalFinding[];
|
|
124
|
+
/** Create what each engine is missing for the groups placed in it. */
|
|
125
|
+
ensureSchema(): Promise<void>;
|
|
126
|
+
/** Whether an engine in this map imposes its own schema, so `ensureSchema` sends it nothing. */
|
|
127
|
+
fixedSchemaEngines(): readonly string[];
|
|
128
|
+
save(entity: string, values: Readonly<Row>): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Measure each group's size on its source materialisation, from the engine's catalogue.
|
|
131
|
+
*
|
|
132
|
+
* One catalogue statement per engine, for every table of every group whose source is on it. Only
|
|
133
|
+
* the source counts: a copy a staging is still building is not the group's size. Each size is
|
|
134
|
+
* recorded into this session's recorder, when it has one, for the window's `total_bytes`,
|
|
135
|
+
* `index_to_table_ratio` and `daily_growth_bytes`.
|
|
136
|
+
*
|
|
137
|
+
* **It never throws because an engine could not answer.** An adapter with no catalogue, a refused
|
|
138
|
+
* read, a failed one or a table the map names that does not exist leaves that group's size
|
|
139
|
+
* unknown, with the class of the reason in `unavailable`. Using a closed session, or an adapter
|
|
140
|
+
* another operation owns, still throws, as every method of a session does.
|
|
141
|
+
*/
|
|
142
|
+
measureStorage(): Promise<StorageMeasurement>;
|
|
143
|
+
/** One bounded native insert. No splitting or retry; see docs/bulk-writes.md. */
|
|
144
|
+
saveMany(entity: string, rows: readonly Readonly<Row>[]): Promise<void>;
|
|
145
|
+
get(entity: string, key: Readonly<Row>, options?: {
|
|
146
|
+
fresh?: boolean;
|
|
147
|
+
}): Promise<Row | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Write the row to every `also_write` copy of the group. Additionally, never authoritatively.
|
|
150
|
+
*
|
|
151
|
+
* **A failure here does not interrupt the client's operation.** The row is in the source, which is
|
|
152
|
+
* the copy that counts, and turning a migration into an application outage would make the safest
|
|
153
|
+
* thing this product does the most dangerous. So the divergence is recorded and `verify` is the
|
|
154
|
+
* gate that refuses to switch reads while any of them remain.
|
|
155
|
+
*
|
|
156
|
+
* Inside a write transaction the fan-out is **deferred to commit** rather than skipped or done
|
|
157
|
+
* inline, and each of those three was considered. Inline is wrong: the target is a different
|
|
158
|
+
* engine, so it is outside the source's transaction, and a rolled-back row would exist in the
|
|
159
|
+
* copy - which after the switch is a row the client explicitly undid, readable. Skipping is wrong
|
|
160
|
+
* for a quieter reason: those rows are above the backfill marker, so nothing else copies them,
|
|
161
|
+
* and `verify`'s tail check would refuse the migration of every group that uses a transaction.
|
|
162
|
+
*/
|
|
163
|
+
private fanOut;
|
|
164
|
+
/**
|
|
165
|
+
* Write one row to one copy, measure how long the copy was behind, and never throw.
|
|
166
|
+
*
|
|
167
|
+
* `queuedNs` is when the row was handed to the fan-out, so the interval measured is the whole
|
|
168
|
+
* time the copy did not have a row the source did. Outside a transaction that is the duration of
|
|
169
|
+
* this write; inside one it also includes the rest of the transaction, which **overstates** the
|
|
170
|
+
* staleness - the safe direction for a bound somebody checks a budget against.
|
|
171
|
+
*/
|
|
172
|
+
private replayOne;
|
|
173
|
+
private prepareRead;
|
|
174
|
+
private readProjection;
|
|
175
|
+
/** One bounded page in logical order, independently of migration checkpoints. */
|
|
176
|
+
scan(entity: string, options?: ScanOptions): Promise<ScanPage>;
|
|
177
|
+
/** An exact count; the result value itself is never telemetry. */
|
|
178
|
+
count(entity: string, options?: CountOptions): Promise<bigint>;
|
|
179
|
+
summarize(entity: string, field: string, options?: SummaryOptions): Promise<NumericSummary>;
|
|
180
|
+
private observe;
|
|
181
|
+
/**
|
|
182
|
+
* Run `body` inside a transaction covering the given entities.
|
|
183
|
+
*
|
|
184
|
+
* They must share a colocation group, because a transaction is one engine's transaction. If they
|
|
185
|
+
* do not, this throws before anything is opened and names the fix: declare the atomicity, and the
|
|
186
|
+
* planner will colocate them.
|
|
187
|
+
*
|
|
188
|
+
* Called with no entities it covers the whole model, which is only legal when the model has one
|
|
189
|
+
* group. That is not a convenience for small models so much as a refusal to let a two-group model
|
|
190
|
+
* quietly get a transaction that only covers half of what the caller meant.
|
|
191
|
+
*/
|
|
192
|
+
transaction<T>(entities: readonly string[], body: (session: Session) => Promise<T>): Promise<T>;
|
|
193
|
+
}
|
|
194
|
+
/** The table this layout gives an entity, refusing a map that places a group it cannot name. */
|
|
195
|
+
export declare function tableFor(layout: PhysicalLayout, entity: string): string;
|