{"_id":"@canon-solana/sdk","_rev":"2-8485c07419288dcb3afc1bf1fb79b648","name":"@canon-solana/sdk","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@canon-solana/sdk","version":"0.1.0","keywords":["canon","solana","state","reactive","websocket"],"author":{"name":"Canon"},"license":"MIT","_id":"@canon-solana/sdk@0.1.0","maintainers":[{"name":"woodfish","email":"jasonholt2002@gmail.com"}],"dist":{"shasum":"f6ccc35a2555dc350642ec988ced6195cf504b72","tarball":"https://registry.npmjs.org/@canon-solana/sdk/-/sdk-0.1.0.tgz","fileCount":14,"integrity":"sha512-oo4In7mWAfcsgsToZWveFqfRUk/0KXfimkMrMxW2uCM7zlu+dJUybn0b8W6kmmHjUJJQobgG1QGyc9lvXwzWtg==","signatures":[{"sig":"MEQCIADxyCebg2BuC+m0aRSsyYN8qlY3jYekt11yD0R8TgtpAiASdiGAfCo1YsG2Qpq7knyg2qPLfMzGQI1Guq6YH1wEhA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":24604},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"7865f8c611e34e210b196b208f9962378ea72efd","scripts":{"build":"tsc","watch":"tsc --watch","prepublishOnly":"npm run build"},"_npmUser":{"name":"woodfish","email":"jasonholt2002@gmail.com"},"_npmVersion":"11.4.2","description":"TypeScript SDK for Canon - Reactive state streams for Solana","directories":{},"_nodeVersion":"24.3.0","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.3.3","@types/node":"^20.10.0"},"peerDependencies":{"ws":"^8.0.0"},"peerDependenciesMeta":{"ws":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.0_1768359619830_0.8063093486118809","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@canon-solana/sdk","version":"0.1.2","description":"TypeScript SDK for Canon - Reactive state streams for Solana","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"tsc","watch":"tsc --watch","prepublishOnly":"npm run build"},"keywords":["canon","solana","state","reactive","websocket"],"author":{"name":"Canon"},"license":"MIT","devDependencies":{"typescript":"^5.3.3","@types/node":"^20.10.0"},"peerDependencies":{"ws":"^8.0.0"},"peerDependenciesMeta":{"ws":{"optional":true}},"_id":"@canon-solana/sdk@0.1.2","gitHead":"3d7c3d0832fa6d202c0897415d67fe0d745c27a8","_nodeVersion":"24.3.0","_npmVersion":"11.4.2","dist":{"integrity":"sha512-HsDhVcZOPyR2UjZlv2XV0VqZqdFqEksPzZEk6y4JSTZJlX/ND0drKkbyLRQkclP8GDqVo8Npiq2U3eEBaDym8w==","shasum":"d9b176be4c3c9089b88e87d567cd142e351fb732","tarball":"https://registry.npmjs.org/@canon-solana/sdk/-/sdk-0.1.2.tgz","fileCount":14,"unpackedSize":24646,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDwqTNwrmv7EtnLM2X+Gy1HgZs/vgZxg5A8qIWu6mKo8gIgZTzjVYtxR7wbEPU9Sq18kpRgbY3Ng7dRB7rvA+TH+r8="}]},"_npmUser":{"name":"woodfish","email":"jasonholt2002@gmail.com"},"directories":{},"maintainers":[{"name":"woodfish","email":"jasonholt2002@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.1.2_1768360051459_0.866829198175171"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-14T03:00:19.743Z","modified":"2026-01-14T03:07:31.709Z","0.1.0":"2026-01-14T03:00:19.987Z","0.1.2":"2026-01-14T03:07:31.597Z"},"author":{"name":"Canon"},"license":"MIT","keywords":["canon","solana","state","reactive","websocket"],"description":"TypeScript SDK for Canon - Reactive state streams for Solana","maintainers":[{"name":"woodfish","email":"jasonholt2002@gmail.com"}],"readme":"# @canon-solana/sdk\n\nTypeScript SDK for [Canon](https://usecanon.dev) - Reactive state streams for Solana programs.\n\n## Installation\n\n```bash\nnpm install @canon-solana/sdk\n```\n\nFor Node.js environments, also install the WebSocket peer dependency:\n\n```bash\nnpm install ws\n```\n\n## Quick Start\n\n```typescript\nimport { createCanonClient } from \"@canon-solana/sdk\";\n\n// Create client\nconst canon = createCanonClient({\n  endpoint: \"https://api.usecanon.dev\",\n  projectId: \"your-project-id\",\n  apiKey: \"your-api-key\",\n});\n\n// Get full state\nconst state = await canon.state.get(\"/\");\nconsole.log(\"Full state:\", state);\n\n// Get value at specific path\nconst balance = await canon.state.get(\"/users/9x.../balance\", {\n  format: \"value\",\n});\nconsole.log(\"Balance:\", balance);\n\n// Subscribe to real-time updates\nconst unsubscribe = canon.state.subscribe((message) => {\n  if (message.type === \"snapshot\") {\n    console.log(\"Initial snapshot:\", message.value);\n  } else if (message.type === \"update\") {\n    console.log(\"State updated:\", message);\n  }\n});\n\n// Later: unsubscribe\nunsubscribe();\n```\n\n## API Reference\n\n### `createCanonClient(config)`\n\nCreates a new Canon client instance.\n\n**Parameters:**\n\n- `config.endpoint` (string, required): API endpoint URL\n- `config.projectId` (string, required): Your Canon project ID\n- `config.apiKey` (string, required): Your API key for authentication\n\n**Returns:** `CanonClient`\n\n### `canon.state.get(path?, opts?)`\n\nFetches the current state value.\n\n**Parameters:**\n\n- `path` (string, optional): JSON path to fetch (default: `\"/\"` for full state)\n- `opts.format` (\"envelope\" | \"value\", optional): Response format\n  - `\"envelope\"`: Includes metadata (cursor, version, timestamp)\n  - `\"value\"`: Returns raw value only (default)\n\n**Returns:** `Promise<any>`\n\n**Examples:**\n\n```typescript\n// Get full state\nconst fullState = await canon.state.get(\"/\");\n\n// Get nested value\nconst userBalance = await canon.state.get(\"/users/abc123/balance\");\n\n// Get with envelope (includes metadata)\nconst envelope = await canon.state.get(\"/\", { format: \"envelope\" });\nconsole.log(envelope.value); // state value\nconsole.log(envelope.cursor); // { slot: 12345 }\nconsole.log(envelope.updated_at); // \"2024-01-15T10:30:00Z\"\n```\n\n### `canon.state.subscribe(callback, opts?)`\n\nSubscribes to real-time state updates via WebSocket.\n\n**Parameters:**\n\n- `callback` (function): Called when state changes\n  - Receives `StateMessage` (either `SnapshotMessage` or `UpdateMessage`)\n- `opts.reconnect` (boolean, optional): Auto-reconnect on disconnect (default: `true`)\n- `opts.reconnectMaxDelayMs` (number, optional): Max reconnection delay (default: `30000`)\n\n**Returns:** `UnsubscribeFn` - Call this function to unsubscribe\n\n**Examples:**\n\n```typescript\n// Basic subscription\nconst unsub = canon.state.subscribe((msg) => {\n  console.log(\"State update:\", msg);\n});\n\n// With options\nconst unsub = canon.state.subscribe(\n  (msg) => {\n    if (msg.type === \"snapshot\") {\n      // Initial or full state snapshot\n      console.log(\"Snapshot at slot\", msg.cursor?.slot);\n      console.log(\"State:\", msg.value);\n    } else if (msg.type === \"update\") {\n      // Incremental update\n      if (msg.patch) {\n        // JSON Patch operations\n        console.log(\"Patch:\", msg.patch);\n      } else if (msg.snapshot) {\n        // Fallback full snapshot\n        console.log(\"Snapshot:\", msg.snapshot);\n      }\n    }\n  },\n  {\n    reconnect: true,\n    reconnectMaxDelayMs: 30000,\n  }\n);\n\n// Don't forget to unsubscribe when done\nunsub();\n```\n\n### `canon.state.select(value, path)`\n\nClient-side helper to traverse a state object by path.\n\n**Parameters:**\n\n- `value` (any): State object to traverse\n- `path` (string | string[]): Path as string (`\"/users/abc/balance\"`) or array (`[\"users\", \"abc\", \"balance\"]`)\n\n**Returns:** `any` - Value at path, or `undefined` if not found\n\n**Examples:**\n\n```typescript\nconst state = await canon.state.get(\"/\");\n\n// Select nested value\nconst balance = canon.state.select(state, \"/users/abc123/balance\");\n// or\nconst balance = canon.state.select(state, [\"users\", \"abc123\", \"balance\"]);\n```\n\n### Message Types\n\n#### `SnapshotMessage`\n\nFull state snapshot:\n\n```typescript\n{\n  type: \"snapshot\";\n  path: \"/\";\n  value: any; // State value\n  cursor?: { slot: number };\n  reducer_version?: string | null;\n  updated_at?: string; // RFC 3339 timestamp\n}\n```\n\n#### `UpdateMessage`\n\nIncremental state update:\n\n```typescript\n{\n  type: \"update\";\n  cursor?: { slot: number };\n  updated_at?: string;\n  patch?: PatchOperation[]; // JSON Patch operations\n  snapshot?: any; // Fallback full snapshot\n}\n```\n\n## Environment Support\n\n- ✅ Modern browsers (using native `fetch` and `WebSocket`)\n- ✅ Node.js 18+ (install `ws` peer dependency for WebSocket support)\n- ✅ Edge runtimes (Cloudflare Workers, Vercel Edge Functions, etc.)\n\n## Error Handling\n\nThe SDK throws specific error types for different scenarios:\n\n- `ApiError`: API request failed\n- `WebSocketError`: WebSocket connection error\n- `PathError`: Invalid path traversal\n- `NotImplementedError`: Feature not yet available\n\n```typescript\nimport { ApiError, WebSocketError } from \"@canon-solana/sdk\";\n\ntry {\n  const state = await canon.state.get(\"/nonexistent\");\n} catch (error) {\n  if (error instanceof ApiError) {\n    console.error(\"API error:\", error.statusCode, error.message);\n  }\n}\n```\n\n## Path Syntax\n\nPaths use JSON Pointer-like syntax:\n\n- `/` - Root (full state)\n- `/users` - Top-level key\n- `/users/abc123` - Nested key\n- `/users/abc123/balance` - Deep nesting\n- `/items/0` - Array index\n\n**Limitations (v1):**\n\n- No escaping of special characters (`/`, `~`)\n- Segments are treated as literal keys or array indices\n\n## State Guarantees\n\nCanon guarantees:\n\n- ✅ State is **always derived** from reducers processing Solana events\n- ✅ No arbitrary state writes - state changes only through reducers\n- ✅ Large integers are preserved as strings (no precision loss)\n- ✅ State is versioned by Solana slot number\n- ✅ Updates are real-time and consistent\n\n## License\n\nMIT\n","readmeFilename":"README.md"}