@diagonal-hq/cli 0.0.0-stage → 0.2.0

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.
@@ -0,0 +1,368 @@
1
+ import './globals';
2
+ import type { ResourceCapabilities } from './capabilities.generated';
3
+ import type { DataApiEndpoint, FieldDefinition, FieldType } from './fields.generated';
4
+
5
+ // The configuration helpers of file-based workspaces. The workspace build
6
+ // reads these calls as static data. index.js implements each define* function
7
+ // as an identity function and each field builder as `{ $field, ...options }`.
8
+
9
+ export type StaticValue =
10
+ | string
11
+ | number
12
+ | boolean
13
+ | null
14
+ | readonly StaticValue[]
15
+ | { readonly [key: string]: StaticValue };
16
+
17
+ export type ConfigDefinition = {
18
+ name: string;
19
+ brand?: { readonly [key: string]: StaticValue };
20
+ settings?: { readonly [key: string]: StaticValue };
21
+ };
22
+
23
+ export declare function defineConfig<const T extends ConfigDefinition>(config: T): T;
24
+
25
+ export { field } from './fields.generated';
26
+ export type {
27
+ CommonFieldSettings,
28
+ DataApiEndpoint,
29
+ FieldDefinition,
30
+ FieldSchemaDefinition,
31
+ FieldSchemaSetting,
32
+ FieldType,
33
+ RelationFieldOptions,
34
+ RelationInverse,
35
+ Resolver
36
+ } from './fields.generated';
37
+ export type { CapabilityType, FieldKeyOf, ResourceCapabilities } from './capabilities.generated';
38
+
39
+ export type PolicyScope =
40
+ | 'index'
41
+ | 'show'
42
+ | 'count'
43
+ | 'aggregate'
44
+ | 'store'
45
+ | 'storeMany'
46
+ | 'update'
47
+ | 'destroy'
48
+ | 'restore'
49
+ | 'validate';
50
+
51
+ export type PolicyAccess = 'public' | 'protected';
52
+
53
+ export type PolicyDefinition = {
54
+ name: string;
55
+ scope: 'all' | readonly PolicyScope[];
56
+ access: PolicyAccess;
57
+ description?: string | null;
58
+ };
59
+
60
+ export type IndexDefinition<TKey extends string = string> = {
61
+ fields: readonly TKey[];
62
+ unique?: boolean;
63
+ };
64
+
65
+ export type ResourceEmbeddings<TKey extends string = string> = {
66
+ enabled?: boolean;
67
+ /** Field keys. Relation paths are not allowed. */
68
+ fields?: readonly TKey[] | null;
69
+ };
70
+
71
+ export type ResourceDataApi = {
72
+ /** An omitted endpoint stays enabled. */
73
+ endpoints?: Partial<Record<DataApiEndpoint, { enabled?: boolean }>>;
74
+ };
75
+
76
+ export type ResourceDefinition<
77
+ TFields extends Record<string, FieldDefinition> = Record<string, FieldDefinition>
78
+ > = {
79
+ /**
80
+ * A lowercase UUID v7, for example '0199f000-0000-7000-8000-000000000001'.
81
+ * `diagonal check` suggests a new one when the id is not v7. Keep it
82
+ * stable: records and workflows refer to it.
83
+ */
84
+ id: string;
85
+ slug: string;
86
+ name: string;
87
+ singularName: string;
88
+ displayField?: Extract<keyof TFields, string>;
89
+ /**
90
+ * The field keys and relation paths that record search matches, for example
91
+ * `['title', 'body', 'author.full_name']`. Without it, search matches the
92
+ * display field and every text and markdown field.
93
+ */
94
+ searchableFields?: readonly (
95
+ | Extract<keyof TFields, string>
96
+ | `${Extract<keyof TFields, string>}.${string}`
97
+ )[];
98
+ /** Every field key once, in the order that forms and tables show them. Defaults to the order of `fields`. */
99
+ fieldOrder?: readonly Extract<keyof TFields, string>[];
100
+ fields: TFields;
101
+ indexes?: readonly IndexDefinition<Extract<keyof TFields, string>>[];
102
+ embeddings?: ResourceEmbeddings<Extract<keyof TFields, string>>;
103
+ dataApi?: ResourceDataApi;
104
+ /** A static resource refuses normal record writes. */
105
+ static?: boolean;
106
+ policies: readonly PolicyDefinition[];
107
+ /**
108
+ * The platform capabilities of the resource, keyed by capability. A field
109
+ * reference names a field of this resource by its key. The deploy validates
110
+ * each capability against the deployed schema and runs its provisioning.
111
+ */
112
+ capabilities?: ResourceCapabilities<TFields>;
113
+ };
114
+
115
+ export declare function defineResource<const TFields extends Record<string, FieldDefinition>>(
116
+ resource: ResourceDefinition<TFields>
117
+ ): ResourceDefinition<TFields>;
118
+
119
+ /** A TipTap document whose mentions resolve against the page's bound records. */
120
+ export type PageMetaResolver = { readonly type: string; readonly [key: string]: unknown };
121
+
122
+ export type PageRouteParam = {
123
+ /** The slug or id of the resource whose record the parameter names. */
124
+ resource: string;
125
+ /** The field the parameter matches. Defaults to the record id. */
126
+ lookupField?: string | null;
127
+ /** Relation fields to load with the record. */
128
+ relations?: readonly string[];
129
+ scope?: 'active' | 'activeOrArchived' | 'archived' | null;
130
+ };
131
+
132
+ export type PageAuthorizer =
133
+ | null
134
+ | 'authenticated'
135
+ | {
136
+ /** The slug or id of the resource whose signed-in records may view the page. */
137
+ resource?: string | null;
138
+ /** Permission ids. A viewer needs any one of them. */
139
+ permissions?: readonly string[];
140
+ };
141
+
142
+ export type PageDefinition = {
143
+ /** The URL pattern. Route parameters use `{name}`, for example `/members/{id}`. */
144
+ route: string;
145
+ /** The name in the admin. Defaults to the title. */
146
+ name?: string | null;
147
+ title?: string | PageMetaResolver | null;
148
+ description?: string | PageMetaResolver | null;
149
+ /** A public workspace file id, or a resolver that names one. */
150
+ ogImage?: string | PageMetaResolver | null;
151
+ /** HTML added to the page's `<head>`. */
152
+ headHtml?: string | null;
153
+ /** The file name of a layout in `layouts/`, without the extension. */
154
+ layout?: string | null;
155
+ /** `print` renders without a layout. */
156
+ renderProfile?: 'standard' | 'print';
157
+ /** Whether the sitemap lists the page. Defaults to true, or false for a print page. */
158
+ sitemap?: boolean;
159
+ /** The host of a connected custom domain. A page with a domain serves only there. */
160
+ domain?: string | null;
161
+ /** A paused page does not serve. */
162
+ paused?: boolean;
163
+ authorizer?: PageAuthorizer;
164
+ /** The record each route parameter binds to, keyed by parameter name. */
165
+ params?: Readonly<Record<string, PageRouteParam>>;
166
+ };
167
+
168
+ export type PageComponent = () => unknown;
169
+
170
+ export declare function definePage<const T extends PageDefinition>(
171
+ page: T,
172
+ component: PageComponent
173
+ ): T;
174
+
175
+ export type LayoutProps = { children?: React.ReactNode };
176
+
177
+ export type NavigationItem = { label: string; route: string; icon?: string | null };
178
+
179
+ export type NavigationDefinition = { id: string; items: readonly NavigationItem[] };
180
+
181
+ export declare function defineNavigation<const T extends NavigationDefinition>(navigation: T): T;
182
+
183
+ /** The slug, name, or id of a resource. */
184
+ export type ResourceReference = string;
185
+
186
+ export type WorkflowRunAs =
187
+ | 'system'
188
+ /** Only for request workflows: the signed-in user who calls the route. */
189
+ | 'session'
190
+ | {
191
+ resource: ResourceReference;
192
+ /** The lowercase UUID of the identity record. */
193
+ record: string;
194
+ };
195
+
196
+ export type RequestTrigger = {
197
+ type: 'request';
198
+ /** The route. Route parameters use `{name}`, for example `/api/members/{id}`. */
199
+ path: string;
200
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
201
+ };
202
+
203
+ export type RecordEventTrigger = {
204
+ type: 'recordEvent';
205
+ resource: ResourceReference;
206
+ event: 'created' | 'updated' | 'deleted' | 'restored' | 'saved';
207
+ filters?: StaticValue;
208
+ };
209
+
210
+ export type RecordLifecycleTrigger = {
211
+ type: 'recordLifecycle';
212
+ resource: ResourceReference;
213
+ hook: 'creating' | 'updating' | 'saving' | 'deleting' | 'restoring';
214
+ };
215
+
216
+ export type RecordScheduleTrigger = {
217
+ type: 'recordSchedule';
218
+ resource: ResourceReference;
219
+ /** The key of a date or datetime field of the resource. */
220
+ dateField: string;
221
+ /** Signed ISO 8601 durations in days, hours, or minutes, for example `'-P1D'` or `'+PT2H'`. */
222
+ offsets: readonly string[];
223
+ };
224
+
225
+ export type ScheduledTrigger = {
226
+ type: 'scheduled';
227
+ /** A cron expression with 5 fields, for example `'0 2 * * *'`. */
228
+ expression: string;
229
+ };
230
+
231
+ export type WorkflowEventTrigger = {
232
+ type: 'workflowEvent';
233
+ /** The path of another workflow, for example `'workflows/welcome.ts'`. */
234
+ workflow: `workflows/${string}.ts`;
235
+ event: 'execution.success' | 'execution.error';
236
+ };
237
+
238
+ export type ManualTrigger = { type: 'manual' };
239
+
240
+ type ConfigBase = { title: string; description?: string | null };
241
+
242
+ /**
243
+ * The `export const config` of a `workflows/<name>.ts` file. The build reads it as static data and
244
+ * never runs it. A background trigger (recordEvent, recordSchedule, scheduled, workflowEvent)
245
+ * needs `runAs`. A request workflow runs as the caller (`'session'`) by default.
246
+ */
247
+ export type WorkflowConfig =
248
+ | (ConfigBase & {
249
+ trigger: RequestTrigger;
250
+ runAs?: WorkflowRunAs;
251
+ authorizer?: 'authenticated';
252
+ })
253
+ | (ConfigBase & {
254
+ trigger: RecordEventTrigger | RecordScheduleTrigger | ScheduledTrigger | WorkflowEventTrigger;
255
+ runAs: Exclude<WorkflowRunAs, 'session'>;
256
+ })
257
+ | (ConfigBase & { trigger: ManualTrigger; runAs?: Exclude<WorkflowRunAs, 'session'> })
258
+ | (ConfigBase & { trigger: RecordLifecycleTrigger });
259
+
260
+ /**
261
+ * Liquid source: inline text, or the path of a `.liquid` file relative to the
262
+ * definition file, for example `{ file: './standard.email.liquid' }`.
263
+ */
264
+ export type LiquidSource = string | { readonly file: `./${string}.liquid` };
265
+
266
+ /** The key of a notification type. */
267
+ export type NotificationTypeReference = string;
268
+
269
+ /** The key of a template, unique within its notification type. */
270
+ export type NotificationTemplateReference = string;
271
+
272
+ /** The key of an email layout, or `'default'` for the built-in layout. */
273
+ export type EmailLayoutReference = string;
274
+
275
+ /** A variable whose value is one record. The sender passes the record id. */
276
+ export type RecordVariableDefinition = {
277
+ /** The slug of the resource whose record the sender passes. */
278
+ resource: ResourceReference;
279
+ label?: string;
280
+ description?: string | null;
281
+ required?: boolean;
282
+ };
283
+
284
+ /**
285
+ * A value that the sender passes to the notification. The name cannot be
286
+ * `record` or `ref`: templates read the recipient as `{{ record.* }}` and the
287
+ * generators as `{{ ref.* }}`.
288
+ */
289
+ export type NotificationVariableDefinition =
290
+ | FieldDefinition<Exclude<FieldType, 'relation'>>
291
+ | RecordVariableDefinition;
292
+
293
+ /** A `notifications/<key>/type.ts` file. */
294
+ export type NotificationTypeDefinition = {
295
+ /**
296
+ * Lowercase letters, digits, and hyphens, for example `'member-welcome'`.
297
+ * Templates, fixtures, and workflows refer to it. Moving the file keeps it.
298
+ */
299
+ key: string;
300
+ name: string;
301
+ /**
302
+ * The resource that receives the notification. The resource maps the email
303
+ * and phone fields. Leave it out to send to email addresses.
304
+ */
305
+ recipient?: { resource: ResourceReference };
306
+ variables?: { readonly [key: string]: NotificationVariableDefinition };
307
+ defaultTemplate: NotificationTemplateReference;
308
+ };
309
+
310
+ export declare function defineNotificationType<const T extends NotificationTypeDefinition>(
311
+ type: T
312
+ ): T;
313
+
314
+ export type NotificationReference = {
315
+ label: string;
316
+ /** The server generator, for example `'page.url'` or `'workspace.login_url'`. */
317
+ generator: string;
318
+ settings?: { readonly [setting: string]: StaticValue };
319
+ };
320
+
321
+ export type NotificationTemplateEmail = {
322
+ subject: string;
323
+ /** Defaults to `'default'`, the built-in layout with one field, `content`. */
324
+ layout?: EmailLayoutReference;
325
+ /** A value for each field of the layout. */
326
+ fields: { readonly [field: string]: LiquidSource };
327
+ };
328
+
329
+ export type NotificationTemplateSms = { body: LiquidSource };
330
+
331
+ /** A `notifications/<type key>/<key>.template.ts` file. A template has an email, an SMS, or both. */
332
+ export type NotificationTemplateDefinition = {
333
+ /** Unique within its type, for example `'standard'`. */
334
+ key: string;
335
+ type: NotificationTypeReference;
336
+ name: string;
337
+ /** Generators that the template reads as `{{ ref.<key> }}`. */
338
+ references?: { readonly [key: string]: NotificationReference };
339
+ } & (
340
+ | { email: NotificationTemplateEmail; sms?: NotificationTemplateSms }
341
+ | { email?: NotificationTemplateEmail; sms: NotificationTemplateSms }
342
+ );
343
+
344
+ export declare function defineNotificationTemplate<const T extends NotificationTemplateDefinition>(
345
+ template: T
346
+ ): T;
347
+
348
+ /** A slot in the layout body. A template fills it with Liquid. */
349
+ export type EmailLayoutFieldDefinition = {
350
+ label: string;
351
+ description?: string | null;
352
+ required?: boolean;
353
+ };
354
+
355
+ /** An `email-layouts/<key>/layout.ts` file. */
356
+ export type EmailLayoutDefinition = {
357
+ /**
358
+ * Lowercase letters, digits, and hyphens, for example `'standard'`. Templates
359
+ * refer to it. `'default'` is the built-in layout.
360
+ */
361
+ key: string;
362
+ name: string;
363
+ body: LiquidSource;
364
+ /** The fields that each template fills in. The body reads them as `{{ <field> }}`. */
365
+ fields?: { readonly [key: string]: EmailLayoutFieldDefinition };
366
+ };
367
+
368
+ export declare function defineEmailLayout<const T extends EmailLayoutDefinition>(layout: T): T;
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ // The configuration helpers. The workspace build reads these calls as static
2
+ // data, so the helpers only return their argument. A Vite build or a Node
3
+ // script that imports a configuration file runs them.
4
+ //
5
+ // The field builders come from fields.generated.js, which
6
+ // `artisan app:generate-custom-typescript` generates from the field types.
7
+ //
8
+ // Each function must not refer to anything outside its own body:
9
+ // scripts/lib/runtime-build/app-modules.mjs copies their source into the
10
+ // workspace modules that a page or a workflow imports.
11
+
12
+ export const defineConfig = (config) => config;
13
+
14
+ export const defineResource = (resource) => resource;
15
+
16
+ export const definePage = (page, _component) => page;
17
+
18
+ export const defineNavigation = (navigation) => navigation;
19
+
20
+ export const defineNotificationType = (type) => type;
21
+
22
+ export const defineNotificationTemplate = (template) => template;
23
+
24
+ export const defineEmailLayout = (layout) => layout;
25
+
26
+ export { field } from './fields.generated.js';