@instantdb/react 0.22.155 → 0.22.156

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.
@@ -85,7 +85,6 @@ function createHydrationStreamProvider() {
85
85
  if (!stream.length) {
86
86
  return null;
87
87
  }
88
- // console.log(`pushing ${stream.length} entries`)
89
88
  const serializedCacheArgs = stream
90
89
  .map((entry) => transformer.serialize(entry))
91
90
  .map((entry) => JSON.stringify(entry))
@@ -1 +1 @@
1
- {"version":3,"file":"HydrationStreamProvider.js","sourceRoot":"","sources":["../../../src/next-ssr/HydrationStreamProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// This allows us to send serialized json from the server to the client using 'useServerInsertedHTML'\n// We are allowed to send whatever json we want, however it must be triggered at the same time as the client is recieving new html to display\n// after a suspsense boundary resolves\n// We do this to send a list of triples and attrs for each query that the server made so they can be pre-filled on the client render.\n// useServerInsertedHTML is called whenever a suspense boundary resolves, resulting in new html being streamed down to the client.\n\nexport const isServer = typeof window === 'undefined' || 'Deno' in globalThis;\n\nimport { useServerInsertedHTML } from 'next/navigation.js';\n\nimport * as React from 'react';\nimport { htmlEscapeJsonString } from './htmlescape.js';\n\nconst serializedSymbol = Symbol('serialized');\n\ninterface DataTransformer {\n serialize: (object: any) => any;\n deserialize: (object: any) => any;\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData;\n};\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>;\n deserialize: (obj: Serialized<TData>) => TData;\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string;\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void;\n };\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n );\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`;\n const idJSON = htmlEscapeJsonString(JSON.stringify(id));\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n );\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!isServer) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>;\n }\n return [];\n });\n const count = React.useRef(0);\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []));\n\n if (!stream.length) {\n return null;\n }\n // console.log(`pushing ${stream.length} entries`)\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',');\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0;\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ];\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n );\n });\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!isServer) {\n const win = window as any;\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n );\n props.onEntries(entries);\n };\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? [];\n\n onEntries(...winStream);\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n };\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n );\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n };\n}\n"]}
1
+ {"version":3,"file":"HydrationStreamProvider.js","sourceRoot":"","sources":["../../../src/next-ssr/HydrationStreamProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// This allows us to send serialized json from the server to the client using 'useServerInsertedHTML'\n// We are allowed to send whatever json we want, however it must be triggered at the same time as the client is recieving new html to display\n// after a suspsense boundary resolves\n// We do this to send a list of triples and attrs for each query that the server made so they can be pre-filled on the client render.\n// useServerInsertedHTML is called whenever a suspense boundary resolves, resulting in new html being streamed down to the client.\n\nexport const isServer = typeof window === 'undefined' || 'Deno' in globalThis;\n\nimport { useServerInsertedHTML } from 'next/navigation.js';\n\nimport * as React from 'react';\nimport { htmlEscapeJsonString } from './htmlescape.js';\n\nconst serializedSymbol = Symbol('serialized');\n\ninterface DataTransformer {\n serialize: (object: any) => any;\n deserialize: (object: any) => any;\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData;\n};\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>;\n deserialize: (obj: Serialized<TData>) => TData;\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string;\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void;\n };\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n );\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`;\n const idJSON = htmlEscapeJsonString(JSON.stringify(id));\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n );\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!isServer) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>;\n }\n return [];\n });\n const count = React.useRef(0);\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []));\n\n if (!stream.length) {\n return null;\n }\n\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',');\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0;\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ];\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n );\n });\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!isServer) {\n const win = window as any;\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n );\n props.onEntries(entries);\n };\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? [];\n\n onEntries(...winStream);\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n };\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n );\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n };\n}\n"]}
@@ -1,12 +1,9 @@
1
- import { InstantConfig, InstantSchemaDef, InstaQLResponse, PageInfoResponse, RuleParams, User, ValidQuery } from '@instantdb/core';
1
+ import { InstantSchemaDef, InstaQLResponse, PageInfoResponse, RuleParams, User, ValidQuery } from '@instantdb/core';
2
2
  import InstantReactWebDatabase from '../InstantReactWebDatabase.ts';
3
3
  type InstantSuspenseProviderProps<Schema extends InstantSchemaDef<any, any, any>> = {
4
4
  nonce?: string;
5
5
  children: React.ReactNode;
6
- db?: InstantReactWebDatabase<Schema, any>;
7
- config?: Omit<InstantConfig<any, any>, 'schema'> & {
8
- schema: string;
9
- };
6
+ db: InstantReactWebDatabase<Schema, any>;
10
7
  user?: User | null;
11
8
  };
12
9
  type SuspenseQueryContextValue = {
@@ -1 +1 @@
1
- {"version":3,"file":"InstantSuspenseProvider.d.ts","sourceRoot":"","sources":["../../../src/next-ssr/InstantSuspenseProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,IAAI,EACJ,UAAU,EACX,MAAM,iBAAiB,CAAC;AACzB,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AAQpE,KAAK,4BAA4B,CAC/B,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAC5C;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,EAAE,CAAC,EAAE,uBAAuB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,GAAG;QACjD,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB,CAAC;AAIF,KAAK,yBAAyB,GAAG;IAC/B,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,GAAG,CAAC;IAChE,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,qBAAqB,2DACqB,CAAC;AAGxD,eAAO,MAAM,sBAAsB,GACjC,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC9C,QAAQ,SAAS,OAAO,EAExB,KAAK,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,KAC7C,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,EAClC,CAAC,EAAE,CAAC,EACJ,IAAI,CAAC,EAAE;IACL,UAAU,EAAE,UAAU,CAAC;CACxB,KACE;IACH,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAChC,CAUA,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,OAAO,4BAA4B,CAAC,GAAG,CAAC,4CAkHzC,CAAC"}
1
+ {"version":3,"file":"InstantSuspenseProvider.d.ts","sourceRoot":"","sources":["../../../src/next-ssr/InstantSuspenseProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAGL,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,IAAI,EACJ,UAAU,EACX,MAAM,iBAAiB,CAAC;AACzB,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AAmBpE,KAAK,4BAA4B,CAC/B,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAC5C;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,EAAE,EAAE,uBAAuB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB,CAAC;AAIF,KAAK,yBAAyB,GAAG;IAC/B,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,GAAG,CAAC;IAChE,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,qBAAqB,2DACqB,CAAC;AAGxD,eAAO,MAAM,sBAAsB,GACjC,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC9C,QAAQ,SAAS,OAAO,EAExB,KAAK,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,KAC7C,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,EAClC,CAAC,EAAE,CAAC,EACJ,IAAI,CAAC,EAAE;IACL,UAAU,EAAE,UAAU,CAAC;CACxB,KACE;IACH,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAChC,CAUA,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC;AAqJF,eAAO,MAAM,uBAAuB,GAClC,OAAO,4BAA4B,CAAC,GAAG,CAAC,4CA4DzC,CAAC"}
@@ -7,6 +7,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
7
7
  const core_1 = require("@instantdb/core");
8
8
  const HydrationStreamProvider_tsx_1 = require("./HydrationStreamProvider.js");
9
9
  const react_1 = require("react");
10
+ const react_common_1 = require("@instantdb/react-common");
10
11
  const stream = (0, HydrationStreamProvider_tsx_1.createHydrationStreamProvider)();
11
12
  exports.SuspsenseQueryContext = (0, react_1.createContext)(null);
12
13
  // Creates a typed useSuspense hook
@@ -20,48 +21,11 @@ const createUseSuspenseQuery = (_db) => {
20
21
  };
21
22
  };
22
23
  exports.createUseSuspenseQuery = createUseSuspenseQuery;
23
- const InstantSuspenseProvider = (props) => {
24
- const clientRef = (0, react_1.useRef)(null);
25
- if (!props.db) {
26
- throw new Error('Must provide either a db or config to InstantSuspenseProvider');
27
- }
28
- const db = (0, react_1.useRef)(props.db);
29
- const [trackedKeys] = (0, react_1.useState)(() => new Set());
30
- if (!clientRef.current) {
31
- if (props.user && !props.user.refresh_token) {
32
- throw new Error('User must have a refresh_token field. Recieved: ' +
33
- JSON.stringify(props.user, null, 2));
34
- }
35
- clientRef.current = new core_1.FrameworkClient({
36
- token: props.user?.refresh_token,
37
- db: db.current.core,
38
- });
39
- }
40
- if (HydrationStreamProvider_tsx_1.isServer) {
41
- clientRef.current.subscribe((result) => {
42
- const { queryHash } = result;
43
- trackedKeys.add(queryHash);
44
- });
45
- }
46
- const useSuspenseQuery = (query, opts) => {
47
- const nonSuspenseResult = db.current.useQuery(query, {
48
- ...opts,
49
- });
50
- if (nonSuspenseResult.data) {
51
- return {
52
- data: nonSuspenseResult.data,
53
- pageInfo: nonSuspenseResult.pageInfo,
54
- };
55
- }
56
- // should never happen (typeguard)
57
- if (!clientRef.current) {
58
- throw new Error('Client ref not set up');
59
- }
60
- let entry = clientRef.current.getExistingResultForQuery(query, {
61
- ruleParams: opts?.ruleParams,
62
- });
24
+ function makeUseSuspenseQueryServer(client) {
25
+ return function useSuspenseQueryServer(query, opts) {
26
+ let entry = client.getExistingResultForQuery(query, opts);
63
27
  if (!entry) {
64
- entry = clientRef.current.query(query, opts);
28
+ entry = client.query(query, opts);
65
29
  }
66
30
  if (entry.status === 'pending') {
67
31
  throw entry.promise;
@@ -69,16 +33,120 @@ const InstantSuspenseProvider = (props) => {
69
33
  if (entry.status === 'error') {
70
34
  throw entry.error;
71
35
  }
72
- if (entry.status === 'success' && entry.type === 'session') {
73
- return entry.data;
74
- }
75
36
  if (entry.status === 'success') {
76
- const data = entry.data;
77
- const result = clientRef.current.completeIsomorphic(query, data.triples, data.attrs, data.pageInfo);
78
- return result;
37
+ switch (entry.type) {
38
+ case 'session': {
39
+ return entry.data;
40
+ }
41
+ case 'http': {
42
+ const data = entry.data;
43
+ const result = client.completeIsomorphic(query, data.triples, data.attrs, data.pageInfo);
44
+ return result;
45
+ }
46
+ }
47
+ }
48
+ };
49
+ }
50
+ function makeUseSuspenseQueryClient(db, client) {
51
+ function getEntry(query, opts, allowFetch) {
52
+ const entry = client.getExistingResultForQuery(query, opts);
53
+ if (entry?.status === 'pending') {
54
+ throw entry.promise;
55
+ }
56
+ if (entry?.status === 'error') {
57
+ throw entry.error;
58
+ }
59
+ if (entry?.status === 'success') {
60
+ switch (entry.type) {
61
+ case 'session': {
62
+ return entry.data;
63
+ }
64
+ case 'http': {
65
+ const data = entry.data;
66
+ const result = client.completeIsomorphic(query, data.triples, data.attrs, data.pageInfo);
67
+ return result;
68
+ }
69
+ }
70
+ }
71
+ if (allowFetch) {
72
+ const promise = client.queryClient(query, opts);
73
+ throw promise;
74
+ }
75
+ }
76
+ return function useSuspenseQueryClient(query, opts) {
77
+ const useQueryResult = (0, react_common_1.useQueryInternal)(db.core, query, opts,
78
+ // Returns the server result for useSyncExternalStore
79
+ () => {
80
+ try {
81
+ const res = getEntry(query, opts, false);
82
+ return res;
83
+ }
84
+ catch (throwable) {
85
+ return { error: throwable };
86
+ }
87
+ });
88
+ const hasData = !!useQueryResult.state.data;
89
+ const queryHash = useQueryResult.queryHash;
90
+ (0, react_1.useEffect)(() => {
91
+ if (hasData) {
92
+ // We have a newer result, so remove the cached SSR or suspended
93
+ // result from the framework client cache
94
+ client.removeCachedQueryResult(queryHash);
95
+ }
96
+ }, [hasData, queryHash]);
97
+ if (useQueryResult.state.data) {
98
+ return {
99
+ data: useQueryResult.state.data,
100
+ pageInfo: useQueryResult.state.pageInfo,
101
+ };
102
+ }
103
+ if (useQueryResult.state.error) {
104
+ throw useQueryResult.state.error;
79
105
  }
106
+ return getEntry(query, opts, true);
80
107
  };
81
- return ((0, jsx_runtime_1.jsx)(exports.SuspsenseQueryContext.Provider, { value: { useSuspenseQuery, ssrUser: props.user }, children: (0, jsx_runtime_1.jsx)(stream.Provider, { nonce: props.nonce, onFlush: () => {
108
+ }
109
+ function createFrameworkClient(db, user) {
110
+ if (HydrationStreamProvider_tsx_1.isServer) {
111
+ if (user && !user.refresh_token) {
112
+ throw new Error('User must have a refresh_token field. Recieved: ' +
113
+ JSON.stringify(user, null, 2));
114
+ }
115
+ return new core_1.FrameworkClient({
116
+ token: user?.refresh_token,
117
+ db: db.core,
118
+ });
119
+ }
120
+ // On the client, make sure we only have a single framework
121
+ // in case our suspense provider gets unmounted
122
+ const existing = db.core._reactor._frameworkClient;
123
+ if (existing) {
124
+ return existing;
125
+ }
126
+ const client = new core_1.FrameworkClient({ db: db.core });
127
+ db.core._reactor.setFrameworkClient(client);
128
+ return client;
129
+ }
130
+ const InstantSuspenseProvider = (props) => {
131
+ if (!props.db) {
132
+ throw new Error('Must provide db to InstantSuspenseProvider');
133
+ }
134
+ const db = props.db;
135
+ const [trackedKeys] = (0, react_1.useState)(() => new Set());
136
+ const clientRef = (0, react_1.useRef)(createFrameworkClient(props.db, props.user));
137
+ if (HydrationStreamProvider_tsx_1.isServer) {
138
+ clientRef.current.subscribe((result) => {
139
+ const { queryHash } = result;
140
+ trackedKeys.add(queryHash);
141
+ });
142
+ }
143
+ const useSuspenseQuery = (0, react_1.useCallback)(HydrationStreamProvider_tsx_1.isServer
144
+ ? makeUseSuspenseQueryServer(clientRef.current)
145
+ : makeUseSuspenseQueryClient(db, clientRef.current), []);
146
+ const contextValue = (0, react_1.useMemo)(() => {
147
+ return { useSuspenseQuery, ssrUser: props.user };
148
+ }, [useSuspenseQuery, props.user]);
149
+ return ((0, jsx_runtime_1.jsx)(exports.SuspsenseQueryContext.Provider, { value: contextValue, children: (0, jsx_runtime_1.jsx)(stream.Provider, { nonce: props.nonce, onFlush: () => {
82
150
  const toSend = [];
83
151
  for (const [key, value] of clientRef.current.resultMap.entries()) {
84
152
  if (trackedKeys.has(key) && value.status === 'success') {
@@ -1 +1 @@
1
- {"version":3,"file":"InstantSuspenseProvider.js","sourceRoot":"","sources":["../../../src/next-ssr/InstantSuspenseProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n// InstantSuspenseProvider can only be used in a client context so this prevents errors from trying to use it in a server component.\n\nimport {\n FrameworkClient,\n InstantConfig,\n InstantSchemaDef,\n InstaQLResponse,\n PageInfoResponse,\n RuleParams,\n User,\n ValidQuery,\n} from '@instantdb/core';\nimport InstantReactWebDatabase from '../InstantReactWebDatabase.ts';\nimport {\n createHydrationStreamProvider,\n isServer,\n} from './HydrationStreamProvider.tsx';\nimport { createContext, useContext, useRef, useState } from 'react';\nimport { InstantReactAbstractDatabase } from '@instantdb/react-common';\n\ntype InstantSuspenseProviderProps<\n Schema extends InstantSchemaDef<any, any, any>,\n> = {\n nonce?: string;\n children: React.ReactNode;\n db?: InstantReactWebDatabase<Schema, any>;\n config?: Omit<InstantConfig<any, any>, 'schema'> & {\n schema: string;\n };\n user?: User | null;\n};\n\nconst stream = createHydrationStreamProvider<any>();\n\ntype SuspenseQueryContextValue = {\n useSuspenseQuery: (query: any, opts?: SuspenseQueryOpts) => any;\n ssrUser: User | null | undefined;\n};\n\nexport const SuspsenseQueryContext =\n createContext<SuspenseQueryContextValue | null>(null);\n\n// Creates a typed useSuspense hook\nexport const createUseSuspenseQuery = <\n Schema extends InstantSchemaDef<any, any, any>,\n UseDates extends boolean,\n>(\n _db: InstantReactWebDatabase<Schema, UseDates>,\n): (<Q extends ValidQuery<Q, Schema>>(\n q: Q,\n opts?: {\n ruleParams: RuleParams;\n },\n) => {\n data: InstaQLResponse<Schema, Q, NonNullable<UseDates>>;\n pageInfo?: PageInfoResponse<Q>;\n}) => {\n return <Q extends ValidQuery<Q, Schema>>(q: any, opts: any) => {\n const ctx = useContext(SuspsenseQueryContext);\n if (!ctx) {\n throw new Error(\n 'useSuspenseQuery must be used within a SuspenseQueryProvider',\n );\n }\n return ctx.useSuspenseQuery(q, opts) as any;\n };\n};\n\ntype SuspenseQueryOpts = {\n ruleParams: RuleParams;\n};\n\nexport const InstantSuspenseProvider = (\n props: InstantSuspenseProviderProps<any>,\n) => {\n const clientRef = useRef<FrameworkClient | null>(null);\n\n if (!props.db) {\n throw new Error(\n 'Must provide either a db or config to InstantSuspenseProvider',\n );\n }\n\n const db = useRef<InstantReactAbstractDatabase<any, any>>(props.db);\n\n const [trackedKeys] = useState(() => new Set<string>());\n\n if (!clientRef.current) {\n if (props.user && !props.user.refresh_token) {\n throw new Error(\n 'User must have a refresh_token field. Recieved: ' +\n JSON.stringify(props.user, null, 2),\n );\n }\n clientRef.current = new FrameworkClient({\n token: props.user?.refresh_token,\n db: db.current.core,\n });\n }\n\n if (isServer) {\n clientRef.current.subscribe((result) => {\n const { queryHash } = result;\n trackedKeys.add(queryHash);\n });\n }\n\n const useSuspenseQuery = (query: any, opts: SuspenseQueryOpts) => {\n const nonSuspenseResult = db.current.useQuery(query, {\n ...opts,\n });\n\n if (nonSuspenseResult.data) {\n return {\n data: nonSuspenseResult.data,\n pageInfo: nonSuspenseResult.pageInfo,\n };\n }\n\n // should never happen (typeguard)\n if (!clientRef.current) {\n throw new Error('Client ref not set up');\n }\n\n let entry = clientRef.current.getExistingResultForQuery(query, {\n ruleParams: opts?.ruleParams,\n });\n\n if (!entry) {\n entry = clientRef.current!.query(query, opts);\n }\n\n if (entry.status === 'pending') {\n throw entry.promise;\n }\n\n if (entry.status === 'error') {\n throw entry.error;\n }\n\n if (entry.status === 'success' && entry.type === 'session') {\n return entry.data;\n }\n\n if (entry.status === 'success') {\n const data = entry.data;\n const result = clientRef.current.completeIsomorphic(\n query,\n data.triples,\n data.attrs,\n data.pageInfo,\n );\n\n return result;\n }\n };\n\n return (\n <SuspsenseQueryContext.Provider\n value={{ useSuspenseQuery, ssrUser: props.user }}\n >\n <stream.Provider\n nonce={props.nonce}\n onFlush={() => {\n const toSend: { queryKey: string; value: any }[] = [];\n for (const [key, value] of clientRef.current!.resultMap.entries()) {\n if (trackedKeys.has(key) && value.status === 'success') {\n toSend.push({\n queryKey: key,\n value: value.data,\n });\n }\n }\n\n trackedKeys.clear();\n return toSend;\n }}\n onEntries={(entries) => {\n entries.forEach((entry) => {\n clientRef.current!.addQueryResult(entry.queryKey, entry.value);\n });\n }}\n >\n {props.children}\n </stream.Provider>\n </SuspsenseQueryContext.Provider>\n );\n};\n"]}
1
+ {"version":3,"file":"InstantSuspenseProvider.js","sourceRoot":"","sources":["../../../src/next-ssr/InstantSuspenseProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n// InstantSuspenseProvider can only be used in a client context so this prevents errors from trying to use it in a server component.\n\nimport {\n FrameworkClient,\n InstantConfig,\n InstantSchemaDef,\n InstaQLResponse,\n PageInfoResponse,\n RuleParams,\n User,\n ValidQuery,\n} from '@instantdb/core';\nimport InstantReactWebDatabase from '../InstantReactWebDatabase.ts';\nimport {\n createHydrationStreamProvider,\n isServer,\n} from './HydrationStreamProvider.tsx';\nimport {\n createContext,\n useCallback,\n useContext,\n useMemo,\n useRef,\n useState,\n useEffect,\n} from 'react';\nimport {\n InstantReactAbstractDatabase,\n useQueryInternal,\n} from '@instantdb/react-common';\n\ntype InstantSuspenseProviderProps<\n Schema extends InstantSchemaDef<any, any, any>,\n> = {\n nonce?: string;\n children: React.ReactNode;\n db: InstantReactWebDatabase<Schema, any>;\n user?: User | null;\n};\n\nconst stream = createHydrationStreamProvider<any>();\n\ntype SuspenseQueryContextValue = {\n useSuspenseQuery: (query: any, opts?: SuspenseQueryOpts) => any;\n ssrUser: User | null | undefined;\n};\n\nexport const SuspsenseQueryContext =\n createContext<SuspenseQueryContextValue | null>(null);\n\n// Creates a typed useSuspense hook\nexport const createUseSuspenseQuery = <\n Schema extends InstantSchemaDef<any, any, any>,\n UseDates extends boolean,\n>(\n _db: InstantReactWebDatabase<Schema, UseDates>,\n): (<Q extends ValidQuery<Q, Schema>>(\n q: Q,\n opts?: {\n ruleParams: RuleParams;\n },\n) => {\n data: InstaQLResponse<Schema, Q, NonNullable<UseDates>>;\n pageInfo?: PageInfoResponse<Q>;\n}) => {\n return <Q extends ValidQuery<Q, Schema>>(q: any, opts: any) => {\n const ctx = useContext(SuspsenseQueryContext);\n if (!ctx) {\n throw new Error(\n 'useSuspenseQuery must be used within a SuspenseQueryProvider',\n );\n }\n return ctx.useSuspenseQuery(q, opts) as any;\n };\n};\n\ntype SuspenseQueryOpts = {\n ruleParams: RuleParams;\n};\n\nfunction makeUseSuspenseQueryServer(client: FrameworkClient) {\n return function useSuspenseQueryServer(query: any, opts: SuspenseQueryOpts) {\n let entry = client.getExistingResultForQuery(query, opts);\n\n if (!entry) {\n entry = client.query(query, opts);\n }\n\n if (entry.status === 'pending') {\n throw entry.promise;\n }\n\n if (entry.status === 'error') {\n throw entry.error;\n }\n\n if (entry.status === 'success') {\n switch (entry.type) {\n case 'session': {\n return entry.data;\n }\n case 'http': {\n const data = entry.data;\n const result = client.completeIsomorphic(\n query,\n data.triples,\n data.attrs,\n data.pageInfo,\n );\n\n return result;\n }\n }\n }\n };\n}\n\nfunction makeUseSuspenseQueryClient(\n db: InstantReactAbstractDatabase<any, any>,\n client: FrameworkClient,\n) {\n function getEntry(query: any, opts: SuspenseQueryOpts, allowFetch: boolean) {\n const entry = client.getExistingResultForQuery(query, opts);\n\n if (entry?.status === 'pending') {\n throw entry.promise;\n }\n\n if (entry?.status === 'error') {\n throw entry.error;\n }\n\n if (entry?.status === 'success') {\n switch (entry.type) {\n case 'session': {\n return entry.data;\n }\n case 'http': {\n const data = entry.data;\n const result = client.completeIsomorphic(\n query,\n data.triples,\n data.attrs,\n data.pageInfo,\n );\n\n return result;\n }\n }\n }\n\n if (allowFetch) {\n const promise = client.queryClient(query, opts);\n throw promise;\n }\n }\n\n return function useSuspenseQueryClient(query: any, opts: SuspenseQueryOpts) {\n const useQueryResult = useQueryInternal(\n db.core,\n query,\n opts,\n // Returns the server result for useSyncExternalStore\n () => {\n try {\n const res = getEntry(query, opts, false);\n return res;\n } catch (throwable) {\n return { error: throwable };\n }\n },\n );\n\n const hasData = !!useQueryResult.state.data;\n const queryHash = useQueryResult.queryHash;\n\n useEffect(() => {\n if (hasData) {\n // We have a newer result, so remove the cached SSR or suspended\n // result from the framework client cache\n client.removeCachedQueryResult(queryHash);\n }\n }, [hasData, queryHash]);\n\n if (useQueryResult.state.data) {\n return {\n data: useQueryResult.state.data,\n pageInfo: useQueryResult.state.pageInfo,\n };\n }\n\n if (useQueryResult.state.error) {\n throw useQueryResult.state.error;\n }\n\n return getEntry(query, opts, true);\n };\n}\n\nfunction createFrameworkClient(\n db: InstantReactAbstractDatabase<any, any>,\n user: User | null | undefined,\n) {\n if (isServer) {\n if (user && !user.refresh_token) {\n throw new Error(\n 'User must have a refresh_token field. Recieved: ' +\n JSON.stringify(user, null, 2),\n );\n }\n return new FrameworkClient({\n token: user?.refresh_token,\n db: db.core,\n });\n }\n\n // On the client, make sure we only have a single framework\n // in case our suspense provider gets unmounted\n const existing = db.core._reactor._frameworkClient;\n if (existing) {\n return existing;\n }\n const client = new FrameworkClient({ db: db.core });\n db.core._reactor.setFrameworkClient(client);\n return client;\n}\n\nexport const InstantSuspenseProvider = (\n props: InstantSuspenseProviderProps<any>,\n) => {\n if (!props.db) {\n throw new Error('Must provide db to InstantSuspenseProvider');\n }\n\n const db = props.db;\n\n const [trackedKeys] = useState(() => new Set<string>());\n\n const clientRef = useRef<FrameworkClient>(\n createFrameworkClient(props.db, props.user),\n );\n\n if (isServer) {\n clientRef.current.subscribe((result) => {\n const { queryHash } = result;\n trackedKeys.add(queryHash);\n });\n }\n\n const useSuspenseQuery = useCallback(\n isServer\n ? makeUseSuspenseQueryServer(clientRef.current)\n : makeUseSuspenseQueryClient(db, clientRef.current),\n [],\n );\n\n const contextValue = useMemo(() => {\n return { useSuspenseQuery, ssrUser: props.user };\n }, [useSuspenseQuery, props.user]);\n\n return (\n <SuspsenseQueryContext.Provider value={contextValue}>\n <stream.Provider\n nonce={props.nonce}\n onFlush={() => {\n const toSend: { queryKey: string; value: any }[] = [];\n for (const [key, value] of clientRef.current!.resultMap.entries()) {\n if (trackedKeys.has(key) && value.status === 'success') {\n toSend.push({\n queryKey: key,\n value: value.data,\n });\n }\n }\n\n trackedKeys.clear();\n return toSend;\n }}\n onEntries={(entries) => {\n entries.forEach((entry) => {\n clientRef.current!.addQueryResult(entry.queryKey, entry.value);\n });\n }}\n >\n {props.children}\n </stream.Provider>\n </SuspsenseQueryContext.Provider>\n );\n};\n"]}
@@ -48,7 +48,6 @@ export function createHydrationStreamProvider() {
48
48
  if (!stream.length) {
49
49
  return null;
50
50
  }
51
- // console.log(`pushing ${stream.length} entries`)
52
51
  const serializedCacheArgs = stream
53
52
  .map((entry) => transformer.serialize(entry))
54
53
  .map((entry) => JSON.stringify(entry))
@@ -1 +1 @@
1
- {"version":3,"file":"HydrationStreamProvider.js","sourceRoot":"","sources":["../../../src/next-ssr/HydrationStreamProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// This allows us to send serialized json from the server to the client using 'useServerInsertedHTML'\n// We are allowed to send whatever json we want, however it must be triggered at the same time as the client is recieving new html to display\n// after a suspsense boundary resolves\n// We do this to send a list of triples and attrs for each query that the server made so they can be pre-filled on the client render.\n// useServerInsertedHTML is called whenever a suspense boundary resolves, resulting in new html being streamed down to the client.\n\nexport const isServer = typeof window === 'undefined' || 'Deno' in globalThis;\n\nimport { useServerInsertedHTML } from 'next/navigation.js';\n\nimport * as React from 'react';\nimport { htmlEscapeJsonString } from './htmlescape.js';\n\nconst serializedSymbol = Symbol('serialized');\n\ninterface DataTransformer {\n serialize: (object: any) => any;\n deserialize: (object: any) => any;\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData;\n};\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>;\n deserialize: (obj: Serialized<TData>) => TData;\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string;\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void;\n };\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n );\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`;\n const idJSON = htmlEscapeJsonString(JSON.stringify(id));\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n );\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!isServer) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>;\n }\n return [];\n });\n const count = React.useRef(0);\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []));\n\n if (!stream.length) {\n return null;\n }\n // console.log(`pushing ${stream.length} entries`)\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',');\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0;\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ];\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n );\n });\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!isServer) {\n const win = window as any;\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n );\n props.onEntries(entries);\n };\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? [];\n\n onEntries(...winStream);\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n };\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n );\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n };\n}\n"]}
1
+ {"version":3,"file":"HydrationStreamProvider.js","sourceRoot":"","sources":["../../../src/next-ssr/HydrationStreamProvider.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// This allows us to send serialized json from the server to the client using 'useServerInsertedHTML'\n// We are allowed to send whatever json we want, however it must be triggered at the same time as the client is recieving new html to display\n// after a suspsense boundary resolves\n// We do this to send a list of triples and attrs for each query that the server made so they can be pre-filled on the client render.\n// useServerInsertedHTML is called whenever a suspense boundary resolves, resulting in new html being streamed down to the client.\n\nexport const isServer = typeof window === 'undefined' || 'Deno' in globalThis;\n\nimport { useServerInsertedHTML } from 'next/navigation.js';\n\nimport * as React from 'react';\nimport { htmlEscapeJsonString } from './htmlescape.js';\n\nconst serializedSymbol = Symbol('serialized');\n\ninterface DataTransformer {\n serialize: (object: any) => any;\n deserialize: (object: any) => any;\n}\n\ntype Serialized<TData> = unknown & {\n [serializedSymbol]: TData;\n};\n\ninterface TypedDataTransformer<TData> {\n serialize: (obj: TData) => Serialized<TData>;\n deserialize: (obj: Serialized<TData>) => TData;\n}\n\ninterface HydrationStreamContext<TShape> {\n id: string;\n stream: {\n /**\n * **Server method**\n * Push a new entry to the stream\n * Will be ignored on the client\n */\n push: (...shape: Array<TShape>) => void;\n };\n}\n\nexport interface HydrationStreamProviderProps<TShape> {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n}\n\nexport function createHydrationStreamProvider<TShape>() {\n const context = React.createContext<HydrationStreamContext<TShape>>(\n null as any,\n );\n /**\n\n * 1. (Happens on server): `useServerInsertedHTML()` is called **on the server** whenever a `Suspense`-boundary completes\n * - This means that we might have some new entries in the cache that needs to be flushed\n * - We pass these to the client by inserting a `<script>`-tag where we do `window[id].push(serializedVersionOfCache)`\n * 2. (Happens in browser) In `useEffect()`:\n * - We check if `window[id]` is set to an array and call `push()` on all the entries which will call `onEntries()` with the new entries\n * - We replace `window[id]` with a `push()`-method that will be called whenever new entries are received\n **/\n function UseClientHydrationStreamProvider(props: {\n children: React.ReactNode;\n /**\n * Optional transformer to serialize/deserialize the data\n * Example devalue, superjson et al\n */\n transformer?: DataTransformer;\n /**\n * **Client method**\n * Called in the browser when new entries are received\n */\n onEntries: (entries: Array<TShape>) => void;\n /**\n * **Server method**\n * onFlush is called on the server when the cache is flushed\n */\n onFlush?: () => Array<TShape>;\n /**\n * A nonce that'll allow the inline script to be executed when Content Security Policy is enforced\n */\n nonce?: string;\n }) {\n // unique id for the cache provider\n const id = `__RQ${React.useId()}`;\n const idJSON = htmlEscapeJsonString(JSON.stringify(id));\n\n const [transformer] = React.useState(\n () =>\n (props.transformer ?? {\n // noop\n serialize: (obj: any) => obj,\n deserialize: (obj: any) => obj,\n }) as unknown as TypedDataTransformer<TShape>,\n );\n\n // <server stuff>\n const [stream] = React.useState<Array<TShape>>(() => {\n if (!isServer) {\n return {\n push() {\n // no-op on the client\n },\n } as unknown as Array<TShape>;\n }\n return [];\n });\n const count = React.useRef(0);\n useServerInsertedHTML(() => {\n // This only happens on the server\n stream.push(...(props.onFlush?.() ?? []));\n\n if (!stream.length) {\n return null;\n }\n\n const serializedCacheArgs = stream\n .map((entry) => transformer.serialize(entry))\n .map((entry) => JSON.stringify(entry))\n .join(',');\n\n // Flush stream\n // eslint-disable-next-line react-hooks/immutability\n stream.length = 0;\n\n const html: Array<string> = [\n `window[${idJSON}] = window[${idJSON}] || [];`,\n `window[${idJSON}].push(${htmlEscapeJsonString(serializedCacheArgs)});`,\n ];\n return (\n <script\n key={count.current++}\n nonce={props.nonce}\n dangerouslySetInnerHTML={{\n __html: html.join(''),\n }}\n />\n );\n });\n // </server stuff>\n\n // <client stuff>\n // Setup and run the onEntries handler on the client only, but do it during\n // the initial render so children have access to the data immediately\n // This is important to avoid the client suspending during the initial render\n // if the data has not yet been hydrated.\n if (!isServer) {\n const win = window as any;\n if (!win[id]?.initialized) {\n // Client: consume cache:\n const onEntries = (...serializedEntries: Array<Serialized<TShape>>) => {\n const entries = serializedEntries.map((serialized) =>\n transformer.deserialize(serialized),\n );\n props.onEntries(entries);\n };\n\n const winStream: Array<Serialized<TShape>> = win[id] ?? [];\n\n onEntries(...winStream);\n\n // eslint-disable-next-line react-hooks/immutability\n win[id] = {\n initialized: true,\n push: onEntries,\n };\n }\n }\n // </client stuff>\n\n return (\n <context.Provider value={{ stream, id }}>\n {props.children}\n </context.Provider>\n );\n }\n\n return {\n Provider: UseClientHydrationStreamProvider,\n context,\n };\n}\n"]}
@@ -1,12 +1,9 @@
1
- import { InstantConfig, InstantSchemaDef, InstaQLResponse, PageInfoResponse, RuleParams, User, ValidQuery } from '@instantdb/core';
1
+ import { InstantSchemaDef, InstaQLResponse, PageInfoResponse, RuleParams, User, ValidQuery } from '@instantdb/core';
2
2
  import InstantReactWebDatabase from '../InstantReactWebDatabase.ts';
3
3
  type InstantSuspenseProviderProps<Schema extends InstantSchemaDef<any, any, any>> = {
4
4
  nonce?: string;
5
5
  children: React.ReactNode;
6
- db?: InstantReactWebDatabase<Schema, any>;
7
- config?: Omit<InstantConfig<any, any>, 'schema'> & {
8
- schema: string;
9
- };
6
+ db: InstantReactWebDatabase<Schema, any>;
10
7
  user?: User | null;
11
8
  };
12
9
  type SuspenseQueryContextValue = {
@@ -1 +1 @@
1
- {"version":3,"file":"InstantSuspenseProvider.d.ts","sourceRoot":"","sources":["../../../src/next-ssr/InstantSuspenseProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,IAAI,EACJ,UAAU,EACX,MAAM,iBAAiB,CAAC;AACzB,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AAQpE,KAAK,4BAA4B,CAC/B,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAC5C;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,EAAE,CAAC,EAAE,uBAAuB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,GAAG;QACjD,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB,CAAC;AAIF,KAAK,yBAAyB,GAAG;IAC/B,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,GAAG,CAAC;IAChE,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,qBAAqB,2DACqB,CAAC;AAGxD,eAAO,MAAM,sBAAsB,GACjC,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC9C,QAAQ,SAAS,OAAO,EAExB,KAAK,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,KAC7C,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,EAClC,CAAC,EAAE,CAAC,EACJ,IAAI,CAAC,EAAE;IACL,UAAU,EAAE,UAAU,CAAC;CACxB,KACE;IACH,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAChC,CAUA,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,OAAO,4BAA4B,CAAC,GAAG,CAAC,4CAkHzC,CAAC"}
1
+ {"version":3,"file":"InstantSuspenseProvider.d.ts","sourceRoot":"","sources":["../../../src/next-ssr/InstantSuspenseProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAGL,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,IAAI,EACJ,UAAU,EACX,MAAM,iBAAiB,CAAC;AACzB,OAAO,uBAAuB,MAAM,+BAA+B,CAAC;AAmBpE,KAAK,4BAA4B,CAC/B,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,IAC5C;IACF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,EAAE,EAAE,uBAAuB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CACpB,CAAC;AAIF,KAAK,yBAAyB,GAAG;IAC/B,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,iBAAiB,KAAK,GAAG,CAAC;IAChE,OAAO,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,eAAO,MAAM,qBAAqB,2DACqB,CAAC;AAGxD,eAAO,MAAM,sBAAsB,GACjC,MAAM,SAAS,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC9C,QAAQ,SAAS,OAAO,EAExB,KAAK,uBAAuB,CAAC,MAAM,EAAE,QAAQ,CAAC,KAC7C,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,EAAE,MAAM,CAAC,EAClC,CAAC,EAAE,CAAC,EACJ,IAAI,CAAC,EAAE;IACL,UAAU,EAAE,UAAU,CAAC;CACxB,KACE;IACH,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAChC,CAUA,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC;AAqJF,eAAO,MAAM,uBAAuB,GAClC,OAAO,4BAA4B,CAAC,GAAG,CAAC,4CA4DzC,CAAC"}
@@ -3,7 +3,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  // InstantSuspenseProvider can only be used in a client context so this prevents errors from trying to use it in a server component.
4
4
  import { FrameworkClient, } from '@instantdb/core';
5
5
  import { createHydrationStreamProvider, isServer, } from "./HydrationStreamProvider.js";
6
- import { createContext, useContext, useRef, useState } from 'react';
6
+ import { createContext, useCallback, useContext, useMemo, useRef, useState, useEffect, } from 'react';
7
+ import { useQueryInternal, } from '@instantdb/react-common';
7
8
  const stream = createHydrationStreamProvider();
8
9
  export const SuspsenseQueryContext = createContext(null);
9
10
  // Creates a typed useSuspense hook
@@ -16,48 +17,11 @@ export const createUseSuspenseQuery = (_db) => {
16
17
  return ctx.useSuspenseQuery(q, opts);
17
18
  };
18
19
  };
19
- export const InstantSuspenseProvider = (props) => {
20
- const clientRef = useRef(null);
21
- if (!props.db) {
22
- throw new Error('Must provide either a db or config to InstantSuspenseProvider');
23
- }
24
- const db = useRef(props.db);
25
- const [trackedKeys] = useState(() => new Set());
26
- if (!clientRef.current) {
27
- if (props.user && !props.user.refresh_token) {
28
- throw new Error('User must have a refresh_token field. Recieved: ' +
29
- JSON.stringify(props.user, null, 2));
30
- }
31
- clientRef.current = new FrameworkClient({
32
- token: props.user?.refresh_token,
33
- db: db.current.core,
34
- });
35
- }
36
- if (isServer) {
37
- clientRef.current.subscribe((result) => {
38
- const { queryHash } = result;
39
- trackedKeys.add(queryHash);
40
- });
41
- }
42
- const useSuspenseQuery = (query, opts) => {
43
- const nonSuspenseResult = db.current.useQuery(query, {
44
- ...opts,
45
- });
46
- if (nonSuspenseResult.data) {
47
- return {
48
- data: nonSuspenseResult.data,
49
- pageInfo: nonSuspenseResult.pageInfo,
50
- };
51
- }
52
- // should never happen (typeguard)
53
- if (!clientRef.current) {
54
- throw new Error('Client ref not set up');
55
- }
56
- let entry = clientRef.current.getExistingResultForQuery(query, {
57
- ruleParams: opts?.ruleParams,
58
- });
20
+ function makeUseSuspenseQueryServer(client) {
21
+ return function useSuspenseQueryServer(query, opts) {
22
+ let entry = client.getExistingResultForQuery(query, opts);
59
23
  if (!entry) {
60
- entry = clientRef.current.query(query, opts);
24
+ entry = client.query(query, opts);
61
25
  }
62
26
  if (entry.status === 'pending') {
63
27
  throw entry.promise;
@@ -65,16 +29,120 @@ export const InstantSuspenseProvider = (props) => {
65
29
  if (entry.status === 'error') {
66
30
  throw entry.error;
67
31
  }
68
- if (entry.status === 'success' && entry.type === 'session') {
69
- return entry.data;
70
- }
71
32
  if (entry.status === 'success') {
72
- const data = entry.data;
73
- const result = clientRef.current.completeIsomorphic(query, data.triples, data.attrs, data.pageInfo);
74
- return result;
33
+ switch (entry.type) {
34
+ case 'session': {
35
+ return entry.data;
36
+ }
37
+ case 'http': {
38
+ const data = entry.data;
39
+ const result = client.completeIsomorphic(query, data.triples, data.attrs, data.pageInfo);
40
+ return result;
41
+ }
42
+ }
43
+ }
44
+ };
45
+ }
46
+ function makeUseSuspenseQueryClient(db, client) {
47
+ function getEntry(query, opts, allowFetch) {
48
+ const entry = client.getExistingResultForQuery(query, opts);
49
+ if (entry?.status === 'pending') {
50
+ throw entry.promise;
51
+ }
52
+ if (entry?.status === 'error') {
53
+ throw entry.error;
54
+ }
55
+ if (entry?.status === 'success') {
56
+ switch (entry.type) {
57
+ case 'session': {
58
+ return entry.data;
59
+ }
60
+ case 'http': {
61
+ const data = entry.data;
62
+ const result = client.completeIsomorphic(query, data.triples, data.attrs, data.pageInfo);
63
+ return result;
64
+ }
65
+ }
66
+ }
67
+ if (allowFetch) {
68
+ const promise = client.queryClient(query, opts);
69
+ throw promise;
70
+ }
71
+ }
72
+ return function useSuspenseQueryClient(query, opts) {
73
+ const useQueryResult = useQueryInternal(db.core, query, opts,
74
+ // Returns the server result for useSyncExternalStore
75
+ () => {
76
+ try {
77
+ const res = getEntry(query, opts, false);
78
+ return res;
79
+ }
80
+ catch (throwable) {
81
+ return { error: throwable };
82
+ }
83
+ });
84
+ const hasData = !!useQueryResult.state.data;
85
+ const queryHash = useQueryResult.queryHash;
86
+ useEffect(() => {
87
+ if (hasData) {
88
+ // We have a newer result, so remove the cached SSR or suspended
89
+ // result from the framework client cache
90
+ client.removeCachedQueryResult(queryHash);
91
+ }
92
+ }, [hasData, queryHash]);
93
+ if (useQueryResult.state.data) {
94
+ return {
95
+ data: useQueryResult.state.data,
96
+ pageInfo: useQueryResult.state.pageInfo,
97
+ };
98
+ }
99
+ if (useQueryResult.state.error) {
100
+ throw useQueryResult.state.error;
75
101
  }
102
+ return getEntry(query, opts, true);
76
103
  };
77
- return (_jsx(SuspsenseQueryContext.Provider, { value: { useSuspenseQuery, ssrUser: props.user }, children: _jsx(stream.Provider, { nonce: props.nonce, onFlush: () => {
104
+ }
105
+ function createFrameworkClient(db, user) {
106
+ if (isServer) {
107
+ if (user && !user.refresh_token) {
108
+ throw new Error('User must have a refresh_token field. Recieved: ' +
109
+ JSON.stringify(user, null, 2));
110
+ }
111
+ return new FrameworkClient({
112
+ token: user?.refresh_token,
113
+ db: db.core,
114
+ });
115
+ }
116
+ // On the client, make sure we only have a single framework
117
+ // in case our suspense provider gets unmounted
118
+ const existing = db.core._reactor._frameworkClient;
119
+ if (existing) {
120
+ return existing;
121
+ }
122
+ const client = new FrameworkClient({ db: db.core });
123
+ db.core._reactor.setFrameworkClient(client);
124
+ return client;
125
+ }
126
+ export const InstantSuspenseProvider = (props) => {
127
+ if (!props.db) {
128
+ throw new Error('Must provide db to InstantSuspenseProvider');
129
+ }
130
+ const db = props.db;
131
+ const [trackedKeys] = useState(() => new Set());
132
+ const clientRef = useRef(createFrameworkClient(props.db, props.user));
133
+ if (isServer) {
134
+ clientRef.current.subscribe((result) => {
135
+ const { queryHash } = result;
136
+ trackedKeys.add(queryHash);
137
+ });
138
+ }
139
+ const useSuspenseQuery = useCallback(isServer
140
+ ? makeUseSuspenseQueryServer(clientRef.current)
141
+ : makeUseSuspenseQueryClient(db, clientRef.current), []);
142
+ const contextValue = useMemo(() => {
143
+ return { useSuspenseQuery, ssrUser: props.user };
144
+ }, [useSuspenseQuery, props.user]);
145
+ return (_jsx(SuspsenseQueryContext.Provider, { value: contextValue, children: _jsx(stream.Provider, { nonce: props.nonce, onFlush: () => {
78
146
  const toSend = [];
79
147
  for (const [key, value] of clientRef.current.resultMap.entries()) {
80
148
  if (trackedKeys.has(key) && value.status === 'success') {