@venizia/ignis-worker 0.4.3 → 0.4.5
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/LICENSE.md +2 -5
- package/README.md +13 -0
- package/package.json +9 -15
- package/dist/cjs/applications/common/index.d.ts +0 -2
- package/dist/cjs/applications/common/index.d.ts.map +0 -1
- package/dist/cjs/applications/common/index.js +0 -18
- package/dist/cjs/applications/common/index.js.map +0 -1
- package/dist/cjs/applications/common/types.d.ts +0 -10
- package/dist/cjs/applications/common/types.d.ts.map +0 -1
- package/dist/cjs/applications/common/types.js +0 -3
- package/dist/cjs/applications/common/types.js.map +0 -1
- package/dist/cjs/applications/index.d.ts +0 -3
- package/dist/cjs/applications/index.d.ts.map +0 -1
- package/dist/cjs/applications/index.js +0 -19
- package/dist/cjs/applications/index.js.map +0 -1
- package/dist/cjs/applications/worker.d.ts +0 -79
- package/dist/cjs/applications/worker.d.ts.map +0 -1
- package/dist/cjs/applications/worker.js +0 -190
- package/dist/cjs/applications/worker.js.map +0 -1
- package/dist/cjs/envelope/common/index.d.ts +0 -2
- package/dist/cjs/envelope/common/index.d.ts.map +0 -1
- package/dist/cjs/envelope/common/index.js +0 -18
- package/dist/cjs/envelope/common/index.js.map +0 -1
- package/dist/cjs/envelope/common/types.d.ts +0 -37
- package/dist/cjs/envelope/common/types.d.ts.map +0 -1
- package/dist/cjs/envelope/common/types.js +0 -3
- package/dist/cjs/envelope/common/types.js.map +0 -1
- package/dist/cjs/envelope/encode.d.ts +0 -61
- package/dist/cjs/envelope/encode.d.ts.map +0 -1
- package/dist/cjs/envelope/encode.js +0 -116
- package/dist/cjs/envelope/encode.js.map +0 -1
- package/dist/cjs/envelope/index.d.ts +0 -3
- package/dist/cjs/envelope/index.d.ts.map +0 -1
- package/dist/cjs/envelope/index.js +0 -19
- package/dist/cjs/envelope/index.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -5
- package/dist/cjs/index.d.ts.map +0 -1
- package/dist/cjs/index.js +0 -21
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/transport/common/index.d.ts +0 -2
- package/dist/cjs/transport/common/index.d.ts.map +0 -1
- package/dist/cjs/transport/common/index.js +0 -18
- package/dist/cjs/transport/common/index.js.map +0 -1
- package/dist/cjs/transport/common/types.d.ts +0 -6
- package/dist/cjs/transport/common/types.d.ts.map +0 -1
- package/dist/cjs/transport/common/types.js +0 -3
- package/dist/cjs/transport/common/types.js.map +0 -1
- package/dist/cjs/transport/fetch-bridge.d.ts +0 -41
- package/dist/cjs/transport/fetch-bridge.d.ts.map +0 -1
- package/dist/cjs/transport/fetch-bridge.js +0 -104
- package/dist/cjs/transport/fetch-bridge.js.map +0 -1
- package/dist/cjs/transport/in-process.d.ts +0 -23
- package/dist/cjs/transport/in-process.d.ts.map +0 -1
- package/dist/cjs/transport/in-process.js +0 -47
- package/dist/cjs/transport/in-process.js.map +0 -1
- package/dist/cjs/transport/index.d.ts +0 -6
- package/dist/cjs/transport/index.d.ts.map +0 -1
- package/dist/cjs/transport/index.js +0 -22
- package/dist/cjs/transport/index.js.map +0 -1
- package/dist/cjs/transport/shared/common/constants.d.ts +0 -14
- package/dist/cjs/transport/shared/common/constants.d.ts.map +0 -1
- package/dist/cjs/transport/shared/common/constants.js +0 -19
- package/dist/cjs/transport/shared/common/constants.js.map +0 -1
- package/dist/cjs/transport/shared/common/index.d.ts +0 -3
- package/dist/cjs/transport/shared/common/index.d.ts.map +0 -1
- package/dist/cjs/transport/shared/common/index.js +0 -19
- package/dist/cjs/transport/shared/common/index.js.map +0 -1
- package/dist/cjs/transport/shared/common/types.d.ts +0 -35
- package/dist/cjs/transport/shared/common/types.d.ts.map +0 -1
- package/dist/cjs/transport/shared/common/types.js +0 -3
- package/dist/cjs/transport/shared/common/types.js.map +0 -1
- package/dist/cjs/transport/shared/index.d.ts +0 -4
- package/dist/cjs/transport/shared/index.d.ts.map +0 -1
- package/dist/cjs/transport/shared/index.js +0 -11
- package/dist/cjs/transport/shared/index.js.map +0 -1
- package/dist/cjs/transport/shared/transport.d.ts +0 -87
- package/dist/cjs/transport/shared/transport.d.ts.map +0 -1
- package/dist/cjs/transport/shared/transport.js +0 -384
- package/dist/cjs/transport/shared/transport.js.map +0 -1
- package/dist/cjs/transport/worker.d.ts +0 -62
- package/dist/cjs/transport/worker.d.ts.map +0 -1
- package/dist/cjs/transport/worker.js +0 -224
- package/dist/cjs/transport/worker.js.map +0 -1
package/LICENSE.md
CHANGED
|
@@ -4,7 +4,7 @@ Ignis is a TypeScript Server Infrastructure framework built on [Hono](https://ho
|
|
|
4
4
|
|
|
5
5
|
## MIT License
|
|
6
6
|
|
|
7
|
-
Copyright (c) 2025
|
|
7
|
+
Copyright (c) 2025 Venizia Co., Ltd.
|
|
8
8
|
|
|
9
9
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
10
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -88,12 +88,9 @@ Ignis uses the following open-source libraries. We are grateful to the authors a
|
|
|
88
88
|
|
|
89
89
|
## Acknowledgments
|
|
90
90
|
|
|
91
|
-
Ignis Framework is
|
|
91
|
+
Ignis Framework is built on the work of open-source projects:
|
|
92
92
|
|
|
93
93
|
- **[Hono](https://hono.dev/)** by Yusuke Wada - The ultrafast web framework that powers Ignis
|
|
94
|
-
- **[LoopBack 4](https://loopback.io/)** by IBM/StrongLoop - Inspiration for the enterprise architecture patterns
|
|
95
|
-
- **[NestJS](https://nestjs.com/)** by Kamil Mysliwiec - Inspiration for decorator-based controllers
|
|
96
|
-
- **[InversifyJS](https://inversify.io/)** - Inspiration for the IoC container design
|
|
97
94
|
|
|
98
95
|
We thank all the maintainers and contributors of these projects for their excellent work.
|
|
99
96
|
|
package/README.md
CHANGED
|
@@ -131,3 +131,16 @@ browser sibling of `@venizia/ignis`; neither depends on the other, and it does n
|
|
|
131
131
|
- [All changelogs](https://ignis.venizia.ai/changelogs/)
|
|
132
132
|
|
|
133
133
|
MIT licensed - see [LICENSE.md](https://github.com/VENIZIA-AI/ignis/blob/main/LICENSE.md).
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
<p align="center">
|
|
138
|
+
<a href="https://venizia.ai">
|
|
139
|
+
<picture>
|
|
140
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://venizia.ai/assets/logo-white.svg">
|
|
141
|
+
<img src="https://venizia.ai/assets/logo.svg" alt="VENIZIA" height="28">
|
|
142
|
+
</picture>
|
|
143
|
+
</a>
|
|
144
|
+
<br>
|
|
145
|
+
<sub>@venizia/ignis-worker is part of IGNIS, built and maintained by <a href="https://venizia.ai">VENIZIA</a>.</sub>
|
|
146
|
+
</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@venizia/ignis-worker",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Browser Web Worker host for the IGNIS framework: the request/response envelope, the transport contract, and WorkerApplication - the layer that listens on `onmessage` instead of a socket, so a RestApplication can serve its controllers with no server anywhere.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"browser",
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
"envelope",
|
|
9
9
|
"hono",
|
|
10
10
|
"ignis",
|
|
11
|
-
"loopback",
|
|
12
|
-
"loopback4",
|
|
13
11
|
"rest",
|
|
14
12
|
"transport",
|
|
15
13
|
"typescript",
|
|
@@ -39,18 +37,14 @@
|
|
|
39
37
|
"!**/*.tsbuildinfo"
|
|
40
38
|
],
|
|
41
39
|
"sideEffects": [
|
|
42
|
-
"./dist/cjs/index.js",
|
|
43
40
|
"./dist/esm/index.js"
|
|
44
41
|
],
|
|
45
|
-
"main": "./dist/
|
|
46
|
-
"
|
|
47
|
-
"types": "./dist/cjs/index.d.ts",
|
|
42
|
+
"main": "./dist/esm/index.js",
|
|
43
|
+
"types": "./dist/esm/index.d.ts",
|
|
48
44
|
"exports": {
|
|
49
45
|
".": {
|
|
50
|
-
"types": "./dist/
|
|
51
|
-
"
|
|
52
|
-
"require": "./dist/cjs/index.js",
|
|
53
|
-
"default": "./dist/cjs/index.js"
|
|
46
|
+
"types": "./dist/esm/index.d.ts",
|
|
47
|
+
"default": "./dist/esm/index.js"
|
|
54
48
|
},
|
|
55
49
|
"./package.json": "./package.json"
|
|
56
50
|
},
|
|
@@ -62,14 +56,14 @@
|
|
|
62
56
|
"prepublishOnly": "make rebuild"
|
|
63
57
|
},
|
|
64
58
|
"dependencies": {
|
|
65
|
-
"@venizia/ignis-helpers": "0.4.
|
|
66
|
-
"@venizia/ignis-inversion": "0.4.
|
|
67
|
-
"@venizia/ignis-kernel": "0.4.
|
|
59
|
+
"@venizia/ignis-helpers": "0.4.5",
|
|
60
|
+
"@venizia/ignis-inversion": "0.4.4",
|
|
61
|
+
"@venizia/ignis-kernel": "0.4.5",
|
|
68
62
|
"reflect-metadata": "^0.2.2"
|
|
69
63
|
},
|
|
70
64
|
"devDependencies": {
|
|
71
65
|
"@types/bun": "^1.4.3",
|
|
72
|
-
"@venizia/dev-configs": "0.4.
|
|
66
|
+
"@venizia/dev-configs": "0.4.5",
|
|
73
67
|
"eslint": "^10.13.0",
|
|
74
68
|
"oxlint": "1.89.0",
|
|
75
69
|
"oxlint-tsgolint": "7.0.2004",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/applications/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
|
|
@@ -1,18 +0,0 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./types"), exports);
|
|
18
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/applications/common/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB","sourcesContent":["export * from './types';\n"]}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { IApplicationConfigs } from '@venizia/ignis-kernel';
|
|
2
|
-
/**
|
|
3
|
-
* What a Web Worker host configures on top of the kernel's shape. `host`/`port` stay off this
|
|
4
|
-
* interface on purpose - `@venizia/ignis`'s `IServerApplicationConfigs` is the one with a socket.
|
|
5
|
-
*/
|
|
6
|
-
export interface IWorkerApplicationConfigs extends IApplicationConfigs {
|
|
7
|
-
/** Bound as `CoreBindings.APPLICATION_PROJECT_ROOT` when `getProjectRoot()` resolves it, and shared with `ModuleUtility`; default is the host's own working directory when one exists, else `''`. */
|
|
8
|
-
projectRoot?: string;
|
|
9
|
-
}
|
|
10
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/applications/common/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE,qMAAqM;IACrM,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/applications/common/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { IApplicationConfigs } from '@venizia/ignis-kernel';\n\n/**\n * What a Web Worker host configures on top of the kernel's shape. `host`/`port` stay off this\n * interface on purpose - `@venizia/ignis`'s `IServerApplicationConfigs` is the one with a socket.\n */\nexport interface IWorkerApplicationConfigs extends IApplicationConfigs {\n /** Bound as `CoreBindings.APPLICATION_PROJECT_ROOT` when `getProjectRoot()` resolves it, and shared with `ModuleUtility`; default is the host's own working directory when one exists, else `''`. */\n projectRoot?: string;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/applications/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC"}
|
|
@@ -1,19 +0,0 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./common"), exports);
|
|
18
|
-
__exportStar(require("./worker"), exports);
|
|
19
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/applications/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2CAAyB","sourcesContent":["export * from './common';\nexport * from './worker';\n"]}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { RestApplication } from '@venizia/ignis-kernel';
|
|
2
|
-
import type { Env, Schema } from 'hono';
|
|
3
|
-
import type { IWorkerApplicationConfigs } from './common';
|
|
4
|
-
/**
|
|
5
|
-
* Structural shape of the two globals `listen()` can attach to. Never `DedicatedWorkerGlobalScope`:
|
|
6
|
-
* naming it emits it into the published `.d.ts` and forces every consumer to compile with
|
|
7
|
-
* `lib.webworker`. `start` is optional because a `MessagePort`s queue starts disabled and only
|
|
8
|
-
* `start()` enables it, while a worker global scope has no such method at all.
|
|
9
|
-
*/
|
|
10
|
-
export interface IWorkerMessageScope {
|
|
11
|
-
postMessage(message: unknown, transfer?: Array<ArrayBuffer | MessagePort>): void;
|
|
12
|
-
addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
13
|
-
removeEventListener?(type: 'message', listener: (event: MessageEvent) => void): void;
|
|
14
|
-
start?(): void;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Runs an IGNIS application inside a browser Web Worker: `listen()` replaces what
|
|
18
|
-
* `ServerApplication.start()` does with a socket, attaching `onmessage` instead. Gets REST
|
|
19
|
-
* controller registration by inheritance - `RestApplication.registerControllers()` - the whole
|
|
20
|
-
* reason the DI helpers and `RestComponent` sit in the kernel.
|
|
21
|
-
*/
|
|
22
|
-
export declare abstract class WorkerApplication<AppEnv extends Env = Env, AppSchema extends Schema = {}, BasePath extends string = '/'> extends RestApplication<AppEnv, AppSchema, BasePath> {
|
|
23
|
-
/** Widened by this layer, the only one with an optional `projectRoot` to read. Assigned by the base constructor - `declare` so no field initializer runs here and overwrites it with `undefined`. */
|
|
24
|
-
protected configs: IWorkerApplicationConfigs;
|
|
25
|
-
/** Resolves once the router tree is mounted - what an envelope arriving during boot waits on. */
|
|
26
|
-
private servingPromise?;
|
|
27
|
-
/** The whole `listen()` call, kept so a second one is a no-op instead of a second listener. */
|
|
28
|
-
private listenPromise?;
|
|
29
|
-
private detachFromScope?;
|
|
30
|
-
private isStopped;
|
|
31
|
-
/**
|
|
32
|
-
* `configs.projectRoot` wins, else the host's own working directory - read as
|
|
33
|
-
* `globalThis.process?.cwd?.()`, never a bare `process.cwd()`, because a real browser Worker has no
|
|
34
|
-
* such global and this class ships there. Bound AND shared with `ModuleUtility` (through the
|
|
35
|
-
* purity-safe `ProjectRootRegistry`), the same seam `ServerApplication` gives a socket host. No
|
|
36
|
-
* `node_modules` check here, unlike that host: a browser has no filesystem to check, and `node:fs`
|
|
37
|
-
* would bundle a Node builtin straight into a Worker.
|
|
38
|
-
*/
|
|
39
|
-
getProjectRoot(): string;
|
|
40
|
-
/**
|
|
41
|
-
* Every failure here is posted back as an `IBffErrorEnvelope`, the failed `postMessage` included:
|
|
42
|
-
* an exception nobody posts leaves the UI waiting out the transport timeout.
|
|
43
|
-
*/
|
|
44
|
-
private handleMessage;
|
|
45
|
-
/** No transfer list: an error envelope carries no `ArrayBuffer` body, and this is already the fallback path - nothing left to hand off if this post fails too. */
|
|
46
|
-
private postErrorEnvelope;
|
|
47
|
-
/** Everything `ServerApplication.start()` does short of binding a socket. Once this resolves the application answers requests. */
|
|
48
|
-
private startServing;
|
|
49
|
-
/**
|
|
50
|
-
* Holds an envelope until the application is serving. A dedicated worker's queue is enabled when
|
|
51
|
-
* its module script is evaluated, so anything posted during boot - PGlite takes ~350ms - reaches
|
|
52
|
-
* this listener and would otherwise be dropped. One shared promise also hands them on in arrival
|
|
53
|
-
* order.
|
|
54
|
-
*/
|
|
55
|
-
private dispatchMessage;
|
|
56
|
-
private attachTo;
|
|
57
|
-
/**
|
|
58
|
-
* `ServerApplication.start()` minus the socket. `opts.scope` defaults to `globalThis`, never a
|
|
59
|
-
* bare `self`, which would tie the published types to `lib.webworker`. Idempotent: without the
|
|
60
|
-
* guard one envelope runs its handler once per call - a POST inserting two rows - and Vite HMR
|
|
61
|
-
* re-evaluating the worker module is the everyday trigger. A boot failure keeps the listener
|
|
62
|
-
* attached so every request hears the failure instead of waiting out the transport timeout.
|
|
63
|
-
*/
|
|
64
|
-
listen(opts?: {
|
|
65
|
-
scope?: MessagePort | IWorkerMessageScope;
|
|
66
|
-
}): Promise<void>;
|
|
67
|
-
/**
|
|
68
|
-
* The counterpart to `listen()`, and to `ServerApplication.stop()`: runs the post-stop hooks, then
|
|
69
|
-
* closes every datasource the boot configured. Without it the OPFS exclusive lock a PGlite holds
|
|
70
|
-
* is never released - the next Worker then fails with `NoModificationAllowedError` until a full
|
|
71
|
-
* page reload. The Vite recipe is `import.meta.hot?.dispose(() => application.stop())`.
|
|
72
|
-
*
|
|
73
|
-
* Detaches BEFORE draining, unlike the server: here the listener IS the socket, and a request
|
|
74
|
-
* admitted while a datasource is closing has nothing left to run against. A stopped application
|
|
75
|
-
* is not restartable - build a new one.
|
|
76
|
-
*/
|
|
77
|
-
stop(): Promise<void>;
|
|
78
|
-
}
|
|
79
|
-
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../../src/applications/worker.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGxC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,IAAI,CAAC;IACjF,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;IACjF,mBAAmB,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;IACrF,KAAK,CAAC,IAAI,IAAI,CAAC;CAChB;AAED;;;;;GAKG;AACH,8BAAsB,iBAAiB,CACrC,MAAM,SAAS,GAAG,GAAG,GAAG,EACxB,SAAS,SAAS,MAAM,GAAG,EAAE,EAC7B,QAAQ,SAAS,MAAM,GAAG,GAAG,CAC7B,SAAQ,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;IACpD,qMAAqM;IACrM,UAAkB,OAAO,EAAE,yBAAyB,CAAC;IAErD,iGAAiG;IACjG,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,+FAA+F;IAC/F,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,eAAe,CAAC,CAAa;IACrC,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;;;;OAOG;IACM,cAAc,IAAI,MAAM;IASjC;;;OAGG;YACW,aAAa;IAoC3B,kKAAkK;IAClK,OAAO,CAAC,iBAAiB;IAgBzB,kIAAkI;YACpH,YAAY;IAW1B;;;;;OAKG;YACW,eAAe;IAqC7B,OAAO,CAAC,QAAQ;IAsChB;;;;;;OAMG;IACG,MAAM,CAAC,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,WAAW,GAAG,mBAAmB,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBjF;;;;;;;;;OASG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAW5B"}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WorkerApplication = void 0;
|
|
4
|
-
const core_1 = require("@venizia/ignis-helpers/core");
|
|
5
|
-
const common_1 = require("@venizia/ignis-helpers/common");
|
|
6
|
-
const ignis_kernel_1 = require("@venizia/ignis-kernel");
|
|
7
|
-
const encode_1 = require("../envelope/encode");
|
|
8
|
-
/**
|
|
9
|
-
* Runs an IGNIS application inside a browser Web Worker: `listen()` replaces what
|
|
10
|
-
* `ServerApplication.start()` does with a socket, attaching `onmessage` instead. Gets REST
|
|
11
|
-
* controller registration by inheritance - `RestApplication.registerControllers()` - the whole
|
|
12
|
-
* reason the DI helpers and `RestComponent` sit in the kernel.
|
|
13
|
-
*/
|
|
14
|
-
class WorkerApplication extends ignis_kernel_1.RestApplication {
|
|
15
|
-
constructor() {
|
|
16
|
-
super(...arguments);
|
|
17
|
-
this.isStopped = false;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* `configs.projectRoot` wins, else the host's own working directory - read as
|
|
21
|
-
* `globalThis.process?.cwd?.()`, never a bare `process.cwd()`, because a real browser Worker has no
|
|
22
|
-
* such global and this class ships there. Bound AND shared with `ModuleUtility` (through the
|
|
23
|
-
* purity-safe `ProjectRootRegistry`), the same seam `ServerApplication` gives a socket host. No
|
|
24
|
-
* `node_modules` check here, unlike that host: a browser has no filesystem to check, and `node:fs`
|
|
25
|
-
* would bundle a Node builtin straight into a Worker.
|
|
26
|
-
*/
|
|
27
|
-
getProjectRoot() {
|
|
28
|
-
const projectRoot = this.configs.projectRoot ?? globalThis.process?.cwd?.() ?? '';
|
|
29
|
-
this.bind({ key: ignis_kernel_1.CoreBindings.APPLICATION_PROJECT_ROOT }).toValue(projectRoot);
|
|
30
|
-
core_1.ProjectRootRegistry.set({ projectRoot });
|
|
31
|
-
return projectRoot;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Every failure here is posted back as an `IBffErrorEnvelope`, the failed `postMessage` included:
|
|
35
|
-
* an exception nobody posts leaves the UI waiting out the transport timeout.
|
|
36
|
-
*/
|
|
37
|
-
async handleMessage(opts) {
|
|
38
|
-
const { scope, envelope } = opts;
|
|
39
|
-
const logger = this.logger.for(this.handleMessage.name);
|
|
40
|
-
let responseEnvelope;
|
|
41
|
-
let failure;
|
|
42
|
-
try {
|
|
43
|
-
const request = encode_1.BffEnvelope.decodeRequest({ envelope });
|
|
44
|
-
const response = await this.getServer().fetch(request);
|
|
45
|
-
responseEnvelope = await encode_1.BffEnvelope.encodeResponse({ response, id: envelope.id });
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
logger.error('Failed to handle worker message | id: %s | error: %s', envelope.id, error);
|
|
49
|
-
failure = error;
|
|
50
|
-
}
|
|
51
|
-
if (responseEnvelope) {
|
|
52
|
-
try {
|
|
53
|
-
scope.postMessage(responseEnvelope, responseEnvelope.body ? [responseEnvelope.body] : []);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
catch (postError) {
|
|
57
|
-
logger.error('Failed to post response back to scope, falling back to an error envelope | id: %s | error: %s', envelope.id, postError);
|
|
58
|
-
failure = postError;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
this.postErrorEnvelope({ scope, id: envelope.id, error: failure });
|
|
62
|
-
}
|
|
63
|
-
/** No transfer list: an error envelope carries no `ArrayBuffer` body, and this is already the fallback path - nothing left to hand off if this post fails too. */
|
|
64
|
-
postErrorEnvelope(opts) {
|
|
65
|
-
const { scope, id, error } = opts;
|
|
66
|
-
try {
|
|
67
|
-
scope.postMessage(encode_1.BffEnvelope.encodeError({ id, error }));
|
|
68
|
-
}
|
|
69
|
-
catch (postError) {
|
|
70
|
-
this.logger
|
|
71
|
-
.for(this.postErrorEnvelope.name)
|
|
72
|
-
.error('Failed to post error envelope back to scope | id: %s | error: %s', id, postError);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/** Everything `ServerApplication.start()` does short of binding a socket. Once this resolves the application answers requests. */
|
|
76
|
-
async startServing() {
|
|
77
|
-
// The stack itself is `RestApplication.registerDefaultMiddlewares()` - a BFF must answer with
|
|
78
|
-
// the framework's envelope exactly as the server does, so it adds nothing and overrides nothing.
|
|
79
|
-
await this.registerDefaultMiddlewares();
|
|
80
|
-
await this.initialize();
|
|
81
|
-
await this.setupMiddlewares();
|
|
82
|
-
this.getServer().route(this.configs.path.base, this.rootRouter);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Holds an envelope until the application is serving. A dedicated worker's queue is enabled when
|
|
86
|
-
* its module script is evaluated, so anything posted during boot - PGlite takes ~350ms - reaches
|
|
87
|
-
* this listener and would otherwise be dropped. One shared promise also hands them on in arrival
|
|
88
|
-
* order.
|
|
89
|
-
*/
|
|
90
|
-
async dispatchMessage(opts) {
|
|
91
|
-
const { scope, envelope } = opts;
|
|
92
|
-
const logger = this.logger.for(this.dispatchMessage.name);
|
|
93
|
-
try {
|
|
94
|
-
await this.servingPromise;
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
logger.error('The application failed to start, answering with an error envelope | id: %s | error: %s', envelope.id, error);
|
|
98
|
-
this.postErrorEnvelope({ scope, id: envelope.id, error });
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
// Answered, not dropped: a request admitted before `stop()` and still waiting on the boot has
|
|
102
|
-
// a caller holding a promise, and silence there is the timeout this whole path exists to avoid.
|
|
103
|
-
if (this.isStopped) {
|
|
104
|
-
logger.warn('Refusing a request that arrived after stop() | id: %s', envelope.id);
|
|
105
|
-
this.postErrorEnvelope({
|
|
106
|
-
scope,
|
|
107
|
-
id: envelope.id,
|
|
108
|
-
error: (0, core_1.getError)({
|
|
109
|
-
statusCode: common_1.HTTP.ResultCodes.RS_5.ServiceUnavailable,
|
|
110
|
-
message: `[WorkerApplication] The application was stopped before this request could run | id: ${envelope.id}`,
|
|
111
|
-
}),
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
await this.handleMessage({ scope, envelope });
|
|
116
|
-
}
|
|
117
|
-
attachTo(opts) {
|
|
118
|
-
const { scope } = opts;
|
|
119
|
-
const logger = this.logger.for(this.attachTo.name);
|
|
120
|
-
// Typed as the broader `Event`, not `MessageEvent`: Node's ambient `worker_threads.MessagePort`
|
|
121
|
-
// (the fallback this package's DOM-less tsconfig resolves `MessagePort` to) declares
|
|
122
|
-
// `addEventListener` over `Event | MessageEvent`, and a handler narrower than that is not
|
|
123
|
-
// assignable to it.
|
|
124
|
-
const listener = (event) => {
|
|
125
|
-
const envelope = event.data;
|
|
126
|
-
// A worker scope receives every `postMessage` sent to it, not only ours - a dev-server ping,
|
|
127
|
-
// a devtools/extension probe, a library's own init handshake. None of those carry an `id`;
|
|
128
|
-
// silently dropping them (rather than dereferencing `envelope.id` inside `handleMessage`'s
|
|
129
|
-
// own catch block) is what keeps a foreign message from throwing a bare `TypeError` that
|
|
130
|
-
// would otherwise escape this listener entirely, unlogged and unposted.
|
|
131
|
-
if (!envelope?.id) {
|
|
132
|
-
logger.debug('Ignoring worker message with no request id | data: %j', envelope);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
// `dispatchMessage` never rejects - every failure inside it, including a failed post, is
|
|
136
|
-
// caught and handled internally. This `.catch` only guards the sliver of code before its own
|
|
137
|
-
// try block ever runs.
|
|
138
|
-
this.dispatchMessage({ scope, envelope }).catch((error) => {
|
|
139
|
-
logger.error('Unexpected failure handling worker message | error: %s', error);
|
|
140
|
-
});
|
|
141
|
-
};
|
|
142
|
-
scope.addEventListener('message', listener);
|
|
143
|
-
scope.start?.();
|
|
144
|
-
this.detachFromScope = () => {
|
|
145
|
-
scope.removeEventListener?.('message', listener);
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* `ServerApplication.start()` minus the socket. `opts.scope` defaults to `globalThis`, never a
|
|
150
|
-
* bare `self`, which would tie the published types to `lib.webworker`. Idempotent: without the
|
|
151
|
-
* guard one envelope runs its handler once per call - a POST inserting two rows - and Vite HMR
|
|
152
|
-
* re-evaluating the worker module is the everyday trigger. A boot failure keeps the listener
|
|
153
|
-
* attached so every request hears the failure instead of waiting out the transport timeout.
|
|
154
|
-
*/
|
|
155
|
-
async listen(opts) {
|
|
156
|
-
if (this.listenPromise) {
|
|
157
|
-
this.logger
|
|
158
|
-
.for(this.listen.name)
|
|
159
|
-
.warn('Already listening - ignoring this call | one scope, one listener');
|
|
160
|
-
return this.listenPromise;
|
|
161
|
-
}
|
|
162
|
-
// Everything down to the first `await` runs synchronously with the caller, which is what makes
|
|
163
|
-
// the listener observable to anything posted in the same turn.
|
|
164
|
-
const scope = opts?.scope ?? globalThis;
|
|
165
|
-
this.attachTo({ scope });
|
|
166
|
-
this.servingPromise = this.startServing();
|
|
167
|
-
this.listenPromise = this.servingPromise.then(() => this.executePostStartHooks());
|
|
168
|
-
return this.listenPromise;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* The counterpart to `listen()`, and to `ServerApplication.stop()`: runs the post-stop hooks, then
|
|
172
|
-
* closes every datasource the boot configured. Without it the OPFS exclusive lock a PGlite holds
|
|
173
|
-
* is never released - the next Worker then fails with `NoModificationAllowedError` until a full
|
|
174
|
-
* page reload. The Vite recipe is `import.meta.hot?.dispose(() => application.stop())`.
|
|
175
|
-
*
|
|
176
|
-
* Detaches BEFORE draining, unlike the server: here the listener IS the socket, and a request
|
|
177
|
-
* admitted while a datasource is closing has nothing left to run against. A stopped application
|
|
178
|
-
* is not restartable - build a new one.
|
|
179
|
-
*/
|
|
180
|
-
async stop() {
|
|
181
|
-
this.isStopped = true;
|
|
182
|
-
this.detachFromScope?.();
|
|
183
|
-
this.detachFromScope = undefined;
|
|
184
|
-
await this.executePostStopHooks();
|
|
185
|
-
await this.closeDataSources();
|
|
186
|
-
this.logger.for(this.stop.name).info('Worker application STOPPED');
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
exports.WorkerApplication = WorkerApplication;
|
|
190
|
-
//# sourceMappingURL=worker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../../src/applications/worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { getError, ProjectRootRegistry } from '@venizia/ignis-helpers/core';\nimport { HTTP } from '@venizia/ignis-helpers/common';\nimport { CoreBindings, RestApplication } from '@venizia/ignis-kernel';\nimport type { Env, Schema } from 'hono';\nimport { BffEnvelope } from '@/envelope/encode';\nimport type { IBffRequestEnvelope, IBffResponseEnvelope } from '@/envelope/common';\nimport type { IWorkerApplicationConfigs } from './common';\n\n/**\n * Structural shape of the two globals `listen()` can attach to. Never `DedicatedWorkerGlobalScope`:\n * naming it emits it into the published `.d.ts` and forces every consumer to compile with\n * `lib.webworker`. `start` is optional because a `MessagePort`s queue starts disabled and only\n * `start()` enables it, while a worker global scope has no such method at all.\n */\nexport interface IWorkerMessageScope {\n postMessage(message: unknown, transfer?: Array<ArrayBuffer | MessagePort>): void;\n addEventListener(type: 'message', listener: (event: MessageEvent) => void): void;\n removeEventListener?(type: 'message', listener: (event: MessageEvent) => void): void;\n start?(): void;\n}\n\n/**\n * Runs an IGNIS application inside a browser Web Worker: `listen()` replaces what\n * `ServerApplication.start()` does with a socket, attaching `onmessage` instead. Gets REST\n * controller registration by inheritance - `RestApplication.registerControllers()` - the whole\n * reason the DI helpers and `RestComponent` sit in the kernel.\n */\nexport abstract class WorkerApplication<\n AppEnv extends Env = Env,\n AppSchema extends Schema = {},\n BasePath extends string = '/',\n> extends RestApplication<AppEnv, AppSchema, BasePath> {\n /** Widened by this layer, the only one with an optional `projectRoot` to read. Assigned by the base constructor - `declare` so no field initializer runs here and overwrites it with `undefined`. */\n declare protected configs: IWorkerApplicationConfigs;\n\n /** Resolves once the router tree is mounted - what an envelope arriving during boot waits on. */\n private servingPromise?: Promise<void>;\n /** The whole `listen()` call, kept so a second one is a no-op instead of a second listener. */\n private listenPromise?: Promise<void>;\n private detachFromScope?: () => void;\n private isStopped = false;\n\n /**\n * `configs.projectRoot` wins, else the host's own working directory - read as\n * `globalThis.process?.cwd?.()`, never a bare `process.cwd()`, because a real browser Worker has no\n * such global and this class ships there. Bound AND shared with `ModuleUtility` (through the\n * purity-safe `ProjectRootRegistry`), the same seam `ServerApplication` gives a socket host. No\n * `node_modules` check here, unlike that host: a browser has no filesystem to check, and `node:fs`\n * would bundle a Node builtin straight into a Worker.\n */\n override getProjectRoot(): string {\n const projectRoot = this.configs.projectRoot ?? globalThis.process?.cwd?.() ?? '';\n\n this.bind<string>({ key: CoreBindings.APPLICATION_PROJECT_ROOT }).toValue(projectRoot);\n ProjectRootRegistry.set({ projectRoot });\n\n return projectRoot;\n }\n\n /**\n * Every failure here is posted back as an `IBffErrorEnvelope`, the failed `postMessage` included:\n * an exception nobody posts leaves the UI waiting out the transport timeout.\n */\n private async handleMessage(opts: {\n scope: MessagePort | IWorkerMessageScope;\n envelope: IBffRequestEnvelope;\n }): Promise<void> {\n const { scope, envelope } = opts;\n const logger = this.logger.for(this.handleMessage.name);\n\n let responseEnvelope: IBffResponseEnvelope | undefined;\n let failure: unknown;\n\n try {\n const request = BffEnvelope.decodeRequest({ envelope });\n const response = await this.getServer().fetch(request);\n responseEnvelope = await BffEnvelope.encodeResponse({ response, id: envelope.id });\n } catch (error) {\n logger.error('Failed to handle worker message | id: %s | error: %s', envelope.id, error);\n failure = error;\n }\n\n if (responseEnvelope) {\n try {\n scope.postMessage(responseEnvelope, responseEnvelope.body ? [responseEnvelope.body] : []);\n return;\n } catch (postError) {\n logger.error(\n 'Failed to post response back to scope, falling back to an error envelope | id: %s | error: %s',\n envelope.id,\n postError,\n );\n failure = postError;\n }\n }\n\n this.postErrorEnvelope({ scope, id: envelope.id, error: failure });\n }\n\n /** No transfer list: an error envelope carries no `ArrayBuffer` body, and this is already the fallback path - nothing left to hand off if this post fails too. */\n private postErrorEnvelope(opts: {\n scope: MessagePort | IWorkerMessageScope;\n id: string;\n error: unknown;\n }): void {\n const { scope, id, error } = opts;\n\n try {\n scope.postMessage(BffEnvelope.encodeError({ id, error }));\n } catch (postError) {\n this.logger\n .for(this.postErrorEnvelope.name)\n .error('Failed to post error envelope back to scope | id: %s | error: %s', id, postError);\n }\n }\n\n /** Everything `ServerApplication.start()` does short of binding a socket. Once this resolves the application answers requests. */\n private async startServing(): Promise<void> {\n // The stack itself is `RestApplication.registerDefaultMiddlewares()` - a BFF must answer with\n // the framework's envelope exactly as the server does, so it adds nothing and overrides nothing.\n await this.registerDefaultMiddlewares();\n\n await this.initialize();\n await this.setupMiddlewares();\n\n this.getServer().route(this.configs.path.base, this.rootRouter);\n }\n\n /**\n * Holds an envelope until the application is serving. A dedicated worker's queue is enabled when\n * its module script is evaluated, so anything posted during boot - PGlite takes ~350ms - reaches\n * this listener and would otherwise be dropped. One shared promise also hands them on in arrival\n * order.\n */\n private async dispatchMessage(opts: {\n scope: MessagePort | IWorkerMessageScope;\n envelope: IBffRequestEnvelope;\n }): Promise<void> {\n const { scope, envelope } = opts;\n const logger = this.logger.for(this.dispatchMessage.name);\n\n try {\n await this.servingPromise;\n } catch (error) {\n logger.error(\n 'The application failed to start, answering with an error envelope | id: %s | error: %s',\n envelope.id,\n error,\n );\n this.postErrorEnvelope({ scope, id: envelope.id, error });\n return;\n }\n\n // Answered, not dropped: a request admitted before `stop()` and still waiting on the boot has\n // a caller holding a promise, and silence there is the timeout this whole path exists to avoid.\n if (this.isStopped) {\n logger.warn('Refusing a request that arrived after stop() | id: %s', envelope.id);\n this.postErrorEnvelope({\n scope,\n id: envelope.id,\n error: getError({\n statusCode: HTTP.ResultCodes.RS_5.ServiceUnavailable,\n message: `[WorkerApplication] The application was stopped before this request could run | id: ${envelope.id}`,\n }),\n });\n return;\n }\n\n await this.handleMessage({ scope, envelope });\n }\n\n private attachTo(opts: { scope: MessagePort | IWorkerMessageScope }): void {\n const { scope } = opts;\n const logger = this.logger.for(this.attachTo.name);\n\n // Typed as the broader `Event`, not `MessageEvent`: Node's ambient `worker_threads.MessagePort`\n // (the fallback this package's DOM-less tsconfig resolves `MessagePort` to) declares\n // `addEventListener` over `Event | MessageEvent`, and a handler narrower than that is not\n // assignable to it.\n const listener = (event: Event): void => {\n const envelope = (event as MessageEvent).data as IBffRequestEnvelope | undefined;\n\n // A worker scope receives every `postMessage` sent to it, not only ours - a dev-server ping,\n // a devtools/extension probe, a library's own init handshake. None of those carry an `id`;\n // silently dropping them (rather than dereferencing `envelope.id` inside `handleMessage`'s\n // own catch block) is what keeps a foreign message from throwing a bare `TypeError` that\n // would otherwise escape this listener entirely, unlogged and unposted.\n if (!envelope?.id) {\n logger.debug('Ignoring worker message with no request id | data: %j', envelope);\n return;\n }\n\n // `dispatchMessage` never rejects - every failure inside it, including a failed post, is\n // caught and handled internally. This `.catch` only guards the sliver of code before its own\n // try block ever runs.\n this.dispatchMessage({ scope, envelope }).catch((error) => {\n logger.error('Unexpected failure handling worker message | error: %s', error);\n });\n };\n\n scope.addEventListener('message', listener);\n\n scope.start?.();\n\n this.detachFromScope = () => {\n scope.removeEventListener?.('message', listener);\n };\n }\n\n /**\n * `ServerApplication.start()` minus the socket. `opts.scope` defaults to `globalThis`, never a\n * bare `self`, which would tie the published types to `lib.webworker`. Idempotent: without the\n * guard one envelope runs its handler once per call - a POST inserting two rows - and Vite HMR\n * re-evaluating the worker module is the everyday trigger. A boot failure keeps the listener\n * attached so every request hears the failure instead of waiting out the transport timeout.\n */\n async listen(opts?: { scope?: MessagePort | IWorkerMessageScope }): Promise<void> {\n if (this.listenPromise) {\n this.logger\n .for(this.listen.name)\n .warn('Already listening - ignoring this call | one scope, one listener');\n return this.listenPromise;\n }\n\n // Everything down to the first `await` runs synchronously with the caller, which is what makes\n // the listener observable to anything posted in the same turn.\n const scope: MessagePort | IWorkerMessageScope =\n opts?.scope ?? (globalThis as IWorkerMessageScope);\n\n this.attachTo({ scope });\n\n this.servingPromise = this.startServing();\n this.listenPromise = this.servingPromise.then(() => this.executePostStartHooks());\n\n return this.listenPromise;\n }\n\n /**\n * The counterpart to `listen()`, and to `ServerApplication.stop()`: runs the post-stop hooks, then\n * closes every datasource the boot configured. Without it the OPFS exclusive lock a PGlite holds\n * is never released - the next Worker then fails with `NoModificationAllowedError` until a full\n * page reload. The Vite recipe is `import.meta.hot?.dispose(() => application.stop())`.\n *\n * Detaches BEFORE draining, unlike the server: here the listener IS the socket, and a request\n * admitted while a datasource is closing has nothing left to run against. A stopped application\n * is not restartable - build a new one.\n */\n async stop(): Promise<void> {\n this.isStopped = true;\n\n this.detachFromScope?.();\n this.detachFromScope = undefined;\n\n await this.executePostStopHooks();\n await this.closeDataSources();\n\n this.logger.for(this.stop.name).info('Worker application STOPPED');\n }\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/envelope/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC"}
|
|
@@ -1,18 +0,0 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./types"), exports);
|
|
18
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/envelope/common/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB","sourcesContent":["export * from './types';\n"]}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/** Headers are carried as tuples: a `Headers` instance is not structured-cloneable. */
|
|
2
|
-
export type TEnvelopeHeaders = Array<[string, string]>;
|
|
3
|
-
export interface IBffRequestEnvelope {
|
|
4
|
-
id: string;
|
|
5
|
-
method: string;
|
|
6
|
-
url: string;
|
|
7
|
-
headers: TEnvelopeHeaders;
|
|
8
|
-
/** Buffered, never a stream - `ReadableStream` does not transfer in Safari. */
|
|
9
|
-
body?: ArrayBuffer;
|
|
10
|
-
}
|
|
11
|
-
export interface IBffResponseEnvelope {
|
|
12
|
-
id: string;
|
|
13
|
-
status: number;
|
|
14
|
-
statusText?: string;
|
|
15
|
-
headers: TEnvelopeHeaders;
|
|
16
|
-
body?: ArrayBuffer;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* A custom `Error` subclass loses its name, its own properties and its prototype through structured
|
|
20
|
-
* clone, so an `ApplicationError` cannot cross as itself. It crosses as its normalised shape and is
|
|
21
|
-
* rebuilt with `fromError` on the far side.
|
|
22
|
-
*/
|
|
23
|
-
export interface IBffErrorEnvelope {
|
|
24
|
-
id: string;
|
|
25
|
-
/**
|
|
26
|
-
* Carried explicitly, because `fromError` defaults a missing one to 400. This envelope is only
|
|
27
|
-
* ever produced when something escaped Hono's `onError` - the 500-class failures a UI must not
|
|
28
|
-
* classify as its own bad request.
|
|
29
|
-
*/
|
|
30
|
-
statusCode: number;
|
|
31
|
-
error: {
|
|
32
|
-
text: string;
|
|
33
|
-
code?: string;
|
|
34
|
-
args?: unknown;
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/envelope/common/types.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,gBAAgB,CAAC;IAC1B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/envelope/common/types.ts"],"names":[],"mappings":"","sourcesContent":["/** Headers are carried as tuples: a `Headers` instance is not structured-cloneable. */\nexport type TEnvelopeHeaders = Array<[string, string]>;\n\nexport interface IBffRequestEnvelope {\n id: string;\n method: string;\n url: string;\n headers: TEnvelopeHeaders;\n /** Buffered, never a stream - `ReadableStream` does not transfer in Safari. */\n body?: ArrayBuffer;\n}\n\nexport interface IBffResponseEnvelope {\n id: string;\n status: number;\n statusText?: string;\n headers: TEnvelopeHeaders;\n body?: ArrayBuffer;\n}\n\n/**\n * A custom `Error` subclass loses its name, its own properties and its prototype through structured\n * clone, so an `ApplicationError` cannot cross as itself. It crosses as its normalised shape and is\n * rebuilt with `fromError` on the far side.\n */\nexport interface IBffErrorEnvelope {\n id: string;\n /**\n * Carried explicitly, because `fromError` defaults a missing one to 400. This envelope is only\n * ever produced when something escaped Hono's `onError` - the 500-class failures a UI must not\n * classify as its own bad request.\n */\n statusCode: number;\n error: { text: string; code?: string; args?: unknown };\n}\n"]}
|