@protobase/schema 0.13.3 → 0.14.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.
Files changed (57) hide show
  1. package/dist/access/capability.d.ts +3 -0
  2. package/dist/access/index.d.ts +3 -3
  3. package/dist/access/resolve.d.ts +13 -0
  4. package/dist/access/role-entry.d.ts +5 -0
  5. package/dist/access/roles.d.ts +14 -0
  6. package/dist/access/roles.js +11 -0
  7. package/dist/access/rule.d.ts +10 -0
  8. package/dist/access/rule.js +10 -0
  9. package/dist/access/types.d.ts +11 -0
  10. package/dist/assistant/protocol.d.ts +67 -2
  11. package/dist/bottom-bar-errors.d.ts +3 -0
  12. package/dist/bottom-bar.d.ts +3 -1
  13. package/dist/bottom-bar.js +2 -1
  14. package/dist/events/event-helpers.d.ts +41 -8
  15. package/dist/events/event-helpers.js +1 -3
  16. package/dist/field-ref.d.ts +1 -0
  17. package/dist/field.d.ts +54 -1
  18. package/dist/field.js +16 -0
  19. package/dist/fields.d.ts +31 -0
  20. package/dist/fields.js +31 -0
  21. package/dist/file-field.d.ts +6 -1
  22. package/dist/file-field.js +6 -1
  23. package/dist/files/file-options.d.ts +17 -0
  24. package/dist/filter/check.d.ts +5 -0
  25. package/dist/filter/evaluate.d.ts +1 -0
  26. package/dist/filter/order-by.d.ts +14 -0
  27. package/dist/filter/order-by.js +2 -0
  28. package/dist/filter/parse.d.ts +16 -2
  29. package/dist/filter/parse.js +8 -2
  30. package/dist/filter/where.d.ts +61 -0
  31. package/dist/filter/where.js +34 -0
  32. package/dist/index.d.ts +16 -24
  33. package/dist/index.js +6 -18
  34. package/dist/internal.d.ts +17 -0
  35. package/dist/internal.js +17 -0
  36. package/dist/keys.d.ts +1 -0
  37. package/dist/layout.d.ts +15 -0
  38. package/dist/layout.js +15 -0
  39. package/dist/model.d.ts +344 -6
  40. package/dist/refs.d.ts +6 -0
  41. package/dist/relation-field.d.ts +5 -0
  42. package/dist/relation-field.js +5 -0
  43. package/dist/resource-types.d.ts +18 -0
  44. package/dist/resource.d.ts +94 -6
  45. package/dist/resource.js +75 -2
  46. package/dist/user-menu.d.ts +5 -1
  47. package/dist/user-menu.js +4 -1
  48. package/dist/view-actions.d.ts +1 -0
  49. package/dist/view-displays.d.ts +3 -0
  50. package/dist/view-field.d.ts +14 -1
  51. package/dist/view-field.js +7 -0
  52. package/dist/view-widgets.d.ts +7 -0
  53. package/dist/view-widgets.js +7 -0
  54. package/dist/view.d.ts +52 -4
  55. package/dist/view.js +39 -2
  56. package/dist/webhooks/webhook-resources.js +1 -1
  57. package/package.json +3 -2
package/dist/model.d.ts CHANGED
@@ -1,34 +1,57 @@
1
1
  import { ImageVariant } from "./files/file-options.js";
2
2
  //#region src/model.d.ts
3
+ /** A field's storage type, as `f.*` declares it; see [Field types](website/src/content/docs/reference/data-config.mdx#field-types). */
3
4
  export type FieldType = 'text' | 'integer' | 'bigint' | 'decimal' | 'boolean' | 'date' | 'timestamp' | 'uuid' | 'enum' | 'json' | 'relation' | 'file' | 'currency' | 'country';
5
+ /**
6
+ * A field of a resource as the server and the app see it, built from its `f.*` builder and options; see
7
+ * [Field options](website/src/content/docs/reference/data-config.mdx#field-options).
8
+ */
4
9
  export type FieldModel = {
10
+ /** The field's name: in URLs, the API, filters and sorts. */
5
11
  name: string;
12
+ /** The table column it reads and writes: the snake_case of the name unless `.column()` says otherwise. */
6
13
  column: string;
14
+ /** Its storage type. */
7
15
  type: FieldType;
16
+ /** Whether the value may be `null` (`.optional()`). */
8
17
  nullable: boolean;
18
+ /** Never written through the API (`.readOnly()`). */
9
19
  readOnly: boolean;
20
+ /** May appear in filters and filter widgets (`.filterable()`). */
10
21
  filterable: boolean;
22
+ /** May appear in `order_by` and sorts (`.sortable()`). */
11
23
  sortable: boolean;
24
+ /** Other names filters may use for the field (`.alias()`). */
12
25
  aliases: string[];
26
+ /** An enum's values, in order. */
13
27
  enumValues?: string[];
28
+ /** A relation's target. */
14
29
  relation?: {
30
+ /** The resource it points at. */
15
31
  resource: string;
32
+ /** The columns of this table holding the other record's key, in the order of that key. */
16
33
  columns: string[];
17
34
  };
18
35
  /** Never sent with the record; revealed one value at a time (`.sensitive()`). */
19
36
  sensitive?: true;
20
37
  /** A value the form prefills, or `db` when the database supplies it (identity, now(), ...). */
21
38
  default?: {
39
+ /** The value a create uses when none is given (`.default()`). */
22
40
  value: unknown;
23
41
  } | {
42
+ /** The database supplies the value (`.dbDefault()`). */
24
43
  db: true;
25
44
  };
26
45
  /** A file field's allowed types (empty: any), largest upload in bytes, provider, and what each upload writes. */
27
46
  file?: FileFieldModel;
28
47
  };
48
+ /** A file field's settings, as `/meta` gives them; see [Files](website/src/content/docs/reference/files.mdx). */
29
49
  export type FileFieldModel = {
50
+ /** The content types an upload may have, exact or `type/*`; any when empty. */
30
51
  accept: string[];
52
+ /** The largest upload, in bytes. */
31
53
  maxSize: number;
54
+ /** The name of the storage provider the files go to. */
32
55
  provider: string;
33
56
  /** The fields each upload writes: those of `.derive()` and the placeholder's. */
34
57
  derive?: string[];
@@ -41,15 +64,27 @@ export type FileFieldModel = {
41
64
  };
42
65
  /** `digitsEnd`: only the end of the value's digits, spaces, `+` and dashes ignored (phone numbers). */
43
66
  export type SearchMatch = 'digitsEnd';
67
+ /**
68
+ * A resource as the server and the app see it: a table, its fields and what the API does with them, built by
69
+ * `resource(...).toModel()`; see [resource()](website/src/content/docs/reference/data-config.mdx#resource).
70
+ */
44
71
  export type ResourceModel = {
72
+ /** The resource's name: its address (`/api/v1/<name>`, `/<name>` in the admin) and how other config refers to it. */
45
73
  name: string;
74
+ /** The table it reads and writes. */
46
75
  table: {
76
+ /** The table's schema; the search path decides when omitted. */
47
77
  schema?: string;
78
+ /** The table's name. */
48
79
  name: string;
49
80
  };
81
+ /** Its fields, by field name. */
50
82
  fields: Record<string, FieldModel>;
83
+ /** The fields of its key, in order; records are addressed by it. */
51
84
  primaryKey: string[];
85
+ /** The field a delete sets instead of removing the row, and lists leave such rows out (`.softDelete()`). */
52
86
  softDelete?: string;
87
+ /** The field that separates tenants: every query is limited to the caller's tenant (`.tenant()`). */
53
88
  tenant?: string;
54
89
  /** Field names that the bare-text `search` filter matches against. */
55
90
  search?: string[];
@@ -65,74 +100,118 @@ export type EventOperation = 'create' | 'update' | 'delete' | 'undelete';
65
100
  /**
66
101
  * An event a resource declares. It fires for a change whose operation is listed and that passes every condition:
67
102
  * `when` on the record after the change, `from` on the record before it, `becomes` turning true (false or no record
68
- * before, true after), one of the `changed` fields changing, and a code predicate when `match` is set.
103
+ * before, true after), one of the `changed` fields changing, and a code predicate when `match` is set. See
104
+ * [Declaring events](website/src/content/docs/reference/events.mdx#declaring-events).
69
105
  */
70
106
  export type EventModel = {
71
107
  /** `<resource>.<name>`, such as `orders.created`. */
72
108
  name: string;
109
+ /** The operations it fires for. */
73
110
  operations: EventOperation[];
111
+ /** A filter the record after the change must pass. */
74
112
  when?: string;
113
+ /** A filter the record before the change must pass. */
75
114
  from?: string;
115
+ /** A filter that must turn true with the change: false or no record before, true after. */
76
116
  becomes?: string;
117
+ /** Fields of which at least one must change. */
77
118
  changed?: string[];
78
119
  /** A code predicate decides too; it stays in the config. */
79
120
  match?: true;
80
121
  };
122
+ /** A sort: fields with their direction, the first deciding most. */
81
123
  export type SortSpec = Array<[field: string, direction: 'asc' | 'desc']>;
82
124
  /** A value that survives `JSON.stringify` unchanged, as a custom component's props in a view must be. */
83
125
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
84
126
  [key: string]: JsonValue;
85
127
  };
128
+ /** One part of a record's key: a number for integer keys, text for the others (`bigint` included). */
86
129
  export type KeyValue = string | number;
130
+ /** One page of a list, as the query package reads it from the database. */
87
131
  export type ListQuery = {
132
+ /** The rows it keeps, already checked against the resource. */
88
133
  filter?: CheckedFilter;
134
+ /** The order; the primary key decides ties. */
89
135
  sort?: SortSpec;
136
+ /** The fields it reads, by name; every field when omitted. */
90
137
  columns?: string[];
138
+ /** Where the page starts: a cursor from a previous page. */
91
139
  cursor?: string;
140
+ /** Whether the page comes after the cursor (the default) or before it. */
92
141
  direction?: 'after' | 'before';
142
+ /** The most rows the page holds. */
93
143
  limit: number;
94
144
  };
145
+ /** Where a list page's filters sit on a wide screen: in a `bar` over the list, or a `panel` beside it. */
95
146
  export type FilterLayout = 'bar' | 'panel';
96
147
  /** Where a list's map display looks: the centre in decimal degrees, and MapLibre's zoom, from 0 to 22. */
97
148
  export type MapView = {
149
+ /** The centre's latitude, in decimal degrees. */
98
150
  latitude: number;
151
+ /** The centre's longitude, in decimal degrees. */
99
152
  longitude: number;
153
+ /** MapLibre's zoom, from 0 to 22. */
100
154
  zoom: number;
101
155
  };
102
156
  /**
103
157
  * A place in the app: a resource's list, one of its records, its create form, or a composed page. A record's `key` is
104
158
  * its one value or its parts, as the record API takes them; `readAppPath` gives it back as the API's text form. A create
105
159
  * form's `values` fill in fields of the new record by name, as text (`{ listing_id: '42' }`). A list's `map` is where its
106
- * map display looks.
160
+ * map display looks. See [Addresses](website/src/content/docs/reference/layouts.md#addresses).
107
161
  */
108
162
  export type AppAddress = {
163
+ /** A resource's list. */
109
164
  kind: 'list';
165
+ /** The resource listed. */
110
166
  resource: string;
167
+ /** An AIP-160 filter on the list. */
111
168
  filter?: string;
169
+ /** An AIP-132 order of the list. */
112
170
  orderBy?: string;
171
+ /** The display the list shows in, by name; its default when omitted. */
113
172
  display?: string;
173
+ /** Where its filters sit. */
114
174
  filterLayout?: FilterLayout;
175
+ /** Where its map display looks. */
115
176
  map?: MapView;
116
177
  } | {
178
+ /** One record. */
117
179
  kind: 'record';
180
+ /** The record's resource. */
118
181
  resource: string;
182
+ /** The record's key: its value, or its parts for a composite key. */
119
183
  key: KeyValue | readonly KeyValue[];
120
184
  } | {
185
+ /** The form for a new record. */
121
186
  kind: 'create';
187
+ /** The resource the record is created in. */
122
188
  resource: string;
189
+ /** Fields of the new record filled in by name, as text; a start the person can change. */
123
190
  values?: Readonly<Record<string, string>>;
124
191
  } | {
192
+ /** A composed page. */
125
193
  kind: 'page';
194
+ /** The page's name. */
126
195
  page: string;
127
196
  };
197
+ /**
198
+ * How a value is shown: `badge` as a coloured badge, `code` in monospace, `percent` with a % sign. `relative`,
199
+ * `absolute` and `compact` are carried in `/meta` for apps that render their own lists.
200
+ */
128
201
  export type FieldFormat = 'relative' | 'absolute' | 'compact' | 'percent' | 'badge' | 'code';
202
+ /** A view's hints for one field (`.fields((r) => ...)`); see [Names and columns](website/src/content/docs/reference/ui-config.mdx#names-and-columns). */
129
203
  export type FieldViewModel = {
204
+ /** What the field is called, instead of its name in words. */
130
205
  label?: string;
206
+ /** A line under the field on the record page and in forms. */
131
207
  help?: string;
208
+ /** How the value is shown. */
132
209
  format?: FieldFormat;
210
+ /** Text before a number wherever it is shown, such as `€`. */
133
211
  prefix?: string;
134
212
  /** Text after a number wherever it is shown, such as a unit: `m²` shows `120 m²`. */
135
213
  suffix?: string;
214
+ /** How many decimals a number shows. */
136
215
  decimals?: number;
137
216
  /** An icon before the value on a card's meta row, by the names actions take (`ruler`); an unknown name shows none. */
138
217
  icon?: string;
@@ -148,10 +227,13 @@ export type FieldViewModel = {
148
227
  */
149
228
  widget?: FieldWidgetModel;
150
229
  };
230
+ /** How a field's badges are drawn: `arrow` points them right, as on a rating scale. */
151
231
  export type BadgeShape = 'arrow';
152
232
  /** A field type by the name a plugin registers it under, and the plain data it is given. */
153
233
  export type FieldWidgetModel = {
234
+ /** The name the field type is registered under. */
154
235
  name: string;
236
+ /** The plain data it is given. */
155
237
  props?: Record<string, JsonValue>;
156
238
  };
157
239
  /**
@@ -159,52 +241,89 @@ export type FieldWidgetModel = {
159
241
  * a map of the records at their `latitude` and `longitude`, each opening the card of the cards display named `cards`
160
242
  * (`map`), or a project component (`custom`) drawing the rows the list page loads. A card's `amount` is the first of its fields with
161
243
  * a value, `footer` its fields with a value joined by bullets, and `flags` boolean fields shown as small badges of their
162
- * labels when true. `name` is what the address names (`?display=cards`); `label` and `icon` are its switch's.
244
+ * labels when true. `name` is what the address names (`?display=cards`); `label` and `icon` are its switch's. See
245
+ * [List displays](website/src/content/docs/reference/ui-config.mdx#list-displays).
163
246
  */
164
247
  export type ListDisplayModel = {
248
+ /** A card per record. */
165
249
  kind: 'cards';
250
+ /** What the address names (`?display=cards`). */
166
251
  name: string;
252
+ /** The display's name in the switch. */
167
253
  label?: string;
254
+ /** The display's icon in the switch. */
168
255
  icon?: string;
256
+ /** A file field shown as a picture across the card's top. */
169
257
  image?: string;
258
+ /** The card's title; the view's title when omitted. */
170
259
  title?: string;
260
+ /** The line under the title. */
171
261
  subtitle?: string;
262
+ /** Fields of which the first with a value shows in bold, such as a price or a rent. */
172
263
  amount?: string[];
264
+ /** A field drawn as a badge with its value labels. */
173
265
  badge?: string;
266
+ /** A few small fields, such as dates, each shown when it has a value. */
174
267
  meta?: string[];
268
+ /** Fields joined by bullets on a line at the card's bottom. */
175
269
  footer?: string[];
270
+ /** Boolean fields shown as small badges of their labels when true. */
176
271
  flags?: string[];
272
+ /** The component that draws each card's content instead of the built-in card. */
177
273
  card?: string;
274
+ /** Where the card's further photos come from. */
178
275
  photos?: CardPhotosModel;
179
276
  } | {
277
+ /** A map of the records at their position. */
180
278
  kind: 'map';
279
+ /** What the address names (`?display=map`). */
181
280
  name: string;
281
+ /** The display's name in the switch. */
182
282
  label?: string;
283
+ /** The display's icon in the switch. */
183
284
  icon?: string;
285
+ /** The filterable number field holding each record's latitude, in decimal degrees. */
184
286
  latitude: string;
287
+ /** The filterable number field holding each record's longitude, in decimal degrees. */
185
288
  longitude: string;
289
+ /** The cards display whose card a chosen marker shows; the list's first cards display when omitted. */
186
290
  cards?: string;
187
291
  } | {
292
+ /** The rows drawn by a project component. */
188
293
  kind: 'custom';
294
+ /** What the address names (`?display=board`). */
189
295
  name: string;
296
+ /** The display's name in the switch. */
190
297
  label: string;
298
+ /** The display's icon in the switch. */
191
299
  icon?: string;
300
+ /** The component's name, as `protobase.ui.tsx` registers it. */
192
301
  component: string;
302
+ /** The fields it shows, loaded with the names of the records relations point at. */
193
303
  fields: string[];
304
+ /** The plain data the component gets, with field references where it takes a field. */
194
305
  props?: Record<string, JsonValue>;
195
306
  };
196
307
  /**
197
308
  * Where a card's further photos come from: the records of `resource` whose relation `field` points at the card's record,
198
- * in `sort` order, each shown by its file field `image`. The card loads them when its arrows are first used.
309
+ * in `sort` order, each shown by its file field `image`. The card loads them when its arrows are first used. See
310
+ * [Card photos](website/src/content/docs/reference/ui-config.mdx#card-photos).
199
311
  */
200
312
  export type CardPhotosModel = {
313
+ /** The resource holding the photos. */
201
314
  resource: string;
315
+ /** Its filterable relation that points at the card's record. */
202
316
  field: string;
317
+ /** Its file field holding each photo. */
203
318
  image: string;
319
+ /** An AIP-132 order of the photos. */
204
320
  sort?: string;
205
321
  };
322
+ /** A view's list page (`.list(...)`). */
206
323
  export type ListModel = {
324
+ /** The table's columns, in order. */
207
325
  columns: string[];
326
+ /** The order the list starts in. */
208
327
  sort?: SortSpec;
209
328
  /** Defaults for the UI's search box. `ResourceModel.search` is authoritative for what `search(...)` matches on the server. */
210
329
  search?: string[];
@@ -213,44 +332,76 @@ export type ListModel = {
213
332
  /** The display the list opens in when the address names none; the table when omitted. */
214
333
  default?: string;
215
334
  };
335
+ /** A button of a date range filter: today, yesterday, the last 7, 30 or 90 days, or this month, quarter or year. */
216
336
  export type DatePreset = 'today' | 'yesterday' | '7d' | '30d' | '90d' | 'month' | 'quarter' | 'year';
337
+ /**
338
+ * A filter widget above a list (`.filters((r, w) => ...)`), on a filterable field; see
339
+ * [Filters and the chart](website/src/content/docs/reference/ui-config.mdx#filters-and-the-chart).
340
+ */
217
341
  export type FilterWidgetModel = {
342
+ /** A slider between the lowest and highest value. */
218
343
  kind: 'range';
344
+ /** The field it filters. */
219
345
  field: string;
346
+ /** Whether the values' histogram is drawn behind the slider. */
220
347
  histogram: boolean;
221
348
  } | {
349
+ /** A menu of the field's values with counts. */
222
350
  kind: 'facets';
351
+ /** The field it filters. */
223
352
  field: string;
353
+ /** Whether the menu has a search box, for long lists. */
224
354
  search: boolean;
225
355
  } | {
356
+ /** Buttons for date ranges. */
226
357
  kind: 'dateRange';
358
+ /** The date or timestamp field it filters. */
227
359
  field: string;
360
+ /** The buttons it shows. */
228
361
  presets: DatePreset[];
229
362
  } | {
363
+ /** A switch for a boolean field. */
230
364
  kind: 'toggle';
365
+ /** The boolean field it filters. */
231
366
  field: string;
232
367
  } | {
368
+ /** A menu of boolean fields under one label; a record shows when any chosen field is true. */
233
369
  kind: 'flags';
370
+ /** The boolean fields it offers. */
234
371
  fields: string[];
372
+ /** The menu's label. */
235
373
  label: string;
236
374
  };
237
375
  /**
238
376
  * Records of another resource on the record page: those whose `field` holds this record's key or, with `through`,
239
377
  * those whose `field` holds one of the `key` values of the `through` records that point at this record (a join table).
378
+ * See [Related records](website/src/content/docs/reference/ui-config.mdx#related-records).
240
379
  */
241
380
  export type RelatedModel = {
381
+ /** A related records section. */
242
382
  kind: 'related';
383
+ /** The section's title. */
243
384
  title: string;
385
+ /** A line under the title. */
244
386
  help?: string;
387
+ /** The resource whose records it lists. */
245
388
  resource: string;
389
+ /** The filterable field of `resource` matched against this record's key, or with `through` against the join records' keys. */
246
390
  field: string;
391
+ /** The join records between this record and the listed ones. */
247
392
  through?: {
393
+ /** The join resource. */
248
394
  resource: string;
395
+ /** Its filterable relation that holds this record's key. */
249
396
  field: string;
397
+ /** Its field whose values pick the listed records; its primary key when omitted. */
250
398
  key?: string;
399
+ /** An AIP-160 filter on the join records. */
251
400
  filter?: string;
252
401
  };
402
+ /** An AIP-160 filter on the listed records. */
253
403
  filter?: string;
404
+ /** An AIP-132 order of the listed records. */
254
405
  sort?: string;
255
406
  /** Fields of `resource`, through relations too (`unitId.propertyId`). */
256
407
  columns?: string[];
@@ -258,6 +409,7 @@ export type RelatedModel = {
258
409
  limit?: number;
259
410
  /** The display of `resource`'s list it opens in, such as `cards`; the list's default when omitted. */
260
411
  display?: string;
412
+ /** The text when nothing matches. */
261
413
  empty?: string;
262
414
  };
263
415
  /**
@@ -265,41 +417,66 @@ export type RelatedModel = {
265
417
  * sidebar, under `title` when it has one. It gets `props` and reads the record with `useLayoutRecord()`.
266
418
  */
267
419
  export type CustomSectionModel = {
420
+ /** A custom section. */
268
421
  kind: 'custom';
422
+ /** The component's name, as `protobase.ui.tsx` registers it. */
269
423
  component: string;
424
+ /** The title over it. */
270
425
  title?: string;
426
+ /** The plain data the component gets, with field references where it takes a field. */
271
427
  props?: Record<string, JsonValue>;
428
+ /** Drawn as a sidebar card instead of in the main column. */
272
429
  sidebar?: true;
273
430
  };
274
431
  /**
275
432
  * A map of the record's position, its `latitude` and `longitude` fields in decimal degrees (WGS84), centred on a marker
276
433
  * at `zoom`: in the main column under `title`, or with `sidebar` as a sidebar card. The app draws it with the map style
277
- * its server names, and leaves it out without one.
434
+ * its server names, and leaves it out without one. See [Maps](website/src/content/docs/reference/ui-config.mdx#maps).
278
435
  */
279
436
  export type MapSectionModel = {
437
+ /** A map section. */
280
438
  kind: 'map';
439
+ /** The section's title. */
281
440
  title: string;
441
+ /** The number field holding the record's latitude. */
282
442
  latitude: string;
443
+ /** The number field holding the record's longitude. */
283
444
  longitude: string;
445
+ /** MapLibre's zoom, from 0 to 22; 15 when omitted. */
284
446
  zoom?: number;
447
+ /** Drawn as a sidebar card instead of in the main column. */
285
448
  sidebar?: true;
286
449
  };
450
+ /** An item of the record page (`.layout((r) => ...)` with `l`); see [The record page](website/src/content/docs/reference/ui-config.mdx#the-record-page). */
287
451
  export type LayoutItemModel = {
452
+ /** A titled group of fields in the main column, editable where the user may write. */
288
453
  kind: 'section';
454
+ /** The section's title. */
289
455
  title: string;
456
+ /** The fields it shows, in order. */
290
457
  fields: string[];
458
+ /** A line under the title. */
291
459
  help?: string;
292
460
  } | {
461
+ /** A read-only summary card beside the main column. */
293
462
  kind: 'sidebar';
463
+ /** The fields it shows, in order. */
294
464
  fields: string[];
295
465
  } | RelatedModel | CustomSectionModel | MapSectionModel;
466
+ /** How far back a list's chart reaches. */
296
467
  export type ChartRange = '7d' | '30d' | '90d' | '1y' | 'all';
468
+ /** How long each bar of a list's chart is. */
297
469
  export type ChartGranularity = 'hour' | 'day' | 'week' | 'month';
470
+ /** A chart of records over time above a list, following its filters (`.chart(...)`). */
298
471
  export type ChartModel = {
472
+ /** The date or timestamp field the records are counted by. */
299
473
  field: string;
474
+ /** How far back it reaches. */
300
475
  range: ChartRange;
476
+ /** How long each bar is. */
301
477
  granularity: ChartGranularity;
302
478
  };
479
+ /** How the record page shows that a save went through: a `toast` (the default) or on the Save `button`. */
303
480
  export type SaveFeedback = 'toast' | 'button';
304
481
  /**
305
482
  * What a named action does in the app: set fields of the record (`update`), delete it (`delete`), go to an address in
@@ -307,23 +484,37 @@ export type SaveFeedback = 'toast' | 'button';
307
484
  * values. Without it the app runs a handler it registered by name.
308
485
  */
309
486
  export type ActionRun = {
487
+ /** Updates the record. */
310
488
  kind: 'update';
489
+ /** The fields it sets, by name. */
311
490
  values: Record<string, unknown>;
312
491
  } | {
492
+ /** Deletes the record, soft deleting when the resource does. */
313
493
  kind: 'delete';
314
494
  } | {
495
+ /** Goes to an address in the app. */
315
496
  kind: 'link';
497
+ /** The address, with `{field}` replaced by the record's values. */
316
498
  to: AppAddress;
317
499
  } | {
500
+ /** Opens a link that leaves the app. */
318
501
  kind: 'link';
502
+ /** The link (`https:`, `mailto:`), with `{field}` replaced by the record's values, percent-encoded. */
319
503
  href: string;
320
504
  };
505
+ /** A named action of a view (`.actions((a) => ...)`); see [Named actions](website/src/content/docs/reference/layouts.md#named-actions). */
321
506
  export type ActionModel = {
507
+ /** The action's name, which `<Action name="..." />` and handlers use. */
322
508
  name: string;
509
+ /** The button's label. */
323
510
  label: string;
511
+ /** The button's icon: `bed`, `file-text`, `ruler`, `send` or `wallet`; another name shows a lightning bolt. */
324
512
  icon?: string;
513
+ /** The question the button asks before it runs the action. */
325
514
  confirm?: string;
515
+ /** Whether the action is meant for several records at once (`a.action`'s `bulk`). */
326
516
  bulk: boolean;
517
+ /** What it does; without it the app runs the handler registered under `name`. */
327
518
  run?: ActionRun;
328
519
  };
329
520
  /** The colour of a status dot in the sidebar. */
@@ -343,54 +534,90 @@ export type NavRecentModel = {
343
534
  /** How many records to show, 1 to 20; 3 when omitted. */
344
535
  limit?: number;
345
536
  };
537
+ /** Where a resource sits in the sidebar (`.nav(...)`); see [Sidebar](website/src/content/docs/reference/sidebar.md). */
346
538
  export type NavModel = {
347
539
  /** Hide the resource from the sidebar, for line or link tables. */
348
540
  hidden?: boolean;
541
+ /** The sidebar group it sits in; its database schema when omitted. */
349
542
  group?: string;
543
+ /** Where it sorts within its group. */
350
544
  order?: number;
545
+ /** A few of its records listed under its entry. */
351
546
  recent?: NavRecentModel;
352
547
  };
353
548
  /** An entry in the menu under the signed-in user, at the bottom of the sidebar. */
354
549
  export type UserMenuItemModel = {
550
+ /** Opens a resource's list. */
355
551
  kind: 'resource';
552
+ /** The resource, which leaves the sidebar. */
356
553
  resource: string;
554
+ /** The entry's label; the view's plural name when omitted. */
357
555
  label?: string;
556
+ /** The entry's icon; the resource's sidebar icon when omitted. */
358
557
  icon?: string;
359
558
  } | {
559
+ /** Opens a composed page. */
360
560
  kind: 'page';
561
+ /** The page, which leaves the sidebar. */
361
562
  page: string;
563
+ /** The entry's label; the page's title when omitted. */
362
564
  label?: string;
565
+ /** The entry's icon. */
363
566
  icon?: string;
364
567
  } | {
568
+ /** Opens an address outside the admin in a new tab. */
365
569
  kind: 'link';
570
+ /** The entry's label. */
366
571
  label: string;
572
+ /** The address: `https:`, `http:` or `mailto:`. */
367
573
  href: string;
574
+ /** The entry's icon; `external-link` when omitted. */
368
575
  icon?: string;
369
576
  };
577
+ /** The menu under the signed-in user (`userMenu(...)`); see [User menu](website/src/content/docs/reference/user-menu.md). */
370
578
  export type UserMenuModel = {
579
+ /** Its entries, in order, before the built-in account pages and "Sign out". */
371
580
  items: UserMenuItemModel[];
372
581
  };
373
582
  /** A tab of a bottom bar on phones: icon above an explicit label. */
374
583
  export type BottomBarItemModel = {
584
+ /** Opens a resource's list. */
375
585
  kind: 'resource';
586
+ /** The resource. */
376
587
  resource: string;
588
+ /** The tab's label. */
377
589
  label: string;
590
+ /** The tab's icon; the resource's sidebar icon when omitted. */
378
591
  icon?: string;
379
592
  } | {
593
+ /** Opens a composed page. */
380
594
  kind: 'page';
595
+ /** The page. */
381
596
  page: string;
597
+ /** The tab's label. */
382
598
  label: string;
599
+ /** The tab's icon; the page's own when omitted. */
383
600
  icon?: string;
384
601
  } | {
602
+ /** Opens an address outside the admin in a new tab. */
385
603
  kind: 'link';
604
+ /** The tab's label. */
386
605
  label: string;
606
+ /** The address: `https:`, `http:` or `mailto:`. */
387
607
  href: string;
608
+ /** The tab's icon. */
388
609
  icon?: string;
389
610
  };
390
- /** A bottom bar for the users holding one of `roles`; `label` names it in the role view switcher. */
611
+ /**
612
+ * A bottom bar for the users holding one of `roles`; `label` names it in the role view switcher. See
613
+ * [Bottom bar](website/src/content/docs/reference/bottom-bar.md).
614
+ */
391
615
  export type BottomBarModel = {
616
+ /** The bar's name, where someone with several switches between them. */
392
617
  label: string;
618
+ /** The roles that get the bar; a role belongs to one bar. */
393
619
  roles: string[];
620
+ /** Its tabs, in order, at most four before "More". */
394
621
  items: BottomBarItemModel[];
395
622
  };
396
623
  /**
@@ -398,10 +625,17 @@ export type BottomBarModel = {
398
625
  * field, which then shows instead. Without a subtitle, the first search field not in the title is the default.
399
626
  */
400
627
  export type SearchResultModel = {
628
+ /** Fields joined by spaces into the result's bold line; the view's `title` when omitted. */
401
629
  title?: string[];
630
+ /** The field on the line under it. */
402
631
  subtitle?: string;
403
632
  };
633
+ /**
634
+ * How the admin shows a resource: its names, list, filters, record page and actions, built by `view(...)` and sent to
635
+ * each user in `/meta` without what they cannot see. See [Views](website/src/content/docs/reference/ui-config.mdx).
636
+ */
404
637
  export type ViewModel = {
638
+ /** The resource it shows. */
405
639
  resource: string;
406
640
  /** Shown only to users with one of these roles; a view without roles is the default. */
407
641
  roles?: string[];
@@ -409,128 +643,232 @@ export type ViewModel = {
409
643
  title?: string;
410
644
  /** A record in the global search: a title of one or more fields joined by spaces, over a subtitle field by default. */
411
645
  searchResult?: SearchResultModel;
646
+ /** Where the resource sits in the sidebar. */
412
647
  nav?: NavModel;
648
+ /** What one record and several are called, such as "Repair" and "Repairs"; the resource's name in words when omitted. */
413
649
  names?: {
650
+ /** One record's name, as in "New repair". */
414
651
  singular: string;
652
+ /** The records' name, as in the sidebar. */
415
653
  plural: string;
416
654
  };
655
+ /** An icon name, carried in `/meta` for apps that render their own shell. */
417
656
  icon?: string;
657
+ /** A line under the list's title. */
418
658
  help?: string;
659
+ /** Hints per field, by field name. */
419
660
  fields: Record<string, FieldViewModel>;
661
+ /** The list page; the resource's first six fields as columns when omitted. */
420
662
  list?: ListModel;
663
+ /** The filter widgets above the list, in order. */
421
664
  filters: FilterWidgetModel[];
665
+ /** The record page's items, in order; one section of every field when empty. */
422
666
  layout: LayoutItemModel[];
667
+ /** A chart of records over time above the list. */
423
668
  chart?: ChartModel;
669
+ /** How the record page shows that a save went through. */
424
670
  saveFeedback?: SaveFeedback;
671
+ /** The named actions of its records. */
425
672
  actions: ActionModel[];
426
673
  };
674
+ /** Where a part of a filter or order stands in its text, as character offsets. */
427
675
  export type Span = {
676
+ /** The offset of its first character. */
428
677
  start: number;
678
+ /** The offset after its last character. */
429
679
  end: number;
430
680
  };
681
+ /** A field named in a filter or order, as written: its name, or a path through relations (`customer.name`). */
431
682
  export type FieldPath = {
683
+ /** A field path. */
432
684
  kind: 'field';
685
+ /** The names along the path, the field's own last. */
433
686
  path: string[];
687
+ /** Where it stands in the text. */
434
688
  span: Span;
435
689
  };
690
+ /** The unit of a duration: seconds, minutes, hours, days or weeks. */
436
691
  export type DurationUnit = 's' | 'm' | 'h' | 'd' | 'w';
692
+ /** A length of time in a filter, such as `7d`. */
437
693
  export type DurationLiteral = {
694
+ /** A duration. */
438
695
  kind: 'duration';
696
+ /** How many units. */
439
697
  amount: number;
698
+ /** The unit. */
440
699
  unit: DurationUnit;
700
+ /** Where it stands in the filter text. */
441
701
  span: Span;
442
702
  };
703
+ /** A value written in a filter: text, a number, `true` or `false`, a duration or a timestamp. */
443
704
  export type Literal = {
705
+ /** Quoted text. */
444
706
  kind: 'string';
707
+ /** The text, unescaped. */
445
708
  value: string;
709
+ /** Where it stands in the filter text. */
446
710
  span: Span;
447
711
  } | {
712
+ /** A number. */
448
713
  kind: 'number';
714
+ /** The number. */
449
715
  value: number;
716
+ /** The number as written, which keeps digits a `number` cannot hold, as for `bigint` and `decimal` fields. */
450
717
  raw: string;
718
+ /** Where it stands in the filter text. */
451
719
  span: Span;
452
720
  } | {
721
+ /** `true` or `false`. */
453
722
  kind: 'boolean';
723
+ /** The value. */
454
724
  value: boolean;
725
+ /** Where it stands in the filter text. */
455
726
  span: Span;
456
727
  } | DurationLiteral | {
728
+ /** An RFC 3339 timestamp. */
457
729
  kind: 'timestamp';
730
+ /** The timestamp as written. */
458
731
  value: string;
732
+ /** Where it stands in the filter text. */
459
733
  span: Span;
460
734
  };
735
+ /** `now()` in a filter, the moment the filter runs, with an optional offset such as `now() - 7d`. */
461
736
  export type NowValue = {
737
+ /** The current time. */
462
738
  kind: 'now';
739
+ /** A duration added or taken off. */
463
740
  offset?: {
741
+ /** Whether the duration is added or taken off. */
464
742
  sign: '+' | '-';
743
+ /** The duration. */
465
744
  duration: DurationLiteral;
466
745
  };
746
+ /** Where it stands in the filter text. */
467
747
  span: Span;
468
748
  };
749
+ /** What a field is compared with in a filter: a written value or `now()`. */
469
750
  export type FilterValue = Literal | NowValue;
751
+ /** A comparison in a filter. */
470
752
  export type CompareOp = '=' | '!=' | '<' | '<=' | '>' | '>=';
753
+ /**
754
+ * A filter's syntax tree: AIP-160 with the functions the API adds (`search`, `similar`, `regex`, `isNull`, `in`); see
755
+ * [Filters](website/src/content/docs/reference/api.md#filters). `F` is how a field is represented: a `FieldPath` when
756
+ * parsed (`FilterExpr`), a `FieldModel` once checked (`CheckedFilter`).
757
+ */
471
758
  export type FilterOf<F> = {
759
+ /** Every one of `args` holds (`AND`). */
472
760
  kind: 'and';
761
+ /** The filters joined. */
473
762
  args: FilterOf<F>[];
763
+ /** Where it stands in the filter text. */
474
764
  span: Span;
475
765
  } | {
766
+ /** One of `args` holds (`OR`). */
476
767
  kind: 'or';
768
+ /** The filters joined. */
477
769
  args: FilterOf<F>[];
770
+ /** Where it stands in the filter text. */
478
771
  span: Span;
479
772
  } | {
773
+ /** `arg` does not hold (`NOT`, `-`). */
480
774
  kind: 'not';
775
+ /** The filter negated. */
481
776
  arg: FilterOf<F>;
777
+ /** Where it stands in the filter text. */
482
778
  span: Span;
483
779
  } | {
780
+ /** A comparison of a field with a value. */
484
781
  kind: 'compare';
782
+ /** The comparison. */
485
783
  op: CompareOp;
784
+ /** The field compared. */
486
785
  field: F;
786
+ /** What it is compared with. */
487
787
  value: FilterValue;
788
+ /** Where it stands in the filter text. */
488
789
  span: Span;
489
790
  } | {
791
+ /** `field:value`: a JSON field holds the value as a key or element. */
490
792
  kind: 'has';
793
+ /** The field. */
491
794
  field: F;
795
+ /** The value it holds. */
492
796
  value: Literal;
797
+ /** Where it stands in the filter text. */
493
798
  span: Span;
494
799
  } | {
800
+ /** `field:*`: the field has a value. */
495
801
  kind: 'present';
802
+ /** The field. */
496
803
  field: F;
804
+ /** Where it stands in the filter text. */
497
805
  span: Span;
498
806
  } | {
807
+ /** `in(field, v, ...)`: the field equals one of the values. */
499
808
  kind: 'in';
809
+ /** The field. */
500
810
  field: F;
811
+ /** The values. */
501
812
  values: Literal[];
813
+ /** Where it stands in the filter text. */
502
814
  span: Span;
503
815
  } | {
816
+ /** `search("text")` or bare words: every word appears in one of the resource's search fields. */
504
817
  kind: 'search';
818
+ /** The text searched for. */
505
819
  text: string;
820
+ /** Where it stands in the filter text. */
506
821
  span: Span;
507
822
  } | {
823
+ /** `similar(field, "text")`: the field is like the text, by pg_trgm similarity. */
508
824
  kind: 'similar';
825
+ /** The field. */
509
826
  field: F;
827
+ /** The text it is compared with. */
510
828
  text: string;
829
+ /** Where it stands in the filter text. */
511
830
  span: Span;
512
831
  } | {
832
+ /** `regex(field, "pattern")`: the field matches the regular expression. */
513
833
  kind: 'regex';
834
+ /** The field. */
514
835
  field: F;
836
+ /** The regular expression. */
515
837
  pattern: string;
838
+ /** Where it stands in the filter text. */
516
839
  span: Span;
517
840
  } | {
841
+ /** `isNull(field)`: the field has no value. */
518
842
  kind: 'isNull';
843
+ /** The field. */
519
844
  field: F;
845
+ /** Where it stands in the filter text. */
520
846
  span: Span;
521
847
  };
848
+ /** A filter as parsed, its fields as written (`parseFilter`). */
522
849
  export type FilterExpr = FilterOf<FieldPath>;
850
+ /** A filter checked against a resource, its fields resolved to the resource's (`checkFilter`). */
523
851
  export type CheckedFilter = FilterOf<FieldModel>;
852
+ /** Why a filter or order could not be parsed or checked, as `FilterError.code` names it. */
524
853
  export type FilterErrorCode = 'unexpected-character' | 'unterminated-string' | 'invalid-escape' | 'invalid-number' | 'invalid-timestamp' | 'unexpected-token' | 'expected-token' | 'unknown-function' | 'invalid-arguments' | 'unsupported' | 'too-deep' | 'unknown-field' | 'not-filterable' | 'not-sortable' | 'unsupported-traversal' | 'invalid-value' | 'operator-not-allowed' | 'wildcard-not-allowed' | 'invalid-unicode' | 'too-long' | 'invalid-traversal' | 'wrong-arity' | 'wrong-argument' | 'not-boolean' | 'expected-field' | 'no-search-fields';
854
+ /** A problem with a filter or order, where it is and how to fix it. */
525
855
  export type FilterError = {
856
+ /** What kind of problem it is. */
526
857
  code: FilterErrorCode;
858
+ /** What is wrong. */
527
859
  message: string;
860
+ /** How to fix it. */
528
861
  hint: string;
862
+ /** Where it stands in the text. */
529
863
  span: Span;
530
864
  };
865
+ /** One field of an AIP-132 order, as parsed (`parseOrderBy`). */
531
866
  export type OrderByItem = {
867
+ /** The field, as written. */
532
868
  path: FieldPath;
869
+ /** Its direction. */
533
870
  direction: 'asc' | 'desc';
871
+ /** Where it stands in the order text. */
534
872
  span: Span;
535
873
  };
536
874
  //#endregion