@qlever-llc/trellis-svelte 0.7.0 → 0.8.0-rc.3

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.
package/README.md CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  Svelte integration for Trellis browser applications.
4
4
 
5
- Provides `TrellisProvider` for app-level wiring, along with reactive helpers for auth state, NATS connection state, and Trellis client state.
5
+ Provides `TrellisProvider` for app-level wiring and `createTrellisApp(...)` for
6
+ app-scoped typed Svelte context around the real connected Trellis client.
6
7
 
7
8
  Use `TrellisProvider` as the primary integration surface:
8
9
 
9
- - pass `trellisUrl`, `contract`, and `loginPath`
10
- - read the live client with `getTrellis<typeof contract>()`
11
- - use `createAuthState(...)` only for lower-level sign-in flows when needed
10
+ - create an app context with `createTrellisApp({ contract, trellisUrl })`
11
+ - pass `trellisApp` to `TrellisProvider`
12
+ - read the live client synchronously with `app.getTrellis()` from child
13
+ components
12
14
 
13
- Uses the contract/runtime model from `@qlever-llc/trellis/contracts` and `@qlever-llc/trellis`.
15
+ Uses the contract/runtime model from `@qlever-llc/trellis/contracts` and
16
+ `@qlever-llc/trellis`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlever-llc/trellis-svelte",
3
- "version": "0.7.0",
3
+ "version": "0.8.0-rc.3",
4
4
  "type": "module",
5
5
  "description": "Svelte components and state helpers for Trellis browser applications.",
6
6
  "license": "Apache-2.0",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@nats-io/nats-core": "^3.3.1",
31
- "@qlever-llc/trellis": "^0.7.0",
31
+ "@qlever-llc/trellis": "^0.8.0-rc.3",
32
32
  "typebox": "^1.0.15"
33
33
  },
34
34
  "peerDependencies": {
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import {
4
+ provideConnectedTrellisContext,
5
+ type TrellisAppOwner,
6
+ type TrellisContextClient,
7
+ } from "../context.svelte.ts";
8
+
9
+ type Props = {
10
+ trellisApp: TrellisAppOwner;
11
+ trellis: TrellisContextClient;
12
+ children: Snippet;
13
+ };
14
+
15
+ const { trellisApp, trellis, children }: Props = $props();
16
+
17
+ function installContext(): void {
18
+ provideConnectedTrellisContext(trellisApp, trellis);
19
+ }
20
+
21
+ installContext();
22
+ </script>
23
+
24
+ {@render children()}
@@ -1,229 +1,144 @@
1
- <script lang="ts">
2
- import type { NatsConnection } from "@nats-io/nats-core";
3
- import { AsyncResult } from "@qlever-llc/result";
4
- import type { Trellis, TrellisAPI } from "../../../trellis/trellis.ts";
5
- import { onDestroy } from "svelte";
6
- import type { Snippet } from "svelte";
1
+ <script lang="ts" generics="TContract extends TrellisContractLike">
7
2
  import {
8
- setAuthContext,
9
- setNatsStateContext,
10
- setTrellisContext,
11
- } from "../context.svelte.ts";
12
- import { createAuthState, type BindErrorResult } from "../state/auth.svelte.ts";
13
- import { createConnectedNatsState, type NatsState } from "../state/nats.svelte.ts";
14
- import {
15
- type TrellisClientContract,
16
- } from "../state/trellis.svelte.ts";
17
- import { TrellisClient } from "../../../trellis/client_connect.ts";
18
-
19
- type Props = {
20
- children: Snippet;
21
- loading?: Snippet;
22
- bindError?: Snippet<[BindErrorResult]>;
23
- trellisUrl: string;
24
- loginPath?: string;
25
- contract: TrellisClientContract;
26
- onAuthExpired?: () => void;
27
- onAuthFailed?: (error: unknown) => void;
28
- onAuthRequired?: (redirectTo: string) => void;
29
- onBindError?: (result: BindErrorResult) => void;
30
- onNatsConnecting?: () => void;
31
- onNatsConnected?: () => void;
32
- onNatsDisconnect?: () => void;
33
- onNatsReconnecting?: () => void;
34
- onNatsReconnect?: () => void;
35
- onNatsError?: (error: Error) => void;
36
- };
37
-
38
- let {
3
+ ClientAuthHandledError,
4
+ TrellisClient,
5
+ type ClientAuthOptions,
6
+ type ConnectedTrellisClient,
7
+ } from "@qlever-llc/trellis";
8
+ import { onMount } from "svelte";
9
+ import type { TrellisContractLike } from "../context.svelte.ts";
10
+ import { resolveTrellisAppUrl } from "../context.svelte.ts";
11
+ import TrellisContextProvider from "./TrellisContextProvider.svelte";
12
+ import type { TrellisProviderProps } from "./TrellisProvider.types.ts";
13
+
14
+ const {
15
+ trellisApp,
16
+ auth,
17
+ client,
39
18
  children,
40
19
  loading,
41
- bindError,
42
- trellisUrl,
43
- loginPath,
44
- contract,
45
- onAuthExpired,
46
- onAuthFailed,
20
+ error: errorSnippet,
47
21
  onAuthRequired,
48
- onBindError,
49
- onNatsConnecting,
50
- onNatsConnected,
51
- onNatsDisconnect,
52
- onNatsReconnecting,
53
- onNatsReconnect,
54
- onNatsError,
55
- }: Props = $props();
22
+ }: TrellisProviderProps<TContract> = $props();
56
23
 
57
- let bindErrorResult = $state<BindErrorResult | null>(null);
58
- let providerAuth = $state<ReturnType<typeof createAuthState> | null>(null);
24
+ let trellis = $state<ConnectedTrellisClient<TContract> | null>(null);
25
+ let connectError = $state<unknown>(null);
59
26
 
60
- function getProviderAuth() {
61
- if (providerAuth === null) {
62
- providerAuth = createAuthState({ authUrl: trellisUrl, loginPath, contract });
63
- }
64
-
65
- providerAuth.setAuthUrl(trellisUrl);
66
-
67
- return providerAuth;
68
- }
69
-
70
- type InitContext = {
71
- auth: ReturnType<typeof createAuthState>;
72
- nats: NatsState;
73
- trellis: Trellis<TrellisAPI>;
27
+ type SerializableTrellisError = {
28
+ message?: unknown;
29
+ code?: unknown;
30
+ hint?: unknown;
31
+ context?: unknown;
74
32
  };
75
- const isBrowser = typeof window !== "undefined";
76
33
 
77
- function requireTrellisUrl(): string {
78
- const auth = getProviderAuth();
79
- const resolvedTrellisUrl = auth.authUrl;
80
- if (!resolvedTrellisUrl) {
81
- throw new Error("Trellis URL is not configured");
34
+ function maybeSerializableError(
35
+ value: unknown,
36
+ ): SerializableTrellisError | undefined {
37
+ if (!value || typeof value !== "object" || !("toSerializable" in value)) {
38
+ return undefined;
82
39
  }
83
-
84
- return resolvedTrellisUrl;
40
+ const serialize = value.toSerializable;
41
+ if (typeof serialize !== "function") return undefined;
42
+ const serialized = serialize.call(value);
43
+ return serialized && typeof serialized === "object"
44
+ ? (serialized as SerializableTrellisError)
45
+ : undefined;
85
46
  }
86
47
 
87
- function getRedirectTo(): string {
88
- if (typeof window === "undefined") {
89
- return "/";
90
- }
91
-
92
- return window.location.pathname + window.location.search;
48
+ function contextRecord(value: unknown): Record<string, unknown> | undefined {
49
+ return value && typeof value === "object" && !Array.isArray(value)
50
+ ? (value as Record<string, unknown>)
51
+ : undefined;
93
52
  }
94
53
 
95
- function redirectToLogin(redirectTo: string): void {
96
- if (typeof window === "undefined") return;
97
-
98
- const auth = getProviderAuth();
99
- const url = new URL(auth.loginPath, window.location.origin);
100
- url.searchParams.set("redirectTo", redirectTo);
101
- window.location.href = url.toString();
54
+ function logConnectionError(error: unknown): void {
55
+ const serialized = maybeSerializableError(error);
56
+ const context = contextRecord(serialized?.context);
57
+ const causeMessage =
58
+ typeof context?.causeMessage === "string"
59
+ ? context.causeMessage
60
+ : undefined;
61
+ const message =
62
+ typeof serialized?.message === "string"
63
+ ? serialized.message
64
+ : error instanceof Error
65
+ ? error.message
66
+ : String(error);
67
+
68
+ console.error("Error:", error);
102
69
  }
103
70
 
104
- function handleAuthRequired(): void {
105
- const redirectTo = getRedirectTo();
106
- onAuthRequired?.(redirectTo);
107
- if (!onAuthRequired) {
108
- redirectToLogin(redirectTo);
109
- }
110
- }
71
+ onMount(() => {
72
+ let active = true;
111
73
 
112
- async function initialize(): Promise<InitContext | null> {
113
- const result = await AsyncResult.try(async () => {
114
- const auth = getProviderAuth();
115
-
116
- await auth.init();
117
- const bindResult = await auth.handleCallback();
118
-
119
- if (bindResult !== null && bindResult.status !== "bound") {
120
- bindErrorResult = bindResult;
121
- onBindError?.(bindResult);
122
- return null;
123
- }
124
-
125
- if (!auth.isAuthenticated) {
126
- handleAuthRequired();
127
- return null;
74
+ function withBrowserAuthDefaults(
75
+ authOptions: ClientAuthOptions | undefined,
76
+ ): ClientAuthOptions {
77
+ if (authOptions?.mode === "session_key") {
78
+ return authOptions;
128
79
  }
129
80
 
130
- const trellis = await TrellisClient.connect({
131
- trellisUrl: requireTrellisUrl(),
132
- contract,
133
- auth: {
134
- handle: auth.handle ?? undefined,
135
- },
136
- onAuthRequired: ({ loginUrl }) => {
137
- onAuthExpired?.();
138
- const redirectTo = getRedirectTo();
139
- onAuthRequired?.(redirectTo);
140
- if (!onAuthRequired && typeof window !== "undefined") {
141
- window.location.href = loginUrl;
142
- }
143
- },
144
- });
145
-
146
- const natsState = await createConnectedNatsState(trellis.natsConnection, auth, {
147
- onConnecting: onNatsConnecting,
148
- onConnected: onNatsConnected,
149
- onDisconnect: onNatsDisconnect,
150
- onReconnecting: onNatsReconnecting,
151
- onReconnect: onNatsReconnect,
152
- onError: onNatsError,
153
- onAuthRequired: () => {
154
- onAuthExpired?.();
155
- handleAuthRequired();
156
- },
157
- });
158
-
159
81
  return {
160
- auth,
161
- nats: natsState,
162
- trellis,
82
+ ...authOptions,
83
+ currentUrl:
84
+ authOptions?.currentUrl ?? (() => new URL(window.location.href)),
163
85
  };
164
- });
165
-
166
- if (result.isErr()) {
167
- const error = result.error;
168
- getProviderAuth().clearAuth();
169
- onAuthFailed?.(error);
170
- throw error;
171
86
  }
172
87
 
173
- return result.match({
174
- ok: (value) => value,
175
- err: (error) => {
176
- throw error;
177
- },
178
- });
179
- }
180
-
181
- const initPromise = isBrowser ? initialize() : null;
182
- const readyPromise: Promise<InitContext> | null = initPromise?.then((ctx) => {
183
- if (!ctx) {
184
- throw new Error("Trellis context is not available");
88
+ const connectAuth = withBrowserAuthDefaults(auth);
89
+ const trellisUrl = resolveTrellisAppUrl(trellisApp.trellisUrl);
90
+ if (!trellisUrl) {
91
+ connectError = new TypeError(
92
+ "Expected trellisApp to resolve a Trellis URL",
93
+ );
94
+ return;
185
95
  }
186
- return ctx;
187
- }) ?? null;
188
- const natsStatePromise: Promise<NatsState> | null = readyPromise?.then((ctx) => ctx.nats) ?? null;
189
- const trellisPromise: Promise<Trellis<TrellisAPI>> | null = readyPromise?.then((ctx) => ctx.trellis) ?? null;
190
- const natsPromise: Promise<NatsConnection> | null = readyPromise?.then((ctx) => ctx.nats.nc) ?? null;
191
-
192
- if (readyPromise && natsStatePromise && trellisPromise && natsPromise) {
193
- void readyPromise.catch(() => {});
194
- setAuthContext(() => getProviderAuth());
195
- setNatsStateContext(natsStatePromise);
196
- setTrellisContext({
197
- trellis: trellisPromise,
198
- nats: natsPromise,
199
- });
200
- }
201
96
 
202
- onDestroy(() => {
203
- if (!readyPromise) return;
204
-
205
- void readyPromise.then((ctx) => {
206
- void ctx.nats.disconnect();
207
- });
97
+ void (async () => {
98
+ try {
99
+ const connected = await TrellisClient.connect({
100
+ ...client,
101
+ trellisUrl,
102
+ contract: trellisApp.contract,
103
+ auth: connectAuth,
104
+ onAuthRequired: onAuthRequired
105
+ ? async (ctx) => {
106
+ await onAuthRequired(ctx.loginUrl, ctx);
107
+ return { status: "handled" };
108
+ }
109
+ : undefined,
110
+ }).orThrow();
111
+
112
+ if (active) {
113
+ trellis = connected;
114
+ } else {
115
+ await connected.connection.close();
116
+ }
117
+ } catch (error) {
118
+ if (!active) return;
119
+ if (error instanceof ClientAuthHandledError) return;
120
+ logConnectionError(error);
121
+ connectError = error;
122
+ }
123
+ })();
124
+
125
+ return () => {
126
+ active = false;
127
+ const connected = trellis;
128
+ trellis = null;
129
+ if (connected) {
130
+ void connected.connection.close();
131
+ }
132
+ };
208
133
  });
209
134
  </script>
210
135
 
211
- {#if !isBrowser}
212
- {#if loading}
213
- {@render loading()}
136
+ {#if trellis}
137
+ <TrellisContextProvider {trellisApp} {trellis} {children} />
138
+ {:else if connectError}
139
+ {#if errorSnippet}
140
+ {@render errorSnippet(connectError)}
214
141
  {/if}
215
- {:else}
216
- {#await initPromise}
217
- {#if loading}
218
- {@render loading()}
219
- {/if}
220
- {:then ctx}
221
- {#if bindErrorResult}
222
- {#if bindError}
223
- {@render bindError(bindErrorResult)}
224
- {/if}
225
- {:else if ctx}
226
- {@render children()}
227
- {/if}
228
- {/await}
142
+ {:else if loading}
143
+ {@render loading()}
229
144
  {/if}
@@ -0,0 +1,26 @@
1
+ import type {
2
+ ClientAuthOptions,
3
+ ClientAuthRequiredContext,
4
+ ClientOpts,
5
+ } from "@qlever-llc/trellis";
6
+ import type { Snippet } from "svelte";
7
+ import type {
8
+ TrellisAppOwner,
9
+ TrellisContractLike,
10
+ } from "../context.svelte.ts";
11
+
12
+ /** Props accepted by the Svelte Trellis provider component. */
13
+ export type TrellisProviderProps<
14
+ TContract extends TrellisContractLike = TrellisContractLike,
15
+ > = {
16
+ trellisApp: TrellisAppOwner<TContract>;
17
+ auth?: ClientAuthOptions;
18
+ client?: ClientOpts;
19
+ children: Snippet;
20
+ loading?: Snippet;
21
+ error?: Snippet<[unknown]>;
22
+ onAuthRequired?: (
23
+ loginUrl: string,
24
+ context: ClientAuthRequiredContext,
25
+ ) => void | Promise<void>;
26
+ };
@@ -1,50 +1,218 @@
1
- import type { NatsConnection } from "@nats-io/nats-core";
2
- import type { TrellisAPI } from "../../trellis/contracts.ts";
1
+ import type {
2
+ ClientTrellis,
3
+ RuntimeStateStoresForContract,
4
+ TrellisAPI,
5
+ TrellisConnection,
6
+ TrellisConnectionStatus,
7
+ TrellisContractV1,
8
+ } from "@qlever-llc/trellis";
3
9
  import { createContext } from "svelte";
4
- import type { AuthState } from "./state/auth.svelte.ts";
5
- import type { NatsState } from "./state/nats.svelte.ts";
6
-
7
- type TrellisContext = {
8
- trellis: Promise<unknown>;
9
- nats: Promise<NatsConnection>;
10
- };
10
+ import { createSubscriber } from "svelte/reactivity";
11
11
 
12
+ /** Minimal contract shape required to create a typed Trellis Svelte app context. */
12
13
  export type TrellisContractLike<TA extends TrellisAPI = TrellisAPI> = {
14
+ CONTRACT: TrellisContractV1;
15
+ CONTRACT_DIGEST: string;
13
16
  API: {
14
17
  trellis: TA;
15
18
  };
16
19
  };
17
20
 
18
- const [getTrellisContext, setTrellisContextValue] = createContext<TrellisContext>();
19
- const [getNatsStateContext, setNatsStateContextValue] = createContext<Promise<NatsState>>();
20
- const [getAuthContext, setAuthContextValue] = createContext<() => AuthState>();
21
+ /** Real connected Trellis client type exposed by a Svelte app context. */
22
+ export type TrellisClientFor<TContract extends TrellisContractLike> =
23
+ ClientTrellis<
24
+ TContract["API"]["trellis"],
25
+ RuntimeStateStoresForContract<TContract>
26
+ >;
21
27
 
22
- export function setTrellisContext(
23
- ctx: TrellisContext,
24
- ): void {
25
- setTrellisContextValue(ctx);
28
+ /** Minimal client surface required for Trellis Svelte context clients. */
29
+ export type TrellisContextClient = {
30
+ readonly connection: TrellisConnection;
31
+ };
32
+
33
+ /** URL value accepted by a Trellis Svelte app owner. */
34
+ export type TrellisAppUrl = string | URL;
35
+
36
+ /**
37
+ * Trellis URL configuration for a Svelte app owner.
38
+ *
39
+ * A function resolver supports apps whose Trellis instance is selected at
40
+ * runtime. Returning `undefined` means no instance is currently available.
41
+ */
42
+ export type TrellisAppUrlResolver =
43
+ | TrellisAppUrl
44
+ | (() => TrellisAppUrl | undefined);
45
+
46
+ /** Options used to create a Trellis Svelte app owner. */
47
+ export type CreateTrellisAppOptions<
48
+ TContract extends TrellisContractLike = TrellisContractLike,
49
+ > = {
50
+ /** Contract used by this app context and by `TrellisProvider` connections. */
51
+ contract: TContract;
52
+
53
+ /** Trellis URL, or a resolver for runtime-selected Trellis URLs. */
54
+ trellisUrl: TrellisAppUrlResolver;
55
+ };
56
+
57
+ /** Resolves a Trellis app URL config to the string shape required by the client. */
58
+ export function resolveTrellisAppUrl(
59
+ trellisUrl: TrellisAppUrlResolver,
60
+ ): string | undefined {
61
+ const resolved = typeof trellisUrl === "function" ? trellisUrl() : trellisUrl;
62
+ return resolved?.toString();
26
63
  }
27
64
 
28
- export function setNatsStateContext(natsState: Promise<NatsState>): void {
29
- setNatsStateContextValue(natsState);
65
+ /** Svelte-reactive adapter around a framework-neutral Trellis connection. */
66
+ export class SvelteTrellisConnection {
67
+ #connection: TrellisConnection;
68
+ #subscribe: () => void;
69
+
70
+ /** Creates a reactive connection adapter for a connected Trellis runtime. */
71
+ constructor(connection: TrellisConnection) {
72
+ this.#connection = connection;
73
+ this.#subscribe = createSubscriber((update) => {
74
+ return this.#connection.subscribe(() => update());
75
+ });
76
+ }
77
+
78
+ /** Latest connection status, reactive when read by Svelte effects or markup. */
79
+ get status(): TrellisConnectionStatus {
80
+ this.#subscribe();
81
+ return this.#connection.status;
82
+ }
83
+
84
+ /** Closes the underlying Trellis runtime connection. */
85
+ close(): Promise<void> {
86
+ return this.#connection.close();
87
+ }
30
88
  }
31
89
 
32
- export function setAuthContext(getAuth: () => AuthState): void {
33
- setAuthContextValue(getAuth);
90
+ type TrellisAppContext<TClient extends TrellisContextClient> = {
91
+ trellis: TClient;
92
+ connection: SvelteTrellisConnection;
93
+ };
94
+
95
+ const provideTrellisContext = Symbol("provideTrellisContext");
96
+ const trellisAppOwnerBrand: unique symbol = Symbol("trellisAppOwner");
97
+
98
+ /** Minimal branded app owner surface accepted by the Trellis Svelte provider. */
99
+ export type TrellisAppOwner<
100
+ TContract extends TrellisContractLike = TrellisContractLike,
101
+ > = {
102
+ readonly contract: TContract;
103
+ readonly trellisUrl: TrellisAppUrlResolver;
104
+ readonly [trellisAppOwnerBrand]: true;
105
+ };
106
+
107
+ /**
108
+ * Public app-scoped typed Svelte context owner for a Trellis browser app.
109
+ *
110
+ * `TClient` is a type-only facade over the runtime client that `TrellisProvider`
111
+ * installs. Use it with generated client facade types for the same contract.
112
+ */
113
+ export interface TrellisApp<
114
+ TContract extends TrellisContractLike = TrellisContractLike,
115
+ TClient extends TrellisContextClient = TrellisClientFor<TContract>,
116
+ > extends TrellisAppOwner<TContract> {
117
+ /** Contract used by this app context and by `TrellisProvider` connections. */
118
+ readonly contract: TContract;
119
+
120
+ /** Trellis URL configuration used by `TrellisProvider` connections. */
121
+ readonly trellisUrl: TrellisAppUrlResolver;
122
+
123
+ /** Returns the contract-typed connected Trellis client from Svelte context synchronously. */
124
+ getTrellis(): TClient;
125
+
126
+ /** Returns a Svelte-reactive adapter for the real Trellis connection. */
127
+ getConnection(): SvelteTrellisConnection;
34
128
  }
35
129
 
36
- export function getTrellis<T = unknown>(): Promise<T> {
37
- return getTrellisContext().trellis as Promise<T>;
130
+ /** Internal app-scoped typed Svelte context implementation. */
131
+ class TrellisAppImpl<
132
+ TContract extends TrellisContractLike = TrellisContractLike,
133
+ TClient extends TrellisContextClient = TrellisClientFor<TContract>,
134
+ > {
135
+ readonly [trellisAppOwnerBrand] = true as const;
136
+ readonly #contract: TContract;
137
+ readonly #trellisUrl: TrellisAppUrlResolver;
138
+ readonly #getContext: () => TrellisAppContext<TClient>;
139
+ readonly #setContext: (
140
+ context: TrellisAppContext<TClient>,
141
+ ) => TrellisAppContext<TClient>;
142
+
143
+ /** Creates an app-scoped context owner for a specific Trellis contract. */
144
+ constructor(options: CreateTrellisAppOptions<TContract>) {
145
+ const { contract, trellisUrl } = options;
146
+ this.#contract = contract;
147
+ this.#trellisUrl = trellisUrl;
148
+ const [getContext, setContext] = createContext<
149
+ TrellisAppContext<TClient>
150
+ >();
151
+ this.#getContext = getContext;
152
+ this.#setContext = setContext;
153
+ }
154
+
155
+ /** Contract used by this app context and by `TrellisProvider` connections. */
156
+ get contract(): TContract {
157
+ return this.#contract;
158
+ }
159
+
160
+ /** Trellis URL configuration used by `TrellisProvider` connections. */
161
+ get trellisUrl(): TrellisAppUrlResolver {
162
+ return this.#trellisUrl;
163
+ }
164
+
165
+ /** Returns the contract-typed connected Trellis client from Svelte context synchronously. */
166
+ getTrellis(): TClient {
167
+ return this.#getContext().trellis;
168
+ }
169
+
170
+ /** Returns a Svelte-reactive adapter for the real Trellis connection. */
171
+ getConnection(): SvelteTrellisConnection {
172
+ return this.#getContext().connection;
173
+ }
174
+
175
+ /** Installs the connected Trellis runtime into Svelte context synchronously. */
176
+ [provideTrellisContext](trellis: TrellisContextClient): void {
177
+ this.#setContext({
178
+ trellis: trellis as TClient,
179
+ connection: new SvelteTrellisConnection(trellis.connection),
180
+ });
181
+ }
38
182
  }
39
183
 
40
- export function getNats(): Promise<NatsConnection> {
41
- return getTrellisContext().nats;
184
+ /**
185
+ * Creates an app-scoped typed Svelte context owner for a Trellis contract and URL.
186
+ *
187
+ * The optional `TClient` type parameter is a type-only facade over the connected
188
+ * runtime client. It should be a generated client facade for `options.contract`.
189
+ */
190
+ export function createTrellisApp<
191
+ TContract extends TrellisContractLike,
192
+ TClient extends TrellisContextClient = TrellisClientFor<TContract>,
193
+ >(
194
+ options: CreateTrellisAppOptions<TContract>,
195
+ ): TrellisApp<TContract, TClient> {
196
+ return new TrellisAppImpl<TContract, TClient>(options);
42
197
  }
43
198
 
44
- export function getNatsState(): Promise<NatsState> {
45
- return getNatsStateContext();
199
+ function isTrellisAppImpl(
200
+ app: TrellisAppOwner,
201
+ ): app is TrellisAppImpl<TrellisContractLike> {
202
+ return app instanceof TrellisAppImpl;
46
203
  }
47
204
 
48
- export function getAuth(): AuthState {
49
- return getAuthContext()();
205
+ /**
206
+ * Internal provider helper that synchronously installs connected Trellis context.
207
+ *
208
+ * This is intentionally not exported from `src/index.ts`.
209
+ */
210
+ export function provideConnectedTrellisContext(
211
+ app: TrellisAppOwner,
212
+ trellis: TrellisContextClient,
213
+ ): void {
214
+ if (!isTrellisAppImpl(app)) {
215
+ throw new TypeError("Expected an app created by createTrellisApp");
216
+ }
217
+ app[provideTrellisContext](trellis);
50
218
  }