alp-node 6.1.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/AlpNodeApp-node18.mjs +303 -12
  3. package/dist/AlpNodeApp-node18.mjs.map +1 -1
  4. package/dist/definitions/AlpNodeApp.d.ts +15 -7
  5. package/dist/definitions/AlpNodeApp.d.ts.map +1 -1
  6. package/dist/definitions/config.d.ts +18 -0
  7. package/dist/definitions/config.d.ts.map +1 -0
  8. package/dist/definitions/errors.d.ts +4 -0
  9. package/dist/definitions/errors.d.ts.map +1 -0
  10. package/dist/definitions/index.d.ts +5 -3
  11. package/dist/definitions/index.d.ts.map +1 -1
  12. package/dist/definitions/language.d.ts +7 -0
  13. package/dist/definitions/language.d.ts.map +1 -0
  14. package/dist/definitions/listen.d.ts +7 -0
  15. package/dist/definitions/listen.d.ts.map +1 -0
  16. package/dist/definitions/params/ParamValid.d.ts +9 -0
  17. package/dist/definitions/params/ParamValid.d.ts.map +1 -0
  18. package/dist/definitions/params/ParamValidationResult.d.ts +9 -0
  19. package/dist/definitions/params/ParamValidationResult.d.ts.map +1 -0
  20. package/dist/definitions/params/ParamValidationResult.test.d.ts +2 -0
  21. package/dist/definitions/params/ParamValidationResult.test.d.ts.map +1 -0
  22. package/dist/definitions/params/ParamValueFromContext.d.ts +13 -0
  23. package/dist/definitions/params/ParamValueFromContext.d.ts.map +1 -0
  24. package/dist/definitions/params/ParamValueModelValidator.d.ts +4 -0
  25. package/dist/definitions/params/ParamValueModelValidator.d.ts.map +1 -0
  26. package/dist/definitions/params/ParamValueStringValidator.d.ts +5 -0
  27. package/dist/definitions/params/ParamValueStringValidator.d.ts.map +1 -0
  28. package/dist/definitions/params/ParamValueValidator.d.ts +10 -0
  29. package/dist/definitions/params/ParamValueValidator.d.ts.map +1 -0
  30. package/dist/definitions/params/index.d.ts +15 -0
  31. package/dist/definitions/params/index.d.ts.map +1 -0
  32. package/dist/definitions/router.d.ts +14 -0
  33. package/dist/definitions/router.d.ts.map +1 -0
  34. package/dist/definitions/translate/index.d.ts +11 -0
  35. package/dist/definitions/translate/index.d.ts.map +1 -0
  36. package/dist/definitions/translate/load.d.ts +4 -0
  37. package/dist/definitions/translate/load.d.ts.map +1 -0
  38. package/dist/definitions/types.d.ts +53 -0
  39. package/dist/definitions/types.d.ts.map +1 -0
  40. package/dist/index-node18.mjs +402 -15
  41. package/dist/index-node18.mjs.map +1 -1
  42. package/package.json +8 -9
  43. package/src/AlpNodeApp.ts +42 -21
  44. package/src/config.ts +121 -0
  45. package/src/errors.ts +70 -0
  46. package/src/index.ts +18 -3
  47. package/src/language.ts +27 -0
  48. package/src/listen.ts +68 -0
  49. package/src/params/ParamValid.ts +15 -0
  50. package/src/params/ParamValidationResult.test.ts +65 -0
  51. package/src/params/ParamValidationResult.ts +38 -0
  52. package/src/params/ParamValueFromContext.ts +42 -0
  53. package/src/params/ParamValueModelValidator.ts +36 -0
  54. package/src/params/ParamValueStringValidator.ts +13 -0
  55. package/src/params/ParamValueValidator.ts +23 -0
  56. package/src/params/index.ts +66 -0
  57. package/src/router.ts +64 -0
  58. package/src/translate/index.ts +45 -0
  59. package/src/translate/load.ts +30 -0
  60. package/src/types.ts +67 -0
@@ -0,0 +1,65 @@
1
+ import { ParamValidationResult } from './ParamValidationResult';
2
+
3
+ // const createContextMock = (): Context &
4
+ // Record<
5
+ // 'namedParam' | 'otherParam' | 'param' | 'paramGET' | 'paramGETorPOST',
6
+ // ReturnType<typeof jest.fn>
7
+ // > => {
8
+ // return {
9
+ // state: {} as ContextState,
10
+ // sanitizedState: {} as ContextSanitizedState,
11
+ // param: jest.fn(),
12
+ // namedParam: jest.fn(),
13
+ // otherParam: jest.fn(),
14
+ // paramGET: jest.fn(),
15
+ // paramGETorPOST: jest.fn(),
16
+ // } as unknown as Context &
17
+ // Record<
18
+ // 'namedParam' | 'otherParam' | 'param' | 'paramGET' | 'paramGETorPOST',
19
+ // ReturnType<typeof jest.fn>
20
+ // >;
21
+ // };
22
+
23
+ test('on init is valid', () => {
24
+ const paramValidator = new ParamValidationResult();
25
+ expect(paramValidator.getErrors()).toBe(undefined);
26
+ expect(paramValidator.hasErrors()).toBe(false);
27
+ expect(paramValidator.isValid()).toBe(true);
28
+ });
29
+
30
+ test('has error', () => {
31
+ const paramValidator = new ParamValidationResult();
32
+ paramValidator._error('slug', 'notEmpty', '');
33
+ expect(paramValidator.hasErrors()).toBe(true);
34
+ expect(paramValidator.isValid()).toBe(false);
35
+ expect(paramValidator.getErrors()).toEqual({
36
+ slug: { error: 'notEmpty', value: '' },
37
+ });
38
+ });
39
+
40
+ // test('.string() empty', () => {
41
+ // const contextMock = createContextMock();
42
+ // contextMock.param.mockReturnValue('');
43
+
44
+ // const paramValidator = new ParamValidationResult(contextMock);
45
+
46
+ // paramValidator.string('slug').notEmpty();
47
+ // expect(contextMock.param).toHaveBeenNthCalledWith(1, 'slug');
48
+ // expect(paramValidator.hasErrors()).toBe(true);
49
+ // expect(paramValidator.isValid()).toBe(false);
50
+ // expect(paramValidator.getErrors()).toEqual({
51
+ // slug: { error: 'notEmpty', value: '' },
52
+ // });
53
+ // });
54
+
55
+ // test('.string() not empty', () => {
56
+ // const contextMock = createContextMock();
57
+ // contextMock.param.mockReturnValue('testValue');
58
+ // const paramValidator = new ParamValidationResult(contextMock);
59
+
60
+ // paramValidator.string('slug').notEmpty();
61
+ // expect(contextMock.param).toHaveBeenNthCalledWith(1, 'slug');
62
+ // expect(paramValidator.hasErrors()).toBe(false);
63
+ // expect(paramValidator.isValid()).toBe(true);
64
+ // expect(paramValidator.getErrors()).toBe(undefined);
65
+ // });
@@ -0,0 +1,38 @@
1
+ export type Errors = Record<string, any>;
2
+
3
+ export class ParamValidationResult {
4
+ _errors?: Errors;
5
+
6
+ _error(name: string, key: string, value: unknown): void {
7
+ if (!this._errors) {
8
+ this._errors = {};
9
+ }
10
+
11
+ this._errors[name] = { error: key, value };
12
+ }
13
+
14
+ getErrors(): Errors | undefined {
15
+ return this._errors;
16
+ }
17
+
18
+ hasErrors(): boolean {
19
+ return this._errors !== undefined;
20
+ }
21
+
22
+ isValid(): boolean {
23
+ return this._errors === undefined;
24
+ }
25
+
26
+ // string(name: string): ParamValueStringValidator {
27
+ // return new ParamValueStringValidator(this, name, this.context.param(name));
28
+ // }
29
+ /* int(name, position) {
30
+ return new ParamValueIntValidator(this, name, this.context.param(name, position));
31
+ }
32
+ model(modelName, name) {
33
+ name = name || S.string.lcFirst(modelName);
34
+ console.log('paramvalidator model', modelName, M[modelName]);
35
+ let data = this.context.getOrPostParam(name);
36
+ return new ParamValueModelValidator(this, name, !data ? null : new M[modelName](data));
37
+ } */
38
+ }
@@ -0,0 +1,42 @@
1
+ import type { Context } from '../AlpNodeApp';
2
+ import type { ParamValidationResult } from './ParamValidationResult';
3
+ import ParamValueStringValidator from './ParamValueStringValidator';
4
+
5
+ export class ParamValueFromContext {
6
+ readonly validationResult: ParamValidationResult;
7
+
8
+ readonly context: Context;
9
+
10
+ constructor(context: Context, validationResult: ParamValidationResult) {
11
+ this.validationResult = validationResult;
12
+ this.context = context;
13
+ }
14
+
15
+ namedParam(name: string): ParamValueStringValidator {
16
+ return new ParamValueStringValidator(
17
+ this.validationResult,
18
+ name,
19
+ this.context.namedParam(name),
20
+ );
21
+ }
22
+
23
+ otherParam(position: number): ParamValueStringValidator {
24
+ return new ParamValueStringValidator(
25
+ this.validationResult,
26
+ String(position),
27
+ this.context.otherParam(position),
28
+ );
29
+ }
30
+
31
+ queryParam(name: string): ParamValueStringValidator {
32
+ return new ParamValueStringValidator(
33
+ this.validationResult,
34
+ name,
35
+ this.context.queryParam(name),
36
+ );
37
+ }
38
+
39
+ // bodyParam: <T>(name: string): ParamValueValidator<string | undefined> {
40
+
41
+ // }
42
+ }
@@ -0,0 +1,36 @@
1
+ import ParamValueValidator from './ParamValueValidator';
2
+
3
+ export default class ParamValueModelValidator<
4
+ T,
5
+ > extends ParamValueValidator<T> {
6
+ /*
7
+ required() {
8
+ if (this.value == null) {
9
+ this._error('required');
10
+ }
11
+ return this;
12
+ }
13
+ valid(fieldsRequired) {
14
+ if (this.value == null) {
15
+ return this;
16
+ }
17
+ if (S.isString(fieldsRequired)) {
18
+ fieldsRequired = fieldsRequired.split(' ');
19
+ }
20
+ S.forEach(this.value.constructor.Fields, (name, fModel) => {
21
+ let value = this.value[name];
22
+ if (fieldsRequired) {
23
+ if(S.array.has(fieldsRequired, name) && value == null) {
24
+ this._error('required');
25
+ }
26
+ } else {
27
+ if (value == null && fModel[1] && fModel[1].required) {
28
+ this._error('required');
29
+ }
30
+ }
31
+ //TODO ...
32
+ });
33
+ return this;
34
+ }
35
+ */
36
+ }
@@ -0,0 +1,13 @@
1
+ import ParamValueValidator from './ParamValueValidator';
2
+
3
+ export default class ParamValueStringValidator<
4
+ T extends string = string,
5
+ > extends ParamValueValidator<T | null | undefined> {
6
+ notEmpty(): ParamValueValidator<T> {
7
+ if (this.value == null || this.value.trim() === '') {
8
+ this._error('notEmpty');
9
+ }
10
+
11
+ return this as ParamValueValidator<T>;
12
+ }
13
+ }
@@ -0,0 +1,23 @@
1
+ import type { ParamValidationResult } from './ParamValidationResult';
2
+
3
+ export default class ParamValueValidator<T> {
4
+ readonly validationResult: ParamValidationResult;
5
+
6
+ readonly name: string;
7
+
8
+ readonly value: T;
9
+
10
+ constructor(validationResult: ParamValidationResult, name: string, value: T) {
11
+ this.validationResult = validationResult;
12
+ this.name = name;
13
+ this.value = value;
14
+ }
15
+
16
+ isValid(): boolean {
17
+ return this.validationResult.isValid();
18
+ }
19
+
20
+ _error(key: string): void {
21
+ this.validationResult._error(this.name, key, this.value);
22
+ }
23
+ }
@@ -0,0 +1,66 @@
1
+ import { defineLazyProperty } from 'object-properties';
2
+ import type { AlpNodeApp, Context } from '../AlpNodeApp';
3
+ import ParamValid from './ParamValid';
4
+ import { ParamValidationResult } from './ParamValidationResult';
5
+ import { ParamValueFromContext } from './ParamValueFromContext';
6
+
7
+ export interface AlpParamsContext {
8
+ params: ParamValueFromContext;
9
+ validParams: ParamValueFromContext;
10
+ namedParam: (name: string) => string | undefined;
11
+ otherParam: (position: number) => string | undefined;
12
+ queryParam: (name: string) => string | undefined;
13
+ bodyParam: <T>(name: string) => T | undefined;
14
+ }
15
+
16
+ export interface AlpParamsRequest {
17
+ searchParams: URLSearchParams;
18
+ }
19
+
20
+ export default function alpParams(app: AlpNodeApp): void {
21
+ Object.assign(app.context, {
22
+ namedRouteParam(this: Context, name: string): string | undefined {
23
+ const namedParams = this.route.namedParams;
24
+ return namedParams?.get(name);
25
+ },
26
+
27
+ otherRouteParam(this: Context, position: number): string | undefined {
28
+ const otherParams = this.route.otherParams;
29
+ return otherParams?.[position - 1];
30
+ },
31
+
32
+ queryParam(this: Context, name: string): string | undefined {
33
+ const searchParams = this.request.searchParams;
34
+ return searchParams.get(name) ?? undefined;
35
+ },
36
+
37
+ bodyParam<T>(this: Context, name: string): T | undefined {
38
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
39
+ return (this.body as any)[name];
40
+ },
41
+ });
42
+
43
+ defineLazyProperty(
44
+ app.request,
45
+ 'searchParams',
46
+ function (this: Context['request']): URLSearchParams {
47
+ return new URLSearchParams(this.search);
48
+ },
49
+ );
50
+
51
+ defineLazyProperty(
52
+ app.context,
53
+ 'params',
54
+ function (this: Context): ParamValueFromContext {
55
+ return new ParamValueFromContext(this, new ParamValidationResult());
56
+ },
57
+ );
58
+
59
+ defineLazyProperty(
60
+ app.context,
61
+ 'validParams',
62
+ function (this: Context): ParamValueFromContext {
63
+ return new ParamValueFromContext(this, new ParamValid(this));
64
+ },
65
+ );
66
+ }
package/src/router.ts ADDED
@@ -0,0 +1,64 @@
1
+ import type {
2
+ Router,
3
+ LocaleType,
4
+ RouterBuilder,
5
+ RouteMatch,
6
+ } from 'router-segments';
7
+ import { createRouterBuilder } from 'router-segments';
8
+ import type { AlpNodeApp, Context } from './AlpNodeApp';
9
+
10
+ export type AlpRouter<Locales extends LocaleType> = Router<
11
+ Locales,
12
+ AlpRouteRef
13
+ >;
14
+ export type AlpRouteRef = (ctx: Context) => Promise<void> | void;
15
+ type ReturnType = (app: AlpNodeApp) => AlpRouteRef;
16
+
17
+ export interface RouterContext {
18
+ route: RouteMatch<any, AlpRouteRef>;
19
+ }
20
+ export const createAlpRouterBuilder = <
21
+ Locales extends LocaleType,
22
+ >(): RouterBuilder<Locales, AlpRouteRef> =>
23
+ createRouterBuilder<Locales, AlpRouteRef>();
24
+
25
+ export type UrlGenerator = <P extends Record<string, unknown> | undefined>(
26
+ routeKey: string,
27
+ params?: P,
28
+ ) => string;
29
+
30
+ export default function alpRouter<Locales extends string>(
31
+ router: Router<Locales, AlpRouteRef>,
32
+ ): ReturnType {
33
+ return (app: AlpNodeApp) => {
34
+ app.router = router;
35
+
36
+ app.context.urlGenerator = function <
37
+ P extends Record<string, unknown> | undefined,
38
+ >(this: Context, routeKey: string, params?: P): string {
39
+ return router.toLocalizedPath(this.language as Locales, routeKey, params);
40
+ };
41
+
42
+ app.context.redirectTo = function <
43
+ P extends Record<string, unknown> | undefined,
44
+ >(this: Context, to: string, params?: P): void {
45
+ this.redirect(
46
+ router.toLocalizedPath(this.language as Locales, to, params),
47
+ );
48
+ };
49
+
50
+ return async (ctx: Context): Promise<void> => {
51
+ // eslint-disable-next-line unicorn/no-array-method-this-argument
52
+ const routeMatch = router.find(ctx.request.path, ctx.language as Locales);
53
+
54
+ if (!routeMatch) {
55
+ ctx.status = 404;
56
+ throw new Error(`Route not found: ${ctx.request.path}`);
57
+ }
58
+
59
+ ctx.route = routeMatch;
60
+
61
+ await routeMatch.ref(ctx);
62
+ };
63
+ };
64
+ }
@@ -0,0 +1,45 @@
1
+ import { Logger } from 'nightingale-logger';
2
+ import type { AlpNodeApp, Context } from '../AlpNodeApp';
3
+ import type { Translations } from './load';
4
+ import load from './load';
5
+
6
+ const logger = new Logger('alp:translate');
7
+
8
+ type Args = Record<string, any>;
9
+
10
+ export interface TranslateBaseContext {
11
+ t: (id: string, args: Args) => string;
12
+ }
13
+ export interface TranslateContext {
14
+ readonly language: string;
15
+ }
16
+
17
+ export default function alpTranslate(
18
+ dirname: string,
19
+ ): (app: AlpNodeApp) => void {
20
+ dirname = dirname.replace(/\/*$/, '/');
21
+ return (app: AlpNodeApp) => {
22
+ const appTranslations = new Map<string, Translations>();
23
+
24
+ Object.assign(app.context, {
25
+ t(this: Context, id: string, args: Args): string {
26
+ const msg = appTranslations.get(this.language)!.get(id);
27
+ if (!msg) {
28
+ logger.warn('invalid msg', { language: this.language, id });
29
+ return id;
30
+ }
31
+
32
+ return msg.format(args) as string;
33
+ },
34
+ });
35
+
36
+ const config = app.config;
37
+
38
+ config.get<string[]>('availableLanguages').forEach((language) => {
39
+ const translations = app.loadConfigSync(dirname + language);
40
+ appTranslations.set(language, load(translations, language));
41
+ });
42
+
43
+ return appTranslations;
44
+ };
45
+ }
@@ -0,0 +1,30 @@
1
+ import IntlMessageFormatDefault from 'intl-messageformat';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
4
+ const IntlMessageFormat: typeof IntlMessageFormatDefault =
5
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
6
+ (IntlMessageFormatDefault as any).default || IntlMessageFormatDefault;
7
+
8
+ export type Translations = ReadonlyMap<string, IntlMessageFormatDefault>;
9
+
10
+ export default function load(
11
+ translations: ReadonlyMap<string, unknown>,
12
+ language: string,
13
+ ): Translations {
14
+ const result = new Map();
15
+
16
+ (function loadMap(map, prefix) {
17
+ map.forEach((value: any, key) => {
18
+ if (typeof value === 'object') {
19
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
20
+ loadMap(value, `${prefix}${key}.`);
21
+ return;
22
+ }
23
+
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
25
+ result.set(`${prefix}${key}`, new IntlMessageFormat(value, language));
26
+ });
27
+ })(translations, '');
28
+
29
+ return result;
30
+ }
package/src/types.ts ADDED
@@ -0,0 +1,67 @@
1
+ type RawConfig = ReadonlyMap<string, any>;
2
+
3
+ export interface Config {
4
+ get: <T>(key: string) => T;
5
+ }
6
+
7
+ export type PackageConfig = Record<string, any>;
8
+
9
+ export interface NodeConfig extends Config {
10
+ loadConfigSync: (name: string) => RawConfig;
11
+ readonly packageConfig: PackageConfig;
12
+ }
13
+
14
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
15
+ export interface ContextState {}
16
+
17
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
18
+ export interface ContextSanitizedState {}
19
+
20
+ export interface BaseContext {
21
+ config: Config;
22
+ }
23
+
24
+ export interface Context extends BaseContext {
25
+ state: ContextState;
26
+ sanitizedState: ContextSanitizedState;
27
+ status: number;
28
+
29
+ response: any;
30
+ redirect: (url: string) => Promise<void>;
31
+ [key: string]: any;
32
+ }
33
+
34
+ export interface ApplicationInCreation {
35
+ config?: Config;
36
+ context: BaseContext;
37
+ }
38
+
39
+ export interface Application extends ApplicationInCreation {
40
+ config: Config;
41
+ }
42
+
43
+ export interface NodeApplicationInCreation extends ApplicationInCreation {
44
+ loadConfigSync: (name: string) => RawConfig;
45
+ }
46
+
47
+ export interface BrowserApplicationInCreation extends ApplicationInCreation {
48
+ appVersion: string;
49
+ existsConfig: (name: string) => Promise<boolean> | boolean;
50
+ loadConfig: (name: string) => Promise<RawConfig>;
51
+ createContext: () => Context;
52
+ }
53
+
54
+ export interface NodeApplication
55
+ extends Application,
56
+ NodeApplicationInCreation {
57
+ config: NodeConfig;
58
+ dirname: string;
59
+ on: (event: 'close', callback: () => void) => void;
60
+ existsConfigSync: (name: string) => boolean;
61
+ loadConfigSync: (name: string) => RawConfig;
62
+ }
63
+
64
+ export interface HtmlError extends Error {
65
+ status: number;
66
+ expose?: true;
67
+ }