@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.
Files changed (95) hide show
  1. package/README.md +172 -2
  2. package/cjs/index.cjs +1015 -0
  3. package/cjs/runtime.cjs +752 -0
  4. package/cjs/testing.cjs +3591 -0
  5. package/errors.d.ts +17 -0
  6. package/errors.js +53 -0
  7. package/guest.cjs +2659 -0
  8. package/index.d.ts +13 -0
  9. package/index.js +7 -0
  10. package/internal/location.d.ts +6 -0
  11. package/internal/location.js +8 -0
  12. package/package.json +42 -4
  13. package/presentation.d.ts +38 -0
  14. package/presentation.js +268 -0
  15. package/runtime/guest/bridge.d.ts +53 -0
  16. package/runtime/guest/bridge.js +187 -0
  17. package/runtime/guest/cache.d.ts +3 -0
  18. package/runtime/guest/cache.js +42 -0
  19. package/runtime/guest/crypto.d.ts +13 -0
  20. package/runtime/guest/crypto.js +177 -0
  21. package/runtime/guest/ctx.d.ts +30 -0
  22. package/runtime/guest/ctx.js +124 -0
  23. package/runtime/guest/db.d.ts +6 -0
  24. package/runtime/guest/db.js +59 -0
  25. package/runtime/guest/discover.d.ts +29 -0
  26. package/runtime/guest/discover.js +51 -0
  27. package/runtime/guest/failure.d.ts +24 -0
  28. package/runtime/guest/failure.js +63 -0
  29. package/runtime/guest/http.d.ts +4 -0
  30. package/runtime/guest/http.js +204 -0
  31. package/runtime/guest/index.d.ts +12 -0
  32. package/runtime/guest/index.js +89 -0
  33. package/runtime/guest/limits.d.ts +20 -0
  34. package/runtime/guest/limits.js +12 -0
  35. package/runtime/guest/loader.d.ts +15 -0
  36. package/runtime/guest/loader.js +42 -0
  37. package/runtime/guest/log.d.ts +10 -0
  38. package/runtime/guest/log.js +36 -0
  39. package/runtime/guest/map.d.ts +4 -0
  40. package/runtime/guest/map.js +45 -0
  41. package/runtime/guest/paginate.d.ts +6 -0
  42. package/runtime/guest/paginate.js +180 -0
  43. package/runtime/guest/pull.d.ts +12 -0
  44. package/runtime/guest/pull.js +150 -0
  45. package/runtime/guest/push.d.ts +18 -0
  46. package/runtime/guest/push.js +80 -0
  47. package/runtime/guest/signal.d.ts +3 -0
  48. package/runtime/guest/signal.js +12 -0
  49. package/runtime/guest/time.d.ts +5 -0
  50. package/runtime/guest/time.js +127 -0
  51. package/runtime/guest-runtime.d.ts +7 -0
  52. package/runtime/guest-runtime.js +51 -0
  53. package/runtime/index.d.ts +18 -0
  54. package/runtime/index.js +3 -0
  55. package/runtime/protocol.d.ts +276 -0
  56. package/runtime/protocol.js +409 -0
  57. package/runtime/session.d.ts +12 -0
  58. package/runtime/session.js +191 -0
  59. package/t.d.ts +18 -0
  60. package/t.js +26 -0
  61. package/testing/child.cjs +350 -0
  62. package/testing/child.d.ts +1 -0
  63. package/testing/child.js +12 -0
  64. package/testing/compliance.d.ts +41 -0
  65. package/testing/compliance.js +291 -0
  66. package/testing/engine.d.ts +66 -0
  67. package/testing/engine.js +339 -0
  68. package/testing/fixtures.d.ts +145 -0
  69. package/testing/fixtures.js +407 -0
  70. package/testing/gateway.d.ts +65 -0
  71. package/testing/gateway.js +402 -0
  72. package/testing/import-guard.d.ts +57 -0
  73. package/testing/import-guard.js +187 -0
  74. package/testing/index.d.ts +33 -0
  75. package/testing/index.js +10 -0
  76. package/testing/local-db.d.ts +27 -0
  77. package/testing/local-db.js +181 -0
  78. package/testing/pinned-http.d.ts +14 -0
  79. package/testing/pinned-http.js +125 -0
  80. package/testing/recorder.d.ts +15 -0
  81. package/testing/recorder.js +116 -0
  82. package/testing/redaction.d.ts +28 -0
  83. package/testing/redaction.js +67 -0
  84. package/testing/replay.d.ts +50 -0
  85. package/testing/replay.js +313 -0
  86. package/testing/schema.d.ts +13 -0
  87. package/testing/schema.js +65 -0
  88. package/testing/source-loader.d.ts +19 -0
  89. package/testing/source-loader.js +238 -0
  90. package/testing/types.d.ts +79 -0
  91. package/testing/types.js +18 -0
  92. package/types.d.ts +302 -0
  93. package/types.js +13 -0
  94. package/validate.d.ts +78 -0
  95. package/validate.js +597 -0
package/README.md CHANGED
@@ -1,3 +1,173 @@
1
- # Temporary Holding Version
1
+ # @deepexi/datasense-connector
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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.