@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
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (c) 2026 ShopiMind. Tous droits réservés / All rights reserved.
|
|
2
|
+
|
|
3
|
+
FR — Ce dépôt est rendu public à des fins de transparence et de démonstration
|
|
4
|
+
uniquement. Aucune licence d'utilisation, de copie, de modification, de
|
|
5
|
+
distribution ou de création d'œuvres dérivées n'est accordée, en tout ou en
|
|
6
|
+
partie, sans l'accord écrit préalable de ShopiMind.
|
|
7
|
+
|
|
8
|
+
EN — This repository is made public for transparency and demonstration purposes
|
|
9
|
+
only. No license is granted to use, copy, modify, distribute, or create
|
|
10
|
+
derivative works of any part of it without ShopiMind's prior written consent.
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @shopimind/integration-kit-js
|
|
2
|
+
|
|
3
|
+
[](https://github.com/shopimind/integration-kit-js/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
The toolkit for **building a ShopiMind integration**. You write your business logic; the kit provides all the infrastructure.
|
|
6
|
+
|
|
7
|
+
An integration has to receive lifecycle webhooks, sync data reliably, expose widgets, and talk to the ShopiMind API securely. With this kit you only declare **what is specific to your system** (pure functions + declarations) — the rest is provided and tested once.
|
|
8
|
+
|
|
9
|
+
**The kit handles for you:**
|
|
10
|
+
- 🔐 Secured webhooks — HMAC signature verification + anti-replay
|
|
11
|
+
- 🔄 Incremental, **cursor-safe** sync — no data loss on error
|
|
12
|
+
- 🌊 Streaming pagination + bounded concurrency — no memory blow-up, no request bursts (429)
|
|
13
|
+
- 💾 Local persistence (SQLite) with **encrypted secrets** at rest
|
|
14
|
+
- 🔌 **Typed** ShopiMind API client (the SDK, re-exported)
|
|
15
|
+
- ⚙️ **Idempotent** provisioning of data sources, custom data and events
|
|
16
|
+
- 🌐 HTTP server + lifecycle handling (install / activate / config / sync)
|
|
17
|
+
- 🧩 Widget declarations
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
Node.js 18+ and TypeScript (ESM / `NodeNext`).
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm i @shopimind/integration-kit-js
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The ShopiMind SDK is a dependency and is **re-exported**, so you can import SDK resources directly from the kit.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
### 1. Describe your integration
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// integration.ts
|
|
37
|
+
import { defineIntegration } from '@shopimind/integration-kit-js';
|
|
38
|
+
|
|
39
|
+
export const integration = defineIntegration({
|
|
40
|
+
slug: 'my-pos',
|
|
41
|
+
meta: { name: 'My POS', version: '1.0.0' },
|
|
42
|
+
|
|
43
|
+
configSchema: { steps: [ /* the configuration form shown to the merchant */ ] },
|
|
44
|
+
parseSettings: (raw) => ({ /* your typed settings */ }),
|
|
45
|
+
|
|
46
|
+
testConnection: async (ctx) => {
|
|
47
|
+
// check access to the partner system with ctx.settings
|
|
48
|
+
return true;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
provisioning: async (ctx) => ({
|
|
52
|
+
dataSources: [ /* ... */ ],
|
|
53
|
+
customData: [ /* ... */ ],
|
|
54
|
+
events: [ /* ... */ ],
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
widgets: [ /* widgets exposed in the ShopiMind editor */ ],
|
|
58
|
+
|
|
59
|
+
syncSteps: [
|
|
60
|
+
{
|
|
61
|
+
entity: 'customers',
|
|
62
|
+
cursorScope: 'global',
|
|
63
|
+
enabled: (s) => s.syncCustomers,
|
|
64
|
+
run: async (ctx) => {
|
|
65
|
+
// ctx.spm.* (ShopiMind API), ctx.paginate(...), ctx.state, ctx.logger...
|
|
66
|
+
return { items: 0, errors: [] };
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
// Integration<S> types every field (strict TypeScript).
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. Run the app
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// main.ts
|
|
78
|
+
import { createIntegrationApp } from '@shopimind/integration-kit-js';
|
|
79
|
+
import { integration } from './integration.js';
|
|
80
|
+
|
|
81
|
+
const env = process.env;
|
|
82
|
+
|
|
83
|
+
const app = createIntegrationApp(integration, {
|
|
84
|
+
databasePath: env.DATABASE_PATH ?? './data/store.sqlite',
|
|
85
|
+
webhookSecret: env.WEBHOOK_SECRET!, // HMAC secret for ShopiMind webhooks
|
|
86
|
+
credentialsKey: env.CREDENTIALS_KEY, // 64-hex AES key used to encrypt stored secrets
|
|
87
|
+
port: env.PORT ? Number(env.PORT) : 8080,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
void app.start();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`createIntegrationApp` wires the HTTP server, the lifecycle dispatcher (signed webhooks), persistence and the sync engine — the kit builds the ShopiMind SDK client itself. You only have to deploy.
|
|
94
|
+
|
|
95
|
+
> **Encryption is fail-closed.** Without `credentialsKey`, startup fails — pass `allowPlaintextSecrets: true` to allow plaintext secret storage **for local development only**.
|
|
96
|
+
|
|
97
|
+
## The building blocks of an integration
|
|
98
|
+
|
|
99
|
+
| Field | Role |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `configSchema` | the configuration form shown to the merchant |
|
|
102
|
+
| `parseSettings` | turns the raw config into typed settings |
|
|
103
|
+
| `testConnection` | checks access to the partner system |
|
|
104
|
+
| `provisioning` | idempotently creates the data sources / custom data / events on the ShopiMind side |
|
|
105
|
+
| `widgets` | widgets exposed in the ShopiMind editor |
|
|
106
|
+
| `syncSteps` | the sync steps — **the cursor is managed by the kit** |
|
|
107
|
+
| `hooks` *(optional)* | lifecycle callbacks (`onActivate`, `onConfigUpdated`, ...) |
|
|
108
|
+
|
|
109
|
+
## Guarantees
|
|
110
|
+
|
|
111
|
+
- The **cursor only advances when a step finished without error** → no silent data loss; the window is replayed on the next run.
|
|
112
|
+
- **Partner secrets are encrypted** at rest and **redacted in logs**.
|
|
113
|
+
- **Pagination streams** and **concurrency is bounded** → controlled memory, no request bursts.
|
|
114
|
+
- **Webhooks are verified** (signature + anti-replay window) **before** any processing.
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
Source-available, proprietary — see [LICENSE](./LICENSE). You may use and modify the kit for your own use of the ShopiMind service; redistribution and independent use are not granted.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ConfigSchema, ConfigField, RawConfigs } from '../contracts/index.js';
|
|
2
|
+
import type { IntegrationStateRepo } from '../store/repositories.js';
|
|
3
|
+
export declare function collectFields(schema: ConfigSchema): ConfigField[];
|
|
4
|
+
export declare function sensitiveKeys(schema: ConfigSchema): string[];
|
|
5
|
+
export declare function saveConfigs(state: IntegrationStateRepo, id: string, schema: ConfigSchema, configs: RawConfigs): void;
|
|
6
|
+
export declare function loadConfigs(state: IntegrationStateRepo, id: string, schema: ConfigSchema): RawConfigs;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence of an installation's configuration. Fields declared `sensitive`
|
|
3
|
+
* are ENCRYPTED at rest (API key, etc.), the others are stored in plaintext.
|
|
4
|
+
* `loadConfigs` reconstructs the raw map for `integration.parseSettings`.
|
|
5
|
+
*/
|
|
6
|
+
const PLAIN_KEY = 'cfg';
|
|
7
|
+
const SECRET_PREFIX = 'cfgsec:';
|
|
8
|
+
export function collectFields(schema) {
|
|
9
|
+
const out = [];
|
|
10
|
+
if (schema.steps)
|
|
11
|
+
for (const s of schema.steps)
|
|
12
|
+
out.push(...s.fields);
|
|
13
|
+
if (schema.fields)
|
|
14
|
+
out.push(...schema.fields);
|
|
15
|
+
if (schema.groups)
|
|
16
|
+
for (const g of schema.groups)
|
|
17
|
+
out.push(...g.fields);
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
export function sensitiveKeys(schema) {
|
|
21
|
+
return collectFields(schema)
|
|
22
|
+
.filter((f) => f.sensitive === true)
|
|
23
|
+
.map((f) => f.key);
|
|
24
|
+
}
|
|
25
|
+
export function saveConfigs(state, id, schema, configs) {
|
|
26
|
+
const secret = new Set(sensitiveKeys(schema));
|
|
27
|
+
const plain = {};
|
|
28
|
+
for (const [k, v] of Object.entries(configs)) {
|
|
29
|
+
if (secret.has(k)) {
|
|
30
|
+
if (v != null && v !== '')
|
|
31
|
+
state.setSecret(id, SECRET_PREFIX + k, String(v));
|
|
32
|
+
else
|
|
33
|
+
state.delete(id, SECRET_PREFIX + k); // empty value -> actually erase the secret
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
plain[k] = v;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Also erase secrets whose key is no longer present in the payload
|
|
40
|
+
// (otherwise an old secret would survive indefinitely and be re-injected on load).
|
|
41
|
+
for (const k of secret) {
|
|
42
|
+
if (!(k in configs))
|
|
43
|
+
state.delete(id, SECRET_PREFIX + k);
|
|
44
|
+
}
|
|
45
|
+
state.set(id, PLAIN_KEY, JSON.stringify(plain));
|
|
46
|
+
}
|
|
47
|
+
export function loadConfigs(state, id, schema) {
|
|
48
|
+
const raw = state.get(id, PLAIN_KEY);
|
|
49
|
+
const plain = raw ? JSON.parse(raw) : {};
|
|
50
|
+
for (const k of sensitiveKeys(schema)) {
|
|
51
|
+
const v = state.get(id, SECRET_PREFIX + k);
|
|
52
|
+
if (v != null)
|
|
53
|
+
plain[k] = v;
|
|
54
|
+
}
|
|
55
|
+
return plain;
|
|
56
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Localized text, e.g. `{ fr: "Clients", en: "Customers" }`. */
|
|
2
|
+
export interface Localized {
|
|
3
|
+
fr?: string;
|
|
4
|
+
en?: string;
|
|
5
|
+
[lang: string]: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Raw map of configuration values received in lifecycle payloads.
|
|
9
|
+
* An integration transforms it into typed `Settings` via `parseSettings` (zod).
|
|
10
|
+
*/
|
|
11
|
+
export type RawConfigs = Record<string, unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Localized } from './common.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reference to a remote-data resolver for a dynamic select.
|
|
4
|
+
* The integration exposes a `remoteData[resource]` resolver; ShopiMind calls
|
|
5
|
+
* `POST /webhook/remote-data/{resource}` and populates the select with the options.
|
|
6
|
+
*
|
|
7
|
+
* A dynamic select is declared via a structured `remote: RemoteRef`; the three
|
|
8
|
+
* fields `resource`, `label_field` and `value_field` are required.
|
|
9
|
+
*/
|
|
10
|
+
export interface RemoteRef {
|
|
11
|
+
resource: string;
|
|
12
|
+
label_field: string;
|
|
13
|
+
value_field: string;
|
|
14
|
+
description_field?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Scalar field types of an installation form. */
|
|
17
|
+
export type ConfigFieldType = 'text' | 'password' | 'email' | 'url' | 'number' | 'checkbox' | 'textarea' | 'datetime';
|
|
18
|
+
export type ConfigSelectType = 'select' | 'multiselect';
|
|
19
|
+
export interface ConfigOption {
|
|
20
|
+
value: string;
|
|
21
|
+
label?: string | Localized;
|
|
22
|
+
}
|
|
23
|
+
interface ConfigFieldBase {
|
|
24
|
+
key: string;
|
|
25
|
+
required?: boolean;
|
|
26
|
+
label: Localized;
|
|
27
|
+
help?: Localized;
|
|
28
|
+
default?: string | number | boolean;
|
|
29
|
+
/** `integrator` = set by the integrator via the API, hidden from the merchant. */
|
|
30
|
+
owner?: 'merchant' | 'integrator';
|
|
31
|
+
/** Sensitive value -> encrypted at rest, never exposed to the front-end or widgets. */
|
|
32
|
+
sensitive?: boolean;
|
|
33
|
+
/** The field accepts a ShopiMind variable `{var=...}`. */
|
|
34
|
+
supports_variables?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** Scalar field (text, number, checkbox, date, ...). */
|
|
37
|
+
export interface ScalarConfigField extends ConfigFieldBase {
|
|
38
|
+
type: ConfigFieldType;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Select / multiselect field. Options are STATIC (`options`) OR dynamic
|
|
42
|
+
* (`remote: RemoteRef`). There is no other form — in particular, no `remote_data`.
|
|
43
|
+
*/
|
|
44
|
+
export interface SelectConfigField extends ConfigFieldBase {
|
|
45
|
+
type: ConfigSelectType;
|
|
46
|
+
options?: ConfigOption[];
|
|
47
|
+
remote?: RemoteRef;
|
|
48
|
+
}
|
|
49
|
+
export type ConfigField = ScalarConfigField | SelectConfigField;
|
|
50
|
+
/** Action triggered on step completion (e.g. validate the connection). */
|
|
51
|
+
export interface StepAction {
|
|
52
|
+
action: 'test_connection';
|
|
53
|
+
}
|
|
54
|
+
/** A step of the configuration wizard. */
|
|
55
|
+
export interface ConfigStep {
|
|
56
|
+
/**
|
|
57
|
+
* Step identifier. Recommended: makes step validation tracking reliable, as it
|
|
58
|
+
* relies on `step.key`.
|
|
59
|
+
*/
|
|
60
|
+
key?: string;
|
|
61
|
+
label: Localized;
|
|
62
|
+
description?: Localized;
|
|
63
|
+
fields: ConfigField[];
|
|
64
|
+
on_complete?: StepAction;
|
|
65
|
+
}
|
|
66
|
+
export interface ConfigGroup {
|
|
67
|
+
label?: Localized;
|
|
68
|
+
fields: ConfigField[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Configuration schema of an integration: either a wizard (`steps`), a flat list
|
|
72
|
+
* (`fields`), or groups (`groups`).
|
|
73
|
+
*/
|
|
74
|
+
export interface ConfigSchema {
|
|
75
|
+
steps?: ConfigStep[];
|
|
76
|
+
fields?: ConfigField[];
|
|
77
|
+
groups?: ConfigGroup[];
|
|
78
|
+
}
|
|
79
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared contracts.
|
|
3
|
+
*
|
|
4
|
+
* Public surface: TYPES only (config, widgets, SDK, lifecycle).
|
|
5
|
+
* Consumed by `@shopimind/integration-kit-js` and by each integration.
|
|
6
|
+
*/
|
|
7
|
+
export type * from './common.js';
|
|
8
|
+
export type * from './config-schema.js';
|
|
9
|
+
export type * from './widget.js';
|
|
10
|
+
export type * from './sdk.js';
|
|
11
|
+
export type * from './lifecycle.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { RawConfigs } from './common.js';
|
|
2
|
+
/**
|
|
3
|
+
* Lifecycle events sent by ShopiMind (discriminant `event`, in the past tense),
|
|
4
|
+
* HMAC-signed.
|
|
5
|
+
*/
|
|
6
|
+
export type LifecycleEvent = 'integration.installed' | 'integration.activated' | 'integration.deactivated' | 'integration.uninstalled' | 'integration.config_updated';
|
|
7
|
+
export interface LifecyclePayloadBase {
|
|
8
|
+
event: LifecycleEvent;
|
|
9
|
+
id_shop_integration: number;
|
|
10
|
+
id_shop?: number;
|
|
11
|
+
integration_slug?: string;
|
|
12
|
+
shop_domain?: string;
|
|
13
|
+
shop_name?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface InstallPayload extends LifecyclePayloadBase {
|
|
16
|
+
event: 'integration.installed';
|
|
17
|
+
/** The API key (prefix `int...`) — received here, must be persisted: it is not sent again. */
|
|
18
|
+
access_token?: string;
|
|
19
|
+
configs?: RawConfigs;
|
|
20
|
+
installed_at?: string;
|
|
21
|
+
/** OAuth variant (Type B): no id_shop_integration at install time. */
|
|
22
|
+
external_account_id?: string;
|
|
23
|
+
external_account_name?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ActivatePayload extends LifecyclePayloadBase {
|
|
26
|
+
event: 'integration.activated';
|
|
27
|
+
access_token?: string;
|
|
28
|
+
configs?: RawConfigs;
|
|
29
|
+
activated_at?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface DeactivatePayload extends LifecyclePayloadBase {
|
|
32
|
+
event: 'integration.deactivated';
|
|
33
|
+
deactivated_at?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface UninstallPayload extends LifecyclePayloadBase {
|
|
36
|
+
event: 'integration.uninstalled';
|
|
37
|
+
uninstalled_at?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ConfigUpdatedPayload extends LifecyclePayloadBase {
|
|
40
|
+
event: 'integration.config_updated';
|
|
41
|
+
configs?: RawConfigs;
|
|
42
|
+
}
|
|
43
|
+
export type LifecyclePayload = InstallPayload | ActivatePayload | DeactivatePayload | UninstallPayload | ConfigUpdatedPayload;
|
|
44
|
+
/**
|
|
45
|
+
* Response expected by ShopiMind: MUST contain `success: true`, otherwise the
|
|
46
|
+
* call is treated as a failure (and, depending on the event, the key may be revoked).
|
|
47
|
+
*/
|
|
48
|
+
export interface WebhookResponse {
|
|
49
|
+
success: boolean;
|
|
50
|
+
error?: string;
|
|
51
|
+
[k: string]: unknown;
|
|
52
|
+
}
|
|
53
|
+
/** Response from a remote-data resolver: populates a select in the config wizard. */
|
|
54
|
+
export interface RemoteDataResponse {
|
|
55
|
+
data: Array<{
|
|
56
|
+
value: string;
|
|
57
|
+
label: string;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoring types specific to the kit (provisioning declarations + shapes the
|
|
3
|
+
* integration builds). Entity types and the envelope come from the SDK
|
|
4
|
+
* (`@shopimind/sdk-js`, re-exported by the kit).
|
|
5
|
+
*/
|
|
6
|
+
/** Declaration of a data source to ensure (provisioning). */
|
|
7
|
+
export interface NewDataSource {
|
|
8
|
+
label: string;
|
|
9
|
+
type: string;
|
|
10
|
+
parent_id?: number;
|
|
11
|
+
config?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Canonical type of a custom data field, aligned with the ShopiMind API / SDK.
|
|
15
|
+
*
|
|
16
|
+
* This union is the kit's OWN AUTHORING allow-list, deliberately kept distinct
|
|
17
|
+
* from the SDK DTO's field-type type. We restate it here so the author contract
|
|
18
|
+
* stays stable even if the SDK DTO widens/renames its variants, and so the
|
|
19
|
+
* compile-time error a user gets points at the kit (their dependency) rather than
|
|
20
|
+
* leaking the underlying SDK type. Keep the two in sync when a new type lands.
|
|
21
|
+
*/
|
|
22
|
+
export type CustomDataFieldType = 'bool' | 'text' | 'longtext' | 'number' | 'decimal' | 'list' | 'date' | 'datetime' | 'json' | 'geolocation';
|
|
23
|
+
export interface NewCustomDataField {
|
|
24
|
+
name: string;
|
|
25
|
+
/** Display label (default: `name`). */
|
|
26
|
+
label?: string;
|
|
27
|
+
type: CustomDataFieldType;
|
|
28
|
+
/** Required field (default: false). */
|
|
29
|
+
required?: boolean;
|
|
30
|
+
description?: string;
|
|
31
|
+
default?: unknown;
|
|
32
|
+
options?: Array<string | number>;
|
|
33
|
+
}
|
|
34
|
+
export interface NewCustomDataDefinition {
|
|
35
|
+
/** Technical name (e.g. `pos_profile`). Sent as-is to the API (`name` field). */
|
|
36
|
+
name: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
unique_keys?: string[];
|
|
39
|
+
fields: NewCustomDataField[];
|
|
40
|
+
relationships?: Array<{
|
|
41
|
+
target: string;
|
|
42
|
+
by: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/** Order status to provision (declaration). */
|
|
46
|
+
export interface SpmOrderStatus {
|
|
47
|
+
status_id: string;
|
|
48
|
+
lang: string;
|
|
49
|
+
name: string;
|
|
50
|
+
is_deleted: boolean;
|
|
51
|
+
created_at: string;
|
|
52
|
+
updated_at: string;
|
|
53
|
+
id_data_source?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Declaration of an event type (provisioned on activation).
|
|
57
|
+
*
|
|
58
|
+
* Note on `name`: the ShopiMind API accepts an i18n map (`{ en: 'POS sale', fr: 'Vente POS' }`),
|
|
59
|
+
* so the kit types it as `Record<string, string>`. The SDK's create DTO types
|
|
60
|
+
* `name` narrowly as `string`, hence the intentional `as unknown` cast at the
|
|
61
|
+
* single call site (`ensureEvent` in `provisioning/ensure.ts`): the runtime
|
|
62
|
+
* payload is correct, only the SDK's compile-time type is too tight here.
|
|
63
|
+
*/
|
|
64
|
+
export interface NewEvent {
|
|
65
|
+
code_name: string;
|
|
66
|
+
name: Record<string, string>;
|
|
67
|
+
properties?: Record<string, unknown>;
|
|
68
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Localized } from './common.js';
|
|
2
|
+
/**
|
|
3
|
+
* Integration widget declarations.
|
|
4
|
+
*
|
|
5
|
+
* Any deviation of an integration from this contract breaks compilation rather
|
|
6
|
+
* than producing a silently invalid declaration.
|
|
7
|
+
*/
|
|
8
|
+
export type WidgetLocalizedText = Localized;
|
|
9
|
+
export type WidgetTarget = 'email_template' | 'popup' | 'smart_content' | 'stats_dashboard';
|
|
10
|
+
export type WidgetRenderType = 'image' | 'html';
|
|
11
|
+
/** Applicable only to `render_type: html`. */
|
|
12
|
+
export type WidgetRenderMode = 'static' | 'dynamic';
|
|
13
|
+
export type WidgetConfigFieldType = 'text' | 'number' | 'color' | 'select' | 'checkbox' | 'datetime';
|
|
14
|
+
export interface WidgetConfigFieldOption {
|
|
15
|
+
value: string;
|
|
16
|
+
label?: string | WidgetLocalizedText;
|
|
17
|
+
}
|
|
18
|
+
export interface WidgetConfigField {
|
|
19
|
+
key: string;
|
|
20
|
+
type: WidgetConfigFieldType;
|
|
21
|
+
default?: string | number | boolean;
|
|
22
|
+
label?: WidgetLocalizedText;
|
|
23
|
+
options?: WidgetConfigFieldOption[];
|
|
24
|
+
/** The field accepts a ShopiMind variable `{var=...}`. */
|
|
25
|
+
supports_variables?: boolean;
|
|
26
|
+
/** Text translated per language (html widgets) — resolved via a `{LANG_...}` token. */
|
|
27
|
+
translatable?: boolean;
|
|
28
|
+
/** Sample value for the image preview when the field carries a `{var=...}`. */
|
|
29
|
+
preview_value?: string;
|
|
30
|
+
/** `type: color` — emits the hex WITHOUT `#` (for image URLs). */
|
|
31
|
+
strip_hash?: boolean;
|
|
32
|
+
/** Conditional visibility: show when the sibling field matches one of the values. */
|
|
33
|
+
visible_when?: {
|
|
34
|
+
field: string;
|
|
35
|
+
in: string[];
|
|
36
|
+
};
|
|
37
|
+
/** Re-parses the panel when THIS field changes (drives `visible_when`). */
|
|
38
|
+
refresh_fields?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface WidgetConfigGroup {
|
|
41
|
+
label?: WidgetLocalizedText;
|
|
42
|
+
fields: WidgetConfigField[];
|
|
43
|
+
}
|
|
44
|
+
export interface WidgetConfigSchema {
|
|
45
|
+
fields?: WidgetConfigField[];
|
|
46
|
+
groups?: WidgetConfigGroup[];
|
|
47
|
+
style_groups?: WidgetConfigGroup[];
|
|
48
|
+
}
|
|
49
|
+
/** A widget declaration exposed by the integration. */
|
|
50
|
+
export interface WidgetDeclaration {
|
|
51
|
+
key: string;
|
|
52
|
+
name: WidgetLocalizedText;
|
|
53
|
+
description?: WidgetLocalizedText;
|
|
54
|
+
icon_url?: string;
|
|
55
|
+
preview_image_url?: string;
|
|
56
|
+
targets: WidgetTarget[];
|
|
57
|
+
render_type: WidgetRenderType;
|
|
58
|
+
/** `render_type: html` only (omitted for `image`). */
|
|
59
|
+
render_mode?: WidgetRenderMode;
|
|
60
|
+
/** `render_type: image`. */
|
|
61
|
+
image_url_template?: string;
|
|
62
|
+
/** `render_type: html` + `render_mode: static`. */
|
|
63
|
+
html_template?: string;
|
|
64
|
+
/** `render_type: html` + `render_mode: static` (optional). */
|
|
65
|
+
css_template?: string;
|
|
66
|
+
/** `render_type: html` + `render_mode: dynamic` — key of a ShopiMind renderer. */
|
|
67
|
+
renderer_key?: string;
|
|
68
|
+
default_width?: number;
|
|
69
|
+
config_schema?: WidgetConfigSchema;
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ServerRoute } from '@hapi/hapi';
|
|
2
|
+
import { type DispatcherDeps } from '../lifecycle/dispatcher.js';
|
|
3
|
+
import { type InboundDeps } from '../lifecycle/inbound.js';
|
|
4
|
+
export interface RouteDeps<S> {
|
|
5
|
+
dispatcher: DispatcherDeps<S>;
|
|
6
|
+
inbound: InboundDeps<S>;
|
|
7
|
+
adminToken?: string | null;
|
|
8
|
+
/** Limiter (per IP) for /admin/* routes -- bounds token brute-forcing + backfill abuse. */
|
|
9
|
+
adminRateLimit?(key: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Limiter (per IP) for POST /webhook/receive -- bounds a flood of unsigned requests
|
|
12
|
+
* before the (relatively costly) HMAC verification runs. Returns true if allowed.
|
|
13
|
+
*/
|
|
14
|
+
webhookRateLimit?(key: string): boolean;
|
|
15
|
+
runSyncForInstall(id: string, full: boolean): Promise<unknown>;
|
|
16
|
+
recentRuns(id: string): unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare function buildRoutes<S>(deps: RouteDeps<S>): ServerRoute[];
|