@stemy/backend 6.0.2 → 6.0.4

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.
@@ -39,6 +39,7 @@ import { compare } from 'bcrypt';
39
39
  import moment from 'moment';
40
40
  import { GridFSBucket } from 'mongodb/lib/gridfs';
41
41
  import { writeFile as writeFile$2, rm } from 'fs/promises';
42
+ import got from 'got';
42
43
  import { getModelForClass } from '@typegoose/typegoose';
43
44
  import { getValue as getValue$1, setValue } from 'mongoose/lib/utils';
44
45
 
@@ -57,6 +58,8 @@ const DI_CONTAINER = Symbol.for("di-container-token");
57
58
  const OPENAPI_VALIDATION = Symbol.for("openapi-validation-token");
58
59
  const LOCAL_DIR = Symbol.for('asset-local-dir');
59
60
  const ASSET_DRIVER = Symbol.for('assets-driver');
61
+ const ASSET_MAIN_DRIVER = Symbol.for('assets-main-driver');
62
+ const ASSET_FALLBACK_DRIVER = Symbol.for('assets-fallback-driver');
60
63
  class Parameter {
61
64
  name;
62
65
  defaultValue;
@@ -1009,17 +1012,11 @@ class Asset extends BaseEntity {
1009
1012
  async unlink() {
1010
1013
  try {
1011
1014
  await this.driver.delete(this.oid);
1012
- await this.collection.deleteOne({ _id: this.oid });
1013
1015
  }
1014
1016
  catch (error) {
1015
- let err = error;
1016
- if (error) {
1017
- err = error.message || error || "";
1018
- if (!isString(err) || !err.startsWith("FileNotFound")) {
1019
- throw err;
1020
- }
1021
- }
1017
+ console.log("Failed to unlink", error?.message);
1022
1018
  }
1019
+ await this.collection.deleteOne({ _id: this.oid });
1023
1020
  return this.id;
1024
1021
  }
1025
1022
  async setMeta(metadata) {
@@ -1355,7 +1352,6 @@ let JobManager = class JobManager {
1355
1352
  jobTypes;
1356
1353
  jobs;
1357
1354
  messages;
1358
- processing;
1359
1355
  apiPush;
1360
1356
  apiPull;
1361
1357
  workerPush;
@@ -1373,7 +1369,9 @@ let JobManager = class JobManager {
1373
1369
  const messageBridge = {
1374
1370
  sendMessage: (message, params) => {
1375
1371
  params.uniqueId = uniqueId;
1376
- this.workerPush.send([message, JSON.stringify(params)]);
1372
+ this.workerPush.then(sock => {
1373
+ sock.send([message, JSON.stringify(params)]);
1374
+ });
1377
1375
  }
1378
1376
  };
1379
1377
  messageBridge.sendMessage(`job-started`, { name: jobName });
@@ -1382,7 +1380,6 @@ let JobManager = class JobManager {
1382
1380
  return res;
1383
1381
  }, {});
1384
1382
  this.messages = new Subject();
1385
- this.processing = null;
1386
1383
  this.maxTimeout = this.config.resolve("jobTimeout");
1387
1384
  }
1388
1385
  on(message, cb) {
@@ -1429,40 +1426,46 @@ let JobManager = class JobManager {
1429
1426
  });
1430
1427
  });
1431
1428
  }
1432
- async initProcessing() {
1429
+ async startProcessing() {
1433
1430
  const host = this.config.resolve("zmqRemoteHost");
1434
1431
  const pushHost = `${host}:${this.config.resolve("zmqBackPort")}`;
1435
- this.workerPush = socket("push");
1436
- this.workerPush.connect(pushHost);
1437
- this.logger.log("job-manager", `Worker producer connected to: ${pushHost}`);
1438
1432
  const pullHost = `${host}:${this.config.resolve("zmqPort")}`;
1439
- this.workerPull = socket("pull");
1440
- this.workerPull.connect(pullHost);
1441
- this.logger.log("job-manager", `Worker consumer connected to: ${pullHost}`);
1442
- this.workerPull.on("message", async (name, args, uniqId) => {
1443
- try {
1444
- const jobName = name.toString("utf8");
1445
- const jobParams = JSON.parse(args.toString("utf8"));
1446
- const uniqueId = uniqId?.toString("utf8");
1447
- console.time(uniqueId);
1448
- console.timeLog(uniqueId, `Started working on background job: ${colorize(jobName, ConsoleColor.FgCyan)} with args: \n${jsonHighlight(jobParams)}\n\n`);
1433
+ this.workerPush = this.workerPush || new Promise(resolve => {
1434
+ const sock = socket("push");
1435
+ sock.connect(pushHost);
1436
+ this.logger.log("job-manager", `Worker producer connected to: ${pushHost}`);
1437
+ resolve(sock);
1438
+ });
1439
+ this.workerPull = this.workerPull || new Promise(resolve => {
1440
+ const sock = socket("pull");
1441
+ sock.connect(pullHost);
1442
+ sock.on("message", async (name, args, uniqId) => {
1449
1443
  try {
1450
- await Promise.race([this.jobs[jobName](jobParams, uniqueId), promiseTimeout(this.maxTimeout, true)]);
1451
- console.timeLog(uniqueId, `Finished working on background job: ${colorize(jobName, ConsoleColor.FgCyan)}\n\n`);
1444
+ const jobName = name.toString("utf8");
1445
+ const jobParams = JSON.parse(args.toString("utf8"));
1446
+ const uniqueId = uniqId?.toString("utf8");
1447
+ console.time(uniqueId);
1448
+ console.timeLog(uniqueId, `Started working on background job: ${colorize(jobName, ConsoleColor.FgCyan)} with args: \n${jsonHighlight(jobParams)}\n\n`);
1449
+ try {
1450
+ await Promise.race([this.jobs[jobName](jobParams, uniqueId), promiseTimeout(this.maxTimeout, true)]);
1451
+ console.timeLog(uniqueId, `Finished working on background job: ${colorize(jobName, ConsoleColor.FgCyan)}\n\n`);
1452
+ }
1453
+ catch (e) {
1454
+ console.timeLog(uniqueId, `Background job failed: ${colorize(jobName, ConsoleColor.FgRed)}\n${e}\n\n`);
1455
+ }
1456
+ console.timeEnd(uniqueId);
1452
1457
  }
1453
1458
  catch (e) {
1454
- console.timeLog(uniqueId, `Background job failed: ${colorize(jobName, ConsoleColor.FgRed)}\n${e}\n\n`);
1459
+ this.logger.log("job-manager", `Failed to start job: ${e.message}`);
1455
1460
  }
1456
- console.timeEnd(uniqueId);
1457
- }
1458
- catch (e) {
1459
- this.logger.log("job-manager", `Failed to start job: ${e.message}`);
1460
- }
1461
+ });
1462
+ this.logger.log("job-manager", `Worker consumer connected to: ${pullHost}`);
1463
+ resolve(sock);
1461
1464
  });
1462
- }
1463
- startProcessing() {
1464
- this.processing = this.processing || this.initProcessing();
1465
- return this.processing;
1465
+ await Promise.allSettled([
1466
+ this.workerPush,
1467
+ this.workerPull,
1468
+ ]);
1466
1469
  }
1467
1470
  tryResolve(jobType, params) {
1468
1471
  const jobName = getConstructorName(jobType);
@@ -1487,28 +1490,32 @@ let JobManager = class JobManager {
1487
1490
  return this.tryResolveAndInit(jobType, params);
1488
1491
  }
1489
1492
  tryResolveAndInit(jobType, params) {
1490
- if (!this.apiPush) {
1493
+ this.apiPush = this.apiPush || new Promise(resolve => {
1491
1494
  const port = this.config.resolve("zmqPort");
1492
- this.apiPush = socket("push");
1493
- this.apiPush.bind(`tcp://0.0.0.0:${port}`);
1494
- this.logger.log("job-manager", `API producer bound to port: ${port}`);
1495
- }
1496
- if (!this.apiPull) {
1495
+ const sock = socket("push");
1496
+ sock.bind(`tcp://0.0.0.0:${port}`, () => {
1497
+ this.logger.log("job-manager", `API producer bound to port: ${port}`);
1498
+ resolve(sock);
1499
+ });
1500
+ });
1501
+ this.apiPull = this.apiPush || new Promise(resolve => {
1497
1502
  const backPort = this.config.resolve("zmqBackPort");
1498
- this.apiPull = socket("pull");
1499
- this.apiPull.bind(`tcp://0.0.0.0:${backPort}`);
1500
- this.apiPull.on("message", (name, args) => {
1501
- const message = name.toString("utf8");
1502
- const params = JSON.parse(args?.toString("utf8") || "{}");
1503
- const paramTypes = Object.keys(params).reduce((res, key) => {
1504
- res[key] = getType(params[key]);
1505
- return res;
1506
- }, {});
1507
- this.logger.log("job-manager", `Received a message from worker: "${colorize(message, ConsoleColor.FgCyan)}" with args: ${jsonHighlight(paramTypes)}\n\n`);
1508
- this.messages.next({ message, params });
1503
+ const sock = socket("pull");
1504
+ sock.bind(`tcp://0.0.0.0:${backPort}`, () => {
1505
+ sock.on("message", (name, args) => {
1506
+ const message = name.toString("utf8");
1507
+ const params = JSON.parse(args?.toString("utf8") || "{}");
1508
+ const paramTypes = Object.keys(params).reduce((res, key) => {
1509
+ res[key] = getType(params[key]);
1510
+ return res;
1511
+ }, {});
1512
+ this.logger.log("job-manager", `Received a message from worker: "${colorize(message, ConsoleColor.FgCyan)}" with args: ${jsonHighlight(paramTypes)}\n\n`);
1513
+ this.messages.next({ message, params });
1514
+ });
1515
+ this.logger.log("job-manager", `API consumer bound to port: ${backPort}`);
1516
+ resolve(sock);
1509
1517
  });
1510
- this.logger.log("job-manager", `API consumer bound to port: ${backPort}`);
1511
- }
1518
+ });
1512
1519
  return this.tryResolve(jobType, params);
1513
1520
  }
1514
1521
  resolveJobInstance(jobType, params, uniqueId = "") {
@@ -1522,7 +1529,8 @@ let JobManager = class JobManager {
1522
1529
  }
1523
1530
  async sendToWorkers(jobName, params) {
1524
1531
  const uniqueId = new ObjectId$1().toHexString();
1525
- this.apiPush.send([jobName, JSON.stringify(params), uniqueId]);
1532
+ const sock = await this.apiPush;
1533
+ sock.send([jobName, JSON.stringify(params), uniqueId]);
1526
1534
  return uniqueId;
1527
1535
  }
1528
1536
  };
@@ -4007,11 +4015,9 @@ const fixtures = [
4007
4015
  ];
4008
4016
 
4009
4017
  let AssetGridDriver = class AssetGridDriver {
4010
- metaCollection;
4011
4018
  bucket;
4012
4019
  constructor(connector) {
4013
4020
  this.bucket = new GridFSBucket(connector.database, { bucketName: 'assets' });
4014
- this.metaCollection = "assets.files";
4015
4021
  }
4016
4022
  openUploadStream(filename, opts) {
4017
4023
  return this.bucket.openUploadStream(filename, opts);
@@ -4030,10 +4036,8 @@ AssetGridDriver = __decorate([
4030
4036
 
4031
4037
  let AssetLocalDriver = class AssetLocalDriver {
4032
4038
  dir;
4033
- metaCollection;
4034
4039
  constructor(dir) {
4035
4040
  this.dir = dir;
4036
- this.metaCollection = "assets.local";
4037
4041
  }
4038
4042
  openUploadStream(filename, opts) {
4039
4043
  const id = new Types.ObjectId();
@@ -4062,6 +4066,144 @@ AssetLocalDriver = __decorate([
4062
4066
  __metadata("design:paramtypes", [String])
4063
4067
  ], AssetLocalDriver);
4064
4068
 
4069
+ function createFallbackReadable(primary, fallbackFactory) {
4070
+ const proxy = new PassThrough();
4071
+ let hasSwitched = false;
4072
+ const switchToFallback = () => {
4073
+ if (hasSwitched)
4074
+ return;
4075
+ hasSwitched = true;
4076
+ // 1. Cleanup the failing primary
4077
+ primary.unpipe(proxy);
4078
+ primary.destroy();
4079
+ // 2. Initialize and pipe the fallback
4080
+ const fallback = fallbackFactory();
4081
+ // On the fallback, we allow it to end the proxy naturally
4082
+ fallback.pipe(proxy);
4083
+ fallback.on('error', (err) => {
4084
+ proxy.emit('error', err); // If fallback fails too, it's a hard error
4085
+ });
4086
+ };
4087
+ // Pipe primary but prevent it from closing the proxy automatically
4088
+ primary.pipe(proxy, { end: false });
4089
+ primary.on('error', () => {
4090
+ switchToFallback();
4091
+ });
4092
+ // Handle the "Early End" case (e.g., stream closed before data finished)
4093
+ primary.on('end', () => {
4094
+ if (!hasSwitched) {
4095
+ // If we finished successfully without erroring, close the proxy
4096
+ proxy.end();
4097
+ }
4098
+ });
4099
+ return proxy;
4100
+ }
4101
+ /**
4102
+ * Creates a Writable proxy that switches to a fallback destination on error.
4103
+ * @param primary The initial Writable destination.
4104
+ * @param fallbackFactory A function that returns a new Writable destination.
4105
+ */
4106
+ function createFallbackWritable(primary, fallbackFactory) {
4107
+ // 1. The Proxy acts as the stable "entry point" for your data
4108
+ const proxy = new PassThrough();
4109
+ let currentDestination = primary;
4110
+ let hasSwitched = false;
4111
+ // Function to handle the swap
4112
+ const handleSwitch = (err) => {
4113
+ if (hasSwitched)
4114
+ return; // Prevent infinite loops if fallback also fails
4115
+ hasSwitched = true;
4116
+ // 2. Unpipe from the failed stream and destroy it
4117
+ proxy.unpipe(currentDestination);
4118
+ currentDestination.destroy();
4119
+ // 3. Create the new destination and pipe the remaining data to it
4120
+ const fallback = fallbackFactory();
4121
+ currentDestination = fallback;
4122
+ // Pipe the proxy into the fallback
4123
+ proxy.pipe(fallback);
4124
+ // Ensure errors on the fallback are bubbled up
4125
+ fallback.on('error', (fallbackErr) => {
4126
+ proxy.emit('error', fallbackErr);
4127
+ });
4128
+ };
4129
+ // Initial setup
4130
+ primary.on('error', handleSwitch);
4131
+ proxy.on('finish', () => {
4132
+ const final = proxy;
4133
+ final.id = currentDestination.id;
4134
+ final.done = true;
4135
+ });
4136
+ proxy.pipe(primary);
4137
+ return proxy;
4138
+ }
4139
+
4140
+ let AssetFallbackDriver = class AssetFallbackDriver {
4141
+ main;
4142
+ fallback;
4143
+ constructor(main, fallback) {
4144
+ this.main = main;
4145
+ this.fallback = fallback;
4146
+ }
4147
+ openDownloadStream(id) {
4148
+ return createFallbackReadable(this.main.openDownloadStream(id), () => this.fallback.openDownloadStream(id));
4149
+ }
4150
+ openUploadStream(filename, opts) {
4151
+ return createFallbackWritable(this.main.openUploadStream(filename, opts), () => this.fallback.openUploadStream(filename, opts));
4152
+ }
4153
+ async delete(id) {
4154
+ try {
4155
+ await this.main.delete(id);
4156
+ }
4157
+ catch (e) {
4158
+ console.log("Failed to delete from primary: ", e.message);
4159
+ await this.fallback.delete(id);
4160
+ }
4161
+ }
4162
+ };
4163
+ AssetFallbackDriver = __decorate([
4164
+ injectable(),
4165
+ __param(0, inject(ASSET_MAIN_DRIVER)),
4166
+ __param(1, inject(ASSET_FALLBACK_DRIVER)),
4167
+ __metadata("design:paramtypes", [Object, Object])
4168
+ ], AssetFallbackDriver);
4169
+
4170
+ let AssetStorageProxyDriver = class AssetStorageProxyDriver {
4171
+ config;
4172
+ baseUrl;
4173
+ url;
4174
+ constructor(config) {
4175
+ this.config = config;
4176
+ this.baseUrl = this.config.resolve("storageProxyUri");
4177
+ this.url = this.baseUrl + this.config.resolve("storageProxyBucket");
4178
+ }
4179
+ openDownloadStream(id) {
4180
+ return got.stream.get(this.getUrl(id));
4181
+ }
4182
+ openUploadStream(_, opts) {
4183
+ const id = new ObjectId$1();
4184
+ const stream = got.stream.put(this.getUrl(id), { headers: {
4185
+ 'Content-Type': opts?.contentType || 'application/octet-stream'
4186
+ } });
4187
+ stream.done = false;
4188
+ stream.on('finish', () => {
4189
+ stream.id = id;
4190
+ stream.done = true;
4191
+ });
4192
+ return stream;
4193
+ }
4194
+ async delete(id) {
4195
+ console.log("DELETE", id.toHexString());
4196
+ await got.delete(this.getUrl(id));
4197
+ }
4198
+ getUrl(id) {
4199
+ return `${this.url}/${id.toHexString()}`;
4200
+ }
4201
+ };
4202
+ AssetStorageProxyDriver = __decorate([
4203
+ injectable(),
4204
+ __metadata("design:paramtypes", [Configuration])
4205
+ ], AssetStorageProxyDriver);
4206
+
4065
4207
  class BaseDoc {
4066
4208
  _id;
4067
4209
  /**
@@ -4439,6 +4581,8 @@ function createServices() {
4439
4581
  new Parameter("mongoDb", "node-backend"),
4440
4582
  new Parameter("mongoUser", null),
4441
4583
  new Parameter("mongoPassword", null),
4584
+ new Parameter("storageProxyUri", "http://localhost:4500/", prepareUrl),
4585
+ new Parameter("storageProxyBucket", "something"),
4442
4586
  new Parameter("nodeEnv", "production"),
4443
4587
  new Parameter("appPort", 80),
4444
4588
  new Parameter("zmqPort", 3000),
@@ -4612,7 +4756,13 @@ async function setupBackend(config, providers, parent) {
4612
4756
  useValue: config.assetLocalDir || "assets_files"
4613
4757
  });
4614
4758
  diContainer.register(ASSET_DRIVER, {
4615
- useClass: config.assetDriver || AssetGridDriver
4759
+ useClass: config.assetDriver || AssetFallbackDriver
4760
+ });
4761
+ diContainer.register(ASSET_MAIN_DRIVER, {
4762
+ useClass: config.assetMainDriver || AssetGridDriver
4763
+ });
4764
+ diContainer.register(ASSET_FALLBACK_DRIVER, {
4765
+ useClass: config.assetFallbackDriver || AssetLocalDriver
4616
4766
  });
4617
4767
  diContainers.appContainer = diContainers.appContainer || diContainer;
4618
4768
  // Authentication
@@ -4683,5 +4833,5 @@ async function setupBackend(config, providers, parent) {
4683
4833
  * Generated bundle index. Do not edit.
4684
4834
  */
4685
4835
 
4686
- export { AssetGridDriver, AssetImageParams, AssetLocalDriver, AssetProcessor, AssetResolver, Assets, AuthController, BackendProvider, BaseDoc, Cache, CacheProcessor, Configuration, ConsoleColor, DI_CONTAINER, DocumentArray, EXPRESS, EndpointProvider, ErrorHandlerMiddleware, FIXTURE, Fixtures, Gallery, GalleryCache, GalleryController, HTTP_SERVER, IdGenerator, IsDocumented, IsFile, IsObjectId, JOB, JobManager, JsonResponse, LanguageMiddleware, LazyAssetGenerator, LazyAssets, Logger, MailSender, MemoryCache, MongoConnector, OPENAPI_VALIDATION, OpenApi, PARAMETER, Parameter, PrimitiveArray, Progresses, ResolveEntity, ResponseType, SOCKET_CONTROLLERS, SOCKET_SERVER, TERMINAL_COMMAND, TemplateRenderer, TerminalManager, TokenGenerator, TranslationProvider, Translator, Type, UserManager, assign, broadcast, bufferToStream, camelCaseToDash, colorize, convertValue, copy, copyStream, createIdString, createServices, createTransformer, deleteFile, deleteFromBucket, fileTypeFromBuffer, filter, firstItem, flatten, getConstructorName, getDirName, getExtension, getFileName, getFunctionParams, getType, getValue, groupBy, gunzipPromised, gzipPromised, hydratePopulated, idToString, injectServices, isArray, isBoolean, isBuffer, isConstructor, isDate, isDefined, isFunction, isInterface, isNullOrUndefined, isObject, isObjectId, isPrimitive, isString, isType, jsonHighlight, lastItem, lcFirst, letsLookupStage, lookupStages, matchField, matchFieldStages, matchStage, md5, mkdirRecursive, multiSubscription, observableFromFunction, padLeft, padRight, paginate, paginateAggregations, prepareUrl, prepareUrlEmpty, prepareUrlSlash, projectStage, promiseTimeout, rand, random, readAndDeleteFile, readFile, regexEscape, regroup, replaceSpecialChars, resolveUser, runCommand, service, setupBackend, streamToBuffer, toImage, ucFirst, uniqueItems, unwindStage, valueToPromise, wrapError, writeFile };
4836
+ export { AssetGridDriver, AssetImageParams, AssetLocalDriver, AssetProcessor, AssetResolver, AssetStorageProxyDriver, Assets, AuthController, BackendProvider, BaseDoc, Cache, CacheProcessor, Configuration, ConsoleColor, DI_CONTAINER, DocumentArray, EXPRESS, EndpointProvider, ErrorHandlerMiddleware, FIXTURE, Fixtures, Gallery, GalleryCache, GalleryController, HTTP_SERVER, IdGenerator, IsDocumented, IsFile, IsObjectId, JOB, JobManager, JsonResponse, LanguageMiddleware, LazyAssetGenerator, LazyAssets, Logger, MailSender, MemoryCache, MongoConnector, OPENAPI_VALIDATION, OpenApi, PARAMETER, Parameter, PrimitiveArray, Progresses, ResolveEntity, ResponseType, SOCKET_CONTROLLERS, SOCKET_SERVER, TERMINAL_COMMAND, TemplateRenderer, TerminalManager, TokenGenerator, TranslationProvider, Translator, Type, UserManager, assign, broadcast, bufferToStream, camelCaseToDash, colorize, convertValue, copy, copyStream, createIdString, createServices, createTransformer, deleteFile, deleteFromBucket, fileTypeFromBuffer, filter, firstItem, flatten, getConstructorName, getDirName, getExtension, getFileName, getFunctionParams, getType, getValue, groupBy, gunzipPromised, gzipPromised, hydratePopulated, idToString, injectServices, isArray, isBoolean, isBuffer, isConstructor, isDate, isDefined, isFunction, isInterface, isNullOrUndefined, isObject, isObjectId, isPrimitive, isString, isType, jsonHighlight, lastItem, lcFirst, letsLookupStage, lookupStages, matchField, matchFieldStages, matchStage, md5, mkdirRecursive, multiSubscription, observableFromFunction, padLeft, padRight, paginate, paginateAggregations, prepareUrl, prepareUrlEmpty, prepareUrlSlash, projectStage, promiseTimeout, rand, random, readAndDeleteFile, readFile, regexEscape, regroup, replaceSpecialChars, resolveUser, runCommand, service, setupBackend, streamToBuffer, toImage, ucFirst, uniqueItems, unwindStage, valueToPromise, wrapError, writeFile };
4687
4837
  //# sourceMappingURL=stemy-backend.mjs.map