@auersperg/flare 0.0.0-stage → 0.3.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.
@@ -0,0 +1,379 @@
1
+ // @bun
2
+ import {
3
+ bundle
4
+ } from "./chunk-xff4b01y.js";
5
+
6
+ // packages/flare/src/cli/build.mjs
7
+ import { compile } from "json-schema-to-typescript";
8
+
9
+ // packages/flare/src/sdk/server.ts
10
+ import { z } from "zod";
11
+
12
+ // packages/flare/src/sdk/protocol.ts
13
+ var LIMITS = Object.freeze({
14
+ inputBytes: 65536,
15
+ outputBytes: 262144,
16
+ frameBytes: 8192,
17
+ subscriptions: 64,
18
+ fileBytes: 10485760
19
+ });
20
+
21
+ class FlareError extends Error {
22
+ code;
23
+ ambiguous;
24
+ constructor(code, message, ambiguous = false) {
25
+ super(message);
26
+ this.code = code;
27
+ this.ambiguous = ambiguous;
28
+ this.name = "FlareError";
29
+ }
30
+ }
31
+ function assertPath(path) {
32
+ if (path.length > 256 || !/^[A-Za-z][A-Za-z0-9_]*(\/[A-Za-z][A-Za-z0-9_]*)*$/.test(path) || path.split("/").some((p) => ["__proto__", "prototype", "constructor"].includes(p))) {
33
+ throw new FlareError("INVALID_PATH", "Invalid function path");
34
+ }
35
+ }
36
+ function stableJson(value) {
37
+ const ancestors = new Set;
38
+ function visit(v, depth) {
39
+ if (depth > 40)
40
+ throw new FlareError("INVALID_JSON", "JSON nesting limit exceeded");
41
+ if (v === null || typeof v === "string" || typeof v === "boolean")
42
+ return v;
43
+ if (typeof v === "number" && Number.isFinite(v))
44
+ return v;
45
+ if (typeof v !== "object" || v === null || ancestors.has(v))
46
+ throw new FlareError("INVALID_JSON", "Expected lossless JSON");
47
+ if (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null) {
48
+ throw new FlareError("INVALID_JSON", "Expected plain JSON object");
49
+ }
50
+ if (Object.getOwnPropertySymbols(v).length)
51
+ throw new FlareError("INVALID_JSON", "Symbol keys are unsupported");
52
+ ancestors.add(v);
53
+ let result;
54
+ if (Array.isArray(v)) {
55
+ result = Array.from(v, (item) => visit(item, depth + 1));
56
+ } else {
57
+ const out = Object.create(null);
58
+ for (const key of Object.keys(v).sort()) {
59
+ const descriptor = Object.getOwnPropertyDescriptor(v, key);
60
+ if (!("value" in descriptor))
61
+ throw new FlareError("INVALID_JSON", "Getters are unsupported");
62
+ out[key] = visit(descriptor.value, depth + 1);
63
+ }
64
+ result = out;
65
+ }
66
+ ancestors.delete(v);
67
+ return result;
68
+ }
69
+ return JSON.stringify(visit(value, 0));
70
+ }
71
+ function boundedJson(value, maxBytes) {
72
+ const json = stableJson(value);
73
+ if (new TextEncoder().encode(json).byteLength > maxBytes)
74
+ throw new FlareError("TOO_LARGE", "JSON payload exceeds limit");
75
+ return json;
76
+ }
77
+
78
+ // packages/flare/src/sdk/schema.ts
79
+ function identifier(name) {
80
+ if (!/^[a-zA-Z][a-zA-Z0-9_]{0,47}$/.test(name) || name.toLowerCase().startsWith("sqlite_"))
81
+ throw new FlareError("INVALID_SCHEMA", "Invalid table, index or field name");
82
+ return name;
83
+ }
84
+ function schemaContract(schema) {
85
+ const out = Object.create(null);
86
+ if (Object.keys(schema).length > 100)
87
+ throw new FlareError("INVALID_SCHEMA", "Maximum 100 tables");
88
+ for (const [name, table] of Object.entries(schema)) {
89
+ identifier(name);
90
+ const document = table.validator["~standard"].jsonSchema.output({
91
+ target: "draft-2020-12"
92
+ });
93
+ out[name] = { document, indexes: table.indexes };
94
+ }
95
+ assertSchemaContract(out);
96
+ return out;
97
+ }
98
+ function assertSchemaContract(contract) {
99
+ boundedJson(contract, 1048576);
100
+ if (!contract || Array.isArray(contract) || typeof contract !== "object" || Object.keys(contract).length > 100)
101
+ throw new FlareError("INVALID_SCHEMA", "Invalid schema");
102
+ const names = new Set;
103
+ for (const [name, table] of Object.entries(contract)) {
104
+ if (names.has(name.toLowerCase()))
105
+ throw new FlareError("INVALID_SCHEMA", "SQLite table names are case-insensitive");
106
+ names.add(name.toLowerCase());
107
+ identifier(name);
108
+ const doc = table?.document;
109
+ if (!doc || doc.type !== "object" || !doc.properties || doc.additionalProperties !== false)
110
+ throw new FlareError("INVALID_SCHEMA", "Tables require a closed object validator");
111
+ for (const field of Object.keys(doc.properties))
112
+ identifier(field);
113
+ if (Object.keys(doc.properties).length > 64 || !table.indexes || Object.keys(table.indexes).length > 20)
114
+ throw new FlareError("INVALID_SCHEMA", "Schema exceeds limits");
115
+ const indexes = new Set;
116
+ for (const [index, fields] of Object.entries(table.indexes)) {
117
+ if (indexes.has(index.toLowerCase()))
118
+ throw new FlareError("INVALID_SCHEMA", "SQLite index names are case-insensitive");
119
+ indexes.add(index.toLowerCase());
120
+ identifier(index);
121
+ if (!Array.isArray(fields) || fields.length < 1 || fields.length > 4 || new Set(fields).size !== fields.length)
122
+ throw new FlareError("INVALID_SCHEMA", "Invalid index fields");
123
+ for (const field of fields) {
124
+ identifier(field);
125
+ const p = doc.properties[field];
126
+ const scalar = (s) => Array.isArray(s?.type) && s.type.every((type) => ["string", "number", "integer", "boolean", "null"].includes(type)) || ["string", "number", "integer", "boolean", "null"].includes(s?.type) || Array.isArray(s?.anyOf) && s.anyOf.every(scalar);
127
+ if (!p || !scalar(p))
128
+ throw new FlareError("INVALID_SCHEMA", "Indexes require declared scalar fields");
129
+ }
130
+ }
131
+ }
132
+ }
133
+ var documents = new WeakMap;
134
+ // packages/flare/src/sdk/server.ts
135
+ function declare(kind) {
136
+ return (options) => Object.freeze({ ...options, kind, auth: options.auth ?? "required" });
137
+ }
138
+ var query = declare("query");
139
+ var mutation = declare("mutation");
140
+ var action = declare("action");
141
+ function flatten(tree) {
142
+ const out = Object.create(null);
143
+ const ancestors = new Set;
144
+ function walk(node, prefix, depth) {
145
+ if (depth > 20 || ancestors.has(node))
146
+ throw new FlareError("INVALID_CONTRACT", "Invalid function tree");
147
+ ancestors.add(node);
148
+ for (const [name, value] of Object.entries(node)) {
149
+ const path = prefix ? `${prefix}/${name}` : name;
150
+ assertPath(path);
151
+ if (value && typeof value === "object" && "handler" in value && typeof value.handler === "function") {
152
+ const definition = value;
153
+ if (!["query", "mutation", "action"].includes(definition.kind))
154
+ throw new FlareError("INVALID_CONTRACT", "Invalid function kind");
155
+ if (out[path])
156
+ throw new FlareError("INVALID_CONTRACT", "Duplicate function path");
157
+ out[path] = definition;
158
+ } else if (value && typeof value === "object")
159
+ walk(value, path, depth + 1);
160
+ else
161
+ throw new FlareError("INVALID_CONTRACT", "Invalid function definition");
162
+ }
163
+ ancestors.delete(node);
164
+ }
165
+ walk(tree, "", 0);
166
+ return out;
167
+ }
168
+ function manifest(app) {
169
+ const functions = Object.create(null);
170
+ for (const [path, fn] of Object.entries(flatten(app.functions))) {
171
+ const input = fn.input["~standard"].jsonSchema?.input({
172
+ target: "draft-2020-12"
173
+ });
174
+ const output = fn.output["~standard"].jsonSchema?.output({
175
+ target: "draft-2020-12"
176
+ });
177
+ if (!input || !output)
178
+ throw new FlareError("INVALID_CONTRACT", `${path} requires JSON Schema converters`);
179
+ functions[path] = { kind: fn.kind, auth: fn.auth, input, output };
180
+ }
181
+ boundedJson(functions, 1048576);
182
+ return { protocol: 1, runtimeAbi: 4, functions };
183
+ }
184
+
185
+ // packages/flare/src/cli/build.mjs
186
+ import { createHash } from "crypto";
187
+ import {
188
+ access,
189
+ mkdir,
190
+ mkdtemp,
191
+ readFile,
192
+ rename,
193
+ rm,
194
+ writeFile
195
+ } from "fs/promises";
196
+ import { dirname, join, resolve } from "path";
197
+ import { pathToFileURL } from "url";
198
+ var digest = (data) => createHash("sha256").update(data).digest("hex");
199
+ async function buildApplication(options) {
200
+ const entry = resolve(options.entry), outdir = resolve(options.outdir), working = dirname(entry);
201
+ try {
202
+ await access(outdir);
203
+ throw new Error("Output directory already exists; builds never overwrite an existing release");
204
+ } catch (error) {
205
+ if (error.code !== "ENOENT")
206
+ throw error;
207
+ }
208
+ await access(entry);
209
+ await mkdir(join(working, ".flare/tmp"), { recursive: true });
210
+ const temporary = await mkdtemp(join(working, ".flare/tmp/build-"));
211
+ try {
212
+ const metadataPath = join(temporary, "metadata.mjs");
213
+ await bundle({
214
+ entrypoints: [entry],
215
+ outdir: temporary,
216
+ naming: { entry: "metadata.mjs" },
217
+ packages: "external",
218
+ target: "bun",
219
+ format: "esm"
220
+ });
221
+ const module = await import(pathToFileURL(metadataPath).href);
222
+ if (!module.default)
223
+ throw new Error("Application entry must default-export defineApp(...)");
224
+ const contract = manifest(module.default);
225
+ const schema = schemaContract(module.default.schema);
226
+ const facet = await compileRole("facet", entry, working);
227
+ const action = await compileRole("action", options.actionEntry ? resolve(options.actionEntry) : entry, working);
228
+ const checksums = {
229
+ facet: digest(facet),
230
+ action: digest(action),
231
+ schema: digest(stableJson(schema)),
232
+ manifest: digest(stableJson(contract))
233
+ };
234
+ const artifact = {
235
+ protocol: 1,
236
+ compatibilityDate: "2026-10-10",
237
+ checksums
238
+ };
239
+ const hash = digest(stableJson(artifact));
240
+ const staging = join(temporary, "release");
241
+ await mkdir(staging);
242
+ await writeFile(join(staging, "facet.js"), facet);
243
+ await writeFile(join(staging, "action.js"), action);
244
+ await writeFile(join(staging, "manifest.json"), JSON.stringify(contract, null, 2));
245
+ await writeFile(join(staging, "schema.json"), JSON.stringify(schema, null, 2));
246
+ await writeFile(join(staging, "release.json"), JSON.stringify({ ...artifact, hash }, null, 2));
247
+ await writeFile(join(staging, "api.ts"), await generateReferences(contract));
248
+ const calls = Object.entries(contract.functions).filter(([, fn]) => fn.auth !== "internal");
249
+ const schemas = Object.create(null);
250
+ const refSchema = (schema, name) => {
251
+ const rewrite = (value) => {
252
+ if (Array.isArray(value))
253
+ return value.map(rewrite);
254
+ if (!value || typeof value !== "object")
255
+ return value;
256
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
257
+ key,
258
+ key === "$ref" && typeof item === "string" && item.startsWith("#/") ? `#/components/schemas/${name}/${item.slice(2)}` : rewrite(item)
259
+ ]));
260
+ };
261
+ schemas[name] = rewrite(schema);
262
+ return { $ref: `#/components/schemas/${name}` };
263
+ };
264
+ const callSchemas = calls.map(([path, fn], index) => ({
265
+ path,
266
+ fn,
267
+ input: refSchema(fn.input, `Input${index}`),
268
+ output: refSchema(fn.output, `Output${index}`)
269
+ }));
270
+ const openapi = {
271
+ openapi: "3.1.0",
272
+ info: { title: "Flare application contract", version: hash },
273
+ components: {
274
+ schemas,
275
+ securitySchemes: {
276
+ bearer: { type: "http", scheme: "bearer", bearerFormat: "JWT" }
277
+ }
278
+ },
279
+ paths: calls.length ? {
280
+ "/v1/call": {
281
+ post: {
282
+ operationId: "callFunction",
283
+ security: calls.some(([, fn]) => fn.auth === "public") ? [{}, { bearer: [] }] : [{ bearer: [] }],
284
+ requestBody: {
285
+ required: true,
286
+ content: {
287
+ "application/json": {
288
+ schema: {
289
+ oneOf: callSchemas.map(({ path, fn, input }) => ({
290
+ type: "object",
291
+ additionalProperties: false,
292
+ required: ["path", "kind", "args"],
293
+ properties: {
294
+ path: { const: path },
295
+ kind: { const: fn.kind },
296
+ args: input
297
+ }
298
+ }))
299
+ }
300
+ }
301
+ }
302
+ },
303
+ responses: {
304
+ 200: {
305
+ description: "Validated function result",
306
+ content: {
307
+ "application/json": {
308
+ schema: {
309
+ type: "object",
310
+ required: ["result"],
311
+ properties: {
312
+ result: {
313
+ anyOf: callSchemas.map(({ output }) => output)
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ },
320
+ 401: { description: "Unauthorized" },
321
+ 500: {
322
+ description: "Execution failed; write outcome may be ambiguous"
323
+ }
324
+ }
325
+ }
326
+ }
327
+ } : {}
328
+ };
329
+ await writeFile(join(staging, "openapi.json"), JSON.stringify(openapi, null, 2));
330
+ await mkdir(dirname(outdir), { recursive: true });
331
+ await rename(staging, outdir);
332
+ return { hash, outdir, checksums };
333
+ } finally {
334
+ await rm(temporary, { recursive: true, force: true });
335
+ }
336
+ }
337
+ async function compileRole(role, entry, working) {
338
+ const code = role === "facet" ? `import app from ${JSON.stringify(entry)}; import {createFacet} from "@auersperg/flare/cloudflare"; export class AppFacet extends createFacet(app) {}` : `import app from ${JSON.stringify(entry)}; import {createAction} from "@auersperg/flare/cloudflare"; export class Action extends createAction(app) {}`;
339
+ const virtualEntry = join(working, `${role}-entry.ts`);
340
+ const result = await bundle({
341
+ entrypoints: [virtualEntry],
342
+ files: { [virtualEntry]: code },
343
+ external: ["cloudflare:workers"],
344
+ metafile: true
345
+ });
346
+ if (Object.keys(result.metafile.inputs).some((path) => /modelcontextprotocol|agents\//.test(path)))
347
+ throw new Error("Full Agents/MCP code entered application bundle");
348
+ if (result.outputs.length !== 1 || result.outputs[0].kind !== "entry-point")
349
+ throw new Error("Application runtime must compile to one JavaScript module");
350
+ const output = await result.outputs[0].text();
351
+ if (Buffer.byteLength(output) > 1048576)
352
+ throw new Error("Application bundle exceeds the preview 1 MiB limit");
353
+ return output;
354
+ }
355
+ async function generateReferences(contract) {
356
+ let generated = `import {reference} from "@auersperg/flare";
357
+ `;
358
+ const refs = [];
359
+ for (const [index, [path, fn]] of Object.entries(contract.functions).entries()) {
360
+ generated += await compile(fn.input, `Input${index}`, {
361
+ bannerComment: ""
362
+ });
363
+ generated += await compile(fn.output, `Output${index}`, {
364
+ bannerComment: ""
365
+ });
366
+ refs.push(`${JSON.stringify(path)}: reference<Input${index}, Output${index}>(${JSON.stringify(path)}, ${JSON.stringify(fn.kind)})`);
367
+ }
368
+ generated += `
369
+ export const api = {
370
+ ${refs.join(`,
371
+ `)}
372
+ } as const;
373
+ `;
374
+ return generated;
375
+ }
376
+ export {
377
+ buildApplication,
378
+ generateReferences
379
+ };
@@ -0,0 +1,113 @@
1
+ // @bun
2
+ // packages/flare/src/cli/login.mjs
3
+ import { readFile, mkdir, writeFile } from "fs/promises";
4
+ import { join } from "path";
5
+ import { homedir } from "os";
6
+ import {
7
+ randomBytes,
8
+ createHash,
9
+ generateKeyPairSync,
10
+ privateDecrypt,
11
+ constants
12
+ } from "crypto";
13
+ import { createInterface } from "readline/promises";
14
+ import { spawn } from "child_process";
15
+ import { setTimeout as sleep } from "timers/promises";
16
+ var credentialDirectory = () => join(process.env.FLARE_CONFIG_HOME ?? join(homedir(), ".config", "flare"));
17
+ async function storedToken(platform, project) {
18
+ const key = createHash("sha256").update(platform + "/" + project).digest("hex");
19
+ const record = JSON.parse(await readFile(join(credentialDirectory(), key + ".json"), "utf8").catch((e) => {
20
+ if (e.code !== "ENOENT")
21
+ throw e;
22
+ return "null";
23
+ }));
24
+ if (record && record.expiresAt > Date.now())
25
+ return record.token;
26
+ return null;
27
+ }
28
+ async function connect() {
29
+ if (!process.stdin.isTTY)
30
+ throw Error("First run needs an interactive terminal: run flare dev to sign in. CI can use FLARE_DEPLOY_TOKEN with a linked project.");
31
+ let dashboard, platform;
32
+ const existing = JSON.parse(await readFile(".flare/project.json", "utf8").catch((e) => {
33
+ if (e.code !== "ENOENT")
34
+ throw e;
35
+ return "null";
36
+ }));
37
+ if (existing)
38
+ platform = new URL(new URL(existing.platform).origin);
39
+ else {
40
+ const prompt = createInterface({
41
+ input: process.stdin,
42
+ output: process.stdout
43
+ });
44
+ try {
45
+ platform = new URL(await prompt.question("Runtime URL (shown in dashboard Health): "));
46
+ } finally {
47
+ prompt.close();
48
+ }
49
+ }
50
+ if (platform.protocol !== "https:" || platform.username || platform.password || platform.search || platform.hash || platform.pathname !== "/")
51
+ throw Error("Enter a plain HTTPS origin without a path or credentials.");
52
+ platform = platform.origin + "/control";
53
+ const secret = randomBytes(32).toString("hex"), secretHash = createHash("sha256").update(secret).digest("hex");
54
+ const { publicKey, privateKey } = generateKeyPairSync("rsa", {
55
+ modulusLength: 2048
56
+ });
57
+ async function post(path, body) {
58
+ const response = await fetch(platform + "/api/cli/" + path, {
59
+ method: "POST",
60
+ headers: { "content-type": "application/json" },
61
+ body: JSON.stringify(body),
62
+ redirect: "error",
63
+ signal: AbortSignal.timeout(15000)
64
+ });
65
+ const data = await response.json();
66
+ if (!response.ok)
67
+ throw Error(`CLI login ${data.error ?? response.status}. The platform must have the CLI login update installed.`);
68
+ return data;
69
+ }
70
+ const session = await post("start", {
71
+ secretHash,
72
+ publicKey: publicKey.export({ format: "jwk" })
73
+ });
74
+ dashboard = new URL(session.dashboard);
75
+ if (dashboard.protocol !== "https:" || dashboard.username || dashboard.password || dashboard.pathname !== "/" || dashboard.search || dashboard.hash)
76
+ throw Error("Invalid dashboard URL returned by installation");
77
+ const url = dashboard.origin + "/?cli=" + encodeURIComponent(session.id);
78
+ console.log(`Confirm code ${session.code} in your dashboard:
79
+ ${url}`);
80
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? null : "xdg-open";
81
+ if (opener) {
82
+ const child = spawn(opener, [url], { stdio: "ignore" });
83
+ child.on("error", () => {});
84
+ child.unref();
85
+ }
86
+ const end = Date.now() + Math.min(session.expiresIn, 600) * 1000;
87
+ while (Date.now() < end) {
88
+ await sleep(3000);
89
+ const result = await post("poll", { id: session.id, secret });
90
+ if (result.pending)
91
+ continue;
92
+ if (result.platform !== platform || !/^[a-f0-9-]{36}$/.test(result.project))
93
+ throw Error("Unexpected platform/project in login response");
94
+ const token = privateDecrypt({
95
+ key: privateKey,
96
+ oaepHash: "sha256",
97
+ padding: constants.RSA_PKCS1_OAEP_PADDING
98
+ }, Buffer.from(result.encrypted, "base64")).toString();
99
+ const directory = credentialDirectory();
100
+ await mkdir(directory, { recursive: true, mode: 448 });
101
+ const key = createHash("sha256").update(platform + "/" + result.project).digest("hex");
102
+ await writeFile(join(directory, key + ".json"), JSON.stringify({ token, expiresAt: result.expiresAt }) + `
103
+ `, { mode: 384 });
104
+ process.env.FLARE_DEPLOY_TOKEN = token;
105
+ const { link } = await import("./chunk-5cq8wgte.js");
106
+ return link({ platform, project: result.project });
107
+ }
108
+ throw Error("Login expired. Run flare dev again.");
109
+ }
110
+ export {
111
+ connect,
112
+ storedToken
113
+ };
@@ -0,0 +1,18 @@
1
+ // @bun
2
+ // packages/flare/src/cli/bundle.mjs
3
+ async function bundle(options) {
4
+ const result = await Bun.build({
5
+ format: "esm",
6
+ target: "browser",
7
+ env: "disable",
8
+ metafile: true,
9
+ ...options
10
+ });
11
+ if (!result.success)
12
+ throw new AggregateError(result.logs, "Build failed");
13
+ if (!result.metafile)
14
+ throw new Error("Compiler dependency graph is missing");
15
+ return result;
16
+ }
17
+
18
+ export { bundle };
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // packages/flare/src/cli/main.mjs
5
+ if (typeof Bun === "undefined" || !Bun.semver.satisfies(Bun.version, ">=1.4.3")) {
6
+ console.error("Flare CLI requires Bun 1.4.3 or newer.");
7
+ process.exit(1);
8
+ }
9
+ var [command, ...args] = process.argv.slice(2);
10
+ if (!command || ["--help", "help"].includes(command))
11
+ console.log(`Flare 0.3 private minimum
12
+
13
+ flare dev Browser login, setup and cloud dev watch
14
+ flare deploy Confirmed production build and deploy
15
+
16
+ Advanced commands:
17
+ flare releases --env dev|prod
18
+ flare promote --hash <dev release hash> [--cmd "bun run build"]
19
+ flare rollback --hash <retained release hash> --env dev|prod
20
+ flare build --entry <app.ts> --outdir <new directory>
21
+ flare link --platform <management URL> --project <ID>
22
+ flare deploy --env dev|prod --release <directory> [--confirm <project ID>]
23
+ flare dev --entry <app.ts>
24
+ flare backup create|list|restore|delete|status --env dev|prod
25
+ restore --from dev|prod --id <backup ID> (empty target only)
26
+
27
+ Dashboard is Cloudflare-only. Local tooling does not run a dashboard or backend. CLI authentication uses FLARE_DEPLOY_TOKEN from the online dashboard. Run flare dev with no flags for browser login, project selection and automatic schema/API generation. Run flare deploy with no flags for a confirmed production release. Platform installation is separate.`);
28
+ else if ([
29
+ "build",
30
+ "link",
31
+ "deploy",
32
+ "dev",
33
+ "backup",
34
+ "promote",
35
+ "rollback",
36
+ "releases"
37
+ ].includes(command)) {
38
+ try {
39
+ const options = {};
40
+ if (command === "backup")
41
+ options.operation = args.shift() ?? "list";
42
+ const permitted = {
43
+ build: ["entry", "outdir"],
44
+ link: ["platform", "project"],
45
+ deploy: ["env", "release", "confirm", "cmd"],
46
+ promote: ["env", "hash", "confirm", "cmd"],
47
+ rollback: ["env", "hash", "confirm", "cmd"],
48
+ releases: ["env"],
49
+ dev: ["entry", "env"],
50
+ backup: ["env", "id", "from", "confirm", "cursor"]
51
+ }[command];
52
+ for (let i = 0;i < args.length; i += 2) {
53
+ const key = args[i]?.replace(/^--/, "");
54
+ if (!args[i]?.startsWith("--") || !permitted.includes(key) || !args[i + 1] || args[i + 1].startsWith("--") || key in options)
55
+ throw Error("Expected supported unique named options");
56
+ options[key] = args[i + 1];
57
+ }
58
+ if (command === "build") {
59
+ if (!options.entry || !options.outdir)
60
+ throw Error("Required: --entry, --outdir");
61
+ const { buildApplication } = await import("./chunk-vx7kmczm.js");
62
+ const x = await buildApplication(options);
63
+ console.log(`Built immutable release ${x.hash}
64
+ ${x.outdir}`);
65
+ } else {
66
+ const cloud = await import("./chunk-5cq8wgte.js");
67
+ const x = await cloud[command](options);
68
+ if (x)
69
+ console.log(JSON.stringify(x, null, 2));
70
+ }
71
+ } catch (e) {
72
+ console.error(e instanceof Error ? e.message : "Command failed");
73
+ process.exitCode = 1;
74
+ }
75
+ } else {
76
+ console.error(`Unsupported command: ${command}; no deployment has occurred.`);
77
+ process.exitCode = 1;
78
+ }
@@ -0,0 +1,22 @@
1
+ import { type Json } from "./protocol.js";
2
+ import { type NativeStorage, type Schema, type SchemaContract } from "./schema.js";
3
+ export declare const SNAPSHOT_BYTES: number;
4
+ export declare const SNAPSHOT_ROWS = 10000;
5
+ export type DataSnapshot = {
6
+ format: 1;
7
+ schema: SchemaContract;
8
+ revision: number;
9
+ tables: Record<string, {
10
+ _id: string;
11
+ _creationTime: number;
12
+ value: Record<string, Json>;
13
+ }[]>;
14
+ };
15
+ export declare function emptyData(storage: NativeStorage, schema: Schema): boolean;
16
+ /** Caller holds the facet's serial gate; all pages belong to one revision. */
17
+ export declare function exportData(storage: NativeStorage, schema: Schema): DataSnapshot;
18
+ /** Native transaction preserves IDs, references and timestamps; never merges into existing data. */
19
+ export declare function restoreData(storage: NativeStorage, schema: Schema, id: string, snapshot: DataSnapshot): Promise<{
20
+ restored: boolean;
21
+ revision: number;
22
+ }>;