format: format with biome

This commit is contained in:
Max Koon
2025-11-24 22:20:40 -05:00
parent 01edded95a
commit 6fd531d9c3
41 changed files with 1025 additions and 667 deletions

View File

@@ -1,39 +1,70 @@
import { Context, Data, Effect, Layer, Schema, Console, Schedule, Ref, Duration } from "effect";
import {
Context,
Data,
Effect,
Layer,
Schema,
Console,
Schedule,
Ref,
Duration,
} 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";
class AuthClientUnknownError extends Data.TaggedError("AuthClientUnknownError") {};
class AuthClientExpiredToken extends Data.TaggedError("AuthClientExpiredToken") {};
class AuthClientNoData extends Data.TaggedError("AuthClientNoData") {};
class AuthClientFetchError extends Data.TaggedError("AuthClientFetchError")<{ message: string, }> {};
class AuthClientUnknownError extends Data.TaggedError(
"AuthClientUnknownError",
) {}
class AuthClientExpiredToken extends Data.TaggedError(
"AuthClientExpiredToken",
) {}
class AuthClientNoData extends Data.TaggedError("AuthClientNoData") {}
class AuthClientFetchError extends Data.TaggedError("AuthClientFetchError")<{
message: string;
}> {}
class AuthClientError<T> extends Data.TaggedError("AuthClientError")<{
error: T,
}> {};
error: T;
}> {}
type ErrorType<E> = { [key in keyof ((E extends Record<string, any> ? E : {
message?: string;
}) & {
type ErrorType<E> = {
[key in keyof ((E extends Record<string, any>
? E
: {
message?: string;
}) & {
status: number;
statusText: string;
})]: ((E extends Record<string, any> ? E : {
message?: string;
}) & {
})]: ((E extends Record<string, any>
? E
: {
message?: string;
}) & {
status: number;
statusText: string;
})[key]; };
})[key];
};
export class AuthClient extends Context.Tag("AuthClient")<AuthClient, AuthClientImpl>() {};
export class AuthClient extends Context.Tag("AuthClient")<
AuthClient,
AuthClientImpl
>() {}
export interface AuthClientImpl {
use: <T, E>(
fn: (client: typeof authClient) => Promise<BetterFetchResponse<T, E>>,
) => Effect.Effect<T, AuthClientError<ErrorType<E>> | AuthClientFetchError | AuthClientUnknownError | AuthClientNoData, never>
) => Effect.Effect<
T,
| AuthClientError<ErrorType<E>>
| AuthClientFetchError
| AuthClientUnknownError
| AuthClientNoData,
never
>;
}
export const make = () =>
Effect.gen(function* () {
return AuthClient.of({
@@ -41,11 +72,13 @@ export const make = () =>
Effect.gen(function* () {
const { data, error } = yield* Effect.tryPromise({
try: () => fn(authClient),
catch: (error) => error instanceof Error
? new AuthClientFetchError({ message: error.message })
: new AuthClientUnknownError()
catch: (error) =>
error instanceof Error
? new AuthClientFetchError({ message: error.message })
: new AuthClientUnknownError(),
});
if (error != null) return yield* Effect.fail(new AuthClientError({ error }));
if (error != null)
return yield* Effect.fail(new AuthClientError({ error }));
if (data == null) return yield* Effect.fail(new AuthClientNoData());
return data;
}),
@@ -54,76 +87,80 @@ export const make = () =>
export const AuthClientLayer = Layer.scoped(AuthClient, make());
const pollToken = ({ device_code }: { device_code: string }) => Effect.gen(function* () {
const auth = yield* AuthClient;
const intervalRef = yield* Ref.make(5);
const pollToken = ({ device_code }: { device_code: string }) =>
Effect.gen(function* () {
const auth = yield* AuthClient;
const intervalRef = yield* Ref.make(5);
const tokenEffect = auth.use(client => {
Console.debug("Fetching");
const tokenEffect = auth.use((client) => {
Console.debug("Fetching");
return client.device.token({
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
device_code,
client_id: config.authClientId,
fetchOptions: { headers: { "user-agent": config.authClientUserAgent } },
})
}
);
return client.device.token({
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
device_code,
client_id: config.authClientId,
fetchOptions: { headers: { "user-agent": config.authClientUserAgent } },
});
});
return yield* tokenEffect
.pipe(
Effect.tapError(error =>
return yield* tokenEffect.pipe(
Effect.tapError((error) =>
error._tag == "AuthClientError" && error.error.error == "slow_down"
? Ref.update(intervalRef, current => {
Console.debug("updating delay to ", current + 5);
return current + 5
})
: Effect.void
? Ref.update(intervalRef, (current) => {
Console.debug("updating delay to ", current + 5);
return current + 5;
})
: Effect.void,
),
Effect.retry({
schedule: Schedule.addDelayEffect(
Schedule.recurWhile<Effect.Effect.Error<typeof tokenEffect>>(error =>
error._tag == "AuthClientError" &&
(error.error.error == "authorization_pending" || error.error.error == "slow_down")
Schedule.recurWhile<Effect.Effect.Error<typeof tokenEffect>>(
(error) =>
error._tag == "AuthClientError" &&
(error.error.error == "authorization_pending" ||
error.error.error == "slow_down"),
),
() => Ref.get(intervalRef).pipe(Effect.map(Duration.seconds))
)
})
() => Ref.get(intervalRef).pipe(Effect.map(Duration.seconds)),
),
}),
);
});
});
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);
if (auth.session.expiresAt < new Date()) yield* Effect.fail(new AuthClientExpiredToken());
if (auth.session.expiresAt < new Date())
yield* Effect.fail(new AuthClientExpiredToken());
return auth;
});
const requestAuth = Effect.gen(function* () {
const auth = yield* AuthClient;
const { device_code, user_code } = yield* auth.use(client => client.device.code({
client_id: config.authClientId,
scope: "openid profile email",
}));
const { device_code, user_code } = yield* auth.use((client) =>
client.device.code({
client_id: config.authClientId,
scope: "openid profile email",
}),
);
console.log(`Please use the code: ${user_code}`);
const { access_token } = yield* pollToken({ device_code });
const sessionData = yield* auth.use(client => client.getSession({
fetchOptions: {
auth: {
type: "Bearer",
token: access_token,
}
}
}));
const sessionData = yield* auth.use((client) =>
client.getSession({
fetchOptions: {
auth: {
type: "Bearer",
token: access_token,
},
},
}),
);
if (sessionData == null) return yield* Effect.fail(new AuthClientNoData());
const result = yield* Schema.decodeUnknown(AuthState)(sessionData)
const result = yield* Schema.decodeUnknown(AuthState)(sessionData);
const fs = yield* FileSystem.FileSystem;
yield* fs.writeFileString(config.authPath, JSON.stringify(result));
@@ -134,33 +171,51 @@ const requestAuth = Effect.gen(function* () {
export const getAuth = Effect.gen(function* () {
return yield* getFromFromDisk.pipe(
Effect.catchAll(() => requestAuth),
Effect.catchTag("AuthClientFetchError", (err) => Effect.gen(function* () {
yield* Console.error("Authentication failed: " + err.message);
process.exit(1);
})),
Effect.catchTag("AuthClientNoData", () => Effect.gen(function* () {
yield* Console.error("Authentication failed: No error and no data was given by the auth server.");
process.exit(1);
})),
Effect.catchTag("ParseError", (err) => Effect.gen(function* () {
yield* Console.error("Authentication failed: Auth data failed: " + err.toString());
process.exit(1);
})),
Effect.catchTag("BadArgument", () => Effect.gen(function* () {
yield* Console.error("Authentication failed: Bad argument");
process.exit(1);
})),
Effect.catchTag("SystemError", () => Effect.gen(function* () {
yield* Console.error("Authentication failed: System error");
process.exit(1);
})),
Effect.catchTag("AuthClientError", ({ error }) => Effect.gen(function* () {
yield* Console.error("Authentication error: " + error.statusText);
process.exit(1);
})),
Effect.catchTag("AuthClientUnknownError", () => Effect.gen(function* () {
yield* Console.error("Unknown authentication error");
process.exit(1);
})),
Effect.catchTag("AuthClientFetchError", (err) =>
Effect.gen(function* () {
yield* Console.error("Authentication failed: " + err.message);
process.exit(1);
}),
),
Effect.catchTag("AuthClientNoData", () =>
Effect.gen(function* () {
yield* Console.error(
"Authentication failed: No error and no data was given by the auth server.",
);
process.exit(1);
}),
),
Effect.catchTag("ParseError", (err) =>
Effect.gen(function* () {
yield* Console.error(
"Authentication failed: Auth data failed: " + err.toString(),
);
process.exit(1);
}),
),
Effect.catchTag("BadArgument", () =>
Effect.gen(function* () {
yield* Console.error("Authentication failed: Bad argument");
process.exit(1);
}),
),
Effect.catchTag("SystemError", () =>
Effect.gen(function* () {
yield* Console.error("Authentication failed: System error");
process.exit(1);
}),
),
Effect.catchTag("AuthClientError", ({ error }) =>
Effect.gen(function* () {
yield* Console.error("Authentication error: " + error.statusText);
process.exit(1);
}),
),
Effect.catchTag("AuthClientUnknownError", () =>
Effect.gen(function* () {
yield* Console.error("Unknown authentication error");
process.exit(1);
}),
),
);
});

View File

@@ -10,5 +10,5 @@ export const config = {
authClientId: "koon-family",
authClientUserAgent: "CLI",
zeroUrl: "http://laptop:4848",
apiUrl: "http://laptop:3000"
apiUrl: "http://laptop:3000",
};

View File

@@ -2,7 +2,7 @@ import { createCliRenderer } from "@opentui/core";
import { createRoot, useKeyboard } from "@opentui/react";
import { App, type Route } from "@money/ui";
import { ZeroProvider } from "@rocicorp/zero/react";
import { schema } from '@money/shared';
import { schema } from "@money/shared";
import { useState } from "react";
import { AuthClientLayer, getAuth } from "./auth";
import { Effect } from "effect";
@@ -14,28 +14,30 @@ import { config } from "./config";
function Main({ auth }: { auth: AuthData }) {
const [route, setRoute] = useState<Route>("/");
useKeyboard(key => {
useKeyboard((key) => {
if (key.name == "c" && key.ctrl) process.exit(0);
});
return (
<ZeroProvider {...{ userID: auth.user.id, auth: auth.session.token, server: config.zeroUrl, schema, kvStore }}>
<App
auth={auth}
route={route}
setRoute={setRoute}
/>
<ZeroProvider
{...{
userID: auth.user.id,
auth: auth.session.token,
server: config.zeroUrl,
schema,
kvStore,
}}
>
<App auth={auth} route={route} setRoute={setRoute} />
</ZeroProvider>
);
}
const auth = await Effect.runPromise(
getAuth.pipe(
Effect.provide(BunContext.layer),
Effect.provide(AuthClientLayer),
)
),
);
const renderer = await createCliRenderer({ exitOnCtrlC: false });
createRoot(renderer).render(<Main auth={auth} />);

View File

@@ -1,6 +1,9 @@
import { Schema } from "effect";
const DateFromDateOrString = Schema.Union(Schema.DateFromString, Schema.DateFromSelf);
const DateFromDateOrString = Schema.Union(
Schema.DateFromString,
Schema.DateFromSelf,
);
const SessionSchema = Schema.Struct({
expiresAt: DateFromDateOrString,
@@ -23,11 +26,9 @@ const UserSchema = Schema.Struct({
id: Schema.String,
});
export const AuthState = Schema.Struct({
session: SessionSchema,
user: UserSchema,
});
export type AuthData = typeof AuthState.Type;

View File

@@ -23,7 +23,7 @@ async function loadFile(name: string): Promise<Map<string, ReadonlyJSONValue>> {
const buf = await fs.readFile(filePath, "utf8");
const obj = JSON.parse(buf) as Record<string, ReadonlyJSONValue>;
const frozen = Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, deepFreeze(v)])
Object.entries(obj).map(([k, v]) => [k, deepFreeze(v)]),
);
return new Map(Object.entries(frozen));
} catch (err: any) {
@@ -73,7 +73,9 @@ export const kvStore: StoreProvider = {
closed: txClosed,
async has(key: string) {
if (txClosed) throw new Error("transaction closed");
return staging.has(key) ? staging.get(key) !== undefined : data.has(key);
return staging.has(key)
? staging.get(key) !== undefined
: data.has(key);
},
async get(key: string) {
if (txClosed) throw new Error("transaction closed");