@webqit/webflo 1.0.31 → 1.0.33

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.31",
15
+ "version": "1.0.33",
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
  ];
@@ -28,8 +28,10 @@ export class WebfloStorage {
28
28
  }
29
29
 
30
30
  async commit() {
31
- if (!this.#store || !this.#key || !this.#modified) return;
32
- await this.#registry.set(this.#key, this.#store);
31
+ if (this.#store && this.#key && this.#modified) {
32
+ await this.#registry.set(this.#key, this.#store);
33
+ }
34
+ this.#modified = false;
33
35
  }
34
36
 
35
37
  get size() { return this.store().then((store) => Object.keys(store).length); }
@@ -78,6 +80,7 @@ export class WebfloStorage {
78
80
  for (const key of await this.keys()) {
79
81
  Reflect.deleteProperty(await this.store(), key);
80
82
  }
83
+ this.#modified = true;
81
84
  await this.emit();
82
85
  return this;
83
86
  }
@@ -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)
@@ -134,6 +134,8 @@ export class Capabilities {
134
134
  if (this.#exposed.custom_install === 'granted') return;
135
135
  if (this.#exposed.custom_install) {
136
136
  returnValue = await this.#exposed.custom_install.prompt?.();
137
+ const { outcome } = await this.#exposed.custom_install.userChoice;
138
+ if (outcome === 'dismissed') return;
137
139
  }
138
140
  Observer.set(this.#exposed, 'custom_install', 'granted');
139
141
  return returnValue;
@@ -184,8 +186,8 @@ export class Capabilities {
184
186
  if (!params.userVisibleOnly) {
185
187
  params = { ...params, userVisibleOnly: true };
186
188
  }
187
- if (!params.applicationServerKey && this.#params.vapid_public_key) {
188
- 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')) };
189
191
  }
190
192
  }
191
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
  }
@@ -6,7 +6,6 @@ export class SessionStorage extends WebfloStorage {
6
6
 
7
7
  static create(request, params = {}) {
8
8
  let sessionID = request.headers.get('Cookie', true).find((c) => c.name === '__sessid')?.value;
9
- console.log({params});
10
9
  if (sessionID?.includes('.')) {
11
10
  if (params.secret) {
12
11
  const [rand, signature] = sessionID.split('.');
@@ -31,26 +30,28 @@ export class SessionStorage extends WebfloStorage {
31
30
  sessionID = crypto.randomUUID();
32
31
  }
33
32
  }
34
- return new this(params.registry || inmemSessionRegistry, sessionID, request);
33
+ return new this(params.registry || inmemSessionRegistry, sessionID, request, params.ttl);
35
34
  }
36
35
 
37
36
  #sessionID;
38
37
  get sessionID() { return this.#sessionID; }
38
+ #ttl;
39
39
 
40
- constructor(reqistry, sessionID, request) {
40
+ constructor(reqistry, sessionID, request, ttl) {
41
41
  super(
42
42
  reqistry,
43
- `session:${sessionID}`,
43
+ `session/${sessionID}`,
44
44
  request,
45
45
  true
46
46
  );
47
47
  this.#sessionID = sessionID;
48
+ this.#ttl = ttl;
48
49
  }
49
50
 
50
51
  async commit(response = null) {
51
52
  if (response && !response.headers.get('Set-Cookie', true).find((c) => c.name === '__sessid')) {
52
53
  // expires six months
53
- 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}`);
54
55
  }
55
56
  await super.commit();
56
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,11 +121,13 @@ 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
132
  }
125
133
 
@@ -183,43 +191,45 @@ export class WebfloServer extends WebfloRuntime {
183
191
  if (this.#cx.server.capabilities.database_dialect !== 'postgres') {
184
192
  throw new Error(`Only postgres supported for now for database dialect`);
185
193
  }
186
- if (process.env[this.#cx.server.capabilities.database_url_variable]) {
187
- console.log('Database capabilities');
194
+ if (this.env('DATABASE_URL')) {
188
195
  const { SQLClient } = await import('@linked-db/linked-ql/sql');
189
196
  const { default: pg } = await import('pg');
190
197
  // Obtain pg client
191
198
  const pgClient = new pg.Pool({
192
- connectionString: process.env[this.#cx.server.capabilities.database_url_variable],
193
- database: 'postgres',
194
- idleTimeoutMillis: 30000, // 30 seconds,
195
- keepAlive: true
199
+ connectionString: this.env('DATABASE_URL')
196
200
  });
197
- // Connect
198
- await pgClient.connect();
201
+ await (async function connect() {
202
+ pgClient.on('error', (e) => {
203
+ console.log('PG Error', e);
204
+ });
205
+ pgClient.on('end', (e) => {
206
+ console.log('PG End', e);
207
+ });
208
+ pgClient.on('notice', (e) => {
209
+ console.log('PG Notice', e);
210
+ });
211
+ await pgClient.connect();
212
+ })();
199
213
  this.#sdk.db = new SQLClient(pgClient, { dialect: 'postgres' });
200
214
  } else {
201
- console.log('No database capabilities');
202
215
  //const { ODBClient } = await import('@linked-db/linked-ql/odb');
203
216
  //this.#sdk.db = new ODBClient({ dialect: 'postgres' });
204
217
  }
205
218
  }
206
219
  if (this.#cx.server.capabilities?.redis) {
207
220
  const { Redis } = await import('ioredis');
208
- this.#sdk.redis = process.env[this.#cx.server.capabilities.redis_url_variable]
209
- ? 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'))
210
223
  : new Redis;
211
- console.log('Redis capabilities', process.env[this.#cx.server.capabilities.redis_url_variable] ? 'with url' : '');
212
224
  }
213
225
  if (this.#cx.server.capabilities?.webpush) {
214
226
  const { default: webpush } = await import('web-push');
215
- console.log('Webpuah capabilities');
216
227
  this.#sdk.webpush = webpush;
217
- if (process.env[this.#cx.server.capabilities.vapid_public_key_variable]
218
- && process.env[this.#cx.server.capabilities.vapid_private_key_variable]) {
228
+ if (this.env('VAPID_PUBLIC_KEY') && this.env('VAPID_PRIVATE_KEY')) {
219
229
  webpush.setVapidDetails(
220
230
  this.#cx.server.capabilities.vapid_subject,
221
- process.env[this.#cx.server.capabilities.vapid_public_key_variable],
222
- process.env[this.#cx.server.capabilities.vapid_private_key_variable]
231
+ this.env('VAPID_PUBLIC_KEY'),
232
+ this.env('VAPID_PRIVATE_KEY')
223
233
  );
224
234
  }
225
235
  }
@@ -262,11 +272,12 @@ export class WebfloServer extends WebfloRuntime {
262
272
  // and actual processing
263
273
  scope.request = this.createRequest(scope.url.href, requestInit);
264
274
  scope.session = this.constructor.SessionStorage.create(scope.request, {
265
- secret: process.env[this.#cx.server.session_key_variable],
275
+ secret: this.env('SESSION_KEY'),
266
276
  registry: this.#sdk.redis && {
267
- get: async (key) => { return JSON.parse(await this.#sdk.redis.get(key) || null) },
268
- 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*/) },
269
279
  },
280
+ ttl: this.env('SESSION_TTL') || 2592000/*30days*/,
270
281
  });
271
282
  if (!scope.error) {
272
283
  if (!(scope.clientMessagingRegistry = this.#globalMessagingRegistry.get(scope.session.sessionID))) {
@@ -555,6 +566,7 @@ export class WebfloServer extends WebfloRuntime {
555
566
  console.error('Final response already sent');
556
567
  return;
557
568
  }
569
+ await scope.httpEvent.session.commit();
558
570
  return await this.execPush(scope.clientMessaging, response);
559
571
  },
560
572
  };
@@ -566,11 +578,12 @@ export class WebfloServer extends WebfloRuntime {
566
578
  scope.request = this.createRequest(scope.url.href, scope.init, scope.autoHeaders.filter((header) => header.type === 'request'));
567
579
  scope.cookies = this.constructor.CookieStorage.create(scope.request);
568
580
  scope.session = this.constructor.SessionStorage.create(scope.request, {
569
- secret: process.env[this.#cx.server.session_key_variable],
581
+ secret: this.env('SESSION_KEY'),
570
582
  registry: this.#sdk.redis && {
571
- get: async (key) => { return JSON.parse(await this.#sdk.redis.get(key) || null) },
572
- 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*/); },
573
585
  },
586
+ ttl: this.env('SESSION_TTL') || 2592000/*30days*/,
574
587
  });
575
588
  const sessionID = scope.session.sessionID;
576
589
  if (!this.#globalMessagingRegistry.has(sessionID)) {