@simpleapps-com/augur-server 0.1.6 → 0.1.8

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 CHANGED
@@ -18,7 +18,14 @@ interface AugurSession {
18
18
  token?: string;
19
19
  expires: string;
20
20
  }
21
- /** Site-specific callbacks the consumer must provide. */
21
+ /** JWT token shape used by Augur auth. */
22
+ interface AugurJWT {
23
+ id: string;
24
+ username: string;
25
+ isVerified: boolean;
26
+ token?: string;
27
+ }
28
+ /** Site-specific callbacks the consumer can provide. */
22
29
  interface AugurAuthCallbacks {
23
30
  /** Fetch user profile from the Augur API given the user's Joomla ID. */
24
31
  getUserProfile: (userId: string) => Promise<AugurUser | null>;
@@ -27,18 +34,62 @@ interface AugurAuthCallbacks {
27
34
  cartHdrUid?: number;
28
35
  } | null>;
29
36
  }
37
+ /**
38
+ * Minimal interface for the Augur SDK client used by auth internals.
39
+ * Any `AugurAPI` instance from `@simpleapps-com/augur-api` satisfies this.
40
+ */
41
+ interface AugurAuthClient {
42
+ joomla: {
43
+ users: {
44
+ doc: {
45
+ get(userId: number, params?: {
46
+ edgeCache?: number | string;
47
+ }): Promise<{
48
+ data: {
49
+ id: number;
50
+ name: string;
51
+ username: string;
52
+ email: string;
53
+ customerId?: string;
54
+ contactId?: string;
55
+ };
56
+ }>;
57
+ };
58
+ };
59
+ };
60
+ commerce: {
61
+ cartHdr: {
62
+ lookup: {
63
+ get(params: {
64
+ userId: number;
65
+ customerId: number;
66
+ contactId: number;
67
+ cartToken?: string;
68
+ }): Promise<{
69
+ data: {
70
+ cartHdrUid?: number;
71
+ };
72
+ }>;
73
+ };
74
+ };
75
+ };
76
+ }
30
77
  interface CreateAuthConfigOptions {
31
- /** Site-specific callbacks. */
32
- callbacks: AugurAuthCallbacks;
78
+ /** Augur SDK client. When provided, auth SDK calls are handled internally. */
79
+ augurClient?: AugurAuthClient;
80
+ /** Site-specific callbacks. Override internal SDK calls when custom behavior is needed. */
81
+ callbacks?: Partial<AugurAuthCallbacks>;
33
82
  /** NextAuth secret (defaults to NEXT_PUBLIC_AUTH_SECRET env var). */
34
83
  secret?: string;
35
84
  /** Session max age in seconds (defaults to 4 hours). */
36
85
  maxAge?: number;
37
86
  /** Default customer ID when profile doesn't provide one. */
38
87
  defaultCustomerId?: string | number;
88
+ /** Default contact ID for cart header lookups. */
89
+ defaultContactId?: string | number;
39
90
  /** Enable NextAuth debug logging (defaults to NODE_ENV === "development"). */
40
91
  debug?: boolean;
41
92
  }
42
93
  declare function createAuthConfig(options: CreateAuthConfigOptions): NextAuthConfig;
43
94
 
44
- export { type AugurAuthCallbacks, type AugurSession, type AugurUser, type CreateAuthConfigOptions, createAuthConfig };
95
+ export { type AugurAuthCallbacks, type AugurAuthClient, type AugurJWT, type AugurSession, type AugurUser, type CreateAuthConfigOptions, createAuthConfig };
package/dist/auth.js CHANGED
@@ -2,24 +2,75 @@ import "./chunk-DGUM43GV.js";
2
2
 
3
3
  // src/auth.ts
4
4
  import Credentials from "next-auth/providers/credentials";
5
- async function lookupCartHdr(siteCallbacks, profile, defaultCustomerId) {
5
+ function buildInternalCallbacks(augurClient) {
6
+ return {
7
+ async getUserProfile(userId) {
8
+ if (!userId) return null;
9
+ const uid = parseInt(userId, 10);
10
+ if (isNaN(uid)) return null;
11
+ const result = await augurClient.joomla.users.doc.get(uid, {
12
+ edgeCache: 1
13
+ });
14
+ const data = result.data;
15
+ if (!data) return null;
16
+ return {
17
+ id: String(data.id),
18
+ name: data.name,
19
+ username: data.username,
20
+ email: data.email,
21
+ isVerified: true,
22
+ customerId: data.customerId,
23
+ contactId: data.contactId
24
+ };
25
+ },
26
+ async cartHdrLookup(userId, _token, contactId, customerId) {
27
+ const result = await augurClient.commerce.cartHdr.lookup.get({
28
+ userId: Number(userId) || 0,
29
+ customerId: Number(customerId) || 0,
30
+ contactId: Number(contactId) || 0
31
+ });
32
+ return result.data ?? null;
33
+ }
34
+ };
35
+ }
36
+ function resolveCallbacks(options) {
37
+ const { augurClient, callbacks } = options;
38
+ if (augurClient) {
39
+ const internal = buildInternalCallbacks(augurClient);
40
+ return {
41
+ getUserProfile: callbacks?.getUserProfile ?? internal.getUserProfile,
42
+ cartHdrLookup: callbacks?.cartHdrLookup ?? internal.cartHdrLookup
43
+ };
44
+ }
45
+ if (!callbacks?.getUserProfile) {
46
+ throw new Error(
47
+ "createAuthConfig requires either `augurClient` or `callbacks.getUserProfile`"
48
+ );
49
+ }
50
+ return {
51
+ getUserProfile: callbacks.getUserProfile,
52
+ cartHdrLookup: callbacks.cartHdrLookup
53
+ };
54
+ }
55
+ async function lookupCartHdr(siteCallbacks, profile, defaultCustomerId, defaultContactId) {
6
56
  if (!siteCallbacks.cartHdrLookup || !profile.id) return void 0;
7
57
  const cartHdr = await siteCallbacks.cartHdrLookup(
8
58
  profile.id,
9
59
  "",
10
- profile.contactId ?? "",
60
+ profile.contactId ?? defaultContactId ?? "",
11
61
  profile.customerId ?? defaultCustomerId ?? ""
12
62
  );
13
63
  return cartHdr?.cartHdrUid;
14
64
  }
15
65
  function createAuthConfig(options) {
16
66
  const {
17
- callbacks: siteCallbacks,
18
67
  secret = process.env.NEXT_PUBLIC_AUTH_SECRET,
19
68
  maxAge = 4 * 60 * 60,
20
69
  defaultCustomerId,
70
+ defaultContactId,
21
71
  debug = process.env.NODE_ENV === "development"
22
72
  } = options;
73
+ const siteCallbacks = resolveCallbacks(options);
23
74
  return {
24
75
  providers: [
25
76
  Credentials({
@@ -67,7 +118,8 @@ function createAuthConfig(options) {
67
118
  const cartHdrUid = await lookupCartHdr(
68
119
  siteCallbacks,
69
120
  userProfile,
70
- defaultCustomerId
121
+ defaultCustomerId,
122
+ defaultContactId
71
123
  );
72
124
  return buildSession({
73
125
  name: userProfile.name ?? session.user?.name,
package/dist/auth.js.map CHANGED
@@ -1 +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 */\nasync function lookupCartHdr(\n siteCallbacks: AugurAuthCallbacks,\n profile: AugurUser,\n defaultCustomerId?: string | number,\n): Promise<number | undefined> {\n if (!siteCallbacks.cartHdrLookup || !profile.id) return undefined;\n const cartHdr = await siteCallbacks.cartHdrLookup(\n profile.id,\n \"\",\n profile.contactId ?? \"\",\n profile.customerId ?? defaultCustomerId ?? \"\",\n );\n return cartHdr?.cartHdrUid;\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 const baseUser = {\n id: (token.id as string) || \"\",\n username: (token.username as string) || \"\",\n isVerified: (token.isVerified as boolean) || false,\n };\n\n const buildSession = (userOverrides = {}) => ({\n ...session,\n user: { ...session.user, ...baseUser, ...userOverrides },\n token: token.token as string | undefined,\n });\n\n try {\n const userProfile = await siteCallbacks.getUserProfile(baseUser.id);\n if (!userProfile) return buildSession();\n\n const cartHdrUid = await lookupCartHdr(\n siteCallbacks,\n userProfile,\n defaultCustomerId,\n );\n\n return buildSession({\n name: userProfile.name ?? session.user?.name,\n email: userProfile.email ?? session.user?.email,\n customerId: userProfile.customerId ?? defaultCustomerId,\n contactId: userProfile.contactId,\n cartHdrUid,\n });\n } catch {\n return buildSession();\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":";;;AAIA,OAAO,iBAAiB;AAqExB,eAAe,cACb,eACA,SACA,mBAC6B;AAC7B,MAAI,CAAC,cAAc,iBAAiB,CAAC,QAAQ,GAAI,QAAO;AACxD,QAAM,UAAU,MAAM,cAAc;AAAA,IAClC,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ,aAAa;AAAA,IACrB,QAAQ,cAAc,qBAAqB;AAAA,EAC7C;AACA,SAAO,SAAS;AAClB;AAEO,SAAS,iBACd,SACgB;AAChB,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,SAAS,QAAQ,IAAI;AAAA,IACrB,SAAS,IAAI,KAAK;AAAA,IAClB;AAAA,IACA,QAAQ,QAAQ,IAAI,aAAa;AAAA,EACnC,IAAI;AAEJ,SAAO;AAAA,IACL,WAAW;AAAA,MACT,YAAY;AAAA,QACV,MAAM;AAAA,QACN,aAAa;AAAA,UACX,UAAU,EAAE,OAAO,YAAY,MAAM,OAAO;AAAA,UAC5C,UAAU,EAAE,OAAO,YAAY,MAAM,WAAW;AAAA,UAChD,IAAI,EAAE,OAAO,MAAM,MAAM,OAAO;AAAA,UAChC,YAAY,EAAE,OAAO,eAAe,MAAM,OAAO;AAAA,UACjD,OAAO,EAAE,OAAO,SAAS,MAAM,OAAO;AAAA,QACxC;AAAA,QACA,MAAM,UAAU,aAAa;AAC3B,cAAI,CAAC,YAAa,QAAO;AAEzB,gBAAM,EAAE,IAAI,YAAY,UAAU,MAAM,IAAI;AAO5C,iBAAO;AAAA,YACL;AAAA,YACA,YAAY,eAAe;AAAA,YAC3B;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,WAAW;AAAA,MACT,MAAM,SAAS;AACb,eAAO;AAAA,MACT;AAAA,MACA,MAAM,SAAS,EAAE,QAAQ,GAAG;AAC1B,eAAO;AAAA,MACT;AAAA,MACA,MAAM,QAAQ,EAAE,SAAS,MAAM,GAAG;AAChC,cAAM,WAAW;AAAA,UACf,IAAK,MAAM,MAAiB;AAAA,UAC5B,UAAW,MAAM,YAAuB;AAAA,UACxC,YAAa,MAAM,cAA0B;AAAA,QAC/C;AAEA,cAAM,eAAe,CAAC,gBAAgB,CAAC,OAAO;AAAA,UAC5C,GAAG;AAAA,UACH,MAAM,EAAE,GAAG,QAAQ,MAAM,GAAG,UAAU,GAAG,cAAc;AAAA,UACvD,OAAO,MAAM;AAAA,QACf;AAEA,YAAI;AACF,gBAAM,cAAc,MAAM,cAAc,eAAe,SAAS,EAAE;AAClE,cAAI,CAAC,YAAa,QAAO,aAAa;AAEtC,gBAAM,aAAa,MAAM;AAAA,YACvB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAEA,iBAAO,aAAa;AAAA,YAClB,MAAM,YAAY,QAAQ,QAAQ,MAAM;AAAA,YACxC,OAAO,YAAY,SAAS,QAAQ,MAAM;AAAA,YAC1C,YAAY,YAAY,cAAc;AAAA,YACtC,WAAW,YAAY;AAAA,YACvB;AAAA,UACF,CAAC;AAAA,QACH,QAAQ;AACN,iBAAO,aAAa;AAAA,QACtB;AAAA,MACF;AAAA,MACA,MAAM,IAAI,EAAE,OAAO,KAAK,GAAG;AACzB,YAAI,MAAM;AACR,gBAAM,YAAY;AAOlB,iBAAO;AAAA,YACL,GAAG;AAAA,YACH,IAAI,UAAU;AAAA,YACd,YAAY,UAAU;AAAA,YACtB,UAAU,UAAU;AAAA,YACpB,OAAO,UAAU;AAAA,UACnB;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP,UAAU;AAAA,MACV;AAAA,IACF;AAAA,IACA,WAAW;AAAA,EACb;AACF;","names":[]}
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/** JWT token shape used by Augur auth. */\nexport interface AugurJWT {\n id: string;\n username: string;\n isVerified: boolean;\n token?: string;\n}\n\n/** Site-specific callbacks the consumer can 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\n/**\n * Minimal interface for the Augur SDK client used by auth internals.\n * Any `AugurAPI` instance from `@simpleapps-com/augur-api` satisfies this.\n */\nexport interface AugurAuthClient {\n joomla: {\n users: {\n doc: {\n get(\n userId: number,\n params?: { edgeCache?: number | string },\n ): Promise<{\n data: {\n id: number;\n name: string;\n username: string;\n email: string;\n customerId?: string;\n contactId?: string;\n };\n }>;\n };\n };\n };\n commerce: {\n cartHdr: {\n lookup: {\n get(params: {\n userId: number;\n customerId: number;\n contactId: number;\n cartToken?: string;\n }): Promise<{\n data: { cartHdrUid?: number };\n }>;\n };\n };\n };\n}\n\nexport interface CreateAuthConfigOptions {\n /** Augur SDK client. When provided, auth SDK calls are handled internally. */\n augurClient?: AugurAuthClient;\n /** Site-specific callbacks. Override internal SDK calls when custom behavior is needed. */\n callbacks?: Partial<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 /** Default contact ID for cart header lookups. */\n defaultContactId?: string | number;\n /** Enable NextAuth debug logging (defaults to NODE_ENV === \"development\"). */\n debug?: boolean;\n}\n\n/** Build internal callbacks that call the Augur SDK directly. */\nfunction buildInternalCallbacks(\n augurClient: AugurAuthClient,\n): AugurAuthCallbacks {\n return {\n async getUserProfile(userId: string): Promise<AugurUser | null> {\n if (!userId) return null;\n const uid = parseInt(userId, 10);\n if (isNaN(uid)) return null;\n const result = await augurClient.joomla.users.doc.get(uid, {\n edgeCache: 1,\n });\n const data = result.data;\n if (!data) return null;\n return {\n id: String(data.id),\n name: data.name,\n username: data.username,\n email: data.email,\n isVerified: true,\n customerId: data.customerId,\n contactId: data.contactId,\n };\n },\n async cartHdrLookup(\n userId: string | number,\n _token: string,\n contactId: string | number,\n customerId: string | number,\n ): Promise<{ cartHdrUid?: number } | null> {\n const result = await augurClient.commerce.cartHdr.lookup.get({\n userId: Number(userId) || 0,\n customerId: Number(customerId) || 0,\n contactId: Number(contactId) || 0,\n });\n return result.data ?? null;\n },\n };\n}\n\n/** Resolve final callbacks: callback overrides take priority over internal SDK calls. */\nfunction resolveCallbacks(\n options: CreateAuthConfigOptions,\n): AugurAuthCallbacks {\n const { augurClient, callbacks } = options;\n\n if (augurClient) {\n const internal = buildInternalCallbacks(augurClient);\n return {\n getUserProfile: callbacks?.getUserProfile ?? internal.getUserProfile,\n cartHdrLookup: callbacks?.cartHdrLookup ?? internal.cartHdrLookup,\n };\n }\n\n if (!callbacks?.getUserProfile) {\n throw new Error(\n \"createAuthConfig requires either `augurClient` or `callbacks.getUserProfile`\",\n );\n }\n\n return {\n getUserProfile: callbacks.getUserProfile,\n cartHdrLookup: callbacks.cartHdrLookup,\n };\n}\n\n/**\n * Create a NextAuth 5 configuration for an Augur ecommerce site.\n *\n * Accepts either an `augurClient` (recommended) for zero-boilerplate auth,\n * or explicit `callbacks` for custom behavior. Callback overrides take\n * priority when both are provided.\n *\n * @example\n * ```ts\n * // Recommended: pass the SDK client directly\n * import NextAuth from \"next-auth\";\n * import { createAuthConfig } from \"@simpleapps-com/augur-server/auth\";\n * import { augurClient } from \"@/lib/augur-client\";\n *\n * export const { handlers, signIn, signOut, auth } = NextAuth(\n * createAuthConfig({\n * augurClient,\n * defaultCustomerId: process.env.NEXT_PUBLIC_DEFAULT_CUSTOMER_ID,\n * defaultContactId: process.env.NEXT_PUBLIC_DEFAULT_CONTACT_ID,\n * }),\n * );\n * ```\n *\n * @example\n * ```ts\n * // Custom callbacks (backward-compatible)\n * import NextAuth from \"next-auth\";\n * import { createAuthConfig } from \"@simpleapps-com/augur-server/auth\";\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 */\nasync function lookupCartHdr(\n siteCallbacks: AugurAuthCallbacks,\n profile: AugurUser,\n defaultCustomerId?: string | number,\n defaultContactId?: string | number,\n): Promise<number | undefined> {\n if (!siteCallbacks.cartHdrLookup || !profile.id) return undefined;\n const cartHdr = await siteCallbacks.cartHdrLookup(\n profile.id,\n \"\",\n profile.contactId ?? defaultContactId ?? \"\",\n profile.customerId ?? defaultCustomerId ?? \"\",\n );\n return cartHdr?.cartHdrUid;\n}\n\nexport function createAuthConfig(\n options: CreateAuthConfigOptions,\n): NextAuthConfig {\n const {\n secret = process.env.NEXT_PUBLIC_AUTH_SECRET,\n maxAge = 4 * 60 * 60,\n defaultCustomerId,\n defaultContactId,\n debug = process.env.NODE_ENV === \"development\",\n } = options;\n\n const siteCallbacks = resolveCallbacks(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 const baseUser = {\n id: (token.id as string) || \"\",\n username: (token.username as string) || \"\",\n isVerified: (token.isVerified as boolean) || false,\n };\n\n const buildSession = (userOverrides = {}) => ({\n ...session,\n user: { ...session.user, ...baseUser, ...userOverrides },\n token: token.token as string | undefined,\n });\n\n try {\n const userProfile = await siteCallbacks.getUserProfile(baseUser.id);\n if (!userProfile) return buildSession();\n\n const cartHdrUid = await lookupCartHdr(\n siteCallbacks,\n userProfile,\n defaultCustomerId,\n defaultContactId,\n );\n\n return buildSession({\n name: userProfile.name ?? session.user?.name,\n email: userProfile.email ?? session.user?.email,\n customerId: userProfile.customerId ?? defaultCustomerId,\n contactId: userProfile.contactId,\n cartHdrUid,\n });\n } catch {\n return buildSession();\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,50 @@
1
+ // src/query-options-helper.ts
2
+ import { CACHE_CONFIG } from "@simpleapps-com/augur-utils";
3
+ var createQueryOptions = ({
4
+ baseKey,
5
+ params,
6
+ queryFn,
7
+ enabledFn,
8
+ staleTime = CACHE_CONFIG.SEMI_STATIC.staleTime,
9
+ gcTime = CACHE_CONFIG.SEMI_STATIC.gcTime,
10
+ excludeFromKey
11
+ }) => {
12
+ const paramEntries = Object.entries(params).filter(([key, value]) => {
13
+ if (excludeFromKey && key === excludeFromKey) return false;
14
+ return value !== void 0 && value !== null && value !== "";
15
+ }).map(([key, value]) => `${key}:${value}`).sort();
16
+ const queryKey = [baseKey, ...paramEntries];
17
+ return {
18
+ queryKey,
19
+ queryFn: () => queryFn(params),
20
+ enabled: enabledFn ? enabledFn(params) : true,
21
+ staleTime,
22
+ gcTime
23
+ };
24
+ };
25
+ var createSuspenseQueryOptions = ({
26
+ baseKey,
27
+ params,
28
+ queryFn,
29
+ staleTime = CACHE_CONFIG.SEMI_STATIC.staleTime,
30
+ gcTime = CACHE_CONFIG.SEMI_STATIC.gcTime,
31
+ excludeFromKey
32
+ }) => {
33
+ const paramEntries = Object.entries(params).filter(([key, value]) => {
34
+ if (excludeFromKey && key === excludeFromKey) return false;
35
+ return value !== void 0 && value !== null && value !== "";
36
+ }).map(([key, value]) => `${key}:${value}`).sort();
37
+ const queryKey = [baseKey, ...paramEntries];
38
+ return {
39
+ queryKey,
40
+ queryFn: () => queryFn(params),
41
+ staleTime,
42
+ gcTime
43
+ };
44
+ };
45
+
46
+ export {
47
+ createQueryOptions,
48
+ createSuspenseQueryOptions
49
+ };
50
+ //# sourceMappingURL=chunk-I26RJDRF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/query-options-helper.ts"],"sourcesContent":["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 .sort();\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 .sort();\n\n const queryKey = [baseKey, ...paramEntries];\n\n return {\n queryKey,\n queryFn: () => queryFn(params),\n staleTime,\n gcTime,\n };\n};\n"],"mappings":";AAIA,SAAS,oBAAoB;AAuBtB,IAAM,qBAAqB,CAGhC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY,aAAa,YAAY;AAAA,EACrC,SAAS,aAAa,YAAY;AAAA,EAClC;AACF,MAAkE;AAChE,QAAM,eAAe,OAAO,QAAQ,MAAM,EACvC,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM;AACxB,QAAI,kBAAkB,QAAQ,eAAgB,QAAO;AACrD,WAAO,UAAU,UAAa,UAAU,QAAQ,UAAU;AAAA,EAC5D,CAAC,EACA,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,EACvC,KAAK;AAER,QAAM,WAAW,CAAC,SAAS,GAAG,YAAY;AAE1C,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MAAM,QAAQ,MAAM;AAAA,IAC7B,SAAS,YAAY,UAAU,MAAM,IAAI;AAAA,IACzC;AAAA,IACA;AAAA,EACF;AACF;AAOO,IAAM,6BAA6B,CAGxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY,aAAa,YAAY;AAAA,EACrC,SAAS,aAAa,YAAY;AAAA,EAClC;AACF,MAGsC;AACpC,QAAM,eAAe,OAAO,QAAQ,MAAM,EACvC,OAAO,CAAC,CAAC,KAAK,KAAK,MAAM;AACxB,QAAI,kBAAkB,QAAQ,eAAgB,QAAO;AACrD,WAAO,UAAU,UAAa,UAAU,QAAQ,UAAU;AAAA,EAC5D,CAAC,EACA,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG,GAAG,IAAI,KAAK,EAAE,EACvC,KAAK;AAER,QAAM,WAAW,CAAC,SAAS,GAAG,YAAY;AAE1C,SAAO;AAAA,IACL;AAAA,IACA,SAAS,MAAM,QAAQ,MAAM;AAAA,IAC7B;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { QueryClient, UseQueryOptions, UseSuspenseQueryOptions } from '@tanstack/react-query';
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ export { QueryOptionsConfig, createQueryOptions, createSuspenseQueryOptions } from './query.js';
2
3
 
3
4
  declare const env: "development" | "staging" | "production";
4
5
  declare const isDev: boolean;
@@ -88,32 +89,4 @@ declare function createServerQueryClient(): QueryClient;
88
89
  /** Singleton server query client, reused across requests. */
89
90
  declare function getServerQueryClient(): QueryClient;
90
91
 
91
- interface QueryOptionsConfig<TParams, TData> {
92
- /** Base query key (e.g., "customer-orders") */
93
- baseKey: string;
94
- /** Parameters object */
95
- params: TParams;
96
- /** Function that returns the data */
97
- queryFn: (params: TParams) => Promise<TData>;
98
- /** Function to determine if query should be enabled */
99
- enabledFn?: (params: TParams) => boolean;
100
- /** Custom stale time in milliseconds (default: SEMI_STATIC) */
101
- staleTime?: number;
102
- /** Custom garbage collection time in milliseconds (default: SEMI_STATIC) */
103
- gcTime?: number;
104
- /** Key to exclude from query key generation */
105
- excludeFromKey?: keyof TParams;
106
- }
107
- /**
108
- * Creates useQuery options with consistent key generation.
109
- * Filters out undefined/null/empty params from the query key.
110
- */
111
- declare const createQueryOptions: <TParams extends Record<string, unknown>, TData>({ baseKey, params, queryFn, enabledFn, staleTime, gcTime, excludeFromKey, }: QueryOptionsConfig<TParams, TData>) => UseQueryOptions<TData>;
112
- /**
113
- * Creates useSuspenseQuery options with consistent key generation.
114
- * Same as createQueryOptions but without enabledFn (suspense queries
115
- * cannot be disabled).
116
- */
117
- declare const createSuspenseQueryOptions: <TParams extends Record<string, unknown>, TData>({ baseKey, params, queryFn, staleTime, gcTime, excludeFromKey, }: Omit<QueryOptionsConfig<TParams, TData>, "enabledFn">) => UseSuspenseQueryOptions<TData>;
118
-
119
- export { type QueryOptionsConfig, cacheGet, cacheSet, cachedSdkCall, createQueryOptions, createServerQueryClient, createSuspenseQueryOptions, env, getCacheStats, getCircuitState, getServerQueryClient, isDev, isProduction, isRedisConnected, isStaging, sdkCall };
92
+ export { cacheGet, cacheSet, cachedSdkCall, createServerQueryClient, env, getCacheStats, getCircuitState, getServerQueryClient, isDev, isProduction, isRedisConnected, isStaging, sdkCall };
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ createQueryOptions,
3
+ createSuspenseQueryOptions
4
+ } from "./chunk-I26RJDRF.js";
1
5
  import {
2
6
  __require
3
7
  } from "./chunk-DGUM43GV.js";
@@ -314,51 +318,6 @@ function getServerQueryClient() {
314
318
  }
315
319
  return serverQueryClient;
316
320
  }
317
-
318
- // src/query-options-helper.ts
319
- import { CACHE_CONFIG as CACHE_CONFIG2 } from "@simpleapps-com/augur-utils";
320
- var createQueryOptions = ({
321
- baseKey,
322
- params,
323
- queryFn,
324
- enabledFn,
325
- staleTime = CACHE_CONFIG2.SEMI_STATIC.staleTime,
326
- gcTime = CACHE_CONFIG2.SEMI_STATIC.gcTime,
327
- excludeFromKey
328
- }) => {
329
- const paramEntries = Object.entries(params).filter(([key, value]) => {
330
- if (excludeFromKey && key === excludeFromKey) return false;
331
- return value !== void 0 && value !== null && value !== "";
332
- }).map(([key, value]) => `${key}: ${value}`);
333
- const queryKey = [baseKey, ...paramEntries];
334
- return {
335
- queryKey,
336
- queryFn: () => queryFn(params),
337
- enabled: enabledFn ? enabledFn(params) : true,
338
- staleTime,
339
- gcTime
340
- };
341
- };
342
- var createSuspenseQueryOptions = ({
343
- baseKey,
344
- params,
345
- queryFn,
346
- staleTime = CACHE_CONFIG2.SEMI_STATIC.staleTime,
347
- gcTime = CACHE_CONFIG2.SEMI_STATIC.gcTime,
348
- excludeFromKey
349
- }) => {
350
- const paramEntries = Object.entries(params).filter(([key, value]) => {
351
- if (excludeFromKey && key === excludeFromKey) return false;
352
- return value !== void 0 && value !== null && value !== "";
353
- }).map(([key, value]) => `${key}: ${value}`);
354
- const queryKey = [baseKey, ...paramEntries];
355
- return {
356
- queryKey,
357
- queryFn: () => queryFn(params),
358
- staleTime,
359
- gcTime
360
- };
361
- };
362
321
  export {
363
322
  cacheGet,
364
323
  cacheSet,
package/dist/index.js.map CHANGED
@@ -1 +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\n/**\n * Extracts the `edgeCache` value from the arguments and converts it to\n * a TTL in seconds. Supports both the legacy numeric-hours format and the\n * augur-api >= 0.9.6 sub-hour string formats ('30s', '1m', '5m').\n *\n * - Number values (1, 2, 3, 4, 5, 8): treated as hours\n * - Numeric strings ('1', '2', etc.): treated as hours\n * - Duration strings ('30s', '1m', '5m'): parsed as seconds/minutes\n */\nfunction extractTtlSeconds(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 const val = (arg as { edgeCache: unknown }).edgeCache;\n if (typeof val === \"number\") return val * 3600;\n if (typeof val === \"string\") {\n if (val.endsWith(\"s\")) return parseInt(val, 10);\n if (val.endsWith(\"m\")) return parseInt(val, 10) * 60;\n const n = Number(val);\n return isNaN(n) ? undefined : n * 3600;\n }\n return undefined;\n }\n }\n return undefined;\n}\n\nfunction formatTtl(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n if (seconds < 3600) return `${Math.round(seconds / 60)}m`;\n return `${Math.round(seconds / 3600)}h`;\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 any argument\n * contains an `edgeCache` property, the result is cached in Redis for\n * that duration. Without `edgeCache`, the call bypasses cache.\n *\n * Accepted `edgeCache` values (matching augur-api CacheParams):\n * - Numbers: 1–8 (hours)\n * - Sub-hour strings: '30s', '1m', '5m'\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<TArgs extends unknown[], TResult>(\n prefix: string,\n methodPath: string,\n method: (...args: TArgs) => Promise<TResult>,\n ...args: TArgs\n): Promise<TResult> {\n const ttlSeconds = extractTtlSeconds(args);\n\n if (!CACHE_ENABLED || ttlSeconds === 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 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 ${formatTtl(ttlSeconds)})`,\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, forwarding all arguments with full type safety.\n *\n * With augur-api >= 0.9.6, SDK methods properly declare their `CacheParams`\n * option (including `edgeCache`), so this wrapper preserves both parameter\n * and return-type inference end-to-end.\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<TArgs extends unknown[], TResult>(\n method: (...args: TArgs) => Promise<TResult>,\n ...args: TArgs\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"]}
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"],"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\n/**\n * Extracts the `edgeCache` value from the arguments and converts it to\n * a TTL in seconds. Supports both the legacy numeric-hours format and the\n * augur-api >= 0.9.6 sub-hour string formats ('30s', '1m', '5m').\n *\n * - Number values (1, 2, 3, 4, 5, 8): treated as hours\n * - Numeric strings ('1', '2', etc.): treated as hours\n * - Duration strings ('30s', '1m', '5m'): parsed as seconds/minutes\n */\nfunction extractTtlSeconds(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 const val = (arg as { edgeCache: unknown }).edgeCache;\n if (typeof val === \"number\") return val * 3600;\n if (typeof val === \"string\") {\n if (val.endsWith(\"s\")) return parseInt(val, 10);\n if (val.endsWith(\"m\")) return parseInt(val, 10) * 60;\n const n = Number(val);\n return isNaN(n) ? undefined : n * 3600;\n }\n return undefined;\n }\n }\n return undefined;\n}\n\nfunction formatTtl(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n if (seconds < 3600) return `${Math.round(seconds / 60)}m`;\n return `${Math.round(seconds / 3600)}h`;\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 any argument\n * contains an `edgeCache` property, the result is cached in Redis for\n * that duration. Without `edgeCache`, the call bypasses cache.\n *\n * Accepted `edgeCache` values (matching augur-api CacheParams):\n * - Numbers: 1–8 (hours)\n * - Sub-hour strings: '30s', '1m', '5m'\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<TArgs extends unknown[], TResult>(\n prefix: string,\n methodPath: string,\n method: (...args: TArgs) => Promise<TResult>,\n ...args: TArgs\n): Promise<TResult> {\n const ttlSeconds = extractTtlSeconds(args);\n\n if (!CACHE_ENABLED || ttlSeconds === 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 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 ${formatTtl(ttlSeconds)})`,\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, forwarding all arguments with full type safety.\n *\n * With augur-api >= 0.9.6, SDK methods properly declare their `CacheParams`\n * option (including `edgeCache`), so this wrapper preserves both parameter\n * and return-type inference end-to-end.\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<TArgs extends unknown[], TResult>(\n method: (...args: TArgs) => Promise<TResult>,\n ...args: TArgs\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"],"mappings":"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","names":["debugEnabled","g","ms","total"]}
@@ -0,0 +1,31 @@
1
+ import { UseQueryOptions, UseSuspenseQueryOptions } from '@tanstack/react-query';
2
+
3
+ interface QueryOptionsConfig<TParams, TData> {
4
+ /** Base query key (e.g., "customer-orders") */
5
+ baseKey: string;
6
+ /** Parameters object */
7
+ params: TParams;
8
+ /** Function that returns the data */
9
+ queryFn: (params: TParams) => Promise<TData>;
10
+ /** Function to determine if query should be enabled */
11
+ enabledFn?: (params: TParams) => boolean;
12
+ /** Custom stale time in milliseconds (default: SEMI_STATIC) */
13
+ staleTime?: number;
14
+ /** Custom garbage collection time in milliseconds (default: SEMI_STATIC) */
15
+ gcTime?: number;
16
+ /** Key to exclude from query key generation */
17
+ excludeFromKey?: keyof TParams;
18
+ }
19
+ /**
20
+ * Creates useQuery options with consistent key generation.
21
+ * Filters out undefined/null/empty params from the query key.
22
+ */
23
+ declare const createQueryOptions: <TParams extends Record<string, unknown>, TData>({ baseKey, params, queryFn, enabledFn, staleTime, gcTime, excludeFromKey, }: QueryOptionsConfig<TParams, TData>) => UseQueryOptions<TData>;
24
+ /**
25
+ * Creates useSuspenseQuery options with consistent key generation.
26
+ * Same as createQueryOptions but without enabledFn (suspense queries
27
+ * cannot be disabled).
28
+ */
29
+ declare const createSuspenseQueryOptions: <TParams extends Record<string, unknown>, TData>({ baseKey, params, queryFn, staleTime, gcTime, excludeFromKey, }: Omit<QueryOptionsConfig<TParams, TData>, "enabledFn">) => UseSuspenseQueryOptions<TData>;
30
+
31
+ export { type QueryOptionsConfig, createQueryOptions, createSuspenseQueryOptions };
package/dist/query.js ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ createQueryOptions,
3
+ createSuspenseQueryOptions
4
+ } from "./chunk-I26RJDRF.js";
5
+ import "./chunk-DGUM43GV.js";
6
+ export {
7
+ createQueryOptions,
8
+ createSuspenseQueryOptions
9
+ };
10
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simpleapps-com/augur-server",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Server-side utilities for Augur ecommerce sites (Redis caching, SDK helpers, auth)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,6 +17,10 @@
17
17
  "./auth": {
18
18
  "types": "./dist/auth.d.ts",
19
19
  "import": "./dist/auth.js"
20
+ },
21
+ "./query": {
22
+ "types": "./dist/query.d.ts",
23
+ "import": "./dist/query.js"
20
24
  }
21
25
  },
22
26
  "sideEffects": false,
@@ -25,7 +29,7 @@
25
29
  ],
26
30
  "dependencies": {
27
31
  "valibot": "^1.0.0",
28
- "@simpleapps-com/augur-utils": "0.1.6"
32
+ "@simpleapps-com/augur-utils": "0.1.8"
29
33
  },
30
34
  "peerDependencies": {
31
35
  "@simpleapps-com/augur-api": "^0.9.6",