@protobase/schema 0.67.12 → 0.68.1

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.
@@ -11,7 +11,8 @@ export type AddressCheck = {
11
11
  createPages?: ReadonlySet<string>;
12
12
  };
13
13
  /**
14
- * What is wrong with a link's address against these models: an unknown resource or page, a `{field}` the record it sits
14
+ * What is wrong with a link's address against these models: an unknown resource or page, a filter, order or values
15
+ * beside a resource taken from the record (`'{field}'`), a `{field}` the record it sits
15
16
  * on does not have, a filter or order naming fields the resource does not have, or create values for fields it does not
16
17
  * have, unless its view opens a create page, which reads them. Empty when it can be followed.
17
18
  */
@@ -1,4 +1,4 @@
1
- import { addressPlaceholders } from "./address-template.js";
1
+ import { addressPlaceholders, resourceField } from "./address-template.js";
2
2
  import { parseFilter } from "./filter/parse.js";
3
3
  import { checkFilter } from "./filter/check.js";
4
4
  import { checkOrderBy, parseOrderBy } from "./filter/order-by.js";
@@ -30,7 +30,8 @@ const orderErrors = (model, orderBy) => {
30
30
  return checked.ok ? [] : checked.errors.map((error) => `orderBy: ${error.message}`);
31
31
  };
32
32
  /**
33
- * What is wrong with a link's address against these models: an unknown resource or page, a `{field}` the record it sits
33
+ * What is wrong with a link's address against these models: an unknown resource or page, a filter, order or values
34
+ * beside a resource taken from the record (`'{field}'`), a `{field}` the record it sits
34
35
  * on does not have, a filter or order naming fields the resource does not have, or create values for fields it does not
35
36
  * have, unless its view opens a create page, which reads them. Empty when it can be followed.
36
37
  */
@@ -39,6 +40,7 @@ const addressErrors = (to, { models, pages, source, createPages }) => {
39
40
  if (!source) return [`"{${name}}" takes a value from the record, but the link sits on none`];
40
41
  return Object.hasOwn(source.fields, name) ? [] : [`"{${name}}" is not a field of ${source.name}`];
41
42
  });
43
+ if (to.kind !== "page" && resourceField(to) !== void 0) return (to.kind === "list" ? to.filter !== void 0 || to.orderBy !== void 0 : to.kind === "create" && to.values !== void 0) ? [...placeholders, `a link whose resource comes from the record ("${to.resource}") names no filter, order or values`] : placeholders;
42
44
  switch (to.kind) {
43
45
  case "page": return pages && !pages.has(to.page) ? [`unknown page "${to.page}"`] : [];
44
46
  case "list":
@@ -1,10 +1,12 @@
1
1
  import { AppAddress } from "./model.js";
2
2
  //#region src/address-template.d.ts
3
+ /** The field a link's address takes its resource from, when its `resource` is only `{field}`, as `'{name}'`. */
4
+ export declare const resourceField: (to: AppAddress) => string | undefined;
3
5
  /** The fields whose values a link's address takes from the record, from its `{field}` placeholders. */
4
6
  export declare const addressPlaceholders: (to: AppAddress) => string[];
5
7
  /** The fields an href takes from the record, from its `{field}` placeholders. */
6
8
  export declare const hrefPlaceholders: (href: string) => string[];
7
- /** `to` with every `{field}` in its key, filter, order or values replaced by the record's value; a missing value becomes empty. */
9
+ /** `to` with every `{field}` in its resource, key, filter, order or values replaced by the record's value; a missing value becomes empty. */
8
10
  export declare const fillAddress: (to: AppAddress, record?: Record<string, unknown>) => AppAddress;
9
11
  /**
10
12
  * Whether an href leaves the app: it starts with a scheme (`https:`, `mailto:`) or `//`. A path inside the app is an
@@ -1,33 +1,40 @@
1
1
  //#region src/address-template.ts
2
2
  const placeholder = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
3
3
  const names = (text) => [...text.matchAll(placeholder)].map((match) => match[1]);
4
+ /** The field a link's address takes its resource from, when its `resource` is only `{field}`, as `'{name}'`. */
5
+ const resourceField = (to) => to.kind === "page" ? void 0 : /^\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(to.resource)?.[1];
4
6
  const templated = (to) => {
5
- if (to.kind === "record") return (Array.isArray(to.key) ? to.key : [to.key]).filter((part) => typeof part === "string");
6
- if (to.kind === "list") return [to.filter, to.orderBy].filter((part) => part !== void 0);
7
- if (to.kind === "create") return Object.values(to.values ?? {});
8
- return [];
7
+ if (to.kind === "page") return [];
8
+ const resource = resourceField(to) === void 0 ? [] : [to.resource];
9
+ if (to.kind === "record") return [...resource, ...(Array.isArray(to.key) ? to.key : [to.key]).filter((part) => typeof part === "string")];
10
+ if (to.kind === "list") return [...resource, ...[to.filter, to.orderBy].filter((part) => part !== void 0)];
11
+ return [...resource, ...Object.values(to.values ?? {})];
9
12
  };
10
13
  /** The fields whose values a link's address takes from the record, from its `{field}` placeholders. */
11
14
  const addressPlaceholders = (to) => [...new Set(templated(to).flatMap(names))];
12
15
  /** The fields an href takes from the record, from its `{field}` placeholders. */
13
16
  const hrefPlaceholders = (href) => [...new Set(names(href))];
14
17
  const fill = (text, record) => text.replace(placeholder, (_, name) => record[name] === null || record[name] === void 0 ? "" : String(record[name]));
15
- /** `to` with every `{field}` in its key, filter, order or values replaced by the record's value; a missing value becomes empty. */
18
+ /** `to` with every `{field}` in its resource, key, filter, order or values replaced by the record's value; a missing value becomes empty. */
16
19
  const fillAddress = (to, record = {}) => {
20
+ if (to.kind === "page") return to;
21
+ const resource = resourceField(to) === void 0 ? to.resource : fill(to.resource, record);
17
22
  if (to.kind === "record") {
18
23
  const part = (value) => typeof value === "string" ? fill(value, record) : value;
19
24
  return {
20
25
  ...to,
26
+ resource,
21
27
  key: Array.isArray(to.key) ? to.key.map(part) : part(to.key)
22
28
  };
23
29
  }
24
- if (to.kind === "create") return to.values ? {
30
+ if (to.kind === "create") return {
25
31
  ...to,
26
- values: Object.fromEntries(Object.entries(to.values).map(([name, value]) => [name, fill(value, record)]))
27
- } : to;
28
- if (to.kind !== "list") return to;
32
+ resource,
33
+ ...to.values && { values: Object.fromEntries(Object.entries(to.values).map(([name, value]) => [name, fill(value, record)])) }
34
+ };
29
35
  return {
30
36
  ...to,
37
+ resource,
31
38
  ...to.filter !== void 0 && { filter: fill(to.filter, record) },
32
39
  ...to.orderBy !== void 0 && { orderBy: fill(to.orderBy, record) }
33
40
  };
@@ -50,4 +57,4 @@ const linkActionError = (label, run) => {
50
57
  return label.includes("{host}") ? "the label names {host}, which only a link with an href has" : void 0;
51
58
  };
52
59
  //#endregion
53
- export { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError };
60
+ export { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError, resourceField };
@@ -11,7 +11,7 @@ import { parseCapability } from "./access/capability.js";
11
11
  import { checkEventFilter } from "./events/event-models.js";
12
12
  import { webhookResources } from "./webhooks/webhook-resources.js";
13
13
  import { workflowResources } from "./workflows/workflow-resources.js";
14
- import { StepInput, StepInputKind, WorkflowStepType, requiredStepInputs, workflowStepTypes } from "./workflows/step-types.js";
14
+ import { WorkflowTypeEntry, WorkflowTypeList, workflowTypesPath } from "./workflows/workflow-types-path.js";
15
15
  import { PropShapes, RefShape, pruneByShape, shapes } from "./ref-shape.js";
16
16
  import { SearchTerm, matchedSearchField, matchesSearch, searchTerms, termMatches } from "./search-match.js";
17
17
  import { defaultRecentLimit, navRecentErrors, statusTones } from "./nav-recent.js";
@@ -19,7 +19,7 @@ import { PluginHost, PluginRequirement, inRange, pluginProblem } from "./plugin-
19
19
  import { redactSecrets } from "./redact-secrets.js";
20
20
  import { firstPartyCodeKey } from "./first-party-code.js";
21
21
  import { updateSetErrors, updateValues } from "./update-set.js";
22
- import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
22
+ import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError, resourceField } from "./address-template.js";
23
23
  import { promptError, promptFields, renderPrompt } from "./ai-prompt.js";
24
24
  import { FormulaNode, parseFormula } from "./computed/formula.js";
25
25
  import { AddressCheck, addressErrors } from "./address-errors.js";
@@ -45,4 +45,4 @@ import { builtInFieldTypes } from "./built-in-field-types.js";
45
45
  import { CallCandidate, CallClientMessage, CallDescription, CallGuestLink, CallGuestRefusal, CallGuestState, CallLeftReason, CallLobbyGuest, CallMode, CallRemoteTrack, CallResume, CallRosterMember, CallServerMessage, CallTrackRequest, CallTrackSource, CallVideoLayer } from "./calls/call-protocol.js";
46
46
  import { guestCallsPath } from "./calls/call-guests.js";
47
47
  import { CallRing, callsChannelEvent, declineWord, ringNotice, ringsPath } from "./calls/call-rings.js";
48
- export { type AddressCheck, type Availability, type CallCandidate, type CallClientMessage, type CallDescription, type CallGuestLink, type CallGuestRefusal, type CallGuestState, type CallLeftReason, type CallLobbyGuest, type CallMode, type CallRemoteTrack, type CallResume, type CallRing, type CallRosterMember, type CallServerMessage, type CallTrackRequest, type CallTrackSource, type CallVideoLayer, type FormulaNode, type Locale, type MobileReleaseAnswer, type PersonStatus, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type ReleaseBinding, type SearchTerm, type StatusInput, type StepInput, type StepInputKind, type WorkflowStepType, actionConditionError, actionInputErrors, addressErrors, addressPlaceholders, apiOriginMeta, appHrefError, applyAssistantEvent, authBasePath, availabilities, boardErrors, bottomBarFor, builtInFieldTypes, callsChannelEvent, capacitorOrigin, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, confirmationMinutes, datesErrors, declineWord, decodeKey, defaultRecentLimit, deviceIdParam, devicesPath, displayFields, emailCodeMinutes, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, firstPartyCodeKey, guestCallsPath, heldStatus, historyIdParam, historyIdPattern, historyPath, hrefField, hrefPlaceholders, humanize, inRange, isFieldRef, isGroupChart, isLocale, labelFieldOf, layoutItems, leavesApp, linkActionError, listColumnNames, locales, markFieldRefs, matchedSearchField, matchesSearch, maxStatusText, mobileReleaseBasePath, mobileReleaseParams, mobileReleasePath, mobileReleaseQuery, mobileReleasesPath, navRecentErrors, parseCapability, parseFormula, parseSize, parseTable, pickLocale, pluginProblem, promptError, promptFields, pruneByShape, pushRecord, readEventStream, recordTitle, recordTitleField, redactSecrets, releaseManifestFile, releaseStatementContext, releaseStatementFile, renderPrompt, requiredStepInputs, ringNotice, ringsPath, searchTerms, serviceWorkerFile, shapes, shownInputs, statusPath, statusTextProblem, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, viewOfList, webhookResources, workflowResources, workflowStepTypes };
48
+ export { type AddressCheck, type Availability, type CallCandidate, type CallClientMessage, type CallDescription, type CallGuestLink, type CallGuestRefusal, type CallGuestState, type CallLeftReason, type CallLobbyGuest, type CallMode, type CallRemoteTrack, type CallResume, type CallRing, type CallRosterMember, type CallServerMessage, type CallTrackRequest, type CallTrackSource, type CallVideoLayer, type FormulaNode, type Locale, type MobileReleaseAnswer, type PersonStatus, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type ReleaseBinding, type SearchTerm, type StatusInput, type WorkflowTypeEntry, type WorkflowTypeList, actionConditionError, actionInputErrors, addressErrors, addressPlaceholders, apiOriginMeta, appHrefError, applyAssistantEvent, authBasePath, availabilities, boardErrors, bottomBarFor, builtInFieldTypes, callsChannelEvent, capacitorOrigin, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, confirmationMinutes, datesErrors, declineWord, decodeKey, defaultRecentLimit, deviceIdParam, devicesPath, displayFields, emailCodeMinutes, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, firstPartyCodeKey, guestCallsPath, heldStatus, historyIdParam, historyIdPattern, historyPath, hrefField, hrefPlaceholders, humanize, inRange, isFieldRef, isGroupChart, isLocale, labelFieldOf, layoutItems, leavesApp, linkActionError, listColumnNames, locales, markFieldRefs, matchedSearchField, matchesSearch, maxStatusText, mobileReleaseBasePath, mobileReleaseParams, mobileReleasePath, mobileReleaseQuery, mobileReleasesPath, navRecentErrors, parseCapability, parseFormula, parseSize, parseTable, pickLocale, pluginProblem, promptError, promptFields, pruneByShape, pushRecord, readEventStream, recordTitle, recordTitleField, redactSecrets, releaseManifestFile, releaseStatementContext, releaseStatementFile, renderPrompt, resourceField, ringNotice, ringsPath, searchTerms, serviceWorkerFile, shapes, shownInputs, statusPath, statusTextProblem, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, viewOfList, webhookResources, workflowResources, workflowTypesPath };
package/dist/internal.js CHANGED
@@ -1,4 +1,4 @@
1
- import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
1
+ import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError, resourceField } from "./address-template.js";
2
2
  import { promptError, promptFields, renderPrompt } from "./ai-prompt.js";
3
3
  import { parseFormula } from "./computed/formula.js";
4
4
  import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
@@ -19,7 +19,7 @@ import { bottomBarFor } from "./bottom-bar.js";
19
19
  import { decodeKey, encodeKey } from "./keys.js";
20
20
  import { webhookResources } from "./webhooks/webhook-resources.js";
21
21
  import { workflowResources } from "./workflows/workflow-resources.js";
22
- import { requiredStepInputs, workflowStepTypes } from "./workflows/step-types.js";
22
+ import { workflowTypesPath } from "./workflows/workflow-types-path.js";
23
23
  import { pruneByShape, shapes } from "./ref-shape.js";
24
24
  import { inRange, pluginProblem } from "./plugin-version.js";
25
25
  import { redactSecrets } from "./redact-secrets.js";
@@ -44,4 +44,4 @@ import { listColumnNames } from "./list-columns.js";
44
44
  import { builtInFieldTypes } from "./built-in-field-types.js";
45
45
  import { guestCallsPath } from "./calls/call-guests.js";
46
46
  import { callsChannelEvent, declineWord, ringNotice, ringsPath } from "./calls/call-rings.js";
47
- export { actionConditionError, actionInputErrors, addressErrors, addressPlaceholders, apiOriginMeta, appHrefError, applyAssistantEvent, authBasePath, availabilities, boardErrors, bottomBarFor, builtInFieldTypes, callsChannelEvent, capacitorOrigin, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, confirmationMinutes, datesErrors, declineWord, decodeKey, defaultRecentLimit, deviceIdParam, devicesPath, displayFields, emailCodeMinutes, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, firstPartyCodeKey, guestCallsPath, heldStatus, historyIdParam, historyIdPattern, historyPath, hrefField, hrefPlaceholders, humanize, inRange, isFieldRef, isGroupChart, isLocale, labelFieldOf, layoutItems, leavesApp, linkActionError, listColumnNames, locales, markFieldRefs, matchedSearchField, matchesSearch, maxStatusText, mobileReleaseBasePath, mobileReleaseParams, mobileReleasePath, mobileReleaseQuery, mobileReleasesPath, navRecentErrors, parseCapability, parseFormula, parseSize, parseTable, pickLocale, pluginProblem, promptError, promptFields, pruneByShape, pushRecord, readEventStream, recordTitle, recordTitleField, redactSecrets, releaseManifestFile, releaseStatementContext, releaseStatementFile, renderPrompt, requiredStepInputs, ringNotice, ringsPath, searchTerms, serviceWorkerFile, shapes, shownInputs, statusPath, statusTextProblem, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, viewOfList, webhookResources, workflowResources, workflowStepTypes };
47
+ export { actionConditionError, actionInputErrors, addressErrors, addressPlaceholders, apiOriginMeta, appHrefError, applyAssistantEvent, authBasePath, availabilities, boardErrors, bottomBarFor, builtInFieldTypes, callsChannelEvent, capacitorOrigin, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, confirmationMinutes, datesErrors, declineWord, decodeKey, defaultRecentLimit, deviceIdParam, devicesPath, displayFields, emailCodeMinutes, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, firstPartyCodeKey, guestCallsPath, heldStatus, historyIdParam, historyIdPattern, historyPath, hrefField, hrefPlaceholders, humanize, inRange, isFieldRef, isGroupChart, isLocale, labelFieldOf, layoutItems, leavesApp, linkActionError, listColumnNames, locales, markFieldRefs, matchedSearchField, matchesSearch, maxStatusText, mobileReleaseBasePath, mobileReleaseParams, mobileReleasePath, mobileReleaseQuery, mobileReleasesPath, navRecentErrors, parseCapability, parseFormula, parseSize, parseTable, pickLocale, pluginProblem, promptError, promptFields, pruneByShape, pushRecord, readEventStream, recordTitle, recordTitleField, redactSecrets, releaseManifestFile, releaseStatementContext, releaseStatementFile, renderPrompt, resourceField, ringNotice, ringsPath, searchTerms, serviceWorkerFile, shapes, shownInputs, statusPath, statusTextProblem, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, viewOfList, webhookResources, workflowResources, workflowTypesPath };
package/dist/model.d.ts CHANGED
@@ -317,6 +317,8 @@ export type FieldViewModel = {
317
317
  helpWhen?: HelpWhen;
318
318
  /** When the field shows on the record page: always, the default, only on its form or only on its read view. */
319
319
  when?: ShowWhen;
320
+ /** Left off the create form, so a create leaves it empty or to its default; the server refuses it on a field a create needs. */
321
+ notOnCreate?: true;
320
322
  /** The question a form or a list cell asks before the field's value first changes; cancelling keeps the value. */
321
323
  confirm?: string;
322
324
  /** How the value is shown. */
@@ -1157,6 +1159,8 @@ export type NavRecentModel = {
1157
1159
  export type NavModel = {
1158
1160
  /** Hide the resource from the sidebar, for line or link tables. */
1159
1161
  hidden?: boolean;
1162
+ /** Show the entry only to whoever the list offers New, for a list that is there to make records, such as the tables users add. */
1163
+ creatorsOnly?: true;
1160
1164
  /** The sidebar group it sits in; its database schema when omitted. */
1161
1165
  group?: string;
1162
1166
  /** Where it sorts within its group. */
@@ -27,6 +27,12 @@ export declare class FieldView<N extends string = string, V = unknown> {
27
27
  * view and locked form; `always` when omitted. Its lists, cards and filters show it either way.
28
28
  */
29
29
  when(when: ShowWhen): FieldView<N, V>;
30
+ /**
31
+ * Leaves the field off the create form, for one that makes sense only once the record exists, such as a choice among
32
+ * records added after it; the create leaves it empty or to its default, and the record page shows it as usual. The
33
+ * server stops at startup on a field a create needs: not optional and with no default.
34
+ */
35
+ notOnCreate(): FieldView<N, V>;
30
36
  /**
31
37
  * Asks `question` before a person changes the value in a form or a list cell, such as a flag that is easy to set by
32
38
  * mistake: once as the value first moves away from the saved one, not on every key. Cancelling keeps the value. See
@@ -33,6 +33,14 @@ var FieldView = class FieldView {
33
33
  return this.with({ when });
34
34
  }
35
35
  /**
36
+ * Leaves the field off the create form, for one that makes sense only once the record exists, such as a choice among
37
+ * records added after it; the create leaves it empty or to its default, and the record page shows it as usual. The
38
+ * server stops at startup on a field a create needs: not optional and with no default.
39
+ */
40
+ notOnCreate() {
41
+ return this.with({ notOnCreate: true });
42
+ }
43
+ /**
36
44
  * Asks `question` before a person changes the value in a form or a list cell, such as a flag that is easy to set by
37
45
  * mistake: once as the value first moves away from the saved one, not on every key. Cancelling keeps the value. See
38
46
  * [Confirming a change](docs/website/src/content/docs/reference/ui-config.mdx#confirming-a-change).
@@ -15,6 +15,7 @@ export declare const workflowResources: (schema: string) => {
15
15
  readonly description: Field<string | null>;
16
16
  readonly definition: Field<import("zod").JSONType>;
17
17
  readonly version: Field<number | null>;
18
+ readonly enabled_version: Field<number | null>;
18
19
  readonly tenant_id: Field<string | null>;
19
20
  readonly created_at: Field<string>;
20
21
  readonly updated_at: Field<string>;
@@ -25,6 +26,7 @@ export declare const workflowResources: (schema: string) => {
25
26
  readonly description: Field<string | null>;
26
27
  readonly definition: Field<import("zod").JSONType>;
27
28
  readonly version: Field<number | null>;
29
+ readonly enabled_version: Field<number | null>;
28
30
  readonly tenant_id: Field<string | null>;
29
31
  readonly created_at: Field<string>;
30
32
  readonly updated_at: Field<string>;
@@ -125,5 +127,27 @@ export declare const workflowResources: (schema: string) => {
125
127
  readonly finished_at: Field<string>;
126
128
  readonly duration_ms: Field<string | null>;
127
129
  }, Ref<"id", string>>>;
130
+ workflowTriggers: ResourceBuilder<{
131
+ readonly id: Field<string>;
132
+ readonly workflow_id: Field<string | number>;
133
+ readonly version: Field<number>;
134
+ readonly type: Field<string>;
135
+ readonly status: Field<"active" | "failing" | "needs_sign_in" | "stopping">;
136
+ readonly last_error: Field<import("zod").JSONType>;
137
+ readonly next_poll_at: Field<string | null>;
138
+ readonly tenant_id: Field<string | null>;
139
+ readonly updated_at: Field<string>;
140
+ }, Ref<"id", string>>;
141
+ workflowTriggersView: ViewBuilder<ResourceBuilder<{
142
+ readonly id: Field<string>;
143
+ readonly workflow_id: Field<string | number>;
144
+ readonly version: Field<number>;
145
+ readonly type: Field<string>;
146
+ readonly status: Field<"active" | "failing" | "needs_sign_in" | "stopping">;
147
+ readonly last_error: Field<import("zod").JSONType>;
148
+ readonly next_poll_at: Field<string | null>;
149
+ readonly tenant_id: Field<string | null>;
150
+ readonly updated_at: Field<string>;
151
+ }, Ref<"id", string>>>;
128
152
  };
129
153
  //#endregion
@@ -27,6 +27,7 @@ const workflowsResource = (schema) => resource("workflows").table(`${schema}.wor
27
27
  description: f.text().optional().max(2e3),
28
28
  definition: f.json(),
29
29
  version: f.integer().optional().readOnly(),
30
+ enabled_version: f.integer().optional(),
30
31
  tenant_id: f.text().optional().readOnly(),
31
32
  created_at: f.timestamp().readOnly().dbDefault(),
32
33
  updated_at: f.timestamp().readOnly().dbDefault().sortable()
@@ -35,6 +36,7 @@ const workflowsView = view("workflows").names({
35
36
  singular: "Workflow",
36
37
  plural: "Workflows"
37
38
  }).icon("workflow").title((r) => r.name).help("Steps that run one after another along their edges, written in the States Language with JSONata expressions. Each save that changes its definition is a new version; a run keeps the version it started on.").fields((r) => ({
39
+ enabled_version: r.enabled_version.label("Enabled version").help("The version whose trigger starts runs; set it to the current version to turn the trigger on, and clear it to turn it off. A save keeps an enabled workflow enabled on its new version."),
38
40
  definition: r.definition.help("Its trigger and its steps, drawn as a graph that lays itself out. Every value is read by name: the run's input as $states.context.Execution.Input, an earlier result by the variable a state assigned").widget("workflow"),
39
41
  updated_at: r.updated_at.label("Updated")
40
42
  })).list((r) => ({
@@ -56,6 +58,7 @@ const workflowsView = view("workflows").names({
56
58
  layout.section("Workflow", [
57
59
  r.name,
58
60
  r.description,
61
+ r.enabled_version,
59
62
  r.definition
60
63
  ]),
61
64
  layout.sidebar([
@@ -63,6 +66,17 @@ const workflowsView = view("workflows").names({
63
66
  r.created_at,
64
67
  r.updated_at
65
68
  ]),
69
+ layout.related("Trigger", {
70
+ resource: "workflow_triggers",
71
+ field: "workflow_id",
72
+ columns: [
73
+ "type",
74
+ "status",
75
+ "version",
76
+ "last_error",
77
+ "updated_at"
78
+ ]
79
+ }),
66
80
  layout.related("Runs", {
67
81
  resource: "workflow_runs",
68
82
  field: "workflow_id",
@@ -308,6 +322,55 @@ const stepsView = view("workflow_steps").names({
308
322
  r.finished_at,
309
323
  r.duration_ms
310
324
  ])]);
325
+ const triggerStatuses = [
326
+ "active",
327
+ "failing",
328
+ "needs_sign_in",
329
+ "stopping"
330
+ ];
331
+ /** The trigger of each enabled workflow, as the engine keeps it: its type, status and last error, never its state. */
332
+ const triggersResource = (schema) => resource("workflow_triggers").table(`${schema}.triggers`).fields({
333
+ id: f.uuid().readOnly().dbDefault(),
334
+ workflow_id: f.relation("workflows").readOnly().filterable(),
335
+ version: f.integer().readOnly(),
336
+ type: f.text().readOnly(),
337
+ status: f.enum(triggerStatuses).readOnly().filterable(),
338
+ last_error: f.json().optional().readOnly(),
339
+ next_poll_at: f.timestamp().optional().readOnly(),
340
+ tenant_id: f.text().optional().readOnly(),
341
+ updated_at: f.timestamp().readOnly().dbDefault().sortable()
342
+ }).primaryKey((r) => r.id).tenant((r) => r.tenant_id).explicitGrants().access({
343
+ create: () => false,
344
+ update: () => false,
345
+ delete: () => false
346
+ });
347
+ const triggersView = view("workflow_triggers").names({
348
+ singular: "Workflow trigger",
349
+ plural: "Workflow triggers"
350
+ }).icon("zap").title((r) => r.type).fields((r) => ({
351
+ workflow_id: r.workflow_id.label("Workflow"),
352
+ status: r.status.valueLabels({
353
+ active: "Active",
354
+ failing: "Failing",
355
+ needs_sign_in: "Needs sign-in",
356
+ stopping: "Stopping"
357
+ }).valueColors({
358
+ active: "green",
359
+ failing: "red",
360
+ needs_sign_in: "amber"
361
+ }),
362
+ last_error: r.last_error.label("Last error"),
363
+ next_poll_at: r.next_poll_at.label("Next poll"),
364
+ updated_at: r.updated_at.label("Updated")
365
+ })).list((r) => ({
366
+ columns: [
367
+ r.workflow_id,
368
+ r.type,
369
+ r.status,
370
+ r.updated_at
371
+ ],
372
+ sort: [[r.updated_at, "desc"]]
373
+ }));
311
374
  /**
312
375
  * The resources and views `workflows` adds to an app, over the tables of `schema`: Workflows with their versions, runs
313
376
  * and steps. Config only; the server checks and runs them.
@@ -320,7 +383,9 @@ const workflowResources = (schema) => ({
320
383
  workflowRuns: runsResource(schema),
321
384
  workflowRunsView: runsView,
322
385
  workflowSteps: stepsResource(schema),
323
- workflowStepsView: stepsView
386
+ workflowStepsView: stepsView,
387
+ workflowTriggers: triggersResource(schema),
388
+ workflowTriggersView: triggersView
324
389
  });
325
390
  //#endregion
326
391
  export { workflowResources };
@@ -0,0 +1,22 @@
1
+ //#region src/workflows/workflow-types-path.d.ts
2
+ /** The path of the app's workflow trigger and step types under the API's system path (`/api`), as the editor reads them. */
3
+ export declare const workflowTypesPath: (systemPath: string) => string;
4
+ /**
5
+ * A trigger or step type as the server lists it: `id` is a Task's `Resource` or the key of a definition's `trigger`,
6
+ * `input` the JSON Schema of its input, and `available` false, with the `reason`, when this app cannot run it.
7
+ */
8
+ export type WorkflowTypeEntry = {
9
+ id: string;
10
+ kind: string;
11
+ label: string;
12
+ description: string;
13
+ input: Record<string, unknown>;
14
+ available: boolean;
15
+ reason?: string;
16
+ };
17
+ /** The app's step types and triggers, `manual` among them, built-in and plugin types alike. */
18
+ export type WorkflowTypeList = {
19
+ step_types: WorkflowTypeEntry[];
20
+ triggers: WorkflowTypeEntry[];
21
+ };
22
+ //#endregion
@@ -0,0 +1,5 @@
1
+ //#region src/workflows/workflow-types-path.ts
2
+ /** The path of the app's workflow trigger and step types under the API's system path (`/api`), as the editor reads them. */
3
+ const workflowTypesPath = (systemPath) => `${systemPath}/workflows/types`;
4
+ //#endregion
5
+ export { workflowTypesPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/schema",
3
- "version": "0.67.12",
3
+ "version": "0.68.1",
4
4
  "description": "Resource, field, view and access definitions for Protobase configs.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -1,30 +0,0 @@
1
- //#region src/workflows/step-types.d.ts
2
- /**
3
- * How a step type's argument is written: `text`, one line, which may be an expression; `choice`, one of `options`;
4
- * `json`, any JSON value or an expression, such as an object of field values.
5
- */
6
- export type StepInputKind = 'text' | 'choice' | 'json';
7
- /** An argument of a step type: its name in `Arguments`, how it is written, and whether a save needs it. */
8
- export type StepInput = {
9
- name: string;
10
- kind: StepInputKind;
11
- required?: boolean;
12
- options?: readonly string[];
13
- };
14
- /**
15
- * The step types a workflow's Task names as its `Resource`, each with its arguments: the server runs them, a save
16
- * refuses a Task without a required one, and the editor draws a form of them.
17
- */
18
- export declare const workflowStepTypes: {
19
- readonly 'app:http': readonly [StepInput, StepInput, StepInput, StepInput, StepInput, StepInput];
20
- readonly 'app:records.create': readonly [StepInput, StepInput];
21
- readonly 'app:records.update': readonly [StepInput, StepInput, StepInput];
22
- readonly 'app:records.get': readonly [StepInput, StepInput];
23
- readonly 'app:records.list': readonly [StepInput, StepInput, StepInput, StepInput];
24
- readonly 'app:mail.send': readonly [StepInput, StepInput, StepInput, StepInput, StepInput];
25
- readonly 'app:piece': readonly [StepInput, StepInput, StepInput, StepInput];
26
- };
27
- export type WorkflowStepType = keyof typeof workflowStepTypes;
28
- /** The names of the arguments each step type needs, as the definition check takes them. */
29
- export declare const requiredStepInputs: Record<WorkflowStepType, string[]>;
30
- //#endregion
@@ -1,70 +0,0 @@
1
- //#region src/workflows/step-types.ts
2
- const text = (name, required = false) => ({
3
- name,
4
- kind: "text",
5
- ...required && { required }
6
- });
7
- const json = (name, required = false) => ({
8
- name,
9
- kind: "json",
10
- ...required && { required }
11
- });
12
- const choice = (name, options) => ({
13
- name,
14
- kind: "choice",
15
- options
16
- });
17
- /**
18
- * The step types a workflow's Task names as its `Resource`, each with its arguments: the server runs them, a save
19
- * refuses a Task without a required one, and the editor draws a form of them.
20
- */
21
- const workflowStepTypes = {
22
- "app:http": [
23
- text("url", true),
24
- choice("method", [
25
- "GET",
26
- "POST",
27
- "PUT",
28
- "PATCH",
29
- "DELETE"
30
- ]),
31
- json("headers"),
32
- json("query"),
33
- json("body"),
34
- choice("response", [
35
- "json",
36
- "text",
37
- "file"
38
- ])
39
- ],
40
- "app:records.create": [text("resource", true), json("data", true)],
41
- "app:records.update": [
42
- text("resource", true),
43
- json("key", true),
44
- json("data", true)
45
- ],
46
- "app:records.get": [text("resource", true), json("key", true)],
47
- "app:records.list": [
48
- text("resource", true),
49
- text("filter"),
50
- text("order_by"),
51
- json("limit")
52
- ],
53
- "app:mail.send": [
54
- json("to", true),
55
- text("subject", true),
56
- text("text", true),
57
- text("html"),
58
- json("attachments")
59
- ],
60
- "app:piece": [
61
- text("piece", true),
62
- text("action", true),
63
- text("connection"),
64
- json("props")
65
- ]
66
- };
67
- /** The names of the arguments each step type needs, as the definition check takes them. */
68
- const requiredStepInputs = Object.fromEntries(Object.entries(workflowStepTypes).map(([type, inputs]) => [type, inputs.filter((input) => input.required).map((input) => input.name)]));
69
- //#endregion
70
- export { requiredStepInputs, workflowStepTypes };