model-blueprint 0.1.4 → 0.2.0

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.
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ const cuid2 = require('@paralleldrive/cuid2');
4
+
5
+ const createId = ({ ctx }) => {
6
+ return { ...ctx, createId: cuid2.createId, init: cuid2.init };
7
+ };
8
+
9
+ exports.createId = createId;
10
+ //# sourceMappingURL=createId.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createId.cjs","sources":["../src/queryUtils/createId.ts"],"sourcesContent":["import {\n createId as createCuid2Id,\n init as initCuid2,\n} from '@paralleldrive/cuid2';\n\n/**\n * Middleware factory that adds a `createId` and `init` function to the context.\n * This function allows procedures to create CUID2 IDs and initialize CUID2.\n * @template TContext - The context type.\n * @param ctx - The context object.\n * @returns A middleware function that injects the `createId` and `init` helpers into the context.\n */\nexport const createId = <TContext>({ ctx }: { ctx: TContext }) => {\n return { ...ctx, createId: createCuid2Id, init: initCuid2 };\n};\n"],"names":["createCuid2Id","initCuid2"],"mappings":";;;;AAYO,MAAM,QAAA,GAAW,CAAW,EAAE,GAAA,EAAI,KAAyB;AAChE,EAAA,OAAO,EAAE,GAAG,GAAA,EAAK,QAAA,EAAUA,cAAA,EAAe,MAAMC,UAAA,EAAU;AAC5D;;;;"}
@@ -0,0 +1,17 @@
1
+ import { createId as createId$1, init } from '@paralleldrive/cuid2';
2
+
3
+ /**
4
+ * Middleware factory that adds a `createId` and `init` function to the context.
5
+ * This function allows procedures to create CUID2 IDs and initialize CUID2.
6
+ * @template TContext - The context type.
7
+ * @param ctx - The context object.
8
+ * @returns A middleware function that injects the `createId` and `init` helpers into the context.
9
+ */
10
+ declare const createId: <TContext>({ ctx }: {
11
+ ctx: TContext;
12
+ }) => TContext & {
13
+ createId: typeof createId$1;
14
+ init: typeof init;
15
+ };
16
+
17
+ export { createId };
@@ -0,0 +1,17 @@
1
+ import { createId as createId$1, init } from '@paralleldrive/cuid2';
2
+
3
+ /**
4
+ * Middleware factory that adds a `createId` and `init` function to the context.
5
+ * This function allows procedures to create CUID2 IDs and initialize CUID2.
6
+ * @template TContext - The context type.
7
+ * @param ctx - The context object.
8
+ * @returns A middleware function that injects the `createId` and `init` helpers into the context.
9
+ */
10
+ declare const createId: <TContext>({ ctx }: {
11
+ ctx: TContext;
12
+ }) => TContext & {
13
+ createId: typeof createId$1;
14
+ init: typeof init;
15
+ };
16
+
17
+ export { createId };
@@ -0,0 +1,8 @@
1
+ import { init, createId as createId$1 } from '@paralleldrive/cuid2';
2
+
3
+ const createId = ({ ctx }) => {
4
+ return { ...ctx, createId: createId$1, init: init };
5
+ };
6
+
7
+ export { createId };
8
+ //# sourceMappingURL=createId.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createId.mjs","sources":["../src/queryUtils/createId.ts"],"sourcesContent":["import {\n createId as createCuid2Id,\n init as initCuid2,\n} from '@paralleldrive/cuid2';\n\n/**\n * Middleware factory that adds a `createId` and `init` function to the context.\n * This function allows procedures to create CUID2 IDs and initialize CUID2.\n * @template TContext - The context type.\n * @param ctx - The context object.\n * @returns A middleware function that injects the `createId` and `init` helpers into the context.\n */\nexport const createId = <TContext>({ ctx }: { ctx: TContext }) => {\n return { ...ctx, createId: createCuid2Id, init: initCuid2 };\n};\n"],"names":["createCuid2Id","initCuid2"],"mappings":";;AAYO,MAAM,QAAA,GAAW,CAAW,EAAE,GAAA,EAAI,KAAyB;AAChE,EAAA,OAAO,EAAE,GAAG,GAAA,EAAK,QAAA,EAAUA,UAAA,EAAe,MAAMC,IAAA,EAAU;AAC5D;;;;"}
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const utils = require('./shared/model-blueprint.D9hykuhu.cjs');
4
+ require('zod');
5
+
6
+ const dropQuery = (dropMessages) => ({ ctx }) => {
7
+ const drop = (message, code) => {
8
+ throw new utils.ModelError(code ?? 400, {
9
+ message: dropMessages[message]
10
+ });
11
+ };
12
+ return { ...ctx, drop };
13
+ };
14
+
15
+ exports.dropQuery = dropQuery;
16
+ //# sourceMappingURL=dropQuery.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropQuery.cjs","sources":["../src/queryUtils/dropQuery.ts"],"sourcesContent":["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"],"names":["ModelError"],"mappings":";;;;;AASO,MAAM,YACX,CAA0C,YAAA,KAC1C,CAAW,EAAE,KAAI,KAAyB;AACxC,EAAA,MAAM,IAAA,GAAO,CAA6B,OAAA,EAAc,IAAA,KAAkB;AACxE,IAAA,MAAM,IAAIA,gBAAA,CAAW,IAAA,IAAQ,GAAA,EAAK;AAAA,MAChC,OAAA,EAAS,aAAa,OAAO;AAAA,KAC9B,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,OAAO,EAAE,GAAG,GAAA,EAAK,IAAA,EAAK;AACxB;;;;"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Middleware factory that adds a `drop` function to the context.
3
+ * This function allows procedures to throw controlled HTTP errors with specific messages.
4
+ * @template Messages - A map of error keys to their corresponding message strings.
5
+ * @param dropMessages - An object mapping error keys to messages.
6
+ * @returns A middleware function that injects the `drop` helper into the context.
7
+ */
8
+ declare const dropQuery: <Messages extends Record<string, string>>(dropMessages: Messages) => <TContext>({ ctx }: {
9
+ ctx: TContext;
10
+ }) => TContext & {
11
+ drop: <Key extends keyof Messages>(message: Key, code?: number) => never;
12
+ };
13
+
14
+ export { dropQuery };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Middleware factory that adds a `drop` function to the context.
3
+ * This function allows procedures to throw controlled HTTP errors with specific messages.
4
+ * @template Messages - A map of error keys to their corresponding message strings.
5
+ * @param dropMessages - An object mapping error keys to messages.
6
+ * @returns A middleware function that injects the `drop` helper into the context.
7
+ */
8
+ declare const dropQuery: <Messages extends Record<string, string>>(dropMessages: Messages) => <TContext>({ ctx }: {
9
+ ctx: TContext;
10
+ }) => TContext & {
11
+ drop: <Key extends keyof Messages>(message: Key, code?: number) => never;
12
+ };
13
+
14
+ export { dropQuery };
@@ -0,0 +1,14 @@
1
+ import { M as ModelError } from './shared/model-blueprint.JM5S8yAe.mjs';
2
+ import 'zod';
3
+
4
+ const dropQuery = (dropMessages) => ({ ctx }) => {
5
+ const drop = (message, code) => {
6
+ throw new ModelError(code ?? 400, {
7
+ message: dropMessages[message]
8
+ });
9
+ };
10
+ return { ...ctx, drop };
11
+ };
12
+
13
+ export { dropQuery };
14
+ //# sourceMappingURL=dropQuery.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dropQuery.mjs","sources":["../src/queryUtils/dropQuery.ts"],"sourcesContent":["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"],"names":[],"mappings":";;;AASO,MAAM,YACX,CAA0C,YAAA,KAC1C,CAAW,EAAE,KAAI,KAAyB;AACxC,EAAA,MAAM,IAAA,GAAO,CAA6B,OAAA,EAAc,IAAA,KAAkB;AACxE,IAAA,MAAM,IAAI,UAAA,CAAW,IAAA,IAAQ,GAAA,EAAK;AAAA,MAChC,OAAA,EAAS,aAAa,OAAO;AAAA,KAC9B,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,OAAO,EAAE,GAAG,GAAA,EAAK,IAAA,EAAK;AACxB;;;;"}
package/dist/index.cjs ADDED
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ const utils = require('./shared/model-blueprint.D9hykuhu.cjs');
4
+ require('zod');
5
+
6
+ function createModelFactory(blueprint) {
7
+ return function createModel(ctx) {
8
+ const hydrate = (structure) => {
9
+ const result = {};
10
+ for (const key in structure) {
11
+ const value = structure[key];
12
+ if (utils.isQueryBuilder(value)) {
13
+ result[key] = value(ctx);
14
+ } else if (utils.isPlainObject(value)) {
15
+ result[key] = hydrate(value);
16
+ } else {
17
+ result[key] = value;
18
+ }
19
+ }
20
+ return result;
21
+ };
22
+ return hydrate(blueprint);
23
+ };
24
+ }
25
+
26
+ exports.ModelError = utils.ModelError;
27
+ exports.initProcedure = utils.initProcedure;
28
+ exports.createModelFactory = createModelFactory;
29
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/createModelFactory.ts"],"sourcesContent":["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"],"names":["isQueryBuilder","isPlainObject"],"mappings":";;;;;AAcO,SAAS,mBACd,SAAA,EACA;AAKA,EAAA,OAAO,SAAS,YACd,GAAA,EACqC;AAErC,IAAA,MAAM,OAAA,GAAU,CAAC,SAAA,KAAgD;AAC/D,MAAA,MAAM,SAAkC,EAAC;AAEzC,MAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC3B,QAAA,MAAM,KAAA,GAAQ,UAAU,GAAG,CAAA;AAE3B,QAAA,IAAIA,oBAAA,CAAe,KAAK,CAAA,EAAG;AAGzB,UAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA,CAAM,GAAG,CAAA;AAAA,QAKzB,CAAA,MAAA,IAAWC,mBAAA,CAAc,KAAK,CAAA,EAAG;AAC/B,UAAA,MAAA,CAAO,GAAG,CAAA,GAAI,OAAA,CAAQ,KAAK,CAAA;AAAA,QAC7B,CAAA,MAAO;AACL,UAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,QAChB;AAAA,MACF;AACA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,QAAQ,SAAS,CAAA;AAAA,EAC1B,CAAA;AACF;;;;;;"}
@@ -0,0 +1,99 @@
1
+ import z from 'zod';
2
+
3
+ /**
4
+ * Represents a compiled query function that is waiting for a Context to be injected.
5
+ * @template TContext - The type of the context object (e.g., DB connection, User session) this query requires.
6
+ * @template TInput - The Zod-inferred type of the input arguments.
7
+ * @template TOutput - The return type of the query handler.
8
+ */
9
+ type QueryBuilder<TContext, TInput, TOutput> = {
10
+ /**
11
+ * Accepts the Context and returns the executable function.
12
+ * @param ctx - The dependency injection context.
13
+ */
14
+ (ctx: TContext): (input: TInput) => Promise<TOutput>;
15
+ };
16
+ /**
17
+ * A recursive type that transforms a Blueprint definition into a usable API client.
18
+ * - Converts `QueryBuilder<Context, Input, Output>` -> `(input: Input) => Promise<Output>`
19
+ * - Recursively traverses nested objects.
20
+ * - Passes through primitives (like string constants) unchanged.
21
+ */
22
+ type HydratedModel<TBlueprint, TContext> = {
23
+ [K in keyof TBlueprint]: TBlueprint[K] extends QueryBuilder<TContext, infer I, infer O> ? (input: I) => Promise<O> : TBlueprint[K] extends object ? HydratedModel<TBlueprint[K], TContext> : TBlueprint[K];
24
+ };
25
+ /**
26
+ * A generic function type for internal middleware storage.
27
+ * Uses `unknown` to allow storage of diverse middleware chains in the same array.
28
+ */
29
+ type ProcedureMiddleware = (params: {
30
+ ctx: unknown;
31
+ input: unknown;
32
+ }) => Promise<unknown> | unknown;
33
+ type GenericProcedureMiddleware<Context> = (params: {
34
+ ctx: Context;
35
+ input: unknown;
36
+ }) => Promise<unknown> | unknown;
37
+
38
+ /**
39
+ * Creates a Model Factory based on your API Blueprint.
40
+ * @template TBlueprint - The structure of your API (nested objects containing QueryBuilders).
41
+ * @param blueprint - The static object defining your API structure.
42
+ * @returns A function `createModel(ctx)` that takes your Context and returns the fully typed API.
43
+ * @example
44
+ * const modelFactory = createModelFactory({
45
+ * users: { get: userGetQuery }
46
+ * });
47
+ * const DB = modelFactory({ db: myDb });
48
+ */
49
+ declare function createModelFactory<TBlueprint extends Record<string, unknown>>(blueprint: TBlueprint): <TContext>(ctx: TContext) => HydratedModel<TBlueprint, TContext>;
50
+
51
+ /**
52
+ * The core builder class for creating type-safe database procedures.
53
+ * Implements a fluent interface to chain validation, middleware, and handlers.
54
+ * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).
55
+ * @template TCurrentContext - The Context type at the current stage of the pipeline (modified by previous middlewares).
56
+ * @template TInput - The validated input type (inferred from Zod).
57
+ */
58
+ declare class ProcedureBuilder<TRootContext, TCurrentContext, TInput extends z.ZodType> {
59
+ #private;
60
+ constructor(schema?: TInput, middlewares?: ProcedureMiddleware[]);
61
+ /**
62
+ * Define input validation.
63
+ * Resets TInput to the inferred Zod type.
64
+ */
65
+ input<TSchema extends z.ZodType>(schema: TSchema): ProcedureBuilder<TRootContext, TCurrentContext, TSchema>;
66
+ /**
67
+ * Add middleware.
68
+ * Transforms TCurrentContext -> TNextContext.
69
+ */
70
+ use<TNextContext>(middleware: (params: {
71
+ ctx: TCurrentContext;
72
+ input: z.infer<TInput>;
73
+ }) => Promise<TNextContext> | TNextContext): ProcedureBuilder<TRootContext, TNextContext, TInput>;
74
+ /**
75
+ * Finalize the query.
76
+ * Returns a QueryBuilder that expects TRootContext.
77
+ */
78
+ query<TResult>(handler: (params: {
79
+ ctx: TCurrentContext;
80
+ input: z.infer<TInput>;
81
+ }) => Promise<TResult>): QueryBuilder<TRootContext, z.infer<TInput>, TResult>;
82
+ }
83
+
84
+ /**
85
+ * Initialize a new procedure builder.
86
+ * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).
87
+ * @returns A new ProcedureBuilder instance.
88
+ */
89
+ declare function initProcedure<TRootContext>(): ProcedureBuilder<TRootContext, TRootContext, z.ZodAny>;
90
+ type ModelErrorOptions = {
91
+ message?: string;
92
+ };
93
+ declare class ModelError extends Error {
94
+ readonly status: number;
95
+ constructor(status: number, options?: ModelErrorOptions);
96
+ }
97
+
98
+ export { ModelError, createModelFactory, initProcedure };
99
+ export type { GenericProcedureMiddleware, HydratedModel };
@@ -0,0 +1,99 @@
1
+ import z from 'zod';
2
+
3
+ /**
4
+ * Represents a compiled query function that is waiting for a Context to be injected.
5
+ * @template TContext - The type of the context object (e.g., DB connection, User session) this query requires.
6
+ * @template TInput - The Zod-inferred type of the input arguments.
7
+ * @template TOutput - The return type of the query handler.
8
+ */
9
+ type QueryBuilder<TContext, TInput, TOutput> = {
10
+ /**
11
+ * Accepts the Context and returns the executable function.
12
+ * @param ctx - The dependency injection context.
13
+ */
14
+ (ctx: TContext): (input: TInput) => Promise<TOutput>;
15
+ };
16
+ /**
17
+ * A recursive type that transforms a Blueprint definition into a usable API client.
18
+ * - Converts `QueryBuilder<Context, Input, Output>` -> `(input: Input) => Promise<Output>`
19
+ * - Recursively traverses nested objects.
20
+ * - Passes through primitives (like string constants) unchanged.
21
+ */
22
+ type HydratedModel<TBlueprint, TContext> = {
23
+ [K in keyof TBlueprint]: TBlueprint[K] extends QueryBuilder<TContext, infer I, infer O> ? (input: I) => Promise<O> : TBlueprint[K] extends object ? HydratedModel<TBlueprint[K], TContext> : TBlueprint[K];
24
+ };
25
+ /**
26
+ * A generic function type for internal middleware storage.
27
+ * Uses `unknown` to allow storage of diverse middleware chains in the same array.
28
+ */
29
+ type ProcedureMiddleware = (params: {
30
+ ctx: unknown;
31
+ input: unknown;
32
+ }) => Promise<unknown> | unknown;
33
+ type GenericProcedureMiddleware<Context> = (params: {
34
+ ctx: Context;
35
+ input: unknown;
36
+ }) => Promise<unknown> | unknown;
37
+
38
+ /**
39
+ * Creates a Model Factory based on your API Blueprint.
40
+ * @template TBlueprint - The structure of your API (nested objects containing QueryBuilders).
41
+ * @param blueprint - The static object defining your API structure.
42
+ * @returns A function `createModel(ctx)` that takes your Context and returns the fully typed API.
43
+ * @example
44
+ * const modelFactory = createModelFactory({
45
+ * users: { get: userGetQuery }
46
+ * });
47
+ * const DB = modelFactory({ db: myDb });
48
+ */
49
+ declare function createModelFactory<TBlueprint extends Record<string, unknown>>(blueprint: TBlueprint): <TContext>(ctx: TContext) => HydratedModel<TBlueprint, TContext>;
50
+
51
+ /**
52
+ * The core builder class for creating type-safe database procedures.
53
+ * Implements a fluent interface to chain validation, middleware, and handlers.
54
+ * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).
55
+ * @template TCurrentContext - The Context type at the current stage of the pipeline (modified by previous middlewares).
56
+ * @template TInput - The validated input type (inferred from Zod).
57
+ */
58
+ declare class ProcedureBuilder<TRootContext, TCurrentContext, TInput extends z.ZodType> {
59
+ #private;
60
+ constructor(schema?: TInput, middlewares?: ProcedureMiddleware[]);
61
+ /**
62
+ * Define input validation.
63
+ * Resets TInput to the inferred Zod type.
64
+ */
65
+ input<TSchema extends z.ZodType>(schema: TSchema): ProcedureBuilder<TRootContext, TCurrentContext, TSchema>;
66
+ /**
67
+ * Add middleware.
68
+ * Transforms TCurrentContext -> TNextContext.
69
+ */
70
+ use<TNextContext>(middleware: (params: {
71
+ ctx: TCurrentContext;
72
+ input: z.infer<TInput>;
73
+ }) => Promise<TNextContext> | TNextContext): ProcedureBuilder<TRootContext, TNextContext, TInput>;
74
+ /**
75
+ * Finalize the query.
76
+ * Returns a QueryBuilder that expects TRootContext.
77
+ */
78
+ query<TResult>(handler: (params: {
79
+ ctx: TCurrentContext;
80
+ input: z.infer<TInput>;
81
+ }) => Promise<TResult>): QueryBuilder<TRootContext, z.infer<TInput>, TResult>;
82
+ }
83
+
84
+ /**
85
+ * Initialize a new procedure builder.
86
+ * @template TRootContext - The initial Context type provided by the application (e.g., Raw Request Context).
87
+ * @returns A new ProcedureBuilder instance.
88
+ */
89
+ declare function initProcedure<TRootContext>(): ProcedureBuilder<TRootContext, TRootContext, z.ZodAny>;
90
+ type ModelErrorOptions = {
91
+ message?: string;
92
+ };
93
+ declare class ModelError extends Error {
94
+ readonly status: number;
95
+ constructor(status: number, options?: ModelErrorOptions);
96
+ }
97
+
98
+ export { ModelError, createModelFactory, initProcedure };
99
+ export type { GenericProcedureMiddleware, HydratedModel };
package/dist/index.d.ts CHANGED
@@ -30,6 +30,10 @@ type ProcedureMiddleware = (params: {
30
30
  ctx: unknown;
31
31
  input: unknown;
32
32
  }) => Promise<unknown> | unknown;
33
+ type GenericProcedureMiddleware<Context> = (params: {
34
+ ctx: Context;
35
+ input: unknown;
36
+ }) => Promise<unknown> | unknown;
33
37
 
34
38
  /**
35
39
  * Creates a Model Factory based on your API Blueprint.
@@ -91,4 +95,5 @@ declare class ModelError extends Error {
91
95
  constructor(status: number, options?: ModelErrorOptions);
92
96
  }
93
97
 
94
- export { type HydratedModel, ModelError, createModelFactory, initProcedure };
98
+ export { ModelError, createModelFactory, initProcedure };
99
+ export type { GenericProcedureMiddleware, HydratedModel };
package/dist/index.mjs ADDED
@@ -0,0 +1,26 @@
1
+ import { i as isQueryBuilder, a as isPlainObject } from './shared/model-blueprint.JM5S8yAe.mjs';
2
+ export { M as ModelError, b as initProcedure } from './shared/model-blueprint.JM5S8yAe.mjs';
3
+ import 'zod';
4
+
5
+ function createModelFactory(blueprint) {
6
+ return function createModel(ctx) {
7
+ const hydrate = (structure) => {
8
+ const result = {};
9
+ for (const key in structure) {
10
+ const value = structure[key];
11
+ if (isQueryBuilder(value)) {
12
+ result[key] = value(ctx);
13
+ } else if (isPlainObject(value)) {
14
+ result[key] = hydrate(value);
15
+ } else {
16
+ result[key] = value;
17
+ }
18
+ }
19
+ return result;
20
+ };
21
+ return hydrate(blueprint);
22
+ };
23
+ }
24
+
25
+ export { createModelFactory };
26
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/createModelFactory.ts"],"sourcesContent":["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"],"names":[],"mappings":";;;;AAcO,SAAS,mBACd,SAAA,EACA;AAKA,EAAA,OAAO,SAAS,YACd,GAAA,EACqC;AAErC,IAAA,MAAM,OAAA,GAAU,CAAC,SAAA,KAAgD;AAC/D,MAAA,MAAM,SAAkC,EAAC;AAEzC,MAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC3B,QAAA,MAAM,KAAA,GAAQ,UAAU,GAAG,CAAA;AAE3B,QAAA,IAAI,cAAA,CAAe,KAAK,CAAA,EAAG;AAGzB,UAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA,CAAM,GAAG,CAAA;AAAA,QAKzB,CAAA,MAAA,IAAW,aAAA,CAAc,KAAK,CAAA,EAAG;AAC/B,UAAA,MAAA,CAAO,GAAG,CAAA,GAAI,OAAA,CAAQ,KAAK,CAAA;AAAA,QAC7B,CAAA,MAAO;AACL,UAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,QAChB;AAAA,MACF;AACA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,QAAQ,SAAS,CAAA;AAAA,EAC1B,CAAA;AACF;;;;"}
@@ -1,17 +1,22 @@
1
+ 'use strict';
2
+
3
+ const z = require('zod');
4
+
5
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
6
+
7
+ const z__default = /*#__PURE__*/_interopDefaultCompat(z);
8
+
9
+ const QUERY_MARKER = Symbol("QUERY_BUILDER");
10
+
1
11
  var __typeError = (msg) => {
2
12
  throw TypeError(msg);
3
13
  };
4
14
  var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
- var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
15
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), member.get(obj));
6
16
  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
- var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
-
9
- // src/constant.ts
10
- var QUERY_MARKER = /* @__PURE__ */ Symbol("QUERY_BUILDER");
11
-
12
- // src/ProcedureBuilder.ts
17
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
13
18
  var _middlewares, _schema;
14
- var _ProcedureBuilder = class _ProcedureBuilder {
19
+ const _ProcedureBuilder = class _ProcedureBuilder {
15
20
  constructor(schema, middlewares = []) {
16
21
  __privateAdd(this, _middlewares);
17
22
  __privateAdd(this, _schema);
@@ -76,9 +81,8 @@ var _ProcedureBuilder = class _ProcedureBuilder {
76
81
  };
77
82
  _middlewares = new WeakMap();
78
83
  _schema = new WeakMap();
79
- var ProcedureBuilder = _ProcedureBuilder;
84
+ let ProcedureBuilder = _ProcedureBuilder;
80
85
 
81
- // src/utils.ts
82
86
  function isQueryBuilder(value) {
83
87
  return typeof value === "function" && QUERY_MARKER in value && value[QUERY_MARKER] === true;
84
88
  }
@@ -88,46 +92,25 @@ function isPlainObject(value) {
88
92
  function initProcedure() {
89
93
  return new ProcedureBuilder();
90
94
  }
91
- var parseSchema = (schema, input) => {
95
+ const parseSchema = (schema, input) => {
92
96
  const parsed = schema.safeParse(input);
93
97
  if (!parsed.success) {
94
98
  throw new ModelError(400, {
95
- message: "input-not-valid"
99
+ message: z__default.prettifyError(parsed.error)
96
100
  });
97
101
  }
98
102
  return parsed.data;
99
103
  };
100
- var ModelError = class extends Error {
104
+ class ModelError extends Error {
101
105
  constructor(status, options) {
102
106
  super(options?.message);
103
107
  this.status = status;
104
108
  this.name = "ModuleError";
105
109
  }
106
- };
107
-
108
- // src/createModelFactory.ts
109
- function createModelFactory(blueprint) {
110
- return function createModel(ctx) {
111
- const hydrate = (structure) => {
112
- const result = {};
113
- for (const key in structure) {
114
- const value = structure[key];
115
- if (isQueryBuilder(value)) {
116
- result[key] = value(ctx);
117
- } else if (isPlainObject(value)) {
118
- result[key] = hydrate(value);
119
- } else {
120
- result[key] = value;
121
- }
122
- }
123
- return result;
124
- };
125
- return hydrate(blueprint);
126
- };
127
110
  }
128
- export {
129
- ModelError,
130
- createModelFactory,
131
- initProcedure
132
- };
133
- //# sourceMappingURL=index.js.map
111
+
112
+ exports.ModelError = ModelError;
113
+ exports.initProcedure = initProcedure;
114
+ exports.isPlainObject = isPlainObject;
115
+ exports.isQueryBuilder = isQueryBuilder;
116
+ //# sourceMappingURL=model-blueprint.D9hykuhu.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-blueprint.D9hykuhu.cjs","sources":["../../src/constant.ts","../../src/ProcedureBuilder.ts","../../src/utils.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: 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"],"names":["z"],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import z from 'zod';
2
+
3
+ const QUERY_MARKER = Symbol("QUERY_BUILDER");
4
+
5
+ var __typeError = (msg) => {
6
+ throw TypeError(msg);
7
+ };
8
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
9
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), member.get(obj));
10
+ 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);
11
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
12
+ var _middlewares, _schema;
13
+ const _ProcedureBuilder = class _ProcedureBuilder {
14
+ constructor(schema, middlewares = []) {
15
+ __privateAdd(this, _middlewares);
16
+ __privateAdd(this, _schema);
17
+ __privateSet(this, _schema, schema);
18
+ __privateSet(this, _middlewares, middlewares);
19
+ }
20
+ /**
21
+ * Define input validation.
22
+ * Resets TInput to the inferred Zod type.
23
+ */
24
+ input(schema) {
25
+ return new _ProcedureBuilder(
26
+ schema,
27
+ __privateGet(this, _middlewares)
28
+ );
29
+ }
30
+ /**
31
+ * Add middleware.
32
+ * Transforms TCurrentContext -> TNextContext.
33
+ */
34
+ use(middleware) {
35
+ const storedMiddleware = async (p) => {
36
+ return middleware({
37
+ ctx: p.ctx,
38
+ input: p.input
39
+ });
40
+ };
41
+ return new _ProcedureBuilder(
42
+ __privateGet(this, _schema),
43
+ [...__privateGet(this, _middlewares), storedMiddleware]
44
+ );
45
+ }
46
+ /**
47
+ * Finalize the query.
48
+ * Returns a QueryBuilder that expects TRootContext.
49
+ */
50
+ query(handler) {
51
+ const builder = (rootCtx) => {
52
+ return async (rawInput) => {
53
+ if (!__privateGet(this, _schema)) {
54
+ throw new Error("No schema provided for query");
55
+ }
56
+ const validatedInput = parseSchema(__privateGet(this, _schema), rawInput);
57
+ let ctxCursor = rootCtx;
58
+ for (const mw of __privateGet(this, _middlewares)) {
59
+ ctxCursor = await mw({ ctx: ctxCursor, input: validatedInput });
60
+ }
61
+ return handler({
62
+ ctx: ctxCursor,
63
+ input: validatedInput
64
+ });
65
+ };
66
+ };
67
+ Object.defineProperty(builder, QUERY_MARKER, {
68
+ value: true,
69
+ writable: false,
70
+ enumerable: false,
71
+ configurable: false
72
+ });
73
+ return builder;
74
+ }
75
+ };
76
+ _middlewares = new WeakMap();
77
+ _schema = new WeakMap();
78
+ let ProcedureBuilder = _ProcedureBuilder;
79
+
80
+ function isQueryBuilder(value) {
81
+ return typeof value === "function" && QUERY_MARKER in value && value[QUERY_MARKER] === true;
82
+ }
83
+ function isPlainObject(value) {
84
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
85
+ }
86
+ function initProcedure() {
87
+ return new ProcedureBuilder();
88
+ }
89
+ const parseSchema = (schema, input) => {
90
+ const parsed = schema.safeParse(input);
91
+ if (!parsed.success) {
92
+ throw new ModelError(400, {
93
+ message: z.prettifyError(parsed.error)
94
+ });
95
+ }
96
+ return parsed.data;
97
+ };
98
+ class ModelError extends Error {
99
+ constructor(status, options) {
100
+ super(options?.message);
101
+ this.status = status;
102
+ this.name = "ModuleError";
103
+ }
104
+ }
105
+
106
+ export { ModelError as M, isPlainObject as a, initProcedure as b, isQueryBuilder as i };
107
+ //# sourceMappingURL=model-blueprint.JM5S8yAe.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-blueprint.JM5S8yAe.mjs","sources":["../../src/constant.ts","../../src/ProcedureBuilder.ts","../../src/utils.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: 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"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const getCursor = (entries, parameter, pageSize) => entries.length && pageSize === entries.length ? entries[entries.length - 1][parameter] : null;
4
+ const withCursor = (entries, parameter, pageSize, orderedBy = "asc") => {
5
+ const cursor = getCursor(entries, parameter, pageSize);
6
+ return {
7
+ entries: orderedBy === "asc" ? entries : entries.reverse(),
8
+ cursor
9
+ };
10
+ };
11
+
12
+ exports.withCursor = withCursor;
13
+ //# sourceMappingURL=withCursor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withCursor.cjs","sources":["../src/queryUtils/withCursor.ts"],"sourcesContent":["const getCursor = <T extends Record<string, unknown | number>>(\n entries: Array<T>,\n parameter: keyof T,\n pageSize: number\n) =>\n entries.length && pageSize === entries.length\n ? (entries[entries.length - 1][parameter] as number)\n : null;\n\nexport const withCursor = <T extends Record<string, unknown | number>>(\n entries: Array<T>,\n parameter: keyof T,\n pageSize: number,\n orderedBy: 'asc' | 'desc' = 'asc'\n) => {\n const cursor = getCursor<T>(entries, parameter, pageSize);\n\n return {\n entries: orderedBy === 'asc' ? entries : entries.reverse(),\n cursor,\n };\n};\n"],"names":[],"mappings":";;AAAA,MAAM,YAAY,CAChB,OAAA,EACA,SAAA,EACA,QAAA,KAEA,QAAQ,MAAA,IAAU,QAAA,KAAa,OAAA,CAAQ,MAAA,GAClC,QAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,CAAE,SAAS,CAAA,GACtC,IAAA;AAEC,MAAM,aAAa,CACxB,OAAA,EACA,SAAA,EACA,QAAA,EACA,YAA4B,KAAA,KACzB;AACH,EAAA,MAAM,MAAA,GAAS,SAAA,CAAa,OAAA,EAAS,SAAA,EAAW,QAAQ,CAAA;AAExD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,SAAA,KAAc,KAAA,GAAQ,OAAA,GAAU,QAAQ,OAAA,EAAQ;AAAA,IACzD;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,6 @@
1
+ declare const withCursor: <T extends Record<string, unknown | number>>(entries: Array<T>, parameter: keyof T, pageSize: number, orderedBy?: "asc" | "desc") => {
2
+ entries: T[];
3
+ cursor: number | null;
4
+ };
5
+
6
+ export { withCursor };
@@ -0,0 +1,6 @@
1
+ declare const withCursor: <T extends Record<string, unknown | number>>(entries: Array<T>, parameter: keyof T, pageSize: number, orderedBy?: "asc" | "desc") => {
2
+ entries: T[];
3
+ cursor: number | null;
4
+ };
5
+
6
+ export { withCursor };
@@ -0,0 +1,11 @@
1
+ const getCursor = (entries, parameter, pageSize) => entries.length && pageSize === entries.length ? entries[entries.length - 1][parameter] : null;
2
+ const withCursor = (entries, parameter, pageSize, orderedBy = "asc") => {
3
+ const cursor = getCursor(entries, parameter, pageSize);
4
+ return {
5
+ entries: orderedBy === "asc" ? entries : entries.reverse(),
6
+ cursor
7
+ };
8
+ };
9
+
10
+ export { withCursor };
11
+ //# sourceMappingURL=withCursor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withCursor.mjs","sources":["../src/queryUtils/withCursor.ts"],"sourcesContent":["const getCursor = <T extends Record<string, unknown | number>>(\n entries: Array<T>,\n parameter: keyof T,\n pageSize: number\n) =>\n entries.length && pageSize === entries.length\n ? (entries[entries.length - 1][parameter] as number)\n : null;\n\nexport const withCursor = <T extends Record<string, unknown | number>>(\n entries: Array<T>,\n parameter: keyof T,\n pageSize: number,\n orderedBy: 'asc' | 'desc' = 'asc'\n) => {\n const cursor = getCursor<T>(entries, parameter, pageSize);\n\n return {\n entries: orderedBy === 'asc' ? entries : entries.reverse(),\n cursor,\n };\n};\n"],"names":[],"mappings":"AAAA,MAAM,YAAY,CAChB,OAAA,EACA,SAAA,EACA,QAAA,KAEA,QAAQ,MAAA,IAAU,QAAA,KAAa,OAAA,CAAQ,MAAA,GAClC,QAAQ,OAAA,CAAQ,MAAA,GAAS,CAAC,CAAA,CAAE,SAAS,CAAA,GACtC,IAAA;AAEC,MAAM,aAAa,CACxB,OAAA,EACA,SAAA,EACA,QAAA,EACA,YAA4B,KAAA,KACzB;AACH,EAAA,MAAM,MAAA,GAAS,SAAA,CAAa,OAAA,EAAS,SAAA,EAAW,QAAQ,CAAA;AAExD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,SAAA,KAAc,KAAA,GAAQ,OAAA,GAAU,QAAQ,OAAA,EAAQ;AAAA,IACzD;AAAA,GACF;AACF;;;;"}
package/package.json CHANGED
@@ -1,31 +1,42 @@
1
1
  {
2
2
  "name": "model-blueprint",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
- "main": "dist/index.js",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
6
7
  "types": "dist/index.d.ts",
7
8
  "exports": {
8
9
  ".": {
9
- "import": "./dist/index.js",
10
- "types": "./dist/index.d.ts"
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs",
13
+ "default": "./dist/index.mjs"
11
14
  },
12
15
  "./withCursor": {
13
- "import": "./dist/withCursor.js",
14
- "types": "./dist/withCursor.d.ts"
16
+ "types": "./dist/withCursor.d.ts",
17
+ "import": "./dist/withCursor.mjs",
18
+ "require": "./dist/withCursor.cjs",
19
+ "default": "./dist/withCursor.mjs"
15
20
  },
16
21
  "./createId": {
17
- "import": "./dist/createId.js",
18
- "types": "./dist/createId.d.ts"
22
+ "types": "./dist/createId.d.ts",
23
+ "import": "./dist/createId.mjs",
24
+ "require": "./dist/createId.cjs",
25
+ "default": "./dist/createId.mjs"
19
26
  },
20
27
  "./dropQuery": {
21
- "import": "./dist/dropQuery.js",
22
- "types": "./dist/dropQuery.d.ts"
23
- }
28
+ "types": "./dist/dropQuery.d.ts",
29
+ "import": "./dist/dropQuery.mjs",
30
+ "require": "./dist/dropQuery.cjs",
31
+ "default": "./dist/dropQuery.mjs"
32
+ },
33
+ "./package.json": "./package.json"
24
34
  },
25
35
  "scripts": {
26
36
  "prepare": "husky",
27
- "build": "tsup",
28
- "dev": "tsup --watch",
37
+ "build": "unbuild",
38
+ "dev": "unbuild --stub",
39
+ "prepack": "unbuild",
29
40
  "test": "vitest",
30
41
  "test:ui": "vitest --ui",
31
42
  "coverage": "vitest --coverage",
@@ -54,20 +65,20 @@
54
65
  },
55
66
  "description": "model-blueprint",
56
67
  "dependencies": {
57
- "zod": "^4.3.6",
58
- "@paralleldrive/cuid2": "^3.3.0"
68
+ "@paralleldrive/cuid2": "^3.3.0",
69
+ "zod": "^4.4.3"
59
70
  },
60
71
  "devDependencies": {
61
- "@eslint/js": "^9.0.0",
62
- "@typescript-eslint/eslint-plugin": "^8.0.0",
72
+ "@eslint/js": "^10.0.1",
73
+ "@typescript-eslint/eslint-plugin": "^8.64.0",
63
74
  "@typescript-eslint/parser": "^8.0.0",
64
- "@vitest/coverage-v8": "^4.0.18",
65
- "eslint": "^9.0.0",
75
+ "@vitest/coverage-v8": "^4.1.10",
76
+ "eslint": "^10.7.0",
66
77
  "husky": "^9.1.7",
67
- "prettier": "^3.8.1",
68
- "tsup": "^8.5.1",
69
- "typescript": "^5.0.0",
70
- "vitest": "^4.0.18"
78
+ "prettier": "^3.9.5",
79
+ "typescript": "^5.9.3",
80
+ "unbuild": "^3.6.1",
81
+ "vitest": "^4.1.10"
71
82
  },
72
83
  "files": [
73
84
  "dist"
package/dist/createId.js DELETED
@@ -1,12 +0,0 @@
1
- // src/queryUtils/createId.ts
2
- import {
3
- createId as createCuid2Id,
4
- init as initCuid2
5
- } from "@paralleldrive/cuid2";
6
- var createId = ({ ctx }) => {
7
- return { ...ctx, createId: createCuid2Id, init: initCuid2 };
8
- };
9
- export {
10
- createId
11
- };
12
- //# sourceMappingURL=createId.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queryUtils/createId.ts"],"sourcesContent":["import {\n createId as createCuid2Id,\n init as initCuid2,\n} from '@paralleldrive/cuid2';\n\n/**\n * Middleware factory that adds a `createId` and `init` function to the context.\n * This function allows procedures to create CUID2 IDs and initialize CUID2.\n * @template TContext - The context type.\n * @param ctx - The context object.\n * @returns A middleware function that injects the `createId` and `init` helpers into the context.\n */\nexport const createId = <TContext>({ ctx }: { ctx: TContext }) => {\n return { ...ctx, createId: createCuid2Id, init: initCuid2 };\n};\n"],"mappings":";AAAA;AAAA,EACE,YAAY;AAAA,EACZ,QAAQ;AAAA,OACH;AASA,IAAM,WAAW,CAAW,EAAE,IAAI,MAAyB;AAChE,SAAO,EAAE,GAAG,KAAK,UAAU,eAAe,MAAM,UAAU;AAC5D;","names":[]}
package/dist/dropQuery.js DELETED
@@ -1,22 +0,0 @@
1
- // src/utils.ts
2
- var ModelError = class extends Error {
3
- constructor(status, options) {
4
- super(options?.message);
5
- this.status = status;
6
- this.name = "ModuleError";
7
- }
8
- };
9
-
10
- // src/queryUtils/dropQuery.ts
11
- var dropQuery = (dropMessages) => ({ ctx }) => {
12
- const drop = (message, code) => {
13
- throw new ModelError(code ?? 400, {
14
- message: dropMessages[message]
15
- });
16
- };
17
- return { ...ctx, drop };
18
- };
19
- export {
20
- dropQuery
21
- };
22
- //# sourceMappingURL=dropQuery.js.map
@@ -1 +0,0 @@
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.js.map DELETED
@@ -1 +0,0 @@
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,13 +0,0 @@
1
- // src/queryUtils/withCursor.ts
2
- var getCursor = (entries, parameter, pageSize) => entries.length && pageSize === entries.length ? entries[entries.length - 1][parameter] : null;
3
- var withCursor = (entries, parameter, pageSize, orderedBy = "asc") => {
4
- const cursor = getCursor(entries, parameter, pageSize);
5
- return {
6
- entries: orderedBy === "asc" ? entries : entries.reverse(),
7
- cursor
8
- };
9
- };
10
- export {
11
- withCursor
12
- };
13
- //# sourceMappingURL=withCursor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/queryUtils/withCursor.ts"],"sourcesContent":["const getCursor = <T extends Record<string, unknown | number>>(\n entries: Array<T>,\n parameter: keyof T,\n pageSize: number\n) =>\n entries.length && pageSize === entries.length\n ? (entries[entries.length - 1][parameter] as number)\n : null;\n\nexport const withCursor = <T extends Record<string, unknown | number>>(\n entries: Array<T>,\n parameter: keyof T,\n pageSize: number,\n orderedBy: 'asc' | 'desc' = 'asc'\n) => {\n const cursor = getCursor<T>(entries, parameter, pageSize);\n\n return {\n entries: orderedBy === 'asc' ? entries : entries.reverse(),\n cursor,\n };\n};\n"],"mappings":";AAAA,IAAM,YAAY,CAChB,SACA,WACA,aAEA,QAAQ,UAAU,aAAa,QAAQ,SAClC,QAAQ,QAAQ,SAAS,CAAC,EAAE,SAAS,IACtC;AAEC,IAAM,aAAa,CACxB,SACA,WACA,UACA,YAA4B,UACzB;AACH,QAAM,SAAS,UAAa,SAAS,WAAW,QAAQ;AAExD,SAAO;AAAA,IACL,SAAS,cAAc,QAAQ,UAAU,QAAQ,QAAQ;AAAA,IACzD;AAAA,EACF;AACF;","names":[]}