@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.
@@ -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
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "types": ["node"]
7
- },
8
- "include": ["src/**/*"],
9
- "exclude": ["dist", "node_modules", "**/*.test.ts"]
10
- }