@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,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;