@bezoar/pglite 0.0.0-stage → 0.0.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.
@@ -0,0 +1,55 @@
1
+ import { PGliteInterface, PGliteOptions, ParserOptions, Transaction } from "@electric-sql/pglite";
2
+ import { Executor } from "@bezoar/core";
3
+ /**
4
+ * The parsers that make a row match the type table of `@bezoar/schema`.
5
+ * PGlite parses `date` and `timestamp` to a `Date`. The table declares them
6
+ * as strings in the form Postgres writes, so these parsers keep the text.
7
+ * Every other type parses as PGlite parses it.
8
+ */
9
+ export declare const PARSERS: ParserOptions;
10
+ /**
11
+ * The prefix of the savepoint a transaction inside a transaction opens. The
12
+ * depth of the transaction follows it.
13
+ */
14
+ export declare const SAVEPOINT = "bezoar_";
15
+ /**
16
+ * What `pglite` takes: nothing for a database in memory, a path for a
17
+ * database on a data directory, PGlite options, or a client the caller made.
18
+ */
19
+ export type Source = string | PGliteOptions | PGliteInterface;
20
+ /**
21
+ * An {@link Executor} over PGlite. `client` is the PGlite instance, for a
22
+ * caller that needs more of it than the executor exposes. `close` closes the
23
+ * instance.
24
+ */
25
+ export interface PGliteExecutor extends Executor {
26
+ readonly client: PGliteInterface;
27
+ close(): Promise<void>;
28
+ }
29
+ /**
30
+ * A connection the executor runs on: the client, or a transaction of it.
31
+ */
32
+ export type Connection = PGliteInterface | Transaction;
33
+ /**
34
+ * The state the executors over one client share. `changed` is `true` after
35
+ * an `exec` ran and before the client refreshed its array types.
36
+ */
37
+ export interface Session {
38
+ readonly client: PGliteInterface;
39
+ changed: boolean;
40
+ }
41
+ /**
42
+ * Builds a bezoar `Executor` over PGlite.
43
+ *
44
+ * `query` runs one statement with positional parameters. The rows match the
45
+ * type table of `@bezoar/schema`. `exec` runs text that can hold many
46
+ * statements, and then refreshes the array types of the client, so an array
47
+ * of a type a migration created parses as an array. `transaction` runs a
48
+ * function inside a transaction of the client. A transaction inside a
49
+ * transaction is a savepoint.
50
+ *
51
+ * @param source - Nothing for a database in memory, a path to a data
52
+ * directory, PGlite options, or a client. A client the caller made stays
53
+ * the caller's to close.
54
+ */
55
+ export declare const pglite: (source?: Source) => PGliteExecutor;
@@ -0,0 +1,69 @@
1
+ import { PGlite, types } from "@electric-sql/pglite";
2
+ const PARSERS = {
3
+ [types.DATE]: (text) => text,
4
+ [types.TIMESTAMP]: (text) => text
5
+ };
6
+ const SAVEPOINT = "bezoar_";
7
+ const outcome = (results) => ({
8
+ rows: results.rows,
9
+ count: results.affectedRows || results.rows.length
10
+ });
11
+ const refresh = async (session) => {
12
+ if (session.changed) {
13
+ session.changed = false;
14
+ await session.client.refreshArrayTypes();
15
+ }
16
+ };
17
+ const savepoint = async (tx, depth, run) => {
18
+ const name = `${SAVEPOINT}${depth}`;
19
+ await tx.query(`SAVEPOINT ${name}`);
20
+ try {
21
+ const result = await run();
22
+ await tx.query(`RELEASE SAVEPOINT ${name}`);
23
+ return result;
24
+ } catch (error) {
25
+ await tx.query(`ROLLBACK TO SAVEPOINT ${name}`);
26
+ throw error;
27
+ }
28
+ };
29
+ const defineExecutor = (session, connection, depth) => {
30
+ const query = async (sql, params) => outcome(await connection.query(sql, [...params], { parsers: PARSERS }));
31
+ const exec = async (sql) => {
32
+ await connection.exec(sql);
33
+ session.changed = true;
34
+ if (depth === 0) await refresh(session);
35
+ };
36
+ const transaction = async (run) => {
37
+ if ("transaction" in connection) {
38
+ const result = await connection.transaction((tx) => run(defineExecutor(session, tx, depth + 1)));
39
+ await refresh(session);
40
+ return result;
41
+ }
42
+ return savepoint(connection, depth, () => run(defineExecutor(session, connection, depth + 1)));
43
+ };
44
+ return {
45
+ query,
46
+ exec,
47
+ transaction
48
+ };
49
+ };
50
+ const pglite = (source = {}) => {
51
+ let client;
52
+ if (typeof source === "string") client = new PGlite(source);
53
+ else if ("query" in source) client = source;
54
+ else client = new PGlite(source);
55
+ const executor = defineExecutor({
56
+ client,
57
+ changed: false
58
+ }, client, 0);
59
+ const close = async () => {
60
+ await client.waitReady;
61
+ await client.close();
62
+ };
63
+ return {
64
+ ...executor,
65
+ client,
66
+ close
67
+ };
68
+ };
69
+ export { PARSERS, SAVEPOINT, pglite };
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Thorwaldson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,69 @@
1
- # Temporary Holding Version
1
+ # @bezoar/pglite
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
+ A bezoar executor over [PGlite](https://pglite.dev), the Postgres that
4
+ runs in-process. `@bezoar/kit` builds on it. `@bezoar/testing` tests
5
+ on it. An app can develop on it with no server.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ pnpm add @bezoar/pglite
11
+ ```
12
+
13
+ The package carries PGlite.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { makeBezoar } from "@bezoar/core";
19
+ import { pglite } from "@bezoar/pglite";
20
+ import { contract } from "@bbf/database";
21
+ import { queries } from "@bbf/database/queries";
22
+ import { migrations } from "@bbf/database/migrations";
23
+
24
+ const executor = pglite();
25
+ const db = makeBezoar(contract, { executor, queries });
26
+
27
+ await db.migrate(migrations);
28
+ const events = await db.events.list({ day: "sunday" });
29
+
30
+ await executor.close();
31
+ ```
32
+
33
+ `pglite()` makes a database in memory. `pglite("./data")` makes one on a
34
+ data directory. `pglite(options)` passes PGlite options through. The executor
35
+ exposes the instance as `client` and closes it with `close()`.
36
+
37
+ `pglite(client)` wraps a PGlite instance the caller made. The caller closes
38
+ it.
39
+
40
+ ## Types
41
+
42
+ The rows of a query match the type table of `@bezoar/schema`. PGlite
43
+ parses most types the same way. The executor sets two parsers of its own:
44
+ `date` and `timestamp` stay strings in the form Postgres writes, where PGlite
45
+ would make a `Date` of them.
46
+
47
+ An `int8` in the safe integer range is a `number`. One outside the range is
48
+ a `bigint`, which the row validator of the service rejects.
49
+
50
+ ## Array types
51
+
52
+ PGlite reads the array types of the database when it starts. A type that a
53
+ migration creates has an array type PGlite does not know until it reads them
54
+ again. The executor refreshes them after each `exec`, and after each
55
+ transaction that ran one, so a column such as `event_day[]` parses as an
56
+ array as soon as `migrate` returns.
57
+
58
+ ## Transactions
59
+
60
+ `transaction` runs a function inside a transaction of the client. A
61
+ transaction inside a transaction is a savepoint: it is released when the
62
+ function returns and rolled back to when it throws, and the outer transaction
63
+ goes on.
64
+
65
+ ## Related
66
+
67
+ - [`@bezoar/core`](../core): the service that runs on an executor.
68
+ - `@bezoar/pg`: the executor over node-postgres, for a database with a
69
+ server.
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@bezoar/pglite",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.0",
4
+ "description": "A bezoar executor over PGlite",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/zoobzio/bezoar.git",
9
+ "directory": "packages/pglite"
10
+ },
11
+ "type": "module",
12
+ "main": "./.dist/index.mjs",
13
+ "types": "./.dist/index.d.mts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./.dist/index.d.mts",
17
+ "import": "./.dist/index.mjs"
18
+ }
19
+ },
20
+ "files": [
21
+ ".dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "obuild",
25
+ "stub": "obuild --stub",
26
+ "lint": "oxlint",
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "vitest run"
29
+ },
30
+ "dependencies": {
31
+ "@bezoar/core": "workspace:*",
32
+ "@bezoar/schema": "workspace:*",
33
+ "@electric-sql/pglite": "catalog:"
34
+ }
35
+ }