@tinycloud/sdk-core 2.11.0-beta.0 → 2.11.0-beta.3
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/delegations/index.cjs +10 -2
- package/dist/delegations/index.cjs.map +1 -1
- package/dist/delegations/index.d.cts +2 -2
- package/dist/delegations/index.d.ts +2 -2
- package/dist/delegations/index.js +10 -2
- package/dist/delegations/index.js.map +1 -1
- package/dist/index.cjs +80 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -6
- package/dist/index.d.ts +51 -6
- package/dist/index.js +80 -17
- package/dist/index.js.map +1 -1
- package/dist/{recipient-types.schema-B2wH53_t.d.cts → recipient-types.schema-CWIt63Qp.d.cts} +34 -4
- package/dist/{recipient-types.schema-B2wH53_t.d.ts → recipient-types.schema-CWIt63Qp.d.ts} +34 -4
- package/package.json +1 -1
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
|
|
6706
|
-
|
|
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
|
|
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
|
}
|
|
@@ -10126,6 +10130,10 @@ async function createPolicyEnforcementDelegation(input) {
|
|
|
10126
10130
|
spaceId: input.spaceId,
|
|
10127
10131
|
path: input.path,
|
|
10128
10132
|
actions: [...input.actions],
|
|
10133
|
+
...input.decryption === void 0 ? {} : {
|
|
10134
|
+
decryptionNetworkId: input.decryption.networkId,
|
|
10135
|
+
decryptionAction: input.decryption.action
|
|
10136
|
+
},
|
|
10129
10137
|
contentSourceDigest: input.contentSourceDigest,
|
|
10130
10138
|
expiresAt: input.expiresAt
|
|
10131
10139
|
};
|
|
@@ -10145,7 +10153,9 @@ function computeOwnerShareRegistrationCid(registration) {
|
|
|
10145
10153
|
function validateOwnerSharePolicyRegistration(value, expected) {
|
|
10146
10154
|
if (cid(expected.policy.bytes) !== expected.policy.cid) throw new Error("submitted owner-share policy bytes do not match its CID");
|
|
10147
10155
|
const root = assertObject(value, ["registration", "proof"], "owner-share registration response");
|
|
10148
|
-
const
|
|
10156
|
+
const rawRegistration = root.registration;
|
|
10157
|
+
const hasDecryption = typeof rawRegistration === "object" && rawRegistration !== null && Object.prototype.hasOwnProperty.call(rawRegistration, "decryption");
|
|
10158
|
+
const registration = assertObject(root.registration, ["registrationCid", "policyCid", "ownerDelegationCid", "enforcementDelegationCid", "ownerDid", "shareKeyDid", "enforcerDid", "shareId", "recipientMatcher", "target", "resource", "actions", ...hasDecryption ? ["decryption"] : [], "contentSource", "contentSourceDigest", "registeredAt", "expiresAt"], "owner-share registration");
|
|
10149
10159
|
const proof = assertObject(root.proof, ["alg", "kid", "signature"], "owner-share registration proof");
|
|
10150
10160
|
if (registration.policyCid !== expected.policy.cid || registration.ownerDelegationCid !== expected.ownerDelegation.delegationCid || registration.enforcementDelegationCid !== expected.enforcementDelegation.cid || registration.contentSourceDigest !== expected.contentSourceDigest) throw new Error("owner-share registration is not bound to the submitted chain");
|
|
10151
10161
|
let policyValue;
|
|
@@ -10161,7 +10171,9 @@ function validateOwnerSharePolicyRegistration(value, expected) {
|
|
|
10161
10171
|
const registrationTarget = registration.target;
|
|
10162
10172
|
const registrationResource = registration.resource;
|
|
10163
10173
|
const enforcementFacts = expected.enforcementDelegation.facts;
|
|
10164
|
-
|
|
10174
|
+
const decryption = policy.decryption;
|
|
10175
|
+
const decryptionMatches = decryption === void 0 ? registration.decryption === void 0 : registration.decryption !== void 0 && canonicalizeSignedObjectUnsigned(decryption) === canonicalizeSignedObjectUnsigned(registration.decryption);
|
|
10176
|
+
if (typeof target?.origin !== "string" || typeof target.nodeAudience !== "string" || typeof target.enforcerDid !== "string" || typeof target.spaceId !== "string" || typeof resource?.kind !== "string" || !["exact", "prefix"].includes(resource.kind) || typeof resource?.path !== "string" || !Array.isArray(policy.actions) || policy.ownerDid !== registration.ownerDid || policy.shareKeyDid !== registration.shareKeyDid || policy.shareId !== registration.shareId || canonicalizeSignedObjectUnsigned(policy.recipientMatcher) !== canonicalizeSignedObjectUnsigned(registration.recipientMatcher) || target.origin !== registrationTarget.origin || target.nodeAudience !== registrationTarget.nodeAudience || target.enforcerDid !== registration.enforcerDid || target.spaceId !== registrationTarget.spaceId || resource.kind !== registrationResource.kind || resource.path !== registrationResource.path || canonicalizeSignedObjectUnsigned(policy.actions) !== canonicalizeSignedObjectUnsigned(registration.actions) || !decryptionMatches || canonicalizeSignedObjectUnsigned(policy.contentSource) !== canonicalizeSignedObjectUnsigned(registration.contentSource) || policy.expiresAt !== registration.expiresAt || policy.contentSourceDigest !== registration.contentSourceDigest || enforcementFacts.ownerDelegationCid !== registration.ownerDelegationCid || enforcementFacts.policyCid !== registration.policyCid || enforcementFacts.shareKeyDid !== registration.shareKeyDid || enforcementFacts.enforcerDid !== registration.enforcerDid || enforcementFacts.nodeAudience !== registrationTarget.nodeAudience || enforcementFacts.spaceId !== registrationTarget.spaceId || enforcementFacts.path !== registrationResource.path || canonicalizeSignedObjectUnsigned(enforcementFacts.actions) !== canonicalizeSignedObjectUnsigned(registration.actions) || enforcementFacts.decryptionNetworkId !== decryption?.networkId || enforcementFacts.decryptionAction !== decryption?.action || enforcementFacts.expiresAt !== registration.expiresAt) throw new Error("owner-share registration is not bound to the canonical policy");
|
|
10165
10177
|
if (typeof registration.registrationCid !== "string" || typeof registration.expiresAt !== "string" || typeof registration.registeredAt !== "string") throw new Error("owner-share registration timestamps are invalid");
|
|
10166
10178
|
if (new Date(registration.expiresAt).toISOString() !== registration.expiresAt || Date.parse(registration.expiresAt) <= Date.now()) throw new Error("owner-share registration is expired or non-canonical");
|
|
10167
10179
|
const { registrationCid: _registrationCid, ...registrationCore } = registration;
|
|
@@ -12112,6 +12124,22 @@ var ACCOUNT_SPACES_PATH = "spaces/";
|
|
|
12112
12124
|
var AccountService = class {
|
|
12113
12125
|
constructor(config) {
|
|
12114
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();
|
|
12115
12143
|
this.applications = {
|
|
12116
12144
|
list: async (options2 = {}) => {
|
|
12117
12145
|
if (options2.preferIndex) {
|
|
@@ -12145,7 +12173,7 @@ var AccountService = class {
|
|
|
12145
12173
|
if (!loaded.ok) return accountErr(loaded.error);
|
|
12146
12174
|
return (0, import_sdk_services6.ok)(applicationFromRecord(key, loaded.data.data));
|
|
12147
12175
|
},
|
|
12148
|
-
register: async (manifest) => {
|
|
12176
|
+
register: async (manifest, options2 = {}) => {
|
|
12149
12177
|
const manifests = Array.isArray(manifest) ? manifest : [manifest];
|
|
12150
12178
|
const request = composeManifestRequest(manifests);
|
|
12151
12179
|
if (request.registryRecords.length === 0) {
|
|
@@ -12163,7 +12191,7 @@ var AccountService = class {
|
|
|
12163
12191
|
let registered;
|
|
12164
12192
|
for (const record of request.registryRecords) {
|
|
12165
12193
|
const manifestHash = hashJson(record.manifests);
|
|
12166
|
-
if (await this.indexHasApplicationHash(record.app_id, manifestHash)) {
|
|
12194
|
+
if (!options2.assumeUnregistered && await this.indexHasApplicationHash(record.app_id, manifestHash)) {
|
|
12167
12195
|
registered = {
|
|
12168
12196
|
appId: record.app_id,
|
|
12169
12197
|
manifests: record.manifests,
|
|
@@ -12344,6 +12372,7 @@ var AccountService = class {
|
|
|
12344
12372
|
const delegations = await this.delegations.list();
|
|
12345
12373
|
if (!delegations.ok) return delegations;
|
|
12346
12374
|
const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12375
|
+
this.invalidateAccountIndexMemo();
|
|
12347
12376
|
const schema = await this.ensureAccountIndex(dbResult.data);
|
|
12348
12377
|
if (!schema.ok) return schema;
|
|
12349
12378
|
const statements = [
|
|
@@ -12743,7 +12772,41 @@ var AccountService = class {
|
|
|
12743
12772
|
]);
|
|
12744
12773
|
});
|
|
12745
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
|
+
*/
|
|
12746
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) {
|
|
12747
12810
|
const migrated = await db.migrations.apply({
|
|
12748
12811
|
namespace: ACCOUNT_INDEX_NAMESPACE,
|
|
12749
12812
|
migrations: [
|