structured-fw 1.0.8 → 1.0.9

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 (61) hide show
  1. package/README.md +5 -2
  2. package/build/system/EventEmitter.d.ts +1 -1
  3. package/build/system/RequestHandler.d.ts +1 -0
  4. package/build/system/RequestHandler.js +1 -0
  5. package/build/system/RequestMethod.d.ts +1 -0
  6. package/build/system/RequestMethod.js +1 -0
  7. package/build/system/Types.d.ts +10 -175
  8. package/build/system/Types.js +10 -1
  9. package/build/system/Util.d.ts +2 -1
  10. package/build/system/client/ClientComponent.d.ts +2 -1
  11. package/build/system/client/ClientComponent.js +42 -10
  12. package/build/system/client/DataStore.d.ts +1 -1
  13. package/build/system/client/DataStoreView.d.ts +1 -1
  14. package/build/system/client/Net.d.ts +2 -1
  15. package/build/system/client/NetRequest.d.ts +1 -1
  16. package/build/system/request.d.ts +1 -0
  17. package/build/system/request.js +1 -0
  18. package/build/system/server/Application.d.ts +4 -1
  19. package/build/system/server/Application.js +1 -1
  20. package/build/system/server/Component.d.ts +2 -1
  21. package/build/system/server/Component.js +11 -8
  22. package/build/system/server/Components.d.ts +2 -1
  23. package/build/system/server/Components.js +7 -7
  24. package/build/system/server/Cookies.d.ts +1 -1
  25. package/build/system/server/Document.d.ts +5 -3
  26. package/build/system/server/Document.js +3 -3
  27. package/build/system/server/DocumentHead.d.ts +1 -1
  28. package/build/system/server/FormValidation.d.ts +2 -1
  29. package/build/system/server/Handlebars.d.ts +1 -1
  30. package/build/system/server/Layout.d.ts +4 -2
  31. package/build/system/server/Layout.js +3 -1
  32. package/build/system/server/Request.d.ts +2 -1
  33. package/build/system/server/Request.js +1 -0
  34. package/build/system/server/Session.d.ts +2 -1
  35. package/build/system/server/dom/DOMNode.d.ts +1 -0
  36. package/build/system/server/dom/DOMNode.js +9 -3
  37. package/build/system/server/dom/HTMLParser.d.ts +1 -0
  38. package/build/system/server/dom/HTMLParser.js +31 -8
  39. package/build/system/types/application.types.d.ts +1 -0
  40. package/build/system/types/application.types.js +1 -0
  41. package/build/system/types/component.types.d.ts +59 -0
  42. package/build/system/types/component.types.js +1 -0
  43. package/build/system/types/document.types.d.ts +5 -0
  44. package/build/system/types/document.types.js +1 -0
  45. package/build/system/types/eventEmitter.types.d.ts +1 -0
  46. package/build/system/types/eventEmitter.types.js +1 -0
  47. package/build/system/types/general.types.d.ts +1 -0
  48. package/build/system/types/general.types.js +1 -0
  49. package/build/system/types/request.d.ts +57 -0
  50. package/build/system/types/request.js +1 -0
  51. package/build/system/types/request.types.d.ts +58 -0
  52. package/build/system/types/request.types.js +1 -0
  53. package/build/system/types/session.types.d.ts +6 -0
  54. package/build/system/types/session.types.js +1 -0
  55. package/build/system/types/store.types.d.ts +1 -0
  56. package/build/system/types/store.types.js +1 -0
  57. package/build/system/types/structured.types.d.ts +32 -0
  58. package/build/system/types/structured.types.js +1 -0
  59. package/build/system/types/validation.types.d.ts +18 -0
  60. package/build/system/types/validation.types.js +1 -0
  61. package/package.json +2 -2
package/README.md CHANGED
@@ -275,6 +275,9 @@ type RequestContext<Body extends LooseObject | undefined = LooseObject> = {
275
275
  // true if x-requested-with header is received and it equals 'xmlhttprequest'
276
276
  isAjax: boolean,
277
277
 
278
+ // time when request was received (unix timestamp in milliseconds)
279
+ timeStart: number,
280
+
278
281
  // URL GET arguments
279
282
  getArgs: PostedDataDecoded,
280
283
 
@@ -766,9 +769,9 @@ Version 0.8.7 introduced the `Layout` class, which allows accomplishing the abov
766
769
  <template></template>
767
770
  ...
768
771
  ```
769
- 2) `/index.ts` (`app` is an instance of `Application`)
772
+ 2) `/index.ts` (`app` is an instance of `Application`), 3rd argument is optional BCP 47 language tag
770
773
  ```
771
- export const layout = new Layout(app, 'layout');
774
+ export const layout = new Layout(app, 'layout', 'en');
772
775
  ```
773
776
  3) `/app/routes/Test.ts`
774
777
  ```
@@ -1,4 +1,4 @@
1
- import { EventEmitterCallback } from "./Types.js";
1
+ import { EventEmitterCallback } from './types/eventEmitter.types.js';
2
2
  export declare class EventEmitter<T extends Record<string, any> = Record<string, any>> {
3
3
  protected listeners: Partial<Record<Extract<keyof T, string>, Array<EventEmitterCallback<any>>>>;
4
4
  on<K extends Extract<keyof T, string>>(eventName: K, callback: EventEmitterCallback<T[K]>): void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,175 +1,10 @@
1
- import { IncomingMessage, ServerResponse } from "node:http";
2
- import { Application } from "./server/Application.js";
3
- import { symbolArrays } from "./Symbols.js";
4
- import { Net } from './client/Net.js';
5
- import { ClientComponent } from './client/ClientComponent.js';
6
- import { Component } from "./server/Component.js";
7
- export type StructuredConfig = {
8
- readonly envPrefix?: string;
9
- readonly autoInit: boolean;
10
- url: {
11
- removeTrailingSlash: boolean;
12
- componentRender: false | string;
13
- isAsset: (url: string) => boolean;
14
- };
15
- routes: {
16
- readonly path: string;
17
- };
18
- components: {
19
- readonly path: string;
20
- readonly componentNameAttribute: string;
21
- };
22
- session: {
23
- readonly cookieName: string;
24
- readonly keyLength: number;
25
- readonly durationSeconds: number;
26
- readonly garbageCollectIntervalSeconds: number;
27
- };
28
- http: {
29
- host?: string;
30
- port: number;
31
- linkHeaderRel: 'preload' | 'preconnect';
32
- };
33
- readonly runtime: 'Node.js' | 'Deno';
34
- };
35
- export type StructuredClientConfig = {
36
- componentRender: StructuredConfig['url']['componentRender'];
37
- componentNameAttribute: string;
38
- };
39
- export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
40
- export type RequestCallback<R extends any, Body extends LooseObject | undefined> = (ctx: RequestContext<Body>) => Promise<R>;
41
- export type RequestHandler = {
42
- match: Array<URISegmentPattern> | RegExp;
43
- methods: Array<RequestMethod>;
44
- callback: RequestCallback<any, LooseObject | undefined>;
45
- scope: any;
46
- staticAsset: boolean;
47
- };
48
- export type RequestContext<Body extends LooseObject | undefined = LooseObject> = {
49
- request: IncomingMessage;
50
- response: ServerResponse;
51
- args: URIArguments;
52
- handler: null | RequestHandler;
53
- cookies: Record<string, string>;
54
- body: Body;
55
- bodyRaw?: Buffer;
56
- files?: Record<string, RequestBodyRecordValue>;
57
- data: RequestContextData;
58
- sessionId?: string;
59
- isAjax: boolean;
60
- getArgs: PostedDataDecoded;
61
- respondWith: (data: any) => void;
62
- redirect: (to: string, statusCode?: number) => void;
63
- show404: () => Promise<void>;
64
- };
65
- export type PostedDataDecoded = Record<string, string | boolean | Array<string | boolean | PostedDataDecoded> | Record<string, string | boolean | Array<string | boolean | PostedDataDecoded>> | Record<string, string | boolean | Array<string | boolean>>>;
66
- export type RequestBodyRecordValue = string | Array<RequestBodyRecordValue> | {
67
- [key: string]: RequestBodyRecordValue;
68
- } | {
69
- [key: string]: RequestBodyFile;
70
- } | Array<RequestBodyFile> | RequestBodyFile;
71
- export interface RequestBodyArguments {
72
- [key: string]: RequestBodyRecordValue;
73
- [symbolArrays]?: {
74
- [key: string]: Array<string>;
75
- };
76
- }
77
- export type RequestBodyFiles = {
78
- [key: string]: RequestBodyFile;
79
- };
80
- export type RequestBodyFile = {
81
- fileName: string;
82
- data: Buffer;
83
- type: string;
84
- };
85
- export type URISegmentPattern = {
86
- pattern: string | RegExp;
87
- name?: string;
88
- type?: 'string' | 'number';
89
- };
90
- export type URIArguments = {
91
- [key: string]: string | number | RegExpExecArray;
92
- };
93
- export type DocumentResource = {
94
- path: string;
95
- attributes: Record<string, string | null>;
96
- priority: number;
97
- };
98
- export type ComponentEntry = {
99
- name: string;
100
- path: {
101
- absolute: string;
102
- relative: string;
103
- relativeToViews: string;
104
- build: string;
105
- html: string;
106
- jsServer?: string;
107
- jsClient?: string;
108
- };
109
- hasJS: boolean;
110
- html: string;
111
- static: boolean;
112
- module?: ComponentScaffold;
113
- renderTagName?: string;
114
- exportData: boolean;
115
- exportFields?: Array<string>;
116
- attributes?: Record<string, string>;
117
- initializer?: InitializerFunction;
118
- };
119
- export interface ComponentScaffold {
120
- tagName?: string;
121
- exportData?: boolean;
122
- exportFields?: Array<string>;
123
- static?: boolean;
124
- deferred?: (data: Record<string, any>, ctx: RequestContext | undefined, app: Application) => boolean;
125
- attributes?: Record<string, string>;
126
- getData: (this: ComponentScaffold, data: RequestBodyArguments | LooseObject, ctx: undefined | RequestContext, app: Application, component: Component) => Promise<LooseObject | null>;
127
- [key: string]: any;
128
- }
129
- export type LooseObject = Record<string, any>;
130
- export type ApplicationEvents = 'serverStarted' | 'beforeRequestHandler' | 'afterRequestHandler' | 'beforeRoutes' | 'afterRoutes' | 'beforeComponentsLoad' | 'afterComponentsLoaded' | 'documentCreated' | 'beforeAssetAccess' | 'afterAssetAccess' | 'pageNotFound';
131
- export type SessionEntry = {
132
- sessionId: string;
133
- lastRequest: number;
134
- data: LooseObject;
135
- };
136
- export type ValidationRuleWithArguments = [string, any];
137
- export type FormValidationEntry = {
138
- field: [string, string];
139
- rules: Array<string | ValidationRuleWithArguments | ValidatorFunction>;
140
- };
141
- export type ValidatorFunction = (data: PostedDataDecoded, field: string, arg: number, rules: Array<string | ValidationRuleWithArguments | ValidatorFunction>) => Promise<boolean>;
142
- export type ValidatorErrorDecorator = (fieldHumanReadable: string, data: PostedDataDecoded, field: string, arg: any) => string | Promise<string>;
143
- export type ValidationErrors = {
144
- [field: string]: Array<string>;
145
- };
146
- export type ValidationErrorsSingle = {
147
- [field: string]: string;
148
- };
149
- export type ValidationResult = {
150
- valid: boolean;
151
- errors: ValidationErrors | ValidationErrorsSingle;
152
- };
153
- export type InitializerFunction = (this: ClientComponent, ctx: InitializerFunctionContext) => Promise<void>;
154
- export type Initializers = {
155
- [key: string]: InitializerFunction;
156
- };
157
- export type InitializerFunctionContext = {
158
- net: Net;
159
- isRedraw: boolean;
160
- };
161
- export type StoreChangeCallback = (key: string, value: any, oldValue: any, componentId: string) => void;
162
- export type ClientComponentEventCallback<T> = (e: Event, data: T, element: HTMLElement | Window) => void;
163
- export type ClientComponentBoundEvent<T extends LooseObject | undefined = undefined> = {
164
- element: HTMLElement | Window;
165
- event: keyof HTMLElementEventMap;
166
- callback: (e: Event) => void;
167
- callbackOriginal: ClientComponentEventCallback<T>;
168
- };
169
- export type ClientComponentTransition = {
170
- fade: false | number;
171
- slide: false | number;
172
- };
173
- export type ClientComponentTransitionEvent = 'show' | 'hide';
174
- export type ClientComponentTransitions = Record<ClientComponentTransitionEvent, ClientComponentTransition>;
175
- export type EventEmitterCallback<T> = (payload: T, eventName: string) => void;
1
+ export * from './types/request.types.js';
2
+ export * from './types/document.types.js';
3
+ export * from './types/component.types.js';
4
+ export * from './types/session.types.js';
5
+ export * from './types/store.types.js';
6
+ export * from './types/validation.types.js';
7
+ export * from './types/application.types.js';
8
+ export * from './types/eventEmitter.types.js';
9
+ export * from './types/general.types.js';
10
+ export * from './types/structured.types.js';
@@ -1 +1,10 @@
1
- import { symbolArrays } from "./Symbols.js";
1
+ export * from './types/request.types.js';
2
+ export * from './types/document.types.js';
3
+ export * from './types/component.types.js';
4
+ export * from './types/session.types.js';
5
+ export * from './types/store.types.js';
6
+ export * from './types/validation.types.js';
7
+ export * from './types/application.types.js';
8
+ export * from './types/eventEmitter.types.js';
9
+ export * from './types/general.types.js';
10
+ export * from './types/structured.types.js';
@@ -1,4 +1,5 @@
1
- import { LooseObject, PostedDataDecoded } from "./Types.js";
1
+ import { LooseObject } from './types/general.types.js';
2
+ import { PostedDataDecoded } from "./types/request.types.js";
2
3
  export declare function queryStringDecode(queryString: string, initialValue?: PostedDataDecoded, trimValues?: boolean): PostedDataDecoded;
3
4
  export declare function queryStringDecodedSetValue(obj: PostedDataDecoded | string, value: any): LooseObject;
4
5
  export declare function objectEach<T>(obj: T, callbackEach: (key: keyof T, value: T[keyof T]) => void): void;
@@ -1,4 +1,5 @@
1
- import { ClientComponentEventCallback, LooseObject } from '../Types.js';
1
+ import { LooseObject } from '../types/general.types.js';
2
+ import { ClientComponentEventCallback } from '../types/component.types.js';
2
3
  import { DataStoreView } from './DataStoreView.js';
3
4
  import { DataStore } from './DataStore.js';
4
5
  import { Net } from './Net.js';
@@ -407,7 +407,7 @@ export class ClientComponent extends EventEmitter {
407
407
  return isTrue;
408
408
  }
409
409
  else {
410
- const parts = /^(!)?\s*([a-zA-Z]+[a-zA-Z0-9_]*)\s*((?:==)|(?:===)|(?:!=)|(?:!==)|<|>|(?:<=)|(?:>=))?\s*([^=].+)?$/.exec(condition);
410
+ const parts = /^(!)?\s*([a-zA-Z0-9_]+)\s*((?:==)|(?:===)|(?:!=)|(?:!==)|<|>|(?:<=)|(?:>=))?\s?([^=]+)?$/.exec(condition);
411
411
  if (parts === null) {
412
412
  console.error(`Could not parse condition ${condition}`);
413
413
  return false;
@@ -619,25 +619,41 @@ export class ClientComponent extends EventEmitter {
619
619
  const onTransitionEnd = (e) => {
620
620
  domNode.style.opacity = '1';
621
621
  domNode.style.transition = '';
622
- domNode.style.transformOrigin = 'unset';
622
+ domNode.style.transformOrigin = '';
623
+ domNode.style.transform = '';
623
624
  domNode.removeEventListener('transitionend', onTransitionEnd);
624
625
  domNode.removeEventListener('transitioncancel', onTransitionEnd);
625
626
  };
626
627
  domNode.addEventListener('transitionend', onTransitionEnd);
627
628
  domNode.addEventListener('transitioncancel', onTransitionEnd);
628
629
  if (transitionsActive.slide) {
630
+ const axis = this.transitionAxis(domNode, 'show');
631
+ let slideDirection = axis === 'X' ? 'left' : 'up';
632
+ const invert = domNode.hasAttribute('data-transition-slide-invert');
633
+ if (invert) {
634
+ slideDirection = slideDirection === 'left' ? 'right' : (slideDirection === 'up' ? 'down' : 'up');
635
+ }
636
+ const slideLengthMultiplier = slideDirection === 'down' || slideDirection === 'right' ? -1 : 1;
637
+ const slideLength = (axis === 'X' ? domNode.clientWidth : domNode.clientHeight) * 0.5 * slideLengthMultiplier * -1;
638
+ domNode.style.transform = `translate${axis === 'X' ? 'X' : 'Y'}(${slideLength}px)`;
639
+ setTimeout(() => {
640
+ domNode.style.transition = `transform ${transitionsActive.slide}ms linear`;
641
+ domNode.style.transform = `translate${axis === 'X' ? 'X' : 'Y'}(0)`;
642
+ }, 50);
643
+ }
644
+ if (transitionsActive.grow) {
629
645
  const transformOrigin = domNode.getAttribute('data-transform-origin-show') || '50% 0';
630
646
  domNode.style.transformOrigin = transformOrigin;
631
647
  const axis = this.transitionAxis(domNode, 'show');
632
648
  domNode.style.transform = `scale${axis}(0.01)`;
633
- domNode.style.transition = `transform ${transitionsActive.slide / 1000}s`;
649
+ domNode.style.transition = `transform ${transitionsActive.grow}ms`;
634
650
  setTimeout(() => {
635
651
  domNode.style.transform = `scale${axis}(1)`;
636
652
  }, 100);
637
653
  }
638
654
  if (transitionsActive.fade) {
639
655
  domNode.style.opacity = '0';
640
- domNode.style.transition = `opacity ${transitionsActive.fade / 1000}s`;
656
+ domNode.style.transition = `opacity ${transitionsActive.fade}ms`;
641
657
  setTimeout(() => {
642
658
  domNode.style.opacity = '1';
643
659
  }, 100);
@@ -674,20 +690,34 @@ export class ClientComponent extends EventEmitter {
674
690
  domNode.addEventListener('transitionend', onTransitionEnd);
675
691
  domNode.addEventListener('transitioncancel', onTransitionEnd);
676
692
  if (transitionsActive.slide) {
693
+ domNode.style.transition = `transform ${transitionsActive.slide}ms linear`;
694
+ const axis = this.transitionAxis(domNode, 'hide');
695
+ let slideDirection = axis === 'X' ? 'left' : 'up';
696
+ const invert = domNode.hasAttribute('data-transition-slide-invert');
697
+ if (invert) {
698
+ slideDirection = slideDirection === 'left' ? 'right' : (slideDirection === 'up' ? 'down' : 'up');
699
+ }
700
+ setTimeout(() => {
701
+ const slideLengthMultiplier = slideDirection === 'down' || slideDirection === 'right' ? -1 : 1;
702
+ const slideLength = (axis === 'X' ? domNode.clientWidth : domNode.clientHeight) * 0.5 * slideLengthMultiplier * -1;
703
+ domNode.style.transform = `translate${axis === 'X' ? 'X' : 'Y'}(${slideLength}px)`;
704
+ }, 50);
705
+ }
706
+ if (transitionsActive.grow) {
677
707
  const transformOrigin = domNode.getAttribute('data-transform-origin-hide') || '50% 100%';
678
708
  domNode.style.transformOrigin = transformOrigin;
679
- domNode.style.transition = `transform ${transitionsActive.slide / 1000}s ease`;
709
+ domNode.style.transition = `transform ${transitionsActive.grow}ms ease`;
680
710
  setTimeout(() => {
681
711
  const axis = this.transitionAxis(domNode, 'hide');
682
712
  domNode.style.transform = `scale${axis}(0.01)`;
683
- }, 100);
713
+ }, 50);
684
714
  }
685
715
  if (transitionsActive.fade) {
686
716
  domNode.style.opacity = '1';
687
- domNode.style.transition = `opacity ${transitionsActive.fade / 1000}s`;
717
+ domNode.style.transition = `opacity ${transitionsActive.fade}ms`;
688
718
  setTimeout(() => {
689
719
  domNode.style.opacity = '0';
690
- }, 100);
720
+ }, 50);
691
721
  }
692
722
  }
693
723
  }
@@ -695,11 +725,13 @@ export class ClientComponent extends EventEmitter {
695
725
  const transitions = {
696
726
  show: {
697
727
  slide: false,
698
- fade: false
728
+ fade: false,
729
+ grow: false,
699
730
  },
700
731
  hide: {
701
732
  slide: false,
702
- fade: false
733
+ fade: false,
734
+ grow: false,
703
735
  }
704
736
  };
705
737
  objectEach(transitions, (transitionEvent, transition) => {
@@ -1,4 +1,4 @@
1
- import { StoreChangeCallback } from '../Types.js';
1
+ import { StoreChangeCallback } from '../types/store.types.js';
2
2
  import { ClientComponent } from './ClientComponent.js';
3
3
  export declare class DataStore {
4
4
  protected data: {
@@ -1,4 +1,4 @@
1
- import { StoreChangeCallback } from '../Types.js';
1
+ import { StoreChangeCallback } from '../types/store.types.js';
2
2
  import { ClientComponent } from './ClientComponent.js';
3
3
  import { DataStore } from './DataStore.js';
4
4
  export declare class DataStoreView {
@@ -1,5 +1,6 @@
1
1
  import { IncomingHttpHeaders } from 'node:http';
2
- import { LooseObject, RequestMethod } from '../Types.js';
2
+ import { LooseObject } from '../types/general.types.js';
3
+ import { RequestMethod } from "../types/request.types.js";
3
4
  export declare class Net {
4
5
  request(method: RequestMethod, url: string, headers?: IncomingHttpHeaders, body?: any, responseType?: XMLHttpRequestResponseType): Promise<string>;
5
6
  get(url: string, headers?: IncomingHttpHeaders): Promise<string>;
@@ -1,5 +1,5 @@
1
1
  import { IncomingHttpHeaders } from 'node:http';
2
- import { RequestMethod } from '../Types.js';
2
+ import { RequestMethod } from "../types/request.types.js";
3
3
  export declare class NetRequest {
4
4
  xhr: XMLHttpRequest;
5
5
  method: RequestMethod;
@@ -0,0 +1 @@
1
+ export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,8 @@
1
1
  import { Server } from 'node:http';
2
- import { ApplicationEvents, LooseObject, RequestContext, StructuredConfig } from '../Types.js';
2
+ import { ApplicationEvents } from '../types/application.types.js';
3
+ import { StructuredConfig } from '../types/structured.types.js';
4
+ import { LooseObject } from '../types/general.types.js';
5
+ import { RequestContext } from "../types/request.types.js";
3
6
  import { Document } from './Document.js';
4
7
  import { Components } from './Components.js';
5
8
  import { Session } from './Session.js';
@@ -154,7 +154,7 @@ export class Application {
154
154
  const document = new Document(this, '', ctx);
155
155
  const data = attributes;
156
156
  await document.loadComponent(component.name, data);
157
- const exportedData = component.exportData ? document.data : (component.exportFields ? component.exportFields.reduce((prev, curr) => {
157
+ const exportedData = component.exportData ? document.children[0].data : (component.exportFields ? component.exportFields.reduce((prev, curr) => {
158
158
  prev[curr] = document.children[0].data[curr];
159
159
  return prev;
160
160
  }, {}) : {});
@@ -1,5 +1,6 @@
1
1
  import { Document } from './Document.js';
2
- import { ComponentEntry, LooseObject } from '../Types.js';
2
+ import { LooseObject } from '../types/general.types.js';
3
+ import { ComponentEntry } from "../types/component.types.js";
3
4
  import { DOMNode } from './dom/DOMNode.js';
4
5
  import { EventEmitter } from '../EventEmitter.js';
5
6
  export declare class Component<Events extends Record<string, any> = {
@@ -81,9 +81,9 @@ export class Component extends EventEmitter {
81
81
  });
82
82
  this.data = exportedContextData;
83
83
  if (this.entry !== null &&
84
- typeof this.entry.module !== 'undefined' &&
85
- typeof this.entry.module.deferred === 'function' &&
86
- this.entry.module.deferred(this.attributes, this.document.ctx, this.document.application) &&
84
+ typeof this.entry.serverPart !== 'undefined' &&
85
+ typeof this.entry.serverPart.deferred === 'function' &&
86
+ this.entry.serverPart.deferred(this.attributes, this.document.ctx, this.document.application) &&
87
87
  this.attributes.deferred !== false) {
88
88
  this.setAttributes({ deferred: true }, 'data-', true);
89
89
  return;
@@ -91,14 +91,14 @@ export class Component extends EventEmitter {
91
91
  const importedParentData = this.parent ? this.importedParentData(this.parent.data) : {};
92
92
  let dataServerSidePart = {};
93
93
  try {
94
- dataServerSidePart = (this.entry && this.entry.module ?
95
- await this.entry.module.getData(Object.assign(importedParentData, this.attributes, data || {}), this.document.ctx, this.document.application, this) : {}) || {};
94
+ dataServerSidePart = (this.entry && this.entry.serverPart ?
95
+ await this.entry.serverPart.getData(Object.assign(importedParentData, this.attributes, data || {}), this.document.ctx, this.document.application, this) : {}) || {};
96
96
  }
97
97
  catch (e) {
98
98
  throw new Error(`Error executing getData in component ${this.name}: ${e.message}`);
99
99
  }
100
100
  if (data === undefined) {
101
- if (this.entry && this.entry.module) {
101
+ if (this.entry && this.entry.hasServerPart) {
102
102
  this.data = Object.assign(this.data, dataServerSidePart);
103
103
  }
104
104
  else {
@@ -110,10 +110,10 @@ export class Component extends EventEmitter {
110
110
  this.data = Object.assign(this.data, Object.assign(importedParentData, data), this.attributes, dataServerSidePart);
111
111
  }
112
112
  this.fillData(this.data);
113
- if (this.entry === null || this.entry.exportData) {
113
+ if (!this.entry?.hasServerPart) {
114
114
  this.setAttributes(this.data, 'data-');
115
115
  }
116
- else if (this.entry) {
116
+ else {
117
117
  if (this.entry.exportFields) {
118
118
  this.setAttributes(this.entry.exportFields.reduce((prev, field) => {
119
119
  if (this.data[field] !== undefined) {
@@ -122,6 +122,9 @@ export class Component extends EventEmitter {
122
122
  return prev;
123
123
  }, {}), 'data-');
124
124
  }
125
+ else if (this.entry.exportData) {
126
+ this.setAttributes(this.data, 'data-');
127
+ }
125
128
  if (this.entry.attributes) {
126
129
  this.setAttributes(this.entry.attributes, '', false);
127
130
  }
@@ -1,4 +1,5 @@
1
- import { ComponentEntry, StructuredConfig } from '../Types.js';
1
+ import { StructuredConfig } from '../types/structured.types.js';
2
+ import { ComponentEntry } from "../types/component.types.js";
2
3
  import { Application } from './Application.js';
3
4
  export declare class Components {
4
5
  config: StructuredConfig;
@@ -45,7 +45,7 @@ export class Components {
45
45
  jsClient: hasClientJS ? jsClientPath : undefined,
46
46
  jsServer: hasServerJS ? jsServerPath : undefined
47
47
  },
48
- hasJS: existsSync(jsServerPath),
48
+ hasServerPart: existsSync(jsServerPath),
49
49
  html: this.loadHTML(absolutePath),
50
50
  exportData: false,
51
51
  static: false
@@ -56,12 +56,12 @@ export class Components {
56
56
  }
57
57
  if (hasServerJS) {
58
58
  const componentConstructor = await import('file:///' + entry.path.jsServer);
59
- entry.module = new componentConstructor.default();
60
- entry.renderTagName = entry.module?.tagName || 'div';
61
- entry.exportData = typeof entry.module?.exportData === 'boolean' ? entry.module.exportData : false;
62
- entry.exportFields = entry.module?.exportFields;
63
- entry.attributes = entry.module?.attributes;
64
- entry.static = typeof entry.module?.static === 'boolean' ? entry.module.static : false;
59
+ entry.serverPart = new componentConstructor.default();
60
+ entry.renderTagName = entry.serverPart?.tagName || 'div';
61
+ entry.exportData = typeof entry.serverPart?.exportData === 'boolean' ? entry.serverPart.exportData : false;
62
+ entry.exportFields = entry.serverPart?.exportFields;
63
+ entry.attributes = entry.serverPart?.attributes;
64
+ entry.static = typeof entry.serverPart?.static === 'boolean' ? entry.serverPart.static : false;
65
65
  }
66
66
  this.components[componentName.toUpperCase()] = entry;
67
67
  this.componentNames.push(entry.name);
@@ -1,5 +1,5 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
- import { LooseObject } from "../Types.js";
2
+ import { LooseObject } from '../types/general.types.js';
3
3
  export declare class Cookies {
4
4
  parse(request: IncomingMessage): LooseObject;
5
5
  set(response: ServerResponse, name: string, value: string | number, lifetimeSeconds: number, path?: string, sameSite?: 'Strict' | 'Lax' | 'None', domain?: string): void;
@@ -1,5 +1,7 @@
1
1
  import { ServerResponse } from 'node:http';
2
- import { Initializers, LooseObject, RequestContext } from '../../system/Types.js';
2
+ import { LooseObject } from '../types/general.types.js';
3
+ import { Initializers } from '../types/component.types.js';
4
+ import { RequestContext } from "../types/request.types.js";
3
5
  import { Application } from './Application.js';
4
6
  import { DocumentHead } from './DocumentHead.js';
5
7
  import { Component } from './Component.js';
@@ -21,6 +23,6 @@ export declare class Document extends Component<{
21
23
  private initClientConfig;
22
24
  toString(): string;
23
25
  allocateId(): string;
24
- loadView(pathRelative: string, data?: LooseObject): Promise<boolean>;
25
- loadComponent(componentName: string, data?: LooseObject): Promise<void>;
26
+ loadView(pathRelative: string, data?: LooseObject): Promise<Document>;
27
+ loadComponent(componentName: string, data?: LooseObject): Promise<Document>;
26
28
  }
@@ -69,14 +69,13 @@ export class Document extends Component {
69
69
  async loadView(pathRelative, data) {
70
70
  const viewPath = path.resolve('../' + this.application.config.components.path + '/' + pathRelative + (pathRelative.endsWith('.html') ? '' : '.html'));
71
71
  if (!existsSync(viewPath)) {
72
- console.warn(`Couldn't load document ${this.document.head.title}: ${viewPath}`);
73
- return false;
72
+ throw new Error(`Couldn't load document ${this.document.head.title}: ${viewPath}`);
74
73
  }
75
74
  const html = readFileSync(viewPath, {
76
75
  encoding: 'utf-8'
77
76
  }).toString();
78
77
  await this.init(stripBOM(html).replace(/\r/g, ''), data);
79
- return true;
78
+ return this;
80
79
  }
81
80
  async loadComponent(componentName, data) {
82
81
  const componentEntry = this.document.application.components.getByName(componentName);
@@ -87,5 +86,6 @@ export class Document extends Component {
87
86
  }, []).join(' ');
88
87
  await this.init(`<${componentName} ${dataString}></${componentName}>`, data);
89
88
  }
89
+ return this;
90
90
  }
91
91
  }
@@ -1,4 +1,4 @@
1
- import { DocumentResource } from '../Types.js';
1
+ import { DocumentResource } from "../types/document.types.js";
2
2
  export declare class DocumentHead {
3
3
  title: string;
4
4
  js: Array<DocumentResource>;
@@ -1,4 +1,5 @@
1
- import { FormValidationEntry, LooseObject, ValidationResult, ValidationRuleWithArguments, ValidatorErrorDecorator, ValidatorFunction } from '../Types.js';
1
+ import { LooseObject } from '../types/general.types.js';
2
+ import { ValidationResult, ValidatorErrorDecorator, ValidatorFunction, FormValidationEntry, ValidationRuleWithArguments } from '../types/validation.types.js';
2
3
  export declare class FormValidation {
3
4
  fieldRules: Array<FormValidationEntry>;
4
5
  singleError: boolean;
@@ -1,6 +1,6 @@
1
1
  import { HelperDelegate } from "handlebars";
2
2
  import { default as HandlebarsInstance } from 'handlebars';
3
- import { LooseObject } from "../Types.js";
3
+ import { LooseObject } from '../types/general.types.js';
4
4
  export declare class Handlebars {
5
5
  readonly instance: typeof HandlebarsInstance;
6
6
  readonly helpers: Record<string, HelperDelegate>;
@@ -1,10 +1,12 @@
1
- import { LooseObject, RequestContext } from "../Types.js";
1
+ import { LooseObject } from '../types/general.types.js';
2
+ import { RequestContext } from "../types/request.types.js";
2
3
  import { Application } from "./Application.js";
3
4
  import { Document } from "./Document.js";
4
5
  export declare class Layout {
5
6
  layoutComponent: string;
6
7
  app: Application;
7
- constructor(app: Application, layoutComponent: string);
8
+ language: string;
9
+ constructor(app: Application, layoutComponent: string, language?: string);
8
10
  private layoutComponentExists;
9
11
  document(ctx: RequestContext, title: string, componentName: string, data?: LooseObject): Promise<Document>;
10
12
  }
@@ -1,9 +1,10 @@
1
1
  import { Component } from "./Component.js";
2
2
  import { Document } from "./Document.js";
3
3
  export class Layout {
4
- constructor(app, layoutComponent) {
4
+ constructor(app, layoutComponent, language = 'en') {
5
5
  this.app = app;
6
6
  this.layoutComponent = layoutComponent;
7
+ this.language = language;
7
8
  if (this.app.initialized) {
8
9
  this.layoutComponentExists();
9
10
  }
@@ -18,6 +19,7 @@ export class Layout {
18
19
  }
19
20
  async document(ctx, title, componentName, data) {
20
21
  const doc = new Document(this.app, title, ctx);
22
+ doc.language = this.language;
21
23
  await doc.loadComponent(this.layoutComponent, data);
22
24
  const layoutComponent = doc.dom.queryByTagName('template');
23
25
  if (layoutComponent.length === 0) {
@@ -1,5 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from "node:http";
2
- import { LooseObject, PostedDataDecoded, RequestBodyFile, RequestCallback, RequestMethod } from "../Types.js";
2
+ import { LooseObject } from '../types/general.types.js';
3
+ import { RequestMethod, PostedDataDecoded, RequestBodyFile, RequestCallback } from "../types/request.types.js";
3
4
  import { Application } from "./Application.js";
4
5
  export declare class Request {
5
6
  private app;
@@ -105,6 +105,7 @@ export class Request {
105
105
  body: undefined,
106
106
  getArgs,
107
107
  cookies: this.app.cookies.parse(request),
108
+ timeStart: new Date().getTime(),
108
109
  isAjax: request.headers['x-requested-with'] == 'xmlhttprequest',
109
110
  respondWith: function (data) {
110
111
  if (typeof data === 'string' || Buffer.isBuffer(data)) {
@@ -1,4 +1,5 @@
1
- import { LooseObject, SessionEntry } from '../Types.js';
1
+ import { LooseObject } from '../types/general.types.js';
2
+ import { SessionEntry } from '../types/session.types.js';
2
3
  import { Application } from './Application.js';
3
4
  export declare class Session {
4
5
  application: Application;
@@ -21,6 +21,7 @@ export declare class DOMNode {
21
21
  attributeMap: Record<string, DOMNodeAttribute>;
22
22
  style: Partial<CSSStyleDeclaration>;
23
23
  selfClosing: boolean;
24
+ explicitSelfClosing: boolean;
24
25
  potentialComponentChildren: Array<DOMNode>;
25
26
  constructor(root: DOMFragment | null, parentNode: DOMNode | null, tagName: string);
26
27
  appendChild(node: DOMNode | string): void;
@@ -1,6 +1,11 @@
1
1
  import { HTMLParser } from "./HTMLParser.js";
2
- export const selfClosingTags = ['br', 'wbr', 'hr', 'input', 'img', 'link', 'meta', 'source', 'embed', 'path', 'area', 'rect'];
3
- export const recognizedHTMLTags = ['body', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'b', 'i', 'a', 'em', 'strong', 'br', 'wbr', 'hr', 'abbr', 'bdi', 'bdo', 'blockquote', 'cite', 'code', 'del', 'dfn', 'ins', 'kbd', 'mark', 'pre', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span', 'sub', 'sup', 'time', 'u', 'var', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'img', 'area', 'map', 'object', 'param', 'table', 'tr', 'td', 'th', 'caption', 'colgroup', 'col', 'form', 'input', 'label', 'select', 'option', 'textarea', 'button', 'fieldset', 'datalist', 'iframe', 'audio', 'video', 'source', 'track', 'script', 'noscript', 'div', 'nav', 'aside', 'canvas', 'embed', 'template', 'rect'];
2
+ export const selfClosingTags = ['br', 'wbr', 'hr', 'input', 'img', 'link', 'meta', 'source', 'embed',
3
+ 'path', 'area', 'rect', 'ellipse', 'circle', 'line', 'polygon', 'image'
4
+ ];
5
+ export const recognizedHTMLTags = [
6
+ 'body', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'b', 'i', 'a', 'em', 'strong', 'br', 'wbr', 'hr', 'abbr', 'bdi', 'bdo', 'blockquote', 'cite', 'code', 'del', 'dfn', 'ins', 'kbd', 'mark', 'pre', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span', 'sub', 'sup', 'time', 'u', 'var', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'img', 'area', 'map', 'object', 'param', 'table', 'tr', 'td', 'th', 'caption', 'colgroup', 'col', 'form', 'input', 'label', 'select', 'option', 'textarea', 'button', 'fieldset', 'datalist', 'iframe', 'audio', 'video', 'source', 'track', 'script', 'noscript', 'div', 'nav', 'aside', 'canvas', 'embed', 'template',
7
+ 'svg', 'g', 'text', 'path', 'circle', 'clipPath', 'defs', 'ellipse', 'rect', 'polygon', 'image', 'style',
8
+ ];
4
9
  export class DOMNode {
5
10
  constructor(root, parentNode, tagName) {
6
11
  this.parentNode = null;
@@ -8,6 +13,7 @@ export class DOMNode {
8
13
  this.attributes = [];
9
14
  this.attributeMap = {};
10
15
  this.style = {};
16
+ this.explicitSelfClosing = false;
11
17
  this.potentialComponentChildren = [];
12
18
  this.root = root === null ? this : root;
13
19
  this.isRoot = root === null;
@@ -116,7 +122,7 @@ export class DOMNode {
116
122
  style += ` ${styleName}: ${styleValue};`;
117
123
  return style;
118
124
  }, '');
119
- return `<${this.tagName}${attributes}${style.trim().length > 0 ? ` style="${style}"` : ''}>${this.selfClosing ? '' : `${this.innerHTML}</${this.tagName}>`}`;
125
+ return `<${this.tagName}${attributes}${style.trim().length > 0 ? ` style="${style}"` : ''}${this.explicitSelfClosing ? '/' : ''}>${this.selfClosing || this.explicitSelfClosing ? '' : `${this.innerHTML}</${this.tagName}>`}`;
120
126
  }
121
127
  toObject() {
122
128
  return {
@@ -6,6 +6,7 @@ export declare class HTMLParser {
6
6
  private state;
7
7
  private tokenCurrent;
8
8
  private fragment;
9
+ explicitSelfClosing: boolean;
9
10
  private attributeOpenQuote;
10
11
  private attributeNameCurrent;
11
12
  private attributeContext;
@@ -6,6 +6,7 @@ export class HTMLParser {
6
6
  this.state = 'idle';
7
7
  this.tokenCurrent = '';
8
8
  this.fragment = new DOMFragment();
9
+ this.explicitSelfClosing = false;
9
10
  this.attributeOpenQuote = '"';
10
11
  this.attributeNameCurrent = '';
11
12
  this.attributeContext = null;
@@ -46,6 +47,7 @@ export class HTMLParser {
46
47
  }
47
48
  if (this.isLetter(charCode)) {
48
49
  this.state = 'tagOpen';
50
+ this.explicitSelfClosing = false;
49
51
  this.tokenCurrent = char;
50
52
  return true;
51
53
  }
@@ -58,6 +60,7 @@ export class HTMLParser {
58
60
  if (this.tokenCurrent.length === 0) {
59
61
  throw this.error(`Unexpected tag closing sequence "</", expected opening tag`);
60
62
  }
63
+ this.explicitSelfClosing = true;
61
64
  return true;
62
65
  }
63
66
  if (char === '>') {
@@ -66,9 +69,10 @@ export class HTMLParser {
66
69
  }
67
70
  const node = new DOMNode(this.fragment, this.context, this.tokenCurrent);
68
71
  this.context.appendChild(node);
69
- this.state = 'idle';
72
+ this.state = 'text';
70
73
  this.tokenCurrent = '';
71
- if (!node.selfClosing) {
74
+ node.explicitSelfClosing = this.explicitSelfClosing;
75
+ if (!node.selfClosing && !node.explicitSelfClosing) {
72
76
  this.context = node;
73
77
  }
74
78
  this.attributeContext = node;
@@ -82,7 +86,8 @@ export class HTMLParser {
82
86
  const node = new DOMNode(this.fragment, this.context, this.tokenCurrent);
83
87
  this.context.appendChild(node);
84
88
  this.tokenCurrent = '';
85
- if (!node.selfClosing) {
89
+ node.explicitSelfClosing = this.explicitSelfClosing;
90
+ if (!node.selfClosing && !node.explicitSelfClosing) {
86
91
  this.context = node;
87
92
  }
88
93
  this.attributeContext = node;
@@ -99,7 +104,7 @@ export class HTMLParser {
99
104
  return true;
100
105
  }
101
106
  if (char === '>') {
102
- if (this.tokenCurrent !== this.context.tagName) {
107
+ if (this.tokenCurrent !== this.context.tagName && !this.attributeContext?.explicitSelfClosing) {
103
108
  throw this.error(`Found closing tag ${this.tokenCurrent}, expected ${this.context.tagName}`);
104
109
  }
105
110
  this.context = this.context.parentNode || this.fragment;
@@ -123,12 +128,25 @@ export class HTMLParser {
123
128
  }
124
129
  else if (this.state === 'attributeName') {
125
130
  const boundsChar = char === ' ' || char === '\n' || char === '\t';
126
- if (boundsChar || char === '=' || char === '>') {
131
+ if (boundsChar || char === '=' || char === '>' || char === '/') {
127
132
  if (char === '=') {
128
133
  this.state = 'attributeValueStart';
129
134
  }
130
135
  else if (char === '>') {
131
- this.state = 'idle';
136
+ this.state = 'text';
137
+ if (this.tokenCurrent.trim().length === 0) {
138
+ return true;
139
+ }
140
+ }
141
+ else if (char === '/') {
142
+ if (this.attributeContext && this.attributeContext.parentNode) {
143
+ this.context = this.attributeContext.parentNode;
144
+ this.attributeContext.explicitSelfClosing = true;
145
+ this.state = 'tagClose';
146
+ if (this.tokenCurrent.trim().length === 0) {
147
+ return true;
148
+ }
149
+ }
132
150
  }
133
151
  if (this.tokenCurrent.length > 0) {
134
152
  if (this.attributeContext !== null && this.tokenCurrent.trim().length > 0) {
@@ -164,14 +182,19 @@ export class HTMLParser {
164
182
  }
165
183
  else if (this.state === 'attributeEnd') {
166
184
  if (char === '>') {
167
- this.state = 'idle';
185
+ this.state = 'text';
168
186
  return true;
169
187
  }
170
188
  if (char === ' ' || char === '\n') {
171
189
  this.state = 'attributeName';
172
190
  return true;
173
191
  }
174
- else if (char === '/') {
192
+ if (char === '/') {
193
+ if (this.attributeContext && this.attributeContext.parentNode) {
194
+ this.context = this.attributeContext.parentNode;
195
+ this.attributeContext.explicitSelfClosing = true;
196
+ this.tokenCurrent = '';
197
+ }
175
198
  return true;
176
199
  }
177
200
  else {
@@ -0,0 +1 @@
1
+ export type ApplicationEvents = 'serverStarted' | 'beforeRequestHandler' | 'afterRequestHandler' | 'beforeRoutes' | 'afterRoutes' | 'beforeComponentsLoad' | 'afterComponentsLoaded' | 'documentCreated' | 'beforeAssetAccess' | 'afterAssetAccess' | 'pageNotFound';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { ClientComponent } from "../client/ClientComponent.js";
2
+ import { Net } from "../client/Net.js";
3
+ import { Application } from "../server/Application.js";
4
+ import { Component } from "../server/Component.js";
5
+ import { LooseObject } from './general.types.js';
6
+ import { RequestContext, RequestBodyArguments } from "./request.types.js";
7
+ export type ComponentEntry = {
8
+ name: string;
9
+ path: {
10
+ absolute: string;
11
+ relative: string;
12
+ relativeToViews: string;
13
+ build: string;
14
+ html: string;
15
+ jsServer?: string;
16
+ jsClient?: string;
17
+ };
18
+ hasServerPart: boolean;
19
+ serverPart?: ComponentScaffold;
20
+ html: string;
21
+ static: boolean;
22
+ renderTagName?: string;
23
+ exportData: boolean;
24
+ exportFields?: Array<string>;
25
+ attributes?: Record<string, string>;
26
+ initializer?: InitializerFunction;
27
+ };
28
+ export interface ComponentScaffold {
29
+ tagName?: string;
30
+ exportData?: boolean;
31
+ exportFields?: Array<string>;
32
+ static?: boolean;
33
+ deferred?: (data: Record<string, any>, ctx: RequestContext | undefined, app: Application) => boolean;
34
+ attributes?: Record<string, string>;
35
+ getData: (this: ComponentScaffold, data: RequestBodyArguments | LooseObject, ctx: undefined | RequestContext, app: Application, component: Component) => Promise<LooseObject | null>;
36
+ [key: string]: any;
37
+ }
38
+ export type ClientComponentTransition = {
39
+ fade: false | number;
40
+ slide: false | number;
41
+ grow: false | number;
42
+ };
43
+ export type ClientComponentTransitionEvent = 'show' | 'hide';
44
+ export type ClientComponentTransitions = Record<ClientComponentTransitionEvent, ClientComponentTransition>;
45
+ export type ClientComponentBoundEvent<T extends LooseObject | undefined = undefined> = {
46
+ element: HTMLElement | Window;
47
+ event: keyof HTMLElementEventMap;
48
+ callback: (e: Event) => void;
49
+ callbackOriginal: ClientComponentEventCallback<T>;
50
+ };
51
+ export type ClientComponentEventCallback<T> = (e: Event, data: T, element: HTMLElement | Window) => void;
52
+ export type InitializerFunction = (this: ClientComponent, ctx: InitializerFunctionContext) => Promise<void>;
53
+ export type Initializers = {
54
+ [key: string]: InitializerFunction;
55
+ };
56
+ export type InitializerFunctionContext = {
57
+ net: Net;
58
+ isRedraw: boolean;
59
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export type DocumentResource = {
2
+ path: string;
3
+ attributes: Record<string, string | null>;
4
+ priority: number;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type EventEmitterCallback<T> = (payload: T, eventName: string) => void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type LooseObject = Record<string, any>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import { IncomingMessage, ServerResponse } from "http";
2
+ import { LooseObject } from "../Types.js";
3
+ import { symbolArrays } from "../Symbols.js";
4
+ export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
5
+ export type RequestCallback<R extends any, Body extends LooseObject | undefined> = (ctx: RequestContext<Body>) => Promise<R>;
6
+ export type RequestHandler = {
7
+ match: Array<URISegmentPattern> | RegExp;
8
+ methods: Array<RequestMethod>;
9
+ callback: RequestCallback<any, LooseObject | undefined>;
10
+ scope: any;
11
+ staticAsset: boolean;
12
+ };
13
+ export type RequestContext<Body extends LooseObject | undefined = LooseObject> = {
14
+ request: IncomingMessage;
15
+ response: ServerResponse;
16
+ args: URIArguments;
17
+ handler: null | RequestHandler;
18
+ cookies: Record<string, string>;
19
+ body: Body;
20
+ bodyRaw?: Buffer;
21
+ files?: Record<string, RequestBodyRecordValue>;
22
+ data: RequestContextData;
23
+ sessionId?: string;
24
+ isAjax: boolean;
25
+ getArgs: PostedDataDecoded;
26
+ respondWith: (data: any) => void;
27
+ redirect: (to: string, statusCode?: number) => void;
28
+ show404: () => Promise<void>;
29
+ };
30
+ export type PostedDataDecoded = Record<string, string | boolean | Array<string | boolean | PostedDataDecoded> | Record<string, string | boolean | Array<string | boolean | PostedDataDecoded>> | Record<string, string | boolean | Array<string | boolean>>>;
31
+ export type RequestBodyRecordValue = string | Array<RequestBodyRecordValue> | {
32
+ [key: string]: RequestBodyRecordValue;
33
+ } | {
34
+ [key: string]: RequestBodyFile;
35
+ } | Array<RequestBodyFile> | RequestBodyFile;
36
+ export interface RequestBodyArguments {
37
+ [key: string]: RequestBodyRecordValue;
38
+ [symbolArrays]?: {
39
+ [key: string]: Array<string>;
40
+ };
41
+ }
42
+ export type RequestBodyFile = {
43
+ fileName: string;
44
+ data: Buffer;
45
+ type: string;
46
+ };
47
+ export type RequestBodyFiles = {
48
+ [key: string]: RequestBodyFile;
49
+ };
50
+ export type URISegmentPattern = {
51
+ pattern: string | RegExp;
52
+ name?: string;
53
+ type?: 'string' | 'number';
54
+ };
55
+ export type URIArguments = {
56
+ [key: string]: string | number | RegExpExecArray;
57
+ };
@@ -0,0 +1 @@
1
+ import { symbolArrays } from "../Symbols.js";
@@ -0,0 +1,58 @@
1
+ import { IncomingMessage, ServerResponse } from "http";
2
+ import { LooseObject } from './general.types.js';
3
+ import { symbolArrays } from "../Symbols.js";
4
+ export type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
5
+ export type RequestCallback<R extends any, Body extends LooseObject | undefined> = (ctx: RequestContext<Body>) => Promise<R>;
6
+ export type RequestHandler = {
7
+ match: Array<URISegmentPattern> | RegExp;
8
+ methods: Array<RequestMethod>;
9
+ callback: RequestCallback<any, LooseObject | undefined>;
10
+ scope: any;
11
+ staticAsset: boolean;
12
+ };
13
+ export type RequestContext<Body extends LooseObject | undefined = LooseObject> = {
14
+ request: IncomingMessage;
15
+ response: ServerResponse;
16
+ args: URIArguments;
17
+ handler: null | RequestHandler;
18
+ cookies: Record<string, string>;
19
+ body: Body;
20
+ bodyRaw?: Buffer;
21
+ files?: Record<string, RequestBodyRecordValue>;
22
+ data: RequestContextData;
23
+ sessionId?: string;
24
+ isAjax: boolean;
25
+ getArgs: PostedDataDecoded;
26
+ timeStart: number;
27
+ respondWith: (data: any) => void;
28
+ redirect: (to: string, statusCode?: number) => void;
29
+ show404: () => Promise<void>;
30
+ };
31
+ export type PostedDataDecoded = Record<string, string | boolean | Array<string | boolean | PostedDataDecoded> | Record<string, string | boolean | Array<string | boolean | PostedDataDecoded>> | Record<string, string | boolean | Array<string | boolean>>>;
32
+ export type RequestBodyRecordValue = string | Array<RequestBodyRecordValue> | {
33
+ [key: string]: RequestBodyRecordValue;
34
+ } | {
35
+ [key: string]: RequestBodyFile;
36
+ } | Array<RequestBodyFile> | RequestBodyFile;
37
+ export interface RequestBodyArguments {
38
+ [key: string]: RequestBodyRecordValue;
39
+ [symbolArrays]?: {
40
+ [key: string]: Array<string>;
41
+ };
42
+ }
43
+ export type RequestBodyFile = {
44
+ fileName: string;
45
+ data: Buffer;
46
+ type: string;
47
+ };
48
+ export type RequestBodyFiles = {
49
+ [key: string]: RequestBodyFile;
50
+ };
51
+ export type URISegmentPattern = {
52
+ pattern: string | RegExp;
53
+ name?: string;
54
+ type?: 'string' | 'number';
55
+ };
56
+ export type URIArguments = {
57
+ [key: string]: string | number | RegExpExecArray;
58
+ };
@@ -0,0 +1 @@
1
+ import { symbolArrays } from "../Symbols.js";
@@ -0,0 +1,6 @@
1
+ import { LooseObject } from './general.types.js';
2
+ export type SessionEntry = {
3
+ sessionId: string;
4
+ lastRequest: number;
5
+ data: LooseObject;
6
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export type StoreChangeCallback = (key: string, value: any, oldValue: any, componentId: string) => void;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ export type StructuredConfig = {
2
+ readonly envPrefix?: string;
3
+ readonly autoInit: boolean;
4
+ url: {
5
+ removeTrailingSlash: boolean;
6
+ componentRender: false | string;
7
+ isAsset: (url: string) => boolean;
8
+ };
9
+ routes: {
10
+ readonly path: string;
11
+ };
12
+ components: {
13
+ readonly path: string;
14
+ readonly componentNameAttribute: string;
15
+ };
16
+ session: {
17
+ readonly cookieName: string;
18
+ readonly keyLength: number;
19
+ readonly durationSeconds: number;
20
+ readonly garbageCollectIntervalSeconds: number;
21
+ };
22
+ http: {
23
+ host?: string;
24
+ port: number;
25
+ linkHeaderRel: 'preload' | 'preconnect';
26
+ };
27
+ readonly runtime: 'Node.js' | 'Deno';
28
+ };
29
+ export type StructuredClientConfig = {
30
+ componentRender: StructuredConfig['url']['componentRender'];
31
+ componentNameAttribute: string;
32
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { PostedDataDecoded } from './request.types.js';
2
+ export type ValidationRuleWithArguments = [string, any];
3
+ export type FormValidationEntry = {
4
+ field: [string, string];
5
+ rules: Array<string | ValidationRuleWithArguments | ValidatorFunction>;
6
+ };
7
+ export type ValidatorFunction = (data: PostedDataDecoded, field: string, arg: number, rules: Array<string | ValidationRuleWithArguments | ValidatorFunction>) => Promise<boolean>;
8
+ export type ValidatorErrorDecorator = (fieldHumanReadable: string, data: PostedDataDecoded, field: string, arg: any) => string | Promise<string>;
9
+ export type ValidationErrors = {
10
+ [field: string]: Array<string>;
11
+ };
12
+ export type ValidationErrorsSingle = {
13
+ [field: string]: string;
14
+ };
15
+ export type ValidationResult = {
16
+ valid: boolean;
17
+ errors: ValidationErrors | ValidationErrorsSingle;
18
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -19,10 +19,10 @@
19
19
  "license": "MIT",
20
20
  "type": "module",
21
21
  "main": "build/index",
22
- "version": "1.0.8",
22
+ "version": "1.0.9",
23
23
  "scripts": {
24
24
  "develop": "tsc --watch",
25
- "startDev": "cd build && nodemon --watch '../app/**/*' --watch '../build/**/*' -e js,html,css index.js",
25
+ "startDev": "cd build && nodemon --watch '../app/**/*' --watch '../build/**/*' -e js,html,hbs,css index.js",
26
26
  "start": "cd build && node index.js",
27
27
  "compileAndPack": "tsc && npm pack",
28
28
  "compileAndPublish": "tsc && npm publish"