tapimo 0.0.2 → 0.2.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/README.md +38 -16
- package/dist/ApiKey.d.ts +14 -11
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +11 -12
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +207 -0
- package/dist/ApiKeys.d.ts.map +1 -0
- package/dist/ApiKeys.js +282 -0
- package/dist/ApiKeys.js.map +1 -0
- package/dist/App.d.ts +6771 -3819
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +145 -317
- package/dist/App.js.map +1 -1
- package/dist/Handler.d.ts +86 -0
- package/dist/Handler.d.ts.map +1 -0
- package/dist/Handler.js +83 -0
- package/dist/Handler.js.map +1 -0
- package/dist/Scope.d.ts +14 -4
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +16 -4
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +5 -7
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +11 -4
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/Access.d.ts +1 -3
- package/dist/admin/Access.d.ts.map +1 -1
- package/dist/admin/Access.js +1 -3
- package/dist/admin/Access.js.map +1 -1
- package/dist/admin/App.d.ts +167 -205
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +2 -4
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +105 -5
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +75 -9
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +6 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +35 -149
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +18 -39
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +1 -1
- package/dist/apps/data/App.d.ts +19176 -0
- package/dist/apps/data/App.d.ts.map +1 -0
- package/dist/apps/data/App.js +236 -0
- package/dist/apps/data/App.js.map +1 -0
- package/dist/apps/data/index.d.ts +3 -0
- package/dist/apps/data/index.d.ts.map +1 -0
- package/dist/apps/data/index.js +3 -0
- package/dist/apps/data/index.js.map +1 -0
- package/dist/apps/{activities.d.ts → data/routes/activities.d.ts} +32 -32
- package/dist/apps/{activities.d.ts.map → data/routes/activities.d.ts.map} +1 -1
- package/dist/apps/{activities.js → data/routes/activities.js} +13 -13
- package/dist/apps/data/routes/activities.js.map +1 -0
- package/dist/apps/{addresses.d.ts → data/routes/addresses.d.ts} +22 -22
- package/dist/apps/{addresses.d.ts.map → data/routes/addresses.d.ts.map} +1 -1
- package/dist/apps/{addresses.js → data/routes/addresses.js} +5 -5
- package/dist/apps/data/routes/addresses.js.map +1 -0
- package/dist/apps/{balances.d.ts → data/routes/balances.d.ts} +49 -49
- package/dist/apps/data/routes/balances.d.ts.map +1 -0
- package/dist/apps/{balances.js → data/routes/balances.js} +42 -11
- package/dist/apps/data/routes/balances.js.map +1 -0
- package/dist/apps/{blocks.d.ts → data/routes/blocks.d.ts} +47 -47
- package/dist/apps/data/routes/blocks.d.ts.map +1 -0
- package/dist/apps/{blocks.js → data/routes/blocks.js} +13 -13
- package/dist/apps/data/routes/blocks.js.map +1 -0
- package/dist/apps/{coingecko.d.ts → data/routes/coingecko.d.ts} +183 -183
- package/dist/apps/data/routes/coingecko.d.ts.map +1 -0
- package/dist/apps/{coingecko.js → data/routes/coingecko.js} +13 -13
- package/dist/apps/data/routes/coingecko.js.map +1 -0
- package/dist/apps/{exchanges.d.ts → data/routes/exchanges.d.ts} +460 -460
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -0
- package/dist/apps/{exchanges.js → data/routes/exchanges.js} +19 -19
- package/dist/apps/data/routes/exchanges.js.map +1 -0
- package/dist/apps/{fee-amm.d.ts → data/routes/fee-amm.d.ts} +94 -94
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -0
- package/dist/apps/{fee-amm.js → data/routes/fee-amm.js} +17 -17
- package/dist/apps/data/routes/fee-amm.js.map +1 -0
- package/dist/apps/{indexer.d.ts → data/routes/indexer.d.ts} +4 -4
- package/dist/apps/data/routes/indexer.d.ts.map +1 -0
- package/dist/apps/{indexer.js → data/routes/indexer.js} +13 -9
- package/dist/apps/data/routes/indexer.js.map +1 -0
- package/dist/apps/{receipts.d.ts → data/routes/receipts.d.ts} +6 -6
- package/dist/apps/data/routes/receipts.d.ts.map +1 -0
- package/dist/apps/{receipts.js → data/routes/receipts.js} +12 -12
- package/dist/apps/data/routes/receipts.js.map +1 -0
- package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +4 -4
- package/dist/apps/data/routes/rpc.d.ts.map +1 -0
- package/dist/apps/{rpc.js → data/routes/rpc.js} +6 -6
- package/dist/apps/data/routes/rpc.js.map +1 -0
- package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +4 -4
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -0
- package/dist/apps/{tokenlist.js → data/routes/tokenlist.js} +8 -8
- package/dist/apps/data/routes/tokenlist.js.map +1 -0
- package/dist/apps/{tokens.d.ts → data/routes/tokens.d.ts} +11 -11
- package/dist/apps/data/routes/tokens.d.ts.map +1 -0
- package/dist/apps/{tokens.js → data/routes/tokens.js} +19 -19
- package/dist/apps/data/routes/tokens.js.map +1 -0
- package/dist/apps/{transactions.d.ts → data/routes/transactions.d.ts} +288 -288
- package/dist/apps/data/routes/transactions.d.ts.map +1 -0
- package/dist/apps/{transactions.js → data/routes/transactions.js} +13 -13
- package/dist/apps/data/routes/transactions.js.map +1 -0
- package/dist/apps/{transfers.d.ts → data/routes/transfers.d.ts} +35 -35
- package/dist/apps/data/routes/transfers.d.ts.map +1 -0
- package/dist/apps/{transfers.js → data/routes/transfers.js} +13 -13
- package/dist/apps/data/routes/transfers.js.map +1 -0
- package/dist/apps/{verified-tokens.d.ts → data/routes/verified-tokens.d.ts} +42 -42
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -0
- package/dist/apps/{verified-tokens.js → data/routes/verified-tokens.js} +9 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -0
- package/dist/apps/{webhooks.d.ts → data/routes/webhooks.d.ts} +66 -63
- package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
- package/dist/apps/{webhooks.js → data/routes/webhooks.js} +67 -59
- package/dist/apps/data/routes/webhooks.js.map +1 -0
- package/dist/apps/docs/App.d.ts +27 -0
- package/dist/apps/docs/App.d.ts.map +1 -0
- package/dist/apps/docs/App.js +122 -0
- package/dist/apps/docs/App.js.map +1 -0
- package/dist/{internal/OpenApi.fonts.d.ts → apps/docs/fonts.d.ts} +1 -1
- package/dist/apps/docs/fonts.d.ts.map +1 -0
- package/dist/{internal/OpenApi.fonts.js → apps/docs/fonts.js} +1 -1
- package/dist/apps/docs/fonts.js.map +1 -0
- package/dist/apps/docs/index.d.ts +2 -0
- package/dist/apps/docs/index.d.ts.map +1 -0
- package/dist/apps/docs/index.js +2 -0
- package/dist/apps/docs/index.js.map +1 -0
- package/dist/{internal/OpenApi.logo.d.ts → apps/docs/logo.d.ts} +1 -1
- package/dist/apps/docs/logo.d.ts.map +1 -0
- package/dist/{internal/OpenApi.logo.js → apps/docs/logo.js} +1 -1
- package/dist/apps/docs/logo.js.map +1 -0
- package/dist/apps/index.d.ts +4 -0
- package/dist/apps/index.d.ts.map +1 -0
- package/dist/apps/index.js +4 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/metadata.d.ts +7 -0
- package/dist/apps/metadata.d.ts.map +1 -0
- package/dist/apps/metadata.js +15 -0
- package/dist/apps/metadata.js.map +1 -0
- package/dist/apps/platform/App.d.ts +2985 -0
- package/dist/apps/platform/App.d.ts.map +1 -0
- package/dist/apps/platform/App.js +74 -0
- package/dist/apps/platform/App.js.map +1 -0
- package/dist/apps/platform/index.d.ts +2 -0
- package/dist/apps/platform/index.d.ts.map +1 -0
- package/dist/apps/platform/index.js +2 -0
- package/dist/apps/platform/index.js.map +1 -0
- package/dist/apps/platform/routes/api-keys.d.ts +681 -0
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
- package/dist/apps/platform/routes/api-keys.js +328 -0
- package/dist/apps/platform/routes/api-keys.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +733 -0
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
- package/dist/apps/platform/routes/invitations.js +413 -0
- package/dist/apps/platform/routes/invitations.js.map +1 -0
- package/dist/apps/platform/routes/me.d.ts +95 -0
- package/dist/apps/platform/routes/me.d.ts.map +1 -0
- package/dist/apps/platform/routes/me.js +81 -0
- package/dist/apps/platform/routes/me.js.map +1 -0
- package/dist/apps/platform/routes/members.d.ts +554 -0
- package/dist/apps/platform/routes/members.d.ts.map +1 -0
- package/dist/apps/platform/routes/members.js +251 -0
- package/dist/apps/platform/routes/members.js.map +1 -0
- package/dist/apps/platform/routes/orgs.d.ts +582 -0
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
- package/dist/apps/platform/routes/orgs.js +265 -0
- package/dist/apps/platform/routes/orgs.js.map +1 -0
- package/dist/apps/platform/routes/projects.d.ts +740 -0
- package/dist/apps/platform/routes/projects.d.ts.map +1 -0
- package/dist/apps/platform/routes/projects.js +286 -0
- package/dist/apps/platform/routes/projects.js.map +1 -0
- package/dist/apps/relay/App.d.ts +41 -0
- package/dist/apps/relay/App.d.ts.map +1 -0
- package/dist/apps/relay/App.js +103 -0
- package/dist/apps/relay/App.js.map +1 -0
- package/dist/apps/relay/Sponsorships.d.ts +107 -0
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -0
- package/dist/apps/relay/Sponsorships.js +193 -0
- package/dist/apps/relay/Sponsorships.js.map +1 -0
- package/dist/apps/relay/index.d.ts +3 -0
- package/dist/apps/relay/index.d.ts.map +1 -0
- package/dist/apps/relay/index.js +3 -0
- package/dist/apps/relay/index.js.map +1 -0
- package/dist/{Cli.d.ts → cli/Cli.d.ts} +13 -2
- package/dist/cli/Cli.d.ts.map +1 -0
- package/dist/cli/Cli.js +19 -0
- package/dist/cli/Cli.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +4 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +15 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +60 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cloudflare.d.ts +7 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +9 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +113 -0
- package/dist/db/Db.d.ts.map +1 -0
- package/dist/db/Db.js +402 -0
- package/dist/db/Db.js.map +1 -0
- package/dist/db/Schema.d.ts +168 -0
- package/dist/db/Schema.d.ts.map +1 -0
- package/dist/db/Schema.js +229 -0
- package/dist/db/Schema.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +84 -0
- package/dist/db/tables/invitations.d.ts.map +1 -0
- package/dist/db/tables/invitations.js +165 -0
- package/dist/db/tables/invitations.js.map +1 -0
- package/dist/db/tables/memberships.d.ts +108 -0
- package/dist/db/tables/memberships.d.ts.map +1 -0
- package/dist/db/tables/memberships.js +153 -0
- package/dist/db/tables/memberships.js.map +1 -0
- package/dist/db/tables/organizations.d.ts +123 -0
- package/dist/db/tables/organizations.d.ts.map +1 -0
- package/dist/db/tables/organizations.js +170 -0
- package/dist/db/tables/organizations.js.map +1 -0
- package/dist/db/tables/projects.d.ts +65 -0
- package/dist/db/tables/projects.d.ts.map +1 -0
- package/dist/db/tables/projects.js +75 -0
- package/dist/db/tables/projects.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +120 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
- package/dist/db/tables/sponsoredTransactions.js +161 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -0
- package/dist/db/tables/users.d.ts +48 -0
- package/dist/db/tables/users.d.ts.map +1 -0
- package/dist/db/tables/users.js +65 -0
- package/dist/db/tables/users.js.map +1 -0
- package/dist/db/tables/verifiedTokens.d.ts +53 -0
- package/dist/db/tables/verifiedTokens.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokens.js +74 -0
- package/dist/db/tables/verifiedTokens.js.map +1 -0
- package/dist/db/tables/webhookDeliveries.d.ts +71 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -0
- package/dist/db/tables/webhookDeliveries.js +119 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +158 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -0
- package/dist/db/tables/webhookSubscriptions.js +272 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -0
- package/dist/handlers/internal/executionError.d.ts +25 -0
- package/dist/handlers/internal/executionError.d.ts.map +1 -0
- package/dist/handlers/internal/executionError.js +186 -0
- package/dist/handlers/internal/executionError.js.map +1 -0
- package/dist/handlers/internal/multisig.d.ts +233 -0
- package/dist/handlers/internal/multisig.d.ts.map +1 -0
- package/dist/handlers/internal/multisig.js +542 -0
- package/dist/handlers/internal/multisig.js.map +1 -0
- package/dist/handlers/internal/requestListener.d.ts +124 -0
- package/dist/handlers/internal/requestListener.d.ts.map +1 -0
- package/dist/handlers/internal/requestListener.js +173 -0
- package/dist/handlers/internal/requestListener.js.map +1 -0
- package/dist/handlers/internal/sponsorship.d.ts +113 -0
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -0
- package/dist/handlers/internal/sponsorship.js +128 -0
- package/dist/handlers/internal/sponsorship.js.map +1 -0
- package/dist/handlers/internal/types.d.ts +284 -0
- package/dist/handlers/internal/types.d.ts.map +1 -0
- package/dist/handlers/internal/types.js +2 -0
- package/dist/handlers/internal/types.js.map +1 -0
- package/dist/handlers/internal/utils.d.ts +34 -0
- package/dist/handlers/internal/utils.d.ts.map +1 -0
- package/dist/handlers/internal/utils.js +131 -0
- package/dist/handlers/internal/utils.js.map +1 -0
- package/dist/handlers/relay.d.ts +123 -0
- package/dist/handlers/relay.d.ts.map +1 -0
- package/dist/handlers/relay.js +1278 -0
- package/dist/handlers/relay.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +224 -16
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +572 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Log.d.ts +7 -3
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +0 -19
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +0 -57
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RpcSchema.d.ts +10 -0
- package/dist/internal/RpcSchema.d.ts.map +1 -0
- package/dist/internal/RpcSchema.js +2 -0
- package/dist/internal/RpcSchema.js.map +1 -0
- package/dist/internal/Store.d.ts +49 -4
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +81 -5
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts +15 -34
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +115 -159
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +84 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Webhooks.d.ts +50 -36
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +92 -205
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +37 -6
- package/src/ApiKey.test.ts +3 -3
- package/src/ApiKey.ts +11 -12
- package/src/ApiKeys.test.ts +278 -0
- package/src/ApiKeys.ts +358 -0
- package/src/App.test-d.ts +1 -1
- package/src/App.test.ts +40508 -19083
- package/src/App.ts +239 -416
- package/src/Client.test-d.ts +307 -16
- package/src/Handler.ts +154 -0
- package/src/Scope.ts +17 -4
- package/src/Webhooks.test.ts +8 -7
- package/src/Webhooks.ts +13 -7
- package/src/admin/Access.ts +1 -3
- package/src/admin/App.ts +19 -24
- package/src/admin/apps/api-keys.test.ts +70 -18
- package/src/admin/apps/api-keys.ts +92 -9
- package/src/admin/apps/scopes.test.ts +8 -0
- package/src/admin/apps/verified-tokens.test.ts +3 -30
- package/src/admin/apps/verified-tokens.ts +18 -37
- package/src/admin/ui.generated.ts +1 -1
- package/src/apps/composition.test.ts +66 -0
- package/src/apps/data/App.ts +295 -0
- package/src/apps/data/index.ts +2 -0
- package/src/apps/{activities.test.ts → data/routes/activities.test.ts} +3 -3
- package/src/apps/{activities.ts → data/routes/activities.ts} +15 -15
- package/src/apps/{addresses.ts → data/routes/addresses.ts} +6 -6
- package/src/apps/{balances.test.ts → data/routes/balances.test.ts} +6 -5
- package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
- package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +4 -4
- package/src/apps/{blocks.ts → data/routes/blocks.ts} +14 -14
- package/src/apps/{coingecko.test.ts → data/routes/coingecko.test.ts} +2 -2
- package/src/apps/{coingecko.ts → data/routes/coingecko.ts} +14 -14
- package/src/apps/{exchanges.test.ts → data/routes/exchanges.test.ts} +2 -2
- package/src/apps/{exchanges.ts → data/routes/exchanges.ts} +20 -20
- package/src/apps/{fee-amm.test.ts → data/routes/fee-amm.test.ts} +3 -3
- package/src/apps/{fee-amm.ts → data/routes/fee-amm.ts} +18 -18
- package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +27 -6
- package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
- package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +4 -4
- package/src/apps/{receipts.ts → data/routes/receipts.ts} +13 -13
- package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +142 -13
- package/src/apps/{rpc.ts → data/routes/rpc.ts} +7 -7
- package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +8 -10
- package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
- package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +6 -5
- package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
- package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +4 -4
- package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
- package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +3 -3
- package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
- package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +27 -15
- package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
- package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +137 -150
- package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +83 -81
- package/src/apps/docs/App.ts +136 -0
- package/src/apps/docs/index.ts +1 -0
- package/src/apps/index.ts +3 -0
- package/src/apps/metadata.ts +23 -0
- package/src/apps/platform/App.ts +130 -0
- package/src/apps/platform/index.ts +1 -0
- package/src/apps/platform/routes/api-keys.test.ts +330 -0
- package/src/apps/platform/routes/api-keys.ts +427 -0
- package/src/apps/platform/routes/invitations.test.ts +361 -0
- package/src/apps/platform/routes/invitations.ts +519 -0
- package/src/apps/platform/routes/me.test.ts +121 -0
- package/src/apps/platform/routes/me.ts +106 -0
- package/src/apps/platform/routes/members.test.ts +195 -0
- package/src/apps/platform/routes/members.ts +313 -0
- package/src/apps/platform/routes/orgs.test.ts +313 -0
- package/src/apps/platform/routes/orgs.ts +343 -0
- package/src/apps/platform/routes/projects.test.ts +181 -0
- package/src/apps/platform/routes/projects.ts +367 -0
- package/src/apps/relay/App.test.ts +484 -0
- package/src/apps/relay/App.ts +128 -0
- package/src/apps/relay/Sponsorships.test.ts +375 -0
- package/src/apps/relay/Sponsorships.ts +305 -0
- package/src/apps/relay/index.ts +2 -0
- package/src/{Cli.test.ts → cli/Cli.test.ts} +10 -9
- package/src/{Cli.ts → cli/Cli.ts} +16 -9
- package/src/cli/bin.ts +4 -0
- package/src/cli/commands/admin.test.ts +71 -0
- package/src/cli/commands/admin.ts +60 -0
- package/src/cloudflare.ts +10 -0
- package/src/db/Db.test.ts +84 -0
- package/src/db/Db.ts +542 -0
- package/src/db/Schema.ts +301 -0
- package/src/db/tables/invitations.ts +198 -0
- package/src/db/tables/memberships.ts +208 -0
- package/src/db/tables/organizations.test.ts +160 -0
- package/src/db/tables/organizations.ts +232 -0
- package/src/db/tables/projects.ts +107 -0
- package/src/db/tables/sponsoredTransactions.test.ts +218 -0
- package/src/db/tables/sponsoredTransactions.ts +236 -0
- package/src/db/tables/users.ts +85 -0
- package/src/db/tables/verifiedTokens.ts +111 -0
- package/src/db/tables/webhookDeliveries.ts +159 -0
- package/src/db/tables/webhookSubscriptions.ts +358 -0
- package/src/handlers/internal/executionError.ts +207 -0
- package/src/handlers/internal/multisig.test.ts +818 -0
- package/src/handlers/internal/multisig.ts +823 -0
- package/src/handlers/internal/requestListener.ts +273 -0
- package/src/handlers/internal/sponsorship.ts +245 -0
- package/src/handlers/internal/types.ts +378 -0
- package/src/handlers/internal/utils.ts +153 -0
- package/src/handlers/relay.test.ts +2035 -0
- package/src/handlers/relay.ts +1623 -0
- package/src/index.ts +4 -2
- package/src/internal/Auth.test.ts +337 -21
- package/src/internal/Auth.ts +783 -44
- package/src/internal/Cursor.test.ts +3 -2
- package/src/internal/Log.test.ts +1 -1
- package/src/internal/Log.ts +11 -3
- package/src/internal/MetricSink.ts +1 -1
- package/src/internal/OpenApi.ts +0 -59
- package/src/internal/RateLimit.test.ts +1 -1
- package/src/internal/RpcSchema.ts +10 -0
- package/src/internal/Store.test.ts +150 -10
- package/src/internal/Store.ts +136 -11
- package/src/internal/Tidx.local.test.ts +1 -1
- package/src/internal/VerifiedTokens.test.ts +99 -115
- package/src/internal/VerifiedTokens.ts +123 -175
- package/src/internal/WebhookDestination.test.ts +1 -1
- package/src/internal/Webhooks.test.ts +111 -111
- package/src/internal/Webhooks.ts +103 -221
- package/dist/ApiKeySource.d.ts +0 -166
- package/dist/ApiKeySource.d.ts.map +0 -1
- package/dist/ApiKeySource.js +0 -234
- package/dist/ApiKeySource.js.map +0 -1
- package/dist/Cli.d.ts.map +0 -1
- package/dist/Cli.js +0 -13
- package/dist/Cli.js.map +0 -1
- package/dist/apps/activities.js.map +0 -1
- package/dist/apps/addresses.js.map +0 -1
- package/dist/apps/balances.d.ts.map +0 -1
- package/dist/apps/balances.js.map +0 -1
- package/dist/apps/blocks.d.ts.map +0 -1
- package/dist/apps/blocks.js.map +0 -1
- package/dist/apps/coingecko.d.ts.map +0 -1
- package/dist/apps/coingecko.js.map +0 -1
- package/dist/apps/exchanges.d.ts.map +0 -1
- package/dist/apps/exchanges.js.map +0 -1
- package/dist/apps/fee-amm.d.ts.map +0 -1
- package/dist/apps/fee-amm.js.map +0 -1
- package/dist/apps/indexer.d.ts.map +0 -1
- package/dist/apps/indexer.js.map +0 -1
- package/dist/apps/receipts.d.ts.map +0 -1
- package/dist/apps/receipts.js.map +0 -1
- package/dist/apps/rpc.d.ts.map +0 -1
- package/dist/apps/rpc.js.map +0 -1
- package/dist/apps/tokenlist.d.ts.map +0 -1
- package/dist/apps/tokenlist.js.map +0 -1
- package/dist/apps/tokens.d.ts.map +0 -1
- package/dist/apps/tokens.js.map +0 -1
- package/dist/apps/transactions.d.ts.map +0 -1
- package/dist/apps/transactions.js.map +0 -1
- package/dist/apps/transfers.d.ts.map +0 -1
- package/dist/apps/transfers.js.map +0 -1
- package/dist/apps/verified-tokens.d.ts.map +0 -1
- package/dist/apps/verified-tokens.js.map +0 -1
- package/dist/apps/webhooks.js.map +0 -1
- package/dist/internal/OpenApi.fonts.d.ts.map +0 -1
- package/dist/internal/OpenApi.fonts.js.map +0 -1
- package/dist/internal/OpenApi.logo.d.ts.map +0 -1
- package/dist/internal/OpenApi.logo.js.map +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/31318/verified-tokens.json +0 -30
- package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +0 -4
- package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +0 -30
- package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +0 -11
- package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +0 -1
- package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
- package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +0 -5
- package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +0 -1
- package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +0 -1
- package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
- package/seeds/4217/verified-tokens.json +0 -177
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +0 -25
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +0 -1
- package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +0 -1
- package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +0 -1
- package/seeds/42431/verified-tokens.json +0 -44
- package/seeds/openrpc/README.md +0 -27
- package/seeds/openrpc/execution-apis.json +0 -8989
- package/src/ApiKeySource.test-d.ts +0 -11
- package/src/ApiKeySource.test.ts +0 -295
- package/src/ApiKeySource.ts +0 -303
- /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
- /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import type * as App from '../../../App.js'
|
|
5
|
+
import * as Auth from '../../../internal/Auth.js'
|
|
6
|
+
import * as Db from '../../../db/Db.js'
|
|
7
|
+
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
8
|
+
import * as Response from '../../../internal/Response.js'
|
|
9
|
+
import * as Schema from '../../../internal/Schema.js'
|
|
10
|
+
import * as Users from '../../../db/tables/users.js'
|
|
11
|
+
|
|
12
|
+
/** Zod schemas owned by the current-user resource. */
|
|
13
|
+
export namespace schema {
|
|
14
|
+
/** The signed-in developer. */
|
|
15
|
+
export const User = Schema.describe(
|
|
16
|
+
z.object({
|
|
17
|
+
address: z
|
|
18
|
+
.optional(z.string())
|
|
19
|
+
.check(
|
|
20
|
+
z.describe('Wallet address bound to this user, when wallet sign-in established it.'),
|
|
21
|
+
z.meta({ examples: ['0x0000000000000000000000000000000000000001'] }),
|
|
22
|
+
),
|
|
23
|
+
createdAt: z
|
|
24
|
+
.string()
|
|
25
|
+
.check(
|
|
26
|
+
z.describe('When the user was created (ISO 8601).'),
|
|
27
|
+
z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
|
|
28
|
+
),
|
|
29
|
+
email: z
|
|
30
|
+
.optional(z.string())
|
|
31
|
+
.check(z.describe('Verified email from the wallet identity token, when present.')),
|
|
32
|
+
id: z
|
|
33
|
+
.string()
|
|
34
|
+
.check(
|
|
35
|
+
z.describe('Opaque user id (`usr_…`).'),
|
|
36
|
+
z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
37
|
+
),
|
|
38
|
+
updatedAt: z
|
|
39
|
+
.string()
|
|
40
|
+
.check(
|
|
41
|
+
z.describe('When the user was last updated (ISO 8601).'),
|
|
42
|
+
z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
|
|
43
|
+
),
|
|
44
|
+
}),
|
|
45
|
+
'The signed-in developer.',
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Mounts the `/me` resource: the session's user. Session lane only — API keys
|
|
51
|
+
* identify workloads, not developers.
|
|
52
|
+
*/
|
|
53
|
+
export function me() {
|
|
54
|
+
return new Hono<App.Environment>().get(
|
|
55
|
+
'/v1/me',
|
|
56
|
+
Auth.policy({ session: true }),
|
|
57
|
+
OpenApi.describeRoute({
|
|
58
|
+
operationId: 'getMe',
|
|
59
|
+
responses: OpenApi.responses({
|
|
60
|
+
errors: {
|
|
61
|
+
403: 'The caller is not a signed-in user.',
|
|
62
|
+
404: 'No user exists for this session.',
|
|
63
|
+
},
|
|
64
|
+
success: { description: 'The authenticated user.', schema: schema.User },
|
|
65
|
+
}),
|
|
66
|
+
summary: 'Get current user',
|
|
67
|
+
tags: ['Users'],
|
|
68
|
+
}),
|
|
69
|
+
async (c) => {
|
|
70
|
+
if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
|
|
71
|
+
|
|
72
|
+
// The session lane is the only lane here; `super_admin` bypasses lanes
|
|
73
|
+
// but has no user row, so it is rejected too.
|
|
74
|
+
const principal = Auth.getPrincipal(c)
|
|
75
|
+
if (principal?.type !== 'session')
|
|
76
|
+
return Response.error(c, {
|
|
77
|
+
code: 'forbidden',
|
|
78
|
+
message: 'Session required',
|
|
79
|
+
status: 403,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const db = Db.get(c.get('db'))
|
|
83
|
+
try {
|
|
84
|
+
const user = await Users.get(db, principal.id)
|
|
85
|
+
if (!user)
|
|
86
|
+
return Response.error(c, {
|
|
87
|
+
code: 'user_not_found',
|
|
88
|
+
message: 'User not found',
|
|
89
|
+
status: 404,
|
|
90
|
+
})
|
|
91
|
+
return c.json(
|
|
92
|
+
Response.validated(schema.User, {
|
|
93
|
+
...(user.address ? { address: user.address } : {}),
|
|
94
|
+
createdAt: user.createdAt,
|
|
95
|
+
...(user.email ? { email: user.email } : {}),
|
|
96
|
+
id: user.id,
|
|
97
|
+
updatedAt: user.updatedAt,
|
|
98
|
+
}),
|
|
99
|
+
200,
|
|
100
|
+
)
|
|
101
|
+
} catch (cause) {
|
|
102
|
+
return Response.upstream(c, cause)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
2
|
+
|
|
3
|
+
import * as TestApp from '../../../../test/App.js'
|
|
4
|
+
import * as Memberships from '../../../db/tables/memberships.js'
|
|
5
|
+
import * as Members from './members.js'
|
|
6
|
+
import * as Orgs from './orgs.js'
|
|
7
|
+
|
|
8
|
+
/** Origin pinned for SIWE domain binding; Hono test requests use this host. */
|
|
9
|
+
const origin = 'http://localhost'
|
|
10
|
+
|
|
11
|
+
/** RequestInit for a JSON mutation carrying the session cookie. */
|
|
12
|
+
function json(method: 'PATCH' | 'POST', cookie: string, body: unknown) {
|
|
13
|
+
return {
|
|
14
|
+
body: JSON.stringify(body),
|
|
15
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
16
|
+
method,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createApp(db = TestApp.database()) {
|
|
21
|
+
return TestApp.create({ db, session: { wallet: { origin } } })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Signs in a fresh scripted account; returns its cookie and user id. */
|
|
25
|
+
async function session(app: ReturnType<typeof createApp>) {
|
|
26
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
27
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
28
|
+
const me = await app.request('/v1/me', { headers: { cookie: cookie! } })
|
|
29
|
+
const { id } = (await me.json()) as { id: string }
|
|
30
|
+
return { cookie: cookie!, userId: id }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** An org owned by `owner`, with `member` seeded at the given role. */
|
|
34
|
+
async function team(
|
|
35
|
+
app: ReturnType<typeof createApp>,
|
|
36
|
+
db: ReturnType<typeof TestApp.database>,
|
|
37
|
+
role: Memberships.Role,
|
|
38
|
+
) {
|
|
39
|
+
const owner = await session(app)
|
|
40
|
+
const member = await session(app)
|
|
41
|
+
const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
|
|
42
|
+
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
43
|
+
await Memberships.create(db, { orgId: org.id, role, userId: member.userId })
|
|
44
|
+
return { member, org, owner }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('members', () => {
|
|
48
|
+
test('lists the team with roles and identity', async () => {
|
|
49
|
+
const db = TestApp.database()
|
|
50
|
+
const app = createApp(db)
|
|
51
|
+
const { member, org, owner } = await team(app, db, 'member')
|
|
52
|
+
|
|
53
|
+
const response = await app.request(`/v1/orgs/${org.id}/members`, {
|
|
54
|
+
headers: { cookie: member.cookie },
|
|
55
|
+
})
|
|
56
|
+
expect(response.status).toBe(200)
|
|
57
|
+
const body = await TestApp.json(response, Members.schema.listMembers.Response)
|
|
58
|
+
expect(body.data.map(({ role, userId }) => ({ role, userId }))).toEqual([
|
|
59
|
+
{ role: 'owner', userId: owner.userId },
|
|
60
|
+
{ role: 'member', userId: member.userId },
|
|
61
|
+
])
|
|
62
|
+
expect(body.data[0]!.address).toMatch(/^0x/)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('tiers write access by role across the surface', async () => {
|
|
66
|
+
const db = TestApp.database()
|
|
67
|
+
const app = createApp(db)
|
|
68
|
+
const { member, org } = await team(app, db, 'member')
|
|
69
|
+
|
|
70
|
+
// Members read the org but cannot manage it or its projects.
|
|
71
|
+
const read = await app.request(`/v1/orgs/${org.id}`, { headers: { cookie: member.cookie } })
|
|
72
|
+
expect(read.status).toBe(200)
|
|
73
|
+
const rename = await app.request(`/v1/orgs/${org.id}`, json('PATCH', member.cookie, { name: 'Steal' })) // prettier-ignore
|
|
74
|
+
expect(rename.status).toBe(403)
|
|
75
|
+
const project = await app.request(`/v1/orgs/${org.id}/projects`, json('POST', member.cookie, { name: 'Nope' })) // prettier-ignore
|
|
76
|
+
expect(project.status).toBe(403)
|
|
77
|
+
|
|
78
|
+
// Admins manage projects but not the org itself.
|
|
79
|
+
const admin = await session(app)
|
|
80
|
+
await Memberships.create(db, { orgId: org.id, role: 'admin', userId: admin.userId })
|
|
81
|
+
const adminProject = await app.request(`/v1/orgs/${org.id}/projects`, json('POST', admin.cookie, { name: 'Checkout' })) // prettier-ignore
|
|
82
|
+
expect(adminProject.status).toBe(200)
|
|
83
|
+
const adminRename = await app.request(`/v1/orgs/${org.id}`, json('PATCH', admin.cookie, { name: 'Steal' })) // prettier-ignore
|
|
84
|
+
expect(adminRename.status).toBe(403)
|
|
85
|
+
const adminDelete = await app.request(`/v1/orgs/${org.id}`, {
|
|
86
|
+
headers: { cookie: admin.cookie },
|
|
87
|
+
method: 'DELETE',
|
|
88
|
+
})
|
|
89
|
+
expect(adminDelete.status).toBe(403)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('owners change roles; the last owner cannot be demoted', async () => {
|
|
93
|
+
const db = TestApp.database()
|
|
94
|
+
const app = createApp(db)
|
|
95
|
+
const { member, org, owner } = await team(app, db, 'member')
|
|
96
|
+
|
|
97
|
+
const promoted = await app.request(
|
|
98
|
+
`/v1/orgs/${org.id}/members/${member.userId}`,
|
|
99
|
+
json('PATCH', owner.cookie, { role: 'admin' }),
|
|
100
|
+
)
|
|
101
|
+
expect(promoted.status).toBe(200)
|
|
102
|
+
expect((await TestApp.json(promoted, Members.schema.Member)).role).toBe('admin')
|
|
103
|
+
|
|
104
|
+
// A non-owner cannot change roles.
|
|
105
|
+
const foreign = await app.request(
|
|
106
|
+
`/v1/orgs/${org.id}/members/${owner.userId}`,
|
|
107
|
+
json('PATCH', member.cookie, { role: 'member' }),
|
|
108
|
+
)
|
|
109
|
+
expect(foreign.status).toBe(403)
|
|
110
|
+
|
|
111
|
+
const demoted = await app.request(
|
|
112
|
+
`/v1/orgs/${org.id}/members/${owner.userId}`,
|
|
113
|
+
json('PATCH', owner.cookie, { role: 'member' }),
|
|
114
|
+
)
|
|
115
|
+
expect(demoted.status).toBe(409)
|
|
116
|
+
const { requestId, ...body } = (await demoted.json()) as { requestId?: string }
|
|
117
|
+
expect(requestId).toBeDefined()
|
|
118
|
+
expect(body).toMatchInlineSnapshot(`
|
|
119
|
+
{
|
|
120
|
+
"error": {
|
|
121
|
+
"code": "last_owner",
|
|
122
|
+
"message": "The organization would be left without an owner",
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
`)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('concurrent demotions cannot strip the last owner', async () => {
|
|
129
|
+
const db = TestApp.database()
|
|
130
|
+
const app = createApp(db)
|
|
131
|
+
const owner = await session(app)
|
|
132
|
+
const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
|
|
133
|
+
const org = await TestApp.json(created, Orgs.schema.Organization)
|
|
134
|
+
// Two owners, each demoting the other at the same time: the guard must let
|
|
135
|
+
// exactly one through so the org is never left ownerless.
|
|
136
|
+
const second = await session(app)
|
|
137
|
+
await Memberships.create(db, { orgId: org.id, role: 'owner', userId: second.userId })
|
|
138
|
+
|
|
139
|
+
const [a, b] = await Promise.all([
|
|
140
|
+
app.request(`/v1/orgs/${org.id}/members/${owner.userId}`, json('PATCH', second.cookie, { role: 'member' })), // prettier-ignore
|
|
141
|
+
app.request(`/v1/orgs/${org.id}/members/${second.userId}`, json('PATCH', owner.cookie, { role: 'member' })), // prettier-ignore
|
|
142
|
+
])
|
|
143
|
+
expect([a.status, b.status].sort()).toEqual([200, 409])
|
|
144
|
+
const owners = (await Memberships.listByOrg(db, org.id)).filter(({ role }) => role === 'owner')
|
|
145
|
+
expect(owners).toHaveLength(1)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('owners remove members; members remove only themselves', async () => {
|
|
149
|
+
const db = TestApp.database()
|
|
150
|
+
const app = createApp(db)
|
|
151
|
+
const { member, org, owner } = await team(app, db, 'member')
|
|
152
|
+
const other = await session(app)
|
|
153
|
+
await Memberships.create(db, { orgId: org.id, role: 'member', userId: other.userId })
|
|
154
|
+
|
|
155
|
+
// A member cannot remove someone else.
|
|
156
|
+
const foreign = await app.request(`/v1/orgs/${org.id}/members/${other.userId}`, {
|
|
157
|
+
headers: { cookie: member.cookie },
|
|
158
|
+
method: 'DELETE',
|
|
159
|
+
})
|
|
160
|
+
expect(foreign.status).toBe(403)
|
|
161
|
+
|
|
162
|
+
// A member removes themself (leaves).
|
|
163
|
+
const left = await app.request(`/v1/orgs/${org.id}/members/${member.userId}`, {
|
|
164
|
+
headers: { cookie: member.cookie },
|
|
165
|
+
method: 'DELETE',
|
|
166
|
+
})
|
|
167
|
+
expect(left.status).toBe(200)
|
|
168
|
+
const gone = await app.request(`/v1/orgs/${org.id}`, { headers: { cookie: member.cookie } })
|
|
169
|
+
expect(gone.status).toBe(404)
|
|
170
|
+
|
|
171
|
+
// The owner removes the other member; the last owner cannot leave.
|
|
172
|
+
const removed = await app.request(`/v1/orgs/${org.id}/members/${other.userId}`, {
|
|
173
|
+
headers: { cookie: owner.cookie },
|
|
174
|
+
method: 'DELETE',
|
|
175
|
+
})
|
|
176
|
+
expect(removed.status).toBe(200)
|
|
177
|
+
const lastOwner = await app.request(`/v1/orgs/${org.id}/members/${owner.userId}`, {
|
|
178
|
+
headers: { cookie: owner.cookie },
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
})
|
|
181
|
+
expect(lastOwner.status).toBe(409)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('hides the team from foreign sessions', async () => {
|
|
185
|
+
const db = TestApp.database()
|
|
186
|
+
const app = createApp(db)
|
|
187
|
+
const { org } = await team(app, db, 'member')
|
|
188
|
+
|
|
189
|
+
const stranger = await session(app)
|
|
190
|
+
const response = await app.request(`/v1/orgs/${org.id}/members`, {
|
|
191
|
+
headers: { cookie: stranger.cookie },
|
|
192
|
+
})
|
|
193
|
+
expect(response.status).toBe(404)
|
|
194
|
+
})
|
|
195
|
+
})
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { type Context, Hono } from 'hono'
|
|
2
|
+
import * as z from 'zod/mini'
|
|
3
|
+
|
|
4
|
+
import type * as App from '../../../App.js'
|
|
5
|
+
import * as Auth from '../../../internal/Auth.js'
|
|
6
|
+
import * as Db from '../../../db/Db.js'
|
|
7
|
+
import * as Memberships from '../../../db/tables/memberships.js'
|
|
8
|
+
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
9
|
+
import * as Response from '../../../internal/Response.js'
|
|
10
|
+
import * as Schema from '../../../internal/Schema.js'
|
|
11
|
+
import * as Users from '../../../db/tables/users.js'
|
|
12
|
+
|
|
13
|
+
/** Zod schemas owned by the members resource. */
|
|
14
|
+
export namespace schema {
|
|
15
|
+
/** A membership role. */
|
|
16
|
+
export const Role = z
|
|
17
|
+
.enum(Memberships.roles)
|
|
18
|
+
.check(z.describe('Membership role.'), z.meta({ examples: ['admin'] }))
|
|
19
|
+
|
|
20
|
+
/** One organization member: the membership plus the member's identity. */
|
|
21
|
+
export const Member = Schema.describe(
|
|
22
|
+
z.object({
|
|
23
|
+
address: z
|
|
24
|
+
.optional(z.string())
|
|
25
|
+
.check(
|
|
26
|
+
z.describe("The member's wallet address, when wallet sign-in established it."),
|
|
27
|
+
z.meta({ examples: ['0x20f414b3cbcf1fd8e8f5e1c1c2e3d4a5b6c7d8e9'] }),
|
|
28
|
+
),
|
|
29
|
+
createdAt: z
|
|
30
|
+
.string()
|
|
31
|
+
.check(
|
|
32
|
+
z.describe('When the member joined (ISO 8601).'),
|
|
33
|
+
z.meta({ examples: ['2026-01-01T00:00:00.000Z'] }),
|
|
34
|
+
),
|
|
35
|
+
email: z
|
|
36
|
+
.optional(z.string())
|
|
37
|
+
.check(
|
|
38
|
+
z.describe("The member's verified email, when known."),
|
|
39
|
+
z.meta({ examples: ['dev@example.com'] }),
|
|
40
|
+
),
|
|
41
|
+
role: Role,
|
|
42
|
+
userId: z
|
|
43
|
+
.string()
|
|
44
|
+
.check(
|
|
45
|
+
z.describe('Member user id (`usr_…`).'),
|
|
46
|
+
z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
47
|
+
),
|
|
48
|
+
}),
|
|
49
|
+
"One organization member: the membership plus the member's identity.",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
/** Path parameters addressing an organization's member collection. */
|
|
53
|
+
export const Params = z
|
|
54
|
+
.object({
|
|
55
|
+
orgId: z
|
|
56
|
+
.string()
|
|
57
|
+
.check(
|
|
58
|
+
z.describe('The organization id (`org_…`).'),
|
|
59
|
+
z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
60
|
+
),
|
|
61
|
+
})
|
|
62
|
+
.check(z.describe("Path parameters for an organization's members."))
|
|
63
|
+
|
|
64
|
+
/** Path parameters addressing one member. */
|
|
65
|
+
export const MemberParams = z
|
|
66
|
+
.object({
|
|
67
|
+
orgId: z
|
|
68
|
+
.string()
|
|
69
|
+
.check(
|
|
70
|
+
z.describe('The organization id (`org_…`).'),
|
|
71
|
+
z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
72
|
+
),
|
|
73
|
+
userId: z
|
|
74
|
+
.string()
|
|
75
|
+
.check(
|
|
76
|
+
z.describe('The member user id (`usr_…`).'),
|
|
77
|
+
z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
78
|
+
),
|
|
79
|
+
})
|
|
80
|
+
.check(z.describe('Path parameters for one member.'))
|
|
81
|
+
|
|
82
|
+
/** Request body changing a member's role. */
|
|
83
|
+
export const MemberBody = z
|
|
84
|
+
.object({ role: Role })
|
|
85
|
+
.check(z.describe("Fields for changing a member's role."))
|
|
86
|
+
|
|
87
|
+
/** Schemas for the listMembers operation. */
|
|
88
|
+
export namespace listMembers {
|
|
89
|
+
/** Non-paginated list of an organization's members. */
|
|
90
|
+
export const Response = Schema.describe(
|
|
91
|
+
z.object({
|
|
92
|
+
data: z.array(Member).check(z.describe('The members, oldest first.')),
|
|
93
|
+
}),
|
|
94
|
+
"A non-paginated list of an organization's members.",
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Schemas for the removeMember operation. */
|
|
99
|
+
export namespace removeMember {
|
|
100
|
+
/** Confirmation that the member was removed. */
|
|
101
|
+
export const Response = z
|
|
102
|
+
.object({
|
|
103
|
+
userId: z
|
|
104
|
+
.string()
|
|
105
|
+
.check(
|
|
106
|
+
z.describe('User id (`usr_…`) of the removed member.'),
|
|
107
|
+
z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
|
|
108
|
+
),
|
|
109
|
+
})
|
|
110
|
+
.check(z.describe('Confirmation that the member was removed.'))
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Mounts the `/orgs/:orgId/members` resource: the organization's team.
|
|
116
|
+
* Members read; owners change roles and remove members (any member may remove
|
|
117
|
+
* themself). The last owner can never be demoted or removed.
|
|
118
|
+
*/
|
|
119
|
+
export function members() {
|
|
120
|
+
return new Hono<App.Environment>()
|
|
121
|
+
.get(
|
|
122
|
+
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/members',
|
|
123
|
+
Auth.policy({ session: true }),
|
|
124
|
+
Auth.ensureOrg(),
|
|
125
|
+
OpenApi.validate('param', schema.Params, {
|
|
126
|
+
code: 'param_invalid',
|
|
127
|
+
message: 'Check the path parameters and try again.',
|
|
128
|
+
}),
|
|
129
|
+
OpenApi.describeRoute({
|
|
130
|
+
operationId: 'listMembers',
|
|
131
|
+
responses: OpenApi.responses({
|
|
132
|
+
errors: { 404: 'No accessible organization was found for this id.' },
|
|
133
|
+
success: {
|
|
134
|
+
description: 'The members, oldest first.',
|
|
135
|
+
schema: schema.listMembers.Response,
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
summary: 'List members',
|
|
139
|
+
tags: ['Members'],
|
|
140
|
+
}),
|
|
141
|
+
async (c) => {
|
|
142
|
+
if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
|
|
143
|
+
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
144
|
+
if (OpenApi.narrowValidation)
|
|
145
|
+
return OpenApi.validationError(c, {
|
|
146
|
+
code: 'param_invalid',
|
|
147
|
+
message: 'Check the path parameters and try again.',
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const db = Db.get(c.get('db'))
|
|
151
|
+
try {
|
|
152
|
+
const records = await Memberships.listByOrgDetailed(db, Auth.org(c).id)
|
|
153
|
+
return c.json(
|
|
154
|
+
Response.validated(schema.listMembers.Response, {
|
|
155
|
+
data: records.map(serializeMember),
|
|
156
|
+
}),
|
|
157
|
+
200,
|
|
158
|
+
)
|
|
159
|
+
} catch (cause) {
|
|
160
|
+
return Response.upstream(c, cause)
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
.patch(
|
|
165
|
+
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/members/:userId{usr_[A-Za-z0-9_-]+}',
|
|
166
|
+
Auth.policy({ session: true }),
|
|
167
|
+
Auth.ensureOrg({ role: 'owner' }),
|
|
168
|
+
OpenApi.validate('param', schema.MemberParams, {
|
|
169
|
+
code: 'param_invalid',
|
|
170
|
+
message: 'Check the path parameters and try again.',
|
|
171
|
+
}),
|
|
172
|
+
OpenApi.validate('json', schema.MemberBody, {
|
|
173
|
+
code: 'body_invalid',
|
|
174
|
+
message: 'Check the request body and try again.',
|
|
175
|
+
}),
|
|
176
|
+
OpenApi.describeRoute({
|
|
177
|
+
description: "Change a member's role. The last owner cannot be demoted.",
|
|
178
|
+
operationId: 'updateMember',
|
|
179
|
+
responses: OpenApi.responses({
|
|
180
|
+
errors: {
|
|
181
|
+
404: 'No accessible member was found for this id.',
|
|
182
|
+
409: {
|
|
183
|
+
codes: ['last_owner'],
|
|
184
|
+
description: 'The organization would be left without an owner.',
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
success: { description: 'The updated member.', schema: schema.Member },
|
|
188
|
+
}),
|
|
189
|
+
summary: 'Update member',
|
|
190
|
+
tags: ['Members'],
|
|
191
|
+
}),
|
|
192
|
+
async (c) => {
|
|
193
|
+
if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
|
|
194
|
+
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
195
|
+
if (OpenApi.narrowValidation)
|
|
196
|
+
return OpenApi.validationError(c, {
|
|
197
|
+
code: 'param_invalid',
|
|
198
|
+
message: 'Check the path parameters and try again.',
|
|
199
|
+
})
|
|
200
|
+
if (OpenApi.narrowValidation)
|
|
201
|
+
return OpenApi.validationError(c, {
|
|
202
|
+
code: 'body_invalid',
|
|
203
|
+
message: 'Check the request body and try again.',
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const { userId } = c.req.valid('param')
|
|
207
|
+
const body = c.req.valid('json')
|
|
208
|
+
const db = Db.get(c.get('db'))
|
|
209
|
+
try {
|
|
210
|
+
const result = await Memberships.update(db, Auth.org(c).id, userId, { role: body.role })
|
|
211
|
+
if (result.error === 'last_owner') return lastOwner(c)
|
|
212
|
+
if (result.error) return memberNotFound(c)
|
|
213
|
+
const user = await Users.get(db, userId)
|
|
214
|
+
if (!user) return memberNotFound(c)
|
|
215
|
+
return c.json(
|
|
216
|
+
Response.validated(
|
|
217
|
+
schema.Member,
|
|
218
|
+
serializeMember({ ...result.record, address: user.address, email: user.email }),
|
|
219
|
+
),
|
|
220
|
+
200,
|
|
221
|
+
)
|
|
222
|
+
} catch (cause) {
|
|
223
|
+
return Response.upstream(c, cause)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
)
|
|
227
|
+
.delete(
|
|
228
|
+
'/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/members/:userId{usr_[A-Za-z0-9_-]+}',
|
|
229
|
+
Auth.policy({ session: true }),
|
|
230
|
+
Auth.ensureOrg(),
|
|
231
|
+
OpenApi.validate('param', schema.MemberParams, {
|
|
232
|
+
code: 'param_invalid',
|
|
233
|
+
message: 'Check the path parameters and try again.',
|
|
234
|
+
}),
|
|
235
|
+
OpenApi.describeRoute({
|
|
236
|
+
description: 'Remove a member. Owners remove anyone; any member removes themself. The last owner cannot be removed.', // prettier-ignore
|
|
237
|
+
operationId: 'removeMember',
|
|
238
|
+
responses: OpenApi.responses({
|
|
239
|
+
errors: {
|
|
240
|
+
404: 'No accessible member was found for this id.',
|
|
241
|
+
409: {
|
|
242
|
+
codes: ['last_owner'],
|
|
243
|
+
description: 'The organization would be left without an owner.',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
success: {
|
|
247
|
+
description: 'Confirmation that the member was removed.',
|
|
248
|
+
schema: schema.removeMember.Response,
|
|
249
|
+
},
|
|
250
|
+
}),
|
|
251
|
+
summary: 'Remove member',
|
|
252
|
+
tags: ['Members'],
|
|
253
|
+
}),
|
|
254
|
+
async (c) => {
|
|
255
|
+
if (Auth.narrowAccess) return Auth.superAdminAccessError(c)
|
|
256
|
+
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
257
|
+
if (OpenApi.narrowValidation)
|
|
258
|
+
return OpenApi.validationError(c, {
|
|
259
|
+
code: 'param_invalid',
|
|
260
|
+
message: 'Check the path parameters and try again.',
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const { userId } = c.req.valid('param')
|
|
264
|
+
// Owners remove anyone; everyone else only themself (leaving the org).
|
|
265
|
+
const caller = Auth.membership(c)
|
|
266
|
+
if (caller && caller.role !== 'owner' && caller.userId !== userId)
|
|
267
|
+
return Response.error(c, {
|
|
268
|
+
code: 'forbidden',
|
|
269
|
+
message: 'Requires the owner role',
|
|
270
|
+
status: 403,
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const db = Db.get(c.get('db'))
|
|
274
|
+
try {
|
|
275
|
+
const result = await Memberships.remove(db, Auth.org(c).id, userId)
|
|
276
|
+
if (result === 'last_owner') return lastOwner(c)
|
|
277
|
+
if (result === 'not_found') return memberNotFound(c)
|
|
278
|
+
return c.json(Response.validated(schema.removeMember.Response, { userId }), 200)
|
|
279
|
+
} catch (cause) {
|
|
280
|
+
return Response.upstream(c, cause)
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Projects a detailed membership row onto the member shape. */
|
|
287
|
+
function serializeMember(
|
|
288
|
+
record: Memberships.Record & { address: string | null; email: string | null },
|
|
289
|
+
) {
|
|
290
|
+
return {
|
|
291
|
+
...(record.address === null ? {} : { address: record.address }),
|
|
292
|
+
createdAt: record.createdAt,
|
|
293
|
+
...(record.email === null ? {} : { email: record.email }),
|
|
294
|
+
role: record.role,
|
|
295
|
+
userId: record.userId,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function lastOwner(c: Context<App.Environment>) {
|
|
300
|
+
return Response.error(c, {
|
|
301
|
+
code: 'last_owner',
|
|
302
|
+
message: 'The organization would be left without an owner',
|
|
303
|
+
status: 409,
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function memberNotFound(c: Context<App.Environment>) {
|
|
308
|
+
return Response.error(c, {
|
|
309
|
+
code: 'member_not_found',
|
|
310
|
+
message: 'Member not found',
|
|
311
|
+
status: 404,
|
|
312
|
+
})
|
|
313
|
+
}
|