offline-first, p2p synced, atproto enabled, feed reader

replace @traversable/zod since it's incompatible with zod mini

Changed files
+96 -7
src
+3 -2
src/feedline/schema/actions-entry.ts
··· 1 - import {zx} from '@traversable/zod' 2 1 import {z} from 'zod/mini' 2 + 3 + import {deepPartial} from '#lib/schema' 3 4 4 5 import {makeActionSchema} from '#realm/schema' 5 6 ··· 11 12 z.object({ 12 13 guid: z.string(), // entry GUID from feed 13 14 feedurl: z.url(), // which feed this entry belongs to 14 - payload: zx.deepPartial(entrySchema, 'applyToOutputType'), 15 + payload: deepPartial(entrySchema), 15 16 }), 16 17 ) 17 18
+3 -2
src/feedline/schema/actions-feed.ts
··· 1 - import {zx} from '@traversable/zod' 2 1 import {z} from 'zod/mini' 2 + 3 + import {deepPartial} from '#lib/schema' 3 4 4 5 import {makeActionSchema} from '#realm/schema' 5 6 ··· 28 29 'feed:patch', 29 30 z.object({ 30 31 url: z.url(), 31 - payload: zx.deepPartial(feedSchema, 'applyToOutputType'), 32 + payload: deepPartial(feedSchema), 32 33 }), 33 34 ) 34 35
+1
src/lib/schema.ts
··· 3 3 import {normalizeProtocolError} from './errors' 4 4 5 5 export * from './schema/brand' 6 + export * from './schema/deep-partial' 6 7 export * from './schema/json' 7 8 8 9 export const eventSchema = z.object({
+84
src/lib/schema/deep-partial.ts
··· 1 + import {z} from 'zod/mini' 2 + 3 + // Type-level deep partial for zod schemas 4 + type DeepPartialShape<T> = { 5 + [K in keyof T]: T[K] extends z.ZodMiniType<infer O> 6 + ? z.ZodMiniOptional<DeepPartialSchema<T[K] & z.ZodMiniType<O>>> 7 + : T[K] 8 + } 9 + 10 + type DeepPartialSchema<T extends z.ZodMiniType> = 11 + T extends z.ZodMiniObject<infer Shape> 12 + ? z.ZodMiniObject<DeepPartialShape<Shape>> 13 + : T extends z.ZodMiniArray<infer E extends z.ZodMiniType> 14 + ? z.ZodMiniArray<DeepPartialSchema<E>> 15 + : T extends z.ZodMiniOptional<infer U extends z.ZodMiniType> 16 + ? z.ZodMiniOptional<DeepPartialSchema<U>> 17 + : T extends z.ZodMiniNullable<infer U extends z.ZodMiniType> 18 + ? z.ZodMiniNullable<DeepPartialSchema<U>> 19 + : T 20 + 21 + export type DeepPartial<T extends z.ZodMiniType> = DeepPartialSchema<T> 22 + 23 + // Internal type for accessing schema internals 24 + type SchemaDef = {_zod: {def: {type: string}}} 25 + 26 + /** 27 + * Creates a deep partial version of a zod schema. 28 + * All object properties become optional recursively. 29 + */ 30 + export function deepPartial<T extends z.ZodMiniType>(schema: T): DeepPartial<T> { 31 + return deepPartialImpl(schema) as DeepPartial<T> 32 + } 33 + 34 + function deepPartialImpl(schema: z.ZodMiniType): z.ZodMiniType { 35 + const def = (schema as unknown as SchemaDef)._zod.def 36 + 37 + switch (def.type) { 38 + case 'object': { 39 + const objDef = def as {type: 'object'; shape: Record<string, z.ZodMiniType>} 40 + const newShape: Record<string, z.ZodMiniType> = {} 41 + 42 + for (const [key, value] of Object.entries(objDef.shape)) { 43 + newShape[key] = z.optional(deepPartialImpl(value)) 44 + } 45 + 46 + return z.object(newShape) 47 + } 48 + 49 + case 'array': { 50 + const arrDef = def as {type: 'array'; element: z.ZodMiniType} 51 + return z.array(deepPartialImpl(arrDef.element)) 52 + } 53 + 54 + case 'optional': { 55 + const optDef = def as {type: 'optional'; innerType: z.ZodMiniType} 56 + return z.optional(deepPartialImpl(optDef.innerType)) 57 + } 58 + 59 + case 'nullable': { 60 + const nullDef = def as {type: 'nullable'; innerType: z.ZodMiniType} 61 + return z.nullable(deepPartialImpl(nullDef.innerType)) 62 + } 63 + 64 + case 'record': { 65 + type RecordDef = {type: 'record'; keyType: z.core.$ZodRecordKey; valueType: z.ZodMiniType} 66 + const recDef = def as RecordDef 67 + return z.record(recDef.keyType, deepPartialImpl(recDef.valueType)) 68 + } 69 + 70 + case 'union': { 71 + const unionDef = def as {type: 'union'; options: z.ZodMiniType[]} 72 + return z.union(unionDef.options.map(deepPartialImpl) as [z.ZodMiniType, ...z.ZodMiniType[]]) 73 + } 74 + 75 + case 'intersection': { 76 + const intDef = def as {type: 'intersection'; left: z.ZodMiniType; right: z.ZodMiniType} 77 + return z.intersection(deepPartialImpl(intDef.left), deepPartialImpl(intDef.right)) 78 + } 79 + 80 + // Primitives and other types pass through unchanged 81 + default: 82 + return schema 83 + } 84 + }
+5 -3
src/lib/types.ts
··· 1 - import type {deepPartial} from '@traversable/zod' 2 - 3 1 /** 4 2 * Makes all properties in T optional recursively, including nested objects. 5 3 * @typeParam T - The type to make deeply partial 6 4 */ 7 - export type DeepPartial<T> = deepPartial<T> 5 + export type DeepPartial<T> = T extends object 6 + ? T extends (infer U)[] 7 + ? DeepPartial<U>[] 8 + : {[K in keyof T]?: DeepPartial<T[K]>} 9 + : T 8 10 9 11 /** 10 12 * Excludes array types from T. Useful for constraining generics to non-array values.