@protobase/schema 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FileOptions, FileProcessor, FileProcessorInput, ImageFormat, ImageVariant, ImageVariantOptions, imageFormats } from "./files/file-options.js";
2
- import { ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CompareOp, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NavModel, NavRecentModel, NowValue, OrderByItem, RelatedModel, ResourceModel, SaveFeedback, SearchMatch, SearchResultModel, SortSpec, Span, StatusTone, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
2
+ import { ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CardFavoriteModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CompareOp, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NavModel, NavRecentModel, NowValue, OrderByItem, RelatedModel, ResourceModel, SaveFeedback, SearchMatch, SearchResultModel, SignInPromptModel, SignInViewModel, SortSpec, Span, StatusTone, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
3
3
  import { AccessAction, AccessContext, AccessFn, AccessResult, AccessUser, FieldAccess, FieldRule } from "./access/types.js";
4
4
  import { AnyField, Constraint, Field, FieldMeta, FieldTypeDef } from "./field.js";
5
5
  import { RelationField } from "./relation-field.js";
@@ -36,4 +36,4 @@ import { AccessSource, ResolveOptions, ResolvedAccess, RowFilterOperation, acces
36
36
  import { restrictModel } from "./access/restrict.js";
37
37
  import "./access/index.js";
38
38
  import { AssistantAction, AssistantActionRequest, AssistantCardPart, AssistantDiff, AssistantEvent, AssistantMessage, AssistantMessageRequest, AssistantPart, AssistantState, AssistantStep, AssistantStepState, AssistantTablePart, AssistantTextPart, AssistantTone, AssistantWidgetPart } from "./assistant/protocol.js";
39
- export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardPhotosModel, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type ComponentRef, type Constraint, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type Grant, type HeldCapability, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type RecordKey, type RecordOf, type Ref, type Refs, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SortSpec, type Span, type StatusTone, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, resolveAccess, resource, restrictModel, rule, userMenu, view, where };
39
+ export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardFavoriteModel, type CardPhotosModel, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type ComponentRef, type Constraint, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type Grant, type HeldCapability, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type RecordKey, type RecordOf, type Ref, type Refs, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SignInPromptModel, type SignInViewModel, type SortSpec, type Span, type StatusTone, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, imageFormats, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, resolveAccess, resource, restrictModel, rule, userMenu, view, where };
@@ -1,5 +1,5 @@
1
1
  import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
2
- import { displayFields, tableDisplay } from "./view-displays.js";
2
+ import { cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
3
3
  import { fieldRefsIn, isFieldRef, markFieldRefs, unmarkFieldRefs } from "./field-ref.js";
4
4
  import { bottomBarFor } from "./bottom-bar.js";
5
5
  import { decodeKey, encodeKey } from "./keys.js";
@@ -14,4 +14,4 @@ import { addressPlaceholders, appHrefError, fillAddress, hrefPlaceholders, leave
14
14
  import { AddressCheck, addressErrors } from "./address-errors.js";
15
15
  import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
16
16
  import { readEventStream } from "./assistant/event-stream.js";
17
- export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefPlaceholders, inRange, isFieldRef, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
17
+ export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, cardPhotoLimit, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefPlaceholders, inRange, isFieldRef, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
package/dist/internal.js CHANGED
@@ -3,7 +3,7 @@ import { checkEventFilter } from "./events/event-models.js";
3
3
  import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
4
4
  import { addressPlaceholders, appHrefError, fillAddress, hrefPlaceholders, leavesApp } from "./address-template.js";
5
5
  import { fieldRefsIn, isFieldRef, markFieldRefs, unmarkFieldRefs } from "./field-ref.js";
6
- import { displayFields, tableDisplay } from "./view-displays.js";
6
+ import { cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
7
7
  import { bottomBarFor } from "./bottom-bar.js";
8
8
  import { decodeKey, encodeKey } from "./keys.js";
9
9
  import { webhookResources } from "./webhooks/webhook-resources.js";
@@ -14,4 +14,4 @@ import { redactSecrets } from "./redact-secrets.js";
14
14
  import { addressErrors } from "./address-errors.js";
15
15
  import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
16
16
  import { readEventStream } from "./assistant/event-stream.js";
17
- export { addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefPlaceholders, inRange, isFieldRef, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
17
+ export { addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, bottomBarFor, cardPhotoLimit, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefPlaceholders, inRange, isFieldRef, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, pruneByShape, readEventStream, redactSecrets, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
package/dist/model.d.ts CHANGED
@@ -273,6 +273,8 @@ export type ListDisplayModel = {
273
273
  card?: string;
274
274
  /** Where the card's further photos come from. */
275
275
  photos?: CardPhotosModel;
276
+ /** The button in the card's top right corner that saves the record to the user's own list, such as favorites. */
277
+ favorite?: CardFavoriteModel;
276
278
  } | {
277
279
  /** A map of the records at their position. */
278
280
  kind: 'map';
@@ -306,7 +308,8 @@ export type ListDisplayModel = {
306
308
  };
307
309
  /**
308
310
  * Where a card's further photos come from: the records of `resource` whose relation `field` points at the card's record,
309
- * in `sort` order, each shown by its file field `image`. The card loads them when its arrows are first used. See
311
+ * in `sort` order, each shown by its file field `image`. They come with the list's records, in the same request, so
312
+ * an index on `field` and the sorted fields serves them. See
310
313
  * [Card photos](website/src/content/docs/reference/ui-config.mdx#card-photos).
311
314
  */
312
315
  export type CardPhotosModel = {
@@ -319,6 +322,47 @@ export type CardPhotosModel = {
319
322
  /** An AIP-132 order of the photos. */
320
323
  sort?: string;
321
324
  };
325
+ /**
326
+ * The button in a card's top right corner that saves its record for the signed-in user: a record of `resource` whose
327
+ * relation `field` points at the card's record, created on a press and deleted on the next. The resource has an owner,
328
+ * so each user lists and saves their own. A visitor who is not signed in gets the resource's sign-in prompt instead
329
+ * (`.signIn(...)` on its view). See [Favorites](website/src/content/docs/reference/ui-config.mdx#favorites).
330
+ */
331
+ export type CardFavoriteModel = {
332
+ /** The resource holding the saved records, such as favorites. */
333
+ resource: string;
334
+ /** Its filterable relation that points at the card's record. */
335
+ field: string;
336
+ /** The button's accessible name, pressed while the record is saved; "Favorite" when omitted. */
337
+ label?: string;
338
+ };
339
+ /**
340
+ * What a visitor who is not signed in is told about a resource they could use once signed in (`.signIn(...)`): on its
341
+ * list page, in place of the list, and in the dialog of a card's favorite button that saves to it. See
342
+ * [Sign-in prompts](website/src/content/docs/reference/ui-config.mdx#sign-in-prompts).
343
+ */
344
+ export type SignInPromptModel = {
345
+ /** The heading, such as "Keep the homes you like". */
346
+ title: string;
347
+ /** A line under it, saying what signing in gives. */
348
+ description: string;
349
+ /** What the visitor can do once signed in, one short line each. */
350
+ benefits?: string[];
351
+ /** The prompt's icon, a name `icon` hints take; a sign-in icon when omitted. */
352
+ icon?: string;
353
+ /** A page where a visitor makes an account, `https:`, `http:` or `mailto:`, offered beside "Sign in"; none when omitted. */
354
+ signUp?: {
355
+ /** The button's text, such as "Create an account". */
356
+ label: string;
357
+ /** The page's address. */
358
+ href: string;
359
+ };
360
+ };
361
+ /** A resource a visitor cannot open until they sign in, as `/meta` names it: what its view calls it and its prompt. */
362
+ export type SignInViewModel = Pick<ViewModel, 'resource' | 'names' | 'nav'> & {
363
+ /** What the visitor is told. */
364
+ signIn: SignInPromptModel;
365
+ };
322
366
  /** A view's list page (`.list(...)`). */
323
367
  export type ListModel = {
324
368
  /** The table's columns, in order. */
@@ -670,6 +714,10 @@ export type ViewModel = {
670
714
  saveFeedback?: SaveFeedback;
671
715
  /** The named actions of its records. */
672
716
  actions: ActionModel[];
717
+ /** The relation field whose records the list page shows instead of this resource's rows, in their own view's list. */
718
+ listOf?: string;
719
+ /** What a visitor who is not signed in is told in place of the list, and by a card's favorite button that saves here. */
720
+ signIn?: SignInPromptModel;
673
721
  };
674
722
  /** Where a part of a filter or order stands in its text, as character offsets. */
675
723
  export type Span = {
@@ -1,4 +1,4 @@
1
- import { CardPhotosModel, ListDisplayModel } from "./model.js";
1
+ import { CardFavoriteModel, CardPhotosModel, ListDisplayModel } from "./model.js";
2
2
  import { Ref } from "./refs.js";
3
3
  //#region src/view-displays.d.ts
4
4
  /** A custom component as `component(name)` from `@protobase/layout` declares it. */
@@ -16,6 +16,8 @@ type Naming = {
16
16
  label?: string;
17
17
  icon?: string;
18
18
  };
19
+ /** The most photos a card steps through, its own image among them, each marked by a dot: a list includes this many of each record's photos. */
20
+ export declare const cardPhotoLimit = 10;
19
21
  /** The name of the list's own display, which every list has. */
20
22
  export declare const tableDisplay = "table";
21
23
  export declare const displays: {
@@ -27,7 +29,10 @@ export declare const displays: {
27
29
  * With `card`, a component of the project or a plugin draws each card's content instead, from the row of those
28
30
  * fields (`{ row }`); without it, or when it throws, the card is the built-in one. With `photos`, the built-in card's
29
31
  * picture gets arrows through the `image` of the records of another resource that point at the card's record, after
30
- * its own `image`; they load when an arrow is first pressed. Named `cards` unless `name` says otherwise.
32
+ * its own `image`, ten pictures at most; they come with the list's records, so its dots mark the one shown from the
33
+ * start. A list's cards displays name the same `photos`. With `favorite`, a heart in the card's top right corner saves
34
+ * the record to the signed-in user's own records of another resource, such as favorites, and takes it off again; a
35
+ * visitor who is not signed in is told why to sign in. Named `cards` unless `name` says otherwise.
31
36
  */
32
37
  cards: (fields: {
33
38
  image?: Ref;
@@ -41,6 +46,7 @@ export declare const displays: {
41
46
  }, o?: Naming & {
42
47
  card?: ComponentRef<never>;
43
48
  photos?: CardPhotosModel;
49
+ favorite?: CardFavoriteModel;
44
50
  }) => ListDisplayModel;
45
51
  /**
46
52
  * A map of the records at their `latitude` and `longitude`, filterable fields in decimal degrees, drawn with the app's
@@ -4,6 +4,8 @@ const naming = ({ label, icon }) => ({
4
4
  ...label !== void 0 && { label },
5
5
  ...icon !== void 0 && { icon }
6
6
  });
7
+ /** The most photos a card steps through, its own image among them, each marked by a dot: a list includes this many of each record's photos. */
8
+ const cardPhotoLimit = 10;
7
9
  /** The name of the list's own display, which every list has. */
8
10
  const tableDisplay = "table";
9
11
  /** The component of a custom display or section, which must be a project's, not a built-in block. */
@@ -20,7 +22,10 @@ const displays = {
20
22
  * With `card`, a component of the project or a plugin draws each card's content instead, from the row of those
21
23
  * fields (`{ row }`); without it, or when it throws, the card is the built-in one. With `photos`, the built-in card's
22
24
  * picture gets arrows through the `image` of the records of another resource that point at the card's record, after
23
- * its own `image`; they load when an arrow is first pressed. Named `cards` unless `name` says otherwise.
25
+ * its own `image`, ten pictures at most; they come with the list's records, so its dots mark the one shown from the
26
+ * start. A list's cards displays name the same `photos`. With `favorite`, a heart in the card's top right corner saves
27
+ * the record to the signed-in user's own records of another resource, such as favorites, and takes it off again; a
28
+ * visitor who is not signed in is told why to sign in. Named `cards` unless `name` says otherwise.
24
29
  */
25
30
  cards: (fields, o = {}) => ({
26
31
  kind: "cards",
@@ -35,7 +40,8 @@ const displays = {
35
40
  ...fields.footer && { footer: fields.footer.map((ref) => ref.name) },
36
41
  ...fields.flags && { flags: fields.flags.map((ref) => ref.name) },
37
42
  ...o.card && { card: customComponent(o.card, "d.cards card") },
38
- ...o.photos && { photos: { ...o.photos } }
43
+ ...o.photos && { photos: { ...o.photos } },
44
+ ...o.favorite && { favorite: { ...o.favorite } }
39
45
  }),
40
46
  /**
41
47
  * A map of the records at their `latitude` and `longitude`, filterable fields in decimal degrees, drawn with the app's
@@ -94,4 +100,4 @@ const displayFields = (display) => {
94
100
  ].filter((name) => name !== void 0);
95
101
  };
96
102
  //#endregion
97
- export { customComponent, displayErrors, displayFields, displays, tableDisplay };
103
+ export { cardPhotoLimit, customComponent, displayErrors, displayFields, displays, tableDisplay };
package/dist/view.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ActionModel, ChartGranularity, ChartRange, FilterWidgetModel, LayoutItemModel, ListDisplayModel, NavModel, NavRecentModel, ResourceModel, SaveFeedback, ViewModel } from "./model.js";
1
+ import { ActionModel, ChartGranularity, ChartRange, FilterWidgetModel, LayoutItemModel, ListDisplayModel, NavModel, NavRecentModel, ResourceModel, SaveFeedback, SignInPromptModel, ViewModel } from "./model.js";
2
2
  import { FieldRefs, Ref } from "./refs.js";
3
3
  import { actions } from "./view-actions.js";
4
4
  import { displays } from "./view-displays.js";
@@ -98,7 +98,21 @@ export declare class ViewBuilder<R extends {
98
98
  * `a.remove` and `a.link` need no code, `a.action` runs a handler the app registers.
99
99
  */
100
100
  actions(build: (a: typeof actions) => ActionModel[]): ViewBuilder<R>;
101
- /** With the resource's model, also verifies the title, search result and list display fields and the sidebar's `recent` group. */
101
+ /**
102
+ * The list page shows the records this relation field points at instead of this resource's rows, in their own view's
103
+ * list with its displays, filters and search: the listings among a member's favorites, say. The record pages stay this
104
+ * resource's. Throws in `toModel` when the field is not a relation.
105
+ */
106
+ listOf(pick: (r: FieldRefs<FieldsOf<R>>) => Ref): ViewBuilder<R>;
107
+ /**
108
+ * What a visitor who is not signed in is told about this resource, which they could use once signed in: on its list
109
+ * page, in place of the list, and in the dialog of a card's favorite button that saves here. The page offers "Sign
110
+ * in", and a page to make an account when `signUp` names one. With it, the resource keeps its place in the bottom
111
+ * bar and the sidebar of visitors. Throws when `signUp.href` is not `https:`, `http:` or `mailto:`. See
112
+ * [Sign-in prompts](website/src/content/docs/reference/ui-config.mdx#sign-in-prompts).
113
+ */
114
+ signIn(prompt: SignInPromptModel): ViewBuilder<R>;
115
+ /** With the resource's model, also verifies the title, search result and list display fields, `listOf` and the sidebar's `recent` group. */
102
116
  toModel(resource?: ResourceModel): ViewModel;
103
117
  private refs;
104
118
  private next;
package/dist/view.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { createRefs, ref } from "./refs.js";
2
+ import { isMenuHref } from "./menu-href.js";
2
3
  import { navRecentErrors } from "./nav-recent.js";
3
4
  import { actions } from "./view-actions.js";
4
5
  import { displayErrors, displayFields, displays } from "./view-displays.js";
@@ -111,9 +112,28 @@ var ViewBuilder = class ViewBuilder {
111
112
  actions(build) {
112
113
  return this.next({ actions: build(actions) });
113
114
  }
114
- /** With the resource's model, also verifies the title, search result and list display fields and the sidebar's `recent` group. */
115
+ /**
116
+ * The list page shows the records this relation field points at instead of this resource's rows, in their own view's
117
+ * list with its displays, filters and search: the listings among a member's favorites, say. The record pages stay this
118
+ * resource's. Throws in `toModel` when the field is not a relation.
119
+ */
120
+ listOf(pick) {
121
+ return this.next({ listOf: pick(this.refs()).name });
122
+ }
123
+ /**
124
+ * What a visitor who is not signed in is told about this resource, which they could use once signed in: on its list
125
+ * page, in place of the list, and in the dialog of a card's favorite button that saves here. The page offers "Sign
126
+ * in", and a page to make an account when `signUp` names one. With it, the resource keeps its place in the bottom
127
+ * bar and the sidebar of visitors. Throws when `signUp.href` is not `https:`, `http:` or `mailto:`. See
128
+ * [Sign-in prompts](website/src/content/docs/reference/ui-config.mdx#sign-in-prompts).
129
+ */
130
+ signIn(prompt) {
131
+ if (prompt.signUp && !isMenuHref(prompt.signUp.href)) throw new Error(`View "${this.state.resource}": signIn signUp.href must start with https://, http:// or mailto:`);
132
+ return this.next({ signIn: structuredClone(prompt) });
133
+ }
134
+ /** With the resource's model, also verifies the title, search result and list display fields, `listOf` and the sidebar's `recent` group. */
115
135
  toModel(resource) {
116
- const { title, nav, searchResult, list } = this.state;
136
+ const { title, nav, searchResult, list, listOf } = this.state;
117
137
  if (resource && title !== void 0 && !Object.hasOwn(resource.fields, title)) throw new Error(`View "${this.state.resource}": title field "${title}" does not exist on the resource`);
118
138
  const missing = resource ? [...searchResult?.title ?? [], ...searchResult?.subtitle ? [searchResult.subtitle] : []].filter((name) => !Object.hasOwn(resource.fields, name)) : [];
119
139
  if (missing.length > 0) throw new Error(`View "${this.state.resource}": search result field "${missing[0]}" does not exist on the resource`);
@@ -124,6 +144,7 @@ var ViewBuilder = class ViewBuilder {
124
144
  const flag = display.kind === "cards" ? display.flags?.find((name) => resource.fields[name].type !== "boolean") : void 0;
125
145
  if (flag !== void 0) throw new Error(`View "${this.state.resource}": display "${display.name}" shows "${flag}" as a flag, which is not a boolean field`);
126
146
  }
147
+ if (resource && listOf !== void 0 && (!Object.hasOwn(resource.fields, listOf) || resource.fields[listOf].type !== "relation")) throw new Error(`View "${this.state.resource}": listOf names "${listOf}", which is not a relation field of the resource`);
127
148
  const recentErrors = resource && nav?.recent ? navRecentErrors(nav.recent, resource) : [];
128
149
  if (recentErrors.length > 0) throw new Error(`View "${this.state.resource}": nav.recent ${recentErrors.join("; ")}`);
129
150
  return structuredClone(this.state);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/schema",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "description": "Resource, field, view and access definitions for Protobase configs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {