@syncmatters/connector-sdk 1.0.4 → 1.0.5
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/docs/04-meta-objects-fields.md +4 -3
- package/docs/05-query.md +18 -7
- package/docs/07-events-webhooks.md +18 -9
- package/docs/12-example-connector.md +5 -1
- package/docs/16-the-end-game-syncs.md +66 -0
- package/docs/17-user-help-pages.md +98 -0
- package/docs/connector-authoring.md +12 -2
- package/module.d.ts +1 -1
- package/package.json +3 -3
|
@@ -144,9 +144,10 @@ Declaring it on the wrong object doesn't fail here — it fails later, when
|
|
|
144
144
|
## The query capability bar
|
|
145
145
|
|
|
146
146
|
Syncs are built from these capabilities — an object missing one is an object syncs can only
|
|
147
|
-
partially use.
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
partially use ([16-the-end-game-syncs.md](./16-the-end-game-syncs.md) maps each one to the
|
|
148
|
+
sync stage that consumes it). **For every object, strive to support all five; omit a flag
|
|
149
|
+
only after the workaround patterns below have failed**, and note why in a comment so the
|
|
150
|
+
next author doesn't re-investigate:
|
|
150
151
|
|
|
151
152
|
1. `canQueryList`
|
|
152
153
|
2. `canQueryByIds`
|
package/docs/05-query.md
CHANGED
|
@@ -9,22 +9,30 @@ Each return's `queryState` is echoed into the next call, until you return `final
|
|
|
9
9
|
```js
|
|
10
10
|
/** @param {SDK.QueryOptions} options @returns {Promise<SDK.QueryPage>} */
|
|
11
11
|
async query(options) {
|
|
12
|
-
const
|
|
12
|
+
const requestedLimit = options.queryOptions?.limit;
|
|
13
|
+
const queryState = options.queryState || { offset: 0, pageSize: 200, rowCount: 0, startTime: Date.now() };
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
// honor the caller's limit BOTH ways: request no more rows than still needed...
|
|
16
|
+
const pageSize = requestedLimit != null
|
|
17
|
+
? Math.min(queryState.pageSize, requestedLimit - queryState.rowCount)
|
|
18
|
+
: queryState.pageSize;
|
|
19
|
+
const resp = await this.#call(this.#buildRequest(options, queryState, pageSize));
|
|
15
20
|
|
|
16
21
|
/** @type {SDK.Row[]} */
|
|
17
|
-
|
|
22
|
+
let rows = resp.records.map((r) => ({ meta: { key: String(r.id) }, data: r }));
|
|
23
|
+
// ...and trim overflow when the API ignores the page size (never return past the limit)
|
|
24
|
+
if (requestedLimit != null && queryState.rowCount + rows.length > requestedLimit) {
|
|
25
|
+
rows = rows.slice(0, requestedLimit - queryState.rowCount);
|
|
26
|
+
}
|
|
18
27
|
|
|
19
28
|
queryState.offset += rows.length;
|
|
20
29
|
queryState.rowCount += rows.length;
|
|
21
30
|
|
|
22
31
|
/** @type {SDK.QueryPage} */
|
|
23
32
|
const result = { rows, queryState };
|
|
24
|
-
const requestedLimit = options.queryOptions?.limit;
|
|
25
33
|
result.finalPage =
|
|
26
34
|
rows.length === 0 ||
|
|
27
|
-
rows.length <
|
|
35
|
+
rows.length < pageSize ||
|
|
28
36
|
(requestedLimit != null && queryState.rowCount >= requestedLimit);
|
|
29
37
|
|
|
30
38
|
if (options.queryOptions?.checkpointFilter && result.finalPage) {
|
|
@@ -40,8 +48,11 @@ Rules that follow from the lifecycle:
|
|
|
40
48
|
|
|
41
49
|
- `queryState` is yours — an opaque cursor (`offset`, `nextPageToken`, `lastId`, ...). Keep it
|
|
42
50
|
JSON-serializable.
|
|
43
|
-
-
|
|
44
|
-
|
|
51
|
+
- **Honor `options.queryOptions.limit` absolutely** — never return more rows than it, across
|
|
52
|
+
ALL pages combined. Cap the page size you request at the remaining allowance (don't collect
|
|
53
|
+
rows you'll throw away), trim overflow when the API over-returns anyway, and set
|
|
54
|
+
`finalPage: true` once the limit is reached. Callers size buffers and previews off this
|
|
55
|
+
number; over-returning breaks them.
|
|
45
56
|
- Prefer **keyset pagination** (`WHERE id > lastId ORDER BY id`) over offsets when the API
|
|
46
57
|
allows — offset/`after` cursors can skip rows when records are deleted mid-pagination.
|
|
47
58
|
|
|
@@ -93,10 +93,16 @@ if (expected !== computed) return { events: [] };
|
|
|
93
93
|
Failed verification: return no events (and optionally log) — throwing turns an attacker's junk
|
|
94
94
|
into your error noise.
|
|
95
95
|
|
|
96
|
-
## `handleParsedEvent(options)` —
|
|
96
|
+
## `handleParsedEvent(options)` — optional cache hint, usually skipped
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
**Most connectors do not implement this.** The platform's cache stays correct without it —
|
|
99
|
+
your checkpoint queries pick up changes on the next differential run. `handleParsedEvent` is
|
|
100
|
+
an optional latency optimization the platform calls per affected object after events fire;
|
|
101
|
+
implement it only when one of these actually applies:
|
|
102
|
+
|
|
103
|
+
- **Deletion events the differential query cannot see** — many APIs' changed-since filters
|
|
104
|
+
never surface deleted rows, so a `record.deleted` webhook is the only signal. This is the
|
|
105
|
+
one genuinely load-bearing use:
|
|
100
106
|
|
|
101
107
|
```js
|
|
102
108
|
/** @param {SDK.HandleParsedEventOptions} options */
|
|
@@ -107,17 +113,20 @@ async handleParsedEvent(options) {
|
|
|
107
113
|
page: { rows: [ { meta: { key: options.event.key, deleted: true } } ], finalPage: true },
|
|
108
114
|
});
|
|
109
115
|
}
|
|
110
|
-
// updated-record events:
|
|
111
|
-
//
|
|
116
|
+
// updated-record events: usually report NOTHING and let the next differential query pick
|
|
117
|
+
// the row up; fetch-and-report only when near-real-time trigger freshness matters.
|
|
112
118
|
}
|
|
113
119
|
```
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
- **Near-real-time triggers** where waiting for the next differential run is too slow.
|
|
122
|
+
|
|
123
|
+
(`options.event` = `{ id, timestamp, key?, data? }`; `reportChanges({ id, page })` takes a
|
|
124
|
+
normal `QueryPage`.) If neither case applies, omit the method — an empty or speculative
|
|
125
|
+
implementation is maintenance surface with no benefit.
|
|
117
126
|
|
|
118
127
|
## Flow summary
|
|
119
128
|
|
|
120
129
|
external system → platform webhook endpoint → your `parseEvents` (verify, emit
|
|
121
130
|
`ParsedEvent[]`) → platform de-dups (`uniqueId`), resolves connections (`eventIdentity`),
|
|
122
|
-
fires triggers (`typeId`) → your `handleParsedEvent` per changed object →
|
|
123
|
-
updates the cache.
|
|
131
|
+
fires triggers (`typeId`) → [optional] your `handleParsedEvent` per changed object →
|
|
132
|
+
`reportChanges` updates the cache.
|
|
@@ -232,7 +232,11 @@ export default class Acme {
|
|
|
232
232
|
const queryState = options.queryState ||
|
|
233
233
|
{ after: undefined, limit: 200, rowCount: 0, startTime: Date.now() };
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
// request no more rows than the caller still needs (05); overflow is trimmed below
|
|
236
|
+
const pageSize = qo.limit != null
|
|
237
|
+
? Math.min(queryState.limit, qo.limit - queryState.rowCount)
|
|
238
|
+
: queryState.limit;
|
|
239
|
+
const params = [`limit=${pageSize}`];
|
|
236
240
|
if (queryState.after) params.push(`after=${encodeURIComponent(queryState.after)}`);
|
|
237
241
|
|
|
238
242
|
if (qo.idsFilter) {
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# The end game: how syncs consume your connector
|
|
2
|
+
|
|
3
|
+
Connectors are not run for their own sake. The platform's flagship consumer is the **sync**:
|
|
4
|
+
a configured pipeline that takes rows from a source system and creates/updates/deletes rows
|
|
5
|
+
in a destination system — differentially, repeatedly, in scheduled group runs, often
|
|
6
|
+
bidirectionally as a pair of syncs. Every capability your `meta()` declares is an input to
|
|
7
|
+
that pipeline; every one you omit removes a sync feature for every customer using your
|
|
8
|
+
connector. This page maps the sync pipeline to the connector contract so you know what each
|
|
9
|
+
method is FOR — and what silently degrades when you skimp.
|
|
10
|
+
|
|
11
|
+
## The pipeline, from the connector's point of view
|
|
12
|
+
|
|
13
|
+
A sync run walks each source row through these stages. The left column is the sync engine's
|
|
14
|
+
step; the right column is YOUR code being called (on the source or destination connection):
|
|
15
|
+
|
|
16
|
+
| Sync stage | Connector capability consumed |
|
|
17
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
18
|
+
| Collect source rows (differential run) | source `query()` + `checkpointFilter` — or a FULL re-read every run when `canQueryByCheckpoint` is missing |
|
|
19
|
+
| Collect source rows (selected ids / child sync) | source `query()` + `idsFilter` — child syncs resolve referenced rows this way (e.g. the Company a Contact points at) |
|
|
20
|
+
| Validate previously matched rows | destination `query()` + `idsFilter` — the match store re-fetches saved destination rows to confirm they still exist |
|
|
21
|
+
| Collect related source rows | source `query()` + `relatedFilter` — sync configs select declared relationships to pull rows needed for mapping |
|
|
22
|
+
| Collect lookup rows | source `query()` + `idsFilter` on OTHER objects — field values holding ids of rows from other objects |
|
|
23
|
+
| Match rules (no prior match) | **destination** `query()` + `matchFilter` — link the source row to an existing destination row before ever creating one |
|
|
24
|
+
| Write to destination | destination `upsertClean()` (validate + skip no-op writes) then `upsert()` / `delete()` |
|
|
25
|
+
| Deletion propagation | rows with `meta: { key, deleted: true }` from checkpoint streams or `handleParsedEvent` |
|
|
26
|
+
| Near-real-time triggers | `parseEvents()` — webhook events fire sync runs without waiting for the schedule |
|
|
27
|
+
| Batching | `optimalBatchSize()` — the engine slices its reads/writes to your preference |
|
|
28
|
+
|
|
29
|
+
Two structural notes: match rules run against the **destination** connector (matching finds
|
|
30
|
+
the row to update over there), and a "sync" is usually one direction — a bidirectional setup
|
|
31
|
+
is two syncs consuming both your read AND write paths on the same object.
|
|
32
|
+
|
|
33
|
+
## What this means for the code you write
|
|
34
|
+
|
|
35
|
+
- **The [query capability bar](./04-meta-objects-fields.md#the-query-capability-bar) is the
|
|
36
|
+
sync feature list.** An object without `checkpointFilter` re-reads everything every run
|
|
37
|
+
(slow, rate-limit hungry — and for big objects, effectively unsyncable on a schedule).
|
|
38
|
+
Without `idsFilter`, child-sync resolution, lookup collection, and match-store validation
|
|
39
|
+
all fail. Without `matchRules`, every sync run risks creating duplicates instead of linking
|
|
40
|
+
to rows that already exist. Without `relationships`, mappings can't reach related data.
|
|
41
|
+
- **Row keys are forever.** The match store persists source-key → destination-key pairs
|
|
42
|
+
across runs, and checkpoint streams identify rows by key. A key that changes encoding
|
|
43
|
+
(see [11-composite-keys.md](./11-composite-keys.md)) orphans every stored match and makes
|
|
44
|
+
cached rows reappear as new — customer-visible duplicates.
|
|
45
|
+
- **The checkpoint overlap rule is a data-integrity rule, not a performance tip.** A missed
|
|
46
|
+
row in one differential run is missed by every downstream sync until the row happens to
|
|
47
|
+
change again ([05-query.md](./05-query.md)).
|
|
48
|
+
- **Positional upsert alignment corrupts customer data when wrong.** The engine maps
|
|
49
|
+
`results[i]` back to the source row it was processing — misalignment stores the WRONG
|
|
50
|
+
destination id in the match store, and every later run updates the wrong row
|
|
51
|
+
([06-upsert-delete.md](./06-upsert-delete.md)).
|
|
52
|
+
- **`upsertClean` is loop insurance.** In bidirectional pairs, writing unchanged values
|
|
53
|
+
re-triggers the other direction's differential query. `detectFirstChange` skipping no-op
|
|
54
|
+
writes is what keeps two syncs from ping-ponging a row forever.
|
|
55
|
+
- **`meta: { deleted: true }` rows are how deletions propagate.** If your API exposes
|
|
56
|
+
deletions and you never emit them, destination systems accumulate rows the source deleted.
|
|
57
|
+
|
|
58
|
+
## The mindset
|
|
59
|
+
|
|
60
|
+
When implementing an object, don't ask "what does this API endpoint return?" — ask "can a
|
|
61
|
+
customer schedule a differential, deduplicated, relationship-aware, bidirectional sync of
|
|
62
|
+
this object?" Each "no" in that sentence is a missing capability from the table above, and
|
|
63
|
+
[the capability bar](./04-meta-objects-fields.md#the-query-capability-bar) has the workaround
|
|
64
|
+
patterns for awkward APIs. The platform-side sync configuration (mapping, filters, custom
|
|
65
|
+
`filterFast`/`beforeRun` logic, match rule selection) is the customer's job — giving their
|
|
66
|
+
configuration enough capabilities to work with is yours.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Writing the connector's user help pages
|
|
2
|
+
|
|
3
|
+
A finished connector ships with **customer-facing help pages**. They live in the platform
|
|
4
|
+
repo under `docs/help-markdown/connections/<slug>/` (slug = kebab-case product name, e.g.
|
|
5
|
+
`epicor-prophet-21`) and follow a fixed house pattern — copy the structure of an existing
|
|
6
|
+
current page (`greenhouse/` is a clean modern example) rather than inventing one:
|
|
7
|
+
|
|
8
|
+
| File | Audience | Content |
|
|
9
|
+
| -------------- | --------- | -------------------------------------------------------------------- |
|
|
10
|
+
| `index.md` | customer | what it is, how to connect, objects, capabilities & limits |
|
|
11
|
+
| `scripting.md` | developer | task-based code examples for calling the connection from a script |
|
|
12
|
+
| `guides/*.md` | either | optional deep dives (a complex OAuth setup, webhook configuration) |
|
|
13
|
+
|
|
14
|
+
**Derive everything from `meta()` and the sidecar — never from memory.** The settings
|
|
15
|
+
section must mirror `connector_metadata.settings` exactly (same display names, `*(sensitive)*`
|
|
16
|
+
markers on secrets); the objects table lists what `meta()` declares; the operations column is
|
|
17
|
+
computed from the flags (`Q` = query, `U` = `upsertFields`, `D` = `deleteFields`); every
|
|
18
|
+
declared `rowFilter` and match rule deserves a mention or example. If the help page and the
|
|
19
|
+
connector disagree, the page is wrong.
|
|
20
|
+
|
|
21
|
+
## `index.md` — frontmatter and section order
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
---
|
|
25
|
+
title: "Greenhouse"
|
|
26
|
+
area: "connections"
|
|
27
|
+
audience: "customer"
|
|
28
|
+
doc_type: "guide"
|
|
29
|
+
summary: "Connect to Greenhouse with a Harvest API key to query and write candidates, jobs, ..."
|
|
30
|
+
auth: "api-key" # or oauth2-connector / oauth2-connection / basic ...
|
|
31
|
+
agent: "no" # needs the on-premise agent?
|
|
32
|
+
sync_support: "yes"
|
|
33
|
+
class: "system"
|
|
34
|
+
current_version: "2" # when the connector has a Version setting
|
|
35
|
+
template_version: 1
|
|
36
|
+
generated: false
|
|
37
|
+
---
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Sections, in this order (omit ones that don't apply):
|
|
41
|
+
|
|
42
|
+
1. **Overview** — one paragraph: what the product is, what the connector does; then the
|
|
43
|
+
three bullets (`Authentication`, `On-premise agent`, `Sync ready?`).
|
|
44
|
+
2. **Before you begin** — vendor-side prerequisites, each with a link to the OFFICIAL vendor
|
|
45
|
+
doc plus a one-line click-path ("Configure → Dev Center → API Credential Management →
|
|
46
|
+
Create New API Key").
|
|
47
|
+
3. **Connection settings** — one bullet per setting, `*(sensitive)*` on secrets, defaults and
|
|
48
|
+
allowed values stated; `### Advanced settings` for the advanced group.
|
|
49
|
+
4. **Supported objects** — link to the vendor's API reference, then the table
|
|
50
|
+
`| Name | Operations ^1 | Description |` with the `^1 Q=Query, U=Upsert, D=Delete`
|
|
51
|
+
footnote. Note that the live per-connection list is in the connection UI.
|
|
52
|
+
5. **Capabilities & limits** — rate limits, timestamp formats, API quirks, insert-only
|
|
53
|
+
objects, pagination oddities. Start with the standard callout:
|
|
54
|
+
> In a Sync, source filtering and collecting only changed records (incremental/checkpoint)
|
|
55
|
+
> are configured in the Sync UI — no code required.
|
|
56
|
+
6. **Versioning & migration** — only when a `Version` setting exists: the version/status
|
|
57
|
+
table and the "change only after testing" caveat.
|
|
58
|
+
7. **Using this connection in scripts** — the standard `ctx.connections.myX()` snippet plus
|
|
59
|
+
links to `calling-a-connection.md` and `./scripting.md`.
|
|
60
|
+
8. **See also** — vendor API docs, utility functions.
|
|
61
|
+
|
|
62
|
+
## `scripting.md` — task-based examples
|
|
63
|
+
|
|
64
|
+
Frontmatter: `audience: "developer"`, a `slug: "connections/<slug>/scripting"`, one-line
|
|
65
|
+
summary. Open with the standard cross-link paragraph (common pattern lives in
|
|
66
|
+
`calling-a-connection.md`; settings/objects live in `./index.md`). Then one `##` section per
|
|
67
|
+
TASK, each a short runnable snippet — these are integration-SCRIPT examples, so they use the
|
|
68
|
+
script surface, not the connector SDK:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
const greenhouse = await ctx.connections.myGreenhouse(); // ## Connect
|
|
72
|
+
|
|
73
|
+
const one = await greenhouse.queryOne.Candidates({ idsFilter: ['12345'] }); // by id
|
|
74
|
+
|
|
75
|
+
for await (const row of greenhouse.query.Candidates({ // incremental
|
|
76
|
+
limit: 5,
|
|
77
|
+
checkpointFilter: { value: '2020-10-08T11:41:28Z' },
|
|
78
|
+
})) { /* ... */ }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Cover, in rough priority: connect · query by id · incremental (checkpoint) query ·
|
|
82
|
+
relatedFilter traversal · matchFilter · your declared rowFilters (show them combined with a
|
|
83
|
+
list/checkpoint query when `extends` allows) · upsert (add and update) · delete · file
|
|
84
|
+
fields when the connector has them. Match the house code style in these pages: single
|
|
85
|
+
quotes, `ctx.log.info(...)` for output, obvious placeholder ids (`'12345'`,
|
|
86
|
+
`test@example.com`), `// do processing here` bodies.
|
|
87
|
+
|
|
88
|
+
## Rules
|
|
89
|
+
|
|
90
|
+
- **No secrets, no PII, no real tenant data** — placeholder keys, example.com addresses,
|
|
91
|
+
obviously fake ids. Vendor click-paths are fine; screenshots are not yours to add.
|
|
92
|
+
- **Don't document deprecated surface** in new pages — `propertyFilter` examples appear in
|
|
93
|
+
older pages for connectors that still use it; new pages use the standard `fields` filter
|
|
94
|
+
([10-style-and-pitfalls.md](./10-style-and-pitfalls.md)).
|
|
95
|
+
- **Insert-only and other contract quirks belong in Capabilities & limits** — "Messages are
|
|
96
|
+
immutable once sent; updates are not supported" saves every future support ticket.
|
|
97
|
+
- **Keep `summary:` one sentence and verb-led** — it renders in search results and index
|
|
98
|
+
pages ("Connect to X with Y to query and write Z...").
|
|
@@ -10,6 +10,12 @@ one external system: how to authenticate, what objects and fields exist, and how
|
|
|
10
10
|
create, update and delete rows. Your code **executes on the SyncMatters platform**, not
|
|
11
11
|
locally — the local workspace is for editing, type-checking and pushing via the `sm` CLI.
|
|
12
12
|
|
|
13
|
+
**The end game is syncs**: customers configure differential, deduplicated, relationship-aware
|
|
14
|
+
(often bidirectional) syncs BETWEEN two connectors, and every capability you declare is an
|
|
15
|
+
input to that pipeline — [16-the-end-game-syncs.md](./16-the-end-game-syncs.md) maps each
|
|
16
|
+
sync stage to the connector method it consumes. Build objects to be syncable, not merely
|
|
17
|
+
queryable.
|
|
18
|
+
|
|
13
19
|
## Topic files (read what your task touches)
|
|
14
20
|
|
|
15
21
|
| File | Read when you are... |
|
|
@@ -20,7 +26,7 @@ locally — the local workspace is for editing, type-checking and pushing via th
|
|
|
20
26
|
| [04-meta-objects-fields.md](./04-meta-objects-fields.md) | writing `meta()` — objects, fields, flags, keys, relationships, filters |
|
|
21
27
|
| [05-query.md](./05-query.md) | implementing `query()` — paging, checkpoints, ids/match/related filters, `Row` |
|
|
22
28
|
| [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()
|
|
29
|
+
| [07-events-webhooks.md](./07-events-webhooks.md) | webhooks: `parseEvents()`, signature verification (and the usually-skipped `handleParsedEvent()`) |
|
|
24
30
|
| [08-errors.md](./08-errors.md) | error handling — `ConnectorError`, `ErrorCode` selection, `HttpError` |
|
|
25
31
|
| [09-testing.md](./09-testing.md) | the test harness: `mod Test.mjs` + `mod ObjectTestFeatures.mjs` |
|
|
26
32
|
| [10-style-and-pitfalls.md](./10-style-and-pitfalls.md) | style rules, deprecated APIs to avoid, known traps — skim this once regardless |
|
|
@@ -29,6 +35,8 @@ locally — the local workspace is for editing, type-checking and pushing via th
|
|
|
29
35
|
| [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
36
|
| [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
37
|
| [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) |
|
|
38
|
+
| [16-the-end-game-syncs.md](./16-the-end-game-syncs.md) | **why any of this exists** — how the sync pipeline consumes each connector capability, and what silently degrades when one is missing. Read once before designing `meta()` |
|
|
39
|
+
| [17-user-help-pages.md](./17-user-help-pages.md) | writing the connector's customer help pages (`connections/<slug>/index.md` + `scripting.md`) — the house structure, frontmatter, and example conventions |
|
|
32
40
|
|
|
33
41
|
## The 60-second version
|
|
34
42
|
|
|
@@ -115,7 +123,7 @@ Implementing more methods unlocks more platform capability:
|
|
|
115
123
|
| `upsert()` / `delete()` | write data |
|
|
116
124
|
| `upsertClean()` | pre-validate + change-detect rows before writing (`canUpsertClean`) |
|
|
117
125
|
| `upsertFieldOptions()` | auto-add picklist values (`canUpsertFieldOptions`) |
|
|
118
|
-
| `parseEvents()`
|
|
126
|
+
| `parseEvents()` | receive webhooks and fire triggers (optional `handleParsedEvent()` adds cache hints — usually skipped, see 07) |
|
|
119
127
|
| `optimalBatchSize()` | pick batch sizes per operation |
|
|
120
128
|
|
|
121
129
|
## Golden rules (every one is explained in a topic file)
|
|
@@ -161,6 +169,8 @@ Implementing more methods unlocks more platform capability:
|
|
|
161
169
|
9. **Run it**: `sm test` = the full suite (identical to the web UI's connector test — including
|
|
162
170
|
write features, so use a sandbox connection); `--only connection|meta|query` narrows. A
|
|
163
171
|
passing `--only` run is NOT suite coverage (09).
|
|
172
|
+
10. **Document it**: write the customer help pages — `connections/<slug>/index.md` +
|
|
173
|
+
`scripting.md` in the platform repo, derived from `meta()` and the sidecar (17).
|
|
164
174
|
|
|
165
175
|
## Support
|
|
166
176
|
|
package/module.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* SDK specific elements that would be of no interest to users of the API.
|
|
4
4
|
*/
|
|
5
5
|
export { EventScriptComplete } from "@syncmatters/script-api";
|
|
6
|
-
export type { Cache, FileProvider, HttpRequest, HttpClientOptions, KvCollection, KvIterator, Logger, EventContext, TestResult, Row, ObjectMeta, ObjectField, ObjectFieldOption, RateLimiterOptions, ScriptType, } from "@syncmatters/script-api";
|
|
6
|
+
export type { Cache, FileProvider, HttpRequest, HttpClientOptions, KvCollection, KvIterator, Logger, EventContext, TestResult, Row, ObjectMeta, ObjectField, ObjectFieldOption, RateLimiterOptions, ScriptType, JsonValuePath, JsonValuePathPart, QueryMatchFilter, QueryCheckpointFilter, EventTypeMeta, ObjectIndex, ObjectMetaUpsert, ObjectRowFilter, ObjectRelationship, ObjectFieldConstraints, ObjectSettingMeta, UpsertIssue, UpsertFieldOptionMeta, RowMatchRuleType, SyncSession, SyncOptimalBatchSizeOperation, SyncOptimalBatchSizeBatchType, } from "@syncmatters/script-api";
|
|
7
7
|
export * from "./lib/connector.js";
|
|
8
8
|
export * from "./lib/connector-error.js";
|
|
9
9
|
export { envSet } from "./lib/environment.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncmatters/connector-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
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": "
|
|
15
|
+
"typesContentHash": "f0f72980b64b81d6019dbfd86756b2c4dc6ff60eb44b4d1824bde2f8a689fd6b",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@types/node": "*",
|
|
18
|
-
"@syncmatters/script-api": "^1.0.
|
|
18
|
+
"@syncmatters/script-api": "^1.0.4"
|
|
19
19
|
}
|
|
20
20
|
}
|