workerflow 0.1.0 → 0.3.0

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.
@@ -5,7 +5,7 @@ export default `
5
5
  id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
6
6
 
7
7
  status TEXT NOT NULL CHECK (
8
- status IN ('pending', 'running', 'paused', 'completed', 'failed', 'cancelled')
8
+ status IN ('pending', 'initialized', 'running', 'paused', 'completed', 'failed', 'cancelled')
9
9
  ),
10
10
 
11
11
  created_at INTEGER NOT NULL
@@ -15,365 +15,169 @@ export default `
15
15
  updated_at INTEGER NOT NULL
16
16
  DEFAULT (CAST(unixepoch('subsecond') * 1000 AS INTEGER)),
17
17
 
18
- definition_version TEXT
19
- CHECK (definition_version IS NULL OR length(definition_version) > 0),
20
18
  definition_input TEXT
21
19
  CHECK (definition_input IS NULL OR json_valid(definition_input)),
22
20
 
23
21
  CHECK (updated_at >= created_at),
24
22
 
25
- -- definition_version and definition_input must be set together or not set at all
26
- CHECK (definition_version IS NOT NULL OR definition_input IS NULL),
27
-
28
- -- definition must be pinned before running/paused/completing/failing; cancelled is always allowed
29
- CHECK (status IN ('pending', 'cancelled') OR definition_version IS NOT NULL)
23
+ -- definition_input must be NULL until create() initializes the workflow.
24
+ CHECK (status <> 'pending' OR definition_input IS NULL)
30
25
  ) STRICT;
31
26
 
32
27
  CREATE TABLE steps (
33
28
  id TEXT NOT NULL PRIMARY KEY CHECK (length(id) > 0),
34
29
  type TEXT NOT NULL CHECK (type IN ('run', 'sleep', 'wait')),
35
- state TEXT NOT NULL CHECK (state IN (
36
- 'pending',
37
- 'running',
38
- 'succeeded',
39
- 'failed',
30
+ created_at INTEGER NOT NULL
31
+ DEFAULT (CAST(unixepoch('subsecond') * 1000 AS INTEGER))
32
+ CHECK (created_at >= 0),
33
+
34
+ -- sleep / wait only: run rows use run_step_attempts for lifecycle
35
+ state TEXT CHECK (state IN (
40
36
  'waiting',
41
37
  'elapsed',
42
38
  'satisfied',
43
39
  'timed_out'
44
40
  )),
45
- created_at INTEGER NOT NULL
46
- DEFAULT (CAST(unixepoch('subsecond') * 1000 AS INTEGER))
47
- CHECK (created_at >= 0),
48
41
 
49
- -- run-step fields
50
- attempt_count INTEGER,
51
42
  max_attempts INTEGER,
52
- next_attempt_at INTEGER,
53
- result TEXT,
54
- error_message TEXT,
55
- error_name TEXT,
56
43
 
57
- -- sleep-step fields
58
- wake_at INTEGER,
44
+ target_wake_at INTEGER,
59
45
 
60
- -- wait-step fields
61
46
  event_name TEXT,
62
47
  timeout_at INTEGER,
63
- payload TEXT,
64
48
 
65
- -- terminal timestamp
66
49
  resolved_at INTEGER,
67
50
 
68
- -- innermost enclosing run step when this row was created (nested run / sleep / wait under a run callback)
69
51
  parent_step_id TEXT REFERENCES steps(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
70
52
 
71
- CHECK (attempt_count IS NULL OR attempt_count >= 0),
72
53
  CHECK (max_attempts IS NULL OR max_attempts >= 1),
73
- CHECK (next_attempt_at IS NULL OR next_attempt_at >= 0),
74
- CHECK (wake_at IS NULL OR wake_at >= 0),
54
+ CHECK (target_wake_at IS NULL OR target_wake_at >= 0),
75
55
  CHECK (timeout_at IS NULL OR timeout_at >= 0),
76
56
  CHECK (resolved_at IS NULL OR resolved_at >= created_at),
77
- CHECK (error_name IS NULL OR length(error_name) > 0),
78
57
  CHECK (event_name IS NULL OR length(event_name) > 0),
79
58
 
80
- -- run steps may never exceed max_attempts
81
- CHECK (
82
- attempt_count IS NULL OR
83
- max_attempts IS NULL OR
84
- attempt_count <= max_attempts
85
- ),
86
-
87
59
  CHECK (
88
60
  (
89
61
  type = 'run' AND
90
- state = 'pending' AND
91
- attempt_count IS NOT NULL AND attempt_count >= 0 AND
92
- (max_attempts IS NULL OR max_attempts >= 1) AND
93
- (max_attempts IS NULL OR attempt_count < max_attempts) AND
94
- next_attempt_at IS NOT NULL AND
95
- result IS NULL AND
96
- error_message IS NULL AND
97
- error_name IS NULL AND
98
- wake_at IS NULL AND
99
- event_name IS NULL AND
100
- timeout_at IS NULL AND
101
- payload IS NULL AND
102
- resolved_at IS NULL
103
- )
104
- OR
105
- (
106
- type = 'run' AND
107
- state = 'running' AND
108
- attempt_count IS NOT NULL AND attempt_count >= 1 AND
62
+ state IS NULL AND
109
63
  (max_attempts IS NULL OR max_attempts >= 1) AND
110
- (max_attempts IS NULL OR attempt_count <= max_attempts) AND
111
- next_attempt_at IS NULL AND
112
- result IS NULL AND
113
- error_message IS NULL AND
114
- error_name IS NULL AND
115
- wake_at IS NULL AND
64
+ target_wake_at IS NULL AND
116
65
  event_name IS NULL AND
117
66
  timeout_at IS NULL AND
118
- payload IS NULL AND
119
67
  resolved_at IS NULL
120
68
  )
121
69
  OR
122
- (
123
- type = 'run' AND
124
- state = 'succeeded' AND
125
- attempt_count IS NOT NULL AND attempt_count >= 1 AND
126
- (max_attempts IS NULL OR max_attempts >= 1) AND
127
- (max_attempts IS NULL OR attempt_count <= max_attempts) AND
128
- next_attempt_at IS NULL AND
129
- result IS NOT NULL AND
130
- error_message IS NULL AND
131
- error_name IS NULL AND
132
- wake_at IS NULL AND
133
- event_name IS NULL AND
134
- timeout_at IS NULL AND
135
- payload IS NULL AND
136
- resolved_at IS NOT NULL
137
- )
138
- OR
139
- (
140
- type = 'run' AND
141
- state = 'failed' AND
142
- attempt_count IS NOT NULL AND attempt_count >= 1 AND
143
- (max_attempts IS NULL OR max_attempts >= 1) AND
144
- (max_attempts IS NULL OR attempt_count <= max_attempts) AND
145
- next_attempt_at IS NULL AND
146
- result IS NULL AND
147
- error_message IS NOT NULL AND
148
- wake_at IS NULL AND
149
- event_name IS NULL AND
150
- timeout_at IS NULL AND
151
- payload IS NULL AND
152
- resolved_at IS NOT NULL
153
- )
154
- OR
155
70
  (
156
71
  type = 'sleep' AND
157
72
  state = 'waiting' AND
158
- attempt_count IS NULL AND
159
73
  max_attempts IS NULL AND
160
- next_attempt_at IS NULL AND
161
- result IS NULL AND
162
- error_message IS NULL AND
163
- error_name IS NULL AND
164
- wake_at IS NOT NULL AND
74
+ target_wake_at IS NOT NULL AND
165
75
  event_name IS NULL AND
166
76
  timeout_at IS NULL AND
167
- payload IS NULL AND
168
77
  resolved_at IS NULL
169
78
  )
170
79
  OR
171
80
  (
172
81
  type = 'sleep' AND
173
82
  state = 'elapsed' AND
174
- attempt_count IS NULL AND
175
83
  max_attempts IS NULL AND
176
- next_attempt_at IS NULL AND
177
- result IS NULL AND
178
- error_message IS NULL AND
179
- error_name IS NULL AND
180
- wake_at IS NULL AND
84
+ target_wake_at IS NOT NULL AND
181
85
  event_name IS NULL AND
182
86
  timeout_at IS NULL AND
183
- payload IS NULL AND
184
87
  resolved_at IS NOT NULL
185
88
  )
186
89
  OR
187
90
  (
188
91
  type = 'wait' AND
189
92
  state = 'waiting' AND
190
- attempt_count IS NULL AND
191
93
  max_attempts IS NULL AND
192
- next_attempt_at IS NULL AND
193
- result IS NULL AND
194
- error_message IS NULL AND
195
- error_name IS NULL AND
196
- wake_at IS NULL AND
94
+ target_wake_at IS NULL AND
197
95
  event_name IS NOT NULL AND
198
- payload IS NULL AND
199
96
  resolved_at IS NULL
200
97
  )
201
98
  OR
202
99
  (
203
100
  type = 'wait' AND
204
101
  state = 'satisfied' AND
205
- attempt_count IS NULL AND
206
102
  max_attempts IS NULL AND
207
- next_attempt_at IS NULL AND
208
- result IS NULL AND
209
- error_message IS NULL AND
210
- error_name IS NULL AND
211
- wake_at IS NULL AND
103
+ target_wake_at IS NULL AND
212
104
  event_name IS NOT NULL AND
213
- timeout_at IS NULL AND
214
- payload IS NOT NULL AND
215
105
  resolved_at IS NOT NULL
216
106
  )
217
107
  OR
218
108
  (
219
109
  type = 'wait' AND
220
110
  state = 'timed_out' AND
221
- attempt_count IS NULL AND
222
111
  max_attempts IS NULL AND
223
- next_attempt_at IS NULL AND
224
- result IS NULL AND
225
- error_message IS NULL AND
226
- error_name IS NULL AND
227
- wake_at IS NULL AND
112
+ target_wake_at IS NULL AND
228
113
  event_name IS NOT NULL AND
229
- timeout_at IS NULL AND
230
- payload IS NULL AND
114
+ timeout_at IS NOT NULL AND
231
115
  resolved_at IS NOT NULL
232
116
  )
233
117
  ),
234
118
  CHECK (parent_step_id IS NULL OR parent_step_id <> id)
235
119
  ) STRICT;
236
120
 
237
- CREATE TABLE step_events (
121
+ CREATE TABLE run_step_attempts (
238
122
  id TEXT NOT NULL PRIMARY KEY
239
123
  DEFAULT (lower(hex(randomblob(16))))
240
124
  CHECK (length(id) > 0),
241
125
 
242
- step_id TEXT NOT NULL,
243
- recorded_at INTEGER NOT NULL
126
+ step_id TEXT NOT NULL REFERENCES steps(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
127
+
128
+ started_at INTEGER NOT NULL
244
129
  DEFAULT (CAST(unixepoch('subsecond') * 1000 AS INTEGER))
245
- CHECK (recorded_at >= 0),
130
+ CHECK (started_at >= 0),
246
131
 
247
- type TEXT NOT NULL CHECK (type IN (
248
- 'attempt_started',
249
- 'attempt_succeeded',
250
- 'attempt_failed',
251
- 'sleep_waiting',
252
- 'sleep_elapsed',
253
- 'wait_waiting',
254
- 'wait_satisfied',
255
- 'wait_timed_out'
256
- )),
132
+ state TEXT NOT NULL CHECK (state IN ('started', 'succeeded', 'failed')),
133
+
134
+ ended_at INTEGER CHECK (ended_at IS NULL OR ended_at >= started_at),
135
+
136
+ -- Discriminator for the shape of a succeeded result.
137
+ -- 'json' → result_json holds the raw JSON value (never NULL)
138
+ -- 'none' → callback returned undefined/void, result_json IS NULL
139
+ result_type TEXT CHECK (result_type IN ('json', 'none')),
140
+
141
+ -- Raw JSON value. No wrapper objects.
142
+ -- NULL when result_type is 'none', or when the attempt hasn't succeeded yet.
143
+ result_json TEXT CHECK (result_json IS NULL OR json_valid(result_json)),
257
144
 
258
- attempt_number INTEGER,
259
- result TEXT,
260
145
  error_message TEXT,
261
146
  error_name TEXT,
262
- next_attempt_at INTEGER,
263
- wake_at INTEGER,
264
- event_name TEXT,
265
- timeout_at INTEGER,
266
- payload TEXT,
147
+ next_attempt_at INTEGER CHECK (next_attempt_at IS NULL OR next_attempt_at >= 0),
267
148
 
268
- FOREIGN KEY (step_id) REFERENCES steps(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
269
-
270
- CHECK (attempt_number IS NULL OR attempt_number >= 1),
271
- CHECK (next_attempt_at IS NULL OR next_attempt_at >= 0),
272
- CHECK (wake_at IS NULL OR wake_at >= 0),
273
- CHECK (timeout_at IS NULL OR timeout_at >= 0),
274
149
  CHECK (error_name IS NULL OR length(error_name) > 0),
275
- CHECK (event_name IS NULL OR length(event_name) > 0),
276
150
 
277
151
  CHECK (
278
152
  (
279
- type = 'attempt_started' AND
280
- attempt_number IS NOT NULL AND
281
- result IS NULL AND
153
+ state = 'started' AND
154
+ ended_at IS NULL AND
155
+ result_type IS NULL AND
156
+ result_json IS NULL AND
282
157
  error_message IS NULL AND
283
158
  error_name IS NULL AND
284
- next_attempt_at IS NULL AND
285
- wake_at IS NULL AND
286
- event_name IS NULL AND
287
- timeout_at IS NULL AND
288
- payload IS NULL
159
+ next_attempt_at IS NULL
289
160
  )
290
161
  OR
291
162
  (
292
- type = 'attempt_succeeded' AND
293
- attempt_number IS NOT NULL AND
294
- result IS NOT NULL AND
163
+ state = 'succeeded' AND
164
+ ended_at IS NOT NULL AND
165
+ result_type IS NOT NULL AND
166
+ (
167
+ (result_type = 'none' AND result_json IS NULL) OR
168
+ (result_type = 'json' AND result_json IS NOT NULL)
169
+ ) AND
295
170
  error_message IS NULL AND
296
171
  error_name IS NULL AND
297
- next_attempt_at IS NULL AND
298
- wake_at IS NULL AND
299
- event_name IS NULL AND
300
- timeout_at IS NULL AND
301
- payload IS NULL
172
+ next_attempt_at IS NULL
302
173
  )
303
174
  OR
304
175
  (
305
- type = 'attempt_failed' AND
306
- attempt_number IS NOT NULL AND
307
- result IS NULL AND
176
+ state = 'failed' AND
177
+ ended_at IS NOT NULL AND
308
178
  error_message IS NOT NULL AND
309
- wake_at IS NULL AND
310
- event_name IS NULL AND
311
- timeout_at IS NULL AND
312
- payload IS NULL
313
- )
314
- OR
315
- (
316
- type = 'sleep_waiting' AND
317
- attempt_number IS NULL AND
318
- result IS NULL AND
319
- error_message IS NULL AND
320
- error_name IS NULL AND
321
- next_attempt_at IS NULL AND
322
- wake_at IS NOT NULL AND
323
- event_name IS NULL AND
324
- timeout_at IS NULL AND
325
- payload IS NULL
326
- )
327
- OR
328
- (
329
- type = 'sleep_elapsed' AND
330
- attempt_number IS NULL AND
331
- result IS NULL AND
332
- error_message IS NULL AND
333
- error_name IS NULL AND
334
- next_attempt_at IS NULL AND
335
- wake_at IS NULL AND
336
- event_name IS NULL AND
337
- timeout_at IS NULL AND
338
- payload IS NULL
339
- )
340
- OR
341
- (
342
- type = 'wait_waiting' AND
343
- attempt_number IS NULL AND
344
- result IS NULL AND
345
- error_message IS NULL AND
346
- error_name IS NULL AND
347
- next_attempt_at IS NULL AND
348
- wake_at IS NULL AND
349
- event_name IS NOT NULL AND
350
- payload IS NULL
351
- )
352
- OR
353
- (
354
- type = 'wait_satisfied' AND
355
- attempt_number IS NULL AND
356
- result IS NULL AND
357
- error_message IS NULL AND
358
- error_name IS NULL AND
359
- next_attempt_at IS NULL AND
360
- wake_at IS NULL AND
361
- event_name IS NULL AND
362
- timeout_at IS NULL AND
363
- payload IS NOT NULL
364
- )
365
- OR
366
- (
367
- type = 'wait_timed_out' AND
368
- attempt_number IS NULL AND
369
- result IS NULL AND
370
- error_message IS NULL AND
371
- error_name IS NULL AND
372
- next_attempt_at IS NULL AND
373
- wake_at IS NULL AND
374
- event_name IS NULL AND
375
- timeout_at IS NULL AND
376
- payload IS NULL
179
+ result_type IS NULL AND
180
+ result_json IS NULL
377
181
  )
378
182
  )
379
183
  ) STRICT;
@@ -405,6 +209,8 @@ export default `
405
209
  DEFAULT (lower(hex(randomblob(16))))
406
210
  CHECK (length(id) > 0),
407
211
  event_name TEXT NOT NULL CHECK (length(event_name) > 0),
212
+ -- Raw JSON value, or SQL NULL when no payload was provided (undefined).
213
+ -- JSON null is stored as the TEXT literal 'null', distinct from SQL NULL.
408
214
  payload TEXT CHECK (payload IS NULL OR json_valid(payload)),
409
215
  created_at INTEGER NOT NULL
410
216
  DEFAULT (CAST(unixepoch('subsecond') * 1000 AS INTEGER))
@@ -414,20 +220,21 @@ export default `
414
220
 
415
221
  FOREIGN KEY (claimed_by) REFERENCES steps(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
416
222
 
417
- -- claimed_by and claimed_at must be set together or not set at all
418
223
  CHECK (
419
224
  (claimed_by IS NULL AND claimed_at IS NULL) OR
420
225
  (claimed_by IS NOT NULL AND claimed_at IS NOT NULL)
421
226
  )
422
227
  ) STRICT;
423
228
 
424
- -- scheduler/query indexes
425
- CREATE INDEX steps_run_pending_by_time_idx
426
- ON steps(next_attempt_at, id)
427
- WHERE type = 'run' AND state = 'pending';
229
+ CREATE INDEX run_step_attempts_by_step_time_idx
230
+ ON run_step_attempts(step_id, started_at, id);
231
+
232
+ CREATE INDEX run_step_attempts_started_one_idx
233
+ ON run_step_attempts(step_id)
234
+ WHERE state = 'started';
428
235
 
429
236
  CREATE INDEX steps_sleep_waiting_by_time_idx
430
- ON steps(wake_at, id)
237
+ ON steps(target_wake_at, id)
431
238
  WHERE type = 'sleep' AND state = 'waiting';
432
239
 
433
240
  CREATE INDEX steps_wait_waiting_by_event_idx
@@ -442,20 +249,16 @@ export default `
442
249
  ON steps(parent_step_id, id)
443
250
  WHERE parent_step_id IS NOT NULL;
444
251
 
445
- CREATE INDEX step_events_by_step_and_time_idx
446
- ON step_events(step_id, recorded_at, id);
447
-
448
252
  CREATE INDEX workflow_events_by_time_idx
449
253
  ON workflow_events(recorded_at, id);
450
254
 
451
255
  CREATE INDEX inbound_events_by_name_and_time_idx
452
256
  ON inbound_events(event_name, created_at, id);
453
257
 
454
- CREATE INDEX inbound_events_by_claimed_by_idx
455
- ON inbound_events(claimed_by, id)
258
+ CREATE UNIQUE INDEX inbound_events_claimed_by_unique
259
+ ON inbound_events(claimed_by)
456
260
  WHERE claimed_by IS NOT NULL;
457
261
 
458
- -- immutable identity fields
459
262
  CREATE TRIGGER workflow_metadata_immutable_fields
460
263
  BEFORE UPDATE ON workflow_metadata
461
264
  FOR EACH ROW
@@ -464,15 +267,16 @@ export default `
464
267
  SELECT RAISE(ABORT, 'workflow_metadata.id and workflow_metadata.created_at are immutable');
465
268
  END;
466
269
 
467
- -- valid status transitions
468
270
  CREATE TRIGGER workflow_metadata_valid_transition
469
271
  BEFORE UPDATE ON workflow_metadata
470
272
  FOR EACH ROW
471
273
  WHEN NEW.status <> OLD.status
472
274
  BEGIN
473
275
  SELECT CASE
474
- WHEN OLD.status = 'pending' AND NEW.status NOT IN ('running', 'cancelled') THEN
475
- RAISE(ABORT, 'pending can only transition to running or cancelled')
276
+ WHEN OLD.status = 'pending' AND NEW.status NOT IN ('initialized', 'cancelled') THEN
277
+ RAISE(ABORT, 'pending can only transition to initialized or cancelled')
278
+ WHEN OLD.status = 'initialized' AND NEW.status NOT IN ('running', 'cancelled') THEN
279
+ RAISE(ABORT, 'initialized can only transition to running or cancelled')
476
280
  WHEN OLD.status = 'running' AND NEW.status NOT IN ('paused', 'completed', 'failed', 'cancelled') THEN
477
281
  RAISE(ABORT, 'running can only transition to paused, completed, failed, or cancelled')
478
282
  WHEN OLD.status = 'paused' AND NEW.status NOT IN ('running', 'cancelled') THEN
@@ -482,6 +286,14 @@ export default `
482
286
  END;
483
287
  END;
484
288
 
289
+ CREATE TRIGGER workflow_metadata_definition_input_immutable_after_init
290
+ BEFORE UPDATE ON workflow_metadata
291
+ FOR EACH ROW
292
+ WHEN OLD.status <> 'pending' AND NEW.definition_input IS NOT OLD.definition_input
293
+ BEGIN
294
+ SELECT RAISE(ABORT, 'workflow_metadata.definition_input is immutable after initialization');
295
+ END;
296
+
485
297
  CREATE TRIGGER steps_immutable_identity_fields
486
298
  BEFORE UPDATE ON steps
487
299
  FOR EACH ROW
@@ -507,22 +319,34 @@ export default `
507
319
  SELECT RAISE(ABORT, 'steps.parent_step_id must reference a run step');
508
320
  END;
509
321
 
510
- -- append-only step events
511
- CREATE TRIGGER step_events_append_only_update
512
- BEFORE UPDATE ON step_events
513
- FOR EACH ROW
322
+ CREATE TRIGGER run_step_attempts_step_must_be_run
323
+ BEFORE INSERT ON run_step_attempts
324
+ WHEN (SELECT type FROM steps WHERE id = NEW.step_id) IS NOT 'run'
325
+ BEGIN
326
+ SELECT RAISE(ABORT, 'run_step_attempts.step_id must reference a run step');
327
+ END;
328
+
329
+ CREATE TRIGGER run_step_attempts_at_most_one_started_ins
330
+ BEFORE INSERT ON run_step_attempts
331
+ WHEN NEW.state = 'started'
332
+ AND EXISTS (SELECT 1 FROM run_step_attempts WHERE step_id = NEW.step_id AND state = 'started')
514
333
  BEGIN
515
- SELECT RAISE(ABORT, 'step_events is append-only');
334
+ SELECT RAISE(ABORT, 'run step already has an in-flight attempt');
516
335
  END;
517
336
 
518
- CREATE TRIGGER step_events_append_only_delete
519
- BEFORE DELETE ON step_events
337
+ CREATE TRIGGER run_step_attempts_valid_transition
338
+ BEFORE UPDATE ON run_step_attempts
520
339
  FOR EACH ROW
340
+ WHEN NEW.state <> OLD.state
521
341
  BEGIN
522
- SELECT RAISE(ABORT, 'step_events is append-only');
342
+ SELECT CASE
343
+ WHEN OLD.state = 'started' AND NEW.state NOT IN ('succeeded', 'failed') THEN
344
+ RAISE(ABORT, 'started can only transition to succeeded or failed')
345
+ WHEN OLD.state IN ('succeeded', 'failed') THEN
346
+ RAISE(ABORT, 'terminal attempt state cannot transition')
347
+ END;
523
348
  END;
524
349
 
525
- -- append-only workflow events
526
350
  CREATE TRIGGER workflow_events_append_only_update
527
351
  BEFORE UPDATE ON workflow_events
528
352
  FOR EACH ROW
@@ -536,24 +360,4 @@ export default `
536
360
  BEGIN
537
361
  SELECT RAISE(ABORT, 'workflow_events is append-only');
538
362
  END;
539
-
540
- -- step_events.type must match the referenced row in steps.type
541
- CREATE TRIGGER step_events_parent_type_match
542
- BEFORE INSERT ON step_events
543
- FOR EACH ROW
544
- BEGIN
545
- SELECT CASE
546
- WHEN NOT EXISTS (SELECT 1 FROM steps WHERE id = NEW.step_id) THEN
547
- RAISE(ABORT, 'step_events.step_id does not reference an existing steps row')
548
- WHEN NEW.type IN ('attempt_started', 'attempt_succeeded', 'attempt_failed')
549
- AND (SELECT type FROM steps WHERE id = NEW.step_id) <> 'run' THEN
550
- RAISE(ABORT, 'run attempt events require a run step')
551
- WHEN NEW.type IN ('sleep_waiting', 'sleep_elapsed')
552
- AND (SELECT type FROM steps WHERE id = NEW.step_id) <> 'sleep' THEN
553
- RAISE(ABORT, 'sleep events require a sleep step')
554
- WHEN NEW.type IN ('wait_waiting', 'wait_satisfied', 'wait_timed_out')
555
- AND (SELECT type FROM steps WHERE id = NEW.step_id) <> 'wait' THEN
556
- RAISE(ABORT, 'wait events require a wait step')
557
- END;
558
- END;
559
363
  `;