@tinycloud/sdk-core 2.11.0-beta.3 → 2.11.0-beta.5
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 +112 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -7
- package/dist/index.d.ts +35 -7
- package/dist/index.js +116 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -12266,6 +12266,60 @@ var AccountService = class {
|
|
|
12266
12266
|
await this.upsertSpaceIndexQuietly(registered);
|
|
12267
12267
|
return (0, import_sdk_services6.ok)(registered);
|
|
12268
12268
|
},
|
|
12269
|
+
/**
|
|
12270
|
+
* Register multiple spaces in one KV batch write + one multi-row index
|
|
12271
|
+
* write, instead of one `register()` round trip per space (TC-373).
|
|
12272
|
+
*
|
|
12273
|
+
* The stored records are computed exactly once and reused for both the
|
|
12274
|
+
* batch attempt and any reconciliation below, so a fallback never mints a
|
|
12275
|
+
* fresh timestamp for a space that already has a written record — a
|
|
12276
|
+
* second `spaceRecordFromInput` call would produce a new content hash and
|
|
12277
|
+
* leave the first attempt's blocks orphaned.
|
|
12278
|
+
*/
|
|
12279
|
+
registerBatch: async (spaces) => {
|
|
12280
|
+
if (spaces.length === 0) return (0, import_sdk_services6.ok)({ spaces: [] });
|
|
12281
|
+
await this.config.ensureAccountSpaceHosted?.();
|
|
12282
|
+
const kvResult = this.accountKV();
|
|
12283
|
+
if (!kvResult.ok) return kvResult;
|
|
12284
|
+
const stored = spaces.map((space) => spaceRecordFromInput(space));
|
|
12285
|
+
const registered = stored.map(
|
|
12286
|
+
(record) => spaceFromRecord(spaceKey(record.space_id), record)
|
|
12287
|
+
);
|
|
12288
|
+
const items = stored.map((record) => ({
|
|
12289
|
+
key: spaceKey(record.space_id),
|
|
12290
|
+
value: record
|
|
12291
|
+
}));
|
|
12292
|
+
const batchResult = await kvResult.data.batchPut(items);
|
|
12293
|
+
if (batchResult.ok) {
|
|
12294
|
+
await this.upsertSpacesIndexQuietly(registered);
|
|
12295
|
+
return (0, import_sdk_services6.ok)({ spaces: registered });
|
|
12296
|
+
}
|
|
12297
|
+
if (!isAmbiguousBatchFailure(batchResult.error)) {
|
|
12298
|
+
return accountErr(batchResult.error);
|
|
12299
|
+
}
|
|
12300
|
+
const reconcileFailures = [];
|
|
12301
|
+
for (const record of stored) {
|
|
12302
|
+
const written = await kvResult.data.put(spaceKey(record.space_id), record);
|
|
12303
|
+
if (!written.ok) {
|
|
12304
|
+
reconcileFailures.push(`${record.space_id}: ${written.error.message}`);
|
|
12305
|
+
}
|
|
12306
|
+
}
|
|
12307
|
+
if (reconcileFailures.length > 0) {
|
|
12308
|
+
return (0, import_sdk_services6.err)(
|
|
12309
|
+
(0, import_sdk_services6.serviceError)(
|
|
12310
|
+
"KV_BATCH_RECONCILE_FAILED",
|
|
12311
|
+
`KV batch write for ${items.length} space(s) failed ambiguously (${batchResult.error.message}) and per-space reconciliation also failed for: ${reconcileFailures.join("; ")}`,
|
|
12312
|
+
SERVICE_NAME2,
|
|
12313
|
+
{ cause: batchResult.error.cause, meta: { originalError: batchResult.error } }
|
|
12314
|
+
)
|
|
12315
|
+
);
|
|
12316
|
+
}
|
|
12317
|
+
console.warn(
|
|
12318
|
+
`[AccountService] KV batch write for ${items.length} space(s) returned an ambiguous error and was reconciled via per-space fallback. Original error: ${batchResult.error.message}`
|
|
12319
|
+
);
|
|
12320
|
+
await this.upsertSpacesIndexQuietly(registered);
|
|
12321
|
+
return (0, import_sdk_services6.ok)({ spaces: registered, recoveredFromBatchError: batchResult.error });
|
|
12322
|
+
},
|
|
12269
12323
|
syncAccessible: async () => {
|
|
12270
12324
|
const listed = await this.config.getSpaces().list();
|
|
12271
12325
|
if (!listed.ok) return accountErr(listed.error);
|
|
@@ -12652,6 +12706,47 @@ var AccountService = class {
|
|
|
12652
12706
|
if (!written.ok) return accountErr(written.error);
|
|
12653
12707
|
return (0, import_sdk_services6.ok)(void 0);
|
|
12654
12708
|
}
|
|
12709
|
+
async upsertSpacesIndexQuietly(spaces) {
|
|
12710
|
+
await ignoreIndexFailure(() => this.upsertSpacesIndex(spaces));
|
|
12711
|
+
}
|
|
12712
|
+
/**
|
|
12713
|
+
* Upsert multiple space index rows in ONE multi-row `INSERT OR REPLACE`
|
|
12714
|
+
* statement (TC-373), rather than one statement per space. This matters
|
|
12715
|
+
* because `IDatabaseHandle.batch()` executes its statement list as a plain
|
|
12716
|
+
* loop, not a single database transaction — an N-statement batch is not
|
|
12717
|
+
* atomic. A single multi-row INSERT is one SQLite statement, so it applies
|
|
12718
|
+
* atomically: all rows or none.
|
|
12719
|
+
*/
|
|
12720
|
+
async upsertSpacesIndex(spaces) {
|
|
12721
|
+
if (spaces.length === 0) return (0, import_sdk_services6.ok)(void 0);
|
|
12722
|
+
const dbResult = this.accountDb();
|
|
12723
|
+
if (!dbResult.ok) return (0, import_sdk_services6.ok)(void 0);
|
|
12724
|
+
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
12725
|
+
if (!schema.ok) return schema;
|
|
12726
|
+
const placeholders = spaces.map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
12727
|
+
const params = spaces.flatMap((space) => {
|
|
12728
|
+
const updatedAt = space.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
12729
|
+
return [
|
|
12730
|
+
space.spaceId,
|
|
12731
|
+
space.name,
|
|
12732
|
+
space.ownerDid,
|
|
12733
|
+
space.type,
|
|
12734
|
+
JSON.stringify(space.permissions),
|
|
12735
|
+
space.status,
|
|
12736
|
+
space.registeredAt ?? updatedAt,
|
|
12737
|
+
updatedAt,
|
|
12738
|
+
space.expiresAt?.toISOString() ?? null
|
|
12739
|
+
];
|
|
12740
|
+
});
|
|
12741
|
+
const written = await dbResult.data.batch([
|
|
12742
|
+
{
|
|
12743
|
+
sql: `INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES ${placeholders}`,
|
|
12744
|
+
params
|
|
12745
|
+
}
|
|
12746
|
+
]);
|
|
12747
|
+
if (!written.ok) return accountErr(written.error);
|
|
12748
|
+
return (0, import_sdk_services6.ok)(void 0);
|
|
12749
|
+
}
|
|
12655
12750
|
async deleteSpaceIndexQuietly(spaceId) {
|
|
12656
12751
|
await ignoreIndexFailure(() => this.deleteSpaceIndex(spaceId));
|
|
12657
12752
|
}
|
|
@@ -12978,6 +13073,17 @@ function accountErr(error) {
|
|
|
12978
13073
|
function isMissingIndexError(error) {
|
|
12979
13074
|
return /no such table:/i.test(error.message);
|
|
12980
13075
|
}
|
|
13076
|
+
var AMBIGUOUS_WRITE_STATUSES = /* @__PURE__ */ new Set([500, 502, 503, 504, 520, 522, 524]);
|
|
13077
|
+
function isAmbiguousBatchFailure(error) {
|
|
13078
|
+
if (error.code === import_sdk_services6.ErrorCodes.NETWORK_ERROR || error.code === import_sdk_services6.ErrorCodes.TIMEOUT) {
|
|
13079
|
+
return error.meta?.requestMayHaveDispatched === true;
|
|
13080
|
+
}
|
|
13081
|
+
if (error.code === import_sdk_services6.ErrorCodes.KV_WRITE_FAILED) {
|
|
13082
|
+
const status = error.meta?.status;
|
|
13083
|
+
return typeof status === "number" && AMBIGUOUS_WRITE_STATUSES.has(status);
|
|
13084
|
+
}
|
|
13085
|
+
return false;
|
|
13086
|
+
}
|
|
12981
13087
|
async function ignoreIndexFailure(task) {
|
|
12982
13088
|
try {
|
|
12983
13089
|
await task();
|
|
@@ -15350,13 +15456,14 @@ async function discoverLocalTinyCloudNode(options2 = {}) {
|
|
|
15350
15456
|
}
|
|
15351
15457
|
const fetchFn = options2.fetch ?? globalThis.fetch;
|
|
15352
15458
|
const identityStore = options2.identityStore ?? defaultLocalNodeIdentityStore;
|
|
15353
|
-
const staticCandidates = [
|
|
15354
|
-
|
|
15459
|
+
const staticCandidates = [];
|
|
15460
|
+
if (options2.localNodeUrl) {
|
|
15461
|
+
staticCandidates.push({
|
|
15355
15462
|
source: "local-loopback",
|
|
15356
|
-
url: options2.localNodeUrl
|
|
15463
|
+
url: options2.localNodeUrl,
|
|
15357
15464
|
timeoutMs: LOCAL_LOOPBACK_PROBE_TIMEOUT_MS
|
|
15358
|
-
}
|
|
15359
|
-
|
|
15465
|
+
});
|
|
15466
|
+
}
|
|
15360
15467
|
if (options2.localLinkName) {
|
|
15361
15468
|
if (isValidDnsLabel(options2.localLinkName)) {
|
|
15362
15469
|
staticCandidates.push({
|