@tinycloud/sdk-services 3.8.0-beta.9 → 3.8.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.
Files changed (55) hide show
  1. package/dist/{BaseService-DO7vZ67k.d.ts → BaseService-Ma2MwJAd.d.ts} +1 -1
  2. package/dist/{BaseService-D-29xShG.d.cts → BaseService-O0iyuFLk.d.cts} +1 -1
  3. package/dist/IHooksService-4QNODVy6.d.cts +120 -0
  4. package/dist/IHooksService-CvTpJbQD.d.ts +120 -0
  5. package/dist/ISQLService-Aly1CBsW.d.ts +242 -0
  6. package/dist/ISQLService-BOgWzHLT.d.cts +242 -0
  7. package/dist/context.d.cts +1 -1
  8. package/dist/context.d.ts +1 -1
  9. package/dist/encryption/index.cjs +80 -7
  10. package/dist/encryption/index.cjs.map +1 -1
  11. package/dist/encryption/index.d.cts +23 -11
  12. package/dist/encryption/index.d.ts +23 -11
  13. package/dist/encryption/index.js +79 -7
  14. package/dist/encryption/index.js.map +1 -1
  15. package/dist/encryption/named-keys.cjs.map +1 -1
  16. package/dist/encryption/named-keys.d.cts +2 -2
  17. package/dist/encryption/named-keys.d.ts +2 -2
  18. package/dist/encryption/named-keys.js.map +1 -1
  19. package/dist/encryption/resource.cjs.map +1 -1
  20. package/dist/encryption/resource.js.map +1 -1
  21. package/dist/feed/index.cjs +1309 -0
  22. package/dist/feed/index.cjs.map +1 -0
  23. package/dist/feed/index.d.cts +238 -0
  24. package/dist/feed/index.d.ts +238 -0
  25. package/dist/feed/index.js +1258 -0
  26. package/dist/feed/index.js.map +1 -0
  27. package/dist/index.cjs +131 -15
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +16 -116
  30. package/dist/index.d.ts +16 -116
  31. package/dist/index.js +130 -15
  32. package/dist/index.js.map +1 -1
  33. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  34. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  35. package/dist/kv/index.cjs.map +1 -1
  36. package/dist/kv/index.d.cts +4 -4
  37. package/dist/kv/index.d.ts +4 -4
  38. package/dist/kv/index.js.map +1 -1
  39. package/dist/kv/replication/index.d.cts +3 -3
  40. package/dist/kv/replication/index.d.ts +3 -3
  41. package/dist/{scope-1-8skVDk.d.ts → scope-BZr0QwxX.d.ts} +1 -1
  42. package/dist/{scope-Dv3IAXMr.d.cts → scope-DEgc9f-W.d.cts} +1 -1
  43. package/dist/sql/index.cjs +21 -6
  44. package/dist/sql/index.cjs.map +1 -1
  45. package/dist/sql/index.d.cts +6 -236
  46. package/dist/sql/index.d.ts +6 -236
  47. package/dist/sql/index.js +21 -6
  48. package/dist/sql/index.js.map +1 -1
  49. package/dist/telemetry/requestLog.d.cts +1 -1
  50. package/dist/telemetry/requestLog.d.ts +1 -1
  51. package/dist/{types-BGRzS1ob.d.ts → types-Bf91gup6.d.ts} +7 -2
  52. package/dist/{types-DAOyIbXu.d.cts → types-CSd0RYir.d.cts} +1 -1
  53. package/dist/{types-DAOyIbXu.d.ts → types-CSd0RYir.d.ts} +1 -1
  54. package/dist/{types-B_rbS4XE.d.cts → types-D4XbIfFJ.d.cts} +7 -2
  55. package/package.json +6 -1
package/dist/index.js CHANGED
@@ -713,6 +713,26 @@ function parseNetworkId(networkId) {
713
713
  }
714
714
  return { networkId, ownerDid, name };
715
715
  }
716
+ function parseEncryptionResource(resource) {
717
+ const networkSeparator = resource.lastIndexOf(":");
718
+ const nameAndPath = resource.slice(networkSeparator + 1);
719
+ const pathSeparator = nameAndPath.indexOf("/");
720
+ const name = pathSeparator === -1 ? nameAndPath : nameAndPath.slice(0, pathSeparator);
721
+ const networkId = pathSeparator === -1 ? resource : `${resource.slice(0, networkSeparator + 1)}${name}`;
722
+ const parsed = parseNetworkId(networkId);
723
+ if (pathSeparator === -1) return parsed;
724
+ const keyPath = nameAndPath.slice(pathSeparator + 1);
725
+ const segments = keyPath.split("/");
726
+ const prefix = keyPath.endsWith("/");
727
+ if (keyPath.length === 0 || /[\u0000-\u001f\u007f\\]/.test(keyPath) || segments.some(
728
+ (segment, index) => segment === "." || segment === ".." || segment.length === 0 && !(prefix && index === segments.length - 1)
729
+ )) {
730
+ throw new NetworkIdError(
731
+ `encryption resource has an invalid keyPath (got ${JSON.stringify(resource)})`
732
+ );
733
+ }
734
+ return { ...parsed, keyPath };
735
+ }
716
736
  function buildNetworkId(ownerDid, name) {
717
737
  if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
718
738
  throw new NetworkIdError("ownerDid must be a DID");
@@ -3611,12 +3631,13 @@ var SQLService = class extends BaseService {
3611
3631
  request.signal
3612
3632
  );
3613
3633
  if (!response.ok) {
3614
- return await this.handleErrorResponse(response, "query", dbName, [action]);
3634
+ const failure = await this.handleErrorResponse(response, "query", dbName, [action]);
3635
+ return this.redactFailure(failure, options?.redactErrors, response.status);
3615
3636
  }
3616
3637
  const data = await response.json();
3617
3638
  return ok(data);
3618
3639
  } catch (error) {
3619
- return err(wrapError("sql", error));
3640
+ return this.redactFailure(err(wrapError("sql", error)), options?.redactErrors);
3620
3641
  } finally {
3621
3642
  request.dispose();
3622
3643
  }
@@ -3651,12 +3672,13 @@ var SQLService = class extends BaseService {
3651
3672
  request.signal
3652
3673
  );
3653
3674
  if (!response.ok) {
3654
- return await this.handleErrorResponse(response, "execute", dbName, requestedActions);
3675
+ const failure = await this.handleErrorResponse(response, "execute", dbName, requestedActions);
3676
+ return this.redactFailure(failure, options?.redactErrors, response.status);
3655
3677
  }
3656
3678
  const data = await response.json();
3657
3679
  return ok(data);
3658
3680
  } catch (error) {
3659
- return err(wrapError("sql", error));
3681
+ return this.redactFailure(err(wrapError("sql", error)), options?.redactErrors);
3660
3682
  } finally {
3661
3683
  request.dispose();
3662
3684
  }
@@ -3677,12 +3699,13 @@ var SQLService = class extends BaseService {
3677
3699
  request.signal
3678
3700
  );
3679
3701
  if (!response.ok) {
3680
- return await this.handleErrorResponse(response, "batch", dbName, requestedActions);
3702
+ const failure = await this.handleErrorResponse(response, "batch", dbName, requestedActions);
3703
+ return this.redactFailure(failure, options?.redactErrors, response.status);
3681
3704
  }
3682
3705
  const data = await response.json();
3683
3706
  return ok(data);
3684
3707
  } catch (error) {
3685
- return err(wrapError("sql", error));
3708
+ return this.redactFailure(err(wrapError("sql", error)), options?.redactErrors);
3686
3709
  } finally {
3687
3710
  request.dispose();
3688
3711
  }
@@ -3878,6 +3901,18 @@ var SQLService = class extends BaseService {
3878
3901
  }))
3879
3902
  );
3880
3903
  }
3904
+ redactFailure(result, redactErrors, status) {
3905
+ if (!redactErrors || result.ok) return result;
3906
+ const candidate = result.error.code;
3907
+ const code = typeof candidate === "string" && Object.prototype.hasOwnProperty.call(ErrorCodes, candidate) ? candidate : ErrorCodes.SQL_ERROR;
3908
+ const safeStatus = typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599 ? status : void 0;
3909
+ return err(serviceError(
3910
+ code,
3911
+ "SQL operation failed",
3912
+ "sql",
3913
+ safeStatus === void 0 ? void 0 : { meta: { status: safeStatus } }
3914
+ ));
3915
+ }
3881
3916
  async handleErrorResponse(response, operation, dbName, requestedActions) {
3882
3917
  const errorText = await response.text();
3883
3918
  const meta = responseErrorMeta(response.status, response.statusText, errorText);
@@ -4475,6 +4510,7 @@ var HooksService = class extends BaseService {
4475
4510
  this._supervising = false;
4476
4511
  /** Bumped on every session change; part of the plan key. */
4477
4512
  this._sessionGeneration = 0;
4513
+ this._streamGeneration = 0;
4478
4514
  this._config = config;
4479
4515
  }
4480
4516
  get config() {
@@ -4495,6 +4531,7 @@ var HooksService = class extends BaseService {
4495
4531
  requested,
4496
4532
  signatures: requested.map(subscriptionSignature),
4497
4533
  ttlSeconds: options.ttlSeconds,
4534
+ onReady: options.onReady,
4498
4535
  queue: new AsyncQueue()
4499
4536
  };
4500
4537
  const leave = () => {
@@ -4509,6 +4546,7 @@ var HooksService = class extends BaseService {
4509
4546
  signal?.addEventListener("abort", leave, { once: true });
4510
4547
  this._subscribers.add(subscriber);
4511
4548
  this.wake();
4549
+ if (this._readyStream) this.notifyReady(subscriber, this._readyStream);
4512
4550
  try {
4513
4551
  for await (const event of subscriber.queue) {
4514
4552
  yield event;
@@ -4741,6 +4779,7 @@ var HooksService = class extends BaseService {
4741
4779
  async attempt(plan, signal, health) {
4742
4780
  let phase = "mint";
4743
4781
  let request;
4782
+ const generation = ++this._streamGeneration;
4744
4783
  const owns = () => !signal.aborted && !request?.signal.aborted && this.plan()?.key === plan.key;
4745
4784
  try {
4746
4785
  const context = this.context;
@@ -4805,6 +4844,14 @@ var HooksService = class extends BaseService {
4805
4844
  if (next.done) break;
4806
4845
  const message = next.value;
4807
4846
  if (!message.data) continue;
4847
+ if (message.event === "ready") {
4848
+ const marker = JSON.parse(message.data);
4849
+ if (marker !== null && typeof marker === "object" && "type" in marker && marker.type === "ready") {
4850
+ this._readyStream = { key: plan.key, generation };
4851
+ for (const subscriber of this._subscribers) this.notifyReady(subscriber, this._readyStream);
4852
+ }
4853
+ continue;
4854
+ }
4808
4855
  const event = parseHookEvent(message);
4809
4856
  if (!owns()) return { kind: "stopped" };
4810
4857
  this.dispatch(event);
@@ -4817,6 +4864,8 @@ var HooksService = class extends BaseService {
4817
4864
  return owns() ? { kind: "ended" } : { kind: "stopped" };
4818
4865
  } catch (thrown) {
4819
4866
  return request?.signal.aborted || thrown === SUPERSEDED ? { kind: "stopped" } : { kind: "failed", error: hookStreamFailure(phase, thrown) };
4867
+ } finally {
4868
+ if (this._readyStream?.generation === generation) this._readyStream = void 0;
4820
4869
  }
4821
4870
  }
4822
4871
  /**
@@ -4872,6 +4921,15 @@ var HooksService = class extends BaseService {
4872
4921
  ttlSeconds
4873
4922
  };
4874
4923
  }
4924
+ notifyReady(subscriber, ready) {
4925
+ if (!this._subscribers.has(subscriber) || subscriber.readyGeneration === ready.generation || this.plan()?.key !== ready.key || this._phase?.interrupt.signal.aborted) return;
4926
+ subscriber.readyGeneration = ready.generation;
4927
+ try {
4928
+ subscriber.onReady?.();
4929
+ } catch {
4930
+ this.report(serviceError(ErrorCodes.INVALID_INPUT, "Hook readiness callback failed", "hooks"));
4931
+ }
4932
+ }
4875
4933
  dispatch(event) {
4876
4934
  for (const subscriber of this._subscribers) {
4877
4935
  if (matchesAnySubscription(event, subscriber.requested)) {
@@ -5106,7 +5164,7 @@ function abortPromise(signal) {
5106
5164
  function normalizeSubscription(subscription) {
5107
5165
  return {
5108
5166
  ...subscription,
5109
- pathPrefix: normalizePathPrefix(subscription.pathPrefix),
5167
+ pathPrefix: subscription.service === "kv" ? normalizePathPrefix(subscription.pathPrefix) : subscription.pathPrefix?.replace(/^\/+/, "") || void 0,
5110
5168
  abilities: subscription.abilities ? [...subscription.abilities] : []
5111
5169
  };
5112
5170
  }
@@ -5130,7 +5188,11 @@ function matchesSubscription(event, subscription) {
5130
5188
  if (event.service !== subscription.service) {
5131
5189
  return false;
5132
5190
  }
5133
- if (subscription.pathPrefix) {
5191
+ if (event.service === "sql" || event.service === "duckdb") {
5192
+ const scope = subscription.pathPrefix;
5193
+ const database = event.databasePath ?? event.path;
5194
+ if (scope && scope !== "/" && (!database || (scope.endsWith("/") ? !database.startsWith(scope) : database !== scope))) return false;
5195
+ } else if (subscription.pathPrefix) {
5134
5196
  const prefix = subscription.pathPrefix.endsWith("/") ? subscription.pathPrefix : `${subscription.pathPrefix}/`;
5135
5197
  if (event.path && event.path !== subscription.pathPrefix && !event.path.startsWith(prefix)) {
5136
5198
  return false;
@@ -5290,6 +5352,7 @@ function parseHookEvent(message) {
5290
5352
  service: parsed.service ?? "",
5291
5353
  ability: parsed.ability ?? "",
5292
5354
  path: parsed.path,
5355
+ databasePath: typeof parsed.databasePath === "string" ? parsed.databasePath : void 0,
5293
5356
  actor: parsed.actor ?? "",
5294
5357
  epoch: parsed.epoch ?? "",
5295
5358
  eventIndex: parsed.eventIndex ?? 0,
@@ -7810,7 +7873,12 @@ function ensureNetworkUsableForDecrypt(descriptor) {
7810
7873
 
7811
7874
  // src/encryption/envelope.ts
7812
7875
  function encryptToNetwork(crypto2, input) {
7813
- parseNetworkId(input.networkId);
7876
+ const parsedResource = parseEncryptionResource(
7877
+ input.keyPath === void 0 ? input.networkId : `${input.networkId}/${input.keyPath}`
7878
+ );
7879
+ if (parsedResource.keyPath !== input.keyPath || input.keyPath?.endsWith("/")) {
7880
+ throw new NetworkIdError("keyPath must name an exact encryption key");
7881
+ }
7814
7882
  const alg = input.alg ?? DEFAULT_ENCRYPTION_ALG;
7815
7883
  const keyVersion = input.keyVersion ?? DEFAULT_KEY_VERSION;
7816
7884
  const symmetricKey = crypto2.randomBytes(32);
@@ -7824,6 +7892,7 @@ function encryptToNetwork(crypto2, input) {
7824
7892
  const envelope = {
7825
7893
  v: ENVELOPE_VERSION,
7826
7894
  networkId: input.networkId,
7895
+ ...input.keyPath !== void 0 ? { keyPath: input.keyPath } : {},
7827
7896
  alg,
7828
7897
  keyVersion,
7829
7898
  encryptedSymmetricKey,
@@ -7855,13 +7924,24 @@ function validateEnvelope(crypto2, envelope) {
7855
7924
  };
7856
7925
  }
7857
7926
  try {
7858
- parseNetworkId(e.networkId);
7927
+ if (parseEncryptionResource(e.networkId).keyPath !== void 0) {
7928
+ throw new Error("networkId must identify the network root");
7929
+ }
7930
+ if (e.keyPath !== void 0) {
7931
+ if (typeof e.keyPath !== "string") {
7932
+ throw new Error("keyPath must be a string");
7933
+ }
7934
+ const parsed = parseEncryptionResource(`${e.networkId}/${e.keyPath}`);
7935
+ if (parsed.keyPath !== e.keyPath || e.keyPath.endsWith("/")) {
7936
+ throw new Error("keyPath must name an exact encryption key");
7937
+ }
7938
+ }
7859
7939
  } catch (err3) {
7860
7940
  return {
7861
7941
  ok: false,
7862
7942
  error: encryptionError({
7863
7943
  code: "INVALID_ENVELOPE",
7864
- message: `envelope.networkId is malformed: ${err3 instanceof Error ? err3.message : String(err3)}`
7944
+ message: `envelope encryption resource is malformed: ${err3 instanceof Error ? err3.message : String(err3)}`
7865
7945
  })
7866
7946
  };
7867
7947
  }
@@ -7934,6 +8014,7 @@ function buildDecryptFacts(input) {
7934
8014
  type: DECRYPT_FACT_TYPE,
7935
8015
  targetNode: input.body.targetNode,
7936
8016
  networkId: input.body.networkId,
8017
+ ...input.body.keyPath !== void 0 ? { keyPath: input.body.keyPath } : {},
7937
8018
  bodyHash,
7938
8019
  encryptedSymmetricKeyHash: input.encryptedSymmetricKeyHash,
7939
8020
  receiverPublicKeyHash,
@@ -7941,10 +8022,14 @@ function buildDecryptFacts(input) {
7941
8022
  keyVersion: input.body.keyVersion
7942
8023
  };
7943
8024
  }
7944
- function buildDecryptAttenuation(networkId) {
7945
- parseNetworkId(networkId);
8025
+ function buildDecryptAttenuation(networkId, keyPath) {
8026
+ const resource = keyPath === void 0 ? networkId : `${networkId}/${keyPath}`;
8027
+ const parsed = parseEncryptionResource(resource);
8028
+ if (parsed.networkId !== networkId || parsed.keyPath !== keyPath || keyPath?.endsWith("/")) {
8029
+ throw new NetworkIdError("keyPath must name an exact encryption key");
8030
+ }
7946
8031
  return {
7947
- [networkId]: {
8032
+ [resource]: {
7948
8033
  [DECRYPT_ACTION]: {}
7949
8034
  }
7950
8035
  };
@@ -8004,6 +8089,15 @@ function checkDecryptInvocationInput(crypto2, input) {
8004
8089
  })
8005
8090
  };
8006
8091
  }
8092
+ if (input.facts.keyPath !== input.body.keyPath) {
8093
+ return {
8094
+ ok: false,
8095
+ error: encryptionError({
8096
+ code: "INVALID_INPUT",
8097
+ message: "facts.keyPath must equal body.keyPath"
8098
+ })
8099
+ };
8100
+ }
8007
8101
  if (input.facts.alg !== input.body.alg) {
8008
8102
  return {
8009
8103
  ok: false,
@@ -8041,7 +8135,9 @@ function checkDecryptInvocationInput(crypto2, input) {
8041
8135
  };
8042
8136
  }
8043
8137
  try {
8044
- parseNetworkId(input.networkId);
8138
+ if (parseEncryptionResource(input.networkId).keyPath !== void 0) {
8139
+ throw new NetworkIdError("networkId must identify the network root");
8140
+ }
8045
8141
  } catch (err3) {
8046
8142
  return {
8047
8143
  ok: false,
@@ -8051,6 +8147,22 @@ function checkDecryptInvocationInput(crypto2, input) {
8051
8147
  })
8052
8148
  };
8053
8149
  }
8150
+ try {
8151
+ const resource = parseEncryptionResource(
8152
+ input.body.keyPath === void 0 ? input.body.networkId : `${input.body.networkId}/${input.body.keyPath}`
8153
+ );
8154
+ if (resource.keyPath !== input.body.keyPath || input.body.keyPath?.endsWith("/")) {
8155
+ throw new NetworkIdError("keyPath must name an exact encryption key");
8156
+ }
8157
+ } catch (err3) {
8158
+ return {
8159
+ ok: false,
8160
+ error: encryptionError({
8161
+ code: "INVALID_INPUT",
8162
+ message: err3 instanceof Error ? err3.message : String(err3)
8163
+ })
8164
+ };
8165
+ }
8054
8166
  const canonicalBody = canonicalize(
8055
8167
  input.body
8056
8168
  );
@@ -8291,6 +8403,7 @@ var EncryptionService = class extends BaseService {
8291
8403
  // that spelling in the envelope so a later response can be compared
8292
8404
  // byte-for-byte (notably for checksummed did:pkh principals).
8293
8405
  networkId: descriptor.networkId,
8406
+ ...options?.keyPath !== void 0 ? { keyPath: options.keyPath } : {},
8294
8407
  networkPublicKey,
8295
8408
  plaintext,
8296
8409
  ...options?.aad !== void 0 ? { aad: options.aad } : {},
@@ -8350,6 +8463,7 @@ var EncryptionService = class extends BaseService {
8350
8463
  type: DECRYPT_FACT_TYPE,
8351
8464
  targetNode,
8352
8465
  networkId: envelope.networkId,
8466
+ ...envelope.keyPath !== void 0 ? { keyPath: envelope.keyPath } : {},
8353
8467
  alg: envelope.alg,
8354
8468
  keyVersion: envelope.keyVersion,
8355
8469
  encryptedSymmetricKey: envelope.encryptedSymmetricKey,
@@ -8565,6 +8679,7 @@ export {
8565
8679
  openWrappedKey,
8566
8680
  parseAuthError,
8567
8681
  parseCapabilityResource,
8682
+ parseEncryptionResource,
8568
8683
  parseNetworkId,
8569
8684
  parsePermissionHint,
8570
8685
  parsePermissionHintFromErrorText,