@hkdigital/lib-core 0.4.23 → 0.4.25
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/logging/internal/adapters/pino.d.ts +7 -3
- package/dist/logging/internal/adapters/pino.js +200 -67
- package/dist/logging/internal/transports/pretty-transport.d.ts +17 -0
- package/dist/logging/internal/transports/pretty-transport.js +104 -0
- package/dist/logging/internal/transports/test-transport.d.ts +19 -0
- package/dist/logging/internal/transports/test-transport.js +79 -0
- package/dist/network/loaders/audio/AudioScene.svelte.js +2 -2
- package/dist/network/loaders/image/ImageScene.svelte.js +18 -28
- package/dist/network/states/NetworkLoader.svelte.d.ts +7 -1
- package/dist/network/states/NetworkLoader.svelte.js +17 -4
- package/dist/services/README.md +23 -0
- package/dist/services/service-base/ServiceBase.d.ts +12 -8
- package/dist/services/service-base/ServiceBase.js +8 -6
- package/dist/state/classes.d.ts +0 -2
- package/dist/state/classes.js +0 -2
- package/dist/state/{classes → machines}/finite-state-machine/FiniteStateMachine.svelte.d.ts +13 -7
- package/dist/state/machines/finite-state-machine/FiniteStateMachine.svelte.js +181 -0
- package/dist/state/machines/finite-state-machine/README.md +547 -0
- package/dist/state/machines/finite-state-machine/constants.d.ts +13 -0
- package/dist/state/machines/finite-state-machine/constants.js +15 -0
- package/dist/state/{classes → machines}/finite-state-machine/index.d.ts +2 -1
- package/dist/state/{classes → machines}/finite-state-machine/index.js +2 -1
- package/dist/state/machines/finite-state-machine/typedef.d.ts +29 -0
- package/dist/state/machines/finite-state-machine/typedef.js +28 -0
- package/dist/state/machines/loading-state-machine/LoadingStateMachine.svelte.d.ts +22 -0
- package/dist/state/{classes → machines}/loading-state-machine/LoadingStateMachine.svelte.js +34 -29
- package/dist/state/machines/loading-state-machine/README.md +592 -0
- package/dist/state/{classes → machines}/loading-state-machine/constants.d.ts +2 -0
- package/dist/state/{classes → machines}/loading-state-machine/constants.js +2 -0
- package/dist/state/machines/typedef.d.ts +1 -0
- package/dist/state/machines/typedef.js +1 -0
- package/dist/state/machines.d.ts +2 -0
- package/dist/state/machines.js +2 -0
- package/dist/state/typedef.d.ts +1 -0
- package/dist/state/typedef.js +1 -0
- package/dist/ui/components/game-box/README.md +245 -0
- package/package.json +1 -1
- package/dist/logging/internal/adapters/pino.js__ +0 -260
- package/dist/state/classes/finite-state-machine/FiniteStateMachine.svelte.js +0 -133
- package/dist/state/classes/loading-state-machine/LoadingStateMachine.svelte.d.ts +0 -12
- /package/dist/state/{classes → machines}/loading-state-machine/index.d.ts +0 -0
- /package/dist/state/{classes → machines}/loading-state-machine/index.js +0 -0
|
@@ -5,6 +5,7 @@ export const STATE_LOADED = 'loaded';
|
|
|
5
5
|
|
|
6
6
|
export const STATE_CANCELLED = 'cancelled';
|
|
7
7
|
export const STATE_ERROR = 'error';
|
|
8
|
+
export const STATE_TIMEOUT = 'timeout';
|
|
8
9
|
|
|
9
10
|
// > Signals
|
|
10
11
|
|
|
@@ -14,3 +15,4 @@ export const CANCEL = 'cancel';
|
|
|
14
15
|
export const ERROR = 'error';
|
|
15
16
|
export const LOADED = 'loaded';
|
|
16
17
|
export const UNLOAD = 'unload';
|
|
18
|
+
export const TIMEOUT = 'timeout';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./finite-state-machine/typedef.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './finite-state-machine/typedef.js';
|
package/dist/state/typedef.d.ts
CHANGED
package/dist/state/typedef.js
CHANGED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# GameBox Component
|
|
2
|
+
|
|
3
|
+
A responsive container component designed for creating games and fullscreen
|
|
4
|
+
applications with adaptive scaling, orientation handling, and mobile PWA
|
|
5
|
+
support.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Responsive Layout**: Automatically adapts to landscape and portrait
|
|
10
|
+
orientations
|
|
11
|
+
- **Aspect Ratio Control**: Configurable aspect ratios for different
|
|
12
|
+
orientations
|
|
13
|
+
- **Mobile PWA Support**: Handles iOS Safari PWA quirks and fullscreen modes
|
|
14
|
+
- **Dynamic Scaling**: Optional design system scaling based on container size
|
|
15
|
+
- **Fullscreen Management**: Built-in fullscreen request handling
|
|
16
|
+
- **Device Detection**: Automatic mobile and OS detection
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
```svelte
|
|
21
|
+
<script>
|
|
22
|
+
import { GameBox } from '$lib/ui/components/game-box/index.js';
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<GameBox
|
|
26
|
+
aspectOnLandscape={16/9}
|
|
27
|
+
aspectOnPortrait={9/16}
|
|
28
|
+
snippetLandscape={landscapeContent}
|
|
29
|
+
snippetPortrait={portraitContent}
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
{#snippet landscapeContent({ gameWidth, gameHeight, isMobile })}
|
|
33
|
+
<div>Game content for landscape - {gameWidth}x{gameHeight}</div>
|
|
34
|
+
{/snippet}
|
|
35
|
+
|
|
36
|
+
{#snippet portraitContent({ gameWidth, gameHeight, isMobile })}
|
|
37
|
+
<div>Game content for portrait - {gameWidth}x{gameHeight}</div>
|
|
38
|
+
{/snippet}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
### Styling Props
|
|
44
|
+
- `base?: string` - Base CSS classes
|
|
45
|
+
- `bg?: string` - Background CSS classes
|
|
46
|
+
- `classes?: string` - Additional CSS classes
|
|
47
|
+
- `style?: string` - Inline styles
|
|
48
|
+
|
|
49
|
+
### Layout Props
|
|
50
|
+
- `aspectOnLandscape?: number` - Aspect ratio for landscape mode (e.g., 16/9)
|
|
51
|
+
- `aspectOnPortrait?: number` - Aspect ratio for portrait mode (e.g., 9/16)
|
|
52
|
+
- `marginLeft?: number` - Left margin in pixels (default: 0)
|
|
53
|
+
- `marginRight?: number` - Right margin in pixels (default: 0)
|
|
54
|
+
- `marginTop?: number` - Top margin in pixels (default: 0)
|
|
55
|
+
- `marginBottom?: number` - Bottom margin in pixels (default: 0)
|
|
56
|
+
- `center?: boolean` - Center the game box in viewport
|
|
57
|
+
|
|
58
|
+
### Scaling Props
|
|
59
|
+
- `enableScaling?: boolean` - Enable design system scaling (default: false)
|
|
60
|
+
- `designLandscape?: {width: number, height: number}` - Design dimensions
|
|
61
|
+
for landscape (default: {width: 1920, height: 1080})
|
|
62
|
+
- `designPortrait?: {width: number, height: number}` - Design dimensions
|
|
63
|
+
for portrait (default: {width: 1920, height: 1080})
|
|
64
|
+
- `clamping?: object` - Scaling limits for different elements
|
|
65
|
+
|
|
66
|
+
### Snippet Props
|
|
67
|
+
- `snippetLandscape?: Snippet` - Content for landscape orientation
|
|
68
|
+
- `snippetPortrait?: Snippet` - Content for portrait orientation
|
|
69
|
+
- `snippetRequireFullscreen?: Snippet` - Content when fullscreen is required
|
|
70
|
+
- `snippetInstallOnHomeScreen?: Snippet` - Content for home screen install
|
|
71
|
+
prompt
|
|
72
|
+
|
|
73
|
+
## Snippet Parameters
|
|
74
|
+
|
|
75
|
+
All snippets receive these parameters:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
{
|
|
79
|
+
isMobile: boolean, // Is running on mobile device
|
|
80
|
+
os: 'Android'|'iOS', // Operating system
|
|
81
|
+
isFullscreen: boolean, // Is in fullscreen mode
|
|
82
|
+
isDevMode: boolean, // Is in development mode
|
|
83
|
+
requestDevmode: function, // Function to enable dev mode
|
|
84
|
+
requestFullscreen: function, // Function to request fullscreen
|
|
85
|
+
gameWidth: number, // Calculated game width
|
|
86
|
+
gameHeight: number // Calculated game height
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Examples
|
|
91
|
+
|
|
92
|
+
### Simple Game Container
|
|
93
|
+
|
|
94
|
+
```svelte
|
|
95
|
+
<GameBox
|
|
96
|
+
aspectOnLandscape={16/9}
|
|
97
|
+
aspectOnPortrait={9/16}
|
|
98
|
+
center={true}
|
|
99
|
+
snippetLandscape={gameContent}
|
|
100
|
+
snippetPortrait={gameContent}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
{#snippet gameContent({ gameWidth, gameHeight })}
|
|
104
|
+
<div class="game-area bg-surface-900 rounded-lg p-20up">
|
|
105
|
+
<h1 class="type-heading-h1 text-center mb-20up">My Game</h1>
|
|
106
|
+
<div class="game-content" style="width: 100%; height: 100%;">
|
|
107
|
+
Game content goes here
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
{/snippet}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### With Fullscreen Requirement
|
|
114
|
+
|
|
115
|
+
```svelte
|
|
116
|
+
<GameBox
|
|
117
|
+
aspectOnLandscape={21/9}
|
|
118
|
+
center={true}
|
|
119
|
+
snippetLandscape={gameContent}
|
|
120
|
+
snippetRequireFullscreen={fullscreenPrompt}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
{#snippet gameContent({ gameWidth, gameHeight, isFullscreen })}
|
|
124
|
+
<div class="cinematic-game">
|
|
125
|
+
Ultra-wide cinematic game experience
|
|
126
|
+
</div>
|
|
127
|
+
{/snippet}
|
|
128
|
+
|
|
129
|
+
{#snippet fullscreenPrompt({ requestFullscreen })}
|
|
130
|
+
<div class="fullscreen-prompt text-center">
|
|
131
|
+
<h2 class="type-heading-h2 mb-16bt">Fullscreen Required</h2>
|
|
132
|
+
<p class="type-base-md mb-20bt">
|
|
133
|
+
This game requires fullscreen mode for the best experience.
|
|
134
|
+
</p>
|
|
135
|
+
<button class="btn-primary" onclick={requestFullscreen}>
|
|
136
|
+
Enter Fullscreen
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
{/snippet}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### With Scaling Enabled
|
|
143
|
+
|
|
144
|
+
```svelte
|
|
145
|
+
<GameBox
|
|
146
|
+
aspectOnLandscape={16/9}
|
|
147
|
+
enableScaling={true}
|
|
148
|
+
designLandscape={{ width: 1920, height: 1080 }}
|
|
149
|
+
clamping={{
|
|
150
|
+
ui: { min: 0.5, max: 1.5 },
|
|
151
|
+
textBase: { min: 0.8, max: 1.2 },
|
|
152
|
+
textHeading: { min: 0.75, max: 2 },
|
|
153
|
+
textUi: { min: 0.6, max: 1.1 }
|
|
154
|
+
}}
|
|
155
|
+
snippetLandscape={scaledContent}
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
{#snippet scaledContent()}
|
|
159
|
+
<div class="game-ui">
|
|
160
|
+
<!-- UI elements will automatically scale based on container size -->
|
|
161
|
+
<div class="hud">
|
|
162
|
+
<span class="type-ui-sm">Score: 1000</span>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
{/snippet}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Mobile-Specific Handling
|
|
169
|
+
|
|
170
|
+
```svelte
|
|
171
|
+
<GameBox
|
|
172
|
+
aspectOnLandscape={16/9}
|
|
173
|
+
aspectOnPortrait={9/16}
|
|
174
|
+
snippetLandscape={landscapeGame}
|
|
175
|
+
snippetPortrait={portraitGame}
|
|
176
|
+
snippetInstallOnHomeScreen={installPrompt}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
{#snippet landscapeGame({ isMobile, os })}
|
|
180
|
+
<div class="landscape-game">
|
|
181
|
+
{#if isMobile}
|
|
182
|
+
<div class="mobile-controls">
|
|
183
|
+
<!-- Touch controls for mobile -->
|
|
184
|
+
</div>
|
|
185
|
+
{:else}
|
|
186
|
+
<div class="desktop-controls">
|
|
187
|
+
<!-- Keyboard/mouse controls for desktop -->
|
|
188
|
+
</div>
|
|
189
|
+
{/if}
|
|
190
|
+
</div>
|
|
191
|
+
{/snippet}
|
|
192
|
+
|
|
193
|
+
{#snippet portraitGame({ isMobile })}
|
|
194
|
+
<div class="portrait-game">
|
|
195
|
+
<!-- Optimized for portrait mobile gameplay -->
|
|
196
|
+
</div>
|
|
197
|
+
{/snippet}
|
|
198
|
+
|
|
199
|
+
{#snippet installPrompt({ os })}
|
|
200
|
+
<div class="install-prompt text-center">
|
|
201
|
+
<h2 class="type-heading-h2 mb-16bt">Install App</h2>
|
|
202
|
+
<p class="type-base-md mb-20bt">
|
|
203
|
+
{#if os === 'iOS'}
|
|
204
|
+
Tap the share button and select "Add to Home Screen"
|
|
205
|
+
{:else}
|
|
206
|
+
Install this app for the best gaming experience
|
|
207
|
+
{/if}
|
|
208
|
+
</p>
|
|
209
|
+
</div>
|
|
210
|
+
{/snippet}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## CSS Custom Properties
|
|
214
|
+
|
|
215
|
+
The component exposes these CSS custom properties:
|
|
216
|
+
|
|
217
|
+
- `--game-width`: Current game width in pixels
|
|
218
|
+
- `--game-height`: Current game height in pixels
|
|
219
|
+
|
|
220
|
+
These can be used in your CSS for responsive styling:
|
|
221
|
+
|
|
222
|
+
```css
|
|
223
|
+
.game-element {
|
|
224
|
+
width: calc(var(--game-width) * 0.5);
|
|
225
|
+
height: calc(var(--game-height) * 0.2);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Mobile PWA Considerations
|
|
230
|
+
|
|
231
|
+
The component includes special handling for mobile PWAs:
|
|
232
|
+
|
|
233
|
+
- **iOS Safari PWA**: Handles viewport quirks and orientation changes
|
|
234
|
+
- **Fullscreen Detection**: Properly detects PWA fullscreen mode
|
|
235
|
+
- **Screen Orientation**: Listens for orientation changes and updates layout
|
|
236
|
+
- **No Scroll**: Automatically prevents scrolling when active
|
|
237
|
+
|
|
238
|
+
## Development Mode
|
|
239
|
+
|
|
240
|
+
The component automatically enables development mode when:
|
|
241
|
+
- Running on `localhost`
|
|
242
|
+
- Called via `requestDevmode()` function
|
|
243
|
+
|
|
244
|
+
In development mode, fullscreen and installation requirements are bypassed
|
|
245
|
+
for easier testing.
|
package/package.json
CHANGED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pino adapter for server-side logging
|
|
3
|
-
*/
|
|
4
|
-
import pino from 'pino';
|
|
5
|
-
import { dev } from '$app/environment';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
detectErrorMeta,
|
|
9
|
-
findRelevantFrameIndex,
|
|
10
|
-
formatErrorDisplay
|
|
11
|
-
} from './formatting.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Pino adapter that bridges Logger events to pino
|
|
15
|
-
*/
|
|
16
|
-
export class PinoAdapter {
|
|
17
|
-
#projectRoot = null;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create a new PinoAdapter
|
|
21
|
-
*
|
|
22
|
-
* @param {Object} [options] - Pino configuration options
|
|
23
|
-
*/
|
|
24
|
-
constructor(options = {}) {
|
|
25
|
-
// Determine project root once for stack trace cleaning
|
|
26
|
-
this.#projectRoot = import.meta.env.VITE_PROJECT_ROOT || process.cwd();
|
|
27
|
-
const baseOptions = {
|
|
28
|
-
serializers: {
|
|
29
|
-
errors: (err) => {
|
|
30
|
-
|
|
31
|
-
/** @type {import('./typedef').ErrorSummary[]} */
|
|
32
|
-
const chain = [];
|
|
33
|
-
let loggedAt = null;
|
|
34
|
-
|
|
35
|
-
let current = err;
|
|
36
|
-
let isFirst = true;
|
|
37
|
-
|
|
38
|
-
while (current && current instanceof Error) {
|
|
39
|
-
// Check if this is the first error and it's a LoggerError - extract logging context
|
|
40
|
-
if (isFirst && current.name === 'LoggerError') {
|
|
41
|
-
if (current.stack) {
|
|
42
|
-
const cleanedStackString = this.#cleanStackTrace(current.stack);
|
|
43
|
-
const cleanedStackArray = cleanedStackString
|
|
44
|
-
.split('\n')
|
|
45
|
-
.map((line) => line.trim())
|
|
46
|
-
.filter(
|
|
47
|
-
(line) =>
|
|
48
|
-
line && line !== current.name + ': ' + current.message
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
// For LoggerError, we know it's a logger.error call, so find the relevant frame
|
|
52
|
-
const loggerErrorIndex = cleanedStackArray.findIndex(frame =>
|
|
53
|
-
(frame.includes('Logger.error') && frame.includes('logger/Logger.js')) ||
|
|
54
|
-
(frame.includes('error@') && frame.includes('logger/Logger.js'))
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
if (loggerErrorIndex >= 0 && loggerErrorIndex + 1 < cleanedStackArray.length) {
|
|
58
|
-
const relevantFrame = cleanedStackArray[loggerErrorIndex + 1];
|
|
59
|
-
|
|
60
|
-
// Extract function name from the relevant frame
|
|
61
|
-
// const functionName = parseFunctionName(relevantFrame);
|
|
62
|
-
|
|
63
|
-
// const errorType = functionName ? `logger.error in ${functionName}` : 'logger.error';
|
|
64
|
-
loggedAt = relevantFrame.slice(3); // remove "at "
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Skip the LoggerError and move to the actual error
|
|
69
|
-
current = current.cause;
|
|
70
|
-
isFirst = false;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
/** @type {import('./typedef').ErrorSummary} */
|
|
74
|
-
const serialized = {
|
|
75
|
-
name: current.name,
|
|
76
|
-
message: current.message
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// Add error metadata for structured logging and terminal display
|
|
80
|
-
if (current.stack) {
|
|
81
|
-
// Convert cleaned stack string to array format expected by formatting functions
|
|
82
|
-
const cleanedStackString = this.#cleanStackTrace(current.stack);
|
|
83
|
-
const cleanedStackArray = cleanedStackString
|
|
84
|
-
.split('\n')
|
|
85
|
-
.map((line) => line.trim())
|
|
86
|
-
.filter(
|
|
87
|
-
(line) =>
|
|
88
|
-
line && line !== current.name + ': ' + current.message
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
const errorMeta = detectErrorMeta(current, cleanedStackArray);
|
|
92
|
-
const relevantFrameIndex = findRelevantFrameIndex(
|
|
93
|
-
current,
|
|
94
|
-
cleanedStackArray
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
serialized.meta = errorMeta;
|
|
98
|
-
serialized.errorType = formatErrorDisplay(errorMeta);
|
|
99
|
-
|
|
100
|
-
// Include stack frames for terminal display
|
|
101
|
-
serialized.stackFrames = cleanedStackArray
|
|
102
|
-
.slice(0, 9)
|
|
103
|
-
.map((frame, index) => {
|
|
104
|
-
const marker = index === relevantFrameIndex ? '→' : ' ';
|
|
105
|
-
|
|
106
|
-
return `${marker} ${frame}`;
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Include HttpError-specific properties
|
|
111
|
-
const httpError = /** @type {import('$lib/network/errors.js').HttpError} */ (current);
|
|
112
|
-
if (httpError.status !== undefined) {
|
|
113
|
-
serialized.status = httpError.status;
|
|
114
|
-
}
|
|
115
|
-
if (httpError.details !== undefined) {
|
|
116
|
-
serialized.details = httpError.details;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
chain.push(serialized);
|
|
120
|
-
current = current.cause;
|
|
121
|
-
isFirst = false;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return loggedAt ? { chain, loggedAt } : chain;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
// Add error handling for missing pino-pretty in dev
|
|
130
|
-
if ( dev) {
|
|
131
|
-
const devOptions = {
|
|
132
|
-
level: 'debug',
|
|
133
|
-
transport: {
|
|
134
|
-
target: 'pino-pretty',
|
|
135
|
-
options: {
|
|
136
|
-
colorize: true,
|
|
137
|
-
ignore: 'hostname,pid'
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
this.pino = pino({ ...baseOptions, ...devOptions, ...options });
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (
|
|
146
|
-
error.message.includes('Cannot find module') &&
|
|
147
|
-
error.message.includes('pino-pretty')
|
|
148
|
-
) {
|
|
149
|
-
const errorMessage = `
|
|
150
|
-
╭─────────────────────────────────────────────────────────────╮
|
|
151
|
-
│ Missing Dependency │
|
|
152
|
-
├─────────────────────────────────────────────────────────────┤
|
|
153
|
-
│ 'pino-pretty' is required for development logging │
|
|
154
|
-
│ Install it with: pnpm add -D pino-pretty │
|
|
155
|
-
╰─────────────────────────────────────────────────────────────╯`;
|
|
156
|
-
console.error(errorMessage);
|
|
157
|
-
throw new Error('pino-pretty is required for development mode');
|
|
158
|
-
}
|
|
159
|
-
throw error;
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
this.pino = pino({ ...baseOptions, ...options });
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Clean stack trace by removing project root path and simplifying node_modules
|
|
168
|
-
*
|
|
169
|
-
* @param {string} stack - Original stack trace
|
|
170
|
-
* @returns {string} Cleaned stack trace
|
|
171
|
-
*/
|
|
172
|
-
#cleanStackTrace(stack) {
|
|
173
|
-
if (!stack || !this.#projectRoot) {
|
|
174
|
-
return stack;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
let cleaned = stack;
|
|
178
|
-
|
|
179
|
-
// Escape special regex characters in the project root path
|
|
180
|
-
const escapedRoot = this.#projectRoot.replace(
|
|
181
|
-
/[.*+?^${}()|[\]\\]/g,
|
|
182
|
-
'\\$&'
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
// Replace project root path with relative path, handling file:// protocol
|
|
186
|
-
// Match both regular paths and file:// URLs
|
|
187
|
-
const rootRegex = new RegExp(
|
|
188
|
-
`(\\s+at\\s+.*\\()(file://)?${escapedRoot}[\\/\\\\]`,
|
|
189
|
-
'g'
|
|
190
|
-
);
|
|
191
|
-
cleaned = cleaned.replace(rootRegex, '$1');
|
|
192
|
-
|
|
193
|
-
// Simplify pnpm paths: node_modules/.pnpm/package@version_deps/node_modules/package
|
|
194
|
-
// becomes: node_modules/package
|
|
195
|
-
const pnpmRegex =
|
|
196
|
-
/node_modules\/\.pnpm\/([^@\/]+)@[^\/]+\/node_modules\/\1/g;
|
|
197
|
-
cleaned = cleaned.replace(pnpmRegex, 'node_modules/$1');
|
|
198
|
-
|
|
199
|
-
// Also handle cases where the package name might be different in the final path
|
|
200
|
-
const pnpmRegex2 = /node_modules\/\.pnpm\/[^\/]+\/node_modules\/([^\/]+)/g;
|
|
201
|
-
cleaned = cleaned.replace(pnpmRegex2, 'node_modules/$1');
|
|
202
|
-
|
|
203
|
-
return cleaned;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Handle log events from Logger
|
|
208
|
-
*
|
|
209
|
-
* @param {Object} logEvent - Log event from Logger
|
|
210
|
-
*/
|
|
211
|
-
handleLog(logEvent) {
|
|
212
|
-
const { level, message, details, source, timestamp } = logEvent;
|
|
213
|
-
|
|
214
|
-
const logData = {
|
|
215
|
-
source,
|
|
216
|
-
timestamp
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Check if details contains an error and promote it to err property for pino serializer
|
|
220
|
-
if (details) {
|
|
221
|
-
if (details instanceof Error) {
|
|
222
|
-
// details is directly an error
|
|
223
|
-
logData.err = details;
|
|
224
|
-
} else if (details.error instanceof Error) {
|
|
225
|
-
// details has an error property
|
|
226
|
-
logData.err = details.error;
|
|
227
|
-
// Include other details except the error
|
|
228
|
-
// eslint-disable-next-line no-unused-vars
|
|
229
|
-
const { error, ...otherDetails } = details;
|
|
230
|
-
if (Object.keys(otherDetails).length > 0) {
|
|
231
|
-
logData.details = otherDetails;
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
// No error found in details, include all details
|
|
235
|
-
logData.details = details;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Check if we have loggedAt info from the serializer
|
|
240
|
-
if (logData.err && typeof logData.err === 'object' && logData.err.loggedAt) {
|
|
241
|
-
logData.loggedAt = logData.err.loggedAt;
|
|
242
|
-
logData.err = logData.err.chain;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
this.pino[level](logData, message);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Create a child logger with additional context
|
|
250
|
-
*
|
|
251
|
-
* @param {Object} context - Additional context data
|
|
252
|
-
* @returns {PinoAdapter} New adapter instance with context
|
|
253
|
-
*/
|
|
254
|
-
child(context) {
|
|
255
|
-
const childPino = this.pino.child(context);
|
|
256
|
-
const adapter = new PinoAdapter();
|
|
257
|
-
adapter.pino = childPino;
|
|
258
|
-
return adapter;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Initial code borrowed from:
|
|
3
|
-
*
|
|
4
|
-
* @see {@link https://runed.dev/docs/utilities/finite-state-machine}
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Check if the value is valid meta data
|
|
9
|
-
*
|
|
10
|
-
* @param {any} meta
|
|
11
|
-
*/
|
|
12
|
-
export function isLifecycleFnMeta(meta) {
|
|
13
|
-
return (
|
|
14
|
-
!!meta &&
|
|
15
|
-
typeof meta === 'object' &&
|
|
16
|
-
'to' in meta &&
|
|
17
|
-
'from' in meta &&
|
|
18
|
-
'event' in meta &&
|
|
19
|
-
'args' in meta
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Defines a Finite State Machine
|
|
25
|
-
*/
|
|
26
|
-
export default class FiniteStateMachine {
|
|
27
|
-
#current = $state();
|
|
28
|
-
states;
|
|
29
|
-
#timeout = {};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Constructor
|
|
33
|
-
*
|
|
34
|
-
* @param {string} initial
|
|
35
|
-
* @param {{ [key: string]: { [key: string]: (string|((...args: any[])=>void)) } }} states
|
|
36
|
-
*/
|
|
37
|
-
constructor(initial, states) {
|
|
38
|
-
this.#current = initial;
|
|
39
|
-
this.states = states;
|
|
40
|
-
|
|
41
|
-
// synthetically trigger _enter for the initial state.
|
|
42
|
-
this.#dispatch('_enter', {
|
|
43
|
-
from: null,
|
|
44
|
-
to: initial,
|
|
45
|
-
event: null,
|
|
46
|
-
args: []
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Transition to new state
|
|
52
|
-
*
|
|
53
|
-
* @param {string} newState
|
|
54
|
-
* @param {string} event
|
|
55
|
-
* @param {any[]} [args]
|
|
56
|
-
*/
|
|
57
|
-
#transition(newState, event, args) {
|
|
58
|
-
const metadata = { from: this.#current, to: newState, event, args };
|
|
59
|
-
this.#dispatch('_exit', metadata);
|
|
60
|
-
this.#current = newState;
|
|
61
|
-
this.#dispatch('_enter', metadata);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Dispatch an event
|
|
66
|
-
*
|
|
67
|
-
* @param {string} event
|
|
68
|
-
* @param {any[]} args
|
|
69
|
-
*/
|
|
70
|
-
#dispatch(event, ...args) {
|
|
71
|
-
const action =
|
|
72
|
-
this.states[this.#current]?.[event] ?? this.states['*']?.[event];
|
|
73
|
-
if (action instanceof Function) {
|
|
74
|
-
if (event === '_enter' || event === '_exit') {
|
|
75
|
-
if (isLifecycleFnMeta(args[0])) {
|
|
76
|
-
action(args[0]);
|
|
77
|
-
} else {
|
|
78
|
-
console.warn(
|
|
79
|
-
'Invalid metadata passed to lifecycle function of the FSM.'
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
return action(...args);
|
|
84
|
-
}
|
|
85
|
-
} else if (typeof action === 'string') {
|
|
86
|
-
return action;
|
|
87
|
-
} else if (event !== '_enter' && event !== '_exit') {
|
|
88
|
-
console.warn(
|
|
89
|
-
'No action defined for event',
|
|
90
|
-
event,
|
|
91
|
-
'in state',
|
|
92
|
-
this.#current
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Triggers a new event and returns the new state.
|
|
98
|
-
*
|
|
99
|
-
* @param {string} event
|
|
100
|
-
* @param {any[]} args
|
|
101
|
-
*/
|
|
102
|
-
send(event, ...args) {
|
|
103
|
-
const newState = this.#dispatch(event, ...args);
|
|
104
|
-
if (newState && newState !== this.#current) {
|
|
105
|
-
this.#transition(newState, event, args);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return this.#current;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Debounces the triggering of an event.
|
|
112
|
-
*
|
|
113
|
-
* @param {number} wait
|
|
114
|
-
* @param {string} event
|
|
115
|
-
* @param {any[]} args
|
|
116
|
-
*/
|
|
117
|
-
async debounce(wait = 500, event, ...args) {
|
|
118
|
-
if (this.#timeout[event]) {
|
|
119
|
-
clearTimeout(this.#timeout[event]);
|
|
120
|
-
}
|
|
121
|
-
return new Promise((resolve) => {
|
|
122
|
-
this.#timeout[event] = setTimeout(() => {
|
|
123
|
-
delete this.#timeout[event];
|
|
124
|
-
resolve(this.send(event, ...args));
|
|
125
|
-
}, wait);
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** The current state. */
|
|
130
|
-
get current() {
|
|
131
|
-
return this.#current;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* extends FiniteStateMachine<StatesT, EventsT>
|
|
3
|
-
*/
|
|
4
|
-
export default class LoadingStateMachine extends FiniteStateMachine {
|
|
5
|
-
constructor();
|
|
6
|
-
/** @type {(( state: string )=>void)|null} */
|
|
7
|
-
onenter: ((state: string) => void) | null;
|
|
8
|
-
/** The last error */
|
|
9
|
-
get error(): Error;
|
|
10
|
-
#private;
|
|
11
|
-
}
|
|
12
|
-
import FiniteStateMachine from '../finite-state-machine/FiniteStateMachine.svelte.js';
|
|
File without changes
|