@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.
Files changed (73) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +118 -0
  3. package/dist/config/config-store.d.ts +6 -0
  4. package/dist/config/config-store.js +56 -0
  5. package/dist/contracts/common.d.ts +11 -0
  6. package/dist/contracts/common.js +1 -0
  7. package/dist/contracts/config-schema.d.ts +79 -0
  8. package/dist/contracts/config-schema.js +1 -0
  9. package/dist/contracts/index.d.ts +11 -0
  10. package/dist/contracts/index.js +1 -0
  11. package/dist/contracts/lifecycle.d.ts +59 -0
  12. package/dist/contracts/lifecycle.js +1 -0
  13. package/dist/contracts/sdk.d.ts +68 -0
  14. package/dist/contracts/sdk.js +6 -0
  15. package/dist/contracts/widget.d.ts +70 -0
  16. package/dist/contracts/widget.js +1 -0
  17. package/dist/http/routes.d.ts +18 -0
  18. package/dist/http/routes.js +150 -0
  19. package/dist/http/server.d.ts +7 -0
  20. package/dist/http/server.js +19 -0
  21. package/dist/index.d.ts +36 -0
  22. package/dist/index.js +47 -0
  23. package/dist/integration/define-integration.d.ts +16 -0
  24. package/dist/integration/define-integration.js +50 -0
  25. package/dist/integration/types.d.ts +148 -0
  26. package/dist/integration/types.js +1 -0
  27. package/dist/lifecycle/dispatcher.d.ts +33 -0
  28. package/dist/lifecycle/dispatcher.js +315 -0
  29. package/dist/lifecycle/inbound.d.ts +50 -0
  30. package/dist/lifecycle/inbound.js +124 -0
  31. package/dist/logging/logger.d.ts +23 -0
  32. package/dist/logging/logger.js +23 -0
  33. package/dist/manifest.d.ts +52 -0
  34. package/dist/manifest.js +36 -0
  35. package/dist/provisioning/ensure.d.ts +24 -0
  36. package/dist/provisioning/ensure.js +104 -0
  37. package/dist/provisioning/runner.d.ts +16 -0
  38. package/dist/provisioning/runner.js +49 -0
  39. package/dist/runtime/create-app.d.ts +66 -0
  40. package/dist/runtime/create-app.js +211 -0
  41. package/dist/runtime/rate-limiter.d.ts +19 -0
  42. package/dist/runtime/rate-limiter.js +46 -0
  43. package/dist/sdk/send-bulk.d.ts +46 -0
  44. package/dist/sdk/send-bulk.js +40 -0
  45. package/dist/sdk/source-scope.d.ts +38 -0
  46. package/dist/sdk/source-scope.js +34 -0
  47. package/dist/security/crypto.d.ts +19 -0
  48. package/dist/security/crypto.js +82 -0
  49. package/dist/security/redaction.d.ts +15 -0
  50. package/dist/security/redaction.js +56 -0
  51. package/dist/security/signature.d.ts +31 -0
  52. package/dist/security/signature.js +30 -0
  53. package/dist/store/db.d.ts +7 -0
  54. package/dist/store/db.js +22 -0
  55. package/dist/store/migrate.d.ts +10 -0
  56. package/dist/store/migrate.js +35 -0
  57. package/dist/store/migrations.d.ts +27 -0
  58. package/dist/store/migrations.js +128 -0
  59. package/dist/store/repositories.d.ts +102 -0
  60. package/dist/store/repositories.js +281 -0
  61. package/dist/store/types.d.ts +62 -0
  62. package/dist/store/types.js +1 -0
  63. package/dist/sync/concurrency.d.ts +12 -0
  64. package/dist/sync/concurrency.js +30 -0
  65. package/dist/sync/cursor.d.ts +16 -0
  66. package/dist/sync/cursor.js +14 -0
  67. package/dist/sync/engine.d.ts +49 -0
  68. package/dist/sync/engine.js +129 -0
  69. package/dist/sync/paginate.d.ts +14 -0
  70. package/dist/sync/paginate.js +42 -0
  71. package/dist/testing/harness.d.ts +49 -0
  72. package/dist/testing/harness.js +110 -0
  73. 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
+ }