@adminiumjs/manifest 0.3.27 → 0.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/app-grants.d.ts +134 -0
  2. package/dist/app-grants.d.ts.map +1 -0
  3. package/dist/app-grants.js +263 -0
  4. package/dist/app-grants.js.map +1 -0
  5. package/dist/automations.d.ts +2172 -56
  6. package/dist/automations.d.ts.map +1 -1
  7. package/dist/automations.js +438 -25
  8. package/dist/automations.js.map +1 -1
  9. package/dist/compose.d.ts.map +1 -1
  10. package/dist/compose.js +3 -0
  11. package/dist/compose.js.map +1 -1
  12. package/dist/documents.d.ts +1 -0
  13. package/dist/documents.d.ts.map +1 -1
  14. package/dist/documents.js +2 -0
  15. package/dist/documents.js.map +1 -1
  16. package/dist/index.d.ts +11 -5
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +10 -4
  19. package/dist/index.js.map +1 -1
  20. package/dist/ledgers.d.ts.map +1 -1
  21. package/dist/ledgers.js +2 -0
  22. package/dist/ledgers.js.map +1 -1
  23. package/dist/modules.d.ts +93 -0
  24. package/dist/modules.d.ts.map +1 -0
  25. package/dist/modules.js +489 -0
  26. package/dist/modules.js.map +1 -0
  27. package/dist/occupies.d.ts +50 -0
  28. package/dist/occupies.d.ts.map +1 -0
  29. package/dist/occupies.js +123 -0
  30. package/dist/occupies.js.map +1 -0
  31. package/dist/outbox.d.ts +223 -17
  32. package/dist/outbox.d.ts.map +1 -1
  33. package/dist/outbox.js +125 -13
  34. package/dist/outbox.js.map +1 -1
  35. package/dist/page-board.d.ts +144 -0
  36. package/dist/page-board.d.ts.map +1 -0
  37. package/dist/page-board.js +280 -0
  38. package/dist/page-board.js.map +1 -0
  39. package/dist/public-access.d.ts +6 -0
  40. package/dist/public-access.d.ts.map +1 -1
  41. package/dist/public-access.js +74 -0
  42. package/dist/public-access.js.map +1 -1
  43. package/dist/rail.d.ts +50 -0
  44. package/dist/rail.d.ts.map +1 -0
  45. package/dist/rail.js +122 -0
  46. package/dist/rail.js.map +1 -0
  47. package/dist/roles.d.ts +647 -1
  48. package/dist/roles.d.ts.map +1 -1
  49. package/dist/roles.js +459 -3
  50. package/dist/roles.js.map +1 -1
  51. package/dist/sample.d.ts +62 -0
  52. package/dist/sample.d.ts.map +1 -1
  53. package/dist/sample.js +203 -4
  54. package/dist/sample.js.map +1 -1
  55. package/dist/schema.d.ts +4277 -169
  56. package/dist/schema.d.ts.map +1 -1
  57. package/dist/schema.js +539 -40
  58. package/dist/schema.js.map +1 -1
  59. package/dist/shapes.d.ts +3 -2
  60. package/dist/shapes.d.ts.map +1 -1
  61. package/dist/shapes.js +76 -7
  62. package/dist/shapes.js.map +1 -1
  63. package/dist/state-actions.d.ts +4 -0
  64. package/dist/state-actions.d.ts.map +1 -1
  65. package/dist/state-actions.js +7 -0
  66. package/dist/state-actions.js.map +1 -1
  67. package/dist/states.d.ts +20 -1
  68. package/dist/states.d.ts.map +1 -1
  69. package/dist/states.js +47 -5
  70. package/dist/states.js.map +1 -1
  71. package/dist/validate.d.ts.map +1 -1
  72. package/dist/validate.js +41 -0
  73. package/dist/validate.js.map +1 -1
  74. package/dist/words.d.ts +1 -1
  75. package/dist/words.d.ts.map +1 -1
  76. package/dist/words.js +328 -1
  77. package/dist/words.js.map +1 -1
  78. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"automations.d.ts","sourceRoot":"","sources":["../src/automations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"automations.d.ts","sourceRoot":"","sources":["../src/automations.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAW5B,CAAC;AAEL,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
@@ -24,7 +24,9 @@ import { z } from 'zod';
24
24
  import { placeholderNames, requiredNamesOfEmail, showWhenNames } from './placeholders.js';
25
25
  import { bcp47TagSchema, refSchema } from './refs.js';
26
26
  /** The most rules one manifest ships, and the most steps one rule holds. */
27
- export const MANIFEST_AUTOMATIONS_MAX = 12;
27
+ export const MANIFEST_AUTOMATIONS_MAX = 24;
28
+ /** How many a manifest could ship before: one with more says so, so a server that reads only these refuses it by its version. */
29
+ export const MANIFEST_AUTOMATIONS_FIRST_MAX = 12;
28
30
  export const AUTOMATION_NODES_MAX = 40;
29
31
  /** One text, or the same text in several languages (US English always among them). */
30
32
  const localized = (max) => z.union([z.string().max(max), z.record(bcp47TagSchema, z.string().min(1).max(max)).refine((texts) => texts['en-US'] !== undefined, { message: 'a text in several languages includes en-US' })]);
@@ -35,7 +37,15 @@ export const AUTOMATION_CONDITION_OPS = ['is', 'is_not', 'contains', 'gt', 'lt',
35
37
  const RELATIVE_OPS = ['within_next', 'within_last', 'more_than_ago', 'more_than_ahead'];
36
38
  const NO_OPERAND_OPS = ['is_empty', 'not_empty'];
37
39
  const COUNT_OPS = ['is', 'is_not', 'gt', 'lt'];
38
- const operand = z.union([z.string().max(2000), z.number(), z.object({ amount: z.number().int().min(1).max(3650), unit: durationUnit }).strict()]);
40
+ /**
41
+ * An amount of time; `setting` reads it from a whole-number column of the app's settings row when
42
+ * the conditions are read, and `amount` is used while that holds none.
43
+ */
44
+ const operand = z.union([
45
+ z.string().max(2000),
46
+ z.number(),
47
+ z.object({ amount: z.number().int().min(1).max(3650), unit: durationUnit, setting: z.object({ table: refSchema, column: refSchema }).strict().optional() }).strict(),
48
+ ]);
39
49
  const fieldLeft = z.object({ field: refSchema }).strict();
40
50
  const leaf = z.object({ left: fieldLeft, op: z.enum(AUTOMATION_CONDITION_OPS), right: operand.optional() }).strict();
41
51
  const countLeft = z.object({ count: z.object({ table: refSchema, matchColumn: refSchema, equalsField: refSchema, where: leaf.optional() }).strict() }).strict();
@@ -99,22 +109,76 @@ export const manifestTriggerSchema = z.discriminatedUnion('kind', [
99
109
  .object({
100
110
  kind: z.literal('schedule'),
101
111
  schedule: scheduleSchema,
102
- forEach: z.object({ table: refSchema, where: z.array(manifestConditionSchema).max(8), once: z.boolean() }).strict().optional(),
112
+ /**
113
+ * The rows a run visits. `once: true` visits a row one time ever;
114
+ * `once: "per-spell"` one time for each spell the row matches: a row that
115
+ * stops matching and matches again later is visited again (a deal gone
116
+ * quiet, worked on, and quiet once more).
117
+ */
118
+ forEach: z.object({ table: refSchema, where: z.array(manifestConditionSchema).max(8), once: z.union([z.boolean(), z.literal('per-spell')]) }).strict().optional(),
119
+ })
120
+ .strict(),
121
+ ]);
122
+ /** A column of the app's settings row: the row of that table with the lowest key. */
123
+ const settingRef = z.object({ table: refSchema, column: refSchema }).strict();
124
+ /**
125
+ * A value a step writes: text, which may carry `{{record.<column>}}`; the
126
+ * moment it runs; what a column of the app's settings row holds when it runs
127
+ * (`setting`); or a day so many days after a date of the record, or after
128
+ * now, on the business's calendar (`daysAfter`), the days read from a
129
+ * whole-number setting when it holds some.
130
+ */
131
+ const writeValue = z.union([
132
+ z.string().max(4000),
133
+ z.object({ now: z.literal(true) }).strict(),
134
+ z.object({ setting: settingRef }).strict(),
135
+ z
136
+ .object({
137
+ daysAfter: z
138
+ .object({
139
+ column: refSchema.optional(),
140
+ now: z.literal(true).optional(),
141
+ days: z.number().int().min(0).max(3650),
142
+ setting: settingRef.optional(),
143
+ /** The wall time on that day, for a column that holds a moment. */
144
+ at: z.string().regex(HHMM, 'a time such as 09:00').optional(),
145
+ })
146
+ .strict()
147
+ .refine((after) => (after.column === undefined) !== (after.now === undefined), { message: 'days after a column of the record ("column") or after now ("now": true): one of the two' }),
103
148
  })
104
149
  .strict(),
105
150
  ]);
106
- /** A value a step writes: text, which may carry `{{record.<column>}}`, or the moment it runs. */
107
- const writeValue = z.union([z.string().max(4000), z.object({ now: z.literal(true) }).strict()]);
108
151
  const values = z.record(refSchema, writeValue);
109
152
  const roleKey = z.string().regex(/^[a-z][a-z0-9-]*$/, 'a role key');
153
+ /** A staff column of the record, or one of the row a link of it names: `owner_id`, `deal_id.owner_id`. */
154
+ const staffPath = z.string().regex(/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)?$/, 'a column, or <link column>.<column>');
155
+ /** What later steps call a row a step made. */
156
+ const stepName = z.string().regex(/^[a-z][a-z0-9_]{0,39}$/, 'a step name is snake_case, at most 40 characters');
110
157
  export const manifestActionSchema = z.discriminatedUnion('kind', [
111
158
  z
112
159
  .object({
113
160
  kind: z.literal('email'),
114
161
  /** One of the manifest's own templates. */
115
162
  templateKey: z.string().regex(/^[a-z][a-z0-9-]{1,79}$/, 'a template key'),
116
- /** The record's column that holds the address. */
117
- to: z.object({ kind: z.literal('field'), column: refSchema }).strict(),
163
+ /**
164
+ * Who it goes to: `field`, the record's column that holds the address;
165
+ * or `staff`, a staff column of the record (or `<link>.<staff column>`
166
+ * of the row a link of it names), whose person's address Adminium looks up;
167
+ * or `listed`, the people a table that lists people against the record
168
+ * (one of its `belongs` ways `through`) names for it: one mail each, to
169
+ * those of them who can open the record when it is sent.
170
+ */
171
+ to: z.union([
172
+ z.object({ kind: z.literal('field'), column: refSchema }).strict(),
173
+ z.object({ kind: z.literal('staff'), column: staffPath }).strict(),
174
+ z.object({ kind: z.literal('listed'), table: refSchema }).strict(),
175
+ ]),
176
+ /**
177
+ * Where a reply goes: to the person a staff column of the record names
178
+ * (or `<link>.<staff column>` of the row a link of it names), by the
179
+ * address they sign in with. Nobody there: no Reply-To.
180
+ */
181
+ replyTo: z.object({ staff: staffPath }).strict().optional(),
118
182
  /** What fills a placeholder of the template that the record does not. */
119
183
  vars: z
120
184
  .record(z.string().regex(/^[A-Za-z0-9_.-]{1,120}$/), z.string().max(2000))
@@ -122,8 +186,75 @@ export const manifestActionSchema = z.discriminatedUnion('kind', [
122
186
  .optional(),
123
187
  })
124
188
  .strict(),
125
- z.object({ kind: z.literal('notification'), to: z.object({ roles: z.array(roleKey).min(1).max(20) }).strict(), title: localized(200), body: localized(2000).optional() }).strict(),
126
- z.object({ kind: z.literal('record.create'), table: refSchema, values }).strict(),
189
+ z
190
+ .object({
191
+ kind: z.literal('notification'),
192
+ /**
193
+ * Everybody who holds one of the roles; or the person a staff column of
194
+ * the record names (or `<link>.<staff column>`). With `lead: true`, not
195
+ * that person but whoever leads a team they are in (never the person
196
+ * themselves), and of those only who can open the record. `else`: the
197
+ * roles told when the column, or its lead, names nobody; a lead always
198
+ * has one. Or `listed`: the people a table that lists people against the
199
+ * record (one of its `belongs` ways `through`) names for it, of those
200
+ * only who can open the record when the step runs.
201
+ */
202
+ to: z.union([
203
+ z.object({ roles: z.array(roleKey).min(1).max(20) }).strict(),
204
+ z.object({ listed: refSchema }).strict(),
205
+ z
206
+ .object({ column: staffPath, lead: z.literal(true).optional(), else: z.object({ roles: z.array(roleKey).min(1).max(20) }).strict().optional() })
207
+ .strict()
208
+ .refine((to) => to.lead !== true || to.else !== undefined, { message: 'a notice to a lead says who is told when there is none ("else": { "roles": [...] })', path: ['else'] }),
209
+ ]),
210
+ title: localized(200),
211
+ body: localized(2000).optional(),
212
+ /** Told at once, also in the person's quiet hours (a change to today's day sheet). */
213
+ urgent: z.literal(true).optional(),
214
+ })
215
+ .strict(),
216
+ /** `name`: what later steps call the row this one makes, in `{{steps.<name>.<column>}}`. */
217
+ z.object({ kind: z.literal('record.create'), table: refSchema, values, name: stepName.optional() }).strict(),
218
+ /**
219
+ * A summary for each person: one email each, of the rows that are theirs.
220
+ * Each section lists the rows of a table whose `owner` (a staff column) is
221
+ * the person, that meet `where` and that the person may read; a block
222
+ * `email.rows` of the template with `"section": "<key>"` draws it.
223
+ * `"reach": "team"` adds the rows of the people the person leads when it is
224
+ * sent; `sum` adds columns up over every such row; `"by": "owner"` lists a
225
+ * line a person; `target` measures a sum against those people's targets.
226
+ * Nothing in it is read further than the person's own roles reach.
227
+ */
228
+ z
229
+ .object({
230
+ kind: z.literal('digest'),
231
+ templateKey: z.string().regex(/^[a-z][a-z0-9-]{1,79}$/, 'a template key'),
232
+ to: z.object({ roles: z.array(roleKey).min(1).max(20) }).strict(),
233
+ sections: z
234
+ .array(z
235
+ .object({
236
+ key: z.string().regex(/^[a-z][a-z0-9-]{0,39}$/, 'a section key is kebab-case'),
237
+ table: refSchema,
238
+ owner: refSchema,
239
+ where: z.array(manifestConditionSchema).max(8).optional(),
240
+ orderBy: z.object({ column: refSchema, dir: z.enum(['asc', 'desc']).optional() }).strict().optional(),
241
+ limit: z.number().int().min(1).max(50).optional(),
242
+ /** `team`: the person's rows and those of the people they lead when it is sent. Never "all". */
243
+ reach: z.enum(['own', 'team']).optional(),
244
+ /** Number columns added up over every row of the section: `{{sum.<section>.<column>}}`. */
245
+ sum: z.array(refSchema).min(1).max(4).optional(),
246
+ /** One line a person (their name, how many, each sum) instead of one a row. */
247
+ by: z.literal('owner').optional(),
248
+ /** What the sum `of` is measured against: `column` of the rows of `table` whose `owner` names the same people. */
249
+ target: z.object({ table: refSchema, owner: refSchema, column: refSchema, year: refSchema.optional(), of: refSchema }).strict().optional(),
250
+ })
251
+ .strict())
252
+ .min(1)
253
+ .max(6),
254
+ /** Sent also to a person none of whose sections lists a row. Default: such a person gets no email. */
255
+ sendEmpty: z.literal(true).optional(),
256
+ })
257
+ .strict(),
127
258
  z.object({ kind: z.literal('record.update'), values }).strict(),
128
259
  /**
129
260
  * A step an add-on gives (its `addOn.steps`): which add-on, which step, and
@@ -191,6 +322,8 @@ export const manifestAutomationSchema = z
191
322
  enabled: z.boolean(),
192
323
  trigger: manifestTriggerSchema,
193
324
  graph: manifestGraphSchema,
325
+ /** The part of the app the rule belongs to: not run while it is off (see `modules.ts`). */
326
+ module: z.string().regex(/^[a-z][a-z0-9-]{0,39}$/, 'a module id is kebab-case').optional(),
194
327
  })
195
328
  .strict();
196
329
  export const manifestAutomationsSchema = z
@@ -199,6 +332,8 @@ export const manifestAutomationsSchema = z
199
332
  .refine((rules) => new Set(rules.map((rule) => rule.key)).size === rules.length, { message: 'two rules share a key' });
200
333
  /** What every rule's email can read whatever its record is. */
201
334
  export const RULE_EMAIL_VARS = ['now', 'ruleName', 'recordLabel', 'appName'];
335
+ /** What a summary's email can read beside its sections' figures: the person's name, and when the figures were read (`readAt`). */
336
+ export const DIGEST_EMAIL_VARS = ['now', 'ruleName', 'appName', 'name', 'readAt'];
202
337
  /**
203
338
  * Every placeholder a template reads in one language, in the order it reads
204
339
  * them. A `{{row.*}}` is left out: it belongs to the rows a list block draws.
@@ -256,12 +391,30 @@ export function automationIssues(m) {
256
391
  out.push({ path: [...path, 'right'], message: `"${of?.ref ?? ''}.${found.ref}" is a number: compare it with a number (${JSON.stringify(right)} is not one)` });
257
392
  }
258
393
  };
259
- const conditionIssues = (of, condition, path, inScan) => {
394
+ /** An amount read from a setting: a whole-number column of one of the manifest's tables, where the run can read it. */
395
+ const settingIssues = (condition, path, settingsOk) => {
396
+ const right = condition.right;
397
+ if (typeof right !== 'object' || right === null || right.setting === undefined)
398
+ return;
399
+ const at = [...path, 'right', 'setting'];
400
+ if (!settingsOk) {
401
+ out.push({ path: at, message: 'an amount read from a setting is read where a rule visits rows by the clock (forEach.where) and in a condition step, not here' });
402
+ return;
403
+ }
404
+ const held = tableAt(right.setting.table, [...at, 'table']);
405
+ const found = column(held, right.setting.column, [...at, 'column']);
406
+ if (found !== undefined && !['int', 'bigint'].includes(found.type))
407
+ out.push({ path: [...at, 'column'], message: `"${right.setting.table}.${right.setting.column}" is not a whole-number column: it holds an amount of ${right.unit}` });
408
+ };
409
+ const conditionIssues = (of, condition, path, inScan, settingsOk = false) => {
410
+ settingIssues(condition, path, settingsOk);
260
411
  if ('count' in condition.left) {
261
412
  // A scan's filter is one query over the scanned table: it cannot count another table's rows.
262
413
  if (inScan)
263
414
  out.push({ path: [...path, 'left'], message: 'a rule that runs by the clock cannot filter the rows it visits by a count: keep the count in a column of the row (a rollup), and compare that' });
264
415
  const count = condition.left.count;
416
+ if (typeof count.where?.right === 'object' && count.where.right.setting !== undefined)
417
+ out.push({ path: [...path, 'left', 'count', 'where', 'right', 'setting'], message: 'a count reads no setting: write the amount' });
265
418
  const counted = tableAt(count.table, [...path, 'left', 'count', 'table']);
266
419
  column(counted, count.matchColumn, [...path, 'left', 'count', 'matchColumn']);
267
420
  column(of, count.equalsField, [...path, 'left', 'count', 'equalsField']);
@@ -296,8 +449,8 @@ export function automationIssues(m) {
296
449
  else if (trigger.forEach !== undefined) {
297
450
  const forEach = trigger.forEach;
298
451
  record = tableAt(forEach.table, at('trigger', 'forEach', 'table'));
299
- forEach.where.forEach((condition, c) => conditionIssues(record, condition, at('trigger', 'forEach', 'where', c), true));
300
- if (forEach.once && forEach.where.length === 0)
452
+ forEach.where.forEach((condition, c) => conditionIssues(record, condition, at('trigger', 'forEach', 'where', c), true, true));
453
+ if (forEach.once !== false && forEach.where.length === 0)
301
454
  out.push({ path: at('trigger', 'forEach', 'once'), message: 'a row visited once needs a condition that picks it: without one every row is visited on the first run' });
302
455
  }
303
456
  if (trigger.kind === 'schedule' && trigger.schedule.kind !== 'interval') {
@@ -307,10 +460,71 @@ export function automationIssues(m) {
307
460
  if (schedule.kind === 'monthly' && schedule.dayOfMonth === undefined)
308
461
  out.push({ path: at('trigger', 'schedule', 'dayOfMonth'), message: 'a monthly rule names its day (1 to 28)' });
309
462
  }
310
- // Every `{{record.<column>}}` a text carries is a column of the rule's record.
463
+ /** A staff column of the rule's record, or of the row one of its links names: who a step tells. */
464
+ const staffAt = (path, where) => {
465
+ if (record === undefined) {
466
+ out.push({ path: where, message: 'this rule runs for no record, so there is no person on it to tell' });
467
+ return;
468
+ }
469
+ const [first, second] = path.split('.');
470
+ let of = record;
471
+ let ref = first;
472
+ if (second !== undefined) {
473
+ const link = column(record, first, where);
474
+ if (link === undefined)
475
+ return;
476
+ if (link.references === undefined) {
477
+ out.push({ path: where, message: `"${record.ref}.${first}" is not a link to another table` });
478
+ return;
479
+ }
480
+ of = tables.get(link.references);
481
+ // A link into an add-on's table, or one this manifest cannot see: checked where the rule is installed.
482
+ if (of === undefined)
483
+ return;
484
+ ref = second;
485
+ }
486
+ const found = column(of, ref, where);
487
+ if (found !== undefined && found.rules?.['staff'] !== true) {
488
+ out.push({ path: where, message: `"${of.ref}.${ref}" is not a staff column ("rules": { "staff": true }): only one names a person to tell` });
489
+ }
490
+ };
491
+ /** A table that lists people against the rule's record (a `belongs` way `through` it): who a step to "the people listed" tells. */
492
+ const listedAt = (ref, where) => {
493
+ if (record === undefined) {
494
+ out.push({ path: where, message: 'this rule runs for no record, so nobody is listed on it to tell' });
495
+ return;
496
+ }
497
+ const lists = (record.belongs ?? []).some((way) => typeof way === 'object' && way !== null && 'through' in way && way.through?.table === ref);
498
+ if (!lists)
499
+ out.push({ path: where, message: `"${ref}" lists nobody against "${record.ref}": name a table one of its "belongs" ways reads "through"` });
500
+ };
501
+ /** `{{was.<column>}}`: a column of the record, in a rule run by its change. */
502
+ const changing = trigger.kind === 'record' && trigger.event === 'updated';
503
+ const wasIssue = (ref, path) => {
504
+ if (!changing)
505
+ out.push({ path, message: `{{was.${ref}}}: only a rule run when its record is updated reads the record as it was` });
506
+ else if (record !== undefined && !record.columns.some((candidate) => candidate.ref === ref))
507
+ out.push({ path, message: `{{was.${ref}}}: "${record.ref}" has no column "${ref}"` });
508
+ };
509
+ // The rows earlier steps made, by the name each step gave its row.
510
+ const made = new Map();
511
+ // Every `{{record.<column>}}` a text carries is a column of the rule's record; every `{{steps.<name>.<column>}}` a column of a row an earlier step made.
311
512
  const tokens = (text, path) => {
312
513
  const texts = typeof text === 'string' ? [text] : typeof text === 'object' && text !== null ? Object.values(text).filter((value) => typeof value === 'string') : [];
313
514
  for (const each of texts) {
515
+ for (const match of each.matchAll(/\{\{\s*steps\.([A-Za-z0-9_]+)\.([A-Za-z0-9_]+)\s*(\|[^{}]*)?\}\}/g)) {
516
+ const name = match[1];
517
+ if (!made.has(name))
518
+ out.push({ path, message: `{{steps.${name}.${match[2]}}}: no earlier step makes a row called "${name}" (give a "record.create" step "name": "${name}")` });
519
+ else if (made.get(name) !== undefined && !made.get(name).columns.some((candidate) => candidate.ref === match[2]))
520
+ out.push({ path, message: `{{steps.${name}.${match[2]}}}: "${made.get(name).ref}" has no column "${match[2]}"` });
521
+ // A row that was not made has no value to stand in for: the run stops instead.
522
+ if (match[3] !== undefined)
523
+ out.push({ path, message: `{{steps.${name}.${match[2]}}} takes no backup: a row that was not made stops the run` });
524
+ }
525
+ // The record as it was before the change: only a rule about a change has one.
526
+ for (const match of each.matchAll(/\{\{\s*was\.([A-Za-z0-9_]+)\s*(?:\|[^{}]*)?\}\}/g))
527
+ wasIssue(match[1], path);
314
528
  for (const match of each.matchAll(/\{\{\s*record\.([A-Za-z0-9_]+)\s*(?:\|[^{}]*)?\}\}/g)) {
315
529
  if (record === undefined)
316
530
  out.push({ path, message: `{{record.${match[1]}}}: this rule runs for no record (a clock with no rows to visit)` });
@@ -319,21 +533,85 @@ export function automationIssues(m) {
319
533
  }
320
534
  }
321
535
  };
536
+ /** A setting a value reads: a column of one of the manifest's tables that is neither secret nor personal, of a table that is not private. */
537
+ const settingAt = (setting, path) => {
538
+ const held = tableAt(setting.table, [...path, 'table']);
539
+ if (held?.private === true)
540
+ out.push({ path: [...path, 'table'], message: `"${setting.table}" is private: no rule reads a value of it` });
541
+ const found = column(held, setting.column, [...path, 'column']);
542
+ if (found?.rules?.['secret'] === true || found?.rules?.['personal'] === true) {
543
+ out.push({ path: [...path, 'column'], message: `"${setting.table}.${setting.column}" is ${found.rules['secret'] === true ? 'a secret' : 'a personal'} column: a rule never writes it into another row` });
544
+ }
545
+ return found;
546
+ };
547
+ /** One value a step writes into `ref` of `into`: its placeholders, the setting it reads, the date it counts from. */
548
+ const valueIssues = (into, ref, value, path) => {
549
+ if (typeof value === 'string') {
550
+ tokens(value, path);
551
+ return;
552
+ }
553
+ const target = into?.columns.find((candidate) => candidate.ref === ref);
554
+ if ('setting' in value) {
555
+ const found = settingAt(value.setting, [...path, 'setting']);
556
+ if (found === undefined || target === undefined)
557
+ return;
558
+ // A person is filled from a column that holds one; any other value from a column of its own kind.
559
+ if (target.rules?.['staff'] === true && found.rules?.['staff'] !== true)
560
+ out.push({ path: [...path, 'setting', 'column'], message: `"${into?.ref ?? ''}.${ref}" holds a staff member, and "${value.setting.table}.${value.setting.column}" is not a staff column` });
561
+ else if (found.type !== target.type || found.references !== target.references)
562
+ out.push({ path: [...path, 'setting', 'column'], message: `"${value.setting.table}.${value.setting.column}" is ${found.type} and "${into?.ref ?? ''}.${ref}" is ${target.type}: a setting fills a column of its own kind` });
563
+ return;
564
+ }
565
+ if ('daysAfter' in value) {
566
+ const after = value.daysAfter;
567
+ const at = [...path, 'daysAfter'];
568
+ if (target !== undefined && !DATES.includes(target.type))
569
+ out.push({ path, message: `"${into?.ref ?? ''}.${ref}" is ${target.type}: a day so many days on is written into a date column` });
570
+ if (after.at !== undefined && target?.type === 'date')
571
+ out.push({ path: [...at, 'at'], message: `"${into?.ref ?? ''}.${ref}" holds a day, which has no time of day` });
572
+ if (after.column !== undefined) {
573
+ if (record === undefined)
574
+ out.push({ path: [...at, 'column'], message: 'this rule runs for no record, so there is no date of it to count from' });
575
+ const from = column(record, after.column, [...at, 'column']);
576
+ if (from !== undefined && !DATES.includes(from.type))
577
+ out.push({ path: [...at, 'column'], message: `"${record?.ref ?? ''}.${after.column}" is ${from.type}: days are counted from a date` });
578
+ if (from?.rules?.['secret'] === true || from?.rules?.['personal'] === true)
579
+ out.push({ path: [...at, 'column'], message: `"${record?.ref ?? ''}.${after.column}" is a protected column: a day counted from it would tell it` });
580
+ }
581
+ if (after.setting !== undefined) {
582
+ const found = settingAt(after.setting, [...at, 'setting']);
583
+ if (found !== undefined && !['int', 'bigint'].includes(found.type))
584
+ out.push({ path: [...at, 'setting', 'column'], message: `"${after.setting.table}.${after.setting.column}" is not a whole-number column: it holds a number of days` });
585
+ }
586
+ }
587
+ };
322
588
  const ids = [];
323
589
  walk(rule.graph.nodes, at('graph', 'nodes'), (node, path) => {
324
590
  tokens(node.title, [...path, 'title']);
325
591
  tokens(node.sub, [...path, 'sub']);
326
592
  if (node.kind === 'condition' || node.kind === 'branch')
327
- conditionIssues(record, node.condition, [...path, 'condition'], false);
593
+ conditionIssues(record, node.condition, [...path, 'condition'], false, true);
328
594
  if (node.kind !== 'action')
329
595
  return;
330
596
  const action = node.action;
331
597
  const here = (...rest) => [...path, 'action', ...rest];
332
598
  if (action.kind === 'notification') {
333
- action.to.roles.forEach((role, i) => {
334
- if (!m.roles.includes(role))
335
- out.push({ path: here('to', 'roles', i), message: `"${role}" is not one of this manifest's roles` });
336
- });
599
+ if ('roles' in action.to) {
600
+ action.to.roles.forEach((role, i) => {
601
+ if (!m.roles.includes(role))
602
+ out.push({ path: here('to', 'roles', i), message: `"${role}" is not one of this manifest's roles` });
603
+ });
604
+ }
605
+ else if ('listed' in action.to) {
606
+ listedAt(action.to.listed, here('to', 'listed'));
607
+ }
608
+ else {
609
+ staffAt(action.to.column, here('to', 'column'));
610
+ (action.to.else?.roles ?? []).forEach((role, i) => {
611
+ if (!m.roles.includes(role))
612
+ out.push({ path: here('to', 'else', 'roles', i), message: `"${role}" is not one of this manifest's roles` });
613
+ });
614
+ }
337
615
  tokens(action.title, here('title'));
338
616
  tokens(action.body, here('body'));
339
617
  return;
@@ -342,7 +620,121 @@ export function automationIssues(m) {
342
620
  const into = tableAt(action.table, here('table'));
343
621
  for (const [ref, value] of Object.entries(action.values)) {
344
622
  column(into, ref, here('values', ref));
345
- tokens(value, here('values', ref));
623
+ valueIssues(into, ref, value, here('values', ref));
624
+ }
625
+ // Named after its own values are read: a step does not read the row it is about to make.
626
+ if (action.name !== undefined) {
627
+ if (made.has(action.name))
628
+ out.push({ path: here('name'), message: `"${action.name}" already names a row an earlier step makes` });
629
+ if (['record', 'steps', 'now'].includes(action.name))
630
+ out.push({ path: here('name'), message: `"${action.name}" is a word a rule already reads: choose another name` });
631
+ made.set(action.name, into);
632
+ }
633
+ return;
634
+ }
635
+ if (action.kind === 'digest') {
636
+ action.to.roles.forEach((role, i) => {
637
+ if (!m.roles.includes(role))
638
+ out.push({ path: here('to', 'roles', i), message: `"${role}" is not one of this manifest's roles` });
639
+ });
640
+ const keys = new Set();
641
+ action.sections.forEach((section, s) => {
642
+ if (keys.has(section.key))
643
+ out.push({ path: here('sections', s, 'key'), message: `"${section.key}" is the key of an earlier section` });
644
+ keys.add(section.key);
645
+ const of = tableAt(section.table, here('sections', s, 'table'));
646
+ const owner = column(of, section.owner, here('sections', s, 'owner'));
647
+ if (owner !== undefined && owner.rules?.['staff'] !== true) {
648
+ out.push({ path: here('sections', s, 'owner'), message: `"${section.table}.${section.owner}" is not a staff column ("rules": { "staff": true }): a section lists the rows whose person is the one written to` });
649
+ }
650
+ (section.where ?? []).forEach((condition, c) => conditionIssues(of, condition, here('sections', s, 'where', c), true));
651
+ if (section.orderBy !== undefined)
652
+ column(of, section.orderBy.column, here('sections', s, 'orderBy', 'column'));
653
+ // A sum adds up numbers; the same column twice is one figure with two names.
654
+ (section.sum ?? []).forEach((ref, n) => {
655
+ const added = column(of, ref, here('sections', s, 'sum', n));
656
+ if (added !== undefined && !NUMBERS.includes(added.type))
657
+ out.push({ path: here('sections', s, 'sum', n), message: `"${section.table}.${ref}" is not a number column: a sum adds numbers up` });
658
+ if ((section.sum ?? []).indexOf(ref) !== n)
659
+ out.push({ path: here('sections', s, 'sum', n), message: `"${ref}" is summed twice` });
660
+ });
661
+ // A line a person has no row to sort by.
662
+ if (section.by !== undefined && section.orderBy !== undefined)
663
+ out.push({ path: here('sections', s, 'orderBy'), message: 'a section with one line a person ("by": "owner") is listed by name: it has no orderBy' });
664
+ if (section.target !== undefined) {
665
+ const target = section.target;
666
+ if (!(section.sum ?? []).includes(target.of))
667
+ out.push({ path: here('sections', s, 'target', 'of'), message: `"${target.of}" is not one of this section's sums ("sum")` });
668
+ const goals = tableAt(target.table, here('sections', s, 'target', 'table'));
669
+ const whose = column(goals, target.owner, here('sections', s, 'target', 'owner'));
670
+ if (whose !== undefined && whose.rules?.['staff'] !== true)
671
+ out.push({ path: here('sections', s, 'target', 'owner'), message: `"${target.table}.${target.owner}" is not a staff column ("rules": { "staff": true }): a target is somebody's` });
672
+ const goal = column(goals, target.column, here('sections', s, 'target', 'column'));
673
+ if (goal !== undefined && !NUMBERS.includes(goal.type))
674
+ out.push({ path: here('sections', s, 'target', 'column'), message: `"${target.table}.${target.column}" is not a number column` });
675
+ if (target.year !== undefined) {
676
+ const year = column(goals, target.year, here('sections', s, 'target', 'year'));
677
+ if (year !== undefined && !['int', 'bigint'].includes(year.type))
678
+ out.push({ path: here('sections', s, 'target', 'year'), message: `"${target.table}.${target.year}" is not a whole-number column: it holds a calendar year` });
679
+ }
680
+ }
681
+ });
682
+ const template = templates.get(action.templateKey);
683
+ if (template === undefined) {
684
+ out.push({ path: here('templateKey'), message: `"${action.templateKey}" is not one of this manifest's emailTemplates: a shipped rule sends only its own` });
685
+ return;
686
+ }
687
+ if (template.attach !== undefined)
688
+ out.push({ path: here('templateKey'), message: `"${action.templateKey}" carries a document, which a summary does not` });
689
+ for (const [locale, content] of Object.entries(template.locales)) {
690
+ const drawn = new Set();
691
+ content.blocks.forEach((block) => {
692
+ if (block.block !== 'email.rows')
693
+ return;
694
+ const section = block.data?.['section'];
695
+ if (typeof section !== 'string' || !keys.has(section)) {
696
+ out.push({ path: here('templateKey'), message: `"${action.templateKey}" (${locale}) has a rows block that names no section of this step ("section": one of ${[...keys].join(', ')})` });
697
+ return;
698
+ }
699
+ drawn.add(section);
700
+ const drawing = action.sections.find((one) => one.key === section);
701
+ // A line a person reads the person's name, how many rows, each sum, and the target with its cover.
702
+ if (drawing.by !== undefined) {
703
+ const lines = new Set(['row.owner', 'row.count', ...(drawing.sum ?? []).map((ref) => `row.sum.${ref}`), ...(drawing.target === undefined ? [] : ['row.target', 'row.cover'])]);
704
+ for (const name of placeholderNames([block.data?.['row']])) {
705
+ if (name.startsWith('row.') && !lines.has(name))
706
+ out.push({ path: here('templateKey'), message: `"${action.templateKey}" (${locale}) reads {{${name}}} in the section "${section}", which lists one line a person: a line reads ${[...lines].map((one) => `{{${one}}}`).join(', ')}` });
707
+ }
708
+ return;
709
+ }
710
+ // Each line reads the columns of its section's table: {{row.<column>}}, or a form of one ({{row.<column>.money}}).
711
+ const of = tables.get(drawing.table);
712
+ for (const name of placeholderNames([block.data?.['row']])) {
713
+ if (!name.startsWith('row.'))
714
+ continue;
715
+ const ref = name.slice('row.'.length).split('.')[0];
716
+ if (of !== undefined && !of.columns.some((candidate) => candidate.ref === ref)) {
717
+ out.push({ path: here('templateKey'), message: `"${action.templateKey}" (${locale}) reads {{${name}}}, and "${of.ref}" has no column "${ref}"` });
718
+ }
719
+ }
720
+ });
721
+ for (const key of keys) {
722
+ if (!drawn.has(key))
723
+ out.push({ path: here('sections'), message: `"${action.templateKey}" (${locale}) draws no rows block for the section "${key}"` });
724
+ }
725
+ // A section's own figures: how many rows, each sum. They are there for everybody who reads the section.
726
+ const figures = new Set(action.sections.flatMap((one) => [`count.${one.key}`, ...(one.sum ?? []).map((ref) => `sum.${one.key}.${ref}`)]));
727
+ // A target and its cover are there only for a person who has one: a template that reads either says what it writes without.
728
+ const sometimes = new Set(action.sections.flatMap((one) => (one.target === undefined ? [] : [`target.${one.key}`, `cover.${one.key}`])));
729
+ const required = templateRequiredPlaceholdersOf(content);
730
+ const unfilled = required.filter((name) => !DIGEST_EMAIL_VARS.includes(name) && !figures.has(name) && !sometimes.has(name));
731
+ if (unfilled.length > 0) {
732
+ out.push({ path: here(), message: `"${action.templateKey}" (${locale}) reads ${unfilled.map((name) => `{{${name}}}`).join(', ')}, which a summary cannot fill: it fills {{name}}, {{appName}}, {{ruleName}}, {{now}}, {{readAt}}, {{count.<section>}}, and {{sum.<section>.<column>}}, {{target.<section>}} and {{cover.<section>}} of a section that asks for them` });
733
+ }
734
+ const bare = required.filter((name) => sometimes.has(name));
735
+ if (bare.length > 0) {
736
+ out.push({ path: here(), message: `"${action.templateKey}" (${locale}) reads ${bare.map((name) => `{{${name}}}`).join(', ')} with nothing to write for a person who has no target: give it a backup ({{${bare[0]}|none set}}), or tie its block to it with "showWhen"` });
737
+ }
346
738
  }
347
739
  return;
348
740
  }
@@ -352,7 +744,7 @@ export function automationIssues(m) {
352
744
  const decided = record === undefined ? new Set() : (m.decided?.(record.ref) ?? new Set());
353
745
  for (const [ref, value] of Object.entries(action.values)) {
354
746
  const found = column(record, ref, here('values', ref));
355
- tokens(value, here('values', ref));
747
+ valueIssues(record, ref, value, here('values', ref));
356
748
  const rule2 = DECIDING.find((name) => found?.rules?.[name] !== undefined);
357
749
  if (found !== undefined && (rule2 !== undefined || decided.has(ref))) {
358
750
  out.push({ path: here('values', ref), message: `"${record?.ref ?? ''}.${ref}" is decided by Adminium${rule2 === undefined ? '' : ` (its ${rule2} rule)`}: a rule cannot write it` });
@@ -384,7 +776,14 @@ export function automationIssues(m) {
384
776
  // An email: one of the manifest's own templates, to a column of the record, and only one a rule can fill.
385
777
  if (record === undefined)
386
778
  out.push({ path: here(), message: 'this rule runs for no record, so there is no address to send to' });
387
- column(record, action.to.column, here('to', 'column'));
779
+ if (action.to.kind === 'staff')
780
+ staffAt(action.to.column, here('to', 'column'));
781
+ else if (action.to.kind === 'listed')
782
+ listedAt(action.to.table, here('to', 'table'));
783
+ else
784
+ column(record, action.to.column, here('to', 'column'));
785
+ if (action.replyTo !== undefined)
786
+ staffAt(action.replyTo.staff, here('replyTo', 'staff'));
388
787
  const template = templates.get(action.templateKey);
389
788
  if (template === undefined) {
390
789
  out.push({ path: here('templateKey'), message: `"${action.templateKey}" is not one of this manifest's emailTemplates: a shipped rule sends only its own` });
@@ -394,7 +793,17 @@ export function automationIssues(m) {
394
793
  for (const [name, text] of Object.entries(vars))
395
794
  tokens(text, here('vars', name));
396
795
  const columns = new Set((record?.columns ?? []).map((candidate) => candidate.ref));
397
- const filled = (name) => RULE_EMAIL_VARS.includes(name) || Object.hasOwn(vars, name) || columns.has(name) || (name.startsWith('record.') && columns.has(name.slice('record.'.length)));
796
+ // The name of the person a staff column of the record names; the record's address, in a mail to a person.
797
+ const people = new Set((record?.columns ?? []).filter((candidate) => candidate.rules?.['staff'] === true).flatMap((candidate) => [`${candidate.ref}.name`, `record.${candidate.ref}.name`]));
798
+ // A mail to a person, or to each of the people listed on the record: each is somebody the address is worked out for.
799
+ const toPerson = action.to.kind === 'staff' || action.to.kind === 'listed';
800
+ const filled = (name) => RULE_EMAIL_VARS.includes(name) ||
801
+ Object.hasOwn(vars, name) ||
802
+ columns.has(name) ||
803
+ (name.startsWith('record.') && columns.has(name.slice('record.'.length))) ||
804
+ people.has(name) ||
805
+ (toPerson && name === 'recordUrl') ||
806
+ (changing && name.startsWith('was.') && columns.has(name.slice('was.'.length)));
398
807
  const everywhere = new Set();
399
808
  if (template.attach !== undefined)
400
809
  out.push({ path: here('templateKey'), message: `"${action.templateKey}" carries a document, which only the outbox draws: send it from the outbox, or give the rule a template with no "attach"` });
@@ -407,11 +816,15 @@ export function automationIssues(m) {
407
816
  for (const name of names)
408
817
  everywhere.add(name);
409
818
  const unfilled = templateRequiredPlaceholdersOf(content).filter((name) => !filled(name));
410
- if (unfilled.length > 0) {
411
- const first = unfilled[0];
819
+ if (unfilled.includes('recordUrl') && !toPerson) {
820
+ out.push({ path: here(), message: `"${action.templateKey}" (${locale}) reads {{recordUrl}}, which only a mail to a person carries ("to": { "kind": "staff", … }): an address of the record is no link for anybody else` });
821
+ }
822
+ const others = unfilled.filter((name) => name !== 'recordUrl' || toPerson);
823
+ if (others.length > 0) {
824
+ const first = others[0];
412
825
  out.push({
413
826
  path: here(),
414
- message: `"${action.templateKey}" (${locale}) reads ${unfilled.map((name) => `{{${name}}}`).join(', ')}, which a rule cannot fill. Add "vars": { "${first}": "{{record.<column>}}" } to this step, or send this email from the outbox.`,
827
+ message: `"${action.templateKey}" (${locale}) reads ${others.map((name) => `{{${name}}}`).join(', ')}, which a rule cannot fill. Add "vars": { "${first}": "{{record.<column>}}" } to this step, or send this email from the outbox.`,
415
828
  });
416
829
  }
417
830
  }