@shopimind/integration-kit-js 1.0.0 → 1.2.0
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/dist/contracts/sdk.d.ts +11 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/integration/types.d.ts +8 -0
- package/dist/lifecycle/dispatcher.js +5 -0
- package/dist/runtime/create-app.js +3 -0
- package/dist/sdk/custom-data-scope.d.ts +24 -0
- package/dist/sdk/custom-data-scope.js +32 -0
- package/dist/sync/engine.d.ts +6 -0
- package/dist/sync/engine.js +1 -0
- package/package.json +1 -1
package/dist/contracts/sdk.d.ts
CHANGED
|
@@ -37,9 +37,18 @@ export interface NewCustomDataDefinition {
|
|
|
37
37
|
description?: string;
|
|
38
38
|
unique_keys?: string[];
|
|
39
39
|
fields: NewCustomDataField[];
|
|
40
|
+
/**
|
|
41
|
+
* Schema-level relationships to other definitions (forwarded as-is to the API).
|
|
42
|
+
* `sourceField` is a field of THIS definition; it links to `targetSchema` — a
|
|
43
|
+
* `'system'` schema (e.g. `contacts`, `products`) or another `'custom'` definition
|
|
44
|
+
* (by its numeric id) — optionally matched on `targetField` (defaults to the
|
|
45
|
+
* target's id). Mirrors the API's relationship DTO; `custom`→`custom` is supported.
|
|
46
|
+
*/
|
|
40
47
|
relationships?: Array<{
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
sourceField: string;
|
|
49
|
+
targetSchemaType: 'system' | 'custom';
|
|
50
|
+
targetSchema: string;
|
|
51
|
+
targetField?: string;
|
|
43
52
|
}>;
|
|
44
53
|
}
|
|
45
54
|
/** Order status to provision (declaration). */
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from './sync/cursor.js';
|
|
|
13
13
|
export * from './sync/engine.js';
|
|
14
14
|
export * from '@shopimind/sdk-js';
|
|
15
15
|
export * from './sdk/source-scope.js';
|
|
16
|
+
export * from './sdk/custom-data-scope.js';
|
|
16
17
|
export type { BulkResult, SendBulk, SendBulkOptions } from './sdk/send-bulk.js';
|
|
17
18
|
export * from './provisioning/ensure.js';
|
|
18
19
|
export * from './provisioning/runner.js';
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,8 @@ export * from './sync/engine.js';
|
|
|
18
18
|
export * from '@shopimind/sdk-js';
|
|
19
19
|
// Provisioned source helper (`ctx.withSource` -> SourceHandle).
|
|
20
20
|
export * from './sdk/source-scope.js';
|
|
21
|
+
// Provisioned custom data helper (`ctx.customData` -> CustomDataHandle).
|
|
22
|
+
export * from './sdk/custom-data-scope.js';
|
|
21
23
|
// Idempotent provisioning + plan runner
|
|
22
24
|
export * from './provisioning/ensure.js';
|
|
23
25
|
export * from './provisioning/runner.js';
|
|
@@ -2,6 +2,7 @@ import type { ConfigSchema, RawConfigs, WidgetDeclaration, NewDataSource, NewCus
|
|
|
2
2
|
import type { SpmHttpClient } from '@shopimind/sdk-js';
|
|
3
3
|
import type { SourceHandle } from '../sdk/source-scope.js';
|
|
4
4
|
import type { SendBulk } from '../sdk/send-bulk.js';
|
|
5
|
+
import type { CustomDataHandle } from '../sdk/custom-data-scope.js';
|
|
5
6
|
import type { IntegrationStateRepo } from '../store/repositories.js';
|
|
6
7
|
import type { CursorRow } from '../store/types.js';
|
|
7
8
|
import type { Logger } from '../logging/logger.js';
|
|
@@ -58,6 +59,13 @@ export interface IntegrationContext<S> {
|
|
|
58
59
|
* (Also namespace your identifiers: the source alone does not isolate them.)
|
|
59
60
|
*/
|
|
60
61
|
withSource(sourceKey: string): SourceHandle;
|
|
62
|
+
/**
|
|
63
|
+
* Handle for a PROVISIONED custom data definition (declared in
|
|
64
|
+
* `provisioning.customData`): its numeric `id` plus a safe `save(records)`.
|
|
65
|
+
* `name` must match a definition declared in `provisioning.customData`.
|
|
66
|
+
* The custom-data counterpart of {@link IntegrationContext.withSource}.
|
|
67
|
+
*/
|
|
68
|
+
customData(name: string): CustomDataHandle;
|
|
61
69
|
}
|
|
62
70
|
export interface SyncWindow {
|
|
63
71
|
since: Date | null;
|
|
@@ -6,6 +6,7 @@ import { runProvisioning } from '../provisioning/runner.js';
|
|
|
6
6
|
import { ensureInboundSecret } from './inbound.js';
|
|
7
7
|
import { makeWithSource } from '../sdk/source-scope.js';
|
|
8
8
|
import { makeSendBulk } from '../sdk/send-bulk.js';
|
|
9
|
+
import { makeCustomData } from '../sdk/custom-data-scope.js';
|
|
9
10
|
export const ACCESS_TOKEN_KEY = '__access_token';
|
|
10
11
|
/** State key where the provisioning result (sourceIds/defIds) is stored. */
|
|
11
12
|
export const PROVISIONING_KEY = '__provisioning';
|
|
@@ -247,6 +248,7 @@ function buildContext(id, deps) {
|
|
|
247
248
|
setExternalAccount: (acc) => deps.repos.installs.setExternalAccount(id, acc.id, acc.name ?? null),
|
|
248
249
|
inboundSecret: ensureInboundSecret(deps.repos.state, id),
|
|
249
250
|
withSource: makeWithSource(deps.repos.state, id, PROVISIONING_KEY, sendBulk),
|
|
251
|
+
customData: makeCustomData(deps.repos.state, id, PROVISIONING_KEY, sendBulk, spm),
|
|
250
252
|
};
|
|
251
253
|
}
|
|
252
254
|
/** Context without an installation (config wizard): the ShopiMind SDK is not callable here. */
|
|
@@ -264,6 +266,9 @@ function ephemeralContext(configs, deps) {
|
|
|
264
266
|
withSource: () => {
|
|
265
267
|
throw new Error('withSource is unavailable during the configuration wizard (no installation)');
|
|
266
268
|
},
|
|
269
|
+
customData: () => {
|
|
270
|
+
throw new Error('customData is unavailable during the configuration wizard (no installation)');
|
|
271
|
+
},
|
|
267
272
|
};
|
|
268
273
|
}
|
|
269
274
|
async function runHookSafe(hook, id, deps) {
|
|
@@ -8,6 +8,7 @@ import { runIntegrationSync } from '../sync/engine.js';
|
|
|
8
8
|
import { ACCESS_TOKEN_KEY, PROVISIONING_KEY } from '../lifecycle/dispatcher.js';
|
|
9
9
|
import { ensureInboundSecret } from '../lifecycle/inbound.js';
|
|
10
10
|
import { makeWithSource } from '../sdk/source-scope.js';
|
|
11
|
+
import { makeCustomData } from '../sdk/custom-data-scope.js';
|
|
11
12
|
import { makeSendBulk } from '../sdk/send-bulk.js';
|
|
12
13
|
import { createRateLimiter } from './rate-limiter.js';
|
|
13
14
|
import { createServer } from '../http/server.js';
|
|
@@ -52,6 +53,7 @@ export function createIntegrationApp(integration, opts) {
|
|
|
52
53
|
setExternalAccount: (acc) => repos.installs.setExternalAccount(id, acc.id, acc.name ?? null),
|
|
53
54
|
inboundSecret: ensureInboundSecret(repos.state, id),
|
|
54
55
|
withSource: makeWithSource(repos.state, id, PROVISIONING_KEY, sendBulk),
|
|
56
|
+
customData: makeCustomData(repos.state, id, PROVISIONING_KEY, sendBulk, spm),
|
|
55
57
|
};
|
|
56
58
|
};
|
|
57
59
|
// Per-installation overlap lock: prevents a post-activation backfill, a scheduled
|
|
@@ -74,6 +76,7 @@ export function createIntegrationApp(integration, opts) {
|
|
|
74
76
|
cursors: repos.cursors,
|
|
75
77
|
runs: repos.runs,
|
|
76
78
|
makeSource: (sb) => makeWithSource(repos.state, id, PROVISIONING_KEY, sb),
|
|
79
|
+
makeCustomData: (sb) => makeCustomData(repos.state, id, PROVISIONING_KEY, sb, base.spm),
|
|
77
80
|
}, { fullBackfill: o?.full ?? false, backfillDays });
|
|
78
81
|
}
|
|
79
82
|
finally {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type SpmHttpClient } from '@shopimind/sdk-js';
|
|
2
|
+
import type { IntegrationStateRepo } from '../store/repositories.js';
|
|
3
|
+
import type { BulkResult, SendBulk, SendBulkOptions } from './send-bulk.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handle for a PROVISIONED custom data definition. Exposes its numeric `id`
|
|
6
|
+
* (resolved by name) plus a `save()` that upserts records into it through the
|
|
7
|
+
* safe bulk primitive (chunked, throws on a transport failure, surfaces per-item
|
|
8
|
+
* rejections). The counterpart of {@link SourceHandle} for custom data: a data
|
|
9
|
+
* source tags base entities, whereas a custom data definition stores its own
|
|
10
|
+
* records — so the handle exposes `save()` rather than `tag()`.
|
|
11
|
+
*/
|
|
12
|
+
export interface CustomDataHandle {
|
|
13
|
+
/** Numeric `id` of the provisioned custom data definition (resolved by name). */
|
|
14
|
+
readonly id: number;
|
|
15
|
+
/** Safe upsert of records into this definition (matched by its unique keys). */
|
|
16
|
+
save<T extends object>(records: T[], opts?: SendBulkOptions): Promise<BulkResult>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Builds `ctx.customData`: resolves the numeric id of a PROVISIONED custom data
|
|
20
|
+
* definition (from the integration state, by `name`) and returns a
|
|
21
|
+
* {@link CustomDataHandle}. Throws if the definition was not declared in
|
|
22
|
+
* `provisioning.customData` (the symmetric guard of {@link makeWithSource}).
|
|
23
|
+
*/
|
|
24
|
+
export declare function makeCustomData(state: IntegrationStateRepo, installationId: string, provisioningKey: string, sendBulk: SendBulk, spm: SpmHttpClient): (name: string) => CustomDataHandle;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SpmCustomDataRecords } from '@shopimind/sdk-js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds `ctx.customData`: resolves the numeric id of a PROVISIONED custom data
|
|
4
|
+
* definition (from the integration state, by `name`) and returns a
|
|
5
|
+
* {@link CustomDataHandle}. Throws if the definition was not declared in
|
|
6
|
+
* `provisioning.customData` (the symmetric guard of {@link makeWithSource}).
|
|
7
|
+
*/
|
|
8
|
+
export function makeCustomData(state, installationId, provisioningKey, sendBulk, spm) {
|
|
9
|
+
return (name) => {
|
|
10
|
+
const raw = state.get(installationId, provisioningKey);
|
|
11
|
+
// Parse defensively: a corrupt/unreadable persisted blob is treated as
|
|
12
|
+
// "nothing provisioned" and surfaces as the business error below, never as
|
|
13
|
+
// an opaque SyntaxError.
|
|
14
|
+
let defIds = {};
|
|
15
|
+
if (raw) {
|
|
16
|
+
try {
|
|
17
|
+
defIds = JSON.parse(raw).defIds ?? {};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
defIds = {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const id = defIds[name];
|
|
24
|
+
if (id == null) {
|
|
25
|
+
throw new Error(`customData("${name}"): custom data definition not provisioned — declare it in provisioning.customData`);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
id,
|
|
29
|
+
save: (records, opts) => sendBulk(() => SpmCustomDataRecords.bulkSave(spm, id, records, { chunk: true, ...opts })),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/sync/engine.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CursorRepo, RunRepo } from '../store/repositories.js';
|
|
2
2
|
import type { Integration, IntegrationContext, SyncWindow } from '../integration/types.js';
|
|
3
3
|
import type { SourceHandle } from '../sdk/source-scope.js';
|
|
4
|
+
import type { CustomDataHandle } from '../sdk/custom-data-scope.js';
|
|
4
5
|
import { type SendBulk } from '../sdk/send-bulk.js';
|
|
5
6
|
export interface SyncOptions {
|
|
6
7
|
fullBackfill?: boolean;
|
|
@@ -32,6 +33,11 @@ export interface SyncDeps {
|
|
|
32
33
|
* runtime, which knows the installation + provisioning state.
|
|
33
34
|
*/
|
|
34
35
|
makeSource: (sendBulk: SendBulk) => (sourceKey: string) => SourceHandle;
|
|
36
|
+
/**
|
|
37
|
+
* Builds `customData` bound to a given `sendBulk`, so `customData(name).save(...)`
|
|
38
|
+
* inside a step feeds the step's reject accumulator.
|
|
39
|
+
*/
|
|
40
|
+
makeCustomData: (sendBulk: SendBulk) => (name: string) => CustomDataHandle;
|
|
35
41
|
}
|
|
36
42
|
/**
|
|
37
43
|
* Runs the enabled sync steps of an integration. The cursor is managed HERE,
|
package/dist/sync/engine.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopimind/integration-kit-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Foundation for building ShopiMind integrations: a runtime plus typed, once-tested primitives (HMAC webhook signatures, encryption, log redaction, a safe cursor-based sync engine, pagination/concurrency, persistence, ShopiMind SDK re-export, idempotent provisioning, secured inbound middleware, HTTP server). An integration only writes pure functions and declarations passed to defineIntegration.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|