@automate.ax/catalog 0.64.0 → 0.65.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.
@@ -1,44 +1,97 @@
1
1
  import * as z from "zod/mini";
2
2
  export declare const DASHBOARD_RUN_CONFIG_SCHEMA: z.ZodMiniObject<{
3
3
  description: z.ZodMiniOptional<z.ZodMiniString<string>>;
4
- fields: z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
4
+ fields: z.ZodMiniPrefault<z.ZodMiniArray<z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
5
+ defaultValue: z.ZodMiniOptional<z.ZodMiniString<string>>;
5
6
  maxLength: z.ZodMiniOptional<z.ZodMiniNumberFormat>;
6
7
  minLength: z.ZodMiniOptional<z.ZodMiniNumberFormat>;
7
8
  placeholder: z.ZodMiniOptional<z.ZodMiniString<string>>;
9
+ required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
8
10
  type: z.ZodMiniEnum<{
9
11
  email: "email";
10
12
  text: "text";
11
13
  url: "url";
14
+ phone: "phone";
12
15
  textarea: "textarea";
13
16
  }>;
14
17
  description: z.ZodMiniOptional<z.ZodMiniString<string>>;
15
18
  label: z.ZodMiniString<string>;
16
- required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
19
+ name: z.ZodMiniString<string>;
17
20
  }, z.core.$strip>, z.ZodMiniObject<{
21
+ defaultValue: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
18
22
  max: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
19
23
  min: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
20
24
  placeholder: z.ZodMiniOptional<z.ZodMiniString<string>>;
25
+ required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
21
26
  step: z.ZodMiniPrefault<z.ZodMiniNumber<number>>;
22
27
  type: z.ZodMiniLiteral<"number">;
23
28
  description: z.ZodMiniOptional<z.ZodMiniString<string>>;
24
29
  label: z.ZodMiniString<string>;
30
+ name: z.ZodMiniString<string>;
31
+ }, z.core.$strip>, z.ZodMiniObject<{
32
+ defaultValue: z.ZodMiniOptional<z.iso.ZodMiniISODate>;
33
+ max: z.ZodMiniOptional<z.iso.ZodMiniISODate>;
34
+ min: z.ZodMiniOptional<z.iso.ZodMiniISODate>;
25
35
  required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
36
+ step: z.ZodMiniPrefault<z.ZodMiniNumberFormat>;
37
+ type: z.ZodMiniLiteral<"date">;
38
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
39
+ label: z.ZodMiniString<string>;
40
+ name: z.ZodMiniString<string>;
26
41
  }, z.core.$strip>, z.ZodMiniObject<{
42
+ defaultValue: z.ZodMiniOptional<z.iso.ZodMiniISODateTime>;
43
+ max: z.ZodMiniOptional<z.iso.ZodMiniISODateTime>;
44
+ min: z.ZodMiniOptional<z.iso.ZodMiniISODateTime>;
45
+ required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
46
+ step: z.ZodMiniPrefault<z.ZodMiniNumber<number>>;
47
+ type: z.ZodMiniLiteral<"datetime">;
48
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
49
+ label: z.ZodMiniString<string>;
50
+ name: z.ZodMiniString<string>;
51
+ }, z.core.$strip>, z.ZodMiniObject<{
52
+ type: z.ZodMiniLiteral<"select">;
53
+ defaultValue: z.ZodMiniOptional<z.ZodMiniString<string>>;
27
54
  options: z.ZodMiniArray<z.ZodMiniObject<{
28
55
  label: z.ZodMiniString<string>;
29
56
  value: z.ZodMiniString<string>;
30
57
  }, z.core.$strip>>;
31
58
  placeholder: z.ZodMiniOptional<z.ZodMiniString<string>>;
32
- type: z.ZodMiniLiteral<"select">;
59
+ required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
33
60
  description: z.ZodMiniOptional<z.ZodMiniString<string>>;
34
61
  label: z.ZodMiniString<string>;
62
+ name: z.ZodMiniString<string>;
63
+ }, z.core.$strip>, z.ZodMiniObject<{
64
+ type: z.ZodMiniLiteral<"radio">;
65
+ defaultValue: z.ZodMiniOptional<z.ZodMiniString<string>>;
66
+ options: z.ZodMiniArray<z.ZodMiniObject<{
67
+ label: z.ZodMiniString<string>;
68
+ value: z.ZodMiniString<string>;
69
+ }, z.core.$strip>>;
70
+ placeholder: z.ZodMiniOptional<z.ZodMiniString<string>>;
35
71
  required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
72
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
73
+ label: z.ZodMiniString<string>;
74
+ name: z.ZodMiniString<string>;
36
75
  }, z.core.$strip>, z.ZodMiniObject<{
37
- type: z.ZodMiniLiteral<"checkbox">;
76
+ defaultValue: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
77
+ options: z.ZodMiniArray<z.ZodMiniObject<{
78
+ label: z.ZodMiniString<string>;
79
+ value: z.ZodMiniString<string>;
80
+ }, z.core.$strip>>;
81
+ placeholder: z.ZodMiniOptional<z.ZodMiniString<string>>;
82
+ required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
83
+ type: z.ZodMiniLiteral<"multi-select">;
38
84
  description: z.ZodMiniOptional<z.ZodMiniString<string>>;
39
85
  label: z.ZodMiniString<string>;
86
+ name: z.ZodMiniString<string>;
87
+ }, z.core.$strip>, z.ZodMiniObject<{
88
+ defaultValue: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
40
89
  required: z.ZodMiniPrefault<z.ZodMiniBoolean<boolean>>;
41
- }, z.core.$strip>], "type">>;
90
+ type: z.ZodMiniLiteral<"checkbox">;
91
+ description: z.ZodMiniOptional<z.ZodMiniString<string>>;
92
+ label: z.ZodMiniString<string>;
93
+ name: z.ZodMiniString<string>;
94
+ }, z.core.$strip>], "type">>>;
42
95
  submitLabel: z.ZodMiniPrefault<z.ZodMiniString<string>>;
43
96
  title: z.ZodMiniString<string>;
44
97
  }, z.core.$strip>;
@@ -94,15 +147,19 @@ interface DashboardRunFieldBase {
94
147
  description?: string;
95
148
  /** Visible field label. */
96
149
  label: string;
150
+ /** Property name used in submitted trigger data. */
151
+ name: string;
97
152
  /** Whether the form must contain a value. Defaults to true. */
98
153
  required?: boolean;
99
154
  }
100
155
  export type DashboardRunField = (DashboardRunFieldBase & {
156
+ defaultValue?: string;
101
157
  maxLength?: number;
102
158
  minLength?: number;
103
159
  placeholder?: string;
104
- type: "email" | "text" | "textarea" | "url";
160
+ type: "email" | "phone" | "text" | "textarea" | "url";
105
161
  }) | (DashboardRunFieldBase & {
162
+ defaultValue?: number;
106
163
  max?: number;
107
164
  min?: number;
108
165
  placeholder?: string;
@@ -110,6 +167,23 @@ export type DashboardRunField = (DashboardRunFieldBase & {
110
167
  step?: number;
111
168
  type: "number";
112
169
  }) | (DashboardRunFieldBase & {
170
+ /** Initial local date in `YYYY-MM-DD` format. */
171
+ defaultValue?: string;
172
+ max?: string;
173
+ min?: string;
174
+ /** Allowed increment in days. Defaults to 1. */
175
+ step?: number;
176
+ type: "date";
177
+ }) | (DashboardRunFieldBase & {
178
+ /** Initial local date and time in `YYYY-MM-DDTHH:mm` format. */
179
+ defaultValue?: string;
180
+ max?: string;
181
+ min?: string;
182
+ /** Allowed increment in seconds. Defaults to 60. */
183
+ step?: number;
184
+ type: "datetime";
185
+ }) | (DashboardRunFieldBase & {
186
+ defaultValue?: string;
113
187
  options: readonly {
114
188
  label: string;
115
189
  value: string;
@@ -117,13 +191,32 @@ export type DashboardRunField = (DashboardRunFieldBase & {
117
191
  placeholder?: string;
118
192
  type: "select";
119
193
  }) | (DashboardRunFieldBase & {
194
+ defaultValue?: string;
195
+ options: readonly {
196
+ label: string;
197
+ value: string;
198
+ }[];
199
+ type: "radio";
200
+ }) | (DashboardRunFieldBase & {
201
+ defaultValue?: readonly string[];
202
+ options: readonly {
203
+ label: string;
204
+ value: string;
205
+ }[];
206
+ placeholder?: string;
207
+ type: "multi-select";
208
+ }) | (DashboardRunFieldBase & {
209
+ /** Initial checked state. Defaults to false. */
210
+ defaultValue?: boolean;
211
+ /** Whether the checkbox must be checked. Defaults to false. */
212
+ required?: boolean;
120
213
  type: "checkbox";
121
214
  });
122
- export interface DashboardRunConfig<TFields extends Record<string, DashboardRunField> = Record<string, DashboardRunField>> {
215
+ export interface DashboardRunConfig<TFields extends readonly DashboardRunField[] = readonly DashboardRunField[]> {
123
216
  /** Supporting text shown below the form title. */
124
217
  description?: string;
125
- /** Named fields rendered in declaration order. */
126
- fields: TFields;
218
+ /** Ordered fields. Omit to run immediately. */
219
+ fields?: TFields;
127
220
  /** Submit button text. Defaults to "Run". */
128
221
  submitLabel?: string;
129
222
  /** Form heading. */
@@ -1,6 +1,7 @@
1
1
  import * as z from "zod/mini";
2
2
  const MAX_EMAIL_LOCAL_PART_BYTES = 64;
3
3
  const EMAIL_SCHEMA = z.email();
4
+ const URL_SCHEMA = z.url();
4
5
  const CRON_PRESENTATION_CONFIG_SCHEMA = z.object({
5
6
  schedule: z.string(),
6
7
  timeZone: z.prefault(z.string(), "UTC"),
@@ -25,25 +26,48 @@ const FIELD_LENGTH_SCHEMA = z.int().check(z.nonnegative());
25
26
  const DASHBOARD_RUN_FIELD_BASE_SCHEMA = {
26
27
  description: z.optional(FIELD_DESCRIPTION_SCHEMA),
27
28
  label: FIELD_LABEL_SCHEMA,
28
- required: z.prefault(z.boolean(), true),
29
+ name: FIELD_NAME_SCHEMA,
29
30
  };
30
31
  const DASHBOARD_RUN_TEXT_FIELD_SCHEMA = z.object({
31
32
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
33
+ defaultValue: z.optional(z.string()),
32
34
  maxLength: z.optional(FIELD_LENGTH_SCHEMA),
33
35
  minLength: z.optional(FIELD_LENGTH_SCHEMA),
34
36
  placeholder: z.optional(z.string()),
35
- type: z.enum(["email", "text", "textarea", "url"]),
37
+ required: z.prefault(z.boolean(), true),
38
+ type: z.enum(["email", "phone", "text", "textarea", "url"]),
36
39
  });
37
40
  const DASHBOARD_RUN_NUMBER_FIELD_SCHEMA = z.object({
38
41
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
42
+ defaultValue: z.optional(z.number()),
39
43
  max: z.optional(z.number()),
40
44
  min: z.optional(z.number()),
41
45
  placeholder: z.optional(z.string()),
46
+ required: z.prefault(z.boolean(), true),
42
47
  step: z.prefault(z.number().check(z.positive()), 1),
43
48
  type: z.literal("number"),
44
49
  });
45
- const DASHBOARD_RUN_SELECT_FIELD_SCHEMA = z.object({
50
+ const DASHBOARD_RUN_DATE_FIELD_SCHEMA = z.object({
51
+ ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
52
+ defaultValue: z.optional(z.iso.date()),
53
+ max: z.optional(z.iso.date()),
54
+ min: z.optional(z.iso.date()),
55
+ required: z.prefault(z.boolean(), true),
56
+ step: z.prefault(z.int().check(z.positive()), 1),
57
+ type: z.literal("date"),
58
+ });
59
+ const DASHBOARD_RUN_DATETIME_FIELD_SCHEMA = z.object({
60
+ ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
61
+ defaultValue: z.optional(z.iso.datetime({ local: true })),
62
+ max: z.optional(z.iso.datetime({ local: true })),
63
+ min: z.optional(z.iso.datetime({ local: true })),
64
+ required: z.prefault(z.boolean(), true),
65
+ step: z.prefault(z.number().check(z.positive()), 60),
66
+ type: z.literal("datetime"),
67
+ });
68
+ const DASHBOARD_RUN_CHOICE_FIELD_SCHEMA = {
46
69
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
70
+ defaultValue: z.optional(FIELD_NAME_SCHEMA),
47
71
  options: z
48
72
  .array(z.object({
49
73
  label: FIELD_LABEL_SCHEMA,
@@ -51,20 +75,49 @@ const DASHBOARD_RUN_SELECT_FIELD_SCHEMA = z.object({
51
75
  }))
52
76
  .check(z.minLength(1)),
53
77
  placeholder: z.optional(z.string()),
78
+ required: z.prefault(z.boolean(), true),
79
+ };
80
+ const DASHBOARD_RUN_SELECT_FIELD_SCHEMA = z.object({
81
+ ...DASHBOARD_RUN_CHOICE_FIELD_SCHEMA,
54
82
  type: z.literal("select"),
55
83
  });
84
+ const DASHBOARD_RUN_RADIO_FIELD_SCHEMA = z.object({
85
+ ...DASHBOARD_RUN_CHOICE_FIELD_SCHEMA,
86
+ type: z.literal("radio"),
87
+ });
88
+ const DASHBOARD_RUN_MULTI_SELECT_FIELD_SCHEMA = z.object({
89
+ ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
90
+ defaultValue: z.optional(z.array(FIELD_NAME_SCHEMA)),
91
+ options: z
92
+ .array(z.object({
93
+ label: FIELD_LABEL_SCHEMA,
94
+ value: FIELD_NAME_SCHEMA,
95
+ }))
96
+ .check(z.minLength(1)),
97
+ placeholder: z.optional(z.string()),
98
+ required: z.prefault(z.boolean(), true),
99
+ type: z.literal("multi-select"),
100
+ });
56
101
  const DASHBOARD_RUN_CHECKBOX_FIELD_SCHEMA = z.object({
57
102
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
103
+ defaultValue: z.prefault(z.boolean(), false),
104
+ required: z.prefault(z.boolean(), false),
58
105
  type: z.literal("checkbox"),
59
106
  });
107
+ const DASHBOARD_RUN_FIELD_SCHEMA = z.discriminatedUnion("type", [
108
+ DASHBOARD_RUN_TEXT_FIELD_SCHEMA,
109
+ DASHBOARD_RUN_NUMBER_FIELD_SCHEMA,
110
+ DASHBOARD_RUN_DATE_FIELD_SCHEMA,
111
+ DASHBOARD_RUN_DATETIME_FIELD_SCHEMA,
112
+ DASHBOARD_RUN_SELECT_FIELD_SCHEMA,
113
+ DASHBOARD_RUN_RADIO_FIELD_SCHEMA,
114
+ DASHBOARD_RUN_MULTI_SELECT_FIELD_SCHEMA,
115
+ DASHBOARD_RUN_CHECKBOX_FIELD_SCHEMA,
116
+ ]);
117
+ const DASHBOARD_RUN_FIELDS_SCHEMA = z.array(DASHBOARD_RUN_FIELD_SCHEMA).check(z.refine((fields) => new Set(fields.map((field) => field.name)).size === fields.length, "Dashboard run field names must be unique."), z.refine((fields) => fields.every(isDashboardRunFieldDefaultValid), "Dashboard run field defaults must satisfy their field constraints."));
60
118
  export const DASHBOARD_RUN_CONFIG_SCHEMA = z.object({
61
119
  description: z.optional(FIELD_DESCRIPTION_SCHEMA),
62
- fields: z.record(FIELD_NAME_SCHEMA, z.discriminatedUnion("type", [
63
- DASHBOARD_RUN_TEXT_FIELD_SCHEMA,
64
- DASHBOARD_RUN_NUMBER_FIELD_SCHEMA,
65
- DASHBOARD_RUN_SELECT_FIELD_SCHEMA,
66
- DASHBOARD_RUN_CHECKBOX_FIELD_SCHEMA,
67
- ])),
120
+ fields: z.prefault(DASHBOARD_RUN_FIELDS_SCHEMA, []),
68
121
  submitLabel: z.prefault(z.string().check(z.trim(), z.minLength(1), z.maxLength(100)), "Run"),
69
122
  title: z.string().check(z.trim(), z.minLength(1), z.maxLength(255)),
70
123
  });
@@ -212,6 +265,63 @@ export function getDashboardRunEndpointKey(options) {
212
265
  options.hookSlot,
213
266
  ].join(".")}`;
214
267
  }
268
+ /**
269
+ * Checks a configured default against the same constraints as submitted data.
270
+ *
271
+ * @param field - Field definition to validate.
272
+ */
273
+ function isDashboardRunFieldDefaultValid(field) {
274
+ if (field.type === "checkbox" || field.defaultValue === undefined)
275
+ return true;
276
+ if (field.type === "multi-select") {
277
+ if (field.required !== false && field.defaultValue.length === 0)
278
+ return false;
279
+ return field.defaultValue.every((value) => field.options.some((option) => option.value === value));
280
+ }
281
+ if (field.type === "radio" || field.type === "select") {
282
+ return field.options.some((option) => option.value === field.defaultValue);
283
+ }
284
+ if (field.type === "number") {
285
+ return ((field.min === undefined || field.defaultValue >= field.min) &&
286
+ (field.max === undefined || field.defaultValue <= field.max) &&
287
+ !isDashboardRunStepMismatch(field.defaultValue, field.min ?? field.defaultValue, field.step ?? 1));
288
+ }
289
+ if (field.type === "date" || field.type === "datetime") {
290
+ const defaultValue = parseDashboardRunDateValue(field.type, field.defaultValue);
291
+ return ((field.min === undefined ||
292
+ defaultValue >= parseDashboardRunDateValue(field.type, field.min)) &&
293
+ (field.max === undefined ||
294
+ defaultValue <= parseDashboardRunDateValue(field.type, field.max)) &&
295
+ !isDashboardRunStepMismatch(defaultValue, parseDashboardRunDateValue(field.type, field.min ?? field.defaultValue), (field.step ?? (field.type === "date" ? 1 : 60)) *
296
+ (field.type === "date" ? 86_400_000 : 1_000)));
297
+ }
298
+ const defaultValue = field.defaultValue.trim();
299
+ return ((defaultValue.length > 0 || field.required === false) &&
300
+ (field.minLength === undefined || defaultValue.length >= field.minLength) &&
301
+ (field.maxLength === undefined || defaultValue.length <= field.maxLength) &&
302
+ (field.type !== "email" || EMAIL_SCHEMA.safeParse(defaultValue).success) &&
303
+ (field.type !== "url" || URL_SCHEMA.safeParse(defaultValue).success));
304
+ }
305
+ /**
306
+ * Checks whether a value falls outside an allowed step interval.
307
+ *
308
+ * @param value - Configured default value.
309
+ * @param base - Value from which step intervals begin.
310
+ * @param step - Allowed interval size.
311
+ */
312
+ function isDashboardRunStepMismatch(value, base, step) {
313
+ const intervals = (value - base) / step;
314
+ return Math.abs(intervals - Math.round(intervals)) > 1e-9;
315
+ }
316
+ /**
317
+ * Converts a validated local date or date-time string to a UTC number.
318
+ *
319
+ * @param type - Temporal field type.
320
+ * @param value - Local ISO value to convert.
321
+ */
322
+ function parseDashboardRunDateValue(type, value) {
323
+ return Date.parse(type === "date" ? `${value}T00:00:00Z` : `${value}Z`);
324
+ }
215
325
  /**
216
326
  * Removes the redundant TypeID prefix from an address router.
217
327
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/catalog",
3
- "version": "0.64.0",
3
+ "version": "0.65.1",
4
4
  "description": "Shared integration service and trigger definitions for Automate.ax.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -3,6 +3,7 @@ import * as z from "zod/mini"
3
3
 
4
4
  const MAX_EMAIL_LOCAL_PART_BYTES = 64
5
5
  const EMAIL_SCHEMA = z.email()
6
+ const URL_SCHEMA = z.url()
6
7
  const CRON_PRESENTATION_CONFIG_SCHEMA = z.object({
7
8
  schedule: z.string(),
8
9
  timeZone: z.prefault(z.string(), "UTC"),
@@ -27,25 +28,48 @@ const FIELD_LENGTH_SCHEMA = z.int().check(z.nonnegative())
27
28
  const DASHBOARD_RUN_FIELD_BASE_SCHEMA = {
28
29
  description: z.optional(FIELD_DESCRIPTION_SCHEMA),
29
30
  label: FIELD_LABEL_SCHEMA,
30
- required: z.prefault(z.boolean(), true),
31
+ name: FIELD_NAME_SCHEMA,
31
32
  }
32
33
  const DASHBOARD_RUN_TEXT_FIELD_SCHEMA = z.object({
33
34
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
35
+ defaultValue: z.optional(z.string()),
34
36
  maxLength: z.optional(FIELD_LENGTH_SCHEMA),
35
37
  minLength: z.optional(FIELD_LENGTH_SCHEMA),
36
38
  placeholder: z.optional(z.string()),
37
- type: z.enum(["email", "text", "textarea", "url"]),
39
+ required: z.prefault(z.boolean(), true),
40
+ type: z.enum(["email", "phone", "text", "textarea", "url"]),
38
41
  })
39
42
  const DASHBOARD_RUN_NUMBER_FIELD_SCHEMA = z.object({
40
43
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
44
+ defaultValue: z.optional(z.number()),
41
45
  max: z.optional(z.number()),
42
46
  min: z.optional(z.number()),
43
47
  placeholder: z.optional(z.string()),
48
+ required: z.prefault(z.boolean(), true),
44
49
  step: z.prefault(z.number().check(z.positive()), 1),
45
50
  type: z.literal("number"),
46
51
  })
47
- const DASHBOARD_RUN_SELECT_FIELD_SCHEMA = z.object({
52
+ const DASHBOARD_RUN_DATE_FIELD_SCHEMA = z.object({
53
+ ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
54
+ defaultValue: z.optional(z.iso.date()),
55
+ max: z.optional(z.iso.date()),
56
+ min: z.optional(z.iso.date()),
57
+ required: z.prefault(z.boolean(), true),
58
+ step: z.prefault(z.int().check(z.positive()), 1),
59
+ type: z.literal("date"),
60
+ })
61
+ const DASHBOARD_RUN_DATETIME_FIELD_SCHEMA = z.object({
62
+ ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
63
+ defaultValue: z.optional(z.iso.datetime({ local: true })),
64
+ max: z.optional(z.iso.datetime({ local: true })),
65
+ min: z.optional(z.iso.datetime({ local: true })),
66
+ required: z.prefault(z.boolean(), true),
67
+ step: z.prefault(z.number().check(z.positive()), 60),
68
+ type: z.literal("datetime"),
69
+ })
70
+ const DASHBOARD_RUN_CHOICE_FIELD_SCHEMA = {
48
71
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
72
+ defaultValue: z.optional(FIELD_NAME_SCHEMA),
49
73
  options: z
50
74
  .array(
51
75
  z.object({
@@ -55,24 +79,62 @@ const DASHBOARD_RUN_SELECT_FIELD_SCHEMA = z.object({
55
79
  )
56
80
  .check(z.minLength(1)),
57
81
  placeholder: z.optional(z.string()),
82
+ required: z.prefault(z.boolean(), true),
83
+ }
84
+ const DASHBOARD_RUN_SELECT_FIELD_SCHEMA = z.object({
85
+ ...DASHBOARD_RUN_CHOICE_FIELD_SCHEMA,
58
86
  type: z.literal("select"),
59
87
  })
88
+ const DASHBOARD_RUN_RADIO_FIELD_SCHEMA = z.object({
89
+ ...DASHBOARD_RUN_CHOICE_FIELD_SCHEMA,
90
+ type: z.literal("radio"),
91
+ })
92
+ const DASHBOARD_RUN_MULTI_SELECT_FIELD_SCHEMA = z.object({
93
+ ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
94
+ defaultValue: z.optional(z.array(FIELD_NAME_SCHEMA)),
95
+ options: z
96
+ .array(
97
+ z.object({
98
+ label: FIELD_LABEL_SCHEMA,
99
+ value: FIELD_NAME_SCHEMA,
100
+ }),
101
+ )
102
+ .check(z.minLength(1)),
103
+ placeholder: z.optional(z.string()),
104
+ required: z.prefault(z.boolean(), true),
105
+ type: z.literal("multi-select"),
106
+ })
60
107
  const DASHBOARD_RUN_CHECKBOX_FIELD_SCHEMA = z.object({
61
108
  ...DASHBOARD_RUN_FIELD_BASE_SCHEMA,
109
+ defaultValue: z.prefault(z.boolean(), false),
110
+ required: z.prefault(z.boolean(), false),
62
111
  type: z.literal("checkbox"),
63
112
  })
113
+ const DASHBOARD_RUN_FIELD_SCHEMA = z.discriminatedUnion("type", [
114
+ DASHBOARD_RUN_TEXT_FIELD_SCHEMA,
115
+ DASHBOARD_RUN_NUMBER_FIELD_SCHEMA,
116
+ DASHBOARD_RUN_DATE_FIELD_SCHEMA,
117
+ DASHBOARD_RUN_DATETIME_FIELD_SCHEMA,
118
+ DASHBOARD_RUN_SELECT_FIELD_SCHEMA,
119
+ DASHBOARD_RUN_RADIO_FIELD_SCHEMA,
120
+ DASHBOARD_RUN_MULTI_SELECT_FIELD_SCHEMA,
121
+ DASHBOARD_RUN_CHECKBOX_FIELD_SCHEMA,
122
+ ])
123
+ const DASHBOARD_RUN_FIELDS_SCHEMA = z.array(DASHBOARD_RUN_FIELD_SCHEMA).check(
124
+ z.refine(
125
+ (fields) =>
126
+ new Set(fields.map((field) => field.name)).size === fields.length,
127
+ "Dashboard run field names must be unique.",
128
+ ),
129
+ z.refine(
130
+ (fields) => fields.every(isDashboardRunFieldDefaultValid),
131
+ "Dashboard run field defaults must satisfy their field constraints.",
132
+ ),
133
+ )
64
134
 
65
135
  export const DASHBOARD_RUN_CONFIG_SCHEMA = z.object({
66
136
  description: z.optional(FIELD_DESCRIPTION_SCHEMA),
67
- fields: z.record(
68
- FIELD_NAME_SCHEMA,
69
- z.discriminatedUnion("type", [
70
- DASHBOARD_RUN_TEXT_FIELD_SCHEMA,
71
- DASHBOARD_RUN_NUMBER_FIELD_SCHEMA,
72
- DASHBOARD_RUN_SELECT_FIELD_SCHEMA,
73
- DASHBOARD_RUN_CHECKBOX_FIELD_SCHEMA,
74
- ]),
75
- ),
137
+ fields: z.prefault(DASHBOARD_RUN_FIELDS_SCHEMA, []),
76
138
  submitLabel: z.prefault(
77
139
  z.string().check(z.trim(), z.minLength(1), z.maxLength(100)),
78
140
  "Run",
@@ -163,18 +225,23 @@ interface DashboardRunFieldBase {
163
225
  /** Visible field label. */
164
226
  label: string
165
227
 
228
+ /** Property name used in submitted trigger data. */
229
+ name: string
230
+
166
231
  /** Whether the form must contain a value. Defaults to true. */
167
232
  required?: boolean
168
233
  }
169
234
 
170
235
  export type DashboardRunField =
171
236
  | (DashboardRunFieldBase & {
237
+ defaultValue?: string
172
238
  maxLength?: number
173
239
  minLength?: number
174
240
  placeholder?: string
175
- type: "email" | "text" | "textarea" | "url"
241
+ type: "email" | "phone" | "text" | "textarea" | "url"
176
242
  })
177
243
  | (DashboardRunFieldBase & {
244
+ defaultValue?: number
178
245
  max?: number
179
246
  min?: number
180
247
  placeholder?: string
@@ -184,25 +251,59 @@ export type DashboardRunField =
184
251
  type: "number"
185
252
  })
186
253
  | (DashboardRunFieldBase & {
254
+ /** Initial local date in `YYYY-MM-DD` format. */
255
+ defaultValue?: string
256
+ max?: string
257
+ min?: string
258
+
259
+ /** Allowed increment in days. Defaults to 1. */
260
+ step?: number
261
+ type: "date"
262
+ })
263
+ | (DashboardRunFieldBase & {
264
+ /** Initial local date and time in `YYYY-MM-DDTHH:mm` format. */
265
+ defaultValue?: string
266
+ max?: string
267
+ min?: string
268
+
269
+ /** Allowed increment in seconds. Defaults to 60. */
270
+ step?: number
271
+ type: "datetime"
272
+ })
273
+ | (DashboardRunFieldBase & {
274
+ defaultValue?: string
187
275
  options: readonly { label: string; value: string }[]
188
276
  placeholder?: string
189
277
  type: "select"
190
278
  })
191
279
  | (DashboardRunFieldBase & {
280
+ defaultValue?: string
281
+ options: readonly { label: string; value: string }[]
282
+ type: "radio"
283
+ })
284
+ | (DashboardRunFieldBase & {
285
+ defaultValue?: readonly string[]
286
+ options: readonly { label: string; value: string }[]
287
+ placeholder?: string
288
+ type: "multi-select"
289
+ })
290
+ | (DashboardRunFieldBase & {
291
+ /** Initial checked state. Defaults to false. */
292
+ defaultValue?: boolean
293
+
294
+ /** Whether the checkbox must be checked. Defaults to false. */
295
+ required?: boolean
192
296
  type: "checkbox"
193
297
  })
194
298
 
195
299
  export interface DashboardRunConfig<
196
- TFields extends Record<string, DashboardRunField> = Record<
197
- string,
198
- DashboardRunField
199
- >,
300
+ TFields extends readonly DashboardRunField[] = readonly DashboardRunField[],
200
301
  > {
201
302
  /** Supporting text shown below the form title. */
202
303
  description?: string
203
304
 
204
- /** Named fields rendered in declaration order. */
205
- fields: TFields
305
+ /** Ordered fields. Omit to run immediately. */
306
+ fields?: TFields
206
307
 
207
308
  /** Submit button text. Defaults to "Run". */
208
309
  submitLabel?: string
@@ -332,6 +433,89 @@ export function getDashboardRunEndpointKey(
332
433
  ].join(".")}`
333
434
  }
334
435
 
436
+ /**
437
+ * Checks a configured default against the same constraints as submitted data.
438
+ *
439
+ * @param field - Field definition to validate.
440
+ */
441
+ function isDashboardRunFieldDefaultValid(field: DashboardRunField) {
442
+ if (field.type === "checkbox" || field.defaultValue === undefined) return true
443
+
444
+ if (field.type === "multi-select") {
445
+ if (field.required !== false && field.defaultValue.length === 0)
446
+ return false
447
+ return field.defaultValue.every((value) =>
448
+ field.options.some((option) => option.value === value),
449
+ )
450
+ }
451
+
452
+ if (field.type === "radio" || field.type === "select") {
453
+ return field.options.some((option) => option.value === field.defaultValue)
454
+ }
455
+
456
+ if (field.type === "number") {
457
+ return (
458
+ (field.min === undefined || field.defaultValue >= field.min) &&
459
+ (field.max === undefined || field.defaultValue <= field.max) &&
460
+ !isDashboardRunStepMismatch(
461
+ field.defaultValue,
462
+ field.min ?? field.defaultValue,
463
+ field.step ?? 1,
464
+ )
465
+ )
466
+ }
467
+
468
+ if (field.type === "date" || field.type === "datetime") {
469
+ const defaultValue = parseDashboardRunDateValue(
470
+ field.type,
471
+ field.defaultValue,
472
+ )
473
+ return (
474
+ (field.min === undefined ||
475
+ defaultValue >= parseDashboardRunDateValue(field.type, field.min)) &&
476
+ (field.max === undefined ||
477
+ defaultValue <= parseDashboardRunDateValue(field.type, field.max)) &&
478
+ !isDashboardRunStepMismatch(
479
+ defaultValue,
480
+ parseDashboardRunDateValue(field.type, field.min ?? field.defaultValue),
481
+ (field.step ?? (field.type === "date" ? 1 : 60)) *
482
+ (field.type === "date" ? 86_400_000 : 1_000),
483
+ )
484
+ )
485
+ }
486
+
487
+ const defaultValue = field.defaultValue.trim()
488
+ return (
489
+ (defaultValue.length > 0 || field.required === false) &&
490
+ (field.minLength === undefined || defaultValue.length >= field.minLength) &&
491
+ (field.maxLength === undefined || defaultValue.length <= field.maxLength) &&
492
+ (field.type !== "email" || EMAIL_SCHEMA.safeParse(defaultValue).success) &&
493
+ (field.type !== "url" || URL_SCHEMA.safeParse(defaultValue).success)
494
+ )
495
+ }
496
+
497
+ /**
498
+ * Checks whether a value falls outside an allowed step interval.
499
+ *
500
+ * @param value - Configured default value.
501
+ * @param base - Value from which step intervals begin.
502
+ * @param step - Allowed interval size.
503
+ */
504
+ function isDashboardRunStepMismatch(value: number, base: number, step: number) {
505
+ const intervals = (value - base) / step
506
+ return Math.abs(intervals - Math.round(intervals)) > 1e-9
507
+ }
508
+
509
+ /**
510
+ * Converts a validated local date or date-time string to a UTC number.
511
+ *
512
+ * @param type - Temporal field type.
513
+ * @param value - Local ISO value to convert.
514
+ */
515
+ function parseDashboardRunDateValue(type: "date" | "datetime", value: string) {
516
+ return Date.parse(type === "date" ? `${value}T00:00:00Z` : `${value}Z`)
517
+ }
518
+
335
519
  /**
336
520
  * Removes the redundant TypeID prefix from an address router.
337
521
  *