feat: persist push subscriptions in sqlite
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
All checks were successful
CI / Lint, typecheck and build (push) Successful in 9m54s
This commit is contained in:
@@ -3,6 +3,7 @@ WEB_PUSH_VAPID_PRIVATE_KEY=
|
|||||||
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
|
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
|
||||||
NOTIFICATIONS_CRON_SECRET=
|
NOTIFICATIONS_CRON_SECRET=
|
||||||
WTR_APP_URL=http://127.0.0.1:3000
|
WTR_APP_URL=http://127.0.0.1:3000
|
||||||
|
WTR_DATABASE_PATH=./data/wtr.sqlite
|
||||||
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
|
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
|
||||||
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
|
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
|
||||||
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
|
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ out
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
|
data/*.sqlite
|
||||||
|
data/*.sqlite-*
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
`wtr.` to mobilna PWA pogodowa dla Polski. Pokazuje bieżącą analizę IMGW Hybrid, pomiary synoptyczne, dane hydrologiczne i ostrzeżenia z publicznych API IMGW oraz prognozę modelową łączącą IMGW ALARO z Open-Meteo. Open-Meteo Geocoding służy także do wyszukiwania miejscowości, a Nominatim / OpenStreetMap do opcjonalnego reverse geocodingu po zgodzie GPS użytkownika.
|
`wtr.` to mobilna PWA pogodowa dla Polski. Pokazuje bieżącą analizę IMGW Hybrid, pomiary synoptyczne, dane hydrologiczne i ostrzeżenia z publicznych API IMGW oraz prognozę modelową łączącą IMGW ALARO z Open-Meteo. Open-Meteo Geocoding służy także do wyszukiwania miejscowości, a Nominatim / OpenStreetMap do opcjonalnego reverse geocodingu po zgodzie GPS użytkownika.
|
||||||
|
|
||||||
Stack: Next.js App Router, React, TypeScript, Tailwind CSS, TanStack Query, Zustand, Framer Motion, Recharts, Lucide React i `web-push`. PWA korzysta z manifestu oraz własnego service workera.
|
Stack: Next.js App Router, React, TypeScript, Tailwind CSS, TanStack Query, Zustand, Framer Motion, Recharts, Lucide React, `web-push` i SQLite przez `better-sqlite3`. PWA korzysta z manifestu oraz własnego service workera.
|
||||||
|
|
||||||
Najważniejsze katalogi:
|
Najważniejsze katalogi:
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ Repozytorium nie ma obecnie skryptu testów ani formattera. `npm run typecheck`
|
|||||||
- Dashboard pokazuje kompaktowo wyłącznie aktywne i nadchodzące ostrzeżenia meteo dla wybranego obszaru. Filtruj je po `validTo` względem czasu przeglądarki i automatycznie usuwaj wygasłe komunikaty bez przeładowania strony.
|
- Dashboard pokazuje kompaktowo wyłącznie aktywne i nadchodzące ostrzeżenia meteo dla wybranego obszaru. Filtruj je po `validTo` względem czasu przeglądarki i automatycznie usuwaj wygasłe komunikaty bez przeładowania strony.
|
||||||
- Brief dnia i brief na jutro generuj deterministycznie w `lib/weather-brief.ts` z prognozy modelowej i ostrzeżeń IMGW. Nie traktuj ich jako odpowiedzi modelu AI i nie wymagaj klucza OpenAI API. Brief na jutro korzysta z pełnego jutrzejszego dnia prognozy i może informować o burzach na podstawie kodów modelu także bez oficjalnego ostrzeżenia IMGW.
|
- Brief dnia i brief na jutro generuj deterministycznie w `lib/weather-brief.ts` z prognozy modelowej i ostrzeżeń IMGW. Nie traktuj ich jako odpowiedzi modelu AI i nie wymagaj klucza OpenAI API. Brief na jutro korzysta z pełnego jutrzejszego dnia prognozy i może informować o burzach na podstawie kodów modelu także bez oficjalnego ostrzeżenia IMGW.
|
||||||
- Preferencje jednostek temperatury i wiatru są ustawieniami prezentacyjnymi. Dane źródłowe oraz progi logiki pogody pozostają liczone w `°C` i `m/s`, a wybrane jednostki zapisuj w subskrypcji Web Push, żeby briefy serwerowe używały tego samego formatu co UI.
|
- Preferencje jednostek temperatury i wiatru są ustawieniami prezentacyjnymi. Dane źródłowe oraz progi logiki pogody pozostają liczone w `°C` i `m/s`, a wybrane jednostki zapisuj w subskrypcji Web Push, żeby briefy serwerowe używały tego samego formatu co UI.
|
||||||
- Powiadomienia Web Push o ostrzeżeniach meteo, porannym briefie i wieczornym briefie na jutro konfiguruj przez `/settings`, `public/sw.js`, route handlery `app/api/notifications/*`, w tym testowy `/api/notifications/test`, oraz self-hostowany worker `scripts/notification-worker.mjs`. Endpointy harmonogramu nie uruchamiają się same bez workera albo zewnętrznego crona. Wymagają kluczy VAPID w zmiennych środowiskowych. iOS/iPadOS wymaga PWA z ekranu początkowego, ale Android i desktop nie powinny być blokowane wymogiem `standalone`. Obecny `lib/push-store.ts` jest pamięciowy i przed produkcją musi zostać zastąpiony trwałym magazynem subskrypcji.
|
- Powiadomienia Web Push o ostrzeżeniach meteo, porannym briefie i wieczornym briefie na jutro konfiguruj przez `/settings`, `public/sw.js`, route handlery `app/api/notifications/*`, w tym testowy `/api/notifications/test`, oraz self-hostowany worker `scripts/notification-worker.mjs`. Endpointy harmonogramu nie uruchamiają się same bez workera albo zewnętrznego crona. Wymagają kluczy VAPID w zmiennych środowiskowych. iOS/iPadOS wymaga PWA z ekranu początkowego, ale Android i desktop nie powinny być blokowane wymogiem `standalone`. `lib/push-store.ts` zapisuje subskrypcje i historię wysyłek w SQLite wskazanym przez `WTR_DATABASE_PATH`, domyślnie `./data/wtr.sqlite`.
|
||||||
- GPS wymaga świadomej zgody użytkownika i HTTPS. Zaokrąglaj współrzędne przed użyciem i utrzymuj widoczną atrybucję OpenStreetMap dla reverse geocodingu Nominatim.
|
- GPS wymaga świadomej zgody użytkownika i HTTPS. Zaokrąglaj współrzędne przed użyciem i utrzymuj widoczną atrybucję OpenStreetMap dla reverse geocodingu Nominatim.
|
||||||
- Normalizuj zewnętrzne odpowiedzi i obsługuj `null`, puste pola oraz błędne wartości. Brak danych pokazuj jawnie zamiast uzupełniać estymacją.
|
- Normalizuj zewnętrzne odpowiedzi i obsługuj `null`, puste pola oraz błędne wartości. Brak danych pokazuj jawnie zamiast uzupełniać estymacją.
|
||||||
- Dla pobierania danych używaj TanStack Query z sensownym `queryKey`, cache i retry. W UI zachowuj loading, error, retry oraz empty states.
|
- Dla pobierania danych używaj TanStack Query z sensownym `queryKey`, cache i retry. W UI zachowuj loading, error, retry oraz empty states.
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Aplikacja pokazuje bieżącą analizę IMGW Hybrid, pomiary synoptyczne, prognoz
|
|||||||
- Recharts
|
- Recharts
|
||||||
- Lucide React
|
- Lucide React
|
||||||
- `web-push`
|
- `web-push`
|
||||||
|
- SQLite przez `better-sqlite3` dla trwałego magazynu subskrypcji Web Push
|
||||||
- własny service worker i manifest PWA
|
- własny service worker i manifest PWA
|
||||||
|
|
||||||
## Szybki Start
|
## Szybki Start
|
||||||
@@ -50,6 +51,7 @@ WEB_PUSH_VAPID_PRIVATE_KEY=
|
|||||||
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
|
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
|
||||||
NOTIFICATIONS_CRON_SECRET=
|
NOTIFICATIONS_CRON_SECRET=
|
||||||
WTR_APP_URL=http://127.0.0.1:3000
|
WTR_APP_URL=http://127.0.0.1:3000
|
||||||
|
WTR_DATABASE_PATH=./data/wtr.sqlite
|
||||||
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
|
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
|
||||||
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
|
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
|
||||||
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
|
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
|
||||||
@@ -106,7 +108,7 @@ docs/ szczegółowa dokumentacja techniczna
|
|||||||
|
|
||||||
## Status i Ograniczenia
|
## Status i Ograniczenia
|
||||||
|
|
||||||
- `lib/push-store.ts` przechowuje subskrypcje Web Push w pamięci procesu. Produkcyjne wdrożenie wymaga trwałego magazynu, np. bazy danych albo KV.
|
- `lib/push-store.ts` przechowuje subskrypcje Web Push i historię wysyłek w lokalnym SQLite. Na self-hostingu zadbaj o trwały katalog dla `WTR_DATABASE_PATH` i backup pliku bazy.
|
||||||
- Endpoint IMGW Hybrid używany przez dashboard pochodzi z publicznego frontendu `meteo.imgw.pl`, a nie ze stabilnie opisanej dokumentacji `danepubliczne.imgw.pl`.
|
- Endpoint IMGW Hybrid używany przez dashboard pochodzi z publicznego frontendu `meteo.imgw.pl`, a nie ze stabilnie opisanej dokumentacji `danepubliczne.imgw.pl`.
|
||||||
- Publiczne API IMGW potrafi zwrócić `404` z komunikatem `No products were found` dla pustych list ostrzeżeń; aplikacja traktuje to jako brak ostrzeżeń.
|
- Publiczne API IMGW potrafi zwrócić `404` z komunikatem `No products were found` dla pustych list ostrzeżeń; aplikacja traktuje to jako brak ostrzeżeń.
|
||||||
- Prognoza jest prognozą modelową, nie pomiarem IMGW. Bieżące pomiary i prognozy są w UI rozdzielane.
|
- Prognoza jest prognozą modelową, nie pomiarem IMGW. Bieżące pomiary i prognozy są w UI rozdzielane.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Wszystkie zewnętrzne źródła danych są wywoływane przez route handlery Next
|
|||||||
|
|
||||||
## Powiadomienia
|
## Powiadomienia
|
||||||
|
|
||||||
Endpointy powiadomień działają w runtime Node.js, bo korzystają z `web-push` i pamięciowego magazynu subskrypcji.
|
Endpointy powiadomień działają w runtime Node.js, bo korzystają z `web-push` i lokalnego magazynu SQLite dla subskrypcji.
|
||||||
|
|
||||||
| Metoda | Endpoint | Przeznaczenie |
|
| Metoda | Endpoint | Przeznaczenie |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ WEB_PUSH_VAPID_PRIVATE_KEY=
|
|||||||
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
|
WEB_PUSH_VAPID_SUBJECT=mailto:admin@example.com
|
||||||
NOTIFICATIONS_CRON_SECRET=
|
NOTIFICATIONS_CRON_SECRET=
|
||||||
WTR_APP_URL=http://127.0.0.1:3000
|
WTR_APP_URL=http://127.0.0.1:3000
|
||||||
|
WTR_DATABASE_PATH=./data/wtr.sqlite
|
||||||
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
|
NOTIFICATIONS_WARNING_INTERVAL_MINUTES=5
|
||||||
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
|
NOTIFICATIONS_MORNING_BRIEF_TIME=07:00
|
||||||
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
|
NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
|
||||||
@@ -67,6 +68,8 @@ NOTIFICATIONS_TOMORROW_BRIEF_TIME=18:00
|
|||||||
|
|
||||||
Do zwykłego uruchomienia aplikacji pogodowej zmienne Web Push nie są wymagane. Są potrzebne dopiero dla powiadomień.
|
Do zwykłego uruchomienia aplikacji pogodowej zmienne Web Push nie są wymagane. Są potrzebne dopiero dla powiadomień.
|
||||||
|
|
||||||
|
`WTR_DATABASE_PATH` wskazuje plik SQLite z subskrypcjami Web Push i historią wysyłek. Na self-hostingu ustaw go na ścieżkę w trwałym katalogu, np. `/home/admin/wtr-data/wtr.sqlite`, jeśli katalog projektu jest podmieniany podczas deploya.
|
||||||
|
|
||||||
## Jakość
|
## Jakość
|
||||||
|
|
||||||
Przed zakończeniem zmian uruchamiaj:
|
Przed zakończeniem zmian uruchamiaj:
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ Endpointy harmonogramu wymagają też sekretu crona w produkcji:
|
|||||||
NOTIFICATIONS_CRON_SECRET=
|
NOTIFICATIONS_CRON_SECRET=
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Subskrypcje Web Push i historia wysyłek są zapisywane w SQLite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WTR_DATABASE_PATH=./data/wtr.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
Jeśli zmienna nie jest ustawiona, aplikacja używa `./data/wtr.sqlite` względem katalogu projektu.
|
||||||
|
|
||||||
## Preferencje Subskrypcji
|
## Preferencje Subskrypcji
|
||||||
|
|
||||||
Subskrypcja Web Push zapisuje:
|
Subskrypcja Web Push zapisuje:
|
||||||
@@ -114,6 +122,6 @@ x-cron-secret: <NOTIFICATIONS_CRON_SECRET>
|
|||||||
|
|
||||||
## Ograniczenia
|
## Ograniczenia
|
||||||
|
|
||||||
Obecny `lib/push-store.ts` przechowuje subskrypcje i historię wysyłek w pamięci procesu. Po restarcie procesu te dane znikają. Produkcja wymaga trwałego magazynu, np. bazy danych albo KV.
|
`lib/push-store.ts` przechowuje subskrypcje i historię wysyłek w lokalnym SQLite przez `better-sqlite3`. Dane przetrwają restart procesu, jeśli plik wskazany przez `WTR_DATABASE_PATH` znajduje się w trwałym katalogu. Na self-hostingu warto backupować plik bazy razem z plikami `-wal` i `-shm`, jeśli istnieją.
|
||||||
|
|
||||||
Powiadomienie testowe jest wysyłane na konkretny endpoint subskrypcji przekazany z `/settings`, a nie broadcastowane do wszystkich urządzeń.
|
Powiadomienie testowe jest wysyłane na konkretny endpoint subskrypcji przekazany z `/settings`, a nie broadcastowane do wszystkich urządzeń.
|
||||||
|
|||||||
@@ -1,83 +1,265 @@
|
|||||||
|
import Database from "better-sqlite3";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { DEFAULT_TEMPERATURE_UNIT, DEFAULT_WIND_SPEED_UNIT, isTemperatureUnit, isWindSpeedUnit } from "@/lib/weather-utils";
|
||||||
import type { WarningPushSubscription } from "@/types/notifications";
|
import type { WarningPushSubscription } from "@/types/notifications";
|
||||||
|
import type { Province } from "@/types/province";
|
||||||
|
|
||||||
interface PushStore {
|
interface PushDatabase {
|
||||||
subscriptions: Map<string, WarningPushSubscription>;
|
database: Database.Database;
|
||||||
sentWarningIds: Map<string, Set<string>>;
|
|
||||||
sentMorningBriefDates: Map<string, Set<string>>;
|
|
||||||
sentTomorrowBriefDates: Map<string, Set<string>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const globalStore = globalThis as typeof globalThis & { __wtrPushStore?: PushStore };
|
interface PushSubscriptionRow {
|
||||||
|
endpoint: string;
|
||||||
|
subscription_json: string;
|
||||||
|
province: string;
|
||||||
|
language: string;
|
||||||
|
enabled: number;
|
||||||
|
morning_brief_enabled: number;
|
||||||
|
tomorrow_brief_enabled: number;
|
||||||
|
latitude: number | null;
|
||||||
|
longitude: number | null;
|
||||||
|
location_name: string | null;
|
||||||
|
county_teryt: string | null;
|
||||||
|
temperature_unit: string | null;
|
||||||
|
wind_speed_unit: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
function getStore() {
|
const globalStore = globalThis as typeof globalThis & { __wtrPushDatabase?: PushDatabase };
|
||||||
if (!globalStore.__wtrPushStore) {
|
|
||||||
globalStore.__wtrPushStore = {
|
function getDatabasePath() {
|
||||||
subscriptions: new Map(),
|
const configuredPath = process.env.WTR_DATABASE_PATH;
|
||||||
sentWarningIds: new Map(),
|
if (configuredPath) {
|
||||||
sentMorningBriefDates: new Map(),
|
return path.isAbsolute(configuredPath)
|
||||||
sentTomorrowBriefDates: new Map(),
|
? configuredPath
|
||||||
};
|
: path.join(/* turbopackIgnore: true */ process.cwd(), configuredPath);
|
||||||
|
}
|
||||||
|
return path.join(/* turbopackIgnore: true */ process.cwd(), "data", "wtr.sqlite");
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDatabaseDirectory(databasePath: string) {
|
||||||
|
fs.mkdirSync(path.dirname(databasePath), { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeDatabase(database: Database.Database) {
|
||||||
|
database.pragma("journal_mode = WAL");
|
||||||
|
database.pragma("foreign_keys = ON");
|
||||||
|
database.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||||||
|
endpoint TEXT PRIMARY KEY,
|
||||||
|
subscription_json TEXT NOT NULL,
|
||||||
|
province TEXT NOT NULL,
|
||||||
|
language TEXT NOT NULL,
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1,
|
||||||
|
morning_brief_enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
|
tomorrow_brief_enabled INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latitude REAL,
|
||||||
|
longitude REAL,
|
||||||
|
location_name TEXT,
|
||||||
|
county_teryt TEXT,
|
||||||
|
temperature_unit TEXT NOT NULL DEFAULT '${DEFAULT_TEMPERATURE_UNIT}',
|
||||||
|
wind_speed_unit TEXT NOT NULL DEFAULT '${DEFAULT_WIND_SPEED_UNIT}',
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sent_warnings (
|
||||||
|
endpoint TEXT NOT NULL,
|
||||||
|
warning_id TEXT NOT NULL,
|
||||||
|
sent_at TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (endpoint, warning_id),
|
||||||
|
FOREIGN KEY (endpoint) REFERENCES push_subscriptions(endpoint) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sent_morning_briefs (
|
||||||
|
endpoint TEXT NOT NULL,
|
||||||
|
date_key TEXT NOT NULL,
|
||||||
|
sent_at TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (endpoint, date_key),
|
||||||
|
FOREIGN KEY (endpoint) REFERENCES push_subscriptions(endpoint) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sent_tomorrow_briefs (
|
||||||
|
endpoint TEXT NOT NULL,
|
||||||
|
date_key TEXT NOT NULL,
|
||||||
|
sent_at TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (endpoint, date_key),
|
||||||
|
FOREIGN KEY (endpoint) REFERENCES push_subscriptions(endpoint) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatabase() {
|
||||||
|
if (!globalStore.__wtrPushDatabase) {
|
||||||
|
const databasePath = getDatabasePath();
|
||||||
|
ensureDatabaseDirectory(databasePath);
|
||||||
|
const database = new Database(databasePath);
|
||||||
|
initializeDatabase(database);
|
||||||
|
globalStore.__wtrPushDatabase = { database };
|
||||||
|
}
|
||||||
|
return globalStore.__wtrPushDatabase.database;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSubscription(row: PushSubscriptionRow): WarningPushSubscription | null {
|
||||||
|
try {
|
||||||
|
const subscription = JSON.parse(row.subscription_json) as WarningPushSubscription["subscription"];
|
||||||
|
return {
|
||||||
|
endpoint: row.endpoint,
|
||||||
|
subscription,
|
||||||
|
province: row.province as Province,
|
||||||
|
language: row.language === "en" ? "en" : "pl",
|
||||||
|
enabled: row.enabled === 1,
|
||||||
|
morningBriefEnabled: row.morning_brief_enabled === 1,
|
||||||
|
tomorrowBriefEnabled: row.tomorrow_brief_enabled === 1,
|
||||||
|
latitude: row.latitude,
|
||||||
|
longitude: row.longitude,
|
||||||
|
locationName: row.location_name,
|
||||||
|
countyTeryt: row.county_teryt,
|
||||||
|
temperatureUnit: isTemperatureUnit(row.temperature_unit) ? row.temperature_unit : DEFAULT_TEMPERATURE_UNIT,
|
||||||
|
windSpeedUnit: isWindSpeedUnit(row.wind_speed_unit) ? row.wind_speed_unit : DEFAULT_WIND_SPEED_UNIT,
|
||||||
|
createdAt: row.created_at,
|
||||||
|
updatedAt: row.updated_at,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return globalStore.__wtrPushStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function upsertPushSubscription(subscription: WarningPushSubscription) {
|
export function upsertPushSubscription(subscription: WarningPushSubscription) {
|
||||||
getStore().subscriptions.set(subscription.endpoint, subscription);
|
getDatabase().prepare(`
|
||||||
}
|
INSERT INTO push_subscriptions (
|
||||||
|
endpoint,
|
||||||
export function removePushSubscription(endpoint: string) {
|
subscription_json,
|
||||||
const store = getStore();
|
province,
|
||||||
store.subscriptions.delete(endpoint);
|
language,
|
||||||
store.sentWarningIds.delete(endpoint);
|
enabled,
|
||||||
store.sentMorningBriefDates.delete(endpoint);
|
morning_brief_enabled,
|
||||||
store.sentTomorrowBriefDates.delete(endpoint);
|
tomorrow_brief_enabled,
|
||||||
}
|
latitude,
|
||||||
|
longitude,
|
||||||
export function getPushSubscriptions() {
|
location_name,
|
||||||
return Array.from(getStore().subscriptions.values());
|
county_teryt,
|
||||||
}
|
temperature_unit,
|
||||||
|
wind_speed_unit,
|
||||||
export function getPushSubscription(endpoint: string) {
|
created_at,
|
||||||
return getStore().subscriptions.get(endpoint) ?? null;
|
updated_at
|
||||||
}
|
) VALUES (
|
||||||
|
@endpoint,
|
||||||
export function hasSentWarning(endpoint: string, warningId: string) {
|
@subscriptionJson,
|
||||||
return getStore().sentWarningIds.get(endpoint)?.has(warningId) ?? false;
|
@province,
|
||||||
}
|
@language,
|
||||||
|
@enabled,
|
||||||
export function markWarningSent(endpoint: string, warningId: string) {
|
@morningBriefEnabled,
|
||||||
const store = getStore();
|
@tomorrowBriefEnabled,
|
||||||
const sentIds = store.sentWarningIds.get(endpoint) ?? new Set<string>();
|
@latitude,
|
||||||
sentIds.add(warningId);
|
@longitude,
|
||||||
store.sentWarningIds.set(endpoint, sentIds);
|
@locationName,
|
||||||
}
|
@countyTeryt,
|
||||||
|
@temperatureUnit,
|
||||||
export function pruneSentWarnings(activeWarningIds: Set<string>) {
|
@windSpeedUnit,
|
||||||
getStore().sentWarningIds.forEach((sentIds) => {
|
@createdAt,
|
||||||
sentIds.forEach((warningId) => {
|
@updatedAt
|
||||||
if (!activeWarningIds.has(warningId)) sentIds.delete(warningId);
|
)
|
||||||
});
|
ON CONFLICT(endpoint) DO UPDATE SET
|
||||||
|
subscription_json = excluded.subscription_json,
|
||||||
|
province = excluded.province,
|
||||||
|
language = excluded.language,
|
||||||
|
enabled = excluded.enabled,
|
||||||
|
morning_brief_enabled = excluded.morning_brief_enabled,
|
||||||
|
tomorrow_brief_enabled = excluded.tomorrow_brief_enabled,
|
||||||
|
latitude = excluded.latitude,
|
||||||
|
longitude = excluded.longitude,
|
||||||
|
location_name = excluded.location_name,
|
||||||
|
county_teryt = excluded.county_teryt,
|
||||||
|
temperature_unit = excluded.temperature_unit,
|
||||||
|
wind_speed_unit = excluded.wind_speed_unit,
|
||||||
|
updated_at = excluded.updated_at
|
||||||
|
`).run({
|
||||||
|
endpoint: subscription.endpoint,
|
||||||
|
subscriptionJson: JSON.stringify(subscription.subscription),
|
||||||
|
province: subscription.province,
|
||||||
|
language: subscription.language,
|
||||||
|
enabled: subscription.enabled ? 1 : 0,
|
||||||
|
morningBriefEnabled: subscription.morningBriefEnabled ? 1 : 0,
|
||||||
|
tomorrowBriefEnabled: subscription.tomorrowBriefEnabled ? 1 : 0,
|
||||||
|
latitude: subscription.latitude,
|
||||||
|
longitude: subscription.longitude,
|
||||||
|
locationName: subscription.locationName,
|
||||||
|
countyTeryt: subscription.countyTeryt,
|
||||||
|
temperatureUnit: subscription.temperatureUnit,
|
||||||
|
windSpeedUnit: subscription.windSpeedUnit,
|
||||||
|
createdAt: subscription.createdAt,
|
||||||
|
updatedAt: subscription.updatedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removePushSubscription(endpoint: string) {
|
||||||
|
getDatabase().prepare("DELETE FROM push_subscriptions WHERE endpoint = ?").run(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPushSubscriptions() {
|
||||||
|
return getDatabase()
|
||||||
|
.prepare("SELECT * FROM push_subscriptions ORDER BY created_at ASC")
|
||||||
|
.all()
|
||||||
|
.map((row) => parseSubscription(row as PushSubscriptionRow))
|
||||||
|
.filter((subscription): subscription is WarningPushSubscription => subscription !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPushSubscription(endpoint: string) {
|
||||||
|
const row = getDatabase().prepare("SELECT * FROM push_subscriptions WHERE endpoint = ?").get(endpoint);
|
||||||
|
return row ? parseSubscription(row as PushSubscriptionRow) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasSentWarning(endpoint: string, warningId: string) {
|
||||||
|
const row = getDatabase()
|
||||||
|
.prepare("SELECT 1 FROM sent_warnings WHERE endpoint = ? AND warning_id = ?")
|
||||||
|
.get(endpoint, warningId);
|
||||||
|
return Boolean(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markWarningSent(endpoint: string, warningId: string) {
|
||||||
|
getDatabase()
|
||||||
|
.prepare("INSERT OR IGNORE INTO sent_warnings (endpoint, warning_id, sent_at) VALUES (?, ?, ?)")
|
||||||
|
.run(endpoint, warningId, new Date().toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pruneSentWarnings(activeWarningIds: Set<string>) {
|
||||||
|
const database = getDatabase();
|
||||||
|
if (!activeWarningIds.size) {
|
||||||
|
database.prepare("DELETE FROM sent_warnings").run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
database.transaction((warningIds: string[]) => {
|
||||||
|
const placeholders = warningIds.map(() => "?").join(", ");
|
||||||
|
database.prepare(`DELETE FROM sent_warnings WHERE warning_id NOT IN (${placeholders})`).run(...warningIds);
|
||||||
|
})(Array.from(activeWarningIds));
|
||||||
|
}
|
||||||
|
|
||||||
export function hasSentMorningBrief(endpoint: string, dateKey: string) {
|
export function hasSentMorningBrief(endpoint: string, dateKey: string) {
|
||||||
return getStore().sentMorningBriefDates.get(endpoint)?.has(dateKey) ?? false;
|
const row = getDatabase()
|
||||||
|
.prepare("SELECT 1 FROM sent_morning_briefs WHERE endpoint = ? AND date_key = ?")
|
||||||
|
.get(endpoint, dateKey);
|
||||||
|
return Boolean(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markMorningBriefSent(endpoint: string, dateKey: string) {
|
export function markMorningBriefSent(endpoint: string, dateKey: string) {
|
||||||
const store = getStore();
|
getDatabase()
|
||||||
const sentDates = store.sentMorningBriefDates.get(endpoint) ?? new Set<string>();
|
.prepare("INSERT OR IGNORE INTO sent_morning_briefs (endpoint, date_key, sent_at) VALUES (?, ?, ?)")
|
||||||
sentDates.add(dateKey);
|
.run(endpoint, dateKey, new Date().toISOString());
|
||||||
store.sentMorningBriefDates.set(endpoint, sentDates);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasSentTomorrowBrief(endpoint: string, dateKey: string) {
|
export function hasSentTomorrowBrief(endpoint: string, dateKey: string) {
|
||||||
return getStore().sentTomorrowBriefDates.get(endpoint)?.has(dateKey) ?? false;
|
const row = getDatabase()
|
||||||
|
.prepare("SELECT 1 FROM sent_tomorrow_briefs WHERE endpoint = ? AND date_key = ?")
|
||||||
|
.get(endpoint, dateKey);
|
||||||
|
return Boolean(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markTomorrowBriefSent(endpoint: string, dateKey: string) {
|
export function markTomorrowBriefSent(endpoint: string, dateKey: string) {
|
||||||
const store = getStore();
|
getDatabase()
|
||||||
const sentDates = store.sentTomorrowBriefDates.get(endpoint) ?? new Set<string>();
|
.prepare("INSERT OR IGNORE INTO sent_tomorrow_briefs (endpoint, date_key, sent_at) VALUES (?, ?, ?)")
|
||||||
sentDates.add(dateKey);
|
.run(endpoint, dateKey, new Date().toISOString());
|
||||||
store.sentTomorrowBriefDates.set(endpoint, sentDates);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { NextConfig } from "next";
|
|||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
allowedDevOrigins: ["192.168.0.174"],
|
allowedDevOrigins: ["192.168.0.174"],
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
serverExternalPackages: ["better-sqlite3"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
405
package-lock.json
generated
405
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.80.0",
|
"@tanstack/react-query": "^5.80.0",
|
||||||
|
"better-sqlite3": "^12.10.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^12.0.0",
|
"framer-motion": "^12.0.0",
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
@@ -1451,6 +1453,16 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/better-sqlite3": {
|
||||||
|
"version": "7.6.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
||||||
|
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/d3-array": {
|
"node_modules/@types/d3-array": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||||
@@ -2585,6 +2597,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.10.33",
|
"version": "2.10.33",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
|
||||||
@@ -2597,6 +2629,20 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/better-sqlite3": {
|
||||||
|
"version": "12.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz",
|
||||||
|
"integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"prebuild-install": "^7.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
@@ -2610,6 +2656,26 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bl": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer": "^5.5.0",
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"readable-stream": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bn.js": {
|
"node_modules/bn.js": {
|
||||||
"version": "4.12.3",
|
"version": "4.12.3",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz",
|
||||||
@@ -2674,6 +2740,30 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "5.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||||
|
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.1.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer-equal-constant-time": {
|
"node_modules/buffer-equal-constant-time": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
@@ -2825,6 +2915,12 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chownr": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/class-variance-authority": {
|
"node_modules/class-variance-authority": {
|
||||||
"version": "0.7.1",
|
"version": "0.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||||
@@ -3136,6 +3232,30 @@
|
|||||||
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/decompress-response": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-response": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/deep-extend": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -3184,7 +3304,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@@ -3254,6 +3373,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/end-of-stream": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"once": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-abstract": {
|
"node_modules/es-abstract": {
|
||||||
"version": "1.24.2",
|
"version": "1.24.2",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
|
||||||
@@ -3896,6 +4024,15 @@
|
|||||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/expand-template": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||||
|
"license": "(MIT OR WTFPL)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-deep-equal": {
|
"node_modules/fast-deep-equal": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
@@ -3970,6 +4107,12 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -4078,6 +4221,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fs-constants": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -4224,6 +4373,12 @@
|
|||||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/github-from-package": {
|
||||||
|
"version": "0.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
|
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/glob-parent": {
|
"node_modules/glob-parent": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
@@ -4413,6 +4568,26 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -4466,6 +4641,12 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/ini": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/internal-slot": {
|
"node_modules/internal-slot": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
||||||
@@ -5214,6 +5395,18 @@
|
|||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mimic-response": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimalistic-assert": {
|
"node_modules/minimalistic-assert": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
|
||||||
@@ -5242,6 +5435,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mkdirp-classic": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/motion-dom": {
|
"node_modules/motion-dom": {
|
||||||
"version": "12.40.0",
|
"version": "12.40.0",
|
||||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz",
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz",
|
||||||
@@ -5293,6 +5492,12 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/napi-build-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/napi-postinstall": {
|
"node_modules/napi-postinstall": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
|
||||||
@@ -5397,6 +5602,18 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-abi": {
|
||||||
|
"version": "3.92.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz",
|
||||||
|
"integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^7.3.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-exports-info": {
|
"node_modules/node-exports-info": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
|
||||||
@@ -5579,6 +5796,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/once": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -5921,6 +6147,33 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/prebuild-install": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||||
|
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.0",
|
||||||
|
"expand-template": "^2.0.3",
|
||||||
|
"github-from-package": "0.0.0",
|
||||||
|
"minimist": "^1.2.3",
|
||||||
|
"mkdirp-classic": "^0.5.3",
|
||||||
|
"napi-build-utils": "^2.0.0",
|
||||||
|
"node-abi": "^3.3.0",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"rc": "^1.2.7",
|
||||||
|
"simple-get": "^4.0.0",
|
||||||
|
"tar-fs": "^2.0.0",
|
||||||
|
"tunnel-agent": "^0.6.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prebuild-install": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -5943,6 +6196,16 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pump": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
@@ -5974,6 +6237,30 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/rc": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||||
|
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||||
|
"dependencies": {
|
||||||
|
"deep-extend": "^0.6.0",
|
||||||
|
"ini": "~1.3.0",
|
||||||
|
"minimist": "^1.2.0",
|
||||||
|
"strip-json-comments": "~2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rc": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rc/node_modules/strip-json-comments": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "19.2.6",
|
"version": "19.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||||
@@ -6034,6 +6321,20 @@
|
|||||||
"pify": "^2.3.0"
|
"pify": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
@@ -6312,7 +6613,6 @@
|
|||||||
"version": "7.8.1",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -6514,6 +6814,51 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-concat": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/simple-get": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
@@ -6544,6 +6889,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string.prototype.includes": {
|
"node_modules/string.prototype.includes": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
|
||||||
@@ -6852,6 +7206,34 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tar-fs": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chownr": "^1.1.1",
|
||||||
|
"mkdirp-classic": "^0.5.2",
|
||||||
|
"pump": "^3.0.0",
|
||||||
|
"tar-stream": "^2.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tar-stream": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bl": "^4.0.3",
|
||||||
|
"end-of-stream": "^1.4.1",
|
||||||
|
"fs-constants": "^1.0.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"readable-stream": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/thenify": {
|
"node_modules/thenify": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
@@ -6981,6 +7363,18 @@
|
|||||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/tunnel-agent": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||||
@@ -7228,7 +7622,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/victory-vendor": {
|
"node_modules/victory-vendor": {
|
||||||
@@ -7387,6 +7780,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrappy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/react-query": "^5.80.0",
|
"@tanstack/react-query": "^5.80.0",
|
||||||
|
"better-sqlite3": "^12.10.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"framer-motion": "^12.0.0",
|
"framer-motion": "^12.0.0",
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
"zustand": "^5.0.3"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user