webspresso 0.0.97 → 0.0.99

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.
Files changed (136) hide show
  1. package/.agents/ADMIN.md +163 -5
  2. package/.agents/AGENTS.md +1 -1
  3. package/.agents/ARCHITECTURE.md +10 -0
  4. package/.agents/AUTH.md +169 -0
  5. package/.agents/CLI.md +195 -1
  6. package/.agents/ORM.md +88 -4
  7. package/.agents/PLUGINS.md +221 -9
  8. package/.agents/QUEUE.md +127 -0
  9. package/.agents/ROUTING.md +127 -7
  10. package/AGENTS.md +1 -0
  11. package/bin/commands/add-esbuild.js +80 -0
  12. package/bin/commands/add-tailwind.js +76 -31
  13. package/bin/commands/analytics-prune.js +57 -0
  14. package/bin/commands/api.js +207 -15
  15. package/bin/commands/build.js +99 -0
  16. package/bin/commands/dev.js +64 -35
  17. package/bin/commands/doctor.js +26 -0
  18. package/bin/commands/email-prune.js +47 -0
  19. package/bin/commands/make-job.js +86 -0
  20. package/bin/commands/make-model.js +250 -0
  21. package/bin/commands/new.js +45 -28
  22. package/bin/commands/openapi.js +102 -0
  23. package/bin/commands/page.js +6 -3
  24. package/bin/commands/queue-prune.js +59 -0
  25. package/bin/commands/routes.js +52 -0
  26. package/bin/commands/service.js +6 -3
  27. package/bin/commands/typegen.js +106 -0
  28. package/bin/commands/upload-prune.js +79 -0
  29. package/bin/webspresso.js +22 -0
  30. package/core/applySchema.js +13 -6
  31. package/core/auth/hash.js +21 -12
  32. package/core/auth/index.js +60 -0
  33. package/core/auth/manager.js +134 -0
  34. package/core/auth/middleware.js +88 -0
  35. package/core/auth/oauth.js +517 -0
  36. package/core/auth/qrcode.js +499 -0
  37. package/core/auth/totp.js +504 -0
  38. package/core/compression/middleware.js +23 -17
  39. package/core/openapi/build-from-api-routes.js +160 -29
  40. package/core/orm/cache/fingerprint.js +15 -1
  41. package/core/orm/cache/memory-provider.js +27 -2
  42. package/core/orm/eager-loader.js +144 -10
  43. package/core/orm/index.js +20 -7
  44. package/core/orm/model.js +53 -7
  45. package/core/orm/query-builder.js +90 -4
  46. package/core/orm/repository.js +44 -3
  47. package/core/orm/search.js +213 -0
  48. package/core/queue/adapters/database.js +94 -0
  49. package/core/queue/adapters/memory.js +62 -0
  50. package/core/queue/adapters/redis.js +80 -0
  51. package/core/queue/cron.js +331 -0
  52. package/core/queue/index.js +8 -1
  53. package/core/queue/manager.js +205 -1
  54. package/core/queue/scheduler.js +359 -0
  55. package/core/sse/index.js +19 -0
  56. package/core/sse/stream.js +430 -0
  57. package/core/ssr/stream.js +11 -0
  58. package/index.d.ts +1188 -10
  59. package/index.js +88 -96
  60. package/package.json +1 -1
  61. package/plugins/admin-panel/api.js +279 -17
  62. package/plugins/admin-panel/app.js +114 -22
  63. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +314 -9
  64. package/plugins/admin-panel/client/parts/04-field-renderers.js +264 -67
  65. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +333 -36
  66. package/plugins/admin-panel/client/parts/06-login-setup-forms.js +119 -28
  67. package/plugins/admin-panel/client/parts/07-model-list.js +49 -8
  68. package/plugins/admin-panel/client/parts/08-record-list.js +248 -23
  69. package/plugins/admin-panel/client/parts/09-record-form.js +59 -12
  70. package/plugins/admin-panel/client/parts/10-export-registry.js +23 -0
  71. package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
  72. package/plugins/admin-panel/core/admin-module.js +13 -7
  73. package/plugins/admin-panel/core/api-extensions.js +30 -2
  74. package/plugins/admin-panel/core/registry.js +96 -16
  75. package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
  76. package/plugins/admin-panel/field-renderers/index.js +2 -0
  77. package/plugins/admin-panel/index.js +96 -0
  78. package/plugins/admin-panel/lib/login-rate-limiter.js +119 -0
  79. package/plugins/admin-panel/modules/admin-users.js +16 -1
  80. package/plugins/admin-panel/modules/bulk-actions.js +13 -5
  81. package/plugins/admin-panel/modules/custom-pages.js +492 -3
  82. package/plugins/admin-panel/modules/dashboard.js +283 -0
  83. package/plugins/admin-panel/modules/menu.js +56 -18
  84. package/plugins/admin-panel/modules/user-management.js +24 -6
  85. package/plugins/api-key/crypto.js +101 -0
  86. package/plugins/api-key/index.js +288 -0
  87. package/plugins/api-key/middleware.js +461 -0
  88. package/plugins/api-key/migration-template.js +48 -0
  89. package/plugins/api-key/model.js +50 -0
  90. package/plugins/api-key/scopes.js +125 -0
  91. package/plugins/audit-log/admin-component.js +487 -59
  92. package/plugins/audit-log/api-handlers.js +148 -0
  93. package/plugins/audit-log/index.js +153 -3
  94. package/plugins/audit-log/purge.js +60 -10
  95. package/plugins/dashboard/app.js +152 -27
  96. package/plugins/dashboard/index.js +143 -39
  97. package/plugins/dashboard/styles.js +78 -0
  98. package/plugins/data-exchange/export-xlsx.js +9 -2
  99. package/plugins/data-exchange/import.js +9 -2
  100. package/plugins/data-exchange/index.js +6 -3
  101. package/plugins/email/index.js +132 -2
  102. package/plugins/email/purge.js +60 -10
  103. package/plugins/file-manager/admin-component.js +20 -4
  104. package/plugins/file-manager/index.js +21 -4
  105. package/plugins/index.js +10 -0
  106. package/plugins/oauth/index.js +338 -0
  107. package/plugins/orm-cache-admin/index.js +4 -2
  108. package/plugins/queue/index.js +15 -0
  109. package/plugins/schema-explorer/admin-component.js +714 -0
  110. package/plugins/schema-explorer/relations-graph.js +205 -0
  111. package/plugins/schema-explorer.js +248 -69
  112. package/plugins/site-analytics/admin-component.js +23 -0
  113. package/plugins/site-analytics/api-handlers.js +42 -0
  114. package/plugins/site-analytics/index.js +147 -2
  115. package/plugins/site-analytics/purge.js +168 -0
  116. package/plugins/swagger.js +261 -13
  117. package/plugins/upload/index.js +26 -0
  118. package/plugins/upload/orphan-pruner.js +292 -0
  119. package/src/api/api-loader.js +4 -1
  120. package/src/bundle/index.js +517 -0
  121. package/src/bundle/manifest.js +165 -0
  122. package/src/dev/banner.js +296 -0
  123. package/src/dev/colors.js +70 -0
  124. package/src/dev/index.js +62 -0
  125. package/src/dev/profiler.js +354 -0
  126. package/src/dev/typegen.js +497 -0
  127. package/src/dev-reload/index.js +611 -0
  128. package/src/discovery/file-route-parser.js +44 -1
  129. package/src/discovery/scan-routes.js +2 -2
  130. package/src/file-router.js +347 -162
  131. package/src/helpers.js +967 -98
  132. package/src/njk-frontmatter.js +12 -0
  133. package/src/pages/page-loader.js +61 -11
  134. package/src/routing/cascading.js +378 -0
  135. package/src/routing/middleware-resolver.js +92 -0
  136. package/src/server.js +635 -63
package/index.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import type { Application, NextFunction, Request, RequestHandler, Response } from 'express';
6
6
  import type { Knex } from 'knex';
7
7
  import type { ZodObject, ZodTypeAny } from 'zod';
8
+ import type { EventEmitter } from 'events';
8
9
 
9
10
  /** Registered as createApp middlewares[name]: plain handler or (options) => handler (for middleware: ['name', options]). */
10
11
  export type WebspressoRegisteredMiddleware =
@@ -45,6 +46,7 @@ export interface CreateAppOptions {
45
46
  enabled?: boolean;
46
47
  stylesheets?: boolean;
47
48
  scripts?: boolean;
49
+ auto?: boolean;
48
50
  };
49
51
  /** Trust proxy configuration (default: 1) */
50
52
  trustProxy?: boolean | number | string;
@@ -66,6 +68,17 @@ export interface CreateAppOptions {
66
68
  compression?: boolean | CompressionOptions;
67
69
  /** Shutdown and lifecycle configuration */
68
70
  shutdown?: ShutdownOptions;
71
+ /** Development tooling and DX options */
72
+ dev?: {
73
+ banner?: boolean;
74
+ routeTable?: boolean;
75
+ profiler?: boolean;
76
+ nPlusOne?: boolean;
77
+ nPlusOneThreshold?: number;
78
+ slowQueryMs?: number;
79
+ verbose?: boolean;
80
+ colors?: boolean;
81
+ };
69
82
  server?: {
70
83
  port?: number;
71
84
  shutdown?: ShutdownOptions;
@@ -83,10 +96,84 @@ export interface CreateAppOptions {
83
96
  services?: {
84
97
  dir?: string;
85
98
  };
99
+ /** Development SSE live/hot reload configuration */
100
+ devReload?: boolean | DevReloadOptions;
101
+ /** Client-side JS incremental bundler (esbuild context() API) */
102
+ bundle?: boolean | DevBundlerOptions;
86
103
  setupRoutes?: (app: Application, ctx: SetupRoutesContext) => void;
87
104
  [key: string]: unknown;
88
105
  }
89
106
 
107
+ export interface DevReloadOptions {
108
+ enabled?: boolean;
109
+ watch?: string[];
110
+ watchCss?: boolean;
111
+ watchTemplates?: boolean;
112
+ watchPublicAll?: boolean;
113
+ templateExtensions?: string[];
114
+ pageExtensions?: string[];
115
+ autoInject?: boolean;
116
+ debounce?: number;
117
+ }
118
+
119
+ export interface DevReloadHub {
120
+ broadcast(type: 'css' | 'full' | 'error' | 'clear-error' | string, payload?: Record<string, unknown>): void;
121
+ close(): void;
122
+ getClientsCount(): number;
123
+ getWatchersCount(): number;
124
+ getWatchedPaths(): string[];
125
+ attachNunjucks(env: unknown): void;
126
+ setOnTemplateChange(fn: (file?: string) => void): void;
127
+ invalidateTemplateCache(file?: string): void;
128
+ triggerTemplateReload(filename?: string, reason?: string): void;
129
+ triggerCssReload(filename?: string): void;
130
+ handleSse(req: any, res: any): void;
131
+ handleScript(req: any, res: any): void;
132
+ autoInjectMiddleware(req: any, res: any, next: () => void): void;
133
+ }
134
+
135
+ export interface DevBundlerOptions {
136
+ enabled?: boolean;
137
+ entryPoints?: string[] | Record<string, string>;
138
+ outdir?: string;
139
+ bundle?: boolean;
140
+ sourcemap?: boolean;
141
+ minify?: boolean;
142
+ target?: string | string[];
143
+ format?: string;
144
+ metafile?: boolean;
145
+ plugins?: any[];
146
+ esbuild?: any;
147
+ watch?: boolean;
148
+ esbuildOptions?: Record<string, unknown>;
149
+ [key: string]: unknown;
150
+ }
151
+
152
+ export interface DevBundler {
153
+ isAvailable(): boolean;
154
+ isWatching(): boolean;
155
+ isDisposed(): boolean;
156
+ getEntryPoints(): string[] | Record<string, string>;
157
+ getOutdir(): string;
158
+ startWatch(): Promise<any>;
159
+ rebuild(): Promise<any>;
160
+ dispose(): Promise<void>;
161
+ getContext(): any;
162
+ }
163
+
164
+ export interface BuildBundleResult {
165
+ result: unknown;
166
+ manifest: Record<string, unknown>;
167
+ manifestPath: string;
168
+ }
169
+
170
+ export function createDevBundler(options?: DevBundlerOptions, devReloadHub?: unknown): DevBundler;
171
+ export function buildBundle(options?: DevBundlerOptions): Promise<BuildBundleResult>;
172
+ export function generateManifest(metafile: unknown, options?: unknown): Record<string, unknown>;
173
+ export function createManifestPlugin(options?: unknown): unknown;
174
+ export function findDefaultEntryPoints(cwd?: string): string[] | Record<string, string>;
175
+ export function findPageEntryPoints(cwd?: string): Record<string, string>;
176
+
90
177
  export interface ServiceContext {
91
178
  req?: Request;
92
179
  res?: Response;
@@ -314,6 +401,11 @@ export interface WebspressoApplication extends Application {
314
401
  enableShutdownHooks(): this;
315
402
  disableShutdownHooks(): this;
316
403
  setErrorHandler(handler: (error: unknown, req: Request, res: Response, next: NextFunction) => Promise<unknown> | unknown): this;
404
+ broadcastDevReload?(type: 'css' | 'full' | string, payload?: Record<string, unknown>): void;
405
+ devReloadHub?: DevReloadHub;
406
+ devBundler?: DevBundler;
407
+ printDevBanner?(opts?: Record<string, unknown>): void;
408
+ formatDevBanner?(opts?: Record<string, unknown>): { bannerText: string; plainText: string; summary: Record<string, unknown> };
317
409
  }
318
410
 
319
411
  export interface CreateAppResult {
@@ -326,6 +418,11 @@ export interface CreateAppResult {
326
418
 
327
419
  export function createApp(options?: CreateAppOptions): CreateAppResult;
328
420
 
421
+ export function formatDevBanner(context?: Record<string, unknown>, options?: Record<string, unknown>): { bannerText: string; plainText: string; summary: Record<string, unknown> };
422
+ export function printDevBanner(context?: Record<string, unknown>, options?: Record<string, unknown>): void;
423
+ export function formatRouteTable(routeTable: unknown, options?: Record<string, unknown>): string;
424
+ export function createDevProfilerMiddleware(options?: Record<string, unknown>): RequestHandler;
425
+
329
426
  // --- Exceptions & Errors ---
330
427
 
331
428
  export interface ErrorOptions {
@@ -444,8 +541,47 @@ export function mountPages(
444
541
  registerDynamicFileRoutes: () => void;
445
542
  };
446
543
 
544
+ export interface CascadingLayoutInfo {
545
+ layoutRelPath: string;
546
+ fullPath: string;
547
+ configPath: string | null;
548
+ }
549
+
550
+ export namespace cascading {
551
+ export function getDirectoryChain(relativeFilePath: string): string[];
552
+ export function templateHasExtends(templateBody?: string | null, fullPath?: string): boolean;
553
+ export function findCascadingMiddlewareFiles(baseDir: string, relativeFilePath: string, isDev?: boolean): string[];
554
+ export function loadCascadingMiddlewares(
555
+ baseDir: string,
556
+ relativeFilePath: string,
557
+ middlewareRegistry?: Record<string, unknown>,
558
+ isDev?: boolean
559
+ ): Array<(req: any, res: any, next: (err?: any) => void) => void>;
560
+ export function runMiddleware(mw: Function, req: any, res: any): Promise<void>;
561
+ export function runMiddlewareChain(chain: Function[], req: any, res: any): Promise<void>;
562
+ export function findCascadingLayouts(baseDir: string, relativeFilePath: string, isDev?: boolean): CascadingLayoutInfo[];
563
+ export function executeCascadingLayoutLoads(layouts: CascadingLayoutInfo[], req: any, ctx: any, isDev?: boolean): Promise<void>;
564
+ export function applyCascadingLayouts(
565
+ nunjucksEnv: any,
566
+ layouts: CascadingLayoutInfo[],
567
+ pageHtml: string,
568
+ renderContext: Record<string, unknown>,
569
+ isDev?: boolean
570
+ ): string;
571
+ export function clearCascadingCaches(): void;
572
+ }
573
+
574
+ export function resolveMiddlewares(
575
+ middlewareConfig: unknown[] | null | undefined,
576
+ middlewareRegistry?: Record<string, unknown>
577
+ ): Array<(req: any, res: any, next: (err?: any) => void) => void>;
578
+
447
579
  export function filePathToRoute(filePath: string, pagesDir: string): string;
448
580
 
581
+ export function isRouteGroupSegment(segment: string): boolean;
582
+
583
+ export function stripRouteGroups(route: string): string;
584
+
449
585
  export function extractMethodFromFilename(filename: string): {
450
586
  method: string;
451
587
  baseName: string;
@@ -521,6 +657,19 @@ export const utils: Record<string, unknown>;
521
657
 
522
658
  export class AssetManager {
523
659
  constructor(options?: Record<string, unknown>);
660
+ resolve(assetPath: string): string;
661
+ asset(assetPath: string): string;
662
+ js(src: string, attributes?: Record<string, unknown>): string;
663
+ css(href: string, attributes?: Record<string, unknown>): string;
664
+ pageJs(name: string, attributes?: Record<string, unknown>): string;
665
+ pageJsUrl(name: string): string;
666
+ pageCss(name: string, attributes?: Record<string, unknown>): string;
667
+ pageCssUrl(name: string): string;
668
+ hasPageJs(name: string): boolean;
669
+ hasPageCss(name: string): boolean;
670
+ getRouteAssetCandidates(routeFile?: string, routePath?: string): string[];
671
+ pageAssets(nameOrOptions?: string | Record<string, unknown>, attributes?: Record<string, unknown>): string;
672
+ pageAssetsForRoute(routeFile?: string, routePath?: string, options?: Record<string, unknown>): string;
524
673
  [key: string]: unknown;
525
674
  }
526
675
 
@@ -603,10 +752,111 @@ export interface RelationDefinition {
603
752
  localKey?: string;
604
753
  }
605
754
 
755
+ export interface AdminPermissions {
756
+ create?: boolean;
757
+ edit?: boolean;
758
+ delete?: boolean;
759
+ }
760
+
761
+ export interface AdminSortConfig {
762
+ column: string;
763
+ direction?: 'asc' | 'desc';
764
+ }
765
+
766
+ export type AdminBadgeColor =
767
+ | 'green'
768
+ | 'red'
769
+ | 'blue'
770
+ | 'amber'
771
+ | 'yellow'
772
+ | 'purple'
773
+ | 'violet'
774
+ | 'indigo'
775
+ | 'gray'
776
+ | string;
777
+
778
+ export interface AdminColumnConfig {
779
+ label?: string;
780
+ badge?: Record<string, AdminBadgeColor>;
781
+ hidden?: boolean;
782
+ readonly?: boolean;
783
+ sortable?: boolean;
784
+ [key: string]: unknown;
785
+ }
786
+
787
+ export interface AdminActionConfig {
788
+ id: string;
789
+ label: string;
790
+ icon?: string;
791
+ color?: string;
792
+ models?: string | string[];
793
+ confirm?: boolean;
794
+ confirmMessage?: string;
795
+ showWhen?: Record<string, unknown>;
796
+ handler?: (record: any, modelName: string, ctx: any) => Promise<unknown> | unknown;
797
+ visible?: (record: any, modelName: string) => boolean;
798
+ }
799
+
800
+ export interface AdminBulkActionConfig {
801
+ id: string;
802
+ label: string;
803
+ icon?: string;
804
+ color?: string;
805
+ models?: string | string[];
806
+ confirm?: boolean;
807
+ confirmMessage?: string;
808
+ handler?: (records: any[], modelName: string, ctx: any) => Promise<unknown> | unknown;
809
+ }
810
+
811
+ export interface AdminHookContext<TData = any, TRecord = any> {
812
+ model: string;
813
+ data?: TData;
814
+ record?: TRecord;
815
+ id?: string | number;
816
+ req?: any;
817
+ user?: any;
818
+ db?: any;
819
+ stats?: any;
820
+ }
821
+
822
+ export interface AdminHooksConfig {
823
+ beforeCreate?: (ctx: AdminHookContext) => Promise<void> | void;
824
+ afterCreate?: (ctx: AdminHookContext) => Promise<void> | void;
825
+ beforeUpdate?: (ctx: AdminHookContext) => Promise<void> | void;
826
+ afterUpdate?: (ctx: AdminHookContext) => Promise<void> | void;
827
+ beforeDelete?: (ctx: AdminHookContext) => Promise<void> | void;
828
+ afterDelete?: (ctx: AdminHookContext) => Promise<void> | void;
829
+ beforeRestore?: (ctx: AdminHookContext) => Promise<void> | void;
830
+ afterRestore?: (ctx: AdminHookContext) => Promise<void> | void;
831
+ onRecordView?: (ctx: AdminHookContext) => Promise<void> | void;
832
+ onDashboardLoad?: (ctx: AdminHookContext) => Promise<void> | void;
833
+ [key: string]: ((ctx: AdminHookContext) => Promise<void> | void) | undefined;
834
+ }
835
+
836
+ export interface AdminRelationConfig {
837
+ displayField?: string;
838
+ searchFields?: string[];
839
+ limit?: number;
840
+ [key: string]: unknown;
841
+ }
842
+
606
843
  export interface AdminMetadata {
607
844
  enabled?: boolean;
608
845
  label?: string;
609
846
  icon?: string | null;
847
+ group?: string | null;
848
+ menuOrder?: number | null;
849
+ listColumns?: string[];
850
+ searchFields?: string[];
851
+ displayField?: string;
852
+ readOnly?: boolean;
853
+ permissions?: AdminPermissions;
854
+ defaultSort?: AdminSortConfig;
855
+ columns?: Record<string, AdminColumnConfig>;
856
+ actions?: AdminActionConfig[];
857
+ bulkActions?: AdminBulkActionConfig[];
858
+ hooks?: AdminHooksConfig;
859
+ relations?: Record<string, AdminRelationConfig>;
610
860
  customFields?: Record<string, unknown>;
611
861
  queries?: Record<string, (repo: Repository) => Promise<unknown>>;
612
862
  }
@@ -653,6 +903,7 @@ export interface ModelOptions<TSchema extends ZodObject<any> = ZodObject<Record<
653
903
  maxIncludes?: number;
654
904
  maxFilterConditions?: number;
655
905
  };
906
+ searchable?: string[];
656
907
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
657
908
  }
658
909
 
@@ -687,6 +938,7 @@ export interface ModelDefinition<TModel = Record<string, any>> {
687
938
  maxIncludes?: number;
688
939
  maxFilterConditions?: number;
689
940
  };
941
+ searchable?: string[];
690
942
  hooks: Record<string, unknown>;
691
943
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
692
944
  }
@@ -723,7 +975,10 @@ export function getColumnMeta(schema: ZodTypeAny): unknown;
723
975
 
724
976
  export interface FindOptions {
725
977
  with?: string[];
978
+ include?: string[];
726
979
  select?: string[];
980
+ limit?: number;
981
+ offset?: number;
727
982
  }
728
983
 
729
984
  export interface PaginatedResult<T = Record<string, any>> {
@@ -734,6 +989,16 @@ export interface PaginatedResult<T = Record<string, any>> {
734
989
  totalPages: number;
735
990
  }
736
991
 
992
+ export interface SearchOptions {
993
+ mode?: 'auto' | 'fts' | 'like';
994
+ language?: string;
995
+ matchAllWords?: boolean;
996
+ exact?: boolean;
997
+ rank?: boolean;
998
+ ftsTable?: string;
999
+ boolean?: 'and' | 'or';
1000
+ }
1001
+
737
1002
  export interface QueryBuilder<T = Record<string, any>> {
738
1003
  where(column: keyof T | string, value: unknown): this;
739
1004
  where(column: keyof T | string, operator: string, value: unknown): this;
@@ -743,14 +1008,19 @@ export interface QueryBuilder<T = Record<string, any>> {
743
1008
  whereBetween(column: keyof T | string, range: [unknown, unknown]): this;
744
1009
  select(...columns: (keyof T | string)[]): this;
745
1010
  orderBy(column: keyof T | string, direction?: 'asc' | 'desc'): this;
1011
+ orderByRaw(sql: string, bindings?: unknown[]): this;
1012
+ search(term: string, columns?: string | string[], options?: SearchOptions): this;
1013
+ orSearch(term: string, columns?: string | string[], options?: SearchOptions): this;
746
1014
  limit(n: number): this;
747
1015
  offset(n: number): this;
748
1016
  with(...relations: string[]): this;
1017
+ include(...relations: string[]): this;
749
1018
  withTrashed(): this;
750
1019
  onlyTrashed(): this;
751
1020
  first(): Promise<T | null>;
752
1021
  list(): Promise<T[]>;
753
1022
  get(): Promise<T[]>;
1023
+ find(): Promise<T[]>;
754
1024
  count(): Promise<number>;
755
1025
  paginate(page?: number, perPage?: number): Promise<PaginatedResult<T>>;
756
1026
  exists(): Promise<boolean>;
@@ -765,6 +1035,7 @@ export interface Repository<T = Record<string, any>> {
765
1035
  findById(id: string | number, options?: FindOptions): Promise<T | null>;
766
1036
  findOne(conditions: Partial<T> | Record<string, unknown>, options?: FindOptions): Promise<T | null>;
767
1037
  findAll(options?: FindOptions): Promise<T[]>;
1038
+ find(conditions?: Partial<T> | Record<string, unknown>, options?: FindOptions): Promise<T[]>;
768
1039
  create(data: Partial<T> | Record<string, unknown>): Promise<T>;
769
1040
  createMany(data: (Partial<T> | Record<string, unknown>)[]): Promise<T[]>;
770
1041
  update(id: string | number, data: Partial<T> | Record<string, unknown>): Promise<T | null>;
@@ -777,6 +1048,8 @@ export interface Repository<T = Record<string, any>> {
777
1048
  count(conditions?: Partial<T> | Record<string, unknown>): Promise<number>;
778
1049
  exists(conditions: Partial<T> | Record<string, unknown>): Promise<boolean>;
779
1050
  with(...relations: string[]): QueryBuilder<T>;
1051
+ include(...relations: string[]): QueryBuilder<T>;
1052
+ search(term: string, columns?: string | string[], options?: SearchOptions): QueryBuilder<T>;
780
1053
  model: ModelDefinition<T>;
781
1054
  }
782
1055
 
@@ -855,8 +1128,30 @@ export interface MigrationManager {
855
1128
  unlock(): Promise<void>;
856
1129
  }
857
1130
 
1131
+ export interface WebspressoModels {
1132
+ [key: string]: Record<string, any>;
1133
+ }
1134
+
1135
+ export interface WebspressoServices {
1136
+ [key: string]: { input: any; output: any };
1137
+ }
1138
+
1139
+ export interface WebspressoServiceCaller {
1140
+ <K extends keyof WebspressoServices>(
1141
+ name: K,
1142
+ input?: WebspressoServices[K]['input'],
1143
+ opts?: unknown
1144
+ ): Promise<WebspressoServices[K]['output']>;
1145
+ <TInput = unknown, TOutput = unknown>(
1146
+ name: string,
1147
+ input?: TInput,
1148
+ opts?: unknown
1149
+ ): Promise<TOutput>;
1150
+ }
1151
+
858
1152
  export interface TransactionContext {
859
1153
  trx: Knex.Transaction;
1154
+ getRepository<K extends keyof WebspressoModels>(modelName: K, scopeContext?: ScopeContext): Repository<WebspressoModels[K]>;
860
1155
  getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
861
1156
  createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
862
1157
  }
@@ -868,6 +1163,7 @@ export interface DatabaseInstance {
868
1163
  hasModel(name: string): boolean;
869
1164
  getAllModels(): ModelDefinition<any>[];
870
1165
  registerModel<T = Record<string, any>>(model: ModelDefinition<T>): void;
1166
+ getRepository<K extends keyof WebspressoModels>(modelName: K, scopeContext?: ScopeContext): Repository<WebspressoModels[K]>;
871
1167
  getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
872
1168
  createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
873
1169
  query<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): QueryBuilder<T>;
@@ -974,21 +1270,316 @@ export function validateQueryComplexity(
974
1270
  includes: string[];
975
1271
  };
976
1272
 
977
- // --- Built-in plugins (subset re-exported from main index.js) ---
1273
+ export function buildSearchClause(params: {
1274
+ knex: any;
1275
+ model: any;
1276
+ term: string;
1277
+ columns?: string | string[];
1278
+ options?: SearchOptions;
1279
+ }): {
1280
+ raw: true;
1281
+ sql: string;
1282
+ bindings: any[];
1283
+ boolean: 'and' | 'or';
1284
+ rankClause: { sql: string; bindings: any[] } | null;
1285
+ } | null;
1286
+
1287
+ export function tokenizeSearchTerm(term: string): string[];
1288
+
1289
+ export function detectDialect(knex: any): 'sqlite' | 'postgres' | 'mysql' | 'generic';
1290
+
1291
+ export interface SchemaExplorerPluginOptions {
1292
+ /** Raw JSON API endpoint path (default: '/_schema') */
1293
+ path?: string;
1294
+ /** Force enable/disable raw API (default: auto based on NODE_ENV) */
1295
+ enabled?: boolean;
1296
+ /** Model names to exclude from schema exploration */
1297
+ exclude?: string[];
1298
+ /** Include column metadata (default: true) */
1299
+ includeColumns?: boolean;
1300
+ /** Include relation metadata (default: true) */
1301
+ includeRelations?: boolean;
1302
+ /** Include scope configuration (default: true) */
1303
+ includeScopes?: boolean;
1304
+ /** Custom authorization check for raw endpoint */
1305
+ authorize?: (req: any) => boolean;
1306
+ /** Register interactive page in Admin Panel when adminPanelPlugin is present (default: true) */
1307
+ admin?: boolean;
1308
+ /** Path within admin panel (default: '/schema') */
1309
+ adminPath?: string;
1310
+ /** Admin sidebar menu label (default: 'Schema Explorer') */
1311
+ menuLabel?: string;
1312
+ /** Admin sidebar icon (default: 'database') */
1313
+ menuIcon?: string;
1314
+ /** Admin sidebar menu order (default: 85) */
1315
+ menuOrder?: number;
1316
+ /** Admin page title (default: menuLabel or 'Schema Explorer') */
1317
+ title?: string;
1318
+ /** Admin page title alias */
1319
+ adminTitle?: string;
1320
+ /** Admin API prefix (default: '/schema') */
1321
+ apiPrefix?: string;
1322
+ }
1323
+
1324
+ export interface DashboardPluginOptions {
1325
+ /** Base URL path for the developer dashboard (default: '/_webspresso') */
1326
+ path?: string;
1327
+ /** Custom dashboard title displayed in HTML <title> and header (default: 'Webspresso Developer Dashboard') */
1328
+ title?: string;
1329
+ /** Enable or disable the plugin (default: true) */
1330
+ enabled?: boolean;
1331
+ }
1332
+
1333
+ export function dashboardPlugin(options?: DashboardPluginOptions): WebspressoPlugin;
1334
+ export function devToolbarPlugin(options?: DashboardPluginOptions): WebspressoPlugin;
1335
+ export function devtoolsPlugin(options?: DashboardPluginOptions): WebspressoPlugin;
1336
+
1337
+ export function schemaExplorerPlugin(options?: SchemaExplorerPluginOptions): WebspressoPlugin;
978
1338
 
979
- export function schemaExplorerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1339
+ export interface AdminPanelPluginOptions {
1340
+ /** Database instance (required) */
1341
+ db?: any;
1342
+ /** Base URL path for the admin panel (default: '/_admin') */
1343
+ path?: string;
1344
+ /** Enable or disable the plugin (default: true) */
1345
+ enabled?: boolean;
1346
+ /** Custom admin panel title displayed in HTML <title> and header (default: 'Admin Panel') */
1347
+ title?: string;
1348
+ /** Dynamic page title template where %s is page title and %b is base title (default: '%s | %b') */
1349
+ titleTemplate?: string;
1350
+ /** Session secret for admin auth cookie encryption */
1351
+ sessionSecret?: string;
1352
+ /** Auth manager instance for optional user management integration */
1353
+ auth?: any;
1354
+ /** User management configuration */
1355
+ userManagement?: {
1356
+ enabled?: boolean;
1357
+ model?: string;
1358
+ fields?: Record<string, string>;
1359
+ };
1360
+ /** Staff user management option (default: true) */
1361
+ adminUsers?: boolean | { enabled?: boolean };
1362
+ /** Custom registry configuration callback */
1363
+ configure?: (registry: any) => void;
1364
+ /** POST URL for file uploads */
1365
+ uploadUrl?: string;
1366
+ /** Sanitize rich-text HTML values (default: true) */
1367
+ richTextSanitize?: boolean;
1368
+ /** Auto-register data exchange export/import services (default: true) */
1369
+ dataExchange?: boolean;
1370
+ /** Admin login brute-force protection / rate limiting (default: enabled with 5 attempts per 15 mins) */
1371
+ loginRateLimit?: boolean | {
1372
+ enabled?: boolean;
1373
+ maxAttempts?: number;
1374
+ windowMs?: number;
1375
+ blockDurationMs?: number;
1376
+ };
1377
+ [key: string]: unknown;
1378
+ }
980
1379
 
981
- export function adminPanelPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1380
+ export function adminPanelPlugin(options?: AdminPanelPluginOptions): WebspressoPlugin;
982
1381
 
983
1382
  export function dataExchangePlugin(options?: Record<string, unknown>): WebspressoPlugin;
984
1383
 
985
- export function siteAnalyticsPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1384
+ export interface SiteAnalyticsRetentionConfig {
1385
+ enabled?: boolean;
1386
+ days?: number;
1387
+ errorDays?: number;
1388
+ intervalMs?: number;
1389
+ runOnStart?: boolean;
1390
+ }
1391
+
1392
+ export interface SiteAnalyticsPurgeResult {
1393
+ viewsDeleted: number;
1394
+ errorsDeleted: number;
1395
+ viewsCutoff: string;
1396
+ errorsCutoff: string;
1397
+ }
1398
+
1399
+ export interface SiteAnalyticsPurgeOptions {
1400
+ days?: number;
1401
+ errorDays?: number;
1402
+ olderThan?: Date;
1403
+ errorsOlderThan?: Date;
1404
+ tableName?: string;
1405
+ errorsTableName?: string;
1406
+ }
1407
+
1408
+ export interface SiteAnalyticsPluginOptions {
1409
+ db?: any;
1410
+ tableName?: string;
1411
+ errorsTableName?: string;
1412
+ excludePaths?: (string | RegExp)[];
1413
+ excludeBots?: boolean;
1414
+ customBots?: string[];
1415
+ anonymizeIp?: boolean;
1416
+ trustProxy?: boolean;
1417
+ collectErrors?: boolean;
1418
+ dashboard?: boolean;
1419
+ retentionDays?: number;
1420
+ errorRetentionDays?: number;
1421
+ cleanupIntervalMs?: number;
1422
+ autoCleanup?: boolean;
1423
+ cleanupOnStart?: boolean;
1424
+ retention?: boolean | SiteAnalyticsRetentionConfig;
1425
+ [key: string]: unknown;
1426
+ }
1427
+
1428
+ export function siteAnalyticsPlugin(options?: SiteAnalyticsPluginOptions): WebspressoPlugin & {
1429
+ api?: {
1430
+ purge(options?: SiteAnalyticsPurgeOptions): Promise<SiteAnalyticsPurgeResult>;
1431
+ purgeAnalyticsData(knex: any, options?: SiteAnalyticsPurgeOptions): Promise<SiteAnalyticsPurgeResult>;
1432
+ startCleanup(intervalMs?: number): void;
1433
+ stopCleanup(): void;
1434
+ getRetentionConfig(): SiteAnalyticsRetentionConfig;
1435
+ };
1436
+ dispose?(): void;
1437
+ shutdown?(): void;
1438
+ };
1439
+
1440
+ export interface AuditLogRetentionConfig {
1441
+ enabled?: boolean;
1442
+ autoCleanup?: boolean;
1443
+ days?: number;
1444
+ intervalMs?: number;
1445
+ runOnStart?: boolean;
1446
+ }
1447
+
1448
+ export interface AuditLogPluginOptions {
1449
+ db: any;
1450
+ title?: string;
1451
+ adminTitle?: string;
1452
+ pageTitle?: string;
1453
+ adminPath?: string;
1454
+ tableName?: string;
1455
+ includeDefaultPage?: boolean;
1456
+ apiPrefix?: string;
1457
+ registerRecordAction?: boolean;
1458
+ retentionDays?: number;
1459
+ cleanupIntervalMs?: number;
1460
+ autoCleanup?: boolean;
1461
+ cleanupOnStart?: boolean;
1462
+ cleanupStartDelayMs?: number;
1463
+ retention?: boolean | AuditLogRetentionConfig;
1464
+ [key: string]: unknown;
1465
+ }
1466
+
1467
+ export function auditLogPlugin(options?: AuditLogPluginOptions): WebspressoPlugin & {
1468
+ api?: {
1469
+ purge(options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1470
+ purgeAuditLogs(knex?: any, options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1471
+ startCleanup(intervalMs?: number): void;
1472
+ stopCleanup(): void;
1473
+ getRetentionConfig(): AuditLogRetentionConfig;
1474
+ queryLogs(filters?: Record<string, unknown>): Promise<any[]>;
1475
+ queryTimeline(model: string, recordId: string | number): Promise<any[]>;
1476
+ getRecordTimeline(model: string, recordId: string | number): Promise<any[]>;
1477
+ getMigrationTemplate(tableName?: string): string;
1478
+ };
1479
+ dispose?(): void;
1480
+ shutdown?(): void;
1481
+ };
1482
+
1483
+ export interface EmailRetentionConfig {
1484
+ enabled?: boolean;
1485
+ autoCleanup?: boolean;
1486
+ days?: number;
1487
+ intervalMs?: number;
1488
+ runOnStart?: boolean;
1489
+ }
1490
+
1491
+ export interface EmailPluginOptions {
1492
+ db?: any;
1493
+ title?: string;
1494
+ adminTitle?: string;
1495
+ transport?: any;
1496
+ smtp?: any;
1497
+ defaults?: Record<string, unknown>;
1498
+ templatesDir?: string;
1499
+ templates?: Record<string, string>;
1500
+ tableName?: string;
1501
+ includeAdminPage?: boolean;
1502
+ adminPath?: string;
1503
+ apiPrefix?: string;
1504
+ auth?: any;
1505
+ authEmails?: any;
1506
+ emailTemplates?: any;
1507
+ manifest?: any;
1508
+ logToDb?: boolean;
1509
+ retentionDays?: number;
1510
+ cleanupIntervalMs?: number;
1511
+ autoCleanup?: boolean;
1512
+ cleanupOnStart?: boolean;
1513
+ cleanupStartDelayMs?: number;
1514
+ retention?: boolean | EmailRetentionConfig;
1515
+ [key: string]: unknown;
1516
+ }
986
1517
 
987
- export function auditLogPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1518
+ export function emailPlugin(options?: EmailPluginOptions): WebspressoPlugin & {
1519
+ api?: {
1520
+ send(options: Record<string, unknown>): Promise<any>;
1521
+ sendTemplate(name: string, options: Record<string, unknown>): Promise<any>;
1522
+ preview(nameOrOpts: any, data?: Record<string, unknown>): Promise<any>;
1523
+ registerTemplate(id: string, source: string): void;
1524
+ listTemplates(): any[];
1525
+ verifyConnection(): Promise<any>;
1526
+ queryLogs(filters?: Record<string, unknown>): Promise<any>;
1527
+ purge(options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1528
+ purgeLogs(options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1529
+ startCleanup(intervalMs?: number): void;
1530
+ stopCleanup(): void;
1531
+ getRetentionConfig(): EmailRetentionConfig;
1532
+ getMigrationTemplate(tableName?: string): string;
1533
+ isTransportReady(): boolean;
1534
+ loadFromManifest(chunks: any): void;
1535
+ emailPluginOptionsFromManifest(manifest: any): any;
1536
+ };
1537
+ dispose?(): void;
1538
+ shutdown?(): void;
1539
+ };
988
1540
 
989
1541
  export function recaptchaPlugin(options?: Record<string, unknown>): WebspressoPlugin;
990
1542
 
991
- export function swaggerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1543
+ export interface SwaggerPluginOptions {
1544
+ /** Base path for docs (default: '/_swagger') */
1545
+ path?: string;
1546
+ /** UI playground engine: 'swagger' or 'scalar' (default: 'swagger') */
1547
+ ui?: 'swagger' | 'scalar';
1548
+ /** Enable plugin (defaults to true in development) */
1549
+ enabled?: boolean;
1550
+ /** Custom authorization guard */
1551
+ authorize?: (req: any) => boolean;
1552
+ /** Automatically merge ORM model schemas into components.schemas */
1553
+ includeOrmSchemas?: boolean;
1554
+ /** Exclude specific ORM models from documentation */
1555
+ ormExclude?: string[];
1556
+ /** OpenAPI info.title override */
1557
+ title?: string;
1558
+ /** OpenAPI info.version override */
1559
+ version?: string;
1560
+ /** OpenAPI info.description override */
1561
+ description?: string;
1562
+ /** Primary server URL in spec */
1563
+ serverUrl?: string;
1564
+ /** Theme for Scalar UI ('default', 'purple', 'solarized', etc.) */
1565
+ theme?: string;
1566
+ /** Dark mode default for Scalar UI */
1567
+ darkMode?: boolean;
1568
+ /** Auto-register in Admin Panel if admin-panel plugin is present (default: true) */
1569
+ admin?: boolean;
1570
+ /** Admin menu and page title (default: 'API Docs') */
1571
+ adminTitle?: string;
1572
+ /** Admin menu sort order (default: 92) */
1573
+ adminOrder?: number;
1574
+ /** Custom options passed to Scalar */
1575
+ scalarOptions?: Record<string, unknown>;
1576
+ /** Custom options passed to Swagger UI */
1577
+ swaggerOptions?: Record<string, unknown>;
1578
+ }
1579
+
1580
+ export function swaggerPlugin(options?: SwaggerPluginOptions): WebspressoPlugin;
1581
+ export function openapiPlugin(options?: SwaggerPluginOptions): WebspressoPlugin;
1582
+ export function apiDocsPlugin(options?: SwaggerPluginOptions): WebspressoPlugin;
992
1583
 
993
1584
  export function healthCheckPlugin(options?: Record<string, unknown>): WebspressoPlugin;
994
1585
 
@@ -1124,6 +1715,86 @@ export interface BasicAuthPluginOptions {
1124
1715
 
1125
1716
  export function basicAuthPlugin(options?: BasicAuthPluginOptions): WebspressoPlugin;
1126
1717
 
1718
+ export interface ApiKeyRecord {
1719
+ id?: number | string;
1720
+ name: string;
1721
+ key_hash: string;
1722
+ key_hint: string;
1723
+ prefix?: string;
1724
+ user_id?: number | string | null;
1725
+ scopes?: string[] | string | null;
1726
+ rate_limit?: number | null;
1727
+ metadata?: any;
1728
+ expires_at?: Date | string | null;
1729
+ last_used_at?: Date | string | null;
1730
+ revoked_at?: Date | string | null;
1731
+ created_at?: Date | string;
1732
+ updated_at?: Date | string;
1733
+ user?: any;
1734
+ [key: string]: any;
1735
+ }
1736
+
1737
+ export interface ApiKeyPluginOptions {
1738
+ /** Database instance or Knex instance. */
1739
+ db?: any;
1740
+ /** Knex instance. */
1741
+ knex?: any;
1742
+ /** Table name for API keys. Default 'api_keys'. */
1743
+ tableName?: string;
1744
+ /** Default key prefix. Default 'wsp_live_'. */
1745
+ prefix?: string;
1746
+ /** Random byte length for key generation. Default 24. */
1747
+ byteLength?: number;
1748
+ /** Protect all matching routes globally. Default false. */
1749
+ global?: boolean;
1750
+ /** Route prefixes to protect when global: true. Default true (all routes). */
1751
+ routes?: string | string[] | boolean;
1752
+ /** Path prefixes to bypass authentication. */
1753
+ skipPaths?: string[];
1754
+ /** Custom predicate function to skip authentication. */
1755
+ skip?: (req: Request) => boolean;
1756
+ /** Static in-memory keys for testing, serverless, or microservices. */
1757
+ keys?: Array<{ key?: string; key_hash?: string; name?: string; scopes?: string[] | string; [key: string]: any }> | Record<string, any>;
1758
+ /** Automatically update last_used_at timestamp. Default true. */
1759
+ trackUsage?: boolean;
1760
+ /** Throttle interval in milliseconds for last_used_at updates. Default 60000. */
1761
+ updateLastUsedThrottleMs?: number;
1762
+ /** Allow query parameter extraction (?apiKey=...). Default false. */
1763
+ allowQueryParam?: boolean;
1764
+ /** Query parameter name. Default 'apiKey'. */
1765
+ queryParamName?: string;
1766
+ /** Custom key extraction function. */
1767
+ extractKey?: (req: Request) => string | null;
1768
+ /** Custom key lookup by SHA-256 hash. */
1769
+ findKeyByHash?: (hash: string, req?: Request) => Promise<ApiKeyRecord | null> | ApiKeyRecord | null;
1770
+ /** Custom verification hook. */
1771
+ verify?: (rawKey: string, req?: Request) => Promise<ApiKeyRecord | null> | ApiKeyRecord | null;
1772
+ /** Custom user loader for key. */
1773
+ getUser?: (record: ApiKeyRecord, req?: Request) => Promise<any> | any;
1774
+ /** Callback hook on successful authentication. */
1775
+ onAuthenticated?: (req: Request, record: ApiKeyRecord) => void | Promise<void>;
1776
+ /** Custom 401 Unauthorized response handler, string, or object. */
1777
+ unauthorizedResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1778
+ /** Custom 403 Forbidden response handler, string, or object. */
1779
+ forbiddenResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1780
+ }
1781
+
1782
+ export interface ApiKeyRouteOptions {
1783
+ /** Required scope(s) for this route. Supports wildcards ('*', 'reports:*'). */
1784
+ scopes?: string | string[];
1785
+ /** Whether all required scopes must be present (true) or any scope (false). Default true. */
1786
+ requireAllScopes?: boolean;
1787
+ /** If true, allows requests without API key to pass through without error. Default false. */
1788
+ optional?: boolean;
1789
+ /** Custom 401 Unauthorized response. */
1790
+ unauthorizedResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1791
+ /** Custom 403 Forbidden response. */
1792
+ forbiddenResponse?: ((req: Request, res: Response, info?: any) => unknown) | string | Record<string, unknown>;
1793
+ }
1794
+
1795
+ export function apiKeyPlugin(options?: ApiKeyPluginOptions): WebspressoPlugin;
1796
+ export function createApiKeyMiddleware(options?: ApiKeyPluginOptions & ApiKeyRouteOptions): RequestHandler;
1797
+
1127
1798
  export interface ContentPluginOptions {
1128
1799
  db: import('./index').Database;
1129
1800
  adminPath?: string;
@@ -1183,6 +1854,42 @@ export function createLocalFileProvider(options?: {
1183
1854
  publicBasePath?: string;
1184
1855
  }): UploadStorageProvider;
1185
1856
 
1857
+ export interface OrphanedUploadsResult {
1858
+ destDir: string;
1859
+ totalFiles: number;
1860
+ totalBytes: number;
1861
+ totalBytesFormatted: string;
1862
+ referencedCount: number;
1863
+ orphanedCount: number;
1864
+ orphanedBytes: number;
1865
+ orphanedBytesFormatted: string;
1866
+ orphanedFiles: Array<{
1867
+ relativePath: string;
1868
+ absolutePath: string;
1869
+ size: number;
1870
+ publicUrl: string;
1871
+ mtime: Date;
1872
+ }>;
1873
+ dryRun?: boolean;
1874
+ deletedCount?: number;
1875
+ deletedBytes?: number;
1876
+ deletedBytesFormatted?: string;
1877
+ deletedFiles?: string[];
1878
+ }
1879
+
1880
+ export function scanOrphanedUploads(options?: {
1881
+ db?: any;
1882
+ destDir?: string;
1883
+ publicBasePath?: string;
1884
+ }): Promise<OrphanedUploadsResult>;
1885
+
1886
+ export function pruneOrphanedUploads(options?: {
1887
+ db?: any;
1888
+ destDir?: string;
1889
+ publicBasePath?: string;
1890
+ dryRun?: boolean;
1891
+ }): Promise<OrphanedUploadsResult>;
1892
+
1186
1893
  // --- Realtime Layer Types ---
1187
1894
 
1188
1895
  export interface RealtimeSubscriptionCallbacks {
@@ -1508,6 +2215,13 @@ export class MemoryQueueAdapter implements QueueAdapter {
1508
2215
  getJob(jobId: string | number): Promise<Job | null>;
1509
2216
  getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1510
2217
  clear(): Promise<void>;
2218
+ clean?(options?: {
2219
+ status?: string | string[];
2220
+ days?: number;
2221
+ retentionDays?: number;
2222
+ olderThan?: Date | string | number;
2223
+ limit?: number;
2224
+ }): Promise<number>;
1511
2225
  }
1512
2226
 
1513
2227
  export class DatabaseQueueAdapter implements QueueAdapter {
@@ -1521,6 +2235,13 @@ export class DatabaseQueueAdapter implements QueueAdapter {
1521
2235
  getJob(jobId: string | number): Promise<Job | null>;
1522
2236
  getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1523
2237
  clear(): Promise<void>;
2238
+ clean(options?: {
2239
+ status?: string | string[];
2240
+ days?: number;
2241
+ retentionDays?: number;
2242
+ olderThan?: Date | string | number;
2243
+ limit?: number;
2244
+ }): Promise<number>;
1524
2245
  }
1525
2246
 
1526
2247
  export class RedisQueueAdapter implements QueueAdapter {
@@ -1533,6 +2254,13 @@ export class RedisQueueAdapter implements QueueAdapter {
1533
2254
  getJob(jobId: string | number): Promise<Job | null>;
1534
2255
  getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1535
2256
  clear(): Promise<void>;
2257
+ clean(options?: {
2258
+ status?: string | string[];
2259
+ days?: number;
2260
+ retentionDays?: number;
2261
+ olderThan?: Date | string | number;
2262
+ limit?: number;
2263
+ }): Promise<number>;
1536
2264
  }
1537
2265
 
1538
2266
  export interface DispatchOptions {
@@ -1543,28 +2271,130 @@ export interface DispatchOptions {
1543
2271
  backoff?: number | { type?: 'exponential' | 'fixed'; delay?: number };
1544
2272
  }
1545
2273
 
1546
- export class QueueManager extends import('events').EventEmitter {
2274
+ export interface QueueRetentionConfig {
2275
+ enabled?: boolean;
2276
+ autoCleanup?: boolean;
2277
+ days?: number;
2278
+ statuses?: string[];
2279
+ intervalMs?: number;
2280
+ runOnStart?: boolean;
2281
+ }
2282
+
2283
+ export interface ScheduleOptions {
2284
+ id?: string;
2285
+ overlap?: boolean;
2286
+ maxRuns?: number;
2287
+ utc?: boolean;
2288
+ runImmediately?: boolean;
2289
+ delay?: number | Date;
2290
+ attempts?: number;
2291
+ priority?: number;
2292
+ timeout?: number;
2293
+ backoff?: number | { type?: 'exponential' | 'fixed'; delay?: number };
2294
+ [key: string]: unknown;
2295
+ }
2296
+
2297
+ export interface ScheduleInfo {
2298
+ id: string;
2299
+ name: string;
2300
+ cronExpression: string;
2301
+ cron?: CronExpression;
2302
+ data: unknown;
2303
+ options: ScheduleOptions;
2304
+ runsCount: number;
2305
+ lastRunAt: Date | null;
2306
+ nextRunAt: Date | null;
2307
+ isRunning: boolean;
2308
+ activeJobId: string | number | null;
2309
+ registeredAt: Date;
2310
+ }
2311
+
2312
+ export class CronExpression {
2313
+ rawExpression: string;
2314
+ resolvedExpression: string;
2315
+ utc: boolean;
2316
+ minutes: Set<number>;
2317
+ hours: Set<number>;
2318
+ doms: Set<number>;
2319
+ months: Set<number>;
2320
+ dows: Set<number>;
2321
+ constructor(expression: string, options?: { utc?: boolean });
2322
+ match(date?: Date): boolean;
2323
+ getNextDate(fromDate?: Date): Date | null;
2324
+ getNextDates(count?: number, fromDate?: Date): Date[];
2325
+ static isValid(expression: string): boolean;
2326
+ static parse(expression: string, options?: { utc?: boolean }): CronExpression;
2327
+ }
2328
+
2329
+ export function parseCron(expression: string, options?: { utc?: boolean }): CronExpression;
2330
+ export function isValidCron(expression: string): boolean;
2331
+ export function resolveCronAlias(str: string): string;
2332
+ export const CRON_ALIASES: Record<string, string>;
2333
+
2334
+ export class TaskScheduler {
2335
+ queue: QueueManager;
2336
+ maxIntervalMs: number;
2337
+ isRunning: boolean;
2338
+ schedules: Map<string, any>;
2339
+ constructor(queue: QueueManager, options?: { autoStart?: boolean; maxIntervalMs?: number });
2340
+ schedule(cronExpression: string | CronExpression, jobName: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2341
+ cron(cronExpression: string | CronExpression, jobName: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2342
+ unschedule(idOrJobName: string): boolean;
2343
+ getSchedules(): ScheduleInfo[];
2344
+ getSchedule(idOrJobName: string): ScheduleInfo | null;
2345
+ trigger(idOrJobName: string): Promise<Job | null>;
2346
+ start(): this;
2347
+ stop(): this;
2348
+ clear(): void;
2349
+ destroy(): void;
2350
+ }
2351
+
2352
+ export class QueueManager extends EventEmitter {
1547
2353
  adapter: QueueAdapter;
1548
2354
  concurrency: number;
1549
2355
  pollInterval: number;
1550
2356
  isRunning: boolean;
1551
2357
  isPaused: boolean;
1552
2358
  isDraining: boolean;
2359
+ retentionConfig: QueueRetentionConfig;
2360
+ scheduler: TaskScheduler;
1553
2361
  constructor(options?: {
1554
2362
  adapter?: QueueAdapter;
1555
2363
  concurrency?: number;
1556
2364
  pollInterval?: number;
1557
2365
  jobsDir?: string | null;
1558
2366
  autoStart?: boolean;
2367
+ retention?: boolean | QueueRetentionConfig;
2368
+ retentionDays?: number;
2369
+ retentionStatuses?: string[];
2370
+ cleanupIntervalMs?: number;
2371
+ autoCleanup?: boolean;
2372
+ cleanupOnStart?: boolean;
1559
2373
  });
1560
2374
  define(name: string, handler: (job: Job, ctx: { queue: QueueManager }) => Promise<any> | any, options?: DispatchOptions): this;
1561
2375
  has(name: string): boolean;
1562
2376
  dispatch(name: string, data?: unknown, options?: DispatchOptions): Promise<Job>;
2377
+ schedule(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2378
+ cron(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo;
2379
+ unschedule(idOrJobName: string): boolean;
2380
+ getSchedules(): ScheduleInfo[];
2381
+ getSchedule(idOrJobName: string): ScheduleInfo | null;
1563
2382
  start(): this;
1564
2383
  pause(): this;
1565
2384
  resume(): this;
1566
2385
  drain(timeoutMs?: number): Promise<void>;
1567
2386
  stop(): Promise<void>;
2387
+ clean(options?: {
2388
+ status?: string | string[];
2389
+ days?: number;
2390
+ retentionDays?: number;
2391
+ olderThan?: Date | string | number;
2392
+ limit?: number;
2393
+ }): Promise<number>;
2394
+ runCleanup(): Promise<number>;
2395
+ startCleanup(customIntervalMs?: number): void;
2396
+ stopCleanup(): void;
2397
+ getRetentionConfig(): QueueRetentionConfig;
1568
2398
  getStats(): Promise<Record<string, unknown>>;
1569
2399
  getJob(jobId: string | number): Promise<Job | null>;
1570
2400
  loadJobsFromDirectory(dirPath: string): void;
@@ -1576,6 +2406,12 @@ export function createQueueManager(options?: {
1576
2406
  pollInterval?: number;
1577
2407
  jobsDir?: string | null;
1578
2408
  autoStart?: boolean;
2409
+ retention?: boolean | QueueRetentionConfig;
2410
+ retentionDays?: number;
2411
+ retentionStatuses?: string[];
2412
+ cleanupIntervalMs?: number;
2413
+ autoCleanup?: boolean;
2414
+ cleanupOnStart?: boolean;
1579
2415
  }): QueueManager;
1580
2416
 
1581
2417
  export function queuePlugin(options?: {
@@ -1588,7 +2424,28 @@ export function queuePlugin(options?: {
1588
2424
  pollInterval?: number;
1589
2425
  jobsDir?: string;
1590
2426
  autoStart?: boolean;
1591
- }): PluginDefinition;
2427
+ retention?: boolean | QueueRetentionConfig;
2428
+ retentionDays?: number;
2429
+ retentionStatuses?: string[];
2430
+ cleanupIntervalMs?: number;
2431
+ autoCleanup?: boolean;
2432
+ cleanupOnStart?: boolean;
2433
+ }): WebspressoPlugin & {
2434
+ api?: {
2435
+ getQueueManager(): QueueManager | null;
2436
+ clean(options?: {
2437
+ status?: string | string[];
2438
+ days?: number;
2439
+ retentionDays?: number;
2440
+ olderThan?: Date | string | number;
2441
+ limit?: number;
2442
+ }): Promise<number>;
2443
+ schedule(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo | undefined;
2444
+ cron(cronExpression: string | CronExpression, name: string, data?: unknown, options?: ScheduleOptions): ScheduleInfo | undefined;
2445
+ unschedule(idOrJobName: string): boolean | undefined;
2446
+ getSchedules(): ScheduleInfo[];
2447
+ };
2448
+ };
1592
2449
 
1593
2450
  // --- SSR Streaming & Chunked Transfer ---
1594
2451
 
@@ -1619,6 +2476,54 @@ export const ssr: {
1619
2476
  renderStream: typeof renderStream;
1620
2477
  };
1621
2478
 
2479
+ // --- Server-Sent Events (SSE) Streaming ---
2480
+
2481
+ export interface SseOptions {
2482
+ status?: number;
2483
+ headers?: Record<string, string>;
2484
+ heartbeat?: number | boolean;
2485
+ heartbeatComment?: string;
2486
+ retry?: number;
2487
+ map?: (chunk: any) => any;
2488
+ }
2489
+
2490
+ export interface SseEventObject {
2491
+ data?: any;
2492
+ event?: string;
2493
+ id?: string | number;
2494
+ retry?: number;
2495
+ }
2496
+
2497
+ export class ServerSentEventsStream extends EventEmitter {
2498
+ req: import('http').IncomingMessage;
2499
+ res: import('http').ServerResponse;
2500
+ options: SseOptions;
2501
+ isOpen: boolean;
2502
+ isClosed: boolean;
2503
+
2504
+ constructor(req: import('http').IncomingMessage, res: import('http').ServerResponse, options?: SseOptions);
2505
+ send(payloadOrData: any | SseEventObject, event?: string, id?: string | number): Promise<boolean>;
2506
+ comment(text: string): boolean;
2507
+ heartbeat(comment?: string): boolean;
2508
+ retry(ms: number): boolean;
2509
+ pipe(source: AsyncIterable<any> | Iterable<any> | import('stream').Readable, mapFn?: (chunk: any) => any): Promise<void>;
2510
+ stream(generatorFn: (stream: ServerSentEventsStream) => any): Promise<void>;
2511
+ close(): void;
2512
+ }
2513
+
2514
+ export function createSseStream(
2515
+ req: import('http').IncomingMessage,
2516
+ res: import('http').ServerResponse,
2517
+ sourceOrOptions?: SseOptions | AsyncIterable<any> | Iterable<any> | import('stream').Readable | ((stream: ServerSentEventsStream) => any),
2518
+ maybeOptions?: SseOptions
2519
+ ): ServerSentEventsStream | Promise<void>;
2520
+
2521
+ export const sseStream: {
2522
+ ServerSentEventsStream: typeof ServerSentEventsStream;
2523
+ createSseStream: typeof createSseStream;
2524
+ formatSseFrame: (data?: any, event?: string, id?: string | number, retry?: number) => string;
2525
+ };
2526
+
1622
2527
  // --- Model Context Protocol (MCP) Plugin ---
1623
2528
 
1624
2529
  export interface McpPluginOptions {
@@ -1863,7 +2768,7 @@ export interface PageContext<TParams = Record<string, string>, TQuery = Record<s
1863
2768
  res: import('express').Response;
1864
2769
  params: TParams;
1865
2770
  query: TQuery;
1866
- service: <TInput = unknown, TOutput = unknown>(name: string, input?: TInput, opts?: unknown) => Promise<TOutput>;
2771
+ service: WebspressoServiceCaller;
1867
2772
  module: string | null;
1868
2773
  config: Record<string, unknown>;
1869
2774
  logger: Console;
@@ -1897,7 +2802,7 @@ export interface ApiContext<TBody = any, TParams = Record<string, string>, TQuer
1897
2802
  query: TQuery;
1898
2803
  body: TBody;
1899
2804
  input: { body?: TBody; params?: TParams; query?: TQuery; headers?: Record<string, string> };
1900
- service: <TInput = unknown, TOutput = unknown>(name: string, input?: TInput, opts?: unknown) => Promise<TOutput>;
2805
+ service: WebspressoServiceCaller;
1901
2806
  module: string | null;
1902
2807
  config: Record<string, unknown>;
1903
2808
  logger: Console;
@@ -1980,6 +2885,279 @@ export function compileRouteTable(
1980
2885
  options?: { explicitRoutes?: Array<{ method?: string; path: string }>; isDev?: boolean }
1981
2886
  ): RouteTable;
1982
2887
 
2888
+ export interface GenerateTypesOptions {
2889
+ /** Root directory of the project (default: process.cwd()) */
2890
+ rootDir?: string;
2891
+ /** Alias for rootDir */
2892
+ cwd?: string;
2893
+ /** Models directory relative to root (default: 'models') */
2894
+ modelsDir?: string;
2895
+ /** Services directory relative to root (default: 'services') */
2896
+ servicesDir?: string;
2897
+ /** Pages directory relative to root (default: 'pages') */
2898
+ pagesDir?: string;
2899
+ /** Output directory (default: '.webspresso') */
2900
+ outDir?: string;
2901
+ /** Custom output file path (default: '.webspresso/types.d.ts') */
2902
+ outFile?: string;
2903
+ /** Create companion webspresso-env.d.ts at project root (default: true) */
2904
+ createEnvFile?: boolean;
2905
+ /** Write declaration files to disk (default: true) */
2906
+ write?: boolean;
2907
+ /** Suppress console output (default: false) */
2908
+ silent?: boolean;
2909
+ }
2910
+
2911
+ export interface GenerateTypesResult {
2912
+ code: string;
2913
+ modelsCount: number;
2914
+ servicesCount: number;
2915
+ routesCount: number;
2916
+ outFile: string;
2917
+ envFile: string | null;
2918
+ }
2919
+
2920
+ export function generateTypes(options?: GenerateTypesOptions): GenerateTypesResult;
2921
+
2922
+ // --- OAuth 2.0 & Social Login Plugin ---
2923
+
2924
+ export class OAuthError extends Error {
2925
+ code: string;
2926
+ details?: any;
2927
+ constructor(message: string, code?: string, details?: any);
2928
+ }
2929
+
2930
+ export interface OAuthUserProfile {
2931
+ provider: string;
2932
+ id: string;
2933
+ email?: string | null;
2934
+ emailVerified?: boolean;
2935
+ name?: string | null;
2936
+ firstName?: string | null;
2937
+ lastName?: string | null;
2938
+ username?: string | null;
2939
+ avatarUrl?: string | null;
2940
+ bio?: string | null;
2941
+ location?: string | null;
2942
+ locale?: string | null;
2943
+ raw: Record<string, any>;
2944
+ [key: string]: any;
2945
+ }
2946
+
2947
+ export interface OAuthProviderConfig {
2948
+ name?: string;
2949
+ clientId: string;
2950
+ clientSecret: string;
2951
+ redirectUri?: string;
2952
+ authorizationUrl?: string;
2953
+ tokenUrl?: string;
2954
+ userInfoUrl?: string;
2955
+ scopes?: string[] | string;
2956
+ defaultScopes?: string[];
2957
+ params?: Record<string, any>;
2958
+ normalizeProfile?: (raw: any, extra?: any) => OAuthUserProfile;
2959
+ fetchExtra?: (tokens: any) => Promise<any>;
2960
+ }
2961
+
2962
+ export interface OAuthClientConfig {
2963
+ secret?: string;
2964
+ stateTtlMs?: number;
2965
+ providers?: Record<string, OAuthProviderConfig>;
2966
+ }
2967
+
2968
+ export class OAuthClient {
2969
+ secret: string;
2970
+ stateTtlMs: number;
2971
+ providers: Record<string, OAuthProviderConfig>;
2972
+ constructor(config?: OAuthClientConfig);
2973
+ registerProvider(name: string, options?: OAuthProviderConfig): this;
2974
+ getProvider(name: string): OAuthProviderConfig;
2975
+ getProviderNames(): string[];
2976
+ getAuthorizationUrl(providerName: string, options?: {
2977
+ redirectUri?: string;
2978
+ scopes?: string[] | string;
2979
+ stateData?: Record<string, any>;
2980
+ pkce?: boolean;
2981
+ extraParams?: Record<string, any>;
2982
+ }): { url: string; state: string; codeVerifier?: string };
2983
+ exchangeCode(providerName: string, params: {
2984
+ code: string;
2985
+ redirectUri?: string;
2986
+ codeVerifier?: string;
2987
+ }): Promise<Record<string, any>>;
2988
+ fetchUserProfile(providerName: string, tokens: Record<string, any>): Promise<OAuthUserProfile>;
2989
+ handleCallback(providerName: string, params: {
2990
+ code: string;
2991
+ state: string;
2992
+ redirectUri?: string;
2993
+ }): Promise<{ provider: string; user: OAuthUserProfile; tokens: Record<string, any>; stateData: Record<string, any> }>;
2994
+ }
2995
+
2996
+ export function createOAuthClient(config?: OAuthClientConfig): OAuthClient;
2997
+ export function generatePKCE(): { codeVerifier: string; codeChallenge: string; codeChallengeMethod: 'S256' };
2998
+ export function createSignedState(payload: Record<string, any>, secret: string): string;
2999
+ export function verifySignedState(stateString: string, secret: string, maxAgeMs?: number): Record<string, any>;
3000
+
3001
+ export interface OAuthPluginOptions {
3002
+ client?: OAuthClient;
3003
+ providers?: Record<string, OAuthProviderConfig>;
3004
+ secret?: string;
3005
+ pathPrefix?: string;
3006
+ routes?: boolean;
3007
+ pkce?: boolean;
3008
+ stateTtlMs?: number;
3009
+ defaultReturnTo?: string;
3010
+ failureRedirect?: string;
3011
+ findOrCreateUser?: (params: {
3012
+ provider: string;
3013
+ profile: OAuthUserProfile;
3014
+ tokens: Record<string, any>;
3015
+ stateData: Record<string, any>;
3016
+ req: any;
3017
+ res: any;
3018
+ db?: any;
3019
+ }) => Promise<any>;
3020
+ onSuccess?: (params: {
3021
+ provider: string;
3022
+ user: any;
3023
+ profile: OAuthUserProfile;
3024
+ tokens: Record<string, any>;
3025
+ stateData: Record<string, any>;
3026
+ req: any;
3027
+ res: any;
3028
+ client: OAuthClient;
3029
+ }) => Promise<void | any>;
3030
+ onError?: (params: {
3031
+ error: Error | OAuthError;
3032
+ provider: string;
3033
+ req: any;
3034
+ res: any;
3035
+ client: OAuthClient;
3036
+ }) => Promise<void | any>;
3037
+ db?: any;
3038
+ }
3039
+
3040
+ export function oauthPlugin(options?: OAuthPluginOptions): WebspressoPlugin;
3041
+
3042
+ // ==========================================
3043
+ // 2FA / TOTP (RFC 6238, RFC 4226, RFC 4648)
3044
+ // ==========================================
3045
+
3046
+ export interface Base32EncodeOptions {
3047
+ padding?: boolean;
3048
+ }
3049
+
3050
+ export interface TotpSecretOptions extends Base32EncodeOptions {
3051
+ length?: number;
3052
+ }
3053
+
3054
+ export interface HotpOptions {
3055
+ digits?: number;
3056
+ algorithm?: 'sha1' | 'sha256' | 'sha512' | string;
3057
+ encoding?: 'base32' | 'ascii' | 'utf8' | 'hex';
3058
+ }
3059
+
3060
+ export interface TotpOptions extends HotpOptions {
3061
+ time?: number | Date;
3062
+ step?: number;
3063
+ epoch?: number;
3064
+ }
3065
+
3066
+ export interface TotpVerifyOptions extends TotpOptions {
3067
+ window?: number;
3068
+ detailed?: boolean;
3069
+ }
3070
+
3071
+ export interface TotpUriOptions {
3072
+ secret: string;
3073
+ account?: string;
3074
+ issuer?: string;
3075
+ label?: string;
3076
+ algorithm?: string;
3077
+ digits?: number;
3078
+ period?: number;
3079
+ }
3080
+
3081
+ export interface QrSvgOptions {
3082
+ size?: number;
3083
+ margin?: number;
3084
+ darkColor?: string;
3085
+ lightColor?: string;
3086
+ version?: number;
3087
+ ecc?: 'L' | 'M' | 'Q' | 'H';
3088
+ mask?: number;
3089
+ }
3090
+
3091
+ export interface RecoveryCodeOptions {
3092
+ count?: number;
3093
+ length?: number;
3094
+ hyphen?: boolean;
3095
+ }
3096
+
3097
+ export interface TotpSetupOptions extends TotpSecretOptions {
3098
+ user?: any;
3099
+ account?: string;
3100
+ issuer?: string;
3101
+ recoveryCodeCount?: number;
3102
+ qrOptions?: QrSvgOptions;
3103
+ algorithm?: string;
3104
+ digits?: number;
3105
+ period?: number;
3106
+ }
3107
+
3108
+ export interface TotpSetupResult {
3109
+ secret: string;
3110
+ uri: string;
3111
+ qrSvg: string;
3112
+ qrDataUri: string;
3113
+ recoveryCodes: string[];
3114
+ recoveryCodesHashed: string[];
3115
+ }
3116
+
3117
+ export function base32Encode(buffer: Buffer | string, options?: Base32EncodeOptions): string;
3118
+ export function base32Decode(input: string): Buffer;
3119
+ export function generateTotpSecret(options?: TotpSecretOptions): string;
3120
+ export function generateHotp(secret: string | Buffer, counter: number | bigint, options?: HotpOptions): string;
3121
+ export function verifyHotp(token: string, secret: string | Buffer, counter: number | bigint, options?: HotpOptions): boolean;
3122
+ export function generateTotp(secret: string | Buffer, options?: TotpOptions): string;
3123
+ export function verifyTotp(token: string, secret: string | Buffer, options?: TotpVerifyOptions): boolean | { valid: boolean; delta: number | null };
3124
+ export function generateTotpUri(options: TotpUriOptions): string;
3125
+ export function generateTotpQrSvg(uriOrOptions: string | TotpUriOptions, svgOptions?: QrSvgOptions): string;
3126
+ export function generateTotpQrDataUri(uriOrOptions: string | TotpUriOptions, svgOptions?: QrSvgOptions): string;
3127
+ export function generateRecoveryCodes(options?: RecoveryCodeOptions): string[];
3128
+ export function hashRecoveryCode(code: string): string;
3129
+ export function verifyRecoveryCode(code: string, hashedCodes: string[]): { valid: boolean; matchedIndex: number; remainingHashedCodes: string[] };
3130
+ export function createTotpSetup(options?: TotpSetupOptions): TotpSetupResult;
3131
+
3132
+ export function createQrMatrix(text: string, options?: QrSvgOptions): { matrix: boolean[][]; size: number; version: number };
3133
+ export function generateQrSvg(text: string, options?: QrSvgOptions): string;
3134
+ export function generateQrDataUri(text: string, options?: QrSvgOptions): string;
3135
+
3136
+ export namespace totp {
3137
+ export {
3138
+ base32Encode,
3139
+ base32Decode,
3140
+ generateTotpSecret,
3141
+ generateHotp,
3142
+ verifyHotp,
3143
+ generateTotp,
3144
+ verifyTotp,
3145
+ generateTotpUri,
3146
+ generateTotpQrSvg,
3147
+ generateTotpQrDataUri,
3148
+ generateRecoveryCodes,
3149
+ hashRecoveryCode,
3150
+ verifyRecoveryCode,
3151
+ createTotpSetup,
3152
+ createQrMatrix,
3153
+ generateQrSvg,
3154
+ generateQrDataUri,
3155
+ };
3156
+ }
3157
+
3158
+
3159
+
3160
+
1983
3161
 
1984
3162
 
1985
3163