@beechcms/api 0.6.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/assets/dashboard/0auth_mcp.svg +1 -0
  2. package/assets/dashboard/assets/{chart-D_uST30B.js → chart-CYgKoF6U.js} +6 -6
  3. package/assets/dashboard/assets/dist-DHabpZqA.js +140 -0
  4. package/assets/dashboard/assets/index-BZMSpYrl.js +3613 -0
  5. package/assets/dashboard/assets/index-Dex2YZcC.css +1 -0
  6. package/assets/dashboard/assets/json-code-editor-DtZpKDXm.js +12 -0
  7. package/assets/dashboard/assets/pie-chart-recharts-C7WAhcCk.js +1 -0
  8. package/assets/dashboard/assets/richtext-render-x0TT0116.js +258 -0
  9. package/assets/dashboard/assets/timeseries-chart-recharts-B9RquiY6.js +1 -0
  10. package/assets/dashboard/assets/w3c-keyname-BOAvb0qz.js +1 -0
  11. package/assets/dashboard/index.html +4 -2
  12. package/dist/auth/auth.app.d.ts +13 -0
  13. package/dist/auth/index.d.ts +6 -0
  14. package/dist/factory.d.ts +14 -1
  15. package/dist/features/oauth/authorization-request.d.ts +127 -0
  16. package/dist/features/oauth/authorize.d.ts +97 -0
  17. package/dist/features/oauth/consents.d.ts +75 -0
  18. package/dist/features/oauth/constants.d.ts +48 -0
  19. package/dist/features/oauth/index.d.ts +31 -0
  20. package/dist/features/oauth/revoke.d.ts +29 -0
  21. package/dist/features/oauth/token-issuance.d.ts +49 -0
  22. package/dist/features/oauth/token.d.ts +38 -0
  23. package/dist/features/search/constants.d.ts +43 -0
  24. package/dist/features/search/handlers/embed.d.ts +31 -0
  25. package/dist/features/search/handlers/full-text-search.d.ts +28 -0
  26. package/dist/features/search/index.d.ts +20 -0
  27. package/dist/features/search/jobs/semantic-search.hooks.d.ts +21 -0
  28. package/dist/features/search/jobs/semantic-search.worker.d.ts +101 -0
  29. package/dist/features/search/public-search.router.d.ts +15 -0
  30. package/dist/features/search/search.d.ts +14 -6
  31. package/dist/features/search/semantic-search.hooks.d.ts +2 -0
  32. package/dist/features/search/semantic-search.worker.d.ts +21 -0
  33. package/dist/features/search/types.d.ts +100 -0
  34. package/dist/features/search/utils/search-utils.d.ts +56 -0
  35. package/dist/features/seeds/seeds.destructive.d.ts +6 -0
  36. package/dist/features/seeds/seeds.handler.d.ts +14 -0
  37. package/dist/features/seeds/seeds.helpers.d.ts +118 -0
  38. package/dist/features/seeds/seeds.mcp.d.ts +37 -0
  39. package/dist/features/upload/index.d.ts +2 -2
  40. package/dist/index.js +3544 -1368
  41. package/dist/middleware/auth.middleware.d.ts +34 -4
  42. package/dist/middleware/oauth-scope.middleware.d.ts +60 -0
  43. package/dist/middleware/rate-limit.middleware.d.ts +4 -2
  44. package/dist/middleware/repository.middleware.d.ts +9 -1
  45. package/dist/public/api-key-middleware.d.ts +0 -4
  46. package/dist/public/public-add.d.ts +1 -1
  47. package/dist/rate-limit/in-memory-rate-limiter.d.ts +1 -8
  48. package/dist/shared/db/repositories/content.repository.d1.d.ts +4 -2
  49. package/dist/shared/db/repositories/d1-oauth-authorization-code.repository.d.ts +9 -0
  50. package/dist/shared/db/repositories/d1-oauth-client.repository.d.ts +6 -0
  51. package/dist/shared/db/repositories/d1-oauth-consent.repository.d.ts +10 -0
  52. package/dist/shared/db/repositories/d1-oauth-token.repository.d.ts +12 -0
  53. package/dist/shared/db/repositories/d1-search.query.d.ts +54 -0
  54. package/dist/shared/db/repositories/d1-vector.repository.d.ts +11 -0
  55. package/dist/shared/db/repositories/in-memory-seed.repository.d.ts +2 -1
  56. package/dist/shared/db/repositories/seed.repository.d1.d.ts +3 -1
  57. package/dist/shared/db/repositories/time-trap-token.repository.d1.d.ts +7 -0
  58. package/dist/shared/storage/factory.d.ts +84 -4
  59. package/dist/shared/storage/r2-bucket.d.ts +127 -0
  60. package/dist/shared/storage/s3-bucket.d.ts +104 -10
  61. package/dist/shared/storage/upload.d.ts +33 -5
  62. package/dist/shared/utils/dual-key-rate-limiter.d.ts +21 -0
  63. package/dist/shared/utils/media-utils.d.ts +3 -3
  64. package/dist/shared/utils/opaque-token.d.ts +13 -0
  65. package/dist/types.d.ts +49 -1
  66. package/migrations/0000_v040_base.sql +184 -1
  67. package/migrations/_archive/0000_schema_init.sql +257 -0
  68. package/migrations/_archive/0001_init.sql +25 -0
  69. package/migrations/_archive/0002_seed_admin.sql +10 -0
  70. package/migrations/_archive/0003_refresh_tokens.sql +17 -0
  71. package/migrations/_archive/0004_seed_projects.sql +18 -0
  72. package/migrations/_archive/0005_update_projects.sql +56 -0
  73. package/migrations/_archive/0006_add_slug_and_unique_index.sql +7 -0
  74. package/migrations/_archive/0007_cleanup_test_data.sql +4 -0
  75. package/migrations/_archive/0008_seed_content_entries.sql +104 -0
  76. package/migrations/_archive/0009_public_idempotency.sql +11 -0
  77. package/migrations/_archive/0010_seed_real_data.sql +31 -0
  78. package/migrations/_archive/0010_testsite_data.sql +31 -0
  79. package/migrations/_archive/0011_analytics.sql +10 -0
  80. package/migrations/_archive/0012_seed_analytics.sql +17 -0
  81. package/migrations/_archive/0013_system_stats.sql +8 -0
  82. package/migrations/_archive/0014_activity_logs.sql +15 -0
  83. package/migrations/_archive/0015_notifications.sql +12 -0
  84. package/migrations/_archive/0016_fts5_global_search.sql +81 -0
  85. package/migrations/_archive/0017_seed_clienti.sql +90 -0
  86. package/migrations/_archive/0018_draft_data.sql +3 -0
  87. package/migrations/_archive/0019_drop_fts_triggers.sql +7 -0
  88. package/migrations/_archive/0020_composite_indexes.sql +15 -0
  89. package/migrations/_archive/0021_media_objects.sql +15 -0
  90. package/migrations/_archive/0022_analytics_seed.sql +23 -0
  91. package/migrations/_archive/0023_user_profile.sql +4 -0
  92. package/migrations/_archive/0024_activity_user_name.sql +3 -0
  93. package/migrations/_archive/0025_password_reset_tokens.sql +11 -0
  94. package/migrations/_archive/0026_remove_dev_admin.sql +4 -0
  95. package/migrations/_archive/0027_v040_doctype_architecture.sql +51 -0
  96. package/migrations/_archive/0029_fts_all_text_branches.sql +281 -0
  97. package/migrations/_archive/0031_site_settings.sql +9 -0
  98. package/migrations/_archive/0032_seeds.sql +28 -0
  99. package/migrations/_archive/0033_dashboard_layouts.sql +14 -0
  100. package/migrations/_archive/0034_kanban_foundation.sql +24 -0
  101. package/migrations/_archive/0035_setup_lock.sql +10 -0
  102. package/migrations/_archive/0036_notification_type_success.sql +23 -0
  103. package/migrations/_archive/0037_time_trap_tokens.sql +13 -0
  104. package/migrations/_archive/0038_oauth_authorization.sql +95 -0
  105. package/migrations/_archive/0039_oauth_client_beech_mcp.sql +20 -0
  106. package/package.json +3 -5
  107. package/assets/dashboard/assets/index-B58kBLrh.css +0 -1
  108. package/assets/dashboard/assets/index-DjniT5Z9.js +0 -745
  109. package/assets/dashboard/assets/pie-chart-recharts-CnVomiSi.js +0 -1
  110. package/assets/dashboard/assets/timeseries-chart-recharts-CTqi8qnV.js +0 -1
  111. /package/migrations/{0029_automations.sql → _archive/0029_automations.sql} +0 -0
@@ -1,11 +1,41 @@
1
1
  import type { Context, Next } from 'hono';
2
2
  import type { Env, Variables } from '../types';
3
3
  /**
4
- * JWT authentication middleware. Reads the Bearer token from the Authorization
5
- * header and delegates verification to the ITokenService injected in context.
6
- * Returns 401 on any missing or invalid token; never exposes failure details.
4
+ * Configuration options for {@link authMiddleware}.
7
5
  */
8
- export declare function authMiddleware(): (c: Context<{
6
+ export interface AuthMiddlewareOptions {
7
+ /**
8
+ * Whether to accept opaque OAuth 2.1 access tokens in addition to admin JWTs.
9
+ *
10
+ * @defaultValue false
11
+ *
12
+ * @remarks
13
+ * When `false` (the default), only admin JWTs are accepted. This ensures that internal endpoints
14
+ * — developer custom routes (`factory.ts`), OAuth consent APIs (`features/oauth/index.ts`), and the
15
+ * search router (`features/search/search.ts`) — stay JWT-only.
16
+ *
17
+ * When `true`, accepts both admin JWTs and opaque OAuth access tokens looked up via `oauthTokenRepository`.
18
+ * Only `apiProtected` sets this, and only in combination with `oauthScopeMiddleware()`.
19
+ */
20
+ acceptOAuth?: boolean;
21
+ }
22
+ /**
23
+ * Authentication middleware for API routes.
24
+ *
25
+ * @remarks
26
+ * Extracts the Bearer token from the `Authorization` header and validates credentials:
27
+ * - When {@link AuthMiddlewareOptions.acceptOAuth} is `true` and the token is a 64-character hex string,
28
+ * looks up the active access token record via `oauthTokenRepository` and hydrates user claims from `userRepository`.
29
+ * Populates `jwtPayload` and sets `oauthGrant` in the Hono context.
30
+ * - Otherwise, delegates verification to the `ITokenService` injected in context, setting `jwtPayload` and
31
+ * clearing `oauthGrant` (`null`).
32
+ * - Emits HTTP 401 Unauthorized for missing, malformed, expired, or invalid tokens without exposing failure details.
33
+ *
34
+ * @param options - Configuration options controlling token acceptance.
35
+ * @returns A Hono middleware handler.
36
+ * @throws {HTTPException} 401 Unauthorized when authentication fails.
37
+ */
38
+ export declare function authMiddleware(options?: AuthMiddlewareOptions): (c: Context<{
9
39
  Bindings: Env;
10
40
  Variables: Variables;
11
41
  }>, next: Next) => Promise<Response | void>;
@@ -0,0 +1,60 @@
1
+ import type { Context, Next } from 'hono';
2
+ import type { OAuthScope } from '@beechcms/core';
3
+ import type { Env, Variables } from '../types';
4
+ /**
5
+ * Route mapping definition for OAuth scope enforcement.
6
+ */
7
+ export interface OAuthScopeRoute {
8
+ /** HTTP method accepted for the route. */
9
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
10
+ /** Regular expression tested against the full request path (`c.req.path`). */
11
+ pattern: RegExp;
12
+ /** Scope required to access this endpoint. */
13
+ scope: OAuthScope;
14
+ /** Informational identifier of the MCP tool(s) backed by this endpoint. */
15
+ tools: string;
16
+ }
17
+ /**
18
+ * Closed allowlist of API routes accessible via OAuth 2.1 access tokens and their required scopes.
19
+ *
20
+ * @remarks
21
+ * Enforcement is fail-closed: any `/api/*` path not explicitly registered in this table
22
+ * is denied for OAuth-authenticated requests. Centralized enforcement ensures that forgetting
23
+ * to register a route refuses access rather than accidentally permitting it.
24
+ *
25
+ * Route mappings correspond directly to MCP tools (`beech_schema_export` and `beech_schema_validate`
26
+ * share `GET /api/schema`).
27
+ *
28
+ * `POST /api/seeds/:slug/mcp-plan` is intentionally mapped to `schema:read` because it performs
29
+ * a dry-run calculation (DDL and safety classification) without mutating state. Only `mcp-apply`
30
+ * requires `schema:write`.
31
+ *
32
+ * Patterns match against `c.req.path`, which includes the `/api` prefix.
33
+ */
34
+ export declare const OAUTH_SCOPE_ROUTES: readonly OAuthScopeRoute[];
35
+ /**
36
+ * Resolves the required OAuth scope for a given route.
37
+ *
38
+ * @param method - The HTTP request method (e.g. `'GET'`, `'POST'`).
39
+ * @param path - The full request pathname (e.g. `c.req.path`).
40
+ * @returns The required {@link OAuthScope}, or `null` if the route is not OAuth-reachable.
41
+ */
42
+ export declare function resolveRequiredScope(method: string, path: string): OAuthScope | null;
43
+ /**
44
+ * Middleware enforcing OAuth 2.1 scope boundaries on protected API routes.
45
+ *
46
+ * @remarks
47
+ * This middleware operates in a fail-closed manner:
48
+ * - Must be registered immediately after `authMiddleware({ acceptOAuth: true })`, which populates `oauthGrant`.
49
+ * - Requests authenticated via the admin JWT carry `oauthGrant === null` and pass through untouched.
50
+ * - For OAuth requests, verifies that the route is allowlisted in {@link OAUTH_SCOPE_ROUTES} and that the token
51
+ * grant includes the necessary {@link OAuthScope}.
52
+ * - Returns HTTP 403 (`insufficient_scope`) on failure rather than 401, allowing MCP clients to distinguish
53
+ * insufficient privileges from expired or invalid credentials.
54
+ *
55
+ * @returns A Hono middleware handler enforcing scope boundaries.
56
+ */
57
+ export declare function oauthScopeMiddleware(): (c: Context<{
58
+ Bindings: Env;
59
+ Variables: Variables;
60
+ }>, next: Next) => Promise<Response | void>;
@@ -1,9 +1,11 @@
1
1
  import type { IRateLimiter } from '@beechcms/core';
2
- import type { AppEnv } from '../types';
3
- export type RateLimiterName = 'login' | 'tokenRefresh' | 'forgotPassword' | 'resetPassword' | 'publicApiRead' | 'publicApiWrite';
2
+ import type { AppEnv, Env } from '../types';
3
+ export type RateLimiterName = 'login' | 'loginAccount' | 'tokenRefresh' | 'forgotPassword' | 'forgotPasswordAccount' | 'resetPassword' | 'publicApiRead' | 'publicApiWrite' | 'oauthToken' | 'oauthTokenAccount';
4
4
  export interface IRateLimiterRegistry {
5
5
  getLimiter(name: RateLimiterName): IRateLimiter;
6
+ resetAll?(): void;
6
7
  }
8
+ export declare function buildDefaultRegistry(_env?: Env): IRateLimiterRegistry;
7
9
  export declare const rateLimiterMiddleware: (overrides?: {
8
10
  registry?: IRateLimiterRegistry;
9
11
  }) => import("hono").MiddlewareHandler<AppEnv, string, {}, Response>;
@@ -1,4 +1,4 @@
1
- import type { ContentRepository, IdempotencyRepository, MediaRepository, SystemStatsRepository, IUserRepository, ISessionRepository, IPasswordResetTokenRepository, IActivityLogRepository, INotificationRepository, IWidgetRepository, ISearchRepository, IAnalyticsRepository, IContentScanRepository, IClock, IIdGenerator, IAutomationRunner, IAutomationRepository, IScheduler, ISiteSettingsRepository, IDemoDataRepository, ISeedLayoutRepository, ISeedRepository, ISchemaMutator, IDashboardLayoutRepository, BeechHooks, IKanbanPositionRepository } from '@beechcms/core';
1
+ import type { ContentRepository, IdempotencyRepository, MediaRepository, SystemStatsRepository, IUserRepository, ISessionRepository, IPasswordResetTokenRepository, IActivityLogRepository, INotificationRepository, IWidgetRepository, ISearchRepository, IAnalyticsRepository, IContentScanRepository, IClock, IIdGenerator, IAutomationRunner, IAutomationRepository, IScheduler, ISiteSettingsRepository, IDemoDataRepository, ISeedLayoutRepository, ISeedRepository, ISchemaMutator, IDashboardLayoutRepository, BeechHooks, IKanbanPositionRepository, IAntivirusProvider, ITimeTrapTokenRepository, IPrivacyService, IOAuthClientRepository, IOAuthAuthorizationCodeRepository, IOAuthTokenRepository, IOAuthConsentRepository, IRoleGuard } from '@beechcms/core';
2
2
  import type { Env, Variables } from '../types';
3
3
  interface RepositoryOverrides {
4
4
  repository?: ContentRepository;
@@ -26,7 +26,15 @@ interface RepositoryOverrides {
26
26
  schemaMutator?: ISchemaMutator;
27
27
  dashboardLayoutRepository?: IDashboardLayoutRepository;
28
28
  kanbanPositionRepository?: IKanbanPositionRepository;
29
+ antivirusProvider?: IAntivirusProvider;
30
+ timeTrapTokenRepository?: ITimeTrapTokenRepository;
31
+ oauthClientRepository?: IOAuthClientRepository;
32
+ oauthAuthorizationCodeRepository?: IOAuthAuthorizationCodeRepository;
33
+ oauthTokenRepository?: IOAuthTokenRepository;
34
+ oauthConsentRepository?: IOAuthConsentRepository;
35
+ roleGuard?: IRoleGuard;
29
36
  hooks?: BeechHooks;
37
+ privacyService?: IPrivacyService;
30
38
  }
31
39
  export declare const repositoryMiddleware: (overrides?: RepositoryOverrides) => import("hono").MiddlewareHandler<{
32
40
  Bindings: Env;
@@ -1,6 +1,2 @@
1
1
  import type { Context, Next } from 'hono';
2
- /**
3
- * API key auth middleware for Public API routes.
4
- * Uses X-API-Key header only.
5
- */
6
2
  export declare function apiKeyMiddleware(): (c: Context, next: Next) => Promise<Response | void>;
@@ -1,3 +1,3 @@
1
1
  import type { Context } from 'hono';
2
- import { AppEnv } from '../types';
2
+ import type { AppEnv } from '../types.js';
3
3
  export declare function publicAddHandler(context: Context<AppEnv>): Promise<Response>;
@@ -1,8 +1 @@
1
- import type { IRateLimiter, RateLimitResult } from '@beechcms/core';
2
- export declare class InMemoryRateLimiter implements IRateLimiter {
3
- private readonly maxAllowedHits;
4
- private readonly windowSeconds;
5
- private readonly hitCounts;
6
- constructor(maxAllowedHits: number, windowSeconds?: number);
7
- checkLimit(key: string): Promise<RateLimitResult>;
8
- }
1
+ export { TokenBucketRateLimiter as InMemoryRateLimiter } from '@beechcms/core';
@@ -1,4 +1,4 @@
1
- import { ContentRepository, type BulkFieldUpdate, type BatchWrite, type DraftSummary, type Seed, type SelectOptions, type RepositoryOptions, type BeechHooks, type IPrivacyService } from '@beechcms/core';
1
+ import { ContentRepository, type BulkFieldUpdate, type BatchWrite, type DraftSummary, type Seed, type SelectOptions, type RepositoryOptions, type BeechHooks, type IPrivacyService, type IQueueService } from '@beechcms/core';
2
2
  import { BaseD1Repository } from './base.repository.d1';
3
3
  export declare function prepareBlindIndexOptions(seed: Seed, options: SelectOptions, privacyService: IPrivacyService): Promise<SelectOptions>;
4
4
  /**
@@ -16,7 +16,9 @@ export declare function prepareBlindIndexOptions(seed: Seed, options: SelectOpti
16
16
  export declare class D1ContentRepository extends BaseD1Repository implements ContentRepository {
17
17
  private readonly hooks?;
18
18
  private readonly privacyService?;
19
- constructor(database: D1Database, hooks?: BeechHooks | undefined, privacyService?: IPrivacyService | undefined);
19
+ private queue?;
20
+ constructor(database: D1Database, hooks?: BeechHooks | undefined, privacyService?: IPrivacyService | undefined, queue?: IQueueService);
21
+ setQueue(queue: IQueueService): void;
20
22
  /** Assembles the {@link HookContext} passed to every lifecycle hook invocation. */
21
23
  private hookCtx;
22
24
  private serializeAndProtect;
@@ -0,0 +1,9 @@
1
+ import type { IOAuthAuthorizationCodeRepository, NewAuthorizationCode, AuthorizationCodeRecord, IClock } from '@beechcms/core';
2
+ export declare class D1OAuthAuthorizationCodeRepository implements IOAuthAuthorizationCodeRepository {
3
+ private readonly db;
4
+ private readonly clock;
5
+ constructor(db: D1Database, clock: IClock);
6
+ save(record: NewAuthorizationCode): Promise<void>;
7
+ findByHash(codeHash: string, nowTimestamp: number): Promise<AuthorizationCodeRecord | null>;
8
+ consumeByHash(codeHash: string, nowTimestamp: number): Promise<boolean>;
9
+ }
@@ -0,0 +1,6 @@
1
+ import type { IOAuthClientRepository, OAuthClientRecord } from '@beechcms/core';
2
+ export declare class D1OAuthClientRepository implements IOAuthClientRepository {
3
+ private readonly db;
4
+ constructor(db: D1Database);
5
+ findActiveById(clientId: string): Promise<OAuthClientRecord | null>;
6
+ }
@@ -0,0 +1,10 @@
1
+ import type { IOAuthConsentRepository, ConsentRecord, OAuthScope, IIdGenerator } from '@beechcms/core';
2
+ export declare class D1OAuthConsentRepository implements IOAuthConsentRepository {
3
+ private readonly db;
4
+ private readonly idGenerator;
5
+ constructor(db: D1Database, idGenerator: IIdGenerator);
6
+ findActive(clientId: string, userId: string): Promise<ConsentRecord | null>;
7
+ grant(id: string, clientId: string, userId: string, scopes: readonly OAuthScope[], nowTimestamp: number): Promise<void>;
8
+ revoke(clientId: string, userId: string, nowTimestamp: number): Promise<boolean>;
9
+ listForUser(userId: string): Promise<ConsentRecord[]>;
10
+ }
@@ -0,0 +1,12 @@
1
+ import type { IOAuthTokenRepository, NewOAuthToken, OAuthTokenRecord, OAuthTokenType, AuthorizedClientSummary, IClock } from '@beechcms/core';
2
+ export declare class D1OAuthTokenRepository implements IOAuthTokenRepository {
3
+ private readonly db;
4
+ private readonly clock;
5
+ constructor(db: D1Database, clock: IClock);
6
+ save(record: NewOAuthToken): Promise<void>;
7
+ findActiveByHash(tokenHash: string, tokenType: OAuthTokenType, nowTimestamp: number): Promise<OAuthTokenRecord | null>;
8
+ revokeByHash(tokenHash: string, nowTimestamp: number): Promise<boolean>;
9
+ revokeByAuthorizationCode(authorizationCodeHash: string, nowTimestamp: number): Promise<number>;
10
+ revokeAllForClientAndUser(clientId: string, userId: string, nowTimestamp: number): Promise<number>;
11
+ listAuthorizedClientsForUser(userId: string, nowTimestamp: number): Promise<AuthorizedClientSummary[]>;
12
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @module shared/db/repositories/d1-search.query
3
+ * Pure SQL query builder utilities for SQLite/D1 FTS5 full-text search.
4
+ */
5
+ import type { Seed, SearchResultRow } from '@beechcms/core';
6
+ /**
7
+ * Raw database row returned by the UNION ALL FTS query.
8
+ * Field names use snake_case to match SQLite column aliases verbatim.
9
+ */
10
+ export interface FtsRow {
11
+ entry_id: string;
12
+ schema_slug: string;
13
+ slug: string | null;
14
+ status: string;
15
+ title: string | null;
16
+ excerpt: string;
17
+ rank: number;
18
+ }
19
+ /**
20
+ * Validated query parameters passed to {@link buildFtsQuery}.
21
+ */
22
+ export interface FtsQueryParams {
23
+ queryText: string;
24
+ schemaSlug: string | null;
25
+ statusFilter: string | null;
26
+ pageSize: number;
27
+ cursor: string | null;
28
+ }
29
+ /**
30
+ * Result of {@link buildFtsQuery}: parameterised SQL for both the
31
+ * paginated data fetch and the total-count query.
32
+ */
33
+ export interface FtsQueryResult {
34
+ sql: string;
35
+ binds: unknown[];
36
+ countSql: string;
37
+ countBinds: unknown[];
38
+ }
39
+ /**
40
+ * Decodes a pagination cursor (`${rank}:${entryId}`).
41
+ */
42
+ export declare function decodeCursor(cursor: string): {
43
+ rank: number;
44
+ entryId: string;
45
+ } | null;
46
+ /**
47
+ * Builds a UNION ALL query across all per-seed FTS tables that satisfy
48
+ * {@link isSeedFullTextSearchable}.
49
+ */
50
+ export declare function buildFtsQuery(params: FtsQueryParams, seeds: Seed[]): FtsQueryResult;
51
+ /**
52
+ * Maps raw FtsRow database representation to core SearchResultRow.
53
+ */
54
+ export declare function mapFtsRowToResultRow(row: FtsRow): SearchResultRow;
@@ -0,0 +1,11 @@
1
+ import type { Seed, IVectorRepository } from '@beechcms/core';
2
+ export declare class D1VectorRepository implements IVectorRepository {
3
+ private readonly db;
4
+ constructor(db: D1Database);
5
+ saveVector(seed: Seed, entryId: string, vector: Float32Array): Promise<void>;
6
+ deleteVector(seed: Seed, entryId: string): Promise<void>;
7
+ getAllVectors(seed: Seed): Promise<{
8
+ entryId: string;
9
+ vector: Float32Array;
10
+ }[]>;
11
+ }
@@ -1,4 +1,4 @@
1
- import type { ISeedRepository, Seed, SeedRecord } from '@beechcms/core';
1
+ import type { ISeedRepository, Seed, SeedRecord, SeedApplyInput, SeedApplyResult } from '@beechcms/core';
2
2
  /**
3
3
  * In-memory ISeedRepository for testing and for the config.seeds back-compat shim.
4
4
  * Returns a fixed seed list with a static version token.
@@ -15,4 +15,5 @@ export declare class InMemorySeedRepository implements ISeedRepository {
15
15
  hardDelete(_slug: string): Promise<void>;
16
16
  getRegistryVersion(): Promise<number>;
17
17
  bumpRegistryVersion(): Promise<number>;
18
+ applyAtomic(input: SeedApplyInput): Promise<SeedApplyResult>;
18
19
  }
@@ -1,6 +1,7 @@
1
- import type { ISeedRepository, SeedRecord, Seed } from '@beechcms/core';
1
+ import type { ISeedRepository, SeedRecord, Seed, SeedApplyInput, SeedApplyResult } from '@beechcms/core';
2
2
  export declare class D1SeedRepository implements ISeedRepository {
3
3
  private readonly db;
4
+ private static readonly UPSERT_SEED_SQL;
4
5
  constructor(db: D1Database);
5
6
  listActive(): Promise<Seed[]>;
6
7
  listAll(): Promise<SeedRecord[]>;
@@ -10,4 +11,5 @@ export declare class D1SeedRepository implements ISeedRepository {
10
11
  hardDelete(slug: string): Promise<void>;
11
12
  getRegistryVersion(): Promise<number>;
12
13
  bumpRegistryVersion(): Promise<number>;
14
+ applyAtomic(input: SeedApplyInput): Promise<SeedApplyResult>;
13
15
  }
@@ -0,0 +1,7 @@
1
+ import type { ITimeTrapTokenRepository } from '@beechcms/core';
2
+ import { BaseD1Repository } from './base.repository.d1.js';
3
+ export declare class D1TimeTrapTokenRepository extends BaseD1Repository implements ITimeTrapTokenRepository {
4
+ isTokenUsed(tokenHash: string): Promise<boolean>;
5
+ markTokenUsed(tokenHash: string, usedAt: number, expiresAt: number): Promise<void>;
6
+ cleanup(nowSeconds: number): Promise<void>;
7
+ }
@@ -1,8 +1,88 @@
1
- import { BeechBucket } from '@beechcms/core';
1
+ import { BeechBucket, PutBucketOptions, GetBucketResult, PresignOptions } from '@beechcms/core';
2
2
  import { Env } from '../../types';
3
+ export { R2BucketAdapter } from './r2-bucket';
3
4
  /**
4
- * Single storage path: S3-compatible HTTP API.
5
- * Prod → Cloudflare R2 with S3 API token.
6
- * Dev → MinIO container (or R2 staging bucket).
5
+ * Fallback implementation of {@link BeechBucket} used when storage is not configured.
6
+ *
7
+ * @remarks
8
+ * Every bucket operation unconditionally throws an HTTP 503 Service Unavailable exception
9
+ * with error code `storage_not_configured`, providing diagnostic instructions on how to
10
+ * configure Cloudflare R2 credentials or MinIO for local development.
11
+ */
12
+ export declare class NullBucket implements BeechBucket {
13
+ /**
14
+ * Creates an HTTP 503 exception explaining that storage is not configured.
15
+ */
16
+ private createException;
17
+ /**
18
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
19
+ */
20
+ put(_key?: string, _body?: ArrayBuffer | Uint8Array | ReadableStream, _options?: PutBucketOptions): Promise<void>;
21
+ /**
22
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
23
+ */
24
+ get(_key?: string): Promise<GetBucketResult | null>;
25
+ /**
26
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
27
+ */
28
+ delete(_key?: string): Promise<void>;
29
+ /**
30
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
31
+ */
32
+ head(_key?: string): Promise<{
33
+ size: number;
34
+ contentType?: string;
35
+ metadata?: Record<string, string>;
36
+ } | null>;
37
+ /**
38
+ * @throws {HTTPException} Always throws 503 Service Unavailable.
39
+ */
40
+ getUrl(_key?: string): string;
41
+ /**
42
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
43
+ */
44
+ getTotalSize(): Promise<number>;
45
+ /**
46
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
47
+ */
48
+ list(_options?: {
49
+ prefix?: string;
50
+ limit?: number;
51
+ cursor?: string;
52
+ }): Promise<{
53
+ objects: Array<{
54
+ key: string;
55
+ size: number;
56
+ }>;
57
+ cursor?: string;
58
+ }>;
59
+ /**
60
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
61
+ */
62
+ presignPut(_key?: string, _options?: PresignOptions): Promise<string>;
63
+ /**
64
+ * @throws {HTTPException} Always returns a rejected promise with 503 Service Unavailable.
65
+ */
66
+ presignGet(_key?: string, _options?: PresignOptions): Promise<string>;
67
+ }
68
+ /**
69
+ * Resets the warning flags for storage provider configuration.
70
+ *
71
+ * @remarks
72
+ * Useful for test isolation to ensure diagnostic warnings are verified reliably across test suites.
73
+ */
74
+ export declare function resetNullBucketWarning(): void;
75
+ /**
76
+ * Creates and configures the appropriate {@link BeechBucket} storage provider based on environment settings.
77
+ *
78
+ * @remarks
79
+ * Selection precedence:
80
+ * 1. **S3Bucket**: Used when all R2 S3 credentials (`R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_ENDPOINT`, `R2_BUCKET_NAME`) are set.
81
+ * 2. **R2BucketAdapter**: Used when the native Cloudflare Worker `MEDIA_BUCKET` binding is detected (with a notice that presigned uploads require S3 credentials).
82
+ * 3. **NullBucket**: Fallback when no storage is configured. Logs a warning in `development` mode.
83
+ *
84
+ * @param env - Application environment configuration.
85
+ * @param baseUrl - Base URL of the API server used for constructing fallback media URLs.
86
+ * @returns An initialized {@link BeechBucket} provider instance.
7
87
  */
8
88
  export declare function createBucketProvider(env: Env, baseUrl: string): BeechBucket;
@@ -0,0 +1,127 @@
1
+ import { BeechBucket, PutBucketOptions, GetBucketResult, PresignOptions } from '@beechcms/core';
2
+ /**
3
+ * Implementation of {@link BeechBucket} using Cloudflare Workers' native `R2Bucket` binding.
4
+ *
5
+ * @remarks
6
+ * Used when running inside Cloudflare Workers with an `r2_buckets` binding (e.g. `MEDIA_BUCKET`).
7
+ * Provides direct access to Cloudflare R2 storage without needing external S3 SDK requests.
8
+ *
9
+ * Note: Generating presigned URLs (`presignPut`, `presignGet`) is not supported by native `R2Bucket`
10
+ * bindings and will throw HTTP 501 (`presigned_urls_require_s3_credentials`). Direct client uploads
11
+ * with presigned URLs require S3 credentials configured via {@link S3Bucket}.
12
+ */
13
+ export declare class R2BucketAdapter implements BeechBucket {
14
+ /** Native Cloudflare Workers R2 bucket binding instance. */
15
+ private bucket;
16
+ /** Base URL of the API server used for fallback asset access URLs. */
17
+ private baseUrl;
18
+ /** Optional custom CDN base URL prepended to media assets when served via CDN. */
19
+ private cdnUrl;
20
+ /**
21
+ * Initializes a new instance of {@link R2BucketAdapter}.
22
+ *
23
+ * @param bucket - The native Cloudflare Workers `R2Bucket` binding.
24
+ * @param baseUrl - Base URL of the API server used to construct fallback media URLs.
25
+ * @param cdnUrl - Optional custom CDN base URL.
26
+ */
27
+ constructor(bucket: R2Bucket, baseUrl: string, cdnUrl?: string);
28
+ /**
29
+ * Uploads an object directly to the native R2 bucket binding.
30
+ *
31
+ * @param key - The unique object key (path) within the bucket.
32
+ * @param body - The object content as an `ArrayBuffer`, `Uint8Array`, or `ReadableStream`.
33
+ * @param options - Optional upload settings such as `contentType` (stored in `httpMetadata`) and custom `metadata`.
34
+ * @throws {Error} If the R2 upload operation fails.
35
+ * @returns A promise that resolves when the upload completes.
36
+ */
37
+ put(key: string, body: ArrayBuffer | Uint8Array | ReadableStream, options?: PutBucketOptions): Promise<void>;
38
+ /**
39
+ * Retrieves an object from the native R2 bucket.
40
+ *
41
+ * @param key - The unique object key (path) within the bucket.
42
+ * @returns A promise resolving to a {@link GetBucketResult} containing the object's body, metadata, and content type, or `null` if the object does not exist.
43
+ * @throws {Error} If reading from the R2 binding fails.
44
+ */
45
+ get(key: string): Promise<GetBucketResult | null>;
46
+ /**
47
+ * Deletes an object from the native R2 bucket.
48
+ *
49
+ * @param key - The unique object key (path) to delete.
50
+ * @throws {Error} If the deletion operation fails.
51
+ * @returns A promise that resolves when the object is deleted.
52
+ */
53
+ delete(key: string): Promise<void>;
54
+ /**
55
+ * Retrieves object metadata from the native R2 bucket without reading the body.
56
+ *
57
+ * @param key - The unique object key (path) within the bucket.
58
+ * @returns A promise resolving to the object's size, content type, and custom metadata, or `null` if the object is not found.
59
+ * @throws {Error} If the head request fails.
60
+ */
61
+ head(key: string): Promise<{
62
+ size: number;
63
+ contentType?: string;
64
+ metadata?: Record<string, string>;
65
+ } | null>;
66
+ /**
67
+ * Constructs the public access URL for an object key.
68
+ *
69
+ * @remarks
70
+ * If a CDN URL is configured, returns the CDN-prefixed path (`${cdnUrl}/${encodedKey}`).
71
+ * Otherwise, returns the API fallback path (`${baseUrl}/api/media/${encodedKey}`).
72
+ *
73
+ * @param key - The storage object key.
74
+ * @returns The fully qualified URL to access the media object.
75
+ */
76
+ getUrl(key: string): string;
77
+ /**
78
+ * Computes the cumulative size in bytes of all objects stored in the bucket.
79
+ *
80
+ * @remarks
81
+ * Iterates through all bucket objects using paginated R2 `list` requests.
82
+ *
83
+ * @returns A promise resolving to the total storage size in bytes.
84
+ */
85
+ getTotalSize(): Promise<number>;
86
+ /**
87
+ * Lists objects in the R2 bucket matching an optional prefix, with pagination support.
88
+ *
89
+ * @param options - Optional listing parameters including `prefix`, `limit`, and pagination `cursor`.
90
+ * @returns A promise resolving to an array of object summaries (key and size) and an optional continuation cursor.
91
+ */
92
+ list(options?: {
93
+ prefix?: string;
94
+ limit?: number;
95
+ cursor?: string;
96
+ }): Promise<{
97
+ objects: Array<{
98
+ key: string;
99
+ size: number;
100
+ }>;
101
+ cursor?: string;
102
+ }>;
103
+ /**
104
+ * Generates a presigned URL for direct upload via HTTP PUT.
105
+ *
106
+ * @remarks
107
+ * Native Cloudflare `R2Bucket` bindings cannot generate cryptographic SigV4 signatures.
108
+ * This method throws an HTTP 501 exception instructing the user to configure S3 API credentials.
109
+ *
110
+ * @param _key - The target object key (path).
111
+ * @param _options - Presigning options.
112
+ * @throws {HTTPException} Always throws 501 Not Implemented because native bindings lack SigV4 signing capability.
113
+ */
114
+ presignPut(_key: string, _options: PresignOptions): Promise<string>;
115
+ /**
116
+ * Generates a presigned URL for direct download via HTTP GET.
117
+ *
118
+ * @remarks
119
+ * Native Cloudflare `R2Bucket` bindings cannot generate cryptographic SigV4 signatures.
120
+ * This method throws an HTTP 501 exception instructing the user to configure S3 API credentials.
121
+ *
122
+ * @param _key - The target object key (path).
123
+ * @param _options - Presigning options.
124
+ * @throws {HTTPException} Always throws 501 Not Implemented because native bindings lack SigV4 signing capability.
125
+ */
126
+ presignGet(_key: string, _options: PresignOptions): Promise<string>;
127
+ }