@htlkg/data 0.0.1 → 0.0.3
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/README.md +53 -0
- package/dist/client/index.d.ts +117 -31
- package/dist/client/index.js +74 -22
- package/dist/client/index.js.map +1 -1
- package/dist/content-collections/index.js +20 -24
- package/dist/content-collections/index.js.map +1 -1
- package/dist/hooks/index.d.ts +113 -5
- package/dist/hooks/index.js +165 -182
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +305 -182
- package/dist/index.js.map +1 -1
- package/dist/queries/index.d.ts +78 -1
- package/dist/queries/index.js +47 -0
- package/dist/queries/index.js.map +1 -1
- package/dist/stores/index.d.ts +106 -0
- package/dist/stores/index.js +108 -0
- package/dist/stores/index.js.map +1 -0
- package/package.json +60 -37
- package/src/client/__tests__/server.test.ts +100 -0
- package/src/client/client.md +91 -0
- package/src/client/index.test.ts +232 -0
- package/src/client/index.ts +145 -0
- package/src/client/server.ts +118 -0
- package/src/content-collections/content-collections.md +87 -0
- package/src/content-collections/generator.ts +314 -0
- package/src/content-collections/index.ts +32 -0
- package/src/content-collections/schemas.ts +75 -0
- package/src/content-collections/sync.ts +139 -0
- package/src/hooks/README.md +293 -0
- package/src/hooks/createDataHook.ts +208 -0
- package/src/hooks/data-hook-errors.property.test.ts +270 -0
- package/src/hooks/data-hook-filters.property.test.ts +263 -0
- package/src/hooks/data-hooks.property.test.ts +190 -0
- package/src/hooks/hooks.test.ts +76 -0
- package/src/hooks/index.ts +21 -0
- package/src/hooks/useAccounts.ts +66 -0
- package/src/hooks/useBrands.ts +95 -0
- package/src/hooks/useProducts.ts +88 -0
- package/src/hooks/useUsers.ts +89 -0
- package/src/index.ts +32 -0
- package/src/mutations/accounts.ts +127 -0
- package/src/mutations/brands.ts +133 -0
- package/src/mutations/index.ts +32 -0
- package/src/mutations/mutations.md +96 -0
- package/src/mutations/users.ts +136 -0
- package/src/queries/accounts.ts +121 -0
- package/src/queries/brands.ts +176 -0
- package/src/queries/index.ts +45 -0
- package/src/queries/products.ts +282 -0
- package/src/queries/queries.md +88 -0
- package/src/queries/server-helpers.ts +114 -0
- package/src/queries/users.ts +199 -0
- package/src/stores/createStores.ts +148 -0
- package/src/stores/index.ts +15 -0
- package/src/stores/stores.md +104 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Client Module
|
|
2
|
+
|
|
3
|
+
GraphQL client generation for client-side and server-side data fetching.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
generateClient,
|
|
10
|
+
generateServerClient,
|
|
11
|
+
getSharedClient,
|
|
12
|
+
resetSharedClient,
|
|
13
|
+
} from '@htlkg/data/client';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Client-Side
|
|
17
|
+
|
|
18
|
+
### getSharedClient
|
|
19
|
+
|
|
20
|
+
Singleton client for client-side fetching. Avoids creating multiple instances.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { getSharedClient } from '@htlkg/data/client';
|
|
24
|
+
import type { Schema } from '@backend/data/resource';
|
|
25
|
+
|
|
26
|
+
const client = getSharedClient<Schema>();
|
|
27
|
+
const { data } = await client.models.Account.list();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### generateClient
|
|
31
|
+
|
|
32
|
+
Create a new client instance. Use in component setup or after hydration.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { generateClient } from '@htlkg/data/client';
|
|
36
|
+
|
|
37
|
+
const client = generateClient<Schema>();
|
|
38
|
+
const { data: brands } = await client.models.Brand.list();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### resetSharedClient
|
|
42
|
+
|
|
43
|
+
Reset the singleton (useful for auth state changes or testing).
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { resetSharedClient } from '@htlkg/data/client';
|
|
47
|
+
|
|
48
|
+
// After logout
|
|
49
|
+
resetSharedClient();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Server-Side
|
|
53
|
+
|
|
54
|
+
### generateServerClient
|
|
55
|
+
|
|
56
|
+
Create a server-side client for SSR. Must be called inside `runWithAmplifyServerContext`.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { generateServerClient } from '@htlkg/data/client';
|
|
60
|
+
import { createRunWithAmplifyServerContext } from '@htlkg/core/amplify-astro-adapter';
|
|
61
|
+
|
|
62
|
+
const runWithAmplifyServerContext = createRunWithAmplifyServerContext({
|
|
63
|
+
config: amplifyConfig
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const result = await runWithAmplifyServerContext({
|
|
67
|
+
astroServerContext: {
|
|
68
|
+
cookies: Astro.cookies,
|
|
69
|
+
request: Astro.request,
|
|
70
|
+
},
|
|
71
|
+
operation: async (contextSpec) => {
|
|
72
|
+
const client = generateServerClient<Schema>();
|
|
73
|
+
return await client.models.User.list();
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Auth Modes
|
|
79
|
+
|
|
80
|
+
| Mode | Description |
|
|
81
|
+
|------|-------------|
|
|
82
|
+
| `userPool` | Uses JWT from cookies (default) |
|
|
83
|
+
| `apiKey` | Uses API key for public requests |
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Authenticated request
|
|
87
|
+
const client = generateServerClient<Schema>({ authMode: 'userPool' });
|
|
88
|
+
|
|
89
|
+
// Public request
|
|
90
|
+
const client = generateServerClient<Schema>({ authMode: 'apiKey' });
|
|
91
|
+
```
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for @htlkg/data/client
|
|
3
|
+
*
|
|
4
|
+
* Tests the GraphQL client generation functions for both client-side and server-side usage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import type { AstroCookies } from 'astro';
|
|
9
|
+
|
|
10
|
+
// Mock aws-amplify
|
|
11
|
+
vi.mock('aws-amplify', () => ({
|
|
12
|
+
Amplify: {
|
|
13
|
+
configure: vi.fn(),
|
|
14
|
+
getConfig: vi.fn(() => ({}))
|
|
15
|
+
}
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Mock aws-amplify/data
|
|
19
|
+
vi.mock('aws-amplify/data', () => ({
|
|
20
|
+
generateClient: vi.fn(() => ({
|
|
21
|
+
models: {
|
|
22
|
+
Brand: {
|
|
23
|
+
list: vi.fn(),
|
|
24
|
+
get: vi.fn(),
|
|
25
|
+
create: vi.fn(),
|
|
26
|
+
update: vi.fn(),
|
|
27
|
+
delete: vi.fn()
|
|
28
|
+
},
|
|
29
|
+
User: {
|
|
30
|
+
list: vi.fn(),
|
|
31
|
+
get: vi.fn()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}))
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
// Mock @htlkg/core/amplify-astro-adapter
|
|
38
|
+
vi.mock('@htlkg/core/amplify-astro-adapter', () => ({
|
|
39
|
+
createRunWithAmplifyServerContext: vi.fn(() => vi.fn())
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Mock aws-amplify/auth/server
|
|
43
|
+
vi.mock('aws-amplify/auth/server', () => ({
|
|
44
|
+
fetchAuthSession: vi.fn()
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
// Import after mocks are set up
|
|
48
|
+
import { generateClient, generateServerClient } from './index';
|
|
49
|
+
import { Amplify } from 'aws-amplify';
|
|
50
|
+
import { generateClient as generateDataClient } from 'aws-amplify/data';
|
|
51
|
+
import { createRunWithAmplifyServerContext } from '@htlkg/core/amplify-astro-adapter';
|
|
52
|
+
|
|
53
|
+
describe('generateClient', () => {
|
|
54
|
+
it('should generate a client-side GraphQL client', () => {
|
|
55
|
+
const client = generateClient();
|
|
56
|
+
|
|
57
|
+
expect(client).toBeDefined();
|
|
58
|
+
expect(client.models).toBeDefined();
|
|
59
|
+
expect(client.models.Brand).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return a client with model operations', () => {
|
|
63
|
+
const client = generateClient();
|
|
64
|
+
|
|
65
|
+
expect(client.models.Brand.list).toBeDefined();
|
|
66
|
+
expect(client.models.Brand.get).toBeDefined();
|
|
67
|
+
expect(client.models.Brand.create).toBeDefined();
|
|
68
|
+
expect(client.models.Brand.update).toBeDefined();
|
|
69
|
+
expect(client.models.Brand.delete).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should be callable multiple times', () => {
|
|
73
|
+
const client1 = generateClient();
|
|
74
|
+
const client2 = generateClient();
|
|
75
|
+
|
|
76
|
+
expect(client1).toBeDefined();
|
|
77
|
+
expect(client2).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('generateServerClient', () => {
|
|
82
|
+
let mockConfig: any;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
// Mock Amplify config
|
|
86
|
+
mockConfig = {
|
|
87
|
+
auth: {
|
|
88
|
+
user_pool_id: 'test-pool',
|
|
89
|
+
aws_region: 'us-east-1',
|
|
90
|
+
user_pool_client_id: 'test-client'
|
|
91
|
+
},
|
|
92
|
+
data: {
|
|
93
|
+
url: 'https://test.appsync-api.us-east-1.amazonaws.com/graphql',
|
|
94
|
+
aws_region: 'us-east-1',
|
|
95
|
+
api_key: 'test-api-key',
|
|
96
|
+
default_authorization_type: 'AMAZON_COGNITO_USER_POOLS',
|
|
97
|
+
authorization_types: ['API_KEY', 'AWS_IAM']
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
vi.clearAllMocks();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('with userPool auth mode (default)', () => {
|
|
107
|
+
it('should generate a server-side client with default userPool auth', () => {
|
|
108
|
+
const client = generateServerClient({
|
|
109
|
+
config: mockConfig
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(client).toBeDefined();
|
|
113
|
+
expect(client.models).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should use userPool auth mode by default', () => {
|
|
117
|
+
generateServerClient({
|
|
118
|
+
config: mockConfig
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(generateDataClient).toHaveBeenCalledWith({
|
|
122
|
+
authMode: 'userPool'
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should not call Amplify.configure (should be done at app startup)', () => {
|
|
127
|
+
vi.clearAllMocks();
|
|
128
|
+
|
|
129
|
+
generateServerClient({
|
|
130
|
+
config: mockConfig
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// generateServerClient should NOT call Amplify.configure
|
|
134
|
+
// It should be called once at app startup (e.g., in amplify-server.ts)
|
|
135
|
+
expect(Amplify.configure).not.toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should not call createRunWithAmplifyServerContext (should be done outside)', () => {
|
|
139
|
+
vi.clearAllMocks();
|
|
140
|
+
|
|
141
|
+
generateServerClient({
|
|
142
|
+
config: mockConfig
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// generateServerClient should NOT call createRunWithAmplifyServerContext
|
|
146
|
+
// The caller should wrap the operation with runWithAmplifyServerContext
|
|
147
|
+
expect(createRunWithAmplifyServerContext).not.toHaveBeenCalled();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('with apiKey auth mode', () => {
|
|
152
|
+
it('should generate a server-side client with API key auth', () => {
|
|
153
|
+
const client = generateServerClient({
|
|
154
|
+
config: mockConfig,
|
|
155
|
+
authMode: 'apiKey'
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(client).toBeDefined();
|
|
159
|
+
expect(client.models).toBeDefined();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should use apiKey auth mode when specified', () => {
|
|
163
|
+
generateServerClient({
|
|
164
|
+
config: mockConfig,
|
|
165
|
+
authMode: 'apiKey'
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(generateDataClient).toHaveBeenCalledWith({
|
|
169
|
+
authMode: 'apiKey'
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('configuration', () => {
|
|
175
|
+
it('should accept valid Amplify configuration', () => {
|
|
176
|
+
const client = generateServerClient({
|
|
177
|
+
config: mockConfig
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(client).toBeDefined();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should handle configuration with minimal auth setup', () => {
|
|
184
|
+
const minimalConfig = {
|
|
185
|
+
auth: {
|
|
186
|
+
user_pool_id: 'test-pool',
|
|
187
|
+
aws_region: 'us-east-1',
|
|
188
|
+
user_pool_client_id: 'test-client'
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const client = generateServerClient({
|
|
193
|
+
config: minimalConfig,
|
|
194
|
+
authMode: 'apiKey'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(client).toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('multiple instances', () => {
|
|
202
|
+
it('should allow creating multiple server clients', () => {
|
|
203
|
+
const client1 = generateServerClient({
|
|
204
|
+
config: mockConfig,
|
|
205
|
+
authMode: 'userPool'
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const client2 = generateServerClient({
|
|
209
|
+
config: mockConfig,
|
|
210
|
+
authMode: 'apiKey'
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(client1).toBeDefined();
|
|
214
|
+
expect(client2).toBeDefined();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('type exports', () => {
|
|
220
|
+
it('should export AstroAmplifyConfig type', () => {
|
|
221
|
+
// This is a compile-time check, but we can verify the import works
|
|
222
|
+
const config: any = {
|
|
223
|
+
auth: {
|
|
224
|
+
user_pool_id: 'test',
|
|
225
|
+
aws_region: 'us-east-1',
|
|
226
|
+
user_pool_client_id: 'test'
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
expect(config).toBeDefined();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL Client for @htlkg/data
|
|
3
|
+
*
|
|
4
|
+
* Provides both client-side and server-side GraphQL capabilities using AWS Amplify Data.
|
|
5
|
+
* The server-side functions use the Amplify Astro adapter for proper SSR support.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { generateClient as generateDataClient } from "aws-amplify/data";
|
|
9
|
+
import type { ResourcesConfig } from "aws-amplify";
|
|
10
|
+
|
|
11
|
+
// Re-export server-side client generation
|
|
12
|
+
export { generateServerClientUsingCookies } from "./server";
|
|
13
|
+
|
|
14
|
+
// Singleton client instance for client-side fetching
|
|
15
|
+
let sharedClientInstance: any = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get or create the shared GraphQL client instance (singleton pattern)
|
|
19
|
+
* Use this for client-side fetching to avoid creating multiple client instances.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const client = getSharedClient<Schema>();
|
|
24
|
+
* const { data } = await client.models.Account.list();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function getSharedClient<
|
|
28
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
29
|
+
>(): ReturnType<typeof generateDataClient<TSchema>> {
|
|
30
|
+
if (!sharedClientInstance) {
|
|
31
|
+
sharedClientInstance = generateDataClient<TSchema>();
|
|
32
|
+
}
|
|
33
|
+
return sharedClientInstance;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Reset the shared client instance (useful for testing or auth state changes)
|
|
38
|
+
*/
|
|
39
|
+
export function resetSharedClient(): void {
|
|
40
|
+
sharedClientInstance = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generate a client-side GraphQL client for use in Vue components and browser contexts
|
|
45
|
+
* This is SSR-safe and should be called within a component's setup function or after hydration
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import type { Schema } from '@backend/data/resource';
|
|
50
|
+
* import { generateClient } from '@htlkg/data/client';
|
|
51
|
+
*
|
|
52
|
+
* const client = generateClient<Schema>();
|
|
53
|
+
* const { data: brands } = await client.models.Brand.list();
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function generateClient<
|
|
57
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
58
|
+
>(): ReturnType<typeof generateDataClient<TSchema>> {
|
|
59
|
+
return generateDataClient<TSchema>();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Configuration for Amplify (matches amplify_outputs.json format)
|
|
64
|
+
*/
|
|
65
|
+
export type AstroAmplifyConfig = ResourcesConfig;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Authentication mode for server-side GraphQL client
|
|
69
|
+
*/
|
|
70
|
+
export type ServerAuthMode = 'userPool' | 'apiKey';
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Options for generating a server-side GraphQL client
|
|
74
|
+
*/
|
|
75
|
+
export interface GenerateServerClientOptions {
|
|
76
|
+
/** Authentication mode - 'userPool' (default) uses JWT from cookies, 'apiKey' uses API key */
|
|
77
|
+
authMode?: ServerAuthMode;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generate a server-side GraphQL client for use within runWithAmplifyServerContext
|
|
82
|
+
*
|
|
83
|
+
* This function creates a GraphQL client that can be used for server-side data fetching in Astro.
|
|
84
|
+
* It MUST be called within runWithAmplifyServerContext to access JWT tokens from cookies.
|
|
85
|
+
*
|
|
86
|
+
* The client supports two authentication modes:
|
|
87
|
+
* - 'userPool' (default): Uses JWT tokens from cookies (requires runWithAmplifyServerContext)
|
|
88
|
+
* - 'apiKey': Uses API key for public/unauthenticated requests
|
|
89
|
+
*
|
|
90
|
+
* **Important**:
|
|
91
|
+
* - Amplify.configure() must be called once at app startup (e.g., in amplify-server.ts)
|
|
92
|
+
* - This function must be called INSIDE the operation function of runWithAmplifyServerContext
|
|
93
|
+
* - The context automatically provides the token provider that reads JWT tokens from cookies
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* // In your Astro page
|
|
98
|
+
* import type { Schema } from '../amplify/data/resource';
|
|
99
|
+
* import { generateServerClient } from '@htlkg/data/client';
|
|
100
|
+
* import { createRunWithAmplifyServerContext } from '@htlkg/core/amplify-astro-adapter';
|
|
101
|
+
* import outputs from '../amplify_outputs.json';
|
|
102
|
+
*
|
|
103
|
+
* const runWithAmplifyServerContext = createRunWithAmplifyServerContext({ config: outputs });
|
|
104
|
+
*
|
|
105
|
+
* // Fetch data with authentication
|
|
106
|
+
* const result = await runWithAmplifyServerContext({
|
|
107
|
+
* astroServerContext: {
|
|
108
|
+
* cookies: Astro.cookies,
|
|
109
|
+
* request: Astro.request
|
|
110
|
+
* },
|
|
111
|
+
* operation: async (contextSpec) => {
|
|
112
|
+
* // Generate client INSIDE the operation
|
|
113
|
+
* const client = generateServerClient<Schema>({ authMode: 'userPool' });
|
|
114
|
+
* return await client.models.User.list();
|
|
115
|
+
* }
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* const users = result.data || [];
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @example Using API key for public data
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const result = await runWithAmplifyServerContext({
|
|
124
|
+
* astroServerContext: {
|
|
125
|
+
* cookies: Astro.cookies,
|
|
126
|
+
* request: Astro.request
|
|
127
|
+
* },
|
|
128
|
+
* operation: async (contextSpec) => {
|
|
129
|
+
* const client = generateServerClient<Schema>({ authMode: 'apiKey' });
|
|
130
|
+
* return await client.models.Brand.list();
|
|
131
|
+
* }
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function generateServerClient<
|
|
136
|
+
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
137
|
+
>(options?: GenerateServerClientOptions): ReturnType<typeof generateDataClient<TSchema>> {
|
|
138
|
+
// Generate the client without authMode parameter
|
|
139
|
+
// When called within runWithAmplifyServerContext, it will automatically use the token provider
|
|
140
|
+
// from the context (which reads JWT tokens from cookies)
|
|
141
|
+
// The authMode should be specified per-operation, not at client creation
|
|
142
|
+
const client = generateDataClient<TSchema>();
|
|
143
|
+
|
|
144
|
+
return client;
|
|
145
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side data client for Astro
|
|
3
|
+
*
|
|
4
|
+
* Provides a client generator similar to Next.js's generateServerClientUsingCookies
|
|
5
|
+
* using generateClientWithAmplifyInstance for proper server context integration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AstroGlobal } from "astro";
|
|
9
|
+
import type { ResourcesConfig } from "aws-amplify";
|
|
10
|
+
import {
|
|
11
|
+
CommonPublicClientOptions,
|
|
12
|
+
DefaultCommonClientOptions,
|
|
13
|
+
V6ClientSSRCookies,
|
|
14
|
+
generateClientWithAmplifyInstance,
|
|
15
|
+
} from "aws-amplify/api/internals";
|
|
16
|
+
import { getAmplifyServerContext } from "aws-amplify/adapter-core/internals";
|
|
17
|
+
import { createRunWithAmplifyServerContext, createLogger } from "@htlkg/core/amplify-astro-adapter";
|
|
18
|
+
|
|
19
|
+
const log = createLogger('server-client');
|
|
20
|
+
|
|
21
|
+
interface AstroCookiesClientParams {
|
|
22
|
+
cookies: AstroGlobal["cookies"];
|
|
23
|
+
request: AstroGlobal["request"];
|
|
24
|
+
config: ResourcesConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates a server-side data client for Astro (matches Next.js implementation)
|
|
29
|
+
*
|
|
30
|
+
* This function creates a client that automatically wraps all operations in the Amplify server context,
|
|
31
|
+
* ensuring that authentication tokens from cookies are properly used.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* import type { Schema } from '../amplify/data/resource';
|
|
36
|
+
* import { generateServerClientUsingCookies } from '@htlkg/data/client';
|
|
37
|
+
* import { parseAmplifyConfig } from 'aws-amplify/utils';
|
|
38
|
+
* import outputs from '../amplify_outputs.json';
|
|
39
|
+
*
|
|
40
|
+
* const amplifyConfig = parseAmplifyConfig(outputs);
|
|
41
|
+
*
|
|
42
|
+
* const client = generateServerClientUsingCookies<Schema>({
|
|
43
|
+
* config: amplifyConfig,
|
|
44
|
+
* cookies: Astro.cookies,
|
|
45
|
+
* request: Astro.request,
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // Use the client directly - operations are automatically wrapped
|
|
49
|
+
* const result = await client.models.User.list({
|
|
50
|
+
* selectionSet: ['id', 'email'],
|
|
51
|
+
* limit: 100,
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function generateServerClientUsingCookies<
|
|
56
|
+
T extends Record<any, any> = never,
|
|
57
|
+
Options extends CommonPublicClientOptions &
|
|
58
|
+
AstroCookiesClientParams = DefaultCommonClientOptions &
|
|
59
|
+
AstroCookiesClientParams,
|
|
60
|
+
>(options: Options): V6ClientSSRCookies<T, Options> {
|
|
61
|
+
const runWithAmplifyServerContext = createRunWithAmplifyServerContext({
|
|
62
|
+
config: options.config,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const resourcesConfig = options.config;
|
|
66
|
+
|
|
67
|
+
// This function reference gets passed down to InternalGraphQLAPI.ts.graphql
|
|
68
|
+
// where this._graphql is passed in as the `fn` argument
|
|
69
|
+
// causing it to always get invoked inside `runWithAmplifyServerContext`
|
|
70
|
+
const getAmplify = (fn: (amplify: any) => Promise<any>) => {
|
|
71
|
+
return runWithAmplifyServerContext({
|
|
72
|
+
astroServerContext: {
|
|
73
|
+
cookies: options.cookies,
|
|
74
|
+
request: options.request,
|
|
75
|
+
},
|
|
76
|
+
operation: async (contextSpec: any) => {
|
|
77
|
+
const amplifyInstance = getAmplifyServerContext(contextSpec).amplify;
|
|
78
|
+
|
|
79
|
+
// Debug logging (only when DEBUG=true)
|
|
80
|
+
try {
|
|
81
|
+
const config = amplifyInstance.getConfig();
|
|
82
|
+
log.debug('Amplify config from instance:', {
|
|
83
|
+
hasAPI: !!config.API,
|
|
84
|
+
hasGraphQL: !!config.API?.GraphQL,
|
|
85
|
+
endpoint: config.API?.GraphQL?.endpoint,
|
|
86
|
+
defaultAuthMode: config.API?.GraphQL?.defaultAuthMode,
|
|
87
|
+
region: config.API?.GraphQL?.region,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const session = await amplifyInstance.Auth.fetchAuthSession();
|
|
91
|
+
log.debug('Auth session:', {
|
|
92
|
+
hasTokens: !!session.tokens,
|
|
93
|
+
hasAccessToken: !!session.tokens?.accessToken,
|
|
94
|
+
hasIdToken: !!session.tokens?.idToken,
|
|
95
|
+
hasCredentials: !!session.credentials,
|
|
96
|
+
});
|
|
97
|
+
} catch (e: any) {
|
|
98
|
+
log.debug('Error fetching session:', e.message);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return fn(amplifyInstance);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const {
|
|
107
|
+
cookies: _cookies,
|
|
108
|
+
request: _request,
|
|
109
|
+
config: _config,
|
|
110
|
+
...params
|
|
111
|
+
} = options;
|
|
112
|
+
|
|
113
|
+
return generateClientWithAmplifyInstance<T, V6ClientSSRCookies<T, Options>>({
|
|
114
|
+
amplify: getAmplify,
|
|
115
|
+
config: resourcesConfig,
|
|
116
|
+
...params,
|
|
117
|
+
} as any);
|
|
118
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Content Collections Module
|
|
2
|
+
|
|
3
|
+
Astro content collection generators and schema definitions for static content.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
generateCollections,
|
|
10
|
+
syncCollections,
|
|
11
|
+
contentSchemas,
|
|
12
|
+
} from '@htlkg/data/content-collections';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
This module provides utilities for generating Astro content collections from external data sources, enabling static site generation with dynamic content.
|
|
18
|
+
|
|
19
|
+
## Schemas
|
|
20
|
+
|
|
21
|
+
Pre-defined Zod schemas for content types:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { contentSchemas } from '@htlkg/data/content-collections';
|
|
25
|
+
|
|
26
|
+
// Brand content schema
|
|
27
|
+
const brandSchema = contentSchemas.brand;
|
|
28
|
+
|
|
29
|
+
// Product content schema
|
|
30
|
+
const productSchema = contentSchemas.product;
|
|
31
|
+
|
|
32
|
+
// Documentation schema
|
|
33
|
+
const docSchema = contentSchemas.doc;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Generator
|
|
37
|
+
|
|
38
|
+
Generate content collection files from API data:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { generateCollections } from '@htlkg/data/content-collections';
|
|
42
|
+
|
|
43
|
+
await generateCollections({
|
|
44
|
+
outputDir: './src/content',
|
|
45
|
+
collections: ['brands', 'products'],
|
|
46
|
+
client: amplifyClient,
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Sync
|
|
51
|
+
|
|
52
|
+
Synchronize content collections with remote data:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { syncCollections } from '@htlkg/data/content-collections';
|
|
56
|
+
|
|
57
|
+
// In a build script or CI
|
|
58
|
+
await syncCollections({
|
|
59
|
+
collections: ['brands'],
|
|
60
|
+
force: true, // Overwrite existing
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Usage in Astro
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// src/content/config.ts
|
|
68
|
+
import { defineCollection } from 'astro:content';
|
|
69
|
+
import { contentSchemas } from '@htlkg/data/content-collections';
|
|
70
|
+
|
|
71
|
+
export const collections = {
|
|
72
|
+
brands: defineCollection({
|
|
73
|
+
type: 'data',
|
|
74
|
+
schema: contentSchemas.brand,
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```astro
|
|
80
|
+
---
|
|
81
|
+
// src/pages/brands/[id].astro
|
|
82
|
+
import { getCollection, getEntry } from 'astro:content';
|
|
83
|
+
|
|
84
|
+
const brands = await getCollection('brands');
|
|
85
|
+
const brand = await getEntry('brands', Astro.params.id);
|
|
86
|
+
---
|
|
87
|
+
```
|