@soederpop/luca 0.0.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.
- package/CLAUDE.md +71 -0
- package/README.md +78 -0
- package/bun.lock +2928 -0
- package/bunfig.toml +3 -0
- package/commands/audit-docs.ts +740 -0
- package/commands/build-scaffolds.ts +154 -0
- package/commands/generate-api-docs.ts +114 -0
- package/commands/update-introspection.ts +67 -0
- package/docs/CLI.md +335 -0
- package/docs/README.md +88 -0
- package/docs/TABLE-OF-CONTENTS.md +157 -0
- package/docs/apis/clients/elevenlabs.md +84 -0
- package/docs/apis/clients/graph.md +56 -0
- package/docs/apis/clients/openai.md +69 -0
- package/docs/apis/clients/rest.md +41 -0
- package/docs/apis/clients/websocket.md +107 -0
- package/docs/apis/features/agi/assistant.md +471 -0
- package/docs/apis/features/agi/assistants-manager.md +154 -0
- package/docs/apis/features/agi/claude-code.md +602 -0
- package/docs/apis/features/agi/conversation-history.md +352 -0
- package/docs/apis/features/agi/conversation.md +333 -0
- package/docs/apis/features/agi/docs-reader.md +121 -0
- package/docs/apis/features/agi/openai-codex.md +318 -0
- package/docs/apis/features/agi/openapi.md +138 -0
- package/docs/apis/features/agi/semantic-search.md +387 -0
- package/docs/apis/features/agi/skills-library.md +216 -0
- package/docs/apis/features/node/container-link.md +133 -0
- package/docs/apis/features/node/content-db.md +313 -0
- package/docs/apis/features/node/disk-cache.md +379 -0
- package/docs/apis/features/node/dns.md +651 -0
- package/docs/apis/features/node/docker.md +705 -0
- package/docs/apis/features/node/downloader.md +81 -0
- package/docs/apis/features/node/esbuild.md +59 -0
- package/docs/apis/features/node/file-manager.md +182 -0
- package/docs/apis/features/node/fs.md +581 -0
- package/docs/apis/features/node/git.md +330 -0
- package/docs/apis/features/node/google-auth.md +174 -0
- package/docs/apis/features/node/google-calendar.md +187 -0
- package/docs/apis/features/node/google-docs.md +151 -0
- package/docs/apis/features/node/google-drive.md +225 -0
- package/docs/apis/features/node/google-sheets.md +179 -0
- package/docs/apis/features/node/grep.md +290 -0
- package/docs/apis/features/node/helpers.md +135 -0
- package/docs/apis/features/node/ink.md +334 -0
- package/docs/apis/features/node/ipc-socket.md +260 -0
- package/docs/apis/features/node/json-tree.md +86 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +145 -0
- package/docs/apis/features/node/networking.md +281 -0
- package/docs/apis/features/node/nlp.md +133 -0
- package/docs/apis/features/node/opener.md +97 -0
- package/docs/apis/features/node/os.md +118 -0
- package/docs/apis/features/node/package-finder.md +402 -0
- package/docs/apis/features/node/postgres.md +212 -0
- package/docs/apis/features/node/proc.md +430 -0
- package/docs/apis/features/node/process-manager.md +210 -0
- package/docs/apis/features/node/python.md +278 -0
- package/docs/apis/features/node/repl.md +88 -0
- package/docs/apis/features/node/runpod.md +673 -0
- package/docs/apis/features/node/secure-shell.md +169 -0
- package/docs/apis/features/node/semantic-search.md +401 -0
- package/docs/apis/features/node/sqlite.md +211 -0
- package/docs/apis/features/node/telegram.md +254 -0
- package/docs/apis/features/node/tts.md +118 -0
- package/docs/apis/features/node/ui.md +703 -0
- package/docs/apis/features/node/vault.md +64 -0
- package/docs/apis/features/node/vm.md +84 -0
- package/docs/apis/features/node/window-manager.md +337 -0
- package/docs/apis/features/node/yaml-tree.md +85 -0
- package/docs/apis/features/node/yaml.md +176 -0
- package/docs/apis/features/web/asset-loader.md +47 -0
- package/docs/apis/features/web/container-link.md +133 -0
- package/docs/apis/features/web/esbuild.md +59 -0
- package/docs/apis/features/web/helpers.md +135 -0
- package/docs/apis/features/web/network.md +30 -0
- package/docs/apis/features/web/speech.md +55 -0
- package/docs/apis/features/web/vault.md +64 -0
- package/docs/apis/features/web/vm.md +84 -0
- package/docs/apis/features/web/voice.md +67 -0
- package/docs/apis/servers/express.md +127 -0
- package/docs/apis/servers/mcp.md +213 -0
- package/docs/apis/servers/websocket.md +99 -0
- package/docs/documentation-audit.md +134 -0
- package/docs/examples/content-db.md +77 -0
- package/docs/examples/disk-cache.md +83 -0
- package/docs/examples/docker.md +101 -0
- package/docs/examples/downloader.md +70 -0
- package/docs/examples/esbuild.md +80 -0
- package/docs/examples/file-manager.md +82 -0
- package/docs/examples/fs.md +83 -0
- package/docs/examples/git.md +85 -0
- package/docs/examples/google-auth.md +88 -0
- package/docs/examples/google-calendar.md +94 -0
- package/docs/examples/google-docs.md +82 -0
- package/docs/examples/google-drive.md +96 -0
- package/docs/examples/google-sheets.md +95 -0
- package/docs/examples/grep.md +85 -0
- package/docs/examples/ink-blocks.md +75 -0
- package/docs/examples/ink-renderer.md +41 -0
- package/docs/examples/ink.md +103 -0
- package/docs/examples/ipc-socket.md +103 -0
- package/docs/examples/json-tree.md +91 -0
- package/docs/examples/launcher-app-command-listener.md +120 -0
- package/docs/examples/networking.md +58 -0
- package/docs/examples/nlp.md +91 -0
- package/docs/examples/opener.md +78 -0
- package/docs/examples/os.md +72 -0
- package/docs/examples/package-finder.md +89 -0
- package/docs/examples/port-exposer.md +89 -0
- package/docs/examples/postgres.md +91 -0
- package/docs/examples/proc.md +81 -0
- package/docs/examples/process-manager.md +79 -0
- package/docs/examples/python.md +91 -0
- package/docs/examples/repl.md +93 -0
- package/docs/examples/runpod.md +119 -0
- package/docs/examples/secure-shell.md +92 -0
- package/docs/examples/sqlite.md +86 -0
- package/docs/examples/telegram.md +77 -0
- package/docs/examples/tts.md +86 -0
- package/docs/examples/ui.md +80 -0
- package/docs/examples/vault.md +70 -0
- package/docs/examples/vm.md +86 -0
- package/docs/examples/window-manager.md +125 -0
- package/docs/examples/yaml-tree.md +93 -0
- package/docs/examples/yaml.md +104 -0
- package/docs/ideas/class-registration-refactor-possibilities.md +197 -0
- package/docs/ideas/container-use-api.md +9 -0
- package/docs/ideas/easy-auth-for-express-servers-and-luca-serve.md +0 -0
- package/docs/ideas/feature-stacks.md +22 -0
- package/docs/ideas/luca-cli-self-sufficiency-demo.md +23 -0
- package/docs/ideas/mcp-design.md +9 -0
- package/docs/ideas/web-container-debugging-feature.md +13 -0
- package/docs/introspection-audit.md +49 -0
- package/docs/introspection.md +154 -0
- package/docs/mcp/readme.md +162 -0
- package/docs/models.ts +38 -0
- package/docs/philosophy.md +85 -0
- package/docs/principles.md +7 -0
- package/docs/prompts/audit-codebase-for-failures-to-use-the-container.md +34 -0
- package/docs/prompts/mcp-test-easy-command.md +27 -0
- package/docs/reports/assistant-bugs.md +38 -0
- package/docs/reports/attach-pattern-usage.md +18 -0
- package/docs/reports/code-audit-results.md +391 -0
- package/docs/reports/introspection-audit-tasks.md +378 -0
- package/docs/reports/luca-mcp-improvements.md +128 -0
- package/docs/scaffolds/client.md +140 -0
- package/docs/scaffolds/command.md +106 -0
- package/docs/scaffolds/endpoint.md +176 -0
- package/docs/scaffolds/feature.md +148 -0
- package/docs/scaffolds/server.md +187 -0
- package/docs/tasks/web-container-helper-discovery.md +71 -0
- package/docs/todos.md +1 -0
- package/docs/tutorials/01-getting-started.md +106 -0
- package/docs/tutorials/02-container.md +210 -0
- package/docs/tutorials/03-scripts.md +194 -0
- package/docs/tutorials/04-features-overview.md +196 -0
- package/docs/tutorials/05-state-and-events.md +171 -0
- package/docs/tutorials/06-servers.md +157 -0
- package/docs/tutorials/07-endpoints.md +198 -0
- package/docs/tutorials/08-commands.md +171 -0
- package/docs/tutorials/09-clients.md +162 -0
- package/docs/tutorials/10-creating-features.md +198 -0
- package/docs/tutorials/11-contentbase.md +191 -0
- package/docs/tutorials/12-assistants.md +215 -0
- package/docs/tutorials/13-introspection.md +147 -0
- package/docs/tutorials/14-type-system.md +174 -0
- package/docs/tutorials/15-project-patterns.md +222 -0
- package/docs/tutorials/16-google-features.md +534 -0
- package/docs/tutorials/17-tui-blocks.md +530 -0
- package/docs/tutorials/18-semantic-search.md +334 -0
- package/index.ts +1 -0
- package/luca.console.ts +9 -0
- package/main.py +6 -0
- package/package.json +154 -0
- package/pyproject.toml +7 -0
- package/scripts/animations/chrome-glitch.ts +55 -0
- package/scripts/animations/index.ts +16 -0
- package/scripts/animations/neon-pulse.ts +64 -0
- package/scripts/animations/types.ts +6 -0
- package/scripts/build-web.ts +28 -0
- package/scripts/examples/ask-luca-expert.ts +42 -0
- package/scripts/examples/assistant-questions.ts +12 -0
- package/scripts/examples/excalidraw-expert.ts +75 -0
- package/scripts/examples/expert-chat.ts +0 -0
- package/scripts/examples/file-manager.ts +14 -0
- package/scripts/examples/ideas.ts +12 -0
- package/scripts/examples/interactive-chat.ts +20 -0
- package/scripts/examples/openai-tool-calls.ts +113 -0
- package/scripts/examples/opening-a-web-browser.ts +5 -0
- package/scripts/examples/telegram-bot.ts +79 -0
- package/scripts/examples/telegram-ink-ui.ts +302 -0
- package/scripts/examples/using-assistant-with-mcp.ts +560 -0
- package/scripts/examples/using-claude-code.ts +10 -0
- package/scripts/examples/using-contentdb.ts +35 -0
- package/scripts/examples/using-conversations.ts +35 -0
- package/scripts/examples/using-disk-cache.ts +10 -0
- package/scripts/examples/using-docker-shell.ts +75 -0
- package/scripts/examples/using-elevenlabs.ts +25 -0
- package/scripts/examples/using-google-calendar.ts +57 -0
- package/scripts/examples/using-google-docs.ts +74 -0
- package/scripts/examples/using-google-drive.ts +74 -0
- package/scripts/examples/using-google-sheets.ts +89 -0
- package/scripts/examples/using-nlp.ts +55 -0
- package/scripts/examples/using-ollama.ts +10 -0
- package/scripts/examples/using-openai-codex.ts +23 -0
- package/scripts/examples/using-postgres.ts +55 -0
- package/scripts/examples/using-runpod.ts +32 -0
- package/scripts/examples/using-tts.ts +40 -0
- package/scripts/examples/vm-loading-esm-modules.ts +16 -0
- package/scripts/scaffold.ts +391 -0
- package/scripts/scratch.ts +15 -0
- package/scripts/test-command-listener.ts +123 -0
- package/scripts/test-window-manager-lifecycle.ts +86 -0
- package/scripts/test-window-manager.ts +43 -0
- package/scripts/update-introspection-data.ts +58 -0
- package/src/agi/README.md +14 -0
- package/src/agi/container.server.ts +114 -0
- package/src/agi/endpoints/ask.ts +60 -0
- package/src/agi/endpoints/conversations/[id].ts +45 -0
- package/src/agi/endpoints/conversations.ts +31 -0
- package/src/agi/endpoints/experts.ts +37 -0
- package/src/agi/features/assistant.ts +767 -0
- package/src/agi/features/assistants-manager.ts +260 -0
- package/src/agi/features/claude-code.ts +1111 -0
- package/src/agi/features/conversation-history.ts +497 -0
- package/src/agi/features/conversation.ts +799 -0
- package/src/agi/features/openai-codex.ts +631 -0
- package/src/agi/features/openapi.ts +438 -0
- package/src/agi/features/skills-library.ts +425 -0
- package/src/agi/index.ts +6 -0
- package/src/agi/lib/token-counter.ts +122 -0
- package/src/browser.ts +25 -0
- package/src/bus.ts +100 -0
- package/src/cli/cli.ts +70 -0
- package/src/client.ts +461 -0
- package/src/clients/civitai/index.ts +541 -0
- package/src/clients/client-template.ts +41 -0
- package/src/clients/comfyui/index.ts +597 -0
- package/src/clients/elevenlabs/index.ts +291 -0
- package/src/clients/openai/index.ts +451 -0
- package/src/clients/supabase/index.ts +366 -0
- package/src/command.ts +164 -0
- package/src/commands/chat.ts +182 -0
- package/src/commands/console.ts +192 -0
- package/src/commands/describe.ts +433 -0
- package/src/commands/eval.ts +116 -0
- package/src/commands/help.ts +214 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/mcp.ts +64 -0
- package/src/commands/prompt.ts +807 -0
- package/src/commands/run.ts +257 -0
- package/src/commands/sandbox-mcp.ts +439 -0
- package/src/commands/scaffold.ts +79 -0
- package/src/commands/serve.ts +172 -0
- package/src/container.ts +781 -0
- package/src/endpoint.ts +340 -0
- package/src/feature.ts +75 -0
- package/src/hash-object.ts +97 -0
- package/src/helper.ts +543 -0
- package/src/introspection/generated.agi.ts +23388 -0
- package/src/introspection/generated.node.ts +18899 -0
- package/src/introspection/generated.web.ts +2021 -0
- package/src/introspection/index.ts +256 -0
- package/src/introspection/scan.ts +912 -0
- package/src/node/container.ts +354 -0
- package/src/node/feature.ts +13 -0
- package/src/node/features/container-link.ts +558 -0
- package/src/node/features/content-db.ts +475 -0
- package/src/node/features/disk-cache.ts +382 -0
- package/src/node/features/dns.ts +655 -0
- package/src/node/features/docker.ts +912 -0
- package/src/node/features/downloader.ts +92 -0
- package/src/node/features/esbuild.ts +68 -0
- package/src/node/features/file-manager.ts +357 -0
- package/src/node/features/fs.ts +534 -0
- package/src/node/features/git.ts +492 -0
- package/src/node/features/google-auth.ts +502 -0
- package/src/node/features/google-calendar.ts +300 -0
- package/src/node/features/google-docs.ts +404 -0
- package/src/node/features/google-drive.ts +339 -0
- package/src/node/features/google-sheets.ts +279 -0
- package/src/node/features/grep.ts +406 -0
- package/src/node/features/helpers.ts +374 -0
- package/src/node/features/ink.ts +490 -0
- package/src/node/features/ipc-socket.ts +459 -0
- package/src/node/features/json-tree.ts +188 -0
- package/src/node/features/launcher-app-command-listener.ts +388 -0
- package/src/node/features/networking.ts +925 -0
- package/src/node/features/nlp.ts +211 -0
- package/src/node/features/opener.ts +166 -0
- package/src/node/features/os.ts +157 -0
- package/src/node/features/package-finder.ts +539 -0
- package/src/node/features/port-exposer.ts +342 -0
- package/src/node/features/postgres.ts +273 -0
- package/src/node/features/proc.ts +502 -0
- package/src/node/features/process-manager.ts +542 -0
- package/src/node/features/python.ts +444 -0
- package/src/node/features/repl.ts +194 -0
- package/src/node/features/runpod.ts +802 -0
- package/src/node/features/secure-shell.ts +248 -0
- package/src/node/features/semantic-search.ts +924 -0
- package/src/node/features/sqlite.ts +289 -0
- package/src/node/features/telegram.ts +342 -0
- package/src/node/features/tts.ts +184 -0
- package/src/node/features/ui.ts +857 -0
- package/src/node/features/vault.ts +164 -0
- package/src/node/features/vm.ts +312 -0
- package/src/node/features/window-manager.ts +804 -0
- package/src/node/features/yaml-tree.ts +149 -0
- package/src/node/features/yaml.ts +132 -0
- package/src/node.ts +70 -0
- package/src/react/index.ts +175 -0
- package/src/registry.ts +199 -0
- package/src/scaffolds/generated.ts +1613 -0
- package/src/scaffolds/template.ts +37 -0
- package/src/schemas/base.ts +255 -0
- package/src/server.ts +135 -0
- package/src/servers/express.ts +209 -0
- package/src/servers/mcp.ts +805 -0
- package/src/servers/socket.ts +120 -0
- package/src/state.ts +101 -0
- package/src/web/clients/socket.ts +82 -0
- package/src/web/container.ts +74 -0
- package/src/web/extension.ts +30 -0
- package/src/web/feature.ts +12 -0
- package/src/web/features/asset-loader.ts +64 -0
- package/src/web/features/container-link.ts +385 -0
- package/src/web/features/esbuild.ts +79 -0
- package/src/web/features/helpers.ts +267 -0
- package/src/web/features/network.ts +61 -0
- package/src/web/features/speech.ts +87 -0
- package/src/web/features/vault.ts +189 -0
- package/src/web/features/vm.ts +78 -0
- package/src/web/features/voice-recognition.ts +129 -0
- package/src/web/shims/isomorphic-vm.ts +149 -0
- package/test/bus.test.ts +134 -0
- package/test/clients-servers.test.ts +216 -0
- package/test/container-link.test.ts +274 -0
- package/test/features.test.ts +160 -0
- package/test/integration.test.ts +787 -0
- package/test/node-container.test.ts +121 -0
- package/test/rate-limit.test.ts +272 -0
- package/test/semantic-search.test.ts +550 -0
- package/test/state.test.ts +121 -0
- package/test-integration/assistant.test.ts +138 -0
- package/test-integration/assistants-manager.test.ts +123 -0
- package/test-integration/claude-code.test.ts +98 -0
- package/test-integration/conversation-history.test.ts +205 -0
- package/test-integration/conversation.test.ts +137 -0
- package/test-integration/elevenlabs.test.ts +55 -0
- package/test-integration/google-services.test.ts +80 -0
- package/test-integration/helpers.ts +89 -0
- package/test-integration/openai-codex.test.ts +93 -0
- package/test-integration/runpod.test.ts +58 -0
- package/test-integration/server-endpoints.test.ts +97 -0
- package/test-integration/skills-library.test.ts +157 -0
- package/test-integration/telegram.test.ts +46 -0
- package/tsconfig.json +58 -0
- package/uv.lock +8 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: State and Events
|
|
3
|
+
tags: [state, events, observable, reactive, bus, emit, on, once, waitFor]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# State and Events
|
|
7
|
+
|
|
8
|
+
Every container and helper in Luca has observable state and a typed event bus. These are the core primitives for building reactive applications.
|
|
9
|
+
|
|
10
|
+
## Observable State
|
|
11
|
+
|
|
12
|
+
State is a key-value store that notifies observers when values change.
|
|
13
|
+
|
|
14
|
+
### Basic Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Every helper has state
|
|
18
|
+
const feature = container.feature('myFeature')
|
|
19
|
+
|
|
20
|
+
feature.state.set('loading', true)
|
|
21
|
+
feature.state.get('loading') // true
|
|
22
|
+
feature.state.current // Snapshot: { loading: true, ... }
|
|
23
|
+
feature.state.version // Number, increments on every change
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Observing Changes
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Watch all state changes
|
|
30
|
+
const dispose = feature.state.observe((changeType, key, value) => {
|
|
31
|
+
// changeType: 'add' | 'update' | 'delete'
|
|
32
|
+
console.log(`${key} was ${changeType}d:`, value)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Later, stop observing
|
|
36
|
+
dispose()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Container State
|
|
40
|
+
|
|
41
|
+
The container itself tracks important state:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
container.state.get('started') // boolean
|
|
45
|
+
container.state.get('enabledFeatures') // string[]
|
|
46
|
+
container.state.get('registries') // string[]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### State in Custom Features
|
|
50
|
+
|
|
51
|
+
Define your feature's state shape with a Zod schema:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const TaskStateSchema = FeatureStateSchema.extend({
|
|
55
|
+
tasks: z.array(z.object({
|
|
56
|
+
id: z.string(),
|
|
57
|
+
title: z.string(),
|
|
58
|
+
done: z.boolean(),
|
|
59
|
+
})).default([]).describe('List of tasks'),
|
|
60
|
+
filter: z.enum(['all', 'active', 'done']).default('all').describe('Current filter'),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
class TaskManager extends Feature<z.infer<typeof TaskStateSchema>> {
|
|
64
|
+
static override stateSchema = TaskStateSchema
|
|
65
|
+
|
|
66
|
+
addTask(title: string) {
|
|
67
|
+
const tasks = this.state.get('tasks') || []
|
|
68
|
+
const task = { id: crypto.randomUUID(), title, done: false }
|
|
69
|
+
this.state.set('tasks', [...tasks, task])
|
|
70
|
+
this.emit('taskAdded', task)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get activeTasks() {
|
|
74
|
+
return (this.state.get('tasks') || []).filter(t => !t.done)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Event Bus
|
|
80
|
+
|
|
81
|
+
The event bus enables decoupled communication between components.
|
|
82
|
+
|
|
83
|
+
### Emitting and Listening
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Listen for an event
|
|
87
|
+
feature.on('taskCompleted', (task) => {
|
|
88
|
+
console.log(`Task "${task.title}" is done!`)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// Emit an event
|
|
92
|
+
feature.emit('taskCompleted', { id: '1', title: 'Write docs', done: true })
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### One-Time Listeners
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
feature.once('initialized', () => {
|
|
99
|
+
console.log('Feature is ready (this runs once)')
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Waiting for Events (Promise-Based)
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Block until an event fires
|
|
107
|
+
await feature.waitFor('ready')
|
|
108
|
+
console.log('Feature is now ready')
|
|
109
|
+
|
|
110
|
+
// Useful for initialization sequences
|
|
111
|
+
const server = container.server('express', { port: 3000 })
|
|
112
|
+
await server.start()
|
|
113
|
+
console.log('Server is accepting connections on port', server.state.get('port'))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Container Events
|
|
117
|
+
|
|
118
|
+
The container emits events for lifecycle moments:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
container.on('featureEnabled', (featureId, feature) => {
|
|
122
|
+
console.log(`Feature ${featureId} was enabled`)
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Patterns
|
|
127
|
+
|
|
128
|
+
### Coordinating Between Features
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const auth = container.feature('auth')
|
|
132
|
+
const analytics = container.feature('analytics')
|
|
133
|
+
|
|
134
|
+
// Analytics reacts to auth events
|
|
135
|
+
auth.on('userLoggedIn', (user) => {
|
|
136
|
+
analytics.logEvent('login', { userId: user.id })
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
auth.on('userLoggedOut', (user) => {
|
|
140
|
+
analytics.logEvent('logout', { userId: user.id })
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### State-Driven UI Updates
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const cart = container.feature('cart')
|
|
148
|
+
|
|
149
|
+
cart.state.observe((type, key, value) => {
|
|
150
|
+
if (key === 'items') {
|
|
151
|
+
renderCartBadge(value.length)
|
|
152
|
+
}
|
|
153
|
+
if (key === 'total') {
|
|
154
|
+
renderCartTotal(value)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Initialization Gates
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Wait for multiple features to be ready
|
|
163
|
+
await Promise.all([
|
|
164
|
+
container.feature('db').waitFor('connected'),
|
|
165
|
+
container.feature('cache').waitFor('ready'),
|
|
166
|
+
container.feature('auth').waitFor('initialized'),
|
|
167
|
+
])
|
|
168
|
+
|
|
169
|
+
console.log('All systems ready, starting server...')
|
|
170
|
+
await container.server('express', { port: 3000 }).start()
|
|
171
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Servers
|
|
3
|
+
tags: [servers, express, websocket, start, stop, middleware, static]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Servers
|
|
7
|
+
|
|
8
|
+
Servers are helpers that listen for connections. Luca provides Express and WebSocket servers out of the box.
|
|
9
|
+
|
|
10
|
+
## Express Server
|
|
11
|
+
|
|
12
|
+
### Basic Setup
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const server = container.server('express', {
|
|
16
|
+
port: 3000,
|
|
17
|
+
cors: true,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
await server.start()
|
|
21
|
+
console.log('Listening on http://localhost:3000')
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### With Endpoints
|
|
25
|
+
|
|
26
|
+
The most common pattern is file-based endpoints:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const server = container.server('express', { port: 3000, cors: true })
|
|
30
|
+
|
|
31
|
+
// Auto-discover and mount endpoint files
|
|
32
|
+
await server.useEndpoints('./endpoints')
|
|
33
|
+
|
|
34
|
+
// Generate OpenAPI spec
|
|
35
|
+
server.serveOpenAPISpec({
|
|
36
|
+
title: 'My API',
|
|
37
|
+
version: '1.0.0',
|
|
38
|
+
description: 'An awesome API built with Luca',
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await server.start()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Static Files
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const server = container.server('express', {
|
|
48
|
+
port: 3000,
|
|
49
|
+
static: './public', // Serve files from public/ directory
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Port Auto-Discovery
|
|
54
|
+
|
|
55
|
+
If the requested port is taken, `configure()` can find an open one:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const server = container.server('express', { port: 3000 })
|
|
59
|
+
await server.configure() // Finds port 3000 or next available
|
|
60
|
+
await server.start()
|
|
61
|
+
console.log(`Listening on port ${server.state.get('port')}`)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Server State
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// After starting, check server state
|
|
68
|
+
await server.start()
|
|
69
|
+
server.state.get('listening') // true
|
|
70
|
+
server.state.get('port') // 3000
|
|
71
|
+
|
|
72
|
+
// Watch for state changes
|
|
73
|
+
server.state.observe((type, key, value) => {
|
|
74
|
+
if (key === 'listening' && value) {
|
|
75
|
+
console.log('Server is now listening')
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Accessing the Express App
|
|
81
|
+
|
|
82
|
+
For custom middleware or routes beyond file-based endpoints:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const server = container.server('express', { port: 3000 })
|
|
86
|
+
|
|
87
|
+
// Access the underlying express app
|
|
88
|
+
const app = server.app
|
|
89
|
+
|
|
90
|
+
app.use((req, res, next) => {
|
|
91
|
+
console.log(`${req.method} ${req.url}`)
|
|
92
|
+
next()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
app.get('/custom', (req, res) => {
|
|
96
|
+
res.json({ message: 'Custom route' })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
await server.start()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## WebSocket Server
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const ws = container.server('websocket', { port: 8080 })
|
|
106
|
+
|
|
107
|
+
ws.on('connection', (socket) => {
|
|
108
|
+
console.log('Client connected')
|
|
109
|
+
|
|
110
|
+
socket.on('message', (data) => {
|
|
111
|
+
console.log('Received:', data)
|
|
112
|
+
socket.send(JSON.stringify({ echo: data }))
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
await ws.start()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Combining Servers
|
|
120
|
+
|
|
121
|
+
Run HTTP and WebSocket together:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const http = container.server('express', { port: 3000 })
|
|
125
|
+
const ws = container.server('websocket', { port: 8080 })
|
|
126
|
+
|
|
127
|
+
await http.useEndpoints('./endpoints')
|
|
128
|
+
|
|
129
|
+
await Promise.all([
|
|
130
|
+
http.start(),
|
|
131
|
+
ws.start(),
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
console.log('HTTP on :3000, WebSocket on :8080')
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Discovering Servers
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
container.servers.available // ['express', 'websocket']
|
|
141
|
+
container.servers.describe('express')
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## The `luca serve` Command
|
|
145
|
+
|
|
146
|
+
For most projects, you don't need to set up the server manually. The built-in `luca serve` command does it for you:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
luca serve --port 3000
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
It automatically:
|
|
153
|
+
- Finds your `endpoints/` directory
|
|
154
|
+
- Mounts all endpoint files
|
|
155
|
+
- Serves `public/` as static files
|
|
156
|
+
- Generates the OpenAPI spec
|
|
157
|
+
- Prints all routes
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Writing Endpoints
|
|
3
|
+
tags: [endpoints, routes, api, express, openapi, rest, http, server]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Writing Endpoints
|
|
7
|
+
|
|
8
|
+
Endpoints are file-based HTTP routes. Each file in your `endpoints/` directory becomes an API route. Luca auto-discovers them when you run `luca serve`.
|
|
9
|
+
|
|
10
|
+
## Basic Endpoint
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
// endpoints/health.ts
|
|
14
|
+
export const path = '/health'
|
|
15
|
+
export const description = 'Health check endpoint'
|
|
16
|
+
|
|
17
|
+
export async function get() {
|
|
18
|
+
return { status: 'ok', uptime: process.uptime() }
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
That's it. `luca serve` will mount `GET /health` and include it in the auto-generated OpenAPI spec.
|
|
23
|
+
|
|
24
|
+
## Request Validation with Zod
|
|
25
|
+
|
|
26
|
+
Define schemas for your handlers. Parameters are validated automatically:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// endpoints/users.ts
|
|
30
|
+
import { z } from 'zod'
|
|
31
|
+
import type { EndpointContext } from '@soederpop/luca'
|
|
32
|
+
|
|
33
|
+
export const path = '/api/users'
|
|
34
|
+
export const description = 'User management'
|
|
35
|
+
export const tags = ['users']
|
|
36
|
+
|
|
37
|
+
// GET /api/users?role=admin&limit=10
|
|
38
|
+
export const getSchema = z.object({
|
|
39
|
+
role: z.string().optional().describe('Filter by role'),
|
|
40
|
+
limit: z.number().default(50).describe('Max results'),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export async function get(params: z.infer<typeof getSchema>, ctx: EndpointContext) {
|
|
44
|
+
// params.role and params.limit are validated and typed
|
|
45
|
+
return { users: [], total: 0 }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// POST /api/users
|
|
49
|
+
export const postSchema = z.object({
|
|
50
|
+
name: z.string().describe('Full name'),
|
|
51
|
+
email: z.string().email().describe('Email address'),
|
|
52
|
+
role: z.enum(['user', 'admin']).default('user').describe('User role'),
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export async function post(params: z.infer<typeof postSchema>, ctx: EndpointContext) {
|
|
56
|
+
// params are validated
|
|
57
|
+
return { user: { id: '1', ...params }, message: 'User created' }
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## URL Parameters
|
|
62
|
+
|
|
63
|
+
Use `:param` in the path or bracket-based file naming:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// endpoints/users/[id].ts
|
|
67
|
+
import { z } from 'zod'
|
|
68
|
+
import type { EndpointContext } from '@soederpop/luca'
|
|
69
|
+
|
|
70
|
+
export const path = '/api/users/:id'
|
|
71
|
+
export const description = 'Get, update, or delete a specific user'
|
|
72
|
+
export const tags = ['users']
|
|
73
|
+
|
|
74
|
+
export async function get(_params: any, ctx: EndpointContext) {
|
|
75
|
+
const { id } = ctx.params // From the URL
|
|
76
|
+
return { user: { id, name: 'Example' } }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const putSchema = z.object({
|
|
80
|
+
name: z.string().optional(),
|
|
81
|
+
email: z.string().email().optional(),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
export async function put(params: z.infer<typeof putSchema>, ctx: EndpointContext) {
|
|
85
|
+
const { id } = ctx.params
|
|
86
|
+
return { user: { id, ...params }, message: 'Updated' }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Use a named const + re-export for delete (reserved word)
|
|
90
|
+
const del = async (_params: any, ctx: EndpointContext) => {
|
|
91
|
+
const { id } = ctx.params
|
|
92
|
+
return { message: `User ${id} deleted` }
|
|
93
|
+
}
|
|
94
|
+
export { del as delete }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## The EndpointContext
|
|
98
|
+
|
|
99
|
+
Every handler receives `(params, ctx)`. The context gives you access to:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
export async function post(params: any, ctx: EndpointContext) {
|
|
103
|
+
const {
|
|
104
|
+
container, // The Luca container -- access any feature from here
|
|
105
|
+
request, // Express request object
|
|
106
|
+
response, // Express response object
|
|
107
|
+
query, // Parsed query string
|
|
108
|
+
body, // Parsed request body
|
|
109
|
+
params: urlParams, // URL parameters (:id, etc.)
|
|
110
|
+
} = ctx
|
|
111
|
+
|
|
112
|
+
// Use container features
|
|
113
|
+
const data = container.fs.readJson('./data/config.json')
|
|
114
|
+
|
|
115
|
+
return { success: true }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Supported HTTP Methods
|
|
120
|
+
|
|
121
|
+
Export any of these handler functions:
|
|
122
|
+
|
|
123
|
+
- `get` -- GET requests
|
|
124
|
+
- `post` -- POST requests
|
|
125
|
+
- `put` -- PUT requests
|
|
126
|
+
- `patch` -- PATCH requests
|
|
127
|
+
- `delete` -- DELETE requests
|
|
128
|
+
|
|
129
|
+
Each can have a corresponding schema export: `getSchema`, `postSchema`, `putSchema`, `patchSchema`, `deleteSchema`.
|
|
130
|
+
|
|
131
|
+
## What Gets Exported
|
|
132
|
+
|
|
133
|
+
| Export | Required | Description |
|
|
134
|
+
|--------|----------|-------------|
|
|
135
|
+
| `path` | Yes | The route path (e.g. `/api/users`, `/api/users/:id`) |
|
|
136
|
+
| `description` | No | Human-readable description (used in OpenAPI spec) |
|
|
137
|
+
| `tags` | No | Array of tags for OpenAPI grouping |
|
|
138
|
+
| `get`, `post`, etc. | At least one | Handler functions |
|
|
139
|
+
| `getSchema`, `postSchema`, etc. | No | Zod schemas for request validation |
|
|
140
|
+
|
|
141
|
+
## Starting the Server
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Default: looks for endpoints/ or src/endpoints/, serves on port 3000
|
|
145
|
+
luca serve
|
|
146
|
+
|
|
147
|
+
# Custom port and directories
|
|
148
|
+
luca serve --port 4000 --endpointsDir src/routes --staticDir public
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The server automatically:
|
|
152
|
+
- Discovers and mounts all endpoint files
|
|
153
|
+
- Generates an OpenAPI spec at `/openapi.json`
|
|
154
|
+
- Serves static files from `public/` if it exists
|
|
155
|
+
- Enables CORS by default
|
|
156
|
+
- Prints all mounted routes to the console
|
|
157
|
+
|
|
158
|
+
## Programmatic Server Setup
|
|
159
|
+
|
|
160
|
+
You can also set up the server in a script:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import container from '@soederpop/luca'
|
|
164
|
+
|
|
165
|
+
const server = container.server('express', { port: 3000, cors: true })
|
|
166
|
+
|
|
167
|
+
await server.useEndpoints('./endpoints')
|
|
168
|
+
|
|
169
|
+
server.serveOpenAPISpec({
|
|
170
|
+
title: 'My API',
|
|
171
|
+
version: '1.0.0',
|
|
172
|
+
description: 'My awesome API',
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
await server.start()
|
|
176
|
+
console.log('Server running on http://localhost:3000')
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Streaming Responses
|
|
180
|
+
|
|
181
|
+
For endpoints that need to stream (e.g. AI responses), you can write directly to the response:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
export const path = '/api/stream'
|
|
185
|
+
|
|
186
|
+
export async function post(params: any, ctx: EndpointContext) {
|
|
187
|
+
const { response } = ctx
|
|
188
|
+
|
|
189
|
+
response.setHeader('Content-Type', 'text/event-stream')
|
|
190
|
+
response.setHeader('Cache-Control', 'no-cache')
|
|
191
|
+
|
|
192
|
+
for (const chunk of data) {
|
|
193
|
+
response.write(`data: ${JSON.stringify(chunk)}\n\n`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
response.end()
|
|
197
|
+
}
|
|
198
|
+
```
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Writing Commands
|
|
3
|
+
tags: [commands, cli, luca-cli, scripts, args]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Writing Commands
|
|
7
|
+
|
|
8
|
+
Commands are CLI actions that the `luca` command discovers and runs. Projects can define their own commands in a `commands/` directory at the project root.
|
|
9
|
+
|
|
10
|
+
## Basic Command
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
// commands/seed.ts
|
|
14
|
+
import { z } from 'zod'
|
|
15
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
16
|
+
|
|
17
|
+
export const description = 'Seed the database with sample data'
|
|
18
|
+
|
|
19
|
+
export const argsSchema = z.object({
|
|
20
|
+
count: z.number().default(10).describe('Number of records to seed'),
|
|
21
|
+
table: z.string().optional().describe('Specific table to seed'),
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export async function handler(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
25
|
+
const { container } = context
|
|
26
|
+
|
|
27
|
+
console.log(`Seeding ${options.count} records...`)
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < options.count; i++) {
|
|
30
|
+
console.log(` Created record ${i + 1}`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('Done.')
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run it:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
luca seed --count 20 --table users
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How Discovery Works
|
|
44
|
+
|
|
45
|
+
When you run `luca <command>`, the CLI:
|
|
46
|
+
|
|
47
|
+
1. Loads built-in commands (serve, run, etc.)
|
|
48
|
+
2. Looks for a `commands/` directory in your project root
|
|
49
|
+
3. Scans for `.ts` files and registers them as commands
|
|
50
|
+
4. The filename becomes the command name: `commands/seed.ts` -> `luca seed`
|
|
51
|
+
|
|
52
|
+
## Command File Structure
|
|
53
|
+
|
|
54
|
+
| Export | Required | Description |
|
|
55
|
+
|--------|----------|-------------|
|
|
56
|
+
| `handler` | Yes | Async function that runs the command |
|
|
57
|
+
| `argsSchema` | No | Zod schema defining accepted arguments |
|
|
58
|
+
| `description` | No | Help text for the command |
|
|
59
|
+
|
|
60
|
+
## Arguments and the Schema
|
|
61
|
+
|
|
62
|
+
The `argsSchema` uses Zod to define what flags your command accepts. These are parsed from the command line automatically:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
export const argsSchema = z.object({
|
|
66
|
+
// String flag: --name "John"
|
|
67
|
+
name: z.string().describe('User name'),
|
|
68
|
+
|
|
69
|
+
// Number flag: --port 3000
|
|
70
|
+
port: z.number().default(3000).describe('Port number'),
|
|
71
|
+
|
|
72
|
+
// Boolean flag: --verbose
|
|
73
|
+
verbose: z.boolean().default(false).describe('Enable verbose logging'),
|
|
74
|
+
|
|
75
|
+
// Optional flag: --output file.json
|
|
76
|
+
output: z.string().optional().describe('Output file path'),
|
|
77
|
+
|
|
78
|
+
// Enum flag: --format json
|
|
79
|
+
format: z.enum(['json', 'csv', 'table']).default('table').describe('Output format'),
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Using the Container in Commands
|
|
84
|
+
|
|
85
|
+
Commands receive a context with the full container:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
export async function handler(options: any, context: ContainerContext) {
|
|
89
|
+
const { container } = context
|
|
90
|
+
|
|
91
|
+
// File system operations
|
|
92
|
+
const config = container.fs.readJson('./config.json')
|
|
93
|
+
|
|
94
|
+
// Git info (these are getters, not methods)
|
|
95
|
+
const branch = container.git.branch
|
|
96
|
+
const sha = container.git.sha
|
|
97
|
+
|
|
98
|
+
// Terminal UI
|
|
99
|
+
container.ui.colors.green('Success!')
|
|
100
|
+
|
|
101
|
+
// Run external processes (synchronous, returns string)
|
|
102
|
+
const result = container.proc.exec('ls -la')
|
|
103
|
+
|
|
104
|
+
// Use any feature
|
|
105
|
+
const cache = container.feature('diskCache', { path: './.cache' })
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
### Database Migration Command
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// commands/migrate.ts
|
|
115
|
+
import { z } from 'zod'
|
|
116
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
117
|
+
|
|
118
|
+
export const description = 'Run database migrations'
|
|
119
|
+
|
|
120
|
+
export const argsSchema = z.object({
|
|
121
|
+
direction: z.enum(['up', 'down']).default('up').describe('Migration direction'),
|
|
122
|
+
steps: z.number().default(1).describe('Number of migrations to run'),
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
export async function handler(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
126
|
+
const { container } = context
|
|
127
|
+
const { files } = container.fs.walk('./migrations', { include: ['*.sql'] })
|
|
128
|
+
|
|
129
|
+
console.log(`Running ${options.steps} migration(s) ${options.direction}...`)
|
|
130
|
+
|
|
131
|
+
for (const file of files.slice(0, options.steps)) {
|
|
132
|
+
console.log(` Applying: ${file}`)
|
|
133
|
+
const sql = container.fs.readFile(file)
|
|
134
|
+
// ... execute sql
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('Migrations complete.')
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Deploy Command
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// commands/deploy.ts
|
|
145
|
+
import { z } from 'zod'
|
|
146
|
+
import type { ContainerContext } from '@soederpop/luca'
|
|
147
|
+
|
|
148
|
+
export const description = 'Deploy the application'
|
|
149
|
+
|
|
150
|
+
export const argsSchema = z.object({
|
|
151
|
+
env: z.enum(['staging', 'production']).describe('Target environment'),
|
|
152
|
+
dryRun: z.boolean().default(false).describe('Preview without deploying'),
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
export async function handler(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
156
|
+
const { container } = context
|
|
157
|
+
|
|
158
|
+
if (options.dryRun) {
|
|
159
|
+
console.log(`[DRY RUN] Would deploy to ${options.env}`)
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const sha = container.git.sha
|
|
164
|
+
console.log(`Deploying ${sha} to ${options.env}...`)
|
|
165
|
+
|
|
166
|
+
container.proc.exec('bun run build')
|
|
167
|
+
// ... deployment logic
|
|
168
|
+
|
|
169
|
+
console.log('Deployed successfully.')
|
|
170
|
+
}
|
|
171
|
+
```
|