@docmentis/udoc-server 0.0.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +42 -0
- package/config.example.toml +26 -0
- package/dist/collaboration.d.ts +25 -0
- package/dist/collaboration.d.ts.map +1 -0
- package/dist/collaboration.js +348 -0
- package/dist/collaboration.js.map +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +87 -0
- package/dist/config.js.map +1 -0
- package/dist/document.d.ts +29 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +308 -0
- package/dist/document.js.map +1 -0
- package/dist/http.d.ts +44 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +96 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +105 -0
- package/dist/main.js.map +1 -0
- package/dist/package.d.ts +5 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +116 -0
- package/dist/package.js.map +1 -0
- package/dist/route.d.ts +56 -0
- package/dist/route.d.ts.map +1 -0
- package/dist/route.js +126 -0
- package/dist/route.js.map +1 -0
- package/dist/server.d.ts +45 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +103 -0
- package/dist/server.js.map +1 -0
- package/dist/session-pool.d.ts +19 -0
- package/dist/session-pool.d.ts.map +1 -0
- package/dist/session-pool.js +95 -0
- package/dist/session-pool.js.map +1 -0
- package/dist/store.d.ts +115 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +441 -0
- package/dist/store.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +75 -5
package/dist/http.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// What every route answers with: a reply, or a failure — its status and a code
|
|
2
|
+
// to branch on — and how either reaches the client: written as a response, or,
|
|
3
|
+
// for an upgrade the server won't take, written on its socket.
|
|
4
|
+
import { STATUS_CODES } from "node:http";
|
|
5
|
+
import { UDocError } from "@docmentis/udoc-node";
|
|
6
|
+
/**
|
|
7
|
+
* A request answered with a failure: its status, and a code to branch on —
|
|
8
|
+
* the engine's (docs/design/errors.md), or one only HTTP knows:
|
|
9
|
+
* `methodNotAllowed`, `payloadTooLarge`, `upgradeRequired`,
|
|
10
|
+
* `incorrectPassword`, `cannotImportPackage`.
|
|
11
|
+
*/
|
|
12
|
+
export class HttpFailure extends Error {
|
|
13
|
+
status;
|
|
14
|
+
code;
|
|
15
|
+
constructor(status, code, message, options) {
|
|
16
|
+
super(message, options);
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** `value` as JSON, written by `replacer` when it is given. */
|
|
22
|
+
export function json(status, value, replacer) {
|
|
23
|
+
return {
|
|
24
|
+
status,
|
|
25
|
+
headers: { "content-type": "application/json; charset=utf-8" },
|
|
26
|
+
body: Buffer.from(JSON.stringify(value, replacer)),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** `body`, of media type `type`, as a 200 — with `headers` beside. */
|
|
30
|
+
export function bytes(body, type, headers = {}) {
|
|
31
|
+
return { status: 200, headers: { "content-type": type, ...headers }, body };
|
|
32
|
+
}
|
|
33
|
+
/** A 204: done, and nothing to send. */
|
|
34
|
+
export function noContent() {
|
|
35
|
+
return { status: 204, headers: {}, body: new Uint8Array() };
|
|
36
|
+
}
|
|
37
|
+
/** Write `reply` as `response`. */
|
|
38
|
+
export function writeReply(response, { status, headers, body }) {
|
|
39
|
+
// A 204 has no body, so it has no length either.
|
|
40
|
+
if (status === 204) {
|
|
41
|
+
response.writeHead(status, headers).end();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
response.writeHead(status, { ...headers, "content-length": body.byteLength });
|
|
45
|
+
response.end(body);
|
|
46
|
+
}
|
|
47
|
+
/** `failure` as the JSON every failure answers: `{ error, message }`, `error` its code. */
|
|
48
|
+
export function failureReply(failure) {
|
|
49
|
+
return json(failure.status, { error: failure.code, message: failure.message });
|
|
50
|
+
}
|
|
51
|
+
/** Answer an upgrade the server won't take as a request would be answered, and hang up. */
|
|
52
|
+
export function refuseUpgrade(socket, failure) {
|
|
53
|
+
const { body } = failureReply(failure);
|
|
54
|
+
socket.write(`HTTP/1.1 ${failure.status} ${STATUS_CODES[failure.status] ?? ""}\r\n` +
|
|
55
|
+
"content-type: application/json; charset=utf-8\r\n" +
|
|
56
|
+
`content-length: ${body.byteLength}\r\n` +
|
|
57
|
+
"connection: close\r\n\r\n");
|
|
58
|
+
socket.end(body);
|
|
59
|
+
}
|
|
60
|
+
export function notFound(message = "no such resource") {
|
|
61
|
+
return new HttpFailure(404, "notFound", message);
|
|
62
|
+
}
|
|
63
|
+
/** The status each engine code answers with. A license refusal is `503`: this
|
|
64
|
+
* server can't open documents, whatever is asked of it. */
|
|
65
|
+
const STATUS = {
|
|
66
|
+
invalidRequest: 400,
|
|
67
|
+
notEntitled: 403,
|
|
68
|
+
notFound: 404,
|
|
69
|
+
unsupported: 422,
|
|
70
|
+
malformed: 422,
|
|
71
|
+
passwordRequired: 422,
|
|
72
|
+
internal: 500,
|
|
73
|
+
crashed: 500,
|
|
74
|
+
io: 502,
|
|
75
|
+
invalidLicense: 503,
|
|
76
|
+
permitRefused: 503,
|
|
77
|
+
cannotVerify: 503,
|
|
78
|
+
};
|
|
79
|
+
/** The codes a license refuses under: starting a session, or a call it isn't entitled to. */
|
|
80
|
+
export const LICENSE_CODES = new Set([
|
|
81
|
+
"invalidLicense",
|
|
82
|
+
"permitRefused",
|
|
83
|
+
"cannotVerify",
|
|
84
|
+
"notEntitled",
|
|
85
|
+
]);
|
|
86
|
+
/** `error`, whatever a route threw, as the failure it answers with: its own, or the engine's code at its status. */
|
|
87
|
+
export function failureOf(error) {
|
|
88
|
+
if (error instanceof HttpFailure)
|
|
89
|
+
return error;
|
|
90
|
+
const failure = UDocError.from(error);
|
|
91
|
+
return new HttpFailure(STATUS[failure.code], failure.code, failure.message, { cause: failure });
|
|
92
|
+
}
|
|
93
|
+
export function messageOf(error) {
|
|
94
|
+
return error instanceof Error ? error.message : String(error);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createUDocServer } from "./server.ts";
|
|
2
|
+
export type { UDocServer, UDocServerOptions } from "./server.ts";
|
|
3
|
+
export { DirectoryDocumentStore } from "./store.ts";
|
|
4
|
+
export type { DocumentStore, Frontier, KeptDocument, Original, PackageDescription } from "./store.ts";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `udoc-server` — run the udoc server.
|
|
3
|
+
//
|
|
4
|
+
// Licensed from the environment like the `udoc` CLI: `UDOC_LICENSE` for the
|
|
5
|
+
// domain in `UDOC_DOMAIN`, or free usage, which spends a permit per session
|
|
6
|
+
// opened — an upload's, and each reopen of a kept document. It listens on
|
|
7
|
+
// loopback unless told otherwise, because nothing in front of it checks who is
|
|
8
|
+
// asking. Its settings come from its flags, over a TOML config file given with
|
|
9
|
+
// `--config` (see `config.ts`). It is the server's host: its own HTTP server
|
|
10
|
+
// hands the server each request and upgrade, and answers any the server's
|
|
11
|
+
// routes don't have with a 404.
|
|
12
|
+
import { createServer } from "node:http";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import process from "node:process";
|
|
15
|
+
import { parseArgs } from "node:util";
|
|
16
|
+
import { UDocNode } from "@docmentis/udoc-node";
|
|
17
|
+
import { readConfigFile, settingsOf } from "./config.js";
|
|
18
|
+
import { failureReply, notFound, refuseUpgrade, writeReply } from "./http.js";
|
|
19
|
+
import { createUDocServer } from "./server.js";
|
|
20
|
+
import { DirectoryDocumentStore } from "./store.js";
|
|
21
|
+
import { WRAPPER_VERSION } from "./version.js";
|
|
22
|
+
const USAGE = `Usage: udoc-server [--config <path>] [--port <n>] [--host <address>]
|
|
23
|
+
[--data-dir <path>] [--session-timeout <seconds>] [--session-pool <n>]
|
|
24
|
+
|
|
25
|
+
--config <path> a TOML file of settings: each flag below is a key of
|
|
26
|
+
the same name (port = 8080, data-dir = "docs", …),
|
|
27
|
+
and a flag given here overrides it. A relative
|
|
28
|
+
data-dir in the file is relative to the file.
|
|
29
|
+
-p, --port <n> port to listen on (default 8080)
|
|
30
|
+
--host <address> address to bind (default 127.0.0.1)
|
|
31
|
+
--data-dir <path> where documents are kept (default ./udoc-data)
|
|
32
|
+
--session-timeout <s> close a document's session after this long with no
|
|
33
|
+
request for it (default 300)
|
|
34
|
+
--session-pool <n> how many sessions stay open once their requests
|
|
35
|
+
finish (default 32)
|
|
36
|
+
|
|
37
|
+
Environment:
|
|
38
|
+
UDOC_LICENSE commercial license key; without one, usage is
|
|
39
|
+
metered and each session opened needs a permit
|
|
40
|
+
UDOC_DOMAIN the domain that license is issued for
|
|
41
|
+
`;
|
|
42
|
+
async function main() {
|
|
43
|
+
// No defaults here: an absent flag leaves the setting to the config file.
|
|
44
|
+
const { values } = parseArgs({
|
|
45
|
+
options: {
|
|
46
|
+
config: { type: "string" },
|
|
47
|
+
port: { type: "string", short: "p" },
|
|
48
|
+
host: { type: "string" },
|
|
49
|
+
"data-dir": { type: "string" },
|
|
50
|
+
"session-timeout": { type: "string" },
|
|
51
|
+
"session-pool": { type: "string" },
|
|
52
|
+
help: { type: "boolean", short: "h" },
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (values.help) {
|
|
56
|
+
console.log(USAGE);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { config, ...flags } = values;
|
|
60
|
+
const configPath = config === undefined ? undefined : resolve(config);
|
|
61
|
+
const { port, host, dataDir, sessionTimeout, sessionPool } = settingsOf(flags, configPath === undefined ? {} : await readConfigFile(configPath), configPath);
|
|
62
|
+
const udoc = await UDocNode.create({
|
|
63
|
+
...licenseFromEnvironment(),
|
|
64
|
+
wrapper: { name: "udoc-server", version: WRAPPER_VERSION },
|
|
65
|
+
});
|
|
66
|
+
const server = createUDocServer({
|
|
67
|
+
udoc,
|
|
68
|
+
store: new DirectoryDocumentStore(dataDir),
|
|
69
|
+
sessionTimeoutMs: sessionTimeout * 1000,
|
|
70
|
+
sessionPool,
|
|
71
|
+
});
|
|
72
|
+
const http = createServer((request, response) => {
|
|
73
|
+
if (!server.handleRequest(request, response))
|
|
74
|
+
writeReply(response, failureReply(notFound()));
|
|
75
|
+
});
|
|
76
|
+
http.on("upgrade", (request, socket, head) => {
|
|
77
|
+
if (!server.handleUpgrade(request, socket, head))
|
|
78
|
+
refuseUpgrade(socket, notFound());
|
|
79
|
+
});
|
|
80
|
+
http.listen(port, host, () => {
|
|
81
|
+
console.log(`udoc-server listening on http://${host}:${port}, keeping documents in ${dataDir}`);
|
|
82
|
+
});
|
|
83
|
+
const stop = () => {
|
|
84
|
+
const stopped = new Promise((resolve) => http.close(() => resolve()));
|
|
85
|
+
void server
|
|
86
|
+
.close()
|
|
87
|
+
.then(() => stopped)
|
|
88
|
+
.then(() => process.exit(0));
|
|
89
|
+
};
|
|
90
|
+
process.once("SIGINT", stop);
|
|
91
|
+
process.once("SIGTERM", stop);
|
|
92
|
+
}
|
|
93
|
+
function licenseFromEnvironment() {
|
|
94
|
+
const license = process.env["UDOC_LICENSE"];
|
|
95
|
+
const domain = process.env["UDOC_DOMAIN"];
|
|
96
|
+
return {
|
|
97
|
+
...(license ? { license } : {}),
|
|
98
|
+
...(domain ? { domain } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
main().catch((error) => {
|
|
102
|
+
console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Reply } from "./http.ts";
|
|
2
|
+
import type { DocumentStore } from "./store.ts";
|
|
3
|
+
/** Document `id`'s resource at `path` under `…/package` in `store`, as its route answers it. */
|
|
4
|
+
export declare function readPackage(store: DocumentStore, id: string, path: string, query: URLSearchParams): Promise<Reply>;
|
|
5
|
+
//# sourceMappingURL=package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../src/package.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAIvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA6BhD,gGAAgG;AAChG,wBAAsB,WAAW,CAC7B,KAAK,EAAE,aAAa,EACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,eAAe,GACvB,OAAO,CAAC,KAAK,CAAC,CAGhB"}
|
package/dist/package.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// The package routes (docs/design/node-streaming.md): a kept document opened
|
|
2
|
+
// part by part. What a client streaming it fetches under
|
|
3
|
+
// `/documents/:id/package`, and what a session this server opens over its
|
|
4
|
+
// store reads — both answered here, from the store, with no session opened.
|
|
5
|
+
//
|
|
6
|
+
// Each body but the description is sent deflated — raw DEFLATE, which the
|
|
7
|
+
// engine inflates — so none of the document's content crosses as it is.
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
import { deflateRaw } from "node:zlib";
|
|
10
|
+
import { HttpFailure, bytes, json, notFound } from "./http.js";
|
|
11
|
+
import { match, route } from "./route.js";
|
|
12
|
+
import { BLOB_KEY, KINDS, PART_ID } from "./store.js";
|
|
13
|
+
/** The routes of one part or one blob: what a batch names. */
|
|
14
|
+
const ENTRY_ROUTES = [
|
|
15
|
+
route("GET", "blobs/:key", blob, { params: { key: BLOB_KEY } }),
|
|
16
|
+
route("GET", ":kinds/:partId", part, { params: { kinds: KINDS, partId: PART_ID } }),
|
|
17
|
+
];
|
|
18
|
+
/** Every route under `…/package`. */
|
|
19
|
+
const PACKAGE_ROUTES = [
|
|
20
|
+
route("GET", "", description),
|
|
21
|
+
route("GET", "crdt", log),
|
|
22
|
+
route("GET", "batch", batch),
|
|
23
|
+
...ENTRY_ROUTES,
|
|
24
|
+
];
|
|
25
|
+
/** The most parts or blobs one `…/package/batch` names — as many as the engine asks in one. */
|
|
26
|
+
const BATCH_LIMIT = 64;
|
|
27
|
+
/** The length a batch body gives an entry the package doesn't have. */
|
|
28
|
+
const BATCH_ABSENT = 0xffff_ffff;
|
|
29
|
+
const OCTETS = "application/octet-stream";
|
|
30
|
+
const deflate = promisify(deflateRaw);
|
|
31
|
+
/** Document `id`'s resource at `path` under `…/package` in `store`, as its route answers it. */
|
|
32
|
+
export async function readPackage(store, id, path, query) {
|
|
33
|
+
const [{ handle }, params] = match(PACKAGE_ROUTES, "GET", path);
|
|
34
|
+
return handle(store, id, params, query);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The package's description, `{ docType, sourceFormat?, sourceDelimiter?,
|
|
38
|
+
* baselineVersion, baselineFrontier? }`: the latest checkpoint's, with `baselineFrontier`
|
|
39
|
+
* present exactly when there is an edit log.
|
|
40
|
+
*/
|
|
41
|
+
async function description(store, id) {
|
|
42
|
+
const description = await store.getDescription(id);
|
|
43
|
+
if (!description)
|
|
44
|
+
throw notFound(`document ${id} has no package`);
|
|
45
|
+
return json(200, description, bigintsWhole);
|
|
46
|
+
}
|
|
47
|
+
/** The edit log, once the document has been edited. */
|
|
48
|
+
async function log(store, id) {
|
|
49
|
+
const log = await store.getLog(id);
|
|
50
|
+
if (!log)
|
|
51
|
+
throw notFound(`document ${id} has no edit log`);
|
|
52
|
+
// Kept whole, not in the package's zip, so deflated here.
|
|
53
|
+
return bytes(await deflate(log), OCTETS);
|
|
54
|
+
}
|
|
55
|
+
/** One blob, by its key. What is served there never changes: a blob's key is its content. */
|
|
56
|
+
async function blob(store, id, { key }) {
|
|
57
|
+
const body = await store.getBlob(id, key);
|
|
58
|
+
if (!body)
|
|
59
|
+
throw notFound(`document ${id}'s package has no blob ${key}`);
|
|
60
|
+
return bytes(body, OCTETS, { "cache-control": "private, max-age=31536000, immutable" });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* One part, as its JSON, by its kind in kebab-case and plural and its id's
|
|
64
|
+
* key: the latest checkpoint's, or an opaque part an edit brought, kept beside
|
|
65
|
+
* it (docs/design/core-editor-opaque-part.md), or, held by neither, an earlier
|
|
66
|
+
* checkpoint's.
|
|
67
|
+
*/
|
|
68
|
+
async function part(store, id, { kinds, partId }) {
|
|
69
|
+
const body = await store.getPart(id, kinds, partId);
|
|
70
|
+
if (!body)
|
|
71
|
+
throw notFound(`document ${id}'s package has no ${kinds} part ${partId}`);
|
|
72
|
+
return bytes(body, OCTETS);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Up to 64 parts or blobs at once, each `p` of `query` one's route under
|
|
76
|
+
* `…/package`. The body is each entry in turn: its 4-byte big-endian length
|
|
77
|
+
* and its bytes, deflated as its own route sends it, or the length 0xffffffff
|
|
78
|
+
* alone for one the package doesn't have.
|
|
79
|
+
*/
|
|
80
|
+
async function batch(store, id, _params, query) {
|
|
81
|
+
const routes = query.getAll("p");
|
|
82
|
+
if (routes.length > BATCH_LIMIT) {
|
|
83
|
+
throw new HttpFailure(400, "invalidRequest", `a batch names at most ${BATCH_LIMIT} routes, got ${routes.length}`);
|
|
84
|
+
}
|
|
85
|
+
const entries = [];
|
|
86
|
+
for (const path of routes) {
|
|
87
|
+
let entry;
|
|
88
|
+
try {
|
|
89
|
+
entry = match(ENTRY_ROUTES, "GET", path);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new HttpFailure(400, "invalidRequest", `${JSON.stringify(path)} is no part's or blob's route`);
|
|
93
|
+
}
|
|
94
|
+
const [{ handle }, params] = entry;
|
|
95
|
+
const length = Buffer.alloc(4);
|
|
96
|
+
try {
|
|
97
|
+
const { body } = await handle(store, id, params, new URLSearchParams());
|
|
98
|
+
length.writeUInt32BE(body.byteLength);
|
|
99
|
+
entries.push(length, body);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (!(error instanceof HttpFailure) || error.status !== 404)
|
|
103
|
+
throw error;
|
|
104
|
+
length.writeUInt32BE(BATCH_ABSENT);
|
|
105
|
+
entries.push(length);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return bytes(Buffer.concat(entries), OCTETS);
|
|
109
|
+
}
|
|
110
|
+
/** `JSON.rawJSON` (ES2026): in every Node this package runs on, not yet in TypeScript's lib. */
|
|
111
|
+
const rawJSON = JSON.rawJSON;
|
|
112
|
+
/** A frontier's `bigint`s written whole. */
|
|
113
|
+
function bigintsWhole(_key, value) {
|
|
114
|
+
return typeof value === "bigint" ? rawJSON(value.toString()) : value;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=package.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.js","sourceRoot":"","sources":["../src/package.ts"],"names":[],"mappings":"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"}
|
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { Reply, Upgrade } from "./http.ts";
|
|
3
|
+
/** A path's parameters, by name. */
|
|
4
|
+
export type Params = Record<string, string>;
|
|
5
|
+
/** What a route's handler is given: its path's parameters, its query, and the request. */
|
|
6
|
+
export interface RouteRequest {
|
|
7
|
+
params: Params;
|
|
8
|
+
query: URLSearchParams;
|
|
9
|
+
incoming: IncomingMessage;
|
|
10
|
+
}
|
|
11
|
+
/** What answers a request for a route. */
|
|
12
|
+
export type Handler = (request: RouteRequest) => Promise<Reply>;
|
|
13
|
+
/**
|
|
14
|
+
* What takes a WebSocket upgrade for a route — or, refusing it, answers the
|
|
15
|
+
* refusal itself: a browser can't read the answer to an upgrade it isn't given.
|
|
16
|
+
*/
|
|
17
|
+
export type UpgradeHandler = (request: RouteRequest, upgrade: Upgrade) => Promise<void>;
|
|
18
|
+
/** A request of `method` whose path matches `pattern` is answered by `handle`, a WebSocket upgrade by `upgrade`. */
|
|
19
|
+
export interface Route<H = Handler> {
|
|
20
|
+
readonly method: string;
|
|
21
|
+
readonly pattern: RegExp;
|
|
22
|
+
readonly handle: H;
|
|
23
|
+
readonly upgrade?: UpgradeHandler;
|
|
24
|
+
}
|
|
25
|
+
export interface RouteOptions {
|
|
26
|
+
/** The grammar of each parameter that has one: a path whose parameter doesn't match it names no route. */
|
|
27
|
+
params?: Record<string, RegExp>;
|
|
28
|
+
/** Takes a WebSocket upgrade for the route's path. */
|
|
29
|
+
upgrade?: UpgradeHandler;
|
|
30
|
+
}
|
|
31
|
+
/** A request of `method` at a path matching `path`, answered by `handle`. */
|
|
32
|
+
export declare function route<H>(method: string, path: string, handle: H, options?: RouteOptions): Route<H>;
|
|
33
|
+
/**
|
|
34
|
+
* The route of `table` for `method` at `path`, and the path's parameters: the
|
|
35
|
+
* first whose pattern matches and whose method is `method`. None has the path:
|
|
36
|
+
* a 404; one has it, for another method: a 405.
|
|
37
|
+
*/
|
|
38
|
+
export declare function match<H>(table: readonly Route<H>[], method: string, path: string): [Route<H>, Params];
|
|
39
|
+
/** Where a request is: its path beneath the server's base path, and its query. */
|
|
40
|
+
export interface Target {
|
|
41
|
+
path: string;
|
|
42
|
+
query: URLSearchParams;
|
|
43
|
+
}
|
|
44
|
+
/** Where `request` is beneath `base`, a path; `undefined` if its path isn't beneath it. */
|
|
45
|
+
export declare function targetOf(request: IncomingMessage, base: string): Target | undefined;
|
|
46
|
+
/** Whether some route of `table` has `path`, for any method. */
|
|
47
|
+
export declare function has(table: readonly Route[], path: string): boolean;
|
|
48
|
+
/** Answer `request`, at `target`, by the route of `table` it names; failing, with the failure. */
|
|
49
|
+
export declare function answerRequest(table: readonly Route[], { path, query }: Target, request: IncomingMessage, response: ServerResponse): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Answer `upgrade`, at `target`, by the route of `table` that takes upgrades
|
|
52
|
+
* at its path, which answers its refusals itself. A path no such route has is
|
|
53
|
+
* answered as a request would be, and hung up.
|
|
54
|
+
*/
|
|
55
|
+
export declare function answerUpgrade(table: readonly Route[], { path, query }: Target, upgrade: Upgrade): Promise<void>;
|
|
56
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEhD,oCAAoC;AACpC,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE5C,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,eAAe,CAAC;CAC7B;AAED,0CAA0C;AAC1C,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAExF,oHAAoH;AACpH,MAAM,WAAW,KAAK,CAAC,CAAC,GAAG,OAAO;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IACzB,0GAA0G;IAC1G,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,sDAAsD;IACtD,OAAO,CAAC,EAAE,cAAc,CAAC;CAC5B;AAKD,6EAA6E;AAC7E,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,KAAK,CAAC,CAAC,CAAC,CAkBtG;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,CAWrG;AAED,kFAAkF;AAClF,MAAM,WAAW,MAAM;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;CAC1B;AAED,2FAA2F;AAC3F,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAanF;AAED,gEAAgE;AAChE,wBAAgB,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGlE;AAED,kGAAkG;AAClG,wBAAsB,aAAa,CAC/B,KAAK,EAAE,SAAS,KAAK,EAAE,EACvB,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EACvB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,GACzB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAerH"}
|
package/dist/route.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// A route: a method and a path pattern, and what answers a request for both —
|
|
2
|
+
// and how a request or a WebSocket upgrade reaches the route its path names.
|
|
3
|
+
//
|
|
4
|
+
// The server's routes come in families, each a table of its own file's — the
|
|
5
|
+
// document routes in `document.ts` — and are matched in turn: the first route
|
|
6
|
+
// whose pattern matches the path and whose method is the request's answers it.
|
|
7
|
+
// A path no route has is a 404; one some route has, but for another method, a
|
|
8
|
+
// 405. The routes sit beneath a base path, and a request whose path no route
|
|
9
|
+
// has there is not the server's: it is left untouched, for whatever the server
|
|
10
|
+
// is embedded in to answer.
|
|
11
|
+
//
|
|
12
|
+
// A pattern is a path whose segments may hold parameters: `:name` is a segment,
|
|
13
|
+
// or the part of one between literal text (`:index.png`, `file.:format`), and
|
|
14
|
+
// `*name` the rest of the path. A parameter matches any text but a `/`, unless
|
|
15
|
+
// its route gives it a grammar. A path is compared by its segments, so a
|
|
16
|
+
// doubled or trailing `/` changes nothing.
|
|
17
|
+
import { HttpFailure, failureOf, failureReply, notFound, refuseUpgrade, writeReply } from "./http.js";
|
|
18
|
+
/** A `:name` or `*name` parameter in a pattern. */
|
|
19
|
+
const PARAMETER = /:(\w+)|\*(\w+)/g;
|
|
20
|
+
/** A request of `method` at a path matching `path`, answered by `handle`. */
|
|
21
|
+
export function route(method, path, handle, options = {}) {
|
|
22
|
+
const grammar = options.params ?? {};
|
|
23
|
+
const pattern = segmentsOf(path);
|
|
24
|
+
let source = "";
|
|
25
|
+
let literal = 0;
|
|
26
|
+
for (const parameter of pattern.matchAll(PARAMETER)) {
|
|
27
|
+
source += escape(pattern.slice(literal, parameter.index));
|
|
28
|
+
const [whole, one, rest] = parameter;
|
|
29
|
+
source += one !== undefined ? `(?<${one}>${grammar[one]?.source ?? "[^/]+"})` : `(?<${rest}>.+)`;
|
|
30
|
+
literal = parameter.index + whole.length;
|
|
31
|
+
}
|
|
32
|
+
source += escape(pattern.slice(literal));
|
|
33
|
+
return {
|
|
34
|
+
method,
|
|
35
|
+
pattern: new RegExp(`^${source}$`),
|
|
36
|
+
handle,
|
|
37
|
+
...(options.upgrade ? { upgrade: options.upgrade } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The route of `table` for `method` at `path`, and the path's parameters: the
|
|
42
|
+
* first whose pattern matches and whose method is `method`. None has the path:
|
|
43
|
+
* a 404; one has it, for another method: a 405.
|
|
44
|
+
*/
|
|
45
|
+
export function match(table, method, path) {
|
|
46
|
+
const segments = segmentsOf(path);
|
|
47
|
+
const allowed = [];
|
|
48
|
+
for (const route of table) {
|
|
49
|
+
const matched = route.pattern.exec(segments);
|
|
50
|
+
if (!matched)
|
|
51
|
+
continue;
|
|
52
|
+
if (route.method === method)
|
|
53
|
+
return [route, { ...matched.groups }];
|
|
54
|
+
allowed.push(route.method);
|
|
55
|
+
}
|
|
56
|
+
if (allowed.length === 0)
|
|
57
|
+
throw notFound();
|
|
58
|
+
throw new HttpFailure(405, "methodNotAllowed", `${method} is not allowed here; use ${allowed.join(" or ")}`);
|
|
59
|
+
}
|
|
60
|
+
/** Where `request` is beneath `base`, a path; `undefined` if its path isn't beneath it. */
|
|
61
|
+
export function targetOf(request, base) {
|
|
62
|
+
let url;
|
|
63
|
+
try {
|
|
64
|
+
url = new URL(request.url ?? "/", "http://localhost");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
const prefix = segmentsOf(base);
|
|
70
|
+
const path = segmentsOf(url.pathname);
|
|
71
|
+
if (prefix === "")
|
|
72
|
+
return { path, query: url.searchParams };
|
|
73
|
+
if (path === prefix)
|
|
74
|
+
return { path: "", query: url.searchParams };
|
|
75
|
+
if (!path.startsWith(`${prefix}/`))
|
|
76
|
+
return undefined;
|
|
77
|
+
return { path: path.slice(prefix.length + 1), query: url.searchParams };
|
|
78
|
+
}
|
|
79
|
+
/** Whether some route of `table` has `path`, for any method. */
|
|
80
|
+
export function has(table, path) {
|
|
81
|
+
const segments = segmentsOf(path);
|
|
82
|
+
return table.some((route) => route.pattern.test(segments));
|
|
83
|
+
}
|
|
84
|
+
/** Answer `request`, at `target`, by the route of `table` it names; failing, with the failure. */
|
|
85
|
+
export async function answerRequest(table, { path, query }, request, response) {
|
|
86
|
+
try {
|
|
87
|
+
const [route, params] = match(table, request.method ?? "GET", path);
|
|
88
|
+
writeReply(response, await route.handle({ params, query, incoming: request }));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const failure = failureOf(error);
|
|
92
|
+
if (response.headersSent) {
|
|
93
|
+
response.destroy();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (failure.status === 413)
|
|
97
|
+
response.setHeader("connection", "close");
|
|
98
|
+
writeReply(response, failureReply(failure));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Answer `upgrade`, at `target`, by the route of `table` that takes upgrades
|
|
103
|
+
* at its path, which answers its refusals itself. A path no such route has is
|
|
104
|
+
* answered as a request would be, and hung up.
|
|
105
|
+
*/
|
|
106
|
+
export async function answerUpgrade(table, { path, query }, upgrade) {
|
|
107
|
+
const { request, socket } = upgrade;
|
|
108
|
+
let matched;
|
|
109
|
+
try {
|
|
110
|
+
matched = match(table.filter((route) => route.upgrade !== undefined), request.method ?? "GET", path);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
refuseUpgrade(socket, error instanceof HttpFailure ? error : notFound());
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const [route, params] = matched;
|
|
117
|
+
await route.upgrade({ params, query, incoming: request }, upgrade);
|
|
118
|
+
}
|
|
119
|
+
/** `path`'s segments, joined: no leading, trailing or doubled `/`. */
|
|
120
|
+
function segmentsOf(path) {
|
|
121
|
+
return path.split("/").filter(Boolean).join("/");
|
|
122
|
+
}
|
|
123
|
+
function escape(literal) {
|
|
124
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { Duplex } from "node:stream";
|
|
3
|
+
import type { UDocNode } from "@docmentis/udoc-node";
|
|
4
|
+
import type { DocumentStore } from "./store.ts";
|
|
5
|
+
export interface UDocServerOptions {
|
|
6
|
+
/** Opens each document. The embedder builds it, so the license — and
|
|
7
|
+
* whether usage is metered — is theirs to decide. */
|
|
8
|
+
udoc: Pick<UDocNode, "open" | "openRemote">;
|
|
9
|
+
/** Where each document is kept between sessions. */
|
|
10
|
+
store: DocumentStore;
|
|
11
|
+
/** The largest document accepted, in bytes. Defaults to 256 MiB. */
|
|
12
|
+
maxUploadBytes?: number;
|
|
13
|
+
/** How long a session stays open with no request for it, in milliseconds.
|
|
14
|
+
* Defaults to 5 minutes. */
|
|
15
|
+
sessionTimeoutMs?: number;
|
|
16
|
+
/** The session pool's size: how many sessions stay open once their
|
|
17
|
+
* requests finish. Past it, the least recently used idle ones close.
|
|
18
|
+
* Defaults to 32. */
|
|
19
|
+
sessionPool?: number;
|
|
20
|
+
/** The path the routes sit beneath: `"/udoc"` serves `/udoc/documents/…`.
|
|
21
|
+
* Defaults to `""`, the root. */
|
|
22
|
+
basePath?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface UDocServer {
|
|
25
|
+
/**
|
|
26
|
+
* Answer `request` if a route has its path, for any method, and return
|
|
27
|
+
* true; return false, leaving it untouched, if none does.
|
|
28
|
+
*/
|
|
29
|
+
handleRequest(request: IncomingMessage, response: ServerResponse): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Take the WebSocket upgrade of `request` if a route takes upgrades at its
|
|
32
|
+
* path, for any method, and return true; return false, leaving `socket`
|
|
33
|
+
* untouched, if none does.
|
|
34
|
+
*/
|
|
35
|
+
handleUpgrade(request: IncomingMessage, socket: Duplex, head: Buffer): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* End every collaboration once its edits are kept, and close every open
|
|
38
|
+
* session. What is kept stays kept. The host's HTTP server is the host's
|
|
39
|
+
* to close.
|
|
40
|
+
*/
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/** Build the server over `options.store`: its routes are the document routes (see `documentRoutes`), beneath `options.basePath`. */
|
|
44
|
+
export declare function createUDocServer(options: UDocServerOptions): UDocServer;
|
|
45
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,sBAAsB,CAAC;AASlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAC9B;0DACsD;IACtD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC;IAC5C,oDAAoD;IACpD,KAAK,EAAE,aAAa,CAAC;IACrB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iCAC6B;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;0BAEsB;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;sCACkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACvB;;;OAGG;IACH,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC;IAC3E;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/E;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAaD,oIAAoI;AACpI,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA+BvE"}
|