@aithos/sdk 0.1.0-alpha.4 → 0.1.0-alpha.41

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 (80) hide show
  1. package/README.md +211 -7
  2. package/dist/src/apps.d.ts +155 -0
  3. package/dist/src/apps.js +288 -0
  4. package/dist/src/assets.d.ts +207 -0
  5. package/dist/src/assets.js +533 -0
  6. package/dist/src/auth-api.d.ts +138 -0
  7. package/dist/src/auth-api.js +168 -0
  8. package/dist/src/auth.d.ts +536 -119
  9. package/dist/src/auth.js +1207 -152
  10. package/dist/src/compute.d.ts +251 -9
  11. package/dist/src/compute.js +293 -16
  12. package/dist/src/data-schema-contacts-v1.d.ts +14 -0
  13. package/dist/src/data-schema-contacts-v1.js +28 -0
  14. package/dist/src/data.d.ts +153 -0
  15. package/dist/src/data.js +670 -0
  16. package/dist/src/endpoints.d.ts +9 -0
  17. package/dist/src/endpoints.js +5 -0
  18. package/dist/src/ethos.d.ts +202 -1
  19. package/dist/src/ethos.js +821 -16
  20. package/dist/src/index.d.ts +18 -6
  21. package/dist/src/index.js +39 -6
  22. package/dist/src/internal/delegate-bundle.d.ts +18 -0
  23. package/dist/src/internal/delegate-bundle.js +94 -0
  24. package/dist/src/internal/delegate-state.d.ts +45 -0
  25. package/dist/src/internal/delegate-state.js +120 -0
  26. package/dist/src/internal/envelope.d.ts +77 -0
  27. package/dist/src/internal/envelope.js +154 -0
  28. package/dist/src/internal/owner-signers.d.ts +78 -0
  29. package/dist/src/internal/owner-signers.js +179 -0
  30. package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
  31. package/dist/src/internal/protocol-client-bridge.js +20 -0
  32. package/dist/src/internal/recovery-file.d.ts +29 -0
  33. package/dist/src/internal/recovery-file.js +98 -0
  34. package/dist/src/internal/signer.d.ts +59 -0
  35. package/dist/src/internal/signer.js +86 -0
  36. package/dist/src/key-store.d.ts +128 -0
  37. package/dist/src/key-store.js +244 -0
  38. package/dist/src/mandates.d.ts +163 -1
  39. package/dist/src/mandates.js +286 -8
  40. package/dist/src/react/AithosAsset.d.ts +66 -0
  41. package/dist/src/react/AithosAsset.js +67 -0
  42. package/dist/src/react/context.d.ts +29 -0
  43. package/dist/src/react/context.js +31 -0
  44. package/dist/src/react/index.d.ts +28 -0
  45. package/dist/src/react/index.js +30 -0
  46. package/dist/src/react/use-aithos-asset.d.ts +39 -0
  47. package/dist/src/react/use-aithos-asset.js +118 -0
  48. package/dist/src/sdk.d.ts +46 -3
  49. package/dist/src/sdk.js +49 -23
  50. package/dist/src/wallet.d.ts +4 -6
  51. package/dist/src/wallet.js +18 -8
  52. package/dist/src/web.d.ts +279 -0
  53. package/dist/src/web.js +186 -0
  54. package/dist/test/auth-j3.test.d.ts +2 -0
  55. package/dist/test/auth-j3.test.js +391 -0
  56. package/dist/test/compute-delegate-path.test.d.ts +2 -0
  57. package/dist/test/compute-delegate-path.test.js +183 -0
  58. package/dist/test/compute.test.js +26 -11
  59. package/dist/test/endpoints.test.js +20 -1
  60. package/dist/test/envelope.test.d.ts +2 -0
  61. package/dist/test/envelope.test.js +318 -0
  62. package/dist/test/ethos-first-edition.test.d.ts +2 -0
  63. package/dist/test/ethos-first-edition.test.js +248 -0
  64. package/dist/test/ethos.test.d.ts +2 -0
  65. package/dist/test/ethos.test.js +219 -0
  66. package/dist/test/key-store.test.d.ts +2 -0
  67. package/dist/test/key-store.test.js +161 -0
  68. package/dist/test/mandates-compute.test.d.ts +2 -0
  69. package/dist/test/mandates-compute.test.js +256 -0
  70. package/dist/test/mandates.test.d.ts +2 -0
  71. package/dist/test/mandates.test.js +93 -0
  72. package/dist/test/sdk.test.js +70 -30
  73. package/dist/test/signer.test.d.ts +2 -0
  74. package/dist/test/signer.test.js +117 -0
  75. package/dist/test/signup-bootstrap.test.d.ts +2 -0
  76. package/dist/test/signup-bootstrap.test.js +311 -0
  77. package/dist/test/wallet.test.js +20 -9
  78. package/dist/test/web.test.d.ts +2 -0
  79. package/dist/test/web.test.js +270 -0
  80. package/package.json +18 -3
@@ -1,13 +1,291 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright 2026 Mathieu Colla
3
- // Mandates namespace — re-exports of mandate mint/sign primitives.
3
+ // `sdk.mandates` namespace — owner-side mandate lifecycle.
4
4
  //
5
- // A mandate is a signed delegation bundle that grants an app DID the right
6
- // to act on the user's behalf within a scoped set of operations (read
7
- // scopes, compute spend cap, TTL). Mandates are required by the compute
8
- // proxy on every Bedrock call.
5
+ // Three verbs:
6
+ // create(input) → mints + publishes + returns the
7
+ // shareable .aithos-delegate.json bundle
8
+ // list() → mandates the owner has issued
9
+ // revoke(mandateId) → publishes a §4.2 revocation
9
10
  //
10
- // See `@aithos/protocol-client/src/mandate-mint.ts` for the canonical
11
- // implementation; this namespace re-exports the public surface verbatim.
12
- export { mintDelegateBundle, signAndPublishMandate, MintError, DEFAULT_READ_SCOPES, TTL_PRESETS, } from "@aithos/protocol-client";
11
+ // Capability boundary: every method requires an owner signed in. We
12
+ // reach the OwnerSigners through `auth._getOwnerSigners()` and project
13
+ // to a `StoredIdentity` for protocol-client interop. When
14
+ // protocol-client gains a Signer-shaped API the projection step
15
+ // disappears.
16
+ import { buildSignedEnvelope, mintDelegateBundle, readRpc, } from "@aithos/protocol-client";
17
+ import { ownerKeyPair } from "./internal/protocol-client-bridge.js";
18
+ import { AithosSDKError } from "./types.js";
19
+ /**
20
+ * The opt-in scope that authorizes a delegate to spend the subject's
21
+ * compute credits via the Aithos compute proxy. Mirror of
22
+ * `COMPUTE_INVOKE_SCOPE` in `@aithos/protocol-core` v0.4.0.
23
+ *
24
+ * The SDK's `mandates.create()` injects this scope automatically when
25
+ * the caller passes a `compute` namespace, and refuses to mint a
26
+ * mandate where the caller put it directly into `scopes` — this is
27
+ * what makes "compute is a separate, conscious decision" hold at the
28
+ * API surface.
29
+ */
30
+ export const COMPUTE_INVOKE_SCOPE = "compute.invoke";
31
+ export class MandatesNamespace {
32
+ #deps;
33
+ constructor(deps) {
34
+ this.#deps = deps;
35
+ }
36
+ /**
37
+ * Mint, sign, publish, and package a fresh delegate bundle. The
38
+ * grantee's keypair is generated inside this call and never
39
+ * persisted on the owner's machine — the seed flows out via the
40
+ * returned Blob and only via that Blob.
41
+ */
42
+ async create(input) {
43
+ const owner = this.#requireOwner();
44
+ // A mandate must carry at least one capability — either ethos
45
+ // scopes, the compute namespace, or both. A "compute-only" mandate
46
+ // (`scopes: []` + `compute: { ... }`) is legitimate: it gives the
47
+ // grantee no access to the subject's ethos data, only the right
48
+ // to spend a bounded amount of compute credits in their name.
49
+ // Useful for creative assistants, brainstorming agents, etc.
50
+ if (input.scopes.length === 0 && input.compute === undefined) {
51
+ throw new AithosSDKError("mandates_invalid_scopes", "scopes must be a non-empty list (or pass `compute` for a compute-only mandate)");
52
+ }
53
+ if (input.ttlSeconds <= 0) {
54
+ throw new AithosSDKError("mandates_invalid_ttl", "ttlSeconds must be > 0");
55
+ }
56
+ // Forbid `compute.invoke` smuggled in via `scopes[]`. The whole
57
+ // point of the dedicated `compute` namespace is that adding token-
58
+ // spending capability requires a typed, named, reviewable input —
59
+ // not a string lost in a generic list. Type-checking can't catch
60
+ // this (the union doesn't include the literal, but callers can
61
+ // up-cast); the runtime check is what holds.
62
+ if (input.scopes.some((s) => s === COMPUTE_INVOKE_SCOPE)) {
63
+ throw new AithosSDKError("mandates_invalid_scopes", `Pass token-spending capability via the dedicated 'compute' namespace, ` +
64
+ `not by adding "${COMPUTE_INVOKE_SCOPE}" to scopes[]. The namespace forces ` +
65
+ `an explicit budget and is what a consent UI reviews.`);
66
+ }
67
+ // Validate + project the compute namespace if present, then derive
68
+ // the final scopes/constraints to send to the protocol layer.
69
+ const computeProjection = projectCompute(input.compute);
70
+ const projectedScopes = [...input.scopes];
71
+ if (computeProjection) {
72
+ projectedScopes.push(COMPUTE_INVOKE_SCOPE);
73
+ }
74
+ const actorSphere = input.actorSphere ?? defaultSphereFromScopes(input.scopes);
75
+ const ownerStored = owner._unsafeStoredIdentity();
76
+ const result = await mintDelegateBundle({
77
+ owner: ownerStored,
78
+ granteeId: input.granteeId,
79
+ ...(input.granteeLabel ? { granteeLabel: input.granteeLabel } : {}),
80
+ actorSphere,
81
+ scopes: projectedScopes,
82
+ ttlSeconds: input.ttlSeconds,
83
+ // protocol-client v0.1.0-alpha.11 ships MandateConstraints without
84
+ // the `compute` field; the wire format accepts it though (the
85
+ // canonicalizer just serializes whatever's in the object). We
86
+ // up-cast through `unknown` to bypass the structural check until
87
+ // protocol-client picks up protocol-core 0.4.0 types.
88
+ ...(computeProjection
89
+ ? {
90
+ constraints: {
91
+ compute: computeProjection,
92
+ },
93
+ }
94
+ : {}),
95
+ ...(input.notBefore ? { notBefore: input.notBefore } : {}),
96
+ });
97
+ const mandate = result.mandate;
98
+ return {
99
+ mandateId: mandate.id,
100
+ subjectDid: mandate.subject_did,
101
+ granteeId: input.granteeId,
102
+ scopes: projectedScopes,
103
+ expiresAt: mandate.not_after ?? null,
104
+ bundle: result.bundleBlob,
105
+ filename: `aithos-delegate-${mandate.id}.json`,
106
+ };
107
+ }
108
+ /**
109
+ * List mandates issued by the signed-in owner. Pages through
110
+ * `aithos.list_mandates` until exhausted (or until 5 pages have
111
+ * been crawled — an owner with more than 1000 active mandates is
112
+ * out of scope today).
113
+ */
114
+ async list() {
115
+ const owner = this.#requireOwner();
116
+ const out = [];
117
+ let cursor;
118
+ for (let i = 0; i < 5; i++) {
119
+ const page = await readRpc("aithos.list_mandates", {
120
+ issuer_did: owner.did,
121
+ limit: 200,
122
+ ...(cursor ? { cursor } : {}),
123
+ });
124
+ for (const it of page.items) {
125
+ out.push(toOwnedMandate(it));
126
+ }
127
+ if (!page.next_cursor)
128
+ break;
129
+ cursor = page.next_cursor;
130
+ }
131
+ return out;
132
+ }
133
+ /**
134
+ * Publish a §4.2 revocation for `mandateId`. The mandate stops
135
+ * authorizing future actions (artifacts dated before `revoked_at`
136
+ * remain valid — revocation is not retroactive).
137
+ *
138
+ * Server-side: handled by `aithos.publish_revocation`. The envelope
139
+ * is signed by the owner's `#public` sphere — the spec also accepts
140
+ * `#root`, but `#public` is what the existing app does.
141
+ *
142
+ * Throws {@link AithosSDKError} on backend errors. Note that
143
+ * server-side support is in-flight; this method may surface a
144
+ * `mandates_-32601` (method not found) until the auth platform
145
+ * lands the corresponding write handler.
146
+ */
147
+ async revoke(mandateId) {
148
+ const owner = this.#requireOwner();
149
+ const ownerStored = owner._unsafeStoredIdentity();
150
+ // TODO(post-alpha): once @aithos/protocol-client exposes a public
151
+ // configuration API for the `api` endpoint, route this through the
152
+ // same channel as `readRpc` / `publishZoneEdit` so self-hosters
153
+ // can override. For now, target the production write surface.
154
+ const url = "https://api.aithos.be/mcp/primitives/write";
155
+ const params = {
156
+ mandate_id: mandateId,
157
+ revoked_at: new Date().toISOString(),
158
+ };
159
+ // Reach into the owner's #public signer for envelope signing via
160
+ // the centralized migration bridge (will go away when
161
+ // protocol-client accepts Signer-shaped objects).
162
+ const publicKp = ownerKeyPair(owner, "public");
163
+ const envelope = buildSignedEnvelope({
164
+ iss: ownerStored.did,
165
+ aud: url,
166
+ method: "aithos.publish_revocation",
167
+ verificationMethod: `${ownerStored.did}#public`,
168
+ params,
169
+ signer: publicKp,
170
+ });
171
+ let res;
172
+ try {
173
+ res = await this.#deps.fetch(url, {
174
+ method: "POST",
175
+ headers: { "content-type": "application/json" },
176
+ body: JSON.stringify({
177
+ jsonrpc: "2.0",
178
+ id: "aithos.publish_revocation",
179
+ method: "aithos.publish_revocation",
180
+ params: { ...params, _envelope: envelope },
181
+ }),
182
+ });
183
+ }
184
+ catch (e) {
185
+ throw new AithosSDKError("network", e.message);
186
+ }
187
+ const body = (await res.json().catch(() => null));
188
+ if (!body) {
189
+ throw new AithosSDKError("http", `HTTP ${res.status} ${res.statusText} — non-JSON response`, { status: res.status });
190
+ }
191
+ if (body.error) {
192
+ throw new AithosSDKError(`mandates_${body.error.code}`, body.error.message, body.error.data ? { data: body.error.data } : undefined);
193
+ }
194
+ }
195
+ /* ------------------------------------------------------------------------ */
196
+ /* Internals */
197
+ /* ------------------------------------------------------------------------ */
198
+ #requireOwner() {
199
+ const owner = this.#deps.auth._getOwnerSigners();
200
+ if (!owner || owner.destroyed) {
201
+ throw new AithosSDKError("mandates_no_owner", "no owner signed in; sign in first");
202
+ }
203
+ return owner;
204
+ }
205
+ }
206
+ /* -------------------------------------------------------------------------- */
207
+ /* Helpers */
208
+ /* -------------------------------------------------------------------------- */
209
+ function defaultSphereFromScopes(scopes) {
210
+ if (scopes.some((s) => s.endsWith(".self")))
211
+ return "self";
212
+ if (scopes.some((s) => s.endsWith(".circle")))
213
+ return "circle";
214
+ return "public";
215
+ }
216
+ /**
217
+ * Validate the SDK-side `compute` namespace and project it onto the
218
+ * snake_case shape the protocol layer canonicalizes into the mandate.
219
+ *
220
+ * Returns `null` if the caller passed nothing — that's the "no compute
221
+ * authorization" path. Throws {@link AithosSDKError} on any structural
222
+ * problem (camelCase mirror of the rules `validateComputeAuthorization`
223
+ * enforces server-side; we duplicate them here so a misuse fails at the
224
+ * SDK boundary with a precise error rather than only blowing up at mint).
225
+ */
226
+ function projectCompute(c) {
227
+ if (c === undefined)
228
+ return null;
229
+ const hasDaily = typeof c.dailyCapMicrocredits === "number";
230
+ const hasTotal = typeof c.totalCapMicrocredits === "number";
231
+ if (!hasDaily && !hasTotal) {
232
+ throw new AithosSDKError("mandates_invalid_compute", "compute namespace requires at least one of dailyCapMicrocredits " +
233
+ "or totalCapMicrocredits — an unbounded compute mandate is a bearer " +
234
+ "token to drain the subject's wallet.");
235
+ }
236
+ for (const [field, value] of [
237
+ ["dailyCapMicrocredits", c.dailyCapMicrocredits],
238
+ ["totalCapMicrocredits", c.totalCapMicrocredits],
239
+ ["maxCreditsPerCall", c.maxCreditsPerCall],
240
+ ]) {
241
+ if (value === undefined)
242
+ continue;
243
+ if (!Number.isInteger(value) || value <= 0) {
244
+ throw new AithosSDKError("mandates_invalid_compute", `compute.${field} must be a positive integer (got ${value}).`);
245
+ }
246
+ }
247
+ if (c.allowedModels !== undefined) {
248
+ if (!Array.isArray(c.allowedModels)) {
249
+ throw new AithosSDKError("mandates_invalid_compute", "compute.allowedModels must be an array of strings.");
250
+ }
251
+ for (const m of c.allowedModels) {
252
+ if (typeof m !== "string" || m.length === 0) {
253
+ throw new AithosSDKError("mandates_invalid_compute", `compute.allowedModels entries must be non-empty strings (got ${JSON.stringify(m)}).`);
254
+ }
255
+ }
256
+ }
257
+ const wire = {};
258
+ if (hasDaily)
259
+ wire.daily_cap_microcredits = c.dailyCapMicrocredits;
260
+ if (hasTotal)
261
+ wire.total_cap_microcredits = c.totalCapMicrocredits;
262
+ if (c.maxCreditsPerCall !== undefined)
263
+ wire.max_credits_per_call = c.maxCreditsPerCall;
264
+ if (c.allowedModels !== undefined)
265
+ wire.allowed_models = [...c.allowedModels];
266
+ return wire;
267
+ }
268
+ function toOwnedMandate(it) {
269
+ return {
270
+ mandateId: requireString(it, "mandate_id"),
271
+ issuerDid: requireString(it, "issuer_did"),
272
+ actorDid: requireString(it, "actor_did"),
273
+ scopes: filterScopeArray(it["scopes"]),
274
+ notBefore: typeof it["not_before"] === "number" ? it["not_before"] : null,
275
+ notAfter: typeof it["not_after"] === "number" ? it["not_after"] : null,
276
+ createdAt: typeof it["created_at"] === "number" ? it["created_at"] : 0,
277
+ };
278
+ }
279
+ function requireString(o, key) {
280
+ const v = o[key];
281
+ if (typeof v !== "string") {
282
+ throw new AithosSDKError("mandates_response_malformed", `list_mandates row missing string field ${key}`);
283
+ }
284
+ return v;
285
+ }
286
+ function filterScopeArray(v) {
287
+ if (!Array.isArray(v))
288
+ return [];
289
+ return v.filter((s) => typeof s === "string");
290
+ }
13
291
  //# sourceMappingURL=mandates.js.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `<AithosAsset>` — drop-in component for displaying Aithos-hosted
3
+ * binary content with the simplest possible API:
4
+ *
5
+ * <AithosAsset urn={cv.urn} alt="CV" className="w-full" />
6
+ * <AithosAsset urn={video.urn} as="video" controls />
7
+ * <AithosAsset urn={song.urn} as="audio" controls />
8
+ * <AithosAsset urn={cv.urn} as="download" filename="cv.pdf">
9
+ * Download my CV (PDF)
10
+ * </AithosAsset>
11
+ *
12
+ * The component handles the full lifecycle:
13
+ * - Fetch + decrypt via the assets client (from context or `client` prop).
14
+ * - Show `fallback` while loading.
15
+ * - Show `errorFallback` (or alt text in img-mode) on failure.
16
+ * - Revoke the `blob:` URL on unmount or URN change.
17
+ *
18
+ * For public assets attached to the Ethos public zone, you can also
19
+ * just use the stable CloudFront URL directly (`<img src={asset.url} />`).
20
+ * This component is meant for private regime assets that require
21
+ * client-side decryption.
22
+ */
23
+ import type { ReactNode, ImgHTMLAttributes, VideoHTMLAttributes, AudioHTMLAttributes, AnchorHTMLAttributes } from "react";
24
+ import type { AssetsClient } from "../assets.js";
25
+ interface AithosAssetBaseProps {
26
+ /** Asset URN, e.g. `urn:aithos:asset:did:aithos:z6Mkr…:asset_01J…`. */
27
+ readonly urn: string;
28
+ /** Override the assets client from context. */
29
+ readonly client?: AssetsClient | null;
30
+ /** Rendered while the fetch+decrypt is in flight. */
31
+ readonly fallback?: ReactNode;
32
+ /** Rendered on fetch/decrypt failure. `as="img"` defaults to showing alt instead. */
33
+ readonly errorFallback?: ReactNode;
34
+ /** Called once the asset is decrypted and ready to display. */
35
+ readonly onLoad?: () => void;
36
+ /** Called when the fetch/decrypt fails. */
37
+ readonly onError?: (err: Error) => void;
38
+ /**
39
+ * If `true`, keep the previous asset visible while a new URN is
40
+ * being fetched, instead of flashing the fallback. Default `false`.
41
+ */
42
+ readonly keepPreviousOnUrnChange?: boolean;
43
+ }
44
+ export interface AithosImageProps extends AithosAssetBaseProps, Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "onLoad" | "onError"> {
45
+ readonly as?: "img";
46
+ }
47
+ export interface AithosVideoProps extends AithosAssetBaseProps, Omit<VideoHTMLAttributes<HTMLVideoElement>, "src" | "onLoad" | "onError"> {
48
+ readonly as: "video";
49
+ }
50
+ export interface AithosAudioProps extends AithosAssetBaseProps, Omit<AudioHTMLAttributes<HTMLAudioElement>, "src" | "onLoad" | "onError"> {
51
+ readonly as: "audio";
52
+ }
53
+ export interface AithosDownloadProps extends AithosAssetBaseProps, Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "onLoad" | "onError"> {
54
+ readonly as: "download";
55
+ /** Suggested filename in the browser's download dialog. */
56
+ readonly filename?: string;
57
+ readonly children?: ReactNode;
58
+ }
59
+ export type AithosAssetProps = AithosImageProps | AithosVideoProps | AithosAudioProps | AithosDownloadProps;
60
+ /**
61
+ * One component, four media kinds. The `as` prop selects between
62
+ * `<img>` (default), `<video>`, `<audio>`, and a styled `<a download>`.
63
+ */
64
+ export declare function AithosAsset(props: AithosAssetProps): ReactNode;
65
+ export {};
66
+ //# sourceMappingURL=AithosAsset.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import { useAithosAsset } from "./use-aithos-asset.js";
4
+ /* -------------------------------------------------------------------------- */
5
+ /* Component */
6
+ /* -------------------------------------------------------------------------- */
7
+ /**
8
+ * One component, four media kinds. The `as` prop selects between
9
+ * `<img>` (default), `<video>`, `<audio>`, and a styled `<a download>`.
10
+ */
11
+ export function AithosAsset(props) {
12
+ const { urn, client, fallback, errorFallback, onLoad, onError, keepPreviousOnUrnChange, ...rest } = props;
13
+ const state = useAithosAsset(urn, {
14
+ client: client ?? undefined,
15
+ keepPreviousOnUrnChange,
16
+ });
17
+ // onLoad / onError side-effects
18
+ useEffect(() => {
19
+ if (state.url && onLoad)
20
+ onLoad();
21
+ }, [state.url, onLoad]);
22
+ useEffect(() => {
23
+ if (state.error && onError)
24
+ onError(state.error);
25
+ }, [state.error, onError]);
26
+ // Loading
27
+ if (state.loading) {
28
+ return (fallback ?? null);
29
+ }
30
+ // Error
31
+ if (state.error) {
32
+ if (errorFallback !== undefined) {
33
+ return errorFallback;
34
+ }
35
+ // For img-mode, the alt attribute is a sensible default error
36
+ // surface (the browser renders the alt text when src is broken).
37
+ if (rest.as === undefined || rest.as === "img") {
38
+ const imgProps = rest;
39
+ // eslint-disable-next-line jsx-a11y/alt-text
40
+ return _jsx("img", { ...imgProps, src: undefined });
41
+ }
42
+ return null;
43
+ }
44
+ // No URL yet (no URN supplied → idle state)
45
+ if (!state.url) {
46
+ return null;
47
+ }
48
+ // Dispatch on `as`
49
+ const as = rest.as ?? "img";
50
+ const { as: _consumed, ...domProps } = rest;
51
+ void _consumed;
52
+ if (as === "img") {
53
+ return (_jsx("img", { ...domProps, src: state.url }));
54
+ }
55
+ if (as === "video") {
56
+ return (_jsx("video", { ...domProps, src: state.url }));
57
+ }
58
+ if (as === "audio") {
59
+ return (_jsx("audio", { ...domProps, src: state.url }));
60
+ }
61
+ if (as === "download") {
62
+ const { filename, children, ...anchorRest } = domProps;
63
+ return (_jsx("a", { ...anchorRest, href: state.url, download: filename ?? true, children: children }));
64
+ }
65
+ return null;
66
+ }
67
+ //# sourceMappingURL=AithosAsset.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * React context for the Aithos assets client.
3
+ *
4
+ * App roots wrap their tree with `<AssetsClientProvider client={sdk.assets}>`
5
+ * once, then child components use `<AithosAsset urn="..." />` (or the
6
+ * `useAithosAsset` hook) without having to thread the client through
7
+ * props.
8
+ */
9
+ import { type ReactNode } from "react";
10
+ import type { AssetsClient } from "../assets.js";
11
+ export interface AssetsClientProviderProps {
12
+ /** The SDK's assets client — typically `sdk.assets`. */
13
+ readonly client: AssetsClient;
14
+ readonly children?: ReactNode;
15
+ }
16
+ /**
17
+ * Provider that exposes an {@link AssetsClient} to descendants.
18
+ *
19
+ * Typical placement: as high in the React tree as the authenticated
20
+ * session lives (e.g. inside the auth boundary).
21
+ */
22
+ export declare function AssetsClientProvider(props: AssetsClientProviderProps): any;
23
+ /**
24
+ * Return the {@link AssetsClient} from the nearest provider, or `null`
25
+ * if no provider is present. Components SHOULD accept an override via
26
+ * an explicit `client` prop and fall back to this hook when omitted.
27
+ */
28
+ export declare function useAssetsClient(): AssetsClient | null;
29
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Copyright 2026 Mathieu Colla
4
+ /**
5
+ * React context for the Aithos assets client.
6
+ *
7
+ * App roots wrap their tree with `<AssetsClientProvider client={sdk.assets}>`
8
+ * once, then child components use `<AithosAsset urn="..." />` (or the
9
+ * `useAithosAsset` hook) without having to thread the client through
10
+ * props.
11
+ */
12
+ import { createContext, useContext } from "react";
13
+ const AssetsClientContext = createContext(null);
14
+ /**
15
+ * Provider that exposes an {@link AssetsClient} to descendants.
16
+ *
17
+ * Typical placement: as high in the React tree as the authenticated
18
+ * session lives (e.g. inside the auth boundary).
19
+ */
20
+ export function AssetsClientProvider(props) {
21
+ return (_jsx(AssetsClientContext.Provider, { value: props.client, children: props.children }));
22
+ }
23
+ /**
24
+ * Return the {@link AssetsClient} from the nearest provider, or `null`
25
+ * if no provider is present. Components SHOULD accept an override via
26
+ * an explicit `client` prop and fall back to this hook when omitted.
27
+ */
28
+ export function useAssetsClient() {
29
+ return useContext(AssetsClientContext);
30
+ }
31
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `@aithos/sdk/react` — React bindings for the Aithos SDK.
3
+ *
4
+ * Drop-in components and hooks for displaying Aithos-hosted assets in
5
+ * React applications. The package's only peer dependency beyond
6
+ * `@aithos/sdk` itself is `react` (^18 || ^19).
7
+ *
8
+ * Quick start:
9
+ *
10
+ * // 1. Wrap your tree once
11
+ * <AssetsClientProvider client={sdk.assets}>
12
+ * <App />
13
+ * </AssetsClientProvider>
14
+ *
15
+ * // 2. Use anywhere
16
+ * <AithosAsset urn={cv.urn} alt="CV" className="w-full" />
17
+ * <AithosAsset urn={video.urn} as="video" controls />
18
+ * <AithosAsset urn={pdf.urn} as="download" filename="cv.pdf">
19
+ * Download my CV
20
+ * </AithosAsset>
21
+ *
22
+ * // 3. Or use the hook directly for custom rendering
23
+ * const { url, bytes, loading, error } = useAithosAsset(urn);
24
+ */
25
+ export { AssetsClientProvider, useAssetsClient, type AssetsClientProviderProps, } from "./context.js";
26
+ export { useAithosAsset, type UseAithosAssetState, type UseAithosAssetOptions, } from "./use-aithos-asset.js";
27
+ export { AithosAsset, type AithosAssetProps, type AithosImageProps, type AithosVideoProps, type AithosAudioProps, type AithosDownloadProps, } from "./AithosAsset.js";
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,30 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ /**
4
+ * `@aithos/sdk/react` — React bindings for the Aithos SDK.
5
+ *
6
+ * Drop-in components and hooks for displaying Aithos-hosted assets in
7
+ * React applications. The package's only peer dependency beyond
8
+ * `@aithos/sdk` itself is `react` (^18 || ^19).
9
+ *
10
+ * Quick start:
11
+ *
12
+ * // 1. Wrap your tree once
13
+ * <AssetsClientProvider client={sdk.assets}>
14
+ * <App />
15
+ * </AssetsClientProvider>
16
+ *
17
+ * // 2. Use anywhere
18
+ * <AithosAsset urn={cv.urn} alt="CV" className="w-full" />
19
+ * <AithosAsset urn={video.urn} as="video" controls />
20
+ * <AithosAsset urn={pdf.urn} as="download" filename="cv.pdf">
21
+ * Download my CV
22
+ * </AithosAsset>
23
+ *
24
+ * // 3. Or use the hook directly for custom rendering
25
+ * const { url, bytes, loading, error } = useAithosAsset(urn);
26
+ */
27
+ export { AssetsClientProvider, useAssetsClient, } from "./context.js";
28
+ export { useAithosAsset, } from "./use-aithos-asset.js";
29
+ export { AithosAsset, } from "./AithosAsset.js";
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,39 @@
1
+ import type { AssetsClient } from "../assets.js";
2
+ export interface UseAithosAssetState {
3
+ /** A `blob:` URL pointing at the decrypted plaintext. Use in <img src=…> etc. */
4
+ readonly url: string | null;
5
+ /** Raw decrypted bytes. Use when you need the binary itself (e.g. PDF viewer). */
6
+ readonly bytes: Uint8Array | null;
7
+ /** IANA media type — useful for routing rendering. */
8
+ readonly mediaType: string | null;
9
+ /** Plaintext size in bytes. */
10
+ readonly sizeBytes: number | null;
11
+ /** True while the fetch+decrypt is in flight. */
12
+ readonly loading: boolean;
13
+ /** Non-null when the fetch/decrypt failed. */
14
+ readonly error: Error | null;
15
+ }
16
+ export interface UseAithosAssetOptions {
17
+ /** Override the client from context. Useful for tests. */
18
+ readonly client?: AssetsClient | null;
19
+ /**
20
+ * If `true`, the hook keeps the previous blob URL visible while a
21
+ * new URN is being fetched, instead of flashing `null`. Default
22
+ * `false` (more predictable, less visual stutter avoidance).
23
+ */
24
+ readonly keepPreviousOnUrnChange?: boolean;
25
+ }
26
+ /**
27
+ * Fetch + decrypt an Aithos asset and return a stable `blob:` URL.
28
+ *
29
+ * Lifecycle:
30
+ * - On mount or when `urn` changes, runs `client.fetch(urn)`.
31
+ * - On success, creates an object URL from the plaintext bytes.
32
+ * - On unmount or before refetching, revokes the URL to free memory.
33
+ * - Cancels in-flight fetches if the URN changes mid-flight.
34
+ *
35
+ * Throws (via `error`) when no client is available — supply one via
36
+ * `<AssetsClientProvider>` or the `client` option.
37
+ */
38
+ export declare function useAithosAsset(urn: string | null | undefined, options?: UseAithosAssetOptions): UseAithosAssetState;
39
+ //# sourceMappingURL=use-aithos-asset.d.ts.map