tempest-react-sdk 0.51.0 → 0.53.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.
- package/README.md +35 -5
- package/dist/audio/audio-bus.cjs +2 -0
- package/dist/audio/audio-bus.cjs.map +1 -0
- package/dist/audio/audio-bus.js +116 -0
- package/dist/audio/audio-bus.js.map +1 -0
- package/dist/audio/use-audio-bus.cjs +2 -0
- package/dist/audio/use-audio-bus.cjs.map +1 -0
- package/dist/audio/use-audio-bus.js +11 -0
- package/dist/audio/use-audio-bus.js.map +1 -0
- package/dist/auth/refresh-queue.cjs +1 -1
- package/dist/auth/refresh-queue.cjs.map +1 -1
- package/dist/auth/refresh-queue.js +6 -6
- package/dist/auth/refresh-queue.js.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.js +23 -23
- package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.cjs +1 -1
- package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.js +1 -1
- package/dist/components/Scheduler/Scheduler.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.module.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.module.js.map +1 -1
- package/dist/components/Slider/Slider.cjs +1 -1
- package/dist/components/Slider/Slider.cjs.map +1 -1
- package/dist/components/Slider/Slider.js +9 -9
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/forms/FormField.cjs +1 -1
- package/dist/forms/FormField.cjs.map +1 -1
- package/dist/forms/FormField.js +23 -21
- package/dist/forms/FormField.js.map +1 -1
- package/dist/http/describe-api-error.cjs +1 -1
- package/dist/http/describe-api-error.cjs.map +1 -1
- package/dist/http/describe-api-error.js +4 -2
- package/dist/http/describe-api-error.js.map +1 -1
- package/dist/http/use-describe-api-error.cjs +1 -1
- package/dist/http/use-describe-api-error.cjs.map +1 -1
- package/dist/http/use-describe-api-error.js +3 -2
- package/dist/http/use-describe-api-error.js.map +1 -1
- package/dist/offline/create-offline-database.cjs +2 -0
- package/dist/offline/create-offline-database.cjs.map +1 -0
- package/dist/offline/create-offline-database.js +29 -0
- package/dist/offline/create-offline-database.js.map +1 -0
- package/dist/offline/create-offline-store.cjs +1 -1
- package/dist/offline/create-offline-store.cjs.map +1 -1
- package/dist/offline/create-offline-store.js +29 -25
- package/dist/offline/create-offline-store.js.map +1 -1
- package/dist/query/QueryProvider.cjs +1 -1
- package/dist/query/QueryProvider.cjs.map +1 -1
- package/dist/query/QueryProvider.js +13 -11
- package/dist/query/QueryProvider.js.map +1 -1
- package/dist/query/foreign-client-warning.cjs +2 -0
- package/dist/query/foreign-client-warning.cjs.map +1 -0
- package/dist/query/foreign-client-warning.js +12 -0
- package/dist/query/foreign-client-warning.js.map +1 -0
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +695 -41
- package/dist/tempest-react-sdk.js +52 -46
- package/dist/utils/duplicate-instance.cjs +2 -0
- package/dist/utils/duplicate-instance.cjs.map +1 -0
- package/dist/utils/duplicate-instance.js +6 -0
- package/dist/utils/duplicate-instance.js.map +1 -0
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +8 -3
- package/dist/utils/format.js.map +1 -1
- package/dist/webrtc/opus-sdp.cjs +3 -0
- package/dist/webrtc/opus-sdp.cjs.map +1 -0
- package/dist/webrtc/opus-sdp.js +94 -0
- package/dist/webrtc/opus-sdp.js.map +1 -0
- package/dist/webrtc/sender-bitrate.cjs +2 -0
- package/dist/webrtc/sender-bitrate.cjs.map +1 -0
- package/dist/webrtc/sender-bitrate.js +15 -0
- package/dist/webrtc/sender-bitrate.js.map +1 -0
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +123 -47
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/resilience.cjs +2 -0
- package/dist/ws/resilience.cjs.map +1 -0
- package/dist/ws/resilience.js +21 -0
- package/dist/ws/resilience.js.map +1 -0
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +37 -23
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"describe-api-error.js","names":[],"sources":["../../src/http/describe-api-error.ts"],"sourcesContent":["// The last mile of error handling: a typed error is what code reads, a sentence\n// is what a person reads, and every app was writing the funnel between the two.\n// The case everyone forgets is the request that never reached the server, which\n// without special handling renders as the nonsense \"erro 0\".\n\nimport { isApiError, syntheticDetail } from \"./errors\";\n\n/** The fixed sentences {@link describeApiError} may need. */\nexport interface ApiErrorStrings {\n /** Shown when the request never reached the server. */\n offline: string;\n /**\n * Shown when the backend rejected the payload field by field.\n *\n * The per-field messages are on `error.fields`, to be attached to the inputs\n * themselves; this sentence is what the toast says.\n */\n validation: string;\n}\n\n/**\n * PT-BR defaults, used when no strings are supplied and no catalog answers.\n *\n * The SDK's copy is pt-BR everywhere else (`FilterBar`, `DataTable`, `Chat`), so\n * the default here matches rather than introducing an English string that only\n * shows up on a network failure.\n */\nexport const DEFAULT_API_ERROR_STRINGS: ApiErrorStrings = {\n offline: \"Sem conexão com o servidor. Verifique sua internet e tente de novo.\",\n validation: \"Confira os campos destacados e tente de novo.\",\n};\n\n/**\n * Translation key the {@link useDescribeApiError} hook looks up.\n *\n * A catalog that does not define it falls back to\n * {@link DEFAULT_API_ERROR_STRINGS}, because `t` returns the key itself when the\n * lookup misses and printing `tempest.error.offline` at the user would be worse\n * than printing pt-BR at them.\n */\nexport const API_ERROR_OFFLINE_KEY = \"tempest.error.offline\";\n\n/**\n * Translation key for the validation sentence, looked up the same way as\n * {@link API_ERROR_OFFLINE_KEY}.\n */\nexport const API_ERROR_VALIDATION_KEY = \"tempest.error.validation\";\n\n/**\n * Whether the browser currently reports itself as offline.\n *\n * `fetch` rejects a network failure with a plain `TypeError` whose message\n * differs per browser (\"Failed to fetch\", \"NetworkError when attempting to fetch\n * resource.\", \"Load failed\"), so sniffing the message is not portable. The online\n * flag is, and it is the signal that matters for the sentence being chosen.\n *\n * @returns `true` only when the environment positively says it is offline.\n */\nfunction browserIsOffline(): boolean {\n return typeof navigator !== \"undefined\" && navigator.onLine === false;\n}\n\n/**\n * Turn any caught value into a sentence worth showing.\n *\n * The funnel, in order:\n *\n * 1. A request that never reached the server — `status === 0`, or a non-API\n * error thrown while the browser reports itself offline — produces the\n * offline sentence. This is the step apps skip, and skipping it renders\n * \"erro 0\" or a raw `TypeError` at the user.\n * 2. A validation rejection — `error.fields` is set — produces the validation\n * sentence, **not** `detail`. On a `422` the `detail` line is assembled from\n * the backend's field paths and the validator's own wording\n * (`\"items.0.price: Input should be greater than 0\"`), which is right for a\n * log and wrong for a person: it is half English in a pt-BR screen and it\n * names internals. The per-field messages stay on `fields`, where a form can\n * attach them to the inputs that failed.\n * 3. The backend's own `detail`, which is the most specific thing available and\n * is already written for a person.\n * 4. `fallback`, with `(HTTP <status>)` appended when a status is known, so the\n * screenshot in the support ticket carries the one fact a developer needs.\n *\n * Pure on purpose: it works in an interceptor, in a logger and anywhere outside\n * the React tree. {@link useDescribeApiError} is the same funnel with the\n * sentences resolved through `I18nProvider`.\n *\n * @example\n * catch (error) {\n * toast(describeApiError(error, \"Não foi possível salvar o pedido\"));\n * }\n *\n * @param error - The caught value, of any shape.\n * @param fallback - What to say when the error carries nothing better.\n * @param
|
|
1
|
+
{"version":3,"file":"describe-api-error.js","names":[],"sources":["../../src/http/describe-api-error.ts"],"sourcesContent":["// The last mile of error handling: a typed error is what code reads, a sentence\n// is what a person reads, and every app was writing the funnel between the two.\n// The case everyone forgets is the request that never reached the server, which\n// without special handling renders as the nonsense \"erro 0\".\n\nimport { isApiError, syntheticDetail } from \"./errors\";\n\n/** The fixed sentences {@link describeApiError} may need. */\nexport interface ApiErrorStrings {\n /** Shown when the request never reached the server. */\n offline: string;\n /**\n * Shown when the backend rejected the payload field by field.\n *\n * The per-field messages are on `error.fields`, to be attached to the inputs\n * themselves; this sentence is what the toast says.\n */\n validation: string;\n}\n\n/**\n * PT-BR defaults, used when no strings are supplied and no catalog answers.\n *\n * The SDK's copy is pt-BR everywhere else (`FilterBar`, `DataTable`, `Chat`), so\n * the default here matches rather than introducing an English string that only\n * shows up on a network failure.\n */\nexport const DEFAULT_API_ERROR_STRINGS: ApiErrorStrings = {\n offline: \"Sem conexão com o servidor. Verifique sua internet e tente de novo.\",\n validation: \"Confira os campos destacados e tente de novo.\",\n};\n\n/**\n * Translation key the {@link useDescribeApiError} hook looks up.\n *\n * A catalog that does not define it falls back to\n * {@link DEFAULT_API_ERROR_STRINGS}, because `t` returns the key itself when the\n * lookup misses and printing `tempest.error.offline` at the user would be worse\n * than printing pt-BR at them.\n */\nexport const API_ERROR_OFFLINE_KEY = \"tempest.error.offline\";\n\n/**\n * Translation key for the validation sentence, looked up the same way as\n * {@link API_ERROR_OFFLINE_KEY}.\n */\nexport const API_ERROR_VALIDATION_KEY = \"tempest.error.validation\";\n\n/**\n * Everything {@link describeApiError} accepts beyond the error and the fallback.\n *\n * Extends the fixed sentences rather than sitting beside them, so a caller that\n * already passed `{ offline, validation }` keeps compiling untouched.\n */\nexport interface DescribeApiErrorOptions extends Partial<ApiErrorStrings> {\n /**\n * Maps the backend's programmatic `code` to a sentence in your language.\n *\n * The client already surfaces `code` on `ApiError`, but without this every\n * app writes the same `switch` over it. A hit here wins over every other\n * step: it is the only sentence written for that exact case, by someone who\n * knew both the backend contract and the screen it lands on.\n */\n codes?: Readonly<Record<string, string>>;\n /**\n * Whether the backend's `detail` may be shown when no `code` matched.\n * Default `true`.\n *\n * Set it to `false` when `detail` is written for developers rather than\n * users, or when it could echo internals — the result is then always either\n * a sentence you wrote or the fallback.\n */\n useDetail?: boolean;\n}\n\n/**\n * Whether the browser currently reports itself as offline.\n *\n * `fetch` rejects a network failure with a plain `TypeError` whose message\n * differs per browser (\"Failed to fetch\", \"NetworkError when attempting to fetch\n * resource.\", \"Load failed\"), so sniffing the message is not portable. The online\n * flag is, and it is the signal that matters for the sentence being chosen.\n *\n * @returns `true` only when the environment positively says it is offline.\n */\nfunction browserIsOffline(): boolean {\n return typeof navigator !== \"undefined\" && navigator.onLine === false;\n}\n\n/**\n * Turn any caught value into a sentence worth showing.\n *\n * The funnel, in order:\n *\n * 0. `codes[error.code]` — the sentence you wrote for that exact backend case.\n * Checked first because nothing the funnel derives can beat it, and because a\n * request that never landed carries no `code` for it to shadow.\n * 1. A request that never reached the server — `status === 0`, or a non-API\n * error thrown while the browser reports itself offline — produces the\n * offline sentence. This is the step apps skip, and skipping it renders\n * \"erro 0\" or a raw `TypeError` at the user.\n * 2. A validation rejection — `error.fields` is set — produces the validation\n * sentence, **not** `detail`. On a `422` the `detail` line is assembled from\n * the backend's field paths and the validator's own wording\n * (`\"items.0.price: Input should be greater than 0\"`), which is right for a\n * log and wrong for a person: it is half English in a pt-BR screen and it\n * names internals. The per-field messages stay on `fields`, where a form can\n * attach them to the inputs that failed.\n * 3. The backend's own `detail`, which is the most specific thing available and\n * is already written for a person — unless `useDetail: false` says that text\n * is for developers.\n * 4. `fallback`, with `(HTTP <status>)` appended when a status is known, so the\n * screenshot in the support ticket carries the one fact a developer needs.\n *\n * Pure on purpose: it works in an interceptor, in a logger and anywhere outside\n * the React tree. {@link useDescribeApiError} is the same funnel with the\n * sentences resolved through `I18nProvider`.\n *\n * @example\n * catch (error) {\n * toast(describeApiError(error, \"Não foi possível salvar o pedido\"));\n * }\n *\n * @example\n * catch (error) {\n * toast(\n * describeApiError(error, \"Não foi possível se candidatar\", {\n * codes: {\n * SERVICE_FULL: \"Este serviço atingiu o limite de vagas.\",\n * CANDIDATE_ALREADY_EXISTS: \"Você já se candidatou a este serviço.\",\n * },\n * useDetail: false,\n * }),\n * );\n * }\n *\n * @param error - The caught value, of any shape.\n * @param fallback - What to say when the error carries nothing better.\n * @param options - A `codes` catalog, `useDetail`, and overrides for the fixed\n * sentences.\n * @returns A sentence to show the user.\n */\nexport function describeApiError(\n error: unknown,\n fallback: string,\n options?: DescribeApiErrorOptions,\n): string {\n const offline = options?.offline ?? DEFAULT_API_ERROR_STRINGS.offline;\n\n if (isApiError(error)) {\n const mapped = error.code === undefined ? undefined : options?.codes?.[error.code];\n if (mapped !== undefined) return mapped;\n if (error.status === 0) return offline;\n if (error.fields && Object.keys(error.fields).length > 0) {\n return options?.validation ?? DEFAULT_API_ERROR_STRINGS.validation;\n }\n const detail = error.detail.trim();\n if (\n options?.useDetail !== false &&\n detail !== \"\" &&\n detail !== syntheticDetail(error.status)\n ) {\n return detail;\n }\n return `${fallback} (HTTP ${error.status})`;\n }\n\n if (browserIsOffline()) return offline;\n\n return fallback;\n}\n"],"mappings":";;AA2BA,IAAa,IAA6C;CACtD,SAAS;CACT,YAAY;AAChB,GAUa,IAAwB,yBAMxB,IAA2B;AAuCxC,SAAS,IAA4B;CACjC,OAAO,OAAO,YAAc,OAAe,UAAU,WAAW;AACpE;AAuDA,SAAgB,EACZ,GACA,GACA,GACM;CACN,IAAM,IAAU,GAAS,WAAW,EAA0B;CAE9D,IAAI,EAAW,CAAK,GAAG;EACnB,IAAM,IAAS,EAAM,SAAS,KAAA,IAAY,KAAA,IAAY,GAAS,QAAQ,EAAM;EAC7E,IAAI,MAAW,KAAA,GAAW,OAAO;EACjC,IAAI,EAAM,WAAW,GAAG,OAAO;EAC/B,IAAI,EAAM,UAAU,OAAO,KAAK,EAAM,MAAM,CAAC,CAAC,SAAS,GACnD,OAAO,GAAS,cAAc,EAA0B;EAE5D,IAAM,IAAS,EAAM,OAAO,KAAK;EAQjC,OANI,GAAS,cAAc,MACvB,MAAW,MACX,MAAW,EAAgB,EAAM,MAAM,IAEhC,IAEJ,GAAG,EAAS,SAAS,EAAM,OAAO;CAC7C;CAIA,OAFI,EAAiB,IAAU,IAExB;AACX"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./describe-api-error.cjs"),t=require("../i18n/I18nProvider.cjs");let n=require("react");function r(t,n,r){let i=e.DEFAULT_API_ERROR_STRINGS[r];return t?.(n,void 0,{default:i})??i}function i(){let i=t.useOptionalI18n()?.t;return(0,n.useCallback)((t,n)=>e.describeApiError(t,n,{offline:r(i,e.API_ERROR_OFFLINE_KEY,`offline`),validation:r(i,e.API_ERROR_VALIDATION_KEY,`validation`)}),[i])}exports.useDescribeApiError=i;
|
|
1
|
+
const e=require("./describe-api-error.cjs"),t=require("../i18n/I18nProvider.cjs");let n=require("react");function r(t,n,r){let i=e.DEFAULT_API_ERROR_STRINGS[r];return t?.(n,void 0,{default:i})??i}function i(){let i=t.useOptionalI18n()?.t;return(0,n.useCallback)((t,n,a)=>e.describeApiError(t,n,{offline:r(i,e.API_ERROR_OFFLINE_KEY,`offline`),validation:r(i,e.API_ERROR_VALIDATION_KEY,`validation`),...a}),[i])}exports.useDescribeApiError=i;
|
|
2
2
|
//# sourceMappingURL=use-describe-api-error.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-describe-api-error.cjs","names":[],"sources":["../../src/http/use-describe-api-error.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { I18n } from \"../i18n/create-i18n\";\nimport { useOptionalI18n } from \"../i18n/I18nProvider\";\nimport {\n API_ERROR_OFFLINE_KEY,\n API_ERROR_VALIDATION_KEY,\n DEFAULT_API_ERROR_STRINGS,\n describeApiError,\n} from \"./describe-api-error\";\n\n/**\n * {@link describeApiError} with its fixed sentences resolved through the active\n * `I18nProvider`.\n *\n * The funnel is not duplicated — this hook only supplies the strings and calls\n * the pure function. Which is also why both exist: the pure one runs in an\n * interceptor or a logger, where there is no React tree to read a context from,\n * and this one runs in a component without every caller passing translations\n * down by hand.\n *\n * Works with no provider at all: i18n is opt-in in this SDK, so a missing\n * provider — or a catalog that never defined `tempest.error.offline` — falls\n * back to the pt-BR default rather than crashing or printing the raw key.\n *\n * @example\n * const describe = useDescribeApiError();\n * const { mutate } = useMutation({\n * mutationFn: save,\n * onError: (error) => toast(describe(error, t(\"orders.saveFailed\"))),\n * });\n *\n * @returns A stable `(error, fallback) => string` function.\n */\n/**\n * Look one sentence up in the active catalog, falling back to the pt-BR default.\n *\n * The miss is the i18n layer's answer, through `t`'s `default`. It used to be\n * re-derived here by comparing the result against the key that had just been\n * passed in, which is wrong for a catalog that maps a key to itself —\n * `{ \"tempest.error.offline\": \"tempest.error.offline\" }`, which is what a\n * machine-generated or placeholder catalog produces. There the app's own\n * translation lost to the pt-BR default. Only the catalog's owner can answer\n * \"was this key defined\", and `lookup()` already knows.\n *\n * @param translate - The active catalog's `t`, if any provider is mounted.\n * @param key - The translation key to look up.\n * @param fallbackKey - Which {@link DEFAULT_API_ERROR_STRINGS} entry to use on a miss.\n * @returns The sentence to hand to `describeApiError`.\n */\nfunction resolve(\n translate: I18n[\"t\"] | undefined,\n key: string,\n fallbackKey: keyof typeof DEFAULT_API_ERROR_STRINGS,\n): string {\n const fallback = DEFAULT_API_ERROR_STRINGS[fallbackKey];\n return translate?.(key, undefined, { default: fallback }) ?? fallback;\n}\n\nexport function useDescribeApiError(): (error: unknown
|
|
1
|
+
{"version":3,"file":"use-describe-api-error.cjs","names":[],"sources":["../../src/http/use-describe-api-error.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { I18n } from \"../i18n/create-i18n\";\nimport { useOptionalI18n } from \"../i18n/I18nProvider\";\nimport type { DescribeApiErrorOptions } from \"./describe-api-error\";\nimport {\n API_ERROR_OFFLINE_KEY,\n API_ERROR_VALIDATION_KEY,\n DEFAULT_API_ERROR_STRINGS,\n describeApiError,\n} from \"./describe-api-error\";\n\n/**\n * {@link describeApiError} with its fixed sentences resolved through the active\n * `I18nProvider`.\n *\n * The funnel is not duplicated — this hook only supplies the strings and calls\n * the pure function. Which is also why both exist: the pure one runs in an\n * interceptor or a logger, where there is no React tree to read a context from,\n * and this one runs in a component without every caller passing translations\n * down by hand.\n *\n * Works with no provider at all: i18n is opt-in in this SDK, so a missing\n * provider — or a catalog that never defined `tempest.error.offline` — falls\n * back to the pt-BR default rather than crashing or printing the raw key.\n *\n * The per-call options are the pure function's, so a screen that knows the\n * backend codes it can hit passes them at the call site while the translated\n * sentences keep coming from the provider.\n *\n * @example\n * const describe = useDescribeApiError();\n * const { mutate } = useMutation({\n * mutationFn: save,\n * onError: (error) => toast(describe(error, t(\"orders.saveFailed\"))),\n * });\n *\n * @returns A stable `(error, fallback, options?) => string` function.\n */\n/**\n * Look one sentence up in the active catalog, falling back to the pt-BR default.\n *\n * The miss is the i18n layer's answer, through `t`'s `default`. It used to be\n * re-derived here by comparing the result against the key that had just been\n * passed in, which is wrong for a catalog that maps a key to itself —\n * `{ \"tempest.error.offline\": \"tempest.error.offline\" }`, which is what a\n * machine-generated or placeholder catalog produces. There the app's own\n * translation lost to the pt-BR default. Only the catalog's owner can answer\n * \"was this key defined\", and `lookup()` already knows.\n *\n * @param translate - The active catalog's `t`, if any provider is mounted.\n * @param key - The translation key to look up.\n * @param fallbackKey - Which {@link DEFAULT_API_ERROR_STRINGS} entry to use on a miss.\n * @returns The sentence to hand to `describeApiError`.\n */\nfunction resolve(\n translate: I18n[\"t\"] | undefined,\n key: string,\n fallbackKey: keyof typeof DEFAULT_API_ERROR_STRINGS,\n): string {\n const fallback = DEFAULT_API_ERROR_STRINGS[fallbackKey];\n return translate?.(key, undefined, { default: fallback }) ?? fallback;\n}\n\nexport function useDescribeApiError(): (\n error: unknown,\n fallback: string,\n options?: DescribeApiErrorOptions,\n) => string {\n const i18n = useOptionalI18n();\n const translate = i18n?.t;\n\n return useCallback(\n (error: unknown, fallback: string, options?: DescribeApiErrorOptions) => {\n return describeApiError(error, fallback, {\n offline: resolve(translate, API_ERROR_OFFLINE_KEY, \"offline\"),\n validation: resolve(translate, API_ERROR_VALIDATION_KEY, \"validation\"),\n ...options,\n });\n },\n [translate],\n );\n}\n"],"mappings":"yGAuDA,SAAS,EACL,EACA,EACA,EACM,CACN,IAAM,EAAW,EAAA,0BAA0B,GAC3C,OAAO,IAAY,EAAK,IAAA,GAAW,CAAE,QAAS,CAAS,CAAC,GAAK,CACjE,CAEA,SAAgB,GAIJ,CAER,IAAM,EADO,EAAA,gBACK,CAAA,EAAM,EAExB,OAAA,EAAO,EAAA,YAAA,EACF,EAAgB,EAAkB,IACxB,EAAA,iBAAiB,EAAO,EAAU,CACrC,QAAS,EAAQ,EAAW,EAAA,sBAAuB,SAAS,EAC5D,WAAY,EAAQ,EAAW,EAAA,yBAA0B,YAAY,EACrE,GAAG,CACP,CAAC,EAEL,CAAC,CAAS,CACd,CACJ"}
|
|
@@ -8,9 +8,10 @@ function o(e, t, r) {
|
|
|
8
8
|
}
|
|
9
9
|
function s() {
|
|
10
10
|
let n = i()?.t;
|
|
11
|
-
return a((i, a) => r(i, a, {
|
|
11
|
+
return a((i, a, s) => r(i, a, {
|
|
12
12
|
offline: o(n, e, "offline"),
|
|
13
|
-
validation: o(n, t, "validation")
|
|
13
|
+
validation: o(n, t, "validation"),
|
|
14
|
+
...s
|
|
14
15
|
}), [n]);
|
|
15
16
|
}
|
|
16
17
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-describe-api-error.js","names":[],"sources":["../../src/http/use-describe-api-error.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { I18n } from \"../i18n/create-i18n\";\nimport { useOptionalI18n } from \"../i18n/I18nProvider\";\nimport {\n API_ERROR_OFFLINE_KEY,\n API_ERROR_VALIDATION_KEY,\n DEFAULT_API_ERROR_STRINGS,\n describeApiError,\n} from \"./describe-api-error\";\n\n/**\n * {@link describeApiError} with its fixed sentences resolved through the active\n * `I18nProvider`.\n *\n * The funnel is not duplicated — this hook only supplies the strings and calls\n * the pure function. Which is also why both exist: the pure one runs in an\n * interceptor or a logger, where there is no React tree to read a context from,\n * and this one runs in a component without every caller passing translations\n * down by hand.\n *\n * Works with no provider at all: i18n is opt-in in this SDK, so a missing\n * provider — or a catalog that never defined `tempest.error.offline` — falls\n * back to the pt-BR default rather than crashing or printing the raw key.\n *\n * @example\n * const describe = useDescribeApiError();\n * const { mutate } = useMutation({\n * mutationFn: save,\n * onError: (error) => toast(describe(error, t(\"orders.saveFailed\"))),\n * });\n *\n * @returns A stable `(error, fallback) => string` function.\n */\n/**\n * Look one sentence up in the active catalog, falling back to the pt-BR default.\n *\n * The miss is the i18n layer's answer, through `t`'s `default`. It used to be\n * re-derived here by comparing the result against the key that had just been\n * passed in, which is wrong for a catalog that maps a key to itself —\n * `{ \"tempest.error.offline\": \"tempest.error.offline\" }`, which is what a\n * machine-generated or placeholder catalog produces. There the app's own\n * translation lost to the pt-BR default. Only the catalog's owner can answer\n * \"was this key defined\", and `lookup()` already knows.\n *\n * @param translate - The active catalog's `t`, if any provider is mounted.\n * @param key - The translation key to look up.\n * @param fallbackKey - Which {@link DEFAULT_API_ERROR_STRINGS} entry to use on a miss.\n * @returns The sentence to hand to `describeApiError`.\n */\nfunction resolve(\n translate: I18n[\"t\"] | undefined,\n key: string,\n fallbackKey: keyof typeof DEFAULT_API_ERROR_STRINGS,\n): string {\n const fallback = DEFAULT_API_ERROR_STRINGS[fallbackKey];\n return translate?.(key, undefined, { default: fallback }) ?? fallback;\n}\n\nexport function useDescribeApiError(): (error: unknown
|
|
1
|
+
{"version":3,"file":"use-describe-api-error.js","names":[],"sources":["../../src/http/use-describe-api-error.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { I18n } from \"../i18n/create-i18n\";\nimport { useOptionalI18n } from \"../i18n/I18nProvider\";\nimport type { DescribeApiErrorOptions } from \"./describe-api-error\";\nimport {\n API_ERROR_OFFLINE_KEY,\n API_ERROR_VALIDATION_KEY,\n DEFAULT_API_ERROR_STRINGS,\n describeApiError,\n} from \"./describe-api-error\";\n\n/**\n * {@link describeApiError} with its fixed sentences resolved through the active\n * `I18nProvider`.\n *\n * The funnel is not duplicated — this hook only supplies the strings and calls\n * the pure function. Which is also why both exist: the pure one runs in an\n * interceptor or a logger, where there is no React tree to read a context from,\n * and this one runs in a component without every caller passing translations\n * down by hand.\n *\n * Works with no provider at all: i18n is opt-in in this SDK, so a missing\n * provider — or a catalog that never defined `tempest.error.offline` — falls\n * back to the pt-BR default rather than crashing or printing the raw key.\n *\n * The per-call options are the pure function's, so a screen that knows the\n * backend codes it can hit passes them at the call site while the translated\n * sentences keep coming from the provider.\n *\n * @example\n * const describe = useDescribeApiError();\n * const { mutate } = useMutation({\n * mutationFn: save,\n * onError: (error) => toast(describe(error, t(\"orders.saveFailed\"))),\n * });\n *\n * @returns A stable `(error, fallback, options?) => string` function.\n */\n/**\n * Look one sentence up in the active catalog, falling back to the pt-BR default.\n *\n * The miss is the i18n layer's answer, through `t`'s `default`. It used to be\n * re-derived here by comparing the result against the key that had just been\n * passed in, which is wrong for a catalog that maps a key to itself —\n * `{ \"tempest.error.offline\": \"tempest.error.offline\" }`, which is what a\n * machine-generated or placeholder catalog produces. There the app's own\n * translation lost to the pt-BR default. Only the catalog's owner can answer\n * \"was this key defined\", and `lookup()` already knows.\n *\n * @param translate - The active catalog's `t`, if any provider is mounted.\n * @param key - The translation key to look up.\n * @param fallbackKey - Which {@link DEFAULT_API_ERROR_STRINGS} entry to use on a miss.\n * @returns The sentence to hand to `describeApiError`.\n */\nfunction resolve(\n translate: I18n[\"t\"] | undefined,\n key: string,\n fallbackKey: keyof typeof DEFAULT_API_ERROR_STRINGS,\n): string {\n const fallback = DEFAULT_API_ERROR_STRINGS[fallbackKey];\n return translate?.(key, undefined, { default: fallback }) ?? fallback;\n}\n\nexport function useDescribeApiError(): (\n error: unknown,\n fallback: string,\n options?: DescribeApiErrorOptions,\n) => string {\n const i18n = useOptionalI18n();\n const translate = i18n?.t;\n\n return useCallback(\n (error: unknown, fallback: string, options?: DescribeApiErrorOptions) => {\n return describeApiError(error, fallback, {\n offline: resolve(translate, API_ERROR_OFFLINE_KEY, \"offline\"),\n validation: resolve(translate, API_ERROR_VALIDATION_KEY, \"validation\"),\n ...options,\n });\n },\n [translate],\n );\n}\n"],"mappings":";;;;AAuDA,SAAS,EACL,GACA,GACA,GACM;CACN,IAAM,IAAW,EAA0B;CAC3C,OAAO,IAAY,GAAK,KAAA,GAAW,EAAE,SAAS,EAAS,CAAC,KAAK;AACjE;AAEA,SAAgB,IAIJ;CAER,IAAM,IADO,EACK,CAAA,EAAM;CAExB,OAAO,GACF,GAAgB,GAAkB,MACxB,EAAiB,GAAO,GAAU;EACrC,SAAS,EAAQ,GAAW,GAAuB,SAAS;EAC5D,YAAY,EAAQ,GAAW,GAA0B,YAAY;EACrE,GAAG;CACP,CAAC,GAEL,CAAC,CAAS,CACd;AACJ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./create-offline-store.cjs");let n=require("dexie");n=e.__toESM(n,1);var r=class extends n.default{constructor(e,t,n){super(e),this.version(t).stores(n)}};function i(e){let{databaseName:n,version:i,tables:a}=e,o={};for(let e of Object.keys(a))o[e]=a[e].indexes;let s=new r(n,i,o),c=new Map;function l(e){let r=c.get(e);if(r)return r;if(!(e in a))throw Error(`Table "${e}" is not declared in ${n}. Available: ${Object.keys(a).join(`, `)}.`);let i=s.table(e),o=a[e],l=t.buildStore(s,i,o);return c.set(e,l),l}return{store:l,db:s,destroy:()=>s.delete()}}exports.createOfflineDatabase=i;
|
|
2
|
+
//# sourceMappingURL=create-offline-database.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-offline-database.cjs","names":[],"sources":["../../src/offline/create-offline-database.ts"],"sourcesContent":["import Dexie, { type Table } from \"dexie\";\n\nimport { buildStore, type OfflineStore, type OfflineStoreConfig } from \"./create-offline-store\";\n\nexport interface OfflineTableConfig<TItem> {\n /**\n * Dexie index definition for this table. Use `&` for a unique primary key,\n * e.g. `\"&id, service_id, created_at\"`.\n */\n indexes: string;\n /** Property used as the primary key (default: `\"id\"`). */\n keyPath?: keyof TItem & string;\n /**\n * Optional owner scoping for this table. Set per table, since one database\n * commonly mixes scoped and unscoped data.\n */\n ownerField?: keyof TItem & string;\n}\n\n/** Maps each table name to the record type it stores. */\nexport type OfflineSchema = Record<string, unknown>;\n\nexport type OfflineTablesConfig<TSchema extends OfflineSchema> = {\n [K in keyof TSchema]: OfflineTableConfig<TSchema[K]>;\n};\n\nexport interface OfflineDatabaseConfig<TSchema extends OfflineSchema> {\n /** IndexedDB database name. */\n databaseName: string;\n /** Schema version. Bump when changing any table's indexes. */\n version: number;\n /** One entry per object store, all inside this single database. */\n tables: OfflineTablesConfig<TSchema>;\n}\n\nexport interface OfflineDatabase<TSchema extends OfflineSchema> {\n /**\n * The {@link OfflineStore} for one table.\n *\n * The name is checked against the declared schema; the record type is\n * supplied by the caller — `store<Chat>(\"chats\")`. Deriving it from the\n * schema instead (`OfflineStore<TSchema[K], string>`) is what the shape\n * below documents as unavailable: Dexie's `Table<T>` expands `UpdateSpec<T>`\n * over the keys of `T`, and an unresolved indexed access there makes the\n * checker answer TS2589 no matter how the value is cast.\n *\n * Stores are created once and memoised, so repeated calls with the same\n * name return the same object.\n */\n store: <TItem>(name: keyof TSchema & string) => OfflineStore<TItem, string>;\n /** The Dexie instance shared by every store. */\n db: Dexie;\n /** Delete the whole database from the browser. */\n destroy: () => Promise<void>;\n}\n\nclass MultiTableDb extends Dexie {\n constructor(name: string, version: number, stores: Record<string, string>) {\n super(name);\n this.version(version).stores(stores);\n }\n}\n\n/**\n * Build several {@link OfflineStore}s that share one IndexedDB database.\n *\n * `createOfflineStore` gives each store a database of its own, which is the\n * right shape for one isolated cache. It is the wrong shape as soon as the\n * tables belong together: chats and their messages, an entity and its drafts,\n * anything you would read or clear as a unit. Splitting those across databases\n * costs a real transaction — Dexie runs one atomically only *within* a single\n * database — and it splits the version bump for a related change across two\n * places.\n *\n * This keeps them in one database at one version, so a schema change is one\n * bump and a multi-table write can be wrapped in `db.transaction(...)`.\n *\n * Stores are reached through `store<TItem>(name)` rather than a prebuilt map.\n * That is forced rather than chosen: Dexie's `Table<T>` expands `UpdateSpec<T>`\n * over the keys of `T`, so building `{ [K in keyof TSchema]: OfflineStore<…> }`\n * — or even naming `OfflineStore<TSchema[K], string>` inside the accessor —\n * makes the checker answer TS2589 (\"excessively deep\"). Taking the record type\n * as a parameter keeps it a plain type argument, which resolves fine. The table\n * name is still checked against the declared schema.\n *\n * The store surface is identical to `createOfflineStore`; only ownership of the\n * database changes. `ownerField` is set per table, since a database commonly\n * mixes per-user data with shared data.\n *\n * @param config - Database name, version, and one entry per table.\n * @returns A `store(name)` accessor, the shared Dexie instance, and a\n * `destroy()` that drops the database.\n *\n * @example\n * type Chat = { id: string; service_id: string; updated_at: string };\n * type Message = { id: string; service_chat_id: string; created_at: string };\n *\n * const database = createOfflineDatabase<{ chats: Chat; messages: Message }>({\n * databaseName: \"ChatDatabase\",\n * version: 1,\n * tables: {\n * chats: { indexes: \"&id, service_id, updated_at\" },\n * messages: { indexes: \"&id, service_chat_id, created_at\" },\n * },\n * });\n *\n * const chats = database.store<Chat>(\"chats\");\n * const messages = database.store<Message>(\"messages\");\n *\n * // Both tables in one atomic transaction — impossible across two databases.\n * await database.db.transaction(\"rw\", chats.raw, messages.raw, async () => {\n * await chats.put(chat);\n * await messages.bulkPut(pending);\n * });\n */\nexport function createOfflineDatabase<TSchema extends OfflineSchema>(\n config: OfflineDatabaseConfig<TSchema>,\n): OfflineDatabase<TSchema> {\n const { databaseName, version, tables } = config;\n\n const schema: Record<string, string> = {};\n for (const name of Object.keys(tables)) {\n schema[name] = tables[name as keyof TSchema].indexes;\n }\n\n const db = new MultiTableDb(databaseName, version, schema);\n const built = new Map<string, unknown>();\n\n function store<TItem>(name: keyof TSchema & string): OfflineStore<TItem, string> {\n const cached = built.get(name);\n if (cached) return cached as OfflineStore<TItem, string>;\n\n if (!(name in tables)) {\n throw new Error(\n `Table \"${name}\" is not declared in ${databaseName}. ` +\n `Available: ${Object.keys(tables).join(\", \")}.`,\n );\n }\n\n const table = db.table(name) as Table<TItem>;\n const tableConfig = tables[name] as Pick<\n OfflineStoreConfig<TItem>,\n \"keyPath\" | \"ownerField\"\n >;\n // Dexie's `Table<T>` drags in `UpdateSpec<T>`, which maps over the keys\n // of `T`. Instantiating that from inside a second generic function is\n // past what the checker will unfold, and it answers TS2589\n // (\"excessively deep\") — for every shape tried: `unknown`, `object`,\n // `Record<string, unknown>`, a closed interface, an explicit type\n // argument, and a cast through `never`. `createOfflineStore` calls this\n // same `buildStore` without complaint, so it is the nesting, not the\n // store.\n //\n // Suppressed rather than worked around: the emitted types and the\n // runtime are both correct — `create-offline-database.test.ts` covers\n // the full surface, cross-table transactions included.\n // `@ts-expect-error` over `@ts-ignore` on purpose: if a later\n // TypeScript raises the limit, this line starts failing and says so.\n // @ts-expect-error TS2589 — see above\n const created: OfflineStore<TItem> = buildStore<TItem>(db, table, tableConfig);\n built.set(name, created);\n return created;\n }\n\n return {\n store,\n db,\n destroy: () => db.delete(),\n };\n}\n"],"mappings":"qIAwDA,IAAM,EAAN,cAA2B,EAAA,OAAM,CAC7B,YAAY,EAAc,EAAiB,EAAgC,CACvE,MAAM,CAAI,EACV,KAAK,QAAQ,CAAO,CAAC,CAAC,OAAO,CAAM,CACvC,CACJ,EAsDA,SAAgB,EACZ,EACwB,CACxB,GAAM,CAAE,eAAc,UAAS,UAAW,EAEpC,EAAiC,CAAC,EACxC,IAAK,IAAM,KAAQ,OAAO,KAAK,CAAM,EACjC,EAAO,GAAQ,EAAO,EAAsB,CAAC,QAGjD,IAAM,EAAK,IAAI,EAAa,EAAc,EAAS,CAAM,EACnD,EAAQ,IAAI,IAElB,SAAS,EAAa,EAA2D,CAC7E,IAAM,EAAS,EAAM,IAAI,CAAI,EAC7B,GAAI,EAAQ,OAAO,EAEnB,GAAI,EAAE,KAAQ,GACV,MAAU,MACN,UAAU,EAAK,uBAAuB,EAAa,eACjC,OAAO,KAAK,CAAM,CAAC,CAAC,KAAK,IAAI,EAAE,EACrD,EAGJ,IAAM,EAAQ,EAAG,MAAM,CAAI,EACrB,EAAc,EAAO,GAmBrB,EAA+B,EAAA,WAAkB,EAAI,EAAO,CAAW,EAE7E,OADA,EAAM,IAAI,EAAM,CAAO,EAChB,CACX,CAEA,MAAO,CACH,QACA,KACA,YAAe,EAAG,OAAO,CAC7B,CACJ"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildStore as e } from "./create-offline-store.js";
|
|
2
|
+
import t from "dexie";
|
|
3
|
+
//#region src/offline/create-offline-database.ts
|
|
4
|
+
var n = class extends t {
|
|
5
|
+
constructor(e, t, n) {
|
|
6
|
+
super(e), this.version(t).stores(n);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
function r(t) {
|
|
10
|
+
let { databaseName: r, version: i, tables: a } = t, o = {};
|
|
11
|
+
for (let e of Object.keys(a)) o[e] = a[e].indexes;
|
|
12
|
+
let s = new n(r, i, o), c = /* @__PURE__ */ new Map();
|
|
13
|
+
function l(t) {
|
|
14
|
+
let n = c.get(t);
|
|
15
|
+
if (n) return n;
|
|
16
|
+
if (!(t in a)) throw Error(`Table "${t}" is not declared in ${r}. Available: ${Object.keys(a).join(", ")}.`);
|
|
17
|
+
let i = s.table(t), o = a[t], l = e(s, i, o);
|
|
18
|
+
return c.set(t, l), l;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
store: l,
|
|
22
|
+
db: s,
|
|
23
|
+
destroy: () => s.delete()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { r as createOfflineDatabase };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=create-offline-database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-offline-database.js","names":[],"sources":["../../src/offline/create-offline-database.ts"],"sourcesContent":["import Dexie, { type Table } from \"dexie\";\n\nimport { buildStore, type OfflineStore, type OfflineStoreConfig } from \"./create-offline-store\";\n\nexport interface OfflineTableConfig<TItem> {\n /**\n * Dexie index definition for this table. Use `&` for a unique primary key,\n * e.g. `\"&id, service_id, created_at\"`.\n */\n indexes: string;\n /** Property used as the primary key (default: `\"id\"`). */\n keyPath?: keyof TItem & string;\n /**\n * Optional owner scoping for this table. Set per table, since one database\n * commonly mixes scoped and unscoped data.\n */\n ownerField?: keyof TItem & string;\n}\n\n/** Maps each table name to the record type it stores. */\nexport type OfflineSchema = Record<string, unknown>;\n\nexport type OfflineTablesConfig<TSchema extends OfflineSchema> = {\n [K in keyof TSchema]: OfflineTableConfig<TSchema[K]>;\n};\n\nexport interface OfflineDatabaseConfig<TSchema extends OfflineSchema> {\n /** IndexedDB database name. */\n databaseName: string;\n /** Schema version. Bump when changing any table's indexes. */\n version: number;\n /** One entry per object store, all inside this single database. */\n tables: OfflineTablesConfig<TSchema>;\n}\n\nexport interface OfflineDatabase<TSchema extends OfflineSchema> {\n /**\n * The {@link OfflineStore} for one table.\n *\n * The name is checked against the declared schema; the record type is\n * supplied by the caller — `store<Chat>(\"chats\")`. Deriving it from the\n * schema instead (`OfflineStore<TSchema[K], string>`) is what the shape\n * below documents as unavailable: Dexie's `Table<T>` expands `UpdateSpec<T>`\n * over the keys of `T`, and an unresolved indexed access there makes the\n * checker answer TS2589 no matter how the value is cast.\n *\n * Stores are created once and memoised, so repeated calls with the same\n * name return the same object.\n */\n store: <TItem>(name: keyof TSchema & string) => OfflineStore<TItem, string>;\n /** The Dexie instance shared by every store. */\n db: Dexie;\n /** Delete the whole database from the browser. */\n destroy: () => Promise<void>;\n}\n\nclass MultiTableDb extends Dexie {\n constructor(name: string, version: number, stores: Record<string, string>) {\n super(name);\n this.version(version).stores(stores);\n }\n}\n\n/**\n * Build several {@link OfflineStore}s that share one IndexedDB database.\n *\n * `createOfflineStore` gives each store a database of its own, which is the\n * right shape for one isolated cache. It is the wrong shape as soon as the\n * tables belong together: chats and their messages, an entity and its drafts,\n * anything you would read or clear as a unit. Splitting those across databases\n * costs a real transaction — Dexie runs one atomically only *within* a single\n * database — and it splits the version bump for a related change across two\n * places.\n *\n * This keeps them in one database at one version, so a schema change is one\n * bump and a multi-table write can be wrapped in `db.transaction(...)`.\n *\n * Stores are reached through `store<TItem>(name)` rather than a prebuilt map.\n * That is forced rather than chosen: Dexie's `Table<T>` expands `UpdateSpec<T>`\n * over the keys of `T`, so building `{ [K in keyof TSchema]: OfflineStore<…> }`\n * — or even naming `OfflineStore<TSchema[K], string>` inside the accessor —\n * makes the checker answer TS2589 (\"excessively deep\"). Taking the record type\n * as a parameter keeps it a plain type argument, which resolves fine. The table\n * name is still checked against the declared schema.\n *\n * The store surface is identical to `createOfflineStore`; only ownership of the\n * database changes. `ownerField` is set per table, since a database commonly\n * mixes per-user data with shared data.\n *\n * @param config - Database name, version, and one entry per table.\n * @returns A `store(name)` accessor, the shared Dexie instance, and a\n * `destroy()` that drops the database.\n *\n * @example\n * type Chat = { id: string; service_id: string; updated_at: string };\n * type Message = { id: string; service_chat_id: string; created_at: string };\n *\n * const database = createOfflineDatabase<{ chats: Chat; messages: Message }>({\n * databaseName: \"ChatDatabase\",\n * version: 1,\n * tables: {\n * chats: { indexes: \"&id, service_id, updated_at\" },\n * messages: { indexes: \"&id, service_chat_id, created_at\" },\n * },\n * });\n *\n * const chats = database.store<Chat>(\"chats\");\n * const messages = database.store<Message>(\"messages\");\n *\n * // Both tables in one atomic transaction — impossible across two databases.\n * await database.db.transaction(\"rw\", chats.raw, messages.raw, async () => {\n * await chats.put(chat);\n * await messages.bulkPut(pending);\n * });\n */\nexport function createOfflineDatabase<TSchema extends OfflineSchema>(\n config: OfflineDatabaseConfig<TSchema>,\n): OfflineDatabase<TSchema> {\n const { databaseName, version, tables } = config;\n\n const schema: Record<string, string> = {};\n for (const name of Object.keys(tables)) {\n schema[name] = tables[name as keyof TSchema].indexes;\n }\n\n const db = new MultiTableDb(databaseName, version, schema);\n const built = new Map<string, unknown>();\n\n function store<TItem>(name: keyof TSchema & string): OfflineStore<TItem, string> {\n const cached = built.get(name);\n if (cached) return cached as OfflineStore<TItem, string>;\n\n if (!(name in tables)) {\n throw new Error(\n `Table \"${name}\" is not declared in ${databaseName}. ` +\n `Available: ${Object.keys(tables).join(\", \")}.`,\n );\n }\n\n const table = db.table(name) as Table<TItem>;\n const tableConfig = tables[name] as Pick<\n OfflineStoreConfig<TItem>,\n \"keyPath\" | \"ownerField\"\n >;\n // Dexie's `Table<T>` drags in `UpdateSpec<T>`, which maps over the keys\n // of `T`. Instantiating that from inside a second generic function is\n // past what the checker will unfold, and it answers TS2589\n // (\"excessively deep\") — for every shape tried: `unknown`, `object`,\n // `Record<string, unknown>`, a closed interface, an explicit type\n // argument, and a cast through `never`. `createOfflineStore` calls this\n // same `buildStore` without complaint, so it is the nesting, not the\n // store.\n //\n // Suppressed rather than worked around: the emitted types and the\n // runtime are both correct — `create-offline-database.test.ts` covers\n // the full surface, cross-table transactions included.\n // `@ts-expect-error` over `@ts-ignore` on purpose: if a later\n // TypeScript raises the limit, this line starts failing and says so.\n // @ts-expect-error TS2589 — see above\n const created: OfflineStore<TItem> = buildStore<TItem>(db, table, tableConfig);\n built.set(name, created);\n return created;\n }\n\n return {\n store,\n db,\n destroy: () => db.delete(),\n };\n}\n"],"mappings":";;;AAwDA,IAAM,IAAN,cAA2B,EAAM;CAC7B,YAAY,GAAc,GAAiB,GAAgC;EAEvE,AADA,MAAM,CAAI,GACV,KAAK,QAAQ,CAAO,CAAC,CAAC,OAAO,CAAM;CACvC;AACJ;AAsDA,SAAgB,EACZ,GACwB;CACxB,IAAM,EAAE,iBAAc,YAAS,cAAW,GAEpC,IAAiC,CAAC;CACxC,KAAK,IAAM,KAAQ,OAAO,KAAK,CAAM,GACjC,EAAO,KAAQ,EAAO,EAAsB,CAAC;CAGjD,IAAM,IAAK,IAAI,EAAa,GAAc,GAAS,CAAM,GACnD,oBAAQ,IAAI,IAAqB;CAEvC,SAAS,EAAa,GAA2D;EAC7E,IAAM,IAAS,EAAM,IAAI,CAAI;EAC7B,IAAI,GAAQ,OAAO;EAEnB,IAAI,EAAE,KAAQ,IACV,MAAU,MACN,UAAU,EAAK,uBAAuB,EAAa,eACjC,OAAO,KAAK,CAAM,CAAC,CAAC,KAAK,IAAI,EAAE,EACrD;EAGJ,IAAM,IAAQ,EAAG,MAAM,CAAI,GACrB,IAAc,EAAO,IAmBrB,IAA+B,EAAkB,GAAI,GAAO,CAAW;EAE7E,OADA,EAAM,IAAI,GAAM,CAAO,GAChB;CACX;CAEA,OAAO;EACH;EACA;EACA,eAAe,EAAG,OAAO;CAC7B;AACJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("dexie");t=e.__toESM(t,1);var n=class extends t.default{store;constructor(e,t,n,r){super(e),this.version(t).stores({[n]:r}),this.store=this.table(n)}};function r(e){let{databaseName:t,version:r,tableName:
|
|
1
|
+
const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("dexie");t=e.__toESM(t,1);var n=class extends t.default{store;constructor(e,t,n,r){super(e),this.version(t).stores({[n]:r}),this.store=this.table(n)}};function r(e){let{databaseName:t,version:r,tableName:a,indexes:o}=e,s=new n(t,r,a,o);return i(s,s.store,e)}function i(e,t,n){let{keyPath:r=`id`,ownerField:i}=n;function a(e,t){return!i||!t?e:{...e,[i]:t}}async function o(e,n={}){let{orderBy:a=r,reverse:o=!1,limit:s,offset:c,filter:l}=n,u=i&&e?t.where(i).equals(e):t.toCollection();l&&(u=u.filter(l));let d=a===r?await u.toArray():await u.sortBy(a);return o&&(d=d.reverse()),c&&(d=d.slice(c)),typeof s==`number`&&(d=d.slice(0,s)),d}return{put:(e,n)=>t.put(a(e,n)),bulkPut:(e,n)=>t.bulkPut(e.map(e=>a(e,n))),get:e=>t.get(e),list:o,update:(e,n)=>t.update(e,n),updateMany:async(e,n)=>{let r=n;return i&&e?t.where(i).equals(e).modify(r):t.toCollection().modify(r)},delete:e=>t.delete(e),clear:async e=>{if(i&&e){await t.where(i).equals(e).delete();return}await t.clear()},count:e=>i&&e?t.where(i).equals(e).count():t.count(),raw:t,db:e}}exports.buildStore=i,exports.createOfflineStore=r;
|
|
2
2
|
//# sourceMappingURL=create-offline-store.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-offline-store.cjs","names":[],"sources":["../../src/offline/create-offline-store.ts"],"sourcesContent":["import Dexie, { type Table, type UpdateSpec } from \"dexie\";\n\nexport interface OfflineStoreConfig<TItem> {\n /** IndexedDB database name. */\n databaseName: string;\n /** Schema version. Bump when changing indexes; pair with a migration if needed. */\n version: number;\n /** Object-store name. */\n tableName: string;\n /**\n * Dexie index definition for the table. Use `&` for the primary key\n * (unique), e.g. `\"&id, owner_id, created_at\"`. See Dexie docs for the\n * full syntax.\n */\n indexes: string;\n /** Property used as the primary key (default: `\"id\"`). */\n keyPath?: keyof TItem & string;\n /**\n * Optional owner scoping. When set, every read/write method honors the\n * `owner` argument and persists it on each record (e.g. multi-tenant\n * notifications keyed by `user_id`).\n */\n ownerField?: keyof TItem & string;\n}\n\nexport interface ListOptions<TItem> {\n /** Property to order by. Default: `keyPath`. */\n orderBy?: keyof TItem & string;\n /** Reverse the ordering. Default: false. */\n reverse?: boolean;\n /** Maximum number of items to return. */\n limit?: number;\n /** Skip this many items from the start of the result set. */\n offset?: number;\n /** Custom predicate applied after the index query. */\n filter?: (item: TItem) => boolean;\n}\n\nexport interface OfflineStore<TItem, TKey extends string | number> {\n /** Insert or replace a record. */\n put: (item: TItem, owner?: string) => Promise<TKey>;\n /** Insert or replace multiple records in a single transaction. */\n bulkPut: (items: TItem[], owner?: string) => Promise<TKey>;\n /** Fetch one record by its primary key. */\n get: (key: TKey) => Promise<TItem | undefined>;\n /** List records, optionally scoped to `owner` when `ownerField` is configured. */\n list: (owner?: string, options?: ListOptions<TItem>) => Promise<TItem[]>;\n /** Partial update by primary key. Returns the number of records changed. */\n update: (key: TKey, changes: Partial<TItem>) => Promise<number>;\n /** Apply a modification to every record matching `owner`. */\n updateMany: (owner: string | undefined, changes: Partial<TItem>) => Promise<number>;\n /** Delete one record by primary key. */\n delete: (key: TKey) => Promise<void>;\n /** Delete every record matching `owner` (or the entire table when no scope is set). */\n clear: (owner?: string) => Promise<void>;\n /** Count records, optionally scoped to `owner`. */\n count: (owner?: string) => Promise<number>;\n /** Raw Dexie table for advanced queries. */\n raw: Table<TItem, TKey>;\n /** Underlying Dexie instance. */\n db: Dexie;\n}\n\nclass GenericDb<TItem, TKey extends string | number> extends Dexie {\n store!: Table<TItem, TKey>;\n\n constructor(name: string, version: number, tableName: string, indexes: string) {\n super(name);\n this.version(version).stores({ [tableName]: indexes });\n this.store = this.table<TItem, TKey>(tableName);\n }\n}\n\n/**\n * Build a typed IndexedDB-backed store using Dexie. Optionally scope every\n * operation by an `ownerField` (useful for multi-user SSE history, drafts,\n * cache per workspace, etc.).\n *\n * Dexie is an **optional peer dependency** — install it (`npm i dexie`) only\n * when your app needs offline storage.\n *\n * @example\n * type Note = { id: string; owner_id: string; text: string; created_at: string };\n * const notes = createOfflineStore<Note, string>({\n * databaseName: \"TempestNotes\",\n * version: 1,\n * tableName: \"notes\",\n * indexes: \"&id, owner_id, created_at\",\n * ownerField: \"owner_id\",\n * });\n * await notes.put({ id: \"n1\", owner_id: \"u1\", text: \"hi\", created_at: ... }, \"u1\");\n * const mine = await notes.list(\"u1\", { orderBy: \"created_at\", reverse: true });\n */\nexport function createOfflineStore<TItem, TKey extends string | number = string>(\n config: OfflineStoreConfig<TItem>,\n): OfflineStore<TItem, TKey> {\n const { databaseName, version, tableName, indexes
|
|
1
|
+
{"version":3,"file":"create-offline-store.cjs","names":[],"sources":["../../src/offline/create-offline-store.ts"],"sourcesContent":["import Dexie, { type Table, type UpdateSpec } from \"dexie\";\n\nexport interface OfflineStoreConfig<TItem> {\n /** IndexedDB database name. */\n databaseName: string;\n /** Schema version. Bump when changing indexes; pair with a migration if needed. */\n version: number;\n /** Object-store name. */\n tableName: string;\n /**\n * Dexie index definition for the table. Use `&` for the primary key\n * (unique), e.g. `\"&id, owner_id, created_at\"`. See Dexie docs for the\n * full syntax.\n */\n indexes: string;\n /** Property used as the primary key (default: `\"id\"`). */\n keyPath?: keyof TItem & string;\n /**\n * Optional owner scoping. When set, every read/write method honors the\n * `owner` argument and persists it on each record (e.g. multi-tenant\n * notifications keyed by `user_id`).\n */\n ownerField?: keyof TItem & string;\n}\n\nexport interface ListOptions<TItem> {\n /** Property to order by. Default: `keyPath`. */\n orderBy?: keyof TItem & string;\n /** Reverse the ordering. Default: false. */\n reverse?: boolean;\n /** Maximum number of items to return. */\n limit?: number;\n /** Skip this many items from the start of the result set. */\n offset?: number;\n /** Custom predicate applied after the index query. */\n filter?: (item: TItem) => boolean;\n}\n\nexport interface OfflineStore<TItem, TKey extends string | number> {\n /** Insert or replace a record. */\n put: (item: TItem, owner?: string) => Promise<TKey>;\n /** Insert or replace multiple records in a single transaction. */\n bulkPut: (items: TItem[], owner?: string) => Promise<TKey>;\n /** Fetch one record by its primary key. */\n get: (key: TKey) => Promise<TItem | undefined>;\n /** List records, optionally scoped to `owner` when `ownerField` is configured. */\n list: (owner?: string, options?: ListOptions<TItem>) => Promise<TItem[]>;\n /** Partial update by primary key. Returns the number of records changed. */\n update: (key: TKey, changes: Partial<TItem>) => Promise<number>;\n /** Apply a modification to every record matching `owner`. */\n updateMany: (owner: string | undefined, changes: Partial<TItem>) => Promise<number>;\n /** Delete one record by primary key. */\n delete: (key: TKey) => Promise<void>;\n /** Delete every record matching `owner` (or the entire table when no scope is set). */\n clear: (owner?: string) => Promise<void>;\n /** Count records, optionally scoped to `owner`. */\n count: (owner?: string) => Promise<number>;\n /** Raw Dexie table for advanced queries. */\n raw: Table<TItem, TKey>;\n /** Underlying Dexie instance. */\n db: Dexie;\n}\n\nclass GenericDb<TItem, TKey extends string | number> extends Dexie {\n store!: Table<TItem, TKey>;\n\n constructor(name: string, version: number, tableName: string, indexes: string) {\n super(name);\n this.version(version).stores({ [tableName]: indexes });\n this.store = this.table<TItem, TKey>(tableName);\n }\n}\n\n/**\n * Build a typed IndexedDB-backed store using Dexie. Optionally scope every\n * operation by an `ownerField` (useful for multi-user SSE history, drafts,\n * cache per workspace, etc.).\n *\n * Dexie is an **optional peer dependency** — install it (`npm i dexie`) only\n * when your app needs offline storage.\n *\n * @example\n * type Note = { id: string; owner_id: string; text: string; created_at: string };\n * const notes = createOfflineStore<Note, string>({\n * databaseName: \"TempestNotes\",\n * version: 1,\n * tableName: \"notes\",\n * indexes: \"&id, owner_id, created_at\",\n * ownerField: \"owner_id\",\n * });\n * await notes.put({ id: \"n1\", owner_id: \"u1\", text: \"hi\", created_at: ... }, \"u1\");\n * const mine = await notes.list(\"u1\", { orderBy: \"created_at\", reverse: true });\n */\nexport function createOfflineStore<TItem, TKey extends string | number = string>(\n config: OfflineStoreConfig<TItem>,\n): OfflineStore<TItem, TKey> {\n const { databaseName, version, tableName, indexes } = config;\n const db = new GenericDb<TItem, TKey>(databaseName, version, tableName, indexes);\n return buildStore<TItem, TKey>(db, db.store, config);\n}\n\n/**\n * Wrap an existing Dexie table in the {@link OfflineStore} surface.\n *\n * Split out so a store can be built over a table this function did not create,\n * which is what lets {@link createOfflineDatabase} place several stores on one\n * database rather than one database per table.\n *\n * @param db - The Dexie instance owning the table.\n * @param table - The table to wrap.\n * @param config - Key path and owner scoping for this table.\n * @returns The store surface bound to that table.\n */\nexport function buildStore<TItem, TKey extends string | number = string>(\n db: Dexie,\n table: Table<TItem, TKey>,\n config: Pick<OfflineStoreConfig<TItem>, \"keyPath\" | \"ownerField\">,\n): OfflineStore<TItem, TKey> {\n const { keyPath = \"id\", ownerField } = config;\n\n function withOwner(item: TItem, owner?: string): TItem {\n if (!ownerField || !owner) return item;\n return { ...item, [ownerField]: owner } as TItem;\n }\n\n async function list(owner?: string, options: ListOptions<TItem> = {}): Promise<TItem[]> {\n const { orderBy = keyPath, reverse = false, limit, offset, filter } = options;\n\n let collection =\n ownerField && owner ? table.where(ownerField).equals(owner) : table.toCollection();\n\n if (filter) collection = collection.filter(filter);\n\n let items =\n orderBy === keyPath ? await collection.toArray() : await collection.sortBy(orderBy);\n\n if (reverse) items = items.reverse();\n if (offset) items = items.slice(offset);\n if (typeof limit === \"number\") items = items.slice(0, limit);\n return items;\n }\n\n return {\n put: (item, owner) => table.put(withOwner(item, owner)) as Promise<TKey>,\n bulkPut: (items, owner) =>\n table.bulkPut(items.map((item) => withOwner(item, owner))) as Promise<TKey>,\n get: (key) => table.get(key),\n list,\n update: (key, changes) => table.update(key, changes as UpdateSpec<TItem>),\n updateMany: async (owner, changes) => {\n const spec = changes as UpdateSpec<TItem>;\n if (ownerField && owner) {\n return table.where(ownerField).equals(owner).modify(spec);\n }\n return table.toCollection().modify(spec);\n },\n delete: (key) => table.delete(key),\n clear: async (owner) => {\n if (ownerField && owner) {\n await table.where(ownerField).equals(owner).delete();\n return;\n }\n await table.clear();\n },\n count: (owner) => {\n if (ownerField && owner) {\n return table.where(ownerField).equals(owner).count();\n }\n return table.count();\n },\n raw: table,\n db,\n };\n}\n"],"mappings":"6FA+DA,IAAM,EAAN,cAA6D,EAAA,OAAM,CAC/D,MAEA,YAAY,EAAc,EAAiB,EAAmB,EAAiB,CAC3E,MAAM,CAAI,EACV,KAAK,QAAQ,CAAO,CAAC,CAAC,OAAO,EAAG,GAAY,CAAQ,CAAC,EACrD,KAAK,MAAQ,KAAK,MAAmB,CAAS,CAClD,CACJ,EAsBA,SAAgB,EACZ,EACyB,CACzB,GAAM,CAAE,eAAc,UAAS,YAAW,WAAY,EAChD,EAAK,IAAI,EAAuB,EAAc,EAAS,EAAW,CAAO,EAC/E,OAAO,EAAwB,EAAI,EAAG,MAAO,CAAM,CACvD,CAcA,SAAgB,EACZ,EACA,EACA,EACyB,CACzB,GAAM,CAAE,UAAU,KAAM,cAAe,EAEvC,SAAS,EAAU,EAAa,EAAuB,CAEnD,MADI,CAAC,GAAc,CAAC,EAAc,EAC3B,CAAE,GAAG,GAAO,GAAa,CAAM,CAC1C,CAEA,eAAe,EAAK,EAAgB,EAA8B,CAAC,EAAqB,CACpF,GAAM,CAAE,UAAU,EAAS,UAAU,GAAO,QAAO,SAAQ,UAAW,EAElE,EACA,GAAc,EAAQ,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,EAAI,EAAM,aAAa,EAEjF,IAAQ,EAAa,EAAW,OAAO,CAAM,GAEjD,IAAI,EACA,IAAY,EAAU,MAAM,EAAW,QAAQ,EAAI,MAAM,EAAW,OAAO,CAAO,EAKtF,OAHI,IAAS,EAAQ,EAAM,QAAQ,GAC/B,IAAQ,EAAQ,EAAM,MAAM,CAAM,GAClC,OAAO,GAAU,WAAU,EAAQ,EAAM,MAAM,EAAG,CAAK,GACpD,CACX,CAEA,MAAO,CACH,KAAM,EAAM,IAAU,EAAM,IAAI,EAAU,EAAM,CAAK,CAAC,EACtD,SAAU,EAAO,IACb,EAAM,QAAQ,EAAM,IAAK,GAAS,EAAU,EAAM,CAAK,CAAC,CAAC,EAC7D,IAAM,GAAQ,EAAM,IAAI,CAAG,EAC3B,OACA,QAAS,EAAK,IAAY,EAAM,OAAO,EAAK,CAA4B,EACxE,WAAY,MAAO,EAAO,IAAY,CAClC,IAAM,EAAO,EAIb,OAHI,GAAc,EACP,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,CAAI,EAErD,EAAM,aAAa,CAAC,CAAC,OAAO,CAAI,CAC3C,EACA,OAAS,GAAQ,EAAM,OAAO,CAAG,EACjC,MAAO,KAAO,IAAU,CACpB,GAAI,GAAc,EAAO,CACrB,MAAM,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,EACnD,MACJ,CACA,MAAM,EAAM,MAAM,CACtB,EACA,MAAQ,GACA,GAAc,EACP,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,MAAM,EAEhD,EAAM,MAAM,EAEvB,IAAK,EACL,IACJ,CACJ"}
|
|
@@ -7,43 +7,47 @@ var t = class extends e {
|
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
9
|
function n(e) {
|
|
10
|
-
let { databaseName: n, version:
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
let { databaseName: n, version: i, tableName: a, indexes: o } = e, s = new t(n, i, a, o);
|
|
11
|
+
return r(s, s.store, e);
|
|
12
|
+
}
|
|
13
|
+
function r(e, t, n) {
|
|
14
|
+
let { keyPath: r = "id", ownerField: i } = n;
|
|
15
|
+
function a(e, t) {
|
|
16
|
+
return !i || !t ? e : {
|
|
13
17
|
...e,
|
|
14
|
-
[
|
|
18
|
+
[i]: t
|
|
15
19
|
};
|
|
16
20
|
}
|
|
17
|
-
async function
|
|
18
|
-
let { orderBy:
|
|
19
|
-
|
|
20
|
-
let d =
|
|
21
|
-
return
|
|
21
|
+
async function o(e, n = {}) {
|
|
22
|
+
let { orderBy: a = r, reverse: o = !1, limit: s, offset: c, filter: l } = n, u = i && e ? t.where(i).equals(e) : t.toCollection();
|
|
23
|
+
l && (u = u.filter(l));
|
|
24
|
+
let d = a === r ? await u.toArray() : await u.sortBy(a);
|
|
25
|
+
return o && (d = d.reverse()), c && (d = d.slice(c)), typeof s == "number" && (d = d.slice(0, s)), d;
|
|
22
26
|
}
|
|
23
27
|
return {
|
|
24
|
-
put: (e,
|
|
25
|
-
bulkPut: (e,
|
|
26
|
-
get: (e) =>
|
|
27
|
-
list:
|
|
28
|
-
update: (e,
|
|
29
|
-
updateMany: async (e,
|
|
30
|
-
let
|
|
31
|
-
return
|
|
28
|
+
put: (e, n) => t.put(a(e, n)),
|
|
29
|
+
bulkPut: (e, n) => t.bulkPut(e.map((e) => a(e, n))),
|
|
30
|
+
get: (e) => t.get(e),
|
|
31
|
+
list: o,
|
|
32
|
+
update: (e, n) => t.update(e, n),
|
|
33
|
+
updateMany: async (e, n) => {
|
|
34
|
+
let r = n;
|
|
35
|
+
return i && e ? t.where(i).equals(e).modify(r) : t.toCollection().modify(r);
|
|
32
36
|
},
|
|
33
|
-
delete: (e) =>
|
|
37
|
+
delete: (e) => t.delete(e),
|
|
34
38
|
clear: async (e) => {
|
|
35
|
-
if (
|
|
36
|
-
await
|
|
39
|
+
if (i && e) {
|
|
40
|
+
await t.where(i).equals(e).delete();
|
|
37
41
|
return;
|
|
38
42
|
}
|
|
39
|
-
await
|
|
43
|
+
await t.clear();
|
|
40
44
|
},
|
|
41
|
-
count: (e) =>
|
|
42
|
-
raw:
|
|
43
|
-
db:
|
|
45
|
+
count: (e) => i && e ? t.where(i).equals(e).count() : t.count(),
|
|
46
|
+
raw: t,
|
|
47
|
+
db: e
|
|
44
48
|
};
|
|
45
49
|
}
|
|
46
50
|
//#endregion
|
|
47
|
-
export { n as createOfflineStore };
|
|
51
|
+
export { r as buildStore, n as createOfflineStore };
|
|
48
52
|
|
|
49
53
|
//# sourceMappingURL=create-offline-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-offline-store.js","names":[],"sources":["../../src/offline/create-offline-store.ts"],"sourcesContent":["import Dexie, { type Table, type UpdateSpec } from \"dexie\";\n\nexport interface OfflineStoreConfig<TItem> {\n /** IndexedDB database name. */\n databaseName: string;\n /** Schema version. Bump when changing indexes; pair with a migration if needed. */\n version: number;\n /** Object-store name. */\n tableName: string;\n /**\n * Dexie index definition for the table. Use `&` for the primary key\n * (unique), e.g. `\"&id, owner_id, created_at\"`. See Dexie docs for the\n * full syntax.\n */\n indexes: string;\n /** Property used as the primary key (default: `\"id\"`). */\n keyPath?: keyof TItem & string;\n /**\n * Optional owner scoping. When set, every read/write method honors the\n * `owner` argument and persists it on each record (e.g. multi-tenant\n * notifications keyed by `user_id`).\n */\n ownerField?: keyof TItem & string;\n}\n\nexport interface ListOptions<TItem> {\n /** Property to order by. Default: `keyPath`. */\n orderBy?: keyof TItem & string;\n /** Reverse the ordering. Default: false. */\n reverse?: boolean;\n /** Maximum number of items to return. */\n limit?: number;\n /** Skip this many items from the start of the result set. */\n offset?: number;\n /** Custom predicate applied after the index query. */\n filter?: (item: TItem) => boolean;\n}\n\nexport interface OfflineStore<TItem, TKey extends string | number> {\n /** Insert or replace a record. */\n put: (item: TItem, owner?: string) => Promise<TKey>;\n /** Insert or replace multiple records in a single transaction. */\n bulkPut: (items: TItem[], owner?: string) => Promise<TKey>;\n /** Fetch one record by its primary key. */\n get: (key: TKey) => Promise<TItem | undefined>;\n /** List records, optionally scoped to `owner` when `ownerField` is configured. */\n list: (owner?: string, options?: ListOptions<TItem>) => Promise<TItem[]>;\n /** Partial update by primary key. Returns the number of records changed. */\n update: (key: TKey, changes: Partial<TItem>) => Promise<number>;\n /** Apply a modification to every record matching `owner`. */\n updateMany: (owner: string | undefined, changes: Partial<TItem>) => Promise<number>;\n /** Delete one record by primary key. */\n delete: (key: TKey) => Promise<void>;\n /** Delete every record matching `owner` (or the entire table when no scope is set). */\n clear: (owner?: string) => Promise<void>;\n /** Count records, optionally scoped to `owner`. */\n count: (owner?: string) => Promise<number>;\n /** Raw Dexie table for advanced queries. */\n raw: Table<TItem, TKey>;\n /** Underlying Dexie instance. */\n db: Dexie;\n}\n\nclass GenericDb<TItem, TKey extends string | number> extends Dexie {\n store!: Table<TItem, TKey>;\n\n constructor(name: string, version: number, tableName: string, indexes: string) {\n super(name);\n this.version(version).stores({ [tableName]: indexes });\n this.store = this.table<TItem, TKey>(tableName);\n }\n}\n\n/**\n * Build a typed IndexedDB-backed store using Dexie. Optionally scope every\n * operation by an `ownerField` (useful for multi-user SSE history, drafts,\n * cache per workspace, etc.).\n *\n * Dexie is an **optional peer dependency** — install it (`npm i dexie`) only\n * when your app needs offline storage.\n *\n * @example\n * type Note = { id: string; owner_id: string; text: string; created_at: string };\n * const notes = createOfflineStore<Note, string>({\n * databaseName: \"TempestNotes\",\n * version: 1,\n * tableName: \"notes\",\n * indexes: \"&id, owner_id, created_at\",\n * ownerField: \"owner_id\",\n * });\n * await notes.put({ id: \"n1\", owner_id: \"u1\", text: \"hi\", created_at: ... }, \"u1\");\n * const mine = await notes.list(\"u1\", { orderBy: \"created_at\", reverse: true });\n */\nexport function createOfflineStore<TItem, TKey extends string | number = string>(\n config: OfflineStoreConfig<TItem>,\n): OfflineStore<TItem, TKey> {\n const { databaseName, version, tableName, indexes
|
|
1
|
+
{"version":3,"file":"create-offline-store.js","names":[],"sources":["../../src/offline/create-offline-store.ts"],"sourcesContent":["import Dexie, { type Table, type UpdateSpec } from \"dexie\";\n\nexport interface OfflineStoreConfig<TItem> {\n /** IndexedDB database name. */\n databaseName: string;\n /** Schema version. Bump when changing indexes; pair with a migration if needed. */\n version: number;\n /** Object-store name. */\n tableName: string;\n /**\n * Dexie index definition for the table. Use `&` for the primary key\n * (unique), e.g. `\"&id, owner_id, created_at\"`. See Dexie docs for the\n * full syntax.\n */\n indexes: string;\n /** Property used as the primary key (default: `\"id\"`). */\n keyPath?: keyof TItem & string;\n /**\n * Optional owner scoping. When set, every read/write method honors the\n * `owner` argument and persists it on each record (e.g. multi-tenant\n * notifications keyed by `user_id`).\n */\n ownerField?: keyof TItem & string;\n}\n\nexport interface ListOptions<TItem> {\n /** Property to order by. Default: `keyPath`. */\n orderBy?: keyof TItem & string;\n /** Reverse the ordering. Default: false. */\n reverse?: boolean;\n /** Maximum number of items to return. */\n limit?: number;\n /** Skip this many items from the start of the result set. */\n offset?: number;\n /** Custom predicate applied after the index query. */\n filter?: (item: TItem) => boolean;\n}\n\nexport interface OfflineStore<TItem, TKey extends string | number> {\n /** Insert or replace a record. */\n put: (item: TItem, owner?: string) => Promise<TKey>;\n /** Insert or replace multiple records in a single transaction. */\n bulkPut: (items: TItem[], owner?: string) => Promise<TKey>;\n /** Fetch one record by its primary key. */\n get: (key: TKey) => Promise<TItem | undefined>;\n /** List records, optionally scoped to `owner` when `ownerField` is configured. */\n list: (owner?: string, options?: ListOptions<TItem>) => Promise<TItem[]>;\n /** Partial update by primary key. Returns the number of records changed. */\n update: (key: TKey, changes: Partial<TItem>) => Promise<number>;\n /** Apply a modification to every record matching `owner`. */\n updateMany: (owner: string | undefined, changes: Partial<TItem>) => Promise<number>;\n /** Delete one record by primary key. */\n delete: (key: TKey) => Promise<void>;\n /** Delete every record matching `owner` (or the entire table when no scope is set). */\n clear: (owner?: string) => Promise<void>;\n /** Count records, optionally scoped to `owner`. */\n count: (owner?: string) => Promise<number>;\n /** Raw Dexie table for advanced queries. */\n raw: Table<TItem, TKey>;\n /** Underlying Dexie instance. */\n db: Dexie;\n}\n\nclass GenericDb<TItem, TKey extends string | number> extends Dexie {\n store!: Table<TItem, TKey>;\n\n constructor(name: string, version: number, tableName: string, indexes: string) {\n super(name);\n this.version(version).stores({ [tableName]: indexes });\n this.store = this.table<TItem, TKey>(tableName);\n }\n}\n\n/**\n * Build a typed IndexedDB-backed store using Dexie. Optionally scope every\n * operation by an `ownerField` (useful for multi-user SSE history, drafts,\n * cache per workspace, etc.).\n *\n * Dexie is an **optional peer dependency** — install it (`npm i dexie`) only\n * when your app needs offline storage.\n *\n * @example\n * type Note = { id: string; owner_id: string; text: string; created_at: string };\n * const notes = createOfflineStore<Note, string>({\n * databaseName: \"TempestNotes\",\n * version: 1,\n * tableName: \"notes\",\n * indexes: \"&id, owner_id, created_at\",\n * ownerField: \"owner_id\",\n * });\n * await notes.put({ id: \"n1\", owner_id: \"u1\", text: \"hi\", created_at: ... }, \"u1\");\n * const mine = await notes.list(\"u1\", { orderBy: \"created_at\", reverse: true });\n */\nexport function createOfflineStore<TItem, TKey extends string | number = string>(\n config: OfflineStoreConfig<TItem>,\n): OfflineStore<TItem, TKey> {\n const { databaseName, version, tableName, indexes } = config;\n const db = new GenericDb<TItem, TKey>(databaseName, version, tableName, indexes);\n return buildStore<TItem, TKey>(db, db.store, config);\n}\n\n/**\n * Wrap an existing Dexie table in the {@link OfflineStore} surface.\n *\n * Split out so a store can be built over a table this function did not create,\n * which is what lets {@link createOfflineDatabase} place several stores on one\n * database rather than one database per table.\n *\n * @param db - The Dexie instance owning the table.\n * @param table - The table to wrap.\n * @param config - Key path and owner scoping for this table.\n * @returns The store surface bound to that table.\n */\nexport function buildStore<TItem, TKey extends string | number = string>(\n db: Dexie,\n table: Table<TItem, TKey>,\n config: Pick<OfflineStoreConfig<TItem>, \"keyPath\" | \"ownerField\">,\n): OfflineStore<TItem, TKey> {\n const { keyPath = \"id\", ownerField } = config;\n\n function withOwner(item: TItem, owner?: string): TItem {\n if (!ownerField || !owner) return item;\n return { ...item, [ownerField]: owner } as TItem;\n }\n\n async function list(owner?: string, options: ListOptions<TItem> = {}): Promise<TItem[]> {\n const { orderBy = keyPath, reverse = false, limit, offset, filter } = options;\n\n let collection =\n ownerField && owner ? table.where(ownerField).equals(owner) : table.toCollection();\n\n if (filter) collection = collection.filter(filter);\n\n let items =\n orderBy === keyPath ? await collection.toArray() : await collection.sortBy(orderBy);\n\n if (reverse) items = items.reverse();\n if (offset) items = items.slice(offset);\n if (typeof limit === \"number\") items = items.slice(0, limit);\n return items;\n }\n\n return {\n put: (item, owner) => table.put(withOwner(item, owner)) as Promise<TKey>,\n bulkPut: (items, owner) =>\n table.bulkPut(items.map((item) => withOwner(item, owner))) as Promise<TKey>,\n get: (key) => table.get(key),\n list,\n update: (key, changes) => table.update(key, changes as UpdateSpec<TItem>),\n updateMany: async (owner, changes) => {\n const spec = changes as UpdateSpec<TItem>;\n if (ownerField && owner) {\n return table.where(ownerField).equals(owner).modify(spec);\n }\n return table.toCollection().modify(spec);\n },\n delete: (key) => table.delete(key),\n clear: async (owner) => {\n if (ownerField && owner) {\n await table.where(ownerField).equals(owner).delete();\n return;\n }\n await table.clear();\n },\n count: (owner) => {\n if (ownerField && owner) {\n return table.where(ownerField).equals(owner).count();\n }\n return table.count();\n },\n raw: table,\n db,\n };\n}\n"],"mappings":";;AA+DA,IAAM,IAAN,cAA6D,EAAM;CAC/D;CAEA,YAAY,GAAc,GAAiB,GAAmB,GAAiB;EAG3E,AAFA,MAAM,CAAI,GACV,KAAK,QAAQ,CAAO,CAAC,CAAC,OAAO,GAAG,IAAY,EAAQ,CAAC,GACrD,KAAK,QAAQ,KAAK,MAAmB,CAAS;CAClD;AACJ;AAsBA,SAAgB,EACZ,GACyB;CACzB,IAAM,EAAE,iBAAc,YAAS,cAAW,eAAY,GAChD,IAAK,IAAI,EAAuB,GAAc,GAAS,GAAW,CAAO;CAC/E,OAAO,EAAwB,GAAI,EAAG,OAAO,CAAM;AACvD;AAcA,SAAgB,EACZ,GACA,GACA,GACyB;CACzB,IAAM,EAAE,aAAU,MAAM,kBAAe;CAEvC,SAAS,EAAU,GAAa,GAAuB;EAEnD,OADI,CAAC,KAAc,CAAC,IAAc,IAC3B;GAAE,GAAG;IAAO,IAAa;EAAM;CAC1C;CAEA,eAAe,EAAK,GAAgB,IAA8B,CAAC,GAAqB;EACpF,IAAM,EAAE,aAAU,GAAS,aAAU,IAAO,UAAO,WAAQ,cAAW,GAElE,IACA,KAAc,IAAQ,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,IAAI,EAAM,aAAa;EAErF,AAAI,MAAQ,IAAa,EAAW,OAAO,CAAM;EAEjD,IAAI,IACA,MAAY,IAAU,MAAM,EAAW,QAAQ,IAAI,MAAM,EAAW,OAAO,CAAO;EAKtF,OAHI,MAAS,IAAQ,EAAM,QAAQ,IAC/B,MAAQ,IAAQ,EAAM,MAAM,CAAM,IAClC,OAAO,KAAU,aAAU,IAAQ,EAAM,MAAM,GAAG,CAAK,IACpD;CACX;CAEA,OAAO;EACH,MAAM,GAAM,MAAU,EAAM,IAAI,EAAU,GAAM,CAAK,CAAC;EACtD,UAAU,GAAO,MACb,EAAM,QAAQ,EAAM,KAAK,MAAS,EAAU,GAAM,CAAK,CAAC,CAAC;EAC7D,MAAM,MAAQ,EAAM,IAAI,CAAG;EAC3B;EACA,SAAS,GAAK,MAAY,EAAM,OAAO,GAAK,CAA4B;EACxE,YAAY,OAAO,GAAO,MAAY;GAClC,IAAM,IAAO;GAIb,OAHI,KAAc,IACP,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,CAAI,IAErD,EAAM,aAAa,CAAC,CAAC,OAAO,CAAI;EAC3C;EACA,SAAS,MAAQ,EAAM,OAAO,CAAG;EACjC,OAAO,OAAO,MAAU;GACpB,IAAI,KAAc,GAAO;IACrB,MAAM,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO;IACnD;GACJ;GACA,MAAM,EAAM,MAAM;EACtB;EACA,QAAQ,MACA,KAAc,IACP,EAAM,MAAM,CAAU,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,MAAM,IAEhD,EAAM,MAAM;EAEvB,KAAK;EACL;CACJ;AACJ"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./constants.cjs"),t=require("./retry-policy.cjs");let
|
|
1
|
+
const e=require("./constants.cjs"),t=require("./retry-policy.cjs"),n=require("./foreign-client-warning.cjs");let r=require("react"),i=require("react/jsx-runtime"),a=require("@tanstack/react-query");function o({children:o,client:s,defaultOptions:c}){n.warnOnForeignClient(s);let[l]=(0,r.useState)(()=>s??new a.QueryClient({defaultOptions:{queries:{staleTime:e.STALE_TIME.DEFAULT,gcTime:e.CACHE_TIME.DEFAULT,retry:t.shouldRetryQuery,refetchOnWindowFocus:!1,...c?.queries??{}},mutations:{retry:0,...c?.mutations??{}}}}));return(0,i.jsx)(a.QueryClientProvider,{client:l,children:o})}exports.QueryProvider=o;
|
|
2
2
|
//# sourceMappingURL=QueryProvider.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryProvider.cjs","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\nimport { shouldRetryQuery } from \"./retry-policy\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for\n * failures worth replaying).\n *\n * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other\n * than 408/429 is the server refusing on purpose, and replaying it only doubles\n * the network log while the spinner keeps turning.\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: shouldRetryQuery,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"QueryProvider.cjs","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\nimport { shouldRetryQuery } from \"./retry-policy\";\nimport { warnOnForeignClient } from \"./foreign-client-warning\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for\n * failures worth replaying).\n *\n * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other\n * than 408/429 is the server refusing on purpose, and replaying it only doubles\n * the network log while the spinner keeps turning.\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n warnOnForeignClient(client);\n\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: shouldRetryQuery,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":"sMAyBA,SAAgB,EAAc,CAAE,WAAU,SAAQ,kBAAsC,CACpF,EAAA,oBAAoB,CAAM,EAE1B,GAAM,CAAC,IAAA,EAAkB,EAAA,SAAA,KAEjB,GACA,IAAI,EAAA,YAAY,CACZ,eAAgB,CACZ,QAAS,CACL,UAAW,EAAA,WAAW,QACtB,OAAQ,EAAA,WAAW,QACnB,MAAO,EAAA,iBACP,qBAAsB,GACtB,GAAI,GAAgB,SAAW,CAAC,CACpC,EACA,UAAW,CACP,MAAO,EACP,GAAI,GAAgB,WAAa,CAAC,CACtC,CACJ,CACJ,CAAC,CACT,EAEA,OAAO,EAAA,EAAA,IAAA,CAAC,EAAA,oBAAD,CAAqB,OAAQ,EAAiB,UAA8B,CAAA,CACvF"}
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
import { CACHE_TIME as e, STALE_TIME as t } from "./constants.js";
|
|
2
2
|
import { shouldRetryQuery as n } from "./retry-policy.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { warnOnForeignClient as r } from "./foreign-client-warning.js";
|
|
4
|
+
import { useState as i } from "react";
|
|
5
|
+
import { jsx as a } from "react/jsx-runtime";
|
|
6
|
+
import { QueryClient as o, QueryClientProvider as s } from "@tanstack/react-query";
|
|
6
7
|
//#region src/query/QueryProvider.tsx
|
|
7
|
-
function
|
|
8
|
-
|
|
8
|
+
function c({ children: c, client: l, defaultOptions: u }) {
|
|
9
|
+
r(l);
|
|
10
|
+
let [d] = i(() => l ?? new o({ defaultOptions: {
|
|
9
11
|
queries: {
|
|
10
12
|
staleTime: t.DEFAULT,
|
|
11
13
|
gcTime: e.DEFAULT,
|
|
12
14
|
retry: n,
|
|
13
15
|
refetchOnWindowFocus: !1,
|
|
14
|
-
...
|
|
16
|
+
...u?.queries ?? {}
|
|
15
17
|
},
|
|
16
18
|
mutations: {
|
|
17
19
|
retry: 0,
|
|
18
|
-
...
|
|
20
|
+
...u?.mutations ?? {}
|
|
19
21
|
}
|
|
20
22
|
} }));
|
|
21
|
-
return /* @__PURE__ */
|
|
22
|
-
client:
|
|
23
|
-
children:
|
|
23
|
+
return /* @__PURE__ */ a(s, {
|
|
24
|
+
client: d,
|
|
25
|
+
children: c
|
|
24
26
|
});
|
|
25
27
|
}
|
|
26
28
|
//#endregion
|
|
27
|
-
export {
|
|
29
|
+
export { c as QueryProvider };
|
|
28
30
|
|
|
29
31
|
//# sourceMappingURL=QueryProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QueryProvider.js","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\nimport { shouldRetryQuery } from \"./retry-policy\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for\n * failures worth replaying).\n *\n * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other\n * than 408/429 is the server refusing on purpose, and replaying it only doubles\n * the network log while the spinner keeps turning.\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: shouldRetryQuery,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"QueryProvider.js","names":[],"sources":["../../src/query/QueryProvider.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport type { DefaultOptions } from \"@tanstack/react-query\";\nimport { STALE_TIME, CACHE_TIME } from \"./constants\";\nimport { shouldRetryQuery } from \"./retry-policy\";\nimport { warnOnForeignClient } from \"./foreign-client-warning\";\n\nexport interface QueryProviderProps {\n children: ReactNode;\n /** Pass an existing client to share it across roots. */\n client?: QueryClient;\n /** Overrides for the default options applied when no `client` is provided. */\n defaultOptions?: DefaultOptions;\n}\n\n/**\n * Wrapper around `QueryClientProvider` that bootstraps a `QueryClient` with\n * sane SDK defaults (5-minute stale time, 30-minute gc time, one retry for\n * failures worth replaying).\n *\n * Retries follow {@link shouldRetryQuery} rather than a flat count: a 4xx other\n * than 408/429 is the server refusing on purpose, and replaying it only doubles\n * the network log while the spinner keeps turning.\n */\nexport function QueryProvider({ children, client, defaultOptions }: QueryProviderProps) {\n warnOnForeignClient(client);\n\n const [internalClient] = useState<QueryClient>(\n () =>\n client ??\n new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME.DEFAULT,\n gcTime: CACHE_TIME.DEFAULT,\n retry: shouldRetryQuery,\n refetchOnWindowFocus: false,\n ...(defaultOptions?.queries ?? {}),\n },\n mutations: {\n retry: 0,\n ...(defaultOptions?.mutations ?? {}),\n },\n },\n }),\n );\n\n return <QueryClientProvider client={internalClient}>{children}</QueryClientProvider>;\n}\n"],"mappings":";;;;;;;AAyBA,SAAgB,EAAc,EAAE,aAAU,WAAQ,qBAAsC;CACpF,EAAoB,CAAM;CAE1B,IAAM,CAAC,KAAkB,QAEjB,KACA,IAAI,EAAY,EACZ,gBAAgB;EACZ,SAAS;GACL,WAAW,EAAW;GACtB,QAAQ,EAAW;GACnB,OAAO;GACP,sBAAsB;GACtB,GAAI,GAAgB,WAAW,CAAC;EACpC;EACA,WAAW;GACP,OAAO;GACP,GAAI,GAAgB,aAAa,CAAC;EACtC;CACJ,EACJ,CAAC,CACT;CAEA,OAAO,kBAAC,GAAD;EAAqB,QAAQ;EAAiB;CAA8B,CAAA;AACvF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../utils/dev-mode.cjs"),t=require("../utils/duplicate-instance.cjs");let n=require("@tanstack/react-query");var r=!1;function i(i){r||i===void 0||!e.isDevBuild()||i instanceof n.QueryClient||typeof i.getQueryCache==`function`&&typeof i.mount==`function`&&(r=!0,console.warn(`[tempest-react-sdk] <QueryProvider client={...} /> received a QueryClient built by a different copy of @tanstack/react-query. The SDK publishes it on its own copy's context, so the app's \`useQuery\` calls will throw "No QueryClient set" even though this provider is mounted. `+t.DUPLICATE_COPY_REMEDY))}exports.warnOnForeignClient=i;
|
|
2
|
+
//# sourceMappingURL=foreign-client-warning.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-client-warning.cjs","names":[],"sources":["../../src/query/foreign-client-warning.ts"],"sourcesContent":["/**\n * Internal, in its own file for the reason the AppBar warning is: a module that\n * exports a component may export nothing else without breaking Fast Refresh\n * (`react-refresh/only-export-components`), and the warn-once latch needs a\n * reset for tests to assert the warning more than once.\n */\nimport { QueryClient } from \"@tanstack/react-query\";\n\nimport { isDevBuild } from \"../utils/dev-mode\";\nimport { DUPLICATE_COPY_REMEDY } from \"../utils/duplicate-instance\";\n\n/** Set once the warning has printed, so a re-render does not bury the console. */\nlet warnedForeignClient = false;\n\n/**\n * Reset the warn-once latch. Test-only: the latch is module state, so a suite\n * asserting the warning more than once would otherwise see it only the first time.\n */\nexport function resetForeignClientWarning(): void {\n warnedForeignClient = false;\n}\n\n/**\n * Warn when the `client` handed in was built by a different copy of\n * `@tanstack/react-query`.\n *\n * This prop is the one place the app's copy and the SDK's copy touch: the app\n * constructs a `QueryClient` and passes it in, and `QueryClientProvider` —\n * resolved from the SDK's copy — publishes it on *that* copy's context. Every\n * `useQuery` in the app then reads the app copy's context, finds nothing, and\n * throws `No QueryClient set, use QueryClientProvider to set one` while a\n * provider is plainly mounted three lines up. Nothing in that message points at\n * the duplicate, which is why it costs an afternoon.\n *\n * `instanceof` is the discriminator, and it is exactly the right one: two copies\n * define two distinct `QueryClient` classes, so a client from the other copy\n * duck-types perfectly and fails the identity check. The duck-type guard runs\n * first so the warning fires only on the shape that is actually a client —\n * without it, every hand-rolled test double would trip it.\n *\n * @param client - The client the caller passed, if any.\n */\nexport function warnOnForeignClient(client: QueryClient | undefined): void {\n if (warnedForeignClient || client === undefined || !isDevBuild()) return;\n if (client instanceof QueryClient) return;\n const looksLikeClient =\n typeof (client as Partial<QueryClient>).getQueryCache === \"function\" &&\n typeof (client as Partial<QueryClient>).mount === \"function\";\n if (!looksLikeClient) return;\n warnedForeignClient = true;\n console.warn(\n \"[tempest-react-sdk] <QueryProvider client={...} /> received a QueryClient \" +\n \"built by a different copy of @tanstack/react-query. The SDK publishes it on \" +\n \"its own copy's context, so the app's `useQuery` calls will throw \" +\n '\"No QueryClient set\" even though this provider is mounted. ' +\n DUPLICATE_COPY_REMEDY,\n );\n}\n"],"mappings":"6HAYA,IAAI,EAAsB,GA8B1B,SAAgB,EAAoB,EAAuC,CACnE,GAAuB,IAAW,IAAA,IAAa,CAAC,EAAA,WAAW,GAC3D,aAAkB,EAAA,aAElB,OAAQ,EAAgC,eAAkB,YAC1D,OAAQ,EAAgC,OAAU,aAEtD,EAAsB,GACtB,QAAQ,KACJ,uRAII,EAAA,qBACR,EACJ"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { isDevBuild as e } from "../utils/dev-mode.js";
|
|
2
|
+
import { DUPLICATE_COPY_REMEDY as t } from "../utils/duplicate-instance.js";
|
|
3
|
+
import { QueryClient as n } from "@tanstack/react-query";
|
|
4
|
+
//#region src/query/foreign-client-warning.ts
|
|
5
|
+
var r = !1;
|
|
6
|
+
function i(i) {
|
|
7
|
+
r || i === void 0 || !e() || i instanceof n || typeof i.getQueryCache == "function" && typeof i.mount == "function" && (r = !0, console.warn("[tempest-react-sdk] <QueryProvider client={...} /> received a QueryClient built by a different copy of @tanstack/react-query. The SDK publishes it on its own copy's context, so the app's `useQuery` calls will throw \"No QueryClient set\" even though this provider is mounted. " + t));
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { i as warnOnForeignClient };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=foreign-client-warning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-client-warning.js","names":[],"sources":["../../src/query/foreign-client-warning.ts"],"sourcesContent":["/**\n * Internal, in its own file for the reason the AppBar warning is: a module that\n * exports a component may export nothing else without breaking Fast Refresh\n * (`react-refresh/only-export-components`), and the warn-once latch needs a\n * reset for tests to assert the warning more than once.\n */\nimport { QueryClient } from \"@tanstack/react-query\";\n\nimport { isDevBuild } from \"../utils/dev-mode\";\nimport { DUPLICATE_COPY_REMEDY } from \"../utils/duplicate-instance\";\n\n/** Set once the warning has printed, so a re-render does not bury the console. */\nlet warnedForeignClient = false;\n\n/**\n * Reset the warn-once latch. Test-only: the latch is module state, so a suite\n * asserting the warning more than once would otherwise see it only the first time.\n */\nexport function resetForeignClientWarning(): void {\n warnedForeignClient = false;\n}\n\n/**\n * Warn when the `client` handed in was built by a different copy of\n * `@tanstack/react-query`.\n *\n * This prop is the one place the app's copy and the SDK's copy touch: the app\n * constructs a `QueryClient` and passes it in, and `QueryClientProvider` —\n * resolved from the SDK's copy — publishes it on *that* copy's context. Every\n * `useQuery` in the app then reads the app copy's context, finds nothing, and\n * throws `No QueryClient set, use QueryClientProvider to set one` while a\n * provider is plainly mounted three lines up. Nothing in that message points at\n * the duplicate, which is why it costs an afternoon.\n *\n * `instanceof` is the discriminator, and it is exactly the right one: two copies\n * define two distinct `QueryClient` classes, so a client from the other copy\n * duck-types perfectly and fails the identity check. The duck-type guard runs\n * first so the warning fires only on the shape that is actually a client —\n * without it, every hand-rolled test double would trip it.\n *\n * @param client - The client the caller passed, if any.\n */\nexport function warnOnForeignClient(client: QueryClient | undefined): void {\n if (warnedForeignClient || client === undefined || !isDevBuild()) return;\n if (client instanceof QueryClient) return;\n const looksLikeClient =\n typeof (client as Partial<QueryClient>).getQueryCache === \"function\" &&\n typeof (client as Partial<QueryClient>).mount === \"function\";\n if (!looksLikeClient) return;\n warnedForeignClient = true;\n console.warn(\n \"[tempest-react-sdk] <QueryProvider client={...} /> received a QueryClient \" +\n \"built by a different copy of @tanstack/react-query. The SDK publishes it on \" +\n \"its own copy's context, so the app's `useQuery` calls will throw \" +\n '\"No QueryClient set\" even though this provider is mounted. ' +\n DUPLICATE_COPY_REMEDY,\n );\n}\n"],"mappings":";;;;AAYA,IAAI,IAAsB;AA8B1B,SAAgB,EAAoB,GAAuC;CACnE,KAAuB,MAAW,KAAA,KAAa,CAAC,EAAW,KAC3D,aAAkB,KAElB,OAAQ,EAAgC,iBAAkB,cAC1D,OAAQ,EAAgC,SAAU,eAEtD,IAAsB,IACtB,QAAQ,KACJ,yRAII,CACR;AACJ"}
|