@rebasepro/types 0.10.0 → 0.10.1-canary.0a881d4
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/call_context.d.ts +51 -0
- package/dist/controllers/auth_state.d.ts +23 -0
- package/dist/controllers/client.d.ts +3 -1
- package/dist/controllers/collection_registry.d.ts +5 -1
- package/dist/controllers/data_driver.d.ts +31 -2
- package/dist/controllers/index.d.ts +1 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.es.js +374 -1
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +34 -0
- 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/collections.d.ts +17 -789
- package/dist/types/component_ref.d.ts +39 -6
- package/dist/types/entity_callbacks.d.ts +1 -1
- package/dist/types/index.d.ts +7 -15
- package/dist/types/project_manifest.d.ts +336 -0
- package/dist/types/properties.d.ts +12 -157
- package/dist/types/schema_version.d.ts +18 -0
- package/dist/types/security_rules.d.ts +320 -0
- package/dist/types/storage_authorize.d.ts +74 -0
- package/package.json +3 -6
- package/src/call_context.ts +56 -0
- package/src/controllers/auth_state.ts +24 -0
- package/src/controllers/client.ts +3 -2
- package/src/controllers/collection_registry.ts +5 -1
- package/src/controllers/data_driver.ts +58 -2
- package/src/controllers/index.ts +1 -10
- package/src/index.ts +1 -1
- package/src/types/admin_block.ts +75 -0
- package/src/types/backend.ts +33 -1
- package/src/types/channel_bus.ts +202 -0
- package/src/types/collection_contract.ts +278 -0
- package/src/types/collections.ts +25 -848
- package/src/types/component_ref.ts +40 -6
- package/src/types/entity_callbacks.ts +1 -1
- package/src/types/index.ts +7 -15
- package/src/types/policy.ts +25 -11
- package/src/types/project_manifest.ts +362 -0
- package/src/types/properties.ts +11 -169
- package/src/types/schema_version.ts +112 -0
- package/src/types/security_rules.ts +344 -0
- package/src/types/storage_authorize.ts +77 -0
- package/dist/controllers/analytics_controller.d.ts +0 -7
- package/dist/controllers/auth.d.ts +0 -110
- package/dist/controllers/customization_controller.d.ts +0 -61
- package/dist/controllers/dialogs_controller.d.ts +0 -36
- package/dist/controllers/local_config_persistence.d.ts +0 -20
- package/dist/controllers/navigation.d.ts +0 -225
- package/dist/controllers/registry.d.ts +0 -80
- package/dist/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/controllers/side_panel_controller.d.ts +0 -97
- package/dist/controllers/snackbar.d.ts +0 -24
- package/dist/rebase_context.d.ts +0 -110
- package/dist/types/breadcrumbs.d.ts +0 -26
- package/dist/types/builders.d.ts +0 -15
- package/dist/types/component_overrides.d.ts +0 -196
- package/dist/types/entity_actions.d.ts +0 -104
- package/dist/types/entity_link_builder.d.ts +0 -7
- package/dist/types/entity_views.d.ts +0 -95
- package/dist/types/export_import.d.ts +0 -21
- package/dist/types/formex.d.ts +0 -40
- package/dist/types/locales.d.ts +0 -4
- package/dist/types/modify_collections.d.ts +0 -5
- package/dist/types/plugins.d.ts +0 -277
- package/dist/types/property_config.d.ts +0 -74
- package/dist/types/slots.d.ts +0 -262
- package/dist/types/translations.d.ts +0 -915
- package/dist/types/user_management_delegate.d.ts +0 -22
- package/src/controllers/analytics_controller.tsx +0 -57
- package/src/controllers/auth.tsx +0 -121
- package/src/controllers/customization_controller.tsx +0 -72
- package/src/controllers/dialogs_controller.tsx +0 -37
- package/src/controllers/local_config_persistence.tsx +0 -22
- package/src/controllers/navigation.ts +0 -267
- package/src/controllers/registry.ts +0 -95
- package/src/controllers/side_dialogs_controller.tsx +0 -82
- package/src/controllers/side_panel_controller.tsx +0 -112
- package/src/controllers/snackbar.ts +0 -29
- package/src/rebase_context.tsx +0 -130
- package/src/types/breadcrumbs.ts +0 -27
- package/src/types/builders.ts +0 -17
- package/src/types/component_overrides.ts +0 -244
- package/src/types/entity_actions.tsx +0 -125
- package/src/types/entity_link_builder.ts +0 -8
- package/src/types/entity_views.tsx +0 -114
- package/src/types/export_import.ts +0 -26
- package/src/types/formex.ts +0 -45
- package/src/types/locales.ts +0 -81
- package/src/types/modify_collections.tsx +0 -6
- package/src/types/plugins.tsx +0 -346
- package/src/types/property_config.tsx +0 -95
- package/src/types/slots.tsx +0 -307
- package/src/types/translations.ts +0 -1026
- package/src/types/user_management_delegate.ts +0 -23
|
@@ -0,0 +1,278 @@
|
|
|
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
|
+
}
|