model-blueprint 0.1.0 → 0.1.2
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/dropQuery.d.ts +1 -3
- package/dist/dropQuery/dropQuery.js +6 -34
- package/dist/dropQuery/dropQuery.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +9 -38
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { ClientErrorStatusCode } from 'hono/utils/http-status';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Middleware factory that adds a `drop` function to the context.
|
|
5
3
|
* This function allows procedures to throw controlled HTTP errors with specific messages.
|
|
@@ -10,7 +8,7 @@ import { ClientErrorStatusCode } from 'hono/utils/http-status';
|
|
|
10
8
|
declare const dropQuery: <Messages extends Record<string, string>>(dropMessages: Messages) => <TContext>({ ctx }: {
|
|
11
9
|
ctx: TContext;
|
|
12
10
|
}) => TContext & {
|
|
13
|
-
drop: <Key extends keyof Messages>(message: Key, code?:
|
|
11
|
+
drop: <Key extends keyof Messages>(message: Key, code?: number) => never;
|
|
14
12
|
};
|
|
15
13
|
|
|
16
14
|
export { dropQuery };
|
|
@@ -1,44 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// node_modules/.pnpm/hono@4.11.7/node_modules/hono/dist/http-exception.js
|
|
6
|
-
var HTTPException = class extends Error {
|
|
7
|
-
/**
|
|
8
|
-
* Creates an instance of `HTTPException`.
|
|
9
|
-
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
10
|
-
* @param options - Additional options for the exception.
|
|
11
|
-
*/
|
|
12
|
-
constructor(status = 500, options) {
|
|
13
|
-
super(options?.message, { cause: options?.cause });
|
|
14
|
-
__publicField(this, "res");
|
|
15
|
-
__publicField(this, "status");
|
|
16
|
-
this.res = options?.res;
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
var ModelError = class extends Error {
|
|
3
|
+
constructor(status, options) {
|
|
4
|
+
super(options?.message);
|
|
17
5
|
this.status = status;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Returns the response object associated with the exception.
|
|
21
|
-
* If a response object is not provided, a new response is created with the error message and status code.
|
|
22
|
-
* @returns The response object.
|
|
23
|
-
*/
|
|
24
|
-
getResponse() {
|
|
25
|
-
if (this.res) {
|
|
26
|
-
const newResponse = new Response(this.res.body, {
|
|
27
|
-
status: this.status,
|
|
28
|
-
headers: this.res.headers
|
|
29
|
-
});
|
|
30
|
-
return newResponse;
|
|
31
|
-
}
|
|
32
|
-
return new Response(this.message, {
|
|
33
|
-
status: this.status
|
|
34
|
-
});
|
|
6
|
+
this.name = "ModuleError";
|
|
35
7
|
}
|
|
36
8
|
};
|
|
37
9
|
|
|
38
10
|
// src/queryUtils/dropQuery.ts
|
|
39
11
|
var dropQuery = (dropMessages) => ({ ctx }) => {
|
|
40
12
|
const drop = (message, code) => {
|
|
41
|
-
throw new
|
|
13
|
+
throw new ModelError(code ?? 400, {
|
|
42
14
|
message: dropMessages[message]
|
|
43
15
|
});
|
|
44
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../
|
|
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":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -96,5 +96,12 @@ declare class ProcedureBuilder<TRootContext, TCurrentContext, TInput extends z.Z
|
|
|
96
96
|
* @returns A new ProcedureBuilder instance.
|
|
97
97
|
*/
|
|
98
98
|
declare function initProcedure<TRootContext>(): ProcedureBuilder<TRootContext, TRootContext, z.ZodAny>;
|
|
99
|
+
type ModelErrorOptions = {
|
|
100
|
+
message?: string;
|
|
101
|
+
};
|
|
102
|
+
declare class ModelError extends Error {
|
|
103
|
+
readonly status: number;
|
|
104
|
+
constructor(status: number, options?: ModelErrorOptions);
|
|
105
|
+
}
|
|
99
106
|
|
|
100
|
-
export { type HydratedModel, createModelFactory, initProcedure };
|
|
107
|
+
export { type HydratedModel, ModelError, createModelFactory, initProcedure };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
-
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
|
|
5
1
|
// src/constant.ts
|
|
6
2
|
var QUERY_MARKER = /* @__PURE__ */ Symbol("QUERY_BUILDER");
|
|
7
3
|
|
|
@@ -68,39 +64,6 @@ var ProcedureBuilder = class _ProcedureBuilder {
|
|
|
68
64
|
}
|
|
69
65
|
};
|
|
70
66
|
|
|
71
|
-
// node_modules/.pnpm/hono@4.11.7/node_modules/hono/dist/http-exception.js
|
|
72
|
-
var HTTPException = class extends Error {
|
|
73
|
-
/**
|
|
74
|
-
* Creates an instance of `HTTPException`.
|
|
75
|
-
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
76
|
-
* @param options - Additional options for the exception.
|
|
77
|
-
*/
|
|
78
|
-
constructor(status = 500, options) {
|
|
79
|
-
super(options?.message, { cause: options?.cause });
|
|
80
|
-
__publicField(this, "res");
|
|
81
|
-
__publicField(this, "status");
|
|
82
|
-
this.res = options?.res;
|
|
83
|
-
this.status = status;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Returns the response object associated with the exception.
|
|
87
|
-
* If a response object is not provided, a new response is created with the error message and status code.
|
|
88
|
-
* @returns The response object.
|
|
89
|
-
*/
|
|
90
|
-
getResponse() {
|
|
91
|
-
if (this.res) {
|
|
92
|
-
const newResponse = new Response(this.res.body, {
|
|
93
|
-
status: this.status,
|
|
94
|
-
headers: this.res.headers
|
|
95
|
-
});
|
|
96
|
-
return newResponse;
|
|
97
|
-
}
|
|
98
|
-
return new Response(this.message, {
|
|
99
|
-
status: this.status
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
67
|
// src/utils.ts
|
|
105
68
|
function isQueryBuilder(value) {
|
|
106
69
|
return typeof value === "function" && QUERY_MARKER in value && value[QUERY_MARKER] === true;
|
|
@@ -114,12 +77,19 @@ function initProcedure() {
|
|
|
114
77
|
var parseSchema = (schema, input) => {
|
|
115
78
|
const parsed = schema.safeParse(input);
|
|
116
79
|
if (!parsed.success) {
|
|
117
|
-
throw new
|
|
80
|
+
throw new ModelError(400, {
|
|
118
81
|
message: "input-not-valid"
|
|
119
82
|
});
|
|
120
83
|
}
|
|
121
84
|
return parsed.data;
|
|
122
85
|
};
|
|
86
|
+
var ModelError = class extends Error {
|
|
87
|
+
constructor(status, options) {
|
|
88
|
+
super(options?.message);
|
|
89
|
+
this.status = status;
|
|
90
|
+
this.name = "ModuleError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
123
93
|
|
|
124
94
|
// src/createModelFactory.ts
|
|
125
95
|
function createModelFactory(blueprint) {
|
|
@@ -142,6 +112,7 @@ function createModelFactory(blueprint) {
|
|
|
142
112
|
};
|
|
143
113
|
}
|
|
144
114
|
export {
|
|
115
|
+
ModelError,
|
|
145
116
|
createModelFactory,
|
|
146
117
|
initProcedure
|
|
147
118
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constant.ts","../src/ProcedureBuilder.ts","../node_modules/.pnpm/hono@4.11.7/node_modules/hono/dist/http-exception.js","../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 private readonly _middlewares: ProcedureMiddleware[];\n private 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","// src/http-exception.ts\nvar HTTPException = class extends Error {\n res;\n status;\n /**\n * Creates an instance of `HTTPException`.\n * @param status - HTTP status code for the exception. Defaults to 500.\n * @param options - Additional options for the exception.\n */\n constructor(status = 500, options) {\n super(options?.message, { cause: options?.cause });\n this.res = options?.res;\n this.status = status;\n }\n /**\n * Returns the response object associated with the exception.\n * If a response object is not provided, a new response is created with the error message and status code.\n * @returns The response object.\n */\n getResponse() {\n if (this.res) {\n const newResponse = new Response(this.res.body, {\n status: this.status,\n headers: this.res.headers\n });\n return newResponse;\n }\n return new Response(this.message, {\n status: this.status\n });\n }\n};\nexport {\n HTTPException\n};\n","import z, { ZodAny } from 'zod';\nimport { QUERY_MARKER } from './constant';\nimport { QueryBuilder } from './types';\nimport { ProcedureBuilder } from './ProcedureBuilder';\nimport { HTTPException } from 'hono/http-exception';\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 HTTPException(400, {\n message: 'input-not-valid',\n });\n }\n\n return parsed.data;\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":"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","names":[]}
|
|
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 private readonly _middlewares: ProcedureMiddleware[];\n private 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;;;ACO3C,IAAM,mBAAN,MAAM,kBAIX;AAAA,EAIA,YAAY,QAAiB,cAAqC,CAAC,GAAG;AACpE,SAAK,UAAU;AACf,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKO,MAAiC,QAAiB;AACvD,WAAO,IAAI;AAAA,MACT;AAAA,MACA,KAAK;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,KAAK;AAAA,MACL,CAAC,GAAG,KAAK,cAAc,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,KAAK,SAAS;AACjB,gBAAM,IAAI,MAAM,8BAA8B;AAAA,QAChD;AACA,cAAM,iBAAiB,YAAY,KAAK,SAAS,QAAQ;AAIzD,YAAI,YAAqB;AAEzB,mBAAW,MAAM,KAAK,cAAc;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;;;AC9FO,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":[]}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "model-blueprint",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
},
|
|
8
12
|
"./withCursor": {
|
|
9
13
|
"import": "./dist/withCursor/index.js",
|
|
10
14
|
"types": "./dist/withCursor/index.d.ts"
|
|
@@ -54,10 +58,8 @@
|
|
|
54
58
|
},
|
|
55
59
|
"devDependencies": {
|
|
56
60
|
"@paralleldrive/cuid2": "^3.3.0",
|
|
57
|
-
"hono": "^4.11.7",
|
|
58
61
|
"@eslint/js": "^9.0.0",
|
|
59
62
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
60
|
-
"@cloudflare/workers-types": "^4.20260131.0",
|
|
61
63
|
"@typescript-eslint/parser": "^8.0.0",
|
|
62
64
|
"@vitest/coverage-v8": "^4.0.18",
|
|
63
65
|
"eslint": "^9.0.0",
|