@hkdigital/lib-core 0.5.39 → 0.5.41

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.
@@ -162,13 +162,13 @@
162
162
  * @param {DragEvent} event
163
163
  */
164
164
  function handleDragStart(event) {
165
- console.debug('handleDragStart called', {
166
- target: event.target,
167
- currentTarget: event.currentTarget,
168
- disabled,
169
- canDrag: canDrag(item),
170
- dataTransfer: !!event.dataTransfer
171
- });
165
+ // console.debug('handleDragStart called', {
166
+ // target: event.target,
167
+ // currentTarget: event.currentTarget,
168
+ // disabled,
169
+ // canDrag: canDrag(item),
170
+ // dataTransfer: !!event.dataTransfer
171
+ // });
172
172
 
173
173
  if (disabled || !canDrag(item)) {
174
174
  console.debug('Drag prevented: disabled or cannot drag');
@@ -189,7 +189,7 @@
189
189
  return;
190
190
  }
191
191
 
192
- console.debug('Starting drag operation');
192
+ // console.debug('Starting drag operation');
193
193
  currentState = DRAGGING;
194
194
  startDrag(event);
195
195
  }
@@ -12,10 +12,7 @@
12
12
  getIsMobile
13
13
  } from '../../../browser/info/device.js';
14
14
 
15
- import {
16
- getIsPwa,
17
- getIsFullscreen
18
- } from '../../../browser/info/display.js';
15
+ import { getIsPwa, getIsFullscreen } from '../../../browser/info/display.js';
19
16
 
20
17
  import ScaledContainer from './ScaledContainer.svelte';
21
18
 
@@ -53,6 +50,7 @@
53
50
  * snippetPortrait?: GameBoxSnippet,
54
51
  * snippetRequireFullscreen?: GameBoxSnippet,
55
52
  * snippetInstallOnHomeScreen?: GameBoxSnippet,
53
+ * allowDevModeOnLocalhost?: boolean,
56
54
  * debug?: boolean,
57
55
  * [attr: string]: any
58
56
  * }}
@@ -87,6 +85,7 @@
87
85
  snippetRequireFullscreen,
88
86
  snippetInstallOnHomeScreen,
89
87
 
88
+ allowDevModeOnLocalhost = true,
90
89
  debug = false
91
90
  } = $props();
92
91
 
@@ -105,23 +104,21 @@
105
104
 
106
105
  let isLandscape = $state();
107
106
 
108
- let gameWidth = $derived.by( () => {
109
- if( isLandscape ) {
107
+ let gameWidth = $derived.by(() => {
108
+ if (isLandscape) {
110
109
  return gameWidthOnLandscape;
111
- }
112
- else {
110
+ } else {
113
111
  return gameWidthOnPortrait;
114
112
  }
115
- } );
113
+ });
116
114
 
117
- let gameHeight = $derived.by( () => {
118
- if( isLandscape ) {
115
+ let gameHeight = $derived.by(() => {
116
+ if (isLandscape) {
119
117
  return gameHeightOnLandscape;
120
- }
121
- else {
118
+ } else {
122
119
  return gameHeightOnPortrait;
123
120
  }
124
- } );
121
+ });
125
122
 
126
123
  // iPad is also considered Apple mobile
127
124
  const isAppleMobile = getIsAppleMobile();
@@ -132,12 +129,7 @@
132
129
 
133
130
  // Update iOS dimensions when window size changes
134
131
  $effect(() => {
135
- if (
136
- isPwa &&
137
- isAppleMobile &&
138
- windowWidth &&
139
- windowHeight
140
- ) {
132
+ if (isPwa && isAppleMobile && windowWidth && windowHeight) {
141
133
  updateIosWidthHeightAndOrientation();
142
134
  }
143
135
  });
@@ -146,8 +138,9 @@
146
138
  // Use matchMedia as a trigger for orientation changes
147
139
  // The actual orientation is determined in updateIosWidthHeightAndOrientation()
148
140
  if (typeof window !== 'undefined') {
149
- const isPortraitMedia =
150
- window.matchMedia('(orientation: portrait)').matches;
141
+ const isPortraitMedia = window.matchMedia(
142
+ '(orientation: portrait)'
143
+ ).matches;
151
144
 
152
145
  // Trigger iOS dimension update when orientation might have changed
153
146
  if (isPwa && isAppleMobile) {
@@ -164,10 +157,20 @@
164
157
  if (debug) {
165
158
  console.log('[GameBox] isLandscape:', isLandscape);
166
159
  console.log('[GameBox] windowWidth/Height:', windowWidth, windowHeight);
167
- console.log('[GameBox] iosWindowWidth/Height:',
168
- iosWindowWidth, iosWindowHeight);
169
-
170
- console.log( {isPwa, isAppleMobile, isMobile, isIos, isAndroid, isFullscreen} );
160
+ console.log(
161
+ '[GameBox] iosWindowWidth/Height:',
162
+ iosWindowWidth,
163
+ iosWindowHeight
164
+ );
165
+
166
+ console.log({
167
+ isPwa,
168
+ isAppleMobile,
169
+ isMobile,
170
+ isIos,
171
+ isAndroid,
172
+ isFullscreen
173
+ });
171
174
  }
172
175
  });
173
176
 
@@ -181,8 +184,7 @@
181
184
  const availWidth = width - marginLeft - marginRight;
182
185
  const availHeight = height - marginTop - marginBottom;
183
186
 
184
- if( debug )
185
- {
187
+ if (debug) {
186
188
  console.debug('GameBox margins:', {
187
189
  marginLeft,
188
190
  marginRight,
@@ -201,30 +203,24 @@
201
203
  aspectOnLandscape
202
204
  });
203
205
 
204
- if( aspectOnLandscape )
205
- {
206
+ if (aspectOnLandscape) {
206
207
  gameHeightOnLandscape = gameWidthOnLandscape / aspectOnLandscape;
207
- }
208
- else {
208
+ } else {
209
209
  gameHeightOnLandscape = availHeight;
210
210
  }
211
- }
212
- else {
211
+ } else {
213
212
  gameWidthOnPortrait = getGameWidthOnPortrait({
214
213
  windowWidth: availWidth,
215
214
  windowHeight: availHeight,
216
215
  aspectOnPortrait
217
216
  });
218
217
 
219
- if( aspectOnPortrait )
220
- {
218
+ if (aspectOnPortrait) {
221
219
  gameHeightOnPortrait = gameWidthOnPortrait / aspectOnPortrait;
222
- }
223
- else {
220
+ } else {
224
221
  gameHeightOnPortrait = availHeight;
225
222
  }
226
223
  }
227
-
228
224
  });
229
225
 
230
226
  let show = $state(false);
@@ -242,7 +238,6 @@
242
238
 
243
239
  function updateIosWidthHeightAndOrientation() {
244
240
  if (isAppleMobile) {
245
-
246
241
  // unreliable on ios >>
247
242
  // const angle = screen.orientation.angle;
248
243
  // if( window.matchMedia('(orientation: portrait)').matches ) {
@@ -276,16 +271,13 @@
276
271
  iosWindowWidth = window.innerWidth;
277
272
  iosWindowHeight = window.innerHeight;
278
273
 
279
- if( iosWindowHeight > iosWindowWidth )
280
- {
274
+ if (iosWindowHeight > iosWindowWidth) {
281
275
  isLandscape = false;
282
- }
283
- else {
276
+ } else {
284
277
  isLandscape = true;
285
278
  }
286
279
 
287
- if( debug )
288
- {
280
+ if (debug) {
289
281
  console.debug('updateIosWidthHeightAndOrientation', {
290
282
  'screen.orientation.type': screen.orientation.type,
291
283
  isLandscape,
@@ -330,8 +322,7 @@
330
322
  // App visibility detection for iOS debugging
331
323
  const handleVisibilityChange = () => {
332
324
  if (document.visibilityState === 'visible') {
333
-
334
- if( debug ) {
325
+ if (debug) {
335
326
  console.log('App became visible:', {
336
327
  'window.innerWidth': window.innerWidth,
337
328
  'window.innerHeight': window.innerHeight,
@@ -339,13 +330,13 @@
339
330
  'screen.height': screen.height,
340
331
  'screen.orientation.angle': screen.orientation.angle,
341
332
  'screen.orientation.type': screen.orientation.type,
342
- 'matchMedia portrait':
343
- window.matchMedia('(orientation: portrait)').matches,
344
- 'isLandscape': isLandscape,
345
- 'gameWidth': gameWidth,
346
- 'gameHeight': gameHeight,
347
- 'iosWindowWidth': iosWindowWidth,
348
- 'iosWindowHeight': iosWindowHeight
333
+ 'matchMedia portrait': window.matchMedia('(orientation: portrait)')
334
+ .matches,
335
+ isLandscape: isLandscape,
336
+ gameWidth: gameWidth,
337
+ gameHeight: gameHeight,
338
+ iosWindowWidth: iosWindowWidth,
339
+ iosWindowHeight: iosWindowHeight
349
340
  });
350
341
  }
351
342
 
@@ -384,7 +375,7 @@
384
375
  // };
385
376
  return () => {
386
377
  html.classList.remove(gameBoxNoScroll);
387
- }
378
+ };
388
379
  });
389
380
 
390
381
  async function requestFullscreen() {
@@ -432,7 +423,7 @@
432
423
  }
433
424
 
434
425
  $effect(() => {
435
- if (location.hostname === 'localhost') {
426
+ if (allowDevModeOnLocalhost && location.hostname === 'localhost') {
436
427
  isDevMode = true;
437
428
  }
438
429
  });
@@ -444,6 +435,24 @@
444
435
  window.history.pushState({}, '', url);
445
436
  }
446
437
  });
438
+
439
+ let snippetParams = $derived({
440
+ isLandscape,
441
+ isPortrait: !isLandscape,
442
+ isMobile,
443
+ isIos,
444
+ isAndroid,
445
+ isIpadOS,
446
+ isPwa,
447
+ isFullscreen,
448
+ isDevMode,
449
+ requestDevmode,
450
+ requestFullscreen,
451
+ gameWidth,
452
+ gameHeight
453
+ });
454
+
455
+ // $inspect('snippetParams', snippetParams);
447
456
  </script>
448
457
 
449
458
  <svelte:window bind:innerWidth={windowWidth} bind:innerHeight={windowHeight} />
@@ -467,213 +476,144 @@
467
476
  style:margin="{marginTop}px {marginRight}px {marginBottom}px {marginLeft}px"
468
477
  {style}
469
478
  >
470
- {#if show}
471
- <!-- Render both orientations, toggle visibility to preserve state -->
479
+ {#if show && clamping }
480
+
472
481
  {#if snippetRequireFullscreen}
473
- <!-- Require fullscreen -->
482
+
474
483
  {#if isFullscreen && !isDevMode}
484
+
485
+ <!--
486
+ snippetRequireFullscreen &&
487
+ Fullscreen
488
+ => Render both orientations, set visibility to preserve state
489
+ -->
490
+
475
491
  <!-- Landscape content -->
476
492
  <ScaledContainer
477
- enableScaling={enableScaling}
493
+ {enableScaling}
478
494
  design={designLandscape}
479
495
  {clamping}
480
496
  width={gameWidthOnLandscape}
481
497
  height={gameHeightOnLandscape}
498
+ snippet={snippetLandscape}
499
+ {snippetParams}
482
500
  hidden={!isLandscape}
483
- >
484
- {@render snippetLandscape({
485
- isLandscape,
486
- isPortrait: !isLandscape,
487
- isMobile,
488
- isIos,
489
- isAndroid,
490
- isIpadOS,
491
- isPwa,
492
- isFullscreen,
493
- isDevMode,
494
- requestDevmode,
495
- requestFullscreen,
496
- gameWidth,
497
- gameHeight
498
- })}
499
- </ScaledContainer>
501
+ ></ScaledContainer>
502
+
500
503
  <!-- Portrait content -->
501
504
  <ScaledContainer
502
- enableScaling={enableScaling}
505
+ {enableScaling}
503
506
  design={designPortrait}
504
507
  {clamping}
505
508
  width={gameWidthOnPortrait}
506
509
  height={gameHeightOnPortrait}
510
+ snippet={snippetPortrait}
511
+ {snippetParams}
507
512
  hidden={isLandscape}
508
- >
509
- {@render snippetPortrait({
510
- isLandscape,
511
- isPortrait: !isLandscape,
512
- isMobile,
513
- isIos,
514
- isAndroid,
515
- isIpadOS,
516
- isPwa,
517
- isFullscreen,
518
- isDevMode,
519
- requestDevmode,
520
- requestFullscreen,
521
- gameWidth,
522
- gameHeight
523
- })}
524
- </ScaledContainer>
525
- {:else if supportsFullscreen && !isDevMode}
526
- <!-- Require fullscreen -->
513
+ ></ScaledContainer>
514
+
515
+ {:else if isMobile && snippetInstallOnHomeScreen && !isPwa && !isDevMode}
516
+
517
+ <!--
518
+ snippetRequireFullscreen &&
519
+ isMobile &&
520
+ snippetInstallOnHomeScreen &&
521
+ Not PWA
522
+ => show install on home screen message
523
+ -->
524
+
527
525
  <ScaledContainer
528
- enableScaling={enableScaling}
526
+ {enableScaling}
529
527
  design={isLandscape ? designLandscape : designPortrait}
530
528
  {clamping}
531
529
  width={gameWidth}
532
530
  height={gameHeight}
533
- >
534
- {@render snippetRequireFullscreen({
535
- isLandscape,
536
- isPortrait: !isLandscape,
537
- isMobile,
538
- isIos,
539
- isAndroid,
540
- isIpadOS,
541
- isPwa,
542
- isFullscreen,
543
- isDevMode,
544
- requestDevmode,
545
- requestFullscreen,
546
- gameWidth,
547
- gameHeight
548
- })}
549
- </ScaledContainer>
550
- {:else if isMobile && snippetInstallOnHomeScreen && !isPwa && !isDevMode}
551
- <!-- Require install on home screen on mobile -->
531
+ snippet={snippetInstallOnHomeScreen}
532
+ {snippetParams}
533
+ ></ScaledContainer>
534
+
535
+ {:else if supportsFullscreen && !isDevMode}
536
+
537
+ <!--
538
+ snippetRequireFullscreen &&
539
+ Fullscreen supported &&
540
+ Not fullscreen &&
541
+ (Not mobile OR no install snippet OR already PWA)
542
+ => show fullscreen required message
543
+ -->
544
+
552
545
  <ScaledContainer
553
- enableScaling={enableScaling}
546
+ {enableScaling}
554
547
  design={isLandscape ? designLandscape : designPortrait}
555
548
  {clamping}
556
549
  width={gameWidth}
557
550
  height={gameHeight}
558
- >
559
- {@render snippetInstallOnHomeScreen({
560
- isLandscape,
561
- isPortrait: !isLandscape,
562
- isMobile,
563
- isIos,
564
- isAndroid,
565
- isIpadOS,
566
- isPwa,
567
- isFullscreen,
568
- isDevMode,
569
- requestDevmode,
570
- requestFullscreen,
571
- gameWidth,
572
- gameHeight
573
- })}
574
- </ScaledContainer>
551
+ snippet={snippetRequireFullscreen}
552
+ {snippetParams}
553
+ ></ScaledContainer>
554
+
575
555
  {:else}
556
+ <!--
557
+ snippetRequireFullscreen &&
558
+ (Dev mode OR
559
+ Fullscreen not supported OR
560
+ Not mobile OR
561
+ No install snippet OR
562
+ Already PWA)
563
+ => show game content
564
+ -->
565
+
576
566
  <!-- Landscape content -->
567
+
577
568
  <ScaledContainer
578
- enableScaling={enableScaling}
569
+ {enableScaling}
579
570
  design={designLandscape}
580
571
  {clamping}
581
572
  width={gameWidthOnLandscape}
582
573
  height={gameHeightOnLandscape}
574
+ snippet={snippetLandscape}
575
+ {snippetParams}
583
576
  hidden={!isLandscape}
584
- >
585
- {@render snippetLandscape({
586
- isLandscape,
587
- isPortrait: !isLandscape,
588
- isMobile,
589
- isIos,
590
- isAndroid,
591
- isIpadOS,
592
- isPwa,
593
- isFullscreen,
594
- isDevMode,
595
- requestDevmode,
596
- requestFullscreen,
597
- gameWidth,
598
- gameHeight
599
- })}
600
- </ScaledContainer>
577
+ ></ScaledContainer>
578
+
601
579
  <!-- Portrait content -->
602
580
  <ScaledContainer
603
- enableScaling={enableScaling}
581
+ {enableScaling}
604
582
  design={designPortrait}
605
583
  {clamping}
606
584
  width={gameWidthOnPortrait}
607
585
  height={gameHeightOnPortrait}
586
+ snippet={snippetPortrait}
587
+ {snippetParams}
608
588
  hidden={isLandscape}
609
- >
610
- {@render snippetPortrait({
611
- isLandscape,
612
- isPortrait: !isLandscape,
613
- isMobile,
614
- isIos,
615
- isAndroid,
616
- isIpadOS,
617
- isPwa,
618
- isFullscreen,
619
- isDevMode,
620
- requestDevmode,
621
- requestFullscreen,
622
- gameWidth,
623
- gameHeight
624
- })}
625
- </ScaledContainer>
589
+ ></ScaledContainer>
626
590
  {/if}
627
591
  {:else}
628
592
  <!-- Do not require fullscreen -->
593
+
629
594
  <!-- Landscape content -->
630
595
  <ScaledContainer
631
- enableScaling={enableScaling}
596
+ {enableScaling}
632
597
  design={designLandscape}
633
598
  {clamping}
634
599
  width={gameWidthOnLandscape}
635
600
  height={gameHeightOnLandscape}
601
+ snippet={snippetLandscape}
602
+ {snippetParams}
636
603
  hidden={!isLandscape}
637
- >
638
- {@render snippetLandscape({
639
- isLandscape,
640
- isPortrait: !isLandscape,
641
- isMobile,
642
- isIos,
643
- isAndroid,
644
- os,
645
- isFullscreen,
646
- isDevMode,
647
- requestDevmode,
648
- requestFullscreen,
649
- gameWidth,
650
- gameHeight
651
- })}
652
- </ScaledContainer>
604
+ ></ScaledContainer>
605
+
653
606
  <!-- Portrait content -->
654
607
  <ScaledContainer
655
- enableScaling={enableScaling}
608
+ {enableScaling}
656
609
  design={designPortrait}
657
610
  {clamping}
658
611
  width={gameWidthOnPortrait}
659
612
  height={gameHeightOnPortrait}
613
+ snippet={snippetPortrait}
614
+ {snippetParams}
660
615
  hidden={isLandscape}
661
- >
662
- {@render snippetPortrait({
663
- isLandscape,
664
- isPortrait: !isLandscape,
665
- isMobile,
666
- isIos,
667
- isAndroid,
668
- os,
669
- isFullscreen,
670
- isDevMode,
671
- requestDevmode,
672
- requestFullscreen,
673
- gameWidth,
674
- gameHeight
675
- })}
676
- </ScaledContainer>
616
+ ></ScaledContainer>
677
617
  {/if}
678
618
  {/if}
679
619
  </div>
@@ -45,6 +45,7 @@ type GameBox = {
45
45
  snippetPortrait?: Snippet<[SnippetParams]> | undefined;
46
46
  snippetRequireFullscreen?: Snippet<[SnippetParams]> | undefined;
47
47
  snippetInstallOnHomeScreen?: Snippet<[SnippetParams]> | undefined;
48
+ allowDevModeOnLocalhost?: boolean | undefined;
48
49
  debug?: boolean | undefined;
49
50
  }>): void;
50
51
  };
@@ -92,5 +93,6 @@ declare const GameBox: import("svelte").Component<{
92
93
  snippetPortrait?: import("svelte").Snippet<[import("./typedef.js").SnippetParams]>;
93
94
  snippetRequireFullscreen?: import("svelte").Snippet<[import("./typedef.js").SnippetParams]>;
94
95
  snippetInstallOnHomeScreen?: import("svelte").Snippet<[import("./typedef.js").SnippetParams]>;
96
+ allowDevModeOnLocalhost?: boolean;
95
97
  debug?: boolean;
96
98
  }, {}, "">;
@@ -1,22 +1,5 @@
1
1
  <script>
2
- import { enableContainerScaling } from '../../../design/index.js';
3
-
4
- /**
5
- * @typedef {{
6
- * enableScaling?: boolean,
7
- * design?: {width: number, height: number},
8
- * clamping?: {
9
- * ui: {min: number, max: number},
10
- * textBase: {min: number, max: number},
11
- * textHeading: {min: number, max: number},
12
- * textUi: {min: number, max: number}
13
- * },
14
- * width: number,
15
- * height: number,
16
- * hidden?: boolean,
17
- * children: import('svelte').Snippet
18
- * }}
19
- */
2
+ import { clamp, enableContainerScaling } from '../../../design/index.js';
20
3
 
21
4
  /**
22
5
  * Wrapper component that applies container scaling to its children
@@ -24,7 +7,7 @@
24
7
  * @type {{
25
8
  * enableScaling?: boolean,
26
9
  * design?: {width: number, height: number},
27
- * clamping?: {
10
+ * clamping: {
28
11
  * ui: {min: number, max: number},
29
12
  * textBase: {min: number, max: number},
30
13
  * textHeading: {min: number, max: number},
@@ -33,17 +16,19 @@
33
16
  * width: number,
34
17
  * height: number,
35
18
  * hidden?: boolean,
36
- * children: import('svelte').Snippet
19
+ * snippet?: import('./typedef.js').GameBoxSnippet,
20
+ * snippetParams: import('./typedef.js').SnippetParams
37
21
  * }}
38
22
  */
39
23
  let {
40
24
  enableScaling = false,
41
- design = undefined,
42
- clamping = undefined,
25
+ design,
26
+ clamping,
43
27
  width,
44
28
  height,
45
29
  hidden = false,
46
- children
30
+ snippet,
31
+ snippetParams
47
32
  } = $props();
48
33
 
49
34
  let container = $state();
@@ -51,12 +36,13 @@
51
36
  // Apply container scaling when enabled and not hidden
52
37
  $effect(() => {
53
38
  if (
54
- !enableScaling ||
55
39
  !container ||
40
+ !enableScaling ||
56
41
  !width ||
57
42
  !height ||
58
- hidden ||
59
- !design
43
+ !design ||
44
+ !clamping ||
45
+ hidden
60
46
  ) {
61
47
  return;
62
48
  }
@@ -72,14 +58,16 @@
72
58
  });
73
59
  </script>
74
60
 
75
- <div
76
- bind:this={container}
77
- class:hidden
78
- style:width="{width}px"
79
- style:height="{height}px"
80
- >
81
- {@render children()}
82
- </div>
61
+ {#if snippet && snippetParams}
62
+ <div
63
+ bind:this={container}
64
+ class:hidden
65
+ style:width="{width}px"
66
+ style:height="{height}px"
67
+ >
68
+ {@render snippet(snippetParams)}
69
+ </div>
70
+ {/if}
83
71
 
84
72
  <style>
85
73
  .hidden {
@@ -7,7 +7,7 @@ type ScaledContainer = {
7
7
  width: number;
8
8
  height: number;
9
9
  } | undefined;
10
- clamping?: {
10
+ clamping: {
11
11
  ui: {
12
12
  min: number;
13
13
  max: number;
@@ -24,11 +24,12 @@ type ScaledContainer = {
24
24
  min: number;
25
25
  max: number;
26
26
  };
27
- } | undefined;
27
+ };
28
28
  width: number;
29
29
  height: number;
30
30
  hidden?: boolean | undefined;
31
- children: Snippet<[]>;
31
+ snippet?: Snippet<[SnippetParams]> | undefined;
32
+ snippetParams: SnippetParams;
32
33
  }>): void;
33
34
  };
34
35
  declare const ScaledContainer: import("svelte").Component<{
@@ -37,7 +38,7 @@ declare const ScaledContainer: import("svelte").Component<{
37
38
  width: number;
38
39
  height: number;
39
40
  };
40
- clamping?: {
41
+ clamping: {
41
42
  ui: {
42
43
  min: number;
43
44
  max: number;
@@ -58,5 +59,6 @@ declare const ScaledContainer: import("svelte").Component<{
58
59
  width: number;
59
60
  height: number;
60
61
  hidden?: boolean;
61
- children: import("svelte").Snippet;
62
+ snippet?: import("./typedef.js").GameBoxSnippet;
63
+ snippetParams: import("./typedef.js").SnippetParams;
62
64
  }, {}, "">;
@@ -69,18 +69,25 @@ export function generateOrGetInstanceId(randomSize?: number, reset?: boolean): s
69
69
  * @param {number} [timeMs]
70
70
  * Custom time value to be used instead of Date.now()
71
71
  *
72
- * @returns {string} global id
72
+ * @returns {string}
73
+ * Global id (max 39 chars: 1 length prefix + 23 instance + 15 local)
73
74
  */
74
75
  export function generateGlobalId(timeMs?: number): string;
75
76
  /**
76
77
  * Generates and returns a new unique local id
77
- * - The generated id is garanteed to be unique on the currently running
78
+ * - The generated id is guaranteed to be unique on the currently running
78
79
  * local system
80
+ * - Format: <boot-prefix><length-indicator><time-based><count-based>
81
+ * - Boot prefix provides uniqueness across process restarts
82
+ * - Time-based component changes every 30 seconds
83
+ * - Counter resets every 30 seconds (656M IDs per 30s window = 5 chars)
84
+ * - Optimized for high-frequency generation (thousands per second)
79
85
  *
80
86
  * @param {number} [timeMs]
81
87
  * Custom time value to be used instead of Date.now()
82
88
  *
83
- * @returns {string} local id
89
+ * @returns {string}
90
+ * Local id (max 15 chars: 3 boot prefix + 1 length + 6 time + 5 count)
84
91
  */
85
92
  export function generateLocalId(timeMs?: number): string;
86
93
  /**
@@ -141,8 +141,8 @@ export function generateClientSessionId() {
141
141
  */
142
142
  export function generateOrGetInstanceId(randomSize = 16, reset = false) {
143
143
  if (!vars.instanceId || reset) {
144
- const timeBasedPart = getTimeBasedNumber30sBase58();
145
- const lengthPrefix = base58fromNumber(timeBasedPart.length);
144
+ const timeBasedPart = getTimeBasedNumber30sBase58(); // max 6 chars
145
+ const lengthPrefix = base58fromNumber(timeBasedPart.length); // 1 char
146
146
 
147
147
  vars.instanceId =
148
148
  lengthPrefix +
@@ -166,25 +166,35 @@ export function generateOrGetInstanceId(randomSize = 16, reset = false) {
166
166
  * @param {number} [timeMs]
167
167
  * Custom time value to be used instead of Date.now()
168
168
  *
169
- * @returns {string} global id
169
+ * @returns {string}
170
+ * Global id (max 39 chars: 1 length prefix + 23 instance + 15 local)
170
171
  */
171
172
  export function generateGlobalId(timeMs) {
172
- const instanceId = generateOrGetInstanceId();
173
- const localId = generateLocalId(timeMs);
173
+ const instanceId = generateOrGetInstanceId(); // max 1 + 6 + 16 = 23 chars
174
+ const localId = generateLocalId(timeMs); // max 15 chars
175
+
176
+ // Max 1 char (23 + 15 = 38)
174
177
  const lengthPrefix = base58fromNumber(instanceId.length + localId.length);
175
178
 
179
+ // Max 39 chars (1 + 23 + 15 = 39)
176
180
  return lengthPrefix + instanceId + localId;
177
181
  }
178
182
 
179
183
  /**
180
184
  * Generates and returns a new unique local id
181
- * - The generated id is garanteed to be unique on the currently running
185
+ * - The generated id is guaranteed to be unique on the currently running
182
186
  * local system
187
+ * - Format: <boot-prefix><length-indicator><time-based><count-based>
188
+ * - Boot prefix provides uniqueness across process restarts
189
+ * - Time-based component changes every 30 seconds
190
+ * - Counter resets every 30 seconds (656M IDs per 30s window = 5 chars)
191
+ * - Optimized for high-frequency generation (thousands per second)
183
192
  *
184
193
  * @param {number} [timeMs]
185
194
  * Custom time value to be used instead of Date.now()
186
195
  *
187
- * @returns {string} local id
196
+ * @returns {string}
197
+ * Local id (max 15 chars: 3 boot prefix + 1 length + 6 time + 5 count)
188
198
  */
189
199
  export function generateLocalId(timeMs) {
190
200
  const timeBasedNumber = getTimeBasedNumber30s(timeMs);
@@ -222,6 +232,15 @@ export function generateLocalId(timeMs) {
222
232
  //
223
233
  // @note ALPHABET_BASE_58 is used because it is faster than
224
234
  // base58fromNumber for single character encoding
235
+
236
+ //
237
+ // bootTimePrefix => 3 chars
238
+ // ALPHABET_BASE_58[timeBasedValue58.length] => 1 char
239
+ // timeBasedValue58 => max 6 chars
240
+ // countBasedValue58 => max 5 chars
241
+ // (resets every 30 seconds => 58^5 = 656.356.768)
242
+ //
243
+ // Realistic max length: 3 + 1 + 6 + 5 = 15 chars
225
244
  //
226
245
  const id =
227
246
  // idFormatPrefix
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.39",
3
+ "version": "0.5.41",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"