@davidsouther/jiffies 2026.24.0 → 2026.24.2

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 (226) hide show
  1. package/lib/esm/assert.d.ts +26 -0
  2. package/lib/esm/assert.js +38 -0
  3. package/lib/esm/awaitable.js +1 -0
  4. package/lib/esm/case.d.ts +1 -0
  5. package/lib/esm/case.js +5 -0
  6. package/lib/esm/components/accordion.d.ts +5 -0
  7. package/lib/esm/components/accordion.js +9 -0
  8. package/lib/esm/components/alert.d.ts +7 -0
  9. package/lib/esm/components/alert.js +31 -0
  10. package/lib/esm/components/button_bar.d.ts +8 -0
  11. package/lib/esm/components/button_bar.js +25 -0
  12. package/lib/esm/components/card.d.ts +8 -0
  13. package/lib/esm/components/card.js +31 -0
  14. package/lib/esm/components/children.d.ts +2 -0
  15. package/{src/components/children.ts → lib/esm/components/children.js} +2 -6
  16. package/lib/esm/components/form.d.ts +5 -0
  17. package/lib/esm/components/form.js +13 -0
  18. package/{src/components/index.ts → lib/esm/components/index.d.ts} +2 -15
  19. package/lib/esm/components/index.js +10 -0
  20. package/lib/esm/components/inline_edit.d.ts +12 -0
  21. package/lib/esm/components/inline_edit.js +48 -0
  22. package/lib/esm/components/link.d.ts +5 -0
  23. package/lib/esm/components/link.js +11 -0
  24. package/lib/esm/components/logger.d.ts +6 -0
  25. package/lib/esm/components/logger.js +22 -0
  26. package/lib/esm/components/modal.d.ts +2 -0
  27. package/{src/components/modal.ts → lib/esm/components/modal.js} +3 -8
  28. package/lib/esm/components/nav.d.ts +11 -0
  29. package/lib/esm/components/nav.js +27 -0
  30. package/lib/esm/components/property.d.ts +9 -0
  31. package/lib/esm/components/property.js +16 -0
  32. package/lib/esm/components/select.d.ts +10 -0
  33. package/lib/esm/components/select.js +3 -0
  34. package/lib/esm/components/tabs.d.ts +20 -0
  35. package/lib/esm/components/tabs.js +45 -0
  36. package/lib/esm/components/virtual_scroll.d.ts +42 -0
  37. package/lib/esm/components/virtual_scroll.js +94 -0
  38. package/lib/esm/debounce.d.ts +1 -0
  39. package/lib/esm/debounce.js +11 -0
  40. package/lib/esm/diff.d.ts +15 -0
  41. package/lib/esm/diff.js +50 -0
  42. package/lib/esm/display.d.ts +5 -0
  43. package/lib/esm/display.js +11 -0
  44. package/lib/esm/dom/css/border.d.ts +11 -0
  45. package/lib/esm/dom/css/border.js +27 -0
  46. package/lib/esm/dom/css/constants.d.ts +31 -0
  47. package/lib/esm/dom/css/constants.js +28 -0
  48. package/lib/esm/dom/css/core.d.ts +5 -0
  49. package/lib/esm/dom/css/core.js +24 -0
  50. package/lib/esm/dom/css/fstyle.d.ts +5 -0
  51. package/lib/esm/dom/css/fstyle.js +32 -0
  52. package/lib/esm/dom/css/sizing.d.ts +5 -0
  53. package/lib/esm/dom/css/sizing.js +10 -0
  54. package/lib/esm/dom/dom.d.ts +36 -0
  55. package/lib/esm/dom/dom.js +217 -0
  56. package/lib/esm/dom/fc.d.ts +10 -0
  57. package/lib/esm/dom/fc.js +32 -0
  58. package/lib/esm/dom/form/form.app.d.ts +1 -0
  59. package/lib/esm/dom/form/form.app.js +19 -0
  60. package/lib/esm/dom/form/form.d.ts +27 -0
  61. package/lib/esm/dom/form/form.js +65 -0
  62. package/lib/esm/dom/html.d.ts +112 -0
  63. package/{src/dom/html.ts → lib/esm/dom/html.js} +2 -14
  64. package/lib/esm/dom/hydrate.d.ts +39 -0
  65. package/lib/esm/dom/hydrate.js +187 -0
  66. package/lib/esm/dom/index.js +2 -0
  67. package/lib/esm/dom/navigation/index.d.ts +76 -0
  68. package/lib/esm/dom/navigation/index.js +292 -0
  69. package/lib/esm/dom/observable.d.ts +2 -0
  70. package/lib/esm/dom/observable.js +6 -0
  71. package/lib/esm/dom/provide.d.ts +3 -0
  72. package/lib/esm/dom/provide.js +7 -0
  73. package/lib/esm/dom/render.d.ts +8 -0
  74. package/lib/esm/dom/render.js +28 -0
  75. package/lib/esm/dom/router/link.d.ts +6 -0
  76. package/lib/esm/dom/router/link.js +3 -0
  77. package/lib/esm/dom/router/router.d.ts +13 -0
  78. package/lib/esm/dom/router/router.js +52 -0
  79. package/lib/esm/dom/svg.d.ts +64 -0
  80. package/{src/dom/svg.ts → lib/esm/dom/svg.js} +2 -19
  81. package/lib/esm/dom/types/css.d.ts +6590 -0
  82. package/lib/esm/dom/types/css.js +1 -0
  83. package/lib/esm/dom/types/dom.js +1 -0
  84. package/lib/esm/dom/types/html.d.ts +614 -0
  85. package/lib/esm/dom/types/html.js +1 -0
  86. package/lib/esm/dom/xml.d.ts +1 -0
  87. package/lib/esm/dom/xml.js +4 -0
  88. package/lib/esm/equal.d.ts +11 -0
  89. package/lib/esm/equal.js +43 -0
  90. package/lib/esm/fs.d.ts +72 -0
  91. package/lib/esm/fs.js +227 -0
  92. package/lib/esm/fs_node.d.ts +15 -0
  93. package/lib/esm/fs_node.js +45 -0
  94. package/lib/esm/generator.d.ts +1 -0
  95. package/lib/esm/generator.js +10 -0
  96. package/lib/esm/lock.d.ts +1 -0
  97. package/lib/esm/lock.js +23 -0
  98. package/lib/esm/log.d.ts +69 -0
  99. package/lib/esm/log.js +211 -0
  100. package/lib/esm/observable/event.d.ts +35 -0
  101. package/lib/esm/observable/event.js +46 -0
  102. package/lib/esm/observable/observable.d.ts +134 -0
  103. package/lib/esm/observable/observable.js +349 -0
  104. package/lib/esm/range.d.ts +1 -0
  105. package/lib/esm/range.js +7 -0
  106. package/lib/esm/result.d.ts +31 -0
  107. package/lib/esm/result.js +66 -0
  108. package/lib/esm/safe.d.ts +1 -0
  109. package/lib/esm/safe.js +10 -0
  110. package/lib/esm/server/http/apps.d.ts +5 -0
  111. package/lib/esm/server/http/apps.js +23 -0
  112. package/lib/esm/server/http/css.d.ts +5 -0
  113. package/lib/esm/server/http/css.js +43 -0
  114. package/lib/esm/server/http/index.d.ts +16 -0
  115. package/lib/esm/server/http/index.js +78 -0
  116. package/lib/esm/server/http/response.d.ts +4 -0
  117. package/lib/esm/server/http/response.js +43 -0
  118. package/lib/esm/server/http/sitemap.d.ts +2 -0
  119. package/lib/esm/server/http/sitemap.js +22 -0
  120. package/lib/esm/server/http/static.d.ts +2 -0
  121. package/lib/esm/server/http/static.js +22 -0
  122. package/lib/esm/server/http/typescript.d.ts +5 -0
  123. package/lib/esm/server/http/typescript.js +40 -0
  124. package/lib/esm/server/live-reload.d.ts +46 -0
  125. package/lib/esm/server/live-reload.js +161 -0
  126. package/lib/esm/server/main.d.ts +2 -0
  127. package/{src/server/main.ts → lib/esm/server/main.js} +8 -15
  128. package/lib/esm/server/ws/frame.d.ts +2 -0
  129. package/lib/esm/server/ws/frame.js +35 -0
  130. package/lib/esm/server/ws/handshake.d.ts +4 -0
  131. package/lib/esm/server/ws/handshake.js +32 -0
  132. package/lib/esm/server/ws/index.d.ts +14 -0
  133. package/lib/esm/server/ws/index.js +68 -0
  134. package/lib/esm/ssg/bundle.d.ts +14 -0
  135. package/lib/esm/ssg/bundle.js +73 -0
  136. package/lib/esm/ssg/copy-public.d.ts +6 -0
  137. package/lib/esm/ssg/copy-public.js +34 -0
  138. package/lib/esm/ssg/discover.d.ts +15 -0
  139. package/lib/esm/ssg/discover.js +117 -0
  140. package/lib/esm/ssg/main.d.ts +2 -0
  141. package/lib/esm/ssg/main.js +122 -0
  142. package/lib/esm/ssg/rewrite.d.ts +9 -0
  143. package/{src/ssg/rewrite.ts → lib/esm/ssg/rewrite.js} +6 -9
  144. package/lib/esm/ssg/ssg.d.ts +26 -0
  145. package/lib/esm/ssg/ssg.js +84 -0
  146. package/lib/esm/transpile.d.mts +3 -0
  147. package/lib/esm/transpile.mjs +12 -0
  148. package/package.json +11 -7
  149. package/src/404.html +0 -14
  150. package/src/assert.ts +0 -56
  151. package/src/case.ts +0 -5
  152. package/src/components/_notes +0 -33
  153. package/src/components/accordion.ts +0 -25
  154. package/src/components/alert.ts +0 -47
  155. package/src/components/button_bar.ts +0 -42
  156. package/src/components/card.ts +0 -54
  157. package/src/components/form.ts +0 -25
  158. package/src/components/inline_edit.ts +0 -78
  159. package/src/components/link.ts +0 -22
  160. package/src/components/logger.ts +0 -35
  161. package/src/components/nav.ts +0 -42
  162. package/src/components/property.ts +0 -32
  163. package/src/components/select.ts +0 -22
  164. package/src/components/tabs.ts +0 -82
  165. package/src/components/virtual_scroll.ts +0 -199
  166. package/src/debounce.ts +0 -14
  167. package/src/diff.ts +0 -82
  168. package/src/display.ts +0 -18
  169. package/src/dom/README.md +0 -107
  170. package/src/dom/SKILL.md +0 -201
  171. package/src/dom/css/border.ts +0 -47
  172. package/src/dom/css/constants.ts +0 -34
  173. package/src/dom/css/core.ts +0 -28
  174. package/src/dom/css/fstyle.ts +0 -42
  175. package/src/dom/css/sizing.ts +0 -11
  176. package/src/dom/dom.ts +0 -327
  177. package/src/dom/fc.ts +0 -81
  178. package/src/dom/form/form.app.ts +0 -44
  179. package/src/dom/form/form.ts +0 -151
  180. package/src/dom/form/index.html +0 -15
  181. package/src/dom/hydrate.ts +0 -206
  182. package/src/dom/navigation/index.ts +0 -349
  183. package/src/dom/observable.ts +0 -11
  184. package/src/dom/provide.ts +0 -11
  185. package/src/dom/render.ts +0 -41
  186. package/src/dom/router/link.ts +0 -14
  187. package/src/dom/router/router.ts +0 -72
  188. package/src/dom/types/css.ts +0 -10088
  189. package/src/dom/types/html.ts +0 -629
  190. package/src/dom/xml.ts +0 -11
  191. package/src/equal.ts +0 -66
  192. package/src/favicon.ico +0 -0
  193. package/src/fs.ts +0 -300
  194. package/src/fs_node.ts +0 -57
  195. package/src/generator.ts +0 -12
  196. package/src/hooks/_notes +0 -6
  197. package/src/lock.ts +0 -23
  198. package/src/log.ts +0 -307
  199. package/src/observable/_notes +0 -26
  200. package/src/observable/event.ts +0 -93
  201. package/src/observable/observable.ts +0 -484
  202. package/src/range.ts +0 -7
  203. package/src/result.ts +0 -107
  204. package/src/safe.ts +0 -12
  205. package/src/server/http/apps.ts +0 -26
  206. package/src/server/http/css.ts +0 -49
  207. package/src/server/http/index.ts +0 -127
  208. package/src/server/http/response.ts +0 -60
  209. package/src/server/http/sitemap.ts +0 -24
  210. package/src/server/http/static.ts +0 -28
  211. package/src/server/http/typescript.ts +0 -46
  212. package/src/server/live-reload.ts +0 -208
  213. package/src/server/ws/frame.ts +0 -36
  214. package/src/server/ws/handshake.ts +0 -42
  215. package/src/server/ws/index.ts +0 -100
  216. package/src/ssg/bundle.ts +0 -85
  217. package/src/ssg/copy-public.ts +0 -44
  218. package/src/ssg/discover.ts +0 -143
  219. package/src/ssg/main.ts +0 -168
  220. package/src/ssg/ssg.ts +0 -134
  221. package/src/transpile.mjs +0 -16
  222. package/src/zip/spec.txt +0 -3260
  223. package/tsconfig.json +0 -34
  224. /package/{src/awaitable.ts → lib/esm/awaitable.d.ts} +0 -0
  225. /package/{src/dom/index.ts → lib/esm/dom/index.d.ts} +0 -0
  226. /package/{src/dom/types/dom.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
+ }