convex-cms 0.0.2 → 0.0.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/admin-dist/nitro.json +15 -0
- package/admin-dist/public/assets/CmsEmptyState-CRswfTzk.js +5 -0
- package/admin-dist/public/assets/CmsPageHeader-CirpXndm.js +1 -0
- package/admin-dist/public/assets/CmsStatusBadge-CbEUpQu-.js +1 -0
- package/admin-dist/public/assets/CmsToolbar-BI2nZOXp.js +1 -0
- package/admin-dist/public/assets/ContentEntryEditor-CBeCyK_m.js +4 -0
- package/admin-dist/public/assets/ErrorState-BIVaWmom.js +1 -0
- package/admin-dist/public/assets/TaxonomyFilter-ChaY6Y_x.js +1 -0
- package/admin-dist/public/assets/_contentTypeId-DQ8k_Rvw.js +1 -0
- package/admin-dist/public/assets/_entryId-CKU_glsK.js +1 -0
- package/admin-dist/public/assets/alert-BXjTqrwQ.js +1 -0
- package/admin-dist/public/assets/badge-hvUOzpVZ.js +1 -0
- package/admin-dist/public/assets/circle-check-big-CF_pR17r.js +1 -0
- package/admin-dist/public/assets/command-DU82cJlt.js +1 -0
- package/admin-dist/public/assets/content-_LXl3pp7.js +1 -0
- package/admin-dist/public/assets/content-types-KjxaXGxY.js +2 -0
- package/admin-dist/public/assets/globals-CS6BZ0zp.css +1 -0
- package/admin-dist/public/assets/index-DNGIZHL-.js +1 -0
- package/admin-dist/public/assets/label-KNtpL71g.js +1 -0
- package/admin-dist/public/assets/link-2-Bw2aI4V4.js +1 -0
- package/admin-dist/public/assets/list-sYepHjt_.js +1 -0
- package/admin-dist/public/assets/main-CKj5yfEi.js +97 -0
- package/admin-dist/public/assets/media-Bkrkffm7.js +1 -0
- package/admin-dist/public/assets/new._contentTypeId-C3LstjNs.js +1 -0
- package/admin-dist/public/assets/plus-DUn8v_Xf.js +1 -0
- package/admin-dist/public/assets/rotate-ccw-DJEoHcRI.js +1 -0
- package/admin-dist/public/assets/scroll-area-DfIlT0in.js +1 -0
- package/admin-dist/public/assets/search-MuAUDJKR.js +1 -0
- package/admin-dist/public/assets/select-BD29IXCI.js +1 -0
- package/admin-dist/public/assets/settings-DmMyn_6A.js +1 -0
- package/admin-dist/public/assets/switch-h3Rrnl5i.js +1 -0
- package/admin-dist/public/assets/tabs-imc8h-Dp.js +1 -0
- package/admin-dist/public/assets/taxonomies-dAsrT65H.js +1 -0
- package/admin-dist/public/assets/textarea-BTy7nwzR.js +1 -0
- package/admin-dist/public/assets/trash-SAWKZZHv.js +1 -0
- package/admin-dist/public/assets/triangle-alert-E52Vfeuh.js +1 -0
- package/admin-dist/public/assets/useBreadcrumbLabel-BECBMCzM.js +1 -0
- package/admin-dist/public/assets/usePermissions-Basjs9BT.js +1 -0
- package/admin-dist/public/favicon.ico +0 -0
- package/admin-dist/server/_chunks/_libs/@date-fns/tz.mjs +217 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/core.mjs +719 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/dom.mjs +622 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/react-dom.mjs +292 -0
- package/admin-dist/server/_chunks/_libs/@floating-ui/utils.mjs +320 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/number.mjs +6 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/primitive.mjs +11 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-arrow.mjs +23 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-avatar.mjs +119 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-checkbox.mjs +270 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-collection.mjs +69 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-compose-refs.mjs +39 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-context.mjs +137 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-dialog.mjs +325 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-direction.mjs +9 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-dismissable-layer.mjs +210 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-dropdown-menu.mjs +253 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-guards.mjs +29 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-focus-scope.mjs +206 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-id.mjs +14 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-label.mjs +23 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-menu.mjs +812 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-popover.mjs +300 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-popper.mjs +286 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-portal.mjs +16 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-presence.mjs +128 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-primitive.mjs +141 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-roving-focus.mjs +224 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-scroll-area.mjs +721 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-select.mjs +1163 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-separator.mjs +28 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-slot.mjs +601 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-switch.mjs +152 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-tabs.mjs +189 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-callback-ref.mjs +11 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-controllable-state.mjs +69 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-effect-event.mjs +1 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-escape-keydown.mjs +17 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-is-hydrated.mjs +15 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-layout-effect.mjs +6 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-previous.mjs +14 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-use-size.mjs +39 -0
- package/admin-dist/server/_chunks/_libs/@radix-ui/react-visually-hidden.mjs +33 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/history.mjs +409 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/react-router.mjs +1711 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/react-store.mjs +56 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/router-core.mjs +4829 -0
- package/admin-dist/server/_chunks/_libs/@tanstack/store.mjs +134 -0
- package/admin-dist/server/_chunks/_libs/react-dom.mjs +10781 -0
- package/admin-dist/server/_chunks/_libs/react.mjs +513 -0
- package/admin-dist/server/_libs/aria-hidden.mjs +122 -0
- package/admin-dist/server/_libs/class-variance-authority.mjs +44 -0
- package/admin-dist/server/_libs/clsx.mjs +16 -0
- package/admin-dist/server/_libs/cmdk.mjs +315 -0
- package/admin-dist/server/_libs/convex.mjs +4841 -0
- package/admin-dist/server/_libs/cookie-es.mjs +58 -0
- package/admin-dist/server/_libs/croner.mjs +1 -0
- package/admin-dist/server/_libs/crossws.mjs +1 -0
- package/admin-dist/server/_libs/date-fns.mjs +1716 -0
- package/admin-dist/server/_libs/detect-node-es.mjs +1 -0
- package/admin-dist/server/_libs/get-nonce.mjs +9 -0
- package/admin-dist/server/_libs/h3-v2.mjs +277 -0
- package/admin-dist/server/_libs/h3.mjs +401 -0
- package/admin-dist/server/_libs/hookable.mjs +1 -0
- package/admin-dist/server/_libs/isbot.mjs +20 -0
- package/admin-dist/server/_libs/lucide-react.mjs +850 -0
- package/admin-dist/server/_libs/ohash.mjs +1 -0
- package/admin-dist/server/_libs/react-day-picker.mjs +2201 -0
- package/admin-dist/server/_libs/react-remove-scroll-bar.mjs +82 -0
- package/admin-dist/server/_libs/react-remove-scroll.mjs +328 -0
- package/admin-dist/server/_libs/react-style-singleton.mjs +69 -0
- package/admin-dist/server/_libs/rou3.mjs +8 -0
- package/admin-dist/server/_libs/seroval-plugins.mjs +58 -0
- package/admin-dist/server/_libs/seroval.mjs +1765 -0
- package/admin-dist/server/_libs/srvx.mjs +719 -0
- package/admin-dist/server/_libs/tailwind-merge.mjs +3010 -0
- package/admin-dist/server/_libs/tiny-invariant.mjs +12 -0
- package/admin-dist/server/_libs/tiny-warning.mjs +5 -0
- package/admin-dist/server/_libs/tslib.mjs +39 -0
- package/admin-dist/server/_libs/ufo.mjs +54 -0
- package/admin-dist/server/_libs/unctx.mjs +1 -0
- package/admin-dist/server/_libs/unstorage.mjs +1 -0
- package/admin-dist/server/_libs/use-callback-ref.mjs +66 -0
- package/admin-dist/server/_libs/use-sidecar.mjs +106 -0
- package/admin-dist/server/_libs/use-sync-external-store.mjs +139 -0
- package/admin-dist/server/_libs/zod.mjs +4223 -0
- package/admin-dist/server/_ssr/CmsEmptyState-DU7-7-mV.mjs +290 -0
- package/admin-dist/server/_ssr/CmsPageHeader-CseW0AHm.mjs +24 -0
- package/admin-dist/server/_ssr/CmsStatusBadge-B_pi4KCp.mjs +127 -0
- package/admin-dist/server/_ssr/CmsToolbar-X75ex6ek.mjs +49 -0
- package/admin-dist/server/_ssr/ContentEntryEditor-CepusRsA.mjs +3720 -0
- package/admin-dist/server/_ssr/ErrorState-cI-bKLez.mjs +89 -0
- package/admin-dist/server/_ssr/TaxonomyFilter-Bwrq0-cz.mjs +188 -0
- package/admin-dist/server/_ssr/_contentTypeId-BqYKEcLr.mjs +379 -0
- package/admin-dist/server/_ssr/_entryId-CRfnqeDf.mjs +161 -0
- package/admin-dist/server/_ssr/_tanstack-start-manifest_v-BwDlABVk.mjs +4 -0
- package/admin-dist/server/_ssr/alert-CVt45UUP.mjs +92 -0
- package/admin-dist/server/_ssr/badge-6BsP37vG.mjs +125 -0
- package/admin-dist/server/_ssr/command-fy8epIKf.mjs +128 -0
- package/admin-dist/server/_ssr/config.server-D7JHDcDv.mjs +117 -0
- package/admin-dist/server/_ssr/content-B5RhL7uW.mjs +532 -0
- package/admin-dist/server/_ssr/content-types-BIOqCQYN.mjs +1166 -0
- package/admin-dist/server/_ssr/index-DHSHDPt1.mjs +193 -0
- package/admin-dist/server/_ssr/index.mjs +1275 -0
- package/admin-dist/server/_ssr/label-C8Dko1j7.mjs +22 -0
- package/admin-dist/server/_ssr/media-CSx3XttC.mjs +1832 -0
- package/admin-dist/server/_ssr/new._contentTypeId-DzanEZQM.mjs +144 -0
- package/admin-dist/server/_ssr/router-DDWcF-kt.mjs +1556 -0
- package/admin-dist/server/_ssr/scroll-area-bjPYwhXN.mjs +59 -0
- package/admin-dist/server/_ssr/select-BUhDDf4T.mjs +142 -0
- package/admin-dist/server/_ssr/settings-DAsxnw2q.mjs +348 -0
- package/admin-dist/server/_ssr/start-HYkvq4Ni.mjs +4 -0
- package/admin-dist/server/_ssr/switch-BgyRtQ1Z.mjs +31 -0
- package/admin-dist/server/_ssr/tabs-DzMdRB1A.mjs +628 -0
- package/admin-dist/server/_ssr/taxonomies-C8j8g5Q5.mjs +915 -0
- package/admin-dist/server/_ssr/textarea-9jNeYJSc.mjs +18 -0
- package/admin-dist/server/_ssr/trash-DYMxwhZB.mjs +291 -0
- package/admin-dist/server/_ssr/useBreadcrumbLabel-FNSAr2Ha.mjs +16 -0
- package/admin-dist/server/_ssr/usePermissions-BJGGahrJ.mjs +68 -0
- package/admin-dist/server/favicon.ico +0 -0
- package/admin-dist/server/index.mjs +627 -0
- package/dist/cli/index.js +0 -0
- package/dist/client/admin-config.d.ts +0 -1
- package/dist/client/admin-config.d.ts.map +1 -1
- package/dist/client/admin-config.js +0 -1
- package/dist/client/admin-config.js.map +1 -1
- package/dist/client/adminApi.d.ts.map +1 -1
- package/dist/client/agentTools.d.ts +1237 -135
- package/dist/client/agentTools.d.ts.map +1 -1
- package/dist/client/agentTools.js +33 -9
- package/dist/client/agentTools.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +9 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/mediaAssets.d.ts +35 -0
- package/dist/component/mediaAssets.d.ts.map +1 -1
- package/dist/component/mediaAssets.js +81 -0
- package/dist/component/mediaAssets.js.map +1 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +2 -1
- package/dist/test.js.map +1 -1
- package/package.json +9 -5
- package/dist/component/auditLog.d.ts +0 -410
- package/dist/component/auditLog.d.ts.map +0 -1
- package/dist/component/auditLog.js +0 -607
- package/dist/component/auditLog.js.map +0 -1
- package/dist/component/types.d.ts +0 -4
- package/dist/component/types.d.ts.map +0 -1
- package/dist/component/types.js +0 -2
- package/dist/component/types.js.map +0 -1
- package/src/cli/commands/admin.ts +0 -104
- package/src/cli/index.ts +0 -21
- package/src/cli/utils/detectConvexUrl.ts +0 -54
- package/src/cli/utils/openBrowser.ts +0 -16
- package/src/client/admin-config.ts +0 -138
- package/src/client/adminApi.ts +0 -942
- package/src/client/agentTools.ts +0 -1311
- package/src/client/argTypes.ts +0 -316
- package/src/client/field-types.ts +0 -187
- package/src/client/index.ts +0 -1301
- package/src/client/queryBuilder.ts +0 -1100
- package/src/client/schema/codegen.ts +0 -500
- package/src/client/schema/defineContentType.ts +0 -501
- package/src/client/schema/index.ts +0 -169
- package/src/client/schema/schemaDrift.ts +0 -574
- package/src/client/schema/typedClient.ts +0 -688
- package/src/client/schema/types.ts +0 -666
- package/src/client/types.ts +0 -723
- package/src/client/workflows.ts +0 -141
- package/src/client/wrapper.ts +0 -4304
- package/src/component/_generated/api.ts +0 -140
- package/src/component/_generated/component.ts +0 -5029
- package/src/component/_generated/dataModel.ts +0 -60
- package/src/component/_generated/server.ts +0 -156
- package/src/component/authorization.ts +0 -647
- package/src/component/authorizationHooks.ts +0 -668
- package/src/component/bulkOperations.ts +0 -687
- package/src/component/contentEntries.ts +0 -1976
- package/src/component/contentEntryMutations.ts +0 -1223
- package/src/component/contentEntryValidation.ts +0 -707
- package/src/component/contentLock.ts +0 -550
- package/src/component/contentTypeMigration.ts +0 -1064
- package/src/component/contentTypeMutations.ts +0 -969
- package/src/component/contentTypes.ts +0 -346
- package/src/component/convex.config.ts +0 -44
- package/src/component/documentTypes.ts +0 -240
- package/src/component/eventEmitter.ts +0 -485
- package/src/component/exportImport.ts +0 -1169
- package/src/component/index.ts +0 -491
- package/src/component/lib/deepReferenceResolver.ts +0 -999
- package/src/component/lib/errors.ts +0 -816
- package/src/component/lib/index.ts +0 -145
- package/src/component/lib/mediaReferenceResolver.ts +0 -495
- package/src/component/lib/metadataExtractor.ts +0 -792
- package/src/component/lib/mutationAuth.ts +0 -199
- package/src/component/lib/queries.ts +0 -79
- package/src/component/lib/ragContentChunker.ts +0 -1371
- package/src/component/lib/referenceResolver.ts +0 -430
- package/src/component/lib/slugGenerator.ts +0 -262
- package/src/component/lib/slugUniqueness.ts +0 -333
- package/src/component/lib/softDelete.ts +0 -44
- package/src/component/localeFallbackChain.ts +0 -673
- package/src/component/localeFields.ts +0 -896
- package/src/component/mediaAssetMutations.ts +0 -725
- package/src/component/mediaAssets.ts +0 -932
- package/src/component/mediaFolderMutations.ts +0 -1046
- package/src/component/mediaUploadMutations.ts +0 -224
- package/src/component/mediaVariantMutations.ts +0 -900
- package/src/component/mediaVariants.ts +0 -793
- package/src/component/ragContentIndexer.ts +0 -1067
- package/src/component/rateLimitHooks.ts +0 -572
- package/src/component/roles.ts +0 -1360
- package/src/component/scheduledPublish.ts +0 -358
- package/src/component/schema.ts +0 -617
- package/src/component/taxonomies.ts +0 -949
- package/src/component/taxonomyMutations.ts +0 -1210
- package/src/component/trash.ts +0 -724
- package/src/component/userContext.ts +0 -898
- package/src/component/validation.ts +0 -1388
- package/src/component/validators.ts +0 -949
- package/src/component/versionMutations.ts +0 -392
- package/src/component/webhookTrigger.ts +0 -1922
- package/src/react/index.ts +0 -898
- package/src/test.ts +0 -1580
|
@@ -0,0 +1,4841 @@
|
|
|
1
|
+
import { r as reactExports, a as React } from "../_chunks/_libs/react.mjs";
|
|
2
|
+
var lookup = [];
|
|
3
|
+
var revLookup = [];
|
|
4
|
+
var Arr = Uint8Array;
|
|
5
|
+
var code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
6
|
+
for (var i = 0, len = code.length; i < len; ++i) {
|
|
7
|
+
lookup[i] = code[i];
|
|
8
|
+
revLookup[code.charCodeAt(i)] = i;
|
|
9
|
+
}
|
|
10
|
+
revLookup["-".charCodeAt(0)] = 62;
|
|
11
|
+
revLookup["_".charCodeAt(0)] = 63;
|
|
12
|
+
function getLens(b64) {
|
|
13
|
+
var len = b64.length;
|
|
14
|
+
if (len % 4 > 0) {
|
|
15
|
+
throw new Error("Invalid string. Length must be a multiple of 4");
|
|
16
|
+
}
|
|
17
|
+
var validLen = b64.indexOf("=");
|
|
18
|
+
if (validLen === -1) validLen = len;
|
|
19
|
+
var placeHoldersLen = validLen === len ? 0 : 4 - validLen % 4;
|
|
20
|
+
return [validLen, placeHoldersLen];
|
|
21
|
+
}
|
|
22
|
+
function _byteLength(_b64, validLen, placeHoldersLen) {
|
|
23
|
+
return (validLen + placeHoldersLen) * 3 / 4 - placeHoldersLen;
|
|
24
|
+
}
|
|
25
|
+
function toByteArray(b64) {
|
|
26
|
+
var tmp;
|
|
27
|
+
var lens = getLens(b64);
|
|
28
|
+
var validLen = lens[0];
|
|
29
|
+
var placeHoldersLen = lens[1];
|
|
30
|
+
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen));
|
|
31
|
+
var curByte = 0;
|
|
32
|
+
var len = placeHoldersLen > 0 ? validLen - 4 : validLen;
|
|
33
|
+
var i;
|
|
34
|
+
for (i = 0; i < len; i += 4) {
|
|
35
|
+
tmp = revLookup[b64.charCodeAt(i)] << 18 | revLookup[b64.charCodeAt(i + 1)] << 12 | revLookup[b64.charCodeAt(i + 2)] << 6 | revLookup[b64.charCodeAt(i + 3)];
|
|
36
|
+
arr[curByte++] = tmp >> 16 & 255;
|
|
37
|
+
arr[curByte++] = tmp >> 8 & 255;
|
|
38
|
+
arr[curByte++] = tmp & 255;
|
|
39
|
+
}
|
|
40
|
+
if (placeHoldersLen === 2) {
|
|
41
|
+
tmp = revLookup[b64.charCodeAt(i)] << 2 | revLookup[b64.charCodeAt(i + 1)] >> 4;
|
|
42
|
+
arr[curByte++] = tmp & 255;
|
|
43
|
+
}
|
|
44
|
+
if (placeHoldersLen === 1) {
|
|
45
|
+
tmp = revLookup[b64.charCodeAt(i)] << 10 | revLookup[b64.charCodeAt(i + 1)] << 4 | revLookup[b64.charCodeAt(i + 2)] >> 2;
|
|
46
|
+
arr[curByte++] = tmp >> 8 & 255;
|
|
47
|
+
arr[curByte++] = tmp & 255;
|
|
48
|
+
}
|
|
49
|
+
return arr;
|
|
50
|
+
}
|
|
51
|
+
function tripletToBase64(num) {
|
|
52
|
+
return lookup[num >> 18 & 63] + lookup[num >> 12 & 63] + lookup[num >> 6 & 63] + lookup[num & 63];
|
|
53
|
+
}
|
|
54
|
+
function encodeChunk(uint8, start, end) {
|
|
55
|
+
var tmp;
|
|
56
|
+
var output = [];
|
|
57
|
+
for (var i = start; i < end; i += 3) {
|
|
58
|
+
tmp = (uint8[i] << 16 & 16711680) + (uint8[i + 1] << 8 & 65280) + (uint8[i + 2] & 255);
|
|
59
|
+
output.push(tripletToBase64(tmp));
|
|
60
|
+
}
|
|
61
|
+
return output.join("");
|
|
62
|
+
}
|
|
63
|
+
function fromByteArray(uint8) {
|
|
64
|
+
var tmp;
|
|
65
|
+
var len = uint8.length;
|
|
66
|
+
var extraBytes = len % 3;
|
|
67
|
+
var parts = [];
|
|
68
|
+
var maxChunkLength = 16383;
|
|
69
|
+
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
|
|
70
|
+
parts.push(
|
|
71
|
+
encodeChunk(
|
|
72
|
+
uint8,
|
|
73
|
+
i,
|
|
74
|
+
i + maxChunkLength > len2 ? len2 : i + maxChunkLength
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (extraBytes === 1) {
|
|
79
|
+
tmp = uint8[len - 1];
|
|
80
|
+
parts.push(lookup[tmp >> 2] + lookup[tmp << 4 & 63] + "==");
|
|
81
|
+
} else if (extraBytes === 2) {
|
|
82
|
+
tmp = (uint8[len - 2] << 8) + uint8[len - 1];
|
|
83
|
+
parts.push(
|
|
84
|
+
lookup[tmp >> 10] + lookup[tmp >> 4 & 63] + lookup[tmp << 2 & 63] + "="
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return parts.join("");
|
|
88
|
+
}
|
|
89
|
+
function parseArgs(args) {
|
|
90
|
+
if (args === void 0) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
if (!isSimpleObject(args)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`The arguments to a Convex function must be an object. Received: ${args}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return args;
|
|
99
|
+
}
|
|
100
|
+
function validateDeploymentUrl(deploymentUrl) {
|
|
101
|
+
if (typeof deploymentUrl === "undefined") {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Client created with undefined deployment address. If you used an environment variable, check that it's set.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (typeof deploymentUrl !== "string") {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Invalid deployment address: found ${deploymentUrl}".`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (!(deploymentUrl.startsWith("http:") || deploymentUrl.startsWith("https:"))) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Invalid deployment address: Must start with "https://" or "http://". Found "${deploymentUrl}".`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
new URL(deploymentUrl);
|
|
118
|
+
} catch {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Invalid deployment address: "${deploymentUrl}" is not a valid URL. If you believe this URL is correct, use the \`skipConvexDeploymentUrlCheck\` option to bypass this.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (deploymentUrl.endsWith(".convex.site")) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`Invalid deployment address: "${deploymentUrl}" ends with .convex.site, which is used for HTTP Actions. Convex deployment URLs typically end with .convex.cloud? If you believe this URL is correct, use the \`skipConvexDeploymentUrlCheck\` option to bypass this.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function isSimpleObject(value) {
|
|
130
|
+
const isObject = typeof value === "object";
|
|
131
|
+
const prototype = Object.getPrototypeOf(value);
|
|
132
|
+
const isSimple = prototype === null || prototype === Object.prototype || // Objects generated from other contexts (e.g. across Node.js `vm` modules) will not satisfy the previous
|
|
133
|
+
// conditions but are still simple objects.
|
|
134
|
+
prototype?.constructor?.name === "Object";
|
|
135
|
+
return isObject && isSimple;
|
|
136
|
+
}
|
|
137
|
+
const LITTLE_ENDIAN = true;
|
|
138
|
+
const MIN_INT64 = BigInt("-9223372036854775808");
|
|
139
|
+
const MAX_INT64 = BigInt("9223372036854775807");
|
|
140
|
+
const ZERO = BigInt("0");
|
|
141
|
+
const EIGHT = BigInt("8");
|
|
142
|
+
const TWOFIFTYSIX = BigInt("256");
|
|
143
|
+
function isSpecial(n) {
|
|
144
|
+
return Number.isNaN(n) || !Number.isFinite(n) || Object.is(n, -0);
|
|
145
|
+
}
|
|
146
|
+
function slowBigIntToBase64(value) {
|
|
147
|
+
if (value < ZERO) {
|
|
148
|
+
value -= MIN_INT64 + MIN_INT64;
|
|
149
|
+
}
|
|
150
|
+
let hex = value.toString(16);
|
|
151
|
+
if (hex.length % 2 === 1) hex = "0" + hex;
|
|
152
|
+
const bytes = new Uint8Array(new ArrayBuffer(8));
|
|
153
|
+
let i = 0;
|
|
154
|
+
for (const hexByte of hex.match(/.{2}/g).reverse()) {
|
|
155
|
+
bytes.set([parseInt(hexByte, 16)], i++);
|
|
156
|
+
value >>= EIGHT;
|
|
157
|
+
}
|
|
158
|
+
return fromByteArray(bytes);
|
|
159
|
+
}
|
|
160
|
+
function slowBase64ToBigInt(encoded) {
|
|
161
|
+
const integerBytes = toByteArray(encoded);
|
|
162
|
+
if (integerBytes.byteLength !== 8) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Received ${integerBytes.byteLength} bytes, expected 8 for $integer`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
let value = ZERO;
|
|
168
|
+
let power = ZERO;
|
|
169
|
+
for (const byte of integerBytes) {
|
|
170
|
+
value += BigInt(byte) * TWOFIFTYSIX ** power;
|
|
171
|
+
power++;
|
|
172
|
+
}
|
|
173
|
+
if (value > MAX_INT64) {
|
|
174
|
+
value += MIN_INT64 + MIN_INT64;
|
|
175
|
+
}
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
function modernBigIntToBase64(value) {
|
|
179
|
+
if (value < MIN_INT64 || MAX_INT64 < value) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`BigInt ${value} does not fit into a 64-bit signed integer.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const buffer = new ArrayBuffer(8);
|
|
185
|
+
new DataView(buffer).setBigInt64(0, value, true);
|
|
186
|
+
return fromByteArray(new Uint8Array(buffer));
|
|
187
|
+
}
|
|
188
|
+
function modernBase64ToBigInt(encoded) {
|
|
189
|
+
const integerBytes = toByteArray(encoded);
|
|
190
|
+
if (integerBytes.byteLength !== 8) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Received ${integerBytes.byteLength} bytes, expected 8 for $integer`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const intBytesView = new DataView(integerBytes.buffer);
|
|
196
|
+
return intBytesView.getBigInt64(0, true);
|
|
197
|
+
}
|
|
198
|
+
const bigIntToBase64 = DataView.prototype.setBigInt64 ? modernBigIntToBase64 : slowBigIntToBase64;
|
|
199
|
+
const base64ToBigInt = DataView.prototype.getBigInt64 ? modernBase64ToBigInt : slowBase64ToBigInt;
|
|
200
|
+
const MAX_IDENTIFIER_LEN = 1024;
|
|
201
|
+
function validateObjectField(k) {
|
|
202
|
+
if (k.length > MAX_IDENTIFIER_LEN) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Field name ${k} exceeds maximum field name length ${MAX_IDENTIFIER_LEN}.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (k.startsWith("$")) {
|
|
208
|
+
throw new Error(`Field name ${k} starts with a '$', which is reserved.`);
|
|
209
|
+
}
|
|
210
|
+
for (let i = 0; i < k.length; i += 1) {
|
|
211
|
+
const charCode = k.charCodeAt(i);
|
|
212
|
+
if (charCode < 32 || charCode >= 127) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`Field name ${k} has invalid character '${k[i]}': Field names can only contain non-control ASCII characters`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function jsonToConvex(value) {
|
|
220
|
+
if (value === null) {
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
if (typeof value === "boolean") {
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
if (typeof value === "number") {
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
if (typeof value === "string") {
|
|
230
|
+
return value;
|
|
231
|
+
}
|
|
232
|
+
if (Array.isArray(value)) {
|
|
233
|
+
return value.map((value2) => jsonToConvex(value2));
|
|
234
|
+
}
|
|
235
|
+
if (typeof value !== "object") {
|
|
236
|
+
throw new Error(`Unexpected type of ${value}`);
|
|
237
|
+
}
|
|
238
|
+
const entries = Object.entries(value);
|
|
239
|
+
if (entries.length === 1) {
|
|
240
|
+
const key = entries[0][0];
|
|
241
|
+
if (key === "$bytes") {
|
|
242
|
+
if (typeof value.$bytes !== "string") {
|
|
243
|
+
throw new Error(`Malformed $bytes field on ${value}`);
|
|
244
|
+
}
|
|
245
|
+
return toByteArray(value.$bytes).buffer;
|
|
246
|
+
}
|
|
247
|
+
if (key === "$integer") {
|
|
248
|
+
if (typeof value.$integer !== "string") {
|
|
249
|
+
throw new Error(`Malformed $integer field on ${value}`);
|
|
250
|
+
}
|
|
251
|
+
return base64ToBigInt(value.$integer);
|
|
252
|
+
}
|
|
253
|
+
if (key === "$float") {
|
|
254
|
+
if (typeof value.$float !== "string") {
|
|
255
|
+
throw new Error(`Malformed $float field on ${value}`);
|
|
256
|
+
}
|
|
257
|
+
const floatBytes = toByteArray(value.$float);
|
|
258
|
+
if (floatBytes.byteLength !== 8) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Received ${floatBytes.byteLength} bytes, expected 8 for $float`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const floatBytesView = new DataView(floatBytes.buffer);
|
|
264
|
+
const float = floatBytesView.getFloat64(0, LITTLE_ENDIAN);
|
|
265
|
+
if (!isSpecial(float)) {
|
|
266
|
+
throw new Error(`Float ${float} should be encoded as a number`);
|
|
267
|
+
}
|
|
268
|
+
return float;
|
|
269
|
+
}
|
|
270
|
+
if (key === "$set") {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`Received a Set which is no longer supported as a Convex type.`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (key === "$map") {
|
|
276
|
+
throw new Error(
|
|
277
|
+
`Received a Map which is no longer supported as a Convex type.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const out = {};
|
|
282
|
+
for (const [k, v2] of Object.entries(value)) {
|
|
283
|
+
validateObjectField(k);
|
|
284
|
+
out[k] = jsonToConvex(v2);
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
const MAX_VALUE_FOR_ERROR_LEN = 16384;
|
|
289
|
+
function stringifyValueForError(value) {
|
|
290
|
+
const str = JSON.stringify(value, (_key, value2) => {
|
|
291
|
+
if (value2 === void 0) {
|
|
292
|
+
return "undefined";
|
|
293
|
+
}
|
|
294
|
+
if (typeof value2 === "bigint") {
|
|
295
|
+
return `${value2.toString()}n`;
|
|
296
|
+
}
|
|
297
|
+
return value2;
|
|
298
|
+
});
|
|
299
|
+
if (str.length > MAX_VALUE_FOR_ERROR_LEN) {
|
|
300
|
+
const rest = "[...truncated]";
|
|
301
|
+
let truncateAt = MAX_VALUE_FOR_ERROR_LEN - rest.length;
|
|
302
|
+
const codePoint = str.codePointAt(truncateAt - 1);
|
|
303
|
+
if (codePoint !== void 0 && codePoint > 65535) {
|
|
304
|
+
truncateAt -= 1;
|
|
305
|
+
}
|
|
306
|
+
return str.substring(0, truncateAt) + rest;
|
|
307
|
+
}
|
|
308
|
+
return str;
|
|
309
|
+
}
|
|
310
|
+
function convexToJsonInternal(value, originalValue, context, includeTopLevelUndefined) {
|
|
311
|
+
if (value === void 0) {
|
|
312
|
+
const contextText = context && ` (present at path ${context} in original object ${stringifyValueForError(
|
|
313
|
+
originalValue
|
|
314
|
+
)})`;
|
|
315
|
+
throw new Error(
|
|
316
|
+
`undefined is not a valid Convex value${contextText}. To learn about Convex's supported types, see https://docs.convex.dev/using/types.`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
if (value === null) {
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
if (typeof value === "bigint") {
|
|
323
|
+
if (value < MIN_INT64 || MAX_INT64 < value) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`BigInt ${value} does not fit into a 64-bit signed integer.`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return { $integer: bigIntToBase64(value) };
|
|
329
|
+
}
|
|
330
|
+
if (typeof value === "number") {
|
|
331
|
+
if (isSpecial(value)) {
|
|
332
|
+
const buffer = new ArrayBuffer(8);
|
|
333
|
+
new DataView(buffer).setFloat64(0, value, LITTLE_ENDIAN);
|
|
334
|
+
return { $float: fromByteArray(new Uint8Array(buffer)) };
|
|
335
|
+
} else {
|
|
336
|
+
return value;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if (typeof value === "boolean") {
|
|
340
|
+
return value;
|
|
341
|
+
}
|
|
342
|
+
if (typeof value === "string") {
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
if (value instanceof ArrayBuffer) {
|
|
346
|
+
return { $bytes: fromByteArray(new Uint8Array(value)) };
|
|
347
|
+
}
|
|
348
|
+
if (Array.isArray(value)) {
|
|
349
|
+
return value.map(
|
|
350
|
+
(value2, i) => convexToJsonInternal(value2, originalValue, context + `[${i}]`)
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
if (value instanceof Set) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
errorMessageForUnsupportedType(context, "Set", [...value], originalValue)
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (value instanceof Map) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
errorMessageForUnsupportedType(context, "Map", [...value], originalValue)
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (!isSimpleObject(value)) {
|
|
364
|
+
const theType = value?.constructor?.name;
|
|
365
|
+
const typeName = theType ? `${theType} ` : "";
|
|
366
|
+
throw new Error(
|
|
367
|
+
errorMessageForUnsupportedType(context, typeName, value, originalValue)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const out = {};
|
|
371
|
+
const entries = Object.entries(value);
|
|
372
|
+
entries.sort(([k1, _v1], [k2, _v2]) => k1 === k2 ? 0 : k1 < k2 ? -1 : 1);
|
|
373
|
+
for (const [k, v2] of entries) {
|
|
374
|
+
if (v2 !== void 0) {
|
|
375
|
+
validateObjectField(k);
|
|
376
|
+
out[k] = convexToJsonInternal(v2, originalValue, context + `.${k}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
function errorMessageForUnsupportedType(context, typeName, value, originalValue) {
|
|
382
|
+
if (context) {
|
|
383
|
+
return `${typeName}${stringifyValueForError(
|
|
384
|
+
value
|
|
385
|
+
)} is not a supported Convex type (present at path ${context} in original object ${stringifyValueForError(
|
|
386
|
+
originalValue
|
|
387
|
+
)}). To learn about Convex's supported types, see https://docs.convex.dev/using/types.`;
|
|
388
|
+
} else {
|
|
389
|
+
return `${typeName}${stringifyValueForError(
|
|
390
|
+
value
|
|
391
|
+
)} is not a supported Convex type.`;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function convexToJson(value) {
|
|
395
|
+
return convexToJsonInternal(value, value, "");
|
|
396
|
+
}
|
|
397
|
+
var __defProp$d = Object.defineProperty;
|
|
398
|
+
var __defNormalProp$d = (obj, key, value) => key in obj ? __defProp$d(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
399
|
+
var __publicField$d = (obj, key, value) => __defNormalProp$d(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
400
|
+
const UNDEFINED_VALIDATOR_ERROR_URL = "https://docs.convex.dev/error#undefined-validator";
|
|
401
|
+
function throwUndefinedValidatorError(context, fieldName) {
|
|
402
|
+
const fieldInfo = fieldName !== void 0 ? ` for field "${fieldName}"` : "";
|
|
403
|
+
throw new Error(
|
|
404
|
+
`A validator is undefined${fieldInfo} in ${context}. This is often caused by circular imports. See ${UNDEFINED_VALIDATOR_ERROR_URL} for details.`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
class BaseValidator {
|
|
408
|
+
constructor({ isOptional }) {
|
|
409
|
+
__publicField$d(this, "type");
|
|
410
|
+
__publicField$d(this, "fieldPaths");
|
|
411
|
+
__publicField$d(this, "isOptional");
|
|
412
|
+
__publicField$d(this, "isConvexValidator");
|
|
413
|
+
this.isOptional = isOptional;
|
|
414
|
+
this.isConvexValidator = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
class VId extends BaseValidator {
|
|
418
|
+
/**
|
|
419
|
+
* Usually you'd use `v.id(tableName)` instead.
|
|
420
|
+
*/
|
|
421
|
+
constructor({
|
|
422
|
+
isOptional,
|
|
423
|
+
tableName
|
|
424
|
+
}) {
|
|
425
|
+
super({ isOptional });
|
|
426
|
+
__publicField$d(this, "tableName");
|
|
427
|
+
__publicField$d(this, "kind", "id");
|
|
428
|
+
if (typeof tableName !== "string") {
|
|
429
|
+
throw new Error("v.id(tableName) requires a string");
|
|
430
|
+
}
|
|
431
|
+
this.tableName = tableName;
|
|
432
|
+
}
|
|
433
|
+
/** @internal */
|
|
434
|
+
get json() {
|
|
435
|
+
return { type: "id", tableName: this.tableName };
|
|
436
|
+
}
|
|
437
|
+
/** @internal */
|
|
438
|
+
asOptional() {
|
|
439
|
+
return new VId({
|
|
440
|
+
isOptional: "optional",
|
|
441
|
+
tableName: this.tableName
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
class VFloat64 extends BaseValidator {
|
|
446
|
+
constructor() {
|
|
447
|
+
super(...arguments);
|
|
448
|
+
__publicField$d(this, "kind", "float64");
|
|
449
|
+
}
|
|
450
|
+
/** @internal */
|
|
451
|
+
get json() {
|
|
452
|
+
return { type: "number" };
|
|
453
|
+
}
|
|
454
|
+
/** @internal */
|
|
455
|
+
asOptional() {
|
|
456
|
+
return new VFloat64({
|
|
457
|
+
isOptional: "optional"
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
class VInt64 extends BaseValidator {
|
|
462
|
+
constructor() {
|
|
463
|
+
super(...arguments);
|
|
464
|
+
__publicField$d(this, "kind", "int64");
|
|
465
|
+
}
|
|
466
|
+
/** @internal */
|
|
467
|
+
get json() {
|
|
468
|
+
return { type: "bigint" };
|
|
469
|
+
}
|
|
470
|
+
/** @internal */
|
|
471
|
+
asOptional() {
|
|
472
|
+
return new VInt64({ isOptional: "optional" });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
class VBoolean extends BaseValidator {
|
|
476
|
+
constructor() {
|
|
477
|
+
super(...arguments);
|
|
478
|
+
__publicField$d(this, "kind", "boolean");
|
|
479
|
+
}
|
|
480
|
+
/** @internal */
|
|
481
|
+
get json() {
|
|
482
|
+
return { type: this.kind };
|
|
483
|
+
}
|
|
484
|
+
/** @internal */
|
|
485
|
+
asOptional() {
|
|
486
|
+
return new VBoolean({
|
|
487
|
+
isOptional: "optional"
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
class VBytes extends BaseValidator {
|
|
492
|
+
constructor() {
|
|
493
|
+
super(...arguments);
|
|
494
|
+
__publicField$d(this, "kind", "bytes");
|
|
495
|
+
}
|
|
496
|
+
/** @internal */
|
|
497
|
+
get json() {
|
|
498
|
+
return { type: this.kind };
|
|
499
|
+
}
|
|
500
|
+
/** @internal */
|
|
501
|
+
asOptional() {
|
|
502
|
+
return new VBytes({ isOptional: "optional" });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
class VString extends BaseValidator {
|
|
506
|
+
constructor() {
|
|
507
|
+
super(...arguments);
|
|
508
|
+
__publicField$d(this, "kind", "string");
|
|
509
|
+
}
|
|
510
|
+
/** @internal */
|
|
511
|
+
get json() {
|
|
512
|
+
return { type: this.kind };
|
|
513
|
+
}
|
|
514
|
+
/** @internal */
|
|
515
|
+
asOptional() {
|
|
516
|
+
return new VString({
|
|
517
|
+
isOptional: "optional"
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
class VNull extends BaseValidator {
|
|
522
|
+
constructor() {
|
|
523
|
+
super(...arguments);
|
|
524
|
+
__publicField$d(this, "kind", "null");
|
|
525
|
+
}
|
|
526
|
+
/** @internal */
|
|
527
|
+
get json() {
|
|
528
|
+
return { type: this.kind };
|
|
529
|
+
}
|
|
530
|
+
/** @internal */
|
|
531
|
+
asOptional() {
|
|
532
|
+
return new VNull({ isOptional: "optional" });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
class VAny extends BaseValidator {
|
|
536
|
+
constructor() {
|
|
537
|
+
super(...arguments);
|
|
538
|
+
__publicField$d(this, "kind", "any");
|
|
539
|
+
}
|
|
540
|
+
/** @internal */
|
|
541
|
+
get json() {
|
|
542
|
+
return {
|
|
543
|
+
type: this.kind
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
/** @internal */
|
|
547
|
+
asOptional() {
|
|
548
|
+
return new VAny({
|
|
549
|
+
isOptional: "optional"
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
class VObject extends BaseValidator {
|
|
554
|
+
/**
|
|
555
|
+
* Usually you'd use `v.object({ ... })` instead.
|
|
556
|
+
*/
|
|
557
|
+
constructor({
|
|
558
|
+
isOptional,
|
|
559
|
+
fields
|
|
560
|
+
}) {
|
|
561
|
+
super({ isOptional });
|
|
562
|
+
__publicField$d(this, "fields");
|
|
563
|
+
__publicField$d(this, "kind", "object");
|
|
564
|
+
globalThis.Object.entries(fields).forEach(([fieldName, validator]) => {
|
|
565
|
+
if (validator === void 0) {
|
|
566
|
+
throwUndefinedValidatorError("v.object()", fieldName);
|
|
567
|
+
}
|
|
568
|
+
if (!validator.isConvexValidator) {
|
|
569
|
+
throw new Error("v.object() entries must be validators");
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
this.fields = fields;
|
|
573
|
+
}
|
|
574
|
+
/** @internal */
|
|
575
|
+
get json() {
|
|
576
|
+
return {
|
|
577
|
+
type: this.kind,
|
|
578
|
+
value: globalThis.Object.fromEntries(
|
|
579
|
+
globalThis.Object.entries(this.fields).map(([k, v2]) => [
|
|
580
|
+
k,
|
|
581
|
+
{
|
|
582
|
+
fieldType: v2.json,
|
|
583
|
+
optional: v2.isOptional === "optional" ? true : false
|
|
584
|
+
}
|
|
585
|
+
])
|
|
586
|
+
)
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
/** @internal */
|
|
590
|
+
asOptional() {
|
|
591
|
+
return new VObject({
|
|
592
|
+
isOptional: "optional",
|
|
593
|
+
fields: this.fields
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Create a new VObject with the specified fields omitted.
|
|
598
|
+
* @param fields The field names to omit from this VObject.
|
|
599
|
+
*/
|
|
600
|
+
omit(...fields) {
|
|
601
|
+
const newFields = { ...this.fields };
|
|
602
|
+
for (const field of fields) {
|
|
603
|
+
delete newFields[field];
|
|
604
|
+
}
|
|
605
|
+
return new VObject({
|
|
606
|
+
isOptional: this.isOptional,
|
|
607
|
+
fields: newFields
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Create a new VObject with only the specified fields.
|
|
612
|
+
* @param fields The field names to pick from this VObject.
|
|
613
|
+
*/
|
|
614
|
+
pick(...fields) {
|
|
615
|
+
const newFields = {};
|
|
616
|
+
for (const field of fields) {
|
|
617
|
+
newFields[field] = this.fields[field];
|
|
618
|
+
}
|
|
619
|
+
return new VObject({
|
|
620
|
+
isOptional: this.isOptional,
|
|
621
|
+
fields: newFields
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Create a new VObject with all fields marked as optional.
|
|
626
|
+
*/
|
|
627
|
+
partial() {
|
|
628
|
+
const newFields = {};
|
|
629
|
+
for (const [key, validator] of globalThis.Object.entries(this.fields)) {
|
|
630
|
+
newFields[key] = validator.asOptional();
|
|
631
|
+
}
|
|
632
|
+
return new VObject({
|
|
633
|
+
isOptional: this.isOptional,
|
|
634
|
+
fields: newFields
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Create a new VObject with additional fields merged in.
|
|
639
|
+
* @param fields An object with additional validators to merge into this VObject.
|
|
640
|
+
*/
|
|
641
|
+
extend(fields) {
|
|
642
|
+
return new VObject({
|
|
643
|
+
isOptional: this.isOptional,
|
|
644
|
+
fields: { ...this.fields, ...fields }
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
class VLiteral extends BaseValidator {
|
|
649
|
+
/**
|
|
650
|
+
* Usually you'd use `v.literal(value)` instead.
|
|
651
|
+
*/
|
|
652
|
+
constructor({ isOptional, value }) {
|
|
653
|
+
super({ isOptional });
|
|
654
|
+
__publicField$d(this, "value");
|
|
655
|
+
__publicField$d(this, "kind", "literal");
|
|
656
|
+
if (typeof value !== "string" && typeof value !== "boolean" && typeof value !== "number" && typeof value !== "bigint") {
|
|
657
|
+
throw new Error("v.literal(value) must be a string, number, or boolean");
|
|
658
|
+
}
|
|
659
|
+
this.value = value;
|
|
660
|
+
}
|
|
661
|
+
/** @internal */
|
|
662
|
+
get json() {
|
|
663
|
+
return {
|
|
664
|
+
type: this.kind,
|
|
665
|
+
value: convexToJson(this.value)
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
/** @internal */
|
|
669
|
+
asOptional() {
|
|
670
|
+
return new VLiteral({
|
|
671
|
+
isOptional: "optional",
|
|
672
|
+
value: this.value
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
class VArray extends BaseValidator {
|
|
677
|
+
/**
|
|
678
|
+
* Usually you'd use `v.array(element)` instead.
|
|
679
|
+
*/
|
|
680
|
+
constructor({
|
|
681
|
+
isOptional,
|
|
682
|
+
element
|
|
683
|
+
}) {
|
|
684
|
+
super({ isOptional });
|
|
685
|
+
__publicField$d(this, "element");
|
|
686
|
+
__publicField$d(this, "kind", "array");
|
|
687
|
+
if (element === void 0) {
|
|
688
|
+
throwUndefinedValidatorError("v.array()");
|
|
689
|
+
}
|
|
690
|
+
this.element = element;
|
|
691
|
+
}
|
|
692
|
+
/** @internal */
|
|
693
|
+
get json() {
|
|
694
|
+
return {
|
|
695
|
+
type: this.kind,
|
|
696
|
+
value: this.element.json
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/** @internal */
|
|
700
|
+
asOptional() {
|
|
701
|
+
return new VArray({
|
|
702
|
+
isOptional: "optional",
|
|
703
|
+
element: this.element
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
class VRecord extends BaseValidator {
|
|
708
|
+
/**
|
|
709
|
+
* Usually you'd use `v.record(key, value)` instead.
|
|
710
|
+
*/
|
|
711
|
+
constructor({
|
|
712
|
+
isOptional,
|
|
713
|
+
key,
|
|
714
|
+
value
|
|
715
|
+
}) {
|
|
716
|
+
super({ isOptional });
|
|
717
|
+
__publicField$d(this, "key");
|
|
718
|
+
__publicField$d(this, "value");
|
|
719
|
+
__publicField$d(this, "kind", "record");
|
|
720
|
+
if (key === void 0) {
|
|
721
|
+
throwUndefinedValidatorError("v.record()", "key");
|
|
722
|
+
}
|
|
723
|
+
if (value === void 0) {
|
|
724
|
+
throwUndefinedValidatorError("v.record()", "value");
|
|
725
|
+
}
|
|
726
|
+
if (key.isOptional === "optional") {
|
|
727
|
+
throw new Error("Record validator cannot have optional keys");
|
|
728
|
+
}
|
|
729
|
+
if (value.isOptional === "optional") {
|
|
730
|
+
throw new Error("Record validator cannot have optional values");
|
|
731
|
+
}
|
|
732
|
+
if (!key.isConvexValidator || !value.isConvexValidator) {
|
|
733
|
+
throw new Error("Key and value of v.record() but be validators");
|
|
734
|
+
}
|
|
735
|
+
this.key = key;
|
|
736
|
+
this.value = value;
|
|
737
|
+
}
|
|
738
|
+
/** @internal */
|
|
739
|
+
get json() {
|
|
740
|
+
return {
|
|
741
|
+
type: this.kind,
|
|
742
|
+
// This cast is needed because TypeScript thinks the key type is too wide
|
|
743
|
+
keys: this.key.json,
|
|
744
|
+
values: {
|
|
745
|
+
fieldType: this.value.json,
|
|
746
|
+
optional: false
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
/** @internal */
|
|
751
|
+
asOptional() {
|
|
752
|
+
return new VRecord({
|
|
753
|
+
isOptional: "optional",
|
|
754
|
+
key: this.key,
|
|
755
|
+
value: this.value
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
class VUnion extends BaseValidator {
|
|
760
|
+
/**
|
|
761
|
+
* Usually you'd use `v.union(...members)` instead.
|
|
762
|
+
*/
|
|
763
|
+
constructor({ isOptional, members }) {
|
|
764
|
+
super({ isOptional });
|
|
765
|
+
__publicField$d(this, "members");
|
|
766
|
+
__publicField$d(this, "kind", "union");
|
|
767
|
+
members.forEach((member, index) => {
|
|
768
|
+
if (member === void 0) {
|
|
769
|
+
throwUndefinedValidatorError("v.union()", `member at index ${index}`);
|
|
770
|
+
}
|
|
771
|
+
if (!member.isConvexValidator) {
|
|
772
|
+
throw new Error("All members of v.union() must be validators");
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
this.members = members;
|
|
776
|
+
}
|
|
777
|
+
/** @internal */
|
|
778
|
+
get json() {
|
|
779
|
+
return {
|
|
780
|
+
type: this.kind,
|
|
781
|
+
value: this.members.map((v2) => v2.json)
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
/** @internal */
|
|
785
|
+
asOptional() {
|
|
786
|
+
return new VUnion({
|
|
787
|
+
isOptional: "optional",
|
|
788
|
+
members: this.members
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const v = {
|
|
793
|
+
/**
|
|
794
|
+
* Validates that the value corresponds to an ID of a document in given table.
|
|
795
|
+
* @param tableName The name of the table.
|
|
796
|
+
*/
|
|
797
|
+
id: (tableName) => {
|
|
798
|
+
return new VId({
|
|
799
|
+
isOptional: "required",
|
|
800
|
+
tableName
|
|
801
|
+
});
|
|
802
|
+
},
|
|
803
|
+
/**
|
|
804
|
+
* Validates that the value is of type Null.
|
|
805
|
+
*/
|
|
806
|
+
null: () => {
|
|
807
|
+
return new VNull({ isOptional: "required" });
|
|
808
|
+
},
|
|
809
|
+
/**
|
|
810
|
+
* Validates that the value is of Convex type Float64 (Number in JS).
|
|
811
|
+
*
|
|
812
|
+
* Alias for `v.float64()`
|
|
813
|
+
*/
|
|
814
|
+
number: () => {
|
|
815
|
+
return new VFloat64({ isOptional: "required" });
|
|
816
|
+
},
|
|
817
|
+
/**
|
|
818
|
+
* Validates that the value is of Convex type Float64 (Number in JS).
|
|
819
|
+
*/
|
|
820
|
+
float64: () => {
|
|
821
|
+
return new VFloat64({ isOptional: "required" });
|
|
822
|
+
},
|
|
823
|
+
/**
|
|
824
|
+
* @deprecated Use `v.int64()` instead
|
|
825
|
+
*/
|
|
826
|
+
bigint: () => {
|
|
827
|
+
return new VInt64({ isOptional: "required" });
|
|
828
|
+
},
|
|
829
|
+
/**
|
|
830
|
+
* Validates that the value is of Convex type Int64 (BigInt in JS).
|
|
831
|
+
*/
|
|
832
|
+
int64: () => {
|
|
833
|
+
return new VInt64({ isOptional: "required" });
|
|
834
|
+
},
|
|
835
|
+
/**
|
|
836
|
+
* Validates that the value is of type Boolean.
|
|
837
|
+
*/
|
|
838
|
+
boolean: () => {
|
|
839
|
+
return new VBoolean({ isOptional: "required" });
|
|
840
|
+
},
|
|
841
|
+
/**
|
|
842
|
+
* Validates that the value is of type String.
|
|
843
|
+
*/
|
|
844
|
+
string: () => {
|
|
845
|
+
return new VString({ isOptional: "required" });
|
|
846
|
+
},
|
|
847
|
+
/**
|
|
848
|
+
* Validates that the value is of Convex type Bytes (constructed in JS via `ArrayBuffer`).
|
|
849
|
+
*/
|
|
850
|
+
bytes: () => {
|
|
851
|
+
return new VBytes({ isOptional: "required" });
|
|
852
|
+
},
|
|
853
|
+
/**
|
|
854
|
+
* Validates that the value is equal to the given literal value.
|
|
855
|
+
* @param literal The literal value to compare against.
|
|
856
|
+
*/
|
|
857
|
+
literal: (literal) => {
|
|
858
|
+
return new VLiteral({ isOptional: "required", value: literal });
|
|
859
|
+
},
|
|
860
|
+
/**
|
|
861
|
+
* Validates that the value is an Array of the given element type.
|
|
862
|
+
* @param element The validator for the elements of the array.
|
|
863
|
+
*/
|
|
864
|
+
array: (element) => {
|
|
865
|
+
return new VArray({ isOptional: "required", element });
|
|
866
|
+
},
|
|
867
|
+
/**
|
|
868
|
+
* Validates that the value is an Object with the given properties.
|
|
869
|
+
* @param fields An object specifying the validator for each property.
|
|
870
|
+
*/
|
|
871
|
+
object: (fields) => {
|
|
872
|
+
return new VObject({ isOptional: "required", fields });
|
|
873
|
+
},
|
|
874
|
+
/**
|
|
875
|
+
* Validates that the value is a Record with keys and values that match the given types.
|
|
876
|
+
* @param keys The validator for the keys of the record. This cannot contain string literals.
|
|
877
|
+
* @param values The validator for the values of the record.
|
|
878
|
+
*/
|
|
879
|
+
record: (keys, values) => {
|
|
880
|
+
return new VRecord({
|
|
881
|
+
isOptional: "required",
|
|
882
|
+
key: keys,
|
|
883
|
+
value: values
|
|
884
|
+
});
|
|
885
|
+
},
|
|
886
|
+
/**
|
|
887
|
+
* Validates that the value matches one of the given validators.
|
|
888
|
+
* @param members The validators to match against.
|
|
889
|
+
*/
|
|
890
|
+
union: (...members) => {
|
|
891
|
+
return new VUnion({
|
|
892
|
+
isOptional: "required",
|
|
893
|
+
members
|
|
894
|
+
});
|
|
895
|
+
},
|
|
896
|
+
/**
|
|
897
|
+
* Does not validate the value.
|
|
898
|
+
*/
|
|
899
|
+
any: () => {
|
|
900
|
+
return new VAny({ isOptional: "required" });
|
|
901
|
+
},
|
|
902
|
+
/**
|
|
903
|
+
* Allows not specifying a value for a property in an Object.
|
|
904
|
+
* @param value The property value validator to make optional.
|
|
905
|
+
*
|
|
906
|
+
* ```typescript
|
|
907
|
+
* const objectWithOptionalFields = v.object({
|
|
908
|
+
* requiredField: v.string(),
|
|
909
|
+
* optionalField: v.optional(v.string()),
|
|
910
|
+
* });
|
|
911
|
+
* ```
|
|
912
|
+
*/
|
|
913
|
+
optional: (value) => {
|
|
914
|
+
return value.asOptional();
|
|
915
|
+
},
|
|
916
|
+
/**
|
|
917
|
+
* Allows specifying a value or null.
|
|
918
|
+
*/
|
|
919
|
+
nullable: (value) => {
|
|
920
|
+
return v.union(value, v.null());
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
var __defProp$c = Object.defineProperty;
|
|
924
|
+
var __defNormalProp$c = (obj, key, value) => key in obj ? __defProp$c(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
925
|
+
var __publicField$c = (obj, key, value) => __defNormalProp$c(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
926
|
+
var _a, _b;
|
|
927
|
+
const IDENTIFYING_FIELD = Symbol.for("ConvexError");
|
|
928
|
+
class ConvexError extends (_b = Error, _a = IDENTIFYING_FIELD, _b) {
|
|
929
|
+
constructor(data) {
|
|
930
|
+
super(typeof data === "string" ? data : stringifyValueForError(data));
|
|
931
|
+
__publicField$c(this, "name", "ConvexError");
|
|
932
|
+
__publicField$c(this, "data");
|
|
933
|
+
__publicField$c(this, _a, true);
|
|
934
|
+
this.data = data;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
const version = "1.31.6";
|
|
938
|
+
var __defProp$b = Object.defineProperty;
|
|
939
|
+
var __defNormalProp$b = (obj, key, value) => key in obj ? __defProp$b(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
940
|
+
var __publicField$b = (obj, key, value) => __defNormalProp$b(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
941
|
+
const INFO_COLOR = "color:rgb(0, 145, 255)";
|
|
942
|
+
function prefix_for_source(source) {
|
|
943
|
+
switch (source) {
|
|
944
|
+
case "query":
|
|
945
|
+
return "Q";
|
|
946
|
+
case "mutation":
|
|
947
|
+
return "M";
|
|
948
|
+
case "action":
|
|
949
|
+
return "A";
|
|
950
|
+
case "any":
|
|
951
|
+
return "?";
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
class DefaultLogger {
|
|
955
|
+
constructor(options) {
|
|
956
|
+
__publicField$b(this, "_onLogLineFuncs");
|
|
957
|
+
__publicField$b(this, "_verbose");
|
|
958
|
+
this._onLogLineFuncs = {};
|
|
959
|
+
this._verbose = options.verbose;
|
|
960
|
+
}
|
|
961
|
+
addLogLineListener(func) {
|
|
962
|
+
let id = Math.random().toString(36).substring(2, 15);
|
|
963
|
+
for (let i = 0; i < 10; i++) {
|
|
964
|
+
if (this._onLogLineFuncs[id] === void 0) {
|
|
965
|
+
break;
|
|
966
|
+
}
|
|
967
|
+
id = Math.random().toString(36).substring(2, 15);
|
|
968
|
+
}
|
|
969
|
+
this._onLogLineFuncs[id] = func;
|
|
970
|
+
return () => {
|
|
971
|
+
delete this._onLogLineFuncs[id];
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
logVerbose(...args) {
|
|
975
|
+
if (this._verbose) {
|
|
976
|
+
for (const func of Object.values(this._onLogLineFuncs)) {
|
|
977
|
+
func("debug", `${(/* @__PURE__ */ new Date()).toISOString()}`, ...args);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
log(...args) {
|
|
982
|
+
for (const func of Object.values(this._onLogLineFuncs)) {
|
|
983
|
+
func("info", ...args);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
warn(...args) {
|
|
987
|
+
for (const func of Object.values(this._onLogLineFuncs)) {
|
|
988
|
+
func("warn", ...args);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
error(...args) {
|
|
992
|
+
for (const func of Object.values(this._onLogLineFuncs)) {
|
|
993
|
+
func("error", ...args);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function instantiateDefaultLogger(options) {
|
|
998
|
+
const logger = new DefaultLogger(options);
|
|
999
|
+
logger.addLogLineListener((level, ...args) => {
|
|
1000
|
+
switch (level) {
|
|
1001
|
+
case "debug":
|
|
1002
|
+
console.debug(...args);
|
|
1003
|
+
break;
|
|
1004
|
+
case "info":
|
|
1005
|
+
console.log(...args);
|
|
1006
|
+
break;
|
|
1007
|
+
case "warn":
|
|
1008
|
+
console.warn(...args);
|
|
1009
|
+
break;
|
|
1010
|
+
case "error":
|
|
1011
|
+
console.error(...args);
|
|
1012
|
+
break;
|
|
1013
|
+
default: {
|
|
1014
|
+
console.log(...args);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
return logger;
|
|
1019
|
+
}
|
|
1020
|
+
function instantiateNoopLogger(options) {
|
|
1021
|
+
return new DefaultLogger(options);
|
|
1022
|
+
}
|
|
1023
|
+
function logForFunction(logger, type, source, udfPath, message) {
|
|
1024
|
+
const prefix = prefix_for_source(source);
|
|
1025
|
+
if (typeof message === "object") {
|
|
1026
|
+
message = `ConvexError ${JSON.stringify(message.errorData, null, 2)}`;
|
|
1027
|
+
}
|
|
1028
|
+
if (type === "info") {
|
|
1029
|
+
const match = message.match(/^\[.*?\] /);
|
|
1030
|
+
if (match === null) {
|
|
1031
|
+
logger.error(
|
|
1032
|
+
`[CONVEX ${prefix}(${udfPath})] Could not parse console.log`
|
|
1033
|
+
);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const level = message.slice(1, match[0].length - 2);
|
|
1037
|
+
const args = message.slice(match[0].length);
|
|
1038
|
+
logger.log(`%c[CONVEX ${prefix}(${udfPath})] [${level}]`, INFO_COLOR, args);
|
|
1039
|
+
} else {
|
|
1040
|
+
logger.error(`[CONVEX ${prefix}(${udfPath})] ${message}`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function logFatalError(logger, message) {
|
|
1044
|
+
const errorMessage = `[CONVEX FATAL ERROR] ${message}`;
|
|
1045
|
+
logger.error(errorMessage);
|
|
1046
|
+
return new Error(errorMessage);
|
|
1047
|
+
}
|
|
1048
|
+
function createHybridErrorStacktrace(source, udfPath, result) {
|
|
1049
|
+
const prefix = prefix_for_source(source);
|
|
1050
|
+
return `[CONVEX ${prefix}(${udfPath})] ${result.errorMessage}
|
|
1051
|
+
Called by client`;
|
|
1052
|
+
}
|
|
1053
|
+
function forwardData(result, error) {
|
|
1054
|
+
error.data = result.errorData;
|
|
1055
|
+
return error;
|
|
1056
|
+
}
|
|
1057
|
+
function canonicalizeUdfPath(udfPath) {
|
|
1058
|
+
const pieces = udfPath.split(":");
|
|
1059
|
+
let moduleName;
|
|
1060
|
+
let functionName2;
|
|
1061
|
+
if (pieces.length === 1) {
|
|
1062
|
+
moduleName = pieces[0];
|
|
1063
|
+
functionName2 = "default";
|
|
1064
|
+
} else {
|
|
1065
|
+
moduleName = pieces.slice(0, pieces.length - 1).join(":");
|
|
1066
|
+
functionName2 = pieces[pieces.length - 1];
|
|
1067
|
+
}
|
|
1068
|
+
if (moduleName.endsWith(".js")) {
|
|
1069
|
+
moduleName = moduleName.slice(0, -3);
|
|
1070
|
+
}
|
|
1071
|
+
return `${moduleName}:${functionName2}`;
|
|
1072
|
+
}
|
|
1073
|
+
function serializePathAndArgs(udfPath, args) {
|
|
1074
|
+
return JSON.stringify({
|
|
1075
|
+
udfPath: canonicalizeUdfPath(udfPath),
|
|
1076
|
+
args: convexToJson(args)
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
function serializePaginatedPathAndArgs(udfPath, args, options) {
|
|
1080
|
+
const { initialNumItems, id } = options;
|
|
1081
|
+
const result = JSON.stringify({
|
|
1082
|
+
type: "paginated",
|
|
1083
|
+
udfPath: canonicalizeUdfPath(udfPath),
|
|
1084
|
+
args: convexToJson(args),
|
|
1085
|
+
options: convexToJson({ initialNumItems, id })
|
|
1086
|
+
});
|
|
1087
|
+
return result;
|
|
1088
|
+
}
|
|
1089
|
+
var __defProp$a = Object.defineProperty;
|
|
1090
|
+
var __defNormalProp$a = (obj, key, value) => key in obj ? __defProp$a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1091
|
+
var __publicField$a = (obj, key, value) => __defNormalProp$a(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1092
|
+
class LocalSyncState {
|
|
1093
|
+
constructor() {
|
|
1094
|
+
__publicField$a(this, "nextQueryId");
|
|
1095
|
+
__publicField$a(this, "querySetVersion");
|
|
1096
|
+
__publicField$a(this, "querySet");
|
|
1097
|
+
__publicField$a(this, "queryIdToToken");
|
|
1098
|
+
__publicField$a(this, "identityVersion");
|
|
1099
|
+
__publicField$a(this, "auth");
|
|
1100
|
+
__publicField$a(this, "outstandingQueriesOlderThanRestart");
|
|
1101
|
+
__publicField$a(this, "outstandingAuthOlderThanRestart");
|
|
1102
|
+
__publicField$a(this, "paused");
|
|
1103
|
+
__publicField$a(this, "pendingQuerySetModifications");
|
|
1104
|
+
this.nextQueryId = 0;
|
|
1105
|
+
this.querySetVersion = 0;
|
|
1106
|
+
this.identityVersion = 0;
|
|
1107
|
+
this.querySet = /* @__PURE__ */ new Map();
|
|
1108
|
+
this.queryIdToToken = /* @__PURE__ */ new Map();
|
|
1109
|
+
this.outstandingQueriesOlderThanRestart = /* @__PURE__ */ new Set();
|
|
1110
|
+
this.outstandingAuthOlderThanRestart = false;
|
|
1111
|
+
this.paused = false;
|
|
1112
|
+
this.pendingQuerySetModifications = /* @__PURE__ */ new Map();
|
|
1113
|
+
}
|
|
1114
|
+
hasSyncedPastLastReconnect() {
|
|
1115
|
+
return this.outstandingQueriesOlderThanRestart.size === 0 && !this.outstandingAuthOlderThanRestart;
|
|
1116
|
+
}
|
|
1117
|
+
markAuthCompletion() {
|
|
1118
|
+
this.outstandingAuthOlderThanRestart = false;
|
|
1119
|
+
}
|
|
1120
|
+
subscribe(udfPath, args, journal, componentPath) {
|
|
1121
|
+
const canonicalizedUdfPath = canonicalizeUdfPath(udfPath);
|
|
1122
|
+
const queryToken = serializePathAndArgs(canonicalizedUdfPath, args);
|
|
1123
|
+
const existingEntry = this.querySet.get(queryToken);
|
|
1124
|
+
if (existingEntry !== void 0) {
|
|
1125
|
+
existingEntry.numSubscribers += 1;
|
|
1126
|
+
return {
|
|
1127
|
+
queryToken,
|
|
1128
|
+
modification: null,
|
|
1129
|
+
unsubscribe: () => this.removeSubscriber(queryToken)
|
|
1130
|
+
};
|
|
1131
|
+
} else {
|
|
1132
|
+
const queryId = this.nextQueryId++;
|
|
1133
|
+
const query = {
|
|
1134
|
+
id: queryId,
|
|
1135
|
+
canonicalizedUdfPath,
|
|
1136
|
+
args,
|
|
1137
|
+
numSubscribers: 1,
|
|
1138
|
+
journal,
|
|
1139
|
+
componentPath
|
|
1140
|
+
};
|
|
1141
|
+
this.querySet.set(queryToken, query);
|
|
1142
|
+
this.queryIdToToken.set(queryId, queryToken);
|
|
1143
|
+
const baseVersion = this.querySetVersion;
|
|
1144
|
+
const newVersion = this.querySetVersion + 1;
|
|
1145
|
+
const add = {
|
|
1146
|
+
type: "Add",
|
|
1147
|
+
queryId,
|
|
1148
|
+
udfPath: canonicalizedUdfPath,
|
|
1149
|
+
args: [convexToJson(args)],
|
|
1150
|
+
journal,
|
|
1151
|
+
componentPath
|
|
1152
|
+
};
|
|
1153
|
+
if (this.paused) {
|
|
1154
|
+
this.pendingQuerySetModifications.set(queryId, add);
|
|
1155
|
+
} else {
|
|
1156
|
+
this.querySetVersion = newVersion;
|
|
1157
|
+
}
|
|
1158
|
+
const modification = {
|
|
1159
|
+
type: "ModifyQuerySet",
|
|
1160
|
+
baseVersion,
|
|
1161
|
+
newVersion,
|
|
1162
|
+
modifications: [add]
|
|
1163
|
+
};
|
|
1164
|
+
return {
|
|
1165
|
+
queryToken,
|
|
1166
|
+
modification,
|
|
1167
|
+
unsubscribe: () => this.removeSubscriber(queryToken)
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
transition(transition) {
|
|
1172
|
+
for (const modification of transition.modifications) {
|
|
1173
|
+
switch (modification.type) {
|
|
1174
|
+
case "QueryUpdated":
|
|
1175
|
+
case "QueryFailed": {
|
|
1176
|
+
this.outstandingQueriesOlderThanRestart.delete(modification.queryId);
|
|
1177
|
+
const journal = modification.journal;
|
|
1178
|
+
if (journal !== void 0) {
|
|
1179
|
+
const queryToken = this.queryIdToToken.get(modification.queryId);
|
|
1180
|
+
if (queryToken !== void 0) {
|
|
1181
|
+
this.querySet.get(queryToken).journal = journal;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
break;
|
|
1185
|
+
}
|
|
1186
|
+
case "QueryRemoved": {
|
|
1187
|
+
this.outstandingQueriesOlderThanRestart.delete(modification.queryId);
|
|
1188
|
+
break;
|
|
1189
|
+
}
|
|
1190
|
+
default: {
|
|
1191
|
+
throw new Error(`Invalid modification ${modification.type}`);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
queryId(udfPath, args) {
|
|
1197
|
+
const canonicalizedUdfPath = canonicalizeUdfPath(udfPath);
|
|
1198
|
+
const queryToken = serializePathAndArgs(canonicalizedUdfPath, args);
|
|
1199
|
+
const existingEntry = this.querySet.get(queryToken);
|
|
1200
|
+
if (existingEntry !== void 0) {
|
|
1201
|
+
return existingEntry.id;
|
|
1202
|
+
}
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
isCurrentOrNewerAuthVersion(version2) {
|
|
1206
|
+
return version2 >= this.identityVersion;
|
|
1207
|
+
}
|
|
1208
|
+
getAuth() {
|
|
1209
|
+
return this.auth;
|
|
1210
|
+
}
|
|
1211
|
+
setAuth(value) {
|
|
1212
|
+
this.auth = {
|
|
1213
|
+
tokenType: "User",
|
|
1214
|
+
value
|
|
1215
|
+
};
|
|
1216
|
+
const baseVersion = this.identityVersion;
|
|
1217
|
+
if (!this.paused) {
|
|
1218
|
+
this.identityVersion = baseVersion + 1;
|
|
1219
|
+
}
|
|
1220
|
+
return {
|
|
1221
|
+
type: "Authenticate",
|
|
1222
|
+
baseVersion,
|
|
1223
|
+
...this.auth
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
setAdminAuth(value, actingAs) {
|
|
1227
|
+
const auth = {
|
|
1228
|
+
tokenType: "Admin",
|
|
1229
|
+
value,
|
|
1230
|
+
impersonating: actingAs
|
|
1231
|
+
};
|
|
1232
|
+
this.auth = auth;
|
|
1233
|
+
const baseVersion = this.identityVersion;
|
|
1234
|
+
if (!this.paused) {
|
|
1235
|
+
this.identityVersion = baseVersion + 1;
|
|
1236
|
+
}
|
|
1237
|
+
return {
|
|
1238
|
+
type: "Authenticate",
|
|
1239
|
+
baseVersion,
|
|
1240
|
+
...auth
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
clearAuth() {
|
|
1244
|
+
this.auth = void 0;
|
|
1245
|
+
this.markAuthCompletion();
|
|
1246
|
+
const baseVersion = this.identityVersion;
|
|
1247
|
+
if (!this.paused) {
|
|
1248
|
+
this.identityVersion = baseVersion + 1;
|
|
1249
|
+
}
|
|
1250
|
+
return {
|
|
1251
|
+
type: "Authenticate",
|
|
1252
|
+
tokenType: "None",
|
|
1253
|
+
baseVersion
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
hasAuth() {
|
|
1257
|
+
return !!this.auth;
|
|
1258
|
+
}
|
|
1259
|
+
isNewAuth(value) {
|
|
1260
|
+
return this.auth?.value !== value;
|
|
1261
|
+
}
|
|
1262
|
+
queryPath(queryId) {
|
|
1263
|
+
const pathAndArgs = this.queryIdToToken.get(queryId);
|
|
1264
|
+
if (pathAndArgs) {
|
|
1265
|
+
return this.querySet.get(pathAndArgs).canonicalizedUdfPath;
|
|
1266
|
+
}
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
queryArgs(queryId) {
|
|
1270
|
+
const pathAndArgs = this.queryIdToToken.get(queryId);
|
|
1271
|
+
if (pathAndArgs) {
|
|
1272
|
+
return this.querySet.get(pathAndArgs).args;
|
|
1273
|
+
}
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
queryToken(queryId) {
|
|
1277
|
+
return this.queryIdToToken.get(queryId) ?? null;
|
|
1278
|
+
}
|
|
1279
|
+
queryJournal(queryToken) {
|
|
1280
|
+
return this.querySet.get(queryToken)?.journal;
|
|
1281
|
+
}
|
|
1282
|
+
restart(oldRemoteQueryResults) {
|
|
1283
|
+
this.unpause();
|
|
1284
|
+
this.outstandingQueriesOlderThanRestart.clear();
|
|
1285
|
+
const modifications = [];
|
|
1286
|
+
for (const localQuery of this.querySet.values()) {
|
|
1287
|
+
const add = {
|
|
1288
|
+
type: "Add",
|
|
1289
|
+
queryId: localQuery.id,
|
|
1290
|
+
udfPath: localQuery.canonicalizedUdfPath,
|
|
1291
|
+
args: [convexToJson(localQuery.args)],
|
|
1292
|
+
journal: localQuery.journal,
|
|
1293
|
+
componentPath: localQuery.componentPath
|
|
1294
|
+
};
|
|
1295
|
+
modifications.push(add);
|
|
1296
|
+
if (!oldRemoteQueryResults.has(localQuery.id)) {
|
|
1297
|
+
this.outstandingQueriesOlderThanRestart.add(localQuery.id);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
this.querySetVersion = 1;
|
|
1301
|
+
const querySet = {
|
|
1302
|
+
type: "ModifyQuerySet",
|
|
1303
|
+
baseVersion: 0,
|
|
1304
|
+
newVersion: 1,
|
|
1305
|
+
modifications
|
|
1306
|
+
};
|
|
1307
|
+
if (!this.auth) {
|
|
1308
|
+
this.identityVersion = 0;
|
|
1309
|
+
return [querySet, void 0];
|
|
1310
|
+
}
|
|
1311
|
+
this.outstandingAuthOlderThanRestart = true;
|
|
1312
|
+
const authenticate = {
|
|
1313
|
+
type: "Authenticate",
|
|
1314
|
+
baseVersion: 0,
|
|
1315
|
+
...this.auth
|
|
1316
|
+
};
|
|
1317
|
+
this.identityVersion = 1;
|
|
1318
|
+
return [querySet, authenticate];
|
|
1319
|
+
}
|
|
1320
|
+
pause() {
|
|
1321
|
+
this.paused = true;
|
|
1322
|
+
}
|
|
1323
|
+
resume() {
|
|
1324
|
+
const querySet = this.pendingQuerySetModifications.size > 0 ? {
|
|
1325
|
+
type: "ModifyQuerySet",
|
|
1326
|
+
baseVersion: this.querySetVersion,
|
|
1327
|
+
newVersion: ++this.querySetVersion,
|
|
1328
|
+
modifications: Array.from(
|
|
1329
|
+
this.pendingQuerySetModifications.values()
|
|
1330
|
+
)
|
|
1331
|
+
} : void 0;
|
|
1332
|
+
const authenticate = this.auth !== void 0 ? {
|
|
1333
|
+
type: "Authenticate",
|
|
1334
|
+
baseVersion: this.identityVersion++,
|
|
1335
|
+
...this.auth
|
|
1336
|
+
} : void 0;
|
|
1337
|
+
this.unpause();
|
|
1338
|
+
return [querySet, authenticate];
|
|
1339
|
+
}
|
|
1340
|
+
unpause() {
|
|
1341
|
+
this.paused = false;
|
|
1342
|
+
this.pendingQuerySetModifications.clear();
|
|
1343
|
+
}
|
|
1344
|
+
removeSubscriber(queryToken) {
|
|
1345
|
+
const localQuery = this.querySet.get(queryToken);
|
|
1346
|
+
if (localQuery.numSubscribers > 1) {
|
|
1347
|
+
localQuery.numSubscribers -= 1;
|
|
1348
|
+
return null;
|
|
1349
|
+
} else {
|
|
1350
|
+
this.querySet.delete(queryToken);
|
|
1351
|
+
this.queryIdToToken.delete(localQuery.id);
|
|
1352
|
+
this.outstandingQueriesOlderThanRestart.delete(localQuery.id);
|
|
1353
|
+
const baseVersion = this.querySetVersion;
|
|
1354
|
+
const newVersion = this.querySetVersion + 1;
|
|
1355
|
+
const remove = {
|
|
1356
|
+
type: "Remove",
|
|
1357
|
+
queryId: localQuery.id
|
|
1358
|
+
};
|
|
1359
|
+
if (this.paused) {
|
|
1360
|
+
if (this.pendingQuerySetModifications.has(localQuery.id)) {
|
|
1361
|
+
this.pendingQuerySetModifications.delete(localQuery.id);
|
|
1362
|
+
} else {
|
|
1363
|
+
this.pendingQuerySetModifications.set(localQuery.id, remove);
|
|
1364
|
+
}
|
|
1365
|
+
} else {
|
|
1366
|
+
this.querySetVersion = newVersion;
|
|
1367
|
+
}
|
|
1368
|
+
return {
|
|
1369
|
+
type: "ModifyQuerySet",
|
|
1370
|
+
baseVersion,
|
|
1371
|
+
newVersion,
|
|
1372
|
+
modifications: [remove]
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
var __defProp$9 = Object.defineProperty;
|
|
1378
|
+
var __defNormalProp$9 = (obj, key, value) => key in obj ? __defProp$9(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1379
|
+
var __publicField$9 = (obj, key, value) => __defNormalProp$9(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1380
|
+
class RequestManager {
|
|
1381
|
+
constructor(logger, markConnectionStateDirty) {
|
|
1382
|
+
this.logger = logger;
|
|
1383
|
+
this.markConnectionStateDirty = markConnectionStateDirty;
|
|
1384
|
+
__publicField$9(this, "inflightRequests");
|
|
1385
|
+
__publicField$9(this, "requestsOlderThanRestart");
|
|
1386
|
+
__publicField$9(this, "inflightMutationsCount", 0);
|
|
1387
|
+
__publicField$9(this, "inflightActionsCount", 0);
|
|
1388
|
+
this.inflightRequests = /* @__PURE__ */ new Map();
|
|
1389
|
+
this.requestsOlderThanRestart = /* @__PURE__ */ new Set();
|
|
1390
|
+
}
|
|
1391
|
+
request(message, sent) {
|
|
1392
|
+
const result = new Promise((resolve) => {
|
|
1393
|
+
const status = sent ? "Requested" : "NotSent";
|
|
1394
|
+
this.inflightRequests.set(message.requestId, {
|
|
1395
|
+
message,
|
|
1396
|
+
status: { status, requestedAt: /* @__PURE__ */ new Date(), onResult: resolve }
|
|
1397
|
+
});
|
|
1398
|
+
if (message.type === "Mutation") {
|
|
1399
|
+
this.inflightMutationsCount++;
|
|
1400
|
+
} else if (message.type === "Action") {
|
|
1401
|
+
this.inflightActionsCount++;
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
this.markConnectionStateDirty();
|
|
1405
|
+
return result;
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Update the state after receiving a response.
|
|
1409
|
+
*
|
|
1410
|
+
* @returns A RequestId if the request is complete and its optimistic update
|
|
1411
|
+
* can be dropped, null otherwise.
|
|
1412
|
+
*/
|
|
1413
|
+
onResponse(response) {
|
|
1414
|
+
const requestInfo = this.inflightRequests.get(response.requestId);
|
|
1415
|
+
if (requestInfo === void 0) {
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
if (requestInfo.status.status === "Completed") {
|
|
1419
|
+
return null;
|
|
1420
|
+
}
|
|
1421
|
+
const udfType = requestInfo.message.type === "Mutation" ? "mutation" : "action";
|
|
1422
|
+
const udfPath = requestInfo.message.udfPath;
|
|
1423
|
+
for (const line of response.logLines) {
|
|
1424
|
+
logForFunction(this.logger, "info", udfType, udfPath, line);
|
|
1425
|
+
}
|
|
1426
|
+
const status = requestInfo.status;
|
|
1427
|
+
let result;
|
|
1428
|
+
let onResolve;
|
|
1429
|
+
if (response.success) {
|
|
1430
|
+
result = {
|
|
1431
|
+
success: true,
|
|
1432
|
+
logLines: response.logLines,
|
|
1433
|
+
value: jsonToConvex(response.result)
|
|
1434
|
+
};
|
|
1435
|
+
onResolve = () => status.onResult(result);
|
|
1436
|
+
} else {
|
|
1437
|
+
const errorMessage = response.result;
|
|
1438
|
+
const { errorData } = response;
|
|
1439
|
+
logForFunction(this.logger, "error", udfType, udfPath, errorMessage);
|
|
1440
|
+
result = {
|
|
1441
|
+
success: false,
|
|
1442
|
+
errorMessage,
|
|
1443
|
+
errorData: errorData !== void 0 ? jsonToConvex(errorData) : void 0,
|
|
1444
|
+
logLines: response.logLines
|
|
1445
|
+
};
|
|
1446
|
+
onResolve = () => status.onResult(result);
|
|
1447
|
+
}
|
|
1448
|
+
if (response.type === "ActionResponse" || !response.success) {
|
|
1449
|
+
onResolve();
|
|
1450
|
+
this.inflightRequests.delete(response.requestId);
|
|
1451
|
+
this.requestsOlderThanRestart.delete(response.requestId);
|
|
1452
|
+
if (requestInfo.message.type === "Action") {
|
|
1453
|
+
this.inflightActionsCount--;
|
|
1454
|
+
} else if (requestInfo.message.type === "Mutation") {
|
|
1455
|
+
this.inflightMutationsCount--;
|
|
1456
|
+
}
|
|
1457
|
+
this.markConnectionStateDirty();
|
|
1458
|
+
return { requestId: response.requestId, result };
|
|
1459
|
+
}
|
|
1460
|
+
requestInfo.status = {
|
|
1461
|
+
status: "Completed",
|
|
1462
|
+
result,
|
|
1463
|
+
ts: response.ts,
|
|
1464
|
+
onResolve
|
|
1465
|
+
};
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
// Remove and returns completed requests.
|
|
1469
|
+
removeCompleted(ts) {
|
|
1470
|
+
const completeRequests = /* @__PURE__ */ new Map();
|
|
1471
|
+
for (const [requestId, requestInfo] of this.inflightRequests.entries()) {
|
|
1472
|
+
const status = requestInfo.status;
|
|
1473
|
+
if (status.status === "Completed" && status.ts.lessThanOrEqual(ts)) {
|
|
1474
|
+
status.onResolve();
|
|
1475
|
+
completeRequests.set(requestId, status.result);
|
|
1476
|
+
if (requestInfo.message.type === "Mutation") {
|
|
1477
|
+
this.inflightMutationsCount--;
|
|
1478
|
+
} else if (requestInfo.message.type === "Action") {
|
|
1479
|
+
this.inflightActionsCount--;
|
|
1480
|
+
}
|
|
1481
|
+
this.inflightRequests.delete(requestId);
|
|
1482
|
+
this.requestsOlderThanRestart.delete(requestId);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
if (completeRequests.size > 0) {
|
|
1486
|
+
this.markConnectionStateDirty();
|
|
1487
|
+
}
|
|
1488
|
+
return completeRequests;
|
|
1489
|
+
}
|
|
1490
|
+
restart() {
|
|
1491
|
+
this.requestsOlderThanRestart = new Set(this.inflightRequests.keys());
|
|
1492
|
+
const allMessages = [];
|
|
1493
|
+
for (const [requestId, value] of this.inflightRequests) {
|
|
1494
|
+
if (value.status.status === "NotSent") {
|
|
1495
|
+
value.status.status = "Requested";
|
|
1496
|
+
allMessages.push(value.message);
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
if (value.message.type === "Mutation") {
|
|
1500
|
+
allMessages.push(value.message);
|
|
1501
|
+
} else if (value.message.type === "Action") {
|
|
1502
|
+
this.inflightRequests.delete(requestId);
|
|
1503
|
+
this.requestsOlderThanRestart.delete(requestId);
|
|
1504
|
+
this.inflightActionsCount--;
|
|
1505
|
+
if (value.status.status === "Completed") {
|
|
1506
|
+
throw new Error("Action should never be in 'Completed' state");
|
|
1507
|
+
}
|
|
1508
|
+
value.status.onResult({
|
|
1509
|
+
success: false,
|
|
1510
|
+
errorMessage: "Connection lost while action was in flight",
|
|
1511
|
+
logLines: []
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
this.markConnectionStateDirty();
|
|
1516
|
+
return allMessages;
|
|
1517
|
+
}
|
|
1518
|
+
resume() {
|
|
1519
|
+
const allMessages = [];
|
|
1520
|
+
for (const [, value] of this.inflightRequests) {
|
|
1521
|
+
if (value.status.status === "NotSent") {
|
|
1522
|
+
value.status.status = "Requested";
|
|
1523
|
+
allMessages.push(value.message);
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return allMessages;
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* @returns true if there are any requests that have been requested but have
|
|
1531
|
+
* not be completed yet.
|
|
1532
|
+
*/
|
|
1533
|
+
hasIncompleteRequests() {
|
|
1534
|
+
for (const requestInfo of this.inflightRequests.values()) {
|
|
1535
|
+
if (requestInfo.status.status === "Requested") {
|
|
1536
|
+
return true;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* @returns true if there are any inflight requests, including ones that have
|
|
1543
|
+
* completed on the server, but have not been applied.
|
|
1544
|
+
*/
|
|
1545
|
+
hasInflightRequests() {
|
|
1546
|
+
return this.inflightRequests.size > 0;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* @returns true if there are any inflight requests, that have been hanging around
|
|
1550
|
+
* since prior to the most recent restart.
|
|
1551
|
+
*/
|
|
1552
|
+
hasSyncedPastLastReconnect() {
|
|
1553
|
+
return this.requestsOlderThanRestart.size === 0;
|
|
1554
|
+
}
|
|
1555
|
+
timeOfOldestInflightRequest() {
|
|
1556
|
+
if (this.inflightRequests.size === 0) {
|
|
1557
|
+
return null;
|
|
1558
|
+
}
|
|
1559
|
+
let oldestInflightRequest = Date.now();
|
|
1560
|
+
for (const request of this.inflightRequests.values()) {
|
|
1561
|
+
if (request.status.status !== "Completed") {
|
|
1562
|
+
if (request.status.requestedAt.getTime() < oldestInflightRequest) {
|
|
1563
|
+
oldestInflightRequest = request.status.requestedAt.getTime();
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return new Date(oldestInflightRequest);
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* @returns The number of mutations currently in flight.
|
|
1571
|
+
*/
|
|
1572
|
+
inflightMutations() {
|
|
1573
|
+
return this.inflightMutationsCount;
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* @returns The number of actions currently in flight.
|
|
1577
|
+
*/
|
|
1578
|
+
inflightActions() {
|
|
1579
|
+
return this.inflightActionsCount;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
const functionName = Symbol.for("functionName");
|
|
1583
|
+
const toReferencePath = Symbol.for("toReferencePath");
|
|
1584
|
+
function extractReferencePath(reference) {
|
|
1585
|
+
return reference[toReferencePath] ?? null;
|
|
1586
|
+
}
|
|
1587
|
+
function isFunctionHandle(s) {
|
|
1588
|
+
return s.startsWith("function://");
|
|
1589
|
+
}
|
|
1590
|
+
function getFunctionAddress(functionReference) {
|
|
1591
|
+
let functionAddress;
|
|
1592
|
+
if (typeof functionReference === "string") {
|
|
1593
|
+
if (isFunctionHandle(functionReference)) {
|
|
1594
|
+
functionAddress = { functionHandle: functionReference };
|
|
1595
|
+
} else {
|
|
1596
|
+
functionAddress = { name: functionReference };
|
|
1597
|
+
}
|
|
1598
|
+
} else if (functionReference[functionName]) {
|
|
1599
|
+
functionAddress = { name: functionReference[functionName] };
|
|
1600
|
+
} else {
|
|
1601
|
+
const referencePath = extractReferencePath(functionReference);
|
|
1602
|
+
if (!referencePath) {
|
|
1603
|
+
throw new Error(`${functionReference} is not a functionReference`);
|
|
1604
|
+
}
|
|
1605
|
+
functionAddress = { reference: referencePath };
|
|
1606
|
+
}
|
|
1607
|
+
return functionAddress;
|
|
1608
|
+
}
|
|
1609
|
+
function getFunctionName(functionReference) {
|
|
1610
|
+
const address = getFunctionAddress(functionReference);
|
|
1611
|
+
if (address.name === void 0) {
|
|
1612
|
+
if (address.functionHandle !== void 0) {
|
|
1613
|
+
throw new Error(
|
|
1614
|
+
`Expected function reference like "api.file.func" or "internal.file.func", but received function handle ${address.functionHandle}`
|
|
1615
|
+
);
|
|
1616
|
+
} else if (address.reference !== void 0) {
|
|
1617
|
+
throw new Error(
|
|
1618
|
+
`Expected function reference in the current component like "api.file.func" or "internal.file.func", but received reference ${address.reference}`
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
throw new Error(
|
|
1622
|
+
`Expected function reference like "api.file.func" or "internal.file.func", but received ${JSON.stringify(address)}`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
if (typeof functionReference === "string") return functionReference;
|
|
1626
|
+
const name = functionReference[functionName];
|
|
1627
|
+
if (!name) {
|
|
1628
|
+
throw new Error(`${functionReference} is not a functionReference`);
|
|
1629
|
+
}
|
|
1630
|
+
return name;
|
|
1631
|
+
}
|
|
1632
|
+
function makeFunctionReference(name) {
|
|
1633
|
+
return { [functionName]: name };
|
|
1634
|
+
}
|
|
1635
|
+
function createApi(pathParts = []) {
|
|
1636
|
+
const handler = {
|
|
1637
|
+
get(_, prop) {
|
|
1638
|
+
if (typeof prop === "string") {
|
|
1639
|
+
const newParts = [...pathParts, prop];
|
|
1640
|
+
return createApi(newParts);
|
|
1641
|
+
} else if (prop === functionName) {
|
|
1642
|
+
if (pathParts.length < 2) {
|
|
1643
|
+
const found = ["api", ...pathParts].join(".");
|
|
1644
|
+
throw new Error(
|
|
1645
|
+
`API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
|
|
1646
|
+
);
|
|
1647
|
+
}
|
|
1648
|
+
const path = pathParts.slice(0, -1).join("/");
|
|
1649
|
+
const exportName = pathParts[pathParts.length - 1];
|
|
1650
|
+
if (exportName === "default") {
|
|
1651
|
+
return path;
|
|
1652
|
+
} else {
|
|
1653
|
+
return path + ":" + exportName;
|
|
1654
|
+
}
|
|
1655
|
+
} else if (prop === Symbol.toStringTag) {
|
|
1656
|
+
return "FunctionReference";
|
|
1657
|
+
} else {
|
|
1658
|
+
return void 0;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
return new Proxy({}, handler);
|
|
1663
|
+
}
|
|
1664
|
+
const anyApi = createApi();
|
|
1665
|
+
var __defProp$8 = Object.defineProperty;
|
|
1666
|
+
var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1667
|
+
var __publicField$8 = (obj, key, value) => __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1668
|
+
class OptimisticLocalStoreImpl {
|
|
1669
|
+
constructor(queryResults) {
|
|
1670
|
+
__publicField$8(this, "queryResults");
|
|
1671
|
+
__publicField$8(this, "modifiedQueries");
|
|
1672
|
+
this.queryResults = queryResults;
|
|
1673
|
+
this.modifiedQueries = [];
|
|
1674
|
+
}
|
|
1675
|
+
getQuery(query, ...args) {
|
|
1676
|
+
const queryArgs = parseArgs(args[0]);
|
|
1677
|
+
const name = getFunctionName(query);
|
|
1678
|
+
const queryResult = this.queryResults.get(
|
|
1679
|
+
serializePathAndArgs(name, queryArgs)
|
|
1680
|
+
);
|
|
1681
|
+
if (queryResult === void 0) {
|
|
1682
|
+
return void 0;
|
|
1683
|
+
}
|
|
1684
|
+
return OptimisticLocalStoreImpl.queryValue(queryResult.result);
|
|
1685
|
+
}
|
|
1686
|
+
getAllQueries(query) {
|
|
1687
|
+
const queriesWithName = [];
|
|
1688
|
+
const name = getFunctionName(query);
|
|
1689
|
+
for (const queryResult of this.queryResults.values()) {
|
|
1690
|
+
if (queryResult.udfPath === canonicalizeUdfPath(name)) {
|
|
1691
|
+
queriesWithName.push({
|
|
1692
|
+
args: queryResult.args,
|
|
1693
|
+
value: OptimisticLocalStoreImpl.queryValue(queryResult.result)
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return queriesWithName;
|
|
1698
|
+
}
|
|
1699
|
+
setQuery(queryReference, args, value) {
|
|
1700
|
+
const queryArgs = parseArgs(args);
|
|
1701
|
+
const name = getFunctionName(queryReference);
|
|
1702
|
+
const queryToken = serializePathAndArgs(name, queryArgs);
|
|
1703
|
+
let result;
|
|
1704
|
+
if (value === void 0) {
|
|
1705
|
+
result = void 0;
|
|
1706
|
+
} else {
|
|
1707
|
+
result = {
|
|
1708
|
+
success: true,
|
|
1709
|
+
value,
|
|
1710
|
+
// It's an optimistic update, so there are no function logs to show.
|
|
1711
|
+
logLines: []
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
const query = {
|
|
1715
|
+
udfPath: name,
|
|
1716
|
+
args: queryArgs,
|
|
1717
|
+
result
|
|
1718
|
+
};
|
|
1719
|
+
this.queryResults.set(queryToken, query);
|
|
1720
|
+
this.modifiedQueries.push(queryToken);
|
|
1721
|
+
}
|
|
1722
|
+
static queryValue(result) {
|
|
1723
|
+
if (result === void 0) {
|
|
1724
|
+
return void 0;
|
|
1725
|
+
} else if (result.success) {
|
|
1726
|
+
return result.value;
|
|
1727
|
+
} else {
|
|
1728
|
+
return void 0;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
class OptimisticQueryResults {
|
|
1733
|
+
constructor() {
|
|
1734
|
+
__publicField$8(this, "queryResults");
|
|
1735
|
+
__publicField$8(this, "optimisticUpdates");
|
|
1736
|
+
this.queryResults = /* @__PURE__ */ new Map();
|
|
1737
|
+
this.optimisticUpdates = [];
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Apply all optimistic updates on top of server query results
|
|
1741
|
+
*/
|
|
1742
|
+
ingestQueryResultsFromServer(serverQueryResults, optimisticUpdatesToDrop) {
|
|
1743
|
+
this.optimisticUpdates = this.optimisticUpdates.filter((updateAndId) => {
|
|
1744
|
+
return !optimisticUpdatesToDrop.has(updateAndId.mutationId);
|
|
1745
|
+
});
|
|
1746
|
+
const oldQueryResults = this.queryResults;
|
|
1747
|
+
this.queryResults = new Map(serverQueryResults);
|
|
1748
|
+
const localStore = new OptimisticLocalStoreImpl(this.queryResults);
|
|
1749
|
+
for (const updateAndId of this.optimisticUpdates) {
|
|
1750
|
+
updateAndId.update(localStore);
|
|
1751
|
+
}
|
|
1752
|
+
const changedQueries = [];
|
|
1753
|
+
for (const [queryToken, query] of this.queryResults) {
|
|
1754
|
+
const oldQuery = oldQueryResults.get(queryToken);
|
|
1755
|
+
if (oldQuery === void 0 || oldQuery.result !== query.result) {
|
|
1756
|
+
changedQueries.push(queryToken);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return changedQueries;
|
|
1760
|
+
}
|
|
1761
|
+
applyOptimisticUpdate(update, mutationId) {
|
|
1762
|
+
this.optimisticUpdates.push({
|
|
1763
|
+
update,
|
|
1764
|
+
mutationId
|
|
1765
|
+
});
|
|
1766
|
+
const localStore = new OptimisticLocalStoreImpl(this.queryResults);
|
|
1767
|
+
update(localStore);
|
|
1768
|
+
return localStore.modifiedQueries;
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* "Raw" with respect to errors vs values, but query results still have
|
|
1772
|
+
* optimistic updates applied.
|
|
1773
|
+
*
|
|
1774
|
+
* @internal
|
|
1775
|
+
*/
|
|
1776
|
+
rawQueryResult(queryToken) {
|
|
1777
|
+
const query = this.queryResults.get(queryToken);
|
|
1778
|
+
if (query === void 0) {
|
|
1779
|
+
return void 0;
|
|
1780
|
+
}
|
|
1781
|
+
return query.result;
|
|
1782
|
+
}
|
|
1783
|
+
queryResult(queryToken) {
|
|
1784
|
+
const query = this.queryResults.get(queryToken);
|
|
1785
|
+
if (query === void 0) {
|
|
1786
|
+
return void 0;
|
|
1787
|
+
}
|
|
1788
|
+
const result = query.result;
|
|
1789
|
+
if (result === void 0) {
|
|
1790
|
+
return void 0;
|
|
1791
|
+
} else if (result.success) {
|
|
1792
|
+
return result.value;
|
|
1793
|
+
} else {
|
|
1794
|
+
if (result.errorData !== void 0) {
|
|
1795
|
+
throw forwardData(
|
|
1796
|
+
result,
|
|
1797
|
+
new ConvexError(
|
|
1798
|
+
createHybridErrorStacktrace("query", query.udfPath, result)
|
|
1799
|
+
)
|
|
1800
|
+
);
|
|
1801
|
+
}
|
|
1802
|
+
throw new Error(
|
|
1803
|
+
createHybridErrorStacktrace("query", query.udfPath, result)
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
hasQueryResult(queryToken) {
|
|
1808
|
+
return this.queryResults.get(queryToken) !== void 0;
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* @internal
|
|
1812
|
+
*/
|
|
1813
|
+
queryLogs(queryToken) {
|
|
1814
|
+
const query = this.queryResults.get(queryToken);
|
|
1815
|
+
return query?.result?.logLines;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
var __defProp$7 = Object.defineProperty;
|
|
1819
|
+
var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1820
|
+
var __publicField$7 = (obj, key, value) => __defNormalProp$7(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1821
|
+
class Long {
|
|
1822
|
+
constructor(low, high) {
|
|
1823
|
+
__publicField$7(this, "low");
|
|
1824
|
+
__publicField$7(this, "high");
|
|
1825
|
+
__publicField$7(this, "__isUnsignedLong__");
|
|
1826
|
+
this.low = low | 0;
|
|
1827
|
+
this.high = high | 0;
|
|
1828
|
+
this.__isUnsignedLong__ = true;
|
|
1829
|
+
}
|
|
1830
|
+
static isLong(obj) {
|
|
1831
|
+
return (obj && obj.__isUnsignedLong__) === true;
|
|
1832
|
+
}
|
|
1833
|
+
// prettier-ignore
|
|
1834
|
+
static fromBytesLE(bytes) {
|
|
1835
|
+
return new Long(
|
|
1836
|
+
bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24,
|
|
1837
|
+
bytes[4] | bytes[5] << 8 | bytes[6] << 16 | bytes[7] << 24
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
// prettier-ignore
|
|
1841
|
+
toBytesLE() {
|
|
1842
|
+
const hi = this.high;
|
|
1843
|
+
const lo = this.low;
|
|
1844
|
+
return [
|
|
1845
|
+
lo & 255,
|
|
1846
|
+
lo >>> 8 & 255,
|
|
1847
|
+
lo >>> 16 & 255,
|
|
1848
|
+
lo >>> 24,
|
|
1849
|
+
hi & 255,
|
|
1850
|
+
hi >>> 8 & 255,
|
|
1851
|
+
hi >>> 16 & 255,
|
|
1852
|
+
hi >>> 24
|
|
1853
|
+
];
|
|
1854
|
+
}
|
|
1855
|
+
static fromNumber(value) {
|
|
1856
|
+
if (isNaN(value)) return UZERO;
|
|
1857
|
+
if (value < 0) return UZERO;
|
|
1858
|
+
if (value >= TWO_PWR_64_DBL) return MAX_UNSIGNED_VALUE;
|
|
1859
|
+
return new Long(value % TWO_PWR_32_DBL | 0, value / TWO_PWR_32_DBL | 0);
|
|
1860
|
+
}
|
|
1861
|
+
toString() {
|
|
1862
|
+
return (BigInt(this.high) * BigInt(TWO_PWR_32_DBL) + BigInt(this.low)).toString();
|
|
1863
|
+
}
|
|
1864
|
+
equals(other) {
|
|
1865
|
+
if (!Long.isLong(other)) other = Long.fromValue(other);
|
|
1866
|
+
if (this.high >>> 31 === 1 && other.high >>> 31 === 1) return false;
|
|
1867
|
+
return this.high === other.high && this.low === other.low;
|
|
1868
|
+
}
|
|
1869
|
+
notEquals(other) {
|
|
1870
|
+
return !this.equals(other);
|
|
1871
|
+
}
|
|
1872
|
+
comp(other) {
|
|
1873
|
+
if (!Long.isLong(other)) other = Long.fromValue(other);
|
|
1874
|
+
if (this.equals(other)) return 0;
|
|
1875
|
+
return other.high >>> 0 > this.high >>> 0 || other.high === this.high && other.low >>> 0 > this.low >>> 0 ? -1 : 1;
|
|
1876
|
+
}
|
|
1877
|
+
lessThanOrEqual(other) {
|
|
1878
|
+
return this.comp(
|
|
1879
|
+
/* validates */
|
|
1880
|
+
other
|
|
1881
|
+
) <= 0;
|
|
1882
|
+
}
|
|
1883
|
+
static fromValue(val) {
|
|
1884
|
+
if (typeof val === "number") return Long.fromNumber(val);
|
|
1885
|
+
return new Long(val.low, val.high);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
const UZERO = new Long(0, 0);
|
|
1889
|
+
const TWO_PWR_16_DBL = 1 << 16;
|
|
1890
|
+
const TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL;
|
|
1891
|
+
const TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL;
|
|
1892
|
+
const MAX_UNSIGNED_VALUE = new Long(4294967295 | 0, 4294967295 | 0);
|
|
1893
|
+
var __defProp$6 = Object.defineProperty;
|
|
1894
|
+
var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
1895
|
+
var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1896
|
+
class RemoteQuerySet {
|
|
1897
|
+
constructor(queryPath, logger) {
|
|
1898
|
+
__publicField$6(this, "version");
|
|
1899
|
+
__publicField$6(this, "remoteQuerySet");
|
|
1900
|
+
__publicField$6(this, "queryPath");
|
|
1901
|
+
__publicField$6(this, "logger");
|
|
1902
|
+
this.version = { querySet: 0, ts: Long.fromNumber(0), identity: 0 };
|
|
1903
|
+
this.remoteQuerySet = /* @__PURE__ */ new Map();
|
|
1904
|
+
this.queryPath = queryPath;
|
|
1905
|
+
this.logger = logger;
|
|
1906
|
+
}
|
|
1907
|
+
transition(transition) {
|
|
1908
|
+
const start = transition.startVersion;
|
|
1909
|
+
if (this.version.querySet !== start.querySet || this.version.ts.notEquals(start.ts) || this.version.identity !== start.identity) {
|
|
1910
|
+
throw new Error(
|
|
1911
|
+
`Invalid start version: ${start.ts.toString()}:${start.querySet}:${start.identity}, transitioning from ${this.version.ts.toString()}:${this.version.querySet}:${this.version.identity}`
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
for (const modification of transition.modifications) {
|
|
1915
|
+
switch (modification.type) {
|
|
1916
|
+
case "QueryUpdated": {
|
|
1917
|
+
const queryPath = this.queryPath(modification.queryId);
|
|
1918
|
+
if (queryPath) {
|
|
1919
|
+
for (const line of modification.logLines) {
|
|
1920
|
+
logForFunction(this.logger, "info", "query", queryPath, line);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const value = jsonToConvex(modification.value ?? null);
|
|
1924
|
+
this.remoteQuerySet.set(modification.queryId, {
|
|
1925
|
+
success: true,
|
|
1926
|
+
value,
|
|
1927
|
+
logLines: modification.logLines
|
|
1928
|
+
});
|
|
1929
|
+
break;
|
|
1930
|
+
}
|
|
1931
|
+
case "QueryFailed": {
|
|
1932
|
+
const queryPath = this.queryPath(modification.queryId);
|
|
1933
|
+
if (queryPath) {
|
|
1934
|
+
for (const line of modification.logLines) {
|
|
1935
|
+
logForFunction(this.logger, "info", "query", queryPath, line);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
const { errorData } = modification;
|
|
1939
|
+
this.remoteQuerySet.set(modification.queryId, {
|
|
1940
|
+
success: false,
|
|
1941
|
+
errorMessage: modification.errorMessage,
|
|
1942
|
+
errorData: errorData !== void 0 ? jsonToConvex(errorData) : void 0,
|
|
1943
|
+
logLines: modification.logLines
|
|
1944
|
+
});
|
|
1945
|
+
break;
|
|
1946
|
+
}
|
|
1947
|
+
case "QueryRemoved": {
|
|
1948
|
+
this.remoteQuerySet.delete(modification.queryId);
|
|
1949
|
+
break;
|
|
1950
|
+
}
|
|
1951
|
+
default: {
|
|
1952
|
+
throw new Error(`Invalid modification ${modification.type}`);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
this.version = transition.endVersion;
|
|
1957
|
+
}
|
|
1958
|
+
remoteQueryResults() {
|
|
1959
|
+
return this.remoteQuerySet;
|
|
1960
|
+
}
|
|
1961
|
+
timestamp() {
|
|
1962
|
+
return this.version.ts;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
function u64ToLong(encoded) {
|
|
1966
|
+
const integerBytes = toByteArray(encoded);
|
|
1967
|
+
return Long.fromBytesLE(Array.from(integerBytes));
|
|
1968
|
+
}
|
|
1969
|
+
function longToU64(raw) {
|
|
1970
|
+
const integerBytes = new Uint8Array(raw.toBytesLE());
|
|
1971
|
+
return fromByteArray(integerBytes);
|
|
1972
|
+
}
|
|
1973
|
+
function parseServerMessage(encoded) {
|
|
1974
|
+
switch (encoded.type) {
|
|
1975
|
+
case "FatalError":
|
|
1976
|
+
case "AuthError":
|
|
1977
|
+
case "ActionResponse":
|
|
1978
|
+
case "TransitionChunk":
|
|
1979
|
+
case "Ping": {
|
|
1980
|
+
return { ...encoded };
|
|
1981
|
+
}
|
|
1982
|
+
case "MutationResponse": {
|
|
1983
|
+
if (encoded.success) {
|
|
1984
|
+
return { ...encoded, ts: u64ToLong(encoded.ts) };
|
|
1985
|
+
} else {
|
|
1986
|
+
return { ...encoded };
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
case "Transition": {
|
|
1990
|
+
return {
|
|
1991
|
+
...encoded,
|
|
1992
|
+
startVersion: {
|
|
1993
|
+
...encoded.startVersion,
|
|
1994
|
+
ts: u64ToLong(encoded.startVersion.ts)
|
|
1995
|
+
},
|
|
1996
|
+
endVersion: {
|
|
1997
|
+
...encoded.endVersion,
|
|
1998
|
+
ts: u64ToLong(encoded.endVersion.ts)
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return void 0;
|
|
2004
|
+
}
|
|
2005
|
+
function encodeClientMessage(message) {
|
|
2006
|
+
switch (message.type) {
|
|
2007
|
+
case "Authenticate":
|
|
2008
|
+
case "ModifyQuerySet":
|
|
2009
|
+
case "Mutation":
|
|
2010
|
+
case "Action":
|
|
2011
|
+
case "Event": {
|
|
2012
|
+
return { ...message };
|
|
2013
|
+
}
|
|
2014
|
+
case "Connect": {
|
|
2015
|
+
if (message.maxObservedTimestamp !== void 0) {
|
|
2016
|
+
return {
|
|
2017
|
+
...message,
|
|
2018
|
+
maxObservedTimestamp: longToU64(message.maxObservedTimestamp)
|
|
2019
|
+
};
|
|
2020
|
+
} else {
|
|
2021
|
+
return { ...message, maxObservedTimestamp: void 0 };
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
return void 0;
|
|
2026
|
+
}
|
|
2027
|
+
var __defProp$5 = Object.defineProperty;
|
|
2028
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
2029
|
+
var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
2030
|
+
const CLOSE_NORMAL = 1e3;
|
|
2031
|
+
const CLOSE_GOING_AWAY = 1001;
|
|
2032
|
+
const CLOSE_NO_STATUS = 1005;
|
|
2033
|
+
const CLOSE_NOT_FOUND = 4040;
|
|
2034
|
+
let firstTime;
|
|
2035
|
+
function monotonicMillis() {
|
|
2036
|
+
if (firstTime === void 0) {
|
|
2037
|
+
firstTime = Date.now();
|
|
2038
|
+
}
|
|
2039
|
+
if (typeof performance === "undefined" || !performance.now) {
|
|
2040
|
+
return Date.now();
|
|
2041
|
+
}
|
|
2042
|
+
return Math.round(firstTime + performance.now());
|
|
2043
|
+
}
|
|
2044
|
+
function prettyNow() {
|
|
2045
|
+
return `t=${Math.round((monotonicMillis() - firstTime) / 100) / 10}s`;
|
|
2046
|
+
}
|
|
2047
|
+
const serverDisconnectErrors = {
|
|
2048
|
+
// A known error, e.g. during a restart or push
|
|
2049
|
+
InternalServerError: { timeout: 1e3 },
|
|
2050
|
+
// ErrorMetadata::overloaded() messages that we realy should back off
|
|
2051
|
+
SubscriptionsWorkerFullError: { timeout: 3e3 },
|
|
2052
|
+
TooManyConcurrentRequests: { timeout: 3e3 },
|
|
2053
|
+
CommitterFullError: { timeout: 3e3 },
|
|
2054
|
+
AwsTooManyRequestsException: { timeout: 3e3 },
|
|
2055
|
+
ExecuteFullError: { timeout: 3e3 },
|
|
2056
|
+
SystemTimeoutError: { timeout: 3e3 },
|
|
2057
|
+
ExpiredInQueue: { timeout: 3e3 },
|
|
2058
|
+
// ErrorMetadata::feature_temporarily_unavailable() that typically indicate a deploy just happened
|
|
2059
|
+
VectorIndexesUnavailable: { timeout: 1e3 },
|
|
2060
|
+
SearchIndexesUnavailable: { timeout: 1e3 },
|
|
2061
|
+
TableSummariesUnavailable: { timeout: 1e3 },
|
|
2062
|
+
// More ErrorMetadata::overloaded()
|
|
2063
|
+
VectorIndexTooLarge: { timeout: 3e3 },
|
|
2064
|
+
SearchIndexTooLarge: { timeout: 3e3 },
|
|
2065
|
+
TooManyWritesInTimePeriod: { timeout: 3e3 }
|
|
2066
|
+
};
|
|
2067
|
+
function classifyDisconnectError(s) {
|
|
2068
|
+
if (s === void 0) return "Unknown";
|
|
2069
|
+
for (const prefix of Object.keys(
|
|
2070
|
+
serverDisconnectErrors
|
|
2071
|
+
)) {
|
|
2072
|
+
if (s.startsWith(prefix)) {
|
|
2073
|
+
return prefix;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
return "Unknown";
|
|
2077
|
+
}
|
|
2078
|
+
class WebSocketManager {
|
|
2079
|
+
constructor(uri, callbacks, webSocketConstructor, logger, markConnectionStateDirty, debug) {
|
|
2080
|
+
this.markConnectionStateDirty = markConnectionStateDirty;
|
|
2081
|
+
this.debug = debug;
|
|
2082
|
+
__publicField$5(this, "socket");
|
|
2083
|
+
__publicField$5(this, "connectionCount");
|
|
2084
|
+
__publicField$5(this, "_hasEverConnected", false);
|
|
2085
|
+
__publicField$5(this, "lastCloseReason");
|
|
2086
|
+
__publicField$5(this, "transitionChunkBuffer", null);
|
|
2087
|
+
__publicField$5(this, "defaultInitialBackoff");
|
|
2088
|
+
__publicField$5(this, "maxBackoff");
|
|
2089
|
+
__publicField$5(this, "retries");
|
|
2090
|
+
__publicField$5(this, "serverInactivityThreshold");
|
|
2091
|
+
__publicField$5(this, "reconnectDueToServerInactivityTimeout");
|
|
2092
|
+
__publicField$5(this, "scheduledReconnect", null);
|
|
2093
|
+
__publicField$5(this, "networkOnlineHandler", null);
|
|
2094
|
+
__publicField$5(this, "pendingNetworkRecoveryInfo", null);
|
|
2095
|
+
__publicField$5(this, "uri");
|
|
2096
|
+
__publicField$5(this, "onOpen");
|
|
2097
|
+
__publicField$5(this, "onResume");
|
|
2098
|
+
__publicField$5(this, "onMessage");
|
|
2099
|
+
__publicField$5(this, "webSocketConstructor");
|
|
2100
|
+
__publicField$5(this, "logger");
|
|
2101
|
+
__publicField$5(this, "onServerDisconnectError");
|
|
2102
|
+
this.webSocketConstructor = webSocketConstructor;
|
|
2103
|
+
this.socket = { state: "disconnected" };
|
|
2104
|
+
this.connectionCount = 0;
|
|
2105
|
+
this.lastCloseReason = "InitialConnect";
|
|
2106
|
+
this.defaultInitialBackoff = 1e3;
|
|
2107
|
+
this.maxBackoff = 16e3;
|
|
2108
|
+
this.retries = 0;
|
|
2109
|
+
this.serverInactivityThreshold = 6e4;
|
|
2110
|
+
this.reconnectDueToServerInactivityTimeout = null;
|
|
2111
|
+
this.uri = uri;
|
|
2112
|
+
this.onOpen = callbacks.onOpen;
|
|
2113
|
+
this.onResume = callbacks.onResume;
|
|
2114
|
+
this.onMessage = callbacks.onMessage;
|
|
2115
|
+
this.onServerDisconnectError = callbacks.onServerDisconnectError;
|
|
2116
|
+
this.logger = logger;
|
|
2117
|
+
this.setupNetworkListener();
|
|
2118
|
+
this.connect();
|
|
2119
|
+
}
|
|
2120
|
+
setSocketState(state) {
|
|
2121
|
+
this.socket = state;
|
|
2122
|
+
this._logVerbose(
|
|
2123
|
+
`socket state changed: ${this.socket.state}, paused: ${"paused" in this.socket ? this.socket.paused : void 0}`
|
|
2124
|
+
);
|
|
2125
|
+
this.markConnectionStateDirty();
|
|
2126
|
+
}
|
|
2127
|
+
setupNetworkListener() {
|
|
2128
|
+
if (typeof window === "undefined" || typeof window.addEventListener !== "function") {
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
if (this.networkOnlineHandler !== null) {
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
this.networkOnlineHandler = () => {
|
|
2135
|
+
this._logVerbose("network online event detected");
|
|
2136
|
+
this.tryReconnectImmediately();
|
|
2137
|
+
};
|
|
2138
|
+
window.addEventListener("online", this.networkOnlineHandler);
|
|
2139
|
+
this._logVerbose("network online event listener registered");
|
|
2140
|
+
}
|
|
2141
|
+
cleanupNetworkListener() {
|
|
2142
|
+
if (this.networkOnlineHandler && typeof window !== "undefined" && typeof window.removeEventListener === "function") {
|
|
2143
|
+
window.removeEventListener("online", this.networkOnlineHandler);
|
|
2144
|
+
this.networkOnlineHandler = null;
|
|
2145
|
+
this._logVerbose("network online event listener removed");
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
assembleTransition(chunk) {
|
|
2149
|
+
if (chunk.partNumber < 0 || chunk.partNumber >= chunk.totalParts || chunk.totalParts === 0 || this.transitionChunkBuffer && (this.transitionChunkBuffer.totalParts !== chunk.totalParts || this.transitionChunkBuffer.transitionId !== chunk.transitionId)) {
|
|
2150
|
+
this.transitionChunkBuffer = null;
|
|
2151
|
+
throw new Error("Invalid TransitionChunk");
|
|
2152
|
+
}
|
|
2153
|
+
if (this.transitionChunkBuffer === null) {
|
|
2154
|
+
this.transitionChunkBuffer = {
|
|
2155
|
+
chunks: [],
|
|
2156
|
+
totalParts: chunk.totalParts,
|
|
2157
|
+
transitionId: chunk.transitionId
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
if (chunk.partNumber !== this.transitionChunkBuffer.chunks.length) {
|
|
2161
|
+
const expectedLength = this.transitionChunkBuffer.chunks.length;
|
|
2162
|
+
this.transitionChunkBuffer = null;
|
|
2163
|
+
throw new Error(
|
|
2164
|
+
`TransitionChunk received out of order: expected part ${expectedLength}, got ${chunk.partNumber}`
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
this.transitionChunkBuffer.chunks.push(chunk.chunk);
|
|
2168
|
+
if (this.transitionChunkBuffer.chunks.length === chunk.totalParts) {
|
|
2169
|
+
const fullJson = this.transitionChunkBuffer.chunks.join("");
|
|
2170
|
+
this.transitionChunkBuffer = null;
|
|
2171
|
+
const transition = parseServerMessage(JSON.parse(fullJson));
|
|
2172
|
+
if (transition.type !== "Transition") {
|
|
2173
|
+
throw new Error(
|
|
2174
|
+
`Expected Transition, got ${transition.type} after assembling chunks`
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
return transition;
|
|
2178
|
+
}
|
|
2179
|
+
return null;
|
|
2180
|
+
}
|
|
2181
|
+
connect() {
|
|
2182
|
+
if (this.socket.state === "terminated") {
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
if (this.socket.state !== "disconnected" && this.socket.state !== "stopped") {
|
|
2186
|
+
throw new Error(
|
|
2187
|
+
"Didn't start connection from disconnected state: " + this.socket.state
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
const ws = new this.webSocketConstructor(this.uri);
|
|
2191
|
+
this._logVerbose("constructed WebSocket");
|
|
2192
|
+
this.setSocketState({
|
|
2193
|
+
state: "connecting",
|
|
2194
|
+
ws,
|
|
2195
|
+
paused: "no"
|
|
2196
|
+
});
|
|
2197
|
+
this.resetServerInactivityTimeout();
|
|
2198
|
+
ws.onopen = () => {
|
|
2199
|
+
this.logger.logVerbose("begin ws.onopen");
|
|
2200
|
+
if (this.socket.state !== "connecting") {
|
|
2201
|
+
throw new Error("onopen called with socket not in connecting state");
|
|
2202
|
+
}
|
|
2203
|
+
this.setSocketState({
|
|
2204
|
+
state: "ready",
|
|
2205
|
+
ws,
|
|
2206
|
+
paused: this.socket.paused === "yes" ? "uninitialized" : "no"
|
|
2207
|
+
});
|
|
2208
|
+
this.resetServerInactivityTimeout();
|
|
2209
|
+
if (this.socket.paused === "no") {
|
|
2210
|
+
this._hasEverConnected = true;
|
|
2211
|
+
this.onOpen({
|
|
2212
|
+
connectionCount: this.connectionCount,
|
|
2213
|
+
lastCloseReason: this.lastCloseReason,
|
|
2214
|
+
clientTs: monotonicMillis()
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
if (this.lastCloseReason !== "InitialConnect") {
|
|
2218
|
+
if (this.lastCloseReason) {
|
|
2219
|
+
this.logger.log(
|
|
2220
|
+
"WebSocket reconnected at",
|
|
2221
|
+
prettyNow(),
|
|
2222
|
+
"after disconnect due to",
|
|
2223
|
+
this.lastCloseReason
|
|
2224
|
+
);
|
|
2225
|
+
} else {
|
|
2226
|
+
this.logger.log("WebSocket reconnected at", prettyNow());
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
this.connectionCount += 1;
|
|
2230
|
+
this.lastCloseReason = null;
|
|
2231
|
+
if (this.pendingNetworkRecoveryInfo !== null) {
|
|
2232
|
+
const { timeSavedMs } = this.pendingNetworkRecoveryInfo;
|
|
2233
|
+
this.pendingNetworkRecoveryInfo = null;
|
|
2234
|
+
this.sendMessage({
|
|
2235
|
+
type: "Event",
|
|
2236
|
+
eventType: "NetworkRecoveryReconnect",
|
|
2237
|
+
event: { timeSavedMs }
|
|
2238
|
+
});
|
|
2239
|
+
this.logger.log(
|
|
2240
|
+
`Network recovery reconnect saved ~${Math.round(timeSavedMs / 1e3)}s of waiting`
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
ws.onerror = (error) => {
|
|
2245
|
+
this.transitionChunkBuffer = null;
|
|
2246
|
+
const message = error.message;
|
|
2247
|
+
if (message) {
|
|
2248
|
+
this.logger.log(`WebSocket error message: ${message}`);
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
ws.onmessage = (message) => {
|
|
2252
|
+
this.resetServerInactivityTimeout();
|
|
2253
|
+
const messageLength = message.data.length;
|
|
2254
|
+
let serverMessage = parseServerMessage(JSON.parse(message.data));
|
|
2255
|
+
this._logVerbose(`received ws message with type ${serverMessage.type}`);
|
|
2256
|
+
if (serverMessage.type === "Ping") {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
if (serverMessage.type === "TransitionChunk") {
|
|
2260
|
+
const transition = this.assembleTransition(serverMessage);
|
|
2261
|
+
if (!transition) {
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
serverMessage = transition;
|
|
2265
|
+
this._logVerbose(
|
|
2266
|
+
`assembled full ws message of type ${serverMessage.type}`
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
if (this.transitionChunkBuffer !== null) {
|
|
2270
|
+
this.transitionChunkBuffer = null;
|
|
2271
|
+
this.logger.log(
|
|
2272
|
+
`Received unexpected ${serverMessage.type} while buffering TransitionChunks`
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
if (serverMessage.type === "Transition") {
|
|
2276
|
+
this.reportLargeTransition({
|
|
2277
|
+
messageLength,
|
|
2278
|
+
transition: serverMessage
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
const response = this.onMessage(serverMessage);
|
|
2282
|
+
if (response.hasSyncedPastLastReconnect) {
|
|
2283
|
+
this.retries = 0;
|
|
2284
|
+
this.markConnectionStateDirty();
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
ws.onclose = (event) => {
|
|
2288
|
+
this._logVerbose("begin ws.onclose");
|
|
2289
|
+
this.transitionChunkBuffer = null;
|
|
2290
|
+
if (this.lastCloseReason === null) {
|
|
2291
|
+
this.lastCloseReason = event.reason || `closed with code ${event.code}`;
|
|
2292
|
+
}
|
|
2293
|
+
if (event.code !== CLOSE_NORMAL && event.code !== CLOSE_GOING_AWAY && // This commonly gets fired on mobile apps when the app is backgrounded
|
|
2294
|
+
event.code !== CLOSE_NO_STATUS && event.code !== CLOSE_NOT_FOUND) {
|
|
2295
|
+
let msg = `WebSocket closed with code ${event.code}`;
|
|
2296
|
+
if (event.reason) {
|
|
2297
|
+
msg += `: ${event.reason}`;
|
|
2298
|
+
}
|
|
2299
|
+
this.logger.log(msg);
|
|
2300
|
+
if (this.onServerDisconnectError && event.reason) {
|
|
2301
|
+
this.onServerDisconnectError(msg);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
const reason = classifyDisconnectError(event.reason);
|
|
2305
|
+
this.scheduleReconnect(reason);
|
|
2306
|
+
return;
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
/**
|
|
2310
|
+
* @returns The state of the {@link Socket}.
|
|
2311
|
+
*/
|
|
2312
|
+
socketState() {
|
|
2313
|
+
return this.socket.state;
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* @param message - A ClientMessage to send.
|
|
2317
|
+
* @returns Whether the message (might have been) sent.
|
|
2318
|
+
*/
|
|
2319
|
+
sendMessage(message) {
|
|
2320
|
+
const messageForLog = {
|
|
2321
|
+
type: message.type,
|
|
2322
|
+
...message.type === "Authenticate" && message.tokenType === "User" ? {
|
|
2323
|
+
value: `...${message.value.slice(-7)}`
|
|
2324
|
+
} : {}
|
|
2325
|
+
};
|
|
2326
|
+
if (this.socket.state === "ready" && this.socket.paused === "no") {
|
|
2327
|
+
const encodedMessage = encodeClientMessage(message);
|
|
2328
|
+
const request = JSON.stringify(encodedMessage);
|
|
2329
|
+
let sent = false;
|
|
2330
|
+
try {
|
|
2331
|
+
this.socket.ws.send(request);
|
|
2332
|
+
sent = true;
|
|
2333
|
+
} catch (error) {
|
|
2334
|
+
this.logger.log(
|
|
2335
|
+
`Failed to send message on WebSocket, reconnecting: ${error}`
|
|
2336
|
+
);
|
|
2337
|
+
this.closeAndReconnect("FailedToSendMessage");
|
|
2338
|
+
}
|
|
2339
|
+
this._logVerbose(
|
|
2340
|
+
`${sent ? "sent" : "failed to send"} message with type ${message.type}: ${JSON.stringify(
|
|
2341
|
+
messageForLog
|
|
2342
|
+
)}`
|
|
2343
|
+
);
|
|
2344
|
+
return true;
|
|
2345
|
+
}
|
|
2346
|
+
this._logVerbose(
|
|
2347
|
+
`message not sent (socket state: ${this.socket.state}, paused: ${"paused" in this.socket ? this.socket.paused : void 0}): ${JSON.stringify(
|
|
2348
|
+
messageForLog
|
|
2349
|
+
)}`
|
|
2350
|
+
);
|
|
2351
|
+
return false;
|
|
2352
|
+
}
|
|
2353
|
+
resetServerInactivityTimeout() {
|
|
2354
|
+
if (this.socket.state === "terminated") {
|
|
2355
|
+
return;
|
|
2356
|
+
}
|
|
2357
|
+
if (this.reconnectDueToServerInactivityTimeout !== null) {
|
|
2358
|
+
clearTimeout(this.reconnectDueToServerInactivityTimeout);
|
|
2359
|
+
this.reconnectDueToServerInactivityTimeout = null;
|
|
2360
|
+
}
|
|
2361
|
+
this.reconnectDueToServerInactivityTimeout = setTimeout(() => {
|
|
2362
|
+
this.closeAndReconnect("InactiveServer");
|
|
2363
|
+
}, this.serverInactivityThreshold);
|
|
2364
|
+
}
|
|
2365
|
+
scheduleReconnect(reason) {
|
|
2366
|
+
if (this.scheduledReconnect) {
|
|
2367
|
+
clearTimeout(this.scheduledReconnect.timeout);
|
|
2368
|
+
this.scheduledReconnect = null;
|
|
2369
|
+
}
|
|
2370
|
+
this.socket = { state: "disconnected" };
|
|
2371
|
+
const backoff = this.nextBackoff(reason);
|
|
2372
|
+
this.markConnectionStateDirty();
|
|
2373
|
+
this.logger.log(`Attempting reconnect in ${Math.round(backoff)}ms`);
|
|
2374
|
+
const scheduledAt = monotonicMillis();
|
|
2375
|
+
const timeoutId = setTimeout(() => {
|
|
2376
|
+
if (this.scheduledReconnect?.timeout === timeoutId) {
|
|
2377
|
+
this.scheduledReconnect = null;
|
|
2378
|
+
this.connect();
|
|
2379
|
+
}
|
|
2380
|
+
}, backoff);
|
|
2381
|
+
this.scheduledReconnect = {
|
|
2382
|
+
timeout: timeoutId,
|
|
2383
|
+
scheduledAt,
|
|
2384
|
+
backoffMs: backoff
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Close the WebSocket and schedule a reconnect.
|
|
2389
|
+
*
|
|
2390
|
+
* This should be used when we hit an error and would like to restart the session.
|
|
2391
|
+
*/
|
|
2392
|
+
closeAndReconnect(closeReason) {
|
|
2393
|
+
this._logVerbose(`begin closeAndReconnect with reason ${closeReason}`);
|
|
2394
|
+
switch (this.socket.state) {
|
|
2395
|
+
case "disconnected":
|
|
2396
|
+
case "terminated":
|
|
2397
|
+
case "stopped":
|
|
2398
|
+
return;
|
|
2399
|
+
case "connecting":
|
|
2400
|
+
case "ready": {
|
|
2401
|
+
this.lastCloseReason = closeReason;
|
|
2402
|
+
void this.close();
|
|
2403
|
+
this.scheduleReconnect("client");
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2406
|
+
default: {
|
|
2407
|
+
this.socket;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Close the WebSocket, being careful to clear the onclose handler to avoid re-entrant
|
|
2413
|
+
* calls. Use this instead of directly calling `ws.close()`
|
|
2414
|
+
*
|
|
2415
|
+
* It is the callers responsibility to update the state after this method is called so that the
|
|
2416
|
+
* closed socket is not accessible or used again after this method is called
|
|
2417
|
+
*/
|
|
2418
|
+
close() {
|
|
2419
|
+
this.transitionChunkBuffer = null;
|
|
2420
|
+
switch (this.socket.state) {
|
|
2421
|
+
case "disconnected":
|
|
2422
|
+
case "terminated":
|
|
2423
|
+
case "stopped":
|
|
2424
|
+
return Promise.resolve();
|
|
2425
|
+
case "connecting": {
|
|
2426
|
+
const ws = this.socket.ws;
|
|
2427
|
+
ws.onmessage = (_message) => {
|
|
2428
|
+
this._logVerbose("Ignoring message received after close");
|
|
2429
|
+
};
|
|
2430
|
+
return new Promise((r) => {
|
|
2431
|
+
ws.onclose = () => {
|
|
2432
|
+
this._logVerbose("Closed after connecting");
|
|
2433
|
+
r();
|
|
2434
|
+
};
|
|
2435
|
+
ws.onopen = () => {
|
|
2436
|
+
this._logVerbose("Opened after connecting");
|
|
2437
|
+
ws.close();
|
|
2438
|
+
};
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
case "ready": {
|
|
2442
|
+
this._logVerbose("ws.close called");
|
|
2443
|
+
const ws = this.socket.ws;
|
|
2444
|
+
ws.onmessage = (_message) => {
|
|
2445
|
+
this._logVerbose("Ignoring message received after close");
|
|
2446
|
+
};
|
|
2447
|
+
const result = new Promise((r) => {
|
|
2448
|
+
ws.onclose = () => {
|
|
2449
|
+
r();
|
|
2450
|
+
};
|
|
2451
|
+
});
|
|
2452
|
+
ws.close();
|
|
2453
|
+
return result;
|
|
2454
|
+
}
|
|
2455
|
+
default: {
|
|
2456
|
+
this.socket;
|
|
2457
|
+
return Promise.resolve();
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
/**
|
|
2462
|
+
* Close the WebSocket and do not reconnect.
|
|
2463
|
+
* @returns A Promise that resolves when the WebSocket `onClose` callback is called.
|
|
2464
|
+
*/
|
|
2465
|
+
terminate() {
|
|
2466
|
+
if (this.reconnectDueToServerInactivityTimeout) {
|
|
2467
|
+
clearTimeout(this.reconnectDueToServerInactivityTimeout);
|
|
2468
|
+
}
|
|
2469
|
+
if (this.scheduledReconnect) {
|
|
2470
|
+
clearTimeout(this.scheduledReconnect.timeout);
|
|
2471
|
+
this.scheduledReconnect = null;
|
|
2472
|
+
}
|
|
2473
|
+
this.cleanupNetworkListener();
|
|
2474
|
+
switch (this.socket.state) {
|
|
2475
|
+
case "terminated":
|
|
2476
|
+
case "stopped":
|
|
2477
|
+
case "disconnected":
|
|
2478
|
+
case "connecting":
|
|
2479
|
+
case "ready": {
|
|
2480
|
+
const result = this.close();
|
|
2481
|
+
this.setSocketState({ state: "terminated" });
|
|
2482
|
+
return result;
|
|
2483
|
+
}
|
|
2484
|
+
default: {
|
|
2485
|
+
this.socket;
|
|
2486
|
+
throw new Error(
|
|
2487
|
+
`Invalid websocket state: ${this.socket.state}`
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
stop() {
|
|
2493
|
+
switch (this.socket.state) {
|
|
2494
|
+
case "terminated":
|
|
2495
|
+
return Promise.resolve();
|
|
2496
|
+
case "connecting":
|
|
2497
|
+
case "stopped":
|
|
2498
|
+
case "disconnected":
|
|
2499
|
+
case "ready": {
|
|
2500
|
+
this.cleanupNetworkListener();
|
|
2501
|
+
const result = this.close();
|
|
2502
|
+
this.socket = { state: "stopped" };
|
|
2503
|
+
return result;
|
|
2504
|
+
}
|
|
2505
|
+
default: {
|
|
2506
|
+
this.socket;
|
|
2507
|
+
return Promise.resolve();
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Create a new WebSocket after a previous `stop()`, unless `terminate()` was
|
|
2513
|
+
* called before.
|
|
2514
|
+
*/
|
|
2515
|
+
tryRestart() {
|
|
2516
|
+
switch (this.socket.state) {
|
|
2517
|
+
case "stopped":
|
|
2518
|
+
break;
|
|
2519
|
+
case "terminated":
|
|
2520
|
+
case "connecting":
|
|
2521
|
+
case "ready":
|
|
2522
|
+
case "disconnected":
|
|
2523
|
+
this.logger.logVerbose("Restart called without stopping first");
|
|
2524
|
+
return;
|
|
2525
|
+
default: {
|
|
2526
|
+
this.socket;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
this.setupNetworkListener();
|
|
2530
|
+
this.connect();
|
|
2531
|
+
}
|
|
2532
|
+
pause() {
|
|
2533
|
+
switch (this.socket.state) {
|
|
2534
|
+
case "disconnected":
|
|
2535
|
+
case "stopped":
|
|
2536
|
+
case "terminated":
|
|
2537
|
+
return;
|
|
2538
|
+
case "connecting":
|
|
2539
|
+
case "ready": {
|
|
2540
|
+
this.socket = { ...this.socket, paused: "yes" };
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
default: {
|
|
2544
|
+
this.socket;
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
/**
|
|
2550
|
+
* Try to reconnect immediately, canceling any scheduled reconnect.
|
|
2551
|
+
* This is useful when detecting network recovery.
|
|
2552
|
+
* Only takes action if we're in disconnected state (waiting to reconnect).
|
|
2553
|
+
*/
|
|
2554
|
+
tryReconnectImmediately() {
|
|
2555
|
+
this._logVerbose("tryReconnectImmediately called");
|
|
2556
|
+
if (this.socket.state !== "disconnected") {
|
|
2557
|
+
this._logVerbose(
|
|
2558
|
+
`tryReconnectImmediately called but socket state is ${this.socket.state}, no action taken`
|
|
2559
|
+
);
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
let timeSavedMs = null;
|
|
2563
|
+
if (this.scheduledReconnect) {
|
|
2564
|
+
const elapsed = monotonicMillis() - this.scheduledReconnect.scheduledAt;
|
|
2565
|
+
timeSavedMs = Math.max(0, this.scheduledReconnect.backoffMs - elapsed);
|
|
2566
|
+
this._logVerbose(
|
|
2567
|
+
`would have waited ${Math.round(timeSavedMs)}ms more (backoff was ${Math.round(this.scheduledReconnect.backoffMs)}ms, elapsed ${Math.round(elapsed)}ms)`
|
|
2568
|
+
);
|
|
2569
|
+
clearTimeout(this.scheduledReconnect.timeout);
|
|
2570
|
+
this.scheduledReconnect = null;
|
|
2571
|
+
this._logVerbose("canceled scheduled reconnect");
|
|
2572
|
+
}
|
|
2573
|
+
this.logger.log("Network recovery detected, reconnecting immediately");
|
|
2574
|
+
this.pendingNetworkRecoveryInfo = timeSavedMs !== null ? { timeSavedMs } : null;
|
|
2575
|
+
this.connect();
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Resume the state machine if previously paused.
|
|
2579
|
+
*/
|
|
2580
|
+
resume() {
|
|
2581
|
+
switch (this.socket.state) {
|
|
2582
|
+
case "connecting":
|
|
2583
|
+
this.socket = { ...this.socket, paused: "no" };
|
|
2584
|
+
return;
|
|
2585
|
+
case "ready":
|
|
2586
|
+
if (this.socket.paused === "uninitialized") {
|
|
2587
|
+
this.socket = { ...this.socket, paused: "no" };
|
|
2588
|
+
this.onOpen({
|
|
2589
|
+
connectionCount: this.connectionCount,
|
|
2590
|
+
lastCloseReason: this.lastCloseReason,
|
|
2591
|
+
clientTs: monotonicMillis()
|
|
2592
|
+
});
|
|
2593
|
+
} else if (this.socket.paused === "yes") {
|
|
2594
|
+
this.socket = { ...this.socket, paused: "no" };
|
|
2595
|
+
this.onResume();
|
|
2596
|
+
}
|
|
2597
|
+
return;
|
|
2598
|
+
case "terminated":
|
|
2599
|
+
case "stopped":
|
|
2600
|
+
case "disconnected":
|
|
2601
|
+
return;
|
|
2602
|
+
default: {
|
|
2603
|
+
this.socket;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
this.connect();
|
|
2607
|
+
}
|
|
2608
|
+
connectionState() {
|
|
2609
|
+
return {
|
|
2610
|
+
isConnected: this.socket.state === "ready",
|
|
2611
|
+
hasEverConnected: this._hasEverConnected,
|
|
2612
|
+
connectionCount: this.connectionCount,
|
|
2613
|
+
connectionRetries: this.retries
|
|
2614
|
+
};
|
|
2615
|
+
}
|
|
2616
|
+
_logVerbose(message) {
|
|
2617
|
+
this.logger.logVerbose(message);
|
|
2618
|
+
}
|
|
2619
|
+
nextBackoff(reason) {
|
|
2620
|
+
const initialBackoff = reason === "client" ? 100 : reason === "Unknown" ? this.defaultInitialBackoff : serverDisconnectErrors[reason].timeout;
|
|
2621
|
+
const baseBackoff = initialBackoff * Math.pow(2, this.retries);
|
|
2622
|
+
this.retries += 1;
|
|
2623
|
+
const actualBackoff = Math.min(baseBackoff, this.maxBackoff);
|
|
2624
|
+
const jitter = actualBackoff * (Math.random() - 0.5);
|
|
2625
|
+
return actualBackoff + jitter;
|
|
2626
|
+
}
|
|
2627
|
+
reportLargeTransition({
|
|
2628
|
+
transition,
|
|
2629
|
+
messageLength
|
|
2630
|
+
}) {
|
|
2631
|
+
if (transition.clientClockSkew === void 0 || transition.serverTs === void 0) {
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
const transitionTransitTime = monotonicMillis() - // client time now
|
|
2635
|
+
// clientClockSkew = (server time + upstream latency) - client time
|
|
2636
|
+
// clientClockSkew is "how many milliseconds behind (slow) is the client clock"
|
|
2637
|
+
// but the latency of the Connect message inflates this, making it appear further behind
|
|
2638
|
+
transition.clientClockSkew - transition.serverTs / 1e6;
|
|
2639
|
+
const prettyTransitionTime = `${Math.round(transitionTransitTime)}ms`;
|
|
2640
|
+
const prettyMessageMB = `${Math.round(messageLength / 1e4) / 100}MB`;
|
|
2641
|
+
const bytesPerSecond = messageLength / (transitionTransitTime / 1e3);
|
|
2642
|
+
const prettyBytesPerSecond = `${Math.round(bytesPerSecond / 1e4) / 100}MB per second`;
|
|
2643
|
+
this._logVerbose(
|
|
2644
|
+
`received ${prettyMessageMB} transition in ${prettyTransitionTime} at ${prettyBytesPerSecond}`
|
|
2645
|
+
);
|
|
2646
|
+
if (messageLength > 2e7) {
|
|
2647
|
+
this.logger.log(
|
|
2648
|
+
`received query results totaling more that 20MB (${prettyMessageMB}) which will take a long time to download on slower connections`
|
|
2649
|
+
);
|
|
2650
|
+
} else if (transitionTransitTime > 2e4) {
|
|
2651
|
+
this.logger.log(
|
|
2652
|
+
`received query results totaling ${prettyMessageMB} which took more than 20s to arrive (${prettyTransitionTime})`
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
if (this.debug) {
|
|
2656
|
+
this.sendMessage({
|
|
2657
|
+
type: "Event",
|
|
2658
|
+
eventType: "ClientReceivedTransition",
|
|
2659
|
+
event: { transitionTransitTime, messageLength }
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
function newSessionId() {
|
|
2665
|
+
return uuidv4();
|
|
2666
|
+
}
|
|
2667
|
+
function uuidv4() {
|
|
2668
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
2669
|
+
const r = Math.random() * 16 | 0, v2 = c === "x" ? r : r & 3 | 8;
|
|
2670
|
+
return v2.toString(16);
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
class InvalidTokenError extends Error {
|
|
2674
|
+
}
|
|
2675
|
+
InvalidTokenError.prototype.name = "InvalidTokenError";
|
|
2676
|
+
function b64DecodeUnicode(str) {
|
|
2677
|
+
return decodeURIComponent(
|
|
2678
|
+
atob(str).replace(/(.)/g, (_m, p) => {
|
|
2679
|
+
let code2 = p.charCodeAt(0).toString(16).toUpperCase();
|
|
2680
|
+
if (code2.length < 2) {
|
|
2681
|
+
code2 = "0" + code2;
|
|
2682
|
+
}
|
|
2683
|
+
return "%" + code2;
|
|
2684
|
+
})
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
function base64UrlDecode(str) {
|
|
2688
|
+
let output = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
2689
|
+
switch (output.length % 4) {
|
|
2690
|
+
case 0:
|
|
2691
|
+
break;
|
|
2692
|
+
case 2:
|
|
2693
|
+
output += "==";
|
|
2694
|
+
break;
|
|
2695
|
+
case 3:
|
|
2696
|
+
output += "=";
|
|
2697
|
+
break;
|
|
2698
|
+
default:
|
|
2699
|
+
throw new Error("base64 string is not of the correct length");
|
|
2700
|
+
}
|
|
2701
|
+
try {
|
|
2702
|
+
return b64DecodeUnicode(output);
|
|
2703
|
+
} catch {
|
|
2704
|
+
return atob(output);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
function jwtDecode(token, options) {
|
|
2708
|
+
if (typeof token !== "string") {
|
|
2709
|
+
throw new InvalidTokenError("Invalid token specified: must be a string");
|
|
2710
|
+
}
|
|
2711
|
+
options || (options = {});
|
|
2712
|
+
const pos = options.header === true ? 0 : 1;
|
|
2713
|
+
const part = token.split(".")[pos];
|
|
2714
|
+
if (typeof part !== "string") {
|
|
2715
|
+
throw new InvalidTokenError(
|
|
2716
|
+
`Invalid token specified: missing part #${pos + 1}`
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
let decoded;
|
|
2720
|
+
try {
|
|
2721
|
+
decoded = base64UrlDecode(part);
|
|
2722
|
+
} catch (e) {
|
|
2723
|
+
throw new InvalidTokenError(
|
|
2724
|
+
`Invalid token specified: invalid base64 for part #${pos + 1} (${e.message})`
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
try {
|
|
2728
|
+
return JSON.parse(decoded);
|
|
2729
|
+
} catch (e) {
|
|
2730
|
+
throw new InvalidTokenError(
|
|
2731
|
+
`Invalid token specified: invalid json for part #${pos + 1} (${e.message})`
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
var __defProp$4 = Object.defineProperty;
|
|
2736
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
2737
|
+
var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
2738
|
+
const MAXIMUM_REFRESH_DELAY = 20 * 24 * 60 * 60 * 1e3;
|
|
2739
|
+
const MAX_TOKEN_CONFIRMATION_ATTEMPTS = 2;
|
|
2740
|
+
class AuthenticationManager {
|
|
2741
|
+
constructor(syncState, callbacks, config) {
|
|
2742
|
+
__publicField$4(this, "authState", { state: "noAuth" });
|
|
2743
|
+
__publicField$4(this, "configVersion", 0);
|
|
2744
|
+
__publicField$4(this, "syncState");
|
|
2745
|
+
__publicField$4(this, "authenticate");
|
|
2746
|
+
__publicField$4(this, "stopSocket");
|
|
2747
|
+
__publicField$4(this, "tryRestartSocket");
|
|
2748
|
+
__publicField$4(this, "pauseSocket");
|
|
2749
|
+
__publicField$4(this, "resumeSocket");
|
|
2750
|
+
__publicField$4(this, "clearAuth");
|
|
2751
|
+
__publicField$4(this, "logger");
|
|
2752
|
+
__publicField$4(this, "refreshTokenLeewaySeconds");
|
|
2753
|
+
__publicField$4(this, "tokenConfirmationAttempts", 0);
|
|
2754
|
+
this.syncState = syncState;
|
|
2755
|
+
this.authenticate = callbacks.authenticate;
|
|
2756
|
+
this.stopSocket = callbacks.stopSocket;
|
|
2757
|
+
this.tryRestartSocket = callbacks.tryRestartSocket;
|
|
2758
|
+
this.pauseSocket = callbacks.pauseSocket;
|
|
2759
|
+
this.resumeSocket = callbacks.resumeSocket;
|
|
2760
|
+
this.clearAuth = callbacks.clearAuth;
|
|
2761
|
+
this.logger = config.logger;
|
|
2762
|
+
this.refreshTokenLeewaySeconds = config.refreshTokenLeewaySeconds;
|
|
2763
|
+
}
|
|
2764
|
+
async setConfig(fetchToken, onChange) {
|
|
2765
|
+
this.resetAuthState();
|
|
2766
|
+
this._logVerbose("pausing WS for auth token fetch");
|
|
2767
|
+
this.pauseSocket();
|
|
2768
|
+
const token = await this.fetchTokenAndGuardAgainstRace(fetchToken, {
|
|
2769
|
+
forceRefreshToken: false
|
|
2770
|
+
});
|
|
2771
|
+
if (token.isFromOutdatedConfig) {
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
if (token.value) {
|
|
2775
|
+
this.setAuthState({
|
|
2776
|
+
state: "waitingForServerConfirmationOfCachedToken",
|
|
2777
|
+
config: { fetchToken, onAuthChange: onChange },
|
|
2778
|
+
hasRetried: false
|
|
2779
|
+
});
|
|
2780
|
+
this.authenticate(token.value);
|
|
2781
|
+
} else {
|
|
2782
|
+
this.setAuthState({
|
|
2783
|
+
state: "initialRefetch",
|
|
2784
|
+
config: { fetchToken, onAuthChange: onChange }
|
|
2785
|
+
});
|
|
2786
|
+
await this.refetchToken();
|
|
2787
|
+
}
|
|
2788
|
+
this._logVerbose("resuming WS after auth token fetch");
|
|
2789
|
+
this.resumeSocket();
|
|
2790
|
+
}
|
|
2791
|
+
onTransition(serverMessage) {
|
|
2792
|
+
if (!this.syncState.isCurrentOrNewerAuthVersion(
|
|
2793
|
+
serverMessage.endVersion.identity
|
|
2794
|
+
)) {
|
|
2795
|
+
return;
|
|
2796
|
+
}
|
|
2797
|
+
if (serverMessage.endVersion.identity <= serverMessage.startVersion.identity) {
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
if (this.authState.state === "waitingForServerConfirmationOfCachedToken") {
|
|
2801
|
+
this._logVerbose("server confirmed auth token is valid");
|
|
2802
|
+
void this.refetchToken();
|
|
2803
|
+
this.authState.config.onAuthChange(true);
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
if (this.authState.state === "waitingForServerConfirmationOfFreshToken") {
|
|
2807
|
+
this._logVerbose("server confirmed new auth token is valid");
|
|
2808
|
+
this.scheduleTokenRefetch(this.authState.token);
|
|
2809
|
+
this.tokenConfirmationAttempts = 0;
|
|
2810
|
+
if (!this.authState.hadAuth) {
|
|
2811
|
+
this.authState.config.onAuthChange(true);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
onAuthError(serverMessage) {
|
|
2816
|
+
if (serverMessage.authUpdateAttempted === false && (this.authState.state === "waitingForServerConfirmationOfFreshToken" || this.authState.state === "waitingForServerConfirmationOfCachedToken")) {
|
|
2817
|
+
this._logVerbose("ignoring non-auth token expired error");
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2820
|
+
const { baseVersion } = serverMessage;
|
|
2821
|
+
if (!this.syncState.isCurrentOrNewerAuthVersion(baseVersion + 1)) {
|
|
2822
|
+
this._logVerbose("ignoring auth error for previous auth attempt");
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
void this.tryToReauthenticate(serverMessage);
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
// This is similar to `refetchToken` defined below, in fact we
|
|
2829
|
+
// don't represent them as different states, but it is different
|
|
2830
|
+
// in that we pause the WebSocket so that mutations
|
|
2831
|
+
// don't retry with bad auth.
|
|
2832
|
+
async tryToReauthenticate(serverMessage) {
|
|
2833
|
+
this._logVerbose(`attempting to reauthenticate: ${serverMessage.error}`);
|
|
2834
|
+
if (
|
|
2835
|
+
// No way to fetch another token, kaboom
|
|
2836
|
+
this.authState.state === "noAuth" || // We failed on a fresh token. After a small number of retries, we give up
|
|
2837
|
+
// and clear the auth state to avoid infinite retries.
|
|
2838
|
+
this.authState.state === "waitingForServerConfirmationOfFreshToken" && this.tokenConfirmationAttempts >= MAX_TOKEN_CONFIRMATION_ATTEMPTS
|
|
2839
|
+
) {
|
|
2840
|
+
this.logger.error(
|
|
2841
|
+
`Failed to authenticate: "${serverMessage.error}", check your server auth config`
|
|
2842
|
+
);
|
|
2843
|
+
if (this.syncState.hasAuth()) {
|
|
2844
|
+
this.syncState.clearAuth();
|
|
2845
|
+
}
|
|
2846
|
+
if (this.authState.state !== "noAuth") {
|
|
2847
|
+
this.setAndReportAuthFailed(this.authState.config.onAuthChange);
|
|
2848
|
+
}
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
if (this.authState.state === "waitingForServerConfirmationOfFreshToken") {
|
|
2852
|
+
this.tokenConfirmationAttempts++;
|
|
2853
|
+
this._logVerbose(
|
|
2854
|
+
`retrying reauthentication, ${MAX_TOKEN_CONFIRMATION_ATTEMPTS - this.tokenConfirmationAttempts} attempts remaining`
|
|
2855
|
+
);
|
|
2856
|
+
}
|
|
2857
|
+
await this.stopSocket();
|
|
2858
|
+
const token = await this.fetchTokenAndGuardAgainstRace(
|
|
2859
|
+
this.authState.config.fetchToken,
|
|
2860
|
+
{
|
|
2861
|
+
forceRefreshToken: true
|
|
2862
|
+
}
|
|
2863
|
+
);
|
|
2864
|
+
if (token.isFromOutdatedConfig) {
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
if (token.value && this.syncState.isNewAuth(token.value)) {
|
|
2868
|
+
this.authenticate(token.value);
|
|
2869
|
+
this.setAuthState({
|
|
2870
|
+
state: "waitingForServerConfirmationOfFreshToken",
|
|
2871
|
+
config: this.authState.config,
|
|
2872
|
+
token: token.value,
|
|
2873
|
+
hadAuth: this.authState.state === "notRefetching" || this.authState.state === "waitingForScheduledRefetch"
|
|
2874
|
+
});
|
|
2875
|
+
} else {
|
|
2876
|
+
this._logVerbose("reauthentication failed, could not fetch a new token");
|
|
2877
|
+
if (this.syncState.hasAuth()) {
|
|
2878
|
+
this.syncState.clearAuth();
|
|
2879
|
+
}
|
|
2880
|
+
this.setAndReportAuthFailed(this.authState.config.onAuthChange);
|
|
2881
|
+
}
|
|
2882
|
+
this.tryRestartSocket();
|
|
2883
|
+
}
|
|
2884
|
+
// Force refetch the token and schedule another refetch
|
|
2885
|
+
// before the token expires - an active client should never
|
|
2886
|
+
// need to reauthenticate.
|
|
2887
|
+
async refetchToken() {
|
|
2888
|
+
if (this.authState.state === "noAuth") {
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
this._logVerbose("refetching auth token");
|
|
2892
|
+
const token = await this.fetchTokenAndGuardAgainstRace(
|
|
2893
|
+
this.authState.config.fetchToken,
|
|
2894
|
+
{
|
|
2895
|
+
forceRefreshToken: true
|
|
2896
|
+
}
|
|
2897
|
+
);
|
|
2898
|
+
if (token.isFromOutdatedConfig) {
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
if (token.value) {
|
|
2902
|
+
if (this.syncState.isNewAuth(token.value)) {
|
|
2903
|
+
this.setAuthState({
|
|
2904
|
+
state: "waitingForServerConfirmationOfFreshToken",
|
|
2905
|
+
hadAuth: this.syncState.hasAuth(),
|
|
2906
|
+
token: token.value,
|
|
2907
|
+
config: this.authState.config
|
|
2908
|
+
});
|
|
2909
|
+
this.authenticate(token.value);
|
|
2910
|
+
} else {
|
|
2911
|
+
this.setAuthState({
|
|
2912
|
+
state: "notRefetching",
|
|
2913
|
+
config: this.authState.config
|
|
2914
|
+
});
|
|
2915
|
+
}
|
|
2916
|
+
} else {
|
|
2917
|
+
this._logVerbose("refetching token failed");
|
|
2918
|
+
if (this.syncState.hasAuth()) {
|
|
2919
|
+
this.clearAuth();
|
|
2920
|
+
}
|
|
2921
|
+
this.setAndReportAuthFailed(this.authState.config.onAuthChange);
|
|
2922
|
+
}
|
|
2923
|
+
this._logVerbose(
|
|
2924
|
+
"restarting WS after auth token fetch (if currently stopped)"
|
|
2925
|
+
);
|
|
2926
|
+
this.tryRestartSocket();
|
|
2927
|
+
}
|
|
2928
|
+
scheduleTokenRefetch(token) {
|
|
2929
|
+
if (this.authState.state === "noAuth") {
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
const decodedToken = this.decodeToken(token);
|
|
2933
|
+
if (!decodedToken) {
|
|
2934
|
+
this.logger.error(
|
|
2935
|
+
"Auth token is not a valid JWT, cannot refetch the token"
|
|
2936
|
+
);
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
const { iat, exp } = decodedToken;
|
|
2940
|
+
if (!iat || !exp) {
|
|
2941
|
+
this.logger.error(
|
|
2942
|
+
"Auth token does not have required fields, cannot refetch the token"
|
|
2943
|
+
);
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
const tokenValiditySeconds = exp - iat;
|
|
2947
|
+
if (tokenValiditySeconds <= 2) {
|
|
2948
|
+
this.logger.error(
|
|
2949
|
+
"Auth token does not live long enough, cannot refetch the token"
|
|
2950
|
+
);
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
let delay = Math.min(
|
|
2954
|
+
MAXIMUM_REFRESH_DELAY,
|
|
2955
|
+
(tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1e3
|
|
2956
|
+
);
|
|
2957
|
+
if (delay <= 0) {
|
|
2958
|
+
this.logger.warn(
|
|
2959
|
+
`Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`
|
|
2960
|
+
);
|
|
2961
|
+
delay = 0;
|
|
2962
|
+
}
|
|
2963
|
+
const refetchTokenTimeoutId = setTimeout(() => {
|
|
2964
|
+
this._logVerbose("running scheduled token refetch");
|
|
2965
|
+
void this.refetchToken();
|
|
2966
|
+
}, delay);
|
|
2967
|
+
this.setAuthState({
|
|
2968
|
+
state: "waitingForScheduledRefetch",
|
|
2969
|
+
refetchTokenTimeoutId,
|
|
2970
|
+
config: this.authState.config
|
|
2971
|
+
});
|
|
2972
|
+
this._logVerbose(
|
|
2973
|
+
`scheduled preemptive auth token refetching in ${delay}ms`
|
|
2974
|
+
);
|
|
2975
|
+
}
|
|
2976
|
+
// Protects against simultaneous calls to `setConfig`
|
|
2977
|
+
// while we're fetching a token
|
|
2978
|
+
async fetchTokenAndGuardAgainstRace(fetchToken, fetchArgs) {
|
|
2979
|
+
const originalConfigVersion = ++this.configVersion;
|
|
2980
|
+
this._logVerbose(
|
|
2981
|
+
`fetching token with config version ${originalConfigVersion}`
|
|
2982
|
+
);
|
|
2983
|
+
const token = await fetchToken(fetchArgs);
|
|
2984
|
+
if (this.configVersion !== originalConfigVersion) {
|
|
2985
|
+
this._logVerbose(
|
|
2986
|
+
`stale config version, expected ${originalConfigVersion}, got ${this.configVersion}`
|
|
2987
|
+
);
|
|
2988
|
+
return { isFromOutdatedConfig: true };
|
|
2989
|
+
}
|
|
2990
|
+
return { isFromOutdatedConfig: false, value: token };
|
|
2991
|
+
}
|
|
2992
|
+
stop() {
|
|
2993
|
+
this.resetAuthState();
|
|
2994
|
+
this.configVersion++;
|
|
2995
|
+
this._logVerbose(`config version bumped to ${this.configVersion}`);
|
|
2996
|
+
}
|
|
2997
|
+
setAndReportAuthFailed(onAuthChange) {
|
|
2998
|
+
onAuthChange(false);
|
|
2999
|
+
this.resetAuthState();
|
|
3000
|
+
}
|
|
3001
|
+
resetAuthState() {
|
|
3002
|
+
this.setAuthState({ state: "noAuth" });
|
|
3003
|
+
}
|
|
3004
|
+
setAuthState(newAuth) {
|
|
3005
|
+
const authStateForLog = newAuth.state === "waitingForServerConfirmationOfFreshToken" ? {
|
|
3006
|
+
hadAuth: newAuth.hadAuth,
|
|
3007
|
+
state: newAuth.state,
|
|
3008
|
+
token: `...${newAuth.token.slice(-7)}`
|
|
3009
|
+
} : { state: newAuth.state };
|
|
3010
|
+
this._logVerbose(
|
|
3011
|
+
`setting auth state to ${JSON.stringify(authStateForLog)}`
|
|
3012
|
+
);
|
|
3013
|
+
switch (newAuth.state) {
|
|
3014
|
+
case "waitingForScheduledRefetch":
|
|
3015
|
+
case "notRefetching":
|
|
3016
|
+
case "noAuth":
|
|
3017
|
+
this.tokenConfirmationAttempts = 0;
|
|
3018
|
+
break;
|
|
3019
|
+
}
|
|
3020
|
+
if (this.authState.state === "waitingForScheduledRefetch") {
|
|
3021
|
+
clearTimeout(this.authState.refetchTokenTimeoutId);
|
|
3022
|
+
this.syncState.markAuthCompletion();
|
|
3023
|
+
}
|
|
3024
|
+
this.authState = newAuth;
|
|
3025
|
+
}
|
|
3026
|
+
decodeToken(token) {
|
|
3027
|
+
try {
|
|
3028
|
+
return jwtDecode(token);
|
|
3029
|
+
} catch (e) {
|
|
3030
|
+
this._logVerbose(
|
|
3031
|
+
`Error decoding token: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
3032
|
+
);
|
|
3033
|
+
return null;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
_logVerbose(message) {
|
|
3037
|
+
this.logger.logVerbose(`${message} [v${this.configVersion}]`);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
const markNames = [
|
|
3041
|
+
"convexClientConstructed",
|
|
3042
|
+
"convexWebSocketOpen",
|
|
3043
|
+
"convexFirstMessageReceived"
|
|
3044
|
+
];
|
|
3045
|
+
function mark(name, sessionId) {
|
|
3046
|
+
const detail = { sessionId };
|
|
3047
|
+
if (typeof performance === "undefined" || !performance.mark) return;
|
|
3048
|
+
performance.mark(name, { detail });
|
|
3049
|
+
}
|
|
3050
|
+
function performanceMarkToJson(mark2) {
|
|
3051
|
+
let name = mark2.name.slice("convex".length);
|
|
3052
|
+
name = name.charAt(0).toLowerCase() + name.slice(1);
|
|
3053
|
+
return {
|
|
3054
|
+
name,
|
|
3055
|
+
startTime: mark2.startTime
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
3058
|
+
function getMarksReport(sessionId) {
|
|
3059
|
+
if (typeof performance === "undefined" || !performance.getEntriesByName) {
|
|
3060
|
+
return [];
|
|
3061
|
+
}
|
|
3062
|
+
const allMarks = [];
|
|
3063
|
+
for (const name of markNames) {
|
|
3064
|
+
const marks = performance.getEntriesByName(name).filter((entry) => entry.entryType === "mark").filter((mark2) => mark2.detail.sessionId === sessionId);
|
|
3065
|
+
allMarks.push(...marks);
|
|
3066
|
+
}
|
|
3067
|
+
return allMarks.map(performanceMarkToJson);
|
|
3068
|
+
}
|
|
3069
|
+
var __defProp$3 = Object.defineProperty;
|
|
3070
|
+
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3071
|
+
var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
3072
|
+
class BaseConvexClient {
|
|
3073
|
+
/**
|
|
3074
|
+
* @param address - The url of your Convex deployment, often provided
|
|
3075
|
+
* by an environment variable. E.g. `https://small-mouse-123.convex.cloud`.
|
|
3076
|
+
* @param onTransition - A callback receiving an array of query tokens
|
|
3077
|
+
* corresponding to query results that have changed -- additional handlers
|
|
3078
|
+
* can be added via `addOnTransitionHandler`.
|
|
3079
|
+
* @param options - See {@link BaseConvexClientOptions} for a full description.
|
|
3080
|
+
*/
|
|
3081
|
+
constructor(address, onTransition, options) {
|
|
3082
|
+
__publicField$3(this, "address");
|
|
3083
|
+
__publicField$3(this, "state");
|
|
3084
|
+
__publicField$3(this, "requestManager");
|
|
3085
|
+
__publicField$3(this, "webSocketManager");
|
|
3086
|
+
__publicField$3(this, "authenticationManager");
|
|
3087
|
+
__publicField$3(this, "remoteQuerySet");
|
|
3088
|
+
__publicField$3(this, "optimisticQueryResults");
|
|
3089
|
+
__publicField$3(this, "_transitionHandlerCounter", 0);
|
|
3090
|
+
__publicField$3(this, "_nextRequestId");
|
|
3091
|
+
__publicField$3(this, "_onTransitionFns", /* @__PURE__ */ new Map());
|
|
3092
|
+
__publicField$3(this, "_sessionId");
|
|
3093
|
+
__publicField$3(this, "firstMessageReceived", false);
|
|
3094
|
+
__publicField$3(this, "debug");
|
|
3095
|
+
__publicField$3(this, "logger");
|
|
3096
|
+
__publicField$3(this, "maxObservedTimestamp");
|
|
3097
|
+
__publicField$3(this, "connectionStateSubscribers", /* @__PURE__ */ new Map());
|
|
3098
|
+
__publicField$3(this, "nextConnectionStateSubscriberId", 0);
|
|
3099
|
+
__publicField$3(this, "_lastPublishedConnectionState");
|
|
3100
|
+
__publicField$3(this, "markConnectionStateDirty", () => {
|
|
3101
|
+
void Promise.resolve().then(() => {
|
|
3102
|
+
const curConnectionState = this.connectionState();
|
|
3103
|
+
if (JSON.stringify(curConnectionState) !== JSON.stringify(this._lastPublishedConnectionState)) {
|
|
3104
|
+
this._lastPublishedConnectionState = curConnectionState;
|
|
3105
|
+
for (const cb of this.connectionStateSubscribers.values()) {
|
|
3106
|
+
cb(curConnectionState);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
});
|
|
3110
|
+
});
|
|
3111
|
+
__publicField$3(this, "mark", (name) => {
|
|
3112
|
+
if (this.debug) {
|
|
3113
|
+
mark(name, this.sessionId);
|
|
3114
|
+
}
|
|
3115
|
+
});
|
|
3116
|
+
if (typeof address === "object") {
|
|
3117
|
+
throw new Error(
|
|
3118
|
+
"Passing a ClientConfig object is no longer supported. Pass the URL of the Convex deployment as a string directly."
|
|
3119
|
+
);
|
|
3120
|
+
}
|
|
3121
|
+
if (options?.skipConvexDeploymentUrlCheck !== true) {
|
|
3122
|
+
validateDeploymentUrl(address);
|
|
3123
|
+
}
|
|
3124
|
+
options = { ...options };
|
|
3125
|
+
const authRefreshTokenLeewaySeconds = options.authRefreshTokenLeewaySeconds ?? 2;
|
|
3126
|
+
let webSocketConstructor = options.webSocketConstructor;
|
|
3127
|
+
if (!webSocketConstructor && typeof WebSocket === "undefined") {
|
|
3128
|
+
throw new Error(
|
|
3129
|
+
"No WebSocket global variable defined! To use Convex in an environment without WebSocket try the HTTP client: https://docs.convex.dev/api/classes/browser.ConvexHttpClient"
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
webSocketConstructor = webSocketConstructor || WebSocket;
|
|
3133
|
+
this.debug = options.reportDebugInfoToConvex ?? false;
|
|
3134
|
+
this.address = address;
|
|
3135
|
+
this.logger = options.logger === false ? instantiateNoopLogger({ verbose: options.verbose ?? false }) : options.logger !== true && options.logger ? options.logger : instantiateDefaultLogger({ verbose: options.verbose ?? false });
|
|
3136
|
+
const i = address.search("://");
|
|
3137
|
+
if (i === -1) {
|
|
3138
|
+
throw new Error("Provided address was not an absolute URL.");
|
|
3139
|
+
}
|
|
3140
|
+
const origin = address.substring(i + 3);
|
|
3141
|
+
const protocol = address.substring(0, i);
|
|
3142
|
+
let wsProtocol;
|
|
3143
|
+
if (protocol === "http") {
|
|
3144
|
+
wsProtocol = "ws";
|
|
3145
|
+
} else if (protocol === "https") {
|
|
3146
|
+
wsProtocol = "wss";
|
|
3147
|
+
} else {
|
|
3148
|
+
throw new Error(`Unknown parent protocol ${protocol}`);
|
|
3149
|
+
}
|
|
3150
|
+
const wsUri = `${wsProtocol}://${origin}/api/${version}/sync`;
|
|
3151
|
+
this.state = new LocalSyncState();
|
|
3152
|
+
this.remoteQuerySet = new RemoteQuerySet(
|
|
3153
|
+
(queryId) => this.state.queryPath(queryId),
|
|
3154
|
+
this.logger
|
|
3155
|
+
);
|
|
3156
|
+
this.requestManager = new RequestManager(
|
|
3157
|
+
this.logger,
|
|
3158
|
+
this.markConnectionStateDirty
|
|
3159
|
+
);
|
|
3160
|
+
const pauseSocket = () => {
|
|
3161
|
+
this.webSocketManager.pause();
|
|
3162
|
+
this.state.pause();
|
|
3163
|
+
};
|
|
3164
|
+
this.authenticationManager = new AuthenticationManager(
|
|
3165
|
+
this.state,
|
|
3166
|
+
{
|
|
3167
|
+
authenticate: (token) => {
|
|
3168
|
+
const message = this.state.setAuth(token);
|
|
3169
|
+
this.webSocketManager.sendMessage(message);
|
|
3170
|
+
return message.baseVersion;
|
|
3171
|
+
},
|
|
3172
|
+
stopSocket: () => this.webSocketManager.stop(),
|
|
3173
|
+
tryRestartSocket: () => this.webSocketManager.tryRestart(),
|
|
3174
|
+
pauseSocket,
|
|
3175
|
+
resumeSocket: () => this.webSocketManager.resume(),
|
|
3176
|
+
clearAuth: () => {
|
|
3177
|
+
this.clearAuth();
|
|
3178
|
+
}
|
|
3179
|
+
},
|
|
3180
|
+
{
|
|
3181
|
+
logger: this.logger,
|
|
3182
|
+
refreshTokenLeewaySeconds: authRefreshTokenLeewaySeconds
|
|
3183
|
+
}
|
|
3184
|
+
);
|
|
3185
|
+
this.optimisticQueryResults = new OptimisticQueryResults();
|
|
3186
|
+
this.addOnTransitionHandler((transition) => {
|
|
3187
|
+
onTransition(transition.queries.map((q) => q.token));
|
|
3188
|
+
});
|
|
3189
|
+
this._nextRequestId = 0;
|
|
3190
|
+
this._sessionId = newSessionId();
|
|
3191
|
+
const { unsavedChangesWarning } = options;
|
|
3192
|
+
if (typeof window === "undefined" || typeof window.addEventListener === "undefined") {
|
|
3193
|
+
if (unsavedChangesWarning === true) {
|
|
3194
|
+
throw new Error(
|
|
3195
|
+
"unsavedChangesWarning requested, but window.addEventListener not found! Remove {unsavedChangesWarning: true} from Convex client options."
|
|
3196
|
+
);
|
|
3197
|
+
}
|
|
3198
|
+
} else if (unsavedChangesWarning !== false) {
|
|
3199
|
+
window.addEventListener("beforeunload", (e) => {
|
|
3200
|
+
if (this.requestManager.hasIncompleteRequests()) {
|
|
3201
|
+
e.preventDefault();
|
|
3202
|
+
const confirmationMessage = "Are you sure you want to leave? Your changes may not be saved.";
|
|
3203
|
+
(e || window.event).returnValue = confirmationMessage;
|
|
3204
|
+
return confirmationMessage;
|
|
3205
|
+
}
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3208
|
+
this.webSocketManager = new WebSocketManager(
|
|
3209
|
+
wsUri,
|
|
3210
|
+
{
|
|
3211
|
+
onOpen: (reconnectMetadata) => {
|
|
3212
|
+
this.mark("convexWebSocketOpen");
|
|
3213
|
+
this.webSocketManager.sendMessage({
|
|
3214
|
+
...reconnectMetadata,
|
|
3215
|
+
type: "Connect",
|
|
3216
|
+
sessionId: this._sessionId,
|
|
3217
|
+
maxObservedTimestamp: this.maxObservedTimestamp
|
|
3218
|
+
});
|
|
3219
|
+
const oldRemoteQueryResults = new Set(
|
|
3220
|
+
this.remoteQuerySet.remoteQueryResults().keys()
|
|
3221
|
+
);
|
|
3222
|
+
this.remoteQuerySet = new RemoteQuerySet(
|
|
3223
|
+
(queryId) => this.state.queryPath(queryId),
|
|
3224
|
+
this.logger
|
|
3225
|
+
);
|
|
3226
|
+
const [querySetModification, authModification] = this.state.restart(
|
|
3227
|
+
oldRemoteQueryResults
|
|
3228
|
+
);
|
|
3229
|
+
if (authModification) {
|
|
3230
|
+
this.webSocketManager.sendMessage(authModification);
|
|
3231
|
+
}
|
|
3232
|
+
this.webSocketManager.sendMessage(querySetModification);
|
|
3233
|
+
for (const message of this.requestManager.restart()) {
|
|
3234
|
+
this.webSocketManager.sendMessage(message);
|
|
3235
|
+
}
|
|
3236
|
+
},
|
|
3237
|
+
onResume: () => {
|
|
3238
|
+
const [querySetModification, authModification] = this.state.resume();
|
|
3239
|
+
if (authModification) {
|
|
3240
|
+
this.webSocketManager.sendMessage(authModification);
|
|
3241
|
+
}
|
|
3242
|
+
if (querySetModification) {
|
|
3243
|
+
this.webSocketManager.sendMessage(querySetModification);
|
|
3244
|
+
}
|
|
3245
|
+
for (const message of this.requestManager.resume()) {
|
|
3246
|
+
this.webSocketManager.sendMessage(message);
|
|
3247
|
+
}
|
|
3248
|
+
},
|
|
3249
|
+
onMessage: (serverMessage) => {
|
|
3250
|
+
if (!this.firstMessageReceived) {
|
|
3251
|
+
this.firstMessageReceived = true;
|
|
3252
|
+
this.mark("convexFirstMessageReceived");
|
|
3253
|
+
this.reportMarks();
|
|
3254
|
+
}
|
|
3255
|
+
switch (serverMessage.type) {
|
|
3256
|
+
case "Transition": {
|
|
3257
|
+
this.observedTimestamp(serverMessage.endVersion.ts);
|
|
3258
|
+
this.authenticationManager.onTransition(serverMessage);
|
|
3259
|
+
this.remoteQuerySet.transition(serverMessage);
|
|
3260
|
+
this.state.transition(serverMessage);
|
|
3261
|
+
const completedRequests = this.requestManager.removeCompleted(
|
|
3262
|
+
this.remoteQuerySet.timestamp()
|
|
3263
|
+
);
|
|
3264
|
+
this.notifyOnQueryResultChanges(completedRequests);
|
|
3265
|
+
break;
|
|
3266
|
+
}
|
|
3267
|
+
case "MutationResponse": {
|
|
3268
|
+
if (serverMessage.success) {
|
|
3269
|
+
this.observedTimestamp(serverMessage.ts);
|
|
3270
|
+
}
|
|
3271
|
+
const completedMutationInfo = this.requestManager.onResponse(serverMessage);
|
|
3272
|
+
if (completedMutationInfo !== null) {
|
|
3273
|
+
this.notifyOnQueryResultChanges(
|
|
3274
|
+
/* @__PURE__ */ new Map([
|
|
3275
|
+
[
|
|
3276
|
+
completedMutationInfo.requestId,
|
|
3277
|
+
completedMutationInfo.result
|
|
3278
|
+
]
|
|
3279
|
+
])
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
break;
|
|
3283
|
+
}
|
|
3284
|
+
case "ActionResponse": {
|
|
3285
|
+
this.requestManager.onResponse(serverMessage);
|
|
3286
|
+
break;
|
|
3287
|
+
}
|
|
3288
|
+
case "AuthError": {
|
|
3289
|
+
this.authenticationManager.onAuthError(serverMessage);
|
|
3290
|
+
break;
|
|
3291
|
+
}
|
|
3292
|
+
case "FatalError": {
|
|
3293
|
+
const error = logFatalError(this.logger, serverMessage.error);
|
|
3294
|
+
void this.webSocketManager.terminate();
|
|
3295
|
+
throw error;
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
return {
|
|
3299
|
+
hasSyncedPastLastReconnect: this.hasSyncedPastLastReconnect()
|
|
3300
|
+
};
|
|
3301
|
+
},
|
|
3302
|
+
onServerDisconnectError: options.onServerDisconnectError
|
|
3303
|
+
},
|
|
3304
|
+
webSocketConstructor,
|
|
3305
|
+
this.logger,
|
|
3306
|
+
this.markConnectionStateDirty,
|
|
3307
|
+
this.debug
|
|
3308
|
+
);
|
|
3309
|
+
this.mark("convexClientConstructed");
|
|
3310
|
+
if (options.expectAuth) {
|
|
3311
|
+
pauseSocket();
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
/**
|
|
3315
|
+
* Return true if there is outstanding work from prior to the time of the most recent restart.
|
|
3316
|
+
* This indicates that the client has not proven itself to have gotten past the issue that
|
|
3317
|
+
* potentially led to the restart. Use this to influence when to reset backoff after a failure.
|
|
3318
|
+
*/
|
|
3319
|
+
hasSyncedPastLastReconnect() {
|
|
3320
|
+
const hasSyncedPastLastReconnect = this.requestManager.hasSyncedPastLastReconnect() || this.state.hasSyncedPastLastReconnect();
|
|
3321
|
+
return hasSyncedPastLastReconnect;
|
|
3322
|
+
}
|
|
3323
|
+
observedTimestamp(observedTs) {
|
|
3324
|
+
if (this.maxObservedTimestamp === void 0 || this.maxObservedTimestamp.lessThanOrEqual(observedTs)) {
|
|
3325
|
+
this.maxObservedTimestamp = observedTs;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
getMaxObservedTimestamp() {
|
|
3329
|
+
return this.maxObservedTimestamp;
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Compute the current query results based on the remoteQuerySet and the
|
|
3333
|
+
* current optimistic updates and call `onTransition` for all the changed
|
|
3334
|
+
* queries.
|
|
3335
|
+
*
|
|
3336
|
+
* @param completedMutations - A set of mutation IDs whose optimistic updates
|
|
3337
|
+
* are no longer needed.
|
|
3338
|
+
*/
|
|
3339
|
+
notifyOnQueryResultChanges(completedRequests) {
|
|
3340
|
+
const remoteQueryResults = this.remoteQuerySet.remoteQueryResults();
|
|
3341
|
+
const queryTokenToValue = /* @__PURE__ */ new Map();
|
|
3342
|
+
for (const [queryId, result] of remoteQueryResults) {
|
|
3343
|
+
const queryToken = this.state.queryToken(queryId);
|
|
3344
|
+
if (queryToken !== null) {
|
|
3345
|
+
const query = {
|
|
3346
|
+
result,
|
|
3347
|
+
udfPath: this.state.queryPath(queryId),
|
|
3348
|
+
args: this.state.queryArgs(queryId)
|
|
3349
|
+
};
|
|
3350
|
+
queryTokenToValue.set(queryToken, query);
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
const changedQueryTokens = this.optimisticQueryResults.ingestQueryResultsFromServer(
|
|
3354
|
+
queryTokenToValue,
|
|
3355
|
+
new Set(completedRequests.keys())
|
|
3356
|
+
);
|
|
3357
|
+
this.handleTransition({
|
|
3358
|
+
queries: changedQueryTokens.map((token) => {
|
|
3359
|
+
const optimisticResult = this.optimisticQueryResults.rawQueryResult(token);
|
|
3360
|
+
return {
|
|
3361
|
+
token,
|
|
3362
|
+
modification: {
|
|
3363
|
+
kind: "Updated",
|
|
3364
|
+
result: optimisticResult
|
|
3365
|
+
}
|
|
3366
|
+
};
|
|
3367
|
+
}),
|
|
3368
|
+
reflectedMutations: Array.from(completedRequests).map(
|
|
3369
|
+
([requestId, result]) => ({
|
|
3370
|
+
requestId,
|
|
3371
|
+
result
|
|
3372
|
+
})
|
|
3373
|
+
),
|
|
3374
|
+
timestamp: this.remoteQuerySet.timestamp()
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
handleTransition(transition) {
|
|
3378
|
+
for (const fn of this._onTransitionFns.values()) {
|
|
3379
|
+
fn(transition);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Add a handler that will be called on a transition.
|
|
3384
|
+
*
|
|
3385
|
+
* Any external side effects (e.g. setting React state) should be handled here.
|
|
3386
|
+
*
|
|
3387
|
+
* @param fn
|
|
3388
|
+
*
|
|
3389
|
+
* @returns
|
|
3390
|
+
*/
|
|
3391
|
+
addOnTransitionHandler(fn) {
|
|
3392
|
+
const id = this._transitionHandlerCounter++;
|
|
3393
|
+
this._onTransitionFns.set(id, fn);
|
|
3394
|
+
return () => this._onTransitionFns.delete(id);
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Get the current JWT auth token and decoded claims.
|
|
3398
|
+
*/
|
|
3399
|
+
getCurrentAuthClaims() {
|
|
3400
|
+
const authToken = this.state.getAuth();
|
|
3401
|
+
let decoded = {};
|
|
3402
|
+
if (authToken && authToken.tokenType === "User") {
|
|
3403
|
+
try {
|
|
3404
|
+
decoded = authToken ? jwtDecode(authToken.value) : {};
|
|
3405
|
+
} catch {
|
|
3406
|
+
decoded = {};
|
|
3407
|
+
}
|
|
3408
|
+
} else {
|
|
3409
|
+
return void 0;
|
|
3410
|
+
}
|
|
3411
|
+
return { token: authToken.value, decoded };
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Set the authentication token to be used for subsequent queries and mutations.
|
|
3415
|
+
* `fetchToken` will be called automatically again if a token expires.
|
|
3416
|
+
* `fetchToken` should return `null` if the token cannot be retrieved, for example
|
|
3417
|
+
* when the user's rights were permanently revoked.
|
|
3418
|
+
* @param fetchToken - an async function returning the JWT-encoded OpenID Connect Identity Token
|
|
3419
|
+
* @param onChange - a callback that will be called when the authentication status changes
|
|
3420
|
+
*/
|
|
3421
|
+
setAuth(fetchToken, onChange) {
|
|
3422
|
+
void this.authenticationManager.setConfig(fetchToken, onChange);
|
|
3423
|
+
}
|
|
3424
|
+
hasAuth() {
|
|
3425
|
+
return this.state.hasAuth();
|
|
3426
|
+
}
|
|
3427
|
+
/** @internal */
|
|
3428
|
+
setAdminAuth(value, fakeUserIdentity) {
|
|
3429
|
+
const message = this.state.setAdminAuth(value, fakeUserIdentity);
|
|
3430
|
+
this.webSocketManager.sendMessage(message);
|
|
3431
|
+
}
|
|
3432
|
+
clearAuth() {
|
|
3433
|
+
const message = this.state.clearAuth();
|
|
3434
|
+
this.webSocketManager.sendMessage(message);
|
|
3435
|
+
}
|
|
3436
|
+
/**
|
|
3437
|
+
* Subscribe to a query function.
|
|
3438
|
+
*
|
|
3439
|
+
* Whenever this query's result changes, the `onTransition` callback
|
|
3440
|
+
* passed into the constructor will be called.
|
|
3441
|
+
*
|
|
3442
|
+
* @param name - The name of the query.
|
|
3443
|
+
* @param args - An arguments object for the query. If this is omitted, the
|
|
3444
|
+
* arguments will be `{}`.
|
|
3445
|
+
* @param options - A {@link SubscribeOptions} options object for this query.
|
|
3446
|
+
|
|
3447
|
+
* @returns An object containing a {@link QueryToken} corresponding to this
|
|
3448
|
+
* query and an `unsubscribe` callback.
|
|
3449
|
+
*/
|
|
3450
|
+
subscribe(name, args, options) {
|
|
3451
|
+
const argsObject = parseArgs(args);
|
|
3452
|
+
const { modification, queryToken, unsubscribe } = this.state.subscribe(
|
|
3453
|
+
name,
|
|
3454
|
+
argsObject,
|
|
3455
|
+
options?.journal,
|
|
3456
|
+
options?.componentPath
|
|
3457
|
+
);
|
|
3458
|
+
if (modification !== null) {
|
|
3459
|
+
this.webSocketManager.sendMessage(modification);
|
|
3460
|
+
}
|
|
3461
|
+
return {
|
|
3462
|
+
queryToken,
|
|
3463
|
+
unsubscribe: () => {
|
|
3464
|
+
const modification2 = unsubscribe();
|
|
3465
|
+
if (modification2) {
|
|
3466
|
+
this.webSocketManager.sendMessage(modification2);
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
/**
|
|
3472
|
+
* A query result based only on the current, local state.
|
|
3473
|
+
*
|
|
3474
|
+
* The only way this will return a value is if we're already subscribed to the
|
|
3475
|
+
* query or its value has been set optimistically.
|
|
3476
|
+
*/
|
|
3477
|
+
localQueryResult(udfPath, args) {
|
|
3478
|
+
const argsObject = parseArgs(args);
|
|
3479
|
+
const queryToken = serializePathAndArgs(udfPath, argsObject);
|
|
3480
|
+
return this.optimisticQueryResults.queryResult(queryToken);
|
|
3481
|
+
}
|
|
3482
|
+
/**
|
|
3483
|
+
* Get query result by query token based on current, local state
|
|
3484
|
+
*
|
|
3485
|
+
* The only way this will return a value is if we're already subscribed to the
|
|
3486
|
+
* query or its value has been set optimistically.
|
|
3487
|
+
*
|
|
3488
|
+
* @internal
|
|
3489
|
+
*/
|
|
3490
|
+
localQueryResultByToken(queryToken) {
|
|
3491
|
+
return this.optimisticQueryResults.queryResult(queryToken);
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* Whether local query result is available for a token.
|
|
3495
|
+
*
|
|
3496
|
+
* This method does not throw if the result is an error.
|
|
3497
|
+
*
|
|
3498
|
+
* @internal
|
|
3499
|
+
*/
|
|
3500
|
+
hasLocalQueryResultByToken(queryToken) {
|
|
3501
|
+
return this.optimisticQueryResults.hasQueryResult(queryToken);
|
|
3502
|
+
}
|
|
3503
|
+
/**
|
|
3504
|
+
* @internal
|
|
3505
|
+
*/
|
|
3506
|
+
localQueryLogs(udfPath, args) {
|
|
3507
|
+
const argsObject = parseArgs(args);
|
|
3508
|
+
const queryToken = serializePathAndArgs(udfPath, argsObject);
|
|
3509
|
+
return this.optimisticQueryResults.queryLogs(queryToken);
|
|
3510
|
+
}
|
|
3511
|
+
/**
|
|
3512
|
+
* Retrieve the current {@link QueryJournal} for this query function.
|
|
3513
|
+
*
|
|
3514
|
+
* If we have not yet received a result for this query, this will be `undefined`.
|
|
3515
|
+
*
|
|
3516
|
+
* @param name - The name of the query.
|
|
3517
|
+
* @param args - The arguments object for this query.
|
|
3518
|
+
* @returns The query's {@link QueryJournal} or `undefined`.
|
|
3519
|
+
*/
|
|
3520
|
+
queryJournal(name, args) {
|
|
3521
|
+
const argsObject = parseArgs(args);
|
|
3522
|
+
const queryToken = serializePathAndArgs(name, argsObject);
|
|
3523
|
+
return this.state.queryJournal(queryToken);
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3526
|
+
* Get the current {@link ConnectionState} between the client and the Convex
|
|
3527
|
+
* backend.
|
|
3528
|
+
*
|
|
3529
|
+
* @returns The {@link ConnectionState} with the Convex backend.
|
|
3530
|
+
*/
|
|
3531
|
+
connectionState() {
|
|
3532
|
+
const wsConnectionState = this.webSocketManager.connectionState();
|
|
3533
|
+
return {
|
|
3534
|
+
hasInflightRequests: this.requestManager.hasInflightRequests(),
|
|
3535
|
+
isWebSocketConnected: wsConnectionState.isConnected,
|
|
3536
|
+
hasEverConnected: wsConnectionState.hasEverConnected,
|
|
3537
|
+
connectionCount: wsConnectionState.connectionCount,
|
|
3538
|
+
connectionRetries: wsConnectionState.connectionRetries,
|
|
3539
|
+
timeOfOldestInflightRequest: this.requestManager.timeOfOldestInflightRequest(),
|
|
3540
|
+
inflightMutations: this.requestManager.inflightMutations(),
|
|
3541
|
+
inflightActions: this.requestManager.inflightActions()
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
/**
|
|
3545
|
+
* Subscribe to the {@link ConnectionState} between the client and the Convex
|
|
3546
|
+
* backend, calling a callback each time it changes.
|
|
3547
|
+
*
|
|
3548
|
+
* Subscribed callbacks will be called when any part of ConnectionState changes.
|
|
3549
|
+
* ConnectionState may grow in future versions (e.g. to provide a array of
|
|
3550
|
+
* inflight requests) in which case callbacks would be called more frequently.
|
|
3551
|
+
*
|
|
3552
|
+
* @returns An unsubscribe function to stop listening.
|
|
3553
|
+
*/
|
|
3554
|
+
subscribeToConnectionState(cb) {
|
|
3555
|
+
const id = this.nextConnectionStateSubscriberId++;
|
|
3556
|
+
this.connectionStateSubscribers.set(id, cb);
|
|
3557
|
+
return () => {
|
|
3558
|
+
this.connectionStateSubscribers.delete(id);
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* Execute a mutation function.
|
|
3563
|
+
*
|
|
3564
|
+
* @param name - The name of the mutation.
|
|
3565
|
+
* @param args - An arguments object for the mutation. If this is omitted,
|
|
3566
|
+
* the arguments will be `{}`.
|
|
3567
|
+
* @param options - A {@link MutationOptions} options object for this mutation.
|
|
3568
|
+
|
|
3569
|
+
* @returns - A promise of the mutation's result.
|
|
3570
|
+
*/
|
|
3571
|
+
async mutation(name, args, options) {
|
|
3572
|
+
const result = await this.mutationInternal(name, args, options);
|
|
3573
|
+
if (!result.success) {
|
|
3574
|
+
if (result.errorData !== void 0) {
|
|
3575
|
+
throw forwardData(
|
|
3576
|
+
result,
|
|
3577
|
+
new ConvexError(
|
|
3578
|
+
createHybridErrorStacktrace("mutation", name, result)
|
|
3579
|
+
)
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
throw new Error(createHybridErrorStacktrace("mutation", name, result));
|
|
3583
|
+
}
|
|
3584
|
+
return result.value;
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* @internal
|
|
3588
|
+
*/
|
|
3589
|
+
async mutationInternal(udfPath, args, options, componentPath) {
|
|
3590
|
+
const { mutationPromise } = this.enqueueMutation(
|
|
3591
|
+
udfPath,
|
|
3592
|
+
args,
|
|
3593
|
+
options,
|
|
3594
|
+
componentPath
|
|
3595
|
+
);
|
|
3596
|
+
return mutationPromise;
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* @internal
|
|
3600
|
+
*/
|
|
3601
|
+
enqueueMutation(udfPath, args, options, componentPath) {
|
|
3602
|
+
const mutationArgs = parseArgs(args);
|
|
3603
|
+
this.tryReportLongDisconnect();
|
|
3604
|
+
const requestId = this.nextRequestId;
|
|
3605
|
+
this._nextRequestId++;
|
|
3606
|
+
if (options !== void 0) {
|
|
3607
|
+
const optimisticUpdate = options.optimisticUpdate;
|
|
3608
|
+
if (optimisticUpdate !== void 0) {
|
|
3609
|
+
const wrappedUpdate = (localQueryStore) => {
|
|
3610
|
+
const result = optimisticUpdate(
|
|
3611
|
+
localQueryStore,
|
|
3612
|
+
mutationArgs
|
|
3613
|
+
);
|
|
3614
|
+
if (result instanceof Promise) {
|
|
3615
|
+
this.logger.warn(
|
|
3616
|
+
"Optimistic update handler returned a Promise. Optimistic updates should be synchronous."
|
|
3617
|
+
);
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
const changedQueryTokens = this.optimisticQueryResults.applyOptimisticUpdate(
|
|
3621
|
+
wrappedUpdate,
|
|
3622
|
+
requestId
|
|
3623
|
+
);
|
|
3624
|
+
const changedQueries = changedQueryTokens.map((token) => {
|
|
3625
|
+
const localResult = this.localQueryResultByToken(token);
|
|
3626
|
+
return {
|
|
3627
|
+
token,
|
|
3628
|
+
modification: {
|
|
3629
|
+
kind: "Updated",
|
|
3630
|
+
result: localResult === void 0 ? void 0 : {
|
|
3631
|
+
success: true,
|
|
3632
|
+
value: localResult,
|
|
3633
|
+
logLines: []
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
});
|
|
3638
|
+
this.handleTransition({
|
|
3639
|
+
queries: changedQueries,
|
|
3640
|
+
reflectedMutations: [],
|
|
3641
|
+
timestamp: this.remoteQuerySet.timestamp()
|
|
3642
|
+
});
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
const message = {
|
|
3646
|
+
type: "Mutation",
|
|
3647
|
+
requestId,
|
|
3648
|
+
udfPath,
|
|
3649
|
+
componentPath,
|
|
3650
|
+
args: [convexToJson(mutationArgs)]
|
|
3651
|
+
};
|
|
3652
|
+
const mightBeSent = this.webSocketManager.sendMessage(message);
|
|
3653
|
+
const mutationPromise = this.requestManager.request(message, mightBeSent);
|
|
3654
|
+
return {
|
|
3655
|
+
requestId,
|
|
3656
|
+
mutationPromise
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
/**
|
|
3660
|
+
* Execute an action function.
|
|
3661
|
+
*
|
|
3662
|
+
* @param name - The name of the action.
|
|
3663
|
+
* @param args - An arguments object for the action. If this is omitted,
|
|
3664
|
+
* the arguments will be `{}`.
|
|
3665
|
+
* @returns A promise of the action's result.
|
|
3666
|
+
*/
|
|
3667
|
+
async action(name, args) {
|
|
3668
|
+
const result = await this.actionInternal(name, args);
|
|
3669
|
+
if (!result.success) {
|
|
3670
|
+
if (result.errorData !== void 0) {
|
|
3671
|
+
throw forwardData(
|
|
3672
|
+
result,
|
|
3673
|
+
new ConvexError(createHybridErrorStacktrace("action", name, result))
|
|
3674
|
+
);
|
|
3675
|
+
}
|
|
3676
|
+
throw new Error(createHybridErrorStacktrace("action", name, result));
|
|
3677
|
+
}
|
|
3678
|
+
return result.value;
|
|
3679
|
+
}
|
|
3680
|
+
/**
|
|
3681
|
+
* @internal
|
|
3682
|
+
*/
|
|
3683
|
+
async actionInternal(udfPath, args, componentPath) {
|
|
3684
|
+
const actionArgs = parseArgs(args);
|
|
3685
|
+
const requestId = this.nextRequestId;
|
|
3686
|
+
this._nextRequestId++;
|
|
3687
|
+
this.tryReportLongDisconnect();
|
|
3688
|
+
const message = {
|
|
3689
|
+
type: "Action",
|
|
3690
|
+
requestId,
|
|
3691
|
+
udfPath,
|
|
3692
|
+
componentPath,
|
|
3693
|
+
args: [convexToJson(actionArgs)]
|
|
3694
|
+
};
|
|
3695
|
+
const mightBeSent = this.webSocketManager.sendMessage(message);
|
|
3696
|
+
return this.requestManager.request(message, mightBeSent);
|
|
3697
|
+
}
|
|
3698
|
+
/**
|
|
3699
|
+
* Close any network handles associated with this client and stop all subscriptions.
|
|
3700
|
+
*
|
|
3701
|
+
* Call this method when you're done with an {@link BaseConvexClient} to
|
|
3702
|
+
* dispose of its sockets and resources.
|
|
3703
|
+
*
|
|
3704
|
+
* @returns A `Promise` fulfilled when the connection has been completely closed.
|
|
3705
|
+
*/
|
|
3706
|
+
async close() {
|
|
3707
|
+
this.authenticationManager.stop();
|
|
3708
|
+
return this.webSocketManager.terminate();
|
|
3709
|
+
}
|
|
3710
|
+
/**
|
|
3711
|
+
* Return the address for this client, useful for creating a new client.
|
|
3712
|
+
*
|
|
3713
|
+
* Not guaranteed to match the address with which this client was constructed:
|
|
3714
|
+
* it may be canonicalized.
|
|
3715
|
+
*/
|
|
3716
|
+
get url() {
|
|
3717
|
+
return this.address;
|
|
3718
|
+
}
|
|
3719
|
+
/**
|
|
3720
|
+
* @internal
|
|
3721
|
+
*/
|
|
3722
|
+
get nextRequestId() {
|
|
3723
|
+
return this._nextRequestId;
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* @internal
|
|
3727
|
+
*/
|
|
3728
|
+
get sessionId() {
|
|
3729
|
+
return this._sessionId;
|
|
3730
|
+
}
|
|
3731
|
+
/**
|
|
3732
|
+
* Reports performance marks to the server. This should only be called when
|
|
3733
|
+
* we have a functional websocket.
|
|
3734
|
+
*/
|
|
3735
|
+
reportMarks() {
|
|
3736
|
+
if (this.debug) {
|
|
3737
|
+
const report = getMarksReport(this.sessionId);
|
|
3738
|
+
this.webSocketManager.sendMessage({
|
|
3739
|
+
type: "Event",
|
|
3740
|
+
eventType: "ClientConnect",
|
|
3741
|
+
event: report
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
tryReportLongDisconnect() {
|
|
3746
|
+
if (!this.debug) {
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
const timeOfOldestRequest = this.connectionState().timeOfOldestInflightRequest;
|
|
3750
|
+
if (timeOfOldestRequest === null || Date.now() - timeOfOldestRequest.getTime() <= 60 * 1e3) {
|
|
3751
|
+
return;
|
|
3752
|
+
}
|
|
3753
|
+
const endpoint = `${this.address}/api/debug_event`;
|
|
3754
|
+
fetch(endpoint, {
|
|
3755
|
+
method: "POST",
|
|
3756
|
+
headers: {
|
|
3757
|
+
"Content-Type": "application/json",
|
|
3758
|
+
"Convex-Client": `npm-${version}`
|
|
3759
|
+
},
|
|
3760
|
+
body: JSON.stringify({ event: "LongWebsocketDisconnect" })
|
|
3761
|
+
}).then((response) => {
|
|
3762
|
+
if (!response.ok) {
|
|
3763
|
+
this.logger.warn(
|
|
3764
|
+
"Analytics request failed with response:",
|
|
3765
|
+
response.body
|
|
3766
|
+
);
|
|
3767
|
+
}
|
|
3768
|
+
}).catch((error) => {
|
|
3769
|
+
this.logger.warn("Analytics response failed with error:", error);
|
|
3770
|
+
});
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
function asPaginationResult(value) {
|
|
3774
|
+
if (typeof value !== "object" || value === null || !Array.isArray(value.page) || typeof value.isDone !== "boolean" || typeof value.continueCursor !== "string") {
|
|
3775
|
+
throw new Error(`Not a valid paginated query result: ${value?.toString()}`);
|
|
3776
|
+
}
|
|
3777
|
+
return value;
|
|
3778
|
+
}
|
|
3779
|
+
var __defProp$2 = Object.defineProperty;
|
|
3780
|
+
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3781
|
+
var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
3782
|
+
class PaginatedQueryClient {
|
|
3783
|
+
constructor(client, onTransition) {
|
|
3784
|
+
this.client = client;
|
|
3785
|
+
this.onTransition = onTransition;
|
|
3786
|
+
__publicField$2(this, "paginatedQuerySet", /* @__PURE__ */ new Map());
|
|
3787
|
+
__publicField$2(this, "lastTransitionTs");
|
|
3788
|
+
this.lastTransitionTs = Long.fromNumber(0);
|
|
3789
|
+
this.client.addOnTransitionHandler(
|
|
3790
|
+
(transition) => this.onBaseTransition(transition)
|
|
3791
|
+
);
|
|
3792
|
+
}
|
|
3793
|
+
/**
|
|
3794
|
+
* Subscribe to a paginated query.
|
|
3795
|
+
*
|
|
3796
|
+
* @param name - The name of the paginated query function
|
|
3797
|
+
* @param args - Arguments for the query (excluding paginationOpts)
|
|
3798
|
+
* @param options - Pagination options including initialNumItems
|
|
3799
|
+
* @returns Object with paginatedQueryToken and unsubscribe function
|
|
3800
|
+
*/
|
|
3801
|
+
subscribe(name, args, options) {
|
|
3802
|
+
const canonicalizedUdfPath = canonicalizeUdfPath(name);
|
|
3803
|
+
const token = serializePaginatedPathAndArgs(
|
|
3804
|
+
canonicalizedUdfPath,
|
|
3805
|
+
args,
|
|
3806
|
+
options
|
|
3807
|
+
);
|
|
3808
|
+
const unsubscribe = () => this.removePaginatedQuerySubscriber(token);
|
|
3809
|
+
const existingEntry = this.paginatedQuerySet.get(token);
|
|
3810
|
+
if (existingEntry) {
|
|
3811
|
+
existingEntry.numSubscribers += 1;
|
|
3812
|
+
return {
|
|
3813
|
+
paginatedQueryToken: token,
|
|
3814
|
+
unsubscribe
|
|
3815
|
+
};
|
|
3816
|
+
}
|
|
3817
|
+
this.paginatedQuerySet.set(token, {
|
|
3818
|
+
token,
|
|
3819
|
+
canonicalizedUdfPath,
|
|
3820
|
+
args,
|
|
3821
|
+
numSubscribers: 1,
|
|
3822
|
+
options: { initialNumItems: options.initialNumItems },
|
|
3823
|
+
nextPageKey: 0,
|
|
3824
|
+
pageKeys: [],
|
|
3825
|
+
pageKeyToQuery: /* @__PURE__ */ new Map(),
|
|
3826
|
+
ongoingSplits: /* @__PURE__ */ new Map(),
|
|
3827
|
+
skip: false,
|
|
3828
|
+
id: options.id
|
|
3829
|
+
});
|
|
3830
|
+
this.addPageToPaginatedQuery(token, null, options.initialNumItems);
|
|
3831
|
+
return {
|
|
3832
|
+
paginatedQueryToken: token,
|
|
3833
|
+
unsubscribe
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
/**
|
|
3837
|
+
* Get current results for a paginated query based on local state.
|
|
3838
|
+
*
|
|
3839
|
+
* Throws an error when one of the pages has errored.
|
|
3840
|
+
*/
|
|
3841
|
+
localQueryResult(name, args, options) {
|
|
3842
|
+
const canonicalizedUdfPath = canonicalizeUdfPath(name);
|
|
3843
|
+
const token = serializePaginatedPathAndArgs(
|
|
3844
|
+
canonicalizedUdfPath,
|
|
3845
|
+
args,
|
|
3846
|
+
options
|
|
3847
|
+
);
|
|
3848
|
+
return this.localQueryResultByToken(token);
|
|
3849
|
+
}
|
|
3850
|
+
/**
|
|
3851
|
+
* @internal
|
|
3852
|
+
*/
|
|
3853
|
+
localQueryResultByToken(token) {
|
|
3854
|
+
const paginatedQuery = this.paginatedQuerySet.get(token);
|
|
3855
|
+
if (!paginatedQuery) {
|
|
3856
|
+
return void 0;
|
|
3857
|
+
}
|
|
3858
|
+
const activePages = this.activePageQueryTokens(paginatedQuery);
|
|
3859
|
+
if (activePages.length === 0) {
|
|
3860
|
+
return {
|
|
3861
|
+
results: [],
|
|
3862
|
+
status: "LoadingFirstPage",
|
|
3863
|
+
loadMore: (numItems) => {
|
|
3864
|
+
return this.loadMoreOfPaginatedQuery(token, numItems);
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3868
|
+
let allResults = [];
|
|
3869
|
+
let hasUndefined = false;
|
|
3870
|
+
let isDone = false;
|
|
3871
|
+
for (const pageToken of activePages) {
|
|
3872
|
+
const result = this.client.localQueryResultByToken(pageToken);
|
|
3873
|
+
if (result === void 0) {
|
|
3874
|
+
hasUndefined = true;
|
|
3875
|
+
isDone = false;
|
|
3876
|
+
continue;
|
|
3877
|
+
}
|
|
3878
|
+
const paginationResult = asPaginationResult(result);
|
|
3879
|
+
allResults = allResults.concat(paginationResult.page);
|
|
3880
|
+
isDone = !!paginationResult.isDone;
|
|
3881
|
+
}
|
|
3882
|
+
let status;
|
|
3883
|
+
if (hasUndefined) {
|
|
3884
|
+
status = allResults.length === 0 ? "LoadingFirstPage" : "LoadingMore";
|
|
3885
|
+
} else if (isDone) {
|
|
3886
|
+
status = "Exhausted";
|
|
3887
|
+
} else {
|
|
3888
|
+
status = "CanLoadMore";
|
|
3889
|
+
}
|
|
3890
|
+
return {
|
|
3891
|
+
results: allResults,
|
|
3892
|
+
status,
|
|
3893
|
+
loadMore: (numItems) => {
|
|
3894
|
+
return this.loadMoreOfPaginatedQuery(token, numItems);
|
|
3895
|
+
}
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
onBaseTransition(transition) {
|
|
3899
|
+
const changedBaseTokens = transition.queries.map((q) => q.token);
|
|
3900
|
+
const changed = this.queriesContainingTokens(changedBaseTokens);
|
|
3901
|
+
let paginatedQueries = [];
|
|
3902
|
+
if (changed.length > 0) {
|
|
3903
|
+
this.processPaginatedQuerySplits(
|
|
3904
|
+
changed,
|
|
3905
|
+
(token) => this.client.localQueryResultByToken(token)
|
|
3906
|
+
);
|
|
3907
|
+
paginatedQueries = changed.map((token) => ({
|
|
3908
|
+
token,
|
|
3909
|
+
modification: {
|
|
3910
|
+
kind: "Updated",
|
|
3911
|
+
result: this.localQueryResultByToken(token)
|
|
3912
|
+
}
|
|
3913
|
+
}));
|
|
3914
|
+
}
|
|
3915
|
+
const extendedTransition = {
|
|
3916
|
+
...transition,
|
|
3917
|
+
paginatedQueries
|
|
3918
|
+
};
|
|
3919
|
+
this.onTransition(extendedTransition);
|
|
3920
|
+
}
|
|
3921
|
+
/**
|
|
3922
|
+
* Load more items for a paginated query.
|
|
3923
|
+
*
|
|
3924
|
+
* This *always* causes a transition, the status of the query
|
|
3925
|
+
* has probably changed from "CanLoadMore" to "LoadingMore".
|
|
3926
|
+
* Data might have changed too: maybe a subscription to this page
|
|
3927
|
+
* query already exists (unlikely but possible) or this page query
|
|
3928
|
+
* has an optimistic update providing some initial data.
|
|
3929
|
+
*
|
|
3930
|
+
* @internal
|
|
3931
|
+
*/
|
|
3932
|
+
loadMoreOfPaginatedQuery(token, numItems) {
|
|
3933
|
+
this.mustGetPaginatedQuery(token);
|
|
3934
|
+
const lastPageToken = this.queryTokenForLastPageOfPaginatedQuery(token);
|
|
3935
|
+
const lastPageResult = this.client.localQueryResultByToken(lastPageToken);
|
|
3936
|
+
if (!lastPageResult) {
|
|
3937
|
+
return false;
|
|
3938
|
+
}
|
|
3939
|
+
const paginationResult = asPaginationResult(lastPageResult);
|
|
3940
|
+
if (paginationResult.isDone) {
|
|
3941
|
+
return false;
|
|
3942
|
+
}
|
|
3943
|
+
this.addPageToPaginatedQuery(
|
|
3944
|
+
token,
|
|
3945
|
+
paginationResult.continueCursor,
|
|
3946
|
+
numItems
|
|
3947
|
+
);
|
|
3948
|
+
const loadMoreTransition = {
|
|
3949
|
+
timestamp: this.lastTransitionTs,
|
|
3950
|
+
reflectedMutations: [],
|
|
3951
|
+
queries: [],
|
|
3952
|
+
paginatedQueries: [
|
|
3953
|
+
{
|
|
3954
|
+
token,
|
|
3955
|
+
modification: {
|
|
3956
|
+
kind: "Updated",
|
|
3957
|
+
result: this.localQueryResultByToken(token)
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
]
|
|
3961
|
+
};
|
|
3962
|
+
this.onTransition(loadMoreTransition);
|
|
3963
|
+
return true;
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* @internal
|
|
3967
|
+
*/
|
|
3968
|
+
queriesContainingTokens(queryTokens) {
|
|
3969
|
+
if (queryTokens.length === 0) {
|
|
3970
|
+
return [];
|
|
3971
|
+
}
|
|
3972
|
+
const changed = [];
|
|
3973
|
+
const queryTokenSet = new Set(queryTokens);
|
|
3974
|
+
for (const [paginatedToken, paginatedQuery] of this.paginatedQuerySet) {
|
|
3975
|
+
for (const pageToken of this.allQueryTokens(paginatedQuery)) {
|
|
3976
|
+
if (queryTokenSet.has(pageToken)) {
|
|
3977
|
+
changed.push(paginatedToken);
|
|
3978
|
+
break;
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
return changed;
|
|
3983
|
+
}
|
|
3984
|
+
/**
|
|
3985
|
+
* @internal
|
|
3986
|
+
*/
|
|
3987
|
+
processPaginatedQuerySplits(changed, getResult) {
|
|
3988
|
+
for (const paginatedQueryToken of changed) {
|
|
3989
|
+
const paginatedQuery = this.mustGetPaginatedQuery(paginatedQueryToken);
|
|
3990
|
+
const { ongoingSplits, pageKeyToQuery, pageKeys } = paginatedQuery;
|
|
3991
|
+
for (const [pageKey, [splitKey1, splitKey2]] of ongoingSplits) {
|
|
3992
|
+
const bothNewPagesLoaded = getResult(pageKeyToQuery.get(splitKey1).queryToken) !== void 0 && getResult(pageKeyToQuery.get(splitKey2).queryToken) !== void 0;
|
|
3993
|
+
if (bothNewPagesLoaded) {
|
|
3994
|
+
this.completePaginatedQuerySplit(
|
|
3995
|
+
paginatedQuery,
|
|
3996
|
+
pageKey,
|
|
3997
|
+
splitKey1,
|
|
3998
|
+
splitKey2
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
for (const pageKey of pageKeys) {
|
|
4003
|
+
if (ongoingSplits.has(pageKey)) {
|
|
4004
|
+
continue;
|
|
4005
|
+
}
|
|
4006
|
+
const pageToken = pageKeyToQuery.get(pageKey).queryToken;
|
|
4007
|
+
const pageResult = getResult(pageToken);
|
|
4008
|
+
if (!pageResult) {
|
|
4009
|
+
continue;
|
|
4010
|
+
}
|
|
4011
|
+
const result = asPaginationResult(pageResult);
|
|
4012
|
+
const shouldSplit = result.splitCursor && (result.pageStatus === "SplitRecommended" || result.pageStatus === "SplitRequired" || // This client-driven page splitting condition will change in the future.
|
|
4013
|
+
result.page.length > paginatedQuery.options.initialNumItems * 2);
|
|
4014
|
+
if (shouldSplit) {
|
|
4015
|
+
this.splitPaginatedQueryPage(
|
|
4016
|
+
paginatedQuery,
|
|
4017
|
+
pageKey,
|
|
4018
|
+
result.splitCursor,
|
|
4019
|
+
// we just checked
|
|
4020
|
+
result.continueCursor
|
|
4021
|
+
);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
splitPaginatedQueryPage(paginatedQuery, pageKey, splitCursor, continueCursor) {
|
|
4027
|
+
const splitKey1 = paginatedQuery.nextPageKey++;
|
|
4028
|
+
const splitKey2 = paginatedQuery.nextPageKey++;
|
|
4029
|
+
const paginationOpts = {
|
|
4030
|
+
cursor: continueCursor,
|
|
4031
|
+
numItems: paginatedQuery.options.initialNumItems,
|
|
4032
|
+
id: paginatedQuery.id
|
|
4033
|
+
};
|
|
4034
|
+
const firstSubscription = this.client.subscribe(
|
|
4035
|
+
paginatedQuery.canonicalizedUdfPath,
|
|
4036
|
+
{
|
|
4037
|
+
...paginatedQuery.args,
|
|
4038
|
+
paginationOpts: {
|
|
4039
|
+
...paginationOpts,
|
|
4040
|
+
cursor: null,
|
|
4041
|
+
// Start from beginning for first split
|
|
4042
|
+
endCursor: splitCursor
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
);
|
|
4046
|
+
paginatedQuery.pageKeyToQuery.set(splitKey1, firstSubscription);
|
|
4047
|
+
const secondSubscription = this.client.subscribe(
|
|
4048
|
+
paginatedQuery.canonicalizedUdfPath,
|
|
4049
|
+
{
|
|
4050
|
+
...paginatedQuery.args,
|
|
4051
|
+
paginationOpts: {
|
|
4052
|
+
...paginationOpts,
|
|
4053
|
+
cursor: splitCursor,
|
|
4054
|
+
endCursor: continueCursor
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
);
|
|
4058
|
+
paginatedQuery.pageKeyToQuery.set(splitKey2, secondSubscription);
|
|
4059
|
+
paginatedQuery.ongoingSplits.set(pageKey, [splitKey1, splitKey2]);
|
|
4060
|
+
}
|
|
4061
|
+
/**
|
|
4062
|
+
* @internal
|
|
4063
|
+
*/
|
|
4064
|
+
addPageToPaginatedQuery(token, continueCursor, numItems) {
|
|
4065
|
+
const paginatedQuery = this.mustGetPaginatedQuery(token);
|
|
4066
|
+
const pageKey = paginatedQuery.nextPageKey++;
|
|
4067
|
+
const paginationOpts = {
|
|
4068
|
+
cursor: continueCursor,
|
|
4069
|
+
numItems,
|
|
4070
|
+
id: paginatedQuery.id
|
|
4071
|
+
};
|
|
4072
|
+
const pageArgs = {
|
|
4073
|
+
...paginatedQuery.args,
|
|
4074
|
+
paginationOpts
|
|
4075
|
+
};
|
|
4076
|
+
const subscription = this.client.subscribe(
|
|
4077
|
+
paginatedQuery.canonicalizedUdfPath,
|
|
4078
|
+
pageArgs
|
|
4079
|
+
);
|
|
4080
|
+
paginatedQuery.pageKeys.push(pageKey);
|
|
4081
|
+
paginatedQuery.pageKeyToQuery.set(pageKey, subscription);
|
|
4082
|
+
return subscription;
|
|
4083
|
+
}
|
|
4084
|
+
removePaginatedQuerySubscriber(token) {
|
|
4085
|
+
const paginatedQuery = this.paginatedQuerySet.get(token);
|
|
4086
|
+
if (!paginatedQuery) {
|
|
4087
|
+
return;
|
|
4088
|
+
}
|
|
4089
|
+
paginatedQuery.numSubscribers -= 1;
|
|
4090
|
+
if (paginatedQuery.numSubscribers > 0) {
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
for (const subscription of paginatedQuery.pageKeyToQuery.values()) {
|
|
4094
|
+
subscription.unsubscribe();
|
|
4095
|
+
}
|
|
4096
|
+
this.paginatedQuerySet.delete(token);
|
|
4097
|
+
}
|
|
4098
|
+
completePaginatedQuerySplit(paginatedQuery, pageKey, splitKey1, splitKey2) {
|
|
4099
|
+
const originalQuery = paginatedQuery.pageKeyToQuery.get(pageKey);
|
|
4100
|
+
paginatedQuery.pageKeyToQuery.delete(pageKey);
|
|
4101
|
+
const pageIndex = paginatedQuery.pageKeys.indexOf(pageKey);
|
|
4102
|
+
paginatedQuery.pageKeys.splice(pageIndex, 1, splitKey1, splitKey2);
|
|
4103
|
+
paginatedQuery.ongoingSplits.delete(pageKey);
|
|
4104
|
+
originalQuery.unsubscribe();
|
|
4105
|
+
}
|
|
4106
|
+
/** The query tokens for all active pages, in result order */
|
|
4107
|
+
activePageQueryTokens(paginatedQuery) {
|
|
4108
|
+
return paginatedQuery.pageKeys.map(
|
|
4109
|
+
(pageKey) => paginatedQuery.pageKeyToQuery.get(pageKey).queryToken
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
allQueryTokens(paginatedQuery) {
|
|
4113
|
+
return Array.from(paginatedQuery.pageKeyToQuery.values()).map(
|
|
4114
|
+
(sub) => sub.queryToken
|
|
4115
|
+
);
|
|
4116
|
+
}
|
|
4117
|
+
queryTokenForLastPageOfPaginatedQuery(token) {
|
|
4118
|
+
const paginatedQuery = this.mustGetPaginatedQuery(token);
|
|
4119
|
+
const lastPageKey = paginatedQuery.pageKeys[paginatedQuery.pageKeys.length - 1];
|
|
4120
|
+
if (lastPageKey === void 0) {
|
|
4121
|
+
throw new Error(`No pages for paginated query ${token}`);
|
|
4122
|
+
}
|
|
4123
|
+
return paginatedQuery.pageKeyToQuery.get(lastPageKey).queryToken;
|
|
4124
|
+
}
|
|
4125
|
+
mustGetPaginatedQuery(token) {
|
|
4126
|
+
const paginatedQuery = this.paginatedQuerySet.get(token);
|
|
4127
|
+
if (!paginatedQuery) {
|
|
4128
|
+
throw new Error("paginated query no longer exists for token " + token);
|
|
4129
|
+
}
|
|
4130
|
+
return paginatedQuery;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
function useSubscription({
|
|
4134
|
+
// (Synchronously) returns the current value of our subscription.
|
|
4135
|
+
getCurrentValue,
|
|
4136
|
+
// This function is passed an event handler to attach to the subscription.
|
|
4137
|
+
// It should return an unsubscribe function that removes the handler.
|
|
4138
|
+
subscribe
|
|
4139
|
+
}) {
|
|
4140
|
+
const [state, setState] = reactExports.useState(() => ({
|
|
4141
|
+
getCurrentValue,
|
|
4142
|
+
subscribe,
|
|
4143
|
+
value: getCurrentValue()
|
|
4144
|
+
}));
|
|
4145
|
+
let valueToReturn = state.value;
|
|
4146
|
+
if (state.getCurrentValue !== getCurrentValue || state.subscribe !== subscribe) {
|
|
4147
|
+
valueToReturn = getCurrentValue();
|
|
4148
|
+
setState({
|
|
4149
|
+
getCurrentValue,
|
|
4150
|
+
subscribe,
|
|
4151
|
+
value: valueToReturn
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
reactExports.useEffect(() => {
|
|
4155
|
+
let didUnsubscribe = false;
|
|
4156
|
+
const checkForUpdates = () => {
|
|
4157
|
+
if (didUnsubscribe) {
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
4160
|
+
setState((prevState) => {
|
|
4161
|
+
if (prevState.getCurrentValue !== getCurrentValue || prevState.subscribe !== subscribe) {
|
|
4162
|
+
return prevState;
|
|
4163
|
+
}
|
|
4164
|
+
const value = getCurrentValue();
|
|
4165
|
+
if (prevState.value === value) {
|
|
4166
|
+
return prevState;
|
|
4167
|
+
}
|
|
4168
|
+
return { ...prevState, value };
|
|
4169
|
+
});
|
|
4170
|
+
};
|
|
4171
|
+
const unsubscribe = subscribe(checkForUpdates);
|
|
4172
|
+
checkForUpdates();
|
|
4173
|
+
return () => {
|
|
4174
|
+
didUnsubscribe = true;
|
|
4175
|
+
unsubscribe();
|
|
4176
|
+
};
|
|
4177
|
+
}, [getCurrentValue, subscribe]);
|
|
4178
|
+
return valueToReturn;
|
|
4179
|
+
}
|
|
4180
|
+
var __defProp$1 = Object.defineProperty;
|
|
4181
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4182
|
+
var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4183
|
+
const DEFAULT_EXTEND_SUBSCRIPTION_FOR = 5e3;
|
|
4184
|
+
if (typeof React === "undefined") {
|
|
4185
|
+
throw new Error("Required dependency 'react' not found");
|
|
4186
|
+
}
|
|
4187
|
+
function createMutation(mutationReference, client, update) {
|
|
4188
|
+
function mutation(args) {
|
|
4189
|
+
assertNotAccidentalArgument(args);
|
|
4190
|
+
return client.mutation(mutationReference, args, {
|
|
4191
|
+
optimisticUpdate: update
|
|
4192
|
+
});
|
|
4193
|
+
}
|
|
4194
|
+
mutation.withOptimisticUpdate = function withOptimisticUpdate(optimisticUpdate) {
|
|
4195
|
+
if (update !== void 0) {
|
|
4196
|
+
throw new Error(
|
|
4197
|
+
`Already specified optimistic update for mutation ${getFunctionName(
|
|
4198
|
+
mutationReference
|
|
4199
|
+
)}`
|
|
4200
|
+
);
|
|
4201
|
+
}
|
|
4202
|
+
return createMutation(mutationReference, client, optimisticUpdate);
|
|
4203
|
+
};
|
|
4204
|
+
return mutation;
|
|
4205
|
+
}
|
|
4206
|
+
class ConvexReactClient {
|
|
4207
|
+
/**
|
|
4208
|
+
* @param address - The url of your Convex deployment, often provided
|
|
4209
|
+
* by an environment variable. E.g. `https://small-mouse-123.convex.cloud`.
|
|
4210
|
+
* @param options - See {@link ConvexReactClientOptions} for a full description.
|
|
4211
|
+
*/
|
|
4212
|
+
constructor(address, options) {
|
|
4213
|
+
__publicField$1(this, "address");
|
|
4214
|
+
__publicField$1(this, "cachedSync");
|
|
4215
|
+
__publicField$1(this, "cachedPaginatedQueryClient");
|
|
4216
|
+
__publicField$1(this, "listeners");
|
|
4217
|
+
__publicField$1(this, "options");
|
|
4218
|
+
__publicField$1(this, "closed", false);
|
|
4219
|
+
__publicField$1(this, "_logger");
|
|
4220
|
+
__publicField$1(this, "adminAuth");
|
|
4221
|
+
__publicField$1(this, "fakeUserIdentity");
|
|
4222
|
+
if (address === void 0) {
|
|
4223
|
+
throw new Error(
|
|
4224
|
+
"No address provided to ConvexReactClient.\nIf trying to deploy to production, make sure to follow all the instructions found at https://docs.convex.dev/production/hosting/\nIf running locally, make sure to run `convex dev` and ensure the .env.local file is populated."
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
4227
|
+
if (typeof address !== "string") {
|
|
4228
|
+
throw new Error(
|
|
4229
|
+
`ConvexReactClient requires a URL like 'https://happy-otter-123.convex.cloud', received something of type ${typeof address} instead.`
|
|
4230
|
+
);
|
|
4231
|
+
}
|
|
4232
|
+
if (!address.includes("://")) {
|
|
4233
|
+
throw new Error("Provided address was not an absolute URL.");
|
|
4234
|
+
}
|
|
4235
|
+
this.address = address;
|
|
4236
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
4237
|
+
this._logger = options?.logger === false ? instantiateNoopLogger({ verbose: options?.verbose ?? false }) : options?.logger !== true && options?.logger ? options.logger : instantiateDefaultLogger({ verbose: options?.verbose ?? false });
|
|
4238
|
+
this.options = { ...options, logger: this._logger };
|
|
4239
|
+
}
|
|
4240
|
+
/**
|
|
4241
|
+
* Return the address for this client, useful for creating a new client.
|
|
4242
|
+
*
|
|
4243
|
+
* Not guaranteed to match the address with which this client was constructed:
|
|
4244
|
+
* it may be canonicalized.
|
|
4245
|
+
*/
|
|
4246
|
+
get url() {
|
|
4247
|
+
return this.address;
|
|
4248
|
+
}
|
|
4249
|
+
/**
|
|
4250
|
+
* Lazily instantiate the `BaseConvexClient` so we don't create the WebSocket
|
|
4251
|
+
* when server-side rendering.
|
|
4252
|
+
*
|
|
4253
|
+
* @internal
|
|
4254
|
+
*/
|
|
4255
|
+
get sync() {
|
|
4256
|
+
if (this.closed) {
|
|
4257
|
+
throw new Error("ConvexReactClient has already been closed.");
|
|
4258
|
+
}
|
|
4259
|
+
if (this.cachedSync) {
|
|
4260
|
+
return this.cachedSync;
|
|
4261
|
+
}
|
|
4262
|
+
this.cachedSync = new BaseConvexClient(
|
|
4263
|
+
this.address,
|
|
4264
|
+
() => {
|
|
4265
|
+
},
|
|
4266
|
+
// Use the PaginatedQueryClient's transition instead.
|
|
4267
|
+
this.options
|
|
4268
|
+
);
|
|
4269
|
+
if (this.adminAuth) {
|
|
4270
|
+
this.cachedSync.setAdminAuth(this.adminAuth, this.fakeUserIdentity);
|
|
4271
|
+
}
|
|
4272
|
+
this.cachedPaginatedQueryClient = new PaginatedQueryClient(
|
|
4273
|
+
this.cachedSync,
|
|
4274
|
+
(transition) => this.handleTransition(transition)
|
|
4275
|
+
);
|
|
4276
|
+
return this.cachedSync;
|
|
4277
|
+
}
|
|
4278
|
+
/**
|
|
4279
|
+
* Lazily instantiate the `PaginatedQueryClient` so we don't create it
|
|
4280
|
+
* when server-side rendering.
|
|
4281
|
+
*
|
|
4282
|
+
* @internal
|
|
4283
|
+
*/
|
|
4284
|
+
get paginatedQueryClient() {
|
|
4285
|
+
this.sync;
|
|
4286
|
+
if (this.cachedPaginatedQueryClient) {
|
|
4287
|
+
return this.cachedPaginatedQueryClient;
|
|
4288
|
+
}
|
|
4289
|
+
throw new Error("Should already be instantiated");
|
|
4290
|
+
}
|
|
4291
|
+
/**
|
|
4292
|
+
* Set the authentication token to be used for subsequent queries and mutations.
|
|
4293
|
+
* `fetchToken` will be called automatically again if a token expires.
|
|
4294
|
+
* `fetchToken` should return `null` if the token cannot be retrieved, for example
|
|
4295
|
+
* when the user's rights were permanently revoked.
|
|
4296
|
+
* @param fetchToken - an async function returning the JWT-encoded OpenID Connect Identity Token
|
|
4297
|
+
* @param onChange - a callback that will be called when the authentication status changes
|
|
4298
|
+
*/
|
|
4299
|
+
setAuth(fetchToken, onChange) {
|
|
4300
|
+
if (typeof fetchToken === "string") {
|
|
4301
|
+
throw new Error(
|
|
4302
|
+
"Passing a string to ConvexReactClient.setAuth is no longer supported, please upgrade to passing in an async function to handle reauthentication."
|
|
4303
|
+
);
|
|
4304
|
+
}
|
|
4305
|
+
this.sync.setAuth(
|
|
4306
|
+
fetchToken,
|
|
4307
|
+
onChange ?? (() => {
|
|
4308
|
+
})
|
|
4309
|
+
);
|
|
4310
|
+
}
|
|
4311
|
+
/**
|
|
4312
|
+
* Clear the current authentication token if set.
|
|
4313
|
+
*/
|
|
4314
|
+
clearAuth() {
|
|
4315
|
+
this.sync.clearAuth();
|
|
4316
|
+
}
|
|
4317
|
+
/**
|
|
4318
|
+
* @internal
|
|
4319
|
+
*/
|
|
4320
|
+
setAdminAuth(token, identity) {
|
|
4321
|
+
this.adminAuth = token;
|
|
4322
|
+
this.fakeUserIdentity = identity;
|
|
4323
|
+
if (this.closed) {
|
|
4324
|
+
throw new Error("ConvexReactClient has already been closed.");
|
|
4325
|
+
}
|
|
4326
|
+
if (this.cachedSync) {
|
|
4327
|
+
this.sync.setAdminAuth(token, identity);
|
|
4328
|
+
}
|
|
4329
|
+
}
|
|
4330
|
+
/**
|
|
4331
|
+
* Construct a new {@link Watch} on a Convex query function.
|
|
4332
|
+
*
|
|
4333
|
+
* **Most application code should not call this method directly. Instead use
|
|
4334
|
+
* the {@link useQuery} hook.**
|
|
4335
|
+
*
|
|
4336
|
+
* The act of creating a watch does nothing, a Watch is stateless.
|
|
4337
|
+
*
|
|
4338
|
+
* @param query - A {@link server.FunctionReference} for the public query to run.
|
|
4339
|
+
* @param args - An arguments object for the query. If this is omitted,
|
|
4340
|
+
* the arguments will be `{}`.
|
|
4341
|
+
* @param options - A {@link WatchQueryOptions} options object for this query.
|
|
4342
|
+
*
|
|
4343
|
+
* @returns The {@link Watch} object.
|
|
4344
|
+
*/
|
|
4345
|
+
watchQuery(query, ...argsAndOptions) {
|
|
4346
|
+
const [args, options] = argsAndOptions;
|
|
4347
|
+
const name = getFunctionName(query);
|
|
4348
|
+
return {
|
|
4349
|
+
onUpdate: (callback) => {
|
|
4350
|
+
const { queryToken, unsubscribe } = this.sync.subscribe(
|
|
4351
|
+
name,
|
|
4352
|
+
args,
|
|
4353
|
+
options
|
|
4354
|
+
);
|
|
4355
|
+
const currentListeners = this.listeners.get(queryToken);
|
|
4356
|
+
if (currentListeners !== void 0) {
|
|
4357
|
+
currentListeners.add(callback);
|
|
4358
|
+
} else {
|
|
4359
|
+
this.listeners.set(queryToken, /* @__PURE__ */ new Set([callback]));
|
|
4360
|
+
}
|
|
4361
|
+
return () => {
|
|
4362
|
+
if (this.closed) {
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
const currentListeners2 = this.listeners.get(queryToken);
|
|
4366
|
+
currentListeners2.delete(callback);
|
|
4367
|
+
if (currentListeners2.size === 0) {
|
|
4368
|
+
this.listeners.delete(queryToken);
|
|
4369
|
+
}
|
|
4370
|
+
unsubscribe();
|
|
4371
|
+
};
|
|
4372
|
+
},
|
|
4373
|
+
localQueryResult: () => {
|
|
4374
|
+
if (this.cachedSync) {
|
|
4375
|
+
return this.cachedSync.localQueryResult(name, args);
|
|
4376
|
+
}
|
|
4377
|
+
return void 0;
|
|
4378
|
+
},
|
|
4379
|
+
localQueryLogs: () => {
|
|
4380
|
+
if (this.cachedSync) {
|
|
4381
|
+
return this.cachedSync.localQueryLogs(name, args);
|
|
4382
|
+
}
|
|
4383
|
+
return void 0;
|
|
4384
|
+
},
|
|
4385
|
+
journal: () => {
|
|
4386
|
+
if (this.cachedSync) {
|
|
4387
|
+
return this.cachedSync.queryJournal(name, args);
|
|
4388
|
+
}
|
|
4389
|
+
return void 0;
|
|
4390
|
+
}
|
|
4391
|
+
};
|
|
4392
|
+
}
|
|
4393
|
+
// Let's try out a queryOptions-style API.
|
|
4394
|
+
// This method is similar to the React Query API `queryClient.prefetchQuery()`.
|
|
4395
|
+
// In the future an ensureQueryData(): Promise<Data> method could exist.
|
|
4396
|
+
/**
|
|
4397
|
+
* Indicates likely future interest in a query subscription.
|
|
4398
|
+
*
|
|
4399
|
+
* The implementation currently immediately subscribes to a query. In the future this method
|
|
4400
|
+
* may prioritize some queries over others, fetch the query result without subscribing, or
|
|
4401
|
+
* do nothing in slow network connections or high load scenarios.
|
|
4402
|
+
*
|
|
4403
|
+
* To use this in a React component, call useQuery() and ignore the return value.
|
|
4404
|
+
*
|
|
4405
|
+
* @param queryOptions - A query (function reference from an api object) and its args, plus
|
|
4406
|
+
* an optional extendSubscriptionFor for how long to subscribe to the query.
|
|
4407
|
+
*/
|
|
4408
|
+
prewarmQuery(queryOptions) {
|
|
4409
|
+
const extendSubscriptionFor = queryOptions.extendSubscriptionFor ?? DEFAULT_EXTEND_SUBSCRIPTION_FOR;
|
|
4410
|
+
const watch = this.watchQuery(queryOptions.query, queryOptions.args || {});
|
|
4411
|
+
const unsubscribe = watch.onUpdate(() => {
|
|
4412
|
+
});
|
|
4413
|
+
setTimeout(unsubscribe, extendSubscriptionFor);
|
|
4414
|
+
}
|
|
4415
|
+
/**
|
|
4416
|
+
* Construct a new {@link PaginatedWatch} on a Convex paginated query function.
|
|
4417
|
+
*
|
|
4418
|
+
* **Most application code should not call this method directly. Instead use
|
|
4419
|
+
* the {@link usePaginatedQuery} hook.**
|
|
4420
|
+
*
|
|
4421
|
+
* The act of creating a watch does nothing, a Watch is stateless.
|
|
4422
|
+
*
|
|
4423
|
+
* @param query - A {@link server.FunctionReference} for the public query to run.
|
|
4424
|
+
* @param args - An arguments object for the query. If this is omitted,
|
|
4425
|
+
* the arguments will be `{}`.
|
|
4426
|
+
* @param options - A {@link WatchPaginatedQueryOptions} options object for this query.
|
|
4427
|
+
*
|
|
4428
|
+
* @returns The {@link PaginatedWatch} object.
|
|
4429
|
+
*
|
|
4430
|
+
* @internal
|
|
4431
|
+
*/
|
|
4432
|
+
watchPaginatedQuery(query, args, options) {
|
|
4433
|
+
const name = getFunctionName(query);
|
|
4434
|
+
return {
|
|
4435
|
+
onUpdate: (callback) => {
|
|
4436
|
+
const { paginatedQueryToken, unsubscribe } = this.paginatedQueryClient.subscribe(name, args || {}, options);
|
|
4437
|
+
const currentListeners = this.listeners.get(paginatedQueryToken);
|
|
4438
|
+
if (currentListeners !== void 0) {
|
|
4439
|
+
currentListeners.add(callback);
|
|
4440
|
+
} else {
|
|
4441
|
+
this.listeners.set(paginatedQueryToken, /* @__PURE__ */ new Set([callback]));
|
|
4442
|
+
}
|
|
4443
|
+
return () => {
|
|
4444
|
+
if (this.closed) {
|
|
4445
|
+
return;
|
|
4446
|
+
}
|
|
4447
|
+
const currentListeners2 = this.listeners.get(paginatedQueryToken);
|
|
4448
|
+
currentListeners2.delete(callback);
|
|
4449
|
+
if (currentListeners2.size === 0) {
|
|
4450
|
+
this.listeners.delete(paginatedQueryToken);
|
|
4451
|
+
}
|
|
4452
|
+
unsubscribe();
|
|
4453
|
+
};
|
|
4454
|
+
},
|
|
4455
|
+
localQueryResult: () => {
|
|
4456
|
+
return this.paginatedQueryClient.localQueryResult(name, args, options);
|
|
4457
|
+
}
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
/**
|
|
4461
|
+
* Execute a mutation function.
|
|
4462
|
+
*
|
|
4463
|
+
* @param mutation - A {@link server.FunctionReference} for the public mutation
|
|
4464
|
+
* to run.
|
|
4465
|
+
* @param args - An arguments object for the mutation. If this is omitted,
|
|
4466
|
+
* the arguments will be `{}`.
|
|
4467
|
+
* @param options - A {@link MutationOptions} options object for the mutation.
|
|
4468
|
+
* @returns A promise of the mutation's result.
|
|
4469
|
+
*/
|
|
4470
|
+
mutation(mutation, ...argsAndOptions) {
|
|
4471
|
+
const [args, options] = argsAndOptions;
|
|
4472
|
+
const name = getFunctionName(mutation);
|
|
4473
|
+
return this.sync.mutation(name, args, options);
|
|
4474
|
+
}
|
|
4475
|
+
/**
|
|
4476
|
+
* Execute an action function.
|
|
4477
|
+
*
|
|
4478
|
+
* @param action - A {@link server.FunctionReference} for the public action
|
|
4479
|
+
* to run.
|
|
4480
|
+
* @param args - An arguments object for the action. If this is omitted,
|
|
4481
|
+
* the arguments will be `{}`.
|
|
4482
|
+
* @returns A promise of the action's result.
|
|
4483
|
+
*/
|
|
4484
|
+
action(action, ...args) {
|
|
4485
|
+
const name = getFunctionName(action);
|
|
4486
|
+
return this.sync.action(name, ...args);
|
|
4487
|
+
}
|
|
4488
|
+
/**
|
|
4489
|
+
* Fetch a query result once.
|
|
4490
|
+
*
|
|
4491
|
+
* **Most application code should subscribe to queries instead, using
|
|
4492
|
+
* the {@link useQuery} hook.**
|
|
4493
|
+
*
|
|
4494
|
+
* @param query - A {@link server.FunctionReference} for the public query
|
|
4495
|
+
* to run.
|
|
4496
|
+
* @param args - An arguments object for the query. If this is omitted,
|
|
4497
|
+
* the arguments will be `{}`.
|
|
4498
|
+
* @returns A promise of the query's result.
|
|
4499
|
+
*/
|
|
4500
|
+
query(query, ...args) {
|
|
4501
|
+
const watch = this.watchQuery(query, ...args);
|
|
4502
|
+
const existingResult = watch.localQueryResult();
|
|
4503
|
+
if (existingResult !== void 0) {
|
|
4504
|
+
return Promise.resolve(existingResult);
|
|
4505
|
+
}
|
|
4506
|
+
return new Promise((resolve, reject) => {
|
|
4507
|
+
const unsubscribe = watch.onUpdate(() => {
|
|
4508
|
+
unsubscribe();
|
|
4509
|
+
try {
|
|
4510
|
+
resolve(watch.localQueryResult());
|
|
4511
|
+
} catch (e) {
|
|
4512
|
+
reject(e);
|
|
4513
|
+
}
|
|
4514
|
+
});
|
|
4515
|
+
});
|
|
4516
|
+
}
|
|
4517
|
+
/**
|
|
4518
|
+
* Get the current {@link ConnectionState} between the client and the Convex
|
|
4519
|
+
* backend.
|
|
4520
|
+
*
|
|
4521
|
+
* @returns The {@link ConnectionState} with the Convex backend.
|
|
4522
|
+
*/
|
|
4523
|
+
connectionState() {
|
|
4524
|
+
return this.sync.connectionState();
|
|
4525
|
+
}
|
|
4526
|
+
/**
|
|
4527
|
+
* Subscribe to the {@link ConnectionState} between the client and the Convex
|
|
4528
|
+
* backend, calling a callback each time it changes.
|
|
4529
|
+
*
|
|
4530
|
+
* Subscribed callbacks will be called when any part of ConnectionState changes.
|
|
4531
|
+
* ConnectionState may grow in future versions (e.g. to provide a array of
|
|
4532
|
+
* inflight requests) in which case callbacks would be called more frequently.
|
|
4533
|
+
* ConnectionState may also *lose* properties in future versions as we figure
|
|
4534
|
+
* out what information is most useful. As such this API is considered unstable.
|
|
4535
|
+
*
|
|
4536
|
+
* @returns An unsubscribe function to stop listening.
|
|
4537
|
+
*/
|
|
4538
|
+
subscribeToConnectionState(cb) {
|
|
4539
|
+
return this.sync.subscribeToConnectionState(cb);
|
|
4540
|
+
}
|
|
4541
|
+
/**
|
|
4542
|
+
* Get the logger for this client.
|
|
4543
|
+
*
|
|
4544
|
+
* @returns The {@link Logger} for this client.
|
|
4545
|
+
*/
|
|
4546
|
+
get logger() {
|
|
4547
|
+
return this._logger;
|
|
4548
|
+
}
|
|
4549
|
+
/**
|
|
4550
|
+
* Close any network handles associated with this client and stop all subscriptions.
|
|
4551
|
+
*
|
|
4552
|
+
* Call this method when you're done with a {@link ConvexReactClient} to
|
|
4553
|
+
* dispose of its sockets and resources.
|
|
4554
|
+
*
|
|
4555
|
+
* @returns A `Promise` fulfilled when the connection has been completely closed.
|
|
4556
|
+
*/
|
|
4557
|
+
async close() {
|
|
4558
|
+
this.closed = true;
|
|
4559
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
4560
|
+
if (this.cachedPaginatedQueryClient) {
|
|
4561
|
+
this.cachedPaginatedQueryClient = void 0;
|
|
4562
|
+
}
|
|
4563
|
+
if (this.cachedSync) {
|
|
4564
|
+
const sync = this.cachedSync;
|
|
4565
|
+
this.cachedSync = void 0;
|
|
4566
|
+
await sync.close();
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4569
|
+
/**
|
|
4570
|
+
* Handle transitions from both base client and paginated client.
|
|
4571
|
+
* This ensures all transitions are processed synchronously and in order.
|
|
4572
|
+
*/
|
|
4573
|
+
handleTransition(transition) {
|
|
4574
|
+
const simple = transition.queries.map((q) => q.token);
|
|
4575
|
+
const paginated = transition.paginatedQueries.map((q) => q.token);
|
|
4576
|
+
this.transition([...simple, ...paginated]);
|
|
4577
|
+
}
|
|
4578
|
+
transition(updatedQueries) {
|
|
4579
|
+
for (const queryToken of updatedQueries) {
|
|
4580
|
+
const callbacks = this.listeners.get(queryToken);
|
|
4581
|
+
if (callbacks) {
|
|
4582
|
+
for (const callback of callbacks) {
|
|
4583
|
+
callback();
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
const ConvexContext = React.createContext(
|
|
4590
|
+
void 0
|
|
4591
|
+
// in the future this will be a mocked client for testing
|
|
4592
|
+
);
|
|
4593
|
+
function useConvex() {
|
|
4594
|
+
return reactExports.useContext(ConvexContext);
|
|
4595
|
+
}
|
|
4596
|
+
const ConvexProvider = ({ client, children }) => {
|
|
4597
|
+
return React.createElement(
|
|
4598
|
+
ConvexContext.Provider,
|
|
4599
|
+
{ value: client },
|
|
4600
|
+
children
|
|
4601
|
+
);
|
|
4602
|
+
};
|
|
4603
|
+
function useQuery(query, ...args) {
|
|
4604
|
+
const skip = args[0] === "skip";
|
|
4605
|
+
const argsObject = args[0] === "skip" ? {} : parseArgs(args[0]);
|
|
4606
|
+
const queryReference = typeof query === "string" ? makeFunctionReference(query) : query;
|
|
4607
|
+
const queryName = getFunctionName(queryReference);
|
|
4608
|
+
const queries = reactExports.useMemo(
|
|
4609
|
+
() => skip ? {} : { query: { query: queryReference, args: argsObject } },
|
|
4610
|
+
// Stringify args so args that are semantically the same don't trigger a
|
|
4611
|
+
// rerender. Saves developers from adding `useMemo` on every args usage.
|
|
4612
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4613
|
+
[JSON.stringify(convexToJson(argsObject)), queryName, skip]
|
|
4614
|
+
);
|
|
4615
|
+
const results = useQueries(queries);
|
|
4616
|
+
const result = results["query"];
|
|
4617
|
+
if (result instanceof Error) {
|
|
4618
|
+
throw result;
|
|
4619
|
+
}
|
|
4620
|
+
return result;
|
|
4621
|
+
}
|
|
4622
|
+
function useMutation(mutation) {
|
|
4623
|
+
const mutationReference = typeof mutation === "string" ? makeFunctionReference(mutation) : mutation;
|
|
4624
|
+
const convex = reactExports.useContext(ConvexContext);
|
|
4625
|
+
if (convex === void 0) {
|
|
4626
|
+
throw new Error(
|
|
4627
|
+
"Could not find Convex client! `useMutation` must be used in the React component tree under `ConvexProvider`. Did you forget it? See https://docs.convex.dev/quick-start#set-up-convex-in-your-react-app"
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
return reactExports.useMemo(
|
|
4631
|
+
() => createMutation(mutationReference, convex),
|
|
4632
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4633
|
+
[convex, getFunctionName(mutationReference)]
|
|
4634
|
+
);
|
|
4635
|
+
}
|
|
4636
|
+
function assertNotAccidentalArgument(value) {
|
|
4637
|
+
if (typeof value === "object" && value !== null && "bubbles" in value && "persist" in value && "isDefaultPrevented" in value) {
|
|
4638
|
+
throw new Error(
|
|
4639
|
+
`Convex function called with SyntheticEvent object. Did you use a Convex function as an event handler directly? Event handlers like onClick receive an event object as their first argument. These SyntheticEvent objects are not valid Convex values. Try wrapping the function like \`const handler = () => myMutation();\` and using \`handler\` in the event handler.`
|
|
4640
|
+
);
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
var __defProp = Object.defineProperty;
|
|
4644
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4645
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4646
|
+
class QueriesObserver {
|
|
4647
|
+
constructor(createWatch) {
|
|
4648
|
+
__publicField(this, "createWatch");
|
|
4649
|
+
__publicField(this, "queries");
|
|
4650
|
+
__publicField(this, "listeners");
|
|
4651
|
+
this.createWatch = createWatch;
|
|
4652
|
+
this.queries = {};
|
|
4653
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
4654
|
+
}
|
|
4655
|
+
setQueries(newQueries) {
|
|
4656
|
+
for (const identifier of Object.keys(newQueries)) {
|
|
4657
|
+
const { query, args, paginationOptions } = newQueries[identifier];
|
|
4658
|
+
getFunctionName(query);
|
|
4659
|
+
if (this.queries[identifier] === void 0) {
|
|
4660
|
+
this.addQuery(
|
|
4661
|
+
identifier,
|
|
4662
|
+
query,
|
|
4663
|
+
args,
|
|
4664
|
+
paginationOptions ? { paginationOptions } : {}
|
|
4665
|
+
);
|
|
4666
|
+
} else {
|
|
4667
|
+
const existingInfo = this.queries[identifier];
|
|
4668
|
+
if (getFunctionName(query) !== getFunctionName(existingInfo.query) || JSON.stringify(convexToJson(args)) !== JSON.stringify(convexToJson(existingInfo.args)) || JSON.stringify(paginationOptions) !== JSON.stringify(existingInfo.paginationOptions)) {
|
|
4669
|
+
this.removeQuery(identifier);
|
|
4670
|
+
this.addQuery(
|
|
4671
|
+
identifier,
|
|
4672
|
+
query,
|
|
4673
|
+
args,
|
|
4674
|
+
paginationOptions ? { paginationOptions } : {}
|
|
4675
|
+
);
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
for (const identifier of Object.keys(this.queries)) {
|
|
4680
|
+
if (newQueries[identifier] === void 0) {
|
|
4681
|
+
this.removeQuery(identifier);
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
subscribe(listener) {
|
|
4686
|
+
this.listeners.add(listener);
|
|
4687
|
+
return () => {
|
|
4688
|
+
this.listeners.delete(listener);
|
|
4689
|
+
};
|
|
4690
|
+
}
|
|
4691
|
+
getLocalResults(queries) {
|
|
4692
|
+
const result = {};
|
|
4693
|
+
for (const identifier of Object.keys(queries)) {
|
|
4694
|
+
const { query, args } = queries[identifier];
|
|
4695
|
+
const paginationOptions = queries[identifier].paginationOptions;
|
|
4696
|
+
getFunctionName(query);
|
|
4697
|
+
const watch = this.createWatch(
|
|
4698
|
+
query,
|
|
4699
|
+
args,
|
|
4700
|
+
paginationOptions ? { paginationOptions } : {}
|
|
4701
|
+
);
|
|
4702
|
+
let value;
|
|
4703
|
+
try {
|
|
4704
|
+
value = watch.localQueryResult();
|
|
4705
|
+
} catch (e) {
|
|
4706
|
+
if (e instanceof Error) {
|
|
4707
|
+
value = e;
|
|
4708
|
+
} else {
|
|
4709
|
+
throw e;
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
result[identifier] = value;
|
|
4713
|
+
}
|
|
4714
|
+
return result;
|
|
4715
|
+
}
|
|
4716
|
+
setCreateWatch(createWatch) {
|
|
4717
|
+
this.createWatch = createWatch;
|
|
4718
|
+
for (const identifier of Object.keys(this.queries)) {
|
|
4719
|
+
const { query, args, watch, paginationOptions } = this.queries[identifier];
|
|
4720
|
+
const journal = "journal" in watch ? watch.journal() : void 0;
|
|
4721
|
+
this.removeQuery(identifier);
|
|
4722
|
+
this.addQuery(identifier, query, args, {
|
|
4723
|
+
...journal ? { journal } : [],
|
|
4724
|
+
...paginationOptions ? { paginationOptions } : {}
|
|
4725
|
+
});
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
destroy() {
|
|
4729
|
+
for (const identifier of Object.keys(this.queries)) {
|
|
4730
|
+
this.removeQuery(identifier);
|
|
4731
|
+
}
|
|
4732
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
4733
|
+
}
|
|
4734
|
+
addQuery(identifier, query, args, {
|
|
4735
|
+
paginationOptions,
|
|
4736
|
+
journal
|
|
4737
|
+
}) {
|
|
4738
|
+
if (this.queries[identifier] !== void 0) {
|
|
4739
|
+
throw new Error(
|
|
4740
|
+
`Tried to add a new query with identifier ${identifier} when it already exists.`
|
|
4741
|
+
);
|
|
4742
|
+
}
|
|
4743
|
+
const watch = this.createWatch(query, args, {
|
|
4744
|
+
...journal ? { journal } : [],
|
|
4745
|
+
...paginationOptions ? { paginationOptions } : {}
|
|
4746
|
+
});
|
|
4747
|
+
const unsubscribe = watch.onUpdate(() => this.notifyListeners());
|
|
4748
|
+
this.queries[identifier] = {
|
|
4749
|
+
query,
|
|
4750
|
+
args,
|
|
4751
|
+
watch,
|
|
4752
|
+
unsubscribe,
|
|
4753
|
+
...paginationOptions ? { paginationOptions } : {}
|
|
4754
|
+
};
|
|
4755
|
+
}
|
|
4756
|
+
removeQuery(identifier) {
|
|
4757
|
+
const info = this.queries[identifier];
|
|
4758
|
+
if (info === void 0) {
|
|
4759
|
+
throw new Error(`No query found with identifier ${identifier}.`);
|
|
4760
|
+
}
|
|
4761
|
+
info.unsubscribe();
|
|
4762
|
+
delete this.queries[identifier];
|
|
4763
|
+
}
|
|
4764
|
+
notifyListeners() {
|
|
4765
|
+
for (const listener of this.listeners) {
|
|
4766
|
+
listener();
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
function useQueries(queries) {
|
|
4771
|
+
const convex = useConvex();
|
|
4772
|
+
if (convex === void 0) {
|
|
4773
|
+
throw new Error(
|
|
4774
|
+
"Could not find Convex client! `useQuery` must be used in the React component tree under `ConvexProvider`. Did you forget it? See https://docs.convex.dev/quick-start#set-up-convex-in-your-react-app"
|
|
4775
|
+
);
|
|
4776
|
+
}
|
|
4777
|
+
const createWatch = reactExports.useMemo(() => {
|
|
4778
|
+
return (query, args, {
|
|
4779
|
+
journal,
|
|
4780
|
+
paginationOptions
|
|
4781
|
+
}) => {
|
|
4782
|
+
if (paginationOptions) {
|
|
4783
|
+
return convex.watchPaginatedQuery(query, args, paginationOptions);
|
|
4784
|
+
} else {
|
|
4785
|
+
return convex.watchQuery(query, args, journal ? { journal } : {});
|
|
4786
|
+
}
|
|
4787
|
+
};
|
|
4788
|
+
}, [convex]);
|
|
4789
|
+
return useQueriesHelper(queries, createWatch);
|
|
4790
|
+
}
|
|
4791
|
+
function useQueriesHelper(queries, createWatch) {
|
|
4792
|
+
const [observer] = reactExports.useState(() => new QueriesObserver(createWatch));
|
|
4793
|
+
if (observer.createWatch !== createWatch) {
|
|
4794
|
+
observer.setCreateWatch(createWatch);
|
|
4795
|
+
}
|
|
4796
|
+
reactExports.useEffect(() => () => observer.destroy(), [observer]);
|
|
4797
|
+
const subscription = reactExports.useMemo(
|
|
4798
|
+
() => ({
|
|
4799
|
+
getCurrentValue: () => {
|
|
4800
|
+
return observer.getLocalResults(queries);
|
|
4801
|
+
},
|
|
4802
|
+
subscribe: (callback) => {
|
|
4803
|
+
observer.setQueries(queries);
|
|
4804
|
+
return observer.subscribe(callback);
|
|
4805
|
+
}
|
|
4806
|
+
}),
|
|
4807
|
+
[observer, queries]
|
|
4808
|
+
);
|
|
4809
|
+
return useSubscription(subscription);
|
|
4810
|
+
}
|
|
4811
|
+
function createChildComponents(root, pathParts) {
|
|
4812
|
+
const handler = {
|
|
4813
|
+
get(_, prop) {
|
|
4814
|
+
if (typeof prop === "string") {
|
|
4815
|
+
const newParts = [...pathParts, prop];
|
|
4816
|
+
return createChildComponents(root, newParts);
|
|
4817
|
+
} else if (prop === toReferencePath) {
|
|
4818
|
+
if (pathParts.length < 1) {
|
|
4819
|
+
const found = [root, ...pathParts].join(".");
|
|
4820
|
+
throw new Error(
|
|
4821
|
+
`API path is expected to be of the form \`${root}.childComponent.functionName\`. Found: \`${found}\``
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
return `_reference/childComponent/` + pathParts.join("/");
|
|
4825
|
+
} else {
|
|
4826
|
+
return void 0;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
};
|
|
4830
|
+
return new Proxy({}, handler);
|
|
4831
|
+
}
|
|
4832
|
+
const componentsGeneric = () => createChildComponents("components", []);
|
|
4833
|
+
export {
|
|
4834
|
+
ConvexReactClient as C,
|
|
4835
|
+
ConvexProvider as a,
|
|
4836
|
+
anyApi as b,
|
|
4837
|
+
componentsGeneric as c,
|
|
4838
|
+
useMutation as d,
|
|
4839
|
+
useQuery as u,
|
|
4840
|
+
v
|
|
4841
|
+
};
|