@rebasepro/server 0.10.0 → 0.10.1-canary.14e53ae

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,55 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The Rebase runtime entrypoint.
4
+ *
5
+ * Runs a built project bundle. This is what the official `rebasepro/server`
6
+ * container image executes, and what a self-hosted deployment runs directly:
7
+ *
8
+ * rebase-server ./dist-bundle
9
+ * REBASE_BUNDLE=/bundle rebase-server
10
+ *
11
+ * The bundle is the project; this process is the engine. Keeping them separate
12
+ * is what allows the engine to be upgraded — a security patch, a performance
13
+ * fix — without rebuilding anyone's application.
14
+ */
15
+ import { runFromBundle } from "../dist/index.es.js";
16
+
17
+ const args = process.argv.slice(2);
18
+
19
+ if (args[0] === "--help" || args[0] === "-h") {
20
+ console.log(`
21
+ rebase-server — run a built Rebase project bundle
22
+
23
+ Usage:
24
+ rebase-server [bundle-dir]
25
+
26
+ Arguments:
27
+ bundle-dir Path to the bundle. Defaults to $REBASE_BUNDLE, then ./dist-bundle
28
+
29
+ Key environment variables:
30
+ DATABASE_URL Connection string for the default database (required)
31
+ JWT_SECRET Signing secret, >=32 chars (required in production)
32
+ PORT Port to bind (default 3001)
33
+ CORS_ORIGINS Comma-separated allowed origins (required in production)
34
+ REBASE_METRICS "true" to expose Prometheus metrics at /metrics
35
+ REBASE_MIGRATE_ON_BOOT none | ensure (collection tables: run 'rebase db push')
36
+
37
+ Additional databases and buckets are configured by suffixing the variable with
38
+ the source key, e.g. DATABASE_URL__ANALYTICS or S3_BUCKET__MEDIA.
39
+
40
+ Docs: https://rebase.pro/docs/deployment/self-hosting/
41
+ `.trim());
42
+ process.exit(0);
43
+ }
44
+
45
+ if (args[0] === "--version" || args[0] === "-v") {
46
+ const { readFileSync } = await import("node:fs");
47
+ const { fileURLToPath } = await import("node:url");
48
+ const { dirname, join } = await import("node:path");
49
+ const here = dirname(fileURLToPath(import.meta.url));
50
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
51
+ console.log(pkg.version);
52
+ process.exit(0);
53
+ }
54
+
55
+ await runFromBundle({ bundleDir: args[0] });
@@ -0,0 +1,36 @@
1
+ import { Hono } from "hono";
2
+ import { type CollectionConfig } from "@rebasepro/types";
3
+ import type { HonoEnv } from "./types";
4
+ /**
5
+ * The project contract endpoint.
6
+ *
7
+ * This is what makes a repository able to build against a project it does not
8
+ * contain. Without it, a typed client can only be generated from local
9
+ * collection *source*, which means every frontend must live in the same
10
+ * repository as the backend. Serving the contract turns that around: an app
11
+ * asks the project what its shape is, so a web app, a second web app and a
12
+ * mobile app can each live wherever they like and none of them needs to know
13
+ * about the others.
14
+ *
15
+ * Admin-gated. Collection definitions describe every table, column and relation
16
+ * in the project, including ones no security rule would ever expose — that is a
17
+ * map of the database, not public API documentation.
18
+ */
19
+ export interface ContractRoutesConfig {
20
+ collectionRegistry: {
21
+ getRawCollections(): CollectionConfig[];
22
+ };
23
+ /**
24
+ * The schema version recorded at build time.
25
+ *
26
+ * Preferred over recomputing, so that what a client is told matches exactly
27
+ * what the bundle claims. It is recomputed only when a bundle did not record
28
+ * one — a `baas`-mode project derives its collections from the live database
29
+ * at boot, so there was nothing to hash when it was built.
30
+ */
31
+ schemaVersion?: string;
32
+ mode: "cms" | "baas";
33
+ /** Runtime package version, surfaced so a client can report what it built against. */
34
+ runtimeVersion?: string;
35
+ }
36
+ export declare function createContractRoutes(config: ContractRoutesConfig): Hono<HonoEnv>;
@@ -1,6 +1,7 @@
1
1
  import { Hono } from "hono";
2
2
  import { AuthAdapter, DataDriver, CollectionConfig } from "@rebasepro/types";
3
3
  import { HonoEnv } from "../types";
4
+ import { type ListLimitOptions } from "./query-parser";
4
5
  /**
5
6
  * Lightweight REST API generator that leverages existing Rebase DataDriver.
6
7
  * Supports `include` query parameter for eager-loading relations via Drizzle.
@@ -12,8 +13,15 @@ export declare class RestApiGenerator {
12
13
  private router;
13
14
  private driver;
14
15
  private maxBulkRows;
16
+ private listLimits;
15
17
  private authAdapter?;
16
- constructor(collections: CollectionConfig[], driver: DataDriver, authAdapter?: AuthAdapter, maxBulkRows?: number);
18
+ constructor(collections: CollectionConfig[], driver: DataDriver, authAdapter?: AuthAdapter, maxBulkRows?: number, listLimits?: ListLimitOptions);
19
+ /**
20
+ * Parse request query params into QueryOptions, applying this generator's
21
+ * list-pagination bounds (default page size + hard max limit) so no read
22
+ * path can be tricked into buffering an entire table into memory.
23
+ */
24
+ private parseQuery;
17
25
  /**
18
26
  * Generate REST routes using existing DataDriver
19
27
  */
@@ -1,6 +1,22 @@
1
1
  import { QueryOptions } from "../types";
2
2
  export declare const mapOperator: (op: string) => import("@rebasepro/types").WhereFilterOp | null;
3
+ export { DEFAULT_LIST_LIMIT, DEFAULT_VECTOR_LIST_LIMIT, MAX_LIST_LIMIT } from "@rebasepro/types";
4
+ /**
5
+ * Overridable list-pagination bounds for {@link parseQueryOptions}. Without
6
+ * these, `GET /<collection>` with no `?limit` would buffer the ENTIRE table
7
+ * into a JS array + JSON response (a trivial OOM/DoS), and `?limit=100000000`
8
+ * would be honoured verbatim.
9
+ */
10
+ export interface ListLimitOptions {
11
+ /**
12
+ * Page size used when the client sends no `?limit`. Applied to plain and
13
+ * text-search reads — a vector search falls back to its own default (10).
14
+ */
15
+ defaultLimit?: number;
16
+ /** Upper bound clamped onto any client-supplied `?limit`. */
17
+ maxLimit?: number;
18
+ }
3
19
  /**
4
20
  * Parse query parameters into QueryOptions
5
21
  */
6
- export declare function parseQueryOptions(query: Record<string, unknown>): QueryOptions;
22
+ export declare function parseQueryOptions(query: Record<string, unknown>, limits?: ListLimitOptions): QueryOptions;
@@ -119,6 +119,33 @@ export interface RefreshTokenInfo {
119
119
  createdAt: Date;
120
120
  userAgent?: string | null;
121
121
  ipAddress?: string | null;
122
+ /**
123
+ * The sign-in this token descends from. Every token minted by rotating
124
+ * this one carries the same id.
125
+ *
126
+ * Optional because a custom {@link TokenRepository} written against an
127
+ * older release does not supply it; the refresh endpoint then treats the
128
+ * token as a session of one, which costs reuse tolerance but still works.
129
+ */
130
+ sessionId?: string;
131
+ /**
132
+ * Set when this token was superseded by a rotation. Being superseded is
133
+ * not an error — a client whose response was lost still holds it — so it
134
+ * stays usable to mint a sibling for a short window after this instant.
135
+ */
136
+ rotatedAt?: Date | null;
137
+ /** Hard kill (logout, remote session revoke). Never usable again. */
138
+ revoked?: boolean;
139
+ /** When the sign-in happened; carried across rotations, unlike createdAt. */
140
+ sessionStartedAt?: Date;
141
+ }
142
+ /**
143
+ * Identity of the sign-in a refresh token belongs to, threaded through
144
+ * rotation so descendants stay grouped.
145
+ */
146
+ export interface RefreshTokenSession {
147
+ id: string;
148
+ startedAt: Date;
122
149
  }
123
150
  /**
124
151
  * Password reset token info
@@ -278,9 +305,45 @@ export interface RoleRepository {
278
305
  */
279
306
  export interface TokenRepository {
280
307
  /**
281
- * Create a new refresh token
308
+ * Create a new refresh token.
309
+ *
310
+ * `session` groups this token with the sign-in it descends from. It is
311
+ * optional so that repositories written against an older release keep
312
+ * satisfying this interface; implementations that ignore it degrade to one
313
+ * session per token.
314
+ */
315
+ createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<void>;
316
+ /**
317
+ * Mark a token as superseded by a rotation, WITHOUT making it unusable.
318
+ *
319
+ * The distinction from deletion is the entire point: a client that never
320
+ * received the rotated response still holds this token, and must be able
321
+ * to present it and be recognised. Implementations that omit this method
322
+ * fall back to {@link TokenRepository.deleteRefreshToken}, which restores
323
+ * the old, lossy behaviour.
324
+ */
325
+ markRefreshTokenRotated?(tokenHash: string): Promise<void>;
326
+ /**
327
+ * Hard-kill every token of one sign-in (logout, remote session revoke).
328
+ * Unlike rotation this is final — no grace, no replay.
329
+ */
330
+ revokeRefreshTokenSession?(sessionId: string): Promise<void>;
331
+ /**
332
+ * Drop tokens of a session that were superseded before `supersededBefore`,
333
+ * plus anything already expired. Keeps rotation from growing a row per
334
+ * refresh forever; called opportunistically, never load-bearing.
335
+ */
336
+ pruneRefreshTokens?(uid: string, sessionId: string, supersededBefore: Date): Promise<void>;
337
+ /**
338
+ * The instant before which every session of this user is void, or null if
339
+ * none is set. See `users.tokens_valid_after`.
340
+ */
341
+ getTokensValidAfter?(uid: string): Promise<Date | null>;
342
+ /**
343
+ * Void every session that began before `at`. Set alongside deleting the
344
+ * user's tokens so a rotation racing the delete cannot survive it.
282
345
  */
283
- createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
346
+ setTokensValidAfter?(uid: string, at: Date): Promise<void>;
284
347
  /**
285
348
  * Find a refresh token by hash
286
349
  */
@@ -65,6 +65,23 @@ export declare function generateRefreshToken(): string;
65
65
  * Hash a refresh token for database storage (don't store raw tokens)
66
66
  */
67
67
  export declare function hashRefreshToken(token: string): string;
68
+ /**
69
+ * The longest a cookie can live. Chrome (since 104) and RFC 6265bis silently
70
+ * rewrite any `Max-Age` beyond 400 days down to 400 days, so promising a
71
+ * browser more is not a stricter policy — it is a policy that differs from
72
+ * what is actually enforced, which is worse than knowing the ceiling.
73
+ */
74
+ export declare const MAX_COOKIE_AGE_MS: number;
75
+ /**
76
+ * How long a refresh token is valid for, in milliseconds.
77
+ *
78
+ * Every rotation issues a token with a fresh TTL, so this is a sliding window:
79
+ * a user who visits at all keeps their session indefinitely, and one who
80
+ * disappears loses it this long after their last visit. That is what both
81
+ * Firebase and Supabase do by default, and it is the behaviour people mean
82
+ * when they say they expect to still be signed in.
83
+ */
84
+ export declare function getRefreshTokenTtlMs(): number;
68
85
  /**
69
86
  * Calculate refresh token expiration date
70
87
  */
@@ -47,6 +47,22 @@ export interface AuthModuleConfig {
47
47
  * auth endpoints, and CORS must allow credentials (no `origin: "*"`).
48
48
  */
49
49
  cookieAuth?: CookieAuthConfig;
50
+ /**
51
+ * How long a refresh token stays usable after it has been rotated away,
52
+ * in seconds. Default 10, matching GoTrue's `refresh_token_reuse_interval`.
53
+ *
54
+ * Rotation is only safe if the client is guaranteed to receive the
55
+ * replacement, and no network guarantees that. A pod rolls mid-response, a
56
+ * laptop suspends, a second tab boots at the same instant — and the client
57
+ * is left holding a token the database has moved past. Within this window
58
+ * that client is handed a fresh token of the same session instead of a
59
+ * 401, which is the difference between a hiccup and being silently signed
60
+ * out of an app you were using.
61
+ *
62
+ * Widen it if your clients are flaky or your deploys are long; the cost is
63
+ * how long a captured token stays useful to someone who copied it.
64
+ */
65
+ refreshTokenReuseIntervalSeconds?: number;
50
66
  }
51
67
  /**
52
68
  * Configuration for httpOnly refresh-token cookies.
@@ -0,0 +1,59 @@
1
+ import { type Server } from "http";
2
+ import { Hono } from "hono";
3
+ import { type RebaseBackendInstance } from "../init";
4
+ import type { HonoEnv } from "../api/types";
5
+ import { type RebaseBootEnv } from "./env";
6
+ import { type LoadedBundle } from "./bundle";
7
+ import { type InitializedDataSource } from "./driver";
8
+ /** A running runtime, and the handle to stop it. */
9
+ export interface BootedRuntime {
10
+ app: Hono<HonoEnv>;
11
+ server: Server;
12
+ backend: RebaseBackendInstance;
13
+ bundle: LoadedBundle;
14
+ env: RebaseBootEnv;
15
+ /** The port actually bound, which in development may not be the one asked for. */
16
+ port: number;
17
+ dataSources: InitializedDataSource[];
18
+ shutdown: () => Promise<void>;
19
+ }
20
+ export interface BootOptions {
21
+ /** Bundle directory. Defaults to `REBASE_BUNDLE` or `./dist-bundle`. */
22
+ bundleDir?: string;
23
+ /**
24
+ * A bundle that has already been resolved.
25
+ *
26
+ * `rebase dev` passes one built from source (see `createSourceBundle`), so
27
+ * development and production run the identical boot path rather than two
28
+ * implementations that drift apart.
29
+ */
30
+ bundle?: LoadedBundle;
31
+ /** Skip binding a port. Used by tests that drive `app.fetch` directly. */
32
+ listen?: boolean;
33
+ /** Install SIGTERM/SIGINT handlers. Off for tests. */
34
+ handleSignals?: boolean;
35
+ }
36
+ /**
37
+ * Boot a Rebase runtime from a built bundle.
38
+ *
39
+ * This is the entrypoint the official container image runs, and it is the same
40
+ * code path a self-hosted deployment uses — there is no separate "platform"
41
+ * runtime. Everything it does was previously the responsibility of a
42
+ * hand-written `backend/src/index.ts` in every project: wiring CORS and security
43
+ * headers, opening database connections, resolving storage, mounting health and
44
+ * metrics, serving the client bundle, and shutting all of it down cleanly.
45
+ *
46
+ * Moving it here is what makes a project's *code* separable from the *engine*
47
+ * that runs it: the bundle can then be handed to a newer runtime without being
48
+ * rebuilt, which is the precondition for patching a fleet.
49
+ */
50
+ export declare function bootFromBundle(options?: BootOptions): Promise<BootedRuntime>;
51
+ /**
52
+ * Boot and keep running, reporting failures the way a container should.
53
+ *
54
+ * A `BundleError` is a configuration problem with a known fix, so it prints the
55
+ * message and its hint without a stack trace — the stack is noise when the
56
+ * answer is "set DATABASE_URL". Anything else keeps its stack, because it is a
57
+ * bug and the trace is the point.
58
+ */
59
+ export declare function runFromBundle(options?: BootOptions): Promise<BootedRuntime>;
@@ -0,0 +1,134 @@
1
+ import { type CollectionConfig, type CollectionCallbacks, type DataSourceDefinition, type RebaseBundleManifest, type StorageSourceDefinition } from "@rebasepro/types";
2
+ import type { StorageAuthorize } from "../storage/types";
3
+ /** Thrown when a bundle cannot be read, or claims a contract this runtime cannot honour. */
4
+ export declare class BundleError extends Error {
5
+ readonly hint?: string | undefined;
6
+ constructor(message: string, hint?: string | undefined);
7
+ }
8
+ /** A bundle that has been located and whose manifest has been validated. */
9
+ export interface LoadedBundle {
10
+ dir: string;
11
+ manifest: RebaseBundleManifest;
12
+ /** Absolute path to the compiled collections directory, when present. */
13
+ collectionsDir?: string;
14
+ functionsDir?: string;
15
+ cronsDir?: string;
16
+ /** Absolute path to built admin assets, when the admin panel is bundled. */
17
+ adminDir?: string;
18
+ /** Absolute path to built static assets to serve from this process. */
19
+ staticDir?: string;
20
+ }
21
+ /**
22
+ * Read and validate a bundle's manifest.
23
+ *
24
+ * The checks here are the runtime half of the compatibility contract, and they
25
+ * all fail loudly at boot rather than at the first request. A container that
26
+ * refuses to start is a deploy that rolls back; a container that starts and then
27
+ * misbehaves is an incident.
28
+ */
29
+ export declare function readBundleManifest(bundleDir: string): RebaseBundleManifest;
30
+ /**
31
+ * Locate a bundle and resolve every directory the runtime needs from it.
32
+ *
33
+ * Entry paths in the manifest are bundle-relative and are resolved here, once,
34
+ * so nothing downstream has to know the layout. A declared directory that does
35
+ * not exist is dropped with a warning rather than failing the boot: an empty
36
+ * `functions/` is a perfectly ordinary project, and refusing to start over one
37
+ * would be the runtime inventing a requirement the developer never stated.
38
+ */
39
+ /**
40
+ * Resolve a bundle-relative entry, refusing anything that escapes the bundle.
41
+ *
42
+ * Applied to the entries that are `import()`ed — the schema, the config index,
43
+ * the users collection — and not only to the ones that are merely scanned. Those
44
+ * three *execute code*, so they are precisely the ones a malformed or hostile
45
+ * manifest would target, and leaving them unchecked while guarding the read-only
46
+ * paths would be defending the wrong door.
47
+ */
48
+ export declare function resolveBundlePath(bundleDir: string, entry: string, label: string): string;
49
+ export declare function loadBundle(bundleDir: string): LoadedBundle;
50
+ /**
51
+ * The Drizzle schema a bundle ships: tables, enums and relations, as generated
52
+ * from the project's collections.
53
+ */
54
+ export interface BundleSchemaExports {
55
+ tables?: Record<string, unknown>;
56
+ enums?: Record<string, unknown>;
57
+ relations?: Record<string, unknown>;
58
+ }
59
+ /**
60
+ * Import the bundle's Drizzle schema module.
61
+ *
62
+ * Returns `undefined` when the bundle declares none — `baas` mode introspects the
63
+ * live database instead of shipping a schema.
64
+ */
65
+ export declare function loadBundleSchema(bundle: LoadedBundle): Promise<BundleSchemaExports | undefined>;
66
+ /**
67
+ * Build a bundle view over a project's **source** directories.
68
+ *
69
+ * `rebase dev` runs TypeScript directly through tsx, so there is no compiled
70
+ * bundle to load — but everything downstream of loading (drivers, storage, auth,
71
+ * routes) should be identical, or development stops predicting production. This
72
+ * produces the same {@link LoadedBundle} shape from source paths, so the one boot
73
+ * path serves both.
74
+ *
75
+ * The schema version is left empty deliberately: nothing has been built, so
76
+ * there is no build-time answer, and the runtime computes one from the live
77
+ * collections instead.
78
+ */
79
+ export declare function createSourceBundle(options: {
80
+ projectRoot: string;
81
+ config?: string;
82
+ collections?: string;
83
+ functions?: string;
84
+ crons?: string;
85
+ schema?: string;
86
+ mode?: "cms" | "baas" | "static";
87
+ app?: string;
88
+ }): LoadedBundle;
89
+ /**
90
+ * Declarations a bundle's config package exports alongside its collections.
91
+ *
92
+ * These describe *topology* — which databases and which buckets exist — so they
93
+ * belong to the project rather than to the environment. The environment then
94
+ * supplies credentials for each declared key. Splitting it this way is what lets
95
+ * a deploy be validated before it runs: the set of things needing configuration
96
+ * is known from the bundle, without reading anyone's secrets.
97
+ */
98
+ export interface BundleConfigExports {
99
+ dataSources?: DataSourceDefinition[];
100
+ storageSources?: StorageSourceDefinition[];
101
+ /**
102
+ * Per-object storage access control.
103
+ *
104
+ * A function, so it can only come from the project's own code — there is no
105
+ * environment variable that could express "this user may read this key".
106
+ * Without a way to supply it, a production deployment with a bucket would be
107
+ * forced to choose between `STORAGE_PUBLIC_READ` (world-readable) and
108
+ * `STORAGE_ALLOW_ANY_AUTHENTICATED` (every signed-in user can read, overwrite
109
+ * and delete every other user's files) — the runtime would be making an
110
+ * insecure choice on the developer's behalf.
111
+ */
112
+ storageAuthorize?: StorageAuthorize;
113
+ /** Lifecycle callbacks applied to every collection. */
114
+ callbacks?: CollectionCallbacks;
115
+ }
116
+ /**
117
+ * Read the config package's `index` for declarations.
118
+ *
119
+ * Absent, empty or unreadable all mean the same thing: a single default database
120
+ * and a single default bucket. That is the overwhelmingly common project, and it
121
+ * must not be required to say so. A malformed export is reported and ignored
122
+ * rather than fatal — a typo in an optional declaration should not take down a
123
+ * server whose collections are fine.
124
+ */
125
+ export declare function loadBundleConfigExports(bundle: LoadedBundle): Promise<BundleConfigExports>;
126
+ /**
127
+ * Import the collection that backs authentication.
128
+ *
129
+ * Auth needs to know which table holds users. The bundle names the module; the
130
+ * convention (`collections/users`) covers every project that did not rename it.
131
+ * Returning `undefined` is valid — a `baas`-mode project has no config package,
132
+ * and the auth bootstrapper falls back to its own default users table.
133
+ */
134
+ export declare function loadUsersCollection(bundle: LoadedBundle): Promise<CollectionConfig | undefined>;
@@ -0,0 +1,57 @@
1
+ import type { BackendBootstrapper } from "@rebasepro/types";
2
+ import type { ResolvedDataSourceConfig } from "./sources";
3
+ /** The connection handle a driver hands back. */
4
+ export interface DatabaseConnection {
5
+ db: unknown;
6
+ /**
7
+ * Present for pool-based drivers. Closed during shutdown, and used to probe
8
+ * the source for health — `query` lives here, not on the connection itself.
9
+ */
10
+ pool?: {
11
+ end: () => Promise<void>;
12
+ query?: (sql: string) => Promise<unknown>;
13
+ };
14
+ /** Some drivers expose a query directly instead of via a pool. */
15
+ query?: (sql: string) => Promise<unknown>;
16
+ connectionString?: string;
17
+ }
18
+ /**
19
+ * Run a trivial query against a source, to see whether it answers.
20
+ *
21
+ * Returns `undefined` when the driver exposes no way to ask — a driver that
22
+ * cannot be probed must not be reported as unhealthy, only as unknown.
23
+ */
24
+ export declare function probeDataSource(source: InitializedDataSource): Promise<{
25
+ healthy: boolean;
26
+ error?: string;
27
+ } | undefined>;
28
+ /** One initialized data source: its bootstrapper plus the handle to close. */
29
+ export interface InitializedDataSource {
30
+ key: string;
31
+ engine: string;
32
+ driverPackage: string;
33
+ bootstrapper: BackendBootstrapper;
34
+ connection: DatabaseConnection;
35
+ }
36
+ export interface BundleSchema {
37
+ tables?: Record<string, unknown>;
38
+ enums?: Record<string, unknown>;
39
+ relations?: Record<string, unknown>;
40
+ }
41
+ /**
42
+ * Build a driver for one data source.
43
+ *
44
+ * Only the default source receives the bundle's Drizzle schema: the schema
45
+ * describes the tables generated from this project's collections, which live in
46
+ * the default database. Handing it to a secondary source would tell that
47
+ * driver's adapter about tables it does not have.
48
+ */
49
+ export declare function initializeDataSource(source: ResolvedDataSourceConfig, schema: BundleSchema | undefined, resolveFrom?: string[]): Promise<InitializedDataSource>;
50
+ /**
51
+ * Build drivers for every resolved data source.
52
+ *
53
+ * Sequential on purpose: a failure on the second source should not leave a
54
+ * half-opened pool from a third racing behind it, and the log then reads in the
55
+ * order a human would expect.
56
+ */
57
+ export declare function initializeDataSources(sources: ResolvedDataSourceConfig[], schema: BundleSchema | undefined, resolveFrom?: string[]): Promise<InitializedDataSource[]>;
@@ -0,0 +1,129 @@
1
+ import { z } from "zod";
2
+ import { type RebaseEnv } from "../env";
3
+ /**
4
+ * The environment a bundle-booted runtime understands.
5
+ *
6
+ * This extends the base {@link loadEnv} schema with the variables an application
7
+ * used to declare for itself in its own `env.ts`. They live here now because the
8
+ * runtime, not the application, is what reads them: a project ships a bundle and
9
+ * a set of environment variables, and everything either side needs to agree on
10
+ * has to be part of the contract rather than a convention each project reinvents.
11
+ */
12
+ declare const bootEnvExtension: z.ZodObject<{
13
+ SMTP_HOST: z.ZodOptional<z.ZodString>;
14
+ SMTP_PORT: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<number, string>>;
15
+ SMTP_SECURE: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
16
+ "": "";
17
+ true: "true";
18
+ false: "false";
19
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
20
+ SMTP_USER: z.ZodOptional<z.ZodString>;
21
+ SMTP_PASS: z.ZodOptional<z.ZodString>;
22
+ SMTP_FROM: z.ZodOptional<z.ZodString>;
23
+ SMTP_NAME: z.ZodOptional<z.ZodString>;
24
+ APP_NAME: z.ZodDefault<z.ZodString>;
25
+ REBASE_SERVE_STATIC: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
26
+ "": "";
27
+ true: "true";
28
+ false: "false";
29
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
30
+ REBASE_MIGRATE_ON_BOOT: z.ZodOptional<z.ZodEnum<{
31
+ "": "";
32
+ push: "push";
33
+ none: "none";
34
+ ensure: "ensure";
35
+ }>>;
36
+ REBASE_METRICS: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
37
+ "": "";
38
+ true: "true";
39
+ false: "false";
40
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
41
+ REBASE_METRICS_TOKEN: z.ZodOptional<z.ZodString>;
42
+ LOG_LEVEL: z.ZodOptional<z.ZodEnum<{
43
+ "": "";
44
+ debug: "debug";
45
+ info: "info";
46
+ warn: "warn";
47
+ error: "error";
48
+ }>>;
49
+ STORAGE_PUBLIC_READ: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
50
+ "": "";
51
+ true: "true";
52
+ false: "false";
53
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
54
+ STORAGE_ALLOW_ANY_AUTHENTICATED: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
55
+ "": "";
56
+ true: "true";
57
+ false: "false";
58
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
59
+ AUTH_REQUIRE: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
60
+ "": "";
61
+ true: "true";
62
+ false: "false";
63
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
64
+ AUTH_ALLOW_USER_LOOKUP: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
65
+ "": "";
66
+ true: "true";
67
+ false: "false";
68
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
69
+ AUTH_COOKIE_SAME_SITE: z.ZodOptional<z.ZodEnum<{
70
+ "": "";
71
+ Strict: "Strict";
72
+ Lax: "Lax";
73
+ None: "None";
74
+ }>>;
75
+ AUTH_DEFAULT_ROLE: z.ZodOptional<z.ZodString>;
76
+ GITHUB_CLIENT_ID: z.ZodOptional<z.ZodString>;
77
+ GITHUB_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
78
+ MICROSOFT_CLIENT_ID: z.ZodOptional<z.ZodString>;
79
+ MICROSOFT_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
80
+ REBASE_BASE_PATH: z.ZodDefault<z.ZodString>;
81
+ REBASE_ENABLE_SWAGGER: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
82
+ "": "";
83
+ true: "true";
84
+ false: "false";
85
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
86
+ REBASE_MAX_BODY_SIZE: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
87
+ REBASE_COMPRESSION: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
88
+ "": "";
89
+ true: "true";
90
+ false: "false";
91
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
92
+ REBASE_HISTORY: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
93
+ "": "";
94
+ true: "true";
95
+ false: "false";
96
+ }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
97
+ CORS_ORIGINS: z.ZodOptional<z.ZodString>;
98
+ }, z.core.$strip>;
99
+ export type RebaseBootEnv = RebaseEnv & z.infer<typeof bootEnvExtension>;
100
+ /**
101
+ * Load and validate the environment for a bundle boot.
102
+ *
103
+ * Does not read `.env` files — that is the deployment's job (a container gets
104
+ * real environment variables; `rebase dev` and `rebase start` load dotenv before
105
+ * calling in).
106
+ */
107
+ export declare function loadBootEnv(): RebaseBootEnv;
108
+ /**
109
+ * Whether an origin is a loopback address.
110
+ *
111
+ * In development the runtime reflects only localhost origins. It cannot reflect
112
+ * an arbitrary `Origin`, because credentials are enabled: any site the developer
113
+ * happened to visit could otherwise make credentialed requests against the dev
114
+ * server with the developer's session and read the responses.
115
+ */
116
+ export declare function isLocalhostOrigin(origin: string): boolean;
117
+ /** A CORS origin resolver of the shape Hono's `cors()` middleware expects. */
118
+ export type CorsOriginResolver = (origin: string) => string | null;
119
+ /**
120
+ * Build the CORS origin policy.
121
+ *
122
+ * Production serves an explicit allow-list and nothing else. `loadEnv` already
123
+ * refuses to start a production process with neither `CORS_ORIGINS` nor
124
+ * `FRONTEND_URL`, so an empty list here can only mean the values were blank
125
+ * strings — still worth failing on, because the alternative is an API that
126
+ * quietly rejects its own frontend.
127
+ */
128
+ export declare function resolveCorsOrigin(env: RebaseBootEnv): CorsOriginResolver;
129
+ export {};