Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add message-queue-toolkit/schemas #151

Merged
merged 2 commits into from
May 23, 2024
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
81 changes: 12 additions & 69 deletions packages/core/lib/events/baseEventSchemas.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,12 @@
import type { ZodLiteral, ZodObject, ZodOptional, ZodString } from 'zod'
import z from 'zod'
import type { ZodRawShape } from 'zod/lib/types'

// Base event fields that are typically autogenerated
export const GENERATED_BASE_EVENT_SCHEMA = z.object({
id: z.string().describe('event unique identifier'),
timestamp: z.string().datetime().describe('iso 8601 datetime'),
})

// Base event fields that are typically autogenerated, marked as optional
export const OPTIONAL_GENERATED_BASE_EVENT_SCHEMA = z.object({
id: z.string().describe('event unique identifier').optional(),
timestamp: z.string().datetime().describe('iso 8601 datetime').optional(),
})

// Base event fields that are always defined manually
export const CORE_EVENT_SCHEMA = z.object({
type: z.literal<string>('<replace.me>').describe('event type name'),
payload: z.optional(z.object({})).describe('event payload based on type'),
})

// Core fields that describe either internal event or external message
export const CONSUMER_BASE_EVENT_SCHEMA = GENERATED_BASE_EVENT_SCHEMA.extend(
CORE_EVENT_SCHEMA.shape,
)
export const PUBLISHER_BASE_EVENT_SCHEMA = OPTIONAL_GENERATED_BASE_EVENT_SCHEMA.extend(
CORE_EVENT_SCHEMA.shape,
)

export type ConsumerBaseEventType = z.infer<typeof CONSUMER_BASE_EVENT_SCHEMA>
export type PublisherBaseEventType = z.infer<typeof PUBLISHER_BASE_EVENT_SCHEMA>
export type CoreEventType = z.infer<typeof CORE_EVENT_SCHEMA>
export type GeneratedBaseEventType = z.infer<typeof GENERATED_BASE_EVENT_SCHEMA>

type ReturnType<T extends ZodObject<Y>, Y extends ZodRawShape, Z extends string> = {
consumerSchema: ZodObject<{
id: ZodString
timestamp: ZodString
type: ZodLiteral<Z>
payload: T
}>

publisherSchema: ZodObject<{
id: ZodOptional<ZodString>
timestamp: ZodOptional<ZodString>
type: ZodLiteral<Z>
payload: T
}>
}

export function enrichEventSchemaWithBase<
T extends ZodObject<Y>,
Y extends ZodRawShape,
Z extends string,
>(type: Z, payloadSchema: T): ReturnType<T, Y, Z> {
const baseSchema = z.object({
type: z.literal(type),
payload: payloadSchema,
})

const consumerSchema = GENERATED_BASE_EVENT_SCHEMA.merge(baseSchema)
const publisherSchema = OPTIONAL_GENERATED_BASE_EVENT_SCHEMA.merge(baseSchema)

return {
consumerSchema: consumerSchema,
publisherSchema: publisherSchema,
}
}
export {
GENERATED_BASE_EVENT_SCHEMA,
OPTIONAL_GENERATED_BASE_EVENT_SCHEMA,
CORE_EVENT_SCHEMA,
CONSUMER_BASE_EVENT_SCHEMA,
PUBLISHER_BASE_EVENT_SCHEMA,
ConsumerBaseEventType,
PublisherBaseEventType,
CoreEventType,
GeneratedBaseEventType,
enrichEventSchemaWithBase,
} from '@message-queue-toolkit/schemas'
64 changes: 9 additions & 55 deletions packages/core/lib/events/eventTypes.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,9 @@
import type { ZodObject, ZodTypeAny } from 'zod'
import type z from 'zod'

import type { MessageMetadataType } from '../messages/baseMessageSchemas'

import type { CONSUMER_BASE_EVENT_SCHEMA, PUBLISHER_BASE_EVENT_SCHEMA } from './baseEventSchemas'

export type EventTypeNames<EventDefinition extends CommonEventDefinition> =
CommonEventDefinitionConsumerSchemaType<EventDefinition>['type']

export type CommonEventDefinition = {
consumerSchema: ZodObject<
Omit<(typeof CONSUMER_BASE_EVENT_SCHEMA)['shape'], 'payload'> & { payload: ZodTypeAny }
>
publisherSchema: ZodObject<
Omit<(typeof PUBLISHER_BASE_EVENT_SCHEMA)['shape'], 'payload'> & { payload: ZodTypeAny }
>
schemaVersion?: string
}

export type CommonEventDefinitionConsumerSchemaType<T extends CommonEventDefinition> = z.infer<
T['consumerSchema']
>

export type CommonEventDefinitionPublisherSchemaType<T extends CommonEventDefinition> = z.infer<
T['publisherSchema']
>

export type EventHandler<
EventDefinitionSchema extends
CommonEventDefinitionConsumerSchemaType<CommonEventDefinition> = CommonEventDefinitionConsumerSchemaType<CommonEventDefinition>,
MetadataDefinitionSchema extends Partial<MessageMetadataType> = Partial<MessageMetadataType>,
> = {
handleEvent(
event: EventDefinitionSchema,
metadata?: MetadataDefinitionSchema,
): void | Promise<void>
}

export type AnyEventHandler<EventDefinitions extends CommonEventDefinition[]> = EventHandler<
CommonEventDefinitionConsumerSchemaType<EventDefinitions[number]>
>

export type SingleEventHandler<
EventDefinition extends CommonEventDefinition[],
EventTypeName extends EventTypeNames<EventDefinition[number]>,
> = EventHandler<EventFromArrayByTypeName<EventDefinition, EventTypeName>>

type EventFromArrayByTypeName<
EventDefinition extends CommonEventDefinition[],
EventTypeName extends EventTypeNames<EventDefinition[number]>,
> = Extract<
CommonEventDefinitionConsumerSchemaType<EventDefinition[number]>,
{ type: EventTypeName }
>
export {
EventTypeNames,
CommonEventDefinition,
CommonEventDefinitionConsumerSchemaType,
CommonEventDefinitionPublisherSchemaType,
EventHandler,
AnyEventHandler,
SingleEventHandler,
} from '@message-queue-toolkit/schemas'
102 changes: 9 additions & 93 deletions packages/core/lib/messages/baseMessageSchemas.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,9 @@
import z, { type ZodLiteral, type ZodObject, type ZodOptional, type ZodString } from 'zod'
import type { ZodRawShape } from 'zod/lib/types'

import {
CONSUMER_BASE_EVENT_SCHEMA,
GENERATED_BASE_EVENT_SCHEMA,
OPTIONAL_GENERATED_BASE_EVENT_SCHEMA,
} from '../events/baseEventSchemas'
import type { CommonEventDefinition } from '../events/eventTypes'

// External message metadata that describe the context in which the message was created, primarily used for debugging purposes
export const MESSAGE_METADATA_SCHEMA = z
.object({
schemaVersion: z.string().min(1).describe('message schema version'),
// this is always set to a service that created the message
producedBy: z.string().min(1).describe('app/service that produced the message'),
// this is always propagated within the message chain. For the first message in the chain it is equal to "producedBy"
originatedFrom: z
.string()
.min(1)
.describe('app/service that initiated entire workflow that led to creating this message'),
// this is always propagated within the message chain.
correlationId: z.string().describe('unique identifier passed to all events in workflow chain'),
})
.describe('external message metadata')

export const MESSAGE_SCHEMA_EXTENSION = {
// For internal domain events that did not originate within a message chain metadata field can be omitted, producer should then assume it is initiating a new chain
metadata: MESSAGE_METADATA_SCHEMA.optional(),
}

export const BASE_MESSAGE_SCHEMA = CONSUMER_BASE_EVENT_SCHEMA.extend(MESSAGE_SCHEMA_EXTENSION)

export type BaseMessageType = z.infer<typeof BASE_MESSAGE_SCHEMA>

export type MessageMetadataType = z.infer<typeof MESSAGE_METADATA_SCHEMA>

export type CommonMessageDefinitionSchemaType<T extends CommonEventDefinition> = z.infer<
T['consumerSchema']
>

type ReturnType<T extends ZodObject<Y>, Y extends ZodRawShape, Z extends string> = {
consumerSchema: ZodObject<{
id: ZodString
timestamp: ZodString
type: ZodLiteral<Z>
payload: T
metadata: ZodOptional<
ZodObject<{
schemaVersion: ZodString
producedBy: ZodString
originatedFrom: ZodString
correlationId: ZodString
}>
>
}>

publisherSchema: ZodObject<{
id: ZodOptional<ZodString>
timestamp: ZodOptional<ZodString>
type: ZodLiteral<Z>
payload: T
metadata: ZodOptional<
ZodObject<{
schemaVersion: ZodString
producedBy: ZodString
originatedFrom: ZodString
correlationId: ZodString
}>
>
}>
}

export function enrichMessageSchemaWithBase<
T extends ZodObject<Y>,
Y extends ZodRawShape,
Z extends string,
>(type: Z, payloadSchema: T): ReturnType<T, Y, Z> {
const baseSchema = z.object({
type: z.literal(type),
payload: payloadSchema,
})

const consumerSchema =
GENERATED_BASE_EVENT_SCHEMA.merge(baseSchema).extend(MESSAGE_SCHEMA_EXTENSION)
const publisherSchema =
OPTIONAL_GENERATED_BASE_EVENT_SCHEMA.merge(baseSchema).extend(MESSAGE_SCHEMA_EXTENSION)

return {
consumerSchema: consumerSchema,
publisherSchema: publisherSchema,
}
}
export {
MESSAGE_METADATA_SCHEMA,
MESSAGE_SCHEMA_EXTENSION,
BASE_MESSAGE_SCHEMA,
BaseMessageType,
MessageMetadataType,
CommonMessageDefinitionSchemaType,
enrichMessageSchemaWithBase,
} from '@message-queue-toolkit/schemas'
11 changes: 1 addition & 10 deletions packages/core/lib/utils/toDateProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1 @@
export const toDatePreprocessor = (value: unknown) => {
switch (typeof value) {
case 'string':
case 'number':
return new Date(value)

default:
return value // could not coerce, return the original and face the consequences during validation
}
}
export { toDatePreprocessor } from '@message-queue-toolkit/schemas'
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"dependencies": {
"@lokalise/node-core": "^9.17.0",
"@message-queue-toolkit/schemas": "^1.0.0",
"fast-equals": "^5.0.1",
"toad-cache": "^3.7.0",
"zod": "^3.23.8"
Expand Down
8 changes: 4 additions & 4 deletions packages/core/vitest.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export default defineConfig({
reporter: ['text'],
all: true,
thresholds: {
lines: 35,
functions: 65,
branches: 80,
statements: 35,
lines: 30,
functions: 60,
branches: 75,
statements: 30,
},
},
},
Expand Down
4 changes: 4 additions & 0 deletions packages/schemas/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
coverage/
dist/
vitest.config.mts
80 changes: 80 additions & 0 deletions packages/schemas/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"project": "./tsconfig.json"
},
"ignorePatterns": ["node_modules"],
"plugins": ["@typescript-eslint", "vitest", "import"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:vitest/recommended"
],
"rules": {
"@typescript-eslint/no-empty-interface": "warn",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/consistent-type-imports": "warn",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
],
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": "none",
"requireLast": false
},
"singleline": {
"delimiter": "comma",
"requireLast": false
}
}
],
"import/no-default-export": "error",
"import/order": [
"warn",
{
"alphabetize": { "order": "asc" },
"newlines-between": "always"
}
],
"max-lines": ["error", { "max": 600 }],
"max-params": ["error", { "max": 4 }],
"max-statements": ["error", { "max": 15 }],
"complexity": ["error", { "max": 20 }]
},
"overrides": [
{
"files": ["test/**/*.ts", "*.test.ts", "*.spec.ts"],
"rules": {
"@typescript-eslint/require-await": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-call": "off",
"max-statements": ["off"]
}
}
]
}
9 changes: 9 additions & 0 deletions packages/schemas/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"printWidth": 100,
"singleQuote": true,
"bracketSpacing": true,
"semi": false,
"arrowParens": "always",
"endOfLine": "lf",
"trailingComma": "all"
}
5 changes: 5 additions & 0 deletions packages/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { toDatePreprocessor } from './lib/utils/toDateProcessor'

export * from './lib/events/eventTypes'
export * from './lib/events/baseEventSchemas'
export * from './lib/messages/baseMessageSchemas'
Loading
Loading