@syncmatters/connector-sdk 1.0.0 → 1.0.2

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.
@@ -0,0 +1,159 @@
1
+ # Building SyncMatters connectors — the authoring guide
2
+
3
+ This is the canonical guide to writing connectors for the SyncMatters platform. It ships inside
4
+ `@syncmatters/connector-sdk` and is version-locked to the type definitions next to it. It is
5
+ written to be consumed by AI coding assistants as much as by humans: read this file first, then
6
+ open only the topic files your task touches.
7
+
8
+ A **connector** is a JavaScript class (an `.mjs` file) that teaches the platform how to talk to
9
+ one external system: how to authenticate, what objects and fields exist, and how to query,
10
+ create, update and delete rows. Your code **executes on the SyncMatters platform**, not
11
+ locally — the local workspace is for editing, type-checking and pushing via the `sm` CLI.
12
+
13
+ ## Topic files (read what your task touches)
14
+
15
+ | File | Read when you are... |
16
+ | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
17
+ | [01-workspace-and-meta-json.md](./01-workspace-and-meta-json.md) | adding files, wiring modules, editing any `*.meta.json` sidecar, declaring settings/OAuth/webhooks/npm packages |
18
+ | [02-lifecycle-and-shape.md](./02-lifecycle-and-shape.md) | starting a connector; implementing `init`/`test`/`meta`; anything about `InitArgs` |
19
+ | [03-http-auth-ratelimit.md](./03-http-auth-ratelimit.md) | making HTTP calls, rate limiting, retries, API keys, basic auth, OAuth2 |
20
+ | [04-meta-objects-fields.md](./04-meta-objects-fields.md) | writing `meta()` — objects, fields, flags, keys, relationships, filters |
21
+ | [05-query.md](./05-query.md) | implementing `query()` — paging, checkpoints, ids/match/related filters, `Row` |
22
+ | [06-upsert-delete.md](./06-upsert-delete.md) | implementing `upsert()`, `delete()`, `upsertClean()`, `upsertFieldOptions()`, batching |
23
+ | [07-events-webhooks.md](./07-events-webhooks.md) | webhooks: `parseEvents()`, `handleParsedEvent()`, signature verification |
24
+ | [08-errors.md](./08-errors.md) | error handling — `ConnectorError`, `ErrorCode` selection, `HttpError` |
25
+ | [09-testing.md](./09-testing.md) | the test harness: `mod Test.mjs` + `mod ObjectTestFeatures.mjs` |
26
+ | [10-style-and-pitfalls.md](./10-style-and-pitfalls.md) | style rules, deprecated APIs to avoid, known traps — skim this once regardless |
27
+ | [11-composite-keys.md](./11-composite-keys.md) | objects with multi-part identity (child REST resources, junction rows) — key encoding across query/upsert/delete |
28
+ | [12-example-connector.md](./12-example-connector.md) | **the canonical worked connector** — a complete three-object connector (relationships, matching, composite keys, custom fields, upsertClean, query/upsert/delete) on one page; its test-file counterpart is 15 |
29
+ | [13-file-fields.md](./13-file-fields.md) | objects carrying file content — fields of `type: "file"`, FileProvider, fields-driven inclusion (do NOT copy the fleet's legacy `row.file`) |
30
+ | [14-vendor-sdks-and-non-http.md](./14-vendor-sdks-and-non-http.md) | vendor npm SDKs (googleapis, AWS, box), database/on-prem `agent_mode: "always"` connectors, SOAP/XML, self-managed rate limiting |
31
+ | [15-example-test-file.md](./15-example-test-file.md) | **the canonical worked test file** — the complete `mod Test.mjs` + `mod ObjectTestFeatures.mjs` pair for the Acme connector of 12 (TestFeatureProvider, prepare/cleanup hooks, issue simulators, feature scoping) |
32
+
33
+ ## The 60-second version
34
+
35
+ A minimal read-only connector (the full-featured canonical example — relationships, matching,
36
+ custom fields, writes — is [12-example-connector.md](./12-example-connector.md)):
37
+
38
+ ```js
39
+ import SDK from "@syncmatters/connector-sdk";
40
+
41
+ export default class Acme {
42
+ /** @type {SDK.HttpClient | undefined} */ #http;
43
+ /** @type {SDK.Logger | undefined} */ #log;
44
+ /** @type {string} */ #apiKey = "";
45
+
46
+ /** @param {SDK.InitArgs} args @returns {Promise<void>} */
47
+ async init(args) {
48
+ SDK.verifyType(this); // compile-time check that this class matches SDK.Connector
49
+ this.#log = args.log;
50
+ this.#apiKey = args.settings.get("api_key");
51
+ this.#http = SDK.utilities.httpClient({
52
+ limiter: SDK.utilities.rateLimiter({ type: "rolling", count: 10, ms: 1000 }),
53
+ maxRetry: 3,
54
+ });
55
+ }
56
+
57
+ /** @returns {Promise<SDK.TestResult>} */
58
+ async test() {
59
+ try {
60
+ await this.#call({ method: "GET", url: "me" });
61
+ return { success: true };
62
+ } catch (err) {
63
+ return { success: false, message: err instanceof Error ? err.message : String(err) };
64
+ }
65
+ }
66
+
67
+ /** @returns {Promise<SDK.ConnectorMeta>} */
68
+ async meta() {
69
+ const whoAmI = await this.#call({ method: "GET", url: "me" });
70
+ return {
71
+ identity: whoAmi.email,
72
+ objects: [
73
+ {
74
+ id: "contacts",
75
+ name: "Contacts",
76
+ canQueryByCheckpoint: true,
77
+ canQueryList: true,
78
+ canQueryByIds: true,
79
+ queryFields: [
80
+ { id: "id", type: "string", isKey: true },
81
+ { id: "email", type: "string" },
82
+ ],
83
+ matchRules: ["email[ci]", "name[ci]"],
84
+ },
85
+ ],
86
+ };
87
+ }
88
+
89
+ /** @param {SDK.QueryOptions} options @returns {Promise<SDK.QueryPage>} */
90
+ async query(options) {
91
+ const queryState = options.queryState || { page: 1 };
92
+ const resp = await this.#call({ method: "GET", url: `contacts?page=${queryState.page}` });
93
+ const rows = resp.items.map((item) => ({ meta: { key: String(item.id) }, data: item }));
94
+ queryState.page += 1;
95
+ return { rows, queryState, finalPage: rows.length === 0 };
96
+ }
97
+
98
+ /** @param {SDK.HttpRequest} request */
99
+ async #call(request) {
100
+ if (!request.url.startsWith("https://")) {
101
+ request.url = "https://api.example.com/v1/" + request.url;
102
+ }
103
+ request.headers = { ...request.headers, Authorization: `Bearer ${this.#apiKey}` };
104
+ return await this.#http.execute(request);
105
+ }
106
+ }
107
+ ```
108
+
109
+ Implementing more methods unlocks more platform capability:
110
+
111
+ | You implement | The platform can |
112
+ | --------------------------------------- | ------------------------------------------------------------------- |
113
+ | `test()` + `meta()` (**required**) | validate connections, show objects/fields in the UI |
114
+ | `query()` | read data — lists, differential syncs, lookups |
115
+ | `upsert()` / `delete()` | write data |
116
+ | `upsertClean()` | pre-validate + change-detect rows before writing (`canUpsertClean`) |
117
+ | `upsertFieldOptions()` | auto-add picklist values (`canUpsertFieldOptions`) |
118
+ | `parseEvents()` / `handleParsedEvent()` | receive webhooks and keep caches fresh |
119
+ | `optimalBatchSize()` | pick batch sizes per operation |
120
+
121
+ ## Golden rules (every one is explained in a topic file)
122
+
123
+ 1. Import the SDK exactly as `import SDK from "@syncmatters/connector-sdk";` in workspace `.mjs` files.
124
+ 2. **Prefer** calling HTTP through `SDK.utilities.httpClient({ limiter: SDK.utilities.rateLimiter(...) })`.
125
+ More out of the box than `fetch`, `axios`, or `node:https`.
126
+ 3. Rows are `{ meta: { key: string }, data: object }`. `key` must be the value of the field your
127
+ `meta()` flags with `isKey` (or `isUserKey`). Keys are strings.
128
+ 4. `meta().objects` is an **array** of `ObjectMeta`. Every `canQuery*`/`canUpsert*` flag you set
129
+ is a promise — the platform will call the corresponding operation.
130
+ 5. Platform-visible failures are `throw new SDK.ConnectorError(SDK.ErrorCode.<Code>, message)`.
131
+ `test()` is the exception: it catches and returns `{ success: false, message }`.
132
+ 6. `query()` is called repeatedly: return `queryState` to continue, `finalPage: true` to stop.
133
+ Checkpoint queries must emit a `checkpoint` computed with an **overlap window** (see 05).
134
+ 7. `upsert()`/`delete()` results must be **positionally aligned** with `options.rows`.
135
+ 8. Every file needs a `meta/**/*.meta.json` sidecar; multi-file connectors are wired by
136
+ `module_script_file_paths` (entry sidecar lists ALL files, module sidecars list direct deps).
137
+ 9. JSDoc-type everything — workspaces run with `checkJs`. Private state in `#fields`. First line
138
+ of `init()` is `SDK.verifyType(this);`.
139
+ 10. Don't use deprecated APIs: `ObjectMetaSummary`/`ObjectMetaDetail`, `cacheRefreshOptions`,
140
+ `QueryPage.cacheState`, `RateLimiter.take()`. (See 10.)
141
+
142
+ ## Building a new connector, end to end
143
+
144
+ 1. **Scaffold**: `sm connector init` (wizard: settings, agent mode, OAuth mode) — or copy an
145
+ existing connector's file pair as a pattern.
146
+ 2. **Wire auth + HTTP** in `init()` (03). Add connection settings to the sidecar (01).
147
+ 3. **Make `test()` real**: cheapest authenticated call the API offers (02).
148
+ 4. **Describe objects** in `meta()` (04). Start with one object and its `isKey` field; grow from
149
+ there. `SDK.utilities.fieldsFromJSON(sample)` can draft field lists from a sample response.
150
+ 5. **Implement `query()`** for `canQueryList`, then `idsFilter`, then `checkpointFilter` (05).
151
+ 6. **Implement writes** if needed: `upsert()`, `delete()` (06).
152
+ 7. **Validate + push**: `sm validate`, `sm status` / `sm diff`, `sm push -m "message"`.
153
+ A push 409 means the platform copy changed: `sm pull`, merge, push again — never force.
154
+ 8. **Test harness**: add `mod Test.mjs` (+ generated `mod ObjectTestFeatures.mjs`) so the
155
+ platform's connector test suite can exercise your objects (09; worked example: 15).
156
+
157
+ ## Support
158
+
159
+ - <https://syncmatters.com> · <support@syncmatters.com>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncmatters/connector-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "TypeScript type definitions for the SyncMatters connector SDK (types only - connectors execute on the SyncMatters platform)",
5
5
  "types": "./index.d.ts",
6
6
  "exports": {
@@ -12,9 +12,9 @@
12
12
  "license": "MIT",
13
13
  "author": "SyncMatters",
14
14
  "homepage": "https://syncmatters.com",
15
- "typesContentHash": "39bdb6461d3d6962e0d284c2f4b6c71f1d08a6cd6a4b422754b3c731414fe6da",
15
+ "typesContentHash": "1e322fcb4cda8d7a31d350d575470adeb2402009ea1929274efe27d7ccb7e041",
16
16
  "dependencies": {
17
17
  "@types/node": "*",
18
- "@syncmatters/script-api": "^1.0.0"
18
+ "@syncmatters/script-api": "^1.0.1"
19
19
  }
20
20
  }