@tinycloud/sdk-core 2.11.0-beta.1 → 2.11.0-beta.4

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 CHANGED
@@ -6702,15 +6702,28 @@ function normalizeSpace(space) {
6702
6702
  function isCapabilitySubset(requested, granted) {
6703
6703
  const missing = [];
6704
6704
  for (const req of requested) {
6705
- const match = granted.find((g) => canonicalizeEntryMatches(req, g));
6706
- if (match === void 0) {
6705
+ const matchingScope = granted.filter(
6706
+ (entry) => canonicalizeEntryScopeMatches(req, entry)
6707
+ );
6708
+ const requestedActions = expandActionShortNames(
6709
+ req.service,
6710
+ req.actions
6711
+ );
6712
+ const grantedActions = matchingScope.flatMap(
6713
+ (entry) => expandActionShortNames(entry.service, entry.actions)
6714
+ );
6715
+ const covered = requestedActions.every(
6716
+ (requestedAction) => grantedActions.some(
6717
+ (grantedAction) => actionContains(grantedAction, requestedAction)
6718
+ )
6719
+ );
6720
+ if (!covered) {
6707
6721
  missing.push(cloneEntry(req));
6708
- continue;
6709
6722
  }
6710
6723
  }
6711
6724
  return { subset: missing.length === 0, missing };
6712
6725
  }
6713
- function canonicalizeEntryMatches(requested, granted) {
6726
+ function canonicalizeEntryScopeMatches(requested, granted) {
6714
6727
  if (requested.service !== granted.service) {
6715
6728
  return false;
6716
6729
  }
@@ -6720,15 +6733,6 @@ function canonicalizeEntryMatches(requested, granted) {
6720
6733
  if (!pathContains2(granted.path, requested.path)) {
6721
6734
  return false;
6722
6735
  }
6723
- const reqActions = new Set(
6724
- expandActionShortNames(requested.service, requested.actions)
6725
- );
6726
- const grantedActions = expandActionShortNames(granted.service, granted.actions);
6727
- for (const a of reqActions) {
6728
- if (!grantedActions.some((grantedAction) => actionContains(grantedAction, a))) {
6729
- return false;
6730
- }
6731
- }
6732
6736
  if (!recapCaveatsEqual(requested.caveats, granted.caveats)) {
6733
6737
  return false;
6734
6738
  }
@@ -12120,6 +12124,22 @@ var ACCOUNT_SPACES_PATH = "spaces/";
12120
12124
  var AccountService = class {
12121
12125
  constructor(config) {
12122
12126
  this.config = config;
12127
+ /**
12128
+ * Per-account-space memo for {@link ensureAccountIndex}.
12129
+ *
12130
+ * Applying the index migration is a no-op once the schema exists, but the
12131
+ * SQL service still spends two round trips discovering that (create the
12132
+ * migrations table, then read the applied-migration rows). Account bootstrap
12133
+ * touches the index eight times, so without a memo that is ~14 wasted
12134
+ * requests on the cold sign-in path. The in-flight dedupe inside SQLService
12135
+ * never helps here: the node SDK builds a fresh SQLService per
12136
+ * `getAccountDb()` call, so its lock map is always empty.
12137
+ *
12138
+ * Keyed by account space id, so a different account never reuses this entry.
12139
+ * Failures are evicted so a later call retries instead of inheriting a
12140
+ * permanently poisoned result.
12141
+ */
12142
+ this.accountIndexReady = /* @__PURE__ */ new Map();
12123
12143
  this.applications = {
12124
12144
  list: async (options2 = {}) => {
12125
12145
  if (options2.preferIndex) {
@@ -12153,7 +12173,7 @@ var AccountService = class {
12153
12173
  if (!loaded.ok) return accountErr(loaded.error);
12154
12174
  return (0, import_sdk_services6.ok)(applicationFromRecord(key, loaded.data.data));
12155
12175
  },
12156
- register: async (manifest) => {
12176
+ register: async (manifest, options2 = {}) => {
12157
12177
  const manifests = Array.isArray(manifest) ? manifest : [manifest];
12158
12178
  const request = composeManifestRequest(manifests);
12159
12179
  if (request.registryRecords.length === 0) {
@@ -12171,7 +12191,7 @@ var AccountService = class {
12171
12191
  let registered;
12172
12192
  for (const record of request.registryRecords) {
12173
12193
  const manifestHash = hashJson(record.manifests);
12174
- if (await this.indexHasApplicationHash(record.app_id, manifestHash)) {
12194
+ if (!options2.assumeUnregistered && await this.indexHasApplicationHash(record.app_id, manifestHash)) {
12175
12195
  registered = {
12176
12196
  appId: record.app_id,
12177
12197
  manifests: record.manifests,
@@ -12352,6 +12372,7 @@ var AccountService = class {
12352
12372
  const delegations = await this.delegations.list();
12353
12373
  if (!delegations.ok) return delegations;
12354
12374
  const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
12375
+ this.invalidateAccountIndexMemo();
12355
12376
  const schema = await this.ensureAccountIndex(dbResult.data);
12356
12377
  if (!schema.ok) return schema;
12357
12378
  const statements = [
@@ -12751,7 +12772,41 @@ var AccountService = class {
12751
12772
  ]);
12752
12773
  });
12753
12774
  }
12775
+ /**
12776
+ * Apply the account index schema at most once per account space.
12777
+ *
12778
+ * The index is a cache over canonical KV state, and every caller already
12779
+ * tolerates it being absent (`ignoreIndexFailure` / `isMissingIndexError`),
12780
+ * so a memo that is wrong in the "already applied" direction degrades to a
12781
+ * `no such table` read that the caller handles. A memo that is wrong in the
12782
+ * other direction would be a real bug, so failures are never cached.
12783
+ */
12754
12784
  async ensureAccountIndex(db) {
12785
+ const key = this.config.getAccountSpaceId() ?? "";
12786
+ const cached = this.accountIndexReady.get(key);
12787
+ if (cached) return cached;
12788
+ const pending = this.applyAccountIndex(db);
12789
+ this.accountIndexReady.set(key, pending);
12790
+ const evict = () => {
12791
+ if (this.accountIndexReady.get(key) === pending) {
12792
+ this.accountIndexReady.delete(key);
12793
+ }
12794
+ };
12795
+ let settled;
12796
+ try {
12797
+ settled = await pending;
12798
+ } catch (error) {
12799
+ evict();
12800
+ throw error;
12801
+ }
12802
+ if (!settled.ok) evict();
12803
+ return settled;
12804
+ }
12805
+ /** Drop the memo so the next {@link ensureAccountIndex} re-applies the schema. */
12806
+ invalidateAccountIndexMemo() {
12807
+ this.accountIndexReady.delete(this.config.getAccountSpaceId() ?? "");
12808
+ }
12809
+ async applyAccountIndex(db) {
12755
12810
  const migrated = await db.migrations.apply({
12756
12811
  namespace: ACCOUNT_INDEX_NAMESPACE,
12757
12812
  migrations: [
@@ -15295,13 +15350,14 @@ async function discoverLocalTinyCloudNode(options2 = {}) {
15295
15350
  }
15296
15351
  const fetchFn = options2.fetch ?? globalThis.fetch;
15297
15352
  const identityStore = options2.identityStore ?? defaultLocalNodeIdentityStore;
15298
- const staticCandidates = [
15299
- {
15353
+ const staticCandidates = [];
15354
+ if (options2.localNodeUrl) {
15355
+ staticCandidates.push({
15300
15356
  source: "local-loopback",
15301
- url: options2.localNodeUrl ?? DEFAULT_LOCAL_NODE_URL,
15357
+ url: options2.localNodeUrl,
15302
15358
  timeoutMs: LOCAL_LOOPBACK_PROBE_TIMEOUT_MS
15303
- }
15304
- ];
15359
+ });
15360
+ }
15305
15361
  if (options2.localLinkName) {
15306
15362
  if (isValidDnsLabel(options2.localLinkName)) {
15307
15363
  staticCandidates.push({