@tinycloud/sdk-services 2.4.0-beta.8 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +504 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -26
- package/dist/index.d.ts +75 -26
- package/dist/index.js +496 -20
- package/dist/index.js.map +1 -1
- package/dist/sql/index.cjs +210 -10
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +67 -2
- package/dist/sql/index.d.ts +67 -2
- package/dist/sql/index.js +209 -10
- package/dist/sql/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -67,6 +67,202 @@ function serviceError(code, message, service, options) {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// src/debug.ts
|
|
71
|
+
var DEBUG_FLAG = "TinyCloud_debug";
|
|
72
|
+
var MAX_EVENTS = 1e3;
|
|
73
|
+
function getGlobal() {
|
|
74
|
+
return globalThis;
|
|
75
|
+
}
|
|
76
|
+
function nowMs() {
|
|
77
|
+
const performanceNow = globalThis.performance?.now?.bind(globalThis.performance);
|
|
78
|
+
return typeof performanceNow === "function" ? performanceNow() : Date.now();
|
|
79
|
+
}
|
|
80
|
+
function isTrue(value) {
|
|
81
|
+
return value === true || value === "true" || value === "1";
|
|
82
|
+
}
|
|
83
|
+
function getProcessDebugFlag() {
|
|
84
|
+
const processLike = globalThis.process;
|
|
85
|
+
return processLike?.env?.[DEBUG_FLAG];
|
|
86
|
+
}
|
|
87
|
+
function isBrowserWindow() {
|
|
88
|
+
const global = getGlobal();
|
|
89
|
+
return global.window === globalThis;
|
|
90
|
+
}
|
|
91
|
+
function getLocalStorage() {
|
|
92
|
+
if (!isBrowserWindow()) {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
return globalThis.localStorage;
|
|
97
|
+
} catch {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function getStoredDebugFlag() {
|
|
102
|
+
const storage = getLocalStorage();
|
|
103
|
+
if (!storage) {
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
return storage.getItem(DEBUG_FLAG);
|
|
108
|
+
} catch {
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function setStoredDebugFlag(enabled) {
|
|
113
|
+
const storage = getLocalStorage();
|
|
114
|
+
if (!storage) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
if (enabled) {
|
|
119
|
+
storage.setItem(DEBUG_FLAG, "true");
|
|
120
|
+
} else {
|
|
121
|
+
storage.removeItem(DEBUG_FLAG);
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function shouldStartEnabled() {
|
|
127
|
+
const global = getGlobal();
|
|
128
|
+
return isTrue(global[DEBUG_FLAG]) || isTrue(getStoredDebugFlag()) || isTrue(getProcessDebugFlag());
|
|
129
|
+
}
|
|
130
|
+
var TinyCloudDebugLogger = class {
|
|
131
|
+
constructor() {
|
|
132
|
+
this.enabled = shouldStartEnabled();
|
|
133
|
+
this.sequence = 0;
|
|
134
|
+
this.events = [];
|
|
135
|
+
this.maxEvents = MAX_EVENTS;
|
|
136
|
+
}
|
|
137
|
+
isEnabled() {
|
|
138
|
+
return this.enabled || isTrue(getGlobal()[DEBUG_FLAG]) || isTrue(getProcessDebugFlag());
|
|
139
|
+
}
|
|
140
|
+
enable(options = {}) {
|
|
141
|
+
this.enabled = true;
|
|
142
|
+
getGlobal()[DEBUG_FLAG] = true;
|
|
143
|
+
if (options.persist !== false) {
|
|
144
|
+
setStoredDebugFlag(true);
|
|
145
|
+
}
|
|
146
|
+
this.log("debug.enabled", { persisted: options.persist !== false });
|
|
147
|
+
}
|
|
148
|
+
disable(options = {}) {
|
|
149
|
+
this.log("debug.disabled", { persisted: options.persist !== false });
|
|
150
|
+
this.enabled = false;
|
|
151
|
+
getGlobal()[DEBUG_FLAG] = false;
|
|
152
|
+
if (options.persist !== false) {
|
|
153
|
+
setStoredDebugFlag(false);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
clear() {
|
|
157
|
+
this.events = [];
|
|
158
|
+
}
|
|
159
|
+
getLogs() {
|
|
160
|
+
return [...this.events];
|
|
161
|
+
}
|
|
162
|
+
log(event, data, message) {
|
|
163
|
+
if (!this.isEnabled()) {
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
return this.record({
|
|
167
|
+
event,
|
|
168
|
+
data,
|
|
169
|
+
message,
|
|
170
|
+
level: "debug",
|
|
171
|
+
timestamp: Date.now(),
|
|
172
|
+
timestampIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
startTimer(event, data) {
|
|
176
|
+
if (!this.isEnabled()) {
|
|
177
|
+
return { stop: () => void 0 };
|
|
178
|
+
}
|
|
179
|
+
const startedAt = nowMs();
|
|
180
|
+
this.log(`${event}.start`, data);
|
|
181
|
+
return {
|
|
182
|
+
stop: (finishData) => {
|
|
183
|
+
const endedAt = nowMs();
|
|
184
|
+
return this.record({
|
|
185
|
+
event: `${event}.end`,
|
|
186
|
+
data: finishData,
|
|
187
|
+
durationMs: endedAt - startedAt,
|
|
188
|
+
startedAt,
|
|
189
|
+
endedAt,
|
|
190
|
+
level: "debug",
|
|
191
|
+
timestamp: Date.now(),
|
|
192
|
+
timestampIso: (/* @__PURE__ */ new Date()).toISOString()
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async timeAsync(event, operation, data) {
|
|
198
|
+
if (!this.isEnabled()) {
|
|
199
|
+
return operation();
|
|
200
|
+
}
|
|
201
|
+
const timer = this.startTimer(event, data);
|
|
202
|
+
try {
|
|
203
|
+
const result = await operation();
|
|
204
|
+
timer.stop({ ok: true });
|
|
205
|
+
return result;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
timer.stop({ ok: false, error });
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
time(event, operation, data) {
|
|
212
|
+
if (!this.isEnabled()) {
|
|
213
|
+
return operation();
|
|
214
|
+
}
|
|
215
|
+
const timer = this.startTimer(event, data);
|
|
216
|
+
try {
|
|
217
|
+
const result = operation();
|
|
218
|
+
timer.stop({ ok: true });
|
|
219
|
+
return result;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
timer.stop({ ok: false, error });
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
record(event) {
|
|
226
|
+
const debugEvent = {
|
|
227
|
+
...event,
|
|
228
|
+
sequence: ++this.sequence
|
|
229
|
+
};
|
|
230
|
+
this.events.push(debugEvent);
|
|
231
|
+
if (this.events.length > this.maxEvents) {
|
|
232
|
+
this.events.splice(0, this.events.length - this.maxEvents);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
globalThis.console?.debug?.("[TinyCloud]", debugEvent.event, debugEvent);
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
return debugEvent;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var tinyCloudDebugLogger = new TinyCloudDebugLogger();
|
|
242
|
+
function enableTinyCloudDebug(options) {
|
|
243
|
+
tinyCloudDebugLogger.enable(options);
|
|
244
|
+
return tinyCloudDebugLogger;
|
|
245
|
+
}
|
|
246
|
+
function disableTinyCloudDebug(options) {
|
|
247
|
+
tinyCloudDebugLogger.disable(options);
|
|
248
|
+
return tinyCloudDebugLogger;
|
|
249
|
+
}
|
|
250
|
+
function getTinyCloudDebugLogs() {
|
|
251
|
+
return tinyCloudDebugLogger.getLogs();
|
|
252
|
+
}
|
|
253
|
+
function clearTinyCloudDebugLogs() {
|
|
254
|
+
tinyCloudDebugLogger.clear();
|
|
255
|
+
}
|
|
256
|
+
function installTinyCloudDebugGlobals() {
|
|
257
|
+
const global = getGlobal();
|
|
258
|
+
global.TinyCloudDebug = tinyCloudDebugLogger;
|
|
259
|
+
global.enableTinyCloudDebug = enableTinyCloudDebug;
|
|
260
|
+
global.disableTinyCloudDebug = disableTinyCloudDebug;
|
|
261
|
+
global.getTinyCloudDebugLogs = getTinyCloudDebugLogs;
|
|
262
|
+
global.clearTinyCloudDebugLogs = clearTinyCloudDebugLogs;
|
|
263
|
+
}
|
|
264
|
+
installTinyCloudDebugGlobals();
|
|
265
|
+
|
|
70
266
|
// src/types.schema.ts
|
|
71
267
|
import { z } from "zod";
|
|
72
268
|
var ServiceErrorSchema = z.object({
|
|
@@ -308,9 +504,9 @@ var ServiceContext = class {
|
|
|
308
504
|
this._services = /* @__PURE__ */ new Map();
|
|
309
505
|
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
310
506
|
this._abortController = new AbortController();
|
|
311
|
-
this._invoke = config.invoke;
|
|
312
|
-
this._invokeAny = config.invokeAny;
|
|
313
|
-
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
507
|
+
this._invoke = this.wrapInvoke(config.invoke);
|
|
508
|
+
this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
|
|
509
|
+
this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
|
|
314
510
|
this._hosts = config.hosts;
|
|
315
511
|
this._session = config.session ?? null;
|
|
316
512
|
this._retryPolicy = {
|
|
@@ -413,6 +609,7 @@ var ServiceContext = class {
|
|
|
413
609
|
* @param data - Event data
|
|
414
610
|
*/
|
|
415
611
|
emit(event, data) {
|
|
612
|
+
tinyCloudDebugLogger.log(event, data);
|
|
416
613
|
if (this._telemetryEnabled && this._telemetryHandler) {
|
|
417
614
|
try {
|
|
418
615
|
this._telemetryHandler(event, data);
|
|
@@ -502,6 +699,77 @@ var ServiceContext = class {
|
|
|
502
699
|
get retryPolicy() {
|
|
503
700
|
return this._retryPolicy;
|
|
504
701
|
}
|
|
702
|
+
wrapInvoke(invoke) {
|
|
703
|
+
return (session, service, path, action, facts) => {
|
|
704
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
705
|
+
return invoke(session, service, path, action, facts);
|
|
706
|
+
}
|
|
707
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
|
|
708
|
+
service,
|
|
709
|
+
path,
|
|
710
|
+
action,
|
|
711
|
+
facts
|
|
712
|
+
});
|
|
713
|
+
try {
|
|
714
|
+
const result = invoke(session, service, path, action, facts);
|
|
715
|
+
timer.stop({ ok: true, service, path, action });
|
|
716
|
+
return result;
|
|
717
|
+
} catch (error) {
|
|
718
|
+
timer.stop({ ok: false, service, path, action, error });
|
|
719
|
+
throw error;
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
wrapInvokeAny(invokeAny) {
|
|
724
|
+
return (session, entries, facts) => {
|
|
725
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
726
|
+
return invokeAny(session, entries, facts);
|
|
727
|
+
}
|
|
728
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
|
|
729
|
+
entries,
|
|
730
|
+
facts
|
|
731
|
+
});
|
|
732
|
+
try {
|
|
733
|
+
const result = invokeAny(session, entries, facts);
|
|
734
|
+
timer.stop({ ok: true, entries });
|
|
735
|
+
return result;
|
|
736
|
+
} catch (error) {
|
|
737
|
+
timer.stop({ ok: false, entries, error });
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
wrapFetch(fetchFn) {
|
|
743
|
+
return async (url, init) => {
|
|
744
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
745
|
+
return fetchFn(url, init);
|
|
746
|
+
}
|
|
747
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
|
|
748
|
+
url,
|
|
749
|
+
method: init?.method ?? "GET",
|
|
750
|
+
init
|
|
751
|
+
});
|
|
752
|
+
try {
|
|
753
|
+
const response = await fetchFn(url, init);
|
|
754
|
+
timer.stop({
|
|
755
|
+
ok: response.ok,
|
|
756
|
+
url,
|
|
757
|
+
method: init?.method ?? "GET",
|
|
758
|
+
status: response.status,
|
|
759
|
+
statusText: response.statusText
|
|
760
|
+
});
|
|
761
|
+
return response;
|
|
762
|
+
} catch (error) {
|
|
763
|
+
timer.stop({
|
|
764
|
+
ok: false,
|
|
765
|
+
url,
|
|
766
|
+
method: init?.method ?? "GET",
|
|
767
|
+
error
|
|
768
|
+
});
|
|
769
|
+
throw error;
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
}
|
|
505
773
|
};
|
|
506
774
|
|
|
507
775
|
// src/errors.ts
|
|
@@ -1687,11 +1955,59 @@ var KVService = class extends BaseService {
|
|
|
1687
1955
|
*/
|
|
1688
1956
|
KVService.serviceName = "kv";
|
|
1689
1957
|
|
|
1958
|
+
// src/responseErrors.ts
|
|
1959
|
+
function parseServiceErrorBody(errorText) {
|
|
1960
|
+
try {
|
|
1961
|
+
return JSON.parse(errorText);
|
|
1962
|
+
} catch {
|
|
1963
|
+
return {};
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
1967
|
+
if (parsed.message) {
|
|
1968
|
+
return compactMessage(parsed.message);
|
|
1969
|
+
}
|
|
1970
|
+
if (looksLikeHtml(errorText)) {
|
|
1971
|
+
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
1972
|
+
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
1973
|
+
}
|
|
1974
|
+
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
1975
|
+
}
|
|
1976
|
+
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
1977
|
+
}
|
|
1978
|
+
function responseErrorMeta(status, statusText, errorText) {
|
|
1979
|
+
const meta = { status, statusText };
|
|
1980
|
+
const snippet = compactMessage(errorText);
|
|
1981
|
+
if (snippet) {
|
|
1982
|
+
meta.responseSnippet = snippet.slice(0, 300);
|
|
1983
|
+
}
|
|
1984
|
+
return meta;
|
|
1985
|
+
}
|
|
1986
|
+
function looksLikeHtml(text) {
|
|
1987
|
+
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
1988
|
+
}
|
|
1989
|
+
function compactMessage(message) {
|
|
1990
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
1991
|
+
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// src/sql/SQLMigrations.ts
|
|
1995
|
+
var SQLMigrations = class {
|
|
1996
|
+
constructor(service, dbName) {
|
|
1997
|
+
this.service = service;
|
|
1998
|
+
this.dbName = dbName;
|
|
1999
|
+
}
|
|
2000
|
+
apply(options) {
|
|
2001
|
+
return this.service.applyMigrationsOnDb(this.dbName, options);
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
|
|
1690
2005
|
// src/sql/DatabaseHandle.ts
|
|
1691
2006
|
var DatabaseHandle = class {
|
|
1692
2007
|
constructor(service, name) {
|
|
1693
2008
|
this.service = service;
|
|
1694
2009
|
this.name = name;
|
|
2010
|
+
this.migrations = new SQLMigrations(service, name);
|
|
1695
2011
|
}
|
|
1696
2012
|
async query(sql, params, options) {
|
|
1697
2013
|
return this.service.queryOnDb(this.name, sql, params, options);
|
|
@@ -1714,7 +2030,7 @@ var DatabaseHandle = class {
|
|
|
1714
2030
|
var SQLAction = {
|
|
1715
2031
|
READ: "tinycloud.sql/read",
|
|
1716
2032
|
WRITE: "tinycloud.sql/write",
|
|
1717
|
-
|
|
2033
|
+
SCHEMA: "tinycloud.sql/schema",
|
|
1718
2034
|
ADMIN: "tinycloud.sql/admin",
|
|
1719
2035
|
SELECT: "tinycloud.sql/select",
|
|
1720
2036
|
INSERT: "tinycloud.sql/insert",
|
|
@@ -1727,9 +2043,20 @@ var SQLAction = {
|
|
|
1727
2043
|
|
|
1728
2044
|
// src/sql/SQLService.ts
|
|
1729
2045
|
var DDL_TOKENS = /* @__PURE__ */ new Set(["alter", "create", "drop"]);
|
|
2046
|
+
var MIGRATIONS_TABLE = "__tinycloud_sql_migrations";
|
|
2047
|
+
var MIGRATIONS_SCHEMA = `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
|
2048
|
+
key TEXT PRIMARY KEY,
|
|
2049
|
+
namespace TEXT NOT NULL,
|
|
2050
|
+
id TEXT NOT NULL,
|
|
2051
|
+
applied_at TEXT NOT NULL,
|
|
2052
|
+
statement_count INTEGER NOT NULL
|
|
2053
|
+
)`;
|
|
2054
|
+
var MIGRATIONS_META_NAMESPACE = "tinycloud.sql.migrations";
|
|
2055
|
+
var MIGRATIONS_META_ID = "000_metadata";
|
|
1730
2056
|
var SQLService = class extends BaseService {
|
|
1731
2057
|
constructor(config = {}) {
|
|
1732
2058
|
super();
|
|
2059
|
+
this.migrationLocks = /* @__PURE__ */ new Map();
|
|
1733
2060
|
this._config = config;
|
|
1734
2061
|
}
|
|
1735
2062
|
get config() {
|
|
@@ -1765,6 +2092,24 @@ var SQLService = class extends BaseService {
|
|
|
1765
2092
|
async batch(statements, options) {
|
|
1766
2093
|
return this.batchOnDb(this.defaultDbName, statements, options);
|
|
1767
2094
|
}
|
|
2095
|
+
async applyMigrationsOnDb(dbName, options) {
|
|
2096
|
+
const validationError = validateMigrationOptions(options);
|
|
2097
|
+
if (validationError) {
|
|
2098
|
+
return err(serviceError(ErrorCodes.INVALID_INPUT, validationError, "sql"));
|
|
2099
|
+
}
|
|
2100
|
+
const lockKey = `${dbName}\0${options.namespace}`;
|
|
2101
|
+
const existing = this.migrationLocks.get(lockKey);
|
|
2102
|
+
if (existing) {
|
|
2103
|
+
return existing;
|
|
2104
|
+
}
|
|
2105
|
+
const promise = this.applyMigrationsOnDbUnlocked(dbName, options);
|
|
2106
|
+
this.migrationLocks.set(lockKey, promise);
|
|
2107
|
+
try {
|
|
2108
|
+
return await promise;
|
|
2109
|
+
} finally {
|
|
2110
|
+
this.migrationLocks.delete(lockKey);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
1768
2113
|
// === Internal methods called by DatabaseHandle ===
|
|
1769
2114
|
async queryOnDb(dbName, sql, params, options) {
|
|
1770
2115
|
return this.withTelemetry("query", dbName, async () => {
|
|
@@ -1805,7 +2150,7 @@ var SQLService = class extends BaseService {
|
|
|
1805
2150
|
const actions = [
|
|
1806
2151
|
this.actionForSql(sql, SQLAction.WRITE),
|
|
1807
2152
|
...(options?.schema ?? []).map(
|
|
1808
|
-
(statement) => this.actionForSql(statement, SQLAction.
|
|
2153
|
+
(statement) => this.actionForSql(statement, SQLAction.SCHEMA)
|
|
1809
2154
|
)
|
|
1810
2155
|
];
|
|
1811
2156
|
const response = await this.invokeSQL(
|
|
@@ -1895,6 +2240,78 @@ var SQLService = class extends BaseService {
|
|
|
1895
2240
|
}
|
|
1896
2241
|
});
|
|
1897
2242
|
}
|
|
2243
|
+
async applyMigrationsOnDbUnlocked(dbName, options) {
|
|
2244
|
+
return this.withTelemetry("migrations.apply", dbName, async () => {
|
|
2245
|
+
const created = await this.ensureMigrationsTable(dbName, options.signal);
|
|
2246
|
+
if (!created.ok) return created;
|
|
2247
|
+
const listed = await this.queryOnDb(
|
|
2248
|
+
dbName,
|
|
2249
|
+
`SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
|
|
2250
|
+
[options.namespace],
|
|
2251
|
+
{ signal: options.signal }
|
|
2252
|
+
);
|
|
2253
|
+
if (!listed.ok) return listed;
|
|
2254
|
+
const appliedIds = new Set(
|
|
2255
|
+
listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
|
|
2256
|
+
);
|
|
2257
|
+
const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
|
|
2258
|
+
const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
|
|
2259
|
+
const applied = [];
|
|
2260
|
+
for (const migration of pending) {
|
|
2261
|
+
const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
|
|
2262
|
+
if (!result.ok) return result;
|
|
2263
|
+
applied.push(migration.id);
|
|
2264
|
+
}
|
|
2265
|
+
return ok({
|
|
2266
|
+
database: dbName,
|
|
2267
|
+
namespace: options.namespace,
|
|
2268
|
+
status: applied.length > 0 ? "applied" : "already_current",
|
|
2269
|
+
applied,
|
|
2270
|
+
skipped
|
|
2271
|
+
});
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
async applyOneMigration(dbName, namespace, migration, signal) {
|
|
2275
|
+
const statements = [
|
|
2276
|
+
...migration.sql.map(
|
|
2277
|
+
(statement) => typeof statement === "string" ? { sql: statement } : statement
|
|
2278
|
+
),
|
|
2279
|
+
{
|
|
2280
|
+
sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
|
|
2281
|
+
params: [
|
|
2282
|
+
migrationKey(namespace, migration.id),
|
|
2283
|
+
namespace,
|
|
2284
|
+
migration.id,
|
|
2285
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2286
|
+
migration.sql.length
|
|
2287
|
+
]
|
|
2288
|
+
}
|
|
2289
|
+
];
|
|
2290
|
+
const result = await this.batchOnDb(dbName, statements, { signal });
|
|
2291
|
+
if (!result.ok) return result;
|
|
2292
|
+
return ok(void 0);
|
|
2293
|
+
}
|
|
2294
|
+
async ensureMigrationsTable(dbName, signal) {
|
|
2295
|
+
const result = await this.batchOnDb(
|
|
2296
|
+
dbName,
|
|
2297
|
+
[
|
|
2298
|
+
{ sql: MIGRATIONS_SCHEMA },
|
|
2299
|
+
{
|
|
2300
|
+
sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
|
|
2301
|
+
params: [
|
|
2302
|
+
migrationKey(MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID),
|
|
2303
|
+
MIGRATIONS_META_NAMESPACE,
|
|
2304
|
+
MIGRATIONS_META_ID,
|
|
2305
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2306
|
+
1
|
|
2307
|
+
]
|
|
2308
|
+
}
|
|
2309
|
+
],
|
|
2310
|
+
{ signal }
|
|
2311
|
+
);
|
|
2312
|
+
if (!result.ok) return result;
|
|
2313
|
+
return ok(void 0);
|
|
2314
|
+
}
|
|
1898
2315
|
// === Private helpers ===
|
|
1899
2316
|
async invokeSQL(dbName, actions, body, signal) {
|
|
1900
2317
|
const session = this.context.session;
|
|
@@ -1913,7 +2330,7 @@ var SQLService = class extends BaseService {
|
|
|
1913
2330
|
actionForSql(sql, fallback) {
|
|
1914
2331
|
const token = firstSqlToken(sql);
|
|
1915
2332
|
if (token === "pragma") return SQLAction.ADMIN;
|
|
1916
|
-
if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.
|
|
2333
|
+
if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.SCHEMA;
|
|
1917
2334
|
return fallback;
|
|
1918
2335
|
}
|
|
1919
2336
|
actionsForSqlBatch(statements) {
|
|
@@ -1942,17 +2359,19 @@ var SQLService = class extends BaseService {
|
|
|
1942
2359
|
}
|
|
1943
2360
|
async handleErrorResponse(response, operation) {
|
|
1944
2361
|
const errorText = await response.text();
|
|
1945
|
-
|
|
1946
|
-
try {
|
|
1947
|
-
errorBody = JSON.parse(errorText);
|
|
1948
|
-
} catch {
|
|
1949
|
-
}
|
|
2362
|
+
const errorBody = parseServiceErrorBody(errorText);
|
|
1950
2363
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
1951
2364
|
response.status,
|
|
1952
2365
|
errorBody.error
|
|
1953
2366
|
);
|
|
1954
|
-
const message =
|
|
1955
|
-
|
|
2367
|
+
const message = formatServiceResponseError(
|
|
2368
|
+
"SQL",
|
|
2369
|
+
operation,
|
|
2370
|
+
response.status,
|
|
2371
|
+
errorText,
|
|
2372
|
+
errorBody
|
|
2373
|
+
);
|
|
2374
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
1956
2375
|
if (response.status === 401) {
|
|
1957
2376
|
const { resource, action } = parseAuthError(errorText);
|
|
1958
2377
|
if (action) meta.requiredAction = action;
|
|
@@ -2012,6 +2431,53 @@ function firstSqlToken(sql) {
|
|
|
2012
2431
|
const match = /^[A-Za-z_]+/.exec(sql.slice(index));
|
|
2013
2432
|
return match?.[0].toLowerCase();
|
|
2014
2433
|
}
|
|
2434
|
+
function validateMigrationOptions(options) {
|
|
2435
|
+
if (!options || typeof options !== "object") {
|
|
2436
|
+
return "SQL migrations require options";
|
|
2437
|
+
}
|
|
2438
|
+
if (!options.namespace || typeof options.namespace !== "string") {
|
|
2439
|
+
return "SQL migrations require a non-empty namespace";
|
|
2440
|
+
}
|
|
2441
|
+
if (!Array.isArray(options.migrations)) {
|
|
2442
|
+
return "SQL migrations require an ordered migrations array";
|
|
2443
|
+
}
|
|
2444
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2445
|
+
for (const migration of options.migrations) {
|
|
2446
|
+
if (!migration.id || typeof migration.id !== "string") {
|
|
2447
|
+
return "SQL migrations require every migration to have a non-empty id";
|
|
2448
|
+
}
|
|
2449
|
+
if (ids.has(migration.id)) {
|
|
2450
|
+
return `Duplicate SQL migration id: ${migration.id}`;
|
|
2451
|
+
}
|
|
2452
|
+
ids.add(migration.id);
|
|
2453
|
+
if (!Array.isArray(migration.sql)) {
|
|
2454
|
+
return `SQL migration ${migration.id} requires a sql array`;
|
|
2455
|
+
}
|
|
2456
|
+
for (const statement of migration.sql) {
|
|
2457
|
+
if (typeof statement === "string") {
|
|
2458
|
+
if (statement.trim() === "") {
|
|
2459
|
+
return `SQL migration ${migration.id} contains an empty statement`;
|
|
2460
|
+
}
|
|
2461
|
+
continue;
|
|
2462
|
+
}
|
|
2463
|
+
if (!statement || typeof statement.sql !== "string" || statement.sql.trim() === "") {
|
|
2464
|
+
return `SQL migration ${migration.id} contains an invalid statement`;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return null;
|
|
2469
|
+
}
|
|
2470
|
+
function migrationKey(namespace, id) {
|
|
2471
|
+
return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
|
|
2472
|
+
}
|
|
2473
|
+
function rowValue(row, index) {
|
|
2474
|
+
if (Array.isArray(row)) return row[index];
|
|
2475
|
+
if (row && typeof row === "object") {
|
|
2476
|
+
const values = Object.values(row);
|
|
2477
|
+
return values[index];
|
|
2478
|
+
}
|
|
2479
|
+
return void 0;
|
|
2480
|
+
}
|
|
2015
2481
|
|
|
2016
2482
|
// src/duckdb/DuckDbDatabaseHandle.ts
|
|
2017
2483
|
var DuckDbDatabaseHandle = class {
|
|
@@ -2321,17 +2787,19 @@ var DuckDbService = class extends BaseService {
|
|
|
2321
2787
|
}
|
|
2322
2788
|
async handleErrorResponse(response, operation) {
|
|
2323
2789
|
const errorText = await response.text();
|
|
2324
|
-
|
|
2325
|
-
try {
|
|
2326
|
-
errorBody = JSON.parse(errorText);
|
|
2327
|
-
} catch {
|
|
2328
|
-
}
|
|
2790
|
+
const errorBody = parseServiceErrorBody(errorText);
|
|
2329
2791
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
2330
2792
|
response.status,
|
|
2331
2793
|
errorBody.error
|
|
2332
2794
|
);
|
|
2333
|
-
const message =
|
|
2334
|
-
|
|
2795
|
+
const message = formatServiceResponseError(
|
|
2796
|
+
"DuckDB",
|
|
2797
|
+
operation,
|
|
2798
|
+
response.status,
|
|
2799
|
+
errorText,
|
|
2800
|
+
errorBody
|
|
2801
|
+
);
|
|
2802
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
2335
2803
|
if (response.status === 401) {
|
|
2336
2804
|
const { resource, action } = parseAuthError(errorText);
|
|
2337
2805
|
if (action) meta.requiredAction = action;
|
|
@@ -5679,6 +6147,7 @@ export {
|
|
|
5679
6147
|
RetryPolicySchema,
|
|
5680
6148
|
SECRET_NAME_RE,
|
|
5681
6149
|
SQLAction,
|
|
6150
|
+
SQLMigrations,
|
|
5682
6151
|
SQLService,
|
|
5683
6152
|
SecretsService,
|
|
5684
6153
|
ServiceContext,
|
|
@@ -5690,6 +6159,7 @@ export {
|
|
|
5690
6159
|
ServiceSessionSchema,
|
|
5691
6160
|
TelemetryEvents,
|
|
5692
6161
|
TelemetrySpanEventSchema,
|
|
6162
|
+
TinyCloudDebugLogger,
|
|
5693
6163
|
TinyCloudQuota,
|
|
5694
6164
|
VaultHeaders,
|
|
5695
6165
|
VaultPublicSpaceKVActions,
|
|
@@ -5709,21 +6179,26 @@ export {
|
|
|
5709
6179
|
canonicalize as canonicalizeEncryptionJson,
|
|
5710
6180
|
canonicalizeSecretScope,
|
|
5711
6181
|
checkDecryptInvocationInput,
|
|
6182
|
+
clearTinyCloudDebugLogs,
|
|
5712
6183
|
createKVResponseSchema,
|
|
5713
6184
|
createResultSchema,
|
|
5714
6185
|
createVaultCrypto,
|
|
5715
6186
|
decryptEnvelopeWithKey,
|
|
5716
6187
|
defaultRetryPolicy,
|
|
5717
6188
|
deriveSignedReceiverKey,
|
|
6189
|
+
disableTinyCloudDebug,
|
|
5718
6190
|
discoverNetwork,
|
|
6191
|
+
enableTinyCloudDebug,
|
|
5719
6192
|
encryptToNetwork,
|
|
5720
6193
|
encryptionError,
|
|
5721
6194
|
ensureNetworkUsableForDecrypt,
|
|
5722
6195
|
err,
|
|
5723
6196
|
errorResult,
|
|
5724
6197
|
generateRandomReceiverKey,
|
|
6198
|
+
getTinyCloudDebugLogs,
|
|
5725
6199
|
hexDecode,
|
|
5726
6200
|
hexEncode2 as hexEncode,
|
|
6201
|
+
installTinyCloudDebugGlobals,
|
|
5727
6202
|
isNetworkId,
|
|
5728
6203
|
networkDiscoveryKey,
|
|
5729
6204
|
networkError,
|
|
@@ -5739,6 +6214,7 @@ export {
|
|
|
5739
6214
|
storageLimitReachedError,
|
|
5740
6215
|
storageQuotaExceededError,
|
|
5741
6216
|
timeoutError,
|
|
6217
|
+
tinyCloudDebugLogger,
|
|
5742
6218
|
utf8Decode,
|
|
5743
6219
|
utf8Encode,
|
|
5744
6220
|
validateEnvelope,
|