@tinycloud/sdk-services 2.3.0 → 2.4.0-beta.10
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 +326 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +325 -52
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs +70 -29
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +28 -0
- package/dist/kv/index.d.ts +28 -0
- package/dist/kv/index.js +70 -29
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs +247 -16
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +70 -2
- package/dist/sql/index.d.ts +70 -2
- package/dist/sql/index.js +246 -16
- package/dist/sql/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,6 +52,7 @@ __export(index_exports, {
|
|
|
52
52
|
RetryPolicySchema: () => RetryPolicySchema,
|
|
53
53
|
SECRET_NAME_RE: () => SECRET_NAME_RE,
|
|
54
54
|
SQLAction: () => SQLAction,
|
|
55
|
+
SQLMigrations: () => SQLMigrations,
|
|
55
56
|
SQLService: () => SQLService,
|
|
56
57
|
SecretsService: () => SecretsService,
|
|
57
58
|
ServiceContext: () => ServiceContext,
|
|
@@ -1113,6 +1114,31 @@ var KVService = class extends BaseService {
|
|
|
1113
1114
|
}
|
|
1114
1115
|
return void 0;
|
|
1115
1116
|
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Classify a KV 404 by reading the response body once.
|
|
1119
|
+
*
|
|
1120
|
+
* The server returns 404 both for a genuinely missing key AND for an
|
|
1121
|
+
* un-hosted space (body "Space not found"). Previously get/head/delete
|
|
1122
|
+
* collapsed every 404 to KV_NOT_FOUND before reading the body, so an
|
|
1123
|
+
* un-hosted-space read was indistinguishable from a missing key. We now
|
|
1124
|
+
* preserve status + the "Space not found" body for the un-hosted case (so the
|
|
1125
|
+
* CLI/SDK can normalize it to SPACE_NOT_HOSTED, matching put/list/sql), and
|
|
1126
|
+
* fall through to KV_NOT_FOUND for a real missing key.
|
|
1127
|
+
*/
|
|
1128
|
+
async classifyNotFound(response, key) {
|
|
1129
|
+
const errorText = await response.text();
|
|
1130
|
+
if (/space not found/i.test(errorText)) {
|
|
1131
|
+
return err(
|
|
1132
|
+
serviceError(
|
|
1133
|
+
ErrorCodes.KV_NOT_FOUND,
|
|
1134
|
+
`KV ${response.status} - ${errorText}`,
|
|
1135
|
+
"kv",
|
|
1136
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1137
|
+
)
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
return err(serviceError(ErrorCodes.KV_NOT_FOUND, `Key not found: ${key}`, "kv"));
|
|
1141
|
+
}
|
|
1116
1142
|
/**
|
|
1117
1143
|
* Get the full path with optional prefix.
|
|
1118
1144
|
*
|
|
@@ -1163,6 +1189,38 @@ var KVService = class extends BaseService {
|
|
|
1163
1189
|
signal: this.combineSignals(signal)
|
|
1164
1190
|
});
|
|
1165
1191
|
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Serialize a single put value into a fetch body.
|
|
1194
|
+
*
|
|
1195
|
+
* Binary values (Blob/ArrayBuffer/typed-array, incl. Node Buffer) are sent as
|
|
1196
|
+
* raw bytes (as a Blob) so they round-trip byte-identically — without this a
|
|
1197
|
+
* Buffer would be JSON.stringify'd into `{"type":"Buffer","data":[...]}`.
|
|
1198
|
+
* Strings are returned unchanged (preserving prior behavior); other values are
|
|
1199
|
+
* JSON-encoded. `contentType` overrides the inferred type for binary values.
|
|
1200
|
+
*/
|
|
1201
|
+
serializePutValue(value, contentType) {
|
|
1202
|
+
if (value instanceof Blob) {
|
|
1203
|
+
if (!contentType || value.type === contentType) {
|
|
1204
|
+
return value;
|
|
1205
|
+
}
|
|
1206
|
+
return new Blob([value], { type: contentType });
|
|
1207
|
+
}
|
|
1208
|
+
if (value instanceof ArrayBuffer) {
|
|
1209
|
+
return new Blob([value], {
|
|
1210
|
+
type: contentType ?? "application/octet-stream"
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
if (ArrayBuffer.isView(value)) {
|
|
1214
|
+
const view = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
1215
|
+
return new Blob([view], {
|
|
1216
|
+
type: contentType ?? "application/octet-stream"
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
if (typeof value === "string") {
|
|
1220
|
+
return contentType ? new Blob([value], { type: contentType }) : value;
|
|
1221
|
+
}
|
|
1222
|
+
return JSON.stringify(value);
|
|
1223
|
+
}
|
|
1166
1224
|
serializeBatchPutValue(item) {
|
|
1167
1225
|
const contentType = item.contentType;
|
|
1168
1226
|
if (item.value instanceof Blob) {
|
|
@@ -1232,10 +1290,13 @@ var KVService = class extends BaseService {
|
|
|
1232
1290
|
* @param raw - Whether to return raw text
|
|
1233
1291
|
* @returns Parsed data
|
|
1234
1292
|
*/
|
|
1235
|
-
async parseResponse(response, raw = false) {
|
|
1293
|
+
async parseResponse(response, raw = false, binary = false) {
|
|
1236
1294
|
if (!response.ok) {
|
|
1237
1295
|
return void 0;
|
|
1238
1296
|
}
|
|
1297
|
+
if (binary) {
|
|
1298
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
1299
|
+
}
|
|
1239
1300
|
if (raw) {
|
|
1240
1301
|
return await response.text();
|
|
1241
1302
|
}
|
|
@@ -1324,13 +1385,7 @@ var KVService = class extends BaseService {
|
|
|
1324
1385
|
}));
|
|
1325
1386
|
}
|
|
1326
1387
|
if (response.status === 404) {
|
|
1327
|
-
return
|
|
1328
|
-
serviceError(
|
|
1329
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
1330
|
-
`Key not found: ${key}`,
|
|
1331
|
-
"kv"
|
|
1332
|
-
)
|
|
1333
|
-
);
|
|
1388
|
+
return this.classifyNotFound(response, key);
|
|
1334
1389
|
}
|
|
1335
1390
|
const errorText = await response.text();
|
|
1336
1391
|
return err(
|
|
@@ -1342,7 +1397,11 @@ var KVService = class extends BaseService {
|
|
|
1342
1397
|
)
|
|
1343
1398
|
);
|
|
1344
1399
|
}
|
|
1345
|
-
const data = await this.parseResponse(
|
|
1400
|
+
const data = await this.parseResponse(
|
|
1401
|
+
response,
|
|
1402
|
+
options?.raw,
|
|
1403
|
+
options?.binary
|
|
1404
|
+
);
|
|
1346
1405
|
return ok({
|
|
1347
1406
|
data,
|
|
1348
1407
|
headers: this.createResponseHeaders(response.headers)
|
|
@@ -1361,12 +1420,7 @@ var KVService = class extends BaseService {
|
|
|
1361
1420
|
return err(authRequiredError("kv"));
|
|
1362
1421
|
}
|
|
1363
1422
|
const path = this.getFullPath(key, options?.prefix);
|
|
1364
|
-
|
|
1365
|
-
if (typeof value === "string") {
|
|
1366
|
-
body = value;
|
|
1367
|
-
} else {
|
|
1368
|
-
body = JSON.stringify(value);
|
|
1369
|
-
}
|
|
1423
|
+
const body = this.serializePutValue(value, options?.contentType);
|
|
1370
1424
|
try {
|
|
1371
1425
|
const response = await this.invokeOperation(
|
|
1372
1426
|
path,
|
|
@@ -1592,13 +1646,7 @@ var KVService = class extends BaseService {
|
|
|
1592
1646
|
}));
|
|
1593
1647
|
}
|
|
1594
1648
|
if (response.status === 404) {
|
|
1595
|
-
return
|
|
1596
|
-
serviceError(
|
|
1597
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
1598
|
-
`Key not found: ${key}`,
|
|
1599
|
-
"kv"
|
|
1600
|
-
)
|
|
1601
|
-
);
|
|
1649
|
+
return this.classifyNotFound(response, key);
|
|
1602
1650
|
}
|
|
1603
1651
|
const errorText = await response.text();
|
|
1604
1652
|
return err(
|
|
@@ -1643,13 +1691,7 @@ var KVService = class extends BaseService {
|
|
|
1643
1691
|
}));
|
|
1644
1692
|
}
|
|
1645
1693
|
if (response.status === 404) {
|
|
1646
|
-
return
|
|
1647
|
-
serviceError(
|
|
1648
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
1649
|
-
`Key not found: ${key}`,
|
|
1650
|
-
"kv"
|
|
1651
|
-
)
|
|
1652
|
-
);
|
|
1694
|
+
return this.classifyNotFound(response, key);
|
|
1653
1695
|
}
|
|
1654
1696
|
const errorText = await response.text();
|
|
1655
1697
|
return err(
|
|
@@ -1776,11 +1818,59 @@ var KVService = class extends BaseService {
|
|
|
1776
1818
|
*/
|
|
1777
1819
|
KVService.serviceName = "kv";
|
|
1778
1820
|
|
|
1821
|
+
// src/responseErrors.ts
|
|
1822
|
+
function parseServiceErrorBody(errorText) {
|
|
1823
|
+
try {
|
|
1824
|
+
return JSON.parse(errorText);
|
|
1825
|
+
} catch {
|
|
1826
|
+
return {};
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
1830
|
+
if (parsed.message) {
|
|
1831
|
+
return compactMessage(parsed.message);
|
|
1832
|
+
}
|
|
1833
|
+
if (looksLikeHtml(errorText)) {
|
|
1834
|
+
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
1835
|
+
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
1836
|
+
}
|
|
1837
|
+
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
1838
|
+
}
|
|
1839
|
+
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
1840
|
+
}
|
|
1841
|
+
function responseErrorMeta(status, statusText, errorText) {
|
|
1842
|
+
const meta = { status, statusText };
|
|
1843
|
+
const snippet = compactMessage(errorText);
|
|
1844
|
+
if (snippet) {
|
|
1845
|
+
meta.responseSnippet = snippet.slice(0, 300);
|
|
1846
|
+
}
|
|
1847
|
+
return meta;
|
|
1848
|
+
}
|
|
1849
|
+
function looksLikeHtml(text) {
|
|
1850
|
+
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
1851
|
+
}
|
|
1852
|
+
function compactMessage(message) {
|
|
1853
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
1854
|
+
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
// src/sql/SQLMigrations.ts
|
|
1858
|
+
var SQLMigrations = class {
|
|
1859
|
+
constructor(service, dbName) {
|
|
1860
|
+
this.service = service;
|
|
1861
|
+
this.dbName = dbName;
|
|
1862
|
+
}
|
|
1863
|
+
apply(options) {
|
|
1864
|
+
return this.service.applyMigrationsOnDb(this.dbName, options);
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
|
|
1779
1868
|
// src/sql/DatabaseHandle.ts
|
|
1780
1869
|
var DatabaseHandle = class {
|
|
1781
1870
|
constructor(service, name) {
|
|
1782
1871
|
this.service = service;
|
|
1783
1872
|
this.name = name;
|
|
1873
|
+
this.migrations = new SQLMigrations(service, name);
|
|
1784
1874
|
}
|
|
1785
1875
|
async query(sql, params, options) {
|
|
1786
1876
|
return this.service.queryOnDb(this.name, sql, params, options);
|
|
@@ -1803,6 +1893,7 @@ var DatabaseHandle = class {
|
|
|
1803
1893
|
var SQLAction = {
|
|
1804
1894
|
READ: "tinycloud.sql/read",
|
|
1805
1895
|
WRITE: "tinycloud.sql/write",
|
|
1896
|
+
DDL: "tinycloud.sql/ddl",
|
|
1806
1897
|
ADMIN: "tinycloud.sql/admin",
|
|
1807
1898
|
SELECT: "tinycloud.sql/select",
|
|
1808
1899
|
INSERT: "tinycloud.sql/insert",
|
|
@@ -1814,9 +1905,21 @@ var SQLAction = {
|
|
|
1814
1905
|
};
|
|
1815
1906
|
|
|
1816
1907
|
// src/sql/SQLService.ts
|
|
1908
|
+
var DDL_TOKENS = /* @__PURE__ */ new Set(["alter", "create", "drop"]);
|
|
1909
|
+
var MIGRATIONS_TABLE = "__tinycloud_sql_migrations";
|
|
1910
|
+
var MIGRATIONS_SCHEMA = `CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
|
1911
|
+
key TEXT PRIMARY KEY,
|
|
1912
|
+
namespace TEXT NOT NULL,
|
|
1913
|
+
id TEXT NOT NULL,
|
|
1914
|
+
applied_at TEXT NOT NULL,
|
|
1915
|
+
statement_count INTEGER NOT NULL
|
|
1916
|
+
)`;
|
|
1917
|
+
var MIGRATIONS_META_NAMESPACE = "tinycloud.sql.migrations";
|
|
1918
|
+
var MIGRATIONS_META_ID = "000_metadata";
|
|
1817
1919
|
var SQLService = class extends BaseService {
|
|
1818
1920
|
constructor(config = {}) {
|
|
1819
1921
|
super();
|
|
1922
|
+
this.migrationLocks = /* @__PURE__ */ new Map();
|
|
1820
1923
|
this._config = config;
|
|
1821
1924
|
}
|
|
1822
1925
|
get config() {
|
|
@@ -1852,6 +1955,24 @@ var SQLService = class extends BaseService {
|
|
|
1852
1955
|
async batch(statements, options) {
|
|
1853
1956
|
return this.batchOnDb(this.defaultDbName, statements, options);
|
|
1854
1957
|
}
|
|
1958
|
+
async applyMigrationsOnDb(dbName, options) {
|
|
1959
|
+
const validationError = validateMigrationOptions(options);
|
|
1960
|
+
if (validationError) {
|
|
1961
|
+
return err(serviceError(ErrorCodes.INVALID_INPUT, validationError, "sql"));
|
|
1962
|
+
}
|
|
1963
|
+
const lockKey = `${dbName}\0${options.namespace}`;
|
|
1964
|
+
const existing = this.migrationLocks.get(lockKey);
|
|
1965
|
+
if (existing) {
|
|
1966
|
+
return existing;
|
|
1967
|
+
}
|
|
1968
|
+
const promise = this.applyMigrationsOnDbUnlocked(dbName, options);
|
|
1969
|
+
this.migrationLocks.set(lockKey, promise);
|
|
1970
|
+
try {
|
|
1971
|
+
return await promise;
|
|
1972
|
+
} finally {
|
|
1973
|
+
this.migrationLocks.delete(lockKey);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1855
1976
|
// === Internal methods called by DatabaseHandle ===
|
|
1856
1977
|
async queryOnDb(dbName, sql, params, options) {
|
|
1857
1978
|
return this.withTelemetry("query", dbName, async () => {
|
|
@@ -1889,9 +2010,15 @@ var SQLService = class extends BaseService {
|
|
|
1889
2010
|
if (options?.schema) {
|
|
1890
2011
|
body.schema = options.schema;
|
|
1891
2012
|
}
|
|
2013
|
+
const actions = [
|
|
2014
|
+
this.actionForSql(sql, SQLAction.WRITE),
|
|
2015
|
+
...(options?.schema ?? []).map(
|
|
2016
|
+
(statement) => this.actionForSql(statement, SQLAction.DDL)
|
|
2017
|
+
)
|
|
2018
|
+
];
|
|
1892
2019
|
const response = await this.invokeSQL(
|
|
1893
2020
|
dbName,
|
|
1894
|
-
this.
|
|
2021
|
+
this.dedupeActions(actions),
|
|
1895
2022
|
body,
|
|
1896
2023
|
options?.signal
|
|
1897
2024
|
);
|
|
@@ -1913,7 +2040,7 @@ var SQLService = class extends BaseService {
|
|
|
1913
2040
|
try {
|
|
1914
2041
|
const response = await this.invokeSQL(
|
|
1915
2042
|
dbName,
|
|
1916
|
-
this.
|
|
2043
|
+
this.actionsForSqlBatch(statements),
|
|
1917
2044
|
{ action: "batch", statements },
|
|
1918
2045
|
options?.signal
|
|
1919
2046
|
);
|
|
@@ -1976,10 +2103,83 @@ var SQLService = class extends BaseService {
|
|
|
1976
2103
|
}
|
|
1977
2104
|
});
|
|
1978
2105
|
}
|
|
2106
|
+
async applyMigrationsOnDbUnlocked(dbName, options) {
|
|
2107
|
+
return this.withTelemetry("migrations.apply", dbName, async () => {
|
|
2108
|
+
const created = await this.ensureMigrationsTable(dbName, options.signal);
|
|
2109
|
+
if (!created.ok) return created;
|
|
2110
|
+
const listed = await this.queryOnDb(
|
|
2111
|
+
dbName,
|
|
2112
|
+
`SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
|
|
2113
|
+
[options.namespace],
|
|
2114
|
+
{ signal: options.signal }
|
|
2115
|
+
);
|
|
2116
|
+
if (!listed.ok) return listed;
|
|
2117
|
+
const appliedIds = new Set(
|
|
2118
|
+
listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
|
|
2119
|
+
);
|
|
2120
|
+
const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
|
|
2121
|
+
const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
|
|
2122
|
+
const applied = [];
|
|
2123
|
+
for (const migration of pending) {
|
|
2124
|
+
const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
|
|
2125
|
+
if (!result.ok) return result;
|
|
2126
|
+
applied.push(migration.id);
|
|
2127
|
+
}
|
|
2128
|
+
return ok({
|
|
2129
|
+
database: dbName,
|
|
2130
|
+
namespace: options.namespace,
|
|
2131
|
+
status: applied.length > 0 ? "applied" : "already_current",
|
|
2132
|
+
applied,
|
|
2133
|
+
skipped
|
|
2134
|
+
});
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
async applyOneMigration(dbName, namespace, migration, signal) {
|
|
2138
|
+
const statements = [
|
|
2139
|
+
...migration.sql.map(
|
|
2140
|
+
(statement) => typeof statement === "string" ? { sql: statement } : statement
|
|
2141
|
+
),
|
|
2142
|
+
{
|
|
2143
|
+
sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
|
|
2144
|
+
params: [
|
|
2145
|
+
migrationKey(namespace, migration.id),
|
|
2146
|
+
namespace,
|
|
2147
|
+
migration.id,
|
|
2148
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2149
|
+
migration.sql.length
|
|
2150
|
+
]
|
|
2151
|
+
}
|
|
2152
|
+
];
|
|
2153
|
+
const result = await this.batchOnDb(dbName, statements, { signal });
|
|
2154
|
+
if (!result.ok) return result;
|
|
2155
|
+
return ok(void 0);
|
|
2156
|
+
}
|
|
2157
|
+
async ensureMigrationsTable(dbName, signal) {
|
|
2158
|
+
const result = await this.batchOnDb(
|
|
2159
|
+
dbName,
|
|
2160
|
+
[
|
|
2161
|
+
{ sql: MIGRATIONS_SCHEMA },
|
|
2162
|
+
{
|
|
2163
|
+
sql: `INSERT OR REPLACE INTO ${MIGRATIONS_TABLE} (key, namespace, id, applied_at, statement_count) VALUES (?, ?, ?, ?, ?)`,
|
|
2164
|
+
params: [
|
|
2165
|
+
migrationKey(MIGRATIONS_META_NAMESPACE, MIGRATIONS_META_ID),
|
|
2166
|
+
MIGRATIONS_META_NAMESPACE,
|
|
2167
|
+
MIGRATIONS_META_ID,
|
|
2168
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
2169
|
+
1
|
|
2170
|
+
]
|
|
2171
|
+
}
|
|
2172
|
+
],
|
|
2173
|
+
{ signal }
|
|
2174
|
+
);
|
|
2175
|
+
if (!result.ok) return result;
|
|
2176
|
+
return ok(void 0);
|
|
2177
|
+
}
|
|
1979
2178
|
// === Private helpers ===
|
|
1980
|
-
async invokeSQL(dbName,
|
|
2179
|
+
async invokeSQL(dbName, actions, body, signal) {
|
|
1981
2180
|
const session = this.context.session;
|
|
1982
|
-
const
|
|
2181
|
+
const actionList = Array.isArray(actions) ? actions : [actions];
|
|
2182
|
+
const headers = actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
|
|
1983
2183
|
return this.context.fetch(`${this.host}/invoke`, {
|
|
1984
2184
|
method: "POST",
|
|
1985
2185
|
headers: {
|
|
@@ -1991,26 +2191,50 @@ var SQLService = class extends BaseService {
|
|
|
1991
2191
|
});
|
|
1992
2192
|
}
|
|
1993
2193
|
actionForSql(sql, fallback) {
|
|
1994
|
-
|
|
2194
|
+
const token = firstSqlToken(sql);
|
|
2195
|
+
if (token === "pragma") return SQLAction.ADMIN;
|
|
2196
|
+
if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.DDL;
|
|
2197
|
+
return fallback;
|
|
2198
|
+
}
|
|
2199
|
+
actionsForSqlBatch(statements) {
|
|
2200
|
+
return this.dedupeActions(
|
|
2201
|
+
statements.map((statement) => this.actionForSql(statement.sql, SQLAction.WRITE))
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
dedupeActions(actions) {
|
|
2205
|
+
return [...new Set(actions)];
|
|
1995
2206
|
}
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2207
|
+
invokeSQLAny(session, dbName, actions) {
|
|
2208
|
+
if (!this.context.invokeAny) {
|
|
2209
|
+
throw new Error(
|
|
2210
|
+
`SQL operation requires multiple permissions (${actions.join(", ")}) but this SDK runtime does not support multi-resource invocations`
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
return this.context.invokeAny(
|
|
2214
|
+
session,
|
|
2215
|
+
actions.map((action) => ({
|
|
2216
|
+
spaceId: session.spaceId,
|
|
2217
|
+
service: "sql",
|
|
2218
|
+
path: dbName,
|
|
2219
|
+
action
|
|
2220
|
+
}))
|
|
2221
|
+
);
|
|
2000
2222
|
}
|
|
2001
2223
|
async handleErrorResponse(response, operation) {
|
|
2002
2224
|
const errorText = await response.text();
|
|
2003
|
-
|
|
2004
|
-
try {
|
|
2005
|
-
errorBody = JSON.parse(errorText);
|
|
2006
|
-
} catch {
|
|
2007
|
-
}
|
|
2225
|
+
const errorBody = parseServiceErrorBody(errorText);
|
|
2008
2226
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
2009
2227
|
response.status,
|
|
2010
2228
|
errorBody.error
|
|
2011
2229
|
);
|
|
2012
|
-
const message =
|
|
2013
|
-
|
|
2230
|
+
const message = formatServiceResponseError(
|
|
2231
|
+
"SQL",
|
|
2232
|
+
operation,
|
|
2233
|
+
response.status,
|
|
2234
|
+
errorText,
|
|
2235
|
+
errorBody
|
|
2236
|
+
);
|
|
2237
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
2014
2238
|
if (response.status === 401) {
|
|
2015
2239
|
const { resource, action } = parseAuthError(errorText);
|
|
2016
2240
|
if (action) meta.requiredAction = action;
|
|
@@ -2070,6 +2294,53 @@ function firstSqlToken(sql) {
|
|
|
2070
2294
|
const match = /^[A-Za-z_]+/.exec(sql.slice(index));
|
|
2071
2295
|
return match?.[0].toLowerCase();
|
|
2072
2296
|
}
|
|
2297
|
+
function validateMigrationOptions(options) {
|
|
2298
|
+
if (!options || typeof options !== "object") {
|
|
2299
|
+
return "SQL migrations require options";
|
|
2300
|
+
}
|
|
2301
|
+
if (!options.namespace || typeof options.namespace !== "string") {
|
|
2302
|
+
return "SQL migrations require a non-empty namespace";
|
|
2303
|
+
}
|
|
2304
|
+
if (!Array.isArray(options.migrations)) {
|
|
2305
|
+
return "SQL migrations require an ordered migrations array";
|
|
2306
|
+
}
|
|
2307
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2308
|
+
for (const migration of options.migrations) {
|
|
2309
|
+
if (!migration.id || typeof migration.id !== "string") {
|
|
2310
|
+
return "SQL migrations require every migration to have a non-empty id";
|
|
2311
|
+
}
|
|
2312
|
+
if (ids.has(migration.id)) {
|
|
2313
|
+
return `Duplicate SQL migration id: ${migration.id}`;
|
|
2314
|
+
}
|
|
2315
|
+
ids.add(migration.id);
|
|
2316
|
+
if (!Array.isArray(migration.sql)) {
|
|
2317
|
+
return `SQL migration ${migration.id} requires a sql array`;
|
|
2318
|
+
}
|
|
2319
|
+
for (const statement of migration.sql) {
|
|
2320
|
+
if (typeof statement === "string") {
|
|
2321
|
+
if (statement.trim() === "") {
|
|
2322
|
+
return `SQL migration ${migration.id} contains an empty statement`;
|
|
2323
|
+
}
|
|
2324
|
+
continue;
|
|
2325
|
+
}
|
|
2326
|
+
if (!statement || typeof statement.sql !== "string" || statement.sql.trim() === "") {
|
|
2327
|
+
return `SQL migration ${migration.id} contains an invalid statement`;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return null;
|
|
2332
|
+
}
|
|
2333
|
+
function migrationKey(namespace, id) {
|
|
2334
|
+
return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
|
|
2335
|
+
}
|
|
2336
|
+
function rowValue(row, index) {
|
|
2337
|
+
if (Array.isArray(row)) return row[index];
|
|
2338
|
+
if (row && typeof row === "object") {
|
|
2339
|
+
const values = Object.values(row);
|
|
2340
|
+
return values[index];
|
|
2341
|
+
}
|
|
2342
|
+
return void 0;
|
|
2343
|
+
}
|
|
2073
2344
|
|
|
2074
2345
|
// src/duckdb/DuckDbDatabaseHandle.ts
|
|
2075
2346
|
var DuckDbDatabaseHandle = class {
|
|
@@ -2379,17 +2650,19 @@ var DuckDbService = class extends BaseService {
|
|
|
2379
2650
|
}
|
|
2380
2651
|
async handleErrorResponse(response, operation) {
|
|
2381
2652
|
const errorText = await response.text();
|
|
2382
|
-
|
|
2383
|
-
try {
|
|
2384
|
-
errorBody = JSON.parse(errorText);
|
|
2385
|
-
} catch {
|
|
2386
|
-
}
|
|
2653
|
+
const errorBody = parseServiceErrorBody(errorText);
|
|
2387
2654
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
2388
2655
|
response.status,
|
|
2389
2656
|
errorBody.error
|
|
2390
2657
|
);
|
|
2391
|
-
const message =
|
|
2392
|
-
|
|
2658
|
+
const message = formatServiceResponseError(
|
|
2659
|
+
"DuckDB",
|
|
2660
|
+
operation,
|
|
2661
|
+
response.status,
|
|
2662
|
+
errorText,
|
|
2663
|
+
errorBody
|
|
2664
|
+
);
|
|
2665
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
2393
2666
|
if (response.status === 401) {
|
|
2394
2667
|
const { resource, action } = parseAuthError(errorText);
|
|
2395
2668
|
if (action) meta.requiredAction = action;
|
|
@@ -5738,6 +6011,7 @@ EncryptionService.serviceName = "encryption";
|
|
|
5738
6011
|
RetryPolicySchema,
|
|
5739
6012
|
SECRET_NAME_RE,
|
|
5740
6013
|
SQLAction,
|
|
6014
|
+
SQLMigrations,
|
|
5741
6015
|
SQLService,
|
|
5742
6016
|
SecretsService,
|
|
5743
6017
|
ServiceContext,
|