@shivaedev/work-board 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +59 -18
- package/assets/native.js +36 -0
- package/dist/board.d.ts +1 -1
- package/dist/board.d.ts.map +1 -1
- package/dist/board.js +40 -8
- package/dist/board.js.map +1 -1
- package/dist/browser/client.d.ts +88 -0
- package/dist/browser/client.d.ts.map +1 -0
- package/dist/browser/client.js +53 -0
- package/dist/browser/client.js.map +1 -0
- package/dist/browser/session.d.ts +89 -0
- package/dist/browser/session.d.ts.map +1 -0
- package/dist/browser/session.js +20 -0
- package/dist/browser/session.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/files/changes.d.ts +4 -1
- package/dist/files/changes.d.ts.map +1 -1
- package/dist/files/changes.js +7 -3
- package/dist/files/changes.js.map +1 -1
- package/dist/files/list.d.ts +1 -1
- package/dist/files/list.d.ts.map +1 -1
- package/dist/files/list.js +5 -3
- package/dist/files/list.js.map +1 -1
- package/dist/files/watchDirectories.d.ts.map +1 -1
- package/dist/files/watchDirectories.js +2 -1
- package/dist/files/watchDirectories.js.map +1 -1
- package/dist/history/schema.js +1 -1
- package/dist/history/schema.js.map +1 -1
- package/dist/history/script.d.ts.map +1 -1
- package/dist/history/script.js +15 -12
- package/dist/history/script.js.map +1 -1
- package/dist/http/assets.d.ts +2 -0
- package/dist/http/assets.d.ts.map +1 -1
- package/dist/http/assets.js +14 -0
- package/dist/http/assets.js.map +1 -1
- package/dist/http/attachment.d.ts +4 -0
- package/dist/http/attachment.d.ts.map +1 -0
- package/dist/http/attachment.js +43 -0
- package/dist/http/attachment.js.map +1 -0
- package/dist/http/history.d.ts.map +1 -1
- package/dist/http/history.js +1 -13
- package/dist/http/history.js.map +1 -1
- package/dist/http/sameOrigin.d.ts +3 -0
- package/dist/http/sameOrigin.d.ts.map +1 -0
- package/dist/http/sameOrigin.js +14 -0
- package/dist/http/sameOrigin.js.map +1 -0
- package/dist/metadata/sourceLinks.d.ts.map +1 -1
- package/dist/metadata/sourceLinks.js +3 -1
- package/dist/metadata/sourceLinks.js.map +1 -1
- package/dist/page/ageScript.d.ts +2 -0
- package/dist/page/ageScript.d.ts.map +1 -0
- package/dist/page/ageScript.js +23 -0
- package/dist/page/ageScript.js.map +1 -0
- package/dist/page/client.d.ts +1 -1
- package/dist/page/client.d.ts.map +1 -1
- package/dist/page/client.js +74 -74
- package/dist/page/diagrams.d.ts +1 -1
- package/dist/page/diagrams.d.ts.map +1 -1
- package/dist/page/diagrams.js +7 -0
- package/dist/page/diagrams.js.map +1 -1
- package/dist/page/navigationScript.d.ts.map +1 -1
- package/dist/page/navigationScript.js +7 -5
- package/dist/page/navigationScript.js.map +1 -1
- package/dist/page/searchScript.d.ts.map +1 -1
- package/dist/page/searchScript.js +6 -4
- package/dist/page/searchScript.js.map +1 -1
- package/dist/page/shell.d.ts.map +1 -1
- package/dist/page/shell.js +4 -3
- package/dist/page/shell.js.map +1 -1
- package/dist/page/stateScript.js +1 -1
- package/dist/page/style.d.ts.map +1 -1
- package/dist/page/style.js +4 -2
- package/dist/page/style.js.map +1 -1
- package/dist/page/swap.d.ts +1 -1
- package/dist/page/swap.d.ts.map +1 -1
- package/dist/page/swap.js +23 -7
- package/dist/page/swap.js.map +1 -1
- package/dist/page/visualDialog.d.ts +3 -0
- package/dist/page/visualDialog.d.ts.map +1 -0
- package/dist/page/visualDialog.js +25 -0
- package/dist/page/visualDialog.js.map +1 -0
- package/dist/page/visualScript.d.ts +2 -0
- package/dist/page/visualScript.d.ts.map +1 -0
- package/dist/page/visualScript.js +42 -0
- package/dist/page/visualScript.js.map +1 -0
- package/dist/page/visualViewport.d.ts +2 -0
- package/dist/page/visualViewport.d.ts.map +1 -0
- package/dist/page/visualViewport.js +87 -0
- package/dist/page/visualViewport.js.map +1 -0
- package/dist/path/attachment.d.ts +3 -0
- package/dist/path/attachment.d.ts.map +1 -0
- package/dist/path/attachment.js +22 -0
- package/dist/path/attachment.js.map +1 -0
- package/dist/path/isMarkdown.d.ts +2 -0
- package/dist/path/isMarkdown.d.ts.map +1 -0
- package/dist/path/isMarkdown.js +7 -0
- package/dist/path/isMarkdown.js.map +1 -0
- package/dist/render/documentLinks.d.ts +2 -1
- package/dist/render/documentLinks.d.ts.map +1 -1
- package/dist/render/documentLinks.js +15 -4
- package/dist/render/documentLinks.js.map +1 -1
- package/dist/rpc/affected.d.ts +4 -0
- package/dist/rpc/affected.d.ts.map +1 -0
- package/dist/rpc/affected.js +75 -0
- package/dist/rpc/affected.js.map +1 -0
- package/dist/rpc/contract.d.ts +134 -0
- package/dist/rpc/contract.d.ts.map +1 -0
- package/dist/rpc/contract.js +59 -0
- package/dist/rpc/contract.js.map +1 -0
- package/dist/rpc/hints.d.ts +17 -0
- package/dist/rpc/hints.d.ts.map +1 -0
- package/dist/rpc/hints.js +28 -0
- package/dist/rpc/hints.js.map +1 -0
- package/dist/rpc/keys.d.ts +7 -0
- package/dist/rpc/keys.d.ts.map +1 -0
- package/dist/rpc/keys.js +9 -0
- package/dist/rpc/keys.js.map +1 -0
- package/dist/rpc/pageKeys.d.ts +3 -0
- package/dist/rpc/pageKeys.d.ts.map +1 -0
- package/dist/rpc/pageKeys.js +14 -0
- package/dist/rpc/pageKeys.js.map +1 -0
- package/dist/rpc/server.d.ts +12 -0
- package/dist/rpc/server.d.ts.map +1 -0
- package/dist/rpc/server.js +32 -0
- package/dist/rpc/server.js.map +1 -0
- package/package.json +9 -4
- package/src/board.ts +46 -9
- package/src/browser/client.ts +59 -0
- package/src/browser/session.ts +26 -0
- package/src/cli.ts +1 -1
- package/src/files/changes.ts +14 -4
- package/src/files/list.ts +5 -4
- package/src/files/watchDirectories.ts +2 -1
- package/src/history/schema.ts +1 -1
- package/src/history/script.ts +15 -12
- package/src/http/assets.ts +15 -0
- package/src/http/attachment.ts +46 -0
- package/src/http/history.ts +1 -12
- package/src/http/sameOrigin.ts +14 -0
- package/src/metadata/sourceLinks.ts +3 -1
- package/src/page/ageScript.ts +22 -0
- package/src/page/client.ts +74 -74
- package/src/page/diagrams.ts +7 -0
- package/src/page/navigationScript.ts +7 -5
- package/src/page/searchScript.ts +6 -4
- package/src/page/shell.ts +4 -3
- package/src/page/stateScript.ts +1 -1
- package/src/page/style.ts +5 -2
- package/src/page/swap.ts +23 -7
- package/src/page/visualDialog.ts +25 -0
- package/src/page/visualScript.ts +41 -0
- package/src/page/visualViewport.ts +86 -0
- package/src/path/attachment.ts +23 -0
- package/src/path/isMarkdown.ts +6 -0
- package/src/render/documentLinks.ts +15 -4
- package/src/rpc/affected.ts +77 -0
- package/src/rpc/contract.ts +62 -0
- package/src/rpc/hints.ts +34 -0
- package/src/rpc/keys.ts +9 -0
- package/src/rpc/pageKeys.ts +14 -0
- package/src/rpc/server.ts +55 -0
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { defineHastPlugin } from "satteri";
|
|
2
2
|
import { fileUrl } from "#files/url.ts";
|
|
3
|
+
import { attachmentHref } from "#path/attachment.ts";
|
|
3
4
|
|
|
4
5
|
export function documentLinks(file: string) {
|
|
5
6
|
return defineHastPlugin({
|
|
6
7
|
element: {
|
|
7
|
-
filter: ["a"],
|
|
8
|
+
filter: ["a", "img"],
|
|
8
9
|
visit: (element) => {
|
|
9
|
-
const
|
|
10
|
-
|
|
10
|
+
const attribute = element.tagName === "img" ? "src" : "href";
|
|
11
|
+
const href = element.properties[attribute];
|
|
12
|
+
if (typeof href !== "string" || href.startsWith("#") || href.startsWith("//") || /^[a-z][a-z\d+.-]*:/iu.exec(href) !== null) {
|
|
11
13
|
return;
|
|
12
14
|
}
|
|
13
15
|
const target = new URL(href, `http://work-board.local${fileUrl(file)}`);
|
|
14
|
-
|
|
16
|
+
const resolved = target.pathname + target.search + target.hash;
|
|
17
|
+
const image = attachmentHref(resolved);
|
|
18
|
+
return {
|
|
19
|
+
...element,
|
|
20
|
+
properties: {
|
|
21
|
+
...element.properties,
|
|
22
|
+
[attribute]: image ?? resolved,
|
|
23
|
+
...(element.tagName === "img" && image ? { dataLocalImage: image } : {}),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
15
26
|
},
|
|
16
27
|
},
|
|
17
28
|
name: "work-board-document-links",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Key } from "@shivaedev/effect-contract/keys.ts";
|
|
2
|
+
import { backlinksOf } from "#metadata/backlinks.ts";
|
|
3
|
+
import type { MetadataModel } from "#metadata/model.ts";
|
|
4
|
+
import { documents, identities, index, navigation } from "./keys.ts";
|
|
5
|
+
|
|
6
|
+
function uncertain(previous: MetadataModel, current: MetadataModel, paths: readonly string[]) {
|
|
7
|
+
if (previous.unavailable.length > 0 || current.unavailable.length > 0) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
const before = new Map(previous.documents.map((doc) => [doc.file, doc.parsed]));
|
|
11
|
+
const after = new Map(current.documents.map((doc) => [doc.file, doc.parsed]));
|
|
12
|
+
const known = new Set([...before.keys(), ...after.keys()]);
|
|
13
|
+
if (paths.some((path) => !known.has(path))) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
const announced = new Set(paths);
|
|
17
|
+
return [...known].some((file) => {
|
|
18
|
+
const old = before.get(file);
|
|
19
|
+
const next = after.get(file);
|
|
20
|
+
return !announced.has(file) && (old?.body !== next?.body || old?.raw !== next?.raw || old?.bodyLine !== next?.bodyLine);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function connectIdentities(model: MetadataModel, connect: (from: string, to: string) => void) {
|
|
24
|
+
for (const duplicates of model.ids.values()) {
|
|
25
|
+
const first = duplicates[0];
|
|
26
|
+
if (first === undefined) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
for (const duplicate of duplicates.slice(1)) {
|
|
30
|
+
connect(first.file, duplicate.file);
|
|
31
|
+
connect(duplicate.file, first.file);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function linkedFiles(models: readonly MetadataModel[], paths: readonly string[]) {
|
|
36
|
+
const edges = new Map<string, Set<string>>();
|
|
37
|
+
function connect(from: string, to: string) {
|
|
38
|
+
const targets = edges.get(from) ?? new Set();
|
|
39
|
+
targets.add(to);
|
|
40
|
+
edges.set(from, targets);
|
|
41
|
+
}
|
|
42
|
+
for (const model of models) {
|
|
43
|
+
connectIdentities(model, connect);
|
|
44
|
+
for (const document of model.documents) {
|
|
45
|
+
for (const link of backlinksOf(document.file, model)) {
|
|
46
|
+
connect(document.file, link.file);
|
|
47
|
+
connect(link.file, document.file);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const files = new Set(paths);
|
|
52
|
+
for (const file of files) {
|
|
53
|
+
for (const linked of edges.get(file) ?? []) {
|
|
54
|
+
files.add(linked);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return files;
|
|
58
|
+
}
|
|
59
|
+
export function affected(previous: MetadataModel, current: MetadataModel, paths: readonly string[]): readonly Key[] | undefined {
|
|
60
|
+
if (uncertain(previous, current, paths)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const models = [previous, current];
|
|
64
|
+
const files = linkedFiles(models, paths);
|
|
65
|
+
const keys: Key[] = [index.list, navigation.list, ...[...files].map((file) => documents.item(file))];
|
|
66
|
+
for (const model of models) {
|
|
67
|
+
if (model.rootFile && files.has(model.rootFile)) {
|
|
68
|
+
keys.push(documents.item(""));
|
|
69
|
+
}
|
|
70
|
+
for (const document of model.documents) {
|
|
71
|
+
if (files.has(document.file) && document.parsed.fields.id) {
|
|
72
|
+
keys.push(identities.item(document.parsed.fields.id));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return keys;
|
|
77
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { Rpc } from "effect/unstable/rpc";
|
|
3
|
+
import { contract } from "@shivaedev/effect-contract/contract.ts";
|
|
4
|
+
import { LiveHint } from "@shivaedev/effect-contract/live.ts";
|
|
5
|
+
import { query } from "@shivaedev/effect-contract/operation.ts";
|
|
6
|
+
import { HistoryInput, HistoryOutput } from "#history/schema.ts";
|
|
7
|
+
import { index, navigation, watcher, workspace } from "./keys.ts";
|
|
8
|
+
import { pageKeys } from "./pageKeys.ts";
|
|
9
|
+
|
|
10
|
+
export class ReadFailed extends Schema.TaggedError<ReadFailed>()("ReadFailed", { operation: Schema.String, status: Schema.Number }) {}
|
|
11
|
+
const PageUrl = Schema.String.check(
|
|
12
|
+
Schema.isMaxLength(8192),
|
|
13
|
+
Schema.makeFilter((value) => {
|
|
14
|
+
if (!value.startsWith("/") || value.startsWith("//")) {
|
|
15
|
+
return "Expected a local reading URL";
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const path = new URL(value, "http://127.0.0.1").pathname;
|
|
19
|
+
return (
|
|
20
|
+
path === "/"
|
|
21
|
+
|| ["/_board/work", "/_board/overview", "/_board/changes"].includes(path)
|
|
22
|
+
|| /^\/_board\/item\/[^/]+\/$/u.exec(path) !== null
|
|
23
|
+
|| /\.md$/iu.exec(path) !== null
|
|
24
|
+
|| "Expected a local reading URL"
|
|
25
|
+
);
|
|
26
|
+
} catch {
|
|
27
|
+
return "Expected a local reading URL";
|
|
28
|
+
}
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
const Page = Schema.Struct({ html: Schema.String, status: Schema.Number });
|
|
32
|
+
const Entry = Schema.Struct({
|
|
33
|
+
file: Schema.String,
|
|
34
|
+
href: Schema.String,
|
|
35
|
+
kind: Schema.Literals(["document", "heading", "passage"]),
|
|
36
|
+
line: Schema.optional(Schema.Number),
|
|
37
|
+
snippet: Schema.String,
|
|
38
|
+
text: Schema.String,
|
|
39
|
+
title: Schema.String,
|
|
40
|
+
});
|
|
41
|
+
const ReadPage = query("page", { payload: { url: PageUrl }, reads: ({ url }) => pageKeys(url), rejections: { ReadFailed }, success: Page });
|
|
42
|
+
const ReadNavigation = query("navigation", { reads: () => [workspace.list, navigation.list], rejections: { ReadFailed }, success: Schema.String });
|
|
43
|
+
const ReadSearch = query("search", {
|
|
44
|
+
payload: { query: Schema.String.check(Schema.isMaxLength(8192)) },
|
|
45
|
+
reads: () => [workspace.list, index.list],
|
|
46
|
+
rejections: { ReadFailed },
|
|
47
|
+
success: Schema.Struct({ results: Schema.Array(Entry), total: Schema.Number, unavailable: Schema.Array(Schema.String) }),
|
|
48
|
+
});
|
|
49
|
+
const ReadHistory = query("history", {
|
|
50
|
+
payload: HistoryInput,
|
|
51
|
+
reads: () => [workspace.list, index.list],
|
|
52
|
+
rejections: { ReadFailed },
|
|
53
|
+
success: HistoryOutput,
|
|
54
|
+
});
|
|
55
|
+
const ReadWatcher = query("watcher", {
|
|
56
|
+
reads: () => [workspace.list, watcher.list],
|
|
57
|
+
rejections: { ReadFailed },
|
|
58
|
+
success: Schema.Struct({ watching: Schema.Boolean }),
|
|
59
|
+
});
|
|
60
|
+
export const workContract = contract("work-board", { queries: [ReadPage, ReadNavigation, ReadSearch, ReadHistory, ReadWatcher] });
|
|
61
|
+
export const Subscribe = Rpc.make("work-board.subscribe", { error: ReadFailed, stream: true, success: LiveHint });
|
|
62
|
+
export const workRpcs = workContract.add(Subscribe);
|
package/src/rpc/hints.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Effect, PubSub, Stream } from "effect";
|
|
2
|
+
import type { LiveHint } from "@shivaedev/effect-contract/live.ts";
|
|
3
|
+
import type { Change, Changes } from "#files/changes.ts";
|
|
4
|
+
import type { Snapshot } from "#search/snapshot.ts";
|
|
5
|
+
import { affected } from "./affected.ts";
|
|
6
|
+
|
|
7
|
+
export function hints(changes: Changes, index: Effect.Effect<Snapshot, unknown>) {
|
|
8
|
+
return Stream.unwrap(
|
|
9
|
+
Effect.gen(function* () {
|
|
10
|
+
const subscription = yield* PubSub.subscribe(changes.events);
|
|
11
|
+
let sequence = changes.sequence ? yield* changes.sequence : undefined;
|
|
12
|
+
let previous = yield* Effect.option(index);
|
|
13
|
+
function nextHint(change: Change) {
|
|
14
|
+
return Effect.gen(function* () {
|
|
15
|
+
const current = yield* Effect.option(index);
|
|
16
|
+
const contiguous = sequence !== undefined && change.sequence === sequence + 1;
|
|
17
|
+
sequence = change.sequence;
|
|
18
|
+
let hint: LiveHint = { _tag: "Resync" };
|
|
19
|
+
if (contiguous && change._tag === "Changed" && previous._tag === "Some" && current._tag === "Some") {
|
|
20
|
+
const keys = affected(previous.value.model, current.value.model, change.paths);
|
|
21
|
+
if (keys !== undefined) {
|
|
22
|
+
hint = { _tag: "Changed", keys };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
previous = current;
|
|
26
|
+
return hint;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const changesStream = Stream.fromSubscription(subscription).pipe(Stream.mapEffect(nextHint));
|
|
31
|
+
return Stream.concat(Stream.succeed<LiveHint>({ _tag: "Resync" }), changesStream);
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/rpc/keys.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { collection } from "@shivaedev/effect-contract/keys.ts";
|
|
3
|
+
|
|
4
|
+
export const workspace = collection("work-board/workspace", Schema.String);
|
|
5
|
+
export const documents = collection("work-board/documents", Schema.String);
|
|
6
|
+
export const identities = collection("work-board/identities", Schema.String);
|
|
7
|
+
export const navigation = collection("work-board/navigation", Schema.String);
|
|
8
|
+
export const index = collection("work-board/index", Schema.String);
|
|
9
|
+
export const watcher = collection("work-board/watcher", Schema.String);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Key } from "@shivaedev/effect-contract/keys.ts";
|
|
2
|
+
import { documents, identities, index, workspace } from "./keys.ts";
|
|
3
|
+
|
|
4
|
+
export function pageKeys(url: string): readonly Key[] {
|
|
5
|
+
const path = new URL(url, "http://127.0.0.1").pathname;
|
|
6
|
+
const item = /^\/_board\/item\/(?<id>[^/]+)\/$/u.exec(path)?.groups?.id;
|
|
7
|
+
let key: Key = documents.item(decodeURIComponent(path.slice(1)));
|
|
8
|
+
if (item) {
|
|
9
|
+
key = identities.item(decodeURIComponent(item));
|
|
10
|
+
} else if (path.startsWith("/_board/")) {
|
|
11
|
+
key = index.list;
|
|
12
|
+
}
|
|
13
|
+
return [workspace.list, key];
|
|
14
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Clock, Effect, Layer, Ref } from "effect";
|
|
2
|
+
import { HttpIncomingMessage, HttpServerResponse } from "effect/unstable/http";
|
|
3
|
+
import { RpcSerialization, RpcServer } from "effect/unstable/rpc";
|
|
4
|
+
import type { Changes } from "#files/changes.ts";
|
|
5
|
+
import { MAX_BYTES } from "#history/limits.ts";
|
|
6
|
+
import { historyResponse } from "#history/response.ts";
|
|
7
|
+
import { HEADERS } from "#http/respond.ts";
|
|
8
|
+
import { navHtml } from "#page/nav.ts";
|
|
9
|
+
import { searchMatch } from "#search/match.ts";
|
|
10
|
+
import type { Snapshot } from "#search/snapshot.ts";
|
|
11
|
+
import { ReadFailed, workRpcs } from "./contract.ts";
|
|
12
|
+
import { hints } from "./hints.ts";
|
|
13
|
+
|
|
14
|
+
export interface ServerOptions {
|
|
15
|
+
readonly changes: Changes;
|
|
16
|
+
readonly home: string | undefined;
|
|
17
|
+
readonly index: Effect.Effect<Snapshot, unknown>;
|
|
18
|
+
readonly page: (url: string) => Effect.Effect<HttpServerResponse.HttpServerResponse, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const server = Effect.fn("WorkBoard.server")(function* ({ changes, home, index, page }: ServerOptions) {
|
|
22
|
+
const handlers = workRpcs.toLayer({
|
|
23
|
+
"work-board.history": ({ action, baseline }) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
const now = yield* Clock.currentTimeMillis;
|
|
26
|
+
const snapshot = action === "check" ? undefined : yield* index;
|
|
27
|
+
return historyResponse(baseline, now, snapshot);
|
|
28
|
+
}).pipe(Effect.catch(() => Effect.fail(new ReadFailed({ operation: "history", status: 500 })))),
|
|
29
|
+
"work-board.navigation": () =>
|
|
30
|
+
Effect.map(changes.files, (files) => navHtml(files, "", home)).pipe(
|
|
31
|
+
Effect.catch(() => Effect.fail(new ReadFailed({ operation: "navigation", status: 500 }))),
|
|
32
|
+
),
|
|
33
|
+
"work-board.page": ({ url }) =>
|
|
34
|
+
page(url).pipe(
|
|
35
|
+
Effect.flatMap((response) => {
|
|
36
|
+
if (response.status >= 500 || response.body._tag !== "Uint8Array") {
|
|
37
|
+
return Effect.fail(new ReadFailed({ operation: "page", status: response.status }));
|
|
38
|
+
}
|
|
39
|
+
return Effect.succeed({ html: new TextDecoder().decode(response.body.body), status: response.status });
|
|
40
|
+
}),
|
|
41
|
+
Effect.catch((error) => Effect.fail(error instanceof ReadFailed ? error : new ReadFailed({ operation: "page", status: 500 }))),
|
|
42
|
+
),
|
|
43
|
+
"work-board.search": ({ query }) =>
|
|
44
|
+
Effect.map(index, (snapshot) => ({ ...searchMatch(snapshot.entries, query), unavailable: snapshot.unavailable })).pipe(
|
|
45
|
+
Effect.catch(() => Effect.fail(new ReadFailed({ operation: "search", status: 500 }))),
|
|
46
|
+
),
|
|
47
|
+
"work-board.subscribe": () => hints(changes, index),
|
|
48
|
+
"work-board.watcher": () => Effect.map(Ref.get(changes.watching), (watching) => ({ watching })),
|
|
49
|
+
});
|
|
50
|
+
const handler = yield* RpcServer.toHttpEffect(workRpcs).pipe(Effect.provide(Layer.merge(handlers, RpcSerialization.layerNdjson)));
|
|
51
|
+
return handler.pipe(
|
|
52
|
+
Effect.provideService(HttpIncomingMessage.MaxBodySize, BigInt(4 * MAX_BYTES + 4096)),
|
|
53
|
+
Effect.map((response) => HttpServerResponse.setHeaders(response, HEADERS)),
|
|
54
|
+
);
|
|
55
|
+
});
|