busa-sdk 0.98.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -74,6 +74,21 @@ interface AirAppSources {
74
74
  config?: string;
75
75
  /** The slug the package ships this app under, for the `resourceKey` rule. */
76
76
  shippedSlug?: string;
77
+ /**
78
+ * The app's files, one entry each, paths relative to the app's root — for the
79
+ * rules that report a file and line (`airapp/forced-review`).
80
+ *
81
+ * Pass every file; the rule picks its own corpus with
82
+ * {@link isAirAppWriteRuleSource} (script sources only, never `vendor/`,
83
+ * `node_modules/`, tests, or `scripts/`), so a caller cannot widen it by
84
+ * accident. Omitted, the rule falls back to `browserLogic` + `server`, which
85
+ * still covers the provider and the host but can only quote the offending
86
+ * line, not name its file.
87
+ */
88
+ appSources?: readonly {
89
+ path: string;
90
+ content: string;
91
+ }[];
77
92
  }
78
93
  /**
79
94
  * The documented interactive page budget. A higher one may be justified — say so in
@@ -91,6 +106,57 @@ declare const AIRAPP_DEFAULT_READ_LIMIT = 50;
91
106
  * satisfy the rule.
92
107
  */
93
108
  declare const stripComments: (source: string) => string;
109
+ /**
110
+ * The source with every comment, string, template-literal text, and regex
111
+ * literal blanked to spaces — newlines kept, so an offset still maps to its line.
112
+ *
113
+ * Stricter than {@link stripComments}, and for a rule that matches an
114
+ * identifier that has to be: the prose and messages explaining `autoMerge` name
115
+ * it constantly (`"pass autoMerge: false to force review"`), and a rule that
116
+ * fires on its own explanation stops being read. Template-literal
117
+ * *interpolations* stay code — `${autoMerge}` is a real use.
118
+ */
119
+ declare const codeOnly: (source: string) => string;
120
+ /** The reason an `allow-forced-review` comment has to give, at minimum, in characters. */
121
+ declare const AIRAPP_FORCED_REVIEW_MIN_REASON = 20;
122
+ /** Where `autoMerge` was set to something other than literal `true`. */
123
+ interface AirAppForcedReview {
124
+ /** 1-based. */
125
+ line: number;
126
+ /** The offending line, trimmed. */
127
+ text: string;
128
+ problem: string;
129
+ }
130
+ /**
131
+ * Every `autoMerge` in `source`'s code that is not a literal `true`, with the
132
+ * line it is on — an explicit `false`, a value worked out at runtime
133
+ * (`autoMerge: isStandaloneLocalRuntime()`), or a variable passed through
134
+ * (`{ autoMerge }`). Comments, strings, template text, and regexes never match.
135
+ *
136
+ * Why it is a rule: a person's own write follows their permission. The app
137
+ * leaves `autoMerge` unset; Busabase merges it for someone with write access and
138
+ * opens a ChangeRequest for someone without. An app that forces review makes
139
+ * every owner approve their own click in the inbox — a fix that had to be
140
+ * applied to 49 shipped copies by hand.
141
+ *
142
+ * Forcing review is still right when the write is something outgoing a second
143
+ * person must approve before it is sent. Say so on the line, or the line above,
144
+ * and the rule lets it through:
145
+ *
146
+ * ```js
147
+ * autoMerge: false, // allow-forced-review: drafts an outgoing contract that legal must approve
148
+ * ```
149
+ *
150
+ * The reason must be at least {@link AIRAPP_FORCED_REVIEW_MIN_REASON} characters.
151
+ */
152
+ declare const findForcedReview: (source: string) => AirAppForcedReview[];
153
+ /**
154
+ * Whether `path` (relative to the app's root, `/`-separated) belongs to the
155
+ * corpus `airapp/forced-review` scans: a script source of the app's own, not a
156
+ * vendored bundle, a test, or a dev script. The same corpus as
157
+ * `scripts/check-app-writes.mjs` in busabase/templates.
158
+ */
159
+ declare const isAirAppWriteRuleSource: (path: string) => boolean;
94
160
  /**
95
161
  * Objects inside a `bases: [ ... ]` literal, brace-matched rather than regexed across
96
162
  * the array, so a nested `fields:` entry is never mistaken for a Base.
@@ -114,4 +180,4 @@ declare const scanAirAppConfig: (source: string) => {
114
180
  */
115
181
  declare const checkAirApp: (sources: AirAppSources) => AirAppFinding[];
116
182
  //#endregion
117
- export { AIRAPP_DEFAULT_READ_LIMIT, AirAppFinding, AirAppFindingSeverity, AirAppSources, checkAirApp, scanAirAppConfig, stripComments };
183
+ export { AIRAPP_DEFAULT_READ_LIMIT, AIRAPP_FORCED_REVIEW_MIN_REASON, AirAppFinding, AirAppFindingSeverity, AirAppForcedReview, AirAppSources, checkAirApp, codeOnly, findForcedReview, isAirAppWriteRuleSource, scanAirAppConfig, stripComments };
@@ -14,6 +14,10 @@ const FORBIDDEN_DEPENDENCIES = [
14
14
  "webpack",
15
15
  "parcel"
16
16
  ];
17
+ /** Code that branches on where the app is running. */
18
+ const RUNTIME_BRANCH = /createAirAppConnectGate|getRuntime\s*\(|detectAirAppRuntime\s*\(|shouldUseLocalAirAppGateway|\bhosted\b/;
19
+ /** The SDK's browser probe of `__airapp/runtime` (`busabase-sdk/airapp`). */
20
+ const SDK_RUNTIME_PROBE = /\bdetectAirAppRuntime\s*\(/;
17
21
  /**
18
22
  * The documented interactive page budget. A higher one may be justified — say so in
19
23
  * review — so this is a warning rather than a hard bound. A generator checking its
@@ -30,6 +34,163 @@ const AIRAPP_DEFAULT_READ_LIMIT = 50;
30
34
  * satisfy the rule.
31
35
  */
32
36
  const stripComments = (source) => source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:'"`\\])\/\/[^\n]*/g, "$1 ");
37
+ const REGEX_PREFIX = /[(,=:[!&|?{};+\-*%<>~^]$/;
38
+ const REGEX_KEYWORD = /\b(?:return|typeof|instanceof|in|of|new|delete|void|throw|case|do|else|yield|await)$/;
39
+ /**
40
+ * The source with every comment, string, template-literal text, and regex
41
+ * literal blanked to spaces — newlines kept, so an offset still maps to its line.
42
+ *
43
+ * Stricter than {@link stripComments}, and for a rule that matches an
44
+ * identifier that has to be: the prose and messages explaining `autoMerge` name
45
+ * it constantly (`"pass autoMerge: false to force review"`), and a rule that
46
+ * fires on its own explanation stops being read. Template-literal
47
+ * *interpolations* stay code — `${autoMerge}` is a real use.
48
+ */
49
+ const codeOnly = (source) => {
50
+ const out = source.split("");
51
+ const blank = (from, to) => {
52
+ for (let index = from; index < to; index += 1) if (out[index] !== "\n") out[index] = " ";
53
+ };
54
+ const templateDepth = [];
55
+ let index = 0;
56
+ while (index < source.length) {
57
+ const char = source[index];
58
+ const next = source[index + 1];
59
+ if (char === "/" && next === "/") {
60
+ const end = source.indexOf("\n", index);
61
+ const stop = end === -1 ? source.length : end;
62
+ blank(index, stop);
63
+ index = stop;
64
+ continue;
65
+ }
66
+ if (char === "/" && next === "*") {
67
+ const end = source.indexOf("*/", index + 2);
68
+ const stop = end === -1 ? source.length : end + 2;
69
+ blank(index, stop);
70
+ index = stop;
71
+ continue;
72
+ }
73
+ if (char === "\"" || char === "'") {
74
+ let cursor = index + 1;
75
+ while (cursor < source.length && source[cursor] !== char && source[cursor] !== "\n") cursor += source[cursor] === "\\" ? 2 : 1;
76
+ blank(index + 1, cursor);
77
+ index = cursor + 1;
78
+ continue;
79
+ }
80
+ if (char === "`" || char === "}" && templateDepth.at(-1) === 0) {
81
+ if (char === "}") templateDepth.pop();
82
+ let cursor = index + 1;
83
+ while (cursor < source.length && source[cursor] !== "`") {
84
+ if (source[cursor] === "\\") {
85
+ cursor += 2;
86
+ continue;
87
+ }
88
+ if (source[cursor] === "$" && source[cursor + 1] === "{") break;
89
+ cursor += 1;
90
+ }
91
+ blank(index + 1, cursor);
92
+ if (source[cursor] === "$") {
93
+ templateDepth.push(0);
94
+ index = cursor + 2;
95
+ } else index = cursor + 1;
96
+ continue;
97
+ }
98
+ if (char === "/") {
99
+ const before = source.slice(Math.max(0, index - 12), index).trimEnd();
100
+ if (!before || REGEX_PREFIX.test(before) || REGEX_KEYWORD.test(before)) {
101
+ let cursor = index + 1;
102
+ let inClass = false;
103
+ while (cursor < source.length && source[cursor] !== "\n") {
104
+ const current = source[cursor];
105
+ if (current === "\\") {
106
+ cursor += 2;
107
+ continue;
108
+ }
109
+ if (current === "[") inClass = true;
110
+ else if (current === "]") inClass = false;
111
+ else if (current === "/" && !inClass) break;
112
+ cursor += 1;
113
+ }
114
+ blank(index + 1, cursor);
115
+ index = cursor + 1;
116
+ continue;
117
+ }
118
+ }
119
+ if (templateDepth.length) {
120
+ if (char === "{") templateDepth[templateDepth.length - 1] += 1;
121
+ else if (char === "}") templateDepth[templateDepth.length - 1] -= 1;
122
+ }
123
+ index += 1;
124
+ }
125
+ return out.join("");
126
+ };
127
+ /** The reason an `allow-forced-review` comment has to give, at minimum, in characters. */
128
+ const AIRAPP_FORCED_REVIEW_MIN_REASON = 20;
129
+ const ALLOW_FORCED_REVIEW = /allow-forced-review:\s*(.*)$/;
130
+ /**
131
+ * Every `autoMerge` in `source`'s code that is not a literal `true`, with the
132
+ * line it is on — an explicit `false`, a value worked out at runtime
133
+ * (`autoMerge: isStandaloneLocalRuntime()`), or a variable passed through
134
+ * (`{ autoMerge }`). Comments, strings, template text, and regexes never match.
135
+ *
136
+ * Why it is a rule: a person's own write follows their permission. The app
137
+ * leaves `autoMerge` unset; Busabase merges it for someone with write access and
138
+ * opens a ChangeRequest for someone without. An app that forces review makes
139
+ * every owner approve their own click in the inbox — a fix that had to be
140
+ * applied to 49 shipped copies by hand.
141
+ *
142
+ * Forcing review is still right when the write is something outgoing a second
143
+ * person must approve before it is sent. Say so on the line, or the line above,
144
+ * and the rule lets it through:
145
+ *
146
+ * ```js
147
+ * autoMerge: false, // allow-forced-review: drafts an outgoing contract that legal must approve
148
+ * ```
149
+ *
150
+ * The reason must be at least {@link AIRAPP_FORCED_REVIEW_MIN_REASON} characters.
151
+ */
152
+ const findForcedReview = (source) => {
153
+ const code = codeOnly(source);
154
+ const lines = source.split("\n");
155
+ const allowReason = (line) => line?.match(ALLOW_FORCED_REVIEW)?.[1]?.trim() ?? null;
156
+ const findings = [];
157
+ for (const match of code.matchAll(/\bautoMerge\b/g)) {
158
+ const offset = match.index ?? 0;
159
+ const after = code.slice(offset + match[0].length);
160
+ if (/^\s*[:=](?!=)\s*true\b/.test(after)) continue;
161
+ const lineIndex = code.slice(0, offset).split("\n").length - 1;
162
+ const reason = allowReason(lines[lineIndex]) ?? (lineIndex > 0 ? allowReason(lines[lineIndex - 1]) : null);
163
+ if (reason !== null && reason.length >= 20) continue;
164
+ findings.push({
165
+ line: lineIndex + 1,
166
+ text: (lines[lineIndex] ?? "").trim(),
167
+ problem: reason === null ? "forces review on a person's own write (leave autoMerge unset), or says why with an allow-forced-review comment" : `allow-forced-review needs a reason of at least 20 characters`
168
+ });
169
+ }
170
+ return findings;
171
+ };
172
+ /** Directories that are not the app's runtime: third-party bundles, the app's own tests and dev checks. */
173
+ const NOT_APP_RUNTIME = /* @__PURE__ */ new Set([
174
+ "node_modules",
175
+ "vendor",
176
+ "test",
177
+ "tests",
178
+ "scripts",
179
+ ".git"
180
+ ]);
181
+ /**
182
+ * Whether `path` (relative to the app's root, `/`-separated) belongs to the
183
+ * corpus `airapp/forced-review` scans: a script source of the app's own, not a
184
+ * vendored bundle, a test, or a dev script. The same corpus as
185
+ * `scripts/check-app-writes.mjs` in busabase/templates.
186
+ */
187
+ const isAirAppWriteRuleSource = (path) => {
188
+ const segments = path.split(/[\\/]/).filter(Boolean);
189
+ const name = segments.at(-1) ?? "";
190
+ if (!/\.(?:[cm]?js|jsx|ts|tsx)$/.test(name)) return false;
191
+ if (/\.test\.[cm]?[jt]sx?$/.test(name)) return false;
192
+ return !segments.slice(0, -1).some((segment) => NOT_APP_RUNTIME.has(segment));
193
+ };
33
194
  /**
34
195
  * Objects inside a `bases: [ ... ]` literal, brace-matched rather than regexed across
35
196
  * the array, so a nested `fields:` entry is never mistaken for a Base.
@@ -119,17 +280,17 @@ const checkAirApp = (sources) => {
119
280
  if (/(?:src|href)="\/(?!\/)|from\s+["']\/(?!\/)/.test(code)) error("airapp/absolute-asset", "An absolute asset path. Under the Local Node engine the app is proxied onto a sub-path of Busabase's origin, so a leading slash resolves against Busabase itself and 404s.");
120
281
  if (/while\s*\(\s*cursor\s*\)|client\.bases\.list\s*\(/.test(code) || /while\s*\(\s*true\s*\)[\s\S]{0,400}?(?:records\.list|readPage)\s*\(/.test(code)) error("airapp/unbounded-read", "Unbounded loading or runtime Base discovery. Every interactive read gets an explicit budget and fetches one page per user action.");
121
282
  if (/\bmax\w*pages?\w*\s*=\s*\d+[\s\S]{0,400}?(?:client\.)?records\.list\s*\(/i.test(code)) error("airapp/eager-multi-page", "A capped loop fetches several pages of records in one function call. A cap bounds the damage but does not fix the shape: this still hides a multi-page scan behind one loading state instead of fetching one page per user action.");
122
- const branches = /createAirAppConnectGate|getRuntime\s*\(|\bhosted\b/.test(code) || sources.server !== void 0 && /createAirAppConnectGate/.test(sources.server);
283
+ const branches = RUNTIME_BRANCH.test(code) || sources.server !== void 0 && /createAirAppConnectGate/.test(sources.server);
123
284
  if (/location\s*\.\s*(?:hostname|host)\b/.test(code)) error("airapp/runtime-hostname", "Hostname-based runtime detection. Both directions are wrong: a Busabase-hosted AirApp is served from `localhost` on Desktop, and a standalone `npm run dev` is reached over a LAN IP or a signed dev tunnel. Read the runtime from `__airapp/runtime`.");
124
285
  if (/(?:===|!==|==|!=)\s*["'`][^"'`]*(?:localhost|127\.0\.0\.1)|(?:includes|startsWith|endsWith|indexOf|search|match|test)\s*\(\s*\/?["'`]?[^"'`)]*(?:localhost|127\.0\.0\.1)/.test(code)) error("airapp/runtime-loopback", "A loopback host comparison. Runtime detection must not depend on the URL at all.");
125
- if (branches && !logic.includes("__airapp/runtime")) error("airapp/runtime-probe", "This app branches on where it is running but never probes `__airapp/runtime`, so that branch is deciding on something else.");
286
+ if (branches && !logic.includes("__airapp/runtime") && !SDK_RUNTIME_PROBE.test(code)) error("airapp/runtime-probe", "This app branches on where it is running but never probes `__airapp/runtime`, so that branch is deciding on something else.");
126
287
  if (/["'`]\/__airapp\/runtime/.test(logic)) error("airapp/runtime-probe-relative", "The runtime probe has a leading slash. It must be relative (`__airapp/runtime`) — a leading slash resolves against Busabase's root under the Local Node sub-path proxy.");
127
288
  }
128
289
  if (sources.server !== void 0) {
129
290
  const server = sources.server;
130
291
  const serverCode = stripComments(server);
131
292
  const readsRuntimeEnv = (sources.serverLanguage ? sources.serverLanguage === "python" : /^\s*(?:import|from)\s+\w+|def\s+\w+\s*\(/m.test(server)) ? /BUSABASE_AIRAPP_RUNTIME/.test(serverCode) : /(?:read|describe)BusabaseAirAppRuntime\s*\(|process\.env\.BUSABASE_AIRAPP_RUNTIME\b/.test(serverCode);
132
- const clientBranches = sources.browserLogic !== void 0 && /createAirAppConnectGate|getRuntime\s*\(|\bhosted\b/.test(stripComments(sources.browserLogic));
293
+ const clientBranches = sources.browserLogic !== void 0 && RUNTIME_BRANCH.test(stripComments(sources.browserLogic));
133
294
  if (clientBranches && !readsRuntimeEnv) error("airapp/runtime-env", "The server never reads `BUSABASE_AIRAPP_RUNTIME`, directly or through the SDK, so it has nothing to serve at `__airapp/runtime`.");
134
295
  if (/AIRAPP_HOSTED_RUNTIMES\s*=\s*new Set|hosted:\s*\w*RUNTIMES?\w*\.has\s*\(/.test(serverCode)) error("airapp/runtime-engine-list", "Hosting is decided from a hardcoded list of engine names. Use presence, not membership — a private list is what broke 66 apps when `local-node` was renamed `local`.");
135
296
  if (clientBranches && !/["'`]\/__airapp\/runtime["'`]/.test(server)) error("airapp/runtime-route", "Browser code branches on the runtime but the server does not serve `/__airapp/runtime`.");
@@ -141,6 +302,8 @@ const checkAirApp = (sources) => {
141
302
  if (/Bearer\s+(?!\$\{)[A-Za-z0-9_.-]{8,}/.test(downloads)) error("airapp/credential", "A literal Bearer token in browser source.");
142
303
  if (/["'`]\s*Bearer\s*\$\{/.test(downloads)) error("airapp/credential", "Browser code builds an Authorization header. A deployed AirApp uses the viewer's ambient same-origin session and needs none.");
143
304
  }
305
+ const forcedReviewCorpus = sources.appSources ? sources.appSources.filter((file) => isAirAppWriteRuleSource(file.path)) : [sources.browserLogic, sources.server].filter((content) => content !== void 0).map((content) => ({ content }));
306
+ for (const file of forcedReviewCorpus) for (const found of findForcedReview(file.content)) error("airapp/forced-review", `${file.path ? `${file.path}:${found.line}: ` : ""}\`${found.text}\` ${found.problem}. Leave \`autoMerge\` unset: Busabase merges a person's write when they have write access and opens a ChangeRequest when they do not.`);
144
307
  if (sources.config !== void 0) {
145
308
  const config = scanAirAppConfig(sources.config);
146
309
  if (sources.shippedSlug !== void 0 && config.resourceKey !== null && config.resourceKey !== sources.shippedSlug) error("airapp/resource-key", `Config declares resourceKey "${config.resourceKey}" but the package ships this app as "${sources.shippedSlug}". Install stamps nodes with the shipped slug, so the app would not recognise its own node.`);
@@ -156,4 +319,4 @@ const checkAirApp = (sources) => {
156
319
  return findings;
157
320
  };
158
321
  //#endregion
159
- export { AIRAPP_DEFAULT_READ_LIMIT, checkAirApp, scanAirAppConfig, stripComments };
322
+ export { AIRAPP_DEFAULT_READ_LIMIT, AIRAPP_FORCED_REVIEW_MIN_REASON, checkAirApp, codeOnly, findForcedReview, isAirAppWriteRuleSource, scanAirAppConfig, stripComments };
@@ -1,4 +1,4 @@
1
- import { BusabaseAirAppAuthStatus } from "./airapp-node.js";
1
+ import { a as BusabaseAirAppAuthStatus } from "./airapp-node-D2GcMwde.js";
2
2
  //#region src/airapp-gate.d.ts
3
3
  /** Which screen the gate owes the operator right now. */
4
4
  type AirAppGateScreen = "connect" | "space" | "ready";
@@ -0,0 +1,152 @@
1
+ import { t as BusabaseAirAppCredentialStoreOptions } from "./oauth-node-DGiWkh_l.js";
2
+ //#region src/airapp-node.d.ts
3
+ /**
4
+ * The env var Busabase injects into every AirApp process it spawns. Nobody else
5
+ * sets it, which is what makes its **absence** the positive fact "standalone".
6
+ */
7
+ declare const BUSABASE_AIRAPP_RUNTIME_ENV = "BUSABASE_AIRAPP_RUNTIME";
8
+ /**
9
+ * Every runtime Busabase hosts an AirApp in, as known to *this* SDK version.
10
+ *
11
+ * **Informational. Not what decides whether an app is hosted.** That separation
12
+ * is the whole point of this constant existing here rather than being pasted
13
+ * into each app, and getting it backwards is a bug we have already shipped once:
14
+ * every generated AirApp used to carry its own hardcoded copy of this list and
15
+ * answer `hosted` from membership in it. When the `local-node` engine was
16
+ * renamed to `local`, all of them started answering "standalone" *inside a
17
+ * hosted preview* — showing their own connection gate, calling `/api/v1` with no
18
+ * credential, and leaving the operator with nothing to act on.
19
+ *
20
+ * Moving the list into the SDK does not by itself fix that. An app pins an SDK
21
+ * version, so a list that gates behaviour would simply rot on a slower clock:
22
+ * add an engine, and every app on an older SDK is wrong again until it upgrades.
23
+ *
24
+ * So the list names runtimes; {@link isBusabaseAirAppHosted} decides hosting,
25
+ * from presence alone, and needs no upgrade to stay correct.
26
+ */
27
+ declare const BUSABASE_AIRAPP_RUNTIMES: readonly ["browser", "local", "remote", "embed"];
28
+ /**
29
+ * Names Busabase has emitted in the past and may still be emitting to an app
30
+ * pinned to an older deployment. Kept separate from the list above so the
31
+ * current vocabulary stays readable, and exported so an app that genuinely
32
+ * wants to branch (a demo that prints where it is running, say) can normalise
33
+ * instead of growing its own if-chain. Nothing here gates `hosted`.
34
+ */
35
+ declare const BUSABASE_AIRAPP_RUNTIME_ALIASES: Record<string, BusabaseAirAppRuntime>;
36
+ type BusabaseAirAppRuntime = (typeof BUSABASE_AIRAPP_RUNTIMES)[number];
37
+ /** Read the injected runtime, normalised. `""` means nobody hosted this process. */
38
+ declare const readBusabaseAirAppRuntime: (env?: Record<string, string | undefined>) => string;
39
+ /**
40
+ * Whether Busabase is hosting this process.
41
+ *
42
+ * Presence, never membership — see {@link BUSABASE_AIRAPP_RUNTIMES}. An engine
43
+ * this SDK has never heard of still means "Busabase spawned me", because only
44
+ * Busabase sets the variable at all.
45
+ */
46
+ declare const isBusabaseAirAppHosted: (runtime?: string) => boolean;
47
+ /**
48
+ * Narrow a runtime string to one this SDK knows, or `null`.
49
+ *
50
+ * For code that wants to *branch* on a specific engine — an app that renders
51
+ * differently under a public embed, say. Deliberately returns `null` rather than
52
+ * throwing on an unknown value: a newer Busabase naming an engine this SDK
53
+ * predates is normal, not an error, and the caller should fall back to generic
54
+ * behaviour rather than break.
55
+ */
56
+ declare const asKnownBusabaseAirAppRuntime: (runtime: string) => BusabaseAirAppRuntime | null;
57
+ /** The body every AirApp serves at `__airapp/runtime`. */
58
+ interface BusabaseAirAppRuntimeReport {
59
+ /**
60
+ * The engine name verbatim, or `"standalone"` when nothing hosted this process.
61
+ *
62
+ * Deliberately a plain string and not the enum: a newer Busabase naming an engine
63
+ * this SDK predates must still round-trip, and narrowing here would erase it.
64
+ */
65
+ runtime: string;
66
+ /**
67
+ * The same value narrowed to what this SDK knows, or `null` for an engine it has
68
+ * never heard of. This is the field to branch on when behaviour genuinely differs
69
+ * per engine — and `null` is a normal answer, not an error.
70
+ */
71
+ knownRuntime: BusabaseAirAppRuntime | null;
72
+ /** Presence of the injected variable. Never membership of {@link BUSABASE_AIRAPP_RUNTIMES}. */
73
+ hosted: boolean;
74
+ /** Whether a standalone run is proxying to a Busabase base URL. A separate axis. */
75
+ devProxy: boolean;
76
+ }
77
+ /**
78
+ * Build the `__airapp/runtime` body.
79
+ *
80
+ * This exists because the one line it replaces — `hosted: …` — has now regressed
81
+ * twice. Both times a correct app was copied from an older source and came back
82
+ * deciding hosting from a hardcoded list of engine names, and both times every test
83
+ * stayed green, because nothing asserted the shape of that decision.
84
+ *
85
+ * An app that calls this cannot get it wrong: `hosted` is presence by construction,
86
+ * and the engine name is reported both verbatim (so an unknown one survives) and
87
+ * narrowed (so code that wants to branch on a specific engine has a type).
88
+ */
89
+ declare const describeBusabaseAirAppRuntime: (env?: Record<string, string | undefined>) => BusabaseAirAppRuntimeReport;
90
+ declare const BUSABASE_AIRAPP_GATEWAY_REASONS: {
91
+ readonly authRequired: "AUTH_REQUIRED";
92
+ readonly authUnavailable: "AUTH_UNAVAILABLE";
93
+ readonly connectionRequired: "CONNECTION_REQUIRED";
94
+ readonly oauthCallbackInvalid: "OAUTH_CALLBACK_INVALID";
95
+ readonly spaceNotAllowed: "SPACE_NOT_ALLOWED";
96
+ readonly spaceSelectionRequired: "SPACE_SELECTION_REQUIRED";
97
+ };
98
+ type GatewayReason = (typeof BUSABASE_AIRAPP_GATEWAY_REASONS)[keyof typeof BUSABASE_AIRAPP_GATEWAY_REASONS];
99
+ interface AuthSpace {
100
+ id: string;
101
+ name: string;
102
+ slug?: string | null;
103
+ plan?: string | null;
104
+ }
105
+ interface GatewayTarget {
106
+ baseUrl: string;
107
+ accessToken: string;
108
+ selectedSpace?: AuthSpace;
109
+ source: "airapp-oauth-local" | "environment" | "open-server";
110
+ }
111
+ interface BusabaseAirAppLocalGatewayOptions {
112
+ appId: string;
113
+ cloudBaseUrl?: string;
114
+ clientId?: string;
115
+ environment?: Record<string, string | undefined>;
116
+ fetch?: typeof fetch;
117
+ now?: () => number;
118
+ oauthPendingTtlMs?: number;
119
+ requestTimeoutMs?: number;
120
+ credentialStore?: BusabaseAirAppCredentialStoreOptions;
121
+ successPath?: string;
122
+ errorPath?: string;
123
+ }
124
+ interface BusabaseAirAppAuthStatus {
125
+ connected: boolean;
126
+ cloudBaseUrl: string;
127
+ baseUrl?: string;
128
+ source?: GatewayTarget["source"];
129
+ readiness: "needs_connection" | "needs_auth" | "needs_space" | "ready" | "retry";
130
+ action: "connect" | "reconnect" | "select_space" | "continue" | "retry";
131
+ requiresSpace?: boolean;
132
+ selectedSpace?: AuthSpace | null;
133
+ space?: AuthSpace | null;
134
+ spaces?: AuthSpace[];
135
+ reason?: GatewayReason;
136
+ message?: string;
137
+ }
138
+ declare class BusabaseAirAppLocalGateway {
139
+ #private;
140
+ constructor(options: BusabaseAirAppLocalGatewayOptions);
141
+ get cloudBaseUrl(): string;
142
+ status(): Promise<BusabaseAirAppAuthStatus>;
143
+ statusResponse: () => Promise<Response>;
144
+ start: (request: Request) => Promise<Response>;
145
+ callback: (request: Request) => Promise<Response>;
146
+ selectSpace: (request: Request) => Promise<Response>;
147
+ logout: (request: Request) => Promise<Response>;
148
+ proxy: (request: Request) => Promise<Response>;
149
+ }
150
+ declare const createBusabaseAirAppLocalGateway: (options: BusabaseAirAppLocalGatewayOptions) => BusabaseAirAppLocalGateway;
151
+ //#endregion
152
+ export { BusabaseAirAppAuthStatus as a, BusabaseAirAppRuntime as c, createBusabaseAirAppLocalGateway as d, describeBusabaseAirAppRuntime as f, BUSABASE_AIRAPP_RUNTIME_ENV as i, BusabaseAirAppRuntimeReport as l, readBusabaseAirAppRuntime as m, BUSABASE_AIRAPP_RUNTIMES as n, BusabaseAirAppLocalGateway as o, isBusabaseAirAppHosted as p, BUSABASE_AIRAPP_RUNTIME_ALIASES as r, BusabaseAirAppLocalGatewayOptions as s, BUSABASE_AIRAPP_GATEWAY_REASONS as t, asKnownBusabaseAirAppRuntime as u };
@@ -1,152 +1,2 @@
1
- import { t as BusabaseAirAppCredentialStoreOptions } from "./oauth-node-DGiWkh_l.js";
2
- //#region src/airapp-node.d.ts
3
- /**
4
- * The env var Busabase injects into every AirApp process it spawns. Nobody else
5
- * sets it, which is what makes its **absence** the positive fact "standalone".
6
- */
7
- declare const BUSABASE_AIRAPP_RUNTIME_ENV = "BUSABASE_AIRAPP_RUNTIME";
8
- /**
9
- * Every runtime Busabase hosts an AirApp in, as known to *this* SDK version.
10
- *
11
- * **Informational. Not what decides whether an app is hosted.** That separation
12
- * is the whole point of this constant existing here rather than being pasted
13
- * into each app, and getting it backwards is a bug we have already shipped once:
14
- * every generated AirApp used to carry its own hardcoded copy of this list and
15
- * answer `hosted` from membership in it. When the `local-node` engine was
16
- * renamed to `local`, all of them started answering "standalone" *inside a
17
- * hosted preview* — showing their own connection gate, calling `/api/v1` with no
18
- * credential, and leaving the operator with nothing to act on.
19
- *
20
- * Moving the list into the SDK does not by itself fix that. An app pins an SDK
21
- * version, so a list that gates behaviour would simply rot on a slower clock:
22
- * add an engine, and every app on an older SDK is wrong again until it upgrades.
23
- *
24
- * So the list names runtimes; {@link isBusabaseAirAppHosted} decides hosting,
25
- * from presence alone, and needs no upgrade to stay correct.
26
- */
27
- declare const BUSABASE_AIRAPP_RUNTIMES: readonly ["browser", "local", "remote", "embed"];
28
- /**
29
- * Names Busabase has emitted in the past and may still be emitting to an app
30
- * pinned to an older deployment. Kept separate from the list above so the
31
- * current vocabulary stays readable, and exported so an app that genuinely
32
- * wants to branch (a demo that prints where it is running, say) can normalise
33
- * instead of growing its own if-chain. Nothing here gates `hosted`.
34
- */
35
- declare const BUSABASE_AIRAPP_RUNTIME_ALIASES: Record<string, BusabaseAirAppRuntime>;
36
- type BusabaseAirAppRuntime = (typeof BUSABASE_AIRAPP_RUNTIMES)[number];
37
- /** Read the injected runtime, normalised. `""` means nobody hosted this process. */
38
- declare const readBusabaseAirAppRuntime: (env?: Record<string, string | undefined>) => string;
39
- /**
40
- * Whether Busabase is hosting this process.
41
- *
42
- * Presence, never membership — see {@link BUSABASE_AIRAPP_RUNTIMES}. An engine
43
- * this SDK has never heard of still means "Busabase spawned me", because only
44
- * Busabase sets the variable at all.
45
- */
46
- declare const isBusabaseAirAppHosted: (runtime?: string) => boolean;
47
- /**
48
- * Narrow a runtime string to one this SDK knows, or `null`.
49
- *
50
- * For code that wants to *branch* on a specific engine — an app that renders
51
- * differently under a public embed, say. Deliberately returns `null` rather than
52
- * throwing on an unknown value: a newer Busabase naming an engine this SDK
53
- * predates is normal, not an error, and the caller should fall back to generic
54
- * behaviour rather than break.
55
- */
56
- declare const asKnownBusabaseAirAppRuntime: (runtime: string) => BusabaseAirAppRuntime | null;
57
- /** The body every AirApp serves at `__airapp/runtime`. */
58
- interface BusabaseAirAppRuntimeReport {
59
- /**
60
- * The engine name verbatim, or `"standalone"` when nothing hosted this process.
61
- *
62
- * Deliberately a plain string and not the enum: a newer Busabase naming an engine
63
- * this SDK predates must still round-trip, and narrowing here would erase it.
64
- */
65
- runtime: string;
66
- /**
67
- * The same value narrowed to what this SDK knows, or `null` for an engine it has
68
- * never heard of. This is the field to branch on when behaviour genuinely differs
69
- * per engine — and `null` is a normal answer, not an error.
70
- */
71
- knownRuntime: BusabaseAirAppRuntime | null;
72
- /** Presence of the injected variable. Never membership of {@link BUSABASE_AIRAPP_RUNTIMES}. */
73
- hosted: boolean;
74
- /** Whether a standalone run is proxying to a Busabase base URL. A separate axis. */
75
- devProxy: boolean;
76
- }
77
- /**
78
- * Build the `__airapp/runtime` body.
79
- *
80
- * This exists because the one line it replaces — `hosted: …` — has now regressed
81
- * twice. Both times a correct app was copied from an older source and came back
82
- * deciding hosting from a hardcoded list of engine names, and both times every test
83
- * stayed green, because nothing asserted the shape of that decision.
84
- *
85
- * An app that calls this cannot get it wrong: `hosted` is presence by construction,
86
- * and the engine name is reported both verbatim (so an unknown one survives) and
87
- * narrowed (so code that wants to branch on a specific engine has a type).
88
- */
89
- declare const describeBusabaseAirAppRuntime: (env?: Record<string, string | undefined>) => BusabaseAirAppRuntimeReport;
90
- declare const BUSABASE_AIRAPP_GATEWAY_REASONS: {
91
- readonly authRequired: "AUTH_REQUIRED";
92
- readonly authUnavailable: "AUTH_UNAVAILABLE";
93
- readonly connectionRequired: "CONNECTION_REQUIRED";
94
- readonly oauthCallbackInvalid: "OAUTH_CALLBACK_INVALID";
95
- readonly spaceNotAllowed: "SPACE_NOT_ALLOWED";
96
- readonly spaceSelectionRequired: "SPACE_SELECTION_REQUIRED";
97
- };
98
- type GatewayReason = (typeof BUSABASE_AIRAPP_GATEWAY_REASONS)[keyof typeof BUSABASE_AIRAPP_GATEWAY_REASONS];
99
- interface AuthSpace {
100
- id: string;
101
- name: string;
102
- slug?: string | null;
103
- plan?: string | null;
104
- }
105
- interface GatewayTarget {
106
- baseUrl: string;
107
- accessToken: string;
108
- selectedSpace?: AuthSpace;
109
- source: "airapp-oauth-local" | "environment" | "open-server";
110
- }
111
- interface BusabaseAirAppLocalGatewayOptions {
112
- appId: string;
113
- cloudBaseUrl?: string;
114
- clientId?: string;
115
- environment?: Record<string, string | undefined>;
116
- fetch?: typeof fetch;
117
- now?: () => number;
118
- oauthPendingTtlMs?: number;
119
- requestTimeoutMs?: number;
120
- credentialStore?: BusabaseAirAppCredentialStoreOptions;
121
- successPath?: string;
122
- errorPath?: string;
123
- }
124
- interface BusabaseAirAppAuthStatus {
125
- connected: boolean;
126
- cloudBaseUrl: string;
127
- baseUrl?: string;
128
- source?: GatewayTarget["source"];
129
- readiness: "needs_connection" | "needs_auth" | "needs_space" | "ready" | "retry";
130
- action: "connect" | "reconnect" | "select_space" | "continue" | "retry";
131
- requiresSpace?: boolean;
132
- selectedSpace?: AuthSpace | null;
133
- space?: AuthSpace | null;
134
- spaces?: AuthSpace[];
135
- reason?: GatewayReason;
136
- message?: string;
137
- }
138
- declare class BusabaseAirAppLocalGateway {
139
- #private;
140
- constructor(options: BusabaseAirAppLocalGatewayOptions);
141
- get cloudBaseUrl(): string;
142
- status(): Promise<BusabaseAirAppAuthStatus>;
143
- statusResponse: () => Promise<Response>;
144
- start: (request: Request) => Promise<Response>;
145
- callback: (request: Request) => Promise<Response>;
146
- selectSpace: (request: Request) => Promise<Response>;
147
- logout: (request: Request) => Promise<Response>;
148
- proxy: (request: Request) => Promise<Response>;
149
- }
150
- declare const createBusabaseAirAppLocalGateway: (options: BusabaseAirAppLocalGatewayOptions) => BusabaseAirAppLocalGateway;
151
- //#endregion
1
+ import { a as BusabaseAirAppAuthStatus, c as BusabaseAirAppRuntime, d as createBusabaseAirAppLocalGateway, f as describeBusabaseAirAppRuntime, i as BUSABASE_AIRAPP_RUNTIME_ENV, l as BusabaseAirAppRuntimeReport, m as readBusabaseAirAppRuntime, n as BUSABASE_AIRAPP_RUNTIMES, o as BusabaseAirAppLocalGateway, p as isBusabaseAirAppHosted, r as BUSABASE_AIRAPP_RUNTIME_ALIASES, s as BusabaseAirAppLocalGatewayOptions, t as BUSABASE_AIRAPP_GATEWAY_REASONS, u as asKnownBusabaseAirAppRuntime } from "./airapp-node-D2GcMwde.js";
152
2
  export { BUSABASE_AIRAPP_GATEWAY_REASONS, BUSABASE_AIRAPP_RUNTIMES, BUSABASE_AIRAPP_RUNTIME_ALIASES, BUSABASE_AIRAPP_RUNTIME_ENV, BusabaseAirAppAuthStatus, BusabaseAirAppLocalGateway, BusabaseAirAppLocalGatewayOptions, BusabaseAirAppRuntime, BusabaseAirAppRuntimeReport, asKnownBusabaseAirAppRuntime, createBusabaseAirAppLocalGateway, describeBusabaseAirAppRuntime, isBusabaseAirAppHosted, readBusabaseAirAppRuntime };