Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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