@jmruthers/pace-core 0.5.136 → 0.5.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-CYOHOX3O.js → DataTable-6M4L6BI2.js} +10 -9
- package/dist/{EventLogo-801uofbR.d.ts → EventLogo-rFL_kRjk.d.ts} +73 -1
- package/dist/{UnifiedAuthProvider-5E5TUNMS.js → UnifiedAuthProvider-XIQQ7LVU.js} +4 -5
- package/dist/{chunk-YLKIDTUK.js → chunk-22WKWKRX.js} +4 -4
- package/dist/{chunk-TVYPTYOY.js → chunk-4C7EXCAR.js} +60 -24
- package/dist/chunk-4C7EXCAR.js.map +1 -0
- package/dist/{chunk-2TWNJ46Y.js → chunk-6LAAY47Q.js} +2 -2
- package/dist/{chunk-444EZN6N.js → chunk-7QCC6MCP.js} +88 -1
- package/dist/chunk-7QCC6MCP.js.map +1 -0
- package/dist/{chunk-FHWWBIHA.js → chunk-BCIBECNB.js} +5 -5
- package/dist/chunk-BJPBT3CU.js +21 -0
- package/dist/chunk-BJPBT3CU.js.map +1 -0
- package/dist/{chunk-L6PGMCMD.js → chunk-BLCXZEYF.js} +3 -3
- package/dist/{chunk-HJGGOMQ6.js → chunk-HAWZXGR2.js} +147 -103
- package/dist/chunk-HAWZXGR2.js.map +1 -0
- package/dist/{chunk-XARJS7CD.js → chunk-INQLMHPF.js} +2 -2
- package/dist/chunk-JISYG63F.js +70 -0
- package/dist/chunk-JISYG63F.js.map +1 -0
- package/dist/{chunk-NOHEVYVX.js → chunk-KYRHUBIU.js} +417 -319
- package/dist/chunk-KYRHUBIU.js.map +1 -0
- package/dist/{chunk-SL2YQDR6.js → chunk-MA6EPSGZ.js} +2 -2
- package/dist/{chunk-5DPZ5EAT.js → chunk-OWAG3GSU.js} +1 -3
- package/dist/{chunk-LTV3XIJJ.js → chunk-T6JN6LH6.js} +4 -4
- package/dist/{chunk-4MT5BGGL.js → chunk-YCWDTTUK.js} +4 -6
- package/dist/{chunk-4MT5BGGL.js.map → chunk-YCWDTTUK.js.map} +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -11
- package/dist/components.js.map +1 -1
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15 -14
- package/dist/index.js.map +1 -1
- package/dist/providers.js +3 -4
- package/dist/rbac/index.js +8 -9
- package/dist/schema-DTDZQe2u.d.ts +28 -0
- package/dist/types.d.ts +152 -3
- package/dist/types.js +51 -16
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +89 -4
- package/dist/utils.js +214 -96
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +1 -343
- package/dist/validation.js +3 -100
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +27 -0
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +79 -10
- package/docs/architecture/README.md +0 -1
- package/docs/styles/README.md +0 -2
- package/examples/RBAC/CompleteRBACExample.tsx +324 -0
- package/examples/RBAC/EventBasedApp.tsx +239 -0
- package/examples/RBAC/PermissionExample.tsx +151 -0
- package/examples/RBAC/index.ts +13 -0
- package/examples/public-pages/CorrectPublicPageImplementation.tsx +301 -0
- package/examples/public-pages/PublicEventPage.tsx +274 -0
- package/examples/public-pages/PublicPageApp.tsx +308 -0
- package/examples/public-pages/PublicPageUsageExample.tsx +216 -0
- package/examples/public-pages/index.ts +14 -0
- package/package.json +1 -10
- package/src/__tests__/TEST_STANDARD.md +92 -0
- package/src/components/Badge/Badge.test.tsx +314 -0
- package/src/components/Badge/Badge.tsx +304 -0
- package/src/components/Badge/index.ts +3 -0
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +217 -0
- package/src/components/DataTable/__tests__/styles.test.ts +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +8 -4
- package/src/components/DataTable/components/DataTableBody.tsx +461 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
- package/src/components/DataTable/components/FilterRow.tsx +9 -3
- package/src/components/DataTable/components/PaginationControls.tsx +1 -0
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +14 -68
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +62 -0
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +43 -0
- package/src/components/DataTable/core/ActionManager.ts +235 -0
- package/src/components/DataTable/core/ColumnManager.ts +205 -0
- package/src/components/DataTable/core/DataManager.ts +188 -0
- package/src/components/DataTable/core/DataTableContext.tsx +181 -0
- package/src/components/DataTable/core/LocalDataAdapter.ts +273 -0
- package/src/components/DataTable/core/PluginRegistry.ts +229 -0
- package/src/components/DataTable/core/StateManager.ts +311 -0
- package/src/components/DataTable/core/interfaces.ts +338 -0
- package/src/components/DataTable/styles.ts +27 -6
- package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +94 -0
- package/src/components/DataTable/utils/columnUtils.ts +40 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/index.ts +1 -0
- package/src/components/Dialog/README.md +804 -0
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +611 -0
- package/src/components/Dialog/utils/safeHtml.ts +185 -0
- package/src/components/Footer/Footer.test.tsx +1 -1
- package/src/components/Form/Form.test.tsx +1 -1
- package/src/components/Form/FormErrorSummary.tsx +113 -0
- package/src/components/Form/FormFieldset.tsx +127 -0
- package/src/components/Form/FormLiveRegion.tsx +198 -0
- package/src/components/LoginForm/LoginForm.test.tsx +1 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +76 -10
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
- package/src/components/PasswordReset/PasswordResetForm.test.tsx +597 -0
- package/src/components/PasswordReset/PasswordResetForm.tsx +201 -0
- package/src/components/PublicLayout/PublicPageDebugger.tsx +104 -0
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +162 -0
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Select/Select.tsx +20 -8
- package/src/components/Table/__tests__/Table.test.tsx +1 -1
- package/src/components/index.ts +3 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +83 -85
- package/src/index.ts +4 -0
- package/src/styles/core.css +3 -0
- package/src/utils/appConfig.ts +47 -0
- package/src/utils/appIdResolver.test.ts +499 -0
- package/src/utils/appIdResolver.ts +130 -0
- package/src/utils/appNameResolver.simple.test.ts +212 -0
- package/src/utils/appNameResolver.test.ts +121 -0
- package/src/utils/appNameResolver.ts +191 -0
- package/src/utils/audit.ts +127 -0
- package/src/utils/auth-utils.ts +96 -0
- package/src/utils/bundleAnalysis.ts +129 -0
- package/src/utils/cn.ts +7 -0
- package/src/utils/debugLogger.ts +67 -0
- package/src/utils/deviceFingerprint.ts +215 -0
- package/src/utils/dynamicUtils.ts +105 -0
- package/src/utils/file-reference.test.ts +788 -0
- package/src/utils/file-reference.ts +519 -0
- package/src/utils/formatDate.test.ts +237 -0
- package/src/utils/formatting.ts +133 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/lazyLoad.tsx +44 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.test.ts +322 -0
- package/src/utils/organisationContext.ts +153 -0
- package/src/utils/performanceBenchmark.ts +64 -0
- package/src/utils/performanceBudgets.ts +110 -0
- package/src/utils/permissionTypes.ts +37 -0
- package/src/utils/permissionUtils.test.ts +393 -0
- package/src/utils/permissionUtils.ts +34 -0
- package/src/utils/sanitization.ts +264 -0
- package/src/utils/schemaUtils.ts +37 -0
- package/src/utils/secureDataAccess.test.ts +711 -0
- package/src/utils/secureDataAccess.ts +377 -0
- package/src/utils/secureErrors.ts +79 -0
- package/src/utils/secureStorage.ts +244 -0
- package/src/utils/security.ts +156 -0
- package/src/utils/securityMonitor.ts +45 -0
- package/src/utils/sessionTracking.ts +126 -0
- package/src/utils/validation.ts +111 -0
- package/src/utils/validationUtils.ts +120 -0
- package/src/validation/index.ts +2 -2
- package/dist/chunk-444EZN6N.js.map +0 -1
- package/dist/chunk-APIBCTL2.js +0 -670
- package/dist/chunk-APIBCTL2.js.map +0 -1
- package/dist/chunk-HJGGOMQ6.js.map +0 -1
- package/dist/chunk-K2WWTH7O.js +0 -94
- package/dist/chunk-K2WWTH7O.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-NOHEVYVX.js.map +0 -1
- package/dist/chunk-TVYPTYOY.js.map +0 -1
- package/dist/validation-8npbysjg.d.ts +0 -177
- /package/dist/{DataTable-CYOHOX3O.js.map → DataTable-6M4L6BI2.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-5E5TUNMS.js.map → UnifiedAuthProvider-XIQQ7LVU.js.map} +0 -0
- /package/dist/{chunk-YLKIDTUK.js.map → chunk-22WKWKRX.js.map} +0 -0
- /package/dist/{chunk-2TWNJ46Y.js.map → chunk-6LAAY47Q.js.map} +0 -0
- /package/dist/{chunk-FHWWBIHA.js.map → chunk-BCIBECNB.js.map} +0 -0
- /package/dist/{chunk-L6PGMCMD.js.map → chunk-BLCXZEYF.js.map} +0 -0
- /package/dist/{chunk-XARJS7CD.js.map → chunk-INQLMHPF.js.map} +0 -0
- /package/dist/{chunk-SL2YQDR6.js.map → chunk-MA6EPSGZ.js.map} +0 -0
- /package/dist/{chunk-5DPZ5EAT.js.map → chunk-OWAG3GSU.js.map} +0 -0
- /package/dist/{chunk-LTV3XIJJ.js.map → chunk-T6JN6LH6.js.map} +0 -0
- /package/examples/{components → components 2}/DataTable/HierarchicalActionsExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/HierarchicalExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/InitialPageSizeExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/PerformanceExample.tsx +0 -0
- /package/examples/{components → components 2}/DataTable/index.ts +0 -0
- /package/examples/{components → components 2}/Dialog/BasicHtmlTest.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/DebugHtmlExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/HtmlDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/ScrollableDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/SimpleHtmlTest.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/SmartDialogExample.tsx +0 -0
- /package/examples/{components → components 2}/Dialog/index.ts +0 -0
- /package/examples/{components → components 2}/index.ts +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file formatDate Utility Tests
|
|
3
|
+
* @description Comprehensive tests for the formatDate utility function
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { formatDate } from './formatting';
|
|
8
|
+
|
|
9
|
+
describe('formatDate Utility', () => {
|
|
10
|
+
let originalLocale: string;
|
|
11
|
+
let originalTimezone: string;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
// Store original values
|
|
15
|
+
originalLocale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
16
|
+
originalTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('Date Object Input', () => {
|
|
20
|
+
it('formats a Date object correctly', () => {
|
|
21
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
22
|
+
const result = formatDate(date);
|
|
23
|
+
|
|
24
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('formats different Date objects', () => {
|
|
28
|
+
const dates = [
|
|
29
|
+
new Date('2023-12-25T00:00:00.000Z'),
|
|
30
|
+
new Date('2024-06-15T12:00:00.000Z'),
|
|
31
|
+
new Date('2024-12-31T23:59:59.999Z')
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
dates.forEach(date => {
|
|
35
|
+
const result = formatDate(date);
|
|
36
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('String Input', () => {
|
|
42
|
+
it('formats ISO date strings correctly', () => {
|
|
43
|
+
const result = formatDate('2024-01-15T10:30:00.000Z');
|
|
44
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('formats date-only strings correctly', () => {
|
|
48
|
+
const result = formatDate('2024-01-15');
|
|
49
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('formats various date string formats', () => {
|
|
53
|
+
const dateStrings = [
|
|
54
|
+
'2024-01-15',
|
|
55
|
+
'2024-01-15T10:30:00Z',
|
|
56
|
+
'2024-01-15T10:30:00.000Z',
|
|
57
|
+
'2024-01-15T10:30:00+00:00'
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
dateStrings.forEach(dateString => {
|
|
61
|
+
const result = formatDate(dateString);
|
|
62
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('Number Input', () => {
|
|
68
|
+
it('formats timestamp numbers correctly', () => {
|
|
69
|
+
const timestamp = new Date('2024-01-15T10:30:00.000Z').getTime();
|
|
70
|
+
const result = formatDate(timestamp);
|
|
71
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('formats Unix timestamps correctly', () => {
|
|
75
|
+
const unixTimestamp = Math.floor(new Date('2024-01-15T10:30:00.000Z').getTime() / 1000);
|
|
76
|
+
const result = formatDate(unixTimestamp * 1000); // Convert back to milliseconds
|
|
77
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Edge Cases', () => {
|
|
82
|
+
it('handles invalid date strings gracefully', () => {
|
|
83
|
+
const invalidDates = [
|
|
84
|
+
'invalid-date',
|
|
85
|
+
'not-a-date',
|
|
86
|
+
'2024-13-45', // Invalid month and day
|
|
87
|
+
'2024-02-30' // Invalid day for February
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
invalidDates.forEach(invalidDate => {
|
|
91
|
+
const result = formatDate(invalidDate);
|
|
92
|
+
// Should return a string even for invalid dates (may be "Invalid Date")
|
|
93
|
+
expect(typeof result).toBe('string');
|
|
94
|
+
expect(result.length).toBeGreaterThan(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('handles empty string input', () => {
|
|
99
|
+
const result = formatDate('');
|
|
100
|
+
expect(typeof result).toBe('string');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('handles null and undefined input', () => {
|
|
104
|
+
// @ts-expect-error - Testing runtime behavior with invalid types
|
|
105
|
+
expect(() => formatDate(null)).toThrow();
|
|
106
|
+
// @ts-expect-error - Testing runtime behavior with invalid types
|
|
107
|
+
expect(() => formatDate(undefined)).toThrow();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('handles very large numbers', () => {
|
|
111
|
+
const largeNumber = Number.MAX_SAFE_INTEGER;
|
|
112
|
+
const result = formatDate(largeNumber);
|
|
113
|
+
expect(typeof result).toBe('string');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('handles negative numbers', () => {
|
|
117
|
+
const negativeNumber = -1000000;
|
|
118
|
+
const result = formatDate(negativeNumber);
|
|
119
|
+
expect(typeof result).toBe('string');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('handles zero', () => {
|
|
123
|
+
const result = formatDate(0);
|
|
124
|
+
expect(typeof result).toBe('string');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('Format Consistency', () => {
|
|
129
|
+
it('returns consistent format for same date', () => {
|
|
130
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
131
|
+
const result1 = formatDate(date);
|
|
132
|
+
const result2 = formatDate(date);
|
|
133
|
+
|
|
134
|
+
expect(result1).toBe(result2);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('returns consistent format across different input types for same date', () => {
|
|
138
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
139
|
+
const dateString = '2024-01-15T10:30:00.000Z';
|
|
140
|
+
const timestamp = date.getTime();
|
|
141
|
+
|
|
142
|
+
const result1 = formatDate(date);
|
|
143
|
+
const result2 = formatDate(dateString);
|
|
144
|
+
const result3 = formatDate(timestamp);
|
|
145
|
+
|
|
146
|
+
expect(result1).toBe(result2);
|
|
147
|
+
expect(result2).toBe(result3);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('Locale Handling', () => {
|
|
152
|
+
it('uses default locale when no locale is specified', () => {
|
|
153
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
154
|
+
const result = formatDate(date);
|
|
155
|
+
|
|
156
|
+
// Should use the default locale format
|
|
157
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('handles different locales correctly', () => {
|
|
161
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
162
|
+
|
|
163
|
+
const result = formatDate(date);
|
|
164
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('Time Zone Handling', () => {
|
|
169
|
+
it('handles different time zones correctly', () => {
|
|
170
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
171
|
+
const result = formatDate(date);
|
|
172
|
+
|
|
173
|
+
// Should format consistently regardless of timezone
|
|
174
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('Performance', () => {
|
|
179
|
+
it('handles large number of calls efficiently', () => {
|
|
180
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
181
|
+
const startTime = performance.now();
|
|
182
|
+
|
|
183
|
+
// Call formatDate many times
|
|
184
|
+
for (let i = 0; i < 1000; i++) {
|
|
185
|
+
formatDate(date);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const endTime = performance.now();
|
|
189
|
+
const duration = endTime - startTime;
|
|
190
|
+
|
|
191
|
+
// Should complete in reasonable time (less than 200ms for 1000 calls)
|
|
192
|
+
expect(duration).toBeLessThan(200);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('Type Safety', () => {
|
|
197
|
+
it('accepts valid Date types', () => {
|
|
198
|
+
const date = new Date();
|
|
199
|
+
expect(() => formatDate(date)).not.toThrow();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('accepts valid string types', () => {
|
|
203
|
+
const dateString = '2024-01-15';
|
|
204
|
+
expect(() => formatDate(dateString)).not.toThrow();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('accepts valid number types', () => {
|
|
208
|
+
const timestamp = Date.now();
|
|
209
|
+
expect(() => formatDate(timestamp)).not.toThrow();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('Return Value', () => {
|
|
214
|
+
it('returns a string', () => {
|
|
215
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
216
|
+
const result = formatDate(date);
|
|
217
|
+
|
|
218
|
+
expect(typeof result).toBe('string');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('returns non-empty string for valid dates', () => {
|
|
222
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
223
|
+
const result = formatDate(date);
|
|
224
|
+
|
|
225
|
+
expect(result).toBeTruthy();
|
|
226
|
+
expect(result.length).toBeGreaterThan(0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('returns expected format pattern', () => {
|
|
230
|
+
const date = new Date('2024-01-15T10:30:00.000Z');
|
|
231
|
+
const result = formatDate(date);
|
|
232
|
+
|
|
233
|
+
// Should match pattern: "MMM DD, YYYY"
|
|
234
|
+
expect(result).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for formatting data in the application
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format a date as a readable string in "dd mmm yyyy" format (e.g., "15 Jun 2024")
|
|
7
|
+
*/
|
|
8
|
+
export function formatDate(date: Date | string | number): string {
|
|
9
|
+
const dateObj = typeof date === 'string' || typeof date === 'number'
|
|
10
|
+
? new Date(date)
|
|
11
|
+
: date;
|
|
12
|
+
|
|
13
|
+
// Use 'en-GB' locale to ensure "dd mmm yyyy" format (e.g., "15 Jun 2024")
|
|
14
|
+
return dateObj.toLocaleDateString('en-GB', {
|
|
15
|
+
year: 'numeric',
|
|
16
|
+
month: 'short',
|
|
17
|
+
day: 'numeric'
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Format a number as a currency
|
|
23
|
+
*/
|
|
24
|
+
export function formatCurrency(value: number, currencyCode = 'USD', locale = 'en-US'): string {
|
|
25
|
+
return new Intl.NumberFormat(locale, {
|
|
26
|
+
style: 'currency',
|
|
27
|
+
currency: currencyCode,
|
|
28
|
+
}).format(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Format a number with custom options
|
|
33
|
+
*/
|
|
34
|
+
export function formatNumber(
|
|
35
|
+
value: number,
|
|
36
|
+
options: Intl.NumberFormatOptions = {},
|
|
37
|
+
locale = 'en-US'
|
|
38
|
+
): string {
|
|
39
|
+
return new Intl.NumberFormat(locale, options).format(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Format a number as a percentage.
|
|
44
|
+
*
|
|
45
|
+
* The third parameter can be either:
|
|
46
|
+
* - A number for fixed decimal places (backward compatible): `formatPercent(0.81, 'en-US', 2)`
|
|
47
|
+
* - An options object with:
|
|
48
|
+
* - `decimals`: Fixed number of decimal places (default: 1)
|
|
49
|
+
* - `preserveDecimals`: Auto-detect and preserve decimal places from the input value
|
|
50
|
+
* - `maxDecimals`: Maximum decimal places when preserving (default: 10)
|
|
51
|
+
*
|
|
52
|
+
* @param value - The percentage value as a decimal (e.g., 0.81 for 0.81%)
|
|
53
|
+
* @param locale - The locale string (default: 'en-US')
|
|
54
|
+
* @param decimalsOrOptions - Either a number for fixed decimals, or an options object with:
|
|
55
|
+
* - `decimals` - Fixed number of decimal places (default: 1)
|
|
56
|
+
* - `preserveDecimals` - Auto-detect and preserve decimal places from the input value
|
|
57
|
+
* - `maxDecimals` - Maximum decimal places when preserving (default: 10)
|
|
58
|
+
* @returns Formatted percentage string (e.g., "0.81%", "81%")
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* // Fixed decimals (default behavior)
|
|
63
|
+
* formatPercent(0.5) // '0.5%'
|
|
64
|
+
* formatPercent(0.81, 'en-US', 1) // '0.8%' (loses precision)
|
|
65
|
+
*
|
|
66
|
+
* // Preserve decimal places dynamically
|
|
67
|
+
* formatPercent(0.81, 'en-US', { preserveDecimals: true }) // '0.81%'
|
|
68
|
+
* formatPercent(0.8123, 'en-US', { preserveDecimals: true, maxDecimals: 2 }) // '0.81%'
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function formatPercent(
|
|
72
|
+
value: number,
|
|
73
|
+
locale: string = 'en-US',
|
|
74
|
+
decimalsOrOptions?: number | {
|
|
75
|
+
decimals?: number;
|
|
76
|
+
preserveDecimals?: boolean;
|
|
77
|
+
maxDecimals?: number;
|
|
78
|
+
}
|
|
79
|
+
): string {
|
|
80
|
+
let decimals: number;
|
|
81
|
+
|
|
82
|
+
// Backward compatibility: if decimalsOrOptions is a number, use it directly
|
|
83
|
+
if (typeof decimalsOrOptions === 'number') {
|
|
84
|
+
decimals = decimalsOrOptions;
|
|
85
|
+
} else if (decimalsOrOptions && typeof decimalsOrOptions === 'object') {
|
|
86
|
+
// New options object: check if we should preserve decimals
|
|
87
|
+
if (decimalsOrOptions.preserveDecimals) {
|
|
88
|
+
const valueStr = value.toString();
|
|
89
|
+
const decimalIndex = valueStr.indexOf('.');
|
|
90
|
+
|
|
91
|
+
if (decimalIndex !== -1) {
|
|
92
|
+
const detectedDecimals = valueStr.length - decimalIndex - 1;
|
|
93
|
+
const maxDecimals = decimalsOrOptions.maxDecimals ?? 10;
|
|
94
|
+
decimals = Math.min(detectedDecimals, maxDecimals);
|
|
95
|
+
} else {
|
|
96
|
+
decimals = 0;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
decimals = decimalsOrOptions.decimals ?? 1;
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
decimals = 1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return new Intl.NumberFormat(locale, {
|
|
106
|
+
style: 'percent',
|
|
107
|
+
minimumFractionDigits: decimals,
|
|
108
|
+
maximumFractionDigits: decimals,
|
|
109
|
+
}).format(value / 100);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format a large number with abbreviations (K, M, B)
|
|
114
|
+
*/
|
|
115
|
+
export function formatCompactNumber(value: number, locale = 'en-US'): string {
|
|
116
|
+
return new Intl.NumberFormat(locale, {
|
|
117
|
+
notation: 'compact',
|
|
118
|
+
compactDisplay: 'short'
|
|
119
|
+
}).format(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Format a file size in bytes to a human-readable string
|
|
124
|
+
*/
|
|
125
|
+
export function formatFileSize(bytes: number): string {
|
|
126
|
+
if (bytes === 0) return '0 Bytes';
|
|
127
|
+
|
|
128
|
+
const k = 1024;
|
|
129
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
130
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
131
|
+
|
|
132
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
133
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -18,6 +18,13 @@ export * from './app/appIdResolver';
|
|
|
18
18
|
|
|
19
19
|
// Validation and sanitization
|
|
20
20
|
export * from './validation';
|
|
21
|
+
// Explicitly re-export commonly used validation utilities to ensure they're available
|
|
22
|
+
// Import from source modules to avoid re-export issues
|
|
23
|
+
export { validateUserInput, usernameSchema } from './validation/validationUtils';
|
|
24
|
+
export { sanitizeUserInput, sanitizeFormData } from './validation/sanitization';
|
|
25
|
+
export { emailSchema, nameSchema, phoneSchema, urlSchema } from './validation/common';
|
|
26
|
+
export { passwordSchema } from './validation/passwordSchema';
|
|
27
|
+
export { pickSchema, combineSchemas } from './validation/schema';
|
|
21
28
|
|
|
22
29
|
// Security utilities
|
|
23
30
|
export {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { Suspense, ComponentType, lazy } from 'react';
|
|
2
|
+
import { LoadingSpinner } from '../components/LoadingSpinner/LoadingSpinner';
|
|
3
|
+
|
|
4
|
+
interface LazyLoadOptions {
|
|
5
|
+
fallback?: React.ReactNode;
|
|
6
|
+
errorBoundary?: ComponentType<{ children: React.ReactNode }>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a lazy-loaded component with error boundary and loading fallback
|
|
11
|
+
*/
|
|
12
|
+
export function createLazyComponent<T extends ComponentType<any>>(
|
|
13
|
+
importFn: () => Promise<{ default: T }>,
|
|
14
|
+
componentName: string,
|
|
15
|
+
options: LazyLoadOptions = {}
|
|
16
|
+
): T {
|
|
17
|
+
const LazyComponent = lazy(importFn);
|
|
18
|
+
|
|
19
|
+
const WrappedComponent = (props: any) => {
|
|
20
|
+
const content = (
|
|
21
|
+
<Suspense fallback={options.fallback || <LoadingSpinner />}>
|
|
22
|
+
<LazyComponent {...props} />
|
|
23
|
+
</Suspense>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (options.errorBoundary) {
|
|
27
|
+
const ErrorBoundary = options.errorBoundary;
|
|
28
|
+
return <ErrorBoundary>{content}</ErrorBoundary>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return content;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
WrappedComponent.displayName = `Lazy${componentName}`;
|
|
35
|
+
return WrappedComponent as T;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Lazy-loaded DataTable component
|
|
40
|
+
*/
|
|
41
|
+
export const LazyDataTable = createLazyComponent(
|
|
42
|
+
() => import('../components/DataTable').then(module => ({ default: module.DataTable })),
|
|
43
|
+
'DataTable'
|
|
44
|
+
);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Production-Safe Logger Utility
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/Logger
|
|
5
|
+
* @since 0.4.76
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Logger levels for different types of messages
|
|
10
|
+
*/
|
|
11
|
+
export enum LogLevel {
|
|
12
|
+
DEBUG = 0,
|
|
13
|
+
INFO = 1,
|
|
14
|
+
WARN = 2,
|
|
15
|
+
ERROR = 3,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Logger configuration interface
|
|
20
|
+
*/
|
|
21
|
+
export interface LoggerConfig {
|
|
22
|
+
/** Minimum log level to output */
|
|
23
|
+
level: LogLevel;
|
|
24
|
+
/** Whether to include timestamps */
|
|
25
|
+
includeTimestamp: boolean;
|
|
26
|
+
/** Whether to include component names */
|
|
27
|
+
includeComponent: boolean;
|
|
28
|
+
/** Custom prefix for all log messages */
|
|
29
|
+
prefix?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Production-safe logger that no-ops in production builds
|
|
34
|
+
* Only outputs logs in development mode to prevent production console spam
|
|
35
|
+
*/
|
|
36
|
+
export class Logger {
|
|
37
|
+
private static config: LoggerConfig = {
|
|
38
|
+
level: LogLevel.DEBUG,
|
|
39
|
+
includeTimestamp: false,
|
|
40
|
+
includeComponent: true,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if we're in development mode
|
|
45
|
+
*/
|
|
46
|
+
private static get isDevelopment(): boolean {
|
|
47
|
+
return import.meta.env.MODE === 'development';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Configure the logger
|
|
52
|
+
*/
|
|
53
|
+
static configure(config: Partial<LoggerConfig>): void {
|
|
54
|
+
this.config = { ...this.config, ...config };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Format a log message with optional timestamp and component
|
|
59
|
+
*/
|
|
60
|
+
private static formatMessage(
|
|
61
|
+
level: LogLevel,
|
|
62
|
+
component: string,
|
|
63
|
+
message: string
|
|
64
|
+
): string {
|
|
65
|
+
const parts: string[] = [];
|
|
66
|
+
|
|
67
|
+
if (this.config.prefix) {
|
|
68
|
+
parts.push(`[${this.config.prefix}]`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.config.includeTimestamp) {
|
|
72
|
+
parts.push(`[${new Date().toISOString()}]`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
parts.push(`[${LogLevel[level]}]`);
|
|
76
|
+
|
|
77
|
+
if (this.config.includeComponent && component) {
|
|
78
|
+
parts.push(`[${component}]`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
parts.push(message);
|
|
82
|
+
|
|
83
|
+
return parts.join(' ');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if a log level should be output
|
|
88
|
+
*/
|
|
89
|
+
private static shouldLog(level: LogLevel): boolean {
|
|
90
|
+
return this.isDevelopment && level >= this.config.level;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Log debug information (development only)
|
|
95
|
+
*/
|
|
96
|
+
static debug(component: string, message: string, ...args: any[]): void {
|
|
97
|
+
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
98
|
+
try {
|
|
99
|
+
console.debug(this.formatMessage(LogLevel.DEBUG, component, message), ...args);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
// Gracefully handle console method errors
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Log info information (development only)
|
|
108
|
+
*/
|
|
109
|
+
static info(component: string, message: string, ...args: any[]): void {
|
|
110
|
+
if (this.shouldLog(LogLevel.INFO)) {
|
|
111
|
+
try {
|
|
112
|
+
console.info(this.formatMessage(LogLevel.INFO, component, message), ...args);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// Gracefully handle console method errors
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Log warning information (development only)
|
|
121
|
+
*/
|
|
122
|
+
static warn(component: string, message: string, ...args: any[]): void {
|
|
123
|
+
if (this.shouldLog(LogLevel.WARN)) {
|
|
124
|
+
try {
|
|
125
|
+
console.warn(this.formatMessage(LogLevel.WARN, component, message), ...args);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Gracefully handle console method errors
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Log error information (development only)
|
|
134
|
+
*/
|
|
135
|
+
static error(component: string, message: string, ...args: any[]): void {
|
|
136
|
+
if (this.shouldLog(LogLevel.ERROR)) {
|
|
137
|
+
try {
|
|
138
|
+
console.error(this.formatMessage(LogLevel.ERROR, component, message), ...args);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// Gracefully handle console method errors
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create a scoped logger for a specific component
|
|
147
|
+
*/
|
|
148
|
+
static createScopedLogger(component: string) {
|
|
149
|
+
return {
|
|
150
|
+
debug: (message: string, ...args: any[]) => this.debug(component, message, ...args),
|
|
151
|
+
info: (message: string, ...args: any[]) => this.info(component, message, ...args),
|
|
152
|
+
warn: (message: string, ...args: any[]) => this.warn(component, message, ...args),
|
|
153
|
+
error: (message: string, ...args: any[]) => this.error(component, message, ...args),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Default logger instance for convenience
|
|
160
|
+
*/
|
|
161
|
+
export const logger = {
|
|
162
|
+
debug: (component: string, message: string, ...args: any[]) =>
|
|
163
|
+
Logger.debug(component, message, ...args),
|
|
164
|
+
info: (component: string, message: string, ...args: any[]) =>
|
|
165
|
+
Logger.info(component, message, ...args),
|
|
166
|
+
warn: (component: string, message: string, ...args: any[]) =>
|
|
167
|
+
Logger.warn(component, message, ...args),
|
|
168
|
+
error: (component: string, message: string, ...args: any[]) =>
|
|
169
|
+
Logger.error(component, message, ...args),
|
|
170
|
+
createScopedLogger: (component: string) => Logger.createScopedLogger(component),
|
|
171
|
+
configure: (config: Partial<LoggerConfig>) => Logger.configure(config),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Convenience function to create a scoped logger
|
|
176
|
+
*/
|
|
177
|
+
export function createLogger(component: string) {
|
|
178
|
+
return Logger.createScopedLogger(component);
|
|
179
|
+
}
|