@rebasepro/types 0.17.3 → 0.18.1

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 (71) hide show
  1. package/README.md +4 -0
  2. package/dist/call_context.d.ts +20 -0
  3. package/dist/controllers/client.d.ts +36 -4
  4. package/dist/controllers/data.d.ts +120 -10
  5. package/dist/errors.d.ts +83 -4
  6. package/dist/index.es.js +522 -160
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/types/admin_block.d.ts +2 -2
  9. package/dist/types/auth_adapter.d.ts +41 -6
  10. package/dist/types/backend.d.ts +48 -0
  11. package/dist/types/collections.d.ts +25 -1
  12. package/dist/types/cron.d.ts +34 -0
  13. package/dist/types/database_adapter.d.ts +39 -0
  14. package/dist/types/entity_callbacks.d.ts +14 -1
  15. package/dist/types/filter-operators.d.ts +24 -1
  16. package/dist/types/policy.d.ts +29 -1
  17. package/dist/types/properties.d.ts +216 -3
  18. package/dist/types/relations.d.ts +65 -7
  19. package/dist/types/resource_kinds.d.ts +173 -17
  20. package/dist/types/resources.d.ts +108 -7
  21. package/dist/types/rls-functions.d.ts +11 -0
  22. package/dist/types/storage_source.d.ts +12 -23
  23. package/package.json +24 -23
  24. package/src/call_context.ts +0 -120
  25. package/src/controllers/auth_state.ts +0 -24
  26. package/src/controllers/client.ts +0 -494
  27. package/src/controllers/collection_registry.ts +0 -62
  28. package/src/controllers/data.ts +0 -1012
  29. package/src/controllers/data_driver.ts +0 -576
  30. package/src/controllers/effective_role.ts +0 -4
  31. package/src/controllers/email.ts +0 -91
  32. package/src/controllers/index.ts +0 -11
  33. package/src/controllers/storage.ts +0 -252
  34. package/src/errors.ts +0 -119
  35. package/src/index.ts +0 -5
  36. package/src/types/admin_block.ts +0 -209
  37. package/src/types/api_keys.ts +0 -108
  38. package/src/types/auth_adapter.ts +0 -580
  39. package/src/types/backend.ts +0 -987
  40. package/src/types/backup.ts +0 -26
  41. package/src/types/channel_bus.ts +0 -202
  42. package/src/types/chips.ts +0 -34
  43. package/src/types/collection_contract.ts +0 -278
  44. package/src/types/collections.ts +0 -763
  45. package/src/types/component_ref.ts +0 -92
  46. package/src/types/cron.ts +0 -213
  47. package/src/types/data_source.ts +0 -357
  48. package/src/types/database_adapter.ts +0 -267
  49. package/src/types/entities.ts +0 -226
  50. package/src/types/entity_callbacks.ts +0 -229
  51. package/src/types/filter-operators.ts +0 -444
  52. package/src/types/history.ts +0 -66
  53. package/src/types/index.ts +0 -36
  54. package/src/types/indexes.ts +0 -180
  55. package/src/types/policy.ts +0 -328
  56. package/src/types/postgres_introspection.ts +0 -101
  57. package/src/types/project_manifest.ts +0 -598
  58. package/src/types/properties.ts +0 -1368
  59. package/src/types/relations.ts +0 -417
  60. package/src/types/resource_kinds.ts +0 -390
  61. package/src/types/resources.ts +0 -368
  62. package/src/types/rls-functions.ts +0 -98
  63. package/src/types/schema_editing.ts +0 -157
  64. package/src/types/schema_version.ts +0 -112
  65. package/src/types/search.ts +0 -247
  66. package/src/types/security_rules.ts +0 -344
  67. package/src/types/storage_authorize.ts +0 -77
  68. package/src/types/storage_source.ts +0 -248
  69. package/src/types/websockets.ts +0 -117
  70. package/src/users/index.ts +0 -2
  71. package/src/users/user.ts +0 -69
@@ -1,26 +0,0 @@
1
- /**
2
- * Backup type definitions shared across server, client, and studio.
3
- */
4
-
5
- /** Where a backup lives — a local path or an object-storage bucket. */
6
- export type BackupDestinationKind = "local" | "s3" | "gcs";
7
-
8
- /**
9
- * A single backup as surfaced by the admin API / Studio Backups panel.
10
- */
11
- export interface BackupInfo {
12
- /** Storage key (object storage) or absolute file path (local). */
13
- key: string;
14
-
15
- /** Display name — the file's basename. */
16
- name: string;
17
-
18
- /** Size in bytes, when known. */
19
- sizeBytes?: number;
20
-
21
- /** ISO timestamp the backup was created, when recoverable. */
22
- createdAt?: string;
23
-
24
- /** The kind of destination this backup was read from. */
25
- destinationKind: BackupDestinationKind;
26
- }
@@ -1,202 +0,0 @@
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
- /**
20
- * A frame in flight between instances.
21
- *
22
- * `sid` identifies the publishing instance. The realtime service drops frames
23
- * carrying its own `sid` on arrival — local fan-out already happened before the
24
- * publish — so a transport that echoes a publisher's own messages back to it is
25
- * still correct, merely wasteful.
26
- *
27
- * Keys are spelled out rather than abbreviated. The one shipped transport with a
28
- * size limit has a pointer path for anything that would approach it, so shaving
29
- * bytes off key names buys nothing worth the opacity.
30
- */
31
- export type ChannelBusFrame =
32
- /** A broadcast carrying its payload. */
33
- | {
34
- kind: "broadcast";
35
- sid: string;
36
- channel: string;
37
- event: string;
38
- /** Originating client, echoed so receivers can skip it if it is theirs. */
39
- from?: string;
40
- /** Sequence number, present only on retained channels. */
41
- seq?: number;
42
- payload: unknown;
43
- }
44
- /**
45
- * A broadcast too large for the transport to carry inline: the body is
46
- * already durable in `rebase.channel_messages`, so the frame carries only
47
- * its address and each receiver reads it back. Only ever emitted for
48
- * retained channels, and only by a transport with a finite
49
- * {@link ChannelBus.maxFrameBytes}.
50
- */
51
- | {
52
- kind: "broadcast_ref";
53
- sid: string;
54
- channel: string;
55
- from?: string;
56
- seq: number;
57
- }
58
- /** A presence join/leave/update, small by construction. */
59
- | {
60
- kind: "presence_diff";
61
- sid: string;
62
- channel: string;
63
- joins: Record<string, Record<string, unknown>>;
64
- leaves: Record<string, Record<string, unknown>>;
65
- };
66
-
67
- /** Receives frames published by *other* instances. */
68
- export type ChannelBusHandler = (frame: ChannelBusFrame) => void | Promise<void>;
69
-
70
- /**
71
- * A cross-instance transport.
72
- *
73
- * ## What an implementation must guarantee
74
- *
75
- * - **`start()` rejects if the transport is unusable.** The caller falls back to
76
- * in-process delivery when it does. Resolving while disconnected produces a
77
- * cluster that believes it is connected and silently is not, which is the
78
- * exact failure this whole mechanism exists to remove.
79
- * - **`publish()` reaches every *other* instance, or rejects.** Delivery back to
80
- * the publisher is permitted but pointless (see {@link ChannelBusFrame.sid}).
81
- * - **`stop()` is idempotent** and releases everything, including anything
82
- * holding the event loop open.
83
- * - **A malformed message never throws out of the transport.** Parsing happens
84
- * inside the implementation; drop and log what you cannot understand, so one
85
- * bad frame cannot take the listener down.
86
- *
87
- * ## What it does *not* have to guarantee
88
- *
89
- * - **Ordering.** Retained channels carry `seq`, and the client SDK orders by
90
- * it. Unsequenced broadcasts are cursor-grade traffic where order is not
91
- * meaningful.
92
- * - **Durability.** A frame lost in transit is a missed live update; retained
93
- * channels repair themselves through the client's `channel_history` replay.
94
- * - **Exactly-once.** Duplicates are tolerated — retained frames are deduped by
95
- * `seq`, and presence diffs are idempotent by construction.
96
- */
97
- export interface ChannelBus {
98
- /**
99
- * Identifies the transport in logs and in `getChannelBusKind()`. Use your
100
- * own name; the framework only compares against `"memory"` to decide
101
- * whether publishing is worth attempting at all.
102
- */
103
- readonly kind: string;
104
-
105
- /**
106
- * Largest frame this transport will carry, in bytes of encoded JSON, or
107
- * `Infinity` when there is no meaningful ceiling.
108
- *
109
- * A broadcast that exceeds it is published as a `broadcast_ref` pointer when
110
- * the channel is retained, and refused with an error to the sender when it
111
- * is not. Implementations with no limit should return `Infinity` rather than
112
- * a large number, so the pointer path is never taken needlessly.
113
- */
114
- readonly maxFrameBytes: number;
115
-
116
- /** Connect and begin delivering remote frames to `handler`. */
117
- start(handler: ChannelBusHandler): Promise<void>;
118
-
119
- /** Publish a frame to the other instances. */
120
- publish(frame: ChannelBusFrame): Promise<void>;
121
-
122
- /** Disconnect and release resources. Idempotent. */
123
- stop(): Promise<void>;
124
- }
125
-
126
- /**
127
- * Which transport to use, for the two that ship with the Postgres adapter.
128
- *
129
- * To use one that does not ship here — a Redis package, or your own class —
130
- * pass the {@link ChannelBus} instance itself instead of a config object.
131
- *
132
- * There are deliberately only two built in, and neither adds a service to a
133
- * deployment. Rebase deploys as Postgres + backend + frontend; a bus that
134
- * required a message broker would put a second stateful service into every
135
- * `docker-compose.yml` the CLI scaffolds, for a feature most applications never
136
- * use. Measured across two backend instances against one Postgres container,
137
- * the Postgres bus carried ~10k cross-instance messages/second with no losses,
138
- * and stayed flat out to eight instances — comfortably past what live-cursor
139
- * collaboration generates. The extension point below is the answer for anyone
140
- * who does outgrow it.
141
- */
142
- export type ChannelBusConfig =
143
- /**
144
- * In-process only — the historical behaviour. Broadcast and presence reach
145
- * the clients connected to *this* instance and no further.
146
- */
147
- | { type: "memory" }
148
- /**
149
- * Postgres LISTEN/NOTIFY, reusing infrastructure the deployment already has.
150
- *
151
- * `pg_notify` caps a payload at 8000 bytes, so a broadcast larger than that
152
- * is delivered cross-instance only on a *retained* channel, where the
153
- * notification carries a pointer (`seq`) instead of the message and each
154
- * receiver reads the body back from `rebase.channel_messages`. An oversized
155
- * broadcast on an ephemeral channel is refused rather than silently
156
- * delivered to half the cluster.
157
- *
158
- * NOTE: `LISTEN` needs a session-mode connection. Behind PgBouncer in
159
- * transaction mode this must point at the database directly
160
- * (`DATABASE_DIRECT_URL`), not at the pooler.
161
- */
162
- | {
163
- type: "postgres";
164
- /** Direct connection for the LISTEN client. Defaults to `DATABASE_DIRECT_URL`. */
165
- connectionString?: string;
166
- /**
167
- * How long to coalesce outgoing frames into a single notification, in
168
- * milliseconds. Defaults to 10.
169
- *
170
- * A notify is a query on your primary database, so under load this is
171
- * the difference between one query per message and one per window. The
172
- * window is leading-edge: a frame arriving when none is open goes out
173
- * immediately, so an idle channel pays no added latency and only a
174
- * sustained stream is batched.
175
- *
176
- * Set to 0 to disable coalescing and send every frame on its own.
177
- */
178
- batchWindowMs?: number;
179
- };
180
-
181
- /**
182
- * What `realtime.bus` accepts: a built-in transport by name, or any
183
- * {@link ChannelBus} instance.
184
- *
185
- * ```typescript
186
- * realtime: { bus: { type: "postgres" } } // shipped
187
- * realtime: { bus: new MyRedisChannelBus(url) } // a separate package, or your own
188
- * ```
189
- */
190
- export type ChannelBusSetting = ChannelBusConfig | ChannelBus;
191
-
192
- /**
193
- * Whether `setting` is an already-constructed transport rather than a request
194
- * for a built-in one.
195
- *
196
- * Structural rather than nominal so that an instance from a *different copy* of
197
- * `@rebasepro/types` — an entirely normal outcome of a separately versioned
198
- * transport package — is still recognised.
199
- */
200
- export function isChannelBusInstance(setting: ChannelBusSetting | undefined): setting is ChannelBus {
201
- return typeof (setting as ChannelBus | undefined)?.publish === "function";
202
- }
@@ -1,34 +0,0 @@
1
- export type ColorScheme = {
2
- color: string;
3
- text: string;
4
- }
5
-
6
- /**
7
- * The hues a chip can take.
8
- *
9
- * Kept in step with `CHIP_HUES` in `@rebasepro/ui` by hand: config types cannot
10
- * depend on the component library. A hue added there needs adding here too.
11
- */
12
- export type ColorHue =
13
- | "blue"
14
- | "cyan"
15
- | "teal"
16
- | "green"
17
- | "yellow"
18
- | "orange"
19
- | "red"
20
- | "pink"
21
- | "purple"
22
- | "gray"
23
- | "indigo"
24
- | "violet"
25
- | "fuchsia"
26
- | "rose"
27
- | "emerald";
28
-
29
- /**
30
- * How light or saturated a chip is. A hue on its own means `Lighter`.
31
- */
32
- export type ColorTone = "Lighter" | "Light" | "Dark" | "Darker";
33
-
34
- export type ColorKey = ColorHue | `${ColorHue}${ColorTone}`;
@@ -1,278 +0,0 @@
1
- import type { CollectionConfig } from "./collections";
2
-
3
- /**
4
- * Serializing collections so they survive a network hop.
5
- *
6
- * A collection definition is not plain data. Relations point at their target
7
- * with a *function* (`target: () => usersCollection`) so two collections can
8
- * reference each other without an import cycle, and collections also carry
9
- * callbacks, custom views and component references. `JSON.stringify` silently
10
- * drops every one of those, which matters because the SDK generator *calls*
11
- * `relation.target()` to decide whether a foreign key is a string or a number.
12
- * Serialize naively and remote SDK generation produces subtly wrong types
13
- * instead of failing — the worst possible outcome.
14
- *
15
- * So relation targets are resolved to a slug reference on the way out and
16
- * rebuilt into functions on the way in. Everything else that cannot cross a wire
17
- * is dropped deliberately: an SDK is generated from the *shape* of the data, and
18
- * server-side behaviour is neither useful to a client nor safe to publish.
19
- */
20
-
21
- /** Marker replacing a relation's `target` function in serialized form. */
22
- export interface SerializedCollectionRef {
23
- __collectionRef: string;
24
- }
25
-
26
- export function isSerializedCollectionRef(value: unknown): value is SerializedCollectionRef {
27
- return typeof value === "object"
28
- && value !== null
29
- && typeof (value as SerializedCollectionRef).__collectionRef === "string";
30
- }
31
-
32
- /** Depth limit for the walk — deep enough for real configs, finite for cyclic ones. */
33
- const MAX_DEPTH = 64;
34
-
35
- /**
36
- * Resolve whatever a `target` thunk returns down to a collection.
37
- *
38
- * A target may be the collection, a module namespace (when the authoring file
39
- * used `import * as`), or a default-export wrapper. All three appear in real
40
- * projects, and the SDK generator already unwraps them the same way.
41
- */
42
- function unwrapTarget(value: unknown): CollectionConfig | undefined {
43
- if (!value || typeof value !== "object") return undefined;
44
- const candidate = value as { default?: unknown; __esModule?: boolean; properties?: unknown };
45
- if (candidate.default || candidate.__esModule) {
46
- const inner = candidate.default;
47
- if (inner && typeof inner === "object") return inner as CollectionConfig;
48
- }
49
- if (candidate.properties) return value as CollectionConfig;
50
- return undefined;
51
- }
52
-
53
- /** The identity a serialized reference uses. Slug first — it is the routing key. */
54
- function refFor(collection: CollectionConfig | undefined): string | undefined {
55
- if (!collection) return undefined;
56
- const withPath = collection as CollectionConfig & { path?: string };
57
- return collection.slug || withPath.path || collection.name;
58
- }
59
-
60
- /**
61
- * Deep-copy a value into something JSON can carry.
62
- *
63
- * `target` keys are special-cased into refs. Other functions vanish, cycles are
64
- * cut, and everything else is copied structurally.
65
- */
66
- /** Shared walk state: the memo, plus a count of depth-cap hits. */
67
- interface WalkState {
68
- memo: WeakMap<object, unknown>;
69
- /**
70
- * How many times the walk has truncated a subtree — by hitting the depth
71
- * cap, or by cutting a cycle.
72
- *
73
- * Either kind of truncation makes a result valid only at the *position* it
74
- * was produced at, so caching it and serving it elsewhere silently drops
75
- * content that would have been included. Comparing this counter before and
76
- * after a node's children tells us whether its result is position-
77
- * independent and therefore safe to memoize.
78
- *
79
- * The cycle case is the subtle one: with `a.b = b` and `b.a = a`, serializing
80
- * `{ first: b, second: a }` visits `a` beneath `b` — where the cycle back to
81
- * `b` is cut — and would then reuse that truncated `a` for `second`, where
82
- * nothing needed cutting.
83
- */
84
- truncations: number;
85
- }
86
-
87
- function toSerializable(
88
- value: unknown,
89
- seen: WeakSet<object>,
90
- depth: number,
91
- state: WalkState,
92
- key?: string
93
- ): unknown {
94
- if (depth > MAX_DEPTH) {
95
- state.truncations++;
96
- return undefined;
97
- }
98
-
99
- if (typeof value === "function") {
100
- // Only a relation target carries information a client needs. Calling it
101
- // is safe here — this runs on the server, where the target module is
102
- // already loaded — and a throwing target simply yields no reference,
103
- // which degrades the generated FK type rather than failing the request.
104
- if (key === "target") {
105
- try {
106
- const resolved = unwrapTarget((value as () => unknown)());
107
- const ref = refFor(resolved);
108
- return ref ? { __collectionRef: ref } : undefined;
109
- } catch {
110
- return undefined;
111
- }
112
- }
113
- return undefined;
114
- }
115
-
116
- if (value === null || typeof value !== "object") {
117
- return value;
118
- }
119
-
120
- if (value instanceof Date) return value.toISOString();
121
- if (value instanceof RegExp) return value.source;
122
-
123
- if (seen.has(value as object)) {
124
- state.truncations++;
125
- return undefined;
126
- }
127
-
128
- // A shared (non-cyclic) subgraph is reachable by many paths, and `seen` is a
129
- // *path* set — released in the `finally` below so a node referenced twice in
130
- // different branches is emitted twice rather than dropped as a false cycle.
131
- // Without memoization that makes the walk exponential in depth: a diamond
132
- // graph 20 levels deep took ~400ms, and each further level doubled it. The
133
- // result is a plain data tree, so handing back the same converted object for
134
- // a repeat visit is indistinguishable after JSON.stringify.
135
- const cached = state.memo.get(value as object);
136
- if (cached !== undefined) return cached;
137
-
138
- seen.add(value as object);
139
- const truncationsBefore = state.truncations;
140
- const memoize = (result: unknown): unknown => {
141
- // Only cache a result that nothing was cut from.
142
- if (result !== undefined && state.truncations === truncationsBefore) {
143
- state.memo.set(value as object, result);
144
- }
145
- return result;
146
- };
147
-
148
- try {
149
- if (Array.isArray(value)) {
150
- const items = value
151
- .map(item => toSerializable(item, seen, depth + 1, state))
152
- .filter(item => item !== undefined);
153
- // A container that had content, none of which can be represented, is
154
- // itself unrepresentable — see the note below.
155
- return memoize(value.length > 0 && items.length === 0 ? undefined : items);
156
- }
157
-
158
- // A React element or component reference has no meaning to a client and
159
- // will not survive JSON anyway.
160
- if ("$$typeof" in (value as Record<string, unknown>)) return undefined;
161
-
162
- const entries = Object.entries(value as Record<string, unknown>);
163
- const out: Record<string, unknown> = {};
164
- for (const [k, v] of entries) {
165
- const converted = toSerializable(v, seen, depth + 1, state, k);
166
- if (converted !== undefined) out[k] = converted;
167
- }
168
-
169
- // Drop a container whose entire content was dropped.
170
- //
171
- // `callbacks: { beforeSave() {…} }` would otherwise serialize to
172
- // `callbacks: {}` — an empty husk that carries no information but is not
173
- // *nothing*, so it lands in the payload and, worse, in the schema hash.
174
- // Editing a hook would then change every client's schema version and
175
- // report perfectly current SDKs as stale.
176
- //
177
- // A container that started empty stays empty: `properties: {}` is a
178
- // deliberate statement, not a casualty.
179
- if (entries.length > 0 && Object.keys(out).length === 0) return undefined;
180
-
181
- return memoize(out);
182
- } finally {
183
- // Released so a collection referenced twice in different branches is
184
- // emitted twice rather than being dropped as a false cycle.
185
- seen.delete(value as object);
186
- }
187
- }
188
-
189
- /**
190
- * Serialize collections for transport over the contract endpoint.
191
- *
192
- * Sorted by slug so the output — and therefore the schema hash computed from it
193
- * — does not depend on filesystem ordering.
194
- */
195
- export function serializeCollections(collections: CollectionConfig[]): unknown[] {
196
- return [...collections]
197
- .sort((a, b) => String(a.slug ?? "").localeCompare(String(b.slug ?? "")))
198
- .map(collection => toSerializable(withoutAdminBlock(collection), new WeakSet(), 0, {
199
- memo: new WeakMap(),
200
- truncations: 0
201
- }))
202
- .filter((c): c is Record<string, unknown> => c !== undefined);
203
- }
204
-
205
- /**
206
- * Drop the admin block before the walk.
207
- *
208
- * Nothing downstream of serialization is an admin panel. The contract endpoint
209
- * feeds remote SDK generation, and `rebase build` writes the result into a bundle
210
- * manifest that only the backend runtime reads. The block would survive the walk
211
- * as a husk anyway — its React elements and component functions are dropped
212
- * individually — and that husk has two costs worth avoiding: it puts every custom
213
- * component's *file path* on an endpoint whose job is to describe data shapes, and
214
- * it grows a payload that is fetched and cached per project.
215
- *
216
- * Removing it here rather than at each call site means one chokepoint, so a future
217
- * consumer of `serializeCollections` cannot forget.
218
- *
219
- * Child collections carry their own block, so this recurses — stripping only the
220
- * top level was the mistake `stripNonClientFields` in the contract routes already
221
- * had to fix once for security rules.
222
- */
223
- function withoutAdminBlock(collection: CollectionConfig): CollectionConfig {
224
- const { admin: _admin, ...rest } = collection as CollectionConfig & Record<string, unknown>;
225
- const nested = rest as Record<string, unknown>;
226
- if (Array.isArray(nested.subcollections)) {
227
- nested.subcollections = nested.subcollections.map(
228
- (child) => withoutAdminBlock(child as CollectionConfig)
229
- );
230
- }
231
- return rest as CollectionConfig;
232
- }
233
-
234
- /**
235
- * Rebuild collections received from a contract endpoint.
236
- *
237
- * Relation refs become real thunks resolving through the returned set, so
238
- * downstream consumers — the SDK generator above all — see exactly the shape
239
- * they would have seen had the collections been imported from source.
240
- *
241
- * A ref naming a collection that is not in the payload resolves to `undefined`
242
- * rather than throwing: the generator already tolerates an unresolvable target
243
- * by falling back to a permissive key type, and a partial contract should still
244
- * produce a usable SDK.
245
- */
246
- export function deserializeCollections(payload: unknown[]): CollectionConfig[] {
247
- const collections = payload
248
- .filter((c): c is Record<string, unknown> => typeof c === "object" && c !== null)
249
- .map(c => ({ ...c })) as unknown as CollectionConfig[];
250
-
251
- const bySlug = new Map<string, CollectionConfig>();
252
- for (const collection of collections) {
253
- const ref = refFor(collection);
254
- if (ref) bySlug.set(ref, collection);
255
- }
256
-
257
- const rehydrate = (value: unknown, depth: number): void => {
258
- if (depth > MAX_DEPTH || !value || typeof value !== "object") return;
259
-
260
- if (Array.isArray(value)) {
261
- for (const item of value) rehydrate(item, depth + 1);
262
- return;
263
- }
264
-
265
- const record = value as Record<string, unknown>;
266
- for (const [key, child] of Object.entries(record)) {
267
- if (key === "target" && isSerializedCollectionRef(child)) {
268
- const slug = child.__collectionRef;
269
- record.target = () => bySlug.get(slug);
270
- continue;
271
- }
272
- rehydrate(child, depth + 1);
273
- }
274
- };
275
-
276
- for (const collection of collections) rehydrate(collection, 0);
277
- return collections;
278
- }