@geekmidas/cli 10.0.0-alpha.54 → 10.0.0-alpha.56

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 (93) hide show
  1. package/README.md +11 -10
  2. package/dist/{HostingerProvider-GF6NAZAK.mjs → HostingerProvider-cL-6oQ5r.mjs} +2 -2
  3. package/dist/{HostingerProvider-GF6NAZAK.mjs.map → HostingerProvider-cL-6oQ5r.mjs.map} +1 -1
  4. package/dist/LegacyStateStore-ByRWQ9s1.mjs +89 -0
  5. package/dist/LegacyStateStore-ByRWQ9s1.mjs.map +1 -0
  6. package/dist/LegacyStateStore-C_OFs1gN.cjs +89 -0
  7. package/dist/LegacyStateStore-C_OFs1gN.cjs.map +1 -0
  8. package/dist/LocalStateStore-BupdV-VN.mjs +163 -0
  9. package/dist/LocalStateStore-BupdV-VN.mjs.map +1 -0
  10. package/dist/LocalStateStore-DDDU0HkI.cjs +163 -0
  11. package/dist/LocalStateStore-DDDU0HkI.cjs.map +1 -0
  12. package/dist/{OpenApiTsGenerator-BN0PIZxd.cjs → OpenApiTsGenerator-BLRQ7Iu9.cjs} +58 -59
  13. package/dist/OpenApiTsGenerator-BLRQ7Iu9.cjs.map +1 -0
  14. package/dist/{OpenApiTsGenerator-BzIGy8KE.mjs → OpenApiTsGenerator-NThANByQ.mjs} +58 -59
  15. package/dist/{OpenApiTsGenerator-BzIGy8KE.mjs.map → OpenApiTsGenerator-NThANByQ.mjs.map} +1 -1
  16. package/dist/S3StateStore-C7eBae0G.cjs +160 -0
  17. package/dist/S3StateStore-C7eBae0G.cjs.map +1 -0
  18. package/dist/S3StateStore-FhzyENrN.mjs +161 -0
  19. package/dist/S3StateStore-FhzyENrN.mjs.map +1 -0
  20. package/dist/SSMStateStore-DD7jKrCO.cjs +146 -0
  21. package/dist/SSMStateStore-DD7jKrCO.cjs.map +1 -0
  22. package/dist/SSMStateStore-oda9xOGc.mjs +147 -0
  23. package/dist/SSMStateStore-oda9xOGc.mjs.map +1 -0
  24. package/dist/{backends-Dfeu5Dgc.mjs → backends-BcHL7Gy_.mjs} +2 -2
  25. package/dist/{backends-Dfeu5Dgc.mjs.map → backends-BcHL7Gy_.mjs.map} +1 -1
  26. package/dist/{backends-D5aDWxuA.cjs → backends-_gQdAzBK.cjs} +2 -2
  27. package/dist/{backends-D5aDWxuA.cjs.map → backends-_gQdAzBK.cjs.map} +1 -1
  28. package/dist/{bundler--fyybf1N.mjs → bundler-URImKj-A.mjs} +2 -2
  29. package/dist/{bundler--fyybf1N.mjs.map → bundler-URImKj-A.mjs.map} +1 -1
  30. package/dist/{config-SOkDH-n1.mjs → config-20V08LZp.mjs} +2 -3
  31. package/dist/config-20V08LZp.mjs.map +1 -0
  32. package/dist/{config-Cvr5fCbn.cjs → config-CP6wsLHA.cjs} +2 -3
  33. package/dist/config-CP6wsLHA.cjs.map +1 -0
  34. package/dist/config.cjs +2 -2
  35. package/dist/config.d.cts +2 -2
  36. package/dist/config.d.mts +2 -2
  37. package/dist/config.mjs +2 -2
  38. package/dist/{encryption-tB4LqLKN.cjs → encryption-10Srqiix.cjs} +4 -4
  39. package/dist/{encryption-tB4LqLKN.cjs.map → encryption-10Srqiix.cjs.map} +1 -1
  40. package/dist/{encryption-O-XdeNxe.mjs → encryption-KQTYFEzU.mjs} +4 -4
  41. package/dist/{encryption-O-XdeNxe.mjs.map → encryption-KQTYFEzU.mjs.map} +1 -1
  42. package/dist/{github-BGx4U61b.cjs → github-DtRUkHN8.cjs} +3 -3
  43. package/dist/{github-BGx4U61b.cjs.map → github-DtRUkHN8.cjs.map} +1 -1
  44. package/dist/{github-U_frvZgD.mjs → github-y7OEj59M.mjs} +3 -3
  45. package/dist/{github-U_frvZgD.mjs.map → github-y7OEj59M.mjs.map} +1 -1
  46. package/dist/{index-fU3qHy7c.d.cts → index-DMcjpvd-.d.cts} +2 -2
  47. package/dist/{index-DYh5TrsZ.d.mts.map → index-DMcjpvd-.d.cts.map} +1 -1
  48. package/dist/{index-DYh5TrsZ.d.mts → index-RM92lx8A.d.mts} +2 -2
  49. package/dist/{index-fU3qHy7c.d.cts.map → index-RM92lx8A.d.mts.map} +1 -1
  50. package/dist/index.cjs +1176 -1281
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.mjs +1174 -1287
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/{migrate-DWDaaBfo.mjs → migrate-CS3DkO6y.mjs} +4 -4
  55. package/dist/{migrate-DWDaaBfo.mjs.map → migrate-CS3DkO6y.mjs.map} +1 -1
  56. package/dist/{migrate-DN5VM5vy.cjs → migrate-DQ54y2WD.cjs} +4 -4
  57. package/dist/{migrate-DN5VM5vy.cjs.map → migrate-DQ54y2WD.cjs.map} +1 -1
  58. package/dist/openapi.cjs +3 -3
  59. package/dist/openapi.d.cts +1 -1
  60. package/dist/openapi.d.mts +1 -1
  61. package/dist/openapi.mjs +3 -3
  62. package/dist/{ready-C8wG_d0v.mjs → ready-BSpUZnNo.mjs} +2 -2
  63. package/dist/ready-BSpUZnNo.mjs.map +1 -0
  64. package/dist/{ready-VkNwfJu7.cjs → ready-MUhVJDHe.cjs} +2 -2
  65. package/dist/ready-MUhVJDHe.cjs.map +1 -0
  66. package/dist/reconcile/public.d.cts +1 -1
  67. package/dist/reconcile/public.d.mts +1 -1
  68. package/dist/{types-CT0ymlBk.d.mts → types-BIyIQUR-.d.cts} +138 -33
  69. package/dist/types-BIyIQUR-.d.cts.map +1 -0
  70. package/dist/{types-DYDT4FfE.d.cts → types-CmQl6zaP.d.mts} +137 -34
  71. package/dist/types-CmQl6zaP.d.mts.map +1 -0
  72. package/dist/vitest.cjs +1 -1
  73. package/dist/vitest.mjs +1 -1
  74. package/dist/workspace/index.cjs +1 -1
  75. package/dist/workspace/index.d.cts +2 -2
  76. package/dist/workspace/index.d.mts +2 -2
  77. package/dist/workspace/index.mjs +1 -1
  78. package/dist/{workspace-DFYu-iRP.cjs → workspace-BbPJVCto.cjs} +6 -6
  79. package/dist/{workspace-DFYu-iRP.cjs.map → workspace-BbPJVCto.cjs.map} +1 -1
  80. package/dist/{workspace-BcCiLzTO.cjs → workspace-BpdXIeWV.cjs} +22 -18
  81. package/dist/{workspace-BcCiLzTO.cjs.map → workspace-BpdXIeWV.cjs.map} +1 -1
  82. package/dist/{workspace-bOsks1uk.mjs → workspace-C2mFV53i.mjs} +22 -18
  83. package/dist/{workspace-bOsks1uk.mjs.map → workspace-C2mFV53i.mjs.map} +1 -1
  84. package/dist/{workspace-Bwl0728I.mjs → workspace-CgWcE_Yr.mjs} +8 -8
  85. package/dist/{workspace-Bwl0728I.mjs.map → workspace-CgWcE_Yr.mjs.map} +1 -1
  86. package/package.json +13 -14
  87. package/dist/OpenApiTsGenerator-BN0PIZxd.cjs.map +0 -1
  88. package/dist/config-Cvr5fCbn.cjs.map +0 -1
  89. package/dist/config-SOkDH-n1.mjs.map +0 -1
  90. package/dist/ready-C8wG_d0v.mjs.map +0 -1
  91. package/dist/ready-VkNwfJu7.cjs.map +0 -1
  92. package/dist/types-CT0ymlBk.d.mts.map +0 -1
  93. package/dist/types-DYDT4FfE.d.cts.map +0 -1
@@ -0,0 +1,160 @@
1
+ const require_index = require("./index.cjs");
2
+ let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
3
+ //#region src/deploy/S3StateStore.ts
4
+ /**
5
+ * Amazon S3 state store.
6
+ *
7
+ * - `<prefix>/<workspace>/<stage>/state.json`: the state.
8
+ * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.
9
+ * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on
10
+ * migration.
11
+ *
12
+ * Every write is conditional on the server: `If-None-Match: *` to create,
13
+ * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when
14
+ * two conditional writes are in flight at once), so there is no window
15
+ * between a check and a write for another run to slip into.
16
+ */
17
+ /** A conditional write that lost: the object is not what the write assumed. */
18
+ function lostRace(error) {
19
+ const { name, $metadata } = error ?? {};
20
+ return name === "PreconditionFailed" || name === "ConditionalRequestConflict" || $metadata?.httpStatusCode === 412 || $metadata?.httpStatusCode === 409;
21
+ }
22
+ function notFound(error) {
23
+ const { name, $metadata } = error ?? {};
24
+ return name === "NoSuchKey" || name === "NotFound" || $metadata?.httpStatusCode === 404;
25
+ }
26
+ var S3StateStore = class S3StateStore extends require_index.DocumentStateStore {
27
+ workspaceName;
28
+ bucket;
29
+ client;
30
+ prefix;
31
+ constructor(workspaceName, bucket, client, prefix = "gkm") {
32
+ super();
33
+ this.workspaceName = workspaceName;
34
+ this.bucket = bucket;
35
+ this.client = client;
36
+ this.prefix = prefix.replace(/\/+$/, "");
37
+ }
38
+ static create(options) {
39
+ const config = {
40
+ region: options.region,
41
+ endpoint: options.endpoint,
42
+ forcePathStyle: options.endpoint ? true : void 0
43
+ };
44
+ if (options.profile) {
45
+ const { fromIni } = require("@aws-sdk/credential-providers");
46
+ config.credentials = fromIni({ profile: options.profile });
47
+ } else if (options.credentials) config.credentials = options.credentials;
48
+ return new S3StateStore(options.workspaceName, options.bucket, new _aws_sdk_client_s3.S3Client(config), options.prefix);
49
+ }
50
+ key(stage, leaf) {
51
+ const path = `${this.workspaceName}/${stage}/${leaf}`;
52
+ return this.prefix ? `${this.prefix}/${path}` : path;
53
+ }
54
+ location(stage) {
55
+ return `s3://${this.bucket}/${this.key(stage, "state.json")}`;
56
+ }
57
+ async readRaw(stage) {
58
+ const object = await this.get(this.key(stage, "state.json"));
59
+ if (!object) return null;
60
+ return {
61
+ body: object.body,
62
+ version: object.etag
63
+ };
64
+ }
65
+ async writeRaw(stage, body, expectedVersion) {
66
+ const Key = this.key(stage, "state.json");
67
+ try {
68
+ const { ETag } = await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
69
+ Bucket: this.bucket,
70
+ Key,
71
+ Body: body,
72
+ ContentType: "application/json",
73
+ ...expectedVersion === null ? { IfNoneMatch: "*" } : { IfMatch: expectedVersion }
74
+ }));
75
+ if (ETag) return ETag;
76
+ return (await this.get(Key))?.etag ?? "";
77
+ } catch (error) {
78
+ if (lostRace(error)) {
79
+ const current = await this.get(Key);
80
+ throw new require_index.StateVersionConflict(stage, expectedVersion, current?.etag ?? null);
81
+ }
82
+ throw error;
83
+ }
84
+ }
85
+ async writeV1Backup(stage, body) {
86
+ try {
87
+ await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
88
+ Bucket: this.bucket,
89
+ Key: this.key(stage, "state.v1.json"),
90
+ Body: body,
91
+ ContentType: "application/json",
92
+ IfNoneMatch: "*"
93
+ }));
94
+ } catch (error) {
95
+ if (!lostRace(error)) throw error;
96
+ }
97
+ }
98
+ async createLock(stage, holder) {
99
+ const Key = this.key(stage, "lock.json");
100
+ try {
101
+ await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
102
+ Bucket: this.bucket,
103
+ Key,
104
+ Body: JSON.stringify(holder),
105
+ ContentType: "application/json",
106
+ IfNoneMatch: "*"
107
+ }));
108
+ } catch (error) {
109
+ if (lostRace(error)) throw new require_index.StateLocked(stage, await this.readLock(stage), `s3://${this.bucket}/${Key}`);
110
+ throw error;
111
+ }
112
+ }
113
+ async readLock(stage) {
114
+ const object = await this.get(this.key(stage, "lock.json"));
115
+ if (!object) return null;
116
+ try {
117
+ return JSON.parse(object.body);
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ async removeLock(stage, holder) {
123
+ const Key = this.key(stage, "lock.json");
124
+ const object = await this.get(Key);
125
+ if (!object) return;
126
+ if (holder) try {
127
+ if (JSON.parse(object.body).id !== holder.id) return;
128
+ } catch {
129
+ return;
130
+ }
131
+ try {
132
+ await this.client.send(new _aws_sdk_client_s3.DeleteObjectCommand({
133
+ Bucket: this.bucket,
134
+ Key,
135
+ IfMatch: object.etag
136
+ }));
137
+ } catch (error) {
138
+ if (!lostRace(error) && !notFound(error)) throw error;
139
+ }
140
+ }
141
+ async get(Key) {
142
+ try {
143
+ const response = await this.client.send(new _aws_sdk_client_s3.GetObjectCommand({
144
+ Bucket: this.bucket,
145
+ Key
146
+ }));
147
+ return {
148
+ body: await response.Body?.transformToString("utf-8") ?? "",
149
+ etag: response.ETag ?? ""
150
+ };
151
+ } catch (error) {
152
+ if (notFound(error)) return null;
153
+ throw error;
154
+ }
155
+ }
156
+ };
157
+ //#endregion
158
+ exports.S3StateStore = S3StateStore;
159
+
160
+ //# sourceMappingURL=S3StateStore-C7eBae0G.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"S3StateStore-C7eBae0G.cjs","names":["DocumentStateStore","S3Client","PutObjectCommand","StateVersionConflict","StateLocked","DeleteObjectCommand","GetObjectCommand"],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,161 @@
1
+ import { n as __require } from "./rolldown-runtime-BhDjJH2R.mjs";
2
+ import { i as DocumentStateStore, l as StateVersionConflict, o as StateLocked } from "./index.mjs";
3
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
4
+ //#region src/deploy/S3StateStore.ts
5
+ /**
6
+ * Amazon S3 state store.
7
+ *
8
+ * - `<prefix>/<workspace>/<stage>/state.json`: the state.
9
+ * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.
10
+ * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on
11
+ * migration.
12
+ *
13
+ * Every write is conditional on the server: `If-None-Match: *` to create,
14
+ * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when
15
+ * two conditional writes are in flight at once), so there is no window
16
+ * between a check and a write for another run to slip into.
17
+ */
18
+ /** A conditional write that lost: the object is not what the write assumed. */
19
+ function lostRace(error) {
20
+ const { name, $metadata } = error ?? {};
21
+ return name === "PreconditionFailed" || name === "ConditionalRequestConflict" || $metadata?.httpStatusCode === 412 || $metadata?.httpStatusCode === 409;
22
+ }
23
+ function notFound(error) {
24
+ const { name, $metadata } = error ?? {};
25
+ return name === "NoSuchKey" || name === "NotFound" || $metadata?.httpStatusCode === 404;
26
+ }
27
+ var S3StateStore = class S3StateStore extends DocumentStateStore {
28
+ workspaceName;
29
+ bucket;
30
+ client;
31
+ prefix;
32
+ constructor(workspaceName, bucket, client, prefix = "gkm") {
33
+ super();
34
+ this.workspaceName = workspaceName;
35
+ this.bucket = bucket;
36
+ this.client = client;
37
+ this.prefix = prefix.replace(/\/+$/, "");
38
+ }
39
+ static create(options) {
40
+ const config = {
41
+ region: options.region,
42
+ endpoint: options.endpoint,
43
+ forcePathStyle: options.endpoint ? true : void 0
44
+ };
45
+ if (options.profile) {
46
+ const { fromIni } = __require("@aws-sdk/credential-providers");
47
+ config.credentials = fromIni({ profile: options.profile });
48
+ } else if (options.credentials) config.credentials = options.credentials;
49
+ return new S3StateStore(options.workspaceName, options.bucket, new S3Client(config), options.prefix);
50
+ }
51
+ key(stage, leaf) {
52
+ const path = `${this.workspaceName}/${stage}/${leaf}`;
53
+ return this.prefix ? `${this.prefix}/${path}` : path;
54
+ }
55
+ location(stage) {
56
+ return `s3://${this.bucket}/${this.key(stage, "state.json")}`;
57
+ }
58
+ async readRaw(stage) {
59
+ const object = await this.get(this.key(stage, "state.json"));
60
+ if (!object) return null;
61
+ return {
62
+ body: object.body,
63
+ version: object.etag
64
+ };
65
+ }
66
+ async writeRaw(stage, body, expectedVersion) {
67
+ const Key = this.key(stage, "state.json");
68
+ try {
69
+ const { ETag } = await this.client.send(new PutObjectCommand({
70
+ Bucket: this.bucket,
71
+ Key,
72
+ Body: body,
73
+ ContentType: "application/json",
74
+ ...expectedVersion === null ? { IfNoneMatch: "*" } : { IfMatch: expectedVersion }
75
+ }));
76
+ if (ETag) return ETag;
77
+ return (await this.get(Key))?.etag ?? "";
78
+ } catch (error) {
79
+ if (lostRace(error)) {
80
+ const current = await this.get(Key);
81
+ throw new StateVersionConflict(stage, expectedVersion, current?.etag ?? null);
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+ async writeV1Backup(stage, body) {
87
+ try {
88
+ await this.client.send(new PutObjectCommand({
89
+ Bucket: this.bucket,
90
+ Key: this.key(stage, "state.v1.json"),
91
+ Body: body,
92
+ ContentType: "application/json",
93
+ IfNoneMatch: "*"
94
+ }));
95
+ } catch (error) {
96
+ if (!lostRace(error)) throw error;
97
+ }
98
+ }
99
+ async createLock(stage, holder) {
100
+ const Key = this.key(stage, "lock.json");
101
+ try {
102
+ await this.client.send(new PutObjectCommand({
103
+ Bucket: this.bucket,
104
+ Key,
105
+ Body: JSON.stringify(holder),
106
+ ContentType: "application/json",
107
+ IfNoneMatch: "*"
108
+ }));
109
+ } catch (error) {
110
+ if (lostRace(error)) throw new StateLocked(stage, await this.readLock(stage), `s3://${this.bucket}/${Key}`);
111
+ throw error;
112
+ }
113
+ }
114
+ async readLock(stage) {
115
+ const object = await this.get(this.key(stage, "lock.json"));
116
+ if (!object) return null;
117
+ try {
118
+ return JSON.parse(object.body);
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+ async removeLock(stage, holder) {
124
+ const Key = this.key(stage, "lock.json");
125
+ const object = await this.get(Key);
126
+ if (!object) return;
127
+ if (holder) try {
128
+ if (JSON.parse(object.body).id !== holder.id) return;
129
+ } catch {
130
+ return;
131
+ }
132
+ try {
133
+ await this.client.send(new DeleteObjectCommand({
134
+ Bucket: this.bucket,
135
+ Key,
136
+ IfMatch: object.etag
137
+ }));
138
+ } catch (error) {
139
+ if (!lostRace(error) && !notFound(error)) throw error;
140
+ }
141
+ }
142
+ async get(Key) {
143
+ try {
144
+ const response = await this.client.send(new GetObjectCommand({
145
+ Bucket: this.bucket,
146
+ Key
147
+ }));
148
+ return {
149
+ body: await response.Body?.transformToString("utf-8") ?? "",
150
+ etag: response.ETag ?? ""
151
+ };
152
+ } catch (error) {
153
+ if (notFound(error)) return null;
154
+ throw error;
155
+ }
156
+ }
157
+ };
158
+ //#endregion
159
+ export { S3StateStore };
160
+
161
+ //# sourceMappingURL=S3StateStore-FhzyENrN.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"S3StateStore-FhzyENrN.mjs","names":[],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ const require_index = require("./index.cjs");
2
+ let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
3
+ //#region src/deploy/SSMStateStore.ts
4
+ /**
5
+ * AWS SSM Parameter Store state store.
6
+ *
7
+ * - `/gkm/<workspace>/<stage>/state`: the state, a SecureString.
8
+ * - `/gkm/<workspace>/<stage>/lock`: the lock, created only if absent.
9
+ * - `/gkm/<workspace>/<stage>/state.v1`: the v1 state, kept on migration.
10
+ *
11
+ * SSM has no conditional put, so a write checks the parameter's version
12
+ * before it puts, and checks the version the put returns after: a put that
13
+ * did not land exactly one version above the one it was based on raced
14
+ * another writer, and raises `StateVersionConflict` rather than reporting
15
+ * success. Both writes stay in the parameter's history.
16
+ */
17
+ var SSMStateStore = class SSMStateStore extends require_index.DocumentStateStore {
18
+ workspaceName;
19
+ client;
20
+ constructor(workspaceName, client) {
21
+ super();
22
+ this.workspaceName = workspaceName;
23
+ this.client = client;
24
+ }
25
+ static create(options) {
26
+ const config = {
27
+ region: options.region,
28
+ endpoint: options.endpoint
29
+ };
30
+ if (options.profile) {
31
+ const { fromIni } = require("@aws-sdk/credential-providers");
32
+ config.credentials = fromIni({ profile: options.profile });
33
+ } else if (options.credentials) config.credentials = options.credentials;
34
+ return new SSMStateStore(options.workspaceName, new _aws_sdk_client_ssm.SSMClient(config));
35
+ }
36
+ name(stage, leaf) {
37
+ return `/gkm/${this.workspaceName}/${stage}/${leaf}`;
38
+ }
39
+ location(stage) {
40
+ return `ssm:${this.name(stage, "state")}`;
41
+ }
42
+ async readRaw(stage) {
43
+ const parameter = await this.get(this.name(stage, "state"));
44
+ if (!parameter) return null;
45
+ return {
46
+ body: parameter.value,
47
+ version: String(parameter.version)
48
+ };
49
+ }
50
+ async writeRaw(stage, body, expectedVersion) {
51
+ const Name = this.name(stage, "state");
52
+ const Description = `GKM deployment state for ${this.workspaceName}/${stage}`;
53
+ if (expectedVersion === null) try {
54
+ const { Version } = await this.client.send(new _aws_sdk_client_ssm.PutParameterCommand({
55
+ Name,
56
+ Value: body,
57
+ Type: "SecureString",
58
+ Description
59
+ }));
60
+ return String(Version);
61
+ } catch (error) {
62
+ if (error instanceof _aws_sdk_client_ssm.ParameterAlreadyExists) {
63
+ const current = await this.get(Name);
64
+ throw new require_index.StateVersionConflict(stage, null, current ? String(current.version) : null);
65
+ }
66
+ throw error;
67
+ }
68
+ const current = await this.get(Name);
69
+ const actual = current ? String(current.version) : null;
70
+ if (actual !== expectedVersion) throw new require_index.StateVersionConflict(stage, expectedVersion, actual);
71
+ const { Version } = await this.client.send(new _aws_sdk_client_ssm.PutParameterCommand({
72
+ Name,
73
+ Value: body,
74
+ Type: "SecureString",
75
+ Overwrite: true,
76
+ Description
77
+ }));
78
+ if (Version !== Number(expectedVersion) + 1) throw new require_index.StateVersionConflict(stage, expectedVersion, String((Version ?? 1) - 1));
79
+ return String(Version);
80
+ }
81
+ async writeV1Backup(stage, body) {
82
+ try {
83
+ await this.client.send(new _aws_sdk_client_ssm.PutParameterCommand({
84
+ Name: this.name(stage, "state.v1"),
85
+ Value: body,
86
+ Type: "SecureString",
87
+ Description: `GKM v1 deployment state for ${this.workspaceName}/${stage}, kept on migration`
88
+ }));
89
+ } catch (error) {
90
+ if (!(error instanceof _aws_sdk_client_ssm.ParameterAlreadyExists)) throw error;
91
+ }
92
+ }
93
+ async createLock(stage, holder) {
94
+ const Name = this.name(stage, "lock");
95
+ try {
96
+ await this.client.send(new _aws_sdk_client_ssm.PutParameterCommand({
97
+ Name,
98
+ Value: JSON.stringify(holder),
99
+ Type: "String",
100
+ Description: `GKM deploy lock for ${this.workspaceName}/${stage}`
101
+ }));
102
+ } catch (error) {
103
+ if (error instanceof _aws_sdk_client_ssm.ParameterAlreadyExists) throw new require_index.StateLocked(stage, await this.readLock(stage), `ssm:${Name}`);
104
+ throw error;
105
+ }
106
+ }
107
+ async readLock(stage) {
108
+ const parameter = await this.get(this.name(stage, "lock"));
109
+ if (!parameter) return null;
110
+ try {
111
+ return JSON.parse(parameter.value);
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ async removeLock(stage, holder) {
117
+ if (holder) {
118
+ if ((await this.readLock(stage))?.id !== holder.id) return;
119
+ }
120
+ try {
121
+ await this.client.send(new _aws_sdk_client_ssm.DeleteParameterCommand({ Name: this.name(stage, "lock") }));
122
+ } catch (error) {
123
+ if (!(error instanceof _aws_sdk_client_ssm.ParameterNotFound)) throw error;
124
+ }
125
+ }
126
+ async get(Name) {
127
+ try {
128
+ const { Parameter } = await this.client.send(new _aws_sdk_client_ssm.GetParameterCommand({
129
+ Name,
130
+ WithDecryption: true
131
+ }));
132
+ if (Parameter?.Value === void 0) return null;
133
+ return {
134
+ value: Parameter.Value,
135
+ version: Parameter.Version ?? 1
136
+ };
137
+ } catch (error) {
138
+ if (error instanceof _aws_sdk_client_ssm.ParameterNotFound) return null;
139
+ throw error;
140
+ }
141
+ }
142
+ };
143
+ //#endregion
144
+ exports.SSMStateStore = SSMStateStore;
145
+
146
+ //# sourceMappingURL=SSMStateStore-DD7jKrCO.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SSMStateStore-DD7jKrCO.cjs","names":["DocumentStateStore","SSMClient","PutParameterCommand","ParameterAlreadyExists","StateVersionConflict","StateLocked","DeleteParameterCommand","ParameterNotFound","GetParameterCommand"],"sources":["../src/deploy/SSMStateStore.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store state store.\n *\n * - `/gkm/<workspace>/<stage>/state`: the state, a SecureString.\n * - `/gkm/<workspace>/<stage>/lock`: the lock, created only if absent.\n * - `/gkm/<workspace>/<stage>/state.v1`: the v1 state, kept on migration.\n *\n * SSM has no conditional put, so a write checks the parameter's version\n * before it puts, and checks the version the put returns after: a put that\n * did not land exactly one version above the one it was based on raced\n * another writer, and raises `StateVersionConflict` rather than reporting\n * success. Both writes stay in the parameter's history.\n */\n\nimport {\n\tDeleteParameterCommand,\n\tGetParameterCommand,\n\tParameterAlreadyExists,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface SSMStateStoreOptions {\n\tworkspaceName: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\nexport class SSMStateStore extends DocumentStateStore {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {\n\t\tsuper();\n\t}\n\n\tstatic create(options: SSMStateStoreOptions): SSMStateStore {\n\t\tconst config: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new SSMStateStore(options.workspaceName, new SSMClient(config));\n\t}\n\n\tprivate name(stage: string, leaf: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/${leaf}`;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `ssm:${this.name(stage, 'state')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'state'));\n\t\tif (!parameter) return null;\n\t\treturn { body: parameter.value, version: String(parameter.version) };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Name = this.name(stage, 'state');\n\t\tconst Description = `GKM deployment state for ${this.workspaceName}/${stage}`;\n\n\t\tif (expectedVersion === null) {\n\t\t\ttry {\n\t\t\t\tconst { Version } = await this.client.send(\n\t\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\t\tName,\n\t\t\t\t\t\tValue: body,\n\t\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\t\tDescription,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn String(Version);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\t\tconst current = await this.get(Name);\n\t\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\t\tstage,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tcurrent ? String(current.version) : null,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tconst current = await this.get(Name);\n\t\tconst actual = current ? String(current.version) : null;\n\t\tif (actual !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t}\n\n\t\tconst { Version } = await this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName,\n\t\t\t\tValue: body,\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription,\n\t\t\t}),\n\t\t);\n\t\tif (Version !== Number(expectedVersion) + 1) {\n\t\t\t// Another put landed between the check and this one. Ours is now\n\t\t\t// on top, but the caller that wrote the other believes its write\n\t\t\t// is current — its next write will conflict too, so neither is\n\t\t\t// lost silently.\n\t\t\tthrow new StateVersionConflict(\n\t\t\t\tstage,\n\t\t\t\texpectedVersion,\n\t\t\t\tString((Version ?? 1) - 1),\n\t\t\t);\n\t\t}\n\t\treturn String(Version);\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName: this.name(stage, 'state.v1'),\n\t\t\t\t\tValue: body,\n\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\tDescription: `GKM v1 deployment state for ${this.workspaceName}/${stage}, kept on migration`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!(error instanceof ParameterAlreadyExists)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Name = this.name(stage, 'lock');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName,\n\t\t\t\t\tValue: JSON.stringify(holder),\n\t\t\t\t\tType: 'String',\n\t\t\t\t\tDescription: `GKM deploy lock for ${this.workspaceName}/${stage}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\tthrow new StateLocked(stage, await this.readLock(stage), `ssm:${Name}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'lock'));\n\t\tif (!parameter) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(parameter.value) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteParameterCommand({ Name: this.name(stage, 'lock') }),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof ParameterNotFound)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tName: string,\n\t): Promise<{ value: string; version: number } | null> {\n\t\ttry {\n\t\t\tconst { Parameter } = await this.client.send(\n\t\t\t\tnew GetParameterCommand({ Name, WithDecryption: true }),\n\t\t\t);\n\t\t\tif (Parameter?.Value === undefined) return null;\n\t\t\treturn { value: Parameter.Value, version: Parameter.Version ?? 1 };\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterNotFound) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}