@rudderhq/server 0.7.23-canary.8 → 0.7.23
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/services/migration-preflight.d.ts +96 -0
- package/dist/services/migration-preflight.d.ts.map +1 -0
- package/dist/services/migration-preflight.js +645 -0
- package/dist/services/migration-preflight.js.map +1 -0
- package/dist/services/migration-preflight.test.d.ts +2 -0
- package/dist/services/migration-preflight.test.d.ts.map +1 -0
- package/dist/services/migration-preflight.test.js +318 -0
- package/dist/services/migration-preflight.test.js.map +1 -0
- package/package.json +14 -14
- package/ui-dist/assets/{LibraryLiveSurface-CIv5EnMA.js → LibraryLiveSurface-D3A3qnOk.js} +1 -1
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-BOfIouaW.js → abnfDiagram-VRR7QNED-7zCg64WK.js} +1 -1
- package/ui-dist/assets/{addon-fit-CTfNQbtS.js → addon-fit-BO0FuRq2.js} +1 -1
- package/ui-dist/assets/{arc-DCSHQbbO.js → arc-Dk5IuOd1.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-DMiC_MxM.js → architectureDiagram-ZJ3FMSHR-BEtERM-c.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-KyOGsC9f.js → blockDiagram-677ZJIJ3-Cs72WIlc.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-CBkdDMAQ.js → c4Diagram-LMCZKHZV-B5h_fzju.js} +1 -1
- package/ui-dist/assets/channel-CADf2NCi.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-CgP8TvC6.js → chunk-2Q5K7J3B-D9EYf4zB.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-DvfrxldO.js → chunk-32BRIVSS-Bh9Gaxlv.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-COEu5-uU.js → chunk-5VM5RSS4-CYV7PxfU.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-DcElTdSh.js → chunk-EX3LRPZG-CBvIZ1wI.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-Xlx0W99o.js → chunk-JWPE2WC7-C0dVyM-J.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-BT4Mmemz.js → chunk-MOJQB5TN-D0Lu8zGy.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-Dk_Gryfr.js → chunk-RYQCIY6F-Bnj9lQjq.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-Dlh81eQM.js → chunk-V7JOEXUC-9Vn5xKU5.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-CeXzHEzB.js → chunk-VR4S4FIN-COryPczc.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-CL9qSZuZ.js → chunk-XXDRQBXY-Bc_bmocO.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-ad8qW_FN.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-ad8qW_FN.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-DPq6fTzX.js → cose-bilkent-JH36ORCC-BJdnStBC.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-_KhOPE25.js → cynefin-VYW2F7L2-BuUfdk44.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-B2YvTEWK.js → cynefinDiagram-TSTJHNR4-DG4Nrz5z.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-DsAGzP_N.js → dagre-VKFMJZFB-CadCTRAa.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-DVEsxneV.js → diagram-FQU43EPY-42zVLICb.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-BzT0Yh2Z.js → diagram-G47NLZAW-CzaqDcfB.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-DYMdwyvQ.js → diagram-NH7WQ7WH-CIZKotNK.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-BKwp_407.js → diagram-OA4YK3LP-DdXAyR0A.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-6yVN4E1W.js → diagram-WEI45ONY-DPW2Gt0N.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-E2mJ24jm.js → ebnfDiagram-CCIWWBDH-DZPzdEIa.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-CEr35b2y.js → erDiagram-Q63AITRT-BexTDV7N.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-1vsYluTK.js → flowDiagram-23GEKE2U-Bpbw_M6z.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-DZ6CFHMd.js → ganttDiagram-NO4QXBWP-ChZeTtMZ.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-CG4t88m1.js → gitGraphDiagram-IHSO6WYX-DOJdIIiX.js} +1 -1
- package/ui-dist/assets/{graph-DGqKIFAo.js → graph-DG8Wse_7.js} +1 -1
- package/ui-dist/assets/{index-BbNopx8o.css → index-3zlQxsgk.css} +1 -1
- package/ui-dist/assets/{index-JDf4hUVq.js → index-BHEjyNv5.js} +1 -1
- package/ui-dist/assets/{index-ch8zkMEm.js → index-BLEcyXn4.js} +1 -1
- package/ui-dist/assets/{index-U9CohTdB.js → index-BM1H2EfO.js} +1 -1
- package/ui-dist/assets/{index-CmzgkYS5.js → index-BSdgGKQA.js} +551 -556
- package/ui-dist/assets/{index-DBTGJzwz.js → index-BSeNAIn9.js} +1 -1
- package/ui-dist/assets/{index-BHRSmdkW.js → index-BTOInSrJ.js} +1 -1
- package/ui-dist/assets/{index-DUGw7tdV.js → index-BUo8TsYl.js} +1 -1
- package/ui-dist/assets/{index-BtN8wtRk.js → index-C8K4SQdi.js} +1 -1
- package/ui-dist/assets/{index-Djys6yqX.js → index-CnSxxt22.js} +1 -1
- package/ui-dist/assets/{index-BUQl50LW.js → index-Cv8WgN7P.js} +1 -1
- package/ui-dist/assets/{index-CGR6XwGO.js → index-D21QqdV1.js} +1 -1
- package/ui-dist/assets/{index-BLOcE97I.js → index-DXXZk01Q.js} +1 -1
- package/ui-dist/assets/{index-BhNMVm6Z.js → index-FQGPwYVG.js} +1 -1
- package/ui-dist/assets/{index-BUQOBeNG.js → index-IwqVKLSi.js} +1 -1
- package/ui-dist/assets/{index-BjLapuUN.js → index-mQrdToWN.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-BGI84M8V.js → infoDiagram-FWYZ7A6U-Bn8Qy4na.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-C-eLDXPi.js → ishikawaDiagram-FXEZZL3T-Bq3tS5LT.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-DesBN-XJ.js → journeyDiagram-5HDEW3XC-CAeTaVfF.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-DJ_uZnJ4.js → kanban-definition-HUTT4EX6-CJvF765h.js} +1 -1
- package/ui-dist/assets/{layout-BQbdUGNl.js → layout-CCBIwIuW.js} +1 -1
- package/ui-dist/assets/{linear-C_0O2JFz.js → linear-D39WXdj9.js} +1 -1
- package/ui-dist/assets/{map-DK6h0efU.js → map-DTcRLXCo.js} +1 -1
- package/ui-dist/assets/{mermaid.core-Bktugtti.js → mermaid.core-CN7rTRdU.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-UvTzZf6V.js → mindmap-definition-LN4V7U3C-ClZKjxVR.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-BDKDsh9L.js → pegDiagram-2B236MQR-CR3s25Ek.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-DPAgxPjv.js → pieDiagram-ENE6RG2P-NClDuoF5.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-B40ZXjR-.js → quadrantDiagram-ABIIQ3AL-DF9YyNqL.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-D-tsLCpc.js → railroadDiagram-RFXS5EU6-C25-h8pU.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-BXsZ2n_e.js → requirementDiagram-TGXJPOKE-xXi6Fm79.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-C7Sz9_51.js → sankeyDiagram-HTMAVEWB-DLShdNS6.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-DKXztgS-.js → sequenceDiagram-DBY2YBRQ-DoE9QzMr.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-BgsKe5Gc.js → sizeCapture-X5ZJPWSS-Bg96fO6m.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-DXVrScBn.js → stateDiagram-2N3HPSRC-E3PPkS-v.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-8KfukcHf.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-C25nfW7h.js → swimlanes-5IMT3BWC-CR1oL5ol.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CvC7UZmz.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-yJPexRCp.js → timeline-definition-FHXFAJF6-BEf5i7lr.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-D7unTYZ5.js → vennDiagram-L72KCM5P-DR_4a4ZJ.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-DCNwasEy.js → wardleyDiagram-EHGQE667-Dp6HNEIB.js} +1 -1
- package/ui-dist/assets/{xterm-D3QSvD8Z.js → xterm-DPOINJzy.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-a-_5I2F6.js → xychartDiagram-FW5EYKEG-b-RouYSf.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-DZPKvi4p.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-BH5h722g.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-BH5h722g.js +0 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-BReCGW-n.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-eRWsyGRE.js +0 -8
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import { resolveNativeCommand } from "@rudderhq/agent-runtime-utils";
|
|
2
|
+
import { createMigrationManifest, getMigrationSourcePaths, } from "@rudderhq/db";
|
|
3
|
+
import { resolveRudderNativeCapability } from "@rudderhq/shared";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
export const MIGRATION_PREFLIGHT_SCHEMA = "rudder.migration-preflight/v1";
|
|
9
|
+
export const MIGRATION_PREFLIGHT_PROTOCOL_VERSION = 1;
|
|
10
|
+
export const MIGRATION_PREFLIGHT_DATABASE_URL_ENV = "RUDDER_MIGRATION_PREFLIGHT_DATABASE_URL";
|
|
11
|
+
export const MIGRATION_PREFLIGHT_PATH_ENV = "RUDDER_NATIVE_MIGRATION_PREFLIGHT_PATH";
|
|
12
|
+
const LEGACY_MIGRATION_PREFLIGHT_PATH_ENV = "RUDDER_MIGRATION_PREFLIGHT_PATH";
|
|
13
|
+
const MAX_REQUEST_BYTES = 256 * 1024;
|
|
14
|
+
const MAX_DATABASE_URL_BYTES = 8 * 1024;
|
|
15
|
+
const MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
|
|
16
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
17
|
+
const MAX_TIMEOUT_MS = 30_000;
|
|
18
|
+
const MAX_HISTORY_ITEMS = 4_096;
|
|
19
|
+
const PREFLIGHT_STATUSES = [
|
|
20
|
+
"bootstrap",
|
|
21
|
+
"unsafe-legacy",
|
|
22
|
+
"pending",
|
|
23
|
+
"mismatch",
|
|
24
|
+
"missing-core-schema",
|
|
25
|
+
"current",
|
|
26
|
+
];
|
|
27
|
+
const HISTORY_STATUSES = ["upToDate", "needsMigrations"];
|
|
28
|
+
const HISTORY_REASONS = [
|
|
29
|
+
"manifest-match",
|
|
30
|
+
"migration-journal-missing",
|
|
31
|
+
"empty-history",
|
|
32
|
+
"pending-migrations",
|
|
33
|
+
"manifest-mismatch",
|
|
34
|
+
"migration-journal-invalid",
|
|
35
|
+
];
|
|
36
|
+
const ERROR_CLASSIFICATIONS = ["protocol", "source", "configuration", "database"];
|
|
37
|
+
export class MigrationPreflightAdapterError extends Error {
|
|
38
|
+
classification;
|
|
39
|
+
code;
|
|
40
|
+
constructor(classification, code) {
|
|
41
|
+
super(`Migration preflight failed: ${code}`);
|
|
42
|
+
this.classification = classification;
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.name = "MigrationPreflightAdapterError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function expectedMigrationPreflightStatus(state) {
|
|
48
|
+
if (state.status === "upToDate")
|
|
49
|
+
return "current";
|
|
50
|
+
switch (state.reason) {
|
|
51
|
+
case "no-migration-journal-empty-db":
|
|
52
|
+
return "bootstrap";
|
|
53
|
+
case "no-migration-journal-non-empty-db":
|
|
54
|
+
return "unsafe-legacy";
|
|
55
|
+
case "pending-migrations":
|
|
56
|
+
return "pending";
|
|
57
|
+
case "missing-core-schema":
|
|
58
|
+
return "missing-core-schema";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function migrationPreflightSummary(report) {
|
|
62
|
+
return {
|
|
63
|
+
status: report.status,
|
|
64
|
+
tableCount: report.tableCount,
|
|
65
|
+
journalPresent: report.journalPresent,
|
|
66
|
+
journalSchema: report.journalSchema,
|
|
67
|
+
coreSchemaPresent: report.coreSchemaPresent,
|
|
68
|
+
organizationsTablePresent: report.organizationsTablePresent,
|
|
69
|
+
historyStatus: report.history.status,
|
|
70
|
+
historyReason: report.history.reason,
|
|
71
|
+
manifestFingerprint: report.history.manifestFingerprint,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export async function runMigrationPreflightBeforeNodeInspection(options) {
|
|
75
|
+
const policy = resolveRudderNativeCapability({
|
|
76
|
+
capability: "migration-preflight",
|
|
77
|
+
env: options.env ?? process.env,
|
|
78
|
+
});
|
|
79
|
+
if (!policy.enabled) {
|
|
80
|
+
return {
|
|
81
|
+
report: null,
|
|
82
|
+
required: policy.required,
|
|
83
|
+
manifestFingerprint: null,
|
|
84
|
+
manifestMigrationFileNames: null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const sourcePaths = options.sourcePaths ?? getMigrationSourcePaths();
|
|
88
|
+
const migrationManifest = await createMigrationManifest({
|
|
89
|
+
migrationsFolder: sourcePaths.migrationsFolder,
|
|
90
|
+
journalFile: sourcePaths.journalFile,
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
const report = await runMigrationPreflight({
|
|
94
|
+
databaseUrl: options.connectionString,
|
|
95
|
+
source: {
|
|
96
|
+
migrationsDir: sourcePaths.migrationsFolder,
|
|
97
|
+
journalFile: sourcePaths.journalFile,
|
|
98
|
+
expectedFingerprint: migrationManifest.fingerprint,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
options.logger.info({ label: options.label, ...migrationPreflightSummary(report) }, `${options.label} Rust migration preflight completed before Node migration inspection`);
|
|
102
|
+
return {
|
|
103
|
+
report,
|
|
104
|
+
required: policy.required,
|
|
105
|
+
manifestFingerprint: migrationManifest.fingerprint,
|
|
106
|
+
manifestMigrationFileNames: migrationManifest.entries.map(({ fileName }) => fileName),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const code = error instanceof MigrationPreflightAdapterError ? error.code : "unknown";
|
|
111
|
+
if (policy.required) {
|
|
112
|
+
throw new Error(`${options.label} Rust migration preflight is required but failed (${code}); refusing to inspect or mutate migration state.`, { cause: error });
|
|
113
|
+
}
|
|
114
|
+
options.logger.warn({ label: options.label, code }, `${options.label} Rust migration preflight unavailable; continuing with the Node migration authority`);
|
|
115
|
+
return {
|
|
116
|
+
report: null,
|
|
117
|
+
required: false,
|
|
118
|
+
manifestFingerprint: null,
|
|
119
|
+
manifestMigrationFileNames: null,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function assertMigrationPreflightAgreement(options) {
|
|
124
|
+
if (!options.report)
|
|
125
|
+
return;
|
|
126
|
+
const expectedStatus = expectedMigrationPreflightStatus(options.state);
|
|
127
|
+
if (options.report.status !== expectedStatus) {
|
|
128
|
+
const detail = `${options.report.status} != ${expectedStatus}`;
|
|
129
|
+
if (options.required) {
|
|
130
|
+
throw new Error(`${options.label} Rust migration preflight disagreed with the Node migration inspection (${detail}); refusing to mutate migration state.`);
|
|
131
|
+
}
|
|
132
|
+
options.logger.warn({ label: options.label, nativeStatus: options.report.status, nodeStatus: expectedStatus }, `${options.label} Rust migration preflight disagreed with the Node migration inspection; continuing with the Node migration authority`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const nodeAppliedMigrations = options.state.appliedMigrations;
|
|
136
|
+
const nodePendingMigrations = options.state.status === "needsMigrations"
|
|
137
|
+
? options.state.pendingMigrations
|
|
138
|
+
: [];
|
|
139
|
+
const orderedNodePendingMigrations = options.manifestMigrationFileNames
|
|
140
|
+
? orderMigrationsByManifest(nodePendingMigrations, options.manifestMigrationFileNames)
|
|
141
|
+
: nodePendingMigrations;
|
|
142
|
+
const nativeAppliedMigrations = options.report.history.appliedMigrations.map(({ fileName }) => fileName);
|
|
143
|
+
const nativePendingMigrations = options.report.history.pendingMigrations.map(({ fileName }) => fileName);
|
|
144
|
+
const expectedNativePendingMigrations = options.state.status === "needsMigrations"
|
|
145
|
+
&& options.state.reason === "missing-core-schema"
|
|
146
|
+
? []
|
|
147
|
+
: orderedNodePendingMigrations;
|
|
148
|
+
const disagreements = [];
|
|
149
|
+
if (options.manifestFingerprint === null
|
|
150
|
+
|| options.report.history.manifestFingerprint !== options.manifestFingerprint) {
|
|
151
|
+
disagreements.push("manifest-fingerprint");
|
|
152
|
+
}
|
|
153
|
+
if (!sameMigrationPlan(nativeAppliedMigrations, nodeAppliedMigrations)) {
|
|
154
|
+
disagreements.push("applied-migrations");
|
|
155
|
+
}
|
|
156
|
+
if (!sameMigrationPlan(nativePendingMigrations, expectedNativePendingMigrations)) {
|
|
157
|
+
disagreements.push("pending-migrations");
|
|
158
|
+
}
|
|
159
|
+
if (disagreements.length === 0)
|
|
160
|
+
return;
|
|
161
|
+
const detail = disagreements.join(", ");
|
|
162
|
+
if (options.required) {
|
|
163
|
+
throw new Error(`${options.label} Rust migration preflight disagreed with the Node migration plan (${detail}); refusing to mutate migration state.`);
|
|
164
|
+
}
|
|
165
|
+
options.logger.warn({
|
|
166
|
+
label: options.label,
|
|
167
|
+
nativeStatus: options.report.status,
|
|
168
|
+
nodeStatus: expectedStatus,
|
|
169
|
+
disagreements,
|
|
170
|
+
}, `${options.label} Rust migration preflight disagreed with the Node migration plan (${detail}); continuing with the Node migration authority`);
|
|
171
|
+
}
|
|
172
|
+
function sameMigrationPlan(left, right) {
|
|
173
|
+
return left.length === right.length && left.every((fileName, index) => fileName === right[index]);
|
|
174
|
+
}
|
|
175
|
+
function orderMigrationsByManifest(migrations, manifestFileNames) {
|
|
176
|
+
const orderByFileName = new Map(manifestFileNames.map((fileName, order) => [fileName, order]));
|
|
177
|
+
return migrations
|
|
178
|
+
.map((fileName, originalOrder) => ({ fileName, originalOrder }))
|
|
179
|
+
.sort((left, right) => {
|
|
180
|
+
const leftOrder = orderByFileName.get(left.fileName);
|
|
181
|
+
const rightOrder = orderByFileName.get(right.fileName);
|
|
182
|
+
if (leftOrder === undefined && rightOrder === undefined) {
|
|
183
|
+
return left.originalOrder - right.originalOrder;
|
|
184
|
+
}
|
|
185
|
+
if (leftOrder === undefined)
|
|
186
|
+
return 1;
|
|
187
|
+
if (rightOrder === undefined)
|
|
188
|
+
return -1;
|
|
189
|
+
return leftOrder - rightOrder;
|
|
190
|
+
})
|
|
191
|
+
.map(({ fileName }) => fileName);
|
|
192
|
+
}
|
|
193
|
+
export function resolveMigrationPreflightBinary(env = process.env) {
|
|
194
|
+
const configured = env[MIGRATION_PREFLIGHT_PATH_ENV]?.trim()
|
|
195
|
+
|| env[LEGACY_MIGRATION_PREFLIGHT_PATH_ENV]?.trim();
|
|
196
|
+
if (configured)
|
|
197
|
+
return path.resolve(configured);
|
|
198
|
+
const binaryName = process.platform === "win32"
|
|
199
|
+
? "migration-preflight.exe"
|
|
200
|
+
: "migration-preflight";
|
|
201
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
202
|
+
const target = resolveNativeTarget();
|
|
203
|
+
const resourcesPath = env.RUDDER_DESKTOP_RESOURCES_PATH?.trim()
|
|
204
|
+
|| (typeof process.resourcesPath === "string"
|
|
205
|
+
? process.resourcesPath
|
|
206
|
+
: "");
|
|
207
|
+
const candidates = [
|
|
208
|
+
path.resolve(moduleDir, "../../../native/target/debug", binaryName),
|
|
209
|
+
path.resolve(moduleDir, "../../../../native/target/debug", binaryName),
|
|
210
|
+
path.resolve(moduleDir, "../../../native", target ?? "unsupported", binaryName),
|
|
211
|
+
resourcesPath && target ? path.resolve(resourcesPath, "native", target, binaryName) : "",
|
|
212
|
+
path.resolve(moduleDir, "../../../native/target/release", binaryName),
|
|
213
|
+
path.resolve(moduleDir, "../../../../native/target/release", binaryName),
|
|
214
|
+
target ? path.resolve(moduleDir, "../../../native/target", target, "release", binaryName) : "",
|
|
215
|
+
target ? path.resolve(moduleDir, "../../../../native/target", target, "release", binaryName) : "",
|
|
216
|
+
].filter(Boolean);
|
|
217
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
|
|
218
|
+
}
|
|
219
|
+
export async function runMigrationPreflight(input, options = {}) {
|
|
220
|
+
const request = createMigrationPreflightRequest(input.source);
|
|
221
|
+
validateDatabaseUrl(input.databaseUrl);
|
|
222
|
+
const binaryPath = options.binaryPath ?? resolveMigrationPreflightBinary(options.env);
|
|
223
|
+
const command = resolveNativeCommand(binaryPath);
|
|
224
|
+
const env = {
|
|
225
|
+
...(options.env ?? process.env),
|
|
226
|
+
[MIGRATION_PREFLIGHT_DATABASE_URL_ENV]: input.databaseUrl,
|
|
227
|
+
};
|
|
228
|
+
const timeoutMs = validateTimeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
229
|
+
const result = await runPreflightProcess(command.command, command.args, request, env, timeoutMs, options);
|
|
230
|
+
return parsePreflightResponse(result.stdout, result.stderr, result.exitCode, result.signal);
|
|
231
|
+
}
|
|
232
|
+
export function createMigrationPreflightRequest(source) {
|
|
233
|
+
const request = JSON.stringify({
|
|
234
|
+
schema: MIGRATION_PREFLIGHT_SCHEMA,
|
|
235
|
+
protocolVersion: MIGRATION_PREFLIGHT_PROTOCOL_VERSION,
|
|
236
|
+
source: {
|
|
237
|
+
migrationsDir: source.migrationsDir,
|
|
238
|
+
...(source.journalFile === undefined ? {} : { journalFile: source.journalFile }),
|
|
239
|
+
...(source.expectedFingerprint === undefined
|
|
240
|
+
? {}
|
|
241
|
+
: { expectedFingerprint: source.expectedFingerprint }),
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
if (Buffer.byteLength(request, "utf8") > MAX_REQUEST_BYTES) {
|
|
245
|
+
throw new MigrationPreflightAdapterError("protocol", "input_too_large");
|
|
246
|
+
}
|
|
247
|
+
return request;
|
|
248
|
+
}
|
|
249
|
+
function resolveNativeTarget() {
|
|
250
|
+
if (process.platform === "darwin") {
|
|
251
|
+
if (process.arch === "arm64")
|
|
252
|
+
return "aarch64-apple-darwin";
|
|
253
|
+
if (process.arch === "x64")
|
|
254
|
+
return "x86_64-apple-darwin";
|
|
255
|
+
}
|
|
256
|
+
if (process.platform === "linux") {
|
|
257
|
+
if (process.arch === "arm64")
|
|
258
|
+
return "aarch64-unknown-linux-gnu";
|
|
259
|
+
if (process.arch === "x64")
|
|
260
|
+
return "x86_64-unknown-linux-gnu";
|
|
261
|
+
}
|
|
262
|
+
if (process.platform === "win32") {
|
|
263
|
+
if (process.arch === "arm64")
|
|
264
|
+
return "aarch64-pc-windows-msvc";
|
|
265
|
+
if (process.arch === "x64")
|
|
266
|
+
return "x86_64-pc-windows-msvc";
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
function validateDatabaseUrl(databaseUrl) {
|
|
271
|
+
if (!databaseUrl) {
|
|
272
|
+
throw new MigrationPreflightAdapterError("configuration", "database_url_missing");
|
|
273
|
+
}
|
|
274
|
+
if (Buffer.byteLength(databaseUrl, "utf8") > MAX_DATABASE_URL_BYTES) {
|
|
275
|
+
throw new MigrationPreflightAdapterError("configuration", "database_url_too_large");
|
|
276
|
+
}
|
|
277
|
+
if (!databaseUrl.startsWith("postgres://") && !databaseUrl.startsWith("postgresql://")) {
|
|
278
|
+
throw new MigrationPreflightAdapterError("configuration", "database_url_protocol_unsupported");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function validateTimeout(timeoutMs) {
|
|
282
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMEOUT_MS) {
|
|
283
|
+
throw new MigrationPreflightAdapterError("configuration", "timeout_invalid");
|
|
284
|
+
}
|
|
285
|
+
return timeoutMs;
|
|
286
|
+
}
|
|
287
|
+
async function runPreflightProcess(command, args, input, env, timeoutMs, options) {
|
|
288
|
+
const spawnProcess = options.spawnProcess ?? ((file, argv, spawnOptions) => spawn(file, argv, spawnOptions));
|
|
289
|
+
return await new Promise((resolve, reject) => {
|
|
290
|
+
let child;
|
|
291
|
+
try {
|
|
292
|
+
child = spawnProcess(command, args, {
|
|
293
|
+
env,
|
|
294
|
+
shell: false,
|
|
295
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
296
|
+
windowsHide: true,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
reject(new MigrationPreflightAdapterError("process", "spawn_failed"));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
let stdout = Buffer.alloc(0);
|
|
304
|
+
let stderr = Buffer.alloc(0);
|
|
305
|
+
let settled = false;
|
|
306
|
+
let processClosed = false;
|
|
307
|
+
let terminationRequested = false;
|
|
308
|
+
let timeout;
|
|
309
|
+
let forceKill;
|
|
310
|
+
const cleanup = () => {
|
|
311
|
+
if (timeout)
|
|
312
|
+
clearTimeout(timeout);
|
|
313
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
314
|
+
};
|
|
315
|
+
const settleError = (error) => {
|
|
316
|
+
if (settled)
|
|
317
|
+
return;
|
|
318
|
+
settled = true;
|
|
319
|
+
cleanup();
|
|
320
|
+
reject(error);
|
|
321
|
+
};
|
|
322
|
+
const terminate = () => {
|
|
323
|
+
if (terminationRequested)
|
|
324
|
+
return;
|
|
325
|
+
terminationRequested = true;
|
|
326
|
+
try {
|
|
327
|
+
child.kill("SIGTERM");
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// The process may already have exited; the original failure is enough.
|
|
331
|
+
}
|
|
332
|
+
if (processClosed)
|
|
333
|
+
return;
|
|
334
|
+
forceKill = setTimeout(() => {
|
|
335
|
+
try {
|
|
336
|
+
child.kill("SIGKILL");
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// The process may already have exited.
|
|
340
|
+
}
|
|
341
|
+
forceKill = undefined;
|
|
342
|
+
}, 250);
|
|
343
|
+
forceKill.unref?.();
|
|
344
|
+
};
|
|
345
|
+
const onAbort = () => {
|
|
346
|
+
terminate();
|
|
347
|
+
settleError(new MigrationPreflightAdapterError("process", "aborted"));
|
|
348
|
+
};
|
|
349
|
+
const appendOutput = (target, chunk) => {
|
|
350
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
351
|
+
const next = Buffer.concat([target === "stdout" ? stdout : stderr, bytes]);
|
|
352
|
+
if (next.byteLength > MAX_OUTPUT_BYTES) {
|
|
353
|
+
terminate();
|
|
354
|
+
settleError(new MigrationPreflightAdapterError("protocol", "output_too_large"));
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (target === "stdout")
|
|
358
|
+
stdout = next;
|
|
359
|
+
else
|
|
360
|
+
stderr = next;
|
|
361
|
+
};
|
|
362
|
+
child.stdout.on("data", (chunk) => appendOutput("stdout", chunk));
|
|
363
|
+
child.stderr.on("data", (chunk) => appendOutput("stderr", chunk));
|
|
364
|
+
child.once("error", () => settleError(new MigrationPreflightAdapterError("process", "process_failed")));
|
|
365
|
+
child.once("close", (exitCode, signal) => {
|
|
366
|
+
processClosed = true;
|
|
367
|
+
if (settled) {
|
|
368
|
+
if (forceKill)
|
|
369
|
+
clearTimeout(forceKill);
|
|
370
|
+
forceKill = undefined;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
settled = true;
|
|
374
|
+
cleanup();
|
|
375
|
+
if (forceKill)
|
|
376
|
+
clearTimeout(forceKill);
|
|
377
|
+
forceKill = undefined;
|
|
378
|
+
resolve({ stdout, stderr, exitCode, signal });
|
|
379
|
+
});
|
|
380
|
+
child.stdin.once("error", () => settleError(new MigrationPreflightAdapterError("process", "stdin_write_failed")));
|
|
381
|
+
timeout = setTimeout(() => {
|
|
382
|
+
terminate();
|
|
383
|
+
settleError(new MigrationPreflightAdapterError("process", "timeout"));
|
|
384
|
+
}, timeoutMs);
|
|
385
|
+
timeout.unref?.();
|
|
386
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
387
|
+
if (options.signal?.aborted) {
|
|
388
|
+
onAbort();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
child.stdin.end(input, "utf8");
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
settleError(new MigrationPreflightAdapterError("process", "stdin_write_failed"));
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function parsePreflightResponse(stdout, stderr, exitCode, signal) {
|
|
400
|
+
const output = decodeUtf8(stdout, "output_invalid_utf8");
|
|
401
|
+
const stderrOutput = decodeUtf8(stderr, "stderr_invalid_utf8");
|
|
402
|
+
if (stderrOutput.trim()) {
|
|
403
|
+
throw new MigrationPreflightAdapterError("protocol", "unexpected_stderr");
|
|
404
|
+
}
|
|
405
|
+
const line = output.trim();
|
|
406
|
+
if (!line || line.split(/\r?\n/).length !== 1) {
|
|
407
|
+
throw new MigrationPreflightAdapterError("protocol", "response_line_count");
|
|
408
|
+
}
|
|
409
|
+
let value;
|
|
410
|
+
try {
|
|
411
|
+
value = JSON.parse(line);
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
throw new MigrationPreflightAdapterError("protocol", "malformed_json");
|
|
415
|
+
}
|
|
416
|
+
const response = asObject(value, "response_envelope_mismatch");
|
|
417
|
+
assertKeys(response, ["schema", "protocolVersion", "status", "report", "error"], "response_envelope_mismatch");
|
|
418
|
+
if (response.schema !== MIGRATION_PREFLIGHT_SCHEMA
|
|
419
|
+
|| response.protocolVersion !== MIGRATION_PREFLIGHT_PROTOCOL_VERSION
|
|
420
|
+
|| typeof response.status !== "string") {
|
|
421
|
+
throw new MigrationPreflightAdapterError("protocol", "response_envelope_mismatch");
|
|
422
|
+
}
|
|
423
|
+
if (response.status === "error") {
|
|
424
|
+
if (exitCode !== 2 || signal !== null || response.report !== undefined || response.error === undefined) {
|
|
425
|
+
throw new MigrationPreflightAdapterError("protocol", "error_exit_code_mismatch");
|
|
426
|
+
}
|
|
427
|
+
throw parseRemoteError(response.error);
|
|
428
|
+
}
|
|
429
|
+
if (!isPreflightStatus(response.status)
|
|
430
|
+
|| exitCode !== 0
|
|
431
|
+
|| signal !== null
|
|
432
|
+
|| response.error !== undefined
|
|
433
|
+
|| response.report === undefined) {
|
|
434
|
+
throw new MigrationPreflightAdapterError("protocol", "response_envelope_mismatch");
|
|
435
|
+
}
|
|
436
|
+
const report = parseReport(response.report);
|
|
437
|
+
assertReportSemantics(report);
|
|
438
|
+
if (report.status !== response.status) {
|
|
439
|
+
throw new MigrationPreflightAdapterError("protocol", "report_status_mismatch");
|
|
440
|
+
}
|
|
441
|
+
return report;
|
|
442
|
+
}
|
|
443
|
+
function parseRemoteError(value) {
|
|
444
|
+
const error = asObject(value, "error_envelope_mismatch");
|
|
445
|
+
assertKeys(error, ["classification", "code", "message"], "error_envelope_mismatch");
|
|
446
|
+
if (!isErrorClassification(error.classification)
|
|
447
|
+
|| !isSafeToken(error.code)
|
|
448
|
+
|| typeof error.message !== "string") {
|
|
449
|
+
throw new MigrationPreflightAdapterError("protocol", "error_envelope_mismatch");
|
|
450
|
+
}
|
|
451
|
+
return new MigrationPreflightAdapterError(error.classification, error.code);
|
|
452
|
+
}
|
|
453
|
+
function parseReport(value) {
|
|
454
|
+
const report = asObject(value, "report_envelope_mismatch");
|
|
455
|
+
assertKeys(report, [
|
|
456
|
+
"status",
|
|
457
|
+
"tableCount",
|
|
458
|
+
"journalPresent",
|
|
459
|
+
"journalSchema",
|
|
460
|
+
"coreSchemaPresent",
|
|
461
|
+
"organizationsTablePresent",
|
|
462
|
+
"history",
|
|
463
|
+
"diagnostics",
|
|
464
|
+
], "report_envelope_mismatch");
|
|
465
|
+
if (!isPreflightStatus(report.status)
|
|
466
|
+
|| !isSafeNonNegativeInteger(report.tableCount)
|
|
467
|
+
|| typeof report.journalPresent !== "boolean"
|
|
468
|
+
|| !isStringOrNull(report.journalSchema)
|
|
469
|
+
|| typeof report.coreSchemaPresent !== "boolean"
|
|
470
|
+
|| typeof report.organizationsTablePresent !== "boolean"
|
|
471
|
+
|| !Array.isArray(report.diagnostics)) {
|
|
472
|
+
throw new MigrationPreflightAdapterError("protocol", "report_envelope_mismatch");
|
|
473
|
+
}
|
|
474
|
+
const diagnostics = parseStringList(report.diagnostics, "report_diagnostics_invalid");
|
|
475
|
+
const history = parseHistory(report.history);
|
|
476
|
+
return {
|
|
477
|
+
status: report.status,
|
|
478
|
+
tableCount: report.tableCount,
|
|
479
|
+
journalPresent: report.journalPresent,
|
|
480
|
+
journalSchema: report.journalSchema,
|
|
481
|
+
coreSchemaPresent: report.coreSchemaPresent,
|
|
482
|
+
organizationsTablePresent: report.organizationsTablePresent,
|
|
483
|
+
history,
|
|
484
|
+
diagnostics,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function assertReportSemantics(report) {
|
|
488
|
+
if ((report.journalPresent && report.journalSchema === null)
|
|
489
|
+
|| (!report.journalPresent && report.journalSchema !== null)) {
|
|
490
|
+
throw new MigrationPreflightAdapterError("protocol", "report_semantics_invalid");
|
|
491
|
+
}
|
|
492
|
+
const historyNeedsMigrations = report.history.status === "needsMigrations";
|
|
493
|
+
const reasonNeedsMigrations = report.history.reason !== "manifest-match";
|
|
494
|
+
if (historyNeedsMigrations !== reasonNeedsMigrations) {
|
|
495
|
+
throw new MigrationPreflightAdapterError("protocol", "report_semantics_invalid");
|
|
496
|
+
}
|
|
497
|
+
const validStatus = (() => {
|
|
498
|
+
switch (report.status) {
|
|
499
|
+
case "bootstrap":
|
|
500
|
+
return !report.journalPresent
|
|
501
|
+
&& report.tableCount === 0
|
|
502
|
+
&& report.history.reason === "migration-journal-missing";
|
|
503
|
+
case "unsafe-legacy":
|
|
504
|
+
return !report.journalPresent
|
|
505
|
+
&& report.tableCount > 0
|
|
506
|
+
&& report.history.reason === "migration-journal-missing";
|
|
507
|
+
case "pending":
|
|
508
|
+
return report.journalPresent
|
|
509
|
+
&& (report.history.reason === "pending-migrations" || report.history.reason === "empty-history");
|
|
510
|
+
case "mismatch":
|
|
511
|
+
return report.journalPresent
|
|
512
|
+
&& (report.history.reason === "manifest-mismatch" || report.history.reason === "migration-journal-invalid");
|
|
513
|
+
case "missing-core-schema":
|
|
514
|
+
return report.journalPresent
|
|
515
|
+
&& report.history.reason === "manifest-match"
|
|
516
|
+
&& (!report.coreSchemaPresent || !report.organizationsTablePresent);
|
|
517
|
+
case "current":
|
|
518
|
+
return report.journalPresent
|
|
519
|
+
&& report.history.reason === "manifest-match"
|
|
520
|
+
&& report.coreSchemaPresent
|
|
521
|
+
&& report.organizationsTablePresent;
|
|
522
|
+
}
|
|
523
|
+
})();
|
|
524
|
+
if (!validStatus) {
|
|
525
|
+
throw new MigrationPreflightAdapterError("protocol", "report_semantics_invalid");
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function parseHistory(value) {
|
|
529
|
+
const history = asObject(value, "history_envelope_mismatch");
|
|
530
|
+
assertKeys(history, [
|
|
531
|
+
"status",
|
|
532
|
+
"reason",
|
|
533
|
+
"manifestFingerprint",
|
|
534
|
+
"migrationTableSchema",
|
|
535
|
+
"journalEntryCount",
|
|
536
|
+
"appliedMigrations",
|
|
537
|
+
"pendingMigrations",
|
|
538
|
+
"diagnostics",
|
|
539
|
+
], "history_envelope_mismatch");
|
|
540
|
+
if (!isHistoryStatus(history.status)
|
|
541
|
+
|| !isHistoryReason(history.reason)
|
|
542
|
+
|| typeof history.manifestFingerprint !== "string"
|
|
543
|
+
|| !isStringOrNull(history.migrationTableSchema)
|
|
544
|
+
|| !isSafeNonNegativeInteger(history.journalEntryCount)
|
|
545
|
+
|| !Array.isArray(history.appliedMigrations)
|
|
546
|
+
|| !Array.isArray(history.pendingMigrations)
|
|
547
|
+
|| !Array.isArray(history.diagnostics)
|
|
548
|
+
|| history.appliedMigrations.length > MAX_HISTORY_ITEMS
|
|
549
|
+
|| history.pendingMigrations.length > MAX_HISTORY_ITEMS) {
|
|
550
|
+
throw new MigrationPreflightAdapterError("protocol", "history_envelope_mismatch");
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
status: history.status,
|
|
554
|
+
reason: history.reason,
|
|
555
|
+
manifestFingerprint: history.manifestFingerprint,
|
|
556
|
+
migrationTableSchema: history.migrationTableSchema,
|
|
557
|
+
journalEntryCount: history.journalEntryCount,
|
|
558
|
+
appliedMigrations: history.appliedMigrations.map(parseHistoryIdentity),
|
|
559
|
+
pendingMigrations: history.pendingMigrations.map(parseHistoryIdentity),
|
|
560
|
+
diagnostics: parseStringList(history.diagnostics, "history_diagnostics_invalid"),
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function parseHistoryIdentity(value) {
|
|
564
|
+
const identity = asObject(value, "history_identity_invalid");
|
|
565
|
+
assertKeys(identity, ["order", "fileName", "sha256", "id", "name", "hash", "createdAt"], "history_identity_invalid");
|
|
566
|
+
if (!isSafeNonNegativeInteger(identity.order)
|
|
567
|
+
|| typeof identity.fileName !== "string"
|
|
568
|
+
|| typeof identity.sha256 !== "string"
|
|
569
|
+
|| !isPositiveIntegerOrNull(identity.id)
|
|
570
|
+
|| !isStringOrNull(identity.name)
|
|
571
|
+
|| !isStringOrNull(identity.hash)
|
|
572
|
+
|| !isNonNegativeIntegerOrNull(identity.createdAt)) {
|
|
573
|
+
throw new MigrationPreflightAdapterError("protocol", "history_identity_invalid");
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
order: identity.order,
|
|
577
|
+
fileName: identity.fileName,
|
|
578
|
+
sha256: identity.sha256,
|
|
579
|
+
id: identity.id,
|
|
580
|
+
name: identity.name,
|
|
581
|
+
hash: identity.hash,
|
|
582
|
+
createdAt: identity.createdAt,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function parseStringList(value, code) {
|
|
586
|
+
if (value.length > MAX_HISTORY_ITEMS) {
|
|
587
|
+
throw new MigrationPreflightAdapterError("protocol", code);
|
|
588
|
+
}
|
|
589
|
+
if (!value.every((item) => typeof item === "string")) {
|
|
590
|
+
throw new MigrationPreflightAdapterError("protocol", code);
|
|
591
|
+
}
|
|
592
|
+
return value;
|
|
593
|
+
}
|
|
594
|
+
function decodeUtf8(value, code) {
|
|
595
|
+
try {
|
|
596
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(value);
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
throw new MigrationPreflightAdapterError("protocol", code);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function asObject(value, code) {
|
|
603
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
604
|
+
throw new MigrationPreflightAdapterError("protocol", code);
|
|
605
|
+
}
|
|
606
|
+
return value;
|
|
607
|
+
}
|
|
608
|
+
function assertKeys(value, keys, code) {
|
|
609
|
+
const allowed = new Set(keys);
|
|
610
|
+
if (Object.keys(value).some((key) => !allowed.has(key))) {
|
|
611
|
+
throw new MigrationPreflightAdapterError("protocol", code);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function isPreflightStatus(value) {
|
|
615
|
+
return typeof value === "string"
|
|
616
|
+
&& PREFLIGHT_STATUSES.includes(value);
|
|
617
|
+
}
|
|
618
|
+
function isHistoryStatus(value) {
|
|
619
|
+
return typeof value === "string"
|
|
620
|
+
&& HISTORY_STATUSES.includes(value);
|
|
621
|
+
}
|
|
622
|
+
function isHistoryReason(value) {
|
|
623
|
+
return typeof value === "string"
|
|
624
|
+
&& HISTORY_REASONS.includes(value);
|
|
625
|
+
}
|
|
626
|
+
function isErrorClassification(value) {
|
|
627
|
+
return typeof value === "string"
|
|
628
|
+
&& ERROR_CLASSIFICATIONS.includes(value);
|
|
629
|
+
}
|
|
630
|
+
function isSafeToken(value) {
|
|
631
|
+
return typeof value === "string" && /^[a-z][a-z0-9_]{0,127}$/.test(value);
|
|
632
|
+
}
|
|
633
|
+
function isSafeNonNegativeInteger(value) {
|
|
634
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
635
|
+
}
|
|
636
|
+
function isPositiveIntegerOrNull(value) {
|
|
637
|
+
return value === null || (Number.isSafeInteger(value) && typeof value === "number" && value > 0);
|
|
638
|
+
}
|
|
639
|
+
function isNonNegativeIntegerOrNull(value) {
|
|
640
|
+
return value === null || isSafeNonNegativeInteger(value);
|
|
641
|
+
}
|
|
642
|
+
function isStringOrNull(value) {
|
|
643
|
+
return value === null || typeof value === "string";
|
|
644
|
+
}
|
|
645
|
+
//# sourceMappingURL=migration-preflight.js.map
|