model-blueprint 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dropQuery.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/utils.ts
2
+ import z from "zod";
2
3
  var ModelError = class extends Error {
3
4
  constructor(status, options) {
4
5
  super(options?.message);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/queryUtils/dropQuery.ts"],"sourcesContent":["import z, { ZodAny } from 'zod';\nimport { QUERY_MARKER } from './constant';\nimport { QueryBuilder } from './types';\nimport { ProcedureBuilder } from './ProcedureBuilder';\n\n/**\n * Type guard to check if a value is a valid `QueryBuilder`.\n * Used internally by the repository factory during hydration.\n * @param value - The value to check.\n * @returns True if the value is a QueryBuilder function.\n */\nexport function isQueryBuilder<TContext>(\n value: unknown\n): value is QueryBuilder<TContext, unknown, unknown> {\n return (\n typeof value === 'function' &&\n QUERY_MARKER in value &&\n (value as { [QUERY_MARKER]: boolean })[QUERY_MARKER] === true\n );\n}\n\n/**\n * Type guard to check if a value is a plain JavaScript object.\n * Used to determine if recursion is needed during repository hydration.\n * Excludes null, Arrays, and Dates.\n * @param value - The value to check.\n * @returns True if the value is a plain object.\n */\nexport function isPlainObject(\n value: unknown\n): value is Record<string, unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\n/**\n * Initialize a new procedure builder.\n * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).\n * @returns A new ProcedureBuilder instance.\n */\nexport function initProcedure<TRootContext>() {\n // Initial state: Root = Current, Input = unknown\n return new ProcedureBuilder<TRootContext, TRootContext, ZodAny>();\n}\n\nexport const parseSchema = <TInput extends z.ZodType>(\n schema: TInput,\n input: unknown\n) => {\n const parsed = schema.safeParse(input);\n\n if (!parsed.success) {\n throw new ModelError(400, {\n message: 'input-not-valid',\n });\n }\n\n return parsed.data;\n};\n\ntype ModelErrorOptions = {\n message?: string;\n};\n\nexport class ModelError extends Error {\n constructor(\n public readonly status: number,\n options?: ModelErrorOptions\n ) {\n super(options?.message);\n this.name = 'ModuleError';\n }\n}\n","import { ModelError } from '@/utils';\n\n/**\n * Middleware factory that adds a `drop` function to the context.\n * This function allows procedures to throw controlled HTTP errors with specific messages.\n * @template Messages - A map of error keys to their corresponding message strings.\n * @param dropMessages - An object mapping error keys to messages.\n * @returns A middleware function that injects the `drop` helper into the context.\n */\nexport const dropQuery =\n <Messages extends Record<string, string>>(dropMessages: Messages) =>\n <TContext>({ ctx }: { ctx: TContext }) => {\n const drop = <Key extends keyof Messages>(message: Key, code?: number) => {\n throw new ModelError(code ?? 400, {\n message: dropMessages[message],\n });\n };\n\n return { ...ctx, drop };\n };\n"],"mappings":";AAoEO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,QAChB,SACA;AACA,UAAM,SAAS,OAAO;AAHN;AAIhB,SAAK,OAAO;AAAA,EACd;AACF;;;ACnEO,IAAM,YACX,CAA0C,iBAC1C,CAAW,EAAE,IAAI,MAAyB;AACxC,QAAM,OAAO,CAA6B,SAAc,SAAkB;AACxE,UAAM,IAAI,WAAW,QAAQ,KAAK;AAAA,MAChC,SAAS,aAAa,OAAO;AAAA,IAC/B,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,GAAG,KAAK,KAAK;AACxB;","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/queryUtils/dropQuery.ts"],"sourcesContent":["import z, { ZodAny } from 'zod';\nimport { QUERY_MARKER } from './constant';\nimport { QueryBuilder } from './types';\nimport { ProcedureBuilder } from './ProcedureBuilder';\n\n/**\n * Type guard to check if a value is a valid `QueryBuilder`.\n * Used internally by the repository factory during hydration.\n * @param value - The value to check.\n * @returns True if the value is a QueryBuilder function.\n */\nexport function isQueryBuilder<TContext>(\n value: unknown\n): value is QueryBuilder<TContext, unknown, unknown> {\n return (\n typeof value === 'function' &&\n QUERY_MARKER in value &&\n (value as { [QUERY_MARKER]: boolean })[QUERY_MARKER] === true\n );\n}\n\n/**\n * Type guard to check if a value is a plain JavaScript object.\n * Used to determine if recursion is needed during repository hydration.\n * Excludes null, Arrays, and Dates.\n * @param value - The value to check.\n * @returns True if the value is a plain object.\n */\nexport function isPlainObject(\n value: unknown\n): value is Record<string, unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\n/**\n * Initialize a new procedure builder.\n * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).\n * @returns A new ProcedureBuilder instance.\n */\nexport function initProcedure<TRootContext>() {\n // Initial state: Root = Current, Input = unknown\n return new ProcedureBuilder<TRootContext, TRootContext, ZodAny>();\n}\n\nexport const parseSchema = <TInput extends z.ZodType>(\n schema: TInput,\n input: unknown\n) => {\n const parsed = schema.safeParse(input);\n\n if (!parsed.success) {\n throw new ModelError(400, {\n message: z.prettifyError(parsed.error),\n });\n }\n\n return parsed.data;\n};\n\ntype ModelErrorOptions = {\n message?: string;\n};\n\nexport class ModelError extends Error {\n constructor(\n public readonly status: number,\n options?: ModelErrorOptions\n ) {\n super(options?.message);\n this.name = 'ModuleError';\n }\n}\n","import { ModelError } from '@/utils';\n\n/**\n * Middleware factory that adds a `drop` function to the context.\n * This function allows procedures to throw controlled HTTP errors with specific messages.\n * @template Messages - A map of error keys to their corresponding message strings.\n * @param dropMessages - An object mapping error keys to messages.\n * @returns A middleware function that injects the `drop` helper into the context.\n */\nexport const dropQuery =\n <Messages extends Record<string, string>>(dropMessages: Messages) =>\n <TContext>({ ctx }: { ctx: TContext }) => {\n const drop = <Key extends keyof Messages>(message: Key, code?: number) => {\n throw new ModelError(code ?? 400, {\n message: dropMessages[message],\n });\n };\n\n return { ...ctx, drop };\n };\n"],"mappings":";AAAA,OAAO,OAAmB;AAoEnB,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,QAChB,SACA;AACA,UAAM,SAAS,OAAO;AAHN;AAIhB,SAAK,OAAO;AAAA,EACd;AACF;;;ACnEO,IAAM,YACX,CAA0C,iBAC1C,CAAW,EAAE,IAAI,MAAyB;AACxC,QAAM,OAAO,CAA6B,SAAc,SAAkB;AACxE,UAAM,IAAI,WAAW,QAAQ,KAAK;AAAA,MAChC,SAAS,aAAa,OAAO;AAAA,IAC/B,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,GAAG,KAAK,KAAK;AACxB;","names":[]}
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
6
6
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
7
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
8
 
9
+ // src/utils.ts
10
+ import z from "zod";
11
+
9
12
  // src/constant.ts
10
13
  var QUERY_MARKER = /* @__PURE__ */ Symbol("QUERY_BUILDER");
11
14
 
@@ -92,7 +95,7 @@ var parseSchema = (schema, input) => {
92
95
  const parsed = schema.safeParse(input);
93
96
  if (!parsed.success) {
94
97
  throw new ModelError(400, {
95
- message: "input-not-valid"
98
+ message: z.prettifyError(parsed.error)
96
99
  });
97
100
  }
98
101
  return parsed.data;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constant.ts","../src/ProcedureBuilder.ts","../src/utils.ts","../src/createModelFactory.ts"],"sourcesContent":["/**\n * A unique symbol used to identify `QueryBuilder` functions at runtime.\n * This prevents accidental execution of arbitrary functions found in the blueprint object.\n * @internal\n */\nexport const QUERY_MARKER = Symbol('QUERY_BUILDER');\n","import z from 'zod';\nimport type { ProcedureMiddleware, QueryBuilder } from './types';\nimport { QUERY_MARKER } from './constant';\nimport { parseSchema } from './utils';\n\n/**\n * The core builder class for creating type-safe database procedures.\n * Implements a fluent interface to chain validation, middleware, and handlers.\n * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).\n * @template TCurrentContext - The Context type at the current stage of the pipeline (modified by previous middlewares).\n * @template TInput - The validated input type (inferred from Zod).\n */\nexport class ProcedureBuilder<\n TRootContext,\n TCurrentContext,\n TInput extends z.ZodType,\n> {\n readonly #middlewares: ProcedureMiddleware[];\n readonly #schema: TInput | undefined;\n\n constructor(schema?: TInput, middlewares: ProcedureMiddleware[] = []) {\n this.#schema = schema;\n this.#middlewares = middlewares;\n }\n /**\n * Define input validation.\n * Resets TInput to the inferred Zod type.\n */\n public input<TSchema extends z.ZodType>(schema: TSchema) {\n return new ProcedureBuilder<TRootContext, TCurrentContext, TSchema>(\n schema,\n this.#middlewares\n );\n }\n\n /**\n * Add middleware.\n * Transforms TCurrentContext -> TNextContext.\n */\n public use<TNextContext>(\n middleware: (params: {\n ctx: TCurrentContext;\n input: z.infer<TInput>;\n }) => Promise<TNextContext> | TNextContext\n ) {\n // We cast the middleware to the internal unknown type to store it\n const storedMiddleware: ProcedureMiddleware = async (p) => {\n // Safe casting because the class generics enforce strict usage upstream\n return middleware({\n ctx: p.ctx as TCurrentContext,\n input: p.input as z.infer<TInput>,\n });\n };\n\n return new ProcedureBuilder<TRootContext, TNextContext, TInput>(\n this.#schema,\n [...this.#middlewares, storedMiddleware]\n );\n }\n\n /**\n * Finalize the query.\n * Returns a QueryBuilder that expects TRootContext.\n */\n public query<TResult>(\n handler: (params: {\n ctx: TCurrentContext;\n input: z.infer<TInput>;\n }) => Promise<TResult>\n ): QueryBuilder<TRootContext, z.infer<TInput>, TResult> {\n // The Factory-Compatible Builder\n const builder = (rootCtx: TRootContext) => {\n return async (rawInput: z.infer<TInput>): Promise<TResult> => {\n // A. Validation\n if (!this.#schema) {\n throw new Error('No schema provided for query');\n }\n const validatedInput = parseSchema(this.#schema, rawInput);\n\n // B. Middleware Pipeline\n // We start with the Root Context\n let ctxCursor: unknown = rootCtx;\n\n for (const mw of this.#middlewares) {\n ctxCursor = await mw({ ctx: ctxCursor, input: validatedInput });\n }\n\n // C. Handler\n return handler({\n ctx: ctxCursor as TCurrentContext,\n input: validatedInput,\n });\n };\n };\n\n // Attach Marker safely\n Object.defineProperty(builder, QUERY_MARKER, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n\n return builder as QueryBuilder<TRootContext, z.infer<TInput>, TResult>;\n }\n}\n","import z, { ZodAny } from 'zod';\nimport { QUERY_MARKER } from './constant';\nimport { QueryBuilder } from './types';\nimport { ProcedureBuilder } from './ProcedureBuilder';\n\n/**\n * Type guard to check if a value is a valid `QueryBuilder`.\n * Used internally by the repository factory during hydration.\n * @param value - The value to check.\n * @returns True if the value is a QueryBuilder function.\n */\nexport function isQueryBuilder<TContext>(\n value: unknown\n): value is QueryBuilder<TContext, unknown, unknown> {\n return (\n typeof value === 'function' &&\n QUERY_MARKER in value &&\n (value as { [QUERY_MARKER]: boolean })[QUERY_MARKER] === true\n );\n}\n\n/**\n * Type guard to check if a value is a plain JavaScript object.\n * Used to determine if recursion is needed during repository hydration.\n * Excludes null, Arrays, and Dates.\n * @param value - The value to check.\n * @returns True if the value is a plain object.\n */\nexport function isPlainObject(\n value: unknown\n): value is Record<string, unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\n/**\n * Initialize a new procedure builder.\n * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).\n * @returns A new ProcedureBuilder instance.\n */\nexport function initProcedure<TRootContext>() {\n // Initial state: Root = Current, Input = unknown\n return new ProcedureBuilder<TRootContext, TRootContext, ZodAny>();\n}\n\nexport const parseSchema = <TInput extends z.ZodType>(\n schema: TInput,\n input: unknown\n) => {\n const parsed = schema.safeParse(input);\n\n if (!parsed.success) {\n throw new ModelError(400, {\n message: 'input-not-valid',\n });\n }\n\n return parsed.data;\n};\n\ntype ModelErrorOptions = {\n message?: string;\n};\n\nexport class ModelError extends Error {\n constructor(\n public readonly status: number,\n options?: ModelErrorOptions\n ) {\n super(options?.message);\n this.name = 'ModuleError';\n }\n}\n","import type { HydratedModel } from './types';\nimport { isPlainObject, isQueryBuilder } from './utils';\n\n/**\n * Creates a Model Factory based on your API Blueprint.\n * @template TBlueprint - The structure of your API (nested objects containing QueryBuilders).\n * @param blueprint - The static object defining your API structure.\n * @returns A function `createModel(ctx)` that takes your Context and returns the fully typed API.\n * @example\n * const modelFactory = createModelFactory({\n * users: { get: userGetQuery }\n * });\n * const DB = modelFactory({ db: myDb });\n */\nexport function createModelFactory<TBlueprint extends Record<string, unknown>>(\n blueprint: TBlueprint\n) {\n /**\n * The actual factory function used at runtime per-request.\n * @param ctx - The dependency injection context (must match TRootContext of your builders).\n */\n return function createModel<TContext>(\n ctx: TContext\n ): HydratedModel<TBlueprint, TContext> {\n // Recursive function to walk the blueprint tree and inject context.\n const hydrate = (structure: Record<string, unknown>): unknown => {\n const result: Record<string, unknown> = {};\n\n for (const key in structure) {\n const value = structure[key];\n\n if (isQueryBuilder(value)) {\n // Safe execution via Type Guard\n // value is strictly QueryBuilder<unknown, unknown, unknown>\n result[key] = value(ctx);\n // Note: We perform one specific cast here because TypeScript\n // cannot correlate the TContext generic of the *specific* builder\n // with the TContext passed to the factory without excessive complexity.\n // However, the *Generics* on the class ensure safety for the user.\n } else if (isPlainObject(value)) {\n result[key] = hydrate(value);\n } else {\n result[key] = value;\n }\n }\n return result;\n };\n\n return hydrate(blueprint) as HydratedModel<TBlueprint, TContext>;\n };\n}\n"],"mappings":";;;;;;;;;AAKO,IAAM,eAAe,uBAAO,eAAe;;;ACLlD;AAYO,IAAM,oBAAN,MAAM,kBAIX;AAAA,EAIA,YAAY,QAAiB,cAAqC,CAAC,GAAG;AAHtE,uBAAS;AACT,uBAAS;AAGP,uBAAK,SAAU;AACf,uBAAK,cAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKO,MAAiC,QAAiB;AACvD,WAAO,IAAI;AAAA,MACT;AAAA,MACA,mBAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,IACL,YAIA;AAEA,UAAM,mBAAwC,OAAO,MAAM;AAEzD,aAAO,WAAW;AAAA,QAChB,KAAK,EAAE;AAAA,QACP,OAAO,EAAE;AAAA,MACX,CAAC;AAAA,IACH;AAEA,WAAO,IAAI;AAAA,MACT,mBAAK;AAAA,MACL,CAAC,GAAG,mBAAK,eAAc,gBAAgB;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,MACL,SAIsD;AAEtD,UAAM,UAAU,CAAC,YAA0B;AACzC,aAAO,OAAO,aAAgD;AAE5D,YAAI,CAAC,mBAAK,UAAS;AACjB,gBAAM,IAAI,MAAM,8BAA8B;AAAA,QAChD;AACA,cAAM,iBAAiB,YAAY,mBAAK,UAAS,QAAQ;AAIzD,YAAI,YAAqB;AAEzB,mBAAW,MAAM,mBAAK,eAAc;AAClC,sBAAY,MAAM,GAAG,EAAE,KAAK,WAAW,OAAO,eAAe,CAAC;AAAA,QAChE;AAGA,eAAO,QAAQ;AAAA,UACb,KAAK;AAAA,UACL,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAGA,WAAO,eAAe,SAAS,cAAc;AAAA,MAC3C,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAED,WAAO;AAAA,EACT;AACF;AAxFW;AACA;AANJ,IAAM,mBAAN;;;ACDA,SAAS,eACd,OACmD;AACnD,SACE,OAAO,UAAU,cACjB,gBAAgB,SACf,MAAsC,YAAY,MAAM;AAE7D;AASO,SAAS,cACd,OACkC;AAClC,SACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,EAAE,iBAAiB;AAEvB;AAOO,SAAS,gBAA8B;AAE5C,SAAO,IAAI,iBAAqD;AAClE;AAEO,IAAM,cAAc,CACzB,QACA,UACG;AACH,QAAM,SAAS,OAAO,UAAU,KAAK;AAErC,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,WAAW,KAAK;AAAA,MACxB,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAEA,SAAO,OAAO;AAChB;AAMO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,QAChB,SACA;AACA,UAAM,SAAS,OAAO;AAHN;AAIhB,SAAK,OAAO;AAAA,EACd;AACF;;;AC9DO,SAAS,mBACd,WACA;AAKA,SAAO,SAAS,YACd,KACqC;AAErC,UAAM,UAAU,CAAC,cAAgD;AAC/D,YAAM,SAAkC,CAAC;AAEzC,iBAAW,OAAO,WAAW;AAC3B,cAAM,QAAQ,UAAU,GAAG;AAE3B,YAAI,eAAe,KAAK,GAAG;AAGzB,iBAAO,GAAG,IAAI,MAAM,GAAG;AAAA,QAKzB,WAAW,cAAc,KAAK,GAAG;AAC/B,iBAAO,GAAG,IAAI,QAAQ,KAAK;AAAA,QAC7B,OAAO;AACL,iBAAO,GAAG,IAAI;AAAA,QAChB;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,WAAO,QAAQ,SAAS;AAAA,EAC1B;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/constant.ts","../src/ProcedureBuilder.ts","../src/createModelFactory.ts"],"sourcesContent":["import z, { ZodAny } from 'zod';\nimport { QUERY_MARKER } from './constant';\nimport { QueryBuilder } from './types';\nimport { ProcedureBuilder } from './ProcedureBuilder';\n\n/**\n * Type guard to check if a value is a valid `QueryBuilder`.\n * Used internally by the repository factory during hydration.\n * @param value - The value to check.\n * @returns True if the value is a QueryBuilder function.\n */\nexport function isQueryBuilder<TContext>(\n value: unknown\n): value is QueryBuilder<TContext, unknown, unknown> {\n return (\n typeof value === 'function' &&\n QUERY_MARKER in value &&\n (value as { [QUERY_MARKER]: boolean })[QUERY_MARKER] === true\n );\n}\n\n/**\n * Type guard to check if a value is a plain JavaScript object.\n * Used to determine if recursion is needed during repository hydration.\n * Excludes null, Arrays, and Dates.\n * @param value - The value to check.\n * @returns True if the value is a plain object.\n */\nexport function isPlainObject(\n value: unknown\n): value is Record<string, unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n !(value instanceof Date)\n );\n}\n\n/**\n * Initialize a new procedure builder.\n * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).\n * @returns A new ProcedureBuilder instance.\n */\nexport function initProcedure<TRootContext>() {\n // Initial state: Root = Current, Input = unknown\n return new ProcedureBuilder<TRootContext, TRootContext, ZodAny>();\n}\n\nexport const parseSchema = <TInput extends z.ZodType>(\n schema: TInput,\n input: unknown\n) => {\n const parsed = schema.safeParse(input);\n\n if (!parsed.success) {\n throw new ModelError(400, {\n message: z.prettifyError(parsed.error),\n });\n }\n\n return parsed.data;\n};\n\ntype ModelErrorOptions = {\n message?: string;\n};\n\nexport class ModelError extends Error {\n constructor(\n public readonly status: number,\n options?: ModelErrorOptions\n ) {\n super(options?.message);\n this.name = 'ModuleError';\n }\n}\n","/**\n * A unique symbol used to identify `QueryBuilder` functions at runtime.\n * This prevents accidental execution of arbitrary functions found in the blueprint object.\n * @internal\n */\nexport const QUERY_MARKER = Symbol('QUERY_BUILDER');\n","import z from 'zod';\nimport type { ProcedureMiddleware, QueryBuilder } from './types';\nimport { QUERY_MARKER } from './constant';\nimport { parseSchema } from './utils';\n\n/**\n * The core builder class for creating type-safe database procedures.\n * Implements a fluent interface to chain validation, middleware, and handlers.\n * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).\n * @template TCurrentContext - The Context type at the current stage of the pipeline (modified by previous middlewares).\n * @template TInput - The validated input type (inferred from Zod).\n */\nexport class ProcedureBuilder<\n TRootContext,\n TCurrentContext,\n TInput extends z.ZodType,\n> {\n readonly #middlewares: ProcedureMiddleware[];\n readonly #schema: TInput | undefined;\n\n constructor(schema?: TInput, middlewares: ProcedureMiddleware[] = []) {\n this.#schema = schema;\n this.#middlewares = middlewares;\n }\n /**\n * Define input validation.\n * Resets TInput to the inferred Zod type.\n */\n public input<TSchema extends z.ZodType>(schema: TSchema) {\n return new ProcedureBuilder<TRootContext, TCurrentContext, TSchema>(\n schema,\n this.#middlewares\n );\n }\n\n /**\n * Add middleware.\n * Transforms TCurrentContext -> TNextContext.\n */\n public use<TNextContext>(\n middleware: (params: {\n ctx: TCurrentContext;\n input: z.infer<TInput>;\n }) => Promise<TNextContext> | TNextContext\n ) {\n // We cast the middleware to the internal unknown type to store it\n const storedMiddleware: ProcedureMiddleware = async (p) => {\n // Safe casting because the class generics enforce strict usage upstream\n return middleware({\n ctx: p.ctx as TCurrentContext,\n input: p.input as z.infer<TInput>,\n });\n };\n\n return new ProcedureBuilder<TRootContext, TNextContext, TInput>(\n this.#schema,\n [...this.#middlewares, storedMiddleware]\n );\n }\n\n /**\n * Finalize the query.\n * Returns a QueryBuilder that expects TRootContext.\n */\n public query<TResult>(\n handler: (params: {\n ctx: TCurrentContext;\n input: z.infer<TInput>;\n }) => Promise<TResult>\n ): QueryBuilder<TRootContext, z.infer<TInput>, TResult> {\n // The Factory-Compatible Builder\n const builder = (rootCtx: TRootContext) => {\n return async (rawInput: z.infer<TInput>): Promise<TResult> => {\n // A. Validation\n if (!this.#schema) {\n throw new Error('No schema provided for query');\n }\n const validatedInput = parseSchema(this.#schema, rawInput);\n\n // B. Middleware Pipeline\n // We start with the Root Context\n let ctxCursor: unknown = rootCtx;\n\n for (const mw of this.#middlewares) {\n ctxCursor = await mw({ ctx: ctxCursor, input: validatedInput });\n }\n\n // C. Handler\n return handler({\n ctx: ctxCursor as TCurrentContext,\n input: validatedInput,\n });\n };\n };\n\n // Attach Marker safely\n Object.defineProperty(builder, QUERY_MARKER, {\n value: true,\n writable: false,\n enumerable: false,\n configurable: false,\n });\n\n return builder as QueryBuilder<TRootContext, z.infer<TInput>, TResult>;\n }\n}\n","import type { HydratedModel } from './types';\nimport { isPlainObject, isQueryBuilder } from './utils';\n\n/**\n * Creates a Model Factory based on your API Blueprint.\n * @template TBlueprint - The structure of your API (nested objects containing QueryBuilders).\n * @param blueprint - The static object defining your API structure.\n * @returns A function `createModel(ctx)` that takes your Context and returns the fully typed API.\n * @example\n * const modelFactory = createModelFactory({\n * users: { get: userGetQuery }\n * });\n * const DB = modelFactory({ db: myDb });\n */\nexport function createModelFactory<TBlueprint extends Record<string, unknown>>(\n blueprint: TBlueprint\n) {\n /**\n * The actual factory function used at runtime per-request.\n * @param ctx - The dependency injection context (must match TRootContext of your builders).\n */\n return function createModel<TContext>(\n ctx: TContext\n ): HydratedModel<TBlueprint, TContext> {\n // Recursive function to walk the blueprint tree and inject context.\n const hydrate = (structure: Record<string, unknown>): unknown => {\n const result: Record<string, unknown> = {};\n\n for (const key in structure) {\n const value = structure[key];\n\n if (isQueryBuilder(value)) {\n // Safe execution via Type Guard\n // value is strictly QueryBuilder<unknown, unknown, unknown>\n result[key] = value(ctx);\n // Note: We perform one specific cast here because TypeScript\n // cannot correlate the TContext generic of the *specific* builder\n // with the TContext passed to the factory without excessive complexity.\n // However, the *Generics* on the class ensure safety for the user.\n } else if (isPlainObject(value)) {\n result[key] = hydrate(value);\n } else {\n result[key] = value;\n }\n }\n return result;\n };\n\n return hydrate(blueprint) as HydratedModel<TBlueprint, TContext>;\n };\n}\n"],"mappings":";;;;;;;;;AAAA,OAAO,OAAmB;;;ACKnB,IAAM,eAAe,uBAAO,eAAe;;;ACLlD;AAYO,IAAM,oBAAN,MAAM,kBAIX;AAAA,EAIA,YAAY,QAAiB,cAAqC,CAAC,GAAG;AAHtE,uBAAS;AACT,uBAAS;AAGP,uBAAK,SAAU;AACf,uBAAK,cAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKO,MAAiC,QAAiB;AACvD,WAAO,IAAI;AAAA,MACT;AAAA,MACA,mBAAK;AAAA,IACP;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,IACL,YAIA;AAEA,UAAM,mBAAwC,OAAO,MAAM;AAEzD,aAAO,WAAW;AAAA,QAChB,KAAK,EAAE;AAAA,QACP,OAAO,EAAE;AAAA,MACX,CAAC;AAAA,IACH;AAEA,WAAO,IAAI;AAAA,MACT,mBAAK;AAAA,MACL,CAAC,GAAG,mBAAK,eAAc,gBAAgB;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,MACL,SAIsD;AAEtD,UAAM,UAAU,CAAC,YAA0B;AACzC,aAAO,OAAO,aAAgD;AAE5D,YAAI,CAAC,mBAAK,UAAS;AACjB,gBAAM,IAAI,MAAM,8BAA8B;AAAA,QAChD;AACA,cAAM,iBAAiB,YAAY,mBAAK,UAAS,QAAQ;AAIzD,YAAI,YAAqB;AAEzB,mBAAW,MAAM,mBAAK,eAAc;AAClC,sBAAY,MAAM,GAAG,EAAE,KAAK,WAAW,OAAO,eAAe,CAAC;AAAA,QAChE;AAGA,eAAO,QAAQ;AAAA,UACb,KAAK;AAAA,UACL,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAGA,WAAO,eAAe,SAAS,cAAc;AAAA,MAC3C,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,cAAc;AAAA,IAChB,CAAC;AAED,WAAO;AAAA,EACT;AACF;AAxFW;AACA;AANJ,IAAM,mBAAN;;;AFDA,SAAS,eACd,OACmD;AACnD,SACE,OAAO,UAAU,cACjB,gBAAgB,SACf,MAAsC,YAAY,MAAM;AAE7D;AASO,SAAS,cACd,OACkC;AAClC,SACE,OAAO,UAAU,YACjB,UAAU,QACV,CAAC,MAAM,QAAQ,KAAK,KACpB,EAAE,iBAAiB;AAEvB;AAOO,SAAS,gBAA8B;AAE5C,SAAO,IAAI,iBAAqD;AAClE;AAEO,IAAM,cAAc,CACzB,QACA,UACG;AACH,QAAM,SAAS,OAAO,UAAU,KAAK;AAErC,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,WAAW,KAAK;AAAA,MACxB,SAAS,EAAE,cAAc,OAAO,KAAK;AAAA,IACvC,CAAC;AAAA,EACH;AAEA,SAAO,OAAO;AAChB;AAMO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACkB,QAChB,SACA;AACA,UAAM,SAAS,OAAO;AAHN;AAIhB,SAAK,OAAO;AAAA,EACd;AACF;;;AG9DO,SAAS,mBACd,WACA;AAKA,SAAO,SAAS,YACd,KACqC;AAErC,UAAM,UAAU,CAAC,cAAgD;AAC/D,YAAM,SAAkC,CAAC;AAEzC,iBAAW,OAAO,WAAW;AAC3B,cAAM,QAAQ,UAAU,GAAG;AAE3B,YAAI,eAAe,KAAK,GAAG;AAGzB,iBAAO,GAAG,IAAI,MAAM,GAAG;AAAA,QAKzB,WAAW,cAAc,KAAK,GAAG;AAC/B,iBAAO,GAAG,IAAI,QAAQ,KAAK;AAAA,QAC7B,OAAO;AACL,iBAAO,GAAG,IAAI;AAAA,QAChB;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAEA,WAAO,QAAQ,SAAS;AAAA,EAC1B;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "model-blueprint",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",