@rebasepro/types 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.
- package/dist/controllers/client.d.ts +3 -1
- package/dist/controllers/data_driver.d.ts +29 -0
- package/dist/index.es.js +270 -1
- package/dist/index.es.js.map +1 -1
- package/dist/types/backend.d.ts +30 -1
- package/dist/types/channel_bus.d.ts +192 -0
- package/dist/types/collection_contract.d.ts +43 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/project_manifest.d.ts +336 -0
- package/dist/types/schema_version.d.ts +18 -0
- package/dist/types/storage_authorize.d.ts +74 -0
- package/package.json +1 -1
- package/src/controllers/client.ts +3 -2
- package/src/controllers/data_driver.ts +56 -0
- package/src/types/backend.ts +33 -1
- package/src/types/channel_bus.ts +202 -0
- package/src/types/collection_contract.ts +249 -0
- package/src/types/index.ts +5 -0
- package/src/types/policy.ts +25 -11
- package/src/types/project_manifest.ts +362 -0
- package/src/types/schema_version.ts +112 -0
- package/src/types/storage_authorize.ts +77 -0
package/dist/types/backend.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
|
|
2
2
|
import type { AuthAdapter } from "./auth_adapter";
|
|
3
3
|
import type { HistoryConfig } from "../controllers/client";
|
|
4
|
+
import type { ChannelBusSetting } from "./channel_bus";
|
|
4
5
|
/**
|
|
5
6
|
* Abstract database connection interface.
|
|
6
7
|
* Represents a connection to any database system.
|
|
@@ -188,13 +189,23 @@ export interface ChannelRetentionRule {
|
|
|
188
189
|
*/
|
|
189
190
|
ttl?: number | string;
|
|
190
191
|
}
|
|
191
|
-
/**
|
|
192
|
+
/**
|
|
193
|
+
* Server-side realtime options.
|
|
194
|
+
*
|
|
195
|
+
* The channel bus contract and its config live in `./channel_bus` so that a
|
|
196
|
+
* transport shipped as its own package depends on the contract alone.
|
|
197
|
+
*/
|
|
192
198
|
export interface RealtimeChannelsConfig {
|
|
193
199
|
/**
|
|
194
200
|
* Retention rules, most specific first — the first match wins. Omitted or
|
|
195
201
|
* empty means no channel retains anything.
|
|
196
202
|
*/
|
|
197
203
|
channels?: ChannelRetentionRule[];
|
|
204
|
+
/**
|
|
205
|
+
* How channel broadcast and presence reach other backend instances.
|
|
206
|
+
* Defaults to `{ type: "memory" }` — i.e. they don't.
|
|
207
|
+
*/
|
|
208
|
+
bus?: ChannelBusSetting;
|
|
198
209
|
}
|
|
199
210
|
/**
|
|
200
211
|
* Abstract realtime provider interface.
|
|
@@ -582,6 +593,24 @@ export interface BackendBootstrapper {
|
|
|
582
593
|
* Return admin capabilities for this driver.
|
|
583
594
|
*/
|
|
584
595
|
getAdmin?(driverResult: InitializedDriver): DatabaseAdmin | undefined;
|
|
596
|
+
/**
|
|
597
|
+
* Bring the database's collection tables up to date, additively.
|
|
598
|
+
*
|
|
599
|
+
* Optional because it is only meaningful for schema-ful drivers. A managed
|
|
600
|
+
* runtime boots a compiled project against a database it has never seen; auth
|
|
601
|
+
* tables are ensured on boot but collection tables were created by nothing,
|
|
602
|
+
* so every data request answered 500 on a missing relation. The CLI's `db
|
|
603
|
+
* push` cannot fill the gap — it needs Atlas, and the runtime image ships no
|
|
604
|
+
* CLI.
|
|
605
|
+
*
|
|
606
|
+
* Implementations MUST be additive-only: create missing tables, columns and
|
|
607
|
+
* enum types, and never drop, narrow or rewrite anything. This runs
|
|
608
|
+
* unattended against live customer data with nobody reading a diff, so the
|
|
609
|
+
* destructive half stays a deliberate migration.
|
|
610
|
+
*/
|
|
611
|
+
ensureCollectionSchema?(collections: unknown[], driverResult: InitializedDriver, log?: (message: string) => void): Promise<{
|
|
612
|
+
applied: number;
|
|
613
|
+
}>;
|
|
585
614
|
/**
|
|
586
615
|
* Initialize WebSocket server for realtime operations.
|
|
587
616
|
*/
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cross-instance transport for channel broadcast and presence, and the
|
|
3
|
+
* contract anyone implementing one has to meet.
|
|
4
|
+
*
|
|
5
|
+
* These types live in `@rebasepro/types` rather than in the Postgres adapter on
|
|
6
|
+
* purpose: a transport package should depend on the contract, not on the
|
|
7
|
+
* database driver that happens to ship the default implementation. A
|
|
8
|
+
* `@rebasepro/channel-bus-<something>` package needs this file and nothing else.
|
|
9
|
+
*
|
|
10
|
+
* Why a transport exists at all: entity/collection realtime already spans
|
|
11
|
+
* instances (CDC, or per-mutation LISTEN/NOTIFY). Channel broadcast and presence
|
|
12
|
+
* did not — they fanned out from per-process maps, so two clients served by
|
|
13
|
+
* different replicas could not see each other, and nothing errored. The bus is
|
|
14
|
+
* the missing hop, and deliberately *only* that hop: which local clients receive
|
|
15
|
+
* a frame stays in the realtime service, so a transport never has to know what a
|
|
16
|
+
* subscription, a WebSocket or a presence roster is.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* A frame in flight between instances.
|
|
20
|
+
*
|
|
21
|
+
* `sid` identifies the publishing instance. The realtime service drops frames
|
|
22
|
+
* carrying its own `sid` on arrival — local fan-out already happened before the
|
|
23
|
+
* publish — so a transport that echoes a publisher's own messages back to it is
|
|
24
|
+
* still correct, merely wasteful.
|
|
25
|
+
*
|
|
26
|
+
* Keys are spelled out rather than abbreviated. The one shipped transport with a
|
|
27
|
+
* size limit has a pointer path for anything that would approach it, so shaving
|
|
28
|
+
* bytes off key names buys nothing worth the opacity.
|
|
29
|
+
*/
|
|
30
|
+
export type ChannelBusFrame =
|
|
31
|
+
/** A broadcast carrying its payload. */
|
|
32
|
+
{
|
|
33
|
+
kind: "broadcast";
|
|
34
|
+
sid: string;
|
|
35
|
+
channel: string;
|
|
36
|
+
event: string;
|
|
37
|
+
/** Originating client, echoed so receivers can skip it if it is theirs. */
|
|
38
|
+
from?: string;
|
|
39
|
+
/** Sequence number, present only on retained channels. */
|
|
40
|
+
seq?: number;
|
|
41
|
+
payload: unknown;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A broadcast too large for the transport to carry inline: the body is
|
|
45
|
+
* already durable in `rebase.channel_messages`, so the frame carries only
|
|
46
|
+
* its address and each receiver reads it back. Only ever emitted for
|
|
47
|
+
* retained channels, and only by a transport with a finite
|
|
48
|
+
* {@link ChannelBus.maxFrameBytes}.
|
|
49
|
+
*/
|
|
50
|
+
| {
|
|
51
|
+
kind: "broadcast_ref";
|
|
52
|
+
sid: string;
|
|
53
|
+
channel: string;
|
|
54
|
+
from?: string;
|
|
55
|
+
seq: number;
|
|
56
|
+
}
|
|
57
|
+
/** A presence join/leave/update, small by construction. */
|
|
58
|
+
| {
|
|
59
|
+
kind: "presence_diff";
|
|
60
|
+
sid: string;
|
|
61
|
+
channel: string;
|
|
62
|
+
joins: Record<string, Record<string, unknown>>;
|
|
63
|
+
leaves: Record<string, Record<string, unknown>>;
|
|
64
|
+
};
|
|
65
|
+
/** Receives frames published by *other* instances. */
|
|
66
|
+
export type ChannelBusHandler = (frame: ChannelBusFrame) => void | Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* A cross-instance transport.
|
|
69
|
+
*
|
|
70
|
+
* ## What an implementation must guarantee
|
|
71
|
+
*
|
|
72
|
+
* - **`start()` rejects if the transport is unusable.** The caller falls back to
|
|
73
|
+
* in-process delivery when it does. Resolving while disconnected produces a
|
|
74
|
+
* cluster that believes it is connected and silently is not, which is the
|
|
75
|
+
* exact failure this whole mechanism exists to remove.
|
|
76
|
+
* - **`publish()` reaches every *other* instance, or rejects.** Delivery back to
|
|
77
|
+
* the publisher is permitted but pointless (see {@link ChannelBusFrame.sid}).
|
|
78
|
+
* - **`stop()` is idempotent** and releases everything, including anything
|
|
79
|
+
* holding the event loop open.
|
|
80
|
+
* - **A malformed message never throws out of the transport.** Parsing happens
|
|
81
|
+
* inside the implementation; drop and log what you cannot understand, so one
|
|
82
|
+
* bad frame cannot take the listener down.
|
|
83
|
+
*
|
|
84
|
+
* ## What it does *not* have to guarantee
|
|
85
|
+
*
|
|
86
|
+
* - **Ordering.** Retained channels carry `seq`, and the client SDK orders by
|
|
87
|
+
* it. Unsequenced broadcasts are cursor-grade traffic where order is not
|
|
88
|
+
* meaningful.
|
|
89
|
+
* - **Durability.** A frame lost in transit is a missed live update; retained
|
|
90
|
+
* channels repair themselves through the client's `channel_history` replay.
|
|
91
|
+
* - **Exactly-once.** Duplicates are tolerated — retained frames are deduped by
|
|
92
|
+
* `seq`, and presence diffs are idempotent by construction.
|
|
93
|
+
*/
|
|
94
|
+
export interface ChannelBus {
|
|
95
|
+
/**
|
|
96
|
+
* Identifies the transport in logs and in `getChannelBusKind()`. Use your
|
|
97
|
+
* own name; the framework only compares against `"memory"` to decide
|
|
98
|
+
* whether publishing is worth attempting at all.
|
|
99
|
+
*/
|
|
100
|
+
readonly kind: string;
|
|
101
|
+
/**
|
|
102
|
+
* Largest frame this transport will carry, in bytes of encoded JSON, or
|
|
103
|
+
* `Infinity` when there is no meaningful ceiling.
|
|
104
|
+
*
|
|
105
|
+
* A broadcast that exceeds it is published as a `broadcast_ref` pointer when
|
|
106
|
+
* the channel is retained, and refused with an error to the sender when it
|
|
107
|
+
* is not. Implementations with no limit should return `Infinity` rather than
|
|
108
|
+
* a large number, so the pointer path is never taken needlessly.
|
|
109
|
+
*/
|
|
110
|
+
readonly maxFrameBytes: number;
|
|
111
|
+
/** Connect and begin delivering remote frames to `handler`. */
|
|
112
|
+
start(handler: ChannelBusHandler): Promise<void>;
|
|
113
|
+
/** Publish a frame to the other instances. */
|
|
114
|
+
publish(frame: ChannelBusFrame): Promise<void>;
|
|
115
|
+
/** Disconnect and release resources. Idempotent. */
|
|
116
|
+
stop(): Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Which transport to use, for the two that ship with the Postgres adapter.
|
|
120
|
+
*
|
|
121
|
+
* To use one that does not ship here — a Redis package, or your own class —
|
|
122
|
+
* pass the {@link ChannelBus} instance itself instead of a config object.
|
|
123
|
+
*
|
|
124
|
+
* There are deliberately only two built in, and neither adds a service to a
|
|
125
|
+
* deployment. Rebase deploys as Postgres + backend + frontend; a bus that
|
|
126
|
+
* required a message broker would put a second stateful service into every
|
|
127
|
+
* `docker-compose.yml` the CLI scaffolds, for a feature most applications never
|
|
128
|
+
* use. Measured across two backend instances against one Postgres container,
|
|
129
|
+
* the Postgres bus carried ~10k cross-instance messages/second with no losses,
|
|
130
|
+
* and stayed flat out to eight instances — comfortably past what live-cursor
|
|
131
|
+
* collaboration generates. The extension point below is the answer for anyone
|
|
132
|
+
* who does outgrow it.
|
|
133
|
+
*/
|
|
134
|
+
export type ChannelBusConfig =
|
|
135
|
+
/**
|
|
136
|
+
* In-process only — the historical behaviour. Broadcast and presence reach
|
|
137
|
+
* the clients connected to *this* instance and no further.
|
|
138
|
+
*/
|
|
139
|
+
{
|
|
140
|
+
type: "memory";
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Postgres LISTEN/NOTIFY, reusing infrastructure the deployment already has.
|
|
144
|
+
*
|
|
145
|
+
* `pg_notify` caps a payload at 8000 bytes, so a broadcast larger than that
|
|
146
|
+
* is delivered cross-instance only on a *retained* channel, where the
|
|
147
|
+
* notification carries a pointer (`seq`) instead of the message and each
|
|
148
|
+
* receiver reads the body back from `rebase.channel_messages`. An oversized
|
|
149
|
+
* broadcast on an ephemeral channel is refused rather than silently
|
|
150
|
+
* delivered to half the cluster.
|
|
151
|
+
*
|
|
152
|
+
* NOTE: `LISTEN` needs a session-mode connection. Behind PgBouncer in
|
|
153
|
+
* transaction mode this must point at the database directly
|
|
154
|
+
* (`DATABASE_DIRECT_URL`), not at the pooler.
|
|
155
|
+
*/
|
|
156
|
+
| {
|
|
157
|
+
type: "postgres";
|
|
158
|
+
/** Direct connection for the LISTEN client. Defaults to `DATABASE_DIRECT_URL`. */
|
|
159
|
+
connectionString?: string;
|
|
160
|
+
/**
|
|
161
|
+
* How long to coalesce outgoing frames into a single notification, in
|
|
162
|
+
* milliseconds. Defaults to 10.
|
|
163
|
+
*
|
|
164
|
+
* A notify is a query on your primary database, so under load this is
|
|
165
|
+
* the difference between one query per message and one per window. The
|
|
166
|
+
* window is leading-edge: a frame arriving when none is open goes out
|
|
167
|
+
* immediately, so an idle channel pays no added latency and only a
|
|
168
|
+
* sustained stream is batched.
|
|
169
|
+
*
|
|
170
|
+
* Set to 0 to disable coalescing and send every frame on its own.
|
|
171
|
+
*/
|
|
172
|
+
batchWindowMs?: number;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* What `realtime.bus` accepts: a built-in transport by name, or any
|
|
176
|
+
* {@link ChannelBus} instance.
|
|
177
|
+
*
|
|
178
|
+
* ```typescript
|
|
179
|
+
* realtime: { bus: { type: "postgres" } } // shipped
|
|
180
|
+
* realtime: { bus: new MyRedisChannelBus(url) } // a separate package, or your own
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
export type ChannelBusSetting = ChannelBusConfig | ChannelBus;
|
|
184
|
+
/**
|
|
185
|
+
* Whether `setting` is an already-constructed transport rather than a request
|
|
186
|
+
* for a built-in one.
|
|
187
|
+
*
|
|
188
|
+
* Structural rather than nominal so that an instance from a *different copy* of
|
|
189
|
+
* `@rebasepro/types` — an entirely normal outcome of a separately versioned
|
|
190
|
+
* transport package — is still recognised.
|
|
191
|
+
*/
|
|
192
|
+
export declare function isChannelBusInstance(setting: ChannelBusSetting | undefined): setting is ChannelBus;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CollectionConfig } from "./collections";
|
|
2
|
+
/**
|
|
3
|
+
* Serializing collections so they survive a network hop.
|
|
4
|
+
*
|
|
5
|
+
* A collection definition is not plain data. Relations point at their target
|
|
6
|
+
* with a *function* (`target: () => usersCollection`) so two collections can
|
|
7
|
+
* reference each other without an import cycle, and collections also carry
|
|
8
|
+
* callbacks, custom views and component references. `JSON.stringify` silently
|
|
9
|
+
* drops every one of those, which matters because the SDK generator *calls*
|
|
10
|
+
* `relation.target()` to decide whether a foreign key is a string or a number.
|
|
11
|
+
* Serialize naively and remote SDK generation produces subtly wrong types
|
|
12
|
+
* instead of failing — the worst possible outcome.
|
|
13
|
+
*
|
|
14
|
+
* So relation targets are resolved to a slug reference on the way out and
|
|
15
|
+
* rebuilt into functions on the way in. Everything else that cannot cross a wire
|
|
16
|
+
* is dropped deliberately: an SDK is generated from the *shape* of the data, and
|
|
17
|
+
* server-side behaviour is neither useful to a client nor safe to publish.
|
|
18
|
+
*/
|
|
19
|
+
/** Marker replacing a relation's `target` function in serialized form. */
|
|
20
|
+
export interface SerializedCollectionRef {
|
|
21
|
+
__collectionRef: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function isSerializedCollectionRef(value: unknown): value is SerializedCollectionRef;
|
|
24
|
+
/**
|
|
25
|
+
* Serialize collections for transport over the contract endpoint.
|
|
26
|
+
*
|
|
27
|
+
* Sorted by slug so the output — and therefore the schema hash computed from it
|
|
28
|
+
* — does not depend on filesystem ordering.
|
|
29
|
+
*/
|
|
30
|
+
export declare function serializeCollections(collections: CollectionConfig[]): unknown[];
|
|
31
|
+
/**
|
|
32
|
+
* Rebuild collections received from a contract endpoint.
|
|
33
|
+
*
|
|
34
|
+
* Relation refs become real thunks resolving through the returned set, so
|
|
35
|
+
* downstream consumers — the SDK generator above all — see exactly the shape
|
|
36
|
+
* they would have seen had the collections been imported from source.
|
|
37
|
+
*
|
|
38
|
+
* A ref naming a collection that is not in the payload resolves to `undefined`
|
|
39
|
+
* rather than throwing: the generator already tolerates an unresolvable target
|
|
40
|
+
* by falling back to a permissive key type, and a partial contract should still
|
|
41
|
+
* produce a usable SDK.
|
|
42
|
+
*/
|
|
43
|
+
export declare function deserializeCollections(payload: unknown[]): CollectionConfig[];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./modify_collections";
|
|
|
14
14
|
export * from "./formex";
|
|
15
15
|
export * from "./websockets";
|
|
16
16
|
export * from "./backend";
|
|
17
|
+
export * from "./channel_bus";
|
|
17
18
|
export * from "./translations";
|
|
18
19
|
export * from "./plugins";
|
|
19
20
|
export * from "./builders";
|
|
@@ -31,3 +32,7 @@ export * from "./database_adapter";
|
|
|
31
32
|
export * from "./breadcrumbs";
|
|
32
33
|
export * from "./component_overrides";
|
|
33
34
|
export * from "./api_keys";
|
|
35
|
+
export * from "./project_manifest";
|
|
36
|
+
export * from "./collection_contract";
|
|
37
|
+
export * from "./schema_version";
|
|
38
|
+
export * from "./storage_authorize";
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project manifest (`rebase.json`) and the build artifacts derived from it.
|
|
3
|
+
*
|
|
4
|
+
* Three separate documents live in this file, and keeping them distinct matters:
|
|
5
|
+
*
|
|
6
|
+
* 1. {@link RebaseProjectManifest} — `rebase.json`. **Authored** by the developer,
|
|
7
|
+
* committed to the repository. Declares topology only: which runtime major the
|
|
8
|
+
* project targets, and which apps *this repository* contributes to the project.
|
|
9
|
+
* Schema, security rules, hooks and functions stay in TypeScript under the
|
|
10
|
+
* config package — nothing that needs a type system belongs here.
|
|
11
|
+
*
|
|
12
|
+
* 2. {@link RebaseProjectLink} — the per-checkout link (`.rebase/cloud.json`).
|
|
13
|
+
* **Not committed**, because it is per-developer like a git remote. Says which
|
|
14
|
+
* deployed project this working copy points at, whether that is a Rebase Cloud
|
|
15
|
+
* project or the base URL of a self-hosted backend.
|
|
16
|
+
*
|
|
17
|
+
* 3. {@link RebaseBundleManifest} — `manifest.json` inside a built bundle.
|
|
18
|
+
* **Generated**, never hand-edited. It is the lockfile analogue: the exact
|
|
19
|
+
* contract a built artifact claims to satisfy, which the runtime validates
|
|
20
|
+
* before it boots and a control plane validates before it deploys.
|
|
21
|
+
*
|
|
22
|
+
* A repository declares only the apps it contains. The set of apps belonging to a
|
|
23
|
+
* project is held by the project itself, which is what makes multi-repo projects
|
|
24
|
+
* work: two repositories never need to know about each other, only about the
|
|
25
|
+
* project.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Which kind of thing an app is.
|
|
29
|
+
*
|
|
30
|
+
* - `backend` — the collections/hooks/functions that define the project's API.
|
|
31
|
+
* Exactly one per *project* (not per repository); the registry enforces it.
|
|
32
|
+
* - `static` — a pre-built client bundle (SPA, static site) served over CDN.
|
|
33
|
+
* - `admin` — the Rebase admin panel, either hosted by the platform or built
|
|
34
|
+
* into this repository.
|
|
35
|
+
* - `mobile` — a native app. Registration only: it gets client credentials and
|
|
36
|
+
* configuration, and is never built or hosted here.
|
|
37
|
+
* - `custom` — an arbitrary container image built from a Dockerfile. The eject
|
|
38
|
+
* hatch: full control, no managed-runtime guarantees.
|
|
39
|
+
*/
|
|
40
|
+
export type RebaseAppType = "backend" | "static" | "admin" | "mobile" | "custom";
|
|
41
|
+
/**
|
|
42
|
+
* The backend app: the project's API surface.
|
|
43
|
+
*
|
|
44
|
+
* Paths are relative to the directory holding `rebase.json`. The defaults match
|
|
45
|
+
* the layout `rebase init` scaffolds, so a stock project may declare simply
|
|
46
|
+
* `{ "type": "backend" }`.
|
|
47
|
+
*/
|
|
48
|
+
export interface RebaseBackendAppConfig {
|
|
49
|
+
type: "backend";
|
|
50
|
+
/** Directory of the config package (collections + index). Default `config`. */
|
|
51
|
+
config?: string;
|
|
52
|
+
/** Directory of server functions. Default `backend/functions`. */
|
|
53
|
+
functions?: string;
|
|
54
|
+
/** Directory of cron job definitions. Default `backend/crons` when present. */
|
|
55
|
+
crons?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Path to the generated Drizzle schema module (tables/enums/relations).
|
|
58
|
+
* Default `backend/src/schema.generated.ts`.
|
|
59
|
+
*/
|
|
60
|
+
schema?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Which collections source the runtime uses.
|
|
63
|
+
*
|
|
64
|
+
* - `cms` (default) — collections come from the config package.
|
|
65
|
+
* - `baas` — collections are introspected from the live database at boot and
|
|
66
|
+
* the config package is not required.
|
|
67
|
+
*/
|
|
68
|
+
mode?: "cms" | "baas";
|
|
69
|
+
/**
|
|
70
|
+
* Module path (relative to `config`) exporting the auth users collection as
|
|
71
|
+
* its default export. Default `collections/users`.
|
|
72
|
+
*/
|
|
73
|
+
usersCollection?: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A static client bundle — SPA or static site — built here and served from CDN.
|
|
77
|
+
*/
|
|
78
|
+
export interface RebaseStaticAppConfig {
|
|
79
|
+
type: "static";
|
|
80
|
+
/** Package directory containing the client sources. */
|
|
81
|
+
root: string;
|
|
82
|
+
/** Command that produces `output`. Run from the repository root. */
|
|
83
|
+
build?: string;
|
|
84
|
+
/** Directory of built assets, relative to the repository root. */
|
|
85
|
+
output: string;
|
|
86
|
+
/**
|
|
87
|
+
* Serve `index.html` for unmatched paths (client-side routing).
|
|
88
|
+
* Default `true` — the overwhelmingly common case for a client app, and a
|
|
89
|
+
* static *site* generator emits real files for its routes anyway.
|
|
90
|
+
*/
|
|
91
|
+
spa?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The admin panel.
|
|
95
|
+
*
|
|
96
|
+
* `hosted` is the default and means the platform serves it — nothing is built
|
|
97
|
+
* into this repository and nothing ships in the bundle. `bundled` builds it here,
|
|
98
|
+
* which is what a self-hosted or air-gapped deployment wants.
|
|
99
|
+
*/
|
|
100
|
+
export interface RebaseAdminAppConfig {
|
|
101
|
+
type: "admin";
|
|
102
|
+
mode?: "hosted" | "bundled";
|
|
103
|
+
/** Only for `bundled`: package directory containing the admin sources. */
|
|
104
|
+
root?: string;
|
|
105
|
+
/** Only for `bundled`: build command. */
|
|
106
|
+
build?: string;
|
|
107
|
+
/** Only for `bundled`: directory of built assets. */
|
|
108
|
+
output?: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* A native app. Registered for credentials and configuration; never built here.
|
|
112
|
+
*/
|
|
113
|
+
export interface RebaseMobileAppConfig {
|
|
114
|
+
type: "mobile";
|
|
115
|
+
platform: "ios" | "android" | "other";
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* An app built from a Dockerfile into an arbitrary image.
|
|
119
|
+
*
|
|
120
|
+
* This is the deliberate escape hatch. A project containing one is not eligible
|
|
121
|
+
* for the managed runtime — the platform cannot make guarantees about an image
|
|
122
|
+
* it did not build — but it still deploys, and nothing else about the project
|
|
123
|
+
* changes.
|
|
124
|
+
*/
|
|
125
|
+
export interface RebaseCustomAppConfig {
|
|
126
|
+
type: "custom";
|
|
127
|
+
/** Dockerfile path relative to the repository root. */
|
|
128
|
+
dockerfile?: string;
|
|
129
|
+
/** Build context relative to the repository root. Default `.`. */
|
|
130
|
+
context?: string;
|
|
131
|
+
/** Port the container listens on. Default 8080. */
|
|
132
|
+
port?: number;
|
|
133
|
+
}
|
|
134
|
+
export type RebaseAppConfig = RebaseBackendAppConfig | RebaseStaticAppConfig | RebaseAdminAppConfig | RebaseMobileAppConfig | RebaseCustomAppConfig;
|
|
135
|
+
/**
|
|
136
|
+
* `rebase.json` — the authored project manifest.
|
|
137
|
+
*/
|
|
138
|
+
export interface RebaseProjectManifest {
|
|
139
|
+
/** JSON Schema URL, for editor completion. Ignored by the tooling. */
|
|
140
|
+
$schema?: string;
|
|
141
|
+
/**
|
|
142
|
+
* The runtime **major** this project targets, as a semver range
|
|
143
|
+
* (e.g. `^1`, `~1.4`, or an exact `1.4.2` to pin).
|
|
144
|
+
*
|
|
145
|
+
* The platform upgrades patches and minors underneath a project without
|
|
146
|
+
* asking; it never crosses a major. See {@link RUNTIME_CONTRACT_VERSION}.
|
|
147
|
+
*/
|
|
148
|
+
runtime: string;
|
|
149
|
+
/**
|
|
150
|
+
* Apps this repository contributes, keyed by app name. The key is the app's
|
|
151
|
+
* identity within the project: it is what `rebase deploy <app>` names, what
|
|
152
|
+
* client credentials are issued against, and what a second repository must
|
|
153
|
+
* not collide with.
|
|
154
|
+
*/
|
|
155
|
+
apps: Record<string, RebaseAppConfig>;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The per-checkout project link.
|
|
159
|
+
*
|
|
160
|
+
* Deliberately separate from `rebase.json`: the manifest is committed and shared,
|
|
161
|
+
* while the link is per-developer. Keeping them in one file would mean either
|
|
162
|
+
* committing someone's project id or gitignoring the topology.
|
|
163
|
+
*/
|
|
164
|
+
export interface RebaseProjectLink {
|
|
165
|
+
/**
|
|
166
|
+
* A Rebase Cloud project id, or the base URL of any running Rebase backend
|
|
167
|
+
* (`https://api.example.com`). Both are first-class: every command that
|
|
168
|
+
* accepts a project reference accepts either, so a self-hosted project has
|
|
169
|
+
* the same tooling as a cloud one.
|
|
170
|
+
*/
|
|
171
|
+
project: string;
|
|
172
|
+
/** Organization slug. Cloud projects only. */
|
|
173
|
+
org?: string;
|
|
174
|
+
/** Explicit API base URL, when it differs from the project's default. */
|
|
175
|
+
apiUrl?: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Whether a project can run on the managed runtime, and if not, precisely why.
|
|
179
|
+
*
|
|
180
|
+
* The reasons are returned rather than summarised so tooling can print something
|
|
181
|
+
* a developer can act on. "Not eligible" is never a dead end — it selects the
|
|
182
|
+
* custom-runtime path, which still deploys.
|
|
183
|
+
*/
|
|
184
|
+
export interface ManagedCompatibility {
|
|
185
|
+
eligible: boolean;
|
|
186
|
+
reasons: string[];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Version of the bundle *format* itself.
|
|
190
|
+
*
|
|
191
|
+
* Bumped only when the on-disk layout changes in a way an older runtime could
|
|
192
|
+
* not read. A runtime accepts any bundle whose `bundleFormat` is less than or
|
|
193
|
+
* equal to its own — old bundles keep booting on new runtimes, which is the
|
|
194
|
+
* whole point of separating the artifact from the engine.
|
|
195
|
+
*/
|
|
196
|
+
export declare const BUNDLE_FORMAT_VERSION = 1;
|
|
197
|
+
/**
|
|
198
|
+
* The runtime contract major.
|
|
199
|
+
*
|
|
200
|
+
* Distinct from the `@rebasepro/server` package version: the package may release
|
|
201
|
+
* any number of minors and patches while this stays put. It changes only when
|
|
202
|
+
* the bundle/runtime contract breaks compatibility, and a project's
|
|
203
|
+
* `manifest.runtime` range is matched against *this*.
|
|
204
|
+
*/
|
|
205
|
+
export declare const RUNTIME_CONTRACT_VERSION = 1;
|
|
206
|
+
/** Where the runtime finds each part of the bundle. Paths are bundle-relative. */
|
|
207
|
+
export interface RebaseBundleEntrypoints {
|
|
208
|
+
/** Compiled config package directory (collections live under it). */
|
|
209
|
+
config?: string;
|
|
210
|
+
/** Compiled collections directory, when it differs from `<config>/collections`. */
|
|
211
|
+
collections?: string;
|
|
212
|
+
/** Compiled functions directory. */
|
|
213
|
+
functions?: string;
|
|
214
|
+
/** Compiled crons directory. */
|
|
215
|
+
crons?: string;
|
|
216
|
+
/** Compiled Drizzle schema module. */
|
|
217
|
+
schema?: string;
|
|
218
|
+
/** Module exporting the auth users collection (default export). */
|
|
219
|
+
usersCollection?: string;
|
|
220
|
+
/** Built admin assets, when the admin panel is bundled rather than hosted. */
|
|
221
|
+
admin?: string;
|
|
222
|
+
/** Built static assets to serve from the runtime, when not on a CDN. */
|
|
223
|
+
static?: string;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* A native module found in the dependency closure.
|
|
227
|
+
*
|
|
228
|
+
* Recorded rather than merely counted so a rejection can name the offending
|
|
229
|
+
* package instead of saying "something here is native".
|
|
230
|
+
*/
|
|
231
|
+
export interface NativeDependency {
|
|
232
|
+
name: string;
|
|
233
|
+
/** Why it was flagged — a `.node` binary, a gyp build, or an install script. */
|
|
234
|
+
reason: string;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* `manifest.json` — generated, and the document the runtime and control plane
|
|
238
|
+
* both validate against.
|
|
239
|
+
*/
|
|
240
|
+
export interface RebaseBundleManifest {
|
|
241
|
+
/** @see BUNDLE_FORMAT_VERSION */
|
|
242
|
+
bundleFormat: number;
|
|
243
|
+
runtime: {
|
|
244
|
+
/** The `runtime` range copied from `rebase.json`. */
|
|
245
|
+
range: string;
|
|
246
|
+
/** Exact `@rebasepro/server` version this bundle was built against. */
|
|
247
|
+
builtAgainst: string;
|
|
248
|
+
/** Runtime contract major this bundle requires. */
|
|
249
|
+
contract: number;
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Hash of the compiled collection definitions.
|
|
253
|
+
*
|
|
254
|
+
* This is the contract stamp. A generated SDK records the value it was built
|
|
255
|
+
* from, a client sends it back, and a mismatch is what lets the platform say
|
|
256
|
+
* "this app was built against an older schema" instead of failing mysteriously
|
|
257
|
+
* at the first request. It covers collections only — a hook edit does not
|
|
258
|
+
* change a client's contract, so it must not invalidate every SDK.
|
|
259
|
+
*/
|
|
260
|
+
schemaVersion: string;
|
|
261
|
+
/** Which app in `rebase.json` this bundle was built from. */
|
|
262
|
+
app: string;
|
|
263
|
+
/**
|
|
264
|
+
* What the runtime does with this bundle.
|
|
265
|
+
*
|
|
266
|
+
* - `cms` — a backend with declared collections; the runtime provisions their
|
|
267
|
+
* tables and serves the data API.
|
|
268
|
+
* - `baas` — a backend that introspects an existing database rather than
|
|
269
|
+
* declaring collections.
|
|
270
|
+
* - `static` — no backend at all: the bundle is a built SPA (`entry.static`),
|
|
271
|
+
* and the runtime only serves those assets. No database, no data sources —
|
|
272
|
+
* this is how a `static`/`admin` app runs on the same image as the backend.
|
|
273
|
+
*/
|
|
274
|
+
mode: "cms" | "baas" | "static";
|
|
275
|
+
entry: RebaseBundleEntrypoints;
|
|
276
|
+
/** Collection slugs contained in the bundle, for quick inspection. */
|
|
277
|
+
collections?: string[];
|
|
278
|
+
hooks: {
|
|
279
|
+
/**
|
|
280
|
+
* Whether the dependency closure contains native code.
|
|
281
|
+
*
|
|
282
|
+
* The managed runtime refuses these: a prebuilt binary cannot be run on
|
|
283
|
+
* an image the platform did not build it for, and the honest failure is
|
|
284
|
+
* at deploy time rather than at 3am in a crash loop.
|
|
285
|
+
*/
|
|
286
|
+
native: boolean;
|
|
287
|
+
nativeModules?: NativeDependency[];
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* What the bundle's config says about storage access control.
|
|
291
|
+
*
|
|
292
|
+
* Storage is not under RLS and its keys share one flat namespace, so a
|
|
293
|
+
* deployment with file storage enabled and no access model serves every
|
|
294
|
+
* user's files to every signed-in user. The runtime refuses to boot in that
|
|
295
|
+
* state — which, on a hosted platform that enables storage from the *console*
|
|
296
|
+
* rather than from the bundle, surfaces as a crash loop the developer cannot
|
|
297
|
+
* read.
|
|
298
|
+
*
|
|
299
|
+
* Recording it here lets a host reject the deploy with the reason instead.
|
|
300
|
+
* Absent on bundles built before this field existed.
|
|
301
|
+
*/
|
|
302
|
+
storage?: {
|
|
303
|
+
/** Whether the config package exports a `storageAuthorize` hook. */
|
|
304
|
+
authorize: boolean;
|
|
305
|
+
};
|
|
306
|
+
deps: {
|
|
307
|
+
/** Runtime dependencies of user code, as declared. */
|
|
308
|
+
declared: Record<string, string>;
|
|
309
|
+
};
|
|
310
|
+
build: {
|
|
311
|
+
/** `@rebasepro/cli` version that produced this bundle. */
|
|
312
|
+
cli: string;
|
|
313
|
+
/** Node major the bundle was compiled on. */
|
|
314
|
+
node: string;
|
|
315
|
+
/** ISO-8601. */
|
|
316
|
+
createdAt: string;
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/** The contract a running backend serves at `GET /api/meta/contract`. */
|
|
320
|
+
export interface RebaseProjectContract {
|
|
321
|
+
/** Matches {@link RebaseBundleManifest.schemaVersion}. */
|
|
322
|
+
schemaVersion: string;
|
|
323
|
+
runtime: {
|
|
324
|
+
/** `@rebasepro/server` version currently running. */
|
|
325
|
+
version: string;
|
|
326
|
+
contract: number;
|
|
327
|
+
};
|
|
328
|
+
mode: "cms" | "baas";
|
|
329
|
+
/** Full collection definitions, serialized — the input to SDK generation. */
|
|
330
|
+
collections: unknown[];
|
|
331
|
+
/** Collection slugs, for cheap inspection without parsing the definitions. */
|
|
332
|
+
collectionSlugs: string[];
|
|
333
|
+
generatedAt: string;
|
|
334
|
+
}
|
|
335
|
+
/** Header carrying the schema version an SDK was generated from. */
|
|
336
|
+
export declare const SCHEMA_VERSION_HEADER = "x-rebase-schema";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CollectionConfig } from "./collections";
|
|
2
|
+
/**
|
|
3
|
+
* Compute the canonical string a schema version hashes.
|
|
4
|
+
*
|
|
5
|
+
* Exposed separately so the hashing itself can differ by environment: Node has
|
|
6
|
+
* `crypto`, and callers without it can still compare canonical forms directly.
|
|
7
|
+
*/
|
|
8
|
+
export declare function canonicalSchemaPayload(collections: CollectionConfig[]): string;
|
|
9
|
+
/**
|
|
10
|
+
* A short, non-cryptographic digest of the canonical payload.
|
|
11
|
+
*
|
|
12
|
+
* FNV-1a style, 64 bits, as two 32-bit halves. This is an identity, not a
|
|
13
|
+
* security boundary: nothing trusts a schema version to prove anything, it only
|
|
14
|
+
* answers "is this the same schema as before". A hand-rolled hash keeps this
|
|
15
|
+
* module free of `node:crypto`, so the identical function runs in the browser,
|
|
16
|
+
* in the CLI, and in the runtime — which is the property that actually matters.
|
|
17
|
+
*/
|
|
18
|
+
export declare function computeSchemaVersion(collections: CollectionConfig[]): string;
|