@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
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Data Engine - client library for TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* You declare entities and relations. We decide which database engine each colocation group lives in,
|
|
5
|
+
* what its physical layout is there, and when it should move - and your code never names a table or
|
|
6
|
+
* an engine, which is exactly what lets us change both without touching it.
|
|
7
|
+
*
|
|
8
|
+
* This implementation reaches Tier 2 of the capability tiers, for PostgreSQL and ClickHouse: the
|
|
9
|
+
* model, canonical IR and version, colocation groups, operation shapes and ids, placement maps with
|
|
10
|
+
* signature verification and the refusals that go with them, routing, telemetry, schema rendering
|
|
11
|
+
* and application, dual write, and backfill with verification.
|
|
12
|
+
*
|
|
13
|
+
* The shared vectors for Tier 1 and Tier 2 were written **before** this library claimed those
|
|
14
|
+
* tiers, which is what §10 of the format contract requires and is the interesting fact about the
|
|
15
|
+
* claim. Closing that gap found five defects in the reference implementation.
|
|
16
|
+
*
|
|
17
|
+
* **Everything below Tier 2 is synchronous and touches no socket; Tier 2 is asynchronous.** That is
|
|
18
|
+
* the runtime's shape rather than a preference: a synchronous wrapper around Node's drivers means
|
|
19
|
+
* blocking the event loop. The engine adapters are behind their own subpath exports, so importing
|
|
20
|
+
* this module resolves no driver - pinned by a test over the import closure, because that absence
|
|
21
|
+
* is what makes the no-account mode free.
|
|
22
|
+
*
|
|
23
|
+
* It also implements hashed identifiers (section 2a), which is a mode rather than a tier: a complete
|
|
24
|
+
* Tier 0 library may omit it, but one that offers it has to derive the same digests as every other, or
|
|
25
|
+
* two services on one model compute two model versions and each refuses the other's map.
|
|
26
|
+
*
|
|
27
|
+
* Unlike Python, the model is declared explicitly rather than read from annotations - TypeScript's
|
|
28
|
+
* types are erased before the code runs. That is not a workaround; it is why this was the right second
|
|
29
|
+
* implementation. Anything the format contract left implicit had nowhere to hide.
|
|
30
|
+
*/
|
|
31
|
+
export { CanonicalError, canonicalBytes, canonicalString, compareCodePoints, digest16 } from './canonical.js';
|
|
32
|
+
export { DeclarationError, BulkWriteRefused, EngineError, MapError, MapRolledBack, MigrationRefused, ModelPlanningError, SdeError, ResourceBusy, ResourceClosed, } from './errors.js';
|
|
33
|
+
export type { NameMap } from './hashing.js';
|
|
34
|
+
export { DIGEST_CHARS, hashIdentifiers } from './hashing.js';
|
|
35
|
+
export type { Group } from './groups.js';
|
|
36
|
+
export { colocationGroups, groupOf } from './groups.js';
|
|
37
|
+
export type { CostCeiling, Entity, EntityDeclaration, EntitySpec, FieldSpec, LogicalModel, RelationSpec, } from './model.js';
|
|
38
|
+
export { assemble, buildModel, CONTRACT, entity, entityOf, irBytes, neutralDeclaration, ref, } from './model.js';
|
|
39
|
+
export type { DeclaredIndex, DeclaredTable, PartitionSpec, PhysicalFinding } from './physical.js';
|
|
40
|
+
export { PHYSICAL_DESIGN_SINCE, capabilities as physicalCapabilities, describeFinding, } from './physical.js';
|
|
41
|
+
export type { GroupPlacement, LoadOptions, Materialization, PhysicalLayout, PlacementMap, } from './placement.js';
|
|
42
|
+
export { ALSO_WRITE_SINCE, BACKFILL_TABLE, MAP_CONTRACT, MAP_CONTRACT_FLOOR, RESERVED_TABLES, WATERMARK_TABLE, loadMap, materializationById, placementOf, } from './placement.js';
|
|
43
|
+
export { groupColumns } from './layout.js';
|
|
44
|
+
export type { BackfillOptions, BackfillProgress, Difference, EntityProgress, Migratable, VerifyOptions, VerifyReport, } from './migration.js';
|
|
45
|
+
export { backfill, backfillForAHuman, backfillRecord, CHUNK_ROWS, DIALECT_PRECISION, entityProgressRecord, keyColumns, MIGRATABLE_IS_TOTAL, PRECISION_INDEPENDENT, sameWidth, verify, verifyForAHuman, verifyRecord, } from './migration.js';
|
|
46
|
+
export type { Engine, ManagedEngine, Row, SessionOptions } from './session.js';
|
|
47
|
+
export { Session, tableFor } from './session.js';
|
|
48
|
+
export type { ScanOptions, CountOptions, SummaryOptions } from './session.js';
|
|
49
|
+
export { MAX_PAGE_ROWS, QueryRefused } from './query.js';
|
|
50
|
+
export type { Range, ScanPage, Queryable, ReadColumn, ReadFilter, ReadPlan, NumericSummary, Summarizable } from './query.js';
|
|
51
|
+
export type { BulkWritable } from './bulk.js';
|
|
52
|
+
export { MAX_BATCH_ROWS, MAX_BATCH_VALUES } from './bulk.js';
|
|
53
|
+
export type { Protection, WatermarkCheck, WatermarkStore } from './watermark.js';
|
|
54
|
+
export { enforceForwardOnly, WATERMARK_STORE_IS_TOTAL, watermarkRecord, } from './watermark.js';
|
|
55
|
+
export { MIGRATABLE_MEMBERS, satisfies, WATERMARK_MEMBERS } from './capabilities.js';
|
|
56
|
+
export type { ResolveOptions } from './routing.js';
|
|
57
|
+
export { resolve } from './routing.js';
|
|
58
|
+
export type { CompatibilityViews, Dialect, SchemaOptions, ViewOptions } from './schema.js';
|
|
59
|
+
export { compatibilityViews, DIALECTS, FIXED_SCHEMA, QUOTE, schemaIsFixed, schemaStatements, } from './schema.js';
|
|
60
|
+
export type { OperationShape, ShapeKind } from './shapes.js';
|
|
61
|
+
export { enumerateShapes, SHAPE_KINDS, shapeId, shapeIr, WRITE_KINDS } from './shapes.js';
|
|
62
|
+
export { guard, internalFailures, resetInternalFailures } from './internal.js';
|
|
63
|
+
export type { CopyFreshness, FanOutOptions, FeatureOptions, GroupFeatures, ReadPredicates, RecordOptions, StorageMeasurement, StorageOptions, StorageSample, StorageSize, StorageUnavailable, Window, } from './telemetry.js';
|
|
64
|
+
export { BUCKET_BASE_NS, BUCKET_COUNT, copyFreshnessRecord, DAY_NS, exactBytes, FanOutStats, featuresRecord, FIELD_LIST_IS_TOTAL, FILTERED_KINDS, GROWTH_MIN_NS, hasTimeDimension, Histogram, MEASURED_FIELDS, Recorder, SECOND_NS, ShapeStats, STORAGE_UNAVAILABLE, timeFields, windowCopies, windowFeatures, windowRecord, windowShapes, } from './telemetry.js';
|
|
65
|
+
export type { FieldType, NeutralType } from './types.js';
|
|
66
|
+
export { checkType, NEUTRAL_TYPES, T } from './types.js';
|
|
67
|
+
export { Timestamp } from './timestamp.js';
|
|
68
|
+
/** The format contract version this library implements. */
|
|
69
|
+
export declare const CONTRACT_VERSION = 1;
|
|
70
|
+
/** The capability tier this library reaches. See docs/format-contract.md, section 9. */
|
|
71
|
+
export declare const TIER = 2;
|
|
72
|
+
export { VerificationRequest, verificationRequest } from './verification.js';
|
|
73
|
+
export { EPOCH_COLUMN as WRITE_EPOCH_COLUMN, FenceState, WriteFence } from './write-fence.js';
|
|
74
|
+
export { prepareSchema } from './provisioning.js';
|
|
75
|
+
export { InspectionContext } from './inspection.js';
|
|
76
|
+
export { verifyFrozen, frozenVerifyRecord, type FrozenVerifyReport, type FrozenTable } from './frozen-verification.js';
|
|
77
|
+
export { CUTOVER_PROTOCOL, CUTOVER_RELAYOUT_PROTOCOL, CutoverPlan, loadCutoverPlan } from './cutover.js';
|
|
78
|
+
export { STAGING_PROTOCOL, STAGING_RELAYOUT_PROTOCOL, StagingPlan, loadStagingPlan, stagingTableName } from './staging.js';
|
|
79
|
+
export { INDEX_CHANGE_PROTOCOL, INDEX_PROTOCOL, IndexPlan, indexBuildName, loadIndexPlan } from './in-place-index.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Data Engine - client library for TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* You declare entities and relations. We decide which database engine each colocation group lives in,
|
|
5
|
+
* what its physical layout is there, and when it should move - and your code never names a table or
|
|
6
|
+
* an engine, which is exactly what lets us change both without touching it.
|
|
7
|
+
*
|
|
8
|
+
* This implementation reaches Tier 2 of the capability tiers, for PostgreSQL and ClickHouse: the
|
|
9
|
+
* model, canonical IR and version, colocation groups, operation shapes and ids, placement maps with
|
|
10
|
+
* signature verification and the refusals that go with them, routing, telemetry, schema rendering
|
|
11
|
+
* and application, dual write, and backfill with verification.
|
|
12
|
+
*
|
|
13
|
+
* The shared vectors for Tier 1 and Tier 2 were written **before** this library claimed those
|
|
14
|
+
* tiers, which is what §10 of the format contract requires and is the interesting fact about the
|
|
15
|
+
* claim. Closing that gap found five defects in the reference implementation.
|
|
16
|
+
*
|
|
17
|
+
* **Everything below Tier 2 is synchronous and touches no socket; Tier 2 is asynchronous.** That is
|
|
18
|
+
* the runtime's shape rather than a preference: a synchronous wrapper around Node's drivers means
|
|
19
|
+
* blocking the event loop. The engine adapters are behind their own subpath exports, so importing
|
|
20
|
+
* this module resolves no driver - pinned by a test over the import closure, because that absence
|
|
21
|
+
* is what makes the no-account mode free.
|
|
22
|
+
*
|
|
23
|
+
* It also implements hashed identifiers (section 2a), which is a mode rather than a tier: a complete
|
|
24
|
+
* Tier 0 library may omit it, but one that offers it has to derive the same digests as every other, or
|
|
25
|
+
* two services on one model compute two model versions and each refuses the other's map.
|
|
26
|
+
*
|
|
27
|
+
* Unlike Python, the model is declared explicitly rather than read from annotations - TypeScript's
|
|
28
|
+
* types are erased before the code runs. That is not a workaround; it is why this was the right second
|
|
29
|
+
* implementation. Anything the format contract left implicit had nowhere to hide.
|
|
30
|
+
*/
|
|
31
|
+
export { CanonicalError, canonicalBytes, canonicalString, compareCodePoints, digest16 } from './canonical.js';
|
|
32
|
+
export { DeclarationError, BulkWriteRefused, EngineError, MapError, MapRolledBack, MigrationRefused, ModelPlanningError, SdeError, ResourceBusy, ResourceClosed, } from './errors.js';
|
|
33
|
+
export { DIGEST_CHARS, hashIdentifiers } from './hashing.js';
|
|
34
|
+
export { colocationGroups, groupOf } from './groups.js';
|
|
35
|
+
export { assemble, buildModel, CONTRACT, entity, entityOf, irBytes, neutralDeclaration, ref, } from './model.js';
|
|
36
|
+
export { PHYSICAL_DESIGN_SINCE, capabilities as physicalCapabilities, describeFinding, } from './physical.js';
|
|
37
|
+
export { ALSO_WRITE_SINCE, BACKFILL_TABLE, MAP_CONTRACT, MAP_CONTRACT_FLOOR, RESERVED_TABLES, WATERMARK_TABLE, loadMap, materializationById, placementOf, } from './placement.js';
|
|
38
|
+
export { groupColumns } from './layout.js';
|
|
39
|
+
export { backfill, backfillForAHuman, backfillRecord, CHUNK_ROWS, DIALECT_PRECISION, entityProgressRecord, keyColumns, MIGRATABLE_IS_TOTAL, PRECISION_INDEPENDENT, sameWidth, verify, verifyForAHuman, verifyRecord, } from './migration.js';
|
|
40
|
+
export { Session, tableFor } from './session.js';
|
|
41
|
+
export { MAX_PAGE_ROWS, QueryRefused } from './query.js';
|
|
42
|
+
export { MAX_BATCH_ROWS, MAX_BATCH_VALUES } from './bulk.js';
|
|
43
|
+
export { enforceForwardOnly, WATERMARK_STORE_IS_TOTAL, watermarkRecord, } from './watermark.js';
|
|
44
|
+
export { MIGRATABLE_MEMBERS, satisfies, WATERMARK_MEMBERS } from './capabilities.js';
|
|
45
|
+
export { resolve } from './routing.js';
|
|
46
|
+
export { compatibilityViews, DIALECTS, FIXED_SCHEMA, QUOTE, schemaIsFixed, schemaStatements, } from './schema.js';
|
|
47
|
+
export { enumerateShapes, SHAPE_KINDS, shapeId, shapeIr, WRITE_KINDS } from './shapes.js';
|
|
48
|
+
export { guard, internalFailures, resetInternalFailures } from './internal.js';
|
|
49
|
+
export { BUCKET_BASE_NS, BUCKET_COUNT, copyFreshnessRecord, DAY_NS, exactBytes, FanOutStats, featuresRecord, FIELD_LIST_IS_TOTAL, FILTERED_KINDS, GROWTH_MIN_NS, hasTimeDimension, Histogram, MEASURED_FIELDS, Recorder, SECOND_NS, ShapeStats, STORAGE_UNAVAILABLE, timeFields, windowCopies, windowFeatures, windowRecord, windowShapes, } from './telemetry.js';
|
|
50
|
+
export { checkType, NEUTRAL_TYPES, T } from './types.js';
|
|
51
|
+
export { Timestamp } from './timestamp.js';
|
|
52
|
+
/** The format contract version this library implements. */
|
|
53
|
+
export const CONTRACT_VERSION = 1;
|
|
54
|
+
/** The capability tier this library reaches. See docs/format-contract.md, section 9. */
|
|
55
|
+
export const TIER = 2;
|
|
56
|
+
export { VerificationRequest, verificationRequest } from './verification.js';
|
|
57
|
+
export { EPOCH_COLUMN as WRITE_EPOCH_COLUMN, FenceState, WriteFence } from './write-fence.js';
|
|
58
|
+
export { prepareSchema } from './provisioning.js';
|
|
59
|
+
export { InspectionContext } from './inspection.js';
|
|
60
|
+
export { verifyFrozen, frozenVerifyRecord } from './frozen-verification.js';
|
|
61
|
+
export { CUTOVER_PROTOCOL, CUTOVER_RELAYOUT_PROTOCOL, CutoverPlan, loadCutoverPlan } from './cutover.js';
|
|
62
|
+
export { STAGING_PROTOCOL, STAGING_RELAYOUT_PROTOCOL, StagingPlan, loadStagingPlan, stagingTableName } from './staging.js';
|
|
63
|
+
export { INDEX_CHANGE_PROTOCOL, INDEX_PROTOCOL, IndexPlan, indexBuildName, loadIndexPlan } from './in-place-index.js';
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LogicalModel } from './model.js';
|
|
2
|
+
import type { PlacementMap } from './placement.js';
|
|
3
|
+
import type { Engine } from './session.js';
|
|
4
|
+
export interface MigrationView {
|
|
5
|
+
readonly model: LogicalModel;
|
|
6
|
+
readonly placement: PlacementMap;
|
|
7
|
+
readonly projectId: string | undefined;
|
|
8
|
+
engineNamed(name: string): Engine;
|
|
9
|
+
}
|
|
10
|
+
export declare class InspectionContext implements MigrationView {
|
|
11
|
+
readonly model: LogicalModel;
|
|
12
|
+
readonly placement: PlacementMap;
|
|
13
|
+
private readonly engines;
|
|
14
|
+
readonly projectId: string;
|
|
15
|
+
constructor(model: LogicalModel, placement: PlacementMap, engines: Readonly<Record<string, Engine>>, options: {
|
|
16
|
+
projectId: string;
|
|
17
|
+
});
|
|
18
|
+
engineNamed(name: string): Engine;
|
|
19
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** A local operator's data view, without runtime writes or watermark adoption. */
|
|
2
|
+
import { MigrationRefused } from './errors.js';
|
|
3
|
+
import { checkMapProject } from './generation.js';
|
|
4
|
+
export class InspectionContext {
|
|
5
|
+
model;
|
|
6
|
+
placement;
|
|
7
|
+
engines;
|
|
8
|
+
projectId;
|
|
9
|
+
constructor(model, placement, engines, options) {
|
|
10
|
+
this.model = model;
|
|
11
|
+
this.placement = placement;
|
|
12
|
+
if (placement.contract < 4)
|
|
13
|
+
throw new MigrationRefused('operator inspection requires a generation-bearing placement map');
|
|
14
|
+
checkMapProject(placement, options.projectId);
|
|
15
|
+
if (model.version !== placement.modelVersion)
|
|
16
|
+
throw new MigrationRefused('operator inspection needs the model named by its map');
|
|
17
|
+
const missing = [...new Set(Object.values(placement.groups).flatMap((spot) => [spot.source, ...spot.derived].map((material) => material.engine)))].filter((name) => engines[name] === undefined).sort();
|
|
18
|
+
if (missing.length > 0)
|
|
19
|
+
throw new MigrationRefused(`operator inspection is missing engines ${JSON.stringify(missing)}`);
|
|
20
|
+
this.engines = Object.freeze({ ...engines });
|
|
21
|
+
this.projectId = options.projectId;
|
|
22
|
+
Object.freeze(this);
|
|
23
|
+
}
|
|
24
|
+
engineNamed(name) {
|
|
25
|
+
const engine = this.engines[name];
|
|
26
|
+
if (engine === undefined)
|
|
27
|
+
throw new MigrationRefused(`operator inspection has no engine ${name}`);
|
|
28
|
+
return engine;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=inspection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspection.js","sourceRoot":"","sources":["../src/inspection.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAYjD,MAAM,OAAO,iBAAiB;IAGP;IAA8B;IAFlC,OAAO,CAAkC;IACjD,SAAS,CAAQ;IAC1B,YAAqB,KAAmB,EAAW,SAAuB,EACxE,OAAyC,EAAE,OAA8B;QADtD,UAAK,GAAL,KAAK,CAAc;QAAW,cAAS,GAAT,SAAS,CAAc;QAExE,IAAI,SAAS,CAAC,QAAQ,GAAG,CAAC;YAAE,MAAM,IAAI,gBAAgB,CAAC,iEAAiE,CAAC,CAAA;QACzH,eAAe,CAAC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QAC7C,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,YAAY;YAAE,MAAM,IAAI,gBAAgB,CAAC,sDAAsD,CAAC,CAAA;QAChI,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAC3E,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,EAAE,CAAA;QAC3H,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,gBAAgB,CAAC,0CAA0C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QACvH,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAA;QAC5C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;QAClC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC;IACD,WAAW,CAAC,IAAY;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,gBAAgB,CAAC,qCAAqC,IAAI,EAAE,CAAC,CAAA;QACjG,OAAO,MAAM,CAAA;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The boundary between our bugs and the client's uptime.
|
|
3
|
+
*
|
|
4
|
+
* This library runs inside somebody else's application. A defect in our telemetry or our
|
|
5
|
+
* aggregation must not take down their request, and the honest way to guarantee that is to route
|
|
6
|
+
* every purely-internal side effect through one function that cannot throw.
|
|
7
|
+
*
|
|
8
|
+
* The hard part is not the try/catch. It is deciding what counts as internal, and getting that
|
|
9
|
+
* wrong in either direction is bad: swallow too little and a bug in a counter becomes a customer's
|
|
10
|
+
* outage; swallow too much and a write that never happened is reported as success, which is the
|
|
11
|
+
* worst thing this library could do.
|
|
12
|
+
*
|
|
13
|
+
* So the rule is narrow and stated once here: **internal means it cannot change whether the
|
|
14
|
+
* client's operation was performed correctly.** Aggregating a telemetry window is internal.
|
|
15
|
+
* Choosing which materialisation a read goes to is *not* - a wrong route returns wrong data.
|
|
16
|
+
* Writing a row is not. Verifying a placement map's signature is not, because the map decides where
|
|
17
|
+
* data is written.
|
|
18
|
+
*
|
|
19
|
+
* A failure is **counted**, and that counter is what makes this honest: a library that swallows
|
|
20
|
+
* silently is indistinguishable from one that works. It is not logged, unlike the reference
|
|
21
|
+
* implementation, and the difference is deliberate - this library has no logging channel at all
|
|
22
|
+
* (see `tests/silence.test.ts`), so a line here would go to whatever the client's process prints
|
|
23
|
+
* with no handler to turn it off. The counter is readable instead.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* How many times each guarded operation has failed, by name.
|
|
27
|
+
*
|
|
28
|
+
* Exposed rather than hidden. Swallowing a failure and leaving no trace of it would make this
|
|
29
|
+
* library indistinguishable from one that works, and a client should be able to see that our code
|
|
30
|
+
* is misbehaving inside their process even when we cannot tell them.
|
|
31
|
+
*/
|
|
32
|
+
export declare function internalFailures(): Record<string, number>;
|
|
33
|
+
/** For tests. */
|
|
34
|
+
export declare function resetInternalFailures(): void;
|
|
35
|
+
/**
|
|
36
|
+
* Run a purely-internal operation. Never throws.
|
|
37
|
+
*
|
|
38
|
+
* `what` names the operation and becomes the key in {@link internalFailures}, so it should be
|
|
39
|
+
* stable across releases - a client may be alerting on it. The names match the reference
|
|
40
|
+
* implementation's, because a client running both languages has one dashboard.
|
|
41
|
+
*/
|
|
42
|
+
export declare function guard<T>(what: string, operation: () => T): T | undefined;
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The boundary between our bugs and the client's uptime.
|
|
3
|
+
*
|
|
4
|
+
* This library runs inside somebody else's application. A defect in our telemetry or our
|
|
5
|
+
* aggregation must not take down their request, and the honest way to guarantee that is to route
|
|
6
|
+
* every purely-internal side effect through one function that cannot throw.
|
|
7
|
+
*
|
|
8
|
+
* The hard part is not the try/catch. It is deciding what counts as internal, and getting that
|
|
9
|
+
* wrong in either direction is bad: swallow too little and a bug in a counter becomes a customer's
|
|
10
|
+
* outage; swallow too much and a write that never happened is reported as success, which is the
|
|
11
|
+
* worst thing this library could do.
|
|
12
|
+
*
|
|
13
|
+
* So the rule is narrow and stated once here: **internal means it cannot change whether the
|
|
14
|
+
* client's operation was performed correctly.** Aggregating a telemetry window is internal.
|
|
15
|
+
* Choosing which materialisation a read goes to is *not* - a wrong route returns wrong data.
|
|
16
|
+
* Writing a row is not. Verifying a placement map's signature is not, because the map decides where
|
|
17
|
+
* data is written.
|
|
18
|
+
*
|
|
19
|
+
* A failure is **counted**, and that counter is what makes this honest: a library that swallows
|
|
20
|
+
* silently is indistinguishable from one that works. It is not logged, unlike the reference
|
|
21
|
+
* implementation, and the difference is deliberate - this library has no logging channel at all
|
|
22
|
+
* (see `tests/silence.test.ts`), so a line here would go to whatever the client's process prints
|
|
23
|
+
* with no handler to turn it off. The counter is readable instead.
|
|
24
|
+
*/
|
|
25
|
+
const failures = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* How many times each guarded operation has failed, by name.
|
|
28
|
+
*
|
|
29
|
+
* Exposed rather than hidden. Swallowing a failure and leaving no trace of it would make this
|
|
30
|
+
* library indistinguishable from one that works, and a client should be able to see that our code
|
|
31
|
+
* is misbehaving inside their process even when we cannot tell them.
|
|
32
|
+
*/
|
|
33
|
+
export function internalFailures() {
|
|
34
|
+
return Object.fromEntries([...failures.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
|
|
35
|
+
}
|
|
36
|
+
/** For tests. */
|
|
37
|
+
export function resetInternalFailures() {
|
|
38
|
+
failures.clear();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Run a purely-internal operation. Never throws.
|
|
42
|
+
*
|
|
43
|
+
* `what` names the operation and becomes the key in {@link internalFailures}, so it should be
|
|
44
|
+
* stable across releases - a client may be alerting on it. The names match the reference
|
|
45
|
+
* implementation's, because a client running both languages has one dashboard.
|
|
46
|
+
*/
|
|
47
|
+
export function guard(what, operation) {
|
|
48
|
+
try {
|
|
49
|
+
return operation();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
failures.set(what, (failures.get(what) ?? 0) + 1);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=internal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;AAE1C;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACrG,CAAC;AAED,iBAAiB;AACjB,MAAM,UAAU,qBAAqB;IACnC,QAAQ,CAAC,KAAK,EAAE,CAAA;AAClB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAI,IAAY,EAAE,SAAkB;IACvD,IAAI,CAAC;QACH,OAAO,SAAS,EAAE,CAAA;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QACjD,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC"}
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one derivation this library needs from a model that a map does not already carry.
|
|
3
|
+
*
|
|
4
|
+
* A placement map states a layout - table names and **dialect** column types - so nothing here has
|
|
5
|
+
* to work out what a column is called or how PostgreSQL spells a timestamp. That is the planner's
|
|
6
|
+
* job and it is in the control plane.
|
|
7
|
+
*
|
|
8
|
+
* What a map does not carry is the **neutral** type of a column, and one gate needs it: the
|
|
9
|
+
* migration precision check. `timestamptz` is microseconds in PostgreSQL and milliseconds in
|
|
10
|
+
* ClickHouse, so copying that column in that direction truncates every value that has more
|
|
11
|
+
* precision - silently, because the insert succeeds and the value comes back changed. Deciding that
|
|
12
|
+
* from the dialect spellings would mean parsing `DateTime64(3, 'UTC')`, which is a second
|
|
13
|
+
* implementation of the type mapping; deciding it from the neutral vocabulary is a lookup.
|
|
14
|
+
*
|
|
15
|
+
* So this file is deliberately one function and not a layout module. The reference implementation
|
|
16
|
+
* has a much larger one because it also derives the layout itself for the control plane to issue;
|
|
17
|
+
* a client library never does that, and porting it here would be code with no caller and a rule to
|
|
18
|
+
* keep in sync for nothing.
|
|
19
|
+
*/
|
|
20
|
+
import type { Group } from './groups.js';
|
|
21
|
+
import type { LogicalModel } from './model.js';
|
|
22
|
+
/**
|
|
23
|
+
* Every column a group's tables need, per entity, in the neutral vocabulary.
|
|
24
|
+
*
|
|
25
|
+
* A group has a foreign-key column for every relation whose source is a member, named
|
|
26
|
+
* `<relation>_<target key field>` - one column per key field, for a single-field key and a
|
|
27
|
+
* composite one alike. Today those add no *type* the group did not already have, because a relation
|
|
28
|
+
* unions its two ends into one colocation group, so the target's key fields are declared fields of
|
|
29
|
+
* a member. That is a fact about how groups are formed rather than about layouts, and this
|
|
30
|
+
* derivation does not rely on it.
|
|
31
|
+
*
|
|
32
|
+
* Declared field order first, then relations in name order - the same order the reference produces,
|
|
33
|
+
* because the migration gate iterates these and a refusal has to name the same column first in both
|
|
34
|
+
* languages.
|
|
35
|
+
*/
|
|
36
|
+
export declare function groupColumns(model: LogicalModel, group: Group): Readonly<Record<string, Readonly<Record<string, string>>>>;
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one derivation this library needs from a model that a map does not already carry.
|
|
3
|
+
*
|
|
4
|
+
* A placement map states a layout - table names and **dialect** column types - so nothing here has
|
|
5
|
+
* to work out what a column is called or how PostgreSQL spells a timestamp. That is the planner's
|
|
6
|
+
* job and it is in the control plane.
|
|
7
|
+
*
|
|
8
|
+
* What a map does not carry is the **neutral** type of a column, and one gate needs it: the
|
|
9
|
+
* migration precision check. `timestamptz` is microseconds in PostgreSQL and milliseconds in
|
|
10
|
+
* ClickHouse, so copying that column in that direction truncates every value that has more
|
|
11
|
+
* precision - silently, because the insert succeeds and the value comes back changed. Deciding that
|
|
12
|
+
* from the dialect spellings would mean parsing `DateTime64(3, 'UTC')`, which is a second
|
|
13
|
+
* implementation of the type mapping; deciding it from the neutral vocabulary is a lookup.
|
|
14
|
+
*
|
|
15
|
+
* So this file is deliberately one function and not a layout module. The reference implementation
|
|
16
|
+
* has a much larger one because it also derives the layout itself for the control plane to issue;
|
|
17
|
+
* a client library never does that, and porting it here would be code with no caller and a rule to
|
|
18
|
+
* keep in sync for nothing.
|
|
19
|
+
*/
|
|
20
|
+
import { compareCodePoints } from './canonical.js';
|
|
21
|
+
/**
|
|
22
|
+
* Every column a group's tables need, per entity, in the neutral vocabulary.
|
|
23
|
+
*
|
|
24
|
+
* A group has a foreign-key column for every relation whose source is a member, named
|
|
25
|
+
* `<relation>_<target key field>` - one column per key field, for a single-field key and a
|
|
26
|
+
* composite one alike. Today those add no *type* the group did not already have, because a relation
|
|
27
|
+
* unions its two ends into one colocation group, so the target's key fields are declared fields of
|
|
28
|
+
* a member. That is a fact about how groups are formed rather than about layouts, and this
|
|
29
|
+
* derivation does not rely on it.
|
|
30
|
+
*
|
|
31
|
+
* Declared field order first, then relations in name order - the same order the reference produces,
|
|
32
|
+
* because the migration gate iterates these and a refusal has to name the same column first in both
|
|
33
|
+
* languages.
|
|
34
|
+
*/
|
|
35
|
+
export function groupColumns(model, group) {
|
|
36
|
+
const out = {};
|
|
37
|
+
const relations = [...model.relations].sort((a, b) => compareCodePoints(a.name, b.name));
|
|
38
|
+
for (const member of group.members) {
|
|
39
|
+
const spec = model.entities.find((entity) => entity.name === member);
|
|
40
|
+
if (spec === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
const columns = {};
|
|
43
|
+
for (const field of spec.fields)
|
|
44
|
+
columns[field.name] = field.type;
|
|
45
|
+
for (const relation of relations) {
|
|
46
|
+
if (relation.source !== member)
|
|
47
|
+
continue;
|
|
48
|
+
const target = model.entities.find((entity) => entity.name === relation.target);
|
|
49
|
+
if (target === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
for (const keyField of target.key) {
|
|
52
|
+
const declared = target.fields.find((field) => field.name === keyField);
|
|
53
|
+
if (declared === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
columns[`${relation.name}_${keyField}`] = declared.type;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
out[member] = columns;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAIlD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAmB,EACnB,KAAY;IAEZ,MAAM,GAAG,GAA2C,EAAE,CAAA;IACtD,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACxF,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;QACpE,IAAI,IAAI,KAAK,SAAS;YAAE,SAAQ;QAChC,MAAM,OAAO,GAA2B,EAAE,CAAA;QAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,CAAA;QACjE,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAQ;YACxC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM,CAAC,CAAA;YAC/E,IAAI,MAAM,KAAK,SAAS;gBAAE,SAAQ;YAClC,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;gBAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;gBACvE,IAAI,QAAQ,KAAK,SAAS;oBAAE,SAAQ;gBACpC,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAA;YACzD,CAAC;QACH,CAAC;QACD,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAA;IACvB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { MigrationView } from './inspection.js';
|
|
2
|
+
import type { VerificationRequest } from './verification.js';
|
|
3
|
+
import { MIGRATABLE_MEMBERS } from './capabilities.js';
|
|
4
|
+
import type { Row, Session } from './session.js';
|
|
5
|
+
/**
|
|
6
|
+
* Rows per chunk, by default.
|
|
7
|
+
*
|
|
8
|
+
* A thousand rather than a round ten thousand: a chunk is held in memory twice during `verify` -
|
|
9
|
+
* the source's rows and the target's - and the number that matters is not throughput but how much
|
|
10
|
+
* work a crash discards, which is one chunk.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CHUNK_ROWS = 1000;
|
|
13
|
+
/**
|
|
14
|
+
* Sub-second digits each dialect keeps, for the neutral types where dialects differ.
|
|
15
|
+
*
|
|
16
|
+
* Both current SQL timestamp types retain six digits. This table guards future dialects; the
|
|
17
|
+
* runtime value must preserve those digits too, which is why the adapters return Timestamp.
|
|
18
|
+
*/
|
|
19
|
+
export declare const DIALECT_PRECISION: Readonly<Record<string, number>>;
|
|
20
|
+
/**
|
|
21
|
+
* Neutral types a copy between dialects does not silently change.
|
|
22
|
+
*
|
|
23
|
+
* Not the same claim as "every value survives". PostgreSQL's `date` has a wider range than
|
|
24
|
+
* ClickHouse's `Date32`, so a date in the year 1800 does not survive that move - but it fails
|
|
25
|
+
* *loudly*, on the insert or as a mismatch in `verify`. The line drawn here is silent-and-universal
|
|
26
|
+
* loss, which is a much smaller set than lossy, and it is the set worth a refusal that arrives
|
|
27
|
+
* before the work.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PRECISION_INDEPENDENT: ReadonlySet<string>;
|
|
30
|
+
/**
|
|
31
|
+
* What an engine adapter has to offer for a group to be migrated into or out of it.
|
|
32
|
+
*
|
|
33
|
+
* Optional, exactly like the watermark store and for the same reason: requiring these of the engine
|
|
34
|
+
* interface would break every adapter anybody has written, including fakes in someone else's test
|
|
35
|
+
* suite, for a capability an engine with a fixed schema cannot provide. Non-participation is a
|
|
36
|
+
* **named refusal** rather than a silent skip, because a migration that quietly copies nothing is
|
|
37
|
+
* the worst outcome available here.
|
|
38
|
+
*/
|
|
39
|
+
export interface Migratable {
|
|
40
|
+
readonly dialect: string;
|
|
41
|
+
keyRange(table: string, order: readonly string[], options?: {
|
|
42
|
+
readonly after?: readonly unknown[];
|
|
43
|
+
readonly upto?: readonly unknown[];
|
|
44
|
+
readonly limit?: number;
|
|
45
|
+
}): Promise<Row[]>;
|
|
46
|
+
nthKey(table: string, order: readonly string[], options: {
|
|
47
|
+
readonly position: number;
|
|
48
|
+
}): Promise<unknown[] | null>;
|
|
49
|
+
copyIn(table: string, rows: readonly Row[]): Promise<void>;
|
|
50
|
+
count(table: string): Promise<number>;
|
|
51
|
+
get(table: string, key: Readonly<Row>): Promise<Row | null>;
|
|
52
|
+
backfillMarker(options: {
|
|
53
|
+
readonly materialization: string;
|
|
54
|
+
readonly entity: string;
|
|
55
|
+
}): Promise<number>;
|
|
56
|
+
recordBackfillMarker(options: {
|
|
57
|
+
readonly materialization: string;
|
|
58
|
+
readonly entity: string;
|
|
59
|
+
readonly rows: number;
|
|
60
|
+
}): Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
/** A compile-time ratchet between the interface and the list the runtime check uses. */
|
|
63
|
+
export declare const MIGRATABLE_IS_TOTAL: (typeof MIGRATABLE_MEMBERS)[number] extends keyof Migratable ? true : never;
|
|
64
|
+
/**
|
|
65
|
+
* The ordering columns for a keyset scan, refusing an empty one.
|
|
66
|
+
*
|
|
67
|
+
* Here rather than in each adapter so that the two cannot disagree about it, and exported because
|
|
68
|
+
* an adapter written outside this repository has the same argument to check. An empty order is not
|
|
69
|
+
* a scan of everything in an unspecified order - it is a paginated scan with no pagination, which
|
|
70
|
+
* returns the same first page forever.
|
|
71
|
+
*/
|
|
72
|
+
export declare function keyColumns(order: readonly string[], table: string): readonly string[];
|
|
73
|
+
/** A bound has one value per ordering column, or the comparison is not the one intended. */
|
|
74
|
+
export declare function sameWidth(bound: readonly unknown[], cols: readonly string[], name: string): void;
|
|
75
|
+
/** How far one entity's copy into one target has got. */
|
|
76
|
+
export interface EntityProgress {
|
|
77
|
+
readonly entity: string;
|
|
78
|
+
readonly engine: string;
|
|
79
|
+
readonly table: string;
|
|
80
|
+
/** The marker: rows of this entity copied into this target, across every run. */
|
|
81
|
+
readonly rowsCopied: number;
|
|
82
|
+
readonly rowsThisRun: number;
|
|
83
|
+
readonly chunks: number;
|
|
84
|
+
/** Whether the last chunk came back short, which is what "the tail is the fan-out's now" means. */
|
|
85
|
+
readonly complete: boolean;
|
|
86
|
+
}
|
|
87
|
+
export declare function entityProgressRecord(progress: EntityProgress): Record<string, unknown>;
|
|
88
|
+
/** What one call to {@link backfill} did, per entity and per target. */
|
|
89
|
+
export interface BackfillProgress {
|
|
90
|
+
readonly group: string;
|
|
91
|
+
readonly entities: readonly EntityProgress[];
|
|
92
|
+
/** Every entity of every target has reached the end of its table at least once. */
|
|
93
|
+
readonly complete: boolean;
|
|
94
|
+
readonly rowsThisRun: number;
|
|
95
|
+
}
|
|
96
|
+
export declare function backfillRecord(progress: BackfillProgress): Record<string, unknown>;
|
|
97
|
+
export declare function backfillForAHuman(progress: BackfillProgress): string;
|
|
98
|
+
/**
|
|
99
|
+
* One source row the target does not have, or has differently.
|
|
100
|
+
*
|
|
101
|
+
* **This holds the client's own data and it is the reason the verify record does not.** The key is
|
|
102
|
+
* here because "which row" is the first thing an operator needs and a count cannot say it; it stays
|
|
103
|
+
* on their machine because a row is the one thing that must never travel to us. The two facts are
|
|
104
|
+
* the same decision seen from two sides.
|
|
105
|
+
*/
|
|
106
|
+
export interface Difference {
|
|
107
|
+
readonly entity: string;
|
|
108
|
+
readonly table: string;
|
|
109
|
+
readonly key: Readonly<Row>;
|
|
110
|
+
/** Columns whose values differ, or empty when the row is absent from the target altogether. */
|
|
111
|
+
readonly columns: readonly string[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What the comparison found, in the shape the gate needs and nothing wider.
|
|
115
|
+
*
|
|
116
|
+
* {@link verifyRecord} is the boundary. It carries seven counts, and the control plane reads exactly
|
|
117
|
+
* those - so there is no field in which a value of the client's could travel, and adding one would
|
|
118
|
+
* be a visible change to that function rather than an accident somewhere in a call chain.
|
|
119
|
+
* `differences` is the other half of that: the detail that makes a mismatch fixable, kept here and
|
|
120
|
+
* deliberately absent from the record.
|
|
121
|
+
*/
|
|
122
|
+
export interface VerifyReport {
|
|
123
|
+
readonly request?: VerificationRequest;
|
|
124
|
+
readonly at: string;
|
|
125
|
+
readonly group: string;
|
|
126
|
+
readonly chunksCompared: number;
|
|
127
|
+
readonly chunksMismatched: number;
|
|
128
|
+
readonly tailRowsRead: number;
|
|
129
|
+
readonly tailRowsMissingInTarget: number;
|
|
130
|
+
readonly rowsSource: number;
|
|
131
|
+
readonly rowsTarget: number;
|
|
132
|
+
readonly differences: readonly Difference[];
|
|
133
|
+
readonly differencesSuppressed: number;
|
|
134
|
+
/**
|
|
135
|
+
* Whether the target holds everything the source holds. Zero tolerance, both terms.
|
|
136
|
+
*
|
|
137
|
+
* Zero rather than a threshold, and the reason is arithmetic rather than principled: any non-zero
|
|
138
|
+
* threshold is an answer to "how many of your rows may we lose", and there is no number to say
|
|
139
|
+
* out loud there.
|
|
140
|
+
*/
|
|
141
|
+
readonly matched: boolean;
|
|
142
|
+
}
|
|
143
|
+
/** The seven counts the gate reads. **Numbers, never rows** - see {@link VerifyReport}. */
|
|
144
|
+
export declare function verifyRecord(report: VerifyReport): Record<string, unknown>;
|
|
145
|
+
export declare function verifyForAHuman(report: VerifyReport): string;
|
|
146
|
+
/**
|
|
147
|
+
* Why a copy of these columns between these two dialects would change values, or `null`.
|
|
148
|
+
*
|
|
149
|
+
* A string rather than a throw, and dialect names rather than engines, because two doors ask this
|
|
150
|
+
* question and only one of them used to. `backfill` refuses a copy that would truncate; the write
|
|
151
|
+
* fan-out in `Session` was doing the same truncation one row at a time, for as long as an
|
|
152
|
+
* `also_write` map was in force. Measured on live servers before it was fixed: a `timestamptz`
|
|
153
|
+
* written as `09:30:15.123456` came back from PostgreSQL unchanged and from ClickHouse as
|
|
154
|
+
* `09:30:15.123`, with no error on either side.
|
|
155
|
+
*/
|
|
156
|
+
export declare function precisionRefusal(group: string, entity: string, columns: Readonly<Record<string, string>>, sourceDialect: string, targetDialect: string): string | null;
|
|
157
|
+
export interface BackfillOptions {
|
|
158
|
+
readonly chunkRows?: number;
|
|
159
|
+
/**
|
|
160
|
+
* Bound the work to that many chunks per entity, for an operator who wants to copy for a while
|
|
161
|
+
* and stop, and for a test that needs to interrupt at a known point. Absent runs each entity to
|
|
162
|
+
* the end of its table.
|
|
163
|
+
*/
|
|
164
|
+
readonly stopAfter?: number;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Copy a group's existing rows into every fan-out target the map names. Resumable.
|
|
168
|
+
*
|
|
169
|
+
* Called again after an interruption it picks up from the marker, and called again after completion
|
|
170
|
+
* it does nothing - both because the marker is durable and lives in the target engine, next to the
|
|
171
|
+
* rows it describes. That is the correct coupling: a target dropped and recreated loses its marker
|
|
172
|
+
* with its data, and a marker kept anywhere else would claim work that no longer exists.
|
|
173
|
+
*/
|
|
174
|
+
export declare function backfill(session: Session, group: string, options?: BackfillOptions): Promise<BackfillProgress>;
|
|
175
|
+
export interface VerifyOptions {
|
|
176
|
+
readonly request?: VerificationRequest;
|
|
177
|
+
readonly chunkRows?: number;
|
|
178
|
+
/**
|
|
179
|
+
* The instant the report is stamped with. Absent reads the calendar clock.
|
|
180
|
+
*
|
|
181
|
+
* An argument at all because this library reads a wall clock in exactly two places and both are
|
|
182
|
+
* pinned by a test - a map carries no date, so the only thing a clock here could do is stamp a
|
|
183
|
+
* report. Passing one makes the report reproducible for a caller who wants that.
|
|
184
|
+
*/
|
|
185
|
+
readonly at?: string;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Compare both copies of a group and report counts. The gate reads the counts, not the rows.
|
|
189
|
+
*
|
|
190
|
+
* Reads the **source first and the target second**, always, and the order is load-bearing. A write
|
|
191
|
+
* is in the source before it is in the copy, so a row read from the source may not have reached the
|
|
192
|
+
* copy yet - but the copy is read after the whole source window, so the window has already elapsed.
|
|
193
|
+
* Anything still missing is then looked up once more by point read, on what is normally an empty
|
|
194
|
+
* set. Reversing the two reads would make this flaky in the direction that stops a healthy
|
|
195
|
+
* migration.
|
|
196
|
+
*/
|
|
197
|
+
export declare function verify(session: MigrationView, group: string, options?: VerifyOptions): Promise<VerifyReport>;
|