@nextclaw/app-runtime 0.5.0 → 0.7.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 (35) hide show
  1. package/README.md +39 -5
  2. package/dist/bundle/app-bundle.service.d.ts +7 -2
  3. package/dist/bundle/app-bundle.service.js +77 -16
  4. package/dist/bundle/app-bundle.types.d.ts +5 -1
  5. package/dist/cli/app-runtime-cli.service.d.ts +1 -0
  6. package/dist/cli/app-runtime-cli.service.js +23 -5
  7. package/dist/cli/app-runtime-options.service.d.ts +4 -0
  8. package/dist/cli/app-runtime-options.service.js +22 -4
  9. package/dist/commands/pack.controller.d.ts +1 -0
  10. package/dist/commands/pack.controller.js +4 -3
  11. package/dist/commands/publish.controller.d.ts +1 -0
  12. package/dist/commands/publish.controller.js +4 -3
  13. package/dist/commands/validate-publish.controller.d.ts +16 -0
  14. package/dist/commands/validate-publish.controller.js +33 -0
  15. package/dist/index.d.ts +7 -4
  16. package/dist/index.js +6 -3
  17. package/dist/install/app-installation.service.d.ts +4 -1
  18. package/dist/install/app-installation.service.js +21 -3
  19. package/dist/install/app-installation.types.d.ts +4 -0
  20. package/dist/package.js +1 -1
  21. package/dist/publish/app-publish-validation.service.d.ts +40 -0
  22. package/dist/publish/app-publish-validation.service.js +80 -0
  23. package/dist/publish/app-publish.service.d.ts +2 -0
  24. package/dist/publish/app-publish.service.js +9 -3
  25. package/dist/publish/app-publish.types.d.ts +4 -1
  26. package/dist/registry/app-registry.service.d.ts +1 -0
  27. package/dist/registry/app-registry.service.js +2 -1
  28. package/dist/registry/app-registry.types.d.ts +2 -0
  29. package/dist/registry/app-remote-registry-client.service.js +5 -1
  30. package/dist/registry/app-remote-registry.types.d.ts +5 -0
  31. package/dist/scaffold/app-scaffold.service.d.ts +4 -2
  32. package/dist/scaffold/app-scaffold.service.js +14 -1
  33. package/dist/scaffold/app-ts-http-lite-scaffold-template.service.d.ts +18 -0
  34. package/dist/scaffold/app-ts-http-lite-scaffold-template.service.js +267 -0
  35. package/package.json +1 -1
@@ -0,0 +1,267 @@
1
+ import { AppTsHttpScaffoldTemplateService } from "./app-ts-http-scaffold-template.service.js";
2
+ //#region src/scaffold/app-ts-http-lite-scaffold-template.service.ts
3
+ var AppTsHttpLiteScaffoldTemplateService = class {
4
+ constructor(standardTemplateService = new AppTsHttpScaffoldTemplateService()) {
5
+ this.standardTemplateService = standardTemplateService;
6
+ }
7
+ buildFiles = (params) => {
8
+ return this.standardTemplateService.buildFiles(params).map((file) => {
9
+ switch (file.relativePath) {
10
+ case "marketplace.json": return {
11
+ relativePath: file.relativePath,
12
+ content: `${JSON.stringify(this.buildMarketplaceMetadata(params.appName), null, 2)}\n`
13
+ };
14
+ case "README.md": return {
15
+ relativePath: file.relativePath,
16
+ content: this.buildReadme(params.appName, params.appId)
17
+ };
18
+ case "main/package.json": return {
19
+ relativePath: file.relativePath,
20
+ content: `${JSON.stringify(this.buildMainPackageJson(params.appId), null, 2)}\n`
21
+ };
22
+ case "main/src/component.ts": return {
23
+ relativePath: file.relativePath,
24
+ content: this.buildComponentSource()
25
+ };
26
+ default: return file;
27
+ }
28
+ });
29
+ };
30
+ buildMainPackageJson = (appId) => {
31
+ return {
32
+ name: `${appId.replace(/\./g, "-")}-main`,
33
+ version: "0.1.0",
34
+ private: true,
35
+ type: "module",
36
+ scripts: {
37
+ build: "npm run prepare-wit && npm run guest-types && npm run typecheck && npm run bundle && npm run componentize",
38
+ "prepare-wit": "wkg wit fetch",
39
+ "guest-types": "jco guest-types wit --world-name napp-http --out-dir generated/types",
40
+ typecheck: "tsc --noEmit",
41
+ bundle: "rolldown -c",
42
+ componentize: "jco componentize -w wit -n napp-http -o app.wasm dist/component.js"
43
+ },
44
+ dependencies: { "@bytecodealliance/jco-std": "^0.1.3" },
45
+ devDependencies: {
46
+ "@bytecodealliance/componentize-js": "^0.20.0",
47
+ "@bytecodealliance/jco": "^1.19.0",
48
+ rolldown: "^1.0.0-rc.17",
49
+ typescript: "^5.6.3"
50
+ }
51
+ };
52
+ };
53
+ buildMarketplaceMetadata = (appName) => {
54
+ return {
55
+ slug: this.normalizeSlug(appName),
56
+ summary: `A lightweight TypeScript WASI HTTP ${appName} scaffold created by napp.`,
57
+ summaryI18n: {
58
+ en: `A lightweight TypeScript WASI HTTP ${appName} scaffold created by napp.`,
59
+ zh: `一个由 napp 创建的轻量 TypeScript WASI HTTP ${appName} 应用骨架。`
60
+ },
61
+ description: `A lightweight TypeScript NApp scaffold for ${appName}, designed to keep the WASI HTTP backend smaller than the default Hono-based template.`,
62
+ descriptionI18n: {
63
+ en: `A lightweight TypeScript NApp scaffold for ${appName}, designed to keep the WASI HTTP backend smaller than the default Hono-based template.`,
64
+ zh: `一个面向 ${appName} 的轻量 TypeScript NApp 应用骨架,用更薄的 WASI HTTP handler 替代默认的 Hono 模板,优先追求更小包体。`
65
+ },
66
+ author: "NextClaw",
67
+ tags: [
68
+ "starter",
69
+ "typescript",
70
+ "wasi-http",
71
+ "official",
72
+ "lite"
73
+ ],
74
+ sourceRepo: "https://github.com/Peiiii/nextclaw",
75
+ homepage: "https://nextclaw.io",
76
+ featured: false
77
+ };
78
+ };
79
+ buildReadme = (appName, appId) => {
80
+ return `# ${appName}
81
+
82
+ This app was created by \`napp create --template ts-http-lite\`.
83
+
84
+ It keeps the existing NApp directory contract, while making \`main/app.wasm\` a lighter WASI HTTP component without the default Hono routing layer.
85
+
86
+ ## App ID
87
+
88
+ \`${appId}\`
89
+
90
+ ## Build the TypeScript backend
91
+
92
+ Recommended:
93
+
94
+ \`\`\`bash
95
+ napp build . --install
96
+ \`\`\`
97
+
98
+ Manual equivalent:
99
+
100
+ \`\`\`bash
101
+ cd main
102
+ npm install
103
+ npm run build
104
+ \`\`\`
105
+
106
+ The build writes the backend component to:
107
+
108
+ \`\`\`text
109
+ main/app.wasm
110
+ \`\`\`
111
+
112
+ ## Local workflow
113
+
114
+ \`\`\`bash
115
+ napp inspect .
116
+ napp validate-publish .
117
+ napp run . --data ./data
118
+ \`\`\`
119
+
120
+ The runtime mounts the host data directory to guest \`/data\`. This example stores todos in:
121
+
122
+ \`\`\`text
123
+ ./data/todos.json
124
+ \`\`\`
125
+
126
+ The frontend still uses ordinary same-origin HTTP:
127
+
128
+ \`\`\`js
129
+ await fetch("/api/todos");
130
+ \`\`\`
131
+ `;
132
+ };
133
+ buildComponentSource = () => {
134
+ return `import { fire, incomingHandler } from "@bytecodealliance/jco-std/wasi/0.2.6/http/adapters/hono/server";
135
+ import { getDirectories } from "wasi:filesystem/preopens@0.2.2";
136
+
137
+ type Todo = {
138
+ id: string;
139
+ title: string;
140
+ completed: boolean;
141
+ };
142
+
143
+ type TodoInput = {
144
+ title?: string;
145
+ };
146
+
147
+ const TODOS_FILE = "todos.json";
148
+ const encoder = new TextEncoder();
149
+ const decoder = new TextDecoder();
150
+ const app = {
151
+ fetch: (request: Request): Promise<Response> => routeRequest(request),
152
+ };
153
+
154
+ fire(app as never);
155
+
156
+ export { incomingHandler };
157
+
158
+ async function routeRequest(request: Request): Promise<Response> {
159
+ const url = new URL(request.url);
160
+ const pathname = url.pathname;
161
+
162
+ if (request.method === "GET" && pathname === "/api/todos") {
163
+ return json(loadTodos());
164
+ }
165
+
166
+ if (request.method === "POST" && pathname === "/api/todos") {
167
+ const input = await request.json() as TodoInput;
168
+ if (!input.title?.trim()) {
169
+ return json({ error: "title is required" }, 400);
170
+ }
171
+ const todos = loadTodos();
172
+ todos.push({
173
+ id: crypto.randomUUID(),
174
+ title: input.title.trim(),
175
+ completed: false,
176
+ });
177
+ saveTodos(todos);
178
+ return json(todos);
179
+ }
180
+
181
+ if (request.method === "PATCH" && pathname.startsWith("/api/todos/")) {
182
+ const todoId = pathname.slice("/api/todos/".length);
183
+ const input = await request.json() as Partial<Todo>;
184
+ const todos = loadTodos();
185
+ const todo = todos.find((entry) => entry.id === todoId);
186
+ if (!todo) {
187
+ return json({ error: "todo not found" }, 404);
188
+ }
189
+ if (typeof input.title === "string") {
190
+ todo.title = input.title;
191
+ }
192
+ if (typeof input.completed === "boolean") {
193
+ todo.completed = input.completed;
194
+ }
195
+ saveTodos(todos);
196
+ return json(todos);
197
+ }
198
+
199
+ if (request.method === "DELETE" && pathname.startsWith("/api/todos/")) {
200
+ const todoId = pathname.slice("/api/todos/".length);
201
+ const nextTodos = loadTodos().filter((entry) => entry.id !== todoId);
202
+ saveTodos(nextTodos);
203
+ return json(nextTodos);
204
+ }
205
+
206
+ return text("Not found", 404);
207
+ }
208
+
209
+ function json(value: unknown, status = 200): Response {
210
+ return new Response(JSON.stringify(value), {
211
+ status,
212
+ headers: {
213
+ "content-type": "application/json; charset=utf-8",
214
+ },
215
+ });
216
+ }
217
+
218
+ function text(value: string, status = 200): Response {
219
+ return new Response(value, { status });
220
+ }
221
+
222
+ function loadTodos(): Todo[] {
223
+ const file = openTodosFile({ read: true });
224
+ if (!file) {
225
+ return [];
226
+ }
227
+ const [bytes] = file.read(BigInt(1024 * 1024), BigInt(0));
228
+ if (bytes.length === 0) {
229
+ return [];
230
+ }
231
+ return JSON.parse(decoder.decode(bytes)) as Todo[];
232
+ }
233
+
234
+ function saveTodos(todos: Todo[]): void {
235
+ const file = getDataDirectory().openAt(
236
+ { symlinkFollow: true },
237
+ TODOS_FILE,
238
+ { create: true, truncate: true },
239
+ { write: true },
240
+ );
241
+ file.write(encoder.encode(JSON.stringify(todos, null, 2)), BigInt(0));
242
+ }
243
+
244
+ function openTodosFile(flags: { read?: boolean; write?: boolean }) {
245
+ try {
246
+ return getDataDirectory().openAt({ symlinkFollow: true }, TODOS_FILE, {}, flags);
247
+ } catch {
248
+ return undefined;
249
+ }
250
+ }
251
+
252
+ function getDataDirectory() {
253
+ for (const [descriptor, guestPath] of getDirectories()) {
254
+ if (guestPath === "/data") {
255
+ return descriptor;
256
+ }
257
+ }
258
+ throw new Error("Missing /data preopen. NApp runtime must mount the app data directory at /data.");
259
+ }
260
+ `;
261
+ };
262
+ normalizeSlug = (value) => {
263
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
264
+ };
265
+ };
266
+ //#endregion
267
+ export { AppTsHttpLiteScaffoldTemplateService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/app-runtime",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Standalone micro app runtime and CLI for NextClaw apps.",
6
6
  "type": "module",