bisonbase 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,113 @@
1
- # Temporary Holding Version
1
+ # bisonbase
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript and React client for [bisonbase](../README.md). Users sign in with their bisonbase
4
+ account; the app declares typed tables and reads and writes them without writing SQL.
5
+
6
+ > **Status: early.** Only per-user databases (`createUserDb`) exist so far: each signed-in user
7
+ > gets their own SQLite file in `apps/<appId>/` in their account. Shared app databases come later.
8
+
9
+ ```tsx
10
+ import { BisonBase, type RowOf } from 'bisonbase'
11
+ import { AuthElement } from 'bisonbase/react'
12
+
13
+ // A UUID that identifies the app; make one with crypto.randomUUID() and keep it.
14
+ const bb = new BisonBase('f1487da1-14ff-4998-a2c4-90b6353fe8c3', 'My Todo App', {
15
+ url: 'https://bisonbase.example.com', // default: /bb on the page's origin
16
+ })
17
+
18
+ const todos = bb.createUserDb('todos').createTable('todos', {
19
+ content: 'text',
20
+ done: ['boolean', false], // [type, default]
21
+ })
22
+ type Todo = RowOf<typeof todos> // { id: number; content: string; done: boolean }
23
+
24
+ function App() {
25
+ const myTodos = todos.useSelect({ orderBy: 'id' }) // [] until signed in and loaded
26
+ return <>
27
+ <AuthElement />
28
+ {myTodos.map((t) => <p key={t.id}>{t.content}</p>)}
29
+ <button onClick={() => todos.insert({ content: 'New' })}>Add</button>
30
+ </>
31
+ }
32
+ ```
33
+
34
+ [examples/ts/react-todo](../examples/ts/react-todo) is a complete app.
35
+
36
+ ## Tables
37
+
38
+ `createTable(name, columns)` declares a table. Every table also gets `id`, an integer primary key.
39
+
40
+ | Column spec | TypeScript type | Stored as |
41
+ |---|---|---|
42
+ | `"text"` | `string` | `TEXT` |
43
+ | `"integer"` | `number` (safe integer) | `INTEGER` |
44
+ | `"real"` | `number` | `REAL` |
45
+ | `"boolean"` | `boolean` | `BOOLEAN` (0/1) |
46
+ | `"blob"` | `Uint8Array` | `BLOB` |
47
+ | `"json"` | `Json` | `JSON` (text) |
48
+ | `"text?"` etc. | `string \| null` | nullable |
49
+ | `["text", "x"]` | `string` | with a default |
50
+
51
+ Nullable columns and columns with a default may be left out of `insert`.
52
+
53
+ When a signed-in user first touches a database, the library creates it and its tables, and
54
+ adds columns that are new in the code (a new non-nullable column needs a default). It never
55
+ changes a column's type, drops a column or deletes data: if the database doesn't match the
56
+ code in one of those ways, operations fail with a `SchemaError` that says what's different.
57
+
58
+ ## Reading and writing
59
+
60
+ ```ts
61
+ await todos.select({ where: { done: false }, orderBy: ['done', 'id desc'], limit: 20 })
62
+ await todos.select({ where: { id: { $gte: 10, $lt: 20 }, content: { $in: ['a', 'b'] } } })
63
+ await todos.insert({ content: 'a' }) // returns the row, with id and defaults
64
+ await todos.insert([{ content: 'b' }, { content: 'c' }]) // one transaction
65
+ await todos.update(3, { done: true }) // by id; returns the number changed
66
+ await todos.update({ done: true }, { content: 'x' }) // by where; {} means every row
67
+ await todos.delete({ done: true })
68
+ ```
69
+
70
+ `where` matches when every listed column matches; `null` matches NULL. Operators are `$ne`,
71
+ `$lt`, `$lte`, `$gt`, `$gte` and `$in`.
72
+
73
+ Raw SQL works on per-user databases, with interpolated values sent as parameters:
74
+
75
+ ```ts
76
+ const [{ n }] = await todos.db.sql`SELECT count(*) AS n FROM todos WHERE done = ${false}`
77
+ ```
78
+
79
+ ## React
80
+
81
+ Import `bisonbase/react` once (importing `AuthElement` or `useAuth` does it).
82
+
83
+ - `table.useSelect(options?)`: the matching rows, kept up to date. Empty while signed out or
84
+ loading. Components using the same options share one query.
85
+ - `table.useSelectState(options?)`: `{ rows, loading, error }`.
86
+ - `useAuth()`: `{ status: 'signed-out' | 'pending' | 'signed-in', error? }`.
87
+ - `<AuthElement />`: a sign-in button, or a sign-out button once signed in. Unstyled; target
88
+ `.bb-auth`, `.bb-auth-error`.
89
+
90
+ Queries are fetched again after any write through the library to the same table, and when the
91
+ window regains focus. Changes made elsewhere (another device) show up on focus; the server
92
+ doesn't push changes yet.
93
+
94
+ Outside React, `table.liveQuery(options)` returns the same shared query, with `subscribe` and
95
+ `getSnapshot`.
96
+
97
+ ## Signing in
98
+
99
+ `bb.signIn()` sends the browser to bisonbase ("Sign in with bisonbase", OAuth 2.1 with PKCE). When
100
+ it comes back, the constructor takes the code out of the address bar and trades it for a token,
101
+ which is kept in `localStorage`. `bb.signOut()` forgets and revokes it. `bb.auth` and
102
+ `bb.onAuthChange(fn)` give the state outside React.
103
+
104
+ In scripts and tests, pass a token instead: `new BisonBase(id, name, { url, token })`.
105
+
106
+ ## Develop
107
+
108
+ ```sh
109
+ npm install
110
+ npm run typecheck # includes the type tests in test/types.test.ts
111
+ npm test # unit tests, plus end-to-end tests if ../node has been built
112
+ npm run build # dist/
113
+ ```
package/dist/auth.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export interface AuthConfig {
2
+ server: string;
3
+ appId: string;
4
+ appName: string;
5
+ scope: string;
6
+ }
7
+ /** Send the browser to bisonbase to sign in. */
8
+ export declare function startSignIn(cfg: AuthConfig): Promise<void>;
9
+ /**
10
+ * If bisonbase just sent the browser back here, take the response out of the address bar
11
+ * (synchronously, before the app renders) and return a promise for the token.
12
+ */
13
+ export declare function takeSignInResponse(server: string): Promise<string> | null;
14
+ /** Revoke a token on the server. Failures are ignored: the token is forgotten locally anyway. */
15
+ export declare function revoke(server: string, token: string): Promise<void>;
16
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAOD,gDAAgD;AAChD,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBhE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAUzE;AAqBD,iGAAiG;AACjG,wBAAsB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE"}
package/dist/auth.js ADDED
@@ -0,0 +1,75 @@
1
+ // "Sign in with bisonbase": the OAuth 2.1 authorization code flow with PKCE, in the browser.
2
+ const base64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
3
+ const randomString = () => base64url(crypto.getRandomValues(new Uint8Array(32)));
4
+ const sha256 = async (s) => base64url(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s))));
5
+ const FLOW_KEY = 'bisonbase:sign-in';
6
+ // An app is identified by its origin, and bisonbase sends the user back to the current page.
7
+ const clientId = () => location.origin;
8
+ const redirectUri = () => location.origin + location.pathname;
9
+ /** Send the browser to bisonbase to sign in. */
10
+ export async function startSignIn(cfg) {
11
+ const flow = {
12
+ state: randomString(),
13
+ verifier: randomString(),
14
+ server: cfg.server,
15
+ back: location.pathname + location.search + location.hash,
16
+ };
17
+ sessionStorage.setItem(FLOW_KEY, JSON.stringify(flow));
18
+ const params = new URLSearchParams({
19
+ response_type: 'code',
20
+ client_id: clientId(),
21
+ redirect_uri: redirectUri(),
22
+ scope: cfg.scope,
23
+ state: flow.state,
24
+ code_challenge: await sha256(flow.verifier),
25
+ code_challenge_method: 'S256',
26
+ app_id: cfg.appId,
27
+ app_name: cfg.appName,
28
+ });
29
+ location.assign(`${cfg.server}/oauth/authorize?${params}`);
30
+ }
31
+ /**
32
+ * If bisonbase just sent the browser back here, take the response out of the address bar
33
+ * (synchronously, before the app renders) and return a promise for the token.
34
+ */
35
+ export function takeSignInResponse(server) {
36
+ if (typeof location === 'undefined' || typeof sessionStorage === 'undefined')
37
+ return null;
38
+ const params = new URLSearchParams(location.search);
39
+ const saved = sessionStorage.getItem(FLOW_KEY);
40
+ if (!saved || !params.has('state'))
41
+ return null;
42
+ const flow = JSON.parse(saved);
43
+ if (flow.server !== server || params.get('state') !== flow.state)
44
+ return null;
45
+ sessionStorage.removeItem(FLOW_KEY);
46
+ history.replaceState(history.state, '', flow.back); // drop ?code=... from the address bar
47
+ return exchange(server, flow, params);
48
+ }
49
+ async function exchange(server, flow, params) {
50
+ if (params.get('iss') !== server)
51
+ throw new Error("Sign-in failed: the response didn't come from the expected server.");
52
+ if (params.get('error') === 'access_denied')
53
+ throw new Error('Sign-in was cancelled.');
54
+ if (params.has('error'))
55
+ throw new Error(`Sign-in failed: ${params.get('error_description') ?? params.get('error')}`);
56
+ const res = await fetch(`${server}/oauth/token`, {
57
+ method: 'POST',
58
+ body: new URLSearchParams({
59
+ grant_type: 'authorization_code',
60
+ code: params.get('code') ?? '',
61
+ client_id: clientId(),
62
+ redirect_uri: redirectUri(),
63
+ code_verifier: flow.verifier,
64
+ }),
65
+ });
66
+ const json = await res.json().catch(() => ({}));
67
+ if (!res.ok)
68
+ throw new Error(`Sign-in failed: ${json.error_description ?? json.error ?? res.statusText}`);
69
+ return json.access_token;
70
+ }
71
+ /** Revoke a token on the server. Failures are ignored: the token is forgotten locally anyway. */
72
+ export async function revoke(server, token) {
73
+ await fetch(`${server}/oauth/revoke`, { method: 'POST', body: new URLSearchParams({ token }) }).catch(() => { });
74
+ }
75
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,149 @@
1
+ import { SchemaError } from './schema.ts';
2
+ import type { Columns, InsertFor, RowFor, SelectOptions, UpdateFor, Where, Wire } from './types.ts';
3
+ export interface BisonBaseOptions {
4
+ /** The bisonbase server. Default: `/bb` on the current origin. */
5
+ url?: string;
6
+ /**
7
+ * Use this token instead of signing in through the browser, e.g. a personal token in a
8
+ * script or test. It isn't stored.
9
+ */
10
+ token?: string;
11
+ }
12
+ export type AuthState =
13
+ /** `error` says why, when signing in failed or the token stopped working. */
14
+ {
15
+ status: 'signed-out';
16
+ error?: string;
17
+ }
18
+ /** Finishing a sign-in (trading the code from the redirect for a token). */
19
+ | {
20
+ status: 'pending';
21
+ } | {
22
+ status: 'signed-in';
23
+ };
24
+ /** An error response from the bisonbase API. */
25
+ export declare class ApiError extends Error {
26
+ name: string;
27
+ readonly status: number;
28
+ readonly code: string;
29
+ constructor(status: number, code: string, message: string);
30
+ }
31
+ /** An operation needed the user to be signed in. */
32
+ export declare class NotSignedInError extends Error {
33
+ name: string;
34
+ constructor();
35
+ }
36
+ export interface QueryResult {
37
+ columns: string[];
38
+ rows: Wire[][];
39
+ truncated: boolean;
40
+ changes: number;
41
+ last_insert_rowid: number;
42
+ }
43
+ /** The state of a live query, as `useSelectState` returns it. */
44
+ export interface QueryState<R> {
45
+ /** Empty while signed out or before the first load. Kept (stale) while reloading or after an error. */
46
+ rows: readonly R[];
47
+ /** True until the first load finishes (only while signed in or signing in). */
48
+ loading: boolean;
49
+ error: Error | null;
50
+ }
51
+ /** The connection to a bisonbase server for one app. Make one per page. */
52
+ export declare class BisonBase {
53
+ #private;
54
+ /** The instance `<AuthElement />` and other React helpers use. */
55
+ static instance: BisonBase | null;
56
+ readonly appId: string;
57
+ readonly appName: string;
58
+ /** The server's origin, e.g. `https://example.com/bb`, without a trailing slash. */
59
+ readonly url: string;
60
+ /**
61
+ * @param appId A UUID that identifies the app and never changes; the app's files live in
62
+ * `apps/<appId>/` in each user's account. Make one with `crypto.randomUUID()`.
63
+ * @param appName The name shown to users when they sign in and in their file browser.
64
+ */
65
+ constructor(appId: string, appName: string, options?: BisonBaseOptions);
66
+ /** Whether the user is signed in. */
67
+ get auth(): AuthState;
68
+ /** Call `listener` whenever `auth` changes. Returns a function that stops listening. */
69
+ onAuthChange(listener: () => void): () => void;
70
+ /** Send the browser to the server to sign in; it comes back to the current page. */
71
+ signIn(): Promise<void>;
72
+ /** Forget the token here and revoke it on the server. */
73
+ signOut(): Promise<void>;
74
+ /**
75
+ * A database that each user gets their own copy of, stored as `apps/<appId>/<name>.db`
76
+ * in their account. Only that user can read or change it.
77
+ */
78
+ createUserDb(name: string): UserDb;
79
+ }
80
+ /** A query whose rows are kept up to date while something is subscribed to it. */
81
+ export declare class LiveQuery<R> {
82
+ #private;
83
+ readonly table: Table<any>;
84
+ constructor(bb: BisonBase, run: () => Promise<R[]>, table: Table<any>, dispose: () => void);
85
+ /** For `useSyncExternalStore`. */
86
+ subscribe: (listener: () => void) => (() => void);
87
+ /** For `useSyncExternalStore`. Returns the same object until something changes. */
88
+ getSnapshot: () => QueryState<R>;
89
+ }
90
+ /** A database each signed-in user has their own copy of. Make one with `bb.createUserDb`. */
91
+ export declare class UserDb {
92
+ #private;
93
+ readonly bb: BisonBase;
94
+ readonly name: string;
95
+ /** Path within the user's files, e.g. `apps/<appId>/todos.db`. */
96
+ readonly path: string;
97
+ /**
98
+ * Declare a table. It's created, or new columns are added, the first time it's used after
99
+ * signing in. Every table also has an `id` integer primary key.
100
+ *
101
+ * ```ts
102
+ * const todos = db.createTable("todos", { content: "text", done: ["boolean", false] })
103
+ * ```
104
+ */
105
+ createTable<const C extends Columns>(name: string, columns: C): Table<C>;
106
+ /**
107
+ * Run raw SQL against this database, returning rows as objects. Values interpolated into
108
+ * the template are sent as bound parameters. Booleans come back as 0/1 and JSON as text.
109
+ *
110
+ * ```ts
111
+ * const rows = await db.sql`SELECT count(*) AS n FROM todos WHERE done = ${false}`
112
+ * ```
113
+ */
114
+ sql(strings: TemplateStringsArray, ...values: unknown[]): Promise<Record<string, Wire>[]>;
115
+ }
116
+ /** A table in a database. Make one with `db.createTable`. */
117
+ export declare class Table<C extends Columns> {
118
+ #private;
119
+ readonly db: UserDb;
120
+ get name(): string;
121
+ /** Fetch rows once. For rows that stay up to date in a component, use `useSelect`. */
122
+ select(options?: SelectOptions<RowFor<C>>): Promise<RowFor<C>[]>;
123
+ /** Insert a row and return it as stored (with its `id` and defaults). */
124
+ insert(row: InsertFor<C>): Promise<RowFor<C>>;
125
+ /** Insert several rows in one transaction. */
126
+ insert(rows: readonly InsertFor<C>[]): Promise<RowFor<C>[]>;
127
+ /**
128
+ * Change the rows matching `where` (or the row with that id). Returns how many changed.
129
+ * Pass `{}` to change every row.
130
+ */
131
+ update(where: Where<RowFor<C>> | number, changes: UpdateFor<C>): Promise<number>;
132
+ /** Delete the rows matching `where` (or the row with that id). Returns how many were deleted. Pass `{}` to delete every row. */
133
+ delete(where: Where<RowFor<C>> | number): Promise<number>;
134
+ /** The live query for these options, shared with every other user of the same options. */
135
+ liveQuery(options?: SelectOptions<RowFor<C>>): LiveQuery<RowFor<C>>;
136
+ /**
137
+ * React hook: the rows matching `options`, kept up to date. Empty while signed out or
138
+ * loading. Needs `import "bisonbase/react"` somewhere in the app.
139
+ */
140
+ useSelect(options?: SelectOptions<RowFor<C>>): readonly RowFor<C>[];
141
+ /** React hook: like `useSelect`, plus whether it's loading and the last error. */
142
+ useSelectState(options?: SelectOptions<RowFor<C>>): QueryState<RowFor<C>>;
143
+ }
144
+ export { SchemaError };
145
+ /** The row type of a table: `type Todo = RowOf<typeof todos>`. */
146
+ export type RowOf<T> = T extends Table<infer C> ? RowFor<C> : never;
147
+ /** What a table's `insert` takes. */
148
+ export type InsertOf<T> = T extends Table<infer C> ? InsertFor<C> : never;
149
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}