@webqit/webflo 0.8.48-0 → 0.8.52

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 (47) hide show
  1. package/docker/Dockerfile +26 -0
  2. package/docker/README.md +77 -0
  3. package/package.json +9 -8
  4. package/src/{cmd/client.js → build/client/index.js} +24 -11
  5. package/src/build/index.js +5 -0
  6. package/src/index.js +47 -45
  7. package/src/runtime/Router.js +130 -0
  8. package/src/runtime/_FormData.js +60 -0
  9. package/src/runtime/_Headers.js +88 -0
  10. package/src/runtime/_MessageStream.js +191 -0
  11. package/src/runtime/_NavigationEvent.js +89 -0
  12. package/src/runtime/_Request.js +61 -0
  13. package/src/runtime/_RequestHeaders.js +72 -0
  14. package/src/runtime/_Response.js +56 -0
  15. package/src/runtime/_ResponseHeaders.js +81 -0
  16. package/src/runtime/_URL.js +111 -0
  17. package/src/{modules → runtime}/client/Cache.js +0 -0
  18. package/src/{modules → runtime}/client/Http.js +21 -6
  19. package/src/runtime/client/NavigationEvent.js +20 -0
  20. package/src/runtime/client/Router.js +47 -0
  21. package/src/{modules/client/Client.js → runtime/client/Runtime.js} +32 -25
  22. package/src/{modules → runtime}/client/StdRequest.js +4 -8
  23. package/src/{modules → runtime}/client/Storage.js +6 -6
  24. package/src/{modules → runtime}/client/Url.js +0 -0
  25. package/src/{modules → runtime}/client/Worker.js +11 -49
  26. package/src/{modules → runtime}/client/WorkerClient.js +5 -38
  27. package/src/runtime/client/WorkerComm.js +183 -0
  28. package/src/runtime/client/effects/sounds.js +64 -0
  29. package/src/runtime/index.js +5 -0
  30. package/src/runtime/server/NavigationEvent.js +38 -0
  31. package/src/runtime/server/Router.js +158 -0
  32. package/src/{modules/server/Server.js → runtime/server/Runtime.js} +177 -85
  33. package/src/{cmd/server.js → runtime/server/index.js} +9 -9
  34. package/src/{modules/server/start.mjs → runtime/server/index.mjs} +2 -2
  35. package/src/{modules → runtime}/util.js +7 -7
  36. package/src/{modules → services}/certbot/http-auth-hook.js +0 -0
  37. package/src/{modules → services}/certbot/http-cleanup-hook.js +0 -0
  38. package/src/{cmd/certbot.js → services/certbot/index.js} +2 -3
  39. package/src/services/index.js +6 -0
  40. package/src/{cmd/origins.js → services/origins/index.js} +5 -5
  41. package/src/cmd/index.js +0 -8
  42. package/src/modules/NavigationEvent.js +0 -53
  43. package/src/modules/Response.js +0 -98
  44. package/src/modules/XURL.js +0 -125
  45. package/src/modules/client/Router.js +0 -123
  46. package/src/modules/server/Router.js +0 -231
  47. package/src/modules/server/StdIncomingMessage.js +0 -73
@@ -0,0 +1,26 @@
1
+ # Base installations
2
+ FROM node:12-alpine
3
+ RUN apk add git
4
+
5
+ # We'll install npm packages at one-level higher than
6
+ # actuall app root, so that we can bind-mount host system's app root
7
+ # without overriding the container's node_modules directory
8
+ WORKDIR /home/www
9
+ RUN npm install
10
+ RUN npm install @webqit/webflo -g
11
+ RUN npm install @webqit/playui-cli -g
12
+
13
+ # Move one-level in, for the reasons above
14
+ WORKDIR /home/www/app
15
+ ENV NODE_OPTIONS=--openssl-legacy-provider
16
+
17
+ # To auto-start app (flags optional), we would add...
18
+ # CMD ["webflo", "start", "--env=dev", "--watch", "--http-only"]
19
+
20
+ # To build the image locally...
21
+ # docker build --no-cache -t webflo ./docker
22
+
23
+ # To publish to docker hub...
24
+ # docker login -u webqit
25
+ # docker tag webflo webqit/webflo
26
+ # docker push webqit/webflo
@@ -0,0 +1,77 @@
1
+ # Webflo Container
2
+
3
+ This is simply a node.js container with the `@webqit/webflo` framework installed. Once started, any webflo app can be deployed into the container from any git repository.
4
+
5
+ ## Usage
6
+
7
+ This container image lives on Docker Hub and can be pulled to a local machine or a remote Virtual Machine (VM) on any cloud platform.
8
+
9
+ + [To Use Locally](#to-use-locally)
10
+ + [To Use In the Cloud](#to-use-in-the-cloud)
11
+ * [To Deploy An App From Any Repo](#to-deploy-an-app-from-any-repo)
12
+
13
+ ### To Use Locally
14
+
15
+ Ensure you have docker installed on your computer and run the following command from any location on your terminal:
16
+
17
+ ```shell
18
+ docker pull webqit/webflo:latest
19
+ ```
20
+
21
+ The above command pulls the `webqit/webflo` image to your local machine. (But this can be automatically done by docker on running any docker commands that reference the `webqit/webflo` image.)
22
+
23
+ Next is to use the following commands to start the container and the Webflo runtime. In each case, the first part of the command starts the container, while the second part (from `webflo start`) starts the application.
24
+
25
+ #### To Start
26
+
27
+ Start the container using `docker run`; map a port (e.g `80`) of your host machine to `3000` of the container (unless changed webflo expects to run on port `3000`); optionally, give your container a name; reference `webqit/webflo` as the image to use; and lastly, start webflo using `webflo start`.
28
+
29
+ ```shell
30
+ docker run -d -p 80:3000 --name my-app webqit/webflo webflo start
31
+ ```
32
+
33
+ Visit [localhost](http://localhost) to view your app.
34
+
35
+ #### To Start In Dev Mode
36
+
37
+ Webflo's *dev* mode is the perfect mode for developing locally. All you do is append the `--env=dev --watch` flags to your webflo commands. [(Learn more)](#)
38
+
39
+ ```shell
40
+ docker run -d -p 80:3000 --name my-app webqit/webflo webflo start --env=dev --watch
41
+ ```
42
+
43
+ In *dev* mode, webflo automatically restarts as you make changes to your codebase. Since webflo now lives inside a container, you'll need to *bind* the directory of your source code on your host machine to the `/home/www/app` directory of the container.
44
+
45
+ ```shell
46
+ docker run -d -v /Users/me/my-app:/home/www/app -p 80:3000 --name my-app webqit/webflo webflo start --env=dev --watch
47
+ ```
48
+
49
+ ### To Use In the Cloud
50
+
51
+ TODO
52
+
53
+ ### To Deploy An App From Any Repo
54
+
55
+ Whether running locally or in the cloud, webflo can easily take your application from any git repo. This follows webflo's normal `deploy` command.
56
+
57
+ Simply point docker at your container (using `docker exec [container-name]`) and execute the `webflo deploy` command.
58
+
59
+ ```shell
60
+ docker exec my-app webflo deploy https://github.com/me/my-app
61
+ ```
62
+
63
+ If you will need to install any npm dependencies, you would run `npm install` on the appropriate directory in your container.
64
+
65
+ ```shell
66
+ docker exec my-app npm install
67
+ ```
68
+
69
+ If you will need to run additional webflo commands (e.g `webflo restart` to restart the application), you would follow the same pattern above.
70
+
71
+ ```shell
72
+ docker exec my-app webflo restart
73
+ ```
74
+
75
+ ## Extending this Build
76
+
77
+ TODO
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "vanila-javascript"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/webflo",
15
- "version": "0.8.48-0",
15
+ "version": "0.8.52",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "bin": {
31
31
  "webflo": "src/index.js",
32
- "webflo-certbot-http-auth-hook": "src/modules/certbot/http-auth-hook.js",
33
- "webflo-certbot-http-cleanup-hook": "src/modules/certbot/http-cleanup-hook.js"
32
+ "webflo-certbot-http-auth-hook": "src/services/certbot/http-auth-hook.js",
33
+ "webflo-certbot-http-cleanup-hook": "src/services/certbot/http-cleanup-hook.js"
34
34
  },
35
35
  "dependencies": {
36
36
  "@octokit/auth-basic": "^1.4.6",
@@ -38,25 +38,26 @@
38
38
  "@octokit/webhooks": "^7.15.1",
39
39
  "@webqit/backpack": "^0.0.37",
40
40
  "@webqit/browser-pie": "^0.0.16",
41
- "@webqit/pseudo-browser": "^0.3.30",
41
+ "@webqit/pseudo-browser": "^0.3.35",
42
42
  "@webqit/util": "^0.8.7",
43
- "accepts": "^1.3.7",
44
43
  "client-sessions": "^0.8.0",
45
44
  "cookie": "^0.4.1",
45
+ "form-data-encoder": "^1.6.0",
46
+ "formdata-node": "^4.3.0",
46
47
  "formidable": "^2.0.0-dev.20200131.2",
47
48
  "is-glob": "^4.0.1",
48
49
  "micromatch": "^3.1.10",
50
+ "mime-types": "^2.1.33",
49
51
  "minimatch": "^3.0.4",
52
+ "node-fetch": "^2.6.1",
50
53
  "pm2": "^5.1.0",
51
54
  "simple-git": "^2.20.1",
55
+ "stream-slice": "^0.1.2",
52
56
  "touch": "^3.1.0",
53
57
  "undici": "^4.3.1",
54
58
  "uuid": "^8.3.2",
55
59
  "webpack": "^5.50.0"
56
60
  },
57
- "devDependencies": {
58
- "nodemon": "^2.0.7"
59
- },
60
61
  "author": "Oxford Harrison <oxharris.dev@gmail.com>",
61
62
  "maintainers": [
62
63
  "Oxford Harrison <oxharris.dev@gmail.com>"
@@ -9,7 +9,7 @@ import Webpack from 'webpack';
9
9
  import { _beforeLast } from '@webqit/util/str/index.js';
10
10
  import { _isObject, _isArray, _isEmpty } from '@webqit/util/js/index.js';
11
11
  import * as DotJs from '@webqit/backpack/src/dotfiles/DotJs.js';
12
- import * as client from '../config/client.js'
12
+ import * as client from '../../config/client.js'
13
13
 
14
14
 
15
15
  /**
@@ -27,7 +27,7 @@ export async function build(Ui, flags = {}, layout = {}) {
27
27
  const config = await client.read(flags, layout);
28
28
  // Consistent forward slashing
29
29
  const forwardSlash = str => str.replace(/\\/g, '/');
30
- var modulesDir = forwardSlash(Url.fileURLToPath(Path.join(import.meta.url, '../../modules/client')));
30
+ var modulesDir = forwardSlash(Url.fileURLToPath(Path.join(import.meta.url, '../../../runtime/client')));
31
31
 
32
32
  var workerDirSplit = Path.resolve(layout.WORKER_DIR).replace(/\\/g, '/').split('/');
33
33
  var workerParams = config.worker || {};
@@ -85,17 +85,17 @@ export async function build(Ui, flags = {}, layout = {}) {
85
85
  Ui.title(`CLIENT BUILD`);
86
86
 
87
87
  const clientBuild = { imports: {}, code: [], };
88
- clientBuild.imports[modulesDir + '/Client.js'] = 'Client';
88
+ clientBuild.imports[modulesDir + '/Runtime.js'] = 'Runtime';
89
89
 
90
90
  // ------------------
91
91
  // >> Routes mapping
92
92
  buildRoutes(clientBuild, Ui, Path.resolve(layout.CLIENT_DIR), 'Client-Side Routing:');
93
93
  clientBuild.code.push(``);
94
- clientBuild.code.push(`// >> Client Params`);
94
+ clientBuild.code.push(`// >> Runtime Params`);
95
95
  buildParams(clientBuild, clientParams, 'params');
96
96
  clientBuild.code.push(``);
97
- clientBuild.code.push(`// >> Client Instantiation`);
98
- clientBuild.code.push(`Client.call(null, layout, params);`);
97
+ clientBuild.code.push(`// >> Runtime Instantiation`);
98
+ clientBuild.code.push(`Runtime.call(null, layout, params);`);
99
99
  // ------------------
100
100
 
101
101
  // ------------------
@@ -107,9 +107,9 @@ export async function build(Ui, flags = {}, layout = {}) {
107
107
  };
108
108
  waiting = Ui.waiting(`Writing the client entry file: ${clientBundlingConfig.intermediate}`);
109
109
  waiting.start();
110
- DotJs.write(clientBuild, clientBundlingConfig.intermediate, 'Client Build File');
110
+ DotJs.write(clientBuild, clientBundlingConfig.intermediate, 'Runtime Build File');
111
111
  waiting.stop();
112
- Ui.info(Ui.f`Client Build file: ${clientBundlingConfig.intermediate}`);
112
+ Ui.info(Ui.f`Runtime Build file: ${clientBundlingConfig.intermediate}`);
113
113
  // ------------------
114
114
 
115
115
  // -------------------
@@ -124,10 +124,23 @@ export async function build(Ui, flags = {}, layout = {}) {
124
124
  await createBundle(Ui, workerBundlingConfig, 'Bundling the Service Worker Build file');
125
125
  }
126
126
  if (clientParams.bundling !== false) {
127
- await createBundle(Ui, clientBundlingConfig, 'Bundling the Client Build file');
127
+ clientBundlingConfig.experiments = clientBundlingConfig.experiments || {};
128
+ if (!('outputModule' in clientBundlingConfig.experiments)) {
129
+ clientBundlingConfig.experiments.outputModule = true;
130
+ clientBundlingConfig.externalsType = 'module';
131
+ }
132
+ if (clientBundlingConfig.experiments.outputModule !== false) {
133
+ clientBundlingConfig.output = clientBundlingConfig.output || {};
134
+ clientBundlingConfig.output.environment = clientBundlingConfig.output.environment || {};
135
+ if (!('module' in clientBundlingConfig.output)) {
136
+ clientBundlingConfig.output.module = true;
137
+ clientBundlingConfig.output.environment.module = true;
138
+ }
139
+ }
140
+ await createBundle(Ui, clientBundlingConfig, 'Bundling the Runtime Build file');
128
141
  }
129
142
 
130
- };
143
+ }
131
144
 
132
145
  /**
133
146
  * Creates a bundle using webpack
@@ -245,4 +258,4 @@ const buildParams = (build, params, varName = null, indentation = 0) => {
245
258
  }
246
259
  });
247
260
  if (varName) build.code.push(`};`);
248
- }
261
+ };
@@ -0,0 +1,5 @@
1
+
2
+ /**
3
+ * exports
4
+ */
5
+ export * as client from './client/index.js';
package/src/index.js CHANGED
@@ -9,16 +9,18 @@ import parseArgs from '@webqit/backpack/src/cli/parseArgs.js';
9
9
  import Ui from '@webqit/backpack/src/cli/Ui.js';
10
10
  import * as DotJson from '@webqit/backpack/src/dotfiles/DotJson.js';
11
11
  import { Promptx } from '@webqit/backpack/src/cli/Promptx.js';
12
+ import * as build from './build/index.js';
12
13
  import * as config from './config/index.js';
13
- import * as cmd from './cmd/index.js';
14
+ import * as runtime from './runtime/index.js';
15
+ import * as services from './services/index.js';
14
16
 
15
17
  // ------------------------------------------
16
18
 
17
19
  const commands = {
18
20
  config: 'Starts a configuration process.',
19
- build: cmd.client.desc.build,
20
- deploy: cmd.origins.desc.deploy,
21
- ...cmd.server.desc,
21
+ build: build.client.desc.build,
22
+ deploy: services.origins.desc.deploy,
23
+ ...runtime.server.desc,
22
24
  };
23
25
 
24
26
  // ------------------------------------------
@@ -37,60 +39,60 @@ console.log('');
37
39
  // --------------------------
38
40
 
39
41
  case 'build':
40
- cmd.client.build(Ui, flags, layout);
42
+ build.client.build(Ui, flags, layout);
41
43
  break;
42
44
 
43
45
  // --------------------------
44
46
 
45
- case 'deploy':
46
- var origin = Object.keys(keywords)[0],
47
- options;
47
+ case 'config':
48
+
49
+ var domain = Object.keys(keywords)[0];
48
50
  // ----------------
49
- if (!origin && ellipsis) {
50
- if (!(options = (await config.origins.read(flags, layout)).REPOS) || _isEmpty(options)) {
51
- Ui.log(Ui.f`Please configure an origin (${'webflo config ...'}) to use the ${'deploy'} command.`);
52
- return;
53
- }
54
- origin = await Promptx({
55
- name: 'origin',
51
+ if (!domain && ellipsis) {
52
+ domain = await Promptx({
53
+ name: 'domain',
56
54
  type: 'select',
57
- choices: options.map(r => ({value: r.TAG})),
58
- message: 'Please select a origin',
59
- }).then(d => d.origin);
55
+ choices: Object.keys(config).map(c => ({value: c})),
56
+ message: 'Please select a configuration domain',
57
+ }).then(d => d.domain);
60
58
  }
61
- if (!origin) {
62
- Ui.log(Ui.f`Please add an origin name to the ${command} command. For options, use the ellipsis ${'...'}`);
59
+ if (!domain || !config[domain]) {
60
+ Ui.log(Ui.f`Please add a configuration domain to the ${command} command. For options, use the ellipsis ${'...'}`);
63
61
  return;
64
62
  }
65
63
  // ----------------
66
- cmd.origins.deploy(Ui, origin, flags, layout);
64
+ const data = await config[domain].read(flags, layout);
65
+ Promptx(await config[domain].questions(data, {}, layout)).then(async _data => {
66
+ await config[domain].write(_merge(data, _data), flags, layout);
67
+ });
68
+
67
69
  break;
68
70
 
69
71
  // --------------------------
70
72
 
71
73
  case 'start':
72
- cmd.server.start(Ui, flags, layout);
74
+ runtime.server.start(Ui, flags, layout);
73
75
  break;
74
76
 
75
77
  case 'stop':
76
78
  case 'restart':
77
- var runtime = Object.keys(keywords)[0];
79
+ var _runtime = Object.keys(keywords)[0];
78
80
  // ----------------
79
- if (!runtime && ellipsis) {
80
- runtime = await Promptx({
81
+ if (!_runtime && ellipsis) {
82
+ _runtime = await Promptx({
81
83
  name: 'runtime',
82
84
  type: 'select',
83
- choices: (await cmd.server.processes(Ui)).map(r => ({title: r.name, description: r.status, value: r.name})).concat({description: 'All of the above', value: 'all'}),
85
+ choices: (await runtime.server.processes(Ui)).map(r => ({title: r.name, description: r.status, value: r.name})).concat({description: 'All of the above', value: 'all'}),
84
86
  message: 'Please select a runtime name',
85
87
  }).then(d => d.runtime);
86
88
  }
87
89
  // ----------------
88
- await cmd.server[command](Ui, runtime || 'all', flags);
90
+ await runtime.server[command](Ui, _runtime || 'all', flags);
89
91
  process.exit();
90
92
  break;
91
93
 
92
94
  case 'processes':
93
- const processes = await cmd.server.processes(Ui, flags);
95
+ const processes = await runtime.server.processes(Ui, flags);
94
96
  Ui.title(`SERVERS`);
95
97
  if (processes.length) {
96
98
  processes.forEach(service => {
@@ -104,35 +106,35 @@ console.log('');
104
106
 
105
107
  // --------------------------
106
108
 
107
- case 'config':
108
-
109
- var domain = Object.keys(keywords)[0];
109
+ case 'deploy':
110
+ var origin = Object.keys(keywords)[0],
111
+ options;
110
112
  // ----------------
111
- if (!domain && ellipsis) {
112
- domain = await Promptx({
113
- name: 'domain',
113
+ if (!origin && ellipsis) {
114
+ if (!(options = (await config.origins.read(flags, layout)).REPOS) || _isEmpty(options)) {
115
+ Ui.log(Ui.f`Please configure an origin (${'webflo config ...'}) to use the ${'deploy'} command.`);
116
+ return;
117
+ }
118
+ origin = await Promptx({
119
+ name: 'origin',
114
120
  type: 'select',
115
- choices: Object.keys(config).map(c => ({value: c})),
116
- message: 'Please select a configuration domain',
117
- }).then(d => d.domain);
121
+ choices: options.map(r => ({value: r.TAG})),
122
+ message: 'Please select a origin',
123
+ }).then(d => d.origin);
118
124
  }
119
- if (!domain || !config[domain]) {
120
- Ui.log(Ui.f`Please add a configuration domain to the ${command} command. For options, use the ellipsis ${'...'}`);
125
+ if (!origin) {
126
+ Ui.log(Ui.f`Please add an origin name to the ${command} command. For options, use the ellipsis ${'...'}`);
121
127
  return;
122
128
  }
123
129
  // ----------------
124
- const data = await config[domain].read(flags, layout);
125
- Promptx(await config[domain].questions(data, {}, layout)).then(async _data => {
126
- await config[domain].write(_merge(data, _data), flags, layout);
127
- });
128
-
130
+ services.origins.deploy(Ui, origin, flags, layout);
129
131
  break;
130
132
 
131
133
  // --------------------------
132
134
 
133
135
  case 'cert':
134
136
  var domains = Object.keys(keywords);
135
- cmd.certbot.generate(Ui, domains, flags, layout);
137
+ services.certbot.generate(Ui, domains, flags, layout);
136
138
  break;
137
139
 
138
140
  case 'help':
@@ -0,0 +1,130 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import _isString from '@webqit/util/js/isString.js';
6
+ import _isFunction from '@webqit/util/js/isFunction.js';
7
+ import _isArray from '@webqit/util/js/isArray.js';
8
+ import _arrFrom from '@webqit/util/arr/from.js';
9
+
10
+ /**
11
+ * ---------------------------
12
+ * The Router class
13
+ * ---------------------------
14
+ */
15
+
16
+ export default class Router {
17
+
18
+ /**
19
+ * Constructs a new Router instance
20
+ * over route definitions.
21
+ *
22
+ * @param string|array path
23
+ * @param object layout
24
+ * @param object context
25
+ *
26
+ * @return void
27
+ */
28
+ constructor(path, layout, context) {
29
+ this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
30
+ this.layout = layout;
31
+ this.context = context;
32
+ }
33
+
34
+ /**
35
+ * Performs dynamic routing.
36
+ *
37
+ * @param array|string method
38
+ * @param Object event
39
+ * @param any arg
40
+ * @param function _default
41
+ *
42
+ * @return object
43
+ */
44
+ async route(method, event, arg, _default) {
45
+
46
+ const $this = this;
47
+
48
+ // ----------------
49
+ // The loop
50
+ // ----------------
51
+ const next = async function(thisTick) {
52
+ const _this = { ...$this.context };
53
+ if (!thisTick.trail || thisTick.trail.length < thisTick.destination.length) {
54
+ thisTick = await $this.readTick(thisTick);
55
+ // -------------
56
+ _this.pathname = `/${thisTick.trail.join('/')}`;
57
+ _this.stepname = thisTick.trail[thisTick.trail.length - 1];
58
+ $this.finalizeHandlerContext(_this, thisTick);
59
+ // -------------
60
+ if (thisTick.exports) {
61
+ const methods = _arrFrom(thisTick.method);
62
+ const handler = _isFunction(thisTick.exports) && methods.includes('default') ? thisTick.exports : methods.reduce((_handler, name) => _handler || thisTick.exports[name.toLowerCase()], null);
63
+ if (handler) {
64
+ // -------------
65
+ // Dynamic response
66
+ // -------------
67
+ const _next = async (..._args) => {
68
+ const nextTick = { ...thisTick, arg: _args[0] };
69
+ if (_args.length > 1) {
70
+ var _url = _args[1], _request, requestInit = { ...(_args[2] || {}) };
71
+ if (_args[1] instanceof nextTick.event.Request) {
72
+ _request = _args[1];
73
+ _url = _request.url;
74
+ } else if (!_isString(_url)) {
75
+ throw new Error('Router redirect url must be a string!');
76
+ }
77
+ var newDestination = _url.startsWith('/') ? _url : $this.pathJoin(`/${thisTick.trail.join('/')}`, _url);
78
+ if (newDestination.startsWith('../')) {
79
+ throw new Error('Router redirect cannot traverse beyond the routing directory! (' + _url + ' >> ' + newDestination + ')');
80
+ }
81
+ if (requestInit.method) {
82
+ nextTick.method = requestInit.method;
83
+ if (_isArray(requestInit.method)) {
84
+ requestInit.method = requestInit.method[0];
85
+ }
86
+ }
87
+ if (_request) {
88
+ nextTick.event = thisTick.event.retarget(_request, { ...requestInit, _proxy: { url: newDestination/** non-standard but works */ } });
89
+ } else {
90
+ nextTick.event = thisTick.event.retarget(newDestination, requestInit);
91
+ }
92
+
93
+ nextTick.source = thisTick.destination.join('/');
94
+ nextTick.destination = newDestination.split('?').shift().split('/').map(a => a.trim()).filter(a => a);
95
+ nextTick.trail = _args[1].startsWith('/') ? [] : thisTick.trail.reduce((_commonRoot, _seg, i) => _commonRoot.length === i && _seg === nextTick.destination[i] ? _commonRoot.concat(_seg) : _commonRoot, []);
96
+ nextTick.trailOnFile = thisTick.trailOnFile.slice(0, nextTick.trail.length);
97
+ }
98
+ return next(nextTick);
99
+ };
100
+ // -------------
101
+ const nextPathname = thisTick.destination.slice(thisTick.trail.length);
102
+ _next.pathname = nextPathname.join('/');
103
+ _next.stepname = nextPathname[0];
104
+ // -------------
105
+ return await handler.call(_this, thisTick.event, thisTick.arg, _next/*next*/);
106
+ }
107
+ // Handler not found but exports found
108
+ return next(thisTick);
109
+ } else if ((thisTick.currentSegmentOnFile || {}).dirExists) {
110
+ // Exports not found but directory found
111
+ return next(thisTick);
112
+ }
113
+ }
114
+ // -------------
115
+ // Local file
116
+ // -------------
117
+ if (_default) {
118
+ return await _default.call(_this, thisTick.event, thisTick.arg);
119
+ }
120
+ };
121
+
122
+ return next({
123
+ destination: this.path,
124
+ event,
125
+ method,
126
+ arg,
127
+ });
128
+
129
+ }
130
+ };
@@ -0,0 +1,60 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { _isTypeObject, _isNumeric } from '@webqit/util/js/index.js';
6
+ import { _before } from '@webqit/util/str/index.js';
7
+ import { wwwFormSet, wwwFormPathSerializeCallback } from './util.js';
8
+
9
+ /**
10
+ * The _Headers Mixin
11
+ */
12
+ const _FormData = NativeFormData => class extends NativeFormData {
13
+
14
+ tee(callback = null) {
15
+ const formData1 = new this.constructor, formData2 = new this.constructor;
16
+ for (var [ name, value ] of this.entries()) {
17
+ const formDataType = formDataType(value);
18
+ if ((callback && callback(value, name, formDataType)) || (!callback && !formDataType)) {
19
+ formData1.append(name, value);
20
+ } else {
21
+ formData2.append(name, value);
22
+ }
23
+ }
24
+ return [ formData1, formData2 ];
25
+ }
26
+
27
+ json(data = {}, callback = null) {
28
+ if (arguments.length) {
29
+ Object.keys(data).forEach(key => {
30
+ wwwFormPathSerializeCallback(key, data[key], (_wwwFormPath, _value) => {
31
+ if (!callback || callback(_wwwFormPath, _value, _isTypeObject(_value))) {
32
+ this.append(_wwwFormPath, _value);
33
+ }
34
+ }, value => !formDataType(value));
35
+ });
36
+ return;
37
+ }
38
+ var jsonBuild; // We'll dynamically determine if this should be an array or an object
39
+ for (var [ name, value ] of this.entries()) {
40
+ if (!jsonBuild) {
41
+ jsonBuild = _isNumeric(_before(name, '[')) ? [] : {};
42
+ }
43
+ wwwFormSet(jsonBuild, name, value);
44
+ }
45
+ return jsonBuild;
46
+ }
47
+
48
+ }
49
+
50
+ export default _FormData;
51
+
52
+ export const formDataType = (value, list = null) => {
53
+ if (!_isTypeObject(value)) {
54
+ return;
55
+ }
56
+ const toStringTag = value[Symbol.toStringTag];
57
+ return (list || [
58
+ 'Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer', 'Blob', 'File', 'FormData', 'Stream'
59
+ ]).reduce((_toStringTag, type) => _toStringTag || (toStringTag === type ? type : null), null);
60
+ };