tempest-react-sdk 0.50.0 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/audio/sfx-pool.cjs +1 -1
  2. package/dist/audio/sfx-pool.cjs.map +1 -1
  3. package/dist/audio/sfx-pool.js +36 -36
  4. package/dist/audio/sfx-pool.js.map +1 -1
  5. package/dist/auth/refresh-queue.cjs +1 -1
  6. package/dist/auth/refresh-queue.cjs.map +1 -1
  7. package/dist/auth/refresh-queue.js +6 -6
  8. package/dist/auth/refresh-queue.js.map +1 -1
  9. package/dist/components/BarList/BarList.cjs +1 -1
  10. package/dist/components/BarList/BarList.cjs.map +1 -1
  11. package/dist/components/BarList/BarList.js +6 -1
  12. package/dist/components/BarList/BarList.js.map +1 -1
  13. package/dist/components/BarList/bar-list-model.cjs +1 -1
  14. package/dist/components/BarList/bar-list-model.cjs.map +1 -1
  15. package/dist/components/BarList/bar-list-model.js +8 -8
  16. package/dist/components/BarList/bar-list-model.js.map +1 -1
  17. package/dist/components/Markdown/markdown-parse.cjs +5 -5
  18. package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
  19. package/dist/components/Markdown/markdown-parse.js +66 -63
  20. package/dist/components/Markdown/markdown-parse.js.map +1 -1
  21. package/dist/components/Scheduler/Scheduler.cjs +1 -1
  22. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  23. package/dist/components/Scheduler/Scheduler.js +1 -1
  24. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  25. package/dist/components/Scheduler/Scheduler.module.cjs.map +1 -1
  26. package/dist/components/Scheduler/Scheduler.module.js.map +1 -1
  27. package/dist/components/Sidebar/Sidebar.cjs +1 -1
  28. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  29. package/dist/components/Sidebar/Sidebar.js +25 -45
  30. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  31. package/dist/components/Sidebar/Sidebar.module.cjs.map +1 -1
  32. package/dist/components/Sidebar/Sidebar.module.js.map +1 -1
  33. package/dist/components/Sidebar/SidebarEntry.cjs +2 -0
  34. package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -0
  35. package/dist/components/Sidebar/SidebarEntry.js +40 -0
  36. package/dist/components/Sidebar/SidebarEntry.js.map +1 -0
  37. package/dist/http/api-client.cjs +1 -1
  38. package/dist/http/api-client.cjs.map +1 -1
  39. package/dist/http/api-client.js +84 -71
  40. package/dist/http/api-client.js.map +1 -1
  41. package/dist/http/describe-api-error.cjs +1 -1
  42. package/dist/http/describe-api-error.cjs.map +1 -1
  43. package/dist/http/describe-api-error.js +4 -2
  44. package/dist/http/describe-api-error.js.map +1 -1
  45. package/dist/http/timeout.cjs +2 -0
  46. package/dist/http/timeout.cjs.map +1 -0
  47. package/dist/http/timeout.js +22 -0
  48. package/dist/http/timeout.js.map +1 -0
  49. package/dist/http/use-describe-api-error.cjs +1 -1
  50. package/dist/http/use-describe-api-error.cjs.map +1 -1
  51. package/dist/http/use-describe-api-error.js +3 -2
  52. package/dist/http/use-describe-api-error.js.map +1 -1
  53. package/dist/icons/material-symbols.cjs.map +1 -1
  54. package/dist/icons/material-symbols.js.map +1 -1
  55. package/dist/offline/create-offline-database.cjs +2 -0
  56. package/dist/offline/create-offline-database.cjs.map +1 -0
  57. package/dist/offline/create-offline-database.js +29 -0
  58. package/dist/offline/create-offline-database.js.map +1 -0
  59. package/dist/offline/create-offline-store.cjs +1 -1
  60. package/dist/offline/create-offline-store.cjs.map +1 -1
  61. package/dist/offline/create-offline-store.js +29 -25
  62. package/dist/offline/create-offline-store.js.map +1 -1
  63. package/dist/styles.css +1 -1
  64. package/dist/tempest-react-sdk.cjs +1 -1
  65. package/dist/tempest-react-sdk.d.ts +286 -13
  66. package/dist/tempest-react-sdk.js +43 -42
  67. package/dist/utils/csv.cjs.map +1 -1
  68. package/dist/utils/csv.js.map +1 -1
  69. package/dist/utils/format.cjs +1 -1
  70. package/dist/utils/format.cjs.map +1 -1
  71. package/dist/utils/format.js +8 -3
  72. package/dist/utils/format.js.map +1 -1
  73. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  74. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  75. package/dist/vite/tempest-pwa-dev-sw.js +49 -41
  76. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  77. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"material-symbols.js","names":[],"sources":["../../src/icons/material-symbols.ts"],"sourcesContent":["import type { IconName } from \"./generated/icon-name\";\n\n/**\n * The slug `fromMaterialSymbol` falls back to when a code is unknown.\n *\n * A neutral glyph rather than nothing: a category created in an admin with a\n * code this table has not learned yet still has to draw something, or it opens a\n * hole in the grid and the bug reads as a layout problem instead of a missing\n * mapping.\n *\n * Lucide's `circle-help` is a deprecated alias of this slug. `<Icon>` resolves\n * aliases, so both render — but the bridge emits the canonical name, since a\n * value that gets persisted should not be one lucide has already renamed.\n */\nexport const MATERIAL_SYMBOL_FALLBACK: IconName = \"circle-question-mark\";\n\n/**\n * Material Symbols code → lucide slug, for backends that store `icon_code`.\n *\n * Every Python backend we write stores a category icon as a Material Symbol\n * (`build`, `format_paint`, `electrical_services`) — the vocabulary Flutter,\n * Android and the administrative seeds already speak. The SDK speaks lucide in\n * kebab-case. The two lists do not meet, and the failure is nastier than an\n * empty screen: a handful of codes collide by accident, so roughly one row in\n * ten draws the right icon and the bug reads as \"some icons went missing\".\n *\n * **Still not the full vocabulary.** Material Symbols publishes ~6100 names and\n * almost none of them will ever appear in an `icon_code` of ours, so this grows\n * on demand, one hand-written pair at a time — a map generated by name\n * heuristics gets it badly wrong, starting with `build`, which is a wrench in\n * Material Symbols and nothing to do with construction.\n *\n * **How the codes were chosen.** Two lots, merged. The first was the trades seed\n * the bridge started as. The second is the head of Material Symbols' own\n * published popularity ranking (`fonts.google.com/metadata/icons`), which is\n * measured usage rather than a guess at which names a seed will contain — every\n * key was checked to exist in that vocabulary before landing.\n *\n * **Where the two lots disagreed**, six keys, the tie went to whichever mapping\n * keeps two distinct Material Symbols names distinct in lucide: `error` →\n * `circle-alert` because `cancel` already owns `circle-x`; `payments` →\n * `banknote` because `account_balance_wallet` already owns `wallet`;\n * `receipt_long` → `receipt-text` because `receipt` already owns `receipt`;\n * `construction` → itself because `engineering` already owns `hard-hat`; and\n * `today` → `calendar` with `event` → `calendar-days`, splitting the generic\n * calendar names from the dated one.\n *\n * Every target is checked mechanically against the slug list by\n * `material-symbols.test.ts`, so a pair pointing at a name lucide does not ship\n * — or at one it has since deprecated — fails the suite instead of reaching a\n * grid. What no test can check is whether the *chosen* icon is the right\n * metaphor, which is why pairs go in by hand.\n *\n * **Approximations, deliberate and not one-to-one** (the full list lives in\n * `docs/icons.md`):\n *\n * - `plumbing` → `shower-head`, because lucide has no pipe.\n * - `build`, `handyman` and `hardware` all land on `wrench` — Material Symbols\n * distinguishes the tools, lucide does not.\n * - `pedal_bike`, `two_wheeler` and `delivery_dining` all land on `bike`.\n * - `dentistry` → `face-slightly-smiling`, since lucide ships no tooth.\n * - `iron` → `shirt` and `cleaning_services` → `spray-can`: the activity, not\n * the implement.\n * - `roofing` → `house`, the same glyph as `home`, because lucide has no roof.\n *\n * The identity pairs are here on purpose. Their names happen to match in both\n * vocabularies, so they already render today — leaving them out would send them\n * to {@link MATERIAL_SYMBOL_FALLBACK} and make this bridge a regression for\n * exactly the codes that used to work.\n *\n * @see fromMaterialSymbol — the lookup you normally call.\n */\nexport const materialToLucide: Readonly<Record<string, IconName>> = {\n ac_unit: \"snowflake\",\n account_balance: \"landmark\",\n account_balance_wallet: \"wallet\",\n account_box: \"square-user\",\n account_circle: \"circle-user\",\n add: \"plus\",\n add_circle: \"circle-plus\",\n add_circle_outline: \"circle-plus\",\n add_shopping_cart: \"shopping-cart\",\n admin_panel_settings: \"shield-user\",\n analytics: \"chart-column\",\n apps: \"layout-grid\",\n arrow_back: \"arrow-left\",\n arrow_back_ios: \"chevron-left\",\n arrow_back_ios_new: \"chevron-left\",\n arrow_downward: \"arrow-down\",\n arrow_drop_down: \"chevron-down\",\n arrow_forward: \"arrow-right\",\n arrow_forward_ios: \"chevron-right\",\n arrow_right: \"chevron-right\",\n arrow_right_alt: \"arrow-right\",\n arrow_upward: \"arrow-up\",\n article: \"newspaper\",\n assignment: \"clipboard-list\",\n attach_file: \"paperclip\",\n attach_money: \"dollar-sign\",\n autorenew: \"refresh-cw\",\n badge: \"id-card\",\n balance: \"scale\",\n bar_chart: \"chart-column\",\n battery_full: \"battery-full\",\n bluetooth: \"bluetooth\",\n bolt: \"zap\",\n bookmark: \"bookmark\",\n brush: \"brush\",\n build: \"wrench\",\n business: \"building-2\",\n cake: \"cake\",\n calculate: \"calculator\",\n calendar_month: \"calendar\",\n calendar_today: \"calendar\",\n call: \"phone\",\n campaign: \"megaphone\",\n cancel: \"circle-x\",\n car_repair: \"car\",\n carpenter: \"hammer\",\n category: \"shapes\",\n chair: \"armchair\",\n chat: \"message-circle\",\n check: \"check\",\n check_box: \"square-check\",\n check_box_outline_blank: \"square\",\n check_circle: \"circle-check\",\n check_circle_outline: \"circle-check\",\n chevron_left: \"chevron-left\",\n chevron_right: \"chevron-right\",\n child_care: \"baby\",\n circle: \"circle\",\n cleaning_services: \"spray-can\",\n clear: \"x\",\n close: \"x\",\n cloud: \"cloud\",\n code: \"code\",\n computer: \"monitor\",\n construction: \"construction\",\n content_copy: \"copy\",\n content_cut: \"scissors\",\n credit_card: \"credit-card\",\n dark_mode: \"moon\",\n dashboard: \"layout-dashboard\",\n date_range: \"calendar-range\",\n delete: \"trash-2\",\n delete_forever: \"trash-2\",\n delete_outline: \"trash-2\",\n delivery_dining: \"bike\",\n dentistry: \"face-slightly-smiling\",\n description: \"file-text\",\n directions_car: \"car\",\n done: \"check\",\n done_all: \"check-check\",\n door_front: \"door-open\",\n download: \"download\",\n east: \"arrow-right\",\n edit: \"pencil\",\n edit_note: \"pencil-line\",\n electrical_services: \"plug-zap\",\n email: \"mail\",\n emoji_events: \"trophy\",\n engineering: \"hard-hat\",\n error: \"circle-alert\",\n error_outline: \"circle-alert\",\n event: \"calendar-days\",\n expand_less: \"chevron-up\",\n expand_more: \"chevron-down\",\n explore: \"compass\",\n face: \"face-slightly-smiling\",\n fact_check: \"clipboard-check\",\n favorite: \"heart\",\n favorite_border: \"heart\",\n file_download: \"download\",\n file_upload: \"upload\",\n filter_alt: \"funnel\",\n filter_list: \"funnel\",\n fingerprint: \"fingerprint-pattern\",\n fitness_center: \"dumbbell\",\n flight: \"plane\",\n folder: \"folder\",\n format_list_bulleted: \"list\",\n format_paint: \"paint-roller\",\n forum: \"messages-square\",\n gavel: \"gavel\",\n grade: \"star\",\n grass: \"sprout\",\n grid_view: \"layout-grid\",\n group: \"users\",\n groups: \"users\",\n handyman: \"wrench\",\n hardware: \"wrench\",\n help: \"circle-question-mark\",\n help_outline: \"circle-question-mark\",\n highlight_off: \"circle-x\",\n history: \"rotate-ccw-clock\",\n home: \"house\",\n hotel: \"bed-double\",\n image: \"image\",\n info: \"info\",\n insights: \"chart-line\",\n inventory: \"package\",\n inventory_2: \"package\",\n iron: \"shirt\",\n key: \"key\",\n kitchen: \"refrigerator\",\n language: \"globe\",\n laptop: \"laptop\",\n light_mode: \"sun\",\n lightbulb: \"lightbulb\",\n link: \"link\",\n list: \"list\",\n list_alt: \"list\",\n local_bar: \"wine\",\n local_cafe: \"coffee\",\n local_florist: \"flower\",\n local_gas_station: \"fuel\",\n local_laundry_service: \"washing-machine\",\n local_offer: \"tag\",\n local_shipping: \"truck\",\n local_taxi: \"car-taxi-front\",\n location_on: \"map-pin\",\n lock: \"lock\",\n lock_open: \"lock-open\",\n login: \"log-in\",\n logout: \"log-out\",\n mail: \"mail\",\n mail_outline: \"mail\",\n manage_accounts: \"user-cog\",\n map: \"map\",\n medical_services: \"stethoscope\",\n menu: \"menu\",\n menu_book: \"book-open\",\n mic: \"mic\",\n mode_edit: \"pencil\",\n monetization_on: \"circle-dollar-sign\",\n more_horiz: \"ellipsis\",\n more_vert: \"ellipsis-vertical\",\n music_note: \"music\",\n navigate_next: \"chevron-right\",\n note_add: \"file-plus\",\n notifications: \"bell\",\n notifications_active: \"bell-ring\",\n open_in_new: \"external-link\",\n paid: \"circle-dollar-sign\",\n palette: \"palette\",\n pause: \"pause\",\n payments: \"banknote\",\n pedal_bike: \"bike\",\n people: \"users\",\n perm_identity: \"user\",\n person: \"user\",\n person_add: \"user-plus\",\n person_outline: \"user\",\n pest_control: \"bug\",\n pets: \"paw-print\",\n phone: \"phone\",\n phone_iphone: \"smartphone\",\n photo_camera: \"camera\",\n picture_as_pdf: \"file-text\",\n pie_chart: \"chart-pie\",\n place: \"map-pin\",\n play_arrow: \"play\",\n play_circle: \"circle-play\",\n play_circle_filled: \"circle-play\",\n plumbing: \"shower-head\",\n power_settings_new: \"power\",\n print: \"printer\",\n public: \"globe\",\n qr_code_scanner: \"scan-line\",\n question_answer: \"messages-square\",\n radio_button_checked: \"circle-dot\",\n radio_button_unchecked: \"circle\",\n receipt: \"receipt\",\n receipt_long: \"receipt-text\",\n refresh: \"refresh-cw\",\n remove: \"minus\",\n remove_circle_outline: \"circle-minus\",\n reorder: \"list\",\n report_problem: \"triangle-alert\",\n restart_alt: \"rotate-ccw\",\n restaurant: \"utensils\",\n roofing: \"house\",\n room: \"map-pin\",\n router: \"router\",\n save: \"save\",\n savings: \"piggy-bank\",\n schedule: \"clock\",\n school: \"graduation-cap\",\n search: \"search\",\n security: \"shield-check\",\n send: \"send\",\n settings: \"settings\",\n share: \"share-2\",\n shield: \"shield\",\n shopping_bag: \"shopping-bag\",\n shopping_cart: \"shopping-cart\",\n smartphone: \"smartphone\",\n soap: \"soap-dispenser-droplet\",\n sort: \"arrow-down-up\",\n spa: \"flower-2\",\n star: \"star\",\n star_border: \"star\",\n star_rate: \"star\",\n store: \"store\",\n storefront: \"store\",\n support_agent: \"headset\",\n sync: \"refresh-cw\",\n task_alt: \"circle-check\",\n timer: \"timer\",\n today: \"calendar\",\n toggle_on: \"toggle-right\",\n translate: \"languages\",\n trending_up: \"trending-up\",\n tune: \"sliders-horizontal\",\n tv: \"tv\",\n two_wheeler: \"bike\",\n upload: \"upload\",\n upload_file: \"upload\",\n vaccines: \"syringe\",\n verified: \"badge-check\",\n verified_user: \"shield-check\",\n videocam: \"video\",\n view_list: \"list\",\n visibility: \"eye\",\n visibility_off: \"eye-off\",\n vpn_key: \"key-round\",\n warehouse: \"warehouse\",\n warning: \"triangle-alert\",\n warning_amber: \"triangle-alert\",\n watch_later: \"clock\",\n water_drop: \"droplet\",\n wifi: \"wifi\",\n work: \"briefcase\",\n yard: \"trees\",\n};\n\n/**\n * Translate a Material Symbols code into a lucide slug, always returning one.\n *\n * Never returns `undefined`: an unknown code resolves to `fallback`, so a row\n * whose `icon_code` this table has not learned yet still renders. The input is\n * trimmed and lower-cased before lookup, because a seed written by hand is the\n * kind of source that carries stray whitespace and the odd capital.\n *\n * Nothing else in `/icons` imports this module, so an app that does not store\n * Material Symbols never pays for the table.\n *\n * @example\n * <Icon name={fromMaterialSymbol(category.icon_code)} size={20} />\n *\n * @example\n * <Icon name={fromMaterialSymbol(category.icon_code, \"folder\")} size={20} />\n *\n * @param code - A Material Symbols name, or `null`/`undefined` for a row that has none.\n * @param fallback - Slug to use when `code` is empty or unknown. Defaults to {@link MATERIAL_SYMBOL_FALLBACK}.\n * @returns A lucide slug `<Icon>` can render.\n */\nexport function fromMaterialSymbol(\n code: string | null | undefined,\n fallback: IconName = MATERIAL_SYMBOL_FALLBACK,\n): IconName {\n if (!code) return fallback;\n return materialToLucide[code.trim().toLowerCase()] ?? fallback;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"material-symbols.js","names":[],"sources":["../../src/icons/material-symbols.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — the file is one hand-written lookup table plus the\n * two functions that read it. Splitting a table by row count would put pairs that\n * belong together (`format_*`, `electrical_*`) in different files and make the\n * next hand-written pair a question of which file it lands in; the length is the\n * vocabulary's, not the code's.\n */\nimport type { IconName } from \"./generated/icon-name\";\n\n/**\n * The slug `fromMaterialSymbol` falls back to when a code is unknown.\n *\n * A neutral glyph rather than nothing: a category created in an admin with a\n * code this table has not learned yet still has to draw something, or it opens a\n * hole in the grid and the bug reads as a layout problem instead of a missing\n * mapping.\n *\n * Lucide's `circle-help` is a deprecated alias of this slug. `<Icon>` resolves\n * aliases, so both render — but the bridge emits the canonical name, since a\n * value that gets persisted should not be one lucide has already renamed.\n */\nexport const MATERIAL_SYMBOL_FALLBACK: IconName = \"circle-question-mark\";\n\n/**\n * Material Symbols code → lucide slug, for backends that store `icon_code`.\n *\n * Every Python backend we write stores a category icon as a Material Symbol\n * (`build`, `format_paint`, `electrical_services`) — the vocabulary Flutter,\n * Android and the administrative seeds already speak. The SDK speaks lucide in\n * kebab-case. The two lists do not meet, and the failure is nastier than an\n * empty screen: a handful of codes collide by accident, so roughly one row in\n * ten draws the right icon and the bug reads as \"some icons went missing\".\n *\n * **Still not the full vocabulary.** Material Symbols publishes ~6100 names and\n * almost none of them will ever appear in an `icon_code` of ours, so this grows\n * on demand, one hand-written pair at a time — a map generated by name\n * heuristics gets it badly wrong, starting with `build`, which is a wrench in\n * Material Symbols and nothing to do with construction.\n *\n * **How the codes were chosen.** Two lots, merged. The first was the trades seed\n * the bridge started as. The second is the head of Material Symbols' own\n * published popularity ranking (`fonts.google.com/metadata/icons`), which is\n * measured usage rather than a guess at which names a seed will contain — every\n * key was checked to exist in that vocabulary before landing.\n *\n * **Where the two lots disagreed**, six keys, the tie went to whichever mapping\n * keeps two distinct Material Symbols names distinct in lucide: `error` →\n * `circle-alert` because `cancel` already owns `circle-x`; `payments` →\n * `banknote` because `account_balance_wallet` already owns `wallet`;\n * `receipt_long` → `receipt-text` because `receipt` already owns `receipt`;\n * `construction` → itself because `engineering` already owns `hard-hat`; and\n * `today` → `calendar` with `event` → `calendar-days`, splitting the generic\n * calendar names from the dated one.\n *\n * Every target is checked mechanically against the slug list by\n * `material-symbols.test.ts`, so a pair pointing at a name lucide does not ship\n * — or at one it has since deprecated — fails the suite instead of reaching a\n * grid. What no test can check is whether the *chosen* icon is the right\n * metaphor, which is why pairs go in by hand.\n *\n * **Approximations, deliberate and not one-to-one** (the full list lives in\n * `docs/icons.md`):\n *\n * - `plumbing` → `shower-head`, because lucide has no pipe.\n * - `build`, `handyman` and `hardware` all land on `wrench` — Material Symbols\n * distinguishes the tools, lucide does not.\n * - `pedal_bike`, `two_wheeler` and `delivery_dining` all land on `bike`.\n * - `dentistry` → `face-slightly-smiling`, since lucide ships no tooth.\n * - `iron` → `shirt` and `cleaning_services` → `spray-can`: the activity, not\n * the implement.\n * - `roofing` → `house`, the same glyph as `home`, because lucide has no roof.\n *\n * The identity pairs are here on purpose. Their names happen to match in both\n * vocabularies, so they already render today — leaving them out would send them\n * to {@link MATERIAL_SYMBOL_FALLBACK} and make this bridge a regression for\n * exactly the codes that used to work.\n *\n * @see fromMaterialSymbol — the lookup you normally call.\n */\nexport const materialToLucide: Readonly<Record<string, IconName>> = {\n ac_unit: \"snowflake\",\n account_balance: \"landmark\",\n account_balance_wallet: \"wallet\",\n account_box: \"square-user\",\n account_circle: \"circle-user\",\n add: \"plus\",\n add_circle: \"circle-plus\",\n add_circle_outline: \"circle-plus\",\n add_shopping_cart: \"shopping-cart\",\n admin_panel_settings: \"shield-user\",\n analytics: \"chart-column\",\n apps: \"layout-grid\",\n arrow_back: \"arrow-left\",\n arrow_back_ios: \"chevron-left\",\n arrow_back_ios_new: \"chevron-left\",\n arrow_downward: \"arrow-down\",\n arrow_drop_down: \"chevron-down\",\n arrow_forward: \"arrow-right\",\n arrow_forward_ios: \"chevron-right\",\n arrow_right: \"chevron-right\",\n arrow_right_alt: \"arrow-right\",\n arrow_upward: \"arrow-up\",\n article: \"newspaper\",\n assignment: \"clipboard-list\",\n attach_file: \"paperclip\",\n attach_money: \"dollar-sign\",\n autorenew: \"refresh-cw\",\n badge: \"id-card\",\n balance: \"scale\",\n bar_chart: \"chart-column\",\n battery_full: \"battery-full\",\n bluetooth: \"bluetooth\",\n bolt: \"zap\",\n bookmark: \"bookmark\",\n brush: \"brush\",\n build: \"wrench\",\n business: \"building-2\",\n cake: \"cake\",\n calculate: \"calculator\",\n calendar_month: \"calendar\",\n calendar_today: \"calendar\",\n call: \"phone\",\n campaign: \"megaphone\",\n cancel: \"circle-x\",\n car_repair: \"car\",\n carpenter: \"hammer\",\n category: \"shapes\",\n chair: \"armchair\",\n chat: \"message-circle\",\n check: \"check\",\n check_box: \"square-check\",\n check_box_outline_blank: \"square\",\n check_circle: \"circle-check\",\n check_circle_outline: \"circle-check\",\n chevron_left: \"chevron-left\",\n chevron_right: \"chevron-right\",\n child_care: \"baby\",\n circle: \"circle\",\n cleaning_services: \"spray-can\",\n clear: \"x\",\n close: \"x\",\n cloud: \"cloud\",\n code: \"code\",\n computer: \"monitor\",\n construction: \"construction\",\n content_copy: \"copy\",\n content_cut: \"scissors\",\n credit_card: \"credit-card\",\n dark_mode: \"moon\",\n dashboard: \"layout-dashboard\",\n date_range: \"calendar-range\",\n delete: \"trash-2\",\n delete_forever: \"trash-2\",\n delete_outline: \"trash-2\",\n delivery_dining: \"bike\",\n dentistry: \"face-slightly-smiling\",\n description: \"file-text\",\n directions_car: \"car\",\n done: \"check\",\n done_all: \"check-check\",\n door_front: \"door-open\",\n download: \"download\",\n east: \"arrow-right\",\n edit: \"pencil\",\n edit_note: \"pencil-line\",\n electrical_services: \"plug-zap\",\n email: \"mail\",\n emoji_events: \"trophy\",\n engineering: \"hard-hat\",\n error: \"circle-alert\",\n error_outline: \"circle-alert\",\n event: \"calendar-days\",\n expand_less: \"chevron-up\",\n expand_more: \"chevron-down\",\n explore: \"compass\",\n face: \"face-slightly-smiling\",\n fact_check: \"clipboard-check\",\n favorite: \"heart\",\n favorite_border: \"heart\",\n file_download: \"download\",\n file_upload: \"upload\",\n filter_alt: \"funnel\",\n filter_list: \"funnel\",\n fingerprint: \"fingerprint-pattern\",\n fitness_center: \"dumbbell\",\n flight: \"plane\",\n folder: \"folder\",\n format_list_bulleted: \"list\",\n format_paint: \"paint-roller\",\n forum: \"messages-square\",\n gavel: \"gavel\",\n grade: \"star\",\n grass: \"sprout\",\n grid_view: \"layout-grid\",\n group: \"users\",\n groups: \"users\",\n handyman: \"wrench\",\n hardware: \"wrench\",\n help: \"circle-question-mark\",\n help_outline: \"circle-question-mark\",\n highlight_off: \"circle-x\",\n history: \"rotate-ccw-clock\",\n home: \"house\",\n hotel: \"bed-double\",\n image: \"image\",\n info: \"info\",\n insights: \"chart-line\",\n inventory: \"package\",\n inventory_2: \"package\",\n iron: \"shirt\",\n key: \"key\",\n kitchen: \"refrigerator\",\n language: \"globe\",\n laptop: \"laptop\",\n light_mode: \"sun\",\n lightbulb: \"lightbulb\",\n link: \"link\",\n list: \"list\",\n list_alt: \"list\",\n local_bar: \"wine\",\n local_cafe: \"coffee\",\n local_florist: \"flower\",\n local_gas_station: \"fuel\",\n local_laundry_service: \"washing-machine\",\n local_offer: \"tag\",\n local_shipping: \"truck\",\n local_taxi: \"car-taxi-front\",\n location_on: \"map-pin\",\n lock: \"lock\",\n lock_open: \"lock-open\",\n login: \"log-in\",\n logout: \"log-out\",\n mail: \"mail\",\n mail_outline: \"mail\",\n manage_accounts: \"user-cog\",\n map: \"map\",\n medical_services: \"stethoscope\",\n menu: \"menu\",\n menu_book: \"book-open\",\n mic: \"mic\",\n mode_edit: \"pencil\",\n monetization_on: \"circle-dollar-sign\",\n more_horiz: \"ellipsis\",\n more_vert: \"ellipsis-vertical\",\n music_note: \"music\",\n navigate_next: \"chevron-right\",\n note_add: \"file-plus\",\n notifications: \"bell\",\n notifications_active: \"bell-ring\",\n open_in_new: \"external-link\",\n paid: \"circle-dollar-sign\",\n palette: \"palette\",\n pause: \"pause\",\n payments: \"banknote\",\n pedal_bike: \"bike\",\n people: \"users\",\n perm_identity: \"user\",\n person: \"user\",\n person_add: \"user-plus\",\n person_outline: \"user\",\n pest_control: \"bug\",\n pets: \"paw-print\",\n phone: \"phone\",\n phone_iphone: \"smartphone\",\n photo_camera: \"camera\",\n picture_as_pdf: \"file-text\",\n pie_chart: \"chart-pie\",\n place: \"map-pin\",\n play_arrow: \"play\",\n play_circle: \"circle-play\",\n play_circle_filled: \"circle-play\",\n plumbing: \"shower-head\",\n power_settings_new: \"power\",\n print: \"printer\",\n public: \"globe\",\n qr_code_scanner: \"scan-line\",\n question_answer: \"messages-square\",\n radio_button_checked: \"circle-dot\",\n radio_button_unchecked: \"circle\",\n receipt: \"receipt\",\n receipt_long: \"receipt-text\",\n refresh: \"refresh-cw\",\n remove: \"minus\",\n remove_circle_outline: \"circle-minus\",\n reorder: \"list\",\n report_problem: \"triangle-alert\",\n restart_alt: \"rotate-ccw\",\n restaurant: \"utensils\",\n roofing: \"house\",\n room: \"map-pin\",\n router: \"router\",\n save: \"save\",\n savings: \"piggy-bank\",\n schedule: \"clock\",\n school: \"graduation-cap\",\n search: \"search\",\n security: \"shield-check\",\n send: \"send\",\n settings: \"settings\",\n share: \"share-2\",\n shield: \"shield\",\n shopping_bag: \"shopping-bag\",\n shopping_cart: \"shopping-cart\",\n smartphone: \"smartphone\",\n soap: \"soap-dispenser-droplet\",\n sort: \"arrow-down-up\",\n spa: \"flower-2\",\n star: \"star\",\n star_border: \"star\",\n star_rate: \"star\",\n store: \"store\",\n storefront: \"store\",\n support_agent: \"headset\",\n sync: \"refresh-cw\",\n task_alt: \"circle-check\",\n timer: \"timer\",\n today: \"calendar\",\n toggle_on: \"toggle-right\",\n translate: \"languages\",\n trending_up: \"trending-up\",\n tune: \"sliders-horizontal\",\n tv: \"tv\",\n two_wheeler: \"bike\",\n upload: \"upload\",\n upload_file: \"upload\",\n vaccines: \"syringe\",\n verified: \"badge-check\",\n verified_user: \"shield-check\",\n videocam: \"video\",\n view_list: \"list\",\n visibility: \"eye\",\n visibility_off: \"eye-off\",\n vpn_key: \"key-round\",\n warehouse: \"warehouse\",\n warning: \"triangle-alert\",\n warning_amber: \"triangle-alert\",\n watch_later: \"clock\",\n water_drop: \"droplet\",\n wifi: \"wifi\",\n work: \"briefcase\",\n yard: \"trees\",\n};\n\n/**\n * Translate a Material Symbols code into a lucide slug, always returning one.\n *\n * Never returns `undefined`: an unknown code resolves to `fallback`, so a row\n * whose `icon_code` this table has not learned yet still renders. The input is\n * trimmed and lower-cased before lookup, because a seed written by hand is the\n * kind of source that carries stray whitespace and the odd capital.\n *\n * Nothing else in `/icons` imports this module, so an app that does not store\n * Material Symbols never pays for the table.\n *\n * @example\n * <Icon name={fromMaterialSymbol(category.icon_code)} size={20} />\n *\n * @example\n * <Icon name={fromMaterialSymbol(category.icon_code, \"folder\")} size={20} />\n *\n * @param code - A Material Symbols name, or `null`/`undefined` for a row that has none.\n * @param fallback - Slug to use when `code` is empty or unknown. Defaults to {@link MATERIAL_SYMBOL_FALLBACK}.\n * @returns A lucide slug `<Icon>` can render.\n */\nexport function fromMaterialSymbol(\n code: string | null | undefined,\n fallback: IconName = MATERIAL_SYMBOL_FALLBACK,\n): IconName {\n if (!code) return fallback;\n return materialToLucide[code.trim().toLowerCase()] ?? fallback;\n}\n"],"mappings":"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"}
@@ -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:i,indexes:a,keyPath:o=`id`,ownerField:s}=e,c=new n(t,r,i,a),l=c.store;function u(e,t){return!s||!t?e:{...e,[s]:t}}async function d(e,t={}){let{orderBy:n=o,reverse:r=!1,limit:i,offset:a,filter:c}=t,u=s&&e?l.where(s).equals(e):l.toCollection();c&&(u=u.filter(c));let d=n===o?await u.toArray():await u.sortBy(n);return r&&(d=d.reverse()),a&&(d=d.slice(a)),typeof i==`number`&&(d=d.slice(0,i)),d}return{put:(e,t)=>l.put(u(e,t)),bulkPut:(e,t)=>l.bulkPut(e.map(e=>u(e,t))),get:e=>l.get(e),list:d,update:(e,t)=>l.update(e,t),updateMany:async(e,t)=>{let n=t;return s&&e?l.where(s).equals(e).modify(n):l.toCollection().modify(n)},delete:e=>l.delete(e),clear:async e=>{if(s&&e){await l.where(s).equals(e).delete();return}await l.clear()},count:e=>s&&e?l.where(s).equals(e).count():l.count(),raw:l,db:c}}exports.createOfflineStore=r;
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, keyPath = \"id\", ownerField } = config;\n\n const db = new GenericDb<TItem, TKey>(databaseName, version, tableName, indexes);\n const table = db.store;\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,UAAS,UAAU,KAAM,cAAe,EAE5E,EAAK,IAAI,EAAuB,EAAc,EAAS,EAAW,CAAO,EACzE,EAAQ,EAAG,MAEjB,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"}
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: r, tableName: i, indexes: a, keyPath: o = "id", ownerField: s } = e, c = new t(n, r, i, a), l = c.store;
11
- function u(e, t) {
12
- return !s || !t ? e : {
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
- [s]: t
18
+ [i]: t
15
19
  };
16
20
  }
17
- async function d(e, t = {}) {
18
- let { orderBy: n = o, reverse: r = !1, limit: i, offset: a, filter: c } = t, u = s && e ? l.where(s).equals(e) : l.toCollection();
19
- c && (u = u.filter(c));
20
- let d = n === o ? await u.toArray() : await u.sortBy(n);
21
- return r && (d = d.reverse()), a && (d = d.slice(a)), typeof i == "number" && (d = d.slice(0, i)), d;
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, t) => l.put(u(e, t)),
25
- bulkPut: (e, t) => l.bulkPut(e.map((e) => u(e, t))),
26
- get: (e) => l.get(e),
27
- list: d,
28
- update: (e, t) => l.update(e, t),
29
- updateMany: async (e, t) => {
30
- let n = t;
31
- return s && e ? l.where(s).equals(e).modify(n) : l.toCollection().modify(n);
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) => l.delete(e),
37
+ delete: (e) => t.delete(e),
34
38
  clear: async (e) => {
35
- if (s && e) {
36
- await l.where(s).equals(e).delete();
39
+ if (i && e) {
40
+ await t.where(i).equals(e).delete();
37
41
  return;
38
42
  }
39
- await l.clear();
43
+ await t.clear();
40
44
  },
41
- count: (e) => s && e ? l.where(s).equals(e).count() : l.count(),
42
- raw: l,
43
- db: c
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, keyPath = \"id\", ownerField } = config;\n\n const db = new GenericDb<TItem, TKey>(databaseName, version, tableName, indexes);\n const table = db.store;\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,YAAS,aAAU,MAAM,kBAAe,GAE5E,IAAK,IAAI,EAAuB,GAAc,GAAS,GAAW,CAAO,GACzE,IAAQ,EAAG;CAEjB,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
+ {"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"}