@aws-blocks/core 0.1.4 → 0.1.10

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 (91) hide show
  1. package/dist/cdk/index.d.ts +1 -1
  2. package/dist/cdk/index.d.ts.map +1 -1
  3. package/dist/cdk/index.js +1 -1
  4. package/dist/client/index.d.ts +1 -1
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/index.js +1 -1
  7. package/dist/db-naming.d.ts +17 -5
  8. package/dist/db-naming.d.ts.map +1 -1
  9. package/dist/db-naming.js +18 -6
  10. package/dist/db-naming.test.js +44 -3
  11. package/dist/errors.d.ts +28 -0
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +27 -1
  14. package/dist/errors.test.d.ts +2 -0
  15. package/dist/errors.test.d.ts.map +1 -0
  16. package/dist/errors.test.js +47 -0
  17. package/dist/hosting.d.ts +71 -1
  18. package/dist/hosting.d.ts.map +1 -1
  19. package/dist/hosting.js +47 -8
  20. package/dist/hosting.test.js +60 -0
  21. package/dist/index.cdk.d.ts +1 -1
  22. package/dist/index.cdk.d.ts.map +1 -1
  23. package/dist/index.cdk.js +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -1
  27. package/dist/scripts/deploy.d.ts.map +1 -1
  28. package/dist/scripts/deploy.js +4 -2
  29. package/dist/scripts/dev-server-supervisor.test.d.ts +2 -0
  30. package/dist/scripts/dev-server-supervisor.test.d.ts.map +1 -0
  31. package/dist/scripts/dev-server-supervisor.test.js +551 -0
  32. package/dist/scripts/dev-server.d.ts +73 -0
  33. package/dist/scripts/dev-server.d.ts.map +1 -1
  34. package/dist/scripts/dev-server.js +279 -29
  35. package/dist/scripts/ensure-secrets.d.ts +5 -2
  36. package/dist/scripts/ensure-secrets.d.ts.map +1 -1
  37. package/dist/scripts/ensure-secrets.js +14 -6
  38. package/dist/scripts/external-migrations-step.d.ts.map +1 -1
  39. package/dist/scripts/external-migrations-step.js +5 -1
  40. package/dist/scripts/index.d.ts +1 -0
  41. package/dist/scripts/index.d.ts.map +1 -1
  42. package/dist/scripts/index.js +1 -0
  43. package/dist/scripts/process-tree.d.ts +126 -0
  44. package/dist/scripts/process-tree.d.ts.map +1 -0
  45. package/dist/scripts/process-tree.js +198 -0
  46. package/dist/scripts/sandbox.d.ts.map +1 -1
  47. package/dist/scripts/sandbox.js +51 -4
  48. package/dist/scripts/stack-id.d.ts +37 -0
  49. package/dist/scripts/stack-id.d.ts.map +1 -0
  50. package/dist/scripts/stack-id.js +79 -0
  51. package/dist/scripts/stack-id.test.d.ts +2 -0
  52. package/dist/scripts/stack-id.test.d.ts.map +1 -0
  53. package/dist/scripts/stack-id.test.js +104 -0
  54. package/dist/telemetry/client.d.ts +3 -1
  55. package/dist/telemetry/client.d.ts.map +1 -1
  56. package/dist/telemetry/client.js +20 -24
  57. package/dist/telemetry/telemetry-send-worker.d.ts +2 -0
  58. package/dist/telemetry/telemetry-send-worker.d.ts.map +1 -0
  59. package/dist/telemetry/telemetry-send-worker.js +58 -0
  60. package/dist/telemetry/telemetry.test.js +77 -1
  61. package/dist/telemetry/trackCommand.d.ts +1 -1
  62. package/dist/telemetry/trackCommand.js +3 -3
  63. package/dist/version.d.ts +1 -1
  64. package/dist/version.d.ts.map +1 -1
  65. package/dist/version.js +1 -1
  66. package/package.json +1 -1
  67. package/src/cdk/index.ts +1 -1
  68. package/src/client/index.ts +1 -1
  69. package/src/db-naming.test.ts +50 -5
  70. package/src/db-naming.ts +18 -6
  71. package/src/errors.test.ts +55 -0
  72. package/src/errors.ts +32 -1
  73. package/src/hosting.test.ts +79 -0
  74. package/src/hosting.ts +127 -13
  75. package/src/index.cdk.ts +1 -1
  76. package/src/index.ts +1 -1
  77. package/src/scripts/deploy.ts +4 -2
  78. package/src/scripts/dev-server-supervisor.test.ts +621 -0
  79. package/src/scripts/dev-server.ts +316 -27
  80. package/src/scripts/ensure-secrets.ts +17 -6
  81. package/src/scripts/external-migrations-step.ts +5 -1
  82. package/src/scripts/index.ts +1 -0
  83. package/src/scripts/process-tree.ts +245 -0
  84. package/src/scripts/sandbox.ts +50 -4
  85. package/src/scripts/stack-id.test.ts +123 -0
  86. package/src/scripts/stack-id.ts +87 -0
  87. package/src/telemetry/client.ts +22 -30
  88. package/src/telemetry/telemetry-send-worker.ts +60 -0
  89. package/src/telemetry/telemetry.test.ts +91 -1
  90. package/src/telemetry/trackCommand.ts +3 -3
  91. package/src/version.ts +1 -1
@@ -7,7 +7,7 @@ export { SandboxDisableDeletionProtection } from './mixins.js';
7
7
  export { registerConfig, finalizeConfigRegistry } from './config-registry.js';
8
8
  export { synthGuard } from './synth-guard.js';
9
9
  export type { ScopeOptions } from '../index.js';
10
- export { ApiError, isBlocksError } from '../errors.js';
10
+ export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from '../errors.js';
11
11
  export declare class BlocksStack extends cdk.Stack implements BaseBlocksStack {
12
12
  readonly id: string;
13
13
  readonly apiUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,IAAI,eAAe,EACnC,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEvD,qBAAa,WAAY,SAAQ,GAAG,CAAC,KAAM,YAAW,eAAe;IACnE,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IACpD,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C,OAAO;WAcM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CA6B1E;AAED,qBAAa,KAAM,SAAQ,SAAS;IAClC,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,WAAW,CAAC;IAEpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAO9C,IAAI,OAAO,yCAWV;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,SAAS,CAAC,mBAAmB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAKnD,wBAAwB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACzD,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACtD,0BAA0B,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IACrH,IAAI,gBAAgB,IAAI,SAAS,MAAM,EAAE,CAAe;IACxD,IAAI,cAAc,IAAI,SAAS,MAAM,EAAE,CAAe;CACvD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,IAAI,eAAe,EACnC,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE7F,qBAAa,WAAY,SAAQ,GAAG,CAAC,KAAM,YAAW,eAAe;IACnE,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IACpD,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C,OAAO;WAcM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CA6B1E;AAED,qBAAa,KAAM,SAAQ,SAAS;IAClC,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,WAAW,CAAC;IAEpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAO9C,IAAI,OAAO,yCAWV;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,SAAS,CAAC,mBAAmB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAKnD,wBAAwB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACzD,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACtD,0BAA0B,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IACrH,IAAI,gBAAgB,IAAI,SAAS,MAAM,EAAE,CAAe;IACxD,IAAI,cAAc,IAAI,SAAS,MAAM,EAAE,CAAe;CACvD"}
package/dist/cdk/index.js CHANGED
@@ -13,7 +13,7 @@ export { DEFAULT_NODE_RUNTIME } from './node-version.js';
13
13
  export { SandboxDisableDeletionProtection } from './mixins.js';
14
14
  export { registerConfig, finalizeConfigRegistry } from './config-registry.js';
15
15
  export { synthGuard } from './synth-guard.js';
16
- export { ApiError, isBlocksError } from '../errors.js';
16
+ export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from '../errors.js';
17
17
  export class BlocksStack extends cdk.Stack {
18
18
  id;
19
19
  apiUrl;
@@ -92,6 +92,6 @@ export interface ApiNamespaceClientOptions {
92
92
  * ```
93
93
  */
94
94
  export declare function ApiNamespaceClient<T extends Record<string, (...args: any[]) => any>>(name: string, options?: ApiNamespaceClientOptions): AsyncAPI<T>;
95
- export { ApiError, isBlocksError } from '../errors.js';
95
+ export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from '../errors.js';
96
96
  export { Scope, type ScopeOptions } from '../common/index.js';
97
97
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC;AAChC,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC;AAiBhF,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI;KAChE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,CAAC,GACzD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GACtC,KAAK;CACV,CAAC;AAoHF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,iKAAiK;IACjK,YAAY,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,wHAAwH;IACxH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6GAA6G;IAC7G,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC7F,6FAA6F;IAC7F,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACzC;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAErE;AAqBD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,EAClF,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,yBAAyB,GAClC,QAAQ,CAAC,CAAC,CAAC,CAyDb;AAED,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC;AAChC,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC;AAiBhF,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI;KAChE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,CAAC,GACzD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GACtC,KAAK;CACV,CAAC;AAoHF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,iKAAiK;IACjK,YAAY,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,wHAAwH;IACxH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6GAA6G;IAC7G,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC7F,6FAA6F;IAC7F,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACzC;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAErE;AAqBD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,EAClF,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,yBAAyB,GAClC,QAAQ,CAAC,CAAC,CAAC,CAyDb;AAED,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC7F,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
@@ -234,5 +234,5 @@ export function ApiNamespaceClient(name, options) {
234
234
  }
235
235
  });
236
236
  }
237
- export { ApiError, isBlocksError } from '../errors.js';
237
+ export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from '../errors.js';
238
238
  export { Scope } from '../common/index.js';
@@ -3,9 +3,15 @@
3
3
  * database connection string, and for the project ref derived from a Postgres
4
4
  * connection string.
5
5
  *
6
- * Both the deploy-time provisioner (`ensure-secrets`) and the `db pull`
7
- * generated code derive this name from the stage alone. Keep this the only
8
- * place the name is constructed.
6
+ * The connection-string parameter name is **stack-scoped** (embeds the
7
+ * deployment's stack name), so two Blocks apps in the same account + region +
8
+ * stage get distinct names and cannot overwrite each other's credentials.
9
+ *
10
+ * Two call sites compute the name: the pre-deploy writer (`ensure-secrets`) and
11
+ * the `db pull` generated wiring at synth. Both pass the result of
12
+ * `getStackName({ sandbox, projectRoot })` into this function, so they produce
13
+ * the same name by construction. The runtime Lambda does not call this function;
14
+ * it reads the name recorded at synth.
9
15
  */
10
16
  /**
11
17
  * Extract a stable identifier from a Postgres connection string.
@@ -16,6 +22,12 @@
16
22
  * to a sanitized hostname for non-Supabase hosts.
17
23
  */
18
24
  export declare function extractDbRef(connectionString: string): string;
19
- /** SSM parameter name storing the connection string for a given stage. */
20
- export declare function dbConnectionParameterName(stage: string): string;
25
+ /**
26
+ * SSM SecureString parameter name for a deployment's external database
27
+ * connection string.
28
+ *
29
+ * Pure string transform: `/<stackName>-db-url`. The caller is responsible for
30
+ * computing the stack name via `getStackName({ sandbox, projectRoot })`.
31
+ */
32
+ export declare function dbConnectionParameterName(stackName: string): string;
21
33
  //# sourceMappingURL=db-naming.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"db-naming.d.ts","sourceRoot":"","sources":["../src/db-naming.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAc7D;AAED,0EAA0E;AAC1E,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D"}
1
+ {"version":3,"file":"db-naming.d.ts","sourceRoot":"","sources":["../src/db-naming.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAc7D;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE"}
package/dist/db-naming.js CHANGED
@@ -5,9 +5,15 @@
5
5
  * database connection string, and for the project ref derived from a Postgres
6
6
  * connection string.
7
7
  *
8
- * Both the deploy-time provisioner (`ensure-secrets`) and the `db pull`
9
- * generated code derive this name from the stage alone. Keep this the only
10
- * place the name is constructed.
8
+ * The connection-string parameter name is **stack-scoped** (embeds the
9
+ * deployment's stack name), so two Blocks apps in the same account + region +
10
+ * stage get distinct names and cannot overwrite each other's credentials.
11
+ *
12
+ * Two call sites compute the name: the pre-deploy writer (`ensure-secrets`) and
13
+ * the `db pull` generated wiring at synth. Both pass the result of
14
+ * `getStackName({ sandbox, projectRoot })` into this function, so they produce
15
+ * the same name by construction. The runtime Lambda does not call this function;
16
+ * it reads the name recorded at synth.
11
17
  */
12
18
  /**
13
19
  * Extract a stable identifier from a Postgres connection string.
@@ -32,7 +38,13 @@ export function extractDbRef(connectionString) {
32
38
  return host[1].replace(/\./g, '-');
33
39
  throw new Error('Cannot extract database identifier from connection string.');
34
40
  }
35
- /** SSM parameter name storing the connection string for a given stage. */
36
- export function dbConnectionParameterName(stage) {
37
- return `/blocks/${stage}/db-connection-string`;
41
+ /**
42
+ * SSM SecureString parameter name for a deployment's external database
43
+ * connection string.
44
+ *
45
+ * Pure string transform: `/<stackName>-db-url`. The caller is responsible for
46
+ * computing the stack name via `getStackName({ sandbox, projectRoot })`.
47
+ */
48
+ export function dbConnectionParameterName(stackName) {
49
+ return `/${stackName}-db-url`;
38
50
  }
@@ -1,8 +1,12 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { test, describe } from 'node:test';
3
+ import { test, describe, afterEach } from 'node:test';
4
4
  import assert from 'node:assert';
5
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { tmpdir } from 'node:os';
5
8
  import { extractDbRef, dbConnectionParameterName } from './db-naming.js';
9
+ import { getStackName } from './scripts/stack-id.js';
6
10
  describe('extractDbRef', () => {
7
11
  test('pooler form (postgres.{ref}@) yields ref', () => {
8
12
  assert.strictEqual(extractDbRef('postgresql://postgres.abcdef:pw@aws-0-us-east-1.pooler.supabase.com:6543/postgres'), 'abcdef');
@@ -23,7 +27,44 @@ describe('extractDbRef', () => {
23
27
  });
24
28
  });
25
29
  describe('dbConnectionParameterName', () => {
26
- test('composes the stage into the SSM name', () => {
27
- assert.strictEqual(dbConnectionParameterName('production'), '/blocks/production/db-connection-string');
30
+ test('formats stack name into parameter path', () => {
31
+ assert.strictEqual(dbConnectionParameterName('my-app-k7x2mf-prod'), '/my-app-k7x2mf-prod-db-url');
32
+ });
33
+ test('two distinct stack names produce distinct parameter names', () => {
34
+ assert.notStrictEqual(dbConnectionParameterName('app-a-111111-prod'), dbConnectionParameterName('app-b-222222-prod'));
35
+ });
36
+ });
37
+ describe('cross-site invariant: write name == read name', () => {
38
+ let tmpDir;
39
+ let originalCwd;
40
+ afterEach(() => {
41
+ if (originalCwd)
42
+ process.chdir(originalCwd);
43
+ if (tmpDir)
44
+ rmSync(tmpDir, { recursive: true, force: true });
45
+ });
46
+ function setupProject(stackId, sandboxId) {
47
+ tmpDir = mkdtempSync(join(tmpdir(), 'cross-site-'));
48
+ mkdirSync(join(tmpDir, '.blocks'), { recursive: true });
49
+ writeFileSync(join(tmpDir, '.blocks', 'config.json'), JSON.stringify({ stackId }));
50
+ mkdirSync(join(tmpDir, '.blocks-sandbox'), { recursive: true });
51
+ writeFileSync(join(tmpDir, '.blocks-sandbox', 'sandbox-id.txt'), sandboxId);
52
+ return tmpDir;
53
+ }
54
+ test('write-side name == read-side name (sandbox)', () => {
55
+ const root = setupProject('my-app-k7x2mf', 'alice-0d7e1c');
56
+ const writeName = dbConnectionParameterName(getStackName({ sandbox: true, projectRoot: root }));
57
+ originalCwd = process.cwd();
58
+ process.chdir(root);
59
+ const readName = dbConnectionParameterName(getStackName({ sandbox: true }));
60
+ assert.strictEqual(writeName, readName);
61
+ });
62
+ test('write-side name == read-side name (production)', () => {
63
+ const root = setupProject('my-app-k7x2mf', 'alice-0d7e1c');
64
+ const writeName = dbConnectionParameterName(getStackName({ sandbox: false, projectRoot: root }));
65
+ originalCwd = process.cwd();
66
+ process.chdir(root);
67
+ const readName = dbConnectionParameterName(getStackName({ sandbox: false }));
68
+ assert.strictEqual(writeName, readName);
28
69
  });
29
70
  });
package/dist/errors.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * The `name` an `ApiError` falls back to when no structured error name is
3
+ * given. A name equal to this carries no BB-level meaning, so consumers
4
+ * branching on the structured identity should treat it as "no name".
5
+ */
6
+ export declare const DEFAULT_API_ERROR_NAME = "ApiError";
1
7
  /**
2
8
  * Error subclass for errors that cross the wire between server and client.
3
9
  *
@@ -64,4 +70,26 @@ export declare class ApiError extends Error {
64
70
  export declare function isBlocksError<N extends string>(e: unknown, name: N): e is Error & {
65
71
  name: N;
66
72
  };
73
+ /**
74
+ * Type guard for branching on a failed `AuthState` (the recommended
75
+ * `setAuthState` client path) by its structured `errorName`.
76
+ *
77
+ * The returned state is a plain object, not a thrown `Error`, so
78
+ * `isBlocksError` does not apply — use this on the value returned by
79
+ * `setAuthState`/`getAuthState`. Match on the BB error constant, never on
80
+ * the human-facing `error` string.
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const next = await authApi.setAuthState({ action: 'signIn', username, password });
85
+ * if (hasAuthError(next, AuthBasicErrors.InvalidCredentials)) {
86
+ * // unknown user → fall back to sign-up
87
+ * }
88
+ * ```
89
+ */
90
+ export declare function hasAuthError<T extends {
91
+ errorName?: string;
92
+ }, N extends string>(state: T | null | undefined, name: N): state is T & {
93
+ errorName: N;
94
+ };
67
95
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAClC,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE;CAM9G;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAE7F"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAClC,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE;CAM9G;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,GAAG;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAE7F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,SAAS,MAAM,EAC9E,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3B,IAAI,EAAE,CAAC,GACL,KAAK,IAAI,CAAC,GAAG;IAAE,SAAS,EAAE,CAAC,CAAA;CAAE,CAE/B"}
package/dist/errors.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * The `name` an `ApiError` falls back to when no structured error name is
5
+ * given. A name equal to this carries no BB-level meaning, so consumers
6
+ * branching on the structured identity should treat it as "no name".
7
+ */
8
+ export const DEFAULT_API_ERROR_NAME = 'ApiError';
3
9
  /**
4
10
  * Error subclass for errors that cross the wire between server and client.
5
11
  *
@@ -44,7 +50,7 @@ export class ApiError extends Error {
44
50
  retriable;
45
51
  constructor(message, status, options) {
46
52
  super(message, options?.cause ? { cause: options.cause } : undefined);
47
- this.name = options?.name ?? 'ApiError';
53
+ this.name = options?.name ?? DEFAULT_API_ERROR_NAME;
48
54
  this.status = status;
49
55
  this.retriable = options?.retriable ?? false;
50
56
  }
@@ -67,3 +73,23 @@ export class ApiError extends Error {
67
73
  export function isBlocksError(e, name) {
68
74
  return e instanceof Error && e.name === name;
69
75
  }
76
+ /**
77
+ * Type guard for branching on a failed `AuthState` (the recommended
78
+ * `setAuthState` client path) by its structured `errorName`.
79
+ *
80
+ * The returned state is a plain object, not a thrown `Error`, so
81
+ * `isBlocksError` does not apply — use this on the value returned by
82
+ * `setAuthState`/`getAuthState`. Match on the BB error constant, never on
83
+ * the human-facing `error` string.
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const next = await authApi.setAuthState({ action: 'signIn', username, password });
88
+ * if (hasAuthError(next, AuthBasicErrors.InvalidCredentials)) {
89
+ * // unknown user → fall back to sign-up
90
+ * }
91
+ * ```
92
+ */
93
+ export function hasAuthError(state, name) {
94
+ return state?.errorName === name;
95
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=errors.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.test.d.ts","sourceRoot":"","sources":["../src/errors.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { describe, it } from 'node:test';
4
+ import assert from 'node:assert';
5
+ import { ApiError, isBlocksError, hasAuthError } from './errors.js';
6
+ describe('isBlocksError', () => {
7
+ it('matches a thrown ApiError by name', () => {
8
+ const e = new ApiError('nope', 401, { name: 'InvalidCredentialsException' });
9
+ assert.ok(isBlocksError(e, 'InvalidCredentialsException'));
10
+ });
11
+ it('does not match a different name', () => {
12
+ const e = new ApiError('nope', 401, { name: 'InvalidCredentialsException' });
13
+ assert.ok(!isBlocksError(e, 'SomeOtherException'));
14
+ });
15
+ it('does not match a plain object (not an Error)', () => {
16
+ assert.ok(!isBlocksError({ name: 'InvalidCredentialsException' }, 'InvalidCredentialsException'));
17
+ });
18
+ });
19
+ describe('hasAuthError', () => {
20
+ it('matches a state carrying the given errorName', () => {
21
+ const state = { state: 'signedOut', errorName: 'InvalidCredentialsException' };
22
+ assert.ok(hasAuthError(state, 'InvalidCredentialsException'));
23
+ });
24
+ it('does not match a different errorName', () => {
25
+ const state = { errorName: 'InvalidCredentialsException' };
26
+ assert.ok(!hasAuthError(state, 'UserAlreadyExistsException'));
27
+ });
28
+ it('does not match a state with no errorName', () => {
29
+ const state = {};
30
+ assert.ok(!hasAuthError(state, 'InvalidCredentialsException'));
31
+ });
32
+ it('is safe on null / undefined', () => {
33
+ assert.ok(!hasAuthError(null, 'InvalidCredentialsException'));
34
+ assert.ok(!hasAuthError(undefined, 'InvalidCredentialsException'));
35
+ });
36
+ it('narrows the errorName to the matched literal', () => {
37
+ const state = { errorName: 'InvalidCredentialsException' };
38
+ if (hasAuthError(state, 'InvalidCredentialsException')) {
39
+ // Type-level: state.errorName is narrowed to the literal.
40
+ const name = state.errorName;
41
+ assert.strictEqual(name, 'InvalidCredentialsException');
42
+ }
43
+ else {
44
+ assert.fail('expected match');
45
+ }
46
+ });
47
+ });
package/dist/hosting.d.ts CHANGED
@@ -35,8 +35,29 @@ export type ComputeConfig = {
35
35
  * ```
36
36
  */
37
37
  timeout?: cdk.Duration | number;
38
- /** Reserved concurrent executions. Default: undefined (no reservation). */
38
+ /** Reserved concurrent executions for the SSR Lambda. Default: undefined (no reservation). */
39
39
  reservedConcurrency?: number;
40
+ /**
41
+ * Overrides for the image-optimization Lambda.
42
+ *
43
+ * `reservedConcurrency` defaults to undefined (no reservation). It is left
44
+ * unreserved so deploys succeed on fresh AWS accounts, whose default
45
+ * account-level unreserved-concurrency limit is 10 — reserving any
46
+ * concurrency there can drop the account below its required minimum and
47
+ * cause Lambda to reject the stack with a 400. Set this only if you have
48
+ * headroom and want to cap image-opt throughput.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * compute: {
53
+ * imageOptimization: { reservedConcurrency: 5 },
54
+ * }
55
+ * ```
56
+ */
57
+ imageOptimization?: {
58
+ /** Reserved concurrent executions. Default: undefined (no reservation). */
59
+ reservedConcurrency?: number;
60
+ };
40
61
  /** CloudWatch log retention for the SSR Lambda. Default: TWO_WEEKS. */
41
62
  logRetention?: cdk.aws_logs.RetentionDays;
42
63
  };
@@ -84,6 +105,26 @@ export interface HostingProps {
84
105
  buildOutputDir?: string;
85
106
  /** Supply a custom adapter when using an unsupported framework. */
86
107
  customAdapter?: FrameworkAdapterFn;
108
+ /**
109
+ * URL prefix the whole site is served under (Next.js `basePath`, Astro
110
+ * `base`, Nuxt `app.baseURL`). When set, CloudFront behaviors are prefixed
111
+ * with it and the bare root issues a 308 redirect to `/<basePath>/`.
112
+ *
113
+ * Declaring it here is the recommended source of truth: the value is
114
+ * caller-provided rather than reverse-engineered from build output, so it
115
+ * can't drift with framework/bundler internals. When omitted, the adapter
116
+ * falls back to detecting the framework's own base-path config from the
117
+ * build output.
118
+ *
119
+ * Format: leading slash, no trailing slash (e.g. `'/app'`). A trailing
120
+ * slash or bare `'/'` is normalized/ignored.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * new Hosting(stack, 'Web', { root, framework: 'nuxt', basePath: '/app' });
125
+ * ```
126
+ */
127
+ basePath?: string;
87
128
  /**
88
129
  * The Blocks backend stack (or any object with `apiUrl`).
89
130
  *
@@ -127,6 +168,35 @@ export interface HostingProps {
127
168
  type: 'whitelist' | 'blacklist';
128
169
  countries: string[];
129
170
  };
171
+ /**
172
+ * Overrides for the adjustable AWS Service Quotas the CloudFront
173
+ * distribution draws on. Each field maps to a named AWS quota you can
174
+ * request an increase on:
175
+ *
176
+ * - `cacheBehaviors` — "Cache behaviors per distribution" (default 25).
177
+ * Consumed by routed paths, prerendered pages, per-pattern header
178
+ * rules, assetPrefix, and the error-page behavior.
179
+ * - `edgeFunctions` — Lambda@Edge associations per distribution
180
+ * (default 25). Consumed by `runtime: 'edge'` routes.
181
+ * - `headerPolicies` — "Response headers policies per AWS account"
182
+ * (default 20, account-wide).
183
+ *
184
+ * Omitted fields use the AWS default. Set a field ONLY to match a quota
185
+ * increase AWS has actually granted — synth cannot verify your real quota,
186
+ * so an over-set value does not raise the AWS ceiling; it just moves the
187
+ * failure from a clear synth error to an opaque CloudFormation rollback.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * // After AWS grants "Cache behaviors per distribution" = 50:
192
+ * new Hosting(stack, 'Web', { root, quotas: { cacheBehaviors: 50 } });
193
+ * ```
194
+ */
195
+ quotas?: {
196
+ cacheBehaviors?: number;
197
+ edgeFunctions?: number;
198
+ headerPolicies?: number;
199
+ };
130
200
  /**
131
201
  * Build cache configuration. When enabled, provisions an S3 bucket for
132
202
  * framework build caches (e.g. Next.js .next/cache) and exports the bucket
@@ -1 +1 @@
1
- {"version":3,"file":"hosting.d.ts","sourceRoot":"","sources":["../src/hosting.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hosting.d.ts","sourceRoot":"","sources":["../src/hosting.ts"],"names":[],"mappings":"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"}
package/dist/hosting.js CHANGED
@@ -9,7 +9,7 @@ import { execSync } from 'node:child_process';
9
9
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
10
10
  import { join, resolve } from 'node:path';
11
11
  import { HostingConstruct, generateBuildId, } from '@aws-blocks/hosting/constructs';
12
- import { detectFramework, getAdapter, } from '@aws-blocks/hosting/adapters';
12
+ import { detectFramework, getAdapter, normalizeBasePath, } from '@aws-blocks/hosting/adapters';
13
13
  import { BLOCKS_RPC_PREFIX, BLOCKS_AUTH_PREFIX } from './constants.js';
14
14
  import { registerConfig } from './cdk/config-registry.js';
15
15
  import { getRegisteredRoutes } from './raw-route.js';
@@ -123,6 +123,23 @@ export class Hosting extends Construct {
123
123
  if (!manifest.buildId) {
124
124
  manifest.buildId = generateBuildId();
125
125
  }
126
+ // ── 4b'. basePath: prop is the source of truth ───────────────
127
+ // A caller-declared `basePath` overrides whatever the adapter
128
+ // detected from build output. This is the robust path: the value
129
+ // is provided rather than reverse-engineered from framework/bundler
130
+ // internals (which drift across versions). When the prop is omitted,
131
+ // the adapter's detected `manifest.basePath` (if any) stands.
132
+ if (props.basePath !== undefined) {
133
+ const normalized = normalizeBasePath(props.basePath);
134
+ if (normalized) {
135
+ manifest.basePath = normalized;
136
+ }
137
+ else {
138
+ // Explicit '/' (or empty) means "no base path" — clear any value
139
+ // the adapter may have detected so the prop genuinely wins.
140
+ delete manifest.basePath;
141
+ }
142
+ }
126
143
  // ── 4c. Prevent duplicate error pages ────────────────────────
127
144
  // The adapter may auto-detect error pages (e.g. SPA adapter finds
128
145
  // 404.html in build output and sets manifest.errorPages). When the
@@ -185,11 +202,12 @@ export class Hosting extends Construct {
185
202
  storage: props.retainOnDelete != null
186
203
  ? { retainOnDelete: props.retainOnDelete }
187
204
  : undefined,
188
- cdn: (props.contentSecurityPolicy || props.priceClass || props.geoRestriction)
205
+ cdn: (props.contentSecurityPolicy || props.priceClass || props.geoRestriction || props.quotas)
189
206
  ? {
190
207
  contentSecurityPolicy: props.contentSecurityPolicy,
191
208
  priceClass: props.priceClass,
192
209
  geoRestriction: props.geoRestriction,
210
+ quotas: props.quotas,
193
211
  }
194
212
  : undefined,
195
213
  logging: props.logging,
@@ -204,8 +222,15 @@ export class Hosting extends Construct {
204
222
  this.addApiBehaviors(hosting, props.api.apiUrl);
205
223
  }
206
224
  // ── 7a. Inject Blocks env vars into compute functions ───────────
207
- const primaryFunction = hosting.computeFunctions.values().next().value;
225
+ // Lambda@Edge functions (edge-runtime routes) do NOT support environment
226
+ // variables — they surface in computeFunctions as EdgeFunction/IVersion
227
+ // without an `addEnvironment` method. Skip any function that can't take
228
+ // env vars instead of crashing (`fn.addEnvironment is not a function`).
229
+ const canAddEnv = (fn) => typeof fn?.addEnvironment === 'function';
230
+ const primaryFunction = [...hosting.computeFunctions.values()].find(canAddEnv);
208
231
  for (const [, fn] of hosting.computeFunctions) {
232
+ if (!canAddEnv(fn))
233
+ continue; // Lambda@Edge: no env var support
209
234
  if (props.api) {
210
235
  fn.addEnvironment('BLOCKS_API_URL', props.api.apiUrl);
211
236
  }
@@ -227,11 +252,25 @@ export class Hosting extends Construct {
227
252
  distributionPaths: ['/.blocks-sandbox/*'],
228
253
  cacheControl: [s3deploy.CacheControl.fromString('public, max-age=60, must-revalidate')],
229
254
  });
230
- // Ensure config deployment runs after the hosting construct's
231
- // asset deployment so the resolved config.json is not overwritten.
232
- const assetDeployment = hosting.node.tryFindChild('AssetDeployment');
233
- if (assetDeployment) {
234
- configDeployment.node.addDependency(assetDeployment);
255
+ // Ensure the config deployment runs AFTER the hosting construct's
256
+ // asset deployments. Those deployments upload the whole static dir
257
+ // which includes the *placeholder* `.blocks-sandbox/config.json`
258
+ // (`{_placeholder:true}`) written during synth — to the same
259
+ // `builds/<id>/.blocks-sandbox/config.json` key this deployment writes
260
+ // the resolved config to. Without an ordering dependency the
261
+ // placeholder can land last and clobber the real config.
262
+ //
263
+ // We depend on EVERY BucketDeployment under the hosting construct
264
+ // rather than a single hard-coded child id: the real children are
265
+ // `AssetDeploymentImmutable` / `AssetDeploymentHtml` / `...Mutable`
266
+ // (and vary by deploy shape), so the previous
267
+ // `tryFindChild('AssetDeployment')` never matched and the dependency
268
+ // was silently never wired.
269
+ const assetDeployments = hosting.node
270
+ .findAll()
271
+ .filter((c) => c instanceof s3deploy.BucketDeployment);
272
+ for (const dep of assetDeployments) {
273
+ configDeployment.node.addDependency(dep);
235
274
  }
236
275
  }
237
276
  // ── 9. Register public origin + CORS hosting origin into S3 config ──
@@ -1074,4 +1074,64 @@ describe('Hosting', () => {
1074
1074
  template.resourceCountIs('AWS::CloudFront::Distribution', 1);
1075
1075
  });
1076
1076
  });
1077
+ // ── basePath prop (caller-declared source of truth) ─────────
1078
+ // Under KVS edge routing, basePath is no longer expressed as a per-behavior
1079
+ // PathPattern prefix — it lives in the KVS route table's `meta.bp`, which the
1080
+ // edge router uses for the canonical 308 + static strip. So these tests read
1081
+ // the basePath out of the RouteStoreKeys custom resource's Entries.
1082
+ describe('basePath prop', () => {
1083
+ const metaBasePath = (root, basePath) => {
1084
+ const app = new App();
1085
+ const stack = new Stack(app, 'BasePathStack', {
1086
+ env: { account: '123456789012', region: 'us-east-1' },
1087
+ });
1088
+ new Hosting(stack, 'Web', {
1089
+ root,
1090
+ framework: 'spa',
1091
+ buildOutputDir: 'dist',
1092
+ ...(basePath !== undefined ? { basePath } : {}),
1093
+ });
1094
+ const tpl = Template.fromStack(stack).toJSON();
1095
+ const kvKeys = Object.entries(tpl.Resources).find(([id, r]) => r.Type === 'AWS::CloudFormation::CustomResource' &&
1096
+ /RouteStoreKeys/.test(id));
1097
+ assert.ok(kvKeys, 'expected a RouteStoreKeys custom resource');
1098
+ const entries = JSON.parse(kvKeys[1].Properties.Entries);
1099
+ const meta = JSON.parse(entries.meta);
1100
+ return meta.bp;
1101
+ };
1102
+ it('records basePath in the KVS route table when set (SPA, no framework base)', () => {
1103
+ createSpaBuildOutput(tmpDir);
1104
+ assert.strictEqual(metaBasePath(tmpDir, '/app'), '/app');
1105
+ });
1106
+ it('normalizes a trailing slash (/app/ → /app)', () => {
1107
+ createSpaBuildOutput(tmpDir);
1108
+ assert.strictEqual(metaBasePath(tmpDir, '/app/'), '/app');
1109
+ });
1110
+ it('treats "/" as no base path', () => {
1111
+ createSpaBuildOutput(tmpDir);
1112
+ assert.strictEqual(metaBasePath(tmpDir, '/'), '');
1113
+ });
1114
+ });
1115
+ // ── P0.4: config.json ordering dependency ────────────────────
1116
+ describe('config.json deploy ordering (P0.4)', () => {
1117
+ it('BlocksConfigDeployment depends on the asset deployments', () => {
1118
+ // The asset deployments upload the whole static dir — including the
1119
+ // placeholder `.blocks-sandbox/config.json` — to the same key the
1120
+ // resolved config writes to. Without an ordering dependency the
1121
+ // placeholder can clobber the real config. The previous
1122
+ // `tryFindChild('AssetDeployment')` never matched the real child ids
1123
+ // (AssetDeploymentImmutable/Html/Mutable), so the dep was never wired.
1124
+ createSpaBuildOutput(tmpDir);
1125
+ const app = new App();
1126
+ const stack = new Stack(app, 'ConfigOrderStack');
1127
+ new Hosting(stack, 'Hosting', { root: tmpDir, api: MOCK_API });
1128
+ const tpl = Template.fromStack(stack).toJSON();
1129
+ const configId = Object.keys(tpl.Resources).find((id) => /BlocksConfigDeployment/.test(id) && /CustomResource/.test(id));
1130
+ assert.ok(configId, 'expected a BlocksConfigDeployment custom resource');
1131
+ const dependsOn = tpl.Resources[configId].DependsOn ?? [];
1132
+ const assetDeps = dependsOn.filter((d) => /AssetDeployment/.test(d));
1133
+ assert.ok(assetDeps.length >= 1, `BlocksConfigDeployment must DependsOn the asset deployment(s); ` +
1134
+ `found DependsOn=${JSON.stringify(dependsOn)}`);
1135
+ });
1136
+ });
1077
1137
  });
@@ -1,6 +1,6 @@
1
1
  export { ApiNamespace, type BlocksContext, type ApiHandler } from './api.js';
2
2
  export { BLOCKS_RPC_PREFIX, BLOCKS_AUTH_PREFIX } from './constants.js';
3
- export { ApiError, isBlocksError } from './errors.js';
3
+ export { ApiError, isBlocksError, hasAuthError, DEFAULT_API_ERROR_NAME } from './errors.js';
4
4
  export { EventSourceMapping } from './lambda-handler.js';
5
5
  export { BlocksStackProps } from './common/index.js';
6
6
  export { registerSdkIdentifiers, getSdkIdentifiers, getAllSdkIdentifiers, _resetSdkRegistry } from './common/sdk-registry.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC9H,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACxH,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,gCAAgC,EAAE,aAAa,EAAE,cAAc,EAAE,sBAAsB,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACxM,OAAO,EACL,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,aAAa,EACb,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,QAAQ,EACR,WAAW,EACX,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.cdk.d.ts","sourceRoot":"","sources":["../src/index.cdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC9H,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACxH,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,gCAAgC,EAAE,aAAa,EAAE,cAAc,EAAE,sBAAsB,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACxM,OAAO,EACL,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,aAAa,EACb,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,QAAQ,EACR,WAAW,EACX,wBAAwB,EACxB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC"}