@constructive-io/knative-job-service 0.6.15 → 0.7.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/index.js CHANGED
@@ -1,2 +1,371 @@
1
1
  "use strict";
2
- // noop
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.bootJobs = exports.waitForJobsPrereqs = exports.startJobsServices = exports.startKnativeJobsSvcFromEnv = exports.buildKnativeJobsSvcOptionsFromEnv = exports.KnativeJobsSvc = void 0;
21
+ const knative_job_server_1 = __importDefault(require("@constructive-io/knative-job-server"));
22
+ const knative_job_worker_1 = __importDefault(require("@constructive-io/knative-job-worker"));
23
+ const job_scheduler_1 = __importDefault(require("@constructive-io/job-scheduler"));
24
+ const job_pg_1 = __importDefault(require("@constructive-io/job-pg"));
25
+ const job_utils_1 = require("@constructive-io/job-utils");
26
+ const env_1 = require("@pgpmjs/env");
27
+ const logger_1 = require("@pgpmjs/logger");
28
+ const async_retry_1 = __importDefault(require("async-retry"));
29
+ const pg_1 = require("pg");
30
+ const module_1 = require("module");
31
+ const functionRegistry = {
32
+ 'simple-email': {
33
+ moduleName: '@constructive-io/simple-email-fn',
34
+ defaultPort: 8081
35
+ },
36
+ 'send-email-link': {
37
+ moduleName: '@constructive-io/send-email-link-fn',
38
+ defaultPort: 8082
39
+ }
40
+ };
41
+ const log = new logger_1.Logger('knative-job-service');
42
+ const requireFn = (0, module_1.createRequire)(__filename);
43
+ const resolveFunctionEntry = (name) => {
44
+ const entry = functionRegistry[name];
45
+ if (!entry) {
46
+ throw new Error(`Unknown function "${name}".`);
47
+ }
48
+ return entry;
49
+ };
50
+ const loadFunctionApp = (moduleName) => {
51
+ const knativeModuleId = requireFn.resolve('@constructive-io/knative-job-fn');
52
+ delete requireFn.cache[knativeModuleId];
53
+ const moduleId = requireFn.resolve(moduleName);
54
+ delete requireFn.cache[moduleId];
55
+ const mod = requireFn(moduleName);
56
+ const app = mod.default ?? mod;
57
+ if (!app || typeof app.listen !== 'function') {
58
+ throw new Error(`Function module "${moduleName}" does not export a listenable app.`);
59
+ }
60
+ return app;
61
+ };
62
+ const shouldEnableFunctions = (options) => {
63
+ if (!options)
64
+ return false;
65
+ if (typeof options.enabled === 'boolean')
66
+ return options.enabled;
67
+ return Boolean(options.services?.length);
68
+ };
69
+ const normalizeFunctionServices = (options) => {
70
+ if (!shouldEnableFunctions(options))
71
+ return [];
72
+ if (!options?.services?.length) {
73
+ return Object.keys(functionRegistry).map((name) => ({
74
+ name: name
75
+ }));
76
+ }
77
+ return options.services;
78
+ };
79
+ const resolveFunctionPort = (service) => {
80
+ const entry = resolveFunctionEntry(service.name);
81
+ return service.port ?? entry.defaultPort;
82
+ };
83
+ const ensureUniquePorts = (services) => {
84
+ const usedPorts = new Set();
85
+ for (const service of services) {
86
+ const port = resolveFunctionPort(service);
87
+ if (usedPorts.has(port)) {
88
+ throw new Error(`Function port ${port} is assigned more than once.`);
89
+ }
90
+ usedPorts.add(port);
91
+ }
92
+ };
93
+ const startFunction = async (service, functionServers) => {
94
+ const entry = resolveFunctionEntry(service.name);
95
+ const port = resolveFunctionPort(service);
96
+ const app = loadFunctionApp(entry.moduleName);
97
+ await new Promise((resolve, reject) => {
98
+ const server = app.listen(port, () => {
99
+ log.info(`function:${service.name} listening on ${port}`);
100
+ resolve();
101
+ });
102
+ if (server?.on) {
103
+ server.on('error', (err) => {
104
+ log.error(`function:${service.name} failed to start`, err);
105
+ reject(err);
106
+ });
107
+ }
108
+ functionServers.set(service.name, server);
109
+ });
110
+ return { name: service.name, port };
111
+ };
112
+ const startFunctions = async (options, functionServers) => {
113
+ const services = normalizeFunctionServices(options);
114
+ if (!services.length)
115
+ return [];
116
+ ensureUniquePorts(services);
117
+ const started = [];
118
+ for (const service of services) {
119
+ started.push(await startFunction(service, functionServers));
120
+ }
121
+ return started;
122
+ };
123
+ const listenApp = async (app, port, host) => new Promise((resolveListen, rejectListen) => {
124
+ const server = host ? app.listen(port, host) : app.listen(port);
125
+ const cleanup = () => {
126
+ server.off('listening', handleListen);
127
+ server.off('error', handleError);
128
+ };
129
+ const handleListen = () => {
130
+ cleanup();
131
+ resolveListen(server);
132
+ };
133
+ const handleError = (err) => {
134
+ cleanup();
135
+ rejectListen(err);
136
+ };
137
+ server.once('listening', handleListen);
138
+ server.once('error', handleError);
139
+ });
140
+ const closeServer = async (server) => {
141
+ if (!server || !server.listening)
142
+ return;
143
+ await new Promise((resolveClose, rejectClose) => {
144
+ server.close((err) => {
145
+ if (err) {
146
+ rejectClose(err);
147
+ return;
148
+ }
149
+ resolveClose();
150
+ });
151
+ });
152
+ };
153
+ class KnativeJobsSvc {
154
+ options;
155
+ started = false;
156
+ result = {
157
+ functions: [],
158
+ jobs: false
159
+ };
160
+ functionServers = new Map();
161
+ jobsHttpServer;
162
+ worker;
163
+ scheduler;
164
+ jobsPoolManager;
165
+ constructor(options = {}) {
166
+ this.options = options;
167
+ }
168
+ async start() {
169
+ if (this.started)
170
+ return this.result;
171
+ this.started = true;
172
+ this.result = {
173
+ functions: [],
174
+ jobs: false
175
+ };
176
+ if (shouldEnableFunctions(this.options.functions)) {
177
+ log.info('starting functions');
178
+ this.result.functions = await startFunctions(this.options.functions, this.functionServers);
179
+ }
180
+ if (this.options.jobs?.enabled) {
181
+ log.info('starting jobs service');
182
+ await this.startJobs();
183
+ this.result.jobs = true;
184
+ }
185
+ return this.result;
186
+ }
187
+ async stop() {
188
+ if (!this.started)
189
+ return;
190
+ this.started = false;
191
+ if (this.worker?.stop) {
192
+ await this.worker.stop();
193
+ }
194
+ if (this.scheduler?.stop) {
195
+ await this.scheduler.stop();
196
+ }
197
+ this.worker = undefined;
198
+ this.scheduler = undefined;
199
+ await closeServer(this.jobsHttpServer);
200
+ this.jobsHttpServer = undefined;
201
+ if (this.jobsPoolManager) {
202
+ await this.jobsPoolManager.close();
203
+ this.jobsPoolManager = undefined;
204
+ }
205
+ for (const server of this.functionServers.values()) {
206
+ await closeServer(server);
207
+ }
208
+ this.functionServers.clear();
209
+ }
210
+ async startJobs() {
211
+ const pgPool = job_pg_1.default.getPool();
212
+ const jobsApp = (0, knative_job_server_1.default)(pgPool);
213
+ const callbackPort = (0, job_utils_1.getJobsCallbackPort)();
214
+ this.jobsHttpServer = await listenApp(jobsApp, callbackPort);
215
+ const tasks = (0, job_utils_1.getJobSupported)();
216
+ this.worker = new knative_job_worker_1.default({
217
+ pgPool,
218
+ tasks,
219
+ workerId: (0, job_utils_1.getWorkerHostname)()
220
+ });
221
+ this.scheduler = new job_scheduler_1.default({
222
+ pgPool,
223
+ tasks,
224
+ workerId: (0, job_utils_1.getSchedulerHostname)()
225
+ });
226
+ this.jobsPoolManager = job_pg_1.default;
227
+ this.worker.listen();
228
+ this.scheduler.listen();
229
+ }
230
+ }
231
+ exports.KnativeJobsSvc = KnativeJobsSvc;
232
+ const parseList = (value) => {
233
+ if (!value)
234
+ return [];
235
+ return value
236
+ .split(',')
237
+ .map((item) => item.trim())
238
+ .filter(Boolean);
239
+ };
240
+ const parsePortMap = (value) => {
241
+ if (!value)
242
+ return {};
243
+ const trimmed = value.trim();
244
+ if (!trimmed)
245
+ return {};
246
+ if (trimmed.startsWith('{')) {
247
+ try {
248
+ const parsed = JSON.parse(trimmed);
249
+ return Object.entries(parsed).reduce((acc, [key, port]) => {
250
+ const portNumber = Number(port);
251
+ if (Number.isFinite(portNumber)) {
252
+ acc[key] = portNumber;
253
+ }
254
+ return acc;
255
+ }, {});
256
+ }
257
+ catch {
258
+ return {};
259
+ }
260
+ }
261
+ return trimmed.split(',').reduce((acc, pair) => {
262
+ const [rawName, rawPort] = pair.split(/[:=]/).map((item) => item.trim());
263
+ const port = Number(rawPort);
264
+ if (rawName && Number.isFinite(port)) {
265
+ acc[rawName] = port;
266
+ }
267
+ return acc;
268
+ }, {});
269
+ };
270
+ const buildFunctionsOptionsFromEnv = () => {
271
+ const rawFunctions = (process.env.CONSTRUCTIVE_FUNCTIONS || '').trim();
272
+ if (!rawFunctions)
273
+ return undefined;
274
+ const portMap = parsePortMap(process.env.CONSTRUCTIVE_FUNCTION_PORTS);
275
+ const normalized = rawFunctions.toLowerCase();
276
+ if (normalized === 'all' || normalized === '*') {
277
+ return { enabled: true };
278
+ }
279
+ const names = parseList(rawFunctions);
280
+ if (!names.length)
281
+ return undefined;
282
+ const services = names.map((name) => ({
283
+ name,
284
+ port: portMap[name]
285
+ }));
286
+ return {
287
+ enabled: true,
288
+ services
289
+ };
290
+ };
291
+ const buildKnativeJobsSvcOptionsFromEnv = () => ({
292
+ jobs: {
293
+ enabled: (0, env_1.parseEnvBoolean)(process.env.CONSTRUCTIVE_JOBS_ENABLED) ?? true
294
+ },
295
+ functions: buildFunctionsOptionsFromEnv()
296
+ });
297
+ exports.buildKnativeJobsSvcOptionsFromEnv = buildKnativeJobsSvcOptionsFromEnv;
298
+ const startKnativeJobsSvcFromEnv = async () => {
299
+ const server = new KnativeJobsSvc((0, exports.buildKnativeJobsSvcOptionsFromEnv)());
300
+ return server.start();
301
+ };
302
+ exports.startKnativeJobsSvcFromEnv = startKnativeJobsSvcFromEnv;
303
+ const startJobsServices = () => {
304
+ log.info('starting jobs services...');
305
+ const pgPool = job_pg_1.default.getPool();
306
+ const app = (0, knative_job_server_1.default)(pgPool);
307
+ const callbackPort = (0, job_utils_1.getJobsCallbackPort)();
308
+ const httpServer = app.listen(callbackPort, () => {
309
+ log.info(`listening ON ${callbackPort}`);
310
+ const tasks = (0, job_utils_1.getJobSupported)();
311
+ const worker = new knative_job_worker_1.default({
312
+ pgPool,
313
+ workerId: (0, job_utils_1.getWorkerHostname)(),
314
+ tasks
315
+ });
316
+ const scheduler = new job_scheduler_1.default({
317
+ pgPool,
318
+ workerId: (0, job_utils_1.getSchedulerHostname)(),
319
+ tasks
320
+ });
321
+ worker.listen();
322
+ scheduler.listen();
323
+ });
324
+ return { pgPool, httpServer };
325
+ };
326
+ exports.startJobsServices = startJobsServices;
327
+ const waitForJobsPrereqs = async () => {
328
+ log.info('waiting for jobs prereqs');
329
+ let client = null;
330
+ try {
331
+ const cfg = (0, job_utils_1.getJobPgConfig)();
332
+ client = new pg_1.Client({
333
+ host: cfg.host,
334
+ port: cfg.port,
335
+ user: cfg.user,
336
+ password: cfg.password,
337
+ database: cfg.database
338
+ });
339
+ await client.connect();
340
+ const schema = (0, job_utils_1.getJobSchema)();
341
+ await client.query(`SELECT * FROM "${schema}".jobs LIMIT 1;`);
342
+ }
343
+ catch (error) {
344
+ log.error(error);
345
+ throw new Error('jobs server boot failed...');
346
+ }
347
+ finally {
348
+ if (client) {
349
+ void client.end();
350
+ }
351
+ }
352
+ };
353
+ exports.waitForJobsPrereqs = waitForJobsPrereqs;
354
+ const bootJobs = async () => {
355
+ log.info('attempting to boot jobs');
356
+ await (0, async_retry_1.default)(async () => {
357
+ await (0, exports.waitForJobsPrereqs)();
358
+ }, {
359
+ retries: 10,
360
+ factor: 2
361
+ });
362
+ const options = (0, exports.buildKnativeJobsSvcOptionsFromEnv)();
363
+ if (options.jobs?.enabled === false) {
364
+ log.info('jobs disabled; skipping startup');
365
+ return;
366
+ }
367
+ const server = new KnativeJobsSvc(options);
368
+ await server.start();
369
+ };
370
+ exports.bootJobs = bootJobs;
371
+ __exportStar(require("./types"), exports);
package/dist/run.d.ts CHANGED
@@ -1,7 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export declare const startJobsServices: () => {
3
- pgPool: import("pg").Pool;
4
- httpServer: any;
5
- };
6
- export declare const waitForJobsPrereqs: () => Promise<void>;
7
- export declare const bootJobs: () => Promise<void>;
2
+ export { bootJobs, startJobsServices, waitForJobsPrereqs } from './index';
package/dist/run.js CHANGED
@@ -1,84 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
3
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.bootJobs = exports.waitForJobsPrereqs = exports.startJobsServices = void 0;
8
- const job_scheduler_1 = __importDefault(require("@constructive-io/job-scheduler"));
9
- const knative_job_worker_1 = __importDefault(require("@constructive-io/knative-job-worker"));
10
- const knative_job_server_1 = __importDefault(require("@constructive-io/knative-job-server"));
11
- const job_pg_1 = __importDefault(require("@constructive-io/job-pg"));
12
- const pg_1 = require("pg");
13
- const async_retry_1 = __importDefault(require("async-retry"));
14
- const job_utils_1 = require("@constructive-io/job-utils");
15
- const startJobsServices = () => {
16
- // eslint-disable-next-line no-console
17
- console.log('starting jobs services...');
18
- const pgPool = job_pg_1.default.getPool();
19
- const app = (0, knative_job_server_1.default)(pgPool);
20
- const callbackPort = (0, job_utils_1.getJobsCallbackPort)();
21
- const httpServer = app.listen(callbackPort, () => {
22
- // eslint-disable-next-line no-console
23
- console.log(`[cb] listening ON ${callbackPort}`);
24
- const tasks = (0, job_utils_1.getJobSupported)();
25
- const worker = new knative_job_worker_1.default({
26
- pgPool,
27
- workerId: (0, job_utils_1.getWorkerHostname)(),
28
- tasks
29
- });
30
- const scheduler = new job_scheduler_1.default({
31
- pgPool,
32
- workerId: (0, job_utils_1.getSchedulerHostname)(),
33
- tasks
34
- });
35
- worker.listen();
36
- scheduler.listen();
37
- });
38
- return { pgPool, httpServer };
39
- };
40
- exports.startJobsServices = startJobsServices;
41
- const waitForJobsPrereqs = async () => {
42
- // eslint-disable-next-line no-console
43
- console.log('waiting for jobs prereqs');
44
- let client = null;
45
- try {
46
- const cfg = (0, job_utils_1.getJobPgConfig)();
47
- client = new pg_1.Client({
48
- host: cfg.host,
49
- port: cfg.port,
50
- user: cfg.user,
51
- password: cfg.password,
52
- database: cfg.database
53
- });
54
- await client.connect();
55
- const schema = (0, job_utils_1.getJobSchema)();
56
- await client.query(`SELECT * FROM "${schema}".jobs LIMIT 1;`);
57
- }
58
- catch (error) {
59
- // eslint-disable-next-line no-console
60
- console.log(error);
61
- throw new Error('jobs server boot failed...');
62
- }
63
- finally {
64
- if (client) {
65
- void client.end();
66
- }
67
- }
68
- };
69
- exports.waitForJobsPrereqs = waitForJobsPrereqs;
70
- const bootJobs = async () => {
71
- // eslint-disable-next-line no-console
72
- console.log('attempting to boot jobs');
73
- await (0, async_retry_1.default)(async () => {
74
- await (0, exports.waitForJobsPrereqs)();
75
- }, {
76
- retries: 10,
77
- factor: 2
78
- });
79
- (0, exports.startJobsServices)();
80
- };
81
- exports.bootJobs = bootJobs;
4
+ exports.waitForJobsPrereqs = exports.startJobsServices = exports.bootJobs = void 0;
5
+ var index_1 = require("./index");
6
+ Object.defineProperty(exports, "bootJobs", { enumerable: true, get: function () { return index_1.bootJobs; } });
7
+ Object.defineProperty(exports, "startJobsServices", { enumerable: true, get: function () { return index_1.startJobsServices; } });
8
+ Object.defineProperty(exports, "waitForJobsPrereqs", { enumerable: true, get: function () { return index_1.waitForJobsPrereqs; } });
9
+ const index_2 = require("./index");
82
10
  if (require.main === module) {
83
- void (0, exports.bootJobs)();
11
+ void (0, index_2.bootJobs)();
84
12
  }
@@ -0,0 +1,24 @@
1
+ export type FunctionName = 'simple-email' | 'send-email-link';
2
+ export type FunctionServiceConfig = {
3
+ name: FunctionName;
4
+ port?: number;
5
+ };
6
+ export type FunctionsOptions = {
7
+ enabled?: boolean;
8
+ services?: FunctionServiceConfig[];
9
+ };
10
+ export type JobsOptions = {
11
+ enabled?: boolean;
12
+ };
13
+ export type KnativeJobsSvcOptions = {
14
+ functions?: FunctionsOptions;
15
+ jobs?: JobsOptions;
16
+ };
17
+ export type StartedFunction = {
18
+ name: FunctionName;
19
+ port: number;
20
+ };
21
+ export type KnativeJobsSvcResult = {
22
+ functions: StartedFunction[];
23
+ jobs: boolean;
24
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@constructive-io/knative-job-service",
3
- "version": "0.6.15",
3
+ "version": "0.7.0",
4
4
  "description": "knative job service",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/jobs/tree/master/packages/knative-job-service#readme",
@@ -33,16 +33,34 @@
33
33
  "url": "https://github.com/constructive-io/jobs/issues"
34
34
  },
35
35
  "devDependencies": {
36
+ "@constructive-io/graphql-server": "^2.17.0",
37
+ "@constructive-io/graphql-types": "^2.12.14",
38
+ "@pgpm/database-jobs": "^0.16.0",
39
+ "@pgpm/inflection": "^0.16.0",
40
+ "@pgpm/metaschema-modules": "^0.16.1",
41
+ "@pgpm/metaschema-schema": "^0.16.1",
42
+ "@pgpm/services": "^0.16.1",
43
+ "@pgpm/types": "^0.16.0",
44
+ "@pgpm/verify": "^0.16.0",
45
+ "@pgpmjs/core": "^4.15.2",
46
+ "@types/supertest": "^6.0.3",
47
+ "pgsql-test": "^2.24.16",
48
+ "supertest": "^7.2.2",
36
49
  "ts-node": "^10.9.2"
37
50
  },
38
51
  "dependencies": {
39
- "@constructive-io/job-pg": "^0.3.20",
40
- "@constructive-io/job-scheduler": "^0.3.22",
41
- "@constructive-io/job-utils": "^0.5.15",
42
- "@constructive-io/knative-job-server": "^0.3.24",
43
- "@constructive-io/knative-job-worker": "^0.7.15",
52
+ "@constructive-io/job-pg": "^0.3.21",
53
+ "@constructive-io/job-scheduler": "^0.3.23",
54
+ "@constructive-io/job-utils": "^0.5.16",
55
+ "@constructive-io/knative-job-fn": "^0.2.8",
56
+ "@constructive-io/knative-job-server": "^0.3.25",
57
+ "@constructive-io/knative-job-worker": "^0.7.16",
58
+ "@constructive-io/send-email-link-fn": "^0.2.21",
59
+ "@constructive-io/simple-email-fn": "^0.2.22",
60
+ "@pgpmjs/env": "^2.9.5",
61
+ "@pgpmjs/logger": "^1.3.8",
44
62
  "async-retry": "1.3.1",
45
63
  "pg": "8.16.3"
46
64
  },
47
- "gitHead": "b9719cd48487af70e6e065510197c9d2d6b331b6"
65
+ "gitHead": "b7abf2fdaf0a827d79a80c9c0a29e5c960f227df"
48
66
  }