@dismissible/nestjs-api 2.0.1 → 2.0.2-alpha.99ffc23.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.
@@ -31,6 +31,16 @@ storage:
31
31
  jwtAuth:
32
32
  enabled: false
33
33
 
34
+ rateLimiter:
35
+ enabled: true
36
+ points: ${DISMISSIBLE_RATE_LIMITER_POINTS:-10000}
37
+ duration: ${DISMISSIBLE_RATE_LIMITER_DURATION:-1}
38
+ blockDuration: ${DISMISSIBLE_RATE_LIMITER_BLOCK_DURATION:-1}
39
+ keyType: ${DISMISSIBLE_RATE_LIMITER_KEY_TYPE:-ip,origin,referrer}
40
+ keyMode: ${DISMISSIBLE_RATE_LIMITER_KEY_MODE:-any}
41
+ ignoredKeys: ${DISMISSIBLE_RATE_LIMITER_IGNORED_KEYS:-}
42
+ priority: ${DISMISSIBLE_RATE_LIMITER_PRIORITY:--101}
43
+
34
44
  validation:
35
45
  disableErrorMessages: false
36
46
  whitelist: false
package/config/.env.yaml CHANGED
@@ -48,6 +48,23 @@ jwtAuth:
48
48
  userIdMatchRegex: ${DISMISSIBLE_JWT_AUTH_USER_ID_MATCH_REGEX:-}
49
49
  userIdClaim: ${DISMISSIBLE_JWT_AUTH_USER_ID_CLAIM:-sub}
50
50
 
51
+ rateLimiter:
52
+ enabled: ${DISMISSIBLE_RATE_LIMITER_ENABLED:-false}
53
+ # Number of requests allowed per duration window
54
+ points: ${DISMISSIBLE_RATE_LIMITER_POINTS:-10000}
55
+ # Time window in seconds for rate limiting
56
+ duration: ${DISMISSIBLE_RATE_LIMITER_DURATION:-1}
57
+ # Optional: Duration in seconds to block requests after limit is exceeded (if not set, requests are allowed again after the duration window resets)
58
+ blockDuration: ${DISMISSIBLE_RATE_LIMITER_BLOCK_DURATION:-}
59
+ # Key type(s) for rate limiting: 'ip' (by IP address), 'origin' (by Origin header), 'referrer' (by Referer header). Can be comma-separated to combine types (e.g., 'ip,origin')
60
+ keyType: ${DISMISSIBLE_RATE_LIMITER_KEY_TYPE:-ip,origin,referrer}
61
+ # Mode for combining key types: 'and' (combine all), 'or' (use first available), 'any' (check all independently)
62
+ keyMode: ${DISMISSIBLE_RATE_LIMITER_KEY_MODE:-any}
63
+ # Optional: Comma-separated keys to bypass rate limiting. Matching is exact after trim+lowercase. For Origin/Referer, the URL hostname is matched (e.g. "https://google.com/search" matches "google.com").
64
+ ignoredKeys: ${DISMISSIBLE_RATE_LIMITER_IGNORED_KEYS:-}
65
+ # Hook priority (lower numbers run first)
66
+ priority: ${DISMISSIBLE_RATE_LIMITER_PRIORITY:--101}
67
+
51
68
  validation:
52
69
  disableErrorMessages: ${DISMISSIBLE_VALIDATION_DISABLE_ERROR_MESSAGES:-true}
53
70
  whitelist: ${DISMISSIBLE_VALIDATION_WHITELIST:-true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dismissible/nestjs-api",
3
- "version": "2.0.1",
3
+ "version": "2.0.2-alpha.99ffc23.0",
4
4
  "description": "Dismissible API application",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -30,25 +30,26 @@
30
30
  "storage:setup:dynamodb": "npx dismissible-dynamodb-setup"
31
31
  },
32
32
  "dependencies": {
33
- "@dismissible/nestjs-core": "^2.0.1",
34
- "@dismissible/nestjs-item": "^2.0.1",
35
- "@dismissible/nestjs-jwt-auth-hook": "^2.0.1",
36
- "@dismissible/nestjs-storage": "^2.0.1",
37
- "@dismissible/nestjs-postgres-storage": "^2.0.1",
38
- "@dismissible/nestjs-dynamodb-storage": "^2.0.1",
39
- "@dismissible/nestjs-logger": "^2.0.1",
40
- "@nestjs/common": "^11.1.11",
41
- "@nestjs/core": "^11.1.11",
42
- "@nestjs/platform-fastify": "^11.1.11",
43
- "fastify": "^5.6.2",
44
- "@fastify/helmet": "^13.0.2",
45
- "@fastify/static": "^8.3.0",
46
- "@nestjs/swagger": "^11.2.3",
47
- "class-transformer": "^0.5.1",
48
- "class-validator": "^0.14.3",
49
- "nest-typed-config": "^2.10.1",
50
- "reflect-metadata": "^0.2.2",
51
- "rxjs": "^7.8.2"
33
+ "@dismissible/nestjs-core": "2.0.2-alpha.99ffc23.0",
34
+ "@dismissible/nestjs-item": "2.0.2-alpha.99ffc23.0",
35
+ "@dismissible/nestjs-jwt-auth-hook": "2.0.2-alpha.99ffc23.0",
36
+ "@dismissible/nestjs-storage": "2.0.2-alpha.99ffc23.0",
37
+ "@dismissible/nestjs-postgres-storage": "2.0.2-alpha.99ffc23.0",
38
+ "@dismissible/nestjs-dynamodb-storage": "2.0.2-alpha.99ffc23.0",
39
+ "@dismissible/nestjs-logger": "2.0.2-alpha.99ffc23.0",
40
+ "@dismissible/nestjs-rate-limiter-hook": "2.0.1",
41
+ "@nestjs/common": "11.1.11",
42
+ "@nestjs/core": "11.1.11",
43
+ "@nestjs/platform-fastify": "11.1.11",
44
+ "fastify": "5.6.2",
45
+ "@fastify/helmet": "13.0.2",
46
+ "@fastify/static": "8.3.0",
47
+ "@nestjs/swagger": "11.2.3",
48
+ "class-transformer": "0.5.1",
49
+ "class-validator": "0.14.3",
50
+ "nest-typed-config": "2.10.1",
51
+ "reflect-metadata": "0.2.2",
52
+ "rxjs": "7.8.2"
52
53
  },
53
54
  "keywords": [
54
55
  "nestjs",
@@ -64,4 +65,4 @@
64
65
  "access": "public"
65
66
  },
66
67
  "type": "commonjs"
67
- }
68
+ }
@@ -1,11 +1,15 @@
1
1
  import { DynamicModule, ModuleMetadata, Type } from '@nestjs/common';
2
+ import { IDismissibleLifecycleHook } from '@dismissible/nestjs-core';
2
3
  import { IDismissibleLogger } from '@dismissible/nestjs-logger';
3
4
  import { DefaultAppConfig } from './config/default-app.config';
5
+ import { StorageType } from './storage/storage.config';
4
6
  export type AppModuleOptions = {
5
7
  configPath?: string;
6
8
  schema?: new () => DefaultAppConfig;
7
9
  logger?: Type<IDismissibleLogger>;
8
10
  imports?: DynamicModule[];
11
+ hooks?: Type<IDismissibleLifecycleHook>[];
12
+ storage?: StorageType;
9
13
  };
10
14
  export declare class AppModule {
11
15
  static forRoot(options?: AppModuleOptions): {
package/src/app.module.js CHANGED
@@ -11,6 +11,8 @@ const path_1 = require("path");
11
11
  const nestjs_core_1 = require("@dismissible/nestjs-core");
12
12
  const dynamic_storage_module_1 = require("./storage/dynamic-storage.module");
13
13
  const nestjs_jwt_auth_hook_1 = require("@dismissible/nestjs-jwt-auth-hook");
14
+ const nestjs_rate_limiter_hook_1 = require("@dismissible/nestjs-rate-limiter-hook");
15
+ const class_transformer_1 = require("class-transformer");
14
16
  let AppModule = AppModule_1 = class AppModule {
15
17
  static forRoot(options) {
16
18
  return {
@@ -27,19 +29,25 @@ let AppModule = AppModule_1 = class AppModule {
27
29
  }),
28
30
  health_1.HealthModule,
29
31
  ...(options?.imports ?? []),
32
+ nestjs_rate_limiter_hook_1.RateLimiterHookModule.forRootAsync({
33
+ useFactory: (config) => {
34
+ return config.rateLimiter ?? (0, class_transformer_1.plainToClass)(nestjs_rate_limiter_hook_1.RateLimiterHookConfig, { enabled: false });
35
+ },
36
+ inject: [options?.schema ?? app_config_1.AppConfig],
37
+ }),
30
38
  nestjs_jwt_auth_hook_1.JwtAuthHookModule.forRootAsync({
31
39
  useFactory: (config) => config,
32
40
  inject: [nestjs_jwt_auth_hook_1.JwtAuthHookConfig],
33
41
  }),
34
42
  nestjs_core_1.DismissibleModule.forRoot({
35
43
  logger: options?.logger,
36
- hooks: [nestjs_jwt_auth_hook_1.JwtAuthHook],
44
+ hooks: [nestjs_jwt_auth_hook_1.JwtAuthHook, nestjs_rate_limiter_hook_1.RateLimiterHook, ...(options?.hooks ?? [])],
37
45
  storage: dynamic_storage_module_1.DynamicStorageModule.forRootAsync({
38
46
  // TODO: nestjs doesn't support optional dynamic modules.
39
47
  // So instead, we are just using the env vars to switch between modules.
40
48
  // This isn't ideal, but there's not a great option. I will look to see
41
49
  // if we can raise an issue similar to this: https://github.com/nestjs/nest/issues/9868
42
- storage: process.env.DISMISSIBLE_STORAGE_TYPE,
50
+ storage: options?.storage ?? process.env.DISMISSIBLE_STORAGE_TYPE,
43
51
  }),
44
52
  }),
45
53
  ],
@@ -1 +1 @@
1
- {"version":3,"file":"app.module.js","sourceRoot":"","sources":["../../../api/src/app.module.ts"],"names":[],"mappings":";;;;;AAAA,2CAA6E;AAC7E,qCAAwC;AACxC,qCAAwC;AACxC,oDAAgD;AAChD,+BAA4B;AAC5B,0DAA6D;AAG7D,6EAAwE;AACxE,4EAI2C;AAWpC,IAAM,SAAS,iBAAf,MAAM,SAAS;IACpB,MAAM,CAAC,OAAO,CAAC,OAA0B;QACvC,OAAO;YACL,MAAM,EAAE,WAAS;YACjB,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;SACnC,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,iBAAiB,CAAC,OAA0B;QACjD,OAAO;YACL,OAAO,EAAE;gBACP,qBAAY,CAAC,OAAO,CAAC;oBACnB,IAAI,EAAE,OAAO,EAAE,UAAU,IAAI,IAAA,WAAI,EAAC,SAAS,EAAE,WAAW,CAAC;oBACzD,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,sBAAS;iBACrC,CAAC;gBACF,qBAAY;gBACZ,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC;gBAC3B,wCAAiB,CAAC,YAAY,CAAC;oBAC7B,UAAU,EAAE,CAAC,MAAyB,EAAE,EAAE,CAAC,MAAM;oBACjD,MAAM,EAAE,CAAC,wCAAiB,CAAC;iBAC5B,CAAC;gBACF,+BAAiB,CAAC,OAAO,CAAC;oBACxB,MAAM,EAAE,OAAO,EAAE,MAAM;oBACvB,KAAK,EAAE,CAAC,kCAAW,CAAC;oBACpB,OAAO,EAAE,6CAAoB,CAAC,YAAY,CAAC;wBACzC,yDAAyD;wBACzD,0EAA0E;wBAC1E,yEAAyE;wBACzE,yFAAyF;wBACzF,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAuC;qBAC7D,CAAC;iBACH,CAAC;aACH;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AAnCY,8BAAS;oBAAT,SAAS;IADrB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,SAAS,CAmCrB"}
1
+ {"version":3,"file":"app.module.js","sourceRoot":"","sources":["../../../api/src/app.module.ts"],"names":[],"mappings":";;;;;AAAA,2CAA6E;AAC7E,qCAAwC;AACxC,qCAAwC;AACxC,oDAAgD;AAChD,+BAA4B;AAC5B,0DAAwF;AAGxF,6EAAwE;AACxE,4EAI2C;AAE3C,oFAI+C;AAC/C,yDAAiD;AAY1C,IAAM,SAAS,iBAAf,MAAM,SAAS;IACpB,MAAM,CAAC,OAAO,CAAC,OAA0B;QACvC,OAAO;YACL,MAAM,EAAE,WAAS;YACjB,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;SACnC,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,iBAAiB,CAAC,OAA0B;QACjD,OAAO;YACL,OAAO,EAAE;gBACP,qBAAY,CAAC,OAAO,CAAC;oBACnB,IAAI,EAAE,OAAO,EAAE,UAAU,IAAI,IAAA,WAAI,EAAC,SAAS,EAAE,WAAW,CAAC;oBACzD,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,sBAAS;iBACrC,CAAC;gBACF,qBAAY;gBACZ,GAAG,CAAC,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC;gBAC3B,gDAAqB,CAAC,YAAY,CAAC;oBACjC,UAAU,EAAE,CAAC,MAAwB,EAAE,EAAE;wBACvC,OAAO,MAAM,CAAC,WAAW,IAAI,IAAA,gCAAY,EAAC,gDAAqB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;oBACvF,CAAC;oBACD,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,sBAAS,CAAC;iBACvC,CAAC;gBACF,wCAAiB,CAAC,YAAY,CAAC;oBAC7B,UAAU,EAAE,CAAC,MAAyB,EAAE,EAAE,CAAC,MAAM;oBACjD,MAAM,EAAE,CAAC,wCAAiB,CAAC;iBAC5B,CAAC;gBACF,+BAAiB,CAAC,OAAO,CAAC;oBACxB,MAAM,EAAE,OAAO,EAAE,MAAM;oBACvB,KAAK,EAAE,CAAC,kCAAW,EAAE,0CAAe,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;oBAChE,OAAO,EAAE,6CAAoB,CAAC,YAAY,CAAC;wBACzC,yDAAyD;wBACzD,0EAA0E;wBAC1E,yEAAyE;wBACzE,yFAAyF;wBACzF,OAAO,EAAE,OAAO,EAAE,OAAO,IAAK,OAAO,CAAC,GAAG,CAAC,wBAAwC;qBACnF,CAAC;iBACH,CAAC;aACH;SACF,CAAC;IACJ,CAAC;CACF,CAAA;AAzCY,8BAAS;oBAAT,SAAS;IADrB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,SAAS,CAyCrB"}
@@ -2,8 +2,10 @@ import { ServerConfig } from '../server/server.config';
2
2
  import { CorsConfig } from '../cors';
3
3
  import { HelmetConfig } from '../helmet';
4
4
  import { ValidationConfig } from '../validation';
5
+ import { RateLimiterHookConfig } from '@dismissible/nestjs-rate-limiter-hook';
5
6
  export declare class DefaultAppConfig {
6
7
  readonly server: ServerConfig;
8
+ readonly rateLimiter?: RateLimiterHookConfig;
7
9
  readonly cors: CorsConfig;
8
10
  readonly helmet: HelmetConfig;
9
11
  readonly validation: ValidationConfig;
@@ -8,6 +8,7 @@ const server_config_1 = require("../server/server.config");
8
8
  const cors_1 = require("../cors");
9
9
  const helmet_1 = require("../helmet");
10
10
  const validation_1 = require("../validation");
11
+ const nestjs_rate_limiter_hook_1 = require("@dismissible/nestjs-rate-limiter-hook");
11
12
  class DefaultAppConfig {
12
13
  }
13
14
  exports.DefaultAppConfig = DefaultAppConfig;
@@ -16,6 +17,12 @@ tslib_1.__decorate([
16
17
  (0, class_transformer_1.Type)(() => server_config_1.ServerConfig),
17
18
  tslib_1.__metadata("design:type", server_config_1.ServerConfig)
18
19
  ], DefaultAppConfig.prototype, "server", void 0);
20
+ tslib_1.__decorate([
21
+ (0, class_validator_1.IsOptional)(),
22
+ (0, class_validator_1.ValidateNested)(),
23
+ (0, class_transformer_1.Type)(() => nestjs_rate_limiter_hook_1.RateLimiterHookConfig),
24
+ tslib_1.__metadata("design:type", nestjs_rate_limiter_hook_1.RateLimiterHookConfig)
25
+ ], DefaultAppConfig.prototype, "rateLimiter", void 0);
19
26
  tslib_1.__decorate([
20
27
  (0, class_validator_1.ValidateNested)(),
21
28
  (0, class_transformer_1.Type)(() => cors_1.CorsConfig),
@@ -1 +1 @@
1
- {"version":3,"file":"default-app.config.js","sourceRoot":"","sources":["../../../../api/src/config/default-app.config.ts"],"names":[],"mappings":";;;;AAAA,qDAAiD;AACjD,yDAAyC;AACzC,2DAAuD;AACvD,kCAAqC;AACrC,sCAAyC;AACzC,8CAAiD;AAEjD,MAAa,gBAAgB;CAgB5B;AAhBD,4CAgBC;AAbiB;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,4BAAY,CAAC;sCACA,4BAAY;gDAAC;AAItB;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,iBAAU,CAAC;sCACA,iBAAU;8CAAC;AAIlB;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,qBAAY,CAAC;sCACA,qBAAY;gDAAC;AAItB;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,6BAAgB,CAAC;sCACA,6BAAgB;oDAAC"}
1
+ {"version":3,"file":"default-app.config.js","sourceRoot":"","sources":["../../../../api/src/config/default-app.config.ts"],"names":[],"mappings":";;;;AAAA,qDAA6D;AAC7D,yDAAyC;AACzC,2DAAuD;AACvD,kCAAqC;AACrC,sCAAyC;AACzC,8CAAiD;AACjD,oFAA8E;AAE9E,MAAa,gBAAgB;CAqB5B;AArBD,4CAqBC;AAlBiB;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,4BAAY,CAAC;sCACA,4BAAY;gDAAC;AAKtB;IAHf,IAAA,4BAAU,GAAE;IACZ,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,gDAAqB,CAAC;sCACJ,gDAAqB;qDAAC;AAIpC;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,iBAAU,CAAC;sCACA,iBAAU;8CAAC;AAIlB;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,qBAAY,CAAC;sCACA,qBAAY;gDAAC;AAItB;IAFf,IAAA,gCAAc,GAAE;IAChB,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,6BAAgB,CAAC;sCACA,6BAAgB;oDAAC"}
@@ -2,6 +2,8 @@ import { INestApplication } from '@nestjs/common';
2
2
  import { DefaultAppConfig } from './config/default-app.config';
3
3
  import { IDismissibleLogger } from '@dismissible/nestjs-logger';
4
4
  import { Type, DynamicModule } from '@nestjs/common';
5
+ import { IDismissibleLifecycleHook } from '@dismissible/nestjs-hooks';
6
+ import { StorageType } from './storage/storage.config';
5
7
  export interface IDismissibleNestApplication {
6
8
  getNestApplication(): INestApplication;
7
9
  start(): Promise<void>;
@@ -11,6 +13,8 @@ export interface IDismissibleNestFactoryOptions {
11
13
  schema?: new () => DefaultAppConfig;
12
14
  logger?: Type<IDismissibleLogger>;
13
15
  imports?: DynamicModule[];
16
+ hooks?: Type<IDismissibleLifecycleHook>[];
17
+ storage?: StorageType;
14
18
  }
15
19
  export declare class DismissibleNestFactory {
16
20
  static create(options?: IDismissibleNestFactoryOptions): Promise<IDismissibleNestApplication>;
@@ -1 +1 @@
1
- {"version":3,"file":"dismissible-nest-factory.js","sourceRoot":"","sources":["../../../api/src/dismissible-nest-factory.ts"],"names":[],"mappings":";;;AACA,uCAA2C;AAC3C,+DAAkF;AAClF,6CAAyC;AACzC,0DAAsD;AACtD,2CAA2C;AAiB3C,MAAM,0BAA0B;IAC9B,YAA6B,GAA2B;QAA3B,QAAG,GAAH,GAAG,CAAwB;IAAG,CAAC;IAE5D,KAAK,CAAC,KAAK;QACT,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,4BAAY,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,IAAI,IAAI,CAAC;QACvC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,kDAAkD,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;CACF;AAED,MAAa,sBAAsB;IACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CACjB,OAAwC;QAExC,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAClC,sBAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAC1B,IAAI,iCAAc,CAAC;YACjB,SAAS,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO;SAC9B,CAAC,CACH,CAAC;QACF,MAAM,IAAA,wBAAY,EAAC,GAAG,CAAC,CAAC;QAExB,OAAO,IAAI,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;CACF;AAdD,wDAcC"}
1
+ {"version":3,"file":"dismissible-nest-factory.js","sourceRoot":"","sources":["../../../api/src/dismissible-nest-factory.ts"],"names":[],"mappings":";;;AACA,uCAA2C;AAC3C,+DAAkF;AAClF,6CAAyC;AACzC,0DAAsD;AACtD,2CAA2C;AAqB3C,MAAM,0BAA0B;IAC9B,YAA6B,GAA2B;QAA3B,QAAG,GAAH,GAAG,CAAwB;IAAG,CAAC;IAE5D,KAAK,CAAC,KAAK;QACT,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,4BAAY,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,IAAI,IAAI,CAAC;QACvC,MAAM,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,kDAAkD,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,kBAAkB;QAChB,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;CACF;AAED,MAAa,sBAAsB;IACjC,MAAM,CAAC,KAAK,CAAC,MAAM,CACjB,OAAwC;QAExC,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAClC,sBAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAC1B,IAAI,iCAAc,CAAC;YACjB,SAAS,EAAE,EAAE,GAAG,IAAI,EAAE,OAAO;SAC9B,CAAC,CACH,CAAC;QACF,MAAM,IAAA,wBAAY,EAAC,GAAG,CAAC,CAAC;QAExB,OAAO,IAAI,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;CACF;AAdD,wDAcC"}