duron 0.2.2 → 0.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/action-job.d.ts +2 -0
  2. package/dist/action-job.d.ts.map +1 -1
  3. package/dist/action-job.js +20 -1
  4. package/dist/action-manager.d.ts +2 -0
  5. package/dist/action-manager.d.ts.map +1 -1
  6. package/dist/action-manager.js +3 -0
  7. package/dist/action.d.ts +27 -0
  8. package/dist/action.d.ts.map +1 -1
  9. package/dist/action.js +9 -0
  10. package/dist/adapters/adapter.d.ts +10 -2
  11. package/dist/adapters/adapter.d.ts.map +1 -1
  12. package/dist/adapters/adapter.js +59 -1
  13. package/dist/adapters/postgres/base.d.ts +9 -4
  14. package/dist/adapters/postgres/base.d.ts.map +1 -1
  15. package/dist/adapters/postgres/base.js +269 -19
  16. package/dist/adapters/postgres/schema.d.ts +249 -105
  17. package/dist/adapters/postgres/schema.d.ts.map +1 -1
  18. package/dist/adapters/postgres/schema.default.d.ts +249 -106
  19. package/dist/adapters/postgres/schema.default.d.ts.map +1 -1
  20. package/dist/adapters/postgres/schema.default.js +2 -2
  21. package/dist/adapters/postgres/schema.js +29 -1
  22. package/dist/adapters/schemas.d.ts +140 -7
  23. package/dist/adapters/schemas.d.ts.map +1 -1
  24. package/dist/adapters/schemas.js +52 -4
  25. package/dist/client.d.ts +8 -1
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +28 -0
  28. package/dist/errors.d.ts +6 -0
  29. package/dist/errors.d.ts.map +1 -1
  30. package/dist/errors.js +16 -1
  31. package/dist/index.d.ts +4 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +4 -2
  34. package/dist/server.d.ts +220 -16
  35. package/dist/server.d.ts.map +1 -1
  36. package/dist/server.js +123 -8
  37. package/dist/step-manager.d.ts +8 -2
  38. package/dist/step-manager.d.ts.map +1 -1
  39. package/dist/step-manager.js +174 -15
  40. package/dist/telemetry/adapter.d.ts +85 -0
  41. package/dist/telemetry/adapter.d.ts.map +1 -0
  42. package/dist/telemetry/adapter.js +128 -0
  43. package/dist/telemetry/index.d.ts +5 -0
  44. package/dist/telemetry/index.d.ts.map +1 -0
  45. package/dist/telemetry/index.js +4 -0
  46. package/dist/telemetry/local.d.ts +21 -0
  47. package/dist/telemetry/local.d.ts.map +1 -0
  48. package/dist/telemetry/local.js +180 -0
  49. package/dist/telemetry/noop.d.ts +16 -0
  50. package/dist/telemetry/noop.d.ts.map +1 -0
  51. package/dist/telemetry/noop.js +39 -0
  52. package/dist/telemetry/opentelemetry.d.ts +24 -0
  53. package/dist/telemetry/opentelemetry.d.ts.map +1 -0
  54. package/dist/telemetry/opentelemetry.js +202 -0
  55. package/migrations/postgres/20260117231749_clumsy_penance/migration.sql +3 -0
  56. package/migrations/postgres/20260117231749_clumsy_penance/snapshot.json +988 -0
  57. package/migrations/postgres/20260118202533_wealthy_mysterio/migration.sql +24 -0
  58. package/migrations/postgres/20260118202533_wealthy_mysterio/snapshot.json +1362 -0
  59. package/package.json +6 -4
  60. package/src/action-job.ts +35 -0
  61. package/src/action-manager.ts +5 -0
  62. package/src/action.ts +199 -0
  63. package/src/adapters/adapter.ts +151 -0
  64. package/src/adapters/postgres/base.ts +342 -23
  65. package/src/adapters/postgres/schema.default.ts +2 -2
  66. package/src/adapters/postgres/schema.ts +49 -1
  67. package/src/adapters/schemas.ts +81 -5
  68. package/src/client.ts +78 -0
  69. package/src/errors.ts +45 -1
  70. package/src/index.ts +10 -2
  71. package/src/server.ts +163 -8
  72. package/src/step-manager.ts +293 -13
  73. package/src/telemetry/adapter.ts +468 -0
  74. package/src/telemetry/index.ts +17 -0
  75. package/src/telemetry/local.ts +336 -0
  76. package/src/telemetry/noop.ts +95 -0
  77. package/src/telemetry/opentelemetry.ts +310 -0
@@ -64,6 +64,7 @@ export class PostgresBaseAdapter extends Adapter {
64
64
  status: JOB_STATUS_COMPLETED,
65
65
  output,
66
66
  finished_at: sql `now()`,
67
+ updated_at: sql `now()`,
67
68
  })
68
69
  .where(and(eq(this.tables.jobsTable.id, jobId), eq(this.tables.jobsTable.status, JOB_STATUS_ACTIVE), eq(this.tables.jobsTable.client_id, this.id), gt(this.tables.jobsTable.expires_at, sql `now()`)))
69
70
  .returning({ id: this.tables.jobsTable.id });
@@ -76,6 +77,7 @@ export class PostgresBaseAdapter extends Adapter {
76
77
  status: JOB_STATUS_FAILED,
77
78
  error,
78
79
  finished_at: sql `now()`,
80
+ updated_at: sql `now()`,
79
81
  })
80
82
  .where(and(eq(this.tables.jobsTable.id, jobId), eq(this.tables.jobsTable.status, JOB_STATUS_ACTIVE), eq(this.tables.jobsTable.client_id, this.id)))
81
83
  .returning({ id: this.tables.jobsTable.id });
@@ -87,6 +89,7 @@ export class PostgresBaseAdapter extends Adapter {
87
89
  .set({
88
90
  status: JOB_STATUS_CANCELLED,
89
91
  finished_at: sql `now()`,
92
+ updated_at: sql `now()`,
90
93
  })
91
94
  .where(and(eq(this.tables.jobsTable.id, jobId), or(eq(this.tables.jobsTable.status, JOB_STATUS_ACTIVE), eq(this.tables.jobsTable.status, JOB_STATUS_CREATED))))
92
95
  .returning({ id: this.tables.jobsTable.id });
@@ -166,6 +169,173 @@ export class PostgresBaseAdapter extends Adapter {
166
169
  }
167
170
  return result[0].id;
168
171
  }
172
+ async _timeTravelJob({ jobId, stepId }) {
173
+ const result = this._map(await this.db.execute(sql `
174
+ WITH RECURSIVE
175
+ -- Lock and validate the job
176
+ locked_job AS (
177
+ SELECT j.id
178
+ FROM ${this.tables.jobsTable} j
179
+ WHERE j.id = ${jobId}
180
+ AND j.status IN (${JOB_STATUS_COMPLETED}, ${JOB_STATUS_FAILED}, ${JOB_STATUS_CANCELLED})
181
+ FOR UPDATE OF j
182
+ ),
183
+ -- Validate target step exists and belongs to job
184
+ target_step AS (
185
+ SELECT s.id, s.parent_step_id, s.created_at
186
+ FROM ${this.tables.jobStepsTable} s
187
+ WHERE s.id = ${stepId}
188
+ AND s.job_id = ${jobId}
189
+ AND EXISTS (SELECT 1 FROM locked_job)
190
+ ),
191
+ -- Find all ancestor steps recursively (from target up to root)
192
+ ancestors AS (
193
+ SELECT s.id, s.parent_step_id, 0 AS depth
194
+ FROM ${this.tables.jobStepsTable} s
195
+ WHERE s.id = (SELECT parent_step_id FROM target_step)
196
+ AND EXISTS (SELECT 1 FROM target_step)
197
+ UNION ALL
198
+ SELECT s.id, s.parent_step_id, a.depth + 1
199
+ FROM ${this.tables.jobStepsTable} s
200
+ INNER JOIN ancestors a ON s.id = a.parent_step_id
201
+ ),
202
+ -- Steps to keep: completed steps created before target + completed parallel siblings of target and ancestors + their descendants
203
+ parallel_siblings AS (
204
+ -- Completed parallel siblings of target step
205
+ SELECT s.id
206
+ FROM ${this.tables.jobStepsTable} s
207
+ CROSS JOIN target_step ts
208
+ WHERE s.job_id = ${jobId}
209
+ AND s.id != ts.id
210
+ AND s.branch = true
211
+ AND s.status = ${STEP_STATUS_COMPLETED}
212
+ AND (
213
+ (s.parent_step_id IS NULL AND ts.parent_step_id IS NULL)
214
+ OR s.parent_step_id = ts.parent_step_id
215
+ )
216
+ UNION
217
+ -- Completed parallel siblings of each ancestor
218
+ SELECT s.id
219
+ FROM ${this.tables.jobStepsTable} s
220
+ INNER JOIN ancestors a ON (
221
+ (s.parent_step_id IS NULL AND a.parent_step_id IS NULL)
222
+ OR s.parent_step_id = a.parent_step_id
223
+ )
224
+ WHERE s.job_id = ${jobId}
225
+ AND s.id NOT IN (SELECT id FROM ancestors)
226
+ AND s.branch = true
227
+ AND s.status = ${STEP_STATUS_COMPLETED}
228
+ ),
229
+ -- Find all descendants of parallel siblings (to keep their children too)
230
+ parallel_descendants AS (
231
+ SELECT s.id
232
+ FROM ${this.tables.jobStepsTable} s
233
+ WHERE s.id IN (SELECT id FROM parallel_siblings)
234
+ UNION ALL
235
+ SELECT s.id
236
+ FROM ${this.tables.jobStepsTable} s
237
+ INNER JOIN parallel_descendants pd ON s.parent_step_id = pd.id
238
+ WHERE s.job_id = ${jobId}
239
+ ),
240
+ steps_to_keep AS (
241
+ -- Steps created before target that are completed (non-ancestor, non-target)
242
+ SELECT s.id
243
+ FROM ${this.tables.jobStepsTable} s
244
+ CROSS JOIN target_step ts
245
+ WHERE s.job_id = ${jobId}
246
+ AND s.created_at < ts.created_at
247
+ AND s.status = ${STEP_STATUS_COMPLETED}
248
+ AND s.id NOT IN (SELECT id FROM ancestors)
249
+ AND s.id != ts.id
250
+ UNION
251
+ -- All parallel siblings and their descendants
252
+ SELECT id FROM parallel_descendants
253
+ ),
254
+ -- Calculate time offset: shift preserved steps to start from "now"
255
+ time_offset AS (
256
+ SELECT
257
+ now() - MIN(s.started_at) AS offset_interval
258
+ FROM ${this.tables.jobStepsTable} s
259
+ WHERE s.id IN (SELECT id FROM steps_to_keep)
260
+ ),
261
+ -- Shift times of preserved steps to align with current time (only started_at/finished_at, NOT created_at to preserve ordering)
262
+ shift_preserved_times AS (
263
+ UPDATE ${this.tables.jobStepsTable}
264
+ SET
265
+ started_at = started_at + (SELECT offset_interval FROM time_offset),
266
+ finished_at = CASE
267
+ WHEN finished_at IS NOT NULL
268
+ THEN finished_at + (SELECT offset_interval FROM time_offset)
269
+ ELSE NULL
270
+ END,
271
+ updated_at = now()
272
+ WHERE id IN (SELECT id FROM steps_to_keep)
273
+ AND (SELECT offset_interval FROM time_offset) IS NOT NULL
274
+ RETURNING id
275
+ ),
276
+ -- Delete steps that are not in the keep list and are not ancestors/target
277
+ deleted_steps AS (
278
+ DELETE FROM ${this.tables.jobStepsTable}
279
+ WHERE job_id = ${jobId}
280
+ AND id NOT IN (SELECT id FROM steps_to_keep)
281
+ AND id NOT IN (SELECT id FROM ancestors)
282
+ AND id != (SELECT id FROM target_step)
283
+ RETURNING id
284
+ ),
285
+ -- Reset ancestor steps to active
286
+ reset_ancestors AS (
287
+ UPDATE ${this.tables.jobStepsTable}
288
+ SET
289
+ status = ${STEP_STATUS_ACTIVE},
290
+ output = NULL,
291
+ error = NULL,
292
+ finished_at = NULL,
293
+ started_at = now(),
294
+ expires_at = now() + (timeout_ms || ' milliseconds')::interval,
295
+ retries_count = 0,
296
+ delayed_ms = NULL,
297
+ history_failed_attempts = '{}'::jsonb,
298
+ updated_at = now()
299
+ WHERE id IN (SELECT id FROM ancestors)
300
+ RETURNING id
301
+ ),
302
+ -- Reset target step to active
303
+ reset_target AS (
304
+ UPDATE ${this.tables.jobStepsTable}
305
+ SET
306
+ status = ${STEP_STATUS_ACTIVE},
307
+ output = NULL,
308
+ error = NULL,
309
+ finished_at = NULL,
310
+ started_at = now(),
311
+ expires_at = now() + (timeout_ms || ' milliseconds')::interval,
312
+ retries_count = 0,
313
+ delayed_ms = NULL,
314
+ history_failed_attempts = '{}'::jsonb,
315
+ updated_at = now()
316
+ WHERE id = (SELECT id FROM target_step)
317
+ RETURNING id
318
+ ),
319
+ -- Reset job to created status
320
+ reset_job AS (
321
+ UPDATE ${this.tables.jobsTable}
322
+ SET
323
+ status = ${JOB_STATUS_CREATED},
324
+ output = NULL,
325
+ error = NULL,
326
+ started_at = NULL,
327
+ finished_at = NULL,
328
+ client_id = NULL,
329
+ expires_at = NULL,
330
+ updated_at = now()
331
+ WHERE id = ${jobId}
332
+ AND EXISTS (SELECT 1 FROM target_step)
333
+ RETURNING id
334
+ )
335
+ SELECT EXISTS(SELECT 1 FROM reset_job) AS success
336
+ `));
337
+ return result.length > 0 && result[0].success === true;
338
+ }
169
339
  async _deleteJob({ jobId }) {
170
340
  const result = await this.db
171
341
  .delete(this.tables.jobsTable)
@@ -271,7 +441,8 @@ export class PostgresBaseAdapter extends Adapter {
271
441
  SET status = ${JOB_STATUS_ACTIVE},
272
442
  started_at = now(),
273
443
  expires_at = now() + (timeout_ms || ' milliseconds')::interval,
274
- client_id = ${this.id}
444
+ client_id = ${this.id},
445
+ updated_at = now()
275
446
  FROM verify_concurrency vc
276
447
  WHERE j.id = vc.id
277
448
  AND vc.current_active < vc.concurrency_limit -- Final concurrency check using job's concurrency limit
@@ -337,7 +508,8 @@ export class PostgresBaseAdapter extends Adapter {
337
508
  expires_at = NULL,
338
509
  finished_at = NULL,
339
510
  output = NULL,
340
- error = NULL
511
+ error = NULL,
512
+ updated_at = now()
341
513
  WHERE EXISTS (SELECT 1 FROM locked_jobs lj WHERE lj.id = j.id)
342
514
  RETURNING id, checksum
343
515
  ),
@@ -355,7 +527,7 @@ export class PostgresBaseAdapter extends Adapter {
355
527
  }
356
528
  return 0;
357
529
  }
358
- async _createOrRecoverJobStep({ jobId, name, timeoutMs, retriesLimit, }) {
530
+ async _createOrRecoverJobStep({ jobId, name, timeoutMs, retriesLimit, parentStepId, parallel = false, }) {
359
531
  const [result] = this._map(await this.db.execute(sql `
360
532
  WITH job_check AS (
361
533
  SELECT j.id
@@ -373,6 +545,8 @@ export class PostgresBaseAdapter extends Adapter {
373
545
  upserted_step AS (
374
546
  INSERT INTO ${this.tables.jobStepsTable} (
375
547
  job_id,
548
+ parent_step_id,
549
+ branch,
376
550
  name,
377
551
  timeout_ms,
378
552
  retries_limit,
@@ -384,6 +558,8 @@ export class PostgresBaseAdapter extends Adapter {
384
558
  )
385
559
  SELECT
386
560
  ${jobId},
561
+ ${parentStepId},
562
+ ${parallel},
387
563
  ${name},
388
564
  ${timeoutMs},
389
565
  ${retriesLimit},
@@ -452,6 +628,7 @@ export class PostgresBaseAdapter extends Adapter {
452
628
  status: STEP_STATUS_COMPLETED,
453
629
  output,
454
630
  finished_at: sql `now()`,
631
+ updated_at: sql `now()`,
455
632
  })
456
633
  .from(this.tables.jobsTable)
457
634
  .where(and(eq(this.tables.jobStepsTable.job_id, this.tables.jobsTable.id), eq(this.tables.jobStepsTable.id, stepId), eq(this.tables.jobStepsTable.status, STEP_STATUS_ACTIVE), eq(this.tables.jobsTable.status, JOB_STATUS_ACTIVE), or(isNull(this.tables.jobsTable.expires_at), gt(this.tables.jobsTable.expires_at, sql `now()`))))
@@ -465,6 +642,7 @@ export class PostgresBaseAdapter extends Adapter {
465
642
  status: STEP_STATUS_FAILED,
466
643
  error,
467
644
  finished_at: sql `now()`,
645
+ updated_at: sql `now()`,
468
646
  })
469
647
  .from(this.tables.jobsTable)
470
648
  .where(and(eq(this.tables.jobStepsTable.job_id, this.tables.jobsTable.id), eq(this.tables.jobStepsTable.id, stepId), eq(this.tables.jobStepsTable.status, STEP_STATUS_ACTIVE), eq(this.tables.jobsTable.status, JOB_STATUS_ACTIVE)))
@@ -488,6 +666,7 @@ export class PostgresBaseAdapter extends Adapter {
488
666
  'delayedMs', ${delayMs}::integer
489
667
  )
490
668
  )`,
669
+ updated_at: sql `now()`,
491
670
  })
492
671
  .from(jobsTable)
493
672
  .where(and(eq(jobStepsTable.job_id, jobsTable.id), eq(jobStepsTable.id, stepId), eq(jobStepsTable.status, STEP_STATUS_ACTIVE), eq(jobsTable.status, JOB_STATUS_ACTIVE)))
@@ -500,6 +679,7 @@ export class PostgresBaseAdapter extends Adapter {
500
679
  .set({
501
680
  status: STEP_STATUS_CANCELLED,
502
681
  finished_at: sql `now()`,
682
+ updated_at: sql `now()`,
503
683
  })
504
684
  .from(this.tables.jobsTable)
505
685
  .where(and(eq(this.tables.jobStepsTable.job_id, this.tables.jobsTable.id), eq(this.tables.jobStepsTable.id, stepId), eq(this.tables.jobStepsTable.status, STEP_STATUS_ACTIVE), or(eq(this.tables.jobsTable.status, JOB_STATUS_ACTIVE), eq(this.tables.jobsTable.status, JOB_STATUS_CANCELLED))))
@@ -531,7 +711,7 @@ export class PostgresBaseAdapter extends Adapter {
531
711
  return job ?? null;
532
712
  }
533
713
  async _getJobSteps(options) {
534
- const { jobId, page = 1, pageSize = 10, search } = options;
714
+ const { jobId, search } = options;
535
715
  const jobStepsTable = this.tables.jobStepsTable;
536
716
  const fuzzySearch = search?.trim();
537
717
  const where = and(eq(jobStepsTable.job_id, jobId), fuzzySearch && fuzzySearch.length > 0
@@ -539,19 +719,12 @@ export class PostgresBaseAdapter extends Adapter {
539
719
  : undefined, options.updatedAfter
540
720
  ? sql `date_trunc('milliseconds', ${jobStepsTable.updated_at}) > ${options.updatedAfter.toISOString()}::timestamptz`
541
721
  : undefined);
542
- const total = await this.db.$count(jobStepsTable, where);
543
- if (!total) {
544
- return {
545
- steps: [],
546
- total: 0,
547
- page,
548
- pageSize,
549
- };
550
- }
551
722
  const steps = await this.db
552
723
  .select({
553
724
  id: jobStepsTable.id,
554
725
  jobId: jobStepsTable.job_id,
726
+ parentStepId: jobStepsTable.parent_step_id,
727
+ parallel: jobStepsTable.parallel,
555
728
  name: jobStepsTable.name,
556
729
  status: jobStepsTable.status,
557
730
  error: jobStepsTable.error,
@@ -568,14 +741,10 @@ export class PostgresBaseAdapter extends Adapter {
568
741
  })
569
742
  .from(jobStepsTable)
570
743
  .where(where)
571
- .orderBy(asc(jobStepsTable.created_at))
572
- .limit(pageSize)
573
- .offset((page - 1) * pageSize);
744
+ .orderBy(asc(jobStepsTable.created_at));
574
745
  return {
575
746
  steps,
576
- total,
577
- page,
578
- pageSize,
747
+ total: steps.length,
579
748
  };
580
749
  }
581
750
  _buildJobsWhereClause(filters) {
@@ -686,6 +855,8 @@ export class PostgresBaseAdapter extends Adapter {
686
855
  .select({
687
856
  id: this.tables.jobStepsTable.id,
688
857
  jobId: this.tables.jobStepsTable.job_id,
858
+ parentStepId: this.tables.jobStepsTable.parent_step_id,
859
+ parallel: this.tables.jobStepsTable.parallel,
689
860
  name: this.tables.jobStepsTable.name,
690
861
  output: this.tables.jobStepsTable.output,
691
862
  status: this.tables.jobStepsTable.status,
@@ -759,6 +930,85 @@ export class PostgresBaseAdapter extends Adapter {
759
930
  })),
760
931
  };
761
932
  }
933
+ async _insertMetrics(metrics) {
934
+ if (metrics.length === 0) {
935
+ return 0;
936
+ }
937
+ const values = metrics.map((m) => ({
938
+ job_id: m.jobId,
939
+ step_id: m.stepId ?? null,
940
+ name: m.name,
941
+ value: m.value,
942
+ attributes: m.attributes ?? {},
943
+ type: m.type,
944
+ }));
945
+ const result = await this.db
946
+ .insert(this.tables.metricsTable)
947
+ .values(values)
948
+ .returning({ id: this.tables.metricsTable.id });
949
+ return result.length;
950
+ }
951
+ async _getMetrics(options) {
952
+ const metricsTable = this.tables.metricsTable;
953
+ const filters = options.filters ?? {};
954
+ const where = this._buildMetricsWhereClause(options.jobId, options.stepId, filters);
955
+ const sortInput = options.sort ?? { field: 'timestamp', order: 'desc' };
956
+ const sortFieldMap = {
957
+ name: metricsTable.name,
958
+ value: metricsTable.value,
959
+ timestamp: metricsTable.timestamp,
960
+ createdAt: metricsTable.created_at,
961
+ };
962
+ const total = await this.db.$count(metricsTable, where);
963
+ if (!total) {
964
+ return {
965
+ metrics: [],
966
+ total: 0,
967
+ };
968
+ }
969
+ const sortField = sortFieldMap[sortInput.field];
970
+ const orderByClause = sortInput.order === 'asc' ? asc(sortField) : desc(sortField);
971
+ const metrics = await this.db
972
+ .select({
973
+ id: metricsTable.id,
974
+ jobId: metricsTable.job_id,
975
+ stepId: metricsTable.step_id,
976
+ name: metricsTable.name,
977
+ value: metricsTable.value,
978
+ attributes: metricsTable.attributes,
979
+ type: metricsTable.type,
980
+ timestamp: metricsTable.timestamp,
981
+ createdAt: metricsTable.created_at,
982
+ })
983
+ .from(metricsTable)
984
+ .where(where)
985
+ .orderBy(orderByClause);
986
+ return {
987
+ metrics,
988
+ total,
989
+ };
990
+ }
991
+ async _deleteMetrics(options) {
992
+ const result = await this.db
993
+ .delete(this.tables.metricsTable)
994
+ .where(eq(this.tables.metricsTable.job_id, options.jobId))
995
+ .returning({ id: this.tables.metricsTable.id });
996
+ return result.length;
997
+ }
998
+ _buildMetricsWhereClause(jobId, stepId, filters) {
999
+ const metricsTable = this.tables.metricsTable;
1000
+ return and(jobId ? eq(metricsTable.job_id, jobId) : undefined, stepId ? eq(metricsTable.step_id, stepId) : undefined, filters?.name
1001
+ ? Array.isArray(filters.name)
1002
+ ? or(...filters.name.map((n) => ilike(metricsTable.name, `%${n}%`)))
1003
+ : ilike(metricsTable.name, `%${filters.name}%`)
1004
+ : undefined, filters?.type
1005
+ ? inArray(metricsTable.type, Array.isArray(filters.type) ? filters.type : [filters.type])
1006
+ : undefined, filters?.timestampRange && filters.timestampRange.length === 2
1007
+ ? between(metricsTable.timestamp, filters.timestampRange[0], filters.timestampRange[1])
1008
+ : undefined, ...(filters?.attributesFilter && Object.keys(filters.attributesFilter).length > 0
1009
+ ? this.#buildJsonbWhereConditions(filters.attributesFilter, metricsTable.attributes)
1010
+ : []));
1011
+ }
762
1012
  #buildJsonbWhereConditions(filter, jsonbColumn) {
763
1013
  const conditions = [];
764
1014
  for (const [key, value] of Object.entries(filter)) {