@protobase/schema 0.22.0 → 0.22.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.
@@ -12,10 +12,11 @@ import { SearchTerm, matchedSearchField, matchesSearch, searchTerms, termMatches
12
12
  import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
13
13
  import { PluginHost, PluginRequirement, inRange, pluginProblem } from "./plugin-version.js";
14
14
  import { redactSecrets } from "./redact-secrets.js";
15
+ import { updateSetErrors, updateValues } from "./update-set.js";
15
16
  import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
16
17
  import { promptError, promptFields, renderPrompt } from "./ai-prompt.js";
17
18
  import { AddressCheck, addressErrors } from "./address-errors.js";
18
19
  import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
19
20
  import { readEventStream } from "./assistant/event-stream.js";
20
21
  import { layoutItems } from "./layout-items.js";
21
- export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, layoutItems, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
22
+ export { type AddressCheck, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, layoutItems, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, webhookResources };
package/dist/internal.js CHANGED
@@ -5,6 +5,7 @@ import { matchedSearchField, matchesSearch, searchTerms, termMatches } from "./s
5
5
  import { checkEventFilter } from "./events/event-models.js";
6
6
  import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
7
7
  import { actionInputErrors, shownInputs } from "./action-inputs.js";
8
+ import { updateSetErrors, updateValues } from "./update-set.js";
8
9
  import { chartFields, isGroupChart } from "./view-chart.js";
9
10
  import { fieldRefsIn, isFieldRef, markFieldRefs, unmarkFieldRefs } from "./field-ref.js";
10
11
  import { boardErrors, calendarErrors, cardPhotoLimit, displayFields, tableDisplay } from "./view-displays.js";
@@ -18,4 +19,4 @@ import { addressErrors } from "./address-errors.js";
18
19
  import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
19
20
  import { readEventStream } from "./assistant/event-stream.js";
20
21
  import { layoutItems } from "./layout-items.js";
21
- export { actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, layoutItems, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, webhookResources };
22
+ export { actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, layoutItems, leavesApp, linkActionError, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseSize, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, webhookResources };
package/dist/model.d.ts CHANGED
@@ -603,6 +603,7 @@ export type FilterWidgetModel = {
603
603
  /**
604
604
  * Records of another resource on the record page: those whose `field` holds this record's key or, with `through`,
605
605
  * those whose `field` holds one of the `key` values of the `through` records that point at this record (a join table).
606
+ * With `from`, those whose `field` holds the value of that field of this record.
606
607
  * See [Related records](website/src/content/docs/reference/ui-config.mdx#related-records).
607
608
  */
608
609
  export type RelatedModel = {
@@ -614,8 +615,10 @@ export type RelatedModel = {
614
615
  help?: string;
615
616
  /** The resource whose records it lists. */
616
617
  resource: string;
617
- /** The filterable field of `resource` matched against this record's key, or with `through` against the join records' keys. */
618
+ /** The filterable field of `resource` matched against this record's key, or with `through` against the join records' keys, or with `from` against that field's value. */
618
619
  field: string;
620
+ /** A field of this record whose value `field` is matched against instead of its key; not with `through`. */
621
+ from?: string;
619
622
  /** The join records between this record and the listed ones. */
620
623
  through?: {
621
624
  /** The join resource. */
@@ -822,7 +825,7 @@ export type SaveFeedback = 'toast' | 'button';
822
825
  export type ActionRun = {
823
826
  /** Updates the record. */
824
827
  kind: 'update';
825
- /** The fields it sets, by name. */
828
+ /** The fields it sets, by name; a value that is exactly `{input}` is that input's answer. */
826
829
  values: Record<string, unknown>;
827
830
  } | {
828
831
  /** Deletes the record, soft deleting when the resource does. */
@@ -865,7 +868,7 @@ export type ActionModel = {
865
868
  bulk: boolean;
866
869
  /** What it does; without it the app runs the handler registered under `name`. */
867
870
  run?: ActionRun;
868
- /** What the button asks for in a dialog before the handler runs, which gets the answers as `values` (`a.action`'s `inputs`). */
871
+ /** What the button asks for in a dialog before the action runs: a handler gets the answers as `values`, an update writes them through `set`. */
869
872
  inputs?: ActionInputModel[];
870
873
  };
871
874
  /**
@@ -0,0 +1,12 @@
1
+ import { ActionInputModel } from "./model.js";
2
+ //#region src/update-set.d.ts
3
+ /** Why an update's `set` cannot be used: a value that is exactly `{name}` must name one of the inputs the action asks for. */
4
+ export declare const updateSetErrors: (set: Record<string, unknown>, inputs?: ActionInputModel[]) => string[];
5
+ /**
6
+ * The fields an update writes: a literal as it is, a value that is exactly `{input}` as that input's answer. A field whose
7
+ * input was hidden has no answer in `values` and is left out, so it keeps what it holds.
8
+ */
9
+ export declare const updateValues: (set: Record<string, unknown>, values: Record<string, unknown>) => {
10
+ [k: string]: unknown;
11
+ };
12
+ //#endregion
@@ -0,0 +1,19 @@
1
+ import { hrefField } from "./address-template.js";
2
+ //#region src/update-set.ts
3
+ /** Why an update's `set` cannot be used: a value that is exactly `{name}` must name one of the inputs the action asks for. */
4
+ const updateSetErrors = (set, inputs = []) => Object.entries(set).flatMap(([field, value]) => {
5
+ const input = typeof value === "string" ? hrefField(value) : void 0;
6
+ if (input === void 0 || inputs.some((entry) => entry.name === input)) return [];
7
+ return [`set "${field}" names input "${input}", which it does not ask for`];
8
+ });
9
+ /**
10
+ * The fields an update writes: a literal as it is, a value that is exactly `{input}` as that input's answer. A field whose
11
+ * input was hidden has no answer in `values` and is left out, so it keeps what it holds.
12
+ */
13
+ const updateValues = (set, values) => Object.fromEntries(Object.entries(set).flatMap(([field, value]) => {
14
+ const input = typeof value === "string" ? hrefField(value) : void 0;
15
+ if (input === void 0) return [[field, value]];
16
+ return Object.hasOwn(values, input) ? [[field, values[input]]] : [];
17
+ }));
18
+ //#endregion
19
+ export { updateSetErrors, updateValues };
@@ -7,21 +7,26 @@ type ActionOptions = {
7
7
  icon?: string;
8
8
  confirm?: string;
9
9
  };
10
+ /** @inline */
11
+ type InputsOption = {
12
+ inputs?: (i: typeof inputs) => ActionInputModel[];
13
+ };
10
14
  export declare const actions: {
11
15
  /**
12
16
  * An action the app implements: it runs the handler registered under `name`. With `inputs`, such as
13
17
  * `(i) => [i.text('reason', { label: 'Reason' })]`, the button first asks for them in a dialog, and the handler gets the
14
18
  * answers as `values`.
15
19
  */
16
- action: (name: string, o: ActionOptions & {
20
+ action: (name: string, o: ActionOptions & InputsOption & {
17
21
  bulk?: boolean;
18
- inputs?: (i: typeof inputs) => ActionInputModel[];
19
22
  }) => ActionModel;
20
23
  /**
21
24
  * Sets fields of the record, as one update with its ETag, for example `{ status: 'paid' }`. It is not offered on a
22
- * record that holds these values already.
25
+ * record that holds these values already. With `inputs`, the button first asks for them in a dialog, and a `set` value
26
+ * that is exactly `'{name}'` is written as that input's answer, with no handler code:
27
+ * `a.update('answer', { label: 'Answer', inputs: (i) => [i.text('answer', { label: 'Answer', multiline: true })], set: { answer: '{answer}' } })`.
23
28
  */
24
- update: (name: string, o: ActionOptions & {
29
+ update: (name: string, o: ActionOptions & InputsOption & {
25
30
  set: Record<string, unknown>;
26
31
  }) => ActionModel;
27
32
  /** Deletes the record (soft delete when the resource has one). */
@@ -1,6 +1,7 @@
1
1
  import { linkActionError } from "./address-template.js";
2
2
  import { promptError } from "./ai-prompt.js";
3
3
  import { actionInputErrors, inputs } from "./action-inputs.js";
4
+ import { updateSetErrors } from "./update-set.js";
4
5
  //#region src/view-actions.ts
5
6
  const named = (name, o) => ({
6
7
  name,
@@ -9,33 +10,42 @@ const named = (name, o) => ({
9
10
  ...o.icon !== void 0 && { icon: o.icon },
10
11
  ...o.confirm !== void 0 && { confirm: o.confirm }
11
12
  });
13
+ const builtInputs = (name, option) => {
14
+ if (!option) return {};
15
+ const built = option(inputs);
16
+ const errors = actionInputErrors(built);
17
+ if (errors.length > 0) throw new Error(`Action "${name}": ${errors.join("; ")}`);
18
+ return { inputs: built };
19
+ };
12
20
  const actions = {
13
21
  /**
14
22
  * An action the app implements: it runs the handler registered under `name`. With `inputs`, such as
15
23
  * `(i) => [i.text('reason', { label: 'Reason' })]`, the button first asks for them in a dialog, and the handler gets the
16
24
  * answers as `values`.
17
25
  */
18
- action: (name, o) => {
19
- if (!o.inputs) return named(name, o);
20
- const built = o.inputs(inputs);
21
- const errors = actionInputErrors(built);
26
+ action: (name, o) => ({
27
+ ...named(name, o),
28
+ ...builtInputs(name, o.inputs)
29
+ }),
30
+ /**
31
+ * Sets fields of the record, as one update with its ETag, for example `{ status: 'paid' }`. It is not offered on a
32
+ * record that holds these values already. With `inputs`, the button first asks for them in a dialog, and a `set` value
33
+ * that is exactly `'{name}'` is written as that input's answer, with no handler code:
34
+ * `a.update('answer', { label: 'Answer', inputs: (i) => [i.text('answer', { label: 'Answer', multiline: true })], set: { answer: '{answer}' } })`.
35
+ */
36
+ update: (name, o) => {
37
+ const built = builtInputs(name, o.inputs);
38
+ const errors = updateSetErrors(o.set, built.inputs);
22
39
  if (errors.length > 0) throw new Error(`Action "${name}": ${errors.join("; ")}`);
23
40
  return {
24
41
  ...named(name, o),
25
- inputs: built
42
+ ...built,
43
+ run: {
44
+ kind: "update",
45
+ values: o.set
46
+ }
26
47
  };
27
48
  },
28
- /**
29
- * Sets fields of the record, as one update with its ETag, for example `{ status: 'paid' }`. It is not offered on a
30
- * record that holds these values already.
31
- */
32
- update: (name, o) => ({
33
- ...named(name, o),
34
- run: {
35
- kind: "update",
36
- values: o.set
37
- }
38
- }),
39
49
  /** Deletes the record (soft delete when the resource has one). */
40
50
  remove: (name, o) => ({
41
51
  ...named(name, o),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/schema",
3
- "version": "0.22.0",
3
+ "version": "0.22.2",
4
4
  "description": "Resource, field, view and access definitions for Protobase configs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {