@ketvietlab/ketjs 0.1.1

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 (283) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/agent/capabilities.d.ts +62 -0
  4. package/dist/agent/capabilities.d.ts.map +1 -0
  5. package/dist/agent/capabilities.js +89 -0
  6. package/dist/agent/capabilities.js.map +1 -0
  7. package/dist/agent/permissions.d.ts +56 -0
  8. package/dist/agent/permissions.d.ts.map +1 -0
  9. package/dist/agent/permissions.js +209 -0
  10. package/dist/agent/permissions.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +698 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/codegen/dts.d.ts +3 -0
  16. package/dist/codegen/dts.d.ts.map +1 -0
  17. package/dist/codegen/dts.js +94 -0
  18. package/dist/codegen/dts.js.map +1 -0
  19. package/dist/data/adapter.d.ts +4 -0
  20. package/dist/data/adapter.d.ts.map +1 -0
  21. package/dist/data/adapter.js +18 -0
  22. package/dist/data/adapter.js.map +1 -0
  23. package/dist/data/changeset.d.ts +49 -0
  24. package/dist/data/changeset.d.ts.map +1 -0
  25. package/dist/data/changeset.js +206 -0
  26. package/dist/data/changeset.js.map +1 -0
  27. package/dist/data/expr.d.ts +59 -0
  28. package/dist/data/expr.d.ts.map +1 -0
  29. package/dist/data/expr.js +61 -0
  30. package/dist/data/expr.js.map +1 -0
  31. package/dist/data/fleet.d.ts +28 -0
  32. package/dist/data/fleet.d.ts.map +1 -0
  33. package/dist/data/fleet.js +82 -0
  34. package/dist/data/fleet.js.map +1 -0
  35. package/dist/data/list-search.d.ts +93 -0
  36. package/dist/data/list-search.d.ts.map +1 -0
  37. package/dist/data/list-search.js +268 -0
  38. package/dist/data/list-search.js.map +1 -0
  39. package/dist/data/migrate.d.ts +75 -0
  40. package/dist/data/migrate.d.ts.map +1 -0
  41. package/dist/data/migrate.js +136 -0
  42. package/dist/data/migrate.js.map +1 -0
  43. package/dist/data/pool.d.ts +20 -0
  44. package/dist/data/pool.d.ts.map +1 -0
  45. package/dist/data/pool.js +92 -0
  46. package/dist/data/pool.js.map +1 -0
  47. package/dist/data/query.d.ts +111 -0
  48. package/dist/data/query.d.ts.map +1 -0
  49. package/dist/data/query.js +268 -0
  50. package/dist/data/query.js.map +1 -0
  51. package/dist/data/sqlite.d.ts +3 -0
  52. package/dist/data/sqlite.d.ts.map +1 -0
  53. package/dist/data/sqlite.js +107 -0
  54. package/dist/data/sqlite.js.map +1 -0
  55. package/dist/data/time.d.ts +9 -0
  56. package/dist/data/time.d.ts.map +1 -0
  57. package/dist/data/time.js +107 -0
  58. package/dist/data/time.js.map +1 -0
  59. package/dist/index.d.ts +79 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +51 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/kernel/apps.d.ts +43 -0
  64. package/dist/kernel/apps.d.ts.map +1 -0
  65. package/dist/kernel/apps.js +234 -0
  66. package/dist/kernel/apps.js.map +1 -0
  67. package/dist/kernel/compose.d.ts +6 -0
  68. package/dist/kernel/compose.d.ts.map +1 -0
  69. package/dist/kernel/compose.js +976 -0
  70. package/dist/kernel/compose.js.map +1 -0
  71. package/dist/kernel/define.d.ts +9 -0
  72. package/dist/kernel/define.d.ts.map +1 -0
  73. package/dist/kernel/define.js +140 -0
  74. package/dist/kernel/define.js.map +1 -0
  75. package/dist/kernel/diff.d.ts +11 -0
  76. package/dist/kernel/diff.d.ts.map +1 -0
  77. package/dist/kernel/diff.js +106 -0
  78. package/dist/kernel/diff.js.map +1 -0
  79. package/dist/kernel/errors.d.ts +17 -0
  80. package/dist/kernel/errors.d.ts.map +1 -0
  81. package/dist/kernel/errors.js +43 -0
  82. package/dist/kernel/errors.js.map +1 -0
  83. package/dist/kernel/graph.d.ts +3 -0
  84. package/dist/kernel/graph.d.ts.map +1 -0
  85. package/dist/kernel/graph.js +56 -0
  86. package/dist/kernel/graph.js.map +1 -0
  87. package/dist/kernel/i18n.d.ts +43 -0
  88. package/dist/kernel/i18n.d.ts.map +1 -0
  89. package/dist/kernel/i18n.js +79 -0
  90. package/dist/kernel/i18n.js.map +1 -0
  91. package/dist/kernel/index.d.ts +7 -0
  92. package/dist/kernel/index.d.ts.map +1 -0
  93. package/dist/kernel/index.js +7 -0
  94. package/dist/kernel/index.js.map +1 -0
  95. package/dist/kernel/layout.d.ts +17 -0
  96. package/dist/kernel/layout.d.ts.map +1 -0
  97. package/dist/kernel/layout.js +77 -0
  98. package/dist/kernel/layout.js.map +1 -0
  99. package/dist/kernel/menu.d.ts +27 -0
  100. package/dist/kernel/menu.d.ts.map +1 -0
  101. package/dist/kernel/menu.js +65 -0
  102. package/dist/kernel/menu.js.map +1 -0
  103. package/dist/kernel/modules.d.ts +31 -0
  104. package/dist/kernel/modules.d.ts.map +1 -0
  105. package/dist/kernel/modules.js +322 -0
  106. package/dist/kernel/modules.js.map +1 -0
  107. package/dist/kernel/routes.d.ts +24 -0
  108. package/dist/kernel/routes.d.ts.map +1 -0
  109. package/dist/kernel/routes.js +98 -0
  110. package/dist/kernel/routes.js.map +1 -0
  111. package/dist/kernel/types.d.ts +8 -0
  112. package/dist/kernel/types.d.ts.map +1 -0
  113. package/dist/kernel/types.js +67 -0
  114. package/dist/kernel/types.js.map +1 -0
  115. package/dist/kernel/workspace.d.ts +62 -0
  116. package/dist/kernel/workspace.d.ts.map +1 -0
  117. package/dist/kernel/workspace.js +156 -0
  118. package/dist/kernel/workspace.js.map +1 -0
  119. package/dist/scaffold/index.d.ts +2 -0
  120. package/dist/scaffold/index.d.ts.map +1 -0
  121. package/dist/scaffold/index.js +49 -0
  122. package/dist/scaffold/index.js.map +1 -0
  123. package/dist/scaffold/templates/app.test.ts.tmpl +17 -0
  124. package/dist/scaffold/templates/biome.json.tmpl +30 -0
  125. package/dist/scaffold/templates/dev.mjs.tmpl +16 -0
  126. package/dist/scaffold/templates/gitignore.tmpl +24 -0
  127. package/dist/scaffold/templates/ket.workspace.ts.tmpl +18 -0
  128. package/dist/scaffold/templates/module.ts.tmpl +27 -0
  129. package/dist/scaffold/templates/package.json.tmpl +25 -0
  130. package/dist/scaffold/templates/tsconfig.json.tmpl +15 -0
  131. package/dist/server/boot.d.ts +173 -0
  132. package/dist/server/boot.d.ts.map +1 -0
  133. package/dist/server/boot.js +659 -0
  134. package/dist/server/boot.js.map +1 -0
  135. package/dist/server/config.d.ts +48 -0
  136. package/dist/server/config.d.ts.map +1 -0
  137. package/dist/server/config.js +82 -0
  138. package/dist/server/config.js.map +1 -0
  139. package/dist/server/ctx.d.ts +13 -0
  140. package/dist/server/ctx.d.ts.map +1 -0
  141. package/dist/server/ctx.js +496 -0
  142. package/dist/server/ctx.js.map +1 -0
  143. package/dist/server/fn.d.ts +34 -0
  144. package/dist/server/fn.d.ts.map +1 -0
  145. package/dist/server/fn.js +170 -0
  146. package/dist/server/fn.js.map +1 -0
  147. package/dist/server/http.d.ts +79 -0
  148. package/dist/server/http.d.ts.map +1 -0
  149. package/dist/server/http.js +459 -0
  150. package/dist/server/http.js.map +1 -0
  151. package/dist/server/idem.d.ts +31 -0
  152. package/dist/server/idem.d.ts.map +1 -0
  153. package/dist/server/idem.js +68 -0
  154. package/dist/server/idem.js.map +1 -0
  155. package/dist/server/jobs.d.ts +5 -0
  156. package/dist/server/jobs.d.ts.map +1 -0
  157. package/dist/server/jobs.js +24 -0
  158. package/dist/server/jobs.js.map +1 -0
  159. package/dist/server/multipart.d.ts +18 -0
  160. package/dist/server/multipart.d.ts.map +1 -0
  161. package/dist/server/multipart.js +183 -0
  162. package/dist/server/multipart.js.map +1 -0
  163. package/dist/server/project.d.ts +9 -0
  164. package/dist/server/project.d.ts.map +1 -0
  165. package/dist/server/project.js +67 -0
  166. package/dist/server/project.js.map +1 -0
  167. package/dist/server/queue.d.ts +80 -0
  168. package/dist/server/queue.d.ts.map +1 -0
  169. package/dist/server/queue.js +494 -0
  170. package/dist/server/queue.js.map +1 -0
  171. package/dist/server/respond.d.ts +91 -0
  172. package/dist/server/respond.d.ts.map +1 -0
  173. package/dist/server/respond.js +99 -0
  174. package/dist/server/respond.js.map +1 -0
  175. package/dist/server/runtime.d.ts +14 -0
  176. package/dist/server/runtime.d.ts.map +1 -0
  177. package/dist/server/runtime.js +25 -0
  178. package/dist/server/runtime.js.map +1 -0
  179. package/dist/server/session.d.ts +55 -0
  180. package/dist/server/session.d.ts.map +1 -0
  181. package/dist/server/session.js +172 -0
  182. package/dist/server/session.js.map +1 -0
  183. package/dist/server/sessionstore.d.ts +54 -0
  184. package/dist/server/sessionstore.d.ts.map +1 -0
  185. package/dist/server/sessionstore.js +232 -0
  186. package/dist/server/sessionstore.js.map +1 -0
  187. package/dist/server/storage/index.d.ts +11 -0
  188. package/dist/server/storage/index.d.ts.map +1 -0
  189. package/dist/server/storage/index.js +31 -0
  190. package/dist/server/storage/index.js.map +1 -0
  191. package/dist/server/storage/local.d.ts +5 -0
  192. package/dist/server/storage/local.d.ts.map +1 -0
  193. package/dist/server/storage/local.js +165 -0
  194. package/dist/server/storage/local.js.map +1 -0
  195. package/dist/server/storage/s3.d.ts +11 -0
  196. package/dist/server/storage/s3.d.ts.map +1 -0
  197. package/dist/server/storage/s3.js +133 -0
  198. package/dist/server/storage/s3.js.map +1 -0
  199. package/dist/server/storage/sigv4.d.ts +23 -0
  200. package/dist/server/storage/sigv4.d.ts.map +1 -0
  201. package/dist/server/storage/sigv4.js +79 -0
  202. package/dist/server/storage/sigv4.js.map +1 -0
  203. package/dist/server/storage/types.d.ts +37 -0
  204. package/dist/server/storage/types.d.ts.map +1 -0
  205. package/dist/server/storage/types.js +82 -0
  206. package/dist/server/storage/types.js.map +1 -0
  207. package/dist/server/stream.d.ts +43 -0
  208. package/dist/server/stream.d.ts.map +1 -0
  209. package/dist/server/stream.js +117 -0
  210. package/dist/server/stream.js.map +1 -0
  211. package/dist/server/streamstore.d.ts +29 -0
  212. package/dist/server/streamstore.d.ts.map +1 -0
  213. package/dist/server/streamstore.js +140 -0
  214. package/dist/server/streamstore.js.map +1 -0
  215. package/dist/server/tenants.d.ts +108 -0
  216. package/dist/server/tenants.d.ts.map +1 -0
  217. package/dist/server/tenants.js +172 -0
  218. package/dist/server/tenants.js.map +1 -0
  219. package/dist/server/transport/index.d.ts +5 -0
  220. package/dist/server/transport/index.d.ts.map +1 -0
  221. package/dist/server/transport/index.js +3 -0
  222. package/dist/server/transport/index.js.map +1 -0
  223. package/dist/server/transport/memory.d.ts +14 -0
  224. package/dist/server/transport/memory.d.ts.map +1 -0
  225. package/dist/server/transport/memory.js +36 -0
  226. package/dist/server/transport/memory.js.map +1 -0
  227. package/dist/server/transport/types.d.ts +44 -0
  228. package/dist/server/transport/types.d.ts.map +1 -0
  229. package/dist/server/transport/types.js +79 -0
  230. package/dist/server/transport/types.js.map +1 -0
  231. package/dist/server/worker.d.ts +33 -0
  232. package/dist/server/worker.d.ts.map +1 -0
  233. package/dist/server/worker.js +428 -0
  234. package/dist/server/worker.js.map +1 -0
  235. package/dist/testing.d.ts +146 -0
  236. package/dist/testing.d.ts.map +1 -0
  237. package/dist/testing.js +406 -0
  238. package/dist/testing.js.map +1 -0
  239. package/dist/theme/contracts.d.ts +4 -0
  240. package/dist/theme/contracts.d.ts.map +1 -0
  241. package/dist/theme/contracts.js +87 -0
  242. package/dist/theme/contracts.js.map +1 -0
  243. package/dist/theme/index.d.ts +8 -0
  244. package/dist/theme/index.d.ts.map +1 -0
  245. package/dist/theme/index.js +7 -0
  246. package/dist/theme/index.js.map +1 -0
  247. package/dist/theme/joints.d.ts +19 -0
  248. package/dist/theme/joints.d.ts.map +1 -0
  249. package/dist/theme/joints.js +126 -0
  250. package/dist/theme/joints.js.map +1 -0
  251. package/dist/theme/ktl/compile.d.ts +26 -0
  252. package/dist/theme/ktl/compile.d.ts.map +1 -0
  253. package/dist/theme/ktl/compile.js +226 -0
  254. package/dist/theme/ktl/compile.js.map +1 -0
  255. package/dist/theme/ktl/lexer.d.ts +20 -0
  256. package/dist/theme/ktl/lexer.d.ts.map +1 -0
  257. package/dist/theme/ktl/lexer.js +56 -0
  258. package/dist/theme/ktl/lexer.js.map +1 -0
  259. package/dist/theme/ktl/parser.d.ts +64 -0
  260. package/dist/theme/ktl/parser.d.ts.map +1 -0
  261. package/dist/theme/ktl/parser.js +220 -0
  262. package/dist/theme/ktl/parser.js.map +1 -0
  263. package/dist/theme/render.d.ts +19 -0
  264. package/dist/theme/render.d.ts.map +1 -0
  265. package/dist/theme/render.js +246 -0
  266. package/dist/theme/render.js.map +1 -0
  267. package/dist/theme/templates.d.ts +2 -0
  268. package/dist/theme/templates.d.ts.map +1 -0
  269. package/dist/theme/templates.js +40 -0
  270. package/dist/theme/templates.js.map +1 -0
  271. package/dist/theme/tokens.d.ts +4 -0
  272. package/dist/theme/tokens.d.ts.map +1 -0
  273. package/dist/theme/tokens.js +15 -0
  274. package/dist/theme/tokens.js.map +1 -0
  275. package/dist/theme/viewmodel.d.ts +6 -0
  276. package/dist/theme/viewmodel.d.ts.map +1 -0
  277. package/dist/theme/viewmodel.js +40 -0
  278. package/dist/theme/viewmodel.js.map +1 -0
  279. package/dist/types.d.ts +614 -0
  280. package/dist/types.d.ts.map +1 -0
  281. package/dist/types.js +3 -0
  282. package/dist/types.js.map +1 -0
  283. package/package.json +60 -0
package/dist/cli.js ADDED
@@ -0,0 +1,698 @@
1
+ #!/usr/bin/env node
2
+ // The CLI is deliberately thin: everything it prints is read off the manifest,
3
+ // so there is no second source of truth about what an app contains.
4
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
5
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
6
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
7
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
8
+ });
9
+ }
10
+ return path;
11
+ };
12
+ import { writeFileSync, mkdirSync, existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
13
+ import { spawn } from 'node:child_process';
14
+ import { delimiter, extname, join, normalize, resolve } from 'node:path';
15
+ import { pathToFileURL } from 'node:url';
16
+ import { composeWorkspace, explainWorkspace } from './kernel/workspace.js';
17
+ import { resolveWorkspace } from './kernel/modules.js';
18
+ import { diffManifests, formatDiff } from './kernel/diff.js';
19
+ import { generateDts } from './codegen/dts.js';
20
+ import { agentDescriptor } from './agent/capabilities.js';
21
+ import { reachOf, functionsOf, formatReach, formatInventory, grantsOfRole } from './agent/permissions.js';
22
+ import { readConfig, sqliteStore } from './server/config.js';
23
+ import { schemaFromManifest, planMigration, renderSql } from './data/migrate.js';
24
+ import { migrateOne, migrateFleet, formatFleet } from './data/fleet.js';
25
+ import { createAdapterPool } from './data/pool.js';
26
+ import { sqliteAdapter } from './data/sqlite.js';
27
+ import { bootApp, serveApp } from './server/boot.js';
28
+ import { bootRuntime } from './server/runtime.js';
29
+ import { callFn } from './server/fn.js';
30
+ import { createAppRegistry, restrictManifest } from './kernel/apps.js';
31
+ import { bootWorker, serveWorker } from './server/worker.js';
32
+ import { createQueue } from './server/queue.js';
33
+ import { scaffold } from './scaffold/index.js';
34
+ import { KetError } from './kernel/errors.js';
35
+ import { CookieJar, createTestApp, TestClient, TestHttpError } from './testing.js';
36
+ const [, , cmd = 'help', ...rest] = process.argv;
37
+ const flag = (name) => rest.includes(`--${name}`);
38
+ const opt = (name) => {
39
+ const i = rest.indexOf(`--${name}`);
40
+ if (i >= 0)
41
+ return rest[i + 1];
42
+ const inline = rest.find((item) => item.startsWith(`--${name}=`));
43
+ return inline?.slice(name.length + 3);
44
+ };
45
+ const opts = (name) => {
46
+ const out = [];
47
+ for (let i = 0; i < rest.length; i++) {
48
+ if (rest[i] === `--${name}` && rest[i + 1] !== undefined)
49
+ out.push(rest[++i]);
50
+ else if (rest[i]?.startsWith(`--${name}=`))
51
+ out.push(rest[i].slice(name.length + 3));
52
+ }
53
+ return out;
54
+ };
55
+ const positionals = (valueOptions) => {
56
+ const values = new Set(valueOptions);
57
+ const end = rest.indexOf('--');
58
+ const args = end < 0 ? rest : rest.slice(0, end);
59
+ const out = [];
60
+ for (let i = 0; i < args.length; i++) {
61
+ const item = args[i];
62
+ if (!item.startsWith('--')) {
63
+ out.push(item);
64
+ continue;
65
+ }
66
+ const name = item.slice(2).split('=')[0];
67
+ if (!item.includes('=') && values.has(name))
68
+ i++;
69
+ }
70
+ return out;
71
+ };
72
+ const CANDIDATES = [
73
+ 'dist/ket.workspace.js',
74
+ '.build/ket.workspace.js',
75
+ 'ket.workspace.js',
76
+ 'workspace.js',
77
+ 'examples/workspace.js',
78
+ ];
79
+ /** Where the apps are declared. Named explicitly, or the first conventional path. */
80
+ const workspacePath = () => {
81
+ const given = opt('workspace');
82
+ if (given) {
83
+ if (/\.[cm]?tsx?$/.test(given) && process.env.KET_DEV_SOURCE !== '1') {
84
+ throw new Error(`refusing to execute source workspace "${given}" — build it and pass the emitted .js artifact`);
85
+ }
86
+ return given;
87
+ }
88
+ const found = CANDIDATES.find((p) => existsSync(p));
89
+ if (!found)
90
+ throw new Error(`no workspace file found (looked for ${CANDIDATES.join(', ')}); pass --workspace FILE`);
91
+ return found;
92
+ };
93
+ const loadWorkspace = async () => {
94
+ const configuredPath = workspacePath();
95
+ const absolutePath = resolve(configuredPath);
96
+ const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(absolutePath).href)));
97
+ const declaration = mod.default && typeof mod.default === 'object' && Array.isArray(mod.default.apps)
98
+ ? mod.default
99
+ : { apps: mod.apps, modulePaths: mod.modulePaths };
100
+ if (!Array.isArray(declaration.apps))
101
+ throw new Error(`${configuredPath} must default-export defineWorkspace(...) or export \`apps\`, an array of defineApp(...)`);
102
+ const fromEnv = (process.env.KET_MODULE_PATH ?? '')
103
+ .split(delimiter)
104
+ .map((path) => path.trim())
105
+ .filter(Boolean);
106
+ const resolved = await resolveWorkspace(declaration, {
107
+ baseUrl: pathToFileURL(absolutePath),
108
+ extraModulePaths: [...fromEnv, ...opts('module-path')],
109
+ allowSource: process.env.KET_DEV_SOURCE === '1',
110
+ });
111
+ return { ws: composeWorkspace(resolved.apps), apps: resolved.apps, resolved };
112
+ };
113
+ const formatModules = (paths, modules) => {
114
+ const lines = ['module paths:'];
115
+ for (const path of paths)
116
+ lines.push(` ${path}`);
117
+ if (!paths.length)
118
+ lines.push(' (none)');
119
+ lines.push('modules:');
120
+ for (const module of modules) {
121
+ lines.push(` ${module.name.padEnd(24)} ${module.version.padEnd(10)} ${module.kind.padEnd(6)} apps=${module.apps.join(',')} ${module.source}`);
122
+ }
123
+ if (!modules.length)
124
+ lines.push(' (none)');
125
+ return lines.join('\n');
126
+ };
127
+ /** The AppSpec, not the composed manifest — serving needs what the app declared. */
128
+ const pickSpec = (specs) => {
129
+ const name = opt('app');
130
+ if (!name) {
131
+ const servable = specs.filter((s) => s.serve);
132
+ const one = servable[0] ?? specs[0];
133
+ if (!one)
134
+ throw new Error('the workspace declares no apps');
135
+ return one;
136
+ }
137
+ const found = specs.find((s) => s.name === name);
138
+ if (!found)
139
+ throw new Error(`unknown app "${name}" (have: ${specs.map((s) => s.name).join(', ')})`);
140
+ return found;
141
+ };
142
+ const pickApp = (ws) => {
143
+ const name = opt('app') ?? Object.keys(ws.apps)[0];
144
+ const m = ws.apps[name];
145
+ if (!m)
146
+ throw new Error(`unknown app "${name}" (have: ${Object.keys(ws.apps).join(', ')})`);
147
+ return [name, m];
148
+ };
149
+ const HELP = `ket — zero-dependency fullstack framework
150
+
151
+ ket check compose every app and report contract violations
152
+ ket manifest [--app X] print the composed manifest
153
+ ket workspace show apps, datastores and shared modules
154
+ ket modules show resolved modules and their source paths
155
+ ket types [--app X] generate .ket/types.d.ts from the manifest
156
+ ket agent [--app X] print the agent capability descriptor
157
+ ket permissions every function that exists to be granted
158
+ --grant a,b,c …or what a role granted exactly these can reach
159
+ --module NAME …or what granting one module's whole surface reaches
160
+ --role NAME …or what a role in the database actually grants
161
+ ket migrate [--app X] plan migrations (add --allow-destructive to permit data loss)
162
+ --all …or apply them to every tenant database (add --dry-run)
163
+ ket diff --against FILE compare the current manifest with a stored one
164
+ ket snapshot [--app X] write .ket/manifest.<app>.json for a later diff
165
+
166
+ ket serve [--app X] boot the app and serve it
167
+ ket worker [--app X] run declared queues for the same app and manifest
168
+ ket call FUNCTION exercise a function through its real HTTP endpoint
169
+ --against URL call an already-running development server
170
+ --input JSON|@FILE|- function input (default: {})
171
+ --company ID request company; also --companies and --branches
172
+ --tenant KEY conventional x-tenant header (or use --header)
173
+ --cookie-file FILE load and update an isolated JSON cookie jar
174
+ --login JSON|@FILE POST credentials before the call (default: /login)
175
+ --login-path PATH override the application's login route
176
+ --dry-run report declared writes without applying them
177
+ --idempotency-key KEY exercise idempotent retry handling
178
+ --isolated boot a temporary app/database for this call
179
+ ket provision FUNCTION run a declared one-shot bootstrap function
180
+ --input - read JSON, including secrets, from stdin only
181
+ --tenant KEY required when the app uses tenant databases
182
+ ket test [FILES...] run emitted headless tests with Node's test runner
183
+ --watch rerun when the selected JavaScript artifacts change
184
+ --test-name-pattern P select tests by name; --coverage enables coverage
185
+ ket jobs list inspect durable jobs (--tenant is required for tenant apps)
186
+ ket jobs retry ID make a retryable/discarded job available now
187
+ ket jobs cancel ID cancel a pending or executing job
188
+ ket jobs prune apply the 7/30-day retention policy
189
+ ket dev [--app X] serve compiled output, restarting when an artifact changes
190
+ --all run HTTP and worker together under this one watcher
191
+ (--no-auto-install holds back modules that install themselves)
192
+ ket new NAME [--dir D] scaffold an app that runs
193
+
194
+ Options: --workspace FILE (default: dist/ket.workspace.js, ket.workspace.js, workspace.js)
195
+ --module-path DIR (repeatable; KET_MODULE_PATH uses the platform path separator)
196
+ --app NAME, --port N, --header "Name: value" (repeatable)
197
+ `;
198
+ const TEST_VALUE_OPTIONS = ['test-name-pattern', 'test-concurrency', 'reporter', 'out-dir'];
199
+ const collectTests = (path) => {
200
+ if (!existsSync(path))
201
+ throw new Error(`test path does not exist: ${path}`);
202
+ if (!statSync(path).isDirectory())
203
+ return [path];
204
+ const files = [];
205
+ const visit = (dir) => {
206
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
207
+ const child = join(dir, entry.name);
208
+ if (entry.isDirectory())
209
+ visit(child);
210
+ else if (/\.(?:test|spec)\.[cm]?js$/.test(entry.name))
211
+ files.push(child);
212
+ }
213
+ };
214
+ visit(path);
215
+ return files.sort();
216
+ };
217
+ const emittedTestPath = (input) => {
218
+ const source = normalize(input).replace(/^\.\//, '');
219
+ const extension = extname(source);
220
+ if (extension !== '.ts' && extension !== '.tsx')
221
+ return source;
222
+ const emitted = source.slice(0, -extension.length) + '.js';
223
+ const candidates = [...(opt('out-dir') ? [opt('out-dir')] : []), '.build', 'dist'].map((dir) => join(dir, emitted));
224
+ const found = candidates.find((candidate) => existsSync(candidate));
225
+ if (!found)
226
+ throw new Error(`no emitted test for ${input}; build first (looked for ${candidates.join(', ')})`);
227
+ return found;
228
+ };
229
+ const runTests = async () => {
230
+ const selected = positionals(TEST_VALUE_OPTIONS).flatMap((path) => collectTests(emittedTestPath(path)));
231
+ const defaultDir = [opt('out-dir'), '.build/test', 'dist/test'].find((candidate) => Boolean(candidate && existsSync(candidate)));
232
+ const defaults = selected.length ? selected : defaultDir ? collectTests(defaultDir) : [];
233
+ const nodeArgs = ['--test'];
234
+ if (flag('watch'))
235
+ nodeArgs.push('--watch');
236
+ if (flag('coverage'))
237
+ nodeArgs.push('--experimental-test-coverage');
238
+ if (flag('test-only'))
239
+ nodeArgs.push('--test-only');
240
+ for (const [option, nodeOption] of [
241
+ ['test-name-pattern', '--test-name-pattern'],
242
+ ['test-concurrency', '--test-concurrency'],
243
+ ['reporter', '--test-reporter'],
244
+ ]) {
245
+ const value = opt(option);
246
+ if (value)
247
+ nodeArgs.push(`${nodeOption}=${value}`);
248
+ }
249
+ const separator = rest.indexOf('--');
250
+ if (separator >= 0)
251
+ nodeArgs.push(...rest.slice(separator + 1));
252
+ nodeArgs.push(...defaults);
253
+ const child = spawn(process.execPath, nodeArgs, { stdio: 'inherit', env: process.env });
254
+ const code = await new Promise((resolve, reject) => {
255
+ child.on('error', reject);
256
+ child.on('exit', (status, signal) => resolve(status ?? (signal ? 1 : 0)));
257
+ });
258
+ process.exit(code);
259
+ };
260
+ const callHeaders = () => {
261
+ const headers = new Headers();
262
+ for (const raw of opts('header')) {
263
+ const separator = raw.indexOf(':');
264
+ if (separator <= 0)
265
+ throw new Error(`invalid --header "${raw}" (expected "Name: value")`);
266
+ headers.append(raw.slice(0, separator).trim(), raw.slice(separator + 1).trim());
267
+ }
268
+ const cookie = opt('cookie');
269
+ if (cookie)
270
+ headers.set('cookie', cookie);
271
+ return headers;
272
+ };
273
+ const jsonObject = (value, label) => {
274
+ const raw = value === undefined
275
+ ? '{}'
276
+ : value === '-'
277
+ ? readFileSync(0, 'utf8')
278
+ : value.startsWith('@')
279
+ ? readFileSync(value.slice(1), 'utf8')
280
+ : value;
281
+ const parsed = JSON.parse(raw);
282
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
283
+ throw new Error(`${label} must be a JSON object`);
284
+ return parsed;
285
+ };
286
+ const callInput = () => jsonObject(opt('input'), '--input');
287
+ const callWithClient = async (baseUrl) => {
288
+ const fnKey = positionals([
289
+ 'against',
290
+ 'input',
291
+ 'company',
292
+ 'companies',
293
+ 'branches',
294
+ 'tenant',
295
+ 'tenant-header',
296
+ 'locale',
297
+ 'cookie',
298
+ 'cookie-file',
299
+ 'login',
300
+ 'login-path',
301
+ 'idempotency-key',
302
+ 'header',
303
+ 'app',
304
+ 'workspace',
305
+ 'module-path',
306
+ ])[0];
307
+ if (!fnKey)
308
+ throw new Error('usage: ket call FUNCTION [--against URL] [--input JSON|@FILE|-]');
309
+ const cookieFile = opt('cookie-file');
310
+ const jar = cookieFile ? await CookieJar.load(cookieFile) : new CookieJar();
311
+ const list = (name) => opt(name)
312
+ ?.split(',')
313
+ .map((item) => item.trim())
314
+ .filter(Boolean);
315
+ const client = new TestClient(baseUrl, {
316
+ jar,
317
+ company: opt('company'),
318
+ companies: list('companies'),
319
+ branches: list('branches'),
320
+ tenant: opt('tenant'),
321
+ tenantHeader: opt('tenant-header'),
322
+ locale: opt('locale'),
323
+ headers: callHeaders(),
324
+ });
325
+ try {
326
+ if (opt('login'))
327
+ await client.login(jsonObject(opt('login'), '--login'), opt('login-path') ?? '/login');
328
+ const result = await client.call(fnKey, callInput(), {
329
+ dryRun: flag('dry-run'),
330
+ ...(opt('idempotency-key') ? { idempotencyKey: opt('idempotency-key') } : {}),
331
+ });
332
+ if (cookieFile)
333
+ await jar.save(cookieFile);
334
+ const output = flag('value') ? result.value : result;
335
+ console.log(JSON.stringify(output, null, flag('compact') ? 0 : 2));
336
+ return 0;
337
+ }
338
+ catch (error) {
339
+ if (cookieFile)
340
+ await jar.save(cookieFile);
341
+ if (!(error instanceof TestHttpError))
342
+ throw error;
343
+ console.error(JSON.stringify(error.body, null, 2));
344
+ return 1;
345
+ }
346
+ };
347
+ try {
348
+ if (cmd === 'help' || cmd === '--help') {
349
+ console.log(HELP);
350
+ process.exit(0);
351
+ }
352
+ if (cmd === 'new') {
353
+ const name = rest.find((a) => !a.startsWith('--'));
354
+ if (!name)
355
+ throw new Error('usage: ket new NAME [--dir DIR]');
356
+ for (const line of scaffold(name, opt('dir') ?? name))
357
+ console.log(line);
358
+ process.exit(0);
359
+ }
360
+ if (cmd === 'test')
361
+ await runTests();
362
+ // Remote smoke calls need no workspace and do not boot a second app.
363
+ if (cmd === 'call' && opt('against')) {
364
+ const against = new URL(opt('against'));
365
+ if (against.protocol !== 'http:' && against.protocol !== 'https:')
366
+ throw new Error('--against must be an http(s) URL');
367
+ process.exit(await callWithClient(against.toString()));
368
+ }
369
+ if (cmd === 'dev') {
370
+ // This watches emitted JavaScript only. The project owns the compiler watcher;
371
+ // `ket new` wires both sides together without handing source files to Node.
372
+ const childCommand = flag('all') ? 'all' : 'serve';
373
+ const argv = [
374
+ '--watch',
375
+ new URL(import.meta.url).pathname,
376
+ childCommand,
377
+ ...rest.filter((item) => item !== '--all'),
378
+ ];
379
+ const child = spawn(process.execPath, argv, { stdio: 'inherit', env: { ...process.env, KET_DEV: '1' } });
380
+ child.on('exit', (code) => process.exit(code ?? 0));
381
+ for (const sig of ['SIGINT', 'SIGTERM'])
382
+ process.on(sig, () => child.kill(sig));
383
+ await new Promise(() => { });
384
+ }
385
+ const { ws, apps: specs, resolved } = await loadWorkspace();
386
+ mkdirSync('.ket', { recursive: true });
387
+ if (cmd === 'serve') {
388
+ const spec = pickSpec(specs);
389
+ if (!spec.serve)
390
+ throw new Error(`app "${spec.name}" declares no serve block, so there is nothing to run`);
391
+ const port = opt('port');
392
+ const env = flag('no-auto-install') ? { ...process.env, KET_AUTO_INSTALL: '0' } : process.env;
393
+ await serveApp(spec, { env, ...(port ? { port: Number(port) } : {}) });
394
+ }
395
+ else if (cmd === 'worker') {
396
+ const spec = pickSpec(specs);
397
+ const worker = await serveWorker(spec, { env: process.env });
398
+ console.log(`worker ${worker.workerId} is running (${Object.keys(spec.worker?.queues ?? {}).join(', ')})`);
399
+ }
400
+ else if (cmd === 'call') {
401
+ const spec = pickSpec(specs);
402
+ if (!spec.serve)
403
+ throw new Error(`app "${spec.name}" declares no serve block`);
404
+ if (flag('isolated')) {
405
+ const testApp = await createTestApp(spec, { worker: false });
406
+ try {
407
+ process.exitCode = await callWithClient(testApp.baseUrl);
408
+ }
409
+ finally {
410
+ await testApp.close();
411
+ }
412
+ }
413
+ else {
414
+ const env = {
415
+ ...process.env,
416
+ ...(flag('no-migrate') ? { KET_MIGRATE: '0' } : {}),
417
+ ...(flag('no-auto-install') ? { KET_AUTO_INSTALL: '0' } : {}),
418
+ };
419
+ const app = await bootApp(spec, {
420
+ env,
421
+ port: 0,
422
+ log: flag('verbose') ? (line) => console.error(line) : () => { },
423
+ });
424
+ try {
425
+ process.exitCode = await callWithClient(`http://127.0.0.1:${app.port}`);
426
+ }
427
+ finally {
428
+ await app.close();
429
+ }
430
+ }
431
+ }
432
+ else if (cmd === 'provision') {
433
+ const fnKey = positionals(['input', 'tenant', 'app', 'workspace', 'module-path'])[0];
434
+ if (!fnKey)
435
+ throw new Error('usage: ket provision FUNCTION --input - [--tenant KEY]');
436
+ if (opt('input') !== '-')
437
+ throw new Error('ket provision reads input only from stdin; pass --input - so secrets never enter argv');
438
+ const spec = pickSpec(specs);
439
+ if (!spec.serve)
440
+ throw new Error(`app "${spec.name}" declares no serve block`);
441
+ const runtime = await bootRuntime(spec, { env: process.env });
442
+ const meta = runtime.manifest.functions[fnKey];
443
+ if (!meta)
444
+ throw new Error(`unknown function "${fnKey}"`);
445
+ if (!meta.provision || meta.exposure !== 'internal')
446
+ throw new Error(`function "${fnKey}" is not declared internal + provision`);
447
+ const tenant = opt('tenant');
448
+ if (spec.serve.tenants && !tenant)
449
+ throw new Error(`app "${spec.name}" has tenant databases; pass --tenant NAME`);
450
+ const adapter = spec.serve.tenants
451
+ ? await spec.serve.tenants.open(tenant, runtime.config)
452
+ : await (spec.serve.openStore ?? sqliteStore)(runtime.config);
453
+ if (spec.serve.tenants)
454
+ await adapter.open();
455
+ try {
456
+ await migrateOne(adapter, runtime.manifest);
457
+ const apps = await createAppRegistry(runtime.manifest, adapter, {
458
+ autoInstall: runtime.config.autoInstall,
459
+ });
460
+ const bootstrap = runtime.config.bootstrapApps ?? spec.serve.bootstrap ?? [];
461
+ if ((await apps.enabled()).size === 0)
462
+ for (const name of bootstrap)
463
+ await apps.install(name);
464
+ const live = restrictManifest(runtime.manifest, await apps.enabled());
465
+ const result = await callFn(fnKey, jsonObject('-', '--input'), {
466
+ adapter,
467
+ manifest: live,
468
+ actor: 'system:provision',
469
+ scope: { company: null, branch: null },
470
+ });
471
+ console.log(JSON.stringify(result.value, null, 2));
472
+ if (result.value &&
473
+ typeof result.value === 'object' &&
474
+ 'ok' in result.value &&
475
+ result.value.ok === false)
476
+ process.exitCode = 1;
477
+ }
478
+ finally {
479
+ await adapter.close();
480
+ }
481
+ }
482
+ else if (cmd === 'all') {
483
+ const spec = pickSpec(specs);
484
+ if (!spec.serve)
485
+ throw new Error(`app "${spec.name}" declares no serve block`);
486
+ if (!spec.worker)
487
+ throw new Error(`app "${spec.name}" declares no worker block`);
488
+ const app = await bootApp(spec, {
489
+ env: process.env,
490
+ ...(opt('port') ? { port: Number(opt('port')) } : {}),
491
+ });
492
+ const worker = await bootWorker(spec, { env: process.env });
493
+ worker.start();
494
+ console.log(await app.banner());
495
+ console.log(` worker ${worker.workerId} is running in this development process\n`);
496
+ const close = async () => {
497
+ await worker.close();
498
+ await app.close();
499
+ process.exit(0);
500
+ };
501
+ for (const signal of ['SIGINT', 'SIGTERM'])
502
+ process.on(signal, () => void close());
503
+ await new Promise(() => { });
504
+ }
505
+ else if (cmd === 'jobs') {
506
+ const action = rest.find((item) => !item.startsWith('--'));
507
+ const positional = rest.filter((item, index) => {
508
+ if (item.startsWith('--'))
509
+ return false;
510
+ const previous = rest[index - 1];
511
+ return !previous?.startsWith('--');
512
+ });
513
+ const id = positional[1];
514
+ const spec = pickSpec(specs);
515
+ const config = readConfig(process.env, {
516
+ sqliteFile: `.ket/${spec.name}.db`,
517
+ ...spec.serve?.defaults,
518
+ });
519
+ const tenant = opt('tenant');
520
+ if (spec.serve?.tenants && !tenant)
521
+ throw new Error(`app "${spec.name}" has tenant databases; pass --tenant NAME`);
522
+ const adapter = spec.serve?.tenants
523
+ ? await spec.serve.tenants.open(tenant, config)
524
+ : await (spec.serve?.openStore ?? sqliteStore)(config);
525
+ if (spec.serve?.tenants)
526
+ await adapter.open();
527
+ try {
528
+ const queue = await createQueue(adapter);
529
+ if (action === 'list') {
530
+ const state = opt('state');
531
+ if (state &&
532
+ ![
533
+ 'available',
534
+ 'scheduled',
535
+ 'executing',
536
+ 'retryable',
537
+ 'completed',
538
+ 'discarded',
539
+ 'cancelled',
540
+ ].includes(state))
541
+ throw new Error(`unknown job state "${state}"`);
542
+ const limit = opt('limit') === undefined ? undefined : Number(opt('limit'));
543
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 1))
544
+ throw new Error('--limit must be a positive integer');
545
+ const rows = await queue.list({
546
+ ...(state ? { state } : {}),
547
+ ...(opt('queue') ? { queue: opt('queue') } : {}),
548
+ ...(limit === undefined ? {} : { limit }),
549
+ });
550
+ console.log(JSON.stringify(rows, null, 2));
551
+ }
552
+ else if (action === 'retry') {
553
+ if (!id)
554
+ throw new Error('usage: ket jobs retry ID [--tenant NAME]');
555
+ if (!(await queue.retryNow(id)))
556
+ throw new Error(`job "${id}" is not retryable or discarded`);
557
+ console.log(`job ${id} is available`);
558
+ }
559
+ else if (action === 'cancel') {
560
+ if (!id)
561
+ throw new Error('usage: ket jobs cancel ID [--tenant NAME]');
562
+ if (!(await queue.cancel(id)))
563
+ throw new Error(`job "${id}" cannot be cancelled`);
564
+ console.log(`job ${id} is cancelled`);
565
+ }
566
+ else if (action === 'prune') {
567
+ console.log(`pruned ${await queue.prune()} job(s)`);
568
+ }
569
+ else {
570
+ throw new Error('usage: ket jobs list|retry ID|cancel ID|prune [--tenant NAME]');
571
+ }
572
+ }
573
+ finally {
574
+ await adapter.close();
575
+ }
576
+ }
577
+ else if (cmd === 'check') {
578
+ console.log(explainWorkspace(ws));
579
+ console.log('\nall contracts hold');
580
+ }
581
+ else if (cmd === 'workspace') {
582
+ console.log(explainWorkspace(ws));
583
+ }
584
+ else if (cmd === 'modules') {
585
+ console.log(formatModules(resolved.modulePaths, resolved.modules));
586
+ }
587
+ else if (cmd === 'manifest') {
588
+ const [, m] = pickApp(ws);
589
+ console.log(JSON.stringify(m, null, 2));
590
+ }
591
+ else if (cmd === 'types') {
592
+ const [name, m] = pickApp(ws);
593
+ const out = `.ket/types.${name}.d.ts`;
594
+ writeFileSync(out, generateDts(m));
595
+ console.log(`wrote ${out}`);
596
+ }
597
+ else if (cmd === 'permissions') {
598
+ const [, m] = pickApp(ws);
599
+ const module = opt('module');
600
+ const grant = opt('grant');
601
+ const role = opt('role');
602
+ if (role) {
603
+ // The one form that reads the database: a role is data, and what it grants is
604
+ // a fact about a deployment rather than about the code.
605
+ const spec = pickSpec(specs);
606
+ const config = readConfig(process.env, spec.serve?.defaults ?? {});
607
+ const adapter = await (spec.serve?.openStore ?? sqliteStore)(config);
608
+ try {
609
+ const keys = await grantsOfRole(adapter, role);
610
+ console.log(`role "${role}" grants ${keys.length} function(s)\n`);
611
+ console.log(formatReach(reachOf(m, keys)));
612
+ }
613
+ finally {
614
+ await adapter.close();
615
+ }
616
+ }
617
+ else if (module)
618
+ console.log(formatReach(reachOf(m, functionsOf(m, module))));
619
+ else if (grant)
620
+ console.log(formatReach(reachOf(m, grant
621
+ .split(',')
622
+ .map((s) => s.trim())
623
+ .filter(Boolean))));
624
+ else
625
+ console.log(formatInventory(m));
626
+ }
627
+ else if (cmd === 'agent') {
628
+ const [, m] = pickApp(ws);
629
+ console.log(JSON.stringify(agentDescriptor(m), null, 2));
630
+ }
631
+ else if (cmd === 'snapshot') {
632
+ const [name, m] = pickApp(ws);
633
+ const out = `.ket/manifest.${name}.json`;
634
+ writeFileSync(out, JSON.stringify(m, null, 2));
635
+ console.log(`wrote ${out}`);
636
+ }
637
+ else if (cmd === 'diff') {
638
+ const against = opt('against');
639
+ if (!against || !existsSync(against))
640
+ throw new Error('pass --against <manifest.json> (make one with `ket snapshot`)');
641
+ const [, m] = pickApp(ws);
642
+ const before = JSON.parse(readFileSync(against, 'utf8'));
643
+ const items = diffManifests(before, m);
644
+ console.log(formatDiff(items));
645
+ process.exit(items.some((i) => i.severity === 'breaking') ? 1 : 0);
646
+ }
647
+ else if (cmd === 'migrate') {
648
+ const spec = pickSpec(specs);
649
+ if (flag('all')) {
650
+ // The fleet. A deployment that ships a new module has to reach every tenant
651
+ // database, and one that cannot be opened must not stop the others — a
652
+ // half-migrated fleet you cannot see is worse than one you can.
653
+ const tenants = spec.serve?.tenants;
654
+ if (!tenants)
655
+ throw new Error(`app "${spec.name}" serves a single datastore; drop --all`);
656
+ const config = readConfig(process.env, spec.serve?.defaults ?? {});
657
+ const pool = createAdapterPool({ create: (key) => tenants.open(key, config) });
658
+ try {
659
+ const keys = await tenants.list();
660
+ const m = ws.apps[spec.name];
661
+ const results = await migrateFleet(pool, keys, m, {
662
+ allowDestructive: flag('allow-destructive'),
663
+ dryRun: flag('dry-run'),
664
+ });
665
+ console.log(formatFleet(results));
666
+ process.exit(results.some((r) => r.error) ? 1 : 0);
667
+ }
668
+ finally {
669
+ await pool.close();
670
+ }
671
+ }
672
+ const [name, m] = pickApp(ws);
673
+ const adapter = sqliteAdapter();
674
+ adapter.open();
675
+ const snapPath = `.ket/schema.${name}.json`;
676
+ const prev = existsSync(snapPath) ? JSON.parse(readFileSync(snapPath, 'utf8')) : null;
677
+ const next = schemaFromManifest(m);
678
+ const ops = planMigration(prev, next, { allowDestructive: flag('allow-destructive') });
679
+ for (const sql of renderSql(ops, adapter))
680
+ console.log(sql + ';');
681
+ writeFileSync(snapPath, JSON.stringify(next, null, 2));
682
+ console.log(`\n-- ${ops.length} operation(s); schema snapshot written to ${snapPath}`);
683
+ adapter.close();
684
+ }
685
+ else {
686
+ console.error(`unknown command "${cmd}"\n\n${HELP}`);
687
+ process.exit(1);
688
+ }
689
+ }
690
+ catch (e) {
691
+ if (e instanceof KetError) {
692
+ console.error(JSON.stringify(e.toJSON(), null, 2));
693
+ process.exit(1);
694
+ }
695
+ console.error(e.message);
696
+ process.exit(1);
697
+ }
698
+ //# sourceMappingURL=cli.js.map