@parlor/scaffold 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -2
  3. package/dist/add/addon.d.ts +103 -0
  4. package/dist/add/addon.d.ts.map +1 -0
  5. package/dist/add/addon.js +12 -0
  6. package/dist/add/addon.js.map +1 -0
  7. package/dist/add/plan.d.ts +72 -0
  8. package/dist/add/plan.d.ts.map +1 -0
  9. package/dist/add/plan.js +237 -0
  10. package/dist/add/plan.js.map +1 -0
  11. package/dist/add/run.d.ts +92 -0
  12. package/dist/add/run.d.ts.map +1 -0
  13. package/dist/add/run.js +167 -0
  14. package/dist/add/run.js.map +1 -0
  15. package/dist/add/transaction.d.ts +67 -0
  16. package/dist/add/transaction.d.ts.map +1 -0
  17. package/dist/add/transaction.js +181 -0
  18. package/dist/add/transaction.js.map +1 -0
  19. package/dist/addons/agent.d.ts +58 -0
  20. package/dist/addons/agent.d.ts.map +1 -0
  21. package/dist/addons/agent.js +50 -0
  22. package/dist/addons/agent.js.map +1 -0
  23. package/dist/addons/auth.d.ts +103 -0
  24. package/dist/addons/auth.d.ts.map +1 -0
  25. package/dist/addons/auth.js +93 -0
  26. package/dist/addons/auth.js.map +1 -0
  27. package/dist/addons/index.d.ts +159 -0
  28. package/dist/addons/index.d.ts.map +1 -0
  29. package/dist/addons/index.js +8 -0
  30. package/dist/addons/index.js.map +1 -0
  31. package/dist/catalog.d.ts +3 -0
  32. package/dist/catalog.d.ts.map +1 -0
  33. package/dist/catalog.js +23 -0
  34. package/dist/catalog.js.map +1 -0
  35. package/dist/create.d.ts +45 -0
  36. package/dist/create.d.ts.map +1 -0
  37. package/dist/create.js +162 -0
  38. package/dist/create.js.map +1 -0
  39. package/dist/docs.d.ts +12 -0
  40. package/dist/docs.d.ts.map +1 -0
  41. package/dist/docs.js +70 -0
  42. package/dist/docs.js.map +1 -0
  43. package/dist/edit/inspect.d.ts +15 -0
  44. package/dist/edit/inspect.d.ts.map +1 -0
  45. package/dist/edit/inspect.js +73 -0
  46. package/dist/edit/inspect.js.map +1 -0
  47. package/dist/edit/ops.d.ts +92 -0
  48. package/dist/edit/ops.d.ts.map +1 -0
  49. package/dist/edit/ops.js +516 -0
  50. package/dist/edit/ops.js.map +1 -0
  51. package/dist/edit/source.d.ts +24 -0
  52. package/dist/edit/source.d.ts.map +1 -0
  53. package/dist/edit/source.js +36 -0
  54. package/dist/edit/source.js.map +1 -0
  55. package/dist/edit/syntax.d.ts +39 -0
  56. package/dist/edit/syntax.d.ts.map +1 -0
  57. package/dist/edit/syntax.js +129 -0
  58. package/dist/edit/syntax.js.map +1 -0
  59. package/dist/errors.d.ts +5 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +5 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/files.d.ts +15 -0
  64. package/dist/files.d.ts.map +1 -0
  65. package/dist/files.js +65 -0
  66. package/dist/files.js.map +1 -0
  67. package/dist/fixtures.d.ts +28 -0
  68. package/dist/fixtures.d.ts.map +1 -0
  69. package/dist/fixtures.js +66 -0
  70. package/dist/fixtures.js.map +1 -0
  71. package/dist/git.d.ts +22 -0
  72. package/dist/git.d.ts.map +1 -0
  73. package/dist/git.js +56 -0
  74. package/dist/git.js.map +1 -0
  75. package/dist/identity.d.ts +9 -0
  76. package/dist/identity.d.ts.map +1 -0
  77. package/dist/identity.js +23 -0
  78. package/dist/identity.js.map +1 -0
  79. package/dist/index.d.ts +9 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +8 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/package-manager.d.ts +49 -0
  84. package/dist/package-manager.d.ts.map +1 -0
  85. package/dist/package-manager.js +118 -0
  86. package/dist/package-manager.js.map +1 -0
  87. package/dist/version.d.ts +6 -0
  88. package/dist/version.d.ts.map +1 -0
  89. package/dist/version.js +9 -0
  90. package/dist/version.js.map +1 -0
  91. package/package.json +46 -3
  92. package/templates/auth/auth.ts +19 -0
  93. package/templates/auth/schema/auth.ts +1 -0
  94. package/templates/auth/src/auth/client.ts +3 -0
  95. package/templates/auth/src/auth/sign-in-gate.tsx +17 -0
  96. package/templates/auth/src/auth/sign-out-button.tsx +17 -0
  97. package/templates/auth/src/routes/link.tsx +92 -0
  98. package/templates/auth/src/routes/sign-in.tsx +117 -0
  99. package/templates/default/_gitignore +7 -0
  100. package/templates/default/actions/index.ts +58 -0
  101. package/templates/default/actions/todos.spec.ts +38 -0
  102. package/templates/default/index.html +13 -0
  103. package/templates/default/migrations/0000_init.sql +9 -0
  104. package/templates/default/migrations/meta/0000_snapshot.json +87 -0
  105. package/templates/default/migrations/meta/_journal.json +13 -0
  106. package/templates/default/package.json +44 -0
  107. package/templates/default/parlor.config.ts +12 -0
  108. package/templates/default/schema/index.ts +1 -0
  109. package/templates/default/schema/todos.ts +13 -0
  110. package/templates/default/server.ts +13 -0
  111. package/templates/default/src/agent/agent-button.tsx +120 -0
  112. package/templates/default/src/agent/agent-sidebar.tsx +828 -0
  113. package/templates/default/src/agent/agent.css +5 -0
  114. package/templates/default/src/agent/confirmation-card.tsx +42 -0
  115. package/templates/default/src/agent/sidebar-view.ts +36 -0
  116. package/templates/default/src/agent/views.ts +9 -0
  117. package/templates/default/src/app.css +1 -0
  118. package/templates/default/src/header-actions.tsx +9 -0
  119. package/templates/default/src/main.tsx +27 -0
  120. package/templates/default/src/parlor.d.ts +9 -0
  121. package/templates/default/src/routeTree.gen.ts +59 -0
  122. package/templates/default/src/routes/__root.tsx +29 -0
  123. package/templates/default/src/routes/index.tsx +141 -0
  124. package/templates/default/tsconfig.json +8 -0
  125. package/templates/default/vitest.config.ts +5 -0
@@ -0,0 +1,58 @@
1
+ import { ActionError, defineAction } from "@parlor/core";
2
+ import { createRegistry } from "@parlor/core/registry";
3
+ import { and, asc, eq } from "drizzle-orm";
4
+ import { z } from "zod";
5
+
6
+ import { todos } from "../schema/index.js";
7
+
8
+ const mine = (id: string, scope: string) => and(eq(todos.id, id), eq(todos.scope, scope));
9
+
10
+ export const registry = createRegistry({
11
+ listTodos: defineAction({
12
+ description: "List the todos, oldest first.",
13
+ input: z.object({}),
14
+ reads: [todos],
15
+ run: (_, { db, scope }) =>
16
+ db.select().from(todos).where(eq(todos.scope, scope)).orderBy(asc(todos.createdAt)),
17
+ }),
18
+ addTodo: defineAction({
19
+ description: "Add a todo.",
20
+ input: z.object({
21
+ title: z.string().trim().min(1).describe("What needs to be done"),
22
+ }),
23
+ writes: [todos],
24
+ run: async ({ title }, { db, scope }) => {
25
+ const [todo] = await db
26
+ .insert(todos)
27
+ .values({ id: crypto.randomUUID(), scope, title })
28
+ .returning();
29
+ return todo;
30
+ },
31
+ }),
32
+ toggleTodo: defineAction({
33
+ description: "Mark a todo done, or not done again.",
34
+ input: z.object({ id: z.string() }),
35
+ writes: [todos],
36
+ run: async ({ id }, { db, scope }) => {
37
+ const [current] = await db.select().from(todos).where(mine(id, scope));
38
+ if (!current) throw new ActionError("not_found", `No todo ${id}`);
39
+ const [todo] = await db
40
+ .update(todos)
41
+ .set({ done: !current.done })
42
+ .where(mine(id, scope))
43
+ .returning();
44
+ return todo;
45
+ },
46
+ }),
47
+ deleteTodo: defineAction({
48
+ description: "Delete a todo.",
49
+ input: z.object({ id: z.string() }),
50
+ writes: [todos],
51
+ confirm: true,
52
+ run: async ({ id }, { db, scope }) => {
53
+ const [todo] = await db.delete(todos).where(mine(id, scope)).returning();
54
+ if (!todo) throw new ActionError("not_found", `No todo ${id}`);
55
+ return todo;
56
+ },
57
+ }),
58
+ });
@@ -0,0 +1,38 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ import { createActionClient } from "@parlor/client";
4
+ import type { ActionsOf } from "@parlor/core/registry";
5
+ import { createDatabase } from "@parlor/data";
6
+ import { createServer } from "@parlor/server";
7
+ import { afterEach, expect, test } from "vitest";
8
+
9
+ import { registry } from "./index.js";
10
+
11
+ const servers: { close(): Promise<void> }[] = [];
12
+
13
+ afterEach(async () => {
14
+ await Promise.all(servers.splice(0).map((server) => server.close()));
15
+ });
16
+
17
+ async function startApp() {
18
+ const database = await createDatabase({
19
+ dataDir: "memory://",
20
+ migrationsFolder: fileURLToPath(new URL("../migrations", import.meta.url)),
21
+ });
22
+ const server = createServer({ actions: registry, database });
23
+ servers.push(server);
24
+ return createActionClient<ActionsOf<typeof registry>>({
25
+ baseUrl: "http://app.test",
26
+ fetch: (input, init) => server.fetch(new Request(input, init)),
27
+ });
28
+ }
29
+
30
+ test("a todo added through its action is in the list, not done", async () => {
31
+ const app = await startApp();
32
+
33
+ await app.call("addTodo", { title: "Buy milk" });
34
+
35
+ expect(await app.call("listTodos", {})).toMatchObject([
36
+ { title: "Buy milk", done: false },
37
+ ]);
38
+ });
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="data:," />
7
+ <link rel="stylesheet" href="/src/app.css" />
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,9 @@
1
+ CREATE TABLE "todos" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "scope" text DEFAULT 'default' NOT NULL,
4
+ "title" text NOT NULL,
5
+ "done" boolean DEFAULT false NOT NULL,
6
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
7
+ );
8
+ --> statement-breakpoint
9
+ CREATE INDEX "todos_scope_created_at_idx" ON "todos" USING btree ("scope","created_at");
@@ -0,0 +1,87 @@
1
+ {
2
+ "id": "d877853a-ee64-4c03-9a26-1c6dc29be490",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.todos": {
8
+ "name": "todos",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "text",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "scope": {
18
+ "name": "scope",
19
+ "type": "text",
20
+ "primaryKey": false,
21
+ "notNull": true,
22
+ "default": "'default'"
23
+ },
24
+ "title": {
25
+ "name": "title",
26
+ "type": "text",
27
+ "primaryKey": false,
28
+ "notNull": true
29
+ },
30
+ "done": {
31
+ "name": "done",
32
+ "type": "boolean",
33
+ "primaryKey": false,
34
+ "notNull": true,
35
+ "default": false
36
+ },
37
+ "created_at": {
38
+ "name": "created_at",
39
+ "type": "timestamp with time zone",
40
+ "primaryKey": false,
41
+ "notNull": true,
42
+ "default": "now()"
43
+ }
44
+ },
45
+ "indexes": {
46
+ "todos_scope_created_at_idx": {
47
+ "name": "todos_scope_created_at_idx",
48
+ "columns": [
49
+ {
50
+ "expression": "scope",
51
+ "isExpression": false,
52
+ "asc": true,
53
+ "nulls": "last"
54
+ },
55
+ {
56
+ "expression": "created_at",
57
+ "isExpression": false,
58
+ "asc": true,
59
+ "nulls": "last"
60
+ }
61
+ ],
62
+ "isUnique": false,
63
+ "concurrently": false,
64
+ "method": "btree",
65
+ "with": {}
66
+ }
67
+ },
68
+ "foreignKeys": {},
69
+ "compositePrimaryKeys": {},
70
+ "uniqueConstraints": {},
71
+ "policies": {},
72
+ "checkConstraints": {},
73
+ "isRLSEnabled": false
74
+ }
75
+ },
76
+ "enums": {},
77
+ "schemas": {},
78
+ "sequences": {},
79
+ "roles": {},
80
+ "policies": {},
81
+ "views": {},
82
+ "_meta": {
83
+ "columns": {},
84
+ "schemas": {},
85
+ "tables": {}
86
+ }
87
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1792687346970,
9
+ "tag": "0000_init",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "parlor-template-default",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "parlor dev",
8
+ "build": "parlor build",
9
+ "typecheck": "parlor typegen && tsc --noEmit",
10
+ "test": "vitest run",
11
+ "db:generate": "parlor db generate",
12
+ "db:migrate": "parlor db migrate"
13
+ },
14
+ "dependencies": {
15
+ "@parlor/chat": "workspace:*",
16
+ "@parlor/client": "workspace:*",
17
+ "@parlor/core": "workspace:*",
18
+ "@parlor/data": "workspace:*",
19
+ "@parlor/server": "workspace:*",
20
+ "@tanstack/react-query": "catalog:",
21
+ "@tanstack/react-router": "catalog:",
22
+ "drizzle-orm": "^0.45.4",
23
+ "react": "catalog:",
24
+ "react-dom": "catalog:",
25
+ "uqr": "^0.1.3",
26
+ "zod": "catalog:"
27
+ },
28
+ "devDependencies": {
29
+ "@parlor/agent-host": "workspace:*",
30
+ "@parlor/cli": "workspace:*",
31
+ "@parlor/config": "workspace:*",
32
+ "@tailwindcss/vite": "^4.3.3",
33
+ "@types/node": "catalog:",
34
+ "@types/react": "catalog:",
35
+ "@types/react-dom": "catalog:",
36
+ "tailwindcss": "^4.3.3",
37
+ "typescript": "catalog:",
38
+ "vite": "catalog:",
39
+ "vitest": "catalog:"
40
+ },
41
+ "engines": {
42
+ "node": ">=24"
43
+ }
44
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "@parlor/config";
2
+ import tailwindcss from "@tailwindcss/vite";
3
+
4
+ export default defineConfig({
5
+ app: {
6
+ id: "com.example.parlorapp",
7
+ name: "Parlor App",
8
+ },
9
+ vite: {
10
+ plugins: [tailwindcss()],
11
+ },
12
+ });
@@ -0,0 +1 @@
1
+ export { todos } from "./todos.js";
@@ -0,0 +1,13 @@
1
+ import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
2
+
3
+ export const todos = pgTable(
4
+ "todos",
5
+ {
6
+ id: text("id").primaryKey(),
7
+ scope: text("scope").notNull().default("default"),
8
+ title: text("title").notNull(),
9
+ done: boolean("done").notNull().default(false),
10
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
11
+ },
12
+ (table) => [index("todos_scope_created_at_idx").on(table.scope, table.createdAt)],
13
+ );
@@ -0,0 +1,13 @@
1
+ import { chat } from "@parlor/chat/server";
2
+ import { createDatabase } from "@parlor/data";
3
+ import { createServer } from "@parlor/server";
4
+
5
+ import { registry } from "./actions/index.js";
6
+
7
+ const database = await createDatabase({ migrationsFolder: "migrations" });
8
+
9
+ export default createServer({
10
+ actions: registry,
11
+ database,
12
+ plugins: [chat()],
13
+ });
@@ -0,0 +1,120 @@
1
+ import { useAgentConfirmations, useAgentView } from "@parlor/client/react";
2
+ import {
3
+ lazy,
4
+ startTransition,
5
+ Suspense,
6
+ useCallback,
7
+ useEffect,
8
+ useId,
9
+ useRef,
10
+ useState,
11
+ } from "react";
12
+ import { createPortal } from "react-dom";
13
+
14
+ import "./agent.css";
15
+ import { sidebarView } from "./sidebar-view.js";
16
+ import { agentPanel } from "./views.js";
17
+
18
+ const AgentSidebar = lazy(() => import("./agent-sidebar.js"));
19
+
20
+ export function AgentButton() {
21
+ const regionId = useId();
22
+ const titleId = useId();
23
+ const button = useRef<HTMLButtonElement | null>(null);
24
+ const region = useRef<HTMLElement | null>(null);
25
+ const [open, setOpen] = useState(false);
26
+ const [everOpened, setEverOpened] = useState(false);
27
+ const pending = useAgentConfirmations().length;
28
+ const { ref } = useAgentView(agentPanel, { get: () => ({ open }) });
29
+ const attach = useCallback(
30
+ (element: HTMLElement | null) => {
31
+ region.current = element;
32
+ ref(element);
33
+ },
34
+ [ref],
35
+ );
36
+
37
+ useEffect(() => {
38
+ if (open) region.current?.querySelector("textarea")?.focus();
39
+ }, [open]);
40
+ useEffect(() => {
41
+ sidebarView.set({ open });
42
+ return () => sidebarView.set({ open: false });
43
+ }, [open]);
44
+ useEffect(() => {
45
+ if (!open) return;
46
+ const dismiss = (event: KeyboardEvent) => {
47
+ if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
48
+ const focus = document.activeElement;
49
+ const inside =
50
+ focus === document.body ||
51
+ focus === button.current ||
52
+ region.current?.contains(focus);
53
+ if (!inside) return;
54
+ event.preventDefault();
55
+ setOpen(false);
56
+ button.current?.focus();
57
+ };
58
+ document.addEventListener("keydown", dismiss);
59
+ return () => document.removeEventListener("keydown", dismiss);
60
+ }, [open]);
61
+
62
+ const toggle = () => {
63
+ startTransition(() => {
64
+ setOpen(!open);
65
+ setEverOpened(true);
66
+ });
67
+ };
68
+ const close = () => {
69
+ setOpen(false);
70
+ button.current?.focus();
71
+ };
72
+
73
+ return (
74
+ <>
75
+ <button
76
+ ref={button}
77
+ type="button"
78
+ aria-expanded={open}
79
+ aria-controls={regionId}
80
+ aria-label={pending > 0 ? `Agent, ${pending} pending` : undefined}
81
+ onClick={toggle}
82
+ className="inline-flex items-center gap-2 rounded-md border border-zinc-300 bg-white px-3 py-1.5 text-sm font-medium hover:bg-zinc-100 aria-expanded:bg-zinc-900 aria-expanded:text-white"
83
+ >
84
+ Agent
85
+ {pending > 0 ? (
86
+ <span className="rounded-full bg-amber-500 px-1.5 text-xs text-white">
87
+ {pending}
88
+ </span>
89
+ ) : null}
90
+ </button>
91
+ {createPortal(
92
+ <section
93
+ id={regionId}
94
+ ref={attach}
95
+ aria-labelledby={titleId}
96
+ data-agent-sidebar
97
+ hidden={!open}
98
+ className="fixed inset-y-0 right-0 z-50 flex w-full max-w-md flex-col border-l border-zinc-200 bg-white shadow-xl"
99
+ >
100
+ <header className="flex items-center justify-between border-b border-zinc-200 px-4 py-3">
101
+ <h2 id={titleId} className="font-semibold">
102
+ Agent
103
+ </h2>
104
+ <button
105
+ type="button"
106
+ onClick={close}
107
+ className="rounded-md px-2 py-1 text-sm text-zinc-600 hover:bg-zinc-100"
108
+ >
109
+ Close
110
+ </button>
111
+ </header>
112
+ <Suspense fallback={<p className="p-4 text-sm text-zinc-500">Loading...</p>}>
113
+ {everOpened ? <AgentSidebar /> : null}
114
+ </Suspense>
115
+ </section>,
116
+ document.body,
117
+ )}
118
+ </>
119
+ );
120
+ }