@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.
Files changed (185) hide show
  1. package/README.md +158 -139
  2. package/package.json +19 -28
  3. package/scripts/build.ts +188 -0
  4. package/src/define.ts +267 -0
  5. package/src/descriptor.ts +116 -0
  6. package/src/errors.ts +105 -0
  7. package/src/index.ts +108 -0
  8. package/src/internal/location.ts +23 -0
  9. package/src/runtime/guest/bridge.ts +238 -0
  10. package/src/runtime/guest/cache.ts +43 -0
  11. package/src/runtime/guest/check.ts +17 -0
  12. package/src/runtime/guest/crypto.ts +166 -0
  13. package/src/runtime/guest/ctx.ts +157 -0
  14. package/src/runtime/guest/db.ts +59 -0
  15. package/src/runtime/guest/failure.ts +69 -0
  16. package/src/runtime/guest/http.ts +194 -0
  17. package/src/runtime/guest/index.ts +102 -0
  18. package/{runtime/guest/limits.d.ts → src/runtime/guest/limits.ts} +12 -12
  19. package/src/runtime/guest/loader.ts +210 -0
  20. package/src/runtime/guest/log.ts +47 -0
  21. package/src/runtime/guest/map.ts +45 -0
  22. package/src/runtime/guest/operation.ts +193 -0
  23. package/src/runtime/guest/paginate.ts +169 -0
  24. package/src/runtime/guest/pull.ts +177 -0
  25. package/src/runtime/guest/push.ts +92 -0
  26. package/src/runtime/guest/signal.ts +14 -0
  27. package/src/runtime/guest/suggest.ts +51 -0
  28. package/src/runtime/guest/time.ts +137 -0
  29. package/src/runtime/guest-runtime.ts +75 -0
  30. package/src/runtime/index.ts +53 -0
  31. package/src/runtime/protocol.ts +557 -0
  32. package/src/runtime/session.ts +216 -0
  33. package/src/testing/child.ts +21 -0
  34. package/src/testing/compliance.ts +343 -0
  35. package/src/testing/engine.ts +445 -0
  36. package/src/testing/fixtures.ts +591 -0
  37. package/src/testing/gateway.ts +482 -0
  38. package/src/testing/import-guard.ts +221 -0
  39. package/src/testing/index.ts +43 -0
  40. package/src/testing/local-db.ts +226 -0
  41. package/src/testing/mock-external-system.ts +249 -0
  42. package/src/testing/pinned-http.ts +144 -0
  43. package/src/testing/recorder.ts +140 -0
  44. package/src/testing/redaction.ts +94 -0
  45. package/src/testing/replay.ts +437 -0
  46. package/src/testing/run-tests.ts +115 -0
  47. package/src/testing/schema.ts +70 -0
  48. package/src/testing/source-loader.ts +374 -0
  49. package/src/testing/types.ts +78 -0
  50. package/src/types.ts +329 -0
  51. package/src/validate.ts +267 -0
  52. package/test/build.test.ts +159 -0
  53. package/test/define.test-d.ts +182 -0
  54. package/test/fixtures/acceptance.ts +156 -0
  55. package/test/fixtures/connectors/http-pull/.ontos/descriptor.json +72 -0
  56. package/test/fixtures/connectors/http-pull/.ontos/types.ts +61 -0
  57. package/test/fixtures/connectors/http-pull/package.json +5 -0
  58. package/test/fixtures/connectors/http-pull/streams/items/index.ts +37 -0
  59. package/test/fixtures/connectors/http-pull/streams/items/map.ts +12 -0
  60. package/test/fixtures/connectors/http-pull/streams/items/tests/basic/case.json +15 -0
  61. package/test/fixtures/connectors/http-pull/streams/items/tests/basic/exchanges.ndjson +2 -0
  62. package/test/fixtures/connectors/pg-pull/.ontos/descriptor.json +151 -0
  63. package/test/fixtures/connectors/pg-pull/.ontos/types.ts +62 -0
  64. package/test/fixtures/connectors/pg-pull/lib/sql.ts +14 -0
  65. package/test/fixtures/connectors/pg-pull/package.json +5 -0
  66. package/test/fixtures/connectors/pg-pull/streams/customers/index.ts +30 -0
  67. package/test/fixtures/connectors/pg-pull/streams/customers/tests/customers-full/case.json +15 -0
  68. package/test/fixtures/connectors/pg-pull/streams/customers/tests/customers-full/exchanges.ndjson +3 -0
  69. package/test/fixtures/connectors/pg-pull/streams/orders/index.ts +32 -0
  70. package/test/fixtures/descriptor.ts +54 -0
  71. package/test/fixtures/kingdee-example.ts +82 -0
  72. package/test/fixtures/record-acceptance.ts +32 -0
  73. package/test/runtime/ctx.test.ts +434 -0
  74. package/test/runtime/helpers.ts +157 -0
  75. package/test/runtime/jobs.test.ts +444 -0
  76. package/test/runtime/operation.test.ts +175 -0
  77. package/test/runtime/session.test.ts +229 -0
  78. package/test/testing/acceptance-fixtures.test.ts +69 -0
  79. package/test/testing/compliance.test.ts +247 -0
  80. package/test/testing/engine.test.ts +283 -0
  81. package/test/testing/fixtures.test.ts +221 -0
  82. package/test/testing/gateway.test.ts +337 -0
  83. package/test/testing/import-guard.test.ts +45 -0
  84. package/test/testing/mock-external-system.test.ts +144 -0
  85. package/test/testing/operation-fixtures.test.ts +160 -0
  86. package/test/testing/recorder.test.ts +163 -0
  87. package/test/testing/redaction.test.ts +54 -0
  88. package/test/testing/replay.test.ts +207 -0
  89. package/test/testing/run-tests.test.ts +137 -0
  90. package/test/testing/source-loader.test.ts +220 -0
  91. package/test/validate-config.test.ts +104 -0
  92. package/tsconfig.build.json +23 -0
  93. package/tsconfig.json +19 -0
  94. package/cjs/index.cjs +0 -1015
  95. package/cjs/runtime.cjs +0 -752
  96. package/cjs/testing.cjs +0 -3591
  97. package/errors.d.ts +0 -17
  98. package/errors.js +0 -53
  99. package/guest.cjs +0 -2659
  100. package/index.d.ts +0 -13
  101. package/index.js +0 -7
  102. package/internal/location.d.ts +0 -6
  103. package/internal/location.js +0 -8
  104. package/presentation.d.ts +0 -38
  105. package/presentation.js +0 -268
  106. package/runtime/guest/bridge.d.ts +0 -53
  107. package/runtime/guest/bridge.js +0 -187
  108. package/runtime/guest/cache.d.ts +0 -3
  109. package/runtime/guest/cache.js +0 -42
  110. package/runtime/guest/crypto.d.ts +0 -13
  111. package/runtime/guest/crypto.js +0 -177
  112. package/runtime/guest/ctx.d.ts +0 -30
  113. package/runtime/guest/ctx.js +0 -124
  114. package/runtime/guest/db.d.ts +0 -6
  115. package/runtime/guest/db.js +0 -59
  116. package/runtime/guest/discover.d.ts +0 -29
  117. package/runtime/guest/discover.js +0 -51
  118. package/runtime/guest/failure.d.ts +0 -24
  119. package/runtime/guest/failure.js +0 -63
  120. package/runtime/guest/http.d.ts +0 -4
  121. package/runtime/guest/http.js +0 -204
  122. package/runtime/guest/index.d.ts +0 -12
  123. package/runtime/guest/index.js +0 -89
  124. package/runtime/guest/limits.js +0 -12
  125. package/runtime/guest/loader.d.ts +0 -15
  126. package/runtime/guest/loader.js +0 -42
  127. package/runtime/guest/log.d.ts +0 -10
  128. package/runtime/guest/log.js +0 -36
  129. package/runtime/guest/map.d.ts +0 -4
  130. package/runtime/guest/map.js +0 -45
  131. package/runtime/guest/paginate.d.ts +0 -6
  132. package/runtime/guest/paginate.js +0 -180
  133. package/runtime/guest/pull.d.ts +0 -12
  134. package/runtime/guest/pull.js +0 -150
  135. package/runtime/guest/push.d.ts +0 -18
  136. package/runtime/guest/push.js +0 -80
  137. package/runtime/guest/signal.d.ts +0 -3
  138. package/runtime/guest/signal.js +0 -12
  139. package/runtime/guest/time.d.ts +0 -5
  140. package/runtime/guest/time.js +0 -127
  141. package/runtime/guest-runtime.d.ts +0 -7
  142. package/runtime/guest-runtime.js +0 -51
  143. package/runtime/index.d.ts +0 -18
  144. package/runtime/index.js +0 -3
  145. package/runtime/protocol.d.ts +0 -276
  146. package/runtime/protocol.js +0 -409
  147. package/runtime/session.d.ts +0 -12
  148. package/runtime/session.js +0 -191
  149. package/t.d.ts +0 -18
  150. package/t.js +0 -26
  151. package/testing/child.cjs +0 -350
  152. package/testing/child.d.ts +0 -1
  153. package/testing/child.js +0 -12
  154. package/testing/compliance.d.ts +0 -41
  155. package/testing/compliance.js +0 -291
  156. package/testing/engine.d.ts +0 -66
  157. package/testing/engine.js +0 -339
  158. package/testing/fixtures.d.ts +0 -145
  159. package/testing/fixtures.js +0 -407
  160. package/testing/gateway.d.ts +0 -65
  161. package/testing/gateway.js +0 -402
  162. package/testing/import-guard.d.ts +0 -57
  163. package/testing/import-guard.js +0 -187
  164. package/testing/index.d.ts +0 -33
  165. package/testing/index.js +0 -10
  166. package/testing/local-db.d.ts +0 -27
  167. package/testing/local-db.js +0 -181
  168. package/testing/pinned-http.d.ts +0 -14
  169. package/testing/pinned-http.js +0 -125
  170. package/testing/recorder.d.ts +0 -15
  171. package/testing/recorder.js +0 -116
  172. package/testing/redaction.d.ts +0 -28
  173. package/testing/redaction.js +0 -67
  174. package/testing/replay.d.ts +0 -50
  175. package/testing/replay.js +0 -313
  176. package/testing/schema.d.ts +0 -13
  177. package/testing/schema.js +0 -65
  178. package/testing/source-loader.d.ts +0 -19
  179. package/testing/source-loader.js +0 -238
  180. package/testing/types.d.ts +0 -79
  181. package/testing/types.js +0 -18
  182. package/types.d.ts +0 -302
  183. package/types.js +0 -13
  184. package/validate.d.ts +0 -78
  185. package/validate.js +0 -597
@@ -1,204 +0,0 @@
1
- import { errors } from '../../errors.js';
2
- import { HostCallFailure, call, jsonParse, jsonStringify, now, sleep, throwIfAborted } from './bridge.js';
3
- import { bytesToBase64 } from './crypto.js';
4
- import { MAX_RETRY_WAIT_MS, RATE_LIMIT_BACKOFF_MS, RATE_LIMIT_RETRIES, TRANSIENT_BACKOFF_MS, TRANSIENT_RETRIES } from './limits.js';
5
- const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'];
6
- const ABSOLUTE_URL = /^https?:\/\//i;
7
- const TRANSIENT_STATUS = [502, 503, 504];
8
- const IDEMPOTENT_METHODS = ['GET', 'HEAD', 'PUT', 'DELETE'];
9
- const JSON_CONTENT_TYPE = /(?:^|[/+])json\s*(?:;|$)/i;
10
- function isRecord(value) {
11
- return typeof value === 'object' && value !== null && !Array.isArray(value);
12
- }
13
- function stringify(value) {
14
- if (typeof value === 'string')
15
- return value;
16
- if (value instanceof Date)
17
- return value.toISOString();
18
- if (typeof value === 'object' && value !== null)
19
- return jsonStringify(value);
20
- return String(value);
21
- }
22
- export function resolveUrl(url, baseUrl) {
23
- if (ABSOLUTE_URL.test(url))
24
- return url;
25
- if (!baseUrl)
26
- throw new TypeError(`relative URL ${url} needs auth.baseUrl or a fixed host in the connector's hosts`);
27
- const base = baseUrl.replace(/\/+$/, '');
28
- return `${base}${url.startsWith('/') ? '' : '/'}${url}`;
29
- }
30
- function buildHeaders(options) {
31
- const headers = {};
32
- for (const source of [options.auth?.headers, options.headers]) {
33
- if (source === undefined)
34
- continue;
35
- if (!isRecord(source))
36
- throw new TypeError('headers must be an object');
37
- for (const [name, value] of Object.entries(source)) {
38
- if (value === undefined || value === null)
39
- continue;
40
- headers[name.toLowerCase()] = stringify(value);
41
- }
42
- }
43
- return headers;
44
- }
45
- function buildQuery(query) {
46
- const out = {};
47
- if (query === undefined)
48
- return out;
49
- if (!isRecord(query))
50
- throw new TypeError('query must be an object');
51
- for (const [name, value] of Object.entries(query)) {
52
- if (value === undefined || value === null)
53
- continue;
54
- out[name] = Array.isArray(value) ? value.filter((item) => item !== undefined && item !== null).map(stringify) : stringify(value);
55
- }
56
- return out;
57
- }
58
- function buildBody(body, headers) {
59
- if (body === undefined || body === null)
60
- return { body: null, bodyEncoding: 'utf8' };
61
- if (typeof body === 'string')
62
- return { body, bodyEncoding: 'utf8' };
63
- if (body instanceof Uint8Array)
64
- return { body: bytesToBase64(Array.from(body)), bodyEncoding: 'base64' };
65
- if (body instanceof ArrayBuffer)
66
- return { body: bytesToBase64(Array.from(new Uint8Array(body))), bodyEncoding: 'base64' };
67
- const text = jsonStringify(body);
68
- if (typeof text !== 'string')
69
- throw new TypeError('request body is not serializable');
70
- if (headers['content-type'] === undefined)
71
- headers['content-type'] = 'application/json';
72
- return { body: text, bodyEncoding: 'utf8' };
73
- }
74
- function buildRequest(method, url, options, defaultBaseUrl) {
75
- const upper = typeof method === 'string' ? method.toUpperCase() : '';
76
- if (!METHODS.includes(upper))
77
- throw new TypeError(`unsupported HTTP method ${String(method)}`);
78
- if (typeof url !== 'string' || url.length === 0)
79
- throw new TypeError('url must be a non-empty string');
80
- if (!isRecord(options))
81
- throw new TypeError('options must be an object');
82
- const baseUrl = options.auth?.baseUrl ?? defaultBaseUrl;
83
- const headers = buildHeaders(options);
84
- const request = {
85
- method: upper,
86
- url: resolveUrl(url, typeof baseUrl === 'string' ? baseUrl : null),
87
- headers,
88
- query: buildQuery(options.query),
89
- ...buildBody(options.body, headers),
90
- };
91
- if (options.retry !== undefined && typeof options.retry !== 'boolean')
92
- throw new TypeError('retry must be a boolean');
93
- if (options.timeoutMs !== undefined) {
94
- if (typeof options.timeoutMs !== 'number' || !(options.timeoutMs > 0))
95
- throw new TypeError('timeoutMs must be a positive number');
96
- request.timeoutMs = options.timeoutMs;
97
- }
98
- return request;
99
- }
100
- function toResponse(value, parseJson) {
101
- if (!isRecord(value) || typeof value.status !== 'number' || typeof value.body !== 'string')
102
- throw new TypeError('http host call returned a malformed response');
103
- const raw = value;
104
- const headers = {};
105
- if (isRecord(raw.headers)) {
106
- for (const [name, item] of Object.entries(raw.headers))
107
- if (typeof item === 'string')
108
- headers[name.toLowerCase()] = item;
109
- }
110
- const text = raw.body;
111
- const url = typeof raw.url === 'string' ? raw.url : '';
112
- let data = text;
113
- if (parseJson && raw.bodyEncoding !== 'base64' && JSON_CONTENT_TYPE.test(headers['content-type'] ?? '') && text.length > 0) {
114
- try {
115
- data = jsonParse(text);
116
- }
117
- catch {
118
- throw new errors.FatalError(`response from ${url} is not valid JSON`);
119
- }
120
- }
121
- return { status: raw.status, url, headers, data, text };
122
- }
123
- function retryAfterMs(headers) {
124
- const value = headers['retry-after'];
125
- if (value === undefined)
126
- return undefined;
127
- const trimmed = value.trim();
128
- if (/^\d+(?:\.\d+)?$/.test(trimmed))
129
- return Number(trimmed) * 1000;
130
- const at = Date.parse(trimmed);
131
- return Number.isNaN(at) ? undefined : Math.max(0, at - now());
132
- }
133
- function clampWait(ms) {
134
- return Math.min(Math.max(0, ms), MAX_RETRY_WAIT_MS);
135
- }
136
- export function createHttp(defaultBaseUrl) {
137
- async function request(method, url, options = {}) {
138
- const req = buildRequest(method, url, options, defaultBaseUrl);
139
- const raw = options.raw === true;
140
- const label = `${req.method} ${req.url}`;
141
- const retryTransient = options.retry ?? IDEMPOTENT_METHODS.includes(req.method);
142
- const transientLimit = retryTransient ? TRANSIENT_RETRIES : 0;
143
- let rateRetries = 0;
144
- let transientRetries = 0;
145
- for (;;) {
146
- throwIfAborted();
147
- let value;
148
- try {
149
- value = await call('http', req);
150
- }
151
- catch (error) {
152
- if (error instanceof HostCallFailure && error.code === 'EGRESS_RATE_LIMITED') {
153
- const wait = error.retryAfterMs ?? RATE_LIMIT_BACKOFF_MS * 2 ** rateRetries;
154
- if (rateRetries >= RATE_LIMIT_RETRIES)
155
- throw new errors.RateLimited(wait, `${label} is still rate limited after ${RATE_LIMIT_RETRIES} retries`);
156
- rateRetries += 1;
157
- await sleep(clampWait(wait));
158
- continue;
159
- }
160
- if (error instanceof HostCallFailure && error.code === 'EGRESS_TIMEOUT') {
161
- if (transientRetries >= transientLimit)
162
- throw new errors.TransientError(`${label} timed out after ${transientRetries} retries`);
163
- await sleep(TRANSIENT_BACKOFF_MS * 2 ** transientRetries);
164
- transientRetries += 1;
165
- continue;
166
- }
167
- throw error;
168
- }
169
- const response = toResponse(value, !raw);
170
- if (raw)
171
- return response;
172
- const status = response.status;
173
- if (status === 429) {
174
- const wait = retryAfterMs(response.headers) ?? RATE_LIMIT_BACKOFF_MS * 2 ** rateRetries;
175
- if (rateRetries >= RATE_LIMIT_RETRIES)
176
- throw new errors.RateLimited(wait, `${label} returned 429 after ${RATE_LIMIT_RETRIES} retries`);
177
- rateRetries += 1;
178
- await sleep(clampWait(wait));
179
- continue;
180
- }
181
- if (TRANSIENT_STATUS.includes(status)) {
182
- if (transientRetries >= transientLimit)
183
- throw new errors.TransientError(`${label} returned ${status} after ${transientRetries} retries`);
184
- await sleep(TRANSIENT_BACKOFF_MS * 2 ** transientRetries);
185
- transientRetries += 1;
186
- continue;
187
- }
188
- if (status === 401)
189
- throw new errors.AuthError(`${label} returned 401`);
190
- if (status >= 400)
191
- throw new errors.FatalError(`${label} returned ${status}`);
192
- return response;
193
- }
194
- }
195
- const verb = (method) => (url, options) => request(method, url, options);
196
- return Object.freeze({
197
- get: verb('GET'),
198
- post: verb('POST'),
199
- put: verb('PUT'),
200
- patch: verb('PATCH'),
201
- delete: verb('DELETE'),
202
- request,
203
- });
204
- }
@@ -1,12 +0,0 @@
1
- export interface GuestApi {
2
- /** 只生效一次:`json` 为 `{ expect, job }`,`factory` 为上下文里编译出的 `(function (module, exports, require) {…})`。 */
3
- start(json: string, factory: unknown): void;
4
- handle(json: string): void;
5
- poll(): string;
6
- }
7
- export declare function bootGuest(): GuestApi;
8
- /**
9
- * 进程内执行(`./testing` 的 `createInProcessExecutor`,给 vitest 用):每次执行都重新求值一份 guest 运行时,
10
- * 这里的全局对象是宿主进程自己的,所以不拿掉任何全局;其余与 `bootGuest` 相同。
11
- */
12
- export declare function bootLocalGuest(): GuestApi;
@@ -1,89 +0,0 @@
1
- import { summarizeDefinition, validateConfig } from '../../validate.js';
2
- import { call, finish, handle, jsonParse, jsonStringify, poll } from './bridge.js';
3
- import { concurrencyFromDefinitionReply, prepareSecrets } from './ctx.js';
4
- import { discoverDeclaration, runCheck, runDiscover } from './discover.js';
5
- import { connectorStack, errorInfo, failure, failureFromError } from './failure.js';
6
- import { loadDefinition } from './loader.js';
7
- import { createLogBuffer } from './log.js';
8
- import { runPull } from './pull.js';
9
- import { runPush } from './push.js';
10
- const STRIPPED_GLOBALS = ['WebAssembly', 'ShadowRealm', 'console'];
11
- function issuesText(issues) {
12
- return issues.map((issue) => `${issue.path || '(root)'}: ${issue.message}`).join('; ');
13
- }
14
- async function run(definition, job) {
15
- if (job.kind === 'push')
16
- return runPush(definition, job);
17
- if (job.kind === 'pull' && !(definition.streams && Object.hasOwn(definition.streams, job.stream))) {
18
- return failure('CONNECTOR_LOAD_FAILED', `connector has no stream named ${job.stream}`, []);
19
- }
20
- const summary = jsonParse(jsonStringify(summarizeDefinition(definition)));
21
- const logs = createLogBuffer();
22
- let effectiveConcurrency;
23
- try {
24
- effectiveConcurrency = concurrencyFromDefinitionReply(await call('definition', summary));
25
- }
26
- catch (error) {
27
- return failureFromError(error, logs.drain());
28
- }
29
- const issues = validateConfig(definition.config, job.config);
30
- if (issues.length > 0)
31
- return failure('CONFIG_INVALID', `invalid connection config: ${issuesText(issues)}`, logs.drain());
32
- let secrets;
33
- try {
34
- secrets = await prepareSecrets(summary, job.secretFields);
35
- }
36
- catch (error) {
37
- return failureFromError(error, logs.drain());
38
- }
39
- const options = { config: job.config, secrets, summary, logs, effectiveConcurrency };
40
- if (job.kind === 'discover')
41
- return runDiscover(definition, options);
42
- if (job.kind === 'check')
43
- return runCheck(definition, options);
44
- if (job.discoverFirst !== true)
45
- return runPull(definition, job, options);
46
- const declared = await discoverDeclaration(definition, options);
47
- if (!declared.ok)
48
- return declared.failure;
49
- try {
50
- await call('declaration', { summary, discover: declared.discover });
51
- }
52
- catch (error) {
53
- return failureFromError(error, logs.drain());
54
- }
55
- const stream = Object.hasOwn(declared.discover.streams, job.stream) ? declared.discover.streams[job.stream] : undefined;
56
- if (!stream)
57
- return failure('SCHEMA_ERROR', `stream ${job.stream} is not in the discover result`, logs.drain());
58
- return runPull(definition, { ...job, tables: Object.keys(stream.tables) }, options);
59
- }
60
- async function execute(json, factory) {
61
- const { expect, job } = jsonParse(json);
62
- const loaded = loadDefinition(factory, expect);
63
- if (!loaded.ok)
64
- return failure('CONNECTOR_LOAD_FAILED', loaded.message, [], connectorStack(loaded.stack));
65
- return run(loaded.definition, job);
66
- }
67
- let started = false;
68
- function start(json, factory) {
69
- if (started || typeof json !== 'string')
70
- return;
71
- started = true;
72
- let pending;
73
- try {
74
- pending = execute(json, factory);
75
- }
76
- catch (error) {
77
- finish(failure('FATAL_ERROR', errorInfo(error).message, []));
78
- return;
79
- }
80
- pending.then((outcome) => finish(outcome), (error) => finish(failure('FATAL_ERROR', `connector runtime failed: ${errorInfo(error).message}`, [])));
81
- }
82
- export function bootGuest() {
83
- for (const name of STRIPPED_GLOBALS)
84
- Reflect.deleteProperty(globalThis, name);
85
- return Object.freeze({ start, handle, poll });
86
- }
87
- export function bootLocalGuest() {
88
- return Object.freeze({ start, handle, poll });
89
- }
@@ -1,12 +0,0 @@
1
- export const PULL_BATCH_MAX_RECORDS = 1000;
2
- export const PULL_BATCH_MAX_BYTES = 4 * 1024 * 1024;
3
- export const PULL_REJECT_THRESHOLD = 1000;
4
- export const MAX_LOGS = 1000;
5
- export const MAX_LOG_MESSAGE = 4096;
6
- export const DEFAULT_CONCURRENCY = 4;
7
- export const RATE_LIMIT_RETRIES = 5;
8
- export const TRANSIENT_RETRIES = 3;
9
- export const TRANSIENT_BACKOFF_MS = 200;
10
- export const RATE_LIMIT_BACKOFF_MS = 1000;
11
- export const MAX_RETRY_WAIT_MS = 600_000;
12
- export const CONNECTOR_FILENAME = 'connector.js';
@@ -1,15 +0,0 @@
1
- import type { ConnectorDefinition } from '../../types.js';
2
- export declare const SDK_SPECIFIER = "@deepexi/datasense-connector";
3
- export type LoadOutcome = {
4
- ok: true;
5
- definition: ConnectorDefinition;
6
- } | {
7
- ok: false;
8
- message: string;
9
- stack: string | undefined;
10
- };
11
- /** 执行连接器代码、取默认导出、校验定义并核对期望的名字与版本。 */
12
- export declare function loadDefinition(factory: unknown, expect: {
13
- name: string;
14
- version: string;
15
- }): LoadOutcome;
@@ -1,42 +0,0 @@
1
- import { ConnectorError } from '../../errors.js';
2
- import * as sdk from '../../index.js';
3
- import { validateConnectorDefinition } from '../../validate.js';
4
- import { errorInfo } from './failure.js';
5
- export const SDK_SPECIFIER = '@deepexi/datasense-connector';
6
- const sdkModule = Object.freeze({ ...sdk, __esModule: true });
7
- function connectorRequire(specifier) {
8
- if (specifier === SDK_SPECIFIER)
9
- return sdkModule;
10
- throw new ConnectorError('CONNECTOR_LOAD_FAILED', `cannot require ${String(specifier)}: connectors can only import ${SDK_SPECIFIER} and relative files`);
11
- }
12
- function resolveExport(exported) {
13
- if (exported !== null && (typeof exported === 'object' || typeof exported === 'function')) {
14
- const fallback = exported.default;
15
- if (fallback !== undefined && fallback !== null)
16
- return fallback;
17
- }
18
- return exported;
19
- }
20
- export function loadDefinition(factory, expect) {
21
- if (typeof factory !== 'function')
22
- return { ok: false, message: 'connector code did not compile to a module', stack: undefined };
23
- let definition;
24
- try {
25
- const module = { exports: {} };
26
- Reflect.apply(factory, undefined, [module, module.exports, connectorRequire]);
27
- definition = resolveExport(module.exports);
28
- }
29
- catch (error) {
30
- const info = errorInfo(error);
31
- return { ok: false, message: `connector code failed to load: ${info.message}`, stack: info.stack };
32
- }
33
- const issues = validateConnectorDefinition(definition);
34
- if (issues.length > 0) {
35
- return { ok: false, message: `invalid connector definition: ${issues.map((issue) => `${issue.path || '(root)'}: ${issue.message}`).join('; ')}`, stack: undefined };
36
- }
37
- const loaded = definition;
38
- if (loaded.name !== expect.name || loaded.version !== expect.version) {
39
- return { ok: false, message: `connector code defines ${loaded.name}@${loaded.version}, expected ${expect.name}@${expect.version}`, stack: undefined };
40
- }
41
- return { ok: true, definition: loaded };
42
- }
@@ -1,10 +0,0 @@
1
- /** `ctx.log`:一次运行最多保留 1000 条,单条超过 4096 字符截断;拉取作业每批 `emit` 带走已有的日志。 */
2
- import type { ConnectorLog } from '../../types.js';
3
- import type { GuestLog } from '../protocol.js';
4
- export interface LogBuffer {
5
- /** 取走尚未上报的日志。 */
6
- drain(): GuestLog[];
7
- /** `isOpen` 返回 false 时丢弃(推送请求结束后再写日志无效)。 */
8
- logger(requestId?: string, isOpen?: () => boolean): ConnectorLog;
9
- }
10
- export declare function createLogBuffer(): LogBuffer;
@@ -1,36 +0,0 @@
1
- import { jsonStringify } from './bridge.js';
2
- import { MAX_LOG_MESSAGE, MAX_LOGS } from './limits.js';
3
- import { isoNow } from './time.js';
4
- function render(message, data) {
5
- let text = typeof message === 'string' ? message : 'non-string log message';
6
- if (data !== undefined) {
7
- try {
8
- const extra = jsonStringify(data);
9
- if (typeof extra === 'string')
10
- text = `${text} ${extra}`;
11
- }
12
- catch {
13
- }
14
- }
15
- return text.length > MAX_LOG_MESSAGE ? text.slice(0, MAX_LOG_MESSAGE) : text;
16
- }
17
- export function createLogBuffer() {
18
- let entries = [];
19
- let total = 0;
20
- const write = (level, requestId, isOpen) => (message, data) => {
21
- if (total >= MAX_LOGS || (isOpen && !isOpen()))
22
- return;
23
- total += 1;
24
- entries[entries.length] = { at: isoNow(), level, message: render(message, data), ...(requestId !== undefined ? { requestId } : {}) };
25
- };
26
- return {
27
- drain() {
28
- const out = entries;
29
- entries = [];
30
- return out;
31
- },
32
- logger(requestId, isOpen) {
33
- return Object.freeze({ info: write('info', requestId, isOpen), warn: write('warn', requestId, isOpen), error: write('error', requestId, isOpen) });
34
- },
35
- };
36
- }
@@ -1,4 +0,0 @@
1
- export type MapFunction = <T, R>(items: readonly T[], fn: (item: T) => Promise<R>, options?: {
2
- concurrency?: number;
3
- }) => Promise<R[]>;
4
- export declare function createMap(concurrency: number | null): MapFunction;
@@ -1,45 +0,0 @@
1
- import { throwIfAborted } from './bridge.js';
2
- import { DEFAULT_CONCURRENCY } from './limits.js';
3
- function positiveInteger(value, what) {
4
- if (typeof value !== 'number' || !Number.isInteger(value) || value < 1)
5
- throw new TypeError(`${what} must be a positive integer`);
6
- return value;
7
- }
8
- export function createMap(concurrency) {
9
- const cap = concurrency ?? DEFAULT_CONCURRENCY;
10
- return async (items, fn, options) => {
11
- if (!Array.isArray(items))
12
- throw new TypeError('ctx.map expects an array');
13
- if (typeof fn !== 'function')
14
- throw new TypeError('ctx.map expects a function');
15
- const requested = options?.concurrency === undefined ? cap : positiveInteger(options.concurrency, 'ctx.map concurrency');
16
- const limit = Math.min(requested, cap);
17
- const results = new Array(items.length);
18
- let next = 0;
19
- let failed = false;
20
- let firstError;
21
- const worker = async () => {
22
- while (!failed && next < items.length) {
23
- const index = next;
24
- next += 1;
25
- try {
26
- throwIfAborted();
27
- results[index] = await fn(items[index]);
28
- }
29
- catch (error) {
30
- if (!failed) {
31
- failed = true;
32
- firstError = error;
33
- }
34
- }
35
- }
36
- };
37
- const workers = [];
38
- for (let i = 0; i < Math.min(limit, items.length); i += 1)
39
- workers.push(worker());
40
- await Promise.all(workers);
41
- if (failed)
42
- throw firstError;
43
- return results;
44
- };
45
- }
@@ -1,6 +0,0 @@
1
- /** `ctx.paginate`:四种 HTTP 分页与数据库按键分页,都是产出 `Page` 的异步迭代器;取到空页即结束(空页不产出)。 */
2
- import type { HttpClient, Page, Paginate } from '../../types.js';
3
- export declare function makePage<Row>(rows: Row[]): Page<Row>;
4
- /** 解析 `Link` 头里 `rel="next"` 的地址。 */
5
- export declare function nextLink(header: string | undefined): string | null;
6
- export declare function createPaginate(http: HttpClient): Paginate;
@@ -1,180 +0,0 @@
1
- import { throwIfAborted } from './bridge.js';
2
- function compare(a, b) {
3
- if (typeof a === 'number' && typeof b === 'number')
4
- return a - b;
5
- if (typeof a === 'bigint' && typeof b === 'bigint')
6
- return a < b ? -1 : a > b ? 1 : 0;
7
- if (a instanceof Date && b instanceof Date)
8
- return a.getTime() - b.getTime();
9
- const left = String(a);
10
- const right = String(b);
11
- return left < right ? -1 : left > right ? 1 : 0;
12
- }
13
- export function makePage(rows) {
14
- return Object.freeze({
15
- rows,
16
- maxOf(fn) {
17
- let max;
18
- for (const row of rows) {
19
- const value = fn(row);
20
- if (value === undefined || value === null)
21
- continue;
22
- if (max === undefined || compare(value, max) > 0)
23
- max = value;
24
- }
25
- return max;
26
- },
27
- });
28
- }
29
- function rowsOf(value, what) {
30
- if (!Array.isArray(value))
31
- throw new TypeError(`${what} must return an array of rows`);
32
- return value;
33
- }
34
- function requireFunction(value, what) {
35
- if (typeof value !== 'function')
36
- throw new TypeError(`${what} must be a function`);
37
- }
38
- async function* pageIterator(options) {
39
- requireFunction(options.request, 'paginate.page request');
40
- requireFunction(options.rows, 'paginate.page rows');
41
- const first = options.firstPage ?? 1;
42
- for (let page = first;; page += 1) {
43
- throwIfAborted();
44
- const res = await options.request(page);
45
- const rows = rowsOf(options.rows(res), 'paginate.page rows');
46
- if (rows.length === 0)
47
- return;
48
- yield makePage(rows);
49
- if (options.totalPages) {
50
- const total = options.totalPages(res);
51
- if (typeof total !== 'number' || !Number.isFinite(total) || page - first + 1 >= total)
52
- return;
53
- }
54
- }
55
- }
56
- async function* offsetIterator(options) {
57
- requireFunction(options.request, 'paginate.offset request');
58
- requireFunction(options.rows, 'paginate.offset rows');
59
- const limit = options.limit;
60
- if (typeof limit !== 'number' || !Number.isInteger(limit) || limit < 1)
61
- throw new TypeError('paginate.offset limit must be a positive integer');
62
- for (let offset = options.startOffset ?? 0;; offset += limit) {
63
- throwIfAborted();
64
- const res = await options.request(offset, limit);
65
- const rows = rowsOf(options.rows(res), 'paginate.offset rows');
66
- if (rows.length === 0)
67
- return;
68
- yield makePage(rows);
69
- if (options.total) {
70
- const total = options.total(res);
71
- if (typeof total !== 'number' || !Number.isFinite(total) || offset + limit >= total)
72
- return;
73
- }
74
- else if (rows.length < limit) {
75
- return;
76
- }
77
- }
78
- }
79
- async function* cursorIterator(options) {
80
- requireFunction(options.request, 'paginate.cursor request');
81
- requireFunction(options.rows, 'paginate.cursor rows');
82
- requireFunction(options.nextCursor, 'paginate.cursor nextCursor');
83
- let cursor;
84
- const seen = new Set();
85
- for (;;) {
86
- throwIfAborted();
87
- const res = await options.request(cursor);
88
- const rows = rowsOf(options.rows(res), 'paginate.cursor rows');
89
- if (rows.length > 0)
90
- yield makePage(rows);
91
- const next = options.nextCursor(res);
92
- if (next === null || next === undefined || next === '')
93
- return;
94
- const text = String(next);
95
- if (seen.has(text))
96
- throw new TypeError(`paginate.cursor got the same cursor twice: ${text}`);
97
- seen.add(text);
98
- cursor = text;
99
- }
100
- }
101
- export function nextLink(header) {
102
- if (!header)
103
- return null;
104
- for (const part of header.split(',')) {
105
- const match = /^\s*<([^>]*)>(.*)$/.exec(part);
106
- if (!match)
107
- continue;
108
- for (const param of (match[2] ?? '').split(';')) {
109
- const rel = /^\s*rel\s*=\s*"?([^"]*)"?\s*$/i.exec(param);
110
- if (rel && (rel[1] ?? '').toLowerCase().split(/\s+/).includes('next'))
111
- return match[1] ?? null;
112
- }
113
- }
114
- return null;
115
- }
116
- function resolveAgainst(link, current) {
117
- if (/^https?:\/\//i.test(link))
118
- return link;
119
- const origin = /^(https?:\/\/[^/?#]+)/i.exec(current)?.[1];
120
- if (!origin)
121
- return link;
122
- if (link.startsWith('//'))
123
- return `${current.slice(0, current.indexOf(':') + 1)}${link}`;
124
- if (link.startsWith('/'))
125
- return `${origin}${link}`;
126
- const path = current.slice(origin.length).replace(/[?#].*$/, '');
127
- const dir = path.slice(0, path.lastIndexOf('/') + 1) || '/';
128
- return `${origin}${dir}${link}`;
129
- }
130
- function linkIterator(http) {
131
- return async function* (options) {
132
- requireFunction(options.rows, 'paginate.link rows');
133
- if (typeof options.url !== 'string')
134
- throw new TypeError('paginate.link url must be a string');
135
- let url = options.url;
136
- const seen = new Set();
137
- while (url !== null) {
138
- throwIfAborted();
139
- seen.add(url);
140
- const res = await http.get(url, options.options);
141
- const rows = rowsOf(options.rows(res), 'paginate.link rows');
142
- if (rows.length > 0)
143
- yield makePage(rows);
144
- const next = options.next ? options.next(res) : nextLink(res.headers.link);
145
- if (next === null || next === undefined || next === '')
146
- return;
147
- const resolved = resolveAgainst(String(next), res.url || url);
148
- if (seen.has(resolved))
149
- throw new TypeError(`paginate.link got the same next link twice: ${resolved}`);
150
- url = resolved;
151
- }
152
- };
153
- }
154
- async function* keysetIterator(options) {
155
- requireFunction(options.query, 'paginate.keyset query');
156
- requireFunction(options.key, 'paginate.keyset key');
157
- let last;
158
- for (;;) {
159
- throwIfAborted();
160
- const rows = [];
161
- for await (const row of options.query(last))
162
- rows.push(row);
163
- if (rows.length === 0)
164
- return;
165
- yield makePage(rows);
166
- const key = options.key(rows[rows.length - 1]);
167
- if (key === undefined || key === null)
168
- throw new TypeError('paginate.keyset key returned no value for the last row');
169
- last = key;
170
- }
171
- }
172
- export function createPaginate(http) {
173
- return Object.freeze({
174
- page: pageIterator,
175
- offset: offsetIterator,
176
- cursor: cursorIterator,
177
- link: linkIterator(http),
178
- keyset: keysetIterator,
179
- });
180
- }