{"_id":"@axiome974/app-hub-client","_rev":"2-7737d498113a400e9036572971f40607","name":"@axiome974/app-hub-client","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.2":{"name":"@axiome974/app-hub-client","version":"0.1.2","keywords":["axiome","app-hub","client","sdk","realtime","mercure"],"author":{"name":"Axiome"},"license":"MIT","_id":"@axiome974/app-hub-client@0.1.2","maintainers":[{"name":"axiome974","email":"damien.carreno@gmail.com"}],"dist":{"shasum":"ae3f8180ea19b3805d9a89422cb31bb80f391122","tarball":"https://registry.npmjs.org/@axiome974/app-hub-client/-/app-hub-client-0.1.2.tgz","fileCount":9,"integrity":"sha512-4G4B7une3RLs4DKjtV/x6xdKZHYFKCwitFR90jRd0YN0Nuo9377s3QB+4MseJNXcbEgowfwKXeOcov27WSOzjA==","signatures":[{"sig":"MEUCIQCC2nAz84zjeHfH5Qk2tnq8FmtKEdvL3ndGm+gBS8QvdQIgZn8yaqv7LOQ1gsBHHKXbsrg9ZviwC1b06f+/Ao9eG7A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":115741},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"4d9ddcabbf0fc7f806cd31d4b3b2ee5e9e8d8806","scripts":{"dev":"tsup --watch","lint":"eslint .","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"axiome974","email":"damien.carreno@gmail.com"},"_npmVersion":"10.9.3","description":"JS/TypeScript client for Axiome App Hub (storage, sessions, realtime).","directories":{},"sideEffects":false,"_nodeVersion":"22.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","eslint":"^9.18.0","vitest":"^3.0.4","happy-dom":"^16.3.0","@eslint/js":"^9.18.0","typescript":"^5.7.3","@types/node":"^22.10.7","typescript-eslint":"^8.20.0"},"_npmOperationalInternal":{"tmp":"tmp/app-hub-client_0.1.2_1784640454098_0.8186121188252544","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@axiome974/app-hub-client","version":"0.2.0","description":"JS/TypeScript client for Axiome App Hub (storage, sessions, realtime).","type":"module","license":"MIT","author":{"name":"Axiome"},"keywords":["axiome","app-hub","client","sdk","realtime","mercure"],"sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"engines":{"node":">=18"},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","lint":"eslint .","test":"vitest run","test:watch":"vitest","prepublishOnly":"pnpm run build"},"devDependencies":{"@eslint/js":"^9.18.0","@types/node":"^22.10.7","eslint":"^9.18.0","happy-dom":"^16.3.0","tsup":"^8.3.5","typescript":"^5.7.3","typescript-eslint":"^8.20.0","vitest":"^3.0.4"},"publishConfig":{"access":"public"},"_id":"@axiome974/app-hub-client@0.2.0","gitHead":"8bc7a7040923837a23ba63c3b79f92168c3441fd","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-LnLqJrDtlz+H59rqaWOLMNL8ITqgULuYqVkWT8C01NxykrOZ1/3WOj0JtFB4k1DkeO21haPEXcYUToV18tIdcw==","shasum":"9e5de369410f3820a43be319f804e9ef690c1fda","tarball":"https://registry.npmjs.org/@axiome974/app-hub-client/-/app-hub-client-0.2.0.tgz","fileCount":9,"unpackedSize":120804,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEZSD8krzZF+GOpBJBcRYg2olZ94c9UvyoQolCy+ZNJrAiEA8mvTP3EmBMV1Kg373Ze5MUqI83MwjajX7IY1374msds="}]},"_npmUser":{"name":"axiome974","email":"damien.carreno@gmail.com"},"directories":{},"maintainers":[{"name":"axiome974","email":"damien.carreno@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/app-hub-client_0.2.0_1785221314268_0.38552733191741506"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-21T13:27:33.947Z","modified":"2026-07-28T06:48:34.575Z","0.1.2":"2026-07-21T13:27:34.256Z","0.2.0":"2026-07-28T06:48:34.429Z"},"author":{"name":"Axiome"},"license":"MIT","keywords":["axiome","app-hub","client","sdk","realtime","mercure"],"description":"JS/TypeScript client for Axiome App Hub (storage, sessions, realtime).","maintainers":[{"name":"axiome974","email":"damien.carreno@gmail.com"}],"readme":"# @axiome974/app-hub-client\n\nJS/TypeScript client for [Axiome App Hub](./docs/CLAUDE-INTEGRATION-FRONT.md): a shared backend for\nserverless front-end applications (mini-games, tools, demos). Each hub deployment has its\nown address, so this lib hardcodes no URL — you provide the address of the instance you\nwere given access to.\n\n## Installation\n\n```bash\nnpm install @axiome974/app-hub-client\n```\n\n## Configuration\n\n```ts\nimport { AppHubClient } from '@axiome974/app-hub-client';\n\nconst hub = new AppHubClient({\n  apiUrl: import.meta.env.VITE_HUB_API_URL, // e.g. http://localhost:8090/api\n  token: import.meta.env.VITE_HUB_TOKEN,    // e.g. axm_3f9c…\n});\n```\n\n`apiUrl` and `token` are always injected explicitly by the consuming app (no implicit\nread of `process.env`/`import.meta.env` inside the lib, to stay bundler-agnostic). The\ntoken is not a strong secret: real protection comes from the origin rules configured on\nthe hub side.\n\n## Storage\n\nThe vault holds a single JSON document. `set()` **replaces** the whole payload — no\nmerge: the pattern is always read → modify in memory → rewrite.\n\n```ts\nconst payload = await hub.storage.get(); // [] until something is written\nawait hub.storage.set({ scores: [{ name: 'Alice', points: 42 }] });\n```\n\n## Sessions and participants\n\n```ts\n// Create a session (the caller becomes admin + first participant)\nconst { session, participant, ownerSecret } = await hub.sessions.create({\n  ownerId: 'client-abc',\n});\n\n// Join an existing session\nconst { participant: bob } = await hub.sessions.join(session.id, {\n  pseudo: 'Bob',\n  payload: { score: 0 },\n});\n\n// Keep `participant.secret` (or `ownerSecret` for the admin): the hub only stores\n// a hash of it, there is no recovery.\nconst auth = { participantSecret: bob.secret };\n// or, admin side: const auth = { ownerSecret };\n\nawait hub.sessions.get(session.id, auth);\nawait hub.sessions.update(session.id, { started: true }, { ownerSecret }); // shared payload, admin\nawait hub.sessions.participants.update(session.id, bob.id, { score: 10 }, auth); // personal payload\nawait hub.sessions.participants.remove(session.id, bob.id, auth); // leave / kick\n\n// Transfer the admin role to another participant (admin only).\n// The previous ownerSecret is invalidated immediately; relay the new one to Bob yourself.\nconst { ownerSecret: bobOwnerSecret } = await hub.sessions.transferOwner(\n  session.id,\n  { participantId: bob.id, ownerId: 'bob-client-id' },\n  { ownerSecret },\n);\n```\n\n## Realtime\n\n`hub.sessions.realtime.connection(sessionId, auth)` manages the SSE stream and\n**automatically renews the ticket before it expires** (the hub issues tickets valid for\n`expiresIn` seconds, typically 3600).\n\n```ts\nconst connection = hub.sessions.realtime.connection(session.id, auth);\nawait connection.connect();\n\n// All messages, whatever the channel/event\nconst unsubscribeAll = connection.onMessage((message) => {\n  console.log(message.channel, message.event, message.data, message.from);\n});\n\n// Only messages with this event name\nconst unsubscribeChat = connection.on('chat.message', (message) => {\n  renderChatLine(message.data);\n});\n\nconnection.onError((error) => console.error('realtime stream error', error));\n\n// The sender also receives their own message via the SSE echo: do not render\n// locally on send, wait for that echo.\nawait connection.publish({ channel: 'public', event: 'chat.message', data: { text: 'hello' } });\n\n// When done (component unmount, end of game…)\nconnection.close();\n```\n\nFor low-level usage (ticket / publish without connection management),\n`hub.sessions.realtime` also exposes `subscribeTicket(sessionId, auth)` and\n`publish(sessionId, auth, input)`.\n\n## Error handling\n\nEvery hub error response is mapped to a typed class, all extending `HubApiError`\n(`status`, `message` already extracted from the `error` field, raw `body`):\n\n| Class | Status | Typical cases |\n|---|---|---|\n| `HubUnauthorizedError` | 401 | `X-API-Key` missing/empty |\n| `HubForbiddenError` | 403 | origin refused, read-only vault, channel disabled, missing secret |\n| `HubNotFoundError` | 404 | token/vault/session/participant not found |\n| `HubConflictError` | 409 | session full |\n| `HubGoneError` | 410 | session expired |\n| `HubPayloadTooLargeError` | 413 | body > 64 KB |\n| `HubValidationError` | 422 | invalid JSON, scalar payload, invalid field |\n| `HubRateLimitError` | 429 | too many password attempts (`retryAfter` in seconds) |\n\nA network failure (hub unreachable) throws `HubNetworkError` instead of one of these\nclasses. Note: the hub itself returns its `error` messages in French.\n\n```ts\nimport { HubGoneError, HubRateLimitError } from '@axiome974/app-hub-client';\n\ntry {\n  await hub.sessions.join(sessionId, { pseudo: 'Bob' });\n} catch (error) {\n  if (error instanceof HubGoneError) {\n    // session expired → create a new one\n  } else if (error instanceof HubRateLimitError) {\n    // retry after error.retryAfter seconds\n  }\n}\n```\n\n## Development\n\n```bash\npnpm install\npnpm test         # vitest\npnpm typecheck    # tsc --noEmit\npnpm lint         # eslint\npnpm build        # tsup → dist/ (ESM + CJS + .d.ts)\n```\n\nSee [`docs/CLAUDE-INTEGRATION-FRONT.md`](./docs/CLAUDE-INTEGRATION-FRONT.md) for the full documentation of the hub\nAPI this lib wraps.\n","readmeFilename":"README.md"}