@persql/sdk 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/{chunk-SIJU2P7U.js → chunk-HTQP2S7I.js} +135 -1
- package/dist/chunk-HTQP2S7I.js.map +1 -0
- package/dist/cli.cjs +132 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +138 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -1
- package/dist/index.d.ts +83 -1
- package/dist/index.js +7 -3
- package/package.json +1 -1
- package/dist/chunk-SIJU2P7U.js.map +0 -1
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,9 @@ __export(index_exports, {
|
|
|
30
30
|
PerSQLError: () => PerSQLError,
|
|
31
31
|
PerSQLProposals: () => PerSQLProposals,
|
|
32
32
|
RateLimitError: () => RateLimitError,
|
|
33
|
-
SupportClient: () => SupportClient
|
|
33
|
+
SupportClient: () => SupportClient,
|
|
34
|
+
createAuthorizeUrl: () => createAuthorizeUrl,
|
|
35
|
+
exchangeAuthorizationCode: () => exchangeAuthorizationCode
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(index_exports);
|
|
36
38
|
|
|
@@ -207,6 +209,96 @@ function runOne(db, sql, params) {
|
|
|
207
209
|
};
|
|
208
210
|
}
|
|
209
211
|
|
|
212
|
+
// src/connect.ts
|
|
213
|
+
async function createAuthorizeUrl(opts) {
|
|
214
|
+
const base = (opts.baseURL ?? "https://api.persql.com").replace(/\/$/, "");
|
|
215
|
+
const codeVerifier = opts.codeVerifier ?? randomUrlBytes(32);
|
|
216
|
+
const state = opts.state ?? randomUrlBytes(16);
|
|
217
|
+
const challenge = await sha256Base64Url(codeVerifier);
|
|
218
|
+
const params = new URLSearchParams({
|
|
219
|
+
response_type: "code",
|
|
220
|
+
client_id: opts.clientId,
|
|
221
|
+
redirect_uri: opts.redirectUri,
|
|
222
|
+
scope: opts.scope ?? "database",
|
|
223
|
+
code_challenge: challenge,
|
|
224
|
+
code_challenge_method: "S256",
|
|
225
|
+
state
|
|
226
|
+
});
|
|
227
|
+
return { url: `${base}/oauth/authorize?${params.toString()}`, codeVerifier, state };
|
|
228
|
+
}
|
|
229
|
+
async function exchangeAuthorizationCode(opts) {
|
|
230
|
+
const base = (opts.baseURL ?? "https://api.persql.com").replace(/\/$/, "");
|
|
231
|
+
const fetcher = opts.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
232
|
+
if (!fetcher) throw new Error("PerSQL connect: no fetch available \u2014 pass { fetch }.");
|
|
233
|
+
const body = new URLSearchParams({
|
|
234
|
+
grant_type: "authorization_code",
|
|
235
|
+
code: opts.code,
|
|
236
|
+
client_id: opts.clientId,
|
|
237
|
+
redirect_uri: opts.redirectUri,
|
|
238
|
+
code_verifier: opts.codeVerifier
|
|
239
|
+
});
|
|
240
|
+
if (opts.clientSecret) body.set("client_secret", opts.clientSecret);
|
|
241
|
+
const res = await fetcher(`${base}/oauth/token`, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
244
|
+
body: body.toString()
|
|
245
|
+
});
|
|
246
|
+
const json = await res.json().catch(() => null);
|
|
247
|
+
if (!res.ok || !json?.access_token || !json.database) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
json?.error_description ?? json?.error ?? `PerSQL connect: token exchange failed (${res.status})`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
accessToken: json.access_token,
|
|
254
|
+
database: json.database,
|
|
255
|
+
apiUrl: json.api_url ?? base,
|
|
256
|
+
scope: json.scope ?? "",
|
|
257
|
+
idToken: json.id_token,
|
|
258
|
+
userEmail: json.user_email
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function getCrypto() {
|
|
262
|
+
const c = globalThis.crypto;
|
|
263
|
+
if (!c?.getRandomValues) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
'PerSQL connect: Web Crypto is unavailable. In bare React Native, import "react-native-get-random-values" and a Web Crypto SHA-256 polyfill, or exchange the code on your server.'
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return c;
|
|
269
|
+
}
|
|
270
|
+
function randomUrlBytes(n) {
|
|
271
|
+
const bytes = new Uint8Array(n);
|
|
272
|
+
getCrypto().getRandomValues(bytes);
|
|
273
|
+
return base64url(bytes);
|
|
274
|
+
}
|
|
275
|
+
async function sha256Base64Url(input) {
|
|
276
|
+
const c = getCrypto();
|
|
277
|
+
if (!c.subtle) {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"PerSQL connect: crypto.subtle is unavailable (needed for PKCE S256). Polyfill Web Crypto, or exchange the code on your server."
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const digest = await c.subtle.digest("SHA-256", new TextEncoder().encode(input));
|
|
283
|
+
return base64url(new Uint8Array(digest));
|
|
284
|
+
}
|
|
285
|
+
var B64URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
286
|
+
function base64url(bytes) {
|
|
287
|
+
let out = "";
|
|
288
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
289
|
+
const b0 = bytes[i];
|
|
290
|
+
const b1 = bytes[i + 1];
|
|
291
|
+
const b2 = bytes[i + 2];
|
|
292
|
+
out += B64URL[b0 >> 2];
|
|
293
|
+
out += B64URL[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
|
|
294
|
+
if (b1 === void 0) break;
|
|
295
|
+
out += B64URL[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
|
|
296
|
+
if (b2 === void 0) break;
|
|
297
|
+
out += B64URL[b2 & 63];
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
|
|
210
302
|
// src/index.ts
|
|
211
303
|
var PerSQLError = class extends Error {
|
|
212
304
|
status;
|
|
@@ -316,6 +408,38 @@ var PerSQL = class _PerSQL {
|
|
|
316
408
|
const client = new _PerSQL({ token: plaintext, baseURL, fetch: opts.fetch });
|
|
317
409
|
return Object.assign(client, { handedOff });
|
|
318
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Step 1 of the `database`-scope sign-in (user-owned app databases):
|
|
413
|
+
* build the `/oauth/authorize` URL plus the PKCE verifier + state to
|
|
414
|
+
* keep. Open `url` in a browser / in-app browser / Expo AuthSession,
|
|
415
|
+
* then call {@link PerSQL.completeConnect} with the returned `code`.
|
|
416
|
+
*
|
|
417
|
+
* const req = await PerSQL.beginConnect({
|
|
418
|
+
* clientId, redirectUri, scope: "openid database",
|
|
419
|
+
* });
|
|
420
|
+
* // open req.url, capture { code, state }; verify state === req.state
|
|
421
|
+
*/
|
|
422
|
+
static beginConnect(opts) {
|
|
423
|
+
return createAuthorizeUrl(opts);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Step 2: exchange the redirect's `code` for a scoped database token and
|
|
427
|
+
* get back a ready client. The granted database is on `.grant.database`.
|
|
428
|
+
*
|
|
429
|
+
* const persql = await PerSQL.completeConnect({
|
|
430
|
+
* clientId, redirectUri, code, codeVerifier: req.codeVerifier,
|
|
431
|
+
* });
|
|
432
|
+
* const db = persql.database(persql.grant.database);
|
|
433
|
+
*/
|
|
434
|
+
static async completeConnect(opts) {
|
|
435
|
+
const grant = await exchangeAuthorizationCode(opts);
|
|
436
|
+
const client = new _PerSQL({
|
|
437
|
+
token: grant.accessToken,
|
|
438
|
+
baseURL: grant.apiUrl,
|
|
439
|
+
fetch: opts.fetch
|
|
440
|
+
});
|
|
441
|
+
return Object.assign(client, { grant });
|
|
442
|
+
}
|
|
319
443
|
database(a, b) {
|
|
320
444
|
if (b !== void 0) return new PerSQLDatabase(this, a, b);
|
|
321
445
|
const [ns, slug] = a.split("/");
|
|
@@ -480,6 +604,16 @@ var PerSQLDatabases = class {
|
|
|
480
604
|
}
|
|
481
605
|
return { data: body.data ?? [], nextCursor: body.nextCursor ?? null };
|
|
482
606
|
}
|
|
607
|
+
/** Delete a database by slug. Requires an admin-role unscoped token. */
|
|
608
|
+
async delete(slug) {
|
|
609
|
+
if (this.client.local) {
|
|
610
|
+
throw new Error("PerSQL: databases.delete() needs a server-mode token.");
|
|
611
|
+
}
|
|
612
|
+
return this.client.request(
|
|
613
|
+
"DELETE",
|
|
614
|
+
`/v1/databases/${encodeURIComponent(slug)}`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
483
617
|
};
|
|
484
618
|
var PerSQLDatabase = class {
|
|
485
619
|
constructor(client, namespace, slug) {
|
|
@@ -2085,6 +2219,8 @@ function rowsToObjects(columns, rows) {
|
|
|
2085
2219
|
PerSQLError,
|
|
2086
2220
|
PerSQLProposals,
|
|
2087
2221
|
RateLimitError,
|
|
2088
|
-
SupportClient
|
|
2222
|
+
SupportClient,
|
|
2223
|
+
createAuthorizeUrl,
|
|
2224
|
+
exchangeAuthorizationCode
|
|
2089
2225
|
});
|
|
2090
2226
|
//# sourceMappingURL=index.cjs.map
|