@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 +12 -0
- package/LICENSE +24 -0
- package/README.md +52 -2
- package/libsql.d.ts +14 -0
- package/libsql.js +98 -0
- package/package.json +69 -4
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
|
-
#
|
|
1
|
+
# @bastani/atomic-storage-libsql
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|