Compare commits
37 Commits
3cc14b32c7
...
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d295d4cd11 | ||
|
|
daa98762fa | ||
|
|
8b6809cf81 | ||
|
|
809be87e45 | ||
|
|
6713bba164 | ||
|
|
0312565b52 | ||
|
|
da4de9db11 | ||
|
|
704aa945f7 | ||
|
|
ebc5b45380 | ||
|
|
02d8e38f4d | ||
|
|
07578a7ac7 | ||
|
|
24d4ab6e46 | ||
|
|
714450d0bb | ||
|
|
1bf1befd6d | ||
|
|
c0433a8c76 | ||
|
|
95db23b2be | ||
|
|
cfb3d20d44 | ||
|
|
ca1d28a93c | ||
|
|
94f16a3967 | ||
|
|
4366f2af6e | ||
|
|
7179913d6d | ||
|
|
511f43e3f8 | ||
|
|
cdba0c9187 | ||
|
|
aa2478eb86 | ||
|
|
a10e8a58a7 | ||
|
|
a8f7bf0616 | ||
|
|
b0b472e2b2 | ||
|
|
48d6147a05 | ||
|
|
e9f0944d3e | ||
|
|
1355061c56 | ||
|
|
5e1f09dc47 | ||
|
|
fc5d328d83 | ||
|
|
7407b78ca7 | ||
|
|
6e88706f15 | ||
|
|
39093bfc09 | ||
|
|
ddbcfcdc1d | ||
|
|
07ff9a9da2 |
@@ -6,7 +6,7 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
@@ -15,8 +15,6 @@ jobs:
|
|||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
registry-url: https://git.jvalver.de/api/packages/thilawyn/npm
|
|
||||||
scope: "@thilawyn"
|
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -25,7 +23,6 @@ jobs:
|
|||||||
run: npm run build
|
run: npm run build
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
npm adduser
|
npm config set @thilawyn:registry https://git.valverde.cloud/api/packages/thilawyn/npm/
|
||||||
|
npm config set -- //git.valverde.cloud/api/packages/thilawyn/npm/:_authToken "${{ vars.NODE_AUTH_TOKEN }}"
|
||||||
npm publish
|
npm publish
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
test-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
|
|||||||
87
package.json
87
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@thilawyn/thilalib",
|
"name": "@thilawyn/thilalib",
|
||||||
"version": "0.1.0",
|
"version": "0.1.21",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"./dist"
|
"./dist"
|
||||||
@@ -8,58 +8,75 @@
|
|||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": {
|
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"default": "./dist/index.js"
|
"default": "./dist/index.js"
|
||||||
},
|
},
|
||||||
"require": {
|
"./Express": {
|
||||||
"types": "./dist/index.d.cts",
|
"types": "./dist/Express/index.d.ts",
|
||||||
"default": "./dist/index.cjs"
|
"default": "./dist/Express/index.js"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"./effect/schema": {
|
"./TRPC": {
|
||||||
"import": {
|
"types": "./dist/TRPC/index.d.ts",
|
||||||
"types": "./dist/effect/schema/index.d.ts",
|
"default": "./dist/TRPC/index.js"
|
||||||
"default": "./dist/effect/schema/index.js"
|
|
||||||
},
|
},
|
||||||
"require": {
|
"./Types": {
|
||||||
"types": "./dist/effect/schema/index.d.cts",
|
"types": "./dist/Types/index.d.ts",
|
||||||
"default": "./dist/effect/schema/index.cjs"
|
"default": "./dist/Types/index.js"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"./effect/schema/class": {
|
"./Schema": {
|
||||||
"import": {
|
"types": "./dist/Schema/index.d.ts",
|
||||||
"types": "./dist/effect/schema/class/index.d.ts",
|
"default": "./dist/Schema/index.js"
|
||||||
"default": "./dist/effect/schema/class/index.js"
|
|
||||||
},
|
},
|
||||||
"require": {
|
"./Schema/MobX": {
|
||||||
"types": "./dist/effect/schema/class/index.d.cts",
|
"types": "./dist/Schema/MobX/index.d.ts",
|
||||||
"default": "./dist/effect/schema/class/index.cjs"
|
"default": "./dist/Schema/MobX/index.js"
|
||||||
}
|
},
|
||||||
|
"./Schema/TanStackForm": {
|
||||||
|
"types": "./dist/Schema/TanStackForm/index.d.ts",
|
||||||
|
"default": "./dist/Schema/TanStackForm/index.js"
|
||||||
|
},
|
||||||
|
"./JSONWebToken": {
|
||||||
|
"types": "./dist/JSONWebToken.d.ts",
|
||||||
|
"default": "./dist/JSONWebToken.js"
|
||||||
|
},
|
||||||
|
"./OpenAIClient": {
|
||||||
|
"types": "./dist/OpenAIClient.d.ts",
|
||||||
|
"default": "./dist/OpenAIClient.js"
|
||||||
|
},
|
||||||
|
"./*": {
|
||||||
|
"types": "./dist/*.d.ts",
|
||||||
|
"default": "./dist/*.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup",
|
"build": "tsc",
|
||||||
"lint:tsc": "tsc --noEmit",
|
"lint:tsc": "tsc --noEmit",
|
||||||
"clean:cache": "rm -f tsconfig.tsbuildinfo",
|
"clean:cache": "rm -f tsconfig.tsbuildinfo",
|
||||||
"clean:dist": "rm -rf dist",
|
"clean:dist": "rm -rf dist",
|
||||||
"clean:node": "rm -rf node_modules"
|
"clean:node": "rm -rf node_modules"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"remeda": "^2.0.10",
|
"remeda": "^2.12.0",
|
||||||
"type-fest": "^4.20.1"
|
"type-fest": "^4.26.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "^1.1.13",
|
"@effect/schema": "^0.72.3",
|
||||||
"npm-check-updates": "^16.14.20",
|
"@tanstack/form-core": "^0.32.0",
|
||||||
|
"@trpc/server": "^10.45.2",
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
|
"@types/ws": "^8.5.12",
|
||||||
|
"bun-types": "^1.1.27",
|
||||||
|
"effect": "^3.7.2",
|
||||||
|
"express": "^4.21.0",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"mobx": "^6.13.2",
|
||||||
|
"npm-check-updates": "^17.1.1",
|
||||||
"npm-sort": "^0.0.4",
|
"npm-sort": "^0.0.4",
|
||||||
"tsup": "^8.1.0",
|
"openai": "^4.60.0",
|
||||||
"tsx": "^4.15.6",
|
"tsup": "^8.2.4",
|
||||||
"typescript": "^5.4.5"
|
"tsx": "^4.19.1",
|
||||||
},
|
"typescript": "^5.6.2",
|
||||||
"optionalDependencies": {
|
"ws": "^8.18.0"
|
||||||
"@effect/schema": "^0.68.0",
|
|
||||||
"effect": "^3.3.4",
|
|
||||||
"mobx": "^6.12.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/Express/ExpressApp.ts
Normal file
22
src/Express/ExpressApp.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Config, Context, Effect, Layer } from "effect"
|
||||||
|
import type { Express } from "express"
|
||||||
|
import { ImportError } from "../ImportError"
|
||||||
|
|
||||||
|
|
||||||
|
export class ExpressApp extends Context.Tag("ExpressApp")<ExpressApp, Express>() {}
|
||||||
|
|
||||||
|
|
||||||
|
const importExpress = Effect.tryPromise({
|
||||||
|
try: () => import("express"),
|
||||||
|
catch: cause => new ImportError({ path: "express", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ExpressAppLive = (
|
||||||
|
config: {
|
||||||
|
readonly trustProxy?: Config.Config<boolean | undefined>
|
||||||
|
} = {}
|
||||||
|
) => Layer.effect(ExpressApp, Effect.gen(function*() {
|
||||||
|
const app = (yield* importExpress).default()
|
||||||
|
app.set("trust proxy", (yield* config.trustProxy ?? Config.succeed(undefined)) ?? false)
|
||||||
|
return app
|
||||||
|
}))
|
||||||
70
src/Express/ExpressNodeHTTPServer.ts
Normal file
70
src/Express/ExpressNodeHTTPServer.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { Config, Context, Effect, Layer, Match } from "effect"
|
||||||
|
import type { Server } from "node:http"
|
||||||
|
import type { AddressInfo } from "node:net"
|
||||||
|
import { ImportError } from "../ImportError"
|
||||||
|
import { ExpressApp } from "./ExpressApp"
|
||||||
|
|
||||||
|
|
||||||
|
export class ExpressNodeHTTPServer extends Context.Tag("ExpressNodeHTTPServer")<ExpressNodeHTTPServer, Server>() {}
|
||||||
|
|
||||||
|
|
||||||
|
const importNodeHTTP = Effect.tryPromise({
|
||||||
|
try: () => import("node:http"),
|
||||||
|
catch: cause => new ImportError({ path: "node:http", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const serverListeningMessage = Match.type<AddressInfo | string | null>().pipe(
|
||||||
|
Match.when(Match.null, () => "HTTP server listening"),
|
||||||
|
Match.when(Match.string, v => `HTTP server listening on ${ v }`),
|
||||||
|
Match.orElse(v => `HTTP server listening on ${ v.address === "::" ? "*" : v.address }:${ v.port }`),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ExpressNodeHTTPServerLive = (
|
||||||
|
config: {
|
||||||
|
readonly backlog?: Config.Config<number | undefined>
|
||||||
|
readonly exclusive?: Config.Config<boolean | undefined>
|
||||||
|
readonly host?: Config.Config<string | undefined>
|
||||||
|
readonly ipv6Only?: Config.Config<boolean | undefined>
|
||||||
|
readonly path?: Config.Config<string | undefined>
|
||||||
|
readonly port?: Config.Config<number | undefined>
|
||||||
|
readonly readableAll?: Config.Config<boolean | undefined>
|
||||||
|
readonly signal?: AbortSignal
|
||||||
|
readonly writableAll?: Config.Config<boolean | undefined>
|
||||||
|
} = {}
|
||||||
|
) => Layer.effect(ExpressNodeHTTPServer, Effect.acquireRelease(
|
||||||
|
Effect.gen(function*() {
|
||||||
|
const app = yield* ExpressApp
|
||||||
|
const http = yield* importNodeHTTP
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
backlog: yield* config.backlog ?? Config.succeed(undefined),
|
||||||
|
exclusive: yield* config.exclusive ?? Config.succeed(undefined),
|
||||||
|
host: yield* config.host ?? Config.succeed(undefined),
|
||||||
|
ipv6Only: yield* config.ipv6Only ?? Config.succeed(undefined),
|
||||||
|
path: yield* config.path ?? Config.succeed(undefined),
|
||||||
|
port: yield* config.port ?? Config.succeed(undefined),
|
||||||
|
readableAll: yield* config.readableAll ?? Config.succeed(undefined),
|
||||||
|
signal: config.signal,
|
||||||
|
writableAll: yield* config.writableAll ?? Config.succeed(undefined),
|
||||||
|
} as const
|
||||||
|
|
||||||
|
return yield* Effect.async<Server>(resume => {
|
||||||
|
const server = http.createServer(app).listen(options,
|
||||||
|
() => resume(
|
||||||
|
Effect.succeed(server).pipe(
|
||||||
|
Effect.tap(Effect.logInfo(
|
||||||
|
serverListeningMessage(server.address())
|
||||||
|
))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
server => Effect.gen(function*() {
|
||||||
|
yield* Effect.logInfo("HTTP server is stopping. Waiting for existing connections to end...")
|
||||||
|
yield* Effect.async(resume => {
|
||||||
|
server.close(() => resume(Effect.logInfo("HTTP server closed")))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
))
|
||||||
17
src/Express/example.ts
Normal file
17
src/Express/example.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Effect, Layer } from "effect"
|
||||||
|
import { ExpressAppLive } from "./ExpressApp"
|
||||||
|
import { ExpressNodeHTTPServerLive } from "./ExpressNodeHTTPServer"
|
||||||
|
|
||||||
|
|
||||||
|
const AppLive = ExpressAppLive()
|
||||||
|
const HTTPServerLive = ExpressNodeHTTPServerLive()
|
||||||
|
|
||||||
|
const ServerLive = Layer.empty.pipe(
|
||||||
|
Layer.provideMerge(HTTPServerLive),
|
||||||
|
Layer.provideMerge(AppLive),
|
||||||
|
)
|
||||||
|
|
||||||
|
Layer.launch(ServerLive).pipe(
|
||||||
|
Effect.scoped,
|
||||||
|
Effect.runPromise,
|
||||||
|
)
|
||||||
2
src/Express/index.ts
Normal file
2
src/Express/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * as ExpressApp from "./ExpressApp"
|
||||||
|
export * as ExpressNodeHTTPServer from "./ExpressNodeHTTPServer"
|
||||||
11
src/ImportError.ts
Normal file
11
src/ImportError.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Data } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export class ImportError extends Data.TaggedError("ImportError")<{
|
||||||
|
path: string
|
||||||
|
cause: unknown
|
||||||
|
}> {
|
||||||
|
toString(): string {
|
||||||
|
return `Could not import '${ this.path }'`
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/JSONWebToken.ts
Normal file
56
src/JSONWebToken.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { Context, Effect, Layer } from "effect"
|
||||||
|
import type * as JWT from "jsonwebtoken"
|
||||||
|
import { ImportError } from "./ImportError"
|
||||||
|
|
||||||
|
|
||||||
|
export class JSONWebToken extends Context.Tag("JSONWebToken")<JSONWebToken, JSONWebTokenService>() {}
|
||||||
|
|
||||||
|
export interface JSONWebTokenService {
|
||||||
|
sign: (
|
||||||
|
payload: string | object | Buffer,
|
||||||
|
secretOrPrivateKey: JWT.Secret,
|
||||||
|
options: JWT.SignOptions,
|
||||||
|
) => Effect.Effect<
|
||||||
|
string,
|
||||||
|
Error,
|
||||||
|
never
|
||||||
|
>
|
||||||
|
|
||||||
|
verify: (
|
||||||
|
token: string,
|
||||||
|
secretOrPublicKey: JWT.Secret,
|
||||||
|
options: JWT.VerifyOptions,
|
||||||
|
) => Effect.Effect<
|
||||||
|
string | JWT.Jwt | JWT.JwtPayload,
|
||||||
|
JWT.VerifyErrors | Error,
|
||||||
|
never
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const importJWT = Effect.tryPromise({
|
||||||
|
try: () => import("jsonwebtoken"),
|
||||||
|
catch: cause => new ImportError({ path: "jsonwebtoken", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const JSONWebTokenLive = Layer.effect(JSONWebToken, importJWT.pipe(
|
||||||
|
Effect.map(jwt => JSONWebToken.of({
|
||||||
|
sign: (payload, secretOrPrivateKey, options) => Effect.async(resume =>
|
||||||
|
jwt.sign(payload, secretOrPrivateKey, options, (err, token) => {
|
||||||
|
resume(token
|
||||||
|
? Effect.succeed(token)
|
||||||
|
: Effect.fail(err || new Error("Unknown error"))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
verify: (token, secretOrPublicKey, options) => Effect.async(resume =>
|
||||||
|
jwt.verify(token, secretOrPublicKey, options, (err, decoded) => {
|
||||||
|
resume(decoded
|
||||||
|
? Effect.succeed(decoded)
|
||||||
|
: Effect.fail(err || new Error("Unknown error"))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
))
|
||||||
69
src/OpenAIClient.ts
Normal file
69
src/OpenAIClient.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { Config, Context, Effect, Layer } from "effect"
|
||||||
|
import type { OpenAI } from "openai"
|
||||||
|
import { ImportError } from "./ImportError"
|
||||||
|
|
||||||
|
|
||||||
|
export class OpenAIClient extends Context.Tag("OpenAIClient")<OpenAIClient, OpenAIClientService>() {}
|
||||||
|
|
||||||
|
export class OpenAIClientService {
|
||||||
|
constructor(
|
||||||
|
readonly openai: Effect.Effect.Success<typeof importOpenAI>,
|
||||||
|
readonly client: OpenAI,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
try<A>(
|
||||||
|
try_: (
|
||||||
|
client: OpenAI,
|
||||||
|
signal: AbortSignal,
|
||||||
|
) => Promise<A>
|
||||||
|
) {
|
||||||
|
return Effect.tryPromise({
|
||||||
|
try: signal => try_(this.client, signal),
|
||||||
|
catch: e => e instanceof this.openai.OpenAIError
|
||||||
|
? e
|
||||||
|
: new Error(`Unknown OpenAIClient error: ${ e }`),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const importOpenAI = Effect.tryPromise({
|
||||||
|
try: () => import("openai"),
|
||||||
|
catch: cause => new ImportError({ path: "openai", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const OpenAIClientLive = (
|
||||||
|
config: {
|
||||||
|
readonly apiKey: Config.Config<string>
|
||||||
|
readonly organization?: Config.Config<string | undefined>
|
||||||
|
readonly project?: Config.Config<string | undefined>
|
||||||
|
readonly baseURL?: Config.Config<string | undefined>
|
||||||
|
readonly timeout?: Config.Config<number | undefined>
|
||||||
|
readonly maxRetries?: Config.Config<number | undefined>
|
||||||
|
|
||||||
|
readonly httpAgent?: any
|
||||||
|
readonly fetch?: any
|
||||||
|
readonly defaultHeaders?: { [x: string]: string }
|
||||||
|
readonly defaultQuery?: { [x: string]: string }
|
||||||
|
}
|
||||||
|
) => Layer.effect(OpenAIClient, Effect.gen(function*() {
|
||||||
|
const openai = yield* importOpenAI
|
||||||
|
|
||||||
|
return new OpenAIClientService(
|
||||||
|
openai,
|
||||||
|
|
||||||
|
new openai.OpenAI({
|
||||||
|
apiKey: yield* config.apiKey,
|
||||||
|
organization: (yield* config.organization ?? Config.succeed(undefined)) ?? null,
|
||||||
|
project: (yield* config.project ?? Config.succeed(undefined)) ?? null,
|
||||||
|
baseURL: (yield* config.baseURL ?? Config.succeed(undefined)) ?? "https://api.openai.com/v1",
|
||||||
|
timeout: yield* config.timeout ?? Config.succeed(undefined),
|
||||||
|
maxRetries: yield* config.maxRetries ?? Config.succeed(undefined),
|
||||||
|
|
||||||
|
httpAgent: config.httpAgent,
|
||||||
|
fetch: config.fetch,
|
||||||
|
defaultHeaders: config.defaultHeaders,
|
||||||
|
defaultQuery: config.defaultQuery,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}))
|
||||||
44
src/PrismaStudioRoute.ts
Normal file
44
src/PrismaStudioRoute.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// import { StudioServer } from "@prisma/studio-server"
|
||||||
|
// import { Config, Effect, Layer } from "effect"
|
||||||
|
// import proxy from "express-http-proxy"
|
||||||
|
// import { ExpressApp } from "../http/ExpressApp.service"
|
||||||
|
// import { PrismaClient } from "./PrismaClient.service"
|
||||||
|
|
||||||
|
|
||||||
|
// export const PrismaStudioRoute = ({
|
||||||
|
// httpRoot = Config.succeed("/studio"),
|
||||||
|
// httpPort = Config.succeed(5555),
|
||||||
|
// schemaPath = Config.succeed(""),
|
||||||
|
// schemaText = Config.succeed(""),
|
||||||
|
// }: {
|
||||||
|
// readonly httpRoot?: Config.Config<string>
|
||||||
|
// readonly httpPort?: Config.Config<number>
|
||||||
|
// readonly schemaPath?: Config.Config<string>
|
||||||
|
// readonly schemaText?: Config.Config<string>
|
||||||
|
// } = {}) =>
|
||||||
|
// Layer.effectDiscard(Effect.acquireRelease(
|
||||||
|
// Effect.gen(function*() {
|
||||||
|
// const prisma = yield* PrismaClient
|
||||||
|
// const app = yield* ExpressApp
|
||||||
|
|
||||||
|
// const port = yield* httpPort
|
||||||
|
|
||||||
|
// const server = new StudioServer({
|
||||||
|
// port,
|
||||||
|
// schemaPath: yield* schemaPath,
|
||||||
|
// schemaText: yield* schemaText,
|
||||||
|
// versions: { prisma: prisma.Prisma.prismaVersion.client },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// app.use(yield* httpRoot, proxy(`http://localhost:${ port }`))
|
||||||
|
|
||||||
|
// yield* Effect.promise(() => server.start())
|
||||||
|
// return server
|
||||||
|
// }),
|
||||||
|
|
||||||
|
// server => Effect.sync(() => server.stop()),
|
||||||
|
// ))
|
||||||
|
|
||||||
|
|
||||||
|
// export const PrismaStudioRouteLive = Layer.empty
|
||||||
|
// export const PrismaStudioRouteDebug = PrismaStudioRoute()
|
||||||
17
src/Schema/Class.ts
Normal file
17
src/Schema/Class.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
import type { HasFields } from "./util"
|
||||||
|
|
||||||
|
|
||||||
|
export const Class = Schema.Class as <Self>(identifier: string) =>
|
||||||
|
<Fields extends Schema.Struct.Fields>(
|
||||||
|
fieldsOr: Fields | HasFields<Fields>,
|
||||||
|
annotations?: Schema.Annotations.Schema<Self>,
|
||||||
|
) => Schema.Class<
|
||||||
|
Self,
|
||||||
|
Fields,
|
||||||
|
Schema.Struct.Encoded<Fields>,
|
||||||
|
Schema.Struct.Context<Fields>,
|
||||||
|
Schema.Struct.Constructor<Fields>,
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
>
|
||||||
52
src/Schema/DateTime.ts
Normal file
52
src/Schema/DateTime.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { ParseResult, Schema } from "@effect/schema"
|
||||||
|
import { DateTime } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export const DateTimeUtcFromDate = Schema.transformOrFail(
|
||||||
|
Schema.DateFromSelf,
|
||||||
|
Schema.DateTimeUtcFromSelf,
|
||||||
|
|
||||||
|
{
|
||||||
|
strict: true,
|
||||||
|
|
||||||
|
decode: (date, _, ast) => ParseResult.try({
|
||||||
|
try: () => DateTime.unsafeMake(date),
|
||||||
|
|
||||||
|
catch: e => new ParseResult.Type(
|
||||||
|
ast,
|
||||||
|
date,
|
||||||
|
e instanceof Error ? e.message : undefined,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
encode: dateTimeUtc => ParseResult.succeed(
|
||||||
|
DateTime.toDateUtc(dateTimeUtc)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const DateTimeZonedFromDate = (options: {
|
||||||
|
readonly timeZone: number | string | DateTime.TimeZone
|
||||||
|
readonly adjustForTimeZone?: boolean | undefined
|
||||||
|
}) => Schema.transformOrFail(
|
||||||
|
Schema.DateFromSelf,
|
||||||
|
Schema.DateTimeZonedFromSelf,
|
||||||
|
|
||||||
|
{
|
||||||
|
strict: true,
|
||||||
|
|
||||||
|
decode: (date, _, ast) => ParseResult.try({
|
||||||
|
try: () => DateTime.unsafeMakeZoned(date, options),
|
||||||
|
|
||||||
|
catch: e => new ParseResult.Type(
|
||||||
|
ast,
|
||||||
|
date,
|
||||||
|
e instanceof Error ? e.message : undefined,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
encode: dateTime => ParseResult.succeed(
|
||||||
|
DateTime.toDate(dateTime)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
15
src/Schema/Kind.ts
Normal file
15
src/Schema/Kind.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
|
||||||
|
|
||||||
|
export const Kind = <Kind extends string>(kind: Kind) =>
|
||||||
|
Schema.Literal(kind).pipe(
|
||||||
|
Schema.propertySignature,
|
||||||
|
Schema.withConstructorDefault(() => kind),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const KindWithDecodingDefault = <Kind extends string>(kind: Kind) =>
|
||||||
|
Schema.Literal(kind).pipe(
|
||||||
|
Schema.optional,
|
||||||
|
Schema.withConstructorDefault(() => kind),
|
||||||
|
Schema.withDecodingDefault(() => kind),
|
||||||
|
)
|
||||||
26
src/Schema/MobX/ObservableClass.ts
Normal file
26
src/Schema/MobX/ObservableClass.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
import { makeObservable, observable, type CreateObservableOptions } from "mobx"
|
||||||
|
import { mapValues } from "remeda"
|
||||||
|
|
||||||
|
|
||||||
|
export interface ObservableClassSelf {
|
||||||
|
new(...args: any[]): Schema.Struct.Type<Schema.Struct.Fields>
|
||||||
|
readonly fields: { readonly [K in keyof Schema.Struct.Fields]: Schema.Struct.Fields[K] }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObservableClassOptions extends Omit<CreateObservableOptions, "proxy"> {}
|
||||||
|
|
||||||
|
export const ObservableClass = (options?: ObservableClassOptions) =>
|
||||||
|
<Self extends ObservableClassSelf>(self: Self) =>
|
||||||
|
class Observable extends self {
|
||||||
|
declare ["constructor"]: typeof Observable
|
||||||
|
|
||||||
|
constructor(...args: any[]) {
|
||||||
|
super(...args)
|
||||||
|
|
||||||
|
makeObservable(this,
|
||||||
|
mapValues(this.constructor.fields, () => observable),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} as Self
|
||||||
1
src/Schema/MobX/index.ts
Normal file
1
src/Schema/MobX/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./ObservableClass"
|
||||||
54
src/Schema/MutableClass.ts
Normal file
54
src/Schema/MutableClass.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
import type { Mutable } from "effect/Types"
|
||||||
|
import type { StaticType } from "../Types"
|
||||||
|
import type { HasFields } from "./util"
|
||||||
|
|
||||||
|
|
||||||
|
export interface IMutableClass<
|
||||||
|
Self,
|
||||||
|
Fields extends Schema.Struct.Fields,
|
||||||
|
I, R, C,
|
||||||
|
Inherited,
|
||||||
|
Proto,
|
||||||
|
>
|
||||||
|
extends Omit<
|
||||||
|
StaticType<Schema.Class<Self, Fields, I, R, C, Inherited, Proto>>,
|
||||||
|
"extend"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
new(
|
||||||
|
...args: ConstructorParameters<Schema.Class<Self, Fields, I, R, C, Inherited, Proto>>
|
||||||
|
): Omit<
|
||||||
|
InstanceType<Schema.Class<Self, Fields, I, R, C, Inherited, Proto>>,
|
||||||
|
keyof Fields
|
||||||
|
> &
|
||||||
|
Mutable<Schema.Struct.Type<Fields>>
|
||||||
|
|
||||||
|
extend<Extended>(identifier: string): <newFields extends Schema.Struct.Fields>(
|
||||||
|
fields: newFields | HasFields<newFields>,
|
||||||
|
annotations?: Schema.Annotations.Schema<Extended>,
|
||||||
|
) => IMutableClass<
|
||||||
|
Extended,
|
||||||
|
Fields & newFields,
|
||||||
|
I & Schema.Struct.Encoded<newFields>,
|
||||||
|
R | Schema.Struct.Context<newFields>,
|
||||||
|
C & Schema.Struct.Constructor<newFields>,
|
||||||
|
Self,
|
||||||
|
Proto
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const MutableClass = Schema.Class as <Self>(identifier: string) =>
|
||||||
|
<Fields extends Schema.Struct.Fields>(
|
||||||
|
fieldsOr: Fields | HasFields<Fields>,
|
||||||
|
annotations?: Schema.Annotations.Schema<Self>,
|
||||||
|
) => IMutableClass<
|
||||||
|
Self,
|
||||||
|
Fields,
|
||||||
|
Schema.Struct.Encoded<Fields>,
|
||||||
|
Schema.Struct.Context<Fields>,
|
||||||
|
Schema.Struct.Constructor<Fields>,
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
>
|
||||||
37
src/Schema/MutableTaggedClass.ts
Normal file
37
src/Schema/MutableTaggedClass.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
import type { IMutableClass } from "./MutableClass"
|
||||||
|
import type { HasFields } from "./util"
|
||||||
|
|
||||||
|
|
||||||
|
export interface IMutableTaggedClass<
|
||||||
|
Self,
|
||||||
|
Tag,
|
||||||
|
Fields extends Schema.Struct.Fields,
|
||||||
|
>
|
||||||
|
extends IMutableClass<
|
||||||
|
Self,
|
||||||
|
Fields,
|
||||||
|
Schema.Struct.Encoded<Fields>,
|
||||||
|
Schema.Struct.Context<Fields>,
|
||||||
|
Schema.Struct.Constructor<Omit<Fields, "_tag">>,
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
readonly _tag: Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const MutableTaggedClass = Schema.TaggedClass as <Self>(identifier?: string) =>
|
||||||
|
<
|
||||||
|
Tag extends string,
|
||||||
|
Fields extends Schema.Struct.Fields,
|
||||||
|
>(
|
||||||
|
tag: Tag,
|
||||||
|
fieldsOr: Fields | HasFields<Fields>,
|
||||||
|
annotations?: Schema.Annotations.Schema<Self>,
|
||||||
|
) => IMutableTaggedClass<
|
||||||
|
Self,
|
||||||
|
Tag,
|
||||||
|
{ readonly _tag: Schema.PropertySignature<":", Tag, never, ":", Tag, true, never> } & Fields
|
||||||
|
>
|
||||||
15
src/Schema/Tag.ts
Normal file
15
src/Schema/Tag.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
|
||||||
|
|
||||||
|
export const Tag = <Tag extends string>(tag: Tag) =>
|
||||||
|
Schema.Literal(tag).pipe(
|
||||||
|
Schema.propertySignature,
|
||||||
|
Schema.withConstructorDefault(() => tag),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const TagWithDecodingDefault = <Tag extends string>(tag: Tag) =>
|
||||||
|
Schema.Literal(tag).pipe(
|
||||||
|
Schema.optional,
|
||||||
|
Schema.withConstructorDefault(() => tag),
|
||||||
|
Schema.withDecodingDefault(() => tag),
|
||||||
|
)
|
||||||
17
src/Schema/TaggedClass.ts
Normal file
17
src/Schema/TaggedClass.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
import type { HasFields } from "./util"
|
||||||
|
|
||||||
|
|
||||||
|
export const TaggedClass = Schema.TaggedClass as <Self>(identifier?: string) =>
|
||||||
|
<
|
||||||
|
Tag extends string,
|
||||||
|
Fields extends Schema.Struct.Fields,
|
||||||
|
>(
|
||||||
|
tag: Tag,
|
||||||
|
fieldsOr: Fields | HasFields<Fields>,
|
||||||
|
annotations?: Schema.Annotations.Schema<Self>,
|
||||||
|
) => Schema.TaggedClass<
|
||||||
|
Self,
|
||||||
|
Tag,
|
||||||
|
{ readonly _tag: Schema.PropertySignature<":", Tag, never, ":", Tag, true, never> } & Fields
|
||||||
|
>
|
||||||
1
src/Schema/TanStackForm/index.ts
Normal file
1
src/Schema/TanStackForm/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./makeSchemaFormValidator"
|
||||||
138
src/Schema/TanStackForm/makeSchemaFormValidator.ts
Normal file
138
src/Schema/TanStackForm/makeSchemaFormValidator.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { ArrayFormatter, Schema } from "@effect/schema"
|
||||||
|
import type { ParseOptions } from "@effect/schema/AST"
|
||||||
|
import type { DeepKeys, DeepValue, FieldApi, FormApi, FormValidationError, ValidationError, Validator } from "@tanstack/form-core"
|
||||||
|
import { Array, Effect, Either, flow, Layer, ManagedRuntime, pipe } from "effect"
|
||||||
|
import { mapValues } from "remeda"
|
||||||
|
|
||||||
|
|
||||||
|
export const makeSchemaFormValidator = <A, I>(
|
||||||
|
schema: Schema.Schema<A, I, never>,
|
||||||
|
options?: ParseOptions,
|
||||||
|
) => {
|
||||||
|
const decodeEither = Schema.decodeEither(schema, options)
|
||||||
|
|
||||||
|
return <
|
||||||
|
TFormData extends I,
|
||||||
|
TFormValidator extends Validator<TFormData, unknown> | undefined,
|
||||||
|
>(props: {
|
||||||
|
value: TFormData
|
||||||
|
formApi: FormApi<TFormData, TFormValidator>
|
||||||
|
}): FormValidationError<TFormData> =>
|
||||||
|
decodeEither(props.value).pipe(result =>
|
||||||
|
Either.isLeft(result)
|
||||||
|
? {
|
||||||
|
form: "Please check the fields",
|
||||||
|
fields: issuesToFieldsRecord(ArrayFormatter.formatErrorSync(result.left), props.formApi) as any,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeSchemaAsyncFormValidator = <A, I>(
|
||||||
|
schema: Schema.Schema<A, I, never>,
|
||||||
|
options?: ParseOptions,
|
||||||
|
) => {
|
||||||
|
const runtime = ManagedRuntime.make(Layer.empty)
|
||||||
|
const decode = Schema.decode(schema, options)
|
||||||
|
|
||||||
|
return <
|
||||||
|
TFormData extends I,
|
||||||
|
TFormValidator extends Validator<TFormData, unknown> | undefined,
|
||||||
|
>(props: {
|
||||||
|
value: TFormData
|
||||||
|
formApi: FormApi<TFormData, TFormValidator>
|
||||||
|
signal: AbortSignal
|
||||||
|
}): Promise<FormValidationError<TFormData>> =>
|
||||||
|
decode(props.value).pipe(
|
||||||
|
Effect.matchEffect({
|
||||||
|
onSuccess: () => Effect.succeed(null),
|
||||||
|
|
||||||
|
onFailure: e => ArrayFormatter.formatError(e).pipe(
|
||||||
|
Effect.map(issues => ({
|
||||||
|
form: "Please check the fields",
|
||||||
|
fields: issuesToFieldsRecord(issues, props.formApi) as any,
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
prgm => runtime.runPromise(prgm, { signal: props.signal }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: handle nested array fields
|
||||||
|
*/
|
||||||
|
const issuesToFieldsRecord = <
|
||||||
|
TFormData,
|
||||||
|
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
|
||||||
|
>(
|
||||||
|
issues: readonly ArrayFormatter.Issue[],
|
||||||
|
_formApi: FormApi<TFormData, TFormValidator>,
|
||||||
|
) => pipe(issues,
|
||||||
|
Array.groupBy(issue => issue.path.join(".")),
|
||||||
|
|
||||||
|
mapValues(flow(
|
||||||
|
Array.map(issue => issue.message),
|
||||||
|
Array.join("\n"),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
export const makeSchemaFieldValidator = <A, I>(
|
||||||
|
schema: Schema.Schema<A, I, never>,
|
||||||
|
options?: ParseOptions,
|
||||||
|
) => {
|
||||||
|
const decodeEither = Schema.decodeEither(schema, options)
|
||||||
|
|
||||||
|
return <
|
||||||
|
TParentData,
|
||||||
|
TName extends DeepKeys<TParentData>,
|
||||||
|
TFieldValidator extends Validator<DeepValue<TParentData, TName>, unknown> | undefined,
|
||||||
|
TFormValidator extends Validator<TParentData, unknown> | undefined,
|
||||||
|
TData extends I & DeepValue<TParentData, TName>,
|
||||||
|
>(props: {
|
||||||
|
value: TData
|
||||||
|
fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>
|
||||||
|
}): ValidationError =>
|
||||||
|
decodeEither(props.value).pipe(result =>
|
||||||
|
Either.isLeft(result)
|
||||||
|
? ArrayFormatter.formatErrorSync(result.left)
|
||||||
|
.map(issue => issue.message)
|
||||||
|
.join("\n")
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeSchemaAsyncFieldValidator = <A, I>(
|
||||||
|
schema: Schema.Schema<A, I, never>,
|
||||||
|
options?: ParseOptions,
|
||||||
|
) => {
|
||||||
|
const runtime = ManagedRuntime.make(Layer.empty)
|
||||||
|
const decode = Schema.decode(schema, options)
|
||||||
|
|
||||||
|
return <
|
||||||
|
TParentData,
|
||||||
|
TName extends DeepKeys<TParentData>,
|
||||||
|
TFieldValidator extends Validator<DeepValue<TParentData, TName>, unknown> | undefined,
|
||||||
|
TFormValidator extends Validator<TParentData, unknown> | undefined,
|
||||||
|
TData extends I & DeepValue<TParentData, TName>,
|
||||||
|
>(props: {
|
||||||
|
value: TData
|
||||||
|
fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>
|
||||||
|
signal: AbortSignal
|
||||||
|
}): Promise<ValidationError> =>
|
||||||
|
decode(props.value).pipe(
|
||||||
|
Effect.matchEffect({
|
||||||
|
onSuccess: () => Effect.succeed(null),
|
||||||
|
|
||||||
|
onFailure: e => ArrayFormatter.formatError(e).pipe(
|
||||||
|
Effect.map(flow(
|
||||||
|
Array.map(issue => issue.message),
|
||||||
|
Array.join("\n"),
|
||||||
|
))
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
prgm => runtime.runPromise(prgm, { signal: props.signal }),
|
||||||
|
)
|
||||||
|
}
|
||||||
12
src/Schema/email.ts
Normal file
12
src/Schema/email.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
|
||||||
|
|
||||||
|
export const email = Schema.pattern(
|
||||||
|
/^(?!\.)(?!.*\.\.)([A-Z0-9_+-.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i,
|
||||||
|
|
||||||
|
{
|
||||||
|
identifier: "email",
|
||||||
|
title: "email",
|
||||||
|
message: () => "Not an email address",
|
||||||
|
},
|
||||||
|
)
|
||||||
13
src/Schema/encodedAsPrismaJsonValue.ts
Normal file
13
src/Schema/encodedAsPrismaJsonValue.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Schema } from "@effect/schema"
|
||||||
|
import type { JsonValue } from "type-fest"
|
||||||
|
import type { PrismaJson } from "../Types"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a schema of which the encoded value satisfies type-fest's `JsonValue` and returns the same schema with Prisma's `JsonValue` as its encoded type instead.
|
||||||
|
* This allows you use to use jsonifiable schemas to strongly type `Json` database fields in Prisma.
|
||||||
|
*
|
||||||
|
* This is needed because Prisma's `JsonValue` is poorly implemented and does not work well with some types, such as readonly arrays.
|
||||||
|
*/
|
||||||
|
export const encodedAsPrismaJsonValue = <A, E extends JsonValue, R>(schema: Schema.Schema<A, E, R>) =>
|
||||||
|
schema as unknown as Schema.Schema<A, PrismaJson.JsonValue, R>
|
||||||
12
src/Schema/index.ts
Normal file
12
src/Schema/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export * from "./Class"
|
||||||
|
export * from "./DateTime"
|
||||||
|
export * from "./email"
|
||||||
|
export * from "./encodedAsPrismaJsonValue"
|
||||||
|
export * from "./Kind"
|
||||||
|
export * as MobX from "./MobX"
|
||||||
|
export * from "./MutableClass"
|
||||||
|
export * from "./MutableTaggedClass"
|
||||||
|
export * from "./Tag"
|
||||||
|
export * from "./TaggedClass"
|
||||||
|
export * as TanStackForm from "./TanStackForm"
|
||||||
|
export * from "./toJsonifiable"
|
||||||
13
src/Schema/toJsonifiable.ts
Normal file
13
src/Schema/toJsonifiable.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
import type { JsonValue } from "type-fest"
|
||||||
|
|
||||||
|
|
||||||
|
export const toJsonifiable = <
|
||||||
|
JsonifiableA,
|
||||||
|
JsonifiableI extends JsonValue,
|
||||||
|
JsonifiableR,
|
||||||
|
>(
|
||||||
|
jsonifiableSchema: Schema.Schema<JsonifiableA, JsonifiableI, JsonifiableR>
|
||||||
|
) =>
|
||||||
|
<A, R>(decodedSchema: Schema.Schema<A, JsonifiableA, R>) =>
|
||||||
|
Schema.compose(jsonifiableSchema, decodedSchema, { strict: true })
|
||||||
6
src/Schema/util.ts
Normal file
6
src/Schema/util.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
|
||||||
|
|
||||||
|
export type HasFields<Fields extends Schema.Struct.Fields> =
|
||||||
|
| { readonly fields: Fields }
|
||||||
|
| { readonly from: HasFields<Fields> }
|
||||||
20
src/TRPC/TRPCBuilder.ts
Normal file
20
src/TRPC/TRPCBuilder.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Context, Effect, Layer } from "effect"
|
||||||
|
import { type TRPCContext } from "./TRPCContext"
|
||||||
|
import { importTRPCServer } from "./importTRPCServer"
|
||||||
|
|
||||||
|
|
||||||
|
const createTRPC = <R>() => importTRPCServer.pipe(Effect.map(({ initTRPC }) =>
|
||||||
|
initTRPC.context<TRPCContext<R>>().create()
|
||||||
|
))
|
||||||
|
|
||||||
|
export const Identifier = "@thilalib/TRPC/TRPCBuilder"
|
||||||
|
export interface TRPCBuilder<R> extends Context.Tag<typeof Identifier, TRPCBuilderService<R>> {}
|
||||||
|
export interface TRPCBuilderService<R> extends Effect.Effect.Success<ReturnType<typeof createTRPC<R>>> {}
|
||||||
|
|
||||||
|
|
||||||
|
export const make = <R>() => {
|
||||||
|
const TRPCBuilder = Context.GenericTag<typeof Identifier, TRPCBuilderService<R>>(Identifier)
|
||||||
|
const TRPCBuilderLive = Layer.effect(TRPCBuilder, createTRPC())
|
||||||
|
|
||||||
|
return { TRPCBuilder, TRPCBuilderLive } as const
|
||||||
|
}
|
||||||
42
src/TRPC/TRPCContext.ts
Normal file
42
src/TRPC/TRPCContext.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import type { TRPCError } from "@trpc/server"
|
||||||
|
import { Data, type Effect, type Runtime } from "effect"
|
||||||
|
import type { RuntimeFiber } from "effect/Fiber"
|
||||||
|
import type express from "express"
|
||||||
|
import type { IncomingMessage } from "node:http"
|
||||||
|
import type { WebSocket } from "ws"
|
||||||
|
|
||||||
|
|
||||||
|
export interface TRPCContext<R> extends TRPCContextRuntime<R>, TRPCContextTransaction {}
|
||||||
|
|
||||||
|
export interface TRPCContextRuntime<R> {
|
||||||
|
readonly runtime: Runtime.Runtime<R>
|
||||||
|
|
||||||
|
readonly run: <A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
options?: { readonly signal?: AbortSignal },
|
||||||
|
) => Promise<A>
|
||||||
|
|
||||||
|
readonly fork: <A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
options?: Runtime.RunForkOptions,
|
||||||
|
) => RuntimeFiber<A, TRPCError>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TRPCContextTransaction {
|
||||||
|
readonly transaction: TRPCTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type TRPCTransaction = Data.TaggedEnum<{
|
||||||
|
readonly Express: {
|
||||||
|
readonly req: express.Request
|
||||||
|
readonly res: express.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly WebSocket: {
|
||||||
|
readonly req: IncomingMessage
|
||||||
|
readonly res: WebSocket
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
|
||||||
|
export const TRPCTransactionEnum = Data.taggedEnum<TRPCTransaction>()
|
||||||
57
src/TRPC/TRPCContextCreator.ts
Normal file
57
src/TRPC/TRPCContextCreator.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express"
|
||||||
|
import type { CreateWSSContextFnOptions } from "@trpc/server/adapters/ws"
|
||||||
|
import { Context, Effect, Layer, Runtime } from "effect"
|
||||||
|
import { createTRCPErrorMapper } from "./createTRCPErrorMapper"
|
||||||
|
import { TRPCTransactionEnum, type TRPCContext, type TRPCTransaction } from "./TRPCContext"
|
||||||
|
|
||||||
|
|
||||||
|
export const Identifier = "@thilalib/TRPC/TRPCContextCreator"
|
||||||
|
|
||||||
|
export interface TRPCContextCreator<R> extends Context.Tag<typeof Identifier, TRPCContextCreatorService<R>> {}
|
||||||
|
|
||||||
|
export interface TRPCContextCreatorService<R> {
|
||||||
|
readonly createContext: (transaction: TRPCTransaction) => TRPCContext<R>
|
||||||
|
readonly createExpressContext: (context: CreateExpressContextOptions) => TRPCContext<R>
|
||||||
|
readonly createWebSocketContext: (context: CreateWSSContextFnOptions) => TRPCContext<R>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TRPCUnknownContextCreator = Context.GenericTag<typeof Identifier, TRPCContextCreatorService<unknown>>(Identifier)
|
||||||
|
|
||||||
|
|
||||||
|
export const make = <R>() => {
|
||||||
|
const TRPCContextCreator = Context.GenericTag<typeof Identifier, TRPCContextCreatorService<R>>(Identifier)
|
||||||
|
|
||||||
|
const TRPCContextCreatorLive = Layer.effect(TRPCContextCreator, Effect.gen(function*() {
|
||||||
|
const runtime = yield* Effect.runtime<R>()
|
||||||
|
const mapErrors = yield* createTRCPErrorMapper
|
||||||
|
|
||||||
|
const run = <A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
options?: { readonly signal?: AbortSignal },
|
||||||
|
) => Runtime.runPromise(runtime)(
|
||||||
|
effect.pipe(mapErrors),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
|
||||||
|
const fork = <A, E>(
|
||||||
|
effect: Effect.Effect<A, E, R>,
|
||||||
|
options?: Runtime.RunForkOptions,
|
||||||
|
) => Runtime.runFork(runtime)(
|
||||||
|
effect.pipe(mapErrors),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
|
||||||
|
const createContext = (transaction: TRPCTransaction) => ({
|
||||||
|
runtime,
|
||||||
|
run,
|
||||||
|
fork,
|
||||||
|
transaction,
|
||||||
|
})
|
||||||
|
const createExpressContext = (context: CreateExpressContextOptions) => createContext(TRPCTransactionEnum.Express(context))
|
||||||
|
const createWebSocketContext = (context: CreateWSSContextFnOptions) => createContext(TRPCTransactionEnum.WebSocket(context))
|
||||||
|
|
||||||
|
return { createContext, createExpressContext, createWebSocketContext } as const
|
||||||
|
}))
|
||||||
|
|
||||||
|
return { TRPCContextCreator, TRPCContextCreatorLive } as const
|
||||||
|
}
|
||||||
27
src/TRPC/TRPCExpressRoute.ts
Normal file
27
src/TRPC/TRPCExpressRoute.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Config, Effect, Layer } from "effect"
|
||||||
|
import { ExpressApp } from "../Express"
|
||||||
|
import { ImportError } from "../ImportError"
|
||||||
|
import { TRPCUnknownContextCreator } from "./TRPCContextCreator"
|
||||||
|
import { TRPCAnyRouter } from "./TRPCRouter"
|
||||||
|
|
||||||
|
|
||||||
|
const importTRPCServerExpressAdapter = Effect.tryPromise({
|
||||||
|
try: () => import("@trpc/server/adapters/express"),
|
||||||
|
catch: cause => new ImportError({ path: "@trpc/server/adapters/express", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const TRPCExpressRouteLive = (
|
||||||
|
config: {
|
||||||
|
readonly root: Config.Config<string>
|
||||||
|
}
|
||||||
|
) => Layer.effectDiscard(Effect.gen(function*() {
|
||||||
|
const { createExpressMiddleware } = yield* importTRPCServerExpressAdapter
|
||||||
|
const app = yield* ExpressApp.ExpressApp
|
||||||
|
|
||||||
|
app.use(yield* config.root,
|
||||||
|
createExpressMiddleware({
|
||||||
|
router: yield* TRPCAnyRouter,
|
||||||
|
createContext: (yield* TRPCUnknownContextCreator).createExpressContext,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}))
|
||||||
18
src/TRPC/TRPCRouter.ts
Normal file
18
src/TRPC/TRPCRouter.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { AnyRouter } from "@trpc/server"
|
||||||
|
import { Context, Effect, Layer } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export const Identifier = "@thilalib/TRPC/TRPCRouter"
|
||||||
|
export interface TRPCRouter<T extends AnyRouter> extends Context.Tag<typeof Identifier, T> {}
|
||||||
|
|
||||||
|
export const TRPCAnyRouter = Context.GenericTag<typeof Identifier, AnyRouter>(Identifier)
|
||||||
|
|
||||||
|
|
||||||
|
export const make = <A extends AnyRouter, E, R>(
|
||||||
|
router: Effect.Effect<A, E, R>
|
||||||
|
) => {
|
||||||
|
const TRPCRouter = Context.GenericTag<typeof Identifier, A>(Identifier)
|
||||||
|
const TRPCRouterLive = Layer.effect(TRPCRouter, router)
|
||||||
|
|
||||||
|
return { TRPCRouter, TRPCRouterLive } as const
|
||||||
|
}
|
||||||
66
src/TRPC/TRPCWebSocketServer.ts
Normal file
66
src/TRPC/TRPCWebSocketServer.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import type { applyWSSHandler } from "@trpc/server/adapters/ws"
|
||||||
|
import { Config, Context, Effect, Layer } from "effect"
|
||||||
|
import type ws from "ws"
|
||||||
|
import { ExpressNodeHTTPServer } from "../Express"
|
||||||
|
import { ImportError } from "../ImportError"
|
||||||
|
import { TRPCUnknownContextCreator } from "./TRPCContextCreator"
|
||||||
|
import { TRPCAnyRouter } from "./TRPCRouter"
|
||||||
|
|
||||||
|
|
||||||
|
export class TRPCWebSocketServer extends Context.Tag("@thilalib/TRPC/TRPCWebSocketServer")<TRPCWebSocketServer, TRPCWebSocketServerService>() {}
|
||||||
|
|
||||||
|
export interface TRPCWebSocketServerService {
|
||||||
|
wss: ws.Server
|
||||||
|
handler: ReturnType<typeof applyWSSHandler>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const importWS = Effect.tryPromise({
|
||||||
|
try: () => import("ws"),
|
||||||
|
catch: cause => new ImportError({ path: "ws", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const importTRPCServerWSAdapter = Effect.tryPromise({
|
||||||
|
try: () => import("@trpc/server/adapters/ws"),
|
||||||
|
catch: cause => new ImportError({ path: "@trpc/server/adapters/ws", cause }),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const TRPCWebSocketServerLive = (
|
||||||
|
config: {
|
||||||
|
readonly host: Config.Config<string>
|
||||||
|
}
|
||||||
|
) => Layer.effect(TRPCWebSocketServer, Effect.gen(function*() {
|
||||||
|
const { WebSocketServer } = yield* importWS
|
||||||
|
const { applyWSSHandler } = yield* importTRPCServerWSAdapter
|
||||||
|
|
||||||
|
const host = yield* config.host
|
||||||
|
|
||||||
|
return yield* Effect.acquireRelease(
|
||||||
|
Effect.gen(function*() {
|
||||||
|
yield* Effect.logInfo(`WebSocket server started on ${ host }`)
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({
|
||||||
|
server: yield* ExpressNodeHTTPServer.ExpressNodeHTTPServer,
|
||||||
|
host,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
wss,
|
||||||
|
handler: applyWSSHandler({
|
||||||
|
wss,
|
||||||
|
router: yield* TRPCAnyRouter,
|
||||||
|
createContext: (yield* TRPCUnknownContextCreator).createWebSocketContext,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
({ wss, handler }) => Effect.gen(function*() {
|
||||||
|
yield* Effect.logInfo(`WebSocket server on ${ host } is stopping. Waiting for existing connections to end...`)
|
||||||
|
|
||||||
|
handler.broadcastReconnectNotification()
|
||||||
|
yield* Effect.async(resume => {
|
||||||
|
wss.close(() => resume(Effect.logInfo("WebSocket server closed")))
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}))
|
||||||
63
src/TRPC/createTRCPErrorMapper.ts
Normal file
63
src/TRPC/createTRCPErrorMapper.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { Effect, type Cause } from "effect"
|
||||||
|
import { importTRPCServer } from "./importTRPCServer"
|
||||||
|
|
||||||
|
|
||||||
|
export const createTRCPErrorMapper = importTRPCServer.pipe(Effect.map(({ TRPCError }) =>
|
||||||
|
<A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.sandbox(effect).pipe(
|
||||||
|
Effect.catchTags({
|
||||||
|
Empty: cause => Effect.fail(
|
||||||
|
new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
cause: new TRPCErrorCause(cause),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
Fail: cause => Effect.fail(
|
||||||
|
cause.error instanceof TRPCError
|
||||||
|
? cause.error
|
||||||
|
: new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
cause: new TRPCErrorCause(cause),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
Die: cause => Effect.fail(
|
||||||
|
cause.defect instanceof TRPCError
|
||||||
|
? cause.defect
|
||||||
|
: new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
cause: new TRPCErrorCause(cause),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
Interrupt: cause => Effect.fail(
|
||||||
|
new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
cause: new TRPCErrorCause(cause),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
Sequential: cause => Effect.fail(
|
||||||
|
new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
cause: new TRPCErrorCause(cause),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
Parallel: cause => Effect.fail(
|
||||||
|
new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
cause: new TRPCErrorCause(cause),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
Effect.tapError(Effect.logError),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
class TRPCErrorCause<E> extends Error {
|
||||||
|
constructor(readonly cause: Cause.Cause<E>) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/TRPC/example.ts
Normal file
62
src/TRPC/example.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Config, Effect, Layer } from "effect"
|
||||||
|
import * as TRPC from "."
|
||||||
|
import { Express, JSONWebToken } from ".."
|
||||||
|
|
||||||
|
|
||||||
|
// Context available to the router procedures
|
||||||
|
type Services =
|
||||||
|
| JSONWebToken.JSONWebToken
|
||||||
|
|
||||||
|
const ServicesLive = Layer.empty.pipe(
|
||||||
|
Layer.provideMerge(JSONWebToken.JSONWebTokenLive)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const { TRPCContextCreator, TRPCContextCreatorLive } = TRPC.TRPCContextCreator.make<Services>()
|
||||||
|
const { TRPCBuilder, TRPCBuilderLive } = TRPC.TRPCBuilder.make<Services>()
|
||||||
|
|
||||||
|
|
||||||
|
const router = Effect.gen(function*() {
|
||||||
|
const t = yield* TRPCBuilder
|
||||||
|
|
||||||
|
return t.router({
|
||||||
|
ping: t.procedure.query(({ ctx }) => ctx.run(
|
||||||
|
Effect.succeed("pong")
|
||||||
|
)),
|
||||||
|
|
||||||
|
expressOnlyProcedure: t.procedure
|
||||||
|
.use(yield* TRPC.ExpressOnly)
|
||||||
|
.query(({ ctx }) => ctx.run(Effect.gen(function*() {
|
||||||
|
ctx.transaction
|
||||||
|
}))),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { TRPCRouter, TRPCRouterLive } = TRPC.TRPCRouter.make(router)
|
||||||
|
|
||||||
|
|
||||||
|
const ServerLive = Layer.empty.pipe(
|
||||||
|
Layer.provideMerge(TRPC.TRPCExpressRoute.TRPCExpressRouteLive({
|
||||||
|
root: Config.succeed("/rpc")
|
||||||
|
})),
|
||||||
|
Layer.provideMerge(TRPC.TRPCWebSocketServer.TRPCWebSocketServerLive({
|
||||||
|
host: Config.succeed("/rpc")
|
||||||
|
})),
|
||||||
|
|
||||||
|
Layer.provideMerge(TRPCRouterLive),
|
||||||
|
Layer.provideMerge(TRPCBuilderLive),
|
||||||
|
Layer.provideMerge(TRPCContextCreatorLive),
|
||||||
|
|
||||||
|
Layer.provideMerge(Express.ExpressNodeHTTPServer.ExpressNodeHTTPServerLive({
|
||||||
|
port: Config.succeed(3000)
|
||||||
|
})),
|
||||||
|
Layer.provideMerge(Express.ExpressApp.ExpressAppLive())
|
||||||
|
)
|
||||||
|
|
||||||
|
await Effect.gen(function*() {
|
||||||
|
return yield* Layer.launch(ServerLive)
|
||||||
|
}).pipe(
|
||||||
|
Effect.provide(ServicesLive),
|
||||||
|
Effect.scoped,
|
||||||
|
Effect.runPromise,
|
||||||
|
)
|
||||||
11
src/TRPC/importTRPCServer.ts
Normal file
11
src/TRPC/importTRPCServer.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Effect } from "effect"
|
||||||
|
import { ImportError } from "../ImportError"
|
||||||
|
|
||||||
|
|
||||||
|
export const importTRPCServer: Effect.Effect<
|
||||||
|
typeof import("@trpc/server"),
|
||||||
|
ImportError
|
||||||
|
> = Effect.tryPromise({
|
||||||
|
try: () => import("@trpc/server"),
|
||||||
|
catch: cause => new ImportError({ path: "@trpc/server", cause }),
|
||||||
|
})
|
||||||
7
src/TRPC/index.ts
Normal file
7
src/TRPC/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export * from "./middlewares"
|
||||||
|
export * as TRPCBuilder from "./TRPCBuilder"
|
||||||
|
export * as TRPCContext from "./TRPCContext"
|
||||||
|
export * as TRPCContextCreator from "./TRPCContextCreator"
|
||||||
|
export * as TRPCExpressRoute from "./TRPCExpressRoute"
|
||||||
|
export * as TRPCRouter from "./TRPCRouter"
|
||||||
|
export * as TRPCWebSocketServer from "./TRPCWebSocketServer"
|
||||||
33
src/TRPC/middlewares/DecodeInput.ts
Normal file
33
src/TRPC/middlewares/DecodeInput.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Schema } from "@effect/schema"
|
||||||
|
import type { ParseOptions } from "@effect/schema/AST"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { importTRPCServer } from "../importTRPCServer"
|
||||||
|
import type { TRPCContextRuntime } from "../TRPCContext"
|
||||||
|
|
||||||
|
|
||||||
|
export const DecodeInput = <A, I>(
|
||||||
|
schema: Schema.Schema<A, I>,
|
||||||
|
options?: ParseOptions,
|
||||||
|
) => Effect.gen(function*() {
|
||||||
|
const { experimental_standaloneMiddleware, TRPCError } = yield* importTRPCServer
|
||||||
|
|
||||||
|
const decode = (value: I) => Schema.decode(schema, options)(value).pipe(
|
||||||
|
Effect.matchEffect({
|
||||||
|
onSuccess: Effect.succeed,
|
||||||
|
onFailure: e => Effect.fail(new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Could not decode input",
|
||||||
|
cause: e,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return experimental_standaloneMiddleware<{
|
||||||
|
ctx: TRPCContextRuntime<never>
|
||||||
|
input: I
|
||||||
|
}>().create(
|
||||||
|
async ({ ctx, input, next }) => next({
|
||||||
|
ctx: { decodedInput: await ctx.run(decode(input)) } as const
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
24
src/TRPC/middlewares/ExpressOnly.ts
Normal file
24
src/TRPC/middlewares/ExpressOnly.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Effect, identity, Match } from "effect"
|
||||||
|
import { importTRPCServer } from "../importTRPCServer"
|
||||||
|
import type { TRPCContextTransaction } from "../TRPCContext"
|
||||||
|
|
||||||
|
|
||||||
|
export const ExpressOnly = importTRPCServer.pipe(Effect.map(({
|
||||||
|
experimental_standaloneMiddleware,
|
||||||
|
TRPCError,
|
||||||
|
}) => experimental_standaloneMiddleware<{
|
||||||
|
ctx: TRPCContextTransaction
|
||||||
|
}>().create(({ ctx, next }) => next({
|
||||||
|
ctx: {
|
||||||
|
transaction: Match.value(ctx.transaction).pipe(
|
||||||
|
Match.tag("Express", identity),
|
||||||
|
|
||||||
|
Match.orElse(() => {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Only Express transport is supported by this procedure",
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))))
|
||||||
24
src/TRPC/middlewares/WebSocketOnly.ts
Normal file
24
src/TRPC/middlewares/WebSocketOnly.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Effect, identity, Match } from "effect"
|
||||||
|
import { importTRPCServer } from "../importTRPCServer"
|
||||||
|
import type { TRPCContextTransaction } from "../TRPCContext"
|
||||||
|
|
||||||
|
|
||||||
|
export const WebSocketOnly = importTRPCServer.pipe(Effect.map(({
|
||||||
|
experimental_standaloneMiddleware,
|
||||||
|
TRPCError,
|
||||||
|
}) => experimental_standaloneMiddleware<{
|
||||||
|
ctx: TRPCContextTransaction
|
||||||
|
}>().create(({ ctx, next }) => next({
|
||||||
|
ctx: {
|
||||||
|
transaction: Match.value(ctx.transaction).pipe(
|
||||||
|
Match.tag("WebSocket", identity),
|
||||||
|
|
||||||
|
Match.orElse(() => {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Only WebSocket transport is supported by this procedure",
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}))))
|
||||||
3
src/TRPC/middlewares/index.ts
Normal file
3
src/TRPC/middlewares/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./DecodeInput"
|
||||||
|
export * from "./ExpressOnly"
|
||||||
|
export * from "./WebSocketOnly"
|
||||||
4
src/Types/CommonKeys.ts
Normal file
4
src/Types/CommonKeys.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/**
|
||||||
|
* Extracts the common keys between two types
|
||||||
|
*/
|
||||||
|
export type CommonKeys<A, B> = Extract<keyof A, keyof B>
|
||||||
54
src/Types/Extend.ts
Normal file
54
src/Types/Extend.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import type { CommonKeys } from "./CommonKeys"
|
||||||
|
|
||||||
|
|
||||||
|
export type Extend<Super, Self> =
|
||||||
|
Extendable<Super, Self> extends true
|
||||||
|
? Omit<Super, CommonKeys<Self, Super>> & Self
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type Extendable<Super, Self> =
|
||||||
|
Pick<Self, CommonKeys<Self, Super>> extends Pick<Super, CommonKeys<Self, Super>>
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
|
||||||
|
|
||||||
|
export type ExtendAll<T extends readonly object[]> =
|
||||||
|
T extends readonly [
|
||||||
|
infer Super,
|
||||||
|
infer Self,
|
||||||
|
...infer Rest extends readonly object[],
|
||||||
|
]
|
||||||
|
? Extendable<Super, Self> extends true
|
||||||
|
? ExtendAll<readonly [Extend<Super, Self>, ...Rest]>
|
||||||
|
: never
|
||||||
|
: T extends readonly [infer Self]
|
||||||
|
? Self
|
||||||
|
: {}
|
||||||
|
|
||||||
|
export type ExtendableAll<T extends readonly object[]> =
|
||||||
|
T extends readonly [
|
||||||
|
infer Super,
|
||||||
|
infer Self,
|
||||||
|
...infer Rest extends readonly object[],
|
||||||
|
]
|
||||||
|
? Extendable<Super, Self> extends true
|
||||||
|
? ExtendableAll<readonly [Extend<Super, Self>, ...Rest]>
|
||||||
|
: false
|
||||||
|
: true
|
||||||
|
|
||||||
|
// export type NonExtendableKeys<T extends readonly object[]> = (
|
||||||
|
// T extends readonly [
|
||||||
|
// infer Super extends object,
|
||||||
|
// infer Self extends object,
|
||||||
|
// ...infer Rest extends readonly object[],
|
||||||
|
// ]
|
||||||
|
// ? {[K in keyof Super & keyof Self]: Self[K] extends Super[K]
|
||||||
|
// ? never
|
||||||
|
// : K
|
||||||
|
// }[keyof Super & keyof Self]
|
||||||
|
// | NonExtendableKeys<readonly [
|
||||||
|
// Super & Self,
|
||||||
|
// ...Rest,
|
||||||
|
// ]>
|
||||||
|
// : void
|
||||||
|
// )
|
||||||
15
src/Types/Merge.ts
Normal file
15
src/Types/Merge.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { CommonKeys } from "./CommonKeys"
|
||||||
|
|
||||||
|
|
||||||
|
export type Merge<Super, Self> = Omit<Super, CommonKeys<Self, Super>> & Self
|
||||||
|
|
||||||
|
export type MergeAll<T extends readonly object[]> =
|
||||||
|
T extends readonly [
|
||||||
|
infer Super,
|
||||||
|
infer Self,
|
||||||
|
...infer Rest extends readonly object[],
|
||||||
|
]
|
||||||
|
? MergeAll<readonly [Merge<Super, Self>, ...Rest]>
|
||||||
|
: T extends readonly [infer Self]
|
||||||
|
? Self
|
||||||
|
: {}
|
||||||
51
src/Types/PrismaJson.ts
Normal file
51
src/Types/PrismaJson.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* `@prisma/client`'s Json types
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From https://github.com/sindresorhus/type-fest/
|
||||||
|
* Matches a JSON object.
|
||||||
|
* This type can be useful to enforce some input to be JSON-compatible or as a super-type to be extended from.
|
||||||
|
*/
|
||||||
|
export type JsonObject = {[Key in string]?: JsonValue}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From https://github.com/sindresorhus/type-fest/
|
||||||
|
* Matches a JSON array.
|
||||||
|
*/
|
||||||
|
export interface JsonArray extends Array<JsonValue> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From https://github.com/sindresorhus/type-fest/
|
||||||
|
* Matches any valid JSON value.
|
||||||
|
*/
|
||||||
|
export type JsonValue = string | number | boolean | JsonObject | JsonArray | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches a JSON object.
|
||||||
|
* Unlike `JsonObject`, this type allows undefined and read-only properties.
|
||||||
|
*/
|
||||||
|
export type InputJsonObject = {readonly [Key in string]?: InputJsonValue | null}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches a JSON array.
|
||||||
|
* Unlike `JsonArray`, readonly arrays are assignable to this type.
|
||||||
|
*/
|
||||||
|
export interface InputJsonArray extends ReadonlyArray<InputJsonValue | null> {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches any valid value that can be used as an input for operations like
|
||||||
|
* create and update as the value of a JSON field. Unlike `JsonValue`, this
|
||||||
|
* type allows read-only arrays and read-only object properties and disallows
|
||||||
|
* `null` at the top level.
|
||||||
|
*
|
||||||
|
* `null` cannot be used as the value of a JSON field because its meaning
|
||||||
|
* would be ambiguous. Use `Prisma.JsonNull` to store the JSON null value or
|
||||||
|
* `Prisma.DbNull` to clear the JSON value and set the field to the database
|
||||||
|
* NULL value instead.
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values
|
||||||
|
*/
|
||||||
|
export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown }
|
||||||
10
src/Types/UnionToTuple.ts
Normal file
10
src/Types/UnionToTuple.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { UnionToIntersection } from "type-fest"
|
||||||
|
|
||||||
|
|
||||||
|
type LastOf<T> = UnionToIntersection<T extends any ? (x: T) => void : never> extends (x: infer Last) => void
|
||||||
|
? Last
|
||||||
|
: never
|
||||||
|
|
||||||
|
export type UnionToTuple<T, Last = LastOf<T>> = [T] extends [never]
|
||||||
|
? []
|
||||||
|
: [...UnionToTuple<Exclude<T, Last>>, Last]
|
||||||
6
src/Types/index.ts
Normal file
6
src/Types/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from "./CommonKeys"
|
||||||
|
export * from "./Extend"
|
||||||
|
export * from "./Merge"
|
||||||
|
export * as PrismaJson from "./PrismaJson"
|
||||||
|
export * from "./StaticType"
|
||||||
|
export * from "./UnionToTuple"
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
import * as TF from "type-fest"
|
|
||||||
|
|
||||||
|
|
||||||
export function Jsonifiable<
|
|
||||||
A, I, R,
|
|
||||||
JsonifiableA extends I, JsonifiableI extends TF.Jsonifiable, JsonifiableR,
|
|
||||||
>(
|
|
||||||
schema: S.Schema<A, I, R>,
|
|
||||||
jsonifiable: S.Schema<JsonifiableA, JsonifiableI, JsonifiableR>,
|
|
||||||
) {
|
|
||||||
return jsonifiable.pipe(S.compose(schema))
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
import type { HasFields } from "./util"
|
|
||||||
|
|
||||||
|
|
||||||
export const Class = S.Class as <Self>(identifier: string) =>
|
|
||||||
<Fields extends S.Struct.Fields>(
|
|
||||||
fieldsOr: Fields | HasFields<Fields>,
|
|
||||||
annotations?: S.Annotations.Schema<Self>,
|
|
||||||
) => S.Class<
|
|
||||||
Self,
|
|
||||||
Fields,
|
|
||||||
S.Struct.Encoded<Fields>,
|
|
||||||
S.Struct.Context<Fields>,
|
|
||||||
S.Struct.Constructor<Fields>,
|
|
||||||
{},
|
|
||||||
{}
|
|
||||||
>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
import { makeObservable, observable, type CreateObservableOptions } from "mobx"
|
|
||||||
import { mapValues } from "remeda"
|
|
||||||
|
|
||||||
|
|
||||||
interface MobXObservableInput {
|
|
||||||
new(...args: any[]): S.Struct.Type<S.Struct.Fields>
|
|
||||||
readonly fields: { readonly [K in keyof S.Struct.Fields]: S.Struct.Fields[K] }
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MobXObservable<
|
|
||||||
This extends MobXObservableInput
|
|
||||||
>(
|
|
||||||
class_: This,
|
|
||||||
options?: Omit<CreateObservableOptions, "proxy">,
|
|
||||||
) {
|
|
||||||
return class MobXObservable extends class_ {
|
|
||||||
declare ["constructor"]: typeof MobXObservable
|
|
||||||
|
|
||||||
constructor(...args: any[]) {
|
|
||||||
super(...args)
|
|
||||||
|
|
||||||
makeObservable(this,
|
|
||||||
mapValues(this.constructor.fields, () => observable),
|
|
||||||
options,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} as This
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
import type { Mutable } from "effect/Types"
|
|
||||||
import type { StaticType } from "../../../StaticType"
|
|
||||||
import type { HasFields } from "./util"
|
|
||||||
|
|
||||||
|
|
||||||
export interface IMutableClass<
|
|
||||||
Self,
|
|
||||||
Fields extends S.Struct.Fields,
|
|
||||||
I, R, C,
|
|
||||||
Inherited,
|
|
||||||
Proto,
|
|
||||||
>
|
|
||||||
extends Omit<
|
|
||||||
StaticType<S.Class<Self, Fields, I, R, C, Inherited, Proto>>,
|
|
||||||
"extend"
|
|
||||||
>
|
|
||||||
{
|
|
||||||
new(
|
|
||||||
...args: ConstructorParameters<S.Class<Self, Fields, I, R, C, Inherited, Proto>>
|
|
||||||
): Omit<
|
|
||||||
InstanceType<S.Class<Self, Fields, I, R, C, Inherited, Proto>>,
|
|
||||||
keyof Fields
|
|
||||||
> &
|
|
||||||
Mutable<S.Struct.Type<Fields>>
|
|
||||||
|
|
||||||
extend<Extended>(identifier: string): <newFields extends S.Struct.Fields>(
|
|
||||||
fields: newFields | HasFields<newFields>,
|
|
||||||
annotations?: S.Annotations.Schema<Extended>,
|
|
||||||
) => IMutableClass<
|
|
||||||
Extended,
|
|
||||||
Fields & newFields,
|
|
||||||
I & S.Struct.Encoded<newFields>,
|
|
||||||
R | S.Struct.Context<newFields>,
|
|
||||||
C & S.Struct.Constructor<newFields>,
|
|
||||||
Self,
|
|
||||||
Proto
|
|
||||||
>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const MutableClass = S.Class as <Self>(identifier: string) =>
|
|
||||||
<Fields extends S.Struct.Fields>(
|
|
||||||
fieldsOr: Fields | HasFields<Fields>,
|
|
||||||
annotations?: S.Annotations.Schema<Self>,
|
|
||||||
) => IMutableClass<
|
|
||||||
Self,
|
|
||||||
Fields,
|
|
||||||
S.Struct.Encoded<Fields>,
|
|
||||||
S.Struct.Context<Fields>,
|
|
||||||
S.Struct.Constructor<Fields>,
|
|
||||||
{},
|
|
||||||
{}
|
|
||||||
>
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
import type { IMutableClass } from "./MutableClass"
|
|
||||||
import type { HasFields } from "./util"
|
|
||||||
|
|
||||||
|
|
||||||
export interface IMutableTaggedClass<
|
|
||||||
Self,
|
|
||||||
Tag,
|
|
||||||
Fields extends S.Struct.Fields,
|
|
||||||
>
|
|
||||||
extends IMutableClass<
|
|
||||||
Self,
|
|
||||||
Fields,
|
|
||||||
S.Struct.Encoded<Fields>,
|
|
||||||
S.Struct.Context<Fields>,
|
|
||||||
S.Struct.Constructor<Omit<Fields, "_tag">>,
|
|
||||||
{},
|
|
||||||
{}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
readonly _tag: Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const MutableTaggedClass = S.TaggedClass as <Self>(identifier?: string) =>
|
|
||||||
<
|
|
||||||
Tag extends string,
|
|
||||||
Fields extends S.Struct.Fields,
|
|
||||||
>(
|
|
||||||
tag: Tag,
|
|
||||||
fieldsOr: Fields | HasFields<Fields>,
|
|
||||||
annotations?: S.Annotations.Schema<Self>,
|
|
||||||
) => IMutableTaggedClass<
|
|
||||||
Self,
|
|
||||||
Tag,
|
|
||||||
{ readonly _tag: S.PropertySignature<":", Tag, never, ":", Tag, true, never> } & Fields
|
|
||||||
>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
import type { HasFields } from "./util"
|
|
||||||
|
|
||||||
|
|
||||||
export const TaggedClass = S.TaggedClass as <Self>(identifier?: string) =>
|
|
||||||
<
|
|
||||||
Tag extends string,
|
|
||||||
Fields extends S.Struct.Fields,
|
|
||||||
>(
|
|
||||||
tag: Tag,
|
|
||||||
fieldsOr: Fields | HasFields<Fields>,
|
|
||||||
annotations?: S.Annotations.Schema<Self>,
|
|
||||||
) => S.TaggedClass<
|
|
||||||
Self,
|
|
||||||
Tag,
|
|
||||||
{ readonly _tag: S.PropertySignature<":", Tag, never, ":", Tag, true, never> } & Fields
|
|
||||||
>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export { Class } from "./Class"
|
|
||||||
export { MobXObservable } from "./MobXObservable"
|
|
||||||
export { MutableClass } from "./MutableClass"
|
|
||||||
export type { IMutableClass } from "./MutableClass"
|
|
||||||
export { MutableTaggedClass } from "./MutableTaggedClass"
|
|
||||||
export type { IMutableTaggedClass } from "./MutableTaggedClass"
|
|
||||||
export { TaggedClass } from "./TaggedClass"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
import { reaction, runInAction } from "mobx"
|
|
||||||
import { pipe } from "remeda"
|
|
||||||
import { MobXObservable } from "./MobXObservable"
|
|
||||||
import { MutableTaggedClass } from "./MutableTaggedClass"
|
|
||||||
|
|
||||||
|
|
||||||
class User extends pipe(
|
|
||||||
MutableTaggedClass<User>()("User", {
|
|
||||||
id: S.BigIntFromSelf,
|
|
||||||
role: S.Union(S.Literal("BasicUser"), S.Literal("Admin")),
|
|
||||||
}),
|
|
||||||
MobXObservable,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
const user1 = new User({ id: -1n, role: "BasicUser" })
|
|
||||||
reaction(() => user1.id, id => console.log(`user1 id changed: ${ id }`))
|
|
||||||
|
|
||||||
|
|
||||||
class Admin extends User.extend<Admin>("Admin")({
|
|
||||||
// role: S.Literal("Admin")
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
const user2 = new Admin({ id: -1n, role: "Admin" })
|
|
||||||
reaction(() => user2.id, id => console.log(`user2 id changed: ${ id }`))
|
|
||||||
|
|
||||||
|
|
||||||
runInAction(() => { user1.id = 1n })
|
|
||||||
runInAction(() => { user2.id = 2n })
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { Schema as S } from "@effect/schema"
|
|
||||||
|
|
||||||
|
|
||||||
export type HasFields<Fields extends S.Struct.Fields> = (
|
|
||||||
| { readonly fields: Fields }
|
|
||||||
| { readonly from: HasFields<Fields> }
|
|
||||||
)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { Jsonifiable } from "./Jsonifiable"
|
|
||||||
@@ -1 +1,7 @@
|
|||||||
export type { StaticType } from "./StaticType"
|
export * as Express from "./Express"
|
||||||
|
export * from "./ImportError"
|
||||||
|
export * as JSONWebToken from "./JSONWebToken"
|
||||||
|
export * as OpenAIClient from "./OpenAIClient"
|
||||||
|
export * as Schema from "./Schema"
|
||||||
|
export * as TRPC from "./TRPC"
|
||||||
|
export * as Types from "./Types"
|
||||||
|
|||||||
61
src/tests.ts
Normal file
61
src/tests.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Schema as S } from "@effect/schema"
|
||||||
|
import { computed, makeObservable, reaction, runInAction } from "mobx"
|
||||||
|
import type { Simplify } from "type-fest"
|
||||||
|
import { MutableTaggedClass, toJsonifiable } from "./Schema"
|
||||||
|
import { ObservableClass } from "./Schema/MobX"
|
||||||
|
import type { ExtendAll } from "./Types"
|
||||||
|
|
||||||
|
|
||||||
|
type TestA = {
|
||||||
|
heugneu: string
|
||||||
|
type: "Type1" | "Type2"
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestB = {
|
||||||
|
type: "Type1"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Merged = Simplify<ExtendAll<[TestA, TestB]>>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const UserSchema = MutableTaggedClass<User>()("User", {
|
||||||
|
id: S.BigIntFromSelf,
|
||||||
|
role: S.Union(S.Literal("BasicUser"), S.Literal("Admin")),
|
||||||
|
}).pipe(
|
||||||
|
ObservableClass()
|
||||||
|
)
|
||||||
|
|
||||||
|
class User extends UserSchema {
|
||||||
|
constructor(...args: ConstructorParameters<typeof UserSchema>) {
|
||||||
|
super(...args)
|
||||||
|
makeObservable(this, { idAsString: computed })
|
||||||
|
}
|
||||||
|
|
||||||
|
get idAsString() {
|
||||||
|
return this.id.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonifiableUser = User.pipe(
|
||||||
|
toJsonifiable(S.Struct({
|
||||||
|
...User.fields,
|
||||||
|
id: S.BigInt,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const user1 = new User({ id: -1n, role: "BasicUser" })
|
||||||
|
reaction(() => user1.id, id => console.log(`user1 id changed: ${ id }`))
|
||||||
|
|
||||||
|
|
||||||
|
class Admin extends User.extend<Admin>("Admin")({
|
||||||
|
// role: S.Literal("Admin")
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
const user2 = new Admin({ id: -1n, role: "Admin" })
|
||||||
|
reaction(() => user2.id, id => console.log(`user2 id changed: ${ id }`))
|
||||||
|
|
||||||
|
|
||||||
|
runInAction(() => { user1.id = 1n })
|
||||||
|
runInAction(() => { user2.id = 2n })
|
||||||
@@ -6,13 +6,13 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowJs": true,
|
// "allowJs": true,
|
||||||
|
|
||||||
// Bundler mode
|
// Bundler mode
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
// "allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
// "noEmit": true,
|
||||||
|
|
||||||
// Best practices
|
// Best practices
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -22,6 +22,12 @@
|
|||||||
// Some stricter flags (disabled by default)
|
// Some stricter flags (disabled by default)
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
}
|
|
||||||
|
// Build
|
||||||
|
"outDir": "./dist",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"include": ["./src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,16 @@ import { defineConfig } from "tsup"
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry: [
|
entry: [
|
||||||
"./src/index.ts",
|
"./src/index.ts",
|
||||||
"./src/effect/schema/index.ts",
|
"./src/Express/index.ts",
|
||||||
"./src/effect/schema/class/index.ts",
|
"./src/TRPC/index.ts",
|
||||||
|
"./src/Types/index.ts",
|
||||||
|
"./src/Schema/index.ts",
|
||||||
|
"./src/Schema/MobX/index.ts",
|
||||||
|
"./src/Schema/TanStackForm/index.ts",
|
||||||
|
"./src/JSONWebToken.ts",
|
||||||
|
"./src/OpenAIClient.ts",
|
||||||
],
|
],
|
||||||
format: ["esm", "cjs"],
|
format: ["esm"],
|
||||||
skipNodeModulesBundle: true,
|
skipNodeModulesBundle: true,
|
||||||
dts: true,
|
dts: true,
|
||||||
splitting: true,
|
splitting: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user