@rei-standard/amsg-server 2.6.0-next.2 → 2.6.0-next.21
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 +21 -0
- package/README.md +599 -24
- package/dist/adapters/d1.d.ts +416 -0
- package/dist/adapters/factory.d.ts +39 -0
- package/dist/adapters/interface.d.ts +306 -0
- package/dist/adapters/neon.d.ts +88 -0
- package/dist/adapters/pg-shared.d.ts +161 -0
- package/dist/adapters/pg.d.ts +84 -0
- package/dist/adapters/schema.d.ts +57 -0
- package/dist/adapters/schema.sqlite.d.ts +49 -0
- package/dist/chunk-7NMQFTDJ.cjs +148 -0
- package/dist/chunk-E7OWP3VL.cjs +135 -0
- package/dist/chunk-GN44PST5.mjs +148 -0
- package/dist/chunk-KOJQVOYS.mjs +5847 -0
- package/dist/chunk-QHVID3V2.cjs +5847 -0
- package/dist/chunk-ZGGF4GMA.mjs +135 -0
- package/dist/cloudflare/single-user-worker.d.ts +37 -0
- package/dist/cloudflare.cjs +39 -2
- package/dist/cloudflare.d.ts +10 -2
- package/dist/cloudflare.mjs +40 -3
- package/dist/handlers/cancel-message.d.ts +3 -0
- package/dist/handlers/capabilities.d.ts +4 -0
- package/dist/handlers/client-state.d.ts +7 -0
- package/dist/handlers/get-message.d.ts +3 -0
- package/dist/handlers/get-user-key.d.ts +3 -0
- package/dist/handlers/init-tenant.d.ts +23 -0
- package/dist/handlers/llm-credentials.d.ts +5 -0
- package/dist/handlers/messages.d.ts +3 -0
- package/dist/handlers/outbox.d.ts +7 -0
- package/dist/handlers/push-subscription.d.ts +5 -0
- package/dist/handlers/schedule-message.d.ts +3 -0
- package/dist/handlers/send-notifications.d.ts +3 -0
- package/dist/handlers/single-user-init.d.ts +13 -0
- package/dist/handlers/update-message.d.ts +3 -0
- package/dist/handlers/vapid-public-key.d.ts +19 -0
- package/dist/index.cjs +133 -44
- package/dist/index.d.cts +131 -766
- package/dist/index.d.ts +131 -766
- package/dist/index.mjs +125 -36
- package/dist/lib/agentic-fire.d.ts +108 -0
- package/dist/lib/client-state-store.d.ts +80 -0
- package/dist/lib/constant-time.d.ts +14 -0
- package/dist/lib/db-errors.d.ts +10 -0
- package/dist/lib/encryption.d.ts +48 -0
- package/dist/lib/errors.d.ts +215 -0
- package/dist/lib/llm-credentials-store.d.ts +147 -0
- package/dist/lib/llm.d.ts +1 -0
- package/dist/lib/message-processor.d.ts +73 -0
- package/dist/lib/outbox-store.d.ts +95 -0
- package/dist/lib/push-subscription-store.d.ts +94 -0
- package/dist/lib/recurrence.d.ts +51 -0
- package/dist/lib/request.d.ts +182 -0
- package/dist/lib/result-emitter.d.ts +54 -0
- package/dist/lib/run-tick.d.ts +115 -0
- package/dist/lib/schema-version.d.ts +50 -0
- package/dist/lib/state-accessors.d.ts +70 -0
- package/dist/lib/state-chunks.d.ts +55 -0
- package/dist/lib/task-projection.d.ts +92 -0
- package/dist/lib/validation.d.ts +97 -0
- package/dist/lib/version.d.ts +7 -0
- package/dist/lib/webcrypto-utils.d.ts +1 -0
- package/dist/lib/webpush-webcrypto.d.ts +78 -0
- package/dist/{neon-BFUS25UX.cjs → neon-KP2CPA57.cjs} +110 -10
- package/dist/{neon-CU5N3CSW.mjs → neon-ZIMHALYI.mjs} +106 -6
- package/dist/{pg-QO6NKTGL.mjs → pg-QGC2XHUT.mjs} +94 -5
- package/dist/{pg-IIH3M4OM.cjs → pg-RZPQGXR3.cjs} +98 -9
- package/dist/single-user.d.ts +95 -0
- package/dist/tenant/blob-store.d.ts +30 -0
- package/dist/tenant/context.d.ts +47 -0
- package/dist/tenant/single-user-context.d.ts +29 -0
- package/dist/tenant/token.d.ts +26 -0
- package/package.json +3 -3
- package/dist/chunk-5ENJGVZX.mjs +0 -72
- package/dist/chunk-GN5QTMCL.cjs +0 -1870
- package/dist/chunk-PVNLB6TF.mjs +0 -1870
- package/dist/chunk-RGECD4OH.cjs +0 -72
- package/dist/cloudflare-B-E51VjP.d.cts +0 -2848
- package/dist/cloudflare-B-E51VjP.d.ts +0 -2848
- package/dist/cloudflare.d.cts +0 -2
- package/dist/neon-CJl66EGy.d.cts +0 -251
- package/dist/neon-DYvGnCzx.d.ts +0 -251
- package/dist/pg-07S-u_H4.d.cts +0 -246
- package/dist/pg-Du-pN_UT.d.ts +0 -246
- package/dist/schema-C8OnYk6j.d.cts +0 -74
- package/dist/schema-C8OnYk6j.d.ts +0 -74
package/dist/index.d.cts
CHANGED
|
@@ -1,705 +1,9 @@
|
|
|
1
|
-
import { g as getHeader, p as parseJsonBody, i as isValidUUIDv4, r as runScheduledTick, c as createMessagesHandler, a as createCancelMessageHandler, b as createUpdateMessageHandler, d as createScheduleMessageHandler, e as createGetUserKeyHandler } from './cloudflare-B-E51VjP.cjs';
|
|
2
|
-
export { f as createD1Adapter, h as createSingleUserCloudflareWorker, j as createSingleUserServer, k as createWebCryptoWebPush, l as decryptFromStorage, m as decryptPayload, n as deriveUserEncryptionKey, o as encryptForStorage, q as isValidISO8601, s as isValidUUID, t as isValidUrl, v as validateLlmMessagesArray, u as validateScheduleMessagePayload, w as validateSplitPattern } from './cloudflare-B-E51VjP.cjs';
|
|
3
|
-
import { createHash, randomBytes, createCipheriv, createDecipheriv, timingSafeEqual, createHmac, randomUUID } from 'crypto';
|
|
4
|
-
import { normalizeVapidSubject } from '@rei-standard/amsg-shared';
|
|
5
|
-
export { validateAvatarUrl } from '@rei-standard/amsg-shared';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Adapter Factory
|
|
9
|
-
* ReiStandard SDK v2.0.1
|
|
10
|
-
*
|
|
11
|
-
* Creates a database adapter instance based on the supplied configuration.
|
|
12
|
-
*
|
|
13
|
-
* @typedef {'neon'|'pg'} DriverName
|
|
14
|
-
*
|
|
15
|
-
* @typedef {Object} AdapterConfig
|
|
16
|
-
* @property {DriverName} driver - Which database driver to use.
|
|
17
|
-
* @property {string} connectionString - Database connection URL.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a database adapter.
|
|
22
|
-
*
|
|
23
|
-
* @param {AdapterConfig} config
|
|
24
|
-
* @returns {Promise<import('./interface.js').DbAdapter>}
|
|
25
|
-
*/
|
|
26
|
-
async function createAdapter(config) {
|
|
27
|
-
if (!config || !config.driver) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
'[rei-standard-amsg-server] "driver" is required in the db config. ' +
|
|
30
|
-
'Supported drivers: neon, pg'
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!config.connectionString) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
'[rei-standard-amsg-server] "connectionString" is required in the db config.'
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
switch (config.driver) {
|
|
41
|
-
case 'neon': {
|
|
42
|
-
const { NeonAdapter } = await import('./neon-CJl66EGy.d.cts');
|
|
43
|
-
return new NeonAdapter(config.connectionString);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
case 'pg': {
|
|
47
|
-
const { PgAdapter } = await import('./pg-07S-u_H4.d.cts');
|
|
48
|
-
return new PgAdapter(config.connectionString);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
default:
|
|
52
|
-
throw new Error(
|
|
53
|
-
`[rei-standard-amsg-server] Unsupported driver "${config.driver}". ` +
|
|
54
|
-
'Supported drivers: neon, pg'
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Handler: init-tenant
|
|
61
|
-
* ReiStandard SDK v2.0.1
|
|
62
|
-
*
|
|
63
|
-
* @param {Object} ctx - Server context.
|
|
64
|
-
* @returns {{ POST: function }}
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function createInitTenantHandler(ctx) {
|
|
69
|
-
async function POST(headers, body) {
|
|
70
|
-
const initSecret = getHeader(headers, 'x-init-secret');
|
|
71
|
-
|
|
72
|
-
// If initSecret is configured, require a matching X-Init-Secret.
|
|
73
|
-
if (ctx.tenant.initSecret && initSecret !== ctx.tenant.initSecret) {
|
|
74
|
-
return {
|
|
75
|
-
status: 401,
|
|
76
|
-
body: {
|
|
77
|
-
success: false,
|
|
78
|
-
error: {
|
|
79
|
-
code: 'INVALID_INIT_AUTH',
|
|
80
|
-
message: '初始化鉴权失败'
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const parsed = parseJsonBody(body);
|
|
87
|
-
if (!parsed.ok) {
|
|
88
|
-
return {
|
|
89
|
-
status: 400,
|
|
90
|
-
body: {
|
|
91
|
-
success: false,
|
|
92
|
-
error: parsed.error
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const payload = parsed.data;
|
|
98
|
-
const tenantId = payload.tenantId || undefined;
|
|
99
|
-
const driver = payload.driver;
|
|
100
|
-
const databaseUrl = payload.databaseUrl || payload.connectionString;
|
|
101
|
-
|
|
102
|
-
if (tenantId && !isValidUUIDv4(tenantId)) {
|
|
103
|
-
return {
|
|
104
|
-
status: 400,
|
|
105
|
-
body: {
|
|
106
|
-
success: false,
|
|
107
|
-
error: {
|
|
108
|
-
code: 'INVALID_TENANT_ID',
|
|
109
|
-
message: 'tenantId 必须是 UUID v4 格式'
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!databaseUrl || !driver) {
|
|
116
|
-
return {
|
|
117
|
-
status: 400,
|
|
118
|
-
body: {
|
|
119
|
-
success: false,
|
|
120
|
-
error: {
|
|
121
|
-
code: 'INVALID_PARAMETERS',
|
|
122
|
-
message: '缺少 driver 或 databaseUrl 参数'
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const result = await ctx.tenantManager.initializeTenant({
|
|
130
|
-
tenantId,
|
|
131
|
-
driver,
|
|
132
|
-
connectionString: databaseUrl
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
status: 201,
|
|
137
|
-
body: {
|
|
138
|
-
success: true,
|
|
139
|
-
data: {
|
|
140
|
-
tenantId: result.tenantId,
|
|
141
|
-
tenantToken: result.tenantToken,
|
|
142
|
-
cronToken: result.cronToken,
|
|
143
|
-
cronWebhookUrl: result.cronWebhookUrl,
|
|
144
|
-
masterKeyFingerprint: result.masterKeyFingerprint
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
} catch (error) {
|
|
149
|
-
if (error.message === 'INVALID_DRIVER') {
|
|
150
|
-
return {
|
|
151
|
-
status: 400,
|
|
152
|
-
body: {
|
|
153
|
-
success: false,
|
|
154
|
-
error: {
|
|
155
|
-
code: 'INVALID_DRIVER',
|
|
156
|
-
message: 'driver 必须是 neon 或 pg'
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (error.message === 'INVALID_DATABASE_URL') {
|
|
163
|
-
return {
|
|
164
|
-
status: 400,
|
|
165
|
-
body: {
|
|
166
|
-
success: false,
|
|
167
|
-
error: {
|
|
168
|
-
code: 'INVALID_DATABASE_URL',
|
|
169
|
-
message: 'databaseUrl 不能为空'
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (error.message === 'TENANT_ALREADY_INITIALIZED') {
|
|
176
|
-
return {
|
|
177
|
-
status: 409,
|
|
178
|
-
body: {
|
|
179
|
-
success: false,
|
|
180
|
-
error: {
|
|
181
|
-
code: 'TENANT_ALREADY_INITIALIZED',
|
|
182
|
-
message: 'tenantId 已存在,不能重复初始化'
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
throw error;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return { POST };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Handler: send-notifications
|
|
197
|
-
* ReiStandard SDK v2.0.1
|
|
198
|
-
*
|
|
199
|
-
* @param {Object} ctx - Server context.
|
|
200
|
-
* @returns {{ POST: function }}
|
|
201
|
-
*/
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
function createSendNotificationsHandler(ctx) {
|
|
205
|
-
async function POST(urlOrHeaders, maybeHeaders) {
|
|
206
|
-
const url = typeof urlOrHeaders === 'string' ? urlOrHeaders : '';
|
|
207
|
-
const headers = maybeHeaders || (typeof urlOrHeaders === 'object' ? urlOrHeaders : {});
|
|
208
|
-
|
|
209
|
-
const tenantResult = await ctx.tenantManager.resolveTenant(headers, {
|
|
210
|
-
allowCronToken: true,
|
|
211
|
-
url
|
|
212
|
-
});
|
|
213
|
-
if (!tenantResult.ok) {
|
|
214
|
-
return tenantResult.error;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const tenantCtx = tenantResult.context;
|
|
218
|
-
const db = tenantCtx.db;
|
|
219
|
-
const masterKey = tenantCtx.masterKey;
|
|
220
|
-
|
|
221
|
-
if (!ctx.vapid.email || !ctx.vapid.publicKey || !ctx.vapid.privateKey) {
|
|
222
|
-
return {
|
|
223
|
-
status: 500,
|
|
224
|
-
body: {
|
|
225
|
-
success: false,
|
|
226
|
-
error: {
|
|
227
|
-
code: 'VAPID_CONFIG_ERROR',
|
|
228
|
-
message: 'VAPID 配置缺失,无法发送推送通知',
|
|
229
|
-
details: {
|
|
230
|
-
missingKeys: [
|
|
231
|
-
!ctx.vapid.email && 'VAPID_EMAIL',
|
|
232
|
-
!ctx.vapid.publicKey && 'NEXT_PUBLIC_VAPID_PUBLIC_KEY',
|
|
233
|
-
!ctx.vapid.privateKey && 'VAPID_PRIVATE_KEY'
|
|
234
|
-
].filter(Boolean)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const data = await runScheduledTick({ ...ctx, db, masterKey });
|
|
242
|
-
return { status: 200, body: { success: true, data } };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return { POST };
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const inMemoryNamespaces = new Map();
|
|
249
|
-
|
|
250
|
-
function base64UrlEncode$1(input) {
|
|
251
|
-
return Buffer.from(input)
|
|
252
|
-
.toString('base64')
|
|
253
|
-
.replace(/\+/g, '-')
|
|
254
|
-
.replace(/\//g, '_')
|
|
255
|
-
.replace(/=+$/g, '');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function base64UrlDecode$1(input) {
|
|
259
|
-
const normalized = input
|
|
260
|
-
.replace(/-/g, '+')
|
|
261
|
-
.replace(/_/g, '/');
|
|
262
|
-
const padLength = (4 - (normalized.length % 4)) % 4;
|
|
263
|
-
return Buffer.from(normalized + '='.repeat(padLength), 'base64');
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function getKekBuffer(kek) {
|
|
267
|
-
const value = String(kek || '').trim();
|
|
268
|
-
if (!value) {
|
|
269
|
-
throw new Error('[rei-standard-amsg-server] tenant.kek is required');
|
|
270
|
-
}
|
|
271
|
-
return createHash('sha256').update(value).digest();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function encryptConfig(config, kekBuffer) {
|
|
275
|
-
const iv = randomBytes(12);
|
|
276
|
-
const cipher = createCipheriv('aes-256-gcm', kekBuffer, iv);
|
|
277
|
-
const plaintext = Buffer.from(JSON.stringify(config), 'utf8');
|
|
278
|
-
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
279
|
-
const authTag = cipher.getAuthTag();
|
|
280
|
-
|
|
281
|
-
return `v1.${base64UrlEncode$1(iv)}.${base64UrlEncode$1(authTag)}.${base64UrlEncode$1(ciphertext)}`;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function decryptConfig(encrypted, kekBuffer) {
|
|
285
|
-
const parts = String(encrypted || '').split('.');
|
|
286
|
-
if (parts.length !== 4 || parts[0] !== 'v1') {
|
|
287
|
-
throw new Error('INVALID_TENANT_CONFIG');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const iv = base64UrlDecode$1(parts[1]);
|
|
291
|
-
const authTag = base64UrlDecode$1(parts[2]);
|
|
292
|
-
const ciphertext = base64UrlDecode$1(parts[3]);
|
|
293
|
-
|
|
294
|
-
const decipher = createDecipheriv('aes-256-gcm', kekBuffer, iv);
|
|
295
|
-
decipher.setAuthTag(authTag);
|
|
296
|
-
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
|
297
|
-
return JSON.parse(plaintext);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function createInMemoryStore(namespace) {
|
|
301
|
-
if (!inMemoryNamespaces.has(namespace)) {
|
|
302
|
-
inMemoryNamespaces.set(namespace, new Map());
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const namespaceMap = inMemoryNamespaces.get(namespace);
|
|
306
|
-
|
|
307
|
-
return {
|
|
308
|
-
async get(key) {
|
|
309
|
-
return namespaceMap.has(key) ? namespaceMap.get(key) : null;
|
|
310
|
-
},
|
|
311
|
-
async set(key, value) {
|
|
312
|
-
namespaceMap.set(key, value);
|
|
313
|
-
},
|
|
314
|
-
async delete(key) {
|
|
315
|
-
namespaceMap.delete(key);
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async function resolveBlobStore(namespace) {
|
|
321
|
-
if (globalThis.__REI_BLOB_STORE__ && typeof globalThis.__REI_BLOB_STORE__.get === 'function') {
|
|
322
|
-
return globalThis.__REI_BLOB_STORE__;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
const blobModule = await import('@netlify/blobs');
|
|
327
|
-
const getStore = blobModule.getStore || blobModule.default?.getStore;
|
|
328
|
-
if (typeof getStore === 'function') {
|
|
329
|
-
return getStore({ name: namespace });
|
|
330
|
-
}
|
|
331
|
-
} catch (_error) {
|
|
332
|
-
// Fall back to in-memory store for local tests / non-Netlify runtimes
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return createInMemoryStore(namespace);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* @typedef {{
|
|
340
|
-
* tenantId: string,
|
|
341
|
-
* db: { driver: 'neon'|'pg', connectionString: string },
|
|
342
|
-
* masterKey: string,
|
|
343
|
-
* createdAt: string,
|
|
344
|
-
* updatedAt: string
|
|
345
|
-
* }} TenantConfig
|
|
346
|
-
*/
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* @param {{ namespace?: string, kek: string }} options
|
|
350
|
-
*/
|
|
351
|
-
function createTenantBlobStore(options) {
|
|
352
|
-
const namespace = String(options.namespace).trim() || 'rei-tenants';
|
|
353
|
-
const kekBuffer = getKekBuffer(options.kek);
|
|
354
|
-
|
|
355
|
-
function getTenantKey(tenantId) {
|
|
356
|
-
return `tenant/${tenantId}`;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
async getTenantConfig(tenantId) {
|
|
361
|
-
const store = await resolveBlobStore(namespace);
|
|
362
|
-
const encrypted = await store.get(getTenantKey(tenantId), { type: 'text' });
|
|
363
|
-
if (!encrypted) return null;
|
|
364
|
-
return decryptConfig(encrypted, kekBuffer);
|
|
365
|
-
},
|
|
366
|
-
|
|
367
|
-
async setTenantConfig(config) {
|
|
368
|
-
const store = await resolveBlobStore(namespace);
|
|
369
|
-
const encrypted = encryptConfig(config, kekBuffer);
|
|
370
|
-
await store.set(getTenantKey(config.tenantId), encrypted);
|
|
371
|
-
},
|
|
372
|
-
|
|
373
|
-
async deleteTenantConfig(tenantId) {
|
|
374
|
-
const store = await resolveBlobStore(namespace);
|
|
375
|
-
await store.delete(getTenantKey(tenantId));
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function base64UrlEncode(input) {
|
|
381
|
-
return Buffer.from(input)
|
|
382
|
-
.toString('base64')
|
|
383
|
-
.replace(/\+/g, '-')
|
|
384
|
-
.replace(/\//g, '_')
|
|
385
|
-
.replace(/=+$/g, '');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function base64UrlDecode(input) {
|
|
389
|
-
const normalized = input
|
|
390
|
-
.replace(/-/g, '+')
|
|
391
|
-
.replace(/_/g, '/');
|
|
392
|
-
const padLength = (4 - (normalized.length % 4)) % 4;
|
|
393
|
-
return Buffer.from(normalized + '='.repeat(padLength), 'base64');
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function sign(input, secret) {
|
|
397
|
-
return base64UrlEncode(createHmac('sha256', secret).update(input).digest());
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function nowEpochSeconds() {
|
|
401
|
-
return Math.floor(Date.now() / 1000);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* @typedef {'tenant'|'cron'} TokenType
|
|
406
|
-
*/
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* @param {{ tenantId: string, type: TokenType, expiresInSeconds?: number }} params
|
|
410
|
-
* @param {string} secret
|
|
411
|
-
*/
|
|
412
|
-
function createTenantToken(params, secret) {
|
|
413
|
-
const issuedAt = nowEpochSeconds();
|
|
414
|
-
const expiresInSeconds = Number(params.expiresInSeconds || 60 * 60 * 24 * 30);
|
|
415
|
-
const payload = {
|
|
416
|
-
tid: params.tenantId,
|
|
417
|
-
typ: params.type,
|
|
418
|
-
iat: issuedAt,
|
|
419
|
-
exp: issuedAt + expiresInSeconds,
|
|
420
|
-
v: 1
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
const header = { alg: 'HS256', typ: 'JWT' };
|
|
424
|
-
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
425
|
-
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
426
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
427
|
-
const signature = sign(signingInput, secret);
|
|
428
|
-
return `${signingInput}.${signature}`;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* @param {string} token
|
|
433
|
-
* @param {string} secret
|
|
434
|
-
* @param {{ expectedTypes?: TokenType[] }} [options]
|
|
435
|
-
*/
|
|
436
|
-
function verifyTenantToken(token, secret, options = {}) {
|
|
437
|
-
if (!token || typeof token !== 'string') {
|
|
438
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const parts = token.split('.');
|
|
442
|
-
if (parts.length !== 3) {
|
|
443
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const [encodedHeader, encodedPayload, receivedSignature] = parts;
|
|
447
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
448
|
-
const expectedSignature = sign(signingInput, secret);
|
|
449
|
-
|
|
450
|
-
// Compare raw HMAC bytes (decoded from base64url) rather than the
|
|
451
|
-
// encoded character bytes — semantically clearer and avoids the
|
|
452
|
-
// implicit-UTF-8 default of Buffer.from(string).
|
|
453
|
-
let receivedBuffer;
|
|
454
|
-
let expectedBuffer;
|
|
455
|
-
try {
|
|
456
|
-
receivedBuffer = base64UrlDecode(receivedSignature);
|
|
457
|
-
expectedBuffer = base64UrlDecode(expectedSignature);
|
|
458
|
-
} catch {
|
|
459
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
460
|
-
}
|
|
461
|
-
if (
|
|
462
|
-
receivedBuffer.length !== expectedBuffer.length ||
|
|
463
|
-
!timingSafeEqual(receivedBuffer, expectedBuffer)
|
|
464
|
-
) {
|
|
465
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
let payload;
|
|
469
|
-
try {
|
|
470
|
-
payload = JSON.parse(base64UrlDecode(encodedPayload).toString('utf8'));
|
|
471
|
-
} catch {
|
|
472
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (!payload || typeof payload !== 'object' || payload.v !== 1) {
|
|
476
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (!payload.tid || !payload.typ || !payload.exp) {
|
|
480
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (payload.exp <= nowEpochSeconds()) {
|
|
484
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const expectedTypes = options.expectedTypes || [];
|
|
488
|
-
if (expectedTypes.length > 0 && !expectedTypes.includes(payload.typ)) {
|
|
489
|
-
throw new Error('INVALID_TENANT_AUTH');
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return payload;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* @param {Record<string, any>} headers
|
|
497
|
-
* @returns {string}
|
|
498
|
-
*/
|
|
499
|
-
function extractBearerToken(headers = {}) {
|
|
500
|
-
let authorization = '';
|
|
501
|
-
for (const [key, value] of Object.entries(headers || {})) {
|
|
502
|
-
if (String(key).toLowerCase() === 'authorization') {
|
|
503
|
-
authorization = String(value || '').trim();
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (!authorization.toLowerCase().startsWith('bearer ')) {
|
|
508
|
-
return '';
|
|
509
|
-
}
|
|
510
|
-
return authorization.slice(7).trim();
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function fingerprint(masterKey) {
|
|
514
|
-
return createHash('sha256').update(masterKey).digest('hex').slice(0, 16);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function sanitizeDbConfig(driver, connectionString) {
|
|
518
|
-
const normalizedDriver = String(driver || '').trim();
|
|
519
|
-
const normalizedConnectionString = String(connectionString || '').trim();
|
|
520
|
-
|
|
521
|
-
if (!['neon', 'pg'].includes(normalizedDriver)) {
|
|
522
|
-
throw new Error('INVALID_DRIVER');
|
|
523
|
-
}
|
|
524
|
-
if (!normalizedConnectionString) {
|
|
525
|
-
throw new Error('INVALID_DATABASE_URL');
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return {
|
|
529
|
-
driver: normalizedDriver,
|
|
530
|
-
connectionString: normalizedConnectionString
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function parseTokenFromUrl(url) {
|
|
535
|
-
if (!url || typeof url !== 'string') {
|
|
536
|
-
return '';
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
const parsed = new URL(url, 'https://dummy.local');
|
|
541
|
-
return String(parsed.searchParams.get('token') || '').trim();
|
|
542
|
-
} catch {
|
|
543
|
-
return '';
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* @param {{
|
|
549
|
-
* tenantStore: ReturnType<import('./blob-store.js').createTenantBlobStore>,
|
|
550
|
-
* tokenSigningKey: string,
|
|
551
|
-
* publicBaseUrl?: string
|
|
552
|
-
* }} options
|
|
553
|
-
*/
|
|
554
|
-
function createTenantContextManager(options) {
|
|
555
|
-
const tenantStore = options.tenantStore;
|
|
556
|
-
const tokenSigningKey = String(options.tokenSigningKey || '').trim();
|
|
557
|
-
const publicBaseUrl = String(options.publicBaseUrl || '').replace(/\/$/, '');
|
|
558
|
-
const adapterFactory = options.adapterFactory || createAdapter;
|
|
559
|
-
const adapterCache = new Map();
|
|
560
|
-
|
|
561
|
-
if (!tokenSigningKey) {
|
|
562
|
-
throw new Error('[rei-standard-amsg-server] tenant.tokenSigningKey is required');
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async function getOrCreateAdapter(dbConfig) {
|
|
566
|
-
const cacheKey = `${dbConfig.driver}:${dbConfig.connectionString}`;
|
|
567
|
-
if (!adapterCache.has(cacheKey)) {
|
|
568
|
-
const adapter = await adapterFactory(dbConfig);
|
|
569
|
-
adapterCache.set(cacheKey, adapter);
|
|
570
|
-
}
|
|
571
|
-
return adapterCache.get(cacheKey);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
async function initializeTenant({ driver, connectionString, tenantId: providedTenantId }) {
|
|
575
|
-
const dbConfig = sanitizeDbConfig(driver, connectionString);
|
|
576
|
-
const tenantId = String(providedTenantId || randomUUID()).trim();
|
|
577
|
-
|
|
578
|
-
const existing = await tenantStore.getTenantConfig(tenantId);
|
|
579
|
-
if (existing) {
|
|
580
|
-
throw new Error('TENANT_ALREADY_INITIALIZED');
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const adapter = await getOrCreateAdapter(dbConfig);
|
|
584
|
-
await adapter.initSchema();
|
|
585
|
-
|
|
586
|
-
const masterKey = randomBytes(32).toString('hex');
|
|
587
|
-
const now = new Date().toISOString();
|
|
588
|
-
|
|
589
|
-
await tenantStore.setTenantConfig({
|
|
590
|
-
tenantId,
|
|
591
|
-
db: dbConfig,
|
|
592
|
-
masterKey,
|
|
593
|
-
createdAt: now,
|
|
594
|
-
updatedAt: now
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
const tenantToken = createTenantToken({ tenantId, type: 'tenant' }, tokenSigningKey);
|
|
598
|
-
const cronToken = createTenantToken({ tenantId, type: 'cron' }, tokenSigningKey);
|
|
599
|
-
|
|
600
|
-
return {
|
|
601
|
-
tenantId,
|
|
602
|
-
masterKeyFingerprint: fingerprint(masterKey),
|
|
603
|
-
tenantToken,
|
|
604
|
-
cronToken,
|
|
605
|
-
cronWebhookUrl: publicBaseUrl ? `${publicBaseUrl}/api/v1/send-notifications?token=${cronToken}` : ''
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
async function resolveTenant(headers, options = {}) {
|
|
610
|
-
const allowCronToken = options.allowCronToken === true;
|
|
611
|
-
const url = options.url || '';
|
|
612
|
-
|
|
613
|
-
const bearerToken = extractBearerToken(headers);
|
|
614
|
-
const tokenFromQuery = allowCronToken ? parseTokenFromUrl(url) : '';
|
|
615
|
-
const token = bearerToken || tokenFromQuery;
|
|
616
|
-
|
|
617
|
-
if (!token) {
|
|
618
|
-
return {
|
|
619
|
-
ok: false,
|
|
620
|
-
error: {
|
|
621
|
-
status: 401,
|
|
622
|
-
body: { success: false, error: { code: 'INVALID_TENANT_AUTH', message: '缺少租户鉴权信息' } }
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
let payload;
|
|
628
|
-
try {
|
|
629
|
-
payload = verifyTenantToken(token, tokenSigningKey, {
|
|
630
|
-
expectedTypes: allowCronToken ? ['cron'] : ['tenant']
|
|
631
|
-
});
|
|
632
|
-
} catch (_error) {
|
|
633
|
-
return {
|
|
634
|
-
ok: false,
|
|
635
|
-
error: {
|
|
636
|
-
status: 401,
|
|
637
|
-
body: { success: false, error: { code: 'INVALID_TENANT_AUTH', message: '租户令牌无效或已过期' } }
|
|
638
|
-
}
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const tenantConfig = await tenantStore.getTenantConfig(payload.tid);
|
|
643
|
-
if (!tenantConfig) {
|
|
644
|
-
return {
|
|
645
|
-
ok: false,
|
|
646
|
-
error: {
|
|
647
|
-
status: 401,
|
|
648
|
-
body: { success: false, error: { code: 'INVALID_TENANT_AUTH', message: '租户配置不存在或已失效' } }
|
|
649
|
-
}
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
const adapter = await getOrCreateAdapter(tenantConfig.db);
|
|
654
|
-
|
|
655
|
-
return {
|
|
656
|
-
ok: true,
|
|
657
|
-
context: {
|
|
658
|
-
tenantId: tenantConfig.tenantId,
|
|
659
|
-
tokenType: payload.typ,
|
|
660
|
-
db: adapter,
|
|
661
|
-
masterKey: tenantConfig.masterKey
|
|
662
|
-
}
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
return {
|
|
667
|
-
initializeTenant,
|
|
668
|
-
resolveTenant
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
/**
|
|
673
|
-
* ReiStandard Server SDK Entry Point
|
|
674
|
-
* v2.0.1
|
|
675
|
-
*
|
|
676
|
-
* Usage:
|
|
677
|
-
* import { createReiServer } from '@rei-standard/amsg-server';
|
|
678
|
-
*
|
|
679
|
-
* const rei = await createReiServer({
|
|
680
|
-
* vapid: {
|
|
681
|
-
* email: process.env.VAPID_EMAIL,
|
|
682
|
-
* publicKey: process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY,
|
|
683
|
-
* privateKey: process.env.VAPID_PRIVATE_KEY,
|
|
684
|
-
* },
|
|
685
|
-
* tenant: {
|
|
686
|
-
* blobNamespace: 'rei-tenants',
|
|
687
|
-
* kek: process.env.TENANT_CONFIG_KEK,
|
|
688
|
-
* tokenSigningKey: process.env.TENANT_TOKEN_SIGNING_KEY,
|
|
689
|
-
* initSecret: process.env.INIT_SECRET,
|
|
690
|
-
* publicBaseUrl: process.env.PUBLIC_BASE_URL
|
|
691
|
-
* }
|
|
692
|
-
* });
|
|
693
|
-
*/
|
|
694
|
-
|
|
695
|
-
|
|
696
1
|
/**
|
|
697
2
|
* @typedef {Object} VapidConfig
|
|
698
3
|
* @property {string} [email] - VAPID contact email (e.g. mailto:…).
|
|
699
4
|
* @property {string} [publicKey] - VAPID public key.
|
|
700
5
|
* @property {string} [privateKey] - VAPID private key.
|
|
701
6
|
*/
|
|
702
|
-
|
|
703
7
|
/**
|
|
704
8
|
* @typedef {Object} TenantServerConfig
|
|
705
9
|
* @property {string} [blobNamespace] - Netlify Blob namespace.
|
|
@@ -710,13 +14,16 @@ function createTenantContextManager(options) {
|
|
|
710
14
|
* @property {(db: {driver:'neon'|'pg', connectionString:string}) => Promise<any>} [adapterFactory]
|
|
711
15
|
* Optional adapter factory override (mainly for tests).
|
|
712
16
|
*/
|
|
713
|
-
|
|
714
17
|
/**
|
|
715
18
|
* @typedef {Object} ReiServerConfig
|
|
716
19
|
* @property {VapidConfig} [vapid] - VAPID keys for Web Push.
|
|
717
20
|
* @property {TenantServerConfig} tenant - Tenant config & auth settings.
|
|
21
|
+
* @property {{ maxChunkBytes?: number, maxChunks?: number, maxTotalBytes?: number, ttlMs?: number }} [multipart]
|
|
22
|
+
* 分片传输的限额,跟传给 `installReiSW` 的那一份保持一致(同名同键,原样传过来
|
|
23
|
+
* 即可)。一条 push 装不下的思考过程要切片发,切多大、最多几片、重组窗口多长由
|
|
24
|
+
* 接收端说了算——发送端不知道这份配置的话,切出来的分片到了那边会被逐片拒收,
|
|
25
|
+
* 或者整批没能在重组窗口内发完,一条也拼不回来。不配 = 两边都用默认值。
|
|
718
26
|
*/
|
|
719
|
-
|
|
720
27
|
/**
|
|
721
28
|
* @typedef {Object} ReiHandlers
|
|
722
29
|
* @property {{ POST: function }} initTenant
|
|
@@ -726,82 +33,140 @@ function createTenantContextManager(options) {
|
|
|
726
33
|
* @property {{ PUT: function }} updateMessage
|
|
727
34
|
* @property {{ DELETE: function }} cancelMessage
|
|
728
35
|
* @property {{ GET: function }} messages
|
|
36
|
+
* @property {{ GET: function }} getMessage
|
|
37
|
+
* @property {{ PUT: function, GET: function, DELETE: function }} pushSubscription
|
|
38
|
+
* @property {{ PUT: function, GET: function, DELETE: function }} llmCredentials
|
|
729
39
|
*/
|
|
730
|
-
|
|
731
40
|
/**
|
|
732
41
|
* @typedef {Object} ReiServer
|
|
733
42
|
* @property {ReiHandlers} handlers - Standard API route handler objects.
|
|
734
43
|
*/
|
|
735
|
-
|
|
736
44
|
/**
|
|
737
45
|
* Initialise the ReiStandard server.
|
|
738
46
|
*
|
|
739
47
|
* @param {ReiServerConfig} config
|
|
740
48
|
* @returns {Promise<ReiServer>}
|
|
741
49
|
*/
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
50
|
+
export function createReiServer(config: ReiServerConfig): Promise<ReiServer>;
|
|
51
|
+
export { createAdapter } from "./adapters/factory.js";
|
|
52
|
+
export { createD1Adapter } from "./adapters/d1.js";
|
|
53
|
+
export { createSingleUserServer } from "./single-user.js";
|
|
54
|
+
export type VapidConfig = {
|
|
55
|
+
/**
|
|
56
|
+
* - VAPID contact email (e.g. mailto:…).
|
|
57
|
+
*/
|
|
58
|
+
email?: string;
|
|
59
|
+
/**
|
|
60
|
+
* - VAPID public key.
|
|
61
|
+
*/
|
|
62
|
+
publicKey?: string;
|
|
63
|
+
/**
|
|
64
|
+
* - VAPID private key.
|
|
65
|
+
*/
|
|
66
|
+
privateKey?: string;
|
|
67
|
+
};
|
|
68
|
+
export type TenantServerConfig = {
|
|
69
|
+
/**
|
|
70
|
+
* - Netlify Blob namespace.
|
|
71
|
+
*/
|
|
72
|
+
blobNamespace?: string;
|
|
73
|
+
/**
|
|
74
|
+
* - KEK used to encrypt tenant config in blobs.
|
|
75
|
+
*/
|
|
76
|
+
kek: string;
|
|
77
|
+
/**
|
|
78
|
+
* - HMAC key used to sign tenant/cron tokens.
|
|
79
|
+
*/
|
|
80
|
+
tokenSigningKey: string;
|
|
81
|
+
/**
|
|
82
|
+
* - Optional secret for /init-tenant bootstrap endpoint.
|
|
83
|
+
*/
|
|
84
|
+
initSecret?: string;
|
|
85
|
+
/**
|
|
86
|
+
* - Optional base URL for generated cron webhook.
|
|
87
|
+
*/
|
|
88
|
+
publicBaseUrl?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Optional adapter factory override (mainly for tests).
|
|
91
|
+
*/
|
|
92
|
+
adapterFactory?: (db: {
|
|
93
|
+
driver: "neon" | "pg";
|
|
94
|
+
connectionString: string;
|
|
95
|
+
}) => Promise<any>;
|
|
96
|
+
};
|
|
97
|
+
export type ReiServerConfig = {
|
|
98
|
+
/**
|
|
99
|
+
* - VAPID keys for Web Push.
|
|
100
|
+
*/
|
|
101
|
+
vapid?: VapidConfig;
|
|
102
|
+
/**
|
|
103
|
+
* - Tenant config & auth settings.
|
|
104
|
+
*/
|
|
105
|
+
tenant: TenantServerConfig;
|
|
106
|
+
/**
|
|
107
|
+
* 分片传输的限额,跟传给 `installReiSW` 的那一份保持一致(同名同键,原样传过来
|
|
108
|
+
* 即可)。一条 push 装不下的思考过程要切片发,切多大、最多几片、重组窗口多长由
|
|
109
|
+
* 接收端说了算——发送端不知道这份配置的话,切出来的分片到了那边会被逐片拒收,
|
|
110
|
+
* 或者整批没能在重组窗口内发完,一条也拼不回来。不配 = 两边都用默认值。
|
|
111
|
+
*/
|
|
112
|
+
multipart?: {
|
|
113
|
+
maxChunkBytes?: number;
|
|
114
|
+
maxChunks?: number;
|
|
115
|
+
maxTotalBytes?: number;
|
|
116
|
+
ttlMs?: number;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
export type ReiHandlers = {
|
|
120
|
+
initTenant: {
|
|
121
|
+
POST: Function;
|
|
122
|
+
};
|
|
123
|
+
getUserKey: {
|
|
124
|
+
GET: Function;
|
|
125
|
+
};
|
|
126
|
+
scheduleMessage: {
|
|
127
|
+
POST: Function;
|
|
128
|
+
};
|
|
129
|
+
sendNotifications: {
|
|
130
|
+
POST: Function;
|
|
131
|
+
};
|
|
132
|
+
updateMessage: {
|
|
133
|
+
PUT: Function;
|
|
134
|
+
};
|
|
135
|
+
cancelMessage: {
|
|
136
|
+
DELETE: Function;
|
|
137
|
+
};
|
|
138
|
+
messages: {
|
|
139
|
+
GET: Function;
|
|
140
|
+
};
|
|
141
|
+
getMessage: {
|
|
142
|
+
GET: Function;
|
|
143
|
+
};
|
|
144
|
+
pushSubscription: {
|
|
145
|
+
PUT: Function;
|
|
146
|
+
GET: Function;
|
|
147
|
+
DELETE: Function;
|
|
148
|
+
};
|
|
149
|
+
llmCredentials: {
|
|
150
|
+
PUT: Function;
|
|
151
|
+
GET: Function;
|
|
152
|
+
DELETE: Function;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
export type ReiServer = {
|
|
156
|
+
/**
|
|
157
|
+
* - Standard API route handler objects.
|
|
158
|
+
*/
|
|
159
|
+
handlers: ReiHandlers;
|
|
160
|
+
};
|
|
161
|
+
export { runScheduledTick, runTask, DEFAULT_CLAIM_LEASE_MS, DEFAULT_LEASE_HEARTBEAT_MS, DEFAULT_HEARTBEAT_LEASE_TTL_MS, sanitizeErrorSummary } from "./lib/run-tick.js";
|
|
162
|
+
export { NonRetryableError, DeploymentConfigError, isNonRetryableError, summarizeErrorCause } from "./lib/errors.js";
|
|
163
|
+
export { getSchemaVersion, ensureSchema, SCHEMA_VERSION } from "./lib/schema-version.js";
|
|
164
|
+
export { DEFAULT_MAX_TOOL_ITERATIONS, DEFAULT_TOTAL_TIMEOUT_MS, DEFAULT_MAX_SCHEDULED_TASKS_PER_FIRE, MIN_SCHEDULE_LEAD_MS } from "./lib/agentic-fire.js";
|
|
165
|
+
export { CORS_ALLOW_HEADERS, CORS_ALLOW_METHODS, createSingleUserCloudflareWorker } from "./cloudflare/single-user-worker.js";
|
|
166
|
+
export { createWebCryptoWebPush, measurePushPayload, MAX_PUSH_PAYLOAD_BYTES, PUSH_ENVELOPE_RESERVED_BYTES, WEB_PUSH_MAX_BODY_BYTES, WEB_PUSH_ENCRYPTION_OVERHEAD_BYTES } from "./lib/webpush-webcrypto.js";
|
|
167
|
+
export { deriveUserEncryptionKey, decryptPayload, encryptForStorage, decryptFromStorage } from "./lib/encryption.js";
|
|
168
|
+
export { validateScheduleMessagePayload, validateLlmMessagesArray, validateSplitPattern, validateAvatarUrl, isValidISO8601, isValidUrl, isValidUUID, isValidUUIDv4, isValidTimeZoneId, MAX_TASK_PAYLOAD_BYTES } from "./lib/validation.js";
|
|
169
|
+
export { readRequestBody, DEFAULT_MAX_REQUEST_BODY_BYTES } from "./lib/request.js";
|
|
170
|
+
export { supportsLlmCredentialsStore, isValidCredId, validateCredRefs, validateCredValue, hasChatCredRef, resolveLlmCredential, CRED_ID_MAX_LENGTH, CRED_PUT_BATCH_MAX, CRED_ROWS_PER_USER_MAX, CRED_REFS_MAX_ENTRIES } from "./lib/llm-credentials-store.js";
|
|
171
|
+
export { advanceOccurrence, nextFutureOccurrence, planNextOccurrence } from "./lib/recurrence.js";
|
|
172
|
+
export { createTenantToken, verifyTenantToken } from "./tenant/token.js";
|