@plitzi/sdk-server 0.38.6 → 0.38.8
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/CHANGELOG.md +209 -0
- package/README.md +102 -16
- package/dist/adapters/jsonAdapters.d.ts +8 -4
- package/dist/adapters/jsonAdapters.js +5 -9
- package/dist/core/auth/api.d.ts +4 -1
- package/dist/core/auth/api.js +14 -5
- package/dist/core/auth/createAuth.d.ts +7 -1
- package/dist/core/auth/createAuth.js +14 -0
- package/dist/core/auth/credentials.d.ts +2 -0
- package/dist/core/auth/credentials.js +4 -2
- package/dist/core/auth/session.d.ts +2 -0
- package/dist/core/auth/session.js +6 -5
- package/dist/core/createServer.js +25 -4
- package/dist/core/http/stages/devReload.d.ts +2 -2
- package/dist/core/http/stages/devReload.js +2 -2
- package/dist/env.d.ts +1 -0
- package/dist/env.js +19 -0
- package/dist/env.test.d.ts +1 -0
- package/dist/functions-api.d.ts +5 -0
- package/dist/helpers/buildServerInfo.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/modules/actions/runtime/projectData.d.ts +18 -0
- package/dist/modules/actions/runtime/projectData.js +69 -0
- package/dist/modules/actions/runtime/taskContext.d.ts +1 -1
- package/dist/modules/actions/runtime/taskContext.js +6 -3
- package/dist/modules/actions/types.d.ts +7 -1
- package/dist/modules/functions/build.js +10 -0
- package/dist/modules/functions/capabilities.js +5 -0
- package/dist/modules/functions/context.js +10 -2
- package/dist/modules/functions/contract.d.ts +5 -0
- package/dist/modules/functions/driver.js +4 -0
- package/dist/modules/functions/local.d.ts +3 -1
- package/dist/modules/functions/local.js +4 -2
- package/dist/modules/functions/protocol.d.ts +3 -0
- package/dist/modules/functions/scope.d.ts +1 -1
- package/dist/modules/images/imageProxy.js +1 -1
- package/dist/modules/mysql/accounts.js +1 -5
- package/dist/modules/project/plugins.d.ts +31 -0
- package/dist/modules/project/plugins.js +162 -0
- package/dist/modules/project/serveProject.d.ts +78 -0
- package/dist/modules/project/serveProject.js +200 -0
- package/dist/modules/project/serveProject.test.d.ts +1 -0
- package/dist/modules/project/watchSpace.d.ts +14 -0
- package/dist/modules/project/watchSpace.js +77 -0
- package/dist/modules/realtime/failure.d.ts +6 -0
- package/dist/modules/realtime/failure.js +11 -0
- package/dist/modules/realtime/handlers.js +13 -3
- package/dist/modules/realtime/hub.js +4 -1
- package/dist/modules/realtime/socket.js +3 -2
- package/dist/modules/rsc/connectorRscData.d.ts +7 -3
- package/dist/modules/rsc/connectorRscData.js +3 -4
- package/dist/modules/rsc/handler.d.ts +1 -0
- package/dist/modules/rsc/handler.js +11 -1
- package/dist/modules/rsc/publicFileResolver.d.ts +4 -8
- package/dist/modules/rsc/publicFileResolver.js +6 -10
- package/dist/modules/runtime/bundle.d.ts +1 -1
- package/dist/modules/runtime/bundle.js +2 -2
- package/dist/modules/ssr/prepareRender.js +10 -2
- package/dist/modules/ssr/registerExternalPlugins.js +1 -1
- package/dist/modules/ssr/vendor.test.d.ts +1 -0
- package/dist/modules/ssr/views/template.ejs +7 -3
- package/dist/plugins/compile.js +1 -1
- package/dist/plugins/copy.js +1 -1
- package/dist/plugins/manager.js +1 -1
- package/dist/project.d.ts +9 -0
- package/dist/project.js +2 -0
- package/package.json +16 -4
- package/skills/plitzi-authoring/CHEATSHEET.md +2 -2
- package/skills/plitzi-authoring/SKILL.md +34 -148
- package/skills/plitzi-authoring/recipes/plugin.ts +1 -1
- package/skills/plitzi-authoring/reference/accessibility.md +3 -4
- package/skills/plitzi-authoring/reference/auth.md +75 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +19 -4
- package/skills/plitzi-authoring/reference/colours-and-motion.md +3 -2
- package/skills/plitzi-authoring/reference/drawing.md +1 -1
- package/skills/plitzi-authoring/reference/efficiency.md +5 -2
- package/skills/plitzi-authoring/reference/elements-and-styles.md +23 -23
- package/skills/plitzi-authoring/reference/flows.md +15 -8
- package/skills/plitzi-authoring/reference/index.md +33 -0
- package/skills/plitzi-authoring/reference/layouts.md +15 -18
- package/skills/plitzi-authoring/reference/plugins.md +3 -3
- package/skills/plitzi-authoring/reference/recipes.md +25 -0
- package/skills/plitzi-authoring/reference/review-checklist.md +2 -1
- package/skills/plitzi-authoring/reference/rules.md +61 -0
- package/skills/plitzi-authoring/reference/snippets-and-export.md +2 -2
- package/skills/plitzi-authoring/reference/structure.md +4 -3
- package/skills/plitzi-authoring/reference/testing.md +1 -1
- package/skills/plitzi-authoring/reference/validation.md +1 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { loadFunctions, loadFunctionsSource } from "../functions/load.js";
|
|
2
|
+
import { PLUGIN_FUNCTIONS_SOURCE } from "@plitzi/sdk-shared/actions";
|
|
3
|
+
import { isRecord } from "@plitzi/sdk-shared/helpers/isRecord";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, watch } from "node:fs";
|
|
6
|
+
import { checkPluginFolder, layoutFindingText, pluginEntry, pluginTypeOf, readVendorPlugin } from "@plitzi/sdk-shared/project/layout";
|
|
7
|
+
import { PLUGINS_DIR, PLUGIN_FUNCTIONS_DIR, VENDOR_PLUGINS_DIR } from "@plitzi/sdk-shared/project/paths";
|
|
8
|
+
//#region src/modules/project/plugins.ts
|
|
9
|
+
/**
|
|
10
|
+
* `action: 'compile'` is what makes a folder's plugin SERVER-rendered: the server builds the entry with esbuild, keeps
|
|
11
|
+
* React external so the plugin runs on the one copy the page already has, serves the bundle to the browser AND
|
|
12
|
+
* imports it into the render — so the component's markup is in the HTML before any JavaScript arrives.
|
|
13
|
+
*/
|
|
14
|
+
var folderSource = (entry) => ({
|
|
15
|
+
js: entry,
|
|
16
|
+
action: "compile",
|
|
17
|
+
version: "1.0.0"
|
|
18
|
+
});
|
|
19
|
+
var foldersOf = (dir) => existsSync(dir) ? readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort() : [];
|
|
20
|
+
/** Every folder of `src/plugins` the server can build, by the plugin it is: what it registers. */
|
|
21
|
+
var pluginFolders = (root) => {
|
|
22
|
+
const found = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const folder of foldersOf(path.join(root, PLUGINS_DIR))) {
|
|
24
|
+
const entry = pluginEntry(root, folder);
|
|
25
|
+
if (entry && !found.has(pluginTypeOf(folder))) found.set(pluginTypeOf(folder), {
|
|
26
|
+
folder,
|
|
27
|
+
entry
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return found;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* A plugin's server half: its `functions/` folder (`src/plugins/Board/functions/index.ts`), written like the project's
|
|
34
|
+
* own `src/functions/` and loaded with what a plugin may reach — its own corner of `kv`, its routes under
|
|
35
|
+
* `/fn/plugins/<name>/`, its tasks named `<name>.<action>`. A folder without one is a component and nothing else; one
|
|
36
|
+
* with code and no `index.ts` is the layout check's to say (`plugin-functions-entry-missing`).
|
|
37
|
+
*/
|
|
38
|
+
var folderFunctions = async (root, folder) => {
|
|
39
|
+
const functions = path.join(root, PLUGINS_DIR, folder, PLUGIN_FUNCTIONS_DIR);
|
|
40
|
+
return existsSync(path.join(functions, "index.ts")) ? (await loadFunctions(functions)).at(0) : void 0;
|
|
41
|
+
};
|
|
42
|
+
/** A plugin of `vendor/plugins`, from the manifest `plitzi plugin pack` wrote beside its bundle when it was published. */
|
|
43
|
+
var builtPlugin = (root, type) => {
|
|
44
|
+
const read = readVendorPlugin(root, type);
|
|
45
|
+
if ("problem" in read) throw new Error(read.problem.message);
|
|
46
|
+
return {
|
|
47
|
+
type,
|
|
48
|
+
source: {
|
|
49
|
+
js: read.script,
|
|
50
|
+
...read.style ? { css: read.style } : {},
|
|
51
|
+
action: "copy",
|
|
52
|
+
version: read.version
|
|
53
|
+
},
|
|
54
|
+
...read.functions ? { functions: read.functions } : {}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/** The files of a packed plugin's server half (`functions.source.json`), by their path. */
|
|
58
|
+
var carriedSource = (file) => {
|
|
59
|
+
const carried = JSON.parse(readFileSync(file, "utf-8"));
|
|
60
|
+
const files = isRecord(carried) ? Object.entries(carried).flatMap(([name, text]) => typeof text === "string" ? [[name, text]] : []) : [];
|
|
61
|
+
if (!isRecord(carried) || files.length !== Object.keys(carried).length) throw new Error(`${file} is not a plugin's server half: ${PLUGIN_FUNCTIONS_SOURCE} maps each file's path to its text.`);
|
|
62
|
+
return Object.fromEntries(files);
|
|
63
|
+
};
|
|
64
|
+
/** Every plugin of the project at `root`, as its server registers them at boot. */
|
|
65
|
+
var projectPlugins = async (root) => {
|
|
66
|
+
const folders = pluginFolders(root);
|
|
67
|
+
const sources = Object.fromEntries([...folders].map(([name, { entry }]) => [name, folderSource(entry)]));
|
|
68
|
+
const functions = {};
|
|
69
|
+
for (const [name, { folder }] of folders) {
|
|
70
|
+
const definition = await folderFunctions(root, folder);
|
|
71
|
+
if (definition) functions[name] = definition;
|
|
72
|
+
}
|
|
73
|
+
const built = foldersOf(path.join(root, VENDOR_PLUGINS_DIR)).map((type) => builtPlugin(root, type));
|
|
74
|
+
for (const { type, source, functions: carried } of built) {
|
|
75
|
+
sources[type] = source;
|
|
76
|
+
if (carried) {
|
|
77
|
+
const definition = (await loadFunctionsSource(carriedSource(carried))).at(0);
|
|
78
|
+
if (definition) functions[type] = definition;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
sources,
|
|
83
|
+
names: Object.keys(sources),
|
|
84
|
+
functions,
|
|
85
|
+
fromFolders: new Set(folders.keys())
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* A plugin edited while developing is built again by the server, and the open pages swap it where it is drawn — the
|
|
90
|
+
* rest of the page, its state included, stays as it was (`devReload`). Its server half (`functions/`) is loaded again
|
|
91
|
+
* here, in place. A plugin ADDED — a new folder with an `index.ts`, what `plitzi plugin add` writes — is registered
|
|
92
|
+
* here, and the pages load again to render it; one removed is turned off. A folder the server cannot build, or whose
|
|
93
|
+
* server half has no entry, is said in the terminal as the layout check says it at boot — once, until it changes — and
|
|
94
|
+
* the server goes on. Answers what stops watching.
|
|
95
|
+
*/
|
|
96
|
+
var watchProjectPlugins = (root, server, plugins) => {
|
|
97
|
+
const dir = path.join(root, PLUGINS_DIR);
|
|
98
|
+
if (!existsSync(dir)) return () => void 0;
|
|
99
|
+
const { names, fromFolders } = plugins;
|
|
100
|
+
let timer;
|
|
101
|
+
const reloading = /* @__PURE__ */ new Map();
|
|
102
|
+
/** What was said of each folder last — at boot, by the layout check — so a save says only what changed. */
|
|
103
|
+
const said = new Map(foldersOf(dir).map((folder) => [folder, checkPluginFolder(root, folder).map(layoutFindingText).join("\n")]));
|
|
104
|
+
const sayOf = (folder) => {
|
|
105
|
+
const findings = checkPluginFolder(root, folder);
|
|
106
|
+
const text = findings.map(layoutFindingText).join("\n");
|
|
107
|
+
if (text === (said.get(folder) ?? "")) return;
|
|
108
|
+
said.set(folder, text);
|
|
109
|
+
for (const finding of findings) {
|
|
110
|
+
const line = `[plugins] ${layoutFindingText(finding)}`;
|
|
111
|
+
if (finding.level === "error") console.error(line);
|
|
112
|
+
else console.warn(line);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const reloadFunctions = (folder) => {
|
|
116
|
+
const name = pluginTypeOf(folder);
|
|
117
|
+
sayOf(folder);
|
|
118
|
+
folderFunctions(root, folder).then((definition) => {
|
|
119
|
+
server.functions.setPlugin(name, definition);
|
|
120
|
+
if (definition) console.log(`[plugins] ${name}: its functions, loaded again`);
|
|
121
|
+
}).catch((error) => {
|
|
122
|
+
console.error(`[plugins] ${name}: its functions were not loaded again:`, error instanceof Error ? error.message : error);
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const sync = () => {
|
|
126
|
+
const folders = foldersOf(dir);
|
|
127
|
+
folders.forEach(sayOf);
|
|
128
|
+
for (const folder of [...said.keys()].filter((each) => !folders.includes(each))) said.delete(folder);
|
|
129
|
+
const present = pluginFolders(root);
|
|
130
|
+
const added = [...present].filter(([name]) => !fromFolders.has(name));
|
|
131
|
+
const removed = [...fromFolders].filter((name) => !present.has(name));
|
|
132
|
+
for (const [name, { folder, entry }] of added) {
|
|
133
|
+
server.plugins.register(name, folderSource(entry));
|
|
134
|
+
fromFolders.add(name);
|
|
135
|
+
names.push(name);
|
|
136
|
+
reloadFunctions(folder);
|
|
137
|
+
}
|
|
138
|
+
for (const name of removed) {
|
|
139
|
+
fromFolders.delete(name);
|
|
140
|
+
names.splice(names.indexOf(name), 1);
|
|
141
|
+
server.functions.setPlugin(name, void 0);
|
|
142
|
+
}
|
|
143
|
+
if (added.length > 0 || removed.length > 0) server.reloadPages();
|
|
144
|
+
};
|
|
145
|
+
const watcher = watch(dir, { recursive: true }, (_event, file) => {
|
|
146
|
+
const [folder, part] = (file ?? "").split(path.sep);
|
|
147
|
+
if (folder && part === PLUGIN_FUNCTIONS_DIR && fromFolders.has(pluginTypeOf(folder))) {
|
|
148
|
+
clearTimeout(reloading.get(folder));
|
|
149
|
+
reloading.set(folder, setTimeout(() => reloadFunctions(folder), 200));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
timer = setTimeout(sync, 300);
|
|
154
|
+
});
|
|
155
|
+
return () => {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
reloading.forEach(clearTimeout);
|
|
158
|
+
watcher.close();
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { projectPlugins, watchProjectPlugins };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AuthoredDocuments } from './watchSpace';
|
|
2
|
+
import { ServerConfig } from '../../core/createServer';
|
|
3
|
+
import { ConnectorManifest } from '../connectors';
|
|
4
|
+
import { ActionEntry, SSRServer } from '@plitzi/sdk-shared';
|
|
5
|
+
/** What `serveProject` sets itself, from the project's layout — so never the project's options'. */
|
|
6
|
+
type SetByProject = 'port' | 'devMode' | 'devReload' | 'health' | 'adapters' | 'plugins' | 'publicDir' | 'dataDir' | 'functions' | 'action';
|
|
7
|
+
/**
|
|
8
|
+
* What a project's server does besides serving its space — its `src/config/serverOptions.ts`: what `createServer`
|
|
9
|
+
* takes, but for what `serveProject` wires from the project's layout, and an action's `lookups`, which are the
|
|
10
|
+
* project's `src/actions/`. `logLevel` and `logger` are the project's to change; `action.kv` too.
|
|
11
|
+
*/
|
|
12
|
+
export type ProjectServerOptions = Partial<Omit<ServerConfig, SetByProject>> & {
|
|
13
|
+
action?: Omit<NonNullable<ServerConfig['action']>, 'lookups'>;
|
|
14
|
+
};
|
|
15
|
+
/** A space authored in the project — `authorProjectSpace` of `@plitzi/sdk-authoring/node` — and what it warned of. */
|
|
16
|
+
export type ProjectSpace = AuthoredDocuments & {
|
|
17
|
+
warnings?: readonly {
|
|
18
|
+
message: string;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
type ServeProjectBase = {
|
|
22
|
+
/** The space's server actions (`src/actions/`), found by their id. Left out, the server runs none. */
|
|
23
|
+
actions?: readonly ActionEntry[];
|
|
24
|
+
/** The connectors those actions call, by id. */
|
|
25
|
+
connectors?: ReadonlyMap<string, ConnectorManifest>;
|
|
26
|
+
/** `src/config/serverOptions.ts`: spread before what `serveProject` wires, which wins where both say something. */
|
|
27
|
+
serverOptions?: ProjectServerOptions;
|
|
28
|
+
};
|
|
29
|
+
export type ServeProjectOptions = ServeProjectBase & ({
|
|
30
|
+
/**
|
|
31
|
+
* The space, held in the project: authored once its layout is checked — `authorProjectSpace` of
|
|
32
|
+
* `@plitzi/sdk-authoring/node` — served from memory, and, while developing, authored again on a save.
|
|
33
|
+
*/
|
|
34
|
+
space: () => ProjectSpace | Promise<ProjectSpace>;
|
|
35
|
+
cloud?: never;
|
|
36
|
+
} | {
|
|
37
|
+
/**
|
|
38
|
+
* The space stays in Plitzi, read with `PLITZI_HOST_KEY` (and `PLITZI_ENVIRONMENT`, `PLITZI_REVISION`,
|
|
39
|
+
* `PLITZI_SERVER_URL`): `name` is what `/health` answers with — no space document says it here.
|
|
40
|
+
*/
|
|
41
|
+
cloud: {
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
44
|
+
space?: never;
|
|
45
|
+
});
|
|
46
|
+
/** The project's server, running. */
|
|
47
|
+
export type ServedProject = {
|
|
48
|
+
server: SSRServer;
|
|
49
|
+
/** Where it listens, as `tmp/dev-server.json` records it: `http://127.0.0.1:<port>`. */
|
|
50
|
+
url: string;
|
|
51
|
+
/** Stops watching, closes the server and the runtime — what a signal does, for a caller that stops it itself. */
|
|
52
|
+
close: () => Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The server of a project `@plitzi/cli` writes — its `src/main.ts` hands it the space, its actions and its options,
|
|
56
|
+
* and everything else comes from where the project keeps it. The project is the working directory, where its scripts
|
|
57
|
+
* start it (`projectRoot`: refused, saying what is missing, anywhere else), laid out where the server reads each part
|
|
58
|
+
* (`assertProjectLayout`: refused with every error at once; its warnings said while developing), and only then is its
|
|
59
|
+
* space authored. A refusal of any of the three is printed as it is — the message is the whole report, every problem
|
|
60
|
+
* and its fix, and a stack would only point inside the SDK — and the process exits with 1. What it serves:
|
|
61
|
+
*
|
|
62
|
+
* - the port: `PORT`, or 8080 — the next free one from there while developing; `HOST`, loopback by default;
|
|
63
|
+
* - its plugins: every folder of `src/plugins` built from its source and server-rendered, every one of `vendor/plugins`
|
|
64
|
+
* as it was built, each with its server half;
|
|
65
|
+
* - its code: `src/functions/`, and the space's runtime (`src/runtime/` — compiled under `dist/` when the server runs
|
|
66
|
+
* compiled, `projectModule` — or `vendor/runtime.bundle`) in this process;
|
|
67
|
+
* - `public/` served as it is, `src/data/` read and never served, `kv` kept in `state/kv.json`;
|
|
68
|
+
* - `/health` answering with the space's permanent URL (or the cloud project's name), and the port it took written to
|
|
69
|
+
* `tmp/dev-server.json` for `check`, `shot` and `visual` to find;
|
|
70
|
+
* - a signal closes it, finishing what runs.
|
|
71
|
+
*
|
|
72
|
+
* While developing (`NODE_ENV` other than `production`), a save to the space is authored again by the project's
|
|
73
|
+
* `plitzi/author.ts`, in a process of its own, which hands the documents back over IPC: the server swaps them in memory
|
|
74
|
+
* and every open page loads again. A plugin folder added is registered, one removed turned off, and a plugin's
|
|
75
|
+
* `functions/` loaded again; a folder it cannot build is said in the terminal, and the server goes on.
|
|
76
|
+
*/
|
|
77
|
+
export declare const serveProject: (options: ServeProjectOptions) => Promise<ServedProject>;
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { consoleLogger } from "../../helpers/serverLog.js";
|
|
2
|
+
import { isFleetWorker } from "../../core/server/fleet/role.js";
|
|
3
|
+
import { loadFunctions } from "../functions/load.js";
|
|
4
|
+
import { createServer } from "../../core/createServer.js";
|
|
5
|
+
import { closeOnSignals } from "../../core/server/closeOnSignals.js";
|
|
6
|
+
import { freePort } from "../../core/freePort.js";
|
|
7
|
+
import { createCloudAdapters } from "../../adapters/cloudAdapters.js";
|
|
8
|
+
import { createJsonAdapters } from "../../adapters/jsonAdapters.js";
|
|
9
|
+
import { createFileKv } from "../actions/runtime/fileKv.js";
|
|
10
|
+
import { loadRuntime, loadRuntimeModule } from "../runtime/bundle.js";
|
|
11
|
+
import { serveRuntime } from "../runtime/stages.js";
|
|
12
|
+
import { projectPlugins, watchProjectPlugins } from "./plugins.js";
|
|
13
|
+
import { watchSpace } from "./watchSpace.js";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { assertProjectLayout, layoutFindingText } from "@plitzi/sdk-shared/project/layout";
|
|
17
|
+
import { DATA_DIR, DEV_SERVER_FILE, FUNCTIONS_DIR, KV_FILE, PROJECT_TMP, PUBLIC_DIR, RUNTIME_BUNDLE, RUNTIME_ENTRY } from "@plitzi/sdk-shared/project/paths";
|
|
18
|
+
import { projectModule, projectRoot } from "@plitzi/sdk-shared/project/root";
|
|
19
|
+
//#region src/modules/project/serveProject.ts
|
|
20
|
+
/**
|
|
21
|
+
* The errors whose message is the whole report — every problem, where it is and what to write instead — and whose
|
|
22
|
+
* stack would only point inside the SDK: the project's root, its layout, a space module with no space, a space that does
|
|
23
|
+
* not author. Told by name, since each package that throws one carries its own copy of the class.
|
|
24
|
+
*/
|
|
25
|
+
var REPORTS = /* @__PURE__ */ new Set([
|
|
26
|
+
"ProjectRootError",
|
|
27
|
+
"ProjectLayoutError",
|
|
28
|
+
"ProjectSpaceError",
|
|
29
|
+
"SpaceRefusedError"
|
|
30
|
+
]);
|
|
31
|
+
var isReport = (error) => error instanceof Error && REPORTS.has(error.name);
|
|
32
|
+
var ENVIRONMENTS = [
|
|
33
|
+
"main",
|
|
34
|
+
"production",
|
|
35
|
+
"staging",
|
|
36
|
+
"development"
|
|
37
|
+
];
|
|
38
|
+
var isEnvironment = (value) => ENVIRONMENTS.some((environment) => environment === value);
|
|
39
|
+
/** The one space a project's server serves, which is the one it runs the actions on a clock for. */
|
|
40
|
+
var SPACE_ID = 1;
|
|
41
|
+
/**
|
|
42
|
+
* The space's HOST key — not the public one a published page embeds. They are different credentials on purpose: the
|
|
43
|
+
* public `render` key is readable by anyone who views source, and what keeps a copied one from working is that a
|
|
44
|
+
* browser states the origin it presents from. A server has no such statement to make, so it gets a key whose
|
|
45
|
+
* protection is that it is secret: issued once, never committed, never shipped in a page, revocable on its own.
|
|
46
|
+
*/
|
|
47
|
+
var cloudAdapters = (pluginNames) => {
|
|
48
|
+
const webKey = process.env.PLITZI_HOST_KEY ?? "";
|
|
49
|
+
if (!webKey) throw new Error("Set PLITZI_HOST_KEY in .env — Credentials, in the builder.");
|
|
50
|
+
const environment = process.env.PLITZI_ENVIRONMENT ?? "main";
|
|
51
|
+
if (!isEnvironment(environment)) throw new Error(`PLITZI_ENVIRONMENT is "${environment}": one of ${ENVIRONMENTS.join(", ")}.`);
|
|
52
|
+
return createCloudAdapters({
|
|
53
|
+
webKey,
|
|
54
|
+
...process.env.PLITZI_SERVER_URL ? { serverUrl: process.env.PLITZI_SERVER_URL } : {},
|
|
55
|
+
environment,
|
|
56
|
+
...process.env.PLITZI_REVISION ? { revision: Number(process.env.PLITZI_REVISION) } : {},
|
|
57
|
+
deployment: { pluginNames }
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
var actionLookups = (actions, connectors) => ({
|
|
61
|
+
getAction: (_spaceId, actionId) => Promise.resolve(actions.find((entry) => entry.id === actionId)),
|
|
62
|
+
listActions: () => Promise.resolve([...actions]),
|
|
63
|
+
getConnector: (_spaceId, connectorId) => Promise.resolve(connectors.get(connectorId)),
|
|
64
|
+
listScheduledSpaces: () => Promise.resolve([SPACE_ID])
|
|
65
|
+
});
|
|
66
|
+
/** The space this project serves, authored from its own source — or Plitzi's, named for `/health`. */
|
|
67
|
+
var spaceToServe = async ({ space, cloud }) => {
|
|
68
|
+
if (!space) return { name: cloud.name };
|
|
69
|
+
const authored = await space();
|
|
70
|
+
return {
|
|
71
|
+
space: authored,
|
|
72
|
+
name: authored.schema.definition.permanentUrl
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
var startProject = async (options) => {
|
|
76
|
+
const { actions, connectors = /* @__PURE__ */ new Map(), serverOptions = {} } = options;
|
|
77
|
+
const root = projectRoot();
|
|
78
|
+
const developing = process.env.NODE_ENV !== "production";
|
|
79
|
+
const layout = assertProjectLayout(root, {
|
|
80
|
+
mode: "server",
|
|
81
|
+
space: options.space ? "local" : "cloud"
|
|
82
|
+
});
|
|
83
|
+
if (developing) for (const warning of layout) console.warn(`[layout] ${layoutFindingText(warning)}`);
|
|
84
|
+
const host = process.env.HOST ?? "127.0.0.1";
|
|
85
|
+
const port = process.env.PORT ? Number(process.env.PORT) : developing ? await freePort(8080, host) : 8080;
|
|
86
|
+
/** Where people reach this server: `PUBLIC_URL` behind a proxy, its own address otherwise. */
|
|
87
|
+
const publicUrl = (process.env.PUBLIC_URL ?? `http://127.0.0.1:${String(port)}`).replace(/\/+$/, "");
|
|
88
|
+
const { space, name } = await spaceToServe(options);
|
|
89
|
+
const plugins = await projectPlugins(root);
|
|
90
|
+
const held = space && { documents: {
|
|
91
|
+
schema: space.schema,
|
|
92
|
+
style: space.style
|
|
93
|
+
} };
|
|
94
|
+
const speaks = !isFleetWorker();
|
|
95
|
+
for (const warning of speaks ? space?.warnings ?? [] : []) console.warn(`[author] ${warning.message}`);
|
|
96
|
+
const adapters = held ? createJsonAdapters({
|
|
97
|
+
offlineData: () => held.documents,
|
|
98
|
+
deployment: {
|
|
99
|
+
spaceId: SPACE_ID,
|
|
100
|
+
environment: "main",
|
|
101
|
+
revision: 0,
|
|
102
|
+
pluginNames: plugins.names
|
|
103
|
+
}
|
|
104
|
+
}) : cloudAdapters(plugins.names);
|
|
105
|
+
const functionsDir = path.join(root, FUNCTIONS_DIR);
|
|
106
|
+
const functions = existsSync(functionsDir) ? await loadFunctions(functionsDir) : [];
|
|
107
|
+
const runtimeBundle = path.join(root, RUNTIME_BUNDLE);
|
|
108
|
+
const spaceRuntime = await loadRuntimeModule(projectModule(root, RUNTIME_ENTRY, process.argv[1])) ?? (existsSync(runtimeBundle) ? await loadRuntime(readFileSync(runtimeBundle), path.join(root, PROJECT_TMP, "runtime")) : void 0);
|
|
109
|
+
const runtime = spaceRuntime && await serveRuntime(spaceRuntime, {
|
|
110
|
+
env: process.env,
|
|
111
|
+
publicUrl
|
|
112
|
+
});
|
|
113
|
+
const server = createServer({
|
|
114
|
+
logLevel: process.argv.includes("--verbose") ? "info" : "warn",
|
|
115
|
+
logger: consoleLogger,
|
|
116
|
+
...serverOptions,
|
|
117
|
+
port,
|
|
118
|
+
devMode: developing,
|
|
119
|
+
devReload: developing,
|
|
120
|
+
health: { name },
|
|
121
|
+
adapters,
|
|
122
|
+
plugins: plugins.sources,
|
|
123
|
+
publicDir: path.join(root, PUBLIC_DIR),
|
|
124
|
+
dataDir: path.join(root, DATA_DIR),
|
|
125
|
+
functions: {
|
|
126
|
+
native: [...functions, ...runtime?.native ?? []],
|
|
127
|
+
plugins: plugins.functions
|
|
128
|
+
},
|
|
129
|
+
action: {
|
|
130
|
+
signingSecret: process.env.PLITZI_SIGNING_SECRET,
|
|
131
|
+
kv: createFileKv({ file: path.join(root, KV_FILE) }),
|
|
132
|
+
...serverOptions.action,
|
|
133
|
+
...actions ? { lookups: actionLookups(actions, connectors) } : {}
|
|
134
|
+
}
|
|
135
|
+
}, { preAuth: runtime ? [runtime.stage] : [] });
|
|
136
|
+
server.listen(port, host);
|
|
137
|
+
const url = `http://127.0.0.1:${String(port)}`;
|
|
138
|
+
if (speaks) {
|
|
139
|
+
mkdirSync(path.join(root, PROJECT_TMP), { recursive: true });
|
|
140
|
+
writeFileSync(path.join(root, DEV_SERVER_FILE), `${JSON.stringify({
|
|
141
|
+
name,
|
|
142
|
+
port,
|
|
143
|
+
url
|
|
144
|
+
}, null, 2)}\n`);
|
|
145
|
+
console.log(`pages on ${url}/`);
|
|
146
|
+
}
|
|
147
|
+
const watching = developing ? [...held ? [watchSpace(root, (authored) => {
|
|
148
|
+
held.documents = authored;
|
|
149
|
+
server.reloadPages();
|
|
150
|
+
})] : [], watchProjectPlugins(root, server, plugins)] : [];
|
|
151
|
+
const close = async () => {
|
|
152
|
+
watching.forEach((stop) => stop());
|
|
153
|
+
await server.close();
|
|
154
|
+
await runtime?.close();
|
|
155
|
+
};
|
|
156
|
+
const stopListening = closeOnSignals({ close });
|
|
157
|
+
return {
|
|
158
|
+
server,
|
|
159
|
+
url,
|
|
160
|
+
close: async () => {
|
|
161
|
+
stopListening();
|
|
162
|
+
await close();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* The server of a project `@plitzi/cli` writes — its `src/main.ts` hands it the space, its actions and its options,
|
|
168
|
+
* and everything else comes from where the project keeps it. The project is the working directory, where its scripts
|
|
169
|
+
* start it (`projectRoot`: refused, saying what is missing, anywhere else), laid out where the server reads each part
|
|
170
|
+
* (`assertProjectLayout`: refused with every error at once; its warnings said while developing), and only then is its
|
|
171
|
+
* space authored. A refusal of any of the three is printed as it is — the message is the whole report, every problem
|
|
172
|
+
* and its fix, and a stack would only point inside the SDK — and the process exits with 1. What it serves:
|
|
173
|
+
*
|
|
174
|
+
* - the port: `PORT`, or 8080 — the next free one from there while developing; `HOST`, loopback by default;
|
|
175
|
+
* - its plugins: every folder of `src/plugins` built from its source and server-rendered, every one of `vendor/plugins`
|
|
176
|
+
* as it was built, each with its server half;
|
|
177
|
+
* - its code: `src/functions/`, and the space's runtime (`src/runtime/` — compiled under `dist/` when the server runs
|
|
178
|
+
* compiled, `projectModule` — or `vendor/runtime.bundle`) in this process;
|
|
179
|
+
* - `public/` served as it is, `src/data/` read and never served, `kv` kept in `state/kv.json`;
|
|
180
|
+
* - `/health` answering with the space's permanent URL (or the cloud project's name), and the port it took written to
|
|
181
|
+
* `tmp/dev-server.json` for `check`, `shot` and `visual` to find;
|
|
182
|
+
* - a signal closes it, finishing what runs.
|
|
183
|
+
*
|
|
184
|
+
* While developing (`NODE_ENV` other than `production`), a save to the space is authored again by the project's
|
|
185
|
+
* `plitzi/author.ts`, in a process of its own, which hands the documents back over IPC: the server swaps them in memory
|
|
186
|
+
* and every open page loads again. A plugin folder added is registered, one removed turned off, and a plugin's
|
|
187
|
+
* `functions/` loaded again; a folder it cannot build is said in the terminal, and the server goes on.
|
|
188
|
+
*/
|
|
189
|
+
var serveProject = async (options) => {
|
|
190
|
+
try {
|
|
191
|
+
return await startProject(options);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (!isReport(error)) throw error;
|
|
194
|
+
console.error(error.message);
|
|
195
|
+
process.exitCode = 1;
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
//#endregion
|
|
200
|
+
export { serveProject };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Schema, Style } from '@plitzi/sdk-shared';
|
|
2
|
+
/** The two documents a page server renders a space from, as the author script hands them over. */
|
|
3
|
+
export type AuthoredDocuments = {
|
|
4
|
+
schema: Schema;
|
|
5
|
+
style: Style;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* A save to the space, while developing: re-authored by the project's `plitzi/author.ts` in a process of its own — the
|
|
9
|
+
* only way to read every file of it again, which an import never does twice — and handed over IPC (`--ipc`), so
|
|
10
|
+
* `onAuthored` swaps the documents the server serves. What it refuses is printed by the script, and the page keeps
|
|
11
|
+
* the last space that authored. Saves in quick succession author once; one during a run, once more after it. Answers
|
|
12
|
+
* what stops watching.
|
|
13
|
+
*/
|
|
14
|
+
export declare const watchSpace: (root: string, onAuthored: (documents: AuthoredDocuments) => void) => (() => void);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { isRecord } from "@plitzi/sdk-shared/helpers/isRecord";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { watch } from "node:fs";
|
|
4
|
+
import { AUTHOR_FILE, SOURCE_DIR } from "@plitzi/sdk-shared/project/paths";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
//#region src/modules/project/watchSpace.ts
|
|
7
|
+
/**
|
|
8
|
+
* What the author script sent: its own `{ schema, style }`, which `authorSpace` built and checked — the message is
|
|
9
|
+
* looked at only for its shape, so a stray one from a script that is not the CLI's is not served.
|
|
10
|
+
*/
|
|
11
|
+
var isAuthoredDocuments = (message) => isRecord(message) && isRecord(message.schema) && isRecord(message.style);
|
|
12
|
+
/**
|
|
13
|
+
* What of `src/` is the space's: everything but the server's own code — its entry point, the server options,
|
|
14
|
+
* actions, connectors, functions and runtime — which `start:dev` restarts the server on instead; and of a plugin, only
|
|
15
|
+
* its declaration — its component is swapped in the open pages by the server itself.
|
|
16
|
+
*/
|
|
17
|
+
var authored = (file) => file !== "main.ts" && !/^(config|actions|connectors|functions|runtime)([\\/]|$)/.test(file) && (!/^plugins([\\/]|$)/.test(file) || path.basename(file) === "declaration.ts");
|
|
18
|
+
/**
|
|
19
|
+
* A save to the space, while developing: re-authored by the project's `plitzi/author.ts` in a process of its own — the
|
|
20
|
+
* only way to read every file of it again, which an import never does twice — and handed over IPC (`--ipc`), so
|
|
21
|
+
* `onAuthored` swaps the documents the server serves. What it refuses is printed by the script, and the page keeps
|
|
22
|
+
* the last space that authored. Saves in quick succession author once; one during a run, once more after it. Answers
|
|
23
|
+
* what stops watching.
|
|
24
|
+
*/
|
|
25
|
+
var watchSpace = (root, onAuthored) => {
|
|
26
|
+
let timer;
|
|
27
|
+
let running;
|
|
28
|
+
let again = false;
|
|
29
|
+
let stopped = false;
|
|
30
|
+
const author = () => {
|
|
31
|
+
if (stopped) return;
|
|
32
|
+
if (running) {
|
|
33
|
+
again = true;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let documents;
|
|
37
|
+
const child = spawn(process.execPath, [path.join(root, AUTHOR_FILE), "--ipc"], {
|
|
38
|
+
cwd: root,
|
|
39
|
+
stdio: [
|
|
40
|
+
"inherit",
|
|
41
|
+
"inherit",
|
|
42
|
+
"inherit",
|
|
43
|
+
"ipc"
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
running = child;
|
|
47
|
+
child.on("message", (message) => {
|
|
48
|
+
if (isAuthoredDocuments(message)) documents = message;
|
|
49
|
+
});
|
|
50
|
+
child.on("error", (error) => {
|
|
51
|
+
console.error(`[author] ${AUTHOR_FILE} did not run: ${error.message}`);
|
|
52
|
+
});
|
|
53
|
+
child.on("close", (code) => {
|
|
54
|
+
running = void 0;
|
|
55
|
+
if (stopped) return;
|
|
56
|
+
if (code === 0 && documents) onAuthored(documents);
|
|
57
|
+
else if (code === 0) console.error(`[author] ${AUTHOR_FILE} authored the space and handed nothing over: it is older than this server — npx plitzi upgrade files --write`);
|
|
58
|
+
if (again) {
|
|
59
|
+
again = false;
|
|
60
|
+
author();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
const watcher = watch(path.join(root, SOURCE_DIR), { recursive: true }, (_event, file) => {
|
|
65
|
+
if (!file || !authored(file)) return;
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
timer = setTimeout(author, 100);
|
|
68
|
+
});
|
|
69
|
+
return () => {
|
|
70
|
+
stopped = true;
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
watcher.close();
|
|
73
|
+
running?.kill();
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
export { watchSpace };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a realtime promise nobody waits on does when the pub/sub under it fails — Redis timing out under load: it says so
|
|
3
|
+
* and the server goes on. Left to reject, it reached the process's `unhandledRejection` handler, which shuts the whole
|
|
4
|
+
* server down over one undelivered cursor.
|
|
5
|
+
*/
|
|
6
|
+
export declare const warnRealtime: (what: string) => (error: unknown) => void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/modules/realtime/failure.ts
|
|
2
|
+
/**
|
|
3
|
+
* What a realtime promise nobody waits on does when the pub/sub under it fails — Redis timing out under load: it says so
|
|
4
|
+
* and the server goes on. Left to reject, it reached the process's `unhandledRejection` handler, which shuts the whole
|
|
5
|
+
* server down over one undelivered cursor.
|
|
6
|
+
*/
|
|
7
|
+
var warnRealtime = (what) => (error) => {
|
|
8
|
+
console.warn(`[Realtime] ${what}:`, error instanceof Error ? error.message : error);
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { warnRealtime };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import onAbort from "../../helpers/onAbort.js";
|
|
2
2
|
import { openEventStream } from "../../core/http/sse.js";
|
|
3
|
+
import { warnRealtime } from "./failure.js";
|
|
3
4
|
import { accessRefusal } from "@plitzi/sdk-shared/actions";
|
|
4
5
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
5
6
|
import { PRESENCE_TYPE, channelLimits, matchChannel } from "@plitzi/sdk-shared/realtime";
|
|
@@ -155,7 +156,16 @@ var publishFrom = async (hub, connection, { topic, type, data }) => {
|
|
|
155
156
|
reason: "rate_limited"
|
|
156
157
|
};
|
|
157
158
|
if (presence) connection.announced.add(name);
|
|
158
|
-
|
|
159
|
+
try {
|
|
160
|
+
await hub.publish(connection.space, hub.from(connection, name, kind, value));
|
|
161
|
+
} catch (error) {
|
|
162
|
+
warnRealtime("a message was not delivered")(error);
|
|
163
|
+
return {
|
|
164
|
+
status: 503,
|
|
165
|
+
error: "The message was not delivered: send it again",
|
|
166
|
+
reason: "unavailable"
|
|
167
|
+
};
|
|
168
|
+
}
|
|
159
169
|
return { status: 204 };
|
|
160
170
|
};
|
|
161
171
|
/**
|
|
@@ -186,11 +196,11 @@ var handleRealtimeSubscribe = async ({ req, res, raw, signal, hub, resolveChanne
|
|
|
186
196
|
topics: [...admission.accepted.keys()],
|
|
187
197
|
refused: admission.refused
|
|
188
198
|
});
|
|
189
|
-
await hub.connect(connection);
|
|
199
|
+
await hub.connect(connection).catch(warnRealtime("a connection was not announced"));
|
|
190
200
|
await gone;
|
|
191
201
|
release();
|
|
192
202
|
stream.close();
|
|
193
|
-
await hub.disconnect(connection);
|
|
203
|
+
await hub.disconnect(connection).catch(warnRealtime("a connection was not let go"));
|
|
194
204
|
};
|
|
195
205
|
var parseBody = (body) => {
|
|
196
206
|
try {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { warnRealtime } from "./failure.js";
|
|
1
2
|
import { JOIN_TYPE, LEAVE_TYPE, REVOKED_TYPE } from "@plitzi/sdk-shared/realtime";
|
|
2
3
|
//#region src/modules/realtime/hub.ts
|
|
3
4
|
/** A space's topic, as the adapter knows it: namespaced, so two spaces never hear each other. */
|
|
@@ -43,7 +44,9 @@ var createRealtimeHub = (pubsub) => {
|
|
|
43
44
|
[...connections].filter((member) => {
|
|
44
45
|
const opened = member.grants.get(message.topic);
|
|
45
46
|
return opened !== void 0 && (grant === void 0 || opened === grant);
|
|
46
|
-
}).forEach((member) =>
|
|
47
|
+
}).forEach((member) => {
|
|
48
|
+
releaseTopic(member, message.topic).catch(warnRealtime(`"${message.topic}" was not let go`));
|
|
49
|
+
});
|
|
47
50
|
} else if (message) connections.forEach((member) => member.send("message", message));
|
|
48
51
|
})
|
|
49
52
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { warnRealtime } from "./failure.js";
|
|
1
2
|
import { admit, answer, connectionFor, publishFrom } from "./handlers.js";
|
|
2
3
|
import { requestOrigin } from "../../core/requestParser.js";
|
|
3
4
|
import { frameText } from "../../helpers/wsFrame.js";
|
|
@@ -107,7 +108,7 @@ var handleRealtimeSocket = async ({ req, res, raw, upgrade, hub, resolveChannels
|
|
|
107
108
|
});
|
|
108
109
|
socket.on("close", () => {
|
|
109
110
|
clearInterval(heartbeat);
|
|
110
|
-
hub.disconnect(connection);
|
|
111
|
+
hub.disconnect(connection).catch(warnRealtime("a connection was not let go"));
|
|
111
112
|
});
|
|
112
113
|
socket.on("error", () => socket.terminate());
|
|
113
114
|
send("ready", {
|
|
@@ -115,7 +116,7 @@ var handleRealtimeSocket = async ({ req, res, raw, upgrade, hub, resolveChannels
|
|
|
115
116
|
topics: [...admission.accepted.keys()],
|
|
116
117
|
refused: admission.refused
|
|
117
118
|
});
|
|
118
|
-
hub.connect(connection);
|
|
119
|
+
hub.connect(connection).catch(warnRealtime("a connection was not announced"));
|
|
119
120
|
});
|
|
120
121
|
};
|
|
121
122
|
//#endregion
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ActionsModule } from '../actions';
|
|
2
|
+
import { ProjectDataLookup } from '../actions/runtime/projectData';
|
|
2
3
|
import { ActionLookups } from '../actions/types';
|
|
3
4
|
import { ConnectorLookups } from '../connectors/resolver';
|
|
4
5
|
import { SSRAdapters } from '@plitzi/sdk-shared';
|
|
@@ -17,7 +18,7 @@ import { SSRAdapters } from '@plitzi/sdk-shared';
|
|
|
17
18
|
*
|
|
18
19
|
* A deployment that resolves server elements some other way still supplies its own `getRscData`, and that one wins.
|
|
19
20
|
*/
|
|
20
|
-
export declare const connectorRscData: ({ connectors, actions, publicDir,
|
|
21
|
+
export declare const connectorRscData: ({ connectors, actions, publicDir, data, elementTimeoutMs }: {
|
|
21
22
|
connectors?: ConnectorLookups;
|
|
22
23
|
actions?: {
|
|
23
24
|
lookups: ActionLookups;
|
|
@@ -25,8 +26,11 @@ export declare const connectorRscData: ({ connectors, actions, publicDir, dataDi
|
|
|
25
26
|
};
|
|
26
27
|
/** Where the server's static files are: a provider whose `query` is one of them is read from disk. */
|
|
27
28
|
publicDir?: string;
|
|
28
|
-
/**
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The space's data, never served: a provider whose `query` is `/data/<file>` is read from it — the lookup a
|
|
31
|
+
* deployment gave, or the one `createServer` derives from `dataDir`.
|
|
32
|
+
*/
|
|
33
|
+
data?: ProjectDataLookup;
|
|
30
34
|
/** The deployment's per-element ceiling, when it set one. `resolveRscData` decides the default. */
|
|
31
35
|
elementTimeoutMs?: number;
|
|
32
36
|
}) => NonNullable<SSRAdapters["getRscData"]>;
|