@bastani/atomic-storage-libsql 0.0.0-stage → 0.9.42-alpha.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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+
7
+ - First-party libSQL / Turso storage adapter for sessions, workflow journals, and Intercom, published to npm as `@bastani/atomic-storage-libsql` with each Atomic release. Install it with `atomic install npm:@bastani/atomic-storage-libsql` and select it with `"storage": { "adapter": "@bastani/atomic-storage-libsql", "options": { "url": "libsql://<db>.turso.io" }, "secrets": { "authToken": { "file": "..." } } }` ([#3857](https://github.com/bastani-inc/atomic/issues/3857))
8
+
9
+ ### Fixed
10
+
11
+ - Credential redaction scans malformed URLs in linear time, preventing long adversarial URLs from stalling database setup ([#3857](https://github.com/bastani-inc/atomic/issues/3857))
12
+ - The installed SDK entry now works in plain Node without a TypeScript loader. The package ships ESM JavaScript and TypeScript declarations, with no build step ([#3857](https://github.com/bastani-inc/atomic/issues/3857))
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bastani, Inc.
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
+ If your product or service exceeds 100 million monthly active users or $20 million in monthly revenue,
16
+ you must prominently display 'Atomic' on the user interface of such product or service.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,53 @@
1
- # Temporary Holding Version
1
+ # @bastani/atomic-storage-libsql
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 libSQL / [Turso](https://turso.tech) storage adapter for [Atomic](https://github.com/bastani-inc/atomic). It stores sessions, workflow journals, and Intercom multi-node routing in one shared database, so several machines can use the same state. It is a separate package: install it only where you want remote storage.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ atomic install npm:@bastani/atomic-storage-libsql
9
+ ```
10
+
11
+ Install in user scope (without `-l`); Atomic only searches user-scope packages for storage adapters.
12
+
13
+ ## Configure
14
+
15
+ Select the adapter in `~/.atomic/agent/settings.json`:
16
+
17
+ ```json
18
+ {
19
+ "storage": {
20
+ "adapter": "@bastani/atomic-storage-libsql",
21
+ "options": { "url": "libsql://<db>.turso.io" },
22
+ "secrets": { "authToken": { "file": "turso-token" } }
23
+ }
24
+ }
25
+ ```
26
+
27
+ ```bash
28
+ turso db tokens create <db> > ~/.atomic/turso-token
29
+ chmod 600 ~/.atomic/turso-token
30
+ ```
31
+
32
+ | Option | Required | Description |
33
+ |---|---|---|
34
+ | `url` | yes | A `libsql://`, `https://`, `wss://`, or `file:` URL. |
35
+ | `authToken` | for remote databases | The database token. Pass it through `secrets` (`{ "file": ... }` or `{ "env": ... }`) so it stays out of settings. |
36
+
37
+ The token is redacted from error messages and diagnostics. Add a `prefix` to the `storage` block to keep projects or teams apart in one database. See [Storage](../coding-agent/docs/storage.md) for selection precedence, per-process overrides, and the other storage options.
38
+
39
+ ## Use from the SDK
40
+
41
+ ```ts
42
+ import { openLibsqlDatabase } from "@bastani/atomic-storage-libsql";
43
+
44
+ const database = await openLibsqlDatabase({ url: "libsql://<db>.turso.io", authToken });
45
+ ```
46
+
47
+ The returned `SqliteDatabase` can be passed as SDK `sessionStore: { kind: "database", database }` or workflow `journal: { database }`.
48
+
49
+ The package ships plain ESM JavaScript with TypeScript declarations. This import works in supported Node versions without a TypeScript loader.
50
+
51
+ ## License
52
+
53
+ MIT
package/libsql.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { AdapterOptions, SqliteDatabase } from "@bastani/atomic/storage";
2
+
3
+ export interface LibsqlDatabaseOptions {
4
+ url: string;
5
+ authToken?: string;
6
+ }
7
+
8
+ export function openLibsqlDatabase(options: LibsqlDatabaseOptions): Promise<SqliteDatabase>;
9
+
10
+ declare const adapter: {
11
+ atomicStorageAdapter: 1;
12
+ openDatabase(options: AdapterOptions): Promise<SqliteDatabase>;
13
+ };
14
+ export default adapter;
package/libsql.js ADDED
@@ -0,0 +1,98 @@
1
+ // @ts-check
2
+ import { createSqliteDatabase, redactSecrets, StorageAdapterLoadError } from "@bastani/atomic/storage";
3
+
4
+ /** @typedef {{ url: string, authToken?: string }} LibsqlDatabaseOptions */
5
+
6
+ /**
7
+ * Credentials a libSQL URL can carry: its userinfo and every `authToken` query value.
8
+ * @param {string} url
9
+ * @returns {string[]}
10
+ */
11
+ function urlCredentials(url) {
12
+ const secrets = [];
13
+ // Scan each key/value once, including malformed URLs, without retrying a growing suffix at every '?'.
14
+ for (let index = 0; index < url.length; index++) {
15
+ if (url[index] !== "?" && url[index] !== "&") continue;
16
+ const start = ++index;
17
+ while (index < url.length && !"?&=#".includes(url[index])) index++;
18
+ if (url[index] === "=") {
19
+ const valueStart = ++index;
20
+ while (index < url.length && url[index] !== "&" && url[index] !== "#") index++;
21
+ const token = new URLSearchParams(url.slice(start, index)).get("authToken");
22
+ if (token !== null) secrets.push(url.slice(valueStart, index), token);
23
+ }
24
+ index--;
25
+ }
26
+ const userinfo = url.match(/\/\/([^/?#]*)@/)?.[1];
27
+ if (userinfo !== undefined) secrets.push(...userinfo.split(":"));
28
+ return secrets;
29
+ }
30
+
31
+ /**
32
+ * @param {() => import("@libsql/client").Client | import("@libsql/client").Transaction} connection
33
+ * @returns {import("@bastani/atomic/storage").SqliteDriverExecutor}
34
+ */
35
+ function statements(connection) {
36
+ return {
37
+ async execute(sql, params) {
38
+ const result = await connection().execute({ sql, args: [...params] });
39
+ return { columns: result.columns, rows: result.rows, changes: result.rowsAffected };
40
+ },
41
+ executeMultiple: (sql) => connection().executeMultiple(sql),
42
+ };
43
+ }
44
+
45
+ /**
46
+ * @param {LibsqlDatabaseOptions} options
47
+ * @returns {Promise<import("@bastani/atomic/storage").SqliteDatabase>}
48
+ */
49
+ export async function openLibsqlDatabase({ url, authToken }) {
50
+ /** @type {typeof import("@libsql/client").createClient} */
51
+ let createClient;
52
+ try {
53
+ ({ createClient } = await import("@libsql/client"));
54
+ } catch (cause) {
55
+ throw new StorageAdapterLoadError("@libsql/client", /** @type {import("@bastani/atomic/storage").StorageFailureCause} */ (cause));
56
+ }
57
+ /** @type {import("@libsql/client").Client | undefined} */
58
+ let client;
59
+ // Create the client inside a driver call, so the facade redacts credentials from its failures.
60
+ const connection = () => (client ??= createClient({ url, authToken, intMode: "bigint" }));
61
+ const database = createSqliteDatabase({
62
+ descriptor: { adapter: "libsql", location: url },
63
+ redact: redactSecrets([authToken ?? "", ...urlCredentials(url)]),
64
+ ...statements(connection),
65
+ async beginTransaction() {
66
+ const transaction = await connection().transaction("write");
67
+ return {
68
+ ...statements(() => transaction),
69
+ commit: () => transaction.commit(),
70
+ rollback: () => transaction.rollback(),
71
+ close: () => transaction.close(),
72
+ };
73
+ },
74
+ close: () => client?.close(),
75
+ });
76
+ try {
77
+ await database.exec("PRAGMA foreign_keys = ON");
78
+ return database;
79
+ } catch (error) {
80
+ await database.close().catch(() => {});
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ /** @satisfies {import("@bastani/atomic/storage").StorageAdapterModule} */
86
+ const adapter = {
87
+ atomicStorageAdapter: 1,
88
+ openDatabase(options) {
89
+ if (typeof options.url !== "string" || options.url === "") {
90
+ throw new TypeError('The libSQL storage adapter requires a non-empty string "url" option');
91
+ }
92
+ return openLibsqlDatabase({
93
+ url: options.url,
94
+ authToken: typeof options.authToken === "string" ? options.authToken : undefined,
95
+ });
96
+ },
97
+ };
98
+ export default adapter;
package/package.json CHANGED
@@ -1,6 +1,71 @@
1
1
  {
2
2
  "name": "@bastani/atomic-storage-libsql",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.9.42-alpha.1",
4
+ "description": "libSQL / Turso storage adapter for Atomic sessions, workflow journals, and Intercom.",
5
+ "contributors": [
6
+ "Norin Lavaee",
7
+ "Alex Lavaee"
8
+ ],
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "engines": {
12
+ "node": ">=22.19.0"
13
+ },
14
+ "main": "./libsql.js",
15
+ "types": "./libsql.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./libsql.d.ts",
19
+ "import": "./libsql.js",
20
+ "default": "./libsql.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "libsql.js",
25
+ "libsql.d.ts",
26
+ "README.md",
27
+ "CHANGELOG.md",
28
+ "LICENSE"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/bastani-inc/atomic.git",
36
+ "directory": "packages/storage-libsql"
37
+ },
38
+ "homepage": "https://github.com/bastani-inc/atomic/tree/main/packages/storage-libsql#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/bastani-inc/atomic/issues"
41
+ },
42
+ "keywords": [
43
+ "atomic",
44
+ "storage",
45
+ "libsql",
46
+ "turso",
47
+ "sqlite",
48
+ "bastani"
49
+ ],
50
+ "atomic": {
51
+ "storageAdapters": [
52
+ "./libsql.js"
53
+ ]
54
+ },
55
+ "pi": {
56
+ "storageAdapters": [
57
+ "./libsql.js"
58
+ ]
59
+ },
60
+ "dependencies": {
61
+ "@libsql/client": "0.18.0"
62
+ },
63
+ "peerDependencies": {
64
+ "@bastani/atomic": "*"
65
+ },
66
+ "peerDependenciesMeta": {
67
+ "@bastani/atomic": {
68
+ "optional": true
69
+ }
70
+ }
71
+ }