@ketvietlab/ketjs 0.1.2 → 0.1.3
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/LICENSE +1 -1
- package/README.md +8 -2
- package/dist/agent/capabilities.d.ts +1 -3
- package/dist/agent/capabilities.d.ts.map +1 -1
- package/dist/agent/capabilities.js +10 -1
- package/dist/agent/capabilities.js.map +1 -1
- package/dist/agent/permissions.d.ts +43 -1
- package/dist/agent/permissions.d.ts.map +1 -1
- package/dist/agent/permissions.js +50 -2
- package/dist/agent/permissions.js.map +1 -1
- package/dist/cli.js +246 -97
- package/dist/cli.js.map +1 -1
- package/dist/codegen/dts.d.ts.map +1 -1
- package/dist/codegen/dts.js +1 -0
- package/dist/codegen/dts.js.map +1 -1
- package/dist/data/changeset.d.ts +12 -0
- package/dist/data/changeset.d.ts.map +1 -1
- package/dist/data/changeset.js +73 -17
- package/dist/data/changeset.js.map +1 -1
- package/dist/data/expr.d.ts +16 -0
- package/dist/data/expr.d.ts.map +1 -1
- package/dist/data/expr.js +33 -5
- package/dist/data/expr.js.map +1 -1
- package/dist/data/fleet.d.ts +41 -1
- package/dist/data/fleet.d.ts.map +1 -1
- package/dist/data/fleet.js +100 -11
- package/dist/data/fleet.js.map +1 -1
- package/dist/data/migrate.d.ts +20 -0
- package/dist/data/migrate.d.ts.map +1 -1
- package/dist/data/migrate.js +86 -3
- package/dist/data/migrate.js.map +1 -1
- package/dist/data/physical.d.ts +5 -0
- package/dist/data/physical.d.ts.map +1 -0
- package/dist/data/physical.js +196 -0
- package/dist/data/physical.js.map +1 -0
- package/dist/data/pool.d.ts +2 -1
- package/dist/data/pool.d.ts.map +1 -1
- package/dist/data/pool.js +159 -31
- package/dist/data/pool.js.map +1 -1
- package/dist/data/query.d.ts +10 -1
- package/dist/data/query.d.ts.map +1 -1
- package/dist/data/query.js +136 -27
- package/dist/data/query.js.map +1 -1
- package/dist/data/sqlite.d.ts.map +1 -1
- package/dist/data/sqlite.js +350 -52
- package/dist/data/sqlite.js.map +1 -1
- package/dist/data/time.d.ts +13 -0
- package/dist/data/time.d.ts.map +1 -1
- package/dist/data/time.js +61 -17
- package/dist/data/time.js.map +1 -1
- package/dist/index.d.ts +43 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -16
- package/dist/index.js.map +1 -1
- package/dist/kernel/audit.d.ts +27 -0
- package/dist/kernel/audit.d.ts.map +1 -0
- package/dist/kernel/audit.js +67 -0
- package/dist/kernel/audit.js.map +1 -0
- package/dist/kernel/classification.d.ts +26 -0
- package/dist/kernel/classification.d.ts.map +1 -0
- package/dist/kernel/classification.js +73 -0
- package/dist/kernel/classification.js.map +1 -0
- package/dist/kernel/compose.d.ts +6 -2
- package/dist/kernel/compose.d.ts.map +1 -1
- package/dist/kernel/compose.js +269 -15
- package/dist/kernel/compose.js.map +1 -1
- package/dist/kernel/define.d.ts +2 -3
- package/dist/kernel/define.d.ts.map +1 -1
- package/dist/kernel/define.js +22 -12
- package/dist/kernel/define.js.map +1 -1
- package/dist/kernel/diff.d.ts.map +1 -1
- package/dist/kernel/diff.js +28 -4
- package/dist/kernel/diff.js.map +1 -1
- package/dist/kernel/graph.js +1 -1
- package/dist/kernel/graph.js.map +1 -1
- package/dist/kernel/i18n.d.ts +10 -1
- package/dist/kernel/i18n.d.ts.map +1 -1
- package/dist/kernel/i18n.js +34 -1
- package/dist/kernel/i18n.js.map +1 -1
- package/dist/kernel/layout.d.ts +97 -0
- package/dist/kernel/layout.d.ts.map +1 -1
- package/dist/kernel/layout.js +294 -12
- package/dist/kernel/layout.js.map +1 -1
- package/dist/kernel/menu.d.ts +33 -2
- package/dist/kernel/menu.d.ts.map +1 -1
- package/dist/kernel/menu.js +99 -7
- package/dist/kernel/menu.js.map +1 -1
- package/dist/kernel/modules.d.ts +3 -3
- package/dist/kernel/modules.d.ts.map +1 -1
- package/dist/kernel/modules.js +21 -21
- package/dist/kernel/modules.js.map +1 -1
- package/dist/kernel/permissions.d.ts +10 -0
- package/dist/kernel/permissions.d.ts.map +1 -0
- package/dist/kernel/permissions.js +365 -0
- package/dist/kernel/permissions.js.map +1 -0
- package/dist/kernel/schedule.d.ts +23 -0
- package/dist/kernel/schedule.d.ts.map +1 -0
- package/dist/kernel/schedule.js +83 -0
- package/dist/kernel/schedule.js.map +1 -0
- package/dist/kernel/types.d.ts.map +1 -1
- package/dist/kernel/types.js +5 -4
- package/dist/kernel/types.js.map +1 -1
- package/dist/kernel/workspace.d.ts +92 -14
- package/dist/kernel/workspace.d.ts.map +1 -1
- package/dist/kernel/workspace.js +128 -49
- package/dist/kernel/workspace.js.map +1 -1
- package/dist/pdf/assets/Inter-Bold.ttf +0 -0
- package/dist/pdf/assets/Inter-Regular.ttf +0 -0
- package/dist/pdf/assets/Inter-SemiBold.ttf +0 -0
- package/dist/pdf/assets/LICENSE.txt +92 -0
- package/dist/pdf/font.d.ts +10 -0
- package/dist/pdf/font.d.ts.map +1 -0
- package/dist/pdf/font.js +83 -0
- package/dist/pdf/font.js.map +1 -0
- package/dist/pdf/image.d.ts +8 -0
- package/dist/pdf/image.d.ts.map +1 -0
- package/dist/pdf/image.js +94 -0
- package/dist/pdf/image.js.map +1 -0
- package/dist/pdf/index.d.ts +15 -0
- package/dist/pdf/index.d.ts.map +1 -0
- package/dist/pdf/index.js +17 -0
- package/dist/pdf/index.js.map +1 -0
- package/dist/pdf/markup.d.ts +19 -0
- package/dist/pdf/markup.d.ts.map +1 -0
- package/dist/pdf/markup.js +146 -0
- package/dist/pdf/markup.js.map +1 -0
- package/dist/pdf/render.d.ts +14 -0
- package/dist/pdf/render.d.ts.map +1 -0
- package/dist/pdf/render.js +387 -0
- package/dist/pdf/render.js.map +1 -0
- package/dist/scaffold/index.js +6 -6
- package/dist/scaffold/index.js.map +1 -1
- package/dist/scaffold/templates/{app.test.ts.tmpl → deployment.test.ts.tmpl} +3 -3
- package/dist/scaffold/templates/ket.workspace.ts.tmpl +4 -6
- package/dist/scaffold/templates/module.ts.tmpl +1 -4
- package/dist/server/assets.d.ts +23 -0
- package/dist/server/assets.d.ts.map +1 -0
- package/dist/server/assets.js +97 -0
- package/dist/server/assets.js.map +1 -0
- package/dist/server/boot.d.ts +157 -38
- package/dist/server/boot.d.ts.map +1 -1
- package/dist/server/boot.js +360 -138
- package/dist/server/boot.js.map +1 -1
- package/dist/server/config.d.ts +30 -10
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +73 -17
- package/dist/server/config.js.map +1 -1
- package/dist/server/ctx.d.ts +10 -0
- package/dist/server/ctx.d.ts.map +1 -1
- package/dist/server/ctx.js +266 -48
- package/dist/server/ctx.js.map +1 -1
- package/dist/server/development.d.ts +10 -0
- package/dist/server/development.d.ts.map +1 -0
- package/dist/server/development.js +15 -0
- package/dist/server/development.js.map +1 -0
- package/dist/server/fn.d.ts +22 -3
- package/dist/server/fn.d.ts.map +1 -1
- package/dist/server/fn.js +116 -32
- package/dist/server/fn.js.map +1 -1
- package/dist/server/form.d.ts +33 -0
- package/dist/server/form.d.ts.map +1 -0
- package/dist/server/form.js +47 -0
- package/dist/server/form.js.map +1 -0
- package/dist/server/http.d.ts +55 -3
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +306 -22
- package/dist/server/http.js.map +1 -1
- package/dist/server/idem.d.ts +3 -2
- package/dist/server/idem.d.ts.map +1 -1
- package/dist/server/idem.js +12 -4
- package/dist/server/idem.js.map +1 -1
- package/dist/server/jobs.js +1 -1
- package/dist/server/jobs.js.map +1 -1
- package/dist/server/log/combinators.d.ts +42 -0
- package/dist/server/log/combinators.d.ts.map +1 -0
- package/dist/server/log/combinators.js +270 -0
- package/dist/server/log/combinators.js.map +1 -0
- package/dist/server/log/console.d.ts +12 -0
- package/dist/server/log/console.d.ts.map +1 -0
- package/dist/server/log/console.js +96 -0
- package/dist/server/log/console.js.map +1 -0
- package/dist/server/log/file.d.ts +11 -0
- package/dist/server/log/file.d.ts.map +1 -0
- package/dist/server/log/file.js +61 -0
- package/dist/server/log/file.js.map +1 -0
- package/dist/server/log/index.d.ts +23 -0
- package/dist/server/log/index.d.ts.map +1 -0
- package/dist/server/log/index.js +45 -0
- package/dist/server/log/index.js.map +1 -0
- package/dist/server/log/logger.d.ts +53 -0
- package/dist/server/log/logger.d.ts.map +1 -0
- package/dist/server/log/logger.js +94 -0
- package/dist/server/log/logger.js.map +1 -0
- package/dist/server/log/memory.d.ts +11 -0
- package/dist/server/log/memory.d.ts.map +1 -0
- package/dist/server/log/memory.js +25 -0
- package/dist/server/log/memory.js.map +1 -0
- package/dist/server/log/types.d.ts +112 -0
- package/dist/server/log/types.d.ts.map +1 -0
- package/dist/server/log/types.js +58 -0
- package/dist/server/log/types.js.map +1 -0
- package/dist/server/policy.d.ts +29 -0
- package/dist/server/policy.d.ts.map +1 -0
- package/dist/server/policy.js +29 -0
- package/dist/server/policy.js.map +1 -0
- package/dist/server/queue.d.ts +1 -1
- package/dist/server/queue.d.ts.map +1 -1
- package/dist/server/queue.js.map +1 -1
- package/dist/server/ratelimit.d.ts +48 -0
- package/dist/server/ratelimit.d.ts.map +1 -0
- package/dist/server/ratelimit.js +102 -0
- package/dist/server/ratelimit.js.map +1 -0
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +16 -1
- package/dist/server/respond.js.map +1 -1
- package/dist/server/runtime.d.ts +18 -3
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +32 -2
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/schedule.d.ts +18 -0
- package/dist/server/schedule.d.ts.map +1 -0
- package/dist/server/schedule.js +70 -0
- package/dist/server/schedule.js.map +1 -0
- package/dist/server/sequence.d.ts +42 -0
- package/dist/server/sequence.d.ts.map +1 -0
- package/dist/server/sequence.js +97 -0
- package/dist/server/sequence.js.map +1 -0
- package/dist/server/session.d.ts +21 -0
- package/dist/server/session.d.ts.map +1 -1
- package/dist/server/session.js +84 -10
- package/dist/server/session.js.map +1 -1
- package/dist/server/sessionstore.d.ts +3 -1
- package/dist/server/sessionstore.d.ts.map +1 -1
- package/dist/server/sessionstore.js +7 -3
- package/dist/server/sessionstore.js.map +1 -1
- package/dist/server/storage/index.d.ts +1 -1
- package/dist/server/storage/index.d.ts.map +1 -1
- package/dist/server/storage/index.js +61 -1
- package/dist/server/storage/index.js.map +1 -1
- package/dist/server/storage/types.d.ts +8 -0
- package/dist/server/storage/types.d.ts.map +1 -1
- package/dist/server/storage/types.js +58 -0
- package/dist/server/storage/types.js.map +1 -1
- package/dist/server/tenants.d.ts +20 -18
- package/dist/server/tenants.d.ts.map +1 -1
- package/dist/server/tenants.js +133 -84
- package/dist/server/tenants.js.map +1 -1
- package/dist/server/transport/types.js +2 -2
- package/dist/server/transport/types.js.map +1 -1
- package/dist/server/worker.d.ts +19 -3
- package/dist/server/worker.d.ts.map +1 -1
- package/dist/server/worker.js +168 -21
- package/dist/server/worker.js.map +1 -1
- package/dist/testing.d.ts +26 -10
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +41 -14
- package/dist/testing.js.map +1 -1
- package/dist/theme/contracts.d.ts +14 -1
- package/dist/theme/contracts.d.ts.map +1 -1
- package/dist/theme/contracts.js +35 -6
- package/dist/theme/contracts.js.map +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/joint-runtime.d.ts +33 -0
- package/dist/theme/joint-runtime.d.ts.map +1 -0
- package/dist/theme/joint-runtime.js +121 -0
- package/dist/theme/joint-runtime.js.map +1 -0
- package/dist/theme/joints.d.ts.map +1 -1
- package/dist/theme/joints.js +15 -97
- package/dist/theme/joints.js.map +1 -1
- package/dist/theme/ktl/compile.d.ts +4 -0
- package/dist/theme/ktl/compile.d.ts.map +1 -1
- package/dist/theme/ktl/compile.js +54 -1
- package/dist/theme/ktl/compile.js.map +1 -1
- package/dist/theme/ktl/parser.d.ts +4 -0
- package/dist/theme/ktl/parser.d.ts.map +1 -1
- package/dist/theme/ktl/parser.js +8 -0
- package/dist/theme/ktl/parser.js.map +1 -1
- package/dist/theme/render.d.ts +8 -0
- package/dist/theme/render.d.ts.map +1 -1
- package/dist/theme/render.js +88 -118
- package/dist/theme/render.js.map +1 -1
- package/dist/theme/tokens.d.ts +6 -0
- package/dist/theme/tokens.d.ts.map +1 -1
- package/dist/theme/tokens.js +6 -0
- package/dist/theme/tokens.js.map +1 -1
- package/dist/theme/viewmodel.d.ts +24 -0
- package/dist/theme/viewmodel.d.ts.map +1 -1
- package/dist/theme/viewmodel.js +75 -6
- package/dist/theme/viewmodel.js.map +1 -1
- package/dist/types.d.ts +304 -56
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -2
- package/dist/kernel/apps.d.ts +0 -43
- package/dist/kernel/apps.d.ts.map +0 -1
- package/dist/kernel/apps.js +0 -234
- package/dist/kernel/apps.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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
|
|
3
|
+
// so there is no second source of truth about what a deployment contains.
|
|
4
4
|
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
5
5
|
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
6
6
|
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
@@ -18,21 +18,22 @@ import { resolveWorkspace } from './kernel/modules.js';
|
|
|
18
18
|
import { diffManifests, formatDiff } from './kernel/diff.js';
|
|
19
19
|
import { generateDts } from './codegen/dts.js';
|
|
20
20
|
import { agentDescriptor } from './agent/capabilities.js';
|
|
21
|
-
import { reachOf, functionsOf, formatReach, formatInventory, grantsOfRole } from './agent/permissions.js';
|
|
21
|
+
import { reachOf, functionsOf, formatReach, formatInventory, grantsOfRole, permissionInventory, } from './agent/permissions.js';
|
|
22
|
+
import { classificationInventory, formatClassification } from './kernel/classification.js';
|
|
22
23
|
import { readConfig, sqliteStore } from './server/config.js';
|
|
23
24
|
import { schemaFromManifest, planMigration, renderSql } from './data/migrate.js';
|
|
24
|
-
import { migrateOne, migrateFleet, formatFleet } from './data/fleet.js';
|
|
25
|
+
import { migrateOne, migrateFleet, formatFleet, verifyPhysicalSchema } from './data/fleet.js';
|
|
25
26
|
import { createAdapterPool } from './data/pool.js';
|
|
26
27
|
import { sqliteAdapter } from './data/sqlite.js';
|
|
27
|
-
import {
|
|
28
|
+
import { bootDeployment, serveDeployment } from './server/boot.js';
|
|
28
29
|
import { bootRuntime } from './server/runtime.js';
|
|
29
30
|
import { callFn } from './server/fn.js';
|
|
30
|
-
import { createAppRegistry, restrictManifest } from './kernel/apps.js';
|
|
31
31
|
import { bootWorker, serveWorker } from './server/worker.js';
|
|
32
|
+
import { createDevelopmentCloser } from './server/development.js';
|
|
32
33
|
import { createQueue } from './server/queue.js';
|
|
33
34
|
import { scaffold } from './scaffold/index.js';
|
|
34
35
|
import { KetError } from './kernel/errors.js';
|
|
35
|
-
import { CookieJar,
|
|
36
|
+
import { CookieJar, createTestDeployment, TestClient, TestHttpError } from './testing.js';
|
|
36
37
|
const [, , cmd = 'help', ...rest] = process.argv;
|
|
37
38
|
const flag = (name) => rest.includes(`--${name}`);
|
|
38
39
|
const opt = (name) => {
|
|
@@ -76,7 +77,7 @@ const CANDIDATES = [
|
|
|
76
77
|
'workspace.js',
|
|
77
78
|
'examples/workspace.js',
|
|
78
79
|
];
|
|
79
|
-
/** Where
|
|
80
|
+
/** Where deployments are declared. Named explicitly, or the first conventional path. */
|
|
80
81
|
const workspacePath = () => {
|
|
81
82
|
const given = opt('workspace');
|
|
82
83
|
if (given) {
|
|
@@ -94,11 +95,11 @@ const loadWorkspace = async () => {
|
|
|
94
95
|
const configuredPath = workspacePath();
|
|
95
96
|
const absolutePath = resolve(configuredPath);
|
|
96
97
|
const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(absolutePath).href)));
|
|
97
|
-
const declaration = mod.default && typeof mod.default === 'object' && Array.isArray(mod.default.
|
|
98
|
+
const declaration = mod.default && typeof mod.default === 'object' && Array.isArray(mod.default.deployments)
|
|
98
99
|
? mod.default
|
|
99
|
-
: {
|
|
100
|
-
if (!Array.isArray(declaration.
|
|
101
|
-
throw new Error(`${configuredPath} must default-export defineWorkspace(...) or export \`
|
|
100
|
+
: { deployments: mod.deployments, modulePaths: mod.modulePaths };
|
|
101
|
+
if (!Array.isArray(declaration.deployments))
|
|
102
|
+
throw new Error(`${configuredPath} must default-export defineWorkspace(...) or export \`deployments\`, an array of defineDeployment(...)`);
|
|
102
103
|
const fromEnv = (process.env.KET_MODULE_PATH ?? '')
|
|
103
104
|
.split(delimiter)
|
|
104
105
|
.map((path) => path.trim())
|
|
@@ -108,7 +109,11 @@ const loadWorkspace = async () => {
|
|
|
108
109
|
extraModulePaths: [...fromEnv, ...opts('module-path')],
|
|
109
110
|
allowSource: process.env.KET_DEV_SOURCE === '1',
|
|
110
111
|
});
|
|
111
|
-
return {
|
|
112
|
+
return {
|
|
113
|
+
ws: composeWorkspace(resolved.deployments),
|
|
114
|
+
deployments: resolved.deployments,
|
|
115
|
+
resolved,
|
|
116
|
+
};
|
|
112
117
|
};
|
|
113
118
|
const formatModules = (paths, modules) => {
|
|
114
119
|
const lines = ['module paths:'];
|
|
@@ -118,53 +123,68 @@ const formatModules = (paths, modules) => {
|
|
|
118
123
|
lines.push(' (none)');
|
|
119
124
|
lines.push('modules:');
|
|
120
125
|
for (const module of modules) {
|
|
121
|
-
lines.push(` ${module.name.padEnd(24)} ${module.version.padEnd(10)} ${module.kind.padEnd(6)}
|
|
126
|
+
lines.push(` ${module.name.padEnd(24)} ${module.version.padEnd(10)} ${module.kind.padEnd(6)} deployments=${module.deployments.join(',')} ${module.source}`);
|
|
122
127
|
}
|
|
123
128
|
if (!modules.length)
|
|
124
129
|
lines.push(' (none)');
|
|
125
130
|
return lines.join('\n');
|
|
126
131
|
};
|
|
127
|
-
/** The
|
|
132
|
+
/** The DeploymentSpec, not the composed manifest — serving needs the authored runtime settings. */
|
|
128
133
|
const pickSpec = (specs) => {
|
|
129
|
-
const name = opt('
|
|
134
|
+
const name = opt('deployment');
|
|
130
135
|
if (!name) {
|
|
131
136
|
const servable = specs.filter((s) => s.serve);
|
|
132
137
|
const one = servable[0] ?? specs[0];
|
|
133
138
|
if (!one)
|
|
134
|
-
throw new Error('the workspace declares no
|
|
139
|
+
throw new Error('the workspace declares no deployments');
|
|
135
140
|
return one;
|
|
136
141
|
}
|
|
137
142
|
const found = specs.find((s) => s.name === name);
|
|
138
143
|
if (!found)
|
|
139
|
-
throw new Error(`unknown
|
|
144
|
+
throw new Error(`unknown deployment "${name}" (have: ${specs.map((s) => s.name).join(', ')})`);
|
|
140
145
|
return found;
|
|
141
146
|
};
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
const pickFleetSpec = (specs) => {
|
|
148
|
+
if (opt('deployment'))
|
|
149
|
+
return pickSpec(specs);
|
|
150
|
+
const fleets = specs.filter((spec) => spec.serve?.tenants);
|
|
151
|
+
if (!fleets.length)
|
|
152
|
+
throw new Error('the workspace declares no tenant-fleet deployment');
|
|
153
|
+
if (fleets.length > 1)
|
|
154
|
+
throw new Error(`the workspace declares multiple tenant-fleet deployments (${fleets.map((spec) => spec.name).join(', ')}); pass --deployment NAME`);
|
|
155
|
+
return fleets[0];
|
|
156
|
+
};
|
|
157
|
+
const pickDeployment = (ws) => {
|
|
158
|
+
const name = opt('deployment') ?? Object.keys(ws.deployments)[0];
|
|
159
|
+
const m = ws.deployments[name];
|
|
145
160
|
if (!m)
|
|
146
|
-
throw new Error(`unknown
|
|
161
|
+
throw new Error(`unknown deployment "${name}" (have: ${Object.keys(ws.deployments).join(', ')})`);
|
|
147
162
|
return [name, m];
|
|
148
163
|
};
|
|
149
164
|
const HELP = `ket — zero-dependency fullstack framework
|
|
150
165
|
|
|
151
|
-
ket check compose every
|
|
152
|
-
ket manifest [--
|
|
153
|
-
ket workspace show
|
|
166
|
+
ket check compose every deployment and report contract violations
|
|
167
|
+
ket manifest [--deployment X] print the composed manifest
|
|
168
|
+
ket workspace show deployments, datastores and shared modules
|
|
154
169
|
ket modules show resolved modules and their source paths
|
|
155
|
-
ket types [--
|
|
156
|
-
ket agent [--
|
|
170
|
+
ket types [--deployment X] generate .ket/types.d.ts from the manifest
|
|
171
|
+
ket agent [--deployment X] print the agent capability descriptor
|
|
157
172
|
ket permissions every function that exists to be granted
|
|
173
|
+
ket classification which fields hold personal data, and which never leave
|
|
158
174
|
--grant a,b,c …or what a role granted exactly these can reach
|
|
159
175
|
--module NAME …or what granting one module's whole surface reaches
|
|
160
176
|
--role NAME …or what a role in the database actually grants
|
|
161
|
-
|
|
177
|
+
--json machine-readable module/function inventory
|
|
178
|
+
--all with --json, inventory every deployment
|
|
179
|
+
ket migrate [--deployment X] plan migrations (add --allow-destructive to permit data loss)
|
|
162
180
|
--all …or apply them to every tenant database (add --dry-run)
|
|
181
|
+
ket schema verify [--deployment X] compare the physical database, migration marker, and manifest
|
|
182
|
+
--tenant KEY select one tenant database; --all verifies the complete tenant fleet
|
|
163
183
|
ket diff --against FILE compare the current manifest with a stored one
|
|
164
|
-
ket snapshot [--
|
|
184
|
+
ket snapshot [--deployment X] write .ket/manifest.<deployment>.json for a later diff
|
|
165
185
|
|
|
166
|
-
ket serve [--
|
|
167
|
-
ket worker [--
|
|
186
|
+
ket serve [--deployment X] boot and serve the deployment
|
|
187
|
+
ket worker [--deployment X] run declared queues for the same deployment and manifest
|
|
168
188
|
ket call FUNCTION exercise a function through its real HTTP endpoint
|
|
169
189
|
--against URL call an already-running development server
|
|
170
190
|
--input JSON|@FILE|- function input (default: {})
|
|
@@ -175,27 +195,49 @@ const HELP = `ket — zero-dependency fullstack framework
|
|
|
175
195
|
--login-path PATH override the application's login route
|
|
176
196
|
--dry-run report declared writes without applying them
|
|
177
197
|
--idempotency-key KEY exercise idempotent retry handling
|
|
178
|
-
--isolated boot a temporary
|
|
198
|
+
--isolated boot a temporary deployment/database for this call
|
|
179
199
|
ket provision FUNCTION run a declared one-shot bootstrap function
|
|
180
200
|
--input - read JSON, including secrets, from stdin only
|
|
181
|
-
--tenant KEY required when the
|
|
201
|
+
--tenant KEY required when the deployment uses tenant databases
|
|
182
202
|
ket test [FILES...] run emitted headless tests with Node's test runner
|
|
183
203
|
--watch rerun when the selected JavaScript artifacts change
|
|
184
204
|
--test-name-pattern P select tests by name; --coverage enables coverage
|
|
185
|
-
ket jobs list inspect durable jobs (--tenant is required for tenant
|
|
205
|
+
ket jobs list inspect durable jobs (--tenant is required for tenant deployments)
|
|
186
206
|
ket jobs retry ID make a retryable/discarded job available now
|
|
187
207
|
ket jobs cancel ID cancel a pending or executing job
|
|
188
208
|
ket jobs prune apply the 7/30-day retention policy
|
|
189
|
-
ket dev [--
|
|
209
|
+
ket dev [--deployment X] serve compiled output, restarting when an artifact changes
|
|
190
210
|
--all run HTTP and worker together under this one watcher
|
|
191
|
-
|
|
192
|
-
ket new NAME [--dir D] scaffold an app that runs
|
|
211
|
+
ket new NAME [--dir D] scaffold a deployment that runs
|
|
193
212
|
|
|
194
213
|
Options: --workspace FILE (default: dist/ket.workspace.js, ket.workspace.js, workspace.js)
|
|
195
214
|
--module-path DIR (repeatable; KET_MODULE_PATH uses the platform path separator)
|
|
196
|
-
--
|
|
215
|
+
--deployment NAME, --port N, --header "Name: value" (repeatable)
|
|
197
216
|
`;
|
|
198
217
|
const TEST_VALUE_OPTIONS = ['test-name-pattern', 'test-concurrency', 'reporter', 'out-dir'];
|
|
218
|
+
const formatSchemaVerification = (datastore, report) => {
|
|
219
|
+
if (report.ok)
|
|
220
|
+
return `ok ${datastore.padEnd(24)} physical schema, marker and manifest agree`;
|
|
221
|
+
const lines = [`FAIL ${datastore.padEnd(24)} schema verification failed`];
|
|
222
|
+
if (!report.applied)
|
|
223
|
+
lines.push(' marker: missing');
|
|
224
|
+
else if (!report.markerMatchesManifest)
|
|
225
|
+
lines.push(' marker: differs from the current manifest');
|
|
226
|
+
if (report.applied && report.markerIssues.length) {
|
|
227
|
+
lines.push(' physical vs marker:');
|
|
228
|
+
for (const issue of report.markerIssues)
|
|
229
|
+
lines.push(` - ${issue}`);
|
|
230
|
+
}
|
|
231
|
+
const sameIssues = report.markerMatchesManifest &&
|
|
232
|
+
report.markerIssues.length === report.manifestIssues.length &&
|
|
233
|
+
report.markerIssues.every((issue, index) => issue === report.manifestIssues[index]);
|
|
234
|
+
if (!sameIssues && report.manifestIssues.length) {
|
|
235
|
+
lines.push(' physical vs manifest:');
|
|
236
|
+
for (const issue of report.manifestIssues)
|
|
237
|
+
lines.push(` - ${issue}`);
|
|
238
|
+
}
|
|
239
|
+
return lines.join('\n');
|
|
240
|
+
};
|
|
199
241
|
const collectTests = (path) => {
|
|
200
242
|
if (!existsSync(path))
|
|
201
243
|
throw new Error(`test path does not exist: ${path}`);
|
|
@@ -300,7 +342,6 @@ const callWithClient = async (baseUrl) => {
|
|
|
300
342
|
'login-path',
|
|
301
343
|
'idempotency-key',
|
|
302
344
|
'header',
|
|
303
|
-
'app',
|
|
304
345
|
'workspace',
|
|
305
346
|
'module-path',
|
|
306
347
|
])[0];
|
|
@@ -359,7 +400,7 @@ try {
|
|
|
359
400
|
}
|
|
360
401
|
if (cmd === 'test')
|
|
361
402
|
await runTests();
|
|
362
|
-
// Remote smoke calls need no workspace and do not boot a second
|
|
403
|
+
// Remote smoke calls need no workspace and do not boot a second deployment.
|
|
363
404
|
if (cmd === 'call' && opt('against')) {
|
|
364
405
|
const against = new URL(opt('against'));
|
|
365
406
|
if (against.protocol !== 'http:' && against.protocol !== 'https:')
|
|
@@ -382,15 +423,15 @@ try {
|
|
|
382
423
|
process.on(sig, () => child.kill(sig));
|
|
383
424
|
await new Promise(() => { });
|
|
384
425
|
}
|
|
385
|
-
const { ws,
|
|
386
|
-
|
|
426
|
+
const { ws, deployments: specs, resolved } = await loadWorkspace();
|
|
427
|
+
if (!(cmd === 'migrate' && flag('dry-run')) && cmd !== 'schema')
|
|
428
|
+
mkdirSync('.ket', { recursive: true });
|
|
387
429
|
if (cmd === 'serve') {
|
|
388
430
|
const spec = pickSpec(specs);
|
|
389
431
|
if (!spec.serve)
|
|
390
|
-
throw new Error(`
|
|
432
|
+
throw new Error(`deployment "${spec.name}" declares no serve block, so there is nothing to run`);
|
|
391
433
|
const port = opt('port');
|
|
392
|
-
|
|
393
|
-
await serveApp(spec, { env, ...(port ? { port: Number(port) } : {}) });
|
|
434
|
+
await serveDeployment(spec, { env: process.env, ...(port ? { port: Number(port) } : {}) });
|
|
394
435
|
}
|
|
395
436
|
else if (cmd === 'worker') {
|
|
396
437
|
const spec = pickSpec(specs);
|
|
@@ -400,45 +441,42 @@ try {
|
|
|
400
441
|
else if (cmd === 'call') {
|
|
401
442
|
const spec = pickSpec(specs);
|
|
402
443
|
if (!spec.serve)
|
|
403
|
-
throw new Error(`
|
|
444
|
+
throw new Error(`deployment "${spec.name}" declares no serve block`);
|
|
404
445
|
if (flag('isolated')) {
|
|
405
|
-
const
|
|
446
|
+
const testDeployment = await createTestDeployment(spec, { worker: false });
|
|
406
447
|
try {
|
|
407
|
-
process.exitCode = await callWithClient(
|
|
448
|
+
process.exitCode = await callWithClient(testDeployment.baseUrl);
|
|
408
449
|
}
|
|
409
450
|
finally {
|
|
410
|
-
await
|
|
451
|
+
await testDeployment.close();
|
|
411
452
|
}
|
|
412
453
|
}
|
|
413
454
|
else {
|
|
414
|
-
const env = {
|
|
415
|
-
|
|
416
|
-
...(flag('no-migrate') ? { KET_MIGRATE: '0' } : {}),
|
|
417
|
-
...(flag('no-auto-install') ? { KET_AUTO_INSTALL: '0' } : {}),
|
|
418
|
-
};
|
|
419
|
-
const app = await bootApp(spec, {
|
|
455
|
+
const env = { ...process.env, ...(flag('no-migrate') ? { KET_MIGRATE: '0' } : {}) };
|
|
456
|
+
const deployment = await bootDeployment(spec, {
|
|
420
457
|
env,
|
|
421
458
|
port: 0,
|
|
422
459
|
log: flag('verbose') ? (line) => console.error(line) : () => { },
|
|
423
460
|
});
|
|
424
461
|
try {
|
|
425
|
-
process.exitCode = await callWithClient(`http://127.0.0.1:${
|
|
462
|
+
process.exitCode = await callWithClient(`http://127.0.0.1:${deployment.port}`);
|
|
426
463
|
}
|
|
427
464
|
finally {
|
|
428
|
-
await
|
|
465
|
+
await deployment.close();
|
|
429
466
|
}
|
|
430
467
|
}
|
|
431
468
|
}
|
|
432
469
|
else if (cmd === 'provision') {
|
|
433
|
-
const fnKey = positionals(['input', 'tenant', '
|
|
470
|
+
const fnKey = positionals(['input', 'tenant', 'deployment', 'workspace', 'module-path'])[0];
|
|
434
471
|
if (!fnKey)
|
|
435
472
|
throw new Error('usage: ket provision FUNCTION --input - [--tenant KEY]');
|
|
436
473
|
if (opt('input') !== '-')
|
|
437
474
|
throw new Error('ket provision reads input only from stdin; pass --input - so secrets never enter argv');
|
|
438
475
|
const spec = pickSpec(specs);
|
|
439
476
|
if (!spec.serve)
|
|
440
|
-
throw new Error(`
|
|
441
|
-
|
|
477
|
+
throw new Error(`deployment "${spec.name}" declares no serve block`);
|
|
478
|
+
// No socket is opened here, so the records are a command's, not a server's.
|
|
479
|
+
const runtime = await bootRuntime(spec, { env: process.env, role: 'cli' });
|
|
442
480
|
const meta = runtime.manifest.functions[fnKey];
|
|
443
481
|
if (!meta)
|
|
444
482
|
throw new Error(`unknown function "${fnKey}"`);
|
|
@@ -446,7 +484,7 @@ try {
|
|
|
446
484
|
throw new Error(`function "${fnKey}" is not declared internal + provision`);
|
|
447
485
|
const tenant = opt('tenant');
|
|
448
486
|
if (spec.serve.tenants && !tenant)
|
|
449
|
-
throw new Error(`
|
|
487
|
+
throw new Error(`deployment "${spec.name}" has tenant databases; pass --tenant NAME`);
|
|
450
488
|
const adapter = spec.serve.tenants
|
|
451
489
|
? await spec.serve.tenants.open(tenant, runtime.config)
|
|
452
490
|
: await (spec.serve.openStore ?? sqliteStore)(runtime.config);
|
|
@@ -454,17 +492,9 @@ try {
|
|
|
454
492
|
await adapter.open();
|
|
455
493
|
try {
|
|
456
494
|
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
495
|
const result = await callFn(fnKey, jsonObject('-', '--input'), {
|
|
466
496
|
adapter,
|
|
467
|
-
manifest:
|
|
497
|
+
manifest: runtime.manifest,
|
|
468
498
|
actor: 'system:provision',
|
|
469
499
|
scope: { company: null, branch: null },
|
|
470
500
|
});
|
|
@@ -482,24 +512,21 @@ try {
|
|
|
482
512
|
else if (cmd === 'all') {
|
|
483
513
|
const spec = pickSpec(specs);
|
|
484
514
|
if (!spec.serve)
|
|
485
|
-
throw new Error(`
|
|
515
|
+
throw new Error(`deployment "${spec.name}" declares no serve block`);
|
|
486
516
|
if (!spec.worker)
|
|
487
|
-
throw new Error(`
|
|
488
|
-
const
|
|
517
|
+
throw new Error(`deployment "${spec.name}" declares no worker block`);
|
|
518
|
+
const deployment = await bootDeployment(spec, {
|
|
489
519
|
env: process.env,
|
|
490
520
|
...(opt('port') ? { port: Number(opt('port')) } : {}),
|
|
491
521
|
});
|
|
492
522
|
const worker = await bootWorker(spec, { env: process.env });
|
|
493
523
|
worker.start();
|
|
494
|
-
console.log(await
|
|
524
|
+
console.log(await deployment.banner());
|
|
495
525
|
console.log(` worker ${worker.workerId} is running in this development process\n`);
|
|
496
|
-
const close =
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
};
|
|
501
|
-
for (const signal of ['SIGINT', 'SIGTERM'])
|
|
502
|
-
process.on(signal, () => void close());
|
|
526
|
+
const close = createDevelopmentCloser(deployment, worker);
|
|
527
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
528
|
+
process.on(signal, () => void close().then(() => process.exit(0)));
|
|
529
|
+
}
|
|
503
530
|
await new Promise(() => { });
|
|
504
531
|
}
|
|
505
532
|
else if (cmd === 'jobs') {
|
|
@@ -518,7 +545,7 @@ try {
|
|
|
518
545
|
});
|
|
519
546
|
const tenant = opt('tenant');
|
|
520
547
|
if (spec.serve?.tenants && !tenant)
|
|
521
|
-
throw new Error(`
|
|
548
|
+
throw new Error(`deployment "${spec.name}" has tenant databases; pass --tenant NAME`);
|
|
522
549
|
const adapter = spec.serve?.tenants
|
|
523
550
|
? await spec.serve.tenants.open(tenant, config)
|
|
524
551
|
: await (spec.serve?.openStore ?? sqliteStore)(config);
|
|
@@ -585,21 +612,40 @@ try {
|
|
|
585
612
|
console.log(formatModules(resolved.modulePaths, resolved.modules));
|
|
586
613
|
}
|
|
587
614
|
else if (cmd === 'manifest') {
|
|
588
|
-
const [, m] =
|
|
615
|
+
const [, m] = pickDeployment(ws);
|
|
589
616
|
console.log(JSON.stringify(m, null, 2));
|
|
590
617
|
}
|
|
591
618
|
else if (cmd === 'types') {
|
|
592
|
-
const [name, m] =
|
|
619
|
+
const [name, m] = pickDeployment(ws);
|
|
593
620
|
const out = `.ket/types.${name}.d.ts`;
|
|
594
621
|
writeFileSync(out, generateDts(m));
|
|
595
622
|
console.log(`wrote ${out}`);
|
|
596
623
|
}
|
|
597
624
|
else if (cmd === 'permissions') {
|
|
598
|
-
const [, m] = pickApp(ws);
|
|
599
625
|
const module = opt('module');
|
|
600
626
|
const grant = opt('grant');
|
|
601
627
|
const role = opt('role');
|
|
602
|
-
if (
|
|
628
|
+
if (flag('json')) {
|
|
629
|
+
if (module || grant || role)
|
|
630
|
+
throw new Error('permissions --json cannot be combined with --module, --grant or --role');
|
|
631
|
+
if (flag('all')) {
|
|
632
|
+
if (opt('deployment'))
|
|
633
|
+
throw new Error('permissions --json accepts either --deployment NAME or --all, not both');
|
|
634
|
+
const deployments = Object.entries(ws.deployments)
|
|
635
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
636
|
+
.map(([name, manifest]) => ({ name, inventory: permissionInventory(manifest) }));
|
|
637
|
+
console.log(JSON.stringify({ version: 1, deployments }, null, 2));
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
const [name, manifest] = pickDeployment(ws);
|
|
641
|
+
console.log(JSON.stringify({ name, inventory: permissionInventory(manifest) }, null, 2));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else if (flag('all')) {
|
|
645
|
+
throw new Error('permissions --all requires --json');
|
|
646
|
+
}
|
|
647
|
+
else if (role) {
|
|
648
|
+
const [, m] = pickDeployment(ws);
|
|
603
649
|
// The one form that reads the database: a role is data, and what it grants is
|
|
604
650
|
// a fact about a deployment rather than about the code.
|
|
605
651
|
const spec = pickSpec(specs);
|
|
@@ -614,22 +660,34 @@ try {
|
|
|
614
660
|
await adapter.close();
|
|
615
661
|
}
|
|
616
662
|
}
|
|
617
|
-
else if (module)
|
|
663
|
+
else if (module) {
|
|
664
|
+
const [, m] = pickDeployment(ws);
|
|
618
665
|
console.log(formatReach(reachOf(m, functionsOf(m, module))));
|
|
619
|
-
|
|
666
|
+
}
|
|
667
|
+
else if (grant) {
|
|
668
|
+
const [, m] = pickDeployment(ws);
|
|
620
669
|
console.log(formatReach(reachOf(m, grant
|
|
621
670
|
.split(',')
|
|
622
671
|
.map((s) => s.trim())
|
|
623
672
|
.filter(Boolean))));
|
|
624
|
-
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
const [, m] = pickDeployment(ws);
|
|
625
676
|
console.log(formatInventory(m));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
else if (cmd === 'classification') {
|
|
680
|
+
const [, m] = pickDeployment(ws);
|
|
681
|
+
const inventory = classificationInventory(m);
|
|
682
|
+
// JSON for an audit that has to be filed; columns for a person reading it now.
|
|
683
|
+
console.log(flag('json') ? JSON.stringify(inventory, null, 2) : formatClassification(inventory));
|
|
626
684
|
}
|
|
627
685
|
else if (cmd === 'agent') {
|
|
628
|
-
const [, m] =
|
|
686
|
+
const [, m] = pickDeployment(ws);
|
|
629
687
|
console.log(JSON.stringify(agentDescriptor(m), null, 2));
|
|
630
688
|
}
|
|
631
689
|
else if (cmd === 'snapshot') {
|
|
632
|
-
const [name, m] =
|
|
690
|
+
const [name, m] = pickDeployment(ws);
|
|
633
691
|
const out = `.ket/manifest.${name}.json`;
|
|
634
692
|
writeFileSync(out, JSON.stringify(m, null, 2));
|
|
635
693
|
console.log(`wrote ${out}`);
|
|
@@ -638,26 +696,113 @@ try {
|
|
|
638
696
|
const against = opt('against');
|
|
639
697
|
if (!against || !existsSync(against))
|
|
640
698
|
throw new Error('pass --against <manifest.json> (make one with `ket snapshot`)');
|
|
641
|
-
const [, m] =
|
|
699
|
+
const [, m] = pickDeployment(ws);
|
|
642
700
|
const before = JSON.parse(readFileSync(against, 'utf8'));
|
|
643
701
|
const items = diffManifests(before, m);
|
|
644
702
|
console.log(formatDiff(items));
|
|
645
703
|
process.exit(items.some((i) => i.severity === 'breaking') ? 1 : 0);
|
|
646
704
|
}
|
|
705
|
+
else if (cmd === 'schema') {
|
|
706
|
+
const action = positionals(['workspace', 'module-path', 'deployment', 'tenant'])[0];
|
|
707
|
+
if (action !== 'verify')
|
|
708
|
+
throw new Error('usage: ket schema verify [--deployment NAME] [--tenant NAME | --all]');
|
|
709
|
+
if (flag('all')) {
|
|
710
|
+
if (opt('tenant'))
|
|
711
|
+
throw new Error('pass either --tenant NAME or --all, not both');
|
|
712
|
+
const spec = pickFleetSpec(specs);
|
|
713
|
+
const tenants = spec.serve?.tenants;
|
|
714
|
+
if (!tenants)
|
|
715
|
+
throw new Error(`deployment "${spec.name}" serves a single datastore; drop --all`);
|
|
716
|
+
const tenantExists = tenants.exists;
|
|
717
|
+
if (!tenantExists)
|
|
718
|
+
throw new Error(`deployment "${spec.name}" must define serve.tenants.exists(key, config) before schema verification can inspect tenant datastores without creating them`);
|
|
719
|
+
const config = readConfig(process.env, {
|
|
720
|
+
sqliteFile: `.ket/${spec.name}.db`,
|
|
721
|
+
...spec.serve?.defaults,
|
|
722
|
+
});
|
|
723
|
+
const pool = createAdapterPool({ create: (key) => tenants.open(key, config) });
|
|
724
|
+
let failed = false;
|
|
725
|
+
try {
|
|
726
|
+
const manifest = ws.deployments[spec.name];
|
|
727
|
+
const lines = [];
|
|
728
|
+
for (const key of await tenants.list()) {
|
|
729
|
+
try {
|
|
730
|
+
if (!(await tenantExists(key, config))) {
|
|
731
|
+
failed = true;
|
|
732
|
+
lines.push(`FAIL ${key.padEnd(24)} datastore does not exist; schema verification will not create it`);
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
const report = await pool.with(key, (adapter) => verifyPhysicalSchema(adapter, manifest));
|
|
736
|
+
lines.push(formatSchemaVerification(key, report));
|
|
737
|
+
failed ||= !report.ok;
|
|
738
|
+
}
|
|
739
|
+
catch (error) {
|
|
740
|
+
failed = true;
|
|
741
|
+
lines.push(`FAIL ${key.padEnd(24)} ${error.message}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
console.log(lines.join('\n'));
|
|
745
|
+
}
|
|
746
|
+
finally {
|
|
747
|
+
await pool.close();
|
|
748
|
+
}
|
|
749
|
+
process.exitCode = failed ? 1 : 0;
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
const spec = pickSpec(specs);
|
|
753
|
+
if (!spec.serve)
|
|
754
|
+
throw new Error(`deployment "${spec.name}" declares no serve block, so there is no datastore to verify`);
|
|
755
|
+
const tenant = opt('tenant');
|
|
756
|
+
if (spec.serve.tenants && !tenant)
|
|
757
|
+
throw new Error(`deployment "${spec.name}" has tenant databases; pass --tenant NAME or --all`);
|
|
758
|
+
if (!spec.serve.tenants && tenant)
|
|
759
|
+
throw new Error(`deployment "${spec.name}" serves a single datastore; drop --tenant`);
|
|
760
|
+
const config = readConfig(process.env, {
|
|
761
|
+
sqliteFile: `.ket/${spec.name}.db`,
|
|
762
|
+
...spec.serve.defaults,
|
|
763
|
+
});
|
|
764
|
+
if (!spec.serve.tenants &&
|
|
765
|
+
!spec.serve.openStore &&
|
|
766
|
+
!config.databaseUrl &&
|
|
767
|
+
config.sqliteFile !== ':memory:' &&
|
|
768
|
+
!existsSync(config.sqliteFile))
|
|
769
|
+
throw new Error(`SQLite datastore "${config.sqliteFile}" does not exist; schema verification will not create it`);
|
|
770
|
+
if (spec.serve.tenants) {
|
|
771
|
+
const tenantExists = spec.serve.tenants.exists;
|
|
772
|
+
if (!tenantExists)
|
|
773
|
+
throw new Error(`deployment "${spec.name}" must define serve.tenants.exists(key, config) before schema verification can inspect tenant datastores without creating them`);
|
|
774
|
+
if (!(await tenantExists(tenant, config)))
|
|
775
|
+
throw new Error(`tenant datastore "${tenant}" does not exist; schema verification will not create it`);
|
|
776
|
+
}
|
|
777
|
+
const adapter = spec.serve.tenants
|
|
778
|
+
? await spec.serve.tenants.open(tenant, config)
|
|
779
|
+
: await (spec.serve.openStore ?? sqliteStore)(config);
|
|
780
|
+
if (spec.serve.tenants)
|
|
781
|
+
await adapter.open();
|
|
782
|
+
try {
|
|
783
|
+
const report = await verifyPhysicalSchema(adapter, ws.deployments[spec.name]);
|
|
784
|
+
console.log(formatSchemaVerification(tenant ?? spec.name, report));
|
|
785
|
+
process.exitCode = report.ok ? 0 : 1;
|
|
786
|
+
}
|
|
787
|
+
finally {
|
|
788
|
+
await adapter.close();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
647
792
|
else if (cmd === 'migrate') {
|
|
648
|
-
const spec = pickSpec(specs);
|
|
649
793
|
if (flag('all')) {
|
|
650
794
|
// The fleet. A deployment that ships a new module has to reach every tenant
|
|
651
795
|
// database, and one that cannot be opened must not stop the others — a
|
|
652
796
|
// half-migrated fleet you cannot see is worse than one you can.
|
|
797
|
+
const spec = pickFleetSpec(specs);
|
|
653
798
|
const tenants = spec.serve?.tenants;
|
|
654
799
|
if (!tenants)
|
|
655
|
-
throw new Error(`
|
|
800
|
+
throw new Error(`deployment "${spec.name}" serves a single datastore; drop --all`);
|
|
656
801
|
const config = readConfig(process.env, spec.serve?.defaults ?? {});
|
|
657
802
|
const pool = createAdapterPool({ create: (key) => tenants.open(key, config) });
|
|
658
803
|
try {
|
|
659
804
|
const keys = await tenants.list();
|
|
660
|
-
const m = ws.
|
|
805
|
+
const m = ws.deployments[spec.name];
|
|
661
806
|
const results = await migrateFleet(pool, keys, m, {
|
|
662
807
|
allowDestructive: flag('allow-destructive'),
|
|
663
808
|
dryRun: flag('dry-run'),
|
|
@@ -669,7 +814,7 @@ try {
|
|
|
669
814
|
await pool.close();
|
|
670
815
|
}
|
|
671
816
|
}
|
|
672
|
-
const [name, m] =
|
|
817
|
+
const [name, m] = pickDeployment(ws);
|
|
673
818
|
const adapter = sqliteAdapter();
|
|
674
819
|
adapter.open();
|
|
675
820
|
const snapPath = `.ket/schema.${name}.json`;
|
|
@@ -678,8 +823,12 @@ try {
|
|
|
678
823
|
const ops = planMigration(prev, next, { allowDestructive: flag('allow-destructive') });
|
|
679
824
|
for (const sql of renderSql(ops, adapter))
|
|
680
825
|
console.log(sql + ';');
|
|
681
|
-
|
|
682
|
-
|
|
826
|
+
if (flag('dry-run'))
|
|
827
|
+
console.log(`\n-- dry run: ${ops.length} operation(s); schema snapshot unchanged`);
|
|
828
|
+
else {
|
|
829
|
+
writeFileSync(snapPath, JSON.stringify(next, null, 2));
|
|
830
|
+
console.log(`\n-- ${ops.length} operation(s); schema snapshot written to ${snapPath}`);
|
|
831
|
+
}
|
|
683
832
|
adapter.close();
|
|
684
833
|
}
|
|
685
834
|
else {
|