@danceroutine/tango-resources 1.11.3 → 1.11.4

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.
Files changed (49) hide show
  1. package/README.md +2 -2
  2. package/dist/CursorPaginator-B_8MhYZY.js +195 -0
  3. package/dist/CursorPaginator-B_8MhYZY.js.map +1 -0
  4. package/dist/CursorPaginator-CfeMQCdJ.d.ts +177 -0
  5. package/dist/OffsetPaginator-CaycvxJU.js +188 -0
  6. package/dist/OffsetPaginator-CaycvxJU.js.map +1 -0
  7. package/dist/context/index.d.ts +2 -0
  8. package/dist/context/index.js +2 -0
  9. package/dist/context-euBQvNRT.js +65 -0
  10. package/dist/context-euBQvNRT.js.map +1 -0
  11. package/dist/filters/index.d.ts +2 -0
  12. package/dist/filters/index.js +2 -0
  13. package/dist/filters-46d2Nr5C.js +287 -0
  14. package/dist/filters-46d2Nr5C.js.map +1 -0
  15. package/dist/index-Ac94YL5S.d.ts +24 -0
  16. package/dist/index-BJJalUDB.d.ts +54 -0
  17. package/dist/index-Bg6TtnmQ.d.ts +175 -0
  18. package/dist/index-C55GDIOn.d.ts +222 -0
  19. package/dist/index-CiIB-1Ac.d.ts +123 -0
  20. package/dist/index-DkJtxvKu.d.ts +164 -0
  21. package/dist/index.d.ts +10 -12
  22. package/dist/index.js +10 -1013
  23. package/dist/inferModelFieldParsers-2irv7j1T.js +70 -0
  24. package/dist/inferModelFieldParsers-2irv7j1T.js.map +1 -0
  25. package/dist/pagination/index.d.ts +3 -0
  26. package/dist/pagination/index.js +15 -0
  27. package/dist/pagination/index.js.map +1 -0
  28. package/dist/paginators/index.d.ts +9 -0
  29. package/dist/paginators/index.js +12 -0
  30. package/dist/paginators/index.js.map +1 -0
  31. package/dist/resource/index.d.ts +3 -0
  32. package/dist/resource/index.js +7 -0
  33. package/dist/resource/index.js.map +1 -0
  34. package/dist/serializer/index.d.ts +2 -0
  35. package/dist/serializer/index.js +2 -0
  36. package/dist/serializer-RSwlXWls.js +305 -0
  37. package/dist/serializer-RSwlXWls.js.map +1 -0
  38. package/dist/view/index.d.ts +2 -1
  39. package/dist/view/index.js +1 -1
  40. package/dist/{view-C9B5Lln3.js → view-CYdJAO4t.js} +7 -314
  41. package/dist/view-CYdJAO4t.js.map +1 -0
  42. package/dist/viewset/index.d.ts +9 -0
  43. package/dist/viewset/index.js +2 -0
  44. package/dist/viewset-C9j-2U29.js +227 -0
  45. package/dist/viewset-C9j-2U29.js.map +1 -0
  46. package/package.json +21 -17
  47. package/dist/index-D6sfTSEj.d.ts +0 -902
  48. package/dist/index.js.map +0 -1
  49. package/dist/view-C9B5Lln3.js.map +0 -1
package/README.md CHANGED
@@ -143,10 +143,10 @@ The package supports both root imports and domain-style imports:
143
143
 
144
144
  ```ts
145
145
  import { APIView, FilterSet, ModelSerializer, ModelViewSet, OffsetPaginator } from '@danceroutine/tango-resources';
146
- import { context, filters, pagination, serializer, view, viewset } from '@danceroutine/tango-resources';
146
+ import { context, filters, pagination, resource, serializer, view, viewset } from '@danceroutine/tango-resources';
147
147
  ```
148
148
 
149
- Available subpaths include `context`, `filters`, `pagination`, `paginators`, `serializer`, `viewset`, `view`, and `domain`.
149
+ Available subpaths include `context`, `filters`, `pagination`, `paginators`, `resource`, `serializer`, `view`, and `viewset`.
150
150
 
151
151
  ## Developer workflow
152
152
 
@@ -0,0 +1,195 @@
1
+ import { r as BasePaginator } from "./OffsetPaginator-CaycvxJU.js";
2
+ import "@danceroutine/tango-core";
3
+ import { z } from "zod";
4
+ //#region src/pagination/CursorPaginationInput.ts
5
+ const CursorPaginationInput = z.object({
6
+ limit: z.preprocess((value) => {
7
+ if (value === void 0 || value === null || value === "") return;
8
+ const parsed = Number.parseInt(String(value), 10);
9
+ if (!Number.isFinite(parsed) || parsed <= 0) return;
10
+ return parsed;
11
+ }, z.number().int().min(1).transform((value) => Math.min(value, 100)).optional()),
12
+ cursor: z.string().nullable().default(null),
13
+ ordering: z.string().optional()
14
+ });
15
+ //#endregion
16
+ //#region src/paginators/CursorPaginator.ts
17
+ /**
18
+ * Represents a single cursor page of results.
19
+ * Cursor pages do not expose numeric page navigation like offset pagination.
20
+ */
21
+ var CursorPage = class CursorPage {
22
+ results;
23
+ nextCursor;
24
+ previousCursor;
25
+ static BRAND = "tango.resources.cursor_page";
26
+ __tangoBrand = CursorPage.BRAND;
27
+ constructor(results, nextCursor, previousCursor) {
28
+ this.results = results;
29
+ this.nextCursor = nextCursor;
30
+ this.previousCursor = previousCursor;
31
+ }
32
+ static isCursorPage(value) {
33
+ return typeof value === "object" && value !== null && value.__tangoBrand === CursorPage.BRAND;
34
+ }
35
+ /** Whether a next cursor token exists. */
36
+ hasNext() {
37
+ return this.nextCursor !== null;
38
+ }
39
+ /** Whether a previous cursor token exists. */
40
+ hasPrevious() {
41
+ return this.previousCursor !== null;
42
+ }
43
+ nextPageNumber() {
44
+ return null;
45
+ }
46
+ previousPageNumber() {
47
+ return null;
48
+ }
49
+ startIndex() {
50
+ return 0;
51
+ }
52
+ endIndex() {
53
+ return this.results.length;
54
+ }
55
+ };
56
+ /**
57
+ * Cursor-based paginator for stable forward navigation with opaque cursor tokens.
58
+ * It supports `limit`, `cursor`, and `ordering` query params and returns DRF-style
59
+ * paginated envelopes with cursor links.
60
+ */
61
+ var CursorPaginator = class CursorPaginator extends BasePaginator {
62
+ queryset;
63
+ perPage;
64
+ cursorField;
65
+ static BRAND = "tango.resources.cursor_paginator";
66
+ __tangoBrand = CursorPaginator.BRAND;
67
+ limit;
68
+ cursor = null;
69
+ direction = "asc";
70
+ nextCursor = null;
71
+ previousCursor = null;
72
+ constructor(queryset, perPage = 25, cursorField = "id") {
73
+ super();
74
+ this.queryset = queryset;
75
+ this.perPage = perPage;
76
+ this.cursorField = cursorField;
77
+ this.limit = perPage;
78
+ }
79
+ /**
80
+ * Narrow an unknown value to `CursorPaginator`.
81
+ */
82
+ static isCursorPaginator(value) {
83
+ return typeof value === "object" && value !== null && value.__tangoBrand === CursorPaginator.BRAND;
84
+ }
85
+ /**
86
+ * Parse cursor pagination parameters from Tango query params.
87
+ */
88
+ parse(params) {
89
+ const parsed = CursorPaginationInput.parse({
90
+ limit: params.get("limit") ?? void 0,
91
+ cursor: params.get("cursor"),
92
+ ordering: params.get("ordering") ?? void 0
93
+ });
94
+ this.limit = parsed.limit ?? this.perPage;
95
+ this.cursor = parsed.cursor;
96
+ const ordering = parsed.ordering;
97
+ if (ordering) {
98
+ const parsedDirection = ordering.startsWith("-") ? "desc" : "asc";
99
+ const parsedField = ordering.startsWith("-") ? ordering.slice(1) : ordering;
100
+ this.direction = parsedField === String(this.cursorField) ? parsedDirection : "asc";
101
+ } else this.direction = "asc";
102
+ }
103
+ /**
104
+ * Parse params and return compatibility `{ limit, offset }` shape.
105
+ */
106
+ parseParams(params) {
107
+ this.parse(params);
108
+ return {
109
+ limit: this.limit,
110
+ offset: 0
111
+ };
112
+ }
113
+ /**
114
+ * Build a paginated response payload with cursor links.
115
+ */
116
+ needsTotalCount() {
117
+ return false;
118
+ }
119
+ toResponse(results, _context) {
120
+ const response = { results: this.resolveQueryResultRows(results) };
121
+ if (this.nextCursor) response.next = this.buildPageLink(this.nextCursor);
122
+ if (this.previousCursor) response.previous = this.buildPageLink(this.previousCursor);
123
+ return response;
124
+ }
125
+ /**
126
+ * Backward-compatible alias for `toResponse`.
127
+ */
128
+ getPaginatedResponse(results, _totalCount) {
129
+ return this.toResponse(results);
130
+ }
131
+ /**
132
+ * Apply cursor constraints and ordering to a queryset.
133
+ */
134
+ apply(queryset) {
135
+ let qs = queryset.limit(this.limit + 1);
136
+ if (this.cursor) {
137
+ const decoded = this.decodeCursor(this.cursor);
138
+ if (decoded.field !== String(this.cursorField)) throw new Error("Invalid cursor: field mismatch");
139
+ const lookup = this.direction === "asc" ? "__gt" : "__lt";
140
+ const filterInput = { [`${String(this.cursorField)}${lookup}`]: decoded.value };
141
+ qs = qs.filter(filterInput);
142
+ }
143
+ const orderToken = this.direction === "asc" ? String(this.cursorField) : `-${String(this.cursorField)}`;
144
+ return qs.orderBy(orderToken);
145
+ }
146
+ /**
147
+ * Fetch the next cursor page.
148
+ */
149
+ async paginate(cursor) {
150
+ const appliedCursor = cursor ?? this.cursor;
151
+ this.cursor = appliedCursor;
152
+ const fetched = await this.apply(this.queryset).fetch();
153
+ const results = this.resolveQueryResultRows(fetched);
154
+ const hasMore = results.length > this.limit;
155
+ if (hasMore) results.pop();
156
+ this.previousCursor = appliedCursor ?? null;
157
+ const last = results.at(-1);
158
+ this.nextCursor = hasMore && last ? this.encodeCursor(last) : null;
159
+ return new CursorPage(results, this.nextCursor, this.previousCursor);
160
+ }
161
+ /**
162
+ * Cursor paginators only support page `1` as an entry point.
163
+ */
164
+ async getPage(page) {
165
+ if (page !== 1) throw new Error("CursorPaginator only supports getPage(1). Use cursor pagination for subsequent pages.");
166
+ return this.paginate();
167
+ }
168
+ buildPageLink(cursor) {
169
+ const orderingToken = this.direction === "asc" ? String(this.cursorField) : `-${String(this.cursorField)}`;
170
+ return `?limit=${this.limit}&cursor=${encodeURIComponent(cursor)}&ordering=${encodeURIComponent(orderingToken)}`;
171
+ }
172
+ encodeCursor(item) {
173
+ const payload = {
174
+ v: 1,
175
+ field: String(this.cursorField),
176
+ dir: this.direction,
177
+ value: item[this.cursorField]
178
+ };
179
+ return Buffer.from(JSON.stringify(payload), "utf-8").toString("base64");
180
+ }
181
+ decodeCursor(cursor) {
182
+ let parsed;
183
+ try {
184
+ parsed = JSON.parse(Buffer.from(cursor, "base64").toString("utf-8"));
185
+ } catch {
186
+ throw new Error("Invalid cursor: malformed token");
187
+ }
188
+ if (!parsed || typeof parsed !== "object" || parsed.v !== 1 || typeof parsed.field !== "string" || parsed.dir !== "asc" && parsed.dir !== "desc" || !("value" in parsed)) throw new Error("Invalid cursor: unsupported payload");
189
+ return parsed;
190
+ }
191
+ };
192
+ //#endregion
193
+ export { CursorPaginationInput as n, CursorPaginator as t };
194
+
195
+ //# sourceMappingURL=CursorPaginator-B_8MhYZY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CursorPaginator-B_8MhYZY.js","names":[],"sources":["../src/pagination/CursorPaginationInput.ts","../src/paginators/CursorPaginator.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport type CursorPaginationInputValue = {\n limit?: number;\n cursor: string | null;\n ordering?: string;\n};\n\nexport const CursorPaginationInput: z.ZodType<CursorPaginationInputValue> = z.object({\n limit: z.preprocess(\n (value) => {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n const parsed = Number.parseInt(String(value), 10);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n return undefined;\n }\n return parsed;\n },\n z\n .number()\n .int()\n .min(1)\n .transform((value) => Math.min(value, 100))\n .optional()\n ),\n cursor: z.string().nullable().default(null),\n ordering: z.string().optional(),\n});\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { FilterInput, QueryResult, QuerySet } from '@danceroutine/tango-orm';\nimport { BasePaginator } from '../pagination/BasePaginator';\nimport type { Paginator, Page } from '../pagination/Paginator';\nimport type { CursorPaginatedResponse } from '../pagination/PaginatedResponse';\nimport { CursorPaginationInput } from '../pagination/CursorPaginationInput';\n\ntype CursorDirection = 'asc' | 'desc';\n\ntype CursorPayload = {\n v: 1;\n field: string;\n dir: CursorDirection;\n value: unknown;\n};\n\n/**\n * Represents a single cursor page of results.\n * Cursor pages do not expose numeric page navigation like offset pagination.\n */\nclass CursorPage<T> implements Page<T> {\n static readonly BRAND = 'tango.resources.cursor_page' as const;\n readonly __tangoBrand: typeof CursorPage.BRAND = CursorPage.BRAND;\n\n constructor(\n public readonly results: T[],\n public readonly nextCursor: string | null,\n public readonly previousCursor: string | null\n ) {}\n\n static isCursorPage<T>(value: unknown): value is CursorPage<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CursorPage.BRAND\n );\n }\n\n /** Whether a next cursor token exists. */\n hasNext(): boolean {\n return this.nextCursor !== null;\n }\n\n /** Whether a previous cursor token exists. */\n hasPrevious(): boolean {\n return this.previousCursor !== null;\n }\n\n nextPageNumber(): number | null {\n return null;\n }\n\n previousPageNumber(): number | null {\n return null;\n }\n\n startIndex(): number {\n return 0;\n }\n\n endIndex(): number {\n return this.results.length;\n }\n}\n\n/**\n * Cursor-based paginator for stable forward navigation with opaque cursor tokens.\n * It supports `limit`, `cursor`, and `ordering` query params and returns DRF-style\n * paginated envelopes with cursor links.\n */\nexport class CursorPaginator<T extends Record<string, unknown>>\n extends BasePaginator\n implements Paginator<T, T, CursorPaginatedResponse<T>>\n{\n static readonly BRAND = 'tango.resources.cursor_paginator' as const;\n readonly __tangoBrand: typeof CursorPaginator.BRAND = CursorPaginator.BRAND;\n private limit: number;\n private cursor: string | null = null;\n private direction: CursorDirection = 'asc';\n private nextCursor: string | null = null;\n private previousCursor: string | null = null;\n\n constructor(\n private queryset: QuerySet<T>,\n private perPage: number = 25,\n private cursorField: keyof T = 'id' as keyof T\n ) {\n super();\n this.limit = perPage;\n }\n\n /**\n * Narrow an unknown value to `CursorPaginator`.\n */\n static isCursorPaginator<T extends Record<string, unknown>>(value: unknown): value is CursorPaginator<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === CursorPaginator.BRAND\n );\n }\n\n /**\n * Parse cursor pagination parameters from Tango query params.\n */\n parse(params: TangoQueryParams): void {\n const parsed = CursorPaginationInput.parse({\n limit: params.get('limit') ?? undefined,\n cursor: params.get('cursor'),\n ordering: params.get('ordering') ?? undefined,\n });\n\n this.limit = parsed.limit ?? this.perPage;\n this.cursor = parsed.cursor;\n\n const ordering = parsed.ordering;\n if (ordering) {\n const parsedDirection: CursorDirection = ordering.startsWith('-') ? 'desc' : 'asc';\n const parsedField = ordering.startsWith('-') ? ordering.slice(1) : ordering;\n this.direction = parsedField === String(this.cursorField) ? parsedDirection : 'asc';\n } else {\n this.direction = 'asc';\n }\n }\n\n /**\n * Parse params and return compatibility `{ limit, offset }` shape.\n */\n parseParams(params: TangoQueryParams): { limit: number; offset: number } {\n this.parse(params);\n return { limit: this.limit, offset: 0 };\n }\n\n /**\n * Build a paginated response payload with cursor links.\n */\n needsTotalCount(): boolean {\n return false;\n }\n\n toResponse<TResult>(\n results: readonly TResult[] | QueryResult<TResult>,\n _context?: { totalCount?: number; params?: TangoQueryParams }\n ): CursorPaginatedResponse<TResult> {\n const response: CursorPaginatedResponse<TResult> = { results: this.resolveQueryResultRows(results) };\n if (this.nextCursor) {\n response.next = this.buildPageLink(this.nextCursor);\n }\n if (this.previousCursor) {\n response.previous = this.buildPageLink(this.previousCursor);\n }\n return response;\n }\n\n /**\n * Backward-compatible alias for `toResponse`.\n */\n getPaginatedResponse<TResult>(\n results: readonly TResult[] | QueryResult<TResult>,\n _totalCount?: number\n ): CursorPaginatedResponse<TResult> {\n return this.toResponse(results);\n }\n\n /**\n * Apply cursor constraints and ordering to a queryset.\n */\n apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(\n queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>\n ): QuerySet<T, TBaseResult, TSourceModel, THydrated> {\n let qs = queryset.limit(this.limit + 1);\n if (this.cursor) {\n const decoded = this.decodeCursor(this.cursor);\n if (decoded.field !== String(this.cursorField)) {\n throw new Error('Invalid cursor: field mismatch');\n }\n const lookup = this.direction === 'asc' ? '__gt' : '__lt';\n const fieldLookup = `${String(this.cursorField)}${lookup}`;\n const filterInput = { [fieldLookup]: decoded.value } as FilterInput<T>;\n qs = qs.filter(filterInput);\n }\n const orderToken = this.direction === 'asc' ? String(this.cursorField) : `-${String(this.cursorField)}`;\n return qs.orderBy(orderToken as keyof T | `-${string}`);\n }\n\n /**\n * Fetch the next cursor page.\n */\n async paginate(cursor?: string): Promise<Page<T>> {\n const appliedCursor = cursor ?? this.cursor;\n this.cursor = appliedCursor;\n const fetched = await this.apply(this.queryset).fetch();\n const results = this.resolveQueryResultRows(fetched);\n const hasMore = results.length > this.limit;\n\n if (hasMore) {\n results.pop();\n }\n\n this.previousCursor = appliedCursor ?? null;\n const last = results.at(-1);\n this.nextCursor = hasMore && last ? this.encodeCursor(last) : null;\n\n return new CursorPage(results, this.nextCursor, this.previousCursor);\n }\n\n /**\n * Cursor paginators only support page `1` as an entry point.\n */\n async getPage(page: number): Promise<Page<T>> {\n if (page !== 1) {\n throw new Error('CursorPaginator only supports getPage(1). Use cursor pagination for subsequent pages.');\n }\n return this.paginate();\n }\n\n private buildPageLink(cursor: string): string {\n const orderingToken = this.direction === 'asc' ? String(this.cursorField) : `-${String(this.cursorField)}`;\n return `?limit=${this.limit}&cursor=${encodeURIComponent(cursor)}&ordering=${encodeURIComponent(orderingToken)}`;\n }\n\n private encodeCursor(item: T): string {\n const payload: CursorPayload = {\n v: 1,\n field: String(this.cursorField),\n dir: this.direction,\n value: item[this.cursorField],\n };\n return Buffer.from(JSON.stringify(payload), 'utf-8').toString('base64');\n }\n\n private decodeCursor(cursor: string): CursorPayload {\n let parsed: unknown;\n try {\n parsed = JSON.parse(Buffer.from(cursor, 'base64').toString('utf-8'));\n } catch {\n throw new Error('Invalid cursor: malformed token');\n }\n\n if (\n !parsed ||\n typeof parsed !== 'object' ||\n (parsed as { v?: unknown }).v !== 1 ||\n typeof (parsed as { field?: unknown }).field !== 'string' ||\n ((parsed as { dir?: unknown }).dir !== 'asc' && (parsed as { dir?: unknown }).dir !== 'desc') ||\n !('value' in parsed)\n ) {\n throw new Error('Invalid cursor: unsupported payload');\n }\n\n return parsed as CursorPayload;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ import { TangoQueryParams } from "@danceroutine/tango-core";
2
+ import { QueryResult, QuerySet } from "@danceroutine/tango-orm";
3
+
4
+ //#region src/pagination/BasePaginator.d.ts
5
+ declare abstract class BasePaginator {
6
+ protected resolveQueryResultRows<T>(rows: readonly T[] | QueryResult<T>): T[];
7
+ }
8
+ //#endregion
9
+ //#region src/pagination/PaginatedResponse.d.ts
10
+ interface BasePaginatedResponse<T> {
11
+ results: T[];
12
+ next?: string | null;
13
+ previous?: string | null;
14
+ }
15
+ interface OffsetPaginatedResponse<T> extends BasePaginatedResponse<T> {
16
+ count?: number;
17
+ }
18
+ interface CursorPaginatedResponse<T> extends BasePaginatedResponse<T> {
19
+ count?: never;
20
+ }
21
+ type PaginatedResponse<T> = OffsetPaginatedResponse<T> | CursorPaginatedResponse<T>;
22
+ //#endregion
23
+ //#region src/pagination/Paginator.d.ts
24
+ interface Page<T> {
25
+ results: T[];
26
+ hasNext(): boolean;
27
+ hasPrevious(): boolean;
28
+ nextPageNumber(): number | null;
29
+ previousPageNumber(): number | null;
30
+ startIndex(): number;
31
+ endIndex(): number;
32
+ }
33
+ interface Paginator<TModel extends Record<string, unknown>, TResult = TModel, TResponse extends PaginatedResponse<TResult> = PaginatedResponse<TResult>> {
34
+ parse(params: TangoQueryParams): void;
35
+ apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(queryset: QuerySet<TModel, TBaseResult, TSourceModel, THydrated>): QuerySet<TModel, TBaseResult, TSourceModel, THydrated>;
36
+ needsTotalCount(): boolean;
37
+ toResponse(results: readonly TResult[] | QueryResult<TResult>, context?: {
38
+ totalCount?: number;
39
+ params?: TangoQueryParams;
40
+ }): TResponse;
41
+ }
42
+ //#endregion
43
+ //#region src/paginators/OffsetPaginator.d.ts
44
+ /**
45
+ * Offset/limit paginator modelled after DRF's LimitOffsetPagination.
46
+ * Handles parsing limit/offset/page from URL query params and building
47
+ * the paginated response envelope with next/previous links.
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const paginator = new OffsetPaginator(queryset);
52
+ * const { limit, offset } = paginator.parseParams(searchParams);
53
+ * const results = await queryset.limit(limit).offset(offset).fetchAll();
54
+ * const response = paginator.getPaginatedResponse(results, totalCount);
55
+ * ```
56
+ */
57
+ declare class OffsetPaginator<T extends Record<string, unknown>> extends BasePaginator implements Paginator<T, T, OffsetPaginatedResponse<T>> {
58
+ private queryset;
59
+ private perPage;
60
+ static readonly BRAND: "tango.resources.offset_paginator";
61
+ readonly __tangoBrand: typeof OffsetPaginator.BRAND;
62
+ private limit;
63
+ private offset;
64
+ constructor(queryset: QuerySet<T>, perPage?: number);
65
+ /**
66
+ * Narrow an unknown value to `OffsetPaginator`.
67
+ */
68
+ static isOffsetPaginator<T extends Record<string, unknown>>(value: unknown): value is OffsetPaginator<T>;
69
+ /**
70
+ * Parse limit, offset, and page from Tango query params.
71
+ * If `page` is provided, it's converted to an offset.
72
+ * Stores parsed values for use by getPaginatedResponse.
73
+ */
74
+ parse(params: TangoQueryParams): void;
75
+ /**
76
+ * Parse params and return `{ limit, offset }` for compatibility callers.
77
+ */
78
+ parseParams(params: TangoQueryParams): {
79
+ limit: number;
80
+ offset: number;
81
+ };
82
+ /**
83
+ * Build a DRF-style paginated response with count, next, and previous links.
84
+ * Uses the limit/offset stored from the most recent parseParams call.
85
+ */
86
+ needsTotalCount(): boolean;
87
+ toResponse<TResult>(results: readonly TResult[] | QueryResult<TResult>, context?: {
88
+ totalCount?: number;
89
+ params?: TangoQueryParams;
90
+ }): OffsetPaginatedResponse<TResult>;
91
+ /**
92
+ * Backward-compatible alias for `toResponse`.
93
+ */
94
+ getPaginatedResponse<TResult>(results: readonly TResult[] | QueryResult<TResult>, totalCount?: number, params?: TangoQueryParams): OffsetPaginatedResponse<TResult>;
95
+ /**
96
+ * Apply current limit/offset to a queryset.
97
+ */
98
+ apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>): QuerySet<T, TBaseResult, TSourceModel, THydrated>;
99
+ /**
100
+ * Fetch a 1-based page number from the bound queryset.
101
+ */
102
+ paginate(page: number): Promise<Page<T>>;
103
+ /**
104
+ * Fetch a 1-based page and return page metadata.
105
+ */
106
+ getPage(page: number): Promise<Page<T>>;
107
+ /**
108
+ * Count total rows for the current queryset state.
109
+ */
110
+ count(): Promise<number>;
111
+ private buildPageLink;
112
+ }
113
+ //#endregion
114
+ //#region src/paginators/CursorPaginator.d.ts
115
+ /**
116
+ * Cursor-based paginator for stable forward navigation with opaque cursor tokens.
117
+ * It supports `limit`, `cursor`, and `ordering` query params and returns DRF-style
118
+ * paginated envelopes with cursor links.
119
+ */
120
+ declare class CursorPaginator<T extends Record<string, unknown>> extends BasePaginator implements Paginator<T, T, CursorPaginatedResponse<T>> {
121
+ private queryset;
122
+ private perPage;
123
+ private cursorField;
124
+ static readonly BRAND: "tango.resources.cursor_paginator";
125
+ readonly __tangoBrand: typeof CursorPaginator.BRAND;
126
+ private limit;
127
+ private cursor;
128
+ private direction;
129
+ private nextCursor;
130
+ private previousCursor;
131
+ constructor(queryset: QuerySet<T>, perPage?: number, cursorField?: keyof T);
132
+ /**
133
+ * Narrow an unknown value to `CursorPaginator`.
134
+ */
135
+ static isCursorPaginator<T extends Record<string, unknown>>(value: unknown): value is CursorPaginator<T>;
136
+ /**
137
+ * Parse cursor pagination parameters from Tango query params.
138
+ */
139
+ parse(params: TangoQueryParams): void;
140
+ /**
141
+ * Parse params and return compatibility `{ limit, offset }` shape.
142
+ */
143
+ parseParams(params: TangoQueryParams): {
144
+ limit: number;
145
+ offset: number;
146
+ };
147
+ /**
148
+ * Build a paginated response payload with cursor links.
149
+ */
150
+ needsTotalCount(): boolean;
151
+ toResponse<TResult>(results: readonly TResult[] | QueryResult<TResult>, _context?: {
152
+ totalCount?: number;
153
+ params?: TangoQueryParams;
154
+ }): CursorPaginatedResponse<TResult>;
155
+ /**
156
+ * Backward-compatible alias for `toResponse`.
157
+ */
158
+ getPaginatedResponse<TResult>(results: readonly TResult[] | QueryResult<TResult>, _totalCount?: number): CursorPaginatedResponse<TResult>;
159
+ /**
160
+ * Apply cursor constraints and ordering to a queryset.
161
+ */
162
+ apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>): QuerySet<T, TBaseResult, TSourceModel, THydrated>;
163
+ /**
164
+ * Fetch the next cursor page.
165
+ */
166
+ paginate(cursor?: string): Promise<Page<T>>;
167
+ /**
168
+ * Cursor paginators only support page `1` as an entry point.
169
+ */
170
+ getPage(page: number): Promise<Page<T>>;
171
+ private buildPageLink;
172
+ private encodeCursor;
173
+ private decodeCursor;
174
+ }
175
+ //#endregion
176
+ export { BasePaginatedResponse as a, PaginatedResponse as c, Paginator as i, BasePaginator as l, OffsetPaginator as n, CursorPaginatedResponse as o, Page as r, OffsetPaginatedResponse as s, CursorPaginator as t };
177
+ //# sourceMappingURL=CursorPaginator-CfeMQCdJ.d.ts.map
@@ -0,0 +1,188 @@
1
+ import "@danceroutine/tango-core";
2
+ import { QueryResult } from "@danceroutine/tango-orm";
3
+ import { z } from "zod";
4
+ //#region src/pagination/BasePaginator.ts
5
+ var BasePaginator = class {
6
+ resolveQueryResultRows(rows) {
7
+ if (QueryResult.isQueryResult(rows)) return rows.toArray();
8
+ return [...rows];
9
+ }
10
+ };
11
+ //#endregion
12
+ //#region src/pagination/OffsetPaginationInput.ts
13
+ const OffsetPaginationInput = z.object({
14
+ limit: z.coerce.number().int().min(1).default(25).transform((value) => Math.min(value, 100)),
15
+ offset: z.coerce.number().int().min(0).default(0),
16
+ page: z.coerce.number().int().min(1).optional()
17
+ });
18
+ //#endregion
19
+ //#region src/paginators/OffsetPaginator.ts
20
+ var OffsetPage = class OffsetPage {
21
+ results;
22
+ pageNumber;
23
+ perPage;
24
+ totalCount;
25
+ static BRAND = "tango.resources.offset_page";
26
+ __tangoBrand = OffsetPage.BRAND;
27
+ constructor(results, pageNumber, perPage, totalCount) {
28
+ this.results = results;
29
+ this.pageNumber = pageNumber;
30
+ this.perPage = perPage;
31
+ this.totalCount = totalCount;
32
+ }
33
+ static isOffsetPage(value) {
34
+ return typeof value === "object" && value !== null && value.__tangoBrand === OffsetPage.BRAND;
35
+ }
36
+ /** Whether a next page exists based on known total count. */
37
+ hasNext() {
38
+ if (this.totalCount === void 0) return false;
39
+ return this.endIndex() < this.totalCount;
40
+ }
41
+ /** Whether a previous page exists. */
42
+ hasPrevious() {
43
+ return this.pageNumber > 1;
44
+ }
45
+ /** The next page number, if available. */
46
+ nextPageNumber() {
47
+ return this.hasNext() ? this.pageNumber + 1 : null;
48
+ }
49
+ /** The previous page number, if available. */
50
+ previousPageNumber() {
51
+ return this.hasPrevious() ? this.pageNumber - 1 : null;
52
+ }
53
+ /** Zero-based start index of this page in the full result set. */
54
+ startIndex() {
55
+ return (this.pageNumber - 1) * this.perPage;
56
+ }
57
+ /** Exclusive end index of this page in the full result set. */
58
+ endIndex() {
59
+ return this.startIndex() + this.results.length;
60
+ }
61
+ };
62
+ /**
63
+ * Offset/limit paginator modelled after DRF's LimitOffsetPagination.
64
+ * Handles parsing limit/offset/page from URL query params and building
65
+ * the paginated response envelope with next/previous links.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const paginator = new OffsetPaginator(queryset);
70
+ * const { limit, offset } = paginator.parseParams(searchParams);
71
+ * const results = await queryset.limit(limit).offset(offset).fetchAll();
72
+ * const response = paginator.getPaginatedResponse(results, totalCount);
73
+ * ```
74
+ */
75
+ var OffsetPaginator = class OffsetPaginator extends BasePaginator {
76
+ queryset;
77
+ perPage;
78
+ static BRAND = "tango.resources.offset_paginator";
79
+ __tangoBrand = OffsetPaginator.BRAND;
80
+ limit = 25;
81
+ offset = 0;
82
+ constructor(queryset, perPage = 25) {
83
+ super();
84
+ this.queryset = queryset;
85
+ this.perPage = perPage;
86
+ this.limit = perPage;
87
+ }
88
+ /**
89
+ * Narrow an unknown value to `OffsetPaginator`.
90
+ */
91
+ static isOffsetPaginator(value) {
92
+ return typeof value === "object" && value !== null && value.__tangoBrand === OffsetPaginator.BRAND;
93
+ }
94
+ /**
95
+ * Parse limit, offset, and page from Tango query params.
96
+ * If `page` is provided, it's converted to an offset.
97
+ * Stores parsed values for use by getPaginatedResponse.
98
+ */
99
+ parse(params) {
100
+ const input = {
101
+ limit: params.get("limit") ?? void 0,
102
+ offset: params.get("offset") ?? void 0,
103
+ page: params.get("page") ?? void 0
104
+ };
105
+ const parsed = OffsetPaginationInput.parse(input);
106
+ if (parsed.page) parsed.offset = (parsed.page - 1) * parsed.limit;
107
+ this.limit = parsed.limit;
108
+ this.offset = parsed.offset;
109
+ }
110
+ /**
111
+ * Parse params and return `{ limit, offset }` for compatibility callers.
112
+ */
113
+ parseParams(params) {
114
+ this.parse(params);
115
+ return {
116
+ limit: this.limit,
117
+ offset: this.offset
118
+ };
119
+ }
120
+ /**
121
+ * Build a DRF-style paginated response with count, next, and previous links.
122
+ * Uses the limit/offset stored from the most recent parseParams call.
123
+ */
124
+ needsTotalCount() {
125
+ return true;
126
+ }
127
+ toResponse(results, context) {
128
+ const totalCount = context?.totalCount;
129
+ const response = { results: this.resolveQueryResultRows(results) };
130
+ if (totalCount !== void 0) {
131
+ response.count = totalCount;
132
+ if (this.offset + this.limit < totalCount) response.next = this.buildPageLink(this.offset + this.limit, context?.params);
133
+ if (this.offset > 0) {
134
+ const prevOffset = Math.max(0, this.offset - this.limit);
135
+ response.previous = this.buildPageLink(prevOffset, context?.params);
136
+ }
137
+ }
138
+ return response;
139
+ }
140
+ /**
141
+ * Backward-compatible alias for `toResponse`.
142
+ */
143
+ getPaginatedResponse(results, totalCount, params) {
144
+ return this.toResponse(results, {
145
+ totalCount,
146
+ params
147
+ });
148
+ }
149
+ /**
150
+ * Apply current limit/offset to a queryset.
151
+ */
152
+ apply(queryset) {
153
+ return queryset.limit(this.limit).offset(this.offset);
154
+ }
155
+ /**
156
+ * Fetch a 1-based page number from the bound queryset.
157
+ */
158
+ async paginate(page) {
159
+ return this.getPage(page);
160
+ }
161
+ /**
162
+ * Fetch a 1-based page and return page metadata.
163
+ */
164
+ async getPage(page) {
165
+ const offset = (page - 1) * this.perPage;
166
+ const results = await this.queryset.offset(offset).limit(this.perPage).fetch();
167
+ const totalCount = await this.count();
168
+ return new OffsetPage(this.resolveQueryResultRows(results), page, this.perPage, totalCount);
169
+ }
170
+ /**
171
+ * Count total rows for the current queryset state.
172
+ */
173
+ async count() {
174
+ return this.queryset.count();
175
+ }
176
+ buildPageLink(offset, params) {
177
+ if (!params) return `?limit=${this.limit}&offset=${offset}`;
178
+ return params.withValues({
179
+ limit: this.limit,
180
+ offset,
181
+ page: null
182
+ }).toRelativeURL();
183
+ }
184
+ };
185
+ //#endregion
186
+ export { OffsetPaginationInput as n, BasePaginator as r, OffsetPaginator as t };
187
+
188
+ //# sourceMappingURL=OffsetPaginator-CaycvxJU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OffsetPaginator-CaycvxJU.js","names":[],"sources":["../src/pagination/BasePaginator.ts","../src/pagination/OffsetPaginationInput.ts","../src/paginators/OffsetPaginator.ts"],"sourcesContent":["import { QueryResult } from '@danceroutine/tango-orm';\n\nexport abstract class BasePaginator {\n protected resolveQueryResultRows<T>(rows: readonly T[] | QueryResult<T>): T[] {\n if (QueryResult.isQueryResult<T>(rows)) {\n return rows.toArray();\n }\n return [...rows];\n }\n}\n","import { z } from 'zod';\n\nexport type OffsetPaginationInputValue = {\n limit: number;\n offset: number;\n page?: number;\n};\n\nexport const OffsetPaginationInput: z.ZodType<OffsetPaginationInputValue> = z.object({\n limit: z.coerce\n .number()\n .int()\n .min(1)\n .default(25)\n .transform((value) => Math.min(value, 100)),\n offset: z.coerce.number().int().min(0).default(0),\n page: z.coerce.number().int().min(1).optional(),\n});\n","import { TangoQueryParams } from '@danceroutine/tango-core';\nimport type { QueryResult, QuerySet } from '@danceroutine/tango-orm';\nimport { BasePaginator } from '../pagination/BasePaginator';\nimport type { Paginator, Page } from '../pagination/Paginator';\nimport type { OffsetPaginatedResponse } from '../pagination/PaginatedResponse';\nimport { OffsetPaginationInput } from '../pagination/OffsetPaginationInput';\n\nclass OffsetPage<T> implements Page<T> {\n static readonly BRAND = 'tango.resources.offset_page' as const;\n readonly __tangoBrand: typeof OffsetPage.BRAND = OffsetPage.BRAND;\n\n constructor(\n public readonly results: T[],\n private readonly pageNumber: number,\n private readonly perPage: number,\n private readonly totalCount?: number\n ) {}\n\n static isOffsetPage<T>(value: unknown): value is OffsetPage<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === OffsetPage.BRAND\n );\n }\n\n /** Whether a next page exists based on known total count. */\n hasNext(): boolean {\n if (this.totalCount === undefined) {\n return false;\n }\n return this.endIndex() < this.totalCount;\n }\n\n /** Whether a previous page exists. */\n hasPrevious(): boolean {\n return this.pageNumber > 1;\n }\n\n /** The next page number, if available. */\n nextPageNumber(): number | null {\n return this.hasNext() ? this.pageNumber + 1 : null;\n }\n\n /** The previous page number, if available. */\n previousPageNumber(): number | null {\n return this.hasPrevious() ? this.pageNumber - 1 : null;\n }\n\n /** Zero-based start index of this page in the full result set. */\n startIndex(): number {\n return (this.pageNumber - 1) * this.perPage;\n }\n\n /** Exclusive end index of this page in the full result set. */\n endIndex(): number {\n return this.startIndex() + this.results.length;\n }\n}\n\n/**\n * Offset/limit paginator modelled after DRF's LimitOffsetPagination.\n * Handles parsing limit/offset/page from URL query params and building\n * the paginated response envelope with next/previous links.\n *\n * @example\n * ```typescript\n * const paginator = new OffsetPaginator(queryset);\n * const { limit, offset } = paginator.parseParams(searchParams);\n * const results = await queryset.limit(limit).offset(offset).fetchAll();\n * const response = paginator.getPaginatedResponse(results, totalCount);\n * ```\n */\nexport class OffsetPaginator<T extends Record<string, unknown>>\n extends BasePaginator\n implements Paginator<T, T, OffsetPaginatedResponse<T>>\n{\n static readonly BRAND = 'tango.resources.offset_paginator' as const;\n readonly __tangoBrand: typeof OffsetPaginator.BRAND = OffsetPaginator.BRAND;\n private limit = 25;\n private offset = 0;\n\n constructor(\n private queryset: QuerySet<T>,\n private perPage: number = 25\n ) {\n super();\n this.limit = perPage;\n }\n\n /**\n * Narrow an unknown value to `OffsetPaginator`.\n */\n static isOffsetPaginator<T extends Record<string, unknown>>(value: unknown): value is OffsetPaginator<T> {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { __tangoBrand?: unknown }).__tangoBrand === OffsetPaginator.BRAND\n );\n }\n\n /**\n * Parse limit, offset, and page from Tango query params.\n * If `page` is provided, it's converted to an offset.\n * Stores parsed values for use by getPaginatedResponse.\n */\n parse(params: TangoQueryParams): void {\n const input = {\n limit: params.get('limit') ?? undefined,\n offset: params.get('offset') ?? undefined,\n page: params.get('page') ?? undefined,\n };\n\n const parsed = OffsetPaginationInput.parse(input);\n\n if (parsed.page) {\n parsed.offset = (parsed.page - 1) * parsed.limit;\n }\n\n this.limit = parsed.limit;\n this.offset = parsed.offset;\n }\n\n /**\n * Parse params and return `{ limit, offset }` for compatibility callers.\n */\n parseParams(params: TangoQueryParams): { limit: number; offset: number } {\n this.parse(params);\n return { limit: this.limit, offset: this.offset };\n }\n\n /**\n * Build a DRF-style paginated response with count, next, and previous links.\n * Uses the limit/offset stored from the most recent parseParams call.\n */\n needsTotalCount(): boolean {\n return true;\n }\n\n toResponse<TResult>(\n results: readonly TResult[] | QueryResult<TResult>,\n context?: { totalCount?: number; params?: TangoQueryParams }\n ): OffsetPaginatedResponse<TResult> {\n const totalCount = context?.totalCount;\n const response: OffsetPaginatedResponse<TResult> = { results: this.resolveQueryResultRows(results) };\n\n if (totalCount !== undefined) {\n response.count = totalCount;\n\n if (this.offset + this.limit < totalCount) {\n response.next = this.buildPageLink(this.offset + this.limit, context?.params);\n }\n\n if (this.offset > 0) {\n const prevOffset = Math.max(0, this.offset - this.limit);\n response.previous = this.buildPageLink(prevOffset, context?.params);\n }\n }\n\n return response;\n }\n\n /**\n * Backward-compatible alias for `toResponse`.\n */\n getPaginatedResponse<TResult>(\n results: readonly TResult[] | QueryResult<TResult>,\n totalCount?: number,\n params?: TangoQueryParams\n ): OffsetPaginatedResponse<TResult> {\n return this.toResponse(results, { totalCount, params });\n }\n\n /**\n * Apply current limit/offset to a queryset.\n */\n apply<TBaseResult extends Record<string, unknown>, TSourceModel, THydrated extends Record<string, unknown>>(\n queryset: QuerySet<T, TBaseResult, TSourceModel, THydrated>\n ): QuerySet<T, TBaseResult, TSourceModel, THydrated> {\n return queryset.limit(this.limit).offset(this.offset);\n }\n\n /**\n * Fetch a 1-based page number from the bound queryset.\n */\n async paginate(page: number): Promise<Page<T>> {\n return this.getPage(page);\n }\n\n /**\n * Fetch a 1-based page and return page metadata.\n */\n async getPage(page: number): Promise<Page<T>> {\n const offset = (page - 1) * this.perPage;\n const results = await this.queryset.offset(offset).limit(this.perPage).fetch();\n\n const totalCount = await this.count();\n\n return new OffsetPage(this.resolveQueryResultRows(results), page, this.perPage, totalCount);\n }\n\n /**\n * Count total rows for the current queryset state.\n */\n async count(): Promise<number> {\n return this.queryset.count();\n }\n\n private buildPageLink(offset: number, params?: TangoQueryParams): string {\n if (!params) {\n return `?limit=${this.limit}&offset=${offset}`;\n }\n\n return params\n .withValues({\n limit: this.limit,\n offset,\n page: null,\n })\n .toRelativeURL();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as BaseUser, r as RequestContext } from "../index-BJJalUDB.js";
2
+ export { type BaseUser, RequestContext };
@@ -0,0 +1,2 @@
1
+ import { n as RequestContext } from "../context-euBQvNRT.js";
2
+ export { RequestContext };