Skip to content

Commit f93c892

Browse files
authored
Merge pull request #14 from softnetics/yu/feat/plugins
[DRIZZ-69] Plugin system
2 parents 01bb181 + 93fbda8 commit f93c892

12 files changed

Lines changed: 156 additions & 52 deletions

File tree

.changeset/six-moose-sneeze.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@kivotos/core": minor
3+
---
4+
5+
[[DRIZZ-69] Plugin system](https://app.plane.so/softnetics/browse/DRIZZ-69/)

packages/core/src/auth/handlers/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,3 @@ export function createAuthHandlers<TAuthConfig extends AuthConfig>(config: TAuth
2323
handlers,
2424
}
2525
}
26-
27-
export type AuthHandlers = ReturnType<typeof createAuthHandlers>['handlers']

packages/core/src/auth/index.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { AnyColumn, AnyTable } from 'drizzle-orm'
21
import * as R from 'remeda'
32
import type { Simplify } from 'type-fest'
43

@@ -8,15 +7,7 @@ import { createAuthHandlers } from './handlers'
87
import { getFieldsClient, type MinimalContext } from '../config'
98
import type { ApiRouteHandler } from '../endpoint'
109
import type { Fields, FieldsClient } from '../field'
11-
12-
export type AnyTypedColumn<T> = AnyColumn & { _: { data: T; dialect: 'pg' } }
13-
export type WithHasDefault<T> = T & { _: { hasDefault: true } }
14-
export type WithNotNull<T> = T & { _: { notNull: true } }
15-
export type WithAnyTable<TColumns extends Record<string, AnyColumn>> = AnyTable<{
16-
dialect: 'pg'
17-
columns: TColumns
18-
}> &
19-
TColumns
10+
import type { AnyTypedColumn, WithAnyTable, WithHasDefault, WithNotNull } from '../table'
2011

2112
export type AnyUserTable = WithAnyTable<{
2213
id: WithHasDefault<WithNotNull<AnyTypedColumn<string>>>
@@ -133,6 +124,8 @@ export type Auth<
133124
>
134125
}
135126

127+
export type AuthHandlers = Auth<any, any>['handlers']
128+
136129
export type AuthClient = {
137130
login: {
138131
emailAndPassword: {

packages/core/src/builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class Builder<
5858
TSlug extends string = string,
5959
TTableKey extends GetAllTableTsNames<TFullSchema> = GetAllTableTsNames<TFullSchema>,
6060
TFields extends Fields<TContext> = Fields<TContext>,
61-
TApiRouter extends ApiRouter<TContext> = ApiRouter<TContext>,
61+
TApiRouter extends ApiRouter<TContext> = {},
6262
>(tableTsName: TTableKey, config: CollectionConfig<TSlug, TContext, TFields, TApiRouter>) {
6363
const table = this.config.schema[tableTsName]
6464
const tableRelationalConfig =

packages/core/src/collection.ts

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Many, Table, TableRelationalConfig } from 'drizzle-orm'
2-
import type { ConditionalExcept, Simplify, ValueOf } from 'type-fest'
2+
import type { ConditionalExcept, Simplify, UnionToIntersection, ValueOf } from 'type-fest'
33
import z from 'zod'
44

55
import type { MinimalContext } from './config'
@@ -421,7 +421,7 @@ export type CollectionConfig<
421421
TSlug extends string = string,
422422
TContext extends MinimalContext = MinimalContext,
423423
TFields extends FieldsInitial<TContext> = FieldsInitial<TContext>,
424-
TAppRouter extends ApiRouter<TContext> = ApiRouter<TContext>,
424+
TAppRouter extends ApiRouter<TContext> = {},
425425
> = {
426426
slug: TSlug
427427
primaryField: Extract<keyof TFields, string>
@@ -435,7 +435,7 @@ export type Collection<
435435
TFullSchema extends Record<string, unknown> = Record<string, unknown>,
436436
TContext extends MinimalContext = MinimalContext,
437437
TFields extends Fields<TContext> = Fields<TContext>,
438-
TApiRouter extends ApiRouter<TContext> = ApiRouter<TContext>,
438+
TApiRouter extends ApiRouter<TContext> = {},
439439
> = {
440440
_: {
441441
table: TFullSchema[TTableName] extends Table<any> ? TFullSchema[TTableName] : never
@@ -520,30 +520,31 @@ export type GetAllTableTsNames<TFullSchema extends Record<string, unknown>> = Ex
520520
export type ExtractCollectionCustomEndpoints<
521521
TCollection extends Collection<any, any, any, any, any, any>,
522522
> = TCollection['admin']['endpoints'] extends infer TEndpoints
523-
? TEndpoints extends Record<string, ApiRouteSchema>
523+
? TEndpoints extends Record<string, ApiRoute<any, any>>
524524
? {
525-
[TEndpoint in Extract<
526-
keyof TEndpoints,
527-
string
528-
> as `${TCollection['slug']}.${TEndpoint}`]: TEndpoints[TEndpoint]
525+
[TEndpoint in keyof TEndpoints as TEndpoints[TEndpoint]['schema'] extends ApiRouteSchema
526+
? `${TCollection['slug']}.${TEndpoint extends string ? TEndpoint : never}`
527+
: never]: TEndpoints[TEndpoint]
529528
}
530529
: never
531530
: never
532531

533532
export type ExtractAllCollectionCustomEndpoints<
534533
TCollections extends Record<string, Collection<any, any, any, any, any, any>>,
535-
> = ValueOf<{
536-
[TCollectionIndex in keyof TCollections]: TCollections[TCollectionIndex] extends Collection<
537-
any,
538-
any,
539-
any,
540-
any,
541-
any,
542-
any
543-
>
544-
? ExtractCollectionCustomEndpoints<TCollections[TCollectionIndex]>
545-
: {}
546-
}>
534+
> = UnionToIntersection<
535+
ValueOf<{
536+
[TCollectionIndex in keyof TCollections]: TCollections[TCollectionIndex] extends Collection<
537+
any,
538+
any,
539+
any,
540+
any,
541+
any,
542+
any
543+
>
544+
? ExtractCollectionCustomEndpoints<TCollections[TCollectionIndex]>
545+
: {}
546+
}>
547+
>
547548

548549
type SuccessResponse<TFunc extends (...args: any) => any> = ToZodObject<Awaited<ReturnType<TFunc>>>
549550

@@ -619,18 +620,20 @@ export type ExtractCollectionDefaultEndpoints<
619620

620621
export type ExtractAllCollectionDefaultEndpoints<
621622
TCollections extends Record<string, Collection<any, any, any, any, any, any>>,
622-
> = ValueOf<{
623-
[TCollectionIndex in keyof TCollections]: TCollections[TCollectionIndex] extends Collection<
624-
any,
625-
any,
626-
any,
627-
any,
628-
any,
629-
any
630-
>
631-
? ExtractCollectionDefaultEndpoints<TCollections[TCollectionIndex]>
632-
: {}
633-
}>
623+
> = UnionToIntersection<
624+
ValueOf<{
625+
[TCollectionIndex in keyof TCollections]: TCollections[TCollectionIndex] extends Collection<
626+
any,
627+
any,
628+
any,
629+
any,
630+
any,
631+
any
632+
>
633+
? ExtractCollectionDefaultEndpoints<TCollections[TCollectionIndex]>
634+
: {}
635+
}>
636+
>
634637

635638
export function getAllCollectionEndpoints<
636639
TCollections extends Record<string, Collection<any, any, any, any, any, any>>,

packages/core/src/config.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
22
import * as R from 'remeda'
33
import type { Simplify } from 'type-fest'
44

5-
import { type AuthClient, type AuthConfig, createAuth, getAuthClient } from './auth'
6-
import type { AuthHandlers } from './auth/handlers'
5+
import {
6+
type AuthClient,
7+
type AuthConfig,
8+
type AuthHandlers,
9+
createAuth,
10+
getAuthClient,
11+
} from './auth'
712
import {
813
type ClientCollection,
914
type Collection,
@@ -22,6 +27,7 @@ import {
2227
type ToRecordApiRouteSchema,
2328
} from './endpoint'
2429
import type { Field, FieldClient, Fields, FieldsClient } from './field'
30+
import type { KivotosPlugin, MergePlugins } from './plugins'
2531
import { isRelationField } from './utils'
2632

2733
export type MinimalContext<
@@ -96,14 +102,15 @@ export function defineServerConfig<
96102
Collection<any, any, any, any, any, any>
97103
>,
98104
const TEndpoints extends ApiRouter<MinimalContext<TFullSchema, TContext>> = {},
105+
const TPlugins extends KivotosPlugin<any>[] = [],
99106
>(
100107
baseConfig: BaseConfig<TFullSchema, TContext>,
101-
config: { collections: TCollections; endpoints?: TEndpoints }
108+
config: { collections: TCollections; endpoints?: TEndpoints; plugins?: TPlugins }
102109
) {
103110
const auth = createAuth(baseConfig.auth, baseConfig.context)
104111
const collectionEndpoints = getAllCollectionEndpoints(config.collections)
105112

106-
return {
113+
let serverConfig = {
107114
...baseConfig,
108115
collections: config.collections,
109116
endpoints: {
@@ -123,6 +130,12 @@ export function defineServerConfig<
123130
ExtractAllCollectionCustomEndpoints<TCollections> &
124131
ExtractAllCollectionDefaultEndpoints<TCollections>
125132
>
133+
134+
for (const plugin of config.plugins ?? []) {
135+
serverConfig = plugin(serverConfig)
136+
}
137+
138+
return serverConfig as MergePlugins<typeof serverConfig, TPlugins>
126139
}
127140

128141
export interface ClientConfig<

packages/core/src/endpoint.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ export type ToClientApiRouteSchema<TApiRouter extends ApiRouter<any>> = {
161161
}
162162

163163
export function createEndpoint<
164-
TApiEndpointSchema extends ApiRouteSchema,
165-
TContext extends Record<string, unknown> = Record<string, unknown>,
164+
const TApiEndpointSchema extends ApiRouteSchema,
165+
const TContext extends Record<string, unknown> = Record<string, unknown>,
166166
>(schema: TApiEndpointSchema, handler: ApiRouteHandler<TContext, TApiEndpointSchema>) {
167167
return {
168168
schema,

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type { AuthHandlers } from './auth/handlers'
1+
export type { AuthHandlers } from './auth'
22
export { Builder } from './builder'
33
export type {
44
ApiReturnType,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { z } from 'zod'
2+
3+
import { createEndpoint } from '../../endpoint'
4+
import type { AnyTypedColumn, WithAnyTable, WithHasDefault, WithNotNull } from '../../table'
5+
import { createPlugin } from '..'
6+
7+
// TODO: Implement the admin plugin functionality
8+
// Admin plugin provides administrative features for managing the application, such as user management, role assignments, and banning users.
9+
// Including users collection (UI and API), and custom access control.
10+
11+
type AnyUserTable = WithAnyTable<{
12+
id: WithHasDefault<WithNotNull<AnyTypedColumn<string>>>
13+
name: WithNotNull<AnyTypedColumn<string>>
14+
email: WithNotNull<AnyTypedColumn<string>>
15+
emailVerified: WithNotNull<AnyTypedColumn<boolean>>
16+
image: AnyTypedColumn<string | null>
17+
role: WithNotNull<AnyTypedColumn<string>>
18+
banned: AnyTypedColumn<boolean | null>
19+
bannedReason: AnyTypedColumn<string | null>
20+
bannedExpiresAt: AnyTypedColumn<Date | null>
21+
}>
22+
23+
export interface AdminPluginOptions {
24+
schema: {
25+
user: AnyUserTable
26+
}
27+
// TODO: Define specific access control rules for the admin plugin
28+
accessControl: {}
29+
}
30+
31+
export function admin(options?: AdminPluginOptions) {
32+
return createPlugin((input) => {
33+
return {
34+
...input,
35+
endpoints: {
36+
...input.endpoints,
37+
example: createEndpoint(
38+
{
39+
method: 'GET',
40+
path: 'example',
41+
responses: {
42+
200: z.object({
43+
message: z.string(),
44+
}),
45+
},
46+
},
47+
() => {
48+
return {
49+
status: 200 as const,
50+
body: {
51+
message: 'Hello from the admin plugin!',
52+
},
53+
}
54+
}
55+
),
56+
},
57+
}
58+
})
59+
}

packages/core/src/plugins/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ServerConfig } from '../config'
2+
3+
export type KivotosPlugin<TOutput extends ServerConfig> = (input: ServerConfig) => TOutput
4+
5+
type _MergePlugins<TPlugins extends KivotosPlugin<any>[]> = TPlugins extends [
6+
KivotosPlugin<infer TOutput>,
7+
...infer Rest extends KivotosPlugin<any>[],
8+
]
9+
? TOutput & _MergePlugins<Rest>
10+
: {}
11+
12+
export type MergePlugins<
13+
TOriginalServerConfig extends ServerConfig,
14+
TPlugins extends KivotosPlugin<any>[],
15+
> = _MergePlugins<TPlugins> & TOriginalServerConfig
16+
17+
export function createPlugin<TOutput extends ServerConfig>(
18+
plugin: KivotosPlugin<TOutput>
19+
): KivotosPlugin<TOutput> {
20+
return plugin
21+
}

0 commit comments

Comments
 (0)