webspresso 0.0.99 → 0.0.101

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 (108) hide show
  1. package/.agents/ADMIN.md +47 -4
  2. package/.agents/AGENTS.md +4 -2
  3. package/.agents/AUTH.md +309 -0
  4. package/.agents/CLI.md +128 -0
  5. package/.agents/EMAIL.md +84 -2
  6. package/.agents/ERRORS.md +59 -0
  7. package/.agents/EVENTS.md +179 -0
  8. package/.agents/ORM.md +149 -0
  9. package/.agents/PLUGINS.md +44 -7
  10. package/.agents/QUEUE.md +18 -10
  11. package/.agents/ROUTING.md +97 -1
  12. package/.agents/STREAMING.md +35 -0
  13. package/AGENTS.md +2 -0
  14. package/README.md +145 -93
  15. package/bin/commands/add-docker.js +467 -0
  16. package/bin/commands/db-makemigrations.js +83 -0
  17. package/bin/commands/deploy-check.js +339 -0
  18. package/bin/commands/list.js +96 -0
  19. package/bin/commands/make-command.js +93 -0
  20. package/bin/commands/make-event.js +80 -0
  21. package/bin/commands/make-factory.js +72 -0
  22. package/bin/commands/make-mail.js +93 -0
  23. package/bin/commands/make-policy.js +106 -0
  24. package/bin/commands/run.js +195 -0
  25. package/bin/webspresso.js +21 -0
  26. package/core/auth/bouncer.js +196 -0
  27. package/core/auth/index.js +73 -1
  28. package/core/auth/magic-link.js +170 -0
  29. package/core/auth/manager.js +343 -3
  30. package/core/auth/middleware.js +45 -6
  31. package/core/auth/session-manager.js +255 -0
  32. package/core/auth/sessions.js +351 -0
  33. package/core/auth/tokens.js +1 -0
  34. package/core/auth/totp.js +4 -1
  35. package/core/auth/user-agent.js +161 -0
  36. package/core/commands/define-command.js +48 -0
  37. package/core/commands/index.js +18 -0
  38. package/core/commands/registry.js +156 -0
  39. package/core/commands/runner.js +76 -0
  40. package/core/compression/compressible.js +1 -1
  41. package/core/compression/middleware.js +35 -251
  42. package/core/content/service.js +8 -2
  43. package/core/events/discovery.js +142 -0
  44. package/core/events/dispatcher.js +552 -0
  45. package/core/events/index.js +23 -0
  46. package/core/events/signal.js +92 -0
  47. package/core/forms/flash.js +234 -0
  48. package/core/forms/form-builder.js +214 -0
  49. package/core/forms/index.js +16 -0
  50. package/core/mail/index.js +23 -0
  51. package/core/mail/mailable.js +250 -0
  52. package/core/mail/mailer.js +184 -0
  53. package/core/mail/transports.js +238 -0
  54. package/core/orm/factory.js +250 -0
  55. package/core/orm/index.js +8 -1
  56. package/core/orm/migrations/diff.js +185 -0
  57. package/core/orm/migrations/index.js +22 -4
  58. package/core/orm/model.js +55 -1
  59. package/core/orm/repository.js +283 -13
  60. package/core/orm/scopes.js +3 -2
  61. package/core/orm/types.js +6 -1
  62. package/core/queue/cron.js +252 -39
  63. package/core/queue/manager.js +20 -2
  64. package/core/queue/scheduler.js +115 -59
  65. package/core/ssr/stream.js +62 -2
  66. package/index.d.ts +540 -14
  67. package/index.js +44 -0
  68. package/package.json +11 -5
  69. package/plugins/admin-panel/api.js +509 -3
  70. package/plugins/admin-panel/index.js +17 -0
  71. package/plugins/admin-panel/modules/admin-users.js +1 -1
  72. package/plugins/audit-log/admin-component.js +54 -54
  73. package/plugins/audit-log/index.js +2 -2
  74. package/plugins/content/client/inline-edit.js +2 -46
  75. package/plugins/content/models/content-entry.js +4 -3
  76. package/plugins/content/models/content-type.js +4 -3
  77. package/plugins/cors.js +15 -4
  78. package/plugins/email/auth-bridge.js +66 -0
  79. package/plugins/email/bundled-templates.js +15 -0
  80. package/plugins/email/index.js +48 -0
  81. package/plugins/email/preview-handler.js +555 -0
  82. package/plugins/email/service.js +23 -12
  83. package/plugins/email/templates/auth-magic-link.mjml +13 -0
  84. package/plugins/email/transport.js +30 -11
  85. package/plugins/file-manager/admin-component.js +49 -49
  86. package/plugins/file-manager/index.js +4 -4
  87. package/plugins/index.js +2 -0
  88. package/plugins/redirect/index.js +1 -1
  89. package/plugins/webhooks/admin-component.js +608 -0
  90. package/plugins/webhooks/api-handlers.js +237 -0
  91. package/plugins/webhooks/crypto.js +144 -0
  92. package/plugins/webhooks/dispatcher.js +708 -0
  93. package/plugins/webhooks/index.js +263 -0
  94. package/plugins/webhooks/matcher.js +113 -0
  95. package/plugins/webhooks/migration-template.js +75 -0
  96. package/plugins/webhooks/models.js +98 -0
  97. package/plugins/webhooks/sender.js +176 -0
  98. package/src/api/api-loader.js +1 -1
  99. package/src/app-context.js +48 -6
  100. package/src/bundle/index.js +2 -1
  101. package/src/dev-reload/index.js +9 -1
  102. package/src/discovery/scan-routes.js +10 -1
  103. package/src/file-router.js +173 -4
  104. package/src/helpers.js +150 -1
  105. package/src/njk-frontmatter.js +3 -0
  106. package/src/pages/page-loader.js +1 -1
  107. package/src/routing/cascading.js +120 -4
  108. package/src/server.js +180 -26
package/index.d.ts CHANGED
@@ -509,6 +509,8 @@ export function getAppContext(): {
509
509
  db: DatabaseInstance | null;
510
510
  shutdownManager: ShutdownManager | null;
511
511
  serviceRegistry: ServiceRegistry | null;
512
+ events?: any;
513
+ mailer?: Mailer | null;
512
514
  };
513
515
 
514
516
  export function getDb(): DatabaseInstance;
@@ -523,12 +525,18 @@ export function getServiceRegistry(): ServiceRegistry | null;
523
525
 
524
526
  export function hasServiceRegistry(): boolean;
525
527
 
528
+ export function getMailer(): Mailer | null;
529
+
530
+ export function hasMailer(): boolean;
531
+
526
532
  export function resetAppContext(): void;
527
533
 
528
534
  export function setAppContext(partial: {
529
535
  db?: DatabaseInstance | null;
530
536
  shutdownManager?: ShutdownManager | null;
531
537
  serviceRegistry?: ServiceRegistry | null;
538
+ events?: any;
539
+ mailer?: Mailer | null;
532
540
  }): void;
533
541
 
534
542
  // --- File router ---
@@ -547,6 +555,19 @@ export interface CascadingLayoutInfo {
547
555
  configPath: string | null;
548
556
  }
549
557
 
558
+ export interface CascadingErrorBoundaryInfo {
559
+ dirSegment: string;
560
+ errorRelPath: string;
561
+ fullPath: string;
562
+ configPath: string | null;
563
+ }
564
+
565
+ export interface CascadingLoadingInfo {
566
+ dirSegment: string;
567
+ loadingRelPath: string;
568
+ fullPath: string;
569
+ }
570
+
550
571
  export namespace cascading {
551
572
  export function getDirectoryChain(relativeFilePath: string): string[];
552
573
  export function templateHasExtends(templateBody?: string | null, fullPath?: string): boolean;
@@ -568,6 +589,16 @@ export namespace cascading {
568
589
  renderContext: Record<string, unknown>,
569
590
  isDev?: boolean
570
591
  ): string;
592
+ export function findNearestErrorBoundary(
593
+ baseDir: string,
594
+ relativeFilePath: string,
595
+ isDev?: boolean
596
+ ): CascadingErrorBoundaryInfo | null;
597
+ export function findNearestLoadingTemplate(
598
+ baseDir: string,
599
+ relativeFilePath: string,
600
+ isDev?: boolean
601
+ ): CascadingLoadingInfo | null;
571
602
  export function clearCascadingCaches(): void;
572
603
  }
573
604
 
@@ -649,6 +680,125 @@ export function loadNjkRouteTemplate(
649
680
 
650
681
  export function clearNjkFrontmatterCaches(): void;
651
682
 
683
+ // --- Forms & Flash subsystem (AdonisJS & Django-inspired) ---
684
+
685
+ export interface FlashMessages {
686
+ old?: Record<string, any>;
687
+ errors?: Record<string, string[]>;
688
+ [key: string]: any;
689
+ }
690
+
691
+ export function createFlashMiddleware(options?: { cookieName?: string }): import('express').RequestHandler;
692
+
693
+ export function normalizeErrors(errors: any): Record<string, string[]>;
694
+
695
+ export interface FieldRenderOptions {
696
+ label?: string;
697
+ type?: string;
698
+ placeholder?: string;
699
+ required?: boolean;
700
+ disabled?: boolean;
701
+ readonly?: boolean;
702
+ className?: string;
703
+ wrapperClass?: string;
704
+ errorClass?: string;
705
+ showError?: boolean;
706
+ rows?: number;
707
+ selectOptions?: (string | { label: string; value: any })[];
708
+ helpText?: string;
709
+ value?: any;
710
+ default?: any;
711
+ attributes?: Record<string, string>;
712
+ }
713
+
714
+ export function renderField(name: string, options?: FieldRenderOptions, ctx?: { req?: any; res?: any }): string;
715
+
716
+ export interface FormForOptions {
717
+ action?: string;
718
+ method?: string;
719
+ exclude?: string[];
720
+ include?: string[];
721
+ submitLabel?: string;
722
+ csrf?: boolean;
723
+ formClass?: string;
724
+ }
725
+
726
+ export function renderFormFor(modelOrRepo: any, options?: FormForOptions, ctx?: { req?: any; res?: any }): string;
727
+
728
+ // --- Authorization & Bouncer (AdonisJS Bouncer & Django Permissions-inspired) ---
729
+
730
+ export class Bouncer {
731
+ constructor(user?: any, policyManager?: any);
732
+ user: any;
733
+ forUser(user: any): Bouncer;
734
+ allows(actionOrPolicy: string | Function, ...args: any[]): boolean;
735
+ denies(actionOrPolicy: string | Function, ...args: any[]): boolean;
736
+ authorize(actionOrPolicy: string | Function, ...args: any[]): boolean;
737
+ }
738
+
739
+ export function createBouncer(user?: any, policyManager?: any): Bouncer;
740
+
741
+ export function createBouncerMiddleware(options?: { policyManager?: any }): import('express').RequestHandler;
742
+
743
+ export function definePolicy(name: string, rules: Record<string, (user: any, ...args: any[]) => boolean | Promise<boolean>>): void;
744
+
745
+ export function defineGate(name: string, callback: (user: any, ...args: any[]) => boolean | Promise<boolean>): void;
746
+
747
+ export function can(user: any, action: string, policy?: string | null, resource?: any): boolean;
748
+
749
+ export function cannot(user: any, action: string, policy?: string | null, resource?: any): boolean;
750
+
751
+ export function authorize(user: any, action: string, policy?: string | null, resource?: any): void;
752
+
753
+ // --- Decoupled Events & Signals (AdonisJS Events & Django Signals-inspired) ---
754
+
755
+ export interface EventListenerOptions {
756
+ sender?: any;
757
+ priority?: number;
758
+ once?: boolean;
759
+ }
760
+
761
+ export class EventDispatcher {
762
+ constructor(options?: { queue?: any; maxListeners?: number; logger?: any });
763
+ on(event: string, handler: Function, options?: EventListenerOptions): () => void;
764
+ once(event: string, handler: Function, options?: EventListenerOptions): () => void;
765
+ off(event: string, handler: Function): this;
766
+ onAny(handler: (eventName: string, data: any, meta: any) => any, options?: { priority?: number; once?: boolean }): () => void;
767
+ emit(eventOrInstance: string | object, data?: any, options?: { sender?: any }): boolean;
768
+ emitAsync(eventOrInstance: string | object, data?: any, options?: { sender?: any }): Promise<Array<{ status: 'fulfilled' | 'rejected'; value?: any; reason?: any }>>;
769
+ emitParallel(eventOrInstance: string | object, data?: any, options?: { sender?: any }): Promise<Array<{ status: 'fulfilled' | 'rejected'; value?: any; reason?: any }>>;
770
+ emitSerial(eventOrInstance: string | object, data?: any, options?: { sender?: any }): Promise<any[]>;
771
+ emitWaterfall(eventOrInstance: string | object, initialData: any, options?: { sender?: any }): Promise<any>;
772
+ listenAndQueue(event: string, jobName: string, queueOptions?: Record<string, unknown>): () => void;
773
+ bridgeOrm(modelEvents?: any): () => void;
774
+ setQueue(queueManager: any): this;
775
+ eventNames(): string[];
776
+ listenerCount(event?: string): number;
777
+ removeAllListeners(event?: string): this;
778
+ clear(): void;
779
+ }
780
+
781
+ export function createEventDispatcher(options?: { queue?: any; maxListeners?: number; logger?: any }): EventDispatcher;
782
+
783
+ export const events: EventDispatcher;
784
+
785
+ export class Signal {
786
+ constructor(name: string, dispatcher: EventDispatcher);
787
+ name: string;
788
+ dispatcher: EventDispatcher;
789
+ connect(receiver: Function, options?: EventListenerOptions): () => void;
790
+ disconnect(receiver: Function): this;
791
+ send(data?: any, options?: { sender?: any }): boolean;
792
+ sendAsync(data?: any, options?: { sender?: any }): Promise<Array<{ status: 'fulfilled' | 'rejected'; value?: any; reason?: any }>>;
793
+ sendSerial(data?: any, options?: { sender?: any }): Promise<any[]>;
794
+ }
795
+
796
+ export function defineSignal(name: string, dispatcher?: EventDispatcher): Signal;
797
+
798
+ export function defineListener(event: string | string[], handler: Function, options?: EventListenerOptions): { event: string | string[]; handle: Function; [key: string]: any };
799
+
800
+ export function loadListeners(dirPath: string, dispatcher: EventDispatcher, options?: { logger?: any }): Array<{ file: string; event: string; handler: Function }>;
801
+
652
802
  // --- Helpers / assets ---
653
803
 
654
804
  export function createHelpers(context: Record<string, unknown>): Record<string, unknown>;
@@ -745,11 +895,20 @@ export interface ScopeContext {
745
895
 
746
896
  export type RelationType = 'belongsTo' | 'hasMany' | 'hasOne';
747
897
 
898
+ export type RelationCascadeOption =
899
+ | boolean
900
+ | 'delete'
901
+ | 'restore'
902
+ | 'all'
903
+ | ('delete' | 'restore' | 'all')[]
904
+ | { delete?: boolean; restore?: boolean };
905
+
748
906
  export interface RelationDefinition {
749
907
  type: RelationType;
750
- model: () => ModelDefinition;
908
+ model: string | (() => ModelDefinition<any> | string);
751
909
  foreignKey: string;
752
910
  localKey?: string;
911
+ cascade?: RelationCascadeOption;
753
912
  }
754
913
 
755
914
  export interface AdminPermissions {
@@ -955,6 +1114,8 @@ export function hasModel(name: string): boolean;
955
1114
 
956
1115
  export function clearRegistry(): void;
957
1116
 
1117
+ export function isCascadeEnabled(relation: RelationDefinition, operation: 'delete' | 'restore'): boolean;
1118
+
958
1119
  // --- ORM: schema & columns ---
959
1120
 
960
1121
  /** zdb column helpers (id, string, timestamp, …) + `schema()` */
@@ -1040,9 +1201,9 @@ export interface Repository<T = Record<string, any>> {
1040
1201
  createMany(data: (Partial<T> | Record<string, unknown>)[]): Promise<T[]>;
1041
1202
  update(id: string | number, data: Partial<T> | Record<string, unknown>): Promise<T | null>;
1042
1203
  updateWhere(conditions: Partial<T> | Record<string, unknown>, data: Partial<T> | Record<string, unknown>): Promise<number>;
1043
- delete(id: string | number): Promise<boolean>;
1044
- forceDelete(id: string | number): Promise<boolean>;
1045
- restore(id: string | number): Promise<T | null>;
1204
+ delete(id: string | number, options?: { cascade?: boolean; deletedAt?: Date }): Promise<boolean>;
1205
+ forceDelete(id: string | number, options?: { cascade?: boolean }): Promise<boolean>;
1206
+ restore(id: string | number, options?: { cascade?: boolean; allChildren?: boolean }): Promise<T | null>;
1046
1207
  query(): QueryBuilder<T>;
1047
1208
  raw(sql: string, bindings?: unknown[]): Promise<unknown>;
1048
1209
  count(conditions?: Partial<T> | Record<string, unknown>): Promise<number>;
@@ -1126,6 +1287,8 @@ export interface MigrationManager {
1126
1287
  getConfig(): MigrationConfig & { directory: string; tableName: string };
1127
1288
  hasTable(): Promise<boolean>;
1128
1289
  unlock(): Promise<void>;
1290
+ diff(options?: { models?: ModelDefinition<any>[]; includeDrops?: boolean }): Promise<{ hasChanges: boolean; changes: Array<any> }>;
1291
+ makeMigrations(options?: { modelsDir?: string; dryRun?: boolean; includeDrops?: boolean; name?: string }): Promise<{ hasChanges: boolean; created: Array<any>; skipped: Array<any> }>;
1129
1292
  }
1130
1293
 
1131
1294
  export interface WebspressoModels {
@@ -1192,6 +1355,30 @@ export function createMemoryCacheProvider(options?: OrmMemoryCacheOptions): {
1192
1355
  getSizeStats(): { entries: number; tags: number };
1193
1356
  };
1194
1357
 
1358
+ // --- ORM: Model Factory & Seeding (AdonisJS Lucid-inspired) ---
1359
+
1360
+ export interface FactoryBuilder<T = Record<string, any>> {
1361
+ count(n: number): FactoryBuilder<T>;
1362
+ state(...stateNames: string[]): FactoryBuilder<T>;
1363
+ apply(...stateNames: string[]): FactoryBuilder<T>;
1364
+ merge(overrides: Partial<T>): FactoryBuilder<T>;
1365
+ make(overrides?: Partial<T>): T | T[];
1366
+ create(options: { db?: DatabaseInstance | Repository<T>; merge?: Partial<T> } | DatabaseInstance | Repository<T>): Promise<T | T[]>;
1367
+ }
1368
+
1369
+ export function defineFactory<T = Record<string, any>>(
1370
+ modelName: string,
1371
+ definitionFn: (ctx: { faker: any; index: number }) => Partial<T>
1372
+ ): FactoryBuilder<T> & {
1373
+ state(name: string, handler: ((ctx: { faker: any; index: number; current: Partial<T> }) => Partial<T>) | Partial<T>): FactoryBuilder<T>;
1374
+ registerState(name: string, handler: ((ctx: { faker: any; index: number; current: Partial<T> }) => Partial<T>) | Partial<T>): FactoryBuilder<T>;
1375
+ };
1376
+
1377
+ // --- ORM: Schema Diff & Makemigrations (Django-inspired) ---
1378
+
1379
+ export function diffSchema(options: { knex: Knex; models?: ModelDefinition<any>[]; includeDrops?: boolean }): Promise<{ hasChanges: boolean; changes: Array<any> }>;
1380
+ export function makeMigrations(options: { knex: Knex; modelsDir?: string; migrationDir?: string; dryRun?: boolean; includeDrops?: boolean; name?: string }): Promise<{ hasChanges: boolean; created: Array<any>; skipped: Array<any> }>;
1381
+
1195
1382
  // --- ORM: nanoid / zod ---
1196
1383
 
1197
1384
  export function generateNanoid(options?: { maxLength?: number } | number): string;
@@ -1533,11 +1720,73 @@ export function emailPlugin(options?: EmailPluginOptions): WebspressoPlugin & {
1533
1720
  isTransportReady(): boolean;
1534
1721
  loadFromManifest(chunks: any): void;
1535
1722
  emailPluginOptionsFromManifest(manifest: any): any;
1723
+ mailer?: Mailer;
1724
+ Mailable?: typeof Mailable;
1725
+ getOutbox?(): any[];
1726
+ clearOutbox?(): void;
1727
+ lastMessage?(): any;
1536
1728
  };
1537
1729
  dispose?(): void;
1538
1730
  shutdown?(): void;
1539
1731
  };
1540
1732
 
1733
+ export class Mailable {
1734
+ constructor();
1735
+ to(recipient: string | string[] | any, name?: string): this;
1736
+ from(address: string, name?: string): this;
1737
+ replyTo(address: string): this;
1738
+ cc(recipient: string | string[]): this;
1739
+ bcc(recipient: string | string[]): this;
1740
+ subject(subj: string): this;
1741
+ template(tplName: string): this;
1742
+ html(htmlContent: string): this;
1743
+ text(textContent: string): this;
1744
+ mjml(mjmlContent: string): this;
1745
+ view(templateOrData: string | Record<string, unknown>, data?: Record<string, unknown>): this;
1746
+ data(keyOrObj: string | Record<string, unknown>, value?: any): this;
1747
+ attach(attachment: any): this;
1748
+ header(key: string, value: string): this;
1749
+ prepare(): void;
1750
+ build(): void;
1751
+ toMessage(): Record<string, any>;
1752
+ }
1753
+
1754
+ export class Mailer {
1755
+ constructor(options?: any);
1756
+ send(mailableOrOptions: Mailable | Record<string, any>): Promise<any>;
1757
+ preview(mailableOrOptions: Mailable | Record<string, any>): Promise<any>;
1758
+ getOutbox(): any[];
1759
+ clearOutbox(): void;
1760
+ lastMessage(): any;
1761
+ verify(): Promise<any>;
1762
+ }
1763
+
1764
+ export function createMailer(options?: any): Mailer;
1765
+
1766
+ export class MemoryTransport {
1767
+ outbox: any[];
1768
+ constructor(options?: any);
1769
+ sendMail(mailOptions: any): Promise<any>;
1770
+ getOutbox(): any[];
1771
+ clearOutbox(): void;
1772
+ lastMessage(): any;
1773
+ findById(id: string): any;
1774
+ verify(): Promise<boolean>;
1775
+ }
1776
+
1777
+ export class ConsoleTransport {
1778
+ constructor(options?: any);
1779
+ sendMail(mailOptions: any): Promise<any>;
1780
+ verify(): Promise<boolean>;
1781
+ }
1782
+
1783
+ export class FileTransport {
1784
+ dir: string;
1785
+ constructor(options?: any);
1786
+ sendMail(mailOptions: any): Promise<any>;
1787
+ verify(): Promise<boolean>;
1788
+ }
1789
+
1541
1790
  export function recaptchaPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1542
1791
 
1543
1792
  export interface SwaggerPluginOptions {
@@ -1795,6 +2044,82 @@ export interface ApiKeyRouteOptions {
1795
2044
  export function apiKeyPlugin(options?: ApiKeyPluginOptions): WebspressoPlugin;
1796
2045
  export function createApiKeyMiddleware(options?: ApiKeyPluginOptions & ApiKeyRouteOptions): RequestHandler;
1797
2046
 
2047
+ export interface WebhookEndpoint {
2048
+ id?: number | string;
2049
+ name: string;
2050
+ url: string;
2051
+ secret?: string;
2052
+ events?: string[] | string;
2053
+ is_active?: boolean;
2054
+ description?: string | null;
2055
+ headers?: Record<string, string> | string | null;
2056
+ metadata?: Record<string, any> | string | null;
2057
+ created_at?: Date | string;
2058
+ updated_at?: Date | string;
2059
+ [key: string]: any;
2060
+ }
2061
+
2062
+ export interface WebhookDelivery {
2063
+ id?: number | string;
2064
+ endpoint_id: number | string;
2065
+ event: string;
2066
+ payload: string;
2067
+ status_code?: number | null;
2068
+ success: boolean;
2069
+ duration_ms?: number | null;
2070
+ error?: string | null;
2071
+ response_body?: string | null;
2072
+ attempt?: number;
2073
+ max_attempts?: number;
2074
+ next_retry_at?: Date | string | null;
2075
+ created_at?: Date | string;
2076
+ updated_at?: Date | string;
2077
+ [key: string]: any;
2078
+ }
2079
+
2080
+ export interface WebhooksPluginOptions {
2081
+ db?: any;
2082
+ knex?: any;
2083
+ endpointsTable?: string;
2084
+ deliveriesTable?: string;
2085
+ maxAttempts?: number;
2086
+ backoffBaseMs?: number;
2087
+ timeoutMs?: number;
2088
+ wrapPayload?: boolean;
2089
+ queue?: any;
2090
+ endpoints?: WebhookEndpoint[];
2091
+ apiPrefix?: string;
2092
+ includeDefaultPage?: boolean;
2093
+ pageTitle?: string;
2094
+ menuOrder?: number;
2095
+ }
2096
+
2097
+ export interface WebhookDispatchResult {
2098
+ dispatched: number;
2099
+ deliveries: WebhookDelivery[];
2100
+ results?: Array<{ success?: boolean; statusCode?: number; error?: string; [key: string]: any }>;
2101
+ }
2102
+
2103
+ export interface WebhookDispatcherInstance {
2104
+ dispatch(event: string, payload: any, options?: { wait?: boolean; endpointId?: number | string }): Promise<WebhookDispatchResult>;
2105
+ pingEndpoint(endpointId: number | string): Promise<any>;
2106
+ retryDelivery(deliveryId: number | string): Promise<any>;
2107
+ createEndpoint(data: Partial<WebhookEndpoint>): Promise<WebhookEndpoint>;
2108
+ getEndpoint(id: number | string): Promise<WebhookEndpoint | null>;
2109
+ updateEndpoint(id: number | string, data: Partial<WebhookEndpoint>): Promise<WebhookEndpoint | null>;
2110
+ deleteEndpoint(id: number | string): Promise<boolean>;
2111
+ listEndpoints(filters?: { active?: boolean; search?: string; limit?: number; offset?: number }): Promise<WebhookEndpoint[]>;
2112
+ listDeliveries(filters?: { endpointId?: number | string; event?: string; success?: boolean | string; page?: number; limit?: number }): Promise<{ items: WebhookDelivery[]; total: number; page: number; limit: number; pages: number }>;
2113
+ getDelivery(id: number | string): Promise<WebhookDelivery | null>;
2114
+ purgeDeliveries(options?: { days?: number }): Promise<number>;
2115
+ dispose(): void;
2116
+ on(event: string, listener: (...args: any[]) => void): this;
2117
+ }
2118
+
2119
+ export function webhooksPlugin(options?: WebhooksPluginOptions): WebspressoPlugin & {
2120
+ dispatcher: WebhookDispatcherInstance;
2121
+ };
2122
+
1798
2123
  export interface ContentPluginOptions {
1799
2124
  db: import('./index').Database;
1800
2125
  adminPath?: string;
@@ -2283,8 +2608,11 @@ export interface QueueRetentionConfig {
2283
2608
  export interface ScheduleOptions {
2284
2609
  id?: string;
2285
2610
  overlap?: boolean;
2611
+ preventOverlap?: boolean;
2286
2612
  maxRuns?: number;
2287
2613
  utc?: boolean;
2614
+ timezone?: string;
2615
+ tz?: string;
2288
2616
  runImmediately?: boolean;
2289
2617
  delay?: number | Date;
2290
2618
  attempts?: number;
@@ -2306,6 +2634,7 @@ export interface ScheduleInfo {
2306
2634
  nextRunAt: Date | null;
2307
2635
  isRunning: boolean;
2308
2636
  activeJobId: string | number | null;
2637
+ activeJobIds?: (string | number)[];
2309
2638
  registeredAt: Date;
2310
2639
  }
2311
2640
 
@@ -2313,21 +2642,22 @@ export class CronExpression {
2313
2642
  rawExpression: string;
2314
2643
  resolvedExpression: string;
2315
2644
  utc: boolean;
2645
+ timezone: string | null;
2316
2646
  minutes: Set<number>;
2317
2647
  hours: Set<number>;
2318
2648
  doms: Set<number>;
2319
2649
  months: Set<number>;
2320
2650
  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;
2651
+ constructor(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string });
2652
+ match(date?: Date | string | number): boolean;
2653
+ getNextDate(fromDate?: Date | string | number): Date | null;
2654
+ getNextDates(count?: number, fromDate?: Date | string | number): Date[];
2655
+ static isValid(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): boolean;
2656
+ static parse(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): CronExpression;
2327
2657
  }
2328
2658
 
2329
- export function parseCron(expression: string, options?: { utc?: boolean }): CronExpression;
2330
- export function isValidCron(expression: string): boolean;
2659
+ export function parseCron(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): CronExpression;
2660
+ export function isValidCron(expression: string, options?: { utc?: boolean; timezone?: string; tz?: string }): boolean;
2331
2661
  export function resolveCronAlias(str: string): string;
2332
2662
  export const CRON_ALIASES: Record<string, string>;
2333
2663
 
@@ -2345,6 +2675,8 @@ export class TaskScheduler {
2345
2675
  trigger(idOrJobName: string): Promise<Job | null>;
2346
2676
  start(): this;
2347
2677
  stop(): this;
2678
+ pause(): this;
2679
+ resume(): this;
2348
2680
  clear(): void;
2349
2681
  destroy(): void;
2350
2682
  }
@@ -2379,6 +2711,8 @@ export class QueueManager extends EventEmitter {
2379
2711
  unschedule(idOrJobName: string): boolean;
2380
2712
  getSchedules(): ScheduleInfo[];
2381
2713
  getSchedule(idOrJobName: string): ScheduleInfo | null;
2714
+ triggerSchedule(idOrJobName: string): Promise<Job | null>;
2715
+ triggerCron(idOrJobName: string): Promise<Job | null>;
2382
2716
  start(): this;
2383
2717
  pause(): this;
2384
2718
  resume(): this;
@@ -2453,14 +2787,20 @@ export interface HtmlStreamOptions {
2453
2787
  env: import('nunjucks').Environment;
2454
2788
  templatePath: string;
2455
2789
  context?: Record<string, unknown>;
2456
- defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
2790
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string; loadingTemplate?: string }>;
2791
+ nonce?: string;
2792
+ loadingTemplate?: string | null;
2793
+ layouts?: CascadingLayoutInfo[];
2457
2794
  }
2458
2795
 
2459
2796
  export interface RenderStreamOptions {
2460
2797
  env?: import('nunjucks').Environment;
2461
- defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
2798
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string; loadingTemplate?: string }>;
2462
2799
  status?: number;
2463
2800
  flushImmediately?: boolean;
2801
+ nonce?: string;
2802
+ loadingTemplate?: string | null;
2803
+ layouts?: CascadingLayoutInfo[];
2464
2804
  }
2465
2805
 
2466
2806
  export function createHtmlStream(options: HtmlStreamOptions): import('stream').Readable;
@@ -3155,10 +3495,196 @@ export namespace totp {
3155
3495
  };
3156
3496
  }
3157
3497
 
3498
+ // --- Magic Link / Passwordless Authentication ---
3158
3499
 
3500
+ export interface BuildMagicLinkUrlOptions {
3501
+ baseUrl?: string;
3502
+ path?: string;
3503
+ token: string;
3504
+ }
3159
3505
 
3506
+ export interface FormatMagicLinkHtmlOptions {
3507
+ name?: string;
3508
+ magicLinkUrl: string;
3509
+ expiresMinutes?: number;
3510
+ }
3160
3511
 
3512
+ export interface MagicLinkVerifyMiddlewareOptions {
3513
+ tokenParam?: string;
3514
+ redirectTo?: string;
3515
+ failureRedirect?: string;
3516
+ markEmailVerified?: boolean;
3517
+ jwt?: boolean;
3518
+ }
3161
3519
 
3520
+ export interface MagicLinkRequestOptions {
3521
+ baseUrl?: string;
3522
+ path?: string;
3523
+ tokenTtlMs?: number;
3524
+ hideUserNotFound?: boolean;
3525
+ buildMagicLinkUrl?: (token: string) => string;
3526
+ }
3162
3527
 
3528
+ export interface MagicLinkRequestResult {
3529
+ sent: boolean;
3530
+ userFound: boolean;
3531
+ expiresAt?: Date;
3532
+ token?: string;
3533
+ magicLinkUrl?: string;
3534
+ }
3535
+
3536
+ export interface MagicLinkVerifyResult {
3537
+ ok: true;
3538
+ user: any;
3539
+ userId: string | number;
3540
+ accessToken?: string;
3541
+ refreshToken?: string;
3542
+ }
3543
+
3544
+ export function buildMagicLinkUrl(options: BuildMagicLinkUrlOptions): string;
3545
+ export function formatMagicLinkHtml(options: FormatMagicLinkHtmlOptions): string;
3546
+ export function createMagicLinkVerifyMiddleware(authManager: any, options?: MagicLinkVerifyMiddlewareOptions): (req: any, res: any, next: any) => Promise<any>;
3547
+ export function createMagicLinkRequestHandler(authManager: any, options?: MagicLinkRequestOptions): (req: any, res: any) => Promise<any>;
3548
+
3549
+ export namespace magicLink {
3550
+ export {
3551
+ buildMagicLinkUrl,
3552
+ formatMagicLinkHtml,
3553
+ createMagicLinkVerifyMiddleware,
3554
+ createMagicLinkRequestHandler,
3555
+ };
3556
+ }
3557
+
3558
+ // Active Devices & Session Management
3559
+ export interface DeviceInfo {
3560
+ browser: string;
3561
+ os: string;
3562
+ device: 'desktop' | 'mobile' | 'tablet' | 'bot' | 'unknown';
3563
+ isMobile: boolean;
3564
+ isTablet: boolean;
3565
+ isDesktop: boolean;
3566
+ isBot: boolean;
3567
+ }
3568
+
3569
+ export interface UserSession {
3570
+ id?: number | string;
3571
+ session_id: string;
3572
+ user_id: number | string;
3573
+ ip_address?: string | null;
3574
+ user_agent?: string | null;
3575
+ browser?: string | null;
3576
+ os?: string | null;
3577
+ device?: string | null;
3578
+ last_active_at: Date;
3579
+ created_at?: Date;
3580
+ is_current?: boolean;
3581
+ }
3582
+
3583
+ export interface SessionTracker {
3584
+ track(sessionId: string, userId: string | number, metadata?: { ip?: string; userAgent?: string; browser?: string; os?: string; device?: string }): Promise<UserSession | null>;
3585
+ touch(sessionId: string): Promise<boolean>;
3586
+ get(sessionId: string): Promise<UserSession | null>;
3587
+ list(userId: string | number, currentSessionId?: string | null): Promise<UserSession[]>;
3588
+ isSessionValid(sessionId: string, userId?: string | number | null): Promise<boolean>;
3589
+ revoke(sessionId: string): Promise<boolean>;
3590
+ revokeOthers(userId: string | number, currentSessionId?: string | null): Promise<number>;
3591
+ revokeAll(userId: string | number): Promise<number>;
3592
+ purgeExpired(maxAgeMs: number): Promise<number>;
3593
+ }
3594
+
3595
+ export interface SessionManagerOptions {
3596
+ tracker?: SessionTracker;
3597
+ sessionStore?: any;
3598
+ touchThrottleMs?: number;
3599
+ emitter?: any;
3600
+ }
3601
+
3602
+ export class SessionManager {
3603
+ constructor(options?: SessionManagerOptions);
3604
+ tracker: SessionTracker;
3605
+ sessionStore?: any;
3606
+ touchThrottleMs: number;
3607
+ emitter?: any;
3608
+ track(sessionId: string, userId: string | number, req?: any): Promise<UserSession | null>;
3609
+ touch(sessionId: string): Promise<boolean>;
3610
+ isSessionValid(sessionId: string, userId?: string | number | null): Promise<boolean>;
3611
+ listSessions(userId: string | number, currentSessionId?: string | null): Promise<UserSession[]>;
3612
+ revokeSession(sessionId: string, options?: { userId?: string | number; revokedBy?: string }): Promise<boolean>;
3613
+ revokeOtherSessions(userId: string | number, currentSessionId: string): Promise<number>;
3614
+ revokeAllSessions(userId: string | number): Promise<number>;
3615
+ }
3616
+
3617
+ export function parseUserAgent(userAgent?: string): DeviceInfo;
3618
+ export function getClientIp(req?: any): string;
3619
+ export function createUserSessionsTable(knex: any): Promise<void>;
3620
+ export function dropUserSessionsTable(knex: any): Promise<void>;
3621
+ export function createKnexSessionTracker(db: any, options?: { tableName?: string }): SessionTracker;
3622
+ export function createMemorySessionTracker(): SessionTracker;
3623
+ export function createListSessionsHandler(authManager: any): (req: any, res: any) => Promise<any>;
3624
+ export function createRevokeSessionHandler(authManager: any): (req: any, res: any) => Promise<any>;
3625
+
3626
+ export namespace sessions {
3627
+ export {
3628
+ SessionManager,
3629
+ createUserSessionsTable,
3630
+ dropUserSessionsTable,
3631
+ createKnexSessionTracker,
3632
+ createMemorySessionTracker,
3633
+ getClientIp,
3634
+ parseUserAgent,
3635
+ createListSessionsHandler,
3636
+ createRevokeSessionHandler,
3637
+ };
3638
+ }
3639
+
3640
+ // Custom Management Commands (Django & Adonis Ace-inspired)
3641
+ export interface CommandOption {
3642
+ flags: string;
3643
+ description?: string;
3644
+ defaultValue?: any;
3645
+ }
3646
+
3647
+ export interface CommandExecutionContext {
3648
+ db: any;
3649
+ queue: any;
3650
+ services: any;
3651
+ mailer: any;
3652
+ events: any;
3653
+ logger: any;
3654
+ args: string[];
3655
+ flags: Record<string, any>;
3656
+ }
3657
+
3658
+ export interface CommandDefinition {
3659
+ name: string;
3660
+ description?: string;
3661
+ options?: CommandOption[];
3662
+ handle: (ctx: CommandExecutionContext) => Promise<any> | any;
3663
+ }
3664
+
3665
+ export interface CommandRegistry {
3666
+ register(cmd: CommandDefinition): CommandDefinition;
3667
+ reload(): void;
3668
+ has(name: string): boolean;
3669
+ get(name: string): CommandDefinition | null;
3670
+ list(): CommandDefinition[];
3671
+ names(): string[];
3672
+ readonly commandsDir: string;
3673
+ }
3674
+
3675
+ export function defineCommand(definition: CommandDefinition): CommandDefinition;
3676
+ export function createCommandRegistry(options?: { commandsDir?: string; autoLoad?: boolean }): CommandRegistry;
3677
+ export function executeCommand(
3678
+ commandDef: CommandDefinition,
3679
+ executionOptions?: Partial<CommandExecutionContext>
3680
+ ): Promise<{ success: boolean; result?: any; error?: Error; durationMs: number }>;
3681
+
3682
+ export namespace commands {
3683
+ export {
3684
+ defineCommand,
3685
+ createCommandRegistry,
3686
+ executeCommand,
3687
+ };
3688
+ }
3163
3689
 
3164
3690