Skip to content

Commit e72280e

Browse files
feat: schema extension (#65)
1 parent c92b802 commit e72280e

9 files changed

Lines changed: 359 additions & 153 deletions

File tree

.changeset/some-tires-run.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ventyd": minor
3+
---
4+
5+
feat: export event, state schema (schema.event, schema.state)

src/arktype.ts

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,19 @@ import { type Type, type } from "arktype";
88
import { standard } from "./standard";
99
import type { BaseEventType, SchemaInput, ValueOf } from "./types";
1010

11-
type ArktypeObjectType = Type<object, object>;
11+
type ArktypeEmptyObject = Type<object, object>;
12+
13+
type ArktypeEventObject<
14+
$$EventName extends string,
15+
$$Body extends ArktypeEmptyObject,
16+
> = Type<{
17+
eventId: string;
18+
eventCreatedAt: string;
19+
entityName: string;
20+
entityId: string;
21+
eventName: $$EventName;
22+
body: $$Body["infer"];
23+
}>;
1224

1325
/**
1426
* Creates an ArkType schema provider for Ventyd.
@@ -93,15 +105,25 @@ type ArktypeObjectType = Type<object, object>;
93105
export function arktype<
94106
$$EntityName extends string,
95107
$$EventBodyArktypeDefinition extends {
96-
[eventName: string]: ArktypeObjectType;
108+
[eventName: string]: ArktypeEmptyObject;
97109
},
98-
$$StateArktypeDefinition extends ArktypeObjectType,
110+
$$StateArktypeDefinition extends ArktypeEmptyObject,
99111
$$NamespaceSeparator extends string = ":",
100112
>(args: {
101113
event: $$EventBodyArktypeDefinition;
102114
state: $$StateArktypeDefinition;
103115
namespaceSeparator?: $$NamespaceSeparator;
104116
}) {
117+
type $$EventArktypeDefinition = {
118+
[key in Extract<
119+
keyof $$EventBodyArktypeDefinition,
120+
string
121+
>]: ArktypeEventObject<
122+
`${$$EntityName}${$$NamespaceSeparator}${key}`,
123+
$$EventBodyArktypeDefinition[key]
124+
>;
125+
};
126+
105127
type $$EventStandardDefinition = {
106128
[key in Extract<
107129
keyof $$EventBodyArktypeDefinition,
@@ -124,46 +146,56 @@ export function arktype<
124146
>;
125147
type $$StateType = StandardSchemaV1.InferOutput<$$StateStandardDefinition>;
126148

127-
type $$SchemaInput = SchemaInput<$$EntityName, $$EventType, $$StateType>;
149+
type $$SchemaInput = SchemaInput<
150+
$$EntityName,
151+
$$EventType,
152+
$$StateType,
153+
{
154+
event: $$EventArktypeDefinition;
155+
state: $$StateArktypeDefinition;
156+
}
157+
>;
128158

129159
const input: $$SchemaInput = (context) => {
130160
const namespaceSeparator = args.namespaceSeparator ?? ":";
131161

132-
const event = Object.entries(args.event).reduce((acc, [key, body]) => {
133-
const eventName = `${context.entityName}${namespaceSeparator}${key}`;
134-
135-
// ArkType's intersection to combine base event and body
136-
const arktypeSchema = type({
137-
eventId: "string",
138-
eventCreatedAt: "string",
139-
entityName: "string",
140-
entityId: "string",
141-
eventName: `'${eventName}'`,
142-
body,
143-
});
144-
145-
// ArkType natively implements Standard Schema V1
146-
// We can use it directly as a StandardSchemaV1
147-
const standardSchema = arktypeSchema as unknown as StandardSchemaV1;
162+
const eventSchema = Object.entries(args.event).reduce(
163+
(acc, [key, body]) => {
164+
const eventName = `${context.entityName}${namespaceSeparator}${key}`;
148165

149-
return {
150-
// biome-ignore lint/performance/noAccumulatingSpread: readonly acc
151-
...acc,
152-
[eventName]: standardSchema,
153-
};
154-
}, {} as $$EventStandardDefinition);
166+
// ArkType's intersection to combine base event and body
167+
const arktypeSchema = type({
168+
eventId: "string",
169+
eventCreatedAt: "string",
170+
entityName: "string",
171+
entityId: "string",
172+
eventName: `'${eventName}'`,
173+
body,
174+
});
155175

156-
// ArkType state schema is already a Standard Schema V1
157-
const state = args.state as unknown as $$StateStandardDefinition;
176+
return {
177+
// biome-ignore lint/performance/noAccumulatingSpread: readonly acc
178+
...acc,
179+
[eventName]: arktypeSchema,
180+
};
181+
},
182+
{} as $$EventArktypeDefinition,
183+
);
184+
const stateSchema = args.state;
158185

159-
return standard<
160-
$$EntityName,
161-
$$EventStandardDefinition,
162-
$$StateStandardDefinition
163-
>({
164-
event,
165-
state,
166-
})(context);
186+
return {
187+
event: eventSchema,
188+
state: stateSchema,
189+
...standard<
190+
$$EntityName,
191+
$$EventStandardDefinition,
192+
$$StateStandardDefinition
193+
>({
194+
// ArkType natively implements Standard Schema V1
195+
event: eventSchema as unknown as $$EventStandardDefinition,
196+
state: stateSchema as unknown as $$StateStandardDefinition,
197+
})(context),
198+
};
167199
};
168200

169201
return input;

src/defineSchema.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,24 +91,29 @@ export function defineSchema<
9191
$$EventType extends BaseEventType,
9292
$$StateType,
9393
$$InitialEventName extends $$EventType["eventName"],
94+
$$Extension = {},
9495
>(
9596
entityName: $$EntityName,
9697
options: {
97-
schema: SchemaInput<$$EntityName, $$EventType, $$StateType>;
98+
schema: SchemaInput<$$EntityName, $$EventType, $$StateType, $$Extension>;
9899
initialEventName: $$InitialEventName;
99100
generateId?: (type: "eventId" | "entityId") => string;
100101
},
101-
): Schema<$$EntityName, $$EventType, $$StateType, $$InitialEventName> {
102+
): Schema<
103+
$$EntityName,
104+
$$EventType,
105+
$$StateType,
106+
$$InitialEventName,
107+
$$Extension
108+
> {
102109
const generateId = options.generateId ?? defaultGenerateId;
103110

104-
const { parseEvent, parseEventByName, parseState } = options.schema({
111+
const schemaInput = options.schema({
105112
entityName,
106113
});
107114

108115
return {
109-
parseEvent,
110-
parseEventByName,
111-
parseState,
116+
...schemaInput,
112117
" $$entityName": entityName,
113118
" $$initialEventName": options.initialEventName,
114119
" $$generateId": generateId,

src/typebox.ts

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,25 @@
44
*/
55

66
import type { StandardSchemaV1 } from "@standard-schema/spec";
7-
import { Type } from "typebox";
7+
import { type TLiteral, type TObject, type TString, Type } from "typebox";
88
import { standard } from "./standard";
99
import { typeboxToStandardSchema } from "./typeboxToStandardSchema";
1010
import type { BaseEventType, SchemaInput, ValueOf } from "./types";
1111

1212
type TypeboxEmptyObject = Type.TObject<{}>;
1313

14+
type TypeboxEventObject<
15+
$$EventName extends string,
16+
$$Body extends TypeboxEmptyObject,
17+
> = TObject<{
18+
eventId: TString;
19+
eventCreatedAt: TString;
20+
entityName: TString;
21+
entityId: TString;
22+
eventName: TLiteral<$$EventName>;
23+
body: $$Body;
24+
}>;
25+
1426
/**
1527
* Creates a TypeBox schema provider for Ventyd.
1628
*
@@ -103,6 +115,16 @@ export function typebox<
103115
state: $$StateTypeboxDefinition;
104116
namespaceSeparator?: $$NamespaceSeparator;
105117
}) {
118+
type $$EventTypeboxDefinition = {
119+
[key in Extract<
120+
keyof $$EventBodyTypeboxDefinition,
121+
string
122+
>]: TypeboxEventObject<
123+
`${$$EntityName}${$$NamespaceSeparator}${key}`,
124+
$$EventBodyTypeboxDefinition[key]
125+
>;
126+
};
127+
106128
type $$EventStandardDefinition = {
107129
[key in Extract<
108130
keyof $$EventBodyTypeboxDefinition,
@@ -125,41 +147,61 @@ export function typebox<
125147
>;
126148
type $$StateType = StandardSchemaV1.InferOutput<$$StateStandardDefinition>;
127149

128-
type $$SchemaInput = SchemaInput<$$EntityName, $$EventType, $$StateType>;
150+
type $$SchemaInput = SchemaInput<
151+
$$EntityName,
152+
$$EventType,
153+
$$StateType,
154+
{
155+
event: $$EventTypeboxDefinition;
156+
state: $$StateTypeboxDefinition;
157+
}
158+
>;
129159

130160
const input: $$SchemaInput = (context) => {
131161
const namespaceSeparator = args.namespaceSeparator ?? ":";
132162

133-
const event = Object.entries(args.event).reduce((acc, [key, body]) => {
134-
const eventName = `${context.entityName}${namespaceSeparator}${key}`;
135-
const schema = typeboxToStandardSchema(
136-
Type.Object({
163+
const eventSchema = Object.entries(args.event).reduce(
164+
(acc, [key, body]) => {
165+
const eventName = `${context.entityName}${namespaceSeparator}${key}`;
166+
const schema = Type.Object({
137167
eventId: Type.String(),
138168
eventCreatedAt: Type.String(),
139169
entityName: Type.String(),
140170
entityId: Type.String(),
141171
eventName: Type.Literal(eventName),
142172
body,
143-
}),
144-
);
145-
146-
return {
147-
// biome-ignore lint/performance/noAccumulatingSpread: readonly acc
148-
...acc,
149-
[eventName]: schema,
150-
};
151-
}, {} as $$EventStandardDefinition);
152-
153-
const state = typeboxToStandardSchema(args.state);
173+
});
174+
return {
175+
// biome-ignore lint/performance/noAccumulatingSpread: readonly acc
176+
...acc,
177+
[eventName]: schema,
178+
};
179+
},
180+
{} as $$EventTypeboxDefinition,
181+
);
182+
const stateSchema = args.state;
154183

155-
return standard<
156-
$$EntityName,
157-
$$EventStandardDefinition,
158-
$$StateStandardDefinition
159-
>({
160-
event,
161-
state,
162-
})(context);
184+
return {
185+
event: eventSchema,
186+
state: stateSchema,
187+
...standard<
188+
$$EntityName,
189+
$$EventStandardDefinition,
190+
$$StateStandardDefinition
191+
>({
192+
event: Object.entries(eventSchema).reduce(
193+
(acc, [eventName, schema]) => {
194+
return {
195+
// biome-ignore lint/performance/noAccumulatingSpread: readonly acc
196+
...acc,
197+
[eventName]: typeboxToStandardSchema(schema),
198+
};
199+
},
200+
{} as $$EventStandardDefinition,
201+
),
202+
state: typeboxToStandardSchema(args.state),
203+
})(context),
204+
};
163205
};
164206

165207
return input;

0 commit comments

Comments
 (0)