@hot-updater/aws 0.36.7 → 1.0.0-rc.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.
- package/dist/iac/index.cjs +2670 -824
- package/dist/iac/index.d.cts +15 -1
- package/dist/iac/index.d.mts +15 -1
- package/dist/iac/index.mjs +2669 -825
- package/dist/index.cjs +1916 -1853
- package/dist/index.d.cts +37 -46
- package/dist/index.d.mts +39 -47
- package/dist/index.mjs +1916 -1828
- package/dist/init/index.cjs +13 -13
- package/dist/init/index.d.cts +13 -13
- package/dist/init/index.d.mts +13 -13
- package/dist/init/index.mjs +13 -13
- package/dist/lambda/index.cjs +6468 -4308
- package/dist/lambda/index.d.cts +4 -4
- package/package.json +9 -9
package/dist/index.mjs
CHANGED
|
@@ -1,1394 +1,1785 @@
|
|
|
1
1
|
import { CloudFrontClient, CreateInvalidationCommand, GetInvalidationCommand } from "@aws-sdk/client-cloudfront";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { Upload } from "@aws-sdk/lib-storage";
|
|
7
|
-
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
2
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
3
|
+
import { BatchGetCommand, DynamoDBDocumentClient, GetCommand, PutCommand, QueryCommand, ScanCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
|
|
4
|
+
import { createDatabasePlugin, createStorageDownloadUrl, createStorageKeyBuilder, createStoragePlugin, createStorageUri, isDatabaseMetadataObject, parseStorageUri } from "@hot-updater/plugin-core";
|
|
5
|
+
import { createDatabasePluginAdapter } from "@hot-updater/plugin-core/internal";
|
|
8
6
|
import { SSM } from "@aws-sdk/client-ssm";
|
|
9
|
-
import { getSignedUrl
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"application/vnd.apple.keynote": ["key"],
|
|
42
|
-
"application/vnd.apple.mpegurl": ["m3u8"],
|
|
43
|
-
"application/vnd.apple.numbers": ["numbers"],
|
|
44
|
-
"application/vnd.apple.pages": ["pages"],
|
|
45
|
-
"application/vnd.apple.pkpass": ["pkpass"],
|
|
46
|
-
"application/vnd.aristanetworks.swi": ["swi"],
|
|
47
|
-
"application/vnd.astraea-software.iota": ["iota"],
|
|
48
|
-
"application/vnd.audiograph": ["aep"],
|
|
49
|
-
"application/vnd.autodesk.fbx": ["fbx"],
|
|
50
|
-
"application/vnd.balsamiq.bmml+xml": ["bmml"],
|
|
51
|
-
"application/vnd.blueice.multipass": ["mpm"],
|
|
52
|
-
"application/vnd.bmi": ["bmi"],
|
|
53
|
-
"application/vnd.businessobjects": ["rep"],
|
|
54
|
-
"application/vnd.chemdraw+xml": ["cdxml"],
|
|
55
|
-
"application/vnd.chipnuts.karaoke-mmd": ["mmd"],
|
|
56
|
-
"application/vnd.cinderella": ["cdy"],
|
|
57
|
-
"application/vnd.citationstyles.style+xml": ["csl"],
|
|
58
|
-
"application/vnd.claymore": ["cla"],
|
|
59
|
-
"application/vnd.cloanto.rp9": ["rp9"],
|
|
60
|
-
"application/vnd.clonk.c4group": [
|
|
61
|
-
"c4g",
|
|
62
|
-
"c4d",
|
|
63
|
-
"c4f",
|
|
64
|
-
"c4p",
|
|
65
|
-
"c4u"
|
|
66
|
-
],
|
|
67
|
-
"application/vnd.cluetrust.cartomobile-config": ["c11amc"],
|
|
68
|
-
"application/vnd.cluetrust.cartomobile-config-pkg": ["c11amz"],
|
|
69
|
-
"application/vnd.commonspace": ["csp"],
|
|
70
|
-
"application/vnd.contact.cmsg": ["cdbcmsg"],
|
|
71
|
-
"application/vnd.cosmocaller": ["cmc"],
|
|
72
|
-
"application/vnd.crick.clicker": ["clkx"],
|
|
73
|
-
"application/vnd.crick.clicker.keyboard": ["clkk"],
|
|
74
|
-
"application/vnd.crick.clicker.palette": ["clkp"],
|
|
75
|
-
"application/vnd.crick.clicker.template": ["clkt"],
|
|
76
|
-
"application/vnd.crick.clicker.wordbank": ["clkw"],
|
|
77
|
-
"application/vnd.criticaltools.wbs+xml": ["wbs"],
|
|
78
|
-
"application/vnd.ctc-posml": ["pml"],
|
|
79
|
-
"application/vnd.cups-ppd": ["ppd"],
|
|
80
|
-
"application/vnd.curl.car": ["car"],
|
|
81
|
-
"application/vnd.curl.pcurl": ["pcurl"],
|
|
82
|
-
"application/vnd.dart": ["dart"],
|
|
83
|
-
"application/vnd.data-vision.rdz": ["rdz"],
|
|
84
|
-
"application/vnd.dbf": ["dbf"],
|
|
85
|
-
"application/vnd.dcmp+xml": ["dcmp"],
|
|
86
|
-
"application/vnd.dece.data": [
|
|
87
|
-
"uvf",
|
|
88
|
-
"uvvf",
|
|
89
|
-
"uvd",
|
|
90
|
-
"uvvd"
|
|
91
|
-
],
|
|
92
|
-
"application/vnd.dece.ttml+xml": ["uvt", "uvvt"],
|
|
93
|
-
"application/vnd.dece.unspecified": ["uvx", "uvvx"],
|
|
94
|
-
"application/vnd.dece.zip": ["uvz", "uvvz"],
|
|
95
|
-
"application/vnd.denovo.fcselayout-link": ["fe_launch"],
|
|
96
|
-
"application/vnd.dna": ["dna"],
|
|
97
|
-
"application/vnd.dolby.mlp": ["mlp"],
|
|
98
|
-
"application/vnd.dpgraph": ["dpg"],
|
|
99
|
-
"application/vnd.dreamfactory": ["dfac"],
|
|
100
|
-
"application/vnd.ds-keypoint": ["kpxx"],
|
|
101
|
-
"application/vnd.dvb.ait": ["ait"],
|
|
102
|
-
"application/vnd.dvb.service": ["svc"],
|
|
103
|
-
"application/vnd.dynageo": ["geo"],
|
|
104
|
-
"application/vnd.ecowin.chart": ["mag"],
|
|
105
|
-
"application/vnd.enliven": ["nml"],
|
|
106
|
-
"application/vnd.epson.esf": ["esf"],
|
|
107
|
-
"application/vnd.epson.msf": ["msf"],
|
|
108
|
-
"application/vnd.epson.quickanime": ["qam"],
|
|
109
|
-
"application/vnd.epson.salt": ["slt"],
|
|
110
|
-
"application/vnd.epson.ssf": ["ssf"],
|
|
111
|
-
"application/vnd.eszigno3+xml": ["es3", "et3"],
|
|
112
|
-
"application/vnd.ezpix-album": ["ez2"],
|
|
113
|
-
"application/vnd.ezpix-package": ["ez3"],
|
|
114
|
-
"application/vnd.fdf": ["*fdf"],
|
|
115
|
-
"application/vnd.fdsn.mseed": ["mseed"],
|
|
116
|
-
"application/vnd.fdsn.seed": ["seed", "dataless"],
|
|
117
|
-
"application/vnd.flographit": ["gph"],
|
|
118
|
-
"application/vnd.fluxtime.clip": ["ftc"],
|
|
119
|
-
"application/vnd.framemaker": [
|
|
120
|
-
"fm",
|
|
121
|
-
"frame",
|
|
122
|
-
"maker",
|
|
123
|
-
"book"
|
|
124
|
-
],
|
|
125
|
-
"application/vnd.frogans.fnc": ["fnc"],
|
|
126
|
-
"application/vnd.frogans.ltf": ["ltf"],
|
|
127
|
-
"application/vnd.fsc.weblaunch": ["fsc"],
|
|
128
|
-
"application/vnd.fujitsu.oasys": ["oas"],
|
|
129
|
-
"application/vnd.fujitsu.oasys2": ["oa2"],
|
|
130
|
-
"application/vnd.fujitsu.oasys3": ["oa3"],
|
|
131
|
-
"application/vnd.fujitsu.oasysgp": ["fg5"],
|
|
132
|
-
"application/vnd.fujitsu.oasysprs": ["bh2"],
|
|
133
|
-
"application/vnd.fujixerox.ddd": ["ddd"],
|
|
134
|
-
"application/vnd.fujixerox.docuworks": ["xdw"],
|
|
135
|
-
"application/vnd.fujixerox.docuworks.binder": ["xbd"],
|
|
136
|
-
"application/vnd.fuzzysheet": ["fzs"],
|
|
137
|
-
"application/vnd.genomatix.tuxedo": ["txd"],
|
|
138
|
-
"application/vnd.geogebra.file": ["ggb"],
|
|
139
|
-
"application/vnd.geogebra.slides": ["ggs"],
|
|
140
|
-
"application/vnd.geogebra.tool": ["ggt"],
|
|
141
|
-
"application/vnd.geometry-explorer": ["gex", "gre"],
|
|
142
|
-
"application/vnd.geonext": ["gxt"],
|
|
143
|
-
"application/vnd.geoplan": ["g2w"],
|
|
144
|
-
"application/vnd.geospace": ["g3w"],
|
|
145
|
-
"application/vnd.gmx": ["gmx"],
|
|
146
|
-
"application/vnd.google-apps.document": ["gdoc"],
|
|
147
|
-
"application/vnd.google-apps.drawing": ["gdraw"],
|
|
148
|
-
"application/vnd.google-apps.form": ["gform"],
|
|
149
|
-
"application/vnd.google-apps.jam": ["gjam"],
|
|
150
|
-
"application/vnd.google-apps.map": ["gmap"],
|
|
151
|
-
"application/vnd.google-apps.presentation": ["gslides"],
|
|
152
|
-
"application/vnd.google-apps.script": ["gscript"],
|
|
153
|
-
"application/vnd.google-apps.site": ["gsite"],
|
|
154
|
-
"application/vnd.google-apps.spreadsheet": ["gsheet"],
|
|
155
|
-
"application/vnd.google-earth.kml+xml": ["kml"],
|
|
156
|
-
"application/vnd.google-earth.kmz": ["kmz"],
|
|
157
|
-
"application/vnd.gov.sk.xmldatacontainer+xml": ["xdcf"],
|
|
158
|
-
"application/vnd.grafeq": ["gqf", "gqs"],
|
|
159
|
-
"application/vnd.groove-account": ["gac"],
|
|
160
|
-
"application/vnd.groove-help": ["ghf"],
|
|
161
|
-
"application/vnd.groove-identity-message": ["gim"],
|
|
162
|
-
"application/vnd.groove-injector": ["grv"],
|
|
163
|
-
"application/vnd.groove-tool-message": ["gtm"],
|
|
164
|
-
"application/vnd.groove-tool-template": ["tpl"],
|
|
165
|
-
"application/vnd.groove-vcard": ["vcg"],
|
|
166
|
-
"application/vnd.hal+xml": ["hal"],
|
|
167
|
-
"application/vnd.handheld-entertainment+xml": ["zmm"],
|
|
168
|
-
"application/vnd.hbci": ["hbci"],
|
|
169
|
-
"application/vnd.hhe.lesson-player": ["les"],
|
|
170
|
-
"application/vnd.hp-hpgl": ["hpgl"],
|
|
171
|
-
"application/vnd.hp-hpid": ["hpid"],
|
|
172
|
-
"application/vnd.hp-hps": ["hps"],
|
|
173
|
-
"application/vnd.hp-jlyt": ["jlt"],
|
|
174
|
-
"application/vnd.hp-pcl": ["pcl"],
|
|
175
|
-
"application/vnd.hp-pclxl": ["pclxl"],
|
|
176
|
-
"application/vnd.hydrostatix.sof-data": ["sfd-hdstx"],
|
|
177
|
-
"application/vnd.ibm.minipay": ["mpy"],
|
|
178
|
-
"application/vnd.ibm.modcap": [
|
|
179
|
-
"afp",
|
|
180
|
-
"listafp",
|
|
181
|
-
"list3820"
|
|
182
|
-
],
|
|
183
|
-
"application/vnd.ibm.rights-management": ["irm"],
|
|
184
|
-
"application/vnd.ibm.secure-container": ["sc"],
|
|
185
|
-
"application/vnd.iccprofile": ["icc", "icm"],
|
|
186
|
-
"application/vnd.igloader": ["igl"],
|
|
187
|
-
"application/vnd.immervision-ivp": ["ivp"],
|
|
188
|
-
"application/vnd.immervision-ivu": ["ivu"],
|
|
189
|
-
"application/vnd.insors.igm": ["igm"],
|
|
190
|
-
"application/vnd.intercon.formnet": ["xpw", "xpx"],
|
|
191
|
-
"application/vnd.intergeo": ["i2g"],
|
|
192
|
-
"application/vnd.intu.qbo": ["qbo"],
|
|
193
|
-
"application/vnd.intu.qfx": ["qfx"],
|
|
194
|
-
"application/vnd.ipunplugged.rcprofile": ["rcprofile"],
|
|
195
|
-
"application/vnd.irepository.package+xml": ["irp"],
|
|
196
|
-
"application/vnd.is-xpr": ["xpr"],
|
|
197
|
-
"application/vnd.isac.fcs": ["fcs"],
|
|
198
|
-
"application/vnd.jam": ["jam"],
|
|
199
|
-
"application/vnd.jcp.javame.midlet-rms": ["rms"],
|
|
200
|
-
"application/vnd.jisp": ["jisp"],
|
|
201
|
-
"application/vnd.joost.joda-archive": ["joda"],
|
|
202
|
-
"application/vnd.kahootz": ["ktz", "ktr"],
|
|
203
|
-
"application/vnd.kde.karbon": ["karbon"],
|
|
204
|
-
"application/vnd.kde.kchart": ["chrt"],
|
|
205
|
-
"application/vnd.kde.kformula": ["kfo"],
|
|
206
|
-
"application/vnd.kde.kivio": ["flw"],
|
|
207
|
-
"application/vnd.kde.kontour": ["kon"],
|
|
208
|
-
"application/vnd.kde.kpresenter": ["kpr", "kpt"],
|
|
209
|
-
"application/vnd.kde.kspread": ["ksp"],
|
|
210
|
-
"application/vnd.kde.kword": ["kwd", "kwt"],
|
|
211
|
-
"application/vnd.kenameaapp": ["htke"],
|
|
212
|
-
"application/vnd.kidspiration": ["kia"],
|
|
213
|
-
"application/vnd.kinar": ["kne", "knp"],
|
|
214
|
-
"application/vnd.koan": [
|
|
215
|
-
"skp",
|
|
216
|
-
"skd",
|
|
217
|
-
"skt",
|
|
218
|
-
"skm"
|
|
219
|
-
],
|
|
220
|
-
"application/vnd.kodak-descriptor": ["sse"],
|
|
221
|
-
"application/vnd.las.las+xml": ["lasxml"],
|
|
222
|
-
"application/vnd.llamagraphics.life-balance.desktop": ["lbd"],
|
|
223
|
-
"application/vnd.llamagraphics.life-balance.exchange+xml": ["lbe"],
|
|
224
|
-
"application/vnd.lotus-1-2-3": ["123"],
|
|
225
|
-
"application/vnd.lotus-approach": ["apr"],
|
|
226
|
-
"application/vnd.lotus-freelance": ["pre"],
|
|
227
|
-
"application/vnd.lotus-notes": ["nsf"],
|
|
228
|
-
"application/vnd.lotus-organizer": ["org"],
|
|
229
|
-
"application/vnd.lotus-screencam": ["scm"],
|
|
230
|
-
"application/vnd.lotus-wordpro": ["lwp"],
|
|
231
|
-
"application/vnd.macports.portpkg": ["portpkg"],
|
|
232
|
-
"application/vnd.mapbox-vector-tile": ["mvt"],
|
|
233
|
-
"application/vnd.mcd": ["mcd"],
|
|
234
|
-
"application/vnd.medcalcdata": ["mc1"],
|
|
235
|
-
"application/vnd.mediastation.cdkey": ["cdkey"],
|
|
236
|
-
"application/vnd.mfer": ["mwf"],
|
|
237
|
-
"application/vnd.mfmp": ["mfm"],
|
|
238
|
-
"application/vnd.micrografx.flo": ["flo"],
|
|
239
|
-
"application/vnd.micrografx.igx": ["igx"],
|
|
240
|
-
"application/vnd.mif": ["mif"],
|
|
241
|
-
"application/vnd.mobius.daf": ["daf"],
|
|
242
|
-
"application/vnd.mobius.dis": ["dis"],
|
|
243
|
-
"application/vnd.mobius.mbk": ["mbk"],
|
|
244
|
-
"application/vnd.mobius.mqy": ["mqy"],
|
|
245
|
-
"application/vnd.mobius.msl": ["msl"],
|
|
246
|
-
"application/vnd.mobius.plc": ["plc"],
|
|
247
|
-
"application/vnd.mobius.txf": ["txf"],
|
|
248
|
-
"application/vnd.mophun.application": ["mpn"],
|
|
249
|
-
"application/vnd.mophun.certificate": ["mpc"],
|
|
250
|
-
"application/vnd.mozilla.xul+xml": ["xul"],
|
|
251
|
-
"application/vnd.ms-artgalry": ["cil"],
|
|
252
|
-
"application/vnd.ms-cab-compressed": ["cab"],
|
|
253
|
-
"application/vnd.ms-excel": [
|
|
254
|
-
"xls",
|
|
255
|
-
"xlm",
|
|
256
|
-
"xla",
|
|
257
|
-
"xlc",
|
|
258
|
-
"xlt",
|
|
259
|
-
"xlw"
|
|
260
|
-
],
|
|
261
|
-
"application/vnd.ms-excel.addin.macroenabled.12": ["xlam"],
|
|
262
|
-
"application/vnd.ms-excel.sheet.binary.macroenabled.12": ["xlsb"],
|
|
263
|
-
"application/vnd.ms-excel.sheet.macroenabled.12": ["xlsm"],
|
|
264
|
-
"application/vnd.ms-excel.template.macroenabled.12": ["xltm"],
|
|
265
|
-
"application/vnd.ms-fontobject": ["eot"],
|
|
266
|
-
"application/vnd.ms-htmlhelp": ["chm"],
|
|
267
|
-
"application/vnd.ms-ims": ["ims"],
|
|
268
|
-
"application/vnd.ms-lrm": ["lrm"],
|
|
269
|
-
"application/vnd.ms-officetheme": ["thmx"],
|
|
270
|
-
"application/vnd.ms-outlook": ["msg"],
|
|
271
|
-
"application/vnd.ms-pki.seccat": ["cat"],
|
|
272
|
-
"application/vnd.ms-pki.stl": ["*stl"],
|
|
273
|
-
"application/vnd.ms-powerpoint": [
|
|
274
|
-
"ppt",
|
|
275
|
-
"pps",
|
|
276
|
-
"pot"
|
|
277
|
-
],
|
|
278
|
-
"application/vnd.ms-powerpoint.addin.macroenabled.12": ["ppam"],
|
|
279
|
-
"application/vnd.ms-powerpoint.presentation.macroenabled.12": ["pptm"],
|
|
280
|
-
"application/vnd.ms-powerpoint.slide.macroenabled.12": ["sldm"],
|
|
281
|
-
"application/vnd.ms-powerpoint.slideshow.macroenabled.12": ["ppsm"],
|
|
282
|
-
"application/vnd.ms-powerpoint.template.macroenabled.12": ["potm"],
|
|
283
|
-
"application/vnd.ms-project": ["*mpp", "mpt"],
|
|
284
|
-
"application/vnd.ms-visio.viewer": ["vdx"],
|
|
285
|
-
"application/vnd.ms-word.document.macroenabled.12": ["docm"],
|
|
286
|
-
"application/vnd.ms-word.template.macroenabled.12": ["dotm"],
|
|
287
|
-
"application/vnd.ms-works": [
|
|
288
|
-
"wps",
|
|
289
|
-
"wks",
|
|
290
|
-
"wcm",
|
|
291
|
-
"wdb"
|
|
292
|
-
],
|
|
293
|
-
"application/vnd.ms-wpl": ["wpl"],
|
|
294
|
-
"application/vnd.ms-xpsdocument": ["xps"],
|
|
295
|
-
"application/vnd.mseq": ["mseq"],
|
|
296
|
-
"application/vnd.musician": ["mus"],
|
|
297
|
-
"application/vnd.muvee.style": ["msty"],
|
|
298
|
-
"application/vnd.mynfc": ["taglet"],
|
|
299
|
-
"application/vnd.nato.bindingdataobject+xml": ["bdo"],
|
|
300
|
-
"application/vnd.neurolanguage.nlu": ["nlu"],
|
|
301
|
-
"application/vnd.nitf": ["ntf", "nitf"],
|
|
302
|
-
"application/vnd.noblenet-directory": ["nnd"],
|
|
303
|
-
"application/vnd.noblenet-sealer": ["nns"],
|
|
304
|
-
"application/vnd.noblenet-web": ["nnw"],
|
|
305
|
-
"application/vnd.nokia.n-gage.ac+xml": ["*ac"],
|
|
306
|
-
"application/vnd.nokia.n-gage.data": ["ngdat"],
|
|
307
|
-
"application/vnd.nokia.n-gage.symbian.install": ["n-gage"],
|
|
308
|
-
"application/vnd.nokia.radio-preset": ["rpst"],
|
|
309
|
-
"application/vnd.nokia.radio-presets": ["rpss"],
|
|
310
|
-
"application/vnd.novadigm.edm": ["edm"],
|
|
311
|
-
"application/vnd.novadigm.edx": ["edx"],
|
|
312
|
-
"application/vnd.novadigm.ext": ["ext"],
|
|
313
|
-
"application/vnd.oasis.opendocument.chart": ["odc"],
|
|
314
|
-
"application/vnd.oasis.opendocument.chart-template": ["otc"],
|
|
315
|
-
"application/vnd.oasis.opendocument.database": ["odb"],
|
|
316
|
-
"application/vnd.oasis.opendocument.formula": ["odf"],
|
|
317
|
-
"application/vnd.oasis.opendocument.formula-template": ["odft"],
|
|
318
|
-
"application/vnd.oasis.opendocument.graphics": ["odg"],
|
|
319
|
-
"application/vnd.oasis.opendocument.graphics-template": ["otg"],
|
|
320
|
-
"application/vnd.oasis.opendocument.image": ["odi"],
|
|
321
|
-
"application/vnd.oasis.opendocument.image-template": ["oti"],
|
|
322
|
-
"application/vnd.oasis.opendocument.presentation": ["odp"],
|
|
323
|
-
"application/vnd.oasis.opendocument.presentation-template": ["otp"],
|
|
324
|
-
"application/vnd.oasis.opendocument.spreadsheet": ["ods"],
|
|
325
|
-
"application/vnd.oasis.opendocument.spreadsheet-template": ["ots"],
|
|
326
|
-
"application/vnd.oasis.opendocument.text": ["odt"],
|
|
327
|
-
"application/vnd.oasis.opendocument.text-master": ["odm"],
|
|
328
|
-
"application/vnd.oasis.opendocument.text-template": ["ott"],
|
|
329
|
-
"application/vnd.oasis.opendocument.text-web": ["oth"],
|
|
330
|
-
"application/vnd.olpc-sugar": ["xo"],
|
|
331
|
-
"application/vnd.oma.dd2+xml": ["dd2"],
|
|
332
|
-
"application/vnd.openblox.game+xml": ["obgx"],
|
|
333
|
-
"application/vnd.openofficeorg.extension": ["oxt"],
|
|
334
|
-
"application/vnd.openstreetmap.data+xml": ["osm"],
|
|
335
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"],
|
|
336
|
-
"application/vnd.openxmlformats-officedocument.presentationml.slide": ["sldx"],
|
|
337
|
-
"application/vnd.openxmlformats-officedocument.presentationml.slideshow": ["ppsx"],
|
|
338
|
-
"application/vnd.openxmlformats-officedocument.presentationml.template": ["potx"],
|
|
339
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"],
|
|
340
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.template": ["xltx"],
|
|
341
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"],
|
|
342
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.template": ["dotx"],
|
|
343
|
-
"application/vnd.osgeo.mapguide.package": ["mgp"],
|
|
344
|
-
"application/vnd.osgi.dp": ["dp"],
|
|
345
|
-
"application/vnd.osgi.subsystem": ["esa"],
|
|
346
|
-
"application/vnd.palm": [
|
|
347
|
-
"pdb",
|
|
348
|
-
"pqa",
|
|
349
|
-
"oprc"
|
|
350
|
-
],
|
|
351
|
-
"application/vnd.pawaafile": ["paw"],
|
|
352
|
-
"application/vnd.pg.format": ["str"],
|
|
353
|
-
"application/vnd.pg.osasli": ["ei6"],
|
|
354
|
-
"application/vnd.picsel": ["efif"],
|
|
355
|
-
"application/vnd.pmi.widget": ["wg"],
|
|
356
|
-
"application/vnd.pocketlearn": ["plf"],
|
|
357
|
-
"application/vnd.powerbuilder6": ["pbd"],
|
|
358
|
-
"application/vnd.previewsystems.box": ["box"],
|
|
359
|
-
"application/vnd.procrate.brushset": ["brushset"],
|
|
360
|
-
"application/vnd.procreate.brush": ["brush"],
|
|
361
|
-
"application/vnd.procreate.dream": ["drm"],
|
|
362
|
-
"application/vnd.proteus.magazine": ["mgz"],
|
|
363
|
-
"application/vnd.publishare-delta-tree": ["qps"],
|
|
364
|
-
"application/vnd.pvi.ptid1": ["ptid"],
|
|
365
|
-
"application/vnd.pwg-xhtml-print+xml": ["xhtm"],
|
|
366
|
-
"application/vnd.quark.quarkxpress": [
|
|
367
|
-
"qxd",
|
|
368
|
-
"qxt",
|
|
369
|
-
"qwd",
|
|
370
|
-
"qwt",
|
|
371
|
-
"qxl",
|
|
372
|
-
"qxb"
|
|
373
|
-
],
|
|
374
|
-
"application/vnd.rar": ["rar"],
|
|
375
|
-
"application/vnd.realvnc.bed": ["bed"],
|
|
376
|
-
"application/vnd.recordare.musicxml": ["mxl"],
|
|
377
|
-
"application/vnd.recordare.musicxml+xml": ["musicxml"],
|
|
378
|
-
"application/vnd.rig.cryptonote": ["cryptonote"],
|
|
379
|
-
"application/vnd.rim.cod": ["cod"],
|
|
380
|
-
"application/vnd.rn-realmedia": ["rm"],
|
|
381
|
-
"application/vnd.rn-realmedia-vbr": ["rmvb"],
|
|
382
|
-
"application/vnd.route66.link66+xml": ["link66"],
|
|
383
|
-
"application/vnd.sailingtracker.track": ["st"],
|
|
384
|
-
"application/vnd.seemail": ["see"],
|
|
385
|
-
"application/vnd.sema": ["sema"],
|
|
386
|
-
"application/vnd.semd": ["semd"],
|
|
387
|
-
"application/vnd.semf": ["semf"],
|
|
388
|
-
"application/vnd.shana.informed.formdata": ["ifm"],
|
|
389
|
-
"application/vnd.shana.informed.formtemplate": ["itp"],
|
|
390
|
-
"application/vnd.shana.informed.interchange": ["iif"],
|
|
391
|
-
"application/vnd.shana.informed.package": ["ipk"],
|
|
392
|
-
"application/vnd.simtech-mindmapper": ["twd", "twds"],
|
|
393
|
-
"application/vnd.smaf": ["mmf"],
|
|
394
|
-
"application/vnd.smart.teacher": ["teacher"],
|
|
395
|
-
"application/vnd.software602.filler.form+xml": ["fo"],
|
|
396
|
-
"application/vnd.solent.sdkm+xml": ["sdkm", "sdkd"],
|
|
397
|
-
"application/vnd.spotfire.dxp": ["dxp"],
|
|
398
|
-
"application/vnd.spotfire.sfs": ["sfs"],
|
|
399
|
-
"application/vnd.stardivision.calc": ["sdc"],
|
|
400
|
-
"application/vnd.stardivision.draw": ["sda"],
|
|
401
|
-
"application/vnd.stardivision.impress": ["sdd"],
|
|
402
|
-
"application/vnd.stardivision.math": ["smf"],
|
|
403
|
-
"application/vnd.stardivision.writer": ["sdw", "vor"],
|
|
404
|
-
"application/vnd.stardivision.writer-global": ["sgl"],
|
|
405
|
-
"application/vnd.stepmania.package": ["smzip"],
|
|
406
|
-
"application/vnd.stepmania.stepchart": ["sm"],
|
|
407
|
-
"application/vnd.sun.wadl+xml": ["wadl"],
|
|
408
|
-
"application/vnd.sun.xml.calc": ["sxc"],
|
|
409
|
-
"application/vnd.sun.xml.calc.template": ["stc"],
|
|
410
|
-
"application/vnd.sun.xml.draw": ["sxd"],
|
|
411
|
-
"application/vnd.sun.xml.draw.template": ["std"],
|
|
412
|
-
"application/vnd.sun.xml.impress": ["sxi"],
|
|
413
|
-
"application/vnd.sun.xml.impress.template": ["sti"],
|
|
414
|
-
"application/vnd.sun.xml.math": ["sxm"],
|
|
415
|
-
"application/vnd.sun.xml.writer": ["sxw"],
|
|
416
|
-
"application/vnd.sun.xml.writer.global": ["sxg"],
|
|
417
|
-
"application/vnd.sun.xml.writer.template": ["stw"],
|
|
418
|
-
"application/vnd.sus-calendar": ["sus", "susp"],
|
|
419
|
-
"application/vnd.svd": ["svd"],
|
|
420
|
-
"application/vnd.symbian.install": ["sis", "sisx"],
|
|
421
|
-
"application/vnd.syncml+xml": ["xsm"],
|
|
422
|
-
"application/vnd.syncml.dm+wbxml": ["bdm"],
|
|
423
|
-
"application/vnd.syncml.dm+xml": ["xdm"],
|
|
424
|
-
"application/vnd.syncml.dmddf+xml": ["ddf"],
|
|
425
|
-
"application/vnd.tao.intent-module-archive": ["tao"],
|
|
426
|
-
"application/vnd.tcpdump.pcap": [
|
|
427
|
-
"pcap",
|
|
428
|
-
"cap",
|
|
429
|
-
"dmp"
|
|
430
|
-
],
|
|
431
|
-
"application/vnd.tmobile-livetv": ["tmo"],
|
|
432
|
-
"application/vnd.trid.tpt": ["tpt"],
|
|
433
|
-
"application/vnd.triscape.mxs": ["mxs"],
|
|
434
|
-
"application/vnd.trueapp": ["tra"],
|
|
435
|
-
"application/vnd.ufdl": ["ufd", "ufdl"],
|
|
436
|
-
"application/vnd.uiq.theme": ["utz"],
|
|
437
|
-
"application/vnd.umajin": ["umj"],
|
|
438
|
-
"application/vnd.unity": ["unityweb"],
|
|
439
|
-
"application/vnd.uoml+xml": ["uoml", "uo"],
|
|
440
|
-
"application/vnd.vcx": ["vcx"],
|
|
441
|
-
"application/vnd.visio": [
|
|
442
|
-
"vsd",
|
|
443
|
-
"vst",
|
|
444
|
-
"vss",
|
|
445
|
-
"vsw",
|
|
446
|
-
"vsdx",
|
|
447
|
-
"vtx"
|
|
448
|
-
],
|
|
449
|
-
"application/vnd.visionary": ["vis"],
|
|
450
|
-
"application/vnd.vsf": ["vsf"],
|
|
451
|
-
"application/vnd.wap.wbxml": ["wbxml"],
|
|
452
|
-
"application/vnd.wap.wmlc": ["wmlc"],
|
|
453
|
-
"application/vnd.wap.wmlscriptc": ["wmlsc"],
|
|
454
|
-
"application/vnd.webturbo": ["wtb"],
|
|
455
|
-
"application/vnd.wolfram.player": ["nbp"],
|
|
456
|
-
"application/vnd.wordperfect": ["wpd"],
|
|
457
|
-
"application/vnd.wqd": ["wqd"],
|
|
458
|
-
"application/vnd.wt.stf": ["stf"],
|
|
459
|
-
"application/vnd.xara": ["xar"],
|
|
460
|
-
"application/vnd.xfdl": ["xfdl"],
|
|
461
|
-
"application/vnd.yamaha.hv-dic": ["hvd"],
|
|
462
|
-
"application/vnd.yamaha.hv-script": ["hvs"],
|
|
463
|
-
"application/vnd.yamaha.hv-voice": ["hvp"],
|
|
464
|
-
"application/vnd.yamaha.openscoreformat": ["osf"],
|
|
465
|
-
"application/vnd.yamaha.openscoreformat.osfpvg+xml": ["osfpvg"],
|
|
466
|
-
"application/vnd.yamaha.smaf-audio": ["saf"],
|
|
467
|
-
"application/vnd.yamaha.smaf-phrase": ["spf"],
|
|
468
|
-
"application/vnd.yellowriver-custom-menu": ["cmp"],
|
|
469
|
-
"application/vnd.zul": ["zir", "zirz"],
|
|
470
|
-
"application/vnd.zzazz.deck+xml": ["zaz"],
|
|
471
|
-
"application/x-7z-compressed": ["7z"],
|
|
472
|
-
"application/x-abiword": ["abw"],
|
|
473
|
-
"application/x-ace-compressed": ["ace"],
|
|
474
|
-
"application/x-apple-diskimage": ["*dmg"],
|
|
475
|
-
"application/x-arj": ["arj"],
|
|
476
|
-
"application/x-authorware-bin": [
|
|
477
|
-
"aab",
|
|
478
|
-
"x32",
|
|
479
|
-
"u32",
|
|
480
|
-
"vox"
|
|
481
|
-
],
|
|
482
|
-
"application/x-authorware-map": ["aam"],
|
|
483
|
-
"application/x-authorware-seg": ["aas"],
|
|
484
|
-
"application/x-bcpio": ["bcpio"],
|
|
485
|
-
"application/x-bdoc": ["*bdoc"],
|
|
486
|
-
"application/x-bittorrent": ["torrent"],
|
|
487
|
-
"application/x-blender": ["blend"],
|
|
488
|
-
"application/x-blorb": ["blb", "blorb"],
|
|
489
|
-
"application/x-bzip": ["bz"],
|
|
490
|
-
"application/x-bzip2": ["bz2", "boz"],
|
|
491
|
-
"application/x-cbr": [
|
|
492
|
-
"cbr",
|
|
493
|
-
"cba",
|
|
494
|
-
"cbt",
|
|
495
|
-
"cbz",
|
|
496
|
-
"cb7"
|
|
497
|
-
],
|
|
498
|
-
"application/x-cdlink": ["vcd"],
|
|
499
|
-
"application/x-cfs-compressed": ["cfs"],
|
|
500
|
-
"application/x-chat": ["chat"],
|
|
501
|
-
"application/x-chess-pgn": ["pgn"],
|
|
502
|
-
"application/x-chrome-extension": ["crx"],
|
|
503
|
-
"application/x-cocoa": ["cco"],
|
|
504
|
-
"application/x-compressed": ["*rar"],
|
|
505
|
-
"application/x-conference": ["nsc"],
|
|
506
|
-
"application/x-cpio": ["cpio"],
|
|
507
|
-
"application/x-csh": ["csh"],
|
|
508
|
-
"application/x-debian-package": ["*deb", "udeb"],
|
|
509
|
-
"application/x-dgc-compressed": ["dgc"],
|
|
510
|
-
"application/x-director": [
|
|
511
|
-
"dir",
|
|
512
|
-
"dcr",
|
|
513
|
-
"dxr",
|
|
514
|
-
"cst",
|
|
515
|
-
"cct",
|
|
516
|
-
"cxt",
|
|
517
|
-
"w3d",
|
|
518
|
-
"fgd",
|
|
519
|
-
"swa"
|
|
520
|
-
],
|
|
521
|
-
"application/x-doom": ["wad"],
|
|
522
|
-
"application/x-dtbncx+xml": ["ncx"],
|
|
523
|
-
"application/x-dtbook+xml": ["dtb"],
|
|
524
|
-
"application/x-dtbresource+xml": ["res"],
|
|
525
|
-
"application/x-dvi": ["dvi"],
|
|
526
|
-
"application/x-envoy": ["evy"],
|
|
527
|
-
"application/x-eva": ["eva"],
|
|
528
|
-
"application/x-font-bdf": ["bdf"],
|
|
529
|
-
"application/x-font-ghostscript": ["gsf"],
|
|
530
|
-
"application/x-font-linux-psf": ["psf"],
|
|
531
|
-
"application/x-font-pcf": ["pcf"],
|
|
532
|
-
"application/x-font-snf": ["snf"],
|
|
533
|
-
"application/x-font-type1": [
|
|
534
|
-
"pfa",
|
|
535
|
-
"pfb",
|
|
536
|
-
"pfm",
|
|
537
|
-
"afm"
|
|
538
|
-
],
|
|
539
|
-
"application/x-freearc": ["arc"],
|
|
540
|
-
"application/x-futuresplash": ["spl"],
|
|
541
|
-
"application/x-gca-compressed": ["gca"],
|
|
542
|
-
"application/x-glulx": ["ulx"],
|
|
543
|
-
"application/x-gnumeric": ["gnumeric"],
|
|
544
|
-
"application/x-gramps-xml": ["gramps"],
|
|
545
|
-
"application/x-gtar": ["gtar"],
|
|
546
|
-
"application/x-hdf": ["hdf"],
|
|
547
|
-
"application/x-httpd-php": ["php"],
|
|
548
|
-
"application/x-install-instructions": ["install"],
|
|
549
|
-
"application/x-ipynb+json": ["ipynb"],
|
|
550
|
-
"application/x-iso9660-image": ["*iso"],
|
|
551
|
-
"application/x-iwork-keynote-sffkey": ["*key"],
|
|
552
|
-
"application/x-iwork-numbers-sffnumbers": ["*numbers"],
|
|
553
|
-
"application/x-iwork-pages-sffpages": ["*pages"],
|
|
554
|
-
"application/x-java-archive-diff": ["jardiff"],
|
|
555
|
-
"application/x-java-jnlp-file": ["jnlp"],
|
|
556
|
-
"application/x-keepass2": ["kdbx"],
|
|
557
|
-
"application/x-latex": ["latex"],
|
|
558
|
-
"application/x-lua-bytecode": ["luac"],
|
|
559
|
-
"application/x-lzh-compressed": ["lzh", "lha"],
|
|
560
|
-
"application/x-makeself": ["run"],
|
|
561
|
-
"application/x-mie": ["mie"],
|
|
562
|
-
"application/x-mobipocket-ebook": ["*prc", "mobi"],
|
|
563
|
-
"application/x-ms-application": ["application"],
|
|
564
|
-
"application/x-ms-shortcut": ["lnk"],
|
|
565
|
-
"application/x-ms-wmd": ["wmd"],
|
|
566
|
-
"application/x-ms-wmz": ["wmz"],
|
|
567
|
-
"application/x-ms-xbap": ["xbap"],
|
|
568
|
-
"application/x-msaccess": ["mdb"],
|
|
569
|
-
"application/x-msbinder": ["obd"],
|
|
570
|
-
"application/x-mscardfile": ["crd"],
|
|
571
|
-
"application/x-msclip": ["clp"],
|
|
572
|
-
"application/x-msdos-program": ["*exe"],
|
|
573
|
-
"application/x-msdownload": [
|
|
574
|
-
"*exe",
|
|
575
|
-
"*dll",
|
|
576
|
-
"com",
|
|
577
|
-
"bat",
|
|
578
|
-
"*msi"
|
|
579
|
-
],
|
|
580
|
-
"application/x-msmediaview": [
|
|
581
|
-
"mvb",
|
|
582
|
-
"m13",
|
|
583
|
-
"m14"
|
|
584
|
-
],
|
|
585
|
-
"application/x-msmetafile": [
|
|
586
|
-
"*wmf",
|
|
587
|
-
"*wmz",
|
|
588
|
-
"*emf",
|
|
589
|
-
"emz"
|
|
590
|
-
],
|
|
591
|
-
"application/x-msmoney": ["mny"],
|
|
592
|
-
"application/x-mspublisher": ["pub"],
|
|
593
|
-
"application/x-msschedule": ["scd"],
|
|
594
|
-
"application/x-msterminal": ["trm"],
|
|
595
|
-
"application/x-mswrite": ["wri"],
|
|
596
|
-
"application/x-netcdf": ["nc", "cdf"],
|
|
597
|
-
"application/x-ns-proxy-autoconfig": ["pac"],
|
|
598
|
-
"application/x-nzb": ["nzb"],
|
|
599
|
-
"application/x-perl": ["pl", "pm"],
|
|
600
|
-
"application/x-pilot": ["*prc", "*pdb"],
|
|
601
|
-
"application/x-pkcs12": ["p12", "pfx"],
|
|
602
|
-
"application/x-pkcs7-certificates": ["p7b", "spc"],
|
|
603
|
-
"application/x-pkcs7-certreqresp": ["p7r"],
|
|
604
|
-
"application/x-rar-compressed": ["*rar"],
|
|
605
|
-
"application/x-redhat-package-manager": ["rpm"],
|
|
606
|
-
"application/x-research-info-systems": ["ris"],
|
|
607
|
-
"application/x-sea": ["sea"],
|
|
608
|
-
"application/x-sh": ["sh"],
|
|
609
|
-
"application/x-shar": ["shar"],
|
|
610
|
-
"application/x-shockwave-flash": ["swf"],
|
|
611
|
-
"application/x-silverlight-app": ["xap"],
|
|
612
|
-
"application/x-sql": ["*sql"],
|
|
613
|
-
"application/x-stuffit": ["sit"],
|
|
614
|
-
"application/x-stuffitx": ["sitx"],
|
|
615
|
-
"application/x-subrip": ["srt"],
|
|
616
|
-
"application/x-sv4cpio": ["sv4cpio"],
|
|
617
|
-
"application/x-sv4crc": ["sv4crc"],
|
|
618
|
-
"application/x-t3vm-image": ["t3"],
|
|
619
|
-
"application/x-tads": ["gam"],
|
|
620
|
-
"application/x-tar": ["tar"],
|
|
621
|
-
"application/x-tcl": ["tcl", "tk"],
|
|
622
|
-
"application/x-tex": ["tex"],
|
|
623
|
-
"application/x-tex-tfm": ["tfm"],
|
|
624
|
-
"application/x-texinfo": ["texinfo", "texi"],
|
|
625
|
-
"application/x-tgif": ["*obj"],
|
|
626
|
-
"application/x-ustar": ["ustar"],
|
|
627
|
-
"application/x-virtualbox-hdd": ["hdd"],
|
|
628
|
-
"application/x-virtualbox-ova": ["ova"],
|
|
629
|
-
"application/x-virtualbox-ovf": ["ovf"],
|
|
630
|
-
"application/x-virtualbox-vbox": ["vbox"],
|
|
631
|
-
"application/x-virtualbox-vbox-extpack": ["vbox-extpack"],
|
|
632
|
-
"application/x-virtualbox-vdi": ["vdi"],
|
|
633
|
-
"application/x-virtualbox-vhd": ["vhd"],
|
|
634
|
-
"application/x-virtualbox-vmdk": ["vmdk"],
|
|
635
|
-
"application/x-wais-source": ["src"],
|
|
636
|
-
"application/x-web-app-manifest+json": ["webapp"],
|
|
637
|
-
"application/x-x509-ca-cert": [
|
|
638
|
-
"der",
|
|
639
|
-
"crt",
|
|
640
|
-
"pem"
|
|
641
|
-
],
|
|
642
|
-
"application/x-xfig": ["fig"],
|
|
643
|
-
"application/x-xliff+xml": ["*xlf"],
|
|
644
|
-
"application/x-xpinstall": ["xpi"],
|
|
645
|
-
"application/x-xz": ["xz"],
|
|
646
|
-
"application/x-zip-compressed": ["*zip"],
|
|
647
|
-
"application/x-zmachine": [
|
|
648
|
-
"z1",
|
|
649
|
-
"z2",
|
|
650
|
-
"z3",
|
|
651
|
-
"z4",
|
|
652
|
-
"z5",
|
|
653
|
-
"z6",
|
|
654
|
-
"z7",
|
|
655
|
-
"z8"
|
|
656
|
-
],
|
|
657
|
-
"audio/vnd.dece.audio": ["uva", "uvva"],
|
|
658
|
-
"audio/vnd.digital-winds": ["eol"],
|
|
659
|
-
"audio/vnd.dra": ["dra"],
|
|
660
|
-
"audio/vnd.dts": ["dts"],
|
|
661
|
-
"audio/vnd.dts.hd": ["dtshd"],
|
|
662
|
-
"audio/vnd.lucent.voice": ["lvp"],
|
|
663
|
-
"audio/vnd.ms-playready.media.pya": ["pya"],
|
|
664
|
-
"audio/vnd.nuera.ecelp4800": ["ecelp4800"],
|
|
665
|
-
"audio/vnd.nuera.ecelp7470": ["ecelp7470"],
|
|
666
|
-
"audio/vnd.nuera.ecelp9600": ["ecelp9600"],
|
|
667
|
-
"audio/vnd.rip": ["rip"],
|
|
668
|
-
"audio/x-aac": ["*aac"],
|
|
669
|
-
"audio/x-aiff": [
|
|
670
|
-
"aif",
|
|
671
|
-
"aiff",
|
|
672
|
-
"aifc"
|
|
673
|
-
],
|
|
674
|
-
"audio/x-caf": ["caf"],
|
|
675
|
-
"audio/x-flac": ["flac"],
|
|
676
|
-
"audio/x-m4a": ["*m4a"],
|
|
677
|
-
"audio/x-matroska": ["mka"],
|
|
678
|
-
"audio/x-mpegurl": ["m3u"],
|
|
679
|
-
"audio/x-ms-wax": ["wax"],
|
|
680
|
-
"audio/x-ms-wma": ["wma"],
|
|
681
|
-
"audio/x-pn-realaudio": ["ram", "ra"],
|
|
682
|
-
"audio/x-pn-realaudio-plugin": ["rmp"],
|
|
683
|
-
"audio/x-realaudio": ["*ra"],
|
|
684
|
-
"audio/x-wav": ["*wav"],
|
|
685
|
-
"chemical/x-cdx": ["cdx"],
|
|
686
|
-
"chemical/x-cif": ["cif"],
|
|
687
|
-
"chemical/x-cmdf": ["cmdf"],
|
|
688
|
-
"chemical/x-cml": ["cml"],
|
|
689
|
-
"chemical/x-csml": ["csml"],
|
|
690
|
-
"chemical/x-xyz": ["xyz"],
|
|
691
|
-
"image/prs.btif": ["btif", "btf"],
|
|
692
|
-
"image/prs.pti": ["pti"],
|
|
693
|
-
"image/vnd.adobe.photoshop": ["psd"],
|
|
694
|
-
"image/vnd.airzip.accelerator.azv": ["azv"],
|
|
695
|
-
"image/vnd.blockfact.facti": ["facti"],
|
|
696
|
-
"image/vnd.dece.graphic": [
|
|
697
|
-
"uvi",
|
|
698
|
-
"uvvi",
|
|
699
|
-
"uvg",
|
|
700
|
-
"uvvg"
|
|
701
|
-
],
|
|
702
|
-
"image/vnd.djvu": ["djvu", "djv"],
|
|
703
|
-
"image/vnd.dvb.subtitle": ["*sub"],
|
|
704
|
-
"image/vnd.dwg": ["dwg"],
|
|
705
|
-
"image/vnd.dxf": ["dxf"],
|
|
706
|
-
"image/vnd.fastbidsheet": ["fbs"],
|
|
707
|
-
"image/vnd.fpx": ["fpx"],
|
|
708
|
-
"image/vnd.fst": ["fst"],
|
|
709
|
-
"image/vnd.fujixerox.edmics-mmr": ["mmr"],
|
|
710
|
-
"image/vnd.fujixerox.edmics-rlc": ["rlc"],
|
|
711
|
-
"image/vnd.microsoft.icon": ["ico"],
|
|
712
|
-
"image/vnd.ms-dds": ["dds"],
|
|
713
|
-
"image/vnd.ms-modi": ["mdi"],
|
|
714
|
-
"image/vnd.ms-photo": ["wdp"],
|
|
715
|
-
"image/vnd.net-fpx": ["npx"],
|
|
716
|
-
"image/vnd.pco.b16": ["b16"],
|
|
717
|
-
"image/vnd.tencent.tap": ["tap"],
|
|
718
|
-
"image/vnd.valve.source.texture": ["vtf"],
|
|
719
|
-
"image/vnd.wap.wbmp": ["wbmp"],
|
|
720
|
-
"image/vnd.xiff": ["xif"],
|
|
721
|
-
"image/vnd.zbrush.pcx": ["pcx"],
|
|
722
|
-
"image/x-3ds": ["3ds"],
|
|
723
|
-
"image/x-adobe-dng": ["dng"],
|
|
724
|
-
"image/x-cmu-raster": ["ras"],
|
|
725
|
-
"image/x-cmx": ["cmx"],
|
|
726
|
-
"image/x-freehand": [
|
|
727
|
-
"fh",
|
|
728
|
-
"fhc",
|
|
729
|
-
"fh4",
|
|
730
|
-
"fh5",
|
|
731
|
-
"fh7"
|
|
732
|
-
],
|
|
733
|
-
"image/x-icon": ["*ico"],
|
|
734
|
-
"image/x-jng": ["jng"],
|
|
735
|
-
"image/x-mrsid-image": ["sid"],
|
|
736
|
-
"image/x-ms-bmp": ["*bmp"],
|
|
737
|
-
"image/x-pcx": ["*pcx"],
|
|
738
|
-
"image/x-pict": ["pic", "pct"],
|
|
739
|
-
"image/x-portable-anymap": ["pnm"],
|
|
740
|
-
"image/x-portable-bitmap": ["pbm"],
|
|
741
|
-
"image/x-portable-graymap": ["pgm"],
|
|
742
|
-
"image/x-portable-pixmap": ["ppm"],
|
|
743
|
-
"image/x-rgb": ["rgb"],
|
|
744
|
-
"image/x-tga": ["tga"],
|
|
745
|
-
"image/x-xbitmap": ["xbm"],
|
|
746
|
-
"image/x-xpixmap": ["xpm"],
|
|
747
|
-
"image/x-xwindowdump": ["xwd"],
|
|
748
|
-
"message/vnd.wfa.wsc": ["wsc"],
|
|
749
|
-
"model/vnd.bary": ["bary"],
|
|
750
|
-
"model/vnd.cld": ["cld"],
|
|
751
|
-
"model/vnd.collada+xml": ["dae"],
|
|
752
|
-
"model/vnd.dwf": ["dwf"],
|
|
753
|
-
"model/vnd.gdl": ["gdl"],
|
|
754
|
-
"model/vnd.gtw": ["gtw"],
|
|
755
|
-
"model/vnd.mts": ["*mts"],
|
|
756
|
-
"model/vnd.opengex": ["ogex"],
|
|
757
|
-
"model/vnd.parasolid.transmit.binary": ["x_b"],
|
|
758
|
-
"model/vnd.parasolid.transmit.text": ["x_t"],
|
|
759
|
-
"model/vnd.pytha.pyox": ["pyo", "pyox"],
|
|
760
|
-
"model/vnd.sap.vds": ["vds"],
|
|
761
|
-
"model/vnd.usda": ["usda"],
|
|
762
|
-
"model/vnd.usdz+zip": ["usdz"],
|
|
763
|
-
"model/vnd.valve.source.compiled-map": ["bsp"],
|
|
764
|
-
"model/vnd.vtu": ["vtu"],
|
|
765
|
-
"text/prs.lines.tag": ["dsc"],
|
|
766
|
-
"text/vnd.curl": ["curl"],
|
|
767
|
-
"text/vnd.curl.dcurl": ["dcurl"],
|
|
768
|
-
"text/vnd.curl.mcurl": ["mcurl"],
|
|
769
|
-
"text/vnd.curl.scurl": ["scurl"],
|
|
770
|
-
"text/vnd.dvb.subtitle": ["sub"],
|
|
771
|
-
"text/vnd.familysearch.gedcom": ["ged"],
|
|
772
|
-
"text/vnd.fly": ["fly"],
|
|
773
|
-
"text/vnd.fmi.flexstor": ["flx"],
|
|
774
|
-
"text/vnd.graphviz": ["gv"],
|
|
775
|
-
"text/vnd.in3d.3dml": ["3dml"],
|
|
776
|
-
"text/vnd.in3d.spot": ["spot"],
|
|
777
|
-
"text/vnd.sun.j2me.app-descriptor": ["jad"],
|
|
778
|
-
"text/vnd.wap.wml": ["wml"],
|
|
779
|
-
"text/vnd.wap.wmlscript": ["wmls"],
|
|
780
|
-
"text/x-asm": ["s", "asm"],
|
|
781
|
-
"text/x-c": [
|
|
782
|
-
"c",
|
|
783
|
-
"cc",
|
|
784
|
-
"cxx",
|
|
785
|
-
"cpp",
|
|
786
|
-
"h",
|
|
787
|
-
"hh",
|
|
788
|
-
"dic"
|
|
789
|
-
],
|
|
790
|
-
"text/x-component": ["htc"],
|
|
791
|
-
"text/x-fortran": [
|
|
792
|
-
"f",
|
|
793
|
-
"for",
|
|
794
|
-
"f77",
|
|
795
|
-
"f90"
|
|
796
|
-
],
|
|
797
|
-
"text/x-handlebars-template": ["hbs"],
|
|
798
|
-
"text/x-java-source": ["java"],
|
|
799
|
-
"text/x-lua": ["lua"],
|
|
800
|
-
"text/x-markdown": ["mkd"],
|
|
801
|
-
"text/x-nfo": ["nfo"],
|
|
802
|
-
"text/x-opml": ["opml"],
|
|
803
|
-
"text/x-org": ["*org"],
|
|
804
|
-
"text/x-pascal": ["p", "pas"],
|
|
805
|
-
"text/x-processing": ["pde"],
|
|
806
|
-
"text/x-sass": ["sass"],
|
|
807
|
-
"text/x-scss": ["scss"],
|
|
808
|
-
"text/x-setext": ["etx"],
|
|
809
|
-
"text/x-sfv": ["sfv"],
|
|
810
|
-
"text/x-suse-ymp": ["ymp"],
|
|
811
|
-
"text/x-uuencode": ["uu"],
|
|
812
|
-
"text/x-vcalendar": ["vcs"],
|
|
813
|
-
"text/x-vcard": ["vcf"],
|
|
814
|
-
"video/vnd.dece.hd": ["uvh", "uvvh"],
|
|
815
|
-
"video/vnd.dece.mobile": ["uvm", "uvvm"],
|
|
816
|
-
"video/vnd.dece.pd": ["uvp", "uvvp"],
|
|
817
|
-
"video/vnd.dece.sd": ["uvs", "uvvs"],
|
|
818
|
-
"video/vnd.dece.video": ["uvv", "uvvv"],
|
|
819
|
-
"video/vnd.dvb.file": ["dvb"],
|
|
820
|
-
"video/vnd.fvt": ["fvt"],
|
|
821
|
-
"video/vnd.mpegurl": ["mxu", "m4u"],
|
|
822
|
-
"video/vnd.ms-playready.media.pyv": ["pyv"],
|
|
823
|
-
"video/vnd.uvvu.mp4": ["uvu", "uvvu"],
|
|
824
|
-
"video/vnd.vivo": ["viv"],
|
|
825
|
-
"video/x-f4v": ["f4v"],
|
|
826
|
-
"video/x-fli": ["fli"],
|
|
827
|
-
"video/x-flv": ["flv"],
|
|
828
|
-
"video/x-m4v": ["m4v"],
|
|
829
|
-
"video/x-matroska": [
|
|
830
|
-
"mkv",
|
|
831
|
-
"mk3d",
|
|
832
|
-
"mks"
|
|
833
|
-
],
|
|
834
|
-
"video/x-mng": ["mng"],
|
|
835
|
-
"video/x-ms-asf": ["asf", "asx"],
|
|
836
|
-
"video/x-ms-vob": ["vob"],
|
|
837
|
-
"video/x-ms-wm": ["wm"],
|
|
838
|
-
"video/x-ms-wmv": ["wmv"],
|
|
839
|
-
"video/x-ms-wmx": ["wmx"],
|
|
840
|
-
"video/x-ms-wvx": ["wvx"],
|
|
841
|
-
"video/x-msvideo": ["avi"],
|
|
842
|
-
"video/x-sgi-movie": ["movie"],
|
|
843
|
-
"video/x-smv": ["smv"],
|
|
844
|
-
"x-conference/x-cooltalk": ["ice"]
|
|
845
|
-
};
|
|
846
|
-
Object.freeze(types$1);
|
|
847
|
-
//#endregion
|
|
848
|
-
//#region ../../node_modules/.pnpm/mime@4.1.0/node_modules/mime/dist/types/standard.js
|
|
849
|
-
const types = {
|
|
850
|
-
"application/andrew-inset": ["ez"],
|
|
851
|
-
"application/appinstaller": ["appinstaller"],
|
|
852
|
-
"application/applixware": ["aw"],
|
|
853
|
-
"application/appx": ["appx"],
|
|
854
|
-
"application/appxbundle": ["appxbundle"],
|
|
855
|
-
"application/atom+xml": ["atom"],
|
|
856
|
-
"application/atomcat+xml": ["atomcat"],
|
|
857
|
-
"application/atomdeleted+xml": ["atomdeleted"],
|
|
858
|
-
"application/atomsvc+xml": ["atomsvc"],
|
|
859
|
-
"application/atsc-dwd+xml": ["dwd"],
|
|
860
|
-
"application/atsc-held+xml": ["held"],
|
|
861
|
-
"application/atsc-rsat+xml": ["rsat"],
|
|
862
|
-
"application/automationml-aml+xml": ["aml"],
|
|
863
|
-
"application/automationml-amlx+zip": ["amlx"],
|
|
864
|
-
"application/bdoc": ["bdoc"],
|
|
865
|
-
"application/calendar+xml": ["xcs"],
|
|
866
|
-
"application/ccxml+xml": ["ccxml"],
|
|
867
|
-
"application/cdfx+xml": ["cdfx"],
|
|
868
|
-
"application/cdmi-capability": ["cdmia"],
|
|
869
|
-
"application/cdmi-container": ["cdmic"],
|
|
870
|
-
"application/cdmi-domain": ["cdmid"],
|
|
871
|
-
"application/cdmi-object": ["cdmio"],
|
|
872
|
-
"application/cdmi-queue": ["cdmiq"],
|
|
873
|
-
"application/cpl+xml": ["cpl"],
|
|
874
|
-
"application/cu-seeme": ["cu"],
|
|
875
|
-
"application/cwl": ["cwl"],
|
|
876
|
-
"application/dash+xml": ["mpd"],
|
|
877
|
-
"application/dash-patch+xml": ["mpp"],
|
|
878
|
-
"application/davmount+xml": ["davmount"],
|
|
879
|
-
"application/dicom": ["dcm"],
|
|
880
|
-
"application/docbook+xml": ["dbk"],
|
|
881
|
-
"application/dssc+der": ["dssc"],
|
|
882
|
-
"application/dssc+xml": ["xdssc"],
|
|
883
|
-
"application/ecmascript": ["ecma"],
|
|
884
|
-
"application/emma+xml": ["emma"],
|
|
885
|
-
"application/emotionml+xml": ["emotionml"],
|
|
886
|
-
"application/epub+zip": ["epub"],
|
|
887
|
-
"application/exi": ["exi"],
|
|
888
|
-
"application/express": ["exp"],
|
|
889
|
-
"application/fdf": ["fdf"],
|
|
890
|
-
"application/fdt+xml": ["fdt"],
|
|
891
|
-
"application/font-tdpfr": ["pfr"],
|
|
892
|
-
"application/geo+json": ["geojson"],
|
|
893
|
-
"application/gml+xml": ["gml"],
|
|
894
|
-
"application/gpx+xml": ["gpx"],
|
|
895
|
-
"application/gxf": ["gxf"],
|
|
896
|
-
"application/gzip": ["gz"],
|
|
897
|
-
"application/hjson": ["hjson"],
|
|
898
|
-
"application/hyperstudio": ["stk"],
|
|
899
|
-
"application/inkml+xml": ["ink", "inkml"],
|
|
900
|
-
"application/ipfix": ["ipfix"],
|
|
901
|
-
"application/its+xml": ["its"],
|
|
902
|
-
"application/java-archive": [
|
|
903
|
-
"jar",
|
|
904
|
-
"war",
|
|
905
|
-
"ear"
|
|
906
|
-
],
|
|
907
|
-
"application/java-serialized-object": ["ser"],
|
|
908
|
-
"application/java-vm": ["class"],
|
|
909
|
-
"application/javascript": ["*js"],
|
|
910
|
-
"application/json": ["json", "map"],
|
|
911
|
-
"application/json5": ["json5"],
|
|
912
|
-
"application/jsonml+json": ["jsonml"],
|
|
913
|
-
"application/ld+json": ["jsonld"],
|
|
914
|
-
"application/lgr+xml": ["lgr"],
|
|
915
|
-
"application/lost+xml": ["lostxml"],
|
|
916
|
-
"application/mac-binhex40": ["hqx"],
|
|
917
|
-
"application/mac-compactpro": ["cpt"],
|
|
918
|
-
"application/mads+xml": ["mads"],
|
|
919
|
-
"application/manifest+json": ["webmanifest"],
|
|
920
|
-
"application/marc": ["mrc"],
|
|
921
|
-
"application/marcxml+xml": ["mrcx"],
|
|
922
|
-
"application/mathematica": [
|
|
923
|
-
"ma",
|
|
924
|
-
"nb",
|
|
925
|
-
"mb"
|
|
926
|
-
],
|
|
927
|
-
"application/mathml+xml": ["mathml"],
|
|
928
|
-
"application/mbox": ["mbox"],
|
|
929
|
-
"application/media-policy-dataset+xml": ["mpf"],
|
|
930
|
-
"application/mediaservercontrol+xml": ["mscml"],
|
|
931
|
-
"application/metalink+xml": ["metalink"],
|
|
932
|
-
"application/metalink4+xml": ["meta4"],
|
|
933
|
-
"application/mets+xml": ["mets"],
|
|
934
|
-
"application/mmt-aei+xml": ["maei"],
|
|
935
|
-
"application/mmt-usd+xml": ["musd"],
|
|
936
|
-
"application/mods+xml": ["mods"],
|
|
937
|
-
"application/mp21": ["m21", "mp21"],
|
|
938
|
-
"application/mp4": [
|
|
939
|
-
"*mp4",
|
|
940
|
-
"*mpg4",
|
|
941
|
-
"mp4s",
|
|
942
|
-
"m4p"
|
|
943
|
-
],
|
|
944
|
-
"application/msix": ["msix"],
|
|
945
|
-
"application/msixbundle": ["msixbundle"],
|
|
946
|
-
"application/msword": ["doc", "dot"],
|
|
947
|
-
"application/mxf": ["mxf"],
|
|
948
|
-
"application/n-quads": ["nq"],
|
|
949
|
-
"application/n-triples": ["nt"],
|
|
950
|
-
"application/node": ["cjs"],
|
|
951
|
-
"application/octet-stream": [
|
|
952
|
-
"bin",
|
|
953
|
-
"dms",
|
|
954
|
-
"lrf",
|
|
955
|
-
"mar",
|
|
956
|
-
"so",
|
|
957
|
-
"dist",
|
|
958
|
-
"distz",
|
|
959
|
-
"pkg",
|
|
960
|
-
"bpk",
|
|
961
|
-
"dump",
|
|
962
|
-
"elc",
|
|
963
|
-
"deploy",
|
|
964
|
-
"exe",
|
|
965
|
-
"dll",
|
|
966
|
-
"deb",
|
|
967
|
-
"dmg",
|
|
968
|
-
"iso",
|
|
969
|
-
"img",
|
|
970
|
-
"msi",
|
|
971
|
-
"msp",
|
|
972
|
-
"msm",
|
|
973
|
-
"buffer"
|
|
974
|
-
],
|
|
975
|
-
"application/oda": ["oda"],
|
|
976
|
-
"application/oebps-package+xml": ["opf"],
|
|
977
|
-
"application/ogg": ["ogx"],
|
|
978
|
-
"application/omdoc+xml": ["omdoc"],
|
|
979
|
-
"application/onenote": [
|
|
980
|
-
"onetoc",
|
|
981
|
-
"onetoc2",
|
|
982
|
-
"onetmp",
|
|
983
|
-
"onepkg",
|
|
984
|
-
"one",
|
|
985
|
-
"onea"
|
|
986
|
-
],
|
|
987
|
-
"application/oxps": ["oxps"],
|
|
988
|
-
"application/p2p-overlay+xml": ["relo"],
|
|
989
|
-
"application/patch-ops-error+xml": ["xer"],
|
|
990
|
-
"application/pdf": ["pdf"],
|
|
991
|
-
"application/pgp-encrypted": ["pgp"],
|
|
992
|
-
"application/pgp-keys": ["asc"],
|
|
993
|
-
"application/pgp-signature": ["sig", "*asc"],
|
|
994
|
-
"application/pics-rules": ["prf"],
|
|
995
|
-
"application/pkcs10": ["p10"],
|
|
996
|
-
"application/pkcs7-mime": ["p7m", "p7c"],
|
|
997
|
-
"application/pkcs7-signature": ["p7s"],
|
|
998
|
-
"application/pkcs8": ["p8"],
|
|
999
|
-
"application/pkix-attr-cert": ["ac"],
|
|
1000
|
-
"application/pkix-cert": ["cer"],
|
|
1001
|
-
"application/pkix-crl": ["crl"],
|
|
1002
|
-
"application/pkix-pkipath": ["pkipath"],
|
|
1003
|
-
"application/pkixcmp": ["pki"],
|
|
1004
|
-
"application/pls+xml": ["pls"],
|
|
1005
|
-
"application/postscript": [
|
|
1006
|
-
"ai",
|
|
1007
|
-
"eps",
|
|
1008
|
-
"ps"
|
|
1009
|
-
],
|
|
1010
|
-
"application/provenance+xml": ["provx"],
|
|
1011
|
-
"application/pskc+xml": ["pskcxml"],
|
|
1012
|
-
"application/raml+yaml": ["raml"],
|
|
1013
|
-
"application/rdf+xml": ["rdf", "owl"],
|
|
1014
|
-
"application/reginfo+xml": ["rif"],
|
|
1015
|
-
"application/relax-ng-compact-syntax": ["rnc"],
|
|
1016
|
-
"application/resource-lists+xml": ["rl"],
|
|
1017
|
-
"application/resource-lists-diff+xml": ["rld"],
|
|
1018
|
-
"application/rls-services+xml": ["rs"],
|
|
1019
|
-
"application/route-apd+xml": ["rapd"],
|
|
1020
|
-
"application/route-s-tsid+xml": ["sls"],
|
|
1021
|
-
"application/route-usd+xml": ["rusd"],
|
|
1022
|
-
"application/rpki-ghostbusters": ["gbr"],
|
|
1023
|
-
"application/rpki-manifest": ["mft"],
|
|
1024
|
-
"application/rpki-roa": ["roa"],
|
|
1025
|
-
"application/rsd+xml": ["rsd"],
|
|
1026
|
-
"application/rss+xml": ["rss"],
|
|
1027
|
-
"application/rtf": ["rtf"],
|
|
1028
|
-
"application/sbml+xml": ["sbml"],
|
|
1029
|
-
"application/scvp-cv-request": ["scq"],
|
|
1030
|
-
"application/scvp-cv-response": ["scs"],
|
|
1031
|
-
"application/scvp-vp-request": ["spq"],
|
|
1032
|
-
"application/scvp-vp-response": ["spp"],
|
|
1033
|
-
"application/sdp": ["sdp"],
|
|
1034
|
-
"application/senml+xml": ["senmlx"],
|
|
1035
|
-
"application/sensml+xml": ["sensmlx"],
|
|
1036
|
-
"application/set-payment-initiation": ["setpay"],
|
|
1037
|
-
"application/set-registration-initiation": ["setreg"],
|
|
1038
|
-
"application/shf+xml": ["shf"],
|
|
1039
|
-
"application/sieve": ["siv", "sieve"],
|
|
1040
|
-
"application/smil+xml": ["smi", "smil"],
|
|
1041
|
-
"application/sparql-query": ["rq"],
|
|
1042
|
-
"application/sparql-results+xml": ["srx"],
|
|
1043
|
-
"application/sql": ["sql"],
|
|
1044
|
-
"application/srgs": ["gram"],
|
|
1045
|
-
"application/srgs+xml": ["grxml"],
|
|
1046
|
-
"application/sru+xml": ["sru"],
|
|
1047
|
-
"application/ssdl+xml": ["ssdl"],
|
|
1048
|
-
"application/ssml+xml": ["ssml"],
|
|
1049
|
-
"application/swid+xml": ["swidtag"],
|
|
1050
|
-
"application/tei+xml": ["tei", "teicorpus"],
|
|
1051
|
-
"application/thraud+xml": ["tfi"],
|
|
1052
|
-
"application/timestamped-data": ["tsd"],
|
|
1053
|
-
"application/toml": ["toml"],
|
|
1054
|
-
"application/trig": ["trig"],
|
|
1055
|
-
"application/ttml+xml": ["ttml"],
|
|
1056
|
-
"application/ubjson": ["ubj"],
|
|
1057
|
-
"application/urc-ressheet+xml": ["rsheet"],
|
|
1058
|
-
"application/urc-targetdesc+xml": ["td"],
|
|
1059
|
-
"application/voicexml+xml": ["vxml"],
|
|
1060
|
-
"application/wasm": ["wasm"],
|
|
1061
|
-
"application/watcherinfo+xml": ["wif"],
|
|
1062
|
-
"application/widget": ["wgt"],
|
|
1063
|
-
"application/winhlp": ["hlp"],
|
|
1064
|
-
"application/wsdl+xml": ["wsdl"],
|
|
1065
|
-
"application/wspolicy+xml": ["wspolicy"],
|
|
1066
|
-
"application/xaml+xml": ["xaml"],
|
|
1067
|
-
"application/xcap-att+xml": ["xav"],
|
|
1068
|
-
"application/xcap-caps+xml": ["xca"],
|
|
1069
|
-
"application/xcap-diff+xml": ["xdf"],
|
|
1070
|
-
"application/xcap-el+xml": ["xel"],
|
|
1071
|
-
"application/xcap-ns+xml": ["xns"],
|
|
1072
|
-
"application/xenc+xml": ["xenc"],
|
|
1073
|
-
"application/xfdf": ["xfdf"],
|
|
1074
|
-
"application/xhtml+xml": ["xhtml", "xht"],
|
|
1075
|
-
"application/xliff+xml": ["xlf"],
|
|
1076
|
-
"application/xml": [
|
|
1077
|
-
"xml",
|
|
1078
|
-
"xsl",
|
|
1079
|
-
"xsd",
|
|
1080
|
-
"rng"
|
|
1081
|
-
],
|
|
1082
|
-
"application/xml-dtd": ["dtd"],
|
|
1083
|
-
"application/xop+xml": ["xop"],
|
|
1084
|
-
"application/xproc+xml": ["xpl"],
|
|
1085
|
-
"application/xslt+xml": ["*xsl", "xslt"],
|
|
1086
|
-
"application/xspf+xml": ["xspf"],
|
|
1087
|
-
"application/xv+xml": [
|
|
1088
|
-
"mxml",
|
|
1089
|
-
"xhvml",
|
|
1090
|
-
"xvml",
|
|
1091
|
-
"xvm"
|
|
1092
|
-
],
|
|
1093
|
-
"application/yang": ["yang"],
|
|
1094
|
-
"application/yin+xml": ["yin"],
|
|
1095
|
-
"application/zip": ["zip"],
|
|
1096
|
-
"application/zip+dotlottie": ["lottie"],
|
|
1097
|
-
"audio/3gpp": ["*3gpp"],
|
|
1098
|
-
"audio/aac": ["adts", "aac"],
|
|
1099
|
-
"audio/adpcm": ["adp"],
|
|
1100
|
-
"audio/amr": ["amr"],
|
|
1101
|
-
"audio/basic": ["au", "snd"],
|
|
1102
|
-
"audio/midi": [
|
|
1103
|
-
"mid",
|
|
1104
|
-
"midi",
|
|
1105
|
-
"kar",
|
|
1106
|
-
"rmi"
|
|
1107
|
-
],
|
|
1108
|
-
"audio/mobile-xmf": ["mxmf"],
|
|
1109
|
-
"audio/mp3": ["*mp3"],
|
|
1110
|
-
"audio/mp4": [
|
|
1111
|
-
"m4a",
|
|
1112
|
-
"mp4a",
|
|
1113
|
-
"m4b"
|
|
1114
|
-
],
|
|
1115
|
-
"audio/mpeg": [
|
|
1116
|
-
"mpga",
|
|
1117
|
-
"mp2",
|
|
1118
|
-
"mp2a",
|
|
1119
|
-
"mp3",
|
|
1120
|
-
"m2a",
|
|
1121
|
-
"m3a"
|
|
1122
|
-
],
|
|
1123
|
-
"audio/ogg": [
|
|
1124
|
-
"oga",
|
|
1125
|
-
"ogg",
|
|
1126
|
-
"spx",
|
|
1127
|
-
"opus"
|
|
1128
|
-
],
|
|
1129
|
-
"audio/s3m": ["s3m"],
|
|
1130
|
-
"audio/silk": ["sil"],
|
|
1131
|
-
"audio/wav": ["wav"],
|
|
1132
|
-
"audio/wave": ["*wav"],
|
|
1133
|
-
"audio/webm": ["weba"],
|
|
1134
|
-
"audio/xm": ["xm"],
|
|
1135
|
-
"font/collection": ["ttc"],
|
|
1136
|
-
"font/otf": ["otf"],
|
|
1137
|
-
"font/ttf": ["ttf"],
|
|
1138
|
-
"font/woff": ["woff"],
|
|
1139
|
-
"font/woff2": ["woff2"],
|
|
1140
|
-
"image/aces": ["exr"],
|
|
1141
|
-
"image/apng": ["apng"],
|
|
1142
|
-
"image/avci": ["avci"],
|
|
1143
|
-
"image/avcs": ["avcs"],
|
|
1144
|
-
"image/avif": ["avif"],
|
|
1145
|
-
"image/bmp": ["bmp", "dib"],
|
|
1146
|
-
"image/cgm": ["cgm"],
|
|
1147
|
-
"image/dicom-rle": ["drle"],
|
|
1148
|
-
"image/dpx": ["dpx"],
|
|
1149
|
-
"image/emf": ["emf"],
|
|
1150
|
-
"image/fits": ["fits"],
|
|
1151
|
-
"image/g3fax": ["g3"],
|
|
1152
|
-
"image/gif": ["gif"],
|
|
1153
|
-
"image/heic": ["heic"],
|
|
1154
|
-
"image/heic-sequence": ["heics"],
|
|
1155
|
-
"image/heif": ["heif"],
|
|
1156
|
-
"image/heif-sequence": ["heifs"],
|
|
1157
|
-
"image/hej2k": ["hej2"],
|
|
1158
|
-
"image/ief": ["ief"],
|
|
1159
|
-
"image/jaii": ["jaii"],
|
|
1160
|
-
"image/jais": ["jais"],
|
|
1161
|
-
"image/jls": ["jls"],
|
|
1162
|
-
"image/jp2": ["jp2", "jpg2"],
|
|
1163
|
-
"image/jpeg": [
|
|
1164
|
-
"jpg",
|
|
1165
|
-
"jpeg",
|
|
1166
|
-
"jpe"
|
|
1167
|
-
],
|
|
1168
|
-
"image/jph": ["jph"],
|
|
1169
|
-
"image/jphc": ["jhc"],
|
|
1170
|
-
"image/jpm": ["jpm", "jpgm"],
|
|
1171
|
-
"image/jpx": ["jpx", "jpf"],
|
|
1172
|
-
"image/jxl": ["jxl"],
|
|
1173
|
-
"image/jxr": ["jxr"],
|
|
1174
|
-
"image/jxra": ["jxra"],
|
|
1175
|
-
"image/jxrs": ["jxrs"],
|
|
1176
|
-
"image/jxs": ["jxs"],
|
|
1177
|
-
"image/jxsc": ["jxsc"],
|
|
1178
|
-
"image/jxsi": ["jxsi"],
|
|
1179
|
-
"image/jxss": ["jxss"],
|
|
1180
|
-
"image/ktx": ["ktx"],
|
|
1181
|
-
"image/ktx2": ["ktx2"],
|
|
1182
|
-
"image/pjpeg": ["jfif"],
|
|
1183
|
-
"image/png": ["png"],
|
|
1184
|
-
"image/sgi": ["sgi"],
|
|
1185
|
-
"image/svg+xml": ["svg", "svgz"],
|
|
1186
|
-
"image/t38": ["t38"],
|
|
1187
|
-
"image/tiff": ["tif", "tiff"],
|
|
1188
|
-
"image/tiff-fx": ["tfx"],
|
|
1189
|
-
"image/webp": ["webp"],
|
|
1190
|
-
"image/wmf": ["wmf"],
|
|
1191
|
-
"message/disposition-notification": ["disposition-notification"],
|
|
1192
|
-
"message/global": ["u8msg"],
|
|
1193
|
-
"message/global-delivery-status": ["u8dsn"],
|
|
1194
|
-
"message/global-disposition-notification": ["u8mdn"],
|
|
1195
|
-
"message/global-headers": ["u8hdr"],
|
|
1196
|
-
"message/rfc822": [
|
|
1197
|
-
"eml",
|
|
1198
|
-
"mime",
|
|
1199
|
-
"mht",
|
|
1200
|
-
"mhtml"
|
|
1201
|
-
],
|
|
1202
|
-
"model/3mf": ["3mf"],
|
|
1203
|
-
"model/gltf+json": ["gltf"],
|
|
1204
|
-
"model/gltf-binary": ["glb"],
|
|
1205
|
-
"model/iges": ["igs", "iges"],
|
|
1206
|
-
"model/jt": ["jt"],
|
|
1207
|
-
"model/mesh": [
|
|
1208
|
-
"msh",
|
|
1209
|
-
"mesh",
|
|
1210
|
-
"silo"
|
|
1211
|
-
],
|
|
1212
|
-
"model/mtl": ["mtl"],
|
|
1213
|
-
"model/obj": ["obj"],
|
|
1214
|
-
"model/prc": ["prc"],
|
|
1215
|
-
"model/step": [
|
|
1216
|
-
"step",
|
|
1217
|
-
"stp",
|
|
1218
|
-
"stpnc",
|
|
1219
|
-
"p21",
|
|
1220
|
-
"210"
|
|
1221
|
-
],
|
|
1222
|
-
"model/step+xml": ["stpx"],
|
|
1223
|
-
"model/step+zip": ["stpz"],
|
|
1224
|
-
"model/step-xml+zip": ["stpxz"],
|
|
1225
|
-
"model/stl": ["stl"],
|
|
1226
|
-
"model/u3d": ["u3d"],
|
|
1227
|
-
"model/vrml": ["wrl", "vrml"],
|
|
1228
|
-
"model/x3d+binary": ["*x3db", "x3dbz"],
|
|
1229
|
-
"model/x3d+fastinfoset": ["x3db"],
|
|
1230
|
-
"model/x3d+vrml": ["*x3dv", "x3dvz"],
|
|
1231
|
-
"model/x3d+xml": ["x3d", "x3dz"],
|
|
1232
|
-
"model/x3d-vrml": ["x3dv"],
|
|
1233
|
-
"text/cache-manifest": ["appcache", "manifest"],
|
|
1234
|
-
"text/calendar": ["ics", "ifb"],
|
|
1235
|
-
"text/coffeescript": ["coffee", "litcoffee"],
|
|
1236
|
-
"text/css": ["css"],
|
|
1237
|
-
"text/csv": ["csv"],
|
|
1238
|
-
"text/html": [
|
|
1239
|
-
"html",
|
|
1240
|
-
"htm",
|
|
1241
|
-
"shtml"
|
|
1242
|
-
],
|
|
1243
|
-
"text/jade": ["jade"],
|
|
1244
|
-
"text/javascript": ["js", "mjs"],
|
|
1245
|
-
"text/jsx": ["jsx"],
|
|
1246
|
-
"text/less": ["less"],
|
|
1247
|
-
"text/markdown": ["md", "markdown"],
|
|
1248
|
-
"text/mathml": ["mml"],
|
|
1249
|
-
"text/mdx": ["mdx"],
|
|
1250
|
-
"text/n3": ["n3"],
|
|
1251
|
-
"text/plain": [
|
|
1252
|
-
"txt",
|
|
1253
|
-
"text",
|
|
1254
|
-
"conf",
|
|
1255
|
-
"def",
|
|
1256
|
-
"list",
|
|
1257
|
-
"log",
|
|
1258
|
-
"in",
|
|
1259
|
-
"ini"
|
|
1260
|
-
],
|
|
1261
|
-
"text/richtext": ["rtx"],
|
|
1262
|
-
"text/rtf": ["*rtf"],
|
|
1263
|
-
"text/sgml": ["sgml", "sgm"],
|
|
1264
|
-
"text/shex": ["shex"],
|
|
1265
|
-
"text/slim": ["slim", "slm"],
|
|
1266
|
-
"text/spdx": ["spdx"],
|
|
1267
|
-
"text/stylus": ["stylus", "styl"],
|
|
1268
|
-
"text/tab-separated-values": ["tsv"],
|
|
1269
|
-
"text/troff": [
|
|
1270
|
-
"t",
|
|
1271
|
-
"tr",
|
|
1272
|
-
"roff",
|
|
1273
|
-
"man",
|
|
1274
|
-
"me",
|
|
1275
|
-
"ms"
|
|
1276
|
-
],
|
|
1277
|
-
"text/turtle": ["ttl"],
|
|
1278
|
-
"text/uri-list": [
|
|
1279
|
-
"uri",
|
|
1280
|
-
"uris",
|
|
1281
|
-
"urls"
|
|
1282
|
-
],
|
|
1283
|
-
"text/vcard": ["vcard"],
|
|
1284
|
-
"text/vtt": ["vtt"],
|
|
1285
|
-
"text/wgsl": ["wgsl"],
|
|
1286
|
-
"text/xml": ["*xml"],
|
|
1287
|
-
"text/yaml": ["yaml", "yml"],
|
|
1288
|
-
"video/3gpp": ["3gp", "3gpp"],
|
|
1289
|
-
"video/3gpp2": ["3g2"],
|
|
1290
|
-
"video/h261": ["h261"],
|
|
1291
|
-
"video/h263": ["h263"],
|
|
1292
|
-
"video/h264": ["h264"],
|
|
1293
|
-
"video/iso.segment": ["m4s"],
|
|
1294
|
-
"video/jpeg": ["jpgv"],
|
|
1295
|
-
"video/jpm": ["*jpm", "*jpgm"],
|
|
1296
|
-
"video/mj2": ["mj2", "mjp2"],
|
|
1297
|
-
"video/mp2t": [
|
|
1298
|
-
"ts",
|
|
1299
|
-
"m2t",
|
|
1300
|
-
"m2ts",
|
|
1301
|
-
"mts"
|
|
1302
|
-
],
|
|
1303
|
-
"video/mp4": [
|
|
1304
|
-
"mp4",
|
|
1305
|
-
"mp4v",
|
|
1306
|
-
"mpg4"
|
|
1307
|
-
],
|
|
1308
|
-
"video/mpeg": [
|
|
1309
|
-
"mpeg",
|
|
1310
|
-
"mpg",
|
|
1311
|
-
"mpe",
|
|
1312
|
-
"m1v",
|
|
1313
|
-
"m2v"
|
|
1314
|
-
],
|
|
1315
|
-
"video/ogg": ["ogv"],
|
|
1316
|
-
"video/quicktime": ["qt", "mov"],
|
|
1317
|
-
"video/webm": ["webm"]
|
|
1318
|
-
};
|
|
1319
|
-
Object.freeze(types);
|
|
7
|
+
import { getSignedUrl } from "@aws-sdk/cloudfront-signer";
|
|
8
|
+
import { DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
|
|
9
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
|
10
|
+
//#region src/cloudFrontInvalidation.ts
|
|
11
|
+
const DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 300 * 1e3;
|
|
13
|
+
const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
14
|
+
const invalidateCloudFront = async (client, distributionId, paths, options) => {
|
|
15
|
+
if (paths.length === 0) return;
|
|
16
|
+
const response = await client.send(new CreateInvalidationCommand({
|
|
17
|
+
DistributionId: distributionId,
|
|
18
|
+
InvalidationBatch: {
|
|
19
|
+
CallerReference: `invalidation-${Date.now()}`,
|
|
20
|
+
Paths: {
|
|
21
|
+
Quantity: paths.length,
|
|
22
|
+
Items: paths.map((path) => encodeURI(path))
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
if (!options.shouldWait || response.Invalidation?.Status === "Completed") return;
|
|
27
|
+
const invalidationId = response.Invalidation?.Id;
|
|
28
|
+
if (!invalidationId) throw new Error("CloudFront invalidation response is missing Invalidation.Id");
|
|
29
|
+
const deadline = Date.now() + DEFAULT_TIMEOUT_MS;
|
|
30
|
+
while (Date.now() < deadline) {
|
|
31
|
+
await sleep(DEFAULT_POLL_INTERVAL_MS);
|
|
32
|
+
if ((await client.send(new GetInvalidationCommand({
|
|
33
|
+
DistributionId: distributionId,
|
|
34
|
+
Id: invalidationId
|
|
35
|
+
}))).Invalidation?.Status === "Completed") return;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Timed out waiting for CloudFront invalidation ${invalidationId}.`);
|
|
38
|
+
};
|
|
1320
39
|
//#endregion
|
|
1321
|
-
//#region
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
40
|
+
//#region src/dynamoDB.ts
|
|
41
|
+
const DYNAMODB_MAX_METADATA_ITEM_BYTES = 8 * 1024;
|
|
42
|
+
const DYNAMODB_MAX_CATALOG_ITEM_BYTES = 400 * 1024;
|
|
43
|
+
var DynamoDBMetadataItemSizeError = class extends Error {
|
|
44
|
+
name = "DynamoDBMetadataItemSizeError";
|
|
45
|
+
constructor(byteLength) {
|
|
46
|
+
super(`DynamoDB metadata item is ${byteLength} bytes; maximum is ${DYNAMODB_MAX_METADATA_ITEM_BYTES}`);
|
|
47
|
+
this.byteLength = byteLength;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const boundedDynamoDBMetadataItem = (item) => {
|
|
51
|
+
const byteLength = new TextEncoder().encode(JSON.stringify(item)).byteLength;
|
|
52
|
+
if (byteLength > 8192) throw new DynamoDBMetadataItemSizeError(byteLength);
|
|
53
|
+
return item;
|
|
54
|
+
};
|
|
55
|
+
var DynamoDBCatalogItemSizeError = class extends Error {
|
|
56
|
+
name = "DynamoDBCatalogItemSizeError";
|
|
57
|
+
constructor(byteLength) {
|
|
58
|
+
super(`DynamoDB catalog item is ${byteLength} bytes; maximum is ${DYNAMODB_MAX_CATALOG_ITEM_BYTES}`);
|
|
59
|
+
this.byteLength = byteLength;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const boundedDynamoDBCatalogItem = (item) => {
|
|
63
|
+
const byteLength = new TextEncoder().encode(JSON.stringify(item)).byteLength;
|
|
64
|
+
if (byteLength > 409600) throw new DynamoDBCatalogItemSizeError(byteLength);
|
|
65
|
+
return item;
|
|
66
|
+
};
|
|
67
|
+
const DYNAMODB_CHANNEL_PARTITION = "channels";
|
|
68
|
+
const DYNAMODB_CHANNEL_NAME_PARTITION = "_hot-updater#channel-names";
|
|
69
|
+
var DynamoDBStoredItemError = class extends Error {
|
|
70
|
+
name = "DynamoDBStoredItemError";
|
|
71
|
+
constructor() {
|
|
72
|
+
super("DynamoDB contains an invalid Hot Updater row");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const field = (value, name) => Reflect.get(value, name);
|
|
76
|
+
const isNullableString = (value) => value === null || typeof value === "string";
|
|
77
|
+
const isByteSize = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
78
|
+
const withLegacyArchiveByteSize = (value) => typeof value === "object" && value !== null && field(value, "archive_byte_size") === void 0 ? {
|
|
79
|
+
...value,
|
|
80
|
+
archive_byte_size: 0
|
|
81
|
+
} : value;
|
|
82
|
+
const isBundleRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "file_hash") === "string" && isNullableString(field(value, "git_commit_hash")) && typeof field(value, "storage_uri") === "string" && isByteSize(field(value, "archive_byte_size")) && isDatabaseMetadataObject(field(value, "metadata")) && isNullableString(field(value, "manifest_storage_uri")) && isNullableString(field(value, "manifest_file_hash")) && isNullableString(field(value, "asset_base_storage_uri"));
|
|
83
|
+
const isPatchRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "bundle_id") === "string" && typeof field(value, "base_bundle_id") === "string" && typeof field(value, "base_file_hash") === "string" && typeof field(value, "patch_file_hash") === "string" && typeof field(value, "patch_storage_uri") === "string" && isByteSize(field(value, "byte_size")) && typeof field(value, "order_index") === "number";
|
|
84
|
+
const isChannelRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "name") === "string";
|
|
85
|
+
const isReleaseRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "revision") === "number" && typeof field(value, "scope_key") === "string" && typeof field(value, "channel_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && (field(value, "kind") === "BUNDLE" || field(value, "kind") === "EMBEDDED") && isNullableString(field(value, "bundle_id")) && (field(value, "strategy") === "APP_VERSION" || field(value, "strategy") === "FINGERPRINT") && isNullableString(field(value, "target_app_version")) && isNullableString(field(value, "fingerprint_hash")) && typeof field(value, "enabled") === "boolean" && typeof field(value, "should_force_update") === "boolean" && isNullableString(field(value, "message")) && typeof field(value, "rollout_cohort_count") === "number" && Array.isArray(field(value, "target_cohorts")) && field(value, "target_cohorts").every((cohort) => typeof cohort === "string") && (field(value, "operation") === "DEPLOY" || field(value, "operation") === "PROMOTE" || field(value, "operation") === "ROLLBACK") && isNullableString(field(value, "source_release_id")) && typeof field(value, "created_at_ms") === "number" && typeof field(value, "updated_at_ms") === "number";
|
|
86
|
+
const isReleaseCatalogRow = (value) => typeof value === "object" && value !== null && typeof field(value, "scope_key") === "string" && typeof field(value, "catalog_id") === "string" && (field(value, "strategy") === "APP_VERSION" || field(value, "strategy") === "FINGERPRINT") && typeof field(value, "channel_id") === "string" && typeof field(value, "channel_key") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && isNullableString(field(value, "fingerprint_hash")) && typeof field(value, "generation") === "number" && typeof field(value, "payload") === "string" && typeof field(value, "catalog_hash") === "string" && typeof field(value, "byte_size") === "number" && typeof field(value, "is_tombstone") === "boolean" && typeof field(value, "updated_at_ms") === "number";
|
|
87
|
+
const parseDynamoDBItem = (value) => {
|
|
88
|
+
const pk = value.pk;
|
|
89
|
+
const sk = value.sk;
|
|
90
|
+
const version = value.version;
|
|
91
|
+
const relationCount = value.relation_count;
|
|
92
|
+
const ownedPatchCount = value.owned_patch_count;
|
|
93
|
+
const gsi1pk = value.gsi1pk;
|
|
94
|
+
const gsi1sk = value.gsi1sk;
|
|
95
|
+
const row = pk === "bundles" ? withLegacyArchiveByteSize(value.row) : value.row;
|
|
96
|
+
if (typeof sk !== "string" || typeof version !== "number") throw new DynamoDBStoredItemError();
|
|
97
|
+
if (pk === "channels" && typeof value.reference_count === "number" && isChannelRow(row)) return {
|
|
98
|
+
pk,
|
|
99
|
+
sk,
|
|
100
|
+
version,
|
|
101
|
+
reference_count: value.reference_count,
|
|
102
|
+
row
|
|
103
|
+
};
|
|
104
|
+
if (typeof pk === "string" && pk.startsWith("release-scope#") && isReleaseRow(row)) return {
|
|
105
|
+
pk,
|
|
106
|
+
sk,
|
|
107
|
+
version,
|
|
108
|
+
row
|
|
109
|
+
};
|
|
110
|
+
if (pk === "release_catalogs" && isReleaseCatalogRow(row)) return {
|
|
111
|
+
pk,
|
|
112
|
+
sk,
|
|
113
|
+
version,
|
|
114
|
+
row
|
|
115
|
+
};
|
|
116
|
+
if (typeof gsi1pk !== "string" || typeof gsi1sk !== "string") throw new DynamoDBStoredItemError();
|
|
117
|
+
if (pk === "bundles" && isBundleRow(row)) {
|
|
118
|
+
if (typeof relationCount !== "number" || typeof ownedPatchCount !== "number") throw new DynamoDBStoredItemError();
|
|
119
|
+
return {
|
|
120
|
+
pk,
|
|
121
|
+
sk,
|
|
122
|
+
version,
|
|
123
|
+
relation_count: relationCount,
|
|
124
|
+
owned_patch_count: ownedPatchCount,
|
|
125
|
+
gsi1pk,
|
|
126
|
+
gsi1sk,
|
|
127
|
+
row
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (pk === "bundle_patches" && isPatchRow(row)) return {
|
|
131
|
+
pk,
|
|
132
|
+
sk,
|
|
133
|
+
version,
|
|
134
|
+
gsi1pk,
|
|
135
|
+
gsi1sk,
|
|
136
|
+
row
|
|
137
|
+
};
|
|
138
|
+
throw new DynamoDBStoredItemError();
|
|
139
|
+
};
|
|
140
|
+
const bundlePlatformPartition = (row) => `bundle#${row.platform}`;
|
|
141
|
+
const patchOwnerPartition = (bundleId) => `patch-owner#${bundleId}`;
|
|
142
|
+
const patchOrderKey = (row) => `${row.order_index.toString().padStart(10, "0")}#${row.id}`;
|
|
143
|
+
const toDynamoDBBundleItem = (row, version = 1, relationCount = 0, ownedPatchCount = 0) => ({
|
|
144
|
+
pk: "bundles",
|
|
145
|
+
sk: row.id,
|
|
146
|
+
version,
|
|
147
|
+
relation_count: relationCount,
|
|
148
|
+
owned_patch_count: ownedPatchCount,
|
|
149
|
+
gsi1pk: bundlePlatformPartition(row),
|
|
150
|
+
gsi1sk: row.id,
|
|
151
|
+
row
|
|
152
|
+
});
|
|
153
|
+
const toDynamoDBPatchItem = (row, version = 1) => {
|
|
154
|
+
const orderKey = patchOrderKey(row);
|
|
155
|
+
return {
|
|
156
|
+
pk: "bundle_patches",
|
|
157
|
+
sk: row.id,
|
|
158
|
+
version,
|
|
159
|
+
gsi1pk: patchOwnerPartition(row.bundle_id),
|
|
160
|
+
gsi1sk: orderKey,
|
|
161
|
+
row
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const toDynamoDBChannelItem = (row, version = 1, referenceCount = 0) => ({
|
|
165
|
+
pk: DYNAMODB_CHANNEL_PARTITION,
|
|
166
|
+
sk: row.id,
|
|
167
|
+
version,
|
|
168
|
+
reference_count: referenceCount,
|
|
169
|
+
row
|
|
170
|
+
});
|
|
171
|
+
const releaseScopePartition = (scopeKey) => `release-scope#${scopeKey}`;
|
|
172
|
+
const toDynamoDBReleaseItem = (row, version = 1) => ({
|
|
173
|
+
pk: releaseScopePartition(row.scope_key),
|
|
174
|
+
sk: row.id,
|
|
175
|
+
version,
|
|
176
|
+
row
|
|
177
|
+
});
|
|
178
|
+
const toDynamoDBReleaseCatalogItem = (row, version = 1) => ({
|
|
179
|
+
pk: "release_catalogs",
|
|
180
|
+
sk: row.scope_key,
|
|
181
|
+
version,
|
|
182
|
+
row
|
|
183
|
+
});
|
|
184
|
+
const itemKey = (model, id) => ({
|
|
185
|
+
pk: model,
|
|
186
|
+
sk: id
|
|
187
|
+
});
|
|
188
|
+
const exactDynamoDBId = (where) => {
|
|
189
|
+
if (where?.length !== 1) return void 0;
|
|
190
|
+
const condition = where[0];
|
|
191
|
+
if (Reflect.get(condition, "field") !== "id" || (Reflect.get(condition, "operator") ?? "eq") !== "eq" || Reflect.get(condition, "mode") === "insensitive") return;
|
|
192
|
+
const value = Reflect.get(condition, "value");
|
|
193
|
+
return typeof value === "string" ? value : void 0;
|
|
194
|
+
};
|
|
195
|
+
const exactDynamoDBPatchOwner = (where) => {
|
|
196
|
+
const owners = exactDynamoDBPatchOwners(where);
|
|
197
|
+
return owners?.length === 1 ? owners[0] : void 0;
|
|
198
|
+
};
|
|
199
|
+
const exactDynamoDBPatchOwners = (where) => {
|
|
200
|
+
if (!where || where.some((condition) => Reflect.get(condition, "connector") === "OR")) return;
|
|
201
|
+
const ownerConditions = where.filter((condition) => Reflect.get(condition, "field") === "bundle_id");
|
|
202
|
+
if (ownerConditions.length !== 1) return void 0;
|
|
203
|
+
const condition = ownerConditions[0];
|
|
204
|
+
if (Reflect.get(condition, "mode") === "insensitive") return;
|
|
205
|
+
const operator = Reflect.get(condition, "operator") ?? "eq";
|
|
206
|
+
const value = Reflect.get(condition, "value");
|
|
207
|
+
if (operator === "eq") return typeof value === "string" ? [value] : void 0;
|
|
208
|
+
if (operator === "in" && Array.isArray(value) && value.every((id) => typeof id === "string")) return value;
|
|
209
|
+
};
|
|
210
|
+
const exactDynamoDBBundleIds = (where) => {
|
|
211
|
+
if (!where || where.some((condition) => Reflect.get(condition, "connector") === "OR")) return;
|
|
212
|
+
for (const condition of where) {
|
|
213
|
+
if (Reflect.get(condition, "field") !== "id" || Reflect.get(condition, "mode") === "insensitive") continue;
|
|
214
|
+
const operator = Reflect.get(condition, "operator") ?? "eq";
|
|
215
|
+
const value = Reflect.get(condition, "value");
|
|
216
|
+
if (operator === "eq" && typeof value === "string") return [value];
|
|
217
|
+
if (operator === "in" && Array.isArray(value) && value.every((id) => typeof id === "string")) return value;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const dynamoDBBundlePageStart = (where, direction) => {
|
|
221
|
+
if ((where ?? []).some((condition) => Reflect.get(condition, "connector") === "OR")) return;
|
|
222
|
+
let result;
|
|
223
|
+
for (const condition of where ?? []) {
|
|
224
|
+
if (Reflect.get(condition, "field") !== "id") continue;
|
|
225
|
+
const operator = Reflect.get(condition, "operator") ?? "eq";
|
|
226
|
+
const value = Reflect.get(condition, "value");
|
|
227
|
+
if (!(direction === "asc" ? operator === "gt" || operator === "gte" : operator === "lt" || operator === "lte") || typeof value !== "string") continue;
|
|
228
|
+
const inclusive = operator === "gte" || operator === "lte";
|
|
229
|
+
if (result === void 0 || (direction === "asc" ? value > result.id : value < result.id) || value === result.id && !inclusive) result = {
|
|
230
|
+
id: value,
|
|
231
|
+
inclusive
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
};
|
|
236
|
+
const parseStoredItem = (item) => parseDynamoDBItem(item.pk === "release_catalogs" ? boundedDynamoDBCatalogItem(item) : boundedDynamoDBMetadataItem(item));
|
|
237
|
+
const queryItems = async (store, input) => {
|
|
238
|
+
let exclusiveStartKey;
|
|
239
|
+
const items = [];
|
|
240
|
+
do {
|
|
241
|
+
const sortCondition = input.minimumSortKey ? " AND #sortKey >= :minimumSortKey" : "";
|
|
242
|
+
const page = await store.client.send(new QueryCommand({
|
|
243
|
+
TableName: store.tableName,
|
|
244
|
+
...input.indexName ? { IndexName: input.indexName } : {},
|
|
245
|
+
...input.consistentRead ? { ConsistentRead: true } : {},
|
|
246
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
247
|
+
KeyConditionExpression: `#partitionKey = :partition${sortCondition}`,
|
|
248
|
+
ExpressionAttributeNames: {
|
|
249
|
+
"#partitionKey": input.indexName ? "gsi1pk" : "pk",
|
|
250
|
+
...input.minimumSortKey ? { "#sortKey": input.indexName ? "gsi1sk" : "sk" } : {}
|
|
251
|
+
},
|
|
252
|
+
ExpressionAttributeValues: {
|
|
253
|
+
":partition": input.partition,
|
|
254
|
+
...input.minimumSortKey ? { ":minimumSortKey": input.minimumSortKey } : {}
|
|
255
|
+
}
|
|
256
|
+
}));
|
|
257
|
+
const pageItems = (page.Items ?? []).map(parseStoredItem);
|
|
258
|
+
items.push(...pageItems);
|
|
259
|
+
exclusiveStartKey = page.LastEvaluatedKey;
|
|
260
|
+
} while (exclusiveStartKey !== void 0);
|
|
261
|
+
return items;
|
|
262
|
+
};
|
|
263
|
+
const queryBundleItemsPage = async (store, input) => {
|
|
264
|
+
if (input.limit === 0) return [];
|
|
265
|
+
let exclusiveStartKey;
|
|
266
|
+
const items = [];
|
|
267
|
+
do {
|
|
268
|
+
const startOperator = input.start?.inclusive ? input.direction === "asc" ? ">=" : "<=" : input.direction === "asc" ? ">" : "<";
|
|
269
|
+
const page = await store.client.send(new QueryCommand({
|
|
270
|
+
TableName: store.tableName,
|
|
271
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
272
|
+
KeyConditionExpression: input.start ? `#pk = :pk AND #sk ${startOperator} :start` : "#pk = :pk",
|
|
273
|
+
ExpressionAttributeNames: {
|
|
274
|
+
"#pk": "pk",
|
|
275
|
+
...input.start ? { "#sk": "sk" } : {}
|
|
276
|
+
},
|
|
277
|
+
ExpressionAttributeValues: {
|
|
278
|
+
":pk": "bundles",
|
|
279
|
+
...input.start ? { ":start": input.start.id } : {}
|
|
280
|
+
},
|
|
281
|
+
Limit: Math.max(input.limit - items.length, 100),
|
|
282
|
+
ScanIndexForward: input.direction === "asc"
|
|
283
|
+
}));
|
|
284
|
+
for (const item of (page.Items ?? []).map(parseStoredItem)) {
|
|
285
|
+
if (item.pk === "bundles" && input.matches(item.row)) items.push(item);
|
|
286
|
+
if (items.length === input.limit) return items;
|
|
287
|
+
}
|
|
288
|
+
exclusiveStartKey = page.LastEvaluatedKey;
|
|
289
|
+
} while (exclusiveStartKey !== void 0);
|
|
290
|
+
return items;
|
|
291
|
+
};
|
|
292
|
+
const loadModelItems = (store, model) => queryItems(store, {
|
|
293
|
+
partition: model,
|
|
294
|
+
consistentRead: true
|
|
295
|
+
});
|
|
296
|
+
const loadModelItem = async (store, model, id) => {
|
|
297
|
+
const { Item } = await store.client.send(new GetCommand({
|
|
298
|
+
TableName: store.tableName,
|
|
299
|
+
Key: itemKey(model, id),
|
|
300
|
+
ConsistentRead: true
|
|
301
|
+
}));
|
|
302
|
+
return Item === void 0 ? void 0 : parseStoredItem(Item);
|
|
303
|
+
};
|
|
304
|
+
const loadBundleItem = async (store, id) => {
|
|
305
|
+
const item = await loadModelItem(store, "bundles", id);
|
|
306
|
+
return item?.pk === "bundles" ? item : void 0;
|
|
307
|
+
};
|
|
308
|
+
const loadPatchItem = async (store, id) => {
|
|
309
|
+
const item = await loadModelItem(store, "bundle_patches", id);
|
|
310
|
+
return item?.pk === "bundle_patches" ? item : void 0;
|
|
311
|
+
};
|
|
312
|
+
const loadChannelItem = async (store, id) => {
|
|
313
|
+
const item = await loadModelItem(store, DYNAMODB_CHANNEL_PARTITION, id);
|
|
314
|
+
return item?.pk === "channels" ? item : void 0;
|
|
315
|
+
};
|
|
316
|
+
const loadBundleItems = async (store) => {
|
|
317
|
+
return (await loadModelItems(store, "bundles")).filter((item) => item.pk === "bundles");
|
|
318
|
+
};
|
|
319
|
+
const loadPatchItems = async (store) => {
|
|
320
|
+
return (await loadModelItems(store, "bundle_patches")).filter((item) => item.pk === "bundle_patches");
|
|
321
|
+
};
|
|
322
|
+
const loadChannelItems = async (store) => {
|
|
323
|
+
return (await loadModelItems(store, DYNAMODB_CHANNEL_PARTITION)).filter((item) => item.pk === DYNAMODB_CHANNEL_PARTITION);
|
|
324
|
+
};
|
|
325
|
+
const DYNAMODB_RELEASE_ID_PARTITION = "_hot-updater#release-scope-by-id";
|
|
326
|
+
const releaseLocatorItem = (row) => ({
|
|
327
|
+
pk: DYNAMODB_RELEASE_ID_PARTITION,
|
|
328
|
+
sk: row.id,
|
|
329
|
+
scope_key: row.scope_key
|
|
330
|
+
});
|
|
331
|
+
const loadReleaseItem = async (store, id) => {
|
|
332
|
+
const locator = await store.client.send(new GetCommand({
|
|
333
|
+
TableName: store.tableName,
|
|
334
|
+
Key: {
|
|
335
|
+
pk: DYNAMODB_RELEASE_ID_PARTITION,
|
|
336
|
+
sk: id
|
|
337
|
+
},
|
|
338
|
+
ConsistentRead: true
|
|
339
|
+
}));
|
|
340
|
+
if (locator.Item === void 0) return void 0;
|
|
341
|
+
const scopeKey = locator.Item.scope_key;
|
|
342
|
+
if (typeof scopeKey !== "string") throw new DynamoDBStoredItemError();
|
|
343
|
+
const result = await store.client.send(new GetCommand({
|
|
344
|
+
TableName: store.tableName,
|
|
345
|
+
Key: {
|
|
346
|
+
pk: releaseScopePartition(scopeKey),
|
|
347
|
+
sk: id
|
|
348
|
+
},
|
|
349
|
+
ConsistentRead: true
|
|
350
|
+
}));
|
|
351
|
+
if (result.Item === void 0) throw new DynamoDBStoredItemError();
|
|
352
|
+
const item = parseStoredItem(result.Item);
|
|
353
|
+
if (!isReleaseRow(item.row) || item.sk !== id) throw new DynamoDBStoredItemError();
|
|
354
|
+
return item;
|
|
355
|
+
};
|
|
356
|
+
const loadReleaseItemsByScope = async (store, scopeKey) => (await queryItems(store, {
|
|
357
|
+
partition: releaseScopePartition(scopeKey),
|
|
358
|
+
consistentRead: true
|
|
359
|
+
})).filter((item) => item.pk === releaseScopePartition(scopeKey) && isReleaseRow(item.row));
|
|
360
|
+
const loadReleaseItems = async (store) => {
|
|
361
|
+
let exclusiveStartKey;
|
|
362
|
+
const items = [];
|
|
363
|
+
do {
|
|
364
|
+
const page = await store.client.send(new ScanCommand({
|
|
365
|
+
TableName: store.tableName,
|
|
366
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
367
|
+
ConsistentRead: true,
|
|
368
|
+
FilterExpression: "begins_with(#pk, :prefix)",
|
|
369
|
+
ExpressionAttributeNames: { "#pk": "pk" },
|
|
370
|
+
ExpressionAttributeValues: { ":prefix": "release-scope#" }
|
|
371
|
+
}));
|
|
372
|
+
for (const value of page.Items ?? []) {
|
|
373
|
+
const item = parseStoredItem(value);
|
|
374
|
+
if (isReleaseRow(item.row)) items.push(item);
|
|
375
|
+
}
|
|
376
|
+
exclusiveStartKey = page.LastEvaluatedKey;
|
|
377
|
+
} while (exclusiveStartKey !== void 0);
|
|
378
|
+
return items;
|
|
379
|
+
};
|
|
380
|
+
const loadReleaseCatalogItem = async (store, scopeKey) => {
|
|
381
|
+
const result = await store.client.send(new GetCommand({
|
|
382
|
+
TableName: store.tableName,
|
|
383
|
+
Key: itemKey("release_catalogs", scopeKey),
|
|
384
|
+
ConsistentRead: true
|
|
385
|
+
}));
|
|
386
|
+
if (result.Item === void 0) return void 0;
|
|
387
|
+
const item = parseStoredItem(result.Item);
|
|
388
|
+
return item.pk === "release_catalogs" ? item : void 0;
|
|
389
|
+
};
|
|
390
|
+
const loadReleaseCatalogItems = async (store) => (await queryItems(store, {
|
|
391
|
+
partition: "release_catalogs",
|
|
392
|
+
consistentRead: true
|
|
393
|
+
})).filter((item) => item.pk === "release_catalogs");
|
|
394
|
+
const loadChannelByName = async (store, name) => {
|
|
395
|
+
const { Item } = await store.client.send(new GetCommand({
|
|
396
|
+
TableName: store.tableName,
|
|
397
|
+
Key: {
|
|
398
|
+
pk: DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
399
|
+
sk: name
|
|
400
|
+
},
|
|
401
|
+
ConsistentRead: true
|
|
402
|
+
}));
|
|
403
|
+
const id = Item?.channel_id;
|
|
404
|
+
if (Item === void 0) return void 0;
|
|
405
|
+
if (typeof id !== "string") throw new DynamoDBStoredItemError();
|
|
406
|
+
const channel = await loadChannelItem(store, id);
|
|
407
|
+
if (channel === void 0 || channel.row.name !== name) throw new DynamoDBStoredItemError();
|
|
408
|
+
return channel;
|
|
409
|
+
};
|
|
410
|
+
const insertDynamoDBChannel = async (store, input) => {
|
|
411
|
+
const existing = await loadChannelByName(store, input.row.name);
|
|
412
|
+
if (existing !== void 0) return {
|
|
413
|
+
row: existing.row,
|
|
414
|
+
inserted: false
|
|
415
|
+
};
|
|
416
|
+
const existingId = await loadChannelItem(store, input.row.id);
|
|
417
|
+
if (existingId !== void 0) {
|
|
418
|
+
if (existingId.row.name === input.row.name) return {
|
|
419
|
+
row: existingId.row,
|
|
420
|
+
inserted: false
|
|
421
|
+
};
|
|
422
|
+
throw new DynamoDBStoredItemError();
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
await commitDynamoDBTransaction(store, [{ Put: {
|
|
426
|
+
TableName: store.tableName,
|
|
427
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBChannelItem(input.row)),
|
|
428
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
429
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
430
|
+
} }, { Put: {
|
|
431
|
+
TableName: store.tableName,
|
|
432
|
+
Item: {
|
|
433
|
+
pk: DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
434
|
+
sk: input.row.name,
|
|
435
|
+
channel_id: input.row.id
|
|
436
|
+
},
|
|
437
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
438
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
439
|
+
} }]);
|
|
440
|
+
return {
|
|
441
|
+
row: input.row,
|
|
442
|
+
inserted: true
|
|
443
|
+
};
|
|
444
|
+
} catch (error) {
|
|
445
|
+
const canonical = await loadChannelByName(store, input.row.name);
|
|
446
|
+
if (canonical !== void 0) return {
|
|
447
|
+
row: canonical.row,
|
|
448
|
+
inserted: false
|
|
449
|
+
};
|
|
450
|
+
throw error;
|
|
1334
451
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
452
|
+
};
|
|
453
|
+
const deleteDynamoDBChannel = async (store, input) => {
|
|
454
|
+
const current = await loadChannelItem(store, input.id);
|
|
455
|
+
if (current === void 0) return {
|
|
456
|
+
deleted: false,
|
|
457
|
+
reason: "not_found"
|
|
458
|
+
};
|
|
459
|
+
if (current.reference_count !== 0) return {
|
|
460
|
+
deleted: false,
|
|
461
|
+
reason: "not_empty"
|
|
462
|
+
};
|
|
463
|
+
try {
|
|
464
|
+
await commitDynamoDBTransaction(store, [{ Delete: {
|
|
465
|
+
TableName: store.tableName,
|
|
466
|
+
Key: itemKey(DYNAMODB_CHANNEL_PARTITION, current.sk),
|
|
467
|
+
ConditionExpression: "#version = :version AND #referenceCount = :zero",
|
|
468
|
+
ExpressionAttributeNames: {
|
|
469
|
+
"#version": "version",
|
|
470
|
+
"#referenceCount": "reference_count"
|
|
471
|
+
},
|
|
472
|
+
ExpressionAttributeValues: {
|
|
473
|
+
":version": current.version,
|
|
474
|
+
":zero": 0
|
|
1352
475
|
}
|
|
476
|
+
} }, { Delete: {
|
|
477
|
+
TableName: store.tableName,
|
|
478
|
+
Key: {
|
|
479
|
+
pk: DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
480
|
+
sk: current.row.name
|
|
481
|
+
},
|
|
482
|
+
ConditionExpression: "#channelId = :channelId",
|
|
483
|
+
ExpressionAttributeNames: { "#channelId": "channel_id" },
|
|
484
|
+
ExpressionAttributeValues: { ":channelId": current.sk }
|
|
485
|
+
} }]);
|
|
486
|
+
return { deleted: true };
|
|
487
|
+
} catch (error) {
|
|
488
|
+
const latest = await loadChannelItem(store, input.id);
|
|
489
|
+
if (latest !== void 0 && latest.reference_count > 0) return {
|
|
490
|
+
deleted: false,
|
|
491
|
+
reason: "not_empty"
|
|
492
|
+
};
|
|
493
|
+
if (latest === void 0) return {
|
|
494
|
+
deleted: false,
|
|
495
|
+
reason: "not_found"
|
|
496
|
+
};
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
const queryOwnerPatchIds = async (store, indexName, bundleId) => {
|
|
501
|
+
return (await queryItems(store, {
|
|
502
|
+
partition: patchOwnerPartition(bundleId),
|
|
503
|
+
indexName
|
|
504
|
+
})).filter((item) => item.pk === "bundle_patches").map(({ sk }) => sk);
|
|
505
|
+
};
|
|
506
|
+
var DynamoDBTransactionLimitError = class extends Error {
|
|
507
|
+
name = "DynamoDBTransactionLimitError";
|
|
508
|
+
constructor(actionCount) {
|
|
509
|
+
super(`DynamoDB transaction requires ${actionCount} actions; maximum is 100`);
|
|
510
|
+
this.actionCount = actionCount;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
const metadataUpdate = (store, delta) => {
|
|
514
|
+
const entries = Object.entries(delta).filter(([, value]) => value !== 0);
|
|
515
|
+
if (entries.length === 0) return void 0;
|
|
516
|
+
const names = {};
|
|
517
|
+
const values = {};
|
|
518
|
+
const updates = [];
|
|
519
|
+
const conditions = [];
|
|
520
|
+
for (const [model, change] of entries) {
|
|
521
|
+
const name = model === "bundles" ? "bundles" : "patches";
|
|
522
|
+
const valueName = model === "bundles" ? "bundle" : "patch";
|
|
523
|
+
names[`#${name}`] = name;
|
|
524
|
+
values[`:${valueName}Delta`] = change;
|
|
525
|
+
updates.push(`#${name} :${valueName}Delta`);
|
|
526
|
+
if (change < 0) {
|
|
527
|
+
values[`:${valueName}Removal`] = -change;
|
|
528
|
+
conditions.push(`#${name} >= :${valueName}Removal`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return { Update: {
|
|
532
|
+
TableName: store.tableName,
|
|
533
|
+
Key: {
|
|
534
|
+
pk: "_hot-updater",
|
|
535
|
+
sk: "limits.metadata"
|
|
536
|
+
},
|
|
537
|
+
UpdateExpression: `ADD ${updates.join(", ")}`,
|
|
538
|
+
...conditions.length > 0 ? { ConditionExpression: conditions.join(" AND ") } : {},
|
|
539
|
+
ExpressionAttributeNames: names,
|
|
540
|
+
ExpressionAttributeValues: values
|
|
541
|
+
} };
|
|
542
|
+
};
|
|
543
|
+
const updateBundleRelation = (store, bundleId, delta, ownedPatchDelta = 0) => {
|
|
544
|
+
const names = {
|
|
545
|
+
"#pk": "pk",
|
|
546
|
+
"#version": "version"
|
|
547
|
+
};
|
|
548
|
+
const values = { ":one": 1 };
|
|
549
|
+
const additions = [];
|
|
550
|
+
let condition = "attribute_exists(#pk)";
|
|
551
|
+
let update = "SET #version = #version + :one";
|
|
552
|
+
if (delta !== 0) {
|
|
553
|
+
names["#relationCount"] = "relation_count";
|
|
554
|
+
values[":relationDelta"] = delta;
|
|
555
|
+
additions.push("#relationCount :relationDelta");
|
|
556
|
+
if (delta < 0) {
|
|
557
|
+
values[":relationRemoval"] = -delta;
|
|
558
|
+
condition += " AND #relationCount >= :relationRemoval";
|
|
1353
559
|
}
|
|
1354
|
-
return this;
|
|
1355
560
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
561
|
+
if (ownedPatchDelta !== 0) {
|
|
562
|
+
names["#ownedPatchCount"] = "owned_patch_count";
|
|
563
|
+
values[":ownedPatchDelta"] = ownedPatchDelta;
|
|
564
|
+
additions.push("#ownedPatchCount :ownedPatchDelta");
|
|
565
|
+
if (ownedPatchDelta < 0) {
|
|
566
|
+
values[":ownedPatchRemoval"] = -ownedPatchDelta;
|
|
567
|
+
condition += " AND #ownedPatchCount >= :ownedPatchRemoval";
|
|
568
|
+
}
|
|
1363
569
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
570
|
+
if (additions.length > 0) update += ` ADD ${additions.join(", ")}`;
|
|
571
|
+
return { Update: {
|
|
572
|
+
TableName: store.tableName,
|
|
573
|
+
Key: itemKey("bundles", bundleId),
|
|
574
|
+
ConditionExpression: condition,
|
|
575
|
+
ExpressionAttributeNames: names,
|
|
576
|
+
ExpressionAttributeValues: values,
|
|
577
|
+
UpdateExpression: update
|
|
578
|
+
} };
|
|
579
|
+
};
|
|
580
|
+
const commitDynamoDBTransaction = async (store, actions) => {
|
|
581
|
+
if (actions.length > 100) throw new DynamoDBTransactionLimitError(actions.length);
|
|
582
|
+
await store.client.send(new TransactWriteCommand({ TransactItems: [...actions] }));
|
|
583
|
+
};
|
|
584
|
+
const DYNAMODB_BATCH_GET_ATTEMPTS = 5;
|
|
585
|
+
const DYNAMODB_BATCH_GET_BASE_DELAY_MS = 25;
|
|
586
|
+
var DynamoDBBatchGetExhaustedError = class extends Error {
|
|
587
|
+
name = "DynamoDBBatchGetExhaustedError";
|
|
588
|
+
constructor(unprocessedKeyCount) {
|
|
589
|
+
super(`DynamoDB did not process ${unprocessedKeyCount} batch-get keys after ${DYNAMODB_BATCH_GET_ATTEMPTS} attempts`);
|
|
590
|
+
this.unprocessedKeyCount = unprocessedKeyCount;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
const waitBeforeRetry = (attempt) => new Promise((resolve) => setTimeout(resolve, DYNAMODB_BATCH_GET_BASE_DELAY_MS * 2 ** attempt));
|
|
594
|
+
const batchGetDynamoDBItems = async (store, keys) => {
|
|
595
|
+
const items = [];
|
|
596
|
+
for (let offset = 0; offset < keys.length; offset += 100) {
|
|
597
|
+
let pending = keys.slice(offset, offset + 100);
|
|
598
|
+
for (let attempt = 0; attempt < DYNAMODB_BATCH_GET_ATTEMPTS && pending.length > 0; attempt++) {
|
|
599
|
+
const { Responses, UnprocessedKeys } = await store.client.send(new BatchGetCommand({ RequestItems: { [store.tableName]: {
|
|
600
|
+
ConsistentRead: true,
|
|
601
|
+
Keys: pending
|
|
602
|
+
} } }));
|
|
603
|
+
items.push(...Responses?.[store.tableName] ?? []);
|
|
604
|
+
pending = UnprocessedKeys?.[store.tableName]?.Keys ?? [];
|
|
605
|
+
if (pending.length > 0 && attempt + 1 < DYNAMODB_BATCH_GET_ATTEMPTS) await waitBeforeRetry(attempt);
|
|
606
|
+
}
|
|
607
|
+
if (pending.length > 0) throw new DynamoDBBatchGetExhaustedError(pending.length);
|
|
608
|
+
}
|
|
609
|
+
return items;
|
|
610
|
+
};
|
|
611
|
+
const parseKeyReadItem = (item) => parseDynamoDBItem(boundedDynamoDBMetadataItem(item));
|
|
612
|
+
const loadBundleItemsById = async (store, ids) => {
|
|
613
|
+
const uniqueIds = [...new Set(ids)];
|
|
614
|
+
if (uniqueIds.length === 0) return [];
|
|
615
|
+
return (await batchGetDynamoDBItems(store, uniqueIds.map((id) => itemKey("bundles", id)))).map(parseKeyReadItem).filter((item) => item.pk === "bundles");
|
|
616
|
+
};
|
|
617
|
+
const loadPatchItemsById = async (store, ids) => {
|
|
618
|
+
const uniqueIds = [...new Set(ids)];
|
|
619
|
+
if (uniqueIds.length === 0) return [];
|
|
620
|
+
return (await batchGetDynamoDBItems(store, uniqueIds.map((id) => itemKey("bundle_patches", id)))).map(parseKeyReadItem).filter((item) => item.pk === "bundle_patches");
|
|
621
|
+
};
|
|
622
|
+
const loadMetadataCount = async (store, model) => {
|
|
623
|
+
if (model !== "bundles" && model !== "bundle_patches") return void 0;
|
|
624
|
+
const field = model === "bundles" ? "bundles" : "patches";
|
|
625
|
+
const { Item } = await store.client.send(new GetCommand({
|
|
626
|
+
TableName: store.tableName,
|
|
627
|
+
Key: {
|
|
628
|
+
pk: "_hot-updater",
|
|
629
|
+
sk: "limits.metadata"
|
|
630
|
+
},
|
|
631
|
+
ConsistentRead: true,
|
|
632
|
+
ProjectionExpression: "#count",
|
|
633
|
+
ExpressionAttributeNames: { "#count": field }
|
|
634
|
+
}));
|
|
635
|
+
const count = Item?.[field];
|
|
636
|
+
return typeof count === "number" && Number.isSafeInteger(count) && count >= 0 ? count : void 0;
|
|
637
|
+
};
|
|
638
|
+
var DynamoDBPatchIndexConsistencyError = class extends Error {
|
|
639
|
+
name = "DynamoDBPatchIndexConsistencyError";
|
|
640
|
+
constructor(bundleId, expectedCount) {
|
|
641
|
+
super(`DynamoDB patch index did not converge to ${expectedCount} rows for bundle "${bundleId}"`);
|
|
642
|
+
this.bundleId = bundleId;
|
|
643
|
+
this.expectedCount = expectedCount;
|
|
1368
644
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
645
|
+
};
|
|
646
|
+
const waitForIndex = (attempt) => new Promise((resolve) => setTimeout(resolve, 20 * (attempt + 1)));
|
|
647
|
+
const queryCompleteOwnerPatches = async (store, indexName, bundleId) => {
|
|
648
|
+
const owner = await loadBundleItem(store, bundleId);
|
|
649
|
+
if (!owner || owner.owned_patch_count === 0) return [];
|
|
650
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
651
|
+
const patchIds = await queryOwnerPatchIds(store, indexName, bundleId);
|
|
652
|
+
const patchIdSet = new Set(patchIds);
|
|
653
|
+
const patches = (await loadPatchItemsById(store, patchIds)).filter(({ row }) => row.bundle_id === bundleId);
|
|
654
|
+
if (patchIds.length === owner.owned_patch_count && patchIdSet.size === owner.owned_patch_count && patches.length === owner.owned_patch_count && patches.every(({ sk }) => patchIdSet.has(sk))) return patches.map(({ row }) => row).sort((left, right) => left.order_index - right.order_index || left.id.localeCompare(right.id));
|
|
655
|
+
if (attempt < 2) await waitForIndex(attempt);
|
|
656
|
+
}
|
|
657
|
+
throw new DynamoDBPatchIndexConsistencyError(bundleId, owner.owned_patch_count);
|
|
658
|
+
};
|
|
659
|
+
const queryCompleteOwnersPatches = async (store, indexName, bundleIds) => {
|
|
660
|
+
const owners = (await loadBundleItemsById(store, bundleIds)).filter(({ owned_patch_count }) => owned_patch_count > 0);
|
|
661
|
+
const firstOwner = owners[0];
|
|
662
|
+
if (!firstOwner) return [];
|
|
663
|
+
let failedOwner = firstOwner;
|
|
664
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
665
|
+
const candidateIdsByOwner = await Promise.all(owners.map(({ sk }) => queryOwnerPatchIds(store, indexName, sk)));
|
|
666
|
+
const patches = await loadPatchItemsById(store, candidateIdsByOwner.flat());
|
|
667
|
+
const patchesById = new Map(patches.map((patch) => [patch.sk, patch]));
|
|
668
|
+
const mismatchedOwner = owners.find((owner, ownerIndex) => {
|
|
669
|
+
const ids = candidateIdsByOwner[ownerIndex] ?? [];
|
|
670
|
+
const uniqueIds = new Set(ids);
|
|
671
|
+
return ids.length !== owner.owned_patch_count || uniqueIds.size !== owner.owned_patch_count || ids.some((id) => patchesById.get(id)?.row.bundle_id !== owner.sk);
|
|
672
|
+
});
|
|
673
|
+
if (!mismatchedOwner) return patches.map(({ row }) => row);
|
|
674
|
+
failedOwner = mismatchedOwner;
|
|
675
|
+
if (attempt < 2) await waitForIndex(attempt);
|
|
676
|
+
}
|
|
677
|
+
throw new DynamoDBPatchIndexConsistencyError(failedOwner.sk, failedOwner.owned_patch_count);
|
|
678
|
+
};
|
|
679
|
+
const createDynamoDBBundlePatchTable = (store, indexName) => ({ findByBundleIds: (bundleIds) => bundleIds.length === 0 ? Promise.resolve([]) : queryCompleteOwnersPatches(store, indexName, bundleIds) });
|
|
680
|
+
const compare = (left, right) => {
|
|
681
|
+
if (typeof left === "number" && typeof right === "number") return left - right;
|
|
682
|
+
if (left == null) return right == null ? 0 : -1;
|
|
683
|
+
if (right == null) return 1;
|
|
684
|
+
return JSON.stringify(left).localeCompare(JSON.stringify(right));
|
|
685
|
+
};
|
|
686
|
+
const compareString = (actual, expected, mode, predicate) => {
|
|
687
|
+
if (typeof actual !== "string") return false;
|
|
688
|
+
return mode === "insensitive" ? predicate(actual.toLocaleLowerCase(), expected.toLocaleLowerCase()) : predicate(actual, expected);
|
|
689
|
+
};
|
|
690
|
+
const matchesCondition = (row, condition) => {
|
|
691
|
+
const actual = Reflect.get(row, condition.field);
|
|
692
|
+
const expected = Reflect.get(condition, "value");
|
|
693
|
+
switch (Reflect.get(condition, "operator") ?? "eq") {
|
|
694
|
+
case "eq": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value === query) : actual === expected;
|
|
695
|
+
case "ne":
|
|
696
|
+
if (actual == null) return false;
|
|
697
|
+
return typeof expected === "string" ? !compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value === query) : actual !== expected;
|
|
698
|
+
case "gt": return actual != null && compare(actual, expected) > 0;
|
|
699
|
+
case "gte": return actual != null && compare(actual, expected) >= 0;
|
|
700
|
+
case "lt": return actual != null && compare(actual, expected) < 0;
|
|
701
|
+
case "lte": return actual != null && compare(actual, expected) <= 0;
|
|
702
|
+
case "in": return Array.isArray(expected) && expected.some((candidate) => candidate === actual);
|
|
703
|
+
case "not_in": return Array.isArray(expected) && (expected.length === 0 || actual != null && expected.every((candidate) => candidate !== actual));
|
|
704
|
+
case "contains": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value.includes(query)) : false;
|
|
705
|
+
case "starts_with": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value.startsWith(query)) : false;
|
|
706
|
+
case "ends_with": return typeof expected === "string" ? compareString(actual, expected, Reflect.get(condition, "mode"), (value, query) => value.endsWith(query)) : false;
|
|
707
|
+
default: return false;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
const matchesDynamoDBWhere = (row, where) => {
|
|
711
|
+
if (!where || where.length === 0) return true;
|
|
712
|
+
let result = matchesCondition(row, where[0]);
|
|
713
|
+
for (const condition of where.slice(1)) {
|
|
714
|
+
const current = matchesCondition(row, condition);
|
|
715
|
+
result = condition.connector === "OR" ? result || current : result && current;
|
|
716
|
+
}
|
|
717
|
+
return result;
|
|
718
|
+
};
|
|
719
|
+
const compareRows = (left, right, orderBy) => {
|
|
720
|
+
for (const clause of orderBy) {
|
|
721
|
+
const leftValue = Reflect.get(left, clause.field);
|
|
722
|
+
const rightValue = Reflect.get(right, clause.field);
|
|
723
|
+
if (leftValue == null || rightValue == null) {
|
|
724
|
+
if (leftValue == null && rightValue == null) continue;
|
|
725
|
+
const nulls = clause.nulls ?? (clause.direction === "asc" ? "last" : "first");
|
|
726
|
+
const order = leftValue == null ? -1 : 1;
|
|
727
|
+
return nulls === "first" ? order : -order;
|
|
728
|
+
}
|
|
729
|
+
const order = compare(leftValue, rightValue);
|
|
730
|
+
if (order !== 0) return clause.direction === "asc" ? order : -order;
|
|
731
|
+
}
|
|
732
|
+
return 0;
|
|
733
|
+
};
|
|
734
|
+
const distinctKey = (row, distinctOn) => JSON.stringify(distinctOn.fields.map((field) => Reflect.get(row, field)));
|
|
735
|
+
const queryDynamoDBRows = (rows, input) => {
|
|
736
|
+
const filtered = rows.filter((row) => matchesDynamoDBWhere(row, input.where));
|
|
737
|
+
const orderBy = input.orderBy;
|
|
738
|
+
const ordered = orderBy ? filtered.toSorted((left, right) => compareRows(left, right, orderBy)) : filtered;
|
|
739
|
+
const distinctOn = input.distinctOn;
|
|
740
|
+
const distinct = distinctOn ? (() => {
|
|
741
|
+
const seen = /* @__PURE__ */ new Set();
|
|
742
|
+
return ordered.filter((row) => {
|
|
743
|
+
const key = distinctKey(row, distinctOn);
|
|
744
|
+
if (seen.has(key)) return false;
|
|
745
|
+
seen.add(key);
|
|
746
|
+
return true;
|
|
747
|
+
});
|
|
748
|
+
})() : ordered;
|
|
749
|
+
const offset = input.offset ?? 0;
|
|
750
|
+
return distinct.slice(offset, offset + (input.limit ?? 100));
|
|
751
|
+
};
|
|
752
|
+
const countDistinctDynamoDBRows = (rows, fields) => {
|
|
753
|
+
if (fields === void 0) return rows.length;
|
|
754
|
+
return new Set(rows.map((row) => JSON.stringify(fields.map((field) => Reflect.get(row, field))))).size;
|
|
755
|
+
};
|
|
756
|
+
const createDynamoDBBundle = async (store, row) => {
|
|
757
|
+
const counter = metadataUpdate(store, { bundles: 1 });
|
|
758
|
+
if (!counter) return;
|
|
759
|
+
await commitDynamoDBTransaction(store, [counter, { Put: {
|
|
760
|
+
TableName: store.tableName,
|
|
761
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row)),
|
|
762
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
763
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
764
|
+
} }]);
|
|
765
|
+
};
|
|
766
|
+
const createDynamoDBPatch = async (store, row) => {
|
|
767
|
+
const bundleIds = [...new Set([row.bundle_id, row.base_bundle_id])];
|
|
768
|
+
const counter = metadataUpdate(store, { bundle_patches: 1 });
|
|
769
|
+
if (!counter) return;
|
|
770
|
+
await commitDynamoDBTransaction(store, [
|
|
771
|
+
counter,
|
|
772
|
+
...bundleIds.map((bundleId) => updateBundleRelation(store, bundleId, 1, bundleId === row.bundle_id ? 1 : 0)),
|
|
773
|
+
{ Put: {
|
|
774
|
+
TableName: store.tableName,
|
|
775
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBPatchItem(row)),
|
|
776
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
777
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
778
|
+
} }
|
|
779
|
+
]);
|
|
780
|
+
};
|
|
781
|
+
const replaceDynamoDBBundle = async (store, current, row) => {
|
|
782
|
+
await store.client.send(new PutCommand({
|
|
783
|
+
TableName: store.tableName,
|
|
784
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row, current.version + 1, current.relation_count, current.owned_patch_count)),
|
|
785
|
+
ConditionExpression: "#version = :currentVersion",
|
|
786
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
787
|
+
ExpressionAttributeValues: { ":currentVersion": current.version }
|
|
788
|
+
}));
|
|
789
|
+
};
|
|
790
|
+
const deleteAction = (store, item) => ({ Delete: {
|
|
791
|
+
TableName: store.tableName,
|
|
792
|
+
Key: {
|
|
793
|
+
pk: item.pk,
|
|
794
|
+
sk: item.sk
|
|
795
|
+
},
|
|
796
|
+
ConditionExpression: "#version = :currentVersion",
|
|
797
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
798
|
+
ExpressionAttributeValues: { ":currentVersion": item.version }
|
|
799
|
+
} });
|
|
800
|
+
const deleteDynamoDBPatch = async (store, item) => {
|
|
801
|
+
const counter = metadataUpdate(store, { bundle_patches: -1 });
|
|
802
|
+
if (!counter) return;
|
|
803
|
+
await commitDynamoDBTransaction(store, [
|
|
804
|
+
counter,
|
|
805
|
+
...[...new Set([item.row.bundle_id, item.row.base_bundle_id])].map((id) => updateBundleRelation(store, id, -1, id === item.row.bundle_id ? -1 : 0)),
|
|
806
|
+
deleteAction(store, item)
|
|
807
|
+
]);
|
|
808
|
+
};
|
|
809
|
+
const deleteBundle = async (store, bundle, patches) => {
|
|
810
|
+
const counter = metadataUpdate(store, {
|
|
811
|
+
bundles: -1,
|
|
812
|
+
bundle_patches: -patches.length
|
|
813
|
+
});
|
|
814
|
+
if (!counter) return;
|
|
815
|
+
const otherEndpointCounts = /* @__PURE__ */ new Map();
|
|
816
|
+
for (const { row } of patches) for (const id of new Set([row.bundle_id, row.base_bundle_id])) {
|
|
817
|
+
if (id === bundle.sk) continue;
|
|
818
|
+
const current = otherEndpointCounts.get(id) ?? {
|
|
819
|
+
owned: 0,
|
|
820
|
+
relations: 0
|
|
821
|
+
};
|
|
822
|
+
otherEndpointCounts.set(id, {
|
|
823
|
+
owned: current.owned + (id === row.bundle_id ? 1 : 0),
|
|
824
|
+
relations: current.relations + 1
|
|
825
|
+
});
|
|
1372
826
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
827
|
+
await commitDynamoDBTransaction(store, [
|
|
828
|
+
counter,
|
|
829
|
+
...[...otherEndpointCounts].map(([id, count]) => updateBundleRelation(store, id, -count.relations, -count.owned)),
|
|
830
|
+
...patches.map((patch) => deleteAction(store, patch)),
|
|
831
|
+
deleteAction(store, bundle)
|
|
832
|
+
]);
|
|
833
|
+
};
|
|
834
|
+
const deleteDynamoDBBundles = async (store, bundles, allPatches) => {
|
|
835
|
+
let patches = [...allPatches];
|
|
836
|
+
const versionIncrements = /* @__PURE__ */ new Map();
|
|
837
|
+
for (const originalBundle of bundles) {
|
|
838
|
+
const bundle = {
|
|
839
|
+
...originalBundle,
|
|
840
|
+
version: originalBundle.version + (versionIncrements.get(originalBundle.sk) ?? 0)
|
|
1376
841
|
};
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
842
|
+
const related = patches.filter(({ row }) => row.bundle_id === bundle.sk || row.base_bundle_id === bundle.sk);
|
|
843
|
+
await deleteBundle(store, bundle, related);
|
|
844
|
+
const updatedBundleIds = new Set(related.flatMap(({ row }) => [row.bundle_id, row.base_bundle_id]));
|
|
845
|
+
updatedBundleIds.delete(bundle.sk);
|
|
846
|
+
for (const id of updatedBundleIds) versionIncrements.set(id, (versionIncrements.get(id) ?? 0) + 1);
|
|
847
|
+
const removedPatchIds = new Set(related.map(({ sk }) => sk));
|
|
848
|
+
patches = patches.filter(({ sk }) => !removedPatchIds.has(sk));
|
|
1380
849
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
850
|
+
};
|
|
851
|
+
var DynamoDBUnsupportedModelError = class extends Error {
|
|
852
|
+
name = "DynamoDBUnsupportedModelError";
|
|
853
|
+
constructor() {
|
|
854
|
+
super("DynamoDB received an unsupported database model");
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
const distinctFields = (fields) => fields;
|
|
858
|
+
const exactDynamoDBField = (where, fieldName) => {
|
|
859
|
+
const conditions = (where ?? []).filter((condition) => Reflect.get(condition, "field") === fieldName);
|
|
860
|
+
if (conditions.length !== 1) return void 0;
|
|
861
|
+
const condition = conditions[0];
|
|
862
|
+
if ((Reflect.get(condition, "operator") ?? "eq") !== "eq" || Reflect.get(condition, "mode") === "insensitive") return;
|
|
863
|
+
const value = Reflect.get(condition, "value");
|
|
864
|
+
return typeof value === "string" ? value : void 0;
|
|
865
|
+
};
|
|
866
|
+
const createDynamoDBCrud = (store, updateIndexName) => ({
|
|
867
|
+
async create(input) {
|
|
868
|
+
switch (input.model) {
|
|
869
|
+
case "bundles":
|
|
870
|
+
await createDynamoDBBundle(store, input.data);
|
|
871
|
+
return input.data;
|
|
872
|
+
case "bundle_patches":
|
|
873
|
+
await createDynamoDBPatch(store, input.data);
|
|
874
|
+
return input.data;
|
|
875
|
+
case "releases": {
|
|
876
|
+
const channel = await loadChannelItem(store, input.data.channel_id);
|
|
877
|
+
if (channel === void 0) throw new DynamoDBStoredItemError();
|
|
878
|
+
await commitDynamoDBTransaction(store, [
|
|
879
|
+
updateChannelReferenceCount(store, channel, 1),
|
|
880
|
+
{ Put: {
|
|
881
|
+
TableName: store.tableName,
|
|
882
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(input.data)),
|
|
883
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
884
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
885
|
+
} },
|
|
886
|
+
{ Put: {
|
|
887
|
+
TableName: store.tableName,
|
|
888
|
+
Item: releaseLocatorItem(input.data),
|
|
889
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
890
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
891
|
+
} }
|
|
892
|
+
]);
|
|
893
|
+
return input.data;
|
|
894
|
+
}
|
|
895
|
+
case "release_catalogs":
|
|
896
|
+
await store.client.send(new PutCommand({
|
|
897
|
+
TableName: store.tableName,
|
|
898
|
+
Item: boundedDynamoDBCatalogItem(toDynamoDBReleaseCatalogItem(input.data)),
|
|
899
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
900
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
901
|
+
}));
|
|
902
|
+
return input.data;
|
|
903
|
+
case "channels": return (await insertDynamoDBChannel(store, {
|
|
904
|
+
row: input.data,
|
|
905
|
+
onConflict: "returnExisting"
|
|
906
|
+
})).row;
|
|
907
|
+
}
|
|
908
|
+
throw new DynamoDBUnsupportedModelError();
|
|
909
|
+
},
|
|
910
|
+
async update(input) {
|
|
911
|
+
if (input.model === "releases") {
|
|
912
|
+
const id = exactDynamoDBId(input.where);
|
|
913
|
+
if (id === void 0) throw new DynamoDBUnsupportedModelError();
|
|
914
|
+
const current = await loadReleaseItem(store, id);
|
|
915
|
+
if (current === void 0) return null;
|
|
916
|
+
const updated = {
|
|
917
|
+
...current.row,
|
|
918
|
+
...input.update
|
|
919
|
+
};
|
|
920
|
+
const nextPartition = releaseScopePartition(updated.scope_key);
|
|
921
|
+
const moved = current.pk !== nextPartition;
|
|
922
|
+
await commitDynamoDBTransaction(store, [
|
|
923
|
+
...moved ? [{ Delete: {
|
|
924
|
+
TableName: store.tableName,
|
|
925
|
+
Key: {
|
|
926
|
+
pk: current.pk,
|
|
927
|
+
sk: current.sk
|
|
928
|
+
},
|
|
929
|
+
ConditionExpression: "#version = :version",
|
|
930
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
931
|
+
ExpressionAttributeValues: { ":version": current.version }
|
|
932
|
+
} }] : [],
|
|
933
|
+
{ Put: {
|
|
934
|
+
TableName: store.tableName,
|
|
935
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(updated, current.version + 1)),
|
|
936
|
+
ConditionExpression: moved ? "attribute_not_exists(#pk)" : "#version = :version",
|
|
937
|
+
ExpressionAttributeNames: moved ? { "#pk": "pk" } : { "#version": "version" },
|
|
938
|
+
...moved ? {} : { ExpressionAttributeValues: { ":version": current.version } }
|
|
939
|
+
} },
|
|
940
|
+
{ Put: {
|
|
941
|
+
TableName: store.tableName,
|
|
942
|
+
Item: releaseLocatorItem(updated),
|
|
943
|
+
ConditionExpression: "#scopeKey = :scopeKey",
|
|
944
|
+
ExpressionAttributeNames: { "#scopeKey": "scope_key" },
|
|
945
|
+
ExpressionAttributeValues: { ":scopeKey": current.row.scope_key }
|
|
946
|
+
} }
|
|
947
|
+
]);
|
|
948
|
+
return updated;
|
|
949
|
+
}
|
|
950
|
+
if (input.model === "release_catalogs") {
|
|
951
|
+
const scopeKey = exactDynamoDBField(input.where, "scope_key");
|
|
952
|
+
if (scopeKey === void 0) throw new DynamoDBUnsupportedModelError();
|
|
953
|
+
const current = await loadReleaseCatalogItem(store, scopeKey);
|
|
954
|
+
if (current === void 0) return null;
|
|
955
|
+
const updated = {
|
|
956
|
+
...current.row,
|
|
957
|
+
...input.update
|
|
958
|
+
};
|
|
959
|
+
await store.client.send(new PutCommand({
|
|
960
|
+
TableName: store.tableName,
|
|
961
|
+
Item: boundedDynamoDBCatalogItem(toDynamoDBReleaseCatalogItem(updated, current.version + 1)),
|
|
962
|
+
ConditionExpression: "#version = :version",
|
|
963
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
964
|
+
ExpressionAttributeValues: { ":version": current.version }
|
|
965
|
+
}));
|
|
966
|
+
return updated;
|
|
967
|
+
}
|
|
968
|
+
if (input.model !== "bundles") throw new DynamoDBUnsupportedModelError();
|
|
969
|
+
const id = exactDynamoDBId(input.where);
|
|
970
|
+
const current = id === void 0 ? (await loadBundleItems(store)).find(({ row }) => matchesDynamoDBWhere(row, input.where)) : await loadBundleItem(store, id);
|
|
971
|
+
if (!current) return null;
|
|
972
|
+
const updated = {
|
|
973
|
+
...current.row,
|
|
974
|
+
...input.update
|
|
1385
975
|
};
|
|
976
|
+
await replaceDynamoDBBundle(store, current, updated);
|
|
977
|
+
return updated;
|
|
978
|
+
},
|
|
979
|
+
async delete(input) {
|
|
980
|
+
if (input.model === "channels") {
|
|
981
|
+
const id = exactDynamoDBId(input.where);
|
|
982
|
+
if (id !== void 0) await deleteDynamoDBChannel(store, { id });
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (input.model === "bundle_patches") {
|
|
986
|
+
const items = (await loadPatchItems(store)).filter(({ row }) => matchesDynamoDBWhere(row, input.where));
|
|
987
|
+
for (const item of items) await deleteDynamoDBPatch(store, item);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
if (input.model === "releases") {
|
|
991
|
+
const items = (await loadReleaseItems(store)).filter(({ row }) => matchesDynamoDBWhere(row, input.where));
|
|
992
|
+
for (const item of items) {
|
|
993
|
+
const channel = await loadChannelItem(store, item.row.channel_id);
|
|
994
|
+
if (channel === void 0) throw new DynamoDBStoredItemError();
|
|
995
|
+
await commitDynamoDBTransaction(store, [
|
|
996
|
+
updateChannelReferenceCount(store, channel, -1),
|
|
997
|
+
deleteAction(store, item),
|
|
998
|
+
{ Delete: {
|
|
999
|
+
TableName: store.tableName,
|
|
1000
|
+
Key: {
|
|
1001
|
+
pk: DYNAMODB_RELEASE_ID_PARTITION,
|
|
1002
|
+
sk: item.sk
|
|
1003
|
+
},
|
|
1004
|
+
ConditionExpression: "#scopeKey = :scopeKey",
|
|
1005
|
+
ExpressionAttributeNames: { "#scopeKey": "scope_key" },
|
|
1006
|
+
ExpressionAttributeValues: { ":scopeKey": item.row.scope_key }
|
|
1007
|
+
} }
|
|
1008
|
+
]);
|
|
1009
|
+
}
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (input.model !== "bundles") throw new DynamoDBUnsupportedModelError();
|
|
1013
|
+
const bundleItems = (await loadBundleItems(store)).filter(({ row }) => matchesDynamoDBWhere(row, input.where));
|
|
1014
|
+
if (bundleItems.length === 0) return;
|
|
1015
|
+
await deleteDynamoDBBundles(store, bundleItems, await loadPatchItems(store));
|
|
1016
|
+
},
|
|
1017
|
+
async count(input) {
|
|
1018
|
+
if ((input.where === void 0 || input.where.length === 0) && input.distinct === void 0) {
|
|
1019
|
+
const count = await loadMetadataCount(store, input.model);
|
|
1020
|
+
if (count !== void 0) return count;
|
|
1021
|
+
}
|
|
1022
|
+
switch (input.model) {
|
|
1023
|
+
case "bundles": return countDistinctDynamoDBRows((await loadBundleItems(store)).map(({ row }) => row).filter((row) => matchesDynamoDBWhere(row, input.where)), distinctFields(input.distinct));
|
|
1024
|
+
case "bundle_patches": {
|
|
1025
|
+
const ownerId = exactDynamoDBPatchOwner(input.where);
|
|
1026
|
+
return countDistinctDynamoDBRows((ownerId ? await queryCompleteOwnerPatches(store, updateIndexName, ownerId) : (await loadPatchItems(store)).map(({ row }) => row)).filter((row) => matchesDynamoDBWhere(row, input.where)), distinctFields(input.distinct));
|
|
1027
|
+
}
|
|
1028
|
+
case "releases": {
|
|
1029
|
+
const scopeKey = exactDynamoDBField(input.where, "scope_key");
|
|
1030
|
+
return countDistinctDynamoDBRows((scopeKey === void 0 ? await loadReleaseItems(store) : await loadReleaseItemsByScope(store, scopeKey)).map(({ row }) => row).filter((row) => matchesDynamoDBWhere(row, input.where)), distinctFields(input.distinct));
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
throw new DynamoDBUnsupportedModelError();
|
|
1034
|
+
},
|
|
1035
|
+
async findOne(input) {
|
|
1036
|
+
const id = exactDynamoDBId(input.where);
|
|
1037
|
+
switch (input.model) {
|
|
1038
|
+
case "bundles":
|
|
1039
|
+
if (id !== void 0) return (await loadBundleItem(store, id))?.row ?? null;
|
|
1040
|
+
return (await loadBundleItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
|
|
1041
|
+
case "bundle_patches":
|
|
1042
|
+
if (id !== void 0) return (await loadPatchItem(store, id))?.row ?? null;
|
|
1043
|
+
{
|
|
1044
|
+
const ownerId = exactDynamoDBPatchOwner(input.where);
|
|
1045
|
+
if (ownerId === void 0) break;
|
|
1046
|
+
return (await queryCompleteOwnerPatches(store, updateIndexName, ownerId)).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
|
|
1047
|
+
}
|
|
1048
|
+
break;
|
|
1049
|
+
case "channels":
|
|
1050
|
+
if (id !== void 0) return (await loadChannelItem(store, id))?.row ?? null;
|
|
1051
|
+
return (await loadChannelItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
|
|
1052
|
+
case "releases":
|
|
1053
|
+
if (id !== void 0) return (await loadReleaseItem(store, id))?.row ?? null;
|
|
1054
|
+
{
|
|
1055
|
+
const scopeKey = exactDynamoDBField(input.where, "scope_key");
|
|
1056
|
+
return (scopeKey === void 0 ? await loadReleaseItems(store) : await loadReleaseItemsByScope(store, scopeKey)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
|
|
1057
|
+
}
|
|
1058
|
+
case "release_catalogs": {
|
|
1059
|
+
const scopeKey = exactDynamoDBField(input.where, "scope_key");
|
|
1060
|
+
if (scopeKey !== void 0) return (await loadReleaseCatalogItem(store, scopeKey))?.row ?? null;
|
|
1061
|
+
return (await loadReleaseCatalogItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (input.model === "bundle_patches") return (await loadPatchItems(store)).map(({ row }) => row).find((row) => matchesDynamoDBWhere(row, input.where)) ?? null;
|
|
1065
|
+
throw new DynamoDBUnsupportedModelError();
|
|
1066
|
+
},
|
|
1067
|
+
async findMany(input) {
|
|
1068
|
+
if (input.limit === 0) return [];
|
|
1069
|
+
switch (input.model) {
|
|
1070
|
+
case "bundles": {
|
|
1071
|
+
const ids = exactDynamoDBBundleIds(input.where);
|
|
1072
|
+
if (ids !== void 0) return queryDynamoDBRows((await loadBundleItemsById(store, ids)).map(({ row }) => row), input);
|
|
1073
|
+
const orderBy = input.orderBy;
|
|
1074
|
+
const direction = orderBy?.[0]?.direction;
|
|
1075
|
+
if ((input.offset ?? 0) === 0 && input.distinctOn === void 0 && orderBy?.length === 1 && orderBy[0]?.field === "id" && (direction === "asc" || direction === "desc")) return (await queryBundleItemsPage(store, {
|
|
1076
|
+
direction,
|
|
1077
|
+
limit: input.limit ?? 100,
|
|
1078
|
+
matches: (row) => matchesDynamoDBWhere(row, input.where),
|
|
1079
|
+
start: dynamoDBBundlePageStart(input.where, direction)
|
|
1080
|
+
})).map(({ row }) => row);
|
|
1081
|
+
return queryDynamoDBRows((await loadBundleItems(store)).map(({ row }) => row), input);
|
|
1082
|
+
}
|
|
1083
|
+
case "bundle_patches": {
|
|
1084
|
+
const ownerIds = exactDynamoDBPatchOwners(input.where);
|
|
1085
|
+
if (ownerIds !== void 0) {
|
|
1086
|
+
const ownerId = ownerIds.length === 1 ? ownerIds[0] : void 0;
|
|
1087
|
+
return queryDynamoDBRows(ownerId !== void 0 ? await queryCompleteOwnerPatches(store, updateIndexName, ownerId) : await queryCompleteOwnersPatches(store, updateIndexName, ownerIds), input);
|
|
1088
|
+
}
|
|
1089
|
+
return queryDynamoDBRows((await loadPatchItems(store)).map(({ row }) => row), input);
|
|
1090
|
+
}
|
|
1091
|
+
case "channels": return queryDynamoDBRows((await loadChannelItems(store)).map(({ row }) => row), input);
|
|
1092
|
+
case "releases": {
|
|
1093
|
+
const scopeKey = exactDynamoDBField(input.where, "scope_key");
|
|
1094
|
+
return queryDynamoDBRows((scopeKey === void 0 ? await loadReleaseItems(store) : await loadReleaseItemsByScope(store, scopeKey)).map(({ row }) => row), input);
|
|
1095
|
+
}
|
|
1096
|
+
case "release_catalogs": return queryDynamoDBRows((await loadReleaseCatalogItems(store)).map(({ row }) => row), input);
|
|
1097
|
+
}
|
|
1098
|
+
throw new DynamoDBUnsupportedModelError();
|
|
1099
|
+
},
|
|
1100
|
+
insertChannel: (input) => insertDynamoDBChannel(store, input),
|
|
1101
|
+
deleteChannel: (input) => deleteDynamoDBChannel(store, input)
|
|
1102
|
+
});
|
|
1103
|
+
var DynamoDBDuplicatePatchError = class extends Error {
|
|
1104
|
+
name = "DynamoDBDuplicatePatchError";
|
|
1105
|
+
constructor(patchId) {
|
|
1106
|
+
super(`DynamoDB bundle mutation contains duplicate patch "${patchId}"`);
|
|
1107
|
+
this.patchId = patchId;
|
|
1386
1108
|
}
|
|
1387
1109
|
};
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1110
|
+
const putNewBundle = (store, row, relationCount, ownedPatchCount = relationCount) => ({ Put: {
|
|
1111
|
+
TableName: store.tableName,
|
|
1112
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row, 1, relationCount, ownedPatchCount)),
|
|
1113
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1114
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1115
|
+
} });
|
|
1116
|
+
const putUpdatedBundle = (store, current, row, relationCount, ownedPatchCount) => ({ Put: {
|
|
1117
|
+
TableName: store.tableName,
|
|
1118
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBBundleItem(row, current.version + 1, relationCount, ownedPatchCount)),
|
|
1119
|
+
ConditionExpression: "#version = :currentVersion",
|
|
1120
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
1121
|
+
ExpressionAttributeValues: { ":currentVersion": current.version }
|
|
1122
|
+
} });
|
|
1123
|
+
const putPatch = (store, row, current) => ({ Put: {
|
|
1124
|
+
TableName: store.tableName,
|
|
1125
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBPatchItem(row, (current?.version ?? 0) + 1)),
|
|
1126
|
+
ConditionExpression: current ? "#version = :currentVersion" : "attribute_not_exists(#pk)",
|
|
1127
|
+
ExpressionAttributeNames: current ? { "#version": "version" } : { "#pk": "pk" },
|
|
1128
|
+
...current ? { ExpressionAttributeValues: { ":currentVersion": current.version } } : {}
|
|
1129
|
+
} });
|
|
1130
|
+
const deletePatch = (store, item) => ({ Delete: {
|
|
1131
|
+
TableName: store.tableName,
|
|
1132
|
+
Key: itemKey(item.pk, item.sk),
|
|
1133
|
+
ConditionExpression: "#version = :currentVersion",
|
|
1134
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
1135
|
+
ExpressionAttributeValues: { ":currentVersion": item.version }
|
|
1136
|
+
} });
|
|
1137
|
+
var DynamoDBCommitStateError = class extends Error {
|
|
1138
|
+
name = "DynamoDBCommitStateError";
|
|
1139
|
+
constructor(message) {
|
|
1140
|
+
super(message);
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
const rowsEqual = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
1144
|
+
const putChannel = (store, row, current, referenceCount) => ({ Put: {
|
|
1145
|
+
TableName: store.tableName,
|
|
1146
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBChannelItem(row, (current?.version ?? 0) + 1, referenceCount)),
|
|
1147
|
+
ConditionExpression: current ? "#version = :version" : "attribute_not_exists(#pk)",
|
|
1148
|
+
ExpressionAttributeNames: current ? { "#version": "version" } : { "#pk": "pk" },
|
|
1149
|
+
...current ? { ExpressionAttributeValues: { ":version": current.version } } : {}
|
|
1150
|
+
} });
|
|
1151
|
+
const deleteChannelActions = (store, current) => [{ Delete: {
|
|
1152
|
+
TableName: store.tableName,
|
|
1153
|
+
Key: itemKey(DYNAMODB_CHANNEL_PARTITION, current.sk),
|
|
1154
|
+
ConditionExpression: "#version = :version",
|
|
1155
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
1156
|
+
ExpressionAttributeValues: { ":version": current.version }
|
|
1157
|
+
} }, { Delete: {
|
|
1158
|
+
TableName: store.tableName,
|
|
1159
|
+
Key: {
|
|
1160
|
+
pk: DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
1161
|
+
sk: current.row.name
|
|
1162
|
+
},
|
|
1163
|
+
ConditionExpression: "#channelId = :channelId",
|
|
1164
|
+
ExpressionAttributeNames: { "#channelId": "channel_id" },
|
|
1165
|
+
ExpressionAttributeValues: { ":channelId": current.sk }
|
|
1166
|
+
} }];
|
|
1167
|
+
const updateChannelReferenceCount = (store, current, delta) => ({ Update: {
|
|
1168
|
+
TableName: store.tableName,
|
|
1169
|
+
Key: itemKey(DYNAMODB_CHANNEL_PARTITION, current.sk),
|
|
1170
|
+
ConditionExpression: "#row.#name = :name" + (delta < 0 ? " AND #referenceCount >= :removal" : ""),
|
|
1171
|
+
UpdateExpression: "SET #version = #version + :one ADD #referenceCount :delta",
|
|
1172
|
+
ExpressionAttributeNames: {
|
|
1173
|
+
"#row": "row",
|
|
1174
|
+
"#name": "name",
|
|
1175
|
+
"#version": "version",
|
|
1176
|
+
"#referenceCount": "reference_count"
|
|
1177
|
+
},
|
|
1178
|
+
ExpressionAttributeValues: {
|
|
1179
|
+
":name": current.row.name,
|
|
1180
|
+
":one": 1,
|
|
1181
|
+
":delta": delta,
|
|
1182
|
+
...delta < 0 ? { ":removal": -delta } : {}
|
|
1183
|
+
}
|
|
1184
|
+
} });
|
|
1185
|
+
const loadApiKeyByHash = async (store, hash) => {
|
|
1186
|
+
const { Item } = await store.client.send(new GetCommand({
|
|
1187
|
+
TableName: store.tableName,
|
|
1188
|
+
Key: {
|
|
1189
|
+
pk: DYNAMODB_API_KEY_HASH_PARTITION,
|
|
1190
|
+
sk: hash
|
|
1191
|
+
},
|
|
1192
|
+
ConsistentRead: true
|
|
1193
|
+
}));
|
|
1194
|
+
if (Item === void 0) return null;
|
|
1195
|
+
const id = Item.api_key_id;
|
|
1196
|
+
if (typeof id !== "string") throw new DynamoDBStoredItemError();
|
|
1197
|
+
return loadApiKey(store, id);
|
|
1198
|
+
};
|
|
1199
|
+
const compileAndCommitDynamoDBChanges = async (store, input) => {
|
|
1200
|
+
const [originalBundleItems, originalPatchItems, originalChannelItems, originalReleaseItems, originalReleaseCatalogItems] = await Promise.all([
|
|
1201
|
+
loadBundleItems(store),
|
|
1202
|
+
loadPatchItems(store),
|
|
1203
|
+
loadChannelItems(store),
|
|
1204
|
+
loadReleaseItems(store),
|
|
1205
|
+
loadReleaseCatalogItems(store)
|
|
1206
|
+
]);
|
|
1207
|
+
const bundles = new Map(originalBundleItems.map(({ sk, row }) => [sk, row]));
|
|
1208
|
+
const patches = new Map(originalPatchItems.map(({ sk, row }) => [sk, row]));
|
|
1209
|
+
const channels = new Map(originalChannelItems.map(({ sk, row }) => [sk, row]));
|
|
1210
|
+
const channelNames = new Map(originalChannelItems.map(({ sk, row }) => [row.name, sk]));
|
|
1211
|
+
const releases = new Map(originalReleaseItems.map(({ sk, row }) => [sk, row]));
|
|
1212
|
+
const releaseCatalogs = new Map(originalReleaseCatalogItems.map(({ sk, row }) => [sk, row]));
|
|
1213
|
+
const originalApiKeys = /* @__PURE__ */ new Map();
|
|
1214
|
+
const apiKeys = /* @__PURE__ */ new Map();
|
|
1215
|
+
const apiKeyHashes = /* @__PURE__ */ new Map();
|
|
1216
|
+
const analytics = /* @__PURE__ */ new Map();
|
|
1217
|
+
const rememberApiKey = (value) => {
|
|
1218
|
+
if (value === null || apiKeys.has(value.row.id)) return;
|
|
1219
|
+
originalApiKeys.set(value.row.id, value);
|
|
1220
|
+
apiKeys.set(value.row.id, value);
|
|
1221
|
+
apiKeyHashes.set(value.row.hash, value.row.id);
|
|
1222
|
+
};
|
|
1223
|
+
const requireReleaseReferences = (row) => {
|
|
1224
|
+
if (!channels.has(row.channel_id)) throw new DynamoDBCommitStateError(`Release "${row.id}" references an invalid channel`);
|
|
1225
|
+
if (row.bundle_id !== null) {
|
|
1226
|
+
const bundle = bundles.get(row.bundle_id);
|
|
1227
|
+
if (bundle === void 0 || bundle.platform !== row.platform) throw new DynamoDBCommitStateError(`Release "${row.id}" references an invalid bundle`);
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
for (const expectation of input.expectations ?? []) {
|
|
1231
|
+
const actualVersion = expectation.model === "releases" ? releases.get(expectation.id)?.revision ?? null : releaseCatalogs.get(expectation.scopeKey)?.generation ?? null;
|
|
1232
|
+
const expectedVersion = expectation.model === "releases" ? expectation.revision : expectation.generation;
|
|
1233
|
+
if (actualVersion !== expectedVersion) return {
|
|
1234
|
+
committed: false,
|
|
1235
|
+
conflict: {
|
|
1236
|
+
actualVersion,
|
|
1237
|
+
changeIndex: -1,
|
|
1238
|
+
expectedVersion,
|
|
1239
|
+
key: expectation.model === "releases" ? expectation.id : expectation.scopeKey,
|
|
1240
|
+
model: expectation.model,
|
|
1241
|
+
reason: "version_conflict"
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
for (const [changeIndex, change] of input.changes.entries()) switch (change.model) {
|
|
1246
|
+
case "channels":
|
|
1247
|
+
if (change.operation === "insert") {
|
|
1248
|
+
if (channelNames.get(change.row.name) !== void 0) break;
|
|
1249
|
+
if (channels.get(change.row.id) !== void 0) throw new DynamoDBCommitStateError(`Channel id "${change.row.id}" already exists`);
|
|
1250
|
+
channels.set(change.row.id, change.row);
|
|
1251
|
+
channelNames.set(change.row.name, change.row.id);
|
|
1252
|
+
} else {
|
|
1253
|
+
const current = channels.get(change.where.id);
|
|
1254
|
+
if (current === void 0) break;
|
|
1255
|
+
if ([...releases.values()].some(({ channel_id }) => channel_id === change.where.id)) return {
|
|
1256
|
+
committed: false,
|
|
1257
|
+
conflict: {
|
|
1258
|
+
changeIndex,
|
|
1259
|
+
reason: "referenced"
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
channels.delete(change.where.id);
|
|
1263
|
+
channelNames.delete(current.name);
|
|
1264
|
+
}
|
|
1265
|
+
break;
|
|
1266
|
+
case "bundles":
|
|
1267
|
+
if (change.operation === "insert") {
|
|
1268
|
+
if (bundles.has(change.row.id)) throw new DynamoDBCommitStateError(`Bundle "${change.row.id}" already exists`);
|
|
1269
|
+
bundles.set(change.row.id, change.row);
|
|
1270
|
+
} else {
|
|
1271
|
+
const current = bundles.get(change.where.id);
|
|
1272
|
+
if (current === void 0) {
|
|
1273
|
+
if (change.operation === "update") return {
|
|
1274
|
+
committed: false,
|
|
1275
|
+
conflict: {
|
|
1276
|
+
changeIndex,
|
|
1277
|
+
reason: "not_found"
|
|
1278
|
+
}
|
|
1279
|
+
};
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
if (change.operation === "update") {
|
|
1283
|
+
const row = {
|
|
1284
|
+
...current,
|
|
1285
|
+
...change.update
|
|
1286
|
+
};
|
|
1287
|
+
bundles.set(row.id, row);
|
|
1288
|
+
} else {
|
|
1289
|
+
if ([...releases.values()].some(({ bundle_id }) => bundle_id === change.where.id)) return {
|
|
1290
|
+
committed: false,
|
|
1291
|
+
conflict: {
|
|
1292
|
+
changeIndex,
|
|
1293
|
+
reason: "referenced"
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
bundles.delete(change.where.id);
|
|
1297
|
+
for (const [id, patch] of patches) if (patch.bundle_id === change.where.id || patch.base_bundle_id === change.where.id) patches.delete(id);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
break;
|
|
1301
|
+
case "releases":
|
|
1302
|
+
if (change.operation === "insert") {
|
|
1303
|
+
requireReleaseReferences(change.row);
|
|
1304
|
+
if (releases.has(change.row.id)) throw new DynamoDBCommitStateError(`Release "${change.row.id}" already exists`);
|
|
1305
|
+
releases.set(change.row.id, change.row);
|
|
1306
|
+
} else {
|
|
1307
|
+
const current = releases.get(change.where.id);
|
|
1308
|
+
if (current === void 0) {
|
|
1309
|
+
if (change.operation === "update") return {
|
|
1310
|
+
committed: false,
|
|
1311
|
+
conflict: {
|
|
1312
|
+
changeIndex,
|
|
1313
|
+
reason: "not_found"
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
if (change.operation === "update") {
|
|
1319
|
+
const row = {
|
|
1320
|
+
...current,
|
|
1321
|
+
...change.update
|
|
1322
|
+
};
|
|
1323
|
+
requireReleaseReferences(row);
|
|
1324
|
+
releases.set(row.id, row);
|
|
1325
|
+
} else releases.delete(change.where.id);
|
|
1326
|
+
}
|
|
1327
|
+
break;
|
|
1328
|
+
case "releaseCatalogs":
|
|
1329
|
+
if (!channels.has(change.row.channel_id)) throw new DynamoDBCommitStateError(`Release catalog "${change.row.scope_key}" references an invalid channel`);
|
|
1330
|
+
releaseCatalogs.set(change.row.scope_key, change.row);
|
|
1331
|
+
break;
|
|
1332
|
+
case "bundlePatches":
|
|
1333
|
+
if (change.operation === "insert") {
|
|
1334
|
+
if (!bundles.has(change.row.bundle_id) || !bundles.has(change.row.base_bundle_id)) throw new DynamoDBCommitStateError(`Patch "${change.row.id}" references a missing bundle`);
|
|
1335
|
+
if (patches.has(change.row.id)) throw new DynamoDBDuplicatePatchError(change.row.id);
|
|
1336
|
+
patches.set(change.row.id, change.row);
|
|
1337
|
+
} else for (const [id, patch] of patches) if (patch.bundle_id === change.where.bundleId) patches.delete(id);
|
|
1338
|
+
break;
|
|
1339
|
+
case "analytics": {
|
|
1340
|
+
const key = analyticsSortKey(change.row);
|
|
1341
|
+
if (analytics.has(key)) throw new DynamoDBCommitStateError(`Analytics event "${change.row.id}" is duplicated`);
|
|
1342
|
+
analytics.set(key, change.row);
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
case "apiKeys":
|
|
1346
|
+
if (change.operation === "insert") {
|
|
1347
|
+
rememberApiKey(await loadApiKeyByHash(store, change.row.hash));
|
|
1348
|
+
if (apiKeyHashes.has(change.row.hash)) break;
|
|
1349
|
+
rememberApiKey(await loadApiKey(store, change.row.id));
|
|
1350
|
+
if (apiKeys.has(change.row.id)) throw new DynamoDBCommitStateError(`API key id "${change.row.id}" already exists`);
|
|
1351
|
+
const inserted = {
|
|
1352
|
+
row: change.row,
|
|
1353
|
+
version: 1
|
|
1354
|
+
};
|
|
1355
|
+
apiKeys.set(change.row.id, inserted);
|
|
1356
|
+
apiKeyHashes.set(change.row.hash, change.row.id);
|
|
1357
|
+
} else {
|
|
1358
|
+
rememberApiKey(await loadApiKey(store, change.where.id));
|
|
1359
|
+
const current = apiKeys.get(change.where.id);
|
|
1360
|
+
if (current === void 0) return {
|
|
1361
|
+
committed: false,
|
|
1362
|
+
conflict: {
|
|
1363
|
+
changeIndex,
|
|
1364
|
+
reason: "not_found"
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
apiKeys.set(change.where.id, {
|
|
1368
|
+
row: {
|
|
1369
|
+
...current.row,
|
|
1370
|
+
revoked_at_ms: change.update.revokedAtMs
|
|
1371
|
+
},
|
|
1372
|
+
version: current.version
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
const relationCounts = /* @__PURE__ */ new Map();
|
|
1378
|
+
const ownedPatchCounts = /* @__PURE__ */ new Map();
|
|
1379
|
+
for (const patch of patches.values()) {
|
|
1380
|
+
for (const id of new Set([patch.bundle_id, patch.base_bundle_id])) relationCounts.set(id, (relationCounts.get(id) ?? 0) + 1);
|
|
1381
|
+
ownedPatchCounts.set(patch.bundle_id, (ownedPatchCounts.get(patch.bundle_id) ?? 0) + 1);
|
|
1382
|
+
}
|
|
1383
|
+
const channelReferenceCounts = /* @__PURE__ */ new Map();
|
|
1384
|
+
for (const release of releases.values()) channelReferenceCounts.set(release.channel_id, (channelReferenceCounts.get(release.channel_id) ?? 0) + 1);
|
|
1385
|
+
const actions = [];
|
|
1386
|
+
const originalChannels = new Map(originalChannelItems.map((item) => [item.sk, item]));
|
|
1387
|
+
for (const original of originalChannelItems) if (!channels.has(original.sk)) actions.push(...deleteChannelActions(store, original));
|
|
1388
|
+
for (const [id, row] of channels) {
|
|
1389
|
+
const original = originalChannels.get(id);
|
|
1390
|
+
const referenceCount = channelReferenceCounts.get(id) ?? 0;
|
|
1391
|
+
if (original === void 0) {
|
|
1392
|
+
actions.push(putChannel(store, row, original, referenceCount));
|
|
1393
|
+
actions.push({ Put: {
|
|
1394
|
+
TableName: store.tableName,
|
|
1395
|
+
Item: {
|
|
1396
|
+
pk: DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
1397
|
+
sk: row.name,
|
|
1398
|
+
channel_id: id
|
|
1399
|
+
},
|
|
1400
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1401
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1402
|
+
} });
|
|
1403
|
+
} else {
|
|
1404
|
+
if (!rowsEqual(original.row, row)) throw new DynamoDBCommitStateError(`Channel "${id}" cannot be updated`);
|
|
1405
|
+
const delta = referenceCount - original.reference_count;
|
|
1406
|
+
if (delta !== 0) actions.push(updateChannelReferenceCount(store, original, delta));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
const originalBundles = new Map(originalBundleItems.map((item) => [item.sk, item]));
|
|
1410
|
+
for (const original of originalBundleItems) if (!bundles.has(original.sk)) actions.push(deleteAction(store, original));
|
|
1411
|
+
for (const [id, row] of bundles) {
|
|
1412
|
+
const original = originalBundles.get(id);
|
|
1413
|
+
const relationCount = relationCounts.get(id) ?? 0;
|
|
1414
|
+
const ownedPatchCount = ownedPatchCounts.get(id) ?? 0;
|
|
1415
|
+
if (original === void 0) actions.push(putNewBundle(store, row, relationCount, ownedPatchCount));
|
|
1416
|
+
else if (!rowsEqual(original.row, row)) actions.push(putUpdatedBundle(store, original, row, relationCount, ownedPatchCount));
|
|
1417
|
+
else {
|
|
1418
|
+
const relationDelta = relationCount - original.relation_count;
|
|
1419
|
+
const ownedPatchDelta = ownedPatchCount - original.owned_patch_count;
|
|
1420
|
+
if (relationDelta !== 0 || ownedPatchDelta !== 0) actions.push(updateBundleRelation(store, id, relationDelta, ownedPatchDelta));
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
const originalPatches = new Map(originalPatchItems.map((item) => [item.sk, item]));
|
|
1424
|
+
for (const original of originalPatchItems) if (!patches.has(original.sk)) actions.push(deletePatch(store, original));
|
|
1425
|
+
for (const [id, row] of patches) {
|
|
1426
|
+
const original = originalPatches.get(id);
|
|
1427
|
+
if (original === void 0 || !rowsEqual(original.row, row)) actions.push(putPatch(store, row, original));
|
|
1428
|
+
}
|
|
1429
|
+
const originalReleases = new Map(originalReleaseItems.map((item) => [item.sk, item]));
|
|
1430
|
+
for (const original of originalReleaseItems) {
|
|
1431
|
+
if (releases.get(original.sk) !== void 0) continue;
|
|
1432
|
+
actions.push(deleteAction(store, original));
|
|
1433
|
+
actions.push({ Delete: {
|
|
1434
|
+
TableName: store.tableName,
|
|
1435
|
+
Key: {
|
|
1436
|
+
pk: DYNAMODB_RELEASE_ID_PARTITION,
|
|
1437
|
+
sk: original.sk
|
|
1438
|
+
},
|
|
1439
|
+
ConditionExpression: "#scopeKey = :scopeKey",
|
|
1440
|
+
ExpressionAttributeNames: { "#scopeKey": "scope_key" },
|
|
1441
|
+
ExpressionAttributeValues: { ":scopeKey": original.row.scope_key }
|
|
1442
|
+
} });
|
|
1443
|
+
}
|
|
1444
|
+
for (const [id, row] of releases) {
|
|
1445
|
+
const original = originalReleases.get(id);
|
|
1446
|
+
if (original === void 0) {
|
|
1447
|
+
actions.push({ Put: {
|
|
1448
|
+
TableName: store.tableName,
|
|
1449
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(row)),
|
|
1450
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1451
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1452
|
+
} });
|
|
1453
|
+
actions.push({ Put: {
|
|
1454
|
+
TableName: store.tableName,
|
|
1455
|
+
Item: releaseLocatorItem(row),
|
|
1456
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1457
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1458
|
+
} });
|
|
1459
|
+
} else if (!rowsEqual(original.row, row)) {
|
|
1460
|
+
const moved = original.row.scope_key !== row.scope_key;
|
|
1461
|
+
if (moved) actions.push(deleteAction(store, original));
|
|
1462
|
+
actions.push({ Put: {
|
|
1463
|
+
TableName: store.tableName,
|
|
1464
|
+
Item: boundedDynamoDBMetadataItem(toDynamoDBReleaseItem(row, original.version + 1)),
|
|
1465
|
+
ConditionExpression: moved ? "attribute_not_exists(#pk)" : "#version = :version",
|
|
1466
|
+
ExpressionAttributeNames: moved ? { "#pk": "pk" } : { "#version": "version" },
|
|
1467
|
+
...moved ? {} : { ExpressionAttributeValues: { ":version": original.version } }
|
|
1468
|
+
} });
|
|
1469
|
+
if (moved) actions.push({ Put: {
|
|
1470
|
+
TableName: store.tableName,
|
|
1471
|
+
Item: releaseLocatorItem(row),
|
|
1472
|
+
ConditionExpression: "#scopeKey = :scopeKey",
|
|
1473
|
+
ExpressionAttributeNames: { "#scopeKey": "scope_key" },
|
|
1474
|
+
ExpressionAttributeValues: { ":scopeKey": original.row.scope_key }
|
|
1475
|
+
} });
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
const originalReleaseCatalogs = new Map(originalReleaseCatalogItems.map((item) => [item.sk, item]));
|
|
1479
|
+
for (const [scopeKey, row] of releaseCatalogs) {
|
|
1480
|
+
const original = originalReleaseCatalogs.get(scopeKey);
|
|
1481
|
+
if (original === void 0 || !rowsEqual(original.row, row)) actions.push({ Put: {
|
|
1482
|
+
TableName: store.tableName,
|
|
1483
|
+
Item: boundedDynamoDBCatalogItem(toDynamoDBReleaseCatalogItem(row, (original?.version ?? 0) + 1)),
|
|
1484
|
+
ConditionExpression: original ? "#version = :version" : "attribute_not_exists(#pk)",
|
|
1485
|
+
ExpressionAttributeNames: original ? { "#version": "version" } : { "#pk": "pk" },
|
|
1486
|
+
...original ? { ExpressionAttributeValues: { ":version": original.version } } : {}
|
|
1487
|
+
} });
|
|
1488
|
+
}
|
|
1489
|
+
const changedReleaseIds = new Set([...new Set([...originalReleases.keys(), ...releases.keys()])].filter((id) => {
|
|
1490
|
+
const original = originalReleases.get(id)?.row;
|
|
1491
|
+
const next = releases.get(id);
|
|
1492
|
+
return original === void 0 || next === void 0 || !rowsEqual(original, next);
|
|
1493
|
+
}));
|
|
1494
|
+
const changedCatalogScopes = new Set([...releaseCatalogs].flatMap(([scopeKey, row]) => {
|
|
1495
|
+
const original = originalReleaseCatalogs.get(scopeKey)?.row;
|
|
1496
|
+
return original === void 0 || !rowsEqual(original, row) ? [scopeKey] : [];
|
|
1497
|
+
}));
|
|
1498
|
+
for (const expectation of input.expectations ?? []) {
|
|
1499
|
+
if (expectation.model === "releases" && !changedReleaseIds.has(expectation.id)) {
|
|
1500
|
+
const original = originalReleases.get(expectation.id);
|
|
1501
|
+
actions.push({ ConditionCheck: {
|
|
1502
|
+
TableName: store.tableName,
|
|
1503
|
+
Key: original === void 0 ? {
|
|
1504
|
+
pk: DYNAMODB_RELEASE_ID_PARTITION,
|
|
1505
|
+
sk: expectation.id
|
|
1506
|
+
} : {
|
|
1507
|
+
pk: original.pk,
|
|
1508
|
+
sk: original.sk
|
|
1509
|
+
},
|
|
1510
|
+
ConditionExpression: original === void 0 ? "attribute_not_exists(#pk)" : "#version = :version",
|
|
1511
|
+
ExpressionAttributeNames: original === void 0 ? { "#pk": "pk" } : { "#version": "version" },
|
|
1512
|
+
...original === void 0 ? {} : { ExpressionAttributeValues: { ":version": original.version } }
|
|
1513
|
+
} });
|
|
1514
|
+
}
|
|
1515
|
+
if (expectation.model === "releaseCatalogs" && !changedCatalogScopes.has(expectation.scopeKey)) {
|
|
1516
|
+
const original = originalReleaseCatalogs.get(expectation.scopeKey);
|
|
1517
|
+
actions.push({ ConditionCheck: {
|
|
1518
|
+
TableName: store.tableName,
|
|
1519
|
+
Key: itemKey("release_catalogs", expectation.scopeKey),
|
|
1520
|
+
ConditionExpression: original === void 0 ? "attribute_not_exists(#pk)" : "#version = :version",
|
|
1521
|
+
ExpressionAttributeNames: original === void 0 ? { "#pk": "pk" } : { "#version": "version" },
|
|
1522
|
+
...original === void 0 ? {} : { ExpressionAttributeValues: { ":version": original.version } }
|
|
1523
|
+
} });
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
const counter = metadataUpdate(store, {
|
|
1527
|
+
bundles: bundles.size - originalBundles.size,
|
|
1528
|
+
bundle_patches: patches.size - originalPatches.size
|
|
1529
|
+
});
|
|
1530
|
+
if (counter !== void 0) actions.push(counter);
|
|
1531
|
+
for (const [key, row] of analytics) actions.push({ Put: {
|
|
1532
|
+
TableName: store.tableName,
|
|
1533
|
+
Item: boundedDynamoDBMetadataItem({
|
|
1534
|
+
pk: DYNAMODB_ANALYTICS_PARTITION,
|
|
1535
|
+
sk: key,
|
|
1536
|
+
version: 1,
|
|
1537
|
+
row
|
|
1538
|
+
}),
|
|
1539
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1540
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1541
|
+
} });
|
|
1542
|
+
for (const [id, current] of apiKeys) {
|
|
1543
|
+
const original = originalApiKeys.get(id);
|
|
1544
|
+
if (original !== void 0 && rowsEqual(original.row, current.row)) continue;
|
|
1545
|
+
actions.push({ Put: {
|
|
1546
|
+
TableName: store.tableName,
|
|
1547
|
+
Item: boundedDynamoDBMetadataItem(apiKeyItem(current.row, original === void 0 ? 1 : original.version + 1)),
|
|
1548
|
+
ConditionExpression: original ? "#version = :version" : "attribute_not_exists(#pk)",
|
|
1549
|
+
ExpressionAttributeNames: original ? { "#version": "version" } : { "#pk": "pk" },
|
|
1550
|
+
...original ? { ExpressionAttributeValues: { ":version": original.version } } : {}
|
|
1551
|
+
} });
|
|
1552
|
+
if (original === void 0) actions.push({ Put: {
|
|
1553
|
+
TableName: store.tableName,
|
|
1554
|
+
Item: {
|
|
1555
|
+
pk: DYNAMODB_API_KEY_HASH_PARTITION,
|
|
1556
|
+
sk: current.row.hash,
|
|
1557
|
+
api_key_id: current.row.id
|
|
1558
|
+
},
|
|
1559
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1560
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1561
|
+
} });
|
|
1562
|
+
}
|
|
1563
|
+
if (actions.length > 0) await commitDynamoDBTransaction(store, actions);
|
|
1564
|
+
return { committed: true };
|
|
1565
|
+
};
|
|
1566
|
+
const isDynamoDBTransactionConflict = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "TransactionCanceledException";
|
|
1567
|
+
const createDynamoDBCommit = (store) => async (input) => {
|
|
1568
|
+
try {
|
|
1569
|
+
return await compileAndCommitDynamoDBChanges(store, input);
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
if (!isDynamoDBTransactionConflict(error)) throw error;
|
|
1572
|
+
return compileAndCommitDynamoDBChanges(store, input);
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
const DYNAMODB_ANALYTICS_PARTITION = "bundle_events";
|
|
1576
|
+
const DYNAMODB_API_KEY_PARTITION = "api_keys";
|
|
1577
|
+
const DYNAMODB_API_KEY_HASH_PARTITION = "_hot-updater#api-key-hashes";
|
|
1578
|
+
const hasValidBundleEventShape = (value) => {
|
|
1579
|
+
const type = field(value, "type");
|
|
1580
|
+
const fromBundleId = field(value, "from_bundle_id");
|
|
1581
|
+
const updateStrategy = field(value, "update_strategy");
|
|
1582
|
+
return (type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
|
|
1583
|
+
};
|
|
1584
|
+
const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
|
|
1585
|
+
const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
|
|
1586
|
+
const parseOfficialRowItem = (value, partition, isRow) => {
|
|
1587
|
+
if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
|
|
1588
|
+
return {
|
|
1589
|
+
row: value.row,
|
|
1590
|
+
version: value.version
|
|
1591
|
+
};
|
|
1592
|
+
};
|
|
1593
|
+
const timestampSortKey = (timestampMs) => Math.trunc(timestampMs).toString().padStart(16, "0");
|
|
1594
|
+
const analyticsSortKey = (row) => `${timestampSortKey(row.received_at_ms)}#${row.id}`;
|
|
1595
|
+
const createDynamoDBAnalyticsTable = (store) => ({
|
|
1596
|
+
async append(row) {
|
|
1597
|
+
await store.client.send(new PutCommand({
|
|
1598
|
+
TableName: store.tableName,
|
|
1599
|
+
Item: boundedDynamoDBMetadataItem({
|
|
1600
|
+
pk: DYNAMODB_ANALYTICS_PARTITION,
|
|
1601
|
+
sk: analyticsSortKey(row),
|
|
1602
|
+
version: 1,
|
|
1603
|
+
row
|
|
1604
|
+
}),
|
|
1605
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1606
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1607
|
+
}));
|
|
1608
|
+
},
|
|
1609
|
+
async scan(input) {
|
|
1610
|
+
if (input.limit <= 0) return [];
|
|
1611
|
+
if (input.after !== void 0 && input.after.receivedAtMs >= input.beforeReceivedAtMs) return [];
|
|
1612
|
+
const rows = [];
|
|
1613
|
+
const afterKey = input.after === void 0 ? void 0 : `${timestampSortKey(input.after.receivedAtMs)}#${input.after.id}`;
|
|
1614
|
+
let exclusiveStartKey;
|
|
1615
|
+
do {
|
|
1616
|
+
const page = await store.client.send(new QueryCommand({
|
|
1617
|
+
TableName: store.tableName,
|
|
1618
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
1619
|
+
KeyConditionExpression: afterKey === void 0 ? "#pk = :pk AND #sk < :before" : "#pk = :pk AND #sk BETWEEN :after AND :before",
|
|
1620
|
+
ExpressionAttributeNames: {
|
|
1621
|
+
"#pk": "pk",
|
|
1622
|
+
"#sk": "sk"
|
|
1623
|
+
},
|
|
1624
|
+
ExpressionAttributeValues: {
|
|
1625
|
+
":before": `${timestampSortKey(input.beforeReceivedAtMs)}#`,
|
|
1626
|
+
":pk": DYNAMODB_ANALYTICS_PARTITION,
|
|
1627
|
+
...afterKey === void 0 ? {} : { ":after": afterKey }
|
|
1628
|
+
},
|
|
1629
|
+
Limit: input.limit - rows.length + (afterKey === void 0 ? 0 : 1),
|
|
1630
|
+
ScanIndexForward: true
|
|
1631
|
+
}));
|
|
1632
|
+
for (const item of page.Items ?? []) {
|
|
1633
|
+
if (item.sk === afterKey) continue;
|
|
1634
|
+
rows.push(parseOfficialRowItem(item, DYNAMODB_ANALYTICS_PARTITION, isBundleEventRow).row);
|
|
1635
|
+
if (rows.length === input.limit) return rows;
|
|
1636
|
+
}
|
|
1637
|
+
exclusiveStartKey = page.LastEvaluatedKey;
|
|
1638
|
+
} while (exclusiveStartKey !== void 0);
|
|
1639
|
+
return rows;
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
const apiKeyItem = (row, version) => ({
|
|
1643
|
+
pk: DYNAMODB_API_KEY_PARTITION,
|
|
1644
|
+
sk: row.id,
|
|
1645
|
+
version,
|
|
1646
|
+
row
|
|
1647
|
+
});
|
|
1648
|
+
const loadApiKey = async (store, id) => {
|
|
1649
|
+
const result = await store.client.send(new GetCommand({
|
|
1650
|
+
TableName: store.tableName,
|
|
1651
|
+
Key: {
|
|
1652
|
+
pk: DYNAMODB_API_KEY_PARTITION,
|
|
1653
|
+
sk: id
|
|
1654
|
+
},
|
|
1655
|
+
ConsistentRead: true
|
|
1656
|
+
}));
|
|
1657
|
+
return result.Item === void 0 ? null : parseOfficialRowItem(result.Item, DYNAMODB_API_KEY_PARTITION, isApiKeyRow);
|
|
1658
|
+
};
|
|
1659
|
+
const createDynamoDBApiKeyTable = (store) => {
|
|
1660
|
+
const findByHash = async (hash) => {
|
|
1661
|
+
const lookup = await store.client.send(new GetCommand({
|
|
1662
|
+
TableName: store.tableName,
|
|
1663
|
+
Key: {
|
|
1664
|
+
pk: DYNAMODB_API_KEY_HASH_PARTITION,
|
|
1665
|
+
sk: hash
|
|
1666
|
+
},
|
|
1667
|
+
ConsistentRead: true
|
|
1668
|
+
}));
|
|
1669
|
+
const id = lookup.Item?.api_key_id;
|
|
1670
|
+
if (lookup.Item === void 0) return null;
|
|
1671
|
+
if (typeof id !== "string") throw new DynamoDBStoredItemError();
|
|
1672
|
+
return (await loadApiKey(store, id))?.row ?? null;
|
|
1673
|
+
};
|
|
1674
|
+
return {
|
|
1675
|
+
async create(row) {
|
|
1676
|
+
if (await findByHash(row.hash) !== null) return "existing";
|
|
1677
|
+
try {
|
|
1678
|
+
await commitDynamoDBTransaction(store, [{ Put: {
|
|
1679
|
+
TableName: store.tableName,
|
|
1680
|
+
Item: boundedDynamoDBMetadataItem(apiKeyItem(row, 1)),
|
|
1681
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1682
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1683
|
+
} }, { Put: {
|
|
1684
|
+
TableName: store.tableName,
|
|
1685
|
+
Item: {
|
|
1686
|
+
pk: DYNAMODB_API_KEY_HASH_PARTITION,
|
|
1687
|
+
sk: row.hash,
|
|
1688
|
+
api_key_id: row.id
|
|
1689
|
+
},
|
|
1690
|
+
ConditionExpression: "attribute_not_exists(#pk)",
|
|
1691
|
+
ExpressionAttributeNames: { "#pk": "pk" }
|
|
1692
|
+
} }]);
|
|
1693
|
+
return "created";
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
if (await findByHash(row.hash) !== null) return "existing";
|
|
1696
|
+
throw error;
|
|
1697
|
+
}
|
|
1698
|
+
},
|
|
1699
|
+
findByHash,
|
|
1700
|
+
async list() {
|
|
1701
|
+
const rows = [];
|
|
1702
|
+
let exclusiveStartKey;
|
|
1703
|
+
do {
|
|
1704
|
+
const page = await store.client.send(new QueryCommand({
|
|
1705
|
+
TableName: store.tableName,
|
|
1706
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
1707
|
+
KeyConditionExpression: "#pk = :pk",
|
|
1708
|
+
ExpressionAttributeNames: { "#pk": "pk" },
|
|
1709
|
+
ExpressionAttributeValues: { ":pk": DYNAMODB_API_KEY_PARTITION },
|
|
1710
|
+
ConsistentRead: true
|
|
1711
|
+
}));
|
|
1712
|
+
rows.push(...(page.Items ?? []).map((item) => parseOfficialRowItem(item, DYNAMODB_API_KEY_PARTITION, isApiKeyRow).row));
|
|
1713
|
+
exclusiveStartKey = page.LastEvaluatedKey;
|
|
1714
|
+
} while (exclusiveStartKey !== void 0);
|
|
1715
|
+
return rows.sort((left, right) => right.created_at_ms - left.created_at_ms || left.id.localeCompare(right.id));
|
|
1716
|
+
},
|
|
1717
|
+
async revoke({ id, revokedAtMs }) {
|
|
1718
|
+
const current = await loadApiKey(store, id);
|
|
1719
|
+
if (current === null) return null;
|
|
1720
|
+
const row = {
|
|
1721
|
+
...current.row,
|
|
1722
|
+
revoked_at_ms: revokedAtMs
|
|
1723
|
+
};
|
|
1724
|
+
await store.client.send(new PutCommand({
|
|
1725
|
+
TableName: store.tableName,
|
|
1726
|
+
Item: boundedDynamoDBMetadataItem(apiKeyItem(row, current.version + 1)),
|
|
1727
|
+
ConditionExpression: "#version = :currentVersion",
|
|
1728
|
+
ExpressionAttributeNames: { "#version": "version" },
|
|
1729
|
+
ExpressionAttributeValues: { ":currentVersion": current.version }
|
|
1730
|
+
}));
|
|
1731
|
+
return row;
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
};
|
|
1735
|
+
const DYNAMODB_UPDATE_INDEX_NAME = "hot-updater-update-index";
|
|
1736
|
+
const dynamoDB = (config) => {
|
|
1737
|
+
const { apiBasePath = "/release-catalogs", cloudfrontDistributionId, shouldWaitForInvalidation = false, tableName, ...clientConfig } = config;
|
|
1738
|
+
const client = DynamoDBDocumentClient.from(new DynamoDBClient(clientConfig), { marshallOptions: { removeUndefinedValues: true } });
|
|
1739
|
+
const cloudFront = cloudfrontDistributionId ? new CloudFrontClient({
|
|
1740
|
+
credentials: clientConfig.credentials,
|
|
1741
|
+
region: clientConfig.region
|
|
1742
|
+
}) : null;
|
|
1743
|
+
const store = {
|
|
1744
|
+
client,
|
|
1745
|
+
tableName
|
|
1746
|
+
};
|
|
1747
|
+
const crud = createDynamoDBCrud(store, DYNAMODB_UPDATE_INDEX_NAME);
|
|
1748
|
+
const invalidateUpdateRoutes = async () => {
|
|
1749
|
+
if (!cloudFront || !cloudfrontDistributionId) return;
|
|
1750
|
+
try {
|
|
1751
|
+
await invalidateCloudFront(cloudFront, cloudfrontDistributionId, [`${apiBasePath.replace(/\/+$/, "")}/*`], { shouldWait: shouldWaitForInvalidation });
|
|
1752
|
+
} catch (error) {
|
|
1753
|
+
console.warn("[hot-updater/aws] CloudFront invalidation failed; continuing without cache invalidation.", {
|
|
1754
|
+
distributionId: cloudfrontDistributionId,
|
|
1755
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
const adapter = createDatabasePluginAdapter("dynamoDB", {
|
|
1760
|
+
...crud,
|
|
1761
|
+
commit: createDynamoDBCommit(store),
|
|
1762
|
+
dispose: async () => {
|
|
1763
|
+
client.destroy();
|
|
1764
|
+
cloudFront?.destroy();
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
return createDatabasePlugin({
|
|
1768
|
+
name: "dynamoDB",
|
|
1769
|
+
models: {
|
|
1770
|
+
...adapter.models,
|
|
1771
|
+
bundlePatches: createDynamoDBBundlePatchTable(store, DYNAMODB_UPDATE_INDEX_NAME),
|
|
1772
|
+
analytics: createDynamoDBAnalyticsTable(store),
|
|
1773
|
+
apiKeys: createDynamoDBApiKeyTable(store)
|
|
1774
|
+
},
|
|
1775
|
+
async commit(input) {
|
|
1776
|
+
const result = await adapter.commit(input);
|
|
1777
|
+
if (result.committed && input.changes.some(({ model }) => model === "bundles" || model === "bundlePatches")) await invalidateUpdateRoutes();
|
|
1778
|
+
return result;
|
|
1779
|
+
},
|
|
1780
|
+
dispose: adapter.dispose
|
|
1781
|
+
});
|
|
1782
|
+
};
|
|
1392
1783
|
//#endregion
|
|
1393
1784
|
//#region src/runtimeAwsConfig.ts
|
|
1394
1785
|
const truthyValues = new Set([
|
|
@@ -1425,473 +1816,170 @@ const applySsmRuntimeAwsConfig = (config) => {
|
|
|
1425
1816
|
};
|
|
1426
1817
|
};
|
|
1427
1818
|
//#endregion
|
|
1428
|
-
//#region src/
|
|
1429
|
-
const streamToString = (stream) => {
|
|
1430
|
-
return new Promise((resolve, reject) => {
|
|
1431
|
-
const chunks = [];
|
|
1432
|
-
stream.on("data", (chunk) => chunks.push(chunk));
|
|
1433
|
-
stream.on("error", reject);
|
|
1434
|
-
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
1435
|
-
});
|
|
1436
|
-
};
|
|
1437
|
-
//#endregion
|
|
1438
|
-
//#region src/s3Database.ts
|
|
1439
|
-
const DEFAULT_INVALIDATION_POLL_INTERVAL_MS = 2e3;
|
|
1440
|
-
const DEFAULT_INVALIDATION_TIMEOUT_MS = 300 * 1e3;
|
|
1441
|
-
const S3_LIST_OBJECTS_CONCURRENCY = 4;
|
|
1442
|
-
const S3_DIRECTORY_DELIMITER = "/";
|
|
1443
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1444
|
-
const getS3ErrorProperty = (error, key) => {
|
|
1445
|
-
if (typeof error !== "object" || error === null) return;
|
|
1446
|
-
const value = error[key];
|
|
1447
|
-
return typeof value === "string" ? value : void 0;
|
|
1448
|
-
};
|
|
1449
|
-
const isArchivedS3ObjectError = (error) => {
|
|
1450
|
-
if (!(error instanceof Error)) return false;
|
|
1451
|
-
return error.name === "InvalidObjectState" || getS3ErrorProperty(error, "Code") === "InvalidObjectState";
|
|
1452
|
-
};
|
|
1453
|
-
const createArchivedS3ObjectError = ({ bucket, key, error }) => {
|
|
1454
|
-
const storageClass = getS3ErrorProperty(error, "StorageClass") ?? "archived storage";
|
|
1455
|
-
const nextError = new Error(`S3 object "${key}" in bucket "${bucket}" is archived (${storageClass}) and cannot be read. Restore the object in S3 or exclude Hot Updater metadata from lifecycle archival: "**/target-app-versions.json" and "**/update.json".`, { cause: error });
|
|
1456
|
-
nextError.name = "S3ArchivedObjectError";
|
|
1457
|
-
return nextError;
|
|
1458
|
-
};
|
|
1459
|
-
function normalizeBasePath(basePath) {
|
|
1460
|
-
return basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
1461
|
-
}
|
|
1462
|
-
function createDatabaseKeyBuilder(basePath) {
|
|
1463
|
-
const normalizedBasePath = normalizeBasePath(basePath);
|
|
1464
|
-
const toStorageKey = (key) => [normalizedBasePath, key].filter(Boolean).join("/");
|
|
1465
|
-
const fromStorageKey = (key) => {
|
|
1466
|
-
if (!normalizedBasePath) return key;
|
|
1467
|
-
const prefix = `${normalizedBasePath}/`;
|
|
1468
|
-
return key.startsWith(prefix) ? key.slice(prefix.length) : key;
|
|
1469
|
-
};
|
|
1470
|
-
return {
|
|
1471
|
-
fromStorageKey,
|
|
1472
|
-
toStorageKey
|
|
1473
|
-
};
|
|
1474
|
-
}
|
|
1475
|
-
function normalizeDirectoryPrefix(prefix) {
|
|
1476
|
-
if (!prefix) return "";
|
|
1477
|
-
return prefix.endsWith(S3_DIRECTORY_DELIMITER) ? prefix : `${prefix}${S3_DIRECTORY_DELIMITER}`;
|
|
1478
|
-
}
|
|
1479
|
-
function getRelativeDirectoryPrefix(prefix, rootPrefix) {
|
|
1480
|
-
if (!rootPrefix) return prefix;
|
|
1481
|
-
return prefix.startsWith(rootPrefix) ? prefix.slice(rootPrefix.length) : prefix;
|
|
1482
|
-
}
|
|
1483
|
-
function getDirectoryDepth(prefix, rootPrefix) {
|
|
1484
|
-
return getRelativeDirectoryPrefix(prefix, rootPrefix).split(S3_DIRECTORY_DELIMITER).filter(Boolean).length;
|
|
1485
|
-
}
|
|
1486
|
-
function getLastDirectorySegment(prefix) {
|
|
1487
|
-
return prefix.split(S3_DIRECTORY_DELIMITER).filter(Boolean).at(-1);
|
|
1488
|
-
}
|
|
1489
|
-
function isPlatformDirectoryPrefix(prefix) {
|
|
1490
|
-
const lastSegment = getLastDirectorySegment(prefix);
|
|
1491
|
-
return lastSegment === "ios" || lastSegment === "android";
|
|
1492
|
-
}
|
|
1493
|
-
function isLegacyBundleStorageDirectoryPrefix(prefix) {
|
|
1494
|
-
const lastSegment = getLastDirectorySegment(prefix);
|
|
1495
|
-
return lastSegment !== void 0 && /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(lastSegment);
|
|
1496
|
-
}
|
|
1497
|
-
function isUpdateJsonKey(key) {
|
|
1498
|
-
return key.endsWith(`${S3_DIRECTORY_DELIMITER}update.json`);
|
|
1499
|
-
}
|
|
1500
|
-
function isUuidChannelUpdateJsonKey(key, rootPrefix, uuidChannels) {
|
|
1501
|
-
const [channel, platform, target, fileName, ...rest] = getRelativeDirectoryPrefix(key, rootPrefix).split(S3_DIRECTORY_DELIMITER);
|
|
1502
|
-
return channel !== void 0 && uuidChannels.has(channel) && (platform === "ios" || platform === "android") && target !== void 0 && target.length > 0 && fileName === "update.json" && rest.length === 0;
|
|
1503
|
-
}
|
|
1504
|
-
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
1505
|
-
const results = [];
|
|
1506
|
-
let nextIndex = 0;
|
|
1507
|
-
const workerCount = Math.min(concurrency, items.length);
|
|
1508
|
-
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
1509
|
-
while (nextIndex < items.length) {
|
|
1510
|
-
const index = nextIndex;
|
|
1511
|
-
nextIndex += 1;
|
|
1512
|
-
const item = items[index];
|
|
1513
|
-
if (item === void 0) break;
|
|
1514
|
-
results[index] = await mapper(item, index);
|
|
1515
|
-
}
|
|
1516
|
-
}));
|
|
1517
|
-
return results.filter((result) => result !== void 0);
|
|
1518
|
-
}
|
|
1519
|
-
/**
|
|
1520
|
-
* Loads JSON data from S3.
|
|
1521
|
-
* Returns null if NoSuchKey error occurs.
|
|
1522
|
-
*/
|
|
1523
|
-
async function loadJsonFromS3(client, bucket, key) {
|
|
1524
|
-
try {
|
|
1525
|
-
const { Body } = await client.send(new GetObjectCommand({
|
|
1526
|
-
Bucket: bucket,
|
|
1527
|
-
Key: key
|
|
1528
|
-
}));
|
|
1529
|
-
if (!Body) return null;
|
|
1530
|
-
const bodyContents = await streamToString(Body);
|
|
1531
|
-
return JSON.parse(bodyContents);
|
|
1532
|
-
} catch (e) {
|
|
1533
|
-
if (e instanceof NoSuchKey) return null;
|
|
1534
|
-
if (isArchivedS3ObjectError(e)) throw createArchivedS3ObjectError({
|
|
1535
|
-
bucket,
|
|
1536
|
-
key,
|
|
1537
|
-
error: e
|
|
1538
|
-
});
|
|
1539
|
-
throw e;
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
/**
|
|
1543
|
-
* Converts data to JSON string and uploads to S3.
|
|
1544
|
-
*/
|
|
1545
|
-
async function uploadJsonToS3(client, bucket, key, data) {
|
|
1546
|
-
const Body = JSON.stringify(data);
|
|
1547
|
-
const ContentType = src_default.getType(key) ?? "application/json";
|
|
1548
|
-
await client.send(new PutObjectCommand({
|
|
1549
|
-
Bucket: bucket,
|
|
1550
|
-
Key: key,
|
|
1551
|
-
Body,
|
|
1552
|
-
ContentType,
|
|
1553
|
-
CacheControl: "max-age=31536000"
|
|
1554
|
-
}));
|
|
1555
|
-
}
|
|
1556
|
-
async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
1557
|
-
const normalizedRootPrefix = normalizeDirectoryPrefix(rootPrefix);
|
|
1558
|
-
const listPrefix = async (currentPrefix, delimiter = S3_DIRECTORY_DELIMITER) => {
|
|
1559
|
-
let continuationToken;
|
|
1560
|
-
const keys = [];
|
|
1561
|
-
const commonPrefixes = /* @__PURE__ */ new Set();
|
|
1562
|
-
do {
|
|
1563
|
-
const response = await client.send(new ListObjectsV2Command({
|
|
1564
|
-
Bucket: bucketName,
|
|
1565
|
-
Prefix: currentPrefix,
|
|
1566
|
-
Delimiter: delimiter ?? void 0,
|
|
1567
|
-
ContinuationToken: continuationToken
|
|
1568
|
-
}));
|
|
1569
|
-
const found = (response.Contents ?? []).map((item) => item.Key).filter((key) => !!key);
|
|
1570
|
-
keys.push(...found);
|
|
1571
|
-
for (const commonPrefix of response.CommonPrefixes ?? []) if (commonPrefix.Prefix) commonPrefixes.add(commonPrefix.Prefix);
|
|
1572
|
-
continuationToken = response.NextContinuationToken;
|
|
1573
|
-
} while (continuationToken);
|
|
1574
|
-
return {
|
|
1575
|
-
commonPrefixes: Array.from(commonPrefixes),
|
|
1576
|
-
keys
|
|
1577
|
-
};
|
|
1578
|
-
};
|
|
1579
|
-
const collectUpdateJsonKeys = async (currentPrefix) => {
|
|
1580
|
-
const { commonPrefixes, keys } = await listPrefix(currentPrefix);
|
|
1581
|
-
const depth = getDirectoryDepth(currentPrefix, normalizedRootPrefix);
|
|
1582
|
-
if (depth >= 2) return [...keys.filter(isUpdateJsonKey), ...commonPrefixes.map((commonPrefix) => `${commonPrefix}update.json`)];
|
|
1583
|
-
const uuidPrefixes = depth === 0 ? commonPrefixes.filter(isLegacyBundleStorageDirectoryPrefix) : [];
|
|
1584
|
-
const uuidPrefixSet = new Set(uuidPrefixes);
|
|
1585
|
-
const nestedKeys = await mapWithConcurrency(depth === 0 ? commonPrefixes.filter((commonPrefix) => !uuidPrefixSet.has(commonPrefix)) : depth === 1 ? commonPrefixes.filter(isPlatformDirectoryPrefix) : commonPrefixes, S3_LIST_OBJECTS_CONCURRENCY, (nextPrefix) => collectUpdateJsonKeys(nextPrefix));
|
|
1586
|
-
const uuidChannels = new Set(uuidPrefixes.map(getLastDirectorySegment).filter((segment) => !!segment));
|
|
1587
|
-
const uuidChannelKeys = (await mapWithConcurrency(Array.from(new Set(Array.from(uuidChannels, (channel) => `${normalizedRootPrefix}${channel[0]}`))), S3_LIST_OBJECTS_CONCURRENCY, async (scanPrefix) => (await listPrefix(scanPrefix, null)).keys)).flat().filter((key) => isUuidChannelUpdateJsonKey(key, normalizedRootPrefix, uuidChannels));
|
|
1588
|
-
return [...nestedKeys.flat(), ...uuidChannelKeys];
|
|
1589
|
-
};
|
|
1590
|
-
const normalizedPrefix = normalizeDirectoryPrefix(prefix);
|
|
1591
|
-
return Array.from(new Set(await collectUpdateJsonKeys(normalizedPrefix)));
|
|
1592
|
-
}
|
|
1593
|
-
async function deleteObjectInS3(client, bucketName, key) {
|
|
1594
|
-
await client.send(new DeleteObjectCommand({
|
|
1595
|
-
Bucket: bucketName,
|
|
1596
|
-
Key: key
|
|
1597
|
-
}));
|
|
1598
|
-
}
|
|
1599
|
-
/**
|
|
1600
|
-
* Invalidates CloudFront cache for the given paths.
|
|
1601
|
-
*/
|
|
1602
|
-
async function invalidateCloudFront(client, distributionId, paths, options) {
|
|
1603
|
-
if (paths.length === 0) return;
|
|
1604
|
-
const timestamp = Date.now();
|
|
1605
|
-
try {
|
|
1606
|
-
const response = await client.send(new CreateInvalidationCommand({
|
|
1607
|
-
DistributionId: distributionId,
|
|
1608
|
-
InvalidationBatch: {
|
|
1609
|
-
CallerReference: `invalidation-${timestamp}`,
|
|
1610
|
-
Paths: {
|
|
1611
|
-
Quantity: paths.length,
|
|
1612
|
-
Items: paths
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
}));
|
|
1616
|
-
if (!options?.shouldWaitForInvalidation) return;
|
|
1617
|
-
const invalidationId = response.Invalidation?.Id;
|
|
1618
|
-
if (!invalidationId) throw new Error("CloudFront invalidation response is missing Invalidation.Id");
|
|
1619
|
-
if (response.Invalidation?.Status === "Completed") return;
|
|
1620
|
-
const timeoutMs = DEFAULT_INVALIDATION_TIMEOUT_MS;
|
|
1621
|
-
const pollIntervalMs = DEFAULT_INVALIDATION_POLL_INTERVAL_MS;
|
|
1622
|
-
const deadline = Date.now() + timeoutMs;
|
|
1623
|
-
while (Date.now() < deadline) {
|
|
1624
|
-
await sleep(pollIntervalMs);
|
|
1625
|
-
if ((await client.send(new GetInvalidationCommand({
|
|
1626
|
-
DistributionId: distributionId,
|
|
1627
|
-
Id: invalidationId
|
|
1628
|
-
}))).Invalidation?.Status === "Completed") return;
|
|
1629
|
-
}
|
|
1630
|
-
throw new Error(`Timed out waiting for CloudFront invalidation ${invalidationId} to complete after ${timeoutMs}ms`);
|
|
1631
|
-
} catch (error) {
|
|
1632
|
-
if (options?.shouldWaitForInvalidation) throw error;
|
|
1633
|
-
const message = error instanceof Error ? error.message : "Unknown invalidation error";
|
|
1634
|
-
console.warn(`[hot-updater/aws] CloudFront invalidation failed for distribution ${distributionId}; continuing without cache invalidation.`, {
|
|
1635
|
-
error: message,
|
|
1636
|
-
paths
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
const s3Database = createBlobDatabasePlugin({
|
|
1641
|
-
name: "s3Database",
|
|
1642
|
-
factory: (config) => {
|
|
1643
|
-
const { basePath, bucketName, cloudfrontDistributionId, apiBasePath = "/api/check-update", shouldWaitForInvalidation = false, ...s3Config } = config;
|
|
1644
|
-
const client = new S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
1645
|
-
const { fromStorageKey, toStorageKey } = createDatabaseKeyBuilder(basePath);
|
|
1646
|
-
const rootPrefix = toStorageKey("");
|
|
1647
|
-
const cloudfrontClient = cloudfrontDistributionId ? new CloudFrontClient({
|
|
1648
|
-
credentials: s3Config.credentials,
|
|
1649
|
-
region: s3Config.region
|
|
1650
|
-
}) : void 0;
|
|
1651
|
-
return {
|
|
1652
|
-
apiBasePath,
|
|
1653
|
-
listObjects: (prefix) => listObjectsInS3(client, bucketName, toStorageKey(prefix), rootPrefix).then((keys) => keys.map(fromStorageKey)),
|
|
1654
|
-
loadObject: (key) => loadJsonFromS3(client, bucketName, toStorageKey(key)),
|
|
1655
|
-
uploadObject: (key, data) => uploadJsonToS3(client, bucketName, toStorageKey(key), data),
|
|
1656
|
-
deleteObject: (key) => deleteObjectInS3(client, bucketName, toStorageKey(key)),
|
|
1657
|
-
shouldSkipLoadObjectError: (error) => error instanceof Error && error.name === "S3ArchivedObjectError",
|
|
1658
|
-
invalidatePaths: (pathsToInvalidate) => {
|
|
1659
|
-
if (cloudfrontClient && cloudfrontDistributionId && pathsToInvalidate.length > 0) return invalidateCloudFront(cloudfrontClient, cloudfrontDistributionId, pathsToInvalidate, { shouldWaitForInvalidation });
|
|
1660
|
-
return Promise.resolve();
|
|
1661
|
-
}
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
});
|
|
1665
|
-
//#endregion
|
|
1666
|
-
//#region src/s3Storage.ts
|
|
1667
|
-
const s3Storage = createUniversalStoragePlugin({
|
|
1668
|
-
name: "s3Storage",
|
|
1669
|
-
supportedProtocol: "s3",
|
|
1670
|
-
factory: (config) => {
|
|
1671
|
-
const { bucketName, ...s3Config } = config;
|
|
1672
|
-
const client = new S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
1673
|
-
const normalizedBasePath = config.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
1674
|
-
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
1675
|
-
const getListPrefix = (prefix = "") => {
|
|
1676
|
-
const value = [normalizedBasePath, prefix.replace(/^\/+|\/+$/g, "")].filter(Boolean).join("/");
|
|
1677
|
-
return value ? `${value}/` : "";
|
|
1678
|
-
};
|
|
1679
|
-
const getRelativeKey = (key) => {
|
|
1680
|
-
if (!normalizedBasePath) return key;
|
|
1681
|
-
const basePrefix = `${normalizedBasePath}/`;
|
|
1682
|
-
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
1683
|
-
};
|
|
1684
|
-
return {
|
|
1685
|
-
node: {
|
|
1686
|
-
async listObjects(prefix) {
|
|
1687
|
-
const objects = [];
|
|
1688
|
-
let continuationToken;
|
|
1689
|
-
do {
|
|
1690
|
-
const response = await client.send(new ListObjectsV2Command({
|
|
1691
|
-
Bucket: bucketName,
|
|
1692
|
-
ContinuationToken: continuationToken,
|
|
1693
|
-
Prefix: getListPrefix(prefix)
|
|
1694
|
-
}));
|
|
1695
|
-
for (const object of response.Contents ?? []) {
|
|
1696
|
-
if (!object.Key) continue;
|
|
1697
|
-
objects.push({
|
|
1698
|
-
key: getRelativeKey(object.Key),
|
|
1699
|
-
lastModifiedAt: object.LastModified,
|
|
1700
|
-
size: object.Size ?? 0,
|
|
1701
|
-
storageUri: `s3://${bucketName}/${object.Key}`
|
|
1702
|
-
});
|
|
1703
|
-
}
|
|
1704
|
-
continuationToken = response.NextContinuationToken;
|
|
1705
|
-
} while (continuationToken);
|
|
1706
|
-
return objects;
|
|
1707
|
-
},
|
|
1708
|
-
async deleteObjects(relativeKeys) {
|
|
1709
|
-
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
1710
|
-
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
1711
|
-
const batch = keys.slice(offset, offset + 1e3);
|
|
1712
|
-
if (batch.length === 0) continue;
|
|
1713
|
-
const response = await client.send(new DeleteObjectsCommand({
|
|
1714
|
-
Bucket: bucketName,
|
|
1715
|
-
Delete: {
|
|
1716
|
-
Objects: batch.map((Key) => ({ Key })),
|
|
1717
|
-
Quiet: true
|
|
1718
|
-
}
|
|
1719
|
-
}));
|
|
1720
|
-
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
1721
|
-
}
|
|
1722
|
-
},
|
|
1723
|
-
async delete(storageUri) {
|
|
1724
|
-
const { bucket, key } = parseStorageUri(storageUri, "s3");
|
|
1725
|
-
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
1726
|
-
const listCommand = new ListObjectsV2Command({
|
|
1727
|
-
Bucket: bucketName,
|
|
1728
|
-
Prefix: key
|
|
1729
|
-
});
|
|
1730
|
-
const listResponse = await client.send(listCommand);
|
|
1731
|
-
if (listResponse.Contents && listResponse.Contents.length > 0) {
|
|
1732
|
-
const deleteCommand = new DeleteObjectsCommand({
|
|
1733
|
-
Bucket: bucketName,
|
|
1734
|
-
Delete: {
|
|
1735
|
-
Objects: listResponse.Contents.map((obj) => ({ Key: obj.Key })),
|
|
1736
|
-
Quiet: true
|
|
1737
|
-
}
|
|
1738
|
-
});
|
|
1739
|
-
await client.send(deleteCommand);
|
|
1740
|
-
return;
|
|
1741
|
-
}
|
|
1742
|
-
throw new Error("Bundle Not Found");
|
|
1743
|
-
},
|
|
1744
|
-
async upload(key, filePath) {
|
|
1745
|
-
const Body = await fs.readFile(filePath);
|
|
1746
|
-
const ContentType = getContentType(filePath);
|
|
1747
|
-
const Key = getStorageKey(key, path.basename(filePath));
|
|
1748
|
-
const response = await new Upload({
|
|
1749
|
-
client,
|
|
1750
|
-
params: {
|
|
1751
|
-
ContentType,
|
|
1752
|
-
Bucket: bucketName,
|
|
1753
|
-
Key,
|
|
1754
|
-
Body,
|
|
1755
|
-
CacheControl: "max-age=31536000"
|
|
1756
|
-
}
|
|
1757
|
-
}).done();
|
|
1758
|
-
if (!response.Bucket || !response.Key) throw new Error("Upload Failed");
|
|
1759
|
-
return { storageUri: `s3://${bucketName}/${Key}` };
|
|
1760
|
-
},
|
|
1761
|
-
async exists(storageUri) {
|
|
1762
|
-
const { bucket, key } = parseStorageUri(storageUri, "s3");
|
|
1763
|
-
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
1764
|
-
try {
|
|
1765
|
-
await client.send(new HeadObjectCommand({
|
|
1766
|
-
Bucket: bucketName,
|
|
1767
|
-
Key: key
|
|
1768
|
-
}));
|
|
1769
|
-
return true;
|
|
1770
|
-
} catch (error) {
|
|
1771
|
-
if (error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey")) return false;
|
|
1772
|
-
throw error;
|
|
1773
|
-
}
|
|
1774
|
-
},
|
|
1775
|
-
async downloadFile(storageUri, filePath) {
|
|
1776
|
-
const { bucket, key } = parseStorageUri(storageUri, "s3");
|
|
1777
|
-
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
1778
|
-
const response = await client.send(new GetObjectCommand({
|
|
1779
|
-
Bucket: bucket,
|
|
1780
|
-
Key: key
|
|
1781
|
-
}));
|
|
1782
|
-
if (!response.Body) throw new Error("S3 object body is empty");
|
|
1783
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1784
|
-
await fs.writeFile(filePath, await response.Body.transformToByteArray());
|
|
1785
|
-
}
|
|
1786
|
-
},
|
|
1787
|
-
runtime: {
|
|
1788
|
-
async readText(storageUri) {
|
|
1789
|
-
const { bucket, key } = parseStorageUri(storageUri, "s3");
|
|
1790
|
-
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
1791
|
-
try {
|
|
1792
|
-
const response = await client.send(new GetObjectCommand({
|
|
1793
|
-
Bucket: bucket,
|
|
1794
|
-
Key: key
|
|
1795
|
-
}));
|
|
1796
|
-
if (!response.Body) return null;
|
|
1797
|
-
return response.Body.transformToString();
|
|
1798
|
-
} catch (error) {
|
|
1799
|
-
if (error instanceof Error && error.name === "NoSuchKey") return null;
|
|
1800
|
-
throw error;
|
|
1801
|
-
}
|
|
1802
|
-
},
|
|
1803
|
-
async getDownloadUrl(storageUri) {
|
|
1804
|
-
const { bucket, key } = parseStorageUri(storageUri, "s3");
|
|
1805
|
-
if (!bucket || !key) throw new Error("Invalid S3 storage URI: missing bucket or key");
|
|
1806
|
-
try {
|
|
1807
|
-
const signedUrl = await getSignedUrl(client, new GetObjectCommand({
|
|
1808
|
-
Bucket: bucket,
|
|
1809
|
-
Key: key
|
|
1810
|
-
}), { expiresIn: 3600 });
|
|
1811
|
-
if (!signedUrl) throw new Error("Failed to presign S3 URL");
|
|
1812
|
-
return { fileUrl: signedUrl };
|
|
1813
|
-
} catch (e) {
|
|
1814
|
-
throw new Error(e instanceof Error ? `Failed to presign S3 URL: ${e.message}` : "Failed to presign S3 URL");
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
});
|
|
1821
|
-
//#endregion
|
|
1822
|
-
//#region src/withCloudFrontSignedUrl.ts
|
|
1819
|
+
//#region src/cloudFrontDownloadUrl.ts
|
|
1823
1820
|
const ONE_YEAR_IN_SECONDS = 3600 * 24 * 365;
|
|
1824
1821
|
const privateKeyCache = /* @__PURE__ */ new Map();
|
|
1825
1822
|
const getPrivateKeyFromSsm = async (region, parameterName) => {
|
|
1826
|
-
|
|
1827
|
-
const parameter = (await new SSM(applySsmRuntimeAwsConfig({ region })).getParameter({
|
|
1823
|
+
const value = (await new SSM(applySsmRuntimeAwsConfig({ region })).getParameter({
|
|
1828
1824
|
Name: parameterName,
|
|
1829
1825
|
WithDecryption: true
|
|
1830
|
-
})).Parameter;
|
|
1831
|
-
if (!
|
|
1832
|
-
const
|
|
1833
|
-
if (
|
|
1834
|
-
let keyPair;
|
|
1835
|
-
try {
|
|
1836
|
-
keyPair = JSON.parse(parameterValue);
|
|
1837
|
-
} catch (error) {
|
|
1838
|
-
throw new Error(`Invalid JSON format in SSM parameter: ${parameterName}. ${error instanceof Error ? error.message : String(error)}`);
|
|
1839
|
-
}
|
|
1840
|
-
const privateKey = keyPair.privateKey;
|
|
1841
|
-
if (!privateKey || typeof privateKey !== "string") throw new Error(`Invalid private key format in SSM parameter: ${parameterName}`);
|
|
1826
|
+
})).Parameter?.Value;
|
|
1827
|
+
if (!value) throw new Error(`CloudFront private key is missing: ${parameterName}`);
|
|
1828
|
+
const privateKey = JSON.parse(value).privateKey;
|
|
1829
|
+
if (typeof privateKey !== "string" || privateKey.length === 0) throw new Error(`Invalid CloudFront private key: ${parameterName}`);
|
|
1842
1830
|
return privateKey;
|
|
1843
1831
|
};
|
|
1844
1832
|
const resolvePrivateKey = (config) => {
|
|
1845
|
-
if (
|
|
1833
|
+
if (config.getPrivateKey) return config.getPrivateKey();
|
|
1846
1834
|
const cacheKey = `${config.ssmRegion}:${config.ssmParameterName}`;
|
|
1847
|
-
const
|
|
1848
|
-
if (
|
|
1849
|
-
const
|
|
1835
|
+
const cached = privateKeyCache.get(cacheKey);
|
|
1836
|
+
if (cached) return cached;
|
|
1837
|
+
const pending = getPrivateKeyFromSsm(config.ssmRegion, config.ssmParameterName).catch((error) => {
|
|
1850
1838
|
privateKeyCache.delete(cacheKey);
|
|
1851
1839
|
throw error;
|
|
1852
1840
|
});
|
|
1853
|
-
privateKeyCache.set(cacheKey,
|
|
1854
|
-
return
|
|
1841
|
+
privateKeyCache.set(cacheKey, pending);
|
|
1842
|
+
return pending;
|
|
1855
1843
|
};
|
|
1856
|
-
const
|
|
1857
|
-
const
|
|
1858
|
-
if (
|
|
1859
|
-
|
|
1844
|
+
const cloudFrontDownloadUrl = (config) => async ({ storageUri }) => {
|
|
1845
|
+
const storageUrl = new URL(storageUri);
|
|
1846
|
+
if (storageUrl.protocol !== "s3:") throw new Error("CloudFront download URLs require an s3 storage URI.");
|
|
1847
|
+
const url = new URL(config.publicBaseUrl);
|
|
1848
|
+
url.pathname = storageUrl.pathname;
|
|
1849
|
+
url.search = "";
|
|
1850
|
+
return { url: getSignedUrl({
|
|
1851
|
+
url: url.toString(),
|
|
1852
|
+
keyPairId: config.keyPairId,
|
|
1853
|
+
privateKey: await resolvePrivateKey(config),
|
|
1854
|
+
dateLessThan: new Date(Date.now() + (config.expiresSeconds ?? ONE_YEAR_IN_SECONDS) * 1e3).toISOString()
|
|
1855
|
+
}) };
|
|
1860
1856
|
};
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1857
|
+
//#endregion
|
|
1858
|
+
//#region src/s3Storage.ts
|
|
1859
|
+
const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
|
|
1860
|
+
function s3Storage(config) {
|
|
1861
|
+
const { bucketName, basePath, downloadUrlSigningKey, getDownloadUrl: configuredGetDownloadUrl, ...s3Config } = config;
|
|
1862
|
+
const getDownloadUrl = configuredGetDownloadUrl ?? (downloadUrlSigningKey ? createStorageDownloadUrl(downloadUrlSigningKey) : void 0);
|
|
1863
|
+
const client = new S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
1864
|
+
const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
1865
|
+
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
1866
|
+
const getListPrefix = (prefix = "") => {
|
|
1867
|
+
const value = [normalizedBasePath, prefix.replace(/^\/+|\/+$/g, "")].filter(Boolean).join("/");
|
|
1868
|
+
return value ? `${value}/` : "";
|
|
1869
|
+
};
|
|
1870
|
+
const getRelativeKey = (key) => {
|
|
1871
|
+
if (!normalizedBasePath) return key;
|
|
1872
|
+
const basePrefix = `${normalizedBasePath}/`;
|
|
1873
|
+
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
1874
|
+
};
|
|
1875
|
+
const parseAndValidate = (storageUri) => {
|
|
1876
|
+
const parsed = parseStorageUri(storageUri, "s3");
|
|
1877
|
+
if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
|
|
1878
|
+
return parsed;
|
|
1879
|
+
};
|
|
1880
|
+
return createStoragePlugin({
|
|
1881
|
+
name: "s3Storage",
|
|
1882
|
+
protocol: "s3",
|
|
1883
|
+
async listObjects(prefix) {
|
|
1884
|
+
const objects = [];
|
|
1885
|
+
let continuationToken;
|
|
1886
|
+
do {
|
|
1887
|
+
const response = await client.send(new ListObjectsV2Command({
|
|
1888
|
+
Bucket: bucketName,
|
|
1889
|
+
ContinuationToken: continuationToken,
|
|
1890
|
+
Prefix: getListPrefix(prefix)
|
|
1891
|
+
}));
|
|
1892
|
+
for (const object of response.Contents ?? []) {
|
|
1893
|
+
if (!object.Key) continue;
|
|
1894
|
+
objects.push({
|
|
1895
|
+
key: getRelativeKey(object.Key),
|
|
1896
|
+
lastModifiedAt: object.LastModified,
|
|
1897
|
+
size: object.Size ?? 0,
|
|
1898
|
+
storageUri: `s3://${bucketName}/${object.Key}`
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
continuationToken = response.NextContinuationToken;
|
|
1902
|
+
} while (continuationToken);
|
|
1903
|
+
return objects;
|
|
1904
|
+
},
|
|
1905
|
+
async deleteObjects(relativeKeys) {
|
|
1906
|
+
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
1907
|
+
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
1908
|
+
const batch = keys.slice(offset, offset + 1e3);
|
|
1909
|
+
if (batch.length === 0) continue;
|
|
1910
|
+
const response = await client.send(new DeleteObjectsCommand({
|
|
1911
|
+
Bucket: bucketName,
|
|
1912
|
+
Delete: {
|
|
1913
|
+
Objects: batch.map((Key) => ({ Key })),
|
|
1914
|
+
Quiet: true
|
|
1884
1915
|
}
|
|
1916
|
+
}));
|
|
1917
|
+
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
1918
|
+
}
|
|
1919
|
+
},
|
|
1920
|
+
async put({ key, body, contentLength, contentType }) {
|
|
1921
|
+
const storageKey = getStorageKey(key);
|
|
1922
|
+
const response = await new Upload({
|
|
1923
|
+
client,
|
|
1924
|
+
params: {
|
|
1925
|
+
Body: body,
|
|
1926
|
+
Bucket: bucketName,
|
|
1927
|
+
CacheControl: "max-age=31536000",
|
|
1928
|
+
...contentLength === void 0 ? {} : { ContentLength: contentLength },
|
|
1929
|
+
ContentType: contentType,
|
|
1930
|
+
Key: storageKey
|
|
1885
1931
|
}
|
|
1932
|
+
}).done();
|
|
1933
|
+
if (!response.Bucket || !response.Key) throw new Error("Upload failed");
|
|
1934
|
+
return { storageUri: createStorageUri({
|
|
1935
|
+
bucket: bucketName,
|
|
1936
|
+
key: storageKey,
|
|
1937
|
+
protocol: "s3"
|
|
1938
|
+
}) };
|
|
1939
|
+
},
|
|
1940
|
+
async get({ storageUri }) {
|
|
1941
|
+
const { key } = parseAndValidate(storageUri);
|
|
1942
|
+
try {
|
|
1943
|
+
const response = await client.send(new GetObjectCommand({
|
|
1944
|
+
Bucket: bucketName,
|
|
1945
|
+
Key: key
|
|
1946
|
+
}));
|
|
1947
|
+
if (!response.Body) return { response: null };
|
|
1948
|
+
const headers = new Headers();
|
|
1949
|
+
if (response.ContentType) headers.set("content-type", response.ContentType);
|
|
1950
|
+
if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
|
|
1951
|
+
return { response: new Response(response.Body.transformToWebStream(), { headers }) };
|
|
1952
|
+
} catch (error) {
|
|
1953
|
+
if (isObjectNotFoundError(error)) return { response: null };
|
|
1954
|
+
throw error;
|
|
1886
1955
|
}
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1956
|
+
},
|
|
1957
|
+
...getDownloadUrl ? { async getDownloadUrl(input) {
|
|
1958
|
+
parseAndValidate(input.storageUri);
|
|
1959
|
+
return getDownloadUrl(input);
|
|
1960
|
+
} } : {},
|
|
1961
|
+
async exists({ storageUri }) {
|
|
1962
|
+
const { key } = parseAndValidate(storageUri);
|
|
1963
|
+
try {
|
|
1964
|
+
await client.send(new HeadObjectCommand({
|
|
1965
|
+
Bucket: bucketName,
|
|
1966
|
+
Key: key
|
|
1967
|
+
}));
|
|
1968
|
+
return { exists: true };
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
if (isObjectNotFoundError(error)) return { exists: false };
|
|
1971
|
+
throw error;
|
|
1972
|
+
}
|
|
1973
|
+
},
|
|
1974
|
+
async delete({ storageUri }) {
|
|
1975
|
+
const { key } = parseAndValidate(storageUri);
|
|
1976
|
+
await client.send(new DeleteObjectCommand({
|
|
1977
|
+
Bucket: bucketName,
|
|
1978
|
+
Key: key
|
|
1979
|
+
}));
|
|
1980
|
+
return { deleted: true };
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1896
1984
|
//#endregion
|
|
1897
|
-
export {
|
|
1985
|
+
export { DYNAMODB_UPDATE_INDEX_NAME, cloudFrontDownloadUrl, dynamoDB, s3Storage };
|