@devrev/airsync-sdk 2.0.0-beta.1 → 2.0.0-beta.11
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 +7 -21
- package/dist/attachments-streaming/attachments-streaming-pool.d.ts +6 -7
- package/dist/attachments-streaming/attachments-streaming-pool.d.ts.map +1 -1
- package/dist/attachments-streaming/attachments-streaming-pool.interfaces.d.ts +4 -3
- package/dist/attachments-streaming/attachments-streaming-pool.interfaces.d.ts.map +1 -1
- package/dist/attachments-streaming/attachments-streaming-pool.js +69 -61
- package/dist/common/constants.d.ts +15 -12
- package/dist/common/constants.d.ts.map +1 -1
- package/dist/common/constants.js +20 -34
- package/dist/common/helpers.d.ts +3 -27
- package/dist/common/helpers.d.ts.map +1 -1
- package/dist/common/helpers.js +6 -36
- package/dist/http/client.d.ts +4 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +80 -0
- package/dist/http/errors.d.ts +8 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/errors.js +31 -0
- package/dist/index.d.ts +30 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -28
- package/dist/logger/logger.d.ts +13 -70
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.interfaces.d.ts +4 -7
- package/dist/logger/logger.interfaces.d.ts.map +1 -1
- package/dist/logger/logger.js +67 -92
- package/dist/mappers/mappers.d.ts +10 -43
- package/dist/mappers/mappers.d.ts.map +1 -1
- package/dist/mappers/mappers.interfaces.d.ts +173 -0
- package/dist/mappers/mappers.interfaces.d.ts.map +1 -0
- package/dist/mappers/{mappers.interface.js → mappers.interfaces.js} +1 -10
- package/dist/mappers/mappers.js +32 -46
- package/dist/multithreading/adapters/base-adapter.d.ts +39 -0
- package/dist/multithreading/adapters/base-adapter.d.ts.map +1 -0
- package/dist/multithreading/adapters/base-adapter.js +212 -0
- package/dist/multithreading/adapters/extraction-adapter.d.ts +49 -0
- package/dist/multithreading/adapters/extraction-adapter.d.ts.map +1 -0
- package/dist/multithreading/adapters/extraction-adapter.js +375 -0
- package/dist/multithreading/adapters/loading-adapter.d.ts +26 -0
- package/dist/multithreading/adapters/loading-adapter.d.ts.map +1 -0
- package/dist/multithreading/adapters/loading-adapter.helpers.d.ts +12 -0
- package/dist/multithreading/adapters/loading-adapter.helpers.d.ts.map +1 -0
- package/dist/multithreading/{worker-adapter/worker-adapter.helpers.js → adapters/loading-adapter.helpers.js} +2 -12
- package/dist/multithreading/adapters/loading-adapter.js +279 -0
- package/dist/multithreading/emit.d.ts +11 -0
- package/dist/multithreading/emit.d.ts.map +1 -0
- package/dist/multithreading/emit.js +25 -0
- package/dist/multithreading/process-task.d.ts +6 -1
- package/dist/multithreading/process-task.d.ts.map +1 -1
- package/dist/multithreading/process-task.js +120 -82
- package/dist/multithreading/spawn/spawn.d.ts +10 -11
- package/dist/multithreading/spawn/spawn.d.ts.map +1 -1
- package/dist/multithreading/spawn/spawn.helpers.d.ts +12 -11
- package/dist/multithreading/spawn/spawn.helpers.d.ts.map +1 -1
- package/dist/multithreading/spawn/spawn.helpers.js +142 -77
- package/dist/multithreading/spawn/spawn.js +59 -75
- package/dist/multithreading/worker.js +16 -5
- package/dist/repo/repo.d.ts +14 -5
- package/dist/repo/repo.d.ts.map +1 -1
- package/dist/repo/repo.helpers.d.ts +6 -0
- package/dist/repo/repo.helpers.d.ts.map +1 -0
- package/dist/repo/repo.helpers.js +16 -0
- package/dist/repo/repo.interfaces.d.ts +11 -20
- package/dist/repo/repo.interfaces.d.ts.map +1 -1
- package/dist/repo/repo.js +36 -22
- package/dist/state/base-state.d.ts +58 -0
- package/dist/state/base-state.d.ts.map +1 -0
- package/dist/state/base-state.js +242 -0
- package/dist/state/extraction-state.d.ts +14 -0
- package/dist/state/extraction-state.d.ts.map +1 -0
- package/dist/state/extraction-state.js +146 -0
- package/dist/state/install-initial-domain-mapping.d.ts +4 -0
- package/dist/state/install-initial-domain-mapping.d.ts.map +1 -0
- package/dist/{common → state}/install-initial-domain-mapping.js +5 -7
- package/dist/state/loading-state.d.ts +8 -0
- package/dist/state/loading-state.d.ts.map +1 -0
- package/dist/state/loading-state.js +29 -0
- package/dist/state/state.d.ts +3 -29
- package/dist/state/state.d.ts.map +1 -1
- package/dist/state/state.interfaces.d.ts +31 -12
- package/dist/state/state.interfaces.d.ts.map +1 -1
- package/dist/state/state.interfaces.js +17 -1
- package/dist/state/state.js +9 -164
- package/dist/state/time-value-resolver.d.ts +17 -0
- package/dist/state/time-value-resolver.d.ts.map +1 -0
- package/dist/state/time-value-resolver.js +110 -0
- package/dist/testing/mock-event.d.ts +10 -0
- package/dist/testing/mock-event.d.ts.map +1 -0
- package/dist/testing/mock-event.js +96 -0
- package/dist/testing/mock-server.d.ts +31 -0
- package/dist/testing/mock-server.d.ts.map +1 -0
- package/dist/testing/mock-server.interfaces.d.ts +54 -0
- package/dist/testing/mock-server.interfaces.d.ts.map +1 -0
- package/dist/testing/mock-server.interfaces.js +4 -0
- package/dist/testing/mock-server.js +297 -0
- package/dist/testing/test-adapters.d.ts +22 -0
- package/dist/testing/test-adapters.d.ts.map +1 -0
- package/dist/testing/test-adapters.js +32 -0
- package/dist/types/common.d.ts +4 -37
- package/dist/types/common.d.ts.map +1 -1
- package/dist/types/common.js +2 -17
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +28 -0
- package/dist/types/external-domain-metadata.d.ts +40 -162
- package/dist/types/external-domain-metadata.d.ts.map +1 -1
- package/dist/types/extraction.d.ts +135 -278
- package/dist/types/extraction.d.ts.map +1 -1
- package/dist/types/extraction.js +46 -133
- package/dist/types/loading.d.ts +15 -55
- package/dist/types/loading.d.ts.map +1 -1
- package/dist/types/loading.js +1 -23
- package/dist/types/workers.d.ts +70 -103
- package/dist/types/workers.d.ts.map +1 -1
- package/dist/types/workers.js +1 -6
- package/dist/uploader/uploader.d.ts +10 -61
- package/dist/uploader/uploader.d.ts.map +1 -1
- package/dist/uploader/uploader.helpers.d.ts +7 -27
- package/dist/uploader/uploader.helpers.d.ts.map +1 -1
- package/dist/uploader/uploader.helpers.js +80 -30
- package/dist/uploader/uploader.interfaces.d.ts +19 -54
- package/dist/uploader/uploader.interfaces.d.ts.map +1 -1
- package/dist/uploader/uploader.interfaces.js +8 -0
- package/dist/uploader/uploader.js +72 -124
- package/docs/MIGRATION.md +751 -0
- package/package.json +35 -22
- package/dist/attachments-streaming/attachments-streaming-pool.test.d.ts +0 -2
- package/dist/attachments-streaming/attachments-streaming-pool.test.d.ts.map +0 -1
- package/dist/attachments-streaming/attachments-streaming-pool.test.js +0 -321
- package/dist/common/control-protocol.d.ts +0 -10
- package/dist/common/control-protocol.d.ts.map +0 -1
- package/dist/common/control-protocol.js +0 -31
- package/dist/common/errors.d.ts +0 -6
- package/dist/common/errors.d.ts.map +0 -1
- package/dist/common/errors.js +0 -4
- package/dist/common/event-type-translation.d.ts +0 -24
- package/dist/common/event-type-translation.d.ts.map +0 -1
- package/dist/common/event-type-translation.js +0 -117
- package/dist/common/install-initial-domain-mapping.d.ts +0 -4
- package/dist/common/install-initial-domain-mapping.d.ts.map +0 -1
- package/dist/common/install-initial-domain-mapping.test.d.ts +0 -2
- package/dist/common/install-initial-domain-mapping.test.d.ts.map +0 -1
- package/dist/common/install-initial-domain-mapping.test.js +0 -208
- package/dist/deprecated/adapter/index.d.ts +0 -62
- package/dist/deprecated/adapter/index.d.ts.map +0 -1
- package/dist/deprecated/adapter/index.js +0 -151
- package/dist/deprecated/common/helpers.d.ts +0 -7
- package/dist/deprecated/common/helpers.d.ts.map +0 -1
- package/dist/deprecated/common/helpers.js +0 -47
- package/dist/deprecated/demo-extractor/external_domain_metadata.json +0 -38
- package/dist/deprecated/demo-extractor/index.d.ts +0 -18
- package/dist/deprecated/demo-extractor/index.d.ts.map +0 -1
- package/dist/deprecated/demo-extractor/index.js +0 -161
- package/dist/deprecated/http/client.d.ts +0 -22
- package/dist/deprecated/http/client.d.ts.map +0 -1
- package/dist/deprecated/http/client.js +0 -161
- package/dist/deprecated/uploader/index.d.ts +0 -35
- package/dist/deprecated/uploader/index.d.ts.map +0 -1
- package/dist/deprecated/uploader/index.js +0 -161
- package/dist/http/axios-client-internal.d.ts +0 -3
- package/dist/http/axios-client-internal.d.ts.map +0 -1
- package/dist/http/axios-client-internal.js +0 -66
- package/dist/http/axios-client-internal.test.d.ts +0 -2
- package/dist/http/axios-client-internal.test.d.ts.map +0 -1
- package/dist/http/axios-client-internal.test.js +0 -154
- package/dist/http/axios-client.d.ts +0 -27
- package/dist/http/axios-client.d.ts.map +0 -1
- package/dist/http/axios-client.js +0 -57
- package/dist/http/constants.d.ts +0 -4
- package/dist/http/constants.d.ts.map +0 -1
- package/dist/http/constants.js +0 -6
- package/dist/http/index.d.ts +0 -3
- package/dist/http/index.d.ts.map +0 -1
- package/dist/http/index.js +0 -18
- package/dist/http/types.d.ts +0 -17
- package/dist/http/types.d.ts.map +0 -1
- package/dist/http/types.js +0 -2
- package/dist/logger/logger.constants.d.ts +0 -6
- package/dist/logger/logger.constants.d.ts.map +0 -1
- package/dist/logger/logger.constants.js +0 -13
- package/dist/logger/logger.context.d.ts +0 -58
- package/dist/logger/logger.context.d.ts.map +0 -1
- package/dist/logger/logger.context.js +0 -86
- package/dist/logger/logger.test.d.ts +0 -2
- package/dist/logger/logger.test.d.ts.map +0 -1
- package/dist/logger/logger.test.js +0 -490
- package/dist/mappers/mappers.interface.d.ts +0 -294
- package/dist/mappers/mappers.interface.d.ts.map +0 -1
- package/dist/mappers/mappers.test.d.ts +0 -2
- package/dist/mappers/mappers.test.d.ts.map +0 -1
- package/dist/mappers/mappers.test.js +0 -107
- package/dist/multithreading/create-worker.test.d.ts +0 -2
- package/dist/multithreading/create-worker.test.d.ts.map +0 -1
- package/dist/multithreading/create-worker.test.js +0 -89
- package/dist/multithreading/spawn/spawn.helpers.test.d.ts +0 -2
- package/dist/multithreading/spawn/spawn.helpers.test.d.ts.map +0 -1
- package/dist/multithreading/spawn/spawn.helpers.test.js +0 -293
- package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.d.ts +0 -2
- package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.d.ts.map +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.artifacts.test.js +0 -127
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts +0 -91
- package/dist/multithreading/worker-adapter/worker-adapter.d.ts.map +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.helpers.d.ts +0 -22
- package/dist/multithreading/worker-adapter/worker-adapter.helpers.d.ts.map +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.helpers.test.d.ts +0 -2
- package/dist/multithreading/worker-adapter/worker-adapter.helpers.test.d.ts.map +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.helpers.test.js +0 -514
- package/dist/multithreading/worker-adapter/worker-adapter.js +0 -745
- package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts +0 -2
- package/dist/multithreading/worker-adapter/worker-adapter.test.d.ts.map +0 -1
- package/dist/multithreading/worker-adapter/worker-adapter.test.js +0 -483
- package/dist/repo/repo.test.d.ts +0 -2
- package/dist/repo/repo.test.d.ts.map +0 -1
- package/dist/repo/repo.test.js +0 -131
- package/dist/state/state.test.d.ts +0 -2
- package/dist/state/state.test.d.ts.map +0 -1
- package/dist/state/state.test.js +0 -224
- package/dist/types/extraction.test.d.ts +0 -2
- package/dist/types/extraction.test.d.ts.map +0 -1
- package/dist/types/extraction.test.js +0 -70
- package/dist/types/index.d.ts +0 -10
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/uploader/uploader.helpers.test.d.ts +0 -2
- package/dist/uploader/uploader.helpers.test.d.ts.map +0 -1
- package/dist/uploader/uploader.helpers.test.js +0 -267
- package/dist/uploader/uploader.test.d.ts +0 -2
- package/dist/uploader/uploader.test.d.ts.map +0 -1
- package/dist/uploader/uploader.test.js +0 -589
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
# Migrating connectors from v1 (`@devrev/ts-adaas`) to v2 (`@devrev/airsync-sdk`)
|
|
2
|
+
|
|
3
|
+
This is the complete list of connector-facing changes from `@devrev/ts-adaas`
|
|
4
|
+
1.x to `@devrev/airsync-sdk` 2.x, written so an AI agent can apply it on its
|
|
5
|
+
own:
|
|
6
|
+
|
|
7
|
+
> Read `MIGRATION.md` from `@devrev/airsync-sdk` and migrate this connector.
|
|
8
|
+
|
|
9
|
+
It ships in the package at `node_modules/@devrev/airsync-sdk/docs/MIGRATION.md`
|
|
10
|
+
(read it from disk; the `exports` map only blocks `import`/`require`). Before
|
|
11
|
+
v2 is installed, use
|
|
12
|
+
`https://raw.githubusercontent.com/devrev/adaas-sdk/v2/docs/MIGRATION.md`.
|
|
13
|
+
|
|
14
|
+
**The wire protocol is unchanged.** Event payloads, routes, headers, the
|
|
15
|
+
artifact flow and every event-type and `ERROR_CODE=…` string are identical to
|
|
16
|
+
v1. Only the TypeScript API changed, all at once, with no compatibility shims.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Runbook
|
|
21
|
+
|
|
22
|
+
**Rules.** Work autonomously; when a rewrite is ambiguous, pick the option
|
|
23
|
+
that preserves runtime behavior and note it in `MIGRATION_REVIEW.md`. Migrate
|
|
24
|
+
source **and** tests. Never change a runtime string value. Don't add comments
|
|
25
|
+
about the migration; the `// v1` / `// v2` labels here are reading aids. Leave
|
|
26
|
+
no compile errors or orphaned imports. A section with no hits is a no-op.
|
|
27
|
+
Existing comments and commented-out code that describe v1 behavior (`emit`,
|
|
28
|
+
`{ id }` / `{ error }` returns, `.data.sync_mapper_record`) are rewritten to
|
|
29
|
+
v2; templates are copied into new connectors, so stale comments spread.
|
|
30
|
+
|
|
31
|
+
1. **Locate the package.** Find the `package.json` that depends on
|
|
32
|
+
`@devrev/ts-adaas` (generated connectors keep it in `code/`). Run everything
|
|
33
|
+
from there. If it already depends on `@devrev/airsync-sdk` only, report
|
|
34
|
+
"already on v2" and stop.
|
|
35
|
+
2. **Start from green latest v1.** If behind the latest stable 1.x, upgrade and
|
|
36
|
+
apply the release notes of every release in between that mentions a
|
|
37
|
+
migration (the last ones were 1.16.0–1.18.0), then get tsc/lint/build/test
|
|
38
|
+
green on v1. If the upgrade alone breaks tsc with axios type mismatches,
|
|
39
|
+
bump the connector's `axios` to the version that SDK release depends on.
|
|
40
|
+
Record failures that remain as **Pre-existing**.
|
|
41
|
+
3. **Install v2.**
|
|
42
|
+
```bash
|
|
43
|
+
npm uninstall @devrev/ts-adaas
|
|
44
|
+
npm install @devrev/airsync-sdk@beta # @latest after GA
|
|
45
|
+
```
|
|
46
|
+
Rename or remove `overrides` / `resolutions` entries keyed on
|
|
47
|
+
`@devrev/ts-adaas`; `npm uninstall` leaves them behind. If the installed
|
|
48
|
+
`docs/MIGRATION.md` differs from this copy, switch to it.
|
|
49
|
+
4. **Apply §1–§14 in order** across source and tests. After §1, search by
|
|
50
|
+
symbol (`\.emit\(`, `WorkerAdapter`, `sync_mapper_record`, …), not by the old
|
|
51
|
+
package name; test files that import nothing from the SDK still need
|
|
52
|
+
changes.
|
|
53
|
+
5. **Verify**: `npm install && npx tsc --noEmit && npm run lint && npm run build && npm test`.
|
|
54
|
+
If tsconfig excludes folders the connector still runs (`test/`, a local
|
|
55
|
+
runner), type-check them too: use the repo's `tsconfig.test.json` if it has
|
|
56
|
+
one, otherwise a temporary tsconfig that extends `tsconfig.json` and
|
|
57
|
+
overrides `"include": ["src", "test"]`, `"exclude": ["node_modules", "dist"]`,
|
|
58
|
+
`"rootDir": "."` and `"noEmit": true` (the base `exclude` and `rootDir` still
|
|
59
|
+
apply otherwise), then `npx tsc -p` it. Fix and rerun,
|
|
60
|
+
at most 4 attempts; the migration must add no new failures compared to
|
|
61
|
+
step 2.
|
|
62
|
+
6. **Sweep.** This must return nothing except legitimate hits (an
|
|
63
|
+
`EventEmitter.emit`, the connector's own `axiosClient` from §9,
|
|
64
|
+
`@devrev/typescript-sdk` in `package.json` from §1, `git config --local`,
|
|
65
|
+
legacy event names in fields the SDK doesn't read, such as
|
|
66
|
+
`event_type_adaas`):
|
|
67
|
+
```bash
|
|
68
|
+
grep -rnE "@devrev/ts-adaas|airsync-sdk/dist|\bprocessTask\b|\bWorkerAdapter\b|\.emit\(|hasWorkerEmitted|\bAirdrop(Event|Message)\b|axiosClient|(format|serialize)AxiosError|workerPath:|overridenOptions|skipConfirmation|isLocalDevelopment|--local|-- local\b|@devrev/typescript-sdk|(Extractor|Loader)EventType|ExtractionCommonError|ERROR_CODE=|sdkState|onUpload|uploadedArtifacts|currentEventDataLength|EXTRACTION_(DATA|METADATA|ATTACHMENTS|EXTERNAL_SYNC_UNITS)_" \
|
|
69
|
+
--include='*.ts' --include='*.js' --include='*.json' --include='*.yml' --include='*.yaml' --include='*.md' --include='*.mdc' --include='*.sh' . \
|
|
70
|
+
| grep -vE 'node_modules|/dist/|/coverage/|package-lock.json|MIGRATION_REVIEW.md'
|
|
71
|
+
```
|
|
72
|
+
The sweep covers `test/` and other folders that tsc, lint and jest often
|
|
73
|
+
exclude. A hit there is real: `@devrev/typescript-sdk` must be in
|
|
74
|
+
`package.json` (§1), and a `local` argv must become the env var (§3).
|
|
75
|
+
Also check `package.json` scripts and shell scripts for a bare `local`
|
|
76
|
+
argument (`npm run test:server -- local`, `main.ts local`).
|
|
77
|
+
Also grep `getByExternalId|getByTargetId` and check each call against §10:
|
|
78
|
+
existence checks there compile but silently invert.
|
|
79
|
+
Then rewrite any connector docs (`README`, `AGENTS.md`, `CLAUDE.md`, `docs/`)
|
|
80
|
+
that describe v1 behavior, including text that paraphrases DevRev docs with
|
|
81
|
+
legacy event names.
|
|
82
|
+
7. **Write `MIGRATION_REVIEW.md`** at the repo root: one line per section that
|
|
83
|
+
changed code with `high` / `medium` / `low confidence`, a sentence on what
|
|
84
|
+
to verify for medium/low, and the final check results with an
|
|
85
|
+
**Unresolved** list. Use medium/low for anything judged: emits bubbled out of
|
|
86
|
+
helpers (§4), paths that emitted nothing (§4), `onTimeout` bodies kept (§4),
|
|
87
|
+
delay values that look like milliseconds or headers (§4), the cursor decision
|
|
88
|
+
(§8), ESU repo rewrites (§7), mapper error handling (§10), and a v1 baseline
|
|
89
|
+
upgrade. Record the exact installed versions.
|
|
90
|
+
```markdown
|
|
91
|
+
# v2 Migration Review
|
|
92
|
+
Migrated `@devrev/ts-adaas` 1.20.2 → `@devrev/airsync-sdk` 2.0.0.
|
|
93
|
+
- §4 emit→return — high confidence
|
|
94
|
+
- §8 state / cursor — low confidence: repointed `lastSuccessfulSyncStarted`
|
|
95
|
+
to `extract_from`; confirm the connector didn't own this cursor.
|
|
96
|
+
## Verification
|
|
97
|
+
tsc ✓ · lint ✓ · build ✓ · test ✓ (48 passed)
|
|
98
|
+
```
|
|
99
|
+
8. **Report** the start and end versions, which sections fired, the check
|
|
100
|
+
results and how many review entries need a second look.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 1. Package, imports and runtime
|
|
105
|
+
|
|
106
|
+
- Replace `@devrev/ts-adaas` with `@devrev/airsync-sdk` **everywhere the
|
|
107
|
+
string appears**: imports, `jest.mock` / `jest.requireActual` arguments and
|
|
108
|
+
`moduleNameMapper`.
|
|
109
|
+
- **Import only from the root.** `exports` exposes nothing else, so every
|
|
110
|
+
`@devrev/airsync-sdk/dist/**` import fails with
|
|
111
|
+
`ERR_PACKAGE_PATH_NOT_EXPORTED`. Everything public is on the root, including
|
|
112
|
+
`Mappers`, `Item`, `ItemTypeToLoad`, `InitialSyncScope`, the mapper
|
|
113
|
+
`*Params` / `*Response` types and `SyncMapperRecord*`. Deep imports with no
|
|
114
|
+
root equivalent are listed in §11. Never import the SDK's internal HTTP client
|
|
115
|
+
(`dist/http/axios-client-internal`): use the connector's own client (§9).
|
|
116
|
+
- **Node.js ≥ 20.** Bump the connector's `engines`, CI image and `.nvmrc` if
|
|
117
|
+
they pin older. `@types/node` can stay as it is. Dependabot / CI configs that name `@devrev/ts-adaas` switch to
|
|
118
|
+
`@devrev/airsync-sdk`.
|
|
119
|
+
- `ts-node` and `@devrev/typescript-sdk` are no longer SDK dependencies. A
|
|
120
|
+
connector that runs `.ts` workers directly needs `ts-node` in its own
|
|
121
|
+
`devDependencies`. A connector that uses `@devrev/typescript-sdk` must list
|
|
122
|
+
it. The template's local test server (`code/test/runner.ts`, and the copy
|
|
123
|
+
`airsync start-worker` writes to `code/.snapin-test/`) imports
|
|
124
|
+
`@devrev/typescript-sdk/dist/snap-ins`, so template connectors add it to
|
|
125
|
+
`devDependencies` (`1.1.78`, the version the template pins). Most connectors exclude `test/` from tsc, lint and jest,
|
|
126
|
+
so the gates pass and the local worker then fails with
|
|
127
|
+
`Cannot find module '@devrev/typescript-sdk/dist/snap-ins'`.
|
|
128
|
+
`InputData` (the type of `event.input_data`) is now exported from the
|
|
129
|
+
root.
|
|
130
|
+
|
|
131
|
+
## 2. Event types
|
|
132
|
+
|
|
133
|
+
| v1 | v2 |
|
|
134
|
+
|----|----|
|
|
135
|
+
| `AirdropEvent` | `AirSyncEvent` |
|
|
136
|
+
| `AirdropMessage` | `AirSyncMessage` |
|
|
137
|
+
|
|
138
|
+
The shapes are unchanged. `AirSyncEvent.context` now also declares `user_id`,
|
|
139
|
+
`dev_oid`, `source_id` and `service_account_id` (on `event.context`, not on
|
|
140
|
+
`payload.event_context`). Delete a hand-rolled context type that added these
|
|
141
|
+
(e.g. `interface CustomAirdropEvent extends AirdropEvent { context: CustomContext }`):
|
|
142
|
+
a narrower `context` no longer satisfies `AirSyncEvent`. Keep only the fields
|
|
143
|
+
the SDK type lacks. These fields are required, so typed mock events must set
|
|
144
|
+
them. `execution_metadata` is typed `{ devrev_endpoint }`; a custom type that
|
|
145
|
+
widened it with `@devrev/typescript-sdk` fields goes too, along with mock values
|
|
146
|
+
for fields nothing reads.
|
|
147
|
+
|
|
148
|
+
Deprecated `EventContext` aliases are removed. Use the current names:
|
|
149
|
+
|
|
150
|
+
| Removed | Use |
|
|
151
|
+
|---------|-----|
|
|
152
|
+
| `dev_org` | `dev_oid` |
|
|
153
|
+
| `dev_user`, `dev_user_id` | `dev_uid` |
|
|
154
|
+
| `external_sync_unit` | `external_sync_unit_id` |
|
|
155
|
+
| `external_system` | `external_system_id` |
|
|
156
|
+
| `sync_run`, `sync_run_id` | `run_id` |
|
|
157
|
+
| `uuid` | `request_id_adaas` |
|
|
158
|
+
|
|
159
|
+
Rename these keys in fixture and test `event_context` objects too. When an
|
|
160
|
+
object already has the new key (or has two old keys for one new one, like
|
|
161
|
+
`dev_user` and `dev_user_id`), keep the existing new-name value, otherwise the
|
|
162
|
+
first old one, and drop the rest. Untyped
|
|
163
|
+
JSON still compiles, but the SDK reads only the current names (e.g. state
|
|
164
|
+
requests carry `request_id_adaas`).
|
|
165
|
+
|
|
166
|
+
`reset_extraction` and `reset_extract_from` remain (the SDK never read them);
|
|
167
|
+
leave connector logic that uses them alone.
|
|
168
|
+
|
|
169
|
+
## 3. `spawn`
|
|
170
|
+
|
|
171
|
+
| v1 | v2 |
|
|
172
|
+
|----|----|
|
|
173
|
+
| `workerPath` | removed; use `baseWorkerPath` |
|
|
174
|
+
| `baseWorkerPath?` | **required**, the directory containing `workers/` (usually `__dirname`) |
|
|
175
|
+
| `options.workerPathOverrides` | top-level `workerPathOverrides` |
|
|
176
|
+
| `options: WorkerAdapterOptions` | `options?: SpawnOptions` (`{ timeout?, batchSize? }`) |
|
|
177
|
+
| `options.isLocalDevelopment`, `local` / `--local` argv | removed; set `AIRSYNC_LOCAL_DEVELOPMENT=1` |
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// v1
|
|
181
|
+
spawn({
|
|
182
|
+
event,
|
|
183
|
+
initialState,
|
|
184
|
+
workerPath: __dirname + '/workers/data-extraction',
|
|
185
|
+
options: { isLocalDevelopment: true, workerPathOverrides: { [EventType.StartExtractingData]: '/workers/custom' } },
|
|
186
|
+
});
|
|
187
|
+
// v2
|
|
188
|
+
spawn({
|
|
189
|
+
event,
|
|
190
|
+
initialState,
|
|
191
|
+
baseWorkerPath: __dirname,
|
|
192
|
+
workerPathOverrides: { [EventType.StartExtractingData]: '/workers/custom' },
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- If `workerPath` came from a dispatcher helper (e.g.
|
|
197
|
+
`getWorkerPerLoadingPhase(event)`), delete the helper and its now-unused
|
|
198
|
+
imports.
|
|
199
|
+
- **Fixture and test events** must use the modern event-type strings; v1
|
|
200
|
+
translated legacy ones and v2 doesn't. Replace `EXTRACTION_EXTERNAL_SYNC_UNITS_START`,
|
|
201
|
+
`EXTRACTION_METADATA_START`, `EXTRACTION_DATA_START` / `_CONTINUE` / `_DELETE`
|
|
202
|
+
and `EXTRACTION_ATTACHMENTS_START` / `_CONTINUE` / `_DELETE` with
|
|
203
|
+
`START_EXTRACTING_EXTERNAL_SYNC_UNITS`, `START_EXTRACTING_METADATA`,
|
|
204
|
+
`START_EXTRACTING_DATA` / `CONTINUE_EXTRACTING_DATA` /
|
|
205
|
+
`START_DELETING_EXTRACTOR_STATE` and `START_EXTRACTING_ATTACHMENTS` /
|
|
206
|
+
`CONTINUE_EXTRACTING_ATTACHMENTS` / `START_DELETING_EXTRACTOR_ATTACHMENTS_STATE`.
|
|
207
|
+
- **Local mode** comes only from the `AIRSYNC_LOCAL_DEVELOPMENT=1` environment
|
|
208
|
+
variable, and is ignored on Lambda. v1 also enabled it from a bare `local`
|
|
209
|
+
argument, which the template test server receives (`main.ts local --port …`,
|
|
210
|
+
also used by `airsync start-worker`). v2 ignores both `local` and `--local`
|
|
211
|
+
without an error: logs come out as production JSON and no copies land in
|
|
212
|
+
`extracted_files/`. A local runner that did `process.argv.push('--local')`
|
|
213
|
+
or relied on the `local` argument sets
|
|
214
|
+
`process.env.AIRSYNC_LOCAL_DEVELOPMENT = '1'` before calling `spawn` instead,
|
|
215
|
+
or documents running the worker with `AIRSYNC_LOCAL_DEVELOPMENT=1`.
|
|
216
|
+
|
|
217
|
+
## 4. Workers return a `TaskResult` instead of emitting
|
|
218
|
+
|
|
219
|
+
`adapter.emit` is gone (a compile error). A task reports how the phase ended by
|
|
220
|
+
returning a result; the SDK picks the platform event and emits it exactly once.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
export type TaskResult =
|
|
224
|
+
| { status: 'success' }
|
|
225
|
+
| { status: 'progress' }
|
|
226
|
+
| { status: 'delay'; delaySeconds: number }
|
|
227
|
+
| { status: 'error'; error: ErrorRecord }; // { message: string; code?: AirSyncErrorCode }
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Replace `processTask` with `processExtractionTask` or `processLoadingTask`,
|
|
231
|
+
depending on the worker (extraction workers use repos or `streamAttachments`;
|
|
232
|
+
loading workers use `loadItemTypes` or `loadAttachments`). Their
|
|
233
|
+
generic stays the connector state: `processTask<State>` →
|
|
234
|
+
`processExtractionTask<State>`.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
// v1
|
|
238
|
+
processTask({
|
|
239
|
+
task: async ({ adapter }) => {
|
|
240
|
+
// ...
|
|
241
|
+
await adapter.emit(ExtractorEventType.DataExtractionDone);
|
|
242
|
+
},
|
|
243
|
+
onTimeout: async ({ adapter }) => {
|
|
244
|
+
await adapter.postState();
|
|
245
|
+
await adapter.emit(ExtractorEventType.DataExtractionProgress);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
// v2
|
|
249
|
+
processExtractionTask({
|
|
250
|
+
task: async ({ adapter }) => {
|
|
251
|
+
// ...
|
|
252
|
+
return { status: 'success' };
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
| v1 | v2 |
|
|
258
|
+
|----|----|
|
|
259
|
+
| `emit(XxxDone)` | `return { status: 'success' }` |
|
|
260
|
+
| `emit(XxxProgress, { progress })` | `return { status: 'progress' }` |
|
|
261
|
+
| `emit(XxxDelayed, { delay })` | `return { status: 'delay', delaySeconds: delay }` — keep the same number; the platform always read it as seconds. A raw `Retry-After` header stays `Number(header)`. If it is clearly milliseconds (a `*_MS` constant, a value in the thousands, "ms" in the message), keep the number, flag it and list it under **Unresolved**: v1 waited that many seconds too, and fixing it changes behavior |
|
|
262
|
+
| `emit(XxxDelayed)` with no `delay` | `delaySeconds` is required; use the connector's existing default retry constant, else `0` (what v1 effectively sent), and flag it |
|
|
263
|
+
| `emit(XxxError, { error })` | `return { status: 'error', error }` — `error` is required; fall back to `{ message: '<phase> failed' }` |
|
|
264
|
+
| `emit(XxxDone, { error })` (finished with warnings) | `return { status: 'success' }` and log the error. Returning `error` would fail the phase |
|
|
265
|
+
| `emit(ExternalSyncUnitExtractionDone, { external_sync_units })` | push to a repo, then `success` (§7) |
|
|
266
|
+
| `emit(MetadataExtractionDone, { pre_extraction_item_counts })` | `adapter.preExtractionItemCounts = counts`, then `success` |
|
|
267
|
+
| a path that **emitted nothing** (a `catch` that only logs, a branch that falls through) | `{ status: 'error', error: { message } }`, or rethrow. In v1 the SDK reported these as a phase error ("Worker exited without emitting"); never turn them into `success` or `progress` |
|
|
268
|
+
| any `emit(XxxDone)` after `loadItemTypes` / `loadAttachments` / `streamAttachments`, guarded (`!adapter.hasWorkerEmitted && !adapter.isTimeout`) or not | `return adapter.loadItemTypes(...)` (or the other call). Never discard their result and return `success`: v1 emitted their `delay` / `error` itself. Drop the guard and any module-level "already emitted" flags. If v1 did more work after the call and then emitted `Done` with an `error`, return the call's result and log that error |
|
|
269
|
+
|
|
270
|
+
In non-resumable phases (external sync units, state deletion), `progress` and
|
|
271
|
+
`delay` become `*Error`.
|
|
272
|
+
|
|
273
|
+
**Emits inside helpers.** Only the task's return value reaches the SDK, so a
|
|
274
|
+
helper that emitted must hand its result back up. Either return
|
|
275
|
+
`TaskResult | null` up the call chain (`null` = keep going), or record it and
|
|
276
|
+
return it once the loop ends:
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
private result: TaskResult | undefined;
|
|
280
|
+
private onPermissionDenied() {
|
|
281
|
+
this.result = { status: 'error', error: { message: '...' } };
|
|
282
|
+
}
|
|
283
|
+
// at the end of the task: a recorded result, else continue only on timeout
|
|
284
|
+
return this.result ?? (adapter.isTimeout
|
|
285
|
+
? { status: 'progress' }
|
|
286
|
+
: { status: 'error', error: { message: 'Extraction ended without a result' } });
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
If a shared `handleError` emitted from many `catch` blocks that rethrow, v1
|
|
290
|
+
kept only the first emit. Record the first result (`firstError ??= …`) and
|
|
291
|
+
return it from the task's outermost `catch`.
|
|
292
|
+
|
|
293
|
+
If a helper **emitted and then kept going** (no throw), the caller often
|
|
294
|
+
emitted again later, e.g. `DataExtractionDone` after the loop, and v1 dropped
|
|
295
|
+
every emit after the first. Record the first result the same way, stop the
|
|
296
|
+
work that followed it (return out of the loop or extractor), and return it
|
|
297
|
+
from the task. Unawaited emits inside loops count too. When the emitting helper
|
|
298
|
+
is deep in the call stack, throw an error class that carries the `TaskResult`
|
|
299
|
+
instead (`class TaskResultError extends Error { constructor(readonly result: TaskResult) { super(); } }`),
|
|
300
|
+
stop the extractor, and return `e.result` from the task's outermost `catch`.
|
|
301
|
+
A free-function handler that must keep only the first result stores it per
|
|
302
|
+
adapter (`WeakMap<adapter, TaskResult>`) and the task reads it back.
|
|
303
|
+
|
|
304
|
+
A helper that returned `{ delay?, error? }` and emitted only when one was set
|
|
305
|
+
needs an explicit result when neither is set: `success` if the caller carried
|
|
306
|
+
on, otherwise `error`.
|
|
307
|
+
|
|
308
|
+
If a helper (often the HTTP client's 429 handler) **emitted and then threw a
|
|
309
|
+
sentinel error** that call sites rethrow, keep the throw, drop the emit, and map
|
|
310
|
+
it in the task's outermost `catch`:
|
|
311
|
+
`if (e instanceof RateLimitDelayedError) return { status: 'delay', delaySeconds: e.retryAfterSeconds };`.
|
|
312
|
+
Don't let it reach a generic error branch, or every rate limit fails the sync.
|
|
313
|
+
In external sync units (non-resumable) the SDK still sends `delay` as an
|
|
314
|
+
error; that matches nothing better, so keep it and flag it in the review.
|
|
315
|
+
|
|
316
|
+
Tests of helpers that now record a result read it back
|
|
317
|
+
(`expect(extractor['result']).toEqual(…)`) or, for `TaskResult | null`
|
|
318
|
+
helpers, assert on the return value.
|
|
319
|
+
|
|
320
|
+
**Timeouts.**
|
|
321
|
+
|
|
322
|
+
- In the **task body**, `if (adapter.isTimeout) return;` becomes
|
|
323
|
+
`return { status: 'progress' };`. The same applies to a `return;` in a
|
|
324
|
+
`catch` that handles a timeout (`if (adapter.isTimeout || e instanceof
|
|
325
|
+
MyTimeout) return;`). Early `return`s inside `Promise<void>` helpers stay as
|
|
326
|
+
they are, as long as the task checks `isTimeout` after calling them.
|
|
327
|
+
- After the soft timeout, a returned `progress` is replaced by the
|
|
328
|
+
`onTimeout` result, and so is a returned `success` in resumable phases
|
|
329
|
+
(metadata, data, attachments, loading). A returned `error` or `delay`, and
|
|
330
|
+
a `success` in external sync units or state deletion, are emitted as is.
|
|
331
|
+
- `onTimeout` is optional and defaults to `progress` (or a timeout error in
|
|
332
|
+
external sync units and state deletion). v1 ran `onTimeout` only when the
|
|
333
|
+
task finished **without emitting**; v2 also runs it when a resumable task
|
|
334
|
+
returns `success` after the timeout, which costs one extra `PROGRESS` →
|
|
335
|
+
`CONTINUE` round instead of `DONE`. So:
|
|
336
|
+
- **Delete** it when it only emitted the usual event or computed a
|
|
337
|
+
`progress` number, when the v1 task emitted on every path, or when the task
|
|
338
|
+
only returns `loadItemTypes` / `loadAttachments` / `streamAttachments`
|
|
339
|
+
(they handled the timeout themselves).
|
|
340
|
+
- **Keep** it, returning a `TaskResult`, only when it could actually run in
|
|
341
|
+
v1 (the task had a path that didn't emit) **and** it did real work or set a
|
|
342
|
+
custom error. Make its body safe to run after a completed task. A custom
|
|
343
|
+
"Lambda timeout" `onTimeout` on a task that emits on every path is
|
|
344
|
+
deleted; v1 never ran it.
|
|
345
|
+
- `onTimeout` runs only after the task has returned, in v1 and v2, so
|
|
346
|
+
cleanup there (cancelling a rate limiter, aborting requests) never
|
|
347
|
+
interrupted a stuck task. Move it into the task's `isTimeout` /
|
|
348
|
+
`timeoutSignal` handling if it matters, otherwise delete it.
|
|
349
|
+
- Don't call `process.exit()` or `postState()` in `onTimeout`; the SDK saves
|
|
350
|
+
state and exits.
|
|
351
|
+
|
|
352
|
+
**Throws.** A task that throws emits the phase's `*Error` with the message,
|
|
353
|
+
along with the artifacts already uploaded. `repo.push()` and `repo.upload()`
|
|
354
|
+
resolve `void` and throw if an upload fails. **Any use of `push()`'s value is
|
|
355
|
+
now wrong**, and `Boolean(await repo.push(x))` or `if (!(await …push(x)))`
|
|
356
|
+
compiles but is always falsy. Make wrappers `await repo.push(x)` and return
|
|
357
|
+
`true` (or `void`). Keep a top-level `try/catch` that maps an error to `delay`
|
|
358
|
+
(e.g. a thrown 429); otherwise it's only needed for a custom message.
|
|
359
|
+
`loadItemTypes` and `loadAttachments` don't throw: a `create` / `update` that
|
|
360
|
+
throws becomes their `error` result, so a 429 must be returned as
|
|
361
|
+
`{ status: 'delay', delaySeconds }` from the callback (§6). A `catch` around
|
|
362
|
+
them that checks `isAxiosError(e) && status === 429` is dead; delete it.
|
|
363
|
+
|
|
364
|
+
`ProcessTaskInterface<T>` and `TaskAdapterInterface<T>` now take the
|
|
365
|
+
**adapter** type: `ProcessTaskInterface<ExtractionAdapter<State>>`, not
|
|
366
|
+
`<State>`.
|
|
367
|
+
|
|
368
|
+
## 5. Adapters
|
|
369
|
+
|
|
370
|
+
The `WorkerAdapter` class is gone. Annotate with the phase's adapter:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
async function extractList(adapter: ExtractionAdapter<State>) { ... }
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
| Members | On |
|
|
377
|
+
|---------|----|
|
|
378
|
+
| `initializeRepos`, `getRepo`, `streamAttachments`, `processAttachment`, `shouldExtract`, `artifacts`, `preExtractionItemCounts` | `ExtractionAdapter` |
|
|
379
|
+
| `loadItemTypes`, `loadAttachments`, `reports`, `processedFiles` | `LoadingAdapter` |
|
|
380
|
+
| `event`, `state`, `mappers`, `postState`, `uploadArtifact`, `downloadArtifact`, `isTimeout`, `extractionScope` | both |
|
|
381
|
+
|
|
382
|
+
- The adapters and `Repo` are **type-only**: `new ExtractionAdapter(...)` and
|
|
383
|
+
`class X extends LoadingAdapter<S>` or `extends Repo` (test doubles that
|
|
384
|
+
subclassed `Repo` through `dist/`) don't compile; use a plain object with
|
|
385
|
+
the methods the code calls. `interface X extends …`
|
|
386
|
+
does. SDK internals (`emit`, `sdkState`, `options`, constructors, …) aren't
|
|
387
|
+
public. `isTimeout` and `artifacts` are read-only.
|
|
388
|
+
- `adapter.mappers` works in extraction as in v1; keep those calls and only
|
|
389
|
+
apply the §10 return-shape changes.
|
|
390
|
+
- **Integration tests** that built an adapter by hand use the root factories:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
// v1
|
|
394
|
+
const adapter = new WorkerAdapter({ event, adapterState: new State({ event, initialState }) });
|
|
395
|
+
adapterState.state.fromDevRev = { filesToLoad };
|
|
396
|
+
// v2
|
|
397
|
+
const adapter = createTestExtractionAdapter({ event, initialState });
|
|
398
|
+
const loader = createTestLoadingAdapter({ event, initialState, filesToLoad });
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Platform calls still go to the event's URLs. Build the event with
|
|
402
|
+
`createMockEvent(mockServer.baseUrl, …)` and serve the URLs with
|
|
403
|
+
`MockServer`. Artifact downloads need two routes: `GET
|
|
404
|
+
/internal/airdrop.artifacts.download-url` returning `{ download_url }`, and
|
|
405
|
+
that URL serving the gzipped JSONL. Assert on the returned `TaskResult`,
|
|
406
|
+
`adapter.reports` and `adapter.processedFiles`, not on `emit` or SDK state;
|
|
407
|
+
to test a resume, build a second adapter with `filesToLoad` at the cursor you
|
|
408
|
+
expect. To simulate a timeout, use a plain mock object, not a real adapter.
|
|
409
|
+
Test repos are plain objects (`{ itemType, push: jest.fn() }`).
|
|
410
|
+
- `adapter.postState()` takes no argument and **throws** on failure (v1 exited
|
|
411
|
+
the process). State is saved on every emit, so keep a call only to save
|
|
412
|
+
state before an external write that must not be repeated. v1
|
|
413
|
+
`postState(next)` becomes `adapter.state = next; await adapter.postState();`.
|
|
414
|
+
|
|
415
|
+
## 6. Loading, attachments and callback results
|
|
416
|
+
|
|
417
|
+
`loadItemTypes`, `loadAttachments` and `streamAttachments` return a
|
|
418
|
+
`TaskResult`. Return it directly:
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
processLoadingTask({ task: async ({ adapter }) => adapter.loadItemTypes({ itemTypesToLoad }) });
|
|
422
|
+
processExtractionTask({ task: async ({ adapter }) => adapter.streamAttachments({ stream }) });
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The result has no `reports` / `processed_files`. To add a report, push it to
|
|
426
|
+
`adapter.reports` before returning. Tests assert on `adapter.reports`.
|
|
427
|
+
|
|
428
|
+
**Connector callbacks** return a `status` union, like `TaskResult`:
|
|
429
|
+
|
|
430
|
+
| Callback | v1 | v2 |
|
|
431
|
+
|----------|----|----|
|
|
432
|
+
| attachment `stream` | `{ httpStream }` / `{ delay }` / `{ error: { message, statusCode? } }` | `{ status: 'success', httpStream }` / `{ status: 'delay', delaySeconds }` / `{ status: 'error', error: { message } }` |
|
|
433
|
+
| loader `create` / `update` | `{ id, modifiedDate? }` / `{ delay }` / `{ error: 'msg' \| { message } }` / neither (counted as failed) | `{ status: 'success', id, modifiedDate? }` / `{ status: 'delay', delaySeconds }` / `{ status: 'error', error: { message } }` |
|
|
434
|
+
| `adapter.processAttachment`, custom `iterator` | `{ delay? , error? } \| undefined` | `{ status: 'success' }` / `{ status: 'delay', delaySeconds }` / `{ status: 'error', error }` |
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
// custom iterator, v2
|
|
438
|
+
const response = await adapter.processAttachment(attachment, stream);
|
|
439
|
+
if (response.status === 'delay') return response;
|
|
440
|
+
if (response.status === 'error') console.warn(response.error.message);
|
|
441
|
+
// ...
|
|
442
|
+
return { status: 'success' }; // was a bare `return;`
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
- Loader `error` results still count as failed and loading continues. A v1
|
|
446
|
+
result with **both** `delay` and `error` becomes `delay` (v1 honored the delay).
|
|
447
|
+
- `success` needs a real `id`: a v1 `{ id }` whose value may be `undefined`
|
|
448
|
+
(from an untyped response) was counted as failed, so guard it:
|
|
449
|
+
`if (!id) return { status: 'error', error: { message } }`. Extra fields such
|
|
450
|
+
as `skip` are dropped; every `success` creates or updates a mapper record.
|
|
451
|
+
- Shared helpers typed as these responses change too. Callers that read
|
|
452
|
+
`result.delay` or interpolate `${result.error}` (now an object) switch on
|
|
453
|
+
`result.status`. A helper that only builds delays and errors returns the
|
|
454
|
+
narrowed type (`Extract<…, { status: 'delay' | 'error' }>`), so callers and
|
|
455
|
+
tests can read `delaySeconds` and `error` without a check.
|
|
456
|
+
- An error `code` returned from the stream callback is only logged; the pool
|
|
457
|
+
skips that attachment.
|
|
458
|
+
- `httpStream` is typed `HttpStreamResponse` (`{ data, headers }`); an axios
|
|
459
|
+
response still satisfies it. Types derived by indexing the old interface
|
|
460
|
+
(`ExternalSystemAttachmentStreamingResponse['httpStream']`) use the root
|
|
461
|
+
`HttpStreamResponse` instead.
|
|
462
|
+
- `statusCode` on stream errors is gone; put it in the message if you need it.
|
|
463
|
+
|
|
464
|
+
**`processAttachment` works in any extraction phase.** SSOR artifacts created
|
|
465
|
+
outside the attachments phase are held by the SDK and reported with
|
|
466
|
+
`ATTACHMENT_EXTRACTION_DONE`. **Delete any workaround** for this, along with
|
|
467
|
+
its state fields and tests. Workarounds typically override the
|
|
468
|
+
`ssor_attachment` repo's `onUpload`, splice `uploadedArtifacts`, adjust
|
|
469
|
+
`currentEventDataLength`, or stash artifact refs in state.
|
|
470
|
+
|
|
471
|
+
- A worker whose only job was re-injecting those refs returns
|
|
472
|
+
`{ status: 'success' }` (or its remaining work); keep its phase wired.
|
|
473
|
+
- A connector's own `{ itemType: 'ssor_attachment' }` repo entry can stay.
|
|
474
|
+
- Tests that mocked the deep `Uploader` use adapter doubles instead:
|
|
475
|
+
`{ uploadArtifact: jest.fn(), downloadArtifact: jest.fn() }`.
|
|
476
|
+
- If the workaround stashed refs in connector state, syncs between data done and
|
|
477
|
+
attachments done lose them after the upgrade; drain them first.
|
|
478
|
+
|
|
479
|
+
## 7. External sync units go through a repo
|
|
480
|
+
|
|
481
|
+
`EventData.external_sync_units` is removed. Push them to a repo:
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
adapter.initializeRepos([
|
|
485
|
+
// matches the batching v1 used for the inline path
|
|
486
|
+
{ itemType: AirSyncDefaultItemTypes.EXTERNAL_SYNC_UNITS, overriddenOptions: { batchSize: 25000 } },
|
|
487
|
+
]);
|
|
488
|
+
await adapter.getRepo(AirSyncDefaultItemTypes.EXTERNAL_SYNC_UNITS)?.push(externalSyncUnits);
|
|
489
|
+
return { status: 'success' };
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
If the connector already pushed ESUs to a repo, keep its options as they are
|
|
493
|
+
(apart from the §14 rename and removing `skipConfirmation`) and only convert
|
|
494
|
+
the emit.
|
|
495
|
+
|
|
496
|
+
## 8. State: connector state only
|
|
497
|
+
|
|
498
|
+
- `adapter.state` is the connector's own state (the `initialState` shape);
|
|
499
|
+
reads and writes work as before. SDK bookkeeping (`toDevRev`, `fromDevRev`,
|
|
500
|
+
`workers*`, `pendingWorkers*`, `lastSyncStarted`,
|
|
501
|
+
`lastSuccessfulSyncStarted`, `snapInVersionId`) is internal. The
|
|
502
|
+
`AdapterState<S>` alias is removed; use your own `State` type.
|
|
503
|
+
- **Persisted state** becomes `{ stateVersion: 2, connectorState, sdkState }`.
|
|
504
|
+
A v1 blob is migrated automatically on first read: known SDK keys move to
|
|
505
|
+
`sdkState` and everything else becomes connector state. **Rolling back to v1
|
|
506
|
+
after a v2 worker has run is not supported**; drain or reset syncs first. A
|
|
507
|
+
v1 state with a top-level key named `connectorState` or `sdkState` is
|
|
508
|
+
misread; rename it before upgrading.
|
|
509
|
+
- **Sync window**: read
|
|
510
|
+
`adapter.event.payload.event_context.extract_from` / `extract_to`. The SDK
|
|
511
|
+
resolves them from the platform's time values, or from the last successful
|
|
512
|
+
sync on platforms that don't send them.
|
|
513
|
+
- Remove SDK fields (`toDevRev`, `fromDevRev`, …) from the connector's own
|
|
514
|
+
`State` interface, `getInitialState` and fixtures.
|
|
515
|
+
|
|
516
|
+
**`lastSyncStarted` / `lastSuccessfulSyncStarted`: rename or repoint.**
|
|
517
|
+
Getting this wrong compiles but silently breaks incremental sync.
|
|
518
|
+
|
|
519
|
+
These rules cover **top-level** state keys only; the v1 migration moves only
|
|
520
|
+
those. A cursor nested in the connector's own object (`state.extractor.lastSuccessfulSyncStarted`)
|
|
521
|
+
is connector-owned: leave it alone.
|
|
522
|
+
|
|
523
|
+
- **Not in the connector's `State` interface**, or declared but never written
|
|
524
|
+
by the connector (it came from the SDK): delete writes, and repoint reads to
|
|
525
|
+
`extract_from`.
|
|
526
|
+
- **Declared and used as the connector's own cursor** (derived from data, or
|
|
527
|
+
read alongside `extract_from`): **rename** it (e.g. →
|
|
528
|
+
`lastSuccessfulWindowStart`) everywhere, including fixtures. Otherwise the v1
|
|
529
|
+
auto-migration moves it into SDK state and the connector loses it.
|
|
530
|
+
- **Declared but only mirrors the SDK**: its only writes are "now" when data
|
|
531
|
+
extraction starts and a copy on finish. Treat it as not declared, **even if
|
|
532
|
+
it is read next to `extract_from`**. Renaming would leave it empty after the
|
|
533
|
+
upgrade, because the v1 value moves into SDK state.
|
|
534
|
+
|
|
535
|
+
After repointing, branches that chose between the two
|
|
536
|
+
(`reset_extract_from ? extract_from : lastSuccessfulSyncStarted`) read the same
|
|
537
|
+
value; collapse them and drop the `reset_extract_from` read. If the connector
|
|
538
|
+
already used `extract_from` for something else (e.g. a skip filter) and the
|
|
539
|
+
cursor for a second window (e.g. "re-emit and bump the floor"), repointing
|
|
540
|
+
merges the two, so code below the window may become dead. Flag that low
|
|
541
|
+
confidence.
|
|
542
|
+
|
|
543
|
+
`extract_from` equals the last successful sync start only when the platform
|
|
544
|
+
sends no time window. If a repointed read feeds logic beyond "skip items older
|
|
545
|
+
than this" (e.g. distinguishing first syncs), flag it as low confidence; there
|
|
546
|
+
is no public "last successful sync" value. Fixtures move from
|
|
547
|
+
`adapter.state.lastSuccessfulSyncStarted = X` to
|
|
548
|
+
`event.payload.event_context.extract_from = X`.
|
|
549
|
+
|
|
550
|
+
## 9. HTTP: bring your own axios client
|
|
551
|
+
|
|
552
|
+
`axios`, `axiosClient`, `formatAxiosError`, `serializeAxiosError` and
|
|
553
|
+
`HTTPResponse` are no longer exported. Add `axios` (and `axios-retry` for a
|
|
554
|
+
retrying client) to the connector's dependencies.
|
|
555
|
+
|
|
556
|
+
- **`axios`** (v1 re-exported plain axios, without retries) →
|
|
557
|
+
`import axios from 'axios'`. Calls stay as they are; don't route them through
|
|
558
|
+
a retrying client.
|
|
559
|
+
- **`axiosClient`** → the connector's own client, in its own module (e.g.
|
|
560
|
+
`src/http/axios-client.ts`), so tests can mock that module:
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
import axios from 'axios';
|
|
564
|
+
import axiosRetry from 'axios-retry';
|
|
565
|
+
|
|
566
|
+
export const axiosClient = axios.create();
|
|
567
|
+
axiosRetry(axiosClient as Parameters<typeof axiosRetry>[0], {
|
|
568
|
+
retries: 5,
|
|
569
|
+
retryDelay: (retryCount, error) => axiosRetry.exponentialDelay(retryCount, error, 1000),
|
|
570
|
+
retryCondition: (error) =>
|
|
571
|
+
(axiosRetry.isNetworkOrIdempotentRequestError(error) && error.response?.status !== 429) ||
|
|
572
|
+
(error.response?.status ?? 0) >= 500,
|
|
573
|
+
onMaxRetryTimesExceeded: (error) => {
|
|
574
|
+
delete error.config?.headers?.authorization;
|
|
575
|
+
delete error.config?.headers?.Authorization;
|
|
576
|
+
delete error.request?._header;
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
The cast is needed under ts-jest, which can resolve axios's `.d.ts` and
|
|
582
|
+
`.d.cts` to different types.
|
|
583
|
+
|
|
584
|
+
A deep import of the SDK's internal client (`dist/http/axios-client-internal`)
|
|
585
|
+
relied on more: a 30 s timeout, retries of 429s that send `Retry-After` (waiting
|
|
586
|
+
that long) and of `ECONNABORTED`. Reproduce what the connector depends on with
|
|
587
|
+
`axios.create({ timeout: 30_000 })`, `shouldResetTimeout: true`, and a
|
|
588
|
+
`retryCondition` / `retryDelay` that honor `Retry-After`.
|
|
589
|
+
|
|
590
|
+
For logging, use the root `serializeError(error)`, which returns a string.
|
|
591
|
+
For an axios error it is JSON of the request method, URL and params plus the
|
|
592
|
+
response status and body, without headers. Interpolating `serializeAxiosError`
|
|
593
|
+
printed `[object Object]` in v1, so such messages now carry these details.
|
|
594
|
+
Where `serializeAxiosError(e)` was used as an object (spread, or `.response`),
|
|
595
|
+
build the fields you need from the axios error yourself, leaving out headers.
|
|
596
|
+
|
|
597
|
+
## 10. Mappers
|
|
598
|
+
|
|
599
|
+
| Method | v1 | v2 |
|
|
600
|
+
|--------|----|----|
|
|
601
|
+
| `getByTargetId`, `getByExternalId` | `AxiosResponse<T>`; rejects with 404 when missing | `T \| null`; `null` when no record exists |
|
|
602
|
+
| `create`, `update` | `AxiosResponse<T>` | `T` |
|
|
603
|
+
| any other failure | axios error | `AirSyncHttpError` (`message`, `status?`), exported from the root |
|
|
604
|
+
|
|
605
|
+
```ts
|
|
606
|
+
// v1
|
|
607
|
+
try {
|
|
608
|
+
const res = await mappers.getByTargetId({ sync_unit, target });
|
|
609
|
+
return res.data.sync_mapper_record.external_ids[0];
|
|
610
|
+
} catch (e) {
|
|
611
|
+
if (axios.isAxiosError(e) && e.response?.status === 404) return undefined;
|
|
612
|
+
throw e;
|
|
613
|
+
}
|
|
614
|
+
// v2
|
|
615
|
+
const record = await mappers.getByTargetId({ sync_unit, target });
|
|
616
|
+
return record?.sync_mapper_record.external_ids[0];
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
- Drop `.data`; loosely typed code would otherwise read `undefined`.
|
|
620
|
+
- **Existence checks invert silently.** `try { await mappers.getByExternalId(…); return true; } catch { return false; }`
|
|
621
|
+
now always returns `true`, because a missing record resolves `null`. Rewrite
|
|
622
|
+
the `try` body as `return (await mappers.getByExternalId(…)) !== null;` and
|
|
623
|
+
**keep the `catch`** if v1 had one, so other failures still mean what they
|
|
624
|
+
meant before. Find these with
|
|
625
|
+
`grep -n "getByExternalId\|getByTargetId"`, not by searching for 404.
|
|
626
|
+
- Hand-written structural types (`{ getByTargetId(): Promise<{ data?: … }> }`)
|
|
627
|
+
keep `.data` compiling. Replace them with `Pick<Mappers, 'getByTargetId'>`.
|
|
628
|
+
- Replace 404 `catch` branches with a `null` check.
|
|
629
|
+
- A **generic `catch`** around a lookup (no 404 test) also handled "not
|
|
630
|
+
found": it reported an item error, logged a warning or tried the next ID.
|
|
631
|
+
With `null` that path no longer runs, so add a `null` branch that does what
|
|
632
|
+
the `catch` did, and keep the `catch` for other failures.
|
|
633
|
+
- Replace `isAxiosError` checks on mapper calls with
|
|
634
|
+
`e instanceof AirSyncHttpError` / `e.status`. Checks on the connector's own
|
|
635
|
+
HTTP calls stay.
|
|
636
|
+
- Test doubles: `mockResolvedValue({ data: { sync_mapper_record } })` →
|
|
637
|
+
`mockResolvedValue({ sync_mapper_record })`; a 404 `mockRejectedValue(...)`
|
|
638
|
+
or a "not found" `mockResolvedValue({ data: {} })` → `mockResolvedValue(null)`.
|
|
639
|
+
Find them with `grep sync_mapper_record`.
|
|
640
|
+
|
|
641
|
+
## 11. Removed symbols
|
|
642
|
+
|
|
643
|
+
| Removed | Replacement |
|
|
644
|
+
|---------|-------------|
|
|
645
|
+
| `ExtractorEventType`, `LoaderEventType` | none; return a `TaskResult` (§4), and in tests assert on it. Compare against the raw wire string if really needed |
|
|
646
|
+
| `ExtractionCommonError` | `AirSyncErrorCode` on `error.code` (§12) |
|
|
647
|
+
| `WorkerAdapterOptions` | `SpawnOptions` (spawn), `RepoOptions` (`overriddenOptions`) |
|
|
648
|
+
| `WorkerAdapterInterface`, `SpawnInterface`, `StreamResponse`, `StreamAttachmentsResponse` | none; drop the import |
|
|
649
|
+
| `ArtifactsPrepareResponse` | none; if the connector uses it as its own type, declare it locally (`{ url: string; id: string; form_data: { key: string; value: string }[] }`) |
|
|
650
|
+
| worker IPC types (`WorkerEvent`, `WorkerMessage*`, `WorkerData`, `GetWorkerPathInterface`) | none; assert on the returned `TaskResult` |
|
|
651
|
+
| `State`, `createAdapterState`, `ToDevRev`, `AdapterState` | `createTest*Adapter` (§5); drop SDK state types (§8) |
|
|
652
|
+
| `Uploader` (deep import of `dist/uploader/uploader`, used to store and read back the connector's own artifacts) | `adapter.uploadArtifact(itemType, data)` → `{ id }` and `adapter.downloadArtifact<T>(id)` → `T[]`; both throw instead of returning `{ error }` |
|
|
653
|
+
| `Adapter`, `createAdapter`, `DemoExtractor`, `HTTPClient`, `defaultResponse`, deprecated `Uploader` | the v2 worker contract, your own axios client, repos |
|
|
654
|
+
| `ExtractionMode` | `SyncMode` |
|
|
655
|
+
| `EventContextIn`, `EventContextOut` | `EventContext` |
|
|
656
|
+
| `DomainObjectState`, `ErrorLevel`, `LogRecord`, `AdapterUpdateParams` | none |
|
|
657
|
+
| `translate*EventType` helpers | none; the platform sends modern strings |
|
|
658
|
+
| `EventType.UnknownEventType` | the string `'UNKNOWN_EVENT_TYPE'` |
|
|
659
|
+
| `adapter.hasWorkerEmitted`, `adapter.emit` spies | none; return / assert on the `TaskResult` (§4, §13) |
|
|
660
|
+
| `EventData.progress`, `EventData.external_sync_units` | none (§4, §7) |
|
|
661
|
+
|
|
662
|
+
Deprecated `EventType` members are removed. They had **different** string
|
|
663
|
+
values from their replacements:
|
|
664
|
+
|
|
665
|
+
| Removed | Use |
|
|
666
|
+
|---------|-----|
|
|
667
|
+
| `ExtractionExternalSyncUnitsStart` | `StartExtractingExternalSyncUnits` |
|
|
668
|
+
| `ExtractionMetadataStart` | `StartExtractingMetadata` |
|
|
669
|
+
| `ExtractionDataStart` / `ExtractionDataContinue` | `StartExtractingData` / `ContinueExtractingData` |
|
|
670
|
+
| `ExtractionDataDelete` | `StartDeletingExtractorState` |
|
|
671
|
+
| `ExtractionAttachmentsStart` / `ExtractionAttachmentsContinue` | `StartExtractingAttachments` / `ContinueExtractingAttachments` |
|
|
672
|
+
| `ExtractionAttachmentsDelete` | `StartDeletingExtractorAttachmentsState` |
|
|
673
|
+
|
|
674
|
+
This also applies to `workerPathOverrides` keys. `EventType` can gain members
|
|
675
|
+
in minor versions, so give `switch` statements over it a `default`.
|
|
676
|
+
|
|
677
|
+
## 12. Error codes
|
|
678
|
+
|
|
679
|
+
`ExtractionCommonError` is now `AirSyncErrorCode`. Its values are bare codes,
|
|
680
|
+
and you set them on `error.code`:
|
|
681
|
+
|
|
682
|
+
```ts
|
|
683
|
+
// v1
|
|
684
|
+
return { status: 'error', error: { message: ExtractionCommonError.EXTERNAL_SYNC_UNIT_DELETED } };
|
|
685
|
+
// v2
|
|
686
|
+
return {
|
|
687
|
+
status: 'error',
|
|
688
|
+
error: { message: `Project ${id} was deleted.`, code: AirSyncErrorCode.EXTERNAL_SYNC_UNIT_DELETED },
|
|
689
|
+
};
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
The platform only recognizes a code when the message is exactly
|
|
693
|
+
`ERROR_CODE=<code>`, so the SDK sends just that and logs your `message`. Write
|
|
694
|
+
a useful `message`. Raw `'ERROR_CODE=…'` strings and connector helpers that
|
|
695
|
+
return them (`HISTORICAL_DONE`, `authErrorCode(e)`) still work at runtime but
|
|
696
|
+
are migrated too: make the helper return `AirSyncErrorCode | undefined`, set it
|
|
697
|
+
as `code`, and update tests that pin the literal. The wire string doesn't
|
|
698
|
+
change.
|
|
699
|
+
|
|
700
|
+
## 13. Tests
|
|
701
|
+
|
|
702
|
+
- Mock factories: `processTask` → `processExtractionTask` /
|
|
703
|
+
`processLoadingTask`. Drop the `WorkerAdapter`, `axios`, `axiosClient` and
|
|
704
|
+
`emit: jest.fn()` keys; if nothing is left, or the file under test never
|
|
705
|
+
imports the SDK, delete the `jest.mock` entirely. Keep `jest.requireActual`
|
|
706
|
+
only for enums that still exist (`EventType`, `SyncMode`, `AirSyncErrorCode`).
|
|
707
|
+
- HTTP mocks: a test that mocked the SDK's `axiosClient` mocks the connector's
|
|
708
|
+
client module instead (`jest.mock('../http/axios-client', () => ({ axiosClient: { get: jest.fn(), post: jest.fn() } }))`).
|
|
709
|
+
Don't automock `axios` when the source calls `axios.create()`, because the
|
|
710
|
+
client becomes `undefined`. For plain `axios` use
|
|
711
|
+
`jest.mock('axios', () => ({ __esModule: true, default: { get: jest.fn(), isAxiosError: jest.fn() }, AxiosError: jest.requireActual('axios').AxiosError }))`.
|
|
712
|
+
Patching a client's `defaults.adapter` now affects only connector traffic.
|
|
713
|
+
- ESU tests assert on the repo: give the mock adapter
|
|
714
|
+
`initializeRepos: jest.fn()` and `getRepo: () => ({ push: mockPush })`, then
|
|
715
|
+
check `mockPush.mock.calls[0][0]`.
|
|
716
|
+
- Replace emit assertions with assertions on the returned result. This applies
|
|
717
|
+
to helpers you call directly too.
|
|
718
|
+
```ts
|
|
719
|
+
const result = await mockProcessTask.mock.calls[0][0].task({ adapter });
|
|
720
|
+
expect(result).toEqual({ status: 'success' });
|
|
721
|
+
```
|
|
722
|
+
- Casts like `as unknown as WorkerAdapter<S>` become `ExtractionAdapter<S>` /
|
|
723
|
+
`LoadingAdapter<S>`.
|
|
724
|
+
- Update doubles and fixtures to the v2 shapes: `delaySeconds`, callback
|
|
725
|
+
`status` unions (§6), mapper `null` / unwrapped bodies (§10), `code` on
|
|
726
|
+
errors (§12), renamed state fields and the removed SDK fields (§8).
|
|
727
|
+
- For a deleted `onTimeout`, drop the tests of its body and assert it's
|
|
728
|
+
`undefined`. A factory like `processExtractionTask: (_config) => jest.fn()`
|
|
729
|
+
discards the config; use `processExtractionTask: jest.fn()` and read
|
|
730
|
+
`mocked.mock.calls[0][0].onTimeout`. Capture it in a module-level `const`
|
|
731
|
+
right after importing the worker: `jest.clearAllMocks()` in a `beforeEach`
|
|
732
|
+
wipes the calls. Tests of a deleted SSOR workaround go with it.
|
|
733
|
+
|
|
734
|
+
## 14. Smaller changes
|
|
735
|
+
|
|
736
|
+
- `RepoInterface.overridenOptions` → `overriddenOptions`.
|
|
737
|
+
- `skipConfirmation` is removed; the SDK handles the ESU repo itself.
|
|
738
|
+
- `NormalizedAttachment.grand_parent_id` is `string`; use `String(id)`.
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## Rare cases
|
|
743
|
+
|
|
744
|
+
- **Attachment syncs started on SDK < 1.15.2 and still in flight** re-upload
|
|
745
|
+
their already-processed attachments once. Drain them before upgrading if
|
|
746
|
+
that matters.
|
|
747
|
+
- **`is_sdk_log`** is no longer on log lines; update dashboards that filter on
|
|
748
|
+
it.
|
|
749
|
+
- **Connectors migrated against a beta (≤ `2.0.0-beta.6`)** must re-apply
|
|
750
|
+
§3–§14 against GA, because most of the API above changed after those betas.
|
|
751
|
+
State they wrote is still read.
|