@vrplatform/kysely 1.3.45-stage.6066 → 1.3.45-stage.607

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 (97) hide show
  1. package/build/.data.tar +0 -0
  2. package/build/main/control-plane/index.d.ts +312 -0
  3. package/build/main/control-plane/index.js.map +1 -1
  4. package/build/main/index.d.ts +13 -2
  5. package/build/main/index.js +217 -23
  6. package/build/main/index.js.map +1 -1
  7. package/build/main/local/generate.js +2 -0
  8. package/build/main/local/generate.js.map +1 -1
  9. package/build/main/local/index.js +110 -8
  10. package/build/main/local/index.js.map +1 -1
  11. package/build/main/postgres-error.d.ts +3 -3
  12. package/build/main/postgres-error.js +4 -3
  13. package/build/main/postgres-error.js.map +1 -1
  14. package/build/main/query/listing/status.js +2 -6
  15. package/build/main/query/listing/status.js.map +1 -1
  16. package/build/main/query/tenant/status.d.ts +1 -1
  17. package/build/main/query/tenant/status.js +6 -4
  18. package/build/main/query/tenant/status.js.map +1 -1
  19. package/build/main/regional.d.ts +1 -0
  20. package/build/main/regional.js +1 -0
  21. package/build/main/regional.js.map +1 -1
  22. package/build/main/test/index.d.ts +23 -3
  23. package/build/main/test/index.js +35 -13
  24. package/build/main/test/index.js.map +1 -1
  25. package/build/main/v1.generated.d.ts +560 -8
  26. package/build/main/v1.generated.js.map +1 -1
  27. package/build/migrations-v2/0000000000015-durable-flow-events.ts +163 -0
  28. package/build/migrations-v2/0000000000016-billing-listing-facts.ts +539 -0
  29. package/build/migrations-v2/0000000000016-managed-team-access.ts +34 -0
  30. package/build/migrations-v2/0000000000017-drop-trial-until.ts +42 -0
  31. package/build/migrations-v2/0000000000017-webhook-secret-reencryption-failures.ts +18 -0
  32. package/build/migrations-v2/0000000000018-accounting-integrity-monitoring.ts +276 -0
  33. package/build/migrations-v2/0000000000018-durable-flow-event-sensitive-input.ts +28 -0
  34. package/build/migrations-v2/0000000000019-accounting-integrity-run-shards.ts +26 -0
  35. package/build/migrations-v2/0000000000019-asynchronous-operations.ts +736 -0
  36. package/build/migrations-v2/0000000000020-accounting-integrity-case-classification.ts +52 -0
  37. package/build/migrations-v2/0000000000021-payment-line-classification-monitor-indexes.ts +31 -0
  38. package/build/migrations-v2/0000000000022-accounting-integrity-controlled-repairs.ts +281 -0
  39. package/build/migrations-v2/0000000000023-line-classification-usage.ts +325 -0
  40. package/build/migrations-v2/0000000000024-tenant-region-migration.ts +108 -0
  41. package/build/migrations-v2/0000000000025-partner-ach-denial-default.ts +30 -0
  42. package/build/migrations-v2/0000000000026-line-classification-usage-capture.ts +399 -0
  43. package/build/migrations-v2/0000000000027-payout-replacement-chain.ts +27 -0
  44. package/build/migrations-v2/0000000000028-accounting-integrity-operator-workflow.ts +108 -0
  45. package/build/migrations-v2/0000000000029-accounting-integrity-definition-artifacts.ts +40 -0
  46. package/build/migrations-v2/0000000000030-accounting-integrity-review-metadata-compatibility.ts +49 -0
  47. package/build/migrations-v2/0000000000031-ui-permission-role-simplification.ts +70 -0
  48. package/build/migrations-v2/0000000000032-tenant-delete-ownership-cascades.ts +44 -0
  49. package/build/migrations-v2/0000000000033-reservation-issue-seed-bucket-index.ts +19 -0
  50. package/build/migrations-v2/0000000000034-retire-recent-classification-indexes.ts +29 -0
  51. package/build/migrations-v2/0000000000035-core-change-autovacuum.ts +24 -0
  52. package/build/migrations-v2/0000000000036-journal-entry-inactive-reason-constraint.ts +33 -0
  53. package/build/migrations-v2/0000000000037-journal-entry-inactive-reasons.ts +19 -0
  54. package/build/migrations-v2/0000000000038-journal-entry-group-inactive-reason.ts +14 -0
  55. package/build/migrations-v2/0000000000039-reservation-guest-breakdown.ts +19 -0
  56. package/build/migrations-v2/0000000000040-csv-import-source-identity-index.ts +23 -0
  57. package/build/migrations-v2/0000000000041-payment-returns.ts +38 -0
  58. package/build/migrations-v2/0000000000042-operation-resource-types.ts +609 -0
  59. package/build/migrations-v2/0000000000043-reservation-journal-freshness.ts +149 -0
  60. package/build/migrations-v2/0000000000044-reservation-journal-freshness-backfill.ts +254 -0
  61. package/build/migrations-v2/0000000000045-reservation-journal-freshness-revision-repair.ts +83 -0
  62. package/build/migrations-v2/0000000000046-materialized-reservation-journal-operations.ts +709 -0
  63. package/build/migrations-v2/0000000000047-public-audit-completion.ts +77 -0
  64. package/build/migrations-v2/0000000000048-reservation-journal-full-refresh-repair.ts +136 -0
  65. package/build/migrations-v2/0000000000049-queued-journal-operation-failures.ts +60 -0
  66. package/build/migrations-v2/0000000000050-connection-oauth-state.ts +43 -0
  67. package/build/migrations-v2/0000000000051-owner-statement-email-delivery.ts +134 -0
  68. package/build/migrations-v2/0000000000052-fee-journal-lock-skip-operations.ts +59 -0
  69. package/build/migrations-v2/0000000000053-fee-journal-lock-skip-null-attempt.ts +44 -0
  70. package/build/migrations-v2/0000000000054-journal-refresh-issue-codes.ts +16 -0
  71. package/build/migrations-v2/0000000000055-tenant-billing-subscription.ts +8 -0
  72. package/build/module/control-plane/index.d.ts +312 -0
  73. package/build/module/control-plane/index.js.map +1 -1
  74. package/build/module/index.d.ts +13 -2
  75. package/build/module/index.js +219 -24
  76. package/build/module/index.js.map +1 -1
  77. package/build/module/local/generate.js +2 -0
  78. package/build/module/local/generate.js.map +1 -1
  79. package/build/module/local/index.js +110 -8
  80. package/build/module/local/index.js.map +1 -1
  81. package/build/module/postgres-error.d.ts +3 -3
  82. package/build/module/postgres-error.js +4 -3
  83. package/build/module/postgres-error.js.map +1 -1
  84. package/build/module/query/listing/status.js +2 -6
  85. package/build/module/query/listing/status.js.map +1 -1
  86. package/build/module/query/tenant/status.d.ts +1 -1
  87. package/build/module/query/tenant/status.js +6 -4
  88. package/build/module/query/tenant/status.js.map +1 -1
  89. package/build/module/regional.d.ts +1 -0
  90. package/build/module/regional.js +1 -0
  91. package/build/module/regional.js.map +1 -1
  92. package/build/module/test/index.d.ts +23 -3
  93. package/build/module/test/index.js +34 -13
  94. package/build/module/test/index.js.map +1 -1
  95. package/build/module/v1.generated.d.ts +560 -8
  96. package/build/module/v1.generated.js.map +1 -1
  97. package/package.json +4 -2
@@ -0,0 +1,709 @@
1
+ import type { Kysely } from 'kysely';
2
+ import { sql } from 'kysely';
3
+
4
+ async function replaceOperationRefreshFunctions(db: Kysely<unknown>) {
5
+ await sql`
6
+ create or replace function core.refresh_operation_state(
7
+ target_operation_id uuid
8
+ ) returns void
9
+ language plpgsql
10
+ as $$
11
+ declare
12
+ next_status core.operation_status;
13
+ next_started_at timestamptz;
14
+ next_finished_at timestamptz;
15
+ next_failure_code text;
16
+ next_failure_message text;
17
+ target_operation_type core.operation_type;
18
+ target_public_id_format core.operation_public_id_format;
19
+ target_action_id uuid;
20
+ begin
21
+ select
22
+ operation.type,
23
+ operation.public_id_format,
24
+ source.source_id
25
+ into
26
+ target_operation_type,
27
+ target_public_id_format,
28
+ target_action_id
29
+ from core.operation as operation
30
+ left join core.operation_source as source
31
+ on source.operation_id = operation.id
32
+ and source.source_type = 'audit-action'
33
+ where operation.id = target_operation_id;
34
+
35
+ select
36
+ case
37
+ when count(*) filter (where required) = 0 then 'queued'
38
+ when bool_and(status = 'completed') filter (where required)
39
+ then 'completed'
40
+ when bool_and(status in ('completed', 'failed')) filter (where required)
41
+ then 'failed'
42
+ when bool_or(status = 'running') filter (where required)
43
+ or bool_or(status in ('completed', 'failed')) filter (where required)
44
+ then 'running'
45
+ else 'queued'
46
+ end::core.operation_status,
47
+ min(started_at) filter (where required),
48
+ max(finished_at) filter (where required)
49
+ into next_status, next_started_at, next_finished_at
50
+ from core.operation_source
51
+ where operation_id = target_operation_id;
52
+
53
+ if next_status = 'failed' then
54
+ if target_operation_type = 'reservation-journal-refresh'
55
+ and target_public_id_format = 'action'
56
+ and target_action_id is not null then
57
+ select
58
+ case
59
+ when bool_or(
60
+ invalidation.applied_at is null
61
+ and attempt.result_code = 'journal_unbalanced'
62
+ ) then 'journalUnbalanced'
63
+ when bool_or(
64
+ invalidation.applied_at is null
65
+ and attempt.result_code = 'journal_configuration_invalid'
66
+ ) then 'journalConfigurationInvalid'
67
+ when bool_or(
68
+ invalidation.applied_at is null
69
+ and attempt.result_code in (
70
+ 'statement_period_locked',
71
+ 'skipped_locked'
72
+ )
73
+ ) then 'journalLocked'
74
+ else 'journalRefreshFailed'
75
+ end,
76
+ case
77
+ when bool_or(
78
+ invalidation.applied_at is null
79
+ and attempt.result_code = 'journal_unbalanced'
80
+ ) then 'Journal refresh produced unbalanced entries'
81
+ when bool_or(
82
+ invalidation.applied_at is null
83
+ and attempt.result_code = 'journal_configuration_invalid'
84
+ ) then 'Journal configuration is invalid'
85
+ when bool_or(
86
+ invalidation.applied_at is null
87
+ and attempt.result_code in (
88
+ 'statement_period_locked',
89
+ 'skipped_locked'
90
+ )
91
+ ) then 'Journal refresh is blocked by accounting locks'
92
+ else 'Journal refresh failed'
93
+ end
94
+ into next_failure_code, next_failure_message
95
+ from audit.effect as effect
96
+ left join accounting.reservation_journal_invalidation as invalidation
97
+ on invalidation.tenant_id = effect.tenant_id
98
+ and invalidation.effect_id = effect.id
99
+ left join lateral (
100
+ select effect_attempt.result_code
101
+ from audit.effect_attempt as effect_attempt
102
+ where effect_attempt.tenant_id = effect.tenant_id
103
+ and effect_attempt.effect_id = effect.id
104
+ order by effect_attempt.attempt_num desc
105
+ limit 1
106
+ ) as attempt on true
107
+ where effect.action_id = target_action_id
108
+ and effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
109
+ and effect.entity_type = 'reservation';
110
+ else
111
+ next_failure_code := 'operation_failed';
112
+ next_failure_message := 'Operation failed';
113
+ end if;
114
+ else
115
+ next_failure_code := null;
116
+ next_failure_message := null;
117
+ end if;
118
+
119
+ update core.operation
120
+ set
121
+ status = next_status,
122
+ started_at = coalesce(core.operation.started_at, next_started_at),
123
+ finished_at = case
124
+ when next_status in ('completed', 'failed')
125
+ then coalesce(next_finished_at, now())
126
+ else null
127
+ end,
128
+ failure_code = next_failure_code,
129
+ failure_message = next_failure_message,
130
+ updated_at = now(),
131
+ source_version = source_version + 1
132
+ where id = target_operation_id
133
+ and (
134
+ status is distinct from next_status
135
+ or started_at is distinct from coalesce(started_at, next_started_at)
136
+ or finished_at is distinct from case
137
+ when next_status in ('completed', 'failed')
138
+ then coalesce(next_finished_at, finished_at, now())
139
+ else null
140
+ end
141
+ or failure_code is distinct from next_failure_code
142
+ or failure_message is distinct from next_failure_message
143
+ );
144
+ end;
145
+ $$
146
+ `.execute(db);
147
+
148
+ await sql`
149
+ create or replace function core.refresh_audit_action_source(
150
+ target_action_id uuid
151
+ ) returns void
152
+ language plpgsql
153
+ as $$
154
+ declare
155
+ operation_row record;
156
+ next_status core.operation_status;
157
+ next_started_at timestamptz;
158
+ next_finished_at timestamptz;
159
+ begin
160
+ for operation_row in
161
+ select
162
+ operation.id,
163
+ operation.type,
164
+ operation.public_id_format
165
+ from core.operation_source as source
166
+ inner join core.operation as operation
167
+ on operation.id = source.operation_id
168
+ where source.source_type = 'audit-action'
169
+ and source.source_id = target_action_id
170
+ loop
171
+ if operation_row.type = 'reservation-journal-refresh' then
172
+ select
173
+ case
174
+ when count(*) = 0 then 'failed'
175
+ when bool_or(
176
+ (
177
+ invalidation.id is null
178
+ or invalidation.applied_at is null
179
+ )
180
+ and effect.status in ('claimed', 'running')
181
+ ) then 'running'
182
+ when bool_or(
183
+ (
184
+ invalidation.id is null
185
+ or invalidation.applied_at is null
186
+ )
187
+ and (
188
+ effect.status = 'pending'
189
+ or (
190
+ effect.status = 'failed'
191
+ and effect.retry_count < effect.max_retries
192
+ )
193
+ )
194
+ ) then 'queued'
195
+ when bool_and(
196
+ (
197
+ invalidation.id is not null
198
+ and invalidation.applied_at is not null
199
+ )
200
+ or (
201
+ invalidation.id is null
202
+ and effect.status = 'completed'
203
+ )
204
+ ) then 'completed'
205
+ else 'failed'
206
+ end::core.operation_status,
207
+ case
208
+ when count(*) = 0 then now()
209
+ else min(coalesce(effect.claimed_at, effect.last_attempt_at))
210
+ end,
211
+ case
212
+ when count(*) = 0 then now()
213
+ when bool_or(
214
+ (
215
+ invalidation.id is null
216
+ or invalidation.applied_at is null
217
+ )
218
+ and (
219
+ effect.status in ('pending', 'claimed', 'running')
220
+ or (
221
+ effect.status = 'failed'
222
+ and effect.retry_count < effect.max_retries
223
+ )
224
+ )
225
+ ) then null
226
+ else coalesce(
227
+ max(coalesce(
228
+ invalidation.applied_at,
229
+ effect.completed_at,
230
+ effect.last_attempt_at,
231
+ effect.claimed_at,
232
+ invalidation.created_at
233
+ )),
234
+ now()
235
+ )
236
+ end
237
+ into next_status, next_started_at, next_finished_at
238
+ from audit.effect as effect
239
+ left join accounting.reservation_journal_invalidation as invalidation
240
+ on invalidation.tenant_id = effect.tenant_id
241
+ and invalidation.effect_id = effect.id
242
+ where effect.action_id = target_action_id
243
+ and (
244
+ operation_row.public_id_format = 'legacy'
245
+ or (
246
+ effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
247
+ and effect.entity_type = 'reservation'
248
+ )
249
+ );
250
+ else
251
+ select
252
+ case
253
+ when count(*) = 0 then 'completed'
254
+ when bool_and(effect.status = 'completed') then 'completed'
255
+ when bool_and(
256
+ effect.status in ('completed', 'dead_letter')
257
+ or (
258
+ effect.status = 'failed'
259
+ and effect.retry_count >= effect.max_retries
260
+ )
261
+ ) then 'failed'
262
+ when bool_or(effect.status in ('claimed', 'running'))
263
+ or bool_or(
264
+ effect.status in ('completed', 'dead_letter')
265
+ or (
266
+ effect.status = 'failed'
267
+ and effect.retry_count >= effect.max_retries
268
+ )
269
+ ) then 'running'
270
+ else 'queued'
271
+ end::core.operation_status,
272
+ case
273
+ when count(*) = 0 then now()
274
+ else min(coalesce(effect.last_attempt_at, effect.claimed_at))
275
+ end,
276
+ case
277
+ when count(*) = 0 then now()
278
+ when bool_and(
279
+ effect.status in ('completed', 'dead_letter')
280
+ or (
281
+ effect.status = 'failed'
282
+ and effect.retry_count >= effect.max_retries
283
+ )
284
+ ) then coalesce(
285
+ max(coalesce(effect.completed_at, effect.last_attempt_at)),
286
+ now()
287
+ )
288
+ end
289
+ into next_status, next_started_at, next_finished_at
290
+ from audit.effect as effect
291
+ where effect.action_id = target_action_id;
292
+ end if;
293
+
294
+ update core.operation_source as source
295
+ set
296
+ status = next_status,
297
+ started_at = next_started_at,
298
+ finished_at = next_finished_at,
299
+ source_version = source.source_version + 1
300
+ where source.operation_id = operation_row.id
301
+ and source.source_type = 'audit-action'
302
+ and source.source_id = target_action_id
303
+ and (
304
+ source.status is distinct from next_status
305
+ or source.started_at is distinct from next_started_at
306
+ or source.finished_at is distinct from next_finished_at
307
+ );
308
+
309
+ perform core.refresh_operation_state(operation_row.id);
310
+ end loop;
311
+ end;
312
+ $$
313
+ `.execute(db);
314
+ }
315
+
316
+ async function restoreOperationRefreshFunctions(db: Kysely<unknown>) {
317
+ await sql`
318
+ create or replace function core.refresh_operation_state(
319
+ target_operation_id uuid
320
+ ) returns void
321
+ language plpgsql
322
+ as $$
323
+ declare
324
+ next_status core.operation_status;
325
+ next_started_at timestamptz;
326
+ next_finished_at timestamptz;
327
+ begin
328
+ select
329
+ case
330
+ when count(*) filter (where required) = 0 then 'queued'
331
+ when bool_and(status = 'completed') filter (where required)
332
+ then 'completed'
333
+ when bool_and(status in ('completed', 'failed')) filter (where required)
334
+ then 'failed'
335
+ when bool_or(status = 'running') filter (where required)
336
+ or bool_or(status in ('completed', 'failed')) filter (where required)
337
+ then 'running'
338
+ else 'queued'
339
+ end::core.operation_status,
340
+ min(started_at) filter (where required),
341
+ max(finished_at) filter (where required)
342
+ into next_status, next_started_at, next_finished_at
343
+ from core.operation_source
344
+ where operation_id = target_operation_id;
345
+
346
+ update core.operation
347
+ set
348
+ status = next_status,
349
+ started_at = coalesce(core.operation.started_at, next_started_at),
350
+ finished_at = case
351
+ when next_status in ('completed', 'failed')
352
+ then coalesce(next_finished_at, now())
353
+ else null
354
+ end,
355
+ failure_code = case
356
+ when next_status = 'failed' then 'operation_failed'
357
+ end,
358
+ failure_message = case
359
+ when next_status = 'failed' then 'Operation failed'
360
+ end,
361
+ updated_at = now(),
362
+ source_version = source_version + 1
363
+ where id = target_operation_id
364
+ and (
365
+ status is distinct from next_status
366
+ or started_at is distinct from coalesce(started_at, next_started_at)
367
+ or finished_at is distinct from case
368
+ when next_status in ('completed', 'failed')
369
+ then coalesce(next_finished_at, finished_at, now())
370
+ else null
371
+ end
372
+ );
373
+ end;
374
+ $$
375
+ `.execute(db);
376
+
377
+ await sql`
378
+ create or replace function core.refresh_audit_action_source(
379
+ target_action_id uuid
380
+ ) returns void
381
+ language plpgsql
382
+ as $$
383
+ declare
384
+ target_operation_id uuid;
385
+ begin
386
+ update core.operation_source as source
387
+ set
388
+ status = state.status,
389
+ started_at = state.started_at,
390
+ finished_at = state.finished_at,
391
+ source_version = source.source_version + 1
392
+ from (
393
+ select
394
+ case
395
+ when count(*) = 0 then 'completed'
396
+ when bool_and(effect.status = 'completed') then 'completed'
397
+ when bool_and(
398
+ effect.status in ('completed', 'dead_letter')
399
+ or (
400
+ effect.status = 'failed'
401
+ and effect.retry_count >= effect.max_retries
402
+ )
403
+ ) then 'failed'
404
+ when bool_or(effect.status in ('claimed', 'running'))
405
+ or bool_or(
406
+ effect.status in ('completed', 'dead_letter')
407
+ or (
408
+ effect.status = 'failed'
409
+ and effect.retry_count >= effect.max_retries
410
+ )
411
+ ) then 'running'
412
+ else 'queued'
413
+ end::core.operation_status as status,
414
+ case
415
+ when count(*) = 0 then now()
416
+ else min(coalesce(effect.last_attempt_at, effect.claimed_at))
417
+ end as started_at,
418
+ case
419
+ when count(*) = 0 then now()
420
+ when bool_and(
421
+ effect.status in ('completed', 'dead_letter')
422
+ or (
423
+ effect.status = 'failed'
424
+ and effect.retry_count >= effect.max_retries
425
+ )
426
+ ) then coalesce(
427
+ max(coalesce(effect.completed_at, effect.last_attempt_at)),
428
+ now()
429
+ )
430
+ end as finished_at
431
+ from audit.effect
432
+ where action_id = target_action_id
433
+ ) as state
434
+ where source.source_type = 'audit-action'
435
+ and source.source_id = target_action_id
436
+ and (
437
+ source.status is distinct from state.status
438
+ or source.started_at is distinct from state.started_at
439
+ or source.finished_at is distinct from state.finished_at
440
+ );
441
+
442
+ for target_operation_id in
443
+ select operation_id
444
+ from core.operation_source
445
+ where source_type = 'audit-action'
446
+ and source_id = target_action_id
447
+ loop
448
+ perform core.refresh_operation_state(target_operation_id);
449
+ end loop;
450
+ end;
451
+ $$
452
+ `.execute(db);
453
+ }
454
+
455
+ export async function up(db: Kysely<unknown>): Promise<void> {
456
+ await sql`
457
+ create type core.operation_public_id_format as enum ('action', 'legacy')
458
+ `.execute(db);
459
+ await sql`
460
+ alter table core.operation
461
+ add column public_id_format core.operation_public_id_format
462
+ not null default 'legacy'
463
+ `.execute(db);
464
+ await sql`
465
+ create index reservation_journal_invalidation_operation_idx
466
+ on accounting.reservation_journal_invalidation (
467
+ action_id,
468
+ tenant_id,
469
+ effect_id
470
+ )
471
+ where action_id is not null
472
+ `.execute(db);
473
+ await sql`
474
+ create table core.operation_backfill (
475
+ name text primary key,
476
+ completed_at timestamptz,
477
+ created_at timestamptz not null default now(),
478
+ updated_at timestamptz not null default now()
479
+ )
480
+ `.execute(db);
481
+ await sql`
482
+ insert into core.operation_backfill (name)
483
+ values ('reservation-journal-refresh-v1')
484
+ `.execute(db);
485
+
486
+ await replaceOperationRefreshFunctions(db);
487
+
488
+ await sql`
489
+ create function core.register_reservation_journal_operation(
490
+ target_action_id uuid
491
+ ) returns void
492
+ language plpgsql
493
+ as $$
494
+ begin
495
+ insert into core.operation (
496
+ id,
497
+ tenant_id,
498
+ initiated_by_user_id,
499
+ type,
500
+ public_id_format,
501
+ created_at,
502
+ updated_at
503
+ )
504
+ select
505
+ action.id,
506
+ action.tenant_id,
507
+ case when action.actor_type = 'user' then action.actor_id end,
508
+ 'reservation-journal-refresh',
509
+ 'action',
510
+ action.created_at,
511
+ action.created_at
512
+ from audit.action as action
513
+ where action.id = target_action_id
514
+ and exists (
515
+ select 1
516
+ from accounting.reservation_journal_invalidation as invalidation
517
+ where invalidation.action_id = action.id
518
+ and invalidation.tenant_id = action.tenant_id
519
+ )
520
+ and exists (
521
+ select 1
522
+ from audit.effect as effect
523
+ where effect.action_id = action.id
524
+ and effect.tenant_id = action.tenant_id
525
+ and effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
526
+ and effect.entity_type = 'reservation'
527
+ )
528
+ and not exists (
529
+ select 1
530
+ from core.operation_source as source
531
+ where source.source_type = 'audit-action'
532
+ and source.source_id = action.id
533
+ )
534
+ on conflict (id) do nothing;
535
+
536
+ insert into core.operation_source (
537
+ operation_id,
538
+ source_type,
539
+ source_id
540
+ )
541
+ select operation.id, 'audit-action', target_action_id
542
+ from core.operation as operation
543
+ where operation.id = target_action_id
544
+ and operation.type = 'reservation-journal-refresh'
545
+ and operation.public_id_format = 'action'
546
+ and exists (
547
+ select 1
548
+ from audit.effect as effect
549
+ where effect.action_id = target_action_id
550
+ and effect.tenant_id = operation.tenant_id
551
+ and effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
552
+ and effect.entity_type = 'reservation'
553
+ )
554
+ on conflict (source_type, source_id) do nothing;
555
+
556
+ insert into core.operation_resource (
557
+ operation_id,
558
+ resource_type,
559
+ resource_id,
560
+ role,
561
+ is_primary
562
+ )
563
+ select
564
+ operation.id,
565
+ case
566
+ when recurring_fee.id is not null
567
+ then 'recurring-fee'::core.operation_resource_type
568
+ else case action.root_entity_type
569
+ when 'recurringFee'
570
+ then 'recurring-fee'::core.operation_resource_type
571
+ when 'reservation' then 'reservation'::core.operation_resource_type
572
+ when 'listing' then 'listing'::core.operation_resource_type
573
+ when 'ownerStatement' then 'statement'::core.operation_resource_type
574
+ else 'team'::core.operation_resource_type
575
+ end
576
+ end,
577
+ case
578
+ when recurring_fee.id is not null then recurring_fee.id
579
+ when action.root_entity_type in (
580
+ 'recurringFee',
581
+ 'reservation',
582
+ 'listing',
583
+ 'ownerStatement'
584
+ ) and action.root_entity_id is not null then action.root_entity_id
585
+ else action.tenant_id
586
+ end,
587
+ 'source',
588
+ true
589
+ from core.operation as operation
590
+ inner join audit.action as action
591
+ on action.id = operation.id
592
+ and action.tenant_id = operation.tenant_id
593
+ left join lateral (
594
+ select mutation.subject_id as id
595
+ from audit.mutation as mutation
596
+ where mutation.action_id = action.id
597
+ and mutation.tenant_id = action.tenant_id
598
+ and action.action_type = 'recurring_fees.update'
599
+ and mutation.subject_type = 'recurringFee'
600
+ order by mutation.created_at, mutation.id
601
+ limit 1
602
+ ) as recurring_fee on true
603
+ where operation.id = target_action_id
604
+ and operation.type = 'reservation-journal-refresh'
605
+ and operation.public_id_format = 'action'
606
+ on conflict do nothing;
607
+
608
+ perform core.refresh_audit_action_source(target_action_id);
609
+ end;
610
+ $$
611
+ `.execute(db);
612
+
613
+ await sql`
614
+ create function core.refresh_reservation_journal_invalidation_operation()
615
+ returns trigger
616
+ language plpgsql
617
+ as $$
618
+ begin
619
+ if new.action_id is not null then
620
+ perform core.register_reservation_journal_operation(new.action_id);
621
+ end if;
622
+ if tg_op = 'UPDATE'
623
+ and old.action_id is distinct from new.action_id
624
+ and old.action_id is not null then
625
+ perform core.refresh_audit_action_source(old.action_id);
626
+ end if;
627
+ return new;
628
+ end;
629
+ $$
630
+ `.execute(db);
631
+ await sql`
632
+ create trigger refresh_reservation_journal_invalidation_operation
633
+ after insert or update of action_id, applied_at
634
+ on accounting.reservation_journal_invalidation
635
+ for each row
636
+ execute function core.refresh_reservation_journal_invalidation_operation()
637
+ `.execute(db);
638
+
639
+ await sql`
640
+ create function core.refresh_audit_action_operation_attempt()
641
+ returns trigger
642
+ language plpgsql
643
+ as $$
644
+ begin
645
+ perform core.refresh_audit_action_source(
646
+ coalesce(new.action_id, old.action_id)
647
+ );
648
+ return coalesce(new, old);
649
+ end;
650
+ $$
651
+ `.execute(db);
652
+ await sql`
653
+ create trigger refresh_audit_action_operation_attempt
654
+ after insert or update of status, result_code, finished_at or delete
655
+ on audit.effect_attempt
656
+ for each row execute function core.refresh_audit_action_operation_attempt()
657
+ `.execute(db);
658
+
659
+ await sql`
660
+ do $$
661
+ begin
662
+ if exists (select 1 from pg_roles where rolname = 'application') then
663
+ grant execute on function
664
+ core.register_reservation_journal_operation(uuid),
665
+ core.refresh_reservation_journal_invalidation_operation(),
666
+ core.refresh_audit_action_operation_attempt()
667
+ to application;
668
+ grant select, update on core.operation_backfill to application;
669
+ end if;
670
+ end;
671
+ $$
672
+ `.execute(db);
673
+ }
674
+
675
+ export async function down(db: Kysely<unknown>): Promise<void> {
676
+ await sql`
677
+ drop trigger if exists refresh_audit_action_operation_attempt
678
+ on audit.effect_attempt
679
+ `.execute(db);
680
+ await sql`
681
+ drop function if exists core.refresh_audit_action_operation_attempt
682
+ `.execute(db);
683
+ await sql`
684
+ drop trigger if exists refresh_reservation_journal_invalidation_operation
685
+ on accounting.reservation_journal_invalidation
686
+ `.execute(db);
687
+ await sql`
688
+ drop function if exists
689
+ core.refresh_reservation_journal_invalidation_operation
690
+ `.execute(db);
691
+ await sql`
692
+ drop function if exists core.register_reservation_journal_operation
693
+ `.execute(db);
694
+
695
+ await sql`
696
+ delete from core.operation
697
+ where public_id_format = 'action'
698
+ and type = 'reservation-journal-refresh'
699
+ `.execute(db);
700
+ await restoreOperationRefreshFunctions(db);
701
+ await sql`drop table core.operation_backfill`.execute(db);
702
+ await sql`
703
+ drop index accounting.reservation_journal_invalidation_operation_idx
704
+ `.execute(db);
705
+ await sql`
706
+ alter table core.operation drop column public_id_format
707
+ `.execute(db);
708
+ await sql`drop type core.operation_public_id_format`.execute(db);
709
+ }