@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.
@@ -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. **For every object, strive to support all five; omit a flag only after the
148
- workaround patterns below have failed**, and note why in a comment so the next author doesn't
149
- re-investigate:
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 queryState = options.queryState || { offset: 0, limit: 200, rowCount: 0, startTime: Date.now() };
12
+ const requestedLimit = options.queryOptions?.limit;
13
+ const queryState = options.queryState || { offset: 0, pageSize: 200, rowCount: 0, startTime: Date.now() };
13
14
 
14
- const resp = await this.#call(this.#buildRequest(options, queryState));
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
- const rows = resp.records.map((r) => ({ meta: { key: String(r.id) }, data: r }));
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 < queryState.limit ||
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
- - Respect `options.queryOptions.limit`: stop at it (`finalPage: true`) and trim overflow rows
44
- from the last page.
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)` — keep the cache current
96
+ ## `handleParsedEvent(options)` — optional cache hint, usually skipped
97
97
 
98
- After your events fire, the platform calls `handleParsedEvent` per affected object so the
99
- cached data can be updated without a full re-query:
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: either fetch the row and report it, or report nothing and let the
111
- // next differential query pick it up - fetching is better for near-real-time triggers.
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
- `options.event` = `{ id, timestamp, key?, data? }`; `reportChanges({ id, page })` takes a normal
116
- `QueryPage`.
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 → `reportChanges`
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
- const params = [`limit=${queryState.limit}`];
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()`, signature verification |
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()` / `handleParsedEvent()` | receive webhooks and keep caches fresh |
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.4",
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": "b47810ca30415f646b1055512fb4e342e48f3cd7459411c2dabcc7ca29959271",
15
+ "typesContentHash": "f0f72980b64b81d6019dbfd86756b2c4dc6ff60eb44b4d1824bde2f8a689fd6b",
16
16
  "dependencies": {
17
17
  "@types/node": "*",
18
- "@syncmatters/script-api": "^1.0.3"
18
+ "@syncmatters/script-api": "^1.0.4"
19
19
  }
20
20
  }