@htlkg/data 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +72 -0
  2. package/dist/client/index.d.ts +123 -30
  3. package/dist/client/index.js +75 -1
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/hooks/index.d.ts +76 -2
  6. package/dist/hooks/index.js +224 -6
  7. package/dist/hooks/index.js.map +1 -1
  8. package/dist/index.d.ts +6 -4
  9. package/dist/index.js +550 -7
  10. package/dist/index.js.map +1 -1
  11. package/dist/mutations/index.d.ts +149 -5
  12. package/dist/mutations/index.js +397 -0
  13. package/dist/mutations/index.js.map +1 -1
  14. package/dist/productInstances-CzT3NZKU.d.ts +98 -0
  15. package/dist/queries/index.d.ts +54 -2
  16. package/dist/queries/index.js +60 -1
  17. package/dist/queries/index.js.map +1 -1
  18. package/dist/server/index.d.ts +47 -0
  19. package/dist/server/index.js +59 -0
  20. package/dist/server/index.js.map +1 -0
  21. package/package.json +5 -1
  22. package/src/client/index.ts +82 -3
  23. package/src/client/proxy.ts +170 -0
  24. package/src/hooks/index.ts +1 -0
  25. package/src/hooks/useProductInstances.ts +174 -0
  26. package/src/index.ts +11 -0
  27. package/src/mutations/accounts.ts +102 -1
  28. package/src/mutations/brands.ts +102 -1
  29. package/src/mutations/index.ts +23 -0
  30. package/src/mutations/productInstances/index.ts +14 -0
  31. package/src/mutations/productInstances/productInstances.integration.test.ts +621 -0
  32. package/src/mutations/productInstances/productInstances.test.ts +680 -0
  33. package/src/mutations/productInstances/productInstances.ts +280 -0
  34. package/src/mutations/systemSettings.ts +130 -0
  35. package/src/mutations/users.ts +102 -1
  36. package/src/queries/index.ts +9 -0
  37. package/src/queries/systemSettings.ts +115 -0
@@ -0,0 +1,47 @@
1
+ import { AstroGlobal } from 'astro';
2
+ import { ResourcesConfig } from 'aws-amplify';
3
+ import { CommonPublicClientOptions, DefaultCommonClientOptions, V6ClientSSRCookies } from 'aws-amplify/api/internals';
4
+
5
+ /**
6
+ * Server-side data client for Astro
7
+ *
8
+ * Provides a client generator similar to Next.js's generateServerClientUsingCookies
9
+ * using generateClientWithAmplifyInstance for proper server context integration.
10
+ */
11
+
12
+ interface AstroCookiesClientParams {
13
+ cookies: AstroGlobal["cookies"];
14
+ request: AstroGlobal["request"];
15
+ config: ResourcesConfig;
16
+ }
17
+ /**
18
+ * Generates a server-side data client for Astro (matches Next.js implementation)
19
+ *
20
+ * This function creates a client that automatically wraps all operations in the Amplify server context,
21
+ * ensuring that authentication tokens from cookies are properly used.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import type { Schema } from '../amplify/data/resource';
26
+ * import { generateServerClientUsingCookies } from '@htlkg/data/client';
27
+ * import { parseAmplifyConfig } from 'aws-amplify/utils';
28
+ * import outputs from '../amplify_outputs.json';
29
+ *
30
+ * const amplifyConfig = parseAmplifyConfig(outputs);
31
+ *
32
+ * const client = generateServerClientUsingCookies<Schema>({
33
+ * config: amplifyConfig,
34
+ * cookies: Astro.cookies,
35
+ * request: Astro.request,
36
+ * });
37
+ *
38
+ * // Use the client directly - operations are automatically wrapped
39
+ * const result = await client.models.User.list({
40
+ * selectionSet: ['id', 'email'],
41
+ * limit: 100,
42
+ * });
43
+ * ```
44
+ */
45
+ declare function generateServerClientUsingCookies<T extends Record<any, any> = never, Options extends CommonPublicClientOptions & AstroCookiesClientParams = DefaultCommonClientOptions & AstroCookiesClientParams>(options: Options): V6ClientSSRCookies<T, Options>;
46
+
47
+ export { generateServerClientUsingCookies };
@@ -0,0 +1,59 @@
1
+ // src/client/server.ts
2
+ import {
3
+ generateClientWithAmplifyInstance
4
+ } from "aws-amplify/api/internals";
5
+ import { getAmplifyServerContext } from "aws-amplify/adapter-core/internals";
6
+ import { createRunWithAmplifyServerContext, createLogger } from "@htlkg/core/amplify-astro-adapter";
7
+ var log = createLogger("server-client");
8
+ function generateServerClientUsingCookies(options) {
9
+ const runWithAmplifyServerContext = createRunWithAmplifyServerContext({
10
+ config: options.config
11
+ });
12
+ const resourcesConfig = options.config;
13
+ const getAmplify = (fn) => {
14
+ return runWithAmplifyServerContext({
15
+ astroServerContext: {
16
+ cookies: options.cookies,
17
+ request: options.request
18
+ },
19
+ operation: async (contextSpec) => {
20
+ const amplifyInstance = getAmplifyServerContext(contextSpec).amplify;
21
+ try {
22
+ const config = amplifyInstance.getConfig();
23
+ log.debug("Amplify config from instance:", {
24
+ hasAPI: !!config.API,
25
+ hasGraphQL: !!config.API?.GraphQL,
26
+ endpoint: config.API?.GraphQL?.endpoint,
27
+ defaultAuthMode: config.API?.GraphQL?.defaultAuthMode,
28
+ region: config.API?.GraphQL?.region
29
+ });
30
+ const session = await amplifyInstance.Auth.fetchAuthSession();
31
+ log.debug("Auth session:", {
32
+ hasTokens: !!session.tokens,
33
+ hasAccessToken: !!session.tokens?.accessToken,
34
+ hasIdToken: !!session.tokens?.idToken,
35
+ hasCredentials: !!session.credentials
36
+ });
37
+ } catch (e) {
38
+ log.debug("Error fetching session:", e.message);
39
+ }
40
+ return fn(amplifyInstance);
41
+ }
42
+ });
43
+ };
44
+ const {
45
+ cookies: _cookies,
46
+ request: _request,
47
+ config: _config,
48
+ ...params
49
+ } = options;
50
+ return generateClientWithAmplifyInstance({
51
+ amplify: getAmplify,
52
+ config: resourcesConfig,
53
+ ...params
54
+ });
55
+ }
56
+ export {
57
+ generateServerClientUsingCookies
58
+ };
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/server.ts"],"sourcesContent":["/**\n * Server-side data client for Astro\n *\n * Provides a client generator similar to Next.js's generateServerClientUsingCookies\n * using generateClientWithAmplifyInstance for proper server context integration.\n */\n\nimport type { AstroGlobal } from \"astro\";\nimport type { ResourcesConfig } from \"aws-amplify\";\nimport {\n\tCommonPublicClientOptions,\n\tDefaultCommonClientOptions,\n\tV6ClientSSRCookies,\n\tgenerateClientWithAmplifyInstance,\n} from \"aws-amplify/api/internals\";\nimport { getAmplifyServerContext } from \"aws-amplify/adapter-core/internals\";\nimport { createRunWithAmplifyServerContext, createLogger } from \"@htlkg/core/amplify-astro-adapter\";\n\nconst log = createLogger('server-client');\n\ninterface AstroCookiesClientParams {\n\tcookies: AstroGlobal[\"cookies\"];\n\trequest: AstroGlobal[\"request\"];\n\tconfig: ResourcesConfig;\n}\n\n/**\n * Generates a server-side data client for Astro (matches Next.js implementation)\n *\n * This function creates a client that automatically wraps all operations in the Amplify server context,\n * ensuring that authentication tokens from cookies are properly used.\n *\n * @example\n * ```typescript\n * import type { Schema } from '../amplify/data/resource';\n * import { generateServerClientUsingCookies } from '@htlkg/data/client';\n * import { parseAmplifyConfig } from 'aws-amplify/utils';\n * import outputs from '../amplify_outputs.json';\n *\n * const amplifyConfig = parseAmplifyConfig(outputs);\n *\n * const client = generateServerClientUsingCookies<Schema>({\n * config: amplifyConfig,\n * cookies: Astro.cookies,\n * request: Astro.request,\n * });\n *\n * // Use the client directly - operations are automatically wrapped\n * const result = await client.models.User.list({\n * selectionSet: ['id', 'email'],\n * limit: 100,\n * });\n * ```\n */\nexport function generateServerClientUsingCookies<\n\tT extends Record<any, any> = never,\n\tOptions extends CommonPublicClientOptions &\n\t\tAstroCookiesClientParams = DefaultCommonClientOptions &\n\t\tAstroCookiesClientParams,\n>(options: Options): V6ClientSSRCookies<T, Options> {\n\tconst runWithAmplifyServerContext = createRunWithAmplifyServerContext({\n\t\tconfig: options.config,\n\t});\n\n\tconst resourcesConfig = options.config;\n\n\t// This function reference gets passed down to InternalGraphQLAPI.ts.graphql\n\t// where this._graphql is passed in as the `fn` argument\n\t// causing it to always get invoked inside `runWithAmplifyServerContext`\n\tconst getAmplify = (fn: (amplify: any) => Promise<any>) => {\n\t\treturn runWithAmplifyServerContext({\n\t\t\tastroServerContext: {\n\t\t\t\tcookies: options.cookies,\n\t\t\t\trequest: options.request,\n\t\t\t},\n\t\t\toperation: async (contextSpec: any) => {\n\t\t\t\tconst amplifyInstance = getAmplifyServerContext(contextSpec).amplify;\n\t\t\t\t\n\t\t\t\t// Debug logging (only when DEBUG=true)\n\t\t\t\ttry {\n\t\t\t\t\tconst config = amplifyInstance.getConfig();\n\t\t\t\t\tlog.debug('Amplify config from instance:', {\n\t\t\t\t\t\thasAPI: !!config.API,\n\t\t\t\t\t\thasGraphQL: !!config.API?.GraphQL,\n\t\t\t\t\t\tendpoint: config.API?.GraphQL?.endpoint,\n\t\t\t\t\t\tdefaultAuthMode: config.API?.GraphQL?.defaultAuthMode,\n\t\t\t\t\t\tregion: config.API?.GraphQL?.region,\n\t\t\t\t\t});\n\t\t\t\t\t\n\t\t\t\t\tconst session = await amplifyInstance.Auth.fetchAuthSession();\n\t\t\t\t\tlog.debug('Auth session:', {\n\t\t\t\t\t\thasTokens: !!session.tokens,\n\t\t\t\t\t\thasAccessToken: !!session.tokens?.accessToken,\n\t\t\t\t\t\thasIdToken: !!session.tokens?.idToken,\n\t\t\t\t\t\thasCredentials: !!session.credentials,\n\t\t\t\t\t});\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tlog.debug('Error fetching session:', e.message);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\treturn fn(amplifyInstance);\n\t\t\t},\n\t\t});\n\t};\n\n\tconst {\n\t\tcookies: _cookies,\n\t\trequest: _request,\n\t\tconfig: _config,\n\t\t...params\n\t} = options;\n\n\treturn generateClientWithAmplifyInstance<T, V6ClientSSRCookies<T, Options>>({\n\t\tamplify: getAmplify,\n\t\tconfig: resourcesConfig,\n\t\t...params,\n\t} as any);\n}\n"],"mappings":";AASA;AAAA,EAIC;AAAA,OACM;AACP,SAAS,+BAA+B;AACxC,SAAS,mCAAmC,oBAAoB;AAEhE,IAAM,MAAM,aAAa,eAAe;AAoCjC,SAAS,iCAKd,SAAkD;AACnD,QAAM,8BAA8B,kCAAkC;AAAA,IACrE,QAAQ,QAAQ;AAAA,EACjB,CAAC;AAED,QAAM,kBAAkB,QAAQ;AAKhC,QAAM,aAAa,CAAC,OAAuC;AAC1D,WAAO,4BAA4B;AAAA,MAClC,oBAAoB;AAAA,QACnB,SAAS,QAAQ;AAAA,QACjB,SAAS,QAAQ;AAAA,MAClB;AAAA,MACA,WAAW,OAAO,gBAAqB;AACtC,cAAM,kBAAkB,wBAAwB,WAAW,EAAE;AAG7D,YAAI;AACH,gBAAM,SAAS,gBAAgB,UAAU;AACzC,cAAI,MAAM,iCAAiC;AAAA,YAC1C,QAAQ,CAAC,CAAC,OAAO;AAAA,YACjB,YAAY,CAAC,CAAC,OAAO,KAAK;AAAA,YAC1B,UAAU,OAAO,KAAK,SAAS;AAAA,YAC/B,iBAAiB,OAAO,KAAK,SAAS;AAAA,YACtC,QAAQ,OAAO,KAAK,SAAS;AAAA,UAC9B,CAAC;AAED,gBAAM,UAAU,MAAM,gBAAgB,KAAK,iBAAiB;AAC5D,cAAI,MAAM,iBAAiB;AAAA,YAC1B,WAAW,CAAC,CAAC,QAAQ;AAAA,YACrB,gBAAgB,CAAC,CAAC,QAAQ,QAAQ;AAAA,YAClC,YAAY,CAAC,CAAC,QAAQ,QAAQ;AAAA,YAC9B,gBAAgB,CAAC,CAAC,QAAQ;AAAA,UAC3B,CAAC;AAAA,QACF,SAAS,GAAQ;AAChB,cAAI,MAAM,2BAA2B,EAAE,OAAO;AAAA,QAC/C;AAEA,eAAO,GAAG,eAAe;AAAA,MAC1B;AAAA,IACD,CAAC;AAAA,EACF;AAEA,QAAM;AAAA,IACL,SAAS;AAAA,IACT,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,GAAG;AAAA,EACJ,IAAI;AAEJ,SAAO,kCAAqE;AAAA,IAC3E,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,GAAG;AAAA,EACJ,CAAQ;AACT;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htlkg/data",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -11,6 +11,10 @@
11
11
  "import": "./dist/client/index.js",
12
12
  "types": "./dist/client/index.d.ts"
13
13
  },
14
+ "./server": {
15
+ "import": "./dist/server/index.js",
16
+ "types": "./dist/server/index.d.ts"
17
+ },
14
18
  "./queries": {
15
19
  "import": "./dist/queries/index.js",
16
20
  "types": "./dist/queries/index.d.ts"
@@ -3,14 +3,36 @@
3
3
  *
4
4
  * Provides both client-side and server-side GraphQL capabilities using AWS Amplify Data.
5
5
  * The server-side functions use the Amplify Astro adapter for proper SSR support.
6
+ *
7
+ * For server-side usage, use `Astro.locals.amplifyClient` (zero-config, injected by middleware).
6
8
  */
7
9
 
8
10
  import { generateClient as generateDataClient } from "aws-amplify/data";
11
+ import { Amplify } from "aws-amplify";
9
12
  import type { ResourcesConfig } from "aws-amplify";
13
+ import { generateServerClientUsingCookies } from "./server";
10
14
 
11
- // Re-export server-side client generation
15
+ // Re-export server-side client generation (used internally by middleware)
12
16
  export { generateServerClientUsingCookies } from "./server";
13
17
 
18
+ // Re-export proxy functions for authenticated client-side operations
19
+ export {
20
+ mutate,
21
+ query,
22
+ hasErrors,
23
+ getErrorMessage,
24
+ type Operation,
25
+ type GraphQLResponse,
26
+ type ProxyOptions,
27
+ } from "./proxy";
28
+
29
+ /**
30
+ * Type for the server-side Amplify client (for use in type declarations)
31
+ * This represents the client returned by generateServerClientUsingCookies
32
+ */
33
+ export type AmplifyServerClient<TSchema extends Record<string, unknown> = Record<string, unknown>> =
34
+ ReturnType<typeof generateServerClientUsingCookies<TSchema>>;
35
+
14
36
  // Singleton client instance for client-side fetching
15
37
  let sharedClientInstance: any = null;
16
38
 
@@ -134,12 +156,69 @@ export interface GenerateServerClientOptions {
134
156
  */
135
157
  export function generateServerClient<
136
158
  TSchema extends Record<string, unknown> = Record<string, unknown>,
137
- >(options?: GenerateServerClientOptions): ReturnType<typeof generateDataClient<TSchema>> {
159
+ >(_options?: GenerateServerClientOptions): ReturnType<typeof generateDataClient<TSchema>> {
138
160
  // Generate the client without authMode parameter
139
161
  // When called within runWithAmplifyServerContext, it will automatically use the token provider
140
162
  // from the context (which reads JWT tokens from cookies)
141
163
  // The authMode should be specified per-operation, not at client creation
142
164
  const client = generateDataClient<TSchema>();
143
-
165
+
144
166
  return client;
145
167
  }
168
+
169
+ /**
170
+ * Context required for getting a server client in API routes
171
+ */
172
+ export interface ServerClientContext {
173
+ locals: { amplifyClient?: any; user?: any };
174
+ cookies: any;
175
+ request: Request;
176
+ }
177
+
178
+ /**
179
+ * Get the server client from Astro context
180
+ *
181
+ * Uses locals.amplifyClient if available (set by middleware),
182
+ * otherwise creates a new client using Amplify's global config.
183
+ * No config parameter needed - uses the config set by the middleware.
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * import { getServerClient } from '@htlkg/data/client';
188
+ *
189
+ * export const POST: APIRoute = async (context) => {
190
+ * const client = getServerClient(context);
191
+ * if (!client) return new Response('Not authenticated', { status: 401 });
192
+ *
193
+ * const result = await client.models.User.list();
194
+ * };
195
+ * ```
196
+ */
197
+ export function getServerClient<TSchema extends Record<string, unknown> = Record<string, unknown>>(
198
+ context: ServerClientContext,
199
+ ): ReturnType<typeof generateServerClientUsingCookies<TSchema>> | null {
200
+ // Try to use client from middleware first
201
+ if (context.locals.amplifyClient) {
202
+ return context.locals.amplifyClient as ReturnType<typeof generateServerClientUsingCookies<TSchema>>;
203
+ }
204
+
205
+ // If no client from middleware and user is authenticated, create one using global Amplify config
206
+ if (context.locals.user) {
207
+ try {
208
+ // Get config from Amplify (set by middleware)
209
+ const amplifyConfig = Amplify.getConfig();
210
+ if (amplifyConfig) {
211
+ return generateServerClientUsingCookies<TSchema>({
212
+ config: amplifyConfig,
213
+ cookies: context.cookies,
214
+ request: context.request,
215
+ });
216
+ }
217
+ } catch (e) {
218
+ console.error('[getServerClient] Failed to get Amplify config:', e);
219
+ }
220
+ }
221
+
222
+ // No authentication available
223
+ return null;
224
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * GraphQL Proxy Client
3
+ *
4
+ * Client-side helper for making authenticated GraphQL operations through
5
+ * the server-side proxy. This allows Vue components to perform mutations
6
+ * while keeping auth cookies httpOnly for security.
7
+ *
8
+ * @module @htlkg/data/proxy
9
+ */
10
+
11
+ /**
12
+ * Valid GraphQL operations
13
+ */
14
+ export type Operation = "create" | "update" | "delete" | "get" | "list";
15
+
16
+ /**
17
+ * Response type from GraphQL operations
18
+ */
19
+ export interface GraphQLResponse<T = any> {
20
+ data: T | null;
21
+ errors?: Array<{ message: string; [key: string]: any }>;
22
+ }
23
+
24
+ /**
25
+ * Options for proxy requests
26
+ */
27
+ export interface ProxyOptions {
28
+ /** Custom API endpoint (default: /api/graphql) */
29
+ endpoint?: string;
30
+ /** Additional fetch options */
31
+ fetchOptions?: RequestInit;
32
+ }
33
+
34
+ /**
35
+ * Default proxy endpoint
36
+ */
37
+ const DEFAULT_ENDPOINT = "/api/graphql";
38
+
39
+ /**
40
+ * Execute a GraphQL mutation through the server proxy
41
+ *
42
+ * @param model - The model name (e.g., 'User', 'Brand', 'Account')
43
+ * @param operation - The operation type ('create', 'update', 'delete')
44
+ * @param data - The operation data/input
45
+ * @param options - Optional configuration
46
+ * @returns Promise with the operation result
47
+ *
48
+ * @example Create
49
+ * ```typescript
50
+ * const result = await mutate('User', 'create', {
51
+ * email: 'user@example.com',
52
+ * accountId: '123',
53
+ * });
54
+ * ```
55
+ *
56
+ * @example Update
57
+ * ```typescript
58
+ * const result = await mutate('User', 'update', {
59
+ * id: 'user-id',
60
+ * status: 'deleted',
61
+ * deletedAt: new Date().toISOString(),
62
+ * });
63
+ * ```
64
+ *
65
+ * @example Delete
66
+ * ```typescript
67
+ * const result = await mutate('User', 'delete', { id: 'user-id' });
68
+ * ```
69
+ */
70
+ export async function mutate<T = any>(
71
+ model: string,
72
+ operation: "create" | "update" | "delete",
73
+ data: Record<string, any>,
74
+ options?: ProxyOptions,
75
+ ): Promise<GraphQLResponse<T>> {
76
+ return proxyRequest<T>(model, operation, data, options);
77
+ }
78
+
79
+ /**
80
+ * Execute a GraphQL query through the server proxy
81
+ *
82
+ * @param model - The model name (e.g., 'User', 'Brand', 'Account')
83
+ * @param operation - The operation type ('get', 'list')
84
+ * @param data - The query parameters (id for get, filter/limit for list)
85
+ * @param options - Optional configuration
86
+ * @returns Promise with the query result
87
+ *
88
+ * @example Get by ID
89
+ * ```typescript
90
+ * const result = await query('User', 'get', { id: 'user-id' });
91
+ * ```
92
+ *
93
+ * @example List with filter
94
+ * ```typescript
95
+ * const result = await query('User', 'list', {
96
+ * filter: { status: { eq: 'active' } },
97
+ * limit: 100,
98
+ * });
99
+ * ```
100
+ */
101
+ export async function query<T = any>(
102
+ model: string,
103
+ operation: "get" | "list",
104
+ data?: Record<string, any>,
105
+ options?: ProxyOptions,
106
+ ): Promise<GraphQLResponse<T>> {
107
+ return proxyRequest<T>(model, operation, data, options);
108
+ }
109
+
110
+ /**
111
+ * Internal function to make proxy requests
112
+ */
113
+ async function proxyRequest<T>(
114
+ model: string,
115
+ operation: Operation,
116
+ data?: Record<string, any>,
117
+ options?: ProxyOptions,
118
+ ): Promise<GraphQLResponse<T>> {
119
+ const endpoint = options?.endpoint ?? DEFAULT_ENDPOINT;
120
+
121
+ try {
122
+ const response = await fetch(endpoint, {
123
+ method: "POST",
124
+ headers: {
125
+ "Content-Type": "application/json",
126
+ },
127
+ credentials: "include", // Important: include cookies
128
+ ...options?.fetchOptions,
129
+ body: JSON.stringify({ model, operation, data }),
130
+ });
131
+
132
+ const result: GraphQLResponse<T> = await response.json();
133
+
134
+ // Handle auth errors
135
+ if (response.status === 401) {
136
+ console.error("[GraphQL Proxy] Unauthorized - session may have expired");
137
+ // Optionally trigger a redirect to login
138
+ // window.location.href = '/login';
139
+ }
140
+
141
+ return result;
142
+ } catch (error) {
143
+ console.error("[GraphQL Proxy] Request failed:", error);
144
+ return {
145
+ data: null,
146
+ errors: [
147
+ {
148
+ message: error instanceof Error ? error.message : "Network error",
149
+ },
150
+ ],
151
+ };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Helper to check if a response has errors
157
+ */
158
+ export function hasErrors(response: GraphQLResponse): boolean {
159
+ return !!response.errors && response.errors.length > 0;
160
+ }
161
+
162
+ /**
163
+ * Helper to get the first error message from a response
164
+ */
165
+ export function getErrorMessage(response: GraphQLResponse): string | null {
166
+ if (!response.errors || response.errors.length === 0) {
167
+ return null;
168
+ }
169
+ return response.errors[0].message;
170
+ }
@@ -19,3 +19,4 @@ export { useBrands, type UseBrandsOptions, type UseBrandsReturn } from "./useBra
19
19
  export { useAccounts, type UseAccountsOptions, type UseAccountsReturn } from "./useAccounts";
20
20
  export { useUsers, type UseUsersOptions, type UseUsersReturn } from "./useUsers";
21
21
  export { useProducts, type UseProductsOptions, type UseProductsReturn } from "./useProducts";
22
+ export { useProductInstances, type UseProductInstancesOptions, type UseProductInstancesReturn } from "./useProductInstances";
@@ -0,0 +1,174 @@
1
+ /**
2
+ * useProductInstances Hook
3
+ *
4
+ * Vue composable for fetching and managing product instance data with reactive state.
5
+ * Provides loading states, error handling, refetch capabilities, and CRUD operations.
6
+ */
7
+
8
+ import type { Ref, ComputedRef } from "vue";
9
+ import type { ProductInstance } from "@htlkg/core/types";
10
+ import { createDataHook, type BaseHookOptions } from "./createDataHook";
11
+ import { getSharedClient } from "../client";
12
+ import {
13
+ createProductInstance,
14
+ updateProductInstance,
15
+ deleteProductInstance,
16
+ toggleProductInstanceEnabled,
17
+ type CreateProductInstanceInput,
18
+ type UpdateProductInstanceInput,
19
+ } from "../mutations/productInstances";
20
+
21
+ export interface UseProductInstancesOptions extends BaseHookOptions {
22
+ /** Filter criteria for product instances */
23
+ filter?: any;
24
+ /** Limit number of results */
25
+ limit?: number;
26
+ /** Auto-fetch on mount (default: true) */
27
+ autoFetch?: boolean;
28
+ /** Filter by brand ID */
29
+ brandId?: string;
30
+ /** Filter by account ID */
31
+ accountId?: string;
32
+ /** Filter by product ID */
33
+ productId?: string;
34
+ /** Only enabled instances */
35
+ enabledOnly?: boolean;
36
+ }
37
+
38
+ export interface UseProductInstancesReturn {
39
+ /** Reactive array of product instances */
40
+ instances: Ref<ProductInstance[]>;
41
+ /** Computed array of enabled instances only */
42
+ enabledInstances: ComputedRef<ProductInstance[]>;
43
+ /** Loading state */
44
+ loading: Ref<boolean>;
45
+ /** Error state */
46
+ error: Ref<Error | null>;
47
+ /** Refetch product instances */
48
+ refetch: () => Promise<void>;
49
+ /** Create a new product instance */
50
+ createInstance: (input: CreateProductInstanceInput) => Promise<ProductInstance>;
51
+ /** Update an existing product instance */
52
+ updateInstance: (input: UpdateProductInstanceInput) => Promise<ProductInstance>;
53
+ /** Delete a product instance */
54
+ deleteInstance: (id: string) => Promise<boolean>;
55
+ /** Toggle the enabled status of a product instance */
56
+ toggleEnabled: (id: string, enabled: boolean) => Promise<ProductInstance>;
57
+ }
58
+
59
+ /**
60
+ * Build filter from hook options
61
+ */
62
+ function buildFilter(options: UseProductInstancesOptions): any {
63
+ let filter: any = options.filter || {};
64
+
65
+ if (options.brandId) {
66
+ filter = { ...filter, brandId: { eq: options.brandId } };
67
+ }
68
+
69
+ if (options.accountId) {
70
+ filter = { ...filter, accountId: { eq: options.accountId } };
71
+ }
72
+
73
+ if (options.productId) {
74
+ filter = { ...filter, productId: { eq: options.productId } };
75
+ }
76
+
77
+ if (options.enabledOnly) {
78
+ filter = { ...filter, enabled: { eq: true } };
79
+ }
80
+
81
+ return Object.keys(filter).length > 0 ? filter : undefined;
82
+ }
83
+
84
+ /**
85
+ * Internal hook created by factory
86
+ */
87
+ const useProductInstancesInternal = createDataHook<
88
+ ProductInstance,
89
+ UseProductInstancesOptions,
90
+ { enabledInstances: ProductInstance[] }
91
+ >({
92
+ model: "ProductInstance",
93
+ dataPropertyName: "instances",
94
+ buildFilter,
95
+ computedProperties: {
96
+ enabledInstances: (instances) => instances.filter((i) => i.enabled),
97
+ },
98
+ });
99
+
100
+ /**
101
+ * Composable for fetching and managing product instances
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * import { useProductInstances } from '@htlkg/data/hooks';
106
+ *
107
+ * const { instances, loading, error, refetch } = useProductInstances();
108
+ * ```
109
+ *
110
+ * @example With filters
111
+ * ```typescript
112
+ * const { instances, enabledInstances, loading } = useProductInstances({
113
+ * brandId: 'brand-123',
114
+ * enabledOnly: true,
115
+ * limit: 50
116
+ * });
117
+ * ```
118
+ *
119
+ * @example For a specific account
120
+ * ```typescript
121
+ * const { instances, loading } = useProductInstances({
122
+ * accountId: 'account-456',
123
+ * autoFetch: true
124
+ * });
125
+ * ```
126
+ */
127
+ export function useProductInstances(options: UseProductInstancesOptions = {}): UseProductInstancesReturn {
128
+ const result = useProductInstancesInternal(options);
129
+
130
+ // CRUD methods using mutations from @htlkg/data/mutations
131
+ async function createInstance(input: CreateProductInstanceInput): Promise<ProductInstance> {
132
+ const client = getSharedClient();
133
+ const instance = await createProductInstance(client, input);
134
+ if (!instance) {
135
+ throw new Error("Failed to create product instance");
136
+ }
137
+ return instance;
138
+ }
139
+
140
+ async function updateInstance(input: UpdateProductInstanceInput): Promise<ProductInstance> {
141
+ const client = getSharedClient();
142
+ const instance = await updateProductInstance(client, input);
143
+ if (!instance) {
144
+ throw new Error("Failed to update product instance");
145
+ }
146
+ return instance;
147
+ }
148
+
149
+ async function deleteInstance(id: string): Promise<boolean> {
150
+ const client = getSharedClient();
151
+ return deleteProductInstance(client, id);
152
+ }
153
+
154
+ async function toggleEnabled(id: string, enabled: boolean): Promise<ProductInstance> {
155
+ const client = getSharedClient();
156
+ const instance = await toggleProductInstanceEnabled(client, id, enabled);
157
+ if (!instance) {
158
+ throw new Error("Failed to toggle product instance");
159
+ }
160
+ return instance;
161
+ }
162
+
163
+ return {
164
+ instances: result.instances as Ref<ProductInstance[]>,
165
+ enabledInstances: result.enabledInstances as ComputedRef<ProductInstance[]>,
166
+ loading: result.loading,
167
+ error: result.error,
168
+ refetch: result.refetch,
169
+ createInstance,
170
+ updateInstance,
171
+ deleteInstance,
172
+ toggleEnabled,
173
+ };
174
+ }
package/src/index.ts CHANGED
@@ -16,6 +16,17 @@ export {
16
16
  type AstroAmplifyConfig,
17
17
  } from "./client";
18
18
 
19
+ // Proxy exports (for authenticated client-side operations)
20
+ export {
21
+ mutate,
22
+ query,
23
+ hasErrors,
24
+ getErrorMessage,
25
+ type Operation,
26
+ type GraphQLResponse,
27
+ type ProxyOptions,
28
+ } from "./client";
29
+
19
30
  // Query exports
20
31
  export * from "./queries";
21
32