busa-sdk 0.17.2 → 0.18.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.
- package/dist/airapp-gate.d.ts +74 -114
- package/dist/airapp-gate.js +231 -229
- package/dist/airapp-node.d.ts +97 -52
- package/dist/airapp-node.js +422 -444
- package/dist/airapp.d.ts +102 -147
- package/dist/airapp.js +432 -423
- package/dist/client-CbUceGzy.d.ts +20523 -0
- package/dist/index.d.ts +2807 -2739
- package/dist/index.js +4803 -4029
- package/dist/oauth-Dg2Z41RP.js +173 -0
- package/dist/oauth-FfuT0EGC.d.ts +69 -0
- package/dist/oauth-node-DGiWkh_l.d.ts +64 -0
- package/dist/oauth-node.d.ts +2 -64
- package/dist/oauth-node.js +188 -3
- package/dist/oauth.d.ts +2 -67
- package/dist/oauth.js +2 -2
- package/dist/url-B8GMXalA.js +10 -0
- package/package.json +1 -1
- package/dist/chunk-5NYQX65A.js +0 -6
- package/dist/chunk-C23JVY2Y.js +0 -211
- package/dist/chunk-WSHJMHUS.js +0 -215
- package/dist/client-DIlpYL9z.d.ts +0 -17818
package/dist/airapp-gate.js
CHANGED
|
@@ -1,238 +1,240 @@
|
|
|
1
|
-
|
|
1
|
+
//#region src/airapp-gate.ts
|
|
2
|
+
/**
|
|
3
|
+
* Decide the screen from the gateway's status. Headless — a custom renderer
|
|
4
|
+
* should call this rather than re-deriving the rules from `connected`.
|
|
5
|
+
*
|
|
6
|
+
* Driven by `readiness`, with a fallback to the older `connected` /
|
|
7
|
+
* `requiresSpace` pair so an app can upgrade the SDK before its server.
|
|
8
|
+
*/
|
|
2
9
|
function selectAirAppGateScreen(status) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
if (!status.connected) return "connect";
|
|
14
|
-
return status.requiresSpace ? "space" : "ready";
|
|
10
|
+
if (!status) return "connect";
|
|
11
|
+
switch (status.readiness) {
|
|
12
|
+
case "needs_connection":
|
|
13
|
+
case "needs_auth": return "connect";
|
|
14
|
+
case "needs_space": return "space";
|
|
15
|
+
case "ready": return "ready";
|
|
16
|
+
}
|
|
17
|
+
if (!status.connected) return "connect";
|
|
18
|
+
return status.requiresSpace ? "space" : "ready";
|
|
15
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Decode a setup failure. Accepts an `AirAppSetupError`, any `Error` whose
|
|
22
|
+
* message uses the historical `"CODE: detail"` shape, or a bare string — the
|
|
23
|
+
* generated apps throw all three spellings.
|
|
24
|
+
*/
|
|
16
25
|
function describeAirAppSetupError(error) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
canRetry: resolvedCode === "SETUP_PENDING" || resolvedCode === "SCHEMA_INCOMPLETE"
|
|
32
|
-
};
|
|
26
|
+
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "";
|
|
27
|
+
const raw = String((typeof error === "object" && error !== null && "message" in error ? error.message : error) ?? "SETUP_REQUIRED");
|
|
28
|
+
const parsed = /^([A-Z_]+):\s*(.*)$/s.exec(raw);
|
|
29
|
+
const resolvedCode = code || parsed?.[1] || raw.trim() || "SETUP_REQUIRED";
|
|
30
|
+
const detail = parsed ? parsed[2] : code ? raw : "";
|
|
31
|
+
const pending = resolvedCode === "SETUP_PENDING";
|
|
32
|
+
const canProvision = resolvedCode === "SETUP_REQUIRED";
|
|
33
|
+
return {
|
|
34
|
+
code: resolvedCode,
|
|
35
|
+
detail,
|
|
36
|
+
title: pending ? "Waiting for workspace approval" : canProvision ? "Initialize the Busabase workspace" : "Workspace not ready",
|
|
37
|
+
canProvision,
|
|
38
|
+
canRetry: resolvedCode === "SETUP_PENDING" || resolvedCode === "SCHEMA_INCOMPLETE"
|
|
39
|
+
};
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return panel("bbGateWorkspaceTitle", head, body, footer);
|
|
57
|
-
}
|
|
41
|
+
/** HTML-escape. Every interpolation below goes through this. */
|
|
42
|
+
const escapeHtml = (value) => String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
43
|
+
const panel = (labelledBy, head, body, footer) => `<div class="bb-gate-overlay"><section class="bb-gate-panel" role="dialog" aria-modal="true" aria-labelledby="${labelledBy}"><div class="bb-gate-head"><div>${head}</div></div><div class="bb-gate-body">${body}</div><div class="bb-gate-footer">${footer}</div></section></div>`;
|
|
44
|
+
/** The stock three screens. Pure string builders — no DOM, no fetch. */
|
|
45
|
+
const defaultAirAppGateRenderer = {
|
|
46
|
+
connect(view) {
|
|
47
|
+
const head = `<h1 id="bbGateConnectTitle">Connect Busabase</h1><p>${escapeHtml(view.appName)} reads and writes through your Busabase workspace.</p>`;
|
|
48
|
+
const body = (view.oauthError ? `<p class="bb-gate-error" role="alert">${escapeHtml(view.oauthError)}</p>` : "") + (view.reconnect ? `<p class="bb-gate-note">Your session expired. Reconnect to continue.</p>` : "") + `<h2>Server</h2><div class="bb-gate-server-grid"><label class="bb-gate-server-card is-selected"><input type="radio" name="server_mode" value="cloud" checked><span><strong>Busabase Cloud</strong><span>${escapeHtml(hostOf(view.cloudBaseUrl))}</span></span></label><label class="bb-gate-server-card"><input type="radio" name="server_mode" value="custom"><span><strong>Custom server</strong><span>Self-hosted or enterprise address</span></span></label></div><label class="bb-gate-custom-url" data-custom-url hidden><span>Busabase URL</span><input type="url" name="custom_base_url" inputmode="url" placeholder="https://busabase.example.com" autocomplete="url"></label><input type="hidden" name="base_url" value="${escapeHtml(view.cloudBaseUrl)}">`;
|
|
49
|
+
return `<form method="post" action="${escapeHtml(`${view.authBasePath}/auth/start`)}" data-connect-form>${panel("bbGateConnectTitle", head, body, "<span class=\"bb-gate-note\">OAuth credentials stay on this machine (~/.busabase/airapps)</span><button class=\"bb-gate-primary\" type=\"submit\">Connect Busabase</button>")}</form>`;
|
|
50
|
+
},
|
|
51
|
+
space(view) {
|
|
52
|
+
const options = view.spaces.map((space) => `<option value="${escapeHtml(space.id)}">${escapeHtml(space.name)} · ${escapeHtml(space.id)}</option>`).join("");
|
|
53
|
+
const head = `<h1 id="bbGateSpaceTitle">Choose a Busabase Space</h1><p>Signed in to <strong>${escapeHtml(view.baseUrl)}</strong>. Choose where ${escapeHtml(view.appName)}'s data lives.</p>`;
|
|
54
|
+
const body = `<label class="bb-gate-space-select"><span>Space</span><select name="space_id" required>${options}</select></label><p class="bb-gate-error" data-space-error hidden></p>`;
|
|
55
|
+
return `<form data-space-form>${panel("bbGateSpaceTitle", head, body, "<span class=\"bb-gate-note\">Resources are only checked after you confirm</span><button class=\"bb-gate-primary\" type=\"submit\">Use this Space</button>")}</form>`;
|
|
56
|
+
},
|
|
57
|
+
workspace(view) {
|
|
58
|
+
const head = `<h1 id="bbGateWorkspaceTitle">${escapeHtml(view.title)}</h1>`;
|
|
59
|
+
const body = view.canProvision ? `<p>${escapeHtml(view.appName)} will create its Folder and Bases in the current Space.</p><p>Submitted as one idempotent Busabase ChangeRequest; nothing existing is deleted or repurposed.</p><p class="bb-gate-error" data-workspace-status hidden></p>` : `<p>${escapeHtml(view.detail)}</p><p>${escapeHtml(view.appName)} never asks you to create Nodes or Bases by hand, and never silently falls back to local data.</p><p class="bb-gate-error" data-workspace-status hidden></p>`;
|
|
60
|
+
const footer = (view.demoHref ? `<a class="bb-gate-link" href="${escapeHtml(view.demoHref)}">Open the read-only demo</a>` : "<span></span>") + (view.canProvision ? `<button class="bb-gate-primary" type="button" data-provision>Initialize workspace</button>` : view.canRetry ? `<button class="bb-gate-primary" type="button" data-retry>Check again</button>` : "");
|
|
61
|
+
return panel("bbGateWorkspaceTitle", head, body, footer);
|
|
62
|
+
}
|
|
58
63
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
const hostOf = (baseUrl) => {
|
|
65
|
+
try {
|
|
66
|
+
return new URL(baseUrl).host;
|
|
67
|
+
} catch {
|
|
68
|
+
return baseUrl;
|
|
69
|
+
}
|
|
65
70
|
};
|
|
66
|
-
|
|
71
|
+
const DEFAULT_CLOUD_BASE_URL = "https://busabase.com";
|
|
72
|
+
/**
|
|
73
|
+
* Build a gate bound to one app.
|
|
74
|
+
*
|
|
75
|
+
* Nothing is rendered until `pass()` decides a screen is owed, so it is safe to
|
|
76
|
+
* construct at module scope.
|
|
77
|
+
*/
|
|
67
78
|
function createAirAppConnectGate(options) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
close();
|
|
228
|
-
onReady?.();
|
|
229
|
-
});
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
renderConnect(current);
|
|
233
|
-
return false;
|
|
234
|
-
};
|
|
235
|
-
return { pass, renderSetupRequired, close, status };
|
|
79
|
+
const { appName, authBasePath = "", onProvision, demoHref = null, render = defaultAirAppGateRenderer } = options;
|
|
80
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
81
|
+
const root = () => {
|
|
82
|
+
if (options.mount) {
|
|
83
|
+
const element = typeof options.mount === "string" ? document.querySelector(options.mount) : options.mount;
|
|
84
|
+
if (!element) throw new Error(`AirApp gate mount not found: ${String(options.mount)}`);
|
|
85
|
+
document.documentElement.classList.add("bb-gate-active");
|
|
86
|
+
return element;
|
|
87
|
+
}
|
|
88
|
+
let element = document.querySelector("#busabaseAirAppGate");
|
|
89
|
+
if (!element) {
|
|
90
|
+
element = document.createElement("div");
|
|
91
|
+
element.id = "busabaseAirAppGate";
|
|
92
|
+
document.body.prepend(element);
|
|
93
|
+
}
|
|
94
|
+
document.documentElement.classList.add("bb-gate-active");
|
|
95
|
+
return element;
|
|
96
|
+
};
|
|
97
|
+
const close = () => {
|
|
98
|
+
if (options.mount) {
|
|
99
|
+
const element = typeof options.mount === "string" ? document.querySelector(options.mount) : options.mount;
|
|
100
|
+
if (element) element.innerHTML = "";
|
|
101
|
+
} else document.querySelector("#busabaseAirAppGate")?.remove();
|
|
102
|
+
document.documentElement.classList.remove("bb-gate-active");
|
|
103
|
+
};
|
|
104
|
+
const status = async () => {
|
|
105
|
+
try {
|
|
106
|
+
const response = await doFetch(`${authBasePath}/auth/status`, { headers: { accept: "application/json" } });
|
|
107
|
+
const type = response.headers.get("content-type") ?? "";
|
|
108
|
+
if (!response.ok || !type.includes("application/json")) return null;
|
|
109
|
+
return await response.json();
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const renderConnect = (current) => {
|
|
115
|
+
const element = root();
|
|
116
|
+
const oauthError = new URLSearchParams(window.location.search).get("oauth_error") ?? "";
|
|
117
|
+
element.innerHTML = render.connect({
|
|
118
|
+
appName,
|
|
119
|
+
cloudBaseUrl: current?.cloudBaseUrl || "https://busabase.com",
|
|
120
|
+
reconnect: current?.readiness === "needs_auth",
|
|
121
|
+
oauthError,
|
|
122
|
+
authBasePath
|
|
123
|
+
});
|
|
124
|
+
wireConnect(element, current?.cloudBaseUrl || "https://busabase.com");
|
|
125
|
+
};
|
|
126
|
+
const renderSpace = (current, onReady) => {
|
|
127
|
+
const element = root();
|
|
128
|
+
element.innerHTML = render.space({
|
|
129
|
+
appName,
|
|
130
|
+
baseUrl: current.baseUrl ?? "",
|
|
131
|
+
spaces: current.spaces ?? []
|
|
132
|
+
});
|
|
133
|
+
wireSpace(element, onReady);
|
|
134
|
+
};
|
|
135
|
+
const wireConnect = (element, cloudBaseUrl) => {
|
|
136
|
+
const form = element.querySelector("[data-connect-form]");
|
|
137
|
+
if (!form) return;
|
|
138
|
+
const customField = form.querySelector("[data-custom-url]");
|
|
139
|
+
const customInput = customField?.querySelector("input") ?? null;
|
|
140
|
+
const hiddenBaseUrl = form.querySelector("input[name=\"base_url\"]");
|
|
141
|
+
for (const radio of form.querySelectorAll("input[name=\"server_mode\"]")) radio.addEventListener("change", () => {
|
|
142
|
+
const custom = radio.value === "custom";
|
|
143
|
+
for (const card of form.querySelectorAll(".bb-gate-server-card")) card.classList.toggle("is-selected", card.querySelector("input")?.checked === true);
|
|
144
|
+
if (customField) customField.hidden = !custom;
|
|
145
|
+
if (customInput) customInput.required = custom;
|
|
146
|
+
if (hiddenBaseUrl) hiddenBaseUrl.value = custom ? customInput?.value ?? "" : cloudBaseUrl;
|
|
147
|
+
if (custom) customInput?.focus();
|
|
148
|
+
});
|
|
149
|
+
customInput?.addEventListener("input", () => {
|
|
150
|
+
if (hiddenBaseUrl) hiddenBaseUrl.value = customInput.value;
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
const wireSpace = (element, onReady) => {
|
|
154
|
+
const form = element.querySelector("[data-space-form]");
|
|
155
|
+
form?.addEventListener("submit", async (event) => {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
const button = form.querySelector("button[type=submit]");
|
|
158
|
+
const error = form.querySelector("[data-space-error]");
|
|
159
|
+
if (button) button.disabled = true;
|
|
160
|
+
if (error) error.hidden = true;
|
|
161
|
+
try {
|
|
162
|
+
const response = await doFetch(`${authBasePath}/auth/space`, {
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
165
|
+
body: new URLSearchParams(new FormData(form))
|
|
166
|
+
});
|
|
167
|
+
const result = await response.json();
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
if (error) {
|
|
170
|
+
error.textContent = result.error || "Could not select a Space.";
|
|
171
|
+
error.hidden = false;
|
|
172
|
+
}
|
|
173
|
+
if (button) button.disabled = false;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
onReady();
|
|
177
|
+
} catch {
|
|
178
|
+
if (error) {
|
|
179
|
+
error.textContent = "Could not reach this app's server.";
|
|
180
|
+
error.hidden = false;
|
|
181
|
+
}
|
|
182
|
+
if (button) button.disabled = false;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
const renderSetupRequired = (error, onRetry) => {
|
|
187
|
+
const element = root();
|
|
188
|
+
element.innerHTML = render.workspace({
|
|
189
|
+
...describeAirAppSetupError(error),
|
|
190
|
+
appName,
|
|
191
|
+
demoHref
|
|
192
|
+
});
|
|
193
|
+
element.querySelector("[data-retry]")?.addEventListener("click", () => onRetry());
|
|
194
|
+
element.querySelector("[data-provision]")?.addEventListener("click", async (event) => {
|
|
195
|
+
const button = event.currentTarget;
|
|
196
|
+
const line = element.querySelector("[data-workspace-status]");
|
|
197
|
+
button.disabled = true;
|
|
198
|
+
if (line) {
|
|
199
|
+
line.hidden = false;
|
|
200
|
+
line.textContent = "Submitting the workspace structure…";
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await onProvision?.();
|
|
204
|
+
onRetry();
|
|
205
|
+
} catch (provisionError) {
|
|
206
|
+
renderSetupRequired(provisionError, onRetry);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
const pass = async ({ onReady } = {}) => {
|
|
211
|
+
if (options.shouldGate && !await options.shouldGate()) {
|
|
212
|
+
close();
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
const current = await status();
|
|
216
|
+
if (!current) return true;
|
|
217
|
+
const screen = selectAirAppGateScreen(current);
|
|
218
|
+
if (screen === "ready") {
|
|
219
|
+
close();
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
if (screen === "space") {
|
|
223
|
+
renderSpace(current, () => {
|
|
224
|
+
close();
|
|
225
|
+
onReady?.();
|
|
226
|
+
});
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
renderConnect(current);
|
|
230
|
+
return false;
|
|
231
|
+
};
|
|
232
|
+
return {
|
|
233
|
+
pass,
|
|
234
|
+
renderSetupRequired,
|
|
235
|
+
close,
|
|
236
|
+
status
|
|
237
|
+
};
|
|
236
238
|
}
|
|
237
|
-
|
|
239
|
+
//#endregion
|
|
238
240
|
export { DEFAULT_CLOUD_BASE_URL, createAirAppConnectGate, defaultAirAppGateRenderer, describeAirAppSetupError, escapeHtml, selectAirAppGateScreen };
|