@pylonsync/sdk 0.3.22 → 0.3.24
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/package.json +1 -1
- package/src/index.ts +45 -0
- package/src/studio.ts +444 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -592,3 +592,48 @@ export function buildManifest(options: {
|
|
|
592
592
|
auth: options.auth ?? auth(),
|
|
593
593
|
};
|
|
594
594
|
}
|
|
595
|
+
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// Studio configuration — re-exports
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
|
|
600
|
+
export {
|
|
601
|
+
defineStudioConfig,
|
|
602
|
+
defineStudioExtensions,
|
|
603
|
+
type BrandConfig,
|
|
604
|
+
type ThemeConfig,
|
|
605
|
+
type ThemeAccent,
|
|
606
|
+
type ThemeAppearance,
|
|
607
|
+
type IconName,
|
|
608
|
+
type SidebarConfig,
|
|
609
|
+
type SidebarSection,
|
|
610
|
+
type SidebarItem,
|
|
611
|
+
type SidebarPageItem,
|
|
612
|
+
type SidebarResourceItem,
|
|
613
|
+
type SidebarLinkItem,
|
|
614
|
+
type SidebarHeadingItem,
|
|
615
|
+
type SidebarFooter,
|
|
616
|
+
type SidebarFooterCard,
|
|
617
|
+
type SidebarFooterCustom,
|
|
618
|
+
type ResourceConfig,
|
|
619
|
+
type ResourceListConfig,
|
|
620
|
+
type ColumnConfig,
|
|
621
|
+
type ColumnRenderer,
|
|
622
|
+
type RendererKind,
|
|
623
|
+
type RendererText,
|
|
624
|
+
type RendererAvatar,
|
|
625
|
+
type RendererBadge,
|
|
626
|
+
type RendererDate,
|
|
627
|
+
type RendererLink,
|
|
628
|
+
type RendererBoolean,
|
|
629
|
+
type RendererNumber,
|
|
630
|
+
type RendererJson,
|
|
631
|
+
type RendererCustom,
|
|
632
|
+
type BulkAction,
|
|
633
|
+
type RowAction,
|
|
634
|
+
type PageConfig,
|
|
635
|
+
type StudioConfig,
|
|
636
|
+
type StudioCellRendererProps,
|
|
637
|
+
type StudioPageProps,
|
|
638
|
+
type StudioExtensions,
|
|
639
|
+
} from "./studio";
|
package/src/studio.ts
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Pylon Studio configuration
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
//
|
|
5
|
+
// Authored as `studio.config.ts` in the user's pylon project. The CLI runs
|
|
6
|
+
// `bun run studio.config.ts` which prints the result of `defineStudioConfig`
|
|
7
|
+
// to stdout as JSON. The runtime reads `.pylon/studio.config.json` at boot
|
|
8
|
+
// and injects it into the Studio HTML as `window.__PYLON_STUDIO_CONFIG__`.
|
|
9
|
+
//
|
|
10
|
+
// Keep every field here JSON-serializable. React components (custom column
|
|
11
|
+
// renderers, custom pages, custom layout slots) live in the *separate*
|
|
12
|
+
// `studio.entry.tsx` file and are referenced from the config by string id.
|
|
13
|
+
// `studio.entry.tsx` is bundled to ESM and dynamically imported by the
|
|
14
|
+
// Studio shell at boot — see `defineStudioExtensions` below.
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Brand
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export interface BrandConfig {
|
|
21
|
+
/** Display name in the sidebar header. Defaults to manifest.name. */
|
|
22
|
+
name?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Logo source. One of:
|
|
25
|
+
* - data URL (`data:image/svg+xml;base64,...`)
|
|
26
|
+
* - absolute URL (`https://...`)
|
|
27
|
+
* - path served by your app (`/assets/logo.svg`)
|
|
28
|
+
* - emoji or single grapheme rendered as a glyph (`"⚡"`)
|
|
29
|
+
* Omit to fall back to the bolt icon.
|
|
30
|
+
*/
|
|
31
|
+
logo?: string;
|
|
32
|
+
/** Optional subtitle shown under the brand name (e.g. "v1.2.3"). */
|
|
33
|
+
subtitle?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Theme
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/** Built-in accent palettes. The Studio ships CSS variables for each. */
|
|
41
|
+
export type ThemeAccent = "emerald" | "blue" | "violet" | "rose" | "amber";
|
|
42
|
+
|
|
43
|
+
/** Initial appearance — the user can still toggle via the UI (next-themes). */
|
|
44
|
+
export type ThemeAppearance = "dark" | "light" | "system";
|
|
45
|
+
|
|
46
|
+
export interface ThemeConfig {
|
|
47
|
+
accent?: ThemeAccent;
|
|
48
|
+
appearance?: ThemeAppearance;
|
|
49
|
+
/**
|
|
50
|
+
* Override the accent's primary color with a custom hex. When set, the
|
|
51
|
+
* accent CSS variables are derived from this value instead of the
|
|
52
|
+
* built-in palette. Use sparingly — the built-in palettes are tuned for
|
|
53
|
+
* AA contrast in both dark and light.
|
|
54
|
+
*/
|
|
55
|
+
primary?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Sidebar
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
/** Lucide icon name (kebab-case). Validated client-side. */
|
|
63
|
+
export type IconName = string;
|
|
64
|
+
|
|
65
|
+
export interface SidebarPageItem {
|
|
66
|
+
type: "page";
|
|
67
|
+
/**
|
|
68
|
+
* Built-in page id (`overview`, `users`, `roles`, `health`, `sync`,
|
|
69
|
+
* `manifest`, `functions`, `policies`, `routes`, `settings`) OR a key
|
|
70
|
+
* registered by `studio.entry.tsx` via `defineStudioExtensions({ pages })`.
|
|
71
|
+
*/
|
|
72
|
+
id: string;
|
|
73
|
+
label: string;
|
|
74
|
+
icon?: IconName;
|
|
75
|
+
/** Hide unless the current viewer is_admin. */
|
|
76
|
+
requiresAdmin?: boolean;
|
|
77
|
+
/** Hide unless the current viewer has at least one of these roles. */
|
|
78
|
+
requiresRoles?: string[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface SidebarResourceItem {
|
|
82
|
+
type: "resource";
|
|
83
|
+
/** Manifest entity name. The list page is auto-generated. */
|
|
84
|
+
entity: string;
|
|
85
|
+
/** Override the sidebar label. Defaults to entity name. */
|
|
86
|
+
label?: string;
|
|
87
|
+
icon?: IconName;
|
|
88
|
+
requiresAdmin?: boolean;
|
|
89
|
+
requiresRoles?: string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface SidebarLinkItem {
|
|
93
|
+
type: "link";
|
|
94
|
+
label: string;
|
|
95
|
+
href: string;
|
|
96
|
+
icon?: IconName;
|
|
97
|
+
/** Open in a new tab. Defaults to true for absolute URLs. */
|
|
98
|
+
external?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SidebarHeadingItem {
|
|
102
|
+
type: "heading";
|
|
103
|
+
label: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type SidebarItem =
|
|
107
|
+
| SidebarPageItem
|
|
108
|
+
| SidebarResourceItem
|
|
109
|
+
| SidebarLinkItem
|
|
110
|
+
| SidebarHeadingItem;
|
|
111
|
+
|
|
112
|
+
export interface SidebarSection {
|
|
113
|
+
/** Uppercase label rendered above the items. Pass empty string to omit. */
|
|
114
|
+
label: string;
|
|
115
|
+
items: SidebarItem[];
|
|
116
|
+
/** Default open state for the collapse toggle. Defaults to true. */
|
|
117
|
+
defaultOpen?: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// Sidebar footer
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
export interface SidebarFooterCard {
|
|
125
|
+
type: "card";
|
|
126
|
+
title: string;
|
|
127
|
+
description: string;
|
|
128
|
+
/** Optional CTA at the bottom of the card. */
|
|
129
|
+
action?: { label: string; href: string };
|
|
130
|
+
/** Optional progress bar (0..1). Useful for "used space" cards. */
|
|
131
|
+
progress?: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface SidebarFooterCustom {
|
|
135
|
+
type: "custom";
|
|
136
|
+
/** Component id registered via `defineStudioExtensions({ layouts: { footer: { id: ... }}})`. */
|
|
137
|
+
componentId: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type SidebarFooter = SidebarFooterCard | SidebarFooterCustom | null;
|
|
141
|
+
|
|
142
|
+
export interface SidebarConfig {
|
|
143
|
+
sections: SidebarSection[];
|
|
144
|
+
footer?: SidebarFooter;
|
|
145
|
+
/** Org switcher button at the top. Pass `null` to hide. */
|
|
146
|
+
orgSwitcher?: { items: { id: string; label: string }[] } | null;
|
|
147
|
+
/** Show the collapse-to-icons toggle. Defaults to true. */
|
|
148
|
+
collapsible?: boolean;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Resource list configuration
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
/** Built-in cell renderer kinds. Custom renderers register an id under `componentId`. */
|
|
156
|
+
export type RendererKind =
|
|
157
|
+
| "text"
|
|
158
|
+
| "avatar"
|
|
159
|
+
| "badge"
|
|
160
|
+
| "date"
|
|
161
|
+
| "link"
|
|
162
|
+
| "boolean"
|
|
163
|
+
| "number"
|
|
164
|
+
| "json"
|
|
165
|
+
| "custom";
|
|
166
|
+
|
|
167
|
+
export interface RendererText {
|
|
168
|
+
kind: "text";
|
|
169
|
+
/** Truncate long strings to this many chars. Default 80. */
|
|
170
|
+
truncate?: number;
|
|
171
|
+
/** Render as monospace. */
|
|
172
|
+
mono?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface RendererAvatar {
|
|
176
|
+
kind: "avatar";
|
|
177
|
+
/** Field on the row holding the image URL. If omitted, initials only. */
|
|
178
|
+
imageField?: string;
|
|
179
|
+
/** Field holding the secondary line under the name. */
|
|
180
|
+
subtitleField?: string;
|
|
181
|
+
/** Source field for the displayed name. Defaults to the column field. */
|
|
182
|
+
nameField?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface RendererBadge {
|
|
186
|
+
kind: "badge";
|
|
187
|
+
/**
|
|
188
|
+
* Map of value → variant. Variants: green, red, amber, blue, gray.
|
|
189
|
+
* Unknown values render gray. Use `*` as a fallback key.
|
|
190
|
+
*/
|
|
191
|
+
variants?: Record<string, "green" | "red" | "amber" | "blue" | "gray">;
|
|
192
|
+
/** Render a leading dot before the label. Defaults to true. */
|
|
193
|
+
dot?: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface RendererDate {
|
|
197
|
+
kind: "date";
|
|
198
|
+
/** "relative" → "3 days ago"; "absolute" → "May 3, 2026"; "iso" → raw. */
|
|
199
|
+
format?: "relative" | "absolute" | "iso";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface RendererLink {
|
|
203
|
+
kind: "link";
|
|
204
|
+
/**
|
|
205
|
+
* Template for the href. `{value}` and `{row.field}` are interpolated.
|
|
206
|
+
* Example: `"mailto:{value}"` or `"/users/{row.id}"`.
|
|
207
|
+
*/
|
|
208
|
+
href: string;
|
|
209
|
+
external?: boolean;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface RendererBoolean {
|
|
213
|
+
kind: "boolean";
|
|
214
|
+
trueLabel?: string;
|
|
215
|
+
falseLabel?: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface RendererNumber {
|
|
219
|
+
kind: "number";
|
|
220
|
+
/** "decimal" | "percent" | "currency" | "bytes". Default "decimal". */
|
|
221
|
+
style?: "decimal" | "percent" | "currency" | "bytes";
|
|
222
|
+
/** ISO currency code when style="currency". */
|
|
223
|
+
currency?: string;
|
|
224
|
+
/** Locale for Intl.NumberFormat. Default browser locale. */
|
|
225
|
+
locale?: string;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface RendererJson {
|
|
229
|
+
kind: "json";
|
|
230
|
+
/** Truncate JSON preview length. Default 60. */
|
|
231
|
+
truncate?: number;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface RendererCustom {
|
|
235
|
+
kind: "custom";
|
|
236
|
+
/** Id registered via `defineStudioExtensions({ renderers: { ... } })`. */
|
|
237
|
+
componentId: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export type ColumnRenderer =
|
|
241
|
+
| RendererText
|
|
242
|
+
| RendererAvatar
|
|
243
|
+
| RendererBadge
|
|
244
|
+
| RendererDate
|
|
245
|
+
| RendererLink
|
|
246
|
+
| RendererBoolean
|
|
247
|
+
| RendererNumber
|
|
248
|
+
| RendererJson
|
|
249
|
+
| RendererCustom;
|
|
250
|
+
|
|
251
|
+
export interface ColumnConfig {
|
|
252
|
+
/** Field name on the row. Supports dotted paths (`profile.name`). */
|
|
253
|
+
field: string;
|
|
254
|
+
/** Header label. Defaults to humanized field name. */
|
|
255
|
+
label?: string;
|
|
256
|
+
/** Display order in the table. Lower = earlier. Defaults to definition order. */
|
|
257
|
+
order?: number;
|
|
258
|
+
/** Hide column by default; user can toggle in the column picker. */
|
|
259
|
+
hidden?: boolean;
|
|
260
|
+
/** Server-side sort supported. */
|
|
261
|
+
sortable?: boolean;
|
|
262
|
+
/** Include in the search input's matched fields. */
|
|
263
|
+
searchable?: boolean;
|
|
264
|
+
/** Show a filter chip in the toolbar. */
|
|
265
|
+
filterable?: boolean | { options?: { label: string; value: unknown }[] };
|
|
266
|
+
/** Cell renderer config. Defaults to text. */
|
|
267
|
+
renderer?: ColumnRenderer;
|
|
268
|
+
/** Width hint (CSS, e.g. "200px" or "30%"). */
|
|
269
|
+
width?: string;
|
|
270
|
+
/** Right-align the column (good for numbers). */
|
|
271
|
+
align?: "left" | "center" | "right";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export interface BulkAction {
|
|
275
|
+
id: string;
|
|
276
|
+
label: string;
|
|
277
|
+
icon?: IconName;
|
|
278
|
+
/** Built-in action ids: "delete" | "export". Otherwise resolved against extensions. */
|
|
279
|
+
kind?: "delete" | "export" | "custom";
|
|
280
|
+
/** Confirmation dialog message. */
|
|
281
|
+
confirm?: string;
|
|
282
|
+
/** Hide unless viewer is_admin. */
|
|
283
|
+
requiresAdmin?: boolean;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface RowAction {
|
|
287
|
+
id: string;
|
|
288
|
+
label: string;
|
|
289
|
+
icon?: IconName;
|
|
290
|
+
kind?: "delete" | "edit" | "view" | "custom";
|
|
291
|
+
confirm?: string;
|
|
292
|
+
requiresAdmin?: boolean;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface ResourceListConfig {
|
|
296
|
+
/** Columns. If omitted, columns are inferred from the manifest entity fields. */
|
|
297
|
+
columns?: ColumnConfig[];
|
|
298
|
+
/** Search box in the toolbar. Defaults to true if any column is searchable. */
|
|
299
|
+
searchable?: boolean;
|
|
300
|
+
/** Filters dropdown in the toolbar. Defaults to true if any column is filterable. */
|
|
301
|
+
filterable?: boolean;
|
|
302
|
+
/** Bulk action menu (shown when rows are selected). */
|
|
303
|
+
bulkActions?: BulkAction[];
|
|
304
|
+
/** Per-row trailing action menu. */
|
|
305
|
+
rowActions?: RowAction[];
|
|
306
|
+
/** Default sort: field + direction. */
|
|
307
|
+
defaultSort?: { field: string; order: "asc" | "desc" };
|
|
308
|
+
/** Page size options. Default [10, 25, 50, 100]. */
|
|
309
|
+
pageSizes?: number[];
|
|
310
|
+
/** Initial page size. Default 10. */
|
|
311
|
+
defaultPageSize?: number;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface ResourceConfig {
|
|
315
|
+
/** Sidebar / breadcrumb label override. Defaults to entity name. */
|
|
316
|
+
label?: string;
|
|
317
|
+
/** Plural label for headings ("Users" vs "User"). Defaults to label + "s". */
|
|
318
|
+
pluralLabel?: string;
|
|
319
|
+
icon?: IconName;
|
|
320
|
+
/** Hide from the auto-derived sidebar. The user's explicit sidebar config still wins. */
|
|
321
|
+
hidden?: boolean;
|
|
322
|
+
list?: ResourceListConfig;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
// Pages — built-in + custom
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
export interface PageConfig {
|
|
330
|
+
/** Optional subtitle under the page header. */
|
|
331
|
+
subtitle?: string;
|
|
332
|
+
/** Custom component id (extensions). Required for non-built-in ids. */
|
|
333
|
+
componentId?: string;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Top-level config
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
export interface StudioConfig {
|
|
341
|
+
brand?: BrandConfig;
|
|
342
|
+
theme?: ThemeConfig;
|
|
343
|
+
sidebar?: SidebarConfig;
|
|
344
|
+
resources?: Record<string, ResourceConfig>;
|
|
345
|
+
pages?: Record<string, PageConfig>;
|
|
346
|
+
/**
|
|
347
|
+
* When true, the Studio will dynamic-import `/studio/extensions.js` at
|
|
348
|
+
* boot. The CLI sets this automatically when `studio.entry.tsx` is
|
|
349
|
+
* present in the project, so users rarely set it explicitly.
|
|
350
|
+
*/
|
|
351
|
+
hasExtensions?: boolean;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Identity helper for `studio.config.ts`. Provides type checking + lets
|
|
356
|
+
* the SDK evolve the shape under one declaration. The returned object is
|
|
357
|
+
* what the CLI serializes to JSON.
|
|
358
|
+
*
|
|
359
|
+
* ```ts
|
|
360
|
+
* // studio.config.ts
|
|
361
|
+
* import { defineStudioConfig } from "@pylon/sdk";
|
|
362
|
+
* export default defineStudioConfig({
|
|
363
|
+
* brand: { name: "Acme" },
|
|
364
|
+
* theme: { accent: "emerald", appearance: "dark" },
|
|
365
|
+
* sidebar: { sections: [...] },
|
|
366
|
+
* resources: { User: { list: { columns: [...] } } },
|
|
367
|
+
* });
|
|
368
|
+
* ```
|
|
369
|
+
*
|
|
370
|
+
* The CLI's bun runner expects the file's *default export* to be the
|
|
371
|
+
* config object, OR the module to print it via `console.log`. The
|
|
372
|
+
* recommended pattern is `console.log(JSON.stringify(defineStudioConfig({...})))`
|
|
373
|
+
* — the `pylon-studio-config.mjs` shim added by `pylon dev` does this
|
|
374
|
+
* automatically when you `export default`.
|
|
375
|
+
*/
|
|
376
|
+
export function defineStudioConfig(config: StudioConfig): StudioConfig {
|
|
377
|
+
return config;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// Extensions (studio.entry.tsx)
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
//
|
|
384
|
+
// Custom renderers, custom pages, custom layout slots — anything that's a
|
|
385
|
+
// React component lives here. The file is bundled to ESM and dynamic-
|
|
386
|
+
// imported at Studio boot. It must call `registerStudioExtensions` (the
|
|
387
|
+
// global hook) OR default-export the registry — the bundle entry shim
|
|
388
|
+
// supports both.
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Component shape for column renderers. The Studio passes the cell value
|
|
392
|
+
* + the full row + the column config; the renderer returns a React node.
|
|
393
|
+
*
|
|
394
|
+
* Kept as `unknown` here (rather than the actual React types) so this
|
|
395
|
+
* module stays usable from non-React contexts — `studio.config.ts`
|
|
396
|
+
* doesn't import React. The web shell narrows these to the right
|
|
397
|
+
* function signatures at registration time.
|
|
398
|
+
*/
|
|
399
|
+
export interface StudioCellRendererProps {
|
|
400
|
+
value: unknown;
|
|
401
|
+
row: Record<string, unknown>;
|
|
402
|
+
column: ColumnConfig;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export interface StudioPageProps {
|
|
406
|
+
/** Manifest passed to every custom page so they can introspect entities. */
|
|
407
|
+
manifest: unknown;
|
|
408
|
+
/** Auth context — `me`, `is_admin`, etc. */
|
|
409
|
+
auth: unknown;
|
|
410
|
+
/** Imperative `api()` helper, same one built-in pages use. */
|
|
411
|
+
api: <T = unknown>(path: string, opts?: unknown) => Promise<T>;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Registry shape — what `studio.entry.tsx` exports / registers. */
|
|
415
|
+
export interface StudioExtensions {
|
|
416
|
+
renderers?: Record<string, unknown /* (p: StudioCellRendererProps) => ReactNode */>;
|
|
417
|
+
pages?: Record<string, unknown /* (p: StudioPageProps) => ReactNode */>;
|
|
418
|
+
layouts?: {
|
|
419
|
+
footer?: unknown /* () => ReactNode */;
|
|
420
|
+
headerActions?: unknown /* () => ReactNode */;
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Identity helper for `studio.entry.tsx`. The user passes their component
|
|
426
|
+
* map and exports the result as default. The Studio shell imports the
|
|
427
|
+
* bundle and reads either `default` or the `registerStudioExtensions`
|
|
428
|
+
* global, whichever the user used.
|
|
429
|
+
*
|
|
430
|
+
* ```tsx
|
|
431
|
+
* // studio.entry.tsx
|
|
432
|
+
* import { defineStudioExtensions } from "@pylon/sdk";
|
|
433
|
+
* import { CurrencyCell } from "./renderers/currency";
|
|
434
|
+
* import { Dashboard } from "./pages/dashboard";
|
|
435
|
+
*
|
|
436
|
+
* export default defineStudioExtensions({
|
|
437
|
+
* renderers: { currency: CurrencyCell },
|
|
438
|
+
* pages: { dashboard: Dashboard },
|
|
439
|
+
* });
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
export function defineStudioExtensions(ext: StudioExtensions): StudioExtensions {
|
|
443
|
+
return ext;
|
|
444
|
+
}
|