Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"changelog": ["@changesets/changelog-github", { "repo": "softnetics/kivotos" }],
"commit": false,
"fixed": [
["@kivotos/core", "@kivotos/next"]
["@kivotos/core", "@kivotos/next", "@kivotos/rest"]
],
"linked": [],
"access": "restricted",
Expand Down
8 changes: 8 additions & 0 deletions .changeset/late-adults-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@example/erp": patch
"@kivotos/core": minor
"@kivotos/next": minor
"@kivotos/rest": minor
---

[[DRIZZ-40] Rest RPC](https://app.plane.so/softnetics/browse/DRIZZ-40/)
1 change: 1 addition & 0 deletions examples/erp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"dependencies": {
"@kivotos/core": "workspace:^",
"@kivotos/next": "workspace:^",
"@kivotos/rest": "workspace:^",
"@tailwindcss/postcss": "^4.0.14",
"drizzle-orm": "^0.41.0",
"next": "15.2.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/auth/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type AuthContext<TConfig extends AuthConfig = AuthConfig> = {
authConfig: TConfig
internalHandlers: InternalHandlers

requiredAuthenticated: (headers: Record<string, string>) => Promise<InferTableType<AnyUserTable>>
requiredAuthenticated: (headers?: Record<string, string>) => Promise<InferTableType<AnyUserTable>>
}

export function createAuthContext<TAuthConfig extends AuthConfig, TContext extends MinimalContext>(
Expand All @@ -39,7 +39,7 @@ export function createAuthContext<TAuthConfig extends AuthConfig, TContext exten
authConfig: authConfig,
internalHandlers: internalHandlers,

requiredAuthenticated: async (headers: Record<string, string>) => {
requiredAuthenticated: async (headers?: Record<string, string>) => {
const sessionId = getSessionCookie(headers)
if (!sessionId) throw new Error('Unauthorized')
const session = await internalHandlers.session.findUserBySessionId(sessionId)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/handlers/forgot-password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function forgotPasswordEmail<const TOptions extends InternalRouteOptions>
status: z.string(),
}),
},
} satisfies ApiRouteSchema
} as const satisfies ApiRouteSchema

const handler: ApiRouteHandler<AuthContext, typeof schema> = async (args) => {
if (!args.context.authConfig.resetPassword?.enabled) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/handlers/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function me<const TOptions extends InternalRouteOptions>(options: TOption
status: z.string(),
}),
},
} satisfies ApiRouteSchema
} as const satisfies ApiRouteSchema

const handler: ApiRouteHandler<AuthContext, typeof schema> = async (args) => {
const user = await args.context.requiredAuthenticated(args.headers)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/handlers/reset-password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function resetPasswordEmail<const TOptions extends InternalRouteOptions>(
status: z.string(),
}),
},
} satisfies ApiRouteSchema
} as const satisfies ApiRouteSchema

const handler: ApiRouteHandler<AuthContext, typeof schema> = async (args) => {
if (!args.context.authConfig.resetPassword?.enabled) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/handlers/sign-in-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function signInEmail<const TOptions extends InternalRouteOptions>(options
}),
}),
},
} satisfies ApiRouteSchema
} as const satisfies ApiRouteSchema

const handler: ApiRouteHandler<AuthContext, typeof schema> = async (args) => {
const account = await args.context.internalHandlers.account.findByUserEmailAndProvider(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/handlers/sign-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function signOut<const TOptions extends InternalRouteOptions>(options: TO
status: z.string(),
}),
},
} satisfies ApiRouteSchema
} as const satisfies ApiRouteSchema

const handler: ApiRouteHandler<AuthContext, typeof schema> = async (args) => {
const cookie = getSessionCookie(args.headers)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/auth/handlers/sign-up.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function signUp<const TOptions extends InternalRouteOptions>(options: TOp
}),
}),
},
} satisfies ApiRouteSchema
} as const satisfies ApiRouteSchema

const handler: ApiRouteHandler<AuthContext, typeof schema> = async (args) => {
const hasedPassword = args.body.password // TODO: hash password
Expand Down
15 changes: 9 additions & 6 deletions packages/core/src/auth/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
function setCookie(headers: Record<string, string>, name: string, value: string) {
function setCookie(headers: Record<string, string> | undefined, name: string, value: string) {
if (!headers) return
headers['Set-Cookie'] = `${name}=${value}; Path=/; HttpOnly; SameSite=Strict`
}

function getCookie(headers: Record<string, string>, name: string) {
function getCookie(headers: Record<string, string> | undefined, name: string) {
if (!headers) return null
const cookie = headers[name]
if (!cookie) return null
const cookies = cookie.split('; ')
Expand All @@ -13,20 +15,21 @@ function getCookie(headers: Record<string, string>, name: string) {
return null
}

function deleteCookie(headers: Record<string, string>, name: string) {
function deleteCookie(headers: Record<string, string> | undefined, name: string) {
if (!headers) return
headers['Set-Cookie'] = `${name}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0`
}

const SESSION_COOKIE_NAME = 'SESSION_ID'

export function getSessionCookie(headers: Record<string, string>) {
export function getSessionCookie(headers: Record<string, string> | undefined) {
return getCookie(headers, SESSION_COOKIE_NAME)
}

export function setSessionCookie(headers: Record<string, string>, value: string) {
export function setSessionCookie(headers: Record<string, string> | undefined, value: string) {
setCookie(headers, SESSION_COOKIE_NAME, value)
}

export function deleteSessionCookie(headers: Record<string, string>) {
export function deleteSessionCookie(headers?: Record<string, string>) {
deleteCookie(headers, SESSION_COOKIE_NAME)
}
29 changes: 14 additions & 15 deletions packages/core/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,21 +440,19 @@ export type Collection<
admin: CollectionAdmin<TContext, TFields, TApiRouter>
}

export type ToClientCollection<TCollection extends Collection> = ClientCollection<
InferSlugFromCollection<TCollection>,
InferTableNameFromCollection<TCollection>,
InferFullSchemaFromCollection<TCollection>,
InferContextFromCollection<TCollection>,
InferFieldsFromCollection<TCollection>,
// TODO: fix this
// InferApiRouterFromCollection<TCollection>
any
>
export type ToClientCollection<TCollection extends Collection<any, any, any, any, any, any>> =
ClientCollection<
InferSlugFromCollection<TCollection>,
InferTableNameFromCollection<TCollection>,
InferFullSchemaFromCollection<TCollection>,
InferContextFromCollection<TCollection>,
InferFieldsFromCollection<TCollection>,
InferApiRouterFromCollection<TCollection>
>

export type ToClientCollectionList<TCollections extends Collection[]> = TCollections extends [
infer TCollection,
...infer TCollectionsRest,
]
export type ToClientCollectionList<
TCollections extends Collection<any, any, any, any, any, any>[],
> = TCollections extends [infer TCollection, ...infer TCollectionsRest]
? [
ToClientCollection<TCollection extends Collection ? TCollection : never>,
...ToClientCollectionList<TCollectionsRest extends Collection[] ? TCollectionsRest : never>,
Expand All @@ -478,7 +476,8 @@ export type ClientCollection<
}
>

export type InferSlugFromCollection<TCollection extends Collection> = TCollection['slug']
export type InferSlugFromCollection<TCollection extends Collection<any, any, any, any, any, any>> =
TCollection['slug']

export type InferTableNameFromCollection<
TCollection extends Collection<any, any, any, any, any, any>,
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export interface ServerConfig<

export type InferApiRouterFromServerConfig<TServerConfig extends ServerConfig<any, any, any, any>> =
TServerConfig extends ServerConfig<any, any, any, infer TApiRouter>
? TApiRouter extends ApiRouter
? TApiRouter extends ApiRouter<any>
? TApiRouter
: never
: never
Expand Down Expand Up @@ -175,7 +175,7 @@ export function getClientCollection<const TCollection extends Collection>(

export type ToClientConfig<TServerConfig extends ServerConfig<any, any, any, any>> = ClientConfig<
ToClientCollectionList<TServerConfig['collections']>,
ToClientApiRouter<InferApiRouterFromServerConfig<TServerConfig>>
Simplify<ToClientApiRouter<InferApiRouterFromServerConfig<TServerConfig>>>
>

export function getClientConfig<const TServerConfig extends ServerConfig<any, any, any, any>>(
Expand Down
21 changes: 9 additions & 12 deletions packages/core/src/endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ExtractObjectValues, Simplify } from 'drizzle-orm'
import { IsNever, SimplifyDeep } from 'type-fest'
import { IsNever, Simplify, SimplifyDeep, ValueOf } from 'type-fest'
import { z, ZodType } from 'zod'

import { MaybePromise } from './collection'
Expand All @@ -26,11 +25,10 @@ type GetBody<TApiRouteSchema extends ApiRouteSchema> =
: {}
: {}

type GetHeaders<TApiRouteSchema extends ApiRouteSchema> = {
headers: IsNever<Output<TApiRouteSchema['headers']>> extends false
? Output<TApiRouteSchema['headers']> & Record<string, string>
: Record<string, string>
}
type GetHeaders<TApiRouteSchema extends ApiRouteSchema> =
IsNever<Output<TApiRouteSchema['headers']>> extends false
? { headers: Output<TApiRouteSchema['headers']> & Record<string, string> }
: { headers?: Record<string, string> }

type GetQuery<TApiRouteSchema extends ApiRouteSchema> =
IsNever<Output<TApiRouteSchema['query']>> extends false
Expand Down Expand Up @@ -59,7 +57,7 @@ export type ApiRouteHandlerPayloadWithContext<
}

export type ApiRouteResponse<TResponses extends Partial<Record<ApiHttpStatus, InputSchema>>> =
ExtractObjectValues<{
ValueOf<{
[TStatus in Extract<keyof TResponses, number>]: TResponses[TStatus] extends InputSchema
? {
status: TStatus
Expand Down Expand Up @@ -92,7 +90,7 @@ export interface ApiRouteCommonSchema {
responses: Partial<Record<ApiHttpStatus, InputSchema>>
}

export type InferApiRouteResponses<TApiRouteSchema extends ApiRouteSchema> = ExtractObjectValues<{
export type InferApiRouteResponses<TApiRouteSchema extends ApiRouteSchema> = ValueOf<{
[TStatus in keyof TApiRouteSchema['responses']]: TApiRouteSchema['responses'][TStatus] extends InputSchema
? { status: TStatus; data: Output<TApiRouteSchema['responses'][TStatus]> }
: never
Expand Down Expand Up @@ -140,9 +138,8 @@ export interface ClientApiRouter {
}

export type ToClientApiRouter<TApiRouter extends ApiRouter> = {
[TKey in keyof TApiRouter]: Omit<TApiRouter[TKey], 'handler'> extends infer TApiRoute extends
ApiRouteSchema
? TApiRoute
[TKey in keyof TApiRouter]: TApiRouter[TKey] extends ApiRoute<any, infer TApiRouteSchema>
? TApiRouteSchema
: never
}

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type {
ApiRouter,
ApiRouteResponse,
ApiRouteSchema,
ClientApiRouter,
InferApiRouteResponses,
} from './endpoint'
export { type Field, FieldBuilder } from './field'
28 changes: 13 additions & 15 deletions packages/core/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Column, ExtractObjectValues, is, Table, TableRelationalConfig } from 'drizzle-orm'
import { IsNever, Simplify } from 'type-fest'
import { Column, is, Table, TableRelationalConfig } from 'drizzle-orm'
import { IsNever, Simplify, ValueOf } from 'type-fest'
import z, { ZodObject } from 'zod'

import { Field, FieldRelation, Fields, FieldsInitial, FieldsWithFieldName } from './field'
Expand All @@ -8,19 +8,17 @@ export function isRelationField(field: Field): field is FieldRelation {
return field._.source === 'relations'
}

export type GetPrimaryColumn<TTableRelationalConfig extends TableRelationalConfig> =
ExtractObjectValues<{
[K in keyof TTableRelationalConfig['columns']]: TTableRelationalConfig['columns'][K]['_']['isPrimaryKey'] extends true
? TTableRelationalConfig['columns'][K]
: never
}>

export type GetPrimaryColumnTsName<TTableRelationalConfig extends TableRelationalConfig> =
ExtractObjectValues<{
[K in keyof TTableRelationalConfig['columns']]: TTableRelationalConfig['columns'][K]['_']['isPrimaryKey'] extends true
? K
: never
}>
export type GetPrimaryColumn<TTableRelationalConfig extends TableRelationalConfig> = ValueOf<{
[K in keyof TTableRelationalConfig['columns']]: TTableRelationalConfig['columns'][K]['_']['isPrimaryKey'] extends true
? TTableRelationalConfig['columns'][K]
: never
}>

export type GetPrimaryColumnTsName<TTableRelationalConfig extends TableRelationalConfig> = ValueOf<{
[K in keyof TTableRelationalConfig['columns']]: TTableRelationalConfig['columns'][K]['_']['isPrimaryKey'] extends true
? K
: never
}>

export function getPrimaryColumn<TTableConfig extends TableRelationalConfig>(
tableConfig: TTableConfig
Expand Down
28 changes: 14 additions & 14 deletions packages/next/src/server-function.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Simplify } from 'type-fest'
import type { Simplify, ValueOf } from 'type-fest'

import {
ApiRoute,
Expand All @@ -9,8 +9,6 @@ import {
ServerConfig,
} from '@kivotos/core'

type ExtractObjectValues<T> = T[keyof T]

export type ServerFunction<TServerConfig extends ServerConfig<any, any, any, any>> = <
TApiArgs extends GetServerFunctionApiArgs<TServerConfig['endpoints']>,
>(
Expand All @@ -24,18 +22,17 @@ type GetServerFunctionResponse<
? ApiRouteResponse<TApiRoute['schema']['responses']>
: never

export type GetServerFunctionApiArgs<TApiRouter extends ApiRouter<any> | undefined> =
ExtractObjectValues<{
[TMethod in Extract<keyof TApiRouter, string>]: TApiRouter[TMethod] extends ApiRoute<
any,
infer TApiRouteSchema extends ApiRouteSchema
>
? Simplify<{ method: TMethod } & ApiRouteHandlerPayload<TApiRouteSchema>>
: never
}>
export type GetServerFunctionApiArgs<TApiRouter extends ApiRouter<any> | undefined> = ValueOf<{
[TMethod in Extract<keyof TApiRouter, string>]: TApiRouter[TMethod] extends ApiRoute<
any,
infer TApiRouteSchema extends ApiRouteSchema
>
? Simplify<{ method: TMethod } & ApiRouteHandlerPayload<TApiRouteSchema>>
: never
}>

export async function handleServerFunction<
TServerConfig extends ServerConfig<any, any, any, any>,
TServerConfig extends ServerConfig<any, any, any, ApiRouter<any>>,
TApiArgs extends GetServerFunctionApiArgs<TServerConfig['endpoints']>,
>(
serverConfig: TServerConfig,
Expand All @@ -45,5 +42,8 @@ export async function handleServerFunction<
if (!apiRoute) {
throw new Error(`No API route found for method: ${args.method}`)
}
return apiRoute.handler({ ...args, context: serverConfig.context })
return apiRoute.handler({ ...args, context: serverConfig.context }) as GetServerFunctionResponse<
TServerConfig,
TApiArgs['method']
>
}
25 changes: 25 additions & 0 deletions packages/rest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@kivotos/rest",
"private": true,
"sideEffects": false,
"type": "module",
"main": "./src/index.ts",
"module": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit",
"test": "vitest run"
},
"dependencies": {
"@kivotos/core": "workspace:^"
},
"devDependencies": {
"@internals/project-config": "workspace:^",
"type-fest": "^4.41.0"
}
}
Loading
Loading