@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
package/dist/bulk.js ADDED
@@ -0,0 +1,83 @@
1
+ /** Bounded application inserts; deliberately separate from idempotent migration copies. */
2
+ import { compareCodePoints } from './canonical.js';
3
+ import { BulkWriteRefused } from './errors.js';
4
+ import { Timestamp } from './timestamp.js';
5
+ export const MAX_BATCH_ROWS = 1000;
6
+ export const MAX_BATCH_VALUES = 60_000;
7
+ export function bulkWriter(engine) {
8
+ if (!('insertMany' in engine) || typeof engine.insertMany !== 'function') {
9
+ throw new BulkWriteRefused('this adapter does not support bulk writes (insertMany)');
10
+ }
11
+ return engine;
12
+ }
13
+ export function batchColumns(rows, extraColumns = 0) {
14
+ if (!Array.isArray(rows))
15
+ throw new BulkWriteRefused('a batch must be an array of row objects');
16
+ if (rows.length > MAX_BATCH_ROWS) {
17
+ throw new BulkWriteRefused(`a batch may contain at most ${MAX_BATCH_ROWS} rows`);
18
+ }
19
+ let columns;
20
+ for (const row of rows) {
21
+ if (row === null || typeof row !== 'object' || Array.isArray(row) ||
22
+ (Object.getPrototypeOf(row) !== Object.prototype && Object.getPrototypeOf(row) !== null)) {
23
+ throw new BulkWriteRefused('each batch row must be a nonempty object with string fields');
24
+ }
25
+ const here = Object.keys(row).sort(compareCodePoints);
26
+ if (here.length === 0 || Reflect.ownKeys(row).length !== here.length) {
27
+ throw new BulkWriteRefused('each batch row must be a nonempty object with string fields');
28
+ }
29
+ if (columns === undefined) {
30
+ columns = here;
31
+ if (rows.length * (columns.length + extraColumns) > MAX_BATCH_VALUES) {
32
+ throw new BulkWriteRefused(`a batch may contain at most ${MAX_BATCH_VALUES} values, including generation`);
33
+ }
34
+ }
35
+ else if (here.length !== columns.length || columns.some((field, i) => field !== here[i])) {
36
+ throw new BulkWriteRefused('all batch rows must have the same fields');
37
+ }
38
+ }
39
+ return columns ?? [];
40
+ }
41
+ function snapshot(value, active) {
42
+ if (value === null || ['string', 'boolean', 'number', 'bigint'].includes(typeof value))
43
+ return value;
44
+ if (value instanceof Timestamp)
45
+ return value; // Immutable; structuredClone would erase its type.
46
+ if (value instanceof Date)
47
+ return new Date(value.getTime());
48
+ if (Buffer.isBuffer(value))
49
+ return Buffer.from(value);
50
+ if (value instanceof Uint8Array)
51
+ return new Uint8Array(value);
52
+ if (typeof value !== 'object' || value === null) {
53
+ throw new BulkWriteRefused('batch values must use the SDK scalar types or JSON containers');
54
+ }
55
+ if (active.has(value))
56
+ throw new BulkWriteRefused('batch values must not contain cycles');
57
+ active.add(value);
58
+ try {
59
+ if (Array.isArray(value))
60
+ return Array.from(value, (item) => snapshot(item, active));
61
+ if (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null) {
62
+ if (Reflect.ownKeys(value).length !== Object.keys(value).length) {
63
+ throw new BulkWriteRefused('JSON containers must have enumerable string keys');
64
+ }
65
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, active)]));
66
+ }
67
+ throw new BulkWriteRefused('batch values must use the SDK scalar types or JSON containers');
68
+ }
69
+ finally {
70
+ active.delete(value);
71
+ }
72
+ }
73
+ export function snapshotRows(rows) {
74
+ try {
75
+ return rows.map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, snapshot(value, new Set())])));
76
+ }
77
+ catch (error) {
78
+ if (error instanceof RangeError)
79
+ throw new BulkWriteRefused('batch values are nested too deeply');
80
+ throw error;
81
+ }
82
+ }
83
+ //# sourceMappingURL=bulk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk.js","sourceRoot":"","sources":["../src/bulk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Canonical encoding: the one place where the cross-language contract lives.
3
+ *
4
+ * This is a port of the rules in `docs/format-contract.md`, not a port of the Python file. That
5
+ * distinction matters: if this were written by translating Python line by line it would inherit
6
+ * whatever Python happens to do, and the point of a second implementation is to find out where the
7
+ * document said "what Python does" instead of something language-neutral.
8
+ *
9
+ * It found one, immediately, and it is the reason this file does not use `Array.prototype.sort`.
10
+ *
11
+ * JavaScript compares strings by UTF-16 code unit. The contract requires code point order. For
12
+ * everything in the Basic Multilingual Plane those are the same, so the difference is invisible in
13
+ * any test written with Latin or even CJK identifiers. It stops being invisible above U+FFFF: an
14
+ * astral character is stored as a surrogate pair starting at 0xD800, so UTF-16 order places every
15
+ * emoji and every CJK extension character *before* U+E000, while code point order places them after.
16
+ * A model with one such field name would hash differently in Python and in TypeScript, the control
17
+ * plane would see two models, and nothing would fail until half a fleet was writing to the wrong
18
+ * tables.
19
+ *
20
+ * So `compareCodePoints` below exists, and `conformance/vectors/model/004-astral-identifier` exists
21
+ * to make sure nobody ever replaces it with `.sort()`.
22
+ */
23
+ /** A value cannot be encoded canonically. */
24
+ export declare class CanonicalError extends Error {
25
+ readonly name = "CanonicalError";
26
+ }
27
+ export type Canonical = null | boolean | number | string | readonly Canonical[] | {
28
+ readonly [key: string]: Canonical;
29
+ };
30
+ /**
31
+ * Compare two strings by Unicode code point.
32
+ *
33
+ * Not `a < b`, which is UTF-16 code unit order. See the note at the top of this file: the two orders
34
+ * disagree for anything above U+FFFF, and that disagreement would be a hash mismatch between
35
+ * languages rather than a visible bug.
36
+ */
37
+ export declare function compareCodePoints(a: string, b: string): number;
38
+ /** Canonical form as text. Prefer {@link canonicalBytes} for hashing. */
39
+ export declare function canonicalString(value: unknown): string;
40
+ /** Canonical form as UTF-8 bytes. This is what gets hashed and what the vectors compare. */
41
+ export declare function canonicalBytes(value: unknown): Buffer;
42
+ /**
43
+ * The identifier form used for `model_version` and `shape.id`: lowercase hex, first eight bytes of
44
+ * SHA-256 over the canonical bytes.
45
+ */
46
+ export declare function digest16(value: unknown): string;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Canonical encoding: the one place where the cross-language contract lives.
3
+ *
4
+ * This is a port of the rules in `docs/format-contract.md`, not a port of the Python file. That
5
+ * distinction matters: if this were written by translating Python line by line it would inherit
6
+ * whatever Python happens to do, and the point of a second implementation is to find out where the
7
+ * document said "what Python does" instead of something language-neutral.
8
+ *
9
+ * It found one, immediately, and it is the reason this file does not use `Array.prototype.sort`.
10
+ *
11
+ * JavaScript compares strings by UTF-16 code unit. The contract requires code point order. For
12
+ * everything in the Basic Multilingual Plane those are the same, so the difference is invisible in
13
+ * any test written with Latin or even CJK identifiers. It stops being invisible above U+FFFF: an
14
+ * astral character is stored as a surrogate pair starting at 0xD800, so UTF-16 order places every
15
+ * emoji and every CJK extension character *before* U+E000, while code point order places them after.
16
+ * A model with one such field name would hash differently in Python and in TypeScript, the control
17
+ * plane would see two models, and nothing would fail until half a fleet was writing to the wrong
18
+ * tables.
19
+ *
20
+ * So `compareCodePoints` below exists, and `conformance/vectors/model/004-astral-identifier` exists
21
+ * to make sure nobody ever replaces it with `.sort()`.
22
+ */
23
+ import { createHash } from 'node:crypto';
24
+ /** A value cannot be encoded canonically. */
25
+ export class CanonicalError extends Error {
26
+ name = 'CanonicalError';
27
+ }
28
+ const SHORT_ESCAPES = {
29
+ 0x08: '\\b',
30
+ 0x09: '\\t',
31
+ 0x0a: '\\n',
32
+ 0x0c: '\\f',
33
+ 0x0d: '\\r',
34
+ 0x22: '\\"',
35
+ 0x5c: '\\\\',
36
+ };
37
+ /**
38
+ * Compare two strings by Unicode code point.
39
+ *
40
+ * Not `a < b`, which is UTF-16 code unit order. See the note at the top of this file: the two orders
41
+ * disagree for anything above U+FFFF, and that disagreement would be a hash mismatch between
42
+ * languages rather than a visible bug.
43
+ */
44
+ export function compareCodePoints(a, b) {
45
+ const left = [...a];
46
+ const right = [...b];
47
+ const shorter = Math.min(left.length, right.length);
48
+ for (let i = 0; i < shorter; i += 1) {
49
+ const x = left[i].codePointAt(0);
50
+ const y = right[i].codePointAt(0);
51
+ if (x !== y)
52
+ return x < y ? -1 : 1;
53
+ }
54
+ return left.length - right.length;
55
+ }
56
+ function escape(text) {
57
+ const normalised = text.normalize('NFC');
58
+ let out = '"';
59
+ for (const char of normalised) {
60
+ const code = char.codePointAt(0);
61
+ const short = SHORT_ESCAPES[code];
62
+ if (short !== undefined) {
63
+ out += short;
64
+ }
65
+ else if (code < 0x20) {
66
+ out += `\\u${code.toString(16).padStart(4, '0')}`;
67
+ }
68
+ else {
69
+ out += char;
70
+ }
71
+ }
72
+ return `${out}"`;
73
+ }
74
+ function encode(value, path) {
75
+ if (value === null)
76
+ return 'null';
77
+ if (value === true)
78
+ return 'true';
79
+ if (value === false)
80
+ return 'false';
81
+ if (typeof value === 'string')
82
+ return escape(value);
83
+ if (typeof value === 'number') {
84
+ // The contract forbids floating point values in the encoding, and JavaScript has only one
85
+ // numeric type - so "is this an integer" has to be asked explicitly rather than assumed from the
86
+ // declared type. This is a place where a Python port would have nothing to do and TypeScript has
87
+ // to be careful.
88
+ if (!Number.isInteger(value)) {
89
+ throw new CanonicalError(`float at ${path}: floating point is not representable in canonical form, because its ` +
90
+ 'textual form differs between languages. Use an integer, or a decimal string, or the name ' +
91
+ 'of a float type if you meant to describe a type.');
92
+ }
93
+ if (!Number.isSafeInteger(value)) {
94
+ throw new CanonicalError(`integer at ${path} is outside the safe range: ${value}. JavaScript numbers lose precision ` +
95
+ 'above 2^53, so emitting this would produce bytes another language cannot reproduce. ' +
96
+ 'Refused rather than truncated.');
97
+ }
98
+ return String(value);
99
+ }
100
+ if (typeof value === 'bigint') {
101
+ return value.toString();
102
+ }
103
+ if (Array.isArray(value)) {
104
+ return `[${value.map((item, i) => encode(item, `${path}[${i}]`)).join(',')}]`;
105
+ }
106
+ if (typeof value === 'object') {
107
+ const pairs = [];
108
+ for (const [key, item] of Object.entries(value)) {
109
+ if (item === undefined) {
110
+ // `undefined` is not `null` and has no canonical form. JSON.stringify drops such keys
111
+ // silently, which would make the bytes depend on whether a field was omitted or set to
112
+ // undefined - exactly the kind of invisible difference this encoding exists to prevent.
113
+ throw new CanonicalError(`undefined at ${path}.${key}: use null if you mean a null, or omit the key entirely. ` +
114
+ 'Dropping it silently would make the bytes depend on how the caller spelled absence.');
115
+ }
116
+ pairs.push([key.normalize('NFC'), item]);
117
+ }
118
+ pairs.sort((left, right) => compareCodePoints(left[0], right[0]));
119
+ const seen = new Set();
120
+ const parts = [];
121
+ for (const [key, item] of pairs) {
122
+ if (seen.has(key)) {
123
+ throw new CanonicalError(`duplicate key ${JSON.stringify(key)} at ${path} after NFC normalisation: two keys that ` +
124
+ 'differ only in Unicode composition are the same key here');
125
+ }
126
+ seen.add(key);
127
+ parts.push(`${escape(key)}:${encode(item, `${path}.${key}`)}`);
128
+ }
129
+ return `{${parts.join(',')}}`;
130
+ }
131
+ throw new CanonicalError(`${typeof value} at ${path} has no canonical form. The canonical encoding accepts only null, ` +
132
+ 'boolean, integer, string, array and plain object; anything richer has to be reduced to those ' +
133
+ 'by the caller, so that the reduction is visible and testable.');
134
+ }
135
+ /** Canonical form as text. Prefer {@link canonicalBytes} for hashing. */
136
+ export function canonicalString(value) {
137
+ return encode(value, '$');
138
+ }
139
+ /** Canonical form as UTF-8 bytes. This is what gets hashed and what the vectors compare. */
140
+ export function canonicalBytes(value) {
141
+ return Buffer.from(canonicalString(value), 'utf8');
142
+ }
143
+ /**
144
+ * The identifier form used for `model_version` and `shape.id`: lowercase hex, first eight bytes of
145
+ * SHA-256 over the canonical bytes.
146
+ */
147
+ export function digest16(value) {
148
+ return createHash('sha256').update(canonicalBytes(value)).digest('hex').slice(0, 16);
149
+ }
150
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Asking an engine adapter whether it will answer a call, which is not the same as its type.
3
+ *
4
+ * Two optional capabilities decide whether an engine takes part in something: keeping the
5
+ * forward-only bookkeeping, and taking part in a migration. Both are asked for by **name of the
6
+ * members required**, with ordinary property access, and the reason is worth carrying across from
7
+ * the reference implementation even though this runtime cannot reproduce its defect.
8
+ *
9
+ * There, the obvious spelling was `isinstance(engine, Protocol)` - and a `runtime_checkable`
10
+ * protocol resolves its members with `hasattr` up to Python 3.11 and with `inspect.getattr_static`
11
+ * from 3.12, the second of which ignores `__getattr__`. An object forwarding to a wrapped adapter
12
+ * therefore passed on one interpreter and failed on the next: the same client, the same wrapper, a
13
+ * different answer. The consequence was two wrong diagnoses shipped as helpful messages, both
14
+ * naming the client's engine for a property of their own wrapper, and one of them refusing a
15
+ * migration outright.
16
+ *
17
+ * TypeScript has no such split - property access is property access, and it goes through the
18
+ * prototype chain and through a `Proxy` trap alike - so wrapping an adapter works here without
19
+ * anything special. That is exactly why the reason is written down rather than the mechanism
20
+ * assumed: the *question* is the durable part. Will this object respond to these calls?
21
+ */
22
+ /**
23
+ * Whether every member named here can be reached on this object.
24
+ *
25
+ * **Presence, not callability, and that is the reference implementation's decision rather than
26
+ * mine.** A member that exists and is not callable fails at the call with a message naming it,
27
+ * which is a better failure than a capability check that quietly answers "no" and sends the reader
28
+ * to look at their engine. The first version of this file asked `typeof === 'function'`, which is
29
+ * the idiomatic spelling and disagreed with the reference on exactly that input - an adapter with a
30
+ * null method would have been "cannot take part" here and "takes part, then crashes with the name
31
+ * of the method" there. One contract, two behaviours, no compilation error.
32
+ *
33
+ * Read through the property rather than with `in`, because `in` consults a `Proxy`'s `has` trap and
34
+ * a forwarding wrapper usually only implements `get`. That is the same wrapper the reference's
35
+ * module docstring is about, and the reason this question is asked by name at all.
36
+ */
37
+ export declare function satisfies(engine: unknown, members: readonly string[]): boolean;
38
+ /**
39
+ * The members an engine needs for the forward-only map check.
40
+ *
41
+ * A list rather than an interface used with a type guard, because an interface is erased before the
42
+ * code runs and this question is asked at runtime about an object a client constructed. The list and
43
+ * the interface are kept in agreement by the compiler - see `WATERMARK_STORE_IS_TOTAL` in
44
+ * `watermark.ts`.
45
+ */
46
+ export declare const WATERMARK_MEMBERS: readonly ["mapWatermark", "recordMapVersion"];
47
+ /** The members an engine needs to take part in a migration. */
48
+ export declare const MIGRATABLE_MEMBERS: readonly ["keyRange", "nthKey", "copyIn", "count", "get", "backfillMarker", "recordBackfillMarker"];
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Asking an engine adapter whether it will answer a call, which is not the same as its type.
3
+ *
4
+ * Two optional capabilities decide whether an engine takes part in something: keeping the
5
+ * forward-only bookkeeping, and taking part in a migration. Both are asked for by **name of the
6
+ * members required**, with ordinary property access, and the reason is worth carrying across from
7
+ * the reference implementation even though this runtime cannot reproduce its defect.
8
+ *
9
+ * There, the obvious spelling was `isinstance(engine, Protocol)` - and a `runtime_checkable`
10
+ * protocol resolves its members with `hasattr` up to Python 3.11 and with `inspect.getattr_static`
11
+ * from 3.12, the second of which ignores `__getattr__`. An object forwarding to a wrapped adapter
12
+ * therefore passed on one interpreter and failed on the next: the same client, the same wrapper, a
13
+ * different answer. The consequence was two wrong diagnoses shipped as helpful messages, both
14
+ * naming the client's engine for a property of their own wrapper, and one of them refusing a
15
+ * migration outright.
16
+ *
17
+ * TypeScript has no such split - property access is property access, and it goes through the
18
+ * prototype chain and through a `Proxy` trap alike - so wrapping an adapter works here without
19
+ * anything special. That is exactly why the reason is written down rather than the mechanism
20
+ * assumed: the *question* is the durable part. Will this object respond to these calls?
21
+ */
22
+ /**
23
+ * Whether every member named here can be reached on this object.
24
+ *
25
+ * **Presence, not callability, and that is the reference implementation's decision rather than
26
+ * mine.** A member that exists and is not callable fails at the call with a message naming it,
27
+ * which is a better failure than a capability check that quietly answers "no" and sends the reader
28
+ * to look at their engine. The first version of this file asked `typeof === 'function'`, which is
29
+ * the idiomatic spelling and disagreed with the reference on exactly that input - an adapter with a
30
+ * null method would have been "cannot take part" here and "takes part, then crashes with the name
31
+ * of the method" there. One contract, two behaviours, no compilation error.
32
+ *
33
+ * Read through the property rather than with `in`, because `in` consults a `Proxy`'s `has` trap and
34
+ * a forwarding wrapper usually only implements `get`. That is the same wrapper the reference's
35
+ * module docstring is about, and the reason this question is asked by name at all.
36
+ */
37
+ export function satisfies(engine, members) {
38
+ if (typeof engine !== 'object' || engine === null)
39
+ return false;
40
+ const target = engine;
41
+ return members.every((name) => target[name] !== undefined);
42
+ }
43
+ /**
44
+ * The members an engine needs for the forward-only map check.
45
+ *
46
+ * A list rather than an interface used with a type guard, because an interface is erased before the
47
+ * code runs and this question is asked at runtime about an object a client constructed. The list and
48
+ * the interface are kept in agreement by the compiler - see `WATERMARK_STORE_IS_TOTAL` in
49
+ * `watermark.ts`.
50
+ */
51
+ export const WATERMARK_MEMBERS = ['mapWatermark', 'recordMapVersion'];
52
+ /** The members an engine needs to take part in a migration. */
53
+ export const MIGRATABLE_MEMBERS = [
54
+ 'keyRange',
55
+ 'nthKey',
56
+ 'copyIn',
57
+ 'count',
58
+ 'get',
59
+ 'backfillMarker',
60
+ 'recordBackfillMarker',
61
+ ];
62
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,SAAS,CAAC,MAAe,EAAE,OAA0B;IACnE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC/D,MAAM,MAAM,GAAG,MAAiC,CAAA;IAChD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAA;AAC5D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAU,CAAA;AAE9E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,UAAU;IACV,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,KAAK;IACL,gBAAgB;IAChB,sBAAsB;CACd,CAAA"}
@@ -0,0 +1,36 @@
1
+ import type { LogicalModel } from './model.js';
2
+ import { type LoadOptions, type PlacementMap } from './placement.js';
3
+ import { VerificationRequest } from './verification.js';
4
+ /** A move: the maintained copy that takes over is in another engine binding than the source. */
5
+ export declare const CUTOVER_PROTOCOL = 1;
6
+ /** A relayout: the copy that takes over is in the source's own engine binding, under fresh names. */
7
+ export declare const CUTOVER_RELAYOUT_PROTOCOL = 2;
8
+ type Outcome = 'success' | 'abort';
9
+ type PublicKeys = NonNullable<LoadOptions['publicKey']>;
10
+ export declare class CutoverPlan {
11
+ readonly planId: string;
12
+ readonly projectId: string;
13
+ readonly group: string;
14
+ readonly before: PlacementMap;
15
+ readonly success: PlacementMap;
16
+ readonly abort: PlacementMap;
17
+ readonly verification: VerificationRequest;
18
+ readonly pauseBudgetMs: number;
19
+ readonly queryImpactDigest: string;
20
+ readonly verifiedWith: string | null;
21
+ constructor(planId: string, projectId: string, group: string, before: PlacementMap, success: PlacementMap, abort: PlacementMap, verification: VerificationRequest, pauseBudgetMs: number, queryImpactDigest: string, verifiedWith: string | null);
22
+ private loaded;
23
+ get fingerprint(): string | undefined;
24
+ get sourceEpoch(): number;
25
+ get maintenanceEpoch(): number;
26
+ get activationEpoch(): number;
27
+ asRecord(): Record<string, unknown>;
28
+ candidatePayload(outcome: Outcome): Uint8Array;
29
+ checkCurrent(current: PlacementMap): void;
30
+ }
31
+ export declare function loadCutoverPlan(raw: unknown, options: {
32
+ model: LogicalModel;
33
+ projectId: string;
34
+ publicKey: PublicKeys;
35
+ }): CutoverPlan;
36
+ export {};