@protobase/schema 0.13.4 → 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
@@ -1,10 +1,13 @@
1
1
  //#region src/access/capability.d.ts
2
+ /** The operation a capability grants; `read` covers listing too. */
2
3
  export type CapabilityAction = 'read' | 'create' | 'update' | 'delete';
4
+ /** The rows a scoped capability narrows to: the user's own (`.own`) or their team's (`.team`), by the resource's owner field. */
3
5
  export type CapabilityScope = 'own' | 'team';
4
6
  /**
5
7
  * `<resource|area>.<action>[.<scope>]` with `*` wildcards, or `*` alone. Pass the names as the
6
8
  * type parameter of `defineRoles` to make unknown names compile-time errors.
7
9
  */
8
10
  export type Capability<Name extends string = string> = '*' | `${Name | '*'}.${CapabilityAction | '*'}` | `${Name | '*'}.${CapabilityAction | '*'}.${CapabilityScope | '*'}`;
11
+ /** The rows a held capability grants: all of them, or those of a scope. */
9
12
  export type Grant = 'all' | CapabilityScope;
10
13
  //#endregion
@@ -1,9 +1,9 @@
1
1
  import { AccessAction, AccessContext, AccessFn, AccessResult, AccessUser, FieldAccess, FieldRule } from "./types.js";
2
- import { Capability, CapabilityAction, CapabilityScope } from "./capability.js";
3
- import { CapabilityLimit, RoleEntry, anonymousRole } from "./role-entry.js";
2
+ import { Capability, CapabilityAction, CapabilityScope, Grant } from "./capability.js";
3
+ import { CapabilityLimit, HeldCapability, RoleEntry, anonymousRole } from "./role-entry.js";
4
4
  import { Rule, rule } from "./rule.js";
5
5
  import { RoleLimit, Roles, RolesOptions, defineRoles } from "./roles.js";
6
6
  import { pickView } from "./pick-view.js";
7
7
  import { AccessSource, ResolveOptions, ResolvedAccess, RowFilterOperation, accessWarnings, checkRecord, fieldsUsedBy, resolveAccess } from "./resolve.js";
8
8
  import { restrictModel } from "./restrict.js";
9
- export { type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type FieldAccess, type FieldRule, type ResolveOptions, type ResolvedAccess, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, accessWarnings, anonymousRole, checkRecord, defineRoles, fieldsUsedBy, pickView, resolveAccess, restrictModel, rule };
9
+ export { type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type FieldAccess, type FieldRule, type Grant, type HeldCapability, type ResolveOptions, type ResolvedAccess, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, accessWarnings, anonymousRole, checkRecord, defineRoles, fieldsUsedBy, pickView, resolveAccess, restrictModel, rule };
@@ -6,10 +6,14 @@ import { Rule } from "./rule.js";
6
6
  //#region src/access/resolve.d.ts
7
7
  /** What the resolver needs from a resource; `ResourceBuilder` satisfies it. */
8
8
  export type AccessSource = {
9
+ /** The resource's model. */
9
10
  toModel: () => ResourceModel;
11
+ /** The resource's rules, by operation (`.access(...)`). */
10
12
  accessRules: Partial<Record<AccessAction, AccessFn>>;
13
+ /** The fields' own rules, by field name. */
11
14
  fieldAccess: () => Record<string, FieldAccess>;
12
15
  };
16
+ /** How `resolveAccess` decides what the resource's rules leave open. */
13
17
  export type ResolveOptions = {
14
18
  /**
15
19
  * The project's roles. When given, an operation without a rule is denied unless the user's
@@ -18,8 +22,11 @@ export type ResolveOptions = {
18
22
  * holding only that role needs its capability even where a rule or `defaultAccess` would allow.
19
23
  */
20
24
  roles?: {
25
+ /** The rule allowing a capability, as `defineRoles` makes it. */
21
26
  can: (capability: any) => Rule;
27
+ /** The fields the user's roles list for a capability, or `undefined` for every field. */
22
28
  fields?: (user: AccessUser, capability: string) => readonly string[] | undefined;
29
+ /** The roles by name, to tell whether the project has an `anonymous` role. */
23
30
  definitions?: Record<string, unknown>;
24
31
  };
25
32
  /**
@@ -31,15 +38,21 @@ export type ResolveOptions = {
31
38
  };
32
39
  /** Startup warnings for risky option combinations; the caller decides how to report them. */
33
40
  export declare const accessWarnings: (options: ResolveOptions) => string[];
41
+ /** An operation a row filter applies to. */
34
42
  export type RowFilterOperation = 'read' | 'create' | 'update' | 'delete';
43
+ /** What one request may do with one resource, as `resolveAccess` works it out. */
35
44
  export type ResolvedAccess = {
45
+ /** Whether each operation is allowed at all; one in `recordChecks` or `rowFilter` only for some records. */
36
46
  operations: Record<AccessAction, boolean>;
37
47
  /** Operations whose rule needs the record (or input); check them with `checkRecord`. */
38
48
  recordChecks: AccessAction[];
39
49
  /** Columns the caller may see; everything else must be left out of results, filters, sorts and search. */
40
50
  readableFields: string[];
51
+ /** Columns the caller may write. */
41
52
  writableFields: {
53
+ /** Those a create may set. */
42
54
  create: string[];
55
+ /** Those an update may change. */
43
56
  update: string[];
44
57
  };
45
58
  /** ANDed into queries (`read` covers list and read) or checked against records (`create`). */
@@ -6,15 +6,20 @@ import { Capability } from "./capability.js";
6
6
  * text that is ANDed into its rows (`{ filter: 'published = true' }`), or both (`{ fields: [...], filter: '...' }`).
7
7
  */
8
8
  export type CapabilityLimit = readonly string[] | {
9
+ /** The only fields the capability reads or writes. */
9
10
  readonly fields?: readonly string[];
11
+ /** AIP-160 text the capability's rows must match. */
10
12
  readonly filter?: string;
11
13
  };
12
14
  /** One entry of a role: a capability, or capabilities each with its limit, `{ 'products.read': ['sku', 'price'] }`. */
13
15
  export type RoleEntry<Name extends string = string> = Capability<Name> | { readonly [C in Capability<Name>]?: CapabilityLimit; };
14
16
  /** A capability a role holds, with the fields and rows it is limited to when it has a limit. */
15
17
  export type HeldCapability = {
18
+ /** The capability, such as `products.read`. */
16
19
  capability: string;
20
+ /** The only fields it reads or writes; every field when omitted. */
17
21
  fields?: readonly string[];
22
+ /** The filter its rows must match, parsed. */
18
23
  filter?: FilterExpr;
19
24
  };
20
25
  /** Requests without a token act as this role, when the project defines it. */
@@ -3,6 +3,7 @@ import { Capability, Grant } from "./capability.js";
3
3
  import { HeldCapability, RoleEntry } from "./role-entry.js";
4
4
  import { Rule } from "./rule.js";
5
5
  //#region src/access/roles.d.ts
6
+ /** The second argument of `defineRoles`. */
6
7
  export type RolesOptions = {
7
8
  /** Ids of the users in `user`'s team; `.team` scopes become `owner IN teamOf(user)`. */
8
9
  teamOf?: (user: AccessUser) => readonly (string | number)[] | Promise<readonly (string | number)[]>;
@@ -11,6 +12,7 @@ export type RolesOptions = {
11
12
  };
12
13
  /** A capability of a role that is limited to fields or rows; the server checks them against the resources at startup. */
13
14
  export type RoleLimit = HeldCapability & {
15
+ /** The role holding it. */
14
16
  role: string;
15
17
  };
16
18
  /**
@@ -21,8 +23,19 @@ export type RoleLimit = HeldCapability & {
21
23
  * An entry may limit capabilities to fields (an allowlist) and rows (an AIP-160 filter):
22
24
  * `{ 'products.read': ['sku', 'price'] }`, `{ 'products.read': { fields: ['name'], filter: 'published = true' } }`.
23
25
  * The role `anonymous`, when defined, is what requests without a token act as; it may only read listed fields.
26
+ * See [Access](website/src/content/docs/reference/access.mdx#capabilities).
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const roles = defineRoles<'orders' | 'products'>({
31
+ * admin: ['*'],
32
+ * sales: ['orders.read.own', 'orders.create', 'products.read'],
33
+ * reseller: [{ 'products.read': { fields: ['sku', 'name', 'price'], filter: 'published = true' } }],
34
+ * })
35
+ * ```
24
36
  */
25
37
  export declare const defineRoles: <Name extends string = string>(definitions: Record<string, readonly RoleEntry<Name>[]>, options?: RolesOptions) => {
38
+ /** The roles as defined, by name. */
26
39
  definitions: Record<string, readonly RoleEntry<Name>[]>;
27
40
  grants: (user: AccessUser, capability: string) => Set<Grant>;
28
41
  /** The capabilities limited to fields or rows, by role. */
@@ -42,5 +55,6 @@ export declare const defineRoles: <Name extends string = string>(definitions: Re
42
55
  /** A rule that allows when the user has any of the roles. */
43
56
  is: (...roles: string[]) => Rule;
44
57
  };
58
+ /** The roles `defineRoles` returns, as the server's `options.roles` takes them. */
45
59
  export type Roles = ReturnType<typeof defineRoles>;
46
60
  //#endregion
@@ -12,6 +12,16 @@ const rolesOf = (user) => user.roles ?? [];
12
12
  * An entry may limit capabilities to fields (an allowlist) and rows (an AIP-160 filter):
13
13
  * `{ 'products.read': ['sku', 'price'] }`, `{ 'products.read': { fields: ['name'], filter: 'published = true' } }`.
14
14
  * The role `anonymous`, when defined, is what requests without a token act as; it may only read listed fields.
15
+ * See [Access](website/src/content/docs/reference/access.mdx#capabilities).
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const roles = defineRoles<'orders' | 'products'>({
20
+ * admin: ['*'],
21
+ * sales: ['orders.read.own', 'orders.create', 'products.read'],
22
+ * reseller: [{ 'products.read': { fields: ['sku', 'name', 'price'], filter: 'published = true' } }],
23
+ * })
24
+ * ```
15
25
  */
16
26
  const defineRoles = (definitions, options = {}) => {
17
27
  const held = new Map(Object.entries(definitions).map(([role, entries]) => [role, heldCapabilities(role, entries)]));
@@ -34,6 +44,7 @@ const defineRoles = (definitions, options = {}) => {
34
44
  return ids.length === 0 ? false : where.in(owner, [...ids]);
35
45
  };
36
46
  return {
47
+ /** The roles as defined, by name. */
37
48
  definitions,
38
49
  grants,
39
50
  /** The capabilities limited to fields or rows, by role. */
@@ -7,5 +7,15 @@ export type Rule<R = any> = AccessFn<R> & {
7
7
  /** Either may allow; filters are ORed. */
8
8
  or: (other: AccessFn<R>) => Rule<R>;
9
9
  };
10
+ /**
11
+ * Wraps an access function so it combines with others through `.and()` and `.or()`; `roles.can()` and `roles.is()`
12
+ * return such rules already.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const notShipped = rule((ctx, order) => order?.status !== 'shipped')
17
+ * resource('orders').access({ update: notShipped.and(roles.can('orders.update')) })
18
+ * ```
19
+ */
10
20
  export declare const rule: <R = any>(fn: AccessFn<R>) => Rule<R>;
11
21
  //#endregion
@@ -1,6 +1,16 @@
1
1
  import { andResults, orResults } from "./combine.js";
2
2
  //#region src/access/rule.ts
3
3
  const combine = (join, left, right) => async (ctx, record, input) => join(await left(ctx, record, input), await right(ctx, record, input));
4
+ /**
5
+ * Wraps an access function so it combines with others through `.and()` and `.or()`; `roles.can()` and `roles.is()`
6
+ * return such rules already.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const notShipped = rule((ctx, order) => order?.status !== 'shipped')
11
+ * resource('orders').access({ update: notShipped.and(roles.can('orders.update')) })
12
+ * ```
13
+ */
4
14
  const rule = (fn) => Object.assign((ctx, record, input) => fn(ctx, record, input), {
5
15
  and: (other) => rule(combine(andResults, fn, other)),
6
16
  or: (other) => rule(combine(orResults, fn, other))
@@ -1,19 +1,26 @@
1
1
  import { FilterExpr, ResourceModel } from "../model.js";
2
2
  //#region src/access/types.d.ts
3
+ /** An operation on a resource that access rules decide; `list` falls back to the `read` rule. */
3
4
  export type AccessAction = 'list' | 'read' | 'create' | 'update' | 'delete';
4
5
  /** The signed-in user as access rules see it; roles come from the auth user record. */
5
6
  export type AccessUser = {
7
+ /** The user's id, which `.own` compares with the resource's `.owner(...)` field. */
6
8
  id?: string | number;
9
+ /** The user's roles, with organizations those of the membership too. */
7
10
  roles?: readonly string[];
8
11
  [key: string]: unknown;
9
12
  };
13
+ /** What an access rule is given: who asks, for which operation, and where. */
10
14
  export type AccessContext = {
11
15
  /** Always set by the server; optional in the type so rules stay callable with a bare context. */
12
16
  user?: AccessUser;
17
+ /** The operation being decided. */
13
18
  operation?: AccessAction;
19
+ /** The record the operation is on, when there is one. */
14
20
  record?: unknown;
15
21
  /** With organizations, the one the request works in; `.tenant` resources are scoped to it already. */
16
22
  organization?: {
23
+ /** The organization's id. */
17
24
  id: string | number;
18
25
  };
19
26
  /** Set by the resolver; scoped capabilities (`.own`) need `model.owner`. */
@@ -33,9 +40,13 @@ export type AccessResult = boolean | string | FilterExpr;
33
40
  export type AccessFn<R = any> = (ctx: AccessContext, record?: R, input?: Partial<R>) => AccessResult | Promise<AccessResult>;
34
41
  /** A field rule is role-based: it never sees a record, so readable columns are fixed per request. */
35
42
  export type FieldRule = (ctx: AccessContext) => AccessResult | Promise<AccessResult>;
43
+ /** A field's own rules, `.access({ read, create, update })` on the field; an operation without one follows the resource. */
36
44
  export type FieldAccess = {
45
+ /** Whether the caller may see the field. */
37
46
  read?: FieldRule;
47
+ /** Whether the caller may set the field when creating a record. */
38
48
  create?: FieldRule;
49
+ /** Whether the caller may change the field when updating a record. */
39
50
  update?: FieldRule;
40
51
  };
41
52
  //#endregion
@@ -1,58 +1,96 @@
1
1
  //#region src/assistant/protocol.d.ts
2
+ /** The colour of a card, by what it reports. */
2
3
  export type AssistantTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger';
3
4
  /** A button on a card. Clicking it posts `{ partId, actionId }`; with `confirm`, only after the user confirms that text. */
4
5
  export type AssistantAction = {
6
+ /** What the click posts as `actionId`, unique on its card. */
5
7
  id: string;
8
+ /** The button's text. */
6
9
  label: string;
10
+ /** How the button looks; `secondary` when omitted. */
7
11
  style?: 'primary' | 'secondary' | 'ghost' | 'danger';
12
+ /** Shown but not clickable. */
8
13
  disabled?: boolean;
9
14
  /** A line under the buttons, such as why one is disabled. */
10
15
  hint?: string;
16
+ /** A question the user confirms before the click is posted, such as `Run this update?`. */
11
17
  confirm?: string;
12
18
  };
19
+ /** Where a step of a card's work stands. */
13
20
  export type AssistantStepState = 'pending' | 'running' | 'done' | 'failed';
21
+ /** One step of a card's work, listed with its state. */
14
22
  export type AssistantStep = {
23
+ /** What the step does. */
15
24
  label: string;
25
+ /** Where it stands. */
16
26
  state: AssistantStepState;
17
27
  };
18
28
  /** Changed lines: `+` added, `-` removed, anything else context; `start` is the first line's number. */
19
29
  export type AssistantDiff = {
30
+ /** What changed, such as a file's path, shown above the lines. */
20
31
  path?: string;
32
+ /** The lines, one per line of text, each starting with `+`, `-` or anything else. */
21
33
  source: string;
34
+ /** The number of the first line; 1 when omitted. */
22
35
  start?: number;
23
36
  };
37
+ /** A paragraph of plain text. */
24
38
  export type AssistantTextPart = {
39
+ /** Marks a text part. */
25
40
  type: 'text';
41
+ /** The part's id, unique in its message; a part with the same id replaces it. */
26
42
  id: string;
43
+ /** The text. */
27
44
  text: string;
28
45
  };
29
46
  /** Rows of values; `null` stays visible, objects show as JSON. `truncated` says the rows stop at a limit. */
30
47
  export type AssistantTablePart = {
48
+ /** Marks a table part. */
31
49
  type: 'table';
50
+ /** The part's id, unique in its message; a part with the same id replaces it. */
32
51
  id: string;
52
+ /** The column headings. */
33
53
  columns: string[];
54
+ /** One array per row, a value per column. */
34
55
  rows: unknown[][];
56
+ /** A line under the table, such as the query it came from. */
35
57
  caption?: string;
58
+ /** The rows stop at a limit, so there are more than shown. */
36
59
  truncated?: boolean;
37
60
  };
61
+ /**
62
+ * A card with a title, and in this order whichever of `body`, `code`, `fields`, `steps` and `diffs` it has, then its
63
+ * `note` and `actions`: what a backend proposes or reports, such as a query waiting for approval.
64
+ */
38
65
  export type AssistantCardPart = {
66
+ /** Marks a card part. */
39
67
  type: 'card';
68
+ /** The part's id, unique in its message; a part with the same id replaces it, and button clicks post it as `partId`. */
40
69
  id: string;
70
+ /** The card's title. */
41
71
  title: string;
72
+ /** The card's colour; `neutral` when omitted. */
42
73
  tone?: AssistantTone;
43
74
  /** Short text at the end of the title row. */
44
75
  badge?: string;
76
+ /** A paragraph of text. */
45
77
  body?: string;
46
78
  /** Monospace text, such as a query. */
47
79
  code?: string;
80
+ /** Labelled values, one per line. */
48
81
  fields?: Array<{
82
+ /** The value's label. */
49
83
  label: string;
84
+ /** The value, as text. */
50
85
  value: string;
51
86
  }>;
87
+ /** The steps of the card's work, each with its state. */
52
88
  steps?: AssistantStep[];
89
+ /** Changed lines, such as of a file or a record. */
53
90
  diffs?: AssistantDiff[];
54
91
  /** A muted line under the content, such as what happened to the card. */
55
92
  note?: string;
93
+ /** Buttons at the card's bottom. */
56
94
  actions?: AssistantAction[];
57
95
  };
58
96
  /**
@@ -60,7 +98,9 @@ export type AssistantCardPart = {
60
98
  * component's name and props such as `{ taskId }`, so the part shows the record as it is now.
61
99
  */
62
100
  export type AssistantWidgetPart = {
101
+ /** Marks a widget part. */
63
102
  type: 'widget';
103
+ /** The part's id, unique in its message; a part with the same id replaces it. */
64
104
  id: string;
65
105
  /** A component in the app's `components` (its protobase.ui.tsx and the UI configs it extends), PascalCase. */
66
106
  name: string;
@@ -68,17 +108,26 @@ export type AssistantWidgetPart = {
68
108
  props: Record<string, unknown>;
69
109
  /** Drawn, as a plain card, when the app has no component by that name: what the part was about when it was made. */
70
110
  fallback?: {
111
+ /** The card's title. */
71
112
  title: string;
113
+ /** The card's text. */
72
114
  body?: string;
73
115
  };
74
116
  };
117
+ /** A piece of a message, drawn by its `type`; code that switches over it handles every type. */
75
118
  export type AssistantPart = AssistantTextPart | AssistantTablePart | AssistantCardPart | AssistantWidgetPart;
119
+ /** A message of the chat: what the user wrote, or the backend's answer in parts. */
76
120
  export type AssistantMessage = {
121
+ /** The message's id; a message with the same id replaces it. */
77
122
  id: string;
123
+ /** Who wrote it. */
78
124
  from: 'user' | 'assistant';
125
+ /** What it shows, in order. */
79
126
  parts: AssistantPart[];
80
127
  };
128
+ /** Everything the dock shows: the chat and its header, as the event stream builds it up. */
81
129
  export type AssistantState = {
130
+ /** The chat, oldest first. */
82
131
  messages: AssistantMessage[];
83
132
  /** The backend is answering: the user can write but not send. */
84
133
  replying: boolean;
@@ -90,32 +139,48 @@ export type AssistantState = {
90
139
  /**
91
140
  * What the event stream sends. `state` replaces everything and always comes first on a connection; `message` adds a
92
141
  * message or replaces the one with its id; `part` adds a part to a message or replaces the one with its id; `patch`
93
- * changes the top-level fields, `null` clearing one.
142
+ * changes the top-level fields, `null` clearing one. A backend in place of the built-in one sends these; see
143
+ * [Another backend](website/src/content/docs/reference/assistant.md#another-backend).
94
144
  */
95
145
  export type AssistantEvent = {
146
+ /** Replaces the whole state. */
96
147
  type: 'state';
148
+ /** The new state. */
97
149
  state: AssistantState;
98
150
  } | {
151
+ /** Adds or replaces a message. */
99
152
  type: 'message';
153
+ /** The message. */
100
154
  message: AssistantMessage;
101
155
  } | {
156
+ /** Adds or replaces a part of a message. */
102
157
  type: 'part';
158
+ /** The id of the message the part belongs to. */
103
159
  messageId: string;
160
+ /** The part. */
104
161
  part: AssistantPart;
105
162
  } | {
163
+ /** Changes the state's top-level fields. */
106
164
  type: 'patch';
165
+ /** The new `replying`, when it changes. */
107
166
  replying?: boolean;
167
+ /** The new `status`, or `null` to clear it. */
108
168
  status?: string | null;
169
+ /** The new `placeholder`, or `null` to clear it. */
109
170
  placeholder?: string | null;
110
171
  };
111
172
  /** `POST <url>/messages`; `page` is where the user is in the app, below its base path, such as `/orders/42?status=open`. */
112
173
  export type AssistantMessageRequest = {
174
+ /** What the user wrote. */
113
175
  text: string;
176
+ /** Where the user is in the app when they send it. */
114
177
  page?: string;
115
178
  };
116
- /** `POST <url>/actions` */
179
+ /** `POST <url>/actions`: the user clicked a button of a card. */
117
180
  export type AssistantActionRequest = {
181
+ /** The card's `id`. */
118
182
  partId: string;
183
+ /** The button's `id`. */
119
184
  actionId: string;
120
185
  };
121
186
  //#endregion
@@ -2,8 +2,11 @@ import { BottomBarModel } from "./model.js";
2
2
  //#region src/bottom-bar-errors.d.ts
3
3
  /** What the bottom bars may name. Without `roles` (an app whose roles are not declared) any role is accepted. */
4
4
  export type BottomBarNames = {
5
+ /** The app's resources. */
5
6
  resources: readonly string[];
7
+ /** The app's composed pages. */
6
8
  pages: readonly string[];
9
+ /** The app's roles, when it declares them. */
7
10
  roles?: readonly string[];
8
11
  };
9
12
  /**
@@ -20,11 +20,13 @@ declare const items: {
20
20
  };
21
21
  /** What the server needs from a `bottomBar(...)` export of the config module. */
22
22
  export type BottomBarSource = {
23
+ /** The bar's model, a copy each time. */
23
24
  toBottomBarModel(): BottomBarModel;
24
25
  };
25
26
  /**
26
27
  * Tabs at the bottom of the screen on phones, for the users holding one of `roles`. Export one per group of roles; a
27
- * role belongs to one bottom bar. `label` names the bar where someone with several switches between them.
28
+ * role belongs to one bottom bar. `label` names the bar where someone with several switches between them. Its tabs
29
+ * are `m.resource`, `m.page` and `m.link`, at most four. See [Bottom bar](website/src/content/docs/reference/bottom-bar.md).
28
30
  */
29
31
  export declare const bottomBar: (options: {
30
32
  label: string;
@@ -28,7 +28,8 @@ const items = {
28
28
  };
29
29
  /**
30
30
  * Tabs at the bottom of the screen on phones, for the users holding one of `roles`. Export one per group of roles; a
31
- * role belongs to one bottom bar. `label` names the bar where someone with several switches between them.
31
+ * role belongs to one bottom bar. `label` names the bar where someone with several switches between them. Its tabs
32
+ * are `m.resource`, `m.page` and `m.link`, at most four. See [Bottom bar](website/src/content/docs/reference/bottom-bar.md).
32
33
  */
33
34
  const bottomBar = (options, build) => {
34
35
  const model = {
@@ -3,7 +3,9 @@ import { Ref } from "../refs.js";
3
3
  //#region src/events/event-helpers.d.ts
4
4
  /** Who made a write through the API; writes from anywhere else have none. */
5
5
  export type EventActor = {
6
+ /** The user's id. */
6
7
  id: string | number;
8
+ /** The user's roles. */
7
9
  roles: string[];
8
10
  };
9
11
  /**
@@ -11,60 +13,91 @@ export type EventActor = {
11
13
  * sensitive fields, whose names `changed` still lists.
12
14
  */
13
15
  export type EventContext<R = Record<string, unknown>> = {
16
+ /** What happened to the record. */
14
17
  operation: EventOperation;
18
+ /** The record before the change, as it was at the start of the transaction; none for a create. */
15
19
  before?: Partial<R>;
20
+ /** The record after the change, as it is at commit; none for a delete. */
16
21
  after?: Partial<R>;
22
+ /** The names of the fields that changed, sensitive ones included. */
17
23
  changed: string[];
18
24
  /** `null` for a write made outside the API (psql, another service, a database trigger). */
19
25
  actor: EventActor | null;
26
+ /** `api` for a write through the API, `database` for any other. */
20
27
  source: 'api' | 'database';
28
+ /** The record's tenant, when the resource has one. */
21
29
  tenant?: string | number;
22
30
  /** The writing transaction's id. */
23
31
  transaction: string;
24
32
  };
33
+ /** An event's code predicate: whether a change raises the event, for what a filter cannot say. */
25
34
  export type EventMatch<R> = (context: EventContext<R>) => boolean;
26
35
  /** `when` and `from` are filters in the language of access rules and views, on the record after and before the change. */
27
36
  export type CreatedOptions<R> = {
37
+ /** A filter the created record meets. */
28
38
  when?: string;
39
+ /** A code predicate the change meets. */
29
40
  match?: EventMatch<R>;
30
41
  };
42
+ /** The conditions of `e.updated()`, which combine with AND. */
31
43
  export type UpdatedOptions<R> = {
44
+ /** A filter the record meets after the change. */
32
45
  when?: string;
46
+ /** A filter the record met before the change. */
33
47
  from?: string;
48
+ /** Fields of which one changed; it works on sensitive fields too. */
34
49
  changed?: Ref[];
50
+ /** A code predicate the change meets. */
35
51
  match?: EventMatch<R>;
36
52
  };
53
+ /** The conditions of `e.deleted()`, which combine with AND. */
37
54
  export type DeletedOptions<R> = {
55
+ /** A filter the record met before it was deleted. */
38
56
  from?: string;
57
+ /** A code predicate the change meets. */
39
58
  match?: EventMatch<R>;
40
59
  };
60
+ /** The conditions of `e.undeleted()`, which combine with AND. */
41
61
  export type UndeletedOptions<R> = {
62
+ /** A filter the record meets once restored. */
42
63
  when?: string;
64
+ /** A filter the record met while deleted. */
43
65
  from?: string;
66
+ /** A code predicate the change meets. */
44
67
  match?: EventMatch<R>;
45
68
  };
46
69
  /** An event as declared; `buildEventModels` checks it against the resource. */
47
70
  export type EventSpec = {
71
+ /** The event's name within the resource, such as `created`; the event is `<resource>.<name>`. */
48
72
  name: string;
73
+ /** The operations that may raise it. */
49
74
  operations: EventOperation[];
75
+ /** A filter on the record after the change. */
50
76
  when?: string;
77
+ /** A filter on the record before the change. */
51
78
  from?: string;
79
+ /** A filter that turns true with the change. */
52
80
  becomes?: string;
81
+ /** The fields of which one must change, by name. */
53
82
  changed?: string[];
83
+ /** A code predicate the change must meet. */
54
84
  match?: EventMatch<any>;
55
85
  /** Kept only when the resource soft deletes: the `undeleted` of `e.crud()`. */
56
86
  softDeleteOnly?: true;
57
87
  };
58
88
  /** The `e` of `.events((e, r) => [...])`. Options combine with AND. */
59
- export declare const eventHelpers: <R>() => {
60
- created: (name?: string, options?: CreatedOptions<R>) => EventSpec;
61
- updated: (name?: string, options?: UpdatedOptions<R>) => EventSpec;
62
- deleted: (name?: string, options?: DeletedOptions<R>) => EventSpec;
63
- undeleted: (name?: string, options?: UndeletedOptions<R>) => EventSpec;
89
+ export type EventHelpers<R> = {
90
+ /** Fires when a record is created; named `created` unless `name` says otherwise. */
91
+ created(name?: string, options?: CreatedOptions<R>): EventSpec;
92
+ /** Fires when a record is updated; named `updated` unless `name` says otherwise. */
93
+ updated(name?: string, options?: UpdatedOptions<R>): EventSpec;
94
+ /** Fires when a record is deleted, soft deletes included; named `deleted` unless `name` says otherwise. */
95
+ deleted(name?: string, options?: DeletedOptions<R>): EventSpec;
96
+ /** Fires when a soft deleted record is restored; named `undeleted` unless `name` says otherwise. */
97
+ undeleted(name?: string, options?: UndeletedOptions<R>): EventSpec;
64
98
  /** Fires when `condition` turns true: on a create that meets it, or an update after which it holds and before which it did not. */
65
- becomes: (name: string, condition: string) => EventSpec;
99
+ becomes(name: string, condition: string): EventSpec;
66
100
  /** created, updated and deleted, plus undeleted when the resource soft deletes. */
67
- crud: () => EventSpec[];
101
+ crud(): EventSpec[];
68
102
  };
69
- export type EventHelpers<R> = ReturnType<typeof eventHelpers<R>>;
70
103
  //#endregion
@@ -5,7 +5,7 @@ const spec = (name, operation, { changed, ...options }) => ({
5
5
  ...options,
6
6
  ...changed && { changed: changed.map((ref) => ref.name) }
7
7
  });
8
- /** The `e` of `.events((e, r) => [...])`. Options combine with AND. */
8
+ /** The helpers handed to `.events()`, typed against the resource's record `R`. */
9
9
  const eventHelpers = () => {
10
10
  const created = (name = "created", options = {}) => spec(name, "create", options);
11
11
  const updated = (name = "updated", options = {}) => spec(name, "update", options);
@@ -16,13 +16,11 @@ const eventHelpers = () => {
16
16
  updated,
17
17
  deleted,
18
18
  undeleted,
19
- /** Fires when `condition` turns true: on a create that meets it, or an update after which it holds and before which it did not. */
20
19
  becomes: (name, condition) => ({
21
20
  name,
22
21
  operations: ["create", "update"],
23
22
  becomes: condition
24
23
  }),
25
- /** created, updated and deleted, plus undeleted when the resource soft deletes. */
26
24
  crud: () => [
27
25
  created(),
28
26
  updated(),
@@ -5,6 +5,7 @@ import { JsonValue } from "./model.js";
5
5
  * relations (`plan.name`) on a page. Marked, so that the walker leaving out hidden fields finds it wherever it is.
6
6
  */
7
7
  export type FieldRefValue = {
8
+ /** The field's name, or a path through relations. */
8
9
  readonly $field: string;
9
10
  };
10
11
  /** A field reference for a layout's custom component props, such as `<UsageChart field={fieldRef('total')} />`. */