@deepexi/datasense-connector 3.0.1 → 3.1.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/README.md +158 -139
- package/package.json +19 -28
- package/scripts/build.ts +188 -0
- package/src/define.ts +267 -0
- package/src/descriptor.ts +116 -0
- package/src/errors.ts +105 -0
- package/src/index.ts +108 -0
- package/src/internal/location.ts +23 -0
- package/src/runtime/guest/bridge.ts +238 -0
- package/src/runtime/guest/cache.ts +43 -0
- package/src/runtime/guest/check.ts +17 -0
- package/src/runtime/guest/crypto.ts +166 -0
- package/src/runtime/guest/ctx.ts +157 -0
- package/src/runtime/guest/db.ts +59 -0
- package/src/runtime/guest/failure.ts +69 -0
- package/src/runtime/guest/http.ts +194 -0
- package/src/runtime/guest/index.ts +102 -0
- package/{runtime/guest/limits.d.ts → src/runtime/guest/limits.ts} +12 -12
- package/src/runtime/guest/loader.ts +210 -0
- package/src/runtime/guest/log.ts +47 -0
- package/src/runtime/guest/map.ts +45 -0
- package/src/runtime/guest/operation.ts +193 -0
- package/src/runtime/guest/paginate.ts +169 -0
- package/src/runtime/guest/pull.ts +177 -0
- package/src/runtime/guest/push.ts +92 -0
- package/src/runtime/guest/signal.ts +14 -0
- package/src/runtime/guest/suggest.ts +51 -0
- package/src/runtime/guest/time.ts +137 -0
- package/src/runtime/guest-runtime.ts +75 -0
- package/src/runtime/index.ts +53 -0
- package/src/runtime/protocol.ts +557 -0
- package/src/runtime/session.ts +216 -0
- package/src/testing/child.ts +21 -0
- package/src/testing/compliance.ts +343 -0
- package/src/testing/engine.ts +445 -0
- package/src/testing/fixtures.ts +591 -0
- package/src/testing/gateway.ts +482 -0
- package/src/testing/import-guard.ts +221 -0
- package/src/testing/index.ts +43 -0
- package/src/testing/local-db.ts +226 -0
- package/src/testing/mock-external-system.ts +249 -0
- package/src/testing/pinned-http.ts +144 -0
- package/src/testing/recorder.ts +140 -0
- package/src/testing/redaction.ts +94 -0
- package/src/testing/replay.ts +437 -0
- package/src/testing/run-tests.ts +115 -0
- package/src/testing/schema.ts +70 -0
- package/src/testing/source-loader.ts +374 -0
- package/src/testing/types.ts +78 -0
- package/src/types.ts +329 -0
- package/src/validate.ts +267 -0
- package/test/build.test.ts +159 -0
- package/test/define.test-d.ts +182 -0
- package/test/fixtures/acceptance.ts +156 -0
- package/test/fixtures/connectors/http-pull/.ontos/descriptor.json +72 -0
- package/test/fixtures/connectors/http-pull/.ontos/types.ts +61 -0
- package/test/fixtures/connectors/http-pull/package.json +5 -0
- package/test/fixtures/connectors/http-pull/streams/items/index.ts +37 -0
- package/test/fixtures/connectors/http-pull/streams/items/map.ts +12 -0
- package/test/fixtures/connectors/http-pull/streams/items/tests/basic/case.json +15 -0
- package/test/fixtures/connectors/http-pull/streams/items/tests/basic/exchanges.ndjson +2 -0
- package/test/fixtures/connectors/pg-pull/.ontos/descriptor.json +151 -0
- package/test/fixtures/connectors/pg-pull/.ontos/types.ts +62 -0
- package/test/fixtures/connectors/pg-pull/lib/sql.ts +14 -0
- package/test/fixtures/connectors/pg-pull/package.json +5 -0
- package/test/fixtures/connectors/pg-pull/streams/customers/index.ts +30 -0
- package/test/fixtures/connectors/pg-pull/streams/customers/tests/customers-full/case.json +15 -0
- package/test/fixtures/connectors/pg-pull/streams/customers/tests/customers-full/exchanges.ndjson +3 -0
- package/test/fixtures/connectors/pg-pull/streams/orders/index.ts +32 -0
- package/test/fixtures/descriptor.ts +54 -0
- package/test/fixtures/kingdee-example.ts +82 -0
- package/test/fixtures/record-acceptance.ts +32 -0
- package/test/runtime/ctx.test.ts +434 -0
- package/test/runtime/helpers.ts +157 -0
- package/test/runtime/jobs.test.ts +444 -0
- package/test/runtime/operation.test.ts +175 -0
- package/test/runtime/session.test.ts +229 -0
- package/test/testing/acceptance-fixtures.test.ts +69 -0
- package/test/testing/compliance.test.ts +247 -0
- package/test/testing/engine.test.ts +283 -0
- package/test/testing/fixtures.test.ts +221 -0
- package/test/testing/gateway.test.ts +337 -0
- package/test/testing/import-guard.test.ts +45 -0
- package/test/testing/mock-external-system.test.ts +144 -0
- package/test/testing/operation-fixtures.test.ts +160 -0
- package/test/testing/recorder.test.ts +163 -0
- package/test/testing/redaction.test.ts +54 -0
- package/test/testing/replay.test.ts +207 -0
- package/test/testing/run-tests.test.ts +137 -0
- package/test/testing/source-loader.test.ts +220 -0
- package/test/validate-config.test.ts +104 -0
- package/tsconfig.build.json +23 -0
- package/tsconfig.json +19 -0
- package/cjs/index.cjs +0 -1015
- package/cjs/runtime.cjs +0 -752
- package/cjs/testing.cjs +0 -3591
- package/errors.d.ts +0 -17
- package/errors.js +0 -53
- package/guest.cjs +0 -2659
- package/index.d.ts +0 -13
- package/index.js +0 -7
- package/internal/location.d.ts +0 -6
- package/internal/location.js +0 -8
- package/presentation.d.ts +0 -38
- package/presentation.js +0 -268
- package/runtime/guest/bridge.d.ts +0 -53
- package/runtime/guest/bridge.js +0 -187
- package/runtime/guest/cache.d.ts +0 -3
- package/runtime/guest/cache.js +0 -42
- package/runtime/guest/crypto.d.ts +0 -13
- package/runtime/guest/crypto.js +0 -177
- package/runtime/guest/ctx.d.ts +0 -30
- package/runtime/guest/ctx.js +0 -124
- package/runtime/guest/db.d.ts +0 -6
- package/runtime/guest/db.js +0 -59
- package/runtime/guest/discover.d.ts +0 -29
- package/runtime/guest/discover.js +0 -51
- package/runtime/guest/failure.d.ts +0 -24
- package/runtime/guest/failure.js +0 -63
- package/runtime/guest/http.d.ts +0 -4
- package/runtime/guest/http.js +0 -204
- package/runtime/guest/index.d.ts +0 -12
- package/runtime/guest/index.js +0 -89
- package/runtime/guest/limits.js +0 -12
- package/runtime/guest/loader.d.ts +0 -15
- package/runtime/guest/loader.js +0 -42
- package/runtime/guest/log.d.ts +0 -10
- package/runtime/guest/log.js +0 -36
- package/runtime/guest/map.d.ts +0 -4
- package/runtime/guest/map.js +0 -45
- package/runtime/guest/paginate.d.ts +0 -6
- package/runtime/guest/paginate.js +0 -180
- package/runtime/guest/pull.d.ts +0 -12
- package/runtime/guest/pull.js +0 -150
- package/runtime/guest/push.d.ts +0 -18
- package/runtime/guest/push.js +0 -80
- package/runtime/guest/signal.d.ts +0 -3
- package/runtime/guest/signal.js +0 -12
- package/runtime/guest/time.d.ts +0 -5
- package/runtime/guest/time.js +0 -127
- package/runtime/guest-runtime.d.ts +0 -7
- package/runtime/guest-runtime.js +0 -51
- package/runtime/index.d.ts +0 -18
- package/runtime/index.js +0 -3
- package/runtime/protocol.d.ts +0 -276
- package/runtime/protocol.js +0 -409
- package/runtime/session.d.ts +0 -12
- package/runtime/session.js +0 -191
- package/t.d.ts +0 -18
- package/t.js +0 -26
- package/testing/child.cjs +0 -350
- package/testing/child.d.ts +0 -1
- package/testing/child.js +0 -12
- package/testing/compliance.d.ts +0 -41
- package/testing/compliance.js +0 -291
- package/testing/engine.d.ts +0 -66
- package/testing/engine.js +0 -339
- package/testing/fixtures.d.ts +0 -145
- package/testing/fixtures.js +0 -407
- package/testing/gateway.d.ts +0 -65
- package/testing/gateway.js +0 -402
- package/testing/import-guard.d.ts +0 -57
- package/testing/import-guard.js +0 -187
- package/testing/index.d.ts +0 -33
- package/testing/index.js +0 -10
- package/testing/local-db.d.ts +0 -27
- package/testing/local-db.js +0 -181
- package/testing/pinned-http.d.ts +0 -14
- package/testing/pinned-http.js +0 -125
- package/testing/recorder.d.ts +0 -15
- package/testing/recorder.js +0 -116
- package/testing/redaction.d.ts +0 -28
- package/testing/redaction.js +0 -67
- package/testing/replay.d.ts +0 -50
- package/testing/replay.js +0 -313
- package/testing/schema.d.ts +0 -13
- package/testing/schema.js +0 -65
- package/testing/source-loader.d.ts +0 -19
- package/testing/source-loader.js +0 -238
- package/testing/types.d.ts +0 -79
- package/testing/types.js +0 -18
- package/types.d.ts +0 -302
- package/types.js +0 -13
- package/validate.d.ts +0 -78
- package/validate.js +0 -597
package/testing/engine.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
import { fork } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { IS_SOURCE, PACKAGE_ROOT } from '../internal/location.js';
|
|
4
|
-
import { loadGuestRuntime } from '../runtime/guest-runtime.js';
|
|
5
|
-
import { parseChildMessage, undeclaredEmitTable, } from '../runtime/protocol.js';
|
|
6
|
-
import { createInProcessGuestSession } from '../runtime/session.js';
|
|
7
|
-
import { summarizeDefinition } from '../validate.js';
|
|
8
|
-
import { LocalRunError } from './types.js';
|
|
9
|
-
function isRecord(value) {
|
|
10
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
-
}
|
|
12
|
-
function errorMessage(error) {
|
|
13
|
-
return error instanceof Error ? error.message : String(error);
|
|
14
|
-
}
|
|
15
|
-
function emptyResult() {
|
|
16
|
-
return { records: [], removals: [], checkpoints: [], logs: [], rejected: [], declaration: null, stopped: false, error: null };
|
|
17
|
-
}
|
|
18
|
-
function hostCallError(error) {
|
|
19
|
-
const code = isRecord(error) && typeof error.code === 'string' ? error.code : 'HOST_CALL_FAILED';
|
|
20
|
-
const result = { code: code === 'HOST_DENIED' ? 'EGRESS_HOST_DENIED' : code, message: errorMessage(error) };
|
|
21
|
-
if (isRecord(error) && typeof error.status === 'number' && Number.isInteger(error.status))
|
|
22
|
-
result.status = error.status;
|
|
23
|
-
if (isRecord(error) && typeof error.retryAfterMs === 'number' && error.retryAfterMs >= 0)
|
|
24
|
-
result.retryAfterMs = error.retryAfterMs;
|
|
25
|
-
return result;
|
|
26
|
-
}
|
|
27
|
-
function decode(raw) {
|
|
28
|
-
if (typeof raw !== 'string')
|
|
29
|
-
return raw;
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(raw);
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function drive(transportFactory, input) {
|
|
38
|
-
const { execute, host } = input;
|
|
39
|
-
const job = execute.job;
|
|
40
|
-
const redact = (text) => (host.redact ? host.redact(text) : text);
|
|
41
|
-
const result = emptyResult();
|
|
42
|
-
const limit = input.limit !== undefined && Number.isFinite(input.limit) && input.limit >= 0 ? Math.floor(input.limit) : null;
|
|
43
|
-
let declaredTables = [];
|
|
44
|
-
let declared = false;
|
|
45
|
-
let seq = 0;
|
|
46
|
-
let finished = false;
|
|
47
|
-
const addLogs = (logs) => {
|
|
48
|
-
for (const log of logs)
|
|
49
|
-
result.logs.push({ at: log.at, level: log.level, message: redact(log.message) });
|
|
50
|
-
};
|
|
51
|
-
return new Promise((resolve) => {
|
|
52
|
-
let timer;
|
|
53
|
-
let transport = null;
|
|
54
|
-
const finish = (error) => {
|
|
55
|
-
if (finished)
|
|
56
|
-
return;
|
|
57
|
-
finished = true;
|
|
58
|
-
if (timer !== undefined)
|
|
59
|
-
clearTimeout(timer);
|
|
60
|
-
if (error)
|
|
61
|
-
result.error = { code: error.code, message: redact(error.message), stack: error.stack === null ? null : redact(error.stack) };
|
|
62
|
-
const stopping = transport ? transport.stop() : Promise.resolve();
|
|
63
|
-
void stopping.then(() => resolve(result), () => resolve(result));
|
|
64
|
-
};
|
|
65
|
-
const send = (message) => {
|
|
66
|
-
if (finished || !transport)
|
|
67
|
-
return;
|
|
68
|
-
transport.send(message);
|
|
69
|
-
};
|
|
70
|
-
const onEmit = (id, payload) => {
|
|
71
|
-
const batch = payload;
|
|
72
|
-
seq += 1;
|
|
73
|
-
addLogs(batch.logs);
|
|
74
|
-
let records = batch.records;
|
|
75
|
-
let stop = false;
|
|
76
|
-
if (limit !== null) {
|
|
77
|
-
const room = Math.max(0, limit - result.records.length);
|
|
78
|
-
if (records.length >= room) {
|
|
79
|
-
stop = true;
|
|
80
|
-
records = records.slice(0, room);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const truncated = records.length < batch.records.length;
|
|
84
|
-
result.records.push(...records);
|
|
85
|
-
result.removals.push(...batch.removals);
|
|
86
|
-
if (batch.checkpoint && !truncated)
|
|
87
|
-
result.checkpoints.push({ seq, state: batch.checkpoint.state });
|
|
88
|
-
if (stop)
|
|
89
|
-
result.stopped = true;
|
|
90
|
-
send({ type: 'reply', id, ok: true, value: { seq, duplicate: false, rejected: 0, ...(stop ? { stop: true } : {}) } });
|
|
91
|
-
};
|
|
92
|
-
const onCall = (message) => {
|
|
93
|
-
switch (message.method) {
|
|
94
|
-
case 'definition':
|
|
95
|
-
try {
|
|
96
|
-
host.onDefinition?.(message.payload);
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
finish({ code: 'RUNNER_CRASHED', message: `the host rejected the connector definition: ${errorMessage(error)}`, stack: null });
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
send({ type: 'reply', id: message.id, ok: true, value: null });
|
|
103
|
-
return;
|
|
104
|
-
case 'declaration': {
|
|
105
|
-
const report = message.payload;
|
|
106
|
-
result.declaration = { summary: report.summary, discover: report.discover };
|
|
107
|
-
declared = true;
|
|
108
|
-
const stream = job.kind === 'pull' && Object.hasOwn(report.discover.streams, job.stream) ? report.discover.streams[job.stream] : undefined;
|
|
109
|
-
declaredTables = stream ? Object.keys(stream.tables) : [];
|
|
110
|
-
send({ type: 'reply', id: message.id, ok: true, value: null });
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
case 'emit':
|
|
114
|
-
onEmit(message.id, message.payload);
|
|
115
|
-
return;
|
|
116
|
-
default:
|
|
117
|
-
host.call(message.method, message.payload).then((value) => send({ type: 'reply', id: message.id, ok: true, value: value ?? null }), (error) => {
|
|
118
|
-
if (error instanceof LocalRunError && error.code === 'DB_DRIVER_MISSING') {
|
|
119
|
-
finish({ code: error.code, message: error.message, stack: null });
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
send({ type: 'reply', id: message.id, ok: false, error: hostCallError(error) });
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const onResult = (outcome) => {
|
|
127
|
-
switch (outcome.kind) {
|
|
128
|
-
case 'push':
|
|
129
|
-
for (const record of outcome.records)
|
|
130
|
-
result.records.push({ table: record.table, row: record.row });
|
|
131
|
-
for (const removal of outcome.removals)
|
|
132
|
-
result.removals.push({ table: removal.table, key: removal.key });
|
|
133
|
-
result.rejected.push(...outcome.rejected.map((item) => ({ requestId: item.requestId, code: item.code, message: redact(item.message) })));
|
|
134
|
-
break;
|
|
135
|
-
case 'discover':
|
|
136
|
-
result.declaration = { summary: outcome.summary, discover: outcome.discover };
|
|
137
|
-
break;
|
|
138
|
-
case 'check':
|
|
139
|
-
break;
|
|
140
|
-
case 'pull':
|
|
141
|
-
if (outcome.stopped === true)
|
|
142
|
-
result.stopped = true;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
addLogs(outcome.logs);
|
|
146
|
-
finish(null);
|
|
147
|
-
};
|
|
148
|
-
const onMessage = (raw) => {
|
|
149
|
-
if (finished)
|
|
150
|
-
return;
|
|
151
|
-
const decoded = decode(raw);
|
|
152
|
-
const message = parseChildMessage(decoded, job, { tables: declaredTables, declared });
|
|
153
|
-
if (!message) {
|
|
154
|
-
const table = undeclaredEmitTable(decoded, job, { tables: declaredTables });
|
|
155
|
-
const call = decoded;
|
|
156
|
-
if (table !== null) {
|
|
157
|
-
finish({ code: 'SCHEMA_ERROR', message: `connector emitted records for undeclared table ${table}`, stack: null });
|
|
158
|
-
}
|
|
159
|
-
else if (declared && typeof call === 'object' && call !== null && call.type === 'call' && call.method === 'declaration') {
|
|
160
|
-
finish({ code: 'RUNNER_CRASHED', message: 'connector sandbox reported the declaration more than once (protocol error)', stack: null });
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
finish({ code: 'RUNNER_CRASHED', message: 'connector sandbox sent a malformed message', stack: null });
|
|
164
|
-
}
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
switch (message.type) {
|
|
168
|
-
case 'ready':
|
|
169
|
-
send(execute);
|
|
170
|
-
return;
|
|
171
|
-
case 'call':
|
|
172
|
-
onCall(message);
|
|
173
|
-
return;
|
|
174
|
-
case 'result':
|
|
175
|
-
onResult(message.result);
|
|
176
|
-
return;
|
|
177
|
-
case 'failure':
|
|
178
|
-
addLogs(message.logs);
|
|
179
|
-
finish({ code: message.code, message: message.message, stack: message.stack ?? null });
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
const onExit = (description) => {
|
|
184
|
-
finish({ code: 'RUNNER_CRASHED', message: `connector sandbox exited without a result (${description})`, stack: null });
|
|
185
|
-
};
|
|
186
|
-
try {
|
|
187
|
-
transport = transportFactory(onMessage, onExit);
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
finish({ code: 'RUNNER_CRASHED', message: `connector sandbox failed to start: ${errorMessage(error)}`, stack: null });
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
if (input.timeoutMs !== undefined && Number.isFinite(input.timeoutMs)) {
|
|
194
|
-
timer = setTimeout(() => {
|
|
195
|
-
finish({ code: 'TIMEOUT', message: `connector execution exceeded ${input.timeoutMs} ms`, stack: null });
|
|
196
|
-
}, Math.max(0, input.timeoutMs));
|
|
197
|
-
}
|
|
198
|
-
if (!input.waitForReady)
|
|
199
|
-
send(execute);
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
export function createInProcessExecutor(definition) {
|
|
203
|
-
return {
|
|
204
|
-
async run(job, host, options = {}) {
|
|
205
|
-
const guest = await loadGuestRuntime();
|
|
206
|
-
const named = isRecord(definition) ? definition : {};
|
|
207
|
-
const expect = { name: String(named.name ?? ''), version: String(named.version ?? '') };
|
|
208
|
-
const execute = { type: 'execute', guest, code: '', expect, job };
|
|
209
|
-
return drive((onMessage) => {
|
|
210
|
-
let active = true;
|
|
211
|
-
const session = createInProcessGuestSession((json) => {
|
|
212
|
-
if (active)
|
|
213
|
-
queueMicrotask(() => onMessage(json));
|
|
214
|
-
}, definition);
|
|
215
|
-
return {
|
|
216
|
-
send(message) {
|
|
217
|
-
if (active)
|
|
218
|
-
session.handle(JSON.stringify(message));
|
|
219
|
-
},
|
|
220
|
-
async stop() {
|
|
221
|
-
if (!active)
|
|
222
|
-
return;
|
|
223
|
-
session.handle(JSON.stringify({ type: 'cancel' }));
|
|
224
|
-
active = false;
|
|
225
|
-
},
|
|
226
|
-
};
|
|
227
|
-
}, { execute, host, limit: options.limit, timeoutMs: options.timeoutMs, waitForReady: false });
|
|
228
|
-
},
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
export const VM_CHILD_ENTRY = path.join(PACKAGE_ROOT, 'testing', IS_SOURCE ? 'child.ts' : 'child.cjs');
|
|
232
|
-
const KILL_WAIT_MS = 5000;
|
|
233
|
-
function forkTransport(entry) {
|
|
234
|
-
return (onMessage, onExit) => {
|
|
235
|
-
const child = fork(entry, [], {
|
|
236
|
-
env: {},
|
|
237
|
-
execArgv: [],
|
|
238
|
-
stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
|
|
239
|
-
serialization: 'json',
|
|
240
|
-
});
|
|
241
|
-
child.stdout?.on('data', (chunk) => process.stderr.write(chunk));
|
|
242
|
-
let exited = false;
|
|
243
|
-
const exitedPromise = new Promise((resolve) => {
|
|
244
|
-
child.once('exit', (code, signal) => {
|
|
245
|
-
exited = true;
|
|
246
|
-
resolve();
|
|
247
|
-
onExit(signal ? `signal ${signal}` : `exit code ${String(code)}`);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
child.on('message', (raw) => onMessage(raw));
|
|
251
|
-
child.on('error', (error) => onExit(`spawn error: ${error.message}`));
|
|
252
|
-
return {
|
|
253
|
-
send(message) {
|
|
254
|
-
if (exited || !child.connected)
|
|
255
|
-
return;
|
|
256
|
-
try {
|
|
257
|
-
child.send(JSON.stringify(message));
|
|
258
|
-
}
|
|
259
|
-
catch {
|
|
260
|
-
}
|
|
261
|
-
},
|
|
262
|
-
async stop() {
|
|
263
|
-
if (!exited)
|
|
264
|
-
child.kill('SIGKILL');
|
|
265
|
-
await Promise.race([exitedPromise, new Promise((resolve) => setTimeout(resolve, KILL_WAIT_MS))]);
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
export function createVmExecutor(source, options = {}) {
|
|
271
|
-
const entry = options.childEntry ?? VM_CHILD_ENTRY;
|
|
272
|
-
return {
|
|
273
|
-
async run(job, host, runOptions = {}) {
|
|
274
|
-
const guest = options.guestRuntime ?? await loadGuestRuntime();
|
|
275
|
-
const execute = {
|
|
276
|
-
type: 'execute', guest, code: source.code, expect: { name: source.name, version: source.version }, job,
|
|
277
|
-
};
|
|
278
|
-
return drive(forkTransport(entry), { execute, host, limit: runOptions.limit, timeoutMs: runOptions.timeoutMs, waitForReady: true });
|
|
279
|
-
},
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
const INSPECT_ABORT = 'inspect-definition: stop after the definition';
|
|
283
|
-
const MISMATCH_PREFIX = 'connector code defines ';
|
|
284
|
-
export async function inspectDefinition(executor, options = {}) {
|
|
285
|
-
let summary = null;
|
|
286
|
-
const host = {
|
|
287
|
-
call: async (method) => {
|
|
288
|
-
throw new LocalRunError('HOST_DENIED', `${method} is not available while only loading the definition`);
|
|
289
|
-
},
|
|
290
|
-
close: async () => { },
|
|
291
|
-
onDefinition(value) {
|
|
292
|
-
summary = value;
|
|
293
|
-
throw new Error(INSPECT_ABORT);
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
const result = await executor.run({ kind: 'check', config: {}, secretFields: [] }, host, { timeoutMs: options.timeoutMs ?? 30_000 });
|
|
297
|
-
if (isRecord(summary) && typeof summary.name === 'string' && typeof summary.version === 'string') {
|
|
298
|
-
return { ok: true, summary: summary };
|
|
299
|
-
}
|
|
300
|
-
const error = result.error ?? { code: 'CONNECTOR_LOAD_FAILED', message: 'the connector did not report its definition' };
|
|
301
|
-
return { ok: false, code: error.code, message: error.message, mismatch: error.message.startsWith(MISMATCH_PREFIX) };
|
|
302
|
-
}
|
|
303
|
-
function secretFieldsOf(definition) {
|
|
304
|
-
try {
|
|
305
|
-
return summarizeDefinition(definition).secretFields;
|
|
306
|
-
}
|
|
307
|
-
catch {
|
|
308
|
-
return [];
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
export function runStream(definition, input) {
|
|
312
|
-
const job = {
|
|
313
|
-
kind: 'pull',
|
|
314
|
-
config: input.config,
|
|
315
|
-
secretFields: secretFieldsOf(definition),
|
|
316
|
-
stream: input.stream,
|
|
317
|
-
mode: input.mode ?? 'full',
|
|
318
|
-
state: input.state ?? null,
|
|
319
|
-
streamConfig: input.streamConfig ?? {},
|
|
320
|
-
tables: [],
|
|
321
|
-
discoverFirst: true,
|
|
322
|
-
};
|
|
323
|
-
return createInProcessExecutor(definition).run(job, input.host, input.limit !== undefined ? { limit: input.limit } : {});
|
|
324
|
-
}
|
|
325
|
-
export function runDiscover(definition, input) {
|
|
326
|
-
return createInProcessExecutor(definition).run({ kind: 'discover', config: input.config, secretFields: secretFieldsOf(definition) }, input.host);
|
|
327
|
-
}
|
|
328
|
-
export function runCheck(definition, input) {
|
|
329
|
-
return createInProcessExecutor(definition).run({ kind: 'check', config: input.config, secretFields: secretFieldsOf(definition) }, input.host);
|
|
330
|
-
}
|
|
331
|
-
export function runPush(definition, input) {
|
|
332
|
-
const job = {
|
|
333
|
-
kind: 'push',
|
|
334
|
-
config: input.config,
|
|
335
|
-
secretFields: secretFieldsOf(definition),
|
|
336
|
-
requests: input.requests.map((request) => ({ ...request, push: input.push })),
|
|
337
|
-
};
|
|
338
|
-
return createInProcessExecutor(definition).run(job, input.host);
|
|
339
|
-
}
|
package/testing/fixtures.d.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 夹具格式(CLI `read --record` 写、`connector test` 读、runner 服务端回放读,共享值域)。
|
|
3
|
-
*
|
|
4
|
-
* 源码目录下 `fixtures/<用例名>/`:
|
|
5
|
-
* - `case.json`(必有):一个 `FixtureCase`;
|
|
6
|
-
* - `exchanges.ndjson`(可选):每行一个 `FixtureExchange`,`seq` 严格递增;
|
|
7
|
-
* - `requests.ndjson`(推送用例必有,其他用例不能有):每行一个 `FixtureRequest`。
|
|
8
|
-
*
|
|
9
|
-
* 用例名 `^[a-z0-9][a-z0-9_-]{0,63}$`。夹具里长度 ≥ 4 的 Secret 明文在录制时已替换为 `***`,凭证句柄
|
|
10
|
-
* `ontos:secret:<field>` 保持原样(见 `recorder.ts`)。NDJSON 每行以 `\n` 结尾,键按插入顺序输出(便于 diff)。
|
|
11
|
-
*
|
|
12
|
-
* 本文件除 `readFixtureDir`(动态导入 `node:fs`)外不依赖 Node 模块。
|
|
13
|
-
*/
|
|
14
|
-
import type { CryptoRequest } from '../runtime/protocol.js';
|
|
15
|
-
/** 夹具目录里的一个文件;`path` 相对源码根目录,如 `fixtures/basic/case.json`(与 shared 的同名类型同形)。 */
|
|
16
|
-
export interface ConnectorFixtureFile {
|
|
17
|
-
path: string;
|
|
18
|
-
content: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* 用例的期望(全部可选,给出的每一项都必须精确相等,否则 `EXPECTATION_FAILED`):
|
|
22
|
-
* - `records` / `removals`:表 → 条数(只比较给出的表);
|
|
23
|
-
* - `rejected`:推送拒收条数;`checkpoints`:断点个数;
|
|
24
|
-
* - `tables`:用例范围内声明的表(拉取取该数据流、推送取该推送、discover 取全部),按集合比较。
|
|
25
|
-
*/
|
|
26
|
-
export interface FixtureExpectation {
|
|
27
|
-
records?: Record<string, number>;
|
|
28
|
-
removals?: Record<string, number>;
|
|
29
|
-
rejected?: number;
|
|
30
|
-
checkpoints?: number;
|
|
31
|
-
tables?: string[];
|
|
32
|
-
}
|
|
33
|
-
/** `case.json`;`secrets` 是字段 → 值(录制后长度 ≥ 4 的值为 `***`),回放时 `secret.reveal` 照给。 */
|
|
34
|
-
export type FixtureCase = {
|
|
35
|
-
kind: 'pull';
|
|
36
|
-
stream: string;
|
|
37
|
-
mode: 'full' | 'incremental';
|
|
38
|
-
state?: unknown;
|
|
39
|
-
streamConfig?: Record<string, unknown>;
|
|
40
|
-
config: Record<string, unknown>;
|
|
41
|
-
secrets?: Record<string, string>;
|
|
42
|
-
limit?: number;
|
|
43
|
-
expect?: FixtureExpectation;
|
|
44
|
-
} | {
|
|
45
|
-
kind: 'push';
|
|
46
|
-
push: string;
|
|
47
|
-
config: Record<string, unknown>;
|
|
48
|
-
secrets?: Record<string, string>;
|
|
49
|
-
expect?: FixtureExpectation;
|
|
50
|
-
} | {
|
|
51
|
-
kind: 'discover';
|
|
52
|
-
config: Record<string, unknown>;
|
|
53
|
-
secrets?: Record<string, string>;
|
|
54
|
-
expect?: FixtureExpectation;
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* 一次宿主往来:
|
|
58
|
-
* - `http`:`request.url` 已并入 query 并按键排序、含句柄的 query 值为 `***`;凭证类头为 `***`;body 里的句柄原样保留;
|
|
59
|
-
* - `db`:一次查询的全部分页合成一条,`rows` 为全部行;
|
|
60
|
-
* - `crypto`:请求(key / data 里的句柄原样)与结果。
|
|
61
|
-
*/
|
|
62
|
-
export type FixtureExchange = {
|
|
63
|
-
seq: number;
|
|
64
|
-
kind: 'http';
|
|
65
|
-
request: {
|
|
66
|
-
method: string;
|
|
67
|
-
url: string;
|
|
68
|
-
headers: Record<string, string>;
|
|
69
|
-
body: string | null;
|
|
70
|
-
bodyEncoding: 'utf8' | 'base64';
|
|
71
|
-
};
|
|
72
|
-
response: {
|
|
73
|
-
status: number;
|
|
74
|
-
headers: Record<string, string>;
|
|
75
|
-
body: string;
|
|
76
|
-
bodyEncoding: 'utf8' | 'base64';
|
|
77
|
-
} | null;
|
|
78
|
-
error: {
|
|
79
|
-
code: string;
|
|
80
|
-
message: string;
|
|
81
|
-
} | null;
|
|
82
|
-
} | {
|
|
83
|
-
seq: number;
|
|
84
|
-
kind: 'db';
|
|
85
|
-
sql: string;
|
|
86
|
-
params: unknown[];
|
|
87
|
-
fields: Array<{
|
|
88
|
-
name: string;
|
|
89
|
-
dataTypeId: number;
|
|
90
|
-
}>;
|
|
91
|
-
rows: Array<Record<string, unknown>>;
|
|
92
|
-
error: {
|
|
93
|
-
code: string;
|
|
94
|
-
message: string;
|
|
95
|
-
} | null;
|
|
96
|
-
} | {
|
|
97
|
-
seq: number;
|
|
98
|
-
kind: 'crypto';
|
|
99
|
-
request: CryptoRequest;
|
|
100
|
-
value: string;
|
|
101
|
-
};
|
|
102
|
-
/** 推送用例重放的一条收件箱请求。 */
|
|
103
|
-
export interface FixtureRequest {
|
|
104
|
-
requestId: string;
|
|
105
|
-
receivedAt: string;
|
|
106
|
-
headers: Record<string, string>;
|
|
107
|
-
body: string;
|
|
108
|
-
}
|
|
109
|
-
export interface LoadedFixture {
|
|
110
|
-
name: string;
|
|
111
|
-
case: FixtureCase;
|
|
112
|
-
exchanges: FixtureExchange[];
|
|
113
|
-
requests: FixtureRequest[];
|
|
114
|
-
}
|
|
115
|
-
/** 夹具格式错误:`file` 为相对源码根目录的路径,`line` 为 NDJSON 的行号(从 1 起;与整个文件相关时为 null)。 */
|
|
116
|
-
export declare class FixtureFormatError extends Error {
|
|
117
|
-
readonly file: string;
|
|
118
|
-
readonly line: number | null;
|
|
119
|
-
constructor(message: string, file: string, line?: number | null);
|
|
120
|
-
}
|
|
121
|
-
export declare const FIXTURE_CASE_NAME_PATTERN: RegExp;
|
|
122
|
-
export declare const FIXTURES_DIR = "fixtures";
|
|
123
|
-
/**
|
|
124
|
-
* 校验并解析夹具文件(只看 `fixtures/` 下的文件,其余路径报错);结果按用例名排序。
|
|
125
|
-
* 格式错误抛 `FixtureFormatError`(含 file、line)。以 `.` 开头的文件(如 `.DS_Store`)忽略。
|
|
126
|
-
*/
|
|
127
|
-
export declare function parseFixtures(files: ConnectorFixtureFile[]): LoadedFixture[];
|
|
128
|
-
/**
|
|
129
|
-
* 把一个用例写成夹具文件:`case.json`(两格缩进)、`exchanges.ndjson`(有往来时)、`requests.ndjson`(推送用例)。
|
|
130
|
-
* `case.secrets` 里长度 ≥ 4 的值写成 `***`。与 `parseFixtures` 往返无损(`secrets` 已遮蔽时)。
|
|
131
|
-
*/
|
|
132
|
-
export declare function serializeFixture(name: string, input: {
|
|
133
|
-
case: FixtureCase;
|
|
134
|
-
exchanges: FixtureExchange[];
|
|
135
|
-
requests?: FixtureRequest[];
|
|
136
|
-
}): ConnectorFixtureFile[];
|
|
137
|
-
/**
|
|
138
|
-
* 读 `<dir>/fixtures/**` 下的全部文件(`path` 相对 `dir`、用 `/` 分隔、按路径排序);没有 `fixtures/` 时返回空数组。
|
|
139
|
-
* 以 `.` 开头的文件与目录跳过;遇到符号链接抛 `FixtureFormatError`(夹具是租户数据,不跟随链接)。
|
|
140
|
-
*/
|
|
141
|
-
export declare function readFixtureDir(dir: string): Promise<ConnectorFixtureFile[]>;
|
|
142
|
-
/** http 请求的规范 URL:query 并入 URL,再按键排序(同名键保持相对顺序)。解析不了时原样返回。 */
|
|
143
|
-
export declare function canonicalRequestUrl(url: string, query: Record<string, string | string[]> | undefined): string;
|
|
144
|
-
/** 键排序后的 JSON(db 参数、crypto 请求的比较用)。 */
|
|
145
|
-
export declare function canonicalJson(value: unknown): string;
|