@zm2231/kysely-powersync-dialect 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/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/auth.d.ts +9 -0
- package/dist/auth.js +75 -0
- package/dist/auth.js.map +1 -0
- package/dist/connect.d.ts +7 -0
- package/dist/connect.js +115 -0
- package/dist/connect.js.map +1 -0
- package/dist/dialect.d.ts +41 -0
- package/dist/dialect.js +291 -0
- package/dist/dialect.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/remote-dialect.d.ts +10 -0
- package/dist/remote-dialect.js +70 -0
- package/dist/remote-dialect.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/examples/README.md +8 -0
- package/examples/extension-hooks.md +62 -0
- package/examples/local-powersync.md +50 -0
- package/examples/remote-writes.md +32 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zain
|
|
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
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @zm2231/kysely-powersync-dialect
|
|
2
|
+
|
|
3
|
+
A small Kysely dialect for PowerSync Node clients.
|
|
4
|
+
|
|
5
|
+
PowerSync gives Node apps a local SQLite replica plus an upload queue. Kysely expects one database connection. This dialect splits the path:
|
|
6
|
+
|
|
7
|
+
- read queries use the local SQLite replica through `better-sqlite3`
|
|
8
|
+
- writes go through `PowerSyncDatabase.execute()`, or through a remote write endpoint
|
|
9
|
+
- PowerSync credentials and uploads use explicit URLs by default
|
|
10
|
+
- auth, upload, read classification, and write routing can be replaced with hooks
|
|
11
|
+
|
|
12
|
+
This is intentionally only the dialect layer. It does not ship app schemas, migrations, CRM tables, sync rules, or schema engines.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @zm2231/kysely-powersync-dialect kysely @powersync/common @powersync/node better-sqlite3
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
- [Examples index](examples/README.md)
|
|
23
|
+
- [Local PowerSync database](examples/local-powersync.md)
|
|
24
|
+
- [Remote writes](examples/remote-writes.md)
|
|
25
|
+
- [Extension hooks](examples/extension-hooks.md)
|
|
26
|
+
|
|
27
|
+
## Limitations
|
|
28
|
+
|
|
29
|
+
- The dialect is designed for PowerSync's local-first model. Reads inside Kysely transactions still read from the local replica, so they are useful for read-only transactional code but should not be treated as read-your-write guarantees after queued PowerSync writes.
|
|
30
|
+
- Kysely transactions are read-only in this dialect. Writes inside a Kysely transaction throw instead of pretending the split read/write paths are atomic.
|
|
31
|
+
- Raw SQL read/write routing is conservative. Override `readQueryClassifier` when your app has SQL forms the default classifier should not decide.
|
|
32
|
+
- The package does not delete SQLite `-wal` or `-shm` sidecars. Those files can contain uncheckpointed data. Handle recovery explicitly in your app if you need it.
|
|
33
|
+
- Users provide their own PowerSync schema. No built-in tables are created for you.
|
|
34
|
+
- `RETURNING` rows are passed through when the underlying PowerSync SDK returns rows. If your PowerSync runtime does not return rows for a write, Kysely receives an empty row list.
|
|
35
|
+
|
|
36
|
+
## Why this exists
|
|
37
|
+
|
|
38
|
+
PowerSync's web SDK has Kysely-friendly patterns, but Node apps need a small bridge between Kysely's dialect API and PowerSync's local replica/write queue split. This package is that bridge.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PowerSyncConfig, TokenResponse } from "./types.js";
|
|
2
|
+
export interface PowerSyncConfigValidationOptions {
|
|
3
|
+
requireAuthUrl?: boolean;
|
|
4
|
+
requireUploadUrl?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function validatePowerSyncConfig(config: PowerSyncConfig, options?: PowerSyncConfigValidationOptions): void;
|
|
7
|
+
export declare function fetchPowerSyncToken(config: PowerSyncConfig): Promise<TokenResponse>;
|
|
8
|
+
export declare function cfHeaders(config: PowerSyncConfig): Record<string, string>;
|
|
9
|
+
export declare function parseTokenResponse(payload: unknown): TokenResponse;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export function validatePowerSyncConfig(config, options = {}) {
|
|
2
|
+
const requireAuthUrl = options.requireAuthUrl ?? true;
|
|
3
|
+
const requireUploadUrl = options.requireUploadUrl ?? true;
|
|
4
|
+
validateHttpUrl("powersync_url", config.powersync_url);
|
|
5
|
+
if (config.auth_url) {
|
|
6
|
+
validateHttpUrl("auth_url", config.auth_url);
|
|
7
|
+
}
|
|
8
|
+
else if (requireAuthUrl) {
|
|
9
|
+
throw new Error("PowerSync auth_url is required unless fetchCredentials is provided");
|
|
10
|
+
}
|
|
11
|
+
if (config.upload_url) {
|
|
12
|
+
validateHttpUrl("upload_url", config.upload_url);
|
|
13
|
+
}
|
|
14
|
+
else if (requireUploadUrl) {
|
|
15
|
+
throw new Error("PowerSync upload_url is required unless uploadTransaction is provided");
|
|
16
|
+
}
|
|
17
|
+
if (!config.user_id.trim()) {
|
|
18
|
+
throw new Error("PowerSync user_id is required");
|
|
19
|
+
}
|
|
20
|
+
if (!config.db_path.trim()) {
|
|
21
|
+
throw new Error("PowerSync db_path is required");
|
|
22
|
+
}
|
|
23
|
+
const hasCfId = Boolean(config.cf_access_client_id);
|
|
24
|
+
const hasCfSecret = Boolean(config.cf_access_client_secret);
|
|
25
|
+
if (hasCfId !== hasCfSecret) {
|
|
26
|
+
throw new Error("PowerSync Cloudflare Access config requires both client id and client secret");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function fetchPowerSyncToken(config) {
|
|
30
|
+
validatePowerSyncConfig(config, { requireAuthUrl: true, requireUploadUrl: false });
|
|
31
|
+
const url = new URL(config.auth_url);
|
|
32
|
+
url.searchParams.set("user_id", config.user_id);
|
|
33
|
+
const response = await fetch(url, { headers: cfHeaders(config) });
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`PowerSync auth failed: ${response.status} ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
return parseTokenResponse(await response.json());
|
|
38
|
+
}
|
|
39
|
+
export function cfHeaders(config) {
|
|
40
|
+
const headers = {};
|
|
41
|
+
if (config.cf_access_client_id && config.cf_access_client_secret) {
|
|
42
|
+
headers["CF-Access-Client-Id"] = config.cf_access_client_id;
|
|
43
|
+
headers["CF-Access-Client-Secret"] = config.cf_access_client_secret;
|
|
44
|
+
}
|
|
45
|
+
return headers;
|
|
46
|
+
}
|
|
47
|
+
function validateHttpUrl(field, value) {
|
|
48
|
+
let parsed;
|
|
49
|
+
try {
|
|
50
|
+
parsed = new URL(value);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new Error(`PowerSync ${field} must be a valid URL`);
|
|
54
|
+
}
|
|
55
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
56
|
+
throw new Error(`PowerSync ${field} must use http or https`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function parseTokenResponse(payload) {
|
|
60
|
+
if (!payload || typeof payload !== "object") {
|
|
61
|
+
throw new Error("PowerSync auth response must be a JSON object");
|
|
62
|
+
}
|
|
63
|
+
const response = payload;
|
|
64
|
+
if (typeof response.token !== "string" || response.token.trim().length === 0) {
|
|
65
|
+
throw new Error("PowerSync auth response is missing token");
|
|
66
|
+
}
|
|
67
|
+
if (typeof response.expires_at !== "number" || !Number.isFinite(response.expires_at)) {
|
|
68
|
+
throw new Error("PowerSync auth response is missing numeric expires_at");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
token: response.token,
|
|
72
|
+
expires_at: response.expires_at,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PowerSyncDatabase } from "@powersync/node";
|
|
2
|
+
import type { Schema } from "@powersync/common";
|
|
3
|
+
import type { CreatePowerSyncDatabaseOptions, PowerSyncConfig, PowerSyncCrudTransaction, PowerSyncUploadContext } from "./types.js";
|
|
4
|
+
export declare function createConnectedPowerSyncDatabase(config: PowerSyncConfig, schema: Schema, options?: CreatePowerSyncDatabaseOptions): Promise<PowerSyncDatabase>;
|
|
5
|
+
export declare function defaultUploadTransaction(transaction: PowerSyncCrudTransaction, context: PowerSyncUploadContext): Promise<{
|
|
6
|
+
checkpoint?: string;
|
|
7
|
+
} | void>;
|
package/dist/connect.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { Agent } from "undici";
|
|
4
|
+
import { PowerSyncDatabase } from "@powersync/node";
|
|
5
|
+
import { cfHeaders, fetchPowerSyncToken, validatePowerSyncConfig } from "./auth.js";
|
|
6
|
+
import { ensureSqliteFileExists } from "./dialect.js";
|
|
7
|
+
export async function createConnectedPowerSyncDatabase(config, schema, options) {
|
|
8
|
+
validatePowerSyncConfig(config, {
|
|
9
|
+
requireAuthUrl: !options?.fetchCredentials,
|
|
10
|
+
requireUploadUrl: !options?.uploadTransaction,
|
|
11
|
+
});
|
|
12
|
+
mkdirSync(dirname(config.db_path), { recursive: true });
|
|
13
|
+
ensureSqliteFileExists(config.db_path);
|
|
14
|
+
const db = new PowerSyncDatabase({
|
|
15
|
+
schema,
|
|
16
|
+
database: { dbFilename: config.db_path },
|
|
17
|
+
});
|
|
18
|
+
const headers = cfHeaders(config);
|
|
19
|
+
const dispatcher = Object.keys(headers).length > 0
|
|
20
|
+
? new Agent({
|
|
21
|
+
interceptors: {
|
|
22
|
+
Pool: [(dispatch) => (opts, handler) => {
|
|
23
|
+
if (opts.headers && typeof opts.headers === "object" && !Array.isArray(opts.headers)) {
|
|
24
|
+
Object.assign(opts.headers, headers);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
opts.headers = { ...(opts.headers ?? {}), ...headers };
|
|
28
|
+
}
|
|
29
|
+
return dispatch(opts, handler);
|
|
30
|
+
}],
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
: undefined;
|
|
34
|
+
if (options?.onStatusChanged) {
|
|
35
|
+
db.registerListener({
|
|
36
|
+
statusChanged: (status) => {
|
|
37
|
+
options.onStatusChanged?.(status);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const connector = {
|
|
42
|
+
async fetchCredentials() {
|
|
43
|
+
if (options?.fetchCredentials) {
|
|
44
|
+
return await options.fetchCredentials(config);
|
|
45
|
+
}
|
|
46
|
+
const credentials = await fetchPowerSyncToken(config);
|
|
47
|
+
return {
|
|
48
|
+
endpoint: config.powersync_url,
|
|
49
|
+
token: credentials.token,
|
|
50
|
+
expiresAt: new Date(credentials.expires_at * 1000),
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
async uploadData() {
|
|
54
|
+
await flushCrud(db, config, headers, options?.uploadTransaction);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
await db.connect(connector, {
|
|
58
|
+
fetchOptions: { headers },
|
|
59
|
+
...(dispatcher ? { dispatcher } : {}),
|
|
60
|
+
});
|
|
61
|
+
options?.onStatusChanged?.(db.currentStatus);
|
|
62
|
+
return db;
|
|
63
|
+
}
|
|
64
|
+
async function flushCrud(db, config, headers, uploadTransaction = defaultUploadTransaction) {
|
|
65
|
+
while (true) {
|
|
66
|
+
const tx = await db.getNextCrudTransaction();
|
|
67
|
+
if (!tx)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
const result = await uploadTransaction(tx, { config, headers });
|
|
71
|
+
await tx.complete(result?.checkpoint);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function defaultUploadTransaction(transaction, context) {
|
|
79
|
+
if (!context.config.upload_url) {
|
|
80
|
+
throw new Error("PowerSync upload_url is required for defaultUploadTransaction");
|
|
81
|
+
}
|
|
82
|
+
const response = await fetch(context.config.upload_url, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
...context.headers,
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
transactionId: transaction.transactionId,
|
|
90
|
+
operations: transaction.crud.map(serializeCrudEntry),
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`PowerSync upload failed: ${response.status} ${await response.text()}`);
|
|
95
|
+
}
|
|
96
|
+
if (response.status === 204)
|
|
97
|
+
return;
|
|
98
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
99
|
+
if (!contentType.includes("application/json"))
|
|
100
|
+
return;
|
|
101
|
+
const payload = await response.json();
|
|
102
|
+
if (payload.checkpoint !== undefined && typeof payload.checkpoint !== "string") {
|
|
103
|
+
throw new Error("PowerSync upload response checkpoint must be a string");
|
|
104
|
+
}
|
|
105
|
+
return payload.checkpoint ? { checkpoint: payload.checkpoint } : undefined;
|
|
106
|
+
}
|
|
107
|
+
function serializeCrudEntry(op) {
|
|
108
|
+
return {
|
|
109
|
+
table: op.table,
|
|
110
|
+
id: op.id,
|
|
111
|
+
data: op.opData,
|
|
112
|
+
op: op.op,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=connect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
2
|
+
import { CompiledQuery, Kysely, type DatabaseConnection, type DatabaseIntrospector, type Dialect, type DialectAdapter, type Driver, type QueryCompiler, type QueryResult, type TransactionSettings } from "kysely";
|
|
3
|
+
import type { PowerSyncDialectConfig, ReadQueryClassifier } from "./types.js";
|
|
4
|
+
export interface WriteExecutor {
|
|
5
|
+
executeQuery<R>(compiledQuery: CompiledQuery): Promise<QueryResult<R>>;
|
|
6
|
+
destroy(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export interface SplitPowerSyncDriverConfig {
|
|
9
|
+
readDatabase: BetterSqlite3.Database | (() => Promise<BetterSqlite3.Database> | BetterSqlite3.Database);
|
|
10
|
+
createWriteExecutor: () => Promise<WriteExecutor>;
|
|
11
|
+
readQueryClassifier?: ReadQueryClassifier;
|
|
12
|
+
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export declare class PowerSyncDialect implements Dialect {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(config: PowerSyncDialectConfig);
|
|
17
|
+
createDriver(): Driver;
|
|
18
|
+
createQueryCompiler(): QueryCompiler;
|
|
19
|
+
createAdapter(): DialectAdapter;
|
|
20
|
+
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector;
|
|
21
|
+
}
|
|
22
|
+
export declare function openPowerSyncReadDatabase(dbPath: string): Promise<BetterSqlite3.Database>;
|
|
23
|
+
export declare function isPowerSyncReadQuery(sqlText: string): boolean;
|
|
24
|
+
export declare const defaultReadQueryClassifier: ReadQueryClassifier;
|
|
25
|
+
export declare class SplitPowerSyncDriver implements Driver {
|
|
26
|
+
#private;
|
|
27
|
+
constructor(config: SplitPowerSyncDriverConfig);
|
|
28
|
+
init(): Promise<void>;
|
|
29
|
+
acquireConnection(): Promise<DatabaseConnection>;
|
|
30
|
+
beginTransaction(connection: DatabaseConnection, _settings: TransactionSettings): Promise<void>;
|
|
31
|
+
commitTransaction(connection: DatabaseConnection): Promise<void>;
|
|
32
|
+
rollbackTransaction(connection: DatabaseConnection): Promise<void>;
|
|
33
|
+
savepoint(connection: DatabaseConnection, savepointName: string): Promise<void>;
|
|
34
|
+
rollbackToSavepoint(connection: DatabaseConnection, savepointName: string): Promise<void>;
|
|
35
|
+
releaseSavepoint(connection: DatabaseConnection, savepointName: string): Promise<void>;
|
|
36
|
+
releaseConnection(_connection: DatabaseConnection): Promise<void>;
|
|
37
|
+
destroy(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare function resolveValue<T>(value: T | (() => T | Promise<T>) | Promise<T>): Promise<T>;
|
|
40
|
+
export declare function normalizeRemoteResult<R>(payload: unknown): QueryResult<R>;
|
|
41
|
+
export declare function ensureSqliteFileExists(dbPath: string): void;
|
package/dist/dialect.js
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
4
|
+
import { SelectQueryNode, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, } from "kysely";
|
|
5
|
+
const READ_QUERY_REGEX = /^(SELECT|EXPLAIN)\b/i;
|
|
6
|
+
const READ_PRAGMA_REGEX = /^PRAGMA\s+(table_info|table_xinfo|index_info|index_xinfo|index_list|foreign_key_list|database_list|quick_check|integrity_check|user_version|application_id|schema_version)\b/i;
|
|
7
|
+
export class PowerSyncDialect {
|
|
8
|
+
#config;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.#config = Object.freeze({ ...config });
|
|
11
|
+
}
|
|
12
|
+
createDriver() {
|
|
13
|
+
return new SplitPowerSyncDriver({
|
|
14
|
+
readDatabase: this.#config.readDatabase,
|
|
15
|
+
createWriteExecutor: async () => new LocalPowerSyncWriteExecutor(await resolveValue(this.#config.writeDatabase)),
|
|
16
|
+
readQueryClassifier: this.#config.readQueryClassifier,
|
|
17
|
+
onCreateConnection: this.#config.onCreateConnection,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
createQueryCompiler() {
|
|
21
|
+
return new SqliteQueryCompiler();
|
|
22
|
+
}
|
|
23
|
+
createAdapter() {
|
|
24
|
+
return new SqliteAdapter();
|
|
25
|
+
}
|
|
26
|
+
createIntrospector(db) {
|
|
27
|
+
return new SqliteIntrospector(db);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function openPowerSyncReadDatabase(dbPath) {
|
|
31
|
+
ensureSqliteFileExists(dbPath);
|
|
32
|
+
return new BetterSqlite3(dbPath, { readonly: true, fileMustExist: true });
|
|
33
|
+
}
|
|
34
|
+
export function isPowerSyncReadQuery(sqlText) {
|
|
35
|
+
return isReadSql(sqlText);
|
|
36
|
+
}
|
|
37
|
+
export const defaultReadQueryClassifier = (compiledQuery) => {
|
|
38
|
+
if (SelectQueryNode.is(compiledQuery.query))
|
|
39
|
+
return true;
|
|
40
|
+
return isReadSql(compiledQuery.sql);
|
|
41
|
+
};
|
|
42
|
+
export class SplitPowerSyncDriver {
|
|
43
|
+
#config;
|
|
44
|
+
#connectionMutex = new ConnectionMutex();
|
|
45
|
+
#readDb;
|
|
46
|
+
#writeExecutor;
|
|
47
|
+
#connection;
|
|
48
|
+
constructor(config) {
|
|
49
|
+
this.#config = Object.freeze({ ...config });
|
|
50
|
+
}
|
|
51
|
+
async init() {
|
|
52
|
+
this.#readDb = await resolveValue(this.#config.readDatabase);
|
|
53
|
+
this.#writeExecutor = await this.#config.createWriteExecutor();
|
|
54
|
+
this.#connection = new SplitPowerSyncConnection(this.#readDb, this.#writeExecutor, this.#config.readQueryClassifier ?? defaultReadQueryClassifier);
|
|
55
|
+
if (this.#config.onCreateConnection) {
|
|
56
|
+
await this.#config.onCreateConnection(this.#connection);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async acquireConnection() {
|
|
60
|
+
if (!this.#connection)
|
|
61
|
+
throw new Error("PowerSync driver has not been initialized");
|
|
62
|
+
await this.#connectionMutex.lock();
|
|
63
|
+
return this.#connection;
|
|
64
|
+
}
|
|
65
|
+
async beginTransaction(connection, _settings) {
|
|
66
|
+
if (!(connection instanceof SplitPowerSyncConnection)) {
|
|
67
|
+
throw new Error("Unsupported PowerSync connection instance");
|
|
68
|
+
}
|
|
69
|
+
connection.enterTransaction();
|
|
70
|
+
try {
|
|
71
|
+
await connection.beginReadTransaction();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
connection.exitTransaction();
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async commitTransaction(connection) {
|
|
79
|
+
if (!(connection instanceof SplitPowerSyncConnection)) {
|
|
80
|
+
throw new Error("Unsupported PowerSync connection instance");
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
await connection.commitReadTransaction();
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
connection.exitTransaction();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async rollbackTransaction(connection) {
|
|
90
|
+
if (!(connection instanceof SplitPowerSyncConnection)) {
|
|
91
|
+
throw new Error("Unsupported PowerSync connection instance");
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await connection.rollbackReadTransaction();
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
connection.exitTransaction();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async savepoint(connection, savepointName) {
|
|
101
|
+
if (!(connection instanceof SplitPowerSyncConnection)) {
|
|
102
|
+
throw new Error("Unsupported PowerSync connection instance");
|
|
103
|
+
}
|
|
104
|
+
await connection.savepoint(savepointName);
|
|
105
|
+
}
|
|
106
|
+
async rollbackToSavepoint(connection, savepointName) {
|
|
107
|
+
if (!(connection instanceof SplitPowerSyncConnection)) {
|
|
108
|
+
throw new Error("Unsupported PowerSync connection instance");
|
|
109
|
+
}
|
|
110
|
+
await connection.rollbackToSavepoint(savepointName);
|
|
111
|
+
}
|
|
112
|
+
async releaseSavepoint(connection, savepointName) {
|
|
113
|
+
if (!(connection instanceof SplitPowerSyncConnection)) {
|
|
114
|
+
throw new Error("Unsupported PowerSync connection instance");
|
|
115
|
+
}
|
|
116
|
+
await connection.releaseSavepoint(savepointName);
|
|
117
|
+
}
|
|
118
|
+
async releaseConnection(_connection) {
|
|
119
|
+
this.#connectionMutex.unlock();
|
|
120
|
+
}
|
|
121
|
+
async destroy() {
|
|
122
|
+
await this.#writeExecutor?.destroy();
|
|
123
|
+
this.#readDb?.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
class SplitPowerSyncConnection {
|
|
127
|
+
#readDb;
|
|
128
|
+
#writeExecutor;
|
|
129
|
+
#readQueryClassifier;
|
|
130
|
+
#transactionDepth = 0;
|
|
131
|
+
constructor(readDb, writeExecutor, readQueryClassifier) {
|
|
132
|
+
this.#readDb = readDb;
|
|
133
|
+
this.#writeExecutor = writeExecutor;
|
|
134
|
+
this.#readQueryClassifier = readQueryClassifier;
|
|
135
|
+
}
|
|
136
|
+
enterTransaction() {
|
|
137
|
+
this.#transactionDepth += 1;
|
|
138
|
+
}
|
|
139
|
+
exitTransaction() {
|
|
140
|
+
this.#transactionDepth = Math.max(0, this.#transactionDepth - 1);
|
|
141
|
+
}
|
|
142
|
+
async executeQuery(compiledQuery) {
|
|
143
|
+
if (this.#readQueryClassifier(compiledQuery)) {
|
|
144
|
+
return this.#executeReadQuery(compiledQuery);
|
|
145
|
+
}
|
|
146
|
+
if (this.#transactionDepth > 0) {
|
|
147
|
+
throw new Error("PowerSync dialect only supports read queries inside Kysely transactions");
|
|
148
|
+
}
|
|
149
|
+
return this.#writeExecutor.executeQuery(compiledQuery);
|
|
150
|
+
}
|
|
151
|
+
async beginReadTransaction() {
|
|
152
|
+
this.#readDb.exec("begin");
|
|
153
|
+
}
|
|
154
|
+
async commitReadTransaction() {
|
|
155
|
+
this.#readDb.exec("commit");
|
|
156
|
+
}
|
|
157
|
+
async rollbackReadTransaction() {
|
|
158
|
+
this.#readDb.exec("rollback");
|
|
159
|
+
}
|
|
160
|
+
async savepoint(savepointName) {
|
|
161
|
+
this.#readDb.exec(`savepoint ${quoteIdentifier(savepointName)}`);
|
|
162
|
+
}
|
|
163
|
+
async rollbackToSavepoint(savepointName) {
|
|
164
|
+
this.#readDb.exec(`rollback to ${quoteIdentifier(savepointName)}`);
|
|
165
|
+
}
|
|
166
|
+
async releaseSavepoint(savepointName) {
|
|
167
|
+
this.#readDb.exec(`release ${quoteIdentifier(savepointName)}`);
|
|
168
|
+
}
|
|
169
|
+
async *streamQuery(compiledQuery) {
|
|
170
|
+
if (!SelectQueryNode.is(compiledQuery.query)) {
|
|
171
|
+
throw new Error("PowerSync dialect only supports streaming select queries");
|
|
172
|
+
}
|
|
173
|
+
const stmt = this.#readDb.prepare(compiledQuery.sql);
|
|
174
|
+
const iter = stmt.iterate(compiledQuery.parameters);
|
|
175
|
+
for (const row of iter) {
|
|
176
|
+
yield { rows: [row] };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
#executeReadQuery(compiledQuery) {
|
|
180
|
+
const stmt = this.#readDb.prepare(compiledQuery.sql);
|
|
181
|
+
const rows = stmt.all(compiledQuery.parameters);
|
|
182
|
+
return { rows };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
class LocalPowerSyncWriteExecutor {
|
|
186
|
+
#db;
|
|
187
|
+
constructor(db) {
|
|
188
|
+
this.#db = db;
|
|
189
|
+
}
|
|
190
|
+
async executeQuery(compiledQuery) {
|
|
191
|
+
const result = await this.#db.execute(compiledQuery.sql, [...compiledQuery.parameters]);
|
|
192
|
+
const rows = result.rows;
|
|
193
|
+
return {
|
|
194
|
+
rows: Array.isArray(rows) ? rows : [],
|
|
195
|
+
numAffectedRows: typeof result.rowsAffected === "number" ? BigInt(result.rowsAffected) : undefined,
|
|
196
|
+
insertId: toBigInt(result.insertId),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async destroy() {
|
|
200
|
+
await this.#db.disconnect();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export async function resolveValue(value) {
|
|
204
|
+
if (typeof value === "function") {
|
|
205
|
+
return await value();
|
|
206
|
+
}
|
|
207
|
+
return await value;
|
|
208
|
+
}
|
|
209
|
+
export function normalizeRemoteResult(payload) {
|
|
210
|
+
if (!payload || typeof payload !== "object") {
|
|
211
|
+
return { rows: [] };
|
|
212
|
+
}
|
|
213
|
+
const result = payload;
|
|
214
|
+
return {
|
|
215
|
+
rows: Array.isArray(result.rows) ? result.rows : [],
|
|
216
|
+
numAffectedRows: toBigInt(result.numAffectedRows),
|
|
217
|
+
insertId: toBigInt(result.insertId),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export function ensureSqliteFileExists(dbPath) {
|
|
221
|
+
if (existsSync(dbPath))
|
|
222
|
+
return;
|
|
223
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
224
|
+
const bootstrap = new BetterSqlite3(dbPath);
|
|
225
|
+
bootstrap.pragma("journal_mode = WAL");
|
|
226
|
+
bootstrap.close();
|
|
227
|
+
}
|
|
228
|
+
function toBigInt(value) {
|
|
229
|
+
if (value === null || value === undefined)
|
|
230
|
+
return undefined;
|
|
231
|
+
if (typeof value === "bigint")
|
|
232
|
+
return value;
|
|
233
|
+
if (typeof value === "number")
|
|
234
|
+
return BigInt(value);
|
|
235
|
+
if (typeof value === "string" && value.length > 0)
|
|
236
|
+
return BigInt(value);
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
function isReadSql(sqlText) {
|
|
240
|
+
const normalized = stripLeadingSqlTrivia(sqlText);
|
|
241
|
+
if (READ_QUERY_REGEX.test(normalized))
|
|
242
|
+
return true;
|
|
243
|
+
const statement = stripTrailingStatementTerminators(normalized);
|
|
244
|
+
if (READ_PRAGMA_REGEX.test(statement) && !/=/.test(statement))
|
|
245
|
+
return true;
|
|
246
|
+
if (!/^WITH\b/i.test(statement))
|
|
247
|
+
return false;
|
|
248
|
+
return /\bSELECT\b/i.test(statement) &&
|
|
249
|
+
!/\b(INSERT|UPDATE|DELETE|REPLACE|UPSERT|CREATE|ALTER|DROP)\b/i.test(statement);
|
|
250
|
+
}
|
|
251
|
+
function stripLeadingSqlTrivia(sqlText) {
|
|
252
|
+
let remaining = sqlText.trimStart();
|
|
253
|
+
while (true) {
|
|
254
|
+
if (remaining.startsWith("--")) {
|
|
255
|
+
const nextLine = remaining.indexOf("\n");
|
|
256
|
+
remaining = nextLine === -1 ? "" : remaining.slice(nextLine + 1).trimStart();
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (remaining.startsWith("/*")) {
|
|
260
|
+
const close = remaining.indexOf("*/");
|
|
261
|
+
remaining = close === -1 ? "" : remaining.slice(close + 2).trimStart();
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
return remaining;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function stripTrailingStatementTerminators(sqlText) {
|
|
268
|
+
return sqlText.replace(/;\s*$/, "");
|
|
269
|
+
}
|
|
270
|
+
function quoteIdentifier(identifier) {
|
|
271
|
+
return `"${identifier.replaceAll("\"", "\"\"")}"`;
|
|
272
|
+
}
|
|
273
|
+
class ConnectionMutex {
|
|
274
|
+
#promise;
|
|
275
|
+
#resolve;
|
|
276
|
+
async lock() {
|
|
277
|
+
while (this.#promise) {
|
|
278
|
+
await this.#promise;
|
|
279
|
+
}
|
|
280
|
+
this.#promise = new Promise((resolve) => {
|
|
281
|
+
this.#resolve = resolve;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
unlock() {
|
|
285
|
+
const resolve = this.#resolve;
|
|
286
|
+
this.#promise = undefined;
|
|
287
|
+
this.#resolve = undefined;
|
|
288
|
+
resolve?.();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=dialect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialect.js","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { PowerSyncDialect, SplitPowerSyncDriver, defaultReadQueryClassifier, ensureSqliteFileExists, isPowerSyncReadQuery, normalizeRemoteResult, openPowerSyncReadDatabase, resolveValue, } from "./dialect.js";
|
|
2
|
+
export { RemotePowerSyncDialect } from "./remote-dialect.js";
|
|
3
|
+
export { createConnectedPowerSyncDatabase, defaultUploadTransaction } from "./connect.js";
|
|
4
|
+
export { cfHeaders, fetchPowerSyncToken, parseTokenResponse, validatePowerSyncConfig } from "./auth.js";
|
|
5
|
+
export type { SplitPowerSyncDriverConfig, WriteExecutor } from "./dialect.js";
|
|
6
|
+
export type { CreatePowerSyncDatabaseOptions, PowerSyncCredentials, PowerSyncConfig, PowerSyncCrudEntry, PowerSyncCrudTransaction, PowerSyncDialectConfig, PowerSyncMode, PowerSyncRuntimeStatus, PowerSyncUploadContext, ReadQueryClassifier, RemotePowerSyncDialectConfig, TokenResponse, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { PowerSyncDialect, SplitPowerSyncDriver, defaultReadQueryClassifier, ensureSqliteFileExists, isPowerSyncReadQuery, normalizeRemoteResult, openPowerSyncReadDatabase, resolveValue, } from "./dialect.js";
|
|
2
|
+
export { RemotePowerSyncDialect } from "./remote-dialect.js";
|
|
3
|
+
export { createConnectedPowerSyncDatabase, defaultUploadTransaction } from "./connect.js";
|
|
4
|
+
export { cfHeaders, fetchPowerSyncToken, parseTokenResponse, validatePowerSyncConfig } from "./auth.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,EAC1B,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,EACzB,YAAY,GACb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,gCAAgC,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Kysely, type DatabaseIntrospector, type Dialect, type DialectAdapter, type Driver, type QueryCompiler } from "kysely";
|
|
2
|
+
import type { RemotePowerSyncDialectConfig } from "./types.js";
|
|
3
|
+
export declare class RemotePowerSyncDialect implements Dialect {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(config: RemotePowerSyncDialectConfig);
|
|
6
|
+
createDriver(): Driver;
|
|
7
|
+
createQueryCompiler(): QueryCompiler;
|
|
8
|
+
createAdapter(): DialectAdapter;
|
|
9
|
+
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector;
|
|
10
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, } from "kysely";
|
|
2
|
+
import { normalizeRemoteResult, SplitPowerSyncDriver } from "./dialect.js";
|
|
3
|
+
const TRANSACTION_SQL_REGEX = /^\s*(BEGIN|COMMIT|ROLLBACK|SAVEPOINT|RELEASE)\b/i;
|
|
4
|
+
export class RemotePowerSyncDialect {
|
|
5
|
+
#config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.#config = Object.freeze({ ...config });
|
|
8
|
+
}
|
|
9
|
+
createDriver() {
|
|
10
|
+
return new SplitPowerSyncDriver({
|
|
11
|
+
readDatabase: this.#config.readDatabase,
|
|
12
|
+
createWriteExecutor: async () => new RemotePowerSyncWriteExecutor(this.#config),
|
|
13
|
+
readQueryClassifier: this.#config.readQueryClassifier,
|
|
14
|
+
onCreateConnection: this.#config.onCreateConnection,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
createQueryCompiler() {
|
|
18
|
+
return new SqliteQueryCompiler();
|
|
19
|
+
}
|
|
20
|
+
createAdapter() {
|
|
21
|
+
return new SqliteAdapter();
|
|
22
|
+
}
|
|
23
|
+
createIntrospector(db) {
|
|
24
|
+
return new SqliteIntrospector(db);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
class RemotePowerSyncWriteExecutor {
|
|
28
|
+
#config;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.#config = config;
|
|
31
|
+
}
|
|
32
|
+
async executeQuery(compiledQuery) {
|
|
33
|
+
if (TRANSACTION_SQL_REGEX.test(compiledQuery.sql)) {
|
|
34
|
+
throw new Error("RemotePowerSyncDialect does not support remote transaction control statements");
|
|
35
|
+
}
|
|
36
|
+
const body = this.#config.writeBody
|
|
37
|
+
? this.#config.writeBody(compiledQuery.sql, compiledQuery.parameters)
|
|
38
|
+
: { sql: compiledQuery.sql, params: compiledQuery.parameters };
|
|
39
|
+
const response = await fetch(this.#config.writeEndpoint, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
headers: {
|
|
42
|
+
"content-type": "application/json",
|
|
43
|
+
...(this.#config.writeHeaders ?? {}),
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify(body),
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(`Remote PowerSync write failed: ${response.status} ${await response.text()}`);
|
|
49
|
+
}
|
|
50
|
+
const payload = await readResponsePayload(response);
|
|
51
|
+
if (this.#config.mapResult)
|
|
52
|
+
return this.#config.mapResult(payload);
|
|
53
|
+
return normalizeRemoteResult(payload);
|
|
54
|
+
}
|
|
55
|
+
async destroy() {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function readResponsePayload(response) {
|
|
60
|
+
if (response.status === 204)
|
|
61
|
+
return undefined;
|
|
62
|
+
const text = await response.text();
|
|
63
|
+
if (text.length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
66
|
+
if (!contentType.includes("application/json"))
|
|
67
|
+
return text;
|
|
68
|
+
return JSON.parse(text);
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=remote-dialect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-dialect.js","sourceRoot":"","sources":["../src/remote-dialect.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type BetterSqlite3 from "better-sqlite3";
|
|
2
|
+
import type { CompiledQuery, DatabaseConnection, QueryResult } from "kysely";
|
|
3
|
+
import type { PowerSyncDatabase } from "@powersync/node";
|
|
4
|
+
export type PowerSyncMode = "daemon" | "in-process" | "auto";
|
|
5
|
+
export type ReadQueryClassifier = (compiledQuery: CompiledQuery) => boolean;
|
|
6
|
+
export interface PowerSyncConfig {
|
|
7
|
+
type?: "powersync";
|
|
8
|
+
mode?: PowerSyncMode;
|
|
9
|
+
powersync_url: string;
|
|
10
|
+
auth_url?: string;
|
|
11
|
+
upload_url?: string;
|
|
12
|
+
user_id: string;
|
|
13
|
+
db_path: string;
|
|
14
|
+
cf_access_client_id?: string;
|
|
15
|
+
cf_access_client_secret?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface PowerSyncDialectConfig {
|
|
18
|
+
readDatabase: BetterSqlite3.Database | (() => Promise<BetterSqlite3.Database> | BetterSqlite3.Database);
|
|
19
|
+
writeDatabase: PowerSyncDatabase | (() => Promise<PowerSyncDatabase> | PowerSyncDatabase);
|
|
20
|
+
readQueryClassifier?: ReadQueryClassifier;
|
|
21
|
+
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export interface RemotePowerSyncDialectConfig {
|
|
24
|
+
readDatabase: BetterSqlite3.Database | (() => Promise<BetterSqlite3.Database> | BetterSqlite3.Database);
|
|
25
|
+
writeEndpoint: string;
|
|
26
|
+
writeHeaders?: Record<string, string>;
|
|
27
|
+
writeBody?: (sql: string, parameters: readonly unknown[]) => unknown;
|
|
28
|
+
mapResult?: <R>(payload: unknown) => QueryResult<R>;
|
|
29
|
+
readQueryClassifier?: ReadQueryClassifier;
|
|
30
|
+
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
export interface PowerSyncRuntimeStatus {
|
|
33
|
+
connected?: boolean;
|
|
34
|
+
connecting?: boolean;
|
|
35
|
+
hasSynced?: boolean;
|
|
36
|
+
lastSyncedAt?: string | Date | null;
|
|
37
|
+
downloading?: boolean;
|
|
38
|
+
uploading?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface CreatePowerSyncDatabaseOptions {
|
|
41
|
+
onStatusChanged?: (status: PowerSyncRuntimeStatus) => void;
|
|
42
|
+
fetchCredentials?: (config: PowerSyncConfig) => Promise<PowerSyncCredentials>;
|
|
43
|
+
uploadTransaction?: (transaction: PowerSyncCrudTransaction, context: PowerSyncUploadContext) => Promise<PowerSyncUploadResult | void>;
|
|
44
|
+
}
|
|
45
|
+
export interface TokenResponse {
|
|
46
|
+
token: string;
|
|
47
|
+
expires_at: number;
|
|
48
|
+
}
|
|
49
|
+
export interface PowerSyncCredentials {
|
|
50
|
+
endpoint: string;
|
|
51
|
+
token: string;
|
|
52
|
+
expiresAt: Date;
|
|
53
|
+
}
|
|
54
|
+
export interface PowerSyncCrudEntry {
|
|
55
|
+
op: "PUT" | "PATCH" | "DELETE" | string;
|
|
56
|
+
table: string;
|
|
57
|
+
id: string;
|
|
58
|
+
opData?: Record<string, unknown>;
|
|
59
|
+
}
|
|
60
|
+
export interface PowerSyncCrudTransaction {
|
|
61
|
+
crud: PowerSyncCrudEntry[];
|
|
62
|
+
complete: (checkpoint?: string) => Promise<void>;
|
|
63
|
+
transactionId?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface PowerSyncUploadContext {
|
|
66
|
+
config: PowerSyncConfig;
|
|
67
|
+
headers: Record<string, string>;
|
|
68
|
+
}
|
|
69
|
+
export interface PowerSyncUploadResult {
|
|
70
|
+
checkpoint?: string;
|
|
71
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
These examples are intentionally small. They show the package surface without shipping app schemas, migrations, sync rules, or backend code.
|
|
4
|
+
|
|
5
|
+
- [Local PowerSync database](local-powersync.md): connect a PowerSync Node database and read from the local SQLite replica through Kysely.
|
|
6
|
+
- [Remote writes](remote-writes.md): read locally while forwarding writes to an HTTP service.
|
|
7
|
+
- [Extension hooks](extension-hooks.md): replace auth, upload, and read/write routing behavior.
|
|
8
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Extension Hooks
|
|
2
|
+
|
|
3
|
+
The high-level helper is hookable for custom auth and upload flows. When both hooks are provided, `auth_url` and `upload_url` are not required:
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import {
|
|
7
|
+
createConnectedPowerSyncDatabase,
|
|
8
|
+
type PowerSyncConfig,
|
|
9
|
+
} from "@zm2231/kysely-powersync-dialect";
|
|
10
|
+
|
|
11
|
+
const appUploadEndpoint = "https://api.example.com/powersync/upload";
|
|
12
|
+
const config: PowerSyncConfig = {
|
|
13
|
+
powersync_url: "https://powersync.example.com",
|
|
14
|
+
user_id: "user-123",
|
|
15
|
+
db_path: "./data/powersync.db",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const writeDatabase = await createConnectedPowerSyncDatabase(config, schema, {
|
|
19
|
+
async fetchCredentials(config) {
|
|
20
|
+
return {
|
|
21
|
+
endpoint: config.powersync_url,
|
|
22
|
+
token: await getTokenFromYourAuthProvider(),
|
|
23
|
+
expiresAt: new Date(Date.now() + 60 * 60 * 1000),
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
async uploadTransaction(transaction, context) {
|
|
27
|
+
const response = await fetch(appUploadEndpoint, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"content-type": "application/json",
|
|
31
|
+
...context.headers,
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
transactionId: transaction.transactionId,
|
|
35
|
+
operations: transaction.crud,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Upload failed: ${response.status} ${await response.text()}`);
|
|
40
|
+
}
|
|
41
|
+
const payload = await response.json() as { checkpoint?: string };
|
|
42
|
+
return { checkpoint: payload.checkpoint };
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If you only replace upload, keep `auth_url` in `config`. If you only replace auth, keep `upload_url` in `config`.
|
|
48
|
+
|
|
49
|
+
For lower-level routing, import `SplitPowerSyncDriver`, `WriteExecutor`, and `defaultReadQueryClassifier` and compose your own Kysely `Dialect`. Pass `readQueryClassifier` to `PowerSyncDialect` or `RemotePowerSyncDialect` when your app needs different read/write routing.
|
|
50
|
+
|
|
51
|
+
The default classifier uses Kysely's select AST when available, then falls back to SQL text after stripping leading comments.
|
|
52
|
+
|
|
53
|
+
The default uploader sends a whole PowerSync CRUD transaction in one `POST`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"transactionId": 123,
|
|
58
|
+
"operations": [
|
|
59
|
+
{ "op": "PUT", "table": "todos", "id": "todo-1", "data": { "title": "Ship it" } }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Local PowerSync Database
|
|
2
|
+
|
|
3
|
+
Use this path when your process owns the PowerSync Node client.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Kysely } from "kysely";
|
|
7
|
+
import { Schema, Table, column } from "@powersync/common";
|
|
8
|
+
import {
|
|
9
|
+
PowerSyncDialect,
|
|
10
|
+
createConnectedPowerSyncDatabase,
|
|
11
|
+
openPowerSyncReadDatabase,
|
|
12
|
+
} from "@zm2231/kysely-powersync-dialect";
|
|
13
|
+
|
|
14
|
+
interface DB {
|
|
15
|
+
todos: {
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
done: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const schema = new Schema({
|
|
23
|
+
todos: new Table({
|
|
24
|
+
title: column.text,
|
|
25
|
+
done: column.integer,
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const config = {
|
|
30
|
+
powersync_url: "https://powersync.example.com",
|
|
31
|
+
auth_url: "https://api.example.com/api/auth/token",
|
|
32
|
+
upload_url: "https://api.example.com/api/data",
|
|
33
|
+
user_id: "user-123",
|
|
34
|
+
db_path: "./data/powersync.db",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const writeDatabase = await createConnectedPowerSyncDatabase(config, schema);
|
|
38
|
+
const readDatabase = await openPowerSyncReadDatabase(config.db_path);
|
|
39
|
+
|
|
40
|
+
const db = new Kysely<DB>({
|
|
41
|
+
dialect: new PowerSyncDialect({
|
|
42
|
+
readDatabase,
|
|
43
|
+
writeDatabase,
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const rows = await db.selectFrom("todos").selectAll().execute();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`createConnectedPowerSyncDatabase()` uses `auth_url` for credentials and `upload_url` for the default CRUD upload handler. Use [extension hooks](extension-hooks.md) when your app already owns those flows.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Remote Writes
|
|
2
|
+
|
|
3
|
+
Use `RemotePowerSyncDialect` when the process can read a local replica but writes must go through an HTTP service.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { Kysely } from "kysely";
|
|
7
|
+
import {
|
|
8
|
+
RemotePowerSyncDialect,
|
|
9
|
+
openPowerSyncReadDatabase,
|
|
10
|
+
} from "@zm2231/kysely-powersync-dialect";
|
|
11
|
+
|
|
12
|
+
const db = new Kysely({
|
|
13
|
+
dialect: new RemotePowerSyncDialect({
|
|
14
|
+
readDatabase: await openPowerSyncReadDatabase("./data/powersync.db"),
|
|
15
|
+
writeEndpoint: "https://api.example.com/kysely-write",
|
|
16
|
+
writeHeaders: { authorization: `Bearer ${token}` },
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Kysely transactions are read-only in this dialect. Writes inside a Kysely transaction throw instead of pretending the split read/write paths are atomic.
|
|
22
|
+
|
|
23
|
+
The default remote write body is:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"sql": "insert into todos (id, title) values (?, ?)",
|
|
28
|
+
"params": ["todo-1", "Ship it"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use `writeBody` and `mapResult` when your service needs a different protocol.
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zm2231/kysely-powersync-dialect",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Kysely dialect for PowerSync Node clients backed by a local SQLite read replica.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Zain",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"examples",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
+
"prepack": "npm run build",
|
|
24
|
+
"pretest": "npm run build",
|
|
25
|
+
"test": "node --test test/*.test.js"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.18.1"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"kysely": "^0.28.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@powersync/common": "^1.50.0",
|
|
35
|
+
"@powersync/node": "^0.18.2",
|
|
36
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
37
|
+
"better-sqlite3": "^12.8.0",
|
|
38
|
+
"cross-fetch": "^4.1.0",
|
|
39
|
+
"undici": "^7.11.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"kysely": "^0.28.8",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/zm2231/kysely-powersync-dialect.git"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/zm2231/kysely-powersync-dialect#readme",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/zm2231/kysely-powersync-dialect/issues"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"kysely",
|
|
56
|
+
"kysely-dialect",
|
|
57
|
+
"powersync",
|
|
58
|
+
"sqlite",
|
|
59
|
+
"better-sqlite3",
|
|
60
|
+
"sync",
|
|
61
|
+
"offline-first"
|
|
62
|
+
]
|
|
63
|
+
}
|