@everfur/sdk 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 (89) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +161 -0
  4. package/animations/package.json +8 -0
  5. package/chat/package.json +8 -0
  6. package/client/package.json +8 -0
  7. package/core/package.json +8 -0
  8. package/dist/CameraPort-Cv31Pz7g.d.cts +29 -0
  9. package/dist/CameraPort-Cv31Pz7g.d.ts +29 -0
  10. package/dist/ChatController-CKdBvPj2.d.ts +146 -0
  11. package/dist/ChatController-CpUMvvZf.d.cts +146 -0
  12. package/dist/EverfurResult-D92-uL82.d.cts +240 -0
  13. package/dist/EverfurResult-D92-uL82.d.ts +240 -0
  14. package/dist/FilePort-BabWrv7I.d.cts +22 -0
  15. package/dist/FilePort-BabWrv7I.d.ts +22 -0
  16. package/dist/PhotoController-BItt5M7u.d.cts +43 -0
  17. package/dist/PhotoController-D8zMTdcW.d.ts +43 -0
  18. package/dist/TelemetryPort-BDNr00hu.d.cts +12 -0
  19. package/dist/TelemetryPort-BDNr00hu.d.ts +12 -0
  20. package/dist/animations/index.cjs +1997 -0
  21. package/dist/animations/index.d.cts +517 -0
  22. package/dist/animations/index.d.ts +517 -0
  23. package/dist/animations/index.js +1972 -0
  24. package/dist/cacheEpoch-DknKn3S0.d.cts +36 -0
  25. package/dist/cacheEpoch-DknKn3S0.d.ts +36 -0
  26. package/dist/chat/index.cjs +1170 -0
  27. package/dist/chat/index.d.cts +59 -0
  28. package/dist/chat/index.d.ts +59 -0
  29. package/dist/chat/index.js +1167 -0
  30. package/dist/client/index.cjs +2317 -0
  31. package/dist/client/index.d.cts +65 -0
  32. package/dist/client/index.d.ts +65 -0
  33. package/dist/client/index.js +2217 -0
  34. package/dist/config-BSjBdxrZ.d.cts +501 -0
  35. package/dist/config-CiJ0PVBB.d.ts +501 -0
  36. package/dist/core/index.cjs +3175 -0
  37. package/dist/core/index.d.cts +70 -0
  38. package/dist/core/index.d.ts +70 -0
  39. package/dist/core/index.js +3163 -0
  40. package/dist/identity-Brl-lDd6.d.cts +91 -0
  41. package/dist/identity-DK9zORrG.d.ts +91 -0
  42. package/dist/ids-CJ1S6adf.d.cts +46 -0
  43. package/dist/ids-CJ1S6adf.d.ts +46 -0
  44. package/dist/index.cjs +4488 -0
  45. package/dist/index.d.cts +156 -0
  46. package/dist/index.d.ts +156 -0
  47. package/dist/index.js +4464 -0
  48. package/dist/petsRepository-BEGb97M9.d.cts +326 -0
  49. package/dist/petsRepository-Bu18r2kK.d.ts +326 -0
  50. package/dist/photo/index.cjs +2189 -0
  51. package/dist/photo/index.d.cts +43 -0
  52. package/dist/photo/index.d.ts +43 -0
  53. package/dist/photo/index.js +2186 -0
  54. package/dist/projection-CeIUsbSk.d.cts +8 -0
  55. package/dist/projection-CeIUsbSk.d.ts +8 -0
  56. package/dist/records/index.cjs +2840 -0
  57. package/dist/records/index.d.cts +224 -0
  58. package/dist/records/index.d.ts +224 -0
  59. package/dist/records/index.js +2834 -0
  60. package/dist/requestFunnel-DuUH-kAe.d.cts +28 -0
  61. package/dist/requestFunnel-dio5OmR9.d.ts +28 -0
  62. package/dist/resolve-Dq_4_agU.d.cts +86 -0
  63. package/dist/resolve-Dq_4_agU.d.ts +86 -0
  64. package/dist/runtime-BgQnA594.d.cts +349 -0
  65. package/dist/runtime-CBA-LvdM.d.ts +349 -0
  66. package/dist/server/index.cjs +533 -0
  67. package/dist/server/index.d.cts +48 -0
  68. package/dist/server/index.d.ts +48 -0
  69. package/dist/server/index.js +530 -0
  70. package/dist/testing/index.cjs +825 -0
  71. package/dist/testing/index.d.cts +113 -0
  72. package/dist/testing/index.d.ts +113 -0
  73. package/dist/testing/index.js +822 -0
  74. package/dist/testing/rn/index.cjs +449 -0
  75. package/dist/testing/rn/index.d.cts +49 -0
  76. package/dist/testing/rn/index.d.ts +49 -0
  77. package/dist/testing/rn/index.js +444 -0
  78. package/dist/video/index.cjs +1941 -0
  79. package/dist/video/index.d.cts +66 -0
  80. package/dist/video/index.d.ts +66 -0
  81. package/dist/video/index.js +1938 -0
  82. package/package.json +311 -0
  83. package/photo/package.json +8 -0
  84. package/records/package.json +8 -0
  85. package/server/device-blocked.cjs +15 -0
  86. package/server/package.json +9 -0
  87. package/testing/package.json +8 -0
  88. package/testing/rn/package.json +8 -0
  89. package/video/package.json +8 -0
@@ -0,0 +1,28 @@
1
+ import { E as EverfurResult } from './EverfurResult-D92-uL82.cjs';
2
+ import { E as EverfurRequest, F as Frame } from './config-BSjBdxrZ.cjs';
3
+
4
+ interface FunnelSendOptions {
5
+ /**
6
+ * Force the idempotency decision. Omit to derive it from the method (mutating -> minted). Pass `false`
7
+ * for a mutating request the server does not dedup and that must not claim to be idempotent.
8
+ */
9
+ readonly idempotent?: boolean;
10
+ /** Per-call attempt ceiling override (a poll loop that owns its own budget passes 1). */
11
+ readonly maxAttempts?: number;
12
+ }
13
+ interface RequestFunnel {
14
+ /** The ONE unary path: headers, auth, deadline, retry, idempotency, request-id, telemetry. */
15
+ send<T>(call: string, req: EverfurRequest, opts?: FunnelSendOptions): Promise<EverfurResult<T>>;
16
+ /**
17
+ * Resolve device auth onto a request (bearer XOR publishable key, caller auth headers stripped).
18
+ * Rejects only when the TokenProvider fails; the caller settles that into its own terminal outcome.
19
+ */
20
+ authorize(req: EverfurRequest): Promise<EverfurRequest>;
21
+ /**
22
+ * Open an SSE stream. NOT idempotency-wrapped and NOT buffered: frames are yielded as they arrive.
23
+ * `req` is expected to have been through `authorize`.
24
+ */
25
+ stream(call: string, req: EverfurRequest, signal: AbortSignal): AsyncIterable<Frame>;
26
+ }
27
+
28
+ export type { RequestFunnel as R };
@@ -0,0 +1,28 @@
1
+ import { E as EverfurResult } from './EverfurResult-D92-uL82.js';
2
+ import { E as EverfurRequest, F as Frame } from './config-CiJ0PVBB.js';
3
+
4
+ interface FunnelSendOptions {
5
+ /**
6
+ * Force the idempotency decision. Omit to derive it from the method (mutating -> minted). Pass `false`
7
+ * for a mutating request the server does not dedup and that must not claim to be idempotent.
8
+ */
9
+ readonly idempotent?: boolean;
10
+ /** Per-call attempt ceiling override (a poll loop that owns its own budget passes 1). */
11
+ readonly maxAttempts?: number;
12
+ }
13
+ interface RequestFunnel {
14
+ /** The ONE unary path: headers, auth, deadline, retry, idempotency, request-id, telemetry. */
15
+ send<T>(call: string, req: EverfurRequest, opts?: FunnelSendOptions): Promise<EverfurResult<T>>;
16
+ /**
17
+ * Resolve device auth onto a request (bearer XOR publishable key, caller auth headers stripped).
18
+ * Rejects only when the TokenProvider fails; the caller settles that into its own terminal outcome.
19
+ */
20
+ authorize(req: EverfurRequest): Promise<EverfurRequest>;
21
+ /**
22
+ * Open an SSE stream. NOT idempotency-wrapped and NOT buffered: frames are yielded as they arrive.
23
+ * `req` is expected to have been through `authorize`.
24
+ */
25
+ stream(call: string, req: EverfurRequest, signal: AbortSignal): AsyncIterable<Frame>;
26
+ }
27
+
28
+ export type { RequestFunnel as R };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Every entitlement key the PUBLIC contract declares. Internal packs are absent by construction (the
3
+ * projection redacts them), so this union is exactly what a partner surface may require.
4
+ */
5
+ type EverfurCapabilityKey = "animations.state.read" | "chat.conversation.read" | "chat.message.create" | "photo.checkup.execute" | "records.record.export" | "records.record.read" | "records.record.share" | "records.upload.create" | "video.gait.execute";
6
+
7
+ interface LevelGrant {
8
+ /** capability keys this level permits */
9
+ readonly allow: readonly string[];
10
+ /** capability keys this level hard-denies (a deny wins over any allow) */
11
+ readonly deny: readonly string[];
12
+ }
13
+ interface ResolvedEntitlements {
14
+ readonly granted: readonly string[];
15
+ /** the fold-so-far as a LevelGrant, so resolve is associative */
16
+ readonly asGrant: LevelGrant;
17
+ }
18
+ /**
19
+ * Fold = intersection-of-allows minus the union of every deny. FAIL-CLOSED: an absent or empty stack, or
20
+ * an empty first-level allow, yields granted:[]. Pure, total, never throws. Laws (property-tested):
21
+ * intersection, deny-wins, associative, commutative / order-independent, monotonic (adding a level can only
22
+ * shrink or hold `granted`). L3/L5 activation is purely additional LevelGrants appended to the input; this
23
+ * function is unchanged.
24
+ */
25
+ declare function resolveEntitlements(levels: readonly LevelGrant[]): ResolvedEntitlements;
26
+ /** Coarse UI capability ids. NOT valid entitlement keys; bridged to resource keys by `requires`. */
27
+ type CapabilityName = 'chat' | 'records' | 'video' | 'photo' | 'animations';
28
+ type DisabledLevel = 'contract' | 'partner_config' | 'user_entitlement' | 'version_floor' | 'kill_switch'
29
+ /**
30
+ * The SDK itself is not configured (no publishableKey, or no resolvable apiBaseUrl), so NOTHING can be
31
+ * enabled. Distinct from `partner_config`, which means the partner's server-side surface config turned a
32
+ * capability off: that is a product state, this is a HOST INTEGRATION defect and the fix is in the
33
+ * partner's own code. See `tryResolveClientConfig` in client/config.ts.
34
+ */
35
+ | 'sdk_config';
36
+ interface CapabilityDisabled {
37
+ /** SAFE localizable string, never the raw operator note. */
38
+ readonly reason: string;
39
+ readonly level: DisabledLevel;
40
+ readonly recoverable: boolean;
41
+ }
42
+ interface CapabilityDecision {
43
+ readonly name: CapabilityName;
44
+ readonly enabled: boolean;
45
+ readonly disabled?: CapabilityDisabled;
46
+ readonly limits: Readonly<Record<string, number>>;
47
+ /** true during the first resolve or while serving a stale value. */
48
+ readonly isPending: boolean;
49
+ }
50
+ /**
51
+ * One server-resolved decision for a single dotted resource key (SPEC-00 5.3, the shape
52
+ * EntitlementRepository.resolve returns). Owned here as the pure contract; the repository port imports it.
53
+ */
54
+ interface EntitlementDecision {
55
+ /** dotted resource key, e.g. 'chat.message.create' */
56
+ readonly resource: string;
57
+ readonly granted: boolean;
58
+ readonly limits?: Readonly<Record<string, number>>;
59
+ readonly disabled?: CapabilityDisabled;
60
+ }
61
+ /** The resource keys a coarse capability requires; all must be granted. */
62
+ declare function requires(name: CapabilityName): readonly EverfurCapabilityKey[];
63
+ /**
64
+ * Every resource key the SDK knows about, denied, each carrying the SAME `disabled` verdict.
65
+ *
66
+ * This exists so a DISABLED runtime needs no new resolver branch: `decideCapability` already prefers a
67
+ * supplied `disabled` from the first constraining key (see `disabledFor`), so seeding these decisions makes
68
+ * every capability settle to the deliberate OffState through the ORDINARY path. Note it must not be the
69
+ * empty array: an absent key resolves to the fail-closed default reason (`accessDenied` at level
70
+ * `user_entitlement`), which would tell a partner their USER lacks entitlement when in fact their own
71
+ * integration is misconfigured.
72
+ */
73
+ declare function deniedDecisionsFor(level: DisabledLevel, reason: string): readonly EntitlementDecision[];
74
+ interface DecideOptions {
75
+ /** true during first resolve or while serving a stale value -> skeleton, never a flash of enabled UI. */
76
+ readonly isPending?: boolean;
77
+ }
78
+ /**
79
+ * The single-input resolver: takes ONLY the server-resolved decision set and returns the resolved capability
80
+ * decision for `name`. All-must-pass: enabled iff EVERY key in requires(name) is present-and-granted. A
81
+ * denied capability returns a deliberate disabled decision (never null), so a gate can render a non-zero
82
+ * height OffState. Never throws.
83
+ */
84
+ declare function decideCapability(name: CapabilityName, decisions: readonly EntitlementDecision[], opts?: DecideOptions): CapabilityDecision;
85
+
86
+ export { type CapabilityDecision as C, type DecideOptions as D, type EntitlementDecision as E, type LevelGrant as L, type ResolvedEntitlements as R, type CapabilityDisabled as a, type CapabilityName as b, type DisabledLevel as c, decideCapability as d, deniedDecisionsFor as e, resolveEntitlements as f, requires as r };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Every entitlement key the PUBLIC contract declares. Internal packs are absent by construction (the
3
+ * projection redacts them), so this union is exactly what a partner surface may require.
4
+ */
5
+ type EverfurCapabilityKey = "animations.state.read" | "chat.conversation.read" | "chat.message.create" | "photo.checkup.execute" | "records.record.export" | "records.record.read" | "records.record.share" | "records.upload.create" | "video.gait.execute";
6
+
7
+ interface LevelGrant {
8
+ /** capability keys this level permits */
9
+ readonly allow: readonly string[];
10
+ /** capability keys this level hard-denies (a deny wins over any allow) */
11
+ readonly deny: readonly string[];
12
+ }
13
+ interface ResolvedEntitlements {
14
+ readonly granted: readonly string[];
15
+ /** the fold-so-far as a LevelGrant, so resolve is associative */
16
+ readonly asGrant: LevelGrant;
17
+ }
18
+ /**
19
+ * Fold = intersection-of-allows minus the union of every deny. FAIL-CLOSED: an absent or empty stack, or
20
+ * an empty first-level allow, yields granted:[]. Pure, total, never throws. Laws (property-tested):
21
+ * intersection, deny-wins, associative, commutative / order-independent, monotonic (adding a level can only
22
+ * shrink or hold `granted`). L3/L5 activation is purely additional LevelGrants appended to the input; this
23
+ * function is unchanged.
24
+ */
25
+ declare function resolveEntitlements(levels: readonly LevelGrant[]): ResolvedEntitlements;
26
+ /** Coarse UI capability ids. NOT valid entitlement keys; bridged to resource keys by `requires`. */
27
+ type CapabilityName = 'chat' | 'records' | 'video' | 'photo' | 'animations';
28
+ type DisabledLevel = 'contract' | 'partner_config' | 'user_entitlement' | 'version_floor' | 'kill_switch'
29
+ /**
30
+ * The SDK itself is not configured (no publishableKey, or no resolvable apiBaseUrl), so NOTHING can be
31
+ * enabled. Distinct from `partner_config`, which means the partner's server-side surface config turned a
32
+ * capability off: that is a product state, this is a HOST INTEGRATION defect and the fix is in the
33
+ * partner's own code. See `tryResolveClientConfig` in client/config.ts.
34
+ */
35
+ | 'sdk_config';
36
+ interface CapabilityDisabled {
37
+ /** SAFE localizable string, never the raw operator note. */
38
+ readonly reason: string;
39
+ readonly level: DisabledLevel;
40
+ readonly recoverable: boolean;
41
+ }
42
+ interface CapabilityDecision {
43
+ readonly name: CapabilityName;
44
+ readonly enabled: boolean;
45
+ readonly disabled?: CapabilityDisabled;
46
+ readonly limits: Readonly<Record<string, number>>;
47
+ /** true during the first resolve or while serving a stale value. */
48
+ readonly isPending: boolean;
49
+ }
50
+ /**
51
+ * One server-resolved decision for a single dotted resource key (SPEC-00 5.3, the shape
52
+ * EntitlementRepository.resolve returns). Owned here as the pure contract; the repository port imports it.
53
+ */
54
+ interface EntitlementDecision {
55
+ /** dotted resource key, e.g. 'chat.message.create' */
56
+ readonly resource: string;
57
+ readonly granted: boolean;
58
+ readonly limits?: Readonly<Record<string, number>>;
59
+ readonly disabled?: CapabilityDisabled;
60
+ }
61
+ /** The resource keys a coarse capability requires; all must be granted. */
62
+ declare function requires(name: CapabilityName): readonly EverfurCapabilityKey[];
63
+ /**
64
+ * Every resource key the SDK knows about, denied, each carrying the SAME `disabled` verdict.
65
+ *
66
+ * This exists so a DISABLED runtime needs no new resolver branch: `decideCapability` already prefers a
67
+ * supplied `disabled` from the first constraining key (see `disabledFor`), so seeding these decisions makes
68
+ * every capability settle to the deliberate OffState through the ORDINARY path. Note it must not be the
69
+ * empty array: an absent key resolves to the fail-closed default reason (`accessDenied` at level
70
+ * `user_entitlement`), which would tell a partner their USER lacks entitlement when in fact their own
71
+ * integration is misconfigured.
72
+ */
73
+ declare function deniedDecisionsFor(level: DisabledLevel, reason: string): readonly EntitlementDecision[];
74
+ interface DecideOptions {
75
+ /** true during first resolve or while serving a stale value -> skeleton, never a flash of enabled UI. */
76
+ readonly isPending?: boolean;
77
+ }
78
+ /**
79
+ * The single-input resolver: takes ONLY the server-resolved decision set and returns the resolved capability
80
+ * decision for `name`. All-must-pass: enabled iff EVERY key in requires(name) is present-and-granted. A
81
+ * denied capability returns a deliberate disabled decision (never null), so a gate can render a non-zero
82
+ * height OffState. Never throws.
83
+ */
84
+ declare function decideCapability(name: CapabilityName, decisions: readonly EntitlementDecision[], opts?: DecideOptions): CapabilityDecision;
85
+
86
+ export { type CapabilityDecision as C, type DecideOptions as D, type EntitlementDecision as E, type LevelGrant as L, type ResolvedEntitlements as R, type CapabilityDisabled as a, type CapabilityName as b, type DisabledLevel as c, decideCapability as d, deniedDecisionsFor as e, resolveEntitlements as f, requires as r };
@@ -0,0 +1,349 @@
1
+ import { E as EntitlementDecision, b as CapabilityName } from './resolve-Dq_4_agU.cjs';
2
+ import { C as ClientBranding, F as FetchEntitlementsArgs, c as EntitlementResolution, w as PetProfileInput, R as RegisteredPet } from './petsRepository-BEGb97M9.cjs';
3
+ import { E as EverfurResult } from './EverfurResult-D92-uL82.cjs';
4
+ import { A as AuthContext, T as TokenProvider } from './identity-Brl-lDd6.cjs';
5
+ import { U as UserRef, P as PetRef, a as ConversationId } from './ids-CJ1S6adf.cjs';
6
+ import { T as TransportPort, k as EventSourceFactory } from './config-BSjBdxrZ.cjs';
7
+ import { I as ImageCachePort, K as KvPort } from './cacheEpoch-DknKn3S0.cjs';
8
+ import { T as TelemetryPort } from './TelemetryPort-BDNr00hu.cjs';
9
+ import { E as ErrorPolicyPort, C as ChatController } from './ChatController-CpUMvvZf.cjs';
10
+ import { F as FileHandle } from './FilePort-BabWrv7I.cjs';
11
+ import { C as CameraPort } from './CameraPort-Cv31Pz7g.cjs';
12
+
13
+ /**
14
+ * Opt-in encrypted secret storage. Backed by a Keychain / Keystore native peer through an rn-layer adapter.
15
+ * Absent by default; the SDK never persists a credential unless the host supplies this port.
16
+ */
17
+ interface SecureStorePort {
18
+ readonly isAvailable: boolean;
19
+ getSecret(key: string): Promise<string | null>;
20
+ setSecret(key: string, value: string): Promise<void>;
21
+ deleteSecret(key: string): Promise<void>;
22
+ }
23
+ /**
24
+ * The safe default: NO persistence. `currentToken()` lives only in IdentityProvider memory (cleared on
25
+ * setUser / clear). Attempting to persist without an explicit port fails loud so a partner opts in on
26
+ * purpose rather than silently leaking a token to disk.
27
+ */
28
+ declare const NULL_SECURE_STORE: SecureStorePort;
29
+
30
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
31
+ /**
32
+ * One structured, correlated log record. `context` is key-allowlisted and value-scrubbed by createLogPort
33
+ * BEFORE it reaches any sink; the raw untrusted `wireMessage` never flows into a rendered node.
34
+ */
35
+ interface LogRecord {
36
+ readonly level: LogLevel;
37
+ /** Stable machine id, e.g. 'chat.stream.error' | 'transport.retry'. */
38
+ readonly event: string;
39
+ /** One id per request; also sent as X-Everfur-Correlation-Id in debug mode. */
40
+ readonly correlationId?: string;
41
+ readonly code?: string;
42
+ readonly httpStatus?: number;
43
+ readonly attempt?: number;
44
+ readonly latencyMs?: number;
45
+ /** Key-allowlisted + value-scrubbed before ANY sink. */
46
+ readonly context?: Record<string, unknown>;
47
+ }
48
+ /** The sink partners inject. It only ever receives already-redacted records. */
49
+ interface LogSink {
50
+ emit(record: Readonly<LogRecord>): void;
51
+ }
52
+
53
+ /** Minimum re-poll interval; a server ttl below this is clamped so we never hot-loop the endpoint. */
54
+ declare const MIN_POLL_INTERVAL_SECONDS = 30;
55
+ /** Injectable timer seam so a suite drives the re-poll clock deterministically (default: global timers). */
56
+ type TimerHandle = ReturnType<typeof setTimeout>;
57
+ interface PollScheduler {
58
+ set(callback: () => void, ms: number): TimerHandle;
59
+ clear(handle: TimerHandle): void;
60
+ }
61
+ interface EntitlementPollerDeps {
62
+ /** Re-derived every poll so a token refresh / user re-scope is reflected on the next hop. */
63
+ readonly authFor: () => AuthContext;
64
+ /**
65
+ * Push a fresh verdict into the runtime store. Called ONLY on a 200 'updated'. Carries the server-driven
66
+ * branding parsed from the SAME body (null when absent/unparseable -> the compiled default theme); branding
67
+ * is additive and orthogonal to the decisions.
68
+ */
69
+ readonly update: (decisions: readonly EntitlementDecision[], branding: ClientBranding | null) => void;
70
+ /** Optional region hint forwarded to the endpoint. */
71
+ readonly region?: string;
72
+ readonly telemetry?: TelemetryPort;
73
+ /** Test seam; defaults to global timers (unref'd so a live poll never pins a Node event loop). */
74
+ readonly scheduler?: PollScheduler;
75
+ /** Test seam; defaults to the real repository call. */
76
+ readonly resolve?: (auth: AuthContext, args: FetchEntitlementsArgs) => Promise<EntitlementResolution>;
77
+ /** Floor override for tests; defaults to MIN_POLL_INTERVAL_SECONDS. */
78
+ readonly floorSeconds?: number;
79
+ /** Test seam for deterministic failure-retry jitter; defaults to Math.random. */
80
+ readonly rng?: () => number;
81
+ }
82
+ interface EntitlementPoller {
83
+ /** Idempotent. Kicks an immediate first poll, then self-schedules on the returned cadence. */
84
+ start(): void;
85
+ /** Idempotent. Clears the pending timer and prevents any post-stop update (dispose calls this). */
86
+ stop(): void;
87
+ /**
88
+ * Drop the cached revision and re-poll immediately. A user / pet re-scope invalidates the cached verdict, so
89
+ * the next request must be UNCONDITIONAL (no If-None-Match) to force a fresh 200 for the new scope.
90
+ */
91
+ refresh(): void;
92
+ }
93
+ declare function createEntitlementPoller(deps: EntitlementPollerDeps): EntitlementPoller;
94
+
95
+ /** The presigned POST/PUT policy returned by `/uploads/initiate` (SPEC-11b §2.1). */
96
+ interface PresignedPolicy {
97
+ /** Absolute S3 endpoint URL to POST/PUT the bytes at. */
98
+ readonly url: string;
99
+ /** POST-policy form fields to include with the upload (empty for a plain PUT). */
100
+ readonly fields: Readonly<Record<string, string>>;
101
+ /** The owned-uploads-prefixed key the analyze call then references. */
102
+ readonly s3Key: string;
103
+ /** epoch seconds the policy expires; telemetry only. */
104
+ readonly expiresAt: number | null;
105
+ }
106
+ /**
107
+ * The native upload seam. It PUTs `file` at the presigned `target` (SPEC-11b §2.1). `idempotencyKey` is reused
108
+ * on every retry so a re-uploaded identical file dedupes server-side. A network failure REJECTS; the controller
109
+ * normalizes that to a `mediaUploadFailed` and exposes a retake affordance (never auto-retried, SPEC-00 §3.2).
110
+ *
111
+ * NOTE: the spine `FilePort.presignedPut(file, { idempotencyKey, signal })` has no slot for the presigned
112
+ * target; this richer port is the shape it must grow to. See the manifest gap note.
113
+ */
114
+ interface UploadPort {
115
+ presignedPut(file: FileHandle, opts: {
116
+ readonly target: PresignedPolicy;
117
+ readonly idempotencyKey: string;
118
+ readonly signal?: AbortSignal;
119
+ }): Promise<void>;
120
+ }
121
+
122
+ interface UploadPolicyWire {
123
+ readonly upload_id: string;
124
+ readonly upload_url: string;
125
+ readonly upload_fields: Readonly<Record<string, string>>;
126
+ readonly expires_in_seconds: number;
127
+ readonly max_content_length: number;
128
+ }
129
+ /**
130
+ * Platform-specific: the rn adapter binds `expo-file-system` (uploadAsync) behind `optionalModule`; the
131
+ * controller stays platform-neutral and never sees a native module. Resolves on S3's 204; rejects on a
132
+ * policy 403 / stream failure, which the controller normalizes to a typed `mediaUploadFailed` (never a throw
133
+ * that escapes the controller).
134
+ */
135
+ interface UploadTransport {
136
+ postMultipart(policy: UploadPolicyWire, file: FileHandle, opts: {
137
+ readonly onProgress?: (fraction: number) => void;
138
+ readonly signal?: AbortSignal;
139
+ }): Promise<void>;
140
+ }
141
+
142
+ /**
143
+ * The public init config the host passes at EverfurProvider mount: the lean client subset
144
+ * (publishableKey / apiBaseUrl / contractVersion / transport / debug) PLUS the host-seam ports. The client
145
+ * layer validates only its own subset; the ports below are consumed here in the core runtime. The rn
146
+ * facade declares a structurally identical `EverfurConfig`, which is assignable to this type.
147
+ */
148
+ interface EverfurRuntimeConfig {
149
+ readonly publishableKey: string;
150
+ readonly apiBaseUrl?: string;
151
+ readonly contractVersion?: string;
152
+ /** Non-secret durable KV; a token is NEVER written through it. In-memory fallback when absent. */
153
+ readonly storage?: KvPort;
154
+ /** Opt-in encrypted storage, the ONLY port a token may cross to disk. Omit for memory-only tokens. */
155
+ readonly secureStore?: SecureStorePort;
156
+ /** Analytics sink; the SDK never imports PostHog / Sentry. No-op default. */
157
+ readonly telemetry?: TelemetryPort;
158
+ /** Receives ALREADY-redacted structured log records. */
159
+ readonly logSink?: LogSink;
160
+ /** Recovery decisions the host can override; a default policy is applied when absent. */
161
+ readonly errorPolicy?: ErrorPolicyPort;
162
+ /** Test seam; production builds the real transport from apiBaseUrl. */
163
+ readonly transport?: TransportPort;
164
+ /**
165
+ * Native capture seam for video / photo (an rn adapter over the camera peer). Absent =>
166
+ * `unavailableCamera`, so those surfaces render the deliberate capture-unavailable state, never a crash.
167
+ */
168
+ readonly camera?: CameraPort;
169
+ /**
170
+ * Native presigned-PUT seam for video / photo uploads. Absent => `unavailableUpload` (rejects), so a
171
+ * submit settles to a handled `mediaUploadFailed` rather than silently succeeding.
172
+ */
173
+ readonly upload?: UploadPort;
174
+ /**
175
+ * Native S3-multipart seam for records document upload. Absent => an unavailable transport (rejects), so
176
+ * records READS still work and only the upload action settles to a handled failure.
177
+ */
178
+ readonly uploadTransport?: UploadTransport;
179
+ /**
180
+ * Platform image-cache seam for the animations fleet-wide epoch purge (rn: expo-image clear*). Absent =>
181
+ * a no-op cache (the cosmetic purge never fires); every other animations path is unaffected.
182
+ */
183
+ readonly imageCache?: ImageCachePort;
184
+ /** Structured, redacted LogPort events when true. */
185
+ readonly debug?: boolean;
186
+ }
187
+ /** Identity handed to the runtime: the partner's opaque user id + a lazy token minter (SPEC-00 §4.3). */
188
+ interface RuntimeUser {
189
+ readonly userRef: UserRef;
190
+ readonly getToken: TokenProvider;
191
+ }
192
+ /** Opaque, permissive theme input; server branding merges ON TOP. Sanitized downstream. */
193
+ type RuntimeTheme = Readonly<Record<string, string | number | boolean>>;
194
+ interface CreateEverfurRuntimeInput {
195
+ readonly config: EverfurRuntimeConfig;
196
+ /** null => general (anonymous) mode: pk alone on the wire. */
197
+ readonly user: RuntimeUser | null;
198
+ readonly activePet: PetRef | null;
199
+ readonly theme: RuntimeTheme | null;
200
+ }
201
+ /** Scope key for a chat controller: two hooks with the same scope share ONE ref-counted controller. */
202
+ interface ChatScope {
203
+ readonly userRef: UserRef | null;
204
+ readonly petRef: PetRef | null;
205
+ readonly conversationId: ConversationId | null;
206
+ }
207
+ /** The four optional (tree-shakeable) capabilities that self-register a controller through the registry. */
208
+ type CapabilityKind = 'records' | 'video' | 'photo' | 'animations';
209
+ /**
210
+ * The shared services a capability hook builds its controller from: the device AuthContext, the analytics
211
+ * sink, and the injected native seams. Handing these out (rather than importing every controller here) is what
212
+ * lets the runtime stay tree-shake-clean while every capability is still genuinely wired.
213
+ */
214
+ interface CapabilityServices {
215
+ /** Build a device AuthContext for a scope (personalized bearer XOR anonymous pk). */
216
+ authFor(userRef: UserRef | null): AuthContext;
217
+ readonly telemetry: TelemetryPort;
218
+ readonly errorPolicy: ErrorPolicyPort | undefined;
219
+ readonly camera: CameraPort;
220
+ readonly upload: UploadPort;
221
+ readonly uploadTransport: UploadTransport;
222
+ readonly imageCache: ImageCachePort;
223
+ readonly kv: KvPort;
224
+ }
225
+ /**
226
+ * The ONE ref-counted capability registry (SPEC-10 §7.4). Chat (the spine) is acquired by its typed method;
227
+ * every optional capability is acquired through the generic `acquire`, keyed by (kind, scopeKey) under the
228
+ * current epoch, so two hooks with the same scope share ONE controller and the last release disposes it. A
229
+ * user / pet re-scope bumps the epoch, disposing every open controller so a fresh one is keyed to the new
230
+ * scope. The controller type is inferred from `create`, so a capability hook binds BY NAME (its kind + its own
231
+ * factory) with NO cast, while this module never statically names a media capability controller.
232
+ */
233
+ interface CapabilityRegistry {
234
+ /** The shared services each capability hook builds its controller from (native seams + auth + telemetry). */
235
+ readonly services: CapabilityServices;
236
+ /** Chat is the spine capability; the registry constructs + ref-counts it directly. */
237
+ acquireChat(scope: ChatScope): ChatController;
238
+ releaseChat(controller: ChatController): void;
239
+ /**
240
+ * Ref-counted acquire for the tree-shakeable capabilities. Two calls with the same (kind, scopeKey) under
241
+ * the current epoch share ONE controller; the last release disposes it via `destroy`.
242
+ */
243
+ acquire<C extends object>(kind: CapabilityKind, scopeKey: string, create: () => C, destroy: (controller: C) => void): C;
244
+ /** Release a controller acquired via `acquire`; the last release disposes it. */
245
+ release(controller: object): void;
246
+ }
247
+ /** @deprecated Pre-capability alias retained for back-compat; the registry is a full CapabilityRegistry. */
248
+ type ChatControllerRegistry = CapabilityRegistry;
249
+ /** The already-resolved server verdict the capability hooks render (never re-folded here). */
250
+ interface EntitlementSnapshot {
251
+ readonly decisions: readonly EntitlementDecision[];
252
+ /** true during the first resolve or while serving a stale value -> skeleton, never a flash of UI. */
253
+ readonly isPending: boolean;
254
+ /**
255
+ * Server-driven theme branding from the SAME entitlements body (orthogonal to decisions). null/absent =>
256
+ * the compiled Everfur default theme. The rn EverfurThemeProvider reads this off the store snapshot and
257
+ * feeds it to resolveTheme; the SERVER stays authoritative for capabilities regardless of branding.
258
+ */
259
+ readonly branding?: ClientBranding | null;
260
+ }
261
+ /**
262
+ * Framework-free entitlement store bound by `useCapability` through useSyncExternalStore. `getSnapshot`
263
+ * returns a STABLE reference until a real change (tearing-safe). `subscribe`/`getSnapshot` are closures,
264
+ * safe to pass as bare references.
265
+ */
266
+ interface EntitlementStore {
267
+ subscribe(listener: () => void): () => void;
268
+ getSnapshot(): EntitlementSnapshot;
269
+ }
270
+ interface EverfurRuntime {
271
+ readonly registry: CapabilityRegistry;
272
+ /**
273
+ * True once `dispose()` has run. Every verb early-returns after that, so a host holding a disposed
274
+ * runtime silently gets nothing: the entitlement store never advances and every gate renders its
275
+ * off-state forever. React StrictMode's dev double-mount runs the unmount cleanup once, which makes this
276
+ * reachable in ordinary development, so the provider checks it and rebuilds rather than republishing a
277
+ * dead runtime.
278
+ */
279
+ readonly isDisposed: boolean;
280
+ readonly entitlements: EntitlementStore;
281
+ /** null => logout: drops the cached bearer and re-scopes every open capability. */
282
+ setUser(user: RuntimeUser | null): void;
283
+ /**
284
+ * Full sign-out: setUser(null) (drop the bearer + re-scope every open capability), and ADDITIONALLY sweep
285
+ * the SDK's genuinely PER-USER non-secret durable KV keys so the next user does not inherit the previous
286
+ * user's cached state. The per-user set is the explicit allowlist `PER_USER_KV_KEYS`, which is EMPTY today:
287
+ * no SDK state persisted through KvPort is per-user yet. Device-global keys (manifest last-known-good,
288
+ * petState cacheEpoch) are deliberately NOT swept. The bearer never enters KV (KvPort privacy invariant),
289
+ * so this touches only non-secret durable state. Returns a promise so a host can await the sweep before
290
+ * calling `setUser(nextUser)`.
291
+ */
292
+ logout(): Promise<void>;
293
+ /** Re-scopes pet-bound controllers in the same commit. */
294
+ setActivePet(pet: PetRef | null): void;
295
+ /**
296
+ * Register (or update) the widget pet profile the records and consent routes assume already exists.
297
+ *
298
+ * All three consent routes resolve `pet_ref` through the server's `_resolve_pet_id`, which 404s when no
299
+ * widget pet exists — so consent, and therefore records, is UNREACHABLE for any pet the partner has not
300
+ * upserted out of band. `createPetsRepository` exists on the `@everfur/sdk/client` subpath, but that is
301
+ * the advanced-integrator surface and it takes a raw `AuthContext`; a partner using the provider had no
302
+ * way to call it. This is that way. Idempotent: the route is an upsert keyed on `pet_ref`.
303
+ */
304
+ registerPet(pet: PetRef, profile?: PetProfileInput): Promise<EverfurResult<RegisteredPet>>;
305
+ /**
306
+ * Replace the rendered entitlement verdict (a future poller / a manifest refresh). The SERVER stays
307
+ * authoritative: this only feeds the render store, so the pending/enabled paths become reachable while a
308
+ * 403 on any request still settles the controller to its denied state.
309
+ */
310
+ updateEntitlements(next: EntitlementSnapshot): void;
311
+ /** Navigate / mount a prebuilt capability screen (host-routed). No-op at the spine. */
312
+ open(capability: CapabilityName): void;
313
+ /**
314
+ * Idempotent teardown: aborts in-flight capability requests, tears down every open SSE stream, and stops
315
+ * the entitlement poller (an in-flight entitlement poll's result is DROPPED, not aborted mid-flight).
316
+ */
317
+ dispose(): void;
318
+ }
319
+ /** Runtime-construction options, distinct from the partner-facing config (host wiring / test seams). */
320
+ interface CreateRuntimeOptions {
321
+ /**
322
+ * Whether to start the entitlement poller. Default: start ONLY when the transport was NOT injected (a real,
323
+ * base-URL runtime). An injected transport is the test seam, so auto-start stays OFF to keep MockTransport
324
+ * suites network-free; pass `true` to exercise the wired poller against a MockTransport.
325
+ */
326
+ readonly startEntitlementPolling?: boolean;
327
+ /** Test seam: an injected scheduler so a suite can drive the entitlement re-poll timer deterministically. */
328
+ readonly pollScheduler?: PollScheduler;
329
+ /**
330
+ * Host-wiring seam: the EventSource factory the built HTTP transport falls back to when a streaming
331
+ * `res.body` is unavailable (stock RN fetch). The rn facade injects react-native-sse here so streaming works
332
+ * on stock React Native; core NEVER imports react-native-sse (it stays framework-free and only threads this
333
+ * through). Absent (web/expo, or an injected transport) => the fetch-body streaming path is used.
334
+ */
335
+ readonly eventSourceFactory?: EventSourceFactory;
336
+ }
337
+ /**
338
+ * Construct the runtime once (EverfurProvider mount). Builds the transport (injected test seam, else the
339
+ * real HTTP transport from the resolved client config), the IdentityProvider when a user is set, the
340
+ * capability registry, the entitlement store, and (for a real runtime) the entitlement poller.
341
+ * A config that cannot be resolved (missing publishableKey, or an unresolvable / non-https base URL when
342
+ * no transport is injected) does NOT throw: it yields a DISABLED runtime whose transport refuses and
343
+ * whose every capability settles to the deliberate off-state at `disabled.level === 'sdk_config'`.
344
+ * @throws {EverfurConfigError} only for a supplied ACTIVE secureStore, which expresses a security
345
+ * intent the SDK cannot yet honour and must not silently discard.
346
+ */
347
+ declare function createEverfurRuntime(input: CreateEverfurRuntimeInput, opts?: CreateRuntimeOptions): EverfurRuntime;
348
+
349
+ export { type CapabilityKind as C, type EntitlementPoller as E, type LogLevel as L, MIN_POLL_INTERVAL_SECONDS as M, NULL_SECURE_STORE as N, type PollScheduler as P, type RuntimeTheme as R, type SecureStorePort as S, type TimerHandle as T, type UploadPort as U, type CapabilityRegistry as a, type CapabilityServices as b, type ChatControllerRegistry as c, type ChatScope as d, type CreateEverfurRuntimeInput as e, type CreateRuntimeOptions as f, type EntitlementPollerDeps as g, type EntitlementSnapshot as h, type EntitlementStore as i, type EverfurRuntime as j, type EverfurRuntimeConfig as k, type LogRecord as l, type LogSink as m, type RuntimeUser as n, createEntitlementPoller as o, createEverfurRuntime as p, type UploadTransport as q, type PresignedPolicy as r, type UploadPolicyWire as s };