@malloy-publisher/server 0.0.119 → 0.0.120

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 (28) hide show
  1. package/dist/app/api-doc.yaml +324 -335
  2. package/dist/app/assets/{HomePage-BxFnfH3M.js → HomePage-xhvGPTSO.js} +1 -1
  3. package/dist/app/assets/{MainPage-D301Y0mT.js → MainPage-Bq95p8Cl.js} +1 -1
  4. package/dist/app/assets/{ModelPage-Df8ivC1J.js → ModelPage-CbfBNWIi.js} +1 -1
  5. package/dist/app/assets/{PackagePage-CE41SCV_.js → PackagePage-CGS612C4.js} +1 -1
  6. package/dist/app/assets/ProjectPage-Dpn9pqSB.js +1 -0
  7. package/dist/app/assets/{RouteError-l_WGtNhS.js → RouteError-BLPhl1wC.js} +1 -1
  8. package/dist/app/assets/{WorkbookPage-CY-1oBvt.js → WorkbookPage-Dt93gSZ3.js} +1 -1
  9. package/dist/app/assets/{index-D5BBaLz8.js → index-B8wuAjgG.js} +1 -1
  10. package/dist/app/assets/{index-DjbXd602.js → index-CVbROKL7.js} +113 -113
  11. package/dist/app/assets/{index-DlZbNvNc.js → index-DxKW6bXB.js} +1 -1
  12. package/dist/app/assets/{index.umd-DQiSWsWe.js → index.umd-CVy5LWk2.js} +1 -1
  13. package/dist/app/index.html +1 -1
  14. package/dist/server.js +35396 -144724
  15. package/k6-tests/common.ts +12 -3
  16. package/package.json +1 -1
  17. package/src/controller/connection.controller.ts +82 -72
  18. package/src/controller/query.controller.ts +1 -1
  19. package/src/server.ts +6 -48
  20. package/src/service/connection.ts +384 -305
  21. package/src/service/db_utils.ts +407 -303
  22. package/src/service/package.spec.ts +8 -97
  23. package/src/service/package.ts +24 -46
  24. package/src/service/project.ts +8 -24
  25. package/src/service/project_store.ts +0 -1
  26. package/dist/app/assets/ProjectPage-DA66xbmQ.js +0 -1
  27. package/src/controller/schedule.controller.ts +0 -21
  28. package/src/service/scheduler.ts +0 -190
@@ -1,16 +1,14 @@
1
1
  import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
+ import { Stats } from "fs";
2
3
  import fs from "fs/promises";
3
4
  import { join } from "path";
4
5
  import sinon from "sinon";
5
6
  import { PackageNotFoundError } from "../errors";
6
- import { readConnectionConfig } from "./connection";
7
7
  import { Model } from "./model";
8
8
  import { Package } from "./package";
9
- import { Scheduler } from "./scheduler";
10
- import { Stats } from "fs";
11
9
 
12
10
  // Minimal partial types for mocking
13
- type PartialScheduler = Pick<Scheduler, "list">;
11
+ type PartialModel = Pick<Model, "getPath">;
14
12
 
15
13
  describe("service/package", () => {
16
14
  const testPackageDirectory = "testPackage";
@@ -48,16 +46,13 @@ describe("service/package", () => {
48
46
  new Map([
49
47
  [
50
48
  "model1.malloy",
51
- // @ts-expect-error PartialModel is a partial type
52
- { getPath: () => "model1.malloy" } as PartialModel,
49
+ { getPath: () => "model1.malloy" } as unknown as Model,
53
50
  ],
54
51
  [
55
52
  "model2.malloynb",
56
- // @ts-expect-error PartialModel is a partial type
57
- { getPath: () => "model2.malloynb" } as PartialModel,
53
+ { getPath: () => "model2.malloynb" } as unknown as Model,
58
54
  ],
59
55
  ]),
60
- undefined,
61
56
  );
62
57
 
63
58
  expect(pkg).toBeInstanceOf(Package);
@@ -75,7 +70,7 @@ describe("service/package", () => {
75
70
  "testPackage",
76
71
  testPackageDirectory,
77
72
  new Map(),
78
- "/server/root",
73
+ [],
79
74
  ),
80
75
  ).rejects.toThrowError(
81
76
  new PackageNotFoundError(
@@ -96,17 +91,11 @@ describe("service/package", () => {
96
91
  );
97
92
 
98
93
  // Still use Partial<Model> for the stub resolution type
99
- type PartialModel = Pick<Model, "getPath">;
100
94
  sinon
101
95
  .stub(Model, "create")
102
96
  // @ts-expect-error PartialModel is a partial type
103
97
  .resolves({ getPath: () => "model1.model" } as PartialModel);
104
98
 
105
- // @ts-expect-error PartialScheduler is a partial type
106
- sinon.stub(Scheduler, "create").returns({
107
- list: () => [],
108
- } as PartialScheduler);
109
-
110
99
  readFileStub.restore();
111
100
  readFileStub.resolves(Buffer.from(JSON.stringify([])));
112
101
 
@@ -115,7 +104,7 @@ describe("service/package", () => {
115
104
  "testPackage",
116
105
  testPackageDirectory,
117
106
  new Map(),
118
- "/server/root",
107
+ [],
119
108
  );
120
109
 
121
110
  expect(packageInstance).toBeInstanceOf(Package);
@@ -138,7 +127,6 @@ describe("service/package", () => {
138
127
  },
139
128
  ]);
140
129
  expect(packageInstance.listModels()).toBeEmpty();
141
- expect(packageInstance.listSchedules()).toBeEmpty();
142
130
  },
143
131
  { timeout: 15000 },
144
132
  );
@@ -159,8 +147,7 @@ describe("service/package", () => {
159
147
  {
160
148
  getPath: () => "model1.malloy",
161
149
  getModel: () => "foo",
162
- // @ts-expect-error PartialModel is a partial type
163
- } as PartialModel,
150
+ } as unknown as Model,
164
151
  ],
165
152
  [
166
153
  "model2.malloynb",
@@ -171,11 +158,9 @@ describe("service/package", () => {
171
158
  message: "This is the error",
172
159
  };
173
160
  },
174
- // @ts-expect-error PartialModel is a partial type
175
- } as PartialModel,
161
+ } as unknown as Model,
176
162
  ],
177
163
  ]),
178
- undefined,
179
164
  );
180
165
 
181
166
  const models = await packageInstance.listModels();
@@ -222,79 +207,5 @@ describe("service/package", () => {
222
207
  });
223
208
  });
224
209
  });
225
-
226
- describe("readConnectionConfig", () => {
227
- it("should return an empty array if no project name or server root path is provided", async () => {
228
- const config = await readConnectionConfig(testPackageDirectory);
229
- expect(Array.isArray(config)).toBe(true);
230
- expect(config).toHaveLength(0);
231
- });
232
-
233
- it("should return an empty array if project name or server root path is missing", async () => {
234
- const config1 = await readConnectionConfig(
235
- testPackageDirectory,
236
- "testProject",
237
- );
238
- expect(Array.isArray(config1)).toBe(true);
239
- expect(config1).toHaveLength(0);
240
-
241
- const config2 = await readConnectionConfig(
242
- testPackageDirectory,
243
- undefined,
244
- "/server/root",
245
- );
246
- expect(Array.isArray(config2)).toBe(true);
247
- expect(config2).toHaveLength(0);
248
- });
249
-
250
- it("should return connections from publisher config when project name and server root are provided", async () => {
251
- const tempServerRoot = "/tmp/test-server-root";
252
- const tempConfigPath = join(
253
- tempServerRoot,
254
- "publisher.config.json",
255
- );
256
-
257
- // Create temp directory and config file
258
- await fs.mkdir(tempServerRoot, { recursive: true });
259
- await fs.writeFile(
260
- tempConfigPath,
261
- JSON.stringify({
262
- frozenConfig: false,
263
- projects: [
264
- {
265
- name: "testProject",
266
- packages: [],
267
- connections: [
268
- { name: "test-conn", type: "postgres" },
269
- { name: "test-conn2", type: "bigquery" },
270
- ],
271
- },
272
- ],
273
- }),
274
- );
275
-
276
- const config = await readConnectionConfig(
277
- testPackageDirectory,
278
- "testProject",
279
- tempServerRoot,
280
- );
281
-
282
- expect(Array.isArray(config)).toBe(true);
283
- expect(config).toHaveLength(2);
284
- expect(config[0]).toMatchObject({
285
- name: "test-conn",
286
- type: "postgres",
287
- resource: "/api/v0/connections/test-conn",
288
- });
289
- expect(config[1]).toMatchObject({
290
- name: "test-conn2",
291
- type: "bigquery",
292
- resource: "/api/v0/connections/test-conn2",
293
- });
294
-
295
- // Clean up
296
- await fs.rm(tempServerRoot, { recursive: true, force: true });
297
- });
298
- });
299
210
  });
300
211
  });
@@ -19,15 +19,13 @@ import {
19
19
  } from "../constants";
20
20
  import { PackageNotFoundError } from "../errors";
21
21
  import { logger } from "../logger";
22
- import { createConnections } from "./connection";
23
- import { Model } from "./model";
24
- import { Scheduler } from "./scheduler";
22
+ import { createPackageDuckDBConnections } from "./connection";
23
+ import { ApiConnection, Model } from "./model";
25
24
 
26
25
  type ApiDatabase = components["schemas"]["Database"];
27
26
  type ApiModel = components["schemas"]["Model"];
28
27
  type ApiNotebook = components["schemas"]["Notebook"];
29
28
  export type ApiPackage = components["schemas"]["Package"];
30
- type ApiSchedule = components["schemas"]["Schedule"];
31
29
  type ApiColumn = components["schemas"]["Column"];
32
30
  type ApiTableDescription = components["schemas"]["TableDescription"];
33
31
 
@@ -38,8 +36,8 @@ export class Package {
38
36
  private packageMetadata: ApiPackage;
39
37
  private databases: ApiDatabase[];
40
38
  private models: Map<string, Model> = new Map();
41
- private scheduler: Scheduler | undefined;
42
39
  private packagePath: string;
40
+ private connections: Map<string, Connection> = new Map();
43
41
  private static meter = metrics.getMeter("publisher");
44
42
  private static packageLoadHistogram = this.meter.createHistogram(
45
43
  "malloy_package_load_duration",
@@ -56,7 +54,7 @@ export class Package {
56
54
  packageMetadata: ApiPackage,
57
55
  databases: ApiDatabase[],
58
56
  models: Map<string, Model>,
59
- scheduler: Scheduler | undefined,
57
+ connections: Map<string, Connection> = new Map(),
60
58
  ) {
61
59
  this.projectName = projectName;
62
60
  this.packageName = packageName;
@@ -64,7 +62,7 @@ export class Package {
64
62
  this.packageMetadata = packageMetadata;
65
63
  this.databases = databases;
66
64
  this.models = models;
67
- this.scheduler = scheduler;
65
+ this.connections = connections;
68
66
  }
69
67
 
70
68
  static async create(
@@ -72,7 +70,7 @@ export class Package {
72
70
  packageName: string,
73
71
  packagePath: string,
74
72
  projectConnections: Map<string, Connection>,
75
- serverRootPath: string,
73
+ packageConnections: ApiConnection[],
76
74
  ): Promise<Package> {
77
75
  const startTime = performance.now();
78
76
  await Package.validatePackageManifestExistsOrThrowError(packagePath);
@@ -102,34 +100,15 @@ export class Package {
102
100
  unit: "ms",
103
101
  });
104
102
  const connections = new Map<string, Connection>(projectConnections);
105
- logger.info(`Project connections: ${connections.size}`, {
106
- connections,
107
- projectConnections,
108
- });
109
- // Package connections override project connections.
110
- const { malloyConnections: packageConnections } =
111
- await createConnections(
112
- packagePath,
113
- [],
114
- projectName,
115
- serverRootPath,
116
- );
117
- const connectionsTime = performance.now();
118
- logger.info("Package connections created", {
119
- packageName,
120
- connectionCount: packageConnections.size,
121
- duration: connectionsTime - databasesTime,
122
- unit: "ms",
123
- });
124
- packageConnections.forEach((connection) => {
125
- connections.set(connection.name, connection);
126
- });
127
103
 
128
104
  // Add a duckdb connection for the package.
129
- connections.set(
130
- "duckdb",
131
- new DuckDBConnection("duckdb", ":memory:", packagePath),
105
+ const duckdbConnections = await createPackageDuckDBConnections(
106
+ packageConnections,
107
+ packagePath,
132
108
  );
109
+ duckdbConnections.malloyConnections.forEach((connection, name) => {
110
+ connections.set(name, connection);
111
+ });
133
112
 
134
113
  const models = await Package.loadModels(
135
114
  packageName,
@@ -140,14 +119,7 @@ export class Package {
140
119
  logger.info("Models loaded", {
141
120
  packageName,
142
121
  modelCount: models.size,
143
- duration: modelsTime - connectionsTime,
144
- unit: "ms",
145
- });
146
- const scheduler = Scheduler.create(models);
147
- const schedulerTime = performance.now();
148
- logger.info("Scheduler created", {
149
- packageName,
150
- duration: schedulerTime - modelsTime,
122
+ duration: modelsTime - databasesTime,
151
123
  unit: "ms",
152
124
  });
153
125
  const endTime = performance.now();
@@ -168,7 +140,7 @@ export class Package {
168
140
  packageConfig,
169
141
  databases,
170
142
  models,
171
- scheduler,
143
+ connections,
172
144
  );
173
145
  } catch (error) {
174
146
  logger.error(`Error loading package ${packageName}`, { error });
@@ -195,14 +167,20 @@ export class Package {
195
167
  return this.databases;
196
168
  }
197
169
 
198
- public listSchedules(): ApiSchedule[] {
199
- return this.scheduler ? this.scheduler.list() : [];
200
- }
201
-
202
170
  public getModel(modelPath: string): Model | undefined {
203
171
  return this.models.get(modelPath);
204
172
  }
205
173
 
174
+ public getMalloyConnection(connectionName: string): Connection {
175
+ const connection = this.connections.get(connectionName);
176
+ if (!connection) {
177
+ throw new Error(
178
+ `Connection ${connectionName} not found in package ${this.packageName}`,
179
+ );
180
+ }
181
+ return connection;
182
+ }
183
+
206
184
  public async getModelFileText(modelPath: string): Promise<string> {
207
185
  const model = this.getModel(modelPath);
208
186
  if (!model) {
@@ -10,7 +10,7 @@ import {
10
10
  ProjectNotFoundError,
11
11
  } from "../errors";
12
12
  import { logger } from "../logger";
13
- import { createConnections, InternalConnection } from "./connection";
13
+ import { createProjectConnections, InternalConnection } from "./connection";
14
14
  import { ApiConnection } from "./model";
15
15
  import { Package } from "./package";
16
16
 
@@ -37,7 +37,6 @@ export class Project {
37
37
  private apiConnections: ApiConnection[];
38
38
  private projectPath: string;
39
39
  private projectName: string;
40
- private serverRootPath: string;
41
40
  public metadata: ApiProject;
42
41
 
43
42
  constructor(
@@ -45,11 +44,9 @@ export class Project {
45
44
  projectPath: string,
46
45
  malloyConnections: Map<string, BaseConnection>,
47
46
  apiConnections: InternalConnection[],
48
- serverRootPath: string,
49
47
  ) {
50
48
  this.projectName = projectName;
51
49
  this.projectPath = projectPath;
52
- this.serverRootPath = serverRootPath;
53
50
  this.malloyConnections = malloyConnections;
54
51
  this.apiConnections = apiConnections;
55
52
  this.metadata = {
@@ -73,10 +70,8 @@ export class Project {
73
70
  );
74
71
 
75
72
  // Reload connections with full config
76
- const { malloyConnections, apiConnections } = await createConnections(
77
- this.projectPath,
78
- payload.connections,
79
- );
73
+ const { malloyConnections, apiConnections } =
74
+ await createProjectConnections(payload.connections);
80
75
 
81
76
  // Update the project's connection maps
82
77
  this.malloyConnections = malloyConnections;
@@ -98,8 +93,7 @@ export class Project {
98
93
  static async create(
99
94
  projectName: string,
100
95
  projectPath: string,
101
- defaultConnections: ApiConnection[],
102
- serverRootPath?: string,
96
+ connections: ApiConnection[],
103
97
  ): Promise<Project> {
104
98
  if (!(await fs.promises.stat(projectPath)).isDirectory()) {
105
99
  throw new ProjectNotFoundError(
@@ -107,18 +101,9 @@ export class Project {
107
101
  );
108
102
  }
109
103
 
110
- let malloyConnections: Map<string, BaseConnection> = new Map();
111
- let apiConnections: InternalConnection[] = [];
112
-
113
104
  logger.info(`Creating project with connection configuration`);
114
- const result = await createConnections(
115
- projectPath,
116
- defaultConnections,
117
- projectName,
118
- serverRootPath,
119
- );
120
- malloyConnections = result.malloyConnections;
121
- apiConnections = result.apiConnections;
105
+ const { malloyConnections, apiConnections } =
106
+ await createProjectConnections(connections);
122
107
 
123
108
  logger.info(
124
109
  `Loaded ${malloyConnections.size + apiConnections.length} connections for project ${projectName}`,
@@ -133,7 +118,6 @@ export class Project {
133
118
  projectPath,
134
119
  malloyConnections,
135
120
  apiConnections,
136
- serverRootPath || "",
137
121
  );
138
122
  }
139
123
 
@@ -264,7 +248,7 @@ export class Project {
264
248
  packageName,
265
249
  path.join(this.projectPath, packageName),
266
250
  this.malloyConnections,
267
- this.serverRootPath,
251
+ this.apiConnections,
268
252
  );
269
253
  this.packages.set(packageName, _package);
270
254
 
@@ -309,7 +293,7 @@ export class Project {
309
293
  packageName,
310
294
  packagePath,
311
295
  this.malloyConnections,
312
- this.serverRootPath,
296
+ this.apiConnections,
313
297
  ),
314
298
  );
315
299
  } catch (error) {
@@ -232,7 +232,6 @@ export class ProjectStore {
232
232
  projectName,
233
233
  absoluteProjectPath,
234
234
  project.connections || [],
235
- this.serverRootPath,
236
235
  );
237
236
  this.projects.set(projectName, newProject);
238
237
  projectConfig?.packages.forEach((_package) => {
@@ -1 +0,0 @@
1
- import{G as t,p as n,j as e,F as o,N as c}from"./index-DjbXd602.js";function j(){const r=t(),{projectName:s}=n();if(s){const a=o({projectName:s});return e.jsx(c,{onSelectPackage:r,resourceUri:a})}else return e.jsx("div",{children:e.jsx("h2",{children:"Missing project name"})})}export{j as default};
@@ -1,21 +0,0 @@
1
- import { components } from "../api";
2
- import { ProjectStore } from "../service/project_store";
3
-
4
- type ApiSchedule = components["schemas"]["Schedule"];
5
-
6
- export class ScheduleController {
7
- private projectStore: ProjectStore;
8
-
9
- constructor(projectStore: ProjectStore) {
10
- this.projectStore = projectStore;
11
- }
12
-
13
- public async listSchedules(
14
- projectName: string,
15
- packageName: string,
16
- ): Promise<ApiSchedule[]> {
17
- const project = await this.projectStore?.getProject?.(projectName);
18
- const p = await project?.getPackage?.(packageName);
19
- return p?.listSchedules?.();
20
- }
21
- }
@@ -1,190 +0,0 @@
1
- import cron from "node-cron";
2
- import { components } from "../api";
3
- import { logger } from "../logger";
4
- import { Model } from "./model";
5
-
6
- // @ts-expect-error TODO: Fix missing Source type in API
7
- type ApiSource = components["schemas"]["Source"];
8
- type ApiView = components["schemas"]["View"];
9
- type ApiQuery = components["schemas"]["Query"];
10
- type ApiSchedule = components["schemas"]["Schedule"];
11
-
12
- const SCHEDULE_ANNOTATION = "# schedule";
13
-
14
- class Schedule {
15
- private model: Model;
16
- private source: ApiSource | undefined;
17
- private view: ApiView | undefined;
18
- private query: ApiQuery | undefined;
19
-
20
- private schedule: string;
21
- private action: string;
22
- private connection: string;
23
- private task: cron.ScheduledTask;
24
- private lastRunTime: number | undefined;
25
- private lastRunStatus: string;
26
-
27
- public constructor(
28
- model: Model,
29
- source: ApiSource | undefined,
30
- view: ApiView | undefined,
31
- query: ApiQuery | undefined,
32
- annotation: string,
33
- ) {
34
- this.model = model;
35
- this.source = source;
36
- this.view = view;
37
- this.query = query;
38
- const { origSchedule, cronSchedule, action, connection } =
39
- Schedule.parseAnnotation(annotation);
40
- this.schedule = origSchedule;
41
- this.action = action;
42
- this.connection = connection;
43
- this.lastRunTime = undefined;
44
- this.lastRunStatus = "unknown";
45
-
46
- this.task = cron.schedule(cronSchedule, () => {
47
- this.lastRunTime = Date.now();
48
- this.lastRunStatus = "ok";
49
- });
50
- }
51
-
52
- public stop() {
53
- this.task.stop();
54
- }
55
-
56
- public get(): ApiSchedule {
57
- const query = this.source
58
- ? `${this.source.name} > ${this.view?.name}`
59
- : this.query?.name;
60
- return {
61
- resource: `${this.model.getPath()} > ${query}`,
62
- schedule: this.schedule,
63
- action: this.action,
64
- connection: this.connection,
65
- lastRunTime: this.lastRunTime,
66
- lastRunStatus: this.lastRunStatus,
67
- };
68
- }
69
-
70
- private static parseAnnotation(annotation: string) {
71
- // Example schedule annotation
72
- // # schedule @hourly materialize duckdb
73
- // # schedule "0 * * * *" materialize duckdb
74
- // TODO: Don't split quoted strings. Use regex instead of split.
75
- const annotationSplit = annotation.split(/\s+/);
76
- if (annotationSplit.length != 6) {
77
- logger.info("Length: " + annotationSplit.length);
78
- throw new Error(
79
- "Invalid annotation string does not have enough parts: " +
80
- annotation,
81
- );
82
- }
83
-
84
- if (annotationSplit[0] != "#") {
85
- throw new Error(
86
- "Invalid annotation string does not have start with #: " +
87
- annotationSplit[0],
88
- );
89
- }
90
-
91
- if (annotationSplit[1] != "schedule") {
92
- throw new Error(
93
- "Invalid annotation string does not start with schedule command: " +
94
- annotationSplit[1],
95
- );
96
- }
97
-
98
- const standardCron = Schedule.translateNonStandardCron(
99
- annotationSplit[2],
100
- );
101
- if (!cron.validate(standardCron)) {
102
- throw new Error(
103
- "Invalid annotation string does not have valid cron schedule: " +
104
- standardCron,
105
- );
106
- }
107
-
108
- if (
109
- annotationSplit[3] != "materialize" &&
110
- annotationSplit[3] != "report"
111
- ) {
112
- throw new Error(
113
- "Invalid annotation string unrecognized command: " + annotation,
114
- );
115
- }
116
-
117
- // TODO: Validate connection exists.
118
-
119
- return {
120
- origSchedule: annotationSplit[2],
121
- cronSchedule: standardCron,
122
- action: annotationSplit[3],
123
- connection: annotationSplit[4],
124
- };
125
- }
126
-
127
- public static translateNonStandardCron(schedule: string): string {
128
- let standardCron = schedule;
129
- switch (schedule) {
130
- case "@yearly":
131
- case "@anually":
132
- standardCron = "0 0 1 1 *";
133
- break;
134
- case "@monthly":
135
- standardCron = "0 0 1 * *";
136
- break;
137
- case "@weekly":
138
- standardCron = "0 0 * * 0";
139
- break;
140
- case "@daily":
141
- case "@midnight":
142
- standardCron = "0 0 * * *";
143
- break;
144
- case "@hourly":
145
- standardCron = "0 * * * *";
146
- break;
147
- case "@minutely":
148
- standardCron = "* * * * *";
149
- }
150
- return standardCron;
151
- }
152
- }
153
-
154
- export class Scheduler {
155
- private schedules: Schedule[];
156
-
157
- private constructor(schedules: Schedule[]) {
158
- this.schedules = schedules;
159
- }
160
-
161
- public static create(models: Map<string, Model>): Scheduler {
162
- const schedules: Schedule[] = [];
163
-
164
- models.forEach((m) => {
165
- m.getSources()?.forEach((s) => {
166
- s.views?.forEach((v: ApiView) => {
167
- v.annotations?.forEach((a: string) => {
168
- if (a.startsWith(SCHEDULE_ANNOTATION)) {
169
- schedules.push(new Schedule(m, s, v, undefined, a));
170
- }
171
- });
172
- });
173
- });
174
-
175
- m.getQueries()?.forEach((q) => {
176
- q.annotations?.forEach((a) => {
177
- if (a.startsWith(SCHEDULE_ANNOTATION)) {
178
- schedules.push(new Schedule(m, undefined, undefined, q, a));
179
- }
180
- });
181
- });
182
- });
183
-
184
- return new Scheduler(schedules);
185
- }
186
-
187
- public list(): ApiSchedule[] {
188
- return this.schedules.map((s) => s.get());
189
- }
190
- }