@auersperg/flare 0.3.9 → 0.3.11
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/README.md +3 -1
- package/dist/cli/{chunk-0s0vrjtw.js → chunk-1yvbecac.js} +97 -11
- package/dist/cli/{chunk-5yg1zj5n.js → chunk-7ym0f672.js} +6 -6
- package/dist/cli/{chunk-5s6zv1h9.js → chunk-8gzfscca.js} +5 -7
- package/dist/cli/chunk-xn3e3gev.js +9 -0
- package/dist/cli/{chunk-vx7kmczm.js → chunk-yhvbksrm.js} +32 -7
- package/dist/cli/flare.js +5 -5
- package/dist/platform/admin.js +35 -24
- package/dist/platform/dashboard/_astro/ai-view.DxgwI4OB.js +1 -0
- package/dist/platform/dashboard/_astro/{auth-view.EASEF9BN.js → auth-view.C16WPVOx.js} +1 -1
- package/dist/platform/dashboard/_astro/{cli-approval.DwZVHdyo.js → cli-approval.emPTVRqn.js} +1 -1
- package/dist/platform/dashboard/_astro/client.4z54xJ_z.js +1 -0
- package/dist/platform/dashboard/_astro/{common.TAClb6G9.js → common.9pujiaSZ.js} +1 -1
- package/dist/platform/dashboard/_astro/{data-view.BaHEU4Xa.js → data-view.BCmdi_cs.js} +1 -1
- package/dist/platform/dashboard/_astro/{functions-view.BGOYuDwu.js → functions-view.Cf640vhX.js} +1 -1
- package/dist/platform/dashboard/_astro/index.CDrYoVkO.css +2 -0
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.cFeBPGtv.js +3 -0
- package/dist/platform/dashboard/_astro/input-group.Dsc4z0iT.js +1 -0
- package/dist/platform/dashboard/_astro/{sdk-view.2ErPn4cO.js → sdk-view.Cs96E0YV.js} +1 -1
- package/dist/platform/dashboard/_astro/{services-view.iZujitxl.js → services-view.DhwKE_RK.js} +1 -1
- package/dist/platform/dashboard/_astro/{settings-view.CqK-mrlB.js → settings-view.Dsc34AAW.js} +1 -1
- package/dist/platform/dashboard/bootstrap/action.js +43 -0
- package/dist/platform/dashboard/bootstrap/facet.js +64 -46
- package/dist/platform/dashboard/bootstrap/openapi.json +1 -1
- package/dist/platform/dashboard/bootstrap/release.json +3 -3
- package/dist/platform/dashboard/index.html +1 -1
- package/dist/platform/manifest.json +48 -43
- package/dist/platform/runtime.js +24267 -865
- package/dist/sdk/backup.js +4 -4
- package/dist/sdk/client.js +22 -41
- package/dist/sdk/cloudflare.d.ts +4 -0
- package/dist/sdk/cloudflare.js +14 -11
- package/dist/sdk/codex.d.ts +20 -0
- package/dist/sdk/codex.js +17 -0
- package/dist/sdk/database.js +45 -27
- package/dist/sdk/http.d.ts +2 -0
- package/dist/sdk/http.js +27 -0
- package/dist/sdk/protocol.d.ts +6 -0
- package/dist/sdk/protocol.js +12 -0
- package/dist/sdk/schema.d.ts +2 -2
- package/dist/sdk/schema.js +7 -6
- package/dist/sdk/search.d.ts +25 -0
- package/dist/sdk/search.js +22 -0
- package/dist/sdk/server.d.ts +14 -1
- package/dist/sdk/server.js +9 -1
- package/package.json +2 -2
- package/dist/platform/dashboard/_astro/client.BUUZgEFj.js +0 -1
- package/dist/platform/dashboard/_astro/index.BPQdjJ4j.css +0 -2
- package/dist/platform/dashboard/_astro/index.astro_astro_type_script_index_0_lang.Blg5hU9g.js +0 -3
- package/dist/platform/dashboard/_astro/input-group.DUp-h0xx.js +0 -1
package/README.md
CHANGED
|
@@ -127,4 +127,6 @@ Password reset/block/revoke disconnects affected live users. Live cache is clear
|
|
|
127
127
|
|
|
128
128
|
SDK and CLI are MIT-licensed; see LICENSE. npm name: `@auersperg/flare`.
|
|
129
129
|
The `flare` binary remains unchanged. Preview releases are published under the `next` tag. Core/dashboard are separate non-published
|
|
130
|
-
|
|
130
|
+
workspaces, not separately published npm packages. Their compiled, checksummed platform payload is included under `dist/platform` for CLI installation/update; browser SDK imports do not load it.
|
|
131
|
+
|
|
132
|
+
For new applications and coding agents, see the [application guide](https://github.com/auersperg/flare/blob/main/docs/build-an-app.md) and [app AGENTS.md template](https://github.com/auersperg/flare/blob/main/docs/templates/AGENTS.md). Match their verified SDK version to your pinned dependency.
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
storedToken2
|
|
4
|
+
} from "./chunk-8gzfscca.js";
|
|
5
|
+
|
|
2
6
|
// packages/flare/src/cli/platform.mjs
|
|
3
7
|
import { readFile as readFile4, mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, cp } from "fs/promises";
|
|
4
8
|
import { join as join3 } from "path";
|
|
@@ -22,14 +26,17 @@ var sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
|
22
26
|
var manifestDigest = ({ digest: _digest, ...manifest }) => sha256(JSON.stringify(manifest));
|
|
23
27
|
async function payloadFiles(directory, prefix = "") {
|
|
24
28
|
const files = [];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
const entries = await readdir(join(directory, prefix), {
|
|
30
|
+
withFileTypes: true
|
|
31
|
+
});
|
|
32
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
const path = prefix + entry.name;
|
|
35
|
+
if (entry.isSymbolicLink())
|
|
29
36
|
throw Error("Platform payload contains a symlink");
|
|
30
|
-
if (
|
|
37
|
+
if (entry.isDirectory())
|
|
31
38
|
files.push(...await payloadFiles(directory, path + "/"));
|
|
32
|
-
else if (
|
|
39
|
+
else if (entry.isFile())
|
|
33
40
|
files.push(path);
|
|
34
41
|
else
|
|
35
42
|
throw Error("Unsupported platform payload entry");
|
|
@@ -290,8 +297,8 @@ import { randomUUID } from "crypto";
|
|
|
290
297
|
import { readFile as readFile2 } from "fs/promises";
|
|
291
298
|
function validatePlatformConfig(value = {}) {
|
|
292
299
|
const object = (v) => v && typeof v === "object" && !Array.isArray(v);
|
|
293
|
-
if (!object(value) || Object.keys(value).some((k) => !["capabilities", "secrets"].includes(k)))
|
|
294
|
-
throw Error("Platform config accepts only capabilities and
|
|
300
|
+
if (!object(value) || Object.keys(value).some((k) => !["capabilities", "secrets", "search", "codex"].includes(k)))
|
|
301
|
+
throw Error("Platform config accepts only capabilities, secrets, search and codex references");
|
|
295
302
|
const capabilities = value.capabilities ?? {}, secrets = value.secrets ?? [];
|
|
296
303
|
if (!object(capabilities) || !Array.isArray(secrets) || secrets.length > 64)
|
|
297
304
|
throw Error("Invalid platform capabilities/secrets references");
|
|
@@ -306,11 +313,37 @@ function validatePlatformConfig(value = {}) {
|
|
|
306
313
|
secret_name: r.secret_name
|
|
307
314
|
};
|
|
308
315
|
}).sort((a, b) => a.binding.localeCompare(b.binding));
|
|
316
|
+
const search = value.search ?? [];
|
|
317
|
+
if (!Array.isArray(search) || search.length > 64)
|
|
318
|
+
throw Error("Invalid search references");
|
|
319
|
+
const searchNames = new Set, instances = new Set;
|
|
320
|
+
const searchReferences = search.map((r) => {
|
|
321
|
+
if (!object(r) || Object.keys(r).sort().join() !== "binding,instance_name,scope" || !/^APP_SEARCH_[A-Z0-9_]{1,64}$/.test(r.binding) || typeof r.instance_name !== "string" || r.instance_name.length > 64 || !/^[a-z0-9_]+(?:-[a-z0-9_]+)*$/.test(r.instance_name) || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\/(dev|prod)$/.test(r.scope) || searchNames.has(r.binding) || instances.has(r.instance_name))
|
|
322
|
+
throw Error("Invalid or duplicate native search reference");
|
|
323
|
+
searchNames.add(r.binding);
|
|
324
|
+
instances.add(r.instance_name);
|
|
325
|
+
return {
|
|
326
|
+
binding: r.binding,
|
|
327
|
+
instance_name: r.instance_name,
|
|
328
|
+
scope: r.scope
|
|
329
|
+
};
|
|
330
|
+
}).sort((a, b) => a.binding.localeCompare(b.binding));
|
|
331
|
+
const codex = value.codex ?? [];
|
|
332
|
+
if (!Array.isArray(codex) || codex.length > 16)
|
|
333
|
+
throw Error("Invalid Codex references");
|
|
334
|
+
const codexBindings = new Set, codexServices = new Set;
|
|
335
|
+
const codexReferences = codex.map((r) => {
|
|
336
|
+
if (!object(r) || Object.keys(r).sort().join() !== "binding,scope,service" || !/^APP_CODEX_[A-Z0-9_]{1,64}$/.test(r.binding) || typeof r.service !== "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(r.service) || typeof r.scope !== "string" || !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\/(dev|prod)$/.test(r.scope) || codexBindings.has(r.binding) || codexServices.has(r.service))
|
|
337
|
+
throw Error("Invalid or duplicate Codex runner reference");
|
|
338
|
+
codexBindings.add(r.binding);
|
|
339
|
+
codexServices.add(r.service);
|
|
340
|
+
return { binding: r.binding, service: r.service, scope: r.scope };
|
|
341
|
+
}).sort((a, b) => a.binding.localeCompare(b.binding));
|
|
309
342
|
const result = {};
|
|
310
343
|
const used = new Set;
|
|
311
344
|
for (const scope of Object.keys(capabilities).sort()) {
|
|
312
345
|
const p = capabilities[scope];
|
|
313
|
-
if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\/(dev|prod)$/.test(scope) || !object(p) || Object.keys(p).some((k) => !["outbound", "secrets"].includes(k)))
|
|
346
|
+
if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\/(dev|prod)$/.test(scope) || !object(p) || Object.keys(p).some((k) => !["outbound", "secrets", "search", "codex"].includes(k)))
|
|
314
347
|
throw Error("Invalid capability scope");
|
|
315
348
|
const hosts = p.outbound ?? [], mapping = p.secrets ?? {};
|
|
316
349
|
if (!Array.isArray(hosts) || hosts.length > 20 || !object(mapping) || Object.keys(mapping).length > 16)
|
|
@@ -323,7 +356,13 @@ function validatePlatformConfig(value = {}) {
|
|
|
323
356
|
throw Error("Secret grant must reference an explicit APP_SECRET binding");
|
|
324
357
|
used.add(binding);
|
|
325
358
|
}
|
|
359
|
+
if (p.search !== undefined && !searchReferences.some((r) => r.binding === p.search && r.scope === scope))
|
|
360
|
+
throw Error("Search grant must reference an instance owned by this environment");
|
|
361
|
+
if (p.codex !== undefined && !codexReferences.some((r) => r.binding === p.codex && r.scope === scope))
|
|
362
|
+
throw Error("Codex grant must reference a runner owned by this environment");
|
|
326
363
|
result[scope] = {
|
|
364
|
+
...p.codex === undefined ? {} : { codex: p.codex },
|
|
365
|
+
...p.search === undefined ? {} : { search: p.search },
|
|
327
366
|
outbound: [...new Set(hosts)].sort(),
|
|
328
367
|
secrets: Object.fromEntries(Object.entries(mapping).sort())
|
|
329
368
|
};
|
|
@@ -332,7 +371,12 @@ function validatePlatformConfig(value = {}) {
|
|
|
332
371
|
throw Error("Unused secret binding; grant it explicitly or remove it");
|
|
333
372
|
if (JSON.stringify(result).length > 16384)
|
|
334
373
|
throw Error("Capabilities exceed 16 KiB");
|
|
335
|
-
return {
|
|
374
|
+
return {
|
|
375
|
+
capabilities: result,
|
|
376
|
+
secrets: references,
|
|
377
|
+
...codexReferences.length ? { codex: codexReferences } : {},
|
|
378
|
+
...searchReferences.length ? { search: searchReferences } : {}
|
|
379
|
+
};
|
|
336
380
|
}
|
|
337
381
|
async function readPlatformConfig(path) {
|
|
338
382
|
const bytes = await readFile2(path);
|
|
@@ -358,6 +402,19 @@ async function accessSnapshot(cf, id) {
|
|
|
358
402
|
digest: configurationDigest(accessConfiguration(app, policies))
|
|
359
403
|
};
|
|
360
404
|
}
|
|
405
|
+
async function validateSearchTransition(previous, next, inspect) {
|
|
406
|
+
const old = validatePlatformConfig(previous), desired = validatePlatformConfig(next);
|
|
407
|
+
for (const ref of old.search ?? []) {
|
|
408
|
+
const retained = desired.search?.find((r) => r.binding === ref.binding);
|
|
409
|
+
if (retained && JSON.stringify(retained) === JSON.stringify(ref))
|
|
410
|
+
continue;
|
|
411
|
+
if (old.capabilities[ref.scope]?.search === ref.binding)
|
|
412
|
+
throw Error("Disable the search grant first, keeping its reference for durable cleanup");
|
|
413
|
+
const state = await inspect(ref.scope);
|
|
414
|
+
if (state?.search?.remaining !== 0)
|
|
415
|
+
throw Error("Search cleanup is pending; keep the old binding and retry after cleanup");
|
|
416
|
+
}
|
|
417
|
+
}
|
|
361
418
|
|
|
362
419
|
// packages/flare/src/cli/platform-state.mjs
|
|
363
420
|
var leaseMilliseconds = 15 * 60 * 1000;
|
|
@@ -828,7 +885,12 @@ function workerConfiguration(state, release, role, bootstrap = false) {
|
|
|
828
885
|
return {
|
|
829
886
|
...common,
|
|
830
887
|
worker_loaders: [{ binding: "LOADER" }],
|
|
831
|
-
...state.config ? {
|
|
888
|
+
...state.config ? {
|
|
889
|
+
secrets_store_secrets: validatePlatformConfig(state.config).secrets,
|
|
890
|
+
...validatePlatformConfig(state.config).search?.length ? {
|
|
891
|
+
ai_search: validatePlatformConfig(state.config).search.map(({ binding, instance_name }) => ({ binding, instance_name }))
|
|
892
|
+
} : {}
|
|
893
|
+
} : {},
|
|
832
894
|
durable_objects: {
|
|
833
895
|
bindings: [{ name: "SUPERVISORS", class_name: "EnvironmentSupervisor" }]
|
|
834
896
|
},
|
|
@@ -860,6 +922,11 @@ function workerConfiguration(state, release, role, bootstrap = false) {
|
|
|
860
922
|
}
|
|
861
923
|
],
|
|
862
924
|
services: bootstrap ? [] : [
|
|
925
|
+
...(validatePlatformConfig(state.config).codex ?? []).map(({ binding, service }) => ({
|
|
926
|
+
binding,
|
|
927
|
+
service,
|
|
928
|
+
entrypoint: "CodexService"
|
|
929
|
+
})),
|
|
863
930
|
{
|
|
864
931
|
binding: "ADMIN_API",
|
|
865
932
|
service: state.workers.admin.name,
|
|
@@ -1337,6 +1404,25 @@ async function runPlatform(options, dependencies = {}) {
|
|
|
1337
1404
|
const configChanged = requestedConfig && configurationDigest(requestedConfig) !== configurationDigest(validatePlatformConfig(state.config));
|
|
1338
1405
|
if (state.operation && requestedConfig && configurationDigest(requestedConfig) !== state.operation.configDigest)
|
|
1339
1406
|
throw Error("Pending config differs; resume with the original config or omit --config");
|
|
1407
|
+
if (configChanged && !state.operation)
|
|
1408
|
+
await validateSearchTransition(state.config, requestedConfig, async (scope) => {
|
|
1409
|
+
const [project, environment] = scope.split("/");
|
|
1410
|
+
const environments = await cf.sql(state.resources.catalog.id, "SELECT 1 FROM environments WHERE projectId=? AND env=?", [project, environment]);
|
|
1411
|
+
if (!environments.results.length)
|
|
1412
|
+
return { search: { remaining: 0 } };
|
|
1413
|
+
const platform = "https://" + state.addresses.runtimeHost + "/control";
|
|
1414
|
+
const token = process.env.FLARE_DEPLOY_TOKEN ?? await storedToken2(platform, project);
|
|
1415
|
+
if (!token)
|
|
1416
|
+
throw Error("Sign in to the affected project before removing a search binding");
|
|
1417
|
+
const response = await fetch(`${platform}/api/projects/${project}/${environment}/ai`, {
|
|
1418
|
+
headers: { authorization: `Bearer ${token}` },
|
|
1419
|
+
redirect: "error",
|
|
1420
|
+
signal: AbortSignal.timeout(1e4)
|
|
1421
|
+
});
|
|
1422
|
+
if (!response.ok)
|
|
1423
|
+
throw Error("Cannot verify search cleanup; old binding must be retained");
|
|
1424
|
+
return response.json();
|
|
1425
|
+
});
|
|
1340
1426
|
let accessChanged = false;
|
|
1341
1427
|
if (options["accept-access"]) {
|
|
1342
1428
|
if (!/^[a-f0-9]{64}$/.test(options["accept-access"]) || operation !== "update" || state.operation)
|
|
@@ -26,7 +26,7 @@ async function request(platform, path, body) {
|
|
|
26
26
|
return "null";
|
|
27
27
|
}));
|
|
28
28
|
if (config) {
|
|
29
|
-
const { storedToken } = await import("./chunk-
|
|
29
|
+
const { storedToken } = await import("./chunk-xn3e3gev.js");
|
|
30
30
|
token = await storedToken(platform, config.project);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -107,7 +107,7 @@ async function deploy(options = {}) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
const { prepare, generateApi } = await import("./chunk-fv98dfg5.js");
|
|
110
|
-
const { buildApplication } = await import("./chunk-
|
|
110
|
+
const { buildApplication } = await import("./chunk-yhvbksrm.js");
|
|
111
111
|
const source = await prepare(), directory = resolve(".flare/releases/" + randomUUID());
|
|
112
112
|
try {
|
|
113
113
|
await buildApplication({ ...source, outdir: directory });
|
|
@@ -160,7 +160,7 @@ async function dev(options = {}) {
|
|
|
160
160
|
} catch (e) {
|
|
161
161
|
if (e.code !== "ENOENT")
|
|
162
162
|
throw e;
|
|
163
|
-
const { connect } = await import("./chunk-
|
|
163
|
+
const { connect } = await import("./chunk-xn3e3gev.js");
|
|
164
164
|
await connect();
|
|
165
165
|
}
|
|
166
166
|
}
|
|
@@ -172,11 +172,11 @@ async function dev(options = {}) {
|
|
|
172
172
|
} catch (e) {
|
|
173
173
|
if (process.env.FLARE_DEPLOY_TOKEN || e.status !== 401 && !e.message.startsWith("Sign in"))
|
|
174
174
|
throw e;
|
|
175
|
-
const { connect } = await import("./chunk-
|
|
175
|
+
const { connect } = await import("./chunk-xn3e3gev.js");
|
|
176
176
|
await connect();
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
-
const { buildApplication } = await import("./chunk-
|
|
179
|
+
const { buildApplication } = await import("./chunk-yhvbksrm.js");
|
|
180
180
|
let busy = false, pending = false, timer, hash;
|
|
181
181
|
const publish = async (initial = false) => {
|
|
182
182
|
if (busy) {
|
|
@@ -308,7 +308,7 @@ async function releaseOperation(options, operation) {
|
|
|
308
308
|
const bundle = await request(config.platform, `/api/projects/${config.project}/${source}/release-bundle?hash=${options.hash}`);
|
|
309
309
|
if (bundle.release.hash !== options.hash)
|
|
310
310
|
throw Error("Release hash mismatch");
|
|
311
|
-
const { generateReferences } = await import("./chunk-
|
|
311
|
+
const { generateReferences } = await import("./chunk-yhvbksrm.js");
|
|
312
312
|
const { withGeneratedApi } = await import("./chunk-fv98dfg5.js");
|
|
313
313
|
await mkdir(".flare", { recursive: true });
|
|
314
314
|
const directory = await mkdtemp(resolve(".flare/release-contract-"));
|
|
@@ -14,7 +14,7 @@ import { createInterface } from "readline/promises";
|
|
|
14
14
|
import { spawn } from "child_process";
|
|
15
15
|
import { setTimeout as sleep } from "timers/promises";
|
|
16
16
|
var credentialDirectory = () => join(process.env.FLARE_CONFIG_HOME ?? join(homedir(), ".config", "flare"));
|
|
17
|
-
async function
|
|
17
|
+
async function storedToken2(platform, project) {
|
|
18
18
|
const key = createHash("sha256").update(platform + "/" + project).digest("hex");
|
|
19
19
|
const record = JSON.parse(await readFile(join(credentialDirectory(), key + ".json"), "utf8").catch((e) => {
|
|
20
20
|
if (e.code !== "ENOENT")
|
|
@@ -25,7 +25,7 @@ async function storedToken(platform, project) {
|
|
|
25
25
|
return record.token;
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
|
-
async function
|
|
28
|
+
async function connect2() {
|
|
29
29
|
if (!process.stdin.isTTY)
|
|
30
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
31
|
let dashboard, platform;
|
|
@@ -102,12 +102,10 @@ ${url}`);
|
|
|
102
102
|
await writeFile(join(directory, key + ".json"), JSON.stringify({ token, expiresAt: result.expiresAt }) + `
|
|
103
103
|
`, { mode: 384 });
|
|
104
104
|
process.env.FLARE_DEPLOY_TOKEN = token;
|
|
105
|
-
const { link } = await import("./chunk-
|
|
105
|
+
const { link } = await import("./chunk-7ym0f672.js");
|
|
106
106
|
return link({ platform, project: result.project });
|
|
107
107
|
}
|
|
108
108
|
throw Error("Login expired. Run flare dev again.");
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
storedToken
|
|
113
|
-
};
|
|
110
|
+
|
|
111
|
+
export { storedToken2, connect2 };
|
|
@@ -74,6 +74,18 @@ function boundedJson(value, maxBytes) {
|
|
|
74
74
|
throw new FlareError("TOO_LARGE", "JSON payload exceeds limit");
|
|
75
75
|
return json;
|
|
76
76
|
}
|
|
77
|
+
function assertMcpTools(functions) {
|
|
78
|
+
const names = new Set;
|
|
79
|
+
for (const fn of Object.values(functions)) {
|
|
80
|
+
if (fn.mcp === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
const m = fn.mcp;
|
|
83
|
+
if (!m || typeof m !== "object" || Object.keys(m).sort().join() !== "description,name" || typeof m.name !== "string" || !/^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(m.name) || typeof m.description !== "string" || !m.description.trim() || m.description.length > 1000 || names.has(m.name) || fn.auth === "internal" || fn.input.type !== "object")
|
|
84
|
+
throw new FlareError("INVALID_CONTRACT", "Invalid MCP tool contract");
|
|
85
|
+
boundedJson({ mcp: m, input: fn.input }, 16384);
|
|
86
|
+
names.add(m.name);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
77
89
|
|
|
78
90
|
// packages/flare/src/sdk/schema.ts
|
|
79
91
|
function identifier(name) {
|
|
@@ -176,8 +188,15 @@ function manifest(app) {
|
|
|
176
188
|
});
|
|
177
189
|
if (!input || !output)
|
|
178
190
|
throw new FlareError("INVALID_CONTRACT", `${path} requires JSON Schema converters`);
|
|
179
|
-
functions[path] = {
|
|
191
|
+
functions[path] = {
|
|
192
|
+
kind: fn.kind,
|
|
193
|
+
auth: fn.auth,
|
|
194
|
+
input,
|
|
195
|
+
output,
|
|
196
|
+
...fn.mcp !== undefined ? { mcp: fn.mcp } : {}
|
|
197
|
+
};
|
|
180
198
|
}
|
|
199
|
+
assertMcpTools(functions);
|
|
181
200
|
boundedJson(functions, 1048576);
|
|
182
201
|
return { protocol: 1, runtimeAbi: 4, functions };
|
|
183
202
|
}
|
|
@@ -356,13 +375,19 @@ async function generateReferences(contract) {
|
|
|
356
375
|
let generated = `import {reference} from "@auersperg/flare";
|
|
357
376
|
`;
|
|
358
377
|
const refs = [];
|
|
378
|
+
const types = new Map;
|
|
379
|
+
const declaration = async (schema, name) => {
|
|
380
|
+
const key = stableJson(schema), previous = types.get(key);
|
|
381
|
+
if (previous)
|
|
382
|
+
return `export type ${name} = ${previous};
|
|
383
|
+
`;
|
|
384
|
+
const code = await compile(schema, name, { bannerComment: "" });
|
|
385
|
+
types.set(key, name);
|
|
386
|
+
return code;
|
|
387
|
+
};
|
|
359
388
|
for (const [index, [path, fn]] of Object.entries(contract.functions).entries()) {
|
|
360
|
-
generated += await
|
|
361
|
-
|
|
362
|
-
});
|
|
363
|
-
generated += await compile(fn.output, `Output${index}`, {
|
|
364
|
-
bannerComment: ""
|
|
365
|
-
});
|
|
389
|
+
generated += await declaration(fn.input, `Input${index}`);
|
|
390
|
+
generated += await declaration(fn.output, `Output${index}`);
|
|
366
391
|
refs.push(`${JSON.stringify(path)}: reference<Input${index}, Output${index}>(${JSON.stringify(path)}, ${JSON.stringify(fn.kind)})`);
|
|
367
392
|
}
|
|
368
393
|
generated += `
|
package/dist/cli/flare.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// packages/flare/package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@auersperg/flare",
|
|
6
|
-
version: "0.3.
|
|
6
|
+
version: "0.3.11",
|
|
7
7
|
license: "MIT",
|
|
8
8
|
type: "module",
|
|
9
9
|
description: "Flare client, server declarations and development CLI",
|
|
@@ -39,7 +39,7 @@ var package_default = {
|
|
|
39
39
|
},
|
|
40
40
|
dependencies: {
|
|
41
41
|
"@standard-schema/spec": "1.1.0",
|
|
42
|
-
zod: "4.6.
|
|
42
|
+
zod: "4.6.6",
|
|
43
43
|
"json-schema-to-typescript": "16.0.0"
|
|
44
44
|
},
|
|
45
45
|
engines: {
|
|
@@ -147,7 +147,7 @@ Uses the platform payload bundled with this CLI release. Never updates applicati
|
|
|
147
147
|
options[key] = args[++i];
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
const { platform } = await import("./chunk-
|
|
150
|
+
const { platform } = await import("./chunk-1yvbecac.js");
|
|
151
151
|
console.log(JSON.stringify(await platform(options), null, 2));
|
|
152
152
|
}
|
|
153
153
|
} catch (error) {
|
|
@@ -187,12 +187,12 @@ Uses the platform payload bundled with this CLI release. Never updates applicati
|
|
|
187
187
|
if (command === "build") {
|
|
188
188
|
if (!options.entry || !options.outdir)
|
|
189
189
|
throw Error("Required: --entry, --outdir");
|
|
190
|
-
const { buildApplication } = await import("./chunk-
|
|
190
|
+
const { buildApplication } = await import("./chunk-yhvbksrm.js");
|
|
191
191
|
const x = await buildApplication(options);
|
|
192
192
|
console.log(`Built immutable release ${x.hash}
|
|
193
193
|
${x.outdir}`);
|
|
194
194
|
} else {
|
|
195
|
-
const cloud = await import("./chunk-
|
|
195
|
+
const cloud = await import("./chunk-7ym0f672.js");
|
|
196
196
|
const x = await cloud[command](options);
|
|
197
197
|
if (x)
|
|
198
198
|
console.log(JSON.stringify(x, null, 2));
|
package/dist/platform/admin.js
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
// packages/flare/src/sdk/http.ts
|
|
2
|
+
async function readBody(body, limit, tooLarge) {
|
|
3
|
+
const reader = body.getReader();
|
|
4
|
+
const chunks = [];
|
|
5
|
+
let size = 0;
|
|
6
|
+
try {
|
|
7
|
+
for (;; ) {
|
|
8
|
+
const { done, value } = await reader.read();
|
|
9
|
+
if (done)
|
|
10
|
+
break;
|
|
11
|
+
size += value.byteLength;
|
|
12
|
+
if (size > limit)
|
|
13
|
+
tooLarge();
|
|
14
|
+
chunks.push(value);
|
|
15
|
+
}
|
|
16
|
+
} finally {
|
|
17
|
+
await reader.cancel().catch(() => {});
|
|
18
|
+
}
|
|
19
|
+
if (chunks.length === 1)
|
|
20
|
+
return chunks[0];
|
|
21
|
+
const bytes = new Uint8Array(size);
|
|
22
|
+
let offset = 0;
|
|
23
|
+
for (const chunk of chunks) {
|
|
24
|
+
bytes.set(chunk, offset);
|
|
25
|
+
offset += chunk.byteLength;
|
|
26
|
+
}
|
|
27
|
+
return bytes;
|
|
28
|
+
}
|
|
29
|
+
|
|
1
30
|
// packages/flare/src/sdk/protocol.ts
|
|
2
31
|
var LIMITS = Object.freeze({
|
|
3
32
|
inputBytes: 65536,
|
|
@@ -75,28 +104,7 @@ async function jsonBody(request, limit = LIMITS.inputBytes) {
|
|
|
75
104
|
fail("INVALID_CONTENT_TYPE");
|
|
76
105
|
if (!request.body)
|
|
77
106
|
fail("INVALID_INPUT");
|
|
78
|
-
const
|
|
79
|
-
const chunks = [];
|
|
80
|
-
let size = 0;
|
|
81
|
-
try {
|
|
82
|
-
for (;; ) {
|
|
83
|
-
const { done, value } = await reader.read();
|
|
84
|
-
if (done)
|
|
85
|
-
break;
|
|
86
|
-
size += value.byteLength;
|
|
87
|
-
if (size > limit)
|
|
88
|
-
fail("TOO_LARGE");
|
|
89
|
-
chunks.push(value);
|
|
90
|
-
}
|
|
91
|
-
} finally {
|
|
92
|
-
await reader.cancel().catch(() => {});
|
|
93
|
-
}
|
|
94
|
-
const bytes = new Uint8Array(size);
|
|
95
|
-
let offset = 0;
|
|
96
|
-
for (const c of chunks) {
|
|
97
|
-
bytes.set(c, offset);
|
|
98
|
-
offset += c.byteLength;
|
|
99
|
-
}
|
|
107
|
+
const bytes = await readBody(request.body, limit, () => fail("TOO_LARGE"));
|
|
100
108
|
try {
|
|
101
109
|
const parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
102
110
|
boundedJson(parsed, limit);
|
|
@@ -301,7 +309,7 @@ async function collectCatalog(database, now = Date.now()) {
|
|
|
301
309
|
// packages/core/package.json
|
|
302
310
|
var package_default = {
|
|
303
311
|
name: "@flare/platform",
|
|
304
|
-
version: "0.3.
|
|
312
|
+
version: "0.3.11",
|
|
305
313
|
type: "module",
|
|
306
314
|
license: "MIT",
|
|
307
315
|
private: true,
|
|
@@ -313,7 +321,8 @@ var package_default = {
|
|
|
313
321
|
"README.md"
|
|
314
322
|
],
|
|
315
323
|
dependencies: {
|
|
316
|
-
agents: "0.28.0"
|
|
324
|
+
agents: "0.28.0",
|
|
325
|
+
"@modelcontextprotocol/server": "2.3.0"
|
|
317
326
|
},
|
|
318
327
|
repository: {
|
|
319
328
|
type: "git",
|
|
@@ -660,6 +669,7 @@ async function api(request, env, a) {
|
|
|
660
669
|
"schema",
|
|
661
670
|
"data",
|
|
662
671
|
"functions",
|
|
672
|
+
"ai",
|
|
663
673
|
"logs",
|
|
664
674
|
"history",
|
|
665
675
|
"jobs",
|
|
@@ -731,6 +741,7 @@ async function api(request, env, a) {
|
|
|
731
741
|
if ([
|
|
732
742
|
"file-upload",
|
|
733
743
|
"file-delete",
|
|
744
|
+
"search-clear",
|
|
734
745
|
"job-cancel",
|
|
735
746
|
"cron-set",
|
|
736
747
|
"cron-cancel"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,r as t}from"./client.BeKNsjPN.js";import{At as n,g as r,j as i,m as a,p as o,pt as s,q as c,s as l,u}from"./common.9pujiaSZ.js";import{i as d,t as f}from"./client.4z54xJ_z.js";var p=e(t(),1),m=n();function h({project:e,env:t,session:n}){let h=r(e,t),g=u(h+`/ai`),[_,v]=(0,p.useState)(``),[y,b]=(0,p.useState)(``),[x,S]=(0,p.useState)(`{"query":""}`),[C,w]=(0,p.useState)(``),[T,E]=(0,p.useState)(``),[D,O]=(0,p.useState)(!1);(0,p.useEffect)(()=>{v(``),w(``),E(``)},[h]);async function k(){O(!0),E(``),w(``);let r=f({url:`${n.runtime}/p/${e.id}/${t}`,getToken:()=>_});try{let e=await r.action(d(y,`action`),JSON.parse(x));w(JSON.stringify(e,null,2))}catch(e){E(a(e))}finally{r.dispose(),O(!1)}}async function A(){if(confirm(`Exclude all indexed files in ${e.name}/${t}? Originals remain in Files. Provider cleanup is asynchronous.`)){O(!0),E(``);try{await o(h+`/search-clear`,t===`prod`?{confirm:e.id}:{}),g.refresh()}catch(e){E(a(e))}finally{O(!1)}}}return(0,m.jsxs)(`div`,{className:`space-y-6 p-6`,children:[(0,m.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,m.jsx)(`h1`,{className:`page-title`,children:`AI`}),(0,m.jsx)(s,{variant:`outline`,onClick:g.refresh,children:`Refresh`})]}),(0,m.jsx)(l,{loading:g.loading,error:g.error||T}),g.data&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`section`,{className:`space-y-3`,children:[(0,m.jsx)(`h2`,{className:`text-lg font-medium`,children:`Search`}),(0,m.jsx)(`p`,{children:g.data.search.binding?`Binding: ${g.data.search.binding}`:`Not connected. Configure an existing AI Search instance through platform update --config. No resource is created here.`}),(0,m.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`TXT/PDF · up to 10 MiB · explicit file indexing from application actions. File permissions belong to your application.`}),(0,m.jsxs)(`p`,{children:[`Tracked index items: `,g.data.search.remaining]}),g.data.search.states.map(e=>(0,m.jsxs)(`p`,{children:[e.status,`: `,e.count]},e.status)),g.data.search.errors>0&&(0,m.jsxs)(`p`,{role:`alert`,children:[g.data.search.errors,` operations need attention. Retry indexing through the application; retry pending cleanup with Exclude all.`]}),(0,m.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Processing states are last observed, not a live provider status. Use action search.status(fileId) to refresh a document. Files, Functions and Logs remain in their existing sections.`}),n.role!==`viewer`&&g.data.search.remaining>0&&(0,m.jsx)(s,{variant:`outline`,disabled:D,onClick:A,children:`Exclude all / retry cleanup`})]}),(0,m.jsxs)(`section`,{className:`space-y-3`,children:[(0,m.jsx)(`h2`,{className:`text-lg font-medium`,children:`Test application search`}),(0,m.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Calls your existing search action with a real application session, not dashboard/admin identity. The application must authorize each result. Session is held only in this view, never saved. Dashboard origin must be allowed by the application.`}),(0,m.jsxs)(`label`,{className:`block`,children:[`Application session`,(0,m.jsx)(c,{type:`password`,autoComplete:`off`,value:_,onChange:e=>v(e.target.value),placeholder:`flared_session_…`})]}),(0,m.jsxs)(`label`,{className:`block`,children:[`Search action path`,(0,m.jsx)(c,{value:y,onChange:e=>b(e.target.value),placeholder:`documents/search`})]}),(0,m.jsxs)(`label`,{className:`block`,children:[`Arguments (JSON)`,(0,m.jsx)(i,{value:x,onChange:e=>S(e.target.value)})]}),(0,m.jsx)(s,{disabled:D||!_.startsWith(`flared_session_`)||!y||!g.data.search.binding,onClick:k,children:`Run once`}),C&&(0,m.jsx)(`pre`,{className:`whitespace-pre-wrap break-words text-sm`,children:C})]}),(0,m.jsxs)(`section`,{className:`space-y-3`,children:[(0,m.jsx)(`h2`,{className:`text-lg font-medium`,children:`MCP`}),(0,m.jsx)(`code`,{className:`break-all`,children:g.data.endpoint}),(0,m.jsx)(`p`,{className:`text-sm text-muted-foreground`,children:`Stateless HTTP. Use a client supporting an Authorization: Bearer application-session header. No OAuth discovery. Admin, deploy and dev tokens are not accepted. Logout, expiry and disabling the user revoke access.`}),g.data.tools.length?g.data.tools.map(e=>(0,m.jsxs)(`p`,{children:[(0,m.jsx)(`strong`,{children:e.name}),` — `,e.description]},e.name)):(0,m.jsx)(`p`,{children:`No tools exposed. Add explicit mcp metadata to selected application functions and deploy that application.`})]})]})]})}export{h as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,r as t}from"./client.BeKNsjPN.js";import{At as n,S as r,_ as i,a,b as o,f as s,g as c,n as l,p as u,pt as d,s as f,u as p,v as m,x as h,y as g}from"./common.
|
|
1
|
+
import{a as e,r as t}from"./client.BeKNsjPN.js";import{At as n,S as r,_ as i,a,b as o,f as s,g as c,n as l,p as u,pt as d,s as f,u as p,v as m,x as h,y as g}from"./common.9pujiaSZ.js";import{o as _}from"./index.astro_astro_type_script_index_0_lang.cFeBPGtv.js";var v=e(t(),1),y=n();function b(e){let t=c(e.project,e.env),[n,b]=(0,v.useState)(``),[x,S]=(0,v.useState)(),C=p(t+`/auth-users`+(n?`?cursor=`+encodeURIComponent(n):``)),w=p(t+`/auth-settings`),T=e.session.role===`owner`,E=(n,r,i,a=[])=>{S({title:r,description:`Passwords are never displayed. Changes revoke existing sessions.`,productionId:e.env===`prod`?e.project.id:void 0,fields:a,submit:async r=>{try{await u(t+`/`+n,{...i,...s(a,e=>new FormData(r).get(e)),confirm:e.project.id})}catch(e){throw e instanceof Error&&e.message===`INVALID_PASSWORD`?Error(`Password must contain 6–128 characters. Spaces are allowed; the password is not trimmed.`):e instanceof Error&&e.message===`REGISTRATION_UNAVAILABLE`?Error(`This login is already registered. Choose another login or manage the existing user.`):e}C.refresh(),w.refresh()}})},D={name:`password`,type:`password`,required:!0,description:`6–128 characters.`};return(0,y.jsxs)(`div`,{className:`space-y-4 p-4`,children:[(0,y.jsx)(`h1`,{className:`page-title`,children:`Auth`}),(0,y.jsx)(f,{loading:C.loading||w.loading,error:C.error||w.error}),w.data&&(0,y.jsxs)(`div`,{className:`flex flex-wrap items-center gap-2`,children:[(0,y.jsx)(_,{status:w.data.enabled?`Enabled`:`Disabled`}),(0,y.jsx)(_,{status:w.data.registration?`Public registration`:`Registration closed`}),T&&(0,y.jsx)(d,{variant:`outline`,onClick:()=>E(`auth-configure`,`Configure authentication`,{},[{name:`enabled`,type:`boolean`,value:w.data.enabled},{name:`registration`,type:`boolean`,value:w.data.registration}]),children:`Configure`}),T&&(0,y.jsx)(d,{onClick:()=>E(`auth-create`,`Create user`,{},[{name:`login`,type:`string`,required:!0},D]),children:`Create user`})]}),C.data&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsxs)(i,{children:[(0,y.jsx)(h,{children:(0,y.jsxs)(r,{children:[(0,y.jsx)(o,{children:`Login`}),(0,y.jsx)(o,{children:`User ID`}),(0,y.jsx)(o,{children:`Status`}),(0,y.jsx)(o,{children:`Created`}),(0,y.jsx)(o,{children:`Actions`})]})}),(0,y.jsx)(m,{children:C.data.users.map(e=>(0,y.jsxs)(r,{children:[(0,y.jsx)(g,{children:e.login}),(0,y.jsx)(g,{children:(0,y.jsx)(`code`,{children:e.id})}),(0,y.jsx)(g,{children:(0,y.jsx)(_,{status:e.disabled?`Blocked`:`Active`})}),(0,y.jsx)(g,{children:new Date(e.createdAt).toLocaleString()}),(0,y.jsx)(g,{children:(0,y.jsx)(`div`,{className:`flex flex-wrap gap-2`,children:T&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(d,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-password`,`Change user password`,{id:e.id},[D]),children:`Change password`}),(0,y.jsx)(d,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-revoke`,`Revoke all sessions`,{id:e.id}),children:`Revoke sessions`}),(0,y.jsx)(d,{variant:`outline`,size:`sm`,onClick:()=>E(`auth-disable`,e.disabled?`Unblock user`:`Block user`,{id:e.id,disabled:!e.disabled}),children:e.disabled?`Unblock`:`Block`})]})})})]},e.id))})]}),!C.data.users.length&&(0,y.jsx)(l,{title:`No users`}),(0,y.jsxs)(`div`,{className:`flex gap-2`,children:[(0,y.jsx)(d,{variant:`outline`,disabled:!n,onClick:()=>b(``),children:`First page`}),(0,y.jsx)(d,{variant:`outline`,disabled:!C.data.next,onClick:()=>b(C.data.next),children:`Next page`})]})]}),x&&(0,y.jsx)(a,{modal:x,onClose:()=>S(void 0)})]})}export{b as default};
|
package/dist/platform/dashboard/_astro/{cli-approval.DwZVHdyo.js → cli-approval.emPTVRqn.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,r as t}from"./client.BeKNsjPN.js";import{A as n,At as r,O as i,k as a,m as o,p as s,pt as c,q as l,r as u,t as d}from"./common.
|
|
1
|
+
import{a as e,r as t}from"./client.BeKNsjPN.js";import{A as n,At as r,O as i,k as a,m as o,p as s,pt as c,q as l,r as u,t as d}from"./common.9pujiaSZ.js";var f=e(t(),1),p=r();function m(){let e=new URLSearchParams(location.search).get(`cli`)??``,[t,r]=(0,f.useState)(``),[m,h]=(0,f.useState)([]),[g,_]=(0,f.useState)(`new`),[v,y]=(0,f.useState)(``),[b,x]=(0,f.useState)(``),[S,C]=(0,f.useState)(!1),[w,T]=(0,f.useState)(!1);(0,f.useEffect)(()=>{let t=new AbortController;return Promise.all([s(`cli/approval?id=`+encodeURIComponent(e),void 0,t.signal),s(`projects`,void 0,t.signal)]).then(([e,t])=>{r(e.code),h(t),_(`new`)}).catch(e=>{t.signal.aborted||x(o(e))}),()=>t.abort()},[e]);async function E(){C(!0),x(``);try{let t=g;if(t===`new`){let e=await s(`projects`,{name:v,customer:`Applications`,origins:[]});t=e.id,h(t=>[...t,{id:e.id,name:v,customer:`Applications`}]),_(t)}await s(`cli/approval`,{id:e,project:t}),T(!0)}catch(e){x(o(e))}finally{C(!1)}}return(0,p.jsxs)(`main`,{className:`mx-auto max-w-lg p-8`,children:[(0,p.jsx)(`h1`,{className:`mb-4 text-xl font-semibold`,children:`Connect Flare CLI`}),w?(0,p.jsx)(`p`,{children:`Connected. Return to your terminal. You can close this tab.`}):(0,p.jsxs)(a,{children:[(0,p.jsx)(`p`,{children:`Approve only a login you just started. Compare this code with your terminal:`}),(0,p.jsx)(`strong`,{className:`text-2xl font-mono`,children:t||`Loading…`}),(0,p.jsx)(`p`,{children:`Grants this computer deployment access to the selected project, including production. The credential expires in 30 days and can be revoked in Settings.`}),(0,p.jsxs)(i,{children:[(0,p.jsx)(n,{children:`Project`}),(0,p.jsx)(d,{label:`CLI project`,value:g,onChange:_,items:[...m.map(e=>({value:e.id,label:e.name})),{value:`new`,label:`Create new project`}]})]}),g===`new`&&(0,p.jsxs)(i,{children:[(0,p.jsx)(n,{children:`New project name`}),(0,p.jsx)(l,{value:v,onChange:e=>y(e.target.value),maxLength:100})]}),b&&(0,p.jsx)(u,{children:b}),(0,p.jsx)(c,{disabled:S||!t||!g||g===`new`&&!v.trim(),onClick:E,children:`Approve connection`})]})]})}export{m as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
async function e(e,t,n){let r=e.getReader(),i=[],a=0;try{for(;;){let{done:e,value:o}=await r.read();if(e)break;a+=o.byteLength,a>t&&n(),i.push(o)}}finally{await r.cancel().catch(()=>{})}if(i.length===1)return i[0];let o=new Uint8Array(a),s=0;for(let e of i)o.set(e,s),s+=e.byteLength;return o}var t=Object.freeze({inputBytes:65536,outputBytes:262144,frameBytes:8192,subscriptions:64,fileBytes:10485760}),n=class extends Error{code;ambiguous;constructor(e,t,n=!1){super(t),this.code=e,this.ambiguous=n,this.name=`FlareError`}};function r(e){if(e.length>256||!/^[A-Za-z][A-Za-z0-9_]*(\/[A-Za-z][A-Za-z0-9_]*)*$/.test(e)||e.split(`/`).some(e=>[`__proto__`,`prototype`,`constructor`].includes(e)))throw new n(`INVALID_PATH`,`Invalid function path`)}function i(e,t){return r(e),Object.freeze({path:e,kind:t})}function a(e){let t=new Set;function r(e,i){if(i>40)throw new n(`INVALID_JSON`,`JSON nesting limit exceeded`);if(e===null||typeof e==`string`||typeof e==`boolean`||typeof e==`number`&&Number.isFinite(e))return e;if(typeof e!=`object`||!e||t.has(e))throw new n(`INVALID_JSON`,`Expected lossless JSON`);if(!Array.isArray(e)&&Object.getPrototypeOf(e)!==Object.prototype&&Object.getPrototypeOf(e)!==null)throw new n(`INVALID_JSON`,`Expected plain JSON object`);if(Object.getOwnPropertySymbols(e).length)throw new n(`INVALID_JSON`,`Symbol keys are unsupported`);t.add(e);let a;if(Array.isArray(e))a=Array.from(e,e=>r(e,i+1));else{let t=Object.create(null);for(let a of Object.keys(e).sort()){let o=Object.getOwnPropertyDescriptor(e,a);if(!(`value`in o))throw new n(`INVALID_JSON`,`Getters are unsupported`);t[a]=r(o.value,i+1)}a=t}return t.delete(e),a}return JSON.stringify(r(e,0))}function o(e,t){let r=a(e);if(new TextEncoder().encode(r).byteLength>t)throw new n(`TOO_LARGE`,`JSON payload exceeds limit`);return r}function s(e){if(e.size>t.fileBytes)throw new n(`TOO_LARGE`,`Maximum file size: 10 MiB`);if(!e.name||e.name.length>120||/[\x00-\x1f\x7f/\\]/.test(e.name))throw new n(`INVALID_INPUT`,`File name must be 1–120 characters, without path separators`);return{name:e.name,size:e.size,type:[`text/plain`,`image/png`,`image/jpeg`,`image/webp`,`application/pdf`].includes(e.type)?e.type:`application/octet-stream`}}async function c(r,i,a={}){if(i.size>t.fileBytes)throw new n(`TOO_LARGE`,`Maximum file size: 10 MiB`);let o=new URL(r.url);if(o.username||o.password||o.protocol!==`https:`&&!(o.protocol===`http:`&&[`localhost`,`127.0.0.1`,`[::1]`].includes(o.hostname)))throw new n(`INVALID_URL`,`Expected secure upload URL`);let s;try{s=await(a.fetch??globalThis.fetch)(o.href,{method:`POST`,body:i,redirect:`error`,credentials:`omit`,signal:AbortSignal.any([...a.signal?[a.signal]:[],AbortSignal.timeout(a.timeoutMs??12e4)])})}catch{throw new n(`UPLOAD_UNKNOWN`,`Connection interrupted or upload timed out; refresh the file list before trying again`,!0)}let c=s.body?await e(s.body,t.outputBytes,()=>{throw Error(`Upload response exceeds limit`)}).then(e=>JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(e))).catch(()=>null):null;if(!s.ok){let e=c&&typeof c==`object`&&`error`in c&&typeof c.error==`string`?c.error:`HTTP_${s.status}`;throw new n(e,`Upload failed: ${e}`,s.status>=500)}if(!c||typeof c!=`object`||!(`id`in c)||typeof c.id!=`string`)throw new n(`INVALID_RESPONSE`,`Upload outcome is unknown; refresh the file list before uploading again`,!0);return{id:c.id}}var l=new Set([`UNAUTHORIZED`,`AUTH_EXPIRED`,`FORBIDDEN`,`AUTH_DISABLED`,`HTTP_401`,`HTTP_403`]);function u(i){let s=new URL(i.url);if(!/^https?:$/.test(s.protocol)||s.username||s.password||s.search||s.hash)throw new n(`INVALID_URL`,`Expected backend HTTP URL`);if(s.protocol===`http:`&&![`localhost`,`127.0.0.1`,`[::1]`].includes(s.hostname))throw new n(`INVALID_URL`,`Remote backends require HTTPS`);s.pathname=s.pathname.replace(/\/$/,``);let c=i.fetch??globalThis.fetch;if(!c)throw new n(`TRANSPORT_UNAVAILABLE`,`Provide a fetch implementation`);let u=new Map,d=!1,f,p=!1,m=0,h=0,g=0,_,v=new Set,y=e=>`${s.href.replace(/\/$/,``)}${e}`,b=(e,t,n)=>{for(let r of e.observers)try{r[t]?.(t===`next`?JSON.parse(n):n)}catch{}};function x(){if(d)throw new n(`DISPOSED`,`Client is disposed`)}async function S(r,a,s){x();let l=o(a,t.inputBytes),u;try{u=await i.getToken?.()??null}catch{throw new n(`AUTH_UNAVAILABLE`,`Cannot obtain access token`)}x();let d=new AbortController,f=setTimeout(()=>d.abort(),i.requestTimeoutMs??3e4);try{let i=await c(y(r),{method:`POST`,headers:{"Content-Type":`application/json`,...u?{Authorization:`Bearer ${u}`}:{}},body:l,signal:d.signal,redirect:`error`});if(!i.body)throw new n(`INVALID_RESPONSE`,`Missing response body`,s);let a=await e(i.body,t.outputBytes,()=>{throw new n(`INVALID_RESPONSE`,`Response exceeds limit`,s)}),f;try{f=JSON.parse(new TextDecoder(`utf-8`,{fatal:!0}).decode(a))}catch(e){throw i.ok?e:new n(`HTTP_${i.status}`,`Backend request failed`,s&&i.status>=500)}if(!i.ok)throw new n(f&&typeof f==`object`&&`error`in f&&typeof f.error==`string`&&/^[A-Z][A-Z0-9_]{0,63}$/.test(f.error)?f.error:`HTTP_${i.status}`,`Backend request failed`,s&&i.status>=500);if(!f||typeof f!=`object`||!(`result`in f))throw new n(`INVALID_RESPONSE`,`Invalid protocol envelope`,s);return o(f.result,t.outputBytes),f.result}catch(e){throw e instanceof n?e:e instanceof SyntaxError?new n(`INVALID_RESPONSE`,`Invalid JSON response`,s):new n(`TRANSPORT_ERROR`,s?`Write outcome is unknown; do not replay automatically`:`Backend request interrupted`,s)}finally{clearTimeout(f)}}function C(e){f?.send(o(e,t.frameBytes))}function w(e){C({type:`subscribe`,id:e.id,path:e.ref.path,args:e.args})}function T(){if(d||!u.size||_)return;let e=Math.min(3e4,250*2**Math.min(m++,7));_=setTimeout(()=>{_=void 0,E()},e)}async function E(){if(d||p||f||!u.size)return;p=!0;let e=++h;try{let r=await S(`/v1/live/ticket`,{},!1);if(d||e!==h||!u.size)return;if(!r||typeof r.ticket!=`string`||r.ticket.length>1024)throw new n(`INVALID_RESPONSE`,`Invalid live ticket`);let a=new URL(y(`/v1/live`));a.protocol=a.protocol===`https:`?`wss:`:`ws:`,a.searchParams.set(`ticket`,r.ticket);let s=i.createWebSocket?i.createWebSocket(a.href):new globalThis.WebSocket(a.href);f=s;let c=()=>{if(!(f!==s||d)){m=0;for(let e of u.values())e.revision=-1,e.snapshot=void 0,e.terminal||w(e)}},p=e=>{if(!(f!==s||d))try{if(typeof e.data!=`string`||new TextEncoder().encode(e.data).byteLength>t.outputBytes+1024)throw Error(`Invalid frame`);let r=JSON.parse(e.data);if(r.type===`ready`)return;let i=[...u.values()].find(e=>e.id===r.id);if(!i||i.terminal)return;if(r.type===`error`){i.terminal=!0,l.has(r.code)&&(i.snapshot=void 0),b(i,`error`,new n(typeof r.code==`string`?r.code:`LIVE_ERROR`,`Live subscription failed`));return}if(r.type!==`snapshot`||!Number.isSafeInteger(r.revision)||r.revision<0||!(`result`in r))throw Error(`Invalid snapshot`);let a=o(r.result,t.outputBytes);if(r.revision>i.revision){let e=i.snapshot!==a;i.revision=r.revision,i.snapshot=a,e&&b(i,`next`,i.snapshot)}C({type:`ack`,id:i.id,revision:i.revision})}catch{s.close(1002,`Invalid protocol frame`)}},g=e=>{if(x(),f===s){f=void 0;for(let t of u.values())e.code===1008&&(t.snapshot=void 0,t.revision=-1),t.terminal||b(t,`error`,new n(e.code===1008?`AUTH_EXPIRED`:`LIVE_DISCONNECTED`,`Live reconnecting; next snapshot will be current`));T()}},_=()=>s.close(),x=()=>{s.removeEventListener(`open`,c),s.removeEventListener(`message`,p),s.removeEventListener(`close`,g),s.removeEventListener(`error`,_),v.delete(x)};v.add(x),s.addEventListener(`open`,c),s.addEventListener(`message`,p),s.addEventListener(`close`,g),s.addEventListener(`error`,_)}catch(t){if(d||e!==h)return;for(let e of u.values())t instanceof n&&l.has(t.code)&&(e.snapshot=void 0,e.revision=-1),b(e,`error`,t instanceof n?t:new n(`LIVE_UNAVAILABLE`,`Live connection failed`));T()}finally{p=!1,!d&&e!==h&&!f&&u.size&&T()}}function D(e){return async(t,i)=>{if(r(t.path),t.kind!==e)throw new n(`INVALID_KIND`,`Expected a ${e} function`);return S(`/v1/call`,{path:t.path,kind:t.kind,args:i},t.kind===`mutation`||t.kind===`action`)}}return{query:D(`query`),mutation:D(`mutation`),action:D(`action`),onUpdate(e,i,s,c){let l={next:s,error:c};if(x(),r(e.path),e.kind!==`query`)throw new n(`INVALID_KIND`,`Only queries are subscribable`);o({type:`subscribe`,id:`subscription-9999999999`,path:e.path,args:i},t.frameBytes);let d=a({path:e.path,args:i}),p=u.get(d);if(!p){if(u.size>=t.subscriptions)throw new n(`TOO_MANY_SUBSCRIPTIONS`,`Subscription limit exceeded`);p={id:`subscription-${++g}`,ref:e,args:JSON.parse(a(i)),observers:new Set,revision:-1,terminal:!1},u.set(d,p),f?.readyState===1&&w(p)}let m={next:l.next,error:l.error};p.observers.add(m),p.snapshot!==void 0&&b({...p,observers:new Set([l])},`next`,p.snapshot),E();let y=!1;return()=>{if(!y&&(y=!0,p.observers.delete(m),p.observers.size||(u.delete(d),f?.readyState===1&&C({type:`unsubscribe`,id:p.id})),!u.size)){++h,_&&clearTimeout(_),_=void 0;let e=f;f=void 0;for(let e of[...v])e();e?.close(1e3,`No subscriptions`)}}},refreshAuth(){x(),++h,_&&clearTimeout(_),_=void 0;for(let e of[...v])e();let e=f;f=void 0,e?.close(1e3,`Authorization changed`);for(let e of u.values())e.snapshot=void 0,e.revision=-1,e.terminal=!1,b(e,`error`,new n(`AUTH_CHANGED`,`Authorization changed; cached value invalidated`));E()},dispose(){if(!d){d=!0,++h,_&&clearTimeout(_),_=void 0;for(let e of[...v])e();u.clear(),f?.close(1e3,`Disposed`),f=void 0}}}}export{i,s as n,c as r,u as t};
|