@aws-blocks/core 0.2.0 → 0.3.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 (123) hide show
  1. package/dist/cdk/blocks-backend.d.ts +35 -9
  2. package/dist/cdk/blocks-backend.d.ts.map +1 -1
  3. package/dist/cdk/blocks-backend.js +52 -69
  4. package/dist/cdk/blocks-backend.test.js +61 -69
  5. package/dist/cdk/blocks-defaults.d.ts +27 -1
  6. package/dist/cdk/blocks-defaults.d.ts.map +1 -1
  7. package/dist/cdk/blocks-defaults.js +5 -1
  8. package/dist/cdk/blocks-defaults.test.js +4 -2
  9. package/dist/cdk/blocks-stack.test.js +73 -37
  10. package/dist/cdk/compute/default-compute-factory.d.ts +33 -0
  11. package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -0
  12. package/dist/cdk/compute/default-compute-factory.js +3 -0
  13. package/dist/cdk/index.d.ts +46 -5
  14. package/dist/cdk/index.d.ts.map +1 -1
  15. package/dist/cdk/index.js +57 -7
  16. package/dist/cdk/internal.d.ts +1 -0
  17. package/dist/cdk/internal.d.ts.map +1 -1
  18. package/dist/hosting-secrets.d.ts +48 -0
  19. package/dist/hosting-secrets.d.ts.map +1 -0
  20. package/dist/hosting-secrets.js +57 -0
  21. package/dist/hosting-secrets.test.d.ts +2 -0
  22. package/dist/hosting-secrets.test.d.ts.map +1 -0
  23. package/dist/hosting-secrets.test.js +81 -0
  24. package/dist/hosting.d.ts +88 -6
  25. package/dist/hosting.d.ts.map +1 -1
  26. package/dist/hosting.js +83 -24
  27. package/dist/hosting.test.js +158 -10
  28. package/dist/index.cdk.d.ts +13 -12
  29. package/dist/index.cdk.d.ts.map +1 -1
  30. package/dist/index.cdk.js +13 -9
  31. package/dist/index.d.ts +8 -7
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +9 -5
  34. package/dist/lambda-handler.test.js +66 -0
  35. package/dist/pipeline/index.d.ts +24 -2
  36. package/dist/pipeline/index.d.ts.map +1 -1
  37. package/dist/pipeline/index.js +41 -1
  38. package/dist/pipeline.test.d.ts +2 -0
  39. package/dist/pipeline.test.d.ts.map +1 -0
  40. package/dist/pipeline.test.js +47 -0
  41. package/dist/rpc.d.ts +21 -0
  42. package/dist/rpc.d.ts.map +1 -1
  43. package/dist/rpc.js +34 -0
  44. package/dist/rpc.test.js +38 -1
  45. package/dist/scripts/config.d.ts +15 -0
  46. package/dist/scripts/config.d.ts.map +1 -0
  47. package/dist/scripts/config.js +29 -0
  48. package/dist/scripts/deploy.d.ts.map +1 -1
  49. package/dist/scripts/deploy.js +4 -0
  50. package/dist/scripts/dev-server.d.ts +8 -0
  51. package/dist/scripts/dev-server.d.ts.map +1 -1
  52. package/dist/scripts/dev-server.js +31 -0
  53. package/dist/scripts/generate-client.d.ts.map +1 -1
  54. package/dist/scripts/generate-client.js +9 -4
  55. package/dist/scripts/generate-client.test.js +19 -4
  56. package/dist/scripts/index.d.ts +13 -10
  57. package/dist/scripts/index.d.ts.map +1 -1
  58. package/dist/scripts/index.js +11 -8
  59. package/dist/scripts/preflight-credentials.d.ts +30 -0
  60. package/dist/scripts/preflight-credentials.d.ts.map +1 -0
  61. package/dist/scripts/preflight-credentials.js +108 -0
  62. package/dist/scripts/preflight-credentials.test.d.ts +2 -0
  63. package/dist/scripts/preflight-credentials.test.d.ts.map +1 -0
  64. package/dist/scripts/preflight-credentials.test.js +77 -0
  65. package/dist/scripts/sandbox-args.test.d.ts +2 -0
  66. package/dist/scripts/sandbox-args.test.d.ts.map +1 -0
  67. package/dist/scripts/sandbox-args.test.js +77 -0
  68. package/dist/scripts/sandbox.d.ts +52 -0
  69. package/dist/scripts/sandbox.d.ts.map +1 -1
  70. package/dist/scripts/sandbox.js +92 -15
  71. package/dist/scripts/secret.d.ts +15 -0
  72. package/dist/scripts/secret.d.ts.map +1 -0
  73. package/dist/scripts/secret.js +29 -0
  74. package/dist/scripts/secret.test.d.ts +2 -0
  75. package/dist/scripts/secret.test.d.ts.map +1 -0
  76. package/dist/scripts/secret.test.js +28 -0
  77. package/dist/scripts/typegen.d.ts +15 -0
  78. package/dist/scripts/typegen.d.ts.map +1 -0
  79. package/dist/scripts/typegen.js +16 -0
  80. package/dist/secret-naming.d.ts +58 -0
  81. package/dist/secret-naming.d.ts.map +1 -0
  82. package/dist/secret-naming.js +67 -0
  83. package/dist/secret-naming.test.d.ts +2 -0
  84. package/dist/secret-naming.test.d.ts.map +1 -0
  85. package/dist/secret-naming.test.js +40 -0
  86. package/dist/version.d.ts +1 -1
  87. package/dist/version.js +1 -1
  88. package/package.json +2 -1
  89. package/src/cdk/blocks-backend.test.ts +267 -272
  90. package/src/cdk/blocks-backend.ts +71 -79
  91. package/src/cdk/blocks-defaults.test.ts +4 -2
  92. package/src/cdk/blocks-defaults.ts +27 -1
  93. package/src/cdk/blocks-stack.test.ts +161 -118
  94. package/src/cdk/compute/default-compute-factory.ts +37 -0
  95. package/src/cdk/index.ts +84 -8
  96. package/src/cdk/internal.ts +1 -0
  97. package/src/hosting-secrets.test.ts +106 -0
  98. package/src/hosting-secrets.ts +106 -0
  99. package/src/hosting.test.ts +244 -63
  100. package/src/hosting.ts +180 -68
  101. package/src/index.cdk.ts +77 -38
  102. package/src/index.ts +35 -16
  103. package/src/lambda-handler.test.ts +74 -0
  104. package/src/pipeline/index.ts +64 -11
  105. package/src/pipeline.test.ts +55 -0
  106. package/src/rpc.test.ts +44 -1
  107. package/src/rpc.ts +41 -0
  108. package/src/scripts/config.ts +35 -0
  109. package/src/scripts/deploy.ts +5 -0
  110. package/src/scripts/dev-server.ts +40 -0
  111. package/src/scripts/generate-client.test.ts +43 -8
  112. package/src/scripts/generate-client.ts +70 -65
  113. package/src/scripts/index.ts +20 -17
  114. package/src/scripts/preflight-credentials.test.ts +110 -0
  115. package/src/scripts/preflight-credentials.ts +131 -0
  116. package/src/scripts/sandbox-args.test.ts +92 -0
  117. package/src/scripts/sandbox.ts +102 -15
  118. package/src/scripts/secret.test.ts +35 -0
  119. package/src/scripts/secret.ts +35 -0
  120. package/src/scripts/typegen.ts +18 -0
  121. package/src/secret-naming.test.ts +59 -0
  122. package/src/secret-naming.ts +99 -0
  123. package/src/version.ts +1 -1
package/src/hosting.ts CHANGED
@@ -1,42 +1,38 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import * as cdk from 'aws-cdk-lib';
5
- import {
6
- AllowedMethods,
7
- CachePolicy,
8
- OriginRequestPolicy,
9
- ViewerProtocolPolicy,
10
- } from 'aws-cdk-lib/aws-cloudfront';
11
- import { HttpOrigin } from 'aws-cdk-lib/aws-cloudfront-origins';
12
- import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
13
- import { Construct } from 'constructs';
14
4
  import { execSync } from 'node:child_process';
15
5
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
16
6
  import { join, resolve } from 'node:path';
17
-
7
+ import type { DeployManifest, FrameworkType, KindStoreOptions, RouteBehavior } from '@aws-blocks/hosting/constructs';
8
+ import { detectFramework, type FrameworkAdapterFn, getAdapter, normalizeBasePath } from '@aws-blocks/hosting/adapters';
18
9
  import {
19
- HostingConstruct,
20
10
  generateBuildId,
11
+ HostingConstruct,
21
12
  type HostingConstructProps,
22
13
  type HostingDomainConfig,
23
14
  type HostingWafConfig,
24
15
  type SkewProtectionConfig,
25
16
  } from '@aws-blocks/hosting/constructs';
26
- import {
27
- detectFramework,
28
- getAdapter,
29
- normalizeBasePath,
30
- type FrameworkAdapterFn,
31
- } from '@aws-blocks/hosting/adapters';
32
- import type {
33
- DeployManifest,
34
- RouteBehavior,
35
- FrameworkType,
36
- } from '@aws-blocks/hosting';
37
- import { BLOCKS_RPC_PREFIX, BLOCKS_AUTH_PREFIX } from './constants.js';
38
- import { BLOCKS_SANDBOX_DIR } from './common/constants.js';
17
+ import * as cdk from 'aws-cdk-lib';
18
+ import { AllowedMethods, CachePolicy, OriginRequestPolicy, ViewerProtocolPolicy } from 'aws-cdk-lib/aws-cloudfront';
19
+ import { HttpOrigin } from 'aws-cdk-lib/aws-cloudfront-origins';
20
+ import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
21
+ import { Construct } from 'constructs';
39
22
  import { registerConfig } from './cdk/config-registry.js';
23
+ import { BLOCKS_SANDBOX_DIR } from './common/constants.js';
24
+ import { BLOCKS_AUTH_PREFIX, BLOCKS_RPC_PREFIX } from './constants.js';
25
+ import {
26
+ assertMarkersExistAtSynth,
27
+ collectSynthMarkers,
28
+ type DomainNameInput,
29
+ type EnvValue,
30
+ partitionEnvironment,
31
+ resolveDomainNames,
32
+ resolveSecretsAtSynth,
33
+ wireByo,
34
+ wireManagedValue,
35
+ } from './hosting-secrets.js';
40
36
  import { getRegisteredRoutes } from './raw-route.js';
41
37
 
42
38
  // ─── Public types ────────────────────────────────────────────────
@@ -102,6 +98,19 @@ export type ComputeConfig = {
102
98
 
103
99
  export type { FrameworkType, HostingDomainConfig, HostingWafConfig, SkewProtectionConfig };
104
100
 
101
+ /**
102
+ * Custom-domain config for the Blocks {@link Hosting} block — the leaf
103
+ * {@link HostingDomainConfig} with `domainName` widened to also accept a
104
+ * `config()` marker (resolved to a literal at synth). Only `config()` or a plain
105
+ * string is accepted here, never `secret()` (a synth-inlined value would leak into
106
+ * the template). All other fields (`hostedZone`, `certificate`, `wwwRedirect`, …)
107
+ * are inherited unchanged from {@link HostingDomainConfig}.
108
+ */
109
+ export interface HostingDomain extends Omit<HostingDomainConfig, 'domainName'> {
110
+ /** Domain name(s): a plain string / string[] and/or a `config()` marker resolved at synth. */
111
+ domainName: DomainNameInput;
112
+ }
113
+
105
114
  /**
106
115
  * Structural interface for the Blocks backend stack.
107
116
  *
@@ -199,8 +208,55 @@ export interface HostingProps {
199
208
  /** Lambda compute configuration for SSR frameworks. */
200
209
  compute?: ComputeConfig;
201
210
 
202
- /** Custom domain configuration. */
203
- domain?: HostingDomainConfig;
211
+ /**
212
+ * Custom environment variables injected into all compute Lambda functions.
213
+ *
214
+ * Values are either plain strings or {@link secret} references:
215
+ *
216
+ * ```ts
217
+ * environment: {
218
+ * FEATURE_FLAG: 'on', // plaintext (in CFN template + Lambda config)
219
+ * STRIPE_KEY: secret('STRIPE_KEY'), // encrypted; read at runtime via getSecret()
220
+ * }
221
+ * ```
222
+ *
223
+ * A plain string is injected verbatim (visible in the CloudFormation
224
+ * template — safe for non-sensitive literals). A `secret('K')` value (AWS
225
+ * Secrets Manager) or `config('K')` value (SSM Parameter Store) injects only
226
+ * the store LOCATOR — never the value — and grants the compute role scoped
227
+ * read + decrypt; fetch it at runtime with `await getSecret('K')` /
228
+ * `await getConfig('K')`. A bring-your-own CDK `ISecret` / `IParameter` handle
229
+ * is accepted too and resolves identically.
230
+ *
231
+ * The env-var name must equal the key: `STRIPE_KEY: secret('STRIPE_KEY')`.
232
+ */
233
+ environment?: Record<string, EnvValue>;
234
+
235
+ /**
236
+ * Custom domain configuration. `domainName` accepts plain strings and/or
237
+ * {@link config} references (e.g. `domain: { domainName: config('DOMAIN_PROD') }`).
238
+ * A domain is resolved at synth time and inlined as a literal into the template,
239
+ * so only `config()` (non-sensitive → SSM) is allowed here, never `secret()`
240
+ * (which would leak into the template). Any domain marker requires constructing
241
+ * via the async {@link Hosting.create}.
242
+ */
243
+ domain?: HostingDomain;
244
+
245
+ /**
246
+ * Namespace/cache config for `secret()` values (AWS Secrets Manager). Defaults
247
+ * to the Blocks `/blocks/secrets` prefix; set `prefix` to namespace per app,
248
+ * `stage` for per-stage values with a shared fallback, or `cacheTtlSeconds` to
249
+ * refresh a rotated value on a warm compute without a cold start. Any field the
250
+ * app sets overrides the Blocks default.
251
+ */
252
+ secretStore?: KindStoreOptions;
253
+
254
+ /**
255
+ * Namespace/cache config for `config()` values (SSM Parameter Store). Defaults
256
+ * to the Blocks `/blocks/config` prefix; same override semantics as
257
+ * {@link HostingProps.secretStore}.
258
+ */
259
+ configStore?: KindStoreOptions;
204
260
 
205
261
  /** WAF protection configuration. */
206
262
  waf?: HostingWafConfig;
@@ -399,7 +455,44 @@ export class Hosting extends Construct {
399
455
  /** SNS topic for hosting CloudWatch alarms (present when `monitoring.enabled` is true). */
400
456
  public readonly monitoringTopic?: cdk.aws_sns.ITopic;
401
457
 
402
- constructor(scope: Construct, id: string, props: HostingProps) {
458
+ /**
459
+ * Async constructor. Required when a `secret()` / `config()` value resolves at
460
+ * **synth time** — i.e. a `domain.domainName` marker — because that value is
461
+ * fetched from its store (Secrets Manager / SSM) during synthesis. Returns a
462
+ * fully-constructed {@link Hosting}.
463
+ *
464
+ * For runtime `environment` markers (read via `getSecret`/`getConfig`) and
465
+ * plain literals, `new Hosting(...)` works directly; `create()` is also safe
466
+ * to use uniformly.
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * await Hosting.create(stack, 'Web', {
471
+ * root: '.', framework: 'nextjs', api: blocksStack,
472
+ * domain: { domainName: config('DOMAIN_PROD') },
473
+ * });
474
+ * ```
475
+ */
476
+ static async create(scope: Construct, id: string, props: HostingProps): Promise<Hosting> {
477
+ const storeConfig = { secretStore: props.secretStore, configStore: props.configStore };
478
+ const synthMarkers = collectSynthMarkers(props.domain?.domainName);
479
+ const resolved = synthMarkers.length
480
+ ? await resolveSecretsAtSynth(synthMarkers, undefined, storeConfig)
481
+ : new Map<string, string>();
482
+ // Fail the deploy early if a declared `environment` secret/config has no value
483
+ // set — existence only, never fetching the value. (Domain markers above already
484
+ // fail this way; this extends it to runtime markers.)
485
+ await assertMarkersExistAtSynth(partitionEnvironment(props.environment).managed, storeConfig);
486
+ return new Hosting(scope, id, props, resolved);
487
+ }
488
+
489
+ /**
490
+ * @param resolvedSecrets - Synth-time-resolved secret values, keyed by secret
491
+ * key. Populated by {@link Hosting.create}; empty for the direct
492
+ * `new Hosting()` path (which then rejects any synth-time secret with a
493
+ * clear "use Hosting.create()" error).
494
+ */
495
+ constructor(scope: Construct, id: string, props: HostingProps, resolvedSecrets: Map<string, string> = new Map()) {
403
496
  super(scope, id);
404
497
 
405
498
  const root = resolve(props.root);
@@ -415,7 +508,7 @@ export class Hosting extends Construct {
415
508
  // from breaking frontend builds (e.g., Next.js webpack module resolution).
416
509
  const nodeOptions = (process.env.NODE_OPTIONS || '')
417
510
  .split(/\s+/)
418
- .filter(opt => opt !== '--conditions=cdk')
511
+ .filter((opt) => opt !== '--conditions=cdk')
419
512
  .join(' ');
420
513
  const buildEnv = { ...process.env, NODE_OPTIONS: nodeOptions };
421
514
  try {
@@ -430,7 +523,7 @@ export class Hosting extends Construct {
430
523
  } catch (error) {
431
524
  throw new Error(
432
525
  `Frontend build failed: ${props.buildCommand}\n` +
433
- `Ensure the build command is correct and all dependencies are installed.`,
526
+ `Ensure the build command is correct and all dependencies are installed.`,
434
527
  );
435
528
  }
436
529
  }
@@ -445,7 +538,7 @@ export class Hosting extends Construct {
445
538
  if (!existsSync(expectedOutputDir)) {
446
539
  throw new Error(
447
540
  `Build output directory not found: ${expectedOutputDir}\n` +
448
- `Provide a buildCommand or ensure the directory exists before synthesis.`,
541
+ `Provide a buildCommand or ensure the directory exists before synthesis.`,
449
542
  );
450
543
  }
451
544
 
@@ -494,8 +587,7 @@ export class Hosting extends Construct {
494
587
  // errorPages will drive the CloudFront configuration correctly,
495
588
  // and the HTML files are already in the static assets directory
496
589
  // (copied by the adapter).
497
- const manifestHasErrorPages = manifest.errorPages !== undefined &&
498
- Object.keys(manifest.errorPages).length > 0;
590
+ const manifestHasErrorPages = manifest.errorPages !== undefined && Object.keys(manifest.errorPages).length > 0;
499
591
  const skipPropsErrorPages = !!(props.errorPages && manifestHasErrorPages);
500
592
 
501
593
  // ── 5. Write placeholder config.json into static assets ─────────
@@ -506,10 +598,7 @@ export class Hosting extends Construct {
506
598
  const staticDir = manifest.staticAssets.directory;
507
599
  const blocksSandboxDir = join(staticDir, '.blocks-sandbox');
508
600
  mkdirSync(blocksSandboxDir, { recursive: true });
509
- writeFileSync(
510
- join(blocksSandboxDir, 'config.json'),
511
- JSON.stringify({ _placeholder: true }),
512
- );
601
+ writeFileSync(join(blocksSandboxDir, 'config.json'), JSON.stringify({ _placeholder: true }));
513
602
 
514
603
  // ── 5a. Mark config.json as a no-cache path ─────────────────────
515
604
  // config.json is a fixed-name, mutable runtime-config file: it must
@@ -532,7 +621,7 @@ export class Hosting extends Construct {
532
621
  target: 'static',
533
622
  };
534
623
  // Insert before any catch-all route
535
- const catchAllIdx = manifest.routes.findIndex((r) => r.pattern === '/*' || r.pattern === '/(.*)')
624
+ const catchAllIdx = manifest.routes.findIndex((r) => r.pattern === '/*' || r.pattern === '/(.*)');
536
625
  if (catchAllIdx >= 0) {
537
626
  manifest.routes.splice(catchAllIdx, 0, blocksConfigRoute);
538
627
  } else {
@@ -550,28 +639,48 @@ export class Hosting extends Construct {
550
639
  const normalizedCompute = props.compute
551
640
  ? {
552
641
  ...props.compute,
553
- timeout: typeof props.compute.timeout === 'number'
554
- ? cdk.Duration.seconds(props.compute.timeout)
555
- : props.compute.timeout,
642
+ timeout:
643
+ typeof props.compute.timeout === 'number'
644
+ ? cdk.Duration.seconds(props.compute.timeout)
645
+ : props.compute.timeout,
646
+ }
647
+ : undefined;
648
+
649
+ // ── 6b. Resolve secret() markers ─────────────────────────────
650
+ // Env values split into plain / runtime-secret / deploy-time buckets;
651
+ // domain names may contain markers resolved at synth time. See
652
+ // ./hosting-secrets.ts for the two-strategy rationale.
653
+ const { plain: plainEnv, managed, byo } = partitionEnvironment(props.environment);
654
+
655
+ // Resolved domain (markers → literals); falls back to the raw config when
656
+ // no domain is set. resolveDomainNames throws a "use Hosting.create()"
657
+ // error if a domain marker reached the sync path unresolved.
658
+ const resolvedDomain = props.domain
659
+ ? {
660
+ ...props.domain,
661
+ domainName: resolveDomainNames(props.domain.domainName, resolvedSecrets),
556
662
  }
557
663
  : undefined;
558
664
 
559
665
  const hostingProps: HostingConstructProps = {
560
666
  manifest,
561
667
  compute: normalizedCompute,
562
- domain: props.domain,
668
+ // Plain env + deploy-time literals go straight to the L3 (which stays
669
+ // secret-agnostic: it only ever sees plain strings). Runtime secrets are
670
+ // wired separately below so we can attach IAM grants per function.
671
+ environment: Object.keys(plainEnv).length ? plainEnv : undefined,
672
+ domain: resolvedDomain,
563
673
  waf: props.waf,
564
- storage: props.retainOnDelete != null
565
- ? { retainOnDelete: props.retainOnDelete }
566
- : undefined,
567
- cdn: (props.contentSecurityPolicy || props.priceClass || props.geoRestriction || props.quotas)
568
- ? {
569
- contentSecurityPolicy: props.contentSecurityPolicy,
570
- priceClass: props.priceClass,
571
- geoRestriction: props.geoRestriction,
572
- quotas: props.quotas,
573
- }
574
- : undefined,
674
+ storage: props.retainOnDelete != null ? { retainOnDelete: props.retainOnDelete } : undefined,
675
+ cdn:
676
+ props.contentSecurityPolicy || props.priceClass || props.geoRestriction || props.quotas
677
+ ? {
678
+ contentSecurityPolicy: props.contentSecurityPolicy,
679
+ priceClass: props.priceClass,
680
+ geoRestriction: props.geoRestriction,
681
+ quotas: props.quotas,
682
+ }
683
+ : undefined,
575
684
  logging: props.logging,
576
685
  buildCache: props.buildCache,
577
686
  errorPages: skipPropsErrorPages ? undefined : props.errorPages,
@@ -591,14 +700,10 @@ export class Hosting extends Construct {
591
700
  // variables — they surface in computeFunctions as EdgeFunction/IVersion
592
701
  // without an `addEnvironment` method. Skip any function that can't take
593
702
  // env vars instead of crashing (`fn.addEnvironment is not a function`).
594
- const canAddEnv = (
595
- fn: unknown,
596
- ): fn is cdk.aws_lambda.Function =>
703
+ const canAddEnv = (fn: unknown): fn is cdk.aws_lambda.Function =>
597
704
  typeof (fn as { addEnvironment?: unknown })?.addEnvironment === 'function';
598
705
 
599
- const primaryFunction = [...hosting.computeFunctions.values()].find(
600
- canAddEnv,
601
- );
706
+ const primaryFunction = [...hosting.computeFunctions.values()].find(canAddEnv);
602
707
 
603
708
  for (const [, fn] of hosting.computeFunctions) {
604
709
  if (!canAddEnv(fn)) continue; // Lambda@Edge: no env var support
@@ -608,15 +713,25 @@ export class Hosting extends Construct {
608
713
  if (props.backendConfig) {
609
714
  fn.addEnvironment('BLOCKS_CONFIG', JSON.stringify(props.backendConfig));
610
715
  }
716
+ // Runtime managed markers (secret|config): inject the store locator
717
+ // (never the value) + grant read/decrypt; read via getSecret()/getConfig().
718
+ // The app's secretStore/configStore (prefix, stage, cacheTtlSeconds) layer
719
+ // over the Blocks defaults.
720
+ const storeConfig = { secretStore: props.secretStore, configStore: props.configStore };
721
+ for (const marker of managed) {
722
+ wireManagedValue(fn, marker, storeConfig);
723
+ }
724
+ // BYO handles (ISecret|IParameter): grant via the handle + inject its locator.
725
+ for (const binding of byo) {
726
+ wireByo(fn, binding, storeConfig);
727
+ }
611
728
  }
612
729
 
613
730
  // ── 8. Deploy config.json with resolved CDK tokens ───────────
614
731
  const buildId = manifest.buildId;
615
732
  if (buildId) {
616
733
  const configDeployment = new s3deploy.BucketDeployment(this, 'BlocksConfigDeployment', {
617
- sources: [
618
- s3deploy.Source.jsonData('config.json', this.buildConfigJson(props)),
619
- ],
734
+ sources: [s3deploy.Source.jsonData('config.json', this.buildConfigJson(props))],
620
735
  destinationBucket: hosting.bucket,
621
736
  destinationKeyPrefix: `builds/${buildId}/.blocks-sandbox`,
622
737
  prune: false,
@@ -629,10 +744,7 @@ export class Hosting extends Construct {
629
744
  // primary guard against staleness is step 5a's no-cache placeholder;
630
745
  // this is defense-in-depth so a post-deploy invalidation actually
631
746
  // clears any edge entry at its real key.)
632
- distributionPaths: [
633
- `/builds/${buildId}/.blocks-sandbox/*`,
634
- '/.blocks-sandbox/*',
635
- ],
747
+ distributionPaths: [`/builds/${buildId}/.blocks-sandbox/*`, '/.blocks-sandbox/*'],
636
748
  cacheControl: [s3deploy.CacheControl.fromString('public, max-age=60, must-revalidate')],
637
749
  });
638
750
 
@@ -764,8 +876,8 @@ export class Hosting extends Construct {
764
876
  if (behaviorPattern.endsWith('/*')) {
765
877
  console.warn(
766
878
  `[Hosting] ⚠️ RawRoute '${route.path}' creates CloudFront behavior '${behaviorPattern}' ` +
767
- `which may shadow SSR/frontend routes under the same prefix. ` +
768
- `Consider placing this route under ${BLOCKS_RPC_PREFIX}/ to avoid conflicts.`,
879
+ `which may shadow SSR/frontend routes under the same prefix. ` +
880
+ `Consider placing this route under ${BLOCKS_RPC_PREFIX}/ to avoid conflicts.`,
769
881
  );
770
882
  }
771
883
 
package/src/index.cdk.ts CHANGED
@@ -1,50 +1,89 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- export { ApiNamespace, type BlocksContext, type ApiHandler } from './api.js';
5
- export { BLOCKS_RPC_PREFIX, BLOCKS_AUTH_PREFIX } from './constants.js';
6
- export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from './errors.js';
7
- export { EventSourceMapping } from './lambda-handler.js';
8
- export { BlocksStackProps } from './common/index.js';
9
- export { registerSdkIdentifiers, getSdkIdentifiers, getAllSdkIdentifiers, _resetSdkRegistry } from './common/sdk-registry.js';
10
- export { getConfig, getConfigSync, preloadConfig, loadConfigToProcessEnv, _resetConfigCache } from './common/config.js';
11
- export { BlocksStack, Scope, SandboxDisableDeletionProtection, BlocksBackend, registerConfig, finalizeConfigRegistry, synthGuard, DEFAULT_NODE_RUNTIME, blocksNodejsBundling, type BlocksBackendProps } from './cdk/index.js';
4
+ // Declare functions for infra (secret/config). The runtime getters
5
+ // (getSecret/getConfig) are imported from '@aws-blocks/hosting' — core's own
6
+ // getConfig (backend config loader, above) owns that name here.
7
+ export {
8
+ type ConfigValue,
9
+ config,
10
+ DEFAULT_CONFIG_PARAMETER_PREFIX,
11
+ DEFAULT_SECRET_PARAMETER_PREFIX,
12
+ isConfig,
13
+ isManagedValue,
14
+ isSecret,
15
+ type ManagedValue,
16
+ type SecretValue,
17
+ secret,
18
+ type ValueKind,
19
+ } from '@aws-blocks/hosting';
20
+ export { type ApiHandler, ApiNamespace, type BlocksContext } from './api.js';
12
21
  export {
13
- type BlocksDefaults,
14
- BlocksPresets,
22
+ BlocksBackend,
23
+ type BlocksBackendProps,
24
+ type BlocksDefaults,
25
+ BlocksPresets,
26
+ BlocksStack,
27
+ blocksNodejsBundling,
28
+ type CoreBlocksBackendProps,
29
+ type CoreBlocksStackProps,
30
+ DEFAULT_NODE_RUNTIME,
31
+ finalizeConfigRegistry,
32
+ registerConfig,
33
+ SandboxDisableDeletionProtection,
34
+ Scope,
35
+ SHARED_HANDLER_TIMEOUT_SECONDS,
36
+ synthGuard,
15
37
  } from './cdk/index.js';
38
+ export { _resetConfigCache, getConfig, getConfigSync, loadConfigToProcessEnv, preloadConfig } from './common/config.js';
39
+ export { BlocksStackProps } from './common/index.js';
40
+ export {
41
+ _resetSdkRegistry,
42
+ getAllSdkIdentifiers,
43
+ getSdkIdentifiers,
44
+ registerSdkIdentifiers,
45
+ } from './common/sdk-registry.js';
46
+ export { BLOCKS_AUTH_PREFIX, BLOCKS_RPC_PREFIX } from './constants.js';
47
+ export { ApiError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
16
48
  export {
17
- Hosting,
18
- type HostingProps,
19
- type BlocksStackApi,
20
- type FrameworkType,
21
- type ComputeConfig,
22
- type HostingDomainConfig,
23
- type HostingWafConfig,
49
+ type BlocksStackApi,
50
+ type ComputeConfig,
51
+ type FrameworkType,
52
+ Hosting,
53
+ type HostingDomainConfig,
54
+ type HostingProps,
55
+ type HostingWafConfig,
24
56
  } from './hosting.js';
57
+ export { EventSourceMapping } from './lambda-handler.js';
58
+ export {
59
+ __PIPELINE_STAGE_SCOPE__,
60
+ type BranchConfig,
61
+ DeployStage,
62
+ type DeployStageProps,
63
+ Pipeline,
64
+ type PipelineProps,
65
+ type PipelineSourceConfig,
66
+ type PipelineStageConfig,
67
+ type PipelineSynthConfig,
68
+ } from './pipeline/index.js';
25
69
  export {
26
- RawRoute,
27
- RawRouteErrors,
28
- type RawRouteOptions,
29
- type HttpMethod,
70
+ type HttpMethod,
71
+ RawRoute,
72
+ RawRouteErrors,
73
+ type RawRouteOptions,
30
74
  } from './raw-route.cdk.js';
31
75
  export {
32
- registerRoute,
33
- matchRoute,
34
- getRegisteredRoutes,
35
- clearRouteRegistry,
36
- lockRouteRegistry,
37
- unlockRouteRegistry,
38
- type RegisteredRoute,
76
+ clearRouteRegistry,
77
+ getRegisteredRoutes,
78
+ lockRouteRegistry,
79
+ matchRoute,
80
+ type RegisteredRoute,
81
+ registerRoute,
82
+ unlockRouteRegistry,
39
83
  } from './raw-route.js';
40
84
  export {
41
- Pipeline,
42
- DeployStage,
43
- __PIPELINE_STAGE_SCOPE__,
44
- type DeployStageProps,
45
- type BranchConfig,
46
- type PipelineProps,
47
- type PipelineSourceConfig,
48
- type PipelineSynthConfig,
49
- type PipelineStageConfig,
50
- } from './pipeline/index.js';
85
+ BLOCKS_CONFIG_PARAMETER_PREFIX,
86
+ BLOCKS_SECRET_PARAMETER_PREFIX,
87
+ blocksConfigParameterName,
88
+ blocksSecretParameterName,
89
+ } from './secret-naming.js';
package/src/index.ts CHANGED
@@ -1,22 +1,41 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- export { ApiNamespace, type BlocksContext, type ApiHandler } from './api.js';
5
- export { BLOCKS_RPC_PREFIX, BLOCKS_AUTH_PREFIX } from './constants.js';
6
- export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from './errors.js';
7
- export { Scope, type ScopeOptions, type ScopeParent, type BuildingBlockMeta } from './common/index.js';
8
- export { registerSdkIdentifiers, getSdkIdentifiers, getAllSdkIdentifiers, _resetSdkRegistry } from './common/sdk-registry.js';
9
- export { getConfig, getConfigSync, preloadConfig, loadConfigToProcessEnv, _resetConfigCache } from './common/config.js';
4
+ // secret()/config() declare markers (CDK-free subpath). The runtime getters
5
+ // getSecret()/getConfig() are imported from '@aws-blocks/hosting' directly —
6
+ // core already exports a backend `getConfig` (config loader) under this name.
10
7
  export {
11
- registerRoute,
12
- matchRoute,
13
- getRegisteredRoutes,
14
- clearRouteRegistry,
15
- lockRouteRegistry,
16
- unlockRouteRegistry,
17
- RawRouteErrors,
18
- type RawRouteOptions,
19
- type HttpMethod,
20
- type RegisteredRoute,
8
+ type ConfigValue,
9
+ config,
10
+ isConfig,
11
+ isManagedValue,
12
+ isSecret,
13
+ type ManagedValue,
14
+ type SecretValue,
15
+ secret,
16
+ type ValueKind,
17
+ } from '@aws-blocks/hosting';
18
+ export { type ApiHandler, ApiNamespace, type BlocksContext } from './api.js';
19
+ export { _resetConfigCache, getConfig, getConfigSync, loadConfigToProcessEnv, preloadConfig } from './common/config.js';
20
+ export { type BuildingBlockMeta, Scope, type ScopeOptions, type ScopeParent } from './common/index.js';
21
+ export {
22
+ _resetSdkRegistry,
23
+ getAllSdkIdentifiers,
24
+ getSdkIdentifiers,
25
+ registerSdkIdentifiers,
26
+ } from './common/sdk-registry.js';
27
+ export { BLOCKS_AUTH_PREFIX, BLOCKS_RPC_PREFIX } from './constants.js';
28
+ export { ApiError, DEFAULT_API_ERROR_NAME, hasAuthError, isBlocksError } from './errors.js';
29
+ export {
30
+ clearRouteRegistry,
31
+ getRegisteredRoutes,
32
+ type HttpMethod,
33
+ lockRouteRegistry,
34
+ matchRoute,
35
+ RawRouteErrors,
36
+ type RawRouteOptions,
37
+ type RegisteredRoute,
38
+ registerRoute,
39
+ unlockRouteRegistry,
21
40
  } from './raw-route.js';
22
41
  export { RawRoute } from './raw-route.mock.js';
@@ -1072,3 +1072,77 @@ describe('createLambdaHandler — sandbox forwarded-host reaches the route', ()
1072
1072
  assert.strictEqual(capturedHost, 'abc123.execute-api.us-east-1.amazonaws.com');
1073
1073
  });
1074
1074
  });
1075
+
1076
+ // ── SQS partial batch failure tests ─────────────────────────────────────────
1077
+
1078
+ /**
1079
+ * These pin the runtime half of the bb-async-job BatchSize fix: with a batch
1080
+ * larger than 1, failing records must be reported individually via
1081
+ * `batchItemFailures` so SQS retries only those messages. If this response
1082
+ * shape regresses, every successful sibling message in a partially-failing
1083
+ * batch would be redelivered (or, worse, the failures silently dropped).
1084
+ */
1085
+ function makeSqsBatchEvent(count: number) {
1086
+ return {
1087
+ Records: Array.from({ length: count }, (_, i) => ({
1088
+ eventSource: 'aws:sqs',
1089
+ eventSourceARN: 'arn:aws:sqs:us-east-1:123456789012:my-queue',
1090
+ messageId: `msg-${i}`,
1091
+ body: JSON.stringify({ n: i }),
1092
+ })),
1093
+ };
1094
+ }
1095
+
1096
+ describe('handleEventSourceRecords — SQS partial batch responses', () => {
1097
+ it('reports only the failed records of a mixed-success 10-record batch', async () => {
1098
+ const processed: number[] = [];
1099
+ const handlers = new Map<string, (record: any) => Promise<void>>();
1100
+ handlers.set('aws:sqs:my-queue', async (record: any) => {
1101
+ const { n } = JSON.parse(record.body);
1102
+ if (n % 3 === 0) throw new Error(`boom on ${n}`);
1103
+ processed.push(n);
1104
+ });
1105
+ (globalThis as any).__BLOCKS_LAMBDA_EVENT_HANDLERS__ = handlers;
1106
+
1107
+ try {
1108
+ const handler = createLambdaHandler(async () => ({}));
1109
+ const result: any = await handler(makeSqsBatchEvent(10));
1110
+
1111
+ assert.deepStrictEqual(
1112
+ result.batchItemFailures.map((f: any) => f.itemIdentifier).sort(),
1113
+ ['msg-0', 'msg-3', 'msg-6', 'msg-9']
1114
+ );
1115
+ assert.deepStrictEqual(processed.sort((a, b) => a - b), [1, 2, 4, 5, 7, 8]);
1116
+ } finally {
1117
+ delete (globalThis as any).__BLOCKS_LAMBDA_EVENT_HANDLERS__;
1118
+ }
1119
+ });
1120
+
1121
+ it('returns no batchItemFailures when every record in the batch succeeds', async () => {
1122
+ const handlers = new Map<string, (record: any) => Promise<void>>();
1123
+ handlers.set('aws:sqs:my-queue', async () => {});
1124
+ (globalThis as any).__BLOCKS_LAMBDA_EVENT_HANDLERS__ = handlers;
1125
+
1126
+ try {
1127
+ const handler = createLambdaHandler(async () => ({}));
1128
+ const result: any = await handler(makeSqsBatchEvent(10));
1129
+ assert.deepStrictEqual(result, {});
1130
+ } finally {
1131
+ delete (globalThis as any).__BLOCKS_LAMBDA_EVENT_HANDLERS__;
1132
+ }
1133
+ });
1134
+
1135
+ it('reports all records when the whole batch fails (no silent drop)', async () => {
1136
+ const handlers = new Map<string, (record: any) => Promise<void>>();
1137
+ handlers.set('aws:sqs:my-queue', async () => { throw new Error('always'); });
1138
+ (globalThis as any).__BLOCKS_LAMBDA_EVENT_HANDLERS__ = handlers;
1139
+
1140
+ try {
1141
+ const handler = createLambdaHandler(async () => ({}));
1142
+ const result: any = await handler(makeSqsBatchEvent(10));
1143
+ assert.strictEqual(result.batchItemFailures.length, 10);
1144
+ } finally {
1145
+ delete (globalThis as any).__BLOCKS_LAMBDA_EVENT_HANDLERS__;
1146
+ }
1147
+ });
1148
+ });