mtrl-addons 0.1.2 → 0.2.2
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/AI.md +28 -230
- package/CLAUDE.md +882 -0
- package/build.js +253 -24
- package/package.json +14 -4
- package/scripts/debug/vlist-selection.ts +121 -0
- package/src/components/index.ts +5 -41
- package/src/components/{list → vlist}/config.ts +66 -95
- package/src/components/vlist/constants.ts +23 -0
- package/src/components/vlist/features/api.ts +626 -0
- package/src/components/vlist/features/index.ts +10 -0
- package/src/components/vlist/features/selection.ts +436 -0
- package/src/components/vlist/features/viewport.ts +59 -0
- package/src/components/vlist/index.ts +17 -0
- package/src/components/{list → vlist}/types.ts +242 -32
- package/src/components/vlist/vlist.ts +92 -0
- package/src/core/compose/features/gestures/index.ts +227 -0
- package/src/core/compose/features/gestures/longpress.ts +383 -0
- package/src/core/compose/features/gestures/pan.ts +424 -0
- package/src/core/compose/features/gestures/pinch.ts +475 -0
- package/src/core/compose/features/gestures/rotate.ts +485 -0
- package/src/core/compose/features/gestures/swipe.ts +492 -0
- package/src/core/compose/features/gestures/tap.ts +334 -0
- package/src/core/compose/features/index.ts +2 -38
- package/src/core/compose/index.ts +13 -29
- package/src/core/gestures/index.ts +31 -0
- package/src/core/gestures/longpress.ts +68 -0
- package/src/core/gestures/manager.ts +418 -0
- package/src/core/gestures/pan.ts +48 -0
- package/src/core/gestures/pinch.ts +58 -0
- package/src/core/gestures/rotate.ts +58 -0
- package/src/core/gestures/swipe.ts +66 -0
- package/src/core/gestures/tap.ts +45 -0
- package/src/core/gestures/types.ts +387 -0
- package/src/core/gestures/utils.ts +128 -0
- package/src/core/index.ts +27 -151
- package/src/core/layout/schema.ts +153 -72
- package/src/core/layout/types.ts +5 -2
- package/src/core/viewport/constants.ts +145 -0
- package/src/core/viewport/features/base.ts +73 -0
- package/src/core/viewport/features/collection.ts +1182 -0
- package/src/core/viewport/features/events.ts +130 -0
- package/src/core/viewport/features/index.ts +20 -0
- package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
- package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
- package/src/core/viewport/features/momentum.ts +269 -0
- package/src/core/viewport/features/placeholders.ts +335 -0
- package/src/core/viewport/features/rendering.ts +962 -0
- package/src/core/viewport/features/scrollbar.ts +434 -0
- package/src/core/viewport/features/scrolling.ts +634 -0
- package/src/core/viewport/features/utils.ts +94 -0
- package/src/core/viewport/features/virtual.ts +525 -0
- package/src/core/viewport/index.ts +31 -0
- package/src/core/viewport/types.ts +133 -0
- package/src/core/viewport/utils/speed-tracker.ts +79 -0
- package/src/core/viewport/viewport.ts +265 -0
- package/src/index.ts +0 -7
- package/src/styles/components/_vlist.scss +352 -0
- package/src/styles/index.scss +1 -1
- package/test/components/vlist-selection.test.ts +240 -0
- package/test/components/vlist.test.ts +63 -0
- package/test/core/collection/adapter.test.ts +161 -0
- package/bun.lock +0 -792
- package/src/components/list/api.ts +0 -314
- package/src/components/list/constants.ts +0 -56
- package/src/components/list/features/api.ts +0 -428
- package/src/components/list/features/index.ts +0 -31
- package/src/components/list/features/list-manager.ts +0 -502
- package/src/components/list/index.ts +0 -39
- package/src/components/list/list.ts +0 -234
- package/src/core/collection/base-collection.ts +0 -100
- package/src/core/collection/collection-composer.ts +0 -178
- package/src/core/collection/collection.ts +0 -745
- package/src/core/collection/constants.ts +0 -172
- package/src/core/collection/events.ts +0 -428
- package/src/core/collection/features/api/loading.ts +0 -279
- package/src/core/collection/features/operations/data-operations.ts +0 -147
- package/src/core/collection/index.ts +0 -104
- package/src/core/collection/state.ts +0 -497
- package/src/core/collection/types.ts +0 -404
- package/src/core/compose/features/collection.ts +0 -119
- package/src/core/compose/features/selection.ts +0 -213
- package/src/core/compose/features/styling.ts +0 -108
- package/src/core/list-manager/api.ts +0 -599
- package/src/core/list-manager/config.ts +0 -593
- package/src/core/list-manager/constants.ts +0 -268
- package/src/core/list-manager/features/api.ts +0 -58
- package/src/core/list-manager/features/collection/collection.ts +0 -705
- package/src/core/list-manager/features/collection/index.ts +0 -17
- package/src/core/list-manager/features/viewport/constants.ts +0 -42
- package/src/core/list-manager/features/viewport/index.ts +0 -16
- package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
- package/src/core/list-manager/features/viewport/rendering.ts +0 -575
- package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
- package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
- package/src/core/list-manager/features/viewport/template.ts +0 -220
- package/src/core/list-manager/features/viewport/viewport.ts +0 -654
- package/src/core/list-manager/features/viewport/virtual.ts +0 -309
- package/src/core/list-manager/index.ts +0 -279
- package/src/core/list-manager/list-manager.ts +0 -206
- package/src/core/list-manager/types.ts +0 -439
- package/src/core/list-manager/utils/calculations.ts +0 -290
- package/src/core/list-manager/utils/range-calculator.ts +0 -349
- package/src/core/list-manager/utils/speed-tracker.ts +0 -273
- package/src/styles/components/_list.scss +0 -244
- package/src/types/mtrl.d.ts +0 -6
- package/test/components/list.test.ts +0 -256
- package/test/core/collection/failed-ranges.test.ts +0 -270
- package/test/core/compose/features.test.ts +0 -183
- package/test/core/list-manager/features/collection.test.ts +0 -704
- package/test/core/list-manager/features/viewport.test.ts +0 -698
- package/test/core/list-manager/list-manager.test.ts +0 -593
- package/test/core/list-manager/utils/calculations.test.ts +0 -433
- package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
- package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
- package/tsconfig.build.json +0 -23
- /package/src/components/{list → vlist}/features.ts +0 -0
- /package/src/core/{compose → viewport}/features/performance.ts +0 -0
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Constants for the mtrl-addons collection system (Pure Data Layer)
|
|
3
|
-
*
|
|
4
|
-
* Data-related constants only - NO UI constants
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Data pagination constants
|
|
9
|
-
*/
|
|
10
|
-
export const DATA_PAGINATION = {
|
|
11
|
-
DEFAULT_PAGE_SIZE: 20,
|
|
12
|
-
DEFAULT_CURRENT_PAGE: 1,
|
|
13
|
-
MAX_PREFETCH_PAGES: 5,
|
|
14
|
-
MIN_PAGE_SIZE: 1,
|
|
15
|
-
MAX_PAGE_SIZE: 1000,
|
|
16
|
-
} as const;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Data caching constants
|
|
20
|
-
*/
|
|
21
|
-
export const DATA_CACHE = {
|
|
22
|
-
DEFAULT_MAX_SIZE: 1000,
|
|
23
|
-
DEFAULT_MAX_AGE: 60 * 60 * 1000, // 1 hour
|
|
24
|
-
CLEANUP_INTERVAL: 5 * 60 * 1000, // 5 minutes
|
|
25
|
-
DEFAULT_STRATEGY: "memory",
|
|
26
|
-
LRU_DEFAULT_SIZE: 500,
|
|
27
|
-
} as const;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Data persistence constants
|
|
31
|
-
*/
|
|
32
|
-
export const DATA_PERSISTENCE = {
|
|
33
|
-
DEFAULT_KEY_PREFIX: "mtrl-collection-",
|
|
34
|
-
DEFAULT_VERSION: 1,
|
|
35
|
-
SYNC_DEBOUNCE: 1000, // 1 second
|
|
36
|
-
DEFAULT_STRATEGY: "localStorage",
|
|
37
|
-
MAX_LOCALSTORAGE_SIZE: 5 * 1024 * 1024, // 5MB
|
|
38
|
-
INDEXED_DB_VERSION: 1,
|
|
39
|
-
} as const;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Web worker constants
|
|
43
|
-
*/
|
|
44
|
-
export const WEB_WORKERS = {
|
|
45
|
-
MAX_WORKERS:
|
|
46
|
-
typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 4 : 4,
|
|
47
|
-
TASK_TIMEOUT: 30000, // 30 seconds
|
|
48
|
-
CHUNK_SIZE: 1000, // Items per worker task
|
|
49
|
-
DEFAULT_PRIORITY: "normal",
|
|
50
|
-
RETRY_ATTEMPTS: 3,
|
|
51
|
-
RETRY_DELAY: 1000, // 1 second
|
|
52
|
-
} as const;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Data validation constants
|
|
56
|
-
*/
|
|
57
|
-
export const DATA_VALIDATION = {
|
|
58
|
-
DEFAULT_STRICT_MODE: false,
|
|
59
|
-
MAX_VALIDATION_ERRORS: 100,
|
|
60
|
-
VALIDATION_TIMEOUT: 5000, // 5 seconds
|
|
61
|
-
DEFAULT_SANITIZE: true,
|
|
62
|
-
} as const;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Data transformation constants
|
|
66
|
-
*/
|
|
67
|
-
export const DATA_TRANSFORMATION = {
|
|
68
|
-
MAX_TRANSFORM_TIME: 10000, // 10 seconds
|
|
69
|
-
BATCH_SIZE: 500,
|
|
70
|
-
DEFAULT_NORMALIZE: true,
|
|
71
|
-
MAX_DEPTH: 10, // For nested object transformation
|
|
72
|
-
} as const;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* API adapter constants
|
|
76
|
-
*/
|
|
77
|
-
export const API_ADAPTER = {
|
|
78
|
-
DEFAULT_TIMEOUT: 30000, // 30 seconds
|
|
79
|
-
RETRY_ATTEMPTS: 3,
|
|
80
|
-
RETRY_DELAY: 1000, // 1 second
|
|
81
|
-
MAX_CONCURRENT_REQUESTS: 5,
|
|
82
|
-
DEFAULT_HEADERS: {
|
|
83
|
-
"Content-Type": "application/json",
|
|
84
|
-
},
|
|
85
|
-
} as const;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Background processing constants
|
|
89
|
-
*/
|
|
90
|
-
export const BACKGROUND_PROCESSING = {
|
|
91
|
-
PREFETCH_THRESHOLD: 0.8, // Start prefetching when 80% through current data
|
|
92
|
-
MAX_PREFETCH_QUEUE: 10,
|
|
93
|
-
PREFETCH_DEBOUNCE: 500, // 500ms
|
|
94
|
-
BACKGROUND_SYNC_INTERVAL: 30000, // 30 seconds
|
|
95
|
-
} as const;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Data logging constants
|
|
99
|
-
*/
|
|
100
|
-
export const DATA_LOGGING = {
|
|
101
|
-
PREFIX: "[COLLECTION]",
|
|
102
|
-
LEVELS: {
|
|
103
|
-
ERROR: "ERROR",
|
|
104
|
-
WARN: "WARN",
|
|
105
|
-
INFO: "INFO",
|
|
106
|
-
DEBUG: "DEBUG",
|
|
107
|
-
},
|
|
108
|
-
ENABLE_DEBUG: false, // Set to true for development
|
|
109
|
-
} as const;
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Collection state constants
|
|
113
|
-
*/
|
|
114
|
-
export const COLLECTION_STATE = {
|
|
115
|
-
INITIAL_SIZE: 0,
|
|
116
|
-
INITIAL_PAGE: 1,
|
|
117
|
-
INITIAL_LOADING: false,
|
|
118
|
-
INITIAL_ERROR: null,
|
|
119
|
-
INITIAL_HAS_MORE: true,
|
|
120
|
-
} as const;
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Data event timing constants
|
|
124
|
-
*/
|
|
125
|
-
export const EVENT_TIMING = {
|
|
126
|
-
DEBOUNCE_DELAY: 100, // 100ms
|
|
127
|
-
THROTTLE_DELAY: 16, // ~60fps
|
|
128
|
-
EMIT_TIMEOUT: 5000, // 5 seconds
|
|
129
|
-
MAX_LISTENERS: 100,
|
|
130
|
-
} as const;
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Data aggregation constants
|
|
134
|
-
*/
|
|
135
|
-
export const DATA_AGGREGATION = {
|
|
136
|
-
SUPPORTED_OPERATIONS: [
|
|
137
|
-
"sum",
|
|
138
|
-
"count",
|
|
139
|
-
"avg",
|
|
140
|
-
"min",
|
|
141
|
-
"max",
|
|
142
|
-
"distinct",
|
|
143
|
-
] as const,
|
|
144
|
-
MAX_FIELD_DEPTH: 5,
|
|
145
|
-
AGGREGATION_TIMEOUT: 10000, // 10 seconds
|
|
146
|
-
} as const;
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Data search constants
|
|
150
|
-
*/
|
|
151
|
-
export const DATA_SEARCH = {
|
|
152
|
-
DEFAULT_FIELDS: ["id"],
|
|
153
|
-
MAX_SEARCH_FIELDS: 20,
|
|
154
|
-
SEARCH_DEBOUNCE: 300, // 300ms
|
|
155
|
-
MIN_SEARCH_LENGTH: 1,
|
|
156
|
-
MAX_SEARCH_LENGTH: 1000,
|
|
157
|
-
} as const;
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Collection defaults for initialization
|
|
161
|
-
*/
|
|
162
|
-
export const COLLECTION_DEFAULTS = {
|
|
163
|
-
PAGE_SIZE: DATA_PAGINATION.DEFAULT_PAGE_SIZE,
|
|
164
|
-
CURRENT_PAGE: DATA_PAGINATION.DEFAULT_CURRENT_PAGE,
|
|
165
|
-
CACHE_STRATEGY: DATA_CACHE.DEFAULT_STRATEGY,
|
|
166
|
-
PERSISTENCE_STRATEGY: DATA_PERSISTENCE.DEFAULT_STRATEGY,
|
|
167
|
-
WEB_WORKERS_ENABLED: false,
|
|
168
|
-
VALIDATION_STRICT: DATA_VALIDATION.DEFAULT_STRICT_MODE,
|
|
169
|
-
PREFETCH_ENABLED: false,
|
|
170
|
-
} as const;
|
|
171
|
-
|
|
172
|
-
// NO UI constants: STYLING, DOM, TEMPLATE, SCROLL, INTERSECTION - those belong to List Manager/Component
|
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Event system for mtrl-addons collection (Pure Data Layer)
|
|
3
|
-
*
|
|
4
|
-
* Handles data-focused events with zero UI concerns
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
CollectionItem,
|
|
9
|
-
CollectionObserver,
|
|
10
|
-
CollectionUnsubscribe,
|
|
11
|
-
CollectionEventPayload,
|
|
12
|
-
CollectionDataEvents,
|
|
13
|
-
} from "./types";
|
|
14
|
-
import { DATA_LOGGING, EVENT_TIMING } from "./constants";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Data-focused collection events (NO UI EVENTS)
|
|
18
|
-
*/
|
|
19
|
-
export const CollectionEvents = {
|
|
20
|
-
// Data lifecycle events
|
|
21
|
-
ITEMS_LOADED: "items:loaded" as const,
|
|
22
|
-
ITEMS_ADDED: "items:added" as const,
|
|
23
|
-
ITEMS_UPDATED: "items:updated" as const,
|
|
24
|
-
ITEMS_REMOVED: "items:removed" as const,
|
|
25
|
-
ITEMS_CLEARED: "items:cleared" as const,
|
|
26
|
-
|
|
27
|
-
// Data state events
|
|
28
|
-
LOADING_START: "loading:start" as const,
|
|
29
|
-
LOADING_END: "loading:end" as const,
|
|
30
|
-
ERROR_OCCURRED: "error:occurred" as const,
|
|
31
|
-
|
|
32
|
-
// Data operations events
|
|
33
|
-
CACHE_HIT: "cache:hit" as const,
|
|
34
|
-
CACHE_MISS: "cache:miss" as const,
|
|
35
|
-
CACHE_CLEARED: "cache:cleared" as const,
|
|
36
|
-
SYNC_START: "sync:start" as const,
|
|
37
|
-
SYNC_COMPLETE: "sync:complete" as const,
|
|
38
|
-
|
|
39
|
-
// Background operations events
|
|
40
|
-
PREFETCH_START: "prefetch:start" as const,
|
|
41
|
-
PREFETCH_COMPLETE: "prefetch:complete" as const,
|
|
42
|
-
WORKER_TASK_START: "worker:task:start" as const,
|
|
43
|
-
WORKER_TASK_COMPLETE: "worker:task:complete" as const,
|
|
44
|
-
|
|
45
|
-
// Data validation events
|
|
46
|
-
VALIDATION_START: "validation:start" as const,
|
|
47
|
-
VALIDATION_COMPLETE: "validation:complete" as const,
|
|
48
|
-
VALIDATION_ERROR: "validation:error" as const,
|
|
49
|
-
|
|
50
|
-
// Data transformation events
|
|
51
|
-
TRANSFORM_START: "transform:start" as const,
|
|
52
|
-
TRANSFORM_COMPLETE: "transform:complete" as const,
|
|
53
|
-
TRANSFORM_ERROR: "transform:error" as const,
|
|
54
|
-
|
|
55
|
-
// Data persistence events
|
|
56
|
-
SAVE_START: "save:start" as const,
|
|
57
|
-
SAVE_COMPLETE: "save:complete" as const,
|
|
58
|
-
SAVE_ERROR: "save:error" as const,
|
|
59
|
-
LOAD_START: "load:start" as const,
|
|
60
|
-
LOAD_COMPLETE: "load:complete" as const,
|
|
61
|
-
LOAD_ERROR: "load:error" as const,
|
|
62
|
-
|
|
63
|
-
// NO UI events: render:start, scroll:changed, viewport:changed, etc.
|
|
64
|
-
} as const;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Collection event emitter interface
|
|
68
|
-
*/
|
|
69
|
-
export interface CollectionEventEmitter<
|
|
70
|
-
T extends CollectionItem = CollectionItem
|
|
71
|
-
> {
|
|
72
|
-
subscribe(observer: CollectionObserver<T>): CollectionUnsubscribe;
|
|
73
|
-
emit(event: CollectionDataEvents, data?: any): void;
|
|
74
|
-
removeAllListeners(): void;
|
|
75
|
-
getListenerCount(): number;
|
|
76
|
-
destroy(): void;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Creates a data-focused event emitter for collection
|
|
81
|
-
*/
|
|
82
|
-
export function createCollectionEventEmitter<
|
|
83
|
-
T extends CollectionItem = CollectionItem
|
|
84
|
-
>(): CollectionEventEmitter<T> {
|
|
85
|
-
const listeners = new Map<string, Set<CollectionObserver<T>>>();
|
|
86
|
-
let isDestroyed = false;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Subscribe to data events
|
|
90
|
-
*/
|
|
91
|
-
const subscribe = (
|
|
92
|
-
observer: CollectionObserver<T>
|
|
93
|
-
): CollectionUnsubscribe => {
|
|
94
|
-
if (isDestroyed) {
|
|
95
|
-
console.warn(
|
|
96
|
-
`${DATA_LOGGING.PREFIX} Cannot subscribe to destroyed event emitter`
|
|
97
|
-
);
|
|
98
|
-
return () => {};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Validate observer
|
|
102
|
-
if (typeof observer !== "function") {
|
|
103
|
-
throw new Error("Observer must be a function");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Check listener limit
|
|
107
|
-
const totalListeners = Array.from(listeners.values()).reduce(
|
|
108
|
-
(sum, set) => sum + set.size,
|
|
109
|
-
0
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
if (totalListeners >= EVENT_TIMING.MAX_LISTENERS) {
|
|
113
|
-
console.warn(
|
|
114
|
-
`${DATA_LOGGING.PREFIX} Maximum listeners (${EVENT_TIMING.MAX_LISTENERS}) reached`
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Add observer to all data events
|
|
119
|
-
Object.values(CollectionEvents).forEach((event) => {
|
|
120
|
-
if (!listeners.has(event)) {
|
|
121
|
-
listeners.set(event, new Set());
|
|
122
|
-
}
|
|
123
|
-
listeners.get(event)!.add(observer);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// Return unsubscribe function
|
|
127
|
-
return () => {
|
|
128
|
-
Object.values(CollectionEvents).forEach((event) => {
|
|
129
|
-
const eventListeners = listeners.get(event);
|
|
130
|
-
if (eventListeners) {
|
|
131
|
-
eventListeners.delete(observer);
|
|
132
|
-
if (eventListeners.size === 0) {
|
|
133
|
-
listeners.delete(event);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
};
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Emit data events to subscribers
|
|
142
|
-
*/
|
|
143
|
-
const emit = (event: CollectionDataEvents, data?: any): void => {
|
|
144
|
-
if (isDestroyed) {
|
|
145
|
-
console.warn(
|
|
146
|
-
`${DATA_LOGGING.PREFIX} Cannot emit on destroyed event emitter`
|
|
147
|
-
);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const eventListeners = listeners.get(event);
|
|
152
|
-
if (!eventListeners || eventListeners.size === 0) {
|
|
153
|
-
return; // No listeners for this event
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const payload: CollectionEventPayload<T> = {
|
|
157
|
-
event,
|
|
158
|
-
data,
|
|
159
|
-
timestamp: Date.now(),
|
|
160
|
-
source: "collection",
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// Emit to all listeners with error handling
|
|
164
|
-
eventListeners.forEach((observer) => {
|
|
165
|
-
try {
|
|
166
|
-
// Use timeout to prevent blocking
|
|
167
|
-
const timeoutId = setTimeout(() => {
|
|
168
|
-
observer(payload);
|
|
169
|
-
}, 0);
|
|
170
|
-
|
|
171
|
-
// Clear timeout after emit timeout
|
|
172
|
-
setTimeout(() => {
|
|
173
|
-
clearTimeout(timeoutId);
|
|
174
|
-
}, EVENT_TIMING.EMIT_TIMEOUT);
|
|
175
|
-
} catch (error) {
|
|
176
|
-
console.error(`${DATA_LOGGING.PREFIX} Error in event observer:`, error);
|
|
177
|
-
|
|
178
|
-
// Remove problematic observer
|
|
179
|
-
eventListeners.delete(observer);
|
|
180
|
-
|
|
181
|
-
// Emit error event
|
|
182
|
-
if (event !== CollectionEvents.ERROR_OCCURRED) {
|
|
183
|
-
emit(CollectionEvents.ERROR_OCCURRED, {
|
|
184
|
-
error,
|
|
185
|
-
originalEvent: event,
|
|
186
|
-
observer: observer.name || "anonymous",
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// Debug logging
|
|
193
|
-
if (DATA_LOGGING.ENABLE_DEBUG) {
|
|
194
|
-
console.log(`${DATA_LOGGING.PREFIX} Event emitted:`, {
|
|
195
|
-
event,
|
|
196
|
-
data,
|
|
197
|
-
listenerCount: eventListeners.size,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Remove all listeners
|
|
204
|
-
*/
|
|
205
|
-
const removeAllListeners = (): void => {
|
|
206
|
-
listeners.clear();
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Get total listener count
|
|
211
|
-
*/
|
|
212
|
-
const getListenerCount = (): number => {
|
|
213
|
-
return Array.from(listeners.values()).reduce(
|
|
214
|
-
(sum, set) => sum + set.size,
|
|
215
|
-
0
|
|
216
|
-
);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Destroy the event emitter
|
|
221
|
-
*/
|
|
222
|
-
const destroy = (): void => {
|
|
223
|
-
removeAllListeners();
|
|
224
|
-
isDestroyed = true;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
subscribe,
|
|
229
|
-
emit,
|
|
230
|
-
removeAllListeners,
|
|
231
|
-
getListenerCount,
|
|
232
|
-
destroy,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Event payload factory functions for type safety
|
|
238
|
-
*/
|
|
239
|
-
export const createEventPayload = {
|
|
240
|
-
itemsLoaded: <T extends CollectionItem>(
|
|
241
|
-
items: T[],
|
|
242
|
-
meta?: any
|
|
243
|
-
): CollectionEventPayload<T> => ({
|
|
244
|
-
event: CollectionEvents.ITEMS_LOADED,
|
|
245
|
-
data: { items, meta },
|
|
246
|
-
items,
|
|
247
|
-
timestamp: Date.now(),
|
|
248
|
-
source: "collection",
|
|
249
|
-
}),
|
|
250
|
-
|
|
251
|
-
itemsAdded: <T extends CollectionItem>(
|
|
252
|
-
items: T[],
|
|
253
|
-
indices: number[]
|
|
254
|
-
): CollectionEventPayload<T> => ({
|
|
255
|
-
event: CollectionEvents.ITEMS_ADDED,
|
|
256
|
-
data: { items, indices },
|
|
257
|
-
items,
|
|
258
|
-
timestamp: Date.now(),
|
|
259
|
-
source: "collection",
|
|
260
|
-
}),
|
|
261
|
-
|
|
262
|
-
itemsUpdated: <T extends CollectionItem>(
|
|
263
|
-
items: T[],
|
|
264
|
-
indices: number[]
|
|
265
|
-
): CollectionEventPayload<T> => ({
|
|
266
|
-
event: CollectionEvents.ITEMS_UPDATED,
|
|
267
|
-
data: { items, indices },
|
|
268
|
-
items,
|
|
269
|
-
timestamp: Date.now(),
|
|
270
|
-
source: "collection",
|
|
271
|
-
}),
|
|
272
|
-
|
|
273
|
-
itemsRemoved: <T extends CollectionItem>(
|
|
274
|
-
ids: string[]
|
|
275
|
-
): CollectionEventPayload<T> => ({
|
|
276
|
-
event: CollectionEvents.ITEMS_REMOVED,
|
|
277
|
-
data: { ids },
|
|
278
|
-
timestamp: Date.now(),
|
|
279
|
-
source: "collection",
|
|
280
|
-
}),
|
|
281
|
-
|
|
282
|
-
loadingStart: <T extends CollectionItem>(
|
|
283
|
-
reason: string
|
|
284
|
-
): CollectionEventPayload<T> => ({
|
|
285
|
-
event: CollectionEvents.LOADING_START,
|
|
286
|
-
data: { reason },
|
|
287
|
-
timestamp: Date.now(),
|
|
288
|
-
source: "collection",
|
|
289
|
-
}),
|
|
290
|
-
|
|
291
|
-
loadingEnd: <T extends CollectionItem>(
|
|
292
|
-
reason: string
|
|
293
|
-
): CollectionEventPayload<T> => ({
|
|
294
|
-
event: CollectionEvents.LOADING_END,
|
|
295
|
-
data: { reason },
|
|
296
|
-
timestamp: Date.now(),
|
|
297
|
-
source: "collection",
|
|
298
|
-
}),
|
|
299
|
-
|
|
300
|
-
errorOccurred: <T extends CollectionItem>(
|
|
301
|
-
error: Error,
|
|
302
|
-
context?: any
|
|
303
|
-
): CollectionEventPayload<T> => ({
|
|
304
|
-
event: CollectionEvents.ERROR_OCCURRED,
|
|
305
|
-
data: { error, context },
|
|
306
|
-
error,
|
|
307
|
-
timestamp: Date.now(),
|
|
308
|
-
source: "collection",
|
|
309
|
-
}),
|
|
310
|
-
|
|
311
|
-
cacheHit: <T extends CollectionItem>(
|
|
312
|
-
key: string,
|
|
313
|
-
size: number
|
|
314
|
-
): CollectionEventPayload<T> => ({
|
|
315
|
-
event: CollectionEvents.CACHE_HIT,
|
|
316
|
-
data: { key, size },
|
|
317
|
-
timestamp: Date.now(),
|
|
318
|
-
source: "collection",
|
|
319
|
-
}),
|
|
320
|
-
|
|
321
|
-
cacheMiss: <T extends CollectionItem>(
|
|
322
|
-
key: string
|
|
323
|
-
): CollectionEventPayload<T> => ({
|
|
324
|
-
event: CollectionEvents.CACHE_MISS,
|
|
325
|
-
data: { key },
|
|
326
|
-
timestamp: Date.now(),
|
|
327
|
-
source: "collection",
|
|
328
|
-
}),
|
|
329
|
-
|
|
330
|
-
workerTaskStart: <T extends CollectionItem>(
|
|
331
|
-
operation: string,
|
|
332
|
-
itemCount: number
|
|
333
|
-
): CollectionEventPayload<T> => ({
|
|
334
|
-
event: CollectionEvents.WORKER_TASK_START,
|
|
335
|
-
data: { operation, itemCount },
|
|
336
|
-
timestamp: Date.now(),
|
|
337
|
-
source: "collection",
|
|
338
|
-
}),
|
|
339
|
-
|
|
340
|
-
workerTaskComplete: <T extends CollectionItem>(
|
|
341
|
-
operation: string,
|
|
342
|
-
duration: number,
|
|
343
|
-
result?: any
|
|
344
|
-
): CollectionEventPayload<T> => ({
|
|
345
|
-
event: CollectionEvents.WORKER_TASK_COMPLETE,
|
|
346
|
-
data: { operation, duration, result },
|
|
347
|
-
timestamp: Date.now(),
|
|
348
|
-
source: "collection",
|
|
349
|
-
}),
|
|
350
|
-
|
|
351
|
-
prefetchStart: <T extends CollectionItem>(
|
|
352
|
-
pages: number[]
|
|
353
|
-
): CollectionEventPayload<T> => ({
|
|
354
|
-
event: CollectionEvents.PREFETCH_START,
|
|
355
|
-
data: { pages },
|
|
356
|
-
timestamp: Date.now(),
|
|
357
|
-
source: "collection",
|
|
358
|
-
}),
|
|
359
|
-
|
|
360
|
-
prefetchComplete: <T extends CollectionItem>(
|
|
361
|
-
items: T[],
|
|
362
|
-
pages: number[]
|
|
363
|
-
): CollectionEventPayload<T> => ({
|
|
364
|
-
event: CollectionEvents.PREFETCH_COMPLETE,
|
|
365
|
-
data: { items, pages },
|
|
366
|
-
items,
|
|
367
|
-
timestamp: Date.now(),
|
|
368
|
-
source: "collection",
|
|
369
|
-
}),
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Event utilities for debugging and monitoring
|
|
374
|
-
*/
|
|
375
|
-
export const eventUtils = {
|
|
376
|
-
/**
|
|
377
|
-
* Log event for debugging
|
|
378
|
-
*/
|
|
379
|
-
logEvent: <T extends CollectionItem>(
|
|
380
|
-
payload: CollectionEventPayload<T>
|
|
381
|
-
): void => {
|
|
382
|
-
if (DATA_LOGGING.ENABLE_DEBUG) {
|
|
383
|
-
console.log(`${DATA_LOGGING.PREFIX} ${payload.event}:`, {
|
|
384
|
-
data: payload.data,
|
|
385
|
-
timestamp: new Date(payload.timestamp).toISOString(),
|
|
386
|
-
source: payload.source,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Check if event is a data lifecycle event
|
|
393
|
-
*/
|
|
394
|
-
isDataLifecycleEvent: (event: string): boolean => {
|
|
395
|
-
return [
|
|
396
|
-
CollectionEvents.ITEMS_LOADED,
|
|
397
|
-
CollectionEvents.ITEMS_ADDED,
|
|
398
|
-
CollectionEvents.ITEMS_UPDATED,
|
|
399
|
-
CollectionEvents.ITEMS_REMOVED,
|
|
400
|
-
CollectionEvents.ITEMS_CLEARED,
|
|
401
|
-
].includes(event as any);
|
|
402
|
-
},
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Check if event is a background operation event
|
|
406
|
-
*/
|
|
407
|
-
isBackgroundEvent: (event: string): boolean => {
|
|
408
|
-
return [
|
|
409
|
-
CollectionEvents.PREFETCH_START,
|
|
410
|
-
CollectionEvents.PREFETCH_COMPLETE,
|
|
411
|
-
CollectionEvents.WORKER_TASK_START,
|
|
412
|
-
CollectionEvents.WORKER_TASK_COMPLETE,
|
|
413
|
-
].includes(event as any);
|
|
414
|
-
},
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Check if event is an error event
|
|
418
|
-
*/
|
|
419
|
-
isErrorEvent: (event: string): boolean => {
|
|
420
|
-
return [
|
|
421
|
-
CollectionEvents.ERROR_OCCURRED,
|
|
422
|
-
CollectionEvents.VALIDATION_ERROR,
|
|
423
|
-
CollectionEvents.TRANSFORM_ERROR,
|
|
424
|
-
CollectionEvents.SAVE_ERROR,
|
|
425
|
-
CollectionEvents.LOAD_ERROR,
|
|
426
|
-
].includes(event as any);
|
|
427
|
-
},
|
|
428
|
-
};
|