@shivaedev/work-board 0.0.0 → 0.2.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 (144) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/LICENSE +21 -0
  3. package/README.md +157 -2
  4. package/dist/board.d.ts +8 -0
  5. package/dist/board.d.ts.map +1 -0
  6. package/dist/board.js +27 -0
  7. package/dist/board.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +17 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/files/changes.d.ts +17 -0
  13. package/dist/files/changes.d.ts.map +1 -0
  14. package/dist/files/changes.js +38 -0
  15. package/dist/files/changes.js.map +1 -0
  16. package/dist/files/home.d.ts +11 -0
  17. package/dist/files/home.d.ts.map +1 -0
  18. package/dist/files/home.js +33 -0
  19. package/dist/files/home.js.map +1 -0
  20. package/dist/files/list.d.ts +9 -0
  21. package/dist/files/list.d.ts.map +1 -0
  22. package/dist/files/list.js +38 -0
  23. package/dist/files/list.js.map +1 -0
  24. package/dist/http/assets.d.ts +15 -0
  25. package/dist/http/assets.d.ts.map +1 -0
  26. package/dist/http/assets.js +32 -0
  27. package/dist/http/assets.js.map +1 -0
  28. package/dist/http/events.d.ts +5 -0
  29. package/dist/http/events.d.ts.map +1 -0
  30. package/dist/http/events.js +17 -0
  31. package/dist/http/events.js.map +1 -0
  32. package/dist/http/loopback.d.ts +4 -0
  33. package/dist/http/loopback.d.ts.map +1 -0
  34. package/dist/http/loopback.js +22 -0
  35. package/dist/http/loopback.js.map +1 -0
  36. package/dist/http/page.d.ts +9 -0
  37. package/dist/http/page.d.ts.map +1 -0
  38. package/dist/http/page.js +43 -0
  39. package/dist/http/page.js.map +1 -0
  40. package/dist/http/respond.d.ts +8 -0
  41. package/dist/http/respond.d.ts.map +1 -0
  42. package/dist/http/respond.js +15 -0
  43. package/dist/http/respond.js.map +1 -0
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +2 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/page/board.d.ts +4 -0
  49. package/dist/page/board.d.ts.map +1 -0
  50. package/dist/page/board.js +32 -0
  51. package/dist/page/board.js.map +1 -0
  52. package/dist/page/client.d.ts +2 -0
  53. package/dist/page/client.d.ts.map +1 -0
  54. package/dist/page/client.js +91 -0
  55. package/dist/page/client.js.map +1 -0
  56. package/dist/page/diagrams.d.ts +2 -0
  57. package/dist/page/diagrams.d.ts.map +1 -0
  58. package/dist/page/diagrams.js +73 -0
  59. package/dist/page/diagrams.js.map +1 -0
  60. package/dist/page/document.d.ts +4 -0
  61. package/dist/page/document.d.ts.map +1 -0
  62. package/dist/page/document.js +9 -0
  63. package/dist/page/document.js.map +1 -0
  64. package/dist/page/escape.d.ts +2 -0
  65. package/dist/page/escape.d.ts.map +1 -0
  66. package/dist/page/escape.js +3 -0
  67. package/dist/page/escape.js.map +1 -0
  68. package/dist/page/nav.d.ts +3 -0
  69. package/dist/page/nav.d.ts.map +1 -0
  70. package/dist/page/nav.js +22 -0
  71. package/dist/page/nav.js.map +1 -0
  72. package/dist/page/shell.d.ts +2 -0
  73. package/dist/page/shell.d.ts.map +1 -0
  74. package/dist/page/shell.js +11 -0
  75. package/dist/page/shell.js.map +1 -0
  76. package/dist/page/style.d.ts +2 -0
  77. package/dist/page/style.d.ts.map +1 -0
  78. package/dist/page/style.js +75 -0
  79. package/dist/page/style.js.map +1 -0
  80. package/dist/page/swap.d.ts +2 -0
  81. package/dist/page/swap.d.ts.map +1 -0
  82. package/dist/page/swap.js +123 -0
  83. package/dist/page/swap.js.map +1 -0
  84. package/dist/page/updated.d.ts +2 -0
  85. package/dist/page/updated.d.ts.map +1 -0
  86. package/dist/page/updated.js +2 -0
  87. package/dist/page/updated.js.map +1 -0
  88. package/dist/render/board.d.ts +20 -0
  89. package/dist/render/board.d.ts.map +1 -0
  90. package/dist/render/board.js +54 -0
  91. package/dist/render/board.js.map +1 -0
  92. package/dist/render/code.d.ts +10 -0
  93. package/dist/render/code.d.ts.map +1 -0
  94. package/dist/render/code.js +47 -0
  95. package/dist/render/code.js.map +1 -0
  96. package/dist/render/failed.d.ts +9 -0
  97. package/dist/render/failed.d.ts.map +1 -0
  98. package/dist/render/failed.js +4 -0
  99. package/dist/render/failed.js.map +1 -0
  100. package/dist/render/footnotes.d.ts +19 -0
  101. package/dist/render/footnotes.d.ts.map +1 -0
  102. package/dist/render/footnotes.js +23 -0
  103. package/dist/render/footnotes.js.map +1 -0
  104. package/dist/render/highlighter.d.ts +13 -0
  105. package/dist/render/highlighter.d.ts.map +1 -0
  106. package/dist/render/highlighter.js +11 -0
  107. package/dist/render/highlighter.js.map +1 -0
  108. package/dist/render/markdown.d.ts +5 -0
  109. package/dist/render/markdown.d.ts.map +1 -0
  110. package/dist/render/markdown.js +15 -0
  111. package/dist/render/markdown.js.map +1 -0
  112. package/dist/serve.d.ts +9 -0
  113. package/dist/serve.d.ts.map +1 -0
  114. package/dist/serve.js +9 -0
  115. package/dist/serve.js.map +1 -0
  116. package/package.json +62 -3
  117. package/src/board.ts +44 -0
  118. package/src/cli.ts +24 -0
  119. package/src/files/changes.ts +71 -0
  120. package/src/files/home.ts +37 -0
  121. package/src/files/list.ts +55 -0
  122. package/src/http/assets.ts +38 -0
  123. package/src/http/events.ts +29 -0
  124. package/src/http/loopback.ts +29 -0
  125. package/src/http/page.ts +54 -0
  126. package/src/http/respond.ts +18 -0
  127. package/src/index.ts +1 -0
  128. package/src/page/board.ts +37 -0
  129. package/src/page/client.ts +90 -0
  130. package/src/page/diagrams.ts +72 -0
  131. package/src/page/document.ts +10 -0
  132. package/src/page/escape.ts +3 -0
  133. package/src/page/nav.ts +28 -0
  134. package/src/page/shell.ts +12 -0
  135. package/src/page/style.ts +79 -0
  136. package/src/page/swap.ts +122 -0
  137. package/src/page/updated.ts +1 -0
  138. package/src/render/board.ts +82 -0
  139. package/src/render/code.ts +54 -0
  140. package/src/render/failed.ts +3 -0
  141. package/src/render/footnotes.ts +32 -0
  142. package/src/render/highlighter.ts +17 -0
  143. package/src/render/markdown.ts +16 -0
  144. package/src/serve.ts +16 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,51 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 - 2026-09-28
4
+
5
+ ### Changed
6
+
7
+ - Add `@effect/platform-node-shared` as a peer dependency pinned to the same
8
+ exact version as the `effect` and `@effect/platform-node` peers, so an
9
+ install holds one copy of Effect. An application that embeds `boardLayer` and
10
+ already depends on the Effect stack adds `@effect/platform-node-shared` at
11
+ that version; pnpm, npm and Bun install it otherwise.
12
+
13
+ ### Fixed
14
+
15
+ - Pin `@effect/platform-node-shared` to the `effect` version, so the
16
+ `work-board` command starts after a fresh install. Before,
17
+ `@effect/platform-node` resolved a newer `@effect/platform-node-shared`
18
+ prerelease than its `effect`, and the command crashed on start importing
19
+ modules that `effect` lacks.
20
+
21
+ ## 0.1.0 - 2026-09-26
22
+
23
+ ### Added
24
+
25
+ - Add the `work-board` command: `work-board <dir> [--port 4747] [--home <file>]`
26
+ serves every markdown file under a folder on 127.0.0.1 from one long-lived
27
+ process, with a top bar listing them and how long ago each changed. Files
28
+ and folders below it starting with a dot and `node_modules` are never
29
+ entered, the folder itself may be a dot folder, paths and symlinks leading
30
+ outside the folder are never served, requests whose `Host` header is not a
31
+ loopback host are refused, and a missing `--home` file stops the command
32
+ with an error.
33
+ - Pages render GitHub Flavored Markdown on the server with raw HTML allowed,
34
+ including `<details>` blocks with markdown inside, and highlight fenced code
35
+ with Shiki in light and dark. Mermaid diagrams are drawn in the browser from
36
+ the installed package, only on pages that have one, into reserved space and
37
+ without ever showing their source while drawing; they follow the colour
38
+ scheme, and a diagram that does not parse shows its source and the error.
39
+ - The `--home` file is shown at `/` as a board: `##` sections, `###` items, a
40
+ footer after a `---` that follows the last heading, and a count per section
41
+ derived from its items. Reference links and footnotes work inside cards.
42
+ - Pages update in place: the server watches the folder recursively and pushes
43
+ the changed markdown paths over server-sent events at `/events`, and the page
44
+ replaces only the changed blocks without reloading, keeping which `<details>`
45
+ are open, every drawn diagram that did not change, and an edited diagram's old
46
+ drawing until its new one is ready. A refresh that gets no page back keeps the
47
+ page and says so. A failed watch is restarted, waiting longer after each
48
+ failure, while pages show "reconnecting" and then catch up. Ctrl-C stops the
49
+ command at once, even with pages open.
50
+ - Add `boardLayer`, the router Layer the command serves, for embedding the board
51
+ in another Effect HTTP server or as a fetch handler.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ShivaeDev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,158 @@
1
- # @shivaedev/work-board
1
+ # `@shivaedev/work-board`
2
2
 
3
- This version only reserves the name. Releases are published from https://github.com/ShivaeDev/platform.
3
+ A tiny local server that shows a folder of markdown files as a live page. Edit a
4
+ file and every open page updates in place, with no reload, no rebuild and
5
+ nothing to publish.
6
+
7
+ ```sh
8
+ pnpm add --global @shivaedev/work-board
9
+ work-board ./project-notes --port 4747 --home plan.md
10
+ ```
11
+
12
+ It listens on `127.0.0.1` only and answers only requests addressed to a
13
+ loopback host. Every `.md` file under the folder appears in the top bar with how
14
+ long ago it changed. Below the folder, files and folders starting with a dot and
15
+ `node_modules` are never entered, and anything that is not markdown is skipped;
16
+ the folder itself may be a dot folder such as `.notes`. No path outside the
17
+ folder is served, and a symlink is followed only when it leads to a place
18
+ inside the folder. One long-lived process picks up new files and edits without
19
+ a restart.
20
+
21
+ Pages render GitHub Flavored Markdown on the server: tables, task lists,
22
+ footnotes, raw HTML (including `<details>` with markdown inside) and fenced code,
23
+ highlighted with Shiki's GitHub light and dark themes. Raw HTML is not
24
+ sanitized, so serve only folders you trust. Pages send a content security
25
+ policy that allows only the board's own scripts, and `Cache-Control: no-store`.
26
+ The page uses system fonts and follows the light or dark colour scheme.
27
+
28
+ | Option | Default | Meaning |
29
+ | --- | --- | --- |
30
+ | `<dir>` | required | The folder to serve. |
31
+ | `--port` | `4747` | The port on `127.0.0.1`. `0` picks a free one. |
32
+ | `--home` | none | The file shown at `/` as a board, relative to the folder. The command stops with an error unless it leads to one of the markdown files listed from the folder. Without it, `/` shows the first file as a document. |
33
+
34
+ ## Diagrams
35
+
36
+ A ` ```mermaid ` block is drawn in the browser. Mermaid loads from the installed
37
+ package, and only on pages with a diagram.
38
+
39
+ - A diagram is drawn into space reserved for it; its source is never shown
40
+ while it is drawn or redrawn.
41
+ - A drawn diagram stays in place when the page around it updates. An edited
42
+ diagram keeps its old drawing until the new one is ready, as long as the page
43
+ keeps the same number of diagrams; otherwise it shows its reserved space.
44
+ - Diagrams follow the light or dark colour scheme; switching schemes redraws
45
+ them in place.
46
+ - A diagram that does not parse shows its source and the error, without the
47
+ drawing it had before.
48
+
49
+ ## Boards
50
+
51
+ The home file is shown as a board: a title, an intro, then sections of cards
52
+ with a count per section in the header. The counts are derived from the
53
+ markdown, so they are never typed by hand.
54
+
55
+ ```md
56
+ # Project notes
57
+
58
+ What is open this week.
59
+
60
+ ## In review
61
+
62
+ ### `docs #12` [Refresh the install guide](https://example.com/pull/12)
63
+
64
+ Checks are green.
65
+
66
+ ## To do
67
+
68
+ ### `ops` Rotate the deploy key
69
+
70
+ <details>
71
+ <summary>Steps</summary>
72
+
73
+ 1. Create the key.
74
+ 2. Replace the old one.
75
+
76
+ </details>
77
+
78
+ ## Later
79
+
80
+ Nothing planned yet.
81
+
82
+ ---
83
+
84
+ Finished work moves to the changelog.
85
+ ```
86
+
87
+ - `#` is the title, and anything before the first `##` is the intro.
88
+ - Each `##` is a section. Its `###` headings are its items, and its count is
89
+ the number of items. A section without `###` items, such as "Later", has no
90
+ count and shows its text as one card.
91
+ - An item's heading may start with a code span, shown as a quiet tag before the
92
+ title, and the title may be a link. Everything up to the next heading is the
93
+ item's body.
94
+ - Markdown inside `<details>` needs a blank line after `<summary>` and before
95
+ `</details>`.
96
+ - A `---` after the last heading starts the footer; a `---` inside a card stays
97
+ in the card.
98
+ - Reference links and footnotes work inside cards, wherever their definitions
99
+ are in the file, and each card's footnotes get their own ids.
100
+
101
+ ## Live updates
102
+
103
+ The server watches the folder recursively. Changes settle for 100 ms, then
104
+ `/events` sends one server-sent event naming the markdown files that changed:
105
+
106
+ ```text
107
+ event: change
108
+ data: {"paths":["plan.md"]}
109
+ ```
110
+
111
+ Open pages fetch their file again and replace only the blocks that changed,
112
+ also when blocks were added or removed around them; the rest of the page,
113
+ including which `<details>` are open and every drawn diagram, stays as it is,
114
+ and the page never reloads. A page that loses the connection shows
115
+ "reconnecting" and catches up once it is back. When the open file is deleted
116
+ or renamed, the page shows that it is gone. A refresh that gets no page back
117
+ keeps the page as it is and shows "refresh failed" until the next one
118
+ succeeds.
119
+
120
+ If watching the folder fails, the server restarts the watch, waiting longer
121
+ after each failure. Meanwhile `/events` sends `event: down`, open pages show
122
+ "reconnecting", and once watching resumes `event: ready` makes them catch up on
123
+ anything they missed.
124
+
125
+ Each open page keeps one connection to `/events`, and browsers allow only a few
126
+ per host, so the intended use is one board per browser.
127
+
128
+ ## Embedding
129
+
130
+ `boardLayer({ root, home })` is the router Layer the command serves. Serve it
131
+ with `HttpRouter.serve` on any Effect HTTP server, or turn it into a fetch
132
+ handler with `HttpRouter.toWebHandler`. It needs `FileSystem` and `Path`, for
133
+ example from `NodeServices.layer`. It still judges every request by its `Host`
134
+ header, so pass requests on with that header: a request without one, or whose
135
+ `Host` is not a loopback host, is refused, and so is a request from a
136
+ non-loopback address when the server knows the address.
137
+
138
+ ```ts
139
+ import { NodeServices } from "@effect/platform-node"
140
+ import { Layer } from "effect"
141
+ import { HttpRouter } from "effect/unstable/http"
142
+ import { boardLayer } from "@shivaedev/work-board"
143
+
144
+ const board = HttpRouter.toWebHandler(
145
+ Layer.provide(boardLayer({ root: "./project-notes", home: "plan.md" }), NodeServices.layer),
146
+ )
147
+ ```
148
+
149
+ On pnpm 11, installing into a project needs a decision on `msgpackr-extract`,
150
+ which `effect` pulls in: pnpm refuses its build script by default, and
151
+ `pnpm add` fails with `ERR_PNPM_IGNORED_BUILDS`. Record the decision under
152
+ `allowBuilds` in `pnpm-workspace.yaml`, or run `pnpm approve-builds`; `false`
153
+ skips the build. `pnpm dlx` and `pnpm add --global` need no setting.
154
+
155
+ ```yaml
156
+ allowBuilds:
157
+ msgpackr-extract: false
158
+ ```
@@ -0,0 +1,8 @@
1
+ import { type FileSystem, Layer, type Path } from "effect";
2
+ import { HttpRouter } from "effect/unstable/http";
3
+ export interface BoardOptions {
4
+ readonly root: string;
5
+ readonly home?: string | undefined;
6
+ }
7
+ export declare const boardLayer: (options: BoardOptions) => Layer.Layer<never, import("./files/home.ts").HomeMissing | import("./http/assets.ts").MermaidMissing | import("effect/PlatformError").PlatformError | import("./render/failed.ts").RenderFailed, FileSystem.FileSystem | HttpRouter.HttpRouter | Path.Path | HttpRouter.Request<"Error", import("effect/PlatformError").PlatformError>>;
8
+ //# sourceMappingURL=board.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,UAAU,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,UAAU,EAAmD,MAAM,sBAAsB,CAAC;AAUnG,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AA6BD,eAAO,MAAM,UAAU,GAAI,SAAS,YAAY,4UAA2D,CAAC"}
package/dist/board.js ADDED
@@ -0,0 +1,27 @@
1
+ import { Effect, Layer } from "effect";
2
+ import { HttpRouter } from "effect/unstable/http";
3
+ import { watchChanges } from "./files/changes.js";
4
+ import { homeIn } from "./files/home.js";
5
+ import { ASSETS, MERMAID_ROUTE, mermaidFile, mermaidRoot } from "./http/assets.js";
6
+ import { events } from "./http/events.js";
7
+ import { loopbackOnly } from "./http/loopback.js";
8
+ import { page } from "./http/page.js";
9
+ import { respond } from "./http/respond.js";
10
+ import { Highlighter } from "./render/highlighter.js";
11
+ const routes = (options) => HttpRouter.use((router) => Effect.gen(function* () {
12
+ const home = yield* homeIn(options.root, options.home);
13
+ const mermaid = yield* mermaidRoot();
14
+ const changes = yield* watchChanges(options.root);
15
+ const context = yield* Effect.context();
16
+ const serve = (route, handler) => router.add("GET", route, loopbackOnly((request) => Effect.provideContext(handler(request), context)));
17
+ yield* serve("/events", events(changes));
18
+ for (const [route, body, contentType] of ASSETS) {
19
+ yield* serve(route, () => Effect.succeed(respond(body, contentType)));
20
+ }
21
+ yield* serve(MERMAID_ROUTE, mermaidFile(mermaid));
22
+ const pages = page({ root: options.root, home }, changes);
23
+ yield* serve("/", pages);
24
+ yield* serve("/*", pages);
25
+ }));
26
+ export const boardLayer = (options) => routes(options).pipe(Layer.provide(Highlighter.layer));
27
+ //# sourceMappingURL=board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAmB,KAAK,EAAa,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,UAAU,EAAmD,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAUtD,MAAM,MAAM,GAAG,CAAC,OAAqB,EAAE,EAAE,CACxC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAY,CAAC;IAClD,MAAM,KAAK,GAAG,CAAI,KAA2B,EAAE,OAAmB,EAAE,EAAE,CACrE,MAAM,CAAC,GAAG,CACT,KAAK,EACL,KAAK,EACL,YAAY,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAC3E,CAAC;IACH,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,MAAM,EAAE,CAAC;QACjD,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IACD,KAAK,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IAC1D,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACzB,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC,CAAC,CACF,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAqB,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
3
+ import { Effect, Layer, Option, Path } from "effect";
4
+ import { Argument, Command, Flag } from "effect/unstable/cli";
5
+ import { HOST, serveBoard } from "./serve.js";
6
+ const VERSION = "0.2.0";
7
+ const serve = Effect.fn("WorkBoard.serve")(function* (input) {
8
+ const path = yield* Path.Path;
9
+ return yield* Layer.launch(serveBoard({ root: path.resolve(input.dir), home: Option.getOrUndefined(input.home), port: input.port }));
10
+ });
11
+ const workBoard = Command.make("work-board", {
12
+ dir: Argument.directory("dir", { mustExist: true }).pipe(Argument.withDescription("The folder of markdown files to serve")),
13
+ port: Flag.integer("port").pipe(Flag.withDefault(4747), Flag.withDescription(`The port to listen on at ${HOST}`)),
14
+ home: Flag.string("home").pipe(Flag.optional, Flag.withDescription("The file shown as a board at /, relative to the folder")),
15
+ }, serve).pipe(Command.withDescription("Serve a folder of markdown files as a live page that updates in place when a file changes."));
16
+ Command.run(workBoard, { version: VERSION }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
17
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAA4F;IACjJ,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACtI,CAAC,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAC7B,YAAY,EACZ;IACC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,uCAAuC,CAAC,CAAC;IAC3H,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;IACjH,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,CAAC,wDAAwD,CAAC,CAAC;CAC7H,EACD,KAAK,CACL,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,4FAA4F,CAAC,CAAC,CAAC;AAE9H,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { Effect, FileSystem, Path, type PlatformError, PubSub, Ref } from "effect";
2
+ import { type MarkdownFile } from "./list.ts";
3
+ export type Change = {
4
+ readonly _tag: "Changed";
5
+ readonly paths: ReadonlyArray<string>;
6
+ } | {
7
+ readonly _tag: "Watching";
8
+ readonly watching: boolean;
9
+ };
10
+ export interface Changes {
11
+ readonly realRoot: string;
12
+ readonly events: PubSub.PubSub<Change>;
13
+ readonly watching: Ref.Ref<boolean>;
14
+ readonly files: Effect.Effect<ReadonlyArray<MarkdownFile>, PlatformError.PlatformError>;
15
+ }
16
+ export declare const watchChanges: (root: string) => Effect.Effect<Changes, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | import("effect/Scope").Scope>;
17
+ //# sourceMappingURL=changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../../src/files/changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,MAAM,EAAE,UAAU,EAAU,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,GAAG,EAAoB,MAAM,QAAQ,CAAC;AAC7H,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAExE,MAAM,MAAM,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC;AAErJ,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;CACxF;AAcD,eAAO,MAAM,YAAY,yIA8CvB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { Data, Deferred, Effect, FileSystem, Option, Path, PubSub, Ref, Schedule, Stream } from "effect";
2
+ import { isMarkdown, listMarkdown } from "./list.js";
3
+ class WatchEnded extends Data.TaggedError("WatchEnded") {
4
+ }
5
+ const SETTLE = "100 millis";
6
+ const BATCH = 256;
7
+ const BACKLOG = 16;
8
+ const RETRY = Schedule.min([Schedule.exponential("100 millis"), Schedule.spaced("5 seconds")]);
9
+ export const watchChanges = Effect.fn("WorkBoard.watchChanges")(function* (root) {
10
+ const fs = yield* FileSystem.FileSystem;
11
+ const path = yield* Path.Path;
12
+ const realRoot = yield* fs.realPath(root);
13
+ const events = yield* Effect.acquireRelease(PubSub.sliding(BACKLOG), PubSub.shutdown);
14
+ const watching = yield* Ref.make(false);
15
+ const generation = yield* Ref.make(0);
16
+ const cached = yield* Ref.make(Option.none());
17
+ const settled = yield* Deferred.make();
18
+ const stale = Ref.update(generation, (count) => count + 1);
19
+ const relative = (changed) => (path.isAbsolute(changed) ? path.relative(root, changed) : changed).split(path.sep).join("/");
20
+ const setWatching = (now) => Effect.andThen(Effect.flatMap(Ref.getAndSet(watching, now), (was) => was === now ? Effect.void : Effect.andThen(stale, PubSub.publish(events, { _tag: "Watching", watching: now }))), Deferred.succeed(settled, undefined));
21
+ const watchOnce = fs.watch(root, { recursive: true }).pipe(Stream.tap(() => stale), Stream.map((event) => relative(event.path)), Stream.filter(isMarkdown), Stream.groupedWithin(BATCH, SETTLE), Stream.runForEach((paths) => PubSub.publish(events, { _tag: "Changed", paths: [...new Set(paths)] })), Effect.andThen(Effect.fail(new WatchEnded())));
22
+ const attempt = Effect.scoped(Effect.andThen(Effect.forkScoped(Effect.delay(setWatching(true), SETTLE)), watchOnce));
23
+ yield* attempt.pipe(Effect.sandbox, Effect.tapError((cause) => Effect.andThen(setWatching(false), Effect.logWarning("Watching the folder failed; retrying", cause))), Effect.retry(RETRY), Effect.forkScoped);
24
+ yield* Deferred.await(settled);
25
+ const files = Effect.gen(function* () {
26
+ const now = yield* Ref.get(generation);
27
+ const hit = Option.filter(yield* Ref.get(cached), (entry) => entry.generation === now);
28
+ if ((yield* Ref.get(watching)) && Option.isSome(hit)) {
29
+ return hit.value.files;
30
+ }
31
+ const listed = yield* listMarkdown(root, realRoot).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
32
+ yield* Ref.set(cached, Option.some({ generation: now, files: listed }));
33
+ return listed;
34
+ });
35
+ const changes = { realRoot, events, watching, files };
36
+ return changes;
37
+ });
38
+ //# sourceMappingURL=changes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changes.js","sourceRoot":"","sources":["../../src/files/changes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ declare const HomeMissing_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
3
+ readonly _tag: "HomeMissing";
4
+ } & Readonly<A>;
5
+ export declare class HomeMissing extends HomeMissing_base<{
6
+ readonly message: string;
7
+ }> {
8
+ }
9
+ export declare const homeIn: (root: string, home: string | undefined) => Effect.Effect<string | undefined, HomeMissing | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
10
+ export {};
11
+ //# sourceMappingURL=home.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../src/files/home.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,UAAU,EAAU,IAAI,EAAE,MAAM,QAAQ,CAAC;;;;AAGhE,qBAAa,WAAY,SAAQ,iBAAgC;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;CAAG;AAEjG,eAAO,MAAM,MAAM,8KA+BjB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Data, Effect, FileSystem, Option, Path } from "effect";
2
+ import { listMarkdown } from "./list.js";
3
+ export class HomeMissing extends Data.TaggedError("HomeMissing") {
4
+ }
5
+ export const homeIn = Effect.fn("WorkBoard.homeIn")(function* (root, home) {
6
+ if (home === undefined) {
7
+ return undefined;
8
+ }
9
+ const fs = yield* FileSystem.FileSystem;
10
+ const path = yield* Path.Path;
11
+ const missing = new HomeMissing({ message: `The home file ${home} is not a markdown file in ${root}` });
12
+ const realRoot = yield* fs.realPath(root);
13
+ const real = yield* Effect.option(fs.realPath(path.resolve(root, home)));
14
+ if (Option.isNone(real)) {
15
+ return yield* missing;
16
+ }
17
+ const target = yield* Effect.option(fs.stat(real.value));
18
+ if (Option.isNone(target)) {
19
+ return yield* missing;
20
+ }
21
+ const files = yield* listMarkdown(root, realRoot);
22
+ const relative = path.relative(realRoot, real.value).split(path.sep).join("/");
23
+ if (files.some((file) => file.path === relative)) {
24
+ return relative;
25
+ }
26
+ const inode = Option.getOrUndefined(target.value.ino);
27
+ const same = (info) => inode !== undefined && info.dev === target.value.dev && Option.getOrUndefined(info.ino) === inode;
28
+ const matches = yield* Effect.filter(files, (file) => Effect.map(Effect.option(fs.stat(path.join(realRoot, file.path))), (info) => Option.isSome(info) && same(info.value)));
29
+ const direct = yield* Effect.filter(matches, (file) => Effect.map(Effect.option(fs.realPath(path.join(realRoot, file.path))), (real) => Option.contains(real, path.join(realRoot, file.path))));
30
+ const chosen = matches.find((file) => file.path.toLowerCase() === relative.toLowerCase()) ?? direct[0] ?? matches[0];
31
+ return chosen === undefined ? yield* missing : chosen.path;
32
+ });
33
+ //# sourceMappingURL=home.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home.js","sourceRoot":"","sources":["../../src/files/home.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { Effect, FileSystem, Path, type PlatformError } from "effect";
2
+ export interface MarkdownFile {
3
+ readonly path: string;
4
+ readonly modified: number;
5
+ }
6
+ export declare const isMarkdown: (path: string) => boolean;
7
+ export declare const within: (realRoot: string, separator: string, real: string) => boolean;
8
+ export declare const listMarkdown: (root: string, realRoot: string) => Effect.Effect<MarkdownFile[], PlatformError.PlatformError, FileSystem.FileSystem | Path.Path>;
9
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/files/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAU,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAID,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAiE,CAAC;AAE5G,eAAO,MAAM,MAAM,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,EAAE,MAAM,MAAM,KAAG,OAA0E,CAAC;AAQtJ,eAAO,MAAM,YAAY,mIAmCvB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { Effect, FileSystem, Option, Path } from "effect";
2
+ const skipped = (segment) => segment.startsWith(".") || segment === "node_modules";
3
+ export const isMarkdown = (path) => path.endsWith(".md") && !path.split("/").some(skipped);
4
+ export const within = (realRoot, separator, real) => real === realRoot || real.startsWith(`${realRoot}${separator}`);
5
+ const folderOf = (path) => path.slice(0, Math.max(0, path.lastIndexOf("/")));
6
+ const byFolder = (left, right) => folderOf(left).localeCompare(folderOf(right)) || left.localeCompare(right);
7
+ export const listMarkdown = Effect.fn("WorkBoard.listMarkdown")(function* (root, realRoot) {
8
+ const fs = yield* FileSystem.FileSystem;
9
+ const path = yield* Path.Path;
10
+ const visited = new Set();
11
+ const entry = (relative) => Effect.gen(function* () {
12
+ const real = yield* Effect.option(fs.realPath(path.join(root, relative)));
13
+ if (Option.isNone(real) || !within(realRoot, path.sep, real.value)) {
14
+ return [];
15
+ }
16
+ const info = yield* Effect.option(fs.stat(real.value));
17
+ if (Option.isNone(info)) {
18
+ return [];
19
+ }
20
+ if (info.value.type === "Directory") {
21
+ return yield* Effect.orElseSucceed(folder(relative, real.value), () => []);
22
+ }
23
+ const modified = Option.match(info.value.mtime, { onNone: () => 0, onSome: (date) => date.getTime() });
24
+ return info.value.type === "File" && relative.endsWith(".md") ? [{ path: relative, modified }] : [];
25
+ });
26
+ const folder = (relative, real) => Effect.gen(function* () {
27
+ if (visited.has(real)) {
28
+ return [];
29
+ }
30
+ visited.add(real);
31
+ const names = yield* fs.readDirectory(real);
32
+ const found = yield* Effect.forEach(names.filter((name) => !skipped(name)), (name) => entry(relative === "" ? name : `${relative}/${name}`));
33
+ return found.flat();
34
+ });
35
+ const files = yield* folder("", realRoot);
36
+ return files.toSorted((left, right) => byFolder(left.path, right.path));
37
+ });
38
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/files/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ import type { HttpRouter, HttpServerRequest } from "effect/unstable/http";
3
+ export declare const ASSETS: ReadonlyArray<readonly [HttpRouter.PathInput, string, string]>;
4
+ declare const MermaidMissing_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
5
+ readonly _tag: "MermaidMissing";
6
+ } & Readonly<A>;
7
+ export declare class MermaidMissing extends MermaidMissing_base<{
8
+ readonly cause: unknown;
9
+ }> {
10
+ }
11
+ export declare const mermaidRoot: () => Effect.Effect<string, MermaidMissing, Path.Path>;
12
+ export declare const mermaidFile: (mermaidRoot: string) => (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<import("effect/unstable/http/HttpServerResponse").HttpServerResponse, import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
13
+ export declare const MERMAID_ROUTE: HttpRouter.PathInput;
14
+ export {};
15
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/http/assets.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAO1E,eAAO,MAAM,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAKjF,CAAC;;;;AAIF,qBAAa,cAAe,SAAQ,oBAAmC;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;CAAG;AAEtG,eAAO,MAAM,WAAW,wDAItB,CAAC;AAEH,eAAO,MAAM,WAAW,GAAI,aAAa,MAAM,2NAS5C,CAAC;AAEJ,eAAO,MAAM,aAAa,EAAE,UAAU,CAAC,SAAyB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { createRequire } from "node:module";
2
+ import { Data, Effect, FileSystem, Path } from "effect";
3
+ import { client } from "../page/client.js";
4
+ import { diagrams } from "../page/diagrams.js";
5
+ import { style } from "../page/style.js";
6
+ import { swap } from "../page/swap.js";
7
+ import { respond } from "./respond.js";
8
+ export const ASSETS = [
9
+ ["/_board/style.css", style, "text/css"],
10
+ ["/_board/client.js", client, "text/javascript"],
11
+ ["/_board/diagrams.js", diagrams, "text/javascript"],
12
+ ["/_board/swap.js", swap, "text/javascript"],
13
+ ];
14
+ const MERMAID = "/_board/mermaid/";
15
+ export class MermaidMissing extends Data.TaggedError("MermaidMissing") {
16
+ }
17
+ export const mermaidRoot = Effect.fn("WorkBoard.mermaidRoot")(function* () {
18
+ const path = yield* Path.Path;
19
+ const entry = yield* Effect.try({ try: () => createRequire(import.meta.url).resolve("mermaid"), catch: (cause) => new MermaidMissing({ cause }) });
20
+ return path.dirname(entry);
21
+ });
22
+ export const mermaidFile = (mermaidRoot) => Effect.fn("WorkBoard.mermaidFile")(function* (request) {
23
+ const fs = yield* FileSystem.FileSystem;
24
+ const path = yield* Path.Path;
25
+ const file = path.resolve(mermaidRoot, new URL(request.url, "http://127.0.0.1").pathname.slice(MERMAID.length));
26
+ if (!file.startsWith(`${mermaidRoot}${path.sep}`) || !file.endsWith(".mjs") || !(yield* fs.exists(file))) {
27
+ return respond("Not found", "text/plain", 404);
28
+ }
29
+ return respond(yield* fs.readFile(file), "text/javascript");
30
+ });
31
+ export const MERMAID_ROUTE = `${MERMAID}*`;
32
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/http/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,CAAC,MAAM,MAAM,GAAmE;IACrF,CAAC,mBAAmB,EAAE,KAAK,EAAE,UAAU,CAAC;IACxC,CAAC,mBAAmB,EAAE,MAAM,EAAE,iBAAiB,CAAC;IAChD,CAAC,qBAAqB,EAAE,QAAQ,EAAE,iBAAiB,CAAC;IACpD,CAAC,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,CAAC;CAC5C,CAAC;AAEF,MAAM,OAAO,GAAG,kBAAkB,CAAC;AAEnC,MAAM,OAAO,cAAe,SAAQ,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAA8B;CAAG;AAEtG,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC;IACtE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,cAAc,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACnJ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,WAAmB,EAAE,EAAE,CAClD,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAAE,OAA4C;IACzF,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAChH,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC1G,OAAO,OAAO,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,MAAM,aAAa,GAAyB,GAAG,OAAO,GAAG,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { Effect } from "effect";
2
+ import { HttpServerResponse } from "effect/unstable/http";
3
+ import type { Changes } from "../files/changes.ts";
4
+ export declare const events: (changes: Changes) => () => Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
5
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/http/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAuB,MAAM,QAAQ,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAU,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAmB3D,eAAO,MAAM,MAAM,GAAI,SAAS,OAAO,6EAMrC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { Effect, PubSub, Ref, Stream } from "effect";
2
+ import { Sse } from "effect/unstable/encoding";
3
+ import { HttpServerResponse } from "effect/unstable/http";
4
+ import { HEADERS } from "./respond.js";
5
+ const event = (name, data = "") => ({ _tag: "Event", event: name, id: undefined, data });
6
+ const watching = (up) => event(up ? "ready" : "down");
7
+ const eventOf = (change) => change._tag === "Changed" ? event("change", JSON.stringify({ paths: change.paths })) : watching(change.watching);
8
+ const subscribed = (changes) => Stream.unwrap(Effect.gen(function* () {
9
+ const subscription = yield* PubSub.subscribe(changes.events);
10
+ const now = yield* Ref.get(changes.watching);
11
+ return Stream.concat(Stream.succeed(watching(now)), Stream.map(Stream.fromSubscription(subscription), eventOf));
12
+ }));
13
+ export const events = (changes) => () => Effect.succeed(HttpServerResponse.stream(Stream.encodeText(Stream.map(subscribed(changes), (message) => Sse.encoder.write(message))), {
14
+ contentType: "text/event-stream",
15
+ headers: HEADERS,
16
+ }));
17
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/http/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,IAAI,GAAG,EAAE,EAAa,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAE5G,MAAM,QAAQ,GAAG,CAAC,EAAW,EAAa,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,MAAM,OAAO,GAAG,CAAC,MAAc,EAAa,EAAE,CAC7C,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAElH,MAAM,UAAU,GAAG,CAAC,OAAgB,EAAE,EAAE,CACvC,MAAM,CAAC,MAAM,CACZ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACjH,CAAC,CAAC,CACF,CAAC;AAEH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,GAAG,EAAE,CAChD,MAAM,CAAC,OAAO,CACb,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;IACtH,WAAW,EAAE,mBAAmB;IAChC,OAAO,EAAE,OAAO;CAChB,CAAC,CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Effect } from "effect";
2
+ import type { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
3
+ export declare const loopbackOnly: <E, R>(handler: (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>) => (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>;
4
+ //# sourceMappingURL=loopback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback.d.ts","sourceRoot":"","sources":["../../src/http/loopback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAwBlF,eAAO,MAAM,YAAY,GACvB,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,iBAAiB,CAAC,iBAAiB,KAAK,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC,MAC3H,SAAS,iBAAiB,CAAC,iBAAiB,+DAC2E,CAAC"}