stitchdb 1.2.0 → 1.3.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/dist/index.d.mts +14 -21
- package/dist/index.d.ts +14 -21
- package/dist/index.js +119 -39
- package/dist/index.mjs +119 -39
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -35,49 +35,42 @@ declare class StitchDBError extends Error {
|
|
|
35
35
|
/**
|
|
36
36
|
* StitchDB client.
|
|
37
37
|
*
|
|
38
|
-
* Uses
|
|
39
|
-
*
|
|
40
|
-
* Batch API sends multiple queries in one request.
|
|
38
|
+
* Uses WebSocket for queries (1 connection, unlimited queries, $0.019/M cost).
|
|
39
|
+
* Falls back to HTTP if WebSocket unavailable.
|
|
41
40
|
*/
|
|
42
41
|
declare class StitchDB {
|
|
43
42
|
private url;
|
|
43
|
+
private wsUrl;
|
|
44
44
|
private apiKey;
|
|
45
|
-
private
|
|
45
|
+
private ws;
|
|
46
|
+
private pending;
|
|
47
|
+
private msgId;
|
|
48
|
+
private connecting;
|
|
49
|
+
private wsSupported;
|
|
50
|
+
private wsFailed;
|
|
46
51
|
constructor(config: StitchDBConfig);
|
|
47
|
-
|
|
52
|
+
private connect;
|
|
53
|
+
private wsSend;
|
|
54
|
+
private httpPost;
|
|
55
|
+
private send;
|
|
48
56
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<QueryResult<T>>;
|
|
49
|
-
/**
|
|
50
|
-
* Run multiple queries in a single HTTP request.
|
|
51
|
-
* All queries execute atomically. One Worker invocation for N queries.
|
|
52
|
-
*/
|
|
53
57
|
batch(queries: {
|
|
54
58
|
sql: string;
|
|
55
59
|
params?: unknown[];
|
|
56
60
|
}[]): Promise<BatchResult>;
|
|
57
|
-
/** Run a DDL statement (CREATE TABLE, ALTER TABLE, DROP TABLE, etc.) */
|
|
58
61
|
run(sql: string): Promise<ExecResult>;
|
|
59
|
-
/** Insert a row. */
|
|
60
62
|
insert(table: string, data: Record<string, unknown>): Promise<QueryResult>;
|
|
61
|
-
/**
|
|
62
|
-
* Insert multiple rows in a single request using batch API.
|
|
63
|
-
* One Worker invocation regardless of how many rows.
|
|
64
|
-
*/
|
|
65
63
|
insertMany(table: string, rows: Record<string, unknown>[]): Promise<BatchResult>;
|
|
66
|
-
/** Update rows matching a WHERE clause. */
|
|
67
64
|
update(table: string, data: Record<string, unknown>, where: string, whereParams?: unknown[]): Promise<QueryResult>;
|
|
68
|
-
/** Delete rows matching a WHERE clause. */
|
|
69
65
|
remove(table: string, where: string, whereParams?: unknown[]): Promise<QueryResult>;
|
|
70
|
-
/** Find one row by ID. */
|
|
71
66
|
find<T = Record<string, unknown>>(table: string, id: unknown, idColumn?: string): Promise<T | null>;
|
|
72
|
-
/** Select rows with optional WHERE, ORDER BY, LIMIT, OFFSET. */
|
|
73
67
|
select<T = Record<string, unknown>>(table: string, where?: string, params?: unknown[], opts?: {
|
|
74
68
|
orderBy?: string;
|
|
75
69
|
limit?: number;
|
|
76
70
|
offset?: number;
|
|
77
71
|
}): Promise<T[]>;
|
|
78
|
-
|
|
72
|
+
close(): void;
|
|
79
73
|
}
|
|
80
|
-
/** Create a StitchDB client. */
|
|
81
74
|
declare function createClient(config: StitchDBConfig): StitchDB;
|
|
82
75
|
|
|
83
76
|
export { type BatchResult, type ExecResult, type QueryResult, StitchDB, type StitchDBConfig, StitchDBError, createClient, StitchDB as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,49 +35,42 @@ declare class StitchDBError extends Error {
|
|
|
35
35
|
/**
|
|
36
36
|
* StitchDB client.
|
|
37
37
|
*
|
|
38
|
-
* Uses
|
|
39
|
-
*
|
|
40
|
-
* Batch API sends multiple queries in one request.
|
|
38
|
+
* Uses WebSocket for queries (1 connection, unlimited queries, $0.019/M cost).
|
|
39
|
+
* Falls back to HTTP if WebSocket unavailable.
|
|
41
40
|
*/
|
|
42
41
|
declare class StitchDB {
|
|
43
42
|
private url;
|
|
43
|
+
private wsUrl;
|
|
44
44
|
private apiKey;
|
|
45
|
-
private
|
|
45
|
+
private ws;
|
|
46
|
+
private pending;
|
|
47
|
+
private msgId;
|
|
48
|
+
private connecting;
|
|
49
|
+
private wsSupported;
|
|
50
|
+
private wsFailed;
|
|
46
51
|
constructor(config: StitchDBConfig);
|
|
47
|
-
|
|
52
|
+
private connect;
|
|
53
|
+
private wsSend;
|
|
54
|
+
private httpPost;
|
|
55
|
+
private send;
|
|
48
56
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<QueryResult<T>>;
|
|
49
|
-
/**
|
|
50
|
-
* Run multiple queries in a single HTTP request.
|
|
51
|
-
* All queries execute atomically. One Worker invocation for N queries.
|
|
52
|
-
*/
|
|
53
57
|
batch(queries: {
|
|
54
58
|
sql: string;
|
|
55
59
|
params?: unknown[];
|
|
56
60
|
}[]): Promise<BatchResult>;
|
|
57
|
-
/** Run a DDL statement (CREATE TABLE, ALTER TABLE, DROP TABLE, etc.) */
|
|
58
61
|
run(sql: string): Promise<ExecResult>;
|
|
59
|
-
/** Insert a row. */
|
|
60
62
|
insert(table: string, data: Record<string, unknown>): Promise<QueryResult>;
|
|
61
|
-
/**
|
|
62
|
-
* Insert multiple rows in a single request using batch API.
|
|
63
|
-
* One Worker invocation regardless of how many rows.
|
|
64
|
-
*/
|
|
65
63
|
insertMany(table: string, rows: Record<string, unknown>[]): Promise<BatchResult>;
|
|
66
|
-
/** Update rows matching a WHERE clause. */
|
|
67
64
|
update(table: string, data: Record<string, unknown>, where: string, whereParams?: unknown[]): Promise<QueryResult>;
|
|
68
|
-
/** Delete rows matching a WHERE clause. */
|
|
69
65
|
remove(table: string, where: string, whereParams?: unknown[]): Promise<QueryResult>;
|
|
70
|
-
/** Find one row by ID. */
|
|
71
66
|
find<T = Record<string, unknown>>(table: string, id: unknown, idColumn?: string): Promise<T | null>;
|
|
72
|
-
/** Select rows with optional WHERE, ORDER BY, LIMIT, OFFSET. */
|
|
73
67
|
select<T = Record<string, unknown>>(table: string, where?: string, params?: unknown[], opts?: {
|
|
74
68
|
orderBy?: string;
|
|
75
69
|
limit?: number;
|
|
76
70
|
offset?: number;
|
|
77
71
|
}): Promise<T[]>;
|
|
78
|
-
|
|
72
|
+
close(): void;
|
|
79
73
|
}
|
|
80
|
-
/** Create a StitchDB client. */
|
|
81
74
|
declare function createClient(config: StitchDBConfig): StitchDB;
|
|
82
75
|
|
|
83
76
|
export { type BatchResult, type ExecResult, type QueryResult, StitchDB, type StitchDBConfig, StitchDBError, createClient, StitchDB as default };
|
package/dist/index.js
CHANGED
|
@@ -35,59 +35,147 @@ var StitchDBError = class extends Error {
|
|
|
35
35
|
};
|
|
36
36
|
var StitchDB = class {
|
|
37
37
|
constructor(config) {
|
|
38
|
+
this.ws = null;
|
|
39
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
40
|
+
this.msgId = 0;
|
|
41
|
+
this.connecting = null;
|
|
42
|
+
this.wsFailed = false;
|
|
38
43
|
this.url = (config.url || "https://db.stitchdb.com").replace(/\/$/, "");
|
|
44
|
+
this.wsUrl = this.url.replace("https://", "wss://").replace("http://", "ws://");
|
|
39
45
|
this.apiKey = config.apiKey;
|
|
40
|
-
this.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
this.wsSupported = typeof WebSocket !== "undefined";
|
|
47
|
+
}
|
|
48
|
+
// ---- WebSocket ----
|
|
49
|
+
async connect() {
|
|
50
|
+
if (this.ws?.readyState === 1) return;
|
|
51
|
+
if (this.connecting) return this.connecting;
|
|
52
|
+
this.connecting = new Promise((resolve, reject) => {
|
|
53
|
+
try {
|
|
54
|
+
const ws = new WebSocket(`${this.wsUrl}/ws/query?key=${this.apiKey}`);
|
|
55
|
+
ws.onopen = () => {
|
|
56
|
+
this.ws = ws;
|
|
57
|
+
this.connecting = null;
|
|
58
|
+
resolve();
|
|
59
|
+
};
|
|
60
|
+
ws.onmessage = (e) => {
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(typeof e.data === "string" ? e.data : "");
|
|
63
|
+
const p = this.pending.get(data.id);
|
|
64
|
+
if (p) {
|
|
65
|
+
this.pending.delete(data.id);
|
|
66
|
+
data.error ? p.reject(new StitchDBError(data.error)) : p.resolve(data);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
ws.onclose = () => {
|
|
72
|
+
this.ws = null;
|
|
73
|
+
this.connecting = null;
|
|
74
|
+
for (const [, p] of this.pending) p.reject(new StitchDBError("Connection closed"));
|
|
75
|
+
this.pending.clear();
|
|
76
|
+
};
|
|
77
|
+
ws.onerror = () => {
|
|
78
|
+
this.ws = null;
|
|
79
|
+
this.connecting = null;
|
|
80
|
+
this.wsFailed = true;
|
|
81
|
+
resolve();
|
|
82
|
+
};
|
|
83
|
+
} catch {
|
|
84
|
+
this.connecting = null;
|
|
85
|
+
this.wsFailed = true;
|
|
86
|
+
resolve();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return this.connecting;
|
|
90
|
+
}
|
|
91
|
+
async wsSend(msg) {
|
|
92
|
+
if (this.wsFailed) return this.httpPost(msg);
|
|
93
|
+
await this.connect();
|
|
94
|
+
if (!this.ws || this.ws.readyState !== 1) {
|
|
95
|
+
this.wsFailed = true;
|
|
96
|
+
return this.httpPost(msg);
|
|
97
|
+
}
|
|
98
|
+
const id = String(++this.msgId);
|
|
99
|
+
msg.id = id;
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const timeout = setTimeout(() => {
|
|
102
|
+
this.pending.delete(id);
|
|
103
|
+
reject(new StitchDBError("Query timeout"));
|
|
104
|
+
}, 3e4);
|
|
105
|
+
this.pending.set(id, {
|
|
106
|
+
resolve: (v) => {
|
|
107
|
+
clearTimeout(timeout);
|
|
108
|
+
resolve(v);
|
|
109
|
+
},
|
|
110
|
+
reject: (e) => {
|
|
111
|
+
clearTimeout(timeout);
|
|
112
|
+
reject(e);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
try {
|
|
116
|
+
this.ws.send(JSON.stringify(msg));
|
|
117
|
+
} catch {
|
|
118
|
+
clearTimeout(timeout);
|
|
119
|
+
this.pending.delete(id);
|
|
120
|
+
this.wsFailed = true;
|
|
121
|
+
this.httpPost(msg).then(resolve, reject);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// ---- HTTP fallback ----
|
|
126
|
+
async httpPost(msg) {
|
|
127
|
+
let path = "/v1/query", body = { sql: msg.sql, params: msg.params };
|
|
128
|
+
if (msg.action === "batch") {
|
|
129
|
+
path = "/v1/batch";
|
|
130
|
+
body = { queries: msg.queries };
|
|
131
|
+
} else if (msg.action === "exec") {
|
|
132
|
+
path = "/v1/exec";
|
|
133
|
+
body = { sql: msg.sql };
|
|
134
|
+
}
|
|
135
|
+
const res = await fetch(`${this.url}${path}`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: { "Authorization": `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
|
|
138
|
+
body: JSON.stringify(body)
|
|
139
|
+
});
|
|
140
|
+
const data = await res.json();
|
|
141
|
+
if (!res.ok || data.error) throw new StitchDBError(data.error || `HTTP ${res.status}`, res.status);
|
|
142
|
+
return data;
|
|
143
|
+
}
|
|
144
|
+
// ---- Public API ----
|
|
145
|
+
async send(action, data) {
|
|
146
|
+
const msg = { action, ...data };
|
|
147
|
+
if (this.wsSupported && !this.wsFailed) return this.wsSend(msg);
|
|
148
|
+
return this.httpPost(msg);
|
|
44
149
|
}
|
|
45
|
-
/** Run a SQL query with parameterized bindings. */
|
|
46
150
|
async query(sql, params) {
|
|
47
|
-
return this.
|
|
151
|
+
return this.send("query", { sql, params });
|
|
48
152
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Run multiple queries in a single HTTP request.
|
|
51
|
-
* All queries execute atomically. One Worker invocation for N queries.
|
|
52
|
-
*/
|
|
53
153
|
async batch(queries) {
|
|
54
|
-
return this.
|
|
154
|
+
return this.send("batch", { queries });
|
|
55
155
|
}
|
|
56
|
-
/** Run a DDL statement (CREATE TABLE, ALTER TABLE, DROP TABLE, etc.) */
|
|
57
156
|
async run(sql) {
|
|
58
|
-
return this.
|
|
157
|
+
return this.send("exec", { sql });
|
|
59
158
|
}
|
|
60
|
-
/** Insert a row. */
|
|
61
159
|
async insert(table, data) {
|
|
62
160
|
const cols = Object.keys(data);
|
|
63
|
-
|
|
64
|
-
return this.query(sql, Object.values(data));
|
|
161
|
+
return this.query(`INSERT INTO "${table}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`, Object.values(data));
|
|
65
162
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Insert multiple rows in a single request using batch API.
|
|
68
|
-
* One Worker invocation regardless of how many rows.
|
|
69
|
-
*/
|
|
70
163
|
async insertMany(table, rows) {
|
|
71
|
-
if (rows.length
|
|
164
|
+
if (!rows.length) return { results: [], meta: { rows_read: 0, rows_written: 0, duration_ms: 0, queries_count: 0 } };
|
|
72
165
|
const cols = Object.keys(rows[0]);
|
|
73
166
|
const sql = `INSERT INTO "${table}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`;
|
|
74
167
|
return this.batch(rows.map((row) => ({ sql, params: cols.map((c) => row[c]) })));
|
|
75
168
|
}
|
|
76
|
-
/** Update rows matching a WHERE clause. */
|
|
77
169
|
async update(table, data, where, whereParams) {
|
|
78
|
-
|
|
79
|
-
return this.query(`UPDATE "${table}" SET ${set} WHERE ${where}`, [...Object.values(data), ...whereParams || []]);
|
|
170
|
+
return this.query(`UPDATE "${table}" SET ${Object.keys(data).map((c) => `"${c}" = ?`).join(", ")} WHERE ${where}`, [...Object.values(data), ...whereParams || []]);
|
|
80
171
|
}
|
|
81
|
-
/** Delete rows matching a WHERE clause. */
|
|
82
172
|
async remove(table, where, whereParams) {
|
|
83
173
|
return this.query(`DELETE FROM "${table}" WHERE ${where}`, whereParams);
|
|
84
174
|
}
|
|
85
|
-
/** Find one row by ID. */
|
|
86
175
|
async find(table, id, idColumn = "id") {
|
|
87
176
|
const { results } = await this.query(`SELECT * FROM "${table}" WHERE "${idColumn}" = ? LIMIT 1`, [id]);
|
|
88
177
|
return results[0] || null;
|
|
89
178
|
}
|
|
90
|
-
/** Select rows with optional WHERE, ORDER BY, LIMIT, OFFSET. */
|
|
91
179
|
async select(table, where, params, opts) {
|
|
92
180
|
let sql = `SELECT * FROM "${table}"`;
|
|
93
181
|
if (where) sql += ` WHERE ${where}`;
|
|
@@ -97,19 +185,11 @@ var StitchDB = class {
|
|
|
97
185
|
const { results } = await this.query(sql, params);
|
|
98
186
|
return results;
|
|
99
187
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
body: JSON.stringify(body),
|
|
105
|
-
// @ts-ignore — keepalive hint for HTTP/2 connection reuse
|
|
106
|
-
keepalive: true
|
|
107
|
-
});
|
|
108
|
-
const data = await res.json();
|
|
109
|
-
if (!res.ok || data.error) {
|
|
110
|
-
throw new StitchDBError(data.error || `Request failed: ${res.status}`, res.status);
|
|
188
|
+
close() {
|
|
189
|
+
if (this.ws) {
|
|
190
|
+
this.ws.close();
|
|
191
|
+
this.ws = null;
|
|
111
192
|
}
|
|
112
|
-
return data;
|
|
113
193
|
}
|
|
114
194
|
};
|
|
115
195
|
function createClient(config) {
|
package/dist/index.mjs
CHANGED
|
@@ -8,59 +8,147 @@ var StitchDBError = class extends Error {
|
|
|
8
8
|
};
|
|
9
9
|
var StitchDB = class {
|
|
10
10
|
constructor(config) {
|
|
11
|
+
this.ws = null;
|
|
12
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
13
|
+
this.msgId = 0;
|
|
14
|
+
this.connecting = null;
|
|
15
|
+
this.wsFailed = false;
|
|
11
16
|
this.url = (config.url || "https://db.stitchdb.com").replace(/\/$/, "");
|
|
17
|
+
this.wsUrl = this.url.replace("https://", "wss://").replace("http://", "ws://");
|
|
12
18
|
this.apiKey = config.apiKey;
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
this.wsSupported = typeof WebSocket !== "undefined";
|
|
20
|
+
}
|
|
21
|
+
// ---- WebSocket ----
|
|
22
|
+
async connect() {
|
|
23
|
+
if (this.ws?.readyState === 1) return;
|
|
24
|
+
if (this.connecting) return this.connecting;
|
|
25
|
+
this.connecting = new Promise((resolve, reject) => {
|
|
26
|
+
try {
|
|
27
|
+
const ws = new WebSocket(`${this.wsUrl}/ws/query?key=${this.apiKey}`);
|
|
28
|
+
ws.onopen = () => {
|
|
29
|
+
this.ws = ws;
|
|
30
|
+
this.connecting = null;
|
|
31
|
+
resolve();
|
|
32
|
+
};
|
|
33
|
+
ws.onmessage = (e) => {
|
|
34
|
+
try {
|
|
35
|
+
const data = JSON.parse(typeof e.data === "string" ? e.data : "");
|
|
36
|
+
const p = this.pending.get(data.id);
|
|
37
|
+
if (p) {
|
|
38
|
+
this.pending.delete(data.id);
|
|
39
|
+
data.error ? p.reject(new StitchDBError(data.error)) : p.resolve(data);
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
ws.onclose = () => {
|
|
45
|
+
this.ws = null;
|
|
46
|
+
this.connecting = null;
|
|
47
|
+
for (const [, p] of this.pending) p.reject(new StitchDBError("Connection closed"));
|
|
48
|
+
this.pending.clear();
|
|
49
|
+
};
|
|
50
|
+
ws.onerror = () => {
|
|
51
|
+
this.ws = null;
|
|
52
|
+
this.connecting = null;
|
|
53
|
+
this.wsFailed = true;
|
|
54
|
+
resolve();
|
|
55
|
+
};
|
|
56
|
+
} catch {
|
|
57
|
+
this.connecting = null;
|
|
58
|
+
this.wsFailed = true;
|
|
59
|
+
resolve();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return this.connecting;
|
|
63
|
+
}
|
|
64
|
+
async wsSend(msg) {
|
|
65
|
+
if (this.wsFailed) return this.httpPost(msg);
|
|
66
|
+
await this.connect();
|
|
67
|
+
if (!this.ws || this.ws.readyState !== 1) {
|
|
68
|
+
this.wsFailed = true;
|
|
69
|
+
return this.httpPost(msg);
|
|
70
|
+
}
|
|
71
|
+
const id = String(++this.msgId);
|
|
72
|
+
msg.id = id;
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const timeout = setTimeout(() => {
|
|
75
|
+
this.pending.delete(id);
|
|
76
|
+
reject(new StitchDBError("Query timeout"));
|
|
77
|
+
}, 3e4);
|
|
78
|
+
this.pending.set(id, {
|
|
79
|
+
resolve: (v) => {
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
resolve(v);
|
|
82
|
+
},
|
|
83
|
+
reject: (e) => {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
reject(e);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
this.ws.send(JSON.stringify(msg));
|
|
90
|
+
} catch {
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
this.pending.delete(id);
|
|
93
|
+
this.wsFailed = true;
|
|
94
|
+
this.httpPost(msg).then(resolve, reject);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// ---- HTTP fallback ----
|
|
99
|
+
async httpPost(msg) {
|
|
100
|
+
let path = "/v1/query", body = { sql: msg.sql, params: msg.params };
|
|
101
|
+
if (msg.action === "batch") {
|
|
102
|
+
path = "/v1/batch";
|
|
103
|
+
body = { queries: msg.queries };
|
|
104
|
+
} else if (msg.action === "exec") {
|
|
105
|
+
path = "/v1/exec";
|
|
106
|
+
body = { sql: msg.sql };
|
|
107
|
+
}
|
|
108
|
+
const res = await fetch(`${this.url}${path}`, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "Authorization": `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
|
|
111
|
+
body: JSON.stringify(body)
|
|
112
|
+
});
|
|
113
|
+
const data = await res.json();
|
|
114
|
+
if (!res.ok || data.error) throw new StitchDBError(data.error || `HTTP ${res.status}`, res.status);
|
|
115
|
+
return data;
|
|
116
|
+
}
|
|
117
|
+
// ---- Public API ----
|
|
118
|
+
async send(action, data) {
|
|
119
|
+
const msg = { action, ...data };
|
|
120
|
+
if (this.wsSupported && !this.wsFailed) return this.wsSend(msg);
|
|
121
|
+
return this.httpPost(msg);
|
|
17
122
|
}
|
|
18
|
-
/** Run a SQL query with parameterized bindings. */
|
|
19
123
|
async query(sql, params) {
|
|
20
|
-
return this.
|
|
124
|
+
return this.send("query", { sql, params });
|
|
21
125
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Run multiple queries in a single HTTP request.
|
|
24
|
-
* All queries execute atomically. One Worker invocation for N queries.
|
|
25
|
-
*/
|
|
26
126
|
async batch(queries) {
|
|
27
|
-
return this.
|
|
127
|
+
return this.send("batch", { queries });
|
|
28
128
|
}
|
|
29
|
-
/** Run a DDL statement (CREATE TABLE, ALTER TABLE, DROP TABLE, etc.) */
|
|
30
129
|
async run(sql) {
|
|
31
|
-
return this.
|
|
130
|
+
return this.send("exec", { sql });
|
|
32
131
|
}
|
|
33
|
-
/** Insert a row. */
|
|
34
132
|
async insert(table, data) {
|
|
35
133
|
const cols = Object.keys(data);
|
|
36
|
-
|
|
37
|
-
return this.query(sql, Object.values(data));
|
|
134
|
+
return this.query(`INSERT INTO "${table}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`, Object.values(data));
|
|
38
135
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Insert multiple rows in a single request using batch API.
|
|
41
|
-
* One Worker invocation regardless of how many rows.
|
|
42
|
-
*/
|
|
43
136
|
async insertMany(table, rows) {
|
|
44
|
-
if (rows.length
|
|
137
|
+
if (!rows.length) return { results: [], meta: { rows_read: 0, rows_written: 0, duration_ms: 0, queries_count: 0 } };
|
|
45
138
|
const cols = Object.keys(rows[0]);
|
|
46
139
|
const sql = `INSERT INTO "${table}" (${cols.map((c) => `"${c}"`).join(", ")}) VALUES (${cols.map(() => "?").join(", ")})`;
|
|
47
140
|
return this.batch(rows.map((row) => ({ sql, params: cols.map((c) => row[c]) })));
|
|
48
141
|
}
|
|
49
|
-
/** Update rows matching a WHERE clause. */
|
|
50
142
|
async update(table, data, where, whereParams) {
|
|
51
|
-
|
|
52
|
-
return this.query(`UPDATE "${table}" SET ${set} WHERE ${where}`, [...Object.values(data), ...whereParams || []]);
|
|
143
|
+
return this.query(`UPDATE "${table}" SET ${Object.keys(data).map((c) => `"${c}" = ?`).join(", ")} WHERE ${where}`, [...Object.values(data), ...whereParams || []]);
|
|
53
144
|
}
|
|
54
|
-
/** Delete rows matching a WHERE clause. */
|
|
55
145
|
async remove(table, where, whereParams) {
|
|
56
146
|
return this.query(`DELETE FROM "${table}" WHERE ${where}`, whereParams);
|
|
57
147
|
}
|
|
58
|
-
/** Find one row by ID. */
|
|
59
148
|
async find(table, id, idColumn = "id") {
|
|
60
149
|
const { results } = await this.query(`SELECT * FROM "${table}" WHERE "${idColumn}" = ? LIMIT 1`, [id]);
|
|
61
150
|
return results[0] || null;
|
|
62
151
|
}
|
|
63
|
-
/** Select rows with optional WHERE, ORDER BY, LIMIT, OFFSET. */
|
|
64
152
|
async select(table, where, params, opts) {
|
|
65
153
|
let sql = `SELECT * FROM "${table}"`;
|
|
66
154
|
if (where) sql += ` WHERE ${where}`;
|
|
@@ -70,19 +158,11 @@ var StitchDB = class {
|
|
|
70
158
|
const { results } = await this.query(sql, params);
|
|
71
159
|
return results;
|
|
72
160
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
body: JSON.stringify(body),
|
|
78
|
-
// @ts-ignore — keepalive hint for HTTP/2 connection reuse
|
|
79
|
-
keepalive: true
|
|
80
|
-
});
|
|
81
|
-
const data = await res.json();
|
|
82
|
-
if (!res.ok || data.error) {
|
|
83
|
-
throw new StitchDBError(data.error || `Request failed: ${res.status}`, res.status);
|
|
161
|
+
close() {
|
|
162
|
+
if (this.ws) {
|
|
163
|
+
this.ws.close();
|
|
164
|
+
this.ws = null;
|
|
84
165
|
}
|
|
85
|
-
return data;
|
|
86
166
|
}
|
|
87
167
|
};
|
|
88
168
|
function createClient(config) {
|