@persql/sdk 1.2.1 → 1.4.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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PerSQL
4
- } from "./chunk-VJ776W3K.js";
4
+ } from "./chunk-5ELQCDXS.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { writeFile } from "fs/promises";
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
 
@@ -197,7 +199,104 @@ function runOne(db, sql, params) {
197
199
  return { columns: cols, rows, rowsRead: rows.length, rowsWritten: 0 };
198
200
  }
199
201
  const info = stmt.run(...params);
200
- return { columns: [], rows: [], rowsRead: 0, rowsWritten: info.changes ?? 0 };
202
+ return {
203
+ columns: [],
204
+ rows: [],
205
+ rowsRead: 0,
206
+ rowsWritten: info.changes ?? 0,
207
+ changes: info.changes ?? 0,
208
+ lastInsertRowid: Number(info.lastInsertRowid ?? 0)
209
+ };
210
+ }
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;
201
300
  }
202
301
 
203
302
  // src/index.ts
@@ -309,6 +408,38 @@ var PerSQL = class _PerSQL {
309
408
  const client = new _PerSQL({ token: plaintext, baseURL, fetch: opts.fetch });
310
409
  return Object.assign(client, { handedOff });
311
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
+ }
312
443
  database(a, b) {
313
444
  if (b !== void 0) return new PerSQLDatabase(this, a, b);
314
445
  const [ns, slug] = a.split("/");
@@ -2078,6 +2209,8 @@ function rowsToObjects(columns, rows) {
2078
2209
  PerSQLError,
2079
2210
  PerSQLProposals,
2080
2211
  RateLimitError,
2081
- SupportClient
2212
+ SupportClient,
2213
+ createAuthorizeUrl,
2214
+ exchangeAuthorizationCode
2082
2215
  });
2083
2216
  //# sourceMappingURL=index.cjs.map