modelence 0.23.1 → 0.24.0-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/chunk-63RBLFRJ.js +4 -0
  2. package/dist/chunk-63RBLFRJ.js.map +1 -0
  3. package/dist/chunk-E3L5INVU.js +41 -0
  4. package/dist/chunk-E3L5INVU.js.map +1 -0
  5. package/dist/chunk-FBYHAMXI.js +2 -0
  6. package/dist/chunk-FBYHAMXI.js.map +1 -0
  7. package/dist/{chunk-UJ2WYHH7.js → chunk-NPBIRXLC.js} +2 -2
  8. package/dist/{chunk-UJ2WYHH7.js.map → chunk-NPBIRXLC.js.map} +1 -1
  9. package/dist/chunk-RZ5A2YM2.js +3 -0
  10. package/dist/chunk-RZ5A2YM2.js.map +1 -0
  11. package/dist/chunk-ZI6PQXEB.js +3 -0
  12. package/dist/{chunk-WDY7BYXR.js.map → chunk-ZI6PQXEB.js.map} +1 -1
  13. package/dist/chunk-ZVA536ET.js +2 -0
  14. package/dist/chunk-ZVA536ET.js.map +1 -0
  15. package/dist/client.d.ts +124 -2
  16. package/dist/client.js +1 -1
  17. package/dist/{package-YDKHBRBH.js → package-43UFIFMI.js} +2 -2
  18. package/dist/{package-YDKHBRBH.js.map → package-43UFIFMI.js.map} +1 -1
  19. package/dist/{render-OM7ZJWZA.js → render-5AGW5SQM.js} +2 -2
  20. package/dist/{render-OM7ZJWZA.js.map → render-5AGW5SQM.js.map} +1 -1
  21. package/dist/renderApp-3C3JFOHW.js +2 -0
  22. package/dist/{renderApp-MD5O44UP.js.map → renderApp-3C3JFOHW.js.map} +1 -1
  23. package/dist/server-TU6GA634.js +2 -0
  24. package/dist/{server-GHQ5XNNQ.js.map → server-TU6GA634.js.map} +1 -1
  25. package/dist/server.d.ts +81 -2
  26. package/dist/server.js +1 -1
  27. package/dist/transport-FTLINZKK.js +2 -0
  28. package/dist/{transport-VKGH6BY6.js.map → transport-FTLINZKK.js.map} +1 -1
  29. package/package.json +1 -1
  30. package/dist/chunk-4I4BI7KD.js +0 -2
  31. package/dist/chunk-4I4BI7KD.js.map +0 -1
  32. package/dist/chunk-KDOTVIEY.js +0 -4
  33. package/dist/chunk-KDOTVIEY.js.map +0 -1
  34. package/dist/chunk-KFTT3M45.js +0 -3
  35. package/dist/chunk-KFTT3M45.js.map +0 -1
  36. package/dist/chunk-LCXJNLNK.js +0 -41
  37. package/dist/chunk-LCXJNLNK.js.map +0 -1
  38. package/dist/chunk-W4RFIS5E.js +0 -2
  39. package/dist/chunk-W4RFIS5E.js.map +0 -1
  40. package/dist/chunk-WDY7BYXR.js +0 -3
  41. package/dist/renderApp-MD5O44UP.js +0 -2
  42. package/dist/server-GHQ5XNNQ.js +0 -2
  43. package/dist/transport-VKGH6BY6.js +0 -2
package/dist/client.d.ts CHANGED
@@ -7,6 +7,12 @@ import { QueryClient, DehydratedState } from '@tanstack/react-query';
7
7
  import 'http';
8
8
  import 'express';
9
9
 
10
+ /**
11
+ * What `openUrl` may return. A `Window` (from `window.open`) enables the popup
12
+ * handoff described on {@link ClientConfig.openUrl}; anything else — including
13
+ * the `Promise` from `Linking.openURL` — is accepted and ignored.
14
+ */
15
+ type OpenUrlResult = void | Window | null | Promise<unknown>;
10
16
  interface ClientConfig {
11
17
  baseUrl: string;
12
18
  getAuthToken: () => string | undefined;
@@ -16,8 +22,15 @@ interface ClientConfig {
16
22
  * Opens a URL for OAuth redirects. React Native must use
17
23
  * `(url) => Linking.openURL(url)` — WebView is not supported.
18
24
  * Defaults to `window.location.href` when not provided.
25
+ *
26
+ * A web client that opens the flow in a popup should return the window
27
+ * `window.open` gave it: `(url) => window.open(url)`. With that reference the
28
+ * callback page in the popup can obtain the sign-in verifier from this page
29
+ * directly, which is what makes `loginWithOAuth` work when the app itself
30
+ * runs inside a cross-origin iframe and the popup's storage is partitioned
31
+ * away from it. Any other return value is ignored.
19
32
  */
20
- openUrl?: (url: string) => void;
33
+ openUrl?: (url: string) => OpenUrlResult;
21
34
  /**
22
35
  * Credentials mode for method-call requests. Defaults to `'include'`, which
23
36
  * browser apps need for the cookie-based flows (password reset, magic link).
@@ -599,19 +612,110 @@ declare function resetPassword(options: {
599
612
  token?: string;
600
613
  password: string;
601
614
  }): Promise<void>;
615
+ /**
616
+ * Start an OAuth sign-in.
617
+ *
618
+ * On the web this navigates to the provider and the flow finishes on its own —
619
+ * the session cookie is set and the browser lands back on your site.
620
+ *
621
+ * Pass `redirectUri` to run the native flow: the device browser opens the
622
+ * provider, and when the flow completes Modelence redirects back to that deep
623
+ * link with a single-use `code` query parameter. Hand that code to
624
+ * {@link loginWithOAuth} to finish signing in. The redirect target must be
625
+ * listed in the server's `auth.mobile.redirectUrls`, otherwise the request is
626
+ * rejected before the provider is ever reached.
627
+ *
628
+ * The native flow additionally binds the sign-in to this device: a verifier is
629
+ * held in memory here and replayed by `loginWithOAuth`, so a `code` delivered
630
+ * to the app from outside this flow cannot be redeemed.
631
+ *
632
+ * @example Web
633
+ * ```ts
634
+ * await signInWithOAuth({ provider: 'google' });
635
+ * ```
636
+ *
637
+ * @example React Native
638
+ * ```ts
639
+ * import { parseDeepLinkParams } from 'modelence/client';
640
+ *
641
+ * await signInWithOAuth({ provider: 'google', redirectUri: 'myapp://auth' });
642
+ *
643
+ * Linking.addEventListener('url', async ({ url }) => {
644
+ * const { code } = parseDeepLinkParams(url);
645
+ * if (code) await loginWithOAuth({ code });
646
+ * });
647
+ * ```
648
+ * @param options.provider - The OAuth provider to sign in with ('google' or 'github').
649
+ * @param options.redirectUri - Deep link to return to. Required on React Native.
650
+ */
651
+ declare function signInWithOAuth(options: {
652
+ provider: OAuthProvider;
653
+ redirectUri?: string;
654
+ }): Promise<void>;
655
+ /**
656
+ * Complete a native OAuth sign-in with the code from the deep link.
657
+ *
658
+ * Exchanges the single-use code that {@link signInWithOAuth} delivered to your
659
+ * app's deep link for a session, stores the auth token, and returns the
660
+ * signed-in user. Codes are valid for one minute and can only be redeemed once.
661
+ *
662
+ * Pairs with `signInWithOAuth({ provider, redirectUri })` — the flow that hands
663
+ * a code back to your app. It works on native and under Expo Web, where the
664
+ * verifier is kept in `sessionStorage` so it survives the navigation to the
665
+ * provider. When the flow ran in a popup whose `openUrl` returned the window
666
+ * it opened, the verifier is fetched from the opening page instead, so it also
667
+ * works when the app is embedded in a cross-origin iframe. A plain web app
668
+ * that calls `signInWithOAuth({ provider })` with no `redirectUri` is signed
669
+ * in by a session cookie and never needs this.
670
+ *
671
+ * The verifier minted by `signInWithOAuth` is replayed here, which is what
672
+ * makes a code usable only by the client that started the flow. Calling this
673
+ * without a preceding `signInWithOAuth` — as a crafted deep link would — fails
674
+ * before the code is ever sent.
675
+ *
676
+ * @example
677
+ * ```ts
678
+ * const user = await loginWithOAuth({ code });
679
+ * ```
680
+ * @param options.code - The `code` query parameter from the deep link.
681
+ */
682
+ declare function loginWithOAuth(options: {
683
+ code: string;
684
+ }): Promise<{
685
+ id: string;
686
+ handle: string;
687
+ roles: string[];
688
+ hasRole: (role: string) => boolean;
689
+ requireRole: (role: string) => void;
690
+ firstName?: string;
691
+ lastName?: string;
692
+ avatarUrl?: string;
693
+ } | null>;
602
694
  /**
603
695
  * Link an OAuth provider to the currently signed-in user's account.
604
696
  * Redirects the browser to the OAuth provider's authorization page.
605
697
  * The provider will redirect back and the account will be linked.
606
698
  *
699
+ * Without `redirectUri` this navigates the current context and authenticates
700
+ * with an httpOnly cookie, so it only works where the navigation stays in the
701
+ * same cookie jar — a browser, or a webview that navigates in place. Clients
702
+ * whose `openUrl` opens an external browser (Electron, Capacitor) must pass a
703
+ * `redirectUri`: that flow carries a single-use nonce in the URL and does not
704
+ * depend on cookies.
705
+ *
607
706
  * @example
608
707
  * ```ts
609
708
  * linkOAuthProvider({ provider: 'google' });
610
709
  * ```
611
710
  * @param options.provider - The OAuth provider to link ('google' or 'github').
711
+ * @param options.redirectUri - Deep link to return to once linking completes.
712
+ * Required for React Native and any client that opens URLs externally; must be
713
+ * listed in the server's `auth.mobile.redirectUrls`. Without it the flow ends
714
+ * wherever the navigation lands rather than back in the app.
612
715
  */
613
716
  declare function linkOAuthProvider(options: {
614
717
  provider: OAuthProvider;
718
+ redirectUri?: string;
615
719
  }): Promise<void>;
616
720
  /**
617
721
  * Unlink an OAuth provider from the currently signed-in user's account.
@@ -626,6 +730,24 @@ declare function unlinkOAuthProvider(options: {
626
730
  provider: OAuthProvider;
627
731
  }): Promise<void>;
628
732
 
733
+ /**
734
+ * Deep-link query parsing that works on bare React Native.
735
+ *
736
+ * `new URL(url).searchParams` is not usable here: React Native's `URL` is a
737
+ * partial implementation that throws on `searchParams` access, and Expo only
738
+ * appears to work because it installs a polyfill. Parsing the query string
739
+ * directly keeps the documented callback snippet working on both.
740
+ */
741
+ /**
742
+ * Reads query parameters out of a deep link such as `myapp://auth?code=abc`.
743
+ *
744
+ * Only the query component is considered — everything from the first `?` up to
745
+ * an optional `#` fragment. Values are percent-decoded, with `+` treated as a
746
+ * space to match `application/x-www-form-urlencoded`, which is how the server's
747
+ * `URL.searchParams.set` encodes them.
748
+ */
749
+ declare function parseDeepLinkParams(url: string): Record<string, string>;
750
+
629
751
  declare function subscribeLiveQuery<T = unknown>(method: string, args: Record<string, unknown>, onData: (data: T) => void, onError?: (error: string) => void): () => void;
630
752
 
631
753
  declare function setWebsocketClientProvider(provider: WebsocketClientProvider | null): void;
@@ -639,4 +761,4 @@ declare function getLocalStorageSession(): any;
639
761
 
640
762
  declare const AppProvider: any;
641
763
 
642
- export { AppProvider, type CallMethodOptions, ClientChannel, type ClientConfig, type MethodArgs, MethodError, ModelenceQueryClient, type ModelenceQueryKey, ModelenceQueryProvider, type UserInfo, ValueType, callMethod, configureClient, connectModelenceQueryClient, createClientModule, createQueryKey, disconnectModelenceQueryClient, getConfig, getLocalStorageSession, getWebsocketClientProvider, linkOAuthProvider, loginWithMagicLink, loginWithOneTimeCode, loginWithPassword, logout, modelenceLiveQuery, modelenceMutation, modelenceQuery, renderApp, resendEmailVerification, resetPassword, sendMagicLink, sendResetPasswordToken, setWebsocketClientProvider, signupWithPassword, startWebsockets, subscribeLiveQuery, systemConfig, unlinkOAuthProvider, updateProfile, useSession, verifyEmail };
764
+ export { AppProvider, type CallMethodOptions, ClientChannel, type ClientConfig, type MethodArgs, MethodError, ModelenceQueryClient, type ModelenceQueryKey, ModelenceQueryProvider, type OpenUrlResult, type UserInfo, ValueType, callMethod, configureClient, connectModelenceQueryClient, createClientModule, createQueryKey, disconnectModelenceQueryClient, getConfig, getLocalStorageSession, getWebsocketClientProvider, linkOAuthProvider, loginWithMagicLink, loginWithOAuth, loginWithOneTimeCode, loginWithPassword, logout, modelenceLiveQuery, modelenceMutation, modelenceQuery, parseDeepLinkParams, renderApp, resendEmailVerification, resetPassword, sendMagicLink, sendResetPasswordToken, setWebsocketClientProvider, signInWithOAuth, signupWithPassword, startWebsockets, subscribeLiveQuery, systemConfig, unlinkOAuthProvider, updateProfile, useSession, verifyEmail };
package/dist/client.js CHANGED
@@ -1,2 +1,2 @@
1
- export{f as AppProvider,e as ClientChannel,a as createClientModule,d as renderApp,b as systemConfig}from'./chunk-4I4BI7KD.js';export{i as ModelenceQueryClient,n as ModelenceQueryProvider,f as connectModelenceQueryClient,m as createQueryKey,h as disconnectModelenceQueryClient,d as getWebsocketClientProvider,k as modelenceLiveQuery,l as modelenceMutation,j as modelenceQuery,c as setWebsocketClientProvider,e as startWebsockets,b as subscribeLiveQuery}from'./chunk-WDY7BYXR.js';export{F as MethodError,I as callMethod,d as configureClient,b as getConfig,c as getLocalStorageSession,A as linkOAuthProvider,x as loginWithMagicLink,y as loginWithOneTimeCode,q as loginWithPassword,u as logout,t as resendEmailVerification,z as resetPassword,w as sendMagicLink,v as sendResetPasswordToken,p as signupWithPassword,B as unlinkOAuthProvider,r as updateProfile,o as useSession,s as verifyEmail}from'./chunk-W4RFIS5E.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=client.js.map
1
+ export{g as AppProvider,f as ClientChannel,a as createClientModule,e as parseDeepLinkParams,d as renderApp,b as systemConfig}from'./chunk-ZVA536ET.js';export{i as ModelenceQueryClient,n as ModelenceQueryProvider,f as connectModelenceQueryClient,m as createQueryKey,h as disconnectModelenceQueryClient,d as getWebsocketClientProvider,k as modelenceLiveQuery,l as modelenceMutation,j as modelenceQuery,c as setWebsocketClientProvider,e as startWebsockets,b as subscribeLiveQuery}from'./chunk-ZI6PQXEB.js';export{H as MethodError,K as callMethod,d as configureClient,b as getConfig,c as getLocalStorageSession,C as linkOAuthProvider,x as loginWithMagicLink,B as loginWithOAuth,y as loginWithOneTimeCode,q as loginWithPassword,u as logout,t as resendEmailVerification,z as resetPassword,w as sendMagicLink,v as sendResetPasswordToken,A as signInWithOAuth,p as signupWithPassword,D as unlinkOAuthProvider,r as updateProfile,o as useSession,s as verifyEmail}from'./chunk-FBYHAMXI.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=client.js.map
2
2
  //# sourceMappingURL=client.js.map
@@ -1,2 +1,2 @@
1
- export{m as author,i as bin,o as bugs,t as default,r as dependencies,d as description,q as devDependencies,j as engines,g as exports,h as files,p as homepage,n as license,e as main,b as name,s as peerDependencies,l as repository,k as scripts,a as type,f as types,c as version}from'./chunk-KFTT3M45.js';//# sourceMappingURL=package-YDKHBRBH.js.map
2
- //# sourceMappingURL=package-YDKHBRBH.js.map
1
+ export{m as author,i as bin,o as bugs,t as default,r as dependencies,d as description,q as devDependencies,j as engines,g as exports,h as files,p as homepage,n as license,e as main,b as name,s as peerDependencies,l as repository,k as scripts,a as type,f as types,c as version}from'./chunk-RZ5A2YM2.js';//# sourceMappingURL=package-43UFIFMI.js.map
2
+ //# sourceMappingURL=package-43UFIFMI.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"package-YDKHBRBH.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"package-43UFIFMI.js"}
@@ -1,2 +1,2 @@
1
- import {a,b,c}from'./chunk-UJ2WYHH7.js';import'./chunk-KDOTVIEY.js';import'./chunk-UW37F3GV.js';import {a as a$1,n}from'./chunk-WDY7BYXR.js';import {m,a as a$2,n as n$1}from'./chunk-W4RFIS5E.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';import {renderToPipeableStream}from'react-dom/server';import {Writable}from'stream';import {QueryClient,dehydrate}from'@tanstack/react-query';import {jsx}from'react/jsx-runtime';var J=()=>{let r=c();return r?n$1(r.session.user):null},M=r=>{let n=c();if(n)return n.session.configs[r]?.value};function D(){m(J),a$2(M);}async function X(r){let{callContext:n$1,loadingElement:C,routesElement:S,router:u,location:x,onShellReady:E,onError:b$1}=r;D();let l=await a("_system.session.init",{},n$1),i=new QueryClient({defaultOptions:{queries:{retry:false,gcTime:0}}}),O=u?u({children:S,location:x}):S,Q=jsx(a$1,{loadingElement:C,children:jsx(n,{client:i,children:O})}),a$2=null;await new Promise((s,m)=>{b({callContext:n$1,queryClient:i,session:{user:l.user,configs:l.configs??{}}},()=>{let e=renderToPipeableStream(Q,{onShellReady(){a$2=e,E?.(),s(e);},onShellError(t){m(t);},onError(t){b$1?.(t);}});return e});});let d=null,_=s=>new Promise((m,e)=>{if(!a$2){e(new Error("SSR stream was not initialized"));return}let t=new Writable({write(o,c,I){s.write(o,N=>I(N??void 0));},final(o){try{let c=dehydrate(i);d=JSON.stringify(c);}finally{i.clear();}o(),m();}});t.on("error",o=>{s.destroy(o),e(o);}),s.on("error",e),a$2.pipe(t);});return {sessionState:JSON.stringify({session:l}),pipe:_,getQueryState:()=>{if(d===null)throw new Error("getQueryState() called before stream finished");return d}}}export{X as renderSsrTreeStream};//# sourceMappingURL=render-OM7ZJWZA.js.map
2
- //# sourceMappingURL=render-OM7ZJWZA.js.map
1
+ import {a,b,c}from'./chunk-NPBIRXLC.js';import'./chunk-63RBLFRJ.js';import'./chunk-UW37F3GV.js';import {a as a$1,n}from'./chunk-ZI6PQXEB.js';import {m,a as a$2,n as n$1}from'./chunk-FBYHAMXI.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';import {renderToPipeableStream}from'react-dom/server';import {Writable}from'stream';import {QueryClient,dehydrate}from'@tanstack/react-query';import {jsx}from'react/jsx-runtime';var J=()=>{let r=c();return r?n$1(r.session.user):null},M=r=>{let n=c();if(n)return n.session.configs[r]?.value};function D(){m(J),a$2(M);}async function X(r){let{callContext:n$1,loadingElement:C,routesElement:S,router:u,location:x,onShellReady:E,onError:b$1}=r;D();let l=await a("_system.session.init",{},n$1),i=new QueryClient({defaultOptions:{queries:{retry:false,gcTime:0}}}),O=u?u({children:S,location:x}):S,Q=jsx(a$1,{loadingElement:C,children:jsx(n,{client:i,children:O})}),a$2=null;await new Promise((s,m)=>{b({callContext:n$1,queryClient:i,session:{user:l.user,configs:l.configs??{}}},()=>{let e=renderToPipeableStream(Q,{onShellReady(){a$2=e,E?.(),s(e);},onShellError(t){m(t);},onError(t){b$1?.(t);}});return e});});let d=null,_=s=>new Promise((m,e)=>{if(!a$2){e(new Error("SSR stream was not initialized"));return}let t=new Writable({write(o,c,I){s.write(o,N=>I(N??void 0));},final(o){try{let c=dehydrate(i);d=JSON.stringify(c);}finally{i.clear();}o(),m();}});t.on("error",o=>{s.destroy(o),e(o);}),s.on("error",e),a$2.pipe(t);});return {sessionState:JSON.stringify({session:l}),pipe:_,getQueryState:()=>{if(d===null)throw new Error("getQueryState() called before stream finished");return d}}}export{X as renderSsrTreeStream};//# sourceMappingURL=render-5AGW5SQM.js.map
2
+ //# sourceMappingURL=render-5AGW5SQM.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ssr/render.tsx"],"names":["sessionResolver","ctx","getSsrContext","_parseSessionUser","configResolver","key","ensureSsrResolversInstalled","_setSsrSessionResolver","_setSsrConfigResolver","renderSsrTreeStream","options","callContext","loadingElement","routesElement","router","location","onShellReady","onError","sessionPayload","callInProcessMethod","queryClient","QueryClient","routedTree","tree","jsx","AppProvider","ModelenceQueryProvider","streamRef","resolve","reject","runWithSsrContext","stream","renderToPipeableStream","error","queryStateJson","pipe","destination","passthrough","Writable","chunk","_encoding","callback","err","dehydratedState","dehydrate"],"mappings":"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","file":"render-OM7ZJWZA.js","sourcesContent":["import React from 'react';\nimport { renderToPipeableStream, type PipeableStream } from 'react-dom/server';\nimport { Writable } from 'node:stream';\nimport { QueryClient, dehydrate, type DehydratedState } from '@tanstack/react-query';\nimport { AppProvider } from '../client/AppProvider';\nimport { ModelenceQueryProvider } from '../client/queryProvider';\nimport { getSsrContext, runWithSsrContext } from './context';\nimport { callInProcessMethod } from './callInProcess';\nimport type { Context } from '../methods/types';\nimport {\n _parseSessionUser,\n _setSsrSessionResolver,\n type SessionInitPayload,\n} from '../client/session';\nimport { _setSsrConfigResolver } from '../config/client';\nimport type { ConfigKey, Configs } from '../config/types';\nimport type { SsrRouter } from '../client/renderApp';\n\nconst sessionResolver = () => {\n const ctx = getSsrContext();\n if (!ctx) {\n return null;\n }\n return _parseSessionUser(ctx.session.user);\n};\n\nconst configResolver = (key: ConfigKey) => {\n const ctx = getSsrContext();\n if (!ctx) {\n return undefined;\n }\n return ctx.session.configs[key]?.value;\n};\n\nfunction ensureSsrResolversInstalled() {\n _setSsrSessionResolver(sessionResolver);\n _setSsrConfigResolver(configResolver);\n}\n\nexport type SsrRenderOptions = {\n callContext: Context;\n loadingElement: React.ReactNode;\n routesElement: React.ReactNode;\n router?: SsrRouter;\n location?: string;\n};\n\nexport type SsrStreamHandle = {\n /** Session bootstrap payload — inline before the shell flushes. */\n sessionState: string;\n /** Pipe React's HTML into `destination`. Resolves when streaming finishes. */\n pipe: (destination: Writable) => Promise<void>;\n /** Dehydrated query state. Only call after `pipe()` resolves. */\n getQueryState: () => string;\n};\n\nexport type SsrStreamOptions = SsrRenderOptions & {\n /** Fires when the shell is flushed; caller writes prelude + state here. */\n onShellReady?: () => void;\n /** Non-fatal SSR errors (Suspense fallbacks, etc.). */\n onError?: (error: unknown) => void;\n};\n\nexport async function renderSsrTreeStream(options: SsrStreamOptions): Promise<SsrStreamHandle> {\n const { callContext, loadingElement, routesElement, router, location, onShellReady, onError } =\n options;\n\n ensureSsrResolversInstalled();\n\n const sessionPayload = await callInProcessMethod<SessionInitPayload>(\n '_system.session.init',\n {},\n callContext\n );\n\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n retry: false,\n gcTime: 0,\n },\n },\n });\n\n const routedTree = router ? router({ children: routesElement, location }) : routesElement;\n const tree = (\n <AppProvider loadingElement={loadingElement}>\n <ModelenceQueryProvider client={queryClient}>{routedTree}</ModelenceQueryProvider>\n </AppProvider>\n );\n\n let streamRef: PipeableStream | null = null;\n // Resolves with the stream once React renders above-fallback content;\n // rejects on shell errors so the caller can fall back to a static response.\n const shellReady = new Promise<PipeableStream>((resolve, reject) => {\n // Run the render inside the SSR context so components can resolve\n // session/config/query state from the per-request scope.\n runWithSsrContext(\n {\n callContext,\n queryClient,\n session: {\n user: sessionPayload.user,\n configs: (sessionPayload.configs as Configs) ?? {},\n },\n },\n () => {\n const stream = renderToPipeableStream(tree, {\n onShellReady() {\n streamRef = stream;\n onShellReady?.();\n resolve(stream);\n },\n onShellError(error) {\n reject(error);\n },\n onError(error) {\n onError?.(error);\n },\n });\n return stream;\n }\n );\n });\n\n // Await so shell errors surface before the caller starts piping.\n await shellReady;\n\n let queryStateJson: string | null = null;\n\n const pipe = (destination: Writable): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (!streamRef) {\n reject(new Error('SSR stream was not initialized'));\n return;\n }\n\n // react-dom calls `destination.end()` when done. The caller still needs\n // to write the epilogue + query state, so wrap with a passthrough whose\n // `final()` resolves the pipe promise without closing the response.\n const passthrough = new Writable({\n write(chunk, _encoding, callback) {\n destination.write(chunk, (err) => callback(err ?? undefined));\n },\n final(callback) {\n try {\n const dehydratedState: DehydratedState = dehydrate(queryClient);\n queryStateJson = JSON.stringify(dehydratedState);\n } finally {\n queryClient.clear();\n }\n callback();\n resolve();\n },\n });\n\n passthrough.on('error', (err) => {\n destination.destroy(err);\n reject(err);\n });\n destination.on('error', reject);\n\n streamRef.pipe(passthrough);\n });\n };\n\n return {\n sessionState: JSON.stringify({ session: sessionPayload }),\n pipe,\n getQueryState: () => {\n if (queryStateJson === null) {\n throw new Error('getQueryState() called before stream finished');\n }\n return queryStateJson;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/ssr/render.tsx"],"names":["sessionResolver","ctx","getSsrContext","_parseSessionUser","configResolver","key","ensureSsrResolversInstalled","_setSsrSessionResolver","_setSsrConfigResolver","renderSsrTreeStream","options","callContext","loadingElement","routesElement","router","location","onShellReady","onError","sessionPayload","callInProcessMethod","queryClient","QueryClient","routedTree","tree","jsx","AppProvider","ModelenceQueryProvider","streamRef","resolve","reject","runWithSsrContext","stream","renderToPipeableStream","error","queryStateJson","pipe","destination","passthrough","Writable","chunk","_encoding","callback","err","dehydratedState","dehydrate"],"mappings":"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","file":"render-5AGW5SQM.js","sourcesContent":["import React from 'react';\nimport { renderToPipeableStream, type PipeableStream } from 'react-dom/server';\nimport { Writable } from 'node:stream';\nimport { QueryClient, dehydrate, type DehydratedState } from '@tanstack/react-query';\nimport { AppProvider } from '../client/AppProvider';\nimport { ModelenceQueryProvider } from '../client/queryProvider';\nimport { getSsrContext, runWithSsrContext } from './context';\nimport { callInProcessMethod } from './callInProcess';\nimport type { Context } from '../methods/types';\nimport {\n _parseSessionUser,\n _setSsrSessionResolver,\n type SessionInitPayload,\n} from '../client/session';\nimport { _setSsrConfigResolver } from '../config/client';\nimport type { ConfigKey, Configs } from '../config/types';\nimport type { SsrRouter } from '../client/renderApp';\n\nconst sessionResolver = () => {\n const ctx = getSsrContext();\n if (!ctx) {\n return null;\n }\n return _parseSessionUser(ctx.session.user);\n};\n\nconst configResolver = (key: ConfigKey) => {\n const ctx = getSsrContext();\n if (!ctx) {\n return undefined;\n }\n return ctx.session.configs[key]?.value;\n};\n\nfunction ensureSsrResolversInstalled() {\n _setSsrSessionResolver(sessionResolver);\n _setSsrConfigResolver(configResolver);\n}\n\nexport type SsrRenderOptions = {\n callContext: Context;\n loadingElement: React.ReactNode;\n routesElement: React.ReactNode;\n router?: SsrRouter;\n location?: string;\n};\n\nexport type SsrStreamHandle = {\n /** Session bootstrap payload — inline before the shell flushes. */\n sessionState: string;\n /** Pipe React's HTML into `destination`. Resolves when streaming finishes. */\n pipe: (destination: Writable) => Promise<void>;\n /** Dehydrated query state. Only call after `pipe()` resolves. */\n getQueryState: () => string;\n};\n\nexport type SsrStreamOptions = SsrRenderOptions & {\n /** Fires when the shell is flushed; caller writes prelude + state here. */\n onShellReady?: () => void;\n /** Non-fatal SSR errors (Suspense fallbacks, etc.). */\n onError?: (error: unknown) => void;\n};\n\nexport async function renderSsrTreeStream(options: SsrStreamOptions): Promise<SsrStreamHandle> {\n const { callContext, loadingElement, routesElement, router, location, onShellReady, onError } =\n options;\n\n ensureSsrResolversInstalled();\n\n const sessionPayload = await callInProcessMethod<SessionInitPayload>(\n '_system.session.init',\n {},\n callContext\n );\n\n const queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n retry: false,\n gcTime: 0,\n },\n },\n });\n\n const routedTree = router ? router({ children: routesElement, location }) : routesElement;\n const tree = (\n <AppProvider loadingElement={loadingElement}>\n <ModelenceQueryProvider client={queryClient}>{routedTree}</ModelenceQueryProvider>\n </AppProvider>\n );\n\n let streamRef: PipeableStream | null = null;\n // Resolves with the stream once React renders above-fallback content;\n // rejects on shell errors so the caller can fall back to a static response.\n const shellReady = new Promise<PipeableStream>((resolve, reject) => {\n // Run the render inside the SSR context so components can resolve\n // session/config/query state from the per-request scope.\n runWithSsrContext(\n {\n callContext,\n queryClient,\n session: {\n user: sessionPayload.user,\n configs: (sessionPayload.configs as Configs) ?? {},\n },\n },\n () => {\n const stream = renderToPipeableStream(tree, {\n onShellReady() {\n streamRef = stream;\n onShellReady?.();\n resolve(stream);\n },\n onShellError(error) {\n reject(error);\n },\n onError(error) {\n onError?.(error);\n },\n });\n return stream;\n }\n );\n });\n\n // Await so shell errors surface before the caller starts piping.\n await shellReady;\n\n let queryStateJson: string | null = null;\n\n const pipe = (destination: Writable): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (!streamRef) {\n reject(new Error('SSR stream was not initialized'));\n return;\n }\n\n // react-dom calls `destination.end()` when done. The caller still needs\n // to write the epilogue + query state, so wrap with a passthrough whose\n // `final()` resolves the pipe promise without closing the response.\n const passthrough = new Writable({\n write(chunk, _encoding, callback) {\n destination.write(chunk, (err) => callback(err ?? undefined));\n },\n final(callback) {\n try {\n const dehydratedState: DehydratedState = dehydrate(queryClient);\n queryStateJson = JSON.stringify(dehydratedState);\n } finally {\n queryClient.clear();\n }\n callback();\n resolve();\n },\n });\n\n passthrough.on('error', (err) => {\n destination.destroy(err);\n reject(err);\n });\n destination.on('error', reject);\n\n streamRef.pipe(passthrough);\n });\n };\n\n return {\n sessionState: JSON.stringify({ session: sessionPayload }),\n pipe,\n getQueryState: () => {\n if (queryStateJson === null) {\n throw new Error('getQueryState() called before stream finished');\n }\n return queryStateJson;\n },\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export{c as _getSsrSnapshot,d as renderApp}from'./chunk-ZVA536ET.js';import'./chunk-ZI6PQXEB.js';import'./chunk-FBYHAMXI.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=renderApp-3C3JFOHW.js.map
2
+ //# sourceMappingURL=renderApp-3C3JFOHW.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"renderApp-MD5O44UP.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"renderApp-3C3JFOHW.js"}
@@ -0,0 +1,2 @@
1
+ export{g as getCallContext,f as startServer}from'./chunk-E3L5INVU.js';import'./chunk-RZ5A2YM2.js';import'./chunk-63RBLFRJ.js';import'./chunk-VYR7VQMQ.js';import'./chunk-UW37F3GV.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=server-TU6GA634.js.map
2
+ //# sourceMappingURL=server-TU6GA634.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"server-GHQ5XNNQ.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"server-TU6GA634.js"}
package/dist/server.d.ts CHANGED
@@ -228,6 +228,8 @@ type AuthRateLimitsConfig = {
228
228
  magicLink?: AuthRateLimitOverride[];
229
229
  /** Rate limits for one-time code sign-in attempts. */
230
230
  oneTimeCode?: AuthRateLimitOverride[];
231
+ /** Rate limits for redeeming a mobile OAuth exchange code. */
232
+ oauthExchange?: AuthRateLimitOverride[];
231
233
  /** Per-user rate limits for profile updates. */
232
234
  updateProfile?: AuthRateLimitOverride[];
233
235
  };
@@ -431,8 +433,10 @@ type AuthConfig = {
431
433
  * Custom handle generator. If provided, overrides the default behavior
432
434
  * (which derives the handle from the email local-part). Receives
433
435
  * `{ email, firstName?, lastName? }` and returns the desired handle
434
- * synchronously or as a `Promise<string>`. If the returned handle collides
435
- * with an existing one, Modelence appends a numeric suffix automatically.
436
+ * synchronously or as a `Promise<string>`. The returned handle must contain
437
+ * only letters, numbers, underscores, and hyphens (matching `HANDLE_REGEX`).
438
+ * If the returned handle collides with an existing one, Modelence appends
439
+ * a numeric suffix automatically.
436
440
  */
437
441
  generateHandle?: (props: GenerateHandleProps) => Promise<string> | string;
438
442
  /** @deprecated Use {@link AuthConfig.onAfterLogin} and {@link AuthConfig.onLoginError} instead. */
@@ -501,6 +505,35 @@ type AuthConfig = {
501
505
  * means disposable emails will be allowed to sign up.
502
506
  */
503
507
  allowDisposableEmails?: boolean;
508
+ /**
509
+ * Settings for authenticating from a native (React Native / Expo) client.
510
+ *
511
+ * @example
512
+ * ```typescript
513
+ * startApp({
514
+ * auth: {
515
+ * mobile: { redirectUrls: ['myapp://auth'] },
516
+ * },
517
+ * });
518
+ * ```
519
+ */
520
+ mobile?: {
521
+ /**
522
+ * Deep links the OAuth callback is allowed to redirect a native app back to,
523
+ * e.g. `['myapp://auth']`. A sign-in request naming any other target is
524
+ * rejected before the user ever reaches the provider's consent screen.
525
+ *
526
+ * This is an allowlist because the redirect target decides where an auth
527
+ * flow ends up: without it, a crafted link could point the callback at an
528
+ * attacker-controlled destination. There is no implicit default — mobile
529
+ * OAuth stays disabled until at least one entry is configured here or via
530
+ * the `auth.mobile.redirectUrls` config value (the two are merged).
531
+ *
532
+ * Entries are matched on scheme, host and path; a request may add query
533
+ * parameters but may not change any of those three.
534
+ */
535
+ redirectUrls?: string[];
536
+ };
504
537
  };
505
538
 
506
539
  /**
@@ -518,6 +551,8 @@ type AuthConfig = {
518
551
  * startApp({
519
552
  * security: {
520
553
  * frameAncestors: ['https://modelence.com', 'https://app.example.com'],
554
+ * trustedProxies: ['loopback', 'linklocal', 'uniquelocal'],
555
+ * clientIpHeader: 'cf-connecting-ip',
521
556
  * },
522
557
  * });
523
558
  * ```
@@ -531,6 +566,50 @@ type SecurityConfig = {
531
566
  * When set, `X-Frame-Options` is omitted since it cannot express multiple origins.
532
567
  */
533
568
  frameAncestors?: string[];
569
+ /**
570
+ * IP addresses or CIDR ranges of reverse proxies that are allowed to supply
571
+ * the client IP through `X-Forwarded-For`. This uses Express's `trust proxy`
572
+ * address syntax, which also supports the named ranges `loopback`,
573
+ * `linklocal`, and `uniquelocal`.
574
+ *
575
+ * For backward compatibility, all proxy addresses are trusted when neither
576
+ * this option nor `MODELENCE_TRUSTED_PROXIES` is set. Configure one of them in
577
+ * production so only addresses that cannot be reached directly by untrusted
578
+ * clients are trusted. Once configured,
579
+ * `connectionInfo.ip` is resolved by walking the proxy chain from the app
580
+ * toward the client and stopping at the first untrusted address. This keeps a
581
+ * caller from choosing its rate-limit identity by prepending a forged
582
+ * `X-Forwarded-For` value.
583
+ *
584
+ * @example
585
+ * ```typescript
586
+ * trustedProxies: ['loopback', '10.0.0.0/8']
587
+ * ```
588
+ */
589
+ trustedProxies?: string | string[];
590
+ /**
591
+ * Name of a single-value header the trusted proxy sets to the originating
592
+ * client IP, used instead of walking the `X-Forwarded-For` chain.
593
+ *
594
+ * Cloudflare recommends reading `CF-Connecting-IP` (or `True-Client-IP` on
595
+ * Enterprise plans) rather than `X-Forwarded-For`, because Cloudflare
596
+ * *appends* to an inbound `X-Forwarded-For` instead of overwriting it, while
597
+ * these headers always carry exactly one address.
598
+ *
599
+ * This header is only read when the immediate peer is a trusted proxy, so
600
+ * `trustedProxies` (or `MODELENCE_TRUSTED_PROXIES`) must also be configured
601
+ * with the proxy's addresses. Without that, a direct caller could set the
602
+ * header themselves and choose their own rate-limit identity. When the peer
603
+ * is untrusted or the header is absent, the IP falls back to the normal
604
+ * `trust proxy` resolution.
605
+ *
606
+ * @example
607
+ * ```typescript
608
+ * // Behind Cloudflare, with Cloudflare's published ranges trusted:
609
+ * clientIpHeader: 'cf-connecting-ip'
610
+ * ```
611
+ */
612
+ clientIpHeader?: string;
534
613
  };
535
614
 
536
615
  type WebsocketConfig = {
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- export{m as ObjectId,k as ServerChannel,a as consumeRateLimit,c as deleteFile,j as deleteUser,i as disableUser,d as downloadFile,e as getFileUrl,b as getUploadUrl,l as sendEmail,h as startApp}from'./chunk-LCXJNLNK.js';import'./chunk-KFTT3M45.js';export{y as LiveData,a as Module,c as Store,x as authenticate,k as clearSessionUser,A as createQuery,m as createSession,h as dbSessions,q as dbUsers,l as invalidateAllUserSessions,i as obtainSession,d as schema,n as setAuthTokenCookie,j as setSessionUser}from'./chunk-KDOTVIEY.js';import'./chunk-VYR7VQMQ.js';export{a as getConfig,h as getEnvironmentId}from'./chunk-UW37F3GV.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=server.js.map
1
+ export{m as ObjectId,k as ServerChannel,a as consumeRateLimit,c as deleteFile,j as deleteUser,i as disableUser,d as downloadFile,e as getFileUrl,b as getUploadUrl,l as sendEmail,h as startApp}from'./chunk-E3L5INVU.js';import'./chunk-RZ5A2YM2.js';export{A as LiveData,a as Module,c as Store,z as authenticate,m as clearSessionUser,C as createQuery,o as createSession,j as dbSessions,s as dbUsers,n as invalidateAllUserSessions,k as obtainSession,d as schema,p as setAuthTokenCookie,l as setSessionUser}from'./chunk-63RBLFRJ.js';import'./chunk-VYR7VQMQ.js';export{a as getConfig,h as getEnvironmentId}from'./chunk-UW37F3GV.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';//# sourceMappingURL=server.js.map
2
2
  //# sourceMappingURL=server.js.map
@@ -0,0 +1,2 @@
1
+ import {c,a}from'./chunk-NPBIRXLC.js';import'./chunk-63RBLFRJ.js';import'./chunk-UW37F3GV.js';import {J,I}from'./chunk-FBYHAMXI.js';import'./chunk-5M6FUMUK.js';import'./chunk-DO5TZLF5.js';function d(){return J(async(t,r)=>{let o=c();return o?a(t,r,o.callContext):I(t,r)})}export{d as installSsrCallMethodTransport};//# sourceMappingURL=transport-FTLINZKK.js.map
2
+ //# sourceMappingURL=transport-FTLINZKK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ssr/transport.ts"],"names":["installSsrCallMethodTransport","setCallMethodTransport","methodName","args","ssrCtx","getSsrContext","callInProcessMethod","defaultCallMethodTransport"],"mappings":"4LAcO,SAASA,CAAAA,EAA4C,CAC1D,OAAOC,CAAAA,CAAuB,MAAUC,EAAoBC,CAAAA,GAAqB,CAC/E,IAAMC,CAAAA,CAASC,CAAAA,EAAc,CAC7B,OAAKD,CAAAA,CAIEE,CAAAA,CAAuBJ,CAAAA,CAAYC,CAAAA,CAAMC,CAAAA,CAAO,WAAW,CAAA,CAHzDG,CAAAA,CAA8BL,CAAAA,CAAYC,CAAI,CAIzD,CAAC,CACH","file":"transport-VKGH6BY6.js","sourcesContent":["import {\n setCallMethodTransport,\n defaultCallMethodTransport,\n type MethodArgs,\n} from '../client/method';\nimport { callInProcessMethod } from './callInProcess';\nimport { getSsrContext } from './context';\n\n/**\n * Routes `callMethod` through `runMethod` in-process during an active SSR\n * render. Outside a render (e.g. server-side `callMethod` from jobs or other\n * non-render code) there is no request context, so it falls back to the\n * default HTTP transport — installing SSR must not break those call sites.\n */\nexport function installSsrCallMethodTransport(): () => void {\n return setCallMethodTransport(async <T>(methodName: string, args: MethodArgs) => {\n const ssrCtx = getSsrContext();\n if (!ssrCtx) {\n return defaultCallMethodTransport<T>(methodName, args);\n }\n\n return callInProcessMethod<T>(methodName, args, ssrCtx.callContext);\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/ssr/transport.ts"],"names":["installSsrCallMethodTransport","setCallMethodTransport","methodName","args","ssrCtx","getSsrContext","callInProcessMethod","defaultCallMethodTransport"],"mappings":"4LAcO,SAASA,CAAAA,EAA4C,CAC1D,OAAOC,CAAAA,CAAuB,MAAUC,EAAoBC,CAAAA,GAAqB,CAC/E,IAAMC,CAAAA,CAASC,CAAAA,EAAc,CAC7B,OAAKD,CAAAA,CAIEE,CAAAA,CAAuBJ,CAAAA,CAAYC,CAAAA,CAAMC,CAAAA,CAAO,WAAW,CAAA,CAHzDG,CAAAA,CAA8BL,CAAAA,CAAYC,CAAI,CAIzD,CAAC,CACH","file":"transport-FTLINZKK.js","sourcesContent":["import {\n setCallMethodTransport,\n defaultCallMethodTransport,\n type MethodArgs,\n} from '../client/method';\nimport { callInProcessMethod } from './callInProcess';\nimport { getSsrContext } from './context';\n\n/**\n * Routes `callMethod` through `runMethod` in-process during an active SSR\n * render. Outside a render (e.g. server-side `callMethod` from jobs or other\n * non-render code) there is no request context, so it falls back to the\n * default HTTP transport — installing SSR must not break those call sites.\n */\nexport function installSsrCallMethodTransport(): () => void {\n return setCallMethodTransport(async <T>(methodName: string, args: MethodArgs) => {\n const ssrCtx = getSsrContext();\n if (!ssrCtx) {\n return defaultCallMethodTransport<T>(methodName, args);\n }\n\n return callInProcessMethod<T>(methodName, args, ssrCtx.callContext);\n });\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "modelence",
4
- "version": "0.23.1",
4
+ "version": "0.24.0-dev.0",
5
5
  "description": "The Node.js Framework for Real-Time MongoDB Apps",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/global.d.ts",
@@ -1,2 +0,0 @@
1
- import {a,d,g as g$1,n}from'./chunk-WDY7BYXR.js';import {I as I$1,b,E,g,k}from'./chunk-W4RFIS5E.js';import R from'react';import C from'react-dom/client';import {jsx}from'react/jsx-runtime';function c(t){return {getConfig(e){return b(`${t}.${e}`)},query(e,...r){let o=r[0]??{};return {queryKey:[t,e,o],queryFn:()=>I$1(`${t}.${e}`,o)}},mutation(e){return {mutationFn:r=>I$1(`${t}.${e}`,r)}},infiniteQuery(e,r){return {queryKey:[t,e,"infinite",r(void 0)],initialPageParam:void 0,queryFn:({pageParam:o})=>I$1(`${t}.${e}`,r(o))}}}}var q=c("_system");var u=class{constructor(e,r){this.category=e,this.onMessage=r;}init(){d()?.on({category:this.category,listener:this.onMessage});}joinChannel(e){d()?.joinChannel({category:this.category,id:e});}leaveChannel(e){d()?.leaveChannel({category:this.category,id:e});}};var A="useClient"in R?R.useClient(a):a;var K="__MODELENCE_STATE__";function W(){return typeof document>"u"?false:document.getElementById(K)!==null}function H(){if(typeof document>"u")return null;let t=document.getElementById(K);if(!t)return null;try{return JSON.parse(t.textContent??"")}catch(e){return console.error("Modelence: failed to parse SSR state",e),null}}var v="__modelence_ssr_snapshot__";function I(t){globalThis[v]=t;}function je(){return globalThis[v]??null}function w(t){if(typeof window>"u"){I(t);return}let{loadingElement:e,routesElement:r,favicon:o,errorHandler:f,router:m,setupElement:k$1}=t;f&&E(f),window.addEventListener("unload",()=>{});let b=W(),y=H();y?.session&&(g(y.session),k());let g$2=document.getElementById("root"),O=window.location.pathname+window.location.search,h=m?m({children:r,location:O}):r,_=g$1()?h:jsx(n,{children:h}),M=jsx(R.StrictMode,{children:jsx(A,{loadingElement:e,setupElement:k$1,children:_})});if(b?C.hydrateRoot(g$2,M):C.createRoot(g$2).render(M),o){let T=document.querySelector("link[rel~='icon']");if(T)T.href=o;else {let s=document.createElement("link");s.rel="icon",s.href=o,document.head.appendChild(s);}}}export{c as a,q as b,je as c,w as d,u as e,A as f};//# sourceMappingURL=chunk-4I4BI7KD.js.map
2
- //# sourceMappingURL=chunk-4I4BI7KD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/client/module.ts","../src/system/client.ts","../src/websocket/clientChannel.ts","../src/client.ts","../src/client/renderApp.tsx"],"names":["createClientModule","moduleName","key","getConfig","name","rest","args","callMethod","getArgs","pageParam","systemConfig","ClientChannel","category","onMessage","getWebsocketClientProvider","id","AppProvider","React","SSR_STATE_SCRIPT_ID","hasSsrMarker","readSsrState","node","SNAPSHOT_KEY","setSnapshot","snapshot","_getSsrSnapshot","renderApp","options","loadingElement","routesElement","favicon","errorHandler","router","setupElement","setErrorHandler","isHydrating","ssrState","hydrateSession","startSessionHeartbeat","container","location","routedTree","appTree","hasConnectedQueryClient","jsx","ModelenceQueryProvider","tree","ReactDOM","link","newLink"],"mappings":"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","file":"chunk-4I4BI7KD.js","sourcesContent":["'use client';\n\nimport type { ObjectId } from 'mongodb';\nimport type { ConfigParams, ConfigType, ValueType } from '../config/types';\nimport { callMethod, type MethodArgs } from './method';\nimport type { AnyMethodShape } from '../methods/types';\n\n// Pulls the config store value without importing server-side code\nimport { getConfig as _getClientConfig } from '../config/client';\n\n// ── type helpers ─────────────────────────────────────────────────────────────\n\n/**\n * Recursively maps ObjectId → string to match the sanitized runtime values\n * sent over the wire. Dates are preserved (revived via typeMap on the client).\n */\ntype Sanitized<T> = T extends ObjectId\n ? string\n : T extends Date\n ? Date\n : T extends (infer U)[]\n ? Sanitized<U>[]\n : T extends object\n ? { [K in keyof T]: Sanitized<T[K]> }\n : T;\n\ntype ExtractArgs<M> = M extends (args: infer A, ...rest: any[]) => any // eslint-disable-line @typescript-eslint/no-explicit-any\n ? A\n : M extends { handler: (args: infer A, ...rest: any[]) => any } // eslint-disable-line @typescript-eslint/no-explicit-any\n ? A\n : MethodArgs;\n\ntype ExtractResult<M> = M extends (...args: any[]) => Promise<infer R> // eslint-disable-line @typescript-eslint/no-explicit-any\n ? Sanitized<R>\n : M extends { handler: (...args: any[]) => Promise<infer R> } // eslint-disable-line @typescript-eslint/no-explicit-any\n ? Sanitized<R>\n : unknown;\n\ntype PublicKeyOf<TSchema extends Record<string, ConfigParams>> = {\n [K in keyof TSchema as TSchema[K] extends ConfigParams<ConfigType, true>\n ? string & K\n : never]: ValueType<TSchema[K]['type']>;\n};\n\ntype AnyModule = {\n name: string;\n configSchema: Record<string, ConfigParams>;\n queries: Record<string, AnyMethodShape>;\n mutations: Record<string, AnyMethodShape>;\n};\n\n// ── createClientModule ────────────────────────────────────────────────────────\n\n/**\n * Creates a typed client accessor for a module's public configs, queries, and mutations.\n *\n * Use `import type` to reference the module so no server code is bundled on the client.\n * Arg and return types for queries and mutations are inferred automatically from the\n * server-side handler signatures.\n *\n * @param moduleName - The module's name as passed to `new Module(name, ...)`.\n *\n * @example\n * ```ts\n * // src/client/payments.ts\n * import type paymentsModule from '../server/payments';\n * import { createClientModule } from 'modelence/client';\n *\n * export const payments = createClientModule<typeof paymentsModule>('payments');\n * ```\n *\n * ```ts\n * // src/components/Checkout.tsx\n * import { useQuery, useMutation } from '@tanstack/react-query';\n * import { payments } from '../client/payments';\n *\n * // Typed config — public keys only, private and secret keys excluded:\n * const currency = payments.getConfig('currency'); // string | undefined\n *\n * // Typed query — pass directly to useQuery:\n * const { data: products } = useQuery(payments.query('getProducts', { page: 1 }));\n *\n * // Typed mutation — pass directly to useMutation:\n * const { mutate: charge } = useMutation(payments.mutation('charge'));\n * charge({ amount: 100 }); // args typed from handler signature\n * ```\n */\nexport function createClientModule<TModule extends AnyModule>(moduleName: string) {\n return {\n getConfig<K extends keyof PublicKeyOf<TModule['configSchema']> & string>(\n key: K\n ): PublicKeyOf<TModule['configSchema']>[K] | undefined {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return _getClientConfig(`${moduleName}.${key}`) as any;\n },\n\n query<K extends keyof TModule['queries'] & string>(\n name: K,\n ...rest: {} extends ExtractArgs<TModule['queries'][K]>\n ? [args?: ExtractArgs<TModule['queries'][K]>]\n : [args: ExtractArgs<TModule['queries'][K]>]\n ) {\n const args = (rest[0] ?? {}) as ExtractArgs<TModule['queries'][K]>;\n return {\n queryKey: [moduleName, name, args] as const,\n queryFn: (): Promise<ExtractResult<TModule['queries'][K]>> =>\n callMethod<ExtractResult<TModule['queries'][K]>>(\n `${moduleName}.${name}`,\n args as MethodArgs\n ),\n };\n },\n\n mutation<K extends keyof TModule['mutations'] & string>(name: K) {\n return {\n mutationFn: (\n args: ExtractArgs<TModule['mutations'][K]>\n ): Promise<ExtractResult<TModule['mutations'][K]>> =>\n callMethod<ExtractResult<TModule['mutations'][K]>>(\n `${moduleName}.${name}`,\n args as MethodArgs\n ),\n };\n },\n\n /**\n * Returns options for `useInfiniteQuery`. The `getArgs` callback receives the\n * current `pageParam` and returns the args to pass to the query handler.\n * Spread the result into `useInfiniteQuery` alongside `getNextPageParam`.\n *\n * Annotate the `pageParam` type in the callback so TypeScript can infer the\n * page param type — no manual generic needed on `useInfiniteQuery`.\n */\n infiniteQuery<K extends keyof TModule['queries'] & string, TPageParam = unknown>(\n name: K,\n getArgs: (pageParam: TPageParam | undefined) => ExtractArgs<TModule['queries'][K]>\n ) {\n return {\n queryKey: [moduleName, name, 'infinite', getArgs(undefined)] as const,\n // Included so TanStack infers TPageParam from the callback type, not from a bare `undefined`.\n initialPageParam: undefined as TPageParam | undefined,\n queryFn: ({\n pageParam,\n }: {\n pageParam: TPageParam | undefined;\n }): Promise<ExtractResult<TModule['queries'][K]>> =>\n callMethod<ExtractResult<TModule['queries'][K]>>(\n `${moduleName}.${name}`,\n getArgs(pageParam) as MethodArgs\n ),\n };\n },\n };\n}\n","import type systemModule from './index';\nimport { createClientModule } from '../client/module';\n\nexport const systemConfig = createClientModule<typeof systemModule>('_system');\n","import { getWebsocketClientProvider } from './client';\n\nexport class ClientChannel<T = unknown> {\n public readonly category: string;\n private readonly onMessage: (data: T) => void;\n\n constructor(category: string, onMessage: (data: T) => void) {\n this.category = category;\n this.onMessage = onMessage;\n }\n\n init() {\n getWebsocketClientProvider()?.on({\n category: this.category,\n listener: this.onMessage,\n });\n }\n\n joinChannel(id: string) {\n getWebsocketClientProvider()?.joinChannel({\n category: this.category,\n id,\n });\n }\n\n leaveChannel(id: string) {\n getWebsocketClientProvider()?.leaveChannel({\n category: this.category,\n id,\n });\n }\n}\n","import React from 'react';\n\nimport { AppProvider as OriginalAppProvider } from './client/AppProvider';\n\nexport { configureClient, type ClientConfig } from './client/clientConfig';\nexport { getConfig } from './config/client';\nexport { createClientModule } from './client/module';\nexport type { ValueType } from './config/types';\nexport { systemConfig } from './system/client';\n\nexport const AppProvider =\n 'useClient' in React\n ? // @ts-ignore: React.useClient only exists in Next.js\n React.useClient(OriginalAppProvider)\n : OriginalAppProvider;\n\nexport { renderApp } from './client/renderApp';\nexport { ModelenceQueryProvider } from './client/queryProvider';\nexport {\n modelenceQuery,\n modelenceLiveQuery,\n modelenceMutation,\n createQueryKey,\n connectModelenceQueryClient,\n disconnectModelenceQueryClient,\n ModelenceQueryClient,\n type ModelenceQueryKey,\n} from './client/query';\nexport { callMethod, MethodError, type MethodArgs, type CallMethodOptions } from './client/method';\nexport { useSession } from './client/session';\nexport {\n signupWithPassword,\n loginWithPassword,\n verifyEmail,\n updateProfile,\n resendEmailVerification,\n logout,\n sendResetPasswordToken,\n resetPassword,\n sendMagicLink,\n loginWithMagicLink,\n loginWithOneTimeCode,\n linkOAuthProvider,\n unlinkOAuthProvider,\n type UserInfo,\n} from './auth/client';\nexport {\n getWebsocketClientProvider,\n setWebsocketClientProvider,\n startWebsockets,\n subscribeLiveQuery,\n} from './websocket/client';\nexport { ClientChannel } from './websocket/clientChannel';\nexport { getLocalStorageSession } from './client/localStorage';\n","import React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { AppProvider } from '../client';\nimport { setErrorHandler, ErrorHandler } from './errorHandler';\nimport { hydrateSession, startSessionHeartbeat, type SessionInitPayload } from './session';\nimport { ModelenceQueryProvider } from './queryProvider';\nimport { hasConnectedQueryClient } from './query';\n\nconst SSR_STATE_SCRIPT_ID = '__MODELENCE_STATE__';\n\ntype SsrState = {\n session?: SessionInitPayload;\n};\n\nfunction hasSsrMarker(): boolean {\n if (typeof document === 'undefined') {\n return false;\n }\n return document.getElementById(SSR_STATE_SCRIPT_ID) !== null;\n}\n\nfunction readSsrState(): SsrState | null {\n if (typeof document === 'undefined') {\n return null;\n }\n\n const node = document.getElementById(SSR_STATE_SCRIPT_ID);\n if (!node) {\n return null;\n }\n\n try {\n return JSON.parse(node.textContent ?? '') as SsrState;\n } catch (e) {\n // Caller must still hydrate (marker presence drives that, not parsed payload).\n console.error('Modelence: failed to parse SSR state', e);\n return null;\n }\n}\n\nexport type SsrRouter = (props: {\n children: React.ReactNode;\n location?: string;\n}) => React.ReactElement;\n\nexport interface RenderAppOptions {\n loadingElement: React.ReactNode;\n routesElement: React.ReactNode;\n favicon?: string;\n errorHandler?: ErrorHandler;\n router?: SsrRouter;\n /*\n Replaces the built-in setup screen shown when a development server has no\n backend yet (no Modelence Cloud connection, no local database). Pass\n `null` to disable the screen entirely.\n */\n setupElement?: React.ReactNode;\n}\n\n// Shared via globalThis: ssrLoadModule loads the user's entry in a separate\n// module graph from the framework runtime.\nconst SNAPSHOT_KEY = '__modelence_ssr_snapshot__';\n\ntype GlobalWithSnapshot = typeof globalThis & {\n [SNAPSHOT_KEY]?: RenderAppOptions | null;\n};\n\nfunction setSnapshot(snapshot: RenderAppOptions | null) {\n (globalThis as GlobalWithSnapshot)[SNAPSHOT_KEY] = snapshot;\n}\n\n/** @internal Used by the SSR runtime after evaluating the user's entry. */\nexport function _getSsrSnapshot(): RenderAppOptions | null {\n return (globalThis as GlobalWithSnapshot)[SNAPSHOT_KEY] ?? null;\n}\n\nexport function renderApp(options: RenderAppOptions) {\n if (typeof window === 'undefined') {\n setSnapshot(options);\n return;\n }\n\n const { loadingElement, routesElement, favicon, errorHandler, router, setupElement } = options;\n\n if (errorHandler) {\n setErrorHandler(errorHandler);\n }\n\n // Empty 'unload' handler prevents bfcache in most browsers.\n window.addEventListener('unload', () => {});\n\n // Hydrate session BEFORE building the tree so `isSessionInitialized()` is\n // true on the first render and matches the server output. Hydration mode\n // tracks marker presence (not parse success): a parse failure still leaves\n // server-rendered DOM that must be hydrated, not replaced.\n const isHydrating = hasSsrMarker();\n const ssrState = readSsrState();\n if (ssrState?.session) {\n hydrateSession(ssrState.session);\n // Fire-and-forget: the heartbeat loop runs in the background.\n void startSessionHeartbeat();\n }\n\n const container = document.getElementById('root')!;\n // Pass the same location the server used (req.originalUrl == path + search;\n // the hash is never sent to the server) so a location-driven router (e.g. a\n // static router) resolves the same route on hydration as it did during SSR,\n // avoiding hydration mismatches.\n const location = window.location.pathname + window.location.search;\n const routedTree = router ? router({ children: routesElement, location }) : routesElement;\n\n // If the app already connected its own QueryClient (the documented\n // bring-your-own-provider pattern connects before calling renderApp), don't\n // inject ours. A second provider would shadow the user's client: useQuery\n // would read the inner client while live-query updates write to the outer\n // one, so real-time queries would never update.\n const appTree = hasConnectedQueryClient() ? (\n routedTree\n ) : (\n <ModelenceQueryProvider>{routedTree}</ModelenceQueryProvider>\n );\n\n const tree = (\n <React.StrictMode>\n <AppProvider loadingElement={loadingElement} setupElement={setupElement}>\n {appTree}\n </AppProvider>\n </React.StrictMode>\n );\n\n if (isHydrating) {\n ReactDOM.hydrateRoot(container, tree);\n } else {\n ReactDOM.createRoot(container).render(tree);\n }\n\n if (favicon) {\n const link = document.querySelector(\"link[rel~='icon']\") as HTMLLinkElement;\n if (!link) {\n const newLink = document.createElement('link');\n newLink.rel = 'icon';\n newLink.href = favicon;\n document.head.appendChild(newLink);\n } else {\n link.href = favicon;\n }\n }\n}\n"]}
@@ -1,4 +0,0 @@
1
- import {a,b as b$1,n,j as j$1}from'./chunk-UW37F3GV.js';import {a as a$2,b as b$2}from'./chunk-5M6FUMUK.js';import {a as a$1}from'./chunk-DO5TZLF5.js';import {isDeepStrictEqual}from'util';import {MongoError,ObjectId,MongoServerError}from'mongodb';import {z}from'zod';import {createHash,randomBytes}from'crypto';var Z=class{constructor(e,{stores:t=[],queries:i={},mutations:r={},routes:a=[],cronJobs:c={},configSchema:d={},rateLimits:m=[],channels:g=[]}={}){this.name=e,this.stores=t,this.queries=i,this.mutations=r,this.routes=a,this.cronJobs=c,this.configSchema=d,this.rateLimits=m,this.channels=g;}getConfig(e){return a(`${this.name}.${e}`)}};function ie(){return process.env.NODE_ENV==="development"&&!process.env.MODELENCE_SERVICE_ENDPOINT&&!a("_system.mongodbUri")}function w(n){let e=n._def;if(e.typeName==="ZodString")return {type:"string"};if(e.typeName==="ZodNumber")return {type:"number"};if(e.typeName==="ZodBoolean")return {type:"boolean"};if(e.typeName==="ZodDate")return {type:"date"};if(e.typeName==="ZodArray")return {type:"array",items:w(e.type)};if(e.typeName==="ZodObject"){let i=e.shape(),r={};for(let[a,c]of Object.entries(i))r[a]=w(c);return {type:"object",items:r}}if(e.typeName==="ZodOptional")return {...w(e.innerType),optional:true};if(e.typeName==="ZodNullable")return {...w(e.innerType),optional:true};if(e.typeName==="ZodEnum")return {type:"enum",items:e.values};if(e.typeName==="ZodUnion")return {type:"union",items:e.options.map(w)};if(e.typeName==="ZodEffects"){let t=e;return t.description?{type:"custom",typeName:t.description}:w(t.schema)}return {type:"custom",typeName:e.typeName}}function N(n){let e={};for(let[t,i]of Object.entries(n))Array.isArray(i)?e[t]=i.map(r=>typeof r=="object"&&"_def"in r?w(r):N(r)):typeof i=="object"&&"_def"in i?e[t]=w(i):e[t]=N(i);return e}var j=n=>typeof n=="object"&&n!==null&&!Array.isArray(n),re=n=>j(n)&&"_def"in n,K=n=>n._def,oe=n=>{let e=K(n);if(e.typeName==="ZodOptional"||e.typeName==="ZodNullable"||e.typeName==="ZodDefault"||e.typeName==="ZodCatch"||e.typeName==="ZodReadonly")return e.innerType;if(e.typeName==="ZodEffects")return e.schema;if(e.typeName==="ZodBranded")return e.type;if(e.typeName==="ZodPipeline")return e.out},se=n=>{let e=K(n);if(e.typeName==="ZodDefault")return {hasDefault:true,value:e.defaultValue()};let t=oe(n);return t?se(t):{hasDefault:false}},Q=(n,e)=>{let t=K(n);if(t.typeName==="ZodObject"&&j(e))return $(t.shape(),e);if(t.typeName==="ZodArray"&&Array.isArray(e))return e.map(r=>Q(t.type,r));let i=oe(n);return i?Q(i,e):e},F=(n,e)=>re(n)?Q(n,e):Array.isArray(n)&&Array.isArray(e)?n.length===1?e.map(t=>F(n[0],t)):e.map((t,i)=>F(n[i],t)):j(n)&&j(e)?$(n,e):e,$=(n,e)=>{let t={...e};for(let[i,r]of Object.entries(n)){let a=t[i];if(a===void 0){if(re(r)){let c=se(r);c.hasDefault&&(t[i]=F(r,c.value));}continue}t[i]=F(r,a);}return t};var De="[modelence:index-error]";function ae(n){return n instanceof MongoServerError?n.code===11e3||n.message.includes("E11000"):false}function ce(n,e,t){let i={problem:"unique-index-violation",collection:n,index:{name:e.name??null,key:e.key,collation:e.collation??null,partialFilterExpression:e.partialFilterExpression??null},duplicateKey:t.keyValue??null,serverError:t.errmsg??t.message},r=e.collation?{collation:e.collation}:void 0,a=`db.getCollection(${JSON.stringify(n)}).aggregate(${JSON.stringify(we(e))}`+(r?`, ${JSON.stringify(r)}`:"")+")";return [`${De} Unique index '${e.name??JSON.stringify(e.key)}' on collection '${n}' could not be created because existing documents already violate it. Until this is resolved the constraint is NOT enforced, so new duplicates can still be written.`,`Details: ${JSON.stringify(i)}`,"Find the conflicting documents with:",` ${a}`,"Fix: merge, delete, or update the conflicting documents so each indexed value is unique, then restart the app \u2014 the index build is retried on startup."].join(`
2
- `)}function we(n){let e=Object.keys(n.key),t=e.flatMap(r=>Ie(r).map(a=>({$unwind:{path:`$${a}`,preserveNullAndEmptyArrays:true}}))),i=Object.fromEntries(e.map(r=>[r.replace(/\./g,"_"),`$${r}`]));return [...n.partialFilterExpression?[{$match:n.partialFilterExpression}]:[],...t,{$group:{_id:i,ids:{$addToSet:"$_id"}}},{$match:{$expr:{$gt:[{$size:"$ids"},1]}}},{$limit:100}]}function Ie(n){let e=n.split(".");return e.map((t,i)=>e.slice(0,i+1).join("."))}var Oe=["background","bits","bucketSize","collation","default_language","expireAfterSeconds","hidden","language_override","max","min","partialFilterExpression","sparse","storageEngine","textIndexVersion","unique","weights","wildcardProjection","2dsphereIndexVersion"],J=n=>typeof n=="object"&&n!==null&&!Array.isArray(n),Ce=n=>n.startsWith("_modelence_"),H=n=>{let e={};for(let t of Oe){let i=n[t];i!==void 0&&(e[t]=i);}return e},ke=(n,e)=>{if(!J(n)||!J(e))return false;let t=Object.entries(n),i=Object.entries(e);return t.length!==i.length?false:t.every(([r,a],c)=>{let[d,m]=i[c]||[];return r===d&&isDeepStrictEqual(a,m)})},de=(n,e)=>ke(n.key,e.key)?isDeepStrictEqual(H(n),H(e)):false,V=n=>J(n)?Object.entries(n).map(([e,t])=>`${e}:${JSON.stringify(t)}`).join("|"):null,_e=async n=>{try{return await n.listIndexes().toArray()}catch(e){if(e instanceof MongoError&&e.code===26)return [];throw e}},Ae=n=>Object.entries(n).map(([e,t])=>`${e}_${t}`).join("_"),Me=n=>{if(n.name){let t=n.name.startsWith("_modelence_")?n.name:`_modelence_${n.name}`;return {...n,name:t}}let e=Ae(n.key);return {...n,name:`_modelence_${e}`}},f=class n{constructor(e,t){this._chainParent=null;this._chainChild=null;this.name=e,this.schema=t.schema,this.methods=t.methods,this.indexes=t.indexes.map(Me),this.searchIndexes=t.searchIndexes||[],this.indexCreationMode=t.indexCreationMode??"background";}getName(){return this.name}getIndexCreationMode(){return this.indexCreationMode}getSchema(){return this.schema}getSerializedSchema(){return N(this.schema)}getIndexes(){return this.indexes}getSearchIndexes(){return this.searchIndexes}getChainTail(){let e=this;for(;e._chainChild;)e=e._chainChild;return e}getChainRoot(){let e=this;for(;e._chainParent;)e=e._chainParent;return e}extend(e){let t=this.getChainTail();if(this.client||t.client)throw new Error(`Store.extend() must be called before startApp(). Store '${this.name}' has already been initialized and cannot be extended.`);let i={...t.schema,...e.schema||{}},r=[...t.indexes,...e.indexes||[]],a=[...t.searchIndexes,...e.searchIndexes||[]],c={...t.methods||{},...e.methods||{}},d=new n(this.name,{schema:i,methods:c,indexes:r,searchIndexes:a,indexCreationMode:e.indexCreationMode??t.indexCreationMode});return t._chainChild=d,d._chainParent=t,d}init(e){if(this.collection)throw new Error(`Collection ${this.name} is already initialized`);this.client=e,this.collection=this.client.db().collection(this.name);}async createIndexes(e="full"){let t=this.requireCollection(),i=e!=="create-only",r=e!=="drop-only",a=await _e(t),c=new Map,d=new Map,m=new Set,g=s=>{c.set(s.name,s);let l=V(s.key);if(!l)return;let u=d.get(l);u?u.add(s.name):d.set(l,new Set([s.name]));},D=s=>{let l=c.get(s);if(!l)return;c.delete(s);let u=V(l.key);if(!u)return;let T=d.get(u);T&&(T.delete(s),T.size===0&&d.delete(u));};for(let s of a)typeof s.name=="string"&&g({...s,name:s.name});let y=async s=>{if(!(s==="_id_"||m.has(s))){try{await t.dropIndex(s);}catch(l){if(!(l instanceof MongoError&&l.code===27))throw l}m.add(s),D(s);}};if(i){let s=new Set(this.indexes.map(u=>u.name).filter(u=>typeof u=="string")),l=[...c.values()].filter(u=>Ce(u.name)&&!s.has(u.name));for(let u of l)await y(u.name);}if(this.indexes.length>0)for(let s of this.indexes){if(!s.name)continue;let l=false,u=c.get(s.name);u&&!de(u,s)&&(i?await y(u.name):l=true);let T=V(s.key);if(T){let x=[...d.get(T)||[]];for(let O of x)O!==s.name&&(i?await y(O):l=true);}let v=c.get(s.name);if(!(!!v&&de(v,s))&&r&&!l){try{await t.createIndexes([s]);}catch(x){throw s.unique&&ae(x)&&console.error(ce(this.name,s,x)),x}g({name:s.name,key:s.key,...H(s)});}}if(r&&this.searchIndexes.length>0)for(let s of this.searchIndexes)try{await t.createSearchIndexes([s]);}catch(l){if(l instanceof MongoError&&l.code===68&&s.name)await t.dropSearchIndex(s.name),await t.createSearchIndexes([s]);else throw l}}wrapDocument(e){return this.methods?Object.create(null,Object.getOwnPropertyDescriptors({...e,...this.methods})):e}getSelector(e){return typeof e=="string"?{_id:new ObjectId(e)}:e instanceof ObjectId?{_id:e}:e}requireCollection(){if(!this.collection)throw new Error(`Collection ${this.name} is not provisioned`);return this.collection}requireClient(){if(!this.client)throw new Error("Database is not connected");return this.client}async findOne(e,t){let i=await this.requireCollection().findOne(e,t);return i?this.wrapDocument(i):null}async requireOne(e,t,i){let r=await this.findOne(e,t);if(!r)throw i?i():new Error(`Record not found in ${this.name}`);return r}find(e,t){let i=this.requireCollection().find(e,t?.projection?{projection:t.projection}:void 0);return t?.sort&&i.sort(t.sort),t?.limit&&i.limit(t.limit),t?.skip&&i.skip(t.skip),i}async findById(e){let t=typeof e=="string"?{_id:new ObjectId(e)}:{_id:e};return await this.findOne(t)}async requireById(e,t){let i=await this.findById(e);if(!i)throw t?t():new Error(`Record with id ${e} not found in ${this.name}`);return i}countDocuments(e){return this.requireCollection().countDocuments(e)}async fetch(e,t){return (await this.find(e,t).toArray()).map(this.wrapDocument.bind(this))}async insertOne(e,t){return await this.requireCollection().insertOne(e,t)}async create(e,t){let i=$(this.schema,{_id:new ObjectId,...e});return await this.requireCollection().insertOne(i,t),this.wrapDocument(i)}async insertMany(e,t){return await this.requireCollection().insertMany(e,t)}async updateOne(e,t,i){return await this.requireCollection().updateOne(this.getSelector(e),t,i)}async upsertOne(e,t,i){return await this.requireCollection().updateOne(this.getSelector(e),t,{upsert:true,...i})}async updateMany(e,t,i){return await this.requireCollection().updateMany(e,t,i)}async upsertMany(e,t,i){return await this.requireCollection().updateMany(e,t,{upsert:true,...i})}async deleteOne(e,t){return await this.requireCollection().deleteOne(this.getSelector(e),t)}async deleteMany(e,t){return await this.requireCollection().deleteMany(e,t)}async findOneAndUpdate(e,t,i){let r=await this.requireCollection().findOneAndUpdate(this.getSelector(e),t,i??{});return r?this.wrapDocument(r):null}async findOneAndUpsert(e,t,i){let r=await this.requireCollection().findOneAndUpdate(this.getSelector(e),t,{upsert:true,...i,returnDocument:"after",includeResultMetadata:true});return {doc:r.value?this.wrapDocument(r.value):null,isNew:!!r.lastErrorObject?.upserted}}async findOneAndDelete(e,t){let i=await this.requireCollection().findOneAndDelete(this.getSelector(e),t??{});return i?this.wrapDocument(i):null}async findOneAndReplace(e,t,i){let r=await this.requireCollection().findOneAndReplace(this.getSelector(e),t,i??{});return r?this.wrapDocument(r):null}async replaceOne(e,t,i){return await this.requireCollection().replaceOne(this.getSelector(e),t,i)}async distinct(e,t,i){let r=t??{};return i!==void 0?await this.requireCollection().distinct(e,r,i):await this.requireCollection().distinct(e,r)}watch(e,t){return this.requireCollection().watch(e,t)}aggregate(e,t){return this.requireCollection().aggregate(e,t)}bulkWrite(e){return this.requireCollection().bulkWrite(e)}getDatabase(){return this.requireClient().db()}rawCollection(){return this.requireCollection()}async renameFrom(e,t){let i=this.getDatabase();if(!this.collection||!i)throw new Error(`Store ${this.name} is not provisioned`);if((await i.listCollections({name:e}).toArray()).length===0)throw new Error(`Collection ${e} not found`);if((await i.listCollections({name:this.name}).toArray()).length>0)throw new Error(`Collection ${this.name} already exists`);await i.collection(e).rename(this.name,t);}async vectorSearch({field:e,embedding:t,numCandidates:i,limit:r,projection:a,indexName:c}){return this.aggregate([{$vectorSearch:{index:c||e+"VectorSearch",path:e,queryVector:t,numCandidates:i||100,limit:r||10}},{$project:{_id:1,score:{$meta:"vectorSearchScore"},...a}}])}static vectorIndex({field:e,dimensions:t,similarity:i="cosine",indexName:r}){return {type:"vectorSearch",name:r||e+"VectorSearch",definition:{fields:[{type:"vector",path:e,numDimensions:t,similarity:i}]}}}};var ve=z.string.bind(z),Ee=z.number.bind(z),Re=z.date.bind(z),Ze=z.boolean.bind(z),Ne=z.array.bind(z),je=z.object.bind(z),Fe=z.enum.bind(z),o={string:ve,number:Ee,date:Re,boolean:Ze,array:Ne,object:je,enum:Fe,embedding(){return z.array(z.number())},objectId(){return z.instanceof(ObjectId).describe("ObjectId")},userId(){return z.instanceof(ObjectId).describe("UserId")},ref(n){return z.instanceof(ObjectId).describe("Ref")},union:z.union.bind(z),infer(n){return {}}};function C(n){return createHash("sha256").update(n).digest("hex")}var G=new f("_modelenceLinkNonces",{schema:{nonce:o.string(),userId:o.string(),expiresAt:o.date()},indexes:[{key:{nonce:1},unique:true},{key:{expiresAt:1},expireAfterSeconds:0}]});async function nn(n){let e=randomBytes(32).toString("hex");return await G.insertOne({nonce:e,userId:n,expiresAt:new Date(Date.now()+a$1.minutes(10))}),e}async function rn(n){let e=await G.findOneAndDelete({nonce:n});return e?e.userId:null}var b=new f("_modelenceSessions",{schema:{authToken:o.string(),createdAt:o.date(),expiresAt:o.date(),userId:o.userId().nullable()},indexes:[{key:{authToken:1},unique:true},{key:{expiresAt:1},expireAfterSeconds:0},{key:{userId:1}}]});async function pe(n){if(n){let e=C(n),t=await b.findOne({authToken:e}),i=/^[0-9a-f]{64}$/i.test(n);if(!t&&!i&&(t=await b.findOne({authToken:n}),t&&await b.updateOne({_id:t._id},{$set:{authToken:e}})),t)return {authToken:n,expiresAt:new Date(t.expiresAt),userId:t.userId??null}}return await ze()}async function on(n,e){await b.updateOne({authToken:C(n)},{$set:{userId:e}});}async function sn(n){await b.updateOne({authToken:C(n)},{$set:{userId:null}});}async function an(n){await b.deleteMany({userId:n});}async function ze(n=null){let e=randomBytes(32).toString("base64url"),t=Date.now(),i=new Date(t+a$1.days(7));return await b.insertOne({authToken:C(e),createdAt:new Date(t),expiresAt:i,userId:n}),{authToken:e,expiresAt:i,userId:n}}async function Pe(n){let e=Date.now(),t=new Date(e+a$1.days(7));await b.updateOne({authToken:C(n.authToken)},{$set:{lastActiveDate:new Date(e),expiresAt:t}});}function qe(n,e){n.cookie("authToken",e,{httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",path:"/",maxAge:a$1.days(7)});}function cn(n){n.clearCookie("authToken",{httpOnly:true,secure:process.env.NODE_ENV==="production",sameSite:"lax",path:"/"});}var dn=new Z("_system.session",{stores:[b,G],mutations:{init:async function(n,{session:e,user:t,res:i}){return i&&e?.userId&&qe(i,e.authToken),{session:e,user:t,configs:b$1(),...ie()?{setupRequired:true}:{}}},heartbeat:async function(n,{session:e}){e&&await Pe(e);}}});var he=new f("_modelenceUsers",{schema:{handle:o.string(),emails:o.array(o.object({address:o.string(),verified:o.boolean()})).optional(),status:o.enum(["active","disabled","deleted"]).optional(),firstName:o.string().optional(),lastName:o.string().optional(),avatarUrl:o.string().optional(),createdAt:o.date(),disabledAt:o.date().optional(),deletedAt:o.date().optional(),roles:o.array(o.string()).optional(),authMethods:o.object({password:o.object({hash:o.string()}).optional(),google:o.object({id:o.string()}).optional(),github:o.object({id:o.string()}).optional()})},indexes:[{key:{handle:1},unique:true,collation:{locale:"en",strength:2}},{key:{"emails.address":1,status:1}},{key:{"emails.address":1},unique:true,collation:{locale:"en",strength:2},partialFilterExpression:{"emails.address":{$exists:true}}},{key:{"authMethods.google.id":1},sparse:true,unique:true},{key:{"authMethods.github.id":1},sparse:true,unique:true}]}),hn=new f("_modelenceDisposableEmailDomains",{schema:{domain:o.string(),addedAt:o.date()},indexes:[{key:{domain:1},unique:true}]}),mn=new f("_modelenceEmailVerificationTokens",{schema:{userId:o.objectId(),email:o.string().optional(),token:o.string(),createdAt:o.date(),expiresAt:o.date()},indexes:[{key:{token:1},unique:true},{key:{expiresAt:1},expireAfterSeconds:0}]}),yn=new f("_modelenceResetPasswordTokens",{schema:{userId:o.objectId(),email:o.string().optional(),token:o.string(),createdAt:o.date(),expiresAt:o.date()},indexes:[{key:{token:1},unique:true},{key:{expiresAt:1},expireAfterSeconds:0}]}),fn=new f("_modelenceMagicLinkTokens",{schema:{email:o.string(),token:o.string(),code:o.string(),attempts:o.number(),createdAt:o.date(),expiresAt:o.date()},indexes:[{key:{token:1},unique:true},{key:{email:1}},{key:{expiresAt:1},expireAfterSeconds:0}]});var me=new Map,k={authenticated:null,unauthenticated:null};function Tn(n,e){k.authenticated=e.authenticated,k.unauthenticated=e.unauthenticated;for(let[t,i]of Object.entries(n))me.set(t,i);}function ye(){return k.unauthenticated?[k.unauthenticated]:[]}function fe(){return k.authenticated?[k.authenticated]:[]}function Y(n,e){let t=e.find(i=>!Le(n,i));if(t)throw new Error(`Access denied - missing permission: '${t}'`)}function Le(n,e){for(let t of n)if(me.get(t)?.permissions?.includes(e))return true;return false}async function ge(n){let e=await pe(n),t=e.userId?await he.findOne({_id:new ObjectId(e.userId),status:{$nin:["deleted","disabled"]}}):null,i=t?{id:t._id.toString(),handle:t.handle,roles:t.roles||[],hasRole:a=>(t.roles||[]).includes(a),requireRole:a=>{if(!(t.roles||[]).includes(a))throw new Error(`Access denied - role '${a}' required`)},firstName:t.firstName??void 0,lastName:t.lastName??void 0,avatarUrl:t.avatarUrl??void 0}:null,r=i?fe():ye();return {user:i,session:e,roles:r}}var A=class{constructor(e){this.fetch=e.fetch,this.watch=e.watch;}};function We(){return typeof window!="object"}function I(){if(!We())throw new Error("This function can only be called on the server")}function Cn(n){return n.replace(/<[^>]*>/g,"").replace(/\s+/g," ").trim()}var M=new Map;function Qe(n){let e=M.get(n.id);return e||(e=new Map,M.set(n.id,e)),e}async function Ke(n,e){let t=z.object({subscriptionId:z.string().min(1),method:z.string().min(1),args:z.record(z.unknown()).default({}),authToken:z.string().nullish(),clientInfo:z.object({screenWidth:z.number(),screenHeight:z.number(),windowWidth:z.number(),windowHeight:z.number(),pixelRatio:z.number(),orientation:z.string().nullable()}).optional()}).safeParse(e);if(!t.success){n.emit("liveQueryError",{subscriptionId:null,error:`Invalid payload: ${t.error.message}`});return}let{subscriptionId:i,method:r,args:a,authToken:c,clientInfo:d}=t.data,m=Qe(n),g=m.get(i);if(g)if(g.cleanup)try{g.cleanup();}catch(y){console.error("[LiveQuery] Error cleaning up existing subscription:",y);}else g.aborted=true;let D={cleanup:null};m.set(i,D);try{let{session:y,user:s,roles:l}=await ge(c??null),u={session:y,user:s,roles:l,clientInfo:d??{screenWidth:0,screenHeight:0,windowWidth:0,windowHeight:0,pixelRatio:1,orientation:null},connectionInfo:{ip:n.handshake.address,userAgent:n.handshake.headers["user-agent"]},req:null,res:null},T=await Te(r,a,u),v=async()=>{let S=a$2(await T.fetch());D.aborted||n.emit("liveQueryData",{subscriptionId:i,data:S,typeMap:b$2(S)});},E=!0,x=!1,O=()=>{D.aborted||!E||x||(E=!1,x=!0,v().catch(S=>{D.aborted||(console.error(`[LiveQuery] Error fetching data for ${r}:`,S),n.emit("liveQueryError",{subscriptionId:i,error:S instanceof Error?S.message:String(S)}));}).finally(()=>{x=!1,O();}));},L=T.watch({publish:()=>{E=!0,O();}});if(D.aborted){if(L)try{L();}catch(S){console.error("[LiveQuery] Error cleaning up after disconnect during setup:",S);}return}D.cleanup=L||null,O();}catch(y){m.delete(i),console.error(`[LiveQuery] Error in ${r}:`,y),n.emit("liveQueryError",{subscriptionId:i,error:y instanceof Error?y.message:String(y)});}}function Ve(n,e){let t=z.object({subscriptionId:z.string().min(1)}).safeParse(e);if(!t.success){console.warn(`[LiveQuery] Invalid unsubscribe payload: ${t.error.message}`);return}let{subscriptionId:i}=t.data,r=M.get(n.id);if(!r)return;let a=r.get(i);if(a){if(a.cleanup)try{a.cleanup();}catch(c){console.error("[LiveQuery] Error in cleanup:",c);}else a.aborted=true;r.delete(i);}}function Be(n){let e=M.get(n.id);if(e){for(let t of e.values())if(t.cleanup)try{t.cleanup();}catch(i){console.error("[LiveQuery] Error in cleanup on disconnect:",i);}else t.aborted=true;M.delete(n.id);}}var P={};function Pn(n,e){return I(),xe(n),q("query",n,e)}function qn(n,e){return I(),xe(n),q("mutation",n,e)}function Ln(n,e){return I(),Se(n),q("query",n,e)}function Un(n,e){return I(),Se(n),q("mutation",n,e)}function xe(n){if(n.toLowerCase().startsWith("_system."))throw new Error(`Method name cannot start with a reserved prefix: '_system.' (${n})`)}function Se(n){if(!n.toLowerCase().startsWith("_system."))throw new Error(`System method name must start with a prefix: '_system.' (${n})`)}function q(n,e,t){if(I(),P[e])throw new Error(`Method with name '${e}' is already defined.`);let i=typeof t=="function"?t:t.handler,r=typeof t=="function"?[]:t.permissions??[];P[e]={type:n,name:e,handler:i,permissions:r};}async function Wn(n$1,e,t){I();let i=P[n$1];if(!i)throw new Error(`Method with name '${n$1}' is not defined.`);let{type:r,handler:a}=i,c=n("method",`method:${n$1}`,{type:r,args:j$1(e)}),d;try{Y(t.roles,i.permissions),d=await a(e,t);}catch(m){throw c.end("error"),m}return c.end(),d}async function Te(n$1,e,t){I();let i=P[n$1];if(!i)throw new Error(`Method with name '${n$1}' is not defined.`);let{type:r,handler:a}=i;if(r!=="query")throw new Error("Live methods are only supported for queries");let c=n("method",`method:${n$1}:live`,{type:r,args:j$1(e)}),d;try{if(Y(t.roles,i.permissions),d=await a(e,t),!(d instanceof A))throw new Error(`Live query handler for '${n$1}' must return a LiveData object with fetch and watch functions. See https://docs.modelence.com/live-queries`)}catch(m){throw c.end("error"),m}return c.end(),d}
3
- export{Pn as A,qn as B,Ln as C,Un as D,Wn as E,Ke as F,Ve as G,Be as H,Z as a,ie as b,f as c,o as d,C as e,nn as f,rn as g,b as h,pe as i,on as j,sn as k,an as l,ze as m,qe as n,cn as o,dn as p,he as q,hn as r,mn as s,yn as t,fn as u,Tn as v,ye as w,ge as x,A as y,Cn as z};//# sourceMappingURL=chunk-KDOTVIEY.js.map
4
- //# sourceMappingURL=chunk-KDOTVIEY.js.map