@objectstack/plugin-reports 16.1.0 → 17.0.0-rc.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.
- package/CHANGELOG.md +462 -0
- package/dist/index.d.mts +36 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +57 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +57 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -5
- package/.turbo/turbo-build.log +0 -22
- package/src/index.ts +0 -34
- package/src/report-service.test.ts +0 -475
- package/src/report-service.ts +0 -628
- package/src/reports-plugin.ts +0 -144
- package/tsconfig.json +0 -10
package/src/reports-plugin.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { Plugin, PluginContext } from '@objectstack/core';
|
|
4
|
-
import {
|
|
5
|
-
SysSavedReport,
|
|
6
|
-
SysReportSchedule,
|
|
7
|
-
} from '@objectstack/platform-objects/audit';
|
|
8
|
-
import { ReportService, type ReportEngine, type ReportEmail } from './report-service.js';
|
|
9
|
-
|
|
10
|
-
export interface ReportsPluginOptions {
|
|
11
|
-
/**
|
|
12
|
-
* How often the dispatcher should poll `sys_report_schedule` for
|
|
13
|
-
* due rows. Defaults to 60 seconds — short enough to honour
|
|
14
|
-
* minute-grained schedules without flooding the DB.
|
|
15
|
-
*/
|
|
16
|
-
dispatchIntervalMs?: number;
|
|
17
|
-
/** Cap rows per report. Mirrors ReportServiceOptions.maxRows. */
|
|
18
|
-
maxRows?: number;
|
|
19
|
-
/** Disable the dispatcher tick entirely. */
|
|
20
|
-
disableDispatcher?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* ReportsServicePlugin — registers `sys_saved_report` /
|
|
25
|
-
* `sys_report_schedule`, the `reports` service, and the dispatcher
|
|
26
|
-
* loop that emails due schedules.
|
|
27
|
-
*
|
|
28
|
-
* The dispatcher uses `IJobService.schedule` when one is registered;
|
|
29
|
-
* otherwise it falls back to a plain `setInterval` so single-kernel
|
|
30
|
-
* deployments work without `service-job`.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```ts
|
|
34
|
-
* import { ReportsServicePlugin } from '@objectstack/plugin-reports';
|
|
35
|
-
*
|
|
36
|
-
* kernel.use(new ReportsServicePlugin({ dispatchIntervalMs: 60_000 }));
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export class ReportsServicePlugin implements Plugin {
|
|
40
|
-
name = 'com.objectstack.service.reports';
|
|
41
|
-
version = '1.0.0';
|
|
42
|
-
type = 'standard';
|
|
43
|
-
dependencies = ['com.objectstack.engine.objectql'];
|
|
44
|
-
|
|
45
|
-
private readonly options: ReportsPluginOptions;
|
|
46
|
-
private service?: ReportService;
|
|
47
|
-
private intervalHandle?: ReturnType<typeof setInterval>;
|
|
48
|
-
private jobName?: string;
|
|
49
|
-
private jobService?: any;
|
|
50
|
-
|
|
51
|
-
constructor(options: ReportsPluginOptions = {}) {
|
|
52
|
-
this.options = options;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async init(ctx: PluginContext): Promise<void> {
|
|
56
|
-
ctx.getService<{ register(m: any): void }>('manifest').register({
|
|
57
|
-
id: 'com.objectstack.service.reports',
|
|
58
|
-
name: 'Reports Service',
|
|
59
|
-
version: '1.0.0',
|
|
60
|
-
type: 'plugin',
|
|
61
|
-
scope: 'system',
|
|
62
|
-
defaultDatasource: 'cloud',
|
|
63
|
-
namespace: 'sys',
|
|
64
|
-
objects: [SysSavedReport, SysReportSchedule],
|
|
65
|
-
});
|
|
66
|
-
ctx.logger.info('ReportsServicePlugin: schemas registered');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async start(ctx: PluginContext): Promise<void> {
|
|
70
|
-
ctx.hook('kernel:ready', async () => {
|
|
71
|
-
let engine: any = null;
|
|
72
|
-
try { engine = ctx.getService<any>('objectql'); }
|
|
73
|
-
catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }
|
|
74
|
-
if (!engine) {
|
|
75
|
-
ctx.logger.warn('ReportsServicePlugin: no ObjectQL engine — service NOT registered');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let email: ReportEmail | undefined;
|
|
80
|
-
try { email = ctx.getService<any>('email'); } catch { /* email is optional */ }
|
|
81
|
-
if (!email) {
|
|
82
|
-
ctx.logger.warn('ReportsServicePlugin: no email service — schedules will fire without delivery');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
this.service = new ReportService({
|
|
86
|
-
engine: engine as ReportEngine,
|
|
87
|
-
email,
|
|
88
|
-
logger: ctx.logger,
|
|
89
|
-
maxRows: this.options.maxRows,
|
|
90
|
-
// Scheduled reports run under the owner's resolved RLS context, not a
|
|
91
|
-
// system bypass (#2980). No owner-context resolver is wired yet — that
|
|
92
|
-
// is the reports-surface consumer of ADR-0073's user-less identity
|
|
93
|
-
// resolution (M2) — so until it lands, scheduled runs FAIL CLOSED
|
|
94
|
-
// (skipped + marked failed) rather than exfiltrate. Interactive runs
|
|
95
|
-
// (run/runAdHoc) are unaffected: they carry the caller's context.
|
|
96
|
-
resolveOwnerContext: undefined,
|
|
97
|
-
});
|
|
98
|
-
ctx.registerService('reports', this.service);
|
|
99
|
-
|
|
100
|
-
if (this.options.disableDispatcher) {
|
|
101
|
-
ctx.logger.info('ReportsServicePlugin: dispatcher disabled (disableDispatcher=true)');
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const intervalMs = Math.max(5_000, this.options.dispatchIntervalMs ?? 60_000);
|
|
106
|
-
|
|
107
|
-
// Prefer the platform job service when available — it lets ops
|
|
108
|
-
// see report dispatch alongside every other scheduled job.
|
|
109
|
-
try {
|
|
110
|
-
const job = ctx.getService<any>('job');
|
|
111
|
-
if (job && typeof job.schedule === 'function') {
|
|
112
|
-
this.jobService = job;
|
|
113
|
-
this.jobName = 'reports.dispatch';
|
|
114
|
-
await job.schedule(this.jobName, { type: 'interval', intervalMs }, async () => {
|
|
115
|
-
try { await this.service?.dispatchDue(); }
|
|
116
|
-
catch (err) { ctx.logger.warn('ReportsServicePlugin: dispatch tick failed', err as any); }
|
|
117
|
-
});
|
|
118
|
-
ctx.logger.info('ReportsServicePlugin: dispatcher registered with job service', { intervalMs });
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
} catch { /* fall through to setInterval */ }
|
|
122
|
-
|
|
123
|
-
this.intervalHandle = setInterval(() => {
|
|
124
|
-
this.service?.dispatchDue().catch(err => {
|
|
125
|
-
ctx.logger.warn('ReportsServicePlugin: dispatch tick failed', err);
|
|
126
|
-
});
|
|
127
|
-
}, intervalMs);
|
|
128
|
-
// Don't keep Node alive purely for the dispatcher — common
|
|
129
|
-
// mistake in tests / serverless. unref is a no-op in some
|
|
130
|
-
// runtimes which is fine.
|
|
131
|
-
(this.intervalHandle as any)?.unref?.();
|
|
132
|
-
ctx.logger.info('ReportsServicePlugin: dispatcher registered (setInterval fallback)', { intervalMs });
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async stop(ctx: PluginContext): Promise<void> {
|
|
137
|
-
if (this.intervalHandle) clearInterval(this.intervalHandle);
|
|
138
|
-
this.intervalHandle = undefined;
|
|
139
|
-
if (this.jobService && this.jobName && typeof this.jobService.cancel === 'function') {
|
|
140
|
-
try { await this.jobService.cancel(this.jobName); }
|
|
141
|
-
catch (err) { ctx.logger.warn('ReportsServicePlugin: failed to cancel job', err as any); }
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|