@dotbots-boutique/server-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.
package/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # @dotbots-boutique/server-sdk
2
+
3
+ The official backend SDK for [DotBots Boutique](https://dotbots.boutique). Use this package to make AI calls, trigger payments and retrieve user information from your Deno backend — without a browser or user session.
4
+
5
+ ## When to use this SDK
6
+
7
+ Use the backend SDK when logic must run server-side:
8
+
9
+ - Webhook handlers
10
+ - Background jobs and scheduled tasks
11
+ - Multi-step backend processing flows
12
+ - Any operation that runs without a user actively waiting in the browser
13
+
14
+ For user-facing interactions in the browser, use the frontend SDK (`@dotbots-boutique/auth-sdk`).
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @dotbots-boutique/server-sdk
20
+ ```
21
+
22
+ Or in Deno:
23
+
24
+ ```typescript
25
+ import { DotBotsBackend } from 'npm:@dotbots-boutique/server-sdk';
26
+ ```
27
+
28
+ ## Environment variables
29
+
30
+ These are injected automatically by the DotBots platform — no manual configuration needed:
31
+
32
+ ```
33
+ DOTBOTS_APP_ID=uuid
34
+ DOTBOTS_APP_SECRET=...
35
+ DOTBOTS_API_URL=https://api.dotbots.ai
36
+ ENVIRONMENT=test|prod
37
+ ```
38
+
39
+ ## Setup
40
+
41
+ Initialise once at app startup, before handling any requests:
42
+
43
+ ```typescript
44
+ import { DotBotsBackend } from 'npm:@dotbots-boutique/server-sdk';
45
+
46
+ const dotbots = new DotBotsBackend({
47
+ appId: Deno.env.get('DOTBOTS_APP_ID')!,
48
+ appSecret: Deno.env.get('DOTBOTS_APP_SECRET')!,
49
+ apiUrl: Deno.env.get('DOTBOTS_API_URL') ?? 'https://api.dotbots.ai',
50
+ environment: Deno.env.get('ENVIRONMENT') ?? 'prod'
51
+ });
52
+
53
+ // Must be called before any other methods
54
+ await dotbots.initialize();
55
+ ```
56
+
57
+ `initialize()` fetches the proxy URL from the platform. It must complete before any AI calls, payments or user lookups are made.
58
+
59
+ ## AI calls
60
+
61
+ ```typescript
62
+ // orgId required when feature is configured as paidBy = 'org' in the platform (default)
63
+ const response = await dotbots.ai('generate-summary', {
64
+ messages: [
65
+ { role: 'user', content: 'Summarise the following document: ...' }
66
+ ],
67
+ orgId: user.orgId
68
+ });
69
+
70
+ console.log(response.content);
71
+ console.log(`Cost: ${response.cost.tokens} tokens`);
72
+
73
+ // Feature configured as paidBy = 'app' in platform — no orgId needed
74
+ const response = await dotbots.ai('internal-classification', {
75
+ messages: [{ role: 'user', content: 'Classify this text...' }]
76
+ });
77
+ ```
78
+
79
+ ## Streaming
80
+
81
+ ```typescript
82
+ let result = '';
83
+
84
+ await dotbots.aiStream(
85
+ 'generate-text',
86
+ {
87
+ messages: [{ role: 'user', content: 'Write a report...' }],
88
+ orgId: user.orgId
89
+ },
90
+ (delta) => {
91
+ result += delta;
92
+ // optionally forward to client via SSE
93
+ },
94
+ (finalResponse) => {
95
+ console.log(`Completed — cost: ${finalResponse.cost.tokens} tokens`);
96
+ }
97
+ );
98
+ ```
99
+
100
+ ## Payments
101
+
102
+ The `paidBy` value is configured per feature in the platform — default is `org`. Your code only needs to provide `orgId` when the feature charges the organisation:
103
+
104
+ ```typescript
105
+ // Feature paidBy = 'org' (default) — provide orgId
106
+ await dotbots.charge('data-export', {
107
+ orgId: user.orgId,
108
+ quantity: 1
109
+ });
110
+
111
+ // Feature paidBy = 'app' — no orgId needed
112
+ await dotbots.charge('internal-job', {
113
+ quantity: 1
114
+ });
115
+ ```
116
+
117
+ ## Getting user info
118
+
119
+ ```typescript
120
+ const user = await dotbots.getUser(userId);
121
+ console.log(user.name, user.orgId, user.roles);
122
+ ```
123
+
124
+ ## Error handling
125
+
126
+ ```typescript
127
+ import { DotBotsBackend, DotBotsBackendError } from 'npm:@dotbots-boutique/server-sdk';
128
+
129
+ try {
130
+ const response = await dotbots.ai('generate-text', { messages, orgId });
131
+ } catch (error) {
132
+ if (error instanceof DotBotsBackendError) {
133
+ switch (error.code) {
134
+ case 'AI_INSUFFICIENT_BALANCE':
135
+ // Org has no tokens — notify user
136
+ break;
137
+ case 'AI_FEATURE_NOT_FOUND':
138
+ // Feature not configured in platform — developer error
139
+ console.error('AI feature not configured in DotBots platform');
140
+ break;
141
+ case 'AI_CALL_FAILED':
142
+ // Provider error — retry or fallback
143
+ break;
144
+ case 'UNAUTHORIZED':
145
+ // Invalid app secret — check DOTBOTS_APP_SECRET env variable
146
+ break;
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## Error codes
153
+
154
+ | Code | Description |
155
+ |------|-------------|
156
+ | `AI_FEATURE_NOT_FOUND` | Feature not configured in the platform |
157
+ | `AI_PROVIDER_NOT_CONFIGURED` | API key not set for the AI provider |
158
+ | `AI_INSUFFICIENT_BALANCE` | Organisation balance is 0 and awaiting top-up |
159
+ | `AI_CALL_FAILED` | AI provider returned an error |
160
+ | `CHARGE_FAILED` | Payment failed |
161
+ | `USER_NOT_FOUND` | User not found |
162
+ | `UNAUTHORIZED` | Invalid app secret |
163
+
164
+ ## Frontend SDK vs Backend SDK
165
+
166
+ | | Frontend SDK | Backend SDK |
167
+ |--|-------------|-------------|
168
+ | Package | `@dotbots-boutique/auth-sdk` | `@dotbots-boutique/server-sdk` |
169
+ | Runtime | Browser | Deno / Node |
170
+ | Auth | User JWT (iframe) | App secret |
171
+ | AI calls | ✅ | ✅ |
172
+ | Payments | ✅ | ✅ |
173
+ | User context | Automatic | Manual via `orgId` |
174
+ | Use case | User interactions | Webhooks, jobs, server logic |
175
+
176
+ **Rule of thumb:** if there is a user actively waiting in the browser, use the frontend SDK. If the logic runs in the background or server-side, use the backend SDK.
177
+
178
+ ## Security
179
+
180
+ - `DOTBOTS_APP_SECRET` is injected by the platform and must never be logged, returned in API responses or stored anywhere other than environment variables.
181
+ - The proxy URL is fetched dynamically at startup — never hardcode it.
182
+ - All communication between the backend SDK and the proxy is server-to-server. The app secret is never exposed to the browser.
183
+
184
+ ## Links
185
+
186
+ - [DotBots Boutique](https://dotbots.boutique)
187
+ - [Developer documentation](https://docs.dotbots.boutique)
188
+ - [Frontend SDK](https://www.npmjs.com/package/@dotbots-boutique/auth-sdk)
@@ -0,0 +1,23 @@
1
+ import type { AiResponse, BackendAiRequest, DotBotsBackendConfig, DotBotsUser } from './types';
2
+ export declare class DotBotsBackend {
3
+ private appId;
4
+ private appSecret;
5
+ private apiUrl;
6
+ private environment;
7
+ private proxyUrl;
8
+ constructor(config: DotBotsBackendConfig);
9
+ initialize(): Promise<void>;
10
+ ai(feature: string, request: BackendAiRequest): Promise<AiResponse>;
11
+ aiStream(feature: string, request: BackendAiRequest, onDelta: (delta: string) => void, onDone?: (response: AiResponse) => void): Promise<void>;
12
+ charge(featureCode: string, options: {
13
+ paidBy?: 'org' | 'app';
14
+ orgId?: string;
15
+ quantity?: number;
16
+ }): Promise<{
17
+ transactionId: string;
18
+ amount: bigint;
19
+ }>;
20
+ getUser(userId: string): Promise<DotBotsUser>;
21
+ private baseHeaders;
22
+ private getProxyUrl;
23
+ }
package/dist/client.js ADDED
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DotBotsBackend = void 0;
4
+ const errors_1 = require("./errors");
5
+ class DotBotsBackend {
6
+ appId;
7
+ appSecret;
8
+ apiUrl;
9
+ environment;
10
+ proxyUrl = null;
11
+ constructor(config) {
12
+ this.appId = config.appId;
13
+ this.appSecret = config.appSecret;
14
+ this.apiUrl = config.apiUrl;
15
+ this.environment = config.environment;
16
+ }
17
+ async initialize() {
18
+ const response = await fetch(`${this.apiUrl}/api/proxy/config`, {
19
+ headers: this.baseHeaders(),
20
+ });
21
+ if (!response.ok) {
22
+ throw new Error(`Failed to fetch proxy config: ${response.status}`);
23
+ }
24
+ const data = (await response.json());
25
+ this.proxyUrl = data.proxyUrl;
26
+ console.log(JSON.stringify({
27
+ level: 'info',
28
+ message: `[DotBotsBackend] Proxy config loaded — proxyUrl: ${data.proxyUrl}`,
29
+ }));
30
+ }
31
+ async ai(feature, request) {
32
+ const response = await fetch(`${this.getProxyUrl()}/ai/call`, {
33
+ method: 'POST',
34
+ headers: this.baseHeaders(),
35
+ body: JSON.stringify({
36
+ feature,
37
+ messages: request.messages,
38
+ tools: request.tools,
39
+ stream: false,
40
+ maxTokens: request.maxTokens,
41
+ paidBy: request.paidBy,
42
+ orgId: request.orgId,
43
+ }),
44
+ });
45
+ if (!response.ok) {
46
+ const body = (await response.json().catch(() => ({})));
47
+ throw new errors_1.DotBotsBackendError(body.error ?? 'AI_CALL_FAILED', response.status);
48
+ }
49
+ return (await response.json());
50
+ }
51
+ async aiStream(feature, request, onDelta, onDone) {
52
+ const response = await fetch(`${this.getProxyUrl()}/ai/call`, {
53
+ method: 'POST',
54
+ headers: this.baseHeaders(),
55
+ body: JSON.stringify({
56
+ feature,
57
+ messages: request.messages,
58
+ tools: request.tools,
59
+ stream: true,
60
+ maxTokens: request.maxTokens,
61
+ paidBy: request.paidBy,
62
+ orgId: request.orgId,
63
+ }),
64
+ });
65
+ if (!response.ok) {
66
+ const body = (await response.json().catch(() => ({})));
67
+ throw new errors_1.DotBotsBackendError(body.error ?? 'AI_CALL_FAILED', response.status);
68
+ }
69
+ const reader = response.body.getReader();
70
+ const decoder = new TextDecoder();
71
+ let buffer = '';
72
+ while (true) {
73
+ const { done, value } = await reader.read();
74
+ if (done)
75
+ break;
76
+ buffer += decoder.decode(value, { stream: true });
77
+ const lines = buffer.split('\n');
78
+ buffer = lines.pop() ?? '';
79
+ for (const line of lines) {
80
+ if (!line.startsWith('data: '))
81
+ continue;
82
+ const data = JSON.parse(line.slice(6));
83
+ if (data.type === 'text')
84
+ onDelta(data.delta);
85
+ else if (data.type === 'done')
86
+ onDone?.(data);
87
+ }
88
+ }
89
+ }
90
+ async charge(featureCode, options) {
91
+ const response = await fetch(`${this.getProxyUrl()}/charge`, {
92
+ method: 'POST',
93
+ headers: this.baseHeaders(),
94
+ body: JSON.stringify({
95
+ featureCode,
96
+ paidBy: options.paidBy,
97
+ orgId: options.orgId,
98
+ quantity: options.quantity ?? 1,
99
+ }),
100
+ });
101
+ if (!response.ok) {
102
+ const body = (await response.json().catch(() => ({})));
103
+ throw new errors_1.DotBotsBackendError(body.error ?? 'CHARGE_FAILED', response.status);
104
+ }
105
+ return (await response.json());
106
+ }
107
+ async getUser(userId) {
108
+ const response = await fetch(`${this.getProxyUrl()}/users/${userId}`, { headers: this.baseHeaders() });
109
+ if (!response.ok) {
110
+ throw new errors_1.DotBotsBackendError('USER_NOT_FOUND', response.status);
111
+ }
112
+ return (await response.json());
113
+ }
114
+ baseHeaders() {
115
+ return {
116
+ 'Content-Type': 'application/json',
117
+ 'X-App-Id': this.appId,
118
+ 'X-App-Secret': this.appSecret,
119
+ 'X-Environment': this.environment,
120
+ };
121
+ }
122
+ getProxyUrl() {
123
+ if (!this.proxyUrl) {
124
+ throw new Error('DotBotsBackend not initialised — call initialize() first');
125
+ }
126
+ return this.proxyUrl;
127
+ }
128
+ }
129
+ exports.DotBotsBackend = DotBotsBackend;
@@ -0,0 +1,5 @@
1
+ export declare class DotBotsBackendError extends Error {
2
+ code: string;
3
+ status: number;
4
+ constructor(code: string, status: number);
5
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DotBotsBackendError = void 0;
4
+ class DotBotsBackendError extends Error {
5
+ code;
6
+ status;
7
+ constructor(code, status) {
8
+ super(code);
9
+ this.code = code;
10
+ this.status = status;
11
+ this.name = 'DotBotsBackendError';
12
+ }
13
+ }
14
+ exports.DotBotsBackendError = DotBotsBackendError;
@@ -0,0 +1,6 @@
1
+ export { DotBotsBackend } from './client';
2
+ export { DotBotsBackendError } from './errors';
3
+ export type { AiMessage, AiResponse, AiTool, AiToolCall, AiToolParameter, BackendAiRequest, DotBotsBackendConfig, DotBotsUser, } from './types';
4
+ export { AppSecretsStore } from './proxy/app-secrets';
5
+ export { handleAiCall, handleCharge, handleGetUser, handleInternalAppSecrets, resolveAuth, } from './proxy/handlers';
6
+ export type { AuthResult, CryptoHelpers, DbClient, FeatureConfig, JwtPayload, UserRecord, } from './proxy/types';
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAuth = exports.handleInternalAppSecrets = exports.handleGetUser = exports.handleCharge = exports.handleAiCall = exports.AppSecretsStore = exports.DotBotsBackendError = exports.DotBotsBackend = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "DotBotsBackend", { enumerable: true, get: function () { return client_1.DotBotsBackend; } });
6
+ var errors_1 = require("./errors");
7
+ Object.defineProperty(exports, "DotBotsBackendError", { enumerable: true, get: function () { return errors_1.DotBotsBackendError; } });
8
+ var app_secrets_1 = require("./proxy/app-secrets");
9
+ Object.defineProperty(exports, "AppSecretsStore", { enumerable: true, get: function () { return app_secrets_1.AppSecretsStore; } });
10
+ var handlers_1 = require("./proxy/handlers");
11
+ Object.defineProperty(exports, "handleAiCall", { enumerable: true, get: function () { return handlers_1.handleAiCall; } });
12
+ Object.defineProperty(exports, "handleCharge", { enumerable: true, get: function () { return handlers_1.handleCharge; } });
13
+ Object.defineProperty(exports, "handleGetUser", { enumerable: true, get: function () { return handlers_1.handleGetUser; } });
14
+ Object.defineProperty(exports, "handleInternalAppSecrets", { enumerable: true, get: function () { return handlers_1.handleInternalAppSecrets; } });
15
+ Object.defineProperty(exports, "resolveAuth", { enumerable: true, get: function () { return handlers_1.resolveAuth; } });
@@ -0,0 +1,14 @@
1
+ import type { CryptoHelpers, DbClient } from './types';
2
+ export declare class AppSecretsStore {
3
+ private secrets;
4
+ private db;
5
+ private crypto;
6
+ constructor(db: DbClient, crypto: CryptoHelpers);
7
+ loadFromDatabase(): Promise<void>;
8
+ upsert(appId: string, secret: string): Promise<void>;
9
+ validate(appId: string, secret: string): boolean;
10
+ validateRequest(req: Request): {
11
+ appId: string;
12
+ valid: boolean;
13
+ };
14
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppSecretsStore = void 0;
4
+ class AppSecretsStore {
5
+ secrets = new Map();
6
+ db;
7
+ crypto;
8
+ constructor(db, crypto) {
9
+ this.db = db;
10
+ this.crypto = crypto;
11
+ }
12
+ async loadFromDatabase() {
13
+ const result = await this.db.queryObject('SELECT app_id, secret_encrypted FROM app_secrets');
14
+ for (const row of result.rows) {
15
+ const plaintext = await this.crypto.decrypt(row.secret_encrypted);
16
+ this.secrets.set(row.app_id, plaintext);
17
+ }
18
+ }
19
+ async upsert(appId, secret) {
20
+ const encrypted = await this.crypto.encrypt(secret);
21
+ await this.db.queryObject(`INSERT INTO app_secrets (app_id, secret_encrypted, updated_at)
22
+ VALUES ($1, $2, NOW())
23
+ ON CONFLICT (app_id) DO UPDATE
24
+ SET secret_encrypted = $2, updated_at = NOW()`, [appId, encrypted]);
25
+ this.secrets.set(appId, secret);
26
+ }
27
+ validate(appId, secret) {
28
+ const stored = this.secrets.get(appId);
29
+ if (!stored)
30
+ return false;
31
+ return stored === secret;
32
+ }
33
+ validateRequest(req) {
34
+ const appId = req.headers.get('x-app-id');
35
+ const appSecret = req.headers.get('x-app-secret');
36
+ if (!appId || !appSecret)
37
+ return { appId: '', valid: false };
38
+ return { appId, valid: this.validate(appId, appSecret) };
39
+ }
40
+ }
41
+ exports.AppSecretsStore = AppSecretsStore;
@@ -0,0 +1,21 @@
1
+ import type { AppSecretsStore } from './app-secrets';
2
+ import type { AuthResult, FeatureConfig, JwtPayload, UserRecord } from './types';
3
+ export declare function handleInternalAppSecrets(store: AppSecretsStore, platformSecret: string): (req: Request) => Promise<Response>;
4
+ export declare function resolveAuth(store: AppSecretsStore, decodeJwt: (token: string) => JwtPayload): (req: Request) => AuthResult | Response;
5
+ export declare function handleAiCall(store: AppSecretsStore, decodeJwt: (token: string) => JwtPayload, getFeatureConfig: (key: string) => FeatureConfig | undefined, processAiCall: (params: {
6
+ appId: string;
7
+ orgId: string | null;
8
+ feature: string;
9
+ messages: unknown[];
10
+ tools?: unknown[];
11
+ stream: boolean;
12
+ maxTokens?: number;
13
+ }) => Promise<Response>): (req: Request) => Promise<Response>;
14
+ export declare function handleCharge(store: AppSecretsStore, decodeJwt: (token: string) => JwtPayload, getFeatureConfig: (key: string) => FeatureConfig | undefined, processCharge: (params: {
15
+ appId: string;
16
+ orgId: string | null;
17
+ userId: string | null;
18
+ featureCode: string;
19
+ quantity: number;
20
+ }) => Promise<Response>): (req: Request) => Promise<Response>;
21
+ export declare function handleGetUser(store: AppSecretsStore, lookupUser: (appId: string, userId: string) => Promise<UserRecord | null>, fetchUserFromPlatform?: (appId: string, userId: string) => Promise<UserRecord | null>): (req: Request, userId: string) => Promise<Response>;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleInternalAppSecrets = handleInternalAppSecrets;
4
+ exports.resolveAuth = resolveAuth;
5
+ exports.handleAiCall = handleAiCall;
6
+ exports.handleCharge = handleCharge;
7
+ exports.handleGetUser = handleGetUser;
8
+ function jsonResponse(body, status = 200) {
9
+ return new Response(JSON.stringify(body), {
10
+ status,
11
+ headers: { 'Content-Type': 'application/json' },
12
+ });
13
+ }
14
+ function errorResponse(error, status) {
15
+ return jsonResponse({ error }, status);
16
+ }
17
+ // ---------------------------------------------------------------------------
18
+ // POST /internal/app-secrets
19
+ // ---------------------------------------------------------------------------
20
+ function handleInternalAppSecrets(store, platformSecret) {
21
+ return async (req) => {
22
+ if (req.headers.get('x-platform-secret') !== platformSecret) {
23
+ return errorResponse('UNAUTHORIZED', 401);
24
+ }
25
+ const body = (await req.json());
26
+ await store.upsert(body.appId, body.secret);
27
+ return new Response(null, { status: 200 });
28
+ };
29
+ }
30
+ // ---------------------------------------------------------------------------
31
+ // Dual auth: JWT or app secret
32
+ // ---------------------------------------------------------------------------
33
+ function resolveAuth(store, decodeJwt) {
34
+ return (req) => {
35
+ const authHeader = req.headers.get('authorization');
36
+ if (authHeader?.startsWith('Bearer ')) {
37
+ const jwt = decodeJwt(authHeader.replace('Bearer ', ''));
38
+ return { appId: jwt.app_id, orgId: jwt.org_id, userId: jwt.sub };
39
+ }
40
+ if (req.headers.get('x-app-secret')) {
41
+ const { appId, valid } = store.validateRequest(req);
42
+ if (!valid)
43
+ return errorResponse('UNAUTHORIZED', 401);
44
+ return { appId, orgId: null, userId: null };
45
+ }
46
+ return errorResponse('UNAUTHORIZED', 401);
47
+ };
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // POST /ai/call
51
+ // ---------------------------------------------------------------------------
52
+ function handleAiCall(store, decodeJwt, getFeatureConfig, processAiCall) {
53
+ const authenticate = resolveAuth(store, decodeJwt);
54
+ return async (req) => {
55
+ const auth = authenticate(req);
56
+ if (auth instanceof Response)
57
+ return auth;
58
+ const body = (await req.json());
59
+ const featureConfig = getFeatureConfig(`${auth.appId}:${body.feature}`);
60
+ if (!featureConfig) {
61
+ return errorResponse('AI_FEATURE_NOT_FOUND', 400);
62
+ }
63
+ const paidBy = featureConfig.paidBy ?? 'org';
64
+ let orgId = auth.orgId;
65
+ if (!orgId && paidBy === 'org') {
66
+ orgId = body.orgId ?? null;
67
+ if (!orgId) {
68
+ return errorResponse('ORG_ID_REQUIRED', 400);
69
+ }
70
+ }
71
+ return processAiCall({
72
+ appId: auth.appId,
73
+ orgId,
74
+ feature: body.feature,
75
+ messages: body.messages,
76
+ tools: body.tools,
77
+ stream: body.stream ?? false,
78
+ maxTokens: body.maxTokens,
79
+ });
80
+ };
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // POST /charge
84
+ // ---------------------------------------------------------------------------
85
+ function handleCharge(store, decodeJwt, getFeatureConfig, processCharge) {
86
+ const authenticate = resolveAuth(store, decodeJwt);
87
+ return async (req) => {
88
+ const auth = authenticate(req);
89
+ if (auth instanceof Response)
90
+ return auth;
91
+ const body = (await req.json());
92
+ const featureConfig = getFeatureConfig(`${auth.appId}:${body.featureCode}`);
93
+ if (!featureConfig) {
94
+ return errorResponse('FEATURE_NOT_FOUND', 400);
95
+ }
96
+ const paidBy = featureConfig.paidBy ?? 'org';
97
+ let orgId = auth.orgId;
98
+ if (!orgId && paidBy === 'org') {
99
+ orgId = body.orgId ?? null;
100
+ if (!orgId) {
101
+ return errorResponse('ORG_ID_REQUIRED', 400);
102
+ }
103
+ }
104
+ return processCharge({
105
+ appId: auth.appId,
106
+ orgId,
107
+ userId: auth.userId,
108
+ featureCode: body.featureCode,
109
+ quantity: body.quantity ?? 1,
110
+ });
111
+ };
112
+ }
113
+ // ---------------------------------------------------------------------------
114
+ // GET /users/:userId
115
+ // ---------------------------------------------------------------------------
116
+ function handleGetUser(store, lookupUser, fetchUserFromPlatform) {
117
+ return async (req, userId) => {
118
+ const { appId, valid } = store.validateRequest(req);
119
+ if (!valid) {
120
+ return errorResponse('UNAUTHORIZED', 401);
121
+ }
122
+ let user = await lookupUser(appId, userId);
123
+ if (!user && fetchUserFromPlatform) {
124
+ user = await fetchUserFromPlatform(appId, userId);
125
+ }
126
+ if (!user) {
127
+ return errorResponse('USER_NOT_FOUND', 404);
128
+ }
129
+ return jsonResponse({
130
+ id: user.id,
131
+ orgId: user.org_id,
132
+ roles: user.roles,
133
+ email: user.email,
134
+ name: user.name,
135
+ });
136
+ };
137
+ }
@@ -0,0 +1,3 @@
1
+ export { AppSecretsStore } from './app-secrets';
2
+ export { handleAiCall, handleCharge, handleGetUser, handleInternalAppSecrets, resolveAuth, } from './handlers';
3
+ export type { AuthResult, CryptoHelpers, DbClient, FeatureConfig, JwtPayload, UserRecord, } from './types';
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAuth = exports.handleInternalAppSecrets = exports.handleGetUser = exports.handleCharge = exports.handleAiCall = exports.AppSecretsStore = void 0;
4
+ var app_secrets_1 = require("./app-secrets");
5
+ Object.defineProperty(exports, "AppSecretsStore", { enumerable: true, get: function () { return app_secrets_1.AppSecretsStore; } });
6
+ var handlers_1 = require("./handlers");
7
+ Object.defineProperty(exports, "handleAiCall", { enumerable: true, get: function () { return handlers_1.handleAiCall; } });
8
+ Object.defineProperty(exports, "handleCharge", { enumerable: true, get: function () { return handlers_1.handleCharge; } });
9
+ Object.defineProperty(exports, "handleGetUser", { enumerable: true, get: function () { return handlers_1.handleGetUser; } });
10
+ Object.defineProperty(exports, "handleInternalAppSecrets", { enumerable: true, get: function () { return handlers_1.handleInternalAppSecrets; } });
11
+ Object.defineProperty(exports, "resolveAuth", { enumerable: true, get: function () { return handlers_1.resolveAuth; } });
@@ -0,0 +1,29 @@
1
+ export interface DbClient {
2
+ queryObject<T>(query: string, args?: unknown[]): Promise<{
3
+ rows: T[];
4
+ }>;
5
+ }
6
+ export interface CryptoHelpers {
7
+ encrypt(plaintext: string): Promise<string>;
8
+ decrypt(ciphertext: string): Promise<string>;
9
+ }
10
+ export interface JwtPayload {
11
+ sub: string;
12
+ org_id: string;
13
+ app_id: string;
14
+ }
15
+ export interface FeatureConfig {
16
+ paidBy?: 'org' | 'app';
17
+ }
18
+ export interface AuthResult {
19
+ appId: string | null;
20
+ orgId: string | null;
21
+ userId: string | null;
22
+ }
23
+ export interface UserRecord {
24
+ id: string;
25
+ org_id: string;
26
+ roles: string[];
27
+ email?: string;
28
+ name?: string;
29
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,58 @@
1
+ export interface DotBotsBackendConfig {
2
+ appId: string;
3
+ appSecret: string;
4
+ apiUrl: string;
5
+ environment: string;
6
+ }
7
+ export interface AiMessage {
8
+ role: 'system' | 'user' | 'assistant';
9
+ content: string;
10
+ }
11
+ export interface AiToolParameter {
12
+ type: string;
13
+ description?: string;
14
+ enum?: string[];
15
+ items?: AiToolParameter;
16
+ properties?: Record<string, AiToolParameter>;
17
+ required?: string[];
18
+ }
19
+ export interface AiTool {
20
+ name: string;
21
+ description: string;
22
+ parameters?: AiToolParameter;
23
+ }
24
+ export interface AiResponse {
25
+ content: string;
26
+ model: string;
27
+ provider: string;
28
+ usage: {
29
+ inputTokens: number;
30
+ outputTokens: number;
31
+ totalTokens: number;
32
+ };
33
+ cost: {
34
+ tokens: bigint;
35
+ eur: number;
36
+ };
37
+ transactionId: string;
38
+ toolCalls?: AiToolCall[];
39
+ }
40
+ export interface AiToolCall {
41
+ name: string;
42
+ arguments: Record<string, unknown>;
43
+ }
44
+ export interface BackendAiRequest {
45
+ messages: AiMessage[];
46
+ tools?: AiTool[];
47
+ maxTokens?: number;
48
+ paidBy?: 'org' | 'app';
49
+ orgId?: string;
50
+ stream?: boolean;
51
+ }
52
+ export interface DotBotsUser {
53
+ id: string;
54
+ orgId: string;
55
+ roles: string[];
56
+ email?: string;
57
+ name?: string;
58
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@dotbots-boutique/server-sdk",
3
+ "version": "0.1.0",
4
+ "description": "DotBots Backend SDK — server-side AI calls, payments, and user lookups via the DotBots proxy",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "dotbots",
16
+ "backend",
17
+ "sdk",
18
+ "ai",
19
+ "proxy"
20
+ ],
21
+ "license": "MIT",
22
+ "devDependencies": {
23
+ "@types/node": "^18.19.130",
24
+ "typescript": "^5.4.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ }
29
+ }