proto.io 0.0.180 → 0.0.181

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,5 +1,5 @@
1
- import { T as TSchema, P as ProtoService } from '../../internals/index-BgZqiNxF.js';
2
- import { F as FileStorageBase, a as FileStorageOptions } from '../../internals/index-B7qz_VeO.js';
1
+ import { T as TSchema, P as ProtoService } from '../../internals/index-YpB-hXxf.js';
2
+ import { F as FileStorageBase, a as FileStorageOptions } from '../../internals/index-BZupzq1Y.js';
3
3
  import '@o2ter/utils-js';
4
4
  import 'jsonwebtoken';
5
5
  import '@o2ter/server-js';
@@ -1,5 +1,5 @@
1
- import { P as ProtoService } from '../../internals/index-BgZqiNxF.js';
2
- import { F as FileStorageBase, a as FileStorageOptions } from '../../internals/index-B7qz_VeO.js';
1
+ import { P as ProtoService } from '../../internals/index-YpB-hXxf.js';
2
+ import { F as FileStorageBase, a as FileStorageOptions } from '../../internals/index-BZupzq1Y.js';
3
3
  import '@o2ter/utils-js';
4
4
  import 'jsonwebtoken';
5
5
  import '@o2ter/server-js';
@@ -1,7 +1,7 @@
1
1
  import * as _google_cloud_storage from '@google-cloud/storage';
2
2
  import { Storage } from '@google-cloud/storage';
3
- import { P as ProtoService } from '../../internals/index-BgZqiNxF.js';
4
- import { F as FileStorageBase, a as FileStorageOptions } from '../../internals/index-B7qz_VeO.js';
3
+ import { P as ProtoService } from '../../internals/index-YpB-hXxf.js';
4
+ import { F as FileStorageBase, a as FileStorageOptions } from '../../internals/index-BZupzq1Y.js';
5
5
  import '@o2ter/utils-js';
6
6
  import 'jsonwebtoken';
7
7
  import '@o2ter/server-js';
@@ -1,5 +1,5 @@
1
1
  import { Pool, PoolClient, PoolConfig } from 'pg';
2
- import { _ as _TValue, T as TSchema, Q as QuerySelector, p as DecodedQuery, F as FindOptions, R as RelationOptions, q as DecodedSortOption, I as InsertOptions, r as TValue, t as FindOneOptions, u as TUpdateOp, v as FieldSelectorExpression, w as QueryExpression, x as TStorage, y as TransactionOptions, h as TObject, z as TQueryRandomOptions, A as TPubSub } from '../../internals/index-BgZqiNxF.js';
2
+ import { _ as _TValue, T as TSchema, Q as QuerySelector, p as DecodedQuery, F as FindOptions, R as RelationOptions, q as DecodedSortOption, I as InsertOptions, r as TValue, t as FindOneOptions, u as TUpdateOp, v as FieldSelectorExpression, w as QueryExpression, x as TStorage, y as TransactionOptions, h as TObject, z as TQueryRandomOptions, A as TPubSub } from '../../internals/index-YpB-hXxf.js';
3
3
  import * as _o2ter_utils_js from '@o2ter/utils-js';
4
4
  import { asyncStream } from '@o2ter/utils-js';
5
5
  import 'jsonwebtoken';
package/dist/client.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { P as ProtoClient } from './internals/index-CyXddWjz.js';
2
- export { c as classExtends, e as isFile, a as isObject, i as isQuery, d as isRole, b as isUser } from './internals/index-CyXddWjz.js';
1
+ import { P as ProtoClient } from './internals/index-cKx59cIc.js';
2
+ export { c as classExtends, e as isFile, a as isObject, i as isQuery, d as isRole, b as isUser } from './internals/index-cKx59cIc.js';
3
3
  export { Decimal } from 'decimal.js';
4
- export { D as DeserializeOptions, S as SerializeOptions, d as TSerializable, e as deserialize, s as serialize } from './internals/index-BgZqiNxF.js';
4
+ export { D as DeserializeOptions, S as SerializeOptions, d as TSerializable, e as deserialize, s as serialize } from './internals/index-YpB-hXxf.js';
5
5
  import '@o2ter/utils-js';
6
6
  import 'socket.io-client';
7
7
  import '@socket.io/component-emitter';
package/dist/client.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var index = require('./internals/index-DwCNTJb5.js');
5
+ var index = require('./internals/index-BnDahEvz.js');
6
6
  var Decimal = require('decimal.js');
7
7
  require('./internals/index-DRq6T_5w.js');
8
8
  require('lodash');
package/dist/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { e as ProtoClient } from './internals/index-CPodB1P0.mjs';
2
- export { f as classExtends, d as deserialize, k as isFile, g as isObject, i as isQuery, j as isRole, h as isUser, s as serialize } from './internals/index-CPodB1P0.mjs';
1
+ import { e as ProtoClient } from './internals/index-CmJh3t_k.mjs';
2
+ export { f as classExtends, d as deserialize, k as isFile, g as isObject, i as isQuery, j as isRole, h as isUser, s as serialize } from './internals/index-CmJh3t_k.mjs';
3
3
  export { Decimal } from 'decimal.js';
4
4
  import './internals/index-DyjcBbS1.mjs';
5
5
  import 'lodash';
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import * as socket_io from 'socket.io';
2
2
  import { Router } from 'express';
3
3
  import { Server } from '@o2ter/server-js';
4
- import { T as TSchema, _ as _TValue, P as ProtoService, a as ProtoServiceOptions, b as ProtoServiceKeyOptions } from './internals/index-BgZqiNxF.js';
5
- export { D as DeserializeOptions, S as SerializeOptions, c as TFileStorage, d as TSerializable, e as deserialize, s as serialize } from './internals/index-BgZqiNxF.js';
4
+ import { T as TSchema, _ as _TValue, P as ProtoService, a as ProtoServiceOptions, b as ProtoServiceKeyOptions } from './internals/index-YpB-hXxf.js';
5
+ export { D as DeserializeOptions, S as SerializeOptions, c as TFileStorage, d as TSerializable, e as deserialize, s as serialize } from './internals/index-YpB-hXxf.js';
6
6
  import Decimal from 'decimal.js';
7
7
  export { Decimal } from 'decimal.js';
8
- export { P as ProtoClient, c as classExtends, e as isFile, a as isObject, i as isQuery, d as isRole, b as isUser } from './internals/index-CyXddWjz.js';
8
+ export { P as ProtoClient, c as classExtends, e as isFile, a as isObject, i as isQuery, d as isRole, b as isUser } from './internals/index-cKx59cIc.js';
9
9
  import '@o2ter/utils-js';
10
10
  import 'jsonwebtoken';
11
11
  import 'lodash';
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ var serverJs = require('@o2ter/server-js');
7
7
  var random = require('./internals/random-CDtFUuES.js');
8
8
  var _private = require('./internals/private-Ciddhure.js');
9
9
  var utilsJs = require('@o2ter/utils-js');
10
- var index = require('./internals/index-DwCNTJb5.js');
10
+ var index = require('./internals/index-BnDahEvz.js');
11
11
  var index$1 = require('./internals/index-DRq6T_5w.js');
12
12
  var jwt = require('jsonwebtoken');
13
13
  var node_buffer = require('node:buffer');
@@ -523,6 +523,61 @@ const defaultSchema = {
523
523
  },
524
524
  ],
525
525
  },
526
+ '_Job': {
527
+ fields: {
528
+ name: 'string',
529
+ data: 'object',
530
+ error: 'object',
531
+ user: { type: 'pointer', target: 'User' },
532
+ startedAt: 'date',
533
+ completedAt: 'date',
534
+ locks: { type: 'relation', target: '_JobScope', foreignField: 'job' },
535
+ },
536
+ classLevelPermissions: {
537
+ find: [],
538
+ count: [],
539
+ create: [],
540
+ update: [],
541
+ delete: [],
542
+ },
543
+ fieldLevelPermissions: {
544
+ name: { update: [] },
545
+ data: { update: [] },
546
+ error: { update: [] },
547
+ user: { update: [] },
548
+ startedAt: { create: [], update: [] },
549
+ completedAt: { create: [], update: [] },
550
+ _expired_at: { create: [], update: [] },
551
+ },
552
+ indexes: [
553
+ { keys: { completedAt: 1, _created_at: 1 } },
554
+ ],
555
+ },
556
+ '_JobScope': {
557
+ fields: {
558
+ scope: 'string',
559
+ job: { type: 'pointer', target: '_Job' },
560
+ },
561
+ classLevelPermissions: {
562
+ get: [],
563
+ find: [],
564
+ count: [],
565
+ create: [],
566
+ update: [],
567
+ delete: [],
568
+ },
569
+ fieldLevelPermissions: {
570
+ scope: { update: [] },
571
+ job: { update: [] },
572
+ _expired_at: { create: [], update: [] },
573
+ },
574
+ indexes: [
575
+ {
576
+ keys: { scope: 1 },
577
+ unique: true,
578
+ },
579
+ ],
580
+ },
526
581
  };
527
582
 
528
583
  //
@@ -755,6 +810,8 @@ const mergeSchema = (...schemas) => _.reduce(schemas, (acc, schema) => ({
755
810
  class ProtoInternal {
756
811
  options;
757
812
  functions = {};
813
+ jobs = {};
814
+ jobRunner = new JobRunner();
758
815
  constructor(options) {
759
816
  validateSchemaName(options.schema);
760
817
  const schema = mergeSchema(defaultSchema, options.fileStorage.schema, options.schema);
@@ -773,6 +830,9 @@ class ProtoInternal {
773
830
  async prepare() {
774
831
  await this.options.storage.prepare(this.options.schema);
775
832
  }
833
+ shutdown() {
834
+ this.jobRunner.shutdown();
835
+ }
776
836
  generateId() {
777
837
  return random.generateId(this.options.objectIdSize);
778
838
  }
@@ -789,7 +849,7 @@ class ProtoInternal {
789
849
  return this.options.storage.setConfig(normalize(values), normalize(acl));
790
850
  }
791
851
  async run(proto, name, payload, options) {
792
- const func = this.functions?.[name];
852
+ const func = this.functions[name];
793
853
  if (_.isNil(func))
794
854
  throw Error('Function not found');
795
855
  if (_.isFunction(func))
@@ -1039,6 +1099,127 @@ class ProtoInternal {
1039
1099
  const storage = _serviceOf(options)?.storage ?? this.options.storage;
1040
1100
  return storage.refs(object, classNames, options?.master ? undefined : roles);
1041
1101
  }
1102
+ async scheduleJob(proto, name, params, options) {
1103
+ const opt = this.jobs[name];
1104
+ if (_.isNil(opt))
1105
+ throw Error('Job not found');
1106
+ const user = await proto.currentUser();
1107
+ if (!_.isFunction(opt)) {
1108
+ const roles = await proto.currentRoles();
1109
+ const { validator } = opt;
1110
+ if (!options?.master) {
1111
+ if (!!validator?.requireUser && !user)
1112
+ throw Error('No permission');
1113
+ if (!!validator?.requireMaster)
1114
+ throw Error('No permission');
1115
+ if (_.isArray(validator?.requireAnyUserRoles) && !_.some(validator?.requireAnyUserRoles, x => _.includes(roles, x)))
1116
+ throw Error('No permission');
1117
+ if (_.isArray(validator?.requireAllUserRoles) && _.some(validator?.requireAllUserRoles, x => !_.includes(roles, x)))
1118
+ throw Error('No permission');
1119
+ }
1120
+ }
1121
+ const obj = proto.Object('_Job');
1122
+ obj.set('name', name);
1123
+ obj.set('data', params);
1124
+ obj.set('user', user);
1125
+ await obj.save({ master: true });
1126
+ this.jobRunner.excuteJob(proto);
1127
+ return obj;
1128
+ }
1129
+ }
1130
+ class JobRunner {
1131
+ _running = false;
1132
+ _stopped = false;
1133
+ static TIMEOUT = 1000 * 60 * 5;
1134
+ static HEALTH = 1000 * 60;
1135
+ shutdown() {
1136
+ this._stopped = true;
1137
+ }
1138
+ async cleanUpOldJobs(proto) {
1139
+ await proto.Query('_JobScope').or(q => q.lessThan('_updated_at', new Date(Date.now() - JobRunner.TIMEOUT)), q => q.notEqualTo('job.completedAt', null)).deleteMany({ master: true });
1140
+ }
1141
+ async getAvailableJobs(proto) {
1142
+ const running = _.map(await proto.Query('_JobScope').find({ master: true }), x => x.get('scope'));
1143
+ const availableJobs = _.pickBy(proto[_private.PVK].jobs, opt => {
1144
+ return _.isFunction(opt) || _.isEmpty(_.intersection(opt.scopes ?? [], running));
1145
+ });
1146
+ return _.keys(availableJobs);
1147
+ }
1148
+ async getNextJob(proto) {
1149
+ const availableJobs = await this.getAvailableJobs(proto);
1150
+ return await proto.Query('_Job')
1151
+ .containsIn('name', availableJobs)
1152
+ .or(q => q.lessThan('startedAt', new Date(Date.now() - JobRunner.TIMEOUT)), q => q.equalTo('startedAt', null))
1153
+ .equalTo('completedAt', null)
1154
+ .empty('locks')
1155
+ .includes('*', 'user')
1156
+ .sort({ _created_at: 1 })
1157
+ .first({ master: true });
1158
+ }
1159
+ async startJob(proto, job, opt) {
1160
+ await proto.withTransaction(async (session) => {
1161
+ for (const scope of _.isFunction(opt) ? [] : opt.scopes ?? []) {
1162
+ const obj = session.Object('_JobScope');
1163
+ obj.set('scope', scope);
1164
+ obj.set('job', job);
1165
+ await obj.save({ master: true });
1166
+ }
1167
+ job.set('startedAt', new Date());
1168
+ await job.save({ master: true, session });
1169
+ });
1170
+ }
1171
+ async updateJobScope(proto, job) {
1172
+ try {
1173
+ await proto.Query('_JobScope').equalTo('job', job).updateOne({}, { master: true });
1174
+ }
1175
+ catch (e) { }
1176
+ }
1177
+ async executeJobFunction(proto, job, opt) {
1178
+ const payload = Object.setPrototypeOf({ params: job.data, user: job.user, job }, this);
1179
+ const func = _.isFunction(opt) ? opt : opt.callback;
1180
+ await func(proxy(payload));
1181
+ }
1182
+ async finalizeJob(job, error = null) {
1183
+ if (error)
1184
+ job.set('error', _.pick(error, _.uniq(_.flatMap(utilsJs.prototypes(error), x => Object.getOwnPropertyNames(x)))));
1185
+ job.set('completedAt', new Date());
1186
+ await job.save({ master: true });
1187
+ }
1188
+ async excuteJob(proto) {
1189
+ if (this._running || this._stopped)
1190
+ return;
1191
+ this._running = true;
1192
+ while (!this._stopped) {
1193
+ await this.cleanUpOldJobs(proto);
1194
+ const job = await this.getNextJob(proto);
1195
+ if (!job)
1196
+ break;
1197
+ const opt = proto[_private.PVK].jobs[job.name];
1198
+ if (_.isNil(opt))
1199
+ continue;
1200
+ try {
1201
+ await this.startJob(proto, job, opt);
1202
+ }
1203
+ catch (e) {
1204
+ continue;
1205
+ }
1206
+ (async () => {
1207
+ const timer = setInterval(() => this.updateJobScope(proto, job), JobRunner.HEALTH);
1208
+ try {
1209
+ await this.executeJobFunction(proto, job, opt);
1210
+ await this.finalizeJob(job);
1211
+ }
1212
+ catch (e) {
1213
+ await this.finalizeJob(job, e);
1214
+ }
1215
+ finally {
1216
+ clearInterval(timer);
1217
+ }
1218
+ this.excuteJob(proto);
1219
+ })();
1220
+ }
1221
+ this._running = false;
1222
+ }
1042
1223
  }
1043
1224
 
1044
1225
  //
@@ -1209,7 +1390,10 @@ const signUser = async (proto, res, user, options) => {
1209
1390
  const scheduleOp = {
1210
1391
  expireDocument: async (proto) => {
1211
1392
  await proto.gc();
1212
- }
1393
+ },
1394
+ excuteJob: async (proto) => {
1395
+ proto[_private.PVK].jobRunner.excuteJob(proto);
1396
+ },
1213
1397
  };
1214
1398
  const schedule = (proto) => {
1215
1399
  let running = false;
@@ -1297,6 +1481,7 @@ class ProtoService extends index.ProtoType {
1297
1481
  }
1298
1482
  async shutdown() {
1299
1483
  this._schedule.destroy();
1484
+ this[_private.PVK].shutdown();
1300
1485
  }
1301
1486
  classes() {
1302
1487
  return _.keys(this[_private.PVK].options.schema);
@@ -1436,6 +1621,12 @@ class ProtoService extends index.ProtoType {
1436
1621
  define(name, callback, options) {
1437
1622
  this[_private.PVK].functions[name] = options ? { callback, ...options } : callback;
1438
1623
  }
1624
+ scheduleJob(name, params, options) {
1625
+ return this[_private.PVK].scheduleJob(this, name, params, options);
1626
+ }
1627
+ defineJob(name, callback, options) {
1628
+ this[_private.PVK].jobs[name] = options ? { callback, ...options } : callback;
1629
+ }
1439
1630
  lockTable(className, update) {
1440
1631
  return this.storage.lockTable(className, update);
1441
1632
  }
@@ -1886,6 +2077,55 @@ var functionRoute = (router, proto) => {
1886
2077
  return router;
1887
2078
  };
1888
2079
 
2080
+ //
2081
+ // job.ts
2082
+ //
2083
+ // The MIT License
2084
+ // Copyright (c) 2021 - 2025 O2ter Limited. All rights reserved.
2085
+ //
2086
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
2087
+ // of this software and associated documentation files (the "Software"), to deal
2088
+ // in the Software without restriction, including without limitation the rights
2089
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2090
+ // copies of the Software, and to permit persons to whom the Software is
2091
+ // furnished to do so, subject to the following conditions:
2092
+ //
2093
+ // The above copyright notice and this permission notice shall be included in
2094
+ // all copies or substantial portions of the Software.
2095
+ //
2096
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2097
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2098
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2099
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2100
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2101
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2102
+ // THE SOFTWARE.
2103
+ //
2104
+ var jobRoute = (router, proto) => {
2105
+ router.get('/jobs/:name', async (req, res) => {
2106
+ res.setHeader('Cache-Control', ['no-cache', 'no-store']);
2107
+ const { name } = req.params;
2108
+ if (_.isNil(proto[_private.PVK].jobs[name]))
2109
+ return void res.sendStatus(404);
2110
+ await response(res, () => {
2111
+ const payload = proto.connect(req);
2112
+ return payload[_private.PVK].scheduleJob(payload, name, null, { master: payload.isMaster });
2113
+ });
2114
+ });
2115
+ router.post('/jobs/:name', serverJs.Server.text({ type: '*/*' }), async (req, res) => {
2116
+ res.setHeader('Cache-Control', ['no-cache', 'no-store']);
2117
+ const { name } = req.params;
2118
+ if (_.isNil(proto[_private.PVK].jobs[name]))
2119
+ return void res.sendStatus(404);
2120
+ await response(res, () => {
2121
+ const payload = proto.connect(req);
2122
+ const params = payload.rebind(index.deserialize(req.body, { objAttrs: index$1.TObject.defaultReadonlyKeys }));
2123
+ return payload[_private.PVK].scheduleJob(payload, name, params, { master: payload.isMaster });
2124
+ });
2125
+ });
2126
+ return router;
2127
+ };
2128
+
1889
2129
  //
1890
2130
  // files.ts
1891
2131
  //
@@ -2280,6 +2520,7 @@ const ProtoRoute = async (options) => {
2280
2520
  });
2281
2521
  classesRoute(router, proto);
2282
2522
  functionRoute(router, proto);
2523
+ jobRoute(router, proto);
2283
2524
  filesRoute(router, proto);
2284
2525
  userRoute(router, proto);
2285
2526
  notifyRoute(router, proto);