@soulcraft/sdk 1.6.2 → 1.7.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.
@@ -0,0 +1,307 @@
1
+ /**
2
+ * @module transports/workshop
3
+ * @description PostMessage transport for non-brainy API calls from kit iframes to
4
+ * the Workshop parent frame.
5
+ *
6
+ * While {@link PostMessageTransport} handles Brainy RPC (`brainy:request` /
7
+ * `brainy:response`), this transport handles generic HTTP-style API calls —
8
+ * AI chat streaming, Hall room joins, Pulse analytics, file operations, and
9
+ * notifications — using the `workshop:request` / `workshop:response` /
10
+ * `workshop:stream` wire protocol.
11
+ *
12
+ * The parent frame (PreviewFrame.svelte) receives `workshop:request` messages,
13
+ * forwards them as real HTTP requests to Workshop's backend, and relays the
14
+ * response back via `workshop:response` (JSON) or `workshop:stream` (SSE chunks).
15
+ *
16
+ * ## Wire protocol
17
+ *
18
+ * **Request** (kit iframe → parent):
19
+ * ```json
20
+ * { "type": "workshop:request", "id": "<uuid>", "path": "/api/ai/chat",
21
+ * "method": "POST", "body": "{...}", "stream": true, "headers": {} }
22
+ * ```
23
+ *
24
+ * **Response** (parent → kit iframe, non-streaming):
25
+ * ```json
26
+ * { "type": "workshop:response", "id": "<uuid>", "result": {...} }
27
+ * { "type": "workshop:response", "id": "<uuid>", "error": { "code": "...", "message": "..." } }
28
+ * ```
29
+ *
30
+ * **Stream chunk** (parent → kit iframe, streaming):
31
+ * ```json
32
+ * { "type": "workshop:stream", "id": "<uuid>", "chunk": "..." }
33
+ * { "type": "workshop:stream", "id": "<uuid>", "done": true }
34
+ * { "type": "workshop:stream", "id": "<uuid>", "error": "..." }
35
+ * ```
36
+ *
37
+ * ## Usage
38
+ *
39
+ * ```typescript
40
+ * import { WorkshopTransport } from '@soulcraft/sdk/client'
41
+ *
42
+ * const transport = new WorkshopTransport('https://workshop.soulcraft.com')
43
+ *
44
+ * // JSON call
45
+ * const result = await transport.call('/api/hall/rooms/my-room/join', 'POST', '{"audio":true}')
46
+ *
47
+ * // Streaming call (AI chat)
48
+ * for await (const chunk of transport.stream('/api/ai/chat', 'POST', body)) {
49
+ * process.stdout.write(chunk)
50
+ * }
51
+ * ```
52
+ *
53
+ * @see {@link PostMessageTransport} for Brainy-specific RPC transport.
54
+ */
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ // Transport
57
+ // ─────────────────────────────────────────────────────────────────────────────
58
+ /**
59
+ * PostMessage transport for generic (non-brainy) API calls from kit iframes.
60
+ *
61
+ * Each `call()` posts a `workshop:request` message to `window.parent` and awaits
62
+ * a matching `workshop:response`. For streaming endpoints, `stream()` collects
63
+ * `workshop:stream` chunks and yields them as an `AsyncIterable<string>`.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const t = new WorkshopTransport(workshopOrigin)
68
+ *
69
+ * // Fire-and-forget analytics
70
+ * t.call('/api/pulse/track', 'POST', JSON.stringify({ event: 'page_view' }))
71
+ *
72
+ * // Stream AI tokens
73
+ * for await (const token of t.stream('/api/ai/chat', 'POST', body)) {
74
+ * output += token
75
+ * }
76
+ * ```
77
+ */
78
+ export class WorkshopTransport {
79
+ _targetOrigin;
80
+ _timeoutMs;
81
+ _pendingCalls = new Map();
82
+ _pendingStreams = new Map();
83
+ _closed = false;
84
+ _messageListener;
85
+ _listenerWindow;
86
+ _parentWindow;
87
+ /**
88
+ * @param targetOrigin - The Workshop parent frame's origin for postMessage security.
89
+ * Must be an explicit origin in production; `'*'` is acceptable only in dev.
90
+ * @param timeoutMs - Per-call timeout in milliseconds. Default: 60 000 (longer than
91
+ * brainy RPC because AI chat streams can take a while to start).
92
+ * @param listenerWindow - Window for message events. Defaults to `window`.
93
+ * @param parentWindow - Window to post messages to. Defaults to `window.parent`.
94
+ */
95
+ constructor(targetOrigin, timeoutMs = 60_000, listenerWindow, parentWindow) {
96
+ this._targetOrigin = targetOrigin;
97
+ this._timeoutMs = timeoutMs;
98
+ this._listenerWindow = listenerWindow ?? window;
99
+ this._parentWindow = parentWindow ?? window.parent;
100
+ this._messageListener = (event) => this._handleMessage(event);
101
+ this._listenerWindow.addEventListener('message', this._messageListener);
102
+ }
103
+ // ── Internal ──────────────────────────────────────────────────────────────
104
+ _handleMessage(event) {
105
+ const data = event.data;
106
+ if (!data || typeof data.type !== 'string')
107
+ return;
108
+ // ── JSON response ─────────────────────────────────────────────────────
109
+ if (data.type === 'workshop:response') {
110
+ const pending = this._pendingCalls.get(data.id);
111
+ if (!pending)
112
+ return;
113
+ clearTimeout(pending.timer);
114
+ this._pendingCalls.delete(data.id);
115
+ if (data.error) {
116
+ pending.reject(new Error(`${data.error.code}: ${data.error.message}`));
117
+ }
118
+ else {
119
+ pending.resolve(data.result);
120
+ }
121
+ return;
122
+ }
123
+ // ── Stream chunk ──────────────────────────────────────────────────────
124
+ if (data.type === 'workshop:stream') {
125
+ const stream = this._pendingStreams.get(data.id);
126
+ if (!stream)
127
+ return;
128
+ if (data.error) {
129
+ stream.ended = true;
130
+ stream.error = data.error;
131
+ clearTimeout(stream.timer);
132
+ this._pendingStreams.delete(data.id);
133
+ if (stream.waiter) {
134
+ const w = stream.waiter;
135
+ stream.waiter = null;
136
+ w({ value: undefined, done: true });
137
+ }
138
+ return;
139
+ }
140
+ if (data.done) {
141
+ stream.ended = true;
142
+ clearTimeout(stream.timer);
143
+ this._pendingStreams.delete(data.id);
144
+ if (stream.waiter) {
145
+ const w = stream.waiter;
146
+ stream.waiter = null;
147
+ w({ value: undefined, done: true });
148
+ }
149
+ return;
150
+ }
151
+ if (data.chunk !== undefined) {
152
+ // Reset timeout on each chunk — the stream is alive.
153
+ clearTimeout(stream.timer);
154
+ stream.timer = setTimeout(() => this._streamTimeout(data.id), this._timeoutMs);
155
+ if (stream.waiter) {
156
+ // Consumer is waiting — deliver directly.
157
+ const w = stream.waiter;
158
+ stream.waiter = null;
159
+ w({ value: data.chunk, done: false });
160
+ }
161
+ else {
162
+ // Consumer hasn't called next() yet — buffer.
163
+ stream.buffer.push(data.chunk);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ _streamTimeout(id) {
169
+ const stream = this._pendingStreams.get(id);
170
+ if (!stream)
171
+ return;
172
+ stream.ended = true;
173
+ stream.error = 'Stream timed out';
174
+ this._pendingStreams.delete(id);
175
+ if (stream.waiter) {
176
+ const w = stream.waiter;
177
+ stream.waiter = null;
178
+ w({ value: undefined, done: true });
179
+ }
180
+ }
181
+ _generateId() {
182
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
183
+ return crypto.randomUUID();
184
+ }
185
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
186
+ }
187
+ // ── Public API ────────────────────────────────────────────────────────────
188
+ /**
189
+ * Send a JSON API call through the PostMessage relay.
190
+ *
191
+ * @param path - API endpoint path (e.g. '/api/hall/rooms/my-room/join').
192
+ * @param method - HTTP method.
193
+ * @param body - JSON-serialized request body.
194
+ * @param headers - Extra headers to forward.
195
+ * @returns The parsed JSON response body.
196
+ * @throws {Error} If the transport is closed, the request times out, or the
197
+ * parent responds with an error.
198
+ */
199
+ async call(path, method = 'POST', body, headers) {
200
+ if (this._closed)
201
+ throw new Error('WorkshopTransport is closed');
202
+ const id = this._generateId();
203
+ return new Promise((resolve, reject) => {
204
+ const timer = setTimeout(() => {
205
+ this._pendingCalls.delete(id);
206
+ reject(new Error(`WorkshopTransport: call to ${path} timed out after ${this._timeoutMs}ms`));
207
+ }, this._timeoutMs);
208
+ this._pendingCalls.set(id, { resolve, reject, timer });
209
+ const message = {
210
+ type: 'workshop:request',
211
+ id,
212
+ path,
213
+ method,
214
+ body,
215
+ headers,
216
+ };
217
+ this._parentWindow.postMessage(message, this._targetOrigin);
218
+ });
219
+ }
220
+ /**
221
+ * Send a streaming API call through the PostMessage relay.
222
+ *
223
+ * The parent frame reads the HTTP response as SSE chunks and forwards each as
224
+ * a `workshop:stream` message. This method yields each chunk as it arrives.
225
+ *
226
+ * @param path - API endpoint path (e.g. '/api/ai/chat').
227
+ * @param method - HTTP method (typically 'POST').
228
+ * @param body - JSON-serialized request body.
229
+ * @param headers - Extra headers to forward.
230
+ * @returns An async iterable of string chunks.
231
+ */
232
+ stream(path, method = 'POST', body, headers) {
233
+ if (this._closed)
234
+ throw new Error('WorkshopTransport is closed');
235
+ const id = this._generateId();
236
+ const pending = {
237
+ buffer: [],
238
+ ended: false,
239
+ waiter: null,
240
+ timer: setTimeout(() => this._streamTimeout(id), this._timeoutMs),
241
+ };
242
+ this._pendingStreams.set(id, pending);
243
+ const message = {
244
+ type: 'workshop:request',
245
+ id,
246
+ path,
247
+ method,
248
+ body,
249
+ stream: true,
250
+ headers,
251
+ };
252
+ this._parentWindow.postMessage(message, this._targetOrigin);
253
+ // Return an AsyncIterable that pulls from the pending stream state.
254
+ return {
255
+ [Symbol.asyncIterator]: () => ({
256
+ next: () => {
257
+ // Check for buffered data first.
258
+ if (pending.buffer.length > 0) {
259
+ return Promise.resolve({ value: pending.buffer.shift(), done: false });
260
+ }
261
+ // Stream ended — check for error.
262
+ if (pending.ended) {
263
+ if (pending.error) {
264
+ return Promise.reject(new Error(`WorkshopTransport stream error: ${pending.error}`));
265
+ }
266
+ return Promise.resolve({ value: undefined, done: true });
267
+ }
268
+ // Wait for the next chunk.
269
+ return new Promise((resolve) => {
270
+ pending.waiter = resolve;
271
+ });
272
+ },
273
+ }),
274
+ };
275
+ }
276
+ /**
277
+ * Returns `true` if the transport is open and usable.
278
+ */
279
+ isAlive() {
280
+ return !this._closed;
281
+ }
282
+ /**
283
+ * Close the transport, removing the message listener and rejecting all pending calls.
284
+ */
285
+ close() {
286
+ if (this._closed)
287
+ return;
288
+ this._closed = true;
289
+ this._listenerWindow.removeEventListener('message', this._messageListener);
290
+ const callError = new Error('WorkshopTransport closed');
291
+ for (const [id, pending] of this._pendingCalls) {
292
+ clearTimeout(pending.timer);
293
+ pending.reject(callError);
294
+ this._pendingCalls.delete(id);
295
+ }
296
+ for (const [id, stream] of this._pendingStreams) {
297
+ clearTimeout(stream.timer);
298
+ stream.ended = true;
299
+ stream.error = 'Transport closed';
300
+ this._pendingStreams.delete(id);
301
+ if (stream.waiter) {
302
+ stream.waiter({ value: undefined, done: true });
303
+ }
304
+ }
305
+ }
306
+ }
307
+ //# sourceMappingURL=workshop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workshop.js","sourceRoot":"","sources":["../../src/transports/workshop.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -4,8 +4,12 @@
4
4
  * interface and configuration options for both server and client modes. All module
5
5
  * namespaces (brainy, vfs, auth, etc.) hang off of SoulcraftSDK.
6
6
  */
7
- /** Products that can register as OIDC clients of auth.soulcraft.com. */
8
- export type SoulcraftProduct = 'workshop' | 'venue' | 'academy' | 'portal';
7
+ import type { SoulcraftProduct } from './modules/auth/products.js';
8
+ /**
9
+ * Products that can register as OIDC clients of auth.soulcraft.com.
10
+ * Derived from `SOULCRAFT_PRODUCTS` registry — see `modules/auth/products.ts`.
11
+ */
12
+ export type { SoulcraftProduct };
9
13
  /** Brainy instance pooling strategies. */
10
14
  export type InstanceStrategy = 'per-user' | 'per-tenant' | 'per-scope';
11
15
  /** Transport protocols supported by the client SDK. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ # ADR-004: Centralized Product Registry
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-03-10
5
+ **Location:** `sdk/src/modules/auth/products.ts`
6
+
7
+ ---
8
+
9
+ ## Context
10
+
11
+ Before this change, adding a new Soulcraft product (e.g. Pulse) required editing
12
+ **five separate hardcoded arrays** in `auth/apps/server/server.ts`:
13
+
14
+ 1. `trustedOrigins` — callback URL allowlist for better-auth
15
+ 2. `cors({ origin: [...] })` — Hono CORS origin list
16
+ 3. `BACKCHANNEL_CLIENTS` — products that receive OIDC Logout Tokens on sign-out
17
+ 4. `trustedClients` — OIDC client registrations (clientId, secret, redirectUrls)
18
+ 5. Status page HTML — product badges displayed on the auth home page
19
+
20
+ Missing any one caused a cryptic 403 on sign-in. Each array had a different shape,
21
+ different ordering, and no compile-time enforcement that they were in sync.
22
+
23
+ ---
24
+
25
+ ## Decision
26
+
27
+ Create a **single typed registry** (`SOULCRAFT_PRODUCTS`) in the SDK. The auth server
28
+ derives all five arrays from it at startup — no hardcoded product lists in server.ts.
29
+
30
+ ### Registry location
31
+
32
+ `@soulcraft/sdk/src/modules/auth/products.ts` — exported from `@soulcraft/sdk/server`.
33
+
34
+ ### Why the SDK (not the auth package)?
35
+
36
+ - The SDK is the cross-project dependency that all products already import.
37
+ - `SoulcraftProduct` was already a type in `sdk/src/types.ts` — the registry
38
+ simply replaces the manual union with a derived one.
39
+ - The auth server already imports from the SDK for session config and user fields.
40
+
41
+ ### Key design choices
42
+
43
+ **`as const satisfies Record<string, ProductRegistration>`**
44
+ Preserves literal types (so `keyof typeof SOULCRAFT_PRODUCTS` gives the exact union)
45
+ while enforcing all required fields at the definition site. Adding a partial entry
46
+ fails immediately with a specific error, not a downstream 403.
47
+
48
+ **`SoulcraftProduct` derived from registry keys**
49
+ Before: `type SoulcraftProduct = 'workshop' | 'venue' | 'academy' | 'portal'`
50
+ After: `type SoulcraftProduct = keyof typeof SOULCRAFT_PRODUCTS`
51
+ Adding a product to the registry extends the type automatically.
52
+
53
+ **Startup validation**
54
+ Products with `backchannelRequired: true` must have their secret env var set.
55
+ The server throws at startup (not at sign-in) with a clear list of missing vars.
56
+
57
+ **Optional products**
58
+ Cookie-proxy products (Pulse) have `backchannelRequired: false`. Their secret is
59
+ best-effort — the server starts without it, logs a notice, and the product is
60
+ excluded from backchannel logout. This matches Pulse's current status (analytics
61
+ dashboard; session termination is not critical path).
62
+
63
+ ---
64
+
65
+ ## Adding a new product
66
+
67
+ 1. Add an entry to `SOULCRAFT_PRODUCTS` in `sdk/src/modules/auth/products.ts`.
68
+ TypeScript will error if any required field is missing.
69
+
70
+ 2. Add the product's secret env var to `.env.production` on `auth-vm`:
71
+ ```bash
72
+ MYPRODUCT_OIDC_CLIENT_SECRET=$(openssl rand -hex 32)
73
+ ```
74
+
75
+ 3. Publish `@soulcraft/sdk` (version bump handled by release script).
76
+
77
+ 4. Deploy the auth server — it reads the new registry and configures all arrays.
78
+
79
+ 5. Set `SOULCRAFT_IDP_URL=https://auth.soulcraft.com` in the product's own
80
+ `.env.production` to activate OIDC client mode.
81
+
82
+ ---
83
+
84
+ ## Consequences
85
+
86
+ **Positive:**
87
+ - One place to add a product; TypeScript enforces completeness.
88
+ - Auth server startup fails fast with a clear error if secrets are missing.
89
+ - Status page and CORS list update automatically.
90
+ - No risk of a product being present in origins but missing from OIDC clients (or vice versa).
91
+
92
+ **Trade-off:**
93
+ - Auth server now depends on `@soulcraft/sdk`. Managed via `file:../../../sdk`
94
+ in `package.json` (local dev) — bundled into `dist/server.js` at build time,
95
+ so no runtime dependency on the symlink in production.
96
+
97
+ ---
98
+
99
+ ## Files changed
100
+
101
+ | File | Change |
102
+ |------|--------|
103
+ | `sdk/src/modules/auth/products.ts` | New — registry + derivation helpers |
104
+ | `sdk/src/types.ts` | `SoulcraftProduct` now re-exported from `products.ts` |
105
+ | `sdk/src/server/index.ts` | Added `SOULCRAFT_PRODUCTS`, `deriveOrigins`, `deriveRedirectUrls` exports |
106
+ | `sdk/src/index.ts` | Added `SoulcraftProduct` export |
107
+ | `auth/apps/server/package.json` | Added `@soulcraft/sdk` dependency |
108
+ | `auth/apps/server/server.ts` | All product arrays derived from registry |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/sdk",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "The unified Soulcraft platform SDK — data, auth, AI, billing, and notifications",
5
5
  "type": "module",
6
6
  "publishConfig": {