@simpleapps-com/augur-server 0.1.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.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { NextAuthConfig } from 'next-auth';
2
+
3
+ /** Base user fields shared across all Augur sites. */
4
+ interface AugurUser {
5
+ id: string;
6
+ username: string;
7
+ isVerified: boolean;
8
+ name?: string;
9
+ email?: string;
10
+ customerId?: string | number;
11
+ contactId?: string | number;
12
+ cartHdrUid?: number;
13
+ token?: string;
14
+ }
15
+ /** Session shape returned by all Augur sites. */
16
+ interface AugurSession {
17
+ user: AugurUser;
18
+ token?: string;
19
+ expires: string;
20
+ }
21
+ /** Site-specific callbacks the consumer must provide. */
22
+ interface AugurAuthCallbacks {
23
+ /** Fetch user profile from the Augur API given the user's Joomla ID. */
24
+ getUserProfile: (userId: string) => Promise<AugurUser | null>;
25
+ /** Look up or create a cart header for the authenticated user. */
26
+ cartHdrLookup?: (userId: string | number, token: string, contactId: string | number, customerId: string | number) => Promise<{
27
+ cartHdrUid?: number;
28
+ } | null>;
29
+ }
30
+ interface CreateAuthConfigOptions {
31
+ /** Site-specific callbacks. */
32
+ callbacks: AugurAuthCallbacks;
33
+ /** NextAuth secret (defaults to NEXT_PUBLIC_AUTH_SECRET env var). */
34
+ secret?: string;
35
+ /** Session max age in seconds (defaults to 4 hours). */
36
+ maxAge?: number;
37
+ /** Default customer ID when profile doesn't provide one. */
38
+ defaultCustomerId?: string | number;
39
+ /** Enable NextAuth debug logging (defaults to NODE_ENV === "development"). */
40
+ debug?: boolean;
41
+ }
42
+ /**
43
+ * Create a NextAuth 5 configuration for an Augur ecommerce site.
44
+ *
45
+ * Each site calls this factory with its own getUserProfile and
46
+ * cartHdrLookup implementations. The result is passed to NextAuth().
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * // auth.ts
51
+ * import NextAuth from "next-auth";
52
+ * import { createAuthConfig } from "@simpleapps-com/augur-server/auth";
53
+ * import { getUserProfile, cartHdrLookup } from "./lib/actions/users";
54
+ *
55
+ * export const { handlers, signIn, signOut, auth } = NextAuth(
56
+ * createAuthConfig({
57
+ * callbacks: { getUserProfile, cartHdrLookup },
58
+ * defaultCustomerId: process.env.NEXT_PUBLIC_DEFAULT_CUSTOMER_ID,
59
+ * }),
60
+ * );
61
+ * ```
62
+ */
63
+ declare function createAuthConfig(options: CreateAuthConfigOptions): NextAuthConfig;
64
+
65
+ export { type AugurAuthCallbacks, type AugurSession, type AugurUser, type CreateAuthConfigOptions, createAuthConfig };
package/dist/auth.js ADDED
@@ -0,0 +1,122 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/auth.ts
4
+ import Credentials from "next-auth/providers/credentials";
5
+ function createAuthConfig(options) {
6
+ const {
7
+ callbacks: siteCallbacks,
8
+ secret = process.env.NEXT_PUBLIC_AUTH_SECRET,
9
+ maxAge = 4 * 60 * 60,
10
+ defaultCustomerId,
11
+ debug = process.env.NODE_ENV === "development"
12
+ } = options;
13
+ return {
14
+ providers: [
15
+ Credentials({
16
+ name: "Credentials",
17
+ credentials: {
18
+ username: { label: "Username", type: "text" },
19
+ password: { label: "Password", type: "password" },
20
+ id: { label: "ID", type: "text" },
21
+ isVerified: { label: "Is Verified", type: "text" },
22
+ token: { label: "Token", type: "text" }
23
+ },
24
+ async authorize(credentials) {
25
+ if (!credentials) return null;
26
+ const { id, isVerified, username, token } = credentials;
27
+ return {
28
+ id,
29
+ isVerified: isVerified === "true",
30
+ username,
31
+ token
32
+ };
33
+ }
34
+ })
35
+ ],
36
+ callbacks: {
37
+ async signIn() {
38
+ return true;
39
+ },
40
+ async redirect({ baseUrl }) {
41
+ return baseUrl;
42
+ },
43
+ async session({ session, token }) {
44
+ try {
45
+ const userId = token.id;
46
+ const userProfile = await siteCallbacks.getUserProfile(userId);
47
+ const baseUser = {
48
+ id: userId || "",
49
+ username: token.username || "",
50
+ isVerified: token.isVerified || false
51
+ };
52
+ if (userProfile) {
53
+ const { email, name, customerId, contactId, id } = userProfile;
54
+ let cartHdrUid;
55
+ if (siteCallbacks.cartHdrLookup && id) {
56
+ const cartHdr = await siteCallbacks.cartHdrLookup(
57
+ id,
58
+ "",
59
+ contactId ?? "",
60
+ customerId ?? defaultCustomerId ?? ""
61
+ );
62
+ cartHdrUid = cartHdr?.cartHdrUid;
63
+ }
64
+ return {
65
+ ...session,
66
+ user: {
67
+ ...session.user,
68
+ ...baseUser,
69
+ name: name ?? session.user?.name,
70
+ email: email ?? session.user?.email,
71
+ customerId: customerId ?? defaultCustomerId,
72
+ contactId,
73
+ cartHdrUid
74
+ },
75
+ token: token.token
76
+ };
77
+ }
78
+ return {
79
+ ...session,
80
+ user: { ...session.user, ...baseUser },
81
+ token: token.token
82
+ };
83
+ } catch {
84
+ return {
85
+ ...session,
86
+ user: {
87
+ ...session.user,
88
+ id: token.id || "",
89
+ username: token.username || "",
90
+ isVerified: token.isVerified || false
91
+ },
92
+ token: token.token
93
+ };
94
+ }
95
+ },
96
+ async jwt({ token, user }) {
97
+ if (user) {
98
+ const typedUser = user;
99
+ return {
100
+ ...token,
101
+ id: typedUser.id,
102
+ isVerified: typedUser.isVerified,
103
+ username: typedUser.username,
104
+ token: typedUser.token
105
+ };
106
+ }
107
+ return token;
108
+ }
109
+ },
110
+ secret,
111
+ debug,
112
+ session: {
113
+ strategy: "jwt",
114
+ maxAge
115
+ },
116
+ trustHost: true
117
+ };
118
+ }
119
+ export {
120
+ createAuthConfig
121
+ };
122
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["// @simpleapps-com/augur-server/auth\n// NextAuth 5 configuration factory for Augur ecommerce sites.\n\nimport type { NextAuthConfig } from \"next-auth\";\nimport Credentials from \"next-auth/providers/credentials\";\n\n/** Base user fields shared across all Augur sites. */\nexport interface AugurUser {\n id: string;\n username: string;\n isVerified: boolean;\n name?: string;\n email?: string;\n customerId?: string | number;\n contactId?: string | number;\n cartHdrUid?: number;\n token?: string;\n}\n\n/** Session shape returned by all Augur sites. */\nexport interface AugurSession {\n user: AugurUser;\n token?: string;\n expires: string;\n}\n\n/** Site-specific callbacks the consumer must provide. */\nexport interface AugurAuthCallbacks {\n /** Fetch user profile from the Augur API given the user's Joomla ID. */\n getUserProfile: (userId: string) => Promise<AugurUser | null>;\n /** Look up or create a cart header for the authenticated user. */\n cartHdrLookup?: (\n userId: string | number,\n token: string,\n contactId: string | number,\n customerId: string | number,\n ) => Promise<{ cartHdrUid?: number } | null>;\n}\n\nexport interface CreateAuthConfigOptions {\n /** Site-specific callbacks. */\n callbacks: AugurAuthCallbacks;\n /** NextAuth secret (defaults to NEXT_PUBLIC_AUTH_SECRET env var). */\n secret?: string;\n /** Session max age in seconds (defaults to 4 hours). */\n maxAge?: number;\n /** Default customer ID when profile doesn't provide one. */\n defaultCustomerId?: string | number;\n /** Enable NextAuth debug logging (defaults to NODE_ENV === \"development\"). */\n debug?: boolean;\n}\n\n/**\n * Create a NextAuth 5 configuration for an Augur ecommerce site.\n *\n * Each site calls this factory with its own getUserProfile and\n * cartHdrLookup implementations. The result is passed to NextAuth().\n *\n * @example\n * ```ts\n * // auth.ts\n * import NextAuth from \"next-auth\";\n * import { createAuthConfig } from \"@simpleapps-com/augur-server/auth\";\n * import { getUserProfile, cartHdrLookup } from \"./lib/actions/users\";\n *\n * export const { handlers, signIn, signOut, auth } = NextAuth(\n * createAuthConfig({\n * callbacks: { getUserProfile, cartHdrLookup },\n * defaultCustomerId: process.env.NEXT_PUBLIC_DEFAULT_CUSTOMER_ID,\n * }),\n * );\n * ```\n */\nexport function createAuthConfig(\n options: CreateAuthConfigOptions,\n): NextAuthConfig {\n const {\n callbacks: siteCallbacks,\n secret = process.env.NEXT_PUBLIC_AUTH_SECRET,\n maxAge = 4 * 60 * 60,\n defaultCustomerId,\n debug = process.env.NODE_ENV === \"development\",\n } = options;\n\n return {\n providers: [\n Credentials({\n name: \"Credentials\",\n credentials: {\n username: { label: \"Username\", type: \"text\" },\n password: { label: \"Password\", type: \"password\" },\n id: { label: \"ID\", type: \"text\" },\n isVerified: { label: \"Is Verified\", type: \"text\" },\n token: { label: \"Token\", type: \"text\" },\n },\n async authorize(credentials) {\n if (!credentials) return null;\n\n const { id, isVerified, username, token } = credentials as {\n id: string;\n isVerified: string;\n username: string;\n token: string;\n };\n\n return {\n id,\n isVerified: isVerified === \"true\",\n username,\n token,\n };\n },\n }),\n ],\n callbacks: {\n async signIn() {\n return true;\n },\n async redirect({ baseUrl }) {\n return baseUrl;\n },\n async session({ session, token }) {\n try {\n const userId = token.id as string;\n const userProfile = await siteCallbacks.getUserProfile(userId);\n\n const baseUser = {\n id: userId || \"\",\n username: (token.username as string) || \"\",\n isVerified: (token.isVerified as boolean) || false,\n };\n\n if (userProfile) {\n const { email, name, customerId, contactId, id } = userProfile;\n\n let cartHdrUid: number | undefined;\n if (siteCallbacks.cartHdrLookup && id) {\n const cartHdr = await siteCallbacks.cartHdrLookup(\n id,\n \"\",\n contactId ?? \"\",\n customerId ?? defaultCustomerId ?? \"\",\n );\n cartHdrUid = cartHdr?.cartHdrUid;\n }\n\n return {\n ...session,\n user: {\n ...session.user,\n ...baseUser,\n name: name ?? session.user?.name,\n email: email ?? session.user?.email,\n customerId: customerId ?? defaultCustomerId,\n contactId,\n cartHdrUid,\n },\n token: token.token as string | undefined,\n };\n }\n\n return {\n ...session,\n user: { ...session.user, ...baseUser },\n token: token.token as string | undefined,\n };\n } catch {\n return {\n ...session,\n user: {\n ...session.user,\n id: (token.id as string) || \"\",\n username: (token.username as string) || \"\",\n isVerified: (token.isVerified as boolean) || false,\n },\n token: token.token as string | undefined,\n };\n }\n },\n async jwt({ token, user }) {\n if (user) {\n const typedUser = user as {\n id: string;\n isVerified: boolean;\n username: string;\n token: string;\n };\n\n return {\n ...token,\n id: typedUser.id,\n isVerified: typedUser.isVerified,\n username: typedUser.username,\n token: typedUser.token,\n };\n }\n return token;\n },\n },\n secret,\n debug,\n session: {\n strategy: \"jwt\",\n maxAge,\n },\n trustHost: true,\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,11 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ export {
9
+ __require
10
+ };
11
+ //# sourceMappingURL=chunk-DGUM43GV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,117 @@
1
+ import { QueryClient, UseQueryOptions, UseSuspenseQueryOptions } from '@tanstack/react-query';
2
+
3
+ declare const env: "development" | "staging" | "production";
4
+ declare const isDev: boolean;
5
+ declare const isStaging: boolean;
6
+ declare const isProduction: boolean;
7
+
8
+ declare function cacheGet(key: string): Promise<string | null>;
9
+ declare function cacheSet(key: string, value: string, ttlSeconds: number): Promise<void>;
10
+ declare function getCircuitState(): "closed" | "open";
11
+ declare function isRedisConnected(): boolean;
12
+
13
+ /**
14
+ * Wraps an SDK call with Redis caching.
15
+ *
16
+ * @param prefix - Redis key prefix (e.g. "mysite:")
17
+ * @param methodPath - Dot-separated SDK method path (used as cache key)
18
+ * @param method - The SDK method to call
19
+ * @param args - Arguments to pass to the SDK method. If the last argument
20
+ * contains an `edgeCache` property (number of hours), the result is cached
21
+ * in Redis for that duration. Without `edgeCache`, the call bypasses cache.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const result = await cachedSdkCall(
26
+ * "mysite:",
27
+ * "items.categories.get",
28
+ * augurServices.items.categories.get,
29
+ * itemCategoryUid,
30
+ * { edgeCache: 1 },
31
+ * );
32
+ * ```
33
+ */
34
+ declare function cachedSdkCall<TResult>(prefix: string, methodPath: string, method: (...args: any[]) => Promise<TResult>, ...args: unknown[]): Promise<TResult>;
35
+ /** Returns aggregated cache stats for monitoring endpoints. */
36
+ declare function getCacheStats(): {
37
+ stats: {
38
+ hits: number;
39
+ misses: number;
40
+ errors: number;
41
+ hitRate: string;
42
+ };
43
+ topKeys: {
44
+ key: string;
45
+ hits: number;
46
+ misses: number;
47
+ avgMs: number;
48
+ }[];
49
+ recentOps: {
50
+ op: "HIT" | "MISS" | "SKIP" | "ERROR";
51
+ key: string;
52
+ ms: number;
53
+ ago: string;
54
+ }[];
55
+ };
56
+
57
+ /**
58
+ * Calls an Augur SDK method whose TypeScript signature only declares a
59
+ * single positional parameter (e.g. `(id: number)`) but whose runtime
60
+ * implementation also accepts an options/params object as a second argument
61
+ * (e.g. `{ edgeCache: 4 }`).
62
+ *
63
+ * This wrapper centralises the single unavoidable `as any` cast so that
64
+ * every call-site in the codebase stays fully type-safe.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const result = await sdkCall(
69
+ * augurServices.items.invMast.get,
70
+ * invMastUid,
71
+ * { edgeCache: 4 },
72
+ * );
73
+ * ```
74
+ */
75
+ declare function sdkCall<TResult>(method: (...args: any[]) => Promise<TResult>, ...args: unknown[]): Promise<TResult>;
76
+
77
+ /**
78
+ * Creates a server-side query client optimised for prefetching.
79
+ *
80
+ * - No persistence (server-only)
81
+ * - Longer cache times for prefetched data
82
+ * - No retries (fail fast on server)
83
+ * - No refetching (server renders are one-shot)
84
+ */
85
+ declare function createServerQueryClient(): QueryClient;
86
+ /** Singleton server query client, reused across requests. */
87
+ declare function getServerQueryClient(): QueryClient;
88
+
89
+ interface QueryOptionsConfig<TParams, TData> {
90
+ /** Base query key (e.g., "customer-orders") */
91
+ baseKey: string;
92
+ /** Parameters object */
93
+ params: TParams;
94
+ /** Function that returns the data */
95
+ queryFn: (params: TParams) => Promise<TData>;
96
+ /** Function to determine if query should be enabled */
97
+ enabledFn?: (params: TParams) => boolean;
98
+ /** Custom stale time in milliseconds (default: SEMI_STATIC) */
99
+ staleTime?: number;
100
+ /** Custom garbage collection time in milliseconds (default: SEMI_STATIC) */
101
+ gcTime?: number;
102
+ /** Key to exclude from query key generation */
103
+ excludeFromKey?: keyof TParams;
104
+ }
105
+ /**
106
+ * Creates useQuery options with consistent key generation.
107
+ * Filters out undefined/null/empty params from the query key.
108
+ */
109
+ declare const createQueryOptions: <TParams extends Record<string, unknown>, TData>({ baseKey, params, queryFn, enabledFn, staleTime, gcTime, excludeFromKey, }: QueryOptionsConfig<TParams, TData>) => UseQueryOptions<TData>;
110
+ /**
111
+ * Creates useSuspenseQuery options with consistent key generation.
112
+ * Same as createQueryOptions but without enabledFn (suspense queries
113
+ * cannot be disabled).
114
+ */
115
+ declare const createSuspenseQueryOptions: <TParams extends Record<string, unknown>, TData>({ baseKey, params, queryFn, staleTime, gcTime, excludeFromKey, }: Omit<QueryOptionsConfig<TParams, TData>, "enabledFn">) => UseSuspenseQueryOptions<TData>;
116
+
117
+ export { type QueryOptionsConfig, cacheGet, cacheSet, cachedSdkCall, createQueryOptions, createServerQueryClient, createSuspenseQueryOptions, env, getCacheStats, getCircuitState, getServerQueryClient, isDev, isProduction, isRedisConnected, isStaging, sdkCall };
package/dist/index.js ADDED
@@ -0,0 +1,367 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-DGUM43GV.js";
4
+
5
+ // src/environment.ts
6
+ function getEnvironment() {
7
+ const deploymentEnv = process.env.DEPLOYMENT_ENV;
8
+ if (deploymentEnv === "dev") return "staging";
9
+ if (deploymentEnv === "live") return "production";
10
+ if (typeof window === "undefined") {
11
+ if (process.env.NODE_ENV === "development") return "development";
12
+ return "production";
13
+ }
14
+ const host = window.location.hostname;
15
+ if (host.includes("localhost") || host.includes("127.0.0.1"))
16
+ return "development";
17
+ if (host.includes("agr-hosting.dev")) return "staging";
18
+ return "production";
19
+ }
20
+ var env = getEnvironment();
21
+ var isDev = env === "development";
22
+ var isStaging = env === "staging";
23
+ var isProduction = env === "production";
24
+
25
+ // src/cache/redis-client.ts
26
+ var CIRCUIT_BREAKER_THRESHOLD = 5;
27
+ var CIRCUIT_BREAKER_RESET_MS = 6e4;
28
+ var g = globalThis;
29
+ if (!g.__redisState) {
30
+ g.__redisState = {
31
+ client: null,
32
+ consecutiveFailures: 0,
33
+ circuitOpenUntil: 0
34
+ };
35
+ }
36
+ var state = g.__redisState;
37
+ var debugEnabled = isDev || isStaging;
38
+ function log(...args) {
39
+ if (debugEnabled) {
40
+ console.log("[Redis]", ...args);
41
+ }
42
+ }
43
+ function isCircuitOpen() {
44
+ if (state.circuitOpenUntil === 0) return false;
45
+ if (Date.now() >= state.circuitOpenUntil) {
46
+ state.circuitOpenUntil = 0;
47
+ state.consecutiveFailures = 0;
48
+ log("Circuit breaker reset -- retrying Redis");
49
+ return false;
50
+ }
51
+ return true;
52
+ }
53
+ function recordFailure() {
54
+ state.consecutiveFailures++;
55
+ if (state.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {
56
+ state.circuitOpenUntil = Date.now() + CIRCUIT_BREAKER_RESET_MS;
57
+ log(
58
+ `Circuit breaker OPEN -- skipping Redis for ${CIRCUIT_BREAKER_RESET_MS / 1e3}s`
59
+ );
60
+ }
61
+ }
62
+ function recordSuccess() {
63
+ state.consecutiveFailures = 0;
64
+ }
65
+ function getRedisUrl() {
66
+ const containerRedis = process.env.REDIS_SERVERS;
67
+ if (containerRedis) return `redis://${containerRedis}`;
68
+ if (isDev) return process.env.REDIS_URL_DEV;
69
+ if (isStaging) return process.env.REDIS_URL_STAGING;
70
+ return process.env.REDIS_URL_PROD;
71
+ }
72
+ function getClient() {
73
+ if (state.client) return state.client;
74
+ const url = getRedisUrl();
75
+ if (!url) {
76
+ log("No REDIS_URL -- cache disabled");
77
+ return null;
78
+ }
79
+ try {
80
+ const IORedis = __require("ioredis");
81
+ state.client = new IORedis(url, {
82
+ maxRetriesPerRequest: 1,
83
+ connectTimeout: 3e3,
84
+ lazyConnect: true,
85
+ enableOfflineQueue: false
86
+ });
87
+ state.client.on("error", (err) => {
88
+ log("Connection error:", err.message);
89
+ recordFailure();
90
+ });
91
+ state.client.on("connect", () => log("Connected"));
92
+ state.client.connect().catch(() => {
93
+ });
94
+ return state.client;
95
+ } catch {
96
+ log("Failed to create client (ioredis not installed?)");
97
+ return null;
98
+ }
99
+ }
100
+ async function cacheGet(key) {
101
+ if (isCircuitOpen()) return null;
102
+ const client = getClient();
103
+ if (!client) return null;
104
+ try {
105
+ const value = await client.get(key);
106
+ recordSuccess();
107
+ return value;
108
+ } catch {
109
+ recordFailure();
110
+ return null;
111
+ }
112
+ }
113
+ async function cacheSet(key, value, ttlSeconds) {
114
+ if (isCircuitOpen()) return;
115
+ const client = getClient();
116
+ if (!client) return;
117
+ try {
118
+ await client.setex(key, ttlSeconds, value);
119
+ recordSuccess();
120
+ } catch {
121
+ recordFailure();
122
+ }
123
+ }
124
+ function getCircuitState() {
125
+ return isCircuitOpen() ? "open" : "closed";
126
+ }
127
+ function isRedisConnected() {
128
+ return state.client?.status === "ready";
129
+ }
130
+
131
+ // src/cache/sdk-cache.ts
132
+ import crypto from "crypto";
133
+ var CACHE_ENABLED = process.env.REDIS_CACHE_ENABLED !== "false";
134
+ var debugEnabled2 = isDev || isStaging;
135
+ var g2 = globalThis;
136
+ if (!g2.__sdkCacheState) {
137
+ g2.__sdkCacheState = { statsMap: /* @__PURE__ */ new Map(), recentOps: [] };
138
+ }
139
+ var { statsMap, recentOps } = g2.__sdkCacheState;
140
+ var MAX_RECENT = 50;
141
+ function pushRecent(op) {
142
+ recentOps.push(op);
143
+ if (recentOps.length > MAX_RECENT) recentOps.shift();
144
+ }
145
+ function getOrCreateStats(method) {
146
+ let s = statsMap.get(method);
147
+ if (!s) {
148
+ s = { hits: 0, misses: 0, errors: 0, totalMs: 0 };
149
+ statsMap.set(method, s);
150
+ }
151
+ return s;
152
+ }
153
+ function hashArgs(args) {
154
+ const normalized = JSON.stringify(args, (_, v) => {
155
+ if (v && typeof v === "object" && !Array.isArray(v)) {
156
+ return Object.keys(v).sort().reduce(
157
+ (acc, k) => {
158
+ acc[k] = v[k];
159
+ return acc;
160
+ },
161
+ {}
162
+ );
163
+ }
164
+ return v;
165
+ });
166
+ return crypto.createHash("sha256").update(normalized).digest("hex").slice(0, 8);
167
+ }
168
+ function extractEdgeCache(args) {
169
+ for (let i = args.length - 1; i >= 0; i--) {
170
+ const arg = args[i];
171
+ if (arg && typeof arg === "object" && "edgeCache" in arg) {
172
+ return arg.edgeCache;
173
+ }
174
+ }
175
+ return void 0;
176
+ }
177
+ async function cachedSdkCall(prefix, methodPath, method, ...args) {
178
+ const edgeCache = extractEdgeCache(args);
179
+ if (!CACHE_ENABLED || edgeCache === void 0) {
180
+ if (debugEnabled2) {
181
+ console.log(`[SDK Cache] SKIP ${methodPath} (no edgeCache, REALTIME)`);
182
+ }
183
+ pushRecent({ op: "SKIP", key: methodPath, ms: 0, ts: Date.now() });
184
+ return method(...args);
185
+ }
186
+ const argsHash = hashArgs(args);
187
+ const cacheKey = `${prefix}sdk:${methodPath}:${argsHash}`;
188
+ const ttlSeconds = edgeCache * 3600;
189
+ const start = Date.now();
190
+ const stats = getOrCreateStats(methodPath);
191
+ try {
192
+ const cached = await cacheGet(cacheKey);
193
+ if (cached !== null) {
194
+ const parsed = JSON.parse(cached);
195
+ const ms2 = Date.now() - start;
196
+ stats.hits++;
197
+ stats.totalMs += ms2;
198
+ if (debugEnabled2) {
199
+ console.log(`[SDK Cache] HIT ${methodPath}:${argsHash} (${ms2}ms)`);
200
+ }
201
+ pushRecent({
202
+ op: "HIT",
203
+ key: `${methodPath}:${argsHash}`,
204
+ ms: ms2,
205
+ ts: Date.now()
206
+ });
207
+ return parsed;
208
+ }
209
+ } catch {
210
+ stats.errors++;
211
+ }
212
+ const result = await method(...args);
213
+ const ms = Date.now() - start;
214
+ stats.misses++;
215
+ stats.totalMs += ms;
216
+ if (debugEnabled2) {
217
+ console.log(
218
+ `[SDK Cache] MISS ${methodPath}:${argsHash} (${ms}ms -> cached, TTL ${edgeCache}h)`
219
+ );
220
+ }
221
+ pushRecent({
222
+ op: "MISS",
223
+ key: `${methodPath}:${argsHash}`,
224
+ ms,
225
+ ts: Date.now()
226
+ });
227
+ try {
228
+ const serialized = JSON.stringify(result);
229
+ cacheSet(cacheKey, serialized, ttlSeconds).catch(() => {
230
+ });
231
+ } catch {
232
+ }
233
+ return result;
234
+ }
235
+ function formatAgo(ms) {
236
+ if (ms < 1e3) return "<1s";
237
+ const s = Math.floor(ms / 1e3);
238
+ if (s < 60) return `${s}s ago`;
239
+ const m = Math.floor(s / 60);
240
+ return `${m}m ago`;
241
+ }
242
+ function getCacheStats() {
243
+ let totalHits = 0;
244
+ let totalMisses = 0;
245
+ let totalErrors = 0;
246
+ const topKeys = [];
247
+ statsMap.forEach((s, key) => {
248
+ totalHits += s.hits;
249
+ totalMisses += s.misses;
250
+ totalErrors += s.errors;
251
+ const total2 = s.hits + s.misses;
252
+ topKeys.push({
253
+ key,
254
+ hits: s.hits,
255
+ misses: s.misses,
256
+ avgMs: total2 > 0 ? Math.round(s.totalMs / total2) : 0
257
+ });
258
+ });
259
+ topKeys.sort((a, b) => b.hits - a.hits);
260
+ const total = totalHits + totalMisses;
261
+ const hitRate = total > 0 ? (totalHits / total * 100).toFixed(1) + "%" : "0%";
262
+ const now = Date.now();
263
+ const recent = recentOps.slice(-20).reverse().map((op) => ({
264
+ op: op.op,
265
+ key: op.key,
266
+ ms: op.ms,
267
+ ago: formatAgo(now - op.ts)
268
+ }));
269
+ return {
270
+ stats: { hits: totalHits, misses: totalMisses, errors: totalErrors, hitRate },
271
+ topKeys: topKeys.slice(0, 15),
272
+ recentOps: recent
273
+ };
274
+ }
275
+
276
+ // src/sdk-call.ts
277
+ async function sdkCall(method, ...args) {
278
+ return method(...args);
279
+ }
280
+
281
+ // src/server-query-client.ts
282
+ import { QueryClient } from "@tanstack/react-query";
283
+ import { CACHE_CONFIG } from "@simpleapps-com/augur-utils";
284
+ function createServerQueryClient() {
285
+ return new QueryClient({
286
+ defaultOptions: {
287
+ queries: {
288
+ staleTime: CACHE_CONFIG.STATIC.staleTime,
289
+ gcTime: CACHE_CONFIG.STATIC.staleTime,
290
+ retry: 0,
291
+ refetchOnMount: false,
292
+ refetchOnWindowFocus: false,
293
+ refetchOnReconnect: false
294
+ }
295
+ }
296
+ });
297
+ }
298
+ var serverQueryClient = void 0;
299
+ function getServerQueryClient() {
300
+ if (!serverQueryClient) {
301
+ serverQueryClient = createServerQueryClient();
302
+ }
303
+ return serverQueryClient;
304
+ }
305
+
306
+ // src/query-options-helper.ts
307
+ import { CACHE_CONFIG as CACHE_CONFIG2 } from "@simpleapps-com/augur-utils";
308
+ var createQueryOptions = ({
309
+ baseKey,
310
+ params,
311
+ queryFn,
312
+ enabledFn,
313
+ staleTime = CACHE_CONFIG2.SEMI_STATIC.staleTime,
314
+ gcTime = CACHE_CONFIG2.SEMI_STATIC.gcTime,
315
+ excludeFromKey
316
+ }) => {
317
+ const paramEntries = Object.entries(params).filter(([key, value]) => {
318
+ if (excludeFromKey && key === excludeFromKey) return false;
319
+ return value !== void 0 && value !== null && value !== "";
320
+ }).map(([key, value]) => `${key}: ${value}`);
321
+ const queryKey = [baseKey, ...paramEntries];
322
+ return {
323
+ queryKey,
324
+ queryFn: () => queryFn(params),
325
+ enabled: enabledFn ? enabledFn(params) : true,
326
+ staleTime,
327
+ gcTime
328
+ };
329
+ };
330
+ var createSuspenseQueryOptions = ({
331
+ baseKey,
332
+ params,
333
+ queryFn,
334
+ staleTime = CACHE_CONFIG2.SEMI_STATIC.staleTime,
335
+ gcTime = CACHE_CONFIG2.SEMI_STATIC.gcTime,
336
+ excludeFromKey
337
+ }) => {
338
+ const paramEntries = Object.entries(params).filter(([key, value]) => {
339
+ if (excludeFromKey && key === excludeFromKey) return false;
340
+ return value !== void 0 && value !== null && value !== "";
341
+ }).map(([key, value]) => `${key}: ${value}`);
342
+ const queryKey = [baseKey, ...paramEntries];
343
+ return {
344
+ queryKey,
345
+ queryFn: () => queryFn(params),
346
+ staleTime,
347
+ gcTime
348
+ };
349
+ };
350
+ export {
351
+ cacheGet,
352
+ cacheSet,
353
+ cachedSdkCall,
354
+ createQueryOptions,
355
+ createServerQueryClient,
356
+ createSuspenseQueryOptions,
357
+ env,
358
+ getCacheStats,
359
+ getCircuitState,
360
+ getServerQueryClient,
361
+ isDev,
362
+ isProduction,
363
+ isRedisConnected,
364
+ isStaging,
365
+ sdkCall
366
+ };
367
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/environment.ts","../src/cache/redis-client.ts","../src/cache/sdk-cache.ts","../src/sdk-call.ts","../src/server-query-client.ts","../src/query-options-helper.ts"],"sourcesContent":["/**\n * Environment detection that works across server, client, and containers.\n *\n * Priority:\n * 1. DEPLOYMENT_ENV (\"dev\" → staging, \"live\" → production)\n * 2. Server-side NODE_ENV fallback\n * 3. Client-side hostname detection\n */\nfunction getEnvironment(): \"development\" | \"staging\" | \"production\" {\n const deploymentEnv = process.env.DEPLOYMENT_ENV;\n if (deploymentEnv === \"dev\") return \"staging\";\n if (deploymentEnv === \"live\") return \"production\";\n\n if (typeof window === \"undefined\") {\n if (process.env.NODE_ENV === \"development\") return \"development\";\n return \"production\";\n }\n\n const host = window.location.hostname;\n if (host.includes(\"localhost\") || host.includes(\"127.0.0.1\"))\n return \"development\";\n if (host.includes(\"agr-hosting.dev\")) return \"staging\";\n return \"production\";\n}\n\nexport const env = getEnvironment();\nexport const isDev = env === \"development\";\nexport const isStaging = env === \"staging\";\nexport const isProduction = env === \"production\";\n","import type Redis from \"ioredis\";\nimport { isDev, isStaging } from \"../environment\";\n\nconst CIRCUIT_BREAKER_THRESHOLD = 5;\nconst CIRCUIT_BREAKER_RESET_MS = 60_000;\n\ninterface RedisGlobalState {\n client: Redis | null;\n consecutiveFailures: number;\n circuitOpenUntil: number;\n}\n\nconst g = globalThis as unknown as { __redisState?: RedisGlobalState };\nif (!g.__redisState) {\n g.__redisState = {\n client: null,\n consecutiveFailures: 0,\n circuitOpenUntil: 0,\n };\n}\nconst state = g.__redisState;\n\nconst debugEnabled = isDev || isStaging;\n\nfunction log(...args: unknown[]) {\n if (debugEnabled) {\n console.log(\"[Redis]\", ...args);\n }\n}\n\nfunction isCircuitOpen(): boolean {\n if (state.circuitOpenUntil === 0) return false;\n if (Date.now() >= state.circuitOpenUntil) {\n state.circuitOpenUntil = 0;\n state.consecutiveFailures = 0;\n log(\"Circuit breaker reset -- retrying Redis\");\n return false;\n }\n return true;\n}\n\nfunction recordFailure() {\n state.consecutiveFailures++;\n if (state.consecutiveFailures >= CIRCUIT_BREAKER_THRESHOLD) {\n state.circuitOpenUntil = Date.now() + CIRCUIT_BREAKER_RESET_MS;\n log(\n `Circuit breaker OPEN -- skipping Redis for ${CIRCUIT_BREAKER_RESET_MS / 1000}s`,\n );\n }\n}\n\nfunction recordSuccess() {\n state.consecutiveFailures = 0;\n}\n\nfunction getRedisUrl(): string | undefined {\n const containerRedis = process.env.REDIS_SERVERS;\n if (containerRedis) return `redis://${containerRedis}`;\n\n if (isDev) return process.env.REDIS_URL_DEV;\n if (isStaging) return process.env.REDIS_URL_STAGING;\n return process.env.REDIS_URL_PROD;\n}\n\nfunction getClient(): Redis | null {\n if (state.client) return state.client;\n\n const url = getRedisUrl();\n if (!url) {\n log(\"No REDIS_URL -- cache disabled\");\n return null;\n }\n\n try {\n // Dynamic import at module level isn't possible, so we require ioredis\n // at runtime. It's an optional peer dep -- if not installed, cache is\n // silently disabled.\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const IORedis = require(\"ioredis\") as typeof import(\"ioredis\").default;\n\n state.client = new IORedis(url, {\n maxRetriesPerRequest: 1,\n connectTimeout: 3000,\n lazyConnect: true,\n enableOfflineQueue: false,\n });\n\n state.client.on(\"error\", (err: Error) => {\n log(\"Connection error:\", err.message);\n recordFailure();\n });\n\n state.client.on(\"connect\", () => log(\"Connected\"));\n\n state.client.connect().catch(() => {\n /* handled by error event */\n });\n\n return state.client;\n } catch {\n log(\"Failed to create client (ioredis not installed?)\");\n return null;\n }\n}\n\nexport async function cacheGet(key: string): Promise<string | null> {\n if (isCircuitOpen()) return null;\n\n const client = getClient();\n if (!client) return null;\n\n try {\n const value = await client.get(key);\n recordSuccess();\n return value;\n } catch {\n recordFailure();\n return null;\n }\n}\n\nexport async function cacheSet(\n key: string,\n value: string,\n ttlSeconds: number,\n): Promise<void> {\n if (isCircuitOpen()) return;\n\n const client = getClient();\n if (!client) return;\n\n try {\n await client.setex(key, ttlSeconds, value);\n recordSuccess();\n } catch {\n recordFailure();\n }\n}\n\nexport function getCircuitState(): \"closed\" | \"open\" {\n return isCircuitOpen() ? \"open\" : \"closed\";\n}\n\nexport function isRedisConnected(): boolean {\n return state.client?.status === \"ready\";\n}\n","import crypto from \"crypto\";\nimport { cacheGet, cacheSet } from \"./redis-client\";\nimport { isDev, isStaging } from \"../environment\";\n\nconst CACHE_ENABLED = process.env.REDIS_CACHE_ENABLED !== \"false\";\nconst debugEnabled = isDev || isStaging;\n\ninterface MethodStats {\n hits: number;\n misses: number;\n errors: number;\n totalMs: number;\n}\n\ninterface RecentOp {\n op: \"HIT\" | \"MISS\" | \"SKIP\" | \"ERROR\";\n key: string;\n ms: number;\n ts: number;\n}\n\ninterface CacheGlobalState {\n statsMap: Map<string, MethodStats>;\n recentOps: RecentOp[];\n}\n\nconst g = globalThis as unknown as { __sdkCacheState?: CacheGlobalState };\nif (!g.__sdkCacheState) {\n g.__sdkCacheState = { statsMap: new Map(), recentOps: [] };\n}\nconst { statsMap, recentOps } = g.__sdkCacheState;\n\nconst MAX_RECENT = 50;\n\nfunction pushRecent(op: RecentOp) {\n recentOps.push(op);\n if (recentOps.length > MAX_RECENT) recentOps.shift();\n}\n\nfunction getOrCreateStats(method: string): MethodStats {\n let s = statsMap.get(method);\n if (!s) {\n s = { hits: 0, misses: 0, errors: 0, totalMs: 0 };\n statsMap.set(method, s);\n }\n return s;\n}\n\nfunction hashArgs(args: unknown[]): string {\n const normalized = JSON.stringify(args, (_, v) => {\n if (v && typeof v === \"object\" && !Array.isArray(v)) {\n return Object.keys(v)\n .sort()\n .reduce(\n (acc, k) => {\n acc[k] = v[k];\n return acc;\n },\n {} as Record<string, unknown>,\n );\n }\n return v;\n });\n return crypto\n .createHash(\"sha256\")\n .update(normalized)\n .digest(\"hex\")\n .slice(0, 8);\n}\n\nfunction extractEdgeCache(args: unknown[]): number | undefined {\n for (let i = args.length - 1; i >= 0; i--) {\n const arg = args[i];\n if (arg && typeof arg === \"object\" && \"edgeCache\" in arg) {\n return (arg as { edgeCache: number }).edgeCache;\n }\n }\n return undefined;\n}\n\n/**\n * Wraps an SDK call with Redis caching.\n *\n * @param prefix - Redis key prefix (e.g. \"mysite:\")\n * @param methodPath - Dot-separated SDK method path (used as cache key)\n * @param method - The SDK method to call\n * @param args - Arguments to pass to the SDK method. If the last argument\n * contains an `edgeCache` property (number of hours), the result is cached\n * in Redis for that duration. Without `edgeCache`, the call bypasses cache.\n *\n * @example\n * ```ts\n * const result = await cachedSdkCall(\n * \"mysite:\",\n * \"items.categories.get\",\n * augurServices.items.categories.get,\n * itemCategoryUid,\n * { edgeCache: 1 },\n * );\n * ```\n */\nexport async function cachedSdkCall<TResult>(\n prefix: string,\n methodPath: string,\n method: (...args: any[]) => Promise<TResult>,\n ...args: unknown[]\n): Promise<TResult> {\n const edgeCache = extractEdgeCache(args);\n\n if (!CACHE_ENABLED || edgeCache === undefined) {\n if (debugEnabled) {\n console.log(`[SDK Cache] SKIP ${methodPath} (no edgeCache, REALTIME)`);\n }\n pushRecent({ op: \"SKIP\", key: methodPath, ms: 0, ts: Date.now() });\n return method(...args);\n }\n\n const argsHash = hashArgs(args);\n const cacheKey = `${prefix}sdk:${methodPath}:${argsHash}`;\n const ttlSeconds = edgeCache * 3600;\n const start = Date.now();\n const stats = getOrCreateStats(methodPath);\n\n try {\n const cached = await cacheGet(cacheKey);\n if (cached !== null) {\n const parsed = JSON.parse(cached);\n const ms = Date.now() - start;\n stats.hits++;\n stats.totalMs += ms;\n if (debugEnabled) {\n console.log(`[SDK Cache] HIT ${methodPath}:${argsHash} (${ms}ms)`);\n }\n pushRecent({\n op: \"HIT\",\n key: `${methodPath}:${argsHash}`,\n ms,\n ts: Date.now(),\n });\n return parsed as TResult;\n }\n } catch {\n stats.errors++;\n }\n\n const result = await method(...args);\n const ms = Date.now() - start;\n stats.misses++;\n stats.totalMs += ms;\n\n if (debugEnabled) {\n console.log(\n `[SDK Cache] MISS ${methodPath}:${argsHash} (${ms}ms -> cached, TTL ${edgeCache}h)`,\n );\n }\n pushRecent({\n op: \"MISS\",\n key: `${methodPath}:${argsHash}`,\n ms,\n ts: Date.now(),\n });\n\n try {\n const serialized = JSON.stringify(result);\n cacheSet(cacheKey, serialized, ttlSeconds).catch(() => {\n /* swallow */\n });\n } catch {\n // Non-serializable result -- skip caching\n }\n\n return result;\n}\n\nfunction formatAgo(ms: number): string {\n if (ms < 1000) return \"<1s\";\n const s = Math.floor(ms / 1000);\n if (s < 60) return `${s}s ago`;\n const m = Math.floor(s / 60);\n return `${m}m ago`;\n}\n\n/** Returns aggregated cache stats for monitoring endpoints. */\nexport function getCacheStats() {\n let totalHits = 0;\n let totalMisses = 0;\n let totalErrors = 0;\n\n const topKeys: {\n key: string;\n hits: number;\n misses: number;\n avgMs: number;\n }[] = [];\n\n statsMap.forEach((s, key) => {\n totalHits += s.hits;\n totalMisses += s.misses;\n totalErrors += s.errors;\n const total = s.hits + s.misses;\n topKeys.push({\n key,\n hits: s.hits,\n misses: s.misses,\n avgMs: total > 0 ? Math.round(s.totalMs / total) : 0,\n });\n });\n\n topKeys.sort((a, b) => b.hits - a.hits);\n\n const total = totalHits + totalMisses;\n const hitRate =\n total > 0 ? ((totalHits / total) * 100).toFixed(1) + \"%\" : \"0%\";\n\n const now = Date.now();\n const recent = recentOps\n .slice(-20)\n .reverse()\n .map((op) => ({\n op: op.op,\n key: op.key,\n ms: op.ms,\n ago: formatAgo(now - op.ts),\n }));\n\n return {\n stats: { hits: totalHits, misses: totalMisses, errors: totalErrors, hitRate },\n topKeys: topKeys.slice(0, 15),\n recentOps: recent,\n };\n}\n","/**\n * Calls an Augur SDK method whose TypeScript signature only declares a\n * single positional parameter (e.g. `(id: number)`) but whose runtime\n * implementation also accepts an options/params object as a second argument\n * (e.g. `{ edgeCache: 4 }`).\n *\n * This wrapper centralises the single unavoidable `as any` cast so that\n * every call-site in the codebase stays fully type-safe.\n *\n * @example\n * ```ts\n * const result = await sdkCall(\n * augurServices.items.invMast.get,\n * invMastUid,\n * { edgeCache: 4 },\n * );\n * ```\n */\nexport async function sdkCall<TResult>(\n method: (...args: any[]) => Promise<TResult>,\n ...args: unknown[]\n): Promise<TResult> {\n return method(...args);\n}\n","import { QueryClient } from \"@tanstack/react-query\";\nimport { CACHE_CONFIG } from \"@simpleapps-com/augur-utils\";\n\n/**\n * Creates a server-side query client optimised for prefetching.\n *\n * - No persistence (server-only)\n * - Longer cache times for prefetched data\n * - No retries (fail fast on server)\n * - No refetching (server renders are one-shot)\n */\nexport function createServerQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: CACHE_CONFIG.STATIC.staleTime,\n gcTime: CACHE_CONFIG.STATIC.staleTime,\n retry: 0,\n refetchOnMount: false,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n },\n },\n });\n}\n\nlet serverQueryClient: QueryClient | undefined = undefined;\n\n/** Singleton server query client, reused across requests. */\nexport function getServerQueryClient(): QueryClient {\n if (!serverQueryClient) {\n serverQueryClient = createServerQueryClient();\n }\n return serverQueryClient;\n}\n","import type {\n UseQueryOptions,\n UseSuspenseQueryOptions,\n} from \"@tanstack/react-query\";\nimport { CACHE_CONFIG } from \"@simpleapps-com/augur-utils\";\n\nexport interface QueryOptionsConfig<TParams, TData> {\n /** Base query key (e.g., \"customer-orders\") */\n baseKey: string;\n /** Parameters object */\n params: TParams;\n /** Function that returns the data */\n queryFn: (params: TParams) => Promise<TData>;\n /** Function to determine if query should be enabled */\n enabledFn?: (params: TParams) => boolean;\n /** Custom stale time in milliseconds (default: SEMI_STATIC) */\n staleTime?: number;\n /** Custom garbage collection time in milliseconds (default: SEMI_STATIC) */\n gcTime?: number;\n /** Key to exclude from query key generation */\n excludeFromKey?: keyof TParams;\n}\n\n/**\n * Creates useQuery options with consistent key generation.\n * Filters out undefined/null/empty params from the query key.\n */\nexport const createQueryOptions = <\n TParams extends Record<string, unknown>,\n TData,\n>({\n baseKey,\n params,\n queryFn,\n enabledFn,\n staleTime = CACHE_CONFIG.SEMI_STATIC.staleTime,\n gcTime = CACHE_CONFIG.SEMI_STATIC.gcTime,\n excludeFromKey,\n}: QueryOptionsConfig<TParams, TData>): UseQueryOptions<TData> => {\n const paramEntries = Object.entries(params)\n .filter(([key, value]) => {\n if (excludeFromKey && key === excludeFromKey) return false;\n return value !== undefined && value !== null && value !== \"\";\n })\n .map(([key, value]) => `${key}: ${value}`);\n\n const queryKey = [baseKey, ...paramEntries];\n\n return {\n queryKey,\n queryFn: () => queryFn(params),\n enabled: enabledFn ? enabledFn(params) : true,\n staleTime,\n gcTime,\n };\n};\n\n/**\n * Creates useSuspenseQuery options with consistent key generation.\n * Same as createQueryOptions but without enabledFn (suspense queries\n * cannot be disabled).\n */\nexport const createSuspenseQueryOptions = <\n TParams extends Record<string, unknown>,\n TData,\n>({\n baseKey,\n params,\n queryFn,\n staleTime = CACHE_CONFIG.SEMI_STATIC.staleTime,\n gcTime = CACHE_CONFIG.SEMI_STATIC.gcTime,\n excludeFromKey,\n}: Omit<\n QueryOptionsConfig<TParams, TData>,\n \"enabledFn\"\n>): UseSuspenseQueryOptions<TData> => {\n const paramEntries = Object.entries(params)\n .filter(([key, value]) => {\n if (excludeFromKey && key === excludeFromKey) return false;\n return value !== undefined && value !== null && value !== \"\";\n })\n .map(([key, value]) => `${key}: ${value}`);\n\n const queryKey = [baseKey, ...paramEntries];\n\n return {\n queryKey,\n queryFn: () => queryFn(params),\n staleTime,\n gcTime,\n };\n};\n"],"mappings":"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","names":["debugEnabled","g","ms","total","CACHE_CONFIG"]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@simpleapps-com/augur-server",
3
+ "version": "0.1.0",
4
+ "description": "Server-side utilities for Augur ecommerce sites (Redis caching, SDK helpers, auth)",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/simpleapps-com/augur-packages.git",
9
+ "directory": "packages/augur-server"
10
+ },
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ },
17
+ "./auth": {
18
+ "types": "./dist/auth.d.ts",
19
+ "import": "./dist/auth.js"
20
+ }
21
+ },
22
+ "sideEffects": false,
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "dependencies": {
27
+ "valibot": "^1.0.0",
28
+ "@simpleapps-com/augur-utils": "0.1.0"
29
+ },
30
+ "peerDependencies": {
31
+ "next": ">=16.0.0",
32
+ "next-auth": "5.0.0-beta.30",
33
+ "react": "^19.0.0",
34
+ "@tanstack/react-query": "^5.80.0",
35
+ "@simpleapps-com/augur-api": "^0.9.0",
36
+ "ioredis": "^5.9.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "ioredis": {
40
+ "optional": true
41
+ },
42
+ "next-auth": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@tanstack/react-query": "^5.80.0",
48
+ "@types/node": "^22.0.0",
49
+ "ioredis": "^5.9.0",
50
+ "react": "^19.0.0",
51
+ "tsup": "^8.5.0",
52
+ "vitest": "^3.2.0",
53
+ "@augur-packages/tsconfig": "0.0.0"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup",
57
+ "dev": "tsup --watch",
58
+ "lint": "eslint src/",
59
+ "test": "vitest run --passWithNoTests",
60
+ "typecheck": "tsc --noEmit"
61
+ }
62
+ }