@nkmc/gateway 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.
Files changed (130) hide show
  1. package/dist/chunk-56RA53VS.js +37 -0
  2. package/dist/chunk-CZJ75YTV.js +969 -0
  3. package/dist/chunk-QGM4M3NI.js +37 -0
  4. package/dist/http.cjs +1772 -0
  5. package/dist/http.d.cts +49 -0
  6. package/dist/http.d.ts +49 -0
  7. package/dist/http.js +748 -0
  8. package/dist/index.cjs +2436 -0
  9. package/dist/index.d.cts +436 -0
  10. package/dist/index.d.ts +436 -0
  11. package/dist/index.js +1434 -0
  12. package/dist/proxy-ClPcDgsO.d.cts +283 -0
  13. package/dist/proxy-qpda1ANS.d.ts +283 -0
  14. package/dist/proxy.cjs +148 -0
  15. package/dist/proxy.d.cts +6 -0
  16. package/dist/proxy.d.ts +6 -0
  17. package/dist/proxy.js +90 -0
  18. package/dist/testing.cjs +865 -0
  19. package/dist/testing.d.cts +12 -0
  20. package/dist/testing.d.ts +12 -0
  21. package/dist/testing.js +831 -0
  22. package/dist/tunnels-BviBEaih.d.cts +12 -0
  23. package/dist/tunnels-DFHNgmN7.d.ts +12 -0
  24. package/dist/types-C6JC9oTm.d.cts +21 -0
  25. package/dist/types-C6JC9oTm.d.ts +21 -0
  26. package/package.json +47 -0
  27. package/src/__tests__/sqlite-integration.test.ts +384 -0
  28. package/src/credential/d1-vault.ts +134 -0
  29. package/src/credential/memory-vault.ts +50 -0
  30. package/src/credential/types.ts +16 -0
  31. package/src/d1/__tests__/sqlite-adapter.test.ts +75 -0
  32. package/src/d1/sqlite-adapter.ts +59 -0
  33. package/src/d1/types.ts +22 -0
  34. package/src/federation/__tests__/d1-peer-store.test.ts +218 -0
  35. package/src/federation/__tests__/peer-client.test.ts +205 -0
  36. package/src/federation/__tests__/peer-store.test.ts +114 -0
  37. package/src/federation/d1-peer-store.ts +164 -0
  38. package/src/federation/peer-backend.ts +60 -0
  39. package/src/federation/peer-client.ts +122 -0
  40. package/src/federation/peer-store.ts +45 -0
  41. package/src/federation/types.ts +39 -0
  42. package/src/http/app.ts +152 -0
  43. package/src/http/lib/dns.ts +30 -0
  44. package/src/http/middleware/admin-auth.ts +18 -0
  45. package/src/http/middleware/agent-auth.ts +27 -0
  46. package/src/http/middleware/publish-auth.ts +39 -0
  47. package/src/http/routes/__tests__/federation.test.ts +364 -0
  48. package/src/http/routes/__tests__/peers.test.ts +290 -0
  49. package/src/http/routes/__tests__/proxy.test.ts +159 -0
  50. package/src/http/routes/auth.ts +39 -0
  51. package/src/http/routes/byok.ts +62 -0
  52. package/src/http/routes/credentials.ts +40 -0
  53. package/src/http/routes/domains.ts +174 -0
  54. package/src/http/routes/federation.ts +170 -0
  55. package/src/http/routes/fs.ts +89 -0
  56. package/src/http/routes/peers.ts +103 -0
  57. package/src/http/routes/proxy.ts +57 -0
  58. package/src/http/routes/registry.ts +222 -0
  59. package/src/http/routes/tunnels.ts +124 -0
  60. package/src/http.ts +9 -0
  61. package/src/index.ts +63 -0
  62. package/src/metering/d1-store.ts +123 -0
  63. package/src/metering/memory-store.ts +29 -0
  64. package/src/metering/pricing-guard.ts +68 -0
  65. package/src/metering/types.ts +25 -0
  66. package/src/onboard/apis-guru.ts +64 -0
  67. package/src/onboard/index.ts +4 -0
  68. package/src/onboard/manifest.ts +362 -0
  69. package/src/onboard/pipeline.ts +214 -0
  70. package/src/onboard/types.ts +72 -0
  71. package/src/proxy/__tests__/tool-registry.test.ts +93 -0
  72. package/src/proxy/tool-registry.ts +122 -0
  73. package/src/proxy.ts +12 -0
  74. package/src/registry/context7-backend.ts +93 -0
  75. package/src/registry/context7.ts +54 -0
  76. package/src/registry/d1-store.ts +242 -0
  77. package/src/registry/memory-store.ts +101 -0
  78. package/src/registry/openapi-compiler.ts +284 -0
  79. package/src/registry/resolver.ts +196 -0
  80. package/src/registry/rpc-compiler.ts +142 -0
  81. package/src/registry/skill-parser.ts +119 -0
  82. package/src/registry/skill-to-config.ts +239 -0
  83. package/src/registry/source-refresher.ts +83 -0
  84. package/src/registry/types.ts +129 -0
  85. package/src/registry/virtual-files.ts +76 -0
  86. package/src/testing/sqlite-d1.ts +64 -0
  87. package/src/testing.ts +2 -0
  88. package/src/tunnel/__tests__/cloudflare-provider.test.ts +255 -0
  89. package/src/tunnel/__tests__/tunnel.test.ts +542 -0
  90. package/src/tunnel/cloudflare-provider.ts +121 -0
  91. package/src/tunnel/memory-store.ts +30 -0
  92. package/src/tunnel/types.ts +28 -0
  93. package/test/credential/d1-vault.test.ts +127 -0
  94. package/test/credential/injection.test.ts +67 -0
  95. package/test/credential/memory-vault.test.ts +63 -0
  96. package/test/http/app.test.ts +300 -0
  97. package/test/http/byok-e2e.test.ts +240 -0
  98. package/test/http/byok.test.ts +115 -0
  99. package/test/http/credentials.test.ts +57 -0
  100. package/test/http/e2e.test.ts +260 -0
  101. package/test/integration/authenticated-apis.test.ts +185 -0
  102. package/test/integration/free-apis-e2e.test.ts +222 -0
  103. package/test/metering/d1-store.test.ts +82 -0
  104. package/test/metering/memory-store.test.ts +76 -0
  105. package/test/metering/pricing-guard.test.ts +108 -0
  106. package/test/onboard/apis-guru.test.ts +57 -0
  107. package/test/onboard/e2e.test.ts +70 -0
  108. package/test/onboard/pipeline.test.ts +318 -0
  109. package/test/onboard/real-apis.test.ts +483 -0
  110. package/test/registry/compilation-correctness.test.ts +132 -0
  111. package/test/registry/context7-backend.test.ts +88 -0
  112. package/test/registry/context7-e2e.test.ts +92 -0
  113. package/test/registry/context7.test.ts +73 -0
  114. package/test/registry/d1-store.test.ts +184 -0
  115. package/test/registry/integration.test.ts +129 -0
  116. package/test/registry/lazy-mount.test.ts +138 -0
  117. package/test/registry/memory-store.test.ts +171 -0
  118. package/test/registry/openapi-compiler.test.ts +267 -0
  119. package/test/registry/openapi-e2e.test.ts +154 -0
  120. package/test/registry/passthrough-e2e.test.ts +109 -0
  121. package/test/registry/resolver-peer.test.ts +299 -0
  122. package/test/registry/resolver.test.ts +228 -0
  123. package/test/registry/rpc-compiler.test.ts +112 -0
  124. package/test/registry/skill-parser.test.ts +151 -0
  125. package/test/registry/skill-to-config.test.ts +151 -0
  126. package/test/registry/skill-to-rpc-config.test.ts +142 -0
  127. package/test/registry/source-refresher.test.ts +90 -0
  128. package/test/registry/virtual-files.test.ts +96 -0
  129. package/tsconfig.json +4 -0
  130. package/tsup.config.ts +8 -0
@@ -0,0 +1,142 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { skillToRpcConfig } from "../../src/registry/skill-to-config.js";
3
+ import type { RpcSourceMeta } from "../../src/registry/types.js";
4
+
5
+ const EVM_META: RpcSourceMeta = {
6
+ rpcUrl: "https://rpc.ankr.com/eth",
7
+ convention: "evm",
8
+ resources: [
9
+ {
10
+ name: "blocks",
11
+ methods: { list: "eth_blockNumber", read: "eth_getBlockByNumber" },
12
+ },
13
+ {
14
+ name: "balances",
15
+ methods: { read: "eth_getBalance" },
16
+ },
17
+ ],
18
+ };
19
+
20
+ const CRUD_META: RpcSourceMeta = {
21
+ rpcUrl: "https://example.com/rpc",
22
+ convention: "crud",
23
+ resources: [
24
+ {
25
+ name: "users",
26
+ idField: "id",
27
+ methods: {
28
+ list: "user.list",
29
+ read: "user.get",
30
+ create: "user.create",
31
+ write: "user.update",
32
+ remove: "user.delete",
33
+ },
34
+ },
35
+ ],
36
+ };
37
+
38
+ const RAW_META: RpcSourceMeta = {
39
+ rpcUrl: "https://example.com/rpc",
40
+ convention: "raw",
41
+ resources: [
42
+ {
43
+ name: "info",
44
+ methods: { read: "getInfo" },
45
+ },
46
+ ],
47
+ };
48
+
49
+ describe("skillToRpcConfig", () => {
50
+ it("should return resources matching meta input", () => {
51
+ const { resources } = skillToRpcConfig(EVM_META);
52
+ expect(resources).toHaveLength(2);
53
+ expect(resources[0].name).toBe("blocks");
54
+ expect(resources[1].name).toBe("balances");
55
+ });
56
+
57
+ it("should rebuild RPC methods with params callbacks", () => {
58
+ const { resources } = skillToRpcConfig(EVM_META);
59
+ const blocks = resources[0];
60
+ expect(blocks.methods.list).toBeDefined();
61
+ expect(blocks.methods.read).toBeDefined();
62
+ expect(blocks.methods.list!.method).toBe("eth_blockNumber");
63
+ expect(blocks.methods.read!.method).toBe("eth_getBlockByNumber");
64
+ });
65
+
66
+ it("should generate evm params for read (hex encoding)", () => {
67
+ const { resources } = skillToRpcConfig(EVM_META);
68
+ const blocks = resources[0];
69
+ // numeric id → hex
70
+ const params = blocks.methods.read!.params({ id: "100" });
71
+ expect(params).toEqual(["0x64", "latest"]);
72
+ // hex id stays as-is
73
+ const params2 = blocks.methods.read!.params({ id: "0xabc" });
74
+ expect(params2).toEqual(["0xabc", "latest"]);
75
+ });
76
+
77
+ it("should generate evm params for list (no params for eth_blockNumber)", () => {
78
+ const { resources } = skillToRpcConfig(EVM_META);
79
+ const blocks = resources[0];
80
+ const params = blocks.methods.list!.params({});
81
+ expect(params).toEqual([]);
82
+ });
83
+
84
+ it("should add evm transforms for blocks (hex block number → recent block list)", () => {
85
+ const { resources } = skillToRpcConfig(EVM_META);
86
+ const blocks = resources[0];
87
+ expect(blocks.transform).toBeDefined();
88
+ expect(blocks.transform!.list).toBeDefined();
89
+
90
+ // eth_blockNumber returns a hex string → transform to last 10 block .json files
91
+ const result = blocks.transform!.list!("0xa") as string[];
92
+ expect(result).toHaveLength(10);
93
+ expect(result[0]).toBe("10.json");
94
+ expect(result[9]).toBe("1.json");
95
+ });
96
+
97
+ it("should add evm transforms for balances", () => {
98
+ const { resources } = skillToRpcConfig(EVM_META);
99
+ const balances = resources[1];
100
+ expect(balances.transform).toBeDefined();
101
+ expect(balances.transform!.read).toBeDefined();
102
+
103
+ const result = balances.transform!.read!("0xde0b6b3a7640000");
104
+ expect(result).toEqual({ wei: "0xde0b6b3a7640000", raw: "0xde0b6b3a7640000" });
105
+ });
106
+
107
+ it("should generate crud params correctly", () => {
108
+ const { resources } = skillToRpcConfig(CRUD_META);
109
+ const users = resources[0];
110
+
111
+ expect(users.methods.list!.params({})).toEqual([]);
112
+ expect(users.methods.read!.params({ id: "123" })).toEqual(["123"]);
113
+ expect(users.methods.create!.params({ data: { name: "test" } })).toEqual([{ name: "test" }]);
114
+ expect(users.methods.write!.params({ id: "123", data: { name: "updated" } })).toEqual(["123", { name: "updated" }]);
115
+ expect(users.methods.remove!.params({ id: "123" })).toEqual(["123"]);
116
+ });
117
+
118
+ it("should preserve idField from meta", () => {
119
+ const { resources } = skillToRpcConfig(CRUD_META);
120
+ expect(resources[0].idField).toBe("id");
121
+ });
122
+
123
+ it("should handle raw convention with pass-through params", () => {
124
+ const { resources } = skillToRpcConfig(RAW_META);
125
+ const info = resources[0];
126
+ expect(info.methods.read!.method).toBe("getInfo");
127
+ // With id
128
+ expect(info.methods.read!.params({ id: "abc" })).toEqual(["abc"]);
129
+ // With data
130
+ expect(info.methods.read!.params({ data: { x: 1 } })).toEqual([{ x: 1 }]);
131
+ // Empty
132
+ expect(info.methods.read!.params({})).toEqual([]);
133
+ });
134
+
135
+ it("should not add transforms for non-evm conventions", () => {
136
+ const { resources } = skillToRpcConfig(CRUD_META);
137
+ expect(resources[0].transform).toBeUndefined();
138
+
139
+ const { resources: rawResources } = skillToRpcConfig(RAW_META);
140
+ expect(rawResources[0].transform).toBeUndefined();
141
+ });
142
+ });
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { SourceRefresher } from "../../src/registry/source-refresher.js";
3
+ import { MemoryRegistryStore } from "../../src/registry/memory-store.js";
4
+ import type { ServiceRecord } from "../../src/registry/types.js";
5
+
6
+ function makeRecord(domain: string, overrides?: Partial<ServiceRecord>): ServiceRecord {
7
+ return {
8
+ domain,
9
+ name: overrides?.name ?? domain,
10
+ description: overrides?.description ?? `Service ${domain}`,
11
+ version: overrides?.version ?? "1.0",
12
+ roles: ["agent"],
13
+ skillMd: "---\nname: test\n---\n# Test\n\nTest service.\n",
14
+ endpoints: [],
15
+ isFirstParty: false,
16
+ createdAt: Date.now(),
17
+ updatedAt: Date.now(),
18
+ status: "active",
19
+ isDefault: true,
20
+ ...overrides,
21
+ };
22
+ }
23
+
24
+ describe("SourceRefresher", () => {
25
+ let store: MemoryRegistryStore;
26
+
27
+ beforeEach(() => {
28
+ store = new MemoryRegistryStore();
29
+ });
30
+
31
+ it("should not refresh without source config", async () => {
32
+ const record = makeRecord("test.com");
33
+ const refresher = new SourceRefresher(store);
34
+ expect(await refresher.shouldRefresh(record)).toBe(false);
35
+ });
36
+
37
+ it("should not refresh when interval has not elapsed", async () => {
38
+ const record = makeRecord("test.com", {
39
+ source: { type: "openapi", url: "https://test.com/spec.json", refreshInterval: 3600, lastRefresh: Date.now() },
40
+ });
41
+ const refresher = new SourceRefresher(store);
42
+ expect(await refresher.shouldRefresh(record)).toBe(false);
43
+ });
44
+
45
+ it("should refresh when interval has elapsed", async () => {
46
+ const record = makeRecord("test.com", {
47
+ source: { type: "openapi", url: "https://test.com/spec.json", refreshInterval: 1, lastRefresh: Date.now() - 2000 },
48
+ });
49
+ const refresher = new SourceRefresher(store);
50
+ expect(await refresher.shouldRefresh(record)).toBe(true);
51
+ });
52
+
53
+ it("should refresh openapi source", async () => {
54
+ const spec = { info: { title: "Updated", version: "2.0" }, paths: {} };
55
+ const mockFetch = async () => new Response(JSON.stringify(spec), { status: 200, headers: { "Content-Type": "application/json" } });
56
+
57
+ const record = makeRecord("test.com", {
58
+ source: { type: "openapi", url: "https://test.com/spec.json", refreshInterval: 1 },
59
+ });
60
+ await store.put("test.com", record);
61
+
62
+ const refresher = new SourceRefresher(store, mockFetch as any);
63
+ const updated = await refresher.refresh(record);
64
+ expect(updated?.name).toBe("Updated");
65
+ expect(updated?.source?.lastRefresh).toBeGreaterThan(0);
66
+ });
67
+
68
+ it("should refresh wellknown source", async () => {
69
+ const skillMd = '---\nname: "Refreshed"\nversion: "2.0"\nroles: [agent]\n---\n\n# Refreshed\n\nRefreshed service.\n';
70
+ const mockFetch = async () => new Response(skillMd, { status: 200 });
71
+
72
+ const record = makeRecord("test.com", {
73
+ source: { type: "wellknown", url: "https://test.com/.well-known/skill.md", refreshInterval: 1 },
74
+ });
75
+ await store.put("test.com", record);
76
+
77
+ const refresher = new SourceRefresher(store, mockFetch as any);
78
+ const updated = await refresher.refresh(record);
79
+ expect(updated?.name).toBe("Refreshed");
80
+ });
81
+
82
+ it("should return null for unknown source type", async () => {
83
+ const record = makeRecord("test.com", {
84
+ source: { type: "skillmd" },
85
+ });
86
+ const refresher = new SourceRefresher(store);
87
+ const result = await refresher.refresh(record);
88
+ expect(result).toBeNull();
89
+ });
90
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { VirtualFileBackend } from "../../src/registry/virtual-files.js";
3
+ import { MemoryRegistryStore } from "../../src/registry/memory-store.js";
4
+ import { parseSkillMd } from "../../src/registry/skill-parser.js";
5
+ import type { FsBackend } from "@nkmc/agent-fs";
6
+
7
+ const SKILL_MD = `---
8
+ name: "Test API"
9
+ gateway: nkmc
10
+ version: "1.0"
11
+ roles: [agent]
12
+ ---
13
+
14
+ # Test API
15
+
16
+ A test service.
17
+
18
+ ## API
19
+
20
+ ### List items
21
+
22
+ \`GET /api/items\` — free
23
+
24
+ ### Create order
25
+
26
+ \`POST /api/orders\` — 0.05 USDC / call
27
+ `;
28
+
29
+ class MockBackend implements FsBackend {
30
+ async list(_path: string): Promise<string[]> { return ["items/", "_api/"]; }
31
+ async read(_path: string): Promise<unknown> { return { mock: true }; }
32
+ async write(_path: string, _data: unknown): Promise<{id: string}> { return { id: "1" }; }
33
+ async remove(_path: string): Promise<void> {}
34
+ async search(_path: string, _pattern: string): Promise<unknown[]> { return []; }
35
+ }
36
+
37
+ describe("VirtualFileBackend", () => {
38
+ let store: MemoryRegistryStore;
39
+ let inner: MockBackend;
40
+ let backend: VirtualFileBackend;
41
+
42
+ beforeEach(async () => {
43
+ store = new MemoryRegistryStore();
44
+ const record = parseSkillMd("test-api.com", SKILL_MD);
45
+ await store.put("test-api.com", record);
46
+
47
+ inner = new MockBackend();
48
+ backend = new VirtualFileBackend({ inner, domain: "test-api.com", store });
49
+ });
50
+
51
+ it("should append virtual files to root listing", async () => {
52
+ const entries = await backend.list("/");
53
+ expect(entries).toContain("items/");
54
+ expect(entries).toContain("_api/");
55
+ expect(entries).toContain("_pricing.json");
56
+ expect(entries).toContain("_versions.json");
57
+ });
58
+
59
+ it("should not append virtual files to non-root listing", async () => {
60
+ const entries = await backend.list("/items");
61
+ expect(entries).not.toContain("_pricing.json");
62
+ });
63
+
64
+ it("should read _pricing.json", async () => {
65
+ const data = await backend.read("/_pricing.json") as any;
66
+ expect(data.domain).toBe("test-api.com");
67
+ expect(data.endpoints.length).toBeGreaterThan(0);
68
+ expect(data.endpoints[0].pricing).toBeDefined();
69
+ });
70
+
71
+ it("should read _versions.json", async () => {
72
+ const data = await backend.read("/_versions.json") as any;
73
+ expect(data.domain).toBe("test-api.com");
74
+ expect(data.versions).toHaveLength(1);
75
+ expect(data.versions[0].version).toBe("1.0");
76
+ });
77
+
78
+ it("should delegate non-virtual reads to inner backend", async () => {
79
+ const data = await backend.read("/items/1.json");
80
+ expect(data).toEqual({ mock: true });
81
+ });
82
+
83
+ it("should delegate write to inner backend", async () => {
84
+ const result = await backend.write("/items/", { name: "test" });
85
+ expect(result.id).toBe("1");
86
+ });
87
+
88
+ it("should delegate remove to inner backend", async () => {
89
+ await expect(backend.remove("/items/1")).resolves.toBeUndefined();
90
+ });
91
+
92
+ it("should delegate search to inner backend", async () => {
93
+ const results = await backend.search("/items", "test");
94
+ expect(results).toEqual([]);
95
+ });
96
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "include": ["src"]
4
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts", "src/http.ts", "src/proxy.ts", "src/testing.ts"],
5
+ format: ["esm", "cjs"],
6
+ dts: true,
7
+ clean: true,
8
+ });