@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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +599 -24
  3. package/dist/adapters/d1.d.ts +416 -0
  4. package/dist/adapters/factory.d.ts +39 -0
  5. package/dist/adapters/interface.d.ts +306 -0
  6. package/dist/adapters/neon.d.ts +88 -0
  7. package/dist/adapters/pg-shared.d.ts +161 -0
  8. package/dist/adapters/pg.d.ts +84 -0
  9. package/dist/adapters/schema.d.ts +57 -0
  10. package/dist/adapters/schema.sqlite.d.ts +49 -0
  11. package/dist/chunk-7NMQFTDJ.cjs +148 -0
  12. package/dist/chunk-E7OWP3VL.cjs +135 -0
  13. package/dist/chunk-GN44PST5.mjs +148 -0
  14. package/dist/chunk-KOJQVOYS.mjs +5847 -0
  15. package/dist/chunk-QHVID3V2.cjs +5847 -0
  16. package/dist/chunk-ZGGF4GMA.mjs +135 -0
  17. package/dist/cloudflare/single-user-worker.d.ts +37 -0
  18. package/dist/cloudflare.cjs +39 -2
  19. package/dist/cloudflare.d.ts +10 -2
  20. package/dist/cloudflare.mjs +40 -3
  21. package/dist/handlers/cancel-message.d.ts +3 -0
  22. package/dist/handlers/capabilities.d.ts +4 -0
  23. package/dist/handlers/client-state.d.ts +7 -0
  24. package/dist/handlers/get-message.d.ts +3 -0
  25. package/dist/handlers/get-user-key.d.ts +3 -0
  26. package/dist/handlers/init-tenant.d.ts +23 -0
  27. package/dist/handlers/llm-credentials.d.ts +5 -0
  28. package/dist/handlers/messages.d.ts +3 -0
  29. package/dist/handlers/outbox.d.ts +7 -0
  30. package/dist/handlers/push-subscription.d.ts +5 -0
  31. package/dist/handlers/schedule-message.d.ts +3 -0
  32. package/dist/handlers/send-notifications.d.ts +3 -0
  33. package/dist/handlers/single-user-init.d.ts +13 -0
  34. package/dist/handlers/update-message.d.ts +3 -0
  35. package/dist/handlers/vapid-public-key.d.ts +19 -0
  36. package/dist/index.cjs +133 -44
  37. package/dist/index.d.cts +131 -766
  38. package/dist/index.d.ts +131 -766
  39. package/dist/index.mjs +125 -36
  40. package/dist/lib/agentic-fire.d.ts +108 -0
  41. package/dist/lib/client-state-store.d.ts +80 -0
  42. package/dist/lib/constant-time.d.ts +14 -0
  43. package/dist/lib/db-errors.d.ts +10 -0
  44. package/dist/lib/encryption.d.ts +48 -0
  45. package/dist/lib/errors.d.ts +215 -0
  46. package/dist/lib/llm-credentials-store.d.ts +147 -0
  47. package/dist/lib/llm.d.ts +1 -0
  48. package/dist/lib/message-processor.d.ts +73 -0
  49. package/dist/lib/outbox-store.d.ts +95 -0
  50. package/dist/lib/push-subscription-store.d.ts +94 -0
  51. package/dist/lib/recurrence.d.ts +51 -0
  52. package/dist/lib/request.d.ts +182 -0
  53. package/dist/lib/result-emitter.d.ts +54 -0
  54. package/dist/lib/run-tick.d.ts +115 -0
  55. package/dist/lib/schema-version.d.ts +50 -0
  56. package/dist/lib/state-accessors.d.ts +70 -0
  57. package/dist/lib/state-chunks.d.ts +55 -0
  58. package/dist/lib/task-projection.d.ts +92 -0
  59. package/dist/lib/validation.d.ts +97 -0
  60. package/dist/lib/version.d.ts +7 -0
  61. package/dist/lib/webcrypto-utils.d.ts +1 -0
  62. package/dist/lib/webpush-webcrypto.d.ts +78 -0
  63. package/dist/{neon-BFUS25UX.cjs → neon-KP2CPA57.cjs} +110 -10
  64. package/dist/{neon-CU5N3CSW.mjs → neon-ZIMHALYI.mjs} +106 -6
  65. package/dist/{pg-QO6NKTGL.mjs → pg-QGC2XHUT.mjs} +94 -5
  66. package/dist/{pg-IIH3M4OM.cjs → pg-RZPQGXR3.cjs} +98 -9
  67. package/dist/single-user.d.ts +95 -0
  68. package/dist/tenant/blob-store.d.ts +30 -0
  69. package/dist/tenant/context.d.ts +47 -0
  70. package/dist/tenant/single-user-context.d.ts +29 -0
  71. package/dist/tenant/token.d.ts +26 -0
  72. package/package.json +3 -3
  73. package/dist/chunk-5ENJGVZX.mjs +0 -72
  74. package/dist/chunk-GN5QTMCL.cjs +0 -1870
  75. package/dist/chunk-PVNLB6TF.mjs +0 -1870
  76. package/dist/chunk-RGECD4OH.cjs +0 -72
  77. package/dist/cloudflare-B-E51VjP.d.cts +0 -2848
  78. package/dist/cloudflare-B-E51VjP.d.ts +0 -2848
  79. package/dist/cloudflare.d.cts +0 -2
  80. package/dist/neon-CJl66EGy.d.cts +0 -251
  81. package/dist/neon-DYvGnCzx.d.ts +0 -251
  82. package/dist/pg-07S-u_H4.d.cts +0 -246
  83. package/dist/pg-Du-pN_UT.d.ts +0 -246
  84. package/dist/schema-C8OnYk6j.d.cts +0 -74
  85. package/dist/schema-C8OnYk6j.d.ts +0 -74
package/dist/index.d.ts 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.js';
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.js';
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-DYvGnCzx.d.ts');
43
- return new NeonAdapter(config.connectionString);
44
- }
45
-
46
- case 'pg': {
47
- const { PgAdapter } = await import('./pg-Du-pN_UT.d.ts');
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
- async function createReiServer(config) {
743
- if (!config) throw new Error('[rei-standard-amsg-server] config is required');
744
- if (!config.tenant) throw new Error('[rei-standard-amsg-server] tenant config is required');
745
-
746
- // web-push is a hard dependency for ReiStandard server features
747
- let webpushModule;
748
- try {
749
- const webpushImport = await import('web-push');
750
- webpushModule = webpushImport.default || webpushImport;
751
- } catch (_err) {
752
- throw new Error(
753
- '[rei-standard-amsg-server] web-push is required. Install it with: npm install web-push'
754
- );
755
- }
756
-
757
- const vapid = config.vapid || {};
758
-
759
- if (vapid.email && vapid.publicKey && vapid.privateKey) {
760
- webpushModule.setVapidDetails(
761
- normalizeVapidSubject(vapid.email),
762
- vapid.publicKey,
763
- vapid.privateKey
764
- );
765
- }
766
-
767
- const tenantStore = createTenantBlobStore({
768
- namespace: config.tenant.blobNamespace || 'rei-tenants',
769
- kek: config.tenant.kek
770
- });
771
-
772
- const tenantManager = createTenantContextManager({
773
- tenantStore,
774
- tokenSigningKey: config.tenant.tokenSigningKey,
775
- publicBaseUrl: config.tenant.publicBaseUrl,
776
- adapterFactory: config.tenant.adapterFactory
777
- });
778
-
779
- const initSecret = String(config.tenant.initSecret || '').trim();
780
-
781
- const ctx = {
782
- vapid: {
783
- email: vapid.email || '',
784
- publicKey: vapid.publicKey || '',
785
- privateKey: vapid.privateKey || ''
786
- },
787
- webpush: webpushModule,
788
- tenant: {
789
- initSecret
790
- },
791
- tenantManager
792
- };
793
-
794
- return {
795
- handlers: {
796
- initTenant: createInitTenantHandler(ctx),
797
- getUserKey: createGetUserKeyHandler(ctx),
798
- scheduleMessage: createScheduleMessageHandler(ctx),
799
- sendNotifications: createSendNotificationsHandler(ctx),
800
- updateMessage: createUpdateMessageHandler(ctx),
801
- cancelMessage: createCancelMessageHandler(ctx),
802
- messages: createMessagesHandler(ctx)
803
- }
804
- };
805
- }
806
-
807
- export { createAdapter, createReiServer, createTenantToken, isValidUUIDv4, runScheduledTick, verifyTenantToken };
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";