@docmentis/udoc-server 0.0.0-stage → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +41 -2
  4. package/config.example.toml +26 -0
  5. package/dist/collaboration.d.ts +25 -0
  6. package/dist/collaboration.d.ts.map +1 -0
  7. package/dist/collaboration.js +348 -0
  8. package/dist/collaboration.js.map +1 -0
  9. package/dist/config.d.ts +30 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +87 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/document.d.ts +29 -0
  14. package/dist/document.d.ts.map +1 -0
  15. package/dist/document.js +308 -0
  16. package/dist/document.js.map +1 -0
  17. package/dist/http.d.ts +44 -0
  18. package/dist/http.d.ts.map +1 -0
  19. package/dist/http.js +96 -0
  20. package/dist/http.js.map +1 -0
  21. package/dist/index.d.ts +5 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +3 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/main.d.ts +3 -0
  26. package/dist/main.d.ts.map +1 -0
  27. package/dist/main.js +105 -0
  28. package/dist/main.js.map +1 -0
  29. package/dist/package.d.ts +5 -0
  30. package/dist/package.d.ts.map +1 -0
  31. package/dist/package.js +116 -0
  32. package/dist/package.js.map +1 -0
  33. package/dist/route.d.ts +56 -0
  34. package/dist/route.d.ts.map +1 -0
  35. package/dist/route.js +126 -0
  36. package/dist/route.js.map +1 -0
  37. package/dist/server.d.ts +45 -0
  38. package/dist/server.d.ts.map +1 -0
  39. package/dist/server.js +103 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/session-pool.d.ts +19 -0
  42. package/dist/session-pool.d.ts.map +1 -0
  43. package/dist/session-pool.js +95 -0
  44. package/dist/session-pool.js.map +1 -0
  45. package/dist/store.d.ts +115 -0
  46. package/dist/store.d.ts.map +1 -0
  47. package/dist/store.js +441 -0
  48. package/dist/store.js.map +1 -0
  49. package/dist/version.d.ts +2 -0
  50. package/dist/version.d.ts.map +1 -0
  51. package/dist/version.js +10 -0
  52. package/dist/version.js.map +1 -0
  53. package/package.json +76 -5
package/dist/server.js ADDED
@@ -0,0 +1,103 @@
1
+ // The udoc server: keeps documents, and serves them over HTTP through sessions
2
+ // opened over what it keeps.
3
+ //
4
+ // A document is imported once — the upload opened, its ids minted — and kept in
5
+ // a `DocumentStore` as its original, the upload as it arrived, and its
6
+ // checkpoints, the udoc packages a later session reopens the latest of: `v0`
7
+ // from the import, then one more each time it is checkpointed. What is kept is
8
+ // the document; a session is a cache over it, one per document on a thread of
9
+ // its own (see `@docmentis/udoc-node`): opened when a request needs it, kept
10
+ // while requests keep coming, closed once idle or once too many are open, and
11
+ // reopened from the store the next time. That is what lets a document outlive
12
+ // the process, and what bounds how much the process holds. Streaming a
13
+ // document's parts reads the store alone. Co-editing it is a collaboration: the
14
+ // clients editing it together over its pooled session, held open while any is
15
+ // connected, and every edit kept in the store as the document's edit log.
16
+ //
17
+ // Each piece is a file of its own: the store (`store.ts`), the session pool
18
+ // over it (`session-pool.ts`), the collaborations over the pool
19
+ // (`collaboration.ts`), and the routes — each family a table of its own, the
20
+ // document routes (`document.ts`) and the package routes beneath them
21
+ // (`package.ts`), matched by `route.ts`. Here they are built over one store.
22
+ //
23
+ // The server answers requests and WebSocket upgrades; it doesn't listen. Its
24
+ // host hands it each one — `main.ts` from an HTTP server of its own, an
25
+ // embedder from theirs — and answers itself any whose path no route has.
26
+ import { UDocError } from "@docmentis/udoc-node";
27
+ import { Collaborations } from "./collaboration.js";
28
+ import { documentRoutes, noDocument } from "./document.js";
29
+ import { HttpFailure, LICENSE_CODES, notFound } from "./http.js";
30
+ import { readPackage } from "./package.js";
31
+ import { answerRequest, answerUpgrade, has, targetOf } from "./route.js";
32
+ import { SessionPool } from "./session-pool.js";
33
+ const DEFAULT_MAX_UPLOAD_BYTES = 256 * 1024 * 1024;
34
+ const DEFAULT_SESSION_TIMEOUT_MS = 5 * 60 * 1000;
35
+ const DEFAULT_SESSION_POOL = 32;
36
+ /**
37
+ * The base a session this server opens over its store fetches its package
38
+ * from. It names no host: every URL under it is answered in-process, from the
39
+ * store, by the same code that answers the package routes.
40
+ */
41
+ const KEPT_ORIGIN = "udoc-server://kept";
42
+ /** Build the server over `options.store`: its routes are the document routes (see `documentRoutes`), beneath `options.basePath`. */
43
+ export function createUDocServer(options) {
44
+ const { udoc, store, basePath = "" } = options;
45
+ const maxUploadBytes = options.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES;
46
+ const sessions = new SessionPool((id) => reopen(udoc, store, id), options.sessionTimeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS, options.sessionPool ?? DEFAULT_SESSION_POOL);
47
+ const collaborations = new Collaborations(store, sessions, maxUploadBytes);
48
+ /** Every route the server answers: each family's, in turn. */
49
+ const routes = [...documentRoutes({ udoc, store, sessions, collaborations, maxUploadBytes })];
50
+ const upgradeRoutes = routes.filter((route) => route.upgrade !== undefined);
51
+ return {
52
+ handleRequest(request, response) {
53
+ const target = targetOf(request, basePath);
54
+ if (!target || !has(routes, target.path))
55
+ return false;
56
+ void answerRequest(routes, target, request, response);
57
+ return true;
58
+ },
59
+ handleUpgrade(request, socket, head) {
60
+ const target = targetOf(request, basePath);
61
+ if (!target || !has(upgradeRoutes, target.path))
62
+ return false;
63
+ answerUpgrade(routes, target, { request, socket, head }).catch(() => socket.destroy());
64
+ return true;
65
+ },
66
+ async close() {
67
+ await collaborations.close();
68
+ await sessions.closeAll();
69
+ },
70
+ };
71
+ }
72
+ /**
73
+ * Open document `id` again from what is kept of it in `store`: its package,
74
+ * over the store — the latest checkpoint's parts and blobs read as the
75
+ * document needs them, the way a browser streams it, and the edit log applied
76
+ * by the open.
77
+ */
78
+ async function reopen(udoc, store, id) {
79
+ try {
80
+ if (!(await store.getDescription(id)))
81
+ throw noDocument(id);
82
+ return await udoc.openRemote(`${KEPT_ORIGIN}/documents/${id}/package`, (url) => kept(store, id, url));
83
+ }
84
+ catch (error) {
85
+ if (error instanceof HttpFailure)
86
+ throw error;
87
+ const failure = UDocError.from(error);
88
+ if (LICENSE_CODES.has(failure.code))
89
+ throw failure;
90
+ // It opened once to be kept, so a failure now is this server's,
91
+ // not the caller's: it answers as one.
92
+ throw new UDocError("internal", failure.message, undefined, { cause: failure });
93
+ }
94
+ }
95
+ /** What a session this server opened over document `id`'s store asks for at `url`, read from the store. */
96
+ async function kept(store, id, url) {
97
+ const base = `/documents/${id}/package`;
98
+ const { pathname, searchParams } = new URL(url);
99
+ if (pathname !== base && !pathname.startsWith(`${base}/`))
100
+ throw notFound();
101
+ return (await readPackage(store, id, pathname.slice(base.length), searchParams)).body;
102
+ }
103
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { UDocSession } from "@docmentis/udoc-node";
2
+ /**
3
+ * The open sessions, by document id: each opened on the first request that
4
+ * needs it, kept while requests keep coming, and closed once idle or once more
5
+ * than its size are open — least recently used first, and never while a request
6
+ * is using it. A closed one is opened again, from the store, by the next
7
+ * request for it.
8
+ */
9
+ export declare class SessionPool {
10
+ #private;
11
+ constructor(open: (id: string) => Promise<UDocSession>, sessionTimeoutMs: number, size: number);
12
+ /** Run `use` on document `id`'s session, opening it if it isn't open. */
13
+ use<T>(id: string, use: (session: UDocSession) => Promise<T>): Promise<T>;
14
+ /** Close document `id`'s session, if one is open. */
15
+ close(id: string): Promise<void>;
16
+ /** Close every open session. */
17
+ closeAll(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=session-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-pool.d.ts","sourceRoot":"","sources":["../src/session-pool.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAexD;;;;;;GAMG;AACH,qBAAa,WAAW;;gBAMR,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAM9F,yEAAyE;IACnE,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAe/E,qDAAqD;IAC/C,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,gCAAgC;IAC1B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAoDlC"}
@@ -0,0 +1,95 @@
1
+ // The sessions open over what the server keeps: a cache, one per document on
2
+ // a thread of its own (see `@docmentis/udoc-node`) — opened when a request
3
+ // needs it, kept while requests keep coming, closed once idle or once too many
4
+ // are open, and reopened the next time. That is what bounds how much the
5
+ // process holds.
6
+ import { clearTimeout, setTimeout } from "node:timers";
7
+ /**
8
+ * The open sessions, by document id: each opened on the first request that
9
+ * needs it, kept while requests keep coming, and closed once idle or once more
10
+ * than its size are open — least recently used first, and never while a request
11
+ * is using it. A closed one is opened again, from the store, by the next
12
+ * request for it.
13
+ */
14
+ export class SessionPool {
15
+ #pooled = new Map();
16
+ #open;
17
+ #sessionTimeoutMs;
18
+ #size;
19
+ constructor(open, sessionTimeoutMs, size) {
20
+ this.#open = open;
21
+ this.#sessionTimeoutMs = sessionTimeoutMs;
22
+ this.#size = size;
23
+ }
24
+ /** Run `use` on document `id`'s session, opening it if it isn't open. */
25
+ async use(id, use) {
26
+ const pooled = this.#pooled.get(id) ?? this.#pool(id, this.#open(id));
27
+ pooled.busy++;
28
+ clearTimeout(pooled.idle);
29
+ pooled.idle = undefined;
30
+ this.#trim();
31
+ try {
32
+ return await use(await pooled.session);
33
+ }
34
+ finally {
35
+ pooled.busy--;
36
+ pooled.lastUsed = Date.now();
37
+ this.#settleIdle(id, pooled);
38
+ }
39
+ }
40
+ /** Close document `id`'s session, if one is open. */
41
+ async close(id) {
42
+ const pooled = this.#pooled.get(id);
43
+ if (pooled)
44
+ await this.#close(id, pooled);
45
+ }
46
+ /** Close every open session. */
47
+ async closeAll() {
48
+ await Promise.all([...this.#pooled].map(([id, pooled]) => this.#close(id, pooled)));
49
+ }
50
+ #pool(id, session) {
51
+ const pooled = { session, busy: 0, lastUsed: Date.now(), idle: undefined };
52
+ this.#pooled.set(id, pooled);
53
+ session.then(
54
+ // However it ends — closed here, or its engine gone — it is no
55
+ // longer this document's session, and the next request reopens.
56
+ (opened) => void opened.closed.then(() => this.#forget(id, pooled)),
57
+ // An open that failed holds nothing; the next request tries again.
58
+ () => this.#forget(id, pooled));
59
+ return pooled;
60
+ }
61
+ /** Once nothing is using `pooled`, start its idle clock and keep to the pool's size. */
62
+ #settleIdle(id, pooled) {
63
+ if (pooled.busy > 0 || this.#pooled.get(id) !== pooled)
64
+ return;
65
+ pooled.idle = setTimeout(() => void this.#close(id, pooled), this.#sessionTimeoutMs);
66
+ // An idle clock is no reason for the process to stay up.
67
+ pooled.idle.unref();
68
+ this.#trim();
69
+ }
70
+ /** Close idle sessions, least recently used first, until no more than the pool's size are open. */
71
+ #trim() {
72
+ while (this.#pooled.size > this.#size) {
73
+ let oldest;
74
+ for (const entry of this.#pooled) {
75
+ if (entry[1].busy === 0 && (!oldest || entry[1].lastUsed < oldest[1].lastUsed))
76
+ oldest = entry;
77
+ }
78
+ // Every open session is in use: the size is kept as they finish.
79
+ if (!oldest)
80
+ return;
81
+ void this.#close(...oldest);
82
+ }
83
+ }
84
+ async #close(id, pooled) {
85
+ this.#forget(id, pooled);
86
+ await pooled.session.then((session) => session.close(), () => { });
87
+ }
88
+ #forget(id, pooled) {
89
+ if (this.#pooled.get(id) !== pooled)
90
+ return;
91
+ clearTimeout(pooled.idle);
92
+ this.#pooled.delete(id);
93
+ }
94
+ }
95
+ //# sourceMappingURL=session-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-pool.js","sourceRoot":"","sources":["../src/session-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ import type { FileFormat, UDocType } from "@docmentis/udoc-node";
2
+ /** A document's upload, as it arrived, and the format it was imported as. */
3
+ export interface Original {
4
+ bytes: Uint8Array;
5
+ format: FileFormat;
6
+ }
7
+ /** What is kept of one document when it is imported. */
8
+ export interface KeptDocument {
9
+ original: Original;
10
+ /** Its first checkpoint, `v0`: the udoc package its import saved. */
11
+ checkpoint: Uint8Array;
12
+ }
13
+ /**
14
+ * A CRDT frontier as the engine writes it: `[peer, counter]` pairs, each a
15
+ * `u64`. A peer is random, past what a JS number holds exactly, so each is a
16
+ * `bigint`.
17
+ */
18
+ export type Frontier = [peer: bigint, counter: bigint][];
19
+ /** What a client opening a package needs to know of it. */
20
+ export interface PackageDescription {
21
+ docType: UDocType;
22
+ /** Absent for a document not imported from a file. */
23
+ sourceFormat?: FileFormat;
24
+ /** The character a CSV source split its fields by; absent for any other source. */
25
+ sourceDelimiter?: string;
26
+ /** The latest checkpoint's version. */
27
+ baselineVersion: number;
28
+ /**
29
+ * Where the edit log replays from — present exactly when there is one:
30
+ * the latest checkpoint's own frontier, or, over `v0`, a flat baseline the
31
+ * whole log was made over, the empty frontier.
32
+ */
33
+ baselineFrontier?: Frontier;
34
+ }
35
+ /**
36
+ * A route's segments under `…/package` (docs/design/node-streaming.md): a
37
+ * part's kind in kebab-case and plural, its id's key — `replica-counter` in
38
+ * hex — and a blob's key. A route is held to them before it reaches a store,
39
+ * so none names a path outside.
40
+ */
41
+ export declare const KINDS: RegExp;
42
+ export declare const PART_ID: RegExp;
43
+ export declare const BLOB_KEY: RegExp;
44
+ /** Where the server keeps each document, by id. */
45
+ export interface DocumentStore {
46
+ /** Keep a new document as `id`. Refused when `id` is already kept. */
47
+ put(id: string, document: KeptDocument): Promise<void>;
48
+ /** Document `id`'s original, or `undefined` when nothing is kept. */
49
+ getOriginal(id: string): Promise<Original | undefined>;
50
+ /** Document `id`'s latest checkpoint, or `undefined` when nothing is kept. */
51
+ getCheckpoint(id: string): Promise<Uint8Array | undefined>;
52
+ /**
53
+ * Keep `checkpoint`, the udoc package of document `id`'s checkpoint
54
+ * `version`, beside the earlier ones, as its latest; then forget each
55
+ * opaque part and blob kept beside them that it holds. Refused when no
56
+ * checkpoint is kept as `id`, or `version` is kept already.
57
+ */
58
+ putCheckpoint(id: string, version: number, checkpoint: Uint8Array): Promise<void>;
59
+ /** Document `id`'s package description, from its latest checkpoint, or `undefined` when it has none. */
60
+ getDescription(id: string): Promise<PackageDescription | undefined>;
61
+ /**
62
+ * One part of document `id`, as its JSON deflated — raw DEFLATE (RFC
63
+ * 1951) — by its route: `kinds`, its kind in kebab-case and plural, and
64
+ * `partId`, its id's key. Its latest checkpoint's, or an opaque part an
65
+ * edit brought, kept beside the checkpoints, or, held by neither, the
66
+ * newest earlier checkpoint's. `undefined` when there is no such part, or
67
+ * no checkpoint.
68
+ */
69
+ getPart(id: string, kinds: string, partId: string): Promise<Uint8Array | undefined>;
70
+ /**
71
+ * One blob of document `id`, deflated, by its key — found as a part is —
72
+ * or `undefined` when it has none.
73
+ */
74
+ getBlob(id: string, key: string): Promise<Uint8Array | undefined>;
75
+ /**
76
+ * Keep every part and blob of `parts` — a client's `opaque parts`,
77
+ * a zip in a package's own layout — beside document `id`'s checkpoints, by
78
+ * route. What is kept already stays as it was. Refused when nothing is
79
+ * kept as `id`.
80
+ */
81
+ putOpaqueParts(id: string, parts: Uint8Array): Promise<void>;
82
+ /**
83
+ * Document `id`'s edit log, or `undefined` when it has none: the one kept,
84
+ * or, for a document checkpointed before any edit was kept, its latest
85
+ * checkpoint's own.
86
+ */
87
+ getLog(id: string): Promise<Uint8Array | undefined>;
88
+ /**
89
+ * Keep `log` as document `id`'s edit log, in place of the one kept — whole
90
+ * or not at all. Refused when nothing is kept as `id`.
91
+ */
92
+ putLog(id: string, log: Uint8Array): Promise<void>;
93
+ /** Forget document `id`; answers whether anything was kept. */
94
+ delete(id: string): Promise<boolean>;
95
+ }
96
+ /**
97
+ * A {@link DocumentStore} over a directory: one directory per document, named
98
+ * by its id, holding its original, its checkpoints and its edit log.
99
+ */
100
+ export declare class DirectoryDocumentStore implements DocumentStore {
101
+ #private;
102
+ constructor(directory: string);
103
+ put(id: string, document: KeptDocument): Promise<void>;
104
+ getOriginal(id: string): Promise<Original | undefined>;
105
+ getCheckpoint(id: string): Promise<Uint8Array | undefined>;
106
+ putCheckpoint(id: string, version: number, checkpoint: Uint8Array): Promise<void>;
107
+ getDescription(id: string): Promise<PackageDescription | undefined>;
108
+ getPart(id: string, kinds: string, partId: string): Promise<Uint8Array | undefined>;
109
+ getBlob(id: string, key: string): Promise<Uint8Array | undefined>;
110
+ putOpaqueParts(id: string, parts: Uint8Array): Promise<void>;
111
+ getLog(id: string): Promise<Uint8Array | undefined>;
112
+ putLog(id: string, log: Uint8Array): Promise<void>;
113
+ delete(id: string): Promise<boolean>;
114
+ }
115
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}