pk: createDelimitedAttribute()

in source/packages/services/device-patcher/src/patch/patch.dao.ts [115:347]


                        pk: createDelimitedAttribute(PkType.PatchTask, patch.taskId),
                        sk: createDelimitedAttribute(PkType.DevicePatch, patch.patchId),
                        si1Sort: createDelimitedAttribute(PkType.PatchTask, patch.taskId),
                        si2Hash: createDelimitedAttribute(PkType.Device, patch.deviceId),
                    },
                },
            };

            requestItems.push(patchTaskRecord);
        }

        // batch write the items to ddb
        // build the request and write to DynamoDB
        const params: AWS.DynamoDB.DocumentClient.BatchWriteItemInput = {
            RequestItems: {},
        };
        params.RequestItems[this.tableName] = requestItems;

        const result = await this.dynamoDbUtils.batchWriteAll(params);
        if (this.dynamoDbUtils.hasUnprocessedItems(result)) {
            throw new Error('SAVE_FAILED');
        }
    }

    public async getBulk(
        patchItems: { patchId: string; deviceId: string }[]
    ): Promise<PatchItem[]> {
        logger.debug(`patch.dao: list: in: patchItems: ${patchItems}`);

        const params = {
            RequestItems: {},
        };

        params.RequestItems[this.tableName] = { Keys: [] };

        for (const item of patchItems) {
            params.RequestItems[this.tableName].Keys.push({
                pk: createDelimitedAttribute(PkType.DevicePatch, item.patchId),
                sk: createDelimitedAttribute(PkType.Device, item.deviceId),
            });

            params.RequestItems[this.tableName].Keys.push({
                pk: createDelimitedAttribute(PkType.DevicePatch, item.patchId),
                sk: createDelimitedAttribute(
                    PkType.DevicePatch,
                    PkType.DevicePatchAssociation,
                    'map'
                ),
            });
        }

        const result = await this.dynamoDbUtils.batchGetAll(params);
        if (
            result.Responses[this.tableName] === undefined ||
            result.Responses[this.tableName].length === 0
        ) {
            logger.debug('patches.dao list: exit: undefined');
            return undefined;
        }

        const patches = this.assemblePatch(result.Responses[this.tableName]);

        logger.debug(`patch.dao: list: exit: patchList: ${JSON.stringify(patches)}`);

        return patches;
    }

    public async get(patchId: string): Promise<PatchItem> {
        logger.debug(`patch.dao: list: in: patchId: ${patchId}`);

        const params = {
            TableName: this.tableName,
            KeyConditionExpression: `#pk=:pk`,
            ExpressionAttributeNames: {
                '#pk': 'pk',
            },
            ExpressionAttributeValues: {
                ':pk': createDelimitedAttribute(PkType.DevicePatch, patchId),
            },
        };

        const result = await this.dc.query(params).promise();
        if (result.Items === undefined || result.Items.length === 0) {
            logger.debug('patches.dao list: exit: undefined');
            return undefined;
        }

        const patches = this.assemblePatch(result.Items);

        logger.debug(`patch.dao: list: exit: patchList: ${JSON.stringify(patches)}`);

        return patches[0];
    }

    public async list(
        deviceId: string,
        status?: string,
        count?: number,
        exclusiveStart?: PatchListPaginationKey
    ): Promise<[PatchItem[], PatchListPaginationKey]> {
        logger.debug(`patch.dao: list: in: patch: ${JSON.stringify(deviceId)}`);

        let exclusiveStartKey: DynamoDbPaginationKey;
        if (exclusiveStart?.nextToken) {
            const decoded = atob(`${exclusiveStart?.nextToken}`);
            exclusiveStartKey = JSON.parse(decoded);
        }

        const params = {
            TableName: this.tableName,
            IndexName: this.SI1_INDEX,
            KeyConditionExpression: `#pk=:pk AND begins_with(#sk,:sk)`,
            ExpressionAttributeNames: {
                '#pk': 'sk',
                '#sk': 'si1Sort',
            },
            ExpressionAttributeValues: {
                ':pk': createDelimitedAttribute(PkType.Device, deviceId),
                ':sk': createDelimitedAttribute(PkType.DevicePatch),
            },
            ExclusiveStartKey: exclusiveStartKey,
            Limit: count,
        };

        if (status) {
            params.ExpressionAttributeValues[':sk'] = createDelimitedAttribute(
                PkType.Device,
                status
            );
        }

        const results = await this.dc.query(params).promise();
        if ((results?.Items?.length ?? 0) === 0) {
            logger.debug(`patchTask.dao:getPatchs exit: undefined`);
            return [undefined, undefined];
        }

        const patchItemKeys = results.Items.map((item) => {
            return {
                patchId: expandDelimitedAttribute(item.pk)[1],
                deviceId: expandDelimitedAttribute(item.sk)[1],
            };
        });

        const patches = await this.getBulk(patchItemKeys);

        let paginationKey: PatchListPaginationKey;
        if (results.LastEvaluatedKey) {
            const nextToken = btoa(`${JSON.stringify(results.LastEvaluatedKey)}`);
            paginationKey = {
                nextToken,
            };
        }

        logger.debug(`patch.dao: list: exit: patchList: ${JSON.stringify(patches)}`);

        return [patches, paginationKey];
    }

    public async update(patch: PatchItem): Promise<void> {
        logger.debug(`patch.dao: update: in: patch: ${JSON.stringify(patch)}`);

        let date = new Date().toISOString();

        if (patch.updatedAt && patch.updatedAt instanceof Date) {
            logger.silly(
                `patch.dao: update: using updated at from payload, updatedAt: ${patch.updatedAt}`
            );
            date = patch.updatedAt.toISOString();
        }

        const params = {
            TableName: this.tableName,
            Key: {
                pk: createDelimitedAttribute(PkType.DevicePatch, patch.patchId),
                sk: createDelimitedAttribute(PkType.Device, patch.deviceId),
            },
            UpdateExpression:
                'set patchStatus = :s, statusMessage = :m, updatedAt = :u, patchTemplateName = :t, si1Sort = :si1Sort',
            ExpressionAttributeValues: {
                ':s': patch.patchStatus,
                ':m': patch.statusMessage || null,
                ':u': date,
                ':t': patch.patchTemplateName,
                ':si1Sort': createDelimitedAttribute(
                    PkType.DevicePatch,
                    patch.patchStatus,
                    patch.patchId
                ),
            },
        };

        const result = await this.dc.update(params).promise();

        logger.debug(`patch.dao: save: exit: result: ${JSON.stringify(result)}`);
    }

    public async delete(patchId: string): Promise<void> {
        logger.debug(`patch.dao: delete: in: patch: ${JSON.stringify(patchId)}`);

        // retrieve all records associated with the template
        const queryParams: AWS.DynamoDB.DocumentClient.QueryInput = {
            TableName: this.tableName,
            KeyConditionExpression: `#hash = :hash`,
            ExpressionAttributeNames: { '#hash': 'pk' },
            ExpressionAttributeValues: {
                ':hash': createDelimitedAttribute(PkType.DevicePatch, patchId),
            },
        };

        let queryResults;
        try {
            queryResults = await this.dc.query(queryParams).promise();
        } catch (err) {
            logger.error(
                `patch.dao: delete: query: params: ${JSON.stringify(
                    queryParams
                )}, error: ${JSON.stringify(err)}`
            );
            throw err;
        }
        if (queryResults?.Items === undefined || queryResults?.Items.length === 0) {
            logger.debug('patches.dao delete: exit: nothing to delete');
            return;
        }

        // batch delete
        const batchParams: AWS.DynamoDB.DocumentClient.BatchWriteItemInput = { RequestItems: {} };
        batchParams.RequestItems[this.tableName] = [];
        queryResults.Items.forEach((i) => {
            const req: AWS.DynamoDB.DocumentClient.WriteRequest = {
                DeleteRequest: {
                    Key: {