@_linked/server 2.0.2 → 2.0.3

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 (49) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/cjs/backend.d.ts +6 -0
  3. package/lib/cjs/backend.js +58 -0
  4. package/lib/cjs/backend.js.map +1 -0
  5. package/lib/cjs/data/lincd-server.json +47 -0
  6. package/lib/cjs/index.d.ts +9 -0
  7. package/lib/cjs/index.js +13 -0
  8. package/lib/cjs/index.js.map +1 -0
  9. package/lib/cjs/ontologies/lincd-server.d.ts +31 -0
  10. package/lib/cjs/ontologies/lincd-server.js +86 -0
  11. package/lib/cjs/ontologies/lincd-server.js.map +1 -0
  12. package/lib/cjs/package.d.ts +4 -0
  13. package/lib/cjs/package.js +7 -0
  14. package/lib/cjs/package.js.map +1 -0
  15. package/lib/cjs/package.json +90 -0
  16. package/lib/cjs/shapes/LincdAPI.d.ts +41 -0
  17. package/lib/cjs/shapes/LincdAPI.js +124 -0
  18. package/lib/cjs/shapes/LincdAPI.js.map +1 -0
  19. package/lib/cjs/shapes/LincdServer.d.ts +79 -0
  20. package/lib/cjs/shapes/LincdServer.js +1339 -0
  21. package/lib/cjs/shapes/LincdServer.js.map +1 -0
  22. package/lib/cjs/shapes/LincdWebApp.d.ts +7 -0
  23. package/lib/cjs/shapes/LincdWebApp.js +43 -0
  24. package/lib/cjs/shapes/LincdWebApp.js.map +1 -0
  25. package/lib/cjs/shapes/filestores/LocalFileStore.d.ts +42 -0
  26. package/lib/cjs/shapes/filestores/LocalFileStore.js +146 -0
  27. package/lib/cjs/shapes/filestores/LocalFileStore.js.map +1 -0
  28. package/lib/cjs/shapes/quadstores/BackendAPIStore.d.ts +33 -0
  29. package/lib/cjs/shapes/quadstores/BackendAPIStore.js +79 -0
  30. package/lib/cjs/shapes/quadstores/BackendAPIStore.js.map +1 -0
  31. package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.d.ts +13 -0
  32. package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.js +30 -0
  33. package/lib/cjs/shapes/quadstores/BackendAPIStoreProvider.js.map +1 -0
  34. package/lib/cjs/types/RouteConfig.d.ts +56 -0
  35. package/lib/cjs/types/RouteConfig.js +3 -0
  36. package/lib/cjs/types/RouteConfig.js.map +1 -0
  37. package/lib/cjs/types.d.ts +12 -0
  38. package/lib/cjs/types.js +1 -0
  39. package/lib/cjs/types.js.map +1 -0
  40. package/lib/cjs/utils/Shapes.d.ts +7 -0
  41. package/lib/cjs/utils/Shapes.js +76 -0
  42. package/lib/cjs/utils/Shapes.js.map +1 -0
  43. package/lib/cjs/utils/accessUrl.d.ts +6 -0
  44. package/lib/cjs/utils/accessUrl.js +13 -0
  45. package/lib/cjs/utils/accessUrl.js.map +1 -0
  46. package/lib/esm/data/lincd-server.json +47 -0
  47. package/lib/esm/package.json +90 -0
  48. package/lib/esm/shapes/LincdServer.js.map +1 -1
  49. package/package.json +7 -4
@@ -0,0 +1,1339 @@
1
+ 'use strict';
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
45
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
46
+ return new (P || (P = Promise))(function (resolve, reject) {
47
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
48
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
49
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
50
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
51
+ });
52
+ };
53
+ var __importDefault = (this && this.__importDefault) || function (mod) {
54
+ return (mod && mod.__esModule) ? mod : { "default": mod };
55
+ };
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.LincdServer = void 0;
58
+ const chalk_1 = __importDefault(require("chalk"));
59
+ const events_1 = __importDefault(require("events"));
60
+ const express_1 = __importDefault(require("express"));
61
+ const fetch_cookie_1 = __importDefault(require("fetch-cookie"));
62
+ const fsNative = __importStar(require("fs"));
63
+ const fs = __importStar(require("fs/promises"));
64
+ const config_webpack_app_1 = require("@_linked/cli/config-webpack-app");
65
+ const cli_methods_1 = require("@_linked/cli/cli-methods");
66
+ const utils_1 = require("@_linked/cli/utils");
67
+ const AppContext_1 = require("@_linked/server-utils/components/AppContext");
68
+ const JSONParser_1 = require("@_linked/server-utils/utils/JSONParser");
69
+ const JSONWriter_1 = require("@_linked/server-utils/utils/JSONWriter");
70
+ const Server_1 = require("@_linked/server-utils/utils/Server");
71
+ const ShapeProvider_1 = require("@_linked/server-utils/utils/ShapeProvider");
72
+ const CoreMap_1 = require("@_linked/core/collections/CoreMap");
73
+ const Shape_1 = require("@_linked/core/shapes/Shape");
74
+ const LinkedErrorLogging_1 = require("@_linked/core/utils/LinkedErrorLogging");
75
+ const LinkedFileStorage_1 = require("@_linked/core/utils/LinkedFileStorage");
76
+ const LinkedStorage_1 = require("@_linked/core/utils/LinkedStorage");
77
+ const Package_1 = require("@_linked/core/utils/Package");
78
+ const ShapeClass_1 = require("@_linked/core/utils/ShapeClass");
79
+ const path_1 = __importDefault(require("path"));
80
+ const process_1 = __importDefault(require("process"));
81
+ const React = __importStar(require("react"));
82
+ const server_1 = require("react-dom/server");
83
+ const server_js_1 = require("react-router-dom/server.js");
84
+ const rimraf_1 = require("rimraf");
85
+ const sharp_1 = __importDefault(require("sharp"));
86
+ const stream_1 = require("stream");
87
+ const tough_cookie_1 = require("tough-cookie");
88
+ const webpack_1 = __importDefault(require("webpack"));
89
+ const webpack_dev_middleware_1 = __importDefault(require("webpack-dev-middleware"));
90
+ const webpack_hot_middleware_1 = __importDefault(require("webpack-hot-middleware"));
91
+ const lincd_server_js_1 = require("../ontologies/lincd-server.js");
92
+ const package_js_1 = require("../package.js");
93
+ const Shapes_js_1 = require("../utils/Shapes.js");
94
+ const LincdAPI_js_1 = require("./LincdAPI.js");
95
+ //prevent errors in node.js when (s)css files are imported in js
96
+ const isProduction = process_1.default.env.NODE_ENV === 'production';
97
+ const isDevelopment = process_1.default.env.NODE_ENV === 'development';
98
+ // Install a global fetch that persists cookies across redirects using a CookieJar.
99
+ // This ensures server-side requests behave more like browsers when handling Set-Cookie + redirects.
100
+ const __lincdCookieJar = new tough_cookie_1.CookieJar();
101
+ // Wrap native fetch with fetch-cookie so it reads/writes cookies in the jar.
102
+ const __lincdFetchWithCookies = (0, fetch_cookie_1.default)(fetch, __lincdCookieJar);
103
+ // Expose (optionally) for debugging/tests; not required by app logic.
104
+ globalThis.__lincdCookieJar = __lincdCookieJar;
105
+ // Set the global fetch used throughout the server code path
106
+ globalThis.fetch = __lincdFetchWithCookies;
107
+ process_1.default.on('uncaughtException', (err) => {
108
+ console.warn(chalk_1.default.red('Asynchronous error caught.'));
109
+ console.error(err);
110
+ // error logging
111
+ LinkedErrorLogging_1.LinkedErrorLogging.log(err);
112
+ });
113
+ process_1.default.on('unhandledRejection', (err) => {
114
+ console.warn(chalk_1.default.red('Unhandled rejection caught.'));
115
+ console.error(err);
116
+ // error logging
117
+ LinkedErrorLogging_1.LinkedErrorLogging.log(err);
118
+ });
119
+ process_1.default.on('warning', (e) => console.warn(e.stack));
120
+ //allow more listeners for when we have many concurrent users
121
+ events_1.default.EventEmitter.prototype.setMaxListeners(500);
122
+ // const jsdom = require("jsdom");
123
+ // const { JSDOM } = jsdom;
124
+ // const { document } = (new JSDOM(`...`)).window;
125
+ //
126
+ // global['document'] = document;
127
+ global['reactStaticRenderer'] = server_1.renderToStaticMarkup;
128
+ (0, Package_1.autoLoadOntologyData)(true);
129
+ let LincdServer = class LincdServer extends Shape_1.Shape {
130
+ /**
131
+ * yarn linked start sends the contents of linked.config.js as an object to this constructor
132
+ * @param n
133
+ */
134
+ constructor(config) {
135
+ super(typeof config === 'string' || (config && 'id' in config)
136
+ ? config
137
+ : undefined);
138
+ this.cachedPaths = new Map();
139
+ this.latestManifest = null;
140
+ this.cssMode = 'scss-modules';
141
+ this.analyse = false;
142
+ this.shapeProviders = new CoreMap_1.CoreMap();
143
+ this.genericProviders = new CoreMap_1.CoreMap();
144
+ //from resizedFileName to full resized path (CDN or similar)
145
+ this.resizePathsMap = new Map();
146
+ if (config && typeof config !== 'string' && !('id' in config)) {
147
+ this.config = config;
148
+ }
149
+ this.api = new LincdAPI_js_1.LincdAPI({ id: process_1.default.env.SITE_ROOT + '/api' });
150
+ //Ensure the Server utility (for Server.call()) directly accesses
151
+ // this server on the backend instead of going through a network call
152
+ Server_1.Server.setLocalServer(this);
153
+ }
154
+ /*async getLincdDependencies(): Promise<string[]> {
155
+ let lincdDependencies = [];
156
+ let dependencies = this.package.dependencies;
157
+
158
+ await Promise.all(
159
+ Object.keys(dependencies).map((dependencyPkgName) => {
160
+ let modulePackageJson = getModulePackageJSON(dependencyPkgName);
161
+ if (modulePackageJson['lincd']) {
162
+ lincdDependencies.push(modulePackageJson.name);
163
+ }
164
+ // //TODO: also iteratively look into dependencies of this dependency
165
+
166
+ // let packagePath;
167
+ // try {
168
+ // packagePath = require.resolve(`${dependencyPkgName}`);
169
+ // } catch (err) {
170
+ // console.warn('Could not find package ' + dependencyPkgName+'. Error: '+err.toString());
171
+ // return;
172
+ // }
173
+ // packagePath = path.dirname(packagePath) + '/package.json'
174
+ // return fs
175
+ // .readFile(packagePath, 'utf-8')
176
+ // .then((res) => {
177
+ // let pkg = JSON.parse(res);
178
+ // if (pkg['lincd']) {
179
+ // lincdDependencies.push(pkg.name);
180
+ // }
181
+ // //TODO: also iteratively look into dependencies of this dependency
182
+ // })
183
+ // .catch((err) => {
184
+ // console.log('Could not read package.json file: '+err);
185
+ // });
186
+ }),
187
+ );
188
+ return lincdDependencies;
189
+ }*/
190
+ get app() {
191
+ return this.server;
192
+ }
193
+ initPackage() {
194
+ this.package = JSON.parse(fsNative.readFileSync(path_1.default.resolve(process_1.default.cwd(), 'package.json'), 'utf-8'));
195
+ }
196
+ initOnly() {
197
+ return __awaiter(this, void 0, void 0, function* () {
198
+ yield this.initOntologies();
199
+ yield this.initStores();
200
+ this.initPackage();
201
+ this.server = (0, express_1.default)();
202
+ yield this.initBackendProviders();
203
+ (0, Shapes_js_1.syncShapes)();
204
+ return this;
205
+ });
206
+ }
207
+ // async serveData(req,res) {
208
+ // let nodeURI = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
209
+ //
210
+ // let store = LinkedStorage.getDatasets().find(store => {
211
+ // return nodeURI.includes(store.namedNode.uri)
212
+ // })
213
+ // }
214
+ start() {
215
+ return __awaiter(this, void 0, void 0, function* () {
216
+ this.initPackage();
217
+ // Static assets should come from the static store URL (versioned path),
218
+ // not the upload store URL. storage-config sets STATIC_ACCESS_URL accordingly.
219
+ // In development we deliberately use a relative path so bundle URLs work
220
+ // regardless of which PORT the dev server bound to (browsers resolve
221
+ // relative URLs against window.location.origin). Hardcoding SITE_ROOT
222
+ // baked :4000 into every SSR'd HTML page.
223
+ const isDevAssets = process_1.default.env.NODE_ENV === 'development';
224
+ const staticAccessURL = isDevAssets
225
+ ? ''
226
+ : (process_1.default.env.STATIC_ACCESS_URL ||
227
+ LinkedFileStorage_1.LinkedFileStorage.accessURL ||
228
+ '').replace(/\/$/, '');
229
+ const staticAsset = (assetPath) => `${staticAccessURL}/public${assetPath}`;
230
+ //for apps with multiple bundles this should be read from the webpack build manifest
231
+ this.assets = {
232
+ 'main.js': staticAsset('/bundles/main.bundle.js') + '?v=' + this.package.version, //output js bundle from Webpack
233
+ 'main.css': staticAsset('/bundles/main.css'), //output css from Webpack
234
+ };
235
+ try {
236
+ const manifestPath = path_1.default.resolve(process_1.default.cwd(), 'public/bundles/manifest.json');
237
+ if (fsNative.existsSync(manifestPath)) {
238
+ const manifestRaw = fsNative.readFileSync(manifestPath, 'utf-8');
239
+ this.assets.manifest = JSON.parse(manifestRaw);
240
+ }
241
+ }
242
+ catch (err) {
243
+ console.warn('Could not load bundle manifest:', err);
244
+ }
245
+ const isProduction = process_1.default.env.NODE_ENV === 'production';
246
+ if (!isProduction && this.config.cssMode === 'tailwind') {
247
+ this.assets['tailwind-cdn'] = 'https://cdn.tailwindcss.com';
248
+ }
249
+ //for multicore we use PM2, and each instance needs to listen to port 0.
250
+ // whilst the main thread will listen to env.PORT automatically
251
+ // const PORT = this.config.multiCore ? 0 : process.env.PORT || 3000;
252
+ // const publicPort = process.env.PORT || 3000;
253
+ //update: back to original setup. multicore handles itself inside @semantu/multicore
254
+ const PORT = parseInt(process_1.default.env.PORT) || 4000;
255
+ this.server = (0, express_1.default)();
256
+ const dirName = path_1.default.resolve(process_1.default.cwd(), 'frontend');
257
+ yield this.initOntologies();
258
+ //TODO: when we do not keep all data in memory (and thus do not need to rely on in memory data for page requests), we can possibly remove await here, since all stores handle their own initialisation before executing commands
259
+ yield this.initStores();
260
+ this.initGarbageCollection();
261
+ // this.app.use((req, res, next) => {
262
+ // console.log('start request');
263
+ // next();
264
+ // });
265
+ // before controllers
266
+ yield this.initBackendProviders();
267
+ (0, Shapes_js_1.syncShapes)();
268
+ //START OF EXPRESS ROUTES AND MIDDLEWARE
269
+ //use cors
270
+ // var corsOptions = {
271
+ // origin: ['http://localhost:4001', 'https://www.mynd.site'],
272
+ // optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
273
+ // };
274
+ // //accept JSON bodies
275
+ // this.server.use(bodyParser.json({limit: '50mb'}));
276
+ this.server.use(express_1.default.json({ limit: '50mb' }));
277
+ // this.server.use(cors(corsOptions));
278
+ //
279
+ // //compress server output with gzip,
280
+ //UPDATE ive set level to 2 (low compression fast speed) because responses were taking too long (98% of server time was used by compress)
281
+ // this.server.use(compress({level: 2}));
282
+ yield this.callGenericBackendProvidersMethod('setupBeforeControllers');
283
+ // if development, run webpack from the server
284
+ // for production you need to build the bundles before starting the server
285
+ const skipBuild = process_1.default.env.NO_WEBPACK === 'true';
286
+ if (isDevelopment && !skipBuild) {
287
+ //three levels up because of lib/esm (2) instead of src (1)
288
+ //@ts-ignore
289
+ // const getWebpackConfig = (await import('../../../site.webpack.config.cjs')).default;
290
+ // let webpackConfig = await getWebpackConfig();
291
+ let webpackConfig = yield (0, config_webpack_app_1.getWebpackAppConfig)();
292
+ // const compare = (c1,c2,path?='') => {
293
+ // for(let key1 of Object.keys(c1)) {
294
+ // if(typeof c1[key1] === 'object') {
295
+ // compare(c1[key1],c2[key1],path+='.'+key1);
296
+ // } else {
297
+ // if(c1[key1] !== c2[key1]) {
298
+ // console.log("Difference "+path+": ");
299
+ // console.log(c1[key1]);
300
+ // console.log(c2[key1]);
301
+ // }
302
+ // }
303
+ // }
304
+ // }
305
+ // compare(webpackConfig,webpackConfig2);
306
+ //default to having webpack cache on and filesystem, unless explicitly set to false
307
+ if (this.cacheWebpack === false) {
308
+ webpackConfig.cache.type = 'memory';
309
+ }
310
+ else {
311
+ webpackConfig.cache.type = 'filesystem';
312
+ }
313
+ //clean dist/build folder
314
+ yield (0, rimraf_1.rimraf)(webpackConfig.output.path).catch((err) => {
315
+ if (err) {
316
+ console.warn(err);
317
+ }
318
+ });
319
+ const compiler = (0, webpack_1.default)(webpackConfig, (err, stats) => {
320
+ //watch build completed
321
+ if (err) {
322
+ console.error(err);
323
+ }
324
+ // Output stats JSON for analysis
325
+ if (this.analyse) {
326
+ fs.writeFile('./data/webpack-stats.json', JSON.stringify(stats.toJson({ all: true }), null, 2)).then(() => {
327
+ console.log('Webpack stats written to ./data/webpack-stats.json');
328
+ });
329
+ }
330
+ });
331
+ if (!compiler) {
332
+ //something went wrong with webpack config, error will be logged above
333
+ return;
334
+ }
335
+ compiler.hooks.afterEmit.tap('cleanup-the-require-cache', () => {
336
+ // After webpack rebuild, clear the files from the require cache,
337
+ // so that next server side render wil be in sync
338
+ // console.log(Object.keys(require.cache).filter(k => k.includes(dirName)).join("\n"));
339
+ // if (typeof require !== 'undefined') {
340
+ // Object.keys(require.cache)
341
+ // .filter((key) => key.includes(dirName))
342
+ // .forEach((key) => delete require.cache[key]);
343
+ // }
344
+ });
345
+ //after the first emit (which means bundles are ready and the site is running) also rebuild the index files of the site
346
+ let updatedMetadata = false;
347
+ compiler.hooks.afterEmit.tap('update-metadata', () => {
348
+ if (!updatedMetadata) {
349
+ updatedMetadata = true;
350
+ //log updated paths
351
+ // buildMetadata()
352
+ // .then((updatedPaths) => {
353
+ // // if(updatedPaths && updatedPaths.length)
354
+ // // {
355
+ // // console.log(chalk.blueBright('Updated metadata:\n - '+updatedPaths.join('\n - ')));
356
+ // // }
357
+ // })
358
+ // .catch((err) => {
359
+ // console.warn('Could not update metadata: ' + err);
360
+ // });
361
+ }
362
+ });
363
+ this.server.use((0, webpack_dev_middleware_1.default)(compiler, {
364
+ serverSideRender: true,
365
+ publicPath: webpackConfig.output.publicPath,
366
+ stats: {
367
+ children: true,
368
+ version: false,
369
+ chunks: false,
370
+ assets: false,
371
+ entrypoints: false,
372
+ modules: false,
373
+ },
374
+ writeToDisk: true,
375
+ }));
376
+ compiler.hooks.afterEmit.tap('store-manifest', (compilation) => {
377
+ try {
378
+ // Read manifest from the filesystem after webpack writes it
379
+ const manifestPath = path_1.default.resolve(compilation.options.output.path, 'manifest.json');
380
+ if (fsNative.existsSync(manifestPath)) {
381
+ const manifestContent = fsNative.readFileSync(manifestPath, 'utf-8');
382
+ this.latestManifest = JSON.parse(manifestContent);
383
+ }
384
+ }
385
+ catch (err) {
386
+ console.warn('Failed to parse manifest from webpack emit:', err);
387
+ }
388
+ });
389
+ this.server.use((0, webpack_hot_middleware_1.default)(compiler, {
390
+ log: false,
391
+ }));
392
+ }
393
+ // //map URL routes to file paths
394
+ const oneYear = 1000 * 60 * 60 * 24 * 365; // in milliseconds
395
+ const oneMonth = 1000 * 60 * 60 * 24 * 30; // in milliseconds
396
+ this.server.use('/public', express_1.default.static('./public', {
397
+ maxAge: oneYear, // Tell browser to cache for 1 year
398
+ immutable: true, // Suggest that the content won't change
399
+ }));
400
+ this.server.use('/uploads', express_1.default.static('./data/uploads', {
401
+ maxAge: oneYear, // Tell browser to cache for 1 year
402
+ immutable: true, // Suggest that the content won't change
403
+ }));
404
+ this.server.use('/', express_1.default.static('./public/root'));
405
+ this.server.use('/favicon.ico', express_1.default.static('./public/favicon.ico', {
406
+ maxAge: oneMonth, // Tell browser to cache for 1 year
407
+ immutable: true, // Suggest that the content won't change
408
+ }));
409
+ this.server.use('/.well-known', express_1.default.static('./public/.well-known'));
410
+ // this.server.post('/data',this.handleErrorsJson(async (req,res) => this.serveData(req, res)));
411
+ this.server.get('/resized/*', (req, res) => __awaiter(this, void 0, void 0, function* () {
412
+ this.resizeImage(req, res);
413
+ }));
414
+ // Scoped-package variants (@scope/pkg). Register BEFORE the unscoped
415
+ // routes — Express `:pkg` won't consume slashes, so a call to
416
+ // `/call/@_linked/auth/signinDev` would otherwise fall through to the
417
+ // 3-segment `:pkg/:shape/:method` route and be misinterpreted as a
418
+ // shape-method call (pkg=`@_linked`, shape=`auth`). These handlers
419
+ // recognise the `@scope/pkg` prefix and rebuild the full package name
420
+ // before dispatching to the same handler used for unscoped packages.
421
+ this.server.post('/call/@:scope/:pkg/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () {
422
+ req.params.pkg = `@${req.params.scope}/${req.params.pkg}`;
423
+ return this.processBackendMethodCall(req, res);
424
+ })));
425
+ this.server.post('/call/@:scope/:pkg/:shape/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () {
426
+ req.params.pkg = `@${req.params.scope}/${req.params.pkg}`;
427
+ return this.processShapeMethodCall(req, res);
428
+ })));
429
+ this.server.post('/call/:pkg/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processBackendMethodCall(req, res); })));
430
+ this.server.post('/call/:pkg/:shape/:method', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processShapeMethodCall(req, res); })));
431
+ this.server.post('/api/:method/:action?', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processAPICall(req, res, 'post'); })));
432
+ this.server.get('/api/:method/:action?', this.handleErrorsJson((req, res) => __awaiter(this, void 0, void 0, function* () { return this.processAPICall(req, res, 'get'); })));
433
+ // this.server.post(
434
+ // '/api/query/:method',
435
+ // this.handleErrorsJson(async (req, res) => this.processQuery(req, res)),
436
+ // );
437
+ // now that all the middleware is defined, we initialise the providers, before we define a catch-all route
438
+ // before catch all
439
+ // await this.initBackendProviders();
440
+ yield this.callGenericBackendProvidersMethod('setupBeforeCatchAllControllers');
441
+ // HEAD catch-all for maintenance/health check (client fetches SITE_ROOT with method: HEAD)
442
+ this.server.head('__health', (_req, res) => {
443
+ res.sendStatus(200);
444
+ });
445
+ this.server.get('*', this.handleErrors((req, res) => __awaiter(this, void 0, void 0, function* () {
446
+ //make sure the frontend bundle has finished building
447
+ // await this.waitForWebpack();
448
+ this.render(req, res);
449
+ })));
450
+ // after controller
451
+ yield this.callGenericBackendProvidersMethod('setupAfterControllers');
452
+ //remove http(s):// and remove port :[port]
453
+ const HOST = process_1.default.env.SITE_ROOT.replace(/https?:\/\//, '').replace(/:\d+$/, '');
454
+ //backlog of 1024 means maximum of 1024 connections in the queue (higher than default)
455
+ this.httpServer = this.server.listen({ port: PORT, backlog: 1024 }, () => {
456
+ console.log(`Up and running at http://localhost:${PORT}`);
457
+ // open(`http://localhost:${PORT}`)
458
+ });
459
+ // Ensure all inactive connections are terminated by the ALB, by setting this a few seconds higher than the ALB idle timeout
460
+ this.httpServer.keepAliveTimeout = 60000;
461
+ // Ensure the headersTimeout is set higher than the keepAliveTimeout due to this nodejs regression bug: https://github.com/nodejs/node/issues/27363
462
+ this.httpServer.headersTimeout = 61000;
463
+ this.httpServer.on('error', function (error) {
464
+ if (error['syscall'] !== 'listen') {
465
+ throw error;
466
+ }
467
+ const isPipe = (portOrPipe) => Number.isNaN(portOrPipe);
468
+ const bind = isPipe(PORT) ? 'Pipe ' + PORT : 'Port ' + PORT;
469
+ switch (error['code']) {
470
+ case 'EACCES':
471
+ console.error(bind + ' requires elevated privileges');
472
+ process_1.default.exit(1);
473
+ case 'EADDRINUSE':
474
+ console.error(bind + ' is already in use');
475
+ process_1.default.exit(1);
476
+ default:
477
+ throw error;
478
+ }
479
+ });
480
+ return this;
481
+ });
482
+ }
483
+ initOntologies() {
484
+ return __awaiter(this, void 0, void 0, function* () { });
485
+ }
486
+ /**
487
+ *
488
+ * @returns
489
+ * @todo Check if default file store is set, if not, set it
490
+ */
491
+ initStores() {
492
+ return __awaiter(this, void 0, void 0, function* () {
493
+ return Promise.all(LinkedStorage_1.LinkedStorage.getDatasets().map((store) => {
494
+ return store.init ? store.init() : Promise.resolve();
495
+ }));
496
+ });
497
+ }
498
+ // get generic backend providers for handle controller methods
499
+ callGenericBackendProvidersMethod(method, ...args) {
500
+ return __awaiter(this, void 0, void 0, function* () {
501
+ for (let genericProvider of this.genericProviders.values()) {
502
+ if (!genericProvider || !genericProvider[method]) {
503
+ continue;
504
+ }
505
+ if (typeof genericProvider[method] == 'function') {
506
+ yield Promise.resolve(genericProvider[method](...args));
507
+ }
508
+ }
509
+ });
510
+ }
511
+ /**
512
+ * Removes temporary nodes from memory if they've been seen twice
513
+ * Current interval is once per hour.
514
+ * So after 2 hours temporary nodes are removed.
515
+ */
516
+ initGarbageCollection() {
517
+ // let seenLastTime:NodeSet<NamedNode> = null;
518
+ // setInterval(() => {
519
+ //
520
+ // let tempNodes = new NodeSet<NamedNode>();
521
+ // NamedNode.getAllNamedNodes().forEach(n => {
522
+ // if(n.isTemporaryNode) {
523
+ // if(seenLastTime && seenLastTime.has(n))
524
+ // {
525
+ // n.remove();
526
+ // } else {
527
+ // tempNodes.add(n);
528
+ // }
529
+ // }
530
+ // })
531
+ // seenLastTime = tempNodes;
532
+ // }, 1000 * 10); //every 10 sec
533
+ // // }, 1000 * 60 * 60); //every hour
534
+ }
535
+ initBackendProviders() {
536
+ return __awaiter(this, void 0, void 0, function* () {
537
+ //get all local workspace lincd packages, then filter to only those
538
+ //in this app's dependency tree. This avoids loading npm-installed
539
+ //legacy packages that may use an old version of the core.
540
+ const allLocalPackages = (0, cli_methods_1.getLincdPackages)();
541
+ const localPackageMap = new Map(allLocalPackages.map((pkg) => [pkg.packageName, pkg]));
542
+ const relevantPackages = this.filterPackagesByDependencyTree(localPackageMap, this.package);
543
+ for (let [pkgName, pkg] of relevantPackages) {
544
+ yield this.indexPackageBackendProviders(pkgName);
545
+ yield this.indexLincdPackage(pkgName);
546
+ }
547
+ try {
548
+ yield fs
549
+ .readFile(path_1.default.join(process_1.default.cwd(), 'package.json'), 'utf-8')
550
+ .then((contents) => __awaiter(this, void 0, void 0, function* () {
551
+ let pkg = JSON.parse(contents);
552
+ yield this.indexPackageBackendProviders(pkg.name, false, path_1.default.join(process_1.default.cwd(), 'src', 'backend.ts'));
553
+ }));
554
+ }
555
+ catch (err) {
556
+ console.warn(err);
557
+ }
558
+ });
559
+ }
560
+ /**
561
+ * Filters local workspace packages to only those reachable from the app's
562
+ * dependency tree. Mirrors the logic used by `buildAll` in lincd-cli.
563
+ */
564
+ filterPackagesByDependencyTree(allPackages, appPackageJson) {
565
+ const relevantPackages = new Map();
566
+ const packagesToCheck = new Set();
567
+ const processedPackages = new Set();
568
+ // Start with direct dependencies from the app
569
+ if (appPackageJson.dependencies) {
570
+ for (const dep of Object.keys(appPackageJson.dependencies)) {
571
+ if (allPackages.has(dep)) {
572
+ packagesToCheck.add(dep);
573
+ }
574
+ }
575
+ }
576
+ // Recursively follow each package's dependencies
577
+ while (packagesToCheck.size > 0) {
578
+ const packageName = packagesToCheck.values().next().value;
579
+ packagesToCheck.delete(packageName);
580
+ if (processedPackages.has(packageName)) {
581
+ continue;
582
+ }
583
+ processedPackages.add(packageName);
584
+ const packageDetails = allPackages.get(packageName);
585
+ if (packageDetails) {
586
+ relevantPackages.set(packageName, packageDetails);
587
+ const pkg = (0, utils_1.getPackageJSON)(packageDetails.path);
588
+ if (pkg === null || pkg === void 0 ? void 0 : pkg.dependencies) {
589
+ for (const dep of Object.keys(pkg.dependencies)) {
590
+ if (allPackages.has(dep) && !processedPackages.has(dep)) {
591
+ packagesToCheck.add(dep);
592
+ }
593
+ }
594
+ }
595
+ }
596
+ }
597
+ return relevantPackages;
598
+ }
599
+ resizeImage(req, res) {
600
+ return __awaiter(this, void 0, void 0, function* () {
601
+ var _a;
602
+ //check if the w or h query parameters are set
603
+ let width = req.query.w;
604
+ let height = req.query.h;
605
+ //if not
606
+ if (!width && !height) {
607
+ //redirect to the original image
608
+ res.redirect(req.originalUrl.replace('/resized', '/uploads'));
609
+ return;
610
+ }
611
+ let imageFileName = req.query.src; // example: https://cdnurl.com/uploads/1710814765388-935b511c9_cropped.jpeg
612
+ const accessURL = LinkedFileStorage_1.LinkedFileStorage.accessURL;
613
+ if (imageFileName) {
614
+ // TODO: restrict resizing to images that are stored by LinkedFileStorage.fileExists()
615
+ // if (imageFileName.startsWith(accessURL)) {
616
+ // const exists = await LinkedFileStorage.fileExists(
617
+ // imageFileName.replace(`${accessURL}/`, ''),
618
+ // );
619
+ // extract the base name and extension from the imageFileName
620
+ // example: /uploads/resized/935b511c9_cropped.jpeg
621
+ const url = new URL(imageFileName);
622
+ const { name, ext } = path_1.default.parse(url.pathname);
623
+ // append the width and height parameters to the base name
624
+ // example: 935b511c9_cropped_w190.jpeg or 935b511c9_cropped_w190h190.jpeg
625
+ const newName = `${name}${width ? '_w' + width : ''}${height ? 'h' + height : ''}`;
626
+ // create a new pathname with dimensions
627
+ // example: /uploads/resized/935b511c9_cropped.jpeg -> /uploads/resized/935b511c9_cropped_w190.jpeg
628
+ const newPathname = path_1.default.join(path_1.default.dirname(url.pathname), 'resized', `${newName}${ext}`);
629
+ // remove the leading slash from the pathname
630
+ // example: /uploads/resized/935b511c9_cropped_w190.jpeg -> uploads/resized/935b511c9_cropped_w190.jpeg
631
+ const resizedImageFileName = newPathname.startsWith('/')
632
+ ? newPathname.slice(1)
633
+ : newPathname;
634
+ if (this.resizePathsMap.has(resizedImageFileName)) {
635
+ res.redirect(this.resizePathsMap.get(resizedImageFileName));
636
+ return;
637
+ }
638
+ // check if the resized image already exists in the CDN
639
+ const imageExists = yield LinkedFileStorage_1.LinkedFileStorage.fileExists(resizedImageFileName);
640
+ // if exists, redirect to the resized image
641
+ if (imageExists) {
642
+ const resizedPathOnCdn = accessURL + '/' + resizedImageFileName;
643
+ //save to cache
644
+ this.resizePathsMap.set(resizedImageFileName, resizedPathOnCdn);
645
+ //redirect this request to the resized image
646
+ res.redirect(resizedPathOnCdn);
647
+ return;
648
+ }
649
+ else {
650
+ console.log(`${process_1.default.pid} - ${process_1.default.env.PORT}: Resizing image: ${imageFileName} to ${width} x ${height || ''}`);
651
+ //in multicore development, we need to access the site from 127.0.0.1, and so all requests need to go there
652
+ // // if (process.env.NODE_ENV === 'development' && process.env.NUM_WORKER_PROCESSES) {
653
+ // // imageFileName = imageFileName.replace('localhost', '127.0.0.1');
654
+ // // }
655
+ // get the image from imageFileName
656
+ const image = yield globalThis
657
+ .fetch(imageFileName)
658
+ .then((res) => res.arrayBuffer())
659
+ .then((arrayBuffer) => Buffer.from(arrayBuffer))
660
+ .catch((err) => {
661
+ console.warn('Could not fetch image from URL: ' + err);
662
+ return null;
663
+ });
664
+ // if image is null, return 404
665
+ if (!image) {
666
+ res.status(404).send({ error: 'Could not fetch image from URL' });
667
+ return;
668
+ }
669
+ // get the format of the image
670
+ let format;
671
+ try {
672
+ format = yield (0, sharp_1.default)(image)
673
+ .metadata()
674
+ .then((meta) => meta.format);
675
+ }
676
+ catch (err) {
677
+ console.warn('Unsupported image format: ' + err);
678
+ res.status(400).send({ error: 'Unsupported image format' });
679
+ return;
680
+ }
681
+ // set the output options based on the format for quality and compression
682
+ let outputOptions;
683
+ switch (format) {
684
+ case 'jpeg':
685
+ outputOptions = { quality: 90 };
686
+ break;
687
+ case 'png':
688
+ outputOptions = { compressionLevel: 9 };
689
+ break;
690
+ case 'webp':
691
+ outputOptions = { quality: 90 };
692
+ break;
693
+ // add more formats here
694
+ default:
695
+ outputOptions = {};
696
+ }
697
+ // resize the image
698
+ const resizedImage = yield (0, sharp_1.default)(image)
699
+ .resize(width ? parseInt(width) : null, height ? parseInt(height) : null)
700
+ .toFormat(format, outputOptions)
701
+ .toBuffer()
702
+ .catch((err) => {
703
+ console.warn('Could not resize image: ' + err);
704
+ return null;
705
+ });
706
+ // if resizedImage is null, return 500
707
+ if (!resizedImage) {
708
+ res.status(500).send({ error: 'Could not resize image' });
709
+ return;
710
+ }
711
+ // upload the resized image to the CDN
712
+ const resizedPathOnCdn = yield LinkedFileStorage_1.LinkedFileStorage.saveFile(newPathname, resizedImage);
713
+ //save to cache
714
+ this.resizePathsMap.set(resizedImageFileName, resizedPathOnCdn);
715
+ //redirect this request to the resized image
716
+ res.redirect(resizedPathOnCdn);
717
+ return;
718
+ }
719
+ }
720
+ else {
721
+ imageFileName = (_a = req.originalUrl.split('/resized/')[1]) === null || _a === void 0 ? void 0 : _a.split('?')[0];
722
+ }
723
+ //if this request has not been made (and stored on the HD) before
724
+ let [trueFileName, ...extensions] = imageFileName.split('.');
725
+ let extension = extensions.join('.');
726
+ let resizedImageFileName = trueFileName +
727
+ '_' +
728
+ (width ? 'w' + width : '') +
729
+ (height ? 'h' + height : '') +
730
+ '.' +
731
+ extension;
732
+ let resizedFilePath = path_1.default.join(process_1.default.cwd(), 'data', 'uploads', 'resized', resizedImageFileName);
733
+ if (!fsNative.existsSync(resizedFilePath)) {
734
+ //ensure the resized folder exists
735
+ // if (!fsNative.existsSync(path.join(process.cwd(), 'data', 'uploads', 'resized'))) {
736
+ // fsNative.mkdirSync(path.join(process.cwd(), 'data', 'uploads', 'resized'), {recursive: true});
737
+ // }
738
+ //then lets resize and store the image:
739
+ // resize the image with sharp and return it
740
+ let originalImagePath = path_1.default.join(process_1.default.cwd(), 'data', 'uploads', imageFileName);
741
+ if (!fsNative.existsSync(originalImagePath)) {
742
+ console.warn('Could not find original image at ' + originalImagePath);
743
+ return res.status(404).send({ error: 'Could not find original image' });
744
+ }
745
+ try {
746
+ let image = (0, sharp_1.default)(originalImagePath);
747
+ image.resize(width ? parseInt(width) : null, height ? parseInt(height) : null);
748
+ //write image to disk
749
+ yield image
750
+ .toFile(resizedFilePath)
751
+ .then(() => {
752
+ // console.log('resized image written to disk: ' + resizedFilePath);
753
+ })
754
+ .catch((err) => {
755
+ console.warn('Could not write resized image to disk at ' +
756
+ resizedFilePath +
757
+ ': ' +
758
+ err);
759
+ });
760
+ //
761
+ // //get content type from the file extension
762
+ // let contentType;
763
+ // let extension = imageFileName.split('.').pop();
764
+ // switch (extension) {
765
+ // case 'jpg':
766
+ // case 'jpeg':
767
+ // contentType = 'image/jpeg';
768
+ // break;
769
+ // case 'png':
770
+ // contentType = 'image/png';
771
+ // break;
772
+ // case 'gif':
773
+ // contentType = 'image/gif';
774
+ // break;
775
+ // default:
776
+ // contentType = 'image/jpeg';
777
+ // }
778
+ // res.setHeader('Content-Type', contentType);
779
+ // image.pipe(res);
780
+ }
781
+ catch (err) {
782
+ console.warn(err);
783
+ res.status(500).send({ error: 'Could not resize image' });
784
+ }
785
+ }
786
+ //send the resized image
787
+ res.sendFile(resizedFilePath);
788
+ });
789
+ }
790
+ indexLincdPackage(pkg_1) {
791
+ return __awaiter(this, arguments, void 0, function* (pkg, warnIfNotFound = false) {
792
+ if (pkg === this.package.name) {
793
+ return;
794
+ }
795
+ try {
796
+ // console.log(`🔍 Loading package: ${pkg}`);
797
+ // console.log(
798
+ // `🔍 Module resolution for ${pkg}:`,
799
+ //@ts-ignore
800
+ // await import.meta.resolve(pkg)
801
+ // );
802
+ yield Promise.resolve(`${pkg}`).then(s => __importStar(require(s)));
803
+ // console.log(`✅ Successfully loaded: ${pkg}`);
804
+ }
805
+ catch (e) {
806
+ let providerNotFound = e.code === 'MODULE_NOT_FOUND' &&
807
+ e.message.indexOf(`Cannot find package '${pkg}'`) !== -1;
808
+ if (providerNotFound) {
809
+ // console.warn('Error loading ' + providerPath + ': ' + e.stack);
810
+ if (warnIfNotFound) {
811
+ console.warn(chalk_1.default.magenta(`Could not load package ${pkg}`, typeof module !== 'undefined' && typeof exports !== 'undefined'
812
+ ? //@ts-ignore
813
+ ' at ' + (yield import.meta.resolve(pkg))
814
+ : ''));
815
+ }
816
+ }
817
+ else {
818
+ console.warn(chalk_1.default.red(`Error loading '${pkg}' ${typeof module !== 'undefined' && typeof exports !== 'undefined'
819
+ ? //@ts-ignore
820
+ ' at ' + (yield import.meta.resolve(pkg))
821
+ : ''}: ${e.message}\n`), e.stack);
822
+ }
823
+ }
824
+ });
825
+ }
826
+ indexPackageBackendProviders(pkg_1) {
827
+ return __awaiter(this, arguments, void 0, function* (pkg, warnIfNotFound = false, backendIndexFilePath) {
828
+ if (!backendIndexFilePath) {
829
+ backendIndexFilePath = `${pkg}/backend`;
830
+ }
831
+ let backendProviderExports;
832
+ let genericBackendProvider;
833
+ let shapeProviders = [];
834
+ yield Promise.resolve(`${backendIndexFilePath}`).then(s => __importStar(require(s))).then((backendProviderExports) => {
835
+ //instantiate the exported provider classes and add them to the right place
836
+ Object.keys(backendProviderExports).forEach((key) => {
837
+ let providerClass = backendProviderExports[key];
838
+ //always send an instance of the express server
839
+ //TODO: do not create an instance, just save the class and instantiate it when needed
840
+ let provider = new providerClass(this.server, this);
841
+ if (provider instanceof ShapeProvider_1.ShapeProvider) {
842
+ shapeProviders.push(provider);
843
+ if (!Object.getOwnPropertyNames(provider).includes('shape')) {
844
+ console.warn(chalk_1.default.red(`${Object.getPrototypeOf(provider).constructor.name} in package ${pkg}
845
+ is not properly linked to a shape. Use public shape = SomeShape.`));
846
+ }
847
+ }
848
+ else {
849
+ if (genericBackendProvider) {
850
+ console.warn(`Package ${pkg} exports two generic backend providers. Only one will work`);
851
+ }
852
+ else {
853
+ genericBackendProvider = provider;
854
+ }
855
+ }
856
+ });
857
+ })
858
+ .catch((e) => {
859
+ const match = e.message.match(/module \'([^\']+)'/);
860
+ //check that the imported /backend path is not found (and only that path, not an import IN that file that is not found, that should still throw an error)
861
+ let providerNotFound = e.code === 'ERR_MODULE_NOT_FOUND' &&
862
+ e.message.indexOf(`Cannot find module`) !== -1 &&
863
+ match &&
864
+ match[1] &&
865
+ match[1].includes('/backend');
866
+ if (providerNotFound) {
867
+ // console.warn('Error loading ' + providerPath + ': ' + e.stack);
868
+ if (warnIfNotFound) {
869
+ console.warn(chalk_1.default.magenta(`Could not find backend file of package ${pkg}.
870
+ Check:\n
871
+ - Make sure backend.ts exists and is included in tsconfig.json\n
872
+ - Make sure the package name in src/package.ts matches the package name in package.json`));
873
+ }
874
+ }
875
+ else {
876
+ console.warn(chalk_1.default.red(`Could not load backend file of module '${pkg}' from ${process_1.default.cwd()}:\n`), e.stack);
877
+ }
878
+ genericBackendProvider = null;
879
+ });
880
+ this.genericProviders.set(pkg, genericBackendProvider);
881
+ this.shapeProviders.set(pkg, shapeProviders);
882
+ return { backendProviderExports, shapeProviders };
883
+ });
884
+ }
885
+ processBackendMethodCall(request, response) {
886
+ return __awaiter(this, void 0, void 0, function* () {
887
+ this.noCache(response);
888
+ yield this.initRequest(request, response);
889
+ let { pkg, method } = request.params;
890
+ let { args } = JSONParser_1.JSONParser.parseObject(request.body);
891
+ return this.callBackendMethod(pkg, method, args, request, response).then((result) => {
892
+ //some methods of backend providers may choose to work with request/response directly and will not return anything
893
+ //so only if a result is returned
894
+ if (typeof result !== 'undefined') {
895
+ //do we convert it to JSON and send it to the frontend
896
+ this.sendJson(response, result);
897
+ }
898
+ else {
899
+ //in other cases, we still need to close the request and send an empty response
900
+ if (!response.headersSent) {
901
+ this.sendJson(response, null);
902
+ }
903
+ }
904
+ });
905
+ });
906
+ }
907
+ noCache(response) {
908
+ response.setHeader('Surrogate-Control', 'no-store');
909
+ response.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
910
+ response.setHeader('Expires', '0');
911
+ }
912
+ processAPICall(request, response, method) {
913
+ return __awaiter(this, void 0, void 0, function* () {
914
+ this.noCache(response);
915
+ const result = yield this.api.process(request, response, method);
916
+ });
917
+ }
918
+ // async processQuery(request, response) {
919
+ //
920
+ // this.api.process(request, response);
921
+ // }
922
+ processShapeMethodCall(request, response) {
923
+ return __awaiter(this, void 0, void 0, function* () {
924
+ var _a, _b;
925
+ this.noCache(response);
926
+ // console.log('process shape call');
927
+ // response.on('finish', function() {
928
+ // console.log('shape method call request finish');
929
+ // });
930
+ //
931
+ // response.on('close', function() {
932
+ // console.log('close');
933
+ // });
934
+ //
935
+ // response.on('end', function() {
936
+ // console.log('end');
937
+ // });
938
+ //
939
+ // response.on('header', function() {
940
+ // console.log('header');
941
+ // console.log(response.statusCode);
942
+ // });
943
+ yield this.initRequest(request, response);
944
+ let { pkg, shape, method } = request.params;
945
+ let { shapeURI, instanceNode, args } = JSONParser_1.JSONParser.parseObject(request.body);
946
+ if (!shapeURI) {
947
+ if ((_a = request.query) === null || _a === void 0 ? void 0 : _a.shapeURI) {
948
+ shapeURI = (_b = request.query) === null || _b === void 0 ? void 0 : _b.shapeURI;
949
+ }
950
+ else {
951
+ response.status(500).send({
952
+ error: 'Invalid server call request: ' + request.originalUrl,
953
+ });
954
+ console.warn(chalk_1.default.red('Invalid server call request: ' + request.originalUrl));
955
+ return;
956
+ }
957
+ }
958
+ return this.callShapeMethod(pkg, method, shapeURI, instanceNode, args, request, response).then((result) => {
959
+ //we return json if something was returned or, if nothing was returned, we still close the request if the method has not accessed response itself already to send things over
960
+ if (typeof result !== 'undefined' || !response.headersSent) {
961
+ this.sendJson(response, result);
962
+ }
963
+ });
964
+ });
965
+ }
966
+ callShapeMethod(pkg, method, shapeURI, instanceNode, args, request, response) {
967
+ return __awaiter(this, void 0, void 0, function* () {
968
+ var _a;
969
+ //- index module providers if not done yet
970
+ if (!this.shapeProviders.has(pkg)) {
971
+ yield this.indexPackageBackendProviders(pkg, true);
972
+ }
973
+ let packageShapeProviders = this.shapeProviders.get(pkg);
974
+ let findProviderForShape = (nodeShapeId) => {
975
+ return packageShapeProviders.find((provider) => {
976
+ var _a, _b;
977
+ //access the static shape (which is a linkedShape() / Shape class)
978
+ //then access the SHACL NodeShape of that shape class, and its id
979
+ return (provider instanceof ShapeProvider_1.ShapeProvider &&
980
+ ((_b = (_a = provider.shape) === null || _a === void 0 ? void 0 : _a.shape) === null || _b === void 0 ? void 0 : _b.id) === nodeShapeId);
981
+ });
982
+ };
983
+ try {
984
+ //- find matching provider
985
+ let shapeClass = (0, ShapeClass_1.getShapeClass)(shapeURI);
986
+ let shapeProvider = findProviderForShape(shapeClass.shape.id);
987
+ if (!shapeProvider) {
988
+ let superShapeClasses = (0, ShapeClass_1.getSuperShapesClasses)(shapeClass);
989
+ for (let superShapeClass of superShapeClasses) {
990
+ let superShapeProvider = findProviderForShape((_a = superShapeClass.shape) === null || _a === void 0 ? void 0 : _a.id);
991
+ if (superShapeProvider) {
992
+ shapeProvider = superShapeProvider;
993
+ break;
994
+ }
995
+ }
996
+ }
997
+ if (shapeProvider) {
998
+ //NOTE: if this is a direct call from backend to backend, we won't know the request & response here because those don't get passed to the Server utility
999
+ //if this is an issue, we need to see how we can get those back
1000
+ if (request && response) {
1001
+ //give the provider a chance to prepare for this request
1002
+ //wrap the call in a promise and wait for it, because providers MAY return a promise
1003
+ yield Promise.resolve(shapeProvider.initRequest(request, response));
1004
+ }
1005
+ //see if the shapeProvider implements the called method
1006
+ if (shapeProvider[method]) {
1007
+ //prepare the first argument
1008
+ //we want to convert the instance node into an instance of the shape that this shapeProvider provides for
1009
+ //let's find the shape class
1010
+ if (shapeProvider.shape) {
1011
+ //instance nodes are not always sent. A shape can also call a shape provider from a static method without a shape instance.
1012
+ if (instanceNode) {
1013
+ let providerShapeClass = shapeProvider.shape;
1014
+ let instance = new providerShapeClass({
1015
+ id: instanceNode.id,
1016
+ });
1017
+ //always send instanceNode as first argument to static provider methods
1018
+ args.unshift(instance);
1019
+ }
1020
+ try {
1021
+ //call the method with the given arguments
1022
+ let result = yield Promise.resolve(shapeProvider[method].apply(shapeProvider, args));
1023
+ return result;
1024
+ }
1025
+ catch (e) {
1026
+ console.warn(`Error whilst calling ${Object.getPrototypeOf(shapeProvider).constructor.name}.${method}(): `, e);
1027
+ }
1028
+ }
1029
+ else {
1030
+ console.warn(`${Object.getPrototypeOf(shapeProvider).constructor.name} does not define its own 'static shape' property. Please connect the provider to a shape.`);
1031
+ }
1032
+ }
1033
+ else {
1034
+ return this.sendError(response, 501, `${Object.getPrototypeOf(shapeProvider).constructor.name} does not have a method called ${method}`);
1035
+ }
1036
+ }
1037
+ else {
1038
+ return this.sendError(response, 501, "Could not find provider for shape '" + shapeURI + "'");
1039
+ }
1040
+ }
1041
+ catch (err) {
1042
+ console.warn(`Error whilst trying to access provider of ${pkg}: `, err);
1043
+ }
1044
+ return null;
1045
+ });
1046
+ }
1047
+ handleErrors(fn) {
1048
+ return function (req, res, next) {
1049
+ return __awaiter(this, void 0, void 0, function* () {
1050
+ try {
1051
+ return yield fn(req, res);
1052
+ }
1053
+ catch (x) {
1054
+ console.log(x);
1055
+ next(x);
1056
+ }
1057
+ });
1058
+ };
1059
+ }
1060
+ handleErrorsJson(fn) {
1061
+ return (req, res, next) => __awaiter(this, void 0, void 0, function* () {
1062
+ try {
1063
+ return yield fn(req, res);
1064
+ }
1065
+ catch (err) {
1066
+ this.sendError(res, 500, 'internal server error' + (isDevelopment ? ': ' + (err === null || err === void 0 ? void 0 : err.stack) : ''), 'internal server error: ' + (err === null || err === void 0 ? void 0 : err.stack));
1067
+ }
1068
+ });
1069
+ }
1070
+ sendError(res, statusCode = 500, message, logMessage) {
1071
+ res === null || res === void 0 ? void 0 : res.status(statusCode);
1072
+ if (message) {
1073
+ res === null || res === void 0 ? void 0 : res.send({ error: message });
1074
+ console.warn(chalk_1.default.red(logMessage || message));
1075
+ }
1076
+ }
1077
+ render(req, res) {
1078
+ return __awaiter(this, void 0, void 0, function* () {
1079
+ var _a, _b, _c, _d;
1080
+ res.socket.on('error', (error) => {
1081
+ console.error('Fatal socket error', error);
1082
+ });
1083
+ //when render() is called, the request is always an 'initial page request', and the server which will return HTML (so this is a SSR request)
1084
+ //in that case we send data back to the frontend in a <script> tag.
1085
+ //We initiate that object here
1086
+ if (req['frontendData'] == null) {
1087
+ req['frontendData'] = {};
1088
+ }
1089
+ //if we are caching this page
1090
+ if (((_a = this.config.server) === null || _a === void 0 ? void 0 : _a.cachePaths) &&
1091
+ this.config.server.cachePaths.includes(req.url)) {
1092
+ //if there is a cach for this path, send it
1093
+ if (this.cachedPaths.has(req.url)) {
1094
+ // console.log(req.url + ': Returning cached path');
1095
+ const html = this.cachedPaths.get(req.url);
1096
+ // res.setHeader('Content-Type', 'text/html; charset=utf-8');
1097
+ // res.setHeader('Content-Length', Buffer.byteLength(html, 'utf8').toString());
1098
+ // res.status(200).end(html);
1099
+ res.send(html);
1100
+ return;
1101
+ }
1102
+ }
1103
+ let didError = false;
1104
+ let App = ((_b = this.config.server) === null || _b === void 0 ? void 0 : _b.loadAppComponent)
1105
+ ? yield this.config.server.loadAppComponent()
1106
+ : null;
1107
+ yield this.initRequest(req, res);
1108
+ let { requestLD, requestObject } = yield this.getRequestData(req, res);
1109
+ //on the backend we need to inform the hook of the request-data value
1110
+ //on the frontend it will be read from the HTML
1111
+ // setRequestData(requestObject);
1112
+ // Abandon and switch to client rendering if enough time passes.
1113
+ // Try lowering this to see the client recover.
1114
+ // Abandon and switch to client rendering if enough time passes.
1115
+ // Try lowering this to see the client recover.
1116
+ let stream;
1117
+ let timedout = false;
1118
+ // const timeout = setTimeout(() => {
1119
+ // stream.abort(`${req.url}: ⏱ SSR stream took too long — force abort`);
1120
+ // if (!res.headersSent) {
1121
+ // res.statusCode = 500;
1122
+ // res.end('SSR timed out');
1123
+ // }
1124
+ // timedout = true;
1125
+ // }, 8_000);
1126
+ let manifest = this.latestManifest || this.assets.manifest || {};
1127
+ let preloadScripts = [];
1128
+ let preloadStyles = [];
1129
+ let matchedRouteKey = null;
1130
+ // Load routes config if available and extract preload chunks for the current route
1131
+ if ((_c = this.config.server) === null || _c === void 0 ? void 0 : _c.loadRoutes) {
1132
+ try {
1133
+ const routesModule = yield this.config.server.loadRoutes();
1134
+ const ROUTES = routesModule.ROUTES ||
1135
+ ((_d = routesModule.default) === null || _d === void 0 ? void 0 : _d.ROUTES) ||
1136
+ routesModule;
1137
+ // Match the current request path to a route
1138
+ let matchedRoute = null;
1139
+ for (const [key, route] of Object.entries(ROUTES)) {
1140
+ if (!(route === null || route === void 0 ? void 0 : route.path))
1141
+ continue;
1142
+ const pathPattern = route.path
1143
+ .replace(/:\w+\??/g, '([^/]+)')
1144
+ .replace(/\*/g, '.*');
1145
+ const regex = new RegExp('^' + pathPattern + '$');
1146
+ if (regex.test(req.path)) {
1147
+ matchedRoute = route;
1148
+ matchedRouteKey = key;
1149
+ break;
1150
+ }
1151
+ }
1152
+ // If we found a matching route with preloadChunks, resolve them to URLs (both JS and CSS)
1153
+ if ((matchedRoute === null || matchedRoute === void 0 ? void 0 : matchedRoute.preloadChunks) &&
1154
+ Array.isArray(matchedRoute.preloadChunks)) {
1155
+ preloadScripts = matchedRoute.preloadChunks
1156
+ .map((chunkName) => manifest[`${chunkName}.js`] ||
1157
+ manifest[`${chunkName}.bundle.js`] ||
1158
+ manifest[`${chunkName}.mjs`])
1159
+ .filter(Boolean);
1160
+ // Also collect CSS chunks for the same routes
1161
+ preloadStyles = matchedRoute.preloadChunks
1162
+ .map((chunkName) => manifest[`${chunkName}.css`])
1163
+ .filter(Boolean);
1164
+ }
1165
+ }
1166
+ catch (err) {
1167
+ console.warn('Failed to resolve preload chunks:', err);
1168
+ }
1169
+ }
1170
+ // Add matched route key to request object for Html component
1171
+ req['matchedRouteKey'] = matchedRouteKey;
1172
+ stream = (0, server_1.renderToPipeableStream)(React.createElement(React.StrictMode, null,
1173
+ React.createElement(server_js_1.StaticRouter, { location: req.url },
1174
+ React.createElement(AppContext_1.AppContextProvider, { assets: this.assets, requestLD: requestLD, requestObject: requestObject, preloadScripts: preloadScripts, preloadStyles: preloadStyles, expressRequest: req, expressResponse: res },
1175
+ React.createElement(App, null)))), {
1176
+ bootstrapScripts: [
1177
+ this.assets['main.js'], //generated webpack bundle from frontend/src
1178
+ ],
1179
+ onShellReady: function () {
1180
+ var _a;
1181
+ res.statusCode = didError ? 500 : 200;
1182
+ res.setHeader('Content-type', 'text/html');
1183
+ // Create a caching transform stream à la mxstbr.com/thoughts/streaming-ssr
1184
+ if (((_a = this.config.server) === null || _a === void 0 ? void 0 : _a.cachePaths) &&
1185
+ this.config.server.cachePaths.includes(req.url)) {
1186
+ const bufferedChunks = [];
1187
+ const cacheStream = new stream_1.Transform({
1188
+ transform(chunk, _enc, cb) {
1189
+ bufferedChunks.push(chunk); // keep a copy
1190
+ cb(null, chunk); // forward unchanged
1191
+ },
1192
+ flush: (cb) => {
1193
+ const html = Buffer.concat(bufferedChunks).toString('utf8');
1194
+ this.cachedPaths.set(req.url, html);
1195
+ if (this.config.server.cacheTimeout) {
1196
+ setTimeout(() => this.cachedPaths.delete(req.url), this.config.server.cacheTimeout);
1197
+ }
1198
+ clearTimeout(timeout); // rendering finished → stop timer
1199
+ cb();
1200
+ },
1201
+ });
1202
+ // Pipe the caching stream into the real response
1203
+ cacheStream.pipe(res);
1204
+ // React may **only be piped once**, so pipe it to the cacheStream
1205
+ stream.pipe(cacheStream);
1206
+ }
1207
+ else {
1208
+ stream.pipe(res);
1209
+ }
1210
+ }.bind(this),
1211
+ onShellError(x) {
1212
+ didError = true;
1213
+ console.error(x);
1214
+ },
1215
+ });
1216
+ // Abandon and switch to client rendering if enough time passes.
1217
+ // Try lowering this to see the client recover.
1218
+ const timeout = setTimeout(() => {
1219
+ stream.abort('⏱ SSR stream took too long — force abort');
1220
+ if (!res.headersSent) {
1221
+ res.statusCode = 500;
1222
+ res.end('SSR timed out');
1223
+ }
1224
+ }, 10000);
1225
+ res.on('close', () => {
1226
+ clearTimeout(timeout); // if you're using a safety timeout
1227
+ });
1228
+ });
1229
+ }
1230
+ //If we ever need to use multiple webpack entry-points and different bundle names, then uncomment this
1231
+ /*getWebpackAssets()
1232
+ {
1233
+ // const { devMiddleware } = res.locals.webpack;
1234
+ // const outputFileSystem = devMiddleware.outputFileSystem;
1235
+ // const jsonWebpackStats = devMiddleware.stats.toJson();
1236
+ // const { assetsByChunkName, outputPath } = jsonWebpackStats;
1237
+ //
1238
+ // let normalizedAssets = normalizeAssets(assetsByChunkName.main);
1239
+ // let cssAssets = normalizedAssets
1240
+ // .filter((path) => path.endsWith(".css"))
1241
+ // .map((filePath) => outputFileSystem.readFileSync(path.join(outputPath, filePath)))
1242
+ // .join("\n");
1243
+ //
1244
+ // let jsAssets = normalizeAssets(assetsByChunkName.main)
1245
+ // .filter((path) => path.endsWith(".js"))
1246
+ // .map((path) => `<script src="${path}"></script>`)
1247
+ // .join("\n")}
1248
+ }*/
1249
+ sendJson(res, obj) {
1250
+ let jsonObject = JSONWriter_1.JSONWriter.toJsObject(obj);
1251
+ res.json(jsonObject);
1252
+ }
1253
+ initRequest(request, response) {
1254
+ return __awaiter(this, void 0, void 0, function* () {
1255
+ // initialise the request for all providers, do it synchroniously, one after the other
1256
+ let p = Promise.resolve();
1257
+ [...this.genericProviders.values()]
1258
+ .filter(Boolean)
1259
+ .forEach((backendProvider) => {
1260
+ p = p
1261
+ .then(() => {
1262
+ return backendProvider.initRequest(request, response);
1263
+ })
1264
+ .catch((err) => {
1265
+ console.warn(`Error during initRequest for provider ${Object.getPrototypeOf(backendProvider).constructor.name}: `, err);
1266
+ });
1267
+ });
1268
+ return p;
1269
+ });
1270
+ }
1271
+ getRequestData(request, response) {
1272
+ return __awaiter(this, void 0, void 0, function* () {
1273
+ // Phase 1: providers return plain JSON data via supplyDataForRequest.
1274
+ // requestLD is kept as empty string for now — SSR data seeding will be
1275
+ // reworked in Phase 2/3 to inject query results instead of graph data.
1276
+ let requestData = {};
1277
+ yield Promise.all([...this.genericProviders.values()].map((backendProvider) => {
1278
+ if (backendProvider) {
1279
+ return Promise.resolve(backendProvider.supplyDataForRequest(request, response, requestData)).catch((err) => {
1280
+ console.warn(`Error requesting page-request data from ${Object.getPrototypeOf(backendProvider).constructor.name}: `, err);
1281
+ });
1282
+ }
1283
+ }));
1284
+ let requestLD = '';
1285
+ let requestObject = JSONWriter_1.JSONWriter.stringify(request['frontendData']);
1286
+ return { requestLD, requestObject };
1287
+ });
1288
+ }
1289
+ callBackendMethod(pkg, method, args, request, response) {
1290
+ return __awaiter(this, void 0, void 0, function* () {
1291
+ if (!this.genericProviders.has(pkg)) {
1292
+ yield this.indexPackageBackendProviders(pkg, true);
1293
+ }
1294
+ //retrieve the indexed provider class and create a new instance for this request
1295
+ let genericBackendProvider = this.genericProviders.get(pkg);
1296
+ let result;
1297
+ if (!genericBackendProvider) {
1298
+ console.warn(`${chalk_1.default.magenta(pkg)} does not have a generic backend provider. If you can edit this package, make sure 'backend.ts' is included in 'tsconfig.json' and that it exports a provider.`);
1299
+ return null;
1300
+ }
1301
+ //test if there is a matching method in the backend provider
1302
+ if (!genericBackendProvider[method]) {
1303
+ console.warn(`Generic provider '${Object.getPrototypeOf(genericBackendProvider).constructor.name}' of ${pkg} does not have a method called ${method}`);
1304
+ return null;
1305
+ }
1306
+ try {
1307
+ //TODO: remove init request.
1308
+ //TODO: refactor this.request and this.response to a request parameter
1309
+ //NOTE: if this is a direct call from backend to backend, we won't know the request & response here because those don't get passed to the Server utility
1310
+ //if this is an issue, we need to see how we can get those back
1311
+ if (request && response) {
1312
+ //initialise the request for this specific provider
1313
+ //wrap the call in a promise and wait for it, because providers MAY return a promise
1314
+ yield Promise.resolve(genericBackendProvider.initRequest(request, response));
1315
+ }
1316
+ // args.push(request);
1317
+ // args.push(response);
1318
+ //call the method with the given arguments and return the result as json
1319
+ result = yield Promise.resolve(genericBackendProvider[method].apply(genericBackendProvider, args));
1320
+ }
1321
+ catch (e) {
1322
+ console.warn(`Error whilst calling ${method}() in provider ${Object.getPrototypeOf(genericBackendProvider).constructor.name} of package ${pkg}:\n`, e);
1323
+ // error logging
1324
+ LinkedErrorLogging_1.LinkedErrorLogging.log(e);
1325
+ }
1326
+ return result;
1327
+ });
1328
+ }
1329
+ };
1330
+ exports.LincdServer = LincdServer;
1331
+ /**
1332
+ * indicates that instances of this shape need to have this rdf.type
1333
+ */
1334
+ LincdServer.targetClass = lincd_server_js_1.lincdServer.LincdServer;
1335
+ exports.LincdServer = LincdServer = __decorate([
1336
+ package_js_1.linkedShape,
1337
+ __metadata("design:paramtypes", [Object])
1338
+ ], LincdServer);
1339
+ //# sourceMappingURL=LincdServer.js.map