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
7 changes: 7 additions & 0 deletions .changeset/empty-worms-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@example/erp": patch
"@kivotos/core": minor
"@kivotos/next": minor
---

[[DRIZZ-57] NextJS Integration Resource Route](https://app.plane.so/softnetics/browse/DRIZZ-57/)
6 changes: 6 additions & 0 deletions examples/erp/src/app/(admin)/api/[...segments]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createApiResourceRouter } from '@kivotos/next'

import { serverConfig } from '~/drizzlify/config'

const { GET, POST, PUT, PATCH, DELETE } = createApiResourceRouter(serverConfig)
export { DELETE, GET, PATCH, POST, PUT }
2 changes: 0 additions & 2 deletions examples/erp/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import './global.css'

import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'

Expand Down
5 changes: 1 addition & 4 deletions packages/core/src/auth/handlers/forgot-password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import z from 'zod'

import { ApiRouteHandler, ApiRouteSchema, createEndpoint } from '../../endpoint'
import { AuthContext } from '../context'
import { WithPrefix } from '../types'

interface InternalRouteOptions {
prefix?: string
Expand All @@ -13,9 +12,7 @@ export function forgotPasswordEmail<const TOptions extends InternalRouteOptions>
) {
const schema = {
method: 'POST',
path: (options.prefix
? `${options.prefix}/auth/forgot-password`
: '/auth/forgot-password') as WithPrefix<TOptions['prefix'], '/auth/forgot-password'>,
path: '/api/auth/forgot-password',
body: z.object({
email: z.string(),
}),
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/auth/handlers/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import z from 'zod'

import { ApiRouteHandler, ApiRouteSchema, createEndpoint } from '../../endpoint'
import { AuthContext } from '../context'
import { WithPrefix } from '../types'

interface InternalRouteOptions {
prefix?: string
Expand All @@ -11,10 +10,7 @@ interface InternalRouteOptions {
export function me<const TOptions extends InternalRouteOptions>(options: TOptions) {
const schema = {
method: 'GET',
path: (options.prefix ? `${options.prefix}/auth/me` : '/auth/me') as WithPrefix<
TOptions['prefix'],
'/auth/me'
>,
path: '/api/auth/me',
responses: {
200: z.object({
id: z.string(),
Expand Down
5 changes: 1 addition & 4 deletions packages/core/src/auth/handlers/reset-password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import z from 'zod'

import { ApiRouteHandler, ApiRouteSchema, createEndpoint } from '../../endpoint'
import { AuthContext } from '../context'
import { WithPrefix } from '../types'

interface InternalRouteOptions {
prefix?: string
Expand All @@ -11,9 +10,7 @@ interface InternalRouteOptions {
export function resetPasswordEmail<const TOptions extends InternalRouteOptions>(options: TOptions) {
const schema = {
method: 'POST',
path: (options.prefix
? `${options.prefix}/auth/reset-password`
: '/auth/reset-password') as WithPrefix<TOptions['prefix'], '/auth/reset-password'>,
path: '/api/auth/reset-password',
query: z.object({
token: z.string(),
}),
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/auth/handlers/sign-in-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import z from 'zod'
import { ApiRouteHandler, ApiRouteSchema, createEndpoint } from '../../endpoint'
import { AccountProvider } from '../constant'
import { AuthContext } from '../context'
import { WithPrefix } from '../types'
import { setSessionCookie } from '../utils'

interface InternalRouteOptions {
Expand All @@ -13,10 +12,7 @@ interface InternalRouteOptions {
export function signInEmail<const TOptions extends InternalRouteOptions>(options: TOptions) {
const schema = {
method: 'POST',
path: (options.prefix
? `${options.prefix}/auth/sign-in/email`
: '/auth/sign-in/email') as WithPrefix<TOptions['prefix'], '/auth/sign-in/email'>,

path: '/api/auth/sign-in-email',
body: z.object({
email: z.string(),
password: z.string(),
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/auth/handlers/sign-out.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import z from 'zod'

import { ApiRouteHandler, ApiRouteSchema, createEndpoint } from '../../endpoint'
import { AuthContext } from '../context'
import { WithPrefix } from '../types'
import { deleteSessionCookie, getSessionCookie } from '../utils'

interface InternalRouteOptions {
Expand All @@ -12,10 +11,7 @@ interface InternalRouteOptions {
export function signOut<const TOptions extends InternalRouteOptions>(options: TOptions) {
const schema = {
method: 'POST',
path: (options.prefix ? `${options.prefix}/auth/sign-out` : '/auth/sign-out') as WithPrefix<
TOptions['prefix'],
'/sign-out'
>,
path: '/api/auth/sign-out',
body: undefined,
responses: {
200: z.object({
Expand Down
10 changes: 2 additions & 8 deletions packages/core/src/auth/handlers/sign-up.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,13 @@ import z from 'zod'
import { ApiRouteHandler, ApiRouteSchema, createEndpoint } from '../../endpoint'
import { AccountProvider } from '../constant'
import { AuthContext } from '../context'
import { WithPrefix } from '../types'

interface InternalRouteOptions {
prefix?: string
}
interface InternalRouteOptions {}

export function signUp<const TOptions extends InternalRouteOptions>(options: TOptions) {
const schema = {
method: 'POST',
path: (options.prefix ? `${options.prefix}/auth/sign-up` : '/auth/sign-up') as WithPrefix<
TOptions['prefix'],
'/auth/sign-up'
>,
path: '/api/auth/sign-up',
body: z
.object({
name: z.string(),
Expand Down
18 changes: 15 additions & 3 deletions packages/core/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ import {
GetAllTableTsNames,
} from './collection'
import { MinimalContext } from './config'
import { ApiRoute, ApiRouteHandler, ApiRouter, ApiRouteSchema, createEndpoint } from './endpoint'
import {
ApiRoute,
ApiRouteHandler,
ApiRouter,
ApiRouteSchema,
AppendPrefixPathToApiRoute,
createEndpoint,
} from './endpoint'
import { FieldBuilder, Fields, FieldsInitial, FieldsWithFieldName, OptionCallback } from './field'
import { appendFieldNameToFields } from './utils'

Expand Down Expand Up @@ -142,7 +149,12 @@ export class Builder<
endpoint<const TApiEndpointSchema extends ApiRouteSchema>(
args: TApiEndpointSchema,
handler: ApiRouteHandler<TContext, TApiEndpointSchema>
): ApiRoute<TContext, TApiEndpointSchema> {
return createEndpoint(args, handler)
): AppendPrefixPathToApiRoute<ApiRoute<TContext, TApiEndpointSchema>, '/api'> {
const prefixPath = '/api'
args.path = `${prefixPath}${args.path}`
return createEndpoint(args, handler) as AppendPrefixPathToApiRoute<
ApiRoute<TContext, TApiEndpointSchema>,
'/api'
>
}
}
32 changes: 16 additions & 16 deletions packages/core/src/collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ export type ConvertCollectionDefaultApiToApiRouteSchema<
TMethod extends ApiDefaultMethod,
> = TMethod extends typeof ApiDefaultMethod.CREATE
? {
path: `/${TCollection['slug']}/${TMethod}`
path: `/api/${TCollection['slug']}/${TMethod}`
method: 'POST'
body: ToZodObject<InferCreateFields<TCollection['fields']>>
responses: {
Expand All @@ -556,7 +556,7 @@ export type ConvertCollectionDefaultApiToApiRouteSchema<
}
: TMethod extends typeof ApiDefaultMethod.FIND_ONE
? {
path: `/${TCollection['slug']}/${TMethod}/:id`
path: `/api/${TCollection['slug']}/${TMethod}/:id`
method: 'GET'
pathParams: ToZodObject<{ id: string }>
responses: {
Expand All @@ -565,7 +565,7 @@ export type ConvertCollectionDefaultApiToApiRouteSchema<
}
: TMethod extends typeof ApiDefaultMethod.FIND_MANY
? {
path: `/${TCollection['slug']}/${TMethod}`
path: `/api/${TCollection['slug']}/${TMethod}`
method: 'GET'
query: ToZodObject<{
limit?: number
Expand All @@ -579,7 +579,7 @@ export type ConvertCollectionDefaultApiToApiRouteSchema<
}
: TMethod extends typeof ApiDefaultMethod.UPDATE
? {
path: `/${TCollection['slug']}/${TMethod}/:id`
path: `/api/${TCollection['slug']}/${TMethod}/:id`
method: 'PATCH'
pathParams: ToZodObject<{ id: string }>
body: ToZodObject<InferUpdateFields<TCollection['fields']>>
Expand All @@ -589,7 +589,7 @@ export type ConvertCollectionDefaultApiToApiRouteSchema<
}
: TMethod extends typeof ApiDefaultMethod.DELETE
? {
path: `/${TCollection['slug']}/${TMethod}`
path: `/api/${TCollection['slug']}/${TMethod}`
method: 'DELETE'
body: ToZodObject<{ ids: string[] | number[] }>
responses: {
Expand Down Expand Up @@ -653,7 +653,7 @@ export function getAllCollectionEndpoints<
const body = fieldsToZodObject(fields)

const schema = {
path: `/${collection.slug}/${method}`,
path: `/api/${collection.slug}/${method}`,
method: 'POST',
// TODO: fieldToZodObject but create fields
body: body,
Expand All @@ -677,13 +677,13 @@ export function getAllCollectionEndpoints<
return { status: 200, body: response }
}

return [[endpointName, { ...schema, handler }]]
return [endpointName, { schema, handler } satisfies ApiRoute<any, typeof schema>]
}
case ApiDefaultMethod.FIND_ONE: {
const response = fieldsToZodObject(fields)

const schema = {
path: `/${collection.slug}/${method}/:id`,
path: `/api/${collection.slug}/${method}/:id`,
method: 'GET',
pathParams: z.object({
id: z.union([z.string(), z.number()]),
Expand All @@ -705,13 +705,13 @@ export function getAllCollectionEndpoints<
return { status: 200, body: response }
}

return [[endpointName, { ...schema, handler }]]
return [endpointName, { schema, handler } satisfies ApiRoute<any, typeof schema>]
}
case ApiDefaultMethod.FIND_MANY: {
const response = fieldsToZodObject(fields)

const schema = {
path: `/${collection.slug}/${method}`,
path: `/api/${collection.slug}/${method}`,
method: 'GET',
query: z.object({
limit: z.number().optional(),
Expand Down Expand Up @@ -739,13 +739,13 @@ export function getAllCollectionEndpoints<
return { status: 200, body: response }
}

return [[endpointName, { ...schema, handler }]]
return [endpointName, { schema, handler } satisfies ApiRoute<any, typeof schema>]
}
case ApiDefaultMethod.UPDATE: {
const body = fieldsToZodObject(fields)

const schema = {
path: `/${collection.slug}/${method}`,
path: `/api/${collection.slug}/${method}`,
method: 'PATCH',
pathParams: z.object({
id: z.union([z.string(), z.number()]),
Expand Down Expand Up @@ -773,11 +773,11 @@ export function getAllCollectionEndpoints<
return { status: 200, body: response }
}

return [[endpointName, { ...schema, handler }]]
return [endpointName, { schema, handler } satisfies ApiRoute<any, typeof schema>]
}
case ApiDefaultMethod.DELETE: {
const schema = {
path: `/${collection.slug}/${method}/:id`,
path: `/api/${collection.slug}/${method}/:id`,
method: 'DELETE',
body: z.object({
ids: z.union([z.string().array(), z.number().array()]),
Expand All @@ -799,7 +799,7 @@ export function getAllCollectionEndpoints<
return { status: 200, body: { message: 'ok' } }
}

return [[endpointName, { ...schema, handler }]]
return [endpointName, { schema, handler } satisfies ApiRoute<any, typeof schema>]
}
default:
throw new Error(`Unknown method: ${method}`)
Expand All @@ -809,8 +809,8 @@ export function getAllCollectionEndpoints<
)

return {
...customEndpoints,
...defaultEndpoints,
...customEndpoints,
} as ExtractAllCollectionCustomEndpoints<TCollections> &
ExtractAllCollectionDefaultEndpoints<TCollections>
}
3 changes: 2 additions & 1 deletion packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ export interface ServerConfig<
any,
any
>[],
TApiRouter extends ApiRouter<TContext> = ReturnType<typeof createAuth<TContext>>['handlers'],
TApiRouter extends ApiRouter<TContext> = ReturnType<typeof createAuth<TContext>>['handlers'] &
ApiRouter<TContext>,
> extends BaseConfig<TFullSchema> {
context: TContext
collections: TCollections
Expand Down
20 changes: 17 additions & 3 deletions packages/core/src/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export type ApiRouteHandlerPayloadWithContext<

export type ApiRouteResponse<TResponses extends Partial<Record<ApiHttpStatus, InputSchema>>> =
ExtractObjectValues<{
[TStatus in keyof TResponses]: TResponses[TStatus] extends InputSchema
[TStatus in Extract<keyof TResponses, number>]: TResponses[TStatus] extends InputSchema
? {
status: TStatus
body: Output<TResponses[TStatus]>
Expand Down Expand Up @@ -111,14 +111,28 @@ export type ApiRouteSchema = ApiRouteQuerySchema | ApiRouteMutationSchema

export type ApiRoute<
TContext extends Record<string, unknown> = Record<string, unknown>,
TApiRouteSchema extends ApiRouteSchema = any,
TApiRouteSchema extends ApiRouteSchema = ApiRouteSchema,
> = {
schema: TApiRouteSchema
handler: ApiRouteHandler<TContext, TApiRouteSchema>
}

export type AppendPrefixPathToApiRoute<
TApiRoute extends ApiRoute<any, any>,
TPrefixPath extends string,
> =
TApiRoute extends ApiRoute<infer TContext, any>
? TApiRoute extends { schema: { path: infer TPath extends string } }
? Simplify<
{ path: `${TPrefixPath}${TPath}` } & Omit<TApiRoute['schema'], 'path'>
> extends infer TNewApiRouteSchema extends ApiRouteSchema
? ApiRoute<TContext, TNewApiRouteSchema>
: never
: never
: never

export interface ApiRouter<TContext extends Record<string, unknown> = Record<string, unknown>> {
[key: string]: ApiRoute<TContext>
[key: string]: ApiRoute<TContext, any>
}

export interface ClientApiRouter {
Expand Down
12 changes: 6 additions & 6 deletions packages/core/src/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,12 +490,12 @@ export function fieldToZodScheama<TField extends Field<any>>(
case 'date':
return z.date() as FieldToZodScheama<TField>
// TODO: relation input
// case 'connect':
// return z.any() as FieldToZodScheama<TField>
// case 'create':
// return z.any() as FieldToZodScheama<TField>
// case 'connectOrCreate':
// return z.any() as FieldToZodScheama<TField>
case 'connect':
return z.any() as unknown as FieldToZodScheama<TField>
case 'create':
return z.any() as unknown as FieldToZodScheama<TField>
case 'connectOrCreate':
return z.any() as unknown as FieldToZodScheama<TField>
default:
throw new Error(`Unknown field type: ${(field as any).type}`)
}
Expand Down
1 change: 1 addition & 0 deletions packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@tanstack/react-virtual": "^3.13.6",
"drizzle-orm": "^0.41.0",
"next": "15.2.2",
"radix3": "^1.1.2",
"react": "^19.1.0",
"react-aria-components": "^1.7.1",
"react-dom": "^19.1.0",
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ export { AutoField } from './components/auto-field'
export { DeleteButton } from './components/delete-button'
export { Nav } from './components/nav'
export { RootCollectionLayout as RootLayout } from './layouts/root-collection'
export { RootCrudPage as RootPage } from './pages/root-collection'
export { RootPage } from './pages/root'
export { createApiResourceRouter } from './resource'
export { handleServerFunction, type ServerFunction } from './server-function'
export { CreateView } from './views/collections/create'
export { ListView } from './views/collections/list'
Expand Down
Loading
Loading