@shivaedev/work-board 0.16.0 → 0.17.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 +18 -0
- package/README.md +4 -4
- package/assets/react.js +52 -39
- package/dist/browser/views/remaining.d.ts +0 -240
- package/dist/browser/views/remaining.d.ts.map +1 -1
- package/dist/browser/views/remaining.js +0 -2
- package/dist/browser/views/remaining.js.map +1 -1
- package/dist/client/react/DocumentAttention.d.ts.map +1 -1
- package/dist/client/react/DocumentAttention.js +2 -3
- package/dist/client/react/DocumentAttention.js.map +1 -1
- package/dist/client/react/ProjectionFrame.js +1 -1
- package/dist/client/react/ProjectionFrame.js.map +1 -1
- package/dist/client/react/RemainingMain.d.ts.map +1 -1
- package/dist/client/react/RemainingMain.js +0 -3
- package/dist/client/react/RemainingMain.js.map +1 -1
- package/dist/client/react/ResultPage.d.ts.map +1 -1
- package/dist/client/react/ResultPage.js +2 -1
- package/dist/client/react/ResultPage.js.map +1 -1
- package/dist/client/react/ShellApi.d.ts +0 -1
- package/dist/client/react/ShellApi.d.ts.map +1 -1
- package/dist/client/react/TodayOptions.d.ts.map +1 -1
- package/dist/client/react/TodayOptions.js +2 -2
- package/dist/client/react/TodayOptions.js.map +1 -1
- package/dist/client/react/entry.js +1 -1
- package/dist/client/react/entry.js.map +1 -1
- package/dist/client/react/loadBoardPage.d.ts.map +1 -1
- package/dist/client/react/loadBoardPage.js +5 -13
- package/dist/client/react/loadBoardPage.js.map +1 -1
- package/dist/client/react/loadRemaining.d.ts +109 -349
- package/dist/client/react/loadRemaining.d.ts.map +1 -1
- package/dist/client/react/loadRemaining.js +0 -2
- package/dist/client/react/loadRemaining.js.map +1 -1
- package/dist/client/react/pageQueries.d.ts.map +1 -1
- package/dist/client/react/pageQueries.js +0 -3
- package/dist/client/react/pageQueries.js.map +1 -1
- package/dist/client/react/readerLink.js +1 -1
- package/dist/client/react/readerLink.js.map +1 -1
- package/dist/client/react/remainingRoute.d.ts +1 -1
- package/dist/client/react/remainingRoute.d.ts.map +1 -1
- package/dist/client/react/remainingRoute.js +0 -2
- package/dist/client/react/remainingRoute.js.map +1 -1
- package/dist/responses/url.js +1 -1
- package/dist/responses/url.js.map +1 -1
- package/dist/rpc/PageUrl.d.ts.map +1 -1
- package/dist/rpc/PageUrl.js +0 -1
- package/dist/rpc/PageUrl.js.map +1 -1
- package/dist/rpc/contract.d.ts +2203 -2926
- package/dist/rpc/contract.d.ts.map +1 -1
- package/dist/rpc/contract.js +6 -9
- package/dist/rpc/contract.js.map +1 -1
- package/dist/rpc/responseContract.d.ts +56 -56
- package/dist/server/{responses/service.d.ts → Responses.d.ts} +9 -21
- package/dist/server/Responses.d.ts.map +1 -0
- package/dist/server/{responses/service.js → Responses.js} +22 -22
- package/dist/server/Responses.js.map +1 -0
- package/dist/server/attention/inbox.d.ts +2 -0
- package/dist/server/attention/inbox.d.ts.map +1 -1
- package/dist/server/attention/inbox.js +1 -0
- package/dist/server/attention/inbox.js.map +1 -1
- package/dist/server/attention/projection.js +1 -1
- package/dist/server/attention/projection.js.map +1 -1
- package/dist/server/attention/requestInbox.d.ts +3 -0
- package/dist/server/attention/requestInbox.d.ts.map +1 -1
- package/dist/server/attention/requestInbox.js +1 -0
- package/dist/server/attention/requestInbox.js.map +1 -1
- package/dist/server/boardRuntime.d.ts +2 -2
- package/dist/server/boardRuntime.d.ts.map +1 -1
- package/dist/server/boardRuntime.js +12 -16
- package/dist/server/boardRuntime.js.map +1 -1
- package/dist/server/boardServices.d.ts +3 -4
- package/dist/server/boardServices.d.ts.map +1 -1
- package/dist/server/boardServices.js +5 -4
- package/dist/server/boardServices.js.map +1 -1
- package/dist/server/boardSocket.d.ts +1 -1
- package/dist/server/boardSocket.d.ts.map +1 -1
- package/dist/server/document/read.d.ts +4 -10
- package/dist/server/document/read.d.ts.map +1 -1
- package/dist/server/document/read.js +5 -5
- package/dist/server/document/read.js.map +1 -1
- package/dist/server/document/requests.d.ts.map +1 -1
- package/dist/server/document/requests.js +1 -2
- package/dist/server/document/requests.js.map +1 -1
- package/dist/server/handoffs/Service.d.ts +1 -2
- package/dist/server/handoffs/Service.d.ts.map +1 -1
- package/dist/server/handoffs/Service.js +14 -13
- package/dist/server/handoffs/Service.js.map +1 -1
- package/dist/server/http/assets.d.ts +0 -3
- package/dist/server/http/assets.d.ts.map +1 -1
- package/dist/server/http/assets.js +1 -7
- package/dist/server/http/assets.js.map +1 -1
- package/dist/server/http/boardRoutes.d.ts.map +1 -1
- package/dist/server/http/boardRoutes.js +3 -3
- package/dist/server/http/boardRoutes.js.map +1 -1
- package/dist/server/integration/responses.d.ts +207 -4
- package/dist/server/integration/responses.d.ts.map +1 -1
- package/dist/server/integration/responses.js +4 -2
- package/dist/server/integration/responses.js.map +1 -1
- package/dist/server/mcp/client.d.ts +2 -242
- package/dist/server/mcp/client.d.ts.map +1 -1
- package/dist/server/mcp/queries.d.ts +24 -744
- package/dist/server/mcp/queries.d.ts.map +1 -1
- package/dist/server/mcp/queries.js +3 -3
- package/dist/server/mcp/queries.js.map +1 -1
- package/dist/server/packageInfo.d.ts +1 -1
- package/dist/server/packageInfo.js +1 -1
- package/dist/server/responses/readingActions.d.ts +10 -1
- package/dist/server/responses/readingActions.d.ts.map +1 -1
- package/dist/server/responses/readingActions.js +2 -4
- package/dist/server/responses/readingActions.js.map +1 -1
- package/dist/server/responses/registerQuestion.d.ts +2 -2
- package/dist/server/responses/registerQuestion.d.ts.map +1 -1
- package/dist/server/responses/registerQuestion.js +3 -3
- package/dist/server/responses/registerQuestion.js.map +1 -1
- package/dist/server/responses/retry.d.ts +2 -2
- package/dist/server/responses/retry.d.ts.map +1 -1
- package/dist/server/responses/retry.js +1 -1
- package/dist/server/responses/retry.js.map +1 -1
- package/dist/server/rpc/HandlerOptions.d.ts +0 -8
- package/dist/server/rpc/HandlerOptions.d.ts.map +1 -1
- package/dist/server/rpc/handlers.d.ts +1 -1
- package/dist/server/rpc/handlers.d.ts.map +1 -1
- package/dist/server/rpc/handlers.js +14 -13
- package/dist/server/rpc/handlers.js.map +1 -1
- package/dist/server/rpc/hints.d.ts +3 -5
- package/dist/server/rpc/hints.d.ts.map +1 -1
- package/dist/server/rpc/hints.js +5 -1
- package/dist/server/rpc/hints.js.map +1 -1
- package/dist/server/views/fleetItems.d.ts +2 -2
- package/dist/server/views/fleetItems.d.ts.map +1 -1
- package/dist/server/views/fleetItems.js +8 -3
- package/dist/server/views/fleetItems.js.map +1 -1
- package/dist/server/views/projectionQueries.d.ts +5 -9
- package/dist/server/views/projectionQueries.d.ts.map +1 -1
- package/dist/server/views/projectionQueries.js +6 -6
- package/dist/server/views/projectionQueries.js.map +1 -1
- package/dist/server/views/remainingQueries.d.ts +4 -252
- package/dist/server/views/remainingQueries.d.ts.map +1 -1
- package/dist/server/views/remainingQueries.js +11 -19
- package/dist/server/views/remainingQueries.js.map +1 -1
- package/dist/server/workspace.d.ts +222 -0
- package/dist/server/workspace.d.ts.map +1 -0
- package/dist/server/workspace.js +25 -0
- package/dist/server/workspace.js.map +1 -0
- package/package.json +3 -3
- package/src/browser/views/remaining.ts +0 -3
- package/src/client/react/DocumentAttention.tsx +2 -3
- package/src/client/react/ProjectionFrame.tsx +1 -1
- package/src/client/react/RemainingMain.tsx +0 -10
- package/src/client/react/ResultPage.tsx +2 -2
- package/src/client/react/ShellApi.ts +0 -1
- package/src/client/react/TodayOptions.tsx +2 -2
- package/src/client/react/entry.tsx +1 -1
- package/src/client/react/loadBoardPage.ts +5 -14
- package/src/client/react/loadRemaining.ts +0 -2
- package/src/client/react/pageQueries.ts +0 -3
- package/src/client/react/readerLink.ts +1 -1
- package/src/client/react/remainingRoute.ts +0 -2
- package/src/responses/url.ts +1 -1
- package/src/rpc/PageUrl.ts +0 -1
- package/src/rpc/contract.ts +6 -9
- package/src/server/{responses/service.ts → Responses.ts} +21 -29
- package/src/server/attention/inbox.ts +1 -0
- package/src/server/attention/projection.ts +1 -1
- package/src/server/attention/requestInbox.ts +1 -0
- package/src/server/boardRuntime.ts +20 -25
- package/src/server/boardServices.ts +7 -7
- package/src/server/document/read.ts +6 -14
- package/src/server/document/requests.ts +1 -2
- package/src/server/handoffs/Service.ts +16 -19
- package/src/server/http/assets.ts +1 -7
- package/src/server/http/boardRoutes.ts +3 -3
- package/src/server/integration/responses.ts +5 -3
- package/src/server/mcp/queries.ts +3 -3
- package/src/server/packageInfo.ts +1 -1
- package/src/server/responses/readingActions.ts +3 -3
- package/src/server/responses/registerQuestion.ts +5 -5
- package/src/server/responses/retry.ts +3 -3
- package/src/server/rpc/HandlerOptions.ts +0 -8
- package/src/server/rpc/handlers.ts +17 -15
- package/src/server/rpc/hints.ts +6 -3
- package/src/server/views/fleetItems.ts +8 -4
- package/src/server/views/projectionQueries.ts +6 -7
- package/src/server/views/remainingQueries.ts +11 -31
- package/src/server/workspace.ts +41 -0
- package/assets/native.js +0 -37
- package/dist/client/entry.d.ts +0 -3
- package/dist/client/entry.d.ts.map +0 -1
- package/dist/client/entry.js +0 -3
- package/dist/client/entry.js.map +0 -1
- package/dist/client/react/RespondPage.d.ts +0 -5
- package/dist/client/react/RespondPage.d.ts.map +0 -1
- package/dist/client/react/RespondPage.js +0 -5
- package/dist/client/react/RespondPage.js.map +0 -1
- package/dist/server/responses/service.d.ts.map +0 -1
- package/dist/server/responses/service.js.map +0 -1
- package/src/client/entry.ts +0 -3
- package/src/client/react/RespondPage.tsx +0 -8
|
@@ -9,26 +9,27 @@ import { handoffFrom, handoffMarkdown, handoffPrompt, matchesHandoff } from "#se
|
|
|
9
9
|
import { handoffSource } from "#server/handoffs/source.ts";
|
|
10
10
|
import { publish } from "#server/responses/publish.ts";
|
|
11
11
|
import { revisionOf } from "#server/responses/records.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { Workspace } from "#server/workspace.ts";
|
|
13
13
|
|
|
14
14
|
export const HandoffsService = defineService({
|
|
15
15
|
id: "WorkBoard/HandoffsService",
|
|
16
16
|
initialize: Effect.gen(function* () {
|
|
17
|
-
|
|
17
|
+
const workspace = yield* Workspace;
|
|
18
|
+
return { fs: yield* FileSystem.FileSystem, lock: yield* Semaphore.make(1), options: yield* workspace.settings(), workspace };
|
|
18
19
|
}),
|
|
19
|
-
methods: ({ options, fs, lock }) => {
|
|
20
|
-
const snapshot =
|
|
20
|
+
methods: ({ options, fs, lock, workspace }) => {
|
|
21
|
+
const snapshot = workspace.index().pipe(
|
|
21
22
|
rpcBoundary(ServiceUnavailable, {
|
|
22
23
|
message: "The workspace could not be indexed.",
|
|
23
24
|
unexpected: (message) => new ServiceUnavailable({ message }),
|
|
24
25
|
}),
|
|
25
26
|
);
|
|
26
|
-
const locate = handoffSource(options.root, options.
|
|
27
|
+
const locate = handoffSource(options.root, options.realRoot, snapshot, fs);
|
|
27
28
|
const read = Effect.fn("WorkBoard.read")(function* (item: string) {
|
|
28
29
|
const data = yield* snapshot;
|
|
29
30
|
const unknown = [...data.unavailable];
|
|
30
31
|
const records = data.documents.flatMap((document) => {
|
|
31
|
-
const record = handoffFrom(document, options.
|
|
32
|
+
const record = handoffFrom(document, options.realRoot);
|
|
32
33
|
if (
|
|
33
34
|
(record && data.model.ids.get(record.id)?.length !== 1)
|
|
34
35
|
|| (document.parsed.fields.kind === "handoff" && !record)
|
|
@@ -45,7 +46,7 @@ export const HandoffsService = defineService({
|
|
|
45
46
|
if (!record || record.file !== `handoffs/${input.id}.md` || !matchesHandoff(record, input) || revisionOf(record.context) !== input.revision) {
|
|
46
47
|
return yield* Effect.fail(new Conflict({ message: "This handoff identity moved, is duplicated or records different direction." }));
|
|
47
48
|
}
|
|
48
|
-
const path = yield* markdownPath(options.root, options.
|
|
49
|
+
const path = yield* markdownPath(options.root, options.realRoot, record.file);
|
|
49
50
|
if (!path) {
|
|
50
51
|
return yield* Effect.fail(new NotFound({ message: "The prepared handoff moved or disappeared." }));
|
|
51
52
|
}
|
|
@@ -55,20 +56,20 @@ export const HandoffsService = defineService({
|
|
|
55
56
|
unexpected: (message) => new ServiceUnavailable({ message }),
|
|
56
57
|
}),
|
|
57
58
|
);
|
|
58
|
-
yield* publish(options.root, options.
|
|
59
|
+
yield* publish(options.root, options.realRoot, record.id, raw, "handoffs");
|
|
59
60
|
return record;
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
function prepare(input: HandoffInput) {
|
|
63
64
|
return lock.withPermit(
|
|
64
65
|
Effect.gen(function* () {
|
|
65
|
-
if (!options.
|
|
66
|
+
if (!options.writable) {
|
|
66
67
|
return yield* Effect.fail(new Forbidden({ message: "Restart with --responses to enable local response/handoff writes." }));
|
|
67
68
|
}
|
|
68
69
|
const data = yield* snapshot;
|
|
69
70
|
const existing = data.model.ids.get(input.id);
|
|
70
71
|
if (existing?.length) {
|
|
71
|
-
const record = existing.length === 1 ? handoffFrom(existing[0], options.
|
|
72
|
+
const record = existing.length === 1 ? handoffFrom(existing[0], options.realRoot) : undefined;
|
|
72
73
|
return yield* reconcile(record, input);
|
|
73
74
|
}
|
|
74
75
|
const current = yield* locate(input.item);
|
|
@@ -91,21 +92,17 @@ export const HandoffsService = defineService({
|
|
|
91
92
|
state: "requested",
|
|
92
93
|
},
|
|
93
94
|
id: input.id,
|
|
94
|
-
prompt: handoffPrompt(join(options.
|
|
95
|
+
prompt: handoffPrompt(join(options.realRoot, file)),
|
|
95
96
|
};
|
|
96
|
-
yield* publish(
|
|
97
|
-
options.
|
|
98
|
-
|
|
99
|
-
record.id,
|
|
100
|
-
handoffMarkdown(record.id, record.handoff, record.context),
|
|
101
|
-
"handoffs",
|
|
102
|
-
).pipe(Effect.ensuring(options.changes.refresh ?? Effect.void));
|
|
97
|
+
yield* publish(options.root, options.realRoot, record.id, handoffMarkdown(record.id, record.handoff, record.context), "handoffs").pipe(
|
|
98
|
+
Effect.ensuring(options.changes.refresh ?? Effect.void),
|
|
99
|
+
);
|
|
103
100
|
return record;
|
|
104
101
|
}),
|
|
105
102
|
);
|
|
106
103
|
}
|
|
107
104
|
return { locate, prepare, read };
|
|
108
105
|
},
|
|
109
|
-
requires: [
|
|
106
|
+
requires: [Workspace, FileSystem.FileSystem, Path.Path],
|
|
110
107
|
});
|
|
111
108
|
export type HandoffsService = typeof HandoffsService.Identifier;
|
|
@@ -35,7 +35,6 @@ export function mermaidFile(root: string) {
|
|
|
35
35
|
|
|
36
36
|
export const MERMAID_ROUTE: HttpRouter.PathInput = `${MERMAID}*`;
|
|
37
37
|
|
|
38
|
-
export const NATIVE_ROUTE: HttpRouter.PathInput = "/_board/native.js";
|
|
39
38
|
function missingBundle(file: string) {
|
|
40
39
|
return `Work Board browser bundle assets/${file} is missing. Run pnpm --filter @shivaedev/work-board build:browser from the repository root to build it.`;
|
|
41
40
|
}
|
|
@@ -57,16 +56,11 @@ const checkAssets = Effect.fn("WorkBoard.checkAssets")(function* (files: readonl
|
|
|
57
56
|
}
|
|
58
57
|
});
|
|
59
58
|
|
|
60
|
-
export function checkNativeAsset() {
|
|
61
|
-
return checkAssets(["native.js"]);
|
|
62
|
-
}
|
|
63
59
|
export function checkBrowserAssets() {
|
|
64
|
-
return checkAssets(["
|
|
60
|
+
return checkAssets(["react.js", "react.css"]);
|
|
65
61
|
}
|
|
66
|
-
export const nativeAsset = bundledAsset("native.js", "text/javascript");
|
|
67
62
|
|
|
68
63
|
const BUNDLED_ASSETS: ReadonlyArray<readonly [HttpRouter.PathInput, ReturnType<typeof bundledAsset>]> = [
|
|
69
|
-
[NATIVE_ROUTE, nativeAsset],
|
|
70
64
|
["/_board/react.js", bundledAsset("react.js", "text/javascript")],
|
|
71
65
|
["/_board/react.css", bundledAsset("react.css", "text/css")],
|
|
72
66
|
];
|
|
@@ -2,13 +2,13 @@ import { Effect, type FileSystem, type Path, type Scope } from "effect";
|
|
|
2
2
|
import { HttpRouter, type HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
3
3
|
import { loopbackOnly } from "@shivaedev/platform/rpc-server/local.ts";
|
|
4
4
|
import type { Diagnostics } from "#diagnostics/source.ts";
|
|
5
|
+
import { responseUrl } from "#responses/url.ts";
|
|
5
6
|
import type { BoardOptions } from "#server/board.ts";
|
|
6
7
|
import type { boardRuntime } from "#server/boardRuntime.ts";
|
|
7
8
|
import type { HandoffsService } from "#server/handoffs/Service.ts";
|
|
8
|
-
import { identityUrl } from "#server/metadata/links.ts";
|
|
9
9
|
import { shell } from "#server/page/shell.ts";
|
|
10
|
+
import { Responses } from "#server/Responses.ts";
|
|
10
11
|
import type { Highlighter } from "#server/render/Highlighter.ts";
|
|
11
|
-
import { Responses } from "#server/responses/service.ts";
|
|
12
12
|
import { ASSET_HANDLERS, MERMAID_ROUTE, mermaidFile } from "./assets.ts";
|
|
13
13
|
import { attachment } from "./attachment.ts";
|
|
14
14
|
import { identity } from "./identity.ts";
|
|
@@ -47,7 +47,7 @@ export function boardRoutes(options: BoardOptions, runtime: Effect.Success<Retur
|
|
|
47
47
|
const params = new URL(request.url, "http://127.0.0.1").searchParams;
|
|
48
48
|
const service = yield* Responses;
|
|
49
49
|
const preview = yield* service.locate(params.get("item") ?? "", params.get("request") ?? "");
|
|
50
|
-
return HttpServerResponse.redirect(
|
|
50
|
+
return HttpServerResponse.redirect(responseUrl(preview.item, preview.request));
|
|
51
51
|
}).pipe(Effect.catch(() => Effect.succeed(respond("Response unavailable", "text/html", 409)))),
|
|
52
52
|
);
|
|
53
53
|
for (const view of ["system", "start", "handoff", "result", "library", "goals", "work"] as const) {
|
|
@@ -5,13 +5,14 @@ import {
|
|
|
5
5
|
RecordedResponse as RecordedResponseSchema,
|
|
6
6
|
} from "#browser/responses/schema.ts";
|
|
7
7
|
import { currentResponses } from "#responses/current.ts";
|
|
8
|
+
import { Responses } from "#server/Responses.ts";
|
|
8
9
|
import { makeResponse } from "#server/responses/make.ts";
|
|
9
10
|
import { publish } from "#server/responses/publish.ts";
|
|
10
11
|
import { readResponses } from "#server/responses/read.ts";
|
|
11
12
|
import { questionFrom, questionId, questionMarkdown, responseMarkdown, revisionOf } from "#server/responses/records.ts";
|
|
12
|
-
import { type
|
|
13
|
+
import { type WorkspaceOptions as InternalWorkspaceOptions, Workspace, WorkspaceConfig } from "#server/workspace.ts";
|
|
13
14
|
|
|
14
|
-
export type
|
|
15
|
+
export type WorkspaceOptions = InternalWorkspaceOptions;
|
|
15
16
|
export type DraftInput = typeof DraftInputSchema.Type;
|
|
16
17
|
export type Question = typeof QuestionSchema.Type;
|
|
17
18
|
export type Reading = typeof ReadingSchema.Type;
|
|
@@ -28,9 +29,10 @@ export const responses = {
|
|
|
28
29
|
questionMarkdown,
|
|
29
30
|
Reading: ReadingSchema,
|
|
30
31
|
RecordedResponse: RecordedResponseSchema,
|
|
31
|
-
ResponseConfig,
|
|
32
32
|
Responses,
|
|
33
33
|
readResponses,
|
|
34
34
|
responseMarkdown,
|
|
35
35
|
revisionOf,
|
|
36
|
+
Workspace,
|
|
37
|
+
WorkspaceConfig,
|
|
36
38
|
} as const;
|
|
@@ -2,6 +2,7 @@ import { Clock, Effect } from "effect";
|
|
|
2
2
|
import { cardStatus } from "#browser/views/now.ts";
|
|
3
3
|
import type { InboxProjection } from "#browser/views/schema.ts";
|
|
4
4
|
import { fileUrl } from "#files/url.ts";
|
|
5
|
+
import { responseUrl } from "#responses/url.ts";
|
|
5
6
|
import { identityUrl } from "#server/metadata/links.ts";
|
|
6
7
|
import type { BoardClient } from "./client.ts";
|
|
7
8
|
|
|
@@ -11,12 +12,11 @@ export const needsYou = Effect.fn("BoardMcp.needsYou")(function* (client: BoardC
|
|
|
11
12
|
const you = yield* Effect.forEach(inbox.you, (row) =>
|
|
12
13
|
Effect.gen(function* () {
|
|
13
14
|
const document = yield* client("work-board.document", { url: fileUrl(row.file) });
|
|
14
|
-
const
|
|
15
|
-
const decision = document.requests.find((entry) => entry.id === request);
|
|
15
|
+
const decision = document.requests.find((entry) => responseUrl(document.metadata.id ?? "", entry.id) === row.href);
|
|
16
16
|
return {
|
|
17
17
|
...row,
|
|
18
18
|
ageMs: row.modified === undefined ? null : Math.max(0, now - row.modified),
|
|
19
|
-
options: (
|
|
19
|
+
options: (decision ? decision.options : document.metadata.options) ?? [],
|
|
20
20
|
prompts: decision?.prompts ?? [],
|
|
21
21
|
promptUnavailable: decision?.promptUnavailable,
|
|
22
22
|
recommended: decision?.recommended ?? document.metadata.recommended,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export const NAME = "work-board";
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.17.0";
|
|
@@ -3,7 +3,7 @@ import { BadRequest } from "@shivaedev/platform/errors/taxonomy.ts";
|
|
|
3
3
|
import { responseUrl } from "#responses/url.ts";
|
|
4
4
|
import { authoredQuestion } from "#server/attention/inbox.ts";
|
|
5
5
|
import type { MetadataDocument } from "#server/metadata/model.ts";
|
|
6
|
-
import {
|
|
6
|
+
import type { locate } from "./locate.ts";
|
|
7
7
|
import { questionTemplate } from "./template.ts";
|
|
8
8
|
|
|
9
9
|
export const readingActions = Effect.fn("WorkBoard.readingActions")(function* (
|
|
@@ -11,16 +11,16 @@ export const readingActions = Effect.fn("WorkBoard.readingActions")(function* (
|
|
|
11
11
|
source: string,
|
|
12
12
|
enabled: boolean,
|
|
13
13
|
identity: string | undefined,
|
|
14
|
+
locateQuestion: ReturnType<typeof locate>,
|
|
14
15
|
) {
|
|
15
16
|
if (!(enabled && identity)) {
|
|
16
17
|
return undefined;
|
|
17
18
|
}
|
|
18
|
-
const service = yield* Responses;
|
|
19
19
|
const item = identity;
|
|
20
20
|
const requests = authoredQuestion(document) ? ["answer"] : (document.parsed.fields.attention ?? []).map((request) => request.id);
|
|
21
21
|
const actions = new Map<string, string>();
|
|
22
22
|
for (const request of requests) {
|
|
23
|
-
const question = yield*
|
|
23
|
+
const question = yield* locateQuestion(item, request).pipe(Effect.option);
|
|
24
24
|
if (question._tag === "Some" && question.value.context === source) {
|
|
25
25
|
const template = yield* Effect.try({
|
|
26
26
|
catch: (cause) => new BadRequest({ message: cause instanceof Error ? cause.message : "Review the question template and retry." }),
|
|
@@ -2,21 +2,21 @@ import { Clock, Effect } from "effect";
|
|
|
2
2
|
import { Conflict, Forbidden, PreconditionFailed, type ServiceUnavailable } from "@shivaedev/platform/errors/taxonomy.ts";
|
|
3
3
|
import type { Question } from "#browser/responses/schema.ts";
|
|
4
4
|
import type { Snapshot } from "#server/search/snapshot.ts";
|
|
5
|
+
import type { WorkspaceSettings } from "#server/workspace.ts";
|
|
5
6
|
import type { locate as locateQuestion } from "./locate.ts";
|
|
6
7
|
import { publish } from "./publish.ts";
|
|
7
8
|
import type { readResponses } from "./read.ts";
|
|
8
9
|
import { questionMarkdown } from "./records.ts";
|
|
9
|
-
import type { ResponseOptions } from "./service.ts";
|
|
10
10
|
|
|
11
11
|
const HOURS_48 = 48 * 60 * 60 * 1000;
|
|
12
12
|
export function registerQuestion(
|
|
13
|
-
options:
|
|
13
|
+
options: WorkspaceSettings,
|
|
14
14
|
snapshot: Effect.Effect<Snapshot, ServiceUnavailable>,
|
|
15
15
|
locate: ReturnType<typeof locateQuestion>,
|
|
16
16
|
read: ReturnType<typeof readResponses>,
|
|
17
17
|
) {
|
|
18
18
|
return Effect.fn("WorkBoard.registerQuestion")(function* (input: { readonly item: string; readonly request: string; readonly revision: string }) {
|
|
19
|
-
if (!options.
|
|
19
|
+
if (!options.writable) {
|
|
20
20
|
return yield* Effect.fail(new Forbidden({ message: "Start with --responses to explicitly enable local question/response writes." }));
|
|
21
21
|
}
|
|
22
22
|
const preview = yield* locate(input.item, input.request);
|
|
@@ -32,7 +32,7 @@ export function registerQuestion(
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
const saved = (yield* read(preview.id)).question;
|
|
35
|
-
yield* publish(options.root, options.
|
|
35
|
+
yield* publish(options.root, options.realRoot, saved.id, questionMarkdown(saved));
|
|
36
36
|
return saved;
|
|
37
37
|
}
|
|
38
38
|
const now = yield* Clock.currentTimeMillis;
|
|
@@ -49,7 +49,7 @@ export function registerQuestion(
|
|
|
49
49
|
source: preview.source,
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
|
-
yield* publish(options.root, options.
|
|
52
|
+
yield* publish(options.root, options.realRoot, question.id, questionMarkdown(question)).pipe(
|
|
53
53
|
Effect.ensuring(options.changes.refresh ?? Effect.void),
|
|
54
54
|
);
|
|
55
55
|
return question;
|
|
@@ -3,15 +3,15 @@ import { Conflict } from "@shivaedev/platform/errors/taxonomy.ts";
|
|
|
3
3
|
import type { DraftInput, RecordedResponse } from "#browser/responses/schema.ts";
|
|
4
4
|
import type { ResponseRejection } from "#rpc/rejections.ts";
|
|
5
5
|
import type { Snapshot } from "#server/search/snapshot.ts";
|
|
6
|
+
import type { WorkspaceSettings } from "#server/workspace.ts";
|
|
6
7
|
import { publish } from "./publish.ts";
|
|
7
8
|
import { matchesResponse, responseMarkdown } from "./records.ts";
|
|
8
|
-
import type { ResponseOptions } from "./service.ts";
|
|
9
9
|
|
|
10
10
|
export const retryResponse = Effect.fn("WorkBoard.retryResponse")(function* (
|
|
11
11
|
existing: RecordedResponse,
|
|
12
12
|
input: DraftInput,
|
|
13
13
|
snapshot: Effect.Effect<Snapshot, ResponseRejection>,
|
|
14
|
-
options:
|
|
14
|
+
options: WorkspaceSettings,
|
|
15
15
|
) {
|
|
16
16
|
if (!matchesResponse(existing, input)) {
|
|
17
17
|
return yield* Effect.fail(new Conflict({ message: "The response identity already records different content." }));
|
|
@@ -19,5 +19,5 @@ export const retryResponse = Effect.fn("WorkBoard.retryResponse")(function* (
|
|
|
19
19
|
if ((yield* snapshot).model.ids.get(input.id)?.[0]?.file !== `responses/${input.id}.md`) {
|
|
20
20
|
return yield* Effect.fail(new Conflict({ message: "The saved response moved. Read its existing record instead of retrying a write." }));
|
|
21
21
|
}
|
|
22
|
-
yield* publish(options.root, options.
|
|
22
|
+
yield* publish(options.root, options.realRoot, existing.id, responseMarkdown(existing));
|
|
23
23
|
});
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import type { Effect } from "effect";
|
|
2
2
|
import type { ServiceUnavailable } from "@shivaedev/platform/errors/taxonomy.ts";
|
|
3
|
-
import type { Document } from "#browser/document/schema.ts";
|
|
4
3
|
import type { Server } from "#rpc/Server.ts";
|
|
5
|
-
import type { Changes } from "#server/files/changes.ts";
|
|
6
|
-
import type { Snapshot } from "#server/search/snapshot.ts";
|
|
7
4
|
|
|
8
5
|
export interface HandlerOptions {
|
|
9
|
-
readonly changes: Changes;
|
|
10
|
-
readonly document?: (url: string) => Effect.Effect<Document, unknown>;
|
|
11
6
|
readonly fleetPort?: number | null;
|
|
12
|
-
readonly home: string | undefined;
|
|
13
|
-
readonly index: Effect.Effect<Snapshot, unknown>;
|
|
14
7
|
readonly server?: Effect.Effect<typeof Server.Type, ServiceUnavailable>;
|
|
15
|
-
readonly writable?: boolean;
|
|
16
8
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { Effect, Layer, Ref, Stream } from "effect";
|
|
2
|
-
import { InternalServerError,
|
|
2
|
+
import { InternalServerError, ServiceUnavailable } from "@shivaedev/platform/errors/taxonomy.ts";
|
|
3
3
|
import { rpcBoundary } from "@shivaedev/platform/rpc-server/boundary.ts";
|
|
4
4
|
import { requestTracingLayer } from "@shivaedev/platform/rpc-server/tracing.ts";
|
|
5
|
-
import { Diagnostics } from "#diagnostics/source.ts";
|
|
6
5
|
import { workRpcs } from "#rpc/contract.ts";
|
|
7
6
|
import { DocumentRejection, ResponseRejection } from "#rpc/rejections.ts";
|
|
8
7
|
import { HandoffsService } from "#server/handoffs/Service.ts";
|
|
9
8
|
import { libraryData } from "#server/navigation/libraryData.ts";
|
|
10
9
|
import { navigationData } from "#server/navigation/model.ts";
|
|
11
|
-
import { Responses } from "#server/
|
|
10
|
+
import { Responses } from "#server/Responses.ts";
|
|
12
11
|
import { searchMatch } from "#server/search/match.ts";
|
|
13
12
|
import { fleetItems } from "#server/views/fleetItems.ts";
|
|
14
13
|
import { goals } from "#server/views/goals.ts";
|
|
15
14
|
import { projectionQueries } from "#server/views/projectionQueries.ts";
|
|
16
15
|
import { remainingQueries } from "#server/views/remainingQueries.ts";
|
|
16
|
+
import { Workspace } from "#server/workspace.ts";
|
|
17
17
|
import type { HandlerOptions } from "./HandlerOptions.ts";
|
|
18
18
|
import { hints } from "./hints.ts";
|
|
19
19
|
|
|
@@ -21,25 +21,29 @@ const readBoundary = rpcBoundary(InternalServerError, { unexpected: (message) =>
|
|
|
21
21
|
const documentBoundary = rpcBoundary(DocumentRejection, { unexpected: (message) => new InternalServerError({ message }) });
|
|
22
22
|
const responseBoundary = rpcBoundary(ResponseRejection, { unexpected: (message) => new InternalServerError({ message }) });
|
|
23
23
|
|
|
24
|
-
export const handlers = Effect.fn("WorkBoard.handlers")(function* (options: HandlerOptions) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
24
|
+
export const handlers = Effect.fn("WorkBoard.handlers")(function* (options: HandlerOptions = {}) {
|
|
25
|
+
const workspace = yield* Workspace;
|
|
26
|
+
const { changes, home } = yield* workspace.settings();
|
|
27
|
+
const index = workspace.index();
|
|
28
|
+
const document = workspace.document;
|
|
29
|
+
const projections = yield* projectionQueries;
|
|
27
30
|
const handoffs = yield* HandoffsService;
|
|
28
31
|
const responses = yield* Responses;
|
|
29
|
-
const
|
|
30
|
-
const remaining = remainingQueries(options, { diagnostics, handoffs, responses });
|
|
32
|
+
const remaining = yield* remainingQueries;
|
|
31
33
|
return workRpcs
|
|
32
34
|
.toLayer({
|
|
33
35
|
"work-board.awaitResponse": (input) => Effect.suspend(() => responses.awaitResponse(input)).pipe(responseBoundary),
|
|
34
36
|
"work-board.board": ({ url }) => Effect.suspend(() => projections.board(url)).pipe(documentBoundary),
|
|
35
37
|
"work-board.diagnostics": () => remaining.diagnostics().pipe(readBoundary),
|
|
36
|
-
"work-board.document": ({ url }) =>
|
|
37
|
-
|
|
38
|
-
"work-board.fleetItems": () => fleetItems(options),
|
|
38
|
+
"work-board.document": ({ url }) => Effect.suspend(() => document(url)).pipe(documentBoundary),
|
|
39
|
+
"work-board.fleetItems": () => fleetItems(options.fleetPort).pipe(readBoundary),
|
|
39
40
|
"work-board.goals": () =>
|
|
40
41
|
Effect.gen(function* () {
|
|
41
42
|
const snapshot = yield* index;
|
|
42
|
-
return goals(
|
|
43
|
+
return goals(
|
|
44
|
+
snapshot,
|
|
45
|
+
yield* fleetItems(options.fleetPort).pipe(Effect.provideService(Workspace, { ...workspace, index: () => Effect.succeed(snapshot) })),
|
|
46
|
+
);
|
|
43
47
|
}).pipe(readBoundary),
|
|
44
48
|
"work-board.handoff": ({ url }) => remaining.handoff(url).pipe(documentBoundary),
|
|
45
49
|
"work-board.handoffSource": ({ item }) => Effect.suspend(() => handoffs.locate(item)).pipe(responseBoundary),
|
|
@@ -57,7 +61,6 @@ export const handlers = Effect.fn("WorkBoard.handlers")(function* (options: Hand
|
|
|
57
61
|
"work-board.question": ({ item, request }) => Effect.suspend(() => responses.locate(item, request)).pipe(responseBoundary),
|
|
58
62
|
"work-board.recordResponse": (input) => Effect.suspend(() => responses.record(input)).pipe(responseBoundary),
|
|
59
63
|
"work-board.registerQuestion": (input) => Effect.suspend(() => responses.register(input)).pipe(responseBoundary),
|
|
60
|
-
"work-board.respond": ({ url }) => remaining.respond(url).pipe(documentBoundary),
|
|
61
64
|
"work-board.responses": ({ question }) => Effect.suspend(() => responses.read(question)).pipe(responseBoundary),
|
|
62
65
|
"work-board.result": ({ url }) => remaining.result(url).pipe(documentBoundary),
|
|
63
66
|
"work-board.search": ({ query }) =>
|
|
@@ -66,8 +69,7 @@ export const handlers = Effect.fn("WorkBoard.handlers")(function* (options: Hand
|
|
|
66
69
|
),
|
|
67
70
|
"work-board.server": () => options.server ?? Effect.fail(new ServiceUnavailable({ message: "The board server address is unavailable." })),
|
|
68
71
|
"work-board.start": () => remaining.start().pipe(readBoundary),
|
|
69
|
-
"work-board.subscribe": () =>
|
|
70
|
-
hints(changes, index).pipe(Stream.catchCause((cause) => Stream.fromEffect(Effect.failCause(cause).pipe(readBoundary)))),
|
|
72
|
+
"work-board.subscribe": () => hints().pipe(Stream.catchCause((cause) => Stream.fromEffect(Effect.failCause(cause).pipe(readBoundary)))),
|
|
71
73
|
"work-board.watcher": () => Effect.map(Ref.get(changes.watching), (watching) => ({ watching })).pipe(readBoundary),
|
|
72
74
|
"work-board.work": ({ url }) => Effect.suspend(() => projections.work(url)).pipe(documentBoundary),
|
|
73
75
|
})
|
package/src/server/rpc/hints.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { Effect, PubSub, Stream } from "effect";
|
|
2
2
|
import type { LiveHint } from "@shivaedev/effect-contract/live.ts";
|
|
3
|
-
import type { Change
|
|
4
|
-
import
|
|
3
|
+
import type { Change } from "#server/files/changes.ts";
|
|
4
|
+
import { Workspace } from "#server/workspace.ts";
|
|
5
5
|
import { affected } from "./affected.ts";
|
|
6
6
|
|
|
7
|
-
export function hints(
|
|
7
|
+
export function hints() {
|
|
8
8
|
return Stream.unwrap(
|
|
9
9
|
Effect.gen(function* () {
|
|
10
|
+
const workspace = yield* Workspace;
|
|
11
|
+
const { changes } = yield* workspace.settings();
|
|
12
|
+
const index = workspace.index();
|
|
10
13
|
const subscription = yield* PubSub.subscribe(changes.events);
|
|
11
14
|
let sequence = changes.sequence ? yield* changes.sequence : undefined;
|
|
12
15
|
let previous = yield* Effect.option(index);
|
|
@@ -2,10 +2,10 @@ import { Effect } from "effect";
|
|
|
2
2
|
import type { FleetItemsProjection } from "#browser/views/FleetItemsProjection.ts";
|
|
3
3
|
import { DAEMON_PORT } from "#diagnostics/daemonContract.ts";
|
|
4
4
|
import { daemonUnavailableReason, readDaemon } from "#server/diagnostics/daemon.ts";
|
|
5
|
-
import type { HandlerOptions } from "#server/rpc/HandlerOptions.ts";
|
|
6
5
|
import { itemsOf } from "#server/views/items.ts";
|
|
6
|
+
import { Workspace } from "#server/workspace.ts";
|
|
7
7
|
|
|
8
|
-
export function fleetItems(
|
|
8
|
+
export function fleetItems(fleetPort: number | null = DAEMON_PORT): Effect.Effect<FleetItemsProjection, never, Workspace> {
|
|
9
9
|
if (fleetPort === null) {
|
|
10
10
|
return Effect.succeed({ reason: "Fleet daemon is not configured for this Board", state: "unavailable" });
|
|
11
11
|
}
|
|
@@ -15,7 +15,11 @@ export function fleetItems({ index, fleetPort = DAEMON_PORT }: Pick<HandlerOptio
|
|
|
15
15
|
return { reason: daemonUnavailableReason(result.failure, fleetPort), state: "unavailable" as const };
|
|
16
16
|
}
|
|
17
17
|
const data = result.success;
|
|
18
|
-
const
|
|
18
|
+
const workspace = yield* Workspace;
|
|
19
|
+
const snapshot = yield* workspace.index().pipe(Effect.catch(() => Effect.succeed(undefined)));
|
|
20
|
+
if (!snapshot) {
|
|
21
|
+
return { reason: "Board documents could not be read for Fleet items", state: "unavailable" as const };
|
|
22
|
+
}
|
|
19
23
|
const documents = new Map(
|
|
20
24
|
itemsOf(snapshot).map(({ id, document, title }) => [id, { file: document.file, metadata: document.parsed.fields, title }]),
|
|
21
25
|
);
|
|
@@ -24,5 +28,5 @@ export function fleetItems({ index, fleetPort = DAEMON_PORT }: Pick<HandlerOptio
|
|
|
24
28
|
items: data.items.map((item) => ({ ...item, document: documents.get(item.workId) ?? null })),
|
|
25
29
|
state: "ready" as const,
|
|
26
30
|
};
|
|
27
|
-
})
|
|
31
|
+
});
|
|
28
32
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { NotFound } from "@shivaedev/platform/errors/taxonomy.ts";
|
|
3
2
|
import type { BoardProjection, WorkProjection } from "#browser/views/schema.ts";
|
|
4
3
|
import { stateOf } from "#browser/views/state.ts";
|
|
5
4
|
import { inboxItems } from "#server/attention/inbox.ts";
|
|
6
5
|
import { attentionProjection } from "#server/attention/projection.ts";
|
|
7
6
|
import { identityUrl } from "#server/metadata/links.ts";
|
|
8
7
|
import { boardOf, countsOf } from "#server/render/board.ts";
|
|
9
|
-
import type { HandlerOptions } from "#server/rpc/HandlerOptions.ts";
|
|
10
8
|
import type { Snapshot } from "#server/search/snapshot.ts";
|
|
9
|
+
import { Workspace } from "#server/workspace.ts";
|
|
11
10
|
import { fleetApproval } from "./fleetApproval.ts";
|
|
12
11
|
import type { Item } from "./items.ts";
|
|
13
12
|
import { projectItems } from "./projection.ts";
|
|
14
13
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
export const projectionQueries = Effect.gen(function* () {
|
|
15
|
+
const workspace = yield* Workspace;
|
|
16
|
+
const index = workspace.index();
|
|
17
|
+
const read = workspace.document;
|
|
19
18
|
return {
|
|
20
19
|
board: (url: string) => Effect.map(read(url), (data): BoardProjection => ({ counts: countsOf(boardOf(data.source)), document: data })),
|
|
21
20
|
inbox: () => Effect.map(index, attentionProjection),
|
|
@@ -54,7 +53,7 @@ export function projectionQueries({ index, document }: { readonly index: Handler
|
|
|
54
53
|
} satisfies WorkProjection;
|
|
55
54
|
}),
|
|
56
55
|
};
|
|
57
|
-
}
|
|
56
|
+
});
|
|
58
57
|
function selectionProblem(snapshot: Snapshot, item: string) {
|
|
59
58
|
return (snapshot.model.ids.get(item)?.length ?? 0) > 1 ? "has a duplicate ID" : "is missing or its identity changed";
|
|
60
59
|
}
|
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import { Clock, Effect } from "effect";
|
|
2
|
-
import { Conflict
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { Conflict } from "@shivaedev/platform/errors/taxonomy.ts";
|
|
3
|
+
import { Diagnostics } from "#diagnostics/source.ts";
|
|
4
|
+
import { HandoffsService } from "#server/handoffs/Service.ts";
|
|
5
5
|
import { identityUrl } from "#server/metadata/links.ts";
|
|
6
6
|
import { metadataParse } from "#server/metadata/parse.ts";
|
|
7
7
|
import { startTemplates } from "#server/page/startTemplates.ts";
|
|
8
|
-
import
|
|
9
|
-
import type { HandlerOptions } from "#server/rpc/HandlerOptions.ts";
|
|
8
|
+
import { Workspace } from "#server/workspace.ts";
|
|
10
9
|
import { resultProjection } from "./resultProjection.ts";
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
},
|
|
18
|
-
) {
|
|
19
|
-
function read(url: string) {
|
|
20
|
-
return document ? document(url) : Effect.fail(new NotFound({ message: "The document is missing." }));
|
|
21
|
-
}
|
|
10
|
+
export const remainingQueries = Effect.gen(function* () {
|
|
11
|
+
const workspace = yield* Workspace;
|
|
12
|
+
const { changes, writable } = yield* workspace.settings();
|
|
13
|
+
const index = workspace.index();
|
|
14
|
+
const read = workspace.document;
|
|
15
|
+
const services = { diagnostics: yield* Diagnostics, handoffs: yield* HandoffsService };
|
|
22
16
|
return {
|
|
23
17
|
diagnostics: () =>
|
|
24
18
|
Effect.gen(function* () {
|
|
@@ -42,20 +36,6 @@ export function remainingQueries(
|
|
|
42
36
|
writable: writable === true,
|
|
43
37
|
};
|
|
44
38
|
}),
|
|
45
|
-
respond: (url: string) =>
|
|
46
|
-
Effect.gen(function* () {
|
|
47
|
-
const params = new URL(url, "http://127.0.0.1").searchParams;
|
|
48
|
-
const service = services.responses;
|
|
49
|
-
const preview = yield* service.locate(params.get("item") ?? "", params.get("request") ?? "");
|
|
50
|
-
yield* service.read(preview.id).pipe(Effect.catch((error) => (error._tag === "NotFound" ? Effect.void : Effect.fail(error))));
|
|
51
|
-
const responses = yield* service.history(preview);
|
|
52
|
-
const data = yield* read(identityUrl(preview.item));
|
|
53
|
-
const request = data.requests.find((entry) => entry.id === preview.request);
|
|
54
|
-
if (!request) {
|
|
55
|
-
return yield* Effect.fail(new Conflict({ message: "The response request is unavailable." }));
|
|
56
|
-
}
|
|
57
|
-
return { document: data, request: { ...request, preview, responses } };
|
|
58
|
-
}),
|
|
59
39
|
result: (url: string) =>
|
|
60
40
|
Effect.gen(function* () {
|
|
61
41
|
const item = new URL(url, "http://127.0.0.1").searchParams.get("item") ?? "";
|
|
@@ -71,4 +51,4 @@ export function remainingQueries(
|
|
|
71
51
|
}),
|
|
72
52
|
start: () => Effect.map(changes.files, (files) => ({ empty: files.length === 0, templates: startTemplates })),
|
|
73
53
|
};
|
|
74
|
-
}
|
|
54
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Context, Effect, FileSystem, Path, type PlatformError } from "effect";
|
|
2
|
+
import { defineService } from "@shivaedev/effect-service/define-service.ts";
|
|
3
|
+
import type { Document } from "#browser/document/schema.ts";
|
|
4
|
+
import { readDocument } from "#server/document/read.ts";
|
|
5
|
+
import type { Changes } from "#server/files/changes.ts";
|
|
6
|
+
import type { Snapshot } from "#server/search/snapshot.ts";
|
|
7
|
+
|
|
8
|
+
export interface WorkspaceOptions {
|
|
9
|
+
readonly changes: Changes;
|
|
10
|
+
readonly home?: string | undefined;
|
|
11
|
+
readonly index: Effect.Effect<Snapshot, PlatformError.PlatformError>;
|
|
12
|
+
readonly root: string;
|
|
13
|
+
readonly writable: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface WorkspaceSettings extends Omit<WorkspaceOptions, "index"> {
|
|
17
|
+
readonly realRoot: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class WorkspaceConfig extends Context.Service<WorkspaceConfig, WorkspaceOptions>()("WorkBoard/WorkspaceConfig") {}
|
|
21
|
+
|
|
22
|
+
export const Workspace = defineService({
|
|
23
|
+
id: "WorkBoard/Workspace",
|
|
24
|
+
initialize: Effect.gen(function* () {
|
|
25
|
+
return yield* WorkspaceConfig;
|
|
26
|
+
}),
|
|
27
|
+
methods: ({ index, ...options }) => {
|
|
28
|
+
const settings = { ...options, realRoot: options.changes.realRoot };
|
|
29
|
+
const read = readDocument(settings);
|
|
30
|
+
function document(url: string): Effect.Effect<Document, Effect.Error<ReturnType<typeof read>>, Effect.Services<ReturnType<typeof read>>> {
|
|
31
|
+
return Effect.flatMap(index, (snapshot) => read(url, snapshot));
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
document,
|
|
35
|
+
index: () => index,
|
|
36
|
+
settings: () => Effect.succeed(settings),
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
requires: [WorkspaceConfig, FileSystem.FileSystem, Path.Path],
|
|
40
|
+
});
|
|
41
|
+
export type Workspace = typeof Workspace.Identifier;
|