@deepexi/datasense-connector 0.0.0-stage → 3.0.1
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 +172 -2
- package/cjs/index.cjs +1015 -0
- package/cjs/runtime.cjs +752 -0
- package/cjs/testing.cjs +3591 -0
- package/errors.d.ts +17 -0
- package/errors.js +53 -0
- package/guest.cjs +2659 -0
- package/index.d.ts +13 -0
- package/index.js +7 -0
- package/internal/location.d.ts +6 -0
- package/internal/location.js +8 -0
- package/package.json +42 -4
- package/presentation.d.ts +38 -0
- package/presentation.js +268 -0
- package/runtime/guest/bridge.d.ts +53 -0
- package/runtime/guest/bridge.js +187 -0
- package/runtime/guest/cache.d.ts +3 -0
- package/runtime/guest/cache.js +42 -0
- package/runtime/guest/crypto.d.ts +13 -0
- package/runtime/guest/crypto.js +177 -0
- package/runtime/guest/ctx.d.ts +30 -0
- package/runtime/guest/ctx.js +124 -0
- package/runtime/guest/db.d.ts +6 -0
- package/runtime/guest/db.js +59 -0
- package/runtime/guest/discover.d.ts +29 -0
- package/runtime/guest/discover.js +51 -0
- package/runtime/guest/failure.d.ts +24 -0
- package/runtime/guest/failure.js +63 -0
- package/runtime/guest/http.d.ts +4 -0
- package/runtime/guest/http.js +204 -0
- package/runtime/guest/index.d.ts +12 -0
- package/runtime/guest/index.js +89 -0
- package/runtime/guest/limits.d.ts +20 -0
- package/runtime/guest/limits.js +12 -0
- package/runtime/guest/loader.d.ts +15 -0
- package/runtime/guest/loader.js +42 -0
- package/runtime/guest/log.d.ts +10 -0
- package/runtime/guest/log.js +36 -0
- package/runtime/guest/map.d.ts +4 -0
- package/runtime/guest/map.js +45 -0
- package/runtime/guest/paginate.d.ts +6 -0
- package/runtime/guest/paginate.js +180 -0
- package/runtime/guest/pull.d.ts +12 -0
- package/runtime/guest/pull.js +150 -0
- package/runtime/guest/push.d.ts +18 -0
- package/runtime/guest/push.js +80 -0
- package/runtime/guest/signal.d.ts +3 -0
- package/runtime/guest/signal.js +12 -0
- package/runtime/guest/time.d.ts +5 -0
- package/runtime/guest/time.js +127 -0
- package/runtime/guest-runtime.d.ts +7 -0
- package/runtime/guest-runtime.js +51 -0
- package/runtime/index.d.ts +18 -0
- package/runtime/index.js +3 -0
- package/runtime/protocol.d.ts +276 -0
- package/runtime/protocol.js +409 -0
- package/runtime/session.d.ts +12 -0
- package/runtime/session.js +191 -0
- package/t.d.ts +18 -0
- package/t.js +26 -0
- package/testing/child.cjs +350 -0
- package/testing/child.d.ts +1 -0
- package/testing/child.js +12 -0
- package/testing/compliance.d.ts +41 -0
- package/testing/compliance.js +291 -0
- package/testing/engine.d.ts +66 -0
- package/testing/engine.js +339 -0
- package/testing/fixtures.d.ts +145 -0
- package/testing/fixtures.js +407 -0
- package/testing/gateway.d.ts +65 -0
- package/testing/gateway.js +402 -0
- package/testing/import-guard.d.ts +57 -0
- package/testing/import-guard.js +187 -0
- package/testing/index.d.ts +33 -0
- package/testing/index.js +10 -0
- package/testing/local-db.d.ts +27 -0
- package/testing/local-db.js +181 -0
- package/testing/pinned-http.d.ts +14 -0
- package/testing/pinned-http.js +125 -0
- package/testing/recorder.d.ts +15 -0
- package/testing/recorder.js +116 -0
- package/testing/redaction.d.ts +28 -0
- package/testing/redaction.js +67 -0
- package/testing/replay.d.ts +50 -0
- package/testing/replay.js +313 -0
- package/testing/schema.d.ts +13 -0
- package/testing/schema.js +65 -0
- package/testing/source-loader.d.ts +19 -0
- package/testing/source-loader.js +238 -0
- package/testing/types.d.ts +79 -0
- package/testing/types.js +18 -0
- package/types.d.ts +302 -0
- package/types.js +13 -0
- package/validate.d.ts +78 -0
- package/validate.js +597 -0
package/README.md
CHANGED
|
@@ -1,3 +1,173 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @deepexi/datasense-connector
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SDK for writing Datasense connectors, plus tools to run them on your own machine.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install --save-dev @deepexi/datasense-connector
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
A connector is a small TypeScript project: a `package.json` whose only dependency is
|
|
10
|
+
`@deepexi/datasense-connector`, and an entry file (`main`, default `src/index.ts`) that default-exports
|
|
11
|
+
`defineConnector(...)`. Upload the source directory with `ontos connector publish`; the platform bundles it and
|
|
12
|
+
the connector runner executes it in an isolated VM context.
|
|
13
|
+
|
|
14
|
+
## `@deepexi/datasense-connector`
|
|
15
|
+
|
|
16
|
+
- `defineConnector` and the `t.*` type builders (`t.secret({ exposeToScript })` for credential fields)
|
|
17
|
+
- Pull connectors: `hosts`, `db`, `rateLimit`, `check(ctx)`, `discover(ctx)` returning streams and push tables,
|
|
18
|
+
and `streams.<name>(ctx, input)` async generators yielding `ctx.record` / `ctx.remove` / `ctx.checkpoint`
|
|
19
|
+
- Push connectors: named handlers `push: { <name>: { verify?, map } }`, for example `push: { default: { map } }`
|
|
20
|
+
- `ConnectorError` and `errors.AuthError` / `RateLimited` / `TransientError` / `SchemaError` / `FatalError` /
|
|
21
|
+
`RejectedPayload` / `NotAvailable`
|
|
22
|
+
- `validateConnectorDefinition` / `validateDiscoverResult` / `validateConfig` / `summarizeDefinition`, shared with the platform
|
|
23
|
+
- `SDK_VERSION` and `SUPPORTED_SDK_MAJORS`
|
|
24
|
+
|
|
25
|
+
Connector code imports only `@deepexi/datasense-connector` itself and relative files. Node or Bun built-ins,
|
|
26
|
+
other packages, subpaths of this package and import attributes are rejected when the source is bundled.
|
|
27
|
+
|
|
28
|
+
The `ctx` passed to `check`, `discover` and streams (`http`, `paginate`, `db`, `map`, `cache`, `crypto`, `time`,
|
|
29
|
+
`log`, `signal`) is provided by the runtime. External write operations (`writes`) and `ctx.files` are not
|
|
30
|
+
available yet.
|
|
31
|
+
|
|
32
|
+
Pull semantics worth knowing:
|
|
33
|
+
|
|
34
|
+
- Records are sent in batches of at most 1000 records or 4 MiB. Yielding `ctx.checkpoint(state)` sends the
|
|
35
|
+
pending batch at once, followed by the checkpoint, and the platform commits the checkpoint after those records
|
|
36
|
+
are written. A failed run resumes from the last committed checkpoint, so only the records yielded after it
|
|
37
|
+
are fetched again. Yield a checkpoint after each page.
|
|
38
|
+
- `hosts` limits every outbound request, and `ctx.db` too. A database connector must list the database host,
|
|
39
|
+
for example `hosts: ['{config.host}']`, or its queries are denied with `EGRESS_HOST_DENIED`.
|
|
40
|
+
- Secrets are handles (`ontos:secret:<field>`) that the gateway replaces on the way out. Plaintext is only
|
|
41
|
+
available for fields declared `t.secret({ exposeToScript: true })`, and reading them is logged on the run.
|
|
42
|
+
- `ctx.http` waits and resends on gateway rate limits and upstream 429. Upstream 502 / 503 / 504 and gateway
|
|
43
|
+
timeouts are retried with backoff only for idempotent methods (GET, HEAD, PUT, DELETE); pass `retry: true`
|
|
44
|
+
to retry a POST / PATCH as well, or `retry: false` to never retry.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { defineConnector, t } from '@deepexi/datasense-connector';
|
|
48
|
+
|
|
49
|
+
export default defineConnector({
|
|
50
|
+
name: 'demo_pull',
|
|
51
|
+
version: '1.0.0',
|
|
52
|
+
config: t.object({ apiKey: t.secret(), region: t.string() }),
|
|
53
|
+
hosts: ['{config.region}.api.example.com'],
|
|
54
|
+
discover: () => ({
|
|
55
|
+
streams: {
|
|
56
|
+
orders: {
|
|
57
|
+
tables: { order: { columns: { id: t.string(), updated_at: t.instant() }, primaryKey: ['id'] } },
|
|
58
|
+
syncModes: ['full', 'incremental'],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}),
|
|
62
|
+
streams: {
|
|
63
|
+
async *orders(ctx, { state }) {
|
|
64
|
+
const since = (state as { since?: string } | undefined)?.since;
|
|
65
|
+
for await (const page of ctx.paginate.cursor({
|
|
66
|
+
request: (cursor) => ctx.http.get<{ items: Array<{ id: string; updated_at: string }>; next?: string }>(
|
|
67
|
+
`https://${String(ctx.config.region)}.api.example.com/orders`,
|
|
68
|
+
{ query: { cursor, since }, headers: { authorization: `Bearer ${ctx.secrets.apiKey}` } },
|
|
69
|
+
),
|
|
70
|
+
rows: (res) => res.data.items,
|
|
71
|
+
nextCursor: (res) => res.data.next,
|
|
72
|
+
})) {
|
|
73
|
+
for (const row of page.rows) yield ctx.record('order', row);
|
|
74
|
+
yield ctx.checkpoint({ since: page.maxOf((row) => row.updated_at) ?? since });
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## `@deepexi/datasense-connector/testing`
|
|
82
|
+
|
|
83
|
+
Runs a connector locally, with a local gateway that answers its outbound HTTP, database, crypto, cache and
|
|
84
|
+
secret calls the way the platform does. Use it from your tests (for example vitest). Connector source must not
|
|
85
|
+
import this entry.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { expect, test } from 'vitest';
|
|
89
|
+
import { createLocalGateway, runStream } from '@deepexi/datasense-connector/testing';
|
|
90
|
+
import connector from '../src/index';
|
|
91
|
+
|
|
92
|
+
test('reads the first page', async () => {
|
|
93
|
+
const host = createLocalGateway({
|
|
94
|
+
hosts: ['eu.api.example.com'],
|
|
95
|
+
secrets: { apiKey: process.env.DEMO_API_KEY ?? '' },
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
const result = await runStream(connector, { stream: 'orders', config: { region: 'eu' }, limit: 50, host });
|
|
99
|
+
expect(result.error).toBeNull();
|
|
100
|
+
expect(result.records.length).toBeGreaterThan(0);
|
|
101
|
+
} finally {
|
|
102
|
+
await host.close();
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- `runStream`, `runDiscover`, `runCheck` and `runPush` run an imported definition in the current process and
|
|
108
|
+
return a `LocalRunResult`: `records`, `removals`, `checkpoints`, `logs`, `rejected`, `declaration`, `stopped`
|
|
109
|
+
and `error` (`null` on success). `runStream` runs `check` and `discover` first and accepts records only for the
|
|
110
|
+
tables the stream declares. With `limit`, the run stops after that many records and `stopped` is `true`.
|
|
111
|
+
- `createLocalGateway(options)`:
|
|
112
|
+
- `hosts`: the hosts the connector may reach (exact names or `*.suffix`). Anything else fails with
|
|
113
|
+
`EGRESS_HOST_DENIED`. Cloud metadata addresses are always denied.
|
|
114
|
+
- `secrets`: field to plaintext. Secret handles in URLs, headers, bodies and crypto keys are replaced on the
|
|
115
|
+
way out, and plaintext in responses, logs and error messages is replaced with `***`.
|
|
116
|
+
- `rateLimit: { perSecond, concurrency }`: requests are spaced and queued instead of failing.
|
|
117
|
+
- `db`: connection settings for `ctx.db`. Queries run in a read-only transaction with cursor paging and
|
|
118
|
+
need the optional peer dependency `postgres` (`npm install --save-dev postgres`); without it the run ends
|
|
119
|
+
with `DB_DRIVER_MISSING`.
|
|
120
|
+
- `fetch`: replaces the network in tests. `onExchange` receives every HTTP, database and crypto exchange.
|
|
121
|
+
- `loadConnectorSource(dir, { typescript })` reads a connector project, checks its imports with the same rules
|
|
122
|
+
as the platform and transpiles it with your TypeScript compiler (`import ts from 'typescript'`).
|
|
123
|
+
`createVmExecutor(source).run(job, host, { limit, timeoutMs })` then runs it in a child process with an empty
|
|
124
|
+
environment and the same VM context as the platform runner, and kills the process on timeout.
|
|
125
|
+
`createInProcessExecutor(definition)` runs the same jobs without a child process.
|
|
126
|
+
|
|
127
|
+
### Fixtures
|
|
128
|
+
|
|
129
|
+
A fixture is a recorded run that replays without the network. Each case lives in
|
|
130
|
+
`fixtures/<case>/` (case names match `^[a-z0-9][a-z0-9_-]{0,63}$`):
|
|
131
|
+
|
|
132
|
+
- `case.json`: `{ kind: 'pull', stream, mode, state?, streamConfig?, config, secrets?, limit?, expect? }`,
|
|
133
|
+
`{ kind: 'push', push, config, secrets?, expect? }` or `{ kind: 'discover', config, secrets?, expect? }`.
|
|
134
|
+
- `exchanges.ndjson`: one recorded HTTP, database or crypto exchange per line.
|
|
135
|
+
- `requests.ndjson`: the inbox requests a push case replays (required for push cases).
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import ts from 'typescript';
|
|
139
|
+
import {
|
|
140
|
+
createInProcessExecutor, createLocalGateway, createRecorder, readFixtureDir, runFixtures, runStream, serializeFixture,
|
|
141
|
+
} from '@deepexi/datasense-connector/testing';
|
|
142
|
+
|
|
143
|
+
// Record: attach a recorder to the local gateway, run, then write the files under fixtures/<case>/.
|
|
144
|
+
const secrets = { apiKey: process.env.DEMO_API_KEY ?? '' };
|
|
145
|
+
const recorder = createRecorder({ secrets });
|
|
146
|
+
const host = createLocalGateway({ hosts: ['eu.api.example.com'], secrets, onExchange: recorder.onExchange });
|
|
147
|
+
await runStream(connector, { stream: 'orders', config: { region: 'eu' }, limit: 20, host });
|
|
148
|
+
const files = serializeFixture('orders-first-page', {
|
|
149
|
+
case: { kind: 'pull', stream: 'orders', mode: 'full', config: { region: 'eu' }, secrets, limit: 20 },
|
|
150
|
+
exchanges: recorder.exchanges(),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Replay: every case must come back ok.
|
|
154
|
+
const results = await runFixtures(createInProcessExecutor(connector), await readFixtureDir('.'));
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- Recording replaces every secret value of 4 or more characters with `***`, as well as the values of credential
|
|
158
|
+
headers (`authorization`, `cookie`, `set-cookie`, `x-api-key`, ...) and of query parameters that contain a
|
|
159
|
+
secret handle. Handles such as `ontos:secret:apiKey` stay as they are. A database query read in several pages
|
|
160
|
+
is recorded as one exchange holding every row.
|
|
161
|
+
- During replay, an HTTP request matches a recorded one by method, URL (query sorted by key) and body; a request
|
|
162
|
+
with no match fails with `FIXTURE_EXCHANGE_MISMATCH` and never reaches the network. `secret.reveal` answers
|
|
163
|
+
from `case.secrets`, the cache is in memory, and `hash` is computed locally when it was not recorded.
|
|
164
|
+
- `runFixture` / `runFixtures` return a `FixtureCaseResult` per case with `ok`, `findings`, `stats`,
|
|
165
|
+
`declaration` and `error`. Pull cases run `check` and `discover` first; full-mode pull cases are replayed twice
|
|
166
|
+
to check that the read is repeatable. Findings: `PRIMARY_KEY_DUPLICATE`, `RECORD_SCHEMA_MISMATCH`,
|
|
167
|
+
`UNDECLARED_TABLE`, `CHECKPOINT_NOT_MONOTONIC`, `FULL_READ_NOT_REPEATABLE`, `FIXTURE_EXCHANGE_MISMATCH`,
|
|
168
|
+
`EXPECTATION_FAILED` and `FIXTURE_EXCHANGE_UNUSED`. A case is `ok` when the run succeeds and the only findings
|
|
169
|
+
are `FIXTURE_EXCHANGE_UNUSED`.
|
|
170
|
+
- `checkCompliance` and `inferRecordSchema` are the same checks as plain functions.
|
|
171
|
+
|
|
172
|
+
The SDK itself has no runtime dependencies. The root entry does not import Node or Bun built-ins, so it can be
|
|
173
|
+
bundled into a connector and run in an isolated VM context; `./testing` needs Node 20+ or Bun.
|