joist-graphql-resolver-utils 2.3.0-next.70 → 2.3.0-next.72

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.
@@ -1,16 +1,20 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./paginationUtils.cjs");
2
+ const require_paginationUtils = require("./paginationUtils.cjs");
3
3
  require("joist-core");
4
4
  //#region src/cursorPagination.ts
5
- /** Returns a cursor connection shape for a generated query resolver. */
6
5
  async function paginateCursor(ctx, type, args) {
7
6
  const limit = args.first ?? args.last ?? 100;
8
7
  const baseFilter = args.filter ?? {};
9
8
  const filter = withCursorFilter(baseFilter, args);
10
9
  const orderBy = { id: args.last ? "DESC" : "ASC" };
11
- const nodes = await ctx.em.findGql(type, filter, {
10
+ const nodes = typeof type === "function" ? await ctx.em.findGql(type, filter, {
12
11
  limit,
13
12
  orderBy
13
+ }) : await require_paginationUtils.queryEntities(ctx, {
14
+ ...withCursorQuery(type, args),
15
+ orderBy: [args.last ? { desc: type.select.id } : { asc: type.select.id }],
16
+ limit,
17
+ offset: void 0
14
18
  });
15
19
  const orderedNodes = args.last ? [...nodes].reverse() : nodes;
16
20
  const edges = orderedNodes.map((node) => ({
@@ -20,7 +24,10 @@ async function paginateCursor(ctx, type, args) {
20
24
  return {
21
25
  edges,
22
26
  nodes: orderedNodes,
23
- pageInfo: new CursorPageInfo(ctx, type, baseFilter, edges)
27
+ pageInfo: new CursorPageInfo(ctx, typeof type === "function" ? type : {
28
+ ...type,
29
+ orderBy: void 0
30
+ }, baseFilter, edges)
24
31
  };
25
32
  }
26
33
  /** Lazily computes cursor page fields. */
@@ -51,15 +58,27 @@ var CursorPageInfo = class {
51
58
  return this.#hasPreviousPagePromise ??= this.#countPastCursor("before", this.startCursor);
52
59
  }
53
60
  get totalCount() {
54
- return this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter);
61
+ return this.#totalCountPromise ??= typeof this.#type === "function" ? this.#ctx.em.findCount(this.#type, this.#filter) : require_paginationUtils.countQuery(this.#ctx, this.#type);
55
62
  }
56
63
  /** Counts rows past a cursor only when the field is requested. */
57
64
  async #countPastCursor(direction, cursor) {
58
65
  if (!cursor) return false;
66
+ if (typeof this.#type !== "function") return await require_paginationUtils.countQuery(this.#ctx, withCursorQuery(this.#type, { [direction]: cursor })) > 0;
59
67
  const filter = withCursorFilter(this.#filter, { [direction]: cursor });
60
68
  return await this.#ctx.em.findCount(this.#type, filter) > 0;
61
69
  }
62
70
  };
71
+ /** Adds both cursor bounds without replacing the query's existing conditions. */
72
+ function withCursorQuery(base, args) {
73
+ return {
74
+ ...base,
75
+ where: { and: [
76
+ base.where,
77
+ args.after ? base.select.id.gt(decodeCursor(args.after)) : void 0,
78
+ args.before ? base.select.id.lt(decodeCursor(args.before)) : void 0
79
+ ] }
80
+ };
81
+ }
63
82
  /** Adds cursor bounds to a filter. */
64
83
  function withCursorFilter(filter, args) {
65
84
  const cursor = args.after ?? args.before;
@@ -1 +1 @@
1
- {"version":3,"file":"cursorPagination.cjs","names":["#ctx","#type","#filter","#edges","#hasNextPagePromise","#countPastCursor","#hasPreviousPagePromise","#totalCountPromise"],"sources":["../src/cursorPagination.ts"],"sourcesContent":["import {\n type Entity,\n type FindGqlFilterOptions,\n type MaybeAbstractEntityConstructor,\n type ValueGraphQLFilter,\n} from \"joist-core\";\n\nimport { type ContextWithEm, type PaginationFilter, defaultLimit } from \"./paginationUtils.ts\";\n\ntype CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n first?: number | null;\n after?: string | null;\n last?: number | null;\n before?: string | null;\n};\n\n/** Returns a cursor connection shape for a generated query resolver. */\nexport async function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: CursorArgs<T, F>,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }> {\n const limit = args.first ?? args.last ?? defaultLimit;\n const baseFilter = (args.filter ?? {}) as PaginationFilter<T>;\n const filter = withCursorFilter(baseFilter, args);\n const orderBy = { id: args.last ? \"DESC\" : \"ASC\" } as FindGqlFilterOptions<T>[\"orderBy\"];\n const nodes = await ctx.em.findGql(type, filter, { limit, orderBy });\n const orderedNodes = args.last ? [...nodes].reverse() : nodes;\n const edges = orderedNodes.map((node) => ({ node, cursor: encodeCursor(String(node.id)) }));\n return {\n edges,\n nodes: orderedNodes,\n pageInfo: new CursorPageInfo(ctx, type, baseFilter, edges),\n };\n}\n\n/** Lazily computes cursor page fields. */\nexport class CursorPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #edges: { node: T; cursor: string }[];\n #filter: PaginationFilter<T>;\n #hasNextPagePromise: Promise<boolean> | undefined;\n #hasPreviousPagePromise: Promise<boolean> | undefined;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T>;\n\n constructor(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n filter: PaginationFilter<T>,\n edges: { node: T; cursor: string }[],\n ) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#edges = edges;\n }\n\n get startCursor(): string | undefined {\n return this.#edges[0]?.cursor;\n }\n\n get endCursor(): string | undefined {\n return this.#edges[this.#edges.length - 1]?.cursor;\n }\n\n get hasNextPage(): Promise<boolean> {\n return (this.#hasNextPagePromise ??= this.#countPastCursor(\"after\", this.endCursor));\n }\n\n get hasPreviousPage(): Promise<boolean> {\n return (this.#hasPreviousPagePromise ??= this.#countPastCursor(\"before\", this.startCursor));\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter));\n }\n\n /** Counts rows past a cursor only when the field is requested. */\n async #countPastCursor(direction: \"after\" | \"before\", cursor: string | undefined): Promise<boolean> {\n if (!cursor) return false;\n const filter = withCursorFilter(this.#filter, { [direction]: cursor });\n return (await this.#ctx.em.findCount(this.#type, filter)) > 0;\n }\n}\n\n/** Adds cursor bounds to a filter. */\nfunction withCursorFilter<T extends Entity>(\n filter: PaginationFilter<T>,\n args: { after?: string | null; before?: string | null },\n): PaginationFilter<T> {\n const cursor = args.after ?? args.before;\n if (!cursor) return filter;\n const op = args.after ? \"gt\" : \"lt\";\n return { ...filter, id: { [op]: decodeCursor(cursor) } as ValueGraphQLFilter<string> } as PaginationFilter<T>;\n}\n\n/** Encodes an entity id as an opaque cursor. */\nfunction encodeCursor(id: string): string {\n return Buffer.from(id).toString(\"base64\");\n}\n\n/** Decodes an opaque cursor back into an entity id. */\nfunction decodeCursor(cursor: string): string {\n return Buffer.from(cursor, \"base64\").toString(\"utf8\");\n}\n"],"mappings":";;;;;AAkBA,eAAsB,eACpB,KACA,MACA,MAC4F;CAC5F,MAAM,QAAQ,KAAK,SAAS,KAAK,QAAA;CACjC,MAAM,aAAc,KAAK,UAAU,CAAC;CACpC,MAAM,SAAS,iBAAiB,YAAY,IAAI;CAChD,MAAM,UAAU,EAAE,IAAI,KAAK,OAAO,SAAS,MAAM;CACjD,MAAM,QAAQ,MAAM,IAAI,GAAG,QAAQ,MAAM,QAAQ;EAAE;EAAO;CAAQ,CAAC;CACnE,MAAM,eAAe,KAAK,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,IAAI;CACxD,MAAM,QAAQ,aAAa,KAAK,UAAU;EAAE;EAAM,QAAQ,aAAa,OAAO,KAAK,EAAE,CAAC;CAAE,EAAE;CAC1F,OAAO;EACL;EACA,OAAO;EACP,UAAU,IAAI,eAAe,KAAK,MAAM,YAAY,KAAK;CAC3D;AACF;;AAGA,IAAa,iBAAb,MAAuD;CACrD;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YACE,KACA,MACA,QACA,OACA;EACA,KAAKA,OAAO;EACZ,KAAKC,QAAQ;EACb,KAAKC,UAAU;EACf,KAAKC,SAAS;CAChB;CAEA,IAAI,cAAkC;EACpC,OAAO,KAAKA,OAAO,EAAE,EAAE;CACzB;CAEA,IAAI,YAAgC;EAClC,OAAO,KAAKA,OAAO,KAAKA,OAAO,SAAS,EAAE,EAAE;CAC9C;CAEA,IAAI,cAAgC;EAClC,OAAQ,KAAKC,wBAAwB,KAAKC,iBAAiB,SAAS,KAAK,SAAS;CACpF;CAEA,IAAI,kBAAoC;EACtC,OAAQ,KAAKC,4BAA4B,KAAKD,iBAAiB,UAAU,KAAK,WAAW;CAC3F;CAEA,IAAI,aAA8B;EAChC,OAAQ,KAAKE,uBAAuB,KAAKP,KAAK,GAAG,UAAU,KAAKC,OAAO,KAAKC,OAAO;CACrF;;CAGA,MAAMG,iBAAiB,WAA+B,QAA8C;EAClG,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,SAAS,iBAAiB,KAAKH,SAAS,GAAG,YAAY,OAAO,CAAC;EACrE,OAAQ,MAAM,KAAKF,KAAK,GAAG,UAAU,KAAKC,OAAO,MAAM,IAAK;CAC9D;AACF;;AAGA,SAAS,iBACP,QACA,MACqB;CACrB,MAAM,SAAS,KAAK,SAAS,KAAK;CAClC,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,KAAK,KAAK,QAAQ,OAAO;CAC/B,OAAO;EAAE,GAAG;EAAQ,IAAI,GAAG,KAAK,aAAa,MAAM,EAAE;CAAgC;AACvF;;AAGA,SAAS,aAAa,IAAoB;CACxC,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,SAAS,QAAQ;AAC1C;;AAGA,SAAS,aAAa,QAAwB;CAC5C,OAAO,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,SAAS,MAAM;AACtD"}
1
+ {"version":3,"file":"cursorPagination.cjs","names":["queryEntities","#ctx","#type","#filter","#edges","#hasNextPagePromise","#countPastCursor","#hasPreviousPagePromise","#totalCountPromise","countQuery"],"sources":["../src/cursorPagination.ts"],"sourcesContent":["import {\n type Entity,\n type FindGqlFilterOptions,\n type IdOf,\n type MaybeAbstractEntityConstructor,\n type ValueGraphQLFilter,\n} from \"joist-core\";\n\nimport {\n type ContextWithEm,\n type PaginationFilter,\n type PaginationQuery,\n countQuery,\n defaultLimit,\n queryEntities,\n} from \"./paginationUtils.ts\";\n\ntype CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n first?: number | null;\n after?: string | null;\n last?: number | null;\n before?: string | null;\n};\n\n/** Returns an ID-ordered cursor connection for a resolver, replacing the entity query's ordering. */\nexport function paginateCursor<T extends Entity>(\n ctx: ContextWithEm,\n query: PaginationQuery<T>,\n args: Omit<CursorArgs<T>, \"filter\">,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }>;\nexport function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: CursorArgs<T, F>,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }>;\nexport async function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n args: CursorArgs<T, F>,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }> {\n const limit = args.first ?? args.last ?? defaultLimit;\n const baseFilter = (args.filter ?? {}) as PaginationFilter<T>;\n const filter = withCursorFilter(baseFilter, args);\n const orderBy = { id: args.last ? \"DESC\" : \"ASC\" } as FindGqlFilterOptions<T>[\"orderBy\"];\n const nodes =\n typeof type === \"function\"\n ? await ctx.em.findGql(type, filter, { limit, orderBy })\n : await queryEntities(ctx, {\n ...withCursorQuery(type, args),\n orderBy: [args.last ? { desc: type.select.id } : { asc: type.select.id }],\n limit,\n offset: undefined,\n });\n const orderedNodes = args.last ? [...nodes].reverse() : nodes;\n const edges = orderedNodes.map((node) => ({ node, cursor: encodeCursor(String(node.id)) }));\n return {\n edges,\n nodes: orderedNodes,\n pageInfo: new CursorPageInfo(\n ctx,\n typeof type === \"function\" ? type : { ...type, orderBy: undefined },\n baseFilter,\n edges,\n ),\n };\n}\n\n/** Lazily computes cursor page fields. */\nexport class CursorPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #edges: { node: T; cursor: string }[];\n #filter: PaginationFilter<T>;\n #hasNextPagePromise: Promise<boolean> | undefined;\n #hasPreviousPagePromise: Promise<boolean> | undefined;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>;\n\n constructor(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n filter: PaginationFilter<T>,\n edges: { node: T; cursor: string }[],\n ) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#edges = edges;\n }\n\n get startCursor(): string | undefined {\n return this.#edges[0]?.cursor;\n }\n\n get endCursor(): string | undefined {\n return this.#edges[this.#edges.length - 1]?.cursor;\n }\n\n get hasNextPage(): Promise<boolean> {\n return (this.#hasNextPagePromise ??= this.#countPastCursor(\"after\", this.endCursor));\n }\n\n get hasPreviousPage(): Promise<boolean> {\n return (this.#hasPreviousPagePromise ??= this.#countPastCursor(\"before\", this.startCursor));\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??=\n typeof this.#type === \"function\"\n ? this.#ctx.em.findCount(this.#type, this.#filter)\n : countQuery(this.#ctx, this.#type));\n }\n\n /** Counts rows past a cursor only when the field is requested. */\n async #countPastCursor(direction: \"after\" | \"before\", cursor: string | undefined): Promise<boolean> {\n if (!cursor) return false;\n if (typeof this.#type !== \"function\") {\n return (await countQuery(this.#ctx, withCursorQuery(this.#type, { [direction]: cursor }))) > 0;\n }\n const filter = withCursorFilter(this.#filter, { [direction]: cursor });\n return (await this.#ctx.em.findCount(this.#type, filter)) > 0;\n }\n}\n\n/** Adds both cursor bounds without replacing the query's existing conditions. */\nfunction withCursorQuery<T extends Entity>(\n base: PaginationQuery<T>,\n args: { after?: string | null; before?: string | null },\n): PaginationQuery<T> {\n return {\n ...base,\n where: {\n and: [\n base.where,\n args.after ? base.select.id.gt(decodeCursor(args.after) as IdOf<T>) : undefined,\n args.before ? base.select.id.lt(decodeCursor(args.before) as IdOf<T>) : undefined,\n ],\n },\n };\n}\n\n/** Adds cursor bounds to a filter. */\nfunction withCursorFilter<T extends Entity>(\n filter: PaginationFilter<T>,\n args: { after?: string | null; before?: string | null },\n): PaginationFilter<T> {\n const cursor = args.after ?? args.before;\n if (!cursor) return filter;\n const op = args.after ? \"gt\" : \"lt\";\n return { ...filter, id: { [op]: decodeCursor(cursor) } as ValueGraphQLFilter<string> } as PaginationFilter<T>;\n}\n\n/** Encodes an entity id as an opaque cursor. */\nfunction encodeCursor(id: string): string {\n return Buffer.from(id).toString(\"base64\");\n}\n\n/** Decodes an opaque cursor back into an entity id. */\nfunction decodeCursor(cursor: string): string {\n return Buffer.from(cursor, \"base64\").toString(\"utf8\");\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ContextWithEm, PaginationFilter } from "./paginationUtils.cjs";
1
+ import { ContextWithEm, PaginationFilter, PaginationQuery } from "./paginationUtils.cjs";
2
2
  import { Entity, MaybeAbstractEntityConstructor } from "joist-core";
3
3
  //#region src/cursorPagination.d.ts
4
4
  type CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {
@@ -8,7 +8,15 @@ type CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {
8
8
  last?: number | null;
9
9
  before?: string | null;
10
10
  };
11
- /** Returns a cursor connection shape for a generated query resolver. */
11
+ /** Returns an ID-ordered cursor connection for a resolver, replacing the entity query's ordering. */
12
+ declare function paginateCursor<T extends Entity>(ctx: ContextWithEm, query: PaginationQuery<T>, args: Omit<CursorArgs<T>, "filter">): Promise<{
13
+ edges: {
14
+ node: T;
15
+ cursor: string;
16
+ }[];
17
+ nodes: T[];
18
+ pageInfo: CursorPageInfo<T>;
19
+ }>;
12
20
  declare function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, args: CursorArgs<T, F>): Promise<{
13
21
  edges: {
14
22
  node: T;
@@ -20,7 +28,7 @@ declare function paginateCursor<T extends Entity, F extends object = PaginationF
20
28
  /** Lazily computes cursor page fields. */
21
29
  declare class CursorPageInfo<T extends Entity = Entity> {
22
30
  #private;
23
- constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, filter: PaginationFilter<T>, edges: {
31
+ constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>, filter: PaginationFilter<T>, edges: {
24
32
  node: T;
25
33
  cursor: string;
26
34
  }[]);
@@ -1 +1 @@
1
- {"version":3,"file":"cursorPagination.d.cts","names":[],"sources":["../src/cursorPagination.ts"],"mappings":";;;KASK,WAAW,UAAU,QAAQ,mBAAmB,iBAAiB;EACpE,SAAS;EACT;EACA;EACA;EACA;;;iBAIoB,eAAe,UAAU,QAAQ,mBAAmB,iBAAiB,IACzF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,WAAW,GAAG,KACnB;EAAU;IAAS,MAAM;IAAG;;EAAoB,OAAO;EAAK,UAAU,eAAe;;;cAgB3E,eAAe,UAAU,SAAS;;EAS7C,YACE,KAAK,eACL,MAAM,+BAA+B,IACrC,QAAQ,iBAAiB,IACzB;IAAS,MAAM;IAAG;;MAQhB;MAIA;MAIA,eAAe;MAIf,mBAAmB;MAInB,cAAc"}
1
+ {"version":3,"file":"cursorPagination.d.cts","names":[],"sources":["../src/cursorPagination.ts"],"mappings":";;;KAiBK,WAAW,UAAU,QAAQ,mBAAmB,iBAAiB;EACpE,SAAS;EACT;EACA;EACA;EACA;;;iBAIc,eAAe,UAAU,QACvC,KAAK,eACL,OAAO,gBAAgB,IACvB,MAAM,KAAK,WAAW,gBACrB;EAAU;IAAS,MAAM;IAAG;;EAAoB,OAAO;EAAK,UAAU,eAAe;;iBACxE,eAAe,UAAU,QAAQ,mBAAmB,iBAAiB,IACnF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,WAAW,GAAG,KACnB;EAAU;IAAS,MAAM;IAAG;;EAAoB,OAAO;EAAK,UAAU,eAAe;;;cAkC3E,eAAe,UAAU,SAAS;;EAS7C,YACE,KAAK,eACL,MAAM,+BAA+B,KAAK,gBAAgB,IAC1D,QAAQ,iBAAiB,IACzB;IAAS,MAAM;IAAG;;MAQhB;MAIA;MAIA,eAAe;MAIf,mBAAmB;MAInB,cAAc"}
@@ -1,4 +1,4 @@
1
- import { ContextWithEm, PaginationFilter } from "./paginationUtils.mjs";
1
+ import { ContextWithEm, PaginationFilter, PaginationQuery } from "./paginationUtils.mjs";
2
2
  import { Entity, MaybeAbstractEntityConstructor } from "joist-core";
3
3
  //#region src/cursorPagination.d.ts
4
4
  type CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {
@@ -8,7 +8,15 @@ type CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {
8
8
  last?: number | null;
9
9
  before?: string | null;
10
10
  };
11
- /** Returns a cursor connection shape for a generated query resolver. */
11
+ /** Returns an ID-ordered cursor connection for a resolver, replacing the entity query's ordering. */
12
+ declare function paginateCursor<T extends Entity>(ctx: ContextWithEm, query: PaginationQuery<T>, args: Omit<CursorArgs<T>, "filter">): Promise<{
13
+ edges: {
14
+ node: T;
15
+ cursor: string;
16
+ }[];
17
+ nodes: T[];
18
+ pageInfo: CursorPageInfo<T>;
19
+ }>;
12
20
  declare function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, args: CursorArgs<T, F>): Promise<{
13
21
  edges: {
14
22
  node: T;
@@ -20,7 +28,7 @@ declare function paginateCursor<T extends Entity, F extends object = PaginationF
20
28
  /** Lazily computes cursor page fields. */
21
29
  declare class CursorPageInfo<T extends Entity = Entity> {
22
30
  #private;
23
- constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, filter: PaginationFilter<T>, edges: {
31
+ constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>, filter: PaginationFilter<T>, edges: {
24
32
  node: T;
25
33
  cursor: string;
26
34
  }[]);
@@ -1 +1 @@
1
- {"version":3,"file":"cursorPagination.d.mts","names":[],"sources":["../src/cursorPagination.ts"],"mappings":";;;KASK,WAAW,UAAU,QAAQ,mBAAmB,iBAAiB;EACpE,SAAS;EACT;EACA;EACA;EACA;;;iBAIoB,eAAe,UAAU,QAAQ,mBAAmB,iBAAiB,IACzF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,WAAW,GAAG,KACnB;EAAU;IAAS,MAAM;IAAG;;EAAoB,OAAO;EAAK,UAAU,eAAe;;;cAgB3E,eAAe,UAAU,SAAS;;EAS7C,YACE,KAAK,eACL,MAAM,+BAA+B,IACrC,QAAQ,iBAAiB,IACzB;IAAS,MAAM;IAAG;;MAQhB;MAIA;MAIA,eAAe;MAIf,mBAAmB;MAInB,cAAc"}
1
+ {"version":3,"file":"cursorPagination.d.mts","names":[],"sources":["../src/cursorPagination.ts"],"mappings":";;;KAiBK,WAAW,UAAU,QAAQ,mBAAmB,iBAAiB;EACpE,SAAS;EACT;EACA;EACA;EACA;;;iBAIc,eAAe,UAAU,QACvC,KAAK,eACL,OAAO,gBAAgB,IACvB,MAAM,KAAK,WAAW,gBACrB;EAAU;IAAS,MAAM;IAAG;;EAAoB,OAAO;EAAK,UAAU,eAAe;;iBACxE,eAAe,UAAU,QAAQ,mBAAmB,iBAAiB,IACnF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,WAAW,GAAG,KACnB;EAAU;IAAS,MAAM;IAAG;;EAAoB,OAAO;EAAK,UAAU,eAAe;;;cAkC3E,eAAe,UAAU,SAAS;;EAS7C,YACE,KAAK,eACL,MAAM,+BAA+B,KAAK,gBAAgB,IAC1D,QAAQ,iBAAiB,IACzB;IAAS,MAAM;IAAG;;MAQhB;MAIA;MAIA,eAAe;MAIf,mBAAmB;MAInB,cAAc"}
@@ -1,15 +1,19 @@
1
- import "./paginationUtils.js";
1
+ import { countQuery, queryEntities } from "./paginationUtils.js";
2
2
  import "joist-core";
3
3
  //#region src/cursorPagination.ts
4
- /** Returns a cursor connection shape for a generated query resolver. */
5
4
  async function paginateCursor(ctx, type, args) {
6
5
  const limit = args.first ?? args.last ?? 100;
7
6
  const baseFilter = args.filter ?? {};
8
7
  const filter = withCursorFilter(baseFilter, args);
9
8
  const orderBy = { id: args.last ? "DESC" : "ASC" };
10
- const nodes = await ctx.em.findGql(type, filter, {
9
+ const nodes = typeof type === "function" ? await ctx.em.findGql(type, filter, {
11
10
  limit,
12
11
  orderBy
12
+ }) : await queryEntities(ctx, {
13
+ ...withCursorQuery(type, args),
14
+ orderBy: [args.last ? { desc: type.select.id } : { asc: type.select.id }],
15
+ limit,
16
+ offset: void 0
13
17
  });
14
18
  const orderedNodes = args.last ? [...nodes].reverse() : nodes;
15
19
  const edges = orderedNodes.map((node) => ({
@@ -19,7 +23,10 @@ async function paginateCursor(ctx, type, args) {
19
23
  return {
20
24
  edges,
21
25
  nodes: orderedNodes,
22
- pageInfo: new CursorPageInfo(ctx, type, baseFilter, edges)
26
+ pageInfo: new CursorPageInfo(ctx, typeof type === "function" ? type : {
27
+ ...type,
28
+ orderBy: void 0
29
+ }, baseFilter, edges)
23
30
  };
24
31
  }
25
32
  /** Lazily computes cursor page fields. */
@@ -50,15 +57,27 @@ var CursorPageInfo = class {
50
57
  return this.#hasPreviousPagePromise ??= this.#countPastCursor("before", this.startCursor);
51
58
  }
52
59
  get totalCount() {
53
- return this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter);
60
+ return this.#totalCountPromise ??= typeof this.#type === "function" ? this.#ctx.em.findCount(this.#type, this.#filter) : countQuery(this.#ctx, this.#type);
54
61
  }
55
62
  /** Counts rows past a cursor only when the field is requested. */
56
63
  async #countPastCursor(direction, cursor) {
57
64
  if (!cursor) return false;
65
+ if (typeof this.#type !== "function") return await countQuery(this.#ctx, withCursorQuery(this.#type, { [direction]: cursor })) > 0;
58
66
  const filter = withCursorFilter(this.#filter, { [direction]: cursor });
59
67
  return await this.#ctx.em.findCount(this.#type, filter) > 0;
60
68
  }
61
69
  };
70
+ /** Adds both cursor bounds without replacing the query's existing conditions. */
71
+ function withCursorQuery(base, args) {
72
+ return {
73
+ ...base,
74
+ where: { and: [
75
+ base.where,
76
+ args.after ? base.select.id.gt(decodeCursor(args.after)) : void 0,
77
+ args.before ? base.select.id.lt(decodeCursor(args.before)) : void 0
78
+ ] }
79
+ };
80
+ }
62
81
  /** Adds cursor bounds to a filter. */
63
82
  function withCursorFilter(filter, args) {
64
83
  const cursor = args.after ?? args.before;
@@ -1 +1 @@
1
- {"version":3,"file":"cursorPagination.js","names":["#ctx","#type","#filter","#edges","#hasNextPagePromise","#countPastCursor","#hasPreviousPagePromise","#totalCountPromise"],"sources":["../src/cursorPagination.ts"],"sourcesContent":["import {\n type Entity,\n type FindGqlFilterOptions,\n type MaybeAbstractEntityConstructor,\n type ValueGraphQLFilter,\n} from \"joist-core\";\n\nimport { type ContextWithEm, type PaginationFilter, defaultLimit } from \"./paginationUtils.ts\";\n\ntype CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n first?: number | null;\n after?: string | null;\n last?: number | null;\n before?: string | null;\n};\n\n/** Returns a cursor connection shape for a generated query resolver. */\nexport async function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: CursorArgs<T, F>,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }> {\n const limit = args.first ?? args.last ?? defaultLimit;\n const baseFilter = (args.filter ?? {}) as PaginationFilter<T>;\n const filter = withCursorFilter(baseFilter, args);\n const orderBy = { id: args.last ? \"DESC\" : \"ASC\" } as FindGqlFilterOptions<T>[\"orderBy\"];\n const nodes = await ctx.em.findGql(type, filter, { limit, orderBy });\n const orderedNodes = args.last ? [...nodes].reverse() : nodes;\n const edges = orderedNodes.map((node) => ({ node, cursor: encodeCursor(String(node.id)) }));\n return {\n edges,\n nodes: orderedNodes,\n pageInfo: new CursorPageInfo(ctx, type, baseFilter, edges),\n };\n}\n\n/** Lazily computes cursor page fields. */\nexport class CursorPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #edges: { node: T; cursor: string }[];\n #filter: PaginationFilter<T>;\n #hasNextPagePromise: Promise<boolean> | undefined;\n #hasPreviousPagePromise: Promise<boolean> | undefined;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T>;\n\n constructor(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n filter: PaginationFilter<T>,\n edges: { node: T; cursor: string }[],\n ) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#edges = edges;\n }\n\n get startCursor(): string | undefined {\n return this.#edges[0]?.cursor;\n }\n\n get endCursor(): string | undefined {\n return this.#edges[this.#edges.length - 1]?.cursor;\n }\n\n get hasNextPage(): Promise<boolean> {\n return (this.#hasNextPagePromise ??= this.#countPastCursor(\"after\", this.endCursor));\n }\n\n get hasPreviousPage(): Promise<boolean> {\n return (this.#hasPreviousPagePromise ??= this.#countPastCursor(\"before\", this.startCursor));\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter));\n }\n\n /** Counts rows past a cursor only when the field is requested. */\n async #countPastCursor(direction: \"after\" | \"before\", cursor: string | undefined): Promise<boolean> {\n if (!cursor) return false;\n const filter = withCursorFilter(this.#filter, { [direction]: cursor });\n return (await this.#ctx.em.findCount(this.#type, filter)) > 0;\n }\n}\n\n/** Adds cursor bounds to a filter. */\nfunction withCursorFilter<T extends Entity>(\n filter: PaginationFilter<T>,\n args: { after?: string | null; before?: string | null },\n): PaginationFilter<T> {\n const cursor = args.after ?? args.before;\n if (!cursor) return filter;\n const op = args.after ? \"gt\" : \"lt\";\n return { ...filter, id: { [op]: decodeCursor(cursor) } as ValueGraphQLFilter<string> } as PaginationFilter<T>;\n}\n\n/** Encodes an entity id as an opaque cursor. */\nfunction encodeCursor(id: string): string {\n return Buffer.from(id).toString(\"base64\");\n}\n\n/** Decodes an opaque cursor back into an entity id. */\nfunction decodeCursor(cursor: string): string {\n return Buffer.from(cursor, \"base64\").toString(\"utf8\");\n}\n"],"mappings":";;;;AAkBA,eAAsB,eACpB,KACA,MACA,MAC4F;CAC5F,MAAM,QAAQ,KAAK,SAAS,KAAK,QAAA;CACjC,MAAM,aAAc,KAAK,UAAU,CAAC;CACpC,MAAM,SAAS,iBAAiB,YAAY,IAAI;CAChD,MAAM,UAAU,EAAE,IAAI,KAAK,OAAO,SAAS,MAAM;CACjD,MAAM,QAAQ,MAAM,IAAI,GAAG,QAAQ,MAAM,QAAQ;EAAE;EAAO;CAAQ,CAAC;CACnE,MAAM,eAAe,KAAK,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,IAAI;CACxD,MAAM,QAAQ,aAAa,KAAK,UAAU;EAAE;EAAM,QAAQ,aAAa,OAAO,KAAK,EAAE,CAAC;CAAE,EAAE;CAC1F,OAAO;EACL;EACA,OAAO;EACP,UAAU,IAAI,eAAe,KAAK,MAAM,YAAY,KAAK;CAC3D;AACF;;AAGA,IAAa,iBAAb,MAAuD;CACrD;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YACE,KACA,MACA,QACA,OACA;EACA,KAAKA,OAAO;EACZ,KAAKC,QAAQ;EACb,KAAKC,UAAU;EACf,KAAKC,SAAS;CAChB;CAEA,IAAI,cAAkC;EACpC,OAAO,KAAKA,OAAO,EAAE,EAAE;CACzB;CAEA,IAAI,YAAgC;EAClC,OAAO,KAAKA,OAAO,KAAKA,OAAO,SAAS,EAAE,EAAE;CAC9C;CAEA,IAAI,cAAgC;EAClC,OAAQ,KAAKC,wBAAwB,KAAKC,iBAAiB,SAAS,KAAK,SAAS;CACpF;CAEA,IAAI,kBAAoC;EACtC,OAAQ,KAAKC,4BAA4B,KAAKD,iBAAiB,UAAU,KAAK,WAAW;CAC3F;CAEA,IAAI,aAA8B;EAChC,OAAQ,KAAKE,uBAAuB,KAAKP,KAAK,GAAG,UAAU,KAAKC,OAAO,KAAKC,OAAO;CACrF;;CAGA,MAAMG,iBAAiB,WAA+B,QAA8C;EAClG,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,SAAS,iBAAiB,KAAKH,SAAS,GAAG,YAAY,OAAO,CAAC;EACrE,OAAQ,MAAM,KAAKF,KAAK,GAAG,UAAU,KAAKC,OAAO,MAAM,IAAK;CAC9D;AACF;;AAGA,SAAS,iBACP,QACA,MACqB;CACrB,MAAM,SAAS,KAAK,SAAS,KAAK;CAClC,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,KAAK,KAAK,QAAQ,OAAO;CAC/B,OAAO;EAAE,GAAG;EAAQ,IAAI,GAAG,KAAK,aAAa,MAAM,EAAE;CAAgC;AACvF;;AAGA,SAAS,aAAa,IAAoB;CACxC,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,SAAS,QAAQ;AAC1C;;AAGA,SAAS,aAAa,QAAwB;CAC5C,OAAO,OAAO,KAAK,QAAQ,QAAQ,CAAC,CAAC,SAAS,MAAM;AACtD"}
1
+ {"version":3,"file":"cursorPagination.js","names":["#ctx","#type","#filter","#edges","#hasNextPagePromise","#countPastCursor","#hasPreviousPagePromise","#totalCountPromise"],"sources":["../src/cursorPagination.ts"],"sourcesContent":["import {\n type Entity,\n type FindGqlFilterOptions,\n type IdOf,\n type MaybeAbstractEntityConstructor,\n type ValueGraphQLFilter,\n} from \"joist-core\";\n\nimport {\n type ContextWithEm,\n type PaginationFilter,\n type PaginationQuery,\n countQuery,\n defaultLimit,\n queryEntities,\n} from \"./paginationUtils.ts\";\n\ntype CursorArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n first?: number | null;\n after?: string | null;\n last?: number | null;\n before?: string | null;\n};\n\n/** Returns an ID-ordered cursor connection for a resolver, replacing the entity query's ordering. */\nexport function paginateCursor<T extends Entity>(\n ctx: ContextWithEm,\n query: PaginationQuery<T>,\n args: Omit<CursorArgs<T>, \"filter\">,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }>;\nexport function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: CursorArgs<T, F>,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }>;\nexport async function paginateCursor<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n args: CursorArgs<T, F>,\n): Promise<{ edges: { node: T; cursor: string }[]; nodes: T[]; pageInfo: CursorPageInfo<T> }> {\n const limit = args.first ?? args.last ?? defaultLimit;\n const baseFilter = (args.filter ?? {}) as PaginationFilter<T>;\n const filter = withCursorFilter(baseFilter, args);\n const orderBy = { id: args.last ? \"DESC\" : \"ASC\" } as FindGqlFilterOptions<T>[\"orderBy\"];\n const nodes =\n typeof type === \"function\"\n ? await ctx.em.findGql(type, filter, { limit, orderBy })\n : await queryEntities(ctx, {\n ...withCursorQuery(type, args),\n orderBy: [args.last ? { desc: type.select.id } : { asc: type.select.id }],\n limit,\n offset: undefined,\n });\n const orderedNodes = args.last ? [...nodes].reverse() : nodes;\n const edges = orderedNodes.map((node) => ({ node, cursor: encodeCursor(String(node.id)) }));\n return {\n edges,\n nodes: orderedNodes,\n pageInfo: new CursorPageInfo(\n ctx,\n typeof type === \"function\" ? type : { ...type, orderBy: undefined },\n baseFilter,\n edges,\n ),\n };\n}\n\n/** Lazily computes cursor page fields. */\nexport class CursorPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #edges: { node: T; cursor: string }[];\n #filter: PaginationFilter<T>;\n #hasNextPagePromise: Promise<boolean> | undefined;\n #hasPreviousPagePromise: Promise<boolean> | undefined;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>;\n\n constructor(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n filter: PaginationFilter<T>,\n edges: { node: T; cursor: string }[],\n ) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#edges = edges;\n }\n\n get startCursor(): string | undefined {\n return this.#edges[0]?.cursor;\n }\n\n get endCursor(): string | undefined {\n return this.#edges[this.#edges.length - 1]?.cursor;\n }\n\n get hasNextPage(): Promise<boolean> {\n return (this.#hasNextPagePromise ??= this.#countPastCursor(\"after\", this.endCursor));\n }\n\n get hasPreviousPage(): Promise<boolean> {\n return (this.#hasPreviousPagePromise ??= this.#countPastCursor(\"before\", this.startCursor));\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??=\n typeof this.#type === \"function\"\n ? this.#ctx.em.findCount(this.#type, this.#filter)\n : countQuery(this.#ctx, this.#type));\n }\n\n /** Counts rows past a cursor only when the field is requested. */\n async #countPastCursor(direction: \"after\" | \"before\", cursor: string | undefined): Promise<boolean> {\n if (!cursor) return false;\n if (typeof this.#type !== \"function\") {\n return (await countQuery(this.#ctx, withCursorQuery(this.#type, { [direction]: cursor }))) > 0;\n }\n const filter = withCursorFilter(this.#filter, { [direction]: cursor });\n return (await this.#ctx.em.findCount(this.#type, filter)) > 0;\n }\n}\n\n/** Adds both cursor bounds without replacing the query's existing conditions. */\nfunction withCursorQuery<T extends Entity>(\n base: PaginationQuery<T>,\n args: { after?: string | null; before?: string | null },\n): PaginationQuery<T> {\n return {\n ...base,\n where: {\n and: [\n base.where,\n args.after ? base.select.id.gt(decodeCursor(args.after) as IdOf<T>) : undefined,\n args.before ? base.select.id.lt(decodeCursor(args.before) as IdOf<T>) : undefined,\n ],\n },\n };\n}\n\n/** Adds cursor bounds to a filter. */\nfunction withCursorFilter<T extends Entity>(\n filter: PaginationFilter<T>,\n args: { after?: string | null; before?: string | null },\n): PaginationFilter<T> {\n const cursor = args.after ?? args.before;\n if (!cursor) return filter;\n const op = args.after ? \"gt\" : \"lt\";\n return { ...filter, id: { [op]: decodeCursor(cursor) } as ValueGraphQLFilter<string> } as PaginationFilter<T>;\n}\n\n/** Encodes an entity id as an opaque cursor. */\nfunction encodeCursor(id: string): string {\n return Buffer.from(id).toString(\"base64\");\n}\n\n/** Decodes an opaque cursor back into an entity id. */\nfunction decodeCursor(cursor: string): string {\n return Buffer.from(cursor, \"base64\").toString(\"utf8\");\n}\n"],"mappings":"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"}
@@ -1,14 +1,17 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./paginationUtils.cjs");
2
+ const require_paginationUtils = require("./paginationUtils.cjs");
3
3
  require("joist-core");
4
4
  //#region src/limitPagination.ts
5
- /** Returns a limit/offset page shape for a generated query resolver. */
6
5
  async function paginateLimit(ctx, type, args) {
7
6
  const limit = args.limit ?? 100;
8
7
  const offset = args.offset ?? 0;
9
8
  const filter = args.filter ?? {};
10
9
  return {
11
- entities: await ctx.em.findGql(type, filter, {
10
+ entities: typeof type === "function" ? await ctx.em.findGql(type, filter, {
11
+ limit,
12
+ offset
13
+ }) : await require_paginationUtils.queryEntities(ctx, {
14
+ ...type,
12
15
  limit,
13
16
  offset
14
17
  }),
@@ -38,7 +41,7 @@ var LimitPageInfo = class {
38
41
  return this.#page.offset > 0;
39
42
  }
40
43
  get totalCount() {
41
- return this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter);
44
+ return this.#totalCountPromise ??= typeof this.#type === "function" ? this.#ctx.em.findCount(this.#type, this.#filter) : require_paginationUtils.countQuery(this.#ctx, this.#type);
42
45
  }
43
46
  get nextPage() {
44
47
  return this.#nextPage();
@@ -1 +1 @@
1
- {"version":3,"file":"limitPagination.cjs","names":["#ctx","#type","#filter","#page","#hasNextPage","#totalCountPromise","#nextPage"],"sources":["../src/limitPagination.ts"],"sourcesContent":["import { type Entity, type MaybeAbstractEntityConstructor } from \"joist-core\";\n\nimport { type ContextWithEm, type PaginationFilter, defaultLimit } from \"./paginationUtils.ts\";\n\ntype LimitArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n limit?: number | null;\n offset?: number | null;\n};\ntype Page = { offset: number; limit: number };\n\n/** Returns a limit/offset page shape for a generated query resolver. */\nexport async function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: LimitArgs<T, F>,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }> {\n const limit = args.limit ?? defaultLimit;\n const offset = args.offset ?? 0;\n const filter = (args.filter ?? {}) as PaginationFilter<T>;\n const entities = await ctx.em.findGql(type, filter, { limit, offset });\n return { entities, pageInfo: new LimitPageInfo(ctx, type, filter, { limit, offset }) };\n}\n\n/** Lazily computes limit/offset page fields. */\nexport class LimitPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #filter: PaginationFilter<T>;\n #page: Page;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T>;\n\n constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, filter: PaginationFilter<T>, page: Page) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#page = page;\n }\n\n get hasNextPage(): Promise<boolean> {\n return this.#hasNextPage();\n }\n\n get hasPreviousPage(): boolean {\n return this.#page.offset > 0;\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter));\n }\n\n get nextPage(): Promise<number | undefined> {\n return this.#nextPage();\n }\n\n get currentPage(): number {\n return Math.floor(this.#page.offset / this.#page.limit) + 1;\n }\n\n /** Returns whether another page exists after this page. */\n async #hasNextPage(): Promise<boolean> {\n const total = await this.totalCount;\n const { offset, limit } = this.#page;\n return offset + limit < total;\n }\n\n /** Returns the next limit/offset page if there is one. */\n async #nextPage(): Promise<number | undefined> {\n if (!(await this.hasNextPage)) return undefined;\n return this.currentPage + 1;\n }\n}\n"],"mappings":";;;;;AAYA,eAAsB,cACpB,KACA,MACA,MACwD;CACxD,MAAM,QAAQ,KAAK,SAAA;CACnB,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,SAAU,KAAK,UAAU,CAAC;CAEhC,OAAO;EAAE,UAAA,MADc,IAAI,GAAG,QAAQ,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC;EAClD,UAAU,IAAI,cAAc,KAAK,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC;CAAE;AACvF;;AAGA,IAAa,gBAAb,MAAsD;CACpD;CACA;CACA;CACA;CACA;CAEA,YAAY,KAAoB,MAAyC,QAA6B,MAAY;EAChH,KAAKA,OAAO;EACZ,KAAKC,QAAQ;EACb,KAAKC,UAAU;EACf,KAAKC,QAAQ;CACf;CAEA,IAAI,cAAgC;EAClC,OAAO,KAAKC,aAAa;CAC3B;CAEA,IAAI,kBAA2B;EAC7B,OAAO,KAAKD,MAAM,SAAS;CAC7B;CAEA,IAAI,aAA8B;EAChC,OAAQ,KAAKE,uBAAuB,KAAKL,KAAK,GAAG,UAAU,KAAKC,OAAO,KAAKC,OAAO;CACrF;CAEA,IAAI,WAAwC;EAC1C,OAAO,KAAKI,UAAU;CACxB;CAEA,IAAI,cAAsB;EACxB,OAAO,KAAK,MAAM,KAAKH,MAAM,SAAS,KAAKA,MAAM,KAAK,IAAI;CAC5D;;CAGA,MAAMC,eAAiC;EACrC,MAAM,QAAQ,MAAM,KAAK;EACzB,MAAM,EAAE,QAAQ,UAAU,KAAKD;EAC/B,OAAO,SAAS,QAAQ;CAC1B;;CAGA,MAAMG,YAAyC;EAC7C,IAAI,CAAE,MAAM,KAAK,aAAc,OAAO,KAAA;EACtC,OAAO,KAAK,cAAc;CAC5B;AACF"}
1
+ {"version":3,"file":"limitPagination.cjs","names":["queryEntities","#ctx","#type","#filter","#page","#hasNextPage","#totalCountPromise","countQuery","#nextPage"],"sources":["../src/limitPagination.ts"],"sourcesContent":["import { type Entity, type MaybeAbstractEntityConstructor } from \"joist-core\";\n\nimport {\n type ContextWithEm,\n type PaginationFilter,\n type PaginationQuery,\n countQuery,\n defaultLimit,\n queryEntities,\n} from \"./paginationUtils.ts\";\n\ntype LimitArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n limit?: number | null;\n offset?: number | null;\n};\ntype Page = { offset: number; limit: number };\n\n/** Returns a limit/offset page shape for a resolver, preserving the entity query's ordering. */\nexport function paginateLimit<T extends Entity>(\n ctx: ContextWithEm,\n query: PaginationQuery<T>,\n args: Omit<LimitArgs<T>, \"filter\">,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }>;\nexport function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: LimitArgs<T, F>,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }>;\nexport async function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n args: LimitArgs<T, F>,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }> {\n const limit = args.limit ?? defaultLimit;\n const offset = args.offset ?? 0;\n const filter = (args.filter ?? {}) as PaginationFilter<T>;\n const entities =\n typeof type === \"function\"\n ? await ctx.em.findGql(type, filter, { limit, offset })\n : await queryEntities<T>(ctx, { ...type, limit, offset });\n return { entities, pageInfo: new LimitPageInfo(ctx, type, filter, { limit, offset }) };\n}\n\n/** Lazily computes limit/offset page fields. */\nexport class LimitPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #filter: PaginationFilter<T>;\n #page: Page;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>;\n\n constructor(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n filter: PaginationFilter<T>,\n page: Page,\n ) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#page = page;\n }\n\n get hasNextPage(): Promise<boolean> {\n return this.#hasNextPage();\n }\n\n get hasPreviousPage(): boolean {\n return this.#page.offset > 0;\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??=\n typeof this.#type === \"function\"\n ? this.#ctx.em.findCount(this.#type, this.#filter)\n : countQuery(this.#ctx, this.#type));\n }\n\n get nextPage(): Promise<number | undefined> {\n return this.#nextPage();\n }\n\n get currentPage(): number {\n return Math.floor(this.#page.offset / this.#page.limit) + 1;\n }\n\n /** Returns whether another page exists after this page. */\n async #hasNextPage(): Promise<boolean> {\n const total = await this.totalCount;\n const { offset, limit } = this.#page;\n return offset + limit < total;\n }\n\n /** Returns the next limit/offset page if there is one. */\n async #nextPage(): Promise<number | undefined> {\n if (!(await this.hasNextPage)) return undefined;\n return this.currentPage + 1;\n }\n}\n"],"mappings":";;;;AA6BA,eAAsB,cACpB,KACA,MACA,MACwD;CACxD,MAAM,QAAQ,KAAK,SAAA;CACnB,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,SAAU,KAAK,UAAU,CAAC;CAKhC,OAAO;EAAE,UAHP,OAAO,SAAS,aACZ,MAAM,IAAI,GAAG,QAAQ,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC,IACpD,MAAMA,wBAAAA,cAAiB,KAAK;GAAE,GAAG;GAAM;GAAO;EAAO,CAAC;EACzC,UAAU,IAAI,cAAc,KAAK,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC;CAAE;AACvF;;AAGA,IAAa,gBAAb,MAAsD;CACpD;CACA;CACA;CACA;CACA;CAEA,YACE,KACA,MACA,QACA,MACA;EACA,KAAKC,OAAO;EACZ,KAAKC,QAAQ;EACb,KAAKC,UAAU;EACf,KAAKC,QAAQ;CACf;CAEA,IAAI,cAAgC;EAClC,OAAO,KAAKC,aAAa;CAC3B;CAEA,IAAI,kBAA2B;EAC7B,OAAO,KAAKD,MAAM,SAAS;CAC7B;CAEA,IAAI,aAA8B;EAChC,OAAQ,KAAKE,uBACX,OAAO,KAAKJ,UAAU,aAClB,KAAKD,KAAK,GAAG,UAAU,KAAKC,OAAO,KAAKC,OAAO,IAC/CI,wBAAAA,WAAW,KAAKN,MAAM,KAAKC,KAAK;CACxC;CAEA,IAAI,WAAwC;EAC1C,OAAO,KAAKM,UAAU;CACxB;CAEA,IAAI,cAAsB;EACxB,OAAO,KAAK,MAAM,KAAKJ,MAAM,SAAS,KAAKA,MAAM,KAAK,IAAI;CAC5D;;CAGA,MAAMC,eAAiC;EACrC,MAAM,QAAQ,MAAM,KAAK;EACzB,MAAM,EAAE,QAAQ,UAAU,KAAKD;EAC/B,OAAO,SAAS,QAAQ;CAC1B;;CAGA,MAAMI,YAAyC;EAC7C,IAAI,CAAE,MAAM,KAAK,aAAc,OAAO,KAAA;EACtC,OAAO,KAAK,cAAc;CAC5B;AACF"}
@@ -1,4 +1,4 @@
1
- import { ContextWithEm, PaginationFilter } from "./paginationUtils.cjs";
1
+ import { ContextWithEm, PaginationFilter, PaginationQuery } from "./paginationUtils.cjs";
2
2
  import { Entity, MaybeAbstractEntityConstructor } from "joist-core";
3
3
  //#region src/limitPagination.d.ts
4
4
  type LimitArgs<T extends Entity, F extends object = PaginationFilter<T>> = {
@@ -10,7 +10,11 @@ type Page = {
10
10
  offset: number;
11
11
  limit: number;
12
12
  };
13
- /** Returns a limit/offset page shape for a generated query resolver. */
13
+ /** Returns a limit/offset page shape for a resolver, preserving the entity query's ordering. */
14
+ declare function paginateLimit<T extends Entity>(ctx: ContextWithEm, query: PaginationQuery<T>, args: Omit<LimitArgs<T>, "filter">): Promise<{
15
+ entities: T[];
16
+ pageInfo: LimitPageInfo<T>;
17
+ }>;
14
18
  declare function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, args: LimitArgs<T, F>): Promise<{
15
19
  entities: T[];
16
20
  pageInfo: LimitPageInfo<T>;
@@ -18,7 +22,7 @@ declare function paginateLimit<T extends Entity, F extends object = PaginationFi
18
22
  /** Lazily computes limit/offset page fields. */
19
23
  declare class LimitPageInfo<T extends Entity = Entity> {
20
24
  #private;
21
- constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, filter: PaginationFilter<T>, page: Page);
25
+ constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>, filter: PaginationFilter<T>, page: Page);
22
26
  get hasNextPage(): Promise<boolean>;
23
27
  get hasPreviousPage(): boolean;
24
28
  get totalCount(): Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"limitPagination.d.cts","names":[],"sources":["../src/limitPagination.ts"],"mappings":";;;KAIK,UAAU,UAAU,QAAQ,mBAAmB,iBAAiB;EACnE,SAAS;EACT;EACA;;KAEG;EAAS;EAAgB;;;iBAGR,cAAc,UAAU,QAAQ,mBAAmB,iBAAiB,IACxF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,UAAU,GAAG,KAClB;EAAU,UAAU;EAAK,UAAU,cAAc;;;cASvC,cAAc,UAAU,SAAS;;EAO5C,YAAY,KAAK,eAAe,MAAM,+BAA+B,IAAI,QAAQ,iBAAiB,IAAI,MAAM;MAOxG,eAAe;MAIf;MAIA,cAAc;MAId,YAAY;MAIZ"}
1
+ {"version":3,"file":"limitPagination.d.cts","names":[],"sources":["../src/limitPagination.ts"],"mappings":";;;KAWK,UAAU,UAAU,QAAQ,mBAAmB,iBAAiB;EACnE,SAAS;EACT;EACA;;KAEG;EAAS;EAAgB;;;iBAGd,cAAc,UAAU,QACtC,KAAK,eACL,OAAO,gBAAgB,IACvB,MAAM,KAAK,UAAU,gBACpB;EAAU,UAAU;EAAK,UAAU,cAAc;;iBACpC,cAAc,UAAU,QAAQ,mBAAmB,iBAAiB,IAClF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,UAAU,GAAG,KAClB;EAAU,UAAU;EAAK,UAAU,cAAc;;;cAiBvC,cAAc,UAAU,SAAS;;EAO5C,YACE,KAAK,eACL,MAAM,+BAA+B,KAAK,gBAAgB,IAC1D,QAAQ,iBAAiB,IACzB,MAAM;MAQJ,eAAe;MAIf;MAIA,cAAc;MAOd,YAAY;MAIZ"}
@@ -1,4 +1,4 @@
1
- import { ContextWithEm, PaginationFilter } from "./paginationUtils.mjs";
1
+ import { ContextWithEm, PaginationFilter, PaginationQuery } from "./paginationUtils.mjs";
2
2
  import { Entity, MaybeAbstractEntityConstructor } from "joist-core";
3
3
  //#region src/limitPagination.d.ts
4
4
  type LimitArgs<T extends Entity, F extends object = PaginationFilter<T>> = {
@@ -10,7 +10,11 @@ type Page = {
10
10
  offset: number;
11
11
  limit: number;
12
12
  };
13
- /** Returns a limit/offset page shape for a generated query resolver. */
13
+ /** Returns a limit/offset page shape for a resolver, preserving the entity query's ordering. */
14
+ declare function paginateLimit<T extends Entity>(ctx: ContextWithEm, query: PaginationQuery<T>, args: Omit<LimitArgs<T>, "filter">): Promise<{
15
+ entities: T[];
16
+ pageInfo: LimitPageInfo<T>;
17
+ }>;
14
18
  declare function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, args: LimitArgs<T, F>): Promise<{
15
19
  entities: T[];
16
20
  pageInfo: LimitPageInfo<T>;
@@ -18,7 +22,7 @@ declare function paginateLimit<T extends Entity, F extends object = PaginationFi
18
22
  /** Lazily computes limit/offset page fields. */
19
23
  declare class LimitPageInfo<T extends Entity = Entity> {
20
24
  #private;
21
- constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, filter: PaginationFilter<T>, page: Page);
25
+ constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>, filter: PaginationFilter<T>, page: Page);
22
26
  get hasNextPage(): Promise<boolean>;
23
27
  get hasPreviousPage(): boolean;
24
28
  get totalCount(): Promise<number>;
@@ -1 +1 @@
1
- {"version":3,"file":"limitPagination.d.mts","names":[],"sources":["../src/limitPagination.ts"],"mappings":";;;KAIK,UAAU,UAAU,QAAQ,mBAAmB,iBAAiB;EACnE,SAAS;EACT;EACA;;KAEG;EAAS;EAAgB;;;iBAGR,cAAc,UAAU,QAAQ,mBAAmB,iBAAiB,IACxF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,UAAU,GAAG,KAClB;EAAU,UAAU;EAAK,UAAU,cAAc;;;cASvC,cAAc,UAAU,SAAS;;EAO5C,YAAY,KAAK,eAAe,MAAM,+BAA+B,IAAI,QAAQ,iBAAiB,IAAI,MAAM;MAOxG,eAAe;MAIf;MAIA,cAAc;MAId,YAAY;MAIZ"}
1
+ {"version":3,"file":"limitPagination.d.mts","names":[],"sources":["../src/limitPagination.ts"],"mappings":";;;KAWK,UAAU,UAAU,QAAQ,mBAAmB,iBAAiB;EACnE,SAAS;EACT;EACA;;KAEG;EAAS;EAAgB;;;iBAGd,cAAc,UAAU,QACtC,KAAK,eACL,OAAO,gBAAgB,IACvB,MAAM,KAAK,UAAU,gBACpB;EAAU,UAAU;EAAK,UAAU,cAAc;;iBACpC,cAAc,UAAU,QAAQ,mBAAmB,iBAAiB,IAClF,KAAK,eACL,MAAM,+BAA+B,IACrC,MAAM,UAAU,GAAG,KAClB;EAAU,UAAU;EAAK,UAAU,cAAc;;;cAiBvC,cAAc,UAAU,SAAS;;EAO5C,YACE,KAAK,eACL,MAAM,+BAA+B,KAAK,gBAAgB,IAC1D,QAAQ,iBAAiB,IACzB,MAAM;MAQJ,eAAe;MAIf;MAIA,cAAc;MAOd,YAAY;MAIZ"}
@@ -1,13 +1,16 @@
1
- import "./paginationUtils.js";
1
+ import { countQuery, queryEntities } from "./paginationUtils.js";
2
2
  import "joist-core";
3
3
  //#region src/limitPagination.ts
4
- /** Returns a limit/offset page shape for a generated query resolver. */
5
4
  async function paginateLimit(ctx, type, args) {
6
5
  const limit = args.limit ?? 100;
7
6
  const offset = args.offset ?? 0;
8
7
  const filter = args.filter ?? {};
9
8
  return {
10
- entities: await ctx.em.findGql(type, filter, {
9
+ entities: typeof type === "function" ? await ctx.em.findGql(type, filter, {
10
+ limit,
11
+ offset
12
+ }) : await queryEntities(ctx, {
13
+ ...type,
11
14
  limit,
12
15
  offset
13
16
  }),
@@ -37,7 +40,7 @@ var LimitPageInfo = class {
37
40
  return this.#page.offset > 0;
38
41
  }
39
42
  get totalCount() {
40
- return this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter);
43
+ return this.#totalCountPromise ??= typeof this.#type === "function" ? this.#ctx.em.findCount(this.#type, this.#filter) : countQuery(this.#ctx, this.#type);
41
44
  }
42
45
  get nextPage() {
43
46
  return this.#nextPage();
@@ -1 +1 @@
1
- {"version":3,"file":"limitPagination.js","names":["#ctx","#type","#filter","#page","#hasNextPage","#totalCountPromise","#nextPage"],"sources":["../src/limitPagination.ts"],"sourcesContent":["import { type Entity, type MaybeAbstractEntityConstructor } from \"joist-core\";\n\nimport { type ContextWithEm, type PaginationFilter, defaultLimit } from \"./paginationUtils.ts\";\n\ntype LimitArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n limit?: number | null;\n offset?: number | null;\n};\ntype Page = { offset: number; limit: number };\n\n/** Returns a limit/offset page shape for a generated query resolver. */\nexport async function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: LimitArgs<T, F>,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }> {\n const limit = args.limit ?? defaultLimit;\n const offset = args.offset ?? 0;\n const filter = (args.filter ?? {}) as PaginationFilter<T>;\n const entities = await ctx.em.findGql(type, filter, { limit, offset });\n return { entities, pageInfo: new LimitPageInfo(ctx, type, filter, { limit, offset }) };\n}\n\n/** Lazily computes limit/offset page fields. */\nexport class LimitPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #filter: PaginationFilter<T>;\n #page: Page;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T>;\n\n constructor(ctx: ContextWithEm, type: MaybeAbstractEntityConstructor<T>, filter: PaginationFilter<T>, page: Page) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#page = page;\n }\n\n get hasNextPage(): Promise<boolean> {\n return this.#hasNextPage();\n }\n\n get hasPreviousPage(): boolean {\n return this.#page.offset > 0;\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??= this.#ctx.em.findCount(this.#type, this.#filter));\n }\n\n get nextPage(): Promise<number | undefined> {\n return this.#nextPage();\n }\n\n get currentPage(): number {\n return Math.floor(this.#page.offset / this.#page.limit) + 1;\n }\n\n /** Returns whether another page exists after this page. */\n async #hasNextPage(): Promise<boolean> {\n const total = await this.totalCount;\n const { offset, limit } = this.#page;\n return offset + limit < total;\n }\n\n /** Returns the next limit/offset page if there is one. */\n async #nextPage(): Promise<number | undefined> {\n if (!(await this.hasNextPage)) return undefined;\n return this.currentPage + 1;\n }\n}\n"],"mappings":";;;;AAYA,eAAsB,cACpB,KACA,MACA,MACwD;CACxD,MAAM,QAAQ,KAAK,SAAA;CACnB,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,SAAU,KAAK,UAAU,CAAC;CAEhC,OAAO;EAAE,UAAA,MADc,IAAI,GAAG,QAAQ,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC;EAClD,UAAU,IAAI,cAAc,KAAK,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC;CAAE;AACvF;;AAGA,IAAa,gBAAb,MAAsD;CACpD;CACA;CACA;CACA;CACA;CAEA,YAAY,KAAoB,MAAyC,QAA6B,MAAY;EAChH,KAAKA,OAAO;EACZ,KAAKC,QAAQ;EACb,KAAKC,UAAU;EACf,KAAKC,QAAQ;CACf;CAEA,IAAI,cAAgC;EAClC,OAAO,KAAKC,aAAa;CAC3B;CAEA,IAAI,kBAA2B;EAC7B,OAAO,KAAKD,MAAM,SAAS;CAC7B;CAEA,IAAI,aAA8B;EAChC,OAAQ,KAAKE,uBAAuB,KAAKL,KAAK,GAAG,UAAU,KAAKC,OAAO,KAAKC,OAAO;CACrF;CAEA,IAAI,WAAwC;EAC1C,OAAO,KAAKI,UAAU;CACxB;CAEA,IAAI,cAAsB;EACxB,OAAO,KAAK,MAAM,KAAKH,MAAM,SAAS,KAAKA,MAAM,KAAK,IAAI;CAC5D;;CAGA,MAAMC,eAAiC;EACrC,MAAM,QAAQ,MAAM,KAAK;EACzB,MAAM,EAAE,QAAQ,UAAU,KAAKD;EAC/B,OAAO,SAAS,QAAQ;CAC1B;;CAGA,MAAMG,YAAyC;EAC7C,IAAI,CAAE,MAAM,KAAK,aAAc,OAAO,KAAA;EACtC,OAAO,KAAK,cAAc;CAC5B;AACF"}
1
+ {"version":3,"file":"limitPagination.js","names":["#ctx","#type","#filter","#page","#hasNextPage","#totalCountPromise","#nextPage"],"sources":["../src/limitPagination.ts"],"sourcesContent":["import { type Entity, type MaybeAbstractEntityConstructor } from \"joist-core\";\n\nimport {\n type ContextWithEm,\n type PaginationFilter,\n type PaginationQuery,\n countQuery,\n defaultLimit,\n queryEntities,\n} from \"./paginationUtils.ts\";\n\ntype LimitArgs<T extends Entity, F extends object = PaginationFilter<T>> = {\n filter?: F | null;\n limit?: number | null;\n offset?: number | null;\n};\ntype Page = { offset: number; limit: number };\n\n/** Returns a limit/offset page shape for a resolver, preserving the entity query's ordering. */\nexport function paginateLimit<T extends Entity>(\n ctx: ContextWithEm,\n query: PaginationQuery<T>,\n args: Omit<LimitArgs<T>, \"filter\">,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }>;\nexport function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T>,\n args: LimitArgs<T, F>,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }>;\nexport async function paginateLimit<T extends Entity, F extends object = PaginationFilter<T>>(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n args: LimitArgs<T, F>,\n): Promise<{ entities: T[]; pageInfo: LimitPageInfo<T> }> {\n const limit = args.limit ?? defaultLimit;\n const offset = args.offset ?? 0;\n const filter = (args.filter ?? {}) as PaginationFilter<T>;\n const entities =\n typeof type === \"function\"\n ? await ctx.em.findGql(type, filter, { limit, offset })\n : await queryEntities<T>(ctx, { ...type, limit, offset });\n return { entities, pageInfo: new LimitPageInfo(ctx, type, filter, { limit, offset }) };\n}\n\n/** Lazily computes limit/offset page fields. */\nexport class LimitPageInfo<T extends Entity = Entity> {\n #ctx: ContextWithEm;\n #filter: PaginationFilter<T>;\n #page: Page;\n #totalCountPromise: Promise<number> | undefined;\n #type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>;\n\n constructor(\n ctx: ContextWithEm,\n type: MaybeAbstractEntityConstructor<T> | PaginationQuery<T>,\n filter: PaginationFilter<T>,\n page: Page,\n ) {\n this.#ctx = ctx;\n this.#type = type;\n this.#filter = filter;\n this.#page = page;\n }\n\n get hasNextPage(): Promise<boolean> {\n return this.#hasNextPage();\n }\n\n get hasPreviousPage(): boolean {\n return this.#page.offset > 0;\n }\n\n get totalCount(): Promise<number> {\n return (this.#totalCountPromise ??=\n typeof this.#type === \"function\"\n ? this.#ctx.em.findCount(this.#type, this.#filter)\n : countQuery(this.#ctx, this.#type));\n }\n\n get nextPage(): Promise<number | undefined> {\n return this.#nextPage();\n }\n\n get currentPage(): number {\n return Math.floor(this.#page.offset / this.#page.limit) + 1;\n }\n\n /** Returns whether another page exists after this page. */\n async #hasNextPage(): Promise<boolean> {\n const total = await this.totalCount;\n const { offset, limit } = this.#page;\n return offset + limit < total;\n }\n\n /** Returns the next limit/offset page if there is one. */\n async #nextPage(): Promise<number | undefined> {\n if (!(await this.hasNextPage)) return undefined;\n return this.currentPage + 1;\n }\n}\n"],"mappings":";;;AA6BA,eAAsB,cACpB,KACA,MACA,MACwD;CACxD,MAAM,QAAQ,KAAK,SAAA;CACnB,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,SAAU,KAAK,UAAU,CAAC;CAKhC,OAAO;EAAE,UAHP,OAAO,SAAS,aACZ,MAAM,IAAI,GAAG,QAAQ,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC,IACpD,MAAM,cAAiB,KAAK;GAAE,GAAG;GAAM;GAAO;EAAO,CAAC;EACzC,UAAU,IAAI,cAAc,KAAK,MAAM,QAAQ;GAAE;GAAO;EAAO,CAAC;CAAE;AACvF;;AAGA,IAAa,gBAAb,MAAsD;CACpD;CACA;CACA;CACA;CACA;CAEA,YACE,KACA,MACA,QACA,MACA;EACA,KAAKA,OAAO;EACZ,KAAKC,QAAQ;EACb,KAAKC,UAAU;EACf,KAAKC,QAAQ;CACf;CAEA,IAAI,cAAgC;EAClC,OAAO,KAAKC,aAAa;CAC3B;CAEA,IAAI,kBAA2B;EAC7B,OAAO,KAAKD,MAAM,SAAS;CAC7B;CAEA,IAAI,aAA8B;EAChC,OAAQ,KAAKE,uBACX,OAAO,KAAKJ,UAAU,aAClB,KAAKD,KAAK,GAAG,UAAU,KAAKC,OAAO,KAAKC,OAAO,IAC/C,WAAW,KAAKF,MAAM,KAAKC,KAAK;CACxC;CAEA,IAAI,WAAwC;EAC1C,OAAO,KAAKK,UAAU;CACxB;CAEA,IAAI,cAAsB;EACxB,OAAO,KAAK,MAAM,KAAKH,MAAM,SAAS,KAAKA,MAAM,KAAK,IAAI;CAC5D;;CAGA,MAAMC,eAAiC;EACrC,MAAM,QAAQ,MAAM,KAAK;EACzB,MAAM,EAAE,QAAQ,UAAU,KAAKD;EAC/B,OAAO,SAAS,QAAQ;CAC1B;;CAGA,MAAMG,YAAyC;EAC7C,IAAI,CAAE,MAAM,KAAK,aAAc,OAAO,KAAA;EACtC,OAAO,KAAK,cAAc;CAC5B;AACF"}
@@ -1,8 +1,34 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("joist-core");
2
+ let joist_core = require("joist-core");
3
3
  //#region src/paginationUtils.ts
4
4
  const defaultLimit = 100;
5
+ /** Executes an entity selection after pagination has been applied. */
6
+ function queryEntities(ctx, base) {
7
+ return ctx.em.query(base);
8
+ }
9
+ /** Counts the unpaginated rows, preserving distinct, grouping, and join semantics. */
10
+ async function countQuery(ctx, base) {
11
+ const select = { id: base.select.id };
12
+ if (Array.isArray(base.orderBy)) for (const [index, order] of base.orderBy.entries()) {
13
+ const expression = order?.asc ?? order?.desc;
14
+ if (expression) select[`order${index}`] = expression;
15
+ }
16
+ const rows = (0, joist_core.query)({
17
+ ...base,
18
+ select,
19
+ orderBy: void 0,
20
+ limit: void 0,
21
+ offset: void 0
22
+ });
23
+ const [result] = await ctx.em.query({
24
+ from: rows,
25
+ select: { count: rows.id.count() }
26
+ });
27
+ return result.count;
28
+ }
5
29
  //#endregion
30
+ exports.countQuery = countQuery;
6
31
  exports.defaultLimit = defaultLimit;
32
+ exports.queryEntities = queryEntities;
7
33
 
8
34
  //# sourceMappingURL=paginationUtils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"paginationUtils.cjs","names":[],"sources":["../src/paginationUtils.ts"],"sourcesContent":["import { type Entity, type EntityManager, type GraphQLFilterWithAlias } from \"joist-core\";\n\nexport type ContextWithEm = { em: EntityManager };\nexport type PaginationFilter<T extends Entity> = GraphQLFilterWithAlias<T>;\n\nexport const defaultLimit = 100;\n"],"mappings":";;;AAKA,MAAa,eAAe"}
1
+ {"version":3,"file":"paginationUtils.cjs","names":["query"],"sources":["../src/paginationUtils.ts"],"sourcesContent":["import {\n type Entity,\n type EntityColumn,\n type EntityManager,\n type ExprLike,\n type GraphQLFilterWithAlias,\n type Query,\n type TableFor,\n query,\n} from \"joist-core\";\n\nexport type ContextWithEm = { em: EntityManager };\nexport type PaginationFilter<T extends Entity> = GraphQLFilterWithAlias<T>;\nexport type PaginationQuery<T extends Entity> = Query<TableFor<T> & { readonly id: EntityColumn<T, never, string> }>;\n\nexport const defaultLimit = 100;\n\n/** Executes an entity selection after pagination has been applied. */\nexport function queryEntities<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<T[]> {\n // Widen the phantom entity type so em.query can resolve its conditional scope checks.\n return ctx.em.query(base as PaginationQuery<Entity>) as Promise<T[]>;\n}\n\n/** Counts the unpaginated rows, preserving distinct, grouping, and join semantics. */\nexport async function countQuery<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<number> {\n const select: { id: EntityColumn<T, never, string> } & Record<string, ExprLike<unknown>> = { id: base.select.id };\n // Keep references from expression ordering so removing ORDER BY does not prune a join.\n // I.e. ordering Authors by Book title must still count the joined Author/Book rows.\n if (Array.isArray(base.orderBy)) {\n for (const [index, order] of base.orderBy.entries()) {\n const expression = order?.asc ?? order?.desc;\n if (expression) select[`order${index}`] = expression;\n }\n }\n const rows = query({ ...base, select, orderBy: undefined, limit: undefined, offset: undefined });\n const [result] = await ctx.em.query({ from: rows, select: { count: rows.id.count() } });\n return result.count;\n}\n"],"mappings":";;;AAeA,MAAa,eAAe;;AAG5B,SAAgB,cAAgC,KAAoB,MAAwC;CAE1G,OAAO,IAAI,GAAG,MAAM,IAA+B;AACrD;;AAGA,eAAsB,WAA6B,KAAoB,MAA2C;CAChH,MAAM,SAAqF,EAAE,IAAI,KAAK,OAAO,GAAG;CAGhH,IAAI,MAAM,QAAQ,KAAK,OAAO,GAC5B,KAAK,MAAM,CAAC,OAAO,UAAU,KAAK,QAAQ,QAAQ,GAAG;EACnD,MAAM,aAAa,OAAO,OAAO,OAAO;EACxC,IAAI,YAAY,OAAO,QAAQ,WAAW;CAC5C;CAEF,MAAM,QAAA,GAAOA,WAAAA,MAAAA,CAAM;EAAE,GAAG;EAAM;EAAQ,SAAS,KAAA;EAAW,OAAO,KAAA;EAAW,QAAQ,KAAA;CAAU,CAAC;CAC/F,MAAM,CAAC,UAAU,MAAM,IAAI,GAAG,MAAM;EAAE,MAAM;EAAM,QAAQ,EAAE,OAAO,KAAK,GAAG,MAAM,EAAE;CAAE,CAAC;CACtF,OAAO,OAAO;AAChB"}
@@ -1,10 +1,17 @@
1
- import { Entity, EntityManager, GraphQLFilterWithAlias } from "joist-core";
1
+ import { Entity, EntityColumn, EntityManager, GraphQLFilterWithAlias, Query, TableFor } from "joist-core";
2
2
  //#region src/paginationUtils.d.ts
3
3
  type ContextWithEm = {
4
4
  em: EntityManager;
5
5
  };
6
6
  type PaginationFilter<T extends Entity> = GraphQLFilterWithAlias<T>;
7
+ type PaginationQuery<T extends Entity> = Query<TableFor<T> & {
8
+ readonly id: EntityColumn<T, never, string>;
9
+ }>;
7
10
  declare const defaultLimit = 100;
11
+ /** Executes an entity selection after pagination has been applied. */
12
+ declare function queryEntities<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<T[]>;
13
+ /** Counts the unpaginated rows, preserving distinct, grouping, and join semantics. */
14
+ declare function countQuery<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<number>;
8
15
  //#endregion
9
- export { ContextWithEm, PaginationFilter, defaultLimit };
16
+ export { ContextWithEm, PaginationFilter, PaginationQuery, countQuery, defaultLimit, queryEntities };
10
17
  //# sourceMappingURL=paginationUtils.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paginationUtils.d.cts","names":[],"sources":["../src/paginationUtils.ts"],"mappings":";;KAEY;EAAkB,IAAI;;KACtB,iBAAiB,UAAU,UAAU,uBAAuB;cAE3D"}
1
+ {"version":3,"file":"paginationUtils.d.cts","names":[],"sources":["../src/paginationUtils.ts"],"mappings":";;KAWY;EAAkB,IAAI;;KACtB,iBAAiB,UAAU,UAAU,uBAAuB;KAC5D,gBAAgB,UAAU,UAAU,MAAM,SAAS;WAAgB,IAAI,aAAa;;cAEnF;;iBAGG,cAAc,UAAU,QAAQ,KAAK,eAAe,MAAM,gBAAgB,KAAK,QAAQ;;iBAMjF,WAAW,UAAU,QAAQ,KAAK,eAAe,MAAM,gBAAgB,KAAK"}
@@ -1,10 +1,17 @@
1
- import { Entity, EntityManager, GraphQLFilterWithAlias } from "joist-core";
1
+ import { Entity, EntityColumn, EntityManager, GraphQLFilterWithAlias, Query, TableFor } from "joist-core";
2
2
  //#region src/paginationUtils.d.ts
3
3
  type ContextWithEm = {
4
4
  em: EntityManager;
5
5
  };
6
6
  type PaginationFilter<T extends Entity> = GraphQLFilterWithAlias<T>;
7
+ type PaginationQuery<T extends Entity> = Query<TableFor<T> & {
8
+ readonly id: EntityColumn<T, never, string>;
9
+ }>;
7
10
  declare const defaultLimit = 100;
11
+ /** Executes an entity selection after pagination has been applied. */
12
+ declare function queryEntities<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<T[]>;
13
+ /** Counts the unpaginated rows, preserving distinct, grouping, and join semantics. */
14
+ declare function countQuery<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<number>;
8
15
  //#endregion
9
- export { ContextWithEm, PaginationFilter, defaultLimit };
16
+ export { ContextWithEm, PaginationFilter, PaginationQuery, countQuery, defaultLimit, queryEntities };
10
17
  //# sourceMappingURL=paginationUtils.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paginationUtils.d.mts","names":[],"sources":["../src/paginationUtils.ts"],"mappings":";;KAEY;EAAkB,IAAI;;KACtB,iBAAiB,UAAU,UAAU,uBAAuB;cAE3D"}
1
+ {"version":3,"file":"paginationUtils.d.mts","names":[],"sources":["../src/paginationUtils.ts"],"mappings":";;KAWY;EAAkB,IAAI;;KACtB,iBAAiB,UAAU,UAAU,uBAAuB;KAC5D,gBAAgB,UAAU,UAAU,MAAM,SAAS;WAAgB,IAAI,aAAa;;cAEnF;;iBAGG,cAAc,UAAU,QAAQ,KAAK,eAAe,MAAM,gBAAgB,KAAK,QAAQ;;iBAMjF,WAAW,UAAU,QAAQ,KAAK,eAAe,MAAM,gBAAgB,KAAK"}
@@ -1,7 +1,31 @@
1
- import "joist-core";
1
+ import { query } from "joist-core";
2
2
  //#region src/paginationUtils.ts
3
3
  const defaultLimit = 100;
4
+ /** Executes an entity selection after pagination has been applied. */
5
+ function queryEntities(ctx, base) {
6
+ return ctx.em.query(base);
7
+ }
8
+ /** Counts the unpaginated rows, preserving distinct, grouping, and join semantics. */
9
+ async function countQuery(ctx, base) {
10
+ const select = { id: base.select.id };
11
+ if (Array.isArray(base.orderBy)) for (const [index, order] of base.orderBy.entries()) {
12
+ const expression = order?.asc ?? order?.desc;
13
+ if (expression) select[`order${index}`] = expression;
14
+ }
15
+ const rows = query({
16
+ ...base,
17
+ select,
18
+ orderBy: void 0,
19
+ limit: void 0,
20
+ offset: void 0
21
+ });
22
+ const [result] = await ctx.em.query({
23
+ from: rows,
24
+ select: { count: rows.id.count() }
25
+ });
26
+ return result.count;
27
+ }
4
28
  //#endregion
5
- export { defaultLimit };
29
+ export { countQuery, defaultLimit, queryEntities };
6
30
 
7
31
  //# sourceMappingURL=paginationUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paginationUtils.js","names":[],"sources":["../src/paginationUtils.ts"],"sourcesContent":["import { type Entity, type EntityManager, type GraphQLFilterWithAlias } from \"joist-core\";\n\nexport type ContextWithEm = { em: EntityManager };\nexport type PaginationFilter<T extends Entity> = GraphQLFilterWithAlias<T>;\n\nexport const defaultLimit = 100;\n"],"mappings":";;AAKA,MAAa,eAAe"}
1
+ {"version":3,"file":"paginationUtils.js","names":[],"sources":["../src/paginationUtils.ts"],"sourcesContent":["import {\n type Entity,\n type EntityColumn,\n type EntityManager,\n type ExprLike,\n type GraphQLFilterWithAlias,\n type Query,\n type TableFor,\n query,\n} from \"joist-core\";\n\nexport type ContextWithEm = { em: EntityManager };\nexport type PaginationFilter<T extends Entity> = GraphQLFilterWithAlias<T>;\nexport type PaginationQuery<T extends Entity> = Query<TableFor<T> & { readonly id: EntityColumn<T, never, string> }>;\n\nexport const defaultLimit = 100;\n\n/** Executes an entity selection after pagination has been applied. */\nexport function queryEntities<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<T[]> {\n // Widen the phantom entity type so em.query can resolve its conditional scope checks.\n return ctx.em.query(base as PaginationQuery<Entity>) as Promise<T[]>;\n}\n\n/** Counts the unpaginated rows, preserving distinct, grouping, and join semantics. */\nexport async function countQuery<T extends Entity>(ctx: ContextWithEm, base: PaginationQuery<T>): Promise<number> {\n const select: { id: EntityColumn<T, never, string> } & Record<string, ExprLike<unknown>> = { id: base.select.id };\n // Keep references from expression ordering so removing ORDER BY does not prune a join.\n // I.e. ordering Authors by Book title must still count the joined Author/Book rows.\n if (Array.isArray(base.orderBy)) {\n for (const [index, order] of base.orderBy.entries()) {\n const expression = order?.asc ?? order?.desc;\n if (expression) select[`order${index}`] = expression;\n }\n }\n const rows = query({ ...base, select, orderBy: undefined, limit: undefined, offset: undefined });\n const [result] = await ctx.em.query({ from: rows, select: { count: rows.id.count() } });\n return result.count;\n}\n"],"mappings":";;AAeA,MAAa,eAAe;;AAG5B,SAAgB,cAAgC,KAAoB,MAAwC;CAE1G,OAAO,IAAI,GAAG,MAAM,IAA+B;AACrD;;AAGA,eAAsB,WAA6B,KAAoB,MAA2C;CAChH,MAAM,SAAqF,EAAE,IAAI,KAAK,OAAO,GAAG;CAGhH,IAAI,MAAM,QAAQ,KAAK,OAAO,GAC5B,KAAK,MAAM,CAAC,OAAO,UAAU,KAAK,QAAQ,QAAQ,GAAG;EACnD,MAAM,aAAa,OAAO,OAAO,OAAO;EACxC,IAAI,YAAY,OAAO,QAAQ,WAAW;CAC5C;CAEF,MAAM,OAAO,MAAM;EAAE,GAAG;EAAM;EAAQ,SAAS,KAAA;EAAW,OAAO,KAAA;EAAW,QAAQ,KAAA;CAAU,CAAC;CAC/F,MAAM,CAAC,UAAU,MAAM,IAAI,GAAG,MAAM;EAAE,MAAM;EAAM,QAAQ,EAAE,OAAO,KAAK,GAAG,MAAM,EAAE;CAAE,CAAC;CACtF,OAAO,OAAO;AAChB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joist-graphql-resolver-utils",
3
- "version": "2.3.0-next.70",
3
+ "version": "2.3.0-next.72",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -84,8 +84,8 @@
84
84
  "build"
85
85
  ],
86
86
  "peerDependencies": {
87
- "joist-core": "2.3.0-next.70",
88
- "joist-test-utils": "2.3.0-next.70"
87
+ "joist-core": "2.3.0-next.72",
88
+ "joist-test-utils": "2.3.0-next.72"
89
89
  },
90
90
  "dependencies": {
91
91
  "graphql": "^17.0.2"