Port to elysia, tailwind (no effect)
This commit is contained in:
@@ -1,29 +1,13 @@
|
||||
{
|
||||
"name": "common",
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "UNLICENSED",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./api": {
|
||||
"types": "./dist/api.d.ts",
|
||||
"import": "./dist/api.js"
|
||||
},
|
||||
"./db": {
|
||||
"types": "./dist/db.d.ts",
|
||||
"import": "./dist/db.js"
|
||||
}
|
||||
".": { "import": "./src/index.ts" },
|
||||
"./*": { "import": "./src/*.ts" }
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@effect/schema": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fast-check": "catalog:",
|
||||
"make-api": "workspace:^"
|
||||
}
|
||||
}
|
||||
|
||||
35
packages/common/src/Brand.ts
Normal file
35
packages/common/src/Brand.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as Function from "./Function";
|
||||
import * as Types from "./Types";
|
||||
|
||||
declare const BrandTypeId: unique symbol;
|
||||
export type BrandTypeId = typeof BrandTypeId;
|
||||
|
||||
declare const ConstructorTypeId: unique symbol;
|
||||
export type ConstructorTypeId = typeof ConstructorTypeId;
|
||||
|
||||
export interface Brand<in out K extends string | symbol> {
|
||||
readonly [BrandTypeId]: {
|
||||
readonly [k in K]: K;
|
||||
}
|
||||
}
|
||||
|
||||
export declare namespace Brand {
|
||||
export interface Constructor<in out A extends Brand<any>> {
|
||||
readonly [ConstructorTypeId]: ConstructorTypeId;
|
||||
(args: Brand.Unbranded<A>): A;
|
||||
}
|
||||
|
||||
export type Unbranded<P> = P extends infer Q & Brands<P> ? Q : P;
|
||||
|
||||
export type Brands<P> = P extends Brand<any>
|
||||
? Types.UnionToIntersection<{
|
||||
[k in keyof P[BrandTypeId]]: k extends string | symbol ? Brand<k> : never
|
||||
}[keyof P[BrandTypeId]]>
|
||||
: never;
|
||||
}
|
||||
|
||||
export type Branded<A, K extends string | symbol> = A & Brand<K>;
|
||||
|
||||
export const nominal = <A extends Brand<any>>(): Brand.Constructor<A> => {
|
||||
return Function.identity as Brand.Constructor<A>;
|
||||
};
|
||||
3
packages/common/src/Function.ts
Normal file
3
packages/common/src/Function.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const identity = <A>(a: A): A => a;
|
||||
|
||||
export const unsafeCoerce: <A, B>(a: A) => B = identity as any;
|
||||
1
packages/common/src/Types.ts
Normal file
1
packages/common/src/Types.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type UnionToIntersection<T> = (T extends any ? (x: T) => any : never) extends (x: infer R) => any ? R : never;
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Api } from "make-api";
|
||||
import { Schema as S } from "@effect/schema";
|
||||
import { PieceId, UserId } from "common";
|
||||
import { pipe } from "effect";
|
||||
import { Piece } from "./db";
|
||||
|
||||
// --- PIECES ------------------------------------------------------------------
|
||||
|
||||
export const CreatePiece = pipe(
|
||||
Api.make("POST", "piece"),
|
||||
Api.requestBodyJson(S.Struct({
|
||||
name: S.NonEmptyString,
|
||||
composer: S.Union(S.NonEmptyString, S.Null),
|
||||
lyricist: S.Union(S.NonEmptyString, S.Null),
|
||||
arranger: S.Union(S.NonEmptyString, S.Null),
|
||||
})),
|
||||
Api.responseBodyJson(201, Piece),
|
||||
Api.responseBodyText(400, S.String),
|
||||
);
|
||||
|
||||
export const GetPieces = pipe(
|
||||
Api.make("GET", "piece"),
|
||||
Api.responseBodyJson(200, S.Array(Piece)),
|
||||
);
|
||||
|
||||
export const UpdatePiece = pipe(
|
||||
Api.make("PUT", "piece", ["pieceId", PieceId]),
|
||||
Api.requestBodyJson(S.Struct({
|
||||
name: S.NonEmptyString,
|
||||
composer: S.Union(S.NonEmptyString, S.Null),
|
||||
lyricist: S.Union(S.NonEmptyString, S.Null),
|
||||
arranger: S.Union(S.NonEmptyString, S.Null),
|
||||
})),
|
||||
Api.responseBodyJson(200, Piece),
|
||||
Api.responseBodyText(400, S.String),
|
||||
);
|
||||
|
||||
export const DeletePiece = pipe(
|
||||
Api.make("DELETE", "piece", ["pieceId", PieceId]),
|
||||
Api.responseBodyNone(200),
|
||||
Api.responseBodyText(400, S.String),
|
||||
Api.responseBodyText(404, S.String),
|
||||
);
|
||||
|
||||
// --- AUTHENTICATION ----------------------------------------------------------
|
||||
|
||||
export const Me = pipe(
|
||||
Api.make("GET", "me"),
|
||||
Api.responseBodyJson(200, S.Struct({
|
||||
userId: UserId,
|
||||
username: S.NonEmptyString,
|
||||
admin: S.Boolean,
|
||||
})),
|
||||
Api.responseBodyText(401, S.String),
|
||||
);
|
||||
|
||||
export const Logout = pipe(
|
||||
Api.make("POST", "logout"),
|
||||
Api.responseBodyNone(204),
|
||||
);
|
||||
|
||||
export const Login = pipe(
|
||||
Api.make("POST", "login"),
|
||||
Api.requestBodyJson(S.Struct({
|
||||
username: S.NonEmptyString,
|
||||
password: S.NonEmptyString,
|
||||
})),
|
||||
Api.responseBodyJson(200, S.Struct({
|
||||
userId: UserId,
|
||||
username: S.NonEmptyString,
|
||||
admin: S.Boolean,
|
||||
})),
|
||||
Api.responseBodyText(400, S.String),
|
||||
Api.responseBodyText(401, S.String),
|
||||
);
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Schema as S } from "@effect/schema";
|
||||
import { AttachmentId, BooleanFromNumber, PieceId, RequestId, SessionId, Sha256, UserId } from "common";
|
||||
import { Brand as B, pipe } from "effect";
|
||||
|
||||
export const SessionData = S.Struct({
|
||||
userId: UserId,
|
||||
});
|
||||
|
||||
export type SessionData = typeof SessionData.Type;
|
||||
|
||||
export const SystemInformation = S.Struct({
|
||||
createdBy: S.Union(UserId, S.Null),
|
||||
createdAt: S.DateTimeUtc,
|
||||
modifiedBy: S.Union(UserId, S.Null),
|
||||
modifiedAt: S.DateTimeUtc,
|
||||
});
|
||||
|
||||
export type SystemInformation = typeof SystemInformation.Type;
|
||||
|
||||
// --- TABLES ------------------------------------------------------------------
|
||||
|
||||
export const AccessLog = S.Struct({
|
||||
timestamp: S.DateTimeUtc,
|
||||
requestId: RequestId,
|
||||
method: S.NonEmptyString,
|
||||
pathname: S.NonEmptyString,
|
||||
query: S.parseJson(S.Record({
|
||||
key: S.String,
|
||||
value: S.String,
|
||||
})),
|
||||
ip: S.Union(S.NonEmptyString, S.Null),
|
||||
});
|
||||
|
||||
export const Attachment = pipe(
|
||||
S.Struct({
|
||||
attachmentId: AttachmentId,
|
||||
pieceId: PieceId,
|
||||
sha256: Sha256,
|
||||
filename: S.NonEmptyString,
|
||||
mediaType: S.NonEmptyString,
|
||||
}),
|
||||
S.extend(SystemInformation),
|
||||
);
|
||||
|
||||
export const Piece = pipe(
|
||||
S.Struct({
|
||||
pieceId: PieceId,
|
||||
name: S.NonEmptyString,
|
||||
composer: S.Union(S.NonEmptyString, S.Null),
|
||||
lyricist: S.Union(S.NonEmptyString, S.Null),
|
||||
arranger: S.Union(S.NonEmptyString, S.Null),
|
||||
}),
|
||||
S.extend(SystemInformation),
|
||||
);
|
||||
|
||||
export const Session = pipe(
|
||||
S.Struct({
|
||||
sessionId: SessionId,
|
||||
expiresAt: S.DateTimeUtc,
|
||||
}),
|
||||
S.extend(SessionData),
|
||||
);
|
||||
|
||||
export const User = S.Struct({
|
||||
userId: UserId,
|
||||
username: S.NonEmptyString,
|
||||
password: S.NonEmptyString,
|
||||
admin: BooleanFromNumber,
|
||||
});
|
||||
|
||||
export type AccessLog = typeof AccessLog.Type;
|
||||
export type Attachment = typeof Attachment.Type;
|
||||
export type Piece = typeof Piece.Type;
|
||||
export type Session = typeof Session.Type;
|
||||
export type User = typeof User.Type;
|
||||
@@ -1,33 +1,22 @@
|
||||
import { Schema as S } from "@effect/schema";
|
||||
import { Brand as B, pipe } from "effect";
|
||||
import * as Brand from "./Brand";
|
||||
|
||||
export const AttachmentId = pipe(S.ULID, S.brand("AttachmentId"));
|
||||
export const PieceId = pipe(S.ULID, S.brand("PieceId"));
|
||||
export const RequestId = pipe(S.ULID, S.brand("RequestId"));
|
||||
export const SessionId = pipe(S.NonEmptyString, S.brand("SessionId"));
|
||||
export const UserId = pipe(S.ULID, S.brand("UserId"));
|
||||
export type UUID = Brand.Branded<string, "UUID">;
|
||||
export const UUID = Brand.nominal<UUID>();
|
||||
|
||||
export type AttachmentId = typeof AttachmentId.Type;
|
||||
export type PieceId = typeof PieceId.Type;
|
||||
export type RequestId = typeof RequestId.Type;
|
||||
export type SessionId = typeof SessionId.Type;
|
||||
export type UserId = typeof UserId.Type;
|
||||
export type Sha256 = Brand.Branded<Uint8Array, "Sha256">;
|
||||
export const Sha256 = Brand.nominal<Sha256>();
|
||||
|
||||
export type Sha256 = B.Branded<Uint8Array, "Sha256">;
|
||||
export const Sha256 = pipe(
|
||||
S.Uint8ArrayFromSelf,
|
||||
S.fromBrand(B.refined<Sha256>(
|
||||
(array) => array.byteLength === 32,
|
||||
() => B.error(`Expected Uint8Array to be 32 bytes long`),
|
||||
)),
|
||||
).annotations({ identifier: "SHA-256" });
|
||||
export type AttachmentId = Brand.Branded<UUID, "AttachmentId">;
|
||||
export const AttachmentId = Brand.nominal<AttachmentId>();
|
||||
|
||||
export class BooleanFromNumber extends S.transform(
|
||||
S.Number,
|
||||
S.Boolean,
|
||||
{
|
||||
strict: true,
|
||||
decode: (a) => a !== 0,
|
||||
encode: (i) => i ? 1 : 0,
|
||||
},
|
||||
).annotations({ identifier: "BooleanFromNumber" }) { }
|
||||
export type PieceId = Brand.Branded<UUID, "PieceId">;
|
||||
export const PieceId = Brand.nominal<PieceId>();
|
||||
|
||||
export type RequestId = Brand.Branded<UUID, "RequestId">;
|
||||
export const RequestId = Brand.nominal<RequestId>();
|
||||
|
||||
export type SessionId = Brand.Branded<string, "SessionId">;
|
||||
export const SessionId = Brand.nominal<SessionId>();
|
||||
|
||||
export type UserId = Brand.Branded<UUID, "UserId">;
|
||||
export const UserId = Brand.nominal<UserId>();
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"references": [
|
||||
{ "path": "../make-api" },
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user