@open-loyalty/mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +654 -0
  3. package/dist/client/http.d.ts +8 -0
  4. package/dist/client/http.js +69 -0
  5. package/dist/config.d.ts +17 -0
  6. package/dist/config.js +40 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +20 -0
  9. package/dist/server.d.ts +4 -0
  10. package/dist/server.js +334 -0
  11. package/dist/tools/achievement.d.ts +983 -0
  12. package/dist/tools/achievement.js +311 -0
  13. package/dist/tools/admin.d.ts +153 -0
  14. package/dist/tools/admin.js +193 -0
  15. package/dist/tools/analytics.d.ts +162 -0
  16. package/dist/tools/analytics.js +245 -0
  17. package/dist/tools/apikey.d.ts +72 -0
  18. package/dist/tools/apikey.js +78 -0
  19. package/dist/tools/audit.d.ts +107 -0
  20. package/dist/tools/audit.js +90 -0
  21. package/dist/tools/badge.d.ts +135 -0
  22. package/dist/tools/badge.js +165 -0
  23. package/dist/tools/campaign.d.ts +1775 -0
  24. package/dist/tools/campaign.js +724 -0
  25. package/dist/tools/export.d.ts +110 -0
  26. package/dist/tools/export.js +147 -0
  27. package/dist/tools/import.d.ts +110 -0
  28. package/dist/tools/import.js +126 -0
  29. package/dist/tools/index.d.ts +22 -0
  30. package/dist/tools/index.js +527 -0
  31. package/dist/tools/member.d.ts +345 -0
  32. package/dist/tools/member.js +358 -0
  33. package/dist/tools/member.test.d.ts +1 -0
  34. package/dist/tools/member.test.js +213 -0
  35. package/dist/tools/points.d.ts +188 -0
  36. package/dist/tools/points.js +306 -0
  37. package/dist/tools/points.test.d.ts +1 -0
  38. package/dist/tools/points.test.js +292 -0
  39. package/dist/tools/reward.d.ts +261 -0
  40. package/dist/tools/reward.js +371 -0
  41. package/dist/tools/reward.test.d.ts +1 -0
  42. package/dist/tools/reward.test.js +240 -0
  43. package/dist/tools/role.d.ts +161 -0
  44. package/dist/tools/role.js +160 -0
  45. package/dist/tools/segment.d.ts +797 -0
  46. package/dist/tools/segment.js +299 -0
  47. package/dist/tools/store.d.ts +101 -0
  48. package/dist/tools/store.js +117 -0
  49. package/dist/tools/tierset.d.ts +288 -0
  50. package/dist/tools/tierset.js +244 -0
  51. package/dist/tools/transaction.d.ts +357 -0
  52. package/dist/tools/transaction.js +242 -0
  53. package/dist/tools/transaction.test.d.ts +1 -0
  54. package/dist/tools/transaction.test.js +235 -0
  55. package/dist/tools/wallet-type.d.ts +32 -0
  56. package/dist/tools/wallet-type.js +58 -0
  57. package/dist/tools/webhook.d.ts +179 -0
  58. package/dist/tools/webhook.js +171 -0
  59. package/dist/types/schemas/achievement.d.ts +1116 -0
  60. package/dist/types/schemas/achievement.js +172 -0
  61. package/dist/types/schemas/admin.d.ts +263 -0
  62. package/dist/types/schemas/admin.js +99 -0
  63. package/dist/types/schemas/analytics.d.ts +542 -0
  64. package/dist/types/schemas/analytics.js +130 -0
  65. package/dist/types/schemas/badge.d.ts +131 -0
  66. package/dist/types/schemas/badge.js +48 -0
  67. package/dist/types/schemas/campaign.d.ts +2005 -0
  68. package/dist/types/schemas/campaign.js +189 -0
  69. package/dist/types/schemas/common.d.ts +52 -0
  70. package/dist/types/schemas/common.js +26 -0
  71. package/dist/types/schemas/export.d.ts +127 -0
  72. package/dist/types/schemas/export.js +43 -0
  73. package/dist/types/schemas/import.d.ts +344 -0
  74. package/dist/types/schemas/import.js +68 -0
  75. package/dist/types/schemas/member.d.ts +443 -0
  76. package/dist/types/schemas/member.js +92 -0
  77. package/dist/types/schemas/points.d.ts +188 -0
  78. package/dist/types/schemas/points.js +54 -0
  79. package/dist/types/schemas/reward.d.ts +278 -0
  80. package/dist/types/schemas/reward.js +69 -0
  81. package/dist/types/schemas/role.d.ts +260 -0
  82. package/dist/types/schemas/role.js +75 -0
  83. package/dist/types/schemas/segment.d.ts +592 -0
  84. package/dist/types/schemas/segment.js +114 -0
  85. package/dist/types/schemas/tierset.d.ts +552 -0
  86. package/dist/types/schemas/tierset.js +87 -0
  87. package/dist/types/schemas/transaction.d.ts +1022 -0
  88. package/dist/types/schemas/transaction.js +63 -0
  89. package/dist/types/schemas/wallet-type.d.ts +99 -0
  90. package/dist/types/schemas/wallet-type.js +17 -0
  91. package/dist/types/schemas/webhook.d.ts +195 -0
  92. package/dist/types/schemas/webhook.js +39 -0
  93. package/dist/utils/cursor.d.ts +84 -0
  94. package/dist/utils/cursor.js +117 -0
  95. package/dist/utils/errors.d.ts +12 -0
  96. package/dist/utils/errors.js +69 -0
  97. package/dist/utils/pagination.d.ts +39 -0
  98. package/dist/utils/pagination.js +77 -0
  99. package/package.json +65 -0
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import { TotalSchema } from "./common.js";
3
+ export const LabelSchema = z.object({
4
+ key: z.string(),
5
+ value: z.string(),
6
+ });
7
+ export const TransactionHeaderSchema = z.object({
8
+ documentNumber: z.string(),
9
+ purchasedAt: z.string(),
10
+ documentType: z.enum(["sell", "return"]).optional(),
11
+ linkedDocumentNumber: z.string().optional(),
12
+ purchasePlace: z.string().optional(),
13
+ labels: z.array(LabelSchema).optional(),
14
+ });
15
+ export const TransactionItemSchema = z.object({
16
+ sku: z.string(),
17
+ name: z.string(),
18
+ grossValue: z.number(),
19
+ category: z.string(),
20
+ quantity: z.number().optional(),
21
+ highPrecisionQuantity: z.number().optional(),
22
+ maker: z.string().optional(),
23
+ labels: z.array(LabelSchema).optional(),
24
+ });
25
+ export const TransactionAddressSchema = z.object({
26
+ street: z.string().optional(),
27
+ address1: z.string().optional(),
28
+ province: z.string().optional(),
29
+ city: z.string().optional(),
30
+ postal: z.string().optional(),
31
+ country: z.string().optional(),
32
+ });
33
+ export const TransactionCustomerDataSchema = z.object({
34
+ customerId: z.string().optional(),
35
+ email: z.string().optional(),
36
+ name: z.string().optional(),
37
+ phone: z.string().optional(),
38
+ loyaltyCardNumber: z.string().optional(),
39
+ nip: z.string().optional(),
40
+ address: TransactionAddressSchema.optional(),
41
+ });
42
+ export const TransactionSchema = z.object({
43
+ transactionId: z.string(),
44
+ header: TransactionHeaderSchema,
45
+ items: z.array(TransactionItemSchema),
46
+ customerData: TransactionCustomerDataSchema.optional(),
47
+ customerId: z.string().optional(),
48
+ matched: z.boolean(),
49
+ grossValue: z.number(),
50
+ pointsEarned: z.number().optional(),
51
+ createdAt: z.string().optional(),
52
+ });
53
+ export const TransactionCreateInputSchema = z.object({
54
+ storeCode: z.string().optional(),
55
+ header: TransactionHeaderSchema,
56
+ items: z.array(TransactionItemSchema),
57
+ customerData: TransactionCustomerDataSchema.optional(),
58
+ channelId: z.string().optional(),
59
+ });
60
+ export const TransactionListResponseSchema = z.object({
61
+ items: z.array(TransactionSchema),
62
+ total: TotalSchema,
63
+ });
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ export declare const WalletTypeSchema: z.ZodObject<{
3
+ walletTypeId: z.ZodString;
4
+ code: z.ZodString;
5
+ name: z.ZodString;
6
+ unitSingularName: z.ZodOptional<z.ZodString>;
7
+ unitPluralName: z.ZodOptional<z.ZodString>;
8
+ active: z.ZodBoolean;
9
+ isDefault: z.ZodBoolean;
10
+ createdAt: z.ZodOptional<z.ZodString>;
11
+ limits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
12
+ allowNegativeBalance: z.ZodOptional<z.ZodBoolean>;
13
+ }, "strip", z.ZodTypeAny, {
14
+ code: string;
15
+ walletTypeId: string;
16
+ name: string;
17
+ active: boolean;
18
+ isDefault: boolean;
19
+ unitSingularName?: string | undefined;
20
+ unitPluralName?: string | undefined;
21
+ createdAt?: string | undefined;
22
+ limits?: Record<string, unknown> | undefined;
23
+ allowNegativeBalance?: boolean | undefined;
24
+ }, {
25
+ code: string;
26
+ walletTypeId: string;
27
+ name: string;
28
+ active: boolean;
29
+ isDefault: boolean;
30
+ unitSingularName?: string | undefined;
31
+ unitPluralName?: string | undefined;
32
+ createdAt?: string | undefined;
33
+ limits?: Record<string, unknown> | undefined;
34
+ allowNegativeBalance?: boolean | undefined;
35
+ }>;
36
+ export declare const WalletTypeListResponseSchema: z.ZodObject<{
37
+ items: z.ZodArray<z.ZodObject<{
38
+ walletTypeId: z.ZodString;
39
+ code: z.ZodString;
40
+ name: z.ZodString;
41
+ unitSingularName: z.ZodOptional<z.ZodString>;
42
+ unitPluralName: z.ZodOptional<z.ZodString>;
43
+ active: z.ZodBoolean;
44
+ isDefault: z.ZodBoolean;
45
+ createdAt: z.ZodOptional<z.ZodString>;
46
+ limits: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
47
+ allowNegativeBalance: z.ZodOptional<z.ZodBoolean>;
48
+ }, "strip", z.ZodTypeAny, {
49
+ code: string;
50
+ walletTypeId: string;
51
+ name: string;
52
+ active: boolean;
53
+ isDefault: boolean;
54
+ unitSingularName?: string | undefined;
55
+ unitPluralName?: string | undefined;
56
+ createdAt?: string | undefined;
57
+ limits?: Record<string, unknown> | undefined;
58
+ allowNegativeBalance?: boolean | undefined;
59
+ }, {
60
+ code: string;
61
+ walletTypeId: string;
62
+ name: string;
63
+ active: boolean;
64
+ isDefault: boolean;
65
+ unitSingularName?: string | undefined;
66
+ unitPluralName?: string | undefined;
67
+ createdAt?: string | undefined;
68
+ limits?: Record<string, unknown> | undefined;
69
+ allowNegativeBalance?: boolean | undefined;
70
+ }>, "many">;
71
+ }, "strip", z.ZodTypeAny, {
72
+ items: {
73
+ code: string;
74
+ walletTypeId: string;
75
+ name: string;
76
+ active: boolean;
77
+ isDefault: boolean;
78
+ unitSingularName?: string | undefined;
79
+ unitPluralName?: string | undefined;
80
+ createdAt?: string | undefined;
81
+ limits?: Record<string, unknown> | undefined;
82
+ allowNegativeBalance?: boolean | undefined;
83
+ }[];
84
+ }, {
85
+ items: {
86
+ code: string;
87
+ walletTypeId: string;
88
+ name: string;
89
+ active: boolean;
90
+ isDefault: boolean;
91
+ unitSingularName?: string | undefined;
92
+ unitPluralName?: string | undefined;
93
+ createdAt?: string | undefined;
94
+ limits?: Record<string, unknown> | undefined;
95
+ allowNegativeBalance?: boolean | undefined;
96
+ }[];
97
+ }>;
98
+ export type WalletType = z.infer<typeof WalletTypeSchema>;
99
+ export type WalletTypeListResponse = z.infer<typeof WalletTypeListResponseSchema>;
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ export const WalletTypeSchema = z.object({
3
+ walletTypeId: z.string(),
4
+ code: z.string(),
5
+ name: z.string(),
6
+ unitSingularName: z.string().optional(),
7
+ unitPluralName: z.string().optional(),
8
+ active: z.boolean(),
9
+ isDefault: z.boolean(),
10
+ createdAt: z.string().optional(),
11
+ limits: z.record(z.unknown()).optional(),
12
+ allowNegativeBalance: z.boolean().optional(),
13
+ });
14
+ // API returns { items: [...] } not just an array
15
+ export const WalletTypeListResponseSchema = z.object({
16
+ items: z.array(WalletTypeSchema),
17
+ });
@@ -0,0 +1,195 @@
1
+ import { z } from "zod";
2
+ export declare const WebhookHeaderSchema: z.ZodObject<{
3
+ headerName: z.ZodString;
4
+ headerValue: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ headerName: string;
7
+ headerValue: string;
8
+ }, {
9
+ headerName: string;
10
+ headerValue: string;
11
+ }>;
12
+ export type WebhookHeader = z.infer<typeof WebhookHeaderSchema>;
13
+ export declare const WebhookSubscriptionSchema: z.ZodObject<{
14
+ webhookSubscriptionId: z.ZodString;
15
+ eventName: z.ZodString;
16
+ url: z.ZodString;
17
+ headers: z.ZodOptional<z.ZodArray<z.ZodObject<{
18
+ headerName: z.ZodString;
19
+ headerValue: z.ZodString;
20
+ }, "strip", z.ZodTypeAny, {
21
+ headerName: string;
22
+ headerValue: string;
23
+ }, {
24
+ headerName: string;
25
+ headerValue: string;
26
+ }>, "many">>;
27
+ createdAt: z.ZodOptional<z.ZodString>;
28
+ }, "strip", z.ZodTypeAny, {
29
+ url: string;
30
+ eventName: string;
31
+ webhookSubscriptionId: string;
32
+ headers?: {
33
+ headerName: string;
34
+ headerValue: string;
35
+ }[] | undefined;
36
+ createdAt?: string | undefined;
37
+ }, {
38
+ url: string;
39
+ eventName: string;
40
+ webhookSubscriptionId: string;
41
+ headers?: {
42
+ headerName: string;
43
+ headerValue: string;
44
+ }[] | undefined;
45
+ createdAt?: string | undefined;
46
+ }>;
47
+ export type WebhookSubscription = z.infer<typeof WebhookSubscriptionSchema>;
48
+ export declare const WebhookSubscriptionCreateInputSchema: z.ZodObject<{
49
+ eventName: z.ZodString;
50
+ url: z.ZodString;
51
+ headers: z.ZodOptional<z.ZodArray<z.ZodObject<{
52
+ headerName: z.ZodString;
53
+ headerValue: z.ZodString;
54
+ }, "strip", z.ZodTypeAny, {
55
+ headerName: string;
56
+ headerValue: string;
57
+ }, {
58
+ headerName: string;
59
+ headerValue: string;
60
+ }>, "many">>;
61
+ }, "strip", z.ZodTypeAny, {
62
+ url: string;
63
+ eventName: string;
64
+ headers?: {
65
+ headerName: string;
66
+ headerValue: string;
67
+ }[] | undefined;
68
+ }, {
69
+ url: string;
70
+ eventName: string;
71
+ headers?: {
72
+ headerName: string;
73
+ headerValue: string;
74
+ }[] | undefined;
75
+ }>;
76
+ export type WebhookSubscriptionCreateInput = z.infer<typeof WebhookSubscriptionCreateInputSchema>;
77
+ export declare const WebhookSubscriptionUpdateInputSchema: z.ZodObject<{
78
+ eventName: z.ZodOptional<z.ZodString>;
79
+ url: z.ZodOptional<z.ZodString>;
80
+ headers: z.ZodOptional<z.ZodArray<z.ZodObject<{
81
+ headerName: z.ZodString;
82
+ headerValue: z.ZodString;
83
+ }, "strip", z.ZodTypeAny, {
84
+ headerName: string;
85
+ headerValue: string;
86
+ }, {
87
+ headerName: string;
88
+ headerValue: string;
89
+ }>, "many">>;
90
+ }, "strip", z.ZodTypeAny, {
91
+ headers?: {
92
+ headerName: string;
93
+ headerValue: string;
94
+ }[] | undefined;
95
+ url?: string | undefined;
96
+ eventName?: string | undefined;
97
+ }, {
98
+ headers?: {
99
+ headerName: string;
100
+ headerValue: string;
101
+ }[] | undefined;
102
+ url?: string | undefined;
103
+ eventName?: string | undefined;
104
+ }>;
105
+ export type WebhookSubscriptionUpdateInput = z.infer<typeof WebhookSubscriptionUpdateInputSchema>;
106
+ export declare const WebhookEventTypesSchema: z.ZodObject<{
107
+ items: z.ZodArray<z.ZodString, "many">;
108
+ }, "strip", z.ZodTypeAny, {
109
+ items: string[];
110
+ }, {
111
+ items: string[];
112
+ }>;
113
+ export type WebhookEventTypes = z.infer<typeof WebhookEventTypesSchema>;
114
+ export declare const WebhookSubscriptionListResponseSchema: z.ZodObject<{
115
+ items: z.ZodArray<z.ZodObject<{
116
+ webhookSubscriptionId: z.ZodString;
117
+ eventName: z.ZodString;
118
+ url: z.ZodString;
119
+ headers: z.ZodOptional<z.ZodArray<z.ZodObject<{
120
+ headerName: z.ZodString;
121
+ headerValue: z.ZodString;
122
+ }, "strip", z.ZodTypeAny, {
123
+ headerName: string;
124
+ headerValue: string;
125
+ }, {
126
+ headerName: string;
127
+ headerValue: string;
128
+ }>, "many">>;
129
+ createdAt: z.ZodOptional<z.ZodString>;
130
+ }, "strip", z.ZodTypeAny, {
131
+ url: string;
132
+ eventName: string;
133
+ webhookSubscriptionId: string;
134
+ headers?: {
135
+ headerName: string;
136
+ headerValue: string;
137
+ }[] | undefined;
138
+ createdAt?: string | undefined;
139
+ }, {
140
+ url: string;
141
+ eventName: string;
142
+ webhookSubscriptionId: string;
143
+ headers?: {
144
+ headerName: string;
145
+ headerValue: string;
146
+ }[] | undefined;
147
+ createdAt?: string | undefined;
148
+ }>, "many">;
149
+ total: z.ZodOptional<z.ZodObject<{
150
+ all: z.ZodOptional<z.ZodUnion<[z.ZodNumber, z.ZodString]>>;
151
+ filtered: z.ZodOptional<z.ZodUnion<[z.ZodNumber, z.ZodString]>>;
152
+ estimated: z.ZodOptional<z.ZodBoolean>;
153
+ }, "strip", z.ZodTypeAny, {
154
+ all?: string | number | undefined;
155
+ filtered?: string | number | undefined;
156
+ estimated?: boolean | undefined;
157
+ }, {
158
+ all?: string | number | undefined;
159
+ filtered?: string | number | undefined;
160
+ estimated?: boolean | undefined;
161
+ }>>;
162
+ }, "strip", z.ZodTypeAny, {
163
+ items: {
164
+ url: string;
165
+ eventName: string;
166
+ webhookSubscriptionId: string;
167
+ headers?: {
168
+ headerName: string;
169
+ headerValue: string;
170
+ }[] | undefined;
171
+ createdAt?: string | undefined;
172
+ }[];
173
+ total?: {
174
+ all?: string | number | undefined;
175
+ filtered?: string | number | undefined;
176
+ estimated?: boolean | undefined;
177
+ } | undefined;
178
+ }, {
179
+ items: {
180
+ url: string;
181
+ eventName: string;
182
+ webhookSubscriptionId: string;
183
+ headers?: {
184
+ headerName: string;
185
+ headerValue: string;
186
+ }[] | undefined;
187
+ createdAt?: string | undefined;
188
+ }[];
189
+ total?: {
190
+ all?: string | number | undefined;
191
+ filtered?: string | number | undefined;
192
+ estimated?: boolean | undefined;
193
+ } | undefined;
194
+ }>;
195
+ export type WebhookSubscriptionListResponse = z.infer<typeof WebhookSubscriptionListResponseSchema>;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ // Webhook header for custom headers in subscriptions
3
+ export const WebhookHeaderSchema = z.object({
4
+ headerName: z.string().describe("Header name."),
5
+ headerValue: z.string().describe("Header value."),
6
+ });
7
+ // Webhook subscription
8
+ export const WebhookSubscriptionSchema = z.object({
9
+ webhookSubscriptionId: z.string().describe("Unique webhook subscription ID (UUID)."),
10
+ eventName: z.string().describe("Event name this subscription listens to."),
11
+ url: z.string().describe("URL to send webhook events to."),
12
+ headers: z.array(WebhookHeaderSchema).optional().describe("Custom headers to include in webhook requests."),
13
+ createdAt: z.string().optional().describe("Creation timestamp (ISO format)."),
14
+ });
15
+ // Input schema for creating a webhook subscription
16
+ export const WebhookSubscriptionCreateInputSchema = z.object({
17
+ eventName: z.string().describe("Event name to subscribe to. Use webhook_events to discover available events."),
18
+ url: z.string().describe("URL to receive webhook events."),
19
+ headers: z.array(WebhookHeaderSchema).optional().describe("Custom headers to include in webhook requests."),
20
+ });
21
+ // Input schema for updating a webhook subscription
22
+ export const WebhookSubscriptionUpdateInputSchema = z.object({
23
+ eventName: z.string().optional().describe("Event name to subscribe to."),
24
+ url: z.string().optional().describe("URL to receive webhook events."),
25
+ headers: z.array(WebhookHeaderSchema).optional().describe("Custom headers to include in webhook requests."),
26
+ });
27
+ // Response schema for webhook event types list
28
+ export const WebhookEventTypesSchema = z.object({
29
+ items: z.array(z.string()).describe("List of available webhook event type names."),
30
+ });
31
+ // Response schema for webhook subscription list
32
+ export const WebhookSubscriptionListResponseSchema = z.object({
33
+ items: z.array(WebhookSubscriptionSchema),
34
+ total: z.object({
35
+ all: z.union([z.number(), z.string()]).optional(),
36
+ filtered: z.union([z.number(), z.string()]).optional(),
37
+ estimated: z.boolean().optional(),
38
+ }).optional(),
39
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Cursor-based pagination utilities for Open Loyalty MCP.
3
+ *
4
+ * Since the Open Loyalty API only supports offset-based pagination (_page/_itemsOnPage),
5
+ * we implement client-side cursor emulation by encoding pagination state into opaque cursors.
6
+ */
7
+ /**
8
+ * Internal cursor data structure.
9
+ * Encoded as base64url JSON for the external cursor string.
10
+ */
11
+ export interface CursorData {
12
+ /** Page number (1-indexed) */
13
+ p: number;
14
+ /** Items per page */
15
+ pp: number;
16
+ /** Version for future migrations */
17
+ v: number;
18
+ /** Optional timestamp for expiration (not currently enforced) */
19
+ ts?: number;
20
+ }
21
+ /**
22
+ * Pagination metadata returned in list responses.
23
+ */
24
+ export interface PaginationMeta {
25
+ /** Current cursor (encodes current page state) */
26
+ cursor: string;
27
+ /** Cursor for the next page, or undefined if no more pages */
28
+ nextCursor: string | undefined;
29
+ /** Whether there are more items after the current page */
30
+ hasMore: boolean;
31
+ }
32
+ /**
33
+ * Encode pagination state into an opaque cursor string.
34
+ * Uses base64url encoding of JSON data.
35
+ *
36
+ * @param data - Pagination state to encode
37
+ * @returns Opaque cursor string
38
+ */
39
+ export declare function encodeCursor(data: Omit<CursorData, 'v'>): string;
40
+ /**
41
+ * Decode an opaque cursor string back to pagination state.
42
+ *
43
+ * @param cursor - Opaque cursor string
44
+ * @returns Decoded pagination state, or null if invalid
45
+ */
46
+ export declare function decodeCursor(cursor: string): CursorData | null;
47
+ /**
48
+ * Build pagination metadata for a list response.
49
+ *
50
+ * @param page - Current page number (1-indexed)
51
+ * @param perPage - Items per page
52
+ * @param itemCount - Number of items returned in current page
53
+ * @param total - Total number of items (from API response, may be undefined)
54
+ * @returns Pagination metadata with cursors
55
+ */
56
+ export declare function buildPaginationMeta(page: number, perPage: number, itemCount: number, total?: number): PaginationMeta;
57
+ /**
58
+ * Pagination input that supports both cursor and page/perPage.
59
+ */
60
+ export interface PaginationInput {
61
+ /** Opaque cursor from previous response (takes priority over page/perPage) */
62
+ cursor?: string;
63
+ /** Page number (1-indexed, used if no cursor) */
64
+ page?: number;
65
+ /** Items per page (used if no cursor) */
66
+ perPage?: number;
67
+ }
68
+ /**
69
+ * Resolved pagination parameters for API calls.
70
+ */
71
+ export interface ResolvedPagination {
72
+ /** Page number to request (1-indexed) */
73
+ page: number;
74
+ /** Items per page */
75
+ perPage: number;
76
+ }
77
+ /**
78
+ * Resolve pagination input to concrete page/perPage values.
79
+ * If a cursor is provided and valid, it takes priority over page/perPage.
80
+ *
81
+ * @param input - Pagination input with optional cursor
82
+ * @returns Resolved page and perPage values
83
+ */
84
+ export declare function resolvePaginationInput(input: PaginationInput): ResolvedPagination;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Cursor-based pagination utilities for Open Loyalty MCP.
3
+ *
4
+ * Since the Open Loyalty API only supports offset-based pagination (_page/_itemsOnPage),
5
+ * we implement client-side cursor emulation by encoding pagination state into opaque cursors.
6
+ */
7
+ /** Current cursor format version */
8
+ const CURSOR_VERSION = 1;
9
+ /**
10
+ * Encode pagination state into an opaque cursor string.
11
+ * Uses base64url encoding of JSON data.
12
+ *
13
+ * @param data - Pagination state to encode
14
+ * @returns Opaque cursor string
15
+ */
16
+ export function encodeCursor(data) {
17
+ const cursorData = {
18
+ ...data,
19
+ v: CURSOR_VERSION,
20
+ };
21
+ const json = JSON.stringify(cursorData);
22
+ // Use base64url encoding (URL-safe base64)
23
+ return Buffer.from(json, 'utf-8')
24
+ .toString('base64')
25
+ .replace(/\+/g, '-')
26
+ .replace(/\//g, '_')
27
+ .replace(/=+$/, '');
28
+ }
29
+ /**
30
+ * Decode an opaque cursor string back to pagination state.
31
+ *
32
+ * @param cursor - Opaque cursor string
33
+ * @returns Decoded pagination state, or null if invalid
34
+ */
35
+ export function decodeCursor(cursor) {
36
+ try {
37
+ // Restore base64 from base64url
38
+ let base64 = cursor.replace(/-/g, '+').replace(/_/g, '/');
39
+ // Add padding if needed
40
+ const paddingNeeded = (4 - (base64.length % 4)) % 4;
41
+ base64 += '='.repeat(paddingNeeded);
42
+ const json = Buffer.from(base64, 'base64').toString('utf-8');
43
+ const data = JSON.parse(json);
44
+ // Validate required fields
45
+ if (typeof data.p !== 'number' || data.p < 1)
46
+ return null;
47
+ if (typeof data.pp !== 'number' || data.pp < 1)
48
+ return null;
49
+ if (typeof data.v !== 'number')
50
+ return null;
51
+ return data;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
57
+ /**
58
+ * Build pagination metadata for a list response.
59
+ *
60
+ * @param page - Current page number (1-indexed)
61
+ * @param perPage - Items per page
62
+ * @param itemCount - Number of items returned in current page
63
+ * @param total - Total number of items (from API response, may be undefined)
64
+ * @returns Pagination metadata with cursors
65
+ */
66
+ export function buildPaginationMeta(page, perPage, itemCount, total) {
67
+ // Build current cursor
68
+ const cursor = encodeCursor({ p: page, pp: perPage });
69
+ // Determine if there are more pages
70
+ let hasMore = false;
71
+ if (total !== undefined && typeof total === 'number') {
72
+ // If we have total, calculate if there are more pages
73
+ hasMore = page * perPage < total;
74
+ }
75
+ else {
76
+ // If no total, assume more pages if we got a full page of results
77
+ hasMore = itemCount >= perPage;
78
+ }
79
+ // Build next cursor if there are more pages
80
+ const nextCursor = hasMore
81
+ ? encodeCursor({ p: page + 1, pp: perPage })
82
+ : undefined;
83
+ return {
84
+ cursor,
85
+ nextCursor,
86
+ hasMore,
87
+ };
88
+ }
89
+ /** Default page number if not specified */
90
+ const DEFAULT_PAGE = 1;
91
+ /** Default items per page if not specified */
92
+ const DEFAULT_PER_PAGE = 25;
93
+ /**
94
+ * Resolve pagination input to concrete page/perPage values.
95
+ * If a cursor is provided and valid, it takes priority over page/perPage.
96
+ *
97
+ * @param input - Pagination input with optional cursor
98
+ * @returns Resolved page and perPage values
99
+ */
100
+ export function resolvePaginationInput(input) {
101
+ // If cursor is provided, try to decode it
102
+ if (input.cursor) {
103
+ const cursorData = decodeCursor(input.cursor);
104
+ if (cursorData) {
105
+ return {
106
+ page: cursorData.p,
107
+ perPage: cursorData.pp,
108
+ };
109
+ }
110
+ // Invalid cursor - fall through to page/perPage
111
+ }
112
+ // Use page/perPage with defaults
113
+ return {
114
+ page: input.page ?? DEFAULT_PAGE,
115
+ perPage: input.perPage ?? DEFAULT_PER_PAGE,
116
+ };
117
+ }
@@ -0,0 +1,12 @@
1
+ export declare class OpenLoyaltyError extends Error {
2
+ code: string;
3
+ hint: string;
4
+ relatedTool: string;
5
+ constructor(options: {
6
+ code: string;
7
+ message: string;
8
+ hint: string;
9
+ relatedTool: string;
10
+ });
11
+ }
12
+ export declare function formatApiError(error: unknown, relatedTool: string): OpenLoyaltyError;