Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57229c69c1 | |||
| 8f3a90eeb2 | |||
| a360965d92 | |||
| eb1ace6d20 | |||
| 497c902283 | |||
| 4d4453b830 | |||
| ef53580ed1 | |||
| 52e5387d47 | |||
| f5b7f7e0e5 | |||
| 592bdbe4d9 | |||
| 6fc227bad6 | |||
| d74f6b0735 | |||
| 62ea796045 | |||
| 064704c9b7 | |||
| c846a1febf | |||
| c3959e8427 | |||
| e770f9b6b7 | |||
| 67f35615a2 | |||
| 5c7b6098a2 | |||
| f7d12b7486 | |||
| 3481313859 | |||
| 2f23556893 |
@@ -4,6 +4,9 @@ inputs:
|
|||||||
install:
|
install:
|
||||||
default: 'true'
|
default: 'true'
|
||||||
description: Whether or not to run 'pnpm install'
|
description: Whether or not to run 'pnpm install'
|
||||||
|
installArgs:
|
||||||
|
default: ''
|
||||||
|
description: Additional args to append to "pnpm install"
|
||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
@@ -17,4 +20,4 @@ runs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: ${{ inputs.install == 'true' }}
|
if: ${{ inputs.install == 'true' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: pnpm install
|
run: pnpm install ${{ inputs.installArgs }}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ on:
|
|||||||
- module-solid
|
- module-solid
|
||||||
- module-svelte
|
- module-svelte
|
||||||
- module-vue
|
- module-vue
|
||||||
|
- storage
|
||||||
- unocss
|
- unocss
|
||||||
- wxt
|
- wxt
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ on:
|
|||||||
default: wxt
|
default: wxt
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- wxt
|
|
||||||
- module-react
|
|
||||||
- module-vue
|
|
||||||
- module-svelte
|
|
||||||
- module-solid
|
|
||||||
- auto-icons
|
- auto-icons
|
||||||
- i18n
|
- i18n
|
||||||
|
- module-react
|
||||||
|
- module-solid
|
||||||
|
- module-svelte
|
||||||
|
- module-vue
|
||||||
|
- storage
|
||||||
|
- wxt
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
@@ -21,6 +22,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup
|
- uses: ./.github/actions/setup
|
||||||
|
with:
|
||||||
|
installArgs: --ignore-scripts
|
||||||
- run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
- run: pnpm tsx scripts/sync-releases.ts ${{ inputs.package }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ Here are some helpful commands:
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Build WXT package
|
# Build WXT package
|
||||||
|
cd packages/wxt
|
||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ const chromeExtensionIds = [
|
|||||||
'nmldnjcblcihmegipecakhmnieiofmgl', // Keyword Rank Checker
|
'nmldnjcblcihmegipecakhmnieiofmgl', // Keyword Rank Checker
|
||||||
'gppllamhaciichleihemgilcpledblpn', // YouTube Simple View - Hide distractions & more
|
'gppllamhaciichleihemgilcpledblpn', // YouTube Simple View - Hide distractions & more
|
||||||
'pccbghdfdnnkkbcdcibchpbffdgednkf', // Propbar - Property Data Enhancer
|
'pccbghdfdnnkkbcdcibchpbffdgednkf', // Propbar - Property Data Enhancer
|
||||||
|
'lfknakglefggmdkjdfhhofkjnnolffkh', // Text Search Pro - Search by case and whole-word match!
|
||||||
|
'mbenhbocjckkbaojacmaepiameldglij', // Invoice Generator
|
||||||
|
'phlfhkmdofajnbhgmbmjkbkdgppgoppb', // Monthly Bill Tracker
|
||||||
];
|
];
|
||||||
|
|
||||||
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
const { data, err, isLoading } = useListExtensionDetails(chromeExtensionIds);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { version as wxtVersion } from '../../packages/wxt/package.json';
|
|||||||
import { version as i18nVersion } from '../../packages/i18n/package.json';
|
import { version as i18nVersion } from '../../packages/i18n/package.json';
|
||||||
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
|
import { version as autoIconsVersion } from '../../packages/auto-icons/package.json';
|
||||||
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
import { version as unocssVersion } from '../../packages/unocss/package.json';
|
||||||
|
import { version as storageVersion } from '../../packages/storage/package.json';
|
||||||
|
|
||||||
const title = 'Next-gen Web Extension Framework';
|
const title = 'Next-gen Web Extension Framework';
|
||||||
const titleSuffix = ' – WXT';
|
const titleSuffix = ' – WXT';
|
||||||
@@ -87,7 +88,7 @@ export default defineConfig({
|
|||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
navItem('Other Packages', [
|
navItem('Other Packages', [
|
||||||
navItem(`wxt/storage — ${wxtVersion}`, '/storage'),
|
navItem(`@wxt-dev/storage — ${storageVersion}`, '/storage'),
|
||||||
navItem(`@wxt-dev/auto-icons — ${autoIconsVersion}`, '/auto-icons'),
|
navItem(`@wxt-dev/auto-icons — ${autoIconsVersion}`, '/auto-icons'),
|
||||||
navItem(`@wxt-dev/i18n — ${i18nVersion}`, '/i18n'),
|
navItem(`@wxt-dev/i18n — ${i18nVersion}`, '/i18n'),
|
||||||
navItem(`@wxt-dev/unocss — ${unocssVersion}`, '/unocss'),
|
navItem(`@wxt-dev/unocss — ${unocssVersion}`, '/unocss'),
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ import { defineWxtModule } from 'wxt/modules';
|
|||||||
|
|
||||||
export default defineWxtModule({
|
export default defineWxtModule({
|
||||||
setup(wxt) {
|
setup(wxt) {
|
||||||
wxt.hook('ready', () => {
|
wxt.hook('config:resolved', () => {
|
||||||
wxt.config.outDir = 'dist';
|
wxt.config.outDir = 'dist';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
+39
-15
@@ -6,23 +6,47 @@ outline: deep
|
|||||||
|
|
||||||
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md)
|
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/wxt/CHANGELOG.md)
|
||||||
|
|
||||||
WXT provides a simplified API to replace the `browser.storage.*` APIs. Use the `storage` auto-import from `wxt/storage` or import it manually to get started:
|
A simplified wrapper around the extension storage APIs.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### With WXT
|
||||||
|
|
||||||
|
This module is built-in to WXT, so you don't need to install anything.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { storage } from 'wxt/storage';
|
import { storage } from 'wxt/storage';
|
||||||
```
|
```
|
||||||
|
|
||||||
> [!IMPORTANT]
|
If you use auto-imports, `storage` is auto-imported for you, so you don't even need to import it!
|
||||||
> To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest:
|
|
||||||
>
|
### Without WXT
|
||||||
> ```ts
|
|
||||||
> // wxt.config.ts
|
Install the NPM package:
|
||||||
> export default defineConfig({
|
|
||||||
> manifest: {
|
```sh
|
||||||
> permissions: ['storage'],
|
npm i @wxt-dev/storage
|
||||||
> },
|
pnpm add @wxt-dev/storage
|
||||||
> });
|
yarn add @wxt-dev/storage
|
||||||
> ```
|
bun add @wxt-dev/storage
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { storage } from '@wxt-dev/storage';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Storage Permission
|
||||||
|
|
||||||
|
To use the `wxt/storage` API, the `"storage"` permission must be added to the manifest:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// wxt.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
manifest: {
|
||||||
|
permissions: ['storage'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
@@ -51,7 +75,7 @@ await storage.watch<number>(
|
|||||||
await storage.getMeta<{ v: number }>('local:installDate');
|
await storage.getMeta<{ v: number }>('local:installDate');
|
||||||
```
|
```
|
||||||
|
|
||||||
For a full list of methods available, see the [API reference](/api/reference/wxt/storage/interfaces/WxtStorage).
|
For a full list of methods available, see the [API reference](/api/reference/@wxt-dev/storage/interfaces/WxtStorage).
|
||||||
|
|
||||||
## Watchers
|
## Watchers
|
||||||
|
|
||||||
@@ -134,7 +158,7 @@ const unwatch = showChangelogOnUpdate.watch((newValue) => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
For a full list of properties and methods available, see the [API reference](/api/reference/wxt/storage/interfaces/WxtStorageItem).
|
For a full list of properties and methods available, see the [API reference](/api/reference/@wxt-dev/storage/interfaces/WxtStorageItem).
|
||||||
|
|
||||||
### Versioning
|
### Versioning
|
||||||
|
|
||||||
@@ -330,4 +354,4 @@ await storage.setItems([
|
|||||||
]);
|
]);
|
||||||
```
|
```
|
||||||
|
|
||||||
Refer to the [API Reference](/api/reference/wxt/storage/interfaces/WxtStorage) for types and examples of how to use all the bulk APIs.
|
Refer to the [API Reference](/api/reference/@wxt-dev/storage/interfaces/WxtStorage) for types and examples of how to use all the bulk APIs.
|
||||||
|
|||||||
+6
-6
@@ -4,7 +4,7 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.20.3"
|
"node": ">=18.20.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.12.0",
|
"packageManager": "pnpm@9.13.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"check": "check && pnpm -r --sequential run check",
|
"check": "check && pnpm -r --sequential run check",
|
||||||
"test": "pnpm -r --sequential run test run",
|
"test": "pnpm -r --sequential run test run",
|
||||||
@@ -20,24 +20,24 @@
|
|||||||
"@aklinker1/buildc": "^1.1.4",
|
"@aklinker1/buildc": "^1.1.4",
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@vitest/coverage-v8": "^2.1.2",
|
"@vitest/coverage-v8": "^2.1.4",
|
||||||
"changelogen": "^0.5.7",
|
"changelogen": "^0.5.7",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"lint-staged": "^15.2.10",
|
"lint-staged": "^15.2.10",
|
||||||
"markdown-it-footnote": "^4.0.0",
|
"markdown-it-footnote": "^4.0.0",
|
||||||
"nano-spawn": "^0.1.0",
|
"nano-spawn": "^0.2.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"tsx": "4.15.7",
|
"tsx": "4.15.7",
|
||||||
"typedoc": "^0.25.4",
|
"typedoc": "^0.25.4",
|
||||||
"typedoc-plugin-markdown": "4.0.0-next.23",
|
"typedoc-plugin-markdown": "4.0.0-next.23",
|
||||||
"typedoc-vitepress-theme": "1.0.0-next.3",
|
"typedoc-vitepress-theme": "1.0.0-next.3",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"vitepress": "^1.3.4",
|
"vitepress": "^1.5.0",
|
||||||
"vitest-mock-extended": "^2.0.2",
|
"vitest-mock-extended": "^2.0.2",
|
||||||
"vue": "^3.5.11",
|
"vue": "^3.5.12",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
|
|||||||
@@ -46,9 +46,9 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"oxlint": "^0.9.9",
|
"oxlint": "^0.11.1",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"confbox": "^0.1.7",
|
"confbox": "^0.1.8",
|
||||||
"fast-glob": "^3.3.2"
|
"fast-glob": "^3.3.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -39,13 +39,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"@types/chrome": "^0.0.268",
|
"@types/chrome": "^0.0.280",
|
||||||
"@types/node": "^20.16.10",
|
"@types/node": "^20.17.6",
|
||||||
"oxlint": "^0.9.9",
|
"oxlint": "^0.11.1",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"vitest": "^2.0.0",
|
"vitest": "^2.1.4",
|
||||||
"vitest-plugin-random-seed": "^1.1.0",
|
"vitest-plugin-random-seed": "^1.1.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.1.2
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.1...module-react-v1.1.2)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Use `config:resolved` hook to update config instead of `ready` ([#1178](https://github.com/wxt-dev/wxt/pull/1178))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
|
||||||
|
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
|
||||||
|
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||||
|
|
||||||
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||||
|
|
||||||
## v1.1.1
|
## v1.1.1
|
||||||
|
|
||||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.0...module-react-v1.1.1)
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-react-v1.1.0...module-react-v1.1.1)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default defineWxtModule<ReactModuleOptions>({
|
|||||||
addImportPreset(wxt, 'react');
|
addImportPreset(wxt, 'react');
|
||||||
|
|
||||||
// Enable auto-imports for JSX files
|
// Enable auto-imports for JSX files
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hook('config:resolved', (wxt) => {
|
||||||
if (wxt.config.imports === false) return;
|
if (wxt.config.imports === false) return;
|
||||||
|
|
||||||
wxt.config.imports.dirsScanOptions ??= {};
|
wxt.config.imports.dirsScanOptions ??= {};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"email": "aaronklinker1+wxt@gmail.com"
|
"email": "aaronklinker1+wxt@gmail.com"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
@@ -44,19 +44,19 @@
|
|||||||
"prepare": "buildc --deps-only -- wxt prepare"
|
"prepare": "buildc --deps-only -- wxt prepare"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"wxt": ">=0.18.6"
|
"wxt": ">=0.19.16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitejs/plugin-react": "^4.3.2"
|
"@vitejs/plugin-react": "^4.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"@types/react": "^18.3.11",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.1",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.1.3
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.1.2...module-solid-v1.1.3)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Use `config:resolved` hook to update config instead of `ready` ([#1178](https://github.com/wxt-dev/wxt/pull/1178))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
|
||||||
|
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
|
||||||
|
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||||
|
|
||||||
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||||
|
|
||||||
## v1.1.2
|
## v1.1.2
|
||||||
|
|
||||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.1.1...module-solid-v1.1.2)
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-solid-v1.1.1...module-solid-v1.1.2)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default defineWxtModule<SolidModuleOptions>({
|
|||||||
addImportPreset(wxt, 'solid-js');
|
addImportPreset(wxt, 'solid-js');
|
||||||
|
|
||||||
// Enable auto-imports for JSX files
|
// Enable auto-imports for JSX files
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hook('config:resolved', (wxt) => {
|
||||||
if (wxt.config.imports === false) return;
|
if (wxt.config.imports === false) return;
|
||||||
|
|
||||||
wxt.config.imports.dirsScanOptions ??= {};
|
wxt.config.imports.dirsScanOptions ??= {};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"email": "aaronklinker1+wxt@gmail.com"
|
"email": "aaronklinker1+wxt@gmail.com"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
@@ -44,16 +44,16 @@
|
|||||||
"prepare": "buildc --deps-only -- wxt prepare"
|
"prepare": "buildc --deps-only -- wxt prepare"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"wxt": ">=0.18.6"
|
"wxt": ">=0.19.16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vite-plugin-solid": "^2.10.2"
|
"vite-plugin-solid": "^2.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"solid-js": "^1.9.1",
|
"solid-js": "^1.9.3",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
|
|
||||||
#### ⚠️ Breaking Changes
|
#### ⚠️ Breaking Changes
|
||||||
|
|
||||||
- ⚠️ Svelte 5 support ([#1104](https://github.com/wxt-dev/wxt/pull/1104))
|
Upgraded `@sveltejs/vite-plugin-svelte` from v3 to v4. This drops support for Svelte 4 and below. To continue using older versions of Svelte, use v1 of this module.
|
||||||
|
|
||||||
|
To upgrade to svelte 5, just install `svelte@5`.
|
||||||
|
|
||||||
### ❤️ Contributors
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,8 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.2
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-vue-v1.0.1...module-vue-v1.0.2)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Use `config:resolved` hook to update config instead of `ready` ([#1178](https://github.com/wxt-dev/wxt/pull/1178))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- Add `oxlint` for linting ([#947](https://github.com/wxt-dev/wxt/pull/947))
|
||||||
|
- **deps:** Bump @vitejs/plugin-vue from 5.1.1 to 5.1.4 ([#1020](https://github.com/wxt-dev/wxt/pull/1020))
|
||||||
|
- Upgrade all non-major dependencies ([#1040](https://github.com/wxt-dev/wxt/pull/1040))
|
||||||
|
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||||
|
|
||||||
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||||
|
|
||||||
## v1.0.1
|
## v1.0.1
|
||||||
|
|
||||||
[compare changes](https://github.com/wxt-dev/wxt/compare/module-vue-v1.0.0...module-vue-v1.0.1)
|
[compare changes](https://github.com/wxt-dev/wxt/compare/module-vue-v1.0.0...module-vue-v1.0.1)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"email": "aaronklinker1+wxt@gmail.com"
|
"email": "aaronklinker1+wxt@gmail.com"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.mjs",
|
"module": "./dist/index.mjs",
|
||||||
@@ -42,15 +42,15 @@
|
|||||||
"check": "pnpm build && check"
|
"check": "pnpm build && check"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"wxt": ">=0.18.6"
|
"wxt": ">=0.19.16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.4"
|
"@vitejs/plugin-vue": "^5.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default defineWxtModule<VueModuleOptions>({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Enable auto-imports in template files
|
// Enable auto-imports in template files
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hook('config:resolved', (wxt) => {
|
||||||
if (!wxt.config.imports) return;
|
if (!wxt.config.imports) return;
|
||||||
|
|
||||||
wxt.config.imports.addons ??= {};
|
wxt.config.imports.addons ??= {};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.0
|
||||||
|
|
||||||
|
Extracted `wxt/storage` into it's own package, `@wxt-dev/storage`!
|
||||||
|
|
||||||
|
It's still shipped inside WXT and accessible via `wxt/storage`, but now:
|
||||||
|
|
||||||
|
- Non-WXT projects can use the storage wrapper.
|
||||||
|
- We can make breaking changes to the API separately.
|
||||||
|
|
||||||
|
[Read the docs](https://wxt.dev/storage.html) for more details.
|
||||||
|
|
||||||
|
> This is apart of the v1.0 initiative for WXT.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# WXT Storage
|
||||||
|
|
||||||
|
[Changelog](https://github.com/wxt-dev/wxt/blob/main/packages/storage/CHANGELOG.md) • [Docs](https://wxt.dev/storage.html)
|
||||||
|
|
||||||
|
A simplified wrapper around the extension storage APIs.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### With WXT
|
||||||
|
|
||||||
|
This module is built-in to WXT, so you don't need to install anything.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { storage } from 'wxt/storage';
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use auto-imports, `storage` is auto-imported for you, so you don't even need to import it!
|
||||||
|
|
||||||
|
### Without WXT
|
||||||
|
|
||||||
|
Install the NPM package:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i @wxt-dev/storage
|
||||||
|
pnpm add @wxt-dev/storage
|
||||||
|
yarn add @wxt-dev/storage
|
||||||
|
bun add @wxt-dev/storage
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { storage } from '@wxt-dev/storage';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Read full docs on the [documentation website](https://wxt.dev/storage.html).
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"name": "@wxt-dev/storage",
|
||||||
|
"description": "Web extension storage API provided by WXT, supports all browsers.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/wxt-dev/wxt.git",
|
||||||
|
"directory": "packages/storage"
|
||||||
|
},
|
||||||
|
"homepage": "https://wxt.dev/storage.html",
|
||||||
|
"keywords": [
|
||||||
|
"wxt",
|
||||||
|
"storage",
|
||||||
|
"extension",
|
||||||
|
"addon",
|
||||||
|
"chrome",
|
||||||
|
"firefox",
|
||||||
|
"edge"
|
||||||
|
],
|
||||||
|
"author": {
|
||||||
|
"name": "Aaron Klinker",
|
||||||
|
"email": "aaronklinker1+wxt@gmail.com"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"scripts": {
|
||||||
|
"build": "buildc -- unbuild",
|
||||||
|
"check": "buildc --deps-only -- check",
|
||||||
|
"test": "buildc --deps-only -- vitest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
|
"dequal": "^2.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@aklinker1/check": "^1.4.5",
|
||||||
|
"@types/chrome": "^0.0.268",
|
||||||
|
"@webext-core/fake-browser": "^1.3.1",
|
||||||
|
"oxlint": "^0.9.9",
|
||||||
|
"publint": "^0.2.11",
|
||||||
|
"typescript": "^5.6.2",
|
||||||
|
"unbuild": "^2.0.0",
|
||||||
|
"vitest": "^2.0.0"
|
||||||
|
},
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.mts",
|
||||||
|
"import": "./dist/index.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
+4
-5
@@ -1,7 +1,6 @@
|
|||||||
import { fakeBrowser } from '@webext-core/fake-browser';
|
import { fakeBrowser } from '@webext-core/fake-browser';
|
||||||
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
|
import { describe, it, expect, beforeEach, vi, expectTypeOf } from 'vitest';
|
||||||
import { browser } from 'wxt/browser';
|
import { MigrationError, type WxtStorageItem, storage } from '../index';
|
||||||
import { MigrationError, WxtStorageItem, storage } from '../storage';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This works because fakeBrowser is synchronous, and is will finish any number of chained
|
* This works because fakeBrowser is synchronous, and is will finish any number of chained
|
||||||
@@ -223,7 +222,7 @@ describe('Storage Utils', () => {
|
|||||||
describe('setMeta', () => {
|
describe('setMeta', () => {
|
||||||
it('should set metadata at key+$', async () => {
|
it('should set metadata at key+$', async () => {
|
||||||
const existing = { v: 1 };
|
const existing = { v: 1 };
|
||||||
await browser.storage[storageArea].set({ count$: existing });
|
await chrome.storage[storageArea].set({ count$: existing });
|
||||||
const newValues = {
|
const newValues = {
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
@@ -239,7 +238,7 @@ describe('Storage Utils', () => {
|
|||||||
'should remove any properties set to %s',
|
'should remove any properties set to %s',
|
||||||
async (version) => {
|
async (version) => {
|
||||||
const existing = { v: 1 };
|
const existing = { v: 1 };
|
||||||
await browser.storage[storageArea].set({ count$: existing });
|
await chrome.storage[storageArea].set({ count$: existing });
|
||||||
const expected = {};
|
const expected = {};
|
||||||
|
|
||||||
await storage.setMeta(`${storageArea}:count`, { v: version });
|
await storage.setMeta(`${storageArea}:count`, { v: version });
|
||||||
@@ -1163,7 +1162,7 @@ describe('Storage Utils', () => {
|
|||||||
|
|
||||||
await item.removeValue();
|
await item.removeValue();
|
||||||
// Make sure it's actually blank before running the test
|
// Make sure it's actually blank before running the test
|
||||||
expect(await browser.storage.local.get()).toEqual({});
|
expect(await chrome.storage.local.get()).toEqual({});
|
||||||
init.mockClear();
|
init.mockClear();
|
||||||
|
|
||||||
const [value1, value2] = await Promise.all([
|
const [value1, value2] = await Promise.all([
|
||||||
@@ -0,0 +1,871 @@
|
|||||||
|
/// <reference types="chrome" />
|
||||||
|
/**
|
||||||
|
* Simplified storage APIs with support for versioned fields, snapshots, metadata, and item definitions.
|
||||||
|
*
|
||||||
|
* See [the guide](https://wxt.dev/storage.html) for more information.
|
||||||
|
* @module @wxt-dev/storage
|
||||||
|
*/
|
||||||
|
import { dequal } from 'dequal/lite';
|
||||||
|
import { Mutex } from 'async-mutex';
|
||||||
|
|
||||||
|
export const storage = createStorage();
|
||||||
|
|
||||||
|
function createStorage(): WxtStorage {
|
||||||
|
const drivers: Record<StorageArea, WxtStorageDriver> = {
|
||||||
|
local: createDriver('local'),
|
||||||
|
session: createDriver('session'),
|
||||||
|
sync: createDriver('sync'),
|
||||||
|
managed: createDriver('managed'),
|
||||||
|
};
|
||||||
|
const getDriver = (area: StorageArea) => {
|
||||||
|
const driver = drivers[area];
|
||||||
|
if (driver == null) {
|
||||||
|
const areaNames = Object.keys(drivers).join(', ');
|
||||||
|
throw Error(`Invalid area "${area}". Options: ${areaNames}`);
|
||||||
|
}
|
||||||
|
return driver;
|
||||||
|
};
|
||||||
|
const resolveKey = (key: StorageItemKey) => {
|
||||||
|
const deliminatorIndex = key.indexOf(':');
|
||||||
|
const driverArea = key.substring(0, deliminatorIndex) as StorageArea;
|
||||||
|
const driverKey = key.substring(deliminatorIndex + 1);
|
||||||
|
if (driverKey == null)
|
||||||
|
throw Error(
|
||||||
|
`Storage key should be in the form of "area:key", but received "${key}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
driverArea,
|
||||||
|
driverKey,
|
||||||
|
driver: getDriver(driverArea),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const getMetaKey = (key: string) => key + '$';
|
||||||
|
const mergeMeta = (oldMeta: any, newMeta: any): any => {
|
||||||
|
const newFields = { ...oldMeta };
|
||||||
|
Object.entries(newMeta).forEach(([key, value]) => {
|
||||||
|
if (value == null) delete newFields[key];
|
||||||
|
else newFields[key] = value;
|
||||||
|
});
|
||||||
|
return newFields;
|
||||||
|
};
|
||||||
|
const getValueOrFallback = (value: any, fallback: any) =>
|
||||||
|
value ?? fallback ?? null;
|
||||||
|
const getMetaValue = (properties: any) =>
|
||||||
|
typeof properties === 'object' && !Array.isArray(properties)
|
||||||
|
? properties
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const getItem = async (
|
||||||
|
driver: WxtStorageDriver,
|
||||||
|
driverKey: string,
|
||||||
|
opts: GetItemOptions<any> | undefined,
|
||||||
|
) => {
|
||||||
|
const res = await driver.getItem<any>(driverKey);
|
||||||
|
return getValueOrFallback(res, opts?.fallback ?? opts?.defaultValue);
|
||||||
|
};
|
||||||
|
const getMeta = async (driver: WxtStorageDriver, driverKey: string) => {
|
||||||
|
const metaKey = getMetaKey(driverKey);
|
||||||
|
const res = await driver.getItem<any>(metaKey);
|
||||||
|
return getMetaValue(res);
|
||||||
|
};
|
||||||
|
const setItem = async (
|
||||||
|
driver: WxtStorageDriver,
|
||||||
|
driverKey: string,
|
||||||
|
value: any,
|
||||||
|
) => {
|
||||||
|
await driver.setItem(driverKey, value ?? null);
|
||||||
|
};
|
||||||
|
const setMeta = async (
|
||||||
|
driver: WxtStorageDriver,
|
||||||
|
driverKey: string,
|
||||||
|
properties: any | undefined,
|
||||||
|
) => {
|
||||||
|
const metaKey = getMetaKey(driverKey);
|
||||||
|
const existingFields = getMetaValue(await driver.getItem(metaKey));
|
||||||
|
await driver.setItem(metaKey, mergeMeta(existingFields, properties));
|
||||||
|
};
|
||||||
|
const removeItem = async (
|
||||||
|
driver: WxtStorageDriver,
|
||||||
|
driverKey: string,
|
||||||
|
opts: RemoveItemOptions | undefined,
|
||||||
|
) => {
|
||||||
|
await driver.removeItem(driverKey);
|
||||||
|
if (opts?.removeMeta) {
|
||||||
|
const metaKey = getMetaKey(driverKey);
|
||||||
|
await driver.removeItem(metaKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const removeMeta = async (
|
||||||
|
driver: WxtStorageDriver,
|
||||||
|
driverKey: string,
|
||||||
|
properties: string | string[] | undefined,
|
||||||
|
) => {
|
||||||
|
const metaKey = getMetaKey(driverKey);
|
||||||
|
if (properties == null) {
|
||||||
|
await driver.removeItem(metaKey);
|
||||||
|
} else {
|
||||||
|
const newFields = getMetaValue(await driver.getItem(metaKey));
|
||||||
|
[properties].flat().forEach((field) => delete newFields[field]);
|
||||||
|
await driver.setItem(metaKey, newFields);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const watch = (
|
||||||
|
driver: WxtStorageDriver,
|
||||||
|
driverKey: string,
|
||||||
|
cb: WatchCallback<any>,
|
||||||
|
) => {
|
||||||
|
return driver.watch(driverKey, cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
const storage: WxtStorage = {
|
||||||
|
getItem: async (key, opts) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
return await getItem(driver, driverKey, opts);
|
||||||
|
},
|
||||||
|
getItems: async (keys) => {
|
||||||
|
const areaToKeyMap = new Map<StorageArea, string[]>();
|
||||||
|
const keyToOptsMap = new Map<string, GetItemOptions<any> | undefined>();
|
||||||
|
const orderedKeys: StorageItemKey[] = [];
|
||||||
|
|
||||||
|
keys.forEach((key) => {
|
||||||
|
let keyStr: StorageItemKey;
|
||||||
|
let opts: GetItemOptions<any> | undefined;
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
// key: string
|
||||||
|
keyStr = key;
|
||||||
|
} else if ('getValue' in key) {
|
||||||
|
// key: WxtStorageItem
|
||||||
|
keyStr = key.key;
|
||||||
|
opts = { fallback: key.fallback };
|
||||||
|
} else {
|
||||||
|
// key: { key, options }
|
||||||
|
keyStr = key.key;
|
||||||
|
opts = key.options;
|
||||||
|
}
|
||||||
|
orderedKeys.push(keyStr);
|
||||||
|
const { driverArea, driverKey } = resolveKey(keyStr);
|
||||||
|
const areaKeys = areaToKeyMap.get(driverArea) ?? [];
|
||||||
|
areaToKeyMap.set(driverArea, areaKeys.concat(driverKey));
|
||||||
|
keyToOptsMap.set(keyStr, opts);
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultsMap = new Map<StorageItemKey, any>();
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(areaToKeyMap.entries()).map(async ([driverArea, keys]) => {
|
||||||
|
const driverResults = await drivers[driverArea].getItems(keys);
|
||||||
|
driverResults.forEach((driverResult) => {
|
||||||
|
const key = `${driverArea}:${driverResult.key}` as StorageItemKey;
|
||||||
|
const opts = keyToOptsMap.get(key);
|
||||||
|
const value = getValueOrFallback(
|
||||||
|
driverResult.value,
|
||||||
|
opts?.fallback ?? opts?.defaultValue,
|
||||||
|
);
|
||||||
|
resultsMap.set(key, value);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return orderedKeys.map((key) => ({
|
||||||
|
key,
|
||||||
|
value: resultsMap.get(key),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
getMeta: async (key) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
return await getMeta(driver, driverKey);
|
||||||
|
},
|
||||||
|
getMetas: async (args) => {
|
||||||
|
const keys = args.map((arg) => {
|
||||||
|
const key = typeof arg === 'string' ? arg : arg.key;
|
||||||
|
const { driverArea, driverKey } = resolveKey(key);
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
driverArea,
|
||||||
|
driverKey,
|
||||||
|
driverMetaKey: getMetaKey(driverKey),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const areaToDriverMetaKeysMap = keys.reduce<
|
||||||
|
Partial<Record<StorageArea, (typeof keys)[number][]>>
|
||||||
|
>((map, key) => {
|
||||||
|
map[key.driverArea] ??= [];
|
||||||
|
map[key.driverArea]!.push(key);
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const resultsMap: Record<string, any> = {};
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(areaToDriverMetaKeysMap).map(async ([area, keys]) => {
|
||||||
|
const areaRes = await chrome.storage[area as StorageArea].get(
|
||||||
|
keys.map((key) => key.driverMetaKey),
|
||||||
|
);
|
||||||
|
keys.forEach((key) => {
|
||||||
|
resultsMap[key.key] = areaRes[key.driverMetaKey] ?? {};
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return keys.map((key) => ({
|
||||||
|
key: key.key,
|
||||||
|
meta: resultsMap[key.key],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
setItem: async (key, value) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
await setItem(driver, driverKey, value);
|
||||||
|
},
|
||||||
|
setItems: async (items) => {
|
||||||
|
const areaToKeyValueMap: Partial<
|
||||||
|
Record<StorageArea, Array<{ key: string; value: any }>>
|
||||||
|
> = {};
|
||||||
|
items.forEach((item) => {
|
||||||
|
const { driverArea, driverKey } = resolveKey(
|
||||||
|
'key' in item ? item.key : item.item.key,
|
||||||
|
);
|
||||||
|
areaToKeyValueMap[driverArea] ??= [];
|
||||||
|
areaToKeyValueMap[driverArea].push({
|
||||||
|
key: driverKey,
|
||||||
|
value: item.value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(areaToKeyValueMap).map(async ([driverArea, values]) => {
|
||||||
|
const driver = getDriver(driverArea as StorageArea);
|
||||||
|
await driver.setItems(values);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
setMeta: async (key, properties) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
await setMeta(driver, driverKey, properties);
|
||||||
|
},
|
||||||
|
setMetas: async (items) => {
|
||||||
|
const areaToMetaUpdatesMap: Partial<
|
||||||
|
Record<StorageArea, { key: string; properties: any }[]>
|
||||||
|
> = {};
|
||||||
|
items.forEach((item) => {
|
||||||
|
const { driverArea, driverKey } = resolveKey(
|
||||||
|
'key' in item ? item.key : item.item.key,
|
||||||
|
);
|
||||||
|
areaToMetaUpdatesMap[driverArea] ??= [];
|
||||||
|
areaToMetaUpdatesMap[driverArea].push({
|
||||||
|
key: driverKey,
|
||||||
|
properties: item.meta,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(areaToMetaUpdatesMap).map(
|
||||||
|
async ([storageArea, updates]) => {
|
||||||
|
const driver = getDriver(storageArea as StorageArea);
|
||||||
|
const metaKeys = updates.map(({ key }) => getMetaKey(key));
|
||||||
|
console.log(storageArea, metaKeys);
|
||||||
|
const existingMetas = await driver.getItems(metaKeys);
|
||||||
|
const existingMetaMap = Object.fromEntries(
|
||||||
|
existingMetas.map(({ key, value }) => [key, getMetaValue(value)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const metaUpdates = updates.map(({ key, properties }) => {
|
||||||
|
const metaKey = getMetaKey(key);
|
||||||
|
return {
|
||||||
|
key: metaKey,
|
||||||
|
value: mergeMeta(existingMetaMap[metaKey] ?? {}, properties),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
await driver.setItems(metaUpdates);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
removeItem: async (key, opts) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
await removeItem(driver, driverKey, opts);
|
||||||
|
},
|
||||||
|
removeItems: async (keys) => {
|
||||||
|
const areaToKeysMap: Partial<Record<StorageArea, string[]>> = {};
|
||||||
|
|
||||||
|
keys.forEach((key) => {
|
||||||
|
let keyStr: StorageItemKey;
|
||||||
|
let opts: RemoveItemOptions | undefined;
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
// key: string
|
||||||
|
keyStr = key;
|
||||||
|
} else if ('getValue' in key) {
|
||||||
|
// key: WxtStorageItem
|
||||||
|
keyStr = key.key;
|
||||||
|
} else if ('item' in key) {
|
||||||
|
// key: { item, options }
|
||||||
|
keyStr = key.item.key;
|
||||||
|
opts = key.options;
|
||||||
|
} else {
|
||||||
|
// key: { key, options }
|
||||||
|
keyStr = key.key;
|
||||||
|
opts = key.options;
|
||||||
|
}
|
||||||
|
const { driverArea, driverKey } = resolveKey(keyStr);
|
||||||
|
areaToKeysMap[driverArea] ??= [];
|
||||||
|
areaToKeysMap[driverArea].push(driverKey);
|
||||||
|
if (opts?.removeMeta) {
|
||||||
|
areaToKeysMap[driverArea].push(getMetaKey(driverKey));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(areaToKeysMap).map(async ([driverArea, keys]) => {
|
||||||
|
const driver = getDriver(driverArea as StorageArea);
|
||||||
|
await driver.removeItems(keys);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
removeMeta: async (key, properties) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
await removeMeta(driver, driverKey, properties);
|
||||||
|
},
|
||||||
|
snapshot: async (base, opts) => {
|
||||||
|
const driver = getDriver(base);
|
||||||
|
const data = await driver.snapshot();
|
||||||
|
opts?.excludeKeys?.forEach((key) => {
|
||||||
|
delete data[key];
|
||||||
|
delete data[getMetaKey(key)];
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
restoreSnapshot: async (base, data) => {
|
||||||
|
const driver = getDriver(base);
|
||||||
|
await driver.restoreSnapshot(data);
|
||||||
|
},
|
||||||
|
watch: (key, cb) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
return watch(driver, driverKey, cb);
|
||||||
|
},
|
||||||
|
unwatch() {
|
||||||
|
Object.values(drivers).forEach((driver) => {
|
||||||
|
driver.unwatch();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
defineItem: (key, opts?: WxtStorageItemOptions<any>) => {
|
||||||
|
const { driver, driverKey } = resolveKey(key);
|
||||||
|
|
||||||
|
const { version: targetVersion = 1, migrations = {} } = opts ?? {};
|
||||||
|
if (targetVersion < 1) {
|
||||||
|
throw Error(
|
||||||
|
'Storage item version cannot be less than 1. Initial versions should be set to 1, not 0.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const migrate = async () => {
|
||||||
|
const driverMetaKey = getMetaKey(driverKey);
|
||||||
|
const [{ value }, { value: meta }] = await driver.getItems([
|
||||||
|
driverKey,
|
||||||
|
driverMetaKey,
|
||||||
|
]);
|
||||||
|
if (value == null) return;
|
||||||
|
|
||||||
|
const currentVersion = meta?.v ?? 1;
|
||||||
|
if (currentVersion > targetVersion) {
|
||||||
|
throw Error(
|
||||||
|
`Version downgrade detected (v${currentVersion} -> v${targetVersion}) for "${key}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug(
|
||||||
|
`[@wxt-dev/storage] Running storage migration for ${key}: v${currentVersion} -> v${targetVersion}`,
|
||||||
|
);
|
||||||
|
const migrationsToRun = Array.from(
|
||||||
|
{ length: targetVersion - currentVersion },
|
||||||
|
(_, i) => currentVersion + i + 1,
|
||||||
|
);
|
||||||
|
let migratedValue = value;
|
||||||
|
for (const migrateToVersion of migrationsToRun) {
|
||||||
|
try {
|
||||||
|
migratedValue =
|
||||||
|
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
|
||||||
|
migratedValue;
|
||||||
|
} catch (err) {
|
||||||
|
throw Error(`v${migrateToVersion} migration failed for "${key}"`, {
|
||||||
|
cause: err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await driver.setItems([
|
||||||
|
{ key: driverKey, value: migratedValue },
|
||||||
|
{ key: driverMetaKey, value: { ...meta, v: targetVersion } },
|
||||||
|
]);
|
||||||
|
console.debug(
|
||||||
|
`[@wxt-dev/storage] Storage migration completed for ${key} v${targetVersion}`,
|
||||||
|
{ migratedValue },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const migrationsDone =
|
||||||
|
opts?.migrations == null
|
||||||
|
? Promise.resolve()
|
||||||
|
: migrate().catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`[@wxt-dev/storage] Migration failed for ${key}`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const initMutex = new Mutex();
|
||||||
|
|
||||||
|
const getFallback = () => opts?.fallback ?? opts?.defaultValue ?? null;
|
||||||
|
|
||||||
|
const getOrInitValue = () =>
|
||||||
|
initMutex.runExclusive(async () => {
|
||||||
|
const value = await driver.getItem<any>(driverKey);
|
||||||
|
// Don't init value if it already exists or the init function isn't provided
|
||||||
|
if (value != null || opts?.init == null) return value;
|
||||||
|
|
||||||
|
const newValue = await opts.init();
|
||||||
|
await driver.setItem<any>(driverKey, newValue);
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize the value once migrations have finished
|
||||||
|
migrationsDone.then(getOrInitValue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
get defaultValue() {
|
||||||
|
return getFallback();
|
||||||
|
},
|
||||||
|
get fallback() {
|
||||||
|
return getFallback();
|
||||||
|
},
|
||||||
|
getValue: async () => {
|
||||||
|
await migrationsDone;
|
||||||
|
if (opts?.init) {
|
||||||
|
return await getOrInitValue();
|
||||||
|
} else {
|
||||||
|
return await getItem(driver, driverKey, opts);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getMeta: async () => {
|
||||||
|
await migrationsDone;
|
||||||
|
return await getMeta(driver, driverKey);
|
||||||
|
},
|
||||||
|
setValue: async (value) => {
|
||||||
|
await migrationsDone;
|
||||||
|
return await setItem(driver, driverKey, value);
|
||||||
|
},
|
||||||
|
setMeta: async (properties) => {
|
||||||
|
await migrationsDone;
|
||||||
|
return await setMeta(driver, driverKey, properties);
|
||||||
|
},
|
||||||
|
removeValue: async (opts) => {
|
||||||
|
await migrationsDone;
|
||||||
|
return await removeItem(driver, driverKey, opts);
|
||||||
|
},
|
||||||
|
removeMeta: async (properties) => {
|
||||||
|
await migrationsDone;
|
||||||
|
return await removeMeta(driver, driverKey, properties);
|
||||||
|
},
|
||||||
|
watch: (cb) =>
|
||||||
|
watch(driver, driverKey, (newValue, oldValue) =>
|
||||||
|
cb(newValue ?? getFallback(), oldValue ?? getFallback()),
|
||||||
|
),
|
||||||
|
migrate,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
||||||
|
const getStorageArea = () => {
|
||||||
|
if (chrome.runtime == null) {
|
||||||
|
throw Error(
|
||||||
|
[
|
||||||
|
"'wxt/storage' must be loaded in a web extension environment",
|
||||||
|
'\n - If thrown during a build, see https://github.com/wxt-dev/wxt/issues/371',
|
||||||
|
" - If thrown during tests, mock 'wxt/browser' correctly. See https://wxt.dev/guide/go-further/testing.html\n",
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (chrome.storage == null) {
|
||||||
|
throw Error(
|
||||||
|
"You must add the 'storage' permission to your manifest to use 'wxt/storage'",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const area = chrome.storage[storageArea];
|
||||||
|
if (area == null)
|
||||||
|
throw Error(`"chrome.storage.${storageArea}" is undefined`);
|
||||||
|
return area;
|
||||||
|
};
|
||||||
|
const watchListeners = new Set<(changes: StorageAreaChanges) => void>();
|
||||||
|
return {
|
||||||
|
getItem: async (key) => {
|
||||||
|
const res = await getStorageArea().get(key);
|
||||||
|
return res[key];
|
||||||
|
},
|
||||||
|
getItems: async (keys) => {
|
||||||
|
const result = await getStorageArea().get(keys);
|
||||||
|
return keys.map((key) => ({ key, value: result[key] ?? null }));
|
||||||
|
},
|
||||||
|
setItem: async (key, value) => {
|
||||||
|
if (value == null) {
|
||||||
|
await getStorageArea().remove(key);
|
||||||
|
} else {
|
||||||
|
await getStorageArea().set({ [key]: value });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setItems: async (values) => {
|
||||||
|
const map = values.reduce<Record<string, unknown>>(
|
||||||
|
(map, { key, value }) => {
|
||||||
|
map[key] = value;
|
||||||
|
return map;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
await getStorageArea().set(map);
|
||||||
|
},
|
||||||
|
removeItem: async (key) => {
|
||||||
|
await getStorageArea().remove(key);
|
||||||
|
},
|
||||||
|
removeItems: async (keys) => {
|
||||||
|
await getStorageArea().remove(keys);
|
||||||
|
},
|
||||||
|
snapshot: async () => {
|
||||||
|
return await getStorageArea().get();
|
||||||
|
},
|
||||||
|
restoreSnapshot: async (data) => {
|
||||||
|
await getStorageArea().set(data);
|
||||||
|
},
|
||||||
|
watch(key, cb) {
|
||||||
|
const listener = (changes: StorageAreaChanges) => {
|
||||||
|
const change = changes[key];
|
||||||
|
if (change == null) return;
|
||||||
|
if (dequal(change.newValue, change.oldValue)) return;
|
||||||
|
cb(change.newValue ?? null, change.oldValue ?? null);
|
||||||
|
};
|
||||||
|
getStorageArea().onChanged.addListener(listener);
|
||||||
|
watchListeners.add(listener);
|
||||||
|
return () => {
|
||||||
|
getStorageArea().onChanged.removeListener(listener);
|
||||||
|
watchListeners.delete(listener);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
unwatch() {
|
||||||
|
watchListeners.forEach((listener) => {
|
||||||
|
getStorageArea().onChanged.removeListener(listener);
|
||||||
|
});
|
||||||
|
watchListeners.clear();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WxtStorage {
|
||||||
|
/**
|
||||||
|
* Get an item from storage, or return `null` if it doesn't exist.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await storage.getItem<number>("local:installDate");
|
||||||
|
*/
|
||||||
|
getItem<TValue>(
|
||||||
|
key: StorageItemKey,
|
||||||
|
opts: GetItemOptions<TValue> & { fallback: TValue },
|
||||||
|
): Promise<TValue>;
|
||||||
|
|
||||||
|
getItem<TValue>(
|
||||||
|
key: StorageItemKey,
|
||||||
|
opts?: GetItemOptions<TValue>,
|
||||||
|
): Promise<TValue | null>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get multiple items from storage. The return order is guaranteed to be the same as the order
|
||||||
|
* requested.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await storage.getItems(["local:installDate", "session:someCounter"]);
|
||||||
|
*/
|
||||||
|
getItems(
|
||||||
|
keys: Array<
|
||||||
|
| StorageItemKey
|
||||||
|
| WxtStorageItem<any, any>
|
||||||
|
| { key: StorageItemKey; options?: GetItemOptions<any> }
|
||||||
|
>,
|
||||||
|
): Promise<Array<{ key: StorageItemKey; value: any }>>;
|
||||||
|
/**
|
||||||
|
* Return an object containing metadata about the key. Object is stored at `key + "$"`. If value
|
||||||
|
* is not an object, it returns an empty object.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await storage.getMeta("local:installDate");
|
||||||
|
*/
|
||||||
|
getMeta<T extends Record<string, unknown>>(key: StorageItemKey): Promise<T>;
|
||||||
|
/**
|
||||||
|
* Get the metadata of multiple storage items.
|
||||||
|
*
|
||||||
|
* @param items List of keys or items to get the metadata of.
|
||||||
|
* @returns An array containing storage keys and their metadata.
|
||||||
|
*/
|
||||||
|
getMetas(
|
||||||
|
keys: Array<StorageItemKey | WxtStorageItem<any, any>>,
|
||||||
|
): Promise<Array<{ key: StorageItemKey; meta: any }>>;
|
||||||
|
/**
|
||||||
|
* Set a value in storage. Setting a value to `null` or `undefined` is equivalent to calling
|
||||||
|
* `removeItem`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await storage.setItem<number>("local:installDate", Date.now());
|
||||||
|
*/
|
||||||
|
setItem<T>(key: StorageItemKey, value: T | null): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Set multiple values in storage. If a value is set to `null` or `undefined`, the key is removed.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await storage.setItem([
|
||||||
|
* { key: "local:installDate", value: Date.now() },
|
||||||
|
* { key: "session:someCounter, value: 5 },
|
||||||
|
* ]);
|
||||||
|
*/
|
||||||
|
setItems(
|
||||||
|
values: Array<
|
||||||
|
| { key: StorageItemKey; value: any }
|
||||||
|
| { item: WxtStorageItem<any, any>; value: any }
|
||||||
|
>,
|
||||||
|
): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Sets metadata properties. If some properties are already set, but are not included in the
|
||||||
|
* `properties` parameter, they will not be removed.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await storage.setMeta("local:installDate", { appVersion });
|
||||||
|
*/
|
||||||
|
setMeta<T extends Record<string, unknown>>(
|
||||||
|
key: StorageItemKey,
|
||||||
|
properties: T | null,
|
||||||
|
): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Set the metadata of multiple storage items.
|
||||||
|
*
|
||||||
|
* @param items List of storage keys or items and metadata to set for each.
|
||||||
|
*/
|
||||||
|
setMetas(
|
||||||
|
metas: Array<
|
||||||
|
| { key: StorageItemKey; meta: Record<string, any> }
|
||||||
|
| { item: WxtStorageItem<any, any>; meta: Record<string, any> }
|
||||||
|
>,
|
||||||
|
): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Removes an item from storage.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* await storage.removeItem("local:installDate");
|
||||||
|
*/
|
||||||
|
removeItem(key: StorageItemKey, opts?: RemoveItemOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Remove a list of keys from storage.
|
||||||
|
*/
|
||||||
|
removeItems(
|
||||||
|
keys: Array<
|
||||||
|
| StorageItemKey
|
||||||
|
| WxtStorageItem<any, any>
|
||||||
|
| { key: StorageItemKey; options?: RemoveItemOptions }
|
||||||
|
| { item: WxtStorageItem<any, any>; options?: RemoveItemOptions }
|
||||||
|
>,
|
||||||
|
): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Remove the entire metadata for a key, or specific properties by name.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Remove all metadata properties from the item
|
||||||
|
* await storage.removeMeta("local:installDate");
|
||||||
|
*
|
||||||
|
* // Remove only specific the "v" field
|
||||||
|
* await storage.removeMeta("local:installDate", "v")
|
||||||
|
*/
|
||||||
|
removeMeta(
|
||||||
|
key: StorageItemKey,
|
||||||
|
properties?: string | string[],
|
||||||
|
): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Return all the items in storage.
|
||||||
|
*/
|
||||||
|
snapshot(
|
||||||
|
base: StorageArea,
|
||||||
|
opts?: SnapshotOptions,
|
||||||
|
): Promise<Record<string, unknown>>;
|
||||||
|
/**
|
||||||
|
* Restores the results of `snapshot`. If new properties have been saved since the snapshot, they are
|
||||||
|
* not overridden. Only values existing in the snapshot are overridden.
|
||||||
|
*/
|
||||||
|
restoreSnapshot(base: StorageArea, data: any): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Watch for changes to a specific key in storage.
|
||||||
|
*/
|
||||||
|
watch<T>(key: StorageItemKey, cb: WatchCallback<T | null>): Unwatch;
|
||||||
|
/**
|
||||||
|
* Remove all watch listeners.
|
||||||
|
*/
|
||||||
|
unwatch(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a storage item with a default value, type, or versioning.
|
||||||
|
*
|
||||||
|
* Read full docs: https://wxt.dev/guide/extension-apis/storage.html#defining-storage-items
|
||||||
|
*/
|
||||||
|
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
||||||
|
key: StorageItemKey,
|
||||||
|
): WxtStorageItem<TValue | null, TMetadata>;
|
||||||
|
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
||||||
|
key: StorageItemKey,
|
||||||
|
options: WxtStorageItemOptions<TValue>,
|
||||||
|
): WxtStorageItem<TValue, TMetadata>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WxtStorageDriver {
|
||||||
|
getItem<T>(key: string): Promise<T | null>;
|
||||||
|
getItems(keys: string[]): Promise<{ key: string; value: any }[]>;
|
||||||
|
setItem<T>(key: string, value: T | null): Promise<void>;
|
||||||
|
setItems(values: Array<{ key: string; value: any }>): Promise<void>;
|
||||||
|
removeItem(key: string): Promise<void>;
|
||||||
|
removeItems(keys: string[]): Promise<void>;
|
||||||
|
snapshot(): Promise<Record<string, unknown>>;
|
||||||
|
restoreSnapshot(data: Record<string, unknown>): Promise<void>;
|
||||||
|
watch<T>(key: string, cb: WatchCallback<T | null>): Unwatch;
|
||||||
|
unwatch(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WxtStorageItem<
|
||||||
|
TValue,
|
||||||
|
TMetadata extends Record<string, unknown>,
|
||||||
|
> {
|
||||||
|
/**
|
||||||
|
* The storage key passed when creating the storage item.
|
||||||
|
*/
|
||||||
|
key: StorageItemKey;
|
||||||
|
/**
|
||||||
|
* @deprecated Renamed to fallback, use it instead.
|
||||||
|
*/
|
||||||
|
defaultValue: TValue;
|
||||||
|
/**
|
||||||
|
* The value provided by the `fallback` option.
|
||||||
|
*/
|
||||||
|
fallback: TValue;
|
||||||
|
/**
|
||||||
|
* Get the latest value from storage.
|
||||||
|
*/
|
||||||
|
getValue(): Promise<TValue>;
|
||||||
|
/**
|
||||||
|
* Get metadata.
|
||||||
|
*/
|
||||||
|
getMeta(): Promise<NullablePartial<TMetadata>>;
|
||||||
|
/**
|
||||||
|
* Set the value in storage.
|
||||||
|
*/
|
||||||
|
setValue(value: TValue): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Set metadata properties.
|
||||||
|
*/
|
||||||
|
setMeta(properties: NullablePartial<TMetadata>): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Remove the value from storage.
|
||||||
|
*/
|
||||||
|
removeValue(opts?: RemoveItemOptions): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Remove all metadata or certain properties from metadata.
|
||||||
|
*/
|
||||||
|
removeMeta(properties?: string[]): Promise<void>;
|
||||||
|
/**
|
||||||
|
* Listen for changes to the value in storage.
|
||||||
|
*/
|
||||||
|
watch(cb: WatchCallback<TValue>): Unwatch;
|
||||||
|
/**
|
||||||
|
* If there are migrations defined on the storage item, migrate to the latest version.
|
||||||
|
*
|
||||||
|
* **This function is ran automatically whenever the extension updates**, so you don't have to call it
|
||||||
|
* manually.
|
||||||
|
*/
|
||||||
|
migrate(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StorageArea = 'local' | 'session' | 'sync' | 'managed';
|
||||||
|
export type StorageItemKey = `${StorageArea}:${string}`;
|
||||||
|
|
||||||
|
export interface GetItemOptions<T> {
|
||||||
|
/**
|
||||||
|
* @deprecated Renamed to `fallback`, use it instead.
|
||||||
|
*/
|
||||||
|
defaultValue?: T;
|
||||||
|
/**
|
||||||
|
* Default value returned when `getItem` would otherwise return `null`.
|
||||||
|
*/
|
||||||
|
fallback?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveItemOptions {
|
||||||
|
/**
|
||||||
|
* Optionally remove metadata when deleting a key.
|
||||||
|
*
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
removeMeta?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SnapshotOptions {
|
||||||
|
/**
|
||||||
|
* Exclude a list of keys. The storage area prefix should be removed since the snapshot is for a
|
||||||
|
* specific storage area already.
|
||||||
|
*/
|
||||||
|
excludeKeys?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WxtStorageItemOptions<T> {
|
||||||
|
/**
|
||||||
|
* @deprecated Renamed to `fallback`, use it instead.
|
||||||
|
*/
|
||||||
|
defaultValue?: T;
|
||||||
|
/**
|
||||||
|
* Default value returned when `getValue` would otherwise return `null`.
|
||||||
|
*/
|
||||||
|
fallback?: T;
|
||||||
|
/**
|
||||||
|
* If passed, a value in storage will be initialized immediately after
|
||||||
|
* defining the storage item. This function returns the value that will be
|
||||||
|
* saved to storage during the initialization process if a value doesn't
|
||||||
|
* already exist.
|
||||||
|
*/
|
||||||
|
init?: () => T | Promise<T>;
|
||||||
|
/**
|
||||||
|
* Provide a version number for the storage item to enable migrations. When changing the version
|
||||||
|
* in the future, migration functions will be ran on application startup.
|
||||||
|
*/
|
||||||
|
version?: number;
|
||||||
|
/**
|
||||||
|
* A map of version numbers to the functions used to migrate the data to that version.
|
||||||
|
*/
|
||||||
|
migrations?: Record<number, (oldValue: any) => any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StorageAreaChanges = {
|
||||||
|
[key: string]: chrome.storage.StorageChange;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as `Partial`, but includes `| null`. It makes all the properties of an object optional and
|
||||||
|
* nullable.
|
||||||
|
*/
|
||||||
|
type NullablePartial<T> = {
|
||||||
|
[key in keyof T]+?: T[key] | undefined | null;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Callback called when a value in storage is changed.
|
||||||
|
*/
|
||||||
|
export type WatchCallback<T> = (newValue: T, oldValue: T) => void;
|
||||||
|
/**
|
||||||
|
* Call to remove a watch listener
|
||||||
|
*/
|
||||||
|
export type Unwatch = () => void;
|
||||||
|
|
||||||
|
export class MigrationError extends Error {
|
||||||
|
constructor(
|
||||||
|
public key: string,
|
||||||
|
public version: number,
|
||||||
|
options?: ErrorOptions,
|
||||||
|
) {
|
||||||
|
super(`v${version} migration failed for "${key}"`, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"types": ["chrome"]
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules/**", "dist/**"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineProject } from 'vitest/config';
|
||||||
|
|
||||||
|
export default defineProject({
|
||||||
|
test: {
|
||||||
|
mockReset: true,
|
||||||
|
restoreMocks: true,
|
||||||
|
setupFiles: ['vitest.setup.ts'],
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { fakeBrowser } from '@webext-core/fake-browser';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
vi.stubGlobal('chrome', fakeBrowser);
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"oxlint": "^0.9.9",
|
"oxlint": "^0.11.1",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"unocss": "^0.63.3",
|
"unocss": "^0.64.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default defineWxtModule<UnoCSSOptions>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
wxt.hooks.hook('vite:devServer:extendConfig', (config) => {
|
wxt.hooks.hook('vite:devServer:extendConfig', (config) => {
|
||||||
config.plugins?.push(UnoCSS());
|
config.plugins?.push(UnoCSS(resolvedOptions.configOrPath));
|
||||||
});
|
});
|
||||||
|
|
||||||
wxt.hooks.hook('vite:build:extendConfig', async (entries, config) => {
|
wxt.hooks.hook('vite:build:extendConfig', async (entries, config) => {
|
||||||
|
|||||||
@@ -22,15 +22,15 @@
|
|||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.280",
|
||||||
"@types/react": "^18.3.11",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@wxt-dev/auto-icons": "workspace:*",
|
"@wxt-dev/auto-icons": "workspace:*",
|
||||||
"@wxt-dev/unocss": "workspace:*",
|
"@wxt-dev/unocss": "workspace:*",
|
||||||
"sass": "^1.79.4",
|
"sass": "^1.80.7",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unocss": "^0.63.3",
|
"unocss": "^0.64.0",
|
||||||
"vitest": "^2.1.2",
|
"vitest": "^2.1.4",
|
||||||
"vitest-plugin-random-seed": "^1.1.0",
|
"vitest-plugin-random-seed": "^1.1.0",
|
||||||
"wxt": "workspace:*"
|
"wxt": "workspace:*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ export default defineBackground({
|
|||||||
|
|
||||||
console.log(useAppConfig());
|
console.log(useAppConfig());
|
||||||
|
|
||||||
// @ts-expect-error: should only accept entrypoints or public assets
|
|
||||||
browser.runtime.getURL('/');
|
browser.runtime.getURL('/');
|
||||||
browser.runtime.getURL('/background.js');
|
browser.runtime.getURL('/background.js');
|
||||||
browser.runtime.getURL('/icons/128.png');
|
browser.runtime.getURL('/icons/128.png');
|
||||||
browser.runtime.getURL('/example.html#hash');
|
browser.runtime.getURL('/example.html#hash');
|
||||||
browser.runtime.getURL('/example.html?query=param');
|
browser.runtime.getURL('/example.html?query=param');
|
||||||
|
// @ts-expect-error: should only accept entrypoints or public assets
|
||||||
|
browser.runtime.getURL('/unknown');
|
||||||
// @ts-expect-error: should only allow hashes/query params on HTML files
|
// @ts-expect-error: should only allow hashes/query params on HTML files
|
||||||
browser.runtime.getURL('/icon-128.png?query=param');
|
browser.runtime.getURL('/icon-128.png?query=param');
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,66 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.19.16
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.15...wxt-v0.19.16)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- **hooks:** Add new `config:resolved` hook ([#1177](https://github.com/wxt-dev/wxt/pull/1177))
|
||||||
|
|
||||||
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||||
|
|
||||||
|
## v0.19.15
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.14...wxt-v0.19.15)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- Extract `wxt/storage` to its own package, `@wxt-dev/storage` ([#1129](https://github.com/wxt-dev/wxt/pull/1129))
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Add "/" to `PublicPath` and `browser.runtime.getURL` ([#1171](https://github.com/wxt-dev/wxt/pull/1171))
|
||||||
|
- Add extension ID to event used to invalidate `ContentScriptContext` ([#1175](https://github.com/wxt-dev/wxt/pull/1175))
|
||||||
|
|
||||||
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||||
|
- Nishu ([@nishu-murmu](http://github.com/nishu-murmu))
|
||||||
|
|
||||||
|
## v0.19.14
|
||||||
|
|
||||||
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.13...wxt-v0.19.14)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- **storage:** Support storage items in batch functions ([#990](https://github.com/wxt-dev/wxt/pull/990))
|
||||||
|
- Automatically disable 'Show warning about Self-XSS when pasing code' in new chrome ([#1159](https://github.com/wxt-dev/wxt/pull/1159))
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Throw when config file does not exist ([#1156](https://github.com/wxt-dev/wxt/pull/1156))
|
||||||
|
|
||||||
|
### 📖 Documentation
|
||||||
|
|
||||||
|
- Cleanup typos and broken links ([bb5ea34](https://github.com/wxt-dev/wxt/commit/bb5ea34))
|
||||||
|
- Fix typo in `popup` and `options` EntrypointOptions ([#1121](https://github.com/wxt-dev/wxt/pull/1121))
|
||||||
|
|
||||||
|
### 🏡 Chore
|
||||||
|
|
||||||
|
- **deps:** Upgrade all non-major dependencies ([#1164](https://github.com/wxt-dev/wxt/pull/1164))
|
||||||
|
- **deps:** Bump dev and non-breaking major dependencies ([#1167](https://github.com/wxt-dev/wxt/pull/1167))
|
||||||
|
|
||||||
|
### ❤️ Contributors
|
||||||
|
|
||||||
|
- Aaron ([@aklinker1](http://github.com/aklinker1))
|
||||||
|
- Rxliuli ([@rxliuli](http://github.com/rxliuli))
|
||||||
|
- Kongmoumou ([@kongmoumou](http://github.com/kongmoumou))
|
||||||
|
- Florian Metz ([@Timeraa](http://github.com/Timeraa))
|
||||||
|
- Bread Grocery <breadgrocery@gmail.com>
|
||||||
|
|
||||||
## v0.19.13
|
## v0.19.13
|
||||||
|
|
||||||
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.12...wxt-v0.19.13)
|
[compare changes](https://github.com/wxt-dev/wxt/compare/wxt-v0.19.12...wxt-v0.19.13)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { WxtHooks } from '../../src/types';
|
|||||||
|
|
||||||
const hooks: WxtHooks = {
|
const hooks: WxtHooks = {
|
||||||
ready: vi.fn(),
|
ready: vi.fn(),
|
||||||
|
'config:resolved': vi.fn(),
|
||||||
'prepare:types': vi.fn(),
|
'prepare:types': vi.fn(),
|
||||||
'prepare:publicPaths': vi.fn(),
|
'prepare:publicPaths': vi.fn(),
|
||||||
'build:before': vi.fn(),
|
'build:before': vi.fn(),
|
||||||
@@ -49,6 +50,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'config:resolved': true,
|
||||||
'prepare:types': true,
|
'prepare:types': true,
|
||||||
'prepare:publicPaths': true,
|
'prepare:publicPaths': true,
|
||||||
'build:before': false,
|
'build:before': false,
|
||||||
@@ -76,6 +78,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'config:resolved': true,
|
||||||
'prepare:types': true,
|
'prepare:types': true,
|
||||||
'prepare:publicPaths': true,
|
'prepare:publicPaths': true,
|
||||||
'build:before': true,
|
'build:before': true,
|
||||||
@@ -103,6 +106,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'config:resolved': true,
|
||||||
'prepare:types': true,
|
'prepare:types': true,
|
||||||
'prepare:publicPaths': true,
|
'prepare:publicPaths': true,
|
||||||
'build:before': true,
|
'build:before': true,
|
||||||
@@ -130,6 +134,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'config:resolved': true,
|
||||||
'prepare:types': true,
|
'prepare:types': true,
|
||||||
'prepare:publicPaths': true,
|
'prepare:publicPaths': true,
|
||||||
'build:before': true,
|
'build:before': true,
|
||||||
@@ -163,6 +168,7 @@ describe('Hooks', () => {
|
|||||||
|
|
||||||
expectHooksToBeCalled({
|
expectHooksToBeCalled({
|
||||||
ready: true,
|
ready: true,
|
||||||
|
'config:resolved': true,
|
||||||
'prepare:types': true,
|
'prepare:types': true,
|
||||||
'prepare:publicPaths': true,
|
'prepare:publicPaths': true,
|
||||||
'build:before': true,
|
'build:before': true,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ describe('TypeScript Project', () => {
|
|||||||
|
|
||||||
declare module "wxt/browser" {
|
declare module "wxt/browser" {
|
||||||
export type PublicPath =
|
export type PublicPath =
|
||||||
|
| "/"
|
||||||
| "/options.html"
|
| "/options.html"
|
||||||
| "/popup.html"
|
| "/popup.html"
|
||||||
| "/sandbox.html"
|
| "/sandbox.html"
|
||||||
|
|||||||
+17
-17
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "wxt",
|
"name": "wxt",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "0.19.13",
|
"version": "0.19.16",
|
||||||
"description": "Next gen framework for developing web extensions",
|
"description": "Next gen framework for developing web extensions",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -82,19 +82,19 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
|
"@aklinker1/rollup-plugin-visualizer": "5.12.0",
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.280",
|
||||||
"@types/webextension-polyfill": "^0.10.7",
|
"@types/webextension-polyfill": "^0.12.1",
|
||||||
"@webext-core/fake-browser": "^1.3.1",
|
"@webext-core/fake-browser": "^1.3.1",
|
||||||
"@webext-core/isolated-element": "^1.1.2",
|
"@webext-core/isolated-element": "^1.1.2",
|
||||||
"@webext-core/match-patterns": "^1.0.3",
|
"@webext-core/match-patterns": "^1.0.3",
|
||||||
|
"@wxt-dev/storage": "workspace:^1.0.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"c12": "^1.11.2",
|
"c12": "^1.11.2",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"ci-info": "^4.0.0",
|
"ci-info": "^4.1.0",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
"dequal": "^2.0.3",
|
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"esbuild": "^0.21.5",
|
"esbuild": "^0.21.5",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
@@ -110,40 +110,40 @@
|
|||||||
"linkedom": "^0.18.5",
|
"linkedom": "^0.18.5",
|
||||||
"magicast": "^0.3.5",
|
"magicast": "^0.3.5",
|
||||||
"minimatch": "^10.0.1",
|
"minimatch": "^10.0.1",
|
||||||
"nano-spawn": "^0.1.0",
|
"nano-spawn": "^0.2.0",
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
"nypm": "^0.3.12",
|
"nypm": "^0.3.12",
|
||||||
"ohash": "^1.1.4",
|
"ohash": "^1.1.4",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"ora": "^8.1.0",
|
"ora": "^8.1.1",
|
||||||
"perfect-debounce": "^1.0.0",
|
"perfect-debounce": "^1.0.0",
|
||||||
"picocolors": "^1.1.0",
|
"picocolors": "^1.1.1",
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
"publish-browser-extension": "^2.2.2",
|
"publish-browser-extension": "^2.2.2",
|
||||||
"scule": "^1.3.0",
|
"scule": "^1.3.0",
|
||||||
"unimport": "^3.13.1",
|
"unimport": "^3.13.1",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.11",
|
||||||
"vite-node": "^2.1.2",
|
"vite-node": "^2.1.4",
|
||||||
"web-ext-run": "^0.2.1",
|
"web-ext-run": "^0.2.1",
|
||||||
"webextension-polyfill": "^0.12.0"
|
"webextension-polyfill": "^0.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aklinker1/check": "^1.4.5",
|
"@aklinker1/check": "^1.4.5",
|
||||||
"@faker-js/faker": "^8.4.1",
|
"@faker-js/faker": "^9.2.0",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/lodash.merge": "^4.6.9",
|
"@types/lodash.merge": "^4.6.9",
|
||||||
"@types/node": "^20.16.10",
|
"@types/node": "^20.17.6",
|
||||||
"@types/normalize-path": "^3.0.2",
|
"@types/normalize-path": "^3.0.2",
|
||||||
"@types/prompts": "^2.4.9",
|
"@types/prompts": "^2.4.9",
|
||||||
"extract-zip": "^2.0.1",
|
"extract-zip": "^2.0.1",
|
||||||
"happy-dom": "^14.12.3",
|
"happy-dom": "^15.11.4",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"oxlint": "^0.9.9",
|
"oxlint": "^0.11.1",
|
||||||
"publint": "^0.2.11",
|
"publint": "^0.2.12",
|
||||||
"tsx": "4.15.7",
|
"tsx": "4.15.7",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"unbuild": "^2.0.0",
|
"unbuild": "^2.0.0",
|
||||||
"vitest": "^2.1.2",
|
"vitest": "^2.1.4",
|
||||||
"vitest-plugin-random-seed": "^1.1.0"
|
"vitest-plugin-random-seed": "^1.1.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe('Module Utilities', () => {
|
|||||||
|
|
||||||
wxt.config.vite = () => Promise.resolve(userConfig);
|
wxt.config.vite = () => Promise.resolve(userConfig);
|
||||||
addViteConfig(wxt, () => moduleConfig);
|
addViteConfig(wxt, () => moduleConfig);
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('config:resolved', wxt);
|
||||||
const actual = await wxt.config.vite(wxt.config.env);
|
const actual = await wxt.config.vite(wxt.config.env);
|
||||||
|
|
||||||
expect(actual).toEqual(expected);
|
expect(actual).toEqual(expected);
|
||||||
@@ -31,7 +31,7 @@ describe('Module Utilities', () => {
|
|||||||
|
|
||||||
wxt.config.vite = () => userConfig;
|
wxt.config.vite = () => userConfig;
|
||||||
addViteConfig(wxt, () => moduleConfig);
|
addViteConfig(wxt, () => moduleConfig);
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('config:resolved', wxt);
|
||||||
const actual = await wxt.config.vite(wxt.config.env);
|
const actual = await wxt.config.vite(wxt.config.env);
|
||||||
|
|
||||||
expect(actual).toEqual(expected);
|
expect(actual).toEqual(expected);
|
||||||
@@ -44,7 +44,7 @@ describe('Module Utilities', () => {
|
|||||||
const wxt = fakeWxt({ hooks: createHooks() });
|
const wxt = fakeWxt({ hooks: createHooks() });
|
||||||
|
|
||||||
addImportPreset(wxt, preset);
|
addImportPreset(wxt, preset);
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('config:resolved', wxt);
|
||||||
|
|
||||||
expect(wxt.config.imports && wxt.config.imports.presets).toContain(
|
expect(wxt.config.imports && wxt.config.imports.presets).toContain(
|
||||||
preset,
|
preset,
|
||||||
@@ -63,7 +63,7 @@ describe('Module Utilities', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addImportPreset(wxt, preset);
|
addImportPreset(wxt, preset);
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('config:resolved', wxt);
|
||||||
|
|
||||||
expect(wxt.config.imports && wxt.config.imports.presets).toHaveLength(2);
|
expect(wxt.config.imports && wxt.config.imports.presets).toHaveLength(2);
|
||||||
});
|
});
|
||||||
@@ -78,7 +78,7 @@ describe('Module Utilities', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addImportPreset(wxt, preset);
|
addImportPreset(wxt, preset);
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('config:resolved', wxt);
|
||||||
|
|
||||||
expect(wxt.config.imports).toBe(false);
|
expect(wxt.config.imports).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ export type {
|
|||||||
Theme,
|
Theme,
|
||||||
TopSites,
|
TopSites,
|
||||||
Types,
|
Types,
|
||||||
Urlbar,
|
|
||||||
UserScripts,
|
UserScripts,
|
||||||
WebNavigation,
|
WebNavigation,
|
||||||
WebRequest,
|
WebRequest,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default defineWxtModule({
|
|||||||
let unimport: Unimport;
|
let unimport: Unimport;
|
||||||
|
|
||||||
// Add user module imports to config
|
// Add user module imports to config
|
||||||
wxt.hooks.hook('ready', () => {
|
wxt.hooks.hook('config:resolved', () => {
|
||||||
const addModuleImports = (module: WxtModule<any>) => {
|
const addModuleImports = (module: WxtModule<any>) => {
|
||||||
if (!module.imports) return;
|
if (!module.imports) return;
|
||||||
|
|
||||||
@@ -30,10 +30,10 @@ export default defineWxtModule({
|
|||||||
wxt.config.userModules.forEach(addModuleImports);
|
wxt.config.userModules.forEach(addModuleImports);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create unimport instance AFTER "ready" so any modifications to the
|
// Create unimport instance AFTER "config:resolved" so any modifications to the
|
||||||
// config inside "ready" are applied.
|
// config inside "config:resolved" are applied.
|
||||||
wxt.hooks.afterEach((event) => {
|
wxt.hooks.afterEach((event) => {
|
||||||
if (event.name === 'ready') {
|
if (event.name === 'config:resolved') {
|
||||||
unimport = createUnimport(options);
|
unimport = createUnimport(options);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ import { createLocationWatcher } from './location-watcher';
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class ContentScriptContext implements AbortController {
|
export class ContentScriptContext implements AbortController {
|
||||||
private static SCRIPT_STARTED_MESSAGE_TYPE = 'wxt:content-script-started';
|
private static SCRIPT_STARTED_MESSAGE_TYPE = getUniqueEventName(
|
||||||
|
'wxt:content-script-started',
|
||||||
|
);
|
||||||
|
|
||||||
private isTopFrame = window.self === window.top;
|
private isTopFrame = window.self === window.top;
|
||||||
private abortController: AbortController;
|
private abortController: AbortController;
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ async function getPathsDeclarationEntry(
|
|||||||
isHtmlEntrypoint(entry) ? '.html' : '.js',
|
isHtmlEntrypoint(entry) ? '.html' : '.js',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.concat([''])
|
||||||
.concat(await getPublicFiles());
|
.concat(await getPublicFiles());
|
||||||
|
|
||||||
await wxt.hooks.callHook('prepare:publicPaths', wxt, paths);
|
await wxt.hooks.callHook('prepare:publicPaths', wxt, paths);
|
||||||
@@ -176,7 +177,7 @@ declare module "wxt/browser" {
|
|||||||
message.message,
|
message.message,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Include a final union-based override so TS accepts valid string templates or concatinations
|
// Include a final union-based override so TS accepts valid string templates or concatenations
|
||||||
// ie: browser.i18n.getMessage(`some_enum_${enumValue}`)
|
// ie: browser.i18n.getMessage(`some_enum_${enumValue}`)
|
||||||
renderGetMessageOverload(
|
renderGetMessageOverload(
|
||||||
messages.map((message) => `"${message.name}"`).join(' | '),
|
messages.map((message) => `"${message.name}"`).join(' | '),
|
||||||
|
|||||||
@@ -50,7 +50,10 @@ export function createWebExtRunner(): ExtensionRunner {
|
|||||||
wxtUserConfig?.chromiumPref,
|
wxtUserConfig?.chromiumPref,
|
||||||
DEFAULT_CHROMIUM_PREFS,
|
DEFAULT_CHROMIUM_PREFS,
|
||||||
),
|
),
|
||||||
args: wxtUserConfig?.chromiumArgs,
|
args: [
|
||||||
|
'--unsafely-disable-devtools-self-xss-warnings',
|
||||||
|
...(wxtUserConfig?.chromiumArgs ?? []),
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ export function hashContentScriptOptions(
|
|||||||
match_about_blank: false,
|
match_about_blank: false,
|
||||||
run_at: 'document_idle',
|
run_at: 'document_idle',
|
||||||
all_frames: false,
|
all_frames: false,
|
||||||
// @ts-expect-error - not in type
|
|
||||||
match_origin_as_fallback: false,
|
match_origin_as_fallback: false,
|
||||||
world: 'ISOLATED',
|
world: 'ISOLATED',
|
||||||
...simplifiedOptions,
|
...simplifiedOptions,
|
||||||
@@ -62,7 +61,6 @@ export function mapWxtOptionsToContentScript(
|
|||||||
css,
|
css,
|
||||||
js,
|
js,
|
||||||
|
|
||||||
// @ts-expect-error: untyped chrome options
|
|
||||||
match_origin_as_fallback: options.matchOriginAsFallback,
|
match_origin_as_fallback: options.matchOriginAsFallback,
|
||||||
world: options.world,
|
world: options.world,
|
||||||
};
|
};
|
||||||
@@ -80,7 +78,6 @@ export function mapWxtOptionsToRegisteredContentScript(
|
|||||||
runAt: options.runAt,
|
runAt: options.runAt,
|
||||||
js,
|
js,
|
||||||
css,
|
css,
|
||||||
// @ts-expect-error: Chrome accepts this, not typed in webextension-polyfill (https://developer.chrome.com/docs/extensions/reference/scripting/#type-RegisteredContentScript)
|
|
||||||
world: options.world,
|
world: options.world,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export async function registerWxt(
|
|||||||
},
|
},
|
||||||
async reloadConfig() {
|
async reloadConfig() {
|
||||||
wxt.config = await resolveConfig(inlineConfig, command);
|
wxt.config = await resolveConfig(inlineConfig, command);
|
||||||
|
await wxt.hooks.callHook('config:resolved', wxt);
|
||||||
},
|
},
|
||||||
pm,
|
pm,
|
||||||
builder,
|
builder,
|
||||||
@@ -82,6 +83,7 @@ export async function registerWxt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await wxt.hooks.callHook('ready', wxt);
|
await wxt.hooks.callHook('ready', wxt);
|
||||||
|
await wxt.hooks.callHook('config:resolved', wxt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function defineWxtModule<TOptions extends WxtModuleOptions>(
|
|||||||
/**
|
/**
|
||||||
* Adds a TS/JS file as an entrypoint to the project. This file will be bundled
|
* Adds a TS/JS file as an entrypoint to the project. This file will be bundled
|
||||||
* along with the other entrypoints.
|
* along with the other entrypoints.
|
||||||
|
*
|
||||||
* If you're publishing the module to NPM, you should probably pre-build the
|
* If you're publishing the module to NPM, you should probably pre-build the
|
||||||
* entrypoint and use `addPublicAssets` instead to copy pre-bundled assets into
|
* entrypoint and use `addPublicAssets` instead to copy pre-bundled assets into
|
||||||
* the output directory. This will speed up project builds since it just has to
|
* the output directory. This will speed up project builds since it just has to
|
||||||
@@ -53,7 +53,7 @@ export function defineWxtModule<TOptions extends WxtModuleOptions>(
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function addEntrypoint(wxt: Wxt, entrypoint: Entrypoint): void {
|
export function addEntrypoint(wxt: Wxt, entrypoint: Entrypoint): void {
|
||||||
wxt.hooks.hook('entrypoints:resolved', (wxt, entrypoints) => {
|
wxt.hooks.hook('entrypoints:resolved', (_, entrypoints) => {
|
||||||
entrypoints.push(entrypoint);
|
entrypoints.push(entrypoint);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ export function addViteConfig(
|
|||||||
wxt: Wxt,
|
wxt: Wxt,
|
||||||
viteConfig: (env: vite.ConfigEnv) => vite.UserConfig | undefined,
|
viteConfig: (env: vite.ConfigEnv) => vite.UserConfig | undefined,
|
||||||
): void {
|
): void {
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hooks.hook('config:resolved', (wxt) => {
|
||||||
const userVite = wxt.config.vite;
|
const userVite = wxt.config.vite;
|
||||||
wxt.config.vite = async (env) => {
|
wxt.config.vite = async (env) => {
|
||||||
const fromUser = await userVite(env);
|
const fromUser = await userVite(env);
|
||||||
@@ -130,7 +130,7 @@ export function addViteConfig(
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function addWxtPlugin(wxt: Wxt, plugin: string): void {
|
export function addWxtPlugin(wxt: Wxt, plugin: string): void {
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hooks.hook('config:resolved', (wxt) => {
|
||||||
wxt.config.plugins.push(plugin);
|
wxt.config.plugins.push(plugin);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ export function addImportPreset(
|
|||||||
wxt: Wxt,
|
wxt: Wxt,
|
||||||
preset: UnimportOptions['presets'][0],
|
preset: UnimportOptions['presets'][0],
|
||||||
): void {
|
): void {
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hooks.hook('config:resolved', (wxt) => {
|
||||||
if (!wxt.config.imports) return;
|
if (!wxt.config.imports) return;
|
||||||
|
|
||||||
wxt.config.imports.presets ??= [];
|
wxt.config.imports.presets ??= [];
|
||||||
@@ -205,12 +205,13 @@ export function addImportPreset(
|
|||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export function addAlias(wxt: Wxt, alias: string, path: string) {
|
export function addAlias(wxt: Wxt, alias: string, path: string) {
|
||||||
wxt.hooks.hook('ready', (wxt) => {
|
wxt.hooks.hook('config:resolved', (wxt) => {
|
||||||
const target = resolve(wxt.config.root, path);
|
const target = resolve(wxt.config.root, path);
|
||||||
if (wxt.config.alias[alias] != null) {
|
if (wxt.config.alias[alias] != null && wxt.config.alias[alias] !== target) {
|
||||||
wxt.logger.warn(
|
wxt.logger.warn(
|
||||||
`Skipped adding alias (${alias} => ${target}) because an alias with the same name already exists: ${alias} => ${wxt.config.alias[alias]}`,
|
`Skipped adding alias (${alias} => ${target}) because an alias with the same name already exists: ${alias} => ${wxt.config.alias[alias]}`,
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
wxt.config.alias[alias] = target;
|
wxt.config.alias[alias] = target;
|
||||||
});
|
});
|
||||||
|
|||||||
+2
-867
@@ -1,869 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Simplified storage APIs with support for versioned fields, snapshots, metadata, and item definitions.
|
* @module @wxt-dev/storage
|
||||||
*
|
|
||||||
* See [the guide](https://wxt.dev/guide/extension-apis/storage.html) for more information.
|
|
||||||
*
|
|
||||||
* @module wxt/storage
|
|
||||||
*/
|
*/
|
||||||
import { Storage, browser } from 'wxt/browser';
|
export * from '@wxt-dev/storage';
|
||||||
import { dequal } from 'dequal/lite';
|
|
||||||
import { logger } from './sandbox/utils/logger';
|
|
||||||
import { toArray } from './core/utils/arrays';
|
|
||||||
import { Mutex } from 'async-mutex';
|
|
||||||
|
|
||||||
export const storage = createStorage();
|
|
||||||
|
|
||||||
function createStorage(): WxtStorage {
|
|
||||||
const drivers: Record<StorageArea, WxtStorageDriver> = {
|
|
||||||
local: createDriver('local'),
|
|
||||||
session: createDriver('session'),
|
|
||||||
sync: createDriver('sync'),
|
|
||||||
managed: createDriver('managed'),
|
|
||||||
};
|
|
||||||
const getDriver = (area: StorageArea) => {
|
|
||||||
const driver = drivers[area];
|
|
||||||
if (driver == null) {
|
|
||||||
const areaNames = Object.keys(drivers).join(', ');
|
|
||||||
throw Error(`Invalid area "${area}". Options: ${areaNames}`);
|
|
||||||
}
|
|
||||||
return driver;
|
|
||||||
};
|
|
||||||
const resolveKey = (key: StorageItemKey) => {
|
|
||||||
const deliminatorIndex = key.indexOf(':');
|
|
||||||
const driverArea = key.substring(0, deliminatorIndex) as StorageArea;
|
|
||||||
const driverKey = key.substring(deliminatorIndex + 1);
|
|
||||||
if (driverKey == null)
|
|
||||||
throw Error(
|
|
||||||
`Storage key should be in the form of "area:key", but received "${key}"`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
driverArea,
|
|
||||||
driverKey,
|
|
||||||
driver: getDriver(driverArea),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const getMetaKey = (key: string) => key + '$';
|
|
||||||
const mergeMeta = (oldMeta: any, newMeta: any): any => {
|
|
||||||
const newFields = { ...oldMeta };
|
|
||||||
Object.entries(newMeta).forEach(([key, value]) => {
|
|
||||||
if (value == null) delete newFields[key];
|
|
||||||
else newFields[key] = value;
|
|
||||||
});
|
|
||||||
return newFields;
|
|
||||||
};
|
|
||||||
const getValueOrFallback = (value: any, fallback: any) =>
|
|
||||||
value ?? fallback ?? null;
|
|
||||||
const getMetaValue = (properties: any) =>
|
|
||||||
typeof properties === 'object' && !Array.isArray(properties)
|
|
||||||
? properties
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const getItem = async (
|
|
||||||
driver: WxtStorageDriver,
|
|
||||||
driverKey: string,
|
|
||||||
opts: GetItemOptions<any> | undefined,
|
|
||||||
) => {
|
|
||||||
const res = await driver.getItem<any>(driverKey);
|
|
||||||
return getValueOrFallback(res, opts?.fallback ?? opts?.defaultValue);
|
|
||||||
};
|
|
||||||
const getMeta = async (driver: WxtStorageDriver, driverKey: string) => {
|
|
||||||
const metaKey = getMetaKey(driverKey);
|
|
||||||
const res = await driver.getItem<any>(metaKey);
|
|
||||||
return getMetaValue(res);
|
|
||||||
};
|
|
||||||
const setItem = async (
|
|
||||||
driver: WxtStorageDriver,
|
|
||||||
driverKey: string,
|
|
||||||
value: any,
|
|
||||||
) => {
|
|
||||||
await driver.setItem(driverKey, value ?? null);
|
|
||||||
};
|
|
||||||
const setMeta = async (
|
|
||||||
driver: WxtStorageDriver,
|
|
||||||
driverKey: string,
|
|
||||||
properties: any | undefined,
|
|
||||||
) => {
|
|
||||||
const metaKey = getMetaKey(driverKey);
|
|
||||||
const existingFields = getMetaValue(await driver.getItem(metaKey));
|
|
||||||
await driver.setItem(metaKey, mergeMeta(existingFields, properties));
|
|
||||||
};
|
|
||||||
const removeItem = async (
|
|
||||||
driver: WxtStorageDriver,
|
|
||||||
driverKey: string,
|
|
||||||
opts: RemoveItemOptions | undefined,
|
|
||||||
) => {
|
|
||||||
await driver.removeItem(driverKey);
|
|
||||||
if (opts?.removeMeta) {
|
|
||||||
const metaKey = getMetaKey(driverKey);
|
|
||||||
await driver.removeItem(metaKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const removeMeta = async (
|
|
||||||
driver: WxtStorageDriver,
|
|
||||||
driverKey: string,
|
|
||||||
properties: string | string[] | undefined,
|
|
||||||
) => {
|
|
||||||
const metaKey = getMetaKey(driverKey);
|
|
||||||
if (properties == null) {
|
|
||||||
await driver.removeItem(metaKey);
|
|
||||||
} else {
|
|
||||||
const newFields = getMetaValue(await driver.getItem(metaKey));
|
|
||||||
toArray(properties).forEach((field) => delete newFields[field]);
|
|
||||||
await driver.setItem(metaKey, newFields);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const watch = (
|
|
||||||
driver: WxtStorageDriver,
|
|
||||||
driverKey: string,
|
|
||||||
cb: WatchCallback<any>,
|
|
||||||
) => {
|
|
||||||
return driver.watch(driverKey, cb);
|
|
||||||
};
|
|
||||||
|
|
||||||
const storage: WxtStorage = {
|
|
||||||
getItem: async (key, opts) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
return await getItem(driver, driverKey, opts);
|
|
||||||
},
|
|
||||||
getItems: async (keys) => {
|
|
||||||
const areaToKeyMap = new Map<StorageArea, string[]>();
|
|
||||||
const keyToOptsMap = new Map<string, GetItemOptions<any> | undefined>();
|
|
||||||
const orderedKeys: StorageItemKey[] = [];
|
|
||||||
|
|
||||||
keys.forEach((key) => {
|
|
||||||
let keyStr: StorageItemKey;
|
|
||||||
let opts: GetItemOptions<any> | undefined;
|
|
||||||
if (typeof key === 'string') {
|
|
||||||
// key: string
|
|
||||||
keyStr = key;
|
|
||||||
} else if ('getValue' in key) {
|
|
||||||
// key: WxtStorageItem
|
|
||||||
keyStr = key.key;
|
|
||||||
opts = { fallback: key.fallback };
|
|
||||||
} else {
|
|
||||||
// key: { key, options }
|
|
||||||
keyStr = key.key;
|
|
||||||
opts = key.options;
|
|
||||||
}
|
|
||||||
orderedKeys.push(keyStr);
|
|
||||||
const { driverArea, driverKey } = resolveKey(keyStr);
|
|
||||||
const areaKeys = areaToKeyMap.get(driverArea) ?? [];
|
|
||||||
areaToKeyMap.set(driverArea, areaKeys.concat(driverKey));
|
|
||||||
keyToOptsMap.set(keyStr, opts);
|
|
||||||
});
|
|
||||||
|
|
||||||
const resultsMap = new Map<StorageItemKey, any>();
|
|
||||||
await Promise.all(
|
|
||||||
Array.from(areaToKeyMap.entries()).map(async ([driverArea, keys]) => {
|
|
||||||
const driverResults = await drivers[driverArea].getItems(keys);
|
|
||||||
driverResults.forEach((driverResult) => {
|
|
||||||
const key = `${driverArea}:${driverResult.key}` as StorageItemKey;
|
|
||||||
const opts = keyToOptsMap.get(key);
|
|
||||||
const value = getValueOrFallback(
|
|
||||||
driverResult.value,
|
|
||||||
opts?.fallback ?? opts?.defaultValue,
|
|
||||||
);
|
|
||||||
resultsMap.set(key, value);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return orderedKeys.map((key) => ({
|
|
||||||
key,
|
|
||||||
value: resultsMap.get(key),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
getMeta: async (key) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
return await getMeta(driver, driverKey);
|
|
||||||
},
|
|
||||||
getMetas: async (args) => {
|
|
||||||
const keys = args.map((arg) => {
|
|
||||||
const key = typeof arg === 'string' ? arg : arg.key;
|
|
||||||
const { driverArea, driverKey } = resolveKey(key);
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
driverArea,
|
|
||||||
driverKey,
|
|
||||||
driverMetaKey: getMetaKey(driverKey),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const areaToDriverMetaKeysMap = keys.reduce<
|
|
||||||
Partial<Record<StorageArea, (typeof keys)[number][]>>
|
|
||||||
>((map, key) => {
|
|
||||||
map[key.driverArea] ??= [];
|
|
||||||
map[key.driverArea]!.push(key);
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const resultsMap: Record<string, any> = {};
|
|
||||||
await Promise.all(
|
|
||||||
Object.entries(areaToDriverMetaKeysMap).map(async ([area, keys]) => {
|
|
||||||
const areaRes = await browser.storage[area as StorageArea].get(
|
|
||||||
keys.map((key) => key.driverMetaKey),
|
|
||||||
);
|
|
||||||
keys.forEach((key) => {
|
|
||||||
resultsMap[key.key] = areaRes[key.driverMetaKey] ?? {};
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return keys.map((key) => ({
|
|
||||||
key: key.key,
|
|
||||||
meta: resultsMap[key.key],
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
setItem: async (key, value) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
await setItem(driver, driverKey, value);
|
|
||||||
},
|
|
||||||
setItems: async (items) => {
|
|
||||||
const areaToKeyValueMap: Partial<
|
|
||||||
Record<StorageArea, Array<{ key: string; value: any }>>
|
|
||||||
> = {};
|
|
||||||
items.forEach((item) => {
|
|
||||||
const { driverArea, driverKey } = resolveKey(
|
|
||||||
'key' in item ? item.key : item.item.key,
|
|
||||||
);
|
|
||||||
areaToKeyValueMap[driverArea] ??= [];
|
|
||||||
areaToKeyValueMap[driverArea].push({
|
|
||||||
key: driverKey,
|
|
||||||
value: item.value,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await Promise.all(
|
|
||||||
Object.entries(areaToKeyValueMap).map(async ([driverArea, values]) => {
|
|
||||||
const driver = getDriver(driverArea as StorageArea);
|
|
||||||
await driver.setItems(values);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
setMeta: async (key, properties) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
await setMeta(driver, driverKey, properties);
|
|
||||||
},
|
|
||||||
setMetas: async (items) => {
|
|
||||||
const areaToMetaUpdatesMap: Partial<
|
|
||||||
Record<StorageArea, { key: string; properties: any }[]>
|
|
||||||
> = {};
|
|
||||||
items.forEach((item) => {
|
|
||||||
const { driverArea, driverKey } = resolveKey(
|
|
||||||
'key' in item ? item.key : item.item.key,
|
|
||||||
);
|
|
||||||
areaToMetaUpdatesMap[driverArea] ??= [];
|
|
||||||
areaToMetaUpdatesMap[driverArea].push({
|
|
||||||
key: driverKey,
|
|
||||||
properties: item.meta,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
Object.entries(areaToMetaUpdatesMap).map(
|
|
||||||
async ([storageArea, updates]) => {
|
|
||||||
const driver = getDriver(storageArea as StorageArea);
|
|
||||||
const metaKeys = updates.map(({ key }) => getMetaKey(key));
|
|
||||||
console.log(storageArea, metaKeys);
|
|
||||||
const existingMetas = await driver.getItems(metaKeys);
|
|
||||||
const existingMetaMap = Object.fromEntries(
|
|
||||||
existingMetas.map(({ key, value }) => [key, getMetaValue(value)]),
|
|
||||||
);
|
|
||||||
|
|
||||||
const metaUpdates = updates.map(({ key, properties }) => {
|
|
||||||
const metaKey = getMetaKey(key);
|
|
||||||
return {
|
|
||||||
key: metaKey,
|
|
||||||
value: mergeMeta(existingMetaMap[metaKey] ?? {}, properties),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
await driver.setItems(metaUpdates);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
removeItem: async (key, opts) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
await removeItem(driver, driverKey, opts);
|
|
||||||
},
|
|
||||||
removeItems: async (keys) => {
|
|
||||||
const areaToKeysMap: Partial<Record<StorageArea, string[]>> = {};
|
|
||||||
|
|
||||||
keys.forEach((key) => {
|
|
||||||
let keyStr: StorageItemKey;
|
|
||||||
let opts: RemoveItemOptions | undefined;
|
|
||||||
if (typeof key === 'string') {
|
|
||||||
// key: string
|
|
||||||
keyStr = key;
|
|
||||||
} else if ('getValue' in key) {
|
|
||||||
// key: WxtStorageItem
|
|
||||||
keyStr = key.key;
|
|
||||||
} else if ('item' in key) {
|
|
||||||
// key: { item, options }
|
|
||||||
keyStr = key.item.key;
|
|
||||||
opts = key.options;
|
|
||||||
} else {
|
|
||||||
// key: { key, options }
|
|
||||||
keyStr = key.key;
|
|
||||||
opts = key.options;
|
|
||||||
}
|
|
||||||
const { driverArea, driverKey } = resolveKey(keyStr);
|
|
||||||
areaToKeysMap[driverArea] ??= [];
|
|
||||||
areaToKeysMap[driverArea].push(driverKey);
|
|
||||||
if (opts?.removeMeta) {
|
|
||||||
areaToKeysMap[driverArea].push(getMetaKey(driverKey));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
Object.entries(areaToKeysMap).map(async ([driverArea, keys]) => {
|
|
||||||
const driver = getDriver(driverArea as StorageArea);
|
|
||||||
await driver.removeItems(keys);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
removeMeta: async (key, properties) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
await removeMeta(driver, driverKey, properties);
|
|
||||||
},
|
|
||||||
snapshot: async (base, opts) => {
|
|
||||||
const driver = getDriver(base);
|
|
||||||
const data = await driver.snapshot();
|
|
||||||
opts?.excludeKeys?.forEach((key) => {
|
|
||||||
delete data[key];
|
|
||||||
delete data[getMetaKey(key)];
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
restoreSnapshot: async (base, data) => {
|
|
||||||
const driver = getDriver(base);
|
|
||||||
await driver.restoreSnapshot(data);
|
|
||||||
},
|
|
||||||
watch: (key, cb) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
return watch(driver, driverKey, cb);
|
|
||||||
},
|
|
||||||
unwatch() {
|
|
||||||
Object.values(drivers).forEach((driver) => {
|
|
||||||
driver.unwatch();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
defineItem: (key, opts?: WxtStorageItemOptions<any>) => {
|
|
||||||
const { driver, driverKey } = resolveKey(key);
|
|
||||||
|
|
||||||
const { version: targetVersion = 1, migrations = {} } = opts ?? {};
|
|
||||||
if (targetVersion < 1) {
|
|
||||||
throw Error(
|
|
||||||
'Storage item version cannot be less than 1. Initial versions should be set to 1, not 0.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const migrate = async () => {
|
|
||||||
const driverMetaKey = getMetaKey(driverKey);
|
|
||||||
const [{ value }, { value: meta }] = await driver.getItems([
|
|
||||||
driverKey,
|
|
||||||
driverMetaKey,
|
|
||||||
]);
|
|
||||||
if (value == null) return;
|
|
||||||
|
|
||||||
const currentVersion = meta?.v ?? 1;
|
|
||||||
if (currentVersion > targetVersion) {
|
|
||||||
throw Error(
|
|
||||||
`Version downgrade detected (v${currentVersion} -> v${targetVersion}) for "${key}"`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Running storage migration for ${key}: v${currentVersion} -> v${targetVersion}`,
|
|
||||||
);
|
|
||||||
const migrationsToRun = Array.from(
|
|
||||||
{ length: targetVersion - currentVersion },
|
|
||||||
(_, i) => currentVersion + i + 1,
|
|
||||||
);
|
|
||||||
let migratedValue = value;
|
|
||||||
for (const migrateToVersion of migrationsToRun) {
|
|
||||||
try {
|
|
||||||
migratedValue =
|
|
||||||
(await migrations?.[migrateToVersion]?.(migratedValue)) ??
|
|
||||||
migratedValue;
|
|
||||||
} catch (err) {
|
|
||||||
throw Error(`v${migrateToVersion} migration failed for "${key}"`, {
|
|
||||||
cause: err,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await driver.setItems([
|
|
||||||
{ key: driverKey, value: migratedValue },
|
|
||||||
{ key: driverMetaKey, value: { ...meta, v: targetVersion } },
|
|
||||||
]);
|
|
||||||
logger.debug(
|
|
||||||
`Storage migration completed for ${key} v${targetVersion}`,
|
|
||||||
{ migratedValue },
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const migrationsDone =
|
|
||||||
opts?.migrations == null
|
|
||||||
? Promise.resolve()
|
|
||||||
: migrate().catch((err) => {
|
|
||||||
logger.error(`Migration failed for ${key}`, err);
|
|
||||||
});
|
|
||||||
|
|
||||||
const initMutex = new Mutex();
|
|
||||||
|
|
||||||
const getFallback = () => opts?.fallback ?? opts?.defaultValue ?? null;
|
|
||||||
|
|
||||||
const getOrInitValue = () =>
|
|
||||||
initMutex.runExclusive(async () => {
|
|
||||||
const value = await driver.getItem<any>(driverKey);
|
|
||||||
// Don't init value if it already exists or the init function isn't provided
|
|
||||||
if (value != null || opts?.init == null) return value;
|
|
||||||
|
|
||||||
const newValue = await opts.init();
|
|
||||||
await driver.setItem<any>(driverKey, newValue);
|
|
||||||
return newValue;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize the value once migrations have finished
|
|
||||||
migrationsDone.then(getOrInitValue);
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
get defaultValue() {
|
|
||||||
return getFallback();
|
|
||||||
},
|
|
||||||
get fallback() {
|
|
||||||
return getFallback();
|
|
||||||
},
|
|
||||||
getValue: async () => {
|
|
||||||
await migrationsDone;
|
|
||||||
if (opts?.init) {
|
|
||||||
return await getOrInitValue();
|
|
||||||
} else {
|
|
||||||
return await getItem(driver, driverKey, opts);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getMeta: async () => {
|
|
||||||
await migrationsDone;
|
|
||||||
return await getMeta(driver, driverKey);
|
|
||||||
},
|
|
||||||
setValue: async (value) => {
|
|
||||||
await migrationsDone;
|
|
||||||
return await setItem(driver, driverKey, value);
|
|
||||||
},
|
|
||||||
setMeta: async (properties) => {
|
|
||||||
await migrationsDone;
|
|
||||||
return await setMeta(driver, driverKey, properties);
|
|
||||||
},
|
|
||||||
removeValue: async (opts) => {
|
|
||||||
await migrationsDone;
|
|
||||||
return await removeItem(driver, driverKey, opts);
|
|
||||||
},
|
|
||||||
removeMeta: async (properties) => {
|
|
||||||
await migrationsDone;
|
|
||||||
return await removeMeta(driver, driverKey, properties);
|
|
||||||
},
|
|
||||||
watch: (cb) =>
|
|
||||||
watch(driver, driverKey, (newValue, oldValue) =>
|
|
||||||
cb(newValue ?? getFallback(), oldValue ?? getFallback()),
|
|
||||||
),
|
|
||||||
migrate,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return storage;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDriver(storageArea: StorageArea): WxtStorageDriver {
|
|
||||||
const getStorageArea = () => {
|
|
||||||
if (browser.runtime == null) {
|
|
||||||
throw Error(
|
|
||||||
[
|
|
||||||
"'wxt/storage' must be loaded in a web extension environment",
|
|
||||||
'\n - If thrown during a build, see https://github.com/wxt-dev/wxt/issues/371',
|
|
||||||
" - If thrown during tests, mock 'wxt/browser' correctly. See https://wxt.dev/guide/go-further/testing.html\n",
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (browser.storage == null) {
|
|
||||||
throw Error(
|
|
||||||
"You must add the 'storage' permission to your manifest to use 'wxt/storage'",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const area = browser.storage[storageArea];
|
|
||||||
if (area == null)
|
|
||||||
throw Error(`"browser.storage.${storageArea}" is undefined`);
|
|
||||||
return area;
|
|
||||||
};
|
|
||||||
const watchListeners = new Set<
|
|
||||||
(changes: Storage.StorageAreaOnChangedChangesType) => void
|
|
||||||
>();
|
|
||||||
return {
|
|
||||||
getItem: async (key) => {
|
|
||||||
const res = await getStorageArea().get(key);
|
|
||||||
return res[key];
|
|
||||||
},
|
|
||||||
getItems: async (keys) => {
|
|
||||||
const result = await getStorageArea().get(keys);
|
|
||||||
return keys.map((key) => ({ key, value: result[key] ?? null }));
|
|
||||||
},
|
|
||||||
setItem: async (key, value) => {
|
|
||||||
if (value == null) {
|
|
||||||
await getStorageArea().remove(key);
|
|
||||||
} else {
|
|
||||||
await getStorageArea().set({ [key]: value });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setItems: async (values) => {
|
|
||||||
const map = values.reduce<Record<string, unknown>>(
|
|
||||||
(map, { key, value }) => {
|
|
||||||
map[key] = value;
|
|
||||||
return map;
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
await getStorageArea().set(map);
|
|
||||||
},
|
|
||||||
removeItem: async (key) => {
|
|
||||||
await getStorageArea().remove(key);
|
|
||||||
},
|
|
||||||
removeItems: async (keys) => {
|
|
||||||
await getStorageArea().remove(keys);
|
|
||||||
},
|
|
||||||
snapshot: async () => {
|
|
||||||
return await getStorageArea().get();
|
|
||||||
},
|
|
||||||
restoreSnapshot: async (data) => {
|
|
||||||
await getStorageArea().set(data);
|
|
||||||
},
|
|
||||||
watch(key, cb) {
|
|
||||||
const listener = (changes: Storage.StorageAreaOnChangedChangesType) => {
|
|
||||||
const change = changes[key];
|
|
||||||
if (change == null) return;
|
|
||||||
if (dequal(change.newValue, change.oldValue)) return;
|
|
||||||
cb(change.newValue ?? null, change.oldValue ?? null);
|
|
||||||
};
|
|
||||||
getStorageArea().onChanged.addListener(listener);
|
|
||||||
watchListeners.add(listener);
|
|
||||||
return () => {
|
|
||||||
getStorageArea().onChanged.removeListener(listener);
|
|
||||||
watchListeners.delete(listener);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
unwatch() {
|
|
||||||
watchListeners.forEach((listener) => {
|
|
||||||
getStorageArea().onChanged.removeListener(listener);
|
|
||||||
});
|
|
||||||
watchListeners.clear();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WxtStorage {
|
|
||||||
/**
|
|
||||||
* Get an item from storage, or return `null` if it doesn't exist.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await storage.getItem<number>("local:installDate");
|
|
||||||
*/
|
|
||||||
getItem<TValue>(
|
|
||||||
key: StorageItemKey,
|
|
||||||
opts: GetItemOptions<TValue> & { fallback: TValue },
|
|
||||||
): Promise<TValue>;
|
|
||||||
|
|
||||||
getItem<TValue>(
|
|
||||||
key: StorageItemKey,
|
|
||||||
opts?: GetItemOptions<TValue>,
|
|
||||||
): Promise<TValue | null>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get multiple items from storage. The return order is guaranteed to be the same as the order
|
|
||||||
* requested.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await storage.getItems(["local:installDate", "session:someCounter"]);
|
|
||||||
*/
|
|
||||||
getItems(
|
|
||||||
keys: Array<
|
|
||||||
| StorageItemKey
|
|
||||||
| WxtStorageItem<any, any>
|
|
||||||
| { key: StorageItemKey; options?: GetItemOptions<any> }
|
|
||||||
>,
|
|
||||||
): Promise<Array<{ key: StorageItemKey; value: any }>>;
|
|
||||||
/**
|
|
||||||
* Return an object containing metadata about the key. Object is stored at `key + "$"`. If value
|
|
||||||
* is not an object, it returns an empty object.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await storage.getMeta("local:installDate");
|
|
||||||
*/
|
|
||||||
getMeta<T extends Record<string, unknown>>(key: StorageItemKey): Promise<T>;
|
|
||||||
/**
|
|
||||||
* Get the metadata of multiple storage items.
|
|
||||||
*
|
|
||||||
* @param items List of keys or items to get the metadata of.
|
|
||||||
* @returns An array containing storage keys and their metadata.
|
|
||||||
*/
|
|
||||||
getMetas(
|
|
||||||
keys: Array<StorageItemKey | WxtStorageItem<any, any>>,
|
|
||||||
): Promise<Array<{ key: StorageItemKey; meta: any }>>;
|
|
||||||
/**
|
|
||||||
* Set a value in storage. Setting a value to `null` or `undefined` is equivalent to calling
|
|
||||||
* `removeItem`.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await storage.setItem<number>("local:installDate", Date.now());
|
|
||||||
*/
|
|
||||||
setItem<T>(key: StorageItemKey, value: T | null): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Set multiple values in storage. If a value is set to `null` or `undefined`, the key is removed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await storage.setItem([
|
|
||||||
* { key: "local:installDate", value: Date.now() },
|
|
||||||
* { key: "session:someCounter, value: 5 },
|
|
||||||
* ]);
|
|
||||||
*/
|
|
||||||
setItems(
|
|
||||||
values: Array<
|
|
||||||
| { key: StorageItemKey; value: any }
|
|
||||||
| { item: WxtStorageItem<any, any>; value: any }
|
|
||||||
>,
|
|
||||||
): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Sets metadata properties. If some properties are already set, but are not included in the
|
|
||||||
* `properties` parameter, they will not be removed.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await storage.setMeta("local:installDate", { appVersion });
|
|
||||||
*/
|
|
||||||
setMeta<T extends Record<string, unknown>>(
|
|
||||||
key: StorageItemKey,
|
|
||||||
properties: T | null,
|
|
||||||
): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Set the metadata of multiple storage items.
|
|
||||||
*
|
|
||||||
* @param items List of storage keys or items and metadata to set for each.
|
|
||||||
*/
|
|
||||||
setMetas(
|
|
||||||
metas: Array<
|
|
||||||
| { key: StorageItemKey; meta: Record<string, any> }
|
|
||||||
| { item: WxtStorageItem<any, any>; meta: Record<string, any> }
|
|
||||||
>,
|
|
||||||
): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Removes an item from storage.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* await storage.removeItem("local:installDate");
|
|
||||||
*/
|
|
||||||
removeItem(key: StorageItemKey, opts?: RemoveItemOptions): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Remove a list of keys from storage.
|
|
||||||
*/
|
|
||||||
removeItems(
|
|
||||||
keys: Array<
|
|
||||||
| StorageItemKey
|
|
||||||
| WxtStorageItem<any, any>
|
|
||||||
| { key: StorageItemKey; options?: RemoveItemOptions }
|
|
||||||
| { item: WxtStorageItem<any, any>; options?: RemoveItemOptions }
|
|
||||||
>,
|
|
||||||
): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Remove the entire metadata for a key, or specific properties by name.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // Remove all metadata properties from the item
|
|
||||||
* await storage.removeMeta("local:installDate");
|
|
||||||
*
|
|
||||||
* // Remove only specific the "v" field
|
|
||||||
* await storage.removeMeta("local:installDate", "v")
|
|
||||||
*/
|
|
||||||
removeMeta(
|
|
||||||
key: StorageItemKey,
|
|
||||||
properties?: string | string[],
|
|
||||||
): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Return all the items in storage.
|
|
||||||
*/
|
|
||||||
snapshot(
|
|
||||||
base: StorageArea,
|
|
||||||
opts?: SnapshotOptions,
|
|
||||||
): Promise<Record<string, unknown>>;
|
|
||||||
/**
|
|
||||||
* Restores the results of `snapshot`. If new properties have been saved since the snapshot, they are
|
|
||||||
* not overridden. Only values existing in the snapshot are overridden.
|
|
||||||
*/
|
|
||||||
restoreSnapshot(base: StorageArea, data: any): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Watch for changes to a specific key in storage.
|
|
||||||
*/
|
|
||||||
watch<T>(key: StorageItemKey, cb: WatchCallback<T | null>): Unwatch;
|
|
||||||
/**
|
|
||||||
* Remove all watch listeners.
|
|
||||||
*/
|
|
||||||
unwatch(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a storage item with a default value, type, or versioning.
|
|
||||||
*
|
|
||||||
* Read full docs: https://wxt.dev/guide/extension-apis/storage.html#defining-storage-items
|
|
||||||
*/
|
|
||||||
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
|
||||||
key: StorageItemKey,
|
|
||||||
): WxtStorageItem<TValue | null, TMetadata>;
|
|
||||||
defineItem<TValue, TMetadata extends Record<string, unknown> = {}>(
|
|
||||||
key: StorageItemKey,
|
|
||||||
options: WxtStorageItemOptions<TValue>,
|
|
||||||
): WxtStorageItem<TValue, TMetadata>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WxtStorageDriver {
|
|
||||||
getItem<T>(key: string): Promise<T | null>;
|
|
||||||
getItems(keys: string[]): Promise<{ key: string; value: any }[]>;
|
|
||||||
setItem<T>(key: string, value: T | null): Promise<void>;
|
|
||||||
setItems(values: Array<{ key: string; value: any }>): Promise<void>;
|
|
||||||
removeItem(key: string): Promise<void>;
|
|
||||||
removeItems(keys: string[]): Promise<void>;
|
|
||||||
snapshot(): Promise<Record<string, unknown>>;
|
|
||||||
restoreSnapshot(data: Record<string, unknown>): Promise<void>;
|
|
||||||
watch<T>(key: string, cb: WatchCallback<T | null>): Unwatch;
|
|
||||||
unwatch(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WxtStorageItem<
|
|
||||||
TValue,
|
|
||||||
TMetadata extends Record<string, unknown>,
|
|
||||||
> {
|
|
||||||
/**
|
|
||||||
* The storage key passed when creating the storage item.
|
|
||||||
*/
|
|
||||||
key: StorageItemKey;
|
|
||||||
/**
|
|
||||||
* @deprecated Renamed to fallback, use it instead.
|
|
||||||
*/
|
|
||||||
defaultValue: TValue;
|
|
||||||
/**
|
|
||||||
* The value provided by the `fallback` option.
|
|
||||||
*/
|
|
||||||
fallback: TValue;
|
|
||||||
/**
|
|
||||||
* Get the latest value from storage.
|
|
||||||
*/
|
|
||||||
getValue(): Promise<TValue>;
|
|
||||||
/**
|
|
||||||
* Get metadata.
|
|
||||||
*/
|
|
||||||
getMeta(): Promise<NullablePartial<TMetadata>>;
|
|
||||||
/**
|
|
||||||
* Set the value in storage.
|
|
||||||
*/
|
|
||||||
setValue(value: TValue): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Set metadata properties.
|
|
||||||
*/
|
|
||||||
setMeta(properties: NullablePartial<TMetadata>): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Remove the value from storage.
|
|
||||||
*/
|
|
||||||
removeValue(opts?: RemoveItemOptions): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Remove all metadata or certain properties from metadata.
|
|
||||||
*/
|
|
||||||
removeMeta(properties?: string[]): Promise<void>;
|
|
||||||
/**
|
|
||||||
* Listen for changes to the value in storage.
|
|
||||||
*/
|
|
||||||
watch(cb: WatchCallback<TValue>): Unwatch;
|
|
||||||
/**
|
|
||||||
* If there are migrations defined on the storage item, migrate to the latest version.
|
|
||||||
*
|
|
||||||
* **This function is ran automatically whenever the extension updates**, so you don't have to call it
|
|
||||||
* manually.
|
|
||||||
*/
|
|
||||||
migrate(): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type StorageArea = 'local' | 'session' | 'sync' | 'managed';
|
|
||||||
export type StorageItemKey = `${StorageArea}:${string}`;
|
|
||||||
|
|
||||||
export interface GetItemOptions<T> {
|
|
||||||
/**
|
|
||||||
* @deprecated Renamed to `fallback`, use it instead.
|
|
||||||
*/
|
|
||||||
defaultValue?: T;
|
|
||||||
/**
|
|
||||||
* Default value returned when `getItem` would otherwise return `null`.
|
|
||||||
*/
|
|
||||||
fallback?: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoveItemOptions {
|
|
||||||
/**
|
|
||||||
* Optionally remove metadata when deleting a key.
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
removeMeta?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SnapshotOptions {
|
|
||||||
/**
|
|
||||||
* Exclude a list of keys. The storage area prefix should be removed since the snapshot is for a
|
|
||||||
* specific storage area already.
|
|
||||||
*/
|
|
||||||
excludeKeys?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WxtStorageItemOptions<T> {
|
|
||||||
/**
|
|
||||||
* @deprecated Renamed to `fallback`, use it instead.
|
|
||||||
*/
|
|
||||||
defaultValue?: T;
|
|
||||||
/**
|
|
||||||
* Default value returned when `getValue` would otherwise return `null`.
|
|
||||||
*/
|
|
||||||
fallback?: T;
|
|
||||||
/**
|
|
||||||
* If passed, a value in storage will be initialized immediately after
|
|
||||||
* defining the storage item. This function returns the value that will be
|
|
||||||
* saved to storage during the initialization process if a value doesn't
|
|
||||||
* already exist.
|
|
||||||
*/
|
|
||||||
init?: () => T | Promise<T>;
|
|
||||||
/**
|
|
||||||
* Provide a version number for the storage item to enable migrations. When changing the version
|
|
||||||
* in the future, migration functions will be ran on application startup.
|
|
||||||
*/
|
|
||||||
version?: number;
|
|
||||||
/**
|
|
||||||
* A map of version numbers to the functions used to migrate the data to that version.
|
|
||||||
*/
|
|
||||||
migrations?: Record<number, (oldValue: any) => any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as `Partial`, but includes `| null`. It makes all the properties of an object optional and
|
|
||||||
* nullable.
|
|
||||||
*/
|
|
||||||
export type NullablePartial<T> = {
|
|
||||||
[key in keyof T]+?: T[key] | undefined | null;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Callback called when a value in storage is changed.
|
|
||||||
*/
|
|
||||||
export type WatchCallback<T> = (newValue: T, oldValue: T) => void;
|
|
||||||
/**
|
|
||||||
* Call to remove a watch listener
|
|
||||||
*/
|
|
||||||
export type Unwatch = () => void;
|
|
||||||
|
|
||||||
export class MigrationError extends Error {
|
|
||||||
constructor(
|
|
||||||
public key: string,
|
|
||||||
public version: number,
|
|
||||||
options?: ErrorOptions,
|
|
||||||
) {
|
|
||||||
super(`v${version} migration failed for "${key}"`, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1100,11 +1100,15 @@ export type HookResult = Promise<void> | void;
|
|||||||
|
|
||||||
export interface WxtHooks {
|
export interface WxtHooks {
|
||||||
/**
|
/**
|
||||||
* Called after WXT initialization, when the WXT instance is ready to work.
|
* Called only one time after WXT initialization, when the WXT instance is ready to work.
|
||||||
* @param wxt The configured WXT object
|
* @param wxt The configured WXT object
|
||||||
* @returns Promise
|
|
||||||
*/
|
*/
|
||||||
ready: (wxt: Wxt) => HookResult;
|
ready: (wxt: Wxt) => HookResult;
|
||||||
|
/**
|
||||||
|
* Called whenever config is loaded or reloaded. Use this hook to modify config by modifying `wxt.config`.
|
||||||
|
* @param wxt The configured WXT object
|
||||||
|
*/
|
||||||
|
'config:resolved': (wxt: Wxt) => HookResult;
|
||||||
/**
|
/**
|
||||||
* Called before WXT writes .wxt/tsconfig.json and .wxt/wxt.d.ts, allowing
|
* Called before WXT writes .wxt/tsconfig.json and .wxt/wxt.d.ts, allowing
|
||||||
* addition of custom references and declarations in wxt.d.ts, or directly
|
* addition of custom references and declarations in wxt.d.ts, or directly
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"src/browser/index.ts",
|
"src/browser/index.ts",
|
||||||
"src/browser/chrome.ts",
|
"src/browser/chrome.ts",
|
||||||
"src/index.ts",
|
"src/index.ts",
|
||||||
"src/storage.ts",
|
"src/modules.ts",
|
||||||
"src/modules.ts"
|
"src/storage.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1024
-706
File diff suppressed because it is too large
Load Diff
@@ -7,5 +7,7 @@ export default {
|
|||||||
'typedoc-vitepress-theme',
|
'typedoc-vitepress-theme',
|
||||||
// Manually manage version so a single version is used:
|
// Manually manage version so a single version is used:
|
||||||
'esbuild',
|
'esbuild',
|
||||||
|
// Maintained manually to match min-node version
|
||||||
|
'@types/node',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.280",
|
||||||
"@types/react": "^18.3.11",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@wxt-dev/module-react": "^1.1.1",
|
"@wxt-dev/module-react": "^1.1.1",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"wxt": "^0.19.11"
|
"wxt": "^0.19.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
"postinstall": "wxt prepare"
|
"postinstall": "wxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"solid-js": "^1.9.1"
|
"solid-js": "^1.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.280",
|
||||||
"@wxt-dev/module-solid": "^1.1.2",
|
"@wxt-dev/module-solid": "^1.1.2",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"wxt": "^0.19.11"
|
"wxt": "^0.19.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
"postinstall": "wxt prepare"
|
"postinstall": "wxt prepare"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.0.269",
|
|
||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"@wxt-dev/module-svelte": "^1.0.1",
|
"@types/chrome": "^0.0.280",
|
||||||
"svelte": "^4.2.19",
|
"@wxt-dev/module-svelte": "^2.0.0",
|
||||||
"svelte-check": "^3.8.6",
|
"svelte": "^5.1.16",
|
||||||
"tslib": "^2.7.0",
|
"svelte-check": "^4.0.7",
|
||||||
"typescript": "^5.6.2",
|
"tslib": "^2.8.1",
|
||||||
"wxt": "^0.19.11"
|
"typescript": "^5.6.3",
|
||||||
|
"wxt": "^0.19.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"postinstall": "wxt prepare"
|
"postinstall": "wxt prepare"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.280",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"wxt": "^0.19.11"
|
"wxt": "^0.19.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
"postinstall": "wxt prepare"
|
"postinstall": "wxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.5.11"
|
"vue": "^3.5.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.280",
|
||||||
"@wxt-dev/module-vue": "^1.0.1",
|
"@wxt-dev/module-vue": "^1.0.1",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.3",
|
||||||
"vue-tsc": "^2.1.6",
|
"vue-tsc": "^2.1.10",
|
||||||
"wxt": "^0.19.11"
|
"wxt": "^0.19.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user