@supacloud/lite 0.33.0 → 0.33.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.33.2](https://github.com/vibeunion/supacloud/compare/supacloud-lite-v0.33.1...supacloud-lite-v0.33.2) (2026-10-25)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **lite:** preserve shared realtime presence sessions ([#2242](https://github.com/vibeunion/supacloud/issues/2242)) ([b6cff5a](https://github.com/vibeunion/supacloud/commit/b6cff5a4117a093fb7c75034bf09575eddb74bd8))
9
+
10
+
11
+ ### Miscellaneous Chores
12
+
13
+ * **deps:** refresh the Elysia runtime and dependency matrix ([#2243](https://github.com/vibeunion/supacloud/issues/2243)) ([917ff0f](https://github.com/vibeunion/supacloud/commit/917ff0fa81e3c47d86d18e8c58fff79c0f0f67f5))
14
+
15
+ ## [0.33.1](https://github.com/vibeunion/supacloud/compare/supacloud-lite-v0.33.0...supacloud-lite-v0.33.1) (2026-10-19)
16
+
17
+
18
+ ### Miscellaneous Chores
19
+
20
+ * **lite:** restore Supabase SDK 2.118.0 ([#2161](https://github.com/vibeunion/supacloud/issues/2161)) ([0c7d4d0](https://github.com/vibeunion/supacloud/commit/0c7d4d08ed10cce4adcf5d30c9e7e473bc7f492a))
21
+
3
22
  ## [0.33.0](https://github.com/vibeunion/supacloud/compare/supacloud-lite-v0.32.0...supacloud-lite-v0.33.0) (2026-10-19)
4
23
 
5
24
 
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { dirname as dirname8, join as join10, resolve as resolve6 } from "path";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@supacloud/lite",
11
- version: "0.33.0",
11
+ version: "0.33.2",
12
12
  description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
13
13
  type: "module",
14
14
  license: "Apache-2.0",
@@ -47,7 +47,7 @@ var package_default = {
47
47
  clean: "bun -e 'import { $ } from \"bun\"; await $`rm -rf dist`'",
48
48
  dev: "bun run src/cli.ts start",
49
49
  start: "bun run src/cli.ts start",
50
- test: "bun test --timeout 20000",
50
+ test: "bun run scripts/run-tests.ts",
51
51
  "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts test/qauth-workflow-matrix.test.ts test/maker-checker-workflow.test.ts test/commands-artifacts.test.ts --timeout 180000",
52
52
  "test:graphql": "SUPACLOUD_LITE_TEST_GRAPHQL=1 bun test test/graphql-native.test.ts --timeout 180000",
53
53
  benchmark: "bun run scripts/benchmark.ts",
@@ -67,31 +67,31 @@ var package_default = {
67
67
  "@electric-sql/pglite-pgmq": "0.0.8",
68
68
  "@electric-sql/pglite-pgvector": "0.0.11",
69
69
  "@electric-sql/pglite-postgis": "0.2.10",
70
- "@supacloud/db": "0.20.0",
70
+ "@supacloud/db": "0.20.1",
71
71
  "exact-mirror": "1.2.6",
72
72
  tar: "^7.5.22",
73
- typebox: "1.3.37"
73
+ typebox: "1.4.2"
74
74
  },
75
75
  devDependencies: {
76
- "@supacloud/app": "0.30.0",
77
- "@supacloud/contracts": "0.13.0",
78
- "@supacloud/compiler": "0.49.0",
79
- "@supacloud/elysia": "0.35.0",
80
- "@supabase/supabase-js": "^2.117.3",
76
+ "@supacloud/app": "0.33.0",
77
+ "@supacloud/contracts": "0.15.1",
78
+ "@supacloud/compiler": "0.51.1",
79
+ "@supacloud/elysia": "0.36.1",
80
+ "@supabase/supabase-js": "^2.118.0",
81
81
  "@types/bun": "^1.4.3",
82
82
  effect: "4.0.5",
83
- elysia: "2.0.0-beta.32",
84
- jose: "^6.2.12",
83
+ elysia: "2.0.0-rc.0",
84
+ jose: "^6.2.13",
85
85
  typescript: "^7.0.2"
86
86
  },
87
87
  overrides: {
88
- "@supacloud/db": "0.20.0",
89
- "@supacloud/app": "0.30.0",
90
- "@supacloud/compiler": "0.49.0",
91
- "@supacloud/elysia": "0.35.0",
92
- "@supacloud/contracts": "0.13.0",
93
- "@supacloud/commands": "0.11.0",
94
- "@supacloud/delivery": "0.21.0",
88
+ "@supacloud/db": "0.20.1",
89
+ "@supacloud/app": "0.33.0",
90
+ "@supacloud/compiler": "0.51.1",
91
+ "@supacloud/elysia": "0.36.1",
92
+ "@supacloud/contracts": "0.15.1",
93
+ "@supacloud/commands": "0.11.1",
94
+ "@supacloud/delivery": "0.21.1",
95
95
  "@graphql-tools/utils": "^12.0.4"
96
96
  },
97
97
  engines: {
@@ -245,7 +245,7 @@ function isAllowedRedirect(requested, siteUrl, allowList = []) {
245
245
  var encoder2 = new TextEncoder;
246
246
  var decoder2 = new TextDecoder;
247
247
  var strictDecoder = new TextDecoder("utf-8", { fatal: true });
248
- var MAX_INT32 = 2 ** 32;
248
+ var UINT32_RANGE = 2 ** 32;
249
249
  function concat(...buffers) {
250
250
  const size = buffers.reduce((acc, { length }) => acc + length, 0), buf = new Uint8Array(size);
251
251
  let i = 0;
@@ -404,23 +404,23 @@ function isDisjoint(...headers) {
404
404
  function decodeBase64url(value, label, ErrorClass) {
405
405
  try {
406
406
  return decode(value);
407
- } catch {
408
- throw new ErrorClass(`Failed to base64url decode the ${label}`);
407
+ } catch (cause) {
408
+ throw new ErrorClass(`Failed to base64url decode the ${label}`, { cause });
409
409
  }
410
410
  }
411
- function encodeBase64url(value, label, ErrorClass) {
411
+ function encodeAsciiMember(value, label, ErrorClass) {
412
412
  try {
413
413
  return encode(value);
414
- } catch {
415
- throw new ErrorClass(`The ${label} is not a valid base64url string`);
414
+ } catch (cause) {
415
+ throw new ErrorClass(`The ${label} is not a valid base64url string`, { cause });
416
416
  }
417
417
  }
418
- function parseJoseHeader(b64, ErrorClass, message) {
418
+ function parseJoseHeader(encodedProtectedHeader, ErrorClass, message) {
419
419
  let parsed;
420
420
  try {
421
- parsed = JSON.parse(strictDecoder.decode(decode(b64)));
422
- } catch {
423
- throw new ErrorClass(message);
421
+ parsed = JSON.parse(strictDecoder.decode(decode(encodedProtectedHeader)));
422
+ } catch (cause) {
423
+ throw new ErrorClass(message, { cause });
424
424
  }
425
425
  if (!isObject(parsed))
426
426
  throw new ErrorClass(message);
@@ -478,15 +478,15 @@ var jwkMatchesOp = (entry, key, usage) => {
478
478
  }
479
479
  };
480
480
  async function prepareKey(entry, key, usage) {
481
- const { alg, secret } = entry, privateKey = usage === "decrypt" || usage === "sign";
481
+ const { alg, secret } = entry, requiresPrivateKey = usage === "decrypt" || usage === "sign";
482
482
  if (secret && key instanceof Uint8Array)
483
483
  return key;
484
484
  let normalized, keyObject;
485
485
  if (isObject(key)) {
486
486
  if (normalized = normalizeJwk(key), typeof normalized.kty != "string")
487
487
  throw invalidKeyType(alg, key, secret);
488
- if (!(secret ? normalized.kty === "oct" && typeof normalized.k == "string" : normalized.kty !== "oct" && (privateKey ? normalized.kty === "AKP" && typeof normalized.priv == "string" || typeof normalized.d == "string" : normalized.d === undefined && normalized.priv === undefined)))
489
- throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present' : `JSON Web Key for this operation must be a ${privateKey ? "private" : "public"} JWK`);
488
+ if (!(secret ? normalized.kty === "oct" && typeof normalized.k == "string" : normalized.kty !== "oct" && (requiresPrivateKey ? normalized.kty === "AKP" && typeof normalized.priv == "string" || typeof normalized.d == "string" : normalized.d === undefined && normalized.priv === undefined)))
489
+ throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present' : `JSON Web Key for this operation must be a ${requiresPrivateKey ? "private" : "public"} JWK`);
490
490
  if (jwkMatchesOp(entry, normalized, usage), normalized.kty === "oct")
491
491
  return decode(normalized.k);
492
492
  if (!Object.isFrozen(key)) {
@@ -496,7 +496,7 @@ async function prepareKey(entry, key, usage) {
496
496
  } else {
497
497
  if (!isKeyLike(key))
498
498
  throw invalidKeyType(alg, key, secret);
499
- const expectedType = secret ? "secret" : privateKey ? "private" : "public";
499
+ const expectedType = secret ? "secret" : requiresPrivateKey ? "private" : "public";
500
500
  if (key.type !== expectedType && (secret || ["secret", "public", "private"].includes(key.type)))
501
501
  throw new TypeError(`${tag(key)} instances must be of type "${expectedType}" for the ${alg} algorithm`);
502
502
  if (isCryptoKey(key))
@@ -635,8 +635,8 @@ function eddsa() {
635
635
  usages: sig
636
636
  };
637
637
  }
638
- function mldsa(bits) {
639
- const subtle = { name: `ML-DSA-${bits}` };
638
+ function mldsa(parameterSet) {
639
+ const subtle = { name: `ML-DSA-${parameterSet}` };
640
640
  return {
641
641
  kty: ["AKP"],
642
642
  subtle,
@@ -674,21 +674,23 @@ function jwsAlgorithm(alg) {
674
674
  function prepareVerify(options) {
675
675
  return [options && validateAlgorithms("algorithms", options.algorithms), options?.crit];
676
676
  }
677
- function parseProtectedHeader(encodedProtected) {
678
- return encodedProtected === undefined ? {} : parseJoseHeader(encodedProtected, JWSInvalid, "JWS Protected Header is invalid");
677
+ function parseProtectedHeader(encodedProtectedHeader) {
678
+ return encodedProtectedHeader === undefined ? {} : parseJoseHeader(encodedProtectedHeader, JWSInvalid, "JWS Protected Header is invalid");
679
679
  }
680
680
  function encodeCompactUnencodedPayload(payload) {
681
681
  try {
682
682
  return encode(payload);
683
- } catch {
684
- throw new JWSInvalid("JWS Compact Serialization payload must use only ASCII characters");
683
+ } catch (cause) {
684
+ throw new JWSInvalid("JWS Compact Serialization payload must use only ASCII characters", {
685
+ cause
686
+ });
685
687
  }
686
688
  }
687
689
  async function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedProtected) {
688
- const { protected: encodedProtected, header, payload: inputPayload } = jws, parsedProt = parsedProtected ?? parseProtectedHeader(encodedProtected);
689
- if (!isDisjoint(parsedProt, header))
690
+ const { protected: encodedProtectedHeader, header, payload: inputPayload } = jws, protectedHeader = parsedProtected ?? parseProtectedHeader(encodedProtectedHeader);
691
+ if (!isDisjoint(protectedHeader, header))
690
692
  throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
691
- const joseHeader = { ...parsedProt, ...header }, b64 = validateB64(parsedProt, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader)), { alg } = joseHeader;
693
+ const joseHeader = { ...protectedHeader, ...header }, b64 = validateB64(protectedHeader, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], protectedHeader, joseHeader)), { alg } = joseHeader;
692
694
  if (typeof alg != "string" || !alg)
693
695
  throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
694
696
  if (shared[0] && !shared[0].has(alg))
@@ -700,17 +702,19 @@ async function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedP
700
702
  throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance");
701
703
  const signingPayload = b64 || typeof inputPayload != "string" ? inputPayload : encodeUnencodedPayload(inputPayload);
702
704
  let resolvedKey = false;
703
- typeof key == "function" && (key = await key(parsedProt, jws), resolvedKey = true);
704
- const entry = jwsAlgorithm(alg), data = concat(encodedProtected !== undefined ? encode(encodedProtected) : new Uint8Array, encode("."), typeof signingPayload == "string" ? shared[2] ??= encodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload), signature = decodeBase64url(jws.signature, "signature", JWSInvalid), k = await prepareKey(entry, key, "verify"), cryptoKey = await rawKey(k, entry.subtle, "verify");
705
+ typeof key == "function" && (key = await key(protectedHeader, jws), resolvedKey = true);
706
+ const entry = jwsAlgorithm(alg), signingInput = concat(encodedProtectedHeader !== undefined ? encode(encodedProtectedHeader) : new Uint8Array, encode("."), typeof signingPayload == "string" ? shared[2] ??= encodeAsciiMember(signingPayload, "payload", JWSInvalid) : signingPayload), signature = decodeBase64url(jws.signature, "signature", JWSInvalid), verificationKey = await prepareKey(entry, key, "verify"), cryptoKey = await rawKey(verificationKey, entry.subtle, "verify");
705
707
  entry.minRsaBits && checkModulusLength(entry.alg, cryptoKey);
706
708
  let verified = false;
707
709
  try {
708
- verified = await crypto.subtle.verify(entry.signing, cryptoKey, signature, data);
709
- } catch {}
710
+ verified = await crypto.subtle.verify(entry.signing, cryptoKey, signature, signingInput);
711
+ } catch (cause) {
712
+ throw new JWSSignatureVerificationFailed(undefined, { cause });
713
+ }
710
714
  if (!verified)
711
715
  throw new JWSSignatureVerificationFailed;
712
716
  const result = { payload: typeof signingPayload == "string" ? decodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload };
713
- return encodedProtected !== undefined && (result.protectedHeader = parsedProt), header !== undefined && (result.unprotectedHeader = header), resolvedKey ? [{ ...result, key: k }, b64] : [result, b64];
717
+ return encodedProtectedHeader !== undefined && (result.protectedHeader = protectedHeader), header !== undefined && (result.unprotectedHeader = header), resolvedKey ? [{ ...result, key: verificationKey }, b64] : [result, b64];
714
718
  }
715
719
  async function verifyCompact(jws, shared, key) {
716
720
  if (jws instanceof Uint8Array && (jws = decoder2.decode(jws)), typeof jws != "string")
@@ -740,8 +744,8 @@ function secs(str) {
740
744
  typeof str != "string" && invalidDuration();
741
745
  const matched = REGEX.exec(str);
742
746
  (!matched || matched[4] && matched[1]) && invalidDuration();
743
- const value = parseFloat(matched[2]), numericDate2 = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
744
- return Number.isFinite(numericDate2) || invalidDuration(), matched[1] === "-" || matched[4] === "ago" ? -numericDate2 : numericDate2;
747
+ const value = parseFloat(matched[2]), durationSeconds = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
748
+ return Number.isFinite(durationSeconds) || invalidDuration(), matched[1] === "-" || matched[4] === "ago" ? -durationSeconds : durationSeconds;
745
749
  }
746
750
  function validateInput(label, input) {
747
751
  if (!Number.isFinite(input))
@@ -753,33 +757,35 @@ var normalizeTyp = (value) => {
753
757
  return value.includes("/") ? normalized : `application/${normalized}`;
754
758
  };
755
759
  var checkAudiencePresence = (audPayload, audOption) => typeof audPayload == "string" ? audOption.includes(audPayload) : Array.isArray(audPayload) ? audOption.some((aud) => audPayload.includes(aud)) : false;
756
- function validateNumericDate(payload, claim, required = false) {
757
- const value = payload[claim];
760
+ function validateNumericDate(claimsSet, claim, required = false) {
761
+ const value = claimsSet[claim];
758
762
  if (!(value === undefined && !required)) {
759
763
  if (typeof value != "number")
760
- throw new JWTClaimValidationFailed(`"${claim}" claim must be a number`, payload, claim, "invalid");
764
+ throw new JWTClaimValidationFailed(`"${claim}" claim must be a number`, claimsSet, claim, "invalid");
761
765
  return value;
762
766
  }
763
767
  }
764
- function unexpectedClaim(payload, claim) {
765
- throw new JWTClaimValidationFailed(`unexpected "${claim}" claim value`, payload, claim, checkFailed);
768
+ function unexpectedClaim(claimsSet, claim) {
769
+ throw new JWTClaimValidationFailed(`unexpected "${claim}" claim value`, claimsSet, claim, checkFailed);
766
770
  }
767
- function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
768
- let payload;
771
+ function validateClaimsSet(protectedHeader, claimsSetBytes, options = {}) {
772
+ let claimsSet;
769
773
  try {
770
- payload = JSON.parse(strictDecoder.decode(encodedPayload));
771
- } catch {}
772
- if (!isObject(payload))
774
+ claimsSet = JSON.parse(strictDecoder.decode(claimsSetBytes));
775
+ } catch (cause) {
776
+ throw new JWTInvalid("JWT Claims Set must be a top-level JSON object", { cause });
777
+ }
778
+ if (!isObject(claimsSet))
773
779
  throw new JWTInvalid("JWT Claims Set must be a top-level JSON object");
774
780
  const { typ } = options;
775
781
  if (typ !== undefined && (typeof protectedHeader.typ != "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ)))
776
- throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, "typ", checkFailed);
782
+ throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', claimsSet, "typ", checkFailed);
777
783
  const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options, presenceCheck = [...requiredClaims];
778
784
  maxTokenAge !== undefined && presenceCheck.push("iat"), audience !== undefined && presenceCheck.push("aud"), subject !== undefined && presenceCheck.push("sub"), issuer !== undefined && presenceCheck.push("iss");
779
785
  for (const claim of new Set(presenceCheck.reverse()))
780
- if (!Object.hasOwn(payload, claim))
781
- throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, "missing");
782
- issuer !== undefined && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss) && unexpectedClaim(payload, "iss"), subject !== undefined && payload.sub !== subject && unexpectedClaim(payload, "sub"), audience !== undefined && !checkAudiencePresence(payload.aud, typeof audience == "string" ? [audience] : audience) && unexpectedClaim(payload, "aud");
786
+ if (!Object.hasOwn(claimsSet, claim))
787
+ throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, claimsSet, claim, "missing");
788
+ issuer !== undefined && !(Array.isArray(issuer) ? issuer : [issuer]).includes(claimsSet.iss) && unexpectedClaim(claimsSet, "iss"), subject !== undefined && claimsSet.sub !== subject && unexpectedClaim(claimsSet, "sub"), audience !== undefined && !checkAudiencePresence(claimsSet.aud, typeof audience == "string" ? [audience] : audience) && unexpectedClaim(claimsSet, "aud");
783
789
  const { clockTolerance } = options;
784
790
  let tolerance = 0;
785
791
  if (typeof clockTolerance == "string")
@@ -790,20 +796,20 @@ function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
790
796
  tolerance = clockTolerance;
791
797
  }
792
798
  validateInput("clockTolerance option", tolerance);
793
- const { currentDate } = options, now = validateInput("currentDate option", epoch(currentDate === undefined ? /* @__PURE__ */ new Date : currentDate)), iat = validateNumericDate(payload, "iat", maxTokenAge !== undefined), nbf = validateNumericDate(payload, "nbf");
799
+ const { currentDate } = options, now = validateInput("currentDate option", epoch(currentDate === undefined ? /* @__PURE__ */ new Date : currentDate)), iat = validateNumericDate(claimsSet, "iat", maxTokenAge !== undefined), nbf = validateNumericDate(claimsSet, "nbf");
794
800
  if (nbf !== undefined && nbf > now + tolerance)
795
- throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, "nbf", checkFailed);
796
- const exp = validateNumericDate(payload, "exp");
801
+ throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', claimsSet, "nbf", checkFailed);
802
+ const exp = validateNumericDate(claimsSet, "exp");
797
803
  if (exp !== undefined && exp <= now - tolerance)
798
- throw new JWTExpired('"exp" claim timestamp check failed', payload, "exp", checkFailed);
804
+ throw new JWTExpired('"exp" claim timestamp check failed', claimsSet, "exp", checkFailed);
799
805
  if (maxTokenAge !== undefined) {
800
806
  const age = now - iat, max = validateInput("maxTokenAge option", typeof maxTokenAge == "number" ? maxTokenAge : secs(maxTokenAge));
801
807
  if (age - tolerance > max)
802
- throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, "iat", checkFailed);
808
+ throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', claimsSet, "iat", checkFailed);
803
809
  if (age < -tolerance)
804
- throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, "iat", checkFailed);
810
+ throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', claimsSet, "iat", checkFailed);
805
811
  }
806
- return payload;
812
+ return claimsSet;
807
813
  }
808
814
 
809
815
  // node_modules/jose/dist/webapi/jwt/verify.js
@@ -838,7 +844,9 @@ function createLocalJWKSet(jwks) {
838
844
  let snapshot;
839
845
  try {
840
846
  snapshot = structuredClone(jwks);
841
- } catch {}
847
+ } catch (cause) {
848
+ throw new JWKSInvalid("JSON Web Key Set malformed", { cause });
849
+ }
842
850
  if (!isJwkSet(snapshot))
843
851
  throw new JWKSInvalid("JSON Web Key Set malformed");
844
852
  const metadata = snapshot.keys.map((jwk) => {
@@ -872,7 +880,7 @@ function isCloudflareWorkers() {
872
880
  return typeof WebSocketPair < "u" || typeof navigator < "u" && navigator.userAgent === "Cloudflare-Workers" || typeof EdgeRuntime < "u" && EdgeRuntime === "vercel";
873
881
  }
874
882
  var USER_AGENT;
875
- (typeof navigator > "u" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) && (USER_AGENT = "jose/v6.2.12");
883
+ (typeof navigator > "u" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) && (USER_AGENT = "jose/v6.2.13");
876
884
  var customFetch = /* @__PURE__ */ Symbol();
877
885
  async function fetchJwks(url, headers, signal, fetchImpl = fetch) {
878
886
  const response = await fetchImpl(url, {
@@ -881,14 +889,14 @@ async function fetchJwks(url, headers, signal, fetchImpl = fetch) {
881
889
  redirect: "manual",
882
890
  headers
883
891
  }).catch((err) => {
884
- throw err.name === "TimeoutError" ? new JWKSTimeout : err;
892
+ throw err.name === "TimeoutError" ? new JWKSTimeout(undefined, { cause: err }) : err;
885
893
  });
886
894
  if (response.status !== 200)
887
895
  throw new JOSEError("Expected 200 OK from the JSON Web Key Set HTTP response");
888
896
  try {
889
897
  return await response.json();
890
- } catch {
891
- throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON");
898
+ } catch (cause) {
899
+ throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON", { cause });
892
900
  }
893
901
  }
894
902
  var jwksCache = /* @__PURE__ */ Symbol();
@@ -9772,8 +9780,7 @@ class RealtimeEngine {
9772
9780
  private: isPrivate,
9773
9781
  canBroadcast
9774
9782
  };
9775
- if (this.resolveExternalToken)
9776
- this.leaveChannel(conn, msg.topic);
9783
+ this.leaveChannel(conn, msg.topic);
9777
9784
  conn.channels.set(msg.topic, channel);
9778
9785
  this.scheduleExternalExpiry(conn, channel);
9779
9786
  this.reply(conn, msg, "ok", {
@@ -9813,21 +9820,32 @@ class RealtimeEngine {
9813
9820
  if (channel.expiryTimer)
9814
9821
  clearTimeout(channel.expiryTimer);
9815
9822
  conn.channels.delete(topic);
9816
- const state = this.presence.get(topic);
9817
- if (state?.has(channel.presenceKey)) {
9818
- const metas = state.get(channel.presenceKey);
9819
- if (!metas)
9820
- return;
9823
+ this.removePresence(channel);
9824
+ }
9825
+ removePresence(channel) {
9826
+ const presenceRef = channel.presenceRef;
9827
+ if (!presenceRef)
9828
+ return;
9829
+ channel.presenceRef = undefined;
9830
+ const state = this.presence.get(channel.topic);
9831
+ const current = state?.get(channel.presenceKey);
9832
+ const leaving = current?.metas.find((meta) => meta.phx_ref === presenceRef);
9833
+ if (!state || !current || !leaving)
9834
+ return;
9835
+ const remaining = current.metas.filter((meta) => meta.phx_ref !== presenceRef);
9836
+ if (remaining.length > 0) {
9837
+ state.set(channel.presenceKey, { metas: remaining });
9838
+ } else {
9821
9839
  state.delete(channel.presenceKey);
9822
9840
  if (state.size === 0)
9823
- this.presence.delete(topic);
9824
- this.broadcastToTopic(topic, {
9825
- topic,
9826
- event: "presence_diff",
9827
- payload: { joins: {}, leaves: { [channel.presenceKey]: metas } },
9828
- ref: null
9829
- });
9841
+ this.presence.delete(channel.topic);
9830
9842
  }
9843
+ this.broadcastToTopic(channel.topic, {
9844
+ topic: channel.topic,
9845
+ event: "presence_diff",
9846
+ payload: { joins: {}, leaves: { [channel.presenceKey]: { metas: [leaving] } } },
9847
+ ref: null
9848
+ });
9831
9849
  }
9832
9850
  scheduleExternalExpiry(conn, channel) {
9833
9851
  if (channel.expiryTimer)
@@ -9907,23 +9925,30 @@ class RealtimeEngine {
9907
9925
  return;
9908
9926
  const event = String(msg.payload?.event ?? "").toLowerCase();
9909
9927
  if (event === "track") {
9910
- const meta = {
9911
- ...msg.payload?.payload ?? {},
9912
- phx_ref: `F${this.phxRefCounter++}`
9913
- };
9928
+ const payload = { ...msg.payload?.payload ?? {} };
9929
+ delete payload.phx_ref;
9930
+ delete payload.phx_ref_prev;
9914
9931
  let state = this.presence.get(msg.topic);
9915
9932
  if (!state) {
9916
9933
  state = new Map;
9917
9934
  this.presence.set(msg.topic, state);
9918
9935
  }
9919
- const previous = state.get(channel.presenceKey);
9920
- state.set(channel.presenceKey, { metas: [meta] });
9936
+ const current = state.get(channel.presenceKey);
9937
+ const previous = current?.metas.find((meta) => meta.phx_ref === channel.presenceRef);
9938
+ const meta = {
9939
+ ...payload,
9940
+ phx_ref: `F${this.phxRefCounter++}`,
9941
+ ...previous ? { phx_ref_prev: previous.phx_ref } : {}
9942
+ };
9943
+ const others = current?.metas.filter((meta) => meta.phx_ref !== channel.presenceRef) ?? [];
9944
+ state.set(channel.presenceKey, { metas: [...others, meta] });
9945
+ channel.presenceRef = meta.phx_ref;
9921
9946
  this.broadcastToTopic(msg.topic, {
9922
9947
  topic: msg.topic,
9923
9948
  event: "presence_diff",
9924
9949
  payload: {
9925
9950
  joins: { [channel.presenceKey]: { metas: [meta] } },
9926
- leaves: previous ? { [channel.presenceKey]: previous } : {}
9951
+ leaves: previous ? { [channel.presenceKey]: { metas: [previous] } } : {}
9927
9952
  },
9928
9953
  ref: null
9929
9954
  });
@@ -9932,19 +9957,7 @@ class RealtimeEngine {
9932
9957
  return;
9933
9958
  }
9934
9959
  if (event === "untrack") {
9935
- const state = this.presence.get(msg.topic);
9936
- const metas = state?.get(channel.presenceKey);
9937
- if (state && metas) {
9938
- state.delete(channel.presenceKey);
9939
- if (state.size === 0)
9940
- this.presence.delete(msg.topic);
9941
- this.broadcastToTopic(msg.topic, {
9942
- topic: msg.topic,
9943
- event: "presence_diff",
9944
- payload: { joins: {}, leaves: { [channel.presenceKey]: metas } },
9945
- ref: null
9946
- });
9947
- }
9960
+ this.removePresence(channel);
9948
9961
  if (msg.ref)
9949
9962
  this.reply(conn, msg, "ok", {});
9950
9963
  }
package/dist/index.js CHANGED
@@ -88,7 +88,7 @@ function randomToken(bytes = 32) {
88
88
  // package.json
89
89
  var package_default = {
90
90
  name: "@supacloud/lite",
91
- version: "0.33.0",
91
+ version: "0.33.2",
92
92
  description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
93
93
  type: "module",
94
94
  license: "Apache-2.0",
@@ -127,7 +127,7 @@ var package_default = {
127
127
  clean: "bun -e 'import { $ } from \"bun\"; await $`rm -rf dist`'",
128
128
  dev: "bun run src/cli.ts start",
129
129
  start: "bun run src/cli.ts start",
130
- test: "bun test --timeout 20000",
130
+ test: "bun run scripts/run-tests.ts",
131
131
  "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts test/qauth-workflow-matrix.test.ts test/maker-checker-workflow.test.ts test/commands-artifacts.test.ts --timeout 180000",
132
132
  "test:graphql": "SUPACLOUD_LITE_TEST_GRAPHQL=1 bun test test/graphql-native.test.ts --timeout 180000",
133
133
  benchmark: "bun run scripts/benchmark.ts",
@@ -147,31 +147,31 @@ var package_default = {
147
147
  "@electric-sql/pglite-pgmq": "0.0.8",
148
148
  "@electric-sql/pglite-pgvector": "0.0.11",
149
149
  "@electric-sql/pglite-postgis": "0.2.10",
150
- "@supacloud/db": "0.20.0",
150
+ "@supacloud/db": "0.20.1",
151
151
  "exact-mirror": "1.2.6",
152
152
  tar: "^7.5.22",
153
- typebox: "1.3.37"
153
+ typebox: "1.4.2"
154
154
  },
155
155
  devDependencies: {
156
- "@supacloud/app": "0.30.0",
157
- "@supacloud/contracts": "0.13.0",
158
- "@supacloud/compiler": "0.49.0",
159
- "@supacloud/elysia": "0.35.0",
160
- "@supabase/supabase-js": "^2.117.3",
156
+ "@supacloud/app": "0.33.0",
157
+ "@supacloud/contracts": "0.15.1",
158
+ "@supacloud/compiler": "0.51.1",
159
+ "@supacloud/elysia": "0.36.1",
160
+ "@supabase/supabase-js": "^2.118.0",
161
161
  "@types/bun": "^1.4.3",
162
162
  effect: "4.0.5",
163
- elysia: "2.0.0-beta.32",
164
- jose: "^6.2.12",
163
+ elysia: "2.0.0-rc.0",
164
+ jose: "^6.2.13",
165
165
  typescript: "^7.0.2"
166
166
  },
167
167
  overrides: {
168
- "@supacloud/db": "0.20.0",
169
- "@supacloud/app": "0.30.0",
170
- "@supacloud/compiler": "0.49.0",
171
- "@supacloud/elysia": "0.35.0",
172
- "@supacloud/contracts": "0.13.0",
173
- "@supacloud/commands": "0.11.0",
174
- "@supacloud/delivery": "0.21.0",
168
+ "@supacloud/db": "0.20.1",
169
+ "@supacloud/app": "0.33.0",
170
+ "@supacloud/compiler": "0.51.1",
171
+ "@supacloud/elysia": "0.36.1",
172
+ "@supacloud/contracts": "0.15.1",
173
+ "@supacloud/commands": "0.11.1",
174
+ "@supacloud/delivery": "0.21.1",
175
175
  "@graphql-tools/utils": "^12.0.4"
176
176
  },
177
177
  engines: {
@@ -251,7 +251,7 @@ function isAllowedRedirect(requested, siteUrl, allowList = []) {
251
251
  var encoder2 = new TextEncoder;
252
252
  var decoder2 = new TextDecoder;
253
253
  var strictDecoder = new TextDecoder("utf-8", { fatal: true });
254
- var MAX_INT32 = 2 ** 32;
254
+ var UINT32_RANGE = 2 ** 32;
255
255
  function concat(...buffers) {
256
256
  const size = buffers.reduce((acc, { length }) => acc + length, 0), buf = new Uint8Array(size);
257
257
  let i = 0;
@@ -410,23 +410,23 @@ function isDisjoint(...headers) {
410
410
  function decodeBase64url(value, label, ErrorClass) {
411
411
  try {
412
412
  return decode(value);
413
- } catch {
414
- throw new ErrorClass(`Failed to base64url decode the ${label}`);
413
+ } catch (cause) {
414
+ throw new ErrorClass(`Failed to base64url decode the ${label}`, { cause });
415
415
  }
416
416
  }
417
- function encodeBase64url(value, label, ErrorClass) {
417
+ function encodeAsciiMember(value, label, ErrorClass) {
418
418
  try {
419
419
  return encode(value);
420
- } catch {
421
- throw new ErrorClass(`The ${label} is not a valid base64url string`);
420
+ } catch (cause) {
421
+ throw new ErrorClass(`The ${label} is not a valid base64url string`, { cause });
422
422
  }
423
423
  }
424
- function parseJoseHeader(b64, ErrorClass, message) {
424
+ function parseJoseHeader(encodedProtectedHeader, ErrorClass, message) {
425
425
  let parsed;
426
426
  try {
427
- parsed = JSON.parse(strictDecoder.decode(decode(b64)));
428
- } catch {
429
- throw new ErrorClass(message);
427
+ parsed = JSON.parse(strictDecoder.decode(decode(encodedProtectedHeader)));
428
+ } catch (cause) {
429
+ throw new ErrorClass(message, { cause });
430
430
  }
431
431
  if (!isObject(parsed))
432
432
  throw new ErrorClass(message);
@@ -484,15 +484,15 @@ var jwkMatchesOp = (entry, key, usage) => {
484
484
  }
485
485
  };
486
486
  async function prepareKey(entry, key, usage) {
487
- const { alg, secret } = entry, privateKey = usage === "decrypt" || usage === "sign";
487
+ const { alg, secret } = entry, requiresPrivateKey = usage === "decrypt" || usage === "sign";
488
488
  if (secret && key instanceof Uint8Array)
489
489
  return key;
490
490
  let normalized, keyObject;
491
491
  if (isObject(key)) {
492
492
  if (normalized = normalizeJwk(key), typeof normalized.kty != "string")
493
493
  throw invalidKeyType(alg, key, secret);
494
- if (!(secret ? normalized.kty === "oct" && typeof normalized.k == "string" : normalized.kty !== "oct" && (privateKey ? normalized.kty === "AKP" && typeof normalized.priv == "string" || typeof normalized.d == "string" : normalized.d === undefined && normalized.priv === undefined)))
495
- throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present' : `JSON Web Key for this operation must be a ${privateKey ? "private" : "public"} JWK`);
494
+ if (!(secret ? normalized.kty === "oct" && typeof normalized.k == "string" : normalized.kty !== "oct" && (requiresPrivateKey ? normalized.kty === "AKP" && typeof normalized.priv == "string" || typeof normalized.d == "string" : normalized.d === undefined && normalized.priv === undefined)))
495
+ throw new TypeError(secret ? 'JSON Web Key for symmetric algorithms must have JWK "kty" (Key Type) equal to "oct" and the JWK "k" (Key Value) present' : `JSON Web Key for this operation must be a ${requiresPrivateKey ? "private" : "public"} JWK`);
496
496
  if (jwkMatchesOp(entry, normalized, usage), normalized.kty === "oct")
497
497
  return decode(normalized.k);
498
498
  if (!Object.isFrozen(key)) {
@@ -502,7 +502,7 @@ async function prepareKey(entry, key, usage) {
502
502
  } else {
503
503
  if (!isKeyLike(key))
504
504
  throw invalidKeyType(alg, key, secret);
505
- const expectedType = secret ? "secret" : privateKey ? "private" : "public";
505
+ const expectedType = secret ? "secret" : requiresPrivateKey ? "private" : "public";
506
506
  if (key.type !== expectedType && (secret || ["secret", "public", "private"].includes(key.type)))
507
507
  throw new TypeError(`${tag(key)} instances must be of type "${expectedType}" for the ${alg} algorithm`);
508
508
  if (isCryptoKey(key))
@@ -641,8 +641,8 @@ function eddsa() {
641
641
  usages: sig
642
642
  };
643
643
  }
644
- function mldsa(bits) {
645
- const subtle = { name: `ML-DSA-${bits}` };
644
+ function mldsa(parameterSet) {
645
+ const subtle = { name: `ML-DSA-${parameterSet}` };
646
646
  return {
647
647
  kty: ["AKP"],
648
648
  subtle,
@@ -680,21 +680,23 @@ function jwsAlgorithm(alg) {
680
680
  function prepareVerify(options) {
681
681
  return [options && validateAlgorithms("algorithms", options.algorithms), options?.crit];
682
682
  }
683
- function parseProtectedHeader(encodedProtected) {
684
- return encodedProtected === undefined ? {} : parseJoseHeader(encodedProtected, JWSInvalid, "JWS Protected Header is invalid");
683
+ function parseProtectedHeader(encodedProtectedHeader) {
684
+ return encodedProtectedHeader === undefined ? {} : parseJoseHeader(encodedProtectedHeader, JWSInvalid, "JWS Protected Header is invalid");
685
685
  }
686
686
  function encodeCompactUnencodedPayload(payload) {
687
687
  try {
688
688
  return encode(payload);
689
- } catch {
690
- throw new JWSInvalid("JWS Compact Serialization payload must use only ASCII characters");
689
+ } catch (cause) {
690
+ throw new JWSInvalid("JWS Compact Serialization payload must use only ASCII characters", {
691
+ cause
692
+ });
691
693
  }
692
694
  }
693
695
  async function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedProtected) {
694
- const { protected: encodedProtected, header, payload: inputPayload } = jws, parsedProt = parsedProtected ?? parseProtectedHeader(encodedProtected);
695
- if (!isDisjoint(parsedProt, header))
696
+ const { protected: encodedProtectedHeader, header, payload: inputPayload } = jws, protectedHeader = parsedProtected ?? parseProtectedHeader(encodedProtectedHeader);
697
+ if (!isDisjoint(protectedHeader, header))
696
698
  throw new JWSInvalid("JWS Protected and JWS Unprotected Header Parameter names must be disjoint");
697
- const joseHeader = { ...parsedProt, ...header }, b64 = validateB64(parsedProt, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], parsedProt, joseHeader)), { alg } = joseHeader;
699
+ const joseHeader = { ...protectedHeader, ...header }, b64 = validateB64(protectedHeader, validateCrit(JWSInvalid, JWS_RECOGNIZED, shared[1], protectedHeader, joseHeader)), { alg } = joseHeader;
698
700
  if (typeof alg != "string" || !alg)
699
701
  throw new JWSInvalid('JWS "alg" (Algorithm) Header Parameter missing or invalid');
700
702
  if (shared[0] && !shared[0].has(alg))
@@ -706,17 +708,19 @@ async function verifySignature(jws, shared, key, encodeUnencodedPayload, parsedP
706
708
  throw new JWSInvalid("JWS Payload must be a string or an Uint8Array instance");
707
709
  const signingPayload = b64 || typeof inputPayload != "string" ? inputPayload : encodeUnencodedPayload(inputPayload);
708
710
  let resolvedKey = false;
709
- typeof key == "function" && (key = await key(parsedProt, jws), resolvedKey = true);
710
- const entry = jwsAlgorithm(alg), data = concat(encodedProtected !== undefined ? encode(encodedProtected) : new Uint8Array, encode("."), typeof signingPayload == "string" ? shared[2] ??= encodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload), signature = decodeBase64url(jws.signature, "signature", JWSInvalid), k = await prepareKey(entry, key, "verify"), cryptoKey = await rawKey(k, entry.subtle, "verify");
711
+ typeof key == "function" && (key = await key(protectedHeader, jws), resolvedKey = true);
712
+ const entry = jwsAlgorithm(alg), signingInput = concat(encodedProtectedHeader !== undefined ? encode(encodedProtectedHeader) : new Uint8Array, encode("."), typeof signingPayload == "string" ? shared[2] ??= encodeAsciiMember(signingPayload, "payload", JWSInvalid) : signingPayload), signature = decodeBase64url(jws.signature, "signature", JWSInvalid), verificationKey = await prepareKey(entry, key, "verify"), cryptoKey = await rawKey(verificationKey, entry.subtle, "verify");
711
713
  entry.minRsaBits && checkModulusLength(entry.alg, cryptoKey);
712
714
  let verified = false;
713
715
  try {
714
- verified = await crypto.subtle.verify(entry.signing, cryptoKey, signature, data);
715
- } catch {}
716
+ verified = await crypto.subtle.verify(entry.signing, cryptoKey, signature, signingInput);
717
+ } catch (cause) {
718
+ throw new JWSSignatureVerificationFailed(undefined, { cause });
719
+ }
716
720
  if (!verified)
717
721
  throw new JWSSignatureVerificationFailed;
718
722
  const result = { payload: typeof signingPayload == "string" ? decodeBase64url(signingPayload, "payload", JWSInvalid) : signingPayload };
719
- return encodedProtected !== undefined && (result.protectedHeader = parsedProt), header !== undefined && (result.unprotectedHeader = header), resolvedKey ? [{ ...result, key: k }, b64] : [result, b64];
723
+ return encodedProtectedHeader !== undefined && (result.protectedHeader = protectedHeader), header !== undefined && (result.unprotectedHeader = header), resolvedKey ? [{ ...result, key: verificationKey }, b64] : [result, b64];
720
724
  }
721
725
  async function verifyCompact(jws, shared, key) {
722
726
  if (jws instanceof Uint8Array && (jws = decoder2.decode(jws)), typeof jws != "string")
@@ -746,8 +750,8 @@ function secs(str) {
746
750
  typeof str != "string" && invalidDuration();
747
751
  const matched = REGEX.exec(str);
748
752
  (!matched || matched[4] && matched[1]) && invalidDuration();
749
- const value = parseFloat(matched[2]), numericDate2 = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
750
- return Number.isFinite(numericDate2) || invalidDuration(), matched[1] === "-" || matched[4] === "ago" ? -numericDate2 : numericDate2;
753
+ const value = parseFloat(matched[2]), durationSeconds = Math.round(value * multipliers[matched[3][0].toLowerCase()]);
754
+ return Number.isFinite(durationSeconds) || invalidDuration(), matched[1] === "-" || matched[4] === "ago" ? -durationSeconds : durationSeconds;
751
755
  }
752
756
  function validateInput(label, input) {
753
757
  if (!Number.isFinite(input))
@@ -759,33 +763,35 @@ var normalizeTyp = (value) => {
759
763
  return value.includes("/") ? normalized : `application/${normalized}`;
760
764
  };
761
765
  var checkAudiencePresence = (audPayload, audOption) => typeof audPayload == "string" ? audOption.includes(audPayload) : Array.isArray(audPayload) ? audOption.some((aud) => audPayload.includes(aud)) : false;
762
- function validateNumericDate(payload, claim, required = false) {
763
- const value = payload[claim];
766
+ function validateNumericDate(claimsSet, claim, required = false) {
767
+ const value = claimsSet[claim];
764
768
  if (!(value === undefined && !required)) {
765
769
  if (typeof value != "number")
766
- throw new JWTClaimValidationFailed(`"${claim}" claim must be a number`, payload, claim, "invalid");
770
+ throw new JWTClaimValidationFailed(`"${claim}" claim must be a number`, claimsSet, claim, "invalid");
767
771
  return value;
768
772
  }
769
773
  }
770
- function unexpectedClaim(payload, claim) {
771
- throw new JWTClaimValidationFailed(`unexpected "${claim}" claim value`, payload, claim, checkFailed);
774
+ function unexpectedClaim(claimsSet, claim) {
775
+ throw new JWTClaimValidationFailed(`unexpected "${claim}" claim value`, claimsSet, claim, checkFailed);
772
776
  }
773
- function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
774
- let payload;
777
+ function validateClaimsSet(protectedHeader, claimsSetBytes, options = {}) {
778
+ let claimsSet;
775
779
  try {
776
- payload = JSON.parse(strictDecoder.decode(encodedPayload));
777
- } catch {}
778
- if (!isObject(payload))
780
+ claimsSet = JSON.parse(strictDecoder.decode(claimsSetBytes));
781
+ } catch (cause) {
782
+ throw new JWTInvalid("JWT Claims Set must be a top-level JSON object", { cause });
783
+ }
784
+ if (!isObject(claimsSet))
779
785
  throw new JWTInvalid("JWT Claims Set must be a top-level JSON object");
780
786
  const { typ } = options;
781
787
  if (typ !== undefined && (typeof protectedHeader.typ != "string" || normalizeTyp(protectedHeader.typ) !== normalizeTyp(typ)))
782
- throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', payload, "typ", checkFailed);
788
+ throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', claimsSet, "typ", checkFailed);
783
789
  const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options, presenceCheck = [...requiredClaims];
784
790
  maxTokenAge !== undefined && presenceCheck.push("iat"), audience !== undefined && presenceCheck.push("aud"), subject !== undefined && presenceCheck.push("sub"), issuer !== undefined && presenceCheck.push("iss");
785
791
  for (const claim of new Set(presenceCheck.reverse()))
786
- if (!Object.hasOwn(payload, claim))
787
- throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, payload, claim, "missing");
788
- issuer !== undefined && !(Array.isArray(issuer) ? issuer : [issuer]).includes(payload.iss) && unexpectedClaim(payload, "iss"), subject !== undefined && payload.sub !== subject && unexpectedClaim(payload, "sub"), audience !== undefined && !checkAudiencePresence(payload.aud, typeof audience == "string" ? [audience] : audience) && unexpectedClaim(payload, "aud");
792
+ if (!Object.hasOwn(claimsSet, claim))
793
+ throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, claimsSet, claim, "missing");
794
+ issuer !== undefined && !(Array.isArray(issuer) ? issuer : [issuer]).includes(claimsSet.iss) && unexpectedClaim(claimsSet, "iss"), subject !== undefined && claimsSet.sub !== subject && unexpectedClaim(claimsSet, "sub"), audience !== undefined && !checkAudiencePresence(claimsSet.aud, typeof audience == "string" ? [audience] : audience) && unexpectedClaim(claimsSet, "aud");
789
795
  const { clockTolerance } = options;
790
796
  let tolerance = 0;
791
797
  if (typeof clockTolerance == "string")
@@ -796,20 +802,20 @@ function validateClaimsSet(protectedHeader, encodedPayload, options = {}) {
796
802
  tolerance = clockTolerance;
797
803
  }
798
804
  validateInput("clockTolerance option", tolerance);
799
- const { currentDate } = options, now = validateInput("currentDate option", epoch(currentDate === undefined ? /* @__PURE__ */ new Date : currentDate)), iat = validateNumericDate(payload, "iat", maxTokenAge !== undefined), nbf = validateNumericDate(payload, "nbf");
805
+ const { currentDate } = options, now = validateInput("currentDate option", epoch(currentDate === undefined ? /* @__PURE__ */ new Date : currentDate)), iat = validateNumericDate(claimsSet, "iat", maxTokenAge !== undefined), nbf = validateNumericDate(claimsSet, "nbf");
800
806
  if (nbf !== undefined && nbf > now + tolerance)
801
- throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', payload, "nbf", checkFailed);
802
- const exp = validateNumericDate(payload, "exp");
807
+ throw new JWTClaimValidationFailed('"nbf" claim timestamp check failed', claimsSet, "nbf", checkFailed);
808
+ const exp = validateNumericDate(claimsSet, "exp");
803
809
  if (exp !== undefined && exp <= now - tolerance)
804
- throw new JWTExpired('"exp" claim timestamp check failed', payload, "exp", checkFailed);
810
+ throw new JWTExpired('"exp" claim timestamp check failed', claimsSet, "exp", checkFailed);
805
811
  if (maxTokenAge !== undefined) {
806
812
  const age = now - iat, max = validateInput("maxTokenAge option", typeof maxTokenAge == "number" ? maxTokenAge : secs(maxTokenAge));
807
813
  if (age - tolerance > max)
808
- throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', payload, "iat", checkFailed);
814
+ throw new JWTExpired('"iat" claim timestamp check failed (too far in the past)', claimsSet, "iat", checkFailed);
809
815
  if (age < -tolerance)
810
- throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', payload, "iat", checkFailed);
816
+ throw new JWTClaimValidationFailed('"iat" claim timestamp check failed (it should be in the past)', claimsSet, "iat", checkFailed);
811
817
  }
812
- return payload;
818
+ return claimsSet;
813
819
  }
814
820
 
815
821
  // node_modules/jose/dist/webapi/jwt/verify.js
@@ -844,7 +850,9 @@ function createLocalJWKSet(jwks) {
844
850
  let snapshot;
845
851
  try {
846
852
  snapshot = structuredClone(jwks);
847
- } catch {}
853
+ } catch (cause) {
854
+ throw new JWKSInvalid("JSON Web Key Set malformed", { cause });
855
+ }
848
856
  if (!isJwkSet(snapshot))
849
857
  throw new JWKSInvalid("JSON Web Key Set malformed");
850
858
  const metadata = snapshot.keys.map((jwk) => {
@@ -878,7 +886,7 @@ function isCloudflareWorkers() {
878
886
  return typeof WebSocketPair < "u" || typeof navigator < "u" && navigator.userAgent === "Cloudflare-Workers" || typeof EdgeRuntime < "u" && EdgeRuntime === "vercel";
879
887
  }
880
888
  var USER_AGENT;
881
- (typeof navigator > "u" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) && (USER_AGENT = "jose/v6.2.12");
889
+ (typeof navigator > "u" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) && (USER_AGENT = "jose/v6.2.13");
882
890
  var customFetch = /* @__PURE__ */ Symbol();
883
891
  async function fetchJwks(url, headers, signal, fetchImpl = fetch) {
884
892
  const response = await fetchImpl(url, {
@@ -887,14 +895,14 @@ async function fetchJwks(url, headers, signal, fetchImpl = fetch) {
887
895
  redirect: "manual",
888
896
  headers
889
897
  }).catch((err) => {
890
- throw err.name === "TimeoutError" ? new JWKSTimeout : err;
898
+ throw err.name === "TimeoutError" ? new JWKSTimeout(undefined, { cause: err }) : err;
891
899
  });
892
900
  if (response.status !== 200)
893
901
  throw new JOSEError("Expected 200 OK from the JSON Web Key Set HTTP response");
894
902
  try {
895
903
  return await response.json();
896
- } catch {
897
- throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON");
904
+ } catch (cause) {
905
+ throw new JOSEError("Failed to parse the JSON Web Key Set HTTP response as JSON", { cause });
898
906
  }
899
907
  }
900
908
  var jwksCache = /* @__PURE__ */ Symbol();
@@ -9766,8 +9774,7 @@ class RealtimeEngine {
9766
9774
  private: isPrivate,
9767
9775
  canBroadcast
9768
9776
  };
9769
- if (this.resolveExternalToken)
9770
- this.leaveChannel(conn, msg.topic);
9777
+ this.leaveChannel(conn, msg.topic);
9771
9778
  conn.channels.set(msg.topic, channel);
9772
9779
  this.scheduleExternalExpiry(conn, channel);
9773
9780
  this.reply(conn, msg, "ok", {
@@ -9807,21 +9814,32 @@ class RealtimeEngine {
9807
9814
  if (channel.expiryTimer)
9808
9815
  clearTimeout(channel.expiryTimer);
9809
9816
  conn.channels.delete(topic);
9810
- const state = this.presence.get(topic);
9811
- if (state?.has(channel.presenceKey)) {
9812
- const metas = state.get(channel.presenceKey);
9813
- if (!metas)
9814
- return;
9817
+ this.removePresence(channel);
9818
+ }
9819
+ removePresence(channel) {
9820
+ const presenceRef = channel.presenceRef;
9821
+ if (!presenceRef)
9822
+ return;
9823
+ channel.presenceRef = undefined;
9824
+ const state = this.presence.get(channel.topic);
9825
+ const current = state?.get(channel.presenceKey);
9826
+ const leaving = current?.metas.find((meta) => meta.phx_ref === presenceRef);
9827
+ if (!state || !current || !leaving)
9828
+ return;
9829
+ const remaining = current.metas.filter((meta) => meta.phx_ref !== presenceRef);
9830
+ if (remaining.length > 0) {
9831
+ state.set(channel.presenceKey, { metas: remaining });
9832
+ } else {
9815
9833
  state.delete(channel.presenceKey);
9816
9834
  if (state.size === 0)
9817
- this.presence.delete(topic);
9818
- this.broadcastToTopic(topic, {
9819
- topic,
9820
- event: "presence_diff",
9821
- payload: { joins: {}, leaves: { [channel.presenceKey]: metas } },
9822
- ref: null
9823
- });
9835
+ this.presence.delete(channel.topic);
9824
9836
  }
9837
+ this.broadcastToTopic(channel.topic, {
9838
+ topic: channel.topic,
9839
+ event: "presence_diff",
9840
+ payload: { joins: {}, leaves: { [channel.presenceKey]: { metas: [leaving] } } },
9841
+ ref: null
9842
+ });
9825
9843
  }
9826
9844
  scheduleExternalExpiry(conn, channel) {
9827
9845
  if (channel.expiryTimer)
@@ -9901,23 +9919,30 @@ class RealtimeEngine {
9901
9919
  return;
9902
9920
  const event = String(msg.payload?.event ?? "").toLowerCase();
9903
9921
  if (event === "track") {
9904
- const meta = {
9905
- ...msg.payload?.payload ?? {},
9906
- phx_ref: `F${this.phxRefCounter++}`
9907
- };
9922
+ const payload = { ...msg.payload?.payload ?? {} };
9923
+ delete payload.phx_ref;
9924
+ delete payload.phx_ref_prev;
9908
9925
  let state = this.presence.get(msg.topic);
9909
9926
  if (!state) {
9910
9927
  state = new Map;
9911
9928
  this.presence.set(msg.topic, state);
9912
9929
  }
9913
- const previous = state.get(channel.presenceKey);
9914
- state.set(channel.presenceKey, { metas: [meta] });
9930
+ const current = state.get(channel.presenceKey);
9931
+ const previous = current?.metas.find((meta) => meta.phx_ref === channel.presenceRef);
9932
+ const meta = {
9933
+ ...payload,
9934
+ phx_ref: `F${this.phxRefCounter++}`,
9935
+ ...previous ? { phx_ref_prev: previous.phx_ref } : {}
9936
+ };
9937
+ const others = current?.metas.filter((meta) => meta.phx_ref !== channel.presenceRef) ?? [];
9938
+ state.set(channel.presenceKey, { metas: [...others, meta] });
9939
+ channel.presenceRef = meta.phx_ref;
9915
9940
  this.broadcastToTopic(msg.topic, {
9916
9941
  topic: msg.topic,
9917
9942
  event: "presence_diff",
9918
9943
  payload: {
9919
9944
  joins: { [channel.presenceKey]: { metas: [meta] } },
9920
- leaves: previous ? { [channel.presenceKey]: previous } : {}
9945
+ leaves: previous ? { [channel.presenceKey]: { metas: [previous] } } : {}
9921
9946
  },
9922
9947
  ref: null
9923
9948
  });
@@ -9926,19 +9951,7 @@ class RealtimeEngine {
9926
9951
  return;
9927
9952
  }
9928
9953
  if (event === "untrack") {
9929
- const state = this.presence.get(msg.topic);
9930
- const metas = state?.get(channel.presenceKey);
9931
- if (state && metas) {
9932
- state.delete(channel.presenceKey);
9933
- if (state.size === 0)
9934
- this.presence.delete(msg.topic);
9935
- this.broadcastToTopic(msg.topic, {
9936
- topic: msg.topic,
9937
- event: "presence_diff",
9938
- payload: { joins: {}, leaves: { [channel.presenceKey]: metas } },
9939
- ref: null
9940
- });
9941
- }
9954
+ this.removePresence(channel);
9942
9955
  if (msg.ref)
9943
9956
  this.reply(conn, msg, "ok", {});
9944
9957
  }
@@ -57,6 +57,7 @@ export declare class RealtimeEngine {
57
57
  private handleMessage;
58
58
  private handleJoin;
59
59
  private leaveChannel;
60
+ private removePresence;
60
61
  private scheduleExternalExpiry;
61
62
  /**
62
63
  * Phoenix binary serializer, kind 3 (userBroadcastPush):
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/runtime/realtime/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAI3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAA;IACrC,wEAAwE;IACxE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5C;AAgED,uFAAuF;AACvF,qBAAa,cAAc;IAUvB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,SAAS,CAAC;IAClB,OAAO,CAAC,oBAAoB,CAAC;IAX/B,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,aAAa,CAAI;IACzB,0BAA0B;IAC1B,OAAO,CAAC,QAAQ,CAAgD;IAChE,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,eAAe,CAAiC;gBAG9C,EAAE,EAAE,QAAQ,EACZ,SAAS,CAAC,EAAE,MAAM,YAAA,EAClB,oBAAoB,CAAC,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,aAAA;IAG3E,oEAAoE;YACtD,gBAAgB;IAS9B;;;;;OAKG;YACW,gBAAgB;IA0C9B,6EAA6E;IAC7E,OAAO,CAAC,mBAAmB;IAc3B,iFAAiF;IAC3E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,qEAAqE;IACrE,OAAO,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG;QAChE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK,IAAI,CAAA;QAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;KACpB;IAcD,OAAO,CAAC,IAAI;IAYZ,OAAO,CAAC,KAAK;YAUC,aAAa;YA+Eb,UAAU;IA0GxB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,sBAAsB;IAY9B;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IA+BpB,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,cAAc;IA+CtB,OAAO,CAAC,gBAAgB;YAQV,WAAW;IAoDzB;;;;;;;OAOG;YACW,MAAM;IA6BpB,OAAO,CAAC,cAAc;CAUvB;AAkBD,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CA2BjF"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/runtime/realtime/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAI3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAA;IACrC,wEAAwE;IACxE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5C;AAkED,uFAAuF;AACvF,qBAAa,cAAc;IAUvB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,SAAS,CAAC;IAClB,OAAO,CAAC,oBAAoB,CAAC;IAX/B,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,aAAa,CAAI;IACzB,0BAA0B;IAC1B,OAAO,CAAC,QAAQ,CAAgD;IAChE,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,eAAe,CAAiC;gBAG9C,EAAE,EAAE,QAAQ,EACZ,SAAS,CAAC,EAAE,MAAM,YAAA,EAClB,oBAAoB,CAAC,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,aAAA;IAG3E,oEAAoE;YACtD,gBAAgB;IAS9B;;;;;OAKG;YACW,gBAAgB;IA0C9B,6EAA6E;IAC7E,OAAO,CAAC,mBAAmB;IAc3B,iFAAiF;IAC3E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAa5B,sEAAsE;IAChE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,qEAAqE;IACrE,OAAO,CAAC,MAAM,EAAE,kBAAkB,EAAE,IAAI,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG;QAChE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK,IAAI,CAAA;QAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;KACpB;IAcD,OAAO,CAAC,IAAI;IAYZ,OAAO,CAAC,KAAK;YAUC,aAAa;YA+Eb,UAAU;IA0GxB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,cAAc;IAuBtB,OAAO,CAAC,sBAAsB;IAY9B;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IA+BpB,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,cAAc;IA4CtB,OAAO,CAAC,gBAAgB;YAQV,WAAW;IAoDzB;;;;;;;OAOG;YACW,MAAM;IA6BpB,OAAO,CAAC,cAAc;CAUvB;AAkBD,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CA2BjF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/lite",
3
- "version": "0.33.0",
3
+ "version": "0.33.2",
4
4
  "description": "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -39,7 +39,7 @@
39
39
  "clean": "bun -e 'import { $ } from \"bun\"; await $`rm -rf dist`'",
40
40
  "dev": "bun run src/cli.ts start",
41
41
  "start": "bun run src/cli.ts start",
42
- "test": "bun test --timeout 20000",
42
+ "test": "bun run scripts/run-tests.ts",
43
43
  "test:native": "SUPACLOUD_LITE_TEST_NATIVE=1 bun test test/native-engine.test.ts test/qauth-workflow-matrix.test.ts test/maker-checker-workflow.test.ts test/commands-artifacts.test.ts --timeout 180000",
44
44
  "test:graphql": "SUPACLOUD_LITE_TEST_GRAPHQL=1 bun test test/graphql-native.test.ts --timeout 180000",
45
45
  "benchmark": "bun run scripts/benchmark.ts",
@@ -59,31 +59,31 @@
59
59
  "@electric-sql/pglite-pgmq": "0.0.8",
60
60
  "@electric-sql/pglite-pgvector": "0.0.11",
61
61
  "@electric-sql/pglite-postgis": "0.2.10",
62
- "@supacloud/db": "0.20.0",
62
+ "@supacloud/db": "0.20.1",
63
63
  "exact-mirror": "1.2.6",
64
64
  "tar": "^7.5.22",
65
- "typebox": "1.3.37"
65
+ "typebox": "1.4.2"
66
66
  },
67
67
  "devDependencies": {
68
- "@supacloud/app": "0.30.0",
69
- "@supacloud/contracts": "0.13.0",
70
- "@supacloud/compiler": "0.49.0",
71
- "@supacloud/elysia": "0.35.0",
72
- "@supabase/supabase-js": "^2.117.3",
68
+ "@supacloud/app": "0.33.0",
69
+ "@supacloud/contracts": "0.15.1",
70
+ "@supacloud/compiler": "0.51.1",
71
+ "@supacloud/elysia": "0.36.1",
72
+ "@supabase/supabase-js": "^2.118.0",
73
73
  "@types/bun": "^1.4.3",
74
74
  "effect": "4.0.5",
75
- "elysia": "2.0.0-beta.32",
76
- "jose": "^6.2.12",
75
+ "elysia": "2.0.0-rc.0",
76
+ "jose": "^6.2.13",
77
77
  "typescript": "^7.0.2"
78
78
  },
79
79
  "overrides": {
80
- "@supacloud/db": "0.20.0",
81
- "@supacloud/app": "0.30.0",
82
- "@supacloud/compiler": "0.49.0",
83
- "@supacloud/elysia": "0.35.0",
84
- "@supacloud/contracts": "0.13.0",
85
- "@supacloud/commands": "0.11.0",
86
- "@supacloud/delivery": "0.21.0",
80
+ "@supacloud/db": "0.20.1",
81
+ "@supacloud/app": "0.33.0",
82
+ "@supacloud/compiler": "0.51.1",
83
+ "@supacloud/elysia": "0.36.1",
84
+ "@supacloud/contracts": "0.15.1",
85
+ "@supacloud/commands": "0.11.1",
86
+ "@supacloud/delivery": "0.21.1",
87
87
  "@graphql-tools/utils": "^12.0.4"
88
88
  },
89
89
  "engines": {