@loadbare/app 0.20.0 → 0.22.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.
- package/README.md +18 -0
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +8 -3
- package/dist/build/assemble.js.map +1 -1
- package/dist/build/build.d.ts +62 -0
- package/dist/build/build.d.ts.map +1 -0
- package/dist/build/build.js +148 -0
- package/dist/build/build.js.map +1 -0
- package/dist/build/cli.d.ts +6 -1
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +25 -78
- package/dist/build/cli.js.map +1 -1
- package/dist/build/declared.d.ts.map +1 -1
- package/dist/build/declared.js +3 -0
- package/dist/build/declared.js.map +1 -1
- package/dist/build/skills-cli.d.ts +2 -1
- package/dist/build/skills-cli.d.ts.map +1 -1
- package/dist/build/skills-cli.js +8 -1
- package/dist/build/skills-cli.js.map +1 -1
- package/dist/build/skills.d.ts +2 -0
- package/dist/build/skills.d.ts.map +1 -1
- package/dist/build/skills.js +2 -0
- package/dist/build/skills.js.map +1 -1
- package/dist/core/lb-constants.d.ts +14 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +25 -0
- package/dist/core/lb-constants.js.map +1 -1
- package/dist/dev/lb-dev.d.ts +128 -0
- package/dist/dev/lb-dev.d.ts.map +1 -0
- package/dist/dev/lb-dev.js +289 -0
- package/dist/dev/lb-dev.js.map +1 -0
- package/dist/hub/lb-apply.d.ts +10 -2
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +8 -2
- package/dist/hub/lb-apply.js.map +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +127 -19
- package/dist/hub/lb-hub.browser.js.map +1 -1
- package/docs/comparison.md +4 -3
- package/docs/reference/builder.md +12 -4
- package/docs/reference/chrome.md +52 -1
- package/docs/reference/data-binding.md +27 -12
- package/docs/reference/dev-server.md +171 -0
- package/docs/reference/names.md +14 -1
- package/docs/reference/overview.md +1 -0
- package/docs/reference/server.md +8 -3
- package/docs/reference/widgets.md +10 -277
- package/docs/testing.md +2 -1
- package/docs/tutorials/000-getting-started.md +1 -1
- package/docs/tutorials/040-displaying-data.md +1 -1
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +4 -2
- package/skills/loadbare-app/SKILL.md +30 -12
- package/skills/loadbare-app/references/builder.md +12 -4
- package/skills/loadbare-app/references/chrome.md +52 -1
- package/skills/loadbare-app/references/data-binding.md +27 -12
- package/skills/loadbare-app/references/dev-server.md +171 -0
- package/skills/loadbare-app/references/names.md +14 -1
- package/skills/loadbare-app/references/overview.md +1 -0
- package/skills/loadbare-app/references/server.md +8 -3
- package/skills/loadbare-app/references/widgets.md +10 -277
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A development server for a Loadbare application: after any change, the
|
|
3
|
+
* developer refreshes the browser, and never restarts anything.
|
|
4
|
+
*
|
|
5
|
+
* browser --> devProxy :port --> appServer :appPort (a child process)
|
|
6
|
+
* |
|
|
7
|
+
* +-- watchApp (in this process)
|
|
8
|
+
*
|
|
9
|
+
* `devServer` is the whole recipe. It is built from four parts, each
|
|
10
|
+
* exported, so an application that wants something else assembles its own:
|
|
11
|
+
*
|
|
12
|
+
* watchApp @loadbare/app/build: rebuilds `dist/`, and says when each
|
|
13
|
+
* build starts and ends
|
|
14
|
+
* appServer the application's own server as a child process, restarted
|
|
15
|
+
* on demand
|
|
16
|
+
* gate holds requests while a build runs or the server restarts
|
|
17
|
+
* devProxy the port the browser uses, passing requests through the gate
|
|
18
|
+
*
|
|
19
|
+
* Why only the server restarts: `app.html`, `client.js` and `app.css` are
|
|
20
|
+
* read from disk on each request, so a rebuild is all they need. The
|
|
21
|
+
* server's own modules are loaded once, and Node cannot unload a module, so
|
|
22
|
+
* a server-side change needs a new process. The URL holds the page's state,
|
|
23
|
+
* so a refresh puts the user back where they were.
|
|
24
|
+
*
|
|
25
|
+
* The application's server runs as it does in production, unchanged, apart
|
|
26
|
+
* from reading its port from the environment.
|
|
27
|
+
*/
|
|
28
|
+
import http from "node:http";
|
|
29
|
+
export interface Gate {
|
|
30
|
+
/** Hold requests from now until this close is matched by an open. */
|
|
31
|
+
close(reason: string): void;
|
|
32
|
+
/** Match one close of this reason. Requests go on once none is left. */
|
|
33
|
+
open(reason: string): void;
|
|
34
|
+
/** Resolves once the gate is open. */
|
|
35
|
+
wait(): Promise<void>;
|
|
36
|
+
readonly isOpen: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Requests wait while any reason to hold them stands: a build running, the
|
|
40
|
+
* server starting. Each close is matched by its own open, so one ending
|
|
41
|
+
* does not open the gate on another still in progress, of the same reason
|
|
42
|
+
* or another.
|
|
43
|
+
*/
|
|
44
|
+
export declare function gate(): Gate;
|
|
45
|
+
export interface AppServerOptions {
|
|
46
|
+
/** The server's entry file, such as `server.ts`. */
|
|
47
|
+
entry: string;
|
|
48
|
+
/** The port it listens on. */
|
|
49
|
+
port: number;
|
|
50
|
+
/** The environment variable it reads its port from, `PORT` when absent. */
|
|
51
|
+
portEnv?: string;
|
|
52
|
+
/** Node's arguments before the entry, `--env-file-if-exists=.env` when absent. */
|
|
53
|
+
node?: string[];
|
|
54
|
+
/** Where its progress goes. */
|
|
55
|
+
log?: (line: string) => void;
|
|
56
|
+
}
|
|
57
|
+
export type AppServerState = "starting" | "running" | "down" | "stopped";
|
|
58
|
+
export interface AppServer {
|
|
59
|
+
/**
|
|
60
|
+
* Stop the server if it runs, and start it again. Resolves once it
|
|
61
|
+
* accepts connections, or once it has exited instead.
|
|
62
|
+
*/
|
|
63
|
+
restart(): Promise<void>;
|
|
64
|
+
/** Stop it, for good: a restart asked for after this does nothing. */
|
|
65
|
+
stop(): Promise<void>;
|
|
66
|
+
/** `down` is a server that exited on its own, as on a throw at import. */
|
|
67
|
+
readonly state: AppServerState;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The application's server as a child process. The child never outlives
|
|
71
|
+
* this process, however this process ends: it is killed on exit as well as
|
|
72
|
+
* on stop.
|
|
73
|
+
*/
|
|
74
|
+
export declare function appServer(options: AppServerOptions): AppServer;
|
|
75
|
+
export interface DevProxyOptions {
|
|
76
|
+
/** The port the browser uses. */
|
|
77
|
+
port: number;
|
|
78
|
+
/** The host to listen on, `127.0.0.1` when absent. */
|
|
79
|
+
host?: string;
|
|
80
|
+
/** Where requests go on to: the application's server. */
|
|
81
|
+
target: number;
|
|
82
|
+
/** Requests wait at it while it is closed. */
|
|
83
|
+
gate: Gate;
|
|
84
|
+
/** True while the application's server is down, which answers 502. */
|
|
85
|
+
isDown: () => boolean;
|
|
86
|
+
}
|
|
87
|
+
/** Said to the browser while the application's server is down. */
|
|
88
|
+
export declare const DOWN_MESSAGE = "The app server is down; see the dev console.";
|
|
89
|
+
/**
|
|
90
|
+
* Listen on the browser's port, and pass every request through the gate to
|
|
91
|
+
* the application's server, method, path, headers and body as they came.
|
|
92
|
+
* Resolves once listening; rejects with a plain message when the port is in
|
|
93
|
+
* use.
|
|
94
|
+
*/
|
|
95
|
+
export declare function devProxy(options: DevProxyOptions): Promise<http.Server>;
|
|
96
|
+
export interface DevServerOptions {
|
|
97
|
+
/** The application's server entry file, such as `server.ts`. */
|
|
98
|
+
server: string;
|
|
99
|
+
/** The port the browser uses: `PORT` from the environment, or 8787. */
|
|
100
|
+
port?: number;
|
|
101
|
+
/** The host the browser's port listens on, `127.0.0.1` when absent. */
|
|
102
|
+
host?: string;
|
|
103
|
+
/** The port the application's server gets, `port + 10000` when absent. */
|
|
104
|
+
appPort?: number;
|
|
105
|
+
/** The environment variable the application's server reads its port from, `PORT` when absent. */
|
|
106
|
+
portEnv?: string;
|
|
107
|
+
/** Node's arguments before the entry, `--env-file-if-exists=.env` when absent. */
|
|
108
|
+
node?: string[];
|
|
109
|
+
/** The builder's source tree, `src` when absent. */
|
|
110
|
+
src?: string;
|
|
111
|
+
/** The builder's output, `dist` when absent. */
|
|
112
|
+
out?: string;
|
|
113
|
+
/** Where progress goes, the console when absent. */
|
|
114
|
+
log?: (line: string) => void;
|
|
115
|
+
}
|
|
116
|
+
export interface DevServer {
|
|
117
|
+
/** The port the browser uses. */
|
|
118
|
+
port: number;
|
|
119
|
+
/** Stop everything: the watch, the application's server and the port. */
|
|
120
|
+
close(): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The whole dev server: watch and rebuild, restart the application's
|
|
124
|
+
* server when a server-side file changed, and hold every request while
|
|
125
|
+
* either is under way. Stops everything on Ctrl-C or a kill.
|
|
126
|
+
*/
|
|
127
|
+
export declare function devServer(options: DevServerOptions): Promise<DevServer>;
|
|
128
|
+
//# sourceMappingURL=lb-dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lb-dev.d.ts","sourceRoot":"","sources":["../../dev/lb-dev.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,WAAW,IAAI;IACnB,qEAAqE;IACrE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wEAAwE;IACxE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,sCAAsC;IACtC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,IAAI,IAAI,CAwB3B;AAID,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,+BAA+B;IAC/B,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,sEAAsE;IACtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CA0D9D;AAoBD,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,IAAI,EAAE,IAAI,CAAC;IACX,sEAAsE;IACtE,MAAM,EAAE,MAAM,OAAO,CAAC;CACvB;AAED,kEAAkE;AAClE,eAAO,MAAM,YAAY,iDAAiD,CAAC;AAE3E;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAqCvE;AAID,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,SAAS;IACxB,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAWD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA4F7E"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A development server for a Loadbare application: after any change, the
|
|
3
|
+
* developer refreshes the browser, and never restarts anything.
|
|
4
|
+
*
|
|
5
|
+
* browser --> devProxy :port --> appServer :appPort (a child process)
|
|
6
|
+
* |
|
|
7
|
+
* +-- watchApp (in this process)
|
|
8
|
+
*
|
|
9
|
+
* `devServer` is the whole recipe. It is built from four parts, each
|
|
10
|
+
* exported, so an application that wants something else assembles its own:
|
|
11
|
+
*
|
|
12
|
+
* watchApp @loadbare/app/build: rebuilds `dist/`, and says when each
|
|
13
|
+
* build starts and ends
|
|
14
|
+
* appServer the application's own server as a child process, restarted
|
|
15
|
+
* on demand
|
|
16
|
+
* gate holds requests while a build runs or the server restarts
|
|
17
|
+
* devProxy the port the browser uses, passing requests through the gate
|
|
18
|
+
*
|
|
19
|
+
* Why only the server restarts: `app.html`, `client.js` and `app.css` are
|
|
20
|
+
* read from disk on each request, so a rebuild is all they need. The
|
|
21
|
+
* server's own modules are loaded once, and Node cannot unload a module, so
|
|
22
|
+
* a server-side change needs a new process. The URL holds the page's state,
|
|
23
|
+
* so a refresh puts the user back where they were.
|
|
24
|
+
*
|
|
25
|
+
* The application's server runs as it does in production, unchanged, apart
|
|
26
|
+
* from reading its port from the environment.
|
|
27
|
+
*/
|
|
28
|
+
import { spawn } from "node:child_process";
|
|
29
|
+
import { watch } from "node:fs";
|
|
30
|
+
import { readFile } from "node:fs/promises";
|
|
31
|
+
import http from "node:http";
|
|
32
|
+
import net from "node:net";
|
|
33
|
+
import path from "node:path";
|
|
34
|
+
import { watchApp } from "../build/build.js";
|
|
35
|
+
/**
|
|
36
|
+
* Requests wait while any reason to hold them stands: a build running, the
|
|
37
|
+
* server starting. Each close is matched by its own open, so one ending
|
|
38
|
+
* does not open the gate on another still in progress, of the same reason
|
|
39
|
+
* or another.
|
|
40
|
+
*/
|
|
41
|
+
export function gate() {
|
|
42
|
+
const reasons = new Map();
|
|
43
|
+
let waiting = [];
|
|
44
|
+
return {
|
|
45
|
+
close(reason) {
|
|
46
|
+
reasons.set(reason, (reasons.get(reason) ?? 0) + 1);
|
|
47
|
+
},
|
|
48
|
+
open(reason) {
|
|
49
|
+
const left = (reasons.get(reason) ?? 0) - 1;
|
|
50
|
+
if (left > 0)
|
|
51
|
+
reasons.set(reason, left);
|
|
52
|
+
else
|
|
53
|
+
reasons.delete(reason);
|
|
54
|
+
if (reasons.size > 0)
|
|
55
|
+
return;
|
|
56
|
+
for (const go of waiting)
|
|
57
|
+
go();
|
|
58
|
+
waiting = [];
|
|
59
|
+
},
|
|
60
|
+
wait() {
|
|
61
|
+
return reasons.size === 0
|
|
62
|
+
? Promise.resolve()
|
|
63
|
+
: new Promise((go) => waiting.push(go));
|
|
64
|
+
},
|
|
65
|
+
get isOpen() {
|
|
66
|
+
return reasons.size === 0;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The application's server as a child process. The child never outlives
|
|
72
|
+
* this process, however this process ends: it is killed on exit as well as
|
|
73
|
+
* on stop.
|
|
74
|
+
*/
|
|
75
|
+
export function appServer(options) {
|
|
76
|
+
const { entry, port, portEnv = "PORT", node = ["--env-file-if-exists=.env"], log = () => { }, } = options;
|
|
77
|
+
let child = null;
|
|
78
|
+
let state = "stopped";
|
|
79
|
+
let stopped = false;
|
|
80
|
+
const kill = () => child?.kill();
|
|
81
|
+
process.on("exit", kill);
|
|
82
|
+
/** Stop the child that runs now, and wait for it to go. */
|
|
83
|
+
const end = async () => {
|
|
84
|
+
const old = child;
|
|
85
|
+
// Forgotten first, so its exit is not taken for a crash.
|
|
86
|
+
child = null;
|
|
87
|
+
if (!old || old.exitCode !== null || old.signalCode !== null)
|
|
88
|
+
return;
|
|
89
|
+
await new Promise((done) => {
|
|
90
|
+
old.once("exit", done);
|
|
91
|
+
old.kill();
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
async restart() {
|
|
96
|
+
if (stopped)
|
|
97
|
+
return;
|
|
98
|
+
const again = child !== null;
|
|
99
|
+
await end();
|
|
100
|
+
if (again)
|
|
101
|
+
log("restarting the app server");
|
|
102
|
+
state = "starting";
|
|
103
|
+
const started = spawn(process.execPath, [...node, entry], {
|
|
104
|
+
stdio: "inherit",
|
|
105
|
+
env: { ...process.env, [portEnv]: String(port) },
|
|
106
|
+
});
|
|
107
|
+
child = started;
|
|
108
|
+
started.once("exit", (code) => {
|
|
109
|
+
// An exit asked for is the restart's or the stop's business.
|
|
110
|
+
if (child !== started)
|
|
111
|
+
return;
|
|
112
|
+
child = null;
|
|
113
|
+
state = "down";
|
|
114
|
+
log(`the app server stopped (exit ${code}); save a fix to restart it`);
|
|
115
|
+
});
|
|
116
|
+
await listening(port, () => child !== started);
|
|
117
|
+
if (child === started && !stopped)
|
|
118
|
+
state = "running";
|
|
119
|
+
},
|
|
120
|
+
async stop() {
|
|
121
|
+
stopped = true;
|
|
122
|
+
state = "stopped";
|
|
123
|
+
await end();
|
|
124
|
+
process.off("exit", kill);
|
|
125
|
+
},
|
|
126
|
+
get state() {
|
|
127
|
+
return state;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Resolves once something accepts connections on the port, or `abandon` says to give up. */
|
|
132
|
+
async function listening(port, abandon) {
|
|
133
|
+
while (!abandon()) {
|
|
134
|
+
const up = await new Promise((done) => {
|
|
135
|
+
const socket = net.connect(port, "127.0.0.1");
|
|
136
|
+
socket.once("connect", () => {
|
|
137
|
+
socket.end();
|
|
138
|
+
done(true);
|
|
139
|
+
});
|
|
140
|
+
socket.once("error", () => done(false));
|
|
141
|
+
});
|
|
142
|
+
if (up)
|
|
143
|
+
return;
|
|
144
|
+
await new Promise((wait) => setTimeout(wait, 50));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Said to the browser while the application's server is down. */
|
|
148
|
+
export const DOWN_MESSAGE = "The app server is down; see the dev console.";
|
|
149
|
+
/**
|
|
150
|
+
* Listen on the browser's port, and pass every request through the gate to
|
|
151
|
+
* the application's server, method, path, headers and body as they came.
|
|
152
|
+
* Resolves once listening; rejects with a plain message when the port is in
|
|
153
|
+
* use.
|
|
154
|
+
*/
|
|
155
|
+
export function devProxy(options) {
|
|
156
|
+
const { port, host = "127.0.0.1", target, gate, isDown } = options;
|
|
157
|
+
const server = http.createServer(async (req, res) => {
|
|
158
|
+
await gate.wait();
|
|
159
|
+
if (isDown()) {
|
|
160
|
+
res.writeHead(502, { "content-type": "text/plain" }).end(DOWN_MESSAGE);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const ahead = http.request({
|
|
164
|
+
host: "127.0.0.1",
|
|
165
|
+
port: target,
|
|
166
|
+
method: req.method,
|
|
167
|
+
path: req.url,
|
|
168
|
+
headers: req.headers,
|
|
169
|
+
}, (answer) => {
|
|
170
|
+
res.writeHead(answer.statusCode ?? 502, answer.headers);
|
|
171
|
+
answer.pipe(res);
|
|
172
|
+
});
|
|
173
|
+
ahead.on("error", () => {
|
|
174
|
+
if (!res.headersSent)
|
|
175
|
+
res.writeHead(502, { "content-type": "text/plain" });
|
|
176
|
+
res.end(DOWN_MESSAGE);
|
|
177
|
+
});
|
|
178
|
+
req.pipe(ahead);
|
|
179
|
+
});
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
server.once("error", (error) => {
|
|
182
|
+
reject(error.code === "EADDRINUSE"
|
|
183
|
+
? new Error(`port ${port} is in use; is another dev server running?`)
|
|
184
|
+
: error);
|
|
185
|
+
});
|
|
186
|
+
server.listen(port, host, () => resolve(server));
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Whether a build's changes need the server restarted: a module the server
|
|
191
|
+
* loads. Markup, stylesheets and browser code are read from disk per
|
|
192
|
+
* request, or bundled into a file that is.
|
|
193
|
+
*/
|
|
194
|
+
function serverSide(file) {
|
|
195
|
+
return file.endsWith(".ts") && !file.endsWith(".browser.ts");
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The whole dev server: watch and rebuild, restart the application's
|
|
199
|
+
* server when a server-side file changed, and hold every request while
|
|
200
|
+
* either is under way. Stops everything on Ctrl-C or a kill.
|
|
201
|
+
*/
|
|
202
|
+
export async function devServer(options) {
|
|
203
|
+
const port = options.port ?? Number(process.env.PORT || 8787);
|
|
204
|
+
const appPort = options.appPort ?? port + 10000;
|
|
205
|
+
const out = options.out ?? "dist";
|
|
206
|
+
const log = options.log ?? ((line) => console.log(`loadbare-dev: ${line}`));
|
|
207
|
+
const entry = path.resolve(options.server);
|
|
208
|
+
const pagesFile = path.resolve(out, "pages.ts");
|
|
209
|
+
const readPages = () => readFile(pagesFile, "utf-8").catch(() => null);
|
|
210
|
+
const held = gate();
|
|
211
|
+
held.close("build");
|
|
212
|
+
const app = appServer({
|
|
213
|
+
entry,
|
|
214
|
+
port: appPort,
|
|
215
|
+
portEnv: options.portEnv,
|
|
216
|
+
node: options.node,
|
|
217
|
+
log,
|
|
218
|
+
});
|
|
219
|
+
// One restart at a time, in the order asked for: a second asked for while
|
|
220
|
+
// one runs follows it, rather than killing the server it is starting.
|
|
221
|
+
let restarts = Promise.resolve();
|
|
222
|
+
const restart = () => {
|
|
223
|
+
held.close("server");
|
|
224
|
+
restarts = restarts
|
|
225
|
+
.then(() => app.restart())
|
|
226
|
+
.finally(() => held.open("server"));
|
|
227
|
+
return restarts;
|
|
228
|
+
};
|
|
229
|
+
// Started first, so a busy port is reported before anything else runs.
|
|
230
|
+
const proxy = await devProxy({
|
|
231
|
+
port,
|
|
232
|
+
host: options.host,
|
|
233
|
+
target: appPort,
|
|
234
|
+
gate: held,
|
|
235
|
+
isDown: () => app.state === "down",
|
|
236
|
+
});
|
|
237
|
+
let pages = null;
|
|
238
|
+
const watcher = watchApp({
|
|
239
|
+
src: options.src,
|
|
240
|
+
out,
|
|
241
|
+
onStart: () => held.close("build"),
|
|
242
|
+
onBuilt: (outcome) => {
|
|
243
|
+
if (outcome.ok)
|
|
244
|
+
log(outcome.built.summary);
|
|
245
|
+
else
|
|
246
|
+
log(`build failed, serving the last good build: ${outcome.error.message}`);
|
|
247
|
+
void (async () => {
|
|
248
|
+
// The registry the server imports names the page modules, so a page
|
|
249
|
+
// gained or lost changes it without any server-side file changing.
|
|
250
|
+
const now = await readPages();
|
|
251
|
+
const restartNeeded = app.state !== "running" ||
|
|
252
|
+
now !== pages ||
|
|
253
|
+
outcome.changed.some(serverSide);
|
|
254
|
+
pages = now;
|
|
255
|
+
// The first build's server is started once the watch is ready.
|
|
256
|
+
if (restartNeeded && outcome.changed.length > 0)
|
|
257
|
+
await restart();
|
|
258
|
+
held.open("build");
|
|
259
|
+
})();
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
// The entry's directory, not the file: an editor that saves by replacing
|
|
263
|
+
// the file would leave a watcher on the one it replaced.
|
|
264
|
+
const entryWatcher = watch(path.dirname(entry), (_event, file) => {
|
|
265
|
+
if (file === path.basename(entry))
|
|
266
|
+
void restart();
|
|
267
|
+
});
|
|
268
|
+
await watcher.ready;
|
|
269
|
+
pages = await readPages();
|
|
270
|
+
await restart();
|
|
271
|
+
held.open("build");
|
|
272
|
+
log(`http://${options.host ?? "127.0.0.1"}:${port}, app server on ${appPort}`);
|
|
273
|
+
const close = async () => {
|
|
274
|
+
watcher.close();
|
|
275
|
+
entryWatcher.close();
|
|
276
|
+
await app.stop();
|
|
277
|
+
// A browser holds its connection open between requests, and close()
|
|
278
|
+
// waits for every open connection to end.
|
|
279
|
+
proxy.closeAllConnections();
|
|
280
|
+
await new Promise((done) => proxy.close(done));
|
|
281
|
+
process.off("SIGINT", onSignal);
|
|
282
|
+
process.off("SIGTERM", onSignal);
|
|
283
|
+
};
|
|
284
|
+
const onSignal = () => void close().then(() => process.exit(0));
|
|
285
|
+
process.on("SIGINT", onSignal);
|
|
286
|
+
process.on("SIGTERM", onSignal);
|
|
287
|
+
return { port, close };
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=lb-dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lb-dev.js","sourceRoot":"","sources":["../../dev/lb-dev.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A development server for a Loadbare application: after any change, the\n * developer refreshes the browser, and never restarts anything.\n *\n * browser --> devProxy :port --> appServer :appPort (a child process)\n * |\n * +-- watchApp (in this process)\n *\n * `devServer` is the whole recipe. It is built from four parts, each\n * exported, so an application that wants something else assembles its own:\n *\n * watchApp @loadbare/app/build: rebuilds `dist/`, and says when each\n * build starts and ends\n * appServer the application's own server as a child process, restarted\n * on demand\n * gate holds requests while a build runs or the server restarts\n * devProxy the port the browser uses, passing requests through the gate\n *\n * Why only the server restarts: `app.html`, `client.js` and `app.css` are\n * read from disk on each request, so a rebuild is all they need. The\n * server's own modules are loaded once, and Node cannot unload a module, so\n * a server-side change needs a new process. The URL holds the page's state,\n * so a refresh puts the user back where they were.\n *\n * The application's server runs as it does in production, unchanged, apart\n * from reading its port from the environment.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { watch } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport http from \"node:http\";\nimport net from \"node:net\";\nimport path from \"node:path\";\nimport { watchApp, type BuildOutcome } from \"../build/build.js\";\n\n// --- the gate ---------------------------------------------------------------\n\nexport interface Gate {\n /** Hold requests from now until this close is matched by an open. */\n close(reason: string): void;\n /** Match one close of this reason. Requests go on once none is left. */\n open(reason: string): void;\n /** Resolves once the gate is open. */\n wait(): Promise<void>;\n readonly isOpen: boolean;\n}\n\n/**\n * Requests wait while any reason to hold them stands: a build running, the\n * server starting. Each close is matched by its own open, so one ending\n * does not open the gate on another still in progress, of the same reason\n * or another.\n */\nexport function gate(): Gate {\n const reasons = new Map<string, number>();\n let waiting: (() => void)[] = [];\n return {\n close(reason) {\n reasons.set(reason, (reasons.get(reason) ?? 0) + 1);\n },\n open(reason) {\n const left = (reasons.get(reason) ?? 0) - 1;\n if (left > 0) reasons.set(reason, left);\n else reasons.delete(reason);\n if (reasons.size > 0) return;\n for (const go of waiting) go();\n waiting = [];\n },\n wait() {\n return reasons.size === 0\n ? Promise.resolve()\n : new Promise((go) => waiting.push(go));\n },\n get isOpen() {\n return reasons.size === 0;\n },\n };\n}\n\n// --- the application's server ----------------------------------------------\n\nexport interface AppServerOptions {\n /** The server's entry file, such as `server.ts`. */\n entry: string;\n /** The port it listens on. */\n port: number;\n /** The environment variable it reads its port from, `PORT` when absent. */\n portEnv?: string;\n /** Node's arguments before the entry, `--env-file-if-exists=.env` when absent. */\n node?: string[];\n /** Where its progress goes. */\n log?: (line: string) => void;\n}\n\nexport type AppServerState = \"starting\" | \"running\" | \"down\" | \"stopped\";\n\nexport interface AppServer {\n /**\n * Stop the server if it runs, and start it again. Resolves once it\n * accepts connections, or once it has exited instead.\n */\n restart(): Promise<void>;\n /** Stop it, for good: a restart asked for after this does nothing. */\n stop(): Promise<void>;\n /** `down` is a server that exited on its own, as on a throw at import. */\n readonly state: AppServerState;\n}\n\n/**\n * The application's server as a child process. The child never outlives\n * this process, however this process ends: it is killed on exit as well as\n * on stop.\n */\nexport function appServer(options: AppServerOptions): AppServer {\n const {\n entry,\n port,\n portEnv = \"PORT\",\n node = [\"--env-file-if-exists=.env\"],\n log = () => {},\n } = options;\n let child: ChildProcess | null = null;\n let state: AppServerState = \"stopped\";\n let stopped = false;\n const kill = () => child?.kill();\n process.on(\"exit\", kill);\n\n /** Stop the child that runs now, and wait for it to go. */\n const end = async () => {\n const old = child;\n // Forgotten first, so its exit is not taken for a crash.\n child = null;\n if (!old || old.exitCode !== null || old.signalCode !== null) return;\n await new Promise((done) => {\n old.once(\"exit\", done);\n old.kill();\n });\n };\n\n return {\n async restart() {\n if (stopped) return;\n const again = child !== null;\n await end();\n if (again) log(\"restarting the app server\");\n state = \"starting\";\n const started = spawn(process.execPath, [...node, entry], {\n stdio: \"inherit\",\n env: { ...process.env, [portEnv]: String(port) },\n });\n child = started;\n started.once(\"exit\", (code) => {\n // An exit asked for is the restart's or the stop's business.\n if (child !== started) return;\n child = null;\n state = \"down\";\n log(`the app server stopped (exit ${code}); save a fix to restart it`);\n });\n await listening(port, () => child !== started);\n if (child === started && !stopped) state = \"running\";\n },\n async stop() {\n stopped = true;\n state = \"stopped\";\n await end();\n process.off(\"exit\", kill);\n },\n get state() {\n return state;\n },\n };\n}\n\n/** Resolves once something accepts connections on the port, or `abandon` says to give up. */\nasync function listening(port: number, abandon: () => boolean): Promise<void> {\n while (!abandon()) {\n const up = await new Promise<boolean>((done) => {\n const socket = net.connect(port, \"127.0.0.1\");\n socket.once(\"connect\", () => {\n socket.end();\n done(true);\n });\n socket.once(\"error\", () => done(false));\n });\n if (up) return;\n await new Promise((wait) => setTimeout(wait, 50));\n }\n}\n\n// --- the port the browser uses ---------------------------------------------\n\nexport interface DevProxyOptions {\n /** The port the browser uses. */\n port: number;\n /** The host to listen on, `127.0.0.1` when absent. */\n host?: string;\n /** Where requests go on to: the application's server. */\n target: number;\n /** Requests wait at it while it is closed. */\n gate: Gate;\n /** True while the application's server is down, which answers 502. */\n isDown: () => boolean;\n}\n\n/** Said to the browser while the application's server is down. */\nexport const DOWN_MESSAGE = \"The app server is down; see the dev console.\";\n\n/**\n * Listen on the browser's port, and pass every request through the gate to\n * the application's server, method, path, headers and body as they came.\n * Resolves once listening; rejects with a plain message when the port is in\n * use.\n */\nexport function devProxy(options: DevProxyOptions): Promise<http.Server> {\n const { port, host = \"127.0.0.1\", target, gate, isDown } = options;\n const server = http.createServer(async (req, res) => {\n await gate.wait();\n if (isDown()) {\n res.writeHead(502, { \"content-type\": \"text/plain\" }).end(DOWN_MESSAGE);\n return;\n }\n const ahead = http.request(\n {\n host: \"127.0.0.1\",\n port: target,\n method: req.method,\n path: req.url,\n headers: req.headers,\n },\n (answer) => {\n res.writeHead(answer.statusCode ?? 502, answer.headers);\n answer.pipe(res);\n },\n );\n ahead.on(\"error\", () => {\n if (!res.headersSent) res.writeHead(502, { \"content-type\": \"text/plain\" });\n res.end(DOWN_MESSAGE);\n });\n req.pipe(ahead);\n });\n return new Promise((resolve, reject) => {\n server.once(\"error\", (error: NodeJS.ErrnoException) => {\n reject(\n error.code === \"EADDRINUSE\"\n ? new Error(`port ${port} is in use; is another dev server running?`)\n : error,\n );\n });\n server.listen(port, host, () => resolve(server));\n });\n}\n\n// --- the recipe ---------------------------------------------------------------\n\nexport interface DevServerOptions {\n /** The application's server entry file, such as `server.ts`. */\n server: string;\n /** The port the browser uses: `PORT` from the environment, or 8787. */\n port?: number;\n /** The host the browser's port listens on, `127.0.0.1` when absent. */\n host?: string;\n /** The port the application's server gets, `port + 10000` when absent. */\n appPort?: number;\n /** The environment variable the application's server reads its port from, `PORT` when absent. */\n portEnv?: string;\n /** Node's arguments before the entry, `--env-file-if-exists=.env` when absent. */\n node?: string[];\n /** The builder's source tree, `src` when absent. */\n src?: string;\n /** The builder's output, `dist` when absent. */\n out?: string;\n /** Where progress goes, the console when absent. */\n log?: (line: string) => void;\n}\n\nexport interface DevServer {\n /** The port the browser uses. */\n port: number;\n /** Stop everything: the watch, the application's server and the port. */\n close(): Promise<void>;\n}\n\n/**\n * Whether a build's changes need the server restarted: a module the server\n * loads. Markup, stylesheets and browser code are read from disk per\n * request, or bundled into a file that is.\n */\nfunction serverSide(file: string): boolean {\n return file.endsWith(\".ts\") && !file.endsWith(\".browser.ts\");\n}\n\n/**\n * The whole dev server: watch and rebuild, restart the application's\n * server when a server-side file changed, and hold every request while\n * either is under way. Stops everything on Ctrl-C or a kill.\n */\nexport async function devServer(options: DevServerOptions): Promise<DevServer> {\n const port = options.port ?? Number(process.env.PORT || 8787);\n const appPort = options.appPort ?? port + 10000;\n const out = options.out ?? \"dist\";\n const log =\n options.log ?? ((line: string) => console.log(`loadbare-dev: ${line}`));\n const entry = path.resolve(options.server);\n const pagesFile = path.resolve(out, \"pages.ts\");\n const readPages = () => readFile(pagesFile, \"utf-8\").catch(() => null);\n\n const held = gate();\n held.close(\"build\");\n\n const app = appServer({\n entry,\n port: appPort,\n portEnv: options.portEnv,\n node: options.node,\n log,\n });\n // One restart at a time, in the order asked for: a second asked for while\n // one runs follows it, rather than killing the server it is starting.\n let restarts = Promise.resolve();\n const restart = (): Promise<void> => {\n held.close(\"server\");\n restarts = restarts\n .then(() => app.restart())\n .finally(() => held.open(\"server\"));\n return restarts;\n };\n\n // Started first, so a busy port is reported before anything else runs.\n const proxy = await devProxy({\n port,\n host: options.host,\n target: appPort,\n gate: held,\n isDown: () => app.state === \"down\",\n });\n\n let pages: string | null = null;\n const watcher = watchApp({\n src: options.src,\n out,\n onStart: () => held.close(\"build\"),\n onBuilt: (outcome: BuildOutcome) => {\n if (outcome.ok) log(outcome.built.summary);\n else log(`build failed, serving the last good build: ${outcome.error.message}`);\n void (async () => {\n // The registry the server imports names the page modules, so a page\n // gained or lost changes it without any server-side file changing.\n const now = await readPages();\n const restartNeeded =\n app.state !== \"running\" ||\n now !== pages ||\n outcome.changed.some(serverSide);\n pages = now;\n // The first build's server is started once the watch is ready.\n if (restartNeeded && outcome.changed.length > 0) await restart();\n held.open(\"build\");\n })();\n },\n });\n\n // The entry's directory, not the file: an editor that saves by replacing\n // the file would leave a watcher on the one it replaced.\n const entryWatcher = watch(path.dirname(entry), (_event, file) => {\n if (file === path.basename(entry)) void restart();\n });\n\n await watcher.ready;\n pages = await readPages();\n await restart();\n held.open(\"build\");\n log(`http://${options.host ?? \"127.0.0.1\"}:${port}, app server on ${appPort}`);\n\n const close = async () => {\n watcher.close();\n entryWatcher.close();\n await app.stop();\n // A browser holds its connection open between requests, and close()\n // waits for every open connection to end.\n proxy.closeAllConnections();\n await new Promise((done) => proxy.close(done));\n process.off(\"SIGINT\", onSignal);\n process.off(\"SIGTERM\", onSignal);\n };\n const onSignal = () => void close().then(() => process.exit(0));\n process.on(\"SIGINT\", onSignal);\n process.on(\"SIGTERM\", onSignal);\n\n return { port, close };\n}\n"]}
|
package/dist/hub/lb-apply.d.ts
CHANGED
|
@@ -92,6 +92,13 @@ export interface RowsOptions {
|
|
|
92
92
|
* real before, so what its rows show cannot be trusted.
|
|
93
93
|
*/
|
|
94
94
|
refill?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* The document the page is in, where new rows are built. An element
|
|
97
|
+
* `lb-show` holds absent is in its template's document, where nothing
|
|
98
|
+
* upgrades, so its own would build rows whose controls are filled as if
|
|
99
|
+
* they were not ones.
|
|
100
|
+
*/
|
|
101
|
+
doc?: Document;
|
|
95
102
|
}
|
|
96
103
|
/**
|
|
97
104
|
* Land rows through a row template.
|
|
@@ -136,8 +143,9 @@ export interface LandOptions {
|
|
|
136
143
|
/** The items answer a request from this page. */
|
|
137
144
|
requested?: boolean;
|
|
138
145
|
/**
|
|
139
|
-
* The element that sent the request
|
|
140
|
-
* keeps its rows where
|
|
146
|
+
* The element that sent the request, or the live row a form that sent it
|
|
147
|
+
* is pointed at. The list it was sent from inside keeps its rows where
|
|
148
|
+
* they stand.
|
|
141
149
|
*/
|
|
142
150
|
origin?: Element;
|
|
143
151
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"AAoFA,OAAO,EASL,KAAK,WAAW,EAKhB,KAAK,YAAY,EACjB,KAAK,GAAG,EACR,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AA0B7B,mEAAmE;AACnE,MAAM,WAAW,OAAQ,SAAQ,OAAO;IACtC,KAAK,EAAE,MAAM,CAAC;CACf;AA4BD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAE5C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAGrD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,EAAE,IAAI,OAAO,CAIpD;AAUD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,OAAO,GACV,IAAI,CAMN;AA0FD;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,CAMjE;AA0CD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAGtD;AAsMD;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,GAAG,EACR,IAAI,UAAQ,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CAIN;AAqED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,GAAG,CAU3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,mBAAmB,GAAG,IAAI,CAKlE;AAQD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAS3C;AAyoBD,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,mDAAmD;IACnD,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"lb-apply.d.ts","sourceRoot":"","sources":["../../hub/lb-apply.ts"],"names":[],"mappings":"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"}
|
package/dist/hub/lb-apply.js
CHANGED
|
@@ -1139,7 +1139,7 @@ export function applyRows(el, template, keyColumn, result, options = {}) {
|
|
|
1139
1139
|
const list = listFor(el, template);
|
|
1140
1140
|
if (!list)
|
|
1141
1141
|
return;
|
|
1142
|
-
const doc = el.ownerDocument;
|
|
1142
|
+
const doc = options.doc ?? el.ownerDocument;
|
|
1143
1143
|
const host = el;
|
|
1144
1144
|
const whole = !isPatch(result);
|
|
1145
1145
|
const shown = rowsUnder(list.root);
|
|
@@ -1394,7 +1394,12 @@ const kept = new WeakMap();
|
|
|
1394
1394
|
function keptFor(root) {
|
|
1395
1395
|
let found = kept.get(root);
|
|
1396
1396
|
if (!found) {
|
|
1397
|
-
|
|
1397
|
+
const node = root;
|
|
1398
|
+
found = {
|
|
1399
|
+
answers: new Map(),
|
|
1400
|
+
landed: new WeakSet(),
|
|
1401
|
+
doc: node.ownerDocument ?? node,
|
|
1402
|
+
};
|
|
1398
1403
|
kept.set(root, found);
|
|
1399
1404
|
}
|
|
1400
1405
|
return found;
|
|
@@ -1414,6 +1419,7 @@ function rowsOptions(state, item) {
|
|
|
1414
1419
|
order: declared.order,
|
|
1415
1420
|
types: declared.types,
|
|
1416
1421
|
choices: state.answers.get(CHOICES_QUERY)?.row ?? undefined,
|
|
1422
|
+
doc: state.doc,
|
|
1417
1423
|
};
|
|
1418
1424
|
}
|
|
1419
1425
|
/**
|