@aws-blocks/bb-app-setting 0.1.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 (43) hide show
  1. package/LICENSE +174 -0
  2. package/README.md +156 -0
  3. package/dist/errors.d.ts +25 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/errors.js +26 -0
  6. package/dist/index.aws.d.ts +76 -0
  7. package/dist/index.aws.d.ts.map +1 -0
  8. package/dist/index.aws.js +135 -0
  9. package/dist/index.browser.d.ts +5 -0
  10. package/dist/index.browser.d.ts.map +1 -0
  11. package/dist/index.browser.js +9 -0
  12. package/dist/index.cdk.d.ts +35 -0
  13. package/dist/index.cdk.d.ts.map +1 -0
  14. package/dist/index.cdk.js +242 -0
  15. package/dist/index.cdk.test.d.ts +2 -0
  16. package/dist/index.cdk.test.d.ts.map +1 -0
  17. package/dist/index.cdk.test.js +187 -0
  18. package/dist/index.hooks.d.ts +2 -0
  19. package/dist/index.hooks.d.ts.map +1 -0
  20. package/dist/index.hooks.js +3 -0
  21. package/dist/index.mock.d.ts +77 -0
  22. package/dist/index.mock.d.ts.map +1 -0
  23. package/dist/index.mock.js +153 -0
  24. package/dist/index.test.d.ts +2 -0
  25. package/dist/index.test.d.ts.map +1 -0
  26. package/dist/index.test.js +542 -0
  27. package/dist/types.d.ts +52 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/types.js +3 -0
  30. package/dist/version.d.ts +3 -0
  31. package/dist/version.d.ts.map +1 -0
  32. package/dist/version.js +3 -0
  33. package/package.json +46 -0
  34. package/src/errors.ts +27 -0
  35. package/src/index.aws.ts +165 -0
  36. package/src/index.browser.ts +10 -0
  37. package/src/index.cdk.test.ts +227 -0
  38. package/src/index.cdk.ts +291 -0
  39. package/src/index.hooks.ts +5 -0
  40. package/src/index.mock.ts +181 -0
  41. package/src/index.test.ts +627 -0
  42. package/src/types.ts +56 -0
  43. package/src/version.ts +3 -0
@@ -0,0 +1,153 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { Scope, registerSdkIdentifiers } from '@aws-blocks/core';
4
+ import { getMockDataDir } from '@aws-blocks/core/bb-utils';
5
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { randomBytes } from 'node:crypto';
8
+ import { AppSettingErrors } from './errors.js';
9
+ import { Logger } from '@aws-blocks/bb-logger';
10
+ import { BB_NAME, BB_VERSION } from './version.js';
11
+ // Re-export public types
12
+ export { AppSettingErrors } from './errors.js';
13
+ // ── Helpers ─────────────────────────────────────────────────────────────────
14
+ const MAX_VALUE_BYTES = 4096; // SSM standard-tier 4 KB limit
15
+ function readSettings(scope) {
16
+ const fp = join(getMockDataDir(scope, { root: true }), 'settings.json');
17
+ if (!existsSync(fp))
18
+ return {};
19
+ try {
20
+ return JSON.parse(readFileSync(fp, 'utf8'));
21
+ }
22
+ catch {
23
+ return {};
24
+ }
25
+ }
26
+ function writeSettings(scope, data) {
27
+ const fp = join(getMockDataDir(scope, { root: true }), 'settings.json');
28
+ writeFileSync(fp, JSON.stringify(data, null, 2));
29
+ }
30
+ function blocksError(name, message) {
31
+ const err = new Error(`${name}: ${message}`);
32
+ err.name = name;
33
+ return err;
34
+ }
35
+ async function validateSchema(schema, value) {
36
+ if (!schema)
37
+ return;
38
+ const result = schema['~standard'].validate(value);
39
+ const resolved = result instanceof Promise ? await result : result;
40
+ if (resolved.issues) {
41
+ throw blocksError(AppSettingErrors.ValidationFailed, resolved.issues[0].message);
42
+ }
43
+ }
44
+ // ── AppSetting (mock) ───────────────────────────────────────────────────────
45
+ /**
46
+ * A single application configuration value backed by SSM Parameter Store.
47
+ *
48
+ * **When to use:** You need to store and retrieve a non-secret configuration
49
+ * value at runtime — a feature flag, API URL, threshold, or structured config
50
+ * object. For sensitive values, set `secret: true` to use SSM SecureString.
51
+ *
52
+ * **When NOT to use:** If you need structured key-value data with conditional
53
+ * writes and queries, use `KVStore` or `DistributedTable`.
54
+ *
55
+ * **Best practices:**
56
+ * - One AppSetting per logical configuration value
57
+ * - Use a schema for structured objects to get type safety and runtime validation
58
+ * - Use `secret: true` for API keys, tokens, and passwords
59
+ *
60
+ * **Scaling:** Standard-tier SSM parameters. 40 TPS default for GetParameter
61
+ * (can be increased). No cost for standard parameters.
62
+ */
63
+ export class AppSetting extends Scope {
64
+ /**
65
+ * Reference an SSM parameter created and owned outside this stack — the local
66
+ * dev mirror of the CDK `fromExisting`. In the mock there is no IAM or
67
+ * bulk-init, so it behaves like a normal setting keyed by its `fullId`; the
68
+ * factory exists so app code uses the same API in dev and deploy.
69
+ *
70
+ * Note: like any mock secret with no value, `get()` returns a random placeholder
71
+ * unless `.bb-data/settings.json` was already seeded for this `fullId` (e.g. by
72
+ * `db pull`). Local dev still depends on whatever seeds that value.
73
+ */
74
+ static fromExisting(scope, id, options) {
75
+ const opts = { ...options, external: true };
76
+ return new AppSetting(scope, id, opts);
77
+ }
78
+ parameterName;
79
+ initialValue;
80
+ schema;
81
+ isSecret;
82
+ /** @internal Logger for internal operations. Defaults to error-level when not provided. */
83
+ log;
84
+ constructor(scope, id, options) {
85
+ super(id, { parent: scope, bbName: BB_NAME, bbVersion: BB_VERSION });
86
+ this.log = options?.logger ?? new Logger(this, 'logger', { level: 'error' });
87
+ this.parameterName = options.name ?? `/${this.fullId}`;
88
+ this.schema = options.schema;
89
+ this.isSecret = options.secret ?? false;
90
+ // Determine initial value: use provided value, or generate random for secrets, or empty string
91
+ if (options.value !== undefined) {
92
+ this.initialValue = options.value;
93
+ }
94
+ else if (this.isSecret) {
95
+ this.initialValue = randomBytes(32).toString('base64url');
96
+ }
97
+ else {
98
+ this.initialValue = '';
99
+ }
100
+ registerSdkIdentifiers(this.fullId, { parameterName: this.parameterName });
101
+ // Persist initial value to settings.json if not already present
102
+ const settings = readSettings(this);
103
+ if (!(this.fullId in settings)) {
104
+ settings[this.fullId] = this.initialValue;
105
+ writeSettings(this, settings);
106
+ }
107
+ }
108
+ /**
109
+ * Retrieve the current value.
110
+ *
111
+ * Returns the stored value from `.bb-data/settings.json`.
112
+ *
113
+ * @returns The current value.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * const retries = await maxRetries.get();
118
+ * ```
119
+ */
120
+ async get() {
121
+ const settings = readSettings(this);
122
+ const value = (this.fullId in settings ? settings[this.fullId] : this.initialValue);
123
+ if (this.isSecret && value === '') {
124
+ throw blocksError(AppSettingErrors.ParameterNotFound, `Secret parameter "${this.parameterName}" has an empty value — secrets must not be empty`);
125
+ }
126
+ return value;
127
+ }
128
+ /**
129
+ * Update the value at runtime.
130
+ *
131
+ * Writes the new value to `.bb-data/settings.json`. When a schema is
132
+ * configured, the value is validated before writing.
133
+ *
134
+ * @param value - The new value to store.
135
+ * @throws {AppSettingErrors.ValidationFailed} If schema validation fails or value exceeds 4 KB.
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * await maxRetries.put('5');
140
+ * await config.put({ maxRetries: 5, timeout: 10000 });
141
+ * ```
142
+ */
143
+ async put(value) {
144
+ await validateSchema(this.schema, value);
145
+ const serialized = JSON.stringify(value);
146
+ if (!this.isSecret && Buffer.byteLength(serialized, 'utf8') > MAX_VALUE_BYTES) {
147
+ throw blocksError(AppSettingErrors.ValidationFailed, `Value size ${Buffer.byteLength(serialized, 'utf8')} bytes exceeds the 4 KB (${MAX_VALUE_BYTES} bytes) limit`);
148
+ }
149
+ const settings = readSettings(this);
150
+ settings[this.fullId] = value;
151
+ writeSettings(this, settings);
152
+ }
153
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}