From 560329a0e93ddfe106b5d3c42c1edaafa29c4a6b Mon Sep 17 00:00:00 2001
From: Travis Vachon
Date: Thu, 19 Sep 2024 18:56:40 +0200
Subject: [PATCH 1/6] fix: disable UCAN log expiration (#416)
it looks like we set this bucket up to expire items - in retrospect I
think we should have created a separate bucket for the UCAN log because
this stream log store bucket was probably originally meant to store
streaming items temporarily
disable expiration for now, but uncomment with a note
---
stacks/firehose-stack.js | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/stacks/firehose-stack.js b/stacks/firehose-stack.js
index 7234487f..cfccb7a0 100644
--- a/stacks/firehose-stack.js
+++ b/stacks/firehose-stack.js
@@ -34,10 +34,13 @@ export function UcanFirehoseStack ({ stack, app }) {
cdk: {
bucket: {
...getBucketConfig('stream-log-store', app.stage),
- lifecycleRules: [{
- enabled: true,
- expiration: Duration.days(90)
- }]
+ lifecycleRules: [
+ // disable this for now - maybe we do want lifecycle rules eventually but we DEFINITELY don't want to expire these logs!
+ // {
+ // enabled: true,
+ // expiration: Duration.days(90)
+ // }
+ ]
}
}
})
From 53cf7cf60ca0f80555ba77cb8b41a19334ce8693 Mon Sep 17 00:00:00 2001
From: Hannah Howard
Date: Thu, 19 Sep 2024 12:17:44 -0700
Subject: [PATCH 2/6] Disable flaky filecoin test for now (#419)
# Goals
currently, seed.run PRs fail 90%+ of the time because of the Filecoin
test.
until we have a more reliable way to test the filecoin flow, the PR
disables it.
the inserted comment suggests a few solutions -- either spinning up
custom infra for w3filecoin pipeline on each pr to get more reliable
results, or mocking out the pipeline
either way, this is the temporary fix -- I don't want to put in extra
retries or anything -- we just need to find a better way to test, or
investigate if there are in fact underlying issues, but in the meantime,
other PRs need to get merged.
---------
Co-authored-by: Petra Jaros
---
test/filecoin.test.js | 166 ++++++++++++++++++++++--------------------
1 file changed, 88 insertions(+), 78 deletions(-)
diff --git a/test/filecoin.test.js b/test/filecoin.test.js
index 4f37bfbb..fb104325 100644
--- a/test/filecoin.test.js
+++ b/test/filecoin.test.js
@@ -1,16 +1,13 @@
import { testFilecoin as test } from './helpers/context.js'
import { fetch } from '@web-std/fetch'
import pWaitFor from 'p-wait-for'
-import pRetry from 'p-retry'
import * as CAR from '@ucanto/transport/car'
import { Storefront } from '@web3-storage/filecoin-client'
-import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import * as Link from 'multiformats/link'
import * as raw from 'multiformats/codecs/raw'
import { base58btc } from 'multiformats/bases/base58'
import * as AgentStore from '../upload-api/stores/agent.js'
-import { useReceiptStore } from '../filecoin/store/receipt.js'
import {
getApiEndpoint,
@@ -24,7 +21,7 @@ import {
import { createMailSlurpInbox, setupNewClient } from './helpers/up-client.js'
import { getClientConfig } from './helpers/fil-client.js'
import { randomFile } from './helpers/random.js'
-import { putTableItem, pollQueryTable } from './helpers/table.js'
+import { pollQueryTable } from './helpers/table.js'
import { waitForStoreOperationOkResult } from './helpers/store.js'
/**
@@ -42,7 +39,7 @@ test.before(t => {
test('w3filecoin integration flow', async t => {
const stage = getStage()
const s3Client = getAwsBucketClient()
- const s3ClientFilecoin = getAwsBucketClient('us-east-2')
+ // const s3ClientFilecoin = getAwsBucketClient('us-east-2')
const inbox = await createMailSlurpInbox()
const endpoint = t.context.apiEndpoint
@@ -147,6 +144,7 @@ test('w3filecoin integration flow', async t => {
const workflowWithReceiptResponseAfterRedirect = await fetch(workflowLocation)
// Get receipt from Message Archive
const agentMessageBytes = new Uint8Array((await workflowWithReceiptResponseAfterRedirect.arrayBuffer()))
+ console.log(agentMessageBytes.length)
const agentMessage = await CAR.request.decode({
body: agentMessageBytes,
headers: {},
@@ -175,7 +173,7 @@ test('w3filecoin integration flow', async t => {
name: '',
},
})
- const receiptStoreFilecoin = useReceiptStore(s3ClientFilecoin, 'invocation-store-staging-0', 'workflow-store-staging-0')
+ // const receiptStoreFilecoin = useReceiptStore(s3ClientFilecoin, 'invocation-store-staging-0', 'workflow-store-staging-0')
// Await for `filecoin/submit` receipt
console.log(`wait for filecoin/submit receipt ${filecoinSubmitReceiptCid.toString()} ...`)
@@ -189,62 +187,74 @@ test('w3filecoin integration flow', async t => {
if (!pieceOfferReceiptCid) {
throw new Error('filecoin/submit receipt has no effect for piece/offer')
}
- console.log(`wait for piece/offer receipt ${pieceOfferReceiptCid.toString()} ...`)
- const receiptPieceOfferRes = await waitForStoreOperationOkResult(
- () => receiptStoreFilecoin.get(pieceOfferReceiptCid),
- (res) => Boolean(res.ok)
- )
- // Await for `piece/accept` receipt
- const pieceAcceptReceiptCid = receiptPieceOfferRes.ok?.fx.join?.link()
- if (!pieceAcceptReceiptCid) {
- throw new Error('piece/offer receipt has no effect for piece/accept')
- }
- console.log(`wait for piece/accept receipt ${pieceAcceptReceiptCid.toString()} ...`)
- const receiptPieceAcceptRes = await waitForStoreOperationOkResult(
- () => receiptStoreFilecoin.get(pieceAcceptReceiptCid),
- (res) => Boolean(res.ok)
- )
- // Await for `aggregate/offer` receipt
- const aggregateOfferReceiptCid = receiptPieceAcceptRes.ok?.fx.join?.link()
- if (!aggregateOfferReceiptCid) {
- throw new Error('piece/accept receipt has no effect for aggregate/offer')
- }
- console.log(`wait for aggregate/offer receipt ${aggregateOfferReceiptCid.toString()} ...`)
- const receiptAggregateOfferRes = await waitForStoreOperationOkResult(
- () => receiptStoreFilecoin.get(aggregateOfferReceiptCid),
- (res) => Boolean(res.ok)
- )
+ // TODO: This code is disabled as it tests running, real, shared infrastructure
+ // that is maintained outside of this repo, and could fail for reasons unrelated
+ // to what's happening in this repo
+ // To the extent we want a kitchen-sink test, it should happen with
+ // infra deployed specifically for the test, with predictable performance
+ // Alternatively, if we're simply testing interactions with expected behavior
+ // for w3-filecoininfra, we should use a mocked version of the service with
+ // predictable responses.
+ // This rest of this test is disabled until one of these solutions is put
+ // in place
- // @ts-ignore no type for aggregate
- const aggregate = receiptAggregateOfferRes.ok?.out.ok?.aggregate
+ // console.log(`wait for piece/offer receipt ${pieceOfferReceiptCid.toString()} ...`)
+ // await waitForStoreOperationOkResult(
+ // () => receiptStoreFilecoin.get(pieceOfferReceiptCid),
+ // (res) => Boolean(res.ok)
+ // )
+ // // Await for `piece/accept` receipt
+ // const pieceAcceptReceiptCid = receiptPieceOfferRes.ok?.fx.join?.link()
+ // if (!pieceAcceptReceiptCid) {
+ // throw new Error('piece/offer receipt has no effect for piece/accept')
+ // }
+ // console.log(`wait for piece/accept receipt ${pieceAcceptReceiptCid.toString()} ...`)
+ // const receiptPieceAcceptRes = await waitForStoreOperationOkResult(
+ // () => receiptStoreFilecoin.get(pieceAcceptReceiptCid),
+ // (res) => Boolean(res.ok)
+ // )
- // Put FAKE value in table to issue final receipt via cron?
- const dealId = 1111
- console.log(`put deal on deal tracker for aggregate ${aggregate}`)
- await putDealToDealTracker(aggregate.toString(), dealId)
+ // // Await for `aggregate/offer` receipt
+ // const aggregateOfferReceiptCid = receiptPieceAcceptRes.ok?.fx.join?.link()
+ // if (!aggregateOfferReceiptCid) {
+ // throw new Error('piece/accept receipt has no effect for aggregate/offer')
+ // }
+ // console.log(`wait for aggregate/offer receipt ${aggregateOfferReceiptCid.toString()} ...`)
+ // const receiptAggregateOfferRes = await waitForStoreOperationOkResult(
+ // () => receiptStoreFilecoin.get(aggregateOfferReceiptCid),
+ // (res) => Boolean(res.ok)
+ // )
- // Await for `aggregate/accept` receipt
- const aggregateAcceptReceiptCid = receiptAggregateOfferRes.ok?.fx.join?.link()
- if (!aggregateAcceptReceiptCid) {
- throw new Error('aggregate/offer receipt has no effect for aggregate/accept')
- }
- console.log(`wait for aggregate/accept receipt ${aggregateAcceptReceiptCid.toString()} ...`)
- await waitForStoreOperationOkResult(
- async () => {
- // Trigger cron to update and issue receipts based on deals
- await pRetry(async () => {
- const url = 'https://staging.dealer.web3.storage/cron'
- const res = await fetch(url)
- if (!res.ok) throw new Error(`failed request to ${url}: ${res.status}`)
- }, { onFailedAttempt: console.warn })
+ // // @ts-ignore no type for aggregate
+ // const aggregate = receiptAggregateOfferRes.ok?.out.ok?.aggregate
- return receiptStoreFilecoin.get(aggregateAcceptReceiptCid)
- // return agentStoreFilecoin.receipts.get(aggregateAcceptReceiptCid)
- },
- (res) => Boolean(res.ok)
- )
+ // // Put FAKE value in table to issue final receipt via cron?
+ // const dealId = 1111
+ // console.log(`put deal on deal tracker for aggregate ${aggregate}`)
+ // await putDealToDealTracker(aggregate.toString(), dealId)
+
+ // // Await for `aggregate/accept` receipt
+ // const aggregateAcceptReceiptCid = receiptAggregateOfferRes.ok?.fx.join?.link()
+ // if (!aggregateAcceptReceiptCid) {
+ // throw new Error('aggregate/offer receipt has no effect for aggregate/accept')
+ // }
+ // console.log(`wait for aggregate/accept receipt ${aggregateAcceptReceiptCid.toString()} ...`)
+ // await waitForStoreOperationOkResult(
+ // async () => {
+ // // Trigger cron to update and issue receipts based on deals
+ // await pRetry(async () => {
+ // const url = 'https://staging.dealer.web3.storage/cron'
+ // const res = await fetch(url)
+ // if (!res.ok) throw new Error(`failed request to ${url}: ${res.status}`)
+ // }, { onFailedAttempt: console.warn })
+
+ // return receiptStoreFilecoin.get(aggregateAcceptReceiptCid)
+ // // return agentStoreFilecoin.receipts.get(aggregateAcceptReceiptCid)
+ // },
+ // (res) => Boolean(res.ok)
+ // )
}))
})
@@ -270,25 +280,25 @@ async function getPiecesByContent (t, content) {
return item
}
-/**
- * @param {string} piece
- * @param {number} dealId
- */
-async function putDealToDealTracker (piece, dealId) {
- const region = 'us-east-2'
- const endpoint = `https://dynamodb.${region}.amazonaws.com`
- const tableName = 'staging-w3filecoin-deal-tracker-deal-store-v1'
- const client = new DynamoDBClient({
- region,
- endpoint
- })
- const record = {
- piece,
- provider: 'f0001',
- dealId,
- expirationEpoch: Date.now() + 10e9,
- insertedAt: (new Date()).toISOString(),
- source: 'testing'
- }
- await putTableItem(client, tableName, record)
-}
+// /**
+// * @param {string} piece
+// * @param {number} dealId
+// */
+// async function putDealToDealTracker (piece, dealId) {
+// const region = 'us-east-2'
+// const endpoint = `https://dynamodb.${region}.amazonaws.com`
+// const tableName = 'staging-w3filecoin-deal-tracker-deal-store-v1'
+// const client = new DynamoDBClient({
+// region,
+// endpoint
+// })
+// const record = {
+// piece,
+// provider: 'f0001',
+// dealId,
+// expirationEpoch: Date.now() + 10e9,
+// insertedAt: (new Date()).toISOString(),
+// source: 'testing'
+// }
+// await putTableItem(client, tableName, record)
+// }
From 374951532f8fac0c2fc663cd28b4c4a030c5aa1c Mon Sep 17 00:00:00 2001
From: Petra Jaros
Date: Thu, 19 Sep 2024 15:20:12 -0400
Subject: [PATCH 3/6] Storacha-branded validation flow (#417)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
**Before Merge:**
* [ ] Set `HOSTED_ZONES` (plural) on `prod`.
* [ ] Merge https://github.com/storacha-network/w3infra/pull/419 (which
this currently includes to get tests to pass).
---
With `NEXT_PUBLIC_W3UP_SERVICE_URL=https://petra.up.storacha.network` in
my local `console`:
![CleanShot 2024-08-19 at 17 33
15](https://github.com/user-attachments/assets/66aa54da-dbf6-47f1-9b1d-a151e9546b9f)
Then…
![CleanShot 2024-08-19 at 17 34
12](https://github.com/user-attachments/assets/03141163-ec6f-447b-bdee-48e3d0ab8abc)
And success!
![CleanShot 2024-08-19 at 17 33
55](https://github.com/user-attachments/assets/fe88b106-daa3-43dd-86ec-a71516bf1c0d)
Or, failure.
![CleanShot 2024-08-19 at 17 34
19](https://github.com/user-attachments/assets/5a8dd394-0f90-429d-a3de-dc6ca9ea3819)
Note that that error message is a dummy message in the test harness, not
something real. Also, I see to have bumped something that made the
Stripe section always appear. Previously, staging/prod was set not to
show it by not providing the right env vars. I'm not sure why that
changed, and whether we want it to.
Closes https://github.com/storacha-network/project-tracking/issues/119
---------
Co-authored-by: hannahhoward
---
.env.tpl | 7 +-
README.md | 4 +-
package-lock.json | 1004 ++++++++++++++++-
package.json | 12 +-
stacks/config.js | 40 +-
stacks/filecoin-stack.js | 2 +-
stacks/upload-api-stack.js | 240 ++--
test/helpers/deployment.js | 18 +-
tsconfig.json | 19 +-
upload-api/email.js | 44 +-
.../functions/ucan-invocation-router.js | 4 +-
upload-api/functions/validate-email.jsx | 142 ++-
upload-api/html-storacha/index.jsx | 273 +++++
upload-api/html-storacha/preact-jsx.d.ts | 25 +
upload-api/html-storacha/storacha-logo.svg | 1 +
upload-api/html-storacha/svg.d.ts | 5 +
upload-api/{html.jsx => html-w3s/index.jsx} | 5 +-
upload-api/package.json | 9 +-
.../test/helpers/validate-email-server.js | 126 ++-
19 files changed, 1706 insertions(+), 274 deletions(-)
create mode 100644 upload-api/html-storacha/index.jsx
create mode 100644 upload-api/html-storacha/preact-jsx.d.ts
create mode 100644 upload-api/html-storacha/storacha-logo.svg
create mode 100644 upload-api/html-storacha/svg.d.ts
rename upload-api/{html.jsx => html-w3s/index.jsx} (98%)
diff --git a/.env.tpl b/.env.tpl
index 89d5168c..1b4c160f 100644
--- a/.env.tpl
+++ b/.env.tpl
@@ -1,8 +1,9 @@
# These variables are only available in your SST code.
-# uncomment to try out deploying the w3up api under a custom domain.
-# the value should match a hosted zone configured in route53 that your aws account has access to.
-# HOSTED_ZONE=up.dag.haus
+# uncomment to try out deploying the w3up api under a custom domain (or more
+# than one). the value should match a hosted zone configured in route53 that
+# your aws account has access to.
+# HOSTED_ZONES=up.web3.storage,up.storacha.network
# uncomment to try out deploying the roundabout api under a custom domain.
# the value should match a hosted zone configured in route53 that your aws account has access to.
diff --git a/README.md b/README.md
index 069f539e..0369b22f 100644
--- a/README.md
+++ b/README.md
@@ -112,9 +112,9 @@ Please notice that appropriate environment variables must be set for the develop
Ensure the following variables are set in the env when deploying
-#### `HOSTED_ZONE`
+#### `HOSTED_ZONES`
-The root domain to deploy the w3up API to. e.g `up.web3.storage`. The value should match a hosted zone configured in route53 that your aws account has access to.
+The root domain(s) to deploy the w3up API to. e.g `up.web3.storage`. The value should match a hosted zone configured in route53 that your aws account has access to. Multiple zones can be specified, in which case they are seperated by a comma, and this will cause deployment to each specified zone.
#### `ROUNDABOUT_HOSTED_ZONE`
diff --git a/package-lock.json b/package-lock.json
index 313544b5..d0b277ce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -65,7 +65,7 @@
"multiformats": "^13.1.0",
"p-retry": "^6.2.0",
"p-wait-for": "^5.0.0",
- "typescript": "^4.9.3"
+ "typescript": "^5.5.4"
}
},
"billing": {
@@ -5780,7 +5780,6 @@
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.6.tgz",
"integrity": "sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==",
"dev": true,
- "peer": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -5792,8 +5791,7 @@
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true,
- "peer": true
+ "dev": true
},
"node_modules/@babel/template": {
"version": "7.24.6",
@@ -5999,6 +5997,22 @@
"node": "^14 || ^16 || ^17 || ^18 || ^19"
}
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+ "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/android-arm": {
"version": "0.18.13",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.13.tgz",
@@ -6254,6 +6268,22 @@
"node": ">=12"
}
},
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+ "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.13",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.13.tgz",
@@ -10537,6 +10567,16 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
},
+ "node_modules/@types/reload": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/@types/reload/-/reload-3.2.3.tgz",
+ "integrity": "sha512-mu4a71e0MJbQlw+cPjH+sZoEihq+QWR9cpM7z6tmM/ZX2rM85y0kji5Hq+szFLEKMRlIfhm+VsbE33WCCP5TpA==",
+ "dev": true,
+ "dependencies": {
+ "@types/express": "*",
+ "@types/ws": "*"
+ }
+ },
"node_modules/@types/retry": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
@@ -14570,6 +14610,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/cli-color": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz",
+ "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==",
+ "dev": true,
+ "dependencies": {
+ "d": "^1.0.1",
+ "es5-ext": "^0.10.64",
+ "es6-iterator": "^2.0.3",
+ "memoizee": "^0.4.15",
+ "timers-ext": "^0.1.7"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
@@ -14965,6 +15021,48 @@
"node": ">=10"
}
},
+ "node_modules/concurrently": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz",
+ "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.2",
+ "date-fns": "^2.30.0",
+ "lodash": "^4.17.21",
+ "rxjs": "^7.8.1",
+ "shell-quote": "^1.8.1",
+ "spawn-command": "0.0.2",
+ "supports-color": "^8.1.1",
+ "tree-kill": "^1.2.2",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": "^14.13.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/conf": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/conf/-/conf-11.0.2.tgz",
@@ -15256,6 +15354,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/d": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
+ "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
+ "dev": true,
+ "dependencies": {
+ "es5-ext": "^0.10.64",
+ "type": "^2.7.2"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
"node_modules/data-uri-to-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
@@ -15336,6 +15447,22 @@
"it-take": "^3.0.4"
}
},
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
"node_modules/date-time": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz",
@@ -15479,6 +15606,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
@@ -16194,12 +16330,64 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/es5-ext": {
+ "version": "0.10.64",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
+ "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "es6-iterator": "^2.0.3",
+ "es6-symbol": "^3.1.3",
+ "esniff": "^2.0.1",
+ "next-tick": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/es6-iterator": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+ "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+ "dev": true,
+ "dependencies": {
+ "d": "1",
+ "es5-ext": "^0.10.35",
+ "es6-symbol": "^3.1.1"
+ }
+ },
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"dev": true
},
+ "node_modules/es6-symbol": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
+ "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
+ "dev": true,
+ "dependencies": {
+ "d": "^1.0.2",
+ "ext": "^1.7.0"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/es6-weak-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
+ "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
+ "dev": true,
+ "dependencies": {
+ "d": "1",
+ "es5-ext": "^0.10.46",
+ "es6-iterator": "^2.0.3",
+ "es6-symbol": "^3.1.1"
+ }
+ },
"node_modules/esbuild": {
"version": "0.18.13",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.13.tgz",
@@ -16987,6 +17175,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/esniff": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
+ "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
+ "dev": true,
+ "dependencies": {
+ "d": "^1.0.1",
+ "es5-ext": "^0.10.62",
+ "event-emitter": "^0.3.5",
+ "type": "^2.7.2"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@@ -17067,6 +17270,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/event-emitter": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
+ "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
+ "dev": true,
+ "dependencies": {
+ "d": "1",
+ "es5-ext": "~0.10.14"
+ }
+ },
"node_modules/event-iterator": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/event-iterator/-/event-iterator-2.0.0.tgz",
@@ -17236,6 +17449,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/ext": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
+ "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
+ "dev": true,
+ "dependencies": {
+ "type": "^2.7.2"
+ }
+ },
"node_modules/fast-decode-uri-component": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
@@ -18086,6 +18308,19 @@
"node": ">=14"
}
},
+ "node_modules/hd-scripts/node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
"node_modules/helia": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/helia/-/helia-4.2.3.tgz",
@@ -18826,7 +19061,6 @@
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true,
- "peer": true,
"bin": {
"is-docker": "cli.js"
},
@@ -19228,7 +19462,6 @@
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true,
- "peer": true,
"dependencies": {
"is-docker": "^2.0.0"
},
@@ -20816,6 +21049,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lru-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
+ "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==",
+ "dev": true,
+ "dependencies": {
+ "es5-ext": "~0.10.2"
+ }
+ },
"node_modules/mailslurp-client": {
"version": "15.20.2",
"resolved": "https://registry.npmjs.org/mailslurp-client/-/mailslurp-client-15.20.2.tgz",
@@ -20970,6 +21212,31 @@
"dev": true,
"peer": true
},
+ "node_modules/memoizee": {
+ "version": "0.4.17",
+ "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz",
+ "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==",
+ "dev": true,
+ "dependencies": {
+ "d": "^1.0.2",
+ "es5-ext": "^0.10.64",
+ "es6-weak-map": "^2.0.3",
+ "event-emitter": "^0.3.5",
+ "is-promise": "^2.2.2",
+ "lru-queue": "^0.1.0",
+ "next-tick": "^1.1.0",
+ "timers-ext": "^0.1.7"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/memoizee/node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+ "dev": true
+ },
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -21872,6 +22139,12 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/next-tick": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
+ "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
+ "dev": true
+ },
"node_modules/nocache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz",
@@ -22015,6 +22288,73 @@
"url": "https://github.com/sponsors/antelle"
}
},
+ "node_modules/nodemon": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
+ "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==",
+ "dev": true,
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/nodemon/node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true
+ },
+ "node_modules/nodemon/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nodemon/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/nofilter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz",
@@ -23516,6 +23856,12 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true
+ },
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -24266,46 +24612,145 @@
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ=="
},
- "node_modules/remeda": {
- "version": "1.61.0",
- "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.61.0.tgz",
- "integrity": "sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A=="
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "engines": {
- "node": ">=0.10.0"
+ "node_modules/reload": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/reload/-/reload-3.3.0.tgz",
+ "integrity": "sha512-TObPRTy5dPlw9DS8n8ROd2BLnxI+RvVn4r0WBARVAfJ493jjcN70NI5TdkcrJmex2aQh5bfQJbFbr1NapU7Lnw==",
+ "dev": true,
+ "dependencies": {
+ "cli-color": "~2.0.0",
+ "commander": "~12.1.0",
+ "finalhandler": "~1.2.0",
+ "minimist": "~1.2.0",
+ "nodemon": "~3.1.4",
+ "open": "^8.0.0",
+ "serve-static": "~1.15.0",
+ "ws": "~8.18.0"
+ },
+ "bin": {
+ "reload": "bin/reload"
}
},
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "node_modules/reload/node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "dev": true,
"engines": {
- "node": ">=0.10.0"
+ "node": ">=18"
}
},
- "node_modules/require-main-filename": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
- "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "node_modules/reload/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
- "peer": true
+ "dependencies": {
+ "ms": "2.0.0"
+ }
},
- "node_modules/requireindex": {
+ "node_modules/reload/node_modules/finalhandler": {
"version": "1.2.0",
- "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
- "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
+ "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"dev": true,
- "engines": {
- "node": ">=0.10.5"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.8",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/reload/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true
+ },
+ "node_modules/reload/node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/reload/node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/reload/node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/remeda": {
+ "version": "1.61.0",
+ "resolved": "https://registry.npmjs.org/remeda/-/remeda-1.61.0.tgz",
+ "integrity": "sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A=="
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/requireindex": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz",
+ "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.5"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"dependencies": {
@@ -24449,6 +24894,15 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/rxjs": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
+ "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -24731,7 +25185,6 @@
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
"devOptional": true,
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -24830,6 +25283,30 @@
"simple-git-hooks": "cli.js"
}
},
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/simple-update-notifier/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -24898,6 +25375,12 @@
"integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==",
"dev": true
},
+ "node_modules/spawn-command": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
+ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
+ "dev": true
+ },
"node_modules/spdx-correct": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
@@ -26600,6 +27083,19 @@
"node": ">=4"
}
},
+ "node_modules/timers-ext": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz",
+ "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==",
+ "dev": true,
+ "dependencies": {
+ "es5-ext": "^0.10.64",
+ "next-tick": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
"node_modules/timestamp-nano": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/timestamp-nano/-/timestamp-nano-1.0.1.tgz",
@@ -26656,6 +27152,15 @@
"resolved": "tools",
"link": true
},
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
@@ -26765,6 +27270,12 @@
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"dev": true
},
+ "node_modules/type": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
+ "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
+ "dev": true
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -26889,16 +27400,16 @@
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
}
},
"node_modules/uint8-varint": {
@@ -26973,6 +27484,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true
+ },
"node_modules/undici": {
"version": "5.28.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz",
@@ -27632,9 +28149,9 @@
}
},
"node_modules/ws": {
- "version": "8.17.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
- "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": {
"node": ">=10.0.0"
},
@@ -28081,18 +28598,415 @@
"@babel/preset-react": "^7.23.3",
"@ipld/car": "^5.2.6",
"@types/aws-lambda": "^8.10.108",
+ "@types/reload": "^3.2.3",
"@web3-storage/content-claims-infra": "^1.2.1",
"@web3-storage/sigv4": "^1.0.2",
"ava": "^4.3.3",
"aws-lambda-test-utils": "^1.3.0",
+ "concurrently": "^8.2.2",
"constructs": "*",
"dotenv": "^16.3.2",
+ "esbuild": "^0.23.1",
+ "express": "^4.19.2",
+ "nodemon": "^3.1.4",
+ "reload": "^3.2.2",
"testcontainers": "^10.7.1"
},
"engines": {
"node": ">=16.15"
}
},
+ "upload-api/node_modules/@esbuild/android-arm": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+ "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/android-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+ "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/android-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+ "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+ "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/darwin-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+ "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+ "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+ "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-arm": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+ "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+ "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-ia32": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+ "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-loong64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+ "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+ "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+ "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+ "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-s390x": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+ "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/linux-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+ "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+ "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+ "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/sunos-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+ "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/win32-arm64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+ "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/win32-ia32": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+ "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/@esbuild/win32-x64": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+ "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "upload-api/node_modules/esbuild": {
+ "version": "0.23.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+ "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.23.1",
+ "@esbuild/android-arm": "0.23.1",
+ "@esbuild/android-arm64": "0.23.1",
+ "@esbuild/android-x64": "0.23.1",
+ "@esbuild/darwin-arm64": "0.23.1",
+ "@esbuild/darwin-x64": "0.23.1",
+ "@esbuild/freebsd-arm64": "0.23.1",
+ "@esbuild/freebsd-x64": "0.23.1",
+ "@esbuild/linux-arm": "0.23.1",
+ "@esbuild/linux-arm64": "0.23.1",
+ "@esbuild/linux-ia32": "0.23.1",
+ "@esbuild/linux-loong64": "0.23.1",
+ "@esbuild/linux-mips64el": "0.23.1",
+ "@esbuild/linux-ppc64": "0.23.1",
+ "@esbuild/linux-riscv64": "0.23.1",
+ "@esbuild/linux-s390x": "0.23.1",
+ "@esbuild/linux-x64": "0.23.1",
+ "@esbuild/netbsd-x64": "0.23.1",
+ "@esbuild/openbsd-arm64": "0.23.1",
+ "@esbuild/openbsd-x64": "0.23.1",
+ "@esbuild/sunos-x64": "0.23.1",
+ "@esbuild/win32-arm64": "0.23.1",
+ "@esbuild/win32-ia32": "0.23.1",
+ "@esbuild/win32-x64": "0.23.1"
+ }
+ },
"upload-api/node_modules/nanoid": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz",
diff --git a/package.json b/package.json
index 9bfd7056..17b25523 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,7 @@
"name": "w3infra",
"version": "0.0.0",
"private": true,
+ "packageManager": "npm@10.8.2+sha256.c8c61ba0fa0ab3b5120efd5ba97fdaf0e0b495eef647a97c4413919eda0a878b",
"type": "module",
"scripts": {
"start": "sst start",
@@ -58,7 +59,7 @@
"multiformats": "^13.1.0",
"p-retry": "^6.2.0",
"p-wait-for": "^5.0.0",
- "typescript": "^4.9.3"
+ "typescript": "^5.5.4"
},
"dependencies": {
"@ipld/dag-json": "^10.1.5",
@@ -109,6 +110,15 @@
"no-loop-func": "off",
"no-warning-comments": "off",
"jsdoc/check-indentation": "off",
+ "jsdoc/check-tag-names": [
+ "error",
+ {
+ "definedTags": [
+ "import",
+ "overload"
+ ]
+ }
+ ],
"jsdoc/require-hyphen-before-param-description": "off",
"react-hooks/rules-of-hooks": "off",
"react/no-danger": "off"
diff --git a/stacks/config.js b/stacks/config.js
index 3bad2884..5c64f3ad 100644
--- a/stacks/config.js
+++ b/stacks/config.js
@@ -1,3 +1,4 @@
+/** @import { ApiDomainProps, App, Stack } from 'sst/constructs' */
import { Duration, RemovalPolicy } from 'aws-cdk-lib'
import { createRequire } from 'module'
import { StartingPosition } from 'aws-cdk-lib/aws-lambda'
@@ -55,10 +56,33 @@ export function getBucketConfig(name, stage, version = 0){
/**
* Return the custom domain config for http api
- *
+ *
+ * @overload
+ * @param {string} stage
+ * @param {string} hostedZone
+ * @returns {ApiDomainProps}
+ */
+/**
+ * Return `undefined`, because {@link hostedZone} is not present.
+ *
+ * @overload
+ * @param {string} stage
+ * @param {undefined} hostedZone
+ * @returns {undefined}
+ */
+/**
+ * Return the custom domain config for http api, or `undefined` if
+ * {@link hostedZone} is not present.
+ *
+ * @overload
+ * @param {string} stage
+ * @param {string | undefined} hostedZone
+ * @returns {ApiDomainProps | undefined}
+ */
+/**
* @param {string} stage
* @param {string | undefined} hostedZone
- * @returns {{domainName: string, hostedZone: string} | undefined}
+ * @returns {ApiDomainProps | undefined}
*/
export function getCustomDomain (stage, hostedZone) {
// return no custom domain config if hostedZone not set
@@ -74,7 +98,7 @@ export function getCustomDomain (stage, hostedZone) {
/**
- * @param {import('sst/constructs').Stack} stack
+ * @param {Stack} stack
*/
export function getEventSourceConfig (stack) {
if (stack.stage !== 'prod') {
@@ -103,7 +127,7 @@ export function getEventSourceConfig (stack) {
}
/**
- * @param {import('sst/constructs').Stack} stack
+ * @param {Stack} stack
*/
export function getKinesisStreamConfig (stack) {
if (stack.stage !== 'prod' && stack.stage !== 'staging') {
@@ -133,8 +157,8 @@ export function getGitInfo () {
}
/**
- * @param {import('sst/constructs').App} app
- * @param {import('sst/constructs').Stack} stack
+ * @param {App} app
+ * @param {Stack} stack
*/
export function setupSentry (app, stack) {
// Skip when locally
@@ -150,8 +174,8 @@ export function setupSentry (app, stack) {
}
/**
- * @param {import('sst/constructs').Stack} stack
- * @param {{domainName: string, hostedZone: string} | undefined} customDomain
+ * @param {Stack} stack
+ * @param {ApiDomainProps | undefined} customDomain
*/
export function getServiceURL (stack, customDomain) {
// in production we use the top level subdomain
diff --git a/stacks/filecoin-stack.js b/stacks/filecoin-stack.js
index 0b2c9e4e..c65eb838 100644
--- a/stacks/filecoin-stack.js
+++ b/stacks/filecoin-stack.js
@@ -31,7 +31,7 @@ export function FilecoinStack({ stack, app }) {
STOREFRONT_PROOF,
START_FILECOIN_METRICS_EPOCH_MS
} = getEnv()
- const storefrontCustomDomain = getCustomDomain(stack.stage, process.env.HOSTED_ZONE)
+ const storefrontCustomDomain = getCustomDomain(stack.stage, process.env.HOSTED_ZONES?.split(",")[0])
// Setup app monitoring with Sentry
setupSentry(app, stack)
diff --git a/stacks/upload-api-stack.js b/stacks/upload-api-stack.js
index ab03d7ad..3e0a98a4 100644
--- a/stacks/upload-api-stack.js
+++ b/stacks/upload-api-stack.js
@@ -20,6 +20,17 @@ import { getCustomDomain, getApiPackageJson, getGitInfo, setupSentry, getEnv, ge
* @param {import('sst/constructs').StackContext} properties
*/
export function UploadApiStack({ stack, app }) {
+ // For loading the Storacha logo
+ stack.setDefaultFunctionProps({
+ nodejs: {
+ esbuild: {
+ loader: {
+ '.svg': 'text',
+ }
+ }
+ }
+ });
+
const {
AGGREGATOR_DID,
CONTENT_CLAIMS_DID,
@@ -39,119 +50,126 @@ export function UploadApiStack({ stack, app }) {
const { blockAdvertPublisherQueue, blockIndexWriterQueue } = use(IndexerStack)
// Setup API
- const customDomain = getCustomDomain(stack.stage, process.env.HOSTED_ZONE)
+ const customDomains = process.env.HOSTED_ZONES?.split(',').map(zone => getCustomDomain(stack.stage, zone))
const pkg = getApiPackageJson()
const git = getGitInfo()
const ucanInvocationPostbasicAuth = new Config.Secret(stack, 'UCAN_INVOCATION_POST_BASIC_AUTH')
- const api = new Api(stack, 'http-gateway', {
- customDomain,
- defaults: {
- function: {
- timeout: '60 seconds',
- permissions: [
- allocationTable,
- storeTable,
- uploadTable,
- customerTable,
- delegationTable,
- revocationTable,
- delegationBucket,
- consumerTable,
- subscriptionTable,
- rateLimitTable,
- adminMetricsTable,
- spaceMetricsTable,
- pieceTable,
- spaceDiffTable,
- spaceSnapshotTable,
- carparkBucket,
- invocationBucket,
- taskBucket,
- workflowBucket,
- ucanStream,
- pieceOfferQueue,
- filecoinSubmitQueue,
- blockAdvertPublisherQueue,
- blockIndexWriterQueue,
- ],
- environment: {
- DID: process.env.UPLOAD_API_DID ?? '',
- AGGREGATOR_DID,
- ALLOCATION_TABLE_NAME: allocationTable.tableName,
- STORE_TABLE_NAME: storeTable.tableName,
- STORE_BUCKET_NAME: carparkBucket.bucketName,
- UPLOAD_TABLE_NAME: uploadTable.tableName,
- CONSUMER_TABLE_NAME: consumerTable.tableName,
- CUSTOMER_TABLE_NAME: customerTable.tableName,
- SUBSCRIPTION_TABLE_NAME: subscriptionTable.tableName,
- SPACE_METRICS_TABLE_NAME: spaceMetricsTable.tableName,
- RATE_LIMIT_TABLE_NAME: rateLimitTable.tableName,
- DELEGATION_TABLE_NAME: delegationTable.tableName,
- REVOCATION_TABLE_NAME: revocationTable.tableName,
- SPACE_DIFF_TABLE_NAME: spaceDiffTable.tableName,
- SPACE_SNAPSHOT_TABLE_NAME: spaceSnapshotTable.tableName,
- DELEGATION_BUCKET_NAME: delegationBucket.bucketName,
- INVOCATION_BUCKET_NAME: invocationBucket.bucketName,
- TASK_BUCKET_NAME: taskBucket.bucketName,
- WORKFLOW_BUCKET_NAME: workflowBucket.bucketName,
- UCAN_LOG_STREAM_NAME: ucanStream.streamName,
- ADMIN_METRICS_TABLE_NAME: adminMetricsTable.tableName,
- PIECE_TABLE_NAME: pieceTable.tableName,
- PIECE_OFFER_QUEUE_URL: pieceOfferQueue.queueUrl,
- FILECOIN_SUBMIT_QUEUE_URL: filecoinSubmitQueue.queueUrl,
- BLOCK_ADVERT_PUBLISHER_QUEUE_URL: blockAdvertPublisherQueue.queueUrl,
- BLOCK_INDEX_WRITER_QUEUE_URL: blockIndexWriterQueue.queueUrl,
- NAME: pkg.name,
- VERSION: pkg.version,
- COMMIT: git.commmit,
- STAGE: stack.stage,
- ACCESS_SERVICE_URL: getServiceURL(stack, customDomain) ?? '',
- UPLOAD_SERVICE_URL: getServiceURL(stack, customDomain) ?? '',
- POSTMARK_TOKEN: process.env.POSTMARK_TOKEN ?? '',
- PROVIDERS: process.env.PROVIDERS ?? '',
- R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID ?? '',
- R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY ?? '',
- R2_REGION: process.env.R2_REGION ?? '',
- R2_CARPARK_BUCKET_NAME: process.env.R2_CARPARK_BUCKET_NAME ?? '',
- R2_DELEGATION_BUCKET_NAME: process.env.R2_DELEGATION_BUCKET_NAME ?? '',
- R2_ENDPOINT: process.env.R2_ENDPOINT ?? '',
- REQUIRE_PAYMENT_PLAN: process.env.REQUIRE_PAYMENT_PLAN ?? '',
- UPLOAD_API_DID: process.env.UPLOAD_API_DID ?? '',
- STRIPE_PRICING_TABLE_ID: process.env.STRIPE_PRICING_TABLE_ID ?? '',
- STRIPE_PUBLISHABLE_KEY: process.env.STRIPE_PUBLISHABLE_KEY ?? '',
- DEAL_TRACKER_DID: process.env.DEAL_TRACKER_DID ?? '',
- DEAL_TRACKER_URL: process.env.DEAL_TRACKER_URL ?? '',
- CONTENT_CLAIMS_DID,
- CONTENT_CLAIMS_URL,
- CONTENT_CLAIMS_PROOF
- },
- bind: [
- privateKey,
- ucanInvocationPostbasicAuth,
- stripeSecretKey,
- contentClaimsPrivateKey
- ]
+ const apis = (customDomains ?? [undefined]).map((customDomain) => {
+ const hostedZone = customDomain?.hostedZone
+ const apiId = [`http-gateway`, hostedZone?.replaceAll('.', '_')]
+ .filter(Boolean)
+ .join('-')
+ return new Api(stack, apiId, {
+ customDomain,
+ defaults: {
+ function: {
+ timeout: '60 seconds',
+ permissions: [
+ allocationTable,
+ storeTable,
+ uploadTable,
+ customerTable,
+ delegationTable,
+ revocationTable,
+ delegationBucket,
+ consumerTable,
+ subscriptionTable,
+ rateLimitTable,
+ adminMetricsTable,
+ spaceMetricsTable,
+ pieceTable,
+ spaceDiffTable,
+ spaceSnapshotTable,
+ carparkBucket,
+ invocationBucket,
+ taskBucket,
+ workflowBucket,
+ ucanStream,
+ pieceOfferQueue,
+ filecoinSubmitQueue,
+ blockAdvertPublisherQueue,
+ blockIndexWriterQueue,
+ ],
+ environment: {
+ DID: process.env.UPLOAD_API_DID ?? '',
+ AGGREGATOR_DID,
+ ALLOCATION_TABLE_NAME: allocationTable.tableName,
+ STORE_TABLE_NAME: storeTable.tableName,
+ STORE_BUCKET_NAME: carparkBucket.bucketName,
+ UPLOAD_TABLE_NAME: uploadTable.tableName,
+ CONSUMER_TABLE_NAME: consumerTable.tableName,
+ CUSTOMER_TABLE_NAME: customerTable.tableName,
+ SUBSCRIPTION_TABLE_NAME: subscriptionTable.tableName,
+ SPACE_METRICS_TABLE_NAME: spaceMetricsTable.tableName,
+ RATE_LIMIT_TABLE_NAME: rateLimitTable.tableName,
+ DELEGATION_TABLE_NAME: delegationTable.tableName,
+ REVOCATION_TABLE_NAME: revocationTable.tableName,
+ SPACE_DIFF_TABLE_NAME: spaceDiffTable.tableName,
+ SPACE_SNAPSHOT_TABLE_NAME: spaceSnapshotTable.tableName,
+ DELEGATION_BUCKET_NAME: delegationBucket.bucketName,
+ INVOCATION_BUCKET_NAME: invocationBucket.bucketName,
+ TASK_BUCKET_NAME: taskBucket.bucketName,
+ WORKFLOW_BUCKET_NAME: workflowBucket.bucketName,
+ UCAN_LOG_STREAM_NAME: ucanStream.streamName,
+ ADMIN_METRICS_TABLE_NAME: adminMetricsTable.tableName,
+ PIECE_TABLE_NAME: pieceTable.tableName,
+ PIECE_OFFER_QUEUE_URL: pieceOfferQueue.queueUrl,
+ FILECOIN_SUBMIT_QUEUE_URL: filecoinSubmitQueue.queueUrl,
+ BLOCK_ADVERT_PUBLISHER_QUEUE_URL: blockAdvertPublisherQueue.queueUrl,
+ BLOCK_INDEX_WRITER_QUEUE_URL: blockIndexWriterQueue.queueUrl,
+ NAME: pkg.name,
+ VERSION: pkg.version,
+ COMMIT: git.commmit,
+ STAGE: stack.stage,
+ ACCESS_SERVICE_URL: getServiceURL(stack, customDomain) ?? '',
+ UPLOAD_SERVICE_URL: getServiceURL(stack, customDomain) ?? '',
+ POSTMARK_TOKEN: process.env.POSTMARK_TOKEN ?? '',
+ PROVIDERS: process.env.PROVIDERS ?? '',
+ R2_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID ?? '',
+ R2_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY ?? '',
+ R2_REGION: process.env.R2_REGION ?? '',
+ R2_CARPARK_BUCKET_NAME: process.env.R2_CARPARK_BUCKET_NAME ?? '',
+ R2_DELEGATION_BUCKET_NAME: process.env.R2_DELEGATION_BUCKET_NAME ?? '',
+ R2_ENDPOINT: process.env.R2_ENDPOINT ?? '',
+ REQUIRE_PAYMENT_PLAN: process.env.REQUIRE_PAYMENT_PLAN ?? '',
+ UPLOAD_API_DID: process.env.UPLOAD_API_DID ?? '',
+ STRIPE_PRICING_TABLE_ID: process.env.STRIPE_PRICING_TABLE_ID ?? '',
+ STRIPE_PUBLISHABLE_KEY: process.env.STRIPE_PUBLISHABLE_KEY ?? '',
+ DEAL_TRACKER_DID: process.env.DEAL_TRACKER_DID ?? '',
+ DEAL_TRACKER_URL: process.env.DEAL_TRACKER_URL ?? '',
+ CONTENT_CLAIMS_DID,
+ CONTENT_CLAIMS_URL,
+ CONTENT_CLAIMS_PROOF,
+ HOSTED_ZONE: hostedZone ?? '',
+ },
+ bind: [
+ privateKey,
+ ucanInvocationPostbasicAuth,
+ stripeSecretKey,
+ contentClaimsPrivateKey
+ ]
+ }
+ },
+ routes: {
+ 'POST /': 'upload-api/functions/ucan-invocation-router.handler',
+ 'POST /ucan': 'upload-api/functions/ucan.handler',
+ 'POST /bridge': 'upload-api/functions/bridge.handler',
+ 'GET /': 'upload-api/functions/get.home',
+ 'GET /validate-email': 'upload-api/functions/validate-email.preValidateEmail',
+ 'POST /validate-email': 'upload-api/functions/validate-email.validateEmail',
+ 'GET /error': 'upload-api/functions/get.error',
+ 'GET /version': 'upload-api/functions/get.version',
+ 'GET /metrics': 'upload-api/functions/metrics.handler',
+ 'GET /receipt/{taskCid}': 'upload-api/functions/receipt.handler',
+ 'GET /storefront-cron': 'upload-api/functions/storefront-cron.handler',
+ // AWS API Gateway does not know trailing slash... and Grafana Agent puts trailing slash
+ 'GET /metrics/{proxy+}': 'upload-api/functions/metrics.handler',
+ },
+ accessLog: {
+ format:'{"requestTime":"$context.requestTime","requestId":"$context.requestId","httpMethod":"$context.httpMethod","path":"$context.path","routeKey":"$context.routeKey","status":$context.status,"responseLatency":$context.responseLatency,"integrationRequestId":"$context.integration.requestId","integrationStatus":"$context.integration.status","integrationLatency":"$context.integration.latency","integrationServiceStatus":"$context.integration.integrationStatus","ip":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent"}'
}
- },
- routes: {
- 'POST /': 'upload-api/functions/ucan-invocation-router.handler',
- 'POST /ucan': 'upload-api/functions/ucan.handler',
- 'POST /bridge': 'upload-api/functions/bridge.handler',
- 'GET /': 'upload-api/functions/get.home',
- 'GET /validate-email': 'upload-api/functions/validate-email.preValidateEmail',
- 'POST /validate-email': 'upload-api/functions/validate-email.validateEmail',
- 'GET /error': 'upload-api/functions/get.error',
- 'GET /version': 'upload-api/functions/get.version',
- 'GET /metrics': 'upload-api/functions/metrics.handler',
- 'GET /receipt/{taskCid}': 'upload-api/functions/receipt.handler',
- 'GET /storefront-cron': 'upload-api/functions/storefront-cron.handler',
- // AWS API Gateway does not know trailing slash... and Grafana Agent puts trailing slash
- 'GET /metrics/{proxy+}': 'upload-api/functions/metrics.handler',
- },
- accessLog: {
- format:'{"requestTime":"$context.requestTime","requestId":"$context.requestId","httpMethod":"$context.httpMethod","path":"$context.path","routeKey":"$context.routeKey","status":$context.status,"responseLatency":$context.responseLatency,"integrationRequestId":"$context.integration.requestId","integrationStatus":"$context.integration.status","integrationLatency":"$context.integration.latency","integrationServiceStatus":"$context.integration.integrationStatus","ip":"$context.identity.sourceIp","userAgent":"$context.identity.userAgent"}'
- }
+ });
})
// UCAN stream metrics for admin and space
@@ -219,7 +237,9 @@ export function UploadApiStack({ stack, app }) {
})
stack.addOutputs({
- ApiEndpoint: api.url,
- CustomDomain: customDomain ? `https://${customDomain.domainName}` : 'Set HOSTED_ZONE in env to deploy to a custom domain'
+ ApiEndpoints: JSON.stringify(apis.map(api => api.url)),
+ CustomDomains: customDomains
+ ? JSON.stringify(customDomains.map(customDomain => `https://${customDomain.domainName}`))
+ : 'Set HOSTED_ZONES in env to deploy to a custom domain',
})
}
diff --git a/test/helpers/deployment.js b/test/helpers/deployment.js
index fc8dce4c..0e5095d4 100644
--- a/test/helpers/deployment.js
+++ b/test/helpers/deployment.js
@@ -49,7 +49,7 @@ export const getApiEndpoint = () => {
// Get Upload API endpoint
const id = 'UploadApiStack'
- return testEnv[`${getStackName()}-${id}`].ApiEndpoint
+ return JSON.parse(testEnv[`${getStackName()}-${id}`].ApiEndpoints)[0]
}
export const getRoundaboutEndpoint = () => {
@@ -71,21 +71,7 @@ export const getRoundaboutEndpoint = () => {
}
export const getReceiptsEndpoint = () => {
- // CI/CD deployment
- if (process.env.SEED_APP_NAME) {
- const stage = getStage()
- return `https://${stage}.up.web3.storage/receipt/`
- }
-
- const require = createRequire(import.meta.url)
- const testEnv = require(path.join(
- process.cwd(),
- '.sst/outputs.json'
- ))
-
- // Get Upload API endpoint
- const id = 'UploadApiStack'
- return `${testEnv[`${getStackName()}-${id}`].ApiEndpoint}/receipt/`
+ return `${getApiEndpoint()}/receipt/`
}
export const getCarparkBucketInfo = () => {
diff --git a/tsconfig.json b/tsconfig.json
index 05cd9117..fe4a6e27 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -33,8 +33,19 @@
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"stripInternal": true,
- "resolveJsonModule": true,
+ "resolveJsonModule": true
},
- "include": [ "billing", "indexer", "upload-api", "stacks", "carpark", "replicator", "ucan-invocation", "filecoin", "tools", "lib" ],
- "exclude": [ "billing/coverage" ]
-}
\ No newline at end of file
+ "include": [
+ "billing",
+ "indexer",
+ "upload-api",
+ "stacks",
+ "carpark",
+ "replicator",
+ "ucan-invocation",
+ "filecoin",
+ "tools",
+ "lib"
+ ],
+ "exclude": ["billing/coverage", "upload-api/dist"]
+}
diff --git a/upload-api/email.js b/upload-api/email.js
index dd34d6f7..785251fd 100644
--- a/upload-api/email.js
+++ b/upload-api/email.js
@@ -12,16 +12,21 @@ export const configure = (opts) => new Email(opts)
export class Email {
/**
* @param {object} opts
- * @param {string} opts.token
- * @param {string} [opts.sender]
+ * @param {string} opts.token - Postmark server token
+ * @param {string} [opts.sender] - Sender email mailbox (`Display Name
+ * `)
+ * @param {string} [opts.environment] - Environment/stage name to display in
+ * the email subject. Omit to show none
+ * (ie, production).
*/
constructor(opts) {
- this.sender = opts.sender || 'web3.storage '
+ this.sender = opts.sender
this.headers = {
Accept: 'text/json',
'Content-Type': 'text/json',
'X-Postmark-Server-Token': opts.token,
}
+ this.environment = opts.environment
}
/**
@@ -29,20 +34,35 @@ export class Email {
*
* @param {ValidationEmailSend} opts
*/
- async sendValidation (opts) {
+ async sendValidation(opts) {
+ const { hostname } = new URL(opts.url)
+ const emailParams = hostname.endsWith('web3.storage')
+ ? {
+ From: this.sender || 'web3.storage ',
+ TemplateAlias: 'welcome',
+ TemplateModel: {
+ product_url: 'https://web3.storage',
+ product_name: 'Web3 Storage',
+ email: opts.to,
+ action_url: opts.url,
+ },
+ }
+ : {
+ From: this.sender || 'Storacha ',
+ TemplateAlias: 'welcome-storacha',
+ TemplateModel: {
+ email: opts.to,
+ action_url: opts.url,
+ environment_name: this.environment,
+ },
+ }
+
const rsp = await fetch('https://api.postmarkapp.com/email/withTemplate', {
method: 'POST',
headers: this.headers,
body: JSON.stringify({
- From: this.sender,
To: opts.to,
- TemplateAlias: 'welcome',
- TemplateModel: {
- product_url: 'https://web3.storage',
- product_name: 'Web3 Storage',
- email: opts.to,
- action_url: opts.url,
- },
+ ...emailParams,
}),
})
diff --git a/upload-api/functions/ucan-invocation-router.js b/upload-api/functions/ucan-invocation-router.js
index ec3651f5..16a3a6f6 100644
--- a/upload-api/functions/ucan-invocation-router.js
+++ b/upload-api/functions/ucan-invocation-router.js
@@ -130,6 +130,7 @@ export async function ucanInvocationRouter(request) {
carparkBucketSecretAccessKey,
blockAdvertisementPublisherQueueConfig,
blockIndexWriterQueueConfig,
+ sstStage
} = getLambdaEnv()
if (request.body === undefined) {
@@ -265,7 +266,7 @@ export async function ucanInvocationRouter(request) {
signer: serviceSigner,
// TODO: we should set URL from a different env var, doing this for now to avoid that refactor - tracking in https://github.com/web3-storage/w3infra/issues/209
url: new URL(accessServiceURL),
- email: new Email({ token: postmarkToken }),
+ email: new Email({ token: postmarkToken, environment: sstStage === 'prod' ? undefined : sstStage, }),
agentStore,
provisionsStorage,
subscriptionsStorage,
@@ -375,6 +376,7 @@ function getLambdaEnv () {
url: new URL(mustGetEnv('BLOCK_INDEX_WRITER_QUEUE_URL')),
region: AWS_REGION
},
+ sstStage: mustGetEnv('SST_STAGE'),
// set for testing
dbEndpoint: process.env.DYNAMO_DB_ENDPOINT,
}
diff --git a/upload-api/functions/validate-email.jsx b/upload-api/functions/validate-email.jsx
index bc9866dd..2a4066d1 100644
--- a/upload-api/functions/validate-email.jsx
+++ b/upload-api/functions/validate-email.jsx
@@ -13,16 +13,14 @@ import { createConsumerTable } from '../tables/consumer.js'
import { createRevocationsTable } from '../stores/revocations.js'
import * as AgentStore from '../stores/agent.js'
import { useProvisionStore } from '../stores/provisions.js'
-import {
- HtmlResponse,
- ValidateEmail,
- ValidateEmailError,
- PendingValidateEmail,
-} from '../html.jsx'
+import * as htmlStoracha from '../html-storacha'
+import * as htmlW3s from '../html-w3s'
import { createRateLimitTable } from '../tables/rate-limit.js'
import { createSpaceMetricsTable } from '../tables/space-metrics.js'
import { createCustomerStore } from '@web3-storage/w3infra-billing/tables/customer'
+const html = process.env.HOSTED_ZONE === 'up.web3.storage' ? htmlW3s : htmlStoracha
+
Sentry.AWSLambda.init({
environment: process.env.SST_STAGE,
dsn: process.env.SENTRY_DSN,
@@ -30,9 +28,9 @@ Sentry.AWSLambda.init({
})
/**
- * @param {HtmlResponse} response
+ * @param {htmlStoracha.HtmlResponse} response
*/
-export function toLambdaResponse (response) {
+export function toLambdaResponse(response) {
const { status = 200, headers: responseHeaders, body } = response
// translate headers from Response format to Lambda format
/** @type {Record} */
@@ -44,7 +42,7 @@ export function toLambdaResponse (response) {
statusCode: status,
headers,
body: body && Buffer.from(response.getStringBody()).toString('base64'),
- isBase64Encoded: true
+ isBase64Encoded: true,
}
}
@@ -53,19 +51,24 @@ export function toLambdaResponse (response) {
*
* @param {import('aws-lambda').APIGatewayProxyEventV2} request
*/
-export async function validateEmailGet (request) {
+export async function validateEmailGet(request) {
+ request.requestContext.domainName
if (!request.queryStringParameters?.ucan) {
- return toLambdaResponse(new HtmlResponse(
-
- ))
+ return toLambdaResponse(
+ new html.HtmlResponse(
+
+ )
+ )
}
- return toLambdaResponse(new HtmlResponse())
+ return toLambdaResponse(
+ new html.HtmlResponse()
+ )
}
export const preValidateEmail = Sentry.AWSLambda.wrapHandler(validateEmailGet)
-function createAuthorizeContext () {
+function createAuthorizeContext() {
const {
ACCESS_SERVICE_URL = '',
AWS_REGION = '',
@@ -88,6 +91,7 @@ function createAuthorizeContext () {
STRIPE_PRICING_TABLE_ID = '',
STRIPE_PUBLISHABLE_KEY = '',
UCAN_LOG_STREAM_NAME = '',
+ SST_STAGE = '',
// set for testing
DYNAMO_DB_ENDPOINT: dbEndpoint,
} = process.env
@@ -97,21 +101,36 @@ function createAuthorizeContext () {
INVOCATION_BUCKET_NAME
)
const workflowBucket = createWorkflowStore(AWS_REGION, WORKFLOW_BUCKET_NAME)
- const delegationBucket = createDelegationsStore(R2_ENDPOINT, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_DELEGATION_BUCKET_NAME)
- const subscriptionTable = createSubscriptionTable(AWS_REGION, SUBSCRIPTION_TABLE_NAME, {
- endpoint: dbEndpoint
- });
+ const delegationBucket = createDelegationsStore(
+ R2_ENDPOINT,
+ R2_ACCESS_KEY_ID,
+ R2_SECRET_ACCESS_KEY,
+ R2_DELEGATION_BUCKET_NAME
+ )
+ const subscriptionTable = createSubscriptionTable(
+ AWS_REGION,
+ SUBSCRIPTION_TABLE_NAME,
+ {
+ endpoint: dbEndpoint,
+ }
+ )
const consumerTable = createConsumerTable(AWS_REGION, CONSUMER_TABLE_NAME, {
- endpoint: dbEndpoint
- });
- const customerStore = createCustomerStore({ region: AWS_REGION }, { tableName: CUSTOMER_TABLE_NAME })
- const spaceMetricsTable = createSpaceMetricsTable(AWS_REGION, SPACE_METRICS_TABLE_NAME)
+ endpoint: dbEndpoint,
+ })
+ const customerStore = createCustomerStore(
+ { region: AWS_REGION },
+ { tableName: CUSTOMER_TABLE_NAME }
+ )
+ const spaceMetricsTable = createSpaceMetricsTable(
+ AWS_REGION,
+ SPACE_METRICS_TABLE_NAME
+ )
const agentStore = AgentStore.open({
store: {
connection: {
address: {
- region: AWS_REGION
+ region: AWS_REGION,
},
},
region: AWS_REGION,
@@ -129,11 +148,26 @@ function createAuthorizeContext () {
return {
// TODO: we should set URL from a different env var, doing this for now to avoid that refactor
url: new URL(ACCESS_SERVICE_URL),
- email: new Email({ token: POSTMARK_TOKEN }),
+ email: new Email({
+ token: POSTMARK_TOKEN,
+ environment: SST_STAGE === 'prod' ? undefined : SST_STAGE,
+ }),
signer: getServiceSigner({ did: UPLOAD_API_DID, privateKey: PRIVATE_KEY }),
- delegationsStorage: createDelegationsTable(AWS_REGION, DELEGATION_TABLE_NAME, { bucket: delegationBucket, invocationBucket, workflowBucket }),
- revocationsStorage: createRevocationsTable(AWS_REGION, REVOCATION_TABLE_NAME),
- provisionsStorage: useProvisionStore(subscriptionTable, consumerTable, spaceMetricsTable, parseServiceDids(PROVIDERS)),
+ delegationsStorage: createDelegationsTable(
+ AWS_REGION,
+ DELEGATION_TABLE_NAME,
+ { bucket: delegationBucket, invocationBucket, workflowBucket }
+ ),
+ revocationsStorage: createRevocationsTable(
+ AWS_REGION,
+ REVOCATION_TABLE_NAME
+ ),
+ provisionsStorage: useProvisionStore(
+ subscriptionTable,
+ consumerTable,
+ spaceMetricsTable,
+ parseServiceDids(PROVIDERS)
+ ),
rateLimitsStorage: createRateLimitTable(AWS_REGION, RATE_LIMIT_TABLE_NAME),
customerStore,
agentStore,
@@ -147,42 +181,56 @@ function createAuthorizeContext () {
*
* @param {import('aws-lambda').APIGatewayProxyEventV2} request
*/
-export async function validateEmailPost (request) {
+export async function validateEmailPost(request) {
const encodedUcan = request.queryStringParameters?.ucan
if (!encodedUcan) {
- return toLambdaResponse(new HtmlResponse(
-
- ))
+ return toLambdaResponse(
+ new html.HtmlResponse(
+
+ )
+ )
}
const context = createAuthorizeContext()
const authorizeResult = await authorize(encodedUcan, context)
if (authorizeResult.error) {
console.error(authorizeResult.error)
- return toLambdaResponse(new HtmlResponse(
- ,
- { status: 500 }
- ))
+ return toLambdaResponse(
+ new html.HtmlResponse(
+ (
+
+ ),
+ { status: 500 }
+ )
+ )
}
const { email, audience, ucan } = authorizeResult.ok
- const planCheckResult = await context.customerStore.get({ customer: DidMailto.fromEmail(email) })
+ const planCheckResult = await context.customerStore.get({
+ customer: DidMailto.fromEmail(email),
+ })
let stripePricingTableId
let stripePublishableKey
- if (!planCheckResult.ok?.product){
+ if (!planCheckResult.ok?.product) {
stripePricingTableId = context.stripePricingTableId
stripePublishableKey = context.stripePublishableKey
}
- return toLambdaResponse(new HtmlResponse(
-
- ))
+ return toLambdaResponse(
+ new html.HtmlResponse(
+ (
+
+ )
+ )
+ )
}
export const validateEmail = Sentry.AWSLambda.wrapHandler(validateEmailPost)
diff --git a/upload-api/html-storacha/index.jsx b/upload-api/html-storacha/index.jsx
new file mode 100644
index 00000000..dd72ae1c
--- /dev/null
+++ b/upload-api/html-storacha/index.jsx
@@ -0,0 +1,273 @@
+// @jsxImportSource preact
+import { render } from 'preact-render-to-string'
+import { Response } from '@web-std/fetch'
+import storachaLogoSvg from './storacha-logo.svg'
+
+/**
+ * Dev changes quickly without deploying to AWS!
+ * Use test/helpers/validate-email-server.js
+ */
+
+/**
+ * Build HTML document
+ *
+ * @param {string} body
+ */
+export function buildDocument(body) {
+ return /* html */ `
+
+
+
+
+ Storacha Email Validation
+
+
+
+
+
+
+
+
+ ${body}
+
+`
+}
+
+export class HtmlResponse extends Response {
+ /**
+ *
+ * @param {import('preact').VNode<{}>} body
+ * @param {ResponseInit} [init]
+ */
+ constructor(body, init = {}) {
+ const headers = {
+ headers: {
+ 'content-type': 'text/html; charset=utf-8',
+ },
+ }
+ const stringBody = buildDocument(render(body))
+ super(stringBody, { ...init, ...headers })
+ this.stringBody = stringBody
+ }
+
+ getStringBody() {
+ return this.stringBody
+ }
+
+ /**
+ * @param {import('preact').VNode<{}>} body
+ * @param {ResponseInit} [init]
+ */
+ static respond(body, init = {}) {
+ return new HtmlResponse(body, init)
+ }
+}
+
+/**
+ *
+ * @param {object} props
+ * @param {boolean} [props.autoApprove]
+ */
+export const PendingValidateEmail = ({ autoApprove }) => (
+ <>
+
+
+
+
Validating Email
+
+ {autoApprove ? (
+
+ ) : undefined}
+
+
+ >
+)
+
+/**
+ *
+ * @param {object} props
+ * @param {string} props.ucan
+ * @param {string} props.email
+ * @param {string} props.audience
+ * @param {string} [props.stripePricingTableId]
+ * @param {string} [props.stripePublishableKey]
+ */
+export const ValidateEmail = ({
+ ucan,
+ email,
+ audience,
+ stripePricingTableId,
+ stripePublishableKey,
+}) => {
+ const showPricingTable = stripePricingTableId && stripePublishableKey
+ return (
+ <>
+
+
+
+
Email Validated
+
+ {email}
was confirmed.{' '}
+ {showPricingTable ? '' : 'You may close this window.'}
+
+
+ {showPricingTable && (
+
+
+ In order to upload data you need to sign up for a billing plan:
+
+
+
+ )}
+
+
+ By registering with Storacha you agree to the Storacha{' '}
+ Terms of Service.
+
+
+
+ {' '}
+ Auth details
+ Validation requested by
+
+ {audience}
+
+ UCAN
+
+ {ucan}
+
+
+
+ >
+ )
+}
+
+/**
+ *
+ * @param {object} props
+ * @param {string} props.msg
+ */
+export const ValidateEmailError = ({ msg }) => (
+ <>
+
+
+
+
Email Validation Failed
+
{msg} You may close this window and try again.
+
+
+ >
+)
diff --git a/upload-api/html-storacha/preact-jsx.d.ts b/upload-api/html-storacha/preact-jsx.d.ts
new file mode 100644
index 00000000..1a31564e
--- /dev/null
+++ b/upload-api/html-storacha/preact-jsx.d.ts
@@ -0,0 +1,25 @@
+declare global {
+ declare module 'preact' {
+ namespace JSX {
+ interface IntrinsicElements {
+ /**
+ * Stripe embedded pricing table
+ * @see https://docs.stripe.com/no-code/pricing-table
+ */
+ 'stripe-pricing-table': {
+ 'pricing-table-id': string
+ 'publishable-key': string
+
+ /** @see https://docs.stripe.com/no-code/pricing-table#handle-fulfillment-with-the-stripe-api */
+ 'client-reference-id'?: string
+
+ /** @see https://docs.stripe.com/no-code/pricing-table#customer-session */
+ 'customer-session-client-secret'?: string
+
+ /** @see https://docs.stripe.com/no-code/pricing-table#customer-email */
+ 'customer-email'?: string
+ }
+ }
+ }
+ }
+}
diff --git a/upload-api/html-storacha/storacha-logo.svg b/upload-api/html-storacha/storacha-logo.svg
new file mode 100644
index 00000000..0b8b883a
--- /dev/null
+++ b/upload-api/html-storacha/storacha-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/upload-api/html-storacha/svg.d.ts b/upload-api/html-storacha/svg.d.ts
new file mode 100644
index 00000000..f2d988c2
--- /dev/null
+++ b/upload-api/html-storacha/svg.d.ts
@@ -0,0 +1,5 @@
+// We load SVGs as strings
+declare module '*.svg' {
+ const svgString: string
+ export default svgString
+}
diff --git a/upload-api/html.jsx b/upload-api/html-w3s/index.jsx
similarity index 98%
rename from upload-api/html.jsx
rename to upload-api/html-w3s/index.jsx
index 4a9a71fe..cb21ac0a 100644
--- a/upload-api/html.jsx
+++ b/upload-api/html-w3s/index.jsx
@@ -1,8 +1,5 @@
-/* eslint-disable jsdoc/check-tag-names */
-/** @jsx preact.h */
-/** @jsxFrag preact.Fragment */
+// @jsxImportSource preact
import { render } from 'preact-render-to-string'
-import * as preact from 'preact'
import { Response } from '@web-std/fetch'
/**
diff --git a/upload-api/package.json b/upload-api/package.json
index 41c2ffb9..d9c122ec 100644
--- a/upload-api/package.json
+++ b/upload-api/package.json
@@ -4,7 +4,8 @@
"type": "module",
"scripts": {
"test": "ava --node-arguments='--experimental-fetch' --verbose --timeout=60s --no-worker-threads --serial --fail-fast '**/*.test.js'",
- "build:html": "babel --presets @babel/preset-react ./html.jsx --out-dir ./dist"
+ "build:html": "esbuild test/helpers/validate-email-server.js --bundle --platform=node --external:./../node_modules/* --loader:.svg=text --format=esm --outfile=./dist/validate-email-server.mjs",
+ "dev:html": "concurrently --names=Build,Server --prefix-colors=bgBlue.bold,bgMagenta.bold \"npm run build:html -- --watch\" \"nodemon --watch dist ./dist/validate-email-server.mjs\""
},
"dependencies": {
"@aws-sdk/client-dynamodb": "^3.515.0",
@@ -42,12 +43,18 @@
"@babel/preset-react": "^7.23.3",
"@ipld/car": "^5.2.6",
"@types/aws-lambda": "^8.10.108",
+ "@types/reload": "^3.2.3",
"@web3-storage/content-claims-infra": "^1.2.1",
"@web3-storage/sigv4": "^1.0.2",
"ava": "^4.3.3",
"aws-lambda-test-utils": "^1.3.0",
+ "concurrently": "^8.2.2",
"constructs": "*",
"dotenv": "^16.3.2",
+ "esbuild": "^0.23.1",
+ "express": "^4.19.2",
+ "nodemon": "^3.1.4",
+ "reload": "^3.2.2",
"testcontainers": "^10.7.1"
},
"ava": {
diff --git a/upload-api/test/helpers/validate-email-server.js b/upload-api/test/helpers/validate-email-server.js
index a72de9e1..aaec53cb 100644
--- a/upload-api/test/helpers/validate-email-server.js
+++ b/upload-api/test/helpers/validate-email-server.js
@@ -1,31 +1,119 @@
/**
* This is a helper to allow rapid local development of the validation email.
* It requires `upload-api/html.jsx` to be compiled to regular JS.
- *
+ *
* Usage:
* cd upload-api
- * npm run build:html
- * node test/helpers/validate-email-server.js
+ * npm run dev:html
*/
+import express from 'express'
+import reload from 'reload'
import http from 'node:http'
import { render } from 'preact-render-to-string'
import dotenv from 'dotenv'
import { fileURLToPath } from 'node:url'
-// @ts-ignore exists only after your run `npm run build:html`
-import { ValidateEmail, buildDocument } from '../../dist/html.js'
-
-dotenv.config({ path: fileURLToPath(new URL('../../../.env', import.meta.url)) })
-
-http.createServer((req, res) => {
- const vnode = /** @type {any} */ (ValidateEmail({
- ucan: 'test',
- email: 'test@example.org',
- audience: 'did:key:z6MkgcDgNxFxtgCfsnzZ8b4Wf5SLCskwwK18EVovcFvJugbK',
- stripePricingTableId: process.env.STRIPE_PRICING_TABLE_ID,
- stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY
- }))
- res.write(buildDocument(render(vnode)))
+
+import * as htmlStoracha from '../../html-storacha/index.jsx'
+import * as htmlW3s from '../../html-w3s/index.jsx'
+
+dotenv.config({
+ path: fileURLToPath(new URL('../../../.env.local', import.meta.url)),
+})
+
+const HTMLS = { Storacha: htmlStoracha, 'web3.storage': htmlW3s }
+
+const COMPONENTS = /** @type { const } */ ([
+ 'ValidateEmail',
+ 'PendingValidateEmail',
+ 'ValidateEmailError',
+])
+
+/**
+ * Insert reload script into HTML
+ *
+ * @param {string} html
+ */
+const insertReloadScript = (html) => {
+ return html.replace(
+ /<\/body>/,
+ '
+ ${Object.entries(HTMLS)
+ .map(
+ ([htmlName, _html]) => /* html */ `
+ ${htmlName}:
+
+ ${COMPONENTS.map(
+ (componentName) =>
+ /* html */ `- ${componentName}
`
+ ).join('')}
+
+ `
+ )
+ .join('')}
+
+