diff --git a/README.md b/README.md index 089e32a..3662698 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ import { createEndpointQuerySchema } from '@platform/backend/query/query.ts' import { z } from 'zod' import { CURSOR_SECRET } from './_env.ts' -import { BaseQuerySchema } from '#shared/server/schemas.ts' +import { BaseQuerySchema } from '#shared/endpoint/schemas.ts' // 1. Declare which fields can be filtered and how diff --git a/packages/backend/src/endpoint/definitions.ts b/packages/backend/src/endpoint/definitions.ts new file mode 100644 index 0000000..62f9659 --- /dev/null +++ b/packages/backend/src/endpoint/definitions.ts @@ -0,0 +1,82 @@ +import type { ValidationTargets } from 'hono' +import type { FormValue, ParsedFormValue } from 'hono/types' +import type z from 'zod' + +/** + * Schema accepting input type I, producing any output + * + * No structural constraints - accepts: + * - Plain objects: z.object({ ... }) + * - Pipes: z.object({ ... }).pipe(...) + * - Transforms: z.object({ ... }).transform(...) + * - Unions, intersections, etc. + * + * Use this when you need input type safety but want to allow transformations. + */ +export type SchemaFor = z.ZodType + +/** + * Helper to normalize record types for compatibility checking + * + * Converts Record to { [key: string]: V } to allow index signature + * schemas to satisfy specific key union requirements. + * + * This enables: z.object({}).catchall(z.string()) to satisfy + * RecordSchemaFor> + */ +type NormalizedRecordInput = + T extends Record + ? K extends string + ? { [key: string]: V } + : T + : T + +/** + * Schema with record-shaped input + * + * Accepts schemas with index signatures that can parse the expected record type. + * This allows catchall schemas to satisfy specific key union requirements. + * + * @example + * // ✓ This works - catchall accepts any string keys + * const schema = z.object({}).catchall(z.string()) + * satisfies RecordSchemaFor> + */ +export type RecordSchemaFor, Output = any> = + SchemaFor, Output> + +/** + * @deprecated Use SchemaFor instead - allows transformations + */ +export type AnySchemaFor = SchemaFor + +/** + * Validation schemas for each input source + * + * Each schema must accept the raw input type from its source: + * - Query: Record + * - Form: Record + * - Json: any + * - Param: Record + * - Header: Record + * - Cookie: Record + * + * Schemas can transform to any output type (pipes/transforms allowed). + */ +export type EndpointDefinitionSchemas = { + [K in keyof ValidationTargets as Capitalize]?: + ValidationTargets[K] extends Record + ? RecordSchemaFor[K]> + : SchemaFor[K]> +} + +// Endpoint definition contract +export type EndpointDefinition = { + Name: string + Route: string + Description?: string + Methods: readonly ('GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH')[] + Input: z.ZodType + Output: z.ZodType + Schemas: EndpointDefinitionSchemas +} diff --git a/supabase/functions/_shared/server/schemas.ts b/packages/backend/src/endpoint/schemas.ts similarity index 98% rename from supabase/functions/_shared/server/schemas.ts rename to packages/backend/src/endpoint/schemas.ts index 5c2dac0..d73d5ad 100644 --- a/supabase/functions/_shared/server/schemas.ts +++ b/packages/backend/src/endpoint/schemas.ts @@ -1,6 +1,6 @@ // schemas.ts import type { FormValue, ParsedFormValue } from 'hono/types'; -import type { EndpointDefinitionSchemas } from './types.ts'; +import type { EndpointDefinitionSchemas } from './definitions.ts'; import { z } from 'zod'; /** diff --git a/packages/backend/src/query/fields.ts b/packages/backend/src/query/fields.ts index 7da4870..623530d 100644 --- a/packages/backend/src/query/fields.ts +++ b/packages/backend/src/query/fields.ts @@ -13,7 +13,7 @@ import { z } from 'zod' -import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema } from '../../../../supabase/functions/_shared/server/schemas.ts' +import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema } from '../endpoint/schemas.ts' import { FieldSelectionNormalizedSchema, type FieldsConfig, diff --git a/packages/backend/src/query/filtering.ts b/packages/backend/src/query/filtering.ts index 1c0659d..26532d7 100644 --- a/packages/backend/src/query/filtering.ts +++ b/packages/backend/src/query/filtering.ts @@ -132,7 +132,7 @@ import { z } from 'zod' -import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema } from '../../../../supabase/functions/_shared/server/schemas.ts' +import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema } from '../endpoint/schemas.ts' import { FiltersNormalizedSchema, type FilterNormalized, diff --git a/packages/backend/src/query/pagination.ts b/packages/backend/src/query/pagination.ts index 70d06c5..1cf07db 100644 --- a/packages/backend/src/query/pagination.ts +++ b/packages/backend/src/query/pagination.ts @@ -18,7 +18,7 @@ import { z } from 'zod' import { createHmac } from 'node:crypto' import { Buffer } from 'node:buffer' -import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema, ZStringOrStringArray } from '../../../../supabase/functions/_shared/server/schemas.ts' +import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema, ZStringOrStringArray } from '../endpoint/schemas.ts' import { PaginationNormalizedSchema, CursorDataSchema, diff --git a/packages/backend/src/query/query.ts b/packages/backend/src/query/query.ts index 27687fa..4c42c77 100644 --- a/packages/backend/src/query/query.ts +++ b/packages/backend/src/query/query.ts @@ -26,7 +26,7 @@ import { createFiltersSchema } from './filtering.ts' import { createSortsSchema } from './sorting.ts' import { createFieldsSchema } from './fields.ts' -import { BaseFormSchema, BaseJsonSchema, BaseQuerySchema } from '../../../../supabase/functions/_shared/server/schemas.ts' +import { BaseFormSchema, BaseJsonSchema, BaseQuerySchema } from '../endpoint/schemas.ts' // Re-export QuerySpec type export { QuerySpecSchema, type QuerySpec } from './schemas.ts' diff --git a/packages/backend/src/query/sorting.ts b/packages/backend/src/query/sorting.ts index 538b8fe..9aa7b63 100644 --- a/packages/backend/src/query/sorting.ts +++ b/packages/backend/src/query/sorting.ts @@ -12,7 +12,7 @@ import { z } from 'zod' -import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema, ZStringOrStringArray } from '../../../../supabase/functions/_shared/server/schemas.ts' +import { BaseQuerySchema, BaseJsonSchema, BaseFormSchema, ZStringOrStringArray } from '../endpoint/schemas.ts' import { SortsNormalizedSchema, type SortNormalized, diff --git a/supabase/functions/_shared/server/types.ts b/supabase/functions/_shared/server/types.ts index 9926315..2b56786 100644 --- a/supabase/functions/_shared/server/types.ts +++ b/supabase/functions/_shared/server/types.ts @@ -1,86 +1,11 @@ import type { AppEnv as SharedAppEnv } from './create-app.ts' -import type { Handler, MiddlewareHandler, ValidationTargets, Input } from 'hono' -import type { FormValue, HandlerResponse, ParsedFormValue } from 'hono/types' +import type { Handler, MiddlewareHandler, Input } from 'hono' +import type { HandlerResponse } from 'hono/types' import type z from 'zod' -/** - * Schema accepting input type I, producing any output - * - * No structural constraints - accepts: - * - Plain objects: z.object({ ... }) - * - Pipes: z.object({ ... }).pipe(...) - * - Transforms: z.object({ ... }).transform(...) - * - Unions, intersections, etc. - * - * Use this when you need input type safety but want to allow transformations. - */ -export type SchemaFor = z.ZodType - -/** - * Helper to normalize record types for compatibility checking - * - * Converts Record to { [key: string]: V } to allow index signature - * schemas to satisfy specific key union requirements. - * - * This enables: z.object({}).catchall(z.string()) to satisfy - * RecordSchemaFor> - */ -type NormalizedRecordInput = - T extends Record - ? K extends string - ? { [key: string]: V } - : T - : T - -/** - * Schema with record-shaped input - * - * Accepts schemas with index signatures that can parse the expected record type. - * This allows catchall schemas to satisfy specific key union requirements. - * - * @example - * // ✓ This works - catchall accepts any string keys - * const schema = z.object({}).catchall(z.string()) - * satisfies RecordSchemaFor> - */ -export type RecordSchemaFor, Output = any> = - SchemaFor, Output> - -/** - * @deprecated Use SchemaFor instead - allows transformations - */ -export type AnySchemaFor = SchemaFor - -/** - * Validation schemas for each input source - * - * Each schema must accept the raw input type from its source: - * - Query: Record - * - Form: Record - * - Json: any - * - Param: Record - * - Header: Record - * - Cookie: Record - * - * Schemas can transform to any output type (pipes/transforms allowed). - */ -export type EndpointDefinitionSchemas = { - [K in keyof ValidationTargets as Capitalize]?: - ValidationTargets[K] extends Record - ? RecordSchemaFor[K]> - : SchemaFor[K]> -} +import type { EndpointDefinition, EndpointDefinitionSchemas } from '@platform/backend/endpoint/definitions.ts' +export type * from '@platform/backend/endpoint/definitions.ts' -// Endpoint definition contract -export type EndpointDefinition = { - Name: string - Route: string - Description?: string - Methods: readonly ('GET' | 'POST' | 'DELETE' | 'PUT' | 'PATCH')[] - Input: z.ZodType - Output: z.ZodType - Schemas: EndpointDefinitionSchemas -} /** * FunctionAppEnv with specific variable requirements. diff --git a/supabase/functions/social/endpoints/follows/check/definition.ts b/supabase/functions/social/endpoints/follows/check/definition.ts index fdee08a..b0f1576 100644 --- a/supabase/functions/social/endpoints/follows/check/definition.ts +++ b/supabase/functions/social/endpoints/follows/check/definition.ts @@ -12,7 +12,7 @@ import type { EndpointDefinition } from '#shared/server/types.ts' import { makeSuccessResponseSchema } from '@platform/backend/response/schemas.ts' -import { BaseQuerySchema } from '#shared/server/schemas.ts' +import { BaseQuerySchema } from '@platform/backend/endpoint/schemas.ts' import { FollowPreferencesSchema } from '../../../utils/_schemas.ts' import { z } from 'zod' diff --git a/supabase/functions/social/endpoints/follows/follow/definition.ts b/supabase/functions/social/endpoints/follows/follow/definition.ts index 87e0f3f..f79ce0b 100644 --- a/supabase/functions/social/endpoints/follows/follow/definition.ts +++ b/supabase/functions/social/endpoints/follows/follow/definition.ts @@ -16,7 +16,7 @@ import type { EndpointDefinition } from '#shared/server/types.ts' import { makeSuccessResponseSchema } from '@platform/backend/response/schemas.ts' -import { BaseJsonSchema } from '#shared/server/schemas.ts' +import { BaseJsonSchema } from '@platform/backend/endpoint/schemas.ts' import { FollowPreferencesSchema, FollowItemSchema } from '../../../utils/_schemas.ts' import { z } from 'zod' diff --git a/supabase/functions/social/endpoints/follows/list/definition.ts b/supabase/functions/social/endpoints/follows/list/definition.ts index 7fa197a..e0f6676 100644 --- a/supabase/functions/social/endpoints/follows/list/definition.ts +++ b/supabase/functions/social/endpoints/follows/list/definition.ts @@ -16,7 +16,7 @@ import type { EndpointDefinition } from '#shared/server/types.ts' import { makePaginationResultSchema } from '@platform/backend/response/schemas.ts' import { createEndpointQuerySchema } from '@platform/backend/query/query.ts' -import { BaseQuerySchema } from '#shared/server/schemas.ts' +import { BaseQuerySchema } from '@platform/backend/endpoint/schemas.ts' import { FollowItemSchema } from '../../../utils/_schemas.ts' import { CURSOR_SECRET } from './_env.ts' import { z } from 'zod' diff --git a/supabase/functions/social/endpoints/follows/unfollow/definition.ts b/supabase/functions/social/endpoints/follows/unfollow/definition.ts index ea98e56..0acb52b 100644 --- a/supabase/functions/social/endpoints/follows/unfollow/definition.ts +++ b/supabase/functions/social/endpoints/follows/unfollow/definition.ts @@ -15,7 +15,7 @@ import type { EndpointDefinition } from '#shared/server/types.ts' import { makeSuccessResponseSchema } from '@platform/backend/response/schemas.ts' -import { BaseQuerySchema, BaseParamSchema } from '#shared/server/schemas.ts' +import { BaseQuerySchema, BaseParamSchema } from '@platform/backend/endpoint/schemas.ts' import { z } from 'zod' // ============================================================================ diff --git a/supabase/functions/social/endpoints/follows/update/definition.ts b/supabase/functions/social/endpoints/follows/update/definition.ts index 4ed4f0c..e64b66a 100644 --- a/supabase/functions/social/endpoints/follows/update/definition.ts +++ b/supabase/functions/social/endpoints/follows/update/definition.ts @@ -14,7 +14,7 @@ import type { EndpointDefinition } from '#shared/server/types.ts' import { makeSuccessResponseSchema } from '@platform/backend/response/schemas.ts' -import { BaseJsonSchema, BaseParamSchema } from '#shared/server/schemas.ts' +import { BaseJsonSchema, BaseParamSchema } from '@platform/backend/endpoint/schemas.ts' import { FollowPreferencesSchema, FollowItemSchema } from '../../../utils/_schemas.ts' import { z } from 'zod' diff --git a/supabase/functions/social/endpoints/likes/check/definition.ts b/supabase/functions/social/endpoints/likes/check/definition.ts index 4906df3..78586a2 100644 --- a/supabase/functions/social/endpoints/likes/check/definition.ts +++ b/supabase/functions/social/endpoints/likes/check/definition.ts @@ -1,7 +1,7 @@ // endpoint/likes/check/definition.ts import type { EndpointDefinition } from '#shared/server/types.ts' -import { BaseQuerySchema } from '#shared/server/schemas.ts' +import { BaseQuerySchema } from '@platform/backend/endpoint/schemas.ts' import { z } from 'zod' export const QuerySchema = BaseQuerySchema.extend({ diff --git a/supabase/functions/social/endpoints/likes/like/definition.ts b/supabase/functions/social/endpoints/likes/like/definition.ts index d060d32..dac1346 100644 --- a/supabase/functions/social/endpoints/likes/like/definition.ts +++ b/supabase/functions/social/endpoints/likes/like/definition.ts @@ -2,7 +2,7 @@ import type { EndpointDefinition } from '#shared/server/types.ts' import { makeSuccessResponseSchema } from '@platform/backend/response/schemas.ts' -import { BaseJsonSchema } from '#shared/server/schemas.ts' +import { BaseJsonSchema } from '@platform/backend/endpoint/schemas.ts' import { z } from 'zod' export const JsonSchema = z.object({ diff --git a/supabase/functions/social/endpoints/likes/list/definition.ts b/supabase/functions/social/endpoints/likes/list/definition.ts index 9f931bb..e7d5530 100644 --- a/supabase/functions/social/endpoints/likes/list/definition.ts +++ b/supabase/functions/social/endpoints/likes/list/definition.ts @@ -5,7 +5,7 @@ import type { EndpointDefinition } from '#shared/server/types.ts' import { makePaginationResultSchema } from '@platform/backend/response/schemas.ts' import { createEndpointQuerySchema } from '@platform/backend/query/query.ts' -import { BaseQuerySchema } from '#shared/server/schemas.ts' +import { BaseQuerySchema } from '@platform/backend/endpoint/schemas.ts' import { CURSOR_SECRET } from './_env.ts' import { z } from 'zod' diff --git a/supabase/functions/social/endpoints/likes/unlike/definition.ts b/supabase/functions/social/endpoints/likes/unlike/definition.ts index d901a95..61a5f22 100644 --- a/supabase/functions/social/endpoints/likes/unlike/definition.ts +++ b/supabase/functions/social/endpoints/likes/unlike/definition.ts @@ -1,7 +1,7 @@ // endpoint/likes/unlike/definition.ts import { z } from 'zod' import type { EndpointDefinition } from '#shared/server/types.ts' -import { BaseQuerySchema, BaseParamSchema } from '#shared/server/schemas.ts' +import { BaseQuerySchema, BaseParamSchema } from '@platform/backend/endpoint/schemas.ts' export const Route = '/likes/:target_id'; export const ParamSchema = BaseParamSchema.extend({