@rebasepro/server 0.11.1-canary.gfd39654 → 0.12.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.
@@ -29,7 +29,6 @@ export interface ContractRoutesConfig {
29
29
  * at boot, so there was nothing to hash when it was built.
30
30
  */
31
31
  schemaVersion?: string;
32
- mode: "cms" | "baas";
33
32
  /** Runtime package version, surfaced so a client can report what it built against. */
34
33
  runtimeVersion?: string;
35
34
  }
@@ -1,26 +1,15 @@
1
1
  /**
2
2
  * Type definitions for Service API Keys.
3
3
  *
4
- * API keys provide machine-to-machine authentication for scripts, cron jobs,
5
- * and third-party integrations. Each key is scoped to specific collections
6
- * and operations via the `ApiKeyPermission` model.
4
+ * The wire contract permissions, the masked key, the create/update payloads —
5
+ * lives in `@rebasepro/types`, because the client SDK needs the same shapes and
6
+ * the two declarations had already drifted apart. Only {@link ApiKey}, the
7
+ * database row carrying `key_hash`, is server-side and stays here.
7
8
  *
8
9
  * @module
9
10
  */
10
- /**
11
- * A single permission entry scoping an API key to a collection and set of operations.
12
- *
13
- * Use `"*"` as the collection value to grant access to all collections
14
- * (and all custom functions). Custom functions are addressed with the
15
- * `functions` namespace: `"functions"` grants every function,
16
- * `"functions/<name>"` grants a single one.
17
- */
18
- export interface ApiKeyPermission {
19
- /** Collection slug, `"functions"`/`"functions/<name>"`, or `"*"` for everything. */
20
- collection: string;
21
- /** Allowed operations on the collection. */
22
- operations: ("read" | "write" | "delete")[];
23
- }
11
+ import type { ApiKeyPermission } from "@rebasepro/types";
12
+ export type { ApiKeyPermission, ApiKeyMasked, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest } from "@rebasepro/types";
24
13
  /**
25
14
  * Full database row for an API key.
26
15
  * The `key_hash` is never exposed via the API — only stored for lookup.
@@ -54,56 +43,3 @@ export interface ApiKey {
54
43
  expires_at: string | null;
55
44
  revoked_at: string | null;
56
45
  }
57
- /**
58
- * Masked version of an API key, safe for API responses.
59
- * Omits `key_hash` and shows only the prefix.
60
- */
61
- export interface ApiKeyMasked {
62
- id: string;
63
- name: string;
64
- key_prefix: string;
65
- permissions: ApiKeyPermission[];
66
- /** When true, the key is granted the `admin` role (admin routes + RLS `default_admin` policies). */
67
- admin: boolean;
68
- rate_limit: number | null;
69
- created_by: string;
70
- created_at: string;
71
- updated_at: string;
72
- last_used_at: string | null;
73
- expires_at: string | null;
74
- revoked_at: string | null;
75
- }
76
- /**
77
- * Request body for creating a new API key.
78
- */
79
- export interface CreateApiKeyRequest {
80
- name: string;
81
- permissions: ApiKeyPermission[];
82
- /** When true, grants the `admin` role (admin routes + RLS `default_admin` policies). */
83
- admin?: boolean;
84
- /** Requests per 15-minute window. Omit or `null` to use the server default (1000/window). */
85
- rate_limit?: number | null;
86
- /** ISO-8601 expiration timestamp. Omit for no expiration. */
87
- expires_at?: string | null;
88
- }
89
- /**
90
- * Request body for updating an existing API key.
91
- * All fields are optional — only provided fields are updated.
92
- */
93
- export interface UpdateApiKeyRequest {
94
- name?: string;
95
- permissions?: ApiKeyPermission[];
96
- /** When true, grants the `admin` role (admin routes + RLS `default_admin` policies). */
97
- admin?: boolean;
98
- rate_limit?: number | null;
99
- expires_at?: string | null;
100
- }
101
- /**
102
- * Returned exactly once when a key is created.
103
- * The `key` field contains the full plaintext key — it is never stored
104
- * or returned again after creation.
105
- */
106
- export interface ApiKeyWithSecret extends ApiKeyMasked {
107
- /** Full plaintext API key (e.g. `rk_live_abc123...`). */
108
- key: string;
109
- }
@@ -28,6 +28,17 @@ export interface BuiltinAuthAdapterConfig {
28
28
  emailConfig?: EmailConfig;
29
29
  /** Whether to allow new user registration. */
30
30
  allowRegistration?: boolean;
31
+ /**
32
+ * Hard kill switch: block self-registration outright, including the
33
+ * first-user bootstrap window that an empty database would otherwise open.
34
+ *
35
+ * Was declared on the route module and read by both config endpoints, but
36
+ * never plumbed through here — so nothing a user of the framework could
37
+ * write ever reached it, and the tests that covered it passed only because
38
+ * they built `createAuthRoutes` directly, bypassing this adapter (the sole
39
+ * wiring path a real backend uses).
40
+ */
41
+ disableSelfRegistration?: boolean;
31
42
  /** Whether to expose the authenticated email→minimal-profile lookup route. */
32
43
  allowUserLookup?: boolean;
33
44
  /** Default role to assign to new users. */
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Whether a registration may proceed — computed in exactly one place.
3
+ *
4
+ * This predicate had drifted across three independent implementations, which is
5
+ * the shape the bug always takes: one endpoint *advertises* that registration is
6
+ * open, another *enforces* that it is closed, and the user lands on a form that
7
+ * can only ever 403.
8
+ *
9
+ * 1. `POST /auth/register` (routes.ts) — enforces.
10
+ * 2. `GET /auth/config` (session-routes.ts) — advertises.
11
+ * 3. `getCapabilities()` (builtin-auth-adapter.ts) — also advertises, and is
12
+ * the one that actually answers `GET /auth/config` on a real backend:
13
+ * `init.ts` registers that path directly *before* mounting the auth router,
14
+ * so Hono resolves it first and the session-routes copy never runs for it.
15
+ *
16
+ * Two of the three had already been fixed for the empty-database case when the
17
+ * third was still returning `allowRegistration || needsSetup` — missing the kill
18
+ * switch entirely. The fix is not to correct the third copy but to delete all
19
+ * three: every caller now goes through this function, so the next term added
20
+ * here reaches every surface at once.
21
+ *
22
+ * The rule itself:
23
+ *
24
+ * - `disableSelfRegistration` is absolute. It blocks even the first user, for
25
+ * operators who provision accounts out of band and never want a public
26
+ * first-come-first-admin window.
27
+ * - Otherwise an **empty** user table always admits the first registration,
28
+ * which auto-promotes to admin. Without this a backend deployed with
29
+ * `allowRegistration: false` is a dead end: `POST /admin/bootstrap` needs an
30
+ * authenticated caller, and an empty database cannot produce one.
31
+ * - Otherwise `allowRegistration` decides, as it always did.
32
+ */
33
+ export interface RegistrationPolicy {
34
+ /** The hard kill switch. Blocks registration including first-user bootstrap. */
35
+ disableSelfRegistration?: boolean;
36
+ /** Steady-state self-registration, once at least one user exists. */
37
+ allowRegistration?: boolean;
38
+ /**
39
+ * True when the user table is empty — the bootstrap window.
40
+ *
41
+ * Callers that already know this (the config endpoints compute it to report
42
+ * `needsSetup`) pass it directly. `POST /auth/register` must not: it serves
43
+ * anonymous callers, so it checks {@link isSteadyStateRegistrationOpen}
44
+ * first and only pays for the count when that says no.
45
+ */
46
+ needsSetup: boolean;
47
+ }
48
+ /** The full predicate, for callers that already know whether setup is needed. */
49
+ export declare function isRegistrationOpen(policy: RegistrationPolicy): boolean;
50
+ /**
51
+ * The same predicate with the bootstrap window assumed closed.
52
+ *
53
+ * Exists so `POST /auth/register` can reject the common case without counting
54
+ * rows. A `false` here does **not** mean "refuse" — it means "the answer depends
55
+ * on whether the table is empty, so now go and look".
56
+ */
57
+ export declare function isSteadyStateRegistrationOpen(policy: Omit<RegistrationPolicy, "needsSetup">): boolean;
@@ -13,10 +13,23 @@ export interface LoadedBundle {
13
13
  collectionsDir?: string;
14
14
  functionsDir?: string;
15
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;
16
+ /**
17
+ * Built static apps to serve from this process, in mount order.
18
+ *
19
+ * A list, not a single directory: one process serves a site at `/` and an
20
+ * admin at `/admin`. Entries whose directory is missing are dropped with a
21
+ * warning, so a partially-built bundle still boots its API.
22
+ */
23
+ staticApps: LoadedStaticApp[];
24
+ }
25
+ /** One built static app inside a loaded bundle, with an absolute directory. */
26
+ export interface LoadedStaticApp {
27
+ /** Public base path, e.g. `/` or `/admin`. */
28
+ path: string;
29
+ /** Absolute path to the built assets. */
30
+ dir: string;
31
+ /** Serve `index.html` for unmatched paths under `path`. */
32
+ spa: boolean;
20
33
  }
21
34
  /**
22
35
  * Read and validate a bundle's manifest.
@@ -83,7 +96,6 @@ export declare function createSourceBundle(options: {
83
96
  functions?: string;
84
97
  crons?: string;
85
98
  schema?: string;
86
- mode?: "cms" | "baas" | "static";
87
99
  app?: string;
88
100
  }): LoadedBundle;
89
101
  /**
@@ -1,7 +1,16 @@
1
1
  import type { BackendBootstrapper } from "@rebasepro/types";
2
2
  import type { ResolvedDataSourceConfig } from "./sources";
3
- /** The connection handle a driver hands back. */
4
- export interface DatabaseConnection {
3
+ /**
4
+ * The connection handle a driver hands back at boot: the client object, the
5
+ * pool to close on shutdown, and how to probe it.
6
+ *
7
+ * Named `DatabaseConnection` until that collided with `DatabaseConnection` in
8
+ * `@rebasepro/types` — an abstract `{ type, isConnected, close() }` that
9
+ * `MongoDBConnection` implements. The two share no field, and both are public:
10
+ * one is re-exported from `@rebasepro/server`'s index, the other from
11
+ * `@rebasepro/types`, packages that are installed together.
12
+ */
13
+ export interface DriverConnection {
5
14
  db: unknown;
6
15
  /**
7
16
  * Present for pool-based drivers. Closed during shutdown, and used to probe
@@ -31,7 +40,7 @@ export interface InitializedDataSource {
31
40
  engine: string;
32
41
  driverPackage: string;
33
42
  bootstrapper: BackendBootstrapper;
34
- connection: DatabaseConnection;
43
+ connection: DriverConnection;
35
44
  }
36
45
  export interface BundleSchema {
37
46
  tables?: Record<string, unknown>;
@@ -55,3 +64,8 @@ export declare function initializeDataSource(source: ResolvedDataSourceConfig, s
55
64
  * order a human would expect.
56
65
  */
57
66
  export declare function initializeDataSources(sources: ResolvedDataSourceConfig[], schema: BundleSchema | undefined, resolveFrom?: string[]): Promise<InitializedDataSource[]>;
67
+ /**
68
+ * @deprecated Use {@link DriverConnection}. This name collides with
69
+ * `DatabaseConnection` from `@rebasepro/types`, which is a different shape.
70
+ */
71
+ export type DatabaseConnection = DriverConnection;
@@ -78,11 +78,11 @@ declare const bootEnvExtension: z.ZodObject<{
78
78
  MICROSOFT_CLIENT_ID: z.ZodOptional<z.ZodString>;
79
79
  MICROSOFT_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
80
80
  REBASE_BASE_PATH: z.ZodDefault<z.ZodString>;
81
- REBASE_ENABLE_SWAGGER: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
81
+ REBASE_ENABLE_SWAGGER: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
82
82
  "": "";
83
83
  true: "true";
84
84
  false: "false";
85
- }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
85
+ }>>, z.ZodTransform<boolean | undefined, "" | "true" | "false" | undefined>>;
86
86
  REBASE_MAX_BODY_SIZE: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
87
87
  REBASE_COMPRESSION: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
88
88
  "": "";
@@ -115,6 +115,20 @@ export declare function loadBootEnv(): RebaseBootEnv;
115
115
  */
116
116
  export declare function isLocalhostOrigin(origin: string): boolean;
117
117
  /** A CORS origin resolver of the shape Hono's `cors()` middleware expects. */
118
+ /**
119
+ * Whether this process serves the OpenAPI docs.
120
+ *
121
+ * An explicit `REBASE_ENABLE_SWAGGER` wins in either direction. Left unset, the
122
+ * docs follow the environment: on in development, where they are part of how a
123
+ * scaffolded project is meant to be explored, and off in production, where the
124
+ * spec enumerates every collection and field to anyone who asks for it.
125
+ *
126
+ * Returning `undefined` for development is the point rather than an oversight —
127
+ * it hands the decision to the server's own policy in `init/docs.ts`, which also
128
+ * knows to withhold the Swagger UI while still serving the spec. Two defaults
129
+ * that can disagree about the same route is the bug this replaces.
130
+ */
131
+ export declare function resolveEnableSwagger(env: RebaseBootEnv): boolean | undefined;
118
132
  export type CorsOriginResolver = (origin: string) => string | null;
119
133
  /**
120
134
  * Build the CORS origin policy.
@@ -30,8 +30,21 @@ export type EnvBag = Record<string, string | undefined>;
30
30
  *
31
31
  * The default key maps to no suffix at all, which is what keeps every existing
32
32
  * single-database deployment working untouched.
33
+ *
34
+ * The rule itself lives in `@rebasepro/types` so the CLI and any control plane
35
+ * derive identical names from identical keys; this wrapper exists only to raise
36
+ * it as a `BundleError`, which is what the rest of boot reports failures as.
33
37
  */
34
38
  export declare function envSuffixForKey(key: string, defaultKey: string): string;
39
+ /**
40
+ * Guard against two distinct keys collapsing onto the same variable name.
41
+ *
42
+ * `media-cdn` and `media_cdn` are different source keys but the same suffix, and
43
+ * without this check one of them would silently read the other's configuration.
44
+ */
45
+ export declare function assertDistinctSuffixes(definitions: {
46
+ key: string;
47
+ }[], defaultKey: string, what: string): void;
35
48
  /** A data source resolved to everything needed to build a driver for it. */
36
49
  export interface ResolvedDataSourceConfig {
37
50
  /** Data-source key — becomes the driver-registry id collections route by. */
@@ -75,3 +88,21 @@ export declare function resolveStorageBackend(env: EnvBag, key: string, engineHi
75
88
  * they just had no way to be configured from the environment.
76
89
  */
77
90
  export declare function resolveStorageSources(env: EnvBag, definitions: StorageSourceDefinition[] | undefined, defaultBasePath: string): Record<string, BackendStorageConfig> | undefined;
91
+ /**
92
+ * Read a project's declared storage sources from its `rebase.json`.
93
+ *
94
+ * A managed bundle carries its topology in `manifest.json`, resolved at build
95
+ * time. A **custom** runtime has no manifest — it builds its own image and its
96
+ * own entrypoint — so without this it would have to re-declare in code what
97
+ * `rebase.json` already says, and the two would drift. Since a custom image
98
+ * contains the repository anyway, reading the file it already ships is what
99
+ * keeps one declaration authoritative for both runtimes.
100
+ *
101
+ * Walks up from `startDir` because an entrypoint lives at `backend/src` in the
102
+ * scaffolded layout and somewhere else in a hand-rolled one. A missing,
103
+ * unreadable or malformed file means "declared nothing" — one default source —
104
+ * which is the correct reading of every project that predates this and must
105
+ * never be an error: a storage declaration is optional, and failing to boot a
106
+ * whole backend over an absent optional file would be the worse bug.
107
+ */
108
+ export declare function loadDeclaredStorageSources(startDir: string, levels?: number): StorageSourceDefinition[];
@@ -1,4 +1,5 @@
1
1
  import { CollectionConfig, SecurityRule } from "@rebasepro/types";
2
+ import { type ValidateCollectionConfigOptions } from "./validate-config";
2
3
  /**
3
4
  * The one definition of "the collections".
4
5
  *
@@ -35,5 +36,14 @@ export declare function applyCollectionDefaults(collections: CollectionConfig[],
35
36
  * configuration error, and continuing produces the worst outcome available: an
36
37
  * API missing a route, or a policy file missing a table, with a successful exit
37
38
  * code. Both read as "no data" rather than as a failure.
39
+ *
40
+ * Every collection is strict-parsed on the way out — see `validate-config` for
41
+ * why a key that moved is fatal and a key nobody recognises only warns. It
42
+ * happens here, at the one definition of "the collections", so the runtime, the
43
+ * schema generator, the policy generator and the doctor all see the same
44
+ * verdict rather than three of them silently accepting a config the fourth
45
+ * rejects.
38
46
  */
39
- export declare function loadCollectionsFromDirectory(source: string): Promise<CollectionConfig[]>;
47
+ export declare function loadCollectionsFromDirectory(source: string, options?: {
48
+ validate?: false | ValidateCollectionConfigOptions;
49
+ }): Promise<CollectionConfig[]>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * A strict parse of every collection config, run at boot.
3
+ *
4
+ * Nothing used to check these files. A config written against an older version
5
+ * loaded clean, and whichever keys had moved since were simply ignored — no
6
+ * warning, no log line, no failed boot. The collection still served rows, so
7
+ * the only signal was the feature quietly not being there: an icon that never
8
+ * appeared, a relation that answered `[]`, a `readOnly` field the panel let you
9
+ * edit. The renames are not the problem; a rename with no runtime signal is.
10
+ *
11
+ * Two severities, because two different things are being detected:
12
+ *
13
+ * - A **known-removed or known-renamed key** is high-confidence and actionable —
14
+ * we know what it used to mean and what replaced it. That is an error, and
15
+ * refusing to boot is the point. A minute of downtime beats a week of "where
16
+ * did my icons go".
17
+ * - An **unrecognised key** is not. Configs legitimately carry extra metadata,
18
+ * and a key we do not know may simply be newer than this list. That warns,
19
+ * loudly, and escalates to an error only when asked
20
+ * (`REBASE_STRICT_COLLECTION_CONFIG=error`, or an explicit option).
21
+ *
22
+ * Everything is reported in one pass. Someone migrating a project wants the
23
+ * whole list once, not fifty-five sequential boots.
24
+ *
25
+ * This is not `validateCollectionJson` in `@rebasepro/admin`. That one parses a
26
+ * JSON string pasted into the panel's import dialog and checks value *shapes*
27
+ * against the flat `AdminCollection` view model. This one checks key *identity*
28
+ * against the authoring contract, on live objects, in a package that may not
29
+ * import the admin. The two answer different questions about different types,
30
+ * and merging them would mean the server depending on the admin panel.
31
+ */
32
+ /** How an unrecognised key is treated. */
33
+ export type UnknownKeyPolicy = "warn" | "error" | "off";
34
+ export interface ConfigProblem {
35
+ severity: "error" | "warning";
36
+ /** Dotted path into the config, e.g. `posts.properties.author`. */
37
+ path: string;
38
+ message: string;
39
+ }
40
+ export interface ValidateCollectionConfigOptions {
41
+ /**
42
+ * What to do with a key that is in no known list. Defaults to the
43
+ * `REBASE_STRICT_COLLECTION_CONFIG` environment variable, and to `"warn"`
44
+ * when that is unset.
45
+ */
46
+ unknownKeys?: UnknownKeyPolicy;
47
+ }
48
+ /**
49
+ * Read the unknown-key policy from the environment.
50
+ *
51
+ * `REBASE_STRICT_COLLECTION_CONFIG` accepts `error`/`strict`/`1`/`true` to
52
+ * escalate, `off`/`0`/`false` to silence, and anything else warns.
53
+ */
54
+ export declare function unknownKeyPolicyFromEnv(env?: Record<string, string | undefined>): UnknownKeyPolicy;
55
+ /**
56
+ * Every problem across every collection, in one pass.
57
+ *
58
+ * Pure: it logs nothing and throws nothing, so callers that want to render the
59
+ * list themselves (the doctor, a test) can.
60
+ */
61
+ export declare function findCollectionConfigProblems(collections: readonly unknown[], options?: ValidateCollectionConfigOptions): ConfigProblem[];
62
+ /**
63
+ * Warn about everything questionable, then refuse to boot if anything is wrong.
64
+ *
65
+ * Warnings are logged even when there are errors: someone migrating wants the
66
+ * whole picture in one run, and the second-most annoying thing after a broken
67
+ * boot is a boot that breaks again on something it could have told you the
68
+ * first time.
69
+ */
70
+ export declare function assertCollectionConfigs(collections: readonly unknown[], options?: ValidateCollectionConfigOptions): void;
@@ -77,6 +77,15 @@ export declare class CronScheduler {
77
77
  * a `skipped: true` result rather than running concurrently.
78
78
  */
79
79
  triggerJob(id: string): Promise<CronJobLogEntry | undefined>;
80
+ /**
81
+ * Warn once at start when the process looks like it is running on a
82
+ * platform that freezes or evicts instances between requests, where the
83
+ * in-process timers this scheduler relies on never fire.
84
+ *
85
+ * Advisory only: any failure here is swallowed so a detection bug can
86
+ * never take a production boot down.
87
+ */
88
+ private warnIfScaleToZero;
80
89
  /**
81
90
  * Schedule the next execution for a job.
82
91
  *
@@ -4,4 +4,5 @@ export { defineCron } from "./define-cron";
4
4
  export { CronScheduler, validateCronExpression } from "./cron-scheduler";
5
5
  export { createCronRoutes } from "./cron-routes";
6
6
  export { createCronStore } from "./cron-store";
7
+ export { detectFreezableRuntime, buildScaleToZeroWarning, CRON_ALWAYS_ON_ENV } from "./scale-to-zero";
7
8
  export type { CronStore } from "./cron-store";
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Scale-to-zero detection for the cron scheduler.
3
+ *
4
+ * The scheduler drives jobs with in-process `setTimeout`. That works on any
5
+ * always-running instance, but on a platform that freezes or evicts the
6
+ * container between requests (Cloud Run with `--min-instances=0`, AWS Lambda,
7
+ * Vercel functions) the timers simply never fire — the process boots, logs the
8
+ * jobs as registered, and silently runs nothing.
9
+ *
10
+ * None of these platforms expose their scaling floor to the container, so this
11
+ * detection is a heuristic: it identifies the *platform*, not the setting. It
12
+ * is a warning only — it must never influence boot.
13
+ *
14
+ * Environment variables used here were verified against vendor documentation:
15
+ * - `K_SERVICE` / `K_REVISION` / `K_CONFIGURATION` — Cloud Run services
16
+ * (Cloud Run container contract; no variable exposes min-instances).
17
+ * - `CLOUD_RUN_JOB` — Cloud Run jobs (same contract).
18
+ * - `AWS_LAMBDA_FUNCTION_NAME` — reserved AWS Lambda runtime variable.
19
+ * - `VERCEL=1` — Vercel system environment variable, available at runtime.
20
+ * - `KUBERNETES_SERVICE_HOST` — injected into every pod by the kubelet. Used
21
+ * as an *exclusion*: a Deployment pod runs continuously, and Knative on
22
+ * Kubernetes also sets `K_SERVICE`, so a pod is never warned about.
23
+ */
24
+ /** Environment variable that permanently silences the scale-to-zero warning. */
25
+ export declare const CRON_ALWAYS_ON_ENV = "REBASE_CRON_ALWAYS_ON";
26
+ /** The subset of `process.env` this module reads. */
27
+ export type EnvLike = Record<string, string | undefined>;
28
+ export interface FreezableRuntime {
29
+ /** Human-readable platform name, used verbatim in the warning. */
30
+ platform: string;
31
+ /** Names of the environment variables that identified the platform. */
32
+ signals: string[];
33
+ }
34
+ /** Minimal shape of a registered job needed to build the warning. */
35
+ export interface WarnableJob {
36
+ id: string;
37
+ enabled: boolean;
38
+ }
39
+ export interface ScaleToZeroWarning {
40
+ message: string;
41
+ data: Record<string, unknown>;
42
+ }
43
+ /**
44
+ * Identify a runtime whose instances can be frozen or torn down between
45
+ * requests. Returns `undefined` when the platform is unknown or known to run
46
+ * continuously.
47
+ */
48
+ export declare function detectFreezableRuntime(env?: EnvLike): FreezableRuntime | undefined;
49
+ /**
50
+ * Build the boot-time warning, or `undefined` when it does not apply.
51
+ *
52
+ * Fires only when all of the following hold:
53
+ * 1. `NODE_ENV=production` — a laptop or CI run is not at risk.
54
+ * 2. At least one *enabled* job is registered — nothing to lose otherwise.
55
+ * 3. The environment looks like a freezable platform (see above).
56
+ * 4. `REBASE_CRON_ALWAYS_ON` is not set to a truthy value.
57
+ */
58
+ export declare function buildScaleToZeroWarning(jobs: WarnableJob[], env?: EnvLike): ScaleToZeroWarning | undefined;
package/dist/env.d.ts CHANGED
@@ -22,6 +22,11 @@ declare const rebaseEnvSchema: z.ZodObject<{
22
22
  true: "true";
23
23
  false: "false";
24
24
  }>>, z.ZodTransform<boolean, "" | "true" | "false">>;
25
+ DISABLE_SELF_REGISTRATION: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
26
+ "": "";
27
+ true: "true";
28
+ false: "false";
29
+ }>>, z.ZodTransform<boolean, "" | "true" | "false" | undefined>>;
25
30
  ALLOW_LOCALHOST_IN_PRODUCTION: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
26
31
  "": "";
27
32
  true: "true";
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { initializeRebaseBackend, isAuthAdapter, isDatabaseAdapter } from "./ini
10
10
  export type { RebaseBackendConfig, RebaseBackendInstance, RebaseAuthConfig, BaasOptions } from "./init";
11
11
  export { rebase, _setRebaseMock, _resetRebaseMock } from "./singleton";
12
12
  export { loadCollectionsFromDirectory, applyCollectionDefaults, type CollectionDefaults } from "./collections/loader";
13
+ export { assertCollectionConfigs, findCollectionConfigProblems, unknownKeyPolicyFromEnv, type ConfigProblem, type UnknownKeyPolicy, type ValidateCollectionConfigOptions } from "./collections/validate-config";
13
14
  export * from "./db/interfaces";
14
15
  export * from "./auth/interfaces";
15
16
  export { requireAuth, requireAdmin, optionalAuth, queryTokenAuth, fileTokenAuth, extractUserFromToken, hashPassword, verifyPassword, validatePasswordStrength, generateSecurePassword, resolveAuthHooks, createBuiltinAuthAdapter, createCustomAuthAdapter, createGoogleProvider, createLinkedinProvider, createGitHubProvider, createMicrosoftProvider, createAppleProvider, createFacebookProvider, createTwitterProvider, createDiscordProvider, createGitLabProvider, createBitbucketProvider, createSlackProvider, createSpotifyProvider, isApiKeyToken, validateApiKey, httpMethodToOperation, isOperationAllowed, safeCompare } from "./auth";
@@ -32,6 +33,7 @@ export { loadEnv } from "./env";
32
33
  export type { RebaseEnv } from "./env";
33
34
  export * from "./types";
34
35
  export * from "./services/driver-registry";
36
+ export * from "./services/webhook-service";
35
37
  export { cleanupDevPortFile, listenWithPortRetry } from "./utils/dev-port";
36
38
  export { serveSPA } from "./serve-spa";
37
39
  export { installShutdownHandlers } from "./init/shutdown";
@@ -43,10 +45,10 @@ export type { LoadedBundle, BundleConfigExports } from "./boot/bundle";
43
45
  export { loadBootEnv, resolveCorsOrigin, isLocalhostOrigin } from "./boot/env";
44
46
  export type { RebaseBootEnv, CorsOriginResolver } from "./boot/env";
45
47
  export { resolveAuthOptions, resolveEmailOptions } from "./boot/options";
46
- export { envSuffixForKey, resolveDataSources, resolveStorageSources, resolveStorageBackend } from "./boot/sources";
48
+ export { envSuffixForKey, assertDistinctSuffixes, loadDeclaredStorageSources, resolveDataSources, resolveStorageSources, resolveStorageBackend } from "./boot/sources";
47
49
  export type { ResolvedDataSourceConfig, EnvBag } from "./boot/sources";
48
50
  export { initializeDataSource, initializeDataSources } from "./boot/driver";
49
- export type { InitializedDataSource, DatabaseConnection, BundleSchema } from "./boot/driver";
51
+ export type { InitializedDataSource, DriverConnection, DatabaseConnection, BundleSchema } from "./boot/driver";
50
52
  export { MetricsRegistry, createMetricsMiddleware, createMetricsRoutes, classifySurface } from "./metrics";
51
53
  export type { MetricSurface, MetricsHandle } from "./metrics";
52
54
  export { createContractRoutes } from "./api/contract-routes";