diff --git a/apps/api/package.json b/apps/api/package.json index 7632eb6..ada9c1c 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -7,10 +7,12 @@ "start": "tsx src/index.ts" }, "dependencies": { - "@hono/node-server": "^1.19.5", + "@effect/platform": "^0.93.2", + "@effect/platform-node": "^0.101.1", + "@effect/rpc": "^0.72.2", "@money/shared": "workspace:*", "better-auth": "^1.3.27", - "hono": "^4.9.12", + "effect": "^3.19.4", "plaid": "^39.0.0", "tsx": "^4.20.6" }, diff --git a/apps/api/src/auth/better-auth.ts b/apps/api/src/auth/better-auth.ts new file mode 100644 index 0000000..d922a10 --- /dev/null +++ b/apps/api/src/auth/better-auth.ts @@ -0,0 +1,23 @@ +import { Effect, Layer } from "effect"; +import { Authorization } from "./context"; +import { auth } from "./config"; +import { AuthorizationError, AuthorizationUnknownError } from "./errors"; + +export const make = () => + Effect.gen(function* () { + return Authorization.of({ + use: (fn) => + Effect.gen(function* () { + const data = yield* Effect.tryPromise({ + try: () => fn(auth), + catch: (error) => + error instanceof Error + ? new AuthorizationError({ message: error.message }) + : new AuthorizationUnknownError(), + }); + return data; + }), + }); + }); + +export const BetterAuthLive = Layer.scoped(Authorization, make()); diff --git a/apps/api/src/auth.ts b/apps/api/src/auth/config.ts similarity index 97% rename from apps/api/src/auth.ts rename to apps/api/src/auth/config.ts index e227df7..d230596 100644 --- a/apps/api/src/auth.ts +++ b/apps/api/src/auth/config.ts @@ -3,7 +3,7 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { bearer, deviceAuthorization, genericOAuth } from "better-auth/plugins"; import { expo } from "@better-auth/expo"; import { drizzleSchema } from "@money/shared/db"; -import { db } from "./db"; +import { db } from "../db"; import { BASE_URL, HOST } from "@money/shared"; export const auth = betterAuth({ diff --git a/apps/api/src/auth/context.ts b/apps/api/src/auth/context.ts new file mode 100644 index 0000000..9fe2e85 --- /dev/null +++ b/apps/api/src/auth/context.ts @@ -0,0 +1,14 @@ +import { Context, type Effect } from "effect"; +import type { AuthorizationError, AuthorizationUnknownError } from "./errors"; +import type { auth } from "./config"; + +export class Authorization extends Context.Tag("Authorization")< + Authorization, + AuthorizationImpl +>() {} + +export interface AuthorizationImpl { + use: ( + fn: (client: typeof auth) => Promise, + ) => Effect.Effect; +} diff --git a/apps/api/src/auth/errors.ts b/apps/api/src/auth/errors.ts new file mode 100644 index 0000000..7ff41e6 --- /dev/null +++ b/apps/api/src/auth/errors.ts @@ -0,0 +1,8 @@ +import { Data } from "effect"; + +export class AuthorizationUnknownError extends Data.TaggedError( + "AuthClientUnknownError", +) {} +export class AuthorizationError extends Data.TaggedError("AuthorizationError")<{ + message: string; +}> {} diff --git a/apps/api/src/auth/handler.ts b/apps/api/src/auth/handler.ts new file mode 100644 index 0000000..bdd642a --- /dev/null +++ b/apps/api/src/auth/handler.ts @@ -0,0 +1,70 @@ +import { + HttpApi, + HttpApiBuilder, + HttpApiEndpoint, + HttpApiGroup, + HttpLayerRouter, + HttpServerRequest, +} from "@effect/platform"; +import * as NodeHttpServerRequest from "@effect/platform-node/NodeHttpServerRequest"; +import { toNodeHandler } from "better-auth/node"; +import { Effect, Layer } from "effect"; +import { AuthorizationError } from "./errors"; +import { auth } from "./config"; + +const authHandler = Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest; + const nodeRequest = NodeHttpServerRequest.toIncomingMessage(request); + const nodeResponse = NodeHttpServerRequest.toServerResponse(request); + + nodeResponse.setHeader("Access-Control-Allow-Origin", "http://laptop:8081"); + nodeResponse.setHeader( + "Access-Control-Allow-Methods", + "GET, POST, PUT, DELETE, OPTIONS", + ); + nodeResponse.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, Authorization, B3, traceparent, Cookie", + ); + nodeResponse.setHeader("Access-Control-Max-Age", "600"); + nodeResponse.setHeader("Access-Control-Allow-Credentials", "true"); + + // Handle preflight requests + if (nodeRequest.method === "OPTIONS") { + nodeResponse.statusCode = 200; + nodeResponse.end(); + + return; + // return nodeResponse; + } + + yield* Effect.tryPromise({ + try: () => toNodeHandler(auth)(nodeRequest, nodeResponse), + catch: (error) => { + return new AuthorizationError({ message: `${error}` }); + }, + }); + + // return nodeResponse; +}); + +export class AuthContractGroup extends HttpApiGroup.make("auth") + .add(HttpApiEndpoint.get("get", "/*")) + .add(HttpApiEndpoint.post("post", "/*")) + .add(HttpApiEndpoint.options("options", "/*")) + .prefix("/api/auth") {} + +export class DomainApi extends HttpApi.make("domain").add(AuthContractGroup) {} + +export const Api = HttpApi.make("api").addHttpApi(DomainApi); + +const AuthLive = HttpApiBuilder.group(Api, "auth", (handlers) => + handlers + .handle("get", () => authHandler.pipe(Effect.orDie)) + .handle("post", () => authHandler.pipe(Effect.orDie)) + .handle("options", () => authHandler.pipe(Effect.orDie)), +); + +export const AuthRoute = HttpLayerRouter.addHttpApi(Api).pipe( + Layer.provide(AuthLive), +); diff --git a/apps/api/src/hono.ts b/apps/api/src/hono.ts deleted file mode 100644 index 9142dd2..0000000 --- a/apps/api/src/hono.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { AuthData } from "@money/shared/auth"; -import { Hono } from "hono"; - -export const getHono = () => - new Hono<{ Variables: { auth: AuthData | null } }>(); diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 25cee8f..1070357 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,58 +1,49 @@ -import { serve } from "@hono/node-server"; -import { authDataSchema } from "@money/shared/auth"; +import * as Layer from "effect/Layer"; +import * as Effect from "effect/Effect"; +import * as HttpLayerRouter from "@effect/platform/HttpLayerRouter"; +import * as HttpServerResponse from "@effect/platform/HttpServerResponse"; +import * as NodeRuntime from "@effect/platform-node/NodeRuntime"; +import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer"; +import { createServer } from "http"; +import { AuthRoute } from "./auth/handler"; +import { BetterAuthLive } from "./auth/better-auth"; +import { WebhookReceiverRoute } from "./webhook"; +import { ZeroMutateRoute, ZeroQueryRoute } from "./zero/handler"; +import { RpcRoute } from "./rpc/handler"; import { BASE_URL } from "@money/shared"; -import { cors } from "hono/cors"; -import { auth } from "./auth"; -import { getHono } from "./hono"; -import { zero } from "./zero"; -import { webhook } from "./webhook"; +import { CurrentSession, SessionMiddleware } from "./middleware/session"; -const app = getHono(); +const RootRoute = HttpLayerRouter.add( + "GET", + "/", + Effect.gen(function* () { + const d = yield* CurrentSession; -app.use( - "/api/*", - cors({ - origin: ["https://money.koon.us", `${BASE_URL}:8081`], - allowMethods: ["POST", "GET", "OPTIONS"], - allowHeaders: ["Content-Type", "Authorization"], - credentials: true, + return HttpServerResponse.text("OK"); }), ); -app.on(["GET", "POST"], "/api/auth/*", (c) => auth.handler(c.req.raw)); - -app.use("*", async (c, next) => { - const authHeader = c.req.raw.headers.get("Authorization"); - const cookie = authHeader?.split("Bearer ")[1]; - - const newHeaders = new Headers(c.req.raw.headers); - - if (cookie) { - newHeaders.set("Cookie", cookie); - } - - const session = await auth.api.getSession({ headers: newHeaders }); - - if (!session) { - c.set("auth", null); - return next(); - } - c.set("auth", authDataSchema.parse(session)); - return next(); -}); - -app.route("/api/zero", zero); - -app.get("/api", (c) => c.text("OK")); -app.get("/api/webhook_receiver", webhook); -app.get("/", (c) => c.text("OK")); - -serve( - { - fetch: app.fetch, - port: process.env.PORT ? parseInt(process.env.PORT) : 3000, - }, - (info) => { - console.log(`Server is running on ${info.address}:${info.port}`); - }, +const AllRoutes = Layer.mergeAll( + RootRoute, + AuthRoute, + ZeroQueryRoute, + ZeroMutateRoute, + RpcRoute, + WebhookReceiverRoute, +).pipe( + Layer.provide(SessionMiddleware.layer), + Layer.provide( + HttpLayerRouter.cors({ + allowedOrigins: ["https://money.koon.us", `${BASE_URL}:8081`], + allowedMethods: ["POST", "GET", "OPTIONS"], + credentials: true, + }), + ), +); + +HttpLayerRouter.serve(AllRoutes).pipe( + Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })), + Layer.provide(BetterAuthLive), + Layer.launch, + NodeRuntime.runMain, ); diff --git a/apps/api/src/middleware/cors.ts b/apps/api/src/middleware/cors.ts new file mode 100644 index 0000000..bcec7b9 --- /dev/null +++ b/apps/api/src/middleware/cors.ts @@ -0,0 +1,12 @@ +import { HttpLayerRouter, HttpMiddleware } from "@effect/platform"; +import { BASE_URL } from "@money/shared"; + +export const CorsMiddleware = HttpLayerRouter.middleware( + HttpMiddleware.cors({ + allowedOrigins: ["https://money.koon.us", `${BASE_URL}:8081`], + allowedMethods: ["POST", "GET", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization"], + credentials: true, + }), + { global: true }, +); diff --git a/apps/api/src/middleware/session.ts b/apps/api/src/middleware/session.ts new file mode 100644 index 0000000..35c671e --- /dev/null +++ b/apps/api/src/middleware/session.ts @@ -0,0 +1,61 @@ +import { AuthSchema } from "@money/shared/auth"; +import { Context, Effect, Schema, Console } from "effect"; +import { Authorization } from "../auth/context"; +import { HttpLayerRouter, HttpServerRequest } from "@effect/platform"; + +export class CurrentSession extends Context.Tag("CurrentSession")< + CurrentSession, + { readonly auth: Schema.Schema.Type | null } +>() {} + +export const SessionMiddleware = HttpLayerRouter.middleware<{ + provides: CurrentSession; +}>()( + Effect.gen(function* () { + const auth = yield* Authorization; + + return (httpEffect) => + Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest; + const headers = { ...request.headers }; + + const token = yield* HttpServerRequest.schemaHeaders( + Schema.Struct({ + authorization: Schema.optional(Schema.String), + }), + ).pipe( + Effect.tap(Console.debug), + Effect.flatMap(({ authorization }) => + authorization != undefined + ? parseAuthorization(authorization) + : Effect.succeed(undefined), + ), + ); + if (token) { + headers["cookie"] = token; + } + + const session = yield* auth + .use((auth) => auth.api.getSession({ headers })) + .pipe( + Effect.flatMap((s) => + s == null ? Effect.succeed(null) : Schema.decode(AuthSchema)(s), + ), + // Effect.tap((s) => Console.debug("Auth result", s)), + ); + + return yield* Effect.provideService(httpEffect, CurrentSession, { + auth: session, + }); + }); + }), +); + +const parseAuthorization = (input: string) => + Effect.gen(function* () { + const m = /^Bearer\s+(.+)$/.exec(input); + if (!m) { + return yield* Effect.fail(new Error("Invalid token")); + } + return m[1]; + }); diff --git a/apps/api/src/rpc/handler.ts b/apps/api/src/rpc/handler.ts new file mode 100644 index 0000000..06547c9 --- /dev/null +++ b/apps/api/src/rpc/handler.ts @@ -0,0 +1,76 @@ +import { RpcSerialization, RpcServer } from "@effect/rpc"; +import { Console, Effect, Layer, Schema } from "effect"; +import { LinkRpcs, Link, AuthMiddleware } from "@money/shared/rpc"; +import { CurrentSession } from "../middleware/session"; +import { Authorization } from "../auth/context"; +import { HttpServerRequest } from "@effect/platform"; +import { AuthSchema } from "@money/shared/auth"; + +const parseAuthorization = (input: string) => + Effect.gen(function* () { + const m = /^Bearer\s+(.+)$/.exec(input); + if (!m) { + return yield* Effect.fail(new Error("Invalid token")); + } + return m[1]; + }); + +export const AuthLive = Layer.scoped( + AuthMiddleware, + Effect.gen(function* () { + const auth = yield* Authorization; + + return AuthMiddleware.of(({ headers, payload, rpc }) => + Effect.gen(function* () { + const newHeaders = { ...headers }; + + const token = yield* Schema.decodeUnknown( + Schema.Struct({ + authorization: Schema.optional(Schema.String), + }), + )(headers).pipe( + // Effect.tap(Console.debug), + Effect.flatMap(({ authorization }) => + authorization != undefined + ? parseAuthorization(authorization) + : Effect.succeed(undefined), + ), + ); + + if (token) { + newHeaders["cookie"] = token; + } + + const session = yield* auth + .use((auth) => auth.api.getSession({ headers: newHeaders })) + .pipe( + Effect.flatMap((s) => + s == null ? Effect.succeed(null) : Schema.decode(AuthSchema)(s), + ), + Effect.tap((s) => Console.debug("Auth result", s)), + ); + + return { auth: session }; + }).pipe(Effect.orDie), + ); + }), +); + +const LinkHandlers = LinkRpcs.toLayer({ + CreateLink: () => + Effect.gen(function* () { + const session = yield* CurrentSession; + + return new Link({ href: session.auth?.user.name || "anon" }); + }), +}); + +export const RpcRoute = RpcServer.layerHttpRouter({ + group: LinkRpcs, + path: "/rpc", + protocol: "http", +}).pipe( + Layer.provide(LinkHandlers), + Layer.provide(RpcSerialization.layerJson), + Layer.provide(AuthLive), +); diff --git a/apps/api/src/webhook.ts b/apps/api/src/webhook.ts index 267f23f..e1eb06b 100644 --- a/apps/api/src/webhook.ts +++ b/apps/api/src/webhook.ts @@ -1,11 +1,21 @@ -import type { Context } from "hono"; +import { + HttpLayerRouter, + HttpServerRequest, + HttpServerResponse, +} from "@effect/platform"; +import { Effect } from "effect"; + import { plaidClient } from "./plaid"; // import { LinkSessionFinishedWebhook, WebhookType } from "plaid"; -export const webhook = async (c: Context) => { - console.log("Got webhook"); - const b = await c.req.text(); - console.log("body:", b); +export const WebhookReceiverRoute = HttpLayerRouter.add( + "*", + "/api/webhook_receiver", - return c.text("Hi"); -}; + Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest; + const body = yield* request.json; + Effect.log("Got a webhook!", body); + return HttpServerResponse.text("HELLO THERE"); + }), +); diff --git a/apps/api/src/zero/errors.ts b/apps/api/src/zero/errors.ts new file mode 100644 index 0000000..1724e4b --- /dev/null +++ b/apps/api/src/zero/errors.ts @@ -0,0 +1,6 @@ +import { Data } from "effect"; + +export class ZeroUnknownError extends Data.TaggedError("ZeroUnknownError") {} +export class ZeroError extends Data.TaggedError("ZeroError")<{ + error: Error; +}> {} diff --git a/apps/api/src/zero/handler.ts b/apps/api/src/zero/handler.ts new file mode 100644 index 0000000..c39c444 --- /dev/null +++ b/apps/api/src/zero/handler.ts @@ -0,0 +1,106 @@ +import { + HttpLayerRouter, + HttpServerRequest, + HttpServerResponse, + Url, +} from "@effect/platform"; +import { Console, Effect, Layer } from "effect"; +import { CurrentSession, SessionMiddleware } from "../middleware/session"; +import { ZeroError, ZeroUnknownError } from "./errors"; +import { withValidation, type ReadonlyJSONValue } from "@rocicorp/zero"; +import { + handleGetQueriesRequest, + PushProcessor, + ZQLDatabase, +} from "@rocicorp/zero/server"; +import { BASE_URL, queries, schema } from "@money/shared"; +import type { AuthSchemaType } from "@money/shared/auth"; +import { PostgresJSConnection } from "@rocicorp/zero/pg"; +import postgres from "postgres"; +import { createMutators } from "./mutators"; + +const processor = new PushProcessor( + new ZQLDatabase( + new PostgresJSConnection(postgres(process.env.ZERO_UPSTREAM_DB! as string)), + schema, + ), +); + +export const ZeroQueryRoute = HttpLayerRouter.add( + "POST", + "/api/zero/get-queries", + + Effect.gen(function* () { + const { auth } = yield* CurrentSession; + + const request = yield* HttpServerRequest.HttpServerRequest; + const body = yield* request.json; + + const result = yield* Effect.tryPromise({ + try: () => + handleGetQueriesRequest( + (name, args) => ({ query: getQuery(auth, name, args) }), + schema, + body as ReadonlyJSONValue, + ), + catch: (error) => + error instanceof Error + ? new ZeroError({ error }) + : new ZeroUnknownError(), + }).pipe( + Effect.tapErrorTag("ZeroError", (err) => + Console.error("Zero Error", err.error), + ), + ); + + return yield* HttpServerResponse.json(result); + }), +).pipe(Layer.provide(SessionMiddleware.layer)); + +function getQuery( + authData: AuthSchemaType | null, + name: string, + args: readonly ReadonlyJSONValue[], +) { + if (name in validatedQueries) { + const q = validatedQueries[name]; + return q(authData, ...args); + } + throw new Error(`Unknown query: ${name}`); +} + +export const validatedQueries = Object.fromEntries( + Object.values(queries).map((q) => [q.queryName, withValidation(q)]), +); + +export const ZeroMutateRoute = HttpLayerRouter.add( + "POST", + "/api/zero/mutate", + + Effect.gen(function* () { + const { auth } = yield* CurrentSession; + + const request = yield* HttpServerRequest.HttpServerRequest; + const url = yield* Url.fromString(`${BASE_URL}${request.url}`); + const body = yield* request.json; + + const result = yield* Effect.tryPromise({ + try: () => + processor.process( + createMutators(auth), + url.searchParams, + body as ReadonlyJSONValue, + ), + catch: (error) => + error instanceof Error + ? new ZeroError({ error }) + : new ZeroUnknownError(), + }).pipe( + Effect.tapErrorTag("ZeroError", (err) => + Console.error("Zero Error", err.error), + ), + ); + + return yield* HttpServerResponse.json(result); + }), +).pipe(Layer.provide(SessionMiddleware.layer)); diff --git a/apps/api/src/zero.ts b/apps/api/src/zero/mutators.ts similarity index 79% rename from apps/api/src/zero.ts rename to apps/api/src/zero/mutators.ts index a59f50e..0529517 100644 --- a/apps/api/src/zero.ts +++ b/apps/api/src/zero/mutators.ts @@ -1,15 +1,3 @@ -import { - type ReadonlyJSONValue, - type Transaction, - withValidation, -} from "@rocicorp/zero"; -import { - handleGetQueriesRequest, - PushProcessor, - ZQLDatabase, -} from "@rocicorp/zero/server"; -import { PostgresJSConnection } from "@rocicorp/zero/pg"; -import postgres from "postgres"; import { createMutators as createMutatorsShared, isLoggedIn, @@ -18,36 +6,27 @@ import { type Mutators, type Schema, } from "@money/shared"; -import type { AuthData } from "@money/shared/auth"; -import { getHono } from "./hono"; +import type { AuthSchemaType } from "@money/shared/auth"; import { - Configuration, - CountryCode, - PlaidApi, - PlaidEnvironments, - Products, -} from "plaid"; -import { randomUUID } from "crypto"; -import { db } from "./db"; + type ReadonlyJSONValue, + type Transaction, + withValidation, +} from "@rocicorp/zero"; +import { plaidClient } from "../plaid"; +import { CountryCode, Products } from "plaid"; import { balance, plaidAccessTokens, plaidLink, transaction, } from "@money/shared/db"; +import { db } from "../db"; +import { randomUUID } from "crypto"; import { and, eq, inArray, sql, type InferInsertModel } from "drizzle-orm"; -import { plaidClient } from "./plaid"; - -const processor = new PushProcessor( - new ZQLDatabase( - new PostgresJSConnection(postgres(process.env.ZERO_UPSTREAM_DB! as string)), - schema, - ), -); type Tx = Transaction; -const createMutators = (authData: AuthData | null) => { +export const createMutators = (authData: AuthSchemaType | null) => { const mutators = createMutatorsShared(authData); return { ...mutators, @@ -221,41 +200,3 @@ const createMutators = (authData: AuthData | null) => { }, } as const satisfies Mutators; }; - -const zero = getHono() - .post("/mutate", async (c) => { - const authData = c.get("auth"); - - const result = await processor.process(createMutators(authData), c.req.raw); - - return c.json(result); - }) - .post("/get-queries", async (c) => { - const authData = c.get("auth"); - - const result = await handleGetQueriesRequest( - (name, args) => ({ query: getQuery(authData, name, args) }), - schema, - c.req.raw, - ); - - return c.json(result); - }); - -const validatedQueries = Object.fromEntries( - Object.values(queries).map((q) => [q.queryName, withValidation(q)]), -); - -function getQuery( - authData: AuthData | null, - name: string, - args: readonly ReadonlyJSONValue[], -) { - if (name in validatedQueries) { - const q = validatedQueries[name]; - return q(authData, ...args); - } - throw new Error(`Unknown query: ${name}`); -} - -export { zero }; diff --git a/apps/expo/app/_layout.tsx b/apps/expo/app/_layout.tsx index a8cff7c..0711ebc 100644 --- a/apps/expo/app/_layout.tsx +++ b/apps/expo/app/_layout.tsx @@ -4,7 +4,7 @@ import "react-native-reanimated"; import { authClient } from "@/lib/auth-client"; import { ZeroProvider } from "@rocicorp/zero/react"; import { useMemo } from "react"; -import { authDataSchema } from "@money/shared/auth"; +import { AuthSchema } from "@money/shared/auth"; import { Platform } from "react-native"; import type { ZeroOptions } from "@rocicorp/zero"; import { @@ -15,6 +15,7 @@ import { BASE_URL, } from "@money/shared"; import { expoSQLiteStoreProvider } from "@rocicorp/zero/react-native"; +import { Schema as S } from "effect"; export const unstable_settings = { anchor: "index", @@ -26,8 +27,8 @@ export default function RootLayout() { const { data: session, isPending } = authClient.useSession(); const authData = useMemo(() => { - const result = authDataSchema.safeParse(session); - return result.success ? result.data : null; + const result = session ? S.decodeSync(AuthSchema)(session) : null; + return result ? result : null; }, [session]); const cookie = useMemo(() => { diff --git a/apps/expo/package.json b/apps/expo/package.json index 8df15c9..a347722 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@better-auth/expo": "^1.3.27", + "@effect-atom/atom-react": "^0.4.0", "@expo/vector-icons": "^15.0.2", "@money/shared": "workspace:*", "@money/ui": "workspace:*", diff --git a/apps/tui/src/auth.ts b/apps/tui/src/auth.ts index 799afdd..8ff4690 100644 --- a/apps/tui/src/auth.ts +++ b/apps/tui/src/auth.ts @@ -11,9 +11,10 @@ import { } from "effect"; import { FileSystem } from "@effect/platform"; import { config } from "./config"; -import { AuthState } from "./schema"; import { authClient } from "@/lib/auth-client"; import type { BetterFetchResponse } from "@better-fetch/fetch"; +import { AuthSchema } from "@money/shared/auth"; +import { encode } from "node:punycode"; class AuthClientUnknownError extends Data.TaggedError( "AuthClientUnknownError", @@ -129,7 +130,7 @@ const pollToken = ({ device_code }: { device_code: string }) => const getFromFromDisk = Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const content = yield* fs.readFileString(config.authPath); - const auth = yield* Schema.decode(Schema.parseJson(AuthState))(content); + const auth = yield* Schema.decode(Schema.parseJson(AuthSchema))(content); if (auth.session.expiresAt < new Date()) yield* Effect.fail(new AuthClientExpiredToken()); return auth; @@ -160,10 +161,11 @@ const requestAuth = Effect.gen(function* () { ); if (sessionData == null) return yield* Effect.fail(new AuthClientNoData()); - const result = yield* Schema.decodeUnknown(AuthState)(sessionData); + const result = yield* Schema.decodeUnknown(AuthSchema)(sessionData); + const encoded = yield* Schema.encode(AuthSchema)(result); const fs = yield* FileSystem.FileSystem; - yield* fs.writeFileString(config.authPath, JSON.stringify(result)); + yield* fs.writeFileString(config.authPath, JSON.stringify(encoded)); return result; }); diff --git a/apps/tui/src/index.tsx b/apps/tui/src/index.tsx index 99cb0fb..0b73c61 100644 --- a/apps/tui/src/index.tsx +++ b/apps/tui/src/index.tsx @@ -5,13 +5,13 @@ import { ZeroProvider } from "@rocicorp/zero/react"; import { schema } from "@money/shared"; import { useState } from "react"; import { AuthClientLayer, getAuth } from "./auth"; -import { Effect } from "effect"; +import { Effect, Redacted } from "effect"; import { BunContext } from "@effect/platform-bun"; -import type { AuthData } from "./schema"; import { kvStore } from "./store"; import { config } from "./config"; +import { type AuthSchemaType } from "@money/shared/auth"; -function Main({ auth }: { auth: AuthData }) { +function Main({ auth }: { auth: AuthSchemaType }) { const [route, setRoute] = useState("/"); useKeyboard((key) => { @@ -22,7 +22,7 @@ function Main({ auth }: { auth: AuthData }) { ; -export type User = z.infer; -export type AuthData = z.infer; +export type AuthSchemaType = Schema.Schema.Type; diff --git a/packages/shared/src/rpc.ts b/packages/shared/src/rpc.ts new file mode 100644 index 0000000..f9f14d6 --- /dev/null +++ b/packages/shared/src/rpc.ts @@ -0,0 +1,29 @@ +import { Context, Schema } from "effect"; +import { Rpc, RpcGroup, RpcMiddleware } from "@effect/rpc"; +import type { AuthSchema } from "./auth"; + +export class Link extends Schema.Class("Link")({ + href: Schema.String, +}) {} + +export class CurrentSession extends Context.Tag("CurrentSession")< + CurrentSession, + { readonly auth: Schema.Schema.Type | null } +>() {} + +export class AuthMiddleware extends RpcMiddleware.Tag()( + "AuthMiddleware", + { + // This middleware will provide the current user context + provides: CurrentSession, + // This middleware requires a client implementation too + requiredForClient: true, + }, +) {} + +export class LinkRpcs extends RpcGroup.make( + Rpc.make("CreateLink", { + success: Link, + error: Schema.String, + }), +).middleware(AuthMiddleware) {} diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 6129f8e..0aef4a3 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -3,7 +3,7 @@ import { Transactions } from "./transactions"; import { View, Text } from "react-native"; import { Settings } from "./settings"; import { useKeyboard } from "./useKeyboard"; -import type { AuthData } from "@money/shared/auth"; +import type { AuthSchemaType } from "@money/shared/auth"; const PAGES = { "/": { @@ -39,7 +39,7 @@ type Routes = { export type Route = Routes; interface RouterContextType { - auth: AuthData | null; + auth: AuthSchemaType | null; route: Route; setRoute: (route: Route) => void; } @@ -51,7 +51,7 @@ export const RouterContext = createContext({ }); type AppProps = { - auth: AuthData | null; + auth: AuthSchemaType | null; route: Route; setRoute: (page: Route) => void; }; diff --git a/packages/ui/src/rpc.ts b/packages/ui/src/rpc.ts new file mode 100644 index 0000000..6631c77 --- /dev/null +++ b/packages/ui/src/rpc.ts @@ -0,0 +1,50 @@ +import { AtomRpc } from "@effect-atom/atom-react"; +import { AuthMiddleware, LinkRpcs } from "@money/shared/rpc"; +import { FetchHttpClient, Headers } from "@effect/platform"; +import { Rpc, RpcClient, RpcMiddleware, RpcSerialization } from "@effect/rpc"; +import * as Layer from "effect/Layer"; +import { Effect } from "effect"; +import { use } from "react"; +import { RouterContext } from "./index"; +import * as Redacted from "effect/Redacted"; +import { Platform } from "react-native"; + +const protocol = RpcClient.layerProtocolHttp({ + url: "http://laptop:3000/rpc", +}).pipe( + Layer.provide([ + RpcSerialization.layerJson, + FetchHttpClient.layer.pipe( + Layer.provide( + Layer.succeed(FetchHttpClient.RequestInit, { + credentials: "include", + }), + ), + ), + ]), +); + +export const useRpc = () => { + const { auth } = use(RouterContext); + + return class Client extends AtomRpc.Tag()("RpcClient", { + group: LinkRpcs, + protocol: Layer.merge( + protocol, + RpcMiddleware.layerClient(AuthMiddleware, ({ request }) => + Effect.succeed({ + ...request, + ...(auth && Platform.OS == ("TUI" as any) + ? { + headers: Headers.set( + request.headers, + "authorization", + "Bearer " + Redacted.value(auth.session.token), + ), + } + : {}), + }), + ), + ), + }) {}; +}; diff --git a/packages/ui/src/settings/accounts.tsx b/packages/ui/src/settings/accounts.tsx index ce4a939..7956dfd 100644 --- a/packages/ui/src/settings/accounts.tsx +++ b/packages/ui/src/settings/accounts.tsx @@ -7,6 +7,9 @@ import { useKeyboard } from "../useKeyboard"; import { Button } from "../../components/Button"; import * as Table from "../../components/Table"; import * as Dialog from "../../components/Dialog"; +import { useAtomSet } from "@effect-atom/atom-react"; +import { useRpc } from "../rpc"; +import * as Exit from "effect/Exit"; const COLUMNS: Table.Column[] = [ { name: "name", label: "Name" }, @@ -116,52 +119,79 @@ export function Accounts() { } function AddAccount() { - const { auth } = use(RouterContext); - const [link, details] = useQuery(queries.getPlaidLink(auth)); + const rpc = useRpc(); + const createLink = useAtomSet(rpc.mutation("CreateLink"), { + mode: "promiseExit", + }); + + const [href, setHref] = useState(""); + + // const [link, details] = useQuery(queries.getPlaidLink(auth)); const { close } = use(Dialog.Context); - const openLink = () => { - if (!link) return; - Linking.openURL(link.link); + const init = () => { + console.log("INIT"); + const p = createLink({ payload: void 0 }) + .then((link) => { + console.log("my link", link); + if (Exit.isSuccess(link)) { + setHref(link.value.href); + } + }) + .finally(() => { + console.log("WHAT"); + }); + console.log(p); }; - const z = useZero(); - useEffect(() => { - console.log(link, details); - if (details.type != "complete") return; - if (link != undefined) { - if (!link.completeAt) { - const timer = setInterval(() => { - console.log("Checking for link"); - z.mutate.link.get({ link_token: link.token }); - }, 1000 * 5); - return () => clearInterval(timer); - } else { - if (close) close(); - return; - } - } - console.log("Creating new link"); - z.mutate.link.create(); - }, [link, details]); + console.log("useEffect"); + init(); + }, []); + + // const openLink = () => { + // if (!link) return; + // Linking.openURL(link.link); + // }; + + // const z = useZero(); + + // useEffect(() => { + // console.log(link, details); + // if (details.type != "complete") return; + // if (link != undefined) { + // if (!link.completeAt) { + // const timer = setInterval(() => { + // console.log("Checking for link"); + // z.mutate.link.get({ link_token: link.token }); + // }, 1000 * 5); + // return () => clearInterval(timer); + // } else { + // if (close) close(); + // return; + // } + // } + // console.log("Creating new link"); + // z.mutate.link.create(); + // }, [link, details]); return ( <> + Href: {href} - {link ? ( - <> - - Please click the button to complete setup. - - - - - ) : ( - Loading Plaid Link - )} + {/* {link ? ( */} + {/* <> */} + {/* */} + {/* Please click the button to complete setup. */} + {/* */} + {/**/} + {/* */} + {/* */} + {/* ) : ( */} + {/* Loading Plaid Link */} + {/* )} */} ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d7e1ef8..751027a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,18 +10,24 @@ importers: apps/api: dependencies: - '@hono/node-server': - specifier: ^1.19.5 - version: 1.19.6(hono@4.10.4) + '@effect/platform': + specifier: ^0.93.2 + version: 0.93.2(effect@3.19.4) + '@effect/platform-node': + specifier: ^0.101.1 + version: 0.101.1(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/rpc': + specifier: ^0.72.2 + version: 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) '@money/shared': specifier: workspace:* version: link:../../packages/shared better-auth: specifier: ^1.3.27 version: 1.3.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - hono: - specifier: ^4.9.12 - version: 4.10.4 + effect: + specifier: ^3.19.4 + version: 3.19.4 plaid: specifier: ^39.0.0 version: 39.1.0 @@ -38,6 +44,9 @@ importers: '@better-auth/expo': specifier: ^1.3.27 version: 1.3.34(better-auth@1.3.34(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(expo-constants@18.0.10)(expo-crypto@15.0.7(expo@54.0.23))(expo-linking@8.0.8)(expo-secure-store@15.0.7(expo@54.0.23))(expo-web-browser@15.0.9(expo@54.0.23)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))) + '@effect-atom/atom-react': + specifier: ^0.4.0 + version: 0.4.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)(react@19.1.0)(scheduler@0.26.0) '@expo/vector-icons': specifier: ^15.0.2 version: 15.0.3(expo-font@14.0.9(expo@54.0.23)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -167,7 +176,7 @@ importers: version: 0.93.2(effect@3.19.4) '@effect/platform-bun': specifier: ^0.83.0 - version: 0.83.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + version: 0.83.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) '@money/shared': specifier: workspace:* version: link:../../packages/shared @@ -798,6 +807,21 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 + '@effect-atom/atom-react@0.4.0': + resolution: {integrity: sha512-5HpKLgXEG8EWr4sBDl7BZjm6koO/5HSb94C9+OkRLDE4mhH2357vNl4uPNqid0ZNGwVvS6bAvKFmBzc0bZU6yg==} + peerDependencies: + effect: ^3.19 + react: '>=18 <20' + scheduler: '*' + + '@effect-atom/atom@0.4.3': + resolution: {integrity: sha512-0XOngJ+oDuJW7/Hgt09Kl8QunF1bGlEtV/K9hMB1MmQPUGb+ZtxfJwZkBeXjMPEL1Lgm04TDBlqB8+qgHz+y0w==} + peerDependencies: + '@effect/experimental': ^0.57.0 + '@effect/platform': ^0.93.0 + '@effect/rpc': ^0.72.1 + effect: ^3.19.0 + '@effect/cluster@0.52.10': resolution: {integrity: sha512-csmU+4h2MXdxsFKF5eY4N52LDcjdpQp//QivOKNL9yNySUBVz/UrBr1FRgvbfHk+sxY03SNcoTNgkcbUaIp2Pg==} peerDependencies: @@ -838,16 +862,34 @@ packages: '@effect/sql': ^0.48.0 effect: ^3.19.0 + '@effect/platform-node-shared@0.54.0': + resolution: {integrity: sha512-prTgG3CXqmrxB4Rg6utfwCTqjlGwjAEvK7R4g3HzVdFpfFRum+FQBpGHUcjyz7EejkDtBY2MWJC3Wr1QKDPjPw==} + peerDependencies: + '@effect/cluster': ^0.53.0 + '@effect/platform': ^0.93.3 + '@effect/rpc': ^0.72.2 + '@effect/sql': ^0.48.0 + effect: ^3.19.5 + + '@effect/platform-node@0.101.1': + resolution: {integrity: sha512-uShujtpWU0VbdhRKhoo6tXzTG1xT0bnj8u5Q1BHpanwKPmzOhf4n0XLlMl5PaihH5Cp7xHuQlwgZlqHzhqSHzw==} + peerDependencies: + '@effect/cluster': ^0.53.4 + '@effect/platform': ^0.93.3 + '@effect/rpc': ^0.72.2 + '@effect/sql': ^0.48.0 + effect: ^3.19.6 + '@effect/platform@0.93.2': resolution: {integrity: sha512-IFWF2xuz37tZbyEsf3hwBlcYYqbqJho+ZM871CG92lWJSjcTgvmjCy77qnV0QhTWVdh9BMs12QKzQCMlqz4cJQ==} peerDependencies: effect: ^3.19.3 - '@effect/rpc@0.72.1': - resolution: {integrity: sha512-crpiAxDvFxM/fGhLuAgB1V8JOtfCm8/6ZdOP4MIdkz14I/ff3LdLJpf8hHJpYIbwYXypglAeAaHpfuZOt5f+SA==} + '@effect/rpc@0.72.2': + resolution: {integrity: sha512-BmTXybXCOq96D2r9mvSW/YdiTQs5CStnd4II+lfVKrMr3pMNERKLZ2LG37Tfm4Sy3Q8ire6IVVKO/CN+VR0uQQ==} peerDependencies: - '@effect/platform': ^0.93.0 - effect: ^3.19.0 + '@effect/platform': ^0.93.3 + effect: ^3.19.5 '@effect/sql@0.48.0': resolution: {integrity: sha512-tubdizHriDwzHUnER9UsZ/0TtF6O2WJckzeYDbVSRPeMkrpdpyEzEsoKctechTm65B3Bxy6JIixGPg2FszY72A==} @@ -1534,12 +1576,6 @@ packages: '@hexagon/base64@1.1.28': resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==} - '@hono/node-server@1.19.6': - resolution: {integrity: sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==} - engines: {node: '>=18.14.1'} - peerDependencies: - hono: ^4 - '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -4867,10 +4903,6 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - hono@4.10.4: - resolution: {integrity: sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg==} - engines: {node: '>=16.9.0'} - hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -6888,6 +6920,10 @@ packages: resolution: {integrity: sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==} engines: {node: '>=18.17'} + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + engines: {node: '>=20.18.1'} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -7895,12 +7931,30 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 - '@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': + '@effect-atom/atom-react@0.4.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)(react@19.1.0)(scheduler@0.26.0)': + dependencies: + '@effect-atom/atom': 0.4.3(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + effect: 3.19.4 + react: 19.1.0 + scheduler: 0.26.0 + transitivePeerDependencies: + - '@effect/experimental' + - '@effect/platform' + - '@effect/rpc' + + '@effect-atom/atom@0.4.3(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': + dependencies: + '@effect/experimental': 0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@effect/platform': 0.93.2(effect@3.19.4) + '@effect/rpc': 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + effect: 3.19.4 + + '@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': dependencies: '@effect/platform': 0.93.2(effect@3.19.4) - '@effect/rpc': 0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@effect/rpc': 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) '@effect/sql': 0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) - '@effect/workflow': 0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/workflow': 0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) effect: 3.19.4 '@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4)': @@ -7909,12 +7963,12 @@ snapshots: effect: 3.19.4 uuid: 11.1.0 - '@effect/platform-bun@0.83.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': + '@effect/platform-bun@0.83.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': dependencies: - '@effect/cluster': 0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/cluster': 0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) '@effect/platform': 0.93.2(effect@3.19.4) - '@effect/platform-node-shared': 0.53.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) - '@effect/rpc': 0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@effect/platform-node-shared': 0.53.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/rpc': 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) '@effect/sql': 0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) effect: 3.19.4 multipasta: 0.2.7 @@ -7922,11 +7976,11 @@ snapshots: - bufferutil - utf-8-validate - '@effect/platform-node-shared@0.53.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': + '@effect/platform-node-shared@0.53.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': dependencies: - '@effect/cluster': 0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/cluster': 0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) '@effect/platform': 0.93.2(effect@3.19.4) - '@effect/rpc': 0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@effect/rpc': 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) '@effect/sql': 0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) '@parcel/watcher': 2.5.1 effect: 3.19.4 @@ -7936,6 +7990,35 @@ snapshots: - bufferutil - utf-8-validate + '@effect/platform-node-shared@0.54.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': + dependencies: + '@effect/cluster': 0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/platform': 0.93.2(effect@3.19.4) + '@effect/rpc': 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@effect/sql': 0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@parcel/watcher': 2.5.1 + effect: 3.19.4 + multipasta: 0.2.7 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@effect/platform-node@0.101.1(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': + dependencies: + '@effect/cluster': 0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/platform': 0.93.2(effect@3.19.4) + '@effect/platform-node-shared': 0.54.0(@effect/cluster@0.52.10(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/sql@0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4) + '@effect/rpc': 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@effect/sql': 0.48.0(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + effect: 3.19.4 + mime: 3.0.0 + undici: 7.16.0 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@effect/platform@0.93.2(effect@3.19.4)': dependencies: effect: 3.19.4 @@ -7943,7 +8026,7 @@ snapshots: msgpackr: 1.11.5 multipasta: 0.2.7 - '@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4)': + '@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4)': dependencies: '@effect/platform': 0.93.2(effect@3.19.4) effect: 3.19.4 @@ -7956,11 +8039,11 @@ snapshots: effect: 3.19.4 uuid: 11.1.0 - '@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': + '@effect/workflow@0.12.5(@effect/experimental@0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(@effect/platform@0.93.2(effect@3.19.4))(@effect/rpc@0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4))(effect@3.19.4)': dependencies: '@effect/experimental': 0.57.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) '@effect/platform': 0.93.2(effect@3.19.4) - '@effect/rpc': 0.72.1(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) + '@effect/rpc': 0.72.2(@effect/platform@0.93.2(effect@3.19.4))(effect@3.19.4) effect: 3.19.4 '@egjs/hammerjs@2.0.17': @@ -8414,6 +8497,7 @@ snapshots: - graphql - supports-color - utf-8-validate + optional: true '@expo/code-signing-certificates@0.0.5': dependencies: @@ -8480,6 +8564,7 @@ snapshots: optionalDependencies: react: 19.2.0 react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + optional: true '@expo/env@2.0.7': dependencies: @@ -8558,7 +8643,7 @@ snapshots: postcss: 8.4.49 resolve-from: 5.0.0 optionalDependencies: - expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - bufferutil - supports-color @@ -8637,7 +8722,7 @@ snapshots: '@expo/json-file': 10.0.7 '@react-native/normalize-colors': 0.81.5 debug: 4.4.3 - expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) resolve-from: 5.0.0 semver: 7.7.3 xml2js: 0.6.0 @@ -8665,6 +8750,7 @@ snapshots: expo-font: 14.0.9(expo@54.0.23)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) react: 19.2.0 react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + optional: true '@expo/ws-tunnel@1.0.6': {} @@ -8736,10 +8822,6 @@ snapshots: '@hexagon/base64@1.1.28': {} - '@hono/node-server@1.19.6(hono@4.10.4)': - dependencies: - hono: 4.10.4 - '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -10671,6 +10753,7 @@ snapshots: react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) optionalDependencies: '@types/react': 19.1.17 + optional: true '@react-navigation/bottom-tabs@7.8.4(@react-navigation/native@7.1.19(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: @@ -11536,7 +11619,7 @@ snapshots: resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.28.4 - expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - '@babel/core' - supports-color @@ -12537,6 +12620,7 @@ snapshots: react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) transitivePeerDependencies: - supports-color + optional: true expo-constants@18.0.10(expo@54.0.23)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)): dependencies: @@ -12555,6 +12639,7 @@ snapshots: react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) transitivePeerDependencies: - supports-color + optional: true expo-crypto@15.0.7(expo@54.0.23): dependencies: @@ -12570,6 +12655,7 @@ snapshots: dependencies: expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + optional: true expo-font@14.0.9(expo@54.0.23)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -12584,6 +12670,7 @@ snapshots: fontfaceobserver: 2.3.0 react: 19.2.0 react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + optional: true expo-haptics@15.0.7(expo@54.0.23): dependencies: @@ -12606,6 +12693,7 @@ snapshots: dependencies: expo: 54.0.23(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.14)(react-native@0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) react: 19.2.0 + optional: true expo-linking@8.0.8(expo@54.0.23)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -12647,6 +12735,7 @@ snapshots: invariant: 2.2.4 react: 19.2.0 react-native: 0.82.1(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + optional: true expo-router@6.0.14(@expo/metro-runtime@6.1.2)(@types/react@19.1.17)(expo-constants@18.0.10)(expo-linking@8.0.8)(expo@54.0.23)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.3(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: @@ -12862,6 +12951,7 @@ snapshots: - graphql - supports-color - utf-8-validate + optional: true exponential-backoff@3.1.3: {} @@ -13214,8 +13304,6 @@ snapshots: dependencies: react-is: 16.13.1 - hono@4.10.4: {} - hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -14991,6 +15079,7 @@ snapshots: - bufferutil - supports-color - utf-8-validate + optional: true react-reconciler@0.32.0(react@19.1.0): dependencies: @@ -15058,7 +15147,8 @@ snapshots: react@19.1.0: {} - react@19.2.0: {} + react@19.2.0: + optional: true readable-stream@3.6.2: dependencies: @@ -15764,6 +15854,8 @@ snapshots: undici@6.22.0: {} + undici@7.16.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: