@edge-base/shared 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1075 @@
1
+ export type FieldType = 'string' | 'text' | 'number' | 'boolean' | 'datetime' | 'json';
2
+ export interface SchemaField {
3
+ type: FieldType;
4
+ required?: boolean;
5
+ default?: unknown;
6
+ unique?: boolean;
7
+ min?: number;
8
+ max?: number;
9
+ pattern?: string;
10
+ enum?: string[];
11
+ primaryKey?: boolean;
12
+ onUpdate?: 'now';
13
+ /**
14
+ * SQLite REFERENCES (FK) for this column. (#133 §35)
15
+ * Object form: { table: 'users', onDelete: 'CASCADE' }
16
+ * String short form: 'users' or 'users(id)'
17
+ * Note: PRAGMA foreign_keys = ON is set at DB init in database-do.ts.
18
+ * Auth-user references (`users`, `_users`, `_users_public`) are logical-only
19
+ * because auth data lives in AUTH_DB, so no physical FK is emitted for them.
20
+ */
21
+ references?: string | FkReference;
22
+ /** SQLite CHECK expression. e.g. check: 'score >= 0 AND score <= 100' (#133 §35) */
23
+ check?: string;
24
+ }
25
+ export interface IndexConfig {
26
+ fields: string[];
27
+ unique?: boolean;
28
+ }
29
+ /**
30
+ * Foreign key reference config for SchemaField.references. (#133 §35)
31
+ * database-do.ts sets PRAGMA foreign_keys = ON at DO init.
32
+ * Cross-DB-block FKs are DDL-excluded (different SQLite files).
33
+ * Auth-user references are also DDL-excluded because they live in AUTH_DB.
34
+ */
35
+ export interface FkReference {
36
+ table: string;
37
+ column?: string;
38
+ onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';
39
+ onUpdate?: 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION';
40
+ }
41
+ export interface MigrationConfig {
42
+ version: number;
43
+ description: string;
44
+ /** SQLite migration SQL. Used for provider='do' or when upPg is not provided. */
45
+ up: string;
46
+ /** PostgreSQL-specific migration SQL. When present, used instead of `up` for provider='neon'|'postgres'. */
47
+ upPg?: string;
48
+ }
49
+ export interface AuthContext {
50
+ id: string;
51
+ role?: string;
52
+ isAnonymous?: boolean;
53
+ email?: string;
54
+ custom?: Record<string, unknown>;
55
+ memberships?: Array<{
56
+ id: string;
57
+ role?: string;
58
+ }>;
59
+ /**
60
+ * Open-ended extension map injected by `auth.handlers.hooks.enrich` (#133 §38).
61
+ * Allows passing arbitrary request-scoped data into rules without JWT re-issuance.
62
+ * e.g. { workspaceRole: 'admin', orgIds: ['o1', 'o2'] }
63
+ */
64
+ meta?: Record<string, unknown>;
65
+ }
66
+ export interface HookCtx {
67
+ db: {
68
+ get(table: string, id: string): Promise<Record<string, unknown> | null>;
69
+ list(table: string, filter?: Record<string, unknown>): Promise<Array<Record<string, unknown>>>;
70
+ exists(table: string, filter: Record<string, unknown>): Promise<boolean>;
71
+ };
72
+ databaseLive: {
73
+ broadcast(channel: string, event: string, data: unknown): Promise<void>;
74
+ };
75
+ push: {
76
+ send(userId: string, payload: {
77
+ title?: string;
78
+ body: string;
79
+ }): Promise<void>;
80
+ };
81
+ waitUntil(promise: Promise<unknown>): void;
82
+ }
83
+ export interface DbRuleCtx {
84
+ db: {
85
+ get(table: string, id: string): Promise<Record<string, unknown> | null>;
86
+ exists(table: string, filter: Record<string, unknown>): Promise<boolean>;
87
+ };
88
+ }
89
+ export interface TableRules {
90
+ /** Who can read (list/get/search) rows. Boolean or (auth, row) => boolean. */
91
+ read?: boolean | ((auth: AuthContext | null, row: Record<string, unknown>) => boolean | Promise<boolean>);
92
+ /** Who can insert rows. Boolean or (auth) => boolean. */
93
+ insert?: boolean | ((auth: AuthContext | null) => boolean | Promise<boolean>);
94
+ /** Who can update rows. Boolean or (auth, row) => boolean. */
95
+ update?: boolean | ((auth: AuthContext | null, row: Record<string, unknown>) => boolean | Promise<boolean>);
96
+ /** Who can delete rows. Boolean or (auth, row) => boolean. */
97
+ delete?: boolean | ((auth: AuthContext | null, row: Record<string, unknown>) => boolean | Promise<boolean>);
98
+ }
99
+ export interface TableHooks {
100
+ /** Runs before insert. Return transformed data or throw to reject. */
101
+ beforeInsert?: (auth: AuthContext | null, data: Record<string, unknown>, ctx: HookCtx) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
102
+ /** Runs after insert (fire-and-forget via waitUntil). */
103
+ afterInsert?: (data: Record<string, unknown>, ctx: HookCtx) => Promise<void> | void;
104
+ /** Runs before update. Return transformed data or throw to reject. */
105
+ beforeUpdate?: (auth: AuthContext | null, before: Record<string, unknown>, data: Record<string, unknown>, ctx: HookCtx) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
106
+ /** Runs after update (fire-and-forget via waitUntil). */
107
+ afterUpdate?: (before: Record<string, unknown>, after: Record<string, unknown>, ctx: HookCtx) => Promise<void> | void;
108
+ /** Runs before delete. Throw to reject. */
109
+ beforeDelete?: (auth: AuthContext | null, data: Record<string, unknown>, ctx: HookCtx) => Promise<void> | void;
110
+ /** Runs after delete (fire-and-forget via waitUntil). */
111
+ afterDelete?: (data: Record<string, unknown>, ctx: HookCtx) => Promise<void> | void;
112
+ /**
113
+ * Runs after read (GET/LIST/SEARCH), before response. Applied per-row. Blocking.
114
+ * Return modified record to add computed fields or strip fields. Return void for no change.
115
+ */
116
+ onEnrich?: (auth: AuthContext | null, record: Record<string, unknown>, ctx: HookCtx) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;
117
+ }
118
+ export type TableAccess = TableRules;
119
+ export interface TableHandlers {
120
+ hooks?: TableHooks;
121
+ }
122
+ export interface TableConfig {
123
+ /** Schema definition. Optional — omit for schemaless CRUD (no type validation/indexes/FTS). */
124
+ schema?: Record<string, SchemaField | false>;
125
+ access?: TableAccess;
126
+ handlers?: TableHandlers;
127
+ indexes?: IndexConfig[];
128
+ fts?: string[];
129
+ migrations?: MigrationConfig[];
130
+ }
131
+ export interface DbLevelRules {
132
+ /**
133
+ * Allow creating a new DO instance (new namespace:id).
134
+ * Default: false — deny-by-default. Must explicitly set to allow creation.
135
+ */
136
+ canCreate?: (auth: AuthContext | null, id: string) => boolean | Promise<boolean>;
137
+ /**
138
+ * Allow accessing an existing DO instance.
139
+ * async supported — can perform DB lookups for membership checks.
140
+ */
141
+ access?: (auth: AuthContext | null, id: string, ctx: DbRuleCtx) => boolean | Promise<boolean>;
142
+ /** Allow deleting a DO instance. */
143
+ delete?: (auth: AuthContext | null, id: string) => boolean;
144
+ }
145
+ export type DbAccess = DbLevelRules;
146
+ export interface AdminInstanceDiscoveryOption {
147
+ id: string;
148
+ label?: string;
149
+ description?: string;
150
+ }
151
+ export interface AdminInstanceDiscoveryContext {
152
+ namespace: string;
153
+ query: string;
154
+ limit: number;
155
+ admin: {
156
+ sql(namespace: string, sql: string, options?: {
157
+ id?: string;
158
+ params?: unknown[];
159
+ }): Promise<Record<string, unknown>[]>;
160
+ };
161
+ }
162
+ export interface ManualAdminInstanceDiscovery {
163
+ source: 'manual';
164
+ targetLabel?: string;
165
+ placeholder?: string;
166
+ helperText?: string;
167
+ }
168
+ export interface TableAdminInstanceDiscovery {
169
+ source: 'table';
170
+ targetLabel?: string;
171
+ namespace: string;
172
+ table: string;
173
+ idField?: string;
174
+ labelField?: string;
175
+ descriptionField?: string;
176
+ searchFields?: string[];
177
+ orderBy?: string;
178
+ limit?: number;
179
+ placeholder?: string;
180
+ helperText?: string;
181
+ }
182
+ export interface FunctionAdminInstanceDiscovery {
183
+ source: 'function';
184
+ targetLabel?: string;
185
+ resolve: (ctx: AdminInstanceDiscoveryContext) => Promise<AdminInstanceDiscoveryOption[]> | AdminInstanceDiscoveryOption[];
186
+ placeholder?: string;
187
+ helperText?: string;
188
+ }
189
+ export type AdminInstanceDiscovery = ManualAdminInstanceDiscovery | TableAdminInstanceDiscovery | FunctionAdminInstanceDiscovery;
190
+ export interface DbAdminConfig {
191
+ /**
192
+ * Admin dashboard instance discovery for dynamic namespaces.
193
+ * Lets the dashboard suggest instance IDs instead of relying on manual entry.
194
+ */
195
+ instances?: AdminInstanceDiscovery;
196
+ }
197
+ /** Database backend provider type. */
198
+ export type DbProvider = 'do' | 'd1' | 'neon' | 'postgres';
199
+ /** Auth database backend provider type. */
200
+ export type AuthDbProvider = 'd1' | 'neon' | 'postgres';
201
+ export interface DbBlock {
202
+ /**
203
+ * Database backend provider.
204
+ * - `'do'`: Durable Object + SQLite. Edge-native, physical isolation per instance.
205
+ * - `'d1'`: Cloudflare D1. Exportable via `wrangler d1 export`, enables migration to PostgreSQL.
206
+ * - `'neon'`: Neon PostgreSQL. Use `npx edgebase neon setup` or provide a connectionString env key; deploy provisions Hyperdrive from it.
207
+ * - `'postgres'`: Custom PostgreSQL. User provides connectionString manually.
208
+ *
209
+ * Default: Single-instance namespaces (no `instance` flag) default to D1.
210
+ * Multi-tenant namespaces (`instance: true`) default to DO.
211
+ *
212
+ * SDK code is identical regardless of provider — the server routes internally.
213
+ */
214
+ provider?: DbProvider;
215
+ /**
216
+ * Multi-tenant instance mode.
217
+ * When true, each instanceId gets its own Durable Object (physical isolation).
218
+ * When false/omitted, the namespace is a single-instance database routed to D1.
219
+ *
220
+ * Example:
221
+ * `instance: true` → `edgebase.db('workspace', 'ws-456')` creates DO per workspace
222
+ * (no instance) → `edgebase.db('shared')` routes to a single D1 database
223
+ */
224
+ instance?: boolean;
225
+ /**
226
+ * PostgreSQL connection string (or env variable reference).
227
+ * Only used when provider is `'neon'` or `'postgres'`.
228
+ * For deploy: reads from `.env.release` using key `DB_POSTGRES_{NAMESPACE_UPPER}_URL`
229
+ * (or a custom env key if connectionString is set).
230
+ * For dev: reads from `.env.development` using the same key.
231
+ */
232
+ connectionString?: string;
233
+ access?: DbAccess;
234
+ admin?: DbAdminConfig;
235
+ /** Tables within this DB namespace. */
236
+ tables?: Record<string, TableConfig>;
237
+ }
238
+ export interface WriteFileMeta {
239
+ /** File size in bytes from form data */
240
+ size: number;
241
+ /** MIME type from form data */
242
+ contentType: string;
243
+ /** Requested file path/key */
244
+ key: string;
245
+ }
246
+ export interface R2FileMeta {
247
+ size: number;
248
+ contentType: string;
249
+ key: string;
250
+ etag?: string;
251
+ uploadedAt?: string;
252
+ uploadedBy?: string;
253
+ customMetadata?: Record<string, string>;
254
+ }
255
+ export interface StorageBucketRules {
256
+ read?: (auth: AuthContext | null, file: R2FileMeta) => boolean;
257
+ /** write: file = form data meta (§19) — size/contentType available before upload */
258
+ write?: (auth: AuthContext | null, file: WriteFileMeta) => boolean;
259
+ delete?: (auth: AuthContext | null, file: R2FileMeta) => boolean;
260
+ }
261
+ export interface StorageHookCtx {
262
+ waitUntil(promise: Promise<unknown>): void;
263
+ push: {
264
+ send(userId: string, payload: {
265
+ title?: string;
266
+ body: string;
267
+ }): Promise<void>;
268
+ };
269
+ }
270
+ export interface StorageHooks {
271
+ /** Before upload. Return custom metadata to merge, or throw to reject. NO file body. */
272
+ beforeUpload?: (auth: AuthContext | null, file: WriteFileMeta, ctx: StorageHookCtx) => Promise<Record<string, string> | void> | Record<string, string> | void;
273
+ /** After upload (fire-and-forget via waitUntil). Receives final R2 metadata. */
274
+ afterUpload?: (auth: AuthContext | null, file: R2FileMeta, ctx: StorageHookCtx) => Promise<void> | void;
275
+ /** Before delete. Throw to reject. */
276
+ beforeDelete?: (auth: AuthContext | null, file: R2FileMeta, ctx: StorageHookCtx) => Promise<void> | void;
277
+ /** After delete (fire-and-forget via waitUntil). */
278
+ afterDelete?: (auth: AuthContext | null, file: R2FileMeta, ctx: StorageHookCtx) => Promise<void> | void;
279
+ /** Before download. Throw to reject. */
280
+ beforeDownload?: (auth: AuthContext | null, file: R2FileMeta, ctx: StorageHookCtx) => Promise<void> | void;
281
+ }
282
+ export type StorageBucketAccess = StorageBucketRules;
283
+ export interface StorageHandlers {
284
+ hooks?: StorageHooks;
285
+ }
286
+ export interface StorageBucketConfig {
287
+ access?: StorageBucketAccess;
288
+ handlers?: StorageHandlers;
289
+ binding?: string;
290
+ }
291
+ export interface StorageConfig {
292
+ buckets?: Record<string, StorageBucketConfig>;
293
+ }
294
+ export interface MagicLinkConfig {
295
+ /** Enable magic link (passwordless email) authentication. Default: false */
296
+ enabled?: boolean;
297
+ /** Auto-create account if email is not registered. Default: true */
298
+ autoCreate?: boolean;
299
+ /** Token time-to-live. Default: '15m' */
300
+ tokenTTL?: string;
301
+ }
302
+ export interface MfaConfig {
303
+ /** Enable TOTP-based multi-factor authentication. Default: false */
304
+ totp?: boolean;
305
+ }
306
+ export interface EmailOtpConfig {
307
+ /** Enable email OTP (passwordless email code) authentication. Default: false */
308
+ enabled?: boolean;
309
+ /** Auto-create new user on first OTP request if email is not registered. Default: true */
310
+ autoCreate?: boolean;
311
+ }
312
+ export interface PasswordPolicyConfig {
313
+ /** Minimum password length. Default: 8 */
314
+ minLength?: number;
315
+ /** Require at least one uppercase letter. Default: false */
316
+ requireUppercase?: boolean;
317
+ /** Require at least one lowercase letter. Default: false */
318
+ requireLowercase?: boolean;
319
+ /** Require at least one digit. Default: false */
320
+ requireNumber?: boolean;
321
+ /** Require at least one special character. Default: false */
322
+ requireSpecial?: boolean;
323
+ /** Check password against HIBP (Have I Been Pwned) database via k-anonymity. Fail-open if API unavailable. Default: false */
324
+ checkLeaked?: boolean;
325
+ }
326
+ export interface OAuthProviderCredentialsConfig {
327
+ clientId: string;
328
+ clientSecret: string;
329
+ }
330
+ export interface OidcProviderCredentialsConfig extends OAuthProviderCredentialsConfig {
331
+ issuer: string;
332
+ scopes?: string[];
333
+ }
334
+ export interface OAuthProvidersConfig {
335
+ /** OIDC federation providers keyed by provider slug. */
336
+ oidc?: Record<string, OidcProviderCredentialsConfig>;
337
+ /** Built-in provider name → credentials. */
338
+ [provider: string]: OAuthProviderCredentialsConfig | Record<string, OidcProviderCredentialsConfig> | undefined;
339
+ }
340
+ export interface AuthConfig {
341
+ /**
342
+ * Auth database backend provider.
343
+ * - `'d1'` (default): Cloudflare D1 (AUTH_DB binding). Zero-cost, global.
344
+ * - `'neon'`: Neon PostgreSQL via Hyperdrive. Zero-downtime upgrade from D1.
345
+ * - `'postgres'`: Custom PostgreSQL. User provides connectionString.
346
+ *
347
+ * SDK code is identical regardless of provider — the server routes internally.
348
+ * Migration: `npx edgebase migrate auth --from=d1 --to=neon`
349
+ */
350
+ provider?: AuthDbProvider;
351
+ /**
352
+ * PostgreSQL connection string environment variable name.
353
+ * Required when `provider` is `'neon'` or `'postgres'`.
354
+ * `npx edgebase neon setup --auth` writes the corresponding value to local env files.
355
+ * CLI stores the actual URL in secrets; this is the env variable key.
356
+ * Example: `'AUTH_POSTGRES_URL'`
357
+ */
358
+ connectionString?: string;
359
+ emailAuth?: boolean;
360
+ anonymousAuth?: boolean;
361
+ /** Enable phone/SMS OTP authentication. Default: false */
362
+ phoneAuth?: boolean;
363
+ allowedOAuthProviders?: string[];
364
+ /**
365
+ * OAuth provider credentials.
366
+ * - Built-in: auth.oauth.{provider}.clientId / clientSecret
367
+ * - OIDC: auth.oauth.oidc.{name}.clientId / clientSecret / issuer
368
+ */
369
+ oauth?: OAuthProvidersConfig;
370
+ /**
371
+ * Optional client redirect URL allowlist for OAuth and email-based auth actions.
372
+ * When unset, redirect URLs are accepted as-is for backward compatibility.
373
+ *
374
+ * Supported forms:
375
+ * - exact URL: 'https://app.example.com/auth/callback'
376
+ * - origin-wide: 'https://app.example.com'
377
+ * - prefix wildcard: 'https://app.example.com/auth/*'
378
+ */
379
+ allowedRedirectUrls?: string[];
380
+ session?: {
381
+ accessTokenTTL?: string;
382
+ refreshTokenTTL?: string;
383
+ /** Maximum number of active sessions per user. 0 or undefined = unlimited. Oldest sessions are evicted when limit is exceeded. */
384
+ maxActiveSessions?: number;
385
+ };
386
+ anonymousRetentionDays?: number;
387
+ /** If true, deletes user DB (user:{id}) when a user is deleted. */
388
+ cleanupOrphanData?: boolean;
389
+ /** Magic link (passwordless email login) configuration. */
390
+ magicLink?: MagicLinkConfig;
391
+ /** MFA/TOTP configuration. */
392
+ mfa?: MfaConfig;
393
+ /** Email OTP (passwordless email code) configuration. */
394
+ emailOtp?: EmailOtpConfig;
395
+ /** Password strength policy configuration. */
396
+ passwordPolicy?: PasswordPolicyConfig;
397
+ /** Passkeys / WebAuthn configuration. */
398
+ passkeys?: PasskeysConfig;
399
+ /** Preferred auth action access config. */
400
+ access?: AuthAccess;
401
+ /** Preferred auth handler groups. */
402
+ handlers?: AuthHandlers;
403
+ }
404
+ export interface PasskeysConfig {
405
+ /** Enable WebAuthn/Passkeys. Default: false */
406
+ enabled?: boolean;
407
+ /** Relying Party name (displayed in authenticator UI). */
408
+ rpName: string;
409
+ /** Relying Party ID (usually your domain, e.g. 'example.com'). */
410
+ rpID: string;
411
+ /** Expected origin(s) for WebAuthn requests (e.g. 'https://example.com'). */
412
+ origin: string | string[];
413
+ }
414
+ /**
415
+ * A string value that can be either a single string (applies to all locales)
416
+ * or a per-locale map (e.g. { en: '...', ko: '...', ja: '...' }).
417
+ * When a per-locale map is used, locale resolution falls back: exact → base language → 'en'.
418
+ */
419
+ export type LocalizedString = string | Record<string, string>;
420
+ /**
421
+ * Custom HTML template overrides for auth emails.
422
+ * Use {{variable}} placeholders for dynamic values.
423
+ * When provided, replaces the default built-in template entirely.
424
+ * Can be a single string (all locales) or a per-locale map for i18n.
425
+ */
426
+ export interface EmailTemplateOverrides {
427
+ /** Custom HTML for email verification. Variables: {{appName}}, {{verifyUrl}}, {{token}}, {{expiresInHours}} */
428
+ verification?: LocalizedString;
429
+ /** Custom HTML for password reset. Variables: {{appName}}, {{resetUrl}}, {{token}}, {{expiresInMinutes}} */
430
+ passwordReset?: LocalizedString;
431
+ /** Custom HTML for magic link login. Variables: {{appName}}, {{magicLinkUrl}}, {{expiresInMinutes}} */
432
+ magicLink?: LocalizedString;
433
+ /** Custom HTML for email OTP. Variables: {{appName}}, {{code}}, {{expiresInMinutes}} */
434
+ emailOtp?: LocalizedString;
435
+ /** Custom HTML for email change verification. Variables: {{appName}}, {{verifyUrl}}, {{token}}, {{newEmail}}, {{expiresInHours}} */
436
+ emailChange?: LocalizedString;
437
+ }
438
+ /**
439
+ * Custom email subject overrides. Use {{appName}} placeholder for the app name.
440
+ * Defaults: "[{{appName}}] Verify your email", "[{{appName}}] Reset your password", etc.
441
+ * Can be a single string (all locales) or a per-locale map for i18n.
442
+ */
443
+ export interface EmailSubjectOverrides {
444
+ verification?: LocalizedString;
445
+ passwordReset?: LocalizedString;
446
+ magicLink?: LocalizedString;
447
+ emailOtp?: LocalizedString;
448
+ emailChange?: LocalizedString;
449
+ }
450
+ export interface EmailConfig {
451
+ provider: 'resend' | 'sendgrid' | 'mailgun' | 'ses';
452
+ apiKey: string;
453
+ from: string;
454
+ domain?: string;
455
+ region?: string;
456
+ appName?: string;
457
+ /** Default locale for auth emails when user has no preference. Default: 'en' */
458
+ defaultLocale?: string;
459
+ verifyUrl?: string;
460
+ resetUrl?: string;
461
+ /** Magic link URL template. Use {token} placeholder. e.g. 'https://app.com/auth/magic-link?token={token}' */
462
+ magicLinkUrl?: string;
463
+ /** Email change verification URL template. Use {token} placeholder. e.g. 'https://app.com/auth/verify-email-change?token={token}' */
464
+ emailChangeUrl?: string;
465
+ /** Custom HTML template overrides for auth emails. */
466
+ templates?: EmailTemplateOverrides;
467
+ /** Custom email subject line overrides. */
468
+ subjects?: EmailSubjectOverrides;
469
+ }
470
+ export type MailType = 'verification' | 'passwordReset' | 'magicLink' | 'emailOtp' | 'emailChange';
471
+ export interface MailHookCtx {
472
+ waitUntil(promise: Promise<unknown>): void;
473
+ }
474
+ export interface MailHooks {
475
+ /**
476
+ * Intercept outgoing emails. Can modify subject/html or reject (throw). Blocking, 5s timeout.
477
+ * The optional `locale` parameter contains the resolved locale used for the email.
478
+ */
479
+ onSend?: (type: MailType, to: string, subject: string, html: string, ctx: MailHookCtx, locale?: string) => Promise<{
480
+ subject?: string;
481
+ html?: string;
482
+ } | void> | {
483
+ subject?: string;
484
+ html?: string;
485
+ } | void;
486
+ }
487
+ export type SmsType = 'phoneOtp' | 'phoneLink';
488
+ export interface SmsHookCtx {
489
+ waitUntil(promise: Promise<unknown>): void;
490
+ }
491
+ export interface SmsHooks {
492
+ /**
493
+ * Intercept outgoing SMS. Can modify body or reject (throw). Blocking, 5s timeout.
494
+ */
495
+ onSend?: (type: SmsType, to: string, body: string, ctx: SmsHookCtx) => Promise<{
496
+ body?: string;
497
+ } | void> | {
498
+ body?: string;
499
+ } | void;
500
+ }
501
+ export interface AuthAccessCtx {
502
+ request?: unknown;
503
+ auth?: AuthContext | null;
504
+ ip?: string;
505
+ }
506
+ export type AuthAccessRule = (input: Record<string, unknown> | null, ctx: AuthAccessCtx) => boolean | Promise<boolean>;
507
+ export interface AuthAccess {
508
+ signUp?: AuthAccessRule;
509
+ signIn?: AuthAccessRule;
510
+ signInAnonymous?: AuthAccessRule;
511
+ signInMagicLink?: AuthAccessRule;
512
+ verifyMagicLink?: AuthAccessRule;
513
+ signInPhone?: AuthAccessRule;
514
+ verifyPhoneOtp?: AuthAccessRule;
515
+ linkPhone?: AuthAccessRule;
516
+ verifyLinkPhone?: AuthAccessRule;
517
+ signInEmailOtp?: AuthAccessRule;
518
+ verifyEmailOtp?: AuthAccessRule;
519
+ mfaTotpEnroll?: AuthAccessRule;
520
+ mfaTotpVerify?: AuthAccessRule;
521
+ mfaVerify?: AuthAccessRule;
522
+ mfaRecovery?: AuthAccessRule;
523
+ mfaTotpDelete?: AuthAccessRule;
524
+ mfaFactors?: AuthAccessRule;
525
+ requestPasswordReset?: AuthAccessRule;
526
+ resetPassword?: AuthAccessRule;
527
+ verifyEmail?: AuthAccessRule;
528
+ changePassword?: AuthAccessRule;
529
+ changeEmail?: AuthAccessRule;
530
+ verifyEmailChange?: AuthAccessRule;
531
+ passkeysRegisterOptions?: AuthAccessRule;
532
+ passkeysRegister?: AuthAccessRule;
533
+ passkeysAuthOptions?: AuthAccessRule;
534
+ passkeysAuthenticate?: AuthAccessRule;
535
+ passkeysList?: AuthAccessRule;
536
+ passkeysDelete?: AuthAccessRule;
537
+ getMe?: AuthAccessRule;
538
+ updateProfile?: AuthAccessRule;
539
+ getSessions?: AuthAccessRule;
540
+ deleteSession?: AuthAccessRule;
541
+ getIdentities?: AuthAccessRule;
542
+ deleteIdentity?: AuthAccessRule;
543
+ linkEmail?: AuthAccessRule;
544
+ oauthRedirect?: AuthAccessRule;
545
+ oauthCallback?: AuthAccessRule;
546
+ oauthLinkStart?: AuthAccessRule;
547
+ oauthLinkCallback?: AuthAccessRule;
548
+ refresh?: AuthAccessRule;
549
+ signOut?: AuthAccessRule;
550
+ }
551
+ export interface AuthHandlerHooks {
552
+ enrich?: (auth: AuthContext, request: unknown) => Promise<Record<string, unknown>> | Record<string, unknown>;
553
+ }
554
+ export interface AuthHandlers {
555
+ hooks?: AuthHandlerHooks;
556
+ email?: MailHooks;
557
+ sms?: SmsHooks;
558
+ }
559
+ export interface SmsConfig {
560
+ provider: 'twilio' | 'messagebird' | 'vonage';
561
+ /** Twilio Account SID */
562
+ accountSid?: string;
563
+ /** Twilio Auth Token */
564
+ authToken?: string;
565
+ /** MessageBird / Vonage API Key */
566
+ apiKey?: string;
567
+ /** Vonage API Secret */
568
+ apiSecret?: string;
569
+ /** Sender phone number in E.164 format (e.g. '+15551234567') */
570
+ from: string;
571
+ }
572
+ export interface CorsConfig {
573
+ origin?: string | string[];
574
+ methods?: string[];
575
+ credentials?: boolean;
576
+ maxAge?: number;
577
+ }
578
+ export interface RateLimitGroupConfig {
579
+ requests: number;
580
+ window: string | number;
581
+ /**
582
+ * Optional Cloudflare Rate Limiting Binding override.
583
+ * Applied by the CLI when synthesizing a temporary wrangler.toml for dev/deploy.
584
+ */
585
+ binding?: RateLimitBindingConfig;
586
+ }
587
+ export interface RateLimitBindingConfig {
588
+ /** Disable the Cloudflare binding for this built-in group. */
589
+ enabled?: boolean;
590
+ /** Binding ceiling. Defaults to the framework safety-net value when omitted. */
591
+ limit?: number;
592
+ /** Cloudflare currently supports only 10s or 60s periods. */
593
+ period?: 10 | 60;
594
+ /** Optional custom namespace_id for the binding. */
595
+ namespaceId?: string;
596
+ }
597
+ export interface RateLimitingConfig {
598
+ [key: string]: RateLimitGroupConfig | undefined;
599
+ global?: RateLimitGroupConfig;
600
+ auth?: RateLimitGroupConfig;
601
+ authSignin?: RateLimitGroupConfig;
602
+ authSignup?: RateLimitGroupConfig;
603
+ db?: RateLimitGroupConfig;
604
+ storage?: RateLimitGroupConfig;
605
+ functions?: RateLimitGroupConfig;
606
+ events?: RateLimitGroupConfig;
607
+ }
608
+ export interface FunctionsConfig {
609
+ scheduleFunctionTimeout?: string;
610
+ }
611
+ export interface CloudflareConfig {
612
+ /**
613
+ * Additional raw Wrangler cron triggers to include at deploy time.
614
+ * These wake the Worker's scheduled() handler even when not tied to a
615
+ * specific schedule function.
616
+ */
617
+ extraCrons?: string[];
618
+ }
619
+ export interface ApiConfig {
620
+ schemaEndpoint?: boolean | 'authenticated';
621
+ }
622
+ export type ScopeString = string;
623
+ export interface ServiceKeyConstraints {
624
+ expiresAt?: string;
625
+ env?: string[];
626
+ ipCidr?: string[];
627
+ tenant?: string;
628
+ }
629
+ export interface ServiceKeyEntry {
630
+ kid: string;
631
+ tier: 'root' | 'scoped';
632
+ scopes: ScopeString[];
633
+ constraints?: ServiceKeyConstraints;
634
+ secretSource: 'dashboard' | 'inline';
635
+ secretRef?: string;
636
+ inlineSecret?: string;
637
+ enabled?: boolean;
638
+ }
639
+ export interface ServiceKeysConfig {
640
+ policyVersion?: number;
641
+ keys: ServiceKeyEntry[];
642
+ }
643
+ export interface CaptchaConfig {
644
+ siteKey: string;
645
+ secretKey: string;
646
+ failMode?: 'open' | 'closed';
647
+ siteverifyTimeout?: number;
648
+ }
649
+ export interface KvNamespaceRules {
650
+ read?: (auth: AuthContext | null) => boolean;
651
+ write?: (auth: AuthContext | null) => boolean;
652
+ }
653
+ export interface KvNamespaceConfig {
654
+ binding: string;
655
+ rules?: KvNamespaceRules;
656
+ }
657
+ export interface D1DatabaseConfig {
658
+ binding: string;
659
+ }
660
+ export interface VectorizeConfig {
661
+ binding?: string;
662
+ dimensions: number;
663
+ metric: 'cosine' | 'euclidean' | 'dot-product';
664
+ }
665
+ /**
666
+ * Optional endpoint overrides for FCM-related APIs.
667
+ * Defaults to Google production URLs when omitted.
668
+ * Used for testing with a mock FCM server.
669
+ */
670
+ export interface PushFcmEndpoints {
671
+ /** Google OAuth2 token endpoint. Default: 'https://oauth2.googleapis.com/token' */
672
+ oauth2TokenUrl?: string;
673
+ /** FCM HTTP v1 send endpoint. Default: 'https://fcm.googleapis.com/v1/projects/{projectId}/messages:send' */
674
+ fcmSendUrl?: string;
675
+ /** IID (Instance ID) API base URL. Default: 'https://iid.googleapis.com' */
676
+ iidBaseUrl?: string;
677
+ }
678
+ export interface PushFcmConfig {
679
+ projectId: string;
680
+ /** Override FCM/OAuth2/IID endpoints for testing. Omit for production. */
681
+ endpoints?: PushFcmEndpoints;
682
+ /**
683
+ * FCM Service Account JSON string. Fallback for environments where
684
+ * the PUSH_FCM_SERVICE_ACCOUNT env var is not directly accessible.
685
+ * Prefer the env var in production; this is primarily for test setups.
686
+ */
687
+ serviceAccount?: string;
688
+ }
689
+ export interface PushRules {
690
+ /** Who can send push notifications. */
691
+ send?: (auth: AuthContext | null, target: {
692
+ userId: string;
693
+ }) => boolean;
694
+ }
695
+ export type PushAccess = PushRules;
696
+ export interface PushHookCtx {
697
+ request?: unknown;
698
+ waitUntil(promise: Promise<unknown>): void;
699
+ }
700
+ export interface PushSendInput {
701
+ kind: 'user' | 'users' | 'token' | 'topic' | 'broadcast';
702
+ payload: Record<string, unknown>;
703
+ userId?: string;
704
+ userIds?: string[];
705
+ token?: string;
706
+ topic?: string;
707
+ platform?: string;
708
+ }
709
+ export interface PushSendOutput {
710
+ sent?: number;
711
+ failed?: number;
712
+ removed?: number;
713
+ error?: string;
714
+ raw?: unknown;
715
+ }
716
+ export interface PushHandlers {
717
+ hooks?: {
718
+ beforeSend?: (auth: AuthContext | null, input: PushSendInput, ctx: PushHookCtx) => Promise<PushSendInput | void> | PushSendInput | void;
719
+ afterSend?: (auth: AuthContext | null, input: PushSendInput, output: PushSendOutput, ctx: PushHookCtx) => Promise<void> | void;
720
+ };
721
+ }
722
+ export interface PushConfig {
723
+ fcm?: PushFcmConfig;
724
+ access?: PushAccess;
725
+ handlers?: PushHandlers;
726
+ }
727
+ export interface DatabaseLiveConfig {
728
+ authTimeoutMs?: number;
729
+ batchThreshold?: number;
730
+ }
731
+ /** Info about the player who triggered the action / lifecycle event. */
732
+ export interface RoomSender {
733
+ userId: string;
734
+ connectionId: string;
735
+ role?: string;
736
+ }
737
+ /** DB table proxy available inside Room handlers via ctx.admin.db(). */
738
+ export interface RoomTableProxy {
739
+ get(id: string): Promise<Record<string, unknown> | null>;
740
+ list(filter?: Record<string, unknown>): Promise<Record<string, unknown>[]>;
741
+ insert(data: Record<string, unknown>): Promise<Record<string, unknown>>;
742
+ update(id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>;
743
+ delete(id: string): Promise<void>;
744
+ }
745
+ /** DB namespace proxy: ctx.admin.db('shared').table('posts') */
746
+ export interface RoomDbProxy {
747
+ table(name: string): RoomTableProxy;
748
+ }
749
+ /** Admin context injected into Room handlers (ctx parameter). */
750
+ export interface RoomHandlerContext {
751
+ admin: {
752
+ db(namespace: string, id?: string): RoomDbProxy;
753
+ push: {
754
+ send(userId: string, payload: {
755
+ title?: string;
756
+ body: string;
757
+ }): Promise<void>;
758
+ sendMany(userIds: string[], payload: {
759
+ title?: string;
760
+ body: string;
761
+ }): Promise<void>;
762
+ };
763
+ broadcast(channel: string, event: string, data?: unknown): Promise<void>;
764
+ };
765
+ }
766
+ /** Public member descriptor used by canonical room hooks. */
767
+ export interface RoomMemberInfo {
768
+ memberId: string;
769
+ userId: string;
770
+ connectionId?: string;
771
+ connectionCount?: number;
772
+ role?: string;
773
+ }
774
+ export type RoomRuntimeTarget = 'rooms';
775
+ export interface RoomRuntimeConfig {
776
+ /** Target runtime. */
777
+ target?: RoomRuntimeTarget;
778
+ }
779
+ /**
780
+ * Server-side Room API available inside handlers (room parameter).
781
+ * All state mutations are server-only — clients can only read + subscribe + send().
782
+ */
783
+ export interface RoomServerAPI {
784
+ /** Current shared state (visible to all clients). */
785
+ getSharedState(): Record<string, unknown>;
786
+ /** Mutate shared state via updater function. Delta auto-broadcast to all clients. */
787
+ setSharedState(updater: (state: Record<string, unknown>) => Record<string, unknown>): void;
788
+ /** Get a specific player's state by userId. */
789
+ player(userId: string): Record<string, unknown>;
790
+ /** Get all players: [userId, state][] */
791
+ players(): Array<[string, Record<string, unknown>]>;
792
+ /** Mutate a player's state. Delta unicast to that player only. */
793
+ setPlayerState(userId: string, updater: (state: Record<string, unknown>) => Record<string, unknown>): void;
794
+ /** Current server-only state (never sent to clients). */
795
+ getServerState(): Record<string, unknown>;
796
+ /** Mutate server-only state. No broadcast. */
797
+ setServerState(updater: (state: Record<string, unknown>) => Record<string, unknown>): void;
798
+ /** Broadcast a one-off message to all connected clients. options.exclude: userIds to skip. */
799
+ sendMessage(type: string, data?: unknown, options?: {
800
+ exclude?: string[];
801
+ }): void;
802
+ /** Send a one-off message to a specific user only (all their connections). */
803
+ sendMessageTo(userId: string, type: string, data?: unknown): void;
804
+ /** Forcefully disconnect a player. Triggers onLeave with reason='kicked'. */
805
+ kick(userId: string): void;
806
+ /** Immediately persist all 3 state areas to DO Storage. Use after critical state changes. */
807
+ saveState(): Promise<void>;
808
+ /** Schedule a named timer. Calls onTimer[name] after ms milliseconds. */
809
+ setTimer(name: string, ms: number, data?: unknown): void;
810
+ /** Cancel a named timer. No-op if timer doesn't exist. */
811
+ clearTimer(name: string): void;
812
+ /** Set developer-defined metadata (queryable via HTTP without joining). */
813
+ setMetadata(data: Record<string, unknown>): void;
814
+ /** Get current room metadata. */
815
+ getMetadata(): Record<string, unknown>;
816
+ }
817
+ /**
818
+ * Room namespace config. Each key in `rooms` is a namespace (e.g. 'game', 'lobby').
819
+ * Client connects via: client.room("namespace", "roomId")
820
+ */
821
+ export interface RoomNamespaceConfig {
822
+ /** Reconnect grace period in ms. 0 = immediate onLeave on disconnect. Default: 30000 */
823
+ reconnectTimeout?: number;
824
+ /** Rate limit for send() calls. Default: { actions: 10 } (per second, token bucket) */
825
+ rateLimit?: {
826
+ actions: number;
827
+ };
828
+ /** Maximum concurrent players. Default: 100 */
829
+ maxPlayers?: number;
830
+ /** Maximum state size in bytes (shared + all player states combined). Default: 1MB */
831
+ maxStateSize?: number;
832
+ /** How often to persist all 3 state areas to DO Storage (ms). Default: 60000 (1 minute). */
833
+ stateSaveInterval?: number;
834
+ /** Time after last save before persisted state is auto-deleted (ms). Default: 86400000 (24 hours). Acts as safety net for orphaned storage. */
835
+ stateTTL?: number;
836
+ /** Public room operation escape hatch for release mode. Default: false. */
837
+ public?: boolean | {
838
+ metadata?: boolean;
839
+ join?: boolean;
840
+ action?: boolean;
841
+ };
842
+ /** Preferred access config for room operations. */
843
+ access?: RoomAccess;
844
+ /** Parallel runtime selection policy for room-sticky rollout. */
845
+ runtime?: RoomRuntimeConfig;
846
+ /** Canonical authoritative state config used by the unified Room runtime. */
847
+ state?: RoomStateConfig;
848
+ /** Canonical extension hooks used by the unified Room runtime. */
849
+ hooks?: RoomHooks;
850
+ /** Preferred handler groups. */
851
+ handlers?: RoomHandlers;
852
+ }
853
+ export interface RoomAccess {
854
+ metadata?: (auth: AuthContext | null, roomId: string) => boolean | Promise<boolean>;
855
+ join?: (auth: AuthContext | null, roomId: string) => boolean | Promise<boolean>;
856
+ action?: (auth: AuthContext | null, roomId: string, actionType: string, payload: unknown) => boolean | Promise<boolean>;
857
+ signal?: (auth: AuthContext | null, roomId: string, event: string, payload: unknown) => boolean | Promise<boolean>;
858
+ media?: RoomMediaAccess;
859
+ admin?: (auth: AuthContext | null, roomId: string, operation: string, payload: unknown) => boolean | Promise<boolean>;
860
+ }
861
+ export type RoomCreateHandler = (room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
862
+ export type RoomJoinHandler = (sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
863
+ export type RoomLeaveHandler = (sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext, reason: 'leave' | 'disconnect' | 'kicked') => Promise<void> | void;
864
+ export type RoomDestroyHandler = (room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
865
+ export type RoomActionHandlers = Record<string, (action: unknown, room: RoomServerAPI, sender: RoomSender, ctx: RoomHandlerContext) => Promise<unknown> | unknown>;
866
+ export type RoomTimerHandlers = Record<string, (room: RoomServerAPI, ctx: RoomHandlerContext, data?: unknown) => Promise<void> | void>;
867
+ export interface RoomLifecycleHandlers {
868
+ onCreate?: RoomCreateHandler;
869
+ onJoin?: RoomJoinHandler;
870
+ onLeave?: RoomLeaveHandler;
871
+ onDestroy?: RoomDestroyHandler;
872
+ }
873
+ export interface RoomMediaAccess {
874
+ subscribe?: (auth: AuthContext | null, roomId: string, payload: unknown) => boolean | Promise<boolean>;
875
+ publish?: (auth: AuthContext | null, roomId: string, kind: string, payload: unknown) => boolean | Promise<boolean>;
876
+ control?: (auth: AuthContext | null, roomId: string, operation: string, payload: unknown) => boolean | Promise<boolean>;
877
+ }
878
+ export interface RoomStateConfig {
879
+ actions?: RoomActionHandlers;
880
+ timers?: RoomTimerHandlers;
881
+ }
882
+ export interface RoomMemberHooks {
883
+ onJoin?: (member: RoomMemberInfo, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
884
+ onLeave?: (member: RoomMemberInfo, room: RoomServerAPI, ctx: RoomHandlerContext, reason: string) => Promise<void> | void;
885
+ onStateChange?: (member: RoomMemberInfo, state: Record<string, unknown>, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
886
+ }
887
+ export interface RoomStateHooks {
888
+ onStateChange?: (delta: Record<string, unknown>, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
889
+ }
890
+ export interface RoomSignalHooks {
891
+ beforeSend?: (event: string, payload: unknown, sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<unknown | false | void> | unknown | false | void;
892
+ onSend?: (event: string, payload: unknown, sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
893
+ }
894
+ export interface RoomMediaHooks {
895
+ beforePublish?: (kind: string, sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<unknown | false | void> | unknown | false | void;
896
+ onPublished?: (kind: string, sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
897
+ onUnpublished?: (kind: string, sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
898
+ onMuteChange?: (kind: string, sender: RoomSender, muted: boolean, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
899
+ }
900
+ export interface RoomSessionHooks {
901
+ onReconnect?: (sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
902
+ onDisconnectTimeout?: (sender: RoomSender, room: RoomServerAPI, ctx: RoomHandlerContext) => Promise<void> | void;
903
+ }
904
+ export interface RoomHooks {
905
+ lifecycle?: RoomLifecycleHandlers;
906
+ members?: RoomMemberHooks;
907
+ state?: RoomStateHooks;
908
+ signals?: RoomSignalHooks;
909
+ media?: RoomMediaHooks;
910
+ session?: RoomSessionHooks;
911
+ }
912
+ export interface RoomHandlers {
913
+ lifecycle?: RoomLifecycleHandlers;
914
+ actions?: RoomActionHandlers;
915
+ timers?: RoomTimerHandlers;
916
+ }
917
+ export interface EdgeBaseConfig {
918
+ /** Optional canonical base URL for auth/OAuth redirects and runtime metadata. */
919
+ baseUrl?: string;
920
+ /**
921
+ * Trust reverse-proxy forwarded client IP headers in self-hosted environments.
922
+ * Default: false — only Cloudflare's CF-Connecting-IP is trusted.
923
+ */
924
+ trustSelfHostedProxy?: boolean;
925
+ /**
926
+ * Database blocks. Each key is a namespace:
927
+ * - 'shared': single static DB (no id)
928
+ * - 'workspace', 'user', etc.: dynamic per-id DB
929
+ * Clients send: edgebase.db('workspace', 'ws-456')
930
+ */
931
+ databases?: Record<string, DbBlock>;
932
+ /** Release mode. false = rules bypassed (dev), true = enforce (prod). Default: false. */
933
+ release?: boolean;
934
+ auth?: AuthConfig;
935
+ email?: EmailConfig;
936
+ /** SMS provider configuration for phone authentication. */
937
+ sms?: SmsConfig;
938
+ storage?: StorageConfig;
939
+ cors?: CorsConfig;
940
+ databaseLive?: DatabaseLiveConfig;
941
+ rateLimiting?: RateLimitingConfig;
942
+ functions?: FunctionsConfig;
943
+ cloudflare?: CloudflareConfig;
944
+ api?: ApiConfig;
945
+ serviceKeys?: ServiceKeysConfig;
946
+ plugins?: PluginInstance[];
947
+ kv?: Record<string, KvNamespaceConfig>;
948
+ d1?: Record<string, D1DatabaseConfig>;
949
+ vectorize?: Record<string, VectorizeConfig>;
950
+ captcha?: boolean | CaptchaConfig;
951
+ push?: PushConfig;
952
+ /** Room namespaces. Key = namespace name (e.g. 'game', 'lobby'). */
953
+ rooms?: Record<string, RoomNamespaceConfig>;
954
+ }
955
+ export declare function getDbAccess(dbBlock?: DbBlock): DbAccess | undefined;
956
+ export declare function getTableAccess(tableConfig?: TableConfig): TableAccess | undefined;
957
+ export declare function getTableHooks(tableConfig?: TableConfig): TableHooks | undefined;
958
+ export declare function getStorageBucketAccess(bucketConfig?: StorageBucketConfig): StorageBucketAccess | undefined;
959
+ export declare function getStorageHooks(bucketConfig?: StorageBucketConfig): StorageHooks | undefined;
960
+ export declare function getPushAccess(config?: PushConfig): PushAccess | undefined;
961
+ export declare function getPushHandlers(config?: PushConfig): PushHandlers | undefined;
962
+ export declare function getAuthAccess(config?: AuthConfig): AuthAccess | undefined;
963
+ export declare function getAuthHandlers(config?: EdgeBaseConfig): AuthHandlers | undefined;
964
+ export declare function getAuthEnrichHandler(config?: EdgeBaseConfig): AuthHandlerHooks['enrich'] | undefined;
965
+ export declare function getMailHooks(config?: EdgeBaseConfig): MailHooks | undefined;
966
+ export declare function getRoomAccess(namespaceConfig?: RoomNamespaceConfig): RoomAccess | undefined;
967
+ export declare function getRoomStateConfig(namespaceConfig?: RoomNamespaceConfig): RoomStateConfig | undefined;
968
+ export declare function getRoomHooks(namespaceConfig?: RoomNamespaceConfig): RoomHooks | undefined;
969
+ export declare function getRoomHandlers(namespaceConfig?: RoomNamespaceConfig): RoomHandlers | undefined;
970
+ export declare function getRoomLifecycleHandlers(namespaceConfig?: RoomNamespaceConfig): RoomLifecycleHandlers | undefined;
971
+ export declare function getRoomActionHandlers(namespaceConfig?: RoomNamespaceConfig): RoomActionHandlers | undefined;
972
+ export declare function getRoomTimerHandlers(namespaceConfig?: RoomNamespaceConfig): RoomTimerHandlers | undefined;
973
+ export declare function materializeConfig(config: EdgeBaseConfig): EdgeBaseConfig;
974
+ export type FunctionTriggerType = 'db' | 'http' | 'schedule' | 'auth' | 'storage';
975
+ export interface DbTrigger {
976
+ type: 'db';
977
+ /** Table name within the DB block. */
978
+ table: string;
979
+ event: 'insert' | 'update' | 'delete';
980
+ }
981
+ export interface HttpTrigger {
982
+ type: 'http';
983
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
984
+ path?: string;
985
+ }
986
+ export interface ScheduleTrigger {
987
+ type: 'schedule';
988
+ cron: string;
989
+ }
990
+ export interface AuthTrigger {
991
+ type: 'auth';
992
+ event: 'beforeSignUp' | 'afterSignUp' | 'beforeSignIn' | 'afterSignIn' | 'onTokenRefresh' | 'beforePasswordReset' | 'afterPasswordReset' | 'beforeSignOut' | 'afterSignOut' | 'onDeleteAccount' | 'onEmailVerified';
993
+ }
994
+ export interface StorageTrigger {
995
+ type: 'storage';
996
+ event: 'beforeUpload' | 'afterUpload' | 'beforeDelete' | 'afterDelete' | 'beforeDownload' | 'onMetadataUpdate';
997
+ }
998
+ export type FunctionTrigger = DbTrigger | HttpTrigger | ScheduleTrigger | AuthTrigger | StorageTrigger;
999
+ export interface FunctionDefinition {
1000
+ trigger: FunctionTrigger;
1001
+ captcha?: boolean;
1002
+ handler: (context: unknown) => Promise<unknown>;
1003
+ }
1004
+ /** Increment when the public plugin contract changes incompatibly. */
1005
+ export declare const CURRENT_PLUGIN_API_VERSION = 1;
1006
+ export interface PluginManifest {
1007
+ /** Human-readable summary shown in CLI and docs. */
1008
+ description?: string;
1009
+ /** Canonical docs page for this plugin. */
1010
+ docsUrl?: string;
1011
+ /** Suggested config object for installation/setup tooling. */
1012
+ configTemplate?: Record<string, unknown>;
1013
+ }
1014
+ /**
1015
+ * A plugin instance returned by a plugin factory function.
1016
+ *
1017
+ * @example
1018
+ * ```typescript
1019
+ * // In edgebase.config.ts:
1020
+ * import { stripePlugin } from '@edge-base/plugin-stripe';
1021
+ * export default defineConfig({
1022
+ * plugins: [ stripePlugin({ secretKey: process.env.STRIPE_SECRET_KEY! }) ],
1023
+ * });
1024
+ * ```
1025
+ */
1026
+ export interface PluginInstance {
1027
+ /** Plugin unique name (e.g. '@edge-base/plugin-stripe'). Used for namespacing. */
1028
+ name: string;
1029
+ /** Public plugin contract version used for compatibility checks. */
1030
+ pluginApiVersion: number;
1031
+ /** Semantic version string (e.g. '1.0.0'). Required for migration support. */
1032
+ version?: string;
1033
+ /** Manifest metadata used by CLI/docs tooling. */
1034
+ manifest?: PluginManifest;
1035
+ /** Developer-supplied plugin config (captured by factory closure). */
1036
+ config: Record<string, unknown>;
1037
+ /** Plugin tables. Keys = table names (plugin.name/ prefix added automatically by CLI). */
1038
+ tables?: Record<string, TableConfig>;
1039
+ /** DB block for plugin tables. Default: 'shared'. */
1040
+ dbBlock?: string;
1041
+ /**
1042
+ * Database provider required by this plugin.
1043
+ * Plugin developers set this based on their data characteristics.
1044
+ * - `'do'` (default): Durable Object + SQLite
1045
+ * - `'neon'`: Requires Neon PostgreSQL and a configured connection string
1046
+ * - `'postgres'`: Requires custom PostgreSQL
1047
+ */
1048
+ provider?: DbProvider;
1049
+ /** Plugin functions. Keys = function names (plugin.name/ prefix added automatically by CLI). */
1050
+ functions?: Record<string, FunctionDefinition>;
1051
+ /** Auth + storage hooks. Event name → handler. */
1052
+ hooks?: Partial<Record<AuthTrigger['event'] | StorageTrigger['event'], (context: unknown) => Promise<unknown>>>;
1053
+ /** Runs once on first deploy with this plugin (version null → version). */
1054
+ onInstall?: (context: unknown) => Promise<void>;
1055
+ /** Version-keyed migration functions. Run in semver order on deploy when version changes. */
1056
+ migrations?: Record<string, (context: unknown) => Promise<void>>;
1057
+ }
1058
+ export declare function defineConfig(config: EdgeBaseConfig): EdgeBaseConfig;
1059
+ /**
1060
+ * Define an App Function.
1061
+ *
1062
+ * @example
1063
+ * // Full definition (DB trigger, schedule, auth hook, or HTTP with explicit trigger)
1064
+ * export default defineFunction({
1065
+ * trigger: { type: 'db', table: 'posts', event: 'insert' },
1066
+ * handler: async ({ data, admin }) => { ... },
1067
+ * });
1068
+ *
1069
+ * // Method-export style (HTTP functions — trigger auto-inferred from export name)
1070
+ * export const GET = defineFunction(async ({ params, admin }) => { ... });
1071
+ * export const POST = defineFunction(async ({ params, auth, admin }) => { ... });
1072
+ */
1073
+ export declare function defineFunction(definition: FunctionDefinition): FunctionDefinition;
1074
+ export declare function defineFunction(handler: (context: unknown) => Promise<unknown>): FunctionDefinition;
1075
+ //# sourceMappingURL=config.d.ts.map