@voltro/sql-turso 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/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +1882 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.js +330 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as Client from '@effect/sql/SqlClient';
|
|
2
|
+
import { ConfigError } from 'effect/ConfigError';
|
|
3
|
+
import { ConnectionConfig } from '@voltro/database';
|
|
4
|
+
import { Context } from 'effect';
|
|
5
|
+
import { Duration } from 'effect';
|
|
6
|
+
import { Layer } from 'effect';
|
|
7
|
+
import { RetryDecision } from '@voltro/database';
|
|
8
|
+
import { SqlClient } from '@effect/sql/SqlClient';
|
|
9
|
+
import { SqlDialect } from '@voltro/database';
|
|
10
|
+
import { SqlError } from '@effect/sql/SqlError';
|
|
11
|
+
|
|
12
|
+
export declare const connectionFromConfig: (config: ConnectionConfig) => ParsedTursoConnection;
|
|
13
|
+
|
|
14
|
+
export declare const isTursoRetryableFailure: (err: unknown) => boolean;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @category models
|
|
18
|
+
*/
|
|
19
|
+
export declare interface LibsqlClient extends Client.SqlClient {
|
|
20
|
+
readonly [TypeId_2]: TypeId_2;
|
|
21
|
+
readonly config: LibsqlClientConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @category tags
|
|
26
|
+
*/
|
|
27
|
+
export declare const LibsqlClient: Context.Tag<LibsqlClient, LibsqlClient>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @category models
|
|
31
|
+
*/
|
|
32
|
+
export declare interface LibsqlClientConfig {
|
|
33
|
+
/**
|
|
34
|
+
* The database URL. Remote Turso Cloud uses `libsql://`, `https://`, or
|
|
35
|
+
* `wss://`; an embedded replica uses a local `file:` path (paired with
|
|
36
|
+
* `syncUrl`).
|
|
37
|
+
*/
|
|
38
|
+
readonly url: string;
|
|
39
|
+
/**
|
|
40
|
+
* Turso Cloud auth token. Sourced from config/env (`DB_AUTH_TOKEN`); never
|
|
41
|
+
* logged or baked into the build.
|
|
42
|
+
*/
|
|
43
|
+
readonly authToken?: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Embedded-replica sync source: the remote Turso Cloud primary a local
|
|
46
|
+
* `file:` replica syncs FROM. When set with a `file:` url, the client runs an
|
|
47
|
+
* embedded replica — local reads, writes forwarded to the primary, periodic
|
|
48
|
+
* pull-sync of the primary's frames.
|
|
49
|
+
*/
|
|
50
|
+
readonly syncUrl?: string | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Embedded-replica pull-sync interval. When set (and `syncUrl` is set), the
|
|
53
|
+
* client resyncs on this cadence; unset means sync only on explicit
|
|
54
|
+
* `client.sync()` (which this layer fires once at startup).
|
|
55
|
+
*/
|
|
56
|
+
readonly syncInterval?: Duration.DurationInput | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Read-your-writes for an embedded replica: after a write to the primary,
|
|
59
|
+
* block local reads until the replica has caught up to that write. Defaults
|
|
60
|
+
* to the driver's default (on).
|
|
61
|
+
*/
|
|
62
|
+
readonly readYourWrites?: boolean | undefined;
|
|
63
|
+
/** Encryption key for a local embedded-replica file at rest. */
|
|
64
|
+
readonly encryptionKey?: string | undefined;
|
|
65
|
+
readonly spanAttributes?: Record<string, unknown> | undefined;
|
|
66
|
+
readonly transformResultNames?: ((str: string) => string) | undefined;
|
|
67
|
+
readonly transformQueryNames?: ((str: string) => string) | undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The parsed remote / embedded-replica (libsql-client) connection shape. */
|
|
71
|
+
export declare interface LibsqlConnection {
|
|
72
|
+
readonly kind: 'remote';
|
|
73
|
+
/** `libsql://` / `https://` / `wss://` (Turso Cloud), or a `file:` embedded replica. */
|
|
74
|
+
readonly url: string;
|
|
75
|
+
/** Turso Cloud auth token (from env; never logged/baked). */
|
|
76
|
+
readonly authToken?: string;
|
|
77
|
+
/** Embedded-replica sync source — the remote primary a `file:` replica syncs from. */
|
|
78
|
+
readonly syncUrl?: string;
|
|
79
|
+
/** Embedded-replica pull-sync interval, in seconds. */
|
|
80
|
+
readonly syncIntervalSeconds?: number;
|
|
81
|
+
/** Read-your-writes for an embedded replica. */
|
|
82
|
+
readonly readYourWrites?: boolean;
|
|
83
|
+
/** Encryption key for a local embedded-replica file at rest. */
|
|
84
|
+
readonly encryptionKey?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export declare const makeLibsqlSqlLayer: (options: Omit<LibsqlConnection, "kind">) => TursoSqlLayer;
|
|
88
|
+
|
|
89
|
+
export declare const makeTursoSqlLayer: (options: Omit<TursoConnection, "kind">) => TursoSqlLayer;
|
|
90
|
+
|
|
91
|
+
export declare const makeTursoSqlLayerFromConfig: (config: ConnectionConfig) => TursoSqlLayer;
|
|
92
|
+
|
|
93
|
+
export declare type ParsedTursoConnection = TursoConnection | LibsqlConnection;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @category models
|
|
97
|
+
*/
|
|
98
|
+
export declare interface TursoClient extends Client.SqlClient {
|
|
99
|
+
readonly [TypeId]: TypeId;
|
|
100
|
+
readonly config: TursoClientConfig;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @category tags
|
|
105
|
+
*/
|
|
106
|
+
export declare const TursoClient: Context.Tag<TursoClient, TursoClient>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @category models
|
|
110
|
+
*/
|
|
111
|
+
export declare interface TursoClientConfig {
|
|
112
|
+
/** `file:`-stripped path, or `:memory:` for an ephemeral in-process DB. */
|
|
113
|
+
readonly filename: string;
|
|
114
|
+
readonly readonly?: boolean | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Connection-pool size. The MVCC write-concurrency knob — this many
|
|
117
|
+
* `BEGIN CONCURRENT` transactions can run at once. Default 4. Forced to 1
|
|
118
|
+
* for `:memory:` (each `:memory:` connection is a PRIVATE database, so a
|
|
119
|
+
* pool would split state).
|
|
120
|
+
*/
|
|
121
|
+
readonly maxConnections?: number | undefined;
|
|
122
|
+
readonly prepareCacheSize?: number | undefined;
|
|
123
|
+
readonly prepareCacheTTL?: Duration.DurationInput | undefined;
|
|
124
|
+
readonly spanAttributes?: Record<string, unknown> | undefined;
|
|
125
|
+
readonly transformResultNames?: ((str: string) => string) | undefined;
|
|
126
|
+
readonly transformQueryNames?: ((str: string) => string) | undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** The parsed local (Rust-engine) connection shape. */
|
|
130
|
+
export declare interface TursoConnection {
|
|
131
|
+
readonly kind: 'local';
|
|
132
|
+
/** Absolute or relative path, or `:memory:` for ephemeral. */
|
|
133
|
+
readonly filename: string;
|
|
134
|
+
readonly readonly?: boolean;
|
|
135
|
+
/** Connection-pool size (the MVCC write-concurrency knob). */
|
|
136
|
+
readonly maxConnections?: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export declare const tursoDialect: SqlDialect;
|
|
140
|
+
|
|
141
|
+
export declare const tursoRetryFilter: (err: unknown) => RetryDecision;
|
|
142
|
+
|
|
143
|
+
declare type TursoSqlLayer = Layer.Layer<SqlClient, ConfigError | SqlError, never>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* @category type ids
|
|
147
|
+
*/
|
|
148
|
+
declare const TypeId: unique symbol;
|
|
149
|
+
|
|
150
|
+
declare type TypeId = typeof TypeId;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @category type ids
|
|
154
|
+
*/
|
|
155
|
+
declare const TypeId_2: unique symbol;
|
|
156
|
+
|
|
157
|
+
declare type TypeId_2 = typeof TypeId_2;
|
|
158
|
+
|
|
159
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { makeSqliteStore as e } from "@voltro/sql-sqlite";
|
|
2
|
+
import { Cache as t, Chunk as n, Config as r, Context as i, Duration as a, Effect as o, FiberRef as s, Layer as c, Pool as l, Scope as u, Stream as d } from "effect";
|
|
3
|
+
import * as f from "@effect/experimental/Reactivity";
|
|
4
|
+
import * as p from "@effect/sql/SqlClient";
|
|
5
|
+
import { SqlError as m } from "@effect/sql/SqlError";
|
|
6
|
+
import * as h from "@effect/sql/Statement";
|
|
7
|
+
import { identity as ee } from "effect/Function";
|
|
8
|
+
import { connect as te } from "@tursodatabase/database";
|
|
9
|
+
import { createLogger as g } from "@voltro/logger";
|
|
10
|
+
import { createClient as _ } from "@libsql/client";
|
|
11
|
+
//#region src/sqlClient.ts
|
|
12
|
+
var v = g({ scope: "voltro:turso" }), y = "db.system.name", ne = /no transaction is active|no active transaction|cannot rollback|cannot commit/i, b = s.unsafeMake(!1), x = (e) => o.locally(e, b, !0), S = Symbol.for("@voltro/sql-turso/TursoClient"), C = i.GenericTag("@voltro/sql-turso/TursoClient"), w = (e) => (t) => {
|
|
13
|
+
let n = t?.message;
|
|
14
|
+
return new m({
|
|
15
|
+
cause: t,
|
|
16
|
+
message: typeof n == "string" ? `${e}: ${n}` : e
|
|
17
|
+
});
|
|
18
|
+
}, re = /file is locked|locking error|failed locking file/i, T = (e) => (t) => {
|
|
19
|
+
let n = String(t?.message ?? t);
|
|
20
|
+
return re.test(n) ? new m({
|
|
21
|
+
cause: t,
|
|
22
|
+
message: `@voltro/sql-turso: database file '${e}' is locked by another process. Turso is single-process (it takes an exclusive file lock) — you cannot run multiple instances / replicas against one file. For a multi-replica deployment use DB_DIALECT=postgres (or mysql / mariadb / mssql).`
|
|
23
|
+
}) : new m({
|
|
24
|
+
cause: t,
|
|
25
|
+
message: `Failed to open Turso database: ${n}`
|
|
26
|
+
});
|
|
27
|
+
}, E = (e) => o.gen(function* () {
|
|
28
|
+
let r = yield* o.scope, c = yield* o.tryPromise({
|
|
29
|
+
try: () => te(e.filename, {
|
|
30
|
+
readonly: e.readonly ?? !1,
|
|
31
|
+
experimental: ["attach"]
|
|
32
|
+
}),
|
|
33
|
+
catch: T(e.filename)
|
|
34
|
+
});
|
|
35
|
+
yield* u.addFinalizer(r, o.promise(async () => {
|
|
36
|
+
try {
|
|
37
|
+
await c.close();
|
|
38
|
+
} catch {}
|
|
39
|
+
})), yield* o.tryPromise({
|
|
40
|
+
try: () => c.pragma("journal_mode=experimental_mvcc", void 0),
|
|
41
|
+
catch: w("Failed to enable Turso MVCC (journal_mode=experimental_mvcc)")
|
|
42
|
+
}), yield* o.tryPromise({
|
|
43
|
+
try: () => c.pragma("foreign_keys=ON", void 0),
|
|
44
|
+
catch: w("Failed to enable foreign-key enforcement (foreign_keys=ON)")
|
|
45
|
+
});
|
|
46
|
+
let l = (e) => c.prepare(e), f = yield* t.make({
|
|
47
|
+
capacity: e.prepareCacheSize ?? 200,
|
|
48
|
+
timeToLive: e.prepareCacheTTL ?? a.minutes(10),
|
|
49
|
+
lookup: (e) => o.try({
|
|
50
|
+
try: () => l(e),
|
|
51
|
+
catch: w("Failed to prepare statement")
|
|
52
|
+
})
|
|
53
|
+
}), h = (e, t, n) => o.withFiberRuntime((r) => (i.get(r.currentContext, p.SafeIntegers) && e.safeIntegers(!0), e.reader ? o.tryPromise({
|
|
54
|
+
try: () => e.all(...t),
|
|
55
|
+
catch: w("Failed to execute statement")
|
|
56
|
+
}) : o.tryPromise({
|
|
57
|
+
try: async () => {
|
|
58
|
+
let r = await e.run(...t);
|
|
59
|
+
return n ? r : [];
|
|
60
|
+
},
|
|
61
|
+
catch: w("Failed to execute statement")
|
|
62
|
+
}))), g = (e, t, n = !1) => o.flatMap(f.get(e), (e) => h(e, t, n)), _ = (e, t) => o.acquireUseRelease(f.get(e), (e) => o.tryPromise({
|
|
63
|
+
try: async () => e.reader ? (e.raw(!0), await e.all(...t)) : (await e.run(...t), []),
|
|
64
|
+
catch: w("Failed to execute statement")
|
|
65
|
+
}), (e) => o.sync(() => {
|
|
66
|
+
e.reader && e.raw(!1);
|
|
67
|
+
}));
|
|
68
|
+
return ee({
|
|
69
|
+
execute(e, t, n) {
|
|
70
|
+
return n ? o.map(g(e, t), n) : g(e, t);
|
|
71
|
+
},
|
|
72
|
+
executeRaw(e, t) {
|
|
73
|
+
return g(e, t, !0);
|
|
74
|
+
},
|
|
75
|
+
executeValues(e, t) {
|
|
76
|
+
return _(e, t);
|
|
77
|
+
},
|
|
78
|
+
executeUnprepared(e, t, n) {
|
|
79
|
+
let r = o.flatMap(s.get(b), (n) => {
|
|
80
|
+
let r = n && e === "BEGIN" ? "BEGIN CONCURRENT" : e;
|
|
81
|
+
return o.acquireUseRelease(o.try({
|
|
82
|
+
try: () => l(r),
|
|
83
|
+
catch: w("Failed to prepare statement")
|
|
84
|
+
}), (e) => h(e, t ?? [], !1).pipe(o.catchIf((e) => ne.test(String(e?.message ?? "")), () => o.succeed([]))), (e) => o.sync(() => {
|
|
85
|
+
try {
|
|
86
|
+
e.close();
|
|
87
|
+
} catch {}
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
return n ? o.map(r, n) : r;
|
|
91
|
+
},
|
|
92
|
+
executeStream(e, t, r) {
|
|
93
|
+
let i = d.acquireRelease(o.try({
|
|
94
|
+
try: () => l(e),
|
|
95
|
+
catch: w("Failed to prepare statement")
|
|
96
|
+
}), (e) => o.sync(() => {
|
|
97
|
+
try {
|
|
98
|
+
e.close();
|
|
99
|
+
} catch {}
|
|
100
|
+
})).pipe(d.flatMap((e) => d.fromAsyncIterable(e.iterate(...t), (e) => new m({
|
|
101
|
+
cause: e,
|
|
102
|
+
message: "Failed to stream statement"
|
|
103
|
+
}))));
|
|
104
|
+
return r ? d.mapChunks(i, (e) => n.unsafeFromArray(r(n.toReadonlyArray(e)))) : i;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}), D = (e) => o.gen(function* () {
|
|
108
|
+
let t = h.makeCompilerSqlite(e.transformQueryNames), n = e.transformResultNames ? h.defaultTransforms(e.transformResultNames).array : void 0, r = e.filename === ":memory:" || e.filename === "" || e.filename.startsWith("file::memory:"), i = e.maxConnections ?? 4;
|
|
109
|
+
r && i > 1 && v.warn(`:memory: Turso DB is per-connection — clamping pool size ${i} → 1`);
|
|
110
|
+
let a = r ? 1 : Math.max(1, i), o = yield* l.make({
|
|
111
|
+
acquire: E(e),
|
|
112
|
+
size: a
|
|
113
|
+
}), s = l.get(o), c = l.get(o), u = yield* p.make({
|
|
114
|
+
acquirer: s,
|
|
115
|
+
compiler: t,
|
|
116
|
+
transactionAcquirer: c,
|
|
117
|
+
beginTransaction: "BEGIN",
|
|
118
|
+
spanAttributes: [...e.spanAttributes ? Object.entries(e.spanAttributes) : [], [y, "turso"]],
|
|
119
|
+
transformRows: n
|
|
120
|
+
});
|
|
121
|
+
return Object.assign(u, {
|
|
122
|
+
[S]: S,
|
|
123
|
+
config: e
|
|
124
|
+
});
|
|
125
|
+
}), ie = (e) => c.scopedContext(r.unwrap(e).pipe(o.flatMap(D), o.map((e) => i.make(C, e).pipe(i.add(p.SqlClient, e))))).pipe(c.provide(f.layer)), ae = g({ scope: "voltro:turso:libsql" }), oe = "db.system.name", O = Symbol.for("@voltro/sql-turso/LibsqlClient"), k = i.GenericTag("@voltro/sql-turso/LibsqlClient"), A = (e) => (t) => {
|
|
126
|
+
let n = t?.message;
|
|
127
|
+
return new m({
|
|
128
|
+
cause: t,
|
|
129
|
+
message: typeof n == "string" ? `${e}: ${n}` : e
|
|
130
|
+
});
|
|
131
|
+
}, se = (e, t) => {
|
|
132
|
+
let n = {};
|
|
133
|
+
for (let r of t) n[r] = e[r];
|
|
134
|
+
return n;
|
|
135
|
+
}, j = (e) => e.rows.map((t) => se(t, e.columns)), M = (e) => e.rows.map((t) => e.columns.map((e, n) => t[n])), N = (e) => e, P = (e) => /^\s*BEGIN\b/i.test(e), F = (e) => /^\s*(COMMIT|END)\b/i.test(e), I = (e) => /^\s*ROLLBACK\b/i.test(e), L = (e) => ({
|
|
136
|
+
execute(t, n, r) {
|
|
137
|
+
let i = o.map(o.tryPromise({
|
|
138
|
+
try: () => e(t, n),
|
|
139
|
+
catch: A("Failed to execute statement")
|
|
140
|
+
}), j);
|
|
141
|
+
return r ? o.map(i, r) : i;
|
|
142
|
+
},
|
|
143
|
+
executeRaw(t, n) {
|
|
144
|
+
return o.map(o.tryPromise({
|
|
145
|
+
try: () => e(t, n),
|
|
146
|
+
catch: A("Failed to execute statement")
|
|
147
|
+
}), j);
|
|
148
|
+
},
|
|
149
|
+
executeValues(t, n) {
|
|
150
|
+
return o.map(o.tryPromise({
|
|
151
|
+
try: () => e(t, n),
|
|
152
|
+
catch: A("Failed to execute statement")
|
|
153
|
+
}), M);
|
|
154
|
+
},
|
|
155
|
+
executeUnprepared(t, n, r) {
|
|
156
|
+
let i = o.map(o.tryPromise({
|
|
157
|
+
try: () => e(t, n),
|
|
158
|
+
catch: A("Failed to execute statement")
|
|
159
|
+
}), j);
|
|
160
|
+
return r ? o.map(i, r) : i;
|
|
161
|
+
},
|
|
162
|
+
executeStream(t, r, i) {
|
|
163
|
+
let a = d.fromIterableEffect(o.map(o.tryPromise({
|
|
164
|
+
try: () => e(t, r),
|
|
165
|
+
catch: A("Failed to stream statement")
|
|
166
|
+
}), j));
|
|
167
|
+
return i ? d.mapChunks(a, (e) => n.unsafeFromArray(i(n.toReadonlyArray(e)))) : a;
|
|
168
|
+
}
|
|
169
|
+
}), R = (e) => L((t, n) => n.length === 0 ? e.execute(t) : e.execute({
|
|
170
|
+
sql: t,
|
|
171
|
+
args: N(n)
|
|
172
|
+
})), z = (e) => o.gen(function* () {
|
|
173
|
+
let t = yield* o.scope, n = yield* o.tryPromise({
|
|
174
|
+
try: () => e.transaction("write"),
|
|
175
|
+
catch: A("Failed to begin transaction")
|
|
176
|
+
});
|
|
177
|
+
return yield* u.addFinalizer(t, o.sync(() => {
|
|
178
|
+
try {
|
|
179
|
+
n.close();
|
|
180
|
+
} catch {}
|
|
181
|
+
})), L((e, t) => P(e) ? Promise.resolve(B) : F(e) ? n.commit().then(() => B) : I(e) ? n.rollback().then(() => B) : t.length === 0 ? n.execute(e) : n.execute({
|
|
182
|
+
sql: e,
|
|
183
|
+
args: N(t)
|
|
184
|
+
}));
|
|
185
|
+
}), B = {
|
|
186
|
+
columns: [],
|
|
187
|
+
columnTypes: [],
|
|
188
|
+
rows: [],
|
|
189
|
+
rowsAffected: 0,
|
|
190
|
+
lastInsertRowid: void 0,
|
|
191
|
+
toJSON: () => ({})
|
|
192
|
+
}, V = (e) => e.url.startsWith("file:") && e.syncUrl !== void 0, H = (e) => o.gen(function* () {
|
|
193
|
+
let t = h.makeCompilerSqlite(e.transformQueryNames), n = e.transformResultNames ? h.defaultTransforms(e.transformResultNames).array : void 0, r = {
|
|
194
|
+
url: e.url,
|
|
195
|
+
...e.authToken === void 0 ? {} : { authToken: e.authToken },
|
|
196
|
+
...e.syncUrl === void 0 ? {} : { syncUrl: e.syncUrl },
|
|
197
|
+
...e.syncInterval === void 0 ? {} : { syncInterval: a.toSeconds(a.decode(e.syncInterval)) },
|
|
198
|
+
...e.readYourWrites === void 0 ? {} : { readYourWrites: e.readYourWrites },
|
|
199
|
+
...e.encryptionKey === void 0 ? {} : { encryptionKey: e.encryptionKey }
|
|
200
|
+
}, i = V(e);
|
|
201
|
+
ae.info(`libsql client: ${i ? "embedded replica" : "remote"} (url scheme=${e.url.split(":")[0]}${e.syncUrl ? ", sync enabled" : ""})`);
|
|
202
|
+
let s = yield* o.acquireRelease(o.try({
|
|
203
|
+
try: () => _(r),
|
|
204
|
+
catch: A("Failed to create libsql client")
|
|
205
|
+
}), (e) => o.sync(() => {
|
|
206
|
+
try {
|
|
207
|
+
e.close();
|
|
208
|
+
} catch {}
|
|
209
|
+
}));
|
|
210
|
+
i && (yield* o.tryPromise({
|
|
211
|
+
try: () => s.sync(),
|
|
212
|
+
catch: A("Failed initial embedded-replica sync")
|
|
213
|
+
}));
|
|
214
|
+
let c = o.succeed(R(s)), l = z(s), u = yield* p.make({
|
|
215
|
+
acquirer: c,
|
|
216
|
+
compiler: t,
|
|
217
|
+
transactionAcquirer: l,
|
|
218
|
+
beginTransaction: "BEGIN",
|
|
219
|
+
spanAttributes: [...e.spanAttributes ? Object.entries(e.spanAttributes) : [], [oe, "turso"]],
|
|
220
|
+
transformRows: n
|
|
221
|
+
});
|
|
222
|
+
return Object.assign(u, {
|
|
223
|
+
[O]: O,
|
|
224
|
+
config: e
|
|
225
|
+
});
|
|
226
|
+
}), U = (e) => c.scopedContext(r.unwrap(e).pipe(o.flatMap(H), o.map((e) => i.make(k, e).pipe(i.add(p.SqlClient, e))))).pipe(c.provide(f.layer)), W = /^(libsql|wss?|https?):/i, G = () => process.env.DB_AUTH_TOKEN ?? process.env.TURSO_AUTH_TOKEN, ce = () => process.env.DB_SYNC_URL ?? process.env.TURSO_SYNC_URL, le = () => {
|
|
227
|
+
let e = process.env.DB_SYNC_INTERVAL ?? process.env.TURSO_SYNC_INTERVAL;
|
|
228
|
+
if (e === void 0 || e.trim() === "") return;
|
|
229
|
+
let t = Number(e);
|
|
230
|
+
if (!Number.isFinite(t) || t <= 0) throw Error(`@voltro/sql-turso: invalid DB_SYNC_INTERVAL '${e}' — expected a positive number of seconds.`);
|
|
231
|
+
return t;
|
|
232
|
+
}, ue = () => process.env.DB_ENCRYPTION_KEY ?? process.env.TURSO_ENCRYPTION_KEY, de = () => {
|
|
233
|
+
let e = process.env.DB_READ_YOUR_WRITES;
|
|
234
|
+
if (e !== void 0) return e !== "false" && e !== "0";
|
|
235
|
+
}, K = (e, t, n) => {
|
|
236
|
+
let r = le(), i = de(), a = ue();
|
|
237
|
+
return {
|
|
238
|
+
kind: "remote",
|
|
239
|
+
url: e,
|
|
240
|
+
authToken: t,
|
|
241
|
+
...n === void 0 ? {} : { syncUrl: n },
|
|
242
|
+
...r === void 0 ? {} : { syncIntervalSeconds: r },
|
|
243
|
+
...i === void 0 ? {} : { readYourWrites: i },
|
|
244
|
+
...a === void 0 ? {} : { encryptionKey: a }
|
|
245
|
+
};
|
|
246
|
+
}, q = (e) => {
|
|
247
|
+
let t = e.maxConnections, n = t === void 0 ? {} : { maxConnections: t }, r = ce();
|
|
248
|
+
if (e.url) {
|
|
249
|
+
if (W.test(e.url)) {
|
|
250
|
+
let t = G();
|
|
251
|
+
if (t === void 0) throw Error(`@voltro/sql-turso: remote Turso url '${e.url}' needs an auth token. Set DB_AUTH_TOKEN (or TURSO_AUTH_TOKEN) to the token from 'turso db tokens create <db>'.`);
|
|
252
|
+
return K(e.url, t, r);
|
|
253
|
+
}
|
|
254
|
+
if (e.url === ":memory:") return {
|
|
255
|
+
kind: "local",
|
|
256
|
+
filename: ":memory:",
|
|
257
|
+
...n
|
|
258
|
+
};
|
|
259
|
+
if (e.url.startsWith("file:")) {
|
|
260
|
+
if (r !== void 0) {
|
|
261
|
+
let t = G();
|
|
262
|
+
if (t === void 0) throw Error(`@voltro/sql-turso: embedded replica (file '${e.url}' + DB_SYNC_URL='${r}') needs an auth token for the remote primary. Set DB_AUTH_TOKEN (or TURSO_AUTH_TOKEN).`);
|
|
263
|
+
return K(e.url, t, r);
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
kind: "local",
|
|
267
|
+
filename: e.url.slice(5),
|
|
268
|
+
...n
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
throw Error(`@voltro/sql-turso: unsupported url '${e.url}'. Accepted: 'file:./db.turso', ':memory:' (local Rust engine), or 'libsql://…'/'https://…'/'wss://…' (remote Turso Cloud).`);
|
|
272
|
+
}
|
|
273
|
+
if (e.database) return {
|
|
274
|
+
kind: "local",
|
|
275
|
+
filename: e.database,
|
|
276
|
+
...n
|
|
277
|
+
};
|
|
278
|
+
throw Error("@voltro/sql-turso: no url/filename supplied. Set DB_URL=file:./db.turso (local), DB_URL=libsql://<db>.turso.io + DB_AUTH_TOKEN (remote), or DB_DATABASE=path/to/db.turso.");
|
|
279
|
+
}, J = (e) => ie({
|
|
280
|
+
filename: r.succeed(e.filename),
|
|
281
|
+
...e.readonly === void 0 ? {} : { readonly: r.succeed(e.readonly) },
|
|
282
|
+
...e.maxConnections === void 0 ? {} : { maxConnections: r.succeed(e.maxConnections) }
|
|
283
|
+
}), Y = (e) => U({
|
|
284
|
+
url: r.succeed(e.url),
|
|
285
|
+
...e.authToken === void 0 ? {} : { authToken: r.succeed(e.authToken) },
|
|
286
|
+
...e.syncUrl === void 0 ? {} : { syncUrl: r.succeed(e.syncUrl) },
|
|
287
|
+
...e.syncIntervalSeconds === void 0 ? {} : { syncInterval: r.succeed(`${e.syncIntervalSeconds} seconds`) },
|
|
288
|
+
...e.readYourWrites === void 0 ? {} : { readYourWrites: r.succeed(e.readYourWrites) },
|
|
289
|
+
...e.encryptionKey === void 0 ? {} : { encryptionKey: r.succeed(e.encryptionKey) }
|
|
290
|
+
}), X = (e) => {
|
|
291
|
+
let t = q(e);
|
|
292
|
+
return t.kind === "remote" ? Y(t) : J(t);
|
|
293
|
+
}, fe = /write-write conflict|write-read conflict|database is locked|table is locked|database is busy/i, pe = /* @__PURE__ */ new Set([
|
|
294
|
+
"SQLITE_BUSY",
|
|
295
|
+
"SQLITE_LOCKED",
|
|
296
|
+
"5",
|
|
297
|
+
"6"
|
|
298
|
+
]), Z = (e, t) => {
|
|
299
|
+
let n = e, r = "";
|
|
300
|
+
for (let e = 0; e < 6 && typeof n == "object" && n; e++) {
|
|
301
|
+
let e = t(n);
|
|
302
|
+
e !== void 0 && (r += ` ${e}`), n = n.cause;
|
|
303
|
+
}
|
|
304
|
+
return r;
|
|
305
|
+
}, me = (e) => Z(e, (e) => typeof e.message == "string" ? e.message : void 0), he = (e) => {
|
|
306
|
+
let t = e;
|
|
307
|
+
for (let e = 0; e < 6 && typeof t == "object" && t; e++) {
|
|
308
|
+
let e = t.code;
|
|
309
|
+
if (typeof e == "string" && e !== "GenericFailure") return e;
|
|
310
|
+
if (typeof e == "number") return String(e);
|
|
311
|
+
t = t.cause;
|
|
312
|
+
}
|
|
313
|
+
}, Q = (e) => {
|
|
314
|
+
if (fe.test(me(e))) return !0;
|
|
315
|
+
let t = he(e);
|
|
316
|
+
return t !== void 0 && pe.has(t);
|
|
317
|
+
}, $ = (e) => Q(e) ? "retry" : "noRetry", ge = {
|
|
318
|
+
id: "turso",
|
|
319
|
+
makeSqlLayer: (e) => X(e),
|
|
320
|
+
makeStore: (t) => e({
|
|
321
|
+
...t,
|
|
322
|
+
isRetryable: Q,
|
|
323
|
+
systemName: "turso",
|
|
324
|
+
wrapTransaction: x
|
|
325
|
+
}),
|
|
326
|
+
compileContains: (e, t, n) => n`${e} LIKE ${`%${t.replace(/[\\%_]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
|
|
327
|
+
retryFilter: $
|
|
328
|
+
};
|
|
329
|
+
//#endregion
|
|
330
|
+
export { k as LibsqlClient, C as TursoClient, q as connectionFromConfig, Q as isTursoRetryableFailure, Y as makeLibsqlSqlLayer, J as makeTursoSqlLayer, X as makeTursoSqlLayerFromConfig, ge as tursoDialect, $ as tursoRetryFilter };
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voltro/sql-turso",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Turso dialect adapter for Voltro's cross-dialect DataStore — local Rust SQLite with MVCC BEGIN CONCURRENT, or remote Turso Cloud (libsql://) with embedded-replica sync.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"voltro",
|
|
7
|
+
"typescript",
|
|
8
|
+
"framework"
|
|
9
|
+
],
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"homepage": "https://voltro.dev",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"email": "support@voltro.dev"
|
|
14
|
+
},
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "Voltro UG",
|
|
17
|
+
"url": "https://voltro.dev"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=24.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@effect/experimental": "^0.60.0",
|
|
36
|
+
"@effect/sql": "^0.51.1",
|
|
37
|
+
"@libsql/client": "^0.17.4",
|
|
38
|
+
"@tursodatabase/database": "^0.6.1",
|
|
39
|
+
"@voltro/database": "0.1.0",
|
|
40
|
+
"@voltro/logger": "0.1.0",
|
|
41
|
+
"@voltro/sql-sqlite": "0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"effect": "^3.21.4"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|