@storecraft/database-turso 1.0.14 → 1.0.16
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 +102 -10
- package/index.js +1 -1
- package/kysely.turso.dialect.js +7 -4
- package/migrate.js +119 -1
- package/migrate_old.js +5 -0
- package/package.json +1 -1
- package/tests/runner.test.js +3 -1
- package/tests/sandbox.js +3 -1
- package/vector-store/index.js +2 -0
- package/vector-store/types.d.ts +1 -1
- package/kysely.turso.utils.js +0 -33
package/README.md
CHANGED
@@ -5,16 +5,85 @@
|
|
5
5
|
width='90%' />
|
6
6
|
</div><hr/><br/>
|
7
7
|
|
8
|
-
Official `libSql` / `Turso` driver for `StoreCraft` on any
|
8
|
+
Official `libSql` / `Turso` driver for `StoreCraft` on any platform.
|
9
9
|
Includes a vector store.
|
10
10
|
|
11
11
|
```bash
|
12
12
|
npm i @storecraft/database-turso
|
13
13
|
```
|
14
14
|
|
15
|
-
##
|
16
|
-
|
17
|
-
|
15
|
+
## Local usage
|
16
|
+
|
17
|
+
```js
|
18
|
+
import 'dotenv/config';
|
19
|
+
import http from "node:http";
|
20
|
+
import { App } from '@storecraft/core'
|
21
|
+
import { NodePlatform } from '@storecraft/core/platform/node';
|
22
|
+
import { NodeLocalStorage } from '@storecraft/core/storage/node'
|
23
|
+
import { Turso, LibSQLVectorStore } from '@storecraft/database-turso'
|
24
|
+
import { migrateToLatest } from '@storecraft/database-turso/migrate.js'
|
25
|
+
|
26
|
+
const app = new App(
|
27
|
+
{
|
28
|
+
auth_admins_emails: ['admin@sc.com'],
|
29
|
+
}
|
30
|
+
)
|
31
|
+
.withPlatform(new NodePlatform())
|
32
|
+
.withDatabase(
|
33
|
+
new Turso(
|
34
|
+
{
|
35
|
+
url: 'file:local.db',
|
36
|
+
}
|
37
|
+
)
|
38
|
+
)
|
39
|
+
.withStorage(new NodeLocalStorage('storage'))
|
40
|
+
.withVectorStore(
|
41
|
+
new LibSQLVectorStore(
|
42
|
+
{
|
43
|
+
embedder: new OpenAIEmbedder(),
|
44
|
+
url: 'file:local-vector.db'
|
45
|
+
}
|
46
|
+
)
|
47
|
+
)
|
48
|
+
|
49
|
+
await app.init();
|
50
|
+
await migrateToLatest(app.db, false);
|
51
|
+
await app.vectorstore.createVectorIndex();
|
52
|
+
|
53
|
+
const server = http.createServer(app.handler).listen(
|
54
|
+
8000,
|
55
|
+
() => {
|
56
|
+
console.log(`Server is running on http://localhost:8000`);
|
57
|
+
}
|
58
|
+
);
|
59
|
+
|
60
|
+
```
|
61
|
+
|
62
|
+
Storecraft will search the following `env` variables
|
63
|
+
|
64
|
+
```bash
|
65
|
+
LIBSQL_URL=./data.db
|
66
|
+
# optional, if absent will use the same as LIBSQL_URL
|
67
|
+
LIBSQL_VECTOR_URL=./data-vector.db
|
68
|
+
```
|
69
|
+
|
70
|
+
So, you can instantiate with empty config
|
71
|
+
|
72
|
+
```ts
|
73
|
+
.withDatabase(
|
74
|
+
new Turso()
|
75
|
+
)
|
76
|
+
.withVectorStore(
|
77
|
+
new LibSQLVectorStore(
|
78
|
+
{
|
79
|
+
embedder: new OpenAIEmbedder(),
|
80
|
+
}
|
81
|
+
)
|
82
|
+
)
|
83
|
+
```
|
84
|
+
|
85
|
+
|
86
|
+
## Cloud usage
|
18
87
|
|
19
88
|
connect to a cloud `libsql` and `Turso` platform
|
20
89
|
- First, login to your [turso](https://turso.tech) account.
|
@@ -36,8 +105,6 @@ import { migrateToLatest } from '@storecraft/database-turso/migrate.js'
|
|
36
105
|
const app = new App(
|
37
106
|
{
|
38
107
|
auth_admins_emails: ['admin@sc.com'],
|
39
|
-
auth_secret_access_token: 'auth_secret_access_token',
|
40
|
-
auth_secret_refresh_token: 'auth_secret_refresh_token'
|
41
108
|
}
|
42
109
|
)
|
43
110
|
.withPlatform(new NodePlatform())
|
@@ -48,8 +115,6 @@ const app = new App(
|
|
48
115
|
// all of these configurations can be inferred by env variables at init
|
49
116
|
url: process.env.LIBSQL_URL,
|
50
117
|
authToken: process.env.LIBSQL_API_TOKEN,
|
51
|
-
// or local
|
52
|
-
url: 'file:local.db',
|
53
118
|
}
|
54
119
|
)
|
55
120
|
)
|
@@ -57,14 +122,17 @@ const app = new App(
|
|
57
122
|
.withVectorStore(
|
58
123
|
new LibSQLVectorStore(
|
59
124
|
{
|
60
|
-
embedder: new OpenAIEmbedder()
|
125
|
+
embedder: new OpenAIEmbedder(),
|
126
|
+
url: process.env.LIBSQL_URL,
|
127
|
+
authToken: process.env.LIBSQL_API_TOKEN,
|
61
128
|
}
|
62
129
|
)
|
63
130
|
)
|
64
131
|
|
65
132
|
await app.init();
|
66
133
|
await migrateToLatest(app.db, false);
|
67
|
-
|
134
|
+
await app.vectorstore.createVectorIndex();
|
135
|
+
|
68
136
|
const server = http.createServer(app.handler).listen(
|
69
137
|
8000,
|
70
138
|
() => {
|
@@ -74,6 +142,30 @@ const server = http.createServer(app.handler).listen(
|
|
74
142
|
|
75
143
|
```
|
76
144
|
|
145
|
+
Storecraft will search the following `env` variables
|
146
|
+
|
147
|
+
```bash
|
148
|
+
LIBSQL_URL=libsql://<your_database_name>.something.com
|
149
|
+
LIBSQL_AUTH_TOKEN=your_api_key
|
150
|
+
```
|
151
|
+
|
152
|
+
So, you can instantiate with empty config
|
153
|
+
|
154
|
+
```ts
|
155
|
+
.withDatabase(
|
156
|
+
new Turso()
|
157
|
+
)
|
158
|
+
.withVectorStore(
|
159
|
+
new LibSQLVectorStore(
|
160
|
+
{
|
161
|
+
embedder: new OpenAIEmbedder(),
|
162
|
+
}
|
163
|
+
)
|
164
|
+
)
|
165
|
+
```
|
166
|
+
|
167
|
+
|
168
|
+
|
77
169
|
```text
|
78
170
|
Author: Tomer Shalev <tomer.shalev@gmail.com>
|
79
171
|
```
|
package/index.js
CHANGED
package/kysely.turso.dialect.js
CHANGED
@@ -26,8 +26,12 @@ export class LibsqlDialect {
|
|
26
26
|
return this.#config;
|
27
27
|
}
|
28
28
|
|
29
|
-
createAdapter() {
|
30
|
-
|
29
|
+
createAdapter() {
|
30
|
+
return new kysely.SqliteAdapter();
|
31
|
+
}
|
32
|
+
createQueryCompiler() {
|
33
|
+
return new kysely.SqliteQueryCompiler();
|
34
|
+
}
|
31
35
|
createDriver() {
|
32
36
|
|
33
37
|
if (this.#config?.url===undefined) {
|
@@ -173,9 +177,7 @@ export class LibsqlConnection {
|
|
173
177
|
}
|
174
178
|
|
175
179
|
/**
|
176
|
-
*
|
177
180
|
* @param {kysely.CompiledQuery} compiledQuery
|
178
|
-
*
|
179
181
|
* @returns {Promise<QueryResult>}
|
180
182
|
*/
|
181
183
|
async executeQuery(compiledQuery) {
|
@@ -193,6 +195,7 @@ export class LibsqlConnection {
|
|
193
195
|
}
|
194
196
|
|
195
197
|
async beginTransaction() {
|
198
|
+
// console.log('beginTransaction')
|
196
199
|
if(this.config.prefers_batch_over_transactions) {
|
197
200
|
this.isBatch = true;
|
198
201
|
this.batch = [];
|
package/migrate.js
CHANGED
@@ -1,5 +1,123 @@
|
|
1
|
-
|
1
|
+
import { SQL } from "@storecraft/database-sql-base";
|
2
|
+
import { AggregateDialect } from "@storecraft/database-sql-base/kysely.aggregate.dialect.js";
|
3
|
+
import { get_migrations, prepare_and_bind } from "@storecraft/database-sql-base/migrate.js";
|
4
|
+
import { Kysely, Migrator } from "kysely";
|
5
|
+
import fs from 'node:fs';
|
6
|
+
import path from 'node:path';
|
7
|
+
import { fileURLToPath } from "node:url";
|
2
8
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
10
|
+
const __dirname = path.dirname(__filename);
|
3
11
|
|
12
|
+
/**
|
13
|
+
*
|
14
|
+
* @param {SQL} db_driver
|
15
|
+
* @param {boolean} [destroy_db_upon_completion=true]
|
16
|
+
*/
|
17
|
+
export async function migrateToLatest(
|
18
|
+
db_driver, destroy_db_upon_completion=true
|
19
|
+
) {
|
20
|
+
const db = db_driver?.client;
|
4
21
|
|
22
|
+
if(!db)
|
23
|
+
throw new Error('No Kysely client found !!!');
|
5
24
|
|
25
|
+
console.log(
|
26
|
+
'Resolving migrations. This may take 2 minutes ...'
|
27
|
+
);
|
28
|
+
|
29
|
+
const migrations = await get_migrations(db_driver.dialectType);
|
30
|
+
|
31
|
+
if(!migrations)
|
32
|
+
throw new Error('No migrations found !!!');
|
33
|
+
|
34
|
+
const sorted = Object
|
35
|
+
.entries(migrations)
|
36
|
+
.sort((a, b) => a[0].localeCompare(b[0]));
|
37
|
+
|
38
|
+
/** @type {Record<string, import("kysely").Migration>} */
|
39
|
+
const rewritten_migrations = {};
|
40
|
+
|
41
|
+
for (const [name, migration] of sorted) {
|
42
|
+
// console.log(`Executing virtual migration "${name}" ...`);
|
43
|
+
const up_agg_dialect = new AggregateDialect(
|
44
|
+
{ dialect: db_driver.config.dialect, }
|
45
|
+
);
|
46
|
+
const up_agg_kysely = new Kysely(
|
47
|
+
{ dialect: up_agg_dialect }
|
48
|
+
);
|
49
|
+
const down_agg_dialect = new AggregateDialect(
|
50
|
+
{ dialect: db_driver.config.dialect, }
|
51
|
+
);
|
52
|
+
const down_agg_kysely = new Kysely(
|
53
|
+
{ dialect: down_agg_dialect }
|
54
|
+
);
|
55
|
+
|
56
|
+
await migration.up(up_agg_kysely);
|
57
|
+
await migration.down(down_agg_kysely);
|
58
|
+
|
59
|
+
// now get the queries
|
60
|
+
const queries_up = up_agg_dialect.queries;
|
61
|
+
const queries_down = down_agg_dialect.queries;
|
62
|
+
|
63
|
+
// turso dialect supports transactions and also batch
|
64
|
+
// we take adavantage of this
|
65
|
+
rewritten_migrations[name] = {
|
66
|
+
up: async (db) => {
|
67
|
+
await db.transaction().execute(
|
68
|
+
async (trx) => {
|
69
|
+
for (const query of queries_up) {
|
70
|
+
await trx.executeQuery(query)
|
71
|
+
}
|
72
|
+
}
|
73
|
+
);
|
74
|
+
},
|
75
|
+
down: async (db) => {
|
76
|
+
await db.transaction().execute(
|
77
|
+
async (trx) => {
|
78
|
+
for (const query of queries_down) {
|
79
|
+
await trx.executeQuery(query)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
const migrator = new Migrator(
|
88
|
+
{
|
89
|
+
db,
|
90
|
+
provider: {
|
91
|
+
getMigrations: async () => {
|
92
|
+
return rewritten_migrations;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
);
|
97
|
+
|
98
|
+
const { error, results } = await migrator.migrateToLatest();
|
99
|
+
|
100
|
+
results?.forEach(
|
101
|
+
(it) => {
|
102
|
+
if (it.status === 'Success') {
|
103
|
+
console.log(
|
104
|
+
`migration "${it.migrationName}" was executed successfully`
|
105
|
+
);
|
106
|
+
} else if (it.status === 'Error') {
|
107
|
+
console.error(
|
108
|
+
`failed to execute migration "${it.migrationName}"`
|
109
|
+
);
|
110
|
+
}
|
111
|
+
}
|
112
|
+
);
|
113
|
+
|
114
|
+
if (error) {
|
115
|
+
console.error('failed to migrate')
|
116
|
+
console.error(JSON.stringify(error, null, 2))
|
117
|
+
process.exit(1)
|
118
|
+
}
|
119
|
+
|
120
|
+
if(destroy_db_upon_completion)
|
121
|
+
await db.destroy();
|
122
|
+
}
|
123
|
+
|
package/migrate_old.js
ADDED
package/package.json
CHANGED
package/tests/runner.test.js
CHANGED
@@ -11,7 +11,9 @@ export const create_app = async () => {
|
|
11
11
|
{
|
12
12
|
auth_admins_emails: ['admin@sc.com'],
|
13
13
|
auth_secret_access_token: 'auth_secret_access_token',
|
14
|
-
auth_secret_refresh_token: 'auth_secret_refresh_token'
|
14
|
+
auth_secret_refresh_token: 'auth_secret_refresh_token',
|
15
|
+
auth_secret_confirm_email_token: 'auth_secret_confirm_email_token',
|
16
|
+
auth_secret_forgot_password_token: 'auth_secret_forgot_password_token',
|
15
17
|
}
|
16
18
|
)
|
17
19
|
.withPlatform(new NodePlatform())
|
package/tests/sandbox.js
CHANGED
@@ -9,7 +9,9 @@ export const test = async () => {
|
|
9
9
|
{
|
10
10
|
auth_admins_emails: ['admin@sc.com'],
|
11
11
|
auth_secret_access_token: 'auth_secret_access_token',
|
12
|
-
auth_secret_refresh_token: 'auth_secret_refresh_token'
|
12
|
+
auth_secret_refresh_token: 'auth_secret_refresh_token',
|
13
|
+
auth_secret_confirm_email_token: 'auth_secret_confirm_email_token',
|
14
|
+
auth_secret_forgot_password_token: 'auth_secret_forgot_password_token',
|
13
15
|
}
|
14
16
|
)
|
15
17
|
.withPlatform(new NodePlatform())
|
package/vector-store/index.js
CHANGED
@@ -71,10 +71,12 @@ export class LibSQLVectorStore {
|
|
71
71
|
};
|
72
72
|
}
|
73
73
|
|
74
|
+
/** @type {VectorStore["metric"]} */
|
74
75
|
get metric() {
|
75
76
|
return this.config.similarity;
|
76
77
|
};
|
77
78
|
|
79
|
+
/** @type {VectorStore["dimensions"]} */
|
78
80
|
get dimensions() {
|
79
81
|
return this.config.dimensions;
|
80
82
|
};
|
package/vector-store/types.d.ts
CHANGED
package/kysely.turso.utils.js
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
|
2
|
-
/**
|
3
|
-
*
|
4
|
-
* @param {string} stmt
|
5
|
-
* @param {any[] | Record<string, any>} params
|
6
|
-
*/
|
7
|
-
export const prepare_and_bind = (stmt='', params=[]) => {
|
8
|
-
const params_object = Array.isArray(params) ?
|
9
|
-
params.reduce((a, v, idx) => ({ ...a, [idx+1]: v}), {}) :
|
10
|
-
params;
|
11
|
-
|
12
|
-
let current = 0;
|
13
|
-
let result = ''
|
14
|
-
let index_run = 1;
|
15
|
-
for (let m of stmt.matchAll(/\?[0-9]*/g)) {
|
16
|
-
result += stmt.slice(current, m.index);
|
17
|
-
|
18
|
-
const match_string = m[0];
|
19
|
-
let index_access = match_string.length > 1 ?
|
20
|
-
Number(match_string.slice(1)) :
|
21
|
-
index_run;
|
22
|
-
|
23
|
-
result += "'" + params_object[index_access] + "'";
|
24
|
-
|
25
|
-
current = m.index + m[0].length;
|
26
|
-
index_run+=1;
|
27
|
-
}
|
28
|
-
|
29
|
-
result += stmt.slice(current);
|
30
|
-
|
31
|
-
return result;
|
32
|
-
}
|
33
|
-
|