wai-license-connector-sdk 0.1.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 (117) hide show
  1. package/README.md +399 -0
  2. package/dist/cjs/client.d.ts +10 -0
  3. package/dist/cjs/client.d.ts.map +1 -0
  4. package/dist/cjs/client.js +57 -0
  5. package/dist/cjs/client.js.map +1 -0
  6. package/dist/cjs/configuration.d.ts +26 -0
  7. package/dist/cjs/configuration.d.ts.map +1 -0
  8. package/dist/cjs/configuration.js +47 -0
  9. package/dist/cjs/configuration.js.map +1 -0
  10. package/dist/cjs/connector.d.ts +63 -0
  11. package/dist/cjs/connector.d.ts.map +1 -0
  12. package/dist/cjs/connector.js +224 -0
  13. package/dist/cjs/connector.js.map +1 -0
  14. package/dist/cjs/errors.d.ts +43 -0
  15. package/dist/cjs/errors.d.ts.map +1 -0
  16. package/dist/cjs/errors.js +87 -0
  17. package/dist/cjs/errors.js.map +1 -0
  18. package/dist/cjs/heartbeat.d.ts +24 -0
  19. package/dist/cjs/heartbeat.d.ts.map +1 -0
  20. package/dist/cjs/heartbeat.js +59 -0
  21. package/dist/cjs/heartbeat.js.map +1 -0
  22. package/dist/cjs/index.d.ts +7 -0
  23. package/dist/cjs/index.d.ts.map +1 -0
  24. package/dist/cjs/index.js +21 -0
  25. package/dist/cjs/index.js.map +1 -0
  26. package/dist/cjs/instance.d.ts +14 -0
  27. package/dist/cjs/instance.d.ts.map +1 -0
  28. package/dist/cjs/instance.js +88 -0
  29. package/dist/cjs/instance.js.map +1 -0
  30. package/dist/cjs/manifest.d.ts +29 -0
  31. package/dist/cjs/manifest.d.ts.map +1 -0
  32. package/dist/cjs/manifest.js +29 -0
  33. package/dist/cjs/manifest.js.map +1 -0
  34. package/dist/cjs/registration.d.ts +20 -0
  35. package/dist/cjs/registration.d.ts.map +1 -0
  36. package/dist/cjs/registration.js +77 -0
  37. package/dist/cjs/registration.js.map +1 -0
  38. package/dist/cjs/types.d.ts +108 -0
  39. package/dist/cjs/types.d.ts.map +1 -0
  40. package/dist/cjs/types.js +3 -0
  41. package/dist/cjs/types.js.map +1 -0
  42. package/dist/esm/client.d.ts +10 -0
  43. package/dist/esm/client.d.ts.map +1 -0
  44. package/dist/esm/client.js +54 -0
  45. package/dist/esm/client.js.map +1 -0
  46. package/dist/esm/configuration.d.ts +26 -0
  47. package/dist/esm/configuration.d.ts.map +1 -0
  48. package/dist/esm/configuration.js +42 -0
  49. package/dist/esm/configuration.js.map +1 -0
  50. package/dist/esm/connector.d.ts +63 -0
  51. package/dist/esm/connector.d.ts.map +1 -0
  52. package/dist/esm/connector.js +220 -0
  53. package/dist/esm/connector.js.map +1 -0
  54. package/dist/esm/errors.d.ts +43 -0
  55. package/dist/esm/errors.d.ts.map +1 -0
  56. package/dist/esm/errors.js +77 -0
  57. package/dist/esm/errors.js.map +1 -0
  58. package/dist/esm/heartbeat.d.ts +24 -0
  59. package/dist/esm/heartbeat.d.ts.map +1 -0
  60. package/dist/esm/heartbeat.js +54 -0
  61. package/dist/esm/heartbeat.js.map +1 -0
  62. package/dist/esm/index.d.ts +7 -0
  63. package/dist/esm/index.d.ts.map +1 -0
  64. package/dist/esm/index.js +9 -0
  65. package/dist/esm/index.js.map +1 -0
  66. package/dist/esm/instance.d.ts +14 -0
  67. package/dist/esm/instance.d.ts.map +1 -0
  68. package/dist/esm/instance.js +51 -0
  69. package/dist/esm/instance.js.map +1 -0
  70. package/dist/esm/manifest.d.ts +29 -0
  71. package/dist/esm/manifest.d.ts.map +1 -0
  72. package/dist/esm/manifest.js +26 -0
  73. package/dist/esm/manifest.js.map +1 -0
  74. package/dist/esm/registration.d.ts +20 -0
  75. package/dist/esm/registration.d.ts.map +1 -0
  76. package/dist/esm/registration.js +41 -0
  77. package/dist/esm/registration.js.map +1 -0
  78. package/dist/esm/types.d.ts +108 -0
  79. package/dist/esm/types.d.ts.map +1 -0
  80. package/dist/esm/types.js +2 -0
  81. package/dist/esm/types.js.map +1 -0
  82. package/dist/types/client.d.ts +10 -0
  83. package/dist/types/client.d.ts.map +1 -0
  84. package/dist/types/configuration.d.ts +26 -0
  85. package/dist/types/configuration.d.ts.map +1 -0
  86. package/dist/types/connector.d.ts +63 -0
  87. package/dist/types/connector.d.ts.map +1 -0
  88. package/dist/types/errors.d.ts +43 -0
  89. package/dist/types/errors.d.ts.map +1 -0
  90. package/dist/types/heartbeat.d.ts +24 -0
  91. package/dist/types/heartbeat.d.ts.map +1 -0
  92. package/dist/types/index.d.ts +7 -0
  93. package/dist/types/index.d.ts.map +1 -0
  94. package/dist/types/instance.d.ts +14 -0
  95. package/dist/types/instance.d.ts.map +1 -0
  96. package/dist/types/manifest.d.ts +29 -0
  97. package/dist/types/manifest.d.ts.map +1 -0
  98. package/dist/types/registration.d.ts +20 -0
  99. package/dist/types/registration.d.ts.map +1 -0
  100. package/dist/types/types.d.ts +108 -0
  101. package/dist/types/types.d.ts.map +1 -0
  102. package/package.json +56 -0
  103. package/src/__tests__/connector.spec.ts +368 -0
  104. package/src/__tests__/heartbeat.spec.ts +105 -0
  105. package/src/__tests__/instance.spec.ts +79 -0
  106. package/src/__tests__/manifest.spec.ts +64 -0
  107. package/src/__tests__/registration.spec.ts +89 -0
  108. package/src/client.ts +58 -0
  109. package/src/configuration.ts +57 -0
  110. package/src/connector.ts +276 -0
  111. package/src/errors.ts +88 -0
  112. package/src/heartbeat.ts +76 -0
  113. package/src/index.ts +35 -0
  114. package/src/instance.ts +55 -0
  115. package/src/manifest.ts +51 -0
  116. package/src/registration.ts +57 -0
  117. package/src/types.ts +150 -0
package/README.md ADDED
@@ -0,0 +1,399 @@
1
+ # @wai-license/connector-sdk
2
+
3
+ Official SDK for making any Node.js application a **Wai License-compatible connector**.
4
+
5
+ Framework-agnostic · Node 20+ · Native fetch only · No axios
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ # npm
13
+ npm install @wai-license/connector-sdk
14
+
15
+ # pnpm (monorepo)
16
+ pnpm add @wai-license/connector-sdk
17
+ ```
18
+
19
+ Node 20 or later is required. No polyfills needed — the SDK uses the built-in `fetch`, `crypto`, and `fs` APIs.
20
+
21
+ ---
22
+
23
+ ## Quick Start
24
+
25
+ ```ts
26
+ import { WaiConnector } from '@wai-license/connector-sdk';
27
+
28
+ const connector = new WaiConnector({
29
+ // Identity
30
+ connectorCode: 'alcya', // Stable code — matches the connector registered in Wai License
31
+ connectorName: 'ALCYA',
32
+ connectorVersion: '1.0.0',
33
+ connectorType: 'api',
34
+
35
+ // Connection
36
+ waiUrl: process.env.WAI_URL!, // e.g. https://wai.example.com
37
+ apiKey: process.env.WAI_API_KEY!,
38
+
39
+ // Capabilities and actions
40
+ capabilities: ['customers', 'licenses'],
41
+ actions: [
42
+ {
43
+ code: 'activate_license',
44
+ name: 'Activate License',
45
+ endpoint: { url: '/wai/licenses/activate' },
46
+ },
47
+ {
48
+ code: 'create_customer',
49
+ name: 'Create Customer',
50
+ endpoint: { url: '/wai/customers', method: 'POST' },
51
+ },
52
+ ],
53
+
54
+ // Optional hooks
55
+ onRegister: (ctx) => console.log('Registered:', ctx.instanceId),
56
+ onHeartbeat: (ctx) => console.log('Heartbeat sent, took', ctx.durationMs, 'ms'),
57
+ onConfiguration: (ctx) => console.log('Config refreshed at', ctx.fetchedAt),
58
+ onError: (err) => console.error('Connector error:', err.name, err.message),
59
+ });
60
+
61
+ await connector.start();
62
+ console.log('Connector started, instanceId:', connector.instanceId);
63
+ ```
64
+
65
+ `start()` will:
66
+ 1. Load or create a persistent instance identity (`.wai-connector.json`)
67
+ 2. Register with Wai License
68
+ 3. Send the first heartbeat
69
+ 4. Start the heartbeat loop (default: every 60 seconds)
70
+
71
+ ---
72
+
73
+ ## Express Integration
74
+
75
+ ```ts
76
+ import express from 'express';
77
+ import { WaiConnector } from '@wai-license/connector-sdk';
78
+
79
+ const connector = new WaiConnector({ /* ...options... */ });
80
+
81
+ const app = express();
82
+ app.use(express.json());
83
+
84
+ // Serve the manifest — Wai License polls this URL
85
+ app.get('/.well-known/wai-license.json', async (_req, res) => {
86
+ const handler = connector.handlers.manifest();
87
+ const response = handler();
88
+ const body = await response.json();
89
+ res.json(body);
90
+ });
91
+
92
+ // Optional: let Wai License push a registration confirmation
93
+ app.post('/wai/register', async (req, res) => {
94
+ const handler = connector.handlers.register();
95
+ const response = handler(req as unknown as Request);
96
+ const body = await response.json();
97
+ res.status(response.status).json(body);
98
+ });
99
+
100
+ // Optional: heartbeat ping endpoint
101
+ app.post('/wai/heartbeat', async (_req, res) => {
102
+ const handler = connector.handlers.heartbeat();
103
+ const response = handler();
104
+ const body = await response.json();
105
+ res.status(response.status).json(body);
106
+ });
107
+
108
+ // Start everything
109
+ app.listen(3000, async () => {
110
+ await connector.start();
111
+ console.log('Express + Wai Connector running on :3000');
112
+ });
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Fastify Integration
118
+
119
+ ```ts
120
+ import Fastify from 'fastify';
121
+ import { WaiConnector } from '@wai-license/connector-sdk';
122
+
123
+ const connector = new WaiConnector({ /* ...options... */ });
124
+ const app = Fastify();
125
+
126
+ app.get('/.well-known/wai-license.json', async (_request, reply) => {
127
+ const handler = connector.handlers.manifest();
128
+ const response = handler();
129
+ const body = await response.json();
130
+ return reply.type('application/json').send(body);
131
+ });
132
+
133
+ app.post('/wai/heartbeat', async (_request, reply) => {
134
+ const handler = connector.handlers.heartbeat();
135
+ const response = handler();
136
+ const body = await response.json();
137
+ return reply.status(response.status).send(body);
138
+ });
139
+
140
+ const start = async () => {
141
+ await connector.start();
142
+ await app.listen({ port: 3000 });
143
+ console.log('Fastify + Wai Connector running on :3000');
144
+ };
145
+
146
+ start().catch(console.error);
147
+ ```
148
+
149
+ ---
150
+
151
+ ## ALCYA Integration Example
152
+
153
+ ALCYA is a Node.js SaaS application that manages software licenses. Here is a complete integration using the connector SDK with Express:
154
+
155
+ ```ts
156
+ // src/wai-connector.ts
157
+ import { WaiConnector, AuthenticationError, NetworkError } from '@wai-license/connector-sdk';
158
+ import { logger } from './logger';
159
+
160
+ export const connector = new WaiConnector({
161
+ connectorCode: 'alcya',
162
+ connectorName: 'ALCYA',
163
+ connectorVersion: process.env.npm_package_version ?? '1.0.0',
164
+ connectorType: 'api',
165
+ waiUrl: process.env.WAI_URL!,
166
+ apiKey: process.env.WAI_API_KEY!,
167
+ capabilities: ['customers', 'licenses', 'subscriptions'],
168
+ actions: [
169
+ { code: 'activate_license', name: 'Activate License', endpoint: { url: '/wai/licenses/activate' } },
170
+ { code: 'deactivate_license', name: 'Deactivate License', endpoint: { url: '/wai/licenses/deactivate' } },
171
+ { code: 'create_customer', name: 'Create Customer', endpoint: { url: '/wai/customers' } },
172
+ { code: 'suspend_customer', name: 'Suspend Customer', endpoint: { url: '/wai/customers/suspend' } },
173
+ ],
174
+ heartbeatIntervalMs: 30_000,
175
+ onRegister: (ctx) => logger.info({ instanceId: ctx.instanceId }, 'Registered with Wai License'),
176
+ onHeartbeat: (ctx) => logger.debug({ durationMs: ctx.durationMs }, 'Heartbeat sent'),
177
+ onConfiguration: (ctx) => {
178
+ logger.info('Wai configuration refreshed');
179
+ // React to configuration changes
180
+ const config = ctx.config;
181
+ if (config['featureFlags']) {
182
+ applyFeatureFlags(config['featureFlags'] as Record<string, boolean>);
183
+ }
184
+ },
185
+ onError: (err) => {
186
+ if (err instanceof AuthenticationError) {
187
+ logger.error('Invalid WAI_API_KEY — check environment configuration');
188
+ } else if (err instanceof NetworkError) {
189
+ logger.warn({ message: err.message }, 'Wai License unreachable, will retry');
190
+ } else {
191
+ logger.error({ err }, 'Wai Connector error');
192
+ }
193
+ },
194
+ });
195
+
196
+ function applyFeatureFlags(_flags: Record<string, boolean>): void {
197
+ // application-specific flag application
198
+ }
199
+ ```
200
+
201
+ ```ts
202
+ // src/app.ts
203
+ import express from 'express';
204
+ import { connector } from './wai-connector';
205
+
206
+ const app = express();
207
+ app.use(express.json());
208
+
209
+ // Manifest endpoint — Wai License discovers the connector from here
210
+ app.get('/.well-known/wai-license.json', async (_req, res) => {
211
+ const handler = connector.handlers.manifest();
212
+ const resp = handler();
213
+ res.status(resp.status).json(await resp.json());
214
+ });
215
+
216
+ // Action endpoints — Wai License calls these when executing sync jobs
217
+ app.post('/wai/licenses/activate', async (req, res) => {
218
+ const { payload } = req.body as { payload: { licenseKey: string; customerId: string } };
219
+ // ... activate the license in ALCYA ...
220
+ res.json({ success: true, licenseKey: payload.licenseKey });
221
+ });
222
+
223
+ app.post('/wai/customers', async (req, res) => {
224
+ const { payload } = req.body as { payload: { email: string; name: string } };
225
+ // ... create the customer in ALCYA ...
226
+ res.json({ success: true, customerId: 'cust_xxx' });
227
+ });
228
+
229
+ app.listen(3000, async () => {
230
+ await connector.start();
231
+ });
232
+ ```
233
+
234
+ ---
235
+
236
+ ## TFC Platform Integration Example
237
+
238
+ TFC Platform is a multi-tenant billing platform that connects with Wai License to manage enterprise licenses:
239
+
240
+ ```ts
241
+ // connector/index.ts
242
+ import { WaiConnector } from '@wai-license/connector-sdk';
243
+ import Hono from 'hono';
244
+
245
+ const wai = new WaiConnector({
246
+ connectorCode: 'tfc-platform',
247
+ connectorName: 'TFC Platform',
248
+ connectorVersion: '2.1.0',
249
+ connectorType: 'webhook',
250
+ waiUrl: process.env.WAI_LICENSE_URL!,
251
+ apiKey: process.env.WAI_LICENSE_KEY!,
252
+ capabilities: ['customers', 'licenses', 'billing'],
253
+ actions: [
254
+ { code: 'provision_license', name: 'Provision License', endpoint: { url: '/webhooks/wai/provision' } },
255
+ { code: 'revoke_license', name: 'Revoke License', endpoint: { url: '/webhooks/wai/revoke' } },
256
+ { code: 'sync_customer', name: 'Sync Customer', endpoint: { url: '/webhooks/wai/customers/sync' } },
257
+ { code: 'update_plan', name: 'Update Plan', endpoint: { url: '/webhooks/wai/plans/update' } },
258
+ ],
259
+ heartbeatIntervalMs: 60_000,
260
+ instanceFilePath: '/data/persistent/.wai-connector.json', // persisted volume
261
+ });
262
+
263
+ const app = new Hono();
264
+
265
+ // Discovery manifest
266
+ app.get('/.well-known/wai-license.json', async (c) => {
267
+ const handler = wai.handlers.manifest();
268
+ const resp = handler();
269
+ return c.json(await resp.json(), resp.status as 200);
270
+ });
271
+
272
+ // Webhook handlers — Wai License POSTs events here
273
+ app.post('/webhooks/wai/provision', async (c) => {
274
+ const body = await c.req.json() as { payload: unknown };
275
+ // ... provision the license in TFC ...
276
+ return c.json({ provisioned: true });
277
+ });
278
+
279
+ app.post('/webhooks/wai/revoke', async (c) => {
280
+ const body = await c.req.json() as { payload: unknown };
281
+ // ... revoke the license ...
282
+ return c.json({ revoked: true });
283
+ });
284
+
285
+ export default {
286
+ fetch: app.fetch,
287
+ async scheduled() {
288
+ await wai.fetchConfiguration();
289
+ },
290
+ };
291
+
292
+ // Start the connector once the Hono server is ready
293
+ await wai.start();
294
+ ```
295
+
296
+ ---
297
+
298
+ ## API Reference
299
+
300
+ ### `new WaiConnector(options)`
301
+
302
+ | Option | Type | Required | Default | Description |
303
+ |--------|------|----------|---------|-------------|
304
+ | `connectorCode` | `string` | Yes | — | Stable connector identifier |
305
+ | `connectorName` | `string` | Yes | — | Human-readable name |
306
+ | `connectorVersion` | `string` | Yes | — | SemVer string |
307
+ | `connectorType` | `string` | Yes | — | `api` or `webhook` |
308
+ | `waiUrl` | `string` | Yes | — | Base URL of the Wai License server |
309
+ | `apiKey` | `string` | Yes | — | Project API key (`wl_...`) |
310
+ | `capabilities` | `string[]` | Yes | — | List of capability keys |
311
+ | `actions` | `ConnectorActionDefinition[]` | Yes | — | Actions exposed to Wai License |
312
+ | `heartbeatIntervalMs` | `number` | No | `60000` | Heartbeat frequency |
313
+ | `timeoutMs` | `number` | No | `10000` | HTTP request timeout |
314
+ | `instanceFilePath` | `string` | No | `.wai-connector.json` | Instance identity persistence path |
315
+ | `onRegister` | `(ctx: RegisterContext) => void` | No | — | Called on successful registration |
316
+ | `onHeartbeat` | `(ctx: HeartbeatContext) => void` | No | — | Called after each heartbeat |
317
+ | `onConfiguration` | `(ctx: ConfigurationContext) => void` | No | — | Called when config is refreshed |
318
+ | `onError` | `(err: WaiConnectorError) => void` | No | — | Called on any SDK error |
319
+
320
+ ### Instance methods
321
+
322
+ | Method | Returns | Description |
323
+ |--------|---------|-------------|
324
+ | `start()` | `Promise<void>` | Register, heartbeat, and start the loop |
325
+ | `stop()` | `Promise<void>` | Stop the heartbeat timer |
326
+ | `getManifest()` | `WaiManifest` | Manifest object for `/.well-known/wai-license.json` |
327
+ | `getConfiguration()` | `Record<string, unknown>` | Last-known config from Wai License |
328
+ | `fetchConfiguration()` | `Promise<Record<string, unknown>>` | Fetch fresh config now |
329
+ | `handlers.manifest()` | `ConnectorHandler` | Framework-agnostic manifest HTTP handler |
330
+ | `handlers.register()` | `ConnectorHandler` | Framework-agnostic register handler |
331
+ | `handlers.heartbeat()` | `ConnectorHandler` | Framework-agnostic heartbeat handler |
332
+ | `handlers.config()` | `ConnectorHandler` | Framework-agnostic config handler |
333
+
334
+ ### Accessors
335
+
336
+ | Getter | Type | Description |
337
+ |--------|------|-------------|
338
+ | `instanceId` | `string \| null` | Set after `start()` |
339
+ | `isStarted` | `boolean` | Whether the connector is running |
340
+
341
+ ---
342
+
343
+ ## Instance Identity
344
+
345
+ Each running process is assigned a stable UUID that persists between restarts. It is stored in `.wai-connector.json` in the current working directory by default.
346
+
347
+ ```json
348
+ { "instanceId": "550e8400-e29b-41d4-a716-446655440000" }
349
+ ```
350
+
351
+ To use a custom path (e.g. a mounted persistent volume):
352
+
353
+ ```ts
354
+ new WaiConnector({ instanceFilePath: '/data/.wai-connector.json', ...opts });
355
+ ```
356
+
357
+ The SDK is fault-tolerant: if the file cannot be written (read-only filesystem, missing permissions), a UUID is still generated for the session lifetime and the failure is silently ignored.
358
+
359
+ ---
360
+
361
+ ## Error Handling
362
+
363
+ All errors extend `WaiConnectorError`:
364
+
365
+ | Class | `statusCode` | When |
366
+ |-------|-------------|------|
367
+ | `AuthenticationError` | 401 | Invalid or missing API key |
368
+ | `RegistrationError` | HTTP status | Registration rejected |
369
+ | `HeartbeatError` | HTTP status | Heartbeat rejected |
370
+ | `ConfigurationError` | HTTP status | Config fetch failed |
371
+ | `NetworkError` | 0 | Timeout, DNS failure, connection refused |
372
+
373
+ ```ts
374
+ import { WaiConnector, AuthenticationError, NetworkError } from '@wai-license/connector-sdk';
375
+
376
+ try {
377
+ await connector.start();
378
+ } catch (err) {
379
+ if (err instanceof AuthenticationError) {
380
+ // Check WAI_API_KEY in your environment
381
+ } else if (err instanceof NetworkError) {
382
+ // Wai License is unreachable
383
+ }
384
+ }
385
+ ```
386
+
387
+ ---
388
+
389
+ ## Security
390
+
391
+ - The `apiKey` is sent as a Bearer token in the HTTP `Authorization` header — it is **never** included in request bodies, error messages, or logs.
392
+ - Instance identity (`instanceId`) is safe to log — it contains no sensitive information.
393
+ - The SDK never logs authorization headers or API keys at any log level.
394
+
395
+ ---
396
+
397
+ ## License
398
+
399
+ Proprietary — Wai License internal package.
@@ -0,0 +1,10 @@
1
+ import type { RequestOptions } from './types';
2
+ /**
3
+ * Low-level HTTP transport for Wai License API calls.
4
+ * Uses native fetch — no axios, no external HTTP libraries.
5
+ * Never logs the Authorization header or raw apiKey.
6
+ *
7
+ * @internal
8
+ */
9
+ export declare function apiRequest<T>(opts: RequestOptions): Promise<T>;
10
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC,CA+CpE"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiRequest = apiRequest;
4
+ const errors_1 = require("./errors");
5
+ /**
6
+ * Low-level HTTP transport for Wai License API calls.
7
+ * Uses native fetch — no axios, no external HTTP libraries.
8
+ * Never logs the Authorization header or raw apiKey.
9
+ *
10
+ * @internal
11
+ */
12
+ async function apiRequest(opts) {
13
+ const url = `${opts.waiUrl.replace(/\/$/, '')}${opts.path}`;
14
+ const headers = {
15
+ Authorization: `Bearer ${opts.apiKey}`,
16
+ 'Content-Type': 'application/json',
17
+ Accept: 'application/json',
18
+ };
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), opts.timeoutMs);
21
+ let response;
22
+ try {
23
+ response = await fetch(url, {
24
+ method: opts.method,
25
+ headers,
26
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
27
+ signal: controller.signal,
28
+ });
29
+ }
30
+ catch (err) {
31
+ clearTimeout(timeoutId);
32
+ const isTimeout = err instanceof Error && err.name === 'AbortError';
33
+ throw new errors_1.NetworkError(isTimeout
34
+ ? `Request timed out after ${opts.timeoutMs}ms`
35
+ : `Network error: ${err instanceof Error ? err.message : String(err)}`);
36
+ }
37
+ finally {
38
+ clearTimeout(timeoutId);
39
+ }
40
+ if (response.ok) {
41
+ const text = await response.text();
42
+ return (text ? JSON.parse(text) : {});
43
+ }
44
+ let errorMessage = `HTTP ${response.status}`;
45
+ try {
46
+ const payload = (await response.json());
47
+ if (typeof payload['message'] === 'string')
48
+ errorMessage = payload['message'];
49
+ else if (typeof payload['error'] === 'string')
50
+ errorMessage = payload['error'];
51
+ }
52
+ catch {
53
+ // non-JSON error body
54
+ }
55
+ throw (0, errors_1.createConnectorError)(response.status, errorMessage);
56
+ }
57
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":";;AAUA,gCA+CC;AAzDD,qCAA8D;AAG9D;;;;;;GAMG;AACI,KAAK,UAAU,UAAU,CAAI,IAAoB;IACtD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5D,MAAM,OAAO,GAA2B;QACtC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;QACtC,cAAc,EAAE,kBAAkB;QAClC,MAAM,EAAE,kBAAkB;KAC3B,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAEvE,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YACrE,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,MAAM,SAAS,GAAG,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC;QACpE,MAAM,IAAI,qBAAY,CACpB,SAAS;YACP,CAAC,CAAC,2BAA2B,IAAI,CAAC,SAAS,IAAI;YAC/C,CAAC,CAAC,kBAAkB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CACzE,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,SAAS,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAM,CAAC;IAC7C,CAAC;IAED,IAAI,YAAY,GAAG,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC7C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;QACnE,IAAI,OAAO,OAAO,CAAC,SAAS,CAAC,KAAK,QAAQ;YAAE,YAAY,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;aACzE,IAAI,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,QAAQ;YAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;IAED,MAAM,IAAA,6BAAoB,EAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { ConfigurationContext } from './types';
2
+ export interface ConfigurationParams {
3
+ waiUrl: string;
4
+ apiKey: string;
5
+ connectorCode: string;
6
+ instanceId: string;
7
+ timeoutMs: number;
8
+ }
9
+ /**
10
+ * In-memory configuration store.
11
+ * Updated whenever getConfiguration() is called successfully.
12
+ *
13
+ * @internal
14
+ */
15
+ export declare class ConfigurationStore {
16
+ private _config;
17
+ get(): Record<string, unknown>;
18
+ set(config: Record<string, unknown>): void;
19
+ }
20
+ /**
21
+ * Fetches the latest connector configuration from the Wai License server.
22
+ * Calls GET /connectors/config with Bearer auth.
23
+ * Stores the result in the provided ConfigurationStore.
24
+ */
25
+ export declare function getConfiguration(params: ConfigurationParams, store: ConfigurationStore): Promise<ConfigurationContext>;
26
+ //# sourceMappingURL=configuration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration.d.ts","sourceRoot":"","sources":["../../src/configuration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAA+B;IAE9C,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI9B,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAG3C;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAkB/B"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigurationStore = void 0;
4
+ exports.getConfiguration = getConfiguration;
5
+ const client_1 = require("./client");
6
+ const errors_1 = require("./errors");
7
+ /**
8
+ * In-memory configuration store.
9
+ * Updated whenever getConfiguration() is called successfully.
10
+ *
11
+ * @internal
12
+ */
13
+ class ConfigurationStore {
14
+ _config = {};
15
+ get() {
16
+ return { ...this._config };
17
+ }
18
+ set(config) {
19
+ this._config = { ...config };
20
+ }
21
+ }
22
+ exports.ConfigurationStore = ConfigurationStore;
23
+ /**
24
+ * Fetches the latest connector configuration from the Wai License server.
25
+ * Calls GET /connectors/config with Bearer auth.
26
+ * Stores the result in the provided ConfigurationStore.
27
+ */
28
+ async function getConfiguration(params, store) {
29
+ let config;
30
+ try {
31
+ config = await (0, client_1.apiRequest)({
32
+ waiUrl: params.waiUrl,
33
+ apiKey: params.apiKey,
34
+ timeoutMs: params.timeoutMs,
35
+ path: `/connectors/config?connectorCode=${encodeURIComponent(params.connectorCode)}&instanceId=${encodeURIComponent(params.instanceId)}`,
36
+ method: 'GET',
37
+ });
38
+ }
39
+ catch (err) {
40
+ const base = err;
41
+ throw (0, errors_1.createConnectorError)(base.statusCode ?? 0, base.message, 'configuration');
42
+ }
43
+ store.set(config);
44
+ const fetchedAt = new Date().toISOString();
45
+ return { config, fetchedAt };
46
+ }
47
+ //# sourceMappingURL=configuration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configuration.js","sourceRoot":"","sources":["../../src/configuration.ts"],"names":[],"mappings":";;;AAmCA,4CAqBC;AAxDD,qCAAsC;AACtC,qCAAgD;AAWhD;;;;;GAKG;AACH,MAAa,kBAAkB;IACrB,OAAO,GAA4B,EAAE,CAAC;IAE9C,GAAG;QACD,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,MAA+B;QACjC,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC/B,CAAC;CACF;AAVD,gDAUC;AAED;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CACpC,MAA2B,EAC3B,KAAyB;IAEzB,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAA,mBAAU,EAA0B;YACjD,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,IAAI,EAAE,oCAAoC,kBAAkB,CAAC,MAAM,CAAC,aAAa,CAAC,eAAe,kBAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE;YACxI,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,GAA+C,CAAC;QAC7D,MAAM,IAAA,6BAAoB,EAAC,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IAClF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,63 @@
1
+ import type { WaiManifest } from './manifest';
2
+ import type { WaiConnectorOptions, ConnectorHandlers } from './types';
3
+ /**
4
+ * Main entry point for the Wai License Connector SDK.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const connector = new WaiConnector({
9
+ * connectorCode: 'alcya',
10
+ * connectorName: 'ALCYA',
11
+ * connectorVersion: '1.0.0',
12
+ * connectorType: 'api',
13
+ * waiUrl: process.env.WAI_URL!,
14
+ * apiKey: process.env.WAI_API_KEY!,
15
+ * capabilities: ['customers', 'licenses'],
16
+ * actions: [
17
+ * { code: 'activate_license', name: 'Activate License', endpoint: { url: '/wai/licenses/activate' } },
18
+ * ],
19
+ * });
20
+ *
21
+ * await connector.start();
22
+ * ```
23
+ */
24
+ export declare class WaiConnector {
25
+ private readonly opts;
26
+ private _instanceId;
27
+ private _heartbeatHandle;
28
+ private _started;
29
+ private readonly _configStore;
30
+ constructor(options: WaiConnectorOptions);
31
+ /**
32
+ * Loads the instance identity, registers with Wai License,
33
+ * sends the first heartbeat, then starts the heartbeat loop.
34
+ */
35
+ start(): Promise<void>;
36
+ /**
37
+ * Stops the heartbeat timer. Safe to call multiple times.
38
+ */
39
+ stop(): Promise<void>;
40
+ /** Returns the connector manifest that Wai License reads from /.well-known/wai-license.json */
41
+ getManifest(): WaiManifest;
42
+ /** Returns the last-known configuration fetched from Wai License. */
43
+ getConfiguration(): Record<string, unknown>;
44
+ /** Fetches fresh configuration from Wai License and updates the in-memory store. */
45
+ fetchConfiguration(): Promise<Record<string, unknown>>;
46
+ /**
47
+ * Returns framework-agnostic HTTP handler functions.
48
+ * Each handler accepts an optional Request and returns a Response.
49
+ *
50
+ * @example Express
51
+ * ```ts
52
+ * app.get('/.well-known/wai-license.json', (req, res) => {
53
+ * const response = connector.handlers.manifest()(req as unknown as Request);
54
+ * res.json(JSON.parse(/* body *\/));
55
+ * });
56
+ * ```
57
+ */
58
+ get handlers(): ConnectorHandlers;
59
+ get instanceId(): string | null;
60
+ get isStarted(): boolean;
61
+ private static _validate;
62
+ }
63
+ //# sourceMappingURL=connector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../src/connector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK9C,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EAElB,MAAM,SAAS,CAAC;AAIjB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAYnB;IAEF,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,gBAAgB,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA4B;gBAE7C,OAAO,EAAE,mBAAmB;IAwBxC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,+FAA+F;IAC/F,WAAW,IAAI,WAAW;IAa1B,qEAAqE;IACrE,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI3C,oFAAoF;IAC9E,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAuB5D;;;;;;;;;;;OAWG;IACH,IAAI,QAAQ,IAAI,iBAAiB,CAuChC;IAID,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAE9B;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAID,OAAO,CAAC,MAAM,CAAC,SAAS;CA0BzB"}