redweb 0.13.5 → 0.15.0

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 (72) hide show
  1. package/CHANGELOG.md +20 -3
  2. package/README.md +289 -288
  3. package/contract.d.ts +11 -3
  4. package/docs/APPLICATION.md +98 -0
  5. package/docs/CLI.md +1 -1
  6. package/docs/CLIENT_DEVELOPMENT.md +9 -5
  7. package/docs/COVERAGE_SCOPE_AUDIT.md +7 -0
  8. package/docs/DEFINE_APP_VERIFICATION.md +101 -0
  9. package/docs/DEVELOPMENT.md +1 -1
  10. package/docs/GETTING_STARTED.md +1 -1
  11. package/docs/LIVE_HTML.md +2 -2
  12. package/docs/MIGRATION.md +1 -1
  13. package/docs/MULTIPLAYER_OPERATIONS.md +6 -0
  14. package/docs/RELEASE_TRUST.md +29 -6
  15. package/docs/RUNTIME_DIAGNOSTICS.md +1 -1
  16. package/docs/SOCKET_CONTRACTS.md +6 -3
  17. package/docs/SOCKET_PAGES.md +99 -0
  18. package/docs/SOCKET_PAGE_RELEASE_PREPARATION.md +120 -0
  19. package/docs/SOCKET_PAGE_VERIFICATION.md +85 -0
  20. package/docs/STARTER_LIFECYCLE_VERIFICATION.md +9 -0
  21. package/docs/generated.json +2217 -2154
  22. package/docs/guides/http-websocket.md +4 -4
  23. package/docs/guides/jsx-without-react.md +1 -1
  24. package/docs/reference.json +40 -1
  25. package/docs/releases/0.14.0.json +2208 -0
  26. package/docs/releases/0.15.0.json +2217 -0
  27. package/docs/topics.json +3 -1
  28. package/examples/live-html/cards.js +87 -86
  29. package/examples/live-html/cards.ts +3 -2
  30. package/examples/live-html/chatroom.js +210 -207
  31. package/examples/live-html/chatroom.tsx +6 -2
  32. package/examples/live-html/components.js +103 -102
  33. package/examples/live-html/components.ts +3 -2
  34. package/examples/live-html/counter.js +74 -73
  35. package/examples/live-html/counter.ts +3 -2
  36. package/examples/live-html/jsx-page.js +2 -1
  37. package/examples/live-html/jsx-page.tsx +3 -2
  38. package/index.d.ts +59 -7
  39. package/index.js +3 -0
  40. package/package.json +8 -5
  41. package/recipes/chat/app.test.cjs +3 -1
  42. package/recipes/chat/app.tsx +4 -7
  43. package/recipes/dashboard/app.test.cjs +10 -10
  44. package/recipes/dashboard/app.tsx +29 -29
  45. package/recipes/http-ws/README.md +1 -1
  46. package/recipes/http-ws/app.test.cjs +8 -10
  47. package/recipes/http-ws/app.tsx +9 -20
  48. package/recipes/realtime/app.tsx +3 -6
  49. package/recipes/shared/README.md +10 -1
  50. package/recipes/shared/lifecycle.test.cjs +81 -0
  51. package/recipes/shared/network.cjs +10 -5
  52. package/recipes/site/app.tsx +3 -6
  53. package/recipes/socket/app.tsx +3 -10
  54. package/src/Application.js +239 -0
  55. package/src/StartupCleanup.js +24 -0
  56. package/src/cli/SourceInspector.js +5 -0
  57. package/src/cli/templates.js +6 -6
  58. package/src/htmx/Jsx.js +2 -2
  59. package/src/htmx/LiveHtmlServer.js +14 -5
  60. package/src/htmx/PageManager.js +13 -8
  61. package/src/htmx/PageSocketRoute.js +132 -0
  62. package/src/htmx/ReactiveRenderer.js +8 -2
  63. package/src/htmx/SocketAction.js +19 -0
  64. package/src/htmx/metadata.js +6 -2
  65. package/src/ws/BaseHandler.js +6 -4
  66. package/src/ws/BaseSocketServer.js +13 -13
  67. package/src/ws/HandlerGuard.js +4 -0
  68. package/src/ws/SocketAction.js +16 -0
  69. package/src/ws/SocketContract.js +3 -2
  70. package/src/ws/SocketRoute.js +9 -4
  71. package/recipes/shared/run-app.test.cjs +0 -158
  72. package/recipes/shared/run-app.ts +0 -50
@@ -1,113 +1,114 @@
1
- "use strict";
2
- var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
- var useValue = arguments.length > 2;
4
- for (var i = 0; i < initializers.length; i++) {
5
- value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
- }
7
- return useValue ? value : void 0;
8
- };
9
- var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
- function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
- var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
- var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
- var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
- var _, done = false;
15
- for (var i = decorators.length - 1; i >= 0; i--) {
16
- var context = {};
17
- for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
- for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
- context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
- var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
- if (kind === "accessor") {
22
- if (result === void 0) continue;
23
- if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
- if (_ = accept(result.get)) descriptor.get = _;
25
- if (_ = accept(result.set)) descriptor.set = _;
26
- if (_ = accept(result.init)) initializers.unshift(_);
27
- }
28
- else if (_ = accept(result)) {
29
- if (kind === "field") initializers.unshift(_);
30
- else descriptor[key] = _;
31
- }
32
- }
33
- if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
- done = true;
35
- };
36
- Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.ComponentsPage = exports.CounterComponent = void 0;
38
- const redweb_1 = require('../..');
39
- let CounterComponent = (() => {
40
- let _classDecorators = [(0, redweb_1.component)()];
41
- let _classDescriptor;
42
- let _classExtraInitializers = [];
43
- let _classThis;
44
- let _instanceExtraInitializers = [];
45
- let _count_decorators;
46
- let _count_initializers = [];
47
- let _count_extraInitializers = [];
48
- let _increment_decorators;
49
- var CounterComponent = class {
50
- static { _classThis = this; }
51
- static {
52
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
53
- _count_decorators = [(0, redweb_1.state)()];
54
- _increment_decorators = [(0, redweb_1.action)()];
55
- __esDecorate(this, null, _increment_decorators, { kind: "method", name: "increment", static: false, private: false, access: { has: obj => "increment" in obj, get: obj => obj.increment }, metadata: _metadata }, null, _instanceExtraInitializers);
56
- __esDecorate(null, null, _count_decorators, { kind: "field", name: "count", static: false, private: false, access: { has: obj => "count" in obj, get: obj => obj.count, set: (obj, value) => { obj.count = value; } }, metadata: _metadata }, _count_initializers, _count_extraInitializers);
57
- __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
58
- CounterComponent = _classThis = _classDescriptor.value;
59
- if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
60
- __runInitializers(_classThis, _classExtraInitializers);
61
- }
62
- label = __runInitializers(this, _instanceExtraInitializers);
63
- count = __runInitializers(this, _count_initializers, 0);
64
- constructor(label) {
65
- __runInitializers(this, _count_extraInitializers);
66
- this.label = label;
67
- }
68
- increment() {
69
- this.count += 1;
70
- }
71
- render() {
1
+ "use strict";
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.ComponentsPage = exports.CounterComponent = void 0;
38
+ const redweb_1 = require('../..');
39
+ let CounterComponent = (() => {
40
+ let _classDecorators = [(0, redweb_1.component)()];
41
+ let _classDescriptor;
42
+ let _classExtraInitializers = [];
43
+ let _classThis;
44
+ let _instanceExtraInitializers = [];
45
+ let _count_decorators;
46
+ let _count_initializers = [];
47
+ let _count_extraInitializers = [];
48
+ let _increment_decorators;
49
+ var CounterComponent = class {
50
+ static { _classThis = this; }
51
+ static {
52
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
53
+ _count_decorators = [(0, redweb_1.state)()];
54
+ _increment_decorators = [(0, redweb_1.action)()];
55
+ __esDecorate(this, null, _increment_decorators, { kind: "method", name: "increment", static: false, private: false, access: { has: obj => "increment" in obj, get: obj => obj.increment }, metadata: _metadata }, null, _instanceExtraInitializers);
56
+ __esDecorate(null, null, _count_decorators, { kind: "field", name: "count", static: false, private: false, access: { has: obj => "count" in obj, get: obj => obj.count, set: (obj, value) => { obj.count = value; } }, metadata: _metadata }, _count_initializers, _count_extraInitializers);
57
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
58
+ CounterComponent = _classThis = _classDescriptor.value;
59
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
60
+ __runInitializers(_classThis, _classExtraInitializers);
61
+ }
62
+ label = __runInitializers(this, _instanceExtraInitializers);
63
+ count = __runInitializers(this, _count_initializers, 0);
64
+ constructor(label) {
65
+ __runInitializers(this, _count_extraInitializers);
66
+ this.label = label;
67
+ }
68
+ increment() {
69
+ this.count += 1;
70
+ }
71
+ render() {
72
72
  return (0, redweb_1.html) `
73
73
  <article class="counter-card">
74
74
  <h2>${this.label}</h2>
75
75
  <output data-rw-state="count">${this.count}</output>
76
76
  <button type="button" rw-click="increment">Increment on the server</button>
77
77
  </article>
78
- `;
79
- }
80
- };
81
- return CounterComponent = _classThis;
82
- })();
83
- exports.CounterComponent = CounterComponent;
84
- let ComponentsPage = (() => {
85
- let _classDecorators = [(0, redweb_1.page)('/', { css: 'components.css' })];
86
- let _classDescriptor;
87
- let _classExtraInitializers = [];
88
- let _classThis;
89
- var ComponentsPage = class {
90
- static { _classThis = this; }
91
- static {
92
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
93
- __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
94
- ComponentsPage = _classThis = _classDescriptor.value;
95
- if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
96
- __runInitializers(_classThis, _classExtraInitializers);
97
- }
98
- primary = new CounterComponent('Primary counter');
99
- secondary = new CounterComponent('Independent counter');
100
- render() {
78
+ `;
79
+ }
80
+ };
81
+ return CounterComponent = _classThis;
82
+ })();
83
+ exports.CounterComponent = CounterComponent;
84
+ let ComponentsPage = (() => {
85
+ let _classDecorators = [(0, redweb_1.page)('/', { css: 'components.css' })];
86
+ let _classDescriptor;
87
+ let _classExtraInitializers = [];
88
+ let _classThis;
89
+ var ComponentsPage = class {
90
+ static { _classThis = this; }
91
+ static {
92
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
93
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
94
+ ComponentsPage = _classThis = _classDescriptor.value;
95
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
96
+ __runInitializers(_classThis, _classExtraInitializers);
97
+ }
98
+ primary = new CounterComponent('Primary counter');
99
+ secondary = new CounterComponent('Independent counter');
100
+ render() {
101
101
  return (0, redweb_1.html) `
102
102
  <main>
103
103
  <h1>Reusable server components</h1>
104
104
  <section class="counter-grid">${this.primary}${this.secondary}</section>
105
105
  </main>
106
- `;
107
- }
108
- };
109
- return ComponentsPage = _classThis;
110
- })();
111
- exports.ComponentsPage = ComponentsPage;
112
- if (require.main === module)
113
- (0, redweb_1.start)(ComponentsPage, { port: 8080 });
106
+ `;
107
+ }
108
+ };
109
+ return ComponentsPage = _classThis;
110
+ })();
111
+ exports.ComponentsPage = ComponentsPage;
112
+ if (require.main === module)
113
+ void (0, redweb_1.defineApp)({ pages: [ComponentsPage], port: 8080 }).run()
114
+ .catch(error => { console.error(error); process.exitCode = 1; });
@@ -1,4 +1,4 @@
1
- import { action, component, html, page, start, state } from 'redweb';
1
+ import { action, component, defineApp, html, page, state } from 'redweb';
2
2
 
3
3
  @component()
4
4
  export class CounterComponent {
@@ -38,4 +38,5 @@ export class ComponentsPage {
38
38
  }
39
39
  }
40
40
 
41
- if (require.main === module) start(ComponentsPage, { port: 8080 });
41
+ if (require.main === module) void defineApp({ pages: [ComponentsPage], port: 8080 }).run()
42
+ .catch(error => { console.error(error); process.exitCode = 1; });
@@ -1,73 +1,74 @@
1
- "use strict";
2
- var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
3
- function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
4
- var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
5
- var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6
- var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
7
- var _, done = false;
8
- for (var i = decorators.length - 1; i >= 0; i--) {
9
- var context = {};
10
- for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
11
- for (var p in contextIn.access) context.access[p] = contextIn.access[p];
12
- context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
13
- var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
14
- if (kind === "accessor") {
15
- if (result === void 0) continue;
16
- if (result === null || typeof result !== "object") throw new TypeError("Object expected");
17
- if (_ = accept(result.get)) descriptor.get = _;
18
- if (_ = accept(result.set)) descriptor.set = _;
19
- if (_ = accept(result.init)) initializers.unshift(_);
20
- }
21
- else if (_ = accept(result)) {
22
- if (kind === "field") initializers.unshift(_);
23
- else descriptor[key] = _;
24
- }
25
- }
26
- if (target) Object.defineProperty(target, contextIn.name, descriptor);
27
- done = true;
28
- };
29
- var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
30
- var useValue = arguments.length > 2;
31
- for (var i = 0; i < initializers.length; i++) {
32
- value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
33
- }
34
- return useValue ? value : void 0;
35
- };
36
- Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.CounterPage = void 0;
38
- const redweb_1 = require('../..');
39
- let CounterPage = (() => {
40
- let _classDecorators = [(0, redweb_1.page)('/', { template: 'counter.html', css: 'counter.css' })];
41
- let _classDescriptor;
42
- let _classExtraInitializers = [];
43
- let _classThis;
44
- let _count_decorators;
45
- let _count_initializers = [];
46
- let _count_extraInitializers = [];
47
- var CounterPage = class {
48
- static { _classThis = this; }
49
- static {
50
- const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
51
- _count_decorators = [(0, redweb_1.state)()];
52
- __esDecorate(null, null, _count_decorators, { kind: "field", name: "count", static: false, private: false, access: { has: obj => "count" in obj, get: obj => obj.count, set: (obj, value) => { obj.count = value; } }, metadata: _metadata }, _count_initializers, _count_extraInitializers);
53
- __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
54
- CounterPage = _classThis = _classDescriptor.value;
55
- if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
56
- __runInitializers(_classThis, _classExtraInitializers);
57
- }
58
- count = __runInitializers(this, _count_initializers, 0);
59
- ticker = (__runInitializers(this, _count_extraInitializers), null);
60
- connected() {
61
- this.ticker = setInterval(() => { this.count += 1; }, 1000);
62
- }
63
- disconnected() {
64
- if (this.ticker)
65
- clearInterval(this.ticker);
66
- this.ticker = null;
67
- }
68
- };
69
- return CounterPage = _classThis;
70
- })();
71
- exports.CounterPage = CounterPage;
72
- if (require.main === module)
73
- (0, redweb_1.start)(CounterPage, { port: 8080 });
1
+ "use strict";
2
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
3
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
4
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
5
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
7
+ var _, done = false;
8
+ for (var i = decorators.length - 1; i >= 0; i--) {
9
+ var context = {};
10
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
11
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
12
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
13
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
14
+ if (kind === "accessor") {
15
+ if (result === void 0) continue;
16
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
17
+ if (_ = accept(result.get)) descriptor.get = _;
18
+ if (_ = accept(result.set)) descriptor.set = _;
19
+ if (_ = accept(result.init)) initializers.unshift(_);
20
+ }
21
+ else if (_ = accept(result)) {
22
+ if (kind === "field") initializers.unshift(_);
23
+ else descriptor[key] = _;
24
+ }
25
+ }
26
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
27
+ done = true;
28
+ };
29
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
30
+ var useValue = arguments.length > 2;
31
+ for (var i = 0; i < initializers.length; i++) {
32
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
33
+ }
34
+ return useValue ? value : void 0;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.CounterPage = void 0;
38
+ const redweb_1 = require('../..');
39
+ let CounterPage = (() => {
40
+ let _classDecorators = [(0, redweb_1.page)('/', { template: 'counter.html', css: 'counter.css' })];
41
+ let _classDescriptor;
42
+ let _classExtraInitializers = [];
43
+ let _classThis;
44
+ let _count_decorators;
45
+ let _count_initializers = [];
46
+ let _count_extraInitializers = [];
47
+ var CounterPage = class {
48
+ static { _classThis = this; }
49
+ static {
50
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
51
+ _count_decorators = [(0, redweb_1.state)()];
52
+ __esDecorate(null, null, _count_decorators, { kind: "field", name: "count", static: false, private: false, access: { has: obj => "count" in obj, get: obj => obj.count, set: (obj, value) => { obj.count = value; } }, metadata: _metadata }, _count_initializers, _count_extraInitializers);
53
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
54
+ CounterPage = _classThis = _classDescriptor.value;
55
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
56
+ __runInitializers(_classThis, _classExtraInitializers);
57
+ }
58
+ count = __runInitializers(this, _count_initializers, 0);
59
+ ticker = (__runInitializers(this, _count_extraInitializers), null);
60
+ connected() {
61
+ this.ticker = setInterval(() => { this.count += 1; }, 1000);
62
+ }
63
+ disconnected() {
64
+ if (this.ticker)
65
+ clearInterval(this.ticker);
66
+ this.ticker = null;
67
+ }
68
+ };
69
+ return CounterPage = _classThis;
70
+ })();
71
+ exports.CounterPage = CounterPage;
72
+ if (require.main === module)
73
+ void (0, redweb_1.defineApp)({ pages: [CounterPage], port: 8080 }).run()
74
+ .catch(error => { console.error(error); process.exitCode = 1; });
@@ -1,4 +1,4 @@
1
- import { page, start, state } from 'redweb';
1
+ import { defineApp, page, state } from 'redweb';
2
2
 
3
3
  @page('/', { template: 'counter.html', css: 'counter.css' })
4
4
  export class CounterPage {
@@ -18,4 +18,5 @@ export class CounterPage {
18
18
 
19
19
  }
20
20
 
21
- if (require.main === module) start(CounterPage, { port: 8080 });
21
+ if (require.main === module) void defineApp({ pages: [CounterPage], port: 8080 }).run()
22
+ .catch(error => { console.error(error); process.exitCode = 1; });
@@ -78,4 +78,5 @@ let JsxPage = (() => {
78
78
  })();
79
79
  exports.JsxPage = JsxPage;
80
80
  if (require.main === module)
81
- (0, redweb_1.start)(JsxPage, { port: 8181 });
81
+ void (0, redweb_1.defineApp)({ pages: [JsxPage], port: 8181 }).run()
82
+ .catch(error => { console.error(error); process.exitCode = 1; });
@@ -1,4 +1,4 @@
1
- import { LivePage, action, component, page, start, state } from 'redweb';
1
+ import { LivePage, action, component, defineApp, page, state } from 'redweb';
2
2
  import type { Child } from 'redweb/jsx-runtime';
3
3
 
4
4
  interface CardProperties {
@@ -38,4 +38,5 @@ export class JsxPage extends LivePage {
38
38
  }
39
39
  }
40
40
 
41
- if (require.main === module) start(JsxPage, { port: 8181 });
41
+ if (require.main === module) void defineApp({ pages: [JsxPage], port: 8181 }).run()
42
+ .catch(error => { console.error(error); process.exitCode = 1; });
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  declare module 'redweb' {
2
2
  export { defineSocketContract } from 'redweb/contract';
3
3
  export type { SocketContract, ContractClient, SocketSchema, ContractInput, ContractOutput, ContractMessage } from 'redweb/contract';
4
- import { Application, RequestHandler } from 'express';
4
+ import { Application as ExpressApplication, RequestHandler } from 'express';
5
5
  import { CorsOptions } from 'cors';
6
6
  import { Server as NodeHttpServer } from 'http';
7
7
  import { Server as NodeHttpsServer } from 'https';
@@ -22,7 +22,7 @@ declare module 'redweb' {
22
22
  listenCallback?: () => void;
23
23
  encoding?: RedWebEncoding;
24
24
  ssl?: { key: string; cert: string };
25
- server?: Application;
25
+ server?: ExpressApplication;
26
26
  corsOptions?: CorsOptions | false;
27
27
  exposeErrors?: boolean;
28
28
  logger?: RedWebLogger | null;
@@ -35,7 +35,9 @@ declare module 'redweb' {
35
35
  isAssigned: boolean;
36
36
  sendJson(data: unknown): boolean;
37
37
  broadcast(data: unknown): number;
38
- context?: RedWebConnectionContext;
38
+ context?: RedWebConnectionContext;
39
+ /** Present on an attached page connection; checks the actual page class and connection lifetime. */
40
+ page?<Page extends object>(PageClass: new () => Page): Page;
39
41
  joinRoom?(roomId: string): boolean;
40
42
  /** Bounded permission check followed by atomic membership insertion. */
41
43
  enterRoom?(roomId: string): Promise<boolean>;
@@ -287,7 +289,10 @@ declare module 'redweb' {
287
289
  handlers: Array<new () => BaseHandler>;
288
290
  services?: Array<new () => SocketService>;
289
291
  allowDuplicateConnections?: boolean;
290
- websocketOptions?: Omit<ServerOptions, 'noServer' | 'path' | 'server' | 'port'>;
292
+ websocketOptions?: Omit<ServerOptions, 'noServer' | 'path' | 'server' | 'port'> & {
293
+ /** Closing-handshake deadline in milliseconds (1..2147483647). Default: 5000. Not an idle timeout. */
294
+ closeTimeout?: number;
295
+ };
291
296
  trustProxy?: boolean;
292
297
  getClientKey?: (request: import('http').IncomingMessage) => string;
293
298
  exposeErrors?: boolean;
@@ -477,7 +482,7 @@ declare module 'redweb' {
477
482
  }
478
483
 
479
484
  export class BaseHttpServer {
480
- app: Application;
485
+ app: ExpressApplication;
481
486
  server?: NodeHttpServer;
482
487
  constructor(options?: RedWebOptions);
483
488
  shutdown?(): Promise<void>;
@@ -551,7 +556,9 @@ declare module 'redweb' {
551
556
  dispose(): Promise<boolean>;
552
557
  }
553
558
 
554
- export type PageOptions = {
559
+ export type PageOptions = {
560
+ /** Use a registered custom route for this private live page's typed TSX commands and rendering. */
561
+ socket?: new () => SocketRoute;
555
562
  template?: string;
556
563
  css?: string | readonly string[];
557
564
  live?: boolean;
@@ -653,6 +660,8 @@ declare module 'redweb' {
653
660
  export type LivePageClass = new () => object;
654
661
 
655
662
  export interface LiveHtmlServerBaseOptions extends Omit<RedWebOptions, 'enableHtmxRendering'> {
663
+ /** Custom routes share the live-page SocketServer; paths must be unique. */
664
+ socketRoutes?: Array<new () => SocketRoute>;
656
665
  development?: LiveDevelopmentOptions;
657
666
  pages: readonly LivePageClass[];
658
667
  templateRoot?: string;
@@ -682,7 +691,7 @@ declare module 'redweb' {
682
691
  export type LiveHtmlStartOptions = Omit<LiveHtmlServerBaseOptions, 'pages'> & LiveHtmlAuthentication;
683
692
 
684
693
  export class LiveHtmlServer {
685
- app: Application;
694
+ app: ExpressApplication;
686
695
  server: NodeHttpServer | NodeHttpsServer;
687
696
  http: HttpServer | HttpsServer;
688
697
  sockets: SocketServer | null;
@@ -699,6 +708,49 @@ declare module 'redweb' {
699
708
  options?: LiveHtmlStartOptions
700
709
  ): LiveHtmlServer;
701
710
 
711
+ export interface ApplicationContext {
712
+ app: ExpressApplication;
713
+ server: NodeHttpServer | NodeHttpsServer;
714
+ http: HttpServer | HttpsServer;
715
+ sockets: SocketServer | null;
716
+ services: ApplicationService[];
717
+ }
718
+
719
+ /** Application-wide resources; route-specific SocketService classes stay on their routes. */
720
+ export interface ApplicationService {
721
+ onInit(app: ApplicationContext, signal: AbortSignal): void | Promise<void>;
722
+ onShutdown(): void | Promise<void>;
723
+ }
724
+
725
+ export type ApplicationOptions = Omit<LiveHtmlServerBaseOptions, 'pages' | 'services' | 'listen' | 'socketRoutes'> & LiveHtmlAuthentication & {
726
+ pages?: readonly LivePageClass[];
727
+ sockets?: ReadonlyArray<new () => SocketRoute>;
728
+ services?: ReadonlyArray<new () => ApplicationService>;
729
+ httpServices?: RedWebOptions['services'];
730
+ startupTimeoutMs?: number;
731
+ /** Install process signal handlers only when run() is called; defaults to true. */
732
+ signals?: boolean;
733
+ };
734
+
735
+ export class Application {
736
+ constructor(options?: ApplicationOptions);
737
+ /** Copied definition; use {...app.options, port: 0} to define an independent test instance. */
738
+ readonly options: Readonly<ApplicationOptions>;
739
+ readonly app: ExpressApplication | null;
740
+ readonly server: NodeHttpServer | NodeHttpsServer | null;
741
+ readonly http: HttpServer | HttpsServer | null;
742
+ readonly sockets: SocketServer | null;
743
+ readonly services: ApplicationService[];
744
+ /** Resolves after services initialize and the single HTTP/WS listener is ready. Cannot restart after shutdown. */
745
+ run(): Promise<Application & ApplicationContext>;
746
+ shutdown(): Promise<void>;
747
+ revoke(principal: string | number | bigint | true): Promise<number>;
748
+ inspect(): DevelopmentSnapshot | null;
749
+ }
750
+
751
+ /** Inert until run(); repeated run calls share startup until shutdown; shutdown is idempotent. */
752
+ export function defineApp(options?: ApplicationOptions): Application;
753
+
702
754
  export interface StaticExportOptions {
703
755
  outDir: string;
704
756
  templateRoot?: string;
package/index.js CHANGED
@@ -15,10 +15,13 @@ const {
15
15
  } = require('./src/ws');
16
16
  const { BaseHandler } = require('./src/ws/BaseHandler');
17
17
  const { defineSocketContract } = require('./contract');
18
+ const { Application, defineApp } = require('./src/Application');
18
19
  const HttpServer = require('./src/http/HttpServer');
19
20
  const HttpsServer = require('./src/http/HttpsServer');
20
21
  const { action, attribute, codeBlock, component, defineSite, each, exportStatic, html, HtmlRenderer, LiveHtmlServer, LivePage, page, start, state, url, view } = require('./src/htmx');
21
22
  module.exports = {
23
+ Application,
24
+ defineApp,
22
25
  HttpServer,
23
26
  HttpsServer,
24
27
  BaseHttpServer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redweb",
3
- "version": "0.13.5",
3
+ "version": "0.15.0",
4
4
  "description": "A small Node.js foundation for HTTP, WebSockets, multiplayer services, and server-rendered HTML",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -93,8 +93,8 @@
93
93
  "verify:client:source-coverage": "node node_modules/jest/bin/jest.js tests/unit/client-source-coverage.unit.test.js tests/integration/client-source-preflight.integration.test.js tests/integration/command-report.integration.test.js --runInBand --coverage --collectCoverageFrom=scripts/lib/ClientSourceCoverage.js --collectCoverageFrom=scripts/lib/reportCommand.js --coverageDirectory=coverage/client-source-collector && node --test tests/integration/client-worker-retention.integration.test.cjs && node scripts/verify-client-source-coverage.js",
94
94
  "verify:development:browser": "node scripts/verify-development-refresh-browser.js",
95
95
  "verify:development:coverage": "node node_modules/jest/bin/jest.js tests/unit/development-refresh-cleanup.unit.test.js tests/unit/development-refresh-boundaries.unit.test.js tests/integration/development-refresh-verifier.integration.test.js --runInBand --coverage --collectCoverageFrom=scripts/verify-development-refresh-browser.js --coverageDirectory=coverage/development-coordinator",
96
- "verify:starters:lifecycle": "node scripts/verify-starter-lifecycle.js",
97
- "verify:starters:lifecycle:coverage": "node node_modules/jest/bin/jest.js tests/unit/starter-lifecycle-command.unit.test.js tests/integration/starter-lifecycle-coverage.integration.test.js --runInBand --silent --coverage --collectCoverageFrom=scripts/verify-starter-lifecycle.js --coverageDirectory=coverage/starter-lifecycle-coordinator",
96
+ "verify:starters:lifecycle": "node node_modules/jest/bin/jest.js tests/integration/define-app-process.integration.test.js --runInBand --coverage=false",
97
+ "verify:starters:lifecycle:coverage": "node node_modules/jest/bin/jest.js tests/unit/define-app.unit.test.js tests/unit/define-app-process.unit.test.js tests/unit/startup-cleanup.unit.test.js tests/integration/define-app.integration.test.js tests/integration/define-app-process.integration.test.js --runInBand --coverage --collectCoverageFrom=src/Application.js --collectCoverageFrom=src/StartupCleanup.js --coverageDirectory=coverage/application-lifecycle",
98
98
  "measure:starters:coverage": "node scripts/measure-starter-coverage.js",
99
99
  "verify:starter-coordinators:coverage": "node node_modules/jest/bin/jest.js tests/unit/starter-coordinators.unit.test.js tests/integration/verification-summary.integration.test.js tests/integration/starter-coordinator-input.integration.test.js --runInBand --silent --coverage --collectCoverageFrom=scripts/measure-starter-coverage.js --collectCoverageFrom=scripts/verify-starter-source-coverage.js --collectCoverageFrom=scripts/lib/finishVerificationSummary.js --coverageDirectory=coverage/starter-coordinators",
100
100
  "verify:starters:source-coverage": "node node_modules/jest/bin/jest.js tests/unit/application-coverage.unit.test.js --runInBand --coverage --collectCoverageFrom=scripts/lib/ApplicationCoverage.js --collectCoverageFrom=scripts/lib/assertCoverageFile.js --coverageDirectory=coverage/application-collector && node scripts/verify-starter-source-coverage.js",
@@ -159,10 +159,13 @@
159
159
  "@types/ws": "^8.18.1",
160
160
  "cors": "^2.8.5",
161
161
  "express": "^4.22.2",
162
- "redweb-client": "^0.2.0",
162
+ "redweb-client": "^0.3.0",
163
163
  "ws": "^8.21.3"
164
164
  },
165
- "devDependencies": {
165
+ "overrides": {
166
+ "express": { "qs": "6.16.0" }
167
+ },
168
+ "devDependencies": {
166
169
  "@types/jest": "^29.5.12",
167
170
  "c8": "^10.1.3",
168
171
  "expect": "29.7.0",
@@ -1,8 +1,10 @@
1
1
  const test = require('node:test');
2
2
  const { once } = require('node:events');
3
3
  const assert = require('node:assert/strict');
4
- const { listen, live, connect } = require('./network.cjs');
4
+ const { listen: listenApp, live, connect } = require('./network.cjs');
5
5
  const { createChatroomPage, chatInputs } = require('../dist/chatroom.js');
6
+ // Each test gets an independent room rather than the default module-level room.
7
+ const listen = t => listenApp(t, { pages: [createChatroomPage()] });
6
8
 
7
9
  test('the standalone canonical chat reports an occupied default port', { timeout: 10000 }, async t => {
8
10
  const net = require('node:net');
@@ -1,9 +1,6 @@
1
- import { start, type LiveHtmlStartOptions } from 'redweb';
2
- import { createChatroomPage } from './chatroom';
3
- import { runApp } from './run-app';
1
+ import { defineApp } from 'redweb';
2
+ import { ChatroomPage } from './chatroom';
4
3
 
5
- export function createApp(options: LiveHtmlStartOptions = {}) {
6
- return start(createChatroomPage(), { port: Number(process.env.PORT ?? 8181), templateRoot: __dirname, ...options });
7
- }
4
+ export const app = defineApp({ pages: [ChatroomPage], port: Number(process.env.PORT ?? 8181), templateRoot: __dirname });
8
5
 
9
- if (require.main === module) runApp(createApp);
6
+ if (require.main === module) void app.run().catch(error => { console.error(error); process.exitCode = 1; });