@shopimind/integration-kit-js 1.0.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/LICENSE +10 -0
- package/README.md +118 -0
- package/dist/config/config-store.d.ts +6 -0
- package/dist/config/config-store.js +56 -0
- package/dist/contracts/common.d.ts +11 -0
- package/dist/contracts/common.js +1 -0
- package/dist/contracts/config-schema.d.ts +79 -0
- package/dist/contracts/config-schema.js +1 -0
- package/dist/contracts/index.d.ts +11 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/lifecycle.d.ts +59 -0
- package/dist/contracts/lifecycle.js +1 -0
- package/dist/contracts/sdk.d.ts +68 -0
- package/dist/contracts/sdk.js +6 -0
- package/dist/contracts/widget.d.ts +70 -0
- package/dist/contracts/widget.js +1 -0
- package/dist/http/routes.d.ts +18 -0
- package/dist/http/routes.js +150 -0
- package/dist/http/server.d.ts +7 -0
- package/dist/http/server.js +19 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +47 -0
- package/dist/integration/define-integration.d.ts +16 -0
- package/dist/integration/define-integration.js +50 -0
- package/dist/integration/types.d.ts +148 -0
- package/dist/integration/types.js +1 -0
- package/dist/lifecycle/dispatcher.d.ts +33 -0
- package/dist/lifecycle/dispatcher.js +315 -0
- package/dist/lifecycle/inbound.d.ts +50 -0
- package/dist/lifecycle/inbound.js +124 -0
- package/dist/logging/logger.d.ts +23 -0
- package/dist/logging/logger.js +23 -0
- package/dist/manifest.d.ts +52 -0
- package/dist/manifest.js +36 -0
- package/dist/provisioning/ensure.d.ts +24 -0
- package/dist/provisioning/ensure.js +104 -0
- package/dist/provisioning/runner.d.ts +16 -0
- package/dist/provisioning/runner.js +49 -0
- package/dist/runtime/create-app.d.ts +66 -0
- package/dist/runtime/create-app.js +211 -0
- package/dist/runtime/rate-limiter.d.ts +19 -0
- package/dist/runtime/rate-limiter.js +46 -0
- package/dist/sdk/send-bulk.d.ts +46 -0
- package/dist/sdk/send-bulk.js +40 -0
- package/dist/sdk/source-scope.d.ts +38 -0
- package/dist/sdk/source-scope.js +34 -0
- package/dist/security/crypto.d.ts +19 -0
- package/dist/security/crypto.js +82 -0
- package/dist/security/redaction.d.ts +15 -0
- package/dist/security/redaction.js +56 -0
- package/dist/security/signature.d.ts +31 -0
- package/dist/security/signature.js +30 -0
- package/dist/store/db.d.ts +7 -0
- package/dist/store/db.js +22 -0
- package/dist/store/migrate.d.ts +10 -0
- package/dist/store/migrate.js +35 -0
- package/dist/store/migrations.d.ts +27 -0
- package/dist/store/migrations.js +128 -0
- package/dist/store/repositories.d.ts +102 -0
- package/dist/store/repositories.js +281 -0
- package/dist/store/types.d.ts +62 -0
- package/dist/store/types.js +1 -0
- package/dist/sync/concurrency.d.ts +12 -0
- package/dist/sync/concurrency.js +30 -0
- package/dist/sync/cursor.d.ts +16 -0
- package/dist/sync/cursor.js +14 -0
- package/dist/sync/engine.d.ts +49 -0
- package/dist/sync/engine.js +129 -0
- package/dist/sync/paginate.d.ts +14 -0
- package/dist/sync/paginate.js +42 -0
- package/dist/testing/harness.d.ts +49 -0
- package/dist/testing/harness.js +110 -0
- package/package.json +51 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type SpmHttpClient } from '@shopimind/sdk-js';
|
|
2
|
+
import { type IntegrationApp } from '../runtime/create-app.js';
|
|
3
|
+
import type { Integration } from '../integration/types.js';
|
|
4
|
+
export interface TestAppOptions {
|
|
5
|
+
/** Simulated SDK client (default: an offline stub returning empty ok responses). */
|
|
6
|
+
spm?: SpmHttpClient;
|
|
7
|
+
secret?: string;
|
|
8
|
+
/** Fixed clock (default). */
|
|
9
|
+
now?: () => number;
|
|
10
|
+
}
|
|
11
|
+
export interface TestApp extends IntegrationApp {
|
|
12
|
+
/** Signs a webhook payload (body + headers ready for `server.inject`). */
|
|
13
|
+
signWebhook(payload: object, ts?: number): {
|
|
14
|
+
body: string;
|
|
15
|
+
headers: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
/** Signs an INBOUND call (route /inbound) with the per-installation secret. */
|
|
18
|
+
signInbound(installationId: string, payload: object, ts?: number): {
|
|
19
|
+
body: string;
|
|
20
|
+
headers: Record<string, string>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Builds a test app: in-memory store, stub SDK client, webhook signing. */
|
|
24
|
+
export declare function makeTestApp<S>(integration: Integration<S>, opts?: TestAppOptions): TestApp;
|
|
25
|
+
/**
|
|
26
|
+
* Offline STUB SDK client: a real client whose axios adapter is replaced by a
|
|
27
|
+
* function returning empty `ok` envelopes (empty lists, create→id 1, bulk→counters
|
|
28
|
+
* at 0). Enough to exercise the lifecycle / provisioning without network access.
|
|
29
|
+
* Can be overridden via `opts.spm`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function makeStubSpmClient(): SpmHttpClient;
|
|
32
|
+
/** Request seen by a scriptable stub (request body already deserialized). */
|
|
33
|
+
export type SpmStubRequest = {
|
|
34
|
+
method: string;
|
|
35
|
+
url: string;
|
|
36
|
+
body: any;
|
|
37
|
+
};
|
|
38
|
+
/** Scripted reply: `body` = RAW HTTP body. `status >= 400` → error (axios rejection). */
|
|
39
|
+
export interface SpmStubReply {
|
|
40
|
+
status?: number;
|
|
41
|
+
body?: unknown;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* SCRIPTABLE STUB SDK client: the axios adapter delegates to `handler(method,url,body)`.
|
|
45
|
+
* Statuses < 300 resolve; statuses >= 400 REJECT like axios (the SDK then encodes
|
|
46
|
+
* `{ ok:false, statusCode }`). Required to test error paths (4xx/5xx, 409 idempotent)
|
|
47
|
+
* that `makeStubSpmClient` (always 200) does not cover.
|
|
48
|
+
*/
|
|
49
|
+
export declare function makeScriptedSpmClient(handler: (req: SpmStubRequest) => SpmStubReply): SpmHttpClient;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { SpmClient } from '@shopimind/sdk-js';
|
|
3
|
+
import { createIntegrationApp } from '../runtime/create-app.js';
|
|
4
|
+
import { createLogger } from '../logging/logger.js';
|
|
5
|
+
import { signShopimindBody } from '../security/signature.js';
|
|
6
|
+
import { ensureInboundSecret } from '../lifecycle/inbound.js';
|
|
7
|
+
/** Builds a test app: in-memory store, stub SDK client, webhook signing. */
|
|
8
|
+
export function makeTestApp(integration, opts = {}) {
|
|
9
|
+
// Guard: this harness is NEVER intended for production (test keys/server).
|
|
10
|
+
if (process.env.NODE_ENV === 'production') {
|
|
11
|
+
throw new Error('makeTestApp() is for tests only and forbidden in production');
|
|
12
|
+
}
|
|
13
|
+
const secret = opts.secret ?? 'test_' + randomBytes(16).toString('hex');
|
|
14
|
+
const fixedNow = opts.now ?? (() => 1_700_000_000_000);
|
|
15
|
+
const stub = opts.spm ?? makeStubSpmClient();
|
|
16
|
+
const app = createIntegrationApp(integration, {
|
|
17
|
+
databasePath: ':memory:',
|
|
18
|
+
webhookSecret: secret,
|
|
19
|
+
credentialsKey: randomBytes(32).toString('hex'),
|
|
20
|
+
makeSpmClient: () => stub,
|
|
21
|
+
autoBackfillOnActivate: false,
|
|
22
|
+
autoSync: false,
|
|
23
|
+
now: fixedNow,
|
|
24
|
+
logger: createLogger({ sink: () => { } }),
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
...app,
|
|
28
|
+
signWebhook(payload, ts = Math.floor(fixedNow() / 1000)) {
|
|
29
|
+
const body = JSON.stringify(payload);
|
|
30
|
+
return {
|
|
31
|
+
body,
|
|
32
|
+
headers: {
|
|
33
|
+
'content-type': 'application/json',
|
|
34
|
+
'x-shopimind-timestamp': String(ts),
|
|
35
|
+
'x-shopimind-signature': signShopimindBody(body, secret, ts),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
signInbound(installationId, payload, ts = Math.floor(fixedNow() / 1000)) {
|
|
40
|
+
const inboundSecret = ensureInboundSecret(app.repos.state, installationId);
|
|
41
|
+
const body = JSON.stringify(payload);
|
|
42
|
+
return {
|
|
43
|
+
body,
|
|
44
|
+
headers: {
|
|
45
|
+
'content-type': 'application/json',
|
|
46
|
+
'x-integration-installation': installationId,
|
|
47
|
+
'x-integration-timestamp': String(ts),
|
|
48
|
+
'x-integration-signature': signShopimindBody(body, inboundSecret, ts),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Offline STUB SDK client: a real client whose axios adapter is replaced by a
|
|
56
|
+
* function returning empty `ok` envelopes (empty lists, create→id 1, bulk→counters
|
|
57
|
+
* at 0). Enough to exercise the lifecycle / provisioning without network access.
|
|
58
|
+
* Can be overridden via `opts.spm`.
|
|
59
|
+
*/
|
|
60
|
+
export function makeStubSpmClient() {
|
|
61
|
+
const client = SpmClient.getClient('v1', 'test', { baseUrl: 'http://localhost', retry: false, labelSource: null });
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
client.defaults.adapter = async (config) => {
|
|
64
|
+
const method = (config.method ?? 'get').toLowerCase();
|
|
65
|
+
const url = config.url ?? '';
|
|
66
|
+
const body = stubBody(method, url);
|
|
67
|
+
return { data: { statusCode: 200, data: body }, status: 200, statusText: 'OK', headers: {}, config };
|
|
68
|
+
};
|
|
69
|
+
return client;
|
|
70
|
+
}
|
|
71
|
+
function stubBody(method, url) {
|
|
72
|
+
if (method === 'get') {
|
|
73
|
+
if (/custom-data-definitions\/\d+$/.test(url))
|
|
74
|
+
return { id_definition: 1, name: 'noop', fields: [] };
|
|
75
|
+
return []; // list endpoints
|
|
76
|
+
}
|
|
77
|
+
if (url === 'data-sources')
|
|
78
|
+
return { id_data_source: 1, label: 'noop', type: 'api' };
|
|
79
|
+
if (url === 'custom-data-definitions')
|
|
80
|
+
return { id_definition: 1, name: 'noop' };
|
|
81
|
+
// bulk-save / other writes → neutral counters
|
|
82
|
+
return { sent_count: 0, rejected_count: 0, failed_count: 0, rejected_items: [] };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* SCRIPTABLE STUB SDK client: the axios adapter delegates to `handler(method,url,body)`.
|
|
86
|
+
* Statuses < 300 resolve; statuses >= 400 REJECT like axios (the SDK then encodes
|
|
87
|
+
* `{ ok:false, statusCode }`). Required to test error paths (4xx/5xx, 409 idempotent)
|
|
88
|
+
* that `makeStubSpmClient` (always 200) does not cover.
|
|
89
|
+
*/
|
|
90
|
+
export function makeScriptedSpmClient(handler) {
|
|
91
|
+
const client = SpmClient.getClient('v1', 'test', { baseUrl: 'http://localhost', retry: false, labelSource: null });
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
client.defaults.adapter = async (config) => {
|
|
94
|
+
const method = String(config.method ?? 'get').toLowerCase();
|
|
95
|
+
const url = String(config.url ?? '');
|
|
96
|
+
const body = config.data ? JSON.parse(config.data) : undefined;
|
|
97
|
+
const r = handler({ method, url, body });
|
|
98
|
+
const status = r.status ?? 200;
|
|
99
|
+
const response = { data: r.body ?? {}, status, statusText: '', headers: {}, config };
|
|
100
|
+
if (status >= 200 && status < 300)
|
|
101
|
+
return response;
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
|
+
const err = new Error(`Request failed with status code ${status}`);
|
|
104
|
+
err.isAxiosError = true;
|
|
105
|
+
err.response = response;
|
|
106
|
+
err.config = config;
|
|
107
|
+
throw err;
|
|
108
|
+
};
|
|
109
|
+
return client;
|
|
110
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shopimind/integration-kit-js",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/shopimind/integration-kit-js.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.17.0"
|
|
27
|
+
},
|
|
28
|
+
"volta": {
|
|
29
|
+
"node": "18.20.8",
|
|
30
|
+
"yarn": "1.22.22"
|
|
31
|
+
},
|
|
32
|
+
"packageManager": "yarn@1.22.22",
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -b",
|
|
35
|
+
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest",
|
|
38
|
+
"clean": "tsc -b --clean"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@hapi/hapi": "^21.3.10",
|
|
42
|
+
"@shopimind/sdk-js": "^1.0.0",
|
|
43
|
+
"better-sqlite3": "^11.3.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
47
|
+
"@types/node": "^18.19.0",
|
|
48
|
+
"typescript": "^5.6.2",
|
|
49
|
+
"vitest": "^2.1.0"
|
|
50
|
+
}
|
|
51
|
+
}
|