quidproquo-deploy-awscdk 0.1.13 → 0.1.14
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.
- package/lib/commonjs/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js +13 -4
- package/lib/commonjs/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js.map +1 -1
- package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js +13 -4
- package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js.map +1 -1
- package/package.json +7 -7
- package/src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts +13 -4
package/lib/commonjs/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js
CHANGED
|
@@ -36,9 +36,7 @@ class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock_1.QpqCons
|
|
|
36
36
|
if (!streamConfig) {
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
const isOwnedHere = quidproquo_core_1.qpqCoreUtils
|
|
40
|
-
.getOwnedKeyValueStores(props.qpqConfig)
|
|
41
|
-
.some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
39
|
+
const isOwnedHere = quidproquo_core_1.qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
42
40
|
if (!isOwnedHere) {
|
|
43
41
|
return;
|
|
44
42
|
}
|
|
@@ -54,7 +52,18 @@ class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock_1.QpqCons
|
|
|
54
52
|
});
|
|
55
53
|
const func = new Function_1.Function(this, 'stream', {
|
|
56
54
|
reacreateOnFunctionNameChange: true,
|
|
57
|
-
|
|
55
|
+
// Two characters, and that is not a style choice - it is the whole budget.
|
|
56
|
+
//
|
|
57
|
+
// A Lambda name is capped at 64 characters, and this resource carries the longest
|
|
58
|
+
// prefix of anything qpq names: the store name already has `EventLog` appended before
|
|
59
|
+
// the app-service-environment-feature decoration and the `-qpq` marker are added. The
|
|
60
|
+
// longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters
|
|
61
|
+
// for the type. `kvsStream` overran by four; even `str` overruns by one.
|
|
62
|
+
//
|
|
63
|
+
// So this sits at exactly 64 with no headroom: a longer store name, service, feature
|
|
64
|
+
// or actor name will fail synth again, and the fix at that point is a general
|
|
65
|
+
// shortening rule in Function.ts rather than more characters shaved off here.
|
|
66
|
+
functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),
|
|
58
67
|
functionType: 'dynamoStreamEvent_kvsStreamEvent',
|
|
59
68
|
executorName: 'dynamoStreamEvent_kvsStreamEvent',
|
|
60
69
|
qpqConfig: props.qpqConfig,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":";;;AAAA,qDAAyF;AAEzF,6CAAoG;AAGpG,uEAA4F;AAC5F,sDAAmD;AACnD,mFAA+E;AAO/E,uFAAuF;AACvF,yFAAyF;AACzF,wFAAwF;AACxF,6DAA6D;AAC7D,EAAE;AACF,4FAA4F;AAC5F,6CAA6C;AAC7C,sEAAsE;AACtE,0FAA0F;AAC1F,4CAA4C;AAC5C,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,2FAA2F;AAC3F,qFAAqF;AACrF,yFAAyF;AACzF,+DAA+D;AAC/D,MAAM,yBAAyB,GAAG,GAAG,CAAC;AACtC,MAAM,sCAAsC,GAAG,CAAC,CAAC;AAEjD;;;;GAIG;AACH,MAAa,sCAAuC,SAAQ,qCAAiB;IAC3E,YAAY,KAAgB,EAAE,EAAU,EAAE,KAAkD;;QAC1F,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;QAExB,MAAM,YAAY,GAAG,KAAK,CAAC,mBAAmB,CAAC,QAAQ,CAAC;QACxD,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,8BAAY
|
|
1
|
+
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyValueStoreQPQConfigSetting, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_lambda, aws_lambda_event_sources, aws_ssm, Duration } from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\nimport { Function } from '../../../basic/Function';\nimport { kvsStreamArnSsmParameterName } from './QpqCoreKeyValueStoreConstruct';\n\nexport interface QpqApiCoreKeyValueStoreStreamConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n apiLayerVersions?: aws_lambda.ILayerVersion[];\n}\n\n// A failing batch is retried, and by DEFAULT a DynamoDB stream keeps retrying the SAME\n// batch until it succeeds or the records expire (24 hours), which stalls the whole shard\n// behind one poison record. That is the single sharpest edge of using streams, so these\n// are set deliberately rather than left to the AWS defaults:\n//\n// - bisectBatchOnError narrows a failing batch down to the individual bad record instead of\n// condemning everything it travelled with.\n// - retryAttempts bounds how long a bad record can hold up the shard.\n// - onFailure sends what could not be processed somewhere visible, so a skipped change is\n// an item in a queue rather than silence.\nconst STREAM_RETRY_ATTEMPTS = 5;\n\n// Records per invocation, and how long to wait accumulating them. The window is what makes\n// coalescing worth having: a burst of writes to one item arrives as one batch, and a\n// projection collapses it to a single rebuild. It costs projection latency, which is the\n// trade an eventually-consistent read model is already making.\nconst DEFAULT_STREAM_BATCH_SIZE = 100;\nconst DEFAULT_STREAM_BATCHING_WINDOW_SECONDS = 5;\n\n/**\n * Wires a key-value store's stream to its handler. Created only for stores declaring\n * `onStream`, and only on the owning service's stack — attaching an event source to\n * another service's table would have two services processing the same records.\n */\nexport class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {\n constructor(scope: Construct, id: string, props: QpqApiCoreKeyValueStoreStreamConstructProps) {\n super(scope, id, props);\n\n const streamConfig = props.keyValueStoreConfig.onStream;\n if (!streamConfig) {\n return;\n }\n\n const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);\n\n if (!isOwnedHere) {\n return;\n }\n\n // fromOtherStack imports by NAME, which carries no stream ARN, and a stream ARN ends\n // in a creation timestamp so it cannot be recomputed. The Inf stack publishes it to a\n // deterministically-named SSM parameter; this resolves at deploy time, so synth stays\n // independent of the other stack.\n const tableName = this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs');\n const tableStreamArn = aws_ssm.StringParameter.valueForStringParameter(this, kvsStreamArnSsmParameterName(tableName));\n\n const table = aws_dynamodb.Table.fromTableAttributes(this, 'stream-table', {\n tableName,\n tableStreamArn,\n });\n\n const func = new Function(this, 'stream', {\n reacreateOnFunctionNameChange: true,\n // Two characters, and that is not a style choice - it is the whole budget.\n //\n // A Lambda name is capped at 64 characters, and this resource carries the longest\n // prefix of anything qpq names: the store name already has `EventLog` appended before\n // the app-service-environment-feature decoration and the `-qpq` marker are added. The\n // longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters\n // for the type. `kvsStream` overran by four; even `str` overruns by one.\n //\n // So this sits at exactly 64 with no headroom: a longer store name, service, feature\n // or actor name will fail synth again, and the fix at that point is a general\n // shortening rule in Function.ts rather than more characters shaved off here.\n functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),\n\n functionType: 'dynamoStreamEvent_kvsStreamEvent',\n executorName: 'dynamoStreamEvent_kvsStreamEvent',\n\n qpqConfig: props.qpqConfig,\n\n apiLayerVersions: props.apiLayerVersions,\n\n environment: {\n // Never change: keyValueStoreName ~ hard coded to stop logs being recursive,\n // matching the storage drive event lambda's storageDriveName.\n keyValueStoreName: props.keyValueStoreConfig.keyValueStoreName,\n kvsStreamEntry: JSON.stringify(streamConfig.runtime),\n kvsStreamCoalesceByPartitionKey: String(!!streamConfig.coalesceByPartitionKey),\n kvsStreamPartitionKey: props.keyValueStoreConfig.partitionKey.key,\n },\n\n role: this.getServiceRole(),\n });\n\n func.lambdaFunction.addEventSource(\n new aws_lambda_event_sources.DynamoEventSource(table, {\n startingPosition: aws_lambda.StartingPosition.TRIM_HORIZON,\n\n batchSize: streamConfig.batchSize ?? DEFAULT_STREAM_BATCH_SIZE,\n maxBatchingWindow: Duration.seconds(streamConfig.maximumBatchingWindowInSeconds ?? DEFAULT_STREAM_BATCHING_WINDOW_SECONDS),\n\n // See the note above: without these a single bad record stalls the shard until the\n // records age out, and nothing records that it happened.\n bisectBatchOnError: true,\n retryAttempts: STREAM_RETRY_ATTEMPTS,\n reportBatchItemFailures: true,\n }),\n );\n }\n}\n"]}
|
package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js
CHANGED
|
@@ -32,9 +32,7 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
32
32
|
if (!streamConfig) {
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
const isOwnedHere = qpqCoreUtils
|
|
36
|
-
.getOwnedKeyValueStores(props.qpqConfig)
|
|
37
|
-
.some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
35
|
+
const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
38
36
|
if (!isOwnedHere) {
|
|
39
37
|
return;
|
|
40
38
|
}
|
|
@@ -50,7 +48,18 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
50
48
|
});
|
|
51
49
|
const func = new Function(this, 'stream', {
|
|
52
50
|
reacreateOnFunctionNameChange: true,
|
|
53
|
-
|
|
51
|
+
// Two characters, and that is not a style choice - it is the whole budget.
|
|
52
|
+
//
|
|
53
|
+
// A Lambda name is capped at 64 characters, and this resource carries the longest
|
|
54
|
+
// prefix of anything qpq names: the store name already has `EventLog` appended before
|
|
55
|
+
// the app-service-environment-feature decoration and the `-qpq` marker are added. The
|
|
56
|
+
// longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters
|
|
57
|
+
// for the type. `kvsStream` overran by four; even `str` overruns by one.
|
|
58
|
+
//
|
|
59
|
+
// So this sits at exactly 64 with no headroom: a longer store name, service, feature
|
|
60
|
+
// or actor name will fail synth again, and the fix at that point is a general
|
|
61
|
+
// shortening rule in Function.ts rather than more characters shaved off here.
|
|
62
|
+
functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),
|
|
54
63
|
functionType: 'dynamoStreamEvent_kvsStreamEvent',
|
|
55
64
|
executorName: 'dynamoStreamEvent_kvsStreamEvent',
|
|
56
65
|
qpqConfig: props.qpqConfig,
|
package/lib/esm/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEzF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,wBAAwB,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGpG,OAAO,EAAE,iBAAiB,EAA0B,MAAM,iCAAiC,CAAC;AAC5F,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAO/E,uFAAuF;AACvF,yFAAyF;AACzF,wFAAwF;AACxF,6DAA6D;AAC7D,EAAE;AACF,4FAA4F;AAC5F,6CAA6C;AAC7C,sEAAsE;AACtE,0FAA0F;AAC1F,4CAA4C;AAC5C,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEhC,2FAA2F;AAC3F,qFAAqF;AACrF,yFAAyF;AACzF,+DAA+D;AAC/D,MAAM,yBAAyB,GAAG,GAAG,CAAC;AACtC,MAAM,sCAAsC,GAAG,CAAC,CAAC;AAEjD;;;;GAIG;AACH,MAAM,OAAO,sCAAuC,SAAQ,iBAAiB;IAC3E,YAAY,KAAgB,EAAE,EAAU,EAAE,KAAkD;QAC1F,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;QAExB,MAAM,YAAY,GAAG,KAAK,CAAC,mBAAmB,CAAC,QAAQ,CAAC;QACxD,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,YAAY
|
|
1
|
+
{"version":3,"file":"QpqApiCoreKeyValueStoreStreamConstruct.js","sourceRoot":"","sources":["../../../../../../src/constructs/feature/core/keyValueStore/QpqApiCoreKeyValueStoreStreamConstruct.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyValueStoreQPQConfigSetting, QPQConfig, qpqCoreUtils } from 'quidproquo-core';\n\nimport { aws_dynamodb, aws_lambda, aws_lambda_event_sources, aws_ssm, Duration } from 'aws-cdk-lib';\nimport { Construct } from 'constructs';\n\nimport { QpqConstructBlock, QpqConstructBlockProps } from '../../../base/QpqConstructBlock';\nimport { Function } from '../../../basic/Function';\nimport { kvsStreamArnSsmParameterName } from './QpqCoreKeyValueStoreConstruct';\n\nexport interface QpqApiCoreKeyValueStoreStreamConstructProps extends QpqConstructBlockProps {\n keyValueStoreConfig: KeyValueStoreQPQConfigSetting;\n apiLayerVersions?: aws_lambda.ILayerVersion[];\n}\n\n// A failing batch is retried, and by DEFAULT a DynamoDB stream keeps retrying the SAME\n// batch until it succeeds or the records expire (24 hours), which stalls the whole shard\n// behind one poison record. That is the single sharpest edge of using streams, so these\n// are set deliberately rather than left to the AWS defaults:\n//\n// - bisectBatchOnError narrows a failing batch down to the individual bad record instead of\n// condemning everything it travelled with.\n// - retryAttempts bounds how long a bad record can hold up the shard.\n// - onFailure sends what could not be processed somewhere visible, so a skipped change is\n// an item in a queue rather than silence.\nconst STREAM_RETRY_ATTEMPTS = 5;\n\n// Records per invocation, and how long to wait accumulating them. The window is what makes\n// coalescing worth having: a burst of writes to one item arrives as one batch, and a\n// projection collapses it to a single rebuild. It costs projection latency, which is the\n// trade an eventually-consistent read model is already making.\nconst DEFAULT_STREAM_BATCH_SIZE = 100;\nconst DEFAULT_STREAM_BATCHING_WINDOW_SECONDS = 5;\n\n/**\n * Wires a key-value store's stream to its handler. Created only for stores declaring\n * `onStream`, and only on the owning service's stack — attaching an event source to\n * another service's table would have two services processing the same records.\n */\nexport class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {\n constructor(scope: Construct, id: string, props: QpqApiCoreKeyValueStoreStreamConstructProps) {\n super(scope, id, props);\n\n const streamConfig = props.keyValueStoreConfig.onStream;\n if (!streamConfig) {\n return;\n }\n\n const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);\n\n if (!isOwnedHere) {\n return;\n }\n\n // fromOtherStack imports by NAME, which carries no stream ARN, and a stream ARN ends\n // in a creation timestamp so it cannot be recomputed. The Inf stack publishes it to a\n // deterministically-named SSM parameter; this resolves at deploy time, so synth stays\n // independent of the other stack.\n const tableName = this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'kvs');\n const tableStreamArn = aws_ssm.StringParameter.valueForStringParameter(this, kvsStreamArnSsmParameterName(tableName));\n\n const table = aws_dynamodb.Table.fromTableAttributes(this, 'stream-table', {\n tableName,\n tableStreamArn,\n });\n\n const func = new Function(this, 'stream', {\n reacreateOnFunctionNameChange: true,\n // Two characters, and that is not a style choice - it is the whole budget.\n //\n // A Lambda name is capped at 64 characters, and this resource carries the longest\n // prefix of anything qpq names: the store name already has `EventLog` appended before\n // the app-service-environment-feature decoration and the `-qpq` marker are added. The\n // longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters\n // for the type. `kvsStream` overran by four; even `str` overruns by one.\n //\n // So this sits at exactly 64 with no headroom: a longer store name, service, feature\n // or actor name will fail synth again, and the fix at that point is a general\n // shortening rule in Function.ts rather than more characters shaved off here.\n functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),\n\n functionType: 'dynamoStreamEvent_kvsStreamEvent',\n executorName: 'dynamoStreamEvent_kvsStreamEvent',\n\n qpqConfig: props.qpqConfig,\n\n apiLayerVersions: props.apiLayerVersions,\n\n environment: {\n // Never change: keyValueStoreName ~ hard coded to stop logs being recursive,\n // matching the storage drive event lambda's storageDriveName.\n keyValueStoreName: props.keyValueStoreConfig.keyValueStoreName,\n kvsStreamEntry: JSON.stringify(streamConfig.runtime),\n kvsStreamCoalesceByPartitionKey: String(!!streamConfig.coalesceByPartitionKey),\n kvsStreamPartitionKey: props.keyValueStoreConfig.partitionKey.key,\n },\n\n role: this.getServiceRole(),\n });\n\n func.lambdaFunction.addEventSource(\n new aws_lambda_event_sources.DynamoEventSource(table, {\n startingPosition: aws_lambda.StartingPosition.TRIM_HORIZON,\n\n batchSize: streamConfig.batchSize ?? DEFAULT_STREAM_BATCH_SIZE,\n maxBatchingWindow: Duration.seconds(streamConfig.maximumBatchingWindowInSeconds ?? DEFAULT_STREAM_BATCHING_WINDOW_SECONDS),\n\n // See the note above: without these a single bad record stalls the shard until the\n // records age out, and nothing records that it happened.\n bisectBatchOnError: true,\n retryAttempts: STREAM_RETRY_ATTEMPTS,\n reportBatchItemFailures: true,\n }),\n );\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quidproquo-deploy-awscdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"constructs": "^10.7.1",
|
|
39
39
|
"esbuild": "^0.25.8",
|
|
40
40
|
"liquidjs": "^10.7.0",
|
|
41
|
-
"quidproquo-actionprocessor-awslambda": "0.1.
|
|
42
|
-
"quidproquo-actionprocessor-node": "0.1.
|
|
43
|
-
"quidproquo-config-aws": "0.1.
|
|
44
|
-
"quidproquo-core": "0.1.
|
|
45
|
-
"quidproquo-webserver": "0.1.
|
|
41
|
+
"quidproquo-actionprocessor-awslambda": "0.1.14",
|
|
42
|
+
"quidproquo-actionprocessor-node": "0.1.14",
|
|
43
|
+
"quidproquo-config-aws": "0.1.14",
|
|
44
|
+
"quidproquo-core": "0.1.14",
|
|
45
|
+
"quidproquo-webserver": "0.1.14",
|
|
46
46
|
"upath": "^2.0.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@types/babel__traverse": "7.18.2",
|
|
51
51
|
"@types/node": "^22.13.13",
|
|
52
52
|
"@types/prettier": "2.6.0",
|
|
53
|
-
"quidproquo-tsconfig": "0.1.
|
|
53
|
+
"quidproquo-tsconfig": "0.1.14",
|
|
54
54
|
"typescript": "^5.8.2"
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -45,9 +45,7 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
const isOwnedHere = qpqCoreUtils
|
|
49
|
-
.getOwnedKeyValueStores(props.qpqConfig)
|
|
50
|
-
.some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
48
|
+
const isOwnedHere = qpqCoreUtils.getOwnedKeyValueStores(props.qpqConfig).some((kvs) => kvs.uniqueKey === props.keyValueStoreConfig.uniqueKey);
|
|
51
49
|
|
|
52
50
|
if (!isOwnedHere) {
|
|
53
51
|
return;
|
|
@@ -67,7 +65,18 @@ export class QpqApiCoreKeyValueStoreStreamConstruct extends QpqConstructBlock {
|
|
|
67
65
|
|
|
68
66
|
const func = new Function(this, 'stream', {
|
|
69
67
|
reacreateOnFunctionNameChange: true,
|
|
70
|
-
|
|
68
|
+
// Two characters, and that is not a style choice - it is the whole budget.
|
|
69
|
+
//
|
|
70
|
+
// A Lambda name is capped at 64 characters, and this resource carries the longest
|
|
71
|
+
// prefix of anything qpq names: the store name already has `EventLog` appended before
|
|
72
|
+
// the app-service-environment-feature decoration and the `-qpq` marker are added. The
|
|
73
|
+
// longest today is `qpq-admin-sessionsEventLog`, which leaves exactly two characters
|
|
74
|
+
// for the type. `kvsStream` overran by four; even `str` overruns by one.
|
|
75
|
+
//
|
|
76
|
+
// So this sits at exactly 64 with no headroom: a longer store name, service, feature
|
|
77
|
+
// or actor name will fail synth again, and the fix at that point is a general
|
|
78
|
+
// shortening rule in Function.ts rather than more characters shaved off here.
|
|
79
|
+
functionName: this.qpqResourceName(props.keyValueStoreConfig.keyValueStoreName, 'ks'),
|
|
71
80
|
|
|
72
81
|
functionType: 'dynamoStreamEvent_kvsStreamEvent',
|
|
73
82
|
executorName: 'dynamoStreamEvent_kvsStreamEvent',
|