@htlkg/core 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 +51 -0
- package/dist/amplify-astro-adapter/index.d.ts +109 -0
- package/dist/amplify-astro-adapter/index.js +295 -0
- package/dist/amplify-astro-adapter/index.js.map +1 -0
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +305 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/index.d.ts +220 -0
- package/dist/index.js +426 -1
- package/dist/index.js.map +1 -1
- package/dist/logger-BTW3fOeM.d.ts +45 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +55 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +56 -33
- package/src/amplify-astro-adapter/amplify-astro-adapter.md +167 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.test.ts +296 -0
- package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.ts +97 -0
- package/src/amplify-astro-adapter/createRunWithAmplifyServerContext.ts +84 -0
- package/src/amplify-astro-adapter/errors.test.ts +115 -0
- package/src/amplify-astro-adapter/errors.ts +105 -0
- package/src/amplify-astro-adapter/globalSettings.test.ts +78 -0
- package/src/amplify-astro-adapter/globalSettings.ts +16 -0
- package/src/amplify-astro-adapter/index.ts +14 -0
- package/src/amplify-astro-adapter/types.ts +55 -0
- package/src/auth/auth.md +178 -0
- package/src/auth/index.test.ts +180 -0
- package/src/auth/index.ts +294 -0
- package/src/constants/constants.md +132 -0
- package/src/constants/index.test.ts +116 -0
- package/src/constants/index.ts +98 -0
- package/src/core-exports.property.test.ts +186 -0
- package/src/errors/errors.md +177 -0
- package/src/errors/index.test.ts +153 -0
- package/src/errors/index.ts +134 -0
- package/src/index.ts +65 -0
- package/src/routes/index.ts +225 -0
- package/src/routes/routes.md +189 -0
- package/src/types/index.ts +94 -0
- package/src/types/types.md +144 -0
- package/src/utils/index.test.ts +257 -0
- package/src/utils/index.ts +112 -0
- package/src/utils/logger.ts +88 -0
- package/src/utils/utils.md +199 -0
- package/src/workspace.property.test.ts +235 -0
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
+
// src/utils/logger.ts
|
|
2
|
+
var isDebugEnabled = () => {
|
|
3
|
+
if (typeof process !== "undefined" && process.env) {
|
|
4
|
+
return process.env.DEBUG === "true" || process.env.HTLKG_DEBUG === "true" || process.env.DEBUG === "*";
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
8
|
+
return import.meta.env.DEBUG === "true" || // @ts-ignore
|
|
9
|
+
import.meta.env.HTLKG_DEBUG === "true" || // @ts-ignore
|
|
10
|
+
import.meta.env.DEV === true;
|
|
11
|
+
}
|
|
12
|
+
} catch {
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
};
|
|
16
|
+
var formatMessage = (namespace, message) => {
|
|
17
|
+
return `[${namespace}] ${message}`;
|
|
18
|
+
};
|
|
19
|
+
var logger = {
|
|
20
|
+
/**
|
|
21
|
+
* Debug log - only shown when DEBUG=true
|
|
22
|
+
*/
|
|
23
|
+
debug(namespace, message, ...args) {
|
|
24
|
+
if (isDebugEnabled()) {
|
|
25
|
+
console.log(formatMessage(namespace, message), ...args);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
/**
|
|
29
|
+
* Info log - always shown
|
|
30
|
+
*/
|
|
31
|
+
info(namespace, message, ...args) {
|
|
32
|
+
console.info(formatMessage(namespace, message), ...args);
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Warning log - always shown
|
|
36
|
+
*/
|
|
37
|
+
warn(namespace, message, ...args) {
|
|
38
|
+
console.warn(formatMessage(namespace, message), ...args);
|
|
39
|
+
},
|
|
40
|
+
/**
|
|
41
|
+
* Error log - always shown
|
|
42
|
+
*/
|
|
43
|
+
error(namespace, message, ...args) {
|
|
44
|
+
console.error(formatMessage(namespace, message), ...args);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var createLogger = (namespace) => ({
|
|
48
|
+
debug: (message, ...args) => logger.debug(namespace, message, ...args),
|
|
49
|
+
info: (message, ...args) => logger.info(namespace, message, ...args),
|
|
50
|
+
warn: (message, ...args) => logger.warn(namespace, message, ...args),
|
|
51
|
+
error: (message, ...args) => logger.error(namespace, message, ...args)
|
|
52
|
+
});
|
|
53
|
+
|
|
1
54
|
// src/utils/index.ts
|
|
2
55
|
function formatDate(date, locale = "en-US", options = {
|
|
3
56
|
year: "numeric",
|
|
@@ -49,9 +102,11 @@ function throttle(fn, limit) {
|
|
|
49
102
|
};
|
|
50
103
|
}
|
|
51
104
|
export {
|
|
105
|
+
createLogger,
|
|
52
106
|
debounce,
|
|
53
107
|
formatDate,
|
|
54
108
|
groupBy,
|
|
109
|
+
logger,
|
|
55
110
|
throttle,
|
|
56
111
|
truncateText
|
|
57
112
|
};
|
package/dist/utils/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/index.ts"],"sourcesContent":["/**\n * @htlkg/core/utils\n * Core utility functions for Hotelinking applications\n */\n\n/**\n * Format a date to a localized string\n * @param date - Date to format\n * @param locale - Locale string (default: 'en-US')\n * @param options - Intl.DateTimeFormatOptions\n * @returns Formatted date string\n */\nexport function formatDate(\n\tdate: Date | string | number,\n\tlocale = \"en-US\",\n\toptions: Intl.DateTimeFormatOptions = {\n\t\tyear: \"numeric\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t},\n): string {\n\tconst dateObj = typeof date === \"string\" || typeof date === \"number\" ? new Date(date) : date;\n\treturn new Intl.DateTimeFormat(locale, options).format(dateObj);\n}\n\n/**\n * Truncate text to a maximum length with ellipsis\n * @param text - Text to truncate\n * @param maxLength - Maximum length before truncation\n * @param suffix - Suffix to append (default: '...')\n * @returns Truncated text\n */\nexport function truncateText(\n\ttext: string,\n\tmaxLength: number,\n\tsuffix = \"...\",\n): string {\n\tif (text.length <= maxLength) return text;\n\treturn text.slice(0, maxLength - suffix.length) + suffix;\n}\n\n/**\n * Group an array of items by a key\n * @param items - Array of items to group\n * @param keyFn - Function to extract the grouping key\n * @returns Object with keys as group names and values as arrays of items\n */\nexport function groupBy<T>(\n\titems: T[],\n\tkeyFn: (item: T) => string | number,\n): Record<string | number, T[]> {\n\treturn items.reduce(\n\t\t(groups, item) => {\n\t\t\tconst key = keyFn(item);\n\t\t\tif (!groups[key]) {\n\t\t\t\tgroups[key] = [];\n\t\t\t}\n\t\t\tgroups[key].push(item);\n\t\t\treturn groups;\n\t\t},\n\t\t{} as Record<string | number, T[]>,\n\t);\n}\n\n/**\n * Debounce a function call\n * @param fn - Function to debounce\n * @param delay - Delay in milliseconds\n * @returns Debounced function\n */\nexport function debounce<T extends (...args: any[]) => any>(\n\tfn: T,\n\tdelay: number,\n): (...args: Parameters<T>) => void {\n\tlet timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n\treturn function debounced(...args: Parameters<T>) {\n\t\tif (timeoutId !== null) {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tfn(...args);\n\t\t\ttimeoutId = null;\n\t\t}, delay);\n\t};\n}\n\n/**\n * Throttle a function call\n * @param fn - Function to throttle\n * @param limit - Minimum time between calls in milliseconds\n * @returns Throttled function\n */\nexport function throttle<T extends (...args: any[]) => any>(\n\tfn: T,\n\tlimit: number,\n): (...args: Parameters<T>) => void {\n\tlet inThrottle = false;\n\n\treturn function throttled(...args: Parameters<T>) {\n\t\tif (!inThrottle) {\n\t\t\tfn(...args);\n\t\t\tinThrottle = true;\n\t\t\tsetTimeout(() => {\n\t\t\t\tinThrottle = false;\n\t\t\t}, limit);\n\t\t}\n\t};\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/utils/logger.ts","../../src/utils/index.ts"],"sourcesContent":["/**\n * Simple debug logger utility\n * \n * Debug logs are only shown when DEBUG=true or HTLKG_DEBUG=true is set in environment.\n * Info logs are always shown.\n * \n * @example\n * ```typescript\n * import { logger } from '@htlkg/core/utils/logger';\n * \n * logger.debug('server-client', 'Detailed debug info', { data });\n * logger.info('server-client', 'Important info message');\n * logger.warn('server-client', 'Warning message');\n * logger.error('server-client', 'Error occurred', error);\n * ```\n */\n\ntype LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst isDebugEnabled = (): boolean => {\n // Check Node.js environment variables\n if (typeof process !== 'undefined' && process.env) {\n return process.env.DEBUG === 'true' || \n process.env.HTLKG_DEBUG === 'true' ||\n process.env.DEBUG === '*';\n }\n // Browser/Vite environment - check for DEV mode\n try {\n // @ts-ignore - import.meta.env is Vite-specific\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n // @ts-ignore\n return import.meta.env.DEBUG === 'true' || \n // @ts-ignore\n import.meta.env.HTLKG_DEBUG === 'true' ||\n // @ts-ignore\n import.meta.env.DEV === true;\n }\n } catch {\n // Ignore errors accessing import.meta\n }\n return false;\n};\n\nconst formatMessage = (namespace: string, message: string): string => {\n return `[${namespace}] ${message}`;\n};\n\nexport const logger = {\n /**\n * Debug log - only shown when DEBUG=true\n */\n debug(namespace: string, message: string, ...args: unknown[]): void {\n if (isDebugEnabled()) {\n console.log(formatMessage(namespace, message), ...args);\n }\n },\n\n /**\n * Info log - always shown\n */\n info(namespace: string, message: string, ...args: unknown[]): void {\n console.info(formatMessage(namespace, message), ...args);\n },\n\n /**\n * Warning log - always shown\n */\n warn(namespace: string, message: string, ...args: unknown[]): void {\n console.warn(formatMessage(namespace, message), ...args);\n },\n\n /**\n * Error log - always shown\n */\n error(namespace: string, message: string, ...args: unknown[]): void {\n console.error(formatMessage(namespace, message), ...args);\n },\n};\n\n/**\n * Create a namespaced logger for a specific module\n */\nexport const createLogger = (namespace: string) => ({\n debug: (message: string, ...args: unknown[]) => logger.debug(namespace, message, ...args),\n info: (message: string, ...args: unknown[]) => logger.info(namespace, message, ...args),\n warn: (message: string, ...args: unknown[]) => logger.warn(namespace, message, ...args),\n error: (message: string, ...args: unknown[]) => logger.error(namespace, message, ...args),\n});\n","/**\n * @htlkg/core/utils\n * Core utility functions for Hotelinking applications\n */\n\n// Logger utility\nexport { logger, createLogger } from './logger';\n\n/**\n * Format a date to a localized string\n * @param date - Date to format\n * @param locale - Locale string (default: 'en-US')\n * @param options - Intl.DateTimeFormatOptions\n * @returns Formatted date string\n */\nexport function formatDate(\n\tdate: Date | string | number,\n\tlocale = \"en-US\",\n\toptions: Intl.DateTimeFormatOptions = {\n\t\tyear: \"numeric\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t},\n): string {\n\tconst dateObj = typeof date === \"string\" || typeof date === \"number\" ? new Date(date) : date;\n\treturn new Intl.DateTimeFormat(locale, options).format(dateObj);\n}\n\n/**\n * Truncate text to a maximum length with ellipsis\n * @param text - Text to truncate\n * @param maxLength - Maximum length before truncation\n * @param suffix - Suffix to append (default: '...')\n * @returns Truncated text\n */\nexport function truncateText(\n\ttext: string,\n\tmaxLength: number,\n\tsuffix = \"...\",\n): string {\n\tif (text.length <= maxLength) return text;\n\treturn text.slice(0, maxLength - suffix.length) + suffix;\n}\n\n/**\n * Group an array of items by a key\n * @param items - Array of items to group\n * @param keyFn - Function to extract the grouping key\n * @returns Object with keys as group names and values as arrays of items\n */\nexport function groupBy<T>(\n\titems: T[],\n\tkeyFn: (item: T) => string | number,\n): Record<string | number, T[]> {\n\treturn items.reduce(\n\t\t(groups, item) => {\n\t\t\tconst key = keyFn(item);\n\t\t\tif (!groups[key]) {\n\t\t\t\tgroups[key] = [];\n\t\t\t}\n\t\t\tgroups[key].push(item);\n\t\t\treturn groups;\n\t\t},\n\t\t{} as Record<string | number, T[]>,\n\t);\n}\n\n/**\n * Debounce a function call\n * @param fn - Function to debounce\n * @param delay - Delay in milliseconds\n * @returns Debounced function\n */\nexport function debounce<T extends (...args: any[]) => any>(\n\tfn: T,\n\tdelay: number,\n): (...args: Parameters<T>) => void {\n\tlet timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n\treturn function debounced(...args: Parameters<T>) {\n\t\tif (timeoutId !== null) {\n\t\t\tclearTimeout(timeoutId);\n\t\t}\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tfn(...args);\n\t\t\ttimeoutId = null;\n\t\t}, delay);\n\t};\n}\n\n/**\n * Throttle a function call\n * @param fn - Function to throttle\n * @param limit - Minimum time between calls in milliseconds\n * @returns Throttled function\n */\nexport function throttle<T extends (...args: any[]) => any>(\n\tfn: T,\n\tlimit: number,\n): (...args: Parameters<T>) => void {\n\tlet inThrottle = false;\n\n\treturn function throttled(...args: Parameters<T>) {\n\t\tif (!inThrottle) {\n\t\t\tfn(...args);\n\t\t\tinThrottle = true;\n\t\t\tsetTimeout(() => {\n\t\t\t\tinThrottle = false;\n\t\t\t}, limit);\n\t\t}\n\t};\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,34 +1,57 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
2
|
+
"name": "@htlkg/core",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts"
|
|
9
|
+
},
|
|
10
|
+
"./auth": {
|
|
11
|
+
"import": "./dist/auth/index.js",
|
|
12
|
+
"types": "./dist/auth/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"import": "./dist/types/index.js",
|
|
16
|
+
"types": "./dist/types/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./utils": {
|
|
19
|
+
"import": "./dist/utils/index.js",
|
|
20
|
+
"types": "./dist/utils/index.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./constants": {
|
|
23
|
+
"import": "./dist/constants/index.js",
|
|
24
|
+
"types": "./dist/constants/index.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./errors": {
|
|
27
|
+
"import": "./dist/errors/index.js",
|
|
28
|
+
"types": "./dist/errors/index.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"./amplify-astro-adapter": {
|
|
31
|
+
"import": "./dist/amplify-astro-adapter/index.js",
|
|
32
|
+
"types": "./dist/amplify-astro-adapter/index.d.ts"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"src",
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"aws-amplify": "^6.15.7"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"astro": "^5.14.7",
|
|
44
|
+
"tsup": "^8.0.0",
|
|
45
|
+
"typescript": "^5.9.2",
|
|
46
|
+
"vitest": "^3.2.4"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "restricted"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Amplify Astro Adapter
|
|
2
|
+
|
|
3
|
+
Server-side authentication support for Astro applications using AWS Amplify. Handles cookie-based authentication context for SSR.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
createRunWithAmplifyServerContext,
|
|
10
|
+
createCookieStorageAdapterFromAstroContext,
|
|
11
|
+
globalSettings
|
|
12
|
+
} from '@htlkg/core/amplify-astro-adapter';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Core Functions
|
|
16
|
+
|
|
17
|
+
### createRunWithAmplifyServerContext
|
|
18
|
+
|
|
19
|
+
Creates a function that runs operations within an authenticated Amplify server context.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Amplify } from 'aws-amplify';
|
|
23
|
+
import { createRunWithAmplifyServerContext, globalSettings } from '@htlkg/core/amplify-astro-adapter';
|
|
24
|
+
|
|
25
|
+
const amplifyConfig = Amplify.getConfig();
|
|
26
|
+
|
|
27
|
+
const runWithAmplifyServerContext = createRunWithAmplifyServerContext({
|
|
28
|
+
config: amplifyConfig,
|
|
29
|
+
globalSettings, // Pass explicitly to avoid module singleton issues
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Use in Astro page or API route
|
|
33
|
+
const result = await runWithAmplifyServerContext({
|
|
34
|
+
astroServerContext: {
|
|
35
|
+
cookies: Astro.cookies,
|
|
36
|
+
request: Astro.request,
|
|
37
|
+
},
|
|
38
|
+
operation: async (contextSpec) => {
|
|
39
|
+
// Perform authenticated operations
|
|
40
|
+
const user = await getCurrentUser(contextSpec);
|
|
41
|
+
return user;
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### createCookieStorageAdapterFromAstroContext
|
|
47
|
+
|
|
48
|
+
Creates a cookie storage adapter compatible with Amplify's auth system.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { createCookieStorageAdapterFromAstroContext } from '@htlkg/core/amplify-astro-adapter';
|
|
52
|
+
|
|
53
|
+
const cookieAdapter = await createCookieStorageAdapterFromAstroContext({
|
|
54
|
+
cookies: Astro.cookies,
|
|
55
|
+
request: Astro.request,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Adapter methods
|
|
59
|
+
cookieAdapter.get('cookie-name');
|
|
60
|
+
cookieAdapter.getAll();
|
|
61
|
+
cookieAdapter.set('name', 'value', options);
|
|
62
|
+
cookieAdapter.delete('name');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### globalSettings
|
|
66
|
+
|
|
67
|
+
Runtime configuration for the adapter.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { globalSettings } from '@htlkg/core/amplify-astro-adapter';
|
|
71
|
+
|
|
72
|
+
// Enable server-side auth (enabled by default)
|
|
73
|
+
globalSettings.enableServerSideAuth();
|
|
74
|
+
|
|
75
|
+
// Check if SSL origin (affects secure cookie flag)
|
|
76
|
+
globalSettings.setIsSSLOrigin(true);
|
|
77
|
+
|
|
78
|
+
// Set custom cookie options
|
|
79
|
+
globalSettings.setRuntimeOptions({
|
|
80
|
+
cookies: {
|
|
81
|
+
domain: '.example.com',
|
|
82
|
+
sameSite: 'lax',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Error Handling
|
|
88
|
+
|
|
89
|
+
The adapter provides specific error classes for different failure scenarios:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import {
|
|
93
|
+
AmplifyAstroAdapterError,
|
|
94
|
+
ErrorCodes,
|
|
95
|
+
createAuthFailedError,
|
|
96
|
+
createCookieError,
|
|
97
|
+
createConfigMissingError,
|
|
98
|
+
} from '@htlkg/core/amplify-astro-adapter';
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// ... adapter operations
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof AmplifyAstroAdapterError) {
|
|
104
|
+
console.error(`[${error.code}] ${error.message}`);
|
|
105
|
+
if (error.recoverySuggestion) {
|
|
106
|
+
console.log('Suggestion:', error.recoverySuggestion);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Error Codes
|
|
113
|
+
|
|
114
|
+
| Code | Description |
|
|
115
|
+
|------|-------------|
|
|
116
|
+
| `CONFIG_MISSING` | Amplify configuration not found |
|
|
117
|
+
| `AUTH_FAILED` | Authentication failed |
|
|
118
|
+
| `COOKIE_ERROR` | Cookie read/write error |
|
|
119
|
+
| `GRAPHQL_ERROR` | GraphQL operation failed |
|
|
120
|
+
| `CONTEXT_CREATION_FAILED` | Server context creation failed |
|
|
121
|
+
| `TOKEN_REFRESH_FAILED` | Token refresh failed |
|
|
122
|
+
|
|
123
|
+
## Types
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import type { AstroServer, AstroAmplifyConfig } from '@htlkg/core/amplify-astro-adapter';
|
|
127
|
+
|
|
128
|
+
// Server context types
|
|
129
|
+
type ServerContext = AstroServer.AstroComponentContext | AstroServer.APIEndpointContext | null;
|
|
130
|
+
|
|
131
|
+
// Runtime options
|
|
132
|
+
interface RuntimeOptions {
|
|
133
|
+
cookies?: RuntimeCookieOptions;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Run operation signature
|
|
137
|
+
type RunOperationWithContext = <T>(input: {
|
|
138
|
+
astroServerContext: ServerContext;
|
|
139
|
+
operation: (contextSpec: unknown) => Promise<T>;
|
|
140
|
+
}) => Promise<T>;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Cookie Handling
|
|
144
|
+
|
|
145
|
+
The adapter handles Cognito authentication cookies with:
|
|
146
|
+
|
|
147
|
+
- Automatic encoding compatible with js-cookie
|
|
148
|
+
- Secure defaults (`httpOnly`, `sameSite: 'lax'`, `secure` based on SSL)
|
|
149
|
+
- Support for both Astro component and API endpoint contexts
|
|
150
|
+
|
|
151
|
+
## Debug Logging
|
|
152
|
+
|
|
153
|
+
Enable debug logs by setting environment variables:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
DEBUG=true
|
|
157
|
+
# or
|
|
158
|
+
HTLKG_DEBUG=true
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { createLogger } from '@htlkg/core/amplify-astro-adapter';
|
|
163
|
+
|
|
164
|
+
const log = createLogger('my-module');
|
|
165
|
+
log.debug('Debug message'); // Only shown when DEBUG=true
|
|
166
|
+
log.info('Info message'); // Always shown
|
|
167
|
+
```
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { createCookieStorageAdapterFromAstroContext } from "./createCookieStorageAdapterFromAstroContext";
|
|
3
|
+
import type { AstroServer } from "./types";
|
|
4
|
+
|
|
5
|
+
describe("createCookieStorageAdapterFromAstroContext", () => {
|
|
6
|
+
describe("with null context", () => {
|
|
7
|
+
it("should return a no-op adapter", async () => {
|
|
8
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(null as any);
|
|
9
|
+
|
|
10
|
+
expect(adapter.get("test")).toBeUndefined();
|
|
11
|
+
expect(adapter.getAll()).toEqual([]);
|
|
12
|
+
|
|
13
|
+
// These should not throw
|
|
14
|
+
adapter.set("test", "value");
|
|
15
|
+
adapter.delete("test");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("with valid context", () => {
|
|
20
|
+
it("should get cookie from Astro cookies", async () => {
|
|
21
|
+
const mockCookies = {
|
|
22
|
+
get: vi.fn((name: string) => ({ name, value: "cookie-value" })),
|
|
23
|
+
set: vi.fn(),
|
|
24
|
+
delete: vi.fn(),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const mockRequest = new Request("https://example.com", {
|
|
28
|
+
headers: { cookie: "" },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const context: AstroServer.ServerContext = {
|
|
32
|
+
cookies: mockCookies as any,
|
|
33
|
+
request: mockRequest,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
37
|
+
const result = adapter.get("test");
|
|
38
|
+
|
|
39
|
+
expect(result).toEqual({ name: "test", value: "cookie-value" });
|
|
40
|
+
expect(mockCookies.get).toHaveBeenCalledWith("test");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should get cookie from request headers if not in Astro cookies", async () => {
|
|
44
|
+
// When cookies.get returns an object without value property, it falls back to headers
|
|
45
|
+
const mockCookies = {
|
|
46
|
+
get: vi.fn(() => ({ value: undefined })),
|
|
47
|
+
set: vi.fn(),
|
|
48
|
+
delete: vi.fn(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const mockRequest = new Request("https://example.com", {
|
|
52
|
+
headers: { cookie: "test=header-value; other=value2" },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const context: AstroServer.ServerContext = {
|
|
56
|
+
cookies: mockCookies as any,
|
|
57
|
+
request: mockRequest,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
61
|
+
const result = adapter.get("test");
|
|
62
|
+
|
|
63
|
+
expect(result).toEqual({ name: "test", value: "header-value" });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should handle URL-encoded cookie values", async () => {
|
|
67
|
+
const mockCookies = {
|
|
68
|
+
get: vi.fn(() => ({ value: undefined })),
|
|
69
|
+
set: vi.fn(),
|
|
70
|
+
delete: vi.fn(),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const mockRequest = new Request("https://example.com", {
|
|
74
|
+
headers: { cookie: "test=hello%20world" },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const context: AstroServer.ServerContext = {
|
|
78
|
+
cookies: mockCookies as any,
|
|
79
|
+
request: mockRequest,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
83
|
+
const result = adapter.get("test");
|
|
84
|
+
|
|
85
|
+
expect(result).toEqual({ name: "test", value: "hello world" });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should get all cookies from getAll method", async () => {
|
|
89
|
+
const mockCookies = {
|
|
90
|
+
get: vi.fn(),
|
|
91
|
+
getAll: vi.fn(() => [
|
|
92
|
+
{ name: "cookie1", value: "value1" },
|
|
93
|
+
{ name: "cookie2", value: "value2" },
|
|
94
|
+
]),
|
|
95
|
+
set: vi.fn(),
|
|
96
|
+
delete: vi.fn(),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const mockRequest = new Request("https://example.com", {
|
|
100
|
+
headers: { cookie: "" },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const context: AstroServer.ServerContext = {
|
|
104
|
+
cookies: mockCookies as any,
|
|
105
|
+
request: mockRequest,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
109
|
+
const result = adapter.getAll();
|
|
110
|
+
|
|
111
|
+
expect(result).toEqual([
|
|
112
|
+
{ name: "cookie1", value: "value1" },
|
|
113
|
+
{ name: "cookie2", value: "value2" },
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should get all cookies from headers when getAll is not available", async () => {
|
|
118
|
+
const mockCookies = {
|
|
119
|
+
get: vi.fn(),
|
|
120
|
+
set: vi.fn(),
|
|
121
|
+
delete: vi.fn(),
|
|
122
|
+
// No getAll method
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const mockRequest = new Request("https://example.com", {
|
|
126
|
+
headers: { cookie: "cookie1=value1; cookie2=value2" },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const context: AstroServer.ServerContext = {
|
|
130
|
+
cookies: mockCookies as any,
|
|
131
|
+
request: mockRequest,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
135
|
+
const result = adapter.getAll();
|
|
136
|
+
|
|
137
|
+
expect(result).toEqual([
|
|
138
|
+
{ name: "cookie1", value: "value1" },
|
|
139
|
+
{ name: "cookie2", value: "value2" },
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should set cookie with options", async () => {
|
|
144
|
+
const mockCookies = {
|
|
145
|
+
get: vi.fn(),
|
|
146
|
+
set: vi.fn(),
|
|
147
|
+
delete: vi.fn(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const mockRequest = new Request("https://example.com", {
|
|
151
|
+
headers: { cookie: "" },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const context: AstroServer.ServerContext = {
|
|
155
|
+
cookies: mockCookies as any,
|
|
156
|
+
request: mockRequest,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
160
|
+
adapter.set("test", "value", {
|
|
161
|
+
httpOnly: true,
|
|
162
|
+
secure: true,
|
|
163
|
+
sameSite: "lax",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(mockCookies.set).toHaveBeenCalledWith("test", "value", {
|
|
167
|
+
httpOnly: true,
|
|
168
|
+
secure: true,
|
|
169
|
+
sameSite: "lax",
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should map sameSite boolean values", async () => {
|
|
174
|
+
const mockCookies = {
|
|
175
|
+
get: vi.fn(),
|
|
176
|
+
set: vi.fn(),
|
|
177
|
+
delete: vi.fn(),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const mockRequest = new Request("https://example.com", {
|
|
181
|
+
headers: { cookie: "" },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const context: AstroServer.ServerContext = {
|
|
185
|
+
cookies: mockCookies as any,
|
|
186
|
+
request: mockRequest,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
190
|
+
|
|
191
|
+
// sameSite: true -> 'strict'
|
|
192
|
+
adapter.set("test1", "value1", { sameSite: true as any });
|
|
193
|
+
expect(mockCookies.set).toHaveBeenCalledWith("test1", "value1", {
|
|
194
|
+
sameSite: "strict",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// sameSite: false -> 'lax'
|
|
198
|
+
adapter.set("test2", "value2", { sameSite: false as any });
|
|
199
|
+
expect(mockCookies.set).toHaveBeenCalledWith("test2", "value2", {
|
|
200
|
+
sameSite: "lax",
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should delete cookie", async () => {
|
|
205
|
+
const mockCookies = {
|
|
206
|
+
get: vi.fn(),
|
|
207
|
+
set: vi.fn(),
|
|
208
|
+
delete: vi.fn(),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const mockRequest = new Request("https://example.com", {
|
|
212
|
+
headers: { cookie: "" },
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const context: AstroServer.ServerContext = {
|
|
216
|
+
cookies: mockCookies as any,
|
|
217
|
+
request: mockRequest,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
221
|
+
adapter.delete("test");
|
|
222
|
+
|
|
223
|
+
expect(mockCookies.delete).toHaveBeenCalledWith("test");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should handle errors gracefully when setting cookies", async () => {
|
|
227
|
+
const mockCookies = {
|
|
228
|
+
get: vi.fn(),
|
|
229
|
+
set: vi.fn(() => {
|
|
230
|
+
throw new Error("Cookie error");
|
|
231
|
+
}),
|
|
232
|
+
delete: vi.fn(),
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const mockRequest = new Request("https://example.com", {
|
|
236
|
+
headers: { cookie: "" },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const context: AstroServer.ServerContext = {
|
|
240
|
+
cookies: mockCookies as any,
|
|
241
|
+
request: mockRequest,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
245
|
+
|
|
246
|
+
// Should not throw
|
|
247
|
+
expect(() => adapter.set("test", "value")).not.toThrow();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should handle errors gracefully when deleting cookies", async () => {
|
|
251
|
+
const mockCookies = {
|
|
252
|
+
get: vi.fn(),
|
|
253
|
+
set: vi.fn(),
|
|
254
|
+
delete: vi.fn(() => {
|
|
255
|
+
throw new Error("Delete error");
|
|
256
|
+
}),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const mockRequest = new Request("https://example.com", {
|
|
260
|
+
headers: { cookie: "" },
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const context: AstroServer.ServerContext = {
|
|
264
|
+
cookies: mockCookies as any,
|
|
265
|
+
request: mockRequest,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
269
|
+
|
|
270
|
+
// Should not throw
|
|
271
|
+
expect(() => adapter.delete("test")).not.toThrow();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should return undefined when cookie not found anywhere", async () => {
|
|
275
|
+
const mockCookies = {
|
|
276
|
+
get: vi.fn(() => ({ value: undefined })),
|
|
277
|
+
set: vi.fn(),
|
|
278
|
+
delete: vi.fn(),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const mockRequest = new Request("https://example.com", {
|
|
282
|
+
headers: { cookie: "" },
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const context: AstroServer.ServerContext = {
|
|
286
|
+
cookies: mockCookies as any,
|
|
287
|
+
request: mockRequest,
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const adapter = await createCookieStorageAdapterFromAstroContext(context);
|
|
291
|
+
const result = adapter.get("nonexistent");
|
|
292
|
+
|
|
293
|
+
expect(result).toBeUndefined();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
});
|