Hell yeah!

This commit is contained in:
Corey Johnson 2025-06-22 12:32:08 -07:00
parent 744a92e446
commit eb4a1fac14
20 changed files with 187 additions and 193 deletions

View File

@ -4,12 +4,27 @@
"": {
"name": "workshop",
},
"packages/attache": {
"name": "attache",
"dependencies": {
"@workshop/shared": "workspace:*",
"hono": "^4.8.0",
"nanoid": "^5.1.5",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
"packages/http": {
"name": "@workshop/http",
"dependencies": {
"@workshop/nano-remix": "workspace:*",
"@workshop/shared": "workspace:*",
"hono": "^4.7.11",
"@workshop/todo": "workspace:*",
"hono": "catalog:",
},
"devDependencies": {
"@types/bun": "latest",
@ -30,7 +45,7 @@
"packages/nano-remix": {
"name": "@workshop/nano-remix",
"dependencies": {
"hono": "^4.7.11",
"hono": "catalog:",
},
"devDependencies": {
"@types/bun": "latest",
@ -77,8 +92,8 @@
"typescript": "^5",
},
},
"packages/text-do": {
"name": "text-do",
"packages/todo": {
"name": "@workshop/todo",
"dependencies": {
"@codemirror/commands": "^6.8.1",
"@codemirror/language": "^6.11.1",
@ -87,17 +102,21 @@
"@lezer/generator": "^1.7.3",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
"hono": "^4.8.0",
"hono": "catalog:",
"luxon": "^3.6.1",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"hono": "^4",
"typescript": "^5",
},
},
},
"catalog": {
"hono": "^4.8.0",
},
"packages": {
"@codemirror/commands": ["@codemirror/commands@6.8.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw=="],
@ -113,11 +132,11 @@
"@discordjs/formatters": ["@discordjs/formatters@0.6.1", "", { "dependencies": { "discord-api-types": "^0.38.1" } }, "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg=="],
"@discordjs/rest": ["@discordjs/rest@2.5.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.1" } }, "sha512-PWhchxTzpn9EV3vvPRpwS0EE2rNYB9pvzDU/eLLW3mByJl0ZHZjHI2/wA8EbH2gRMQV7nu+0FoDF84oiPl8VAQ=="],
"@discordjs/rest": ["@discordjs/rest@2.5.1", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw=="],
"@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/ws": ["@discordjs/ws@1.2.2", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.0", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-dyfq7yn0wO0IYeYOs3z79I6/HumhmKISzFL0Z+007zQJMtAFGtt3AEoq1nuLXtcunUE5YYYQqgKvybXukAK8/w=="],
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
"@lezer/common": ["@lezer/common@1.2.3", "", {}, "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="],
@ -129,7 +148,7 @@
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.12.3", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-DyVYSOafBvk3/j1Oka4z5BWT8o4AFmoNyZY9pALOm7Lh3GZglR71Co4r4dEUoqDWdDazIZQHBe7J2Nwkg6gHgQ=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.13.0", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-P5FZsXU0kY881F6Hbk9GhsYx02/KgWK1DYf7/tyE/1lcFKhDYPQR9iYjhQXJn+Sg6hQleMo3DB7h7+p4wgp2Lw=="],
"@openai/agents": ["@openai/agents@0.0.8", "", { "dependencies": { "@openai/agents-core": "0.0.8", "@openai/agents-openai": "0.0.8", "@openai/agents-realtime": "0.0.8", "debug": "^4.4.0", "openai": "^5.0.1" } }, "sha512-HAPP4QM47kWeWw70uxCzr5zjqHuDIvQ8Obx+98J66lcEeIZzMChHN60k5ew8DITScmzDVAVuwdzfAImSyq002w=="],
@ -139,7 +158,7 @@
"@openai/agents-realtime": ["@openai/agents-realtime@0.0.8", "", { "dependencies": { "@openai/agents-core": "0.0.8", "@openai/zod": "npm:zod@^3.25.40", "@types/ws": "^8.18.1", "debug": "^4.4.0", "ws": "^8.18.1" } }, "sha512-f+CxHICIFvCwbMCznop+bz+TTgnFfFpscN+9OTfiU5ITnaohRf+qbyU8PRgQZnSbsxRZyTOgqFoJ+2wWxM5tHA=="],
"@openai/zod": ["zod@3.25.64", "", {}, "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g=="],
"@openai/zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
"@planetscale/database": ["@planetscale/database@1.19.0", "", {}, "sha512-Tv4jcFUFAFjOWrGSio49H6R2ijALv0ZzVBfJKIdm+kl9X046Fh4LLawrF9OMsglVbK6ukqMJsUCeucGAFTBcMA=="],
@ -149,11 +168,11 @@
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
"@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
"@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
"@types/luxon": ["@types/luxon@3.6.2", "", {}, "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw=="],
"@types/node": ["@types/node@24.0.1", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw=="],
"@types/node": ["@types/node@24.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
@ -171,13 +190,17 @@
"@workshop/spike": ["@workshop/spike@workspace:packages/spike"],
"@workshop/todo": ["@workshop/todo@workspace:packages/todo"],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"attache": ["attache@workspace:packages/attache"],
"body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
"bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="],
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
@ -203,9 +226,9 @@
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"discord-api-types": ["discord-api-types@0.38.11", "", {}, "sha512-XN0qhcQpetkyb/49hcDHuoeUPsQqOkb17wbV/t48gUkoEDi4ajhsxqugGcxvcN17BBtI9FPPWEgzv6IhQmCwyw=="],
"discord-api-types": ["discord-api-types@0.38.12", "", {}, "sha512-vqkRM50N5Zc6OVckAqtSslbUEoXmpN4bd9xq2jkoK9fgO3KNRIOyMMQ7ipqjwjKuAgzWvU6G8bRIcYWaUe1sCA=="],
"discord.js": ["discord.js@14.19.3", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.5.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.2", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.1" } }, "sha512-lncTRk0k+8Q5D3nThnODBR8fR8x2fM798o8Vsr40Krx0DjPwpZCuxxTcFMrXMQVOqM1QB9wqWgaXPg3TbmlHqA=="],
"discord.js": ["discord.js@14.20.0", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.1", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-5fRTptK2vpuz+bTuAEUQLSo/3AgCSLHl6Mm9+/ofb+8cbbnjWllhtaqRBq7XcpzlBnfNEugKv8HvCwcOtIHpCg=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
@ -229,7 +252,7 @@
"express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
"express-rate-limit": ["express-rate-limit@7.5.0", "", { "peerDependencies": { "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
@ -253,7 +276,7 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"hono": ["hono@4.7.11", "", {}, "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ=="],
"hono": ["hono@4.8.2", "", {}, "sha512-hM+1RIn9PK1I6SiTNS6/y7O1mvg88awYLFEuEtoiMtRyT3SD2iu9pSFgbBXT3b1Ua4IwzvSTLvwO0SEhDxCi4w=="],
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
@ -289,6 +312,8 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@ -299,7 +324,7 @@
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"openai": ["openai@5.3.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-VIKmoF7y4oJCDOwP/oHXGzM69+x0dpGFmN9QmYO+uPbLFOmmnwO+x1GbsgUtI+6oraxomGZ566Y421oYVu191w=="],
"openai": ["openai@5.6.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-jNH5z+hYAdOMZXyEt0yZ7246s+UZjg2AwFQqkAhZIPPjxNtHHO5mykOefau6FkOqj16aC94MOdJl/rZBcKj/cQ=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
@ -349,8 +374,6 @@
"style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="],
"text-do": ["text-do@workspace:packages/text-do"],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
@ -361,7 +384,7 @@
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici": ["undici@6.21.1", "", {}, "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ=="],
"undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
@ -379,7 +402,7 @@
"ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
"zod": ["zod@3.25.64", "", {}, "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g=="],
"zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
@ -387,16 +410,6 @@
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
"@workshop/spike/@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
"http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"text-do/@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
"text-do/hono": ["hono@4.8.2", "", {}, "sha512-hM+1RIn9PK1I6SiTNS6/y7O1mvg88awYLFEuEtoiMtRyT3SD2iu9pSFgbBXT3b1Ua4IwzvSTLvwO0SEhDxCi4w=="],
"@workshop/spike/@types/bun/bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
"text-do/@types/bun/bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
}
}

View File

@ -1,9 +1,14 @@
{
"name": "workshop",
"private": true,
"workspaces": [
"packages/*"
],
"workspaces": {
"packages": [
"packages/*"
],
"catalog": {
"hono": "^4.8.0"
}
},
"prettier": {
"printWidth": 110,
"semi": false

View File

@ -12,9 +12,10 @@
"semi": false
},
"dependencies": {
"hono": "^4.7.11",
"hono": "catalog:",
"@workshop/nano-remix": "workspace:*",
"@workshop/shared": "workspace:*"
"@workshop/shared": "workspace:*",
"@workshop/todo": "workspace:*"
},
"devDependencies": {
"@types/bun": "latest"

View File

@ -1,115 +0,0 @@
import KV from "@workshop/shared/kv"
import { Form, type Head, type LoaderProps, useAction } from "@workshop/nano-remix"
import { addReminder, deleteReminder, type Reminder } from "@workshop/shared/reminders"
import { CreateReminder } from "@/components/createReminder"
export const head: Head = {
title: "Todos",
scripts: [{ src: "https://cdn.tailwindcss.com" }],
}
export const loader = async (req: Request) => {
const todos = await KV.get("todos", {})
return { todos }
}
export const action = async (req: Request) => {
const formData = await req.formData()
const action = formData.get("_action") as string
// Handle delete action
if (action === "delete") {
const id = formData.get("id") as string
if (!id) {
return { error: "Reminder ID is required" }
}
try {
await deleteReminder(id)
return { success: true, message: "Reminder deleted successfully" }
} catch (error) {
return { error: `Failed to delete reminder: ${error}` }
}
} else if (action === "create") {
// Handle create action (default)
const title = formData.get("title") as string
const dueDate = formData.get("dueDate") as string
const assignee = (formData.get("assignee") as string) || undefined
if (!title) {
return { error: "Title is required" }
}
if (!dueDate) {
return { error: "Due date is required" }
}
try {
const newReminder = await addReminder(title, dueDate, assignee as any)
return { success: true, newReminder }
} catch (error) {
return { error: `Failed to create reminder: ${error}` }
}
}
}
export default (props: LoaderProps<typeof loader>) => {
const { data, loading, error } = useAction()
return (
<div class="bg-blue-50 min-h-screen pb-8">
<h1 class="p-4 text-2xl bg-blue-100 text-blue-800">Reminders</h1>
<main class="p-4">
<div>
<h2 class="text-xl font-medium text-blue-800 mb-4">Current Reminders</h2>
<Reminders reminders={props.reminders} />
</div>
<div class="mt-8">
<CreateReminder loading={loading} success={data?.success} error={error ?? data?.error} />
</div>
</main>
</div>
)
}
const Reminders = ({ reminders }: { reminders: Reminder[] }) => {
return (
<>
{reminders.length === 0 ? (
<p class="text-gray-600">No reminders found.</p>
) : (
<ul class="bg-white rounded-lg shadow overflow-hidden divide-y divide-gray-200">
{reminders.map((reminder) => (
<li key={reminder.id} class="p-4 hover:bg-gray-50">
<div class="flex justify-between items-start">
<div>
<div class="font-medium">{reminder.title}</div>
<div class="text-sm text-gray-500 flex justify-between mt-1 items-center gap-2">
<span>Due: {new Date(reminder.dueDate).toLocaleString()}</span>
{reminder.assignee && (
<span class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded ml-2">
{reminder.assignee}
</span>
)}
<div class="text-sm text-gray-500 flex p-2">{reminder.status}</div>
</div>
</div>
<Form method="POST" class="inline-block">
<input type="hidden" name="_action" value="delete" />
<input type="hidden" name="id" value={reminder.id} />
<button
type="submit"
class="text-red-600 hover:text-red-800 text-sm font-medium"
title="Delete Reminder"
>
Delete
</button>
</Form>
</div>
</li>
))}
</ul>
)}
</>
)
}

View File

@ -0,0 +1,47 @@
import KV from "@workshop/shared/kv"
import { type Head, type LoaderProps } from "@workshop/nano-remix"
import { TodoEditor } from "@workshop/todo"
export const head: Head = {
title: "Todos",
scripts: [{ src: "https://cdn.tailwindcss.com" }],
}
export const loader = async (req: Request, params: { id: string }) => {
const todos = await KV.get("todos", {})
return { todos: todos[params.id], todoName: params.id }
}
export const action = async (req: Request, params: { id: string }) => {
console.log(`🌭 ACTION ${params}`)
const formData = await req.formData()
const todosString = formData.get("todos") as string
await KV.update("todos", {}, (todos) => {
todos[params.id] = todosString
return todos
})
return { success: true }
}
export default ({ todos, todoName }: LoaderProps<typeof loader>) => {
return (
<div class="bg-blue-50 min-h-screen pb-8">
<h1 class="p-4 text-2xl bg-blue-100 text-blue-800">Hey</h1>
<main class="p-4"></main>
<TodoEditor
todos={todos}
onChange={async (newTodos) => {
const formData = new FormData()
console.log(`🌭`, todoName)
formData.append("todos", newTodos)
const response = await fetch(`/todos/${todoName}`, {
method: "POST",
body: formData,
headers: { Accept: "application/json" },
})
}}
/>
</div>
)
}

View File

@ -12,10 +12,7 @@ function startServer(opts: StartOptions) {
"/*": (req) => nanoRemix(req, opts),
},
development: process.env.NODE_ENV !== "production" && {
hmr: true,
console: true,
},
development: process.env.NODE_ENV !== "production" && { hmr: true, console: true },
})
console.log(`🤖 Server running at ${server.url}`)
@ -25,4 +22,4 @@ if (import.meta.main) {
startServer({ routesDir: join(import.meta.dir, "routes") })
}
export { startServer }
export { startServer }

View File

@ -11,7 +11,7 @@
"typescript": "^5"
},
"dependencies": {
"hono": "^4.7.11"
"hono": "catalog:"
},
"devDependencies": {
"@types/bun": "latest"

View File

@ -4,9 +4,16 @@ import { Form, useAction, wrapComponentWithLoader } from "@/clientHelpers"
export { Form, useAction, wrapComponentWithLoader }
export { nanoRemix }
export type Loader<Data extends object> = (req: Request) => Promise<Data> | Data
export type Loader<Data extends object> = (
req: Request,
params: Record<string, string>
) => Promise<Data> | Data
export type LoaderProps<T> = T extends Loader<infer R> ? Awaited<R> : never
export type Action<Data = unknown> = (req: Request) => Promise<Response | Data>
export type Action<Data = unknown> = (
req: Request,
params: Record<string, string>
) => Promise<Response | Data>
export type Head = {
title?: string

View File

@ -21,6 +21,8 @@ export const nanoRemix = async (req: Request, options: Options = {}) => {
const basename = ext ? url.pathname.slice(0, -ext.length) : url.pathname
const route = router.match(basename)
console.log(`🌭 requesting ${url} got ${route?.pathname}`)
if (!route) {
return new Response("Route Not Found", {
status: 404,
@ -29,7 +31,6 @@ export const nanoRemix = async (req: Request, options: Options = {}) => {
}
const routeName = route.name === "/" ? "/index" : route.name
if (!ext) {
await buildDynamicRoute(distDir, routeName, route.filePath) // Eventually this should be running only on initial build and when a route changes
return await renderServer(req, route)
@ -41,7 +42,6 @@ export const nanoRemix = async (req: Request, options: Options = {}) => {
headers: { "Content-Type": "text/plain" },
})
}
return new Response(file)
}
}

View File

@ -22,9 +22,9 @@ const handleAction = async (req: Request, route: Bun.MatchedRoute) => {
)
}
const actionData = await action(req)
const actionData = await action(req, route.params)
if (actionData instanceof Response) return actionData // This should only happen if the action wants to redirect
const loaderData = await loader?.(req)
const loaderData = await loader?.(req, route.params)
const result = { actionData, loaderData }
return new Response(JSON.stringify(result), {
@ -35,8 +35,8 @@ const handleAction = async (req: Request, route: Bun.MatchedRoute) => {
const renderHtml = async (req: Request, route: Bun.MatchedRoute) => {
const component = await import(route.filePath)
const loader = component.loader
const loaderData = loader ? await loader(req) : {}
const loader = component.loader as Loader<any>
const loaderData = loader ? await loader(req, route.params) : {}
const routeName = route.name === "/" ? "/index" : route.name

View File

@ -1,6 +1,7 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
import { dirname, join } from "node:path"
import type { Reminder } from "@/reminders"
import type { Todo } from "@workshop/todo"
export type Conversation = { message: string; role: "user" | "assistant" }
export interface Keys {

View File

View File

@ -1 +0,0 @@
console.log("Hello via Bun!");

View File

@ -1,7 +1,8 @@
{
"name": "todo",
"module": "index.ts",
"name": "@workshop/todo",
"module": "src/main.ts",
"type": "module",
"types": "src/main.ts",
"private": true,
"dependencies": {
"@codemirror/commands": "^6.8.1",
@ -11,8 +12,8 @@
"@lezer/generator": "^1.7.3",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.2",
"hono": "^4.8.0",
"luxon": "^3.6.1",
"hono": "catalog:",
"luxon": "^3.6.1"
},
"prettier": {
"semi": false,
@ -22,6 +23,7 @@
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
"typescript": "^5",
"hono": "^4"
}
}

View File

@ -0,0 +1,32 @@
/* Todo styling */
.todo-completed {
text-decoration: line-through;
color: #6B7280;
}
.todo-tag {
color: #3B82F6;
}
.todo-completed .todo-tag, .todo-completed .todo-date {
color: #6B7280;
}
.todo-date {
color: #10B981;
}
.todo-invalid {
text-decoration-line: underline;
text-decoration-color: #EF4444;
text-decoration-style: wavy;
}
.todo-header {
font-size: 1.25rem;
font-weight: 700;
}
.todo-filtered {
background-color: greenyellow;
}

View File

@ -1,3 +1,3 @@
export {TodoEditor } from "@/todoEditor"
export {parseTodoLine, parseTodoList} from "@/todo"
export type {Todo} from "@/todo"
export { TodoEditor } from "@/todoEditor"
export { parseTodoLine, parseTodoList } from "@/todo"
export type { Todo } from "@/todo"

View File

@ -1,7 +1,7 @@
import { parseTodoLine, Todo } from "@/todo/todo"
import { parseTodoLine, type Todo } from "@/todo"
import { RangeSetBuilder, StateEffect } from "@codemirror/state"
import { EditorView, Decoration, ViewPlugin, ViewUpdate } from "@codemirror/view"
import { RefObject } from "hono/jsx"
import { type RefObject } from "hono/jsx"
// Effect to trigger a decoration refresh on filter change
export const refreshFilterEffect = StateEffect.define<void>()

View File

@ -1,17 +1,22 @@
import { useCallback, useEffect, useRef, useState } from "hono/jsx"
import { useCallback, useEffect, useRef, useState, type KeyboardEvent } from "hono/jsx"
import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"
import { EditorState } from "@codemirror/state"
import { EditorView, lineNumbers, keymap } from "@codemirror/view"
import { keymap as viewKeymap } from "@codemirror/view" // ensure keymap is imported from view if not already
import { foldGutter, foldKeymap } from "@codemirror/language"
import { refreshFilterEffect, todoDecorations } from "@/todo/todoDecorations"
import { autoTodoOnNewline } from "@/todo/autoTodoOnNewline"
import { todoKeymap } from "@/todo/todoKeymap"
import { refreshFilterEffect, todoDecorations } from "@/todoDecorations"
import { autoTodoOnNewline } from "@/autoTodoOnNewline"
import { todoKeymap } from "@/todoKeymap"
import { DateTime } from "luxon"
import "./index.css"
export const TodoEditor = () => {
type TodoEditorProps = {
todos?: string
onChange: (todos: string) => void
}
export const TodoEditor = ({ todos, onChange }: TodoEditorProps) => {
const editorContainer = useRef<HTMLDivElement>(null)
const editorRef = useRef<EditorView>(null)
const [filter, setFilter] = useState<string>("")
@ -28,13 +33,14 @@ export const TodoEditor = () => {
if (!editorContainer.current) return
const changeListener = EditorView.updateListener.of((update) => {
localStorage.setItem("todo-editor-content", update.state.doc.toString())
if (update.docChanged) {
onChange(update.state.doc.toString())
}
})
const savedDoc = localStorage.getItem("todo-editor-content")
const docText = savedDoc ?? defaultDoc
// Use todos prop for content
const state = EditorState.create({
doc: docText,
doc: todos || defaultDoc,
extensions: [
foldGutter(),
lineNumbers(),
@ -54,7 +60,7 @@ export const TodoEditor = () => {
editorRef.current = view
return () => view.destroy()
}, [])
}, [todos, onChange])
const filterInput = useCallback((e: KeyboardEvent) => {
if (e.key === "Enter" || e.key === "Escape") {
@ -67,7 +73,7 @@ export const TodoEditor = () => {
}, [])
return (
<div class="h-dvh w-dvw flex flex-col">
<div class="h-full w-full flex flex-col ">
<input
type="text"
placeholder="Filter by tag"
@ -82,18 +88,16 @@ export const TodoEditor = () => {
)
}
export default App
const defaultDoc = `
# Today (Group tasks by when they are due)
- [ ] Sample task with a due date @${DateTime.local().toFormat("yyyy/MM/dd")}
- [ ] Sample task with a due date @${DateTime.local().toFormat("MM/dd/yyyy")}
- [ ] You can use a #tag to filter tasks
- [ ] A sub task! Create nested tasks by indenting with <tab>
- [x] Complete a task by pressing <opt+k>
# This week
- [ ] Another task with a due date @${DateTime.local().plus({ days: 3 }).toFormat("yyyy/MM/dd")}
- [ ] Another task with a due date @${DateTime.local().plus({ days: 3 }).toFormat("MM/dd/yyyy")}
# Later
- [ ] I use later as a junk drawer for tasks I don't want to forget

View File

@ -1,7 +1,7 @@
import { indentMore, indentLess } from "@codemirror/commands"
import { EditorView, keymap } from "@codemirror/view"
import { parseTodoLine, todoToText } from "./todo"
import { RefObject } from "hono/jsx"
import { type RefObject } from "hono/jsx"
export const todoKeymap = (filterElRef: RefObject<HTMLInputElement>) => {
return keymap.of([

View File

@ -1,11 +1,12 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"allowJs": true,
// Bundler mode