diff --git a/.github/workflows/sample_firebase.yml b/.github/workflows/sample_firebase.yml index 7e4e923..2d807cc 100644 --- a/.github/workflows/sample_firebase.yml +++ b/.github/workflows/sample_firebase.yml @@ -7,11 +7,17 @@ on: env: ACTIONS_INSTALL_COMMAND: pnpm install - ACTIONS_BUILD_COMMAND: pnpm run build + ACTIONS_BUILD_COMMAND: npm run build ACTIONS_BUILD_DIST: dist ACTIONS_LOCK_FILE: pnpm-lock.yaml NEXT_PUBLIC_SITE_ORIGIN: https://example.com + NEXT_PUBLIC_FIREBASE_API_KEY: "XXXXXXXXXXXXXXXXX" + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: "xxxxxxxxxx.firebaseapp.com" + NEXT_PUBLIC_FIREBASE_PROJECT_ID: "xxxxxxxxxx" + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: "xxxxxxxxxx.firebasestorage.app" + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: "000000000000" + NEXT_PUBLIC_FIREBASE_APP_ID: "0:000000000000:xxx:0000000000000000000000" permissions: id-token: write @@ -45,6 +51,12 @@ jobs: - name: install dependencies run: ${{ env.ACTIONS_INSTALL_COMMAND }} + - name: install functions dependencies + run: pnpm install --prefix functions + + - name: lint + run: npx eslint --ext .ts,.tsx,.js src + - name: build run: ${{ env.ACTIONS_BUILD_COMMAND }} diff --git a/.gitignore b/.gitignore index 5ae1a0f..c9d8562 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ node_modules .DS_Store next-env.d.ts dist +.env.local *.tsbuildinfo -.env*.local +.firebase diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2ce0b60 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,50 @@ +# CLAUDE.md + +## モノレポ構成 + +``` +packages/ + core/ # Firebase非依存の共通型・ユーティリティ(@hinagata-next/core) + web/ # Next.js Webアプリ(@hinagata-next/web) + functions/ # Firebase Cloud Functions(@hinagata-next/functions) +``` + +## Import + +- `packages/web/` 内のパスエイリアスは `~/`(`packages/web/src/` へのマップ) +- `packages/functions/` 内のパスエイリアスは `~/`(`packages/functions/src/` へのマップ) +- core パッケージへのアクセスは `@hinagata-next/core/common/...` または `@hinagata-next/core/feature/...` +- 相対 import(`./`, `../`)は禁止 + +## Code style + +- Prettier: trailingComma=none, arrowParens=avoid +- コンポーネントは arrow function のみ(`function Foo()` は使わない) + +## Styling + +- Emotion(`@emotion/styled`, `@emotion/react`)を使う +- Tailwind・CSS Modules は使わない + +## Next.js + +- `output: "export"`(静的エクスポート)のため SSR 系 API(`cookies()`, `headers()`, Server Actions など)は使えない +- バックエンド処理は Firebase Cloud Functions(`requestAppCallable` で呼ぶ) + +## Firestore + +- Firestore SDK を直接使わず、必ず `ClientDataStoreAgent`(`~/common/ClientDataStoreAgent`)経由でアクセスする +- スキーマ定義は `@hinagata-next/core/feature/app-data-store-scheme.ts` に `DataStoreScheme` として追加する + - `parse` 関数を scheme に持たせる(`withConverter` は `DataStoreAgent` が内部的に使用) + - 型定義ファイル(`@hinagata-next/core/feature/*.ts`)に対応する `parseXxx` 関数も合わせて定義する + - サブコレクションは `parentCollection` で親 scheme を参照して階層を表現する +- `ClientDataStoreAgent` インスタンスはコンポーネントの外(モジュールレベル)で生成する +- React コンポーネントからのリアルタイム購読は `~/common/database-common-hooks` のフックを使う + - 単一ドキュメント: `useDataStoreSingleItem` + - コレクション: `useDataStoreList`(`QueryFormula[]` でクエリを渡す) + - コレクショングループ: `useDataStoreGroupList` +- エラーハンドリングは `extractFirebaseError`(`~/common/FirebaseErrorParameter`)で `FirebaseErrorParameter` に変換する + +## Utilities + +- 配列・文字列・日付などの操作は `@hinagata-next/core/common/*-util` に既存関数がないか先に確認する diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..931c461 --- /dev/null +++ b/firebase.json @@ -0,0 +1,36 @@ +{ + "firestore": { + "database": "(default)", + "location": "asia-northeast1", + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "functions": [ + { + "source": "packages/functions", + "codebase": "default", + "disallowLegacyRuntimeConfig": true, + "runtime": "nodejs22", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log", + "*.local" + ], + "predeploy": [ + "npm --prefix \"$RESOURCE_DIR\" run make-index", + "npm --prefix \"$RESOURCE_DIR\" run lint", + "npm --prefix \"$RESOURCE_DIR\" run build" + ] + } + ], + "hosting": { + "public": "dist", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + } +} \ No newline at end of file diff --git a/firestore.indexes.json b/firestore.indexes.json new file mode 100644 index 0000000..0e6de7c --- /dev/null +++ b/firestore.indexes.json @@ -0,0 +1,51 @@ +{ + // Example (Standard Edition): + // + // "indexes": [ + // { + // "collectionGroup": "widgets", + // "queryScope": "COLLECTION", + // "fields": [ + // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, + // { "fieldPath": "bar", "mode": "DESCENDING" } + // ] + // }, + // + // "fieldOverrides": [ + // { + // "collectionGroup": "widgets", + // "fieldPath": "baz", + // "indexes": [ + // { "order": "ASCENDING", "queryScope": "COLLECTION" } + // ] + // }, + // ] + // ] + // + // Example (Enterprise Edition): + // + // "indexes": [ + // { + // "collectionGroup": "reviews", + // "queryScope": "COLLECTION_GROUP", + // "apiScope": "MONGODB_COMPATIBLE_API", + // "density": "DENSE", + // "multikey": false, + // "fields": [ + // { "fieldPath": "baz", "mode": "ASCENDING" } + // ] + // }, + // { + // "collectionGroup": "items", + // "queryScope": "COLLECTION_GROUP", + // "apiScope": "MONGODB_COMPATIBLE_API", + // "density": "SPARSE_ANY", + // "multikey": true, + // "fields": [ + // { "fieldPath": "baz", "mode": "ASCENDING" } + // ] + // }, + // ] + "indexes": [], + "fieldOverrides": [] +} \ No newline at end of file diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..e70cf83 --- /dev/null +++ b/firestore.rules @@ -0,0 +1,10 @@ +rules_version='2' + +service cloud.firestore { + match /databases/{database}/documents { + match /profiles/{userId} { + allow read: if true; + allow write: if request.auth.uid == userId; + } + } +} diff --git a/packages/core/src/common/CommonPermission.ts b/packages/core/src/common/CommonPermission.ts new file mode 100644 index 0000000..417682f --- /dev/null +++ b/packages/core/src/common/CommonPermission.ts @@ -0,0 +1,10 @@ +import { parseBoolean, parseObject } from "@hinagata-next/core/common/parser-helper"; + +type CommonPermission = { valid: boolean }; + +export const parseCommonPermission = (src: unknown) => + parseObject(src, ({ valid }) => ({ + valid: parseBoolean(valid) + })); + +export default CommonPermission; diff --git a/packages/core/src/common/DataStoreAgent.ts b/packages/core/src/common/DataStoreAgent.ts new file mode 100644 index 0000000..0804095 --- /dev/null +++ b/packages/core/src/common/DataStoreAgent.ts @@ -0,0 +1,362 @@ +import { compact } from "@hinagata-next/core/common/array-util"; + +// NOTE: client/server両方の DocumentSnapshot を満たす型 +export type DocumentSnapshotMock = { + id: string; + ref: { path: string }; + data: () => T | undefined; +}; + +// NOTE: client/server両方の DocumentReference を満たす型 +type DocumentReferenceMock = { + id: string; +}; + +type QuerySnapshotMock = { + docs: DocumentSnapshotMock[]; +}; + +export type TypedCollectionList = { id: string; data: T }[]; + +type TypedCollectionGroupList = { + fullPath: string; + ids: string[]; + data: T; +}[]; + +export type DataStoreScheme< + T, + D extends string, + C extends string | never = never +> = { + name: string; + documentKey: D; + parse: (src: unknown) => T; + parentCollection?: DataStoreScheme; +}; + +const collectionPathFromParent = ( + s: DataStoreScheme, + o: Record +): string[] => [ + ...(s.parentCollection + ? collectionPathFromParent(s.parentCollection, o) + : []), + s.name, + o[s.documentKey] +]; + +const calcCollectionPath = ( + s: DataStoreScheme, + m: Record +) => + [ + ...(s.parentCollection + ? collectionPathFromParent(s.parentCollection, m) + : []), + s.name + ].join("/"); + +type WhereFilterOp = + | "<" + | "<=" + | "==" + | "!=" + | ">=" + | ">" + | "array-contains" + | "in" + | "array-contains-any" + | "not-in"; + +type OrderByDirection = "asc" | "desc"; + +export type QueryFormula = + | ["where", keyof T, WhereFilterOp, unknown] + | ["equal", keyof T, unknown] + | ["orderBy", keyof T, OrderByDirection] + | ["limit", number]; + +export abstract class DataStoreAgent< + T extends object, + D extends string, + C extends string | never, + Dr extends DocumentReferenceMock, + Cr +> { + public readonly scheme: DataStoreScheme; + + public constructor(scheme: DataStoreScheme) { + this.scheme = scheme; + } + + public get converter(): { + toFirestore: (d: T) => T; + fromFirestore: (s: DocumentSnapshotMock) => T; + } { + return { + toFirestore: (d: T) => d, + fromFirestore: s => this.scheme.parse(s.data()) + }; + } + + private calcCollectionParam(opts: Record) { + return { + collectionPath: calcCollectionPath(this.scheme, opts) + }; + } + + private calcDocParam(opts: Record) { + return { + ...this.calcCollectionParam(opts), + id: opts[this.scheme.documentKey] + }; + } + + protected parseCollectionSnapshot( + docs: DocumentSnapshotMock[] + ): TypedCollectionList { + return compact( + docs.map(d => { + const dd = d.data(); + return dd + ? { + id: d.id, + data: dd + } + : null; + }) + ); + } + + protected parseCollectionGroupSnapshot( + docs: DocumentSnapshotMock[] + ): TypedCollectionGroupList { + return compact( + docs.map(d => { + const dd = d.data(); + return dd + ? { + fullPath: d.ref.path, + ids: d.ref.path + .split(/\//g) + .reduce< + string[] + >((prev, curr, i) => (i % 2 === 0 ? prev : [...prev, curr]), []), + data: dd + } + : null; + }) + ); + } + + protected abstract collectionReference(args: { collectionPath: string }): Cr; + + protected abstract collectionGroupReference(): Cr; + + protected abstract documentReference(args: { + collectionPath: string; + id?: string; + }): Dr; + + protected newDocId({ collectionPath }: { collectionPath: string }) { + const documentRef = this.documentReference({ collectionPath }); + return documentRef.id; + } + + protected abstract setDoc(args: { + ref: Dr; + data: Object; + merge?: boolean; + }): Promise; + + protected abstract getDoc(r: Dr): Promise>; + + protected abstract deleteDoc(r: Dr): Promise; + + protected abstract getQueryDocs(r: Cr): Promise>; + + protected abstract getQueryCount(r: Cr): Promise; + + protected abstract subscribeDoc(args: { + ref: Dr; + handler: (d: DocumentSnapshotMock) => void; + onError: (e: unknown) => void; + }): () => void; + + protected abstract subscribeQueryDocs(args: { + ref: Cr; + handler: (l: DocumentSnapshotMock[]) => void; + onError: (e: unknown) => void; + }): () => void; + + protected abstract applyQueryFormula(ref: Cr, query?: QueryFormula[]): Cr; + + public newItemId(opts: Record) { + return this.newDocId({ + collectionPath: calcCollectionPath(this.scheme, opts) + }); + } + + public singleItemReference(opts: Record): Dr { + return this.documentReference(this.calcDocParam(opts)); + } + + private singleNewItemReference(opts: Record): Dr { + return this.documentReference(this.calcCollectionParam(opts)); + } + + private listQueryReference( + opts: Record, + query?: QueryFormula[] + ): Cr { + return this.applyQueryFormula( + this.collectionReference(this.calcCollectionParam(opts)), + query + ); + } + + private groupQueryReference(query?: QueryFormula[]) { + return this.applyQueryFormula(this.collectionGroupReference(), query); + } + + public async fetchItem(opts: Record): Promise { + return this.getDoc(this.singleItemReference(opts)).then(s => s.data()); + } + + public setItem( + opts: Record & { + data: T; + merge?: boolean; + } + ) { + const { data, merge } = opts; + const ref = this.singleItemReference(opts); + return this.setDoc({ + ref, + data, + merge + }).then(() => ref.id); + } + + public addItem(opts: Record & { data: T }) { + const { data } = opts; + const ref = this.singleNewItemReference(opts); + return this.setDoc({ + ref, + data + }).then(() => ref.id); + } + + public mergeItem(opts: Record & { data: Partial }) { + const { data } = opts; + const ref = this.singleItemReference(opts); + return this.setDoc({ + ref, + data: data as Object, + merge: true + }).then(() => ref.id); + } + + public deleteItem(opts: Record) { + return this.deleteDoc(this.singleItemReference(opts)); + } + + public subscribeItem( + opts: Record & { + handler: (d: T | undefined) => void; + onError: (e: unknown) => void; + } + ) { + const { handler, onError } = opts; + return this.subscribeDoc({ + ref: this.singleItemReference(opts), + handler: snapshot => handler(snapshot.data()), + onError + }); + } + + public async fetchList( + opts: Record & { query?: QueryFormula[] } + ) { + const { query } = opts; + const ref = this.listQueryReference(opts, query); + const snapshot = await this.getQueryDocs(ref); + return this.parseCollectionSnapshot(snapshot.docs); + } + + public async fetchListCount( + opts: Record & { query?: QueryFormula[] } + ) { + const { query } = opts; + const ref = this.listQueryReference(opts, query); + const count = await this.getQueryCount(ref); + return count; + } + + public async fetchGroupList(query?: QueryFormula[]) { + const ref = this.groupQueryReference(query); + const snapshot = await this.getQueryDocs(ref); + return this.parseCollectionGroupSnapshot(snapshot.docs); + } + + public fetchGroupListCount(query?: QueryFormula[]) { + const ref = this.groupQueryReference(query); + return this.getQueryCount(ref); + } + + public subscribeList( + opts: Record & { + query?: QueryFormula[]; + handler: (l: TypedCollectionList) => void; + onError: (e: unknown) => void; + } + ) { + const { query, handler, onError } = opts; + return this.subscribeQueryDocs({ + ref: this.listQueryReference(opts, query), + handler: docs => handler(this.parseCollectionSnapshot(docs)), + onError + }); + } + + public subscribeGroupList({ + query, + handler, + onError + }: { + query?: QueryFormula[]; + handler: (l: { fullPath: string; ids: string[]; data: T }[]) => void; + onError: (e: unknown) => void; + }) { + return this.subscribeQueryDocs({ + ref: this.groupQueryReference(query), + handler: docs => handler(this.parseCollectionGroupSnapshot(docs)), + onError + }); + } +} + +export interface TransactionGetStepParams< + Dr extends DocumentReferenceMock, + Cr +> { + get: ( + s: DataStoreAgent, + o: Record + ) => Promise; +} + +export interface TransactionSetStepParams< + Dr extends DocumentReferenceMock, + Cr +> { + set: ( + s: DataStoreAgent, + args: Parameters["setItem"]>[0] + ) => void; + delete: ( + s: DataStoreAgent, + args: Parameters["deleteItem"]>[0] + ) => void; +} diff --git a/packages/core/src/feature/AppCallableScheme.ts b/packages/core/src/feature/AppCallableScheme.ts new file mode 100644 index 0000000..bcbcddd --- /dev/null +++ b/packages/core/src/feature/AppCallableScheme.ts @@ -0,0 +1,25 @@ +export const COMMON_CALLABLE_REGION = "asia-northeast1"; + +export type CommonNGResponse = { + case: "ng"; + error: string; +}; + +export type OKResponse = { + case: "ok"; + data: T; +}; + +type AppCallableFunctionScheme = { + Request: Req; + Response: Res; +}; + +type AppCallableScheme = { + translateWithApi: AppCallableFunctionScheme< + { jaWord: string }, + OKResponse<{ enWord: string }> | CommonNGResponse + >; +}; + +export default AppCallableScheme; diff --git a/packages/core/src/feature/DummyProfile.ts b/packages/core/src/feature/DummyProfile.ts index 6dd4ba9..d81a508 100644 --- a/packages/core/src/feature/DummyProfile.ts +++ b/packages/core/src/feature/DummyProfile.ts @@ -1,4 +1,5 @@ import { + parseArray, parseObject, parseString } from "@hinagata-next/core/common/parser-helper"; @@ -17,4 +18,11 @@ export const parseDummyProfileLink = (src: unknown) => url: parseString(url) })); +export const parseDummyProfile = (src: unknown) => + parseObject(src, ({ name, email, profileLinks }) => ({ + name: parseString(name), + email: parseString(email), + profileLinks: parseArray(profileLinks, parseDummyProfileLink) + })); + export default DummyProfile; diff --git a/packages/core/src/feature/app-data-store-scheme.ts b/packages/core/src/feature/app-data-store-scheme.ts new file mode 100644 index 0000000..4269b61 --- /dev/null +++ b/packages/core/src/feature/app-data-store-scheme.ts @@ -0,0 +1,10 @@ +import { type DataStoreScheme } from "@hinagata-next/core/common/DataStoreAgent"; +import type DummyProfile from "@hinagata-next/core/feature/DummyProfile"; +import { parseDummyProfile } from "@hinagata-next/core/feature/DummyProfile"; + +// eslint-disable-next-line import/prefer-default-export +export const profileDataStoreScheme: DataStoreScheme = { + name: "profiles", + parse: parseDummyProfile, + documentKey: "userId" +}; diff --git a/packages/functions/.gitignore b/packages/functions/.gitignore new file mode 100644 index 0000000..22b5a0e --- /dev/null +++ b/packages/functions/.gitignore @@ -0,0 +1,13 @@ +# Compiled JavaScript files +/lib +/dist + +# TypeScript v1 declaration files +typings/ + +# Node.js dependency directory +node_modules/ + +/src/sync +/src/index.ts +serviceAccountKey.*.json diff --git a/packages/functions/eslint.config.mjs b/packages/functions/eslint.config.mjs new file mode 100644 index 0000000..cd6ff92 --- /dev/null +++ b/packages/functions/eslint.config.mjs @@ -0,0 +1,68 @@ +import js from "@eslint/js"; +import tseslint from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; +import importPlugin from "eslint-plugin-import"; +import eslintConfigPrettier from "eslint-config-prettier"; +import globals from "globals"; +import { sharedRules } from "../../eslint.shared.mjs"; + +export default [ + { + ignores: ["lib/**", "node_modules/**", "src/index.ts"] + }, + { + files: ["src/**/*.ts"], + plugins: { + "@typescript-eslint": tseslint, + import: importPlugin + }, + languageOptions: { + parser: tsParser, + parserOptions: { + project: "./tsconfig.json", + sourceType: "module" + }, + globals: { + ...globals.node + } + }, + rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommended.rules, + ...sharedRules, + "no-shadow": 1, + "no-param-reassign": 1, + "import/no-extraneous-dependencies": 0, + "import/order": [ + "error", + { + pathGroupsExcludedImportTypes: [], + pathGroups: [ + { pattern: "@hinagata-next/core/common/**", group: "internal", position: "after" }, + { pattern: "@hinagata-next/core/feature/**", group: "internal", position: "after" }, + { pattern: "~/common/**", group: "internal", position: "after" }, + { pattern: "~/feature/**", group: "internal", position: "after" } + ] + } + ] + } + }, + + // common 層から feature 層の import を禁止 + { + files: ["src/common/**/*.ts"], + rules: { + "no-restricted-imports": [ + "error", + { + patterns: [ + { group: ["./", "../"] }, + { group: ["~/feature/**"], message: "common から feature は import 不可。" } + ] + } + ] + } + }, + + eslintConfigPrettier +]; diff --git a/packages/functions/package.json b/packages/functions/package.json new file mode 100644 index 0000000..21a17e1 --- /dev/null +++ b/packages/functions/package.json @@ -0,0 +1,39 @@ +{ + "name": "@hinagata-next/functions", + "scripts": { + "lint": "eslint --fix ./src", + "build": "tsc && tsc-alias", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log", + "make-index": "npx tsx scripts/make-index.ts" + }, + "main": "lib/functions/src/index.js", + "dependencies": { + "@google-cloud/functions-framework": "^5.0.2", + "@hinagata-next/core": "workspace:*", + "cors": "^2.8.5", + "express": "^4.17.1", + "firebase-admin": "^12.2.0", + "firebase-functions": "^4.1.1", + "mkdirp": "^1.0.4" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/cors": "^2.8.7", + "@types/node": "^20.14.10", + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.29.1", + "firebase-functions-test": "^0.2.0", + "globals": "^17.5.0", + "tsc-alias": "^1.8.10", + "tsx": "^4.21.0", + "typescript": "^5.7.3" + }, + "private": true +} diff --git a/packages/functions/scripts/make-index.ts b/packages/functions/scripts/make-index.ts new file mode 100644 index 0000000..1c1cc38 --- /dev/null +++ b/packages/functions/scripts/make-index.ts @@ -0,0 +1,21 @@ +import { readdir, writeFile } from "fs/promises"; +import { relative, join, basename } from "path"; + +const SRC_ROOT = "./src"; +const MODULE_ROOT = join(SRC_ROOT, "module"); + +async function main() { + const dest = join(SRC_ROOT, "index.ts"); + const files = await readdir(MODULE_ROOT); + const body = files + .filter(name => /\.ts$/.test(name)) + .map(name => { + const b = basename(name, ".ts"); + const p = relative(SRC_ROOT, join(MODULE_ROOT, b)); + return `if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === "${b}") { exports.${b} = require("./${p}").default; }`; + }) + .join("\n"); + await writeFile(dest, body, { encoding: "utf8" }); +} + +main(); diff --git a/packages/functions/src/common/ServerDataStoreAgent.ts b/packages/functions/src/common/ServerDataStoreAgent.ts new file mode 100644 index 0000000..b24df68 --- /dev/null +++ b/packages/functions/src/common/ServerDataStoreAgent.ts @@ -0,0 +1,150 @@ +import { + type DocumentReference, + type Query, + type Firestore, + type PartialWithFieldValue +} from "firebase-admin/firestore"; +import { + DataStoreAgent, + type DocumentSnapshotMock, + type TransactionGetStepParams, + type TransactionSetStepParams, + type DataStoreScheme, + type QueryFormula +} from "@hinagata-next/core/common/DataStoreAgent"; +import { parseString } from "@hinagata-next/core/common/parser-helper"; + +export class ServerDataStoreAgent< + T extends object, + D extends string, + C extends string +> extends DataStoreAgent { + private adapter: () => Firestore; + + public constructor( + adapter: () => Firestore, + scheme: DataStoreScheme + ) { + super(scheme); + this.adapter = adapter; + } + + protected collectionReference({ + collectionPath + }: { + collectionPath: string; + }) { + return this.adapter() + .collection(collectionPath) + .withConverter(this.converter); + } + + protected collectionGroupReference() { + return this.adapter() + .collectionGroup(this.scheme.name) + .withConverter(this.converter); + } + + protected documentReference({ + collectionPath, + id + }: { + collectionPath: string; + id?: string; + }) { + const collectionRef = this.collectionReference({ collectionPath }); + return id ? collectionRef.doc(id) : collectionRef.doc(); + } + + protected async setDoc({ + ref, + data, + merge + }: { + ref: DocumentReference; + data: PartialWithFieldValue; + merge?: boolean; + }) { + await ref.withConverter(this.converter).set(data, { merge }); + } + + protected getDoc(r: DocumentReference) { + return r.withConverter(this.converter).get(); + } + + protected async deleteDoc(r: DocumentReference) { + await r.delete(); + } + + protected getQueryDocs(r: Query) { + return r.withConverter(this.converter).get(); + } + + protected async getQueryCount(r: Query) { + const snapshot = await r.count().get(); + return snapshot.data().count; + } + + protected subscribeDoc({ + ref, + handler, + onError + }: { + ref: DocumentReference; + handler: (d: object | undefined) => void; + onError: (e: unknown) => void; + }) { + return ref.onSnapshot(handler, onError); + } + + protected subscribeQueryDocs({ + ref, + handler, + onError + }: { + ref: Query; + handler: (l: DocumentSnapshotMock[]) => void; + onError: (e: unknown) => void; + }) { + return ref + .withConverter(this.converter) + .onSnapshot(snapshot => handler(snapshot.docs), onError); + } + + protected applyQueryFormula(ref: Query, query: QueryFormula[] = []) { + return query.reduce((prev, l) => { + switch (l[0]) { + case "limit": + return prev.limit(l[1]); + case "orderBy": + return prev.orderBy(parseString(l[1]), l[2]); + case "equal": + return prev.where(parseString(l[1]), "==", l[2]); + default: + return prev.where(parseString(l[1]), l[2], l[3]); + } + }, ref); + } + + public static runTransaction( + adapter: () => Firestore, + getStep: ( + o: TransactionGetStepParams + ) => Promise, + setStep: (p: M, m: TransactionSetStepParams) => R + ) { + return adapter().runTransaction(async t => { + const r = await getStep({ + get: (a, o) => + t + .get(a.singleItemReference(o).withConverter(a.converter)) + .then(s => s.data()) + }); + return setStep(r, { + set: (s, args) => + t.set(s.singleItemReference(args), args.data, { merge: args.merge }), + delete: (s, args) => t.delete(s.singleItemReference(args)) + }); + }); + } +} diff --git a/packages/functions/src/common/firebase-app.ts b/packages/functions/src/common/firebase-app.ts new file mode 100644 index 0000000..e4faa30 --- /dev/null +++ b/packages/functions/src/common/firebase-app.ts @@ -0,0 +1,18 @@ +import { type App, initializeApp } from "firebase-admin/app"; +import { getAuth } from "firebase-admin/auth"; +import { getFirestore } from "firebase-admin/firestore"; +import { getStorage } from "firebase-admin/storage"; +import { config as functionsConfig } from "firebase-functions"; + +let currentApp: App | null = null; + +const getApp = () => { + if (!currentApp) { + currentApp = initializeApp(functionsConfig().firebase); + } + return currentApp; +}; + +export const firebaseAuth = () => getAuth(getApp()); +export const firebaseFirestore = () => getFirestore(getApp()); +export const firebaseStorage = () => getStorage(getApp()); diff --git a/packages/functions/src/common/handleCallable.ts b/packages/functions/src/common/handleCallable.ts new file mode 100644 index 0000000..767f3b9 --- /dev/null +++ b/packages/functions/src/common/handleCallable.ts @@ -0,0 +1,8 @@ +import { https } from "firebase-functions"; +import { type CallableContext } from "firebase-functions/v1/https"; + +const handleCallable = ( + fn: (data: Req, context: CallableContext) => Promise +) => https.onCall(fn); + +export default handleCallable; diff --git a/packages/functions/src/feature/logger.ts b/packages/functions/src/feature/logger.ts new file mode 100644 index 0000000..8a61995 --- /dev/null +++ b/packages/functions/src/feature/logger.ts @@ -0,0 +1,34 @@ +const logWrapper = ( + opts: + | { + severity: "INFO" | "WARNING"; + message: string; + } + | { + severity: "ERROR"; + message: string; + name: string; + stack_trace?: string; + } +) => console.log(JSON.stringify(opts)); + +export const infoLogger = (message: string, data: object = {}) => + logWrapper({ severity: "INFO", message, ...data }); + +export const warnLogger = (message: string, data: object = {}) => + logWrapper({ severity: "WARNING", message, ...data }); + +export const errorLogger = (message: string, err: Error, data: object) => + logWrapper({ + severity: "ERROR", + message, + name: err.name, + stack_trace: err.stack, + ...data + }); + +export const functionRangeLogger = ( + label: "callable", + step: "begin" | "end", + data?: object +) => infoLogger(`[${label}:${step}]`, data); diff --git a/packages/functions/src/feature/responseAppCallable.ts b/packages/functions/src/feature/responseAppCallable.ts new file mode 100644 index 0000000..e643980 --- /dev/null +++ b/packages/functions/src/feature/responseAppCallable.ts @@ -0,0 +1,23 @@ +import { type CallableRequest } from "firebase-functions/v2/https"; +import { parseString } from "@hinagata-next/core/common/parser-helper"; +import type AppCallableScheme from "@hinagata-next/core/feature/AppCallableScheme"; +import { type CommonNGResponse } from "@hinagata-next/core/feature/AppCallableScheme"; +import { functionRangeLogger } from "~/feature/logger"; + +const responseAppCallable = async ( + req: CallableRequest, + handler: ( + r: CallableRequest + ) => Promise +): Promise => { + functionRangeLogger("callable", "begin"); + const res = await handler(req).catch((e): CommonNGResponse => { + console.log("[app error]", JSON.stringify(e)); + console.error(e); + return { case: "ng", error: parseString(e) }; + }); + functionRangeLogger("callable", "end"); + return res; +}; + +export default responseAppCallable; diff --git a/packages/functions/src/feature/secret-manager.ts b/packages/functions/src/feature/secret-manager.ts new file mode 100644 index 0000000..5e409ef --- /dev/null +++ b/packages/functions/src/feature/secret-manager.ts @@ -0,0 +1,8 @@ +import { defineSecret } from "firebase-functions/params"; + +type SECRET_KEYS = "DEEPL_API_KEY"; + +export const getSecretString = (k: SECRET_KEYS) => process.env[k] || ""; + +export const getSecretParams = (...keys: SECRET_KEYS[]) => + keys.map(k => defineSecret(k)); diff --git a/packages/functions/src/module/translateWithApi.ts b/packages/functions/src/module/translateWithApi.ts new file mode 100644 index 0000000..2149c83 --- /dev/null +++ b/packages/functions/src/module/translateWithApi.ts @@ -0,0 +1,34 @@ +import { onCall } from "firebase-functions/v2/https"; +import { COMMON_CALLABLE_REGION } from "@hinagata-next/core/feature/AppCallableScheme"; +import responseAppCallable from "~/feature/responseAppCallable"; +import { getSecretParams, getSecretString } from "~/feature/secret-manager"; + +export default onCall( + { + region: COMMON_CALLABLE_REGION, + secrets: getSecretParams("DEEPL_API_KEY") + }, + req => + responseAppCallable<"translateWithApi">(req, async ({ data }) => { + const { jaWord } = data; + const DEEPL_API_KEY = getSecretString("DEEPL_API_KEY"); + const res = await fetch("https://api-free.deepl.com/v2/translate", { + method: "POST", + headers: { + Authorization: `DeepL-Auth-Key ${DEEPL_API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ text: [jaWord], target_lang: "EN" }) + }); + if (!res.ok) { + throw new Error(`DeepL error: ${res.status}`); + } + const json = (await res.json()) as { + translations: { text: string }[]; + }; + return { + case: "ok", + data: { enWord: json.translations[0].text } + }; + }) +); diff --git a/packages/functions/tsconfig.json b/packages/functions/tsconfig.json new file mode 100644 index 0000000..7550b2e --- /dev/null +++ b/packages/functions/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "resolveJsonModule": true, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017", + "paths": { + "~/*": [ + "./src/*" + ], + "@hinagata-next/core/*": [ + "../core/src/*" + ] + }, + "rootDir": ".." + }, + "compileOnSave": true, + "include": [ + "src" + ] +} diff --git a/packages/web/package.json b/packages/web/package.json index 914aac7..8b3f280 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@emotion/cache": "^11.14.0", + "firebase": "^12.12.1", "@emotion/react": "^11.11.1", "@emotion/serialize": "^1.1.2", "@emotion/styled": "^11.11.0", diff --git a/packages/web/src/app/page.tsx b/packages/web/src/app/page.tsx index 75ccafa..1168e32 100644 --- a/packages/web/src/app/page.tsx +++ b/packages/web/src/app/page.tsx @@ -1,5 +1,5 @@ -import TopScene from "~/component/TopScene"; +import ProfileFormScene from "~/component/_provider/ProfileFormScene"; -const PageIndex = () => ; +const PageProfileForm = () => ; -export default PageIndex; +export default PageProfileForm; diff --git a/packages/web/src/common/ClientDataStoreAgent.ts b/packages/web/src/common/ClientDataStoreAgent.ts new file mode 100644 index 0000000..c16e77b --- /dev/null +++ b/packages/web/src/common/ClientDataStoreAgent.ts @@ -0,0 +1,155 @@ +import { + collection, + doc, + limit, + onSnapshot, + getDocs, + orderBy, + query, + where, + setDoc, + getDoc, + deleteDoc, + collectionGroup, + type Query, + type DocumentReference, + runTransaction, + getCountFromServer +} from "firebase/firestore"; +import { parseString } from "@hinagata-next/core/common/parser-helper"; +import { + DataStoreAgent, + type QueryFormula, + type DocumentSnapshotMock, + type TransactionGetStepParams, + type TransactionSetStepParams +} from "@hinagata-next/core/common/DataStoreAgent"; +import { firebaseFirestore } from "~/common/firebase-app"; + +// eslint-disable-next-line import/prefer-default-export +export class ClientDataStoreAgent< + T extends object, + D extends string, + C extends string +> extends DataStoreAgent { + protected collectionReference({ + collectionPath + }: { + collectionPath: string; + }) { + return collection(firebaseFirestore(), collectionPath); + } + + protected collectionGroupReference() { + return collectionGroup(firebaseFirestore(), this.scheme.name); + } + + protected documentReference({ + collectionPath, + id + }: { + collectionPath: string; + id?: string; + }) { + const collectionRef = this.collectionReference({ collectionPath }); + return id ? doc(collectionRef, id) : doc(collectionRef); + } + + protected override setDoc({ + ref, + data, + merge + }: { + ref: DocumentReference; + data: Object; + merge?: boolean; + }) { + return setDoc(ref, data, { merge }); + } + + protected getDoc(r: DocumentReference) { + return getDoc(r.withConverter(this.converter)); + } + + protected async deleteDoc(r: DocumentReference) { + await deleteDoc(r); + } + + protected getQueryDocs(r: Query) { + return getDocs(r.withConverter(this.converter)); + } + + protected async getQueryCount(r: Query) { + const snapshot = await getCountFromServer(r); + return snapshot.data().count; + } + + protected subscribeDoc({ + ref, + handler, + onError + }: { + ref: DocumentReference; + handler: (d: Object | undefined) => void; + onError: (e: unknown) => void; + }) { + return onSnapshot(ref, handler, onError); + } + + protected subscribeQueryDocs({ + ref, + handler, + onError + }: { + ref: Query; + handler: (l: DocumentSnapshotMock[]) => void; + onError: (e: unknown) => void; + }) { + return onSnapshot( + ref.withConverter(this.converter), + snapshot => handler(snapshot.docs), + onError + ); + } + + protected applyQueryFormula(ref: Query, q: QueryFormula[] = []): Query { + return q.length + ? query( + ref, + ...q.map(l => { + switch (l[0]) { + case "limit": + return limit(l[1]); + case "orderBy": + return orderBy(parseString(l[1]), l[2]); + case "equal": + return where(parseString(l[1]), "==", l[2]); + default: + return where(parseString(l[1]), l[2], l[3]); + } + }) + ) + : ref; + } + + public static runTransaction( + getStep: ( + o: TransactionGetStepParams + ) => Promise, + setStep: (p: M, m: TransactionSetStepParams) => R + ) { + return runTransaction(firebaseFirestore(), async t => { + const r = await getStep({ + get: (a, o) => + t + .get(a.singleItemReference(o).withConverter(a.converter)) + .then(s => s.data()) + }); + return setStep(r, { + set: (s, args) => + t.set(s.singleItemReference(args), args.data, { merge: args.merge }), + delete: (s, args) => t.delete(s.singleItemReference(args)) + }); + }); + } +} diff --git a/packages/web/src/common/FirebaseErrorParameter.ts b/packages/web/src/common/FirebaseErrorParameter.ts new file mode 100644 index 0000000..6deee95 --- /dev/null +++ b/packages/web/src/common/FirebaseErrorParameter.ts @@ -0,0 +1,52 @@ +import { FirebaseError } from "firebase/app"; + +type FirebaseErrorParameter = + | { + type: "firestore-permission-error"; + } + | { + type: "firestore-precondition-error"; + } + | { + type: "firestore-unknown-error"; + } + | { + type: "firebase-auth-invalid-credential"; + } + | { + type: "firebase-auth-email-already-in-use"; + } + | { type: "firebase-auth-expired-action-code" } + | { type: "firebase-auth-weak-password" }; + +export const extractFirebaseError = (e: unknown): FirebaseErrorParameter => { + if (e instanceof FirebaseError) { + const { code } = e; + if (code === "failed-precondition") { + // NOTE: エラーログにindex設定用のリンクが来るのでログしてほしい + console.error(e); + return { type: "firestore-precondition-error" }; + } + if (code === "permission-denied") { + return { type: "firestore-permission-error" }; + } + if (code === "auth/invalid-credential") { + return { type: "firebase-auth-invalid-credential" }; + } + if (code === "auth/email-already-in-use") { + return { type: "firebase-auth-email-already-in-use" }; + } + if ( + code === "auth/expired-action-code" || + code === "auth/invalid-action-code" + ) { + return { type: "firebase-auth-expired-action-code" }; + } + if (code === "auth/weak-password") { + return { type: "firebase-auth-weak-password" }; + } + } + return { type: "firestore-unknown-error" }; +}; + +export default FirebaseErrorParameter; diff --git a/packages/web/src/common/database-common-hooks.ts b/packages/web/src/common/database-common-hooks.ts new file mode 100644 index 0000000..b290106 --- /dev/null +++ b/packages/web/src/common/database-common-hooks.ts @@ -0,0 +1,127 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + type QueryFormula, + type TypedCollectionList +} from "@hinagata-next/core/common/DataStoreAgent"; +import { type ClientDataStoreAgent } from "~/common/ClientDataStoreAgent"; +import type FirebaseErrorParameter from "~/common/FirebaseErrorParameter"; +import { extractFirebaseError } from "~/common/FirebaseErrorParameter"; + +export const useDataStoreSingleItem = < + T extends {}, + D extends string, + C extends string +>({ + dataStore, + params, + onError +}: { + dataStore: ClientDataStoreAgent; + params: Record | null; + onError: (e: FirebaseErrorParameter) => void; +}) => { + const [remoteValue, setRemoteValue] = useState(null); + const [hasLoaded, setHasLoaded] = useState(false); + const normalizedValue = useMemo( + () => dataStore.scheme.parse(remoteValue), + [dataStore.scheme, remoteValue] + ); + const normalizedValueIfLoaded = useMemo( + () => (hasLoaded ? normalizedValue : null), + [hasLoaded, normalizedValue] + ); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setRemoteValue(null); + setHasLoaded(false); + if (!params) { + return () => {}; + } + return dataStore.subscribeItem({ + ...params, + handler: v => { + setRemoteValue(v || null); + setHasLoaded(true); + }, + onError: e => onError(extractFirebaseError(e)) + }); + }, [dataStore, onError, params]); + + return { + hasLoaded, + remoteValue, + normalizedValue, + normalizedValueIfLoaded + }; +}; + +export const useDataStoreList = < + T extends {}, + D extends string, + C extends string +>({ + dataStore, + params, + query, + onError +}: { + dataStore: ClientDataStoreAgent; + params: Record; + query?: QueryFormula[]; + onError: (e: FirebaseErrorParameter) => void; +}) => { + const [list, setList] = useState | null>(null); + + const handleError = useCallback( + (e: unknown) => onError(extractFirebaseError(e)), + [onError] + ); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setList(null); + return dataStore.subscribeList({ + ...params, + query, + handler: setList, + onError: handleError + }); + }, [dataStore, handleError, params, query]); + + return list; +}; + +export const useDataStoreGroupList = < + T extends {}, + D extends string, + C extends string +>({ + dataStore, + query, + onError +}: { + dataStore: ClientDataStoreAgent; + query?: QueryFormula[]; + onError: (e: FirebaseErrorParameter) => void; +}) => { + const [list, setList] = useState | null>(null); + + const handleError = useCallback( + (e: unknown) => onError(extractFirebaseError(e)), + [onError] + ); + + useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect + setList(null); + return dataStore.subscribeGroupList({ + query, + handler: l => + setList(l.map(({ ids, data }) => ({ id: ids.join("-"), data }))), + onError: handleError + }); + }, [dataStore, handleError, query]); + + return list; +}; diff --git a/packages/web/src/common/firebase-app.ts b/packages/web/src/common/firebase-app.ts new file mode 100644 index 0000000..fa8ec2d --- /dev/null +++ b/packages/web/src/common/firebase-app.ts @@ -0,0 +1,18 @@ +import { getApps as getCurrentApps, initializeApp } from "firebase/app"; +import { getAuth } from "firebase/auth"; +import { getFirestore } from "firebase/firestore"; +import { getStorage } from "firebase/storage"; +import { getFunctions } from "firebase/functions"; +import firebaseConfig from "~/common/firebaseConfig"; + +const FIREBASE_NAME = "default-firebase"; + +const getApp = () => + getCurrentApps().find(a => a.name === FIREBASE_NAME) || + initializeApp(firebaseConfig, FIREBASE_NAME); + +export const firebaseAuth = () => getAuth(getApp()); +export const firebaseFunctions = (region?: string) => + getFunctions(getApp(), region); +export const firebaseStorage = () => getStorage(getApp()); +export const firebaseFirestore = () => getFirestore(getApp()); diff --git a/packages/web/src/common/firebase-auth-tools.ts b/packages/web/src/common/firebase-auth-tools.ts new file mode 100644 index 0000000..d2b7755 --- /dev/null +++ b/packages/web/src/common/firebase-auth-tools.ts @@ -0,0 +1,64 @@ +import { useEffect } from "react"; +import { + getRedirectResult, + onAuthStateChanged, + type User +} from "firebase/auth"; +import { create } from "zustand"; +import { useShallow } from "zustand/react/shallow"; +import { firebaseAuth } from "~/common/firebase-app"; + +export type MeState = { + isAuthLoading: boolean; + myId: string | null; + myEmail: string | null; + emailVerificationProgress: boolean; +}; + +type MeStore = MeState & { + setMe: (payload: Partial) => void; +}; + +const useMeStore = create(set => ({ + isAuthLoading: true, + myId: null, + myEmail: null, + emailVerificationProgress: false, + setMe: (payload: Partial) => + set(s => ({ ...s, ...payload, isAuthLoading: false })) +})); + +export const useAuthorizedUser = (): MeState => + useMeStore( + useShallow(({ isAuthLoading, myId, myEmail, emailVerificationProgress }) => ({ + isAuthLoading, + myId, + myEmail, + emailVerificationProgress + })) + ); + +export const useAuthRoot = () => { + const setMe = useMeStore(s => s.setMe); + + useEffect(() => { + const handleAuthStateChange = (user: User | null) => { + if (user) { + const isEmailUser = user.providerData.find( + p => p.providerId === "password" + ); + setMe({ + myId: user.uid, + myEmail: user.email, + emailVerificationProgress: isEmailUser && !user.emailVerified + }); + } else { + setMe({ isAuthLoading: false, myId: null, myEmail: null }); + } + }; + getRedirectResult(firebaseAuth()); + return onAuthStateChanged(firebaseAuth(), handleAuthStateChange); + }, [setMe]); + + return useAuthorizedUser(); +}; diff --git a/packages/web/src/common/firebaseConfig.ts b/packages/web/src/common/firebaseConfig.ts new file mode 100644 index 0000000..3823830 --- /dev/null +++ b/packages/web/src/common/firebaseConfig.ts @@ -0,0 +1,11 @@ +const firebaseConfig = { + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || "", + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || "", + databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL || "", + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID || "", + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || "", + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID || "", + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID || "" +}; + +export default firebaseConfig; diff --git a/packages/web/src/common/requestCallable.ts b/packages/web/src/common/requestCallable.ts new file mode 100644 index 0000000..a7b7a96 --- /dev/null +++ b/packages/web/src/common/requestCallable.ts @@ -0,0 +1,10 @@ +import { httpsCallable } from "firebase/functions"; +import { firebaseFunctions } from "~/common/firebase-app"; + +const requestCallable = (region: string, name: string, req: Req) => + httpsCallable( + firebaseFunctions(region), + name + )(req).then(r => r.data); + +export default requestCallable; diff --git a/packages/web/src/component/DummyProfileListView.tsx b/packages/web/src/component/DummyProfileListView.tsx index 3beb6f1..b825604 100644 --- a/packages/web/src/component/DummyProfileListView.tsx +++ b/packages/web/src/component/DummyProfileListView.tsx @@ -1,14 +1,14 @@ import type DummyProfile from "@hinagata-next/core/feature/DummyProfile"; import MockListView from "~/component/MockListView"; -const DummyProfileListView = ({ +const DummyProfileListView = ({ list, onEdit, onDelete }: { - list: { id: number; data: DummyProfile }[]; - onEdit: (id: number) => void; - onDelete: (id: number) => void; + list: { id: T; data: DummyProfile }[]; + onEdit: (id: T) => void; + onDelete: (id: T) => void; }) => ( ({ diff --git a/packages/web/src/component/ErrorPopup.tsx b/packages/web/src/component/ErrorPopup.tsx new file mode 100644 index 0000000..15962bf --- /dev/null +++ b/packages/web/src/component/ErrorPopup.tsx @@ -0,0 +1,21 @@ +import { type AppErrorParameter } from "~/feature/AppErrorParameter"; +import MockPopup from "~/component/MockPopup"; + +const ErrorPopup = ({ + error, + onClose +}: { + error: AppErrorParameter; + onClose: () => void; +}) => { + return ( + +
+

ERROR:

+ {error.type} +
+
+ ); +}; + +export default ErrorPopup; diff --git a/packages/web/src/component/ErrorScene.tsx b/packages/web/src/component/ErrorScene.tsx new file mode 100644 index 0000000..a28df94 --- /dev/null +++ b/packages/web/src/component/ErrorScene.tsx @@ -0,0 +1,15 @@ +import { type AppErrorParameter } from "~/feature/AppErrorParameter"; +import MockCenteringLayout from "~/component/MockCenteringLayout"; + +const ErrorScene = ({ error }: { error: AppErrorParameter }) => { + return ( + +
+

ERROR:

+ {error.type} +
+
+ ); +}; + +export default ErrorScene; diff --git a/packages/web/src/component/InlineLoading.tsx b/packages/web/src/component/InlineLoading.tsx new file mode 100644 index 0000000..ea32079 --- /dev/null +++ b/packages/web/src/component/InlineLoading.tsx @@ -0,0 +1,3 @@ +const InlineLoading = () =>

loading...

; + +export default InlineLoading; diff --git a/packages/web/src/component/LayoutRoot.tsx b/packages/web/src/component/LayoutRoot.tsx index 61d3346..4e0ea77 100644 --- a/packages/web/src/component/LayoutRoot.tsx +++ b/packages/web/src/component/LayoutRoot.tsx @@ -1,7 +1,11 @@ +"use client"; + import { type ReactNode } from "react"; +import { useAuthRoot } from "~/common/firebase-auth-tools"; -const LayoutRoot = ({ children }: { children: ReactNode }) => ( -
{children}
-); +const LayoutRoot = ({ children }: { children: ReactNode }) => { + useAuthRoot(); + return
{children}
; +}; export default LayoutRoot; diff --git a/packages/web/src/component/LoadingPopup.tsx b/packages/web/src/component/LoadingPopup.tsx new file mode 100644 index 0000000..02d7238 --- /dev/null +++ b/packages/web/src/component/LoadingPopup.tsx @@ -0,0 +1,5 @@ +import MockPopup from "~/component/MockPopup"; + +const LoadingPopup = () => loading...; + +export default LoadingPopup; diff --git a/packages/web/src/component/_provider/ProfileFormScene.tsx b/packages/web/src/component/_provider/ProfileFormScene.tsx new file mode 100644 index 0000000..ea3ac85 --- /dev/null +++ b/packages/web/src/component/_provider/ProfileFormScene.tsx @@ -0,0 +1,142 @@ +"use client"; + +import { useCallback, useMemo, useState } from "react"; +import { GoogleAuthProvider, signInWithPopup } from "firebase/auth"; +import { useDataStoreList } from "~/common/database-common-hooks"; +import { ClientDataStoreAgent } from "~/common/ClientDataStoreAgent"; +import { useAuthorizedUser } from "~/common/firebase-auth-tools"; +import { firebaseAuth } from "~/common/firebase-app"; +import { profileDataStoreScheme } from "@hinagata-next/core/feature/app-data-store-scheme"; +import type DummyProfile from "@hinagata-next/core/feature/DummyProfile"; +import { parseDummyProfile } from "@hinagata-next/core/feature/DummyProfile"; +import useAsyncHandler from "~/feature/useAsyncHandler"; +import { type AppErrorParameter } from "~/feature/AppErrorParameter"; +import DummyProfileListView from "~/component/DummyProfileListView"; +import DummyProfileForm from "~/component/DummyProfileForm"; +import MockStaticLayout from "~/component/MockStaticLayout"; +import MockActionButton from "~/component/MockActionButton"; +import ErrorScene from "~/component/ErrorScene"; +import ErrorPopup from "~/component/ErrorPopup"; +import LoadingPopup from "~/component/LoadingPopup"; +import InlineLoading from "~/component/InlineLoading"; + +const profileDataStore = new ClientDataStoreAgent(profileDataStoreScheme); + +const ProfileFormScene = () => { + const { myId } = useAuthorizedUser(); + const [formId, setFormId] = useState(""); + const [operationError, setOperationError] = + useState(null); + const [statusError, setStatusError] = useState( + null + ); + + const { isLoading, runAsyncHandler } = useAsyncHandler({ + onError: setOperationError + }); + + const params = useMemo(() => ({}), []); + const list = useDataStoreList({ + dataStore: profileDataStore, + params, + onError: setStatusError + }); + + const currentFormData = useMemo((): DummyProfile | null => { + if (!formId || !list) { + return null; + } + const m = list.find(d => d.id === formId); + return m ? m.data : parseDummyProfile(null); + }, [list, formId]); + + const handleSubmit = useCallback( + (v: DummyProfile) => + runAsyncHandler(() => + profileDataStore + .setItem({ + userId: formId, + data: v + }) + .then(() => setFormId("")) + ), + [formId, runAsyncHandler] + ); + + const handleDelete = useCallback( + (id: string) => + runAsyncHandler(() => { + return profileDataStore.deleteItem({ userId: id }); + }), + [runAsyncHandler] + ); + + if (statusError) { + return ; + } + + return ( + <> + + {formId && currentFormData ? ( + setFormId("")} + /> + ) : ( + <> + {list ? ( + <> + {myId && !list.some(d => d.id === myId) ? ( +

+ setFormId(myId) + }} + > + 新規作成 + +

+ ) : null} + {!myId ? ( +

+ + signInWithPopup( + firebaseAuth(), + new GoogleAuthProvider() + ) + }} + > + ログイン + +

+ ) : null} + setFormId(id)} + onDelete={handleDelete} + /> + + ) : ( + + )} + + )} +
+ {isLoading ? : null} + {operationError ? ( + setOperationError(null)} + /> + ) : null} + + ); +}; + +export default ProfileFormScene; diff --git a/packages/web/src/feature/AppError.ts b/packages/web/src/feature/AppError.ts new file mode 100644 index 0000000..ad09bac --- /dev/null +++ b/packages/web/src/feature/AppError.ts @@ -0,0 +1,15 @@ +import { type AppErrorParameter } from "~/feature/AppErrorParameter"; + +class AppError extends Error { + public readonly parameter: AppErrorParameter; + + public constructor(param: AppErrorParameter) { + super(); + this.parameter = param; + } +} + +export const extractAppError = (err: unknown): AppErrorParameter => + err instanceof AppError ? err.parameter : { type: "unknown" }; + +export default AppError; diff --git a/packages/web/src/feature/AppErrorParameter.ts b/packages/web/src/feature/AppErrorParameter.ts new file mode 100644 index 0000000..074a4b5 --- /dev/null +++ b/packages/web/src/feature/AppErrorParameter.ts @@ -0,0 +1,19 @@ +import type FirebaseErrorParameter from "~/common/FirebaseErrorParameter"; + +export type AppErrorParameter = + | { + type: "unknown"; + } + | { + type: "unauthorized"; + } + | { + type: "bad-parameter"; + } + | { + type: "fail-to-google-auth"; + } + | { + type: "error-in-transaction"; + } + | FirebaseErrorParameter; diff --git a/packages/web/src/feature/emotion-mixin.ts b/packages/web/src/feature/emotion-mixin.ts index 57642d8..e150e26 100644 --- a/packages/web/src/feature/emotion-mixin.ts +++ b/packages/web/src/feature/emotion-mixin.ts @@ -43,10 +43,5 @@ export const spp = (...nums: CSSSizeKeyword[]) => ); export const THEME_COLOR = { - ...PRIMITIVE_COLOR, - DARK: "#333333", - BROWN: "#712636", - BG_YELLOW: "#FFFABA", - WAVE_YELLOW: "#ffeb3c", - PINK: "#E4007F" -} satisfies Record; + ...PRIMITIVE_COLOR +} as const; diff --git a/packages/web/src/feature/requestAppCallable.ts b/packages/web/src/feature/requestAppCallable.ts new file mode 100644 index 0000000..d3b8edf --- /dev/null +++ b/packages/web/src/feature/requestAppCallable.ts @@ -0,0 +1,17 @@ +import requestCallable from "~/common/requestCallable"; +import type AppCallableScheme from "@hinagata-next/core/feature/AppCallableScheme"; +import { + COMMON_CALLABLE_REGION, + type CommonNGResponse +} from "@hinagata-next/core/feature/AppCallableScheme"; + +const requestAppCallable = ( + path: T, + req: AppCallableScheme[T]["Request"] +) => + requestCallable< + AppCallableScheme[T]["Request"], + AppCallableScheme[T]["Response"] | CommonNGResponse + >(COMMON_CALLABLE_REGION, path, req); + +export default requestAppCallable; diff --git a/packages/web/src/feature/useAsyncHandler.ts b/packages/web/src/feature/useAsyncHandler.ts new file mode 100644 index 0000000..3cb41c7 --- /dev/null +++ b/packages/web/src/feature/useAsyncHandler.ts @@ -0,0 +1,32 @@ +import { useCallback, useState } from "react"; +import { extractFirebaseError } from "~/common/FirebaseErrorParameter"; +import { extractAppError } from "~/feature/AppError"; +import { type AppErrorParameter } from "~/feature/AppErrorParameter"; + +const useAsyncHandler = ({ + onError +}: { + onError: (e: AppErrorParameter) => void; +}) => { + const [isLoading, setIsLoading] = useState(false); + + const runAsyncHandler = useCallback( + async (fn: () => Promise) => { + setIsLoading(true); + await fn().catch(err => { + const firebaseError = extractFirebaseError(err); + onError( + firebaseError.type === "firestore-unknown-error" + ? extractAppError(err) + : firebaseError + ); + }); + setIsLoading(false); + }, + [onError] + ); + + return { isLoading, runAsyncHandler }; +}; + +export default useAsyncHandler; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fe10a9..97bbd48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,70 @@ importers: specifier: ^6.0.3 version: 6.0.3 + packages/functions: + dependencies: + '@google-cloud/functions-framework': + specifier: ^5.0.2 + version: 5.0.2 + '@hinagata-next/core': + specifier: workspace:* + version: link:../core + cors: + specifier: ^2.8.5 + version: 2.8.6 + express: + specifier: ^4.17.1 + version: 4.22.2 + firebase-admin: + specifier: ^12.2.0 + version: 12.7.0 + firebase-functions: + specifier: ^4.1.1 + version: 4.9.0(firebase-admin@12.7.0) + mkdirp: + specifier: ^1.0.4 + version: 1.0.4 + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@9.39.4) + '@types/cors': + specifier: ^2.8.7 + version: 2.8.19 + '@types/node': + specifier: ^20.14.10 + version: 20.19.41 + '@typescript-eslint/eslint-plugin': + specifier: ^8.59.0 + version: 8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.59.0 + version: 8.59.3(eslint@9.39.4)(typescript@5.9.3) + eslint: + specifier: ^9.39.4 + version: 9.39.4 + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.39.4) + eslint-plugin-import: + specifier: ^2.29.1 + version: 2.32.0(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4) + firebase-functions-test: + specifier: ^0.2.0 + version: 0.2.3(firebase-admin@12.7.0)(firebase-functions@4.9.0(firebase-admin@12.7.0)) + globals: + specifier: ^17.5.0 + version: 17.6.0 + tsc-alias: + specifier: ^1.8.10 + version: 1.8.17 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + packages/web: dependencies: '@emotion/cache': @@ -78,6 +142,9 @@ importers: emotion-reset: specifier: ^3.0.1 version: 3.0.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.6)) + firebase: + specifier: ^12.12.1 + version: 12.13.0 next: specifier: ^16.2.6 version: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(babel-plugin-macros@3.1.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -669,10 +736,263 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + + '@firebase/ai@2.12.0': + resolution: {integrity: sha512-b+OL4vdyiSLZL/7dLd67V55CjKJvU9MpNmwnday7eA6GG2+J4iwUEsEHgw0/jKY3A41FfkF0SrnYFvtKbQZ65A==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app': 0.x + '@firebase/app-types': 0.x + + '@firebase/analytics-compat@0.2.28': + resolution: {integrity: sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ==} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/analytics-types@0.8.4': + resolution: {integrity: sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ==} + + '@firebase/analytics@0.10.22': + resolution: {integrity: sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow==} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/app-check-compat@0.4.3': + resolution: {integrity: sha512-L3AKIRTJxT9b7cDUH3OyV8gWTnmW3vYkwdzRsukWt4kbPBTct12xalnyvHDkm1lKkr+cQq/4uzBx1bOWsQ2ciw==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/app-check-interop-types@0.3.2': + resolution: {integrity: sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==} + + '@firebase/app-check-interop-types@0.3.4': + resolution: {integrity: sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==} + + '@firebase/app-check-types@0.5.4': + resolution: {integrity: sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw==} + + '@firebase/app-check@0.11.3': + resolution: {integrity: sha512-aJ4DfubWfTO8/2vhEhIAizOoOmiycESTU32e+OUgbWcS/G3PA4Vxlr/9zaiN2wfUG2AptQ7DTvj00tyuFZP5Bg==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/app-compat@0.5.12': + resolution: {integrity: sha512-Pe513OBerK/CIBxz4/za9atd5MsZtd6DzHz4cmqkvkrcDWhQChAoHBpZ3McuZNuSP8YZiKwfX/J1frR07l15/w==} + engines: {node: '>=20.0.0'} + + '@firebase/app-types@0.9.2': + resolution: {integrity: sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==} + + '@firebase/app-types@0.9.5': + resolution: {integrity: sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==} + + '@firebase/app@0.14.12': + resolution: {integrity: sha512-FT+HoNp1NdaZ/N26hCwV3WbxS1m6gTn3p2QRBQ3KH7YqyCQqJx0iT7126RgVk68/Rq+9DeL/zCFnHZ0C4u1nLQ==} + engines: {node: '>=20.0.0'} + + '@firebase/auth-compat@0.6.6': + resolution: {integrity: sha512-KDJ/GAf/rt7galOpn3DRb2buFfGkZCsHTryKjXDG0eeRnok4+2B4nnkMOMdjRnPkElmcJv2Ao0vEA6kp5m98PQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/auth-interop-types@0.2.3': + resolution: {integrity: sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==} + + '@firebase/auth-interop-types@0.2.5': + resolution: {integrity: sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==} + + '@firebase/auth-types@0.13.1': + resolution: {integrity: sha512-0c1Mnid0uMDfGJHeUS4zfvBa4/CedJXotGy/n/NZJnBjwiJawt0ZYU+wH2VAVLiRCEfG2ncCkAX3yd1/2nrB7g==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + + '@firebase/auth@1.13.1': + resolution: {integrity: sha512-/1nkKY/MicI+I9WWcx6R4NKs77AaW9NQ0IwsFdUBomWrW0/cXEmopfM2dtLm2oI1qG6z6vom3CXZDHJIJXoMuw==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app': 0.x + '@react-native-async-storage/async-storage': ^2.2.0 || ^3.0.0 + peerDependenciesMeta: + '@react-native-async-storage/async-storage': + optional: true + + '@firebase/component@0.6.9': + resolution: {integrity: sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==} + + '@firebase/component@0.7.3': + resolution: {integrity: sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==} + engines: {node: '>=20.0.0'} + + '@firebase/data-connect@0.7.0': + resolution: {integrity: sha512-ar9sNOJh5poQCSMSVlnVE8eo8+usTD1POWDCv65omkKUvnFMcdXaQ7J/e7WGKqJzcEMgiezSX/TZiKHZkItMbQ==} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/database-compat@1.0.8': + resolution: {integrity: sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==} + + '@firebase/database-compat@2.1.4': + resolution: {integrity: sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==} + engines: {node: '>=20.0.0'} + + '@firebase/database-types@1.0.20': + resolution: {integrity: sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==} + + '@firebase/database-types@1.0.5': + resolution: {integrity: sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==} + + '@firebase/database@1.0.8': + resolution: {integrity: sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==} + + '@firebase/database@1.1.3': + resolution: {integrity: sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==} + engines: {node: '>=20.0.0'} + + '@firebase/firestore-compat@0.4.9': + resolution: {integrity: sha512-NPtBuFr79BbIQJXFWhW4xFC6rBksK8/ewqCTYbbAYfZBDDx0/iHTUj4WpKi5D4d0Pn2Md/3T/e5V9379G5N/Zg==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/firestore-types@3.0.4': + resolution: {integrity: sha512-jGn+JSS4X9zZsrfu7Yw66v5YRdOLD1oyQh4USR0xWl4CUqV/DA6bNIXRPpxH/cUl3iVTNiP6MN7g+EL42A4qfA==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + + '@firebase/firestore@4.14.1': + resolution: {integrity: sha512-PouS0NJZ3NYOZE/tPDvXa8VUeJ10Ll//7jIdFvMYdhQkd/P3O7nlqhyoTmY0h8Xa9hxg+H0j6gxUytJcoZ9YOg==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/functions-compat@0.4.4': + resolution: {integrity: sha512-Be+MwhseVf/eFAZwGrFJGok6S7cmsLrAPK8MgyM8LjM0MewTsx2n01WOOca9jio1UsCZOJ0aVyQobnINcdNuIQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/functions-types@0.6.4': + resolution: {integrity: sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ==} + + '@firebase/functions@0.13.4': + resolution: {integrity: sha512-oB5rpm2Emxn2+IS1gRelAeT/5tSZMwM/KhqC5LnJsmTNnS1ZDhD7ZMZNgCI8vchTW6PbaXIwEnpUryGuIQsNbg==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/installations-compat@0.2.22': + resolution: {integrity: sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w==} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/installations-types@0.5.4': + resolution: {integrity: sha512-U2eFapdHwjb43Vx9o+Pmj4dFfvcHEK1IirEFLqMtWrTHvmdrS3gBpBD1kmJk/9HjsOtoHZxJ2Paoe79e+L1ZPg==} + peerDependencies: + '@firebase/app-types': 0.x + + '@firebase/installations@0.6.22': + resolution: {integrity: sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw==} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/logger@0.4.2': + resolution: {integrity: sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==} + + '@firebase/logger@0.5.1': + resolution: {integrity: sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==} + engines: {node: '>=20.0.0'} + + '@firebase/messaging-compat@0.2.26': + resolution: {integrity: sha512-fn0XvWOfK4tsDLSipwJUW9Cp6ahWA6z+iJHxZ0pHp9MzMSUNQx85yuxZAuI7gkGXfqs7+DqEDHyyS7jDGswrmQ==} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/messaging-interop-types@0.2.4': + resolution: {integrity: sha512-wrzITQq+xw5LtygX7O0fu43/k9ABQ4x5H9/sR5m1SbNnhIRI5xd3+raSNJaJkYC4BUhM9A4ZNSnyR2sjhxnb2Q==} + + '@firebase/messaging@0.12.26': + resolution: {integrity: sha512-lHVTO9uLofymHVWkYeUtMddIPcmJvSzVbHRB88W6XKfxbcKF+p3QrfqKhDxremSB4NQjUla1Gwn7d9umSMmt/w==} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/performance-compat@0.2.25': + resolution: {integrity: sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA==} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/performance-types@0.2.4': + resolution: {integrity: sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg==} + + '@firebase/performance@0.7.12': + resolution: {integrity: sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ==} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/remote-config-compat@0.2.24': + resolution: {integrity: sha512-EWZTt6fJ7YmPHodQNsSxAIDZY2x8P5kRPvXAc5CmzzBm+NyPFhODbfDsNllDXDL8jlzp50bVWjDY+BXepZS9Mg==} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/remote-config-types@0.5.1': + resolution: {integrity: sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A==} + + '@firebase/remote-config@0.8.3': + resolution: {integrity: sha512-ggGKAaLy9YNOvpFoQZgm5p5SiFw3ZFtwti08dojnBQmQicpThTxvG5xZMSpCTYMj2o3gM/yK9CVd2w+kZub8YA==} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/storage-compat@0.4.3': + resolution: {integrity: sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app-compat': 0.x + + '@firebase/storage-types@0.8.4': + resolution: {integrity: sha512-BT7cwxJOx8SWwlQfrlC+bD/Sk3Cw+1odCi8UZNFNWTVZoPsBnA5W+mqtZzVnvsdJpXCFGSGQ7R7vOR6dtM/BRA==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + + '@firebase/storage@0.14.3': + resolution: {integrity: sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@firebase/app': 0.x + + '@firebase/util@1.10.0': + resolution: {integrity: sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==} + + '@firebase/util@1.15.1': + resolution: {integrity: sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==} + engines: {node: '>=20.0.0'} + + '@firebase/webchannel-wrapper@1.0.6': + resolution: {integrity: sha512-Vr/Mqu79dMwGRAyGbJ4uN4+BtXB3/mRTdzetD1daWNeG8QaWuzhhbG77GltO5c0yYmYls8i250iX73624GJd7Q==} + '@google-cloud/cloud-sql-connector@1.10.0': resolution: {integrity: sha512-PLix9OUaeAfVOKFAqw32/ETvFPef26mcTmDu/iVShGRs+MB1JkL98SwVLHsEjzjfnZrF+BtKqnseoFw0+3LmPw==} engines: {node: '>=18'} + '@google-cloud/firestore@7.11.6': + resolution: {integrity: sha512-EW/O8ktzwLfyWBOsNuhRoMi8lrC3clHM5LVFhGvO1HCsLozCOOXRAlHrYBoE6HL42Sc8yYMuCb2XqcnJ4OOEpw==} + engines: {node: '>=14.0.0'} + + '@google-cloud/functions-framework@5.0.2': + resolution: {integrity: sha512-QDPRkPO1KWqbJ+z5RexKjZzHilIK5vM58YFkr/GZU5NIBcCb7Vd4dBKn9cLxEMaBxg6bXbmqEhF6O6977gLNkQ==} + engines: {node: '>=10.0.0'} + hasBin: true + + '@google-cloud/paginator@5.0.2': + resolution: {integrity: sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==} + engines: {node: '>=14.0.0'} + '@google-cloud/paginator@6.0.0': resolution: {integrity: sha512-g5nmMnzC+94kBxOKkLGpK1ikvolTFCC3s2qtE4F+1EuArcJ7HHC23RDQVt3Ra3CqpUYZ+oXNKZ8n5Cn5yug8DA==} engines: {node: '>=18'} @@ -681,10 +1001,18 @@ packages: resolution: {integrity: sha512-9h0Gvw92EvPdE8AK8AgZPbMnH5ftDyPtKm7/KUfcJVaPEPjwGDsJd1QV0H8esBDV4II41R/2lDWH1epBqIoKUw==} engines: {node: '>=18'} + '@google-cloud/projectify@4.0.0': + resolution: {integrity: sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==} + engines: {node: '>=14.0.0'} + '@google-cloud/projectify@5.0.0': resolution: {integrity: sha512-XXQLaIcLrOAMWvRrzz+mlUGtN6vlVNja3XQbMqRi/V7XJTAVwib3VcKd7oRwyZPkp7rBVlHGcaqdyGRrcnkhlA==} engines: {node: '>=18'} + '@google-cloud/promisify@4.0.0': + resolution: {integrity: sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==} + engines: {node: '>=14'} + '@google-cloud/promisify@5.0.0': resolution: {integrity: sha512-N8qS6dlORGHwk7WjGXKOSsLjIjNINCPicsOX6gyyLiYk7mq3MtII96NZ9N2ahwA2vnkLmZODOIH9rlNniYWvCQ==} engines: {node: '>=18'} @@ -693,6 +1021,10 @@ packages: resolution: {integrity: sha512-hyUoE85Rj3rRUVk3VU+Selp4MorBwEzsQEqAj6+SE+WabR9LIFitYS6A4R+PyiwVaRk/tggGD8p7bNiIY5sk4w==} engines: {node: '>=18'} + '@google-cloud/storage@7.19.0': + resolution: {integrity: sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==} + engines: {node: '>=14'} + '@googleapis/sqladmin@35.2.0': resolution: {integrity: sha512-ajR9EGLs1pCkKfsXxfbVRnQ7ZPyktKNAuahHoU06CVKguWwQo3b9aFmq06PYnGk1oXc0+tlW+XEamNa/HF4pbQ==} engines: {node: '>=12.0.0'} @@ -701,6 +1033,15 @@ packages: resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} engines: {node: '>=12.10.0'} + '@grpc/grpc-js@1.9.15': + resolution: {integrity: sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==} + engines: {node: ^8.13.0 || >=10.10.0} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + '@grpc/proto-loader@0.8.1': resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} engines: {node: '>=6'} @@ -1220,6 +1561,9 @@ packages: cpu: [x64] os: [win32] + '@nodable/entities@2.1.1': + resolution: {integrity: sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1345,6 +1689,10 @@ packages: '@types/react-dom': optional: true + '@tootallnate/once@2.0.1': + resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} + engines: {node: '>= 10'} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -1369,6 +1717,18 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/caseless@0.12.5': + resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -1378,6 +1738,18 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@4.17.3': + resolution: {integrity: sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1399,15 +1771,51 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/long@4.0.2': + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + '@types/node@20.19.41': + resolution: {integrity: sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==} + + '@types/node@22.19.19': + resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/request@2.48.13': + resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -1671,6 +2079,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} @@ -1777,6 +2189,10 @@ packages: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + array.prototype.findlast@1.2.5: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} @@ -1822,6 +2238,9 @@ packages: async-lock@1.4.1: resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -2128,6 +2547,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + cloudevents@10.0.0: + resolution: {integrity: sha512-uyzC+PpMMRawbouHO+3mlisr3QfEDObmo2pN4oTTF6dZncZgpIzdasZx0tRBFI1dMsqCLZZXMtz8cUuvYqHdbw==} + engines: {node: '>=20 <=24'} + co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -2190,6 +2613,10 @@ packages: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + compress-commons@6.0.2: resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} engines: {node: '>= 14'} @@ -2430,6 +2857,10 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + discontinuous-range@1.0.0: resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} @@ -2775,6 +3206,10 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + farmhash-modern@1.1.0: + resolution: {integrity: sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==} + engines: {node: '>=18.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2794,9 +3229,20 @@ packages: fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} + fast-xml-builder@1.2.0: + resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==} + + fast-xml-parser@5.8.0: + resolution: {integrity: sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==} + hasBin: true + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -2849,6 +3295,10 @@ packages: find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up-simple@1.0.1: + resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==} + engines: {node: '>=18'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2857,11 +3307,32 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + firebase-admin@12.7.0: + resolution: {integrity: sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA==} + engines: {node: '>=14'} + + firebase-functions-test@0.2.3: + resolution: {integrity: sha512-zYX0QTm53wCazuej7O0xqbHl90r/v1PTXt/hwa0jo1YF8nDM+iBKnLDlkIoW66MDd0R6aGg4BvKzTTdJpvigUA==} + engines: {node: '>=8.0.0'} + peerDependencies: + firebase-admin: '>=6.0.0' + firebase-functions: '>=2.0.0' + + firebase-functions@4.9.0: + resolution: {integrity: sha512-IqxOEsVAWGcRv9KRGzWQR5mOFuNsil3vsfkRPPiaV1U/ATC27/jbahh4z8I4rW8Xqa6cQE5xqnw0ueyMH7i7Ag==} + engines: {node: '>=14.10.0'} + hasBin: true + peerDependencies: + firebase-admin: ^10.0.0 || ^11.0.0 || ^12.0.0 + firebase-tools@15.18.0: resolution: {integrity: sha512-rILpd9JgjifGRUSxXJC4Iu1h6wEZ7BuoYHZerT+CuY9SNopm8ykHOCjoFCANFplQe0xIGMkJtaXdEVN1uCB75A==} engines: {node: '>=20.0.0 || >=22.0.0 || >=24.0.0'} hasBin: true + firebase@12.13.0: + resolution: {integrity: sha512-iutR8ejvAqk6qUClnsPz3U3VIjTWp243AX4cD3iifak5t56to1J29xUIQgSDDzaAqKvhshZerzSahwMQj2TlvA==} + flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -2880,6 +3351,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@2.5.5: + resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==} + engines: {node: '>= 0.12'} + form-data@4.0.5: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} @@ -2922,6 +3397,9 @@ packages: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} @@ -3033,6 +3511,10 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + google-auth-library@10.6.2: resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==} engines: {node: '>=18'} @@ -3041,6 +3523,10 @@ packages: resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} engines: {node: '>=14'} + google-gax@4.6.1: + resolution: {integrity: sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==} + engines: {node: '>=14'} + google-gax@5.0.6: resolution: {integrity: sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==} engines: {node: '>=18'} @@ -3127,10 +3613,17 @@ packages: resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} engines: {node: '>=16.9.0'} + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -3138,10 +3631,21 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + http-parser-js@0.5.10: + resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -3162,6 +3666,9 @@ packages: resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} + idb@7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3194,6 +3701,10 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -3229,6 +3740,10 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -3598,6 +4113,9 @@ packages: join-path@1.1.1: resolution: {integrity: sha512-jnt9OC34sLXMLJ6YfPQ2ZEKrR9mB5ZbSnQb4LPaOx1c5rTzxpR33L18jjp0r75mGGTJmsil3qwN1B5IBeTnSSA==} + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} @@ -3677,6 +4195,10 @@ packages: jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + jwks-rsa@3.2.2: + resolution: {integrity: sha512-BqTyEDV+lS8F2trk3A+qJnxV5Q9EqKCBJOPti3W97r7qTympCZjb7h2X6f2kc+0K3rsSTY1/6YG2eaXKoj497w==} + engines: {node: '>=14'} + jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} @@ -3711,6 +4233,9 @@ packages: libsodium@0.7.16: resolution: {integrity: sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q==} + limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -3736,6 +4261,9 @@ packages: lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -3793,10 +4321,17 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lru-cache@7.18.3: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lru-memoizer@2.3.0: + resolution: {integrity: sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==} + lsofi@2.0.0: resolution: {integrity: sha512-XTFlOBHeuXnUGuUQI0kBb4O4oQW7qCV7MRGQja1xNpxgrI/jptlly+/5126RiRold1zgyxY520qOZUZ31V0I2A==} engines: {node: '>=20.0.0'} @@ -3890,6 +4425,11 @@ packages: engines: {node: '>=4.0.0'} hasBin: true + mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -3960,6 +4500,10 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} + mylas@2.1.14: + resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==} + engines: {node: '>=16.0.0'} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -4059,6 +4603,10 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + engines: {node: '>= 6.13.0'} + node-gyp@12.3.0: resolution: {integrity: sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -4075,6 +4623,10 @@ packages: engines: {node: ^20.17.0 || >=22.9.0} hasBin: true + normalize-package-data@6.0.2: + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -4210,6 +4762,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse5-htmlparser2-tree-adapter@6.0.1: resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} @@ -4233,6 +4789,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-expression-matcher@1.5.0: + resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} + engines: {node: '>=14.0.0'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -4321,6 +4881,10 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + portfinder@1.0.38: resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==} engines: {node: '>= 10.12'} @@ -4396,6 +4960,10 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proto3-json-serializer@2.0.2: + resolution: {integrity: sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==} + engines: {node: '>=14.0.0'} + proto3-json-serializer@3.0.4: resolution: {integrity: sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==} engines: {node: '>=18'} @@ -4433,6 +5001,10 @@ packages: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} + queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4484,6 +5056,14 @@ packages: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} + read-package-up@11.0.0: + resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} + engines: {node: '>=18'} + + read-pkg@9.0.1: + resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} + engines: {node: '>=18'} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -4566,6 +5146,10 @@ packages: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} + retry-request@7.0.2: + resolution: {integrity: sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==} + engines: {node: '>=14'} + retry-request@8.0.2: resolution: {integrity: sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==} engines: {node: '>=18'} @@ -4766,6 +5350,18 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -4884,6 +5480,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strnum@2.3.0: + resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + stubs@3.0.0: resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} @@ -4959,6 +5558,10 @@ packages: resolution: {integrity: sha512-Xj0ZAQ0CeuQn6UxCDPLbFRlgcSTUEyO3+wiepr2grjIjyL/lMMs1Z4OwXn8kLvn/V1OuaEP0UY7Na6UDNNsYrQ==} engines: {node: '>=18'} + teeny-request@9.0.0: + resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==} + engines: {node: '>=14'} + teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} @@ -5115,6 +5718,11 @@ packages: resolution: {integrity: sha512-iS47YTbdIcvN8Nh/1BFyziyUqmjXz7GVzWu02RaZXqb+e/3Qe1B7IQ4860krOeCGUeJmterAlaM2FRH0Ue0hjw==} hasBin: true + tsc-alias@1.8.17: + resolution: {integrity: sha512-EIduCZHqbNwPm8BZYfq1aD7BQ697A4h6uSGMOFQfYGoQwfrYFTKwYfy9Bv42YxHkduVBcn9Zx0DkX111DKskyg==} + engines: {node: '>=16.20.2'} + hasBin: true + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -5199,6 +5807,9 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@6.25.0: resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} engines: {node: '>=18.17'} @@ -5207,6 +5818,10 @@ packages: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} + unicorn-magic@0.1.0: + resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} + engines: {node: '>=18'} + unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -5248,10 +5863,18 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + uuid@14.0.0: resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true @@ -5273,6 +5896,9 @@ packages: valid-url@1.0.9: resolution: {integrity: sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==} + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -5295,6 +5921,9 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + web-vitals@4.2.4: + resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -5316,6 +5945,14 @@ packages: webpack-cli: optional: true + websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + + websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -5434,6 +6071,10 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} + xml-naming@0.1.0: + resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} + engines: {node: '>=16.0.0'} + xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -5448,6 +6089,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} @@ -6002,6 +6646,371 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@fastify/busboy@3.2.0': {} + + '@firebase/ai@2.12.0(@firebase/app-types@0.9.5)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/app-check-interop-types': 0.3.4 + '@firebase/app-types': 0.9.5 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/analytics-compat@0.2.28(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12)': + dependencies: + '@firebase/analytics': 0.10.22(@firebase/app@0.14.12) + '@firebase/analytics-types': 0.8.4 + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + + '@firebase/analytics-types@0.8.4': {} + + '@firebase/analytics@0.10.22(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/installations': 0.6.22(@firebase/app@0.14.12) + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/app-check-compat@0.4.3(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-check': 0.11.3(@firebase/app@0.14.12) + '@firebase/app-check-types': 0.5.4 + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + + '@firebase/app-check-interop-types@0.3.2': {} + + '@firebase/app-check-interop-types@0.3.4': {} + + '@firebase/app-check-types@0.5.4': {} + + '@firebase/app-check@0.11.3(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/app-compat@0.5.12': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/app-types@0.9.2': {} + + '@firebase/app-types@0.9.5': + dependencies: + '@firebase/logger': 0.5.1 + + '@firebase/app@0.14.12': + dependencies: + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + idb: 7.1.1 + tslib: 2.8.1 + + '@firebase/auth-compat@0.6.6(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/auth': 1.13.1(@firebase/app@0.14.12) + '@firebase/auth-types': 0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) + '@firebase/component': 0.7.3 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - '@react-native-async-storage/async-storage' + + '@firebase/auth-interop-types@0.2.3': {} + + '@firebase/auth-interop-types@0.2.5': {} + + '@firebase/auth-types@0.13.1(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + dependencies: + '@firebase/app-types': 0.9.5 + '@firebase/util': 1.15.1 + + '@firebase/auth@1.13.1(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/component@0.6.9': + dependencies: + '@firebase/util': 1.10.0 + tslib: 2.8.1 + + '@firebase/component@0.7.3': + dependencies: + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/data-connect@0.7.0(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/auth-interop-types': 0.2.5 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/database-compat@1.0.8': + dependencies: + '@firebase/component': 0.6.9 + '@firebase/database': 1.0.8 + '@firebase/database-types': 1.0.5 + '@firebase/logger': 0.4.2 + '@firebase/util': 1.10.0 + tslib: 2.8.1 + + '@firebase/database-compat@2.1.4': + dependencies: + '@firebase/component': 0.7.3 + '@firebase/database': 1.1.3 + '@firebase/database-types': 1.0.20 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/database-types@1.0.20': + dependencies: + '@firebase/app-types': 0.9.5 + '@firebase/util': 1.15.1 + + '@firebase/database-types@1.0.5': + dependencies: + '@firebase/app-types': 0.9.2 + '@firebase/util': 1.10.0 + + '@firebase/database@1.0.8': + dependencies: + '@firebase/app-check-interop-types': 0.3.2 + '@firebase/auth-interop-types': 0.2.3 + '@firebase/component': 0.6.9 + '@firebase/logger': 0.4.2 + '@firebase/util': 1.10.0 + faye-websocket: 0.11.4 + tslib: 2.8.1 + + '@firebase/database@1.1.3': + dependencies: + '@firebase/app-check-interop-types': 0.3.4 + '@firebase/auth-interop-types': 0.2.5 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + faye-websocket: 0.11.4 + tslib: 2.8.1 + + '@firebase/firestore-compat@0.4.9(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/firestore': 4.14.1(@firebase/app@0.14.12) + '@firebase/firestore-types': 3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + + '@firebase/firestore-types@3.0.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + dependencies: + '@firebase/app-types': 0.9.5 + '@firebase/util': 1.15.1 + + '@firebase/firestore@4.14.1(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + '@firebase/webchannel-wrapper': 1.0.6 + '@grpc/grpc-js': 1.9.15 + '@grpc/proto-loader': 0.7.15 + tslib: 2.8.1 + + '@firebase/functions-compat@0.4.4(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/functions': 0.13.4(@firebase/app@0.14.12) + '@firebase/functions-types': 0.6.4 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + + '@firebase/functions-types@0.6.4': {} + + '@firebase/functions@0.13.4(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/app-check-interop-types': 0.3.4 + '@firebase/auth-interop-types': 0.2.5 + '@firebase/component': 0.7.3 + '@firebase/messaging-interop-types': 0.2.4 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/installations-compat@0.2.22(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/installations': 0.6.22(@firebase/app@0.14.12) + '@firebase/installations-types': 0.5.4(@firebase/app-types@0.9.5) + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + + '@firebase/installations-types@0.5.4(@firebase/app-types@0.9.5)': + dependencies: + '@firebase/app-types': 0.9.5 + + '@firebase/installations@0.6.22(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/util': 1.15.1 + idb: 7.1.1 + tslib: 2.8.1 + + '@firebase/logger@0.4.2': + dependencies: + tslib: 2.8.1 + + '@firebase/logger@0.5.1': + dependencies: + tslib: 2.8.1 + + '@firebase/messaging-compat@0.2.26(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/messaging': 0.12.26(@firebase/app@0.14.12) + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + + '@firebase/messaging-interop-types@0.2.4': {} + + '@firebase/messaging@0.12.26(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/installations': 0.6.22(@firebase/app@0.14.12) + '@firebase/messaging-interop-types': 0.2.4 + '@firebase/util': 1.15.1 + idb: 7.1.1 + tslib: 2.8.1 + + '@firebase/performance-compat@0.2.25(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/performance': 0.7.12(@firebase/app@0.14.12) + '@firebase/performance-types': 0.2.4 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + + '@firebase/performance-types@0.2.4': {} + + '@firebase/performance@0.7.12(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/installations': 0.6.22(@firebase/app@0.14.12) + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + web-vitals: 4.2.4 + + '@firebase/remote-config-compat@0.2.24(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/logger': 0.5.1 + '@firebase/remote-config': 0.8.3(@firebase/app@0.14.12) + '@firebase/remote-config-types': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + + '@firebase/remote-config-types@0.5.1': {} + + '@firebase/remote-config@0.8.3(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/installations': 0.6.22(@firebase/app@0.14.12) + '@firebase/logger': 0.5.1 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/storage-compat@0.4.3(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12)': + dependencies: + '@firebase/app-compat': 0.5.12 + '@firebase/component': 0.7.3 + '@firebase/storage': 0.14.3(@firebase/app@0.14.12) + '@firebase/storage-types': 0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1) + '@firebase/util': 1.15.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + + '@firebase/storage-types@0.8.4(@firebase/app-types@0.9.5)(@firebase/util@1.15.1)': + dependencies: + '@firebase/app-types': 0.9.5 + '@firebase/util': 1.15.1 + + '@firebase/storage@0.14.3(@firebase/app@0.14.12)': + dependencies: + '@firebase/app': 0.14.12 + '@firebase/component': 0.7.3 + '@firebase/util': 1.15.1 + tslib: 2.8.1 + + '@firebase/util@1.10.0': + dependencies: + tslib: 2.8.1 + + '@firebase/util@1.15.1': + dependencies: + tslib: 2.8.1 + + '@firebase/webchannel-wrapper@1.0.6': {} + '@google-cloud/cloud-sql-connector@1.10.0': dependencies: '@googleapis/sqladmin': 35.2.0 @@ -6011,14 +7020,50 @@ snapshots: transitivePeerDependencies: - supports-color + '@google-cloud/firestore@7.11.6': + dependencies: + '@opentelemetry/api': 1.9.1 + fast-deep-equal: 3.1.3 + functional-red-black-tree: 1.0.1 + google-gax: 4.6.1 + protobufjs: 7.5.8 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + '@google-cloud/functions-framework@5.0.2': + dependencies: + '@types/express': 5.0.6 + body-parser: 2.2.2 + cloudevents: 10.0.0 + express: 5.2.1 + minimist: 1.2.8 + on-finished: 2.4.1 + read-package-up: 11.0.0 + transitivePeerDependencies: + - supports-color + + '@google-cloud/paginator@5.0.2': + dependencies: + arrify: 2.0.1 + extend: 3.0.2 + optional: true + '@google-cloud/paginator@6.0.0': dependencies: extend: 3.0.2 '@google-cloud/precise-date@5.0.0': {} + '@google-cloud/projectify@4.0.0': + optional: true + '@google-cloud/projectify@5.0.0': {} + '@google-cloud/promisify@4.0.0': + optional: true + '@google-cloud/promisify@5.0.0': {} '@google-cloud/pubsub@5.3.0': @@ -6041,6 +7086,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@google-cloud/storage@7.19.0': + dependencies: + '@google-cloud/paginator': 5.0.2 + '@google-cloud/projectify': 4.0.0 + '@google-cloud/promisify': 4.0.0 + abort-controller: 3.0.0 + async-retry: 1.3.3 + duplexify: 4.1.3 + fast-xml-parser: 5.8.0 + gaxios: 6.7.1 + google-auth-library: 9.15.1 + html-entities: 2.6.0 + mime: 3.0.0 + p-limit: 3.1.0 + retry-request: 7.0.2 + teeny-request: 9.0.0 + uuid: 8.3.2 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + '@googleapis/sqladmin@35.2.0': dependencies: googleapis-common: 8.0.1 @@ -6052,6 +7119,18 @@ snapshots: '@grpc/proto-loader': 0.8.1 '@js-sdsl/ordered-map': 4.4.2 + '@grpc/grpc-js@1.9.15': + dependencies: + '@grpc/proto-loader': 0.7.15 + '@types/node': 17.0.45 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.5.8 + yargs: 17.7.2 + '@grpc/proto-loader@0.8.1': dependencies: lodash.camelcase: 4.3.0 @@ -6596,6 +7675,9 @@ snapshots: '@next/swc-win32-x64-msvc@16.2.6': optional: true + '@nodable/entities@2.1.1': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6711,6 +7793,9 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@tootallnate/once@2.0.1': + optional: true + '@tootallnate/quickjs-emscripten@0.23.0': {} '@ts-morph/common@0.12.3': @@ -6748,6 +7833,22 @@ snapshots: dependencies: '@babel/types': 7.29.0 + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.19.41 + + '@types/caseless@0.12.5': + optional: true + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.41 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 20.19.41 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -6760,6 +7861,27 @@ snapshots: '@types/estree@1.0.9': {} + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 20.19.41 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@4.17.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -6785,14 +7907,57 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 20.19.41 + + '@types/lodash@4.17.24': {} + + '@types/long@4.0.2': + optional: true + + '@types/ms@2.1.0': {} + '@types/node@17.0.45': {} + '@types/node@20.19.41': + dependencies: + undici-types: 6.21.0 + + '@types/node@22.19.19': + dependencies: + undici-types: 6.21.0 + + '@types/normalize-package-data@2.4.4': {} + '@types/parse-json@4.0.2': {} + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + '@types/react@19.2.14': dependencies: csstype: 3.2.3 + '@types/request@2.48.13': + dependencies: + '@types/caseless': 0.12.5 + '@types/node': 20.19.41 + '@types/tough-cookie': 4.0.5 + form-data: 2.5.5 + optional: true + + '@types/send@1.2.1': + dependencies: + '@types/node': 20.19.41 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 20.19.41 + '@types/stack-utils@2.0.3': {} '@types/tough-cookie@4.0.5': {} @@ -6805,6 +7970,22 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.3(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/type-utils': 8.59.3(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.3 + eslint: 9.39.4 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -6821,6 +8002,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.3 + debug: 4.4.3 + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.59.3 @@ -6833,6 +8026,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.59.3(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@5.9.3) + '@typescript-eslint/types': 8.59.3 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.59.3(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@6.0.3) @@ -6847,10 +8049,26 @@ snapshots: '@typescript-eslint/types': 8.59.3 '@typescript-eslint/visitor-keys': 8.59.3 + '@typescript-eslint/tsconfig-utils@8.59.3(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.59.3(typescript@6.0.3)': dependencies: typescript: 6.0.3 + '@typescript-eslint/type-utils@8.59.3(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/type-utils@8.59.3(eslint@9.39.4)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.59.3 @@ -6865,6 +8083,21 @@ snapshots: '@typescript-eslint/types@8.59.3': {} + '@typescript-eslint/typescript-estree@8.59.3(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.59.3(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@5.9.3) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/visitor-keys': 8.59.3 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.0 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/typescript-estree@8.59.3(typescript@6.0.3)': dependencies: '@typescript-eslint/project-service': 8.59.3(typescript@6.0.3) @@ -6880,6 +8113,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.59.3(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.59.3(eslint@9.39.4)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) @@ -7064,6 +8308,13 @@ snapshots: acorn@8.16.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + agent-base@7.1.4: {} ajv-formats@2.1.1(ajv@8.20.0): @@ -7182,6 +8433,8 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 + array-union@2.1.0: {} + array.prototype.findlast@1.2.5: dependencies: call-bind: 1.0.9 @@ -7247,6 +8500,11 @@ snapshots: async-lock@1.4.1: {} + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + optional: true + async@3.2.6: {} asynckit@0.4.0: {} @@ -7581,6 +8839,15 @@ snapshots: clone@1.0.4: {} + cloudevents@10.0.0: + dependencies: + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + json-bigint: 1.0.0 + process: 0.11.10 + util: 0.12.5 + uuid: 8.3.2 + co@4.6.0: {} code-block-writer@11.0.3: {} @@ -7632,6 +8899,8 @@ snapshots: commander@6.2.1: {} + commander@9.5.0: {} + compress-commons@6.0.2: dependencies: crc-32: 1.2.2 @@ -7851,6 +9120,10 @@ snapshots: detect-newline@3.1.0: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + discontinuous-range@1.0.0: {} doctrine@2.1.0: @@ -8090,6 +9363,16 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.59.3(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4): dependencies: debug: 3.2.7 @@ -8100,6 +9383,35 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4) + hasown: 2.0.3 + is-core-module: 2.16.2 + is-glob: 4.0.3 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.59.3(eslint@9.39.4)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4): dependencies: '@rtsao/scc': 1.1.0 @@ -8416,6 +9728,8 @@ snapshots: extend@3.0.2: {} + farmhash-modern@1.1.0: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -8434,10 +9748,29 @@ snapshots: fast-uri@3.1.2: {} + fast-xml-builder@1.2.0: + dependencies: + path-expression-matcher: 1.5.0 + xml-naming: 0.1.0 + optional: true + + fast-xml-parser@5.8.0: + dependencies: + '@nodable/entities': 2.1.1 + fast-xml-builder: 1.2.0 + path-expression-matcher: 1.5.0 + strnum: 2.3.0 + xml-naming: 0.1.0 + optional: true + fastq@1.20.1: dependencies: reusify: 1.1.0 + faye-websocket@0.11.4: + dependencies: + websocket-driver: 0.7.4 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -8506,6 +9839,8 @@ snapshots: find-root@1.1.0: {} + find-up-simple@1.0.1: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -8516,6 +9851,42 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + firebase-admin@12.7.0: + dependencies: + '@fastify/busboy': 3.2.0 + '@firebase/database-compat': 1.0.8 + '@firebase/database-types': 1.0.5 + '@types/node': 22.19.19 + farmhash-modern: 1.1.0 + jsonwebtoken: 9.0.3 + jwks-rsa: 3.2.2 + node-forge: 1.4.0 + uuid: 10.0.0 + optionalDependencies: + '@google-cloud/firestore': 7.11.6 + '@google-cloud/storage': 7.19.0 + transitivePeerDependencies: + - encoding + - supports-color + + firebase-functions-test@0.2.3(firebase-admin@12.7.0)(firebase-functions@4.9.0(firebase-admin@12.7.0)): + dependencies: + '@types/lodash': 4.17.24 + firebase-admin: 12.7.0 + firebase-functions: 4.9.0(firebase-admin@12.7.0) + lodash: 4.18.1 + + firebase-functions@4.9.0(firebase-admin@12.7.0): + dependencies: + '@types/cors': 2.8.19 + '@types/express': 4.17.3 + cors: 2.8.6 + express: 4.22.2 + firebase-admin: 12.7.0 + protobufjs: 7.5.8 + transitivePeerDependencies: + - supports-color + firebase-tools@15.18.0(@types/node@17.0.45): dependencies: '@apphosting/common': 0.0.8 @@ -8605,6 +9976,39 @@ snapshots: - supports-color - utf-8-validate + firebase@12.13.0: + dependencies: + '@firebase/ai': 2.12.0(@firebase/app-types@0.9.5)(@firebase/app@0.14.12) + '@firebase/analytics': 0.10.22(@firebase/app@0.14.12) + '@firebase/analytics-compat': 0.2.28(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12) + '@firebase/app': 0.14.12 + '@firebase/app-check': 0.11.3(@firebase/app@0.14.12) + '@firebase/app-check-compat': 0.4.3(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12) + '@firebase/app-compat': 0.5.12 + '@firebase/app-types': 0.9.5 + '@firebase/auth': 1.13.1(@firebase/app@0.14.12) + '@firebase/auth-compat': 0.6.6(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12) + '@firebase/data-connect': 0.7.0(@firebase/app@0.14.12) + '@firebase/database': 1.1.3 + '@firebase/database-compat': 2.1.4 + '@firebase/firestore': 4.14.1(@firebase/app@0.14.12) + '@firebase/firestore-compat': 0.4.9(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12) + '@firebase/functions': 0.13.4(@firebase/app@0.14.12) + '@firebase/functions-compat': 0.4.4(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12) + '@firebase/installations': 0.6.22(@firebase/app@0.14.12) + '@firebase/installations-compat': 0.2.22(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12) + '@firebase/messaging': 0.12.26(@firebase/app@0.14.12) + '@firebase/messaging-compat': 0.2.26(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12) + '@firebase/performance': 0.7.12(@firebase/app@0.14.12) + '@firebase/performance-compat': 0.2.25(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12) + '@firebase/remote-config': 0.8.3(@firebase/app@0.14.12) + '@firebase/remote-config-compat': 0.2.24(@firebase/app-compat@0.5.12)(@firebase/app@0.14.12) + '@firebase/storage': 0.14.3(@firebase/app@0.14.12) + '@firebase/storage-compat': 0.4.3(@firebase/app-compat@0.5.12)(@firebase/app-types@0.9.5)(@firebase/app@0.14.12) + '@firebase/util': 1.15.1 + transitivePeerDependencies: + - '@react-native-async-storage/async-storage' + flat-cache@4.0.1: dependencies: flatted: 3.4.2 @@ -8623,6 +10027,16 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@2.5.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.3 + mime-types: 2.1.35 + safe-buffer: 5.2.1 + optional: true + form-data@4.0.5: dependencies: asynckit: 0.4.0 @@ -8665,6 +10079,9 @@ snapshots: hasown: 2.0.3 is-callable: 1.2.7 + functional-red-black-tree@1.0.1: + optional: true + functions-have-names@1.2.3: {} fuzzy@0.1.3: {} @@ -8804,6 +10221,15 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + google-auth-library@10.6.2: dependencies: base64-js: 1.5.1 @@ -8827,6 +10253,25 @@ snapshots: - encoding - supports-color + google-gax@4.6.1: + dependencies: + '@grpc/grpc-js': 1.14.3 + '@grpc/proto-loader': 0.7.15 + '@types/long': 4.0.2 + abort-controller: 3.0.0 + duplexify: 4.1.3 + google-auth-library: 9.15.1 + node-fetch: 2.7.0 + object-hash: 3.0.0 + proto3-json-serializer: 2.0.2 + protobufjs: 7.5.8 + retry-request: 7.0.2 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + google-gax@5.0.6: dependencies: '@grpc/grpc-js': 1.14.3 @@ -8920,10 +10365,17 @@ snapshots: hono@4.12.18: {} + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 + html-entities@2.6.0: + optional: true + html-escaper@2.0.2: {} http-errors@2.0.1: @@ -8934,6 +10386,17 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http-parser-js@0.5.10: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.1 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -8941,6 +10404,14 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -8962,6 +10433,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + idb@7.1.1: {} + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -8984,6 +10457,8 @@ snapshots: indent-string@4.0.0: {} + index-to-position@1.2.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -9010,6 +10485,11 @@ snapshots: ipaddr.js@1.9.1: {} + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.9 @@ -9537,7 +11017,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 17.0.45 + '@types/node': 20.19.41 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -9570,6 +11050,8 @@ snapshots: url-join: 0.0.1 valid-url: 1.0.9 + jose@4.15.9: {} + jose@6.2.3: {} js-tokens@4.0.0: {} @@ -9672,6 +11154,16 @@ snapshots: ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 + jwks-rsa@3.2.2: + dependencies: + '@types/jsonwebtoken': 9.0.10 + debug: 4.4.3 + jose: 4.15.9 + limiter: 1.1.5 + lru-memoizer: 2.3.0 + transitivePeerDependencies: + - supports-color + jws@4.0.1: dependencies: jwa: 2.0.1 @@ -9706,6 +11198,8 @@ snapshots: libsodium@0.7.16: {} + limiter@1.1.5: {} + lines-and-columns@1.2.4: {} loader-runner@4.3.2: {} @@ -9728,6 +11222,8 @@ snapshots: lodash.camelcase@4.3.0: {} + lodash.clonedeep@4.5.0: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} @@ -9780,8 +11276,17 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lru-cache@7.18.3: {} + lru-memoizer@2.3.0: + dependencies: + lodash.clonedeep: 4.5.0 + lru-cache: 6.0.0 + lsofi@2.0.0: {} lz-string@1.5.0: {} @@ -9850,6 +11355,9 @@ snapshots: mime@2.6.0: {} + mime@3.0.0: + optional: true + mimic-fn@2.1.0: {} mimic-response@3.1.0: {} @@ -9908,6 +11416,8 @@ snapshots: mute-stream@2.0.0: {} + mylas@2.1.14: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -9999,6 +11509,8 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + node-forge@1.4.0: {} + node-gyp@12.3.0: dependencies: env-paths: 2.2.1 @@ -10022,6 +11534,12 @@ snapshots: abbrev: 4.0.0 optional: true + normalize-package-data@6.0.2: + dependencies: + hosted-git-info: 7.0.2 + semver: 7.8.0 + validate-npm-package-license: 3.0.4 + normalize-path@3.0.0: {} npm-run-path@4.0.1: @@ -10184,6 +11702,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.0 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse5-htmlparser2-tree-adapter@6.0.1: dependencies: parse5: 6.0.1 @@ -10202,6 +11726,9 @@ snapshots: path-exists@4.0.0: {} + path-expression-matcher@1.5.0: + optional: true + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -10274,6 +11801,10 @@ snapshots: dependencies: find-up: 4.1.0 + plimit-lit@1.6.1: + dependencies: + queue-lit: 1.5.2 + portfinder@1.0.38: dependencies: async: 3.2.6 @@ -10350,6 +11881,11 @@ snapshots: proto-list@1.2.4: {} + proto3-json-serializer@2.0.2: + dependencies: + protobufjs: 7.5.8 + optional: true + proto3-json-serializer@3.0.4: dependencies: protobufjs: 7.5.8 @@ -10366,7 +11902,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.1 - '@types/node': 17.0.45 + '@types/node': 20.19.41 long: 5.3.2 proxy-addr@2.0.7: @@ -10406,6 +11942,8 @@ snapshots: dependencies: side-channel: 1.1.0 + queue-lit@1.5.2: {} + queue-microtask@1.2.3: {} railroad-diagrams@1.0.0: {} @@ -10460,6 +11998,20 @@ snapshots: react@19.2.6: {} + read-package-up@11.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 9.0.1 + type-fest: 4.41.0 + + read-pkg@9.0.1: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 6.0.2 + parse-json: 8.3.0 + type-fest: 4.41.0 + unicorn-magic: 0.1.0 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -10566,6 +12118,16 @@ snapshots: ret@0.1.15: {} + retry-request@7.0.2: + dependencies: + '@types/request': 2.48.13 + extend: 3.0.2 + teeny-request: 9.0.0 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + retry-request@8.0.2: dependencies: extend: 3.0.2 @@ -10863,6 +12425,20 @@ snapshots: source-map@0.6.1: {} + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + split2@4.2.0: {} sprintf-js@1.0.3: {} @@ -11005,6 +12581,9 @@ snapshots: strip-json-comments@3.1.1: {} + strnum@2.3.0: + optional: true + stubs@3.0.0: {} styled-jsx@5.1.6(@babel/core@7.29.0)(babel-plugin-macros@3.1.0)(react@19.2.6): @@ -11127,6 +12706,18 @@ snapshots: transitivePeerDependencies: - supports-color + teeny-request@9.0.0: + dependencies: + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + stream-events: 1.0.5 + uuid: 9.0.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + teex@1.0.1: dependencies: streamx: 2.25.0 @@ -11210,6 +12801,10 @@ snapshots: true-myth@4.1.1: {} + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 @@ -11248,6 +12843,16 @@ snapshots: true-myth: 4.1.1 ts-morph: 13.0.3 + tsc-alias@1.8.17: + dependencies: + chokidar: 3.6.0 + commander: 9.5.0 + get-tsconfig: 4.14.0 + globby: 11.1.0 + mylas: 2.1.14 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -11344,11 +12949,15 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 + undici-types@6.21.0: {} + undici@6.25.0: optional: true unicode-emoji-modifier-base@1.0.0: {} + unicorn-magic@0.1.0: {} + unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -11425,8 +13034,18 @@ snapshots: util-deprecate@1.0.2: {} + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.2 + is-typed-array: 1.1.15 + which-typed-array: 1.1.20 + utils-merge@1.0.1: {} + uuid@10.0.0: {} + uuid@14.0.0: {} uuid@8.3.2: {} @@ -11441,6 +13060,11 @@ snapshots: valid-url@1.0.9: {} + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + vary@1.1.2: {} w3c-xmlserializer@5.0.0: @@ -11462,6 +13086,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + web-vitals@4.2.4: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} @@ -11508,6 +13134,14 @@ snapshots: - postcss - uglify-js + websocket-driver@0.7.4: + dependencies: + http-parser-js: 0.5.10 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + + websocket-extensions@0.1.4: {} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 @@ -11644,6 +13278,9 @@ snapshots: xml-name-validator@5.0.0: {} + xml-naming@0.1.0: + optional: true + xmlchars@2.2.0: {} xtend@4.0.2: {} @@ -11652,6 +13289,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yallist@5.0.0: {} yaml@1.10.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2d655e5..4880f86 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,11 +1,13 @@ packages: - 'packages/*' allowBuilds: + '@firebase/util': false esbuild: false protobufjs: false re2: false sharp: false unrs-resolver: false + grpc: false overrides: lodash: ">=4.17.24" fast-uri: ">=3.1.2"