@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,20 @@
1
+ /** An immutable UTC instant with the microsecond precision of the SDE timestamp types.
2
+ *
3
+ * JavaScript Date loses the last three digits before a migration can compare them. Both engine
4
+ * adapters therefore return Timestamp, and accept it on writes and in keys. Date remains a valid
5
+ * input for applications whose values really are millisecond-resolution. See docs/timestamps.md.
6
+ */
7
+ export declare class Timestamp {
8
+ readonly epochMicroseconds: bigint;
9
+ private constructor();
10
+ static fromEpochMicroseconds(value: bigint): Timestamp;
11
+ /** ISO date-time or engine text; an absent offset means UTC, never the process timezone. */
12
+ static from(value: string | Date | Timestamp): Timestamp;
13
+ toISOString(): string;
14
+ toString(): string;
15
+ toJSON(): string;
16
+ /** Convert only when Date can represent the instant exactly. No implicit rounding. */
17
+ toDate(): Date;
18
+ /** Ordering must name the exact quantity rather than coerce it to a lossy number. */
19
+ valueOf(): never;
20
+ }
@@ -0,0 +1,89 @@
1
+ /** An immutable UTC instant with the microsecond precision of the SDE timestamp types.
2
+ *
3
+ * JavaScript Date loses the last three digits before a migration can compare them. Both engine
4
+ * adapters therefore return Timestamp, and accept it on writes and in keys. Date remains a valid
5
+ * input for applications whose values really are millisecond-resolution. See docs/timestamps.md.
6
+ */
7
+ export class Timestamp {
8
+ epochMicroseconds;
9
+ constructor(epochMicroseconds) {
10
+ this.epochMicroseconds = epochMicroseconds;
11
+ Object.freeze(this);
12
+ }
13
+ static fromEpochMicroseconds(value) {
14
+ if (typeof value !== 'bigint')
15
+ throw new TypeError('epochMicroseconds must be a bigint');
16
+ // Years 0001 through 9999, the common finite range of the host representations. Each engine
17
+ // may impose a narrower range; its write error remains authoritative.
18
+ if (value < -62135596800000000n || value > 253402300799999999n) {
19
+ throw new RangeError('Timestamp must be within UTC years 0001 through 9999');
20
+ }
21
+ return new Timestamp(value);
22
+ }
23
+ /** ISO date-time or engine text; an absent offset means UTC, never the process timezone. */
24
+ static from(value) {
25
+ if (value instanceof Timestamp)
26
+ return value;
27
+ if (value instanceof Date) {
28
+ if (!Number.isFinite(value.getTime()))
29
+ throw new RangeError('invalid Date for Timestamp');
30
+ return Timestamp.fromEpochMicroseconds(BigInt(value.getTime()) * 1000n);
31
+ }
32
+ if (typeof value !== 'string')
33
+ throw new TypeError('Timestamp requires a string or Date');
34
+ const parts = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?(?:[Zz]|([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?)?$/.exec(value);
35
+ if (parts === null) {
36
+ throw new RangeError('Timestamp requires a finite ISO date-time with at most six fractional digits');
37
+ }
38
+ const year = Number(parts[1]);
39
+ const month = Number(parts[2]);
40
+ const day = Number(parts[3]);
41
+ const hour = Number(parts[4]);
42
+ const minute = Number(parts[5]);
43
+ const second = Number(parts[6]);
44
+ const date = new Date(0);
45
+ date.setUTCFullYear(year, month - 1, day);
46
+ date.setUTCHours(hour, minute, second, 0);
47
+ if (year < 1 || date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 ||
48
+ date.getUTCDate() !== day || date.getUTCHours() !== hour ||
49
+ date.getUTCMinutes() !== minute || date.getUTCSeconds() !== second) {
50
+ throw new RangeError('invalid calendar date or time for Timestamp');
51
+ }
52
+ const offsetHour = Number(parts[9] ?? 0);
53
+ const offsetMinute = Number(parts[10] ?? 0);
54
+ const offsetSecond = Number(parts[11] ?? 0);
55
+ if (offsetHour > 23 || offsetMinute > 59 || offsetSecond > 59) {
56
+ throw new RangeError('invalid UTC offset for Timestamp');
57
+ }
58
+ const offset = (offsetHour * 3600 + offsetMinute * 60 + offsetSecond) *
59
+ (parts[8] === '-' ? -1 : 1);
60
+ const fraction = BigInt((parts[7] ?? '').padEnd(6, '0'));
61
+ return Timestamp.fromEpochMicroseconds(BigInt(date.getTime()) * 1000n + fraction - BigInt(offset) * 1000000n);
62
+ }
63
+ toISOString() {
64
+ // Floor, not truncation towards zero: the microsecond immediately before the epoch belongs
65
+ // to the previous millisecond, with a positive fractional remainder.
66
+ const remainder = ((this.epochMicroseconds % 1000n) + 1000n) % 1000n;
67
+ const milliseconds = (this.epochMicroseconds - remainder) / 1000n;
68
+ const iso = new Date(Number(milliseconds)).toISOString();
69
+ return `${iso.slice(0, -1)}${remainder.toString().padStart(3, '0')}Z`;
70
+ }
71
+ toString() {
72
+ return this.toISOString();
73
+ }
74
+ toJSON() {
75
+ return this.toISOString();
76
+ }
77
+ /** Convert only when Date can represent the instant exactly. No implicit rounding. */
78
+ toDate() {
79
+ if (this.epochMicroseconds % 1000n !== 0n) {
80
+ throw new RangeError('Timestamp has microseconds that Date cannot represent; use epochMicroseconds or toISOString()');
81
+ }
82
+ return new Date(Number(this.epochMicroseconds / 1000n));
83
+ }
84
+ /** Ordering must name the exact quantity rather than coerce it to a lossy number. */
85
+ valueOf() {
86
+ throw new TypeError('compare Timestamp.epochMicroseconds explicitly');
87
+ }
88
+ }
89
+ //# sourceMappingURL=timestamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timestamp.js","sourceRoot":"","sources":["../src/timestamp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The neutral type vocabulary, and how a TypeScript declaration reaches it.
3
+ *
4
+ * Here is where TypeScript differs from Python in the way that matters, and why it was worth writing
5
+ * second. Python can read a class's annotations at runtime and derive the model from them. TypeScript
6
+ * cannot: its types are erased before the code runs. So the model has to be *stated*, as values, and
7
+ * there is no possibility of inferring it from the shape of a class.
8
+ *
9
+ * That is a better test of the contract than a similar language would have been. Anything the format
10
+ * contract left implicit - anything that was really "what Python's introspection happens to produce"
11
+ * - has nowhere to hide here, because nothing is introspected.
12
+ *
13
+ * The vocabulary itself is fixed and shared:
14
+ *
15
+ * bool int32 int64 float32 float64 decimal(p,s)
16
+ * string bytes uuid date timestamp timestamptz json
17
+ *
18
+ * `decimal` is written `decimal(12,2)` - precision, comma, scale, no spaces, both required. A decimal
19
+ * without precision is not a storable type in any engine we place data in, and letting the engine
20
+ * choose would make the physical schema depend on something the model never said.
21
+ */
22
+ export declare const NEUTRAL_TYPES: readonly ["bool", "int32", "int64", "float32", "float64", "string", "bytes", "uuid", "date", "timestamp", "timestamptz", "json"];
23
+ export type NeutralType = (typeof NEUTRAL_TYPES)[number] | `decimal(${number},${number})`;
24
+ /** A field: its neutral type, and whether it may be null. */
25
+ export interface FieldType {
26
+ readonly type: NeutralType;
27
+ readonly nullable: boolean;
28
+ }
29
+ /**
30
+ * The vocabulary as values.
31
+ *
32
+ * Note what is absent: any mapping from a TypeScript type. `T.timestamptz` is not "the neutral form
33
+ * of `Date`" - a `Date` has no zone information to inspect and `number` could be any of five things.
34
+ * The declaration says which one it is, because only the author knows.
35
+ */
36
+ export declare const T: {
37
+ readonly bool: FieldType & {
38
+ nullable: false;
39
+ };
40
+ readonly int32: FieldType & {
41
+ nullable: false;
42
+ };
43
+ readonly int64: FieldType & {
44
+ nullable: false;
45
+ };
46
+ readonly float32: FieldType & {
47
+ nullable: false;
48
+ };
49
+ readonly float64: FieldType & {
50
+ nullable: false;
51
+ };
52
+ readonly string: FieldType & {
53
+ nullable: false;
54
+ };
55
+ readonly bytes: FieldType & {
56
+ nullable: false;
57
+ };
58
+ readonly uuid: FieldType & {
59
+ nullable: false;
60
+ };
61
+ readonly date: FieldType & {
62
+ nullable: false;
63
+ };
64
+ readonly timestamp: FieldType & {
65
+ nullable: false;
66
+ };
67
+ readonly timestamptz: FieldType & {
68
+ nullable: false;
69
+ };
70
+ readonly json: FieldType & {
71
+ nullable: false;
72
+ };
73
+ /** `T.decimal(12, 2)`. Both arguments are required, for the reason in this module's docstring. */
74
+ readonly decimal: (digits: number, scale: number) => FieldType;
75
+ /** Make any of the above nullable: `T.nullable(T.string)`. */
76
+ readonly nullable: (inner: FieldType) => FieldType;
77
+ };
78
+ /** Validate a type name that came from outside - a vector, or a hand-written declaration. */
79
+ export declare function checkType(name: string, where: string): NeutralType;
package/dist/types.js ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The neutral type vocabulary, and how a TypeScript declaration reaches it.
3
+ *
4
+ * Here is where TypeScript differs from Python in the way that matters, and why it was worth writing
5
+ * second. Python can read a class's annotations at runtime and derive the model from them. TypeScript
6
+ * cannot: its types are erased before the code runs. So the model has to be *stated*, as values, and
7
+ * there is no possibility of inferring it from the shape of a class.
8
+ *
9
+ * That is a better test of the contract than a similar language would have been. Anything the format
10
+ * contract left implicit - anything that was really "what Python's introspection happens to produce"
11
+ * - has nowhere to hide here, because nothing is introspected.
12
+ *
13
+ * The vocabulary itself is fixed and shared:
14
+ *
15
+ * bool int32 int64 float32 float64 decimal(p,s)
16
+ * string bytes uuid date timestamp timestamptz json
17
+ *
18
+ * `decimal` is written `decimal(12,2)` - precision, comma, scale, no spaces, both required. A decimal
19
+ * without precision is not a storable type in any engine we place data in, and letting the engine
20
+ * choose would make the physical schema depend on something the model never said.
21
+ */
22
+ import { DeclarationError } from './errors.js';
23
+ export const NEUTRAL_TYPES = [
24
+ 'bool',
25
+ 'int32',
26
+ 'int64',
27
+ 'float32',
28
+ 'float64',
29
+ 'string',
30
+ 'bytes',
31
+ 'uuid',
32
+ 'date',
33
+ 'timestamp',
34
+ 'timestamptz',
35
+ 'json',
36
+ ];
37
+ function simple(type) {
38
+ return { type, nullable: false };
39
+ }
40
+ /**
41
+ * The vocabulary as values.
42
+ *
43
+ * Note what is absent: any mapping from a TypeScript type. `T.timestamptz` is not "the neutral form
44
+ * of `Date`" - a `Date` has no zone information to inspect and `number` could be any of five things.
45
+ * The declaration says which one it is, because only the author knows.
46
+ */
47
+ export const T = {
48
+ bool: simple('bool'),
49
+ int32: simple('int32'),
50
+ int64: simple('int64'),
51
+ float32: simple('float32'),
52
+ float64: simple('float64'),
53
+ string: simple('string'),
54
+ bytes: simple('bytes'),
55
+ uuid: simple('uuid'),
56
+ date: simple('date'),
57
+ timestamp: simple('timestamp'),
58
+ timestamptz: simple('timestamptz'),
59
+ json: simple('json'),
60
+ /** `T.decimal(12, 2)`. Both arguments are required, for the reason in this module's docstring. */
61
+ decimal(digits, scale) {
62
+ if (!Number.isInteger(digits) || !Number.isInteger(scale)) {
63
+ throw new DeclarationError(`decimal(${digits}, ${scale}) needs whole numbers: a fractional precision is not a thing`);
64
+ }
65
+ if (digits < 1 || scale < 0 || scale > digits) {
66
+ throw new DeclarationError(`decimal(${digits}, ${scale}) is not a usable decimal: digits must be at least 1 and scale ` +
67
+ 'must be between 0 and digits');
68
+ }
69
+ return { type: `decimal(${digits},${scale})`, nullable: false };
70
+ },
71
+ /** Make any of the above nullable: `T.nullable(T.string)`. */
72
+ nullable(inner) {
73
+ return { type: inner.type, nullable: true };
74
+ },
75
+ };
76
+ const DECIMAL = /^decimal\((\d+),(\d+)\)$/;
77
+ /** Validate a type name that came from outside - a vector, or a hand-written declaration. */
78
+ export function checkType(name, where) {
79
+ if (NEUTRAL_TYPES.includes(name)) {
80
+ return name;
81
+ }
82
+ if (name.startsWith('decimal')) {
83
+ const match = DECIMAL.exec(name);
84
+ if (match) {
85
+ const digits = Number(match[1]);
86
+ const scale = Number(match[2]);
87
+ if (digits >= 1 && scale >= 0 && scale <= digits) {
88
+ return name;
89
+ }
90
+ }
91
+ throw new DeclarationError(`${where}: ${JSON.stringify(name)} is not a well-formed decimal. The written form is ` +
92
+ 'decimal(digits,scale) - precision then scale, no spaces, both required. No spaces because ' +
93
+ 'whitespace inside a type name is exactly the sort of thing two libraries would disagree ' +
94
+ 'about, and both required because a decimal without precision is not a storable type in any ' +
95
+ 'engine we place data in.');
96
+ }
97
+ throw new DeclarationError(`${where}: ${JSON.stringify(name)} is not in the neutral type vocabulary ` +
98
+ `(${[...NEUTRAL_TYPES].sort().join(', ')}, decimal(p,s))`);
99
+ }
100
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { PlacementMap } from './placement.js';
2
+ export declare const REQUEST_PROTOCOL = 1;
3
+ export declare function checkProjectId(value: string | undefined): void;
4
+ interface VerificationFields {
5
+ readonly requestId: string;
6
+ readonly projectId: string;
7
+ readonly modelVersion: string;
8
+ readonly mapVersion: number;
9
+ readonly mapFingerprint: string;
10
+ readonly group: string;
11
+ readonly sourceEngine: string;
12
+ readonly sourceId: string;
13
+ readonly targets: readonly (readonly [string, string])[];
14
+ readonly requestedAt: string;
15
+ readonly requiresSignature: boolean;
16
+ }
17
+ export declare class VerificationRequest implements VerificationFields {
18
+ readonly requestId: string;
19
+ readonly projectId: string;
20
+ readonly modelVersion: string;
21
+ readonly mapVersion: number;
22
+ readonly mapFingerprint: string;
23
+ readonly group: string;
24
+ readonly sourceEngine: string;
25
+ readonly sourceId: string;
26
+ readonly targets: readonly (readonly [string, string])[];
27
+ readonly requestedAt: string;
28
+ readonly requiresSignature: boolean;
29
+ constructor(fields: VerificationFields);
30
+ asRecord(): Record<string, unknown>;
31
+ static fromRecord(value: unknown): VerificationRequest;
32
+ checkSession(placement: PlacementMap, projectId: string | undefined, group: string): void;
33
+ checkTime(at: string): void;
34
+ }
35
+ export declare function verificationRequest(placement: PlacementMap, options: {
36
+ readonly group: string;
37
+ readonly projectId: string;
38
+ readonly requestId: string;
39
+ readonly requestedAt: string;
40
+ }): VerificationRequest;
41
+ export {};
@@ -0,0 +1,169 @@
1
+ /** A comparison bound to one request, locally configured project and exact placement map. */
2
+ import { canonicalString, compareCodePoints } from './canonical.js';
3
+ import { MigrationRefused } from './errors.js';
4
+ import { fingerprintOf, placementOf } from './placement.js';
5
+ import { Timestamp } from './timestamp.js';
6
+ export const REQUEST_PROTOCOL = 1;
7
+ function hex(value, width, field) {
8
+ if (typeof value !== 'string' || !new RegExp(`^[0-9a-f]{${width}}$`).test(value)) {
9
+ throw new MigrationRefused(`verification ${field} must be ${width} lowercase hexadecimal digits`);
10
+ }
11
+ }
12
+ export function checkProjectId(value) {
13
+ if (value !== undefined)
14
+ hex(value, 32, 'project_id');
15
+ }
16
+ function name(value) {
17
+ if (typeof value !== 'string' || value.length === 0) {
18
+ throw new MigrationRefused('verification names must be nonempty strings');
19
+ }
20
+ }
21
+ function awareTime(value) {
22
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d{1,6})?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(value)) {
23
+ throw new MigrationRefused('verification time must be an ISO timestamp with an offset');
24
+ }
25
+ try {
26
+ return Timestamp.from(value).epochMicroseconds;
27
+ }
28
+ catch {
29
+ throw new MigrationRefused('verification time must be an ISO timestamp with an offset');
30
+ }
31
+ }
32
+ function exact(value, fields, label) {
33
+ if (typeof value !== 'object' || value === null || Array.isArray(value) ||
34
+ Object.keys(value).sort().join('|') !== [...fields].sort().join('|')) {
35
+ throw new MigrationRefused(`verification ${label} has missing or unknown fields`);
36
+ }
37
+ }
38
+ export class VerificationRequest {
39
+ requestId;
40
+ projectId;
41
+ modelVersion;
42
+ mapVersion;
43
+ mapFingerprint;
44
+ group;
45
+ sourceEngine;
46
+ sourceId;
47
+ targets;
48
+ requestedAt;
49
+ requiresSignature;
50
+ constructor(fields) {
51
+ hex(fields.requestId, 32, 'request_id');
52
+ hex(fields.projectId, 32, 'project_id');
53
+ hex(fields.modelVersion, 16, 'model_version');
54
+ hex(fields.mapFingerprint, 64, 'map_fingerprint');
55
+ if (!Number.isSafeInteger(fields.mapVersion) || fields.mapVersion < 1) {
56
+ throw new MigrationRefused('verification map_version must be a positive integer');
57
+ }
58
+ if (typeof fields.requiresSignature !== 'boolean') {
59
+ throw new MigrationRefused('verification requires_signature must be a boolean');
60
+ }
61
+ for (const value of [fields.group, fields.sourceEngine, fields.sourceId])
62
+ name(value);
63
+ if (!Array.isArray(fields.targets) || fields.targets.length === 0) {
64
+ throw new MigrationRefused('verification targets must be nonempty engine/id pairs');
65
+ }
66
+ for (const target of fields.targets) {
67
+ if (!Array.isArray(target) || target.length !== 2) {
68
+ throw new MigrationRefused('verification targets must be nonempty engine/id pairs');
69
+ }
70
+ name(target[0]);
71
+ name(target[1]);
72
+ if (target[1] === fields.sourceId) {
73
+ throw new MigrationRefused('verification source cannot also be a target');
74
+ }
75
+ }
76
+ if (new Set(fields.targets.map((target) => target[1])).size !== fields.targets.length) {
77
+ throw new MigrationRefused('verification target ids must be unique');
78
+ }
79
+ const sorted = [...fields.targets].sort(compareTargets);
80
+ if (canonicalString(sorted) !== canonicalString(fields.targets)) {
81
+ throw new MigrationRefused('verification targets must be sorted by engine and id');
82
+ }
83
+ awareTime(fields.requestedAt);
84
+ this.requestId = fields.requestId;
85
+ this.projectId = fields.projectId;
86
+ this.modelVersion = fields.modelVersion;
87
+ this.mapVersion = fields.mapVersion;
88
+ this.mapFingerprint = fields.mapFingerprint;
89
+ this.group = fields.group;
90
+ this.sourceEngine = fields.sourceEngine;
91
+ this.sourceId = fields.sourceId;
92
+ this.targets = Object.freeze(fields.targets.map(([engine, id]) => Object.freeze([engine, id])));
93
+ this.requestedAt = fields.requestedAt;
94
+ this.requiresSignature = fields.requiresSignature;
95
+ Object.freeze(this);
96
+ }
97
+ asRecord() {
98
+ return {
99
+ protocol: REQUEST_PROTOCOL,
100
+ request_id: this.requestId, project_id: this.projectId,
101
+ model_version: this.modelVersion, map_version: this.mapVersion,
102
+ map_fingerprint: this.mapFingerprint, group: this.group,
103
+ source: { engine: this.sourceEngine, id: this.sourceId },
104
+ targets: this.targets.map(([engine, id]) => ({ engine, id })),
105
+ requested_at: this.requestedAt, requires_signature: this.requiresSignature,
106
+ };
107
+ }
108
+ static fromRecord(value) {
109
+ exact(value, ['protocol', 'request_id', 'project_id', 'model_version', 'map_version',
110
+ 'map_fingerprint', 'group', 'source', 'targets', 'requested_at', 'requires_signature'], 'request');
111
+ if (value['protocol'] !== REQUEST_PROTOCOL) {
112
+ throw new MigrationRefused('unsupported verification request protocol');
113
+ }
114
+ const source = value['source'];
115
+ exact(source, ['engine', 'id'], 'source');
116
+ if (!Array.isArray(value['targets'])) {
117
+ throw new MigrationRefused('verification targets must contain exactly engine and id');
118
+ }
119
+ const targets = value['targets'].map((target) => {
120
+ exact(target, ['engine', 'id'], 'target');
121
+ return [target['engine'], target['id']];
122
+ });
123
+ return new VerificationRequest({
124
+ requestId: value['request_id'], projectId: value['project_id'],
125
+ modelVersion: value['model_version'], mapVersion: value['map_version'],
126
+ mapFingerprint: value['map_fingerprint'], group: value['group'],
127
+ sourceEngine: source['engine'], sourceId: source['id'], targets,
128
+ requestedAt: value['requested_at'], requiresSignature: value['requires_signature'],
129
+ });
130
+ }
131
+ checkSession(placement, projectId, group) {
132
+ if (projectId !== this.projectId) {
133
+ throw new MigrationRefused('verification request names another project, or this session has no project_id. ' +
134
+ "Configure the project id from the client's enrollment, not from the request.");
135
+ }
136
+ if (group !== this.group)
137
+ throw new MigrationRefused('verification request names another group');
138
+ const expected = verificationRequest(placement, {
139
+ group, projectId, requestId: this.requestId, requestedAt: this.requestedAt,
140
+ });
141
+ if (canonicalString(this.asRecord()) !== canonicalString(expected.asRecord())) {
142
+ throw new MigrationRefused("verification request does not match this session's model, map, source or targets; " +
143
+ 'no comparison was started');
144
+ }
145
+ }
146
+ checkTime(at) {
147
+ if (awareTime(at) < awareTime(this.requestedAt)) {
148
+ throw new MigrationRefused("verification predates its request; check the verifier's clock and run the " +
149
+ 'comparison for the current request');
150
+ }
151
+ }
152
+ }
153
+ function compareTargets(left, right) {
154
+ return compareCodePoints(left[0], right[0]) || compareCodePoints(left[1], right[1]);
155
+ }
156
+ export function verificationRequest(placement, options) {
157
+ const fingerprint = fingerprintOf(placement);
158
+ if (fingerprint === undefined) {
159
+ throw new MigrationRefused('verification needs a loaded, canonically encodable placement map');
160
+ }
161
+ const spot = placementOf(placement, options.group);
162
+ return new VerificationRequest({
163
+ ...options, modelVersion: placement.modelVersion, mapVersion: placement.mapVersion,
164
+ mapFingerprint: fingerprint, sourceEngine: spot.source.engine, sourceId: spot.source.id,
165
+ targets: spot.alsoWrite.map((target) => [target.engine, target.id]).sort(compareTargets),
166
+ requiresSignature: placement.signed,
167
+ });
168
+ }
169
+ //# sourceMappingURL=verification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification.js","sourceRoot":"","sources":["../src/verification.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Refusing a placement map that goes backwards, and the durable state that makes it possible.
3
+ *
4
+ * A signed map for version 3 verifies correctly forever - that is what a signature is. So replacing
5
+ * the client's map file with an older signed one loads cleanly, routes writes to the previous
6
+ * placement, and **nothing protests**. Today that costs a client a stale schema. Once the migration
7
+ * state travels in the map, it costs them writes: a library reverted from dual-write to
8
+ * single-write in the middle of a migration drops exactly the rows the migration exists not to
9
+ * drop.
10
+ *
11
+ * Refusing it needs one thing a library has never had: **memory**. Everything else here is a pure
12
+ * function of a document, a model and a key, which is why it can be verified by reading it. This
13
+ * module is the exception, and each of the three obvious places to keep that memory is worse than
14
+ * the one chosen:
15
+ *
16
+ * - **in the process** protects until the first restart, and a restart is when a swapped file is
17
+ * read. A protection that lapses exactly when it is needed;
18
+ * - **in a file** needs a configured path, and in a container that path is usually ephemeral - so it
19
+ * degrades to the first option while continuing to look like the third. The worst property
20
+ * available: a guarantee present in the code and absent in production;
21
+ * - **with us** would mean the library asking our service whether it may start, which is the one
22
+ * thing this product promises it will never need to do. Our outage would become the client's.
23
+ *
24
+ * So it lives **in the client's own engines**, in a table this library owns. Four properties, and
25
+ * the first two are what make it safe rather than merely present.
26
+ *
27
+ * **Append-only, and the watermark is `max(map_version)`.** No update, no key enforcement, no
28
+ * row-level contention - and therefore identical semantics in PostgreSQL and in ClickHouse, which is
29
+ * the engine that has no unique constraint to offer. A stale row can never lower the bar.
30
+ *
31
+ * **Every participating engine is written, and the watermark is the maximum over all of them.**
32
+ * Losing an engine cannot lose the protection, and one engine lagging cannot weaken it.
33
+ *
34
+ * **An engine that cannot store it does not participate, and that is reported rather than hidden.**
35
+ * A protection whose state cannot be read is a protection taken on trust.
36
+ *
37
+ * **Only signed maps are checked.** An unsigned map is the client's own document: hand-writing one
38
+ * and pointing a library at it is the no-account mode, and their business what they replace it with.
39
+ * In pure no-account mode this module does nothing at all - no table, no query, no cost.
40
+ *
41
+ * The escape hatch is deliberately not a parameter. A legitimate rollback - we issued a bad map -
42
+ * means clearing the bookkeeping, and the refusal says how. A parameter called `allowRollback` would
43
+ * be set once during an incident and left set.
44
+ */
45
+ import { WATERMARK_MEMBERS } from './capabilities.js';
46
+ import type { PlacementMap } from './placement.js';
47
+ export type Protection = 'enforced' | 'unavailable' | 'not_applicable';
48
+ /**
49
+ * What an engine adapter offers to take part.
50
+ *
51
+ * Separate from the engine interface, and optional. Requiring these of every adapter would break
52
+ * every one anybody has written - including the fakes in somebody else's test suite - for a
53
+ * capability one of our own engines cannot provide anyway. So participation is discovered rather
54
+ * than required, and non-participation is a reportable state instead of a crash.
55
+ */
56
+ export interface WatermarkStore {
57
+ mapWatermark(): Promise<number | null>;
58
+ recordMapVersion(version: number, options: {
59
+ readonly modelVersion: string;
60
+ }): Promise<void>;
61
+ }
62
+ /**
63
+ * A compile-time ratchet between the interface and the list the runtime check uses.
64
+ *
65
+ * Add a method to `WatermarkStore` and forget `WATERMARK_MEMBERS`, and this stops being assignable.
66
+ * The runtime list cannot be derived from the interface - types are erased - so the compiler holds
67
+ * the other direction instead.
68
+ */
69
+ export declare const WATERMARK_STORE_IS_TOTAL: (typeof WATERMARK_MEMBERS)[number] extends keyof WatermarkStore ? keyof WatermarkStore extends (typeof WATERMARK_MEMBERS)[number] ? true : never : never;
70
+ /**
71
+ * What the check did, in a form a client can assert on.
72
+ *
73
+ * Exposed rather than kept private on purpose. A protection whose state cannot be read is a
74
+ * protection taken on trust, and this product's whole argument is that its guarantees are checkable
75
+ * by reading the code and now by reading this.
76
+ */
77
+ export interface WatermarkCheck {
78
+ readonly protection: Protection;
79
+ readonly mapVersion: number;
80
+ readonly highestSeen: number | null;
81
+ readonly participating: readonly string[];
82
+ readonly unable: readonly string[];
83
+ readonly why: string;
84
+ }
85
+ export declare function watermarkRecord(check: WatermarkCheck): Record<string, unknown>;
86
+ /**
87
+ * Refuse a signed map older than the newest one these engines have seen.
88
+ *
89
+ * Throws {@link MapRolledBack}. Equal is allowed - restarting a process against the same map is the
90
+ * ordinary case - and only strictly lower is refused.
91
+ *
92
+ * Asynchronous because reading a watermark is a query, and split in two inside: this gathers the
93
+ * numbers and {@link decide} makes the decision from them. The decision is therefore a pure
94
+ * function of a document and a set of integers, which is what lets the `migration/` vectors check
95
+ * it, and what lets a reader confirm by reading that nothing here depends on the order the engines
96
+ * answered in.
97
+ *
98
+ * **Nothing is read for an unsigned map.** The no-account mode promises no table, no query and no
99
+ * cost, and this function gathered every watermark and *then* asked whether the map was signed
100
+ * until `migration/001` existed - the right answer, with the promise broken, which is the one shape
101
+ * of defect a record of the decision cannot show.
102
+ */
103
+ export declare function enforceForwardOnly(placement: PlacementMap, engines: Readonly<Record<string, unknown>>): Promise<WatermarkCheck>;