@fnd-platform/api 1.0.0-alpha.1 → 1.0.0-alpha.3

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.
@@ -81,7 +81,6 @@ class FndApiProject extends projen_1.typescript.TypeScriptProject {
81
81
  rootDir: 'src',
82
82
  declaration: true,
83
83
  declarationMap: true,
84
- sourceMap: true,
85
84
  strict: true,
86
85
  esModuleInterop: true,
87
86
  skipLibCheck: true,
@@ -99,6 +98,8 @@ class FndApiProject extends projen_1.typescript.TypeScriptProject {
99
98
  this.dynamodbEnabled = options.dynamodb ?? true;
100
99
  this.cognitoEnabled = options.cognito ?? true;
101
100
  this.corsEnabled = options.cors ?? true;
101
+ // Add @fnd-platform/api to parent's devDependencies (for .projenrc.ts imports)
102
+ this.parentProject.addDevDeps('@fnd-platform/api@^1.0.0-alpha.1');
102
103
  // Add dependencies
103
104
  this.addApiDependencies();
104
105
  // Generate directory structure and files
@@ -125,6 +126,12 @@ class FndApiProject extends projen_1.typescript.TypeScriptProject {
125
126
  new projen_1.SampleFile(this, 'src/handlers/health.ts', {
126
127
  contents: this.getHealthHandlerTemplate(),
127
128
  });
129
+ // Content CRUD handler - included when DynamoDB is enabled
130
+ if (this.dynamodbEnabled) {
131
+ new projen_1.SampleFile(this, 'src/handlers/content.ts', {
132
+ contents: this.getContentHandlerTemplate(),
133
+ });
134
+ }
128
135
  }
129
136
  /**
130
137
  * Generates the lib directory with utility files.
@@ -138,6 +145,16 @@ class FndApiProject extends projen_1.typescript.TypeScriptProject {
138
145
  new projen_1.SampleFile(this, 'src/lib/errors.ts', {
139
146
  contents: this.getErrorsTemplate(),
140
147
  });
148
+ // Request utilities
149
+ new projen_1.SampleFile(this, 'src/lib/request.ts', {
150
+ contents: this.getRequestTemplate(),
151
+ });
152
+ // DynamoDB key builders (included when DynamoDB is enabled)
153
+ if (this.dynamodbEnabled) {
154
+ new projen_1.SampleFile(this, 'src/lib/keys.ts', {
155
+ contents: this.getKeysTemplate(),
156
+ });
157
+ }
141
158
  // Middleware utilities (stub for Sprint 03)
142
159
  new projen_1.SampleFile(this, 'src/lib/middleware.ts', {
143
160
  contents: this.getMiddlewareTemplate(),
@@ -661,6 +678,585 @@ export interface UserEntity extends BaseEntity {
661
678
  }
662
679
 
663
680
  // Add your custom entity types below
681
+ `;
682
+ }
683
+ /**
684
+ * Returns the request utilities template content.
685
+ */
686
+ getRequestTemplate() {
687
+ return `import type { APIGatewayProxyEvent } from 'aws-lambda';
688
+ import { ValidationError, UnauthorizedError } from './errors';
689
+
690
+ /**
691
+ * API Gateway event with Cognito authorizer claims.
692
+ */
693
+ interface AuthenticatedEvent extends APIGatewayProxyEvent {
694
+ requestContext: APIGatewayProxyEvent['requestContext'] & {
695
+ authorizer: {
696
+ claims: {
697
+ sub: string;
698
+ email: string;
699
+ 'cognito:groups'?: string[];
700
+ };
701
+ };
702
+ };
703
+ }
704
+
705
+ /**
706
+ * Parse JSON body from API Gateway event.
707
+ *
708
+ * @param event - API Gateway proxy event
709
+ * @returns Parsed JSON body as type T
710
+ * @throws {ValidationError} If body is missing or invalid JSON
711
+ */
712
+ export function parseBody<T>(event: APIGatewayProxyEvent): T {
713
+ if (!event.body) {
714
+ throw new ValidationError('Request body is required');
715
+ }
716
+
717
+ try {
718
+ return JSON.parse(event.body) as T;
719
+ } catch {
720
+ throw new ValidationError('Invalid JSON in request body');
721
+ }
722
+ }
723
+
724
+ /**
725
+ * Get a required path parameter from the event.
726
+ *
727
+ * @param event - API Gateway proxy event
728
+ * @param name - Name of the path parameter
729
+ * @returns The path parameter value
730
+ * @throws {ValidationError} If the parameter is missing
731
+ */
732
+ export function requirePathParam(event: APIGatewayProxyEvent, name: string): string {
733
+ const value = event.pathParameters?.[name];
734
+ if (!value) {
735
+ throw new ValidationError(\`Missing path parameter: \${name}\`);
736
+ }
737
+ return value;
738
+ }
739
+
740
+ /**
741
+ * Get an optional query parameter from the event.
742
+ *
743
+ * @param event - API Gateway proxy event
744
+ * @param name - Name of the query parameter
745
+ * @param defaultValue - Optional default value if parameter is missing
746
+ * @returns The query parameter value or default
747
+ */
748
+ export function getQueryParam(
749
+ event: APIGatewayProxyEvent,
750
+ name: string,
751
+ defaultValue?: string
752
+ ): string | undefined {
753
+ return event.queryStringParameters?.[name] ?? defaultValue;
754
+ }
755
+
756
+ /**
757
+ * Get user ID from an authenticated request.
758
+ *
759
+ * @param event - Authenticated API Gateway event
760
+ * @returns The user ID from the JWT token
761
+ * @throws {UnauthorizedError} If user ID is not found in the token
762
+ */
763
+ export function getUserId(event: AuthenticatedEvent): string {
764
+ const userId = event.requestContext.authorizer?.claims?.sub;
765
+ if (!userId) {
766
+ throw new UnauthorizedError('User ID not found in token');
767
+ }
768
+ return userId;
769
+ }
770
+ `;
771
+ }
772
+ /**
773
+ * Returns the DynamoDB key builders template content.
774
+ */
775
+ getKeysTemplate() {
776
+ return `/**
777
+ * DynamoDB key builders for single-table design.
778
+ *
779
+ * These helpers generate consistent key structures for content entities
780
+ * following the fnd-platform single-table design patterns.
781
+ */
782
+
783
+ /**
784
+ * Key structure for DynamoDB primary key.
785
+ */
786
+ export interface PrimaryKey {
787
+ PK: string;
788
+ SK: string;
789
+ }
790
+
791
+ /**
792
+ * Key structure for GSI1 (slug lookup).
793
+ */
794
+ export interface GSI1Key {
795
+ GSI1PK: string;
796
+ GSI1SK: string;
797
+ }
798
+
799
+ /**
800
+ * Key structure for GSI2 (type/status listing).
801
+ */
802
+ export interface GSI2Key {
803
+ GSI2PK: string;
804
+ GSI2SK: string;
805
+ }
806
+
807
+ /**
808
+ * Content key builders for DynamoDB operations.
809
+ */
810
+ export const contentKeys = {
811
+ /**
812
+ * Generate partition key for content item.
813
+ */
814
+ pk: (id: string): string => \`CONTENT#\${id}\`,
815
+
816
+ /**
817
+ * Generate sort key for content item.
818
+ */
819
+ sk: (): string => 'CONTENT',
820
+
821
+ /**
822
+ * Generate full primary key for content item.
823
+ */
824
+ keys: (id: string): PrimaryKey => ({
825
+ PK: contentKeys.pk(id),
826
+ SK: contentKeys.sk(),
827
+ }),
828
+
829
+ /**
830
+ * GSI1 key builders for slug-based lookups.
831
+ */
832
+ gsi1: {
833
+ /**
834
+ * Generate GSI1 keys for looking up content by slug.
835
+ */
836
+ bySlug: (slug: string): GSI1Key => ({
837
+ GSI1PK: \`CONTENT#SLUG#\${slug}\`,
838
+ GSI1SK: 'CONTENT',
839
+ }),
840
+ },
841
+
842
+ /**
843
+ * GSI2 key builders for listing content by type.
844
+ */
845
+ gsi2: {
846
+ /**
847
+ * Generate GSI2 keys for listing content by type with timestamp sorting.
848
+ */
849
+ byType: (contentType: string, timestamp: string): GSI2Key => ({
850
+ GSI2PK: contentType,
851
+ GSI2SK: timestamp,
852
+ }),
853
+ },
854
+ };
855
+
856
+ /**
857
+ * User key builders for DynamoDB operations.
858
+ */
859
+ export const userKeys = {
860
+ /**
861
+ * Generate partition key for user.
862
+ */
863
+ pk: (id: string): string => \`USER#\${id}\`,
864
+
865
+ /**
866
+ * Sort key generators for user-related items.
867
+ */
868
+ sk: {
869
+ /**
870
+ * Generate sort key for user profile.
871
+ */
872
+ profile: (): string => 'PROFILE',
873
+ },
874
+
875
+ /**
876
+ * Generate full primary key for user profile.
877
+ */
878
+ keys: (id: string): PrimaryKey => ({
879
+ PK: userKeys.pk(id),
880
+ SK: userKeys.sk.profile(),
881
+ }),
882
+ };
883
+ `;
884
+ }
885
+ /**
886
+ * Returns the content handler template content.
887
+ */
888
+ getContentHandlerTemplate() {
889
+ return `/**
890
+ * Content CRUD handlers with DynamoDB operations.
891
+ *
892
+ * Implements full CRUD operations for content entities using
893
+ * single-table design patterns with GSI support for efficient queries.
894
+ */
895
+
896
+ import type { APIGatewayProxyHandler, APIGatewayProxyEvent } from 'aws-lambda';
897
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
898
+ import {
899
+ DynamoDBDocumentClient,
900
+ QueryCommand,
901
+ GetCommand,
902
+ PutCommand,
903
+ UpdateCommand,
904
+ DeleteCommand,
905
+ } from '@aws-sdk/lib-dynamodb';
906
+ import { success, notFound, created, badRequest } from '../lib/response';
907
+ import { parseBody, requirePathParam, getQueryParam, getUserId } from '../lib/request';
908
+ import { contentKeys } from '../lib/keys';
909
+
910
+ /**
911
+ * Content item interface.
912
+ */
913
+ interface ContentItem {
914
+ id: string;
915
+ slug: string;
916
+ title: string;
917
+ excerpt?: string;
918
+ content: string;
919
+ contentType: string;
920
+ status: 'draft' | 'published';
921
+ authorId: string;
922
+ createdAt: string;
923
+ updatedAt: string;
924
+ publishedAt?: string;
925
+ }
926
+
927
+ /**
928
+ * Input for creating new content.
929
+ */
930
+ interface CreateContentInput {
931
+ slug: string;
932
+ title: string;
933
+ excerpt?: string;
934
+ content: string;
935
+ contentType: string;
936
+ status?: 'draft' | 'published';
937
+ }
938
+
939
+ /**
940
+ * Input for updating existing content.
941
+ */
942
+ interface UpdateContentInput {
943
+ slug?: string;
944
+ title?: string;
945
+ excerpt?: string;
946
+ content?: string;
947
+ contentType?: string;
948
+ status?: 'draft' | 'published';
949
+ }
950
+
951
+ /**
952
+ * Authenticated event type with Cognito claims.
953
+ */
954
+ interface AuthenticatedEvent extends APIGatewayProxyEvent {
955
+ requestContext: APIGatewayProxyEvent['requestContext'] & {
956
+ authorizer: {
957
+ claims: { sub: string };
958
+ };
959
+ };
960
+ }
961
+
962
+ // Initialize DynamoDB client
963
+ const ddbClient = new DynamoDBClient({});
964
+ const client = DynamoDBDocumentClient.from(ddbClient, {
965
+ marshallOptions: {
966
+ removeUndefinedValues: true,
967
+ },
968
+ });
969
+
970
+ const TABLE_NAME = process.env.TABLE_NAME ?? '';
971
+
972
+ /**
973
+ * Map DynamoDB item to ContentItem.
974
+ */
975
+ function mapToContentItem(item: Record<string, unknown>): ContentItem {
976
+ return {
977
+ id: item.id as string,
978
+ slug: item.slug as string,
979
+ title: item.title as string,
980
+ excerpt: item.excerpt as string | undefined,
981
+ content: item.content as string,
982
+ contentType: item.contentType as string,
983
+ status: item.status as 'draft' | 'published',
984
+ authorId: item.authorId as string,
985
+ createdAt: item.createdAt as string,
986
+ updatedAt: item.updatedAt as string,
987
+ publishedAt: item.publishedAt as string | undefined,
988
+ };
989
+ }
990
+
991
+ /**
992
+ * GET /content - List content items.
993
+ */
994
+ export const list: APIGatewayProxyHandler = async (event) => {
995
+ const limit = Math.min(parseInt(getQueryParam(event, 'limit', '20') ?? '20', 10), 100);
996
+ const cursor = getQueryParam(event, 'cursor');
997
+ const contentType = getQueryParam(event, 'type') ?? 'blog-post';
998
+ const status = getQueryParam(event, 'status') ?? 'published';
999
+
1000
+ const result = await client.send(
1001
+ new QueryCommand({
1002
+ TableName: TABLE_NAME,
1003
+ IndexName: 'GSI2',
1004
+ KeyConditionExpression: 'GSI2PK = :pk',
1005
+ FilterExpression: '#status = :status',
1006
+ ExpressionAttributeNames: { '#status': 'status' },
1007
+ ExpressionAttributeValues: {
1008
+ ':pk': contentType,
1009
+ ':status': status,
1010
+ },
1011
+ ScanIndexForward: false,
1012
+ Limit: limit,
1013
+ ExclusiveStartKey: cursor
1014
+ ? JSON.parse(Buffer.from(cursor, 'base64').toString())
1015
+ : undefined,
1016
+ })
1017
+ );
1018
+
1019
+ const items = (result.Items ?? []).map(mapToContentItem);
1020
+
1021
+ return success({
1022
+ items,
1023
+ nextCursor: result.LastEvaluatedKey
1024
+ ? Buffer.from(JSON.stringify(result.LastEvaluatedKey)).toString('base64')
1025
+ : undefined,
1026
+ });
1027
+ };
1028
+
1029
+ /**
1030
+ * GET /content/:id - Get content by ID.
1031
+ */
1032
+ export const get: APIGatewayProxyHandler = async (event) => {
1033
+ const id = requirePathParam(event, 'id');
1034
+ const keys = contentKeys.keys(id);
1035
+
1036
+ const result = await client.send(
1037
+ new GetCommand({
1038
+ TableName: TABLE_NAME,
1039
+ Key: keys,
1040
+ })
1041
+ );
1042
+
1043
+ if (!result.Item) {
1044
+ return notFound(\`Content with id '\${id}' not found\`);
1045
+ }
1046
+
1047
+ return success(mapToContentItem(result.Item));
1048
+ };
1049
+
1050
+ /**
1051
+ * GET /content/slug/:slug - Get content by slug.
1052
+ */
1053
+ export const getBySlug: APIGatewayProxyHandler = async (event) => {
1054
+ const slug = requirePathParam(event, 'slug');
1055
+ const gsi1Keys = contentKeys.gsi1.bySlug(slug);
1056
+
1057
+ const result = await client.send(
1058
+ new QueryCommand({
1059
+ TableName: TABLE_NAME,
1060
+ IndexName: 'GSI1',
1061
+ KeyConditionExpression: 'GSI1PK = :pk AND GSI1SK = :sk',
1062
+ ExpressionAttributeValues: {
1063
+ ':pk': gsi1Keys.GSI1PK,
1064
+ ':sk': gsi1Keys.GSI1SK,
1065
+ },
1066
+ Limit: 1,
1067
+ })
1068
+ );
1069
+
1070
+ if (!result.Items?.length) {
1071
+ return notFound(\`Content with slug '\${slug}' not found\`);
1072
+ }
1073
+
1074
+ return success(mapToContentItem(result.Items[0]));
1075
+ };
1076
+
1077
+ /**
1078
+ * POST /content - Create new content.
1079
+ */
1080
+ export const create: APIGatewayProxyHandler = async (event) => {
1081
+ const body = parseBody<CreateContentInput>(event);
1082
+
1083
+ if (!body.slug || !body.title || !body.content || !body.contentType) {
1084
+ return badRequest('Missing required fields: slug, title, content, contentType');
1085
+ }
1086
+
1087
+ if (!/^[a-z0-9-]+$/.test(body.slug)) {
1088
+ return badRequest('Slug must contain only lowercase letters, numbers, and hyphens');
1089
+ }
1090
+
1091
+ // Check slug uniqueness
1092
+ const gsi1Keys = contentKeys.gsi1.bySlug(body.slug);
1093
+ const existingResult = await client.send(
1094
+ new QueryCommand({
1095
+ TableName: TABLE_NAME,
1096
+ IndexName: 'GSI1',
1097
+ KeyConditionExpression: 'GSI1PK = :pk AND GSI1SK = :sk',
1098
+ ExpressionAttributeValues: {
1099
+ ':pk': gsi1Keys.GSI1PK,
1100
+ ':sk': gsi1Keys.GSI1SK,
1101
+ },
1102
+ Limit: 1,
1103
+ })
1104
+ );
1105
+
1106
+ if (existingResult.Items?.length) {
1107
+ return badRequest(\`Content with slug '\${body.slug}' already exists\`);
1108
+ }
1109
+
1110
+ const authorId = getUserId(event as AuthenticatedEvent);
1111
+ const now = new Date().toISOString();
1112
+ const id = crypto.randomUUID();
1113
+ const status = body.status ?? 'draft';
1114
+
1115
+ const keys = contentKeys.keys(id);
1116
+ const gsi2Keys = contentKeys.gsi2.byType(body.contentType, now);
1117
+
1118
+ const item = {
1119
+ ...keys,
1120
+ ...gsi1Keys,
1121
+ ...gsi2Keys,
1122
+ id,
1123
+ slug: body.slug,
1124
+ title: body.title,
1125
+ excerpt: body.excerpt,
1126
+ content: body.content,
1127
+ contentType: body.contentType,
1128
+ status,
1129
+ authorId,
1130
+ createdAt: now,
1131
+ updatedAt: now,
1132
+ publishedAt: status === 'published' ? now : undefined,
1133
+ };
1134
+
1135
+ await client.send(
1136
+ new PutCommand({
1137
+ TableName: TABLE_NAME,
1138
+ Item: item,
1139
+ })
1140
+ );
1141
+
1142
+ return created(mapToContentItem(item));
1143
+ };
1144
+
1145
+ /**
1146
+ * PUT /content/:id - Update content.
1147
+ */
1148
+ export const update: APIGatewayProxyHandler = async (event) => {
1149
+ const id = requirePathParam(event, 'id');
1150
+ const body = parseBody<UpdateContentInput>(event);
1151
+ const keys = contentKeys.keys(id);
1152
+
1153
+ const existingResult = await client.send(
1154
+ new GetCommand({
1155
+ TableName: TABLE_NAME,
1156
+ Key: keys,
1157
+ })
1158
+ );
1159
+
1160
+ if (!existingResult.Item) {
1161
+ return notFound(\`Content with id '\${id}' not found\`);
1162
+ }
1163
+
1164
+ const existingItem = existingResult.Item;
1165
+ const now = new Date().toISOString();
1166
+
1167
+ // Build update expression
1168
+ const updateExpressionParts: string[] = ['#updatedAt = :updatedAt'];
1169
+ const expressionAttributeNames: Record<string, string> = { '#updatedAt': 'updatedAt' };
1170
+ const expressionAttributeValues: Record<string, unknown> = { ':updatedAt': now };
1171
+
1172
+ if (body.slug && body.slug !== existingItem.slug) {
1173
+ if (!/^[a-z0-9-]+$/.test(body.slug)) {
1174
+ return badRequest('Slug must contain only lowercase letters, numbers, and hyphens');
1175
+ }
1176
+ const newGsi1Keys = contentKeys.gsi1.bySlug(body.slug);
1177
+ updateExpressionParts.push('#slug = :slug', 'GSI1PK = :gsi1pk', 'GSI1SK = :gsi1sk');
1178
+ expressionAttributeNames['#slug'] = 'slug';
1179
+ expressionAttributeValues[':slug'] = body.slug;
1180
+ expressionAttributeValues[':gsi1pk'] = newGsi1Keys.GSI1PK;
1181
+ expressionAttributeValues[':gsi1sk'] = newGsi1Keys.GSI1SK;
1182
+ }
1183
+
1184
+ if (body.title !== undefined) {
1185
+ updateExpressionParts.push('#title = :title');
1186
+ expressionAttributeNames['#title'] = 'title';
1187
+ expressionAttributeValues[':title'] = body.title;
1188
+ }
1189
+
1190
+ if (body.excerpt !== undefined) {
1191
+ updateExpressionParts.push('excerpt = :excerpt');
1192
+ expressionAttributeValues[':excerpt'] = body.excerpt;
1193
+ }
1194
+
1195
+ if (body.content !== undefined) {
1196
+ updateExpressionParts.push('#content = :content');
1197
+ expressionAttributeNames['#content'] = 'content';
1198
+ expressionAttributeValues[':content'] = body.content;
1199
+ }
1200
+
1201
+ if (body.contentType !== undefined) {
1202
+ const newGsi2Keys = contentKeys.gsi2.byType(body.contentType, existingItem.createdAt as string);
1203
+ updateExpressionParts.push('contentType = :contentType', 'GSI2PK = :gsi2pk');
1204
+ expressionAttributeValues[':contentType'] = body.contentType;
1205
+ expressionAttributeValues[':gsi2pk'] = newGsi2Keys.GSI2PK;
1206
+ }
1207
+
1208
+ if (body.status !== undefined) {
1209
+ updateExpressionParts.push('#status = :status');
1210
+ expressionAttributeNames['#status'] = 'status';
1211
+ expressionAttributeValues[':status'] = body.status;
1212
+
1213
+ if (body.status === 'published' && !existingItem.publishedAt) {
1214
+ updateExpressionParts.push('publishedAt = :publishedAt');
1215
+ expressionAttributeValues[':publishedAt'] = now;
1216
+ }
1217
+ }
1218
+
1219
+ const result = await client.send(
1220
+ new UpdateCommand({
1221
+ TableName: TABLE_NAME,
1222
+ Key: keys,
1223
+ UpdateExpression: \`SET \${updateExpressionParts.join(', ')}\`,
1224
+ ExpressionAttributeNames: expressionAttributeNames,
1225
+ ExpressionAttributeValues: expressionAttributeValues,
1226
+ ReturnValues: 'ALL_NEW',
1227
+ })
1228
+ );
1229
+
1230
+ return success(mapToContentItem(result.Attributes!));
1231
+ };
1232
+
1233
+ /**
1234
+ * DELETE /content/:id - Delete content.
1235
+ */
1236
+ export const remove: APIGatewayProxyHandler = async (event) => {
1237
+ const id = requirePathParam(event, 'id');
1238
+ const keys = contentKeys.keys(id);
1239
+
1240
+ const existingResult = await client.send(
1241
+ new GetCommand({
1242
+ TableName: TABLE_NAME,
1243
+ Key: keys,
1244
+ })
1245
+ );
1246
+
1247
+ if (!existingResult.Item) {
1248
+ return notFound(\`Content with id '\${id}' not found\`);
1249
+ }
1250
+
1251
+ await client.send(
1252
+ new DeleteCommand({
1253
+ TableName: TABLE_NAME,
1254
+ Key: keys,
1255
+ })
1256
+ );
1257
+
1258
+ return success({ deleted: true, id });
1259
+ };
664
1260
  `;
665
1261
  }
666
1262
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api-project.js","sourceRoot":"","sources":["../src/api-project.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-project.js","sourceRoot":"","sources":["../src/api-project.ts"],"names":[],"mappings":";;;AAAA,mCAA4D;AAI5D,MAAM,EAAE,0BAA0B,EAAE,GAAG,mBAAU,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAa,aAAc,SAAQ,mBAAU,CAAC,iBAAiB;IAC7D;;OAEG;IACa,aAAa,CAAqB;IAElD;;OAEG;IACa,eAAe,CAAU;IAEzC;;OAEG;IACa,cAAc,CAAU;IAExC;;OAEG;IACa,WAAW,CAAU;IAErC;;;;;OAKG;IACH,YAAY,OAA6B;QACvC,4BAA4B;QAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5D,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QAE9D,KAAK,CAAC;YACJ,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM;YACN,oBAAoB,EAAE,MAAM;YAE5B,sCAAsC;YACtC,QAAQ,EAAE;gBACR,eAAe,EAAE;oBACf,MAAM,EAAE,QAAQ;oBAChB,MAAM,EAAE,UAAU;oBAClB,gBAAgB,EAAE,0BAA0B,CAAC,SAAS;oBACtD,MAAM,EAAE,KAAK;oBACb,OAAO,EAAE,KAAK;oBACd,WAAW,EAAE,IAAI;oBACjB,cAAc,EAAE,IAAI;oBACpB,MAAM,EAAE,IAAI;oBACZ,eAAe,EAAE,IAAI;oBACrB,YAAY,EAAE,IAAI;oBAClB,gCAAgC,EAAE,IAAI;iBACvC;aACF;YAED,6EAA6E;YAC7E,UAAU,EAAE,KAAK;YACjB,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,KAAK;SAClB,CAAC,CAAC;QAEH,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;QACpC,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC;QAChD,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC;QAC9C,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;QAExC,+EAA+E;QAC/E,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC;QAElE,mBAAmB;QACnB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAE1B,yCAAyC;QACzC,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACjC,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC5B,IAAI,CAAC,sBAAsB,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,uBAAuB;QACvB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,IAAI,CAAC,OAAO,CAAC,0BAA0B,EAAE,uBAAuB,CAAC,CAAC;QACpE,CAAC;QAED,mBAAmB;QACnB,IAAI,CAAC,UAAU,CAAC,mBAAmB,EAAE,SAAS,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC;IACnF,CAAC;IAED;;OAEG;IACK,yBAAyB;QAC/B,yCAAyC;QACzC,IAAI,mBAAU,CAAC,IAAI,EAAE,wBAAwB,EAAE;YAC7C,QAAQ,EAAE,IAAI,CAAC,wBAAwB,EAAE;SAC1C,CAAC,CAAC;QAEH,2DAA2D;QAC3D,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,IAAI,mBAAU,CAAC,IAAI,EAAE,yBAAyB,EAAE;gBAC9C,QAAQ,EAAE,IAAI,CAAC,yBAAyB,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED;;OAEG;IACK,oBAAoB;QAC1B,mBAAmB;QACnB,IAAI,mBAAU,CAAC,IAAI,EAAE,qBAAqB,EAAE;YAC1C,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE;SACrC,CAAC,CAAC;QAEH,gBAAgB;QAChB,IAAI,mBAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE;YACxC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE;SACnC,CAAC,CAAC;QAEH,oBAAoB;QACpB,IAAI,mBAAU,CAAC,IAAI,EAAE,oBAAoB,EAAE;YACzC,QAAQ,EAAE,IAAI,CAAC,kBAAkB,EAAE;SACpC,CAAC,CAAC;QAEH,4DAA4D;QAC5D,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,IAAI,mBAAU,CAAC,IAAI,EAAE,iBAAiB,EAAE;gBACtC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE;aACjC,CAAC,CAAC;QACL,CAAC;QAED,4CAA4C;QAC5C,IAAI,mBAAU,CAAC,IAAI,EAAE,uBAAuB,EAAE;YAC5C,QAAQ,EAAE,IAAI,CAAC,qBAAqB,EAAE;SACvC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,sBAAsB;QAC5B,YAAY;QACZ,IAAI,mBAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE;YACvC,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE;SACrC,CAAC,CAAC;QAEH,oBAAoB;QACpB,IAAI,mBAAU,CAAC,IAAI,EAAE,uBAAuB,EAAE;YAC5C,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE;SACxC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,wBAAwB;QAC9B,OAAO;;;;;;;;;;;;;;;;CAgBV,CAAC;IACA,CAAC;IAED;;OAEG;IACK,mBAAmB;QACzB,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW;YAClC,CAAC,CAAC;;kEAE0D;YAC5D,CAAC,CAAC,EAAE,CAAC;QAEP,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yCAkC8B,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8FnD,CAAC;IACA,CAAC;IAED;;OAEG;IACK,iBAAiB;QACvB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8FV,CAAC;IACA,CAAC;IAED;;OAEG;IACK,qBAAqB;QAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0EV,CAAC;IACA,CAAC;IAED;;OAEG;IACK,mBAAmB;QACzB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkEV,CAAC;IACA,CAAC;IAED;;OAEG;IACK,sBAAsB;QAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwFV,CAAC;IACA,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmFV,CAAC;IACA,CAAC;IAED;;OAEG;IACK,eAAe;QACrB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2GV,CAAC;IACA,CAAC;IAED;;OAEG;IACK,yBAAyB;QAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmXV,CAAC;IACA,CAAC;CACF;AA3uCD,sCA2uCC"}