@adminiumjs/manifest 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capacity.d.ts +786 -0
- package/dist/capacity.d.ts.map +1 -0
- package/dist/capacity.js +717 -0
- package/dist/capacity.js.map +1 -0
- package/dist/documents.d.ts +91 -5
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +129 -32
- package/dist/documents.js.map +1 -1
- package/dist/formula.d.ts +114 -2
- package/dist/formula.d.ts.map +1 -1
- package/dist/formula.js +291 -5
- package/dist/formula.js.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/index.js.map +1 -1
- package/dist/marketplace-wire.d.ts +230 -0
- package/dist/marketplace-wire.d.ts.map +1 -0
- package/dist/marketplace-wire.js +138 -0
- package/dist/marketplace-wire.js.map +1 -0
- package/dist/outbox.d.ts +242 -27
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +553 -27
- package/dist/outbox.js.map +1 -1
- package/dist/plan-context.d.ts +28 -3
- package/dist/plan-context.d.ts.map +1 -1
- package/dist/plan-context.js +291 -14
- package/dist/plan-context.js.map +1 -1
- package/dist/plan-model.d.ts +58 -3
- package/dist/plan-model.d.ts.map +1 -1
- package/dist/public-access.d.ts +637 -0
- package/dist/public-access.d.ts.map +1 -1
- package/dist/public-access.js +1593 -12
- package/dist/public-access.js.map +1 -1
- package/dist/refs.d.ts +86 -0
- package/dist/refs.d.ts.map +1 -1
- package/dist/refs.js +186 -0
- package/dist/refs.js.map +1 -1
- package/dist/roles.d.ts +10 -2
- package/dist/roles.d.ts.map +1 -1
- package/dist/roles.js +27 -5
- package/dist/roles.js.map +1 -1
- package/dist/sample.d.ts +218 -2
- package/dist/sample.d.ts.map +1 -1
- package/dist/sample.js +145 -7
- package/dist/sample.js.map +1 -1
- package/dist/schema.d.ts +2860 -360
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1002 -54
- package/dist/schema.js.map +1 -1
- package/dist/shapes.d.ts +5 -1
- package/dist/shapes.d.ts.map +1 -1
- package/dist/shapes.js +13 -3
- package/dist/shapes.js.map +1 -1
- package/dist/states.d.ts +342 -2
- package/dist/states.d.ts.map +1 -1
- package/dist/states.js +566 -7
- package/dist/states.js.map +1 -1
- package/dist/table-shapes.d.ts +47 -0
- package/dist/table-shapes.d.ts.map +1 -0
- package/dist/table-shapes.js +159 -0
- package/dist/table-shapes.js.map +1 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +80 -2
- package/dist/validate.js.map +1 -1
- package/package.json +2 -2
package/dist/states.js
CHANGED
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
* stamps) keep being written by Adminium.
|
|
32
32
|
* - `children`: child tables tied to this row's state. `lock` refuses their
|
|
33
33
|
* writes while the row is locked; `parentIn` allows them only while the row
|
|
34
|
-
* is in those states (payments on a sent invoice)
|
|
34
|
+
* is in those states (payments on a sent invoice) — or, apart, `createIn`
|
|
35
|
+
* for a new child and `changeIn` for a change or a delete of one (a payment
|
|
36
|
+
* taken only on a live stay, and voided on a cancelled one too); `clearOnCreate` empties
|
|
35
37
|
* columns of this row when one of them is created (a recorded payment
|
|
36
38
|
* clears the client's "I've sent it"). `release` opens a locked child a
|
|
37
39
|
* little while its parent is in some of the lock's states: a change that
|
|
@@ -47,12 +49,28 @@
|
|
|
47
49
|
* - `noDelete`: rows in these states, or any row with a number
|
|
48
50
|
* (`"numbered"`), are never deleted — voided instead.
|
|
49
51
|
* - `onlyLater`: date columns that may move later and never earlier.
|
|
52
|
+
* - A move may also wait for the row one of its links points at
|
|
53
|
+
* (`requires.linked`), for a window on the clock (`requires.time`, read from
|
|
54
|
+
* moments — see `refs.ts`) and for a value of the settings row
|
|
55
|
+
* (`requires.setting`).
|
|
56
|
+
* - `strict`: a write naming the state the row already holds is refused
|
|
57
|
+
* (a ticket let in once), repeating up to four columns of the row.
|
|
58
|
+
* - `late`: a move made inside some time before a moment sets a flag, or is
|
|
59
|
+
* refused.
|
|
60
|
+
* - `timed`: listed moves Adminium makes by itself once a moment of the row
|
|
61
|
+
* has passed (a held order expiring).
|
|
62
|
+
* - `effects`: a move that moves the row one of its links points at too, by a
|
|
63
|
+
* move that table lists (a guest checked out turns the room to cleaning).
|
|
64
|
+
* - `create`: what a new row must meet to be created — the conditions a move
|
|
65
|
+
* waits for, judged when the row is written (a check-in only for a paid
|
|
66
|
+
* ticket, on its day).
|
|
67
|
+
* - A timed move may also write fixed values to other columns (`set`).
|
|
50
68
|
*
|
|
51
69
|
* Adding sample data and importing past records are history: they write any
|
|
52
70
|
* state their rows were in.
|
|
53
71
|
*/
|
|
54
72
|
import { z } from 'zod';
|
|
55
|
-
import { refSchema, scalarSchema, valueFits } from './refs.js';
|
|
73
|
+
import { momentIssues, momentOffsetSchema, momentSchema, refSchema, scalarSchema, valueFits, } from './refs.js';
|
|
56
74
|
const stateName = z.string().min(1).max(64);
|
|
57
75
|
const roleKey = z.string().regex(/^[a-z][a-z0-9-]*$/, 'a role key');
|
|
58
76
|
/** A condition on the row itself, checked in the same statement that changes it. */
|
|
@@ -71,21 +89,57 @@ export const stateConditionSchema = z
|
|
|
71
89
|
.refine((c) => [c.eq, c.in, c.isNull, c.gt, c.gte, c.lt, c.lte].filter((part) => part !== undefined).length === 1, {
|
|
72
90
|
message: 'a condition says one of eq, in, isNull, gt, gte, lt or lte',
|
|
73
91
|
});
|
|
92
|
+
/** Conditions on the row one of this row's links points at (the order a ticket belongs to is paid). */
|
|
93
|
+
export const linkedConditionSchema = z
|
|
94
|
+
.object({
|
|
95
|
+
/** This row's foreign key. */
|
|
96
|
+
via: refSchema,
|
|
97
|
+
where: z.array(stateConditionSchema).min(1).max(8),
|
|
98
|
+
})
|
|
99
|
+
.strict();
|
|
100
|
+
/** A move allowed only after one moment, before another, or between the two. */
|
|
101
|
+
export const timeConditionSchema = z
|
|
102
|
+
.object({ after: momentSchema.optional(), before: momentSchema.optional() })
|
|
103
|
+
.strict()
|
|
104
|
+
.refine((t) => t.after !== undefined || t.before !== undefined, { message: 'a time condition says after, before or both' });
|
|
105
|
+
/** A value of the app's one-row settings table a move waits for (door sales switched on). */
|
|
106
|
+
export const settingConditionSchema = z.object({ table: refSchema, column: refSchema, eq: scalarSchema }).strict();
|
|
74
107
|
export const stateMoveSchema = z.union([
|
|
75
108
|
stateName,
|
|
76
109
|
z
|
|
77
110
|
.object({
|
|
78
111
|
to: stateName,
|
|
79
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* What must be true first: at least n child rows of a table, conditions
|
|
114
|
+
* on the row, on the rows its links point at, on the settings row, and
|
|
115
|
+
* a window in time.
|
|
116
|
+
*/
|
|
80
117
|
requires: z
|
|
81
118
|
.object({
|
|
82
119
|
children: z.record(refSchema, z.number().int().min(1).max(1000)).optional(),
|
|
83
120
|
where: z.array(stateConditionSchema).min(1).max(8).optional(),
|
|
121
|
+
linked: z.array(linkedConditionSchema).min(1).max(4).optional(),
|
|
122
|
+
time: timeConditionSchema.optional(),
|
|
123
|
+
setting: z.array(settingConditionSchema).min(1).max(4).optional(),
|
|
84
124
|
})
|
|
85
125
|
.strict()
|
|
86
126
|
.optional(),
|
|
87
127
|
/** Only people holding one of these app roles may make this move. */
|
|
88
128
|
roles: z.array(roleKey).min(1).max(8).optional(),
|
|
129
|
+
/**
|
|
130
|
+
* The move takes back the listed move the other way (ready → preparing
|
|
131
|
+
* after preparing → ready): made only by a write that names the state
|
|
132
|
+
* it saw the row in, judged on the row as it stands, and emptying the
|
|
133
|
+
* stamps the move it takes back wrote (`stamp.clearOnBack`) while the
|
|
134
|
+
* stamps of the state it returns to keep what they had.
|
|
135
|
+
*/
|
|
136
|
+
undo: z.literal(true).optional(),
|
|
137
|
+
/**
|
|
138
|
+
* An undo's further columns it empties, beside its stamps (how a hand-over
|
|
139
|
+
* taken back was paid): emptied by the move, and open to the table's lock
|
|
140
|
+
* for that move only. Only on a move marked `undo`.
|
|
141
|
+
*/
|
|
142
|
+
clears: z.array(refSchema).min(1).max(8).optional(),
|
|
89
143
|
})
|
|
90
144
|
.strict(),
|
|
91
145
|
]);
|
|
@@ -94,7 +148,12 @@ export const stateChildSchema = z
|
|
|
94
148
|
/** The child's foreign key to this row. */
|
|
95
149
|
via: refSchema,
|
|
96
150
|
lock: z.literal(true).optional(),
|
|
151
|
+
/** Written only while this row is in one of these states: `createIn` and `changeIn` together. */
|
|
97
152
|
parentIn: z.array(stateName).min(1).max(16).optional(),
|
|
153
|
+
/** Added only while this row is in one of these states (a payment taken on a stay still booked or in house). */
|
|
154
|
+
createIn: z.array(stateName).min(1).max(16).optional(),
|
|
155
|
+
/** Changed or deleted only while this row is in one of these states (a payment voided on a cancelled stay too). */
|
|
156
|
+
changeIn: z.array(stateName).min(1).max(16).optional(),
|
|
98
157
|
clearOnCreate: z.array(refSchema).min(1).max(8).optional(),
|
|
99
158
|
/** While this row is in one of `when`, a locked child may still EMPTY these columns of its own, and change nothing else. */
|
|
100
159
|
release: z.object({ when: z.array(stateName).min(1).max(16), columns: z.array(refSchema).min(1).max(8) }).strict().optional(),
|
|
@@ -102,13 +161,142 @@ export const stateChildSchema = z
|
|
|
102
161
|
lockLinked: z.record(refSchema, z.array(refSchema).min(1).max(16)).optional(),
|
|
103
162
|
})
|
|
104
163
|
.strict()
|
|
105
|
-
.refine((c) => c.lock === undefined || c.parentIn === undefined, {
|
|
164
|
+
.refine((c) => c.lock === undefined || (c.parentIn === undefined && c.createIn === undefined && c.changeIn === undefined), {
|
|
106
165
|
message: 'a child is locked with its parent, or writable only in some of its states — not both',
|
|
166
|
+
})
|
|
167
|
+
.refine((c) => c.parentIn === undefined || (c.createIn === undefined && c.changeIn === undefined), {
|
|
168
|
+
message: 'parentIn says createIn and changeIn at once: say parentIn, or createIn and changeIn, not both',
|
|
107
169
|
})
|
|
108
170
|
.refine((c) => c.release === undefined || c.lock === true, { message: 'a child is released only from its parent\'s lock (lock: true)' })
|
|
109
171
|
.refine((c) => c.lockLinked === undefined || (Object.keys(c.lockLinked).length >= 1 && Object.keys(c.lockLinked).length <= 8), {
|
|
110
172
|
message: 'lockLinked names 1 to 8 link columns',
|
|
111
173
|
});
|
|
174
|
+
// ── once means once, late moves, timed moves, moves of a linked row ─────────
|
|
175
|
+
/**
|
|
176
|
+
* `strict`: a write that names the state the row already holds is refused
|
|
177
|
+
* instead of passing silently (a ticket scanned twice). `show` names up to
|
|
178
|
+
* four columns of the row the refusal repeats (the door it came in by);
|
|
179
|
+
* never a personal or secret column.
|
|
180
|
+
*/
|
|
181
|
+
export const strictStatesSchema = z.union([
|
|
182
|
+
z.literal(true),
|
|
183
|
+
z.object({ show: z.array(refSchema).min(1).max(4) }).strict(),
|
|
184
|
+
]);
|
|
185
|
+
/**
|
|
186
|
+
* A move to `to` (from one of `from`, or from anywhere) made inside `within`
|
|
187
|
+
* before `moment`: `flag` lets it through and sets the bool column `flag`;
|
|
188
|
+
* `refuse` turns it away — for public writers, or with `refuse: 'everyone'`
|
|
189
|
+
* for every writer.
|
|
190
|
+
*/
|
|
191
|
+
export const lateMoveSchema = z
|
|
192
|
+
.object({
|
|
193
|
+
to: stateName,
|
|
194
|
+
from: z.array(stateName).min(1).max(16).optional(),
|
|
195
|
+
moment: momentSchema,
|
|
196
|
+
within: momentOffsetSchema,
|
|
197
|
+
mode: z.enum(['flag', 'refuse']),
|
|
198
|
+
flag: refSchema.optional(),
|
|
199
|
+
refuse: z.enum(['public', 'everyone']).optional(),
|
|
200
|
+
})
|
|
201
|
+
.strict()
|
|
202
|
+
.refine((l) => (l.mode === 'flag') === (l.flag !== undefined), {
|
|
203
|
+
message: 'a flag names its column, and only mode "flag" has one',
|
|
204
|
+
path: ['flag'],
|
|
205
|
+
})
|
|
206
|
+
.refine((l) => l.refuse === undefined || l.mode === 'refuse', {
|
|
207
|
+
message: 'who is refused is said only by mode "refuse"',
|
|
208
|
+
path: ['refuse'],
|
|
209
|
+
});
|
|
210
|
+
/**
|
|
211
|
+
* A listed move Adminium makes by itself once `at` has passed, for a row still
|
|
212
|
+
* in `from`. `set` writes fixed values to other columns of the row in the same
|
|
213
|
+
* move (why an order still open at closing was cancelled).
|
|
214
|
+
*/
|
|
215
|
+
export const timedMoveSchema = z
|
|
216
|
+
.object({
|
|
217
|
+
from: stateName,
|
|
218
|
+
to: stateName,
|
|
219
|
+
at: momentSchema,
|
|
220
|
+
set: z
|
|
221
|
+
.record(refSchema, z.union([scalarSchema, z.null()]))
|
|
222
|
+
.refine((set) => Object.keys(set).length >= 1 && Object.keys(set).length <= 8, { message: 'a timed move sets 1 to 8 columns' })
|
|
223
|
+
.optional(),
|
|
224
|
+
})
|
|
225
|
+
.strict();
|
|
226
|
+
/**
|
|
227
|
+
* What a row must meet to be created at all: a value of its own
|
|
228
|
+
* (`where`), of the row one of its links points at (`linked`), of the
|
|
229
|
+
* settings row (`setting`), and a window on the clock (`time`) — the same
|
|
230
|
+
* shapes a move waits for. A check-in recorded only for a paid ticket, on
|
|
231
|
+
* its day, from half an hour before the doors.
|
|
232
|
+
*/
|
|
233
|
+
export const createRequiresSchema = z
|
|
234
|
+
.object({
|
|
235
|
+
requires: z
|
|
236
|
+
.object({
|
|
237
|
+
where: z.array(stateConditionSchema).min(1).max(8).optional(),
|
|
238
|
+
linked: z.array(linkedConditionSchema).min(1).max(4).optional(),
|
|
239
|
+
time: timeConditionSchema.optional(),
|
|
240
|
+
setting: z.array(settingConditionSchema).min(1).max(4).optional(),
|
|
241
|
+
})
|
|
242
|
+
.strict()
|
|
243
|
+
.refine((r) => [r.where, r.linked, r.time, r.setting].some((part) => part !== undefined), {
|
|
244
|
+
message: 'a create requires where, linked, time or setting',
|
|
245
|
+
}),
|
|
246
|
+
})
|
|
247
|
+
.strict();
|
|
248
|
+
/** The one column an effect sets on the linked row (that table's state), and the state it moves to. */
|
|
249
|
+
const effectSetSchema = z
|
|
250
|
+
.record(refSchema, stateName)
|
|
251
|
+
.refine((set) => Object.keys(set).length === 1, { message: 'an effect sets one column: the linked table\'s state' });
|
|
252
|
+
/**
|
|
253
|
+
* When this row moves to `on.to`, the row its link `via` points at moves too,
|
|
254
|
+
* in the same write: `set` names that table's state column and the state it
|
|
255
|
+
* moves to, one of the moves that table lists (a guest checked out turns the
|
|
256
|
+
* room to cleaning). One link, never a chain.
|
|
257
|
+
*/
|
|
258
|
+
export const moveEffectSchema = z
|
|
259
|
+
.object({
|
|
260
|
+
on: z.object({ to: stateName }).strict(),
|
|
261
|
+
via: refSchema,
|
|
262
|
+
set: effectSetSchema,
|
|
263
|
+
})
|
|
264
|
+
.strict();
|
|
265
|
+
/**
|
|
266
|
+
* When the link `on.change` really changes — while this row is in one of
|
|
267
|
+
* `on.in` before and after the write — the row it pointed at moves by `old`
|
|
268
|
+
* and the row it now points at by `new`, in the same write (a guest moved to
|
|
269
|
+
* another room: the old room to cleaning, the new one to occupied, which only
|
|
270
|
+
* a ready room may be). Each is that table's declared move; a new row already
|
|
271
|
+
* in `new`'s state refuses the write. Either side may be left out, and a link
|
|
272
|
+
* emptied or first set moves only the side there is.
|
|
273
|
+
*/
|
|
274
|
+
export const changeEffectSchema = z
|
|
275
|
+
.object({
|
|
276
|
+
on: z.object({ change: refSchema, in: z.array(stateName).min(1).max(16).optional() }).strict(),
|
|
277
|
+
old: z.object({ set: effectSetSchema }).strict().optional(),
|
|
278
|
+
new: z.object({ set: effectSetSchema }).strict().optional(),
|
|
279
|
+
})
|
|
280
|
+
.strict()
|
|
281
|
+
.refine((e) => e.old !== undefined || e.new !== undefined, { message: 'an effect of a changed link moves the old row, the new one, or both' });
|
|
282
|
+
/** Whether an effect is set off by a changed link rather than by a move. */
|
|
283
|
+
export function isChangeEffect(effect) {
|
|
284
|
+
return 'change' in effect.on;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* An effect, parsed by the schema of the form written (a move's, or a changed
|
|
288
|
+
* link's), so a mistake is named where it is: a union of the two would only
|
|
289
|
+
* say the input is invalid.
|
|
290
|
+
*/
|
|
291
|
+
export const stateEffectSchema = z.custom().superRefine((value, ctx) => {
|
|
292
|
+
const on = typeof value === 'object' && value !== null ? value.on : undefined;
|
|
293
|
+
const change = typeof on === 'object' && on !== null && 'change' in on;
|
|
294
|
+
const parsed = (change ? changeEffectSchema : moveEffectSchema).safeParse(value);
|
|
295
|
+
if (parsed.success)
|
|
296
|
+
return;
|
|
297
|
+
for (const issue of parsed.error.issues)
|
|
298
|
+
ctx.addIssue({ ...issue, path: [...issue.path], continue: false });
|
|
299
|
+
});
|
|
112
300
|
export const statesSchema = z
|
|
113
301
|
.object({
|
|
114
302
|
column: refSchema,
|
|
@@ -129,12 +317,76 @@ export const statesSchema = z
|
|
|
129
317
|
.optional(),
|
|
130
318
|
noDelete: z.object({ when: z.union([z.literal('numbered'), z.array(stateName).min(1).max(16)]) }).strict().optional(),
|
|
131
319
|
onlyLater: z.array(refSchema).min(1).max(8).optional(),
|
|
320
|
+
/** A move to the state a row already holds is refused, naming when it got there (see {@link strictStatesSchema}). */
|
|
321
|
+
strict: strictStatesSchema.optional(),
|
|
322
|
+
/** A move made close to a moment sets a flag, or is refused (see {@link lateMoveSchema}). */
|
|
323
|
+
late: z.array(lateMoveSchema).min(1).max(4).optional(),
|
|
324
|
+
/** Moves Adminium makes on its own when a moment passes (see {@link timedMoveSchema}). */
|
|
325
|
+
timed: z.array(timedMoveSchema).min(1).max(8).optional(),
|
|
326
|
+
/** A move that moves the row one of its links points at too (see {@link stateEffectSchema}). */
|
|
327
|
+
effects: z.array(stateEffectSchema).min(1).max(4).optional(),
|
|
328
|
+
/** What a new row must meet to be created (see {@link createRequiresSchema}). */
|
|
329
|
+
create: createRequiresSchema.optional(),
|
|
132
330
|
})
|
|
133
331
|
.strict();
|
|
134
332
|
/** A move written as a bare state is a move with nothing asked first. */
|
|
333
|
+
/**
|
|
334
|
+
* Whether a state is reached only by moves marked `undo`: made only by a
|
|
335
|
+
* person naming the state they saw — never by a door that names none (a
|
|
336
|
+
* timed move, an effect, an email's `onSent`, a guest's writable value).
|
|
337
|
+
*/
|
|
338
|
+
export function reachedOnlyByUndo(states, state) {
|
|
339
|
+
if (states === undefined)
|
|
340
|
+
return false;
|
|
341
|
+
const moves = Object.values(states.moves).flatMap((list) => list.filter((move) => moveTarget(move) === String(state)));
|
|
342
|
+
return moves.length > 0 && moves.every((move) => typeof move === 'object' && move.undo === true);
|
|
343
|
+
}
|
|
135
344
|
export function moveTarget(move) {
|
|
136
345
|
return typeof move === 'string' ? move : move.to;
|
|
137
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* Everything wrong with what a table's undo moves empty (`clears`), and with a
|
|
349
|
+
* move that another move to the same state comes before — the judge takes the
|
|
350
|
+
* first, so a later one marked `undo` or naming `clears` would never be made.
|
|
351
|
+
* Paths are relative to the states. The one judge for an app's manifest and a
|
|
352
|
+
* states rule saved in Studio (the server's `statesRuleIssue`).
|
|
353
|
+
*/
|
|
354
|
+
export function undoMoveIssues(states, table, lookup) {
|
|
355
|
+
const out = [];
|
|
356
|
+
for (const [from, moves] of Object.entries(states.moves)) {
|
|
357
|
+
const marked = (candidate) => typeof candidate === 'object' && (candidate.undo === true || candidate.clears !== undefined);
|
|
358
|
+
moves.forEach((move, m) => {
|
|
359
|
+
const to = moveTarget(move);
|
|
360
|
+
// The judge takes the first move to a state: a second one, when either is an undo or empties columns, is never made.
|
|
361
|
+
const first = moves.slice(0, m).find((other) => moveTarget(other) === to);
|
|
362
|
+
if (first !== undefined && (marked(first) || marked(move))) {
|
|
363
|
+
out.push({ path: ['moves', from, m], message: `another move from "${from}" to "${to}" comes first, so this one is never made` });
|
|
364
|
+
}
|
|
365
|
+
if (typeof move === 'string')
|
|
366
|
+
return;
|
|
367
|
+
if (move.clears !== undefined && move.undo !== true) {
|
|
368
|
+
out.push({ path: ['moves', from, m, 'clears'], message: 'only a move marked undo empties columns as it goes' });
|
|
369
|
+
}
|
|
370
|
+
(move.clears ?? []).forEach((ref, c) => {
|
|
371
|
+
const path = ['moves', from, m, 'clears', c];
|
|
372
|
+
const found = lookup.column(ref);
|
|
373
|
+
if (found === undefined)
|
|
374
|
+
out.push({ path, message: `"${table}" has no column "${ref}"` });
|
|
375
|
+
else if (ref === states.column)
|
|
376
|
+
out.push({ path, message: 'the state moves by the move itself, not by what it empties' });
|
|
377
|
+
else if (found.key)
|
|
378
|
+
out.push({ path, message: `"${table}.${ref}" is the key, which never changes` });
|
|
379
|
+
else if (!found.nullable)
|
|
380
|
+
out.push({ path, message: `"${table}.${ref}" is not nullable, so an undo cannot empty it` });
|
|
381
|
+
else if (lookup.decided(ref))
|
|
382
|
+
out.push({ path, message: `"${table}.${ref}" is written by another rule already` });
|
|
383
|
+
if (move.clears.indexOf(ref) !== c)
|
|
384
|
+
out.push({ path, message: `"${ref}" is named twice` });
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
138
390
|
/** Everything wrong with one table's `states` against the manifest's tables. */
|
|
139
391
|
export function statesIssues(table, states, ctx, at) {
|
|
140
392
|
const out = [];
|
|
@@ -178,8 +430,22 @@ export function statesIssues(table, states, ctx, at) {
|
|
|
178
430
|
out.push({ path: at('moves', from, m, 'roles'), message: `"${role}" is not one of the app's roles` });
|
|
179
431
|
}
|
|
180
432
|
}
|
|
433
|
+
// An undo takes back a listed move: the one from where it goes to where it starts.
|
|
434
|
+
if (move.undo === true && !(states.moves[to] ?? []).some((back) => moveTarget(back) === from)) {
|
|
435
|
+
out.push({ path: at('moves', from, m, 'undo'), message: `no listed move goes from "${to}" to "${from}", so this move takes nothing back` });
|
|
436
|
+
}
|
|
181
437
|
});
|
|
182
438
|
}
|
|
439
|
+
// What an undo empties besides its stamps, and a move another to the same state hides: one judge with a states rule saved in Studio.
|
|
440
|
+
for (const issue of undoMoveIssues(states, table, {
|
|
441
|
+
column: (ref) => {
|
|
442
|
+
const found = index.column(table, ref);
|
|
443
|
+
return found === undefined ? undefined : { key: found.role === 'pk', nullable: found.nullable === true };
|
|
444
|
+
},
|
|
445
|
+
decided: (ref) => ctx.decided?.(ref) === true,
|
|
446
|
+
})) {
|
|
447
|
+
out.push({ path: at(...issue.path), message: issue.message });
|
|
448
|
+
}
|
|
183
449
|
if (states.lock !== undefined) {
|
|
184
450
|
states.lock.when.forEach((value, i) => known(value, at('lock', 'when', i)));
|
|
185
451
|
for (const ref of states.lock.except ?? []) {
|
|
@@ -202,7 +468,8 @@ export function statesIssues(table, states, ctx, at) {
|
|
|
202
468
|
out.push({ path: here('via'), message: `"${child}.${rule.via}" does not point at "${table}"` });
|
|
203
469
|
if (rule.lock === true && states.lock === undefined)
|
|
204
470
|
out.push({ path: here('lock'), message: 'a child is locked with its parent, and the parent has no lock' });
|
|
205
|
-
|
|
471
|
+
for (const key of ['parentIn', 'createIn', 'changeIn'])
|
|
472
|
+
(rule[key] ?? []).forEach((value, i) => known(value, here(key, i)));
|
|
206
473
|
for (const ref of rule.clearOnCreate ?? []) {
|
|
207
474
|
const found = index.column(table, ref);
|
|
208
475
|
if (found === undefined)
|
|
@@ -254,7 +521,8 @@ export function statesIssues(table, states, ctx, at) {
|
|
|
254
521
|
out.push({ path: here('lockLinked', link), message: `"${target}.${ref}" is the key, which never changes` });
|
|
255
522
|
}
|
|
256
523
|
}
|
|
257
|
-
|
|
524
|
+
const tied = rule.parentIn !== undefined || rule.createIn !== undefined || rule.changeIn !== undefined;
|
|
525
|
+
if (rule.lock === undefined && !tied && rule.clearOnCreate === undefined && rule.lockLinked === undefined) {
|
|
258
526
|
out.push({ path: here(), message: 'a child says lock, parentIn, clearOnCreate or lockLinked' });
|
|
259
527
|
}
|
|
260
528
|
}
|
|
@@ -286,9 +554,300 @@ export function statesIssues(table, states, ctx, at) {
|
|
|
286
554
|
else if (found.type !== 'date' && found.type !== 'timestamptz')
|
|
287
555
|
out.push({ path: at('onlyLater'), message: `"${table}.${ref}" is not a date` });
|
|
288
556
|
}
|
|
557
|
+
out.push(...conditionedMoveIssues(table, states, ctx, at, values));
|
|
289
558
|
return out;
|
|
290
559
|
}
|
|
291
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Everything wrong with the rules a move waits for or sets off: conditions on
|
|
562
|
+
* a linked row, the settings row and the clock; the once-means-once refusal;
|
|
563
|
+
* late moves; the moves Adminium makes when a moment passes; and the moves of
|
|
564
|
+
* a linked row a move sets off.
|
|
565
|
+
*/
|
|
566
|
+
function conditionedMoveIssues(table, states, ctx, at, values) {
|
|
567
|
+
const out = [];
|
|
568
|
+
const { index } = ctx;
|
|
569
|
+
const known = (value, path) => {
|
|
570
|
+
if (values.length > 0 && !values.includes(value))
|
|
571
|
+
out.push({ path, message: `"${value}" is not a value of "${table}.${states.column}"` });
|
|
572
|
+
};
|
|
573
|
+
/** The listed move from `from` to `to`, when there is one. */
|
|
574
|
+
const listed = (from, to) => (states.moves[from] ?? []).find((move) => moveTarget(move) === to);
|
|
575
|
+
const reached = (to) => Object.values(states.moves).some((moves) => moves.some((move) => moveTarget(move) === to));
|
|
576
|
+
/** The table a link of this table points at, or an issue. */
|
|
577
|
+
const linkTarget = (via, path) => {
|
|
578
|
+
const found = index.column(table, via);
|
|
579
|
+
if (found === undefined) {
|
|
580
|
+
out.push({ path, message: `"${table}" has no column "${via}"` });
|
|
581
|
+
return undefined;
|
|
582
|
+
}
|
|
583
|
+
if (found.role === 'pk') {
|
|
584
|
+
out.push({ path, message: `"${table}.${via}" is the key, and points at no other row` });
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
if (found.type !== 'fk' || found.references === undefined || index.table(found.references) === undefined) {
|
|
588
|
+
out.push({ path, message: `"${table}.${via}" does not point at a table of this manifest` });
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
return found.references;
|
|
592
|
+
};
|
|
593
|
+
for (const [from, moves] of Object.entries(states.moves)) {
|
|
594
|
+
moves.forEach((move, m) => {
|
|
595
|
+
if (typeof move === 'string' || move.requires === undefined)
|
|
596
|
+
return;
|
|
597
|
+
const here = (...rest) => at('moves', from, m, 'requires', ...rest);
|
|
598
|
+
(move.requires.linked ?? []).forEach((linked, l) => {
|
|
599
|
+
const target = linkTarget(linked.via, here('linked', l, 'via'));
|
|
600
|
+
if (target === undefined)
|
|
601
|
+
return;
|
|
602
|
+
linked.where.forEach((condition, w) => out.push(...conditionIssues(target, condition, index, here('linked', l, 'where', w))));
|
|
603
|
+
});
|
|
604
|
+
const time = move.requires.time;
|
|
605
|
+
if (time?.after !== undefined)
|
|
606
|
+
out.push(...momentIssues(table, time.after, index, here('time', 'after')));
|
|
607
|
+
if (time?.before !== undefined)
|
|
608
|
+
out.push(...momentIssues(table, time.before, index, here('time', 'before')));
|
|
609
|
+
(move.requires.setting ?? []).forEach((setting, k) => {
|
|
610
|
+
const path = here('setting', k);
|
|
611
|
+
if (index.table(setting.table) === undefined) {
|
|
612
|
+
out.push({ path, message: `"${setting.table}" is not a table of this manifest` });
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const found = index.column(setting.table, setting.column);
|
|
616
|
+
if (found === undefined)
|
|
617
|
+
out.push({ path, message: `"${setting.table}" has no column "${setting.column}"` });
|
|
618
|
+
else if (!valueFits(found, setting.eq))
|
|
619
|
+
out.push({ path, message: `${JSON.stringify(setting.eq)} is not a value of "${setting.table}.${setting.column}"` });
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (typeof states.strict === 'object') {
|
|
624
|
+
states.strict.show.forEach((ref, i) => {
|
|
625
|
+
const path = at('strict', 'show', i);
|
|
626
|
+
if (index.column(table, ref) === undefined) {
|
|
627
|
+
out.push({ path, message: `"${table}" has no column "${ref}"` });
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const kept = ctx.keptFromReaders?.(ref) ?? null;
|
|
631
|
+
if (kept !== null)
|
|
632
|
+
out.push({ path, message: `"${table}.${ref}" is ${kept}, so a refusal never repeats it` });
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
(states.late ?? []).forEach((late, i) => {
|
|
636
|
+
const here = (...rest) => at('late', i, ...rest);
|
|
637
|
+
known(late.to, here('to'));
|
|
638
|
+
(late.from ?? []).forEach((from, f) => {
|
|
639
|
+
known(from, here('from', f));
|
|
640
|
+
if (listed(from, late.to) === undefined)
|
|
641
|
+
out.push({ path: here('from', f), message: `no listed move goes from "${from}" to "${late.to}"` });
|
|
642
|
+
});
|
|
643
|
+
if (late.from === undefined && !reached(late.to))
|
|
644
|
+
out.push({ path: here('to'), message: `no listed move goes to "${late.to}"` });
|
|
645
|
+
out.push(...momentIssues(table, late.moment, index, here('moment')));
|
|
646
|
+
if (late.flag !== undefined) {
|
|
647
|
+
const flag = index.column(table, late.flag);
|
|
648
|
+
if (flag === undefined)
|
|
649
|
+
out.push({ path: here('flag'), message: `"${table}" has no column "${late.flag}"` });
|
|
650
|
+
else if (flag.type !== 'bool')
|
|
651
|
+
out.push({ path: here('flag'), message: `"${table}.${late.flag}" is not a bool` });
|
|
652
|
+
else if (late.flag === states.column)
|
|
653
|
+
out.push({ path: here('flag'), message: 'the flag is a column of its own, not the state' });
|
|
654
|
+
if (ctx.decided?.(late.flag) === true)
|
|
655
|
+
out.push({ path: here('flag'), message: `"${table}.${late.flag}" is written by another rule already` });
|
|
656
|
+
if ((states.late ?? []).some((other, j) => j < i && other.flag === late.flag)) {
|
|
657
|
+
out.push({ path: here('flag'), message: `"${late.flag}" is set by another late move of this table already` });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const cancel = ctx.bookingCancel;
|
|
661
|
+
if (cancel !== undefined && cancel.column === states.column && cancel.to === late.to) {
|
|
662
|
+
out.push({ path: here('to'), message: `the booking rule already says when a move to "${late.to}" is late; one late rule per move` });
|
|
663
|
+
}
|
|
664
|
+
if ((states.late ?? []).some((other, j) => j < i && other.to === late.to)) {
|
|
665
|
+
out.push({ path: here('to'), message: `another late rule already judges the move to "${late.to}"; one late rule per move` });
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
(states.timed ?? []).forEach((timed, i) => {
|
|
669
|
+
const here = (...rest) => at('timed', i, ...rest);
|
|
670
|
+
known(timed.from, here('from'));
|
|
671
|
+
known(timed.to, here('to'));
|
|
672
|
+
const move = listed(timed.from, timed.to);
|
|
673
|
+
if (move === undefined)
|
|
674
|
+
out.push({ path: here('to'), message: `no listed move goes from "${timed.from}" to "${timed.to}"` });
|
|
675
|
+
else if (typeof move === 'object' && move.undo === true)
|
|
676
|
+
out.push({ path: here('to'), message: `the move from "${timed.from}" to "${timed.to}" is an undo, which only a person makes` });
|
|
677
|
+
if ((states.timed ?? []).some((other, j) => j < i && other.from === timed.from)) {
|
|
678
|
+
out.push({ path: here('from'), message: `another timed move already leaves "${timed.from}"` });
|
|
679
|
+
}
|
|
680
|
+
const vias = [timed.at, ...(timed.at.or ?? [])].some((moment) => moment.via !== undefined);
|
|
681
|
+
if (vias)
|
|
682
|
+
out.push({ path: here('at'), message: "a timed move reads its own row's time, never a linked row's" });
|
|
683
|
+
else
|
|
684
|
+
out.push(...momentIssues(table, timed.at, index, here('at')));
|
|
685
|
+
// A move waiting for a time later than the one it is made at could never pass.
|
|
686
|
+
const after = typeof move === 'object' ? move.requires?.time?.after : undefined;
|
|
687
|
+
if (after !== undefined && neverPasses(after, timed.at)) {
|
|
688
|
+
out.push({ path: here('at'), message: `the move from "${timed.from}" to "${timed.to}" waits until later than this, so it could never be made in time` });
|
|
689
|
+
}
|
|
690
|
+
for (const [ref, value] of Object.entries(timed.set ?? {})) {
|
|
691
|
+
const path = here('set', ref);
|
|
692
|
+
const found = index.column(table, ref);
|
|
693
|
+
if (found === undefined)
|
|
694
|
+
out.push({ path, message: `"${table}" has no column "${ref}"` });
|
|
695
|
+
else if (ref === states.column)
|
|
696
|
+
out.push({ path, message: 'the state moves by the timed move itself, not by what it sets' });
|
|
697
|
+
else if (found.role === 'pk')
|
|
698
|
+
out.push({ path, message: `"${table}.${ref}" is the key, which never changes` });
|
|
699
|
+
else if (value === null ? found.nullable !== true : !valueFits(found, value))
|
|
700
|
+
out.push({ path, message: `${JSON.stringify(value)} is not a value of "${table}.${ref}"` });
|
|
701
|
+
else if (ctx.decided?.(ref) === true)
|
|
702
|
+
out.push({ path, message: `"${table}.${ref}" is written by another rule already` });
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
const create = states.create?.requires;
|
|
706
|
+
if (create !== undefined) {
|
|
707
|
+
const here = (...rest) => at('create', 'requires', ...rest);
|
|
708
|
+
(create.where ?? []).forEach((condition, w) => out.push(...conditionIssues(table, condition, index, here('where', w))));
|
|
709
|
+
(create.linked ?? []).forEach((linked, l) => {
|
|
710
|
+
const target = linkTarget(linked.via, here('linked', l, 'via'));
|
|
711
|
+
if (target === undefined)
|
|
712
|
+
return;
|
|
713
|
+
linked.where.forEach((condition, w) => out.push(...conditionIssues(target, condition, index, here('linked', l, 'where', w))));
|
|
714
|
+
});
|
|
715
|
+
if (create.time?.after !== undefined)
|
|
716
|
+
out.push(...momentIssues(table, create.time.after, index, here('time', 'after')));
|
|
717
|
+
if (create.time?.before !== undefined)
|
|
718
|
+
out.push(...momentIssues(table, create.time.before, index, here('time', 'before')));
|
|
719
|
+
(create.setting ?? []).forEach((setting, k) => {
|
|
720
|
+
const path = here('setting', k);
|
|
721
|
+
if (index.table(setting.table) === undefined) {
|
|
722
|
+
out.push({ path, message: `"${setting.table}" is not a table of this manifest` });
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
const found = index.column(setting.table, setting.column);
|
|
726
|
+
if (found === undefined)
|
|
727
|
+
out.push({ path, message: `"${setting.table}" has no column "${setting.column}"` });
|
|
728
|
+
else if (!valueFits(found, setting.eq))
|
|
729
|
+
out.push({ path, message: `${JSON.stringify(setting.eq)} is not a value of "${setting.table}.${setting.column}"` });
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
/** Everything wrong with an effect's move of a row of `target` (its states `theirs`) by `set`. */
|
|
733
|
+
const effectMoveIssues = (target, theirs, set, here) => {
|
|
734
|
+
for (const [column, state] of Object.entries(set)) {
|
|
735
|
+
if (column !== theirs.column) {
|
|
736
|
+
out.push({ path: here('set', column), message: `"${target}" moves by "${theirs.column}", not "${column}"` });
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
const moves = Object.values(theirs.moves).flatMap((list) => list.filter((move) => moveTarget(move) === state));
|
|
740
|
+
if (moves.length === 0)
|
|
741
|
+
out.push({ path: here('set', column), message: `no move of "${target}" goes to "${state}"` });
|
|
742
|
+
// An undo is made only by a person naming the state they saw; an effect names none.
|
|
743
|
+
if (moves.length > 0 && moves.every((move) => typeof move === 'object' && move.undo === true)) {
|
|
744
|
+
out.push({ path: here('set', column), message: `every move of "${target}" to "${state}" is an undo, which only a person makes` });
|
|
745
|
+
}
|
|
746
|
+
// The linked row is moved inside this write, after its own rows are held: its move may wait only for its own row.
|
|
747
|
+
const reaches = moves.some((move) => {
|
|
748
|
+
if (typeof move === 'string')
|
|
749
|
+
return false;
|
|
750
|
+
const time = move.requires?.time;
|
|
751
|
+
const vias = [time?.after, time?.before].flatMap((m) => (m === undefined ? [] : [m, ...(m.or ?? [])])).some((m) => m.via !== undefined);
|
|
752
|
+
return (move.requires?.linked?.length ?? 0) > 0 || vias;
|
|
753
|
+
});
|
|
754
|
+
if (reaches)
|
|
755
|
+
out.push({ path: here('set', column), message: `the move of "${target}" to "${state}" waits for another row, so an effect cannot make it` });
|
|
756
|
+
// Judged late by a time read through another row: that row would be read after this write's own rows.
|
|
757
|
+
const lateThrough = (theirs.late ?? []).some((late) => late.to === state && [late.moment, ...(late.moment.or ?? [])].some((m) => m.via !== undefined));
|
|
758
|
+
if (lateThrough)
|
|
759
|
+
out.push({ path: here('set', column), message: `a move of "${target}" to "${state}" is judged late by another row's time, so an effect cannot make it` });
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
/** Everything wrong with the table an effect moves a row of, whatever sets it off. */
|
|
763
|
+
const effectTargetIssues = (target, theirs, path) => {
|
|
764
|
+
if (target === ctx.outboxTable)
|
|
765
|
+
out.push({ path, message: `"${target}" is the app's outbox, whose messages move only by the outbox's own moves` });
|
|
766
|
+
// Moved inside this write, after its own rows are held: a lock taken per day, or a parent held first, would come too late.
|
|
767
|
+
if (ctx.bookedOf?.(target) === true)
|
|
768
|
+
out.push({ path, message: `"${target}" books people by the day, and its lock cannot be taken inside another row's write` });
|
|
769
|
+
const parent = ctx.lineOf?.(target);
|
|
770
|
+
if (parent !== undefined)
|
|
771
|
+
out.push({ path, message: `"${target}" rows are lines of "${parent}", which would be held after this write's own rows` });
|
|
772
|
+
if ((theirs.effects?.length ?? 0) > 0)
|
|
773
|
+
out.push({ path, message: `"${target}" sets off effects of its own; an effect moves one row, never a chain` });
|
|
774
|
+
};
|
|
775
|
+
(states.effects ?? []).forEach((effect, i) => {
|
|
776
|
+
const here = (...rest) => at('effects', i, ...rest);
|
|
777
|
+
if (isChangeEffect(effect)) {
|
|
778
|
+
(effect.on.in ?? []).forEach((state, k) => known(state, here('on', 'in', k)));
|
|
779
|
+
if ((states.effects ?? []).some((other, j) => j < i && isChangeEffect(other) && other.on.change === effect.on.change)) {
|
|
780
|
+
out.push({ path: here(), message: `another effect already moves the rows "${effect.on.change}" points at when it changes` });
|
|
781
|
+
}
|
|
782
|
+
if (effect.on.change === states.column)
|
|
783
|
+
out.push({ path: here('on', 'change'), message: 'the state moves by its moves: an effect of a changed link watches a link' });
|
|
784
|
+
const target = linkTarget(effect.on.change, here('on', 'change'));
|
|
785
|
+
if (target === undefined)
|
|
786
|
+
return;
|
|
787
|
+
const theirs = ctx.statesOf?.(target);
|
|
788
|
+
if (theirs === undefined) {
|
|
789
|
+
out.push({ path: here('on', 'change'), message: `"${target}" declares no states, so its rows have no moves to make` });
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (effect.old !== undefined)
|
|
793
|
+
effectMoveIssues(target, theirs, effect.old.set, (...rest) => here('old', ...rest));
|
|
794
|
+
if (effect.new !== undefined)
|
|
795
|
+
effectMoveIssues(target, theirs, effect.new.set, (...rest) => here('new', ...rest));
|
|
796
|
+
effectTargetIssues(target, theirs, here('on', 'change'));
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
known(effect.on.to, here('on', 'to'));
|
|
800
|
+
if (!reached(effect.on.to))
|
|
801
|
+
out.push({ path: here('on', 'to'), message: `no listed move goes to "${effect.on.to}", so nothing sets this off` });
|
|
802
|
+
if ((states.effects ?? []).some((other, j) => j < i && !isChangeEffect(other) && other.on.to === effect.on.to && other.via === effect.via)) {
|
|
803
|
+
out.push({ path: here(), message: `another effect already moves the row "${effect.via}" points at on a move to "${effect.on.to}"` });
|
|
804
|
+
}
|
|
805
|
+
const target = linkTarget(effect.via, here('via'));
|
|
806
|
+
if (target === undefined)
|
|
807
|
+
return;
|
|
808
|
+
const theirs = ctx.statesOf?.(target);
|
|
809
|
+
if (theirs === undefined) {
|
|
810
|
+
out.push({ path: here('via'), message: `"${target}" declares no states, so its rows have no moves to make` });
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
effectMoveIssues(target, theirs, effect.set, here);
|
|
814
|
+
effectTargetIssues(target, theirs, here('via'));
|
|
815
|
+
});
|
|
816
|
+
return out;
|
|
817
|
+
}
|
|
818
|
+
/** Minutes of a literal shift, or undefined when it is read from a setting. */
|
|
819
|
+
function literalMinutes(offset) {
|
|
820
|
+
if (offset === undefined)
|
|
821
|
+
return 0;
|
|
822
|
+
const { minutes, hours, days } = offset;
|
|
823
|
+
if (typeof minutes === 'number')
|
|
824
|
+
return minutes;
|
|
825
|
+
if (typeof hours === 'number')
|
|
826
|
+
return hours * 60;
|
|
827
|
+
if (typeof days === 'number')
|
|
828
|
+
return days * 1440;
|
|
829
|
+
return undefined;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Whether `after` is certainly later than `at`: both the same own column,
|
|
833
|
+
* no time of day, no fallback, and literal shifts. Anything else depends on
|
|
834
|
+
* the row and the settings, and is the app's to get right.
|
|
835
|
+
*/
|
|
836
|
+
function neverPasses(after, at) {
|
|
837
|
+
const plain = (m) => m.via === undefined && m.time === undefined && m.or === undefined;
|
|
838
|
+
if (!plain(after) || !plain(at) || after.column !== at.column)
|
|
839
|
+
return false;
|
|
840
|
+
const shift = (m) => {
|
|
841
|
+
if (m.plus !== undefined)
|
|
842
|
+
return literalMinutes(m.plus);
|
|
843
|
+
const back = literalMinutes(m.minus);
|
|
844
|
+
return back === undefined ? undefined : -back;
|
|
845
|
+
};
|
|
846
|
+
const a = shift(after);
|
|
847
|
+
const b = shift(at);
|
|
848
|
+
return a !== undefined && b !== undefined && a > b;
|
|
849
|
+
}
|
|
850
|
+
export function conditionIssues(table, condition, index, path) {
|
|
292
851
|
const found = index.column(table, condition.column);
|
|
293
852
|
if (found === undefined)
|
|
294
853
|
return [{ path, message: `"${table}" has no column "${condition.column}"` }];
|