@qlever-llc/trellis-svelte 0.6.0 → 0.7.0-rc.2
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 +5 -1
- package/package.json +3 -3
- package/src/components/TrellisProvider.svelte +64 -30
- package/src/context.svelte.ts +6 -58
- package/src/index.ts +3 -3
- package/src/state/auth.svelte.ts +4 -2
- package/src/state/nats.svelte.ts +48 -1
- package/src/state/trellis.svelte.ts +7 -2
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ Svelte integration for Trellis browser applications.
|
|
|
4
4
|
|
|
5
5
|
Provides `TrellisProvider` for app-level wiring, along with reactive helpers for auth state, NATS connection state, and Trellis client state.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Use `TrellisProvider` as the primary integration surface:
|
|
8
|
+
|
|
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
|
|
8
12
|
|
|
9
13
|
Uses the contract/runtime model from `@qlever-llc/trellis/contracts` and `@qlever-llc/trellis`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qlever-llc/trellis-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-rc.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Svelte components and state helpers for Trellis browser applications.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "https://github.com/Qlever-LLC/trellis"
|
|
13
|
+
"url": "git+https://github.com/Qlever-LLC/trellis.git"
|
|
14
14
|
},
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"access": "public"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@nats-io/nats-core": "^3.3.1",
|
|
31
|
-
"@qlever-llc/trellis": "^0.
|
|
31
|
+
"@qlever-llc/trellis": "^0.7.0-rc.2",
|
|
32
32
|
"typebox": "^1.0.15"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { NatsConnection } from "@nats-io/nats-core";
|
|
3
3
|
import { AsyncResult } from "@qlever-llc/result";
|
|
4
|
+
import type { Trellis, TrellisAPI } from "@qlever-llc/trellis";
|
|
4
5
|
import { onDestroy } from "svelte";
|
|
5
6
|
import type { Snippet } from "svelte";
|
|
6
7
|
import {
|
|
7
|
-
setAppContext,
|
|
8
8
|
setAuthContext,
|
|
9
9
|
setNatsStateContext,
|
|
10
10
|
setTrellisContext,
|
|
11
11
|
} from "../context.svelte.ts";
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
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 "@qlever-llc/trellis";
|
|
19
18
|
|
|
20
19
|
type Props = {
|
|
21
20
|
children: Snippet;
|
|
22
21
|
loading?: Snippet;
|
|
23
22
|
bindError?: Snippet<[BindErrorResult]>;
|
|
24
|
-
|
|
23
|
+
trellisUrl: string;
|
|
24
|
+
loginPath?: string;
|
|
25
|
+
contract: TrellisClientContract;
|
|
25
26
|
onAuthExpired?: () => void;
|
|
26
27
|
onAuthFailed?: (error: unknown) => void;
|
|
27
28
|
onAuthRequired?: (redirectTo: string) => void;
|
|
@@ -38,7 +39,9 @@
|
|
|
38
39
|
children,
|
|
39
40
|
loading,
|
|
40
41
|
bindError,
|
|
41
|
-
|
|
42
|
+
trellisUrl,
|
|
43
|
+
loginPath,
|
|
44
|
+
contract,
|
|
42
45
|
onAuthExpired,
|
|
43
46
|
onAuthFailed,
|
|
44
47
|
onAuthRequired,
|
|
@@ -52,14 +55,35 @@
|
|
|
52
55
|
}: Props = $props();
|
|
53
56
|
|
|
54
57
|
let bindErrorResult = $state<BindErrorResult | null>(null);
|
|
58
|
+
let providerAuth = $state<ReturnType<typeof createAuthState> | null>(null);
|
|
59
|
+
|
|
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
|
+
}
|
|
55
69
|
|
|
56
70
|
type InitContext = {
|
|
57
|
-
auth:
|
|
71
|
+
auth: ReturnType<typeof createAuthState>;
|
|
58
72
|
nats: NatsState;
|
|
59
|
-
trellis:
|
|
73
|
+
trellis: Trellis<TrellisAPI>;
|
|
60
74
|
};
|
|
61
75
|
const isBrowser = typeof window !== "undefined";
|
|
62
76
|
|
|
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");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return resolvedTrellisUrl;
|
|
85
|
+
}
|
|
86
|
+
|
|
63
87
|
function getRedirectTo(): string {
|
|
64
88
|
if (typeof window === "undefined") {
|
|
65
89
|
return "/";
|
|
@@ -71,7 +95,8 @@
|
|
|
71
95
|
function redirectToLogin(redirectTo: string): void {
|
|
72
96
|
if (typeof window === "undefined") return;
|
|
73
97
|
|
|
74
|
-
const
|
|
98
|
+
const auth = getProviderAuth();
|
|
99
|
+
const url = new URL(auth.loginPath, window.location.origin);
|
|
75
100
|
url.searchParams.set("redirectTo", redirectTo);
|
|
76
101
|
window.location.href = url.toString();
|
|
77
102
|
}
|
|
@@ -86,11 +111,10 @@
|
|
|
86
111
|
|
|
87
112
|
async function initialize(): Promise<InitContext | null> {
|
|
88
113
|
const result = await AsyncResult.try(async () => {
|
|
89
|
-
const
|
|
114
|
+
const auth = getProviderAuth();
|
|
90
115
|
|
|
91
|
-
await
|
|
92
|
-
const bindResult = await
|
|
93
|
-
authState.cleanupCallbackUrl();
|
|
116
|
+
await auth.init();
|
|
117
|
+
const bindResult = await auth.handleCallback();
|
|
94
118
|
|
|
95
119
|
if (bindResult !== null && bindResult.status !== "bound") {
|
|
96
120
|
bindErrorResult = bindResult;
|
|
@@ -98,12 +122,28 @@
|
|
|
98
122
|
return null;
|
|
99
123
|
}
|
|
100
124
|
|
|
101
|
-
if (!
|
|
125
|
+
if (!auth.isAuthenticated) {
|
|
102
126
|
handleAuthRequired();
|
|
103
127
|
return null;
|
|
104
128
|
}
|
|
105
129
|
|
|
106
|
-
const
|
|
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, {
|
|
107
147
|
onConnecting: onNatsConnecting,
|
|
108
148
|
onConnected: onNatsConnected,
|
|
109
149
|
onDisconnect: onNatsDisconnect,
|
|
@@ -116,20 +156,16 @@
|
|
|
116
156
|
},
|
|
117
157
|
});
|
|
118
158
|
|
|
119
|
-
const trellisState = await createTrellisState(authState, natsState, {
|
|
120
|
-
contract: authState.contract,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
159
|
return {
|
|
124
|
-
auth
|
|
160
|
+
auth,
|
|
125
161
|
nats: natsState,
|
|
126
|
-
trellis
|
|
162
|
+
trellis,
|
|
127
163
|
};
|
|
128
164
|
});
|
|
129
165
|
|
|
130
166
|
if (result.isErr()) {
|
|
131
167
|
const error = result.error;
|
|
132
|
-
|
|
168
|
+
getProviderAuth().clearAuth();
|
|
133
169
|
onAuthFailed?.(error);
|
|
134
170
|
throw error;
|
|
135
171
|
}
|
|
@@ -150,13 +186,12 @@
|
|
|
150
186
|
return ctx;
|
|
151
187
|
}) ?? null;
|
|
152
188
|
const natsStatePromise: Promise<NatsState> | null = readyPromise?.then((ctx) => ctx.nats) ?? null;
|
|
153
|
-
const trellisPromise: Promise<
|
|
189
|
+
const trellisPromise: Promise<Trellis<TrellisAPI>> | null = readyPromise?.then((ctx) => ctx.trellis) ?? null;
|
|
154
190
|
const natsPromise: Promise<NatsConnection> | null = readyPromise?.then((ctx) => ctx.nats.nc) ?? null;
|
|
155
191
|
|
|
156
192
|
if (readyPromise && natsStatePromise && trellisPromise && natsPromise) {
|
|
157
193
|
void readyPromise.catch(() => {});
|
|
158
|
-
|
|
159
|
-
setAuthContext(() => app.auth);
|
|
194
|
+
setAuthContext(() => getProviderAuth());
|
|
160
195
|
setNatsStateContext(natsStatePromise);
|
|
161
196
|
setTrellisContext({
|
|
162
197
|
trellis: trellisPromise,
|
|
@@ -168,7 +203,6 @@
|
|
|
168
203
|
if (!readyPromise) return;
|
|
169
204
|
|
|
170
205
|
void readyPromise.then((ctx) => {
|
|
171
|
-
ctx.trellis.stop();
|
|
172
206
|
void ctx.nats.disconnect();
|
|
173
207
|
});
|
|
174
208
|
});
|
package/src/context.svelte.ts
CHANGED
|
@@ -1,52 +1,23 @@
|
|
|
1
|
-
import type { BaseError, Result } from "@qlever-llc/result";
|
|
2
1
|
import type { NatsConnection } from "@nats-io/nats-core";
|
|
3
|
-
import type {
|
|
2
|
+
import type { TrellisAPI } from "@qlever-llc/trellis";
|
|
4
3
|
import { createContext } from "svelte";
|
|
5
|
-
import {
|
|
4
|
+
import type { AuthState } from "./state/auth.svelte.ts";
|
|
6
5
|
import type { NatsState } from "./state/nats.svelte.ts";
|
|
7
6
|
|
|
8
7
|
type TrellisContext = {
|
|
9
|
-
trellis: Promise<
|
|
8
|
+
trellis: Promise<unknown>;
|
|
10
9
|
nats: Promise<NatsConnection>;
|
|
11
10
|
};
|
|
12
11
|
|
|
13
|
-
type TrellisContractLike<TA extends TrellisAPI = TrellisAPI> = {
|
|
12
|
+
export type TrellisContractLike<TA extends TrellisAPI = TrellisAPI> = {
|
|
14
13
|
API: {
|
|
15
14
|
trellis: TA;
|
|
16
15
|
};
|
|
17
16
|
};
|
|
18
17
|
|
|
19
|
-
type RequestOpts = {
|
|
20
|
-
timeout?: number;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type TypedTrellis<TA extends TrellisAPI> = Omit<Trellis<TrellisAPI>, "request" | "requestOrThrow"> & {
|
|
24
|
-
request<M extends keyof TA["rpc"] & string>(
|
|
25
|
-
method: M,
|
|
26
|
-
input: InferSchemaType<TA["rpc"][M]["input"]>,
|
|
27
|
-
opts?: RequestOpts,
|
|
28
|
-
): Promise<Result<InferSchemaType<TA["rpc"][M]["output"]>, BaseError>>;
|
|
29
|
-
requestOrThrow<M extends keyof TA["rpc"] & string>(
|
|
30
|
-
method: M,
|
|
31
|
-
input: InferSchemaType<TA["rpc"][M]["input"]>,
|
|
32
|
-
opts?: RequestOpts,
|
|
33
|
-
): Promise<InferSchemaType<TA["rpc"][M]["output"]>>;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
function createTypedTrellis<TA extends TrellisAPI>(trellis: Trellis<TrellisAPI>): TypedTrellis<TA> {
|
|
37
|
-
return trellis as TypedTrellis<TA>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type BoundTrellisApp<TContract extends TrellisContractLike> = {
|
|
41
|
-
auth: AuthState;
|
|
42
|
-
signIn: (options?: SignInOptions) => Promise<never>;
|
|
43
|
-
getTrellis: () => Promise<TypedTrellis<TContract["API"]["trellis"]>>;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
18
|
const [getTrellisContext, setTrellisContextValue] = createContext<TrellisContext>();
|
|
47
19
|
const [getNatsStateContext, setNatsStateContextValue] = createContext<Promise<NatsState>>();
|
|
48
20
|
const [getAuthContext, setAuthContextValue] = createContext<() => AuthState>();
|
|
49
|
-
const [getAppContext, setAppContextValue] = createContext<() => unknown>();
|
|
50
21
|
|
|
51
22
|
export function setTrellisContext(
|
|
52
23
|
ctx: TrellisContext,
|
|
@@ -62,31 +33,8 @@ export function setAuthContext(getAuth: () => AuthState): void {
|
|
|
62
33
|
setAuthContextValue(getAuth);
|
|
63
34
|
}
|
|
64
35
|
|
|
65
|
-
export function
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function createTrellisApp<TContract extends TrellisContractLike>(
|
|
70
|
-
config: AuthStateConfig & { contract: TContract },
|
|
71
|
-
): BoundTrellisApp<TContract> {
|
|
72
|
-
const auth = createAuthState(config);
|
|
73
|
-
|
|
74
|
-
let app!: BoundTrellisApp<TContract>;
|
|
75
|
-
app = {
|
|
76
|
-
auth,
|
|
77
|
-
signIn: (options) => auth.signIn(options),
|
|
78
|
-
getTrellis: () => {
|
|
79
|
-
if (getAppContext()() !== app) {
|
|
80
|
-
throw new Error("getTrellis() was called outside the matching TrellisProvider");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return getTrellisContext().trellis.then((trellis) =>
|
|
84
|
-
createTypedTrellis<TContract["API"]["trellis"]>(trellis)
|
|
85
|
-
);
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
return app;
|
|
36
|
+
export function getTrellis<T = unknown>(): Promise<T> {
|
|
37
|
+
return getTrellisContext().trellis as Promise<T>;
|
|
90
38
|
}
|
|
91
39
|
|
|
92
40
|
export function getNats(): Promise<NatsConnection> {
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { default as TrellisProvider } from "./components/TrellisProvider.svelte";
|
|
2
|
-
export {
|
|
3
|
-
export type {
|
|
2
|
+
export { getAuth, getNats, getNatsState, getTrellis } from "./context.svelte.ts";
|
|
3
|
+
export type { TrellisContractLike } from "./context.svelte.ts";
|
|
4
4
|
export { createPortalFlow, PortalFlowController, type CreatePortalFlowConfig } from "./portal_flow.svelte.ts";
|
|
5
5
|
export { AuthState, type BindErrorResult, type BindResult, createAuthState, type SignInOptions } from "./state/auth.svelte.ts";
|
|
6
6
|
export type { NatsStateConfig, Status as NatsStatus } from "./state/nats.svelte.ts";
|
|
7
|
-
export { createNatsState, NatsState } from "./state/nats.svelte.ts";
|
|
7
|
+
export { createConnectedNatsState, createNatsState, NatsState } from "./state/nats.svelte.ts";
|
|
8
8
|
export type { TrellisClientContract, TrellisStateConfig } from "./state/trellis.svelte.ts";
|
|
9
9
|
export { createTrellisState, TrellisState } from "./state/trellis.svelte.ts";
|
package/src/state/auth.svelte.ts
CHANGED
|
@@ -344,13 +344,15 @@ export class AuthState {
|
|
|
344
344
|
/**
|
|
345
345
|
* Clean up the callback URL by removing the auth flow query params.
|
|
346
346
|
*/
|
|
347
|
-
cleanupCallbackUrl(url: string = window.location.href):
|
|
347
|
+
cleanupCallbackUrl(url: string = window.location.href): string | null {
|
|
348
348
|
const parsed = new URL(url);
|
|
349
349
|
if (parsed.searchParams.has("flowId") || parsed.searchParams.has("authError")) {
|
|
350
350
|
parsed.searchParams.delete("flowId");
|
|
351
351
|
parsed.searchParams.delete("authError");
|
|
352
|
-
|
|
352
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
353
353
|
}
|
|
354
|
+
|
|
355
|
+
return null;
|
|
354
356
|
}
|
|
355
357
|
|
|
356
358
|
/**
|
package/src/state/nats.svelte.ts
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type NatsConnection,
|
|
4
4
|
wsconnect,
|
|
5
5
|
} from "@nats-io/nats-core";
|
|
6
|
-
import {
|
|
6
|
+
import type { Trellis } from "@qlever-llc/trellis";
|
|
7
7
|
import {
|
|
8
8
|
getPublicSessionKey,
|
|
9
9
|
natsConnectSigForBindingToken,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type AuthRenewBindingTokenOutput,
|
|
19
19
|
} from "@qlever-llc/trellis/sdk/auth";
|
|
20
20
|
import type { AuthState } from "./auth.svelte.ts";
|
|
21
|
+
import { createClient } from "../../../trellis/client.ts";
|
|
21
22
|
|
|
22
23
|
const AUTH_RENEW_API = {
|
|
23
24
|
rpc: {
|
|
@@ -202,6 +203,44 @@ export class NatsState {
|
|
|
202
203
|
return state;
|
|
203
204
|
}
|
|
204
205
|
|
|
206
|
+
static async fromConnection(
|
|
207
|
+
nc: NatsConnection,
|
|
208
|
+
authState: AuthState,
|
|
209
|
+
config: NatsStateConfig,
|
|
210
|
+
): Promise<NatsState> {
|
|
211
|
+
config.onConnecting?.();
|
|
212
|
+
|
|
213
|
+
await authState.init();
|
|
214
|
+
|
|
215
|
+
if (!authState.isAuthenticated) {
|
|
216
|
+
config.onAuthRequired?.();
|
|
217
|
+
throw new Error("Not authenticated: missing binding token or sentinel");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { handle, sentinel } = requireBrowserAuth(authState);
|
|
221
|
+
const servers = resolveServers(authState, config);
|
|
222
|
+
const bindingToken = authState.bindingToken;
|
|
223
|
+
if (!bindingToken) {
|
|
224
|
+
throw new Error("Not authenticated: missing binding token or sentinel");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const state = new NatsState(
|
|
228
|
+
nc,
|
|
229
|
+
"connected",
|
|
230
|
+
servers,
|
|
231
|
+
authState,
|
|
232
|
+
config,
|
|
233
|
+
handle,
|
|
234
|
+
{ value: await buildNatsAuthToken(handle, bindingToken) },
|
|
235
|
+
sentinel,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
config.onConnected?.();
|
|
239
|
+
await state.#renewBindingToken();
|
|
240
|
+
|
|
241
|
+
return state;
|
|
242
|
+
}
|
|
243
|
+
|
|
205
244
|
/**
|
|
206
245
|
* Reconnect to NATS with the existing binding token (if still valid).
|
|
207
246
|
* Uses stored sentinel credentials with jwtAuthenticator per ADR.
|
|
@@ -361,3 +400,11 @@ export async function createNatsState(
|
|
|
361
400
|
): Promise<NatsState> {
|
|
362
401
|
return NatsState.connect(authState, config);
|
|
363
402
|
}
|
|
403
|
+
|
|
404
|
+
export async function createConnectedNatsState(
|
|
405
|
+
nc: NatsConnection,
|
|
406
|
+
authState: AuthState,
|
|
407
|
+
config: NatsStateConfig,
|
|
408
|
+
): Promise<NatsState> {
|
|
409
|
+
return NatsState.fromConnection(nc, authState, config);
|
|
410
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createClient,
|
|
3
2
|
defineContract,
|
|
4
3
|
type Trellis,
|
|
5
4
|
type TrellisAPI,
|
|
6
5
|
type TrellisContractV1,
|
|
7
6
|
} from "@qlever-llc/trellis";
|
|
7
|
+
import { TrellisClient } from "../../../trellis/client_connect.ts";
|
|
8
|
+
import { createClient } from "../../../trellis/client.ts";
|
|
8
9
|
import { getPublicSessionKey, signBytes } from "@qlever-llc/trellis/auth";
|
|
9
10
|
import type { AuthState } from "./auth.svelte.ts";
|
|
10
11
|
import type { NatsState } from "./nats.svelte.ts";
|
|
@@ -25,7 +26,7 @@ const DEFAULT_TRELLIS_CONTRACT = defineContract({
|
|
|
25
26
|
id: "trellis.svelte.browser@v1",
|
|
26
27
|
displayName: "Trellis Svelte Browser Client",
|
|
27
28
|
description: "Represent a browser client that only uses its locally declared Trellis APIs.",
|
|
28
|
-
kind: "
|
|
29
|
+
kind: "app",
|
|
29
30
|
}) satisfies TrellisClientContract<TrellisAPI>;
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -67,6 +68,10 @@ export class TrellisState<TApi extends TrellisAPI = TrellisAPI> {
|
|
|
67
68
|
return new TrellisState<TApi>(trellis);
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
static fromTrellis<TApi extends TrellisAPI>(trellis: Trellis<TApi>): TrellisState<TApi> {
|
|
72
|
+
return new TrellisState<TApi>(trellis);
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
stop(): void {
|
|
71
76
|
// no-op (kept for convenience)
|
|
72
77
|
}
|