@webqit/webflo 1.0.32 → 1.0.34

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/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "1.0.32",
15
+ "version": "1.0.34",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -41,6 +41,7 @@
41
41
  "@webqit/observer": "^2.0.7",
42
42
  "@webqit/oohtml-ssr": "^2.1.1",
43
43
  "@webqit/util": "^0.8.11",
44
+ "dotenv": "^16.4.7",
44
45
  "esbuild": "^0.14.38",
45
46
  "ioredis": "^5.5.0",
46
47
  "jsdom": "^21.1.1",
@@ -13,18 +13,13 @@ export default class Env extends Dotfile {
13
13
 
14
14
  // @desc
15
15
  static get ['@desc']() {
16
- return 'Environmental variables config.';
17
- }
18
-
19
- // isEnv
20
- get isEnv() {
21
- return true;
16
+ return 'Environmental variable mappings.';
22
17
  }
23
18
 
24
19
  // Defaults merger
25
20
  withDefaults(config) {
26
21
  return this.merge({
27
- autoload: true,
22
+ mappings: {}
28
23
  }, config, 'patch');
29
24
  }
30
25
 
@@ -33,13 +28,13 @@ export default class Env extends Dotfile {
33
28
  // Questions
34
29
  return [
35
30
  {
36
- name: 'entries',
31
+ name: 'mappings',
37
32
  type: 'recursive',
38
33
  controls: {
39
- name: 'variable',
34
+ name: 'variable mappings',
40
35
  combomode: true,
41
36
  },
42
- initial: config.entries,
37
+ initial: config.mappings,
43
38
  schema: [
44
39
  {
45
40
  name: 'name',
@@ -54,15 +49,7 @@ export default class Env extends Dotfile {
54
49
  validation: ['important'],
55
50
  },
56
51
  ],
57
- },
58
- {
59
- name: 'autoload',
60
- type: 'toggle',
61
- message: 'Choose whether to autoload variables into "process.env"',
62
- active: 'YES',
63
- inactive: 'NO',
64
- initial: config.autoload,
65
- },
52
+ }
66
53
  ];
67
54
  }
68
55
  }
@@ -28,8 +28,6 @@ export default class Client extends Dotfile {
28
28
  webpush: false,
29
29
  custom_install: false,
30
30
  exposed: ['display-mode', 'notifications'],
31
- vapid_public_key_variable: 'VAPID_PUBLIC_KEY',
32
- generic_public_webhook_url_variable: 'GENERIC_PUBLIC_WEBHOOK_URL',
33
31
  },
34
32
  }, config, 'patch');
35
33
  }
@@ -102,17 +100,7 @@ export default class Client extends Dotfile {
102
100
  type: 'list',
103
101
  message: 'Specify features exposed on capabilities.exposed',
104
102
  initial: (config.exposed || []).join(', '),
105
- },
106
- {
107
- name: 'vapid_public_key_variable',
108
- type: (prev, answers) => !answers.webpush ? null : 'text',
109
- message: 'Enter the environment variable name for APP_VAPID_PUBLIC_KEY if not as written',
110
- },
111
- {
112
- name: 'generic_public_webhook_url_variable',
113
- type: 'text',
114
- message: 'Enter the environment variable name for GENERIC_PUBLIC_WEBHOOK_URL if not as written',
115
- },
103
+ }
116
104
  ]
117
105
  }
118
106
  ];
@@ -29,17 +29,12 @@ export default class Server extends Dotfile {
29
29
  force: false,
30
30
  },
31
31
  force_www: '',
32
- session_key_variable: 'SESSION_KEY',
33
32
  capabilities: {
34
33
  database: false,
35
34
  database_dialect: 'postgres',
36
- database_url_variable: 'DATABASE_URL',
37
35
  redis: false,
38
- redis_url_variable: 'REDIS_URL',
39
36
  webpush: false,
40
37
  vapid_subject: 'mailto:foo@example.com',
41
- vapid_public_key_variable: 'VAPID_PUBLIC_KEY',
42
- vapid_private_key_variable: 'VAPID_PRIVATE_KEY',
43
38
  },
44
39
  }, config, 'patch');
45
40
  }
@@ -77,12 +72,6 @@ export default class Server extends Dotfile {
77
72
  choices: CHOICES.force_www,
78
73
  initial: this.indexOfInitial(CHOICES.force_www, config.force_www),
79
74
  },
80
- {
81
- name: 'session_key_variable',
82
- type: 'text',
83
- message: 'Enter the environment variable name for SESSION_KEY if not as written',
84
- initial: config.session_key_variable,
85
- },
86
75
  {
87
76
  name: 'https',
88
77
  controls: {
@@ -142,11 +131,6 @@ export default class Server extends Dotfile {
142
131
  type: (prev, answers) => !answers.database ? null : 'text',
143
132
  message: 'Enter the database dialect (postgres for now)',
144
133
  },
145
- {
146
- name: 'database_url_variable',
147
- type: (prev, answers) => !answers.database ? null : 'text',
148
- message: 'Enter the environment variable name for DATABASE_URL if not as written',
149
- },
150
134
  {
151
135
  name: 'redis',
152
136
  type: 'toggle',
@@ -154,11 +138,6 @@ export default class Server extends Dotfile {
154
138
  active: 'YES',
155
139
  inactive: 'NO',
156
140
  },
157
- {
158
- name: 'redis_url_variable',
159
- type: (prev, answers) => !answers.redis ? null : 'text',
160
- message: 'Enter the environment variable name for REDIS_URL if not as written',
161
- },
162
141
  {
163
142
  name: 'webpush',
164
143
  type: 'toggle',
@@ -170,17 +149,7 @@ export default class Server extends Dotfile {
170
149
  name: 'vapid_subject',
171
150
  type: (prev, answers) => !answers.webpush ? null : 'text',
172
151
  message: 'Enter the vapid_subject URL',
173
- },
174
- {
175
- name: 'vapid_public_key_variable',
176
- type: (prev, answers) => !answers.webpush ? null : 'text',
177
- message: 'Enter the environment variable name for VAPID_PUBLIC_KEY if not as written',
178
- },
179
- {
180
- name: 'vapid_private_key_variable',
181
- type: (prev, answers) => !answers.webpush ? null : 'text',
182
- message: 'Enter the environment variable name for VAPID_PRIVATE_KEY if not as written',
183
- },
152
+ }
184
153
  ]
185
154
  },
186
155
  ];
@@ -6,18 +6,22 @@ export class HttpUser extends WebfloStorage {
6
6
  return new this(request, session, client);
7
7
  }
8
8
 
9
+ #session;
9
10
  #client;
10
11
 
11
12
  constructor(request, session, client) {
12
13
  super(session, '#user', request, session);
14
+ this.#session = session;
13
15
  this.#client = client;
14
16
  }
15
17
 
16
18
  async isSignedIn() {
19
+ await this.#session.refresh();
17
20
  return await this.has('id');
18
21
  }
19
22
 
20
23
  async signIn(...args) {
24
+ await this.#session.refresh();
21
25
  return await this.require(
22
26
  ['id'].concat(typeof args[0] === 'string' || Array.isArray(args[0]) ? args.unshift() : []),
23
27
  ...args
@@ -26,6 +30,7 @@ export class HttpUser extends WebfloStorage {
26
30
 
27
31
  async signOut() {
28
32
  await this.clear();
33
+ await this.#session.commit();
29
34
  }
30
35
 
31
36
  async confirm(data, callback, options = {}) {
@@ -27,9 +27,17 @@ export class WebfloStorage {
27
27
  return this.#store;
28
28
  }
29
29
 
30
+ async refresh() {
31
+ let $store;
32
+ if (!this.#store || !($store = await this.#registry.get(this.#key))) return;
33
+ Object.assign(this.#store, $store);
34
+ }
35
+
30
36
  async commit() {
31
- if (!this.#store || !this.#key || !this.#modified) return;
32
- await this.#registry.set(this.#key, this.#store);
37
+ if (this.#store && this.#key && this.#modified) {
38
+ await this.#registry.set(this.#key, this.#store);
39
+ }
40
+ this.#modified = false;
33
41
  }
34
42
 
35
43
  get size() { return this.store().then((store) => Object.keys(store).length); }
@@ -78,6 +86,7 @@ export class WebfloStorage {
78
86
  for (const key of await this.keys()) {
79
87
  Reflect.deleteProperty(await this.store(), key);
80
88
  }
89
+ this.#modified = true;
81
90
  await this.emit();
82
91
  return this;
83
92
  }
@@ -2,6 +2,7 @@ const { Observer } = webqit;
2
2
 
3
3
  export class Capabilities {
4
4
 
5
+ #runtime;
5
6
  #params;
6
7
 
7
8
  #exposed = {};
@@ -9,11 +10,10 @@ export class Capabilities {
9
10
 
10
11
  #cleanups = [];
11
12
 
12
- static async initialize(params) {
13
+ static async initialize(runtime, params) {
13
14
  const instance = new this;
15
+ instance.#runtime = runtime;
14
16
  instance.#params = params;
15
- instance.#params.generic_public_webhook_url = instance.#params.generic_public_webhook_url_variable && instance.#params.env[instance.#params.generic_public_webhook_url_variable];
16
- instance.#params.vapid_public_key = instance.#params.vapid_public_key_variable && instance.#params.env[instance.#params.vapid_public_key_variable];
17
17
  // --------
18
18
  // Custom install
19
19
  const onbeforeinstallprompt = (e) => {
@@ -26,11 +26,11 @@ export class Capabilities {
26
26
  instance.#cleanups.push(() => window.removeEventListener('beforeinstallprompt', onbeforeinstallprompt));
27
27
  // --------
28
28
  // Webhooks
29
- if (instance.#params.generic_public_webhook_url) {
29
+ if (instance.#runtime.env('GENERIC_PUBLIC_WEBHOOK_URL')) {
30
30
  // --------
31
31
  // app.installed
32
32
  const onappinstalled = () => {
33
- fetch(instance.#params.generic_public_webhook_url, {
33
+ fetch(instance.#runtime.env('GENERIC_PUBLIC_WEBHOOK_URL'), {
34
34
  method: 'POST',
35
35
  headers: { 'Content-Type': 'application/json' },
36
36
  body: JSON.stringify({ type: 'app.installed', data: true })
@@ -51,7 +51,7 @@ export class Capabilities {
51
51
  if (eventPayload.type === 'push.subscribe' && !eventPayload.data) {
52
52
  return window.queueMicrotask(pushPermissionStatusHandler);
53
53
  }
54
- fetch(instance.#params.generic_public_webhook_url, {
54
+ fetch(instance.#runtime.env('GENERIC_PUBLIC_WEBHOOK_URL'), {
55
55
  method: 'POST',
56
56
  headers: { 'Content-Type': 'application/json' },
57
57
  body: JSON.stringify(eventPayload)
@@ -186,8 +186,8 @@ export class Capabilities {
186
186
  if (!params.userVisibleOnly) {
187
187
  params = { ...params, userVisibleOnly: true };
188
188
  }
189
- if (!params.applicationServerKey && this.#params.vapid_public_key) {
190
- params = { ...params, applicationServerKey: urlBase64ToUint8Array(this.#params.vapid_public_key) };
189
+ if (!params.applicationServerKey && this.#runtime.env('VAPID_PUBLIC_KEY')) {
190
+ params = { ...params, applicationServerKey: urlBase64ToUint8Array(this.#runtime.env('VAPID_PUBLIC_KEY')) };
191
191
  }
192
192
  }
193
193
  return params;
@@ -48,6 +48,12 @@ export class WebfloClient extends WebfloRuntime {
48
48
  return document.querySelector('meta[name="webflo-viewtransitions"]')?.value;
49
49
  }
50
50
 
51
+ env(key) {
52
+ return key in this.cx.params.mappings
53
+ ? this.cx.params.env[this.cx.params.mappings[key]]
54
+ : this.cx.params.env[key];
55
+ }
56
+
51
57
  constructor(host) {
52
58
  super();
53
59
  this.#host = host;
@@ -317,6 +323,7 @@ export class WebfloClient extends WebfloRuntime {
317
323
  if (scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
318
324
  throw new Error('Final response already sent');
319
325
  }
326
+ await scope.httpEvent.session.commit();
320
327
  return await this.execPush(scope.clientMessaging, response);
321
328
  },
322
329
  };
@@ -52,7 +52,7 @@ export class WebfloRootClient1 extends WebfloClient {
52
52
  this.#workport = await this.constructor.Workport.initialize(null, (this.cx.params.public_base_url || '') + filename, restServiceWorkerParams);
53
53
  cleanups.push(() => this.#workport.close());
54
54
  }
55
- this.#capabilities = await this.constructor.Capabilities.initialize({ ...this.cx.params.capabilities, env: this.cx.params.env });
55
+ this.#capabilities = await this.constructor.Capabilities.initialize(this, this.cx.params.capabilities);
56
56
  cleanups.push(() => this.#capabilities.close());
57
57
  // --------
58
58
  // Bind network status handlers
@@ -25,14 +25,11 @@ export async function generate() {
25
25
  if (!cx.config.deployment?.Layout) {
26
26
  throw new Error(`The Client configurator "config.deployment.Layout" is required in context.`);
27
27
  }
28
- const env = {};
29
28
  const clientConfig = await (new cx.config.runtime.Client(cx)).read();
30
- clientConfig.env = env;
31
29
  if (clientConfig.capabilities?.service_worker && !cx.config.runtime.client?.Worker) {
32
30
  throw new Error(`The Service Worker configurator "config.runtime.client.Worker" is required in context.`);
33
31
  }
34
32
  const workerConfig = await (new cx.config.runtime.client.Worker(cx)).read();
35
- workerConfig.env = env;
36
33
  // -----------
37
34
  if (!cx.config.deployment?.Layout) {
38
35
  throw new Error(`The Layout configurator "config.deployment.Layout" is required in context.`);
@@ -43,17 +40,25 @@ export async function generate() {
43
40
  const dirClient = Path.resolve(cx.CWD || '', layoutConfig.CLIENT_DIR);
44
41
  const dirWorker = Path.resolve(cx.CWD || '', layoutConfig.WORKER_DIR);
45
42
  const dirSelf = Path.dirname(Url.fileURLToPath(import.meta.url)).replace(/\\/g, '/');
43
+ const env = {};
44
+ const envConfig = { mappings: {} };
45
+ // Copy vars
46
46
  if (clientConfig.copy_public_variables) {
47
- if (!cx.config.deployment?.Env) {
48
- throw new Error(`The Layout configurator "config.deployment.Env" is required in context to bundle public env.`);
49
- }
50
- const envConfig = await (new cx.config.deployment.Env(cx)).read();
51
- const $env = { ...envConfig.entries, ...process.env };
47
+ const $env = { ...process.env };
52
48
  for (const key in $env) {
53
49
  if (!key.includes('PUBLIC_') && !key.includes('_PUBLIC')) continue;
54
50
  env[key] = $env[key];
55
51
  }
52
+ if (cx.config.deployment?.Env) {
53
+ const $envConfig = await (new cx.config.deployment.Env(cx)).read();
54
+ envConfig.mappings = $envConfig.mappings;
55
+ }
56
56
  }
57
+ // Expose these nodes
58
+ clientConfig.env = env;
59
+ workerConfig.env = env;
60
+ clientConfig.mappings = envConfig.mappings;
61
+ workerConfig.mappings = envConfig.mappings;
57
62
  // -----------
58
63
  // Scan Subdocuments
59
64
  const scanSubroots = (sparoot, rootFileName) => {
@@ -76,7 +81,7 @@ export async function generate() {
76
81
  const generateClient = async function(sparoot, spaGraphCallback = null) {
77
82
  let [ subsparoots, targets ] = (sparoot && scanSubroots(sparoot, 'index.html')) || [ [], false ];
78
83
  if (!sparoot) sparoot = '/';
79
- let spaRouting = { root: sparoot, subroots: subsparoots, targets };
84
+ let spaRouting = { name: 'client', root: sparoot, subroots: subsparoots, targets };
80
85
  let codeSplitting = !!(sparoot !== '/' || subsparoots.length);
81
86
  let outfileMain = Path.join(sparoot, clientConfig.bundle_filename),
82
87
  outfileWebflo = _beforeLast(clientConfig.bundle_filename, '.js') + '.webflo.js';
@@ -151,7 +156,7 @@ export async function generate() {
151
156
  const generateWorker = async function(workerroot, workerGraphCallback = null) {
152
157
  let [ subworkerroots, targets ] = workerroot && scanSubroots(workerroot, 'workerroot') || [ [], false ];
153
158
  if (!workerroot) workerroot = '/';
154
- let workerRouting = { root: workerroot, subroots: subworkerroots, targets };
159
+ let workerRouting = { name: 'worker', root: workerroot, subroots: subworkerroots, targets };
155
160
  let gen = { imports: {}, code: [], };
156
161
  if (cx.logger) {
157
162
  cx.logger.log(cx.logger.style.comment(`-----------------`));
@@ -273,17 +278,19 @@ function declareRoutesObj(gen, routesDir, targetDir, varName, routing) {
273
278
  let _routesDir = Path.join(routesDir, routing.root),
274
279
  _targetDir = Path.join(targetDir, routing.root);
275
280
  cx.logger && cx.logger.log(cx.logger.style.keyword(`> `) + `Declaring routes...`);
281
+ const routeFileEnding = new RegExp(`(?:\\/(?:${routing.name}|index)\\.js)$`);
276
282
  // ----------------
277
283
  // Directory walker
278
284
  const walk = (dir, callback) => {
279
285
  Fs.readdirSync(dir).forEach(f => {
280
286
  let resource = Path.join(dir, f);
281
287
  let _namespace = '/' + Path.relative(routesDir, resource).replace(/\\/g, '/');
282
- let namespace = _beforeLast(_namespace, '/index.js') || '/';
283
288
  if (Fs.statSync(resource).isDirectory()) {
284
- if (routing.subroots.includes(namespace)) return;
289
+ if (routing.subroots.includes(_namespace)) return;
285
290
  walk(resource, callback);
286
291
  } else {
292
+ if (f === 'index.js' && Fs.existsSync(Path.join(dir, `${routing.name}.js`))) return;
293
+ let namespace = _namespace.replace(routeFileEnding, '') || '/';
287
294
  let relativePath = Path.relative(_targetDir, resource).replace(/\\/g, '/');
288
295
  callback(resource, namespace, relativePath);
289
296
  }
@@ -295,7 +302,7 @@ function declareRoutesObj(gen, routesDir, targetDir, varName, routing) {
295
302
  let indexCount = 0;
296
303
  if (Fs.existsSync(_routesDir)) {
297
304
  walk(_routesDir, (file, namespace, relativePath) => {
298
- if (relativePath.endsWith('/index.js')) {
305
+ if (routeFileEnding.test(relativePath)) {
299
306
  // Import code
300
307
  let routeName = 'index' + (++ indexCount);
301
308
  // IMPORTANT: we;re taking a step back here so that the parent-child relationship for
@@ -157,6 +157,7 @@ export class WebfloWorker extends WebfloRuntime {
157
157
  if (scope.eventLifecyclePromises.dirty && !scope.eventLifecyclePromises.size) {
158
158
  throw new Error('Final response already sent');
159
159
  }
160
+ await scope.httpEvent.session.commit();
160
161
  return await this.execPush(scope.clientMessaging, response);
161
162
  },
162
163
  };
@@ -13,9 +13,9 @@ export class Router extends WebfloRouter {
13
13
  if (_segmentOnFile.index) return _segmentOnFile;
14
14
  var _currentPath = thisTick.trailOnFile.concat(_seg).join('/'),
15
15
  routeHandlerFile;
16
- return Fs.existsSync(routeHandlerFile = Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, _currentPath, 'index.js')) ? { seg: _seg, index: routeHandlerFile } : (
17
- Fs.existsSync(Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, _currentPath)) ? { seg: _seg, dirExists: true } : _segmentOnFile
18
- );
16
+ return Fs.existsSync(routeHandlerFile = Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, _currentPath, 'server.js')) || Fs.existsSync(routeHandlerFile = Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, _currentPath, 'index.js'))
17
+ ? { seg: _seg, index: routeHandlerFile }
18
+ : (Fs.existsSync(Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, _currentPath)) ? { seg: _seg, dirExists: true } : _segmentOnFile);
19
19
  }, { seg: null });
20
20
  thisTick.trail = thisTick.trail.concat(thisTick.currentSegment);
21
21
  thisTick.trailOnFile = thisTick.trailOnFile.concat(thisTick.currentSegmentOnFile.seg);
@@ -23,8 +23,11 @@ export class Router extends WebfloRouter {
23
23
  } else {
24
24
  thisTick.trail = [];
25
25
  thisTick.trailOnFile = [];
26
- thisTick.currentSegmentOnFile = { index: Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, 'index.js') };
27
- thisTick.exports = Fs.existsSync(thisTick.currentSegmentOnFile.index)
26
+ let routeHandlerFile;
27
+ thisTick.currentSegmentOnFile = Fs.existsSync(routeHandlerFile = Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, 'server.js')) || Fs.existsSync(routeHandlerFile = Path.join(this.cx.CWD, this.cx.layout.SERVER_DIR, 'index.js'))
28
+ ? { index: routeHandlerFile }
29
+ : {};
30
+ thisTick.exports = thisTick.currentSegmentOnFile.index
28
31
  ? await import(Url.pathToFileURL(thisTick.currentSegmentOnFile.index))
29
32
  : null;
30
33
  }
@@ -30,26 +30,28 @@ export class SessionStorage extends WebfloStorage {
30
30
  sessionID = crypto.randomUUID();
31
31
  }
32
32
  }
33
- return new this(params.registry || inmemSessionRegistry, sessionID, request);
33
+ return new this(params.registry || inmemSessionRegistry, sessionID, request, params.ttl);
34
34
  }
35
35
 
36
36
  #sessionID;
37
37
  get sessionID() { return this.#sessionID; }
38
+ #ttl;
38
39
 
39
- constructor(reqistry, sessionID, request) {
40
+ constructor(reqistry, sessionID, request, ttl) {
40
41
  super(
41
42
  reqistry,
42
- `session:${sessionID}`,
43
+ `session/${sessionID}`,
43
44
  request,
44
45
  true
45
46
  );
46
47
  this.#sessionID = sessionID;
48
+ this.#ttl = ttl;
47
49
  }
48
50
 
49
51
  async commit(response = null) {
50
52
  if (response && !response.headers.get('Set-Cookie', true).find((c) => c.name === '__sessid')) {
51
53
  // expires six months
52
- response.headers.append('Set-Cookie', `__sessid=${this.#sessionID}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=15768000`);
54
+ response.headers.append('Set-Cookie', `__sessid=${this.#sessionID}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=${this.#ttl}`);
53
55
  }
54
56
  await super.commit();
55
57
  }
@@ -5,6 +5,7 @@ import Http from 'http';
5
5
  import Https from 'https';
6
6
  import WebSocket from 'ws';
7
7
  import Mime from 'mime-types';
8
+ import 'dotenv/config';
8
9
  import QueryString from 'querystring';
9
10
  import { _from as _arrFrom, _any } from '@webqit/util/arr/index.js';
10
11
  import { _isEmpty, _isObject } from '@webqit/util/js/index.js';
@@ -65,20 +66,25 @@ export class WebfloServer extends WebfloRuntime {
65
66
  this.#cx = cx;
66
67
  }
67
68
 
69
+ env(key) {
70
+ return key in this.#cx.env.mappings
71
+ ? process.env[this.#cx.env.mappings[key]]
72
+ : process.env[key];
73
+ }
74
+
68
75
  async initialize() {
76
+ process.on('uncaughtException', (err) => {
77
+ console.error('Uncaught- Exception:', err);
78
+ });
79
+ process.on('unhandledRejection', (reason, promise) => {
80
+ console.log('Unhandled -Rejection', reason, promise);
81
+ });
69
82
  const resolveContextObj = async (cx, force = false) => {
70
83
  if (_isEmpty(cx.layout) || force) { cx.layout = await (new cx.config.deployment.Layout(cx)).read(); }
71
84
  if (_isEmpty(cx.server) || force) { cx.server = await (new cx.config.runtime.Server(cx)).read(); }
72
85
  if (_isEmpty(cx.env) || force) { cx.env = await (new cx.config.deployment.Env(cx)).read(); }
73
86
  };
74
87
  await resolveContextObj(this.#cx);
75
- if (this.#cx.env.autoload !== false) {
76
- Object.keys(this.#cx.env.entries).forEach(key => {
77
- if (!(key in process.env)) {
78
- process.env[key] = this.#cx.env.entries[key];
79
- }
80
- });
81
- }
82
88
  // ---------------
83
89
  if (this.#cx.config.deployment.Proxy) {
84
90
  const proxied = await (new this.#cx.config.deployment.Proxy(this.#cx)).read();
@@ -115,18 +121,14 @@ export class WebfloServer extends WebfloRuntime {
115
121
  }
116
122
  if (this.#proxies.size) {
117
123
  this.#cx.logger.info(`> Reverse proxy active.`);
118
- for (let [id, def] of this.#proxies) {
124
+ for (const [id, def] of this.#proxies) {
119
125
  this.#cx.logger.info(`> ${id} >>> ${def.port}`);
120
126
  }
121
127
  }
122
128
  this.#cx.logger.info(``);
129
+ this.#cx.logger.info(`Capabilities: ${Object.keys(this.#sdk).join(', ')}`);
130
+ this.#cx.logger.info(``);
123
131
  }
124
- process.on('uncaughtException', (err) => {
125
- console.error('Uncaught Exception:', err);
126
- });
127
- process.on('unhandledRejection', (reason, promise) => {
128
- console.log('Unhandled Rejection', reason, promise);
129
- });
130
132
  }
131
133
 
132
134
  control() {
@@ -189,55 +191,45 @@ export class WebfloServer extends WebfloRuntime {
189
191
  if (this.#cx.server.capabilities.database_dialect !== 'postgres') {
190
192
  throw new Error(`Only postgres supported for now for database dialect`);
191
193
  }
192
- if (process.env[this.#cx.server.capabilities.database_url_variable]) {
193
- console.log('Database capabilities');
194
+ if (this.env('DATABASE_URL')) {
194
195
  const { SQLClient } = await import('@linked-db/linked-ql/sql');
195
196
  const { default: pg } = await import('pg');
196
197
  // Obtain pg client
197
198
  const pgClient = new pg.Pool({
198
- connectionString: process.env[this.#cx.server.capabilities.database_url_variable],
199
- database: 'postgres',
200
- idle_in_transaction_session_timeout: 0
199
+ connectionString: this.env('DATABASE_URL')
201
200
  });
202
- // Connect
203
201
  await (async function connect() {
204
- /*
205
202
  pgClient.on('error', (e) => {
206
- console.log('____________error_', e);
203
+ console.log('PG Error', e);
207
204
  });
208
205
  pgClient.on('end', (e) => {
209
- console.log('____________end_', e);
206
+ console.log('PG End', e);
210
207
  });
211
208
  pgClient.on('notice', (e) => {
212
- console.log('____________notice_', e);
209
+ console.log('PG Notice', e);
213
210
  });
214
- */
215
211
  await pgClient.connect();
216
212
  })();
217
213
  this.#sdk.db = new SQLClient(pgClient, { dialect: 'postgres' });
218
214
  } else {
219
- console.log('No database capabilities');
220
215
  //const { ODBClient } = await import('@linked-db/linked-ql/odb');
221
216
  //this.#sdk.db = new ODBClient({ dialect: 'postgres' });
222
217
  }
223
218
  }
224
219
  if (this.#cx.server.capabilities?.redis) {
225
220
  const { Redis } = await import('ioredis');
226
- this.#sdk.redis = process.env[this.#cx.server.capabilities.redis_url_variable]
227
- ? new Redis(process.env[this.#cx.server.capabilities.redis_url_variable])
221
+ this.#sdk.redis = this.env('REDIS_URL')
222
+ ? new Redis(this.env('REDIS_URL'))
228
223
  : new Redis;
229
- console.log('Redis capabilities', process.env[this.#cx.server.capabilities.redis_url_variable] ? 'with url' : '');
230
224
  }
231
225
  if (this.#cx.server.capabilities?.webpush) {
232
226
  const { default: webpush } = await import('web-push');
233
- console.log('Webpuah capabilities');
234
227
  this.#sdk.webpush = webpush;
235
- if (process.env[this.#cx.server.capabilities.vapid_public_key_variable]
236
- && process.env[this.#cx.server.capabilities.vapid_private_key_variable]) {
228
+ if (this.env('VAPID_PUBLIC_KEY') && this.env('VAPID_PRIVATE_KEY')) {
237
229
  webpush.setVapidDetails(
238
230
  this.#cx.server.capabilities.vapid_subject,
239
- process.env[this.#cx.server.capabilities.vapid_public_key_variable],
240
- process.env[this.#cx.server.capabilities.vapid_private_key_variable]
231
+ this.env('VAPID_PUBLIC_KEY'),
232
+ this.env('VAPID_PRIVATE_KEY')
241
233
  );
242
234
  }
243
235
  }
@@ -280,11 +272,12 @@ export class WebfloServer extends WebfloRuntime {
280
272
  // and actual processing
281
273
  scope.request = this.createRequest(scope.url.href, requestInit);
282
274
  scope.session = this.constructor.SessionStorage.create(scope.request, {
283
- secret: process.env[this.#cx.server.session_key_variable],
275
+ secret: this.env('SESSION_KEY'),
284
276
  registry: this.#sdk.redis && {
285
- get: async (key) => { return JSON.parse(await this.#sdk.redis.get(key) || null) },
286
- set: async (key, value) => { return await this.#sdk.redis.set(key, JSON.stringify(value), 'EX', 15768000) },
277
+ get: async (key) => { return JSON.parse(await this.#sdk.redis.get(`${scope.url.host}/${key}`) || null) },
278
+ set: async (key, value) => { return await this.#sdk.redis.set(`${scope.url.host}/${key}`, JSON.stringify(value), 'EX', this.env('SESSION_TTL') || 2592000/*30days*/) },
287
279
  },
280
+ ttl: this.env('SESSION_TTL') || 2592000/*30days*/,
288
281
  });
289
282
  if (!scope.error) {
290
283
  if (!(scope.clientMessagingRegistry = this.#globalMessagingRegistry.get(scope.session.sessionID))) {
@@ -573,6 +566,7 @@ export class WebfloServer extends WebfloRuntime {
573
566
  console.error('Final response already sent');
574
567
  return;
575
568
  }
569
+ await scope.httpEvent.session.commit();
576
570
  return await this.execPush(scope.clientMessaging, response);
577
571
  },
578
572
  };
@@ -584,11 +578,12 @@ export class WebfloServer extends WebfloRuntime {
584
578
  scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
585
579
  scope.cookies = this.constructor.CookieStorage.create(scope.request);
586
580
  scope.session = this.constructor.SessionStorage.create(scope.request, {
587
- secret: process.env[this.#cx.server.session_key_variable],
581
+ secret: this.env('SESSION_KEY'),
588
582
  registry: this.#sdk.redis && {
589
- get: async (key) => { return JSON.parse(await this.#sdk.redis.get(key) || null) },
590
- set: async (key, value) => { return await this.#sdk.redis.set(key, JSON.stringify(value), 'EX', 15768000) },
583
+ get: async (key) => { return JSON.parse(await this.#sdk.redis.get(`${scope.url.host}/${key}`) || null) },
584
+ set: async (key, value) => { return await this.#sdk.redis.set(`${scope.url.host}/${key}`, JSON.stringify(value), 'EX', this.env('SESSION_TTL') || 2592000/*30days*/); },
591
585
  },
586
+ ttl: this.env('SESSION_TTL') || 2592000/*30days*/,
592
587
  });
593
588
  const sessionID = scope.session.sessionID;
594
589
  if (!this.#globalMessagingRegistry.has(sessionID)) {