@voyant-travel/reporting 0.1.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.
package/dist/schema.js ADDED
@@ -0,0 +1,49 @@
1
+ import { typeId, typeIdRef } from "@voyant-travel/db/lib/typeid-column";
2
+ import { index, integer, jsonb, pgEnum, pgTable, text, timestamp, uniqueIndex, } from "drizzle-orm/pg-core";
3
+ export const reportRunStatusEnum = pgEnum("report_run_status", ["running", "succeeded", "failed"]);
4
+ export const reportDefinitions = pgTable("report_definitions", {
5
+ id: typeId("report_definitions"),
6
+ name: text("name").notNull(),
7
+ description: text("description"),
8
+ sourceTemplateId: text("source_template_id"),
9
+ sourceTemplateVersion: integer("source_template_version"),
10
+ draft: jsonb("draft").$type().notNull(),
11
+ revision: integer("revision").notNull().default(1),
12
+ createdByUserId: text("created_by_user_id"),
13
+ updatedByUserId: text("updated_by_user_id"),
14
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
15
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
16
+ }, (table) => [index("idx_report_definitions_updated").on(table.updatedAt)]);
17
+ export const reportVersions = pgTable("report_versions", {
18
+ id: typeId("report_versions"),
19
+ reportDefinitionId: typeIdRef("report_definition_id")
20
+ .notNull()
21
+ .references(() => reportDefinitions.id, { onDelete: "cascade" }),
22
+ version: integer("version").notNull(),
23
+ name: text("name").notNull(),
24
+ description: text("description"),
25
+ definitionRevision: integer("definition_revision").notNull(),
26
+ snapshot: jsonb("snapshot").$type().notNull(),
27
+ createdByUserId: text("created_by_user_id"),
28
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
29
+ }, (table) => [
30
+ uniqueIndex("uidx_report_versions_definition_version").on(table.reportDefinitionId, table.version),
31
+ index("idx_report_versions_definition_created").on(table.reportDefinitionId, table.createdAt),
32
+ ]);
33
+ export const reportRuns = pgTable("report_runs", {
34
+ id: typeId("report_runs"),
35
+ reportVersionId: typeIdRef("report_version_id")
36
+ .notNull()
37
+ .references(() => reportVersions.id, { onDelete: "restrict" }),
38
+ status: reportRunStatusEnum("status").notNull().default("running"),
39
+ parameters: jsonb("parameters").$type().notNull(),
40
+ output: jsonb("output").$type(),
41
+ error: text("error"),
42
+ triggeredByUserId: text("triggered_by_user_id"),
43
+ startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
44
+ completedAt: timestamp("completed_at", { withTimezone: true }),
45
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
46
+ }, (table) => [
47
+ index("idx_report_runs_version_created").on(table.reportVersionId, table.createdAt),
48
+ index("idx_report_runs_status_started").on(table.status, table.startedAt),
49
+ ]);
@@ -0,0 +1,509 @@
1
+ import type { createReportDefinitionSchema, ReportParameters, updateReportDefinitionSchema } from "@voyant-travel/reporting-contracts";
2
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
+ import type { z } from "zod";
4
+ import type { ReportingRegistry } from "./registry.js";
5
+ import { type ReportDefinitionRow, type ReportRunOutput } from "./schema.js";
6
+ export type CreateReportDefinitionInput = z.infer<typeof createReportDefinitionSchema>;
7
+ export type UpdateReportDefinitionInput = z.infer<typeof updateReportDefinitionSchema>;
8
+ export declare class ReportDefinitionRevisionConflictError extends Error {
9
+ constructor();
10
+ }
11
+ export declare class ReportingRecordNotFoundError extends Error {
12
+ constructor(record: string);
13
+ }
14
+ export declare class ReportDefinitionRetentionConflictError extends Error {
15
+ constructor();
16
+ }
17
+ export declare function createReportingService(registry: ReportingRegistry): {
18
+ list(db: PostgresJsDatabase, input: {
19
+ limit: number;
20
+ offset: number;
21
+ }): Promise<{
22
+ limit: number;
23
+ offset: number;
24
+ data: {
25
+ id: string;
26
+ name: string;
27
+ description: string | null;
28
+ sourceTemplateId: string | null;
29
+ sourceTemplateVersion: number | null;
30
+ draft: {
31
+ parameters: Record<string, string | number | boolean | (string | number | boolean | null)[] | null>;
32
+ widgets: {
33
+ id: string;
34
+ source: {
35
+ kind: "preset";
36
+ widgetId: string;
37
+ version?: number | undefined;
38
+ } | {
39
+ kind: "custom";
40
+ definition: {
41
+ id: string;
42
+ version: number;
43
+ label: string;
44
+ query: {
45
+ dataset: {
46
+ id: string;
47
+ version?: number | undefined;
48
+ };
49
+ select: ({
50
+ kind: "field";
51
+ field: string;
52
+ as?: string | undefined;
53
+ } | {
54
+ kind: "aggregate";
55
+ operation: "minimum" | "maximum" | "count" | "countDistinct" | "sum" | "average";
56
+ as: string;
57
+ field?: string | undefined;
58
+ })[];
59
+ filters: {
60
+ field: string;
61
+ operator: "in" | "equal" | "notEqual" | "notIn" | "greaterThan" | "greaterThanOrEqual" | "lessThan" | "lessThanOrEqual" | "between" | "contains" | "isNull" | "isNotNull";
62
+ value?: {
63
+ kind: "literal";
64
+ value: string | number | boolean | (string | number | boolean | null)[] | null;
65
+ } | {
66
+ kind: "parameter";
67
+ name: string;
68
+ } | undefined;
69
+ }[];
70
+ groupBy: {
71
+ field: string;
72
+ timeGrain?: "day" | "week" | "month" | "quarter" | "year" | undefined;
73
+ }[];
74
+ orderBy: {
75
+ by: string;
76
+ direction: "ascending" | "descending";
77
+ }[];
78
+ limit?: number | undefined;
79
+ };
80
+ visualization: {
81
+ type: "kpi" | "table" | "line" | "bar" | "pie";
82
+ options: Record<string, unknown>;
83
+ };
84
+ defaultSize: {
85
+ width: number;
86
+ height: number;
87
+ };
88
+ description?: string | undefined;
89
+ minimumSize?: {
90
+ width: number;
91
+ height: number;
92
+ } | undefined;
93
+ maximumSize?: {
94
+ width: number;
95
+ height: number;
96
+ } | undefined;
97
+ };
98
+ };
99
+ layout: {
100
+ width: number;
101
+ height: number;
102
+ x: number;
103
+ y: number;
104
+ };
105
+ title?: string | undefined;
106
+ }[];
107
+ };
108
+ revision: number;
109
+ createdByUserId: string | null;
110
+ updatedByUserId: string | null;
111
+ createdAt: Date;
112
+ updatedAt: Date;
113
+ }[];
114
+ total: number;
115
+ }>;
116
+ get(db: PostgresJsDatabase, id: string): Promise<ReportDefinitionRow | null>;
117
+ create(db: PostgresJsDatabase, input: CreateReportDefinitionInput, actorId?: string): Promise<{
118
+ id: string;
119
+ description: string | null;
120
+ name: string;
121
+ sourceTemplateId: string | null;
122
+ sourceTemplateVersion: number | null;
123
+ draft: {
124
+ parameters: Record<string, string | number | boolean | (string | number | boolean | null)[] | null>;
125
+ widgets: {
126
+ id: string;
127
+ source: {
128
+ kind: "preset";
129
+ widgetId: string;
130
+ version?: number | undefined;
131
+ } | {
132
+ kind: "custom";
133
+ definition: {
134
+ id: string;
135
+ version: number;
136
+ label: string;
137
+ query: {
138
+ dataset: {
139
+ id: string;
140
+ version?: number | undefined;
141
+ };
142
+ select: ({
143
+ kind: "field";
144
+ field: string;
145
+ as?: string | undefined;
146
+ } | {
147
+ kind: "aggregate";
148
+ operation: "minimum" | "maximum" | "count" | "countDistinct" | "sum" | "average";
149
+ as: string;
150
+ field?: string | undefined;
151
+ })[];
152
+ filters: {
153
+ field: string;
154
+ operator: "in" | "equal" | "notEqual" | "notIn" | "greaterThan" | "greaterThanOrEqual" | "lessThan" | "lessThanOrEqual" | "between" | "contains" | "isNull" | "isNotNull";
155
+ value?: {
156
+ kind: "literal";
157
+ value: string | number | boolean | (string | number | boolean | null)[] | null;
158
+ } | {
159
+ kind: "parameter";
160
+ name: string;
161
+ } | undefined;
162
+ }[];
163
+ groupBy: {
164
+ field: string;
165
+ timeGrain?: "day" | "week" | "month" | "quarter" | "year" | undefined;
166
+ }[];
167
+ orderBy: {
168
+ by: string;
169
+ direction: "ascending" | "descending";
170
+ }[];
171
+ limit?: number | undefined;
172
+ };
173
+ visualization: {
174
+ type: "kpi" | "table" | "line" | "bar" | "pie";
175
+ options: Record<string, unknown>;
176
+ };
177
+ defaultSize: {
178
+ width: number;
179
+ height: number;
180
+ };
181
+ description?: string | undefined;
182
+ minimumSize?: {
183
+ width: number;
184
+ height: number;
185
+ } | undefined;
186
+ maximumSize?: {
187
+ width: number;
188
+ height: number;
189
+ } | undefined;
190
+ };
191
+ };
192
+ layout: {
193
+ width: number;
194
+ height: number;
195
+ x: number;
196
+ y: number;
197
+ };
198
+ title?: string | undefined;
199
+ }[];
200
+ };
201
+ revision: number;
202
+ createdByUserId: string | null;
203
+ updatedByUserId: string | null;
204
+ createdAt: Date;
205
+ updatedAt: Date;
206
+ }>;
207
+ instantiateTemplate(db: PostgresJsDatabase, input: {
208
+ templateId: string;
209
+ version?: number;
210
+ name: string;
211
+ description?: string | null;
212
+ }, actorId?: string): Promise<{
213
+ id: string;
214
+ description: string | null;
215
+ name: string;
216
+ sourceTemplateId: string | null;
217
+ sourceTemplateVersion: number | null;
218
+ draft: {
219
+ parameters: Record<string, string | number | boolean | (string | number | boolean | null)[] | null>;
220
+ widgets: {
221
+ id: string;
222
+ source: {
223
+ kind: "preset";
224
+ widgetId: string;
225
+ version?: number | undefined;
226
+ } | {
227
+ kind: "custom";
228
+ definition: {
229
+ id: string;
230
+ version: number;
231
+ label: string;
232
+ query: {
233
+ dataset: {
234
+ id: string;
235
+ version?: number | undefined;
236
+ };
237
+ select: ({
238
+ kind: "field";
239
+ field: string;
240
+ as?: string | undefined;
241
+ } | {
242
+ kind: "aggregate";
243
+ operation: "minimum" | "maximum" | "count" | "countDistinct" | "sum" | "average";
244
+ as: string;
245
+ field?: string | undefined;
246
+ })[];
247
+ filters: {
248
+ field: string;
249
+ operator: "in" | "equal" | "notEqual" | "notIn" | "greaterThan" | "greaterThanOrEqual" | "lessThan" | "lessThanOrEqual" | "between" | "contains" | "isNull" | "isNotNull";
250
+ value?: {
251
+ kind: "literal";
252
+ value: string | number | boolean | (string | number | boolean | null)[] | null;
253
+ } | {
254
+ kind: "parameter";
255
+ name: string;
256
+ } | undefined;
257
+ }[];
258
+ groupBy: {
259
+ field: string;
260
+ timeGrain?: "day" | "week" | "month" | "quarter" | "year" | undefined;
261
+ }[];
262
+ orderBy: {
263
+ by: string;
264
+ direction: "ascending" | "descending";
265
+ }[];
266
+ limit?: number | undefined;
267
+ };
268
+ visualization: {
269
+ type: "kpi" | "table" | "line" | "bar" | "pie";
270
+ options: Record<string, unknown>;
271
+ };
272
+ defaultSize: {
273
+ width: number;
274
+ height: number;
275
+ };
276
+ description?: string | undefined;
277
+ minimumSize?: {
278
+ width: number;
279
+ height: number;
280
+ } | undefined;
281
+ maximumSize?: {
282
+ width: number;
283
+ height: number;
284
+ } | undefined;
285
+ };
286
+ };
287
+ layout: {
288
+ width: number;
289
+ height: number;
290
+ x: number;
291
+ y: number;
292
+ };
293
+ title?: string | undefined;
294
+ }[];
295
+ };
296
+ revision: number;
297
+ createdByUserId: string | null;
298
+ updatedByUserId: string | null;
299
+ createdAt: Date;
300
+ updatedAt: Date;
301
+ }>;
302
+ update(db: PostgresJsDatabase, id: string, input: UpdateReportDefinitionInput, actorId?: string): Promise<{
303
+ id: string;
304
+ name: string;
305
+ description: string | null;
306
+ sourceTemplateId: string | null;
307
+ sourceTemplateVersion: number | null;
308
+ draft: {
309
+ parameters: Record<string, string | number | boolean | (string | number | boolean | null)[] | null>;
310
+ widgets: {
311
+ id: string;
312
+ source: {
313
+ kind: "preset";
314
+ widgetId: string;
315
+ version?: number | undefined;
316
+ } | {
317
+ kind: "custom";
318
+ definition: {
319
+ id: string;
320
+ version: number;
321
+ label: string;
322
+ query: {
323
+ dataset: {
324
+ id: string;
325
+ version?: number | undefined;
326
+ };
327
+ select: ({
328
+ kind: "field";
329
+ field: string;
330
+ as?: string | undefined;
331
+ } | {
332
+ kind: "aggregate";
333
+ operation: "minimum" | "maximum" | "count" | "countDistinct" | "sum" | "average";
334
+ as: string;
335
+ field?: string | undefined;
336
+ })[];
337
+ filters: {
338
+ field: string;
339
+ operator: "in" | "equal" | "notEqual" | "notIn" | "greaterThan" | "greaterThanOrEqual" | "lessThan" | "lessThanOrEqual" | "between" | "contains" | "isNull" | "isNotNull";
340
+ value?: {
341
+ kind: "literal";
342
+ value: string | number | boolean | (string | number | boolean | null)[] | null;
343
+ } | {
344
+ kind: "parameter";
345
+ name: string;
346
+ } | undefined;
347
+ }[];
348
+ groupBy: {
349
+ field: string;
350
+ timeGrain?: "day" | "week" | "month" | "quarter" | "year" | undefined;
351
+ }[];
352
+ orderBy: {
353
+ by: string;
354
+ direction: "ascending" | "descending";
355
+ }[];
356
+ limit?: number | undefined;
357
+ };
358
+ visualization: {
359
+ type: "kpi" | "table" | "line" | "bar" | "pie";
360
+ options: Record<string, unknown>;
361
+ };
362
+ defaultSize: {
363
+ width: number;
364
+ height: number;
365
+ };
366
+ description?: string | undefined;
367
+ minimumSize?: {
368
+ width: number;
369
+ height: number;
370
+ } | undefined;
371
+ maximumSize?: {
372
+ width: number;
373
+ height: number;
374
+ } | undefined;
375
+ };
376
+ };
377
+ layout: {
378
+ width: number;
379
+ height: number;
380
+ x: number;
381
+ y: number;
382
+ };
383
+ title?: string | undefined;
384
+ }[];
385
+ };
386
+ revision: number;
387
+ createdByUserId: string | null;
388
+ updatedByUserId: string | null;
389
+ createdAt: Date;
390
+ updatedAt: Date;
391
+ }>;
392
+ remove(db: PostgresJsDatabase, id: string): Promise<boolean>;
393
+ createVersion(db: PostgresJsDatabase, reportDefinitionId: string, expectedRevision: number, actorId?: string): Promise<{
394
+ id: string;
395
+ description: string | null;
396
+ version: number;
397
+ name: string;
398
+ createdByUserId: string | null;
399
+ createdAt: Date;
400
+ reportDefinitionId: string;
401
+ definitionRevision: number;
402
+ snapshot: {
403
+ parameters: Record<string, string | number | boolean | (string | number | boolean | null)[] | null>;
404
+ widgets: {
405
+ id: string;
406
+ source: {
407
+ kind: "preset";
408
+ widgetId: string;
409
+ version?: number | undefined;
410
+ } | {
411
+ kind: "custom";
412
+ definition: {
413
+ id: string;
414
+ version: number;
415
+ label: string;
416
+ query: {
417
+ dataset: {
418
+ id: string;
419
+ version?: number | undefined;
420
+ };
421
+ select: ({
422
+ kind: "field";
423
+ field: string;
424
+ as?: string | undefined;
425
+ } | {
426
+ kind: "aggregate";
427
+ operation: "minimum" | "maximum" | "count" | "countDistinct" | "sum" | "average";
428
+ as: string;
429
+ field?: string | undefined;
430
+ })[];
431
+ filters: {
432
+ field: string;
433
+ operator: "in" | "equal" | "notEqual" | "notIn" | "greaterThan" | "greaterThanOrEqual" | "lessThan" | "lessThanOrEqual" | "between" | "contains" | "isNull" | "isNotNull";
434
+ value?: {
435
+ kind: "literal";
436
+ value: string | number | boolean | (string | number | boolean | null)[] | null;
437
+ } | {
438
+ kind: "parameter";
439
+ name: string;
440
+ } | undefined;
441
+ }[];
442
+ groupBy: {
443
+ field: string;
444
+ timeGrain?: "day" | "week" | "month" | "quarter" | "year" | undefined;
445
+ }[];
446
+ orderBy: {
447
+ by: string;
448
+ direction: "ascending" | "descending";
449
+ }[];
450
+ limit?: number | undefined;
451
+ };
452
+ visualization: {
453
+ type: "kpi" | "table" | "line" | "bar" | "pie";
454
+ options: Record<string, unknown>;
455
+ };
456
+ defaultSize: {
457
+ width: number;
458
+ height: number;
459
+ };
460
+ description?: string | undefined;
461
+ minimumSize?: {
462
+ width: number;
463
+ height: number;
464
+ } | undefined;
465
+ maximumSize?: {
466
+ width: number;
467
+ height: number;
468
+ } | undefined;
469
+ };
470
+ };
471
+ layout: {
472
+ width: number;
473
+ height: number;
474
+ x: number;
475
+ y: number;
476
+ };
477
+ title?: string | undefined;
478
+ }[];
479
+ };
480
+ }>;
481
+ runVersion(db: PostgresJsDatabase, versionId: string, parameters: ReportParameters, context: {
482
+ actorId?: string;
483
+ grantedScopes: readonly string[];
484
+ signal?: AbortSignal;
485
+ }): Promise<{
486
+ error: string | null;
487
+ output: ReportRunOutput | null;
488
+ id: string;
489
+ parameters: Record<string, string | number | boolean | (string | number | boolean | null)[] | null>;
490
+ createdAt: Date;
491
+ reportVersionId: string;
492
+ status: "succeeded" | "failed" | "running";
493
+ triggeredByUserId: string | null;
494
+ startedAt: Date;
495
+ completedAt: Date | null;
496
+ }>;
497
+ getRun(db: PostgresJsDatabase, id: string, grantedScopes: readonly string[]): Promise<{
498
+ id: string;
499
+ reportVersionId: string;
500
+ status: "succeeded" | "failed" | "running";
501
+ parameters: Record<string, string | number | boolean | (string | number | boolean | null)[] | null>;
502
+ output: ReportRunOutput | null;
503
+ error: string | null;
504
+ triggeredByUserId: string | null;
505
+ startedAt: Date;
506
+ completedAt: Date | null;
507
+ createdAt: Date;
508
+ } | null>;
509
+ };