@coherent.js/core 1.1.0 → 2.0.0-rc.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coherent.js/core",
3
- "version": "1.1.0",
3
+ "version": "2.0.0-rc.0",
4
4
  "description": "Core runtime for Coherent.js (SSR framework).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/types/index.d.ts CHANGED
@@ -20,15 +20,20 @@ export type Primitive = string | number | boolean | null | undefined;
20
20
  /** Allow objects and functions in attributes */
21
21
  export type AttributeValue = Primitive | object;
22
22
 
23
+ /** Value accepted by `className` / `class` */
24
+ export type ClassValue = string | ReadonlyArray<ClassValue | false | null | undefined> | Record<string, unknown>;
25
+
23
26
  /** HTML attributes object */
24
27
  export interface HTMLAttributes {
25
28
  [key: string]: AttributeValue;
26
- className?: string;
27
- class?: string;
29
+ /** A string, an array (falsy entries dropped) or an object of `{ className: condition }` */
30
+ className?: ClassValue;
31
+ class?: ClassValue;
28
32
  id?: string;
29
33
  style?: string | Record<string, string | number>;
30
- onClick?: string | (() => void);
31
- onSubmit?: string | (() => void);
34
+ /** Inline code, or a function attached by @coherent.js/client's hydrate() (not rendered on the server) */
35
+ onClick?: string | ((event: any) => void);
36
+ onSubmit?: string | ((event: any) => void);
32
37
  href?: string;
33
38
  src?: string;
34
39
  alt?: string;
@@ -385,13 +390,16 @@ export interface WithStateHOC {
385
390
 
386
391
  /** Memoization options */
387
392
  export interface MemoOptions {
393
+ /**
394
+ * 'lru' (default) and 'ttl' keep at most `maxSize` entries; 'simple' is an
395
+ * unbounded Map; 'weak' keys on the identity of the first argument.
396
+ */
388
397
  strategy?: 'lru' | 'ttl' | 'weak' | 'simple';
389
398
  maxSize?: number;
399
+ /** Entry lifetime in milliseconds (default 5000 with the 'ttl' strategy). */
390
400
  ttl?: number;
391
401
  keyFn?: (...args: any[]) => string;
392
402
  keySerializer?: (value: any) => string;
393
- compareFn?: (a: any, b: any) => boolean;
394
- shallow?: boolean;
395
403
  onHit?: (key: string, value: any, args: any[]) => void;
396
404
  onMiss?: (key: string, args: any[]) => void;
397
405
  onEvict?: (key: string, value: any) => void;
@@ -516,12 +524,26 @@ export interface PerformanceMetrics {
516
524
 
517
525
  /** Render options for `render(component, options)` */
518
526
  export interface RenderOptions {
527
+ /**
528
+ * Cache the HTML of whole renders, keyed on the full component tree.
529
+ * Off by default; trees containing functions are never cached.
530
+ */
519
531
  enableCache?: boolean;
532
+ /** Cache instance (from createCacheManager) to use instead of the shared one. */
533
+ cache?: CacheManager;
520
534
  enableMonitoring?: boolean;
521
535
  minify?: boolean;
522
536
  maxDepth?: number;
537
+ /** @deprecated Ignored. Pass `cache: createCacheManager({ maxCacheSize })` instead. */
523
538
  cacheSize?: number;
539
+ /** Time-to-live in milliseconds for cache entries this render adds. */
524
540
  cacheTTL?: number;
541
+ /**
542
+ * Called when a function component throws. Its return value is rendered in
543
+ * place of the component (`null` omits it). Without it, the error
544
+ * propagates out of render().
545
+ */
546
+ onError?: (error: unknown, info: { path: string }) => CoherentNode;
525
547
  scoped?: boolean;
526
548
  encapsulate?: boolean;
527
549
  }
@@ -529,6 +551,49 @@ export interface RenderOptions {
529
551
  /** Render a Coherent node to an HTML string */
530
552
  export function render(component: CoherentNode, options?: RenderOptions): string;
531
553
 
554
+ export interface StreamOptions extends Omit<RenderOptions, 'enableCache' | 'cache' | 'cacheSize' | 'cacheTTL' | 'minify'> {
555
+ /** Approximate size of each yielded chunk in characters (default 8192). */
556
+ chunkSize?: number;
557
+ }
558
+
559
+ /**
560
+ * Stream a Coherent node as HTML chunks with the same output as render().
561
+ * The event loop gets a turn after every chunk. Errors reject the iteration.
562
+ *
563
+ * @example
564
+ * Readable.from(renderToStream(Page())).pipe(res);
565
+ */
566
+ export function renderToStream(component: CoherentNode, options?: StreamOptions): AsyncGenerator<string, void, undefined>;
567
+
568
+ export interface StreamingResponse {
569
+ headersSent?: boolean;
570
+ getHeader?(name: string): unknown;
571
+ setHeader(name: string, value: string): void;
572
+ write(chunk: string): boolean;
573
+ /** Set once the connection is gone (Node's `ServerResponse#destroyed`). */
574
+ destroyed?: boolean;
575
+ on(event: 'drain' | 'close' | 'error', listener: (...args: unknown[]) => void): unknown;
576
+ off(event: 'drain' | 'close' | 'error', listener: (...args: unknown[]) => void): unknown;
577
+ end(): void;
578
+ destroy(error?: Error): void;
579
+ }
580
+
581
+ export const streamingUtils: {
582
+ /** Concatenate every chunk. */
583
+ collectChunks(chunks: AsyncIterable<string>): Promise<string>;
584
+ /**
585
+ * Write chunks to a Node response with backpressure; aborts it on error.
586
+ * Resolves to the byte count. If the client disconnects, rendering stops and
587
+ * it resolves with the bytes written so far.
588
+ */
589
+ streamToResponse(chunks: AsyncIterable<string>, response: StreamingResponse): Promise<number>;
590
+ /** Re-yield chunks, reporting progress after each one. */
591
+ streamWithProgress(
592
+ chunks: AsyncIterable<string>,
593
+ onProgress?: (progress: { chunkCount: number; totalBytes: number; chunk: string }) => void
594
+ ): AsyncGenerator<string, void, undefined>;
595
+ };
596
+
532
597
  export interface RenderUtilityOptions {
533
598
  enablePerformanceMonitoring?: boolean;
534
599
  template?: string;
@@ -572,7 +637,7 @@ export const withState: WithStateHOC;
572
637
  /** Memoization function */
573
638
  export function memo<T extends (...args: any[]) => any>(
574
639
  fn: T,
575
- options?: MemoOptions
640
+ options?: MemoOptions | ((props: Parameters<T>[0]) => string)
576
641
  ): MemoizedFunction<T>;
577
642
 
578
643
  /** Validate component structure */
@@ -715,6 +780,10 @@ export interface PerformanceMonitor {
715
780
  startRender(componentName?: string): string;
716
781
  endRender(renderId: string): number;
717
782
  recordMetric(name: string, value: number, tags?: Record<string, any>): void;
783
+ /** Called by the renderer when `enableMonitoring` is on. */
784
+ recordRender(operation: string, duration: number, fromCache?: boolean, metadata?: Record<string, any>): void;
785
+ /** Called by the renderer when `enableMonitoring` is on. */
786
+ recordError(operation: string, error: unknown, metadata?: Record<string, any>): void;
718
787
  addMetric(name: string, value: number, tags?: Record<string, any>): void;
719
788
  measure<T>(name: string, fn: () => T): T;
720
789
  measureAsync<T>(name: string, fn: () => Promise<T>): Promise<T>;
@@ -734,20 +803,43 @@ export const performanceMonitor: PerformanceMonitor;
734
803
 
735
804
  /** Cache manager options */
736
805
  export interface CacheManagerOptions {
806
+ /** Maximum entries per cache type (default 1000). */
807
+ maxCacheSize?: number;
808
+ /** Alias for `maxCacheSize`. */
737
809
  maxSize?: number;
738
- ttl?: number;
739
- strategy?: 'lru' | 'fifo' | 'lfu';
810
+ /** Memory budget across all cache types, keys included (default 100). */
811
+ maxMemoryMB?: number;
812
+ /** Default time-to-live in milliseconds (default 5 minutes). */
813
+ ttlMs?: number;
814
+ enableStatistics?: boolean;
740
815
  }
741
816
 
742
- /** Cache manager interface */
817
+ export type CacheType = 'static' | 'component' | 'template' | 'data';
818
+
819
+ export interface CacheStats {
820
+ hits: number;
821
+ misses: number;
822
+ /** Approximate memory use in bytes. */
823
+ size: number;
824
+ entries: number;
825
+ hitRate: Record<CacheType, number>;
826
+ accessCount: Record<CacheType, number>;
827
+ }
828
+
829
+ /** Cache manager: one least-recently-used map per cache type. */
743
830
  export interface CacheManager {
744
- get(key: string): any;
745
- set(key: string, value: any, ttl?: number): void;
746
- has(key: string): boolean;
747
- delete(key: string): boolean;
748
- clear(): void;
749
- size(): number;
750
- prune(): void;
831
+ /** Returns the cached value, or null when missing or expired. */
832
+ get(key: string, type?: CacheType): any;
833
+ set(key: string, value: any, type?: CacheType, metadata?: { ttlMs?: number; [key: string]: any }): void;
834
+ remove(key: string, type?: CacheType): boolean;
835
+ clear(type?: CacheType): void;
836
+ getStats(): CacheStats;
837
+ cleanup(): { freed: number };
838
+ destroy(): void;
839
+ generateCacheKey(component: any, props?: Record<string, any>, context?: Record<string, any>): string;
840
+ hashObject(obj: any): string;
841
+ readonly memoryUsage: number;
842
+ readonly maxMemory: number;
751
843
  }
752
844
 
753
845
  /** Shared cache manager instance */
@@ -891,15 +983,24 @@ export function formatAttributes(props: Record<string, any>): string;
891
983
  /** Mark content as trusted so it is emitted without escaping */
892
984
  export function dangerouslySetInnerContent(content: string): TrustedContent;
893
985
 
894
- /** Content marked trusted by dangerouslySetInnerContent() */
986
+ /** Content marked trusted by dangerouslySetInnerContent() (frozen, symbol-branded) */
895
987
  export interface TrustedContent {
896
- __html: string;
897
- __trusted: true;
988
+ readonly __html: string;
989
+ readonly __trusted: true;
898
990
  }
899
991
 
900
- /** Detect content marked by dangerouslySetInnerContent() */
992
+ /**
993
+ * Detect content marked by dangerouslySetInnerContent(). Markers carry a
994
+ * symbol brand, so objects parsed from JSON are never trusted.
995
+ */
901
996
  export function isTrustedContent(value: unknown): value is TrustedContent;
902
997
 
998
+ /**
999
+ * Whether a string can be emitted as an attribute name. Rendering an element
1000
+ * with an invalid name (whitespace, quotes, `<`, `>`, `/`, `=`, controls) throws.
1001
+ */
1002
+ export function isValidAttributeName(name: string): boolean;
1003
+
903
1004
  // ============================================================================
904
1005
  // Utility Types and Constants
905
1006
  // ============================================================================
@@ -913,6 +1014,7 @@ export const compose: ComposeUtils;
913
1014
  /** Default export with all core functionality */
914
1015
  declare const coherent: {
915
1016
  render: typeof render;
1017
+ renderToStream: typeof renderToStream;
916
1018
  withState: typeof withState;
917
1019
  memo: typeof memo;
918
1020
  validateComponent: typeof validateComponent;