proto.io 0.0.179 → 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.
- package/dist/adapters/file/database.d.ts +2 -2
- package/dist/adapters/file/filesystem.d.ts +2 -2
- package/dist/adapters/file/google-cloud-storage.d.ts +2 -2
- package/dist/adapters/storage/progres.d.ts +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/client.mjs +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +244 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +245 -4
- package/dist/index.mjs.map +1 -1
- package/dist/internals/{index-B7qz_VeO.d.ts → index-BZupzq1Y.d.ts} +2 -2
- package/dist/internals/index-BZupzq1Y.d.ts.map +1 -0
- package/dist/internals/{index-DwCNTJb5.js → index-BnDahEvz.js} +85 -1
- package/dist/internals/index-BnDahEvz.js.map +1 -0
- package/dist/internals/{index-CPodB1P0.mjs → index-CmJh3t_k.mjs} +85 -1
- package/dist/internals/index-CmJh3t_k.mjs.map +1 -0
- package/dist/internals/{index-BgZqiNxF.d.ts → index-YpB-hXxf.d.ts} +84 -1
- package/dist/internals/index-YpB-hXxf.d.ts.map +1 -0
- package/dist/internals/{index-CyXddWjz.d.ts → index-cKx59cIc.d.ts} +3 -2
- package/dist/internals/index-cKx59cIc.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/internals/index-B7qz_VeO.d.ts.map +0 -1
- package/dist/internals/index-BgZqiNxF.d.ts.map +0 -1
- package/dist/internals/index-CPodB1P0.mjs.map +0 -1
- package/dist/internals/index-CyXddWjz.d.ts.map +0 -1
- package/dist/internals/index-DwCNTJb5.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -3,8 +3,8 @@ import { Server } from '@o2ter/server-js';
|
|
|
3
3
|
import { Q as QueryValidator, r as resolveColumn, a as resolveDataType, g as generateId } from './internals/random-BSyWEK8G.mjs';
|
|
4
4
|
import { P as PVK } from './internals/private-CNw40LZ7.mjs';
|
|
5
5
|
import { asyncStream, prototypes, isBinaryData, base64ToBuffer } from '@o2ter/utils-js';
|
|
6
|
-
import { T as TQuery, P as PROTO_NOTY_MSG, M as MASTER_USER_HEADER_NAME, a as MASTER_PASS_HEADER_NAME, A as AUTH_COOKIE_KEY, b as TUser, c as ProtoType, s as serialize, d as deserialize, U as UPLOAD_TOKEN_HEADER_NAME } from './internals/index-
|
|
7
|
-
export { e as ProtoClient, f as classExtends, k as isFile, g as isObject, i as isQuery, j as isRole, h as isUser } from './internals/index-
|
|
6
|
+
import { T as TQuery, P as PROTO_NOTY_MSG, M as MASTER_USER_HEADER_NAME, a as MASTER_PASS_HEADER_NAME, A as AUTH_COOKIE_KEY, b as TUser, c as ProtoType, s as serialize, d as deserialize, U as UPLOAD_TOKEN_HEADER_NAME } from './internals/index-CmJh3t_k.mjs';
|
|
7
|
+
export { e as ProtoClient, f as classExtends, k as isFile, g as isObject, i as isQuery, j as isRole, h as isUser } from './internals/index-CmJh3t_k.mjs';
|
|
8
8
|
import { i as isPointer, a as isRelation, T as TObject, b as isShape, d as defaultObjectKeyTypes, c as isPrimitive } from './internals/index-DyjcBbS1.mjs';
|
|
9
9
|
import jwt from 'jsonwebtoken';
|
|
10
10
|
import { Blob } from 'node:buffer';
|
|
@@ -520,6 +520,61 @@ const defaultSchema = {
|
|
|
520
520
|
},
|
|
521
521
|
],
|
|
522
522
|
},
|
|
523
|
+
'_Job': {
|
|
524
|
+
fields: {
|
|
525
|
+
name: 'string',
|
|
526
|
+
data: 'object',
|
|
527
|
+
error: 'object',
|
|
528
|
+
user: { type: 'pointer', target: 'User' },
|
|
529
|
+
startedAt: 'date',
|
|
530
|
+
completedAt: 'date',
|
|
531
|
+
locks: { type: 'relation', target: '_JobScope', foreignField: 'job' },
|
|
532
|
+
},
|
|
533
|
+
classLevelPermissions: {
|
|
534
|
+
find: [],
|
|
535
|
+
count: [],
|
|
536
|
+
create: [],
|
|
537
|
+
update: [],
|
|
538
|
+
delete: [],
|
|
539
|
+
},
|
|
540
|
+
fieldLevelPermissions: {
|
|
541
|
+
name: { update: [] },
|
|
542
|
+
data: { update: [] },
|
|
543
|
+
error: { update: [] },
|
|
544
|
+
user: { update: [] },
|
|
545
|
+
startedAt: { create: [], update: [] },
|
|
546
|
+
completedAt: { create: [], update: [] },
|
|
547
|
+
_expired_at: { create: [], update: [] },
|
|
548
|
+
},
|
|
549
|
+
indexes: [
|
|
550
|
+
{ keys: { completedAt: 1, _created_at: 1 } },
|
|
551
|
+
],
|
|
552
|
+
},
|
|
553
|
+
'_JobScope': {
|
|
554
|
+
fields: {
|
|
555
|
+
scope: 'string',
|
|
556
|
+
job: { type: 'pointer', target: '_Job' },
|
|
557
|
+
},
|
|
558
|
+
classLevelPermissions: {
|
|
559
|
+
get: [],
|
|
560
|
+
find: [],
|
|
561
|
+
count: [],
|
|
562
|
+
create: [],
|
|
563
|
+
update: [],
|
|
564
|
+
delete: [],
|
|
565
|
+
},
|
|
566
|
+
fieldLevelPermissions: {
|
|
567
|
+
scope: { update: [] },
|
|
568
|
+
job: { update: [] },
|
|
569
|
+
_expired_at: { create: [], update: [] },
|
|
570
|
+
},
|
|
571
|
+
indexes: [
|
|
572
|
+
{
|
|
573
|
+
keys: { scope: 1 },
|
|
574
|
+
unique: true,
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
},
|
|
523
578
|
};
|
|
524
579
|
|
|
525
580
|
//
|
|
@@ -752,6 +807,8 @@ const mergeSchema = (...schemas) => _.reduce(schemas, (acc, schema) => ({
|
|
|
752
807
|
class ProtoInternal {
|
|
753
808
|
options;
|
|
754
809
|
functions = {};
|
|
810
|
+
jobs = {};
|
|
811
|
+
jobRunner = new JobRunner();
|
|
755
812
|
constructor(options) {
|
|
756
813
|
validateSchemaName(options.schema);
|
|
757
814
|
const schema = mergeSchema(defaultSchema, options.fileStorage.schema, options.schema);
|
|
@@ -770,6 +827,9 @@ class ProtoInternal {
|
|
|
770
827
|
async prepare() {
|
|
771
828
|
await this.options.storage.prepare(this.options.schema);
|
|
772
829
|
}
|
|
830
|
+
shutdown() {
|
|
831
|
+
this.jobRunner.shutdown();
|
|
832
|
+
}
|
|
773
833
|
generateId() {
|
|
774
834
|
return generateId(this.options.objectIdSize);
|
|
775
835
|
}
|
|
@@ -786,7 +846,7 @@ class ProtoInternal {
|
|
|
786
846
|
return this.options.storage.setConfig(normalize(values), normalize(acl));
|
|
787
847
|
}
|
|
788
848
|
async run(proto, name, payload, options) {
|
|
789
|
-
const func = this.functions
|
|
849
|
+
const func = this.functions[name];
|
|
790
850
|
if (_.isNil(func))
|
|
791
851
|
throw Error('Function not found');
|
|
792
852
|
if (_.isFunction(func))
|
|
@@ -1036,6 +1096,127 @@ class ProtoInternal {
|
|
|
1036
1096
|
const storage = _serviceOf(options)?.storage ?? this.options.storage;
|
|
1037
1097
|
return storage.refs(object, classNames, options?.master ? undefined : roles);
|
|
1038
1098
|
}
|
|
1099
|
+
async scheduleJob(proto, name, params, options) {
|
|
1100
|
+
const opt = this.jobs[name];
|
|
1101
|
+
if (_.isNil(opt))
|
|
1102
|
+
throw Error('Job not found');
|
|
1103
|
+
const user = await proto.currentUser();
|
|
1104
|
+
if (!_.isFunction(opt)) {
|
|
1105
|
+
const roles = await proto.currentRoles();
|
|
1106
|
+
const { validator } = opt;
|
|
1107
|
+
if (!options?.master) {
|
|
1108
|
+
if (!!validator?.requireUser && !user)
|
|
1109
|
+
throw Error('No permission');
|
|
1110
|
+
if (!!validator?.requireMaster)
|
|
1111
|
+
throw Error('No permission');
|
|
1112
|
+
if (_.isArray(validator?.requireAnyUserRoles) && !_.some(validator?.requireAnyUserRoles, x => _.includes(roles, x)))
|
|
1113
|
+
throw Error('No permission');
|
|
1114
|
+
if (_.isArray(validator?.requireAllUserRoles) && _.some(validator?.requireAllUserRoles, x => !_.includes(roles, x)))
|
|
1115
|
+
throw Error('No permission');
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const obj = proto.Object('_Job');
|
|
1119
|
+
obj.set('name', name);
|
|
1120
|
+
obj.set('data', params);
|
|
1121
|
+
obj.set('user', user);
|
|
1122
|
+
await obj.save({ master: true });
|
|
1123
|
+
this.jobRunner.excuteJob(proto);
|
|
1124
|
+
return obj;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
class JobRunner {
|
|
1128
|
+
_running = false;
|
|
1129
|
+
_stopped = false;
|
|
1130
|
+
static TIMEOUT = 1000 * 60 * 5;
|
|
1131
|
+
static HEALTH = 1000 * 60;
|
|
1132
|
+
shutdown() {
|
|
1133
|
+
this._stopped = true;
|
|
1134
|
+
}
|
|
1135
|
+
async cleanUpOldJobs(proto) {
|
|
1136
|
+
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 });
|
|
1137
|
+
}
|
|
1138
|
+
async getAvailableJobs(proto) {
|
|
1139
|
+
const running = _.map(await proto.Query('_JobScope').find({ master: true }), x => x.get('scope'));
|
|
1140
|
+
const availableJobs = _.pickBy(proto[PVK].jobs, opt => {
|
|
1141
|
+
return _.isFunction(opt) || _.isEmpty(_.intersection(opt.scopes ?? [], running));
|
|
1142
|
+
});
|
|
1143
|
+
return _.keys(availableJobs);
|
|
1144
|
+
}
|
|
1145
|
+
async getNextJob(proto) {
|
|
1146
|
+
const availableJobs = await this.getAvailableJobs(proto);
|
|
1147
|
+
return await proto.Query('_Job')
|
|
1148
|
+
.containsIn('name', availableJobs)
|
|
1149
|
+
.or(q => q.lessThan('startedAt', new Date(Date.now() - JobRunner.TIMEOUT)), q => q.equalTo('startedAt', null))
|
|
1150
|
+
.equalTo('completedAt', null)
|
|
1151
|
+
.empty('locks')
|
|
1152
|
+
.includes('*', 'user')
|
|
1153
|
+
.sort({ _created_at: 1 })
|
|
1154
|
+
.first({ master: true });
|
|
1155
|
+
}
|
|
1156
|
+
async startJob(proto, job, opt) {
|
|
1157
|
+
await proto.withTransaction(async (session) => {
|
|
1158
|
+
for (const scope of _.isFunction(opt) ? [] : opt.scopes ?? []) {
|
|
1159
|
+
const obj = session.Object('_JobScope');
|
|
1160
|
+
obj.set('scope', scope);
|
|
1161
|
+
obj.set('job', job);
|
|
1162
|
+
await obj.save({ master: true });
|
|
1163
|
+
}
|
|
1164
|
+
job.set('startedAt', new Date());
|
|
1165
|
+
await job.save({ master: true, session });
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
async updateJobScope(proto, job) {
|
|
1169
|
+
try {
|
|
1170
|
+
await proto.Query('_JobScope').equalTo('job', job).updateOne({}, { master: true });
|
|
1171
|
+
}
|
|
1172
|
+
catch (e) { }
|
|
1173
|
+
}
|
|
1174
|
+
async executeJobFunction(proto, job, opt) {
|
|
1175
|
+
const payload = Object.setPrototypeOf({ params: job.data, user: job.user, job }, this);
|
|
1176
|
+
const func = _.isFunction(opt) ? opt : opt.callback;
|
|
1177
|
+
await func(proxy(payload));
|
|
1178
|
+
}
|
|
1179
|
+
async finalizeJob(job, error = null) {
|
|
1180
|
+
if (error)
|
|
1181
|
+
job.set('error', _.pick(error, _.uniq(_.flatMap(prototypes(error), x => Object.getOwnPropertyNames(x)))));
|
|
1182
|
+
job.set('completedAt', new Date());
|
|
1183
|
+
await job.save({ master: true });
|
|
1184
|
+
}
|
|
1185
|
+
async excuteJob(proto) {
|
|
1186
|
+
if (this._running || this._stopped)
|
|
1187
|
+
return;
|
|
1188
|
+
this._running = true;
|
|
1189
|
+
while (!this._stopped) {
|
|
1190
|
+
await this.cleanUpOldJobs(proto);
|
|
1191
|
+
const job = await this.getNextJob(proto);
|
|
1192
|
+
if (!job)
|
|
1193
|
+
break;
|
|
1194
|
+
const opt = proto[PVK].jobs[job.name];
|
|
1195
|
+
if (_.isNil(opt))
|
|
1196
|
+
continue;
|
|
1197
|
+
try {
|
|
1198
|
+
await this.startJob(proto, job, opt);
|
|
1199
|
+
}
|
|
1200
|
+
catch (e) {
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
(async () => {
|
|
1204
|
+
const timer = setInterval(() => this.updateJobScope(proto, job), JobRunner.HEALTH);
|
|
1205
|
+
try {
|
|
1206
|
+
await this.executeJobFunction(proto, job, opt);
|
|
1207
|
+
await this.finalizeJob(job);
|
|
1208
|
+
}
|
|
1209
|
+
catch (e) {
|
|
1210
|
+
await this.finalizeJob(job, e);
|
|
1211
|
+
}
|
|
1212
|
+
finally {
|
|
1213
|
+
clearInterval(timer);
|
|
1214
|
+
}
|
|
1215
|
+
this.excuteJob(proto);
|
|
1216
|
+
})();
|
|
1217
|
+
}
|
|
1218
|
+
this._running = false;
|
|
1219
|
+
}
|
|
1039
1220
|
}
|
|
1040
1221
|
|
|
1041
1222
|
//
|
|
@@ -1206,7 +1387,10 @@ const signUser = async (proto, res, user, options) => {
|
|
|
1206
1387
|
const scheduleOp = {
|
|
1207
1388
|
expireDocument: async (proto) => {
|
|
1208
1389
|
await proto.gc();
|
|
1209
|
-
}
|
|
1390
|
+
},
|
|
1391
|
+
excuteJob: async (proto) => {
|
|
1392
|
+
proto[PVK].jobRunner.excuteJob(proto);
|
|
1393
|
+
},
|
|
1210
1394
|
};
|
|
1211
1395
|
const schedule = (proto) => {
|
|
1212
1396
|
let running = false;
|
|
@@ -1294,6 +1478,7 @@ class ProtoService extends ProtoType {
|
|
|
1294
1478
|
}
|
|
1295
1479
|
async shutdown() {
|
|
1296
1480
|
this._schedule.destroy();
|
|
1481
|
+
this[PVK].shutdown();
|
|
1297
1482
|
}
|
|
1298
1483
|
classes() {
|
|
1299
1484
|
return _.keys(this[PVK].options.schema);
|
|
@@ -1433,6 +1618,12 @@ class ProtoService extends ProtoType {
|
|
|
1433
1618
|
define(name, callback, options) {
|
|
1434
1619
|
this[PVK].functions[name] = options ? { callback, ...options } : callback;
|
|
1435
1620
|
}
|
|
1621
|
+
scheduleJob(name, params, options) {
|
|
1622
|
+
return this[PVK].scheduleJob(this, name, params, options);
|
|
1623
|
+
}
|
|
1624
|
+
defineJob(name, callback, options) {
|
|
1625
|
+
this[PVK].jobs[name] = options ? { callback, ...options } : callback;
|
|
1626
|
+
}
|
|
1436
1627
|
lockTable(className, update) {
|
|
1437
1628
|
return this.storage.lockTable(className, update);
|
|
1438
1629
|
}
|
|
@@ -1883,6 +2074,55 @@ var functionRoute = (router, proto) => {
|
|
|
1883
2074
|
return router;
|
|
1884
2075
|
};
|
|
1885
2076
|
|
|
2077
|
+
//
|
|
2078
|
+
// job.ts
|
|
2079
|
+
//
|
|
2080
|
+
// The MIT License
|
|
2081
|
+
// Copyright (c) 2021 - 2025 O2ter Limited. All rights reserved.
|
|
2082
|
+
//
|
|
2083
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
2084
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
2085
|
+
// in the Software without restriction, including without limitation the rights
|
|
2086
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
2087
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
2088
|
+
// furnished to do so, subject to the following conditions:
|
|
2089
|
+
//
|
|
2090
|
+
// The above copyright notice and this permission notice shall be included in
|
|
2091
|
+
// all copies or substantial portions of the Software.
|
|
2092
|
+
//
|
|
2093
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
2094
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
2095
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
2096
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
2097
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
2098
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
2099
|
+
// THE SOFTWARE.
|
|
2100
|
+
//
|
|
2101
|
+
var jobRoute = (router, proto) => {
|
|
2102
|
+
router.get('/jobs/:name', async (req, res) => {
|
|
2103
|
+
res.setHeader('Cache-Control', ['no-cache', 'no-store']);
|
|
2104
|
+
const { name } = req.params;
|
|
2105
|
+
if (_.isNil(proto[PVK].jobs[name]))
|
|
2106
|
+
return void res.sendStatus(404);
|
|
2107
|
+
await response(res, () => {
|
|
2108
|
+
const payload = proto.connect(req);
|
|
2109
|
+
return payload[PVK].scheduleJob(payload, name, null, { master: payload.isMaster });
|
|
2110
|
+
});
|
|
2111
|
+
});
|
|
2112
|
+
router.post('/jobs/:name', Server.text({ type: '*/*' }), async (req, res) => {
|
|
2113
|
+
res.setHeader('Cache-Control', ['no-cache', 'no-store']);
|
|
2114
|
+
const { name } = req.params;
|
|
2115
|
+
if (_.isNil(proto[PVK].jobs[name]))
|
|
2116
|
+
return void res.sendStatus(404);
|
|
2117
|
+
await response(res, () => {
|
|
2118
|
+
const payload = proto.connect(req);
|
|
2119
|
+
const params = payload.rebind(deserialize(req.body, { objAttrs: TObject.defaultReadonlyKeys }));
|
|
2120
|
+
return payload[PVK].scheduleJob(payload, name, params, { master: payload.isMaster });
|
|
2121
|
+
});
|
|
2122
|
+
});
|
|
2123
|
+
return router;
|
|
2124
|
+
};
|
|
2125
|
+
|
|
1886
2126
|
//
|
|
1887
2127
|
// files.ts
|
|
1888
2128
|
//
|
|
@@ -2277,6 +2517,7 @@ const ProtoRoute = async (options) => {
|
|
|
2277
2517
|
});
|
|
2278
2518
|
classesRoute(router, proto);
|
|
2279
2519
|
functionRoute(router, proto);
|
|
2520
|
+
jobRoute(router, proto);
|
|
2280
2521
|
filesRoute(router, proto);
|
|
2281
2522
|
userRoute(router, proto);
|
|
2282
2523
|
notifyRoute(router, proto);
|