@htlkg/core 0.0.2 → 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.
Files changed (35) hide show
  1. package/README.md +51 -0
  2. package/dist/index.d.ts +219 -0
  3. package/dist/index.js +121 -0
  4. package/dist/index.js.map +1 -1
  5. package/package.json +30 -8
  6. package/src/amplify-astro-adapter/amplify-astro-adapter.md +167 -0
  7. package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.test.ts +296 -0
  8. package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.ts +97 -0
  9. package/src/amplify-astro-adapter/createRunWithAmplifyServerContext.ts +84 -0
  10. package/src/amplify-astro-adapter/errors.test.ts +115 -0
  11. package/src/amplify-astro-adapter/errors.ts +105 -0
  12. package/src/amplify-astro-adapter/globalSettings.test.ts +78 -0
  13. package/src/amplify-astro-adapter/globalSettings.ts +16 -0
  14. package/src/amplify-astro-adapter/index.ts +14 -0
  15. package/src/amplify-astro-adapter/types.ts +55 -0
  16. package/src/auth/auth.md +178 -0
  17. package/src/auth/index.test.ts +180 -0
  18. package/src/auth/index.ts +294 -0
  19. package/src/constants/constants.md +132 -0
  20. package/src/constants/index.test.ts +116 -0
  21. package/src/constants/index.ts +98 -0
  22. package/src/core-exports.property.test.ts +186 -0
  23. package/src/errors/errors.md +177 -0
  24. package/src/errors/index.test.ts +153 -0
  25. package/src/errors/index.ts +134 -0
  26. package/src/index.ts +65 -0
  27. package/src/routes/index.ts +225 -0
  28. package/src/routes/routes.md +189 -0
  29. package/src/types/index.ts +94 -0
  30. package/src/types/types.md +144 -0
  31. package/src/utils/index.test.ts +257 -0
  32. package/src/utils/index.ts +112 -0
  33. package/src/utils/logger.ts +88 -0
  34. package/src/utils/utils.md +199 -0
  35. package/src/workspace.property.test.ts +235 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @htlkg/core/utils
3
+ * Core utility functions for Hotelinking applications
4
+ */
5
+
6
+ // Logger utility
7
+ export { logger, createLogger } from './logger';
8
+
9
+ /**
10
+ * Format a date to a localized string
11
+ * @param date - Date to format
12
+ * @param locale - Locale string (default: 'en-US')
13
+ * @param options - Intl.DateTimeFormatOptions
14
+ * @returns Formatted date string
15
+ */
16
+ export function formatDate(
17
+ date: Date | string | number,
18
+ locale = "en-US",
19
+ options: Intl.DateTimeFormatOptions = {
20
+ year: "numeric",
21
+ month: "short",
22
+ day: "numeric",
23
+ },
24
+ ): string {
25
+ const dateObj = typeof date === "string" || typeof date === "number" ? new Date(date) : date;
26
+ return new Intl.DateTimeFormat(locale, options).format(dateObj);
27
+ }
28
+
29
+ /**
30
+ * Truncate text to a maximum length with ellipsis
31
+ * @param text - Text to truncate
32
+ * @param maxLength - Maximum length before truncation
33
+ * @param suffix - Suffix to append (default: '...')
34
+ * @returns Truncated text
35
+ */
36
+ export function truncateText(
37
+ text: string,
38
+ maxLength: number,
39
+ suffix = "...",
40
+ ): string {
41
+ if (text.length <= maxLength) return text;
42
+ return text.slice(0, maxLength - suffix.length) + suffix;
43
+ }
44
+
45
+ /**
46
+ * Group an array of items by a key
47
+ * @param items - Array of items to group
48
+ * @param keyFn - Function to extract the grouping key
49
+ * @returns Object with keys as group names and values as arrays of items
50
+ */
51
+ export function groupBy<T>(
52
+ items: T[],
53
+ keyFn: (item: T) => string | number,
54
+ ): Record<string | number, T[]> {
55
+ return items.reduce(
56
+ (groups, item) => {
57
+ const key = keyFn(item);
58
+ if (!groups[key]) {
59
+ groups[key] = [];
60
+ }
61
+ groups[key].push(item);
62
+ return groups;
63
+ },
64
+ {} as Record<string | number, T[]>,
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Debounce a function call
70
+ * @param fn - Function to debounce
71
+ * @param delay - Delay in milliseconds
72
+ * @returns Debounced function
73
+ */
74
+ export function debounce<T extends (...args: any[]) => any>(
75
+ fn: T,
76
+ delay: number,
77
+ ): (...args: Parameters<T>) => void {
78
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
79
+
80
+ return function debounced(...args: Parameters<T>) {
81
+ if (timeoutId !== null) {
82
+ clearTimeout(timeoutId);
83
+ }
84
+ timeoutId = setTimeout(() => {
85
+ fn(...args);
86
+ timeoutId = null;
87
+ }, delay);
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Throttle a function call
93
+ * @param fn - Function to throttle
94
+ * @param limit - Minimum time between calls in milliseconds
95
+ * @returns Throttled function
96
+ */
97
+ export function throttle<T extends (...args: any[]) => any>(
98
+ fn: T,
99
+ limit: number,
100
+ ): (...args: Parameters<T>) => void {
101
+ let inThrottle = false;
102
+
103
+ return function throttled(...args: Parameters<T>) {
104
+ if (!inThrottle) {
105
+ fn(...args);
106
+ inThrottle = true;
107
+ setTimeout(() => {
108
+ inThrottle = false;
109
+ }, limit);
110
+ }
111
+ };
112
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Simple debug logger utility
3
+ *
4
+ * Debug logs are only shown when DEBUG=true or HTLKG_DEBUG=true is set in environment.
5
+ * Info logs are always shown.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { logger } from '@htlkg/core/utils/logger';
10
+ *
11
+ * logger.debug('server-client', 'Detailed debug info', { data });
12
+ * logger.info('server-client', 'Important info message');
13
+ * logger.warn('server-client', 'Warning message');
14
+ * logger.error('server-client', 'Error occurred', error);
15
+ * ```
16
+ */
17
+
18
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
19
+
20
+ const isDebugEnabled = (): boolean => {
21
+ // Check Node.js environment variables
22
+ if (typeof process !== 'undefined' && process.env) {
23
+ return process.env.DEBUG === 'true' ||
24
+ process.env.HTLKG_DEBUG === 'true' ||
25
+ process.env.DEBUG === '*';
26
+ }
27
+ // Browser/Vite environment - check for DEV mode
28
+ try {
29
+ // @ts-ignore - import.meta.env is Vite-specific
30
+ if (typeof import.meta !== 'undefined' && import.meta.env) {
31
+ // @ts-ignore
32
+ return import.meta.env.DEBUG === 'true' ||
33
+ // @ts-ignore
34
+ import.meta.env.HTLKG_DEBUG === 'true' ||
35
+ // @ts-ignore
36
+ import.meta.env.DEV === true;
37
+ }
38
+ } catch {
39
+ // Ignore errors accessing import.meta
40
+ }
41
+ return false;
42
+ };
43
+
44
+ const formatMessage = (namespace: string, message: string): string => {
45
+ return `[${namespace}] ${message}`;
46
+ };
47
+
48
+ export const logger = {
49
+ /**
50
+ * Debug log - only shown when DEBUG=true
51
+ */
52
+ debug(namespace: string, message: string, ...args: unknown[]): void {
53
+ if (isDebugEnabled()) {
54
+ console.log(formatMessage(namespace, message), ...args);
55
+ }
56
+ },
57
+
58
+ /**
59
+ * Info log - always shown
60
+ */
61
+ info(namespace: string, message: string, ...args: unknown[]): void {
62
+ console.info(formatMessage(namespace, message), ...args);
63
+ },
64
+
65
+ /**
66
+ * Warning log - always shown
67
+ */
68
+ warn(namespace: string, message: string, ...args: unknown[]): void {
69
+ console.warn(formatMessage(namespace, message), ...args);
70
+ },
71
+
72
+ /**
73
+ * Error log - always shown
74
+ */
75
+ error(namespace: string, message: string, ...args: unknown[]): void {
76
+ console.error(formatMessage(namespace, message), ...args);
77
+ },
78
+ };
79
+
80
+ /**
81
+ * Create a namespaced logger for a specific module
82
+ */
83
+ export const createLogger = (namespace: string) => ({
84
+ debug: (message: string, ...args: unknown[]) => logger.debug(namespace, message, ...args),
85
+ info: (message: string, ...args: unknown[]) => logger.info(namespace, message, ...args),
86
+ warn: (message: string, ...args: unknown[]) => logger.warn(namespace, message, ...args),
87
+ error: (message: string, ...args: unknown[]) => logger.error(namespace, message, ...args),
88
+ });
@@ -0,0 +1,199 @@
1
+ # Utils Module
2
+
3
+ Common utility functions for formatting, data manipulation, and function control.
4
+
5
+ ## Installation
6
+
7
+ ```typescript
8
+ import {
9
+ logger,
10
+ createLogger,
11
+ formatDate,
12
+ truncateText,
13
+ groupBy,
14
+ debounce,
15
+ throttle,
16
+ } from '@htlkg/core/utils';
17
+ ```
18
+
19
+ ## Logger
20
+
21
+ Debug-aware logging utility.
22
+
23
+ ### Basic Usage
24
+
25
+ ```typescript
26
+ import { logger } from '@htlkg/core/utils';
27
+
28
+ logger.debug('namespace', 'Debug message', { data }); // Only when DEBUG=true
29
+ logger.info('namespace', 'Info message'); // Always shown
30
+ logger.warn('namespace', 'Warning message'); // Always shown
31
+ logger.error('namespace', 'Error message', error); // Always shown
32
+ ```
33
+
34
+ ### Namespaced Logger
35
+
36
+ ```typescript
37
+ import { createLogger } from '@htlkg/core/utils';
38
+
39
+ const log = createLogger('my-module');
40
+
41
+ log.debug('Processing started');
42
+ log.info('Operation complete');
43
+ log.warn('Deprecated feature used');
44
+ log.error('Operation failed', error);
45
+ ```
46
+
47
+ ### Enabling Debug Logs
48
+
49
+ ```bash
50
+ # Node.js
51
+ DEBUG=true node app.js
52
+ HTLKG_DEBUG=true node app.js
53
+
54
+ # Vite (in .env)
55
+ DEBUG=true
56
+ HTLKG_DEBUG=true
57
+ ```
58
+
59
+ ## formatDate
60
+
61
+ Format dates with locale support.
62
+
63
+ ```typescript
64
+ import { formatDate } from '@htlkg/core/utils';
65
+
66
+ formatDate(new Date());
67
+ // "Dec 30, 2024"
68
+
69
+ formatDate('2024-01-15', 'es-ES');
70
+ // "15 ene 2024"
71
+
72
+ formatDate(1704067200000, 'en-US', {
73
+ year: 'numeric',
74
+ month: 'long',
75
+ day: 'numeric',
76
+ hour: '2-digit',
77
+ minute: '2-digit',
78
+ });
79
+ // "January 1, 2024, 12:00 AM"
80
+ ```
81
+
82
+ ### Parameters
83
+
84
+ | Param | Type | Default | Description |
85
+ |-------|------|---------|-------------|
86
+ | `date` | `Date \| string \| number` | - | Date to format |
87
+ | `locale` | `string` | `'en-US'` | Locale string |
88
+ | `options` | `Intl.DateTimeFormatOptions` | `{ year, month, day }` | Format options |
89
+
90
+ ## truncateText
91
+
92
+ Truncate text with ellipsis.
93
+
94
+ ```typescript
95
+ import { truncateText } from '@htlkg/core/utils';
96
+
97
+ truncateText('Hello World', 8);
98
+ // "Hello..."
99
+
100
+ truncateText('Short', 10);
101
+ // "Short"
102
+
103
+ truncateText('Long text here', 10, ' →');
104
+ // "Long text →"
105
+ ```
106
+
107
+ ### Parameters
108
+
109
+ | Param | Type | Default | Description |
110
+ |-------|------|---------|-------------|
111
+ | `text` | `string` | - | Text to truncate |
112
+ | `maxLength` | `number` | - | Max length including suffix |
113
+ | `suffix` | `string` | `'...'` | Suffix to append |
114
+
115
+ ## groupBy
116
+
117
+ Group array items by a key.
118
+
119
+ ```typescript
120
+ import { groupBy } from '@htlkg/core/utils';
121
+
122
+ const users = [
123
+ { name: 'Alice', role: 'admin' },
124
+ { name: 'Bob', role: 'user' },
125
+ { name: 'Carol', role: 'admin' },
126
+ ];
127
+
128
+ groupBy(users, user => user.role);
129
+ // {
130
+ // admin: [{ name: 'Alice', ... }, { name: 'Carol', ... }],
131
+ // user: [{ name: 'Bob', ... }]
132
+ // }
133
+
134
+ // Group by numeric key
135
+ const items = [
136
+ { id: 1, category: 10 },
137
+ { id: 2, category: 20 },
138
+ { id: 3, category: 10 },
139
+ ];
140
+
141
+ groupBy(items, item => item.category);
142
+ // { 10: [...], 20: [...] }
143
+ ```
144
+
145
+ ## debounce
146
+
147
+ Delay function execution until after a pause in calls.
148
+
149
+ ```typescript
150
+ import { debounce } from '@htlkg/core/utils';
151
+
152
+ const search = debounce((query: string) => {
153
+ console.log('Searching:', query);
154
+ }, 300);
155
+
156
+ // Rapid calls
157
+ search('h');
158
+ search('he');
159
+ search('hel');
160
+ search('hello');
161
+ // Only logs "Searching: hello" after 300ms pause
162
+ ```
163
+
164
+ ### Use Cases
165
+
166
+ - Search input handlers
167
+ - Window resize handlers
168
+ - Form auto-save
169
+
170
+ ## throttle
171
+
172
+ Limit function execution to once per time period.
173
+
174
+ ```typescript
175
+ import { throttle } from '@htlkg/core/utils';
176
+
177
+ const handleScroll = throttle(() => {
178
+ console.log('Scroll position:', window.scrollY);
179
+ }, 100);
180
+
181
+ window.addEventListener('scroll', handleScroll);
182
+ // Logs at most once every 100ms during scrolling
183
+ ```
184
+
185
+ ### Use Cases
186
+
187
+ - Scroll event handlers
188
+ - Mouse move handlers
189
+ - API rate limiting
190
+
191
+ ## Comparison: debounce vs throttle
192
+
193
+ | Scenario | debounce | throttle |
194
+ |----------|----------|----------|
195
+ | Search input | ✓ Wait for typing pause | ✗ |
196
+ | Scroll tracking | ✗ | ✓ Regular updates |
197
+ | Window resize | ✓ Final size only | ✗ |
198
+ | Button spam prevention | ✗ | ✓ First click works |
199
+ | Auto-save | ✓ After editing stops | ✗ |
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Feature: htlkg-modular-architecture, Property 2: Workspace dependencies resolve correctly
3
+ *
4
+ * This test validates that workspace dependencies resolve correctly across all modules.
5
+ * For any module that depends on another workspace module, importing from the dependency
6
+ * should resolve to the local workspace package, not an external npm package.
7
+ */
8
+
9
+ import { describe, it, expect } from "vitest";
10
+ import fc from "fast-check";
11
+ import { readFileSync, existsSync } from "fs";
12
+ import { join, dirname } from "path";
13
+ import { fileURLToPath } from "url";
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+
18
+ // Get the workspace root (shared/)
19
+ const workspaceRoot = join(__dirname, "../../..");
20
+
21
+ interface PackageJson {
22
+ name: string;
23
+ dependencies?: Record<string, string>;
24
+ devDependencies?: Record<string, string>;
25
+ }
26
+
27
+ // Helper to read package.json
28
+ function readPackageJson(packagePath: string): PackageJson {
29
+ const pkgPath = join(workspaceRoot, packagePath, "package.json");
30
+ if (!existsSync(pkgPath)) {
31
+ throw new Error(`Package.json not found at ${pkgPath}`);
32
+ }
33
+ return JSON.parse(readFileSync(pkgPath, "utf-8"));
34
+ }
35
+
36
+ // Helper to check if a dependency uses workspace protocol
37
+ function usesWorkspaceProtocol(version: string): boolean {
38
+ return version.startsWith("workspace:");
39
+ }
40
+
41
+ // Get all workspace packages
42
+ const workspacePackages = [
43
+ "packages/core",
44
+ "packages/data",
45
+ "packages/components",
46
+ "packages/astro",
47
+ ];
48
+
49
+ describe("Workspace Structure Property Tests", () => {
50
+ /**
51
+ * Property 2: Workspace dependencies resolve correctly
52
+ * Validates: Requirements 1.4, 7.1, 7.2, 7.3, 7.4
53
+ *
54
+ * Note: Packages use version numbers (e.g., ^0.0.1) instead of workspace protocol
55
+ * because they are published to npm. This is valid for published packages.
56
+ */
57
+ it("should have valid internal dependencies", () => {
58
+ // For each package in the workspace
59
+ for (const packagePath of workspacePackages) {
60
+ const pkg = readPackageJson(packagePath);
61
+
62
+ // Check all dependencies
63
+ const allDeps = {
64
+ ...pkg.dependencies,
65
+ ...pkg.devDependencies,
66
+ };
67
+
68
+ // For each dependency
69
+ for (const [depName, depVersion] of Object.entries(allDeps)) {
70
+ // If it's an @htlkg package (internal workspace package)
71
+ if (depName.startsWith("@htlkg/")) {
72
+ // It should have a valid version (either workspace protocol or semver)
73
+ const isValid = usesWorkspaceProtocol(depVersion) || /^\^?\d+\.\d+\.\d+/.test(depVersion);
74
+ expect(
75
+ isValid,
76
+ `Package ${pkg.name} should have valid version for ${depName}, but got ${depVersion}`,
77
+ ).toBe(true);
78
+ }
79
+ }
80
+ }
81
+ });
82
+
83
+ it("should have valid package structure for all workspace packages", () => {
84
+ for (const packagePath of workspacePackages) {
85
+ const pkg = readPackageJson(packagePath);
86
+
87
+ // Each package should have a name
88
+ expect(pkg.name).toBeDefined();
89
+ expect(pkg.name).toMatch(/^@htlkg\//);
90
+
91
+ // Each package should have a src directory
92
+ const srcPath = join(workspaceRoot, packagePath, "src");
93
+ expect(
94
+ existsSync(srcPath),
95
+ `Package ${pkg.name} should have a src directory`,
96
+ ).toBe(true);
97
+
98
+ // Each package should have an index.ts
99
+ const indexPath = join(srcPath, "index.ts");
100
+ expect(
101
+ existsSync(indexPath),
102
+ `Package ${pkg.name} should have an index.ts file`,
103
+ ).toBe(true);
104
+ }
105
+ });
106
+
107
+ it("should have correct TypeScript configuration for all packages", () => {
108
+ for (const packagePath of workspacePackages) {
109
+ const tsconfigPath = join(workspaceRoot, packagePath, "tsconfig.json");
110
+
111
+ expect(
112
+ existsSync(tsconfigPath),
113
+ `Package at ${packagePath} should have a tsconfig.json`,
114
+ ).toBe(true);
115
+
116
+ const tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
117
+
118
+ // Should have compiler options
119
+ expect(tsconfig.compilerOptions).toBeDefined();
120
+
121
+ // Should have proper output directory (if it has one)
122
+ if (tsconfig.compilerOptions.outDir) {
123
+ expect(tsconfig.compilerOptions.outDir).toMatch(/\.\/dist|\.\/build/);
124
+ }
125
+ }
126
+ });
127
+
128
+ it("should have correct build configuration for all packages", () => {
129
+ for (const packagePath of workspacePackages) {
130
+ const pkg = readPackageJson(packagePath);
131
+
132
+ // Should have build script
133
+ expect(pkg.scripts?.build, `Package ${pkg.name} should have a build script`).toBeDefined();
134
+
135
+ // Should have dev script
136
+ expect(pkg.scripts?.dev, `Package ${pkg.name} should have a dev script`).toBeDefined();
137
+
138
+ // Should have test scripts
139
+ expect(pkg.scripts?.test, `Package ${pkg.name} should have a test script`).toBeDefined();
140
+ }
141
+ });
142
+
143
+ /**
144
+ * Property-based test: Dependency graph should be acyclic
145
+ * This ensures no circular dependencies exist
146
+ */
147
+ it("should not have circular dependencies", () => {
148
+ // Build dependency graph
149
+ const graph = new Map<string, Set<string>>();
150
+
151
+ for (const packagePath of workspacePackages) {
152
+ const pkg = readPackageJson(packagePath);
153
+ const deps = new Set<string>();
154
+
155
+ // Collect all @htlkg dependencies
156
+ const allDeps = {
157
+ ...pkg.dependencies,
158
+ ...pkg.devDependencies,
159
+ };
160
+
161
+ for (const depName of Object.keys(allDeps)) {
162
+ if (depName.startsWith("@htlkg/")) {
163
+ deps.add(depName);
164
+ }
165
+ }
166
+
167
+ graph.set(pkg.name, deps);
168
+ }
169
+
170
+ // Check for cycles using DFS
171
+ function hasCycle(
172
+ node: string,
173
+ visited: Set<string>,
174
+ recStack: Set<string>,
175
+ ): boolean {
176
+ visited.add(node);
177
+ recStack.add(node);
178
+
179
+ const neighbors = graph.get(node) || new Set();
180
+ for (const neighbor of neighbors) {
181
+ if (!visited.has(neighbor)) {
182
+ if (hasCycle(neighbor, visited, recStack)) {
183
+ return true;
184
+ }
185
+ } else if (recStack.has(neighbor)) {
186
+ return true;
187
+ }
188
+ }
189
+
190
+ recStack.delete(node);
191
+ return false;
192
+ }
193
+
194
+ const visited = new Set<string>();
195
+ for (const node of graph.keys()) {
196
+ if (!visited.has(node)) {
197
+ const recStack = new Set<string>();
198
+ expect(
199
+ hasCycle(node, visited, recStack),
200
+ `Circular dependency detected starting from ${node}`,
201
+ ).toBe(false);
202
+ }
203
+ }
204
+ });
205
+
206
+ /**
207
+ * Property-based test: All exports should be defined
208
+ */
209
+ it("should have all declared exports as files", () => {
210
+ for (const packagePath of workspacePackages) {
211
+ const pkg = readPackageJson(packagePath);
212
+
213
+ if (!pkg.exports) continue;
214
+
215
+ // Check each export path
216
+ for (const [exportKey, exportValue] of Object.entries(pkg.exports)) {
217
+ if (typeof exportValue === "string") {
218
+ // Skip wildcard exports as they represent patterns, not specific files
219
+ if (exportValue.includes("*")) continue;
220
+
221
+ // Simple export
222
+ const exportPath = join(
223
+ workspaceRoot,
224
+ packagePath,
225
+ exportValue.replace("./dist/", "src/").replace(".js", ".ts"),
226
+ );
227
+ expect(
228
+ existsSync(exportPath),
229
+ `Export ${exportKey} in ${pkg.name} should have corresponding source file at ${exportPath}`,
230
+ ).toBe(true);
231
+ }
232
+ }
233
+ }
234
+ });
235
+ });