@rebasepro/types 0.16.0 → 0.16.1-canary.g0d7af95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/call_context.d.ts +5 -5
  2. package/dist/controllers/auth_state.d.ts +1 -1
  3. package/dist/controllers/client.d.ts +22 -9
  4. package/dist/controllers/collection_registry.d.ts +2 -2
  5. package/dist/controllers/data.d.ts +4 -12
  6. package/dist/controllers/data_driver.d.ts +7 -7
  7. package/dist/controllers/email.d.ts +54 -2
  8. package/dist/controllers/index.d.ts +8 -9
  9. package/dist/controllers/storage.d.ts +4 -4
  10. package/dist/index.d.ts +5 -5
  11. package/dist/index.es.js +433 -3
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/types/admin_block.d.ts +1 -1
  14. package/dist/types/auth_adapter.d.ts +18 -9
  15. package/dist/types/backend.d.ts +23 -10
  16. package/dist/types/collection_contract.d.ts +1 -1
  17. package/dist/types/collections.d.ts +34 -9
  18. package/dist/types/component_ref.d.ts +3 -2
  19. package/dist/types/cron.d.ts +1 -25
  20. package/dist/types/data_source.d.ts +1 -1
  21. package/dist/types/database_adapter.d.ts +5 -5
  22. package/dist/types/entities.d.ts +1 -1
  23. package/dist/types/entity_callbacks.d.ts +4 -4
  24. package/dist/types/index.d.ts +33 -29
  25. package/dist/types/indexes.d.ts +179 -0
  26. package/dist/types/project_manifest.d.ts +132 -27
  27. package/dist/types/properties.d.ts +58 -6
  28. package/dist/types/relations.d.ts +1 -1
  29. package/dist/types/resource_kinds.d.ts +189 -0
  30. package/dist/types/resources.d.ts +197 -0
  31. package/dist/types/schema_editing.d.ts +127 -0
  32. package/dist/types/schema_version.d.ts +1 -1
  33. package/dist/types/security_rules.d.ts +1 -1
  34. package/dist/types/storage_source.d.ts +27 -0
  35. package/dist/users/index.d.ts +1 -1
  36. package/package.json +2 -2
  37. package/src/controllers/client.ts +14 -1
  38. package/src/controllers/data.ts +0 -9
  39. package/src/controllers/email.ts +55 -2
  40. package/src/controllers/index.ts +0 -1
  41. package/src/controllers/storage.ts +4 -4
  42. package/src/types/admin_block.ts +1 -2
  43. package/src/types/auth_adapter.ts +18 -10
  44. package/src/types/backend.ts +18 -1
  45. package/src/types/collections.ts +28 -2
  46. package/src/types/component_ref.ts +3 -2
  47. package/src/types/cron.ts +0 -24
  48. package/src/types/index.ts +4 -0
  49. package/src/types/indexes.ts +180 -0
  50. package/src/types/project_manifest.ts +139 -26
  51. package/src/types/properties.ts +54 -0
  52. package/src/types/resource_kinds.ts +324 -0
  53. package/src/types/resources.ts +368 -0
  54. package/src/types/schema_editing.ts +154 -0
  55. package/src/types/storage_source.ts +28 -0
  56. package/dist/controllers/database_admin.d.ts +0 -11
  57. package/src/controllers/database_admin.ts +0 -22
@@ -3,11 +3,13 @@ import type { CollectionCallbacks } from "./entity_callbacks";
3
3
  import type { EnumValues, Properties, PostgresProperties, FirebaseProperties, MongoProperties } from "./properties";
4
4
 
5
5
  import type { User } from "../users";
6
+ import type { EmailSendResult } from "../controllers/email";
6
7
  import type { Relation } from "./relations";
7
8
  import type { SecurityRule } from "./security_rules";
8
9
  import { getDataSourceCapabilities } from "./data_source";
9
10
  import type { WhereFilterOp, FilterValues, FilterPreset } from "./filter-operators";
10
11
  import type { SearchConfig } from "./search";
12
+ import type { CollectionIndex } from "./indexes";
11
13
 
12
14
  /**
13
15
  * Base interface containing all driver-agnostic collection properties.
@@ -322,6 +324,24 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
322
324
  * @see SearchConfig
323
325
  */
324
326
  search?: SearchConfig;
327
+
328
+ /**
329
+ * Ordinary indexes on this collection's table.
330
+ *
331
+ * Collection-level, not per-property, because an index over two columns
332
+ * has no single property to hang on and a partial index has none at all —
333
+ * and because a second declaration site for the single-column case would
334
+ * put the same object in two places. An index's identity is a column list
335
+ * in an order; the single-column case is a degenerate one, not a special
336
+ * one.
337
+ *
338
+ * `VectorProperty.index` stays where it is: an ANN structure is a property
339
+ * of the column's type, not of a query.
340
+ *
341
+ * Postgres-only, like {@link SearchConfig}: refused on another engine
342
+ * rather than silently ignored.
343
+ */
344
+ indexes?: readonly CollectionIndex<Extract<keyof M, string>>[];
325
345
  }
326
346
 
327
347
  /**
@@ -702,8 +722,14 @@ export interface AuthCollectionConfig {
702
722
  export interface AuthCollectionContext {
703
723
  /** Hash a password using the configured algorithm (scrypt by default). */
704
724
  hashPassword: (password: string) => Promise<string>;
705
- /** Send an email. Only available when email service is configured. */
706
- sendEmail?: (options: { to: string; subject: string; html: string; text?: string }) => Promise<void>;
725
+ /**
726
+ * Send an email. Only available when email service is configured.
727
+ *
728
+ * Resolves with what the provider reported — the assigned Message-ID, most
729
+ * usefully — so a hook that sends a message can store the id and later
730
+ * thread a reply back to it. Callers that do not care may ignore it.
731
+ */
732
+ sendEmail?: (options: { to: string; subject: string; html: string; text?: string }) => Promise<EmailSendResult>;
707
733
  /** Whether the email service is configured and available. */
708
734
  emailConfigured: boolean;
709
735
  /** The app name from email config (for templates). */
@@ -2,8 +2,9 @@
2
2
  * How a collection points at a UI component without the backend learning about React.
3
3
  *
4
4
  * This file is the hinge the BaaS/admin split turns on. `ComponentRef` is named
5
- * by `properties.ts` (`ui.Field`, `ui.Preview`, `ui.Filter`), and `properties.ts`
6
- * must stay in the React-free core because every backend subsystem — validation,
5
+ * by a property's `admin` block (`admin.Field`, `admin.Preview`, `admin.Filter`)
6
+ * and imported by `properties.ts`, which must stay in the React-free core
7
+ * because every backend subsystem — validation,
7
8
  * the drizzle schema generator, the OpenAPI generator, the SDK codegen — reads
8
9
  * property definitions. If `ComponentRef` needed `React.ComponentType`, the whole
9
10
  * property model would have to move to the admin layer with it.
package/src/types/cron.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { RebaseServerClient } from "../controllers/client";
2
- import type { RebaseSdkData } from "../controllers/data";
3
2
 
4
3
  /**
5
4
  * Cron Job type definitions for Rebase.
@@ -126,29 +125,6 @@ export interface CronJobContext {
126
125
  */
127
126
  rebase: RebaseServerClient;
128
127
 
129
- /**
130
- * The same object as {@link rebase}, under the name this context used
131
- * before.
132
- *
133
- * @deprecated Use `rebase` instead. Two things made the old name a problem,
134
- * and neither was cosmetic. It contradicted every other server surface,
135
- * where the singleton is `rebase` — the previous docstring had to end with
136
- * *"it is only named `client` here"*. And typing it as `RebaseClient`
137
- * re-exposed `client.data`, the alias that {@link RebaseServerClient}
138
- * deliberately `Omit`s so the RLS-bypassing plane has exactly one name and
139
- * the privilege is visible at the call site. A reader who learned
140
- * `client.data` here carried it to a collection callback, where
141
- * `context.data` is the *user-scoped* plane — same spelling, opposite
142
- * privilege.
143
- *
144
- * Still the full server client at runtime, and `data` still resolves, so
145
- * existing cron files keep working and keep compiling. It will be removed
146
- * in the next major.
147
- */
148
- client: RebaseServerClient & {
149
- /** @deprecated Use `rebase.dataAsAdmin` — the name states the privilege. */
150
- data: RebaseSdkData;
151
- };
152
128
  }
153
129
 
154
130
  // =============================================================================
@@ -6,6 +6,7 @@ export * from "./properties";
6
6
  export * from "./admin_block";
7
7
  export * from "./collections";
8
8
  export * from "./search";
9
+ export * from "./indexes";
9
10
  export * from "./relations";
10
11
  export * from "./policy";
11
12
  export * from "./rls-functions";
@@ -14,8 +15,11 @@ export * from "./security_rules";
14
15
  export * from "./entity_callbacks";
15
16
  export * from "./websockets";
16
17
  export * from "./backend";
18
+ export * from "./schema_editing";
17
19
  export * from "./channel_bus";
18
20
  export * from "./data_source";
21
+ export * from "./resources";
22
+ export * from "./resource_kinds";
19
23
  export * from "./storage_source";
20
24
  export * from "./cron";
21
25
  export * from "./backup";
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Ordinary indexes, declared on a collection.
3
+ *
4
+ * Distinct from the two index-shaped things Rebase already builds. A `search`
5
+ * block builds a GIN index over a generated `tsvector`, and a `vector`
6
+ * property builds an ANN index over an embedding; both are structures the
7
+ * *feature* owns and neither is a query the developer wrote. This is the plain
8
+ * case — the btree behind a `where` clause — which had no declaration site at
9
+ * all, so the only way to have one was to write it by hand, where the next
10
+ * `rebase db push` planned it away.
11
+ *
12
+ * Every form here is core Postgres, deliberately. See {@link CollectionIndex}.
13
+ */
14
+
15
+ /**
16
+ * A key column of an index whose access method has no ordering.
17
+ *
18
+ * `gin` and `brin` reject `ASC`/`DESC`/`NULLS` outright — Postgres answers
19
+ * `access method "gin" does not support ASC/DESC options` — so those methods
20
+ * take this narrower shape and the combination is unrepresentable rather than
21
+ * refused at build time.
22
+ */
23
+ export interface UnorderedIndexKey<Keys extends string = string> {
24
+ /**
25
+ * A property key on this collection — never a column name.
26
+ *
27
+ * Which column that resolves to depends on the property, and the two
28
+ * differ in exactly the case an index is most often wanted for: a
29
+ * `belongsTo` relation compiles to its resolved `localKey`
30
+ * (`primaryCategory` → `primary_category_id`), not to the snake-cased
31
+ * property key. Anything else resolves through `columnName`, or the
32
+ * snake-case default when it declares none.
33
+ *
34
+ * Writing the column name here would work for most properties and quietly
35
+ * index nothing for a foreign key, which is the one people reach for.
36
+ */
37
+ prop: Keys | (string & {});
38
+ }
39
+
40
+ /**
41
+ * A key column of an index, when its order matters.
42
+ *
43
+ * `direction` and `nulls` earn their place only when a query's `ORDER BY`
44
+ * mixes directions. A lone `DESC` index is redundant with its `ASC` twin —
45
+ * Postgres scans a btree backwards just as fast — and declaring both is
46
+ * refused.
47
+ *
48
+ * Writing the Postgres default down explicitly is free: the derived name
49
+ * hashes the *effective* order, so adding `direction: "asc"` to a column that
50
+ * was already ascending is not a redefinition and rebuilds nothing.
51
+ */
52
+ export interface IndexKey<Keys extends string = string> extends UnorderedIndexKey<Keys> {
53
+ direction?: "asc" | "desc";
54
+ /** Postgres's own default: `last` under `asc`, `first` under `desc`. */
55
+ nulls?: "first" | "last";
56
+ }
57
+
58
+ /**
59
+ * The rows a partial index covers.
60
+ *
61
+ * Structure rather than a SQL string, and this is the most load-bearing choice
62
+ * in the type. A string would be replayed verbatim by Atlas in a scratch
63
+ * database, would be the one place a caller reaches for an extension operator
64
+ * class or a subquery, could not be checked against the collection's
65
+ * properties, and could not be fingerprinted — its own text would have to go
66
+ * into the derived name, so reformatting it would rename a live index.
67
+ *
68
+ * Structure keeps every reference resolvable at build time, keeps literals
69
+ * going through the same quoting as the rest of the DDL, and keeps the name
70
+ * stable under any rendering change.
71
+ *
72
+ * There is no `or`. An OR predicate almost always means the index should not
73
+ * be partial at all; a caller who genuinely needs one declares two indexes.
74
+ */
75
+ export type IndexPredicate<Keys extends string = string> =
76
+ | { prop: Keys | (string & {}); op: "="; value: string | number | boolean }
77
+ | { prop: Keys | (string & {}); op: "!=" | "<" | "<=" | ">" | ">="; value: string | number }
78
+ | { prop: Keys | (string & {}); op: "is null" | "is not null" }
79
+ /**
80
+ * A non-empty list, enforced in the type. An empty `IN` is a predicate
81
+ * matching nothing: it builds an index over zero rows and reports success,
82
+ * which is the silent-empty-condition shape this codebase has been bitten
83
+ * by before.
84
+ */
85
+ | { prop: Keys | (string & {}); op: "in"; value: readonly [string | number, ...(string | number)[]] }
86
+ | { and: readonly [IndexPredicate<Keys>, ...IndexPredicate<Keys>[]] };
87
+
88
+ interface BaseCollectionIndex<Keys extends string = string> {
89
+ /**
90
+ * The key columns, in order. This *is* the index's identity.
91
+ *
92
+ * Postgres can only use a leading subset, so `["ownerId", "createdAt"]`
93
+ * serves a query filtering on `ownerId`, and one filtering on both, and
94
+ * never one filtering on `createdAt` alone.
95
+ *
96
+ * Capped at five keys. Postgres allows thirty-two; past four the trailing
97
+ * columns are dead weight on every write, and the declaration is usually
98
+ * someone hoping a query gets faster by accretion. Payload columns that
99
+ * are not searched belong in `include`, which does not count against this.
100
+ */
101
+ on: readonly [Keys | IndexKey<Keys>, ...(Keys | IndexKey<Keys>)[]];
102
+
103
+ where?: IndexPredicate<Keys>;
104
+
105
+ /**
106
+ * Why this index exists, in one line. Required, and the only required
107
+ * field carrying no SQL.
108
+ *
109
+ * An index is the only thing a Rebase config can declare that costs money
110
+ * forever and whose benefit is invisible from the config. `rebase doctor`
111
+ * prints this beside "0 scans in 34 days, 412 MB", which is the one moment
112
+ * anyone is in a position to decide whether to delete it. Without it
113
+ * nobody can decide, so nobody does, and the table accretes indexes for
114
+ * the life of the product.
115
+ */
116
+ reason: string;
117
+ }
118
+
119
+ /**
120
+ * The default. Answers equality, range, `ORDER BY`, and uniqueness.
121
+ */
122
+ export interface BtreeIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
123
+ using?: "btree";
124
+
125
+ /**
126
+ * A composite uniqueness guarantee.
127
+ *
128
+ * Single-column uniqueness is `validation.unique` on the property, and
129
+ * declaring it here is refused rather than accepted as a synonym.
130
+ * `validation.unique` compiles to an inline `UNIQUE` whose backing index
131
+ * Postgres — not Rebase — names `<table>_<column>_key`. That name is in
132
+ * every deployed database, appears in no contract file, and no release can
133
+ * reach in and rename it.
134
+ */
135
+ unique?: boolean;
136
+
137
+ /**
138
+ * Payload columns carried in the leaf pages, for index-only scans. Not
139
+ * searchable and not ordered — they save a heap fetch at the cost of a
140
+ * fatter index. May not overlap `on`.
141
+ */
142
+ include?: readonly (Keys | (string & {}))[];
143
+ }
144
+
145
+ /**
146
+ * Containment over an `array` property or a JSONB `map`, using core operator
147
+ * classes only. Trigram and full-text search are `search:`, not this.
148
+ */
149
+ export interface GinIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
150
+ using: "gin";
151
+ on: readonly [Keys | UnorderedIndexKey<Keys>, ...(Keys | UnorderedIndexKey<Keys>)[]];
152
+ }
153
+
154
+ /**
155
+ * A naturally-ordered column on an append-only table — tiny, and useless the
156
+ * moment rows arrive out of order.
157
+ */
158
+ export interface BrinIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
159
+ using: "brin";
160
+ on: readonly [Keys | UnorderedIndexKey<Keys>, ...(Keys | UnorderedIndexKey<Keys>)[]];
161
+ }
162
+
163
+ /**
164
+ * An index on a collection's table.
165
+ *
166
+ * No `gist` and no `hash`: every interesting gist operator class ships in an
167
+ * extension, and hash indexes cannot be unique, composite, or ordered.
168
+ *
169
+ * The restriction to core Postgres is not conservatism, it is what keeps the
170
+ * whole model on the Atlas path. `rebase db push` materialises the desired
171
+ * state in a bare scratch database to plan against, `--exclude` does not
172
+ * suppress that replay, and `CREATE EXTENSION` cannot be put in the file — so
173
+ * an index needing `gin_trgm_ops` or `vector_cosine_ops` is refused at build
174
+ * time rather than emitted to fail later against a database the author has
175
+ * never heard of. Trigram search is `search:`; ANN is a `vector` property.
176
+ */
177
+ export type CollectionIndex<Keys extends string = string> =
178
+ | BtreeIndex<Keys>
179
+ | GinIndex<Keys>
180
+ | BrinIndex<Keys>;
@@ -26,6 +26,7 @@
26
26
  */
27
27
 
28
28
  import type { StorageSourceDefinition } from "./storage_source";
29
+ import type { ResourceGraph } from "./resources";
29
30
 
30
31
  /**
31
32
  * Which kind of thing an app is.
@@ -61,7 +62,7 @@ export interface RebaseBackendAppConfig {
61
62
  *
62
63
  * Independent of *where* it runs. Both run on Rebase Cloud and both
63
64
  * self-host — the destination lives in `.rebase/cloud.json`, not here. See
64
- * `docker/docker-compose.selfhost.yml`, which boots a managed bundle on a
65
+ * `infra/docker/docker-compose.selfhost.yml`, which boots a managed bundle on a
65
66
  * developer's own Docker host.
66
67
  *
67
68
  * This is authored rather than inferred on purpose. It is the single most
@@ -132,6 +133,38 @@ export interface RebaseStaticAppConfig {
132
133
 
133
134
  export type RebaseAppConfig = RebaseBackendAppConfig | RebaseStaticAppConfig;
134
135
 
136
+ /**
137
+ * Path prefixes the backend owns, which no static app may claim.
138
+ *
139
+ * One process — and, on the platform, one hostname — serves both the API and
140
+ * however many static apps a project has. Mounting is longest-path-first, so an
141
+ * app declaring `/api` would win against the API itself and every request to it
142
+ * would be answered with that app's `index.html`: a 200 carrying HTML where the
143
+ * caller expected JSON, from a project that looks deployed and healthy.
144
+ *
145
+ * Declared here rather than in either enforcer because both must agree. The CLI
146
+ * checks it so a developer finds out while editing `rebase.json`; the control
147
+ * plane checks it again at deploy intake, because the front door's correctness
148
+ * cannot rest on a check that ran in somebody else's CLI — and a repository can
149
+ * be deployed by a CLI older than this rule.
150
+ */
151
+ export const RESERVED_BACKEND_PREFIXES = ["/api", "/health", "/healthz", "/livez", "/readyz", "/metrics"] as const;
152
+
153
+ /**
154
+ * Whether `path` collides with a prefix the backend owns.
155
+ *
156
+ * Compares at segment boundaries, so `/api` and `/api/v2` collide while
157
+ * `/apidocs` does not — the same rule the router matches with, because a check
158
+ * that is stricter than the router rejects paths that would have worked, and one
159
+ * that is looser admits paths that will not.
160
+ */
161
+ export function reservedPrefixFor(path: string): string | undefined {
162
+ const normalized = path.endsWith("/") && path !== "/" ? path.slice(0, -1) : path;
163
+ return RESERVED_BACKEND_PREFIXES.find(
164
+ reserved => normalized === reserved || normalized.startsWith(`${reserved}/`)
165
+ );
166
+ }
167
+
135
168
  /**
136
169
  * One declared storage source, as authored in `rebase.json`.
137
170
  *
@@ -179,27 +212,20 @@ export interface RebaseProjectManifest {
179
212
  */
180
213
  apps: Record<string, RebaseAppConfig>;
181
214
  /**
182
- * Storage sources this project uses, keyed by source key.
215
+ * Buckets are NOT declared here any more.
183
216
  *
184
- * **Topology only — never credentials.** Which buckets exist is a property of
185
- * the project and belongs in the repository; how to reach each one is a
186
- * property of the deployment and lives in the environment, read per source
187
- * from `<BASE>__<KEY>` (`S3_BUCKET__MEDIA` for a source keyed `media`). The
188
- * default source takes no suffix, so a single-bucket project configured with
189
- * plain `S3_BUCKET` keeps working having declared nothing at all.
217
+ * They were, and the runtime merged this block with the declarations in
218
+ * config code — a bucket named in both had one engine kept and the other
219
+ * silently discarded. Two homes for one concept, with a merge to decide
220
+ * between them, is the shape this whole model replaced.
190
221
  *
191
- * Declared here rather than only in the config package because this file is
192
- * the one artifact a host can read *before* running a build. That is what
193
- * lets a console show "this project wants a `media` bucket, and it has none"
194
- * on a project's first deploy, and it is why the managed and custom runtimes
195
- * can present the same list — a custom build emits no bundle manifest, so a
196
- * declaration that lived only in compiled config would leave every custom
197
- * project invisible.
198
- *
199
- * Omitted entirely means one default source, which is the overwhelmingly
200
- * common project and must not be required to say so.
222
+ * `bucket("media", { engine: "s3" })` in the project's config declares one
223
+ * now, and `rebase resources --write` generates `rebase.resources.json`,
224
+ * which is what a host reads before a build. A `storage` block left in this
225
+ * file is refused by the validator, by name, with the replacement in the
226
+ * message — not ignored, because a key that still parses and does nothing
227
+ * is the failure this removed.
201
228
  */
202
- storage?: Record<string, RebaseStorageSourceConfig>;
203
229
  /**
204
230
  * Repository-wide opt-out from anonymous CLI usage sharing.
205
231
  *
@@ -279,6 +305,31 @@ export const BUNDLE_FORMAT_VERSION = 2;
279
305
  * any number of minors and patches while this stays put. It changes only when
280
306
  * the bundle/runtime contract breaks compatibility, and a project's
281
307
  * `manifest.runtime` range is matched against *this*.
308
+ *
309
+ * ## v2 — resources are declared, not configured
310
+ *
311
+ * `RebaseBackendConfig.dataSources` and `.storageSources` are gone. A project
312
+ * declares its databases and buckets with `database()` / `bucket()` in its
313
+ * config, and the runtime reads those declarations.
314
+ *
315
+ * This had to be a major, and the reason is the managed tier: it moves projects
316
+ * onto new images WITHOUT rebuilding them. A bundle built against v1 exports
317
+ * those keys, and a v2 runtime refuses them at boot — so without this bump, one
318
+ * image rollout would crash-loop every tenant that had ever declared a second
319
+ * database or bucket, in a wave, with the cause in a container log nobody is
320
+ * watching.
321
+ *
322
+ * With the bump, a v1 bundle on a v2 runtime is refused by
323
+ * `assertBundleCompatibility` with the remedy in the message, and the platform
324
+ * keeps it on a v1 image until it is rebuilt. That is the whole purpose of this
325
+ * number.
326
+ *
327
+ * **Release order matters and is not optional.** The control plane is the side
328
+ * that rejects, so it ships FIRST: raise `SUPPORTED_RUNTIME_CONTRACT` in the
329
+ * saas repo (it rejects only `contract >` its own, so it then accepts both),
330
+ * deploy that, and only then release a runtime implementing v2. Shipping the
331
+ * runtime first turns every deploy into a rejected intake blaming the tenant's
332
+ * bundle.
282
333
  */
283
334
  export const RUNTIME_CONTRACT_VERSION = 1;
284
335
 
@@ -333,6 +384,36 @@ export interface NativeDependency {
333
384
  * `manifest.json` — generated, and the document the runtime and control plane
334
385
  * both validate against.
335
386
  */
387
+ /**
388
+ * One custom function, as recorded in a built bundle.
389
+ *
390
+ * @see RebaseBundleManifest.functions
391
+ */
392
+ export interface RebaseBundleFunction {
393
+ /**
394
+ * The filename without its extension — which is also the URL segment it
395
+ * mounts at (`/api/functions/<name>`), the API-key permission that grants
396
+ * it, and the name `REBASE_FUNCTIONS_ONLY` selects by. One identity, used
397
+ * everywhere.
398
+ */
399
+ name: string;
400
+ /** Path inside the bundle, so a host can point at the file. */
401
+ file: string;
402
+ /**
403
+ * `false` when the function's own source imports a Node built-in or a
404
+ * package that needs one.
405
+ *
406
+ * Descriptive, never a gate: nothing refuses to build or deploy on this. It
407
+ * says where this function *could* run, not where it should.
408
+ */
409
+ portable: boolean;
410
+ /**
411
+ * Why it is not portable — one short phrase per reason, deduplicated.
412
+ * Absent when it is.
413
+ */
414
+ requires?: string[];
415
+ }
416
+
336
417
  export interface RebaseBundleManifest {
337
418
  /** @see BUNDLE_FORMAT_VERSION */
338
419
  bundleFormat: number;
@@ -373,6 +454,28 @@ export interface RebaseBundleManifest {
373
454
  entry: RebaseBundleEntrypoints;
374
455
  /** Collection slugs contained in the bundle, for quick inspection. */
375
456
  collections?: string[];
457
+ /**
458
+ * Every custom function in the bundle, named and classified.
459
+ *
460
+ * Two things are recorded per function, and both are answers a host would
461
+ * otherwise have to get by importing user code:
462
+ *
463
+ * - **What it is called.** That name is the function's identity everywhere —
464
+ * the URL segment it mounts at, the `functions/<name>` API-key
465
+ * permission, the value `REBASE_FUNCTIONS_ONLY` selects by. A host that
466
+ * wants to give one slow function its own replica count currently has to
467
+ * boot the bundle to discover what is in it.
468
+ * - **Whether it needs Node.** Purely descriptive: a function that opens a
469
+ * file or runs raw SQL is a fine function, and every deployment today is
470
+ * a Node process. It is recorded because the question "which of these
471
+ * could run somewhere else" has to be answerable from the artifact, and
472
+ * because answering it per-file after the fact — across a codebase
473
+ * already written — is the expensive version of the same question.
474
+ *
475
+ * Absent on a bundle built before this field existed, which is why every
476
+ * consumer must treat it as optional rather than as an empty list.
477
+ */
478
+ functions?: RebaseBundleFunction[];
376
479
  hooks: {
377
480
  /**
378
481
  * Whether the dependency closure contains native code.
@@ -401,17 +504,27 @@ export interface RebaseBundleManifest {
401
504
  /** Whether the config package exports a `storageAuthorize` hook. */
402
505
  authorize: boolean;
403
506
  /**
404
- * Every storage source this bundle expects, resolved at build time from
405
- * `rebase.json`'s `storage` block merged with any `storageSources` the
406
- * config package exports.
507
+ * Buckets, on bundles built before {@link RebaseBundleManifest.resources}.
407
508
  *
408
- * Recorded so the runtime does not have to import user code to learn its
409
- * own topology, and so a host can tell — from the artifact alone, before
410
- * starting anything — which buckets need configuring. Absent on bundles
411
- * built before this field existed, which means one default source.
509
+ * No longer written. A host reads `resources`, which carries every kind
510
+ * in one list; this stays declared so a control plane can keep reading
511
+ * the bundles a project shipped before it was rebuilt.
412
512
  */
413
513
  sources?: StorageSourceDefinition[];
414
514
  };
515
+ /**
516
+ * Everything the project declares it needs — databases, buckets, topics,
517
+ * and whatever kind is registered next.
518
+ *
519
+ * Recorded so a host can tell, from the artifact alone and before starting
520
+ * anything, what a deploy will need provisioned. That question used to be
521
+ * answerable for buckets and for nothing else, because buckets were the
522
+ * only kind written into an artifact — which is how a project's databases
523
+ * became invisible to the platform that runs them.
524
+ *
525
+ * Absent on bundles built before this field existed.
526
+ */
527
+ resources?: ResourceGraph;
415
528
  deps: {
416
529
  /** Runtime dependencies of user code, as declared. */
417
530
  declared: Record<string, string>;
@@ -400,6 +400,48 @@ export interface BooleanProperty extends BaseProperty {
400
400
  validation?: PropertyValidationSchema;
401
401
  }
402
402
 
403
+ /**
404
+ * Which pgvector distance a query measures with, and therefore which operator
405
+ * class an index has to be built for. The names match the `distance` option on
406
+ * `vectorSearch`, because an index built for one operator is not used by a
407
+ * query that asks for another.
408
+ *
409
+ * @group Entity properties
410
+ */
411
+ export type VectorDistance = "cosine" | "l2" | "inner_product";
412
+
413
+ /**
414
+ * How the ANN index over a vector column is built.
415
+ *
416
+ * Without an index, `vectorSearch` is an exact scan: correct at any size,
417
+ * and linear in the number of rows. With one, it is approximate and fast.
418
+ * That trade is why this is configurable rather than implied.
419
+ *
420
+ * @group Entity properties
421
+ */
422
+ export interface VectorIndexConfig {
423
+ /**
424
+ * `hnsw` (the default) builds a navigable-graph index: slower to build,
425
+ * better recall, and it needs no training data, so it works on an empty
426
+ * table. `ivfflat` is cheaper to build but partitions by centroid, so an
427
+ * index built on an empty or tiny table has useless partitions — build it
428
+ * after the data is loaded, and set {@link lists}.
429
+ */
430
+ method?: "hnsw" | "ivfflat";
431
+ /**
432
+ * Which distance operators to index, defaulting to `cosine` — the default
433
+ * `vectorSearch` measures with. Name several to index several; each one is
434
+ * a separate index with its own build cost and its own storage.
435
+ */
436
+ distance?: VectorDistance | VectorDistance[];
437
+ /** HNSW: connections per node. Postgres defaults to 16. */
438
+ m?: number;
439
+ /** HNSW: candidate-list size while building. Postgres defaults to 64. */
440
+ efConstruction?: number;
441
+ /** IVFFlat: number of partitions. Postgres defaults to 100. */
442
+ lists?: number;
443
+ }
444
+
403
445
  export interface VectorProperty extends BaseProperty {
404
446
  type: "vector";
405
447
  /**
@@ -407,6 +449,18 @@ export interface VectorProperty extends BaseProperty {
407
449
  */
408
450
  defaultValue?: Vector;
409
451
  dimensions: number;
452
+ /**
453
+ * ANN index configuration for this column.
454
+ *
455
+ * Omitted, a single HNSW index for cosine distance is created — which is
456
+ * what the default `vectorSearch` uses. `false` creates none, leaving
457
+ * `vectorSearch` an exact scan.
458
+ *
459
+ * Indexes are only created when {@link dimensions} is at most 2000:
460
+ * pgvector cannot index a wider `vector` column, so a 3072-dimension
461
+ * embedding is left unindexed rather than failing the boot.
462
+ */
463
+ index?: VectorIndexConfig | false;
410
464
  validation?: PropertyValidationSchema;
411
465
  }
412
466