@davidsouther/jiffies 2026.4.1 → 2026.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/README.md +0 -3
  2. package/lib/esm/assert.d.ts +26 -0
  3. package/lib/esm/assert.js +38 -0
  4. package/lib/esm/awaitable.js +1 -0
  5. package/lib/esm/case.d.ts +1 -0
  6. package/lib/esm/case.js +5 -0
  7. package/lib/esm/components/accordion.d.ts +5 -0
  8. package/lib/esm/components/accordion.js +9 -0
  9. package/lib/esm/components/alert.d.ts +7 -0
  10. package/lib/esm/components/alert.js +31 -0
  11. package/lib/esm/components/button_bar.d.ts +8 -0
  12. package/lib/esm/components/button_bar.js +25 -0
  13. package/lib/esm/components/card.d.ts +8 -0
  14. package/lib/esm/components/card.js +31 -0
  15. package/lib/esm/components/children.d.ts +2 -0
  16. package/lib/esm/components/children.js +7 -0
  17. package/lib/esm/components/form.d.ts +5 -0
  18. package/lib/esm/components/form.js +13 -0
  19. package/lib/esm/components/index.d.ts +9 -0
  20. package/lib/esm/components/index.js +10 -0
  21. package/lib/esm/components/inline_edit.d.ts +12 -0
  22. package/lib/esm/components/inline_edit.js +48 -0
  23. package/lib/esm/components/link.d.ts +5 -0
  24. package/lib/esm/components/link.js +11 -0
  25. package/lib/esm/components/logger.d.ts +6 -0
  26. package/lib/esm/components/logger.js +22 -0
  27. package/lib/esm/components/modal.d.ts +2 -0
  28. package/lib/esm/components/modal.js +10 -0
  29. package/lib/esm/components/nav.d.ts +11 -0
  30. package/lib/esm/components/nav.js +27 -0
  31. package/lib/esm/components/property.d.ts +9 -0
  32. package/lib/esm/components/property.js +16 -0
  33. package/lib/esm/components/select.d.ts +10 -0
  34. package/lib/esm/components/select.js +3 -0
  35. package/lib/esm/components/tabs.d.ts +20 -0
  36. package/lib/esm/components/tabs.js +45 -0
  37. package/lib/esm/components/virtual_scroll.d.ts +42 -0
  38. package/lib/esm/components/virtual_scroll.js +94 -0
  39. package/lib/esm/debounce.d.ts +1 -0
  40. package/lib/esm/debounce.js +11 -0
  41. package/lib/esm/diff.d.ts +15 -0
  42. package/lib/esm/diff.js +50 -0
  43. package/lib/esm/display.d.ts +5 -0
  44. package/lib/esm/display.js +11 -0
  45. package/lib/esm/dom/css/border.d.ts +11 -0
  46. package/lib/esm/dom/css/border.js +27 -0
  47. package/lib/esm/dom/css/constants.d.ts +31 -0
  48. package/lib/esm/dom/css/constants.js +28 -0
  49. package/lib/esm/dom/css/core.d.ts +5 -0
  50. package/lib/esm/dom/css/core.js +24 -0
  51. package/lib/esm/dom/css/fstyle.d.ts +5 -0
  52. package/lib/esm/dom/css/fstyle.js +32 -0
  53. package/lib/esm/dom/css/sizing.d.ts +5 -0
  54. package/lib/esm/dom/css/sizing.js +10 -0
  55. package/lib/esm/dom/dom.d.ts +36 -0
  56. package/lib/esm/dom/dom.js +217 -0
  57. package/lib/esm/dom/fc.d.ts +10 -0
  58. package/lib/esm/dom/fc.js +32 -0
  59. package/lib/esm/dom/form/form.app.d.ts +1 -0
  60. package/lib/esm/dom/form/form.app.js +19 -0
  61. package/lib/esm/dom/form/form.d.ts +27 -0
  62. package/lib/esm/dom/form/form.js +65 -0
  63. package/lib/esm/dom/html.d.ts +112 -0
  64. package/{src/dom/html.ts → lib/esm/dom/html.js} +2 -14
  65. package/lib/esm/dom/hydrate.d.ts +39 -0
  66. package/lib/esm/dom/hydrate.js +187 -0
  67. package/lib/esm/dom/index.js +2 -0
  68. package/lib/esm/dom/navigation/index.d.ts +76 -0
  69. package/lib/esm/dom/navigation/index.js +292 -0
  70. package/lib/esm/dom/observable.d.ts +2 -0
  71. package/lib/esm/dom/observable.js +6 -0
  72. package/lib/esm/dom/provide.d.ts +3 -0
  73. package/lib/esm/dom/provide.js +7 -0
  74. package/lib/esm/dom/render.d.ts +8 -0
  75. package/lib/esm/dom/render.js +28 -0
  76. package/lib/esm/dom/router/link.d.ts +6 -0
  77. package/lib/esm/dom/router/link.js +3 -0
  78. package/lib/esm/dom/router/router.d.ts +13 -0
  79. package/lib/esm/dom/router/router.js +52 -0
  80. package/lib/esm/dom/svg.d.ts +64 -0
  81. package/{src/dom/svg.ts → lib/esm/dom/svg.js} +2 -15
  82. package/lib/esm/dom/types/css.d.ts +6590 -0
  83. package/lib/esm/dom/types/css.js +1 -0
  84. package/lib/esm/dom/types/dom.js +1 -0
  85. package/lib/esm/dom/types/html.d.ts +614 -0
  86. package/lib/esm/dom/types/html.js +1 -0
  87. package/lib/esm/dom/xml.d.ts +1 -0
  88. package/lib/esm/dom/xml.js +4 -0
  89. package/lib/esm/equal.d.ts +11 -0
  90. package/lib/esm/equal.js +43 -0
  91. package/lib/esm/fs.d.ts +72 -0
  92. package/lib/esm/fs.js +227 -0
  93. package/lib/esm/fs_node.d.ts +15 -0
  94. package/lib/esm/fs_node.js +45 -0
  95. package/lib/esm/generator.d.ts +1 -0
  96. package/lib/esm/generator.js +10 -0
  97. package/lib/esm/lock.d.ts +1 -0
  98. package/lib/esm/lock.js +23 -0
  99. package/lib/esm/log.d.ts +69 -0
  100. package/lib/esm/log.js +211 -0
  101. package/lib/esm/observable/event.d.ts +35 -0
  102. package/lib/esm/observable/event.js +46 -0
  103. package/lib/esm/observable/observable.d.ts +134 -0
  104. package/lib/esm/observable/observable.js +349 -0
  105. package/lib/esm/range.d.ts +1 -0
  106. package/lib/esm/range.js +7 -0
  107. package/lib/esm/result.d.ts +31 -0
  108. package/lib/esm/result.js +66 -0
  109. package/lib/esm/safe.d.ts +1 -0
  110. package/lib/esm/safe.js +10 -0
  111. package/lib/esm/server/http/apps.d.ts +5 -0
  112. package/lib/esm/server/http/apps.js +23 -0
  113. package/lib/esm/server/http/css.d.ts +5 -0
  114. package/lib/esm/server/http/css.js +43 -0
  115. package/lib/esm/server/http/index.d.ts +16 -0
  116. package/lib/esm/server/http/index.js +78 -0
  117. package/lib/esm/server/http/response.d.ts +4 -0
  118. package/lib/esm/server/http/response.js +43 -0
  119. package/lib/esm/server/http/sitemap.d.ts +2 -0
  120. package/lib/esm/server/http/sitemap.js +22 -0
  121. package/lib/esm/server/http/static.d.ts +2 -0
  122. package/lib/esm/server/http/static.js +22 -0
  123. package/lib/esm/server/http/typescript.d.ts +5 -0
  124. package/lib/esm/server/http/typescript.js +40 -0
  125. package/lib/esm/server/live-reload.d.ts +46 -0
  126. package/lib/esm/server/live-reload.js +161 -0
  127. package/lib/esm/server/main.d.ts +2 -0
  128. package/lib/esm/server/main.js +23 -0
  129. package/lib/esm/server/ws/frame.d.ts +2 -0
  130. package/lib/esm/server/ws/frame.js +35 -0
  131. package/lib/esm/server/ws/handshake.d.ts +4 -0
  132. package/lib/esm/server/ws/handshake.js +32 -0
  133. package/lib/esm/server/ws/index.d.ts +14 -0
  134. package/lib/esm/server/ws/index.js +68 -0
  135. package/lib/esm/ssg/bundle.d.ts +14 -0
  136. package/lib/esm/ssg/bundle.js +73 -0
  137. package/lib/esm/ssg/copy-public.d.ts +6 -0
  138. package/lib/esm/ssg/copy-public.js +34 -0
  139. package/lib/esm/ssg/discover.d.ts +15 -0
  140. package/lib/esm/ssg/discover.js +117 -0
  141. package/lib/esm/ssg/main.d.ts +2 -0
  142. package/lib/esm/ssg/main.js +122 -0
  143. package/lib/esm/ssg/rewrite.d.ts +9 -0
  144. package/lib/esm/ssg/rewrite.js +15 -0
  145. package/lib/esm/ssg/ssg.d.ts +26 -0
  146. package/lib/esm/ssg/ssg.js +84 -0
  147. package/lib/esm/transpile.d.mts +3 -0
  148. package/lib/esm/transpile.mjs +12 -0
  149. package/package.json +19 -10
  150. package/src/404.html +0 -14
  151. package/src/assert.ts +0 -56
  152. package/src/case.ts +0 -5
  153. package/src/components/_notes +0 -33
  154. package/src/components/button_bar.ts +0 -42
  155. package/src/components/inline_edit.ts +0 -78
  156. package/src/components/logger.ts +0 -35
  157. package/src/components/select.ts +0 -22
  158. package/src/components/test.ts +0 -5
  159. package/src/components/virtual_scroll.test.ts +0 -30
  160. package/src/components/virtual_scroll.ts +0 -199
  161. package/src/context.test.ts +0 -58
  162. package/src/context.ts +0 -67
  163. package/src/debounce.ts +0 -14
  164. package/src/diff.test.ts +0 -48
  165. package/src/diff.ts +0 -82
  166. package/src/display.ts +0 -18
  167. package/src/dom/README.md +0 -102
  168. package/src/dom/css/border.ts +0 -47
  169. package/src/dom/css/constants.ts +0 -34
  170. package/src/dom/css/core.ts +0 -28
  171. package/src/dom/css/fstyle.ts +0 -42
  172. package/src/dom/css/sizing.ts +0 -11
  173. package/src/dom/dom.ts +0 -183
  174. package/src/dom/fc.test.ts +0 -43
  175. package/src/dom/fc.ts +0 -80
  176. package/src/dom/form/form.app.ts +0 -50
  177. package/src/dom/form/form.ts +0 -82
  178. package/src/dom/form/index.html +0 -15
  179. package/src/dom/html.test.ts +0 -74
  180. package/src/dom/observable.test.ts +0 -43
  181. package/src/dom/observable.ts +0 -11
  182. package/src/dom/provide.ts +0 -11
  183. package/src/dom/router/link.ts +0 -14
  184. package/src/dom/router/router.ts +0 -72
  185. package/src/dom/test.ts +0 -11
  186. package/src/dom/types/css.ts +0 -10088
  187. package/src/dom/types/dom.ts +0 -0
  188. package/src/dom/types/html.ts +0 -629
  189. package/src/dom/xml.ts +0 -11
  190. package/src/equal.test.ts +0 -23
  191. package/src/equal.ts +0 -66
  192. package/src/favicon.ico +0 -0
  193. package/src/flags.test.ts +0 -43
  194. package/src/flags.ts +0 -53
  195. package/src/fs.test.ts +0 -106
  196. package/src/fs.ts +0 -300
  197. package/src/fs_node.ts +0 -57
  198. package/src/fs_win.test.ts +0 -11
  199. package/src/generator.test.ts +0 -27
  200. package/src/generator.ts +0 -12
  201. package/src/hooks/_notes +0 -6
  202. package/src/index.html +0 -82
  203. package/src/is_browser.js +0 -1
  204. package/src/lock.test.ts +0 -17
  205. package/src/lock.ts +0 -23
  206. package/src/log.ts +0 -155
  207. package/src/observable/_notes +0 -26
  208. package/src/observable/event.ts +0 -93
  209. package/src/observable/observable.test.ts +0 -73
  210. package/src/observable/observable.ts +0 -484
  211. package/src/pico/_variables.scss +0 -66
  212. package/src/pico/components/_accordion.scss +0 -112
  213. package/src/pico/components/_button-group.scss +0 -51
  214. package/src/pico/components/_card.scss +0 -47
  215. package/src/pico/components/_dropdown.scss +0 -203
  216. package/src/pico/components/_modal.scss +0 -181
  217. package/src/pico/components/_nav.scss +0 -79
  218. package/src/pico/components/_progress.scss +0 -70
  219. package/src/pico/components/_property.scss +0 -34
  220. package/src/pico/content/_button.scss +0 -152
  221. package/src/pico/content/_code.scss +0 -63
  222. package/src/pico/content/_embedded.scss +0 -0
  223. package/src/pico/content/_form-alt.scss +0 -276
  224. package/src/pico/content/_form.scss +0 -259
  225. package/src/pico/content/_misc.scss +0 -0
  226. package/src/pico/content/_table.scss +0 -28
  227. package/src/pico/content/_toggle.scss +0 -132
  228. package/src/pico/content/_typography.scss +0 -232
  229. package/src/pico/layout/_container.scss +0 -40
  230. package/src/pico/layout/_document.scss +0 -0
  231. package/src/pico/layout/_flex.scss +0 -46
  232. package/src/pico/layout/_grid.scss +0 -24
  233. package/src/pico/layout/_scroller.scss +0 -16
  234. package/src/pico/layout/_section.scss +0 -8
  235. package/src/pico/layout/_sectioning.scss +0 -55
  236. package/src/pico/pico.scss +0 -60
  237. package/src/pico/reset/_accessibility.scss +0 -34
  238. package/src/pico/reset/_button.scss +0 -17
  239. package/src/pico/reset/_code.scss +0 -15
  240. package/src/pico/reset/_document.scss +0 -48
  241. package/src/pico/reset/_embedded.scss +0 -39
  242. package/src/pico/reset/_form.scss +0 -97
  243. package/src/pico/reset/_misc.scss +0 -23
  244. package/src/pico/reset/_nav.scss +0 -5
  245. package/src/pico/reset/_progress.scss +0 -4
  246. package/src/pico/reset/_table.scss +0 -8
  247. package/src/pico/reset/_typography.scss +0 -25
  248. package/src/pico/themes/default/_colors.scss +0 -65
  249. package/src/pico/themes/default/_dark.scss +0 -148
  250. package/src/pico/themes/default/_light.scss +0 -149
  251. package/src/pico/themes/default/_styles.scss +0 -272
  252. package/src/pico/themes/default.scss +0 -34
  253. package/src/pico/utilities/_accessibility.scss +0 -3
  254. package/src/pico/utilities/_loading.scss +0 -52
  255. package/src/pico/utilities/_reduce-motion.scss +0 -27
  256. package/src/pico/utilities/_tooltip.scss +0 -101
  257. package/src/range.ts +0 -7
  258. package/src/result.test.ts +0 -101
  259. package/src/result.ts +0 -107
  260. package/src/safe.ts +0 -12
  261. package/src/scope/describe.ts +0 -81
  262. package/src/scope/display/console.ts +0 -26
  263. package/src/scope/display/dom.ts +0 -36
  264. package/src/scope/display/junit.ts +0 -64
  265. package/src/scope/execute.ts +0 -110
  266. package/src/scope/expect.ts +0 -169
  267. package/src/scope/fix.ts +0 -30
  268. package/src/scope/index.ts +0 -11
  269. package/src/scope/scope.ts +0 -21
  270. package/src/scope/state.ts +0 -13
  271. package/src/server/http/apps.ts +0 -26
  272. package/src/server/http/css.ts +0 -49
  273. package/src/server/http/index.ts +0 -127
  274. package/src/server/http/response.ts +0 -57
  275. package/src/server/http/sitemap.ts +0 -48
  276. package/src/server/http/static.ts +0 -30
  277. package/src/server/http/typescript.ts +0 -46
  278. package/src/server/main.ts +0 -23
  279. package/src/test.mjs +0 -33
  280. package/src/test_all.ts +0 -35
  281. package/src/transpile.mjs +0 -16
  282. package/src/zip/spec.txt +0 -3260
  283. package/tsconfig.json +0 -34
  284. /package/{src/awaitable.ts → lib/esm/awaitable.d.ts} +0 -0
  285. /package/{src/dom/index.ts → lib/esm/dom/index.d.ts} +0 -0
  286. /package/{src/dom/form/form.test.ts → lib/esm/dom/types/dom.d.ts} +0 -0
@@ -0,0 +1,72 @@
1
+ export type PathLike = string;
2
+ export interface PlatformParts {
3
+ SEP: string;
4
+ WD: string;
5
+ isAbsolute(path: PathLike): boolean;
6
+ }
7
+ export declare const PLATFORM_PARTS_WIN: PlatformParts;
8
+ export declare const PLATFORM_PARTS_UNIX: PlatformParts;
9
+ export declare const PLATFORM_PARTS: PlatformParts;
10
+ export declare const SEP: string;
11
+ export declare const WD: string;
12
+ export declare const isAbsolute: (path: PathLike) => boolean;
13
+ export interface Stats {
14
+ isDirectory(): boolean;
15
+ isFile(): boolean;
16
+ name: string;
17
+ }
18
+ export declare function basename(filename: PathLike): string;
19
+ export interface FileSystemAdapter {
20
+ stat(path: PathLike): Promise<Stats>;
21
+ readdir(path: PathLike): Promise<string[]>;
22
+ scandir(path: PathLike): Promise<Stats[]>;
23
+ mkdir(path: PathLike): Promise<void>;
24
+ copyFile(from: PathLike, to: PathLike): Promise<void>;
25
+ readFile(path: PathLike): Promise<string>;
26
+ writeFile(path: PathLike, contents: string): Promise<void>;
27
+ rm(path: PathLike): Promise<void>;
28
+ }
29
+ export declare class FileSystem implements FileSystemAdapter {
30
+ protected wd: string;
31
+ protected stack: string[];
32
+ protected adapter: FileSystemAdapter;
33
+ constructor(adapter?: FileSystemAdapter);
34
+ cwd(): string;
35
+ cd(dir: string): void;
36
+ pushd(dir: string): void;
37
+ popd(): void;
38
+ stat(path: PathLike): Promise<Stats>;
39
+ scandir(path: PathLike): Promise<Stats[]>;
40
+ readdir(path: PathLike): Promise<string[]>;
41
+ mkdir(path: string): Promise<void>;
42
+ copyFile(from: PathLike, to: PathLike): Promise<void>;
43
+ readFile(path: PathLike): Promise<string>;
44
+ writeFile(path: PathLike, contents: string): Promise<void>;
45
+ rm(path: PathLike): Promise<void>;
46
+ private p;
47
+ }
48
+ export declare class RecordFileSystemAdapter implements FileSystemAdapter {
49
+ private fs;
50
+ constructor(fs?: Record<string, string>);
51
+ stat(path: PathLike): Promise<Stats>;
52
+ scandir(path: PathLike): Promise<Stats[]>;
53
+ readdir(path: PathLike): Promise<string[]>;
54
+ mkdir(_path: string): Promise<void>;
55
+ copyFile(from: PathLike, to: PathLike): Promise<void>;
56
+ readFile(path: PathLike): Promise<string>;
57
+ writeFile(path: PathLike, contents: string): Promise<void>;
58
+ rm(path: PathLike): Promise<void>;
59
+ }
60
+ export declare class LocalStorageFileSystemAdapter extends RecordFileSystemAdapter {
61
+ constructor();
62
+ }
63
+ export type ObjectFileSystem = {
64
+ [k: string]: string | ObjectFileSystem;
65
+ };
66
+ export declare class ObjectFileSystemAdapter extends RecordFileSystemAdapter {
67
+ constructor(object: ObjectFileSystem);
68
+ }
69
+ export interface Tree {
70
+ [k: string]: string | Tree;
71
+ }
72
+ export declare function reset(fs: FileSystem, tree: Tree): Promise<void>;
package/lib/esm/fs.js ADDED
@@ -0,0 +1,227 @@
1
+ import { assertExists } from "./assert.js";
2
+ export const PLATFORM_PARTS_WIN = {
3
+ SEP: "\\",
4
+ WD: "C:\\\\",
5
+ isAbsolute: (path) => Boolean(path.match(/^[a-zA-Z]:\\/)),
6
+ };
7
+ export const PLATFORM_PARTS_UNIX = {
8
+ SEP: "/",
9
+ WD: "/",
10
+ isAbsolute: (path) => path[0] === "/",
11
+ };
12
+ export const PLATFORM_PARTS = typeof process !== "undefined" && process.platform === "win32"
13
+ ? PLATFORM_PARTS_WIN
14
+ : PLATFORM_PARTS_UNIX;
15
+ export const SEP = PLATFORM_PARTS.SEP;
16
+ export const WD = PLATFORM_PARTS.WD;
17
+ export const isAbsolute = PLATFORM_PARTS.isAbsolute;
18
+ export function basename(filename) {
19
+ if (filename.endsWith(SEP)) {
20
+ filename = filename.substring(0, filename.length - 1);
21
+ }
22
+ const basename = filename.split(SEP).at(-1) ?? "";
23
+ return basename;
24
+ }
25
+ function join(...paths) {
26
+ const pathParts = [];
27
+ for (const path of paths) {
28
+ for (const part of path.split(SEP)) {
29
+ switch (part) {
30
+ case "":
31
+ case ".":
32
+ break;
33
+ case "..":
34
+ pathParts.pop();
35
+ break;
36
+ default:
37
+ pathParts.push(part);
38
+ }
39
+ }
40
+ }
41
+ return ((PLATFORM_PARTS === PLATFORM_PARTS_UNIX ? SEP : "") + pathParts.join(SEP));
42
+ }
43
+ export class FileSystem {
44
+ wd = WD;
45
+ stack = [];
46
+ adapter;
47
+ constructor(adapter = new RecordFileSystemAdapter()) {
48
+ this.adapter = adapter;
49
+ }
50
+ cwd() {
51
+ return this.wd;
52
+ }
53
+ cd(dir) {
54
+ this.wd = this.p(dir);
55
+ }
56
+ pushd(dir) {
57
+ this.stack.push(this.wd);
58
+ this.cd(dir);
59
+ }
60
+ popd() {
61
+ if (this.stack.length > 0) {
62
+ this.wd = assertExists(this.stack.pop());
63
+ }
64
+ }
65
+ stat(path) {
66
+ return this.adapter.stat(this.p(path));
67
+ }
68
+ scandir(path) {
69
+ return this.adapter.scandir(this.p(path));
70
+ }
71
+ readdir(path) {
72
+ return this.adapter.readdir(this.p(path));
73
+ }
74
+ mkdir(path) {
75
+ return this.adapter.mkdir(this.p(path));
76
+ }
77
+ copyFile(from, to) {
78
+ return this.adapter.copyFile(this.p(from), this.p(to));
79
+ }
80
+ readFile(path) {
81
+ return this.adapter.readFile(this.p(path));
82
+ }
83
+ writeFile(path, contents) {
84
+ return this.adapter.writeFile(this.p(path), contents);
85
+ }
86
+ rm(path) {
87
+ return this.adapter.rm(this.p(path));
88
+ }
89
+ p(path) {
90
+ return isAbsolute(path) ? path : join(this.cwd(), path);
91
+ }
92
+ }
93
+ export class RecordFileSystemAdapter {
94
+ fs;
95
+ constructor(fs = {}) {
96
+ this.fs = fs;
97
+ }
98
+ stat(path) {
99
+ return new Promise((resolve, reject) => {
100
+ if (this.fs[path]) {
101
+ return resolve({
102
+ name: basename(path),
103
+ isDirectory() {
104
+ return false;
105
+ },
106
+ isFile() {
107
+ return true;
108
+ },
109
+ });
110
+ }
111
+ if (!path.endsWith(SEP))
112
+ path += SEP;
113
+ for (const filename of Object.keys(this.fs)) {
114
+ if (filename.startsWith(path)) {
115
+ return resolve({
116
+ name: basename(path),
117
+ isDirectory() {
118
+ return true;
119
+ },
120
+ isFile() {
121
+ return false;
122
+ },
123
+ });
124
+ }
125
+ }
126
+ reject();
127
+ });
128
+ }
129
+ async scandir(path) {
130
+ return (await this.readdir(path)).map((name) => {
131
+ const isFile = this.fs[join(path, name)] !== undefined;
132
+ return {
133
+ name,
134
+ isDirectory() {
135
+ return !isFile;
136
+ },
137
+ isFile() {
138
+ return isFile;
139
+ },
140
+ };
141
+ });
142
+ }
143
+ readdir(path) {
144
+ if (!path.endsWith(SEP))
145
+ path += SEP;
146
+ return new Promise((resolve) => {
147
+ const dir = new Set();
148
+ for (const filename of Object.keys(this.fs)) {
149
+ if (filename.startsWith(path)) {
150
+ const end = filename.indexOf(SEP, path.length + 1);
151
+ const basename = filename.substring(path.length, end === -1 ? undefined : end);
152
+ dir.add(basename);
153
+ }
154
+ }
155
+ return resolve([...dir].sort());
156
+ });
157
+ }
158
+ mkdir(_path) {
159
+ return Promise.resolve();
160
+ }
161
+ copyFile(from, to) {
162
+ return new Promise((resolve) => {
163
+ this.fs[to] = this.fs[from];
164
+ resolve();
165
+ });
166
+ }
167
+ readFile(path) {
168
+ return new Promise((resolve, reject) => {
169
+ const file = this.fs[path];
170
+ if (file === undefined) {
171
+ const error = new Error(`File Not Found ${path}`);
172
+ reject(error);
173
+ }
174
+ else {
175
+ resolve(file);
176
+ }
177
+ });
178
+ }
179
+ writeFile(path, contents) {
180
+ return new Promise((resolve) => {
181
+ this.fs[path] = contents;
182
+ resolve();
183
+ });
184
+ }
185
+ rm(path) {
186
+ return new Promise((resolve) => {
187
+ delete this.fs[path];
188
+ resolve();
189
+ });
190
+ }
191
+ }
192
+ export class LocalStorageFileSystemAdapter extends RecordFileSystemAdapter {
193
+ constructor() {
194
+ super(window.localStorage);
195
+ }
196
+ }
197
+ export class ObjectFileSystemAdapter extends RecordFileSystemAdapter {
198
+ constructor(object) {
199
+ super(reduceObjectFileSystem(object));
200
+ }
201
+ }
202
+ function reduceObjectFileSystem(object) {
203
+ const level = {};
204
+ for (const [k, v] of Object.entries(object)) {
205
+ if (typeof v === "string") {
206
+ level[`/${k}`] = v;
207
+ }
208
+ else {
209
+ for (const [k2, v2] of Object.entries(reduceObjectFileSystem(v))) {
210
+ level[`/${k}${k2}`] = v2;
211
+ }
212
+ }
213
+ }
214
+ return level;
215
+ }
216
+ export async function reset(fs, tree) {
217
+ for (const [path, file] of Object.entries(tree)) {
218
+ if (typeof file === "string") {
219
+ await fs.writeFile(path, file);
220
+ }
221
+ else {
222
+ fs.cd(path);
223
+ await reset(fs, file);
224
+ fs.cd("..");
225
+ }
226
+ }
227
+ }
@@ -0,0 +1,15 @@
1
+ import { FileSystem, type FileSystemAdapter, type Stats } from "./fs.ts";
2
+ export declare class NodeFileSystem extends FileSystem {
3
+ constructor(cd?: string);
4
+ }
5
+ /** Jiffies FileSystemAdapter using NodeJS' fs/promises. */
6
+ export declare class NodeFileSystemAdapter implements FileSystemAdapter {
7
+ stat(path: string): Promise<Stats>;
8
+ readdir(path: string): Promise<string[]>;
9
+ mkdir(path: string): Promise<void>;
10
+ scandir(path: string): Promise<Stats[]>;
11
+ copyFile(from: string, to: string): Promise<void>;
12
+ readFile(path: string): Promise<string>;
13
+ writeFile(path: string, contents: string): Promise<void>;
14
+ rm(path: string): Promise<void>;
15
+ }
@@ -0,0 +1,45 @@
1
+ import { copyFile, mkdir, readdir, readFile, rm, stat, writeFile, } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
3
+ import { FileSystem } from "./fs.js";
4
+ export class NodeFileSystem extends FileSystem {
5
+ constructor(cd = process.cwd()) {
6
+ super(new NodeFileSystemAdapter());
7
+ this.cd(cd);
8
+ }
9
+ }
10
+ /** Jiffies FileSystemAdapter using NodeJS' fs/promises. */
11
+ export class NodeFileSystemAdapter {
12
+ async stat(path) {
13
+ const fsStat = await stat(path);
14
+ return {
15
+ name: basename(path),
16
+ isDirectory() {
17
+ return fsStat.isDirectory();
18
+ },
19
+ isFile() {
20
+ return !fsStat.isDirectory();
21
+ },
22
+ };
23
+ }
24
+ readdir(path) {
25
+ return readdir(path);
26
+ }
27
+ async mkdir(path) {
28
+ await mkdir(path, { recursive: true });
29
+ }
30
+ async scandir(path) {
31
+ return Promise.all((await this.readdir(path)).map((name) => this.stat(join(path, name))));
32
+ }
33
+ copyFile(from, to) {
34
+ return copyFile(from, to);
35
+ }
36
+ readFile(path) {
37
+ return readFile(path, "utf-8");
38
+ }
39
+ writeFile(path, contents) {
40
+ return writeFile(path, contents, "utf-8");
41
+ }
42
+ rm(path) {
43
+ return rm(path, { force: true, recursive: true });
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ export declare function takeWhile<T>(predicate: (t: T) => boolean, iterator: Iterable<T>): Generator<T, void, unknown>;
@@ -0,0 +1,10 @@
1
+ export function* takeWhile(predicate, iterator) {
2
+ for (const x of iterator) {
3
+ if (predicate(x)) {
4
+ yield x;
5
+ }
6
+ else {
7
+ return;
8
+ }
9
+ }
10
+ }
@@ -0,0 +1 @@
1
+ export declare function lock<CF extends (...args: unknown[]) => unknown>(fn: CF): CF;
@@ -0,0 +1,23 @@
1
+ const locks = new WeakSet();
2
+ export function lock(fn) {
3
+ const lockingFn = (...args) => {
4
+ let ret;
5
+ let ex = null;
6
+ if (!locks.has(fn)) {
7
+ locks.add(fn);
8
+ try {
9
+ ret = fn(...args);
10
+ }
11
+ catch (e) {
12
+ ex = e;
13
+ }
14
+ }
15
+ locks.delete(fn);
16
+ if (ex !== null) {
17
+ throw ex;
18
+ }
19
+ // @ts-expect-error 2454 can't track ret's assignment
20
+ return ret;
21
+ };
22
+ return lockingFn;
23
+ }
@@ -0,0 +1,69 @@
1
+ import { type Display } from "./display.ts";
2
+ export type Log = (message: Display, data?: object) => void;
3
+ export interface Logger {
4
+ logAt: (level: number, prefix: string, fn?: (logLine: string) => void) => Log;
5
+ level: number;
6
+ format: <D extends {
7
+ name: string;
8
+ prefix: string;
9
+ level: number;
10
+ message: string;
11
+ source: string;
12
+ }>(data: D) => string;
13
+ console: Console;
14
+ default: (logLine: string) => void;
15
+ debug: Log;
16
+ info: Log;
17
+ warn: Log;
18
+ error: Log;
19
+ }
20
+ export declare const LEVEL: {
21
+ UNKNOWN: number;
22
+ DEBUG: number;
23
+ VERBOSE: number;
24
+ INFO: number;
25
+ WARN: number;
26
+ ERROR: number;
27
+ SILENT: number;
28
+ };
29
+ export declare const LEVELS: Record<string, number>;
30
+ export declare function getLogLevel(level?: string): number;
31
+ export declare function basicLogFormatter(data: {
32
+ name: string;
33
+ prefix: string;
34
+ level: number;
35
+ message: string;
36
+ source: string;
37
+ }): string;
38
+ export type LoggerFormatFn = <D extends {
39
+ name: string;
40
+ prefix: string;
41
+ level: number;
42
+ message: string;
43
+ source: string;
44
+ }>(data: D) => string;
45
+ export interface PrettyLogOptions {
46
+ /** Render colored, human-shaped lines. Default: !!process.stdout.isTTY. */
47
+ tty?: boolean;
48
+ /** Emit ANSI color escapes. Default: same as `tty`. */
49
+ color?: boolean;
50
+ /** Injectable clock for deterministic timestamps in tests. Default: new Date. */
51
+ now?: () => Date;
52
+ }
53
+ /**
54
+ * Factory returning a {@link LoggerFormatFn} that is pretty + colored on a TTY
55
+ * and falls back to `JSON.stringify` when not. Options resolve once at
56
+ * construction (`tty` reads `process.stdout.isTTY`), so production piped output
57
+ * stays JSON; tests inject `tty`/`color`/`now` for a deterministic colorless
58
+ * layout assertable without a real terminal.
59
+ */
60
+ export declare function prettyLogFormatter(options?: PrettyLogOptions): LoggerFormatFn;
61
+ export declare function getLogger(name: string, args?: LoggerFormatFn | {
62
+ format?: LoggerFormatFn;
63
+ console?: Console;
64
+ }): Logger;
65
+ export declare const DEFAULT_LOGGER: Logger;
66
+ export declare function debug(message: Display, data?: object): void;
67
+ export declare function info(message: Display, data?: object): void;
68
+ export declare function warn(message: Display, data?: object): void;
69
+ export declare function error(message: Display, data?: object): void;
package/lib/esm/log.js ADDED
@@ -0,0 +1,211 @@
1
+ import { display } from "./display.js";
2
+ export const LEVEL = {
3
+ UNKNOWN: 2,
4
+ DEBUG: 1,
5
+ VERBOSE: 1,
6
+ INFO: 2,
7
+ WARN: 3,
8
+ ERROR: 4,
9
+ SILENT: 5,
10
+ };
11
+ export const LEVELS = {
12
+ unknown: LEVEL.UNKNOWN,
13
+ debug: LEVEL.DEBUG,
14
+ verbose: LEVEL.VERBOSE,
15
+ info: LEVEL.INFO,
16
+ warn: LEVEL.WARN,
17
+ error: LEVEL.ERROR,
18
+ silent: LEVEL.SILENT,
19
+ };
20
+ export function getLogLevel(level = "") {
21
+ return (LEVELS[level.toLowerCase()] ??
22
+ (!Number.isNaN(+level) ? Number(level) : LEVEL.INFO));
23
+ }
24
+ export function basicLogFormatter(data) {
25
+ return `${data.prefix}: ${data.message}`;
26
+ }
27
+ function findSource() {
28
+ const err = new Error();
29
+ // Stack will be:
30
+ // findSource
31
+ // logAt
32
+ // {source}
33
+ const lines = err.stack?.split("\n") ?? [];
34
+ const atLines = lines.filter((line) => line.match(/^\s*at/));
35
+ return atLines[2]?.trim().slice("at ".length) ?? "(unknown)";
36
+ }
37
+ // ── prettyLogFormatter ──────────────────────────────────────────────────────
38
+ // A TTY-aware formatter: compact, color-and-glyph-coded human lines on a
39
+ // terminal, byte-identical JSON.stringify when piped/redirected so machine
40
+ // tooling is unchanged. See docs/developer/2026-06-09-A-log-formatter/design.md.
41
+ // Raw ANSI escapes; no color/log runtime dependency in this library.
42
+ const ANSI = {
43
+ reset: "\x1b[0m",
44
+ dim: "\x1b[2m",
45
+ bold: "\x1b[1m",
46
+ green: "\x1b[32m",
47
+ yellow: "\x1b[33m",
48
+ red: "\x1b[31m",
49
+ cyan: "\x1b[36m",
50
+ white: "\x1b[37m",
51
+ };
52
+ // HH:MM:SS.mmm sliced from a UTC ISO 8601 string. Timezone-stable by
53
+ // construction: it never reads local-time accessors, so output depends only on
54
+ // the ISO input, not the runner's TZ.
55
+ function shortClock(iso) {
56
+ return iso.slice(11, 23);
57
+ }
58
+ // Glyph + color per severity, keyed on the numeric level (DEBUG 1, INFO 2,
59
+ // WARN 3, ERROR 4). Fixed single-column glyph so messages align vertically.
60
+ function levelGlyph(level) {
61
+ if (level <= LEVEL.DEBUG)
62
+ return { glyph: "·", color: "dim" };
63
+ if (level === LEVEL.INFO)
64
+ return { glyph: "ℹ", color: "green" };
65
+ if (level === LEVEL.WARN)
66
+ return { glyph: "⚠", color: "yellow" };
67
+ return { glyph: "✖", color: "red" };
68
+ }
69
+ function methodColor(method) {
70
+ switch (method) {
71
+ case "GET":
72
+ return "cyan";
73
+ case "POST":
74
+ return "green";
75
+ case "PUT":
76
+ case "PATCH":
77
+ return "yellow";
78
+ case "DELETE":
79
+ return "red";
80
+ default:
81
+ return "white";
82
+ }
83
+ }
84
+ function statusColor(status) {
85
+ if (status >= 500)
86
+ return "red";
87
+ if (status >= 400)
88
+ return "yellow";
89
+ if (status >= 300)
90
+ return "cyan";
91
+ return "green";
92
+ }
93
+ // Fields that are pure metadata noise on a human terminal: `name`/`level` are
94
+ // constant or duplicate `prefix`, and `source` resolves to the info() wrapper.
95
+ // `message` is rendered on its own, never echoed into the trailing tail.
96
+ const HUMAN_DROP = new Set(["name", "prefix", "level", "source", "message"]);
97
+ /**
98
+ * Factory returning a {@link LoggerFormatFn} that is pretty + colored on a TTY
99
+ * and falls back to `JSON.stringify` when not. Options resolve once at
100
+ * construction (`tty` reads `process.stdout.isTTY`), so production piped output
101
+ * stays JSON; tests inject `tty`/`color`/`now` for a deterministic colorless
102
+ * layout assertable without a real terminal.
103
+ */
104
+ export function prettyLogFormatter(options = {}) {
105
+ const tty = options.tty ?? !!process.stdout.isTTY;
106
+ const color = options.color ?? tty;
107
+ const now = options.now ?? (() => new Date());
108
+ const paint = (code, s) => color ? `${ANSI[code]}${s}${ANSI.reset}` : s;
109
+ return (data) => {
110
+ if (!tty)
111
+ return JSON.stringify(data);
112
+ const record = data;
113
+ const { glyph, color: glyphColor } = levelGlyph(data.level);
114
+ const mark = paint(glyphColor, glyph);
115
+ // Access-log shape: `<glyph> <clock> <METHOD> <path> <client> [status] [ms]`.
116
+ // Clock comes from the request's `when` ISO (UTC slice), not now().
117
+ if (data.message === "Request") {
118
+ const when = typeof record.when === "string" ? record.when : "";
119
+ const how = typeof record.how === "string" ? record.how : "";
120
+ const space = how.indexOf(" ");
121
+ const method = space === -1 ? how : how.slice(0, space);
122
+ const path = space === -1 ? "" : how.slice(space + 1);
123
+ const segments = [
124
+ mark,
125
+ paint("dim", shortClock(when || now().toISOString())),
126
+ paint(methodColor(method), method),
127
+ paint("bold", path),
128
+ ];
129
+ if (record.who !== undefined) {
130
+ segments.push(paint("dim", String(record.who)));
131
+ }
132
+ if (record.status !== undefined) {
133
+ segments.push(paint(statusColor(Number(record.status)), String(record.status)));
134
+ }
135
+ if (record.ms !== undefined) {
136
+ segments.push(paint("dim", `${record.ms}ms`));
137
+ }
138
+ return segments.filter((s) => s !== "").join(" ");
139
+ }
140
+ // Generic shape: `<glyph> <clock> <message> <dim key=value …>`.
141
+ const tail = Object.entries(record)
142
+ .filter(([key]) => !HUMAN_DROP.has(key))
143
+ .map(([key, value]) => paint("dim", `${key}=${typeof value === "string" ? value : JSON.stringify(value)}`));
144
+ return [
145
+ mark,
146
+ paint("dim", shortClock(now().toISOString())),
147
+ paint("bold", data.message),
148
+ ...tail,
149
+ ].join(" ");
150
+ };
151
+ }
152
+ export function getLogger(name, args = {
153
+ format: JSON.stringify,
154
+ console,
155
+ }) {
156
+ if (args instanceof Function) {
157
+ args = { format: args };
158
+ }
159
+ const defaultLog = (logLine) => {
160
+ logger.console.info(logLine);
161
+ };
162
+ const logAt = (level, prefix, fn = defaultLog) => (message, data) => level >= (logger.level ?? LEVEL.SILENT)
163
+ ? fn(logger.format?.({
164
+ name,
165
+ prefix,
166
+ level,
167
+ message: display(message),
168
+ ...data,
169
+ source: findSource(),
170
+ }))
171
+ : undefined;
172
+ const logger = {
173
+ logAt,
174
+ default: defaultLog,
175
+ level: LEVEL.INFO,
176
+ format: args.format ?? JSON.stringify,
177
+ console: args.console ?? global.console,
178
+ debug: logAt(LEVEL.DEBUG, "DEBUG", (l) => logger.console.debug(l)),
179
+ info: logAt(LEVEL.INFO, "INFO", (l) => logger.console.info(l)),
180
+ warn: logAt(LEVEL.WARN, "WARN", (l) => logger.console.warn(l)),
181
+ error: logAt(LEVEL.ERROR, "ERR", (l) => logger.console.error(l)),
182
+ };
183
+ return logger;
184
+ }
185
+ export const DEFAULT_LOGGER = getLogger("default", {
186
+ format: prettyLogFormatter(),
187
+ });
188
+ export function debug(message, data) {
189
+ if (data)
190
+ DEFAULT_LOGGER.debug(message, data);
191
+ else
192
+ DEFAULT_LOGGER.debug(message);
193
+ }
194
+ export function info(message, data) {
195
+ if (data)
196
+ DEFAULT_LOGGER.info(message, data);
197
+ else
198
+ DEFAULT_LOGGER.info(message);
199
+ }
200
+ export function warn(message, data) {
201
+ if (data)
202
+ DEFAULT_LOGGER.warn(message, data);
203
+ else
204
+ DEFAULT_LOGGER.warn(message);
205
+ }
206
+ export function error(message, data) {
207
+ if (data)
208
+ DEFAULT_LOGGER.error(message, data);
209
+ else
210
+ DEFAULT_LOGGER.error(message);
211
+ }