@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/compliance.js
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { parseIsoInstant } from './schema.js';
|
|
2
|
-
export const COMPLIANCE_CODES = Object.freeze([
|
|
3
|
-
'PRIMARY_KEY_DUPLICATE',
|
|
4
|
-
'RECORD_SCHEMA_MISMATCH',
|
|
5
|
-
'UNDECLARED_TABLE',
|
|
6
|
-
'CHECKPOINT_NOT_MONOTONIC',
|
|
7
|
-
'FULL_READ_NOT_REPEATABLE',
|
|
8
|
-
'FIXTURE_EXCHANGE_UNUSED',
|
|
9
|
-
'FIXTURE_EXCHANGE_MISMATCH',
|
|
10
|
-
'EXPECTATION_FAILED',
|
|
11
|
-
]);
|
|
12
|
-
function isRecord(value) {
|
|
13
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
-
}
|
|
15
|
-
function tablesOf(value) {
|
|
16
|
-
const tables = {};
|
|
17
|
-
if (!isRecord(value))
|
|
18
|
-
return tables;
|
|
19
|
-
for (const [name, table] of Object.entries(value)) {
|
|
20
|
-
if (!isRecord(table) || !isRecord(table.columns))
|
|
21
|
-
continue;
|
|
22
|
-
const columns = {};
|
|
23
|
-
for (const [column, type] of Object.entries(table.columns)) {
|
|
24
|
-
if (isRecord(type) && typeof type.kind === 'string')
|
|
25
|
-
columns[column] = type;
|
|
26
|
-
}
|
|
27
|
-
const primaryKey = Array.isArray(table.primaryKey) ? table.primaryKey.filter((item) => typeof item === 'string') : [];
|
|
28
|
-
tables[name] = { columns, primaryKey };
|
|
29
|
-
}
|
|
30
|
-
return tables;
|
|
31
|
-
}
|
|
32
|
-
function entriesOf(value) {
|
|
33
|
-
return isRecord(value) ? Object.entries(value).filter((entry) => isRecord(entry[1])) : [];
|
|
34
|
-
}
|
|
35
|
-
export function scopedTables(declaration, scope) {
|
|
36
|
-
if (!declaration || !isRecord(declaration.discover))
|
|
37
|
-
return null;
|
|
38
|
-
const { streams, pushes } = declaration.discover;
|
|
39
|
-
if (scope.stream !== undefined) {
|
|
40
|
-
const stream = entriesOf(streams).find(([name]) => name === scope.stream)?.[1];
|
|
41
|
-
return tablesOf(stream?.tables);
|
|
42
|
-
}
|
|
43
|
-
if (scope.push !== undefined) {
|
|
44
|
-
const push = entriesOf(pushes).find(([name]) => name === scope.push)?.[1];
|
|
45
|
-
return tablesOf(push?.tables);
|
|
46
|
-
}
|
|
47
|
-
const all = {};
|
|
48
|
-
for (const [, item] of [...entriesOf(streams), ...entriesOf(pushes)])
|
|
49
|
-
Object.assign(all, tablesOf(item.tables));
|
|
50
|
-
return all;
|
|
51
|
-
}
|
|
52
|
-
const INSTANT_PATTERN = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|[+-]\d{2}(?::?\d{2})?)$/i;
|
|
53
|
-
const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
54
|
-
const INTEGER_PATTERN = /^-?\d+$/;
|
|
55
|
-
const DECIMAL_PATTERN = /^-?(\d+)(?:\.(\d+))?$/;
|
|
56
|
-
function validCalendarDate(year, month, day) {
|
|
57
|
-
return month >= 1 && month <= 12 && day >= 1 && day <= new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
58
|
-
}
|
|
59
|
-
export function columnValueProblem(type, value) {
|
|
60
|
-
if (value === null || value === undefined)
|
|
61
|
-
return null;
|
|
62
|
-
switch (type.kind) {
|
|
63
|
-
case 'string':
|
|
64
|
-
return typeof value === 'string' ? null : 'is not a valid string';
|
|
65
|
-
case 'integer':
|
|
66
|
-
if (typeof value === 'number' && Number.isSafeInteger(value))
|
|
67
|
-
return null;
|
|
68
|
-
if (typeof value === 'bigint')
|
|
69
|
-
return null;
|
|
70
|
-
if (typeof value === 'string' && INTEGER_PATTERN.test(value))
|
|
71
|
-
return null;
|
|
72
|
-
return 'is not a valid integer';
|
|
73
|
-
case 'decimal': {
|
|
74
|
-
let text;
|
|
75
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
76
|
-
text = String(value);
|
|
77
|
-
else if (typeof value === 'string')
|
|
78
|
-
text = value;
|
|
79
|
-
else
|
|
80
|
-
return 'is not a valid decimal';
|
|
81
|
-
const match = DECIMAL_PATTERN.exec(text);
|
|
82
|
-
if (!match)
|
|
83
|
-
return 'is not a valid decimal';
|
|
84
|
-
if (type.precision !== undefined && type.scale !== undefined) {
|
|
85
|
-
const integerDigits = (match[1] ?? '').replace(/^0+(?=\d)/, '');
|
|
86
|
-
if (integerDigits !== '0' && integerDigits.length > type.precision - type.scale)
|
|
87
|
-
return `exceeds decimal precision ${type.precision},${type.scale}`;
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
case 'boolean':
|
|
92
|
-
return typeof value === 'boolean' ? null : 'is not a valid boolean';
|
|
93
|
-
case 'date': {
|
|
94
|
-
if (typeof value !== 'string')
|
|
95
|
-
return 'is not a valid date';
|
|
96
|
-
const match = DATE_PATTERN.exec(value);
|
|
97
|
-
return match && validCalendarDate(Number(match[1]), Number(match[2]), Number(match[3])) ? null : 'is not a valid date';
|
|
98
|
-
}
|
|
99
|
-
case 'instant': {
|
|
100
|
-
if (typeof value !== 'string')
|
|
101
|
-
return 'is not a valid instant';
|
|
102
|
-
const match = INSTANT_PATTERN.exec(value);
|
|
103
|
-
if (!match)
|
|
104
|
-
return 'is not an ISO 8601 timestamp with a time zone offset';
|
|
105
|
-
const [, year, month, day, hour, minute, second] = match;
|
|
106
|
-
const valid = validCalendarDate(Number(year), Number(month), Number(day)) && Number(hour) <= 23 && Number(minute) <= 59 && Number(second ?? '0') <= 59;
|
|
107
|
-
return valid && parseIsoInstant(value) !== null ? null : 'is not a valid point in time';
|
|
108
|
-
}
|
|
109
|
-
case 'json':
|
|
110
|
-
try {
|
|
111
|
-
return JSON.stringify(value) === undefined ? 'is not valid JSON' : null;
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return 'is not valid JSON';
|
|
115
|
-
}
|
|
116
|
-
default:
|
|
117
|
-
return `has an unsupported column type ${type.kind}`;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
class FindingSet {
|
|
121
|
-
items = new Map();
|
|
122
|
-
ordered = [];
|
|
123
|
-
add(key, build) {
|
|
124
|
-
const existing = this.items.get(key);
|
|
125
|
-
if (existing) {
|
|
126
|
-
existing.count += 1;
|
|
127
|
-
existing.finding.detail.count = existing.count;
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const { code, message, table, sample, extra } = build();
|
|
131
|
-
const finding = { code, message, ...(table !== undefined ? { table } : {}), detail: { count: 1, ...(sample !== undefined ? { sample } : {}), ...extra } };
|
|
132
|
-
this.items.set(key, { finding, count: 1 });
|
|
133
|
-
this.ordered.push(finding);
|
|
134
|
-
}
|
|
135
|
-
push(finding) {
|
|
136
|
-
this.ordered.push(finding);
|
|
137
|
-
}
|
|
138
|
-
list() {
|
|
139
|
-
return this.ordered;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
function checkRows(input, tables, findings) {
|
|
143
|
-
const keysSeen = new Map();
|
|
144
|
-
const undeclared = (table, sample) => {
|
|
145
|
-
findings.add(`undeclared\0${table}`, () => ({
|
|
146
|
-
code: 'UNDECLARED_TABLE', table, sample,
|
|
147
|
-
message: `table "${table}" is not declared${input.stream !== undefined ? ` by stream "${input.stream}"` : input.push !== undefined ? ` by push "${input.push}"` : ''}`,
|
|
148
|
-
}));
|
|
149
|
-
};
|
|
150
|
-
const mismatch = (table, column, reason, sample) => {
|
|
151
|
-
findings.add(`schema\0${table}\0${column}\0${reason}`, () => ({
|
|
152
|
-
code: 'RECORD_SCHEMA_MISMATCH', table, sample, extra: { column },
|
|
153
|
-
message: `column "${column}" of table "${table}" ${reason}`,
|
|
154
|
-
}));
|
|
155
|
-
};
|
|
156
|
-
for (const record of input.result.records) {
|
|
157
|
-
const declared = Object.hasOwn(tables, record.table) ? tables[record.table] : undefined;
|
|
158
|
-
if (!declared) {
|
|
159
|
-
undeclared(record.table, record.row);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
for (const column of Object.keys(record.row)) {
|
|
163
|
-
if (!Object.hasOwn(declared.columns, column))
|
|
164
|
-
mismatch(record.table, column, 'is not declared', record.row);
|
|
165
|
-
}
|
|
166
|
-
for (const [column, type] of Object.entries(declared.columns)) {
|
|
167
|
-
const problem = columnValueProblem(type, record.row[column]);
|
|
168
|
-
if (problem)
|
|
169
|
-
mismatch(record.table, column, problem, record.row[column]);
|
|
170
|
-
}
|
|
171
|
-
const missing = declared.primaryKey.filter((column) => record.row[column] === null || record.row[column] === undefined);
|
|
172
|
-
for (const column of missing)
|
|
173
|
-
mismatch(record.table, column, 'is a primary key column and is missing', record.row);
|
|
174
|
-
if (declared.primaryKey.length === 0 || missing.length > 0)
|
|
175
|
-
continue;
|
|
176
|
-
const key = JSON.stringify(declared.primaryKey.map((column) => record.row[column]));
|
|
177
|
-
const seen = keysSeen.get(record.table) ?? new Set();
|
|
178
|
-
if (seen.has(key)) {
|
|
179
|
-
const sample = Object.fromEntries(declared.primaryKey.map((column) => [column, record.row[column]]));
|
|
180
|
-
findings.add(`pk\0${record.table}`, () => ({
|
|
181
|
-
code: 'PRIMARY_KEY_DUPLICATE', table: record.table, sample,
|
|
182
|
-
message: `table "${record.table}" received more than one record with the same primary key (${declared.primaryKey.join(', ')})`,
|
|
183
|
-
}));
|
|
184
|
-
}
|
|
185
|
-
seen.add(key);
|
|
186
|
-
keysSeen.set(record.table, seen);
|
|
187
|
-
}
|
|
188
|
-
for (const removal of input.result.removals) {
|
|
189
|
-
const declared = Object.hasOwn(tables, removal.table) ? tables[removal.table] : undefined;
|
|
190
|
-
if (!declared) {
|
|
191
|
-
undeclared(removal.table, removal.key);
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
for (const column of Object.keys(removal.key)) {
|
|
195
|
-
if (!declared.primaryKey.includes(column))
|
|
196
|
-
mismatch(removal.table, column, 'is not part of the primary key (removal)', removal.key);
|
|
197
|
-
}
|
|
198
|
-
for (const column of declared.primaryKey) {
|
|
199
|
-
const value = removal.key[column];
|
|
200
|
-
if (value === null || value === undefined) {
|
|
201
|
-
mismatch(removal.table, column, 'is a primary key column and is missing (removal)', removal.key);
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
const type = declared.columns[column];
|
|
205
|
-
const problem = type ? columnValueProblem(type, value) : 'is not declared';
|
|
206
|
-
if (problem)
|
|
207
|
-
mismatch(removal.table, column, `${problem} (removal)`, value);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function comparable(value) {
|
|
212
|
-
if (typeof value === 'number' && Number.isFinite(value))
|
|
213
|
-
return { kind: 'number', at: value };
|
|
214
|
-
if (typeof value === 'string') {
|
|
215
|
-
const at = parseIsoInstant(value);
|
|
216
|
-
if (at !== null)
|
|
217
|
-
return { kind: 'time', at };
|
|
218
|
-
}
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
function checkCheckpoints(checkpoints, findings) {
|
|
222
|
-
for (let index = 1; index < checkpoints.length; index += 1) {
|
|
223
|
-
const previous = checkpoints[index - 1];
|
|
224
|
-
const next = checkpoints[index];
|
|
225
|
-
if (!previous || !next || !isRecord(previous.state) || !isRecord(next.state))
|
|
226
|
-
continue;
|
|
227
|
-
for (const [field, before] of Object.entries(previous.state)) {
|
|
228
|
-
if (!Object.hasOwn(next.state, field))
|
|
229
|
-
continue;
|
|
230
|
-
const after = next.state[field];
|
|
231
|
-
const a = comparable(before);
|
|
232
|
-
const b = comparable(after);
|
|
233
|
-
if (!a || !b || a.kind !== b.kind || b.at >= a.at)
|
|
234
|
-
continue;
|
|
235
|
-
findings.push({
|
|
236
|
-
code: 'CHECKPOINT_NOT_MONOTONIC',
|
|
237
|
-
message: `checkpoint field "${field}" went backwards from ${JSON.stringify(before)} to ${JSON.stringify(after)} (checkpoint ${next.seq})`,
|
|
238
|
-
detail: { field, previous: before, next: after, seq: next.seq },
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
function canonical(value) {
|
|
244
|
-
return JSON.stringify(value, (_key, item) => (isRecord(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item));
|
|
245
|
-
}
|
|
246
|
-
function recordSet(records, tables) {
|
|
247
|
-
return records
|
|
248
|
-
.map((record) => {
|
|
249
|
-
const primaryKey = tables && Object.hasOwn(tables, record.table) ? tables[record.table]?.primaryKey ?? [] : [];
|
|
250
|
-
return `${record.table}\0${canonical(primaryKey.map((column) => record.row[column]))}\0${canonical(record.row)}`;
|
|
251
|
-
})
|
|
252
|
-
.sort();
|
|
253
|
-
}
|
|
254
|
-
function checkRepeatable(input, tables, findings) {
|
|
255
|
-
const rerun = input.rerun;
|
|
256
|
-
if (input.mode !== 'full' || !rerun)
|
|
257
|
-
return;
|
|
258
|
-
if (rerun.error) {
|
|
259
|
-
findings.push({
|
|
260
|
-
code: 'FULL_READ_NOT_REPEATABLE',
|
|
261
|
-
message: `the second full read failed (${rerun.error.code}: ${rerun.error.message})`,
|
|
262
|
-
detail: { error: rerun.error },
|
|
263
|
-
});
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
const first = recordSet(input.result.records, tables);
|
|
267
|
-
const second = recordSet(rerun.records, tables);
|
|
268
|
-
if (first.length === second.length && first.every((item, index) => item === second[index]))
|
|
269
|
-
return;
|
|
270
|
-
const firstSet = new Set(first);
|
|
271
|
-
const secondSet = new Set(second);
|
|
272
|
-
findings.push({
|
|
273
|
-
code: 'FULL_READ_NOT_REPEATABLE',
|
|
274
|
-
message: `two full reads returned different records (${first.length} vs ${second.length} record(s))`,
|
|
275
|
-
detail: {
|
|
276
|
-
firstCount: first.length,
|
|
277
|
-
secondCount: second.length,
|
|
278
|
-
onlyInFirst: first.filter((item) => !secondSet.has(item)).length,
|
|
279
|
-
onlyInSecond: second.filter((item) => !firstSet.has(item)).length,
|
|
280
|
-
},
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
export function checkCompliance(input) {
|
|
284
|
-
const findings = new FindingSet();
|
|
285
|
-
const tables = scopedTables(input.declaration, { ...(input.stream !== undefined ? { stream: input.stream } : {}), ...(input.push !== undefined ? { push: input.push } : {}) });
|
|
286
|
-
if (tables)
|
|
287
|
-
checkRows(input, tables, findings);
|
|
288
|
-
checkCheckpoints(input.result.checkpoints, findings);
|
|
289
|
-
checkRepeatable(input, tables, findings);
|
|
290
|
-
return findings.list();
|
|
291
|
-
}
|
package/testing/engine.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type { LoadedSource } from './source-loader.js';
|
|
2
|
-
import { type GuestExecutor, type LocalHost, type LocalRunResult } from './types.js';
|
|
3
|
-
/** vitest:当前进程直接执行已导入的定义对象,不过 vm(复用 guest 运行时的 `ctx` 实现)。 */
|
|
4
|
-
export declare function createInProcessExecutor(definition: unknown): GuestExecutor;
|
|
5
|
-
/** 子进程入口:源码下是 `src/testing/child.ts`(Bun 直接执行),dist 下是构建产出的 `testing/child.cjs`。 */
|
|
6
|
-
export declare const VM_CHILD_ENTRY: string;
|
|
7
|
-
export interface VmExecutorOptions {
|
|
8
|
-
/** guest 运行时源码;缺省 `loadGuestRuntime()`。CLI 传构建时内联的 `guest.cjs` 文本。 */
|
|
9
|
-
guestRuntime?: string;
|
|
10
|
-
/** 子进程入口;缺省 `VM_CHILD_ENTRY`(CLI 打成单文件时传自己发布的路径,测试用来换替身)。 */
|
|
11
|
-
childEntry?: string;
|
|
12
|
-
}
|
|
13
|
-
/** vm 内执行(CLI 用):空环境变量的子进程 + 与 runner 同一个 guest 会话。 */
|
|
14
|
-
export declare function createVmExecutor(source: LoadedSource, options?: VmExecutorOptions): GuestExecutor;
|
|
15
|
-
/** `inspectDefinition` 的结果:成功时是 guest 上报的定义摘要(`summarizeDefinition`)。 */
|
|
16
|
-
export type DefinitionInspection = {
|
|
17
|
-
ok: true;
|
|
18
|
-
summary: {
|
|
19
|
-
name: string;
|
|
20
|
-
version: string;
|
|
21
|
-
} & Record<string, unknown>;
|
|
22
|
-
} | {
|
|
23
|
-
ok: false;
|
|
24
|
-
code: string;
|
|
25
|
-
message: string;
|
|
26
|
-
mismatch: boolean;
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* 只加载连接器、不执行它的任何作业:发一个 `check` 作业,guest 加载代码、校验定义、核对期望的名字与版本
|
|
30
|
-
* (vm 执行器取 `package.json` 的 name / version)后上报定义摘要,宿主在 `onDefinition` 里记下摘要并中止,
|
|
31
|
-
* 连接器的 `check` 与出站调用都不会执行。加载失败时 `code` 为 guest 的失败码(通常 `CONNECTOR_LOAD_FAILED`),
|
|
32
|
-
* `mismatch` 表示定义的名字或版本与 `package.json` 不同。
|
|
33
|
-
*/
|
|
34
|
-
export declare function inspectDefinition(executor: GuestExecutor, options?: {
|
|
35
|
-
timeoutMs?: number;
|
|
36
|
-
}): Promise<DefinitionInspection>;
|
|
37
|
-
export interface StreamRunInput {
|
|
38
|
-
stream: string;
|
|
39
|
-
mode?: 'full' | 'incremental';
|
|
40
|
-
state?: unknown;
|
|
41
|
-
streamConfig?: Record<string, unknown>;
|
|
42
|
-
config: Record<string, unknown>;
|
|
43
|
-
limit?: number;
|
|
44
|
-
host: LocalHost;
|
|
45
|
-
}
|
|
46
|
-
/** 拉取一个数据流(先 check + discover,再按声明里该数据流的表拉取,与预览相同)。 */
|
|
47
|
-
export declare function runStream(definition: unknown, input: StreamRunInput): Promise<LocalRunResult>;
|
|
48
|
-
export declare function runDiscover(definition: unknown, input: {
|
|
49
|
-
config: Record<string, unknown>;
|
|
50
|
-
host: LocalHost;
|
|
51
|
-
}): Promise<LocalRunResult>;
|
|
52
|
-
export declare function runCheck(definition: unknown, input: {
|
|
53
|
-
config: Record<string, unknown>;
|
|
54
|
-
host: LocalHost;
|
|
55
|
-
}): Promise<LocalRunResult>;
|
|
56
|
-
export declare function runPush(definition: unknown, input: {
|
|
57
|
-
push: string;
|
|
58
|
-
requests: Array<{
|
|
59
|
-
requestId: string;
|
|
60
|
-
receivedAt: string;
|
|
61
|
-
headers: Record<string, string>;
|
|
62
|
-
body: string;
|
|
63
|
-
}>;
|
|
64
|
-
config: Record<string, unknown>;
|
|
65
|
-
host: LocalHost;
|
|
66
|
-
}): Promise<LocalRunResult>;
|