tapimo 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/ApiKey.d.ts +12 -11
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +10 -12
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +139 -0
- package/dist/ApiKeys.d.ts.map +1 -0
- package/dist/ApiKeys.js +166 -0
- package/dist/ApiKeys.js.map +1 -0
- package/dist/App.d.ts +920 -910
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +36 -12
- 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 +4 -1
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -1
- package/dist/Scope.js.map +1 -1
- package/dist/Webhooks.d.ts +3 -5
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +9 -2
- 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 +98 -209
- 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 +7 -5
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +8 -9
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/scopes.d.ts +3 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +101 -215
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +17 -38
- 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/balances.d.ts +2 -2
- package/dist/apps/receipts.d.ts +43 -43
- package/dist/apps/tokens.d.ts +454 -454
- package/dist/apps/transactions.d.ts +43 -43
- package/dist/apps/transfers.d.ts +29 -29
- package/dist/apps/webhooks.d.ts +5 -2
- package/dist/apps/webhooks.d.ts.map +1 -1
- package/dist/apps/webhooks.js +38 -34
- package/dist/apps/webhooks.js.map +1 -1
- 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 +103 -0
- package/dist/db/Db.d.ts.map +1 -0
- package/dist/db/Db.js +274 -0
- package/dist/db/Db.js.map +1 -0
- package/dist/db/Schema.d.ts +88 -0
- package/dist/db/Schema.d.ts.map +1 -0
- package/dist/db/Schema.js +130 -0
- package/dist/db/Schema.js.map +1 -0
- package/dist/db/tables/organizations.d.ts +79 -0
- package/dist/db/tables/organizations.d.ts.map +1 -0
- package/dist/db/tables/organizations.js +81 -0
- package/dist/db/tables/organizations.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 +184 -0
- package/dist/handlers/internal/executionError.js.map +1 -0
- package/dist/handlers/internal/multisig.d.ts +237 -0
- package/dist/handlers/internal/multisig.d.ts.map +1 -0
- package/dist/handlers/internal/multisig.js +567 -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 +83 -0
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -0
- package/dist/handlers/internal/sponsorship.js +103 -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 +119 -0
- package/dist/handlers/relay.d.ts.map +1 -0
- package/dist/handlers/relay.js +1264 -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 +16 -10
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +72 -12
- package/dist/internal/Auth.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 +42 -3
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +65 -3
- 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/Webhooks.d.ts +49 -35
- 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 +7 -2
- package/src/ApiKey.test.ts +3 -3
- package/src/ApiKey.ts +10 -12
- package/src/ApiKeys.test.ts +175 -0
- package/src/ApiKeys.ts +205 -0
- package/src/App.test-d.ts +1 -1
- package/src/App.test.ts +10768 -351
- package/src/App.ts +79 -36
- package/src/Client.test-d.ts +1 -1
- package/src/Handler.ts +154 -0
- package/src/Scope.ts +5 -1
- package/src/Webhooks.test.ts +8 -7
- package/src/Webhooks.ts +11 -5
- package/src/admin/Access.ts +1 -3
- package/src/admin/App.ts +19 -24
- package/src/admin/apps/api-keys.test.ts +19 -18
- package/src/admin/apps/api-keys.ts +8 -9
- package/src/admin/apps/scopes.test.ts +4 -0
- package/src/admin/apps/verified-tokens.test.ts +3 -30
- package/src/admin/apps/verified-tokens.ts +17 -36
- package/src/admin/ui.generated.ts +1 -1
- package/src/apps/balances.test.ts +4 -3
- package/src/apps/blocks.test.ts +1 -1
- package/src/apps/exchanges.test.ts +1 -1
- package/src/apps/indexer.test.ts +1 -1
- package/src/apps/receipts.test.ts +1 -1
- package/src/apps/rpc.test.ts +100 -10
- package/src/apps/tokenlist.test.ts +5 -7
- package/src/apps/tokens.test.ts +4 -3
- package/src/apps/transactions.test.ts +1 -1
- package/src/apps/transfers.test.ts +1 -1
- package/src/apps/verified-tokens.test.ts +24 -12
- package/src/apps/webhooks.test.ts +130 -143
- package/src/apps/webhooks.ts +45 -47
- 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 +383 -0
- package/src/db/Schema.ts +164 -0
- package/src/db/tables/organizations.test.ts +124 -0
- package/src/db/tables/organizations.ts +127 -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 +205 -0
- package/src/handlers/internal/multisig.localnet.test.ts +146 -0
- package/src/handlers/internal/multisig.test.ts +744 -0
- package/src/handlers/internal/multisig.ts +858 -0
- package/src/handlers/internal/relay.localnet.test.ts +2035 -0
- package/src/handlers/internal/requestListener.ts +273 -0
- package/src/handlers/internal/sponsorship.ts +186 -0
- package/src/handlers/internal/types.ts +378 -0
- package/src/handlers/internal/utils.ts +153 -0
- package/src/handlers/relay.ts +1605 -0
- package/src/index.ts +4 -2
- package/src/internal/Auth.test.ts +21 -21
- package/src/internal/Auth.ts +92 -19
- package/src/internal/RateLimit.test.ts +1 -1
- package/src/internal/RpcSchema.ts +10 -0
- package/src/internal/Store.test.ts +98 -10
- package/src/internal/Store.ts +112 -6
- package/src/internal/VerifiedTokens.test.ts +99 -115
- package/src/internal/VerifiedTokens.ts +123 -175
- package/src/internal/Webhooks.test.ts +107 -107
- package/src/internal/Webhooks.ts +102 -220
- 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/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/index.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export * as ApiKey from './ApiKey.js'
|
|
2
|
-
export * as
|
|
2
|
+
export * as ApiKeys from './ApiKeys.js'
|
|
3
3
|
export * as App from './App.js'
|
|
4
4
|
export * as Assets from './Assets.js'
|
|
5
5
|
export * as Auth from './Auth.js'
|
|
6
|
-
export * as Cli from './Cli.js'
|
|
6
|
+
export * as Cli from './cli/Cli.js'
|
|
7
7
|
export * as Client from './Client.js'
|
|
8
|
+
export * as Db from './db/Db.js'
|
|
9
|
+
export * as Handler from './Handler.js'
|
|
8
10
|
export * as Log from './internal/Log.js'
|
|
9
11
|
export * as Metrics from './Metrics.js'
|
|
10
12
|
export * as RateLimit from './internal/RateLimit.js'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
2
|
import { requestId } from 'hono/request-id'
|
|
3
3
|
import { Mppx, tempo } from 'mppx/hono'
|
|
4
|
-
import { RateLimit } from '
|
|
4
|
+
import { RateLimit } from 'tapimo'
|
|
5
5
|
import * as ApiKey from '../ApiKey.js'
|
|
6
6
|
import * as Auth from './Auth.js'
|
|
7
7
|
import * as Log from './Log.js'
|
|
@@ -96,7 +96,7 @@ describe('require', () => {
|
|
|
96
96
|
// the downstream validator rejects it as `query_invalid` and the legacy
|
|
97
97
|
// `?key=...` lane breaks on every strict-query endpoint.
|
|
98
98
|
const auth = {
|
|
99
|
-
apiKey: {
|
|
99
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
100
100
|
rateLimit: RateLimit.memory(),
|
|
101
101
|
}
|
|
102
102
|
const app = new Hono<Auth.Environment>()
|
|
@@ -241,7 +241,7 @@ describe('require', () => {
|
|
|
241
241
|
const app = createApp({
|
|
242
242
|
auth: {
|
|
243
243
|
apiKey: {
|
|
244
|
-
|
|
244
|
+
resolve: TestApp.resolver({
|
|
245
245
|
keys: [{ ...key, scopes: [Scope.wildcard], token }],
|
|
246
246
|
}),
|
|
247
247
|
},
|
|
@@ -273,7 +273,7 @@ describe('require', () => {
|
|
|
273
273
|
function createChainApp(options: { public?: boolean } = {}) {
|
|
274
274
|
const auth: Auth.Context = {
|
|
275
275
|
apiKey: {
|
|
276
|
-
|
|
276
|
+
resolve: TestApp.resolver({
|
|
277
277
|
keys: [
|
|
278
278
|
{ ...key, scopes: [Scope.wildcard], token },
|
|
279
279
|
{
|
|
@@ -399,7 +399,7 @@ describe('require', () => {
|
|
|
399
399
|
const mpp = createMppServer()
|
|
400
400
|
const app = createApp({
|
|
401
401
|
auth: {
|
|
402
|
-
apiKey: {
|
|
402
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
403
403
|
mpp,
|
|
404
404
|
rateLimit: overQuota(),
|
|
405
405
|
},
|
|
@@ -420,7 +420,7 @@ describe('require', () => {
|
|
|
420
420
|
const mpp = createMppServer()
|
|
421
421
|
const app = createApp({
|
|
422
422
|
auth: {
|
|
423
|
-
apiKey: {
|
|
423
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
424
424
|
mpp,
|
|
425
425
|
rateLimit: overQuotaOnce(),
|
|
426
426
|
},
|
|
@@ -451,7 +451,7 @@ describe('require', () => {
|
|
|
451
451
|
const mpp = createMppServer()
|
|
452
452
|
const app = createApp({
|
|
453
453
|
auth: {
|
|
454
|
-
apiKey: {
|
|
454
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
455
455
|
mpp,
|
|
456
456
|
rateLimit: overQuota(),
|
|
457
457
|
},
|
|
@@ -477,7 +477,7 @@ describe('require', () => {
|
|
|
477
477
|
const mpp = createMppServer()
|
|
478
478
|
const app = createApp({
|
|
479
479
|
auth: {
|
|
480
|
-
apiKey: {
|
|
480
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
481
481
|
mpp,
|
|
482
482
|
rateLimit: overQuotaOnce(),
|
|
483
483
|
},
|
|
@@ -537,7 +537,7 @@ describe('require', () => {
|
|
|
537
537
|
test('returns rate-limit error without payment context', async () => {
|
|
538
538
|
const app = createApp({
|
|
539
539
|
auth: {
|
|
540
|
-
apiKey: {
|
|
540
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
541
541
|
rateLimit: overQuota(),
|
|
542
542
|
},
|
|
543
543
|
policy: { public: { rateLimit: { perMinute: 1 } } },
|
|
@@ -561,7 +561,7 @@ describe('require', () => {
|
|
|
561
561
|
try {
|
|
562
562
|
const app = createApp({
|
|
563
563
|
auth: {
|
|
564
|
-
apiKey: {
|
|
564
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
565
565
|
rateLimit: {
|
|
566
566
|
async consume() {
|
|
567
567
|
throw new Error('Network connection lost.')
|
|
@@ -594,7 +594,7 @@ describe('require', () => {
|
|
|
594
594
|
try {
|
|
595
595
|
const app = createApp({
|
|
596
596
|
auth: {
|
|
597
|
-
apiKey: {
|
|
597
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
598
598
|
rateLimit: {
|
|
599
599
|
async consume() {
|
|
600
600
|
throw new Error('Network connection lost.')
|
|
@@ -632,7 +632,7 @@ describe('require', () => {
|
|
|
632
632
|
const auth = {
|
|
633
633
|
apiKey: {
|
|
634
634
|
rateLimits: { 'data:read': { perMinute: 1 }, 'webhooks:write': { perMinute: 1 } },
|
|
635
|
-
|
|
635
|
+
resolve: TestApp.resolver({
|
|
636
636
|
keys: [{ ...key, scopes: [Scope.wildcard], token }],
|
|
637
637
|
}),
|
|
638
638
|
},
|
|
@@ -686,7 +686,7 @@ describe('require', () => {
|
|
|
686
686
|
const auth = {
|
|
687
687
|
apiKey: {
|
|
688
688
|
rateLimits: { 'data:read': { perMinute: 1_000 } },
|
|
689
|
-
|
|
689
|
+
resolve: TestApp.resolver({
|
|
690
690
|
keys: [{ ...key, scopes: [Scope.wildcard], token }],
|
|
691
691
|
}),
|
|
692
692
|
},
|
|
@@ -710,7 +710,7 @@ describe('require', () => {
|
|
|
710
710
|
})
|
|
711
711
|
|
|
712
712
|
test('per-key default overrides config default but not a route cap', async () => {
|
|
713
|
-
const
|
|
713
|
+
const resolve = TestApp.resolver({
|
|
714
714
|
keys: [
|
|
715
715
|
{
|
|
716
716
|
...key,
|
|
@@ -723,7 +723,7 @@ describe('require', () => {
|
|
|
723
723
|
|
|
724
724
|
const open = createScopedApp({
|
|
725
725
|
auth: {
|
|
726
|
-
apiKey: { rateLimits: { '*': { perMinute: 1_000 } },
|
|
726
|
+
apiKey: { rateLimits: { '*': { perMinute: 1_000 } }, resolve },
|
|
727
727
|
rateLimit: RateLimit.memory(),
|
|
728
728
|
},
|
|
729
729
|
routes: [{ path: '/protected', policy: { apiKey: { scopes: ['data:read'] } } }],
|
|
@@ -734,7 +734,7 @@ describe('require', () => {
|
|
|
734
734
|
|
|
735
735
|
const capped = createScopedApp({
|
|
736
736
|
auth: {
|
|
737
|
-
apiKey: { rateLimits: { '*': { perMinute: 1_000 } },
|
|
737
|
+
apiKey: { rateLimits: { '*': { perMinute: 1_000 } }, resolve },
|
|
738
738
|
rateLimit: RateLimit.memory(),
|
|
739
739
|
},
|
|
740
740
|
routes: [
|
|
@@ -757,7 +757,7 @@ describe('require', () => {
|
|
|
757
757
|
test('per-key per-scope overrides everything including a route cap', async () => {
|
|
758
758
|
const auth = {
|
|
759
759
|
apiKey: {
|
|
760
|
-
|
|
760
|
+
resolve: TestApp.resolver({
|
|
761
761
|
keys: [
|
|
762
762
|
{
|
|
763
763
|
...key,
|
|
@@ -790,7 +790,7 @@ describe('require', () => {
|
|
|
790
790
|
const entries: Log.Entry[] = []
|
|
791
791
|
const app = createApp({
|
|
792
792
|
auth: {
|
|
793
|
-
apiKey: {
|
|
793
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
794
794
|
rateLimit: overQuota(),
|
|
795
795
|
},
|
|
796
796
|
logger: (entry) => void entries.push(entry),
|
|
@@ -817,7 +817,7 @@ describe('require', () => {
|
|
|
817
817
|
const mpp = createMppServer()
|
|
818
818
|
const app = createApp({
|
|
819
819
|
auth: {
|
|
820
|
-
apiKey: {
|
|
820
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
821
821
|
mpp,
|
|
822
822
|
rateLimit: overQuota(),
|
|
823
823
|
},
|
|
@@ -846,7 +846,7 @@ function createScopedApp(options: {
|
|
|
846
846
|
}) {
|
|
847
847
|
const auth = options.auth ?? {
|
|
848
848
|
apiKey: {
|
|
849
|
-
|
|
849
|
+
resolve: TestApp.resolver({ keys: [{ ...key, scopes: [Scope.wildcard], token }] }),
|
|
850
850
|
},
|
|
851
851
|
rateLimit: RateLimit.memory(),
|
|
852
852
|
}
|
|
@@ -872,7 +872,7 @@ type Options = {
|
|
|
872
872
|
|
|
873
873
|
function createApp(options: Options = {}) {
|
|
874
874
|
const auth = options.auth ?? {
|
|
875
|
-
apiKey: {
|
|
875
|
+
apiKey: { resolve: TestApp.resolver({ keys: [{ ...key, token }] }) },
|
|
876
876
|
rateLimit: RateLimit.memory(),
|
|
877
877
|
}
|
|
878
878
|
const app = new Hono<Auth.Environment>()
|
package/src/internal/Auth.ts
CHANGED
|
@@ -7,8 +7,9 @@ import { findTargetHandler } from 'hono/utils/handler'
|
|
|
7
7
|
import { Credential, type Method } from 'mppx'
|
|
8
8
|
import { Mppx, tempo } from 'mppx/hono'
|
|
9
9
|
|
|
10
|
-
import * as ApiKeySource from '../ApiKeySource.js'
|
|
11
10
|
import * as ApiKey from '../ApiKey.js'
|
|
11
|
+
import * as ApiKeys from '../ApiKeys.js'
|
|
12
|
+
import type * as Store from './Store.js'
|
|
12
13
|
import * as OpenApi from './OpenApi.js'
|
|
13
14
|
import * as RateLimit from './RateLimit.js'
|
|
14
15
|
import * as Response from './Response.js'
|
|
@@ -29,8 +30,8 @@ export type Context = {
|
|
|
29
30
|
* per-key {@link ApiKey.ApiKey.rateLimits} shape.
|
|
30
31
|
*/
|
|
31
32
|
rateLimits?: Record<string, RateLimit.Limit> | undefined
|
|
32
|
-
/**
|
|
33
|
-
|
|
33
|
+
/** Resolves a presented token against the app's KV store on `c`. */
|
|
34
|
+
resolve: (c: hono_Context, token: string) => Promise<ApiKey.ApiKey | null>
|
|
34
35
|
}
|
|
35
36
|
| undefined
|
|
36
37
|
/** MPP payment configuration. */
|
|
@@ -217,14 +218,20 @@ export function middleware<
|
|
|
217
218
|
const { charge = {}, ...mpp } = options.mpp === false ? {} : (options.mpp ?? {})
|
|
218
219
|
const defaults = laneDefaults(options)
|
|
219
220
|
const base = basePolicy(options, defaults)
|
|
221
|
+
// Keys resolve against the app's KV state store on request context
|
|
222
|
+
// (`App.create({ kv })`), so the middleware needs no storage wiring; when no
|
|
223
|
+
// store is configured, every token resolves to `null` (closed).
|
|
220
224
|
// Resolution is cached per isolate by default (positive 60s / negative 10s,
|
|
221
|
-
// keyed by token sha256
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
// keyed by token sha256) so hot keys skip the backend read on the auth path
|
|
226
|
+
// of every request. Pass `apiKey.cache: false` when instant revocation
|
|
227
|
+
// matters more than the per-request read.
|
|
228
|
+
const resolve = (() => {
|
|
229
|
+
const uncached = (c: hono_Context, token: string) => {
|
|
230
|
+
const kv = contextKv(c)
|
|
231
|
+
return kv ? ApiKeys.resolve(kv.store, token) : Promise.resolve(null)
|
|
232
|
+
}
|
|
233
|
+
if (options.apiKey === false || options.apiKey?.cache === false) return uncached
|
|
234
|
+
return cachedResolve(uncached, options.apiKey?.cache ?? {})
|
|
228
235
|
})()
|
|
229
236
|
const auth: Context = {
|
|
230
237
|
...(options.apiKey === false
|
|
@@ -232,7 +239,7 @@ export function middleware<
|
|
|
232
239
|
: {
|
|
233
240
|
apiKey: {
|
|
234
241
|
...(options.apiKey?.rateLimits ? { rateLimits: options.apiKey.rateLimits } : {}),
|
|
235
|
-
|
|
242
|
+
resolve,
|
|
236
243
|
},
|
|
237
244
|
}),
|
|
238
245
|
...(options.mpp === false
|
|
@@ -280,16 +287,14 @@ export declare namespace middleware {
|
|
|
280
287
|
apiKey?:
|
|
281
288
|
| {
|
|
282
289
|
/**
|
|
283
|
-
* Resolve-cache tuning
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
290
|
+
* Resolve-cache tuning (per-isolate, positive 60s / negative 10s by
|
|
291
|
+
* default), or `false` to resolve uncached on every request — e.g.
|
|
292
|
+
* when instant revocation matters more than the per-request backend
|
|
293
|
+
* read.
|
|
287
294
|
*/
|
|
288
|
-
cache?:
|
|
295
|
+
cache?: Cache | false | undefined
|
|
289
296
|
/** API-key quotas keyed by quota scope (reserved `'*'` is the config default). */
|
|
290
297
|
rateLimits?: Record<string, RateLimit.Limit> | undefined
|
|
291
|
-
/** Backing API-key source. Defaults to an empty source (rejects all). */
|
|
292
|
-
source?: ApiKeySource.Source | undefined
|
|
293
298
|
/** Required API-key scopes. */
|
|
294
299
|
scopes?: readonly Scope.Id[] | undefined
|
|
295
300
|
}
|
|
@@ -311,6 +316,16 @@ export declare namespace middleware {
|
|
|
311
316
|
rateLimit?: RateLimit.memory.Options | undefined
|
|
312
317
|
}
|
|
313
318
|
|
|
319
|
+
/** Resolve-cache tuning for API-key resolution. */
|
|
320
|
+
type Cache = {
|
|
321
|
+
/** Time-to-live for negative (`null`) results in milliseconds. */
|
|
322
|
+
negativeTtl?: number | undefined
|
|
323
|
+
/** Clock used for expiry. */
|
|
324
|
+
now?: (() => number) | undefined
|
|
325
|
+
/** Time-to-live for positive results in milliseconds. */
|
|
326
|
+
ttl?: number | undefined
|
|
327
|
+
}
|
|
328
|
+
|
|
314
329
|
/** MPP payment options used by auth middleware. */
|
|
315
330
|
type MppOptions = Omit<NonNullable<Parameters<typeof Mppx.create>[0]>, 'methods'> & {
|
|
316
331
|
/** Global Tempo charge options passed to the MPP method instantiator. */
|
|
@@ -612,7 +627,7 @@ export function require<environment extends Environment = Environment>(
|
|
|
612
627
|
|
|
613
628
|
const hasPayment = hasPaymentCredential(c) && isPaymentEnabled(auth, policy)
|
|
614
629
|
if (credential) {
|
|
615
|
-
const apiKey = await auth.apiKey?.
|
|
630
|
+
const apiKey = await auth.apiKey?.resolve(c, credential.token)
|
|
616
631
|
if (!apiKey)
|
|
617
632
|
return jsonError(c, {
|
|
618
633
|
code: 'api_key_invalid',
|
|
@@ -1010,3 +1025,61 @@ function setRateLimitHeaders(c: hono_Context, result: RateLimit.Result) {
|
|
|
1010
1025
|
c.header('RateLimit-Remaining', String(result.remaining))
|
|
1011
1026
|
c.header('RateLimit-Reset', String(result.reset))
|
|
1012
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
// Reads the app's KV state store from request context (set by `App.create`
|
|
1030
|
+
// from its `kv` option); `undefined` leaves API-key auth closed.
|
|
1031
|
+
function contextKv(c: hono_Context): { store: Store.State } | undefined {
|
|
1032
|
+
return (c.get as (key: string) => unknown)('kv') as { store: Store.State } | undefined
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
/** Maximum resolve-cache entries held per isolate before eviction. */
|
|
1036
|
+
const cachedMaxEntries = 10_000
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Wraps token resolution with a short-TTL per-isolate cache so hot keys skip
|
|
1040
|
+
* the backend read on every request. Positive results cache for `ttl`
|
|
1041
|
+
* (default 60s), negative for `negativeTtl` (default 10s) so a freshly minted
|
|
1042
|
+
* key is usable quickly; concurrent misses for one token are single-flighted.
|
|
1043
|
+
* Entries are keyed by the token's sha256 (`ApiKey.hash`) — never the
|
|
1044
|
+
* plaintext. A revoked key may stay resolvable for up to `ttl` — the accepted
|
|
1045
|
+
* trade-off for removing a backend read from the auth path.
|
|
1046
|
+
*/
|
|
1047
|
+
function cachedResolve(
|
|
1048
|
+
resolve: (c: hono_Context, token: string) => Promise<ApiKey.ApiKey | null>,
|
|
1049
|
+
options: middleware.Cache,
|
|
1050
|
+
): (c: hono_Context, token: string) => Promise<ApiKey.ApiKey | null> {
|
|
1051
|
+
const { negativeTtl = 10_000, now = Date.now, ttl = 60_000 } = options
|
|
1052
|
+
type Cell = { expiresAt: number; value: ApiKey.ApiKey | null }
|
|
1053
|
+
const cells = new Map<string, Cell>()
|
|
1054
|
+
const flights = new Map<string, Promise<ApiKey.ApiKey | null>>()
|
|
1055
|
+
|
|
1056
|
+
return async (c, token) => {
|
|
1057
|
+
// Versioned so a future record-shape change can't serve stale shapes.
|
|
1058
|
+
const key = `v1:${ApiKey.hash(token)}`
|
|
1059
|
+
const cell = cells.get(key)
|
|
1060
|
+
if (cell && cell.expiresAt > now()) return cell.value
|
|
1061
|
+
if (cell) cells.delete(key)
|
|
1062
|
+
|
|
1063
|
+
const existing = flights.get(key)
|
|
1064
|
+
if (existing) return existing
|
|
1065
|
+
|
|
1066
|
+
const flight = (async () => {
|
|
1067
|
+
const value = await resolve(c, token)
|
|
1068
|
+
// Bound the cache: random invalid tokens each add a (negative) entry,
|
|
1069
|
+
// so sweep expired cells at the cap and then drop the oldest.
|
|
1070
|
+
if (cells.size >= cachedMaxEntries) {
|
|
1071
|
+
const time = now()
|
|
1072
|
+
for (const [name, entry] of cells) if (entry.expiresAt <= time) cells.delete(name)
|
|
1073
|
+
while (cells.size >= cachedMaxEntries) cells.delete(cells.keys().next().value as string)
|
|
1074
|
+
}
|
|
1075
|
+
cells.set(key, { expiresAt: now() + (value ? ttl : negativeTtl), value })
|
|
1076
|
+
return value
|
|
1077
|
+
})()
|
|
1078
|
+
flights.set(key, flight)
|
|
1079
|
+
try {
|
|
1080
|
+
return await flight
|
|
1081
|
+
} finally {
|
|
1082
|
+
flights.delete(key)
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RpcSchema } from 'ox'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ox-compatible JSON-RPC schema union used to type the relay handler's incoming
|
|
5
|
+
* requests (`RpcRequest.RpcRequest<Ox>`). The relay switches on `request.method`
|
|
6
|
+
* string literals and casts params per method, so a permissive schema whose
|
|
7
|
+
* `params` is an array is sufficient — the full method-by-method schema union
|
|
8
|
+
* from the accounts SDK is not needed here.
|
|
9
|
+
*/
|
|
10
|
+
export type Ox = RpcSchema.Generic<string, readonly unknown[]>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Store } from '
|
|
1
|
+
import { Store } from 'tapimo'
|
|
2
2
|
|
|
3
3
|
describe('from', () => {
|
|
4
4
|
const ops = {
|
|
@@ -68,7 +68,89 @@ describe('increment', () => {
|
|
|
68
68
|
})
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
+
describe('update', () => {
|
|
72
|
+
test('falls back to a get + put read-modify-write without a native swap', async () => {
|
|
73
|
+
const cells = new Map<string, string>()
|
|
74
|
+
const store = Store.from({
|
|
75
|
+
async delete(key) {
|
|
76
|
+
cells.delete(key)
|
|
77
|
+
},
|
|
78
|
+
async get(key) {
|
|
79
|
+
return cells.get(key) ?? null
|
|
80
|
+
},
|
|
81
|
+
async list() {
|
|
82
|
+
return { keys: [] }
|
|
83
|
+
},
|
|
84
|
+
async put(key, value) {
|
|
85
|
+
cells.set(key, value)
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(await Store.update(store, 'value', (current) => `${current ?? 'start'}+`)).toMatchInlineSnapshot(`"start+"`) // prettier-ignore
|
|
90
|
+
expect(await Store.update(store, 'value', (current) => `${current}+`)).toMatchInlineSnapshot(`"start++"`) // prettier-ignore
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('retries the transform when the compare-and-swap loses', async () => {
|
|
94
|
+
const store = Store.memory()
|
|
95
|
+
await store.put('value', 'a')
|
|
96
|
+
|
|
97
|
+
// Interleave a competing write between the read and the swap once.
|
|
98
|
+
let raced = false
|
|
99
|
+
const get = store.get.bind(store)
|
|
100
|
+
store.get = async (key) => {
|
|
101
|
+
const current = await get(key)
|
|
102
|
+
if (!raced) {
|
|
103
|
+
raced = true
|
|
104
|
+
await store.put('value', 'b')
|
|
105
|
+
}
|
|
106
|
+
return current
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const transforms: (null | string)[] = []
|
|
110
|
+
const written = await Store.update(store, 'value', (current) => {
|
|
111
|
+
transforms.push(current)
|
|
112
|
+
return `${current}c`
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
expect(written).toMatchInlineSnapshot(`"bc"`)
|
|
116
|
+
expect(transforms).toMatchInlineSnapshot(`
|
|
117
|
+
[
|
|
118
|
+
"a",
|
|
119
|
+
"b",
|
|
120
|
+
]
|
|
121
|
+
`)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('fails after exhausting compare-and-swap attempts', async () => {
|
|
125
|
+
const store = Store.memory()
|
|
126
|
+
await store.put('value', 'a')
|
|
127
|
+
|
|
128
|
+
// Every read is immediately invalidated by a competing write.
|
|
129
|
+
const get = store.get.bind(store)
|
|
130
|
+
store.get = async (key) => {
|
|
131
|
+
const current = await get(key)
|
|
132
|
+
await store.put('value', `${current}!`)
|
|
133
|
+
return current
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await expect(
|
|
137
|
+
Store.update(store, 'value', (current) => `${current}c`, { attempts: 2 }),
|
|
138
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
139
|
+
`[Store.UpdateContentionError: Store update on "value" lost 2 compare-and-swap attempts.]`,
|
|
140
|
+
)
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
71
144
|
describe('memory', () => {
|
|
145
|
+
test('swaps atomically against the expected value', async () => {
|
|
146
|
+
const store = Store.memory()
|
|
147
|
+
|
|
148
|
+
expect(await store.swap!('value', null, 'first')).toMatchInlineSnapshot(`true`)
|
|
149
|
+
expect(await store.swap!('value', null, 'clobber')).toMatchInlineSnapshot(`false`)
|
|
150
|
+
expect(await store.swap!('value', 'first', 'second')).toMatchInlineSnapshot(`true`)
|
|
151
|
+
expect(await store.get('value')).toMatchInlineSnapshot(`"second"`)
|
|
152
|
+
})
|
|
153
|
+
|
|
72
154
|
test('treats expired entries as absent', async () => {
|
|
73
155
|
const store = Store.memory()
|
|
74
156
|
|
|
@@ -193,6 +275,16 @@ describe('cloudflareKv', () => {
|
|
|
193
275
|
})
|
|
194
276
|
|
|
195
277
|
describe('durableObject', () => {
|
|
278
|
+
// The stub contract requires native `increment` and `swap` (our DO class
|
|
279
|
+
// implements them); derive them for the in-memory fakes.
|
|
280
|
+
function stubFor(backing: Store.State): Store.durableObject.Stub {
|
|
281
|
+
return {
|
|
282
|
+
...backing,
|
|
283
|
+
increment: (key, options) => Store.increment(backing, key, options),
|
|
284
|
+
swap: (key, expected, next, options) => backing.swap!(key, expected, next, options),
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
196
288
|
test('adapts a Durable Object namespace without caching stubs', async () => {
|
|
197
289
|
const calls: string[] = []
|
|
198
290
|
const backing = Store.memory({
|
|
@@ -201,12 +293,7 @@ describe('durableObject', () => {
|
|
|
201
293
|
const namespace = {
|
|
202
294
|
getByName(name) {
|
|
203
295
|
calls.push(name)
|
|
204
|
-
|
|
205
|
-
// implements it); derive one for the in-memory fake.
|
|
206
|
-
return {
|
|
207
|
-
...backing,
|
|
208
|
-
increment: (key, options) => Store.increment(backing, key, options),
|
|
209
|
-
}
|
|
296
|
+
return stubFor(backing)
|
|
210
297
|
},
|
|
211
298
|
} satisfies Store.durableObject.Namespace
|
|
212
299
|
const store = Store.durableObject(namespace, { name: 'tempo-api' })
|
|
@@ -250,6 +337,7 @@ describe('durableObject', () => {
|
|
|
250
337
|
increment: (key, options) => Store.increment(backing, key, options),
|
|
251
338
|
list: (options) => backing.list(options),
|
|
252
339
|
put: (key, value, options) => backing.put(key, value, options),
|
|
340
|
+
swap: (key, expected, next, options) => backing.swap!(key, expected, next, options),
|
|
253
341
|
} satisfies Store.durableObject.Stub
|
|
254
342
|
return new Proxy(stub, {
|
|
255
343
|
get(target, method: string) {
|
|
@@ -294,7 +382,7 @@ describe('durableObject', () => {
|
|
|
294
382
|
getByName() {
|
|
295
383
|
stubs += 1
|
|
296
384
|
return {
|
|
297
|
-
...backing,
|
|
385
|
+
...stubFor(backing),
|
|
298
386
|
increment: (key, options) => {
|
|
299
387
|
if (failures > 0) {
|
|
300
388
|
failures -= 1
|
|
@@ -317,7 +405,7 @@ describe('durableObject', () => {
|
|
|
317
405
|
const namespace = {
|
|
318
406
|
getByName() {
|
|
319
407
|
return {
|
|
320
|
-
...Store.memory(),
|
|
408
|
+
...stubFor(Store.memory()),
|
|
321
409
|
increment: () => {
|
|
322
410
|
attempts += 1
|
|
323
411
|
throw Object.assign(new Error('Network connection lost.'), { retryable: true })
|
|
@@ -339,7 +427,7 @@ describe('durableObject', () => {
|
|
|
339
427
|
({
|
|
340
428
|
getByName() {
|
|
341
429
|
return {
|
|
342
|
-
...Store.memory(),
|
|
430
|
+
...stubFor(Store.memory()),
|
|
343
431
|
increment: () => {
|
|
344
432
|
attempts += 1
|
|
345
433
|
throw error
|