@qlever-llc/trellis 0.10.12 → 0.10.14
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/esm/auth/browser.js +0 -1
- package/esm/auth.js +0 -1
- package/esm/browser.d.ts +29 -2
- package/esm/browser.d.ts.map +1 -1
- package/esm/browser.js +14 -3
- package/esm/client_connect.d.ts.map +1 -1
- package/esm/client_connect.js +7 -3
- package/esm/contract_support/mod.d.ts +6 -10
- package/esm/contract_support/mod.d.ts.map +1 -1
- package/esm/contract_support/mod.js +42 -31
- package/esm/contract_support/protocol.d.ts +8 -13
- package/esm/contract_support/protocol.d.ts.map +1 -1
- package/esm/contract_support/protocol.js +4 -5
- package/esm/contract_support/schema_pointers.d.ts +6 -0
- package/esm/contract_support/schema_pointers.d.ts.map +1 -1
- package/esm/contract_support/schema_pointers.js +59 -7
- package/esm/contracts.js +0 -1
- package/esm/device.d.ts +0 -29
- package/esm/device.d.ts.map +1 -1
- package/esm/device.js +10 -1
- package/esm/errors/index.js +0 -1
- package/esm/index.d.ts +3 -4
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -4
- package/esm/runtime_transport.d.ts.map +1 -1
- package/esm/runtime_transport.js +4 -2
- package/esm/server/health_rpc.d.ts +4 -2
- package/esm/server/health_rpc.d.ts.map +1 -1
- package/esm/server/health_rpc.js +6 -1
- package/esm/server/service.d.ts +12 -37
- package/esm/server/service.d.ts.map +1 -1
- package/esm/server/service.js +138 -103
- package/esm/server.js +3 -3
- package/esm/service/outbox_inbox.d.ts +1 -6
- package/esm/service/outbox_inbox.d.ts.map +1 -1
- package/esm/service/outbox_inbox.js +0 -21
- package/esm/telemetry/env.d.ts.map +1 -1
- package/esm/telemetry/env.js +5 -6
- package/esm/telemetry/metrics.d.ts +0 -7
- package/esm/telemetry/metrics.d.ts.map +1 -1
- package/esm/trellis.d.ts +1 -19
- package/esm/trellis.d.ts.map +1 -1
- package/esm/trellis.js +12 -8
- package/package.json +2 -2
- package/script/auth/browser.js +0 -1
- package/script/auth.js +0 -1
- package/script/browser.d.ts +29 -2
- package/script/browser.d.ts.map +1 -1
- package/script/browser.js +75 -17
- package/script/client_connect.d.ts.map +1 -1
- package/script/client_connect.js +7 -36
- package/script/contract_support/mod.d.ts +6 -10
- package/script/contract_support/mod.d.ts.map +1 -1
- package/script/contract_support/mod.js +46 -32
- package/script/contract_support/protocol.d.ts +8 -13
- package/script/contract_support/protocol.d.ts.map +1 -1
- package/script/contract_support/protocol.js +5 -6
- package/script/contract_support/schema_pointers.d.ts +6 -0
- package/script/contract_support/schema_pointers.d.ts.map +1 -1
- package/script/contract_support/schema_pointers.js +59 -7
- package/script/contracts.js +0 -1
- package/script/device.d.ts +0 -29
- package/script/device.d.ts.map +1 -1
- package/script/device.js +10 -1
- package/script/errors/index.js +0 -1
- package/script/index.d.ts +3 -4
- package/script/index.d.ts.map +1 -1
- package/script/index.js +1 -6
- package/script/runtime_transport.d.ts.map +1 -1
- package/script/runtime_transport.js +4 -2
- package/script/server/health_rpc.d.ts +4 -2
- package/script/server/health_rpc.d.ts.map +1 -1
- package/script/server/health_rpc.js +6 -1
- package/script/server/service.d.ts +12 -37
- package/script/server/service.d.ts.map +1 -1
- package/script/server/service.js +148 -112
- package/script/server.js +3 -3
- package/script/service/outbox_inbox.d.ts +1 -6
- package/script/service/outbox_inbox.d.ts.map +1 -1
- package/script/service/outbox_inbox.js +0 -21
- package/script/telemetry/env.d.ts.map +1 -1
- package/script/telemetry/env.js +5 -39
- package/script/telemetry/metrics.d.ts +0 -7
- package/script/telemetry/metrics.d.ts.map +1 -1
- package/script/trellis.d.ts +1 -19
- package/script/trellis.d.ts.map +1 -1
- package/script/trellis.js +12 -8
- package/src/browser.ts +200 -2
- package/src/client_connect.ts +10 -2
- package/src/contract_support/mod.ts +73 -45
- package/src/contract_support/protocol.ts +16 -7
- package/src/contract_support/schema_pointers.ts +80 -7
- package/src/device.ts +10 -2
- package/src/index.ts +3 -4
- package/src/runtime_transport.ts +6 -1
- package/src/server/health_rpc.ts +7 -2
- package/src/server/service.ts +177 -126
- package/src/server.ts +3 -3
- package/src/service/outbox_inbox.ts +1 -28
- package/src/telemetry/env.ts +10 -7
- package/src/trellis.ts +22 -13
package/src/server/service.ts
CHANGED
|
@@ -5,14 +5,13 @@ import {
|
|
|
5
5
|
type NatsConnection,
|
|
6
6
|
type Subscription,
|
|
7
7
|
} from "@nats-io/nats-core";
|
|
8
|
+
import type { KVError, StoreError } from "../errors/index.js";
|
|
9
|
+
import { TypedKV } from "../kv.js";
|
|
8
10
|
import {
|
|
9
|
-
type KVError,
|
|
10
|
-
type StoreError,
|
|
11
11
|
type StoreWaitOptions,
|
|
12
|
-
TypedKV,
|
|
13
12
|
TypedStore,
|
|
14
13
|
TypedStoreEntry,
|
|
15
|
-
} from "../
|
|
14
|
+
} from "../store.js";
|
|
16
15
|
import { sdk as trellisAuth } from "../sdk/auth.js";
|
|
17
16
|
import {
|
|
18
17
|
TrellisServiceRuntime,
|
|
@@ -745,7 +744,21 @@ async function fetchServiceBootstrapInfo(args: {
|
|
|
745
744
|
}
|
|
746
745
|
}
|
|
747
746
|
|
|
748
|
-
export class StoreHandle {
|
|
747
|
+
export abstract class StoreHandle {
|
|
748
|
+
abstract readonly binding: ResourceBindingStore;
|
|
749
|
+
|
|
750
|
+
abstract open(): AsyncResult<TypedStore, StoreError>;
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Waits for a staged object to appear in the bound store and returns its entry.
|
|
754
|
+
*/
|
|
755
|
+
abstract waitFor(
|
|
756
|
+
key: string,
|
|
757
|
+
options?: StoreWaitOptions,
|
|
758
|
+
): AsyncResult<TypedStoreEntry, StoreError>;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
class InternalStoreHandle extends StoreHandle {
|
|
749
762
|
readonly binding: ResourceBindingStore;
|
|
750
763
|
readonly #nc: NatsConnection;
|
|
751
764
|
|
|
@@ -754,6 +767,7 @@ export class StoreHandle {
|
|
|
754
767
|
binding: ResourceBindingStore,
|
|
755
768
|
token: typeof storeHandleConstructorToken,
|
|
756
769
|
) {
|
|
770
|
+
super();
|
|
757
771
|
if (token !== storeHandleConstructorToken) {
|
|
758
772
|
throw new TypeError(
|
|
759
773
|
"StoreHandle instances are created by TrellisService",
|
|
@@ -1453,7 +1467,6 @@ export type BoundTrellisService<
|
|
|
1453
1467
|
TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>,
|
|
1454
1468
|
| "name"
|
|
1455
1469
|
| "auth"
|
|
1456
|
-
| "nc"
|
|
1457
1470
|
| "kv"
|
|
1458
1471
|
| "store"
|
|
1459
1472
|
| "connection"
|
|
@@ -1797,6 +1810,9 @@ export type TrellisServiceInternalConnectArgs<
|
|
|
1797
1810
|
contractKv?: TKv;
|
|
1798
1811
|
};
|
|
1799
1812
|
|
|
1813
|
+
/**
|
|
1814
|
+
* @internal Shared by Trellis-owned service bootstrap paths.
|
|
1815
|
+
*/
|
|
1800
1816
|
export async function createConnectedService<
|
|
1801
1817
|
TOwnedApi extends TrellisAPI,
|
|
1802
1818
|
TTrellisApi extends TrellisAPI,
|
|
@@ -1991,12 +2007,12 @@ export async function createConnectedService<
|
|
|
1991
2007
|
stores: Object.fromEntries(
|
|
1992
2008
|
Object.entries(args.bindings.store ?? {}).map(([alias, binding]) => [
|
|
1993
2009
|
alias,
|
|
1994
|
-
new
|
|
2010
|
+
new InternalStoreHandle(args.nc, binding, storeHandleConstructorToken),
|
|
1995
2011
|
]),
|
|
1996
2012
|
),
|
|
1997
2013
|
});
|
|
1998
2014
|
|
|
1999
|
-
const service =
|
|
2015
|
+
const service = Reflect.construct(TrellisService, [
|
|
2000
2016
|
args.name,
|
|
2001
2017
|
args.auth,
|
|
2002
2018
|
args.nc,
|
|
@@ -2011,7 +2027,7 @@ export async function createConnectedService<
|
|
|
2011
2027
|
stopHealthPublishing,
|
|
2012
2028
|
connection,
|
|
2013
2029
|
trellisServiceConstructorToken,
|
|
2014
|
-
)
|
|
2030
|
+
]) as TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>;
|
|
2015
2031
|
handlerResources = {
|
|
2016
2032
|
kv: service.kv,
|
|
2017
2033
|
store: service.store,
|
|
@@ -2757,6 +2773,143 @@ function createBoundJobsFacade<
|
|
|
2757
2773
|
return boundJobs as BoundJobsFacadeOf<TJobs, TTrellisApi, TKv, TDeps>;
|
|
2758
2774
|
}
|
|
2759
2775
|
|
|
2776
|
+
/**
|
|
2777
|
+
* Connects a service with caller-supplied runtime dependencies for tests and
|
|
2778
|
+
* Trellis-owned internals. This helper is intentionally not re-exported from
|
|
2779
|
+
* public package subpaths.
|
|
2780
|
+
*
|
|
2781
|
+
* @internal
|
|
2782
|
+
*/
|
|
2783
|
+
export function connectTrellisServiceWithRuntimeDeps<
|
|
2784
|
+
const TContract extends ServiceContract<
|
|
2785
|
+
TrellisAPI,
|
|
2786
|
+
TrellisAPI | undefined,
|
|
2787
|
+
ContractJobsMetadata,
|
|
2788
|
+
ContractKvMetadata
|
|
2789
|
+
>,
|
|
2790
|
+
>(
|
|
2791
|
+
args: TrellisServiceConnectArgs<TContract>,
|
|
2792
|
+
deps: Partial<TrellisServiceRuntimeDeps>,
|
|
2793
|
+
): AsyncResult<
|
|
2794
|
+
TrellisService<
|
|
2795
|
+
ContractOwnedApi<TContract>,
|
|
2796
|
+
ContractTrellisApi<TContract>,
|
|
2797
|
+
ContractJobsOf<TContract>,
|
|
2798
|
+
ContractKvOf<TContract>
|
|
2799
|
+
>,
|
|
2800
|
+
TransportError | UnexpectedError
|
|
2801
|
+
> {
|
|
2802
|
+
return AsyncResult.from((async () => {
|
|
2803
|
+
try {
|
|
2804
|
+
type TOwnedApi = ContractOwnedApi<TContract>;
|
|
2805
|
+
type TTrellisApi = ContractTrellisApi<TContract>;
|
|
2806
|
+
|
|
2807
|
+
const runtimeDeps = {
|
|
2808
|
+
...(await loadDefaultServiceRuntimeDeps()),
|
|
2809
|
+
...deps,
|
|
2810
|
+
} satisfies TrellisServiceRuntimeDeps;
|
|
2811
|
+
if (automaticTelemetryEnabled(args.telemetry)) {
|
|
2812
|
+
runtimeDeps.initTelemetry?.(args.name);
|
|
2813
|
+
}
|
|
2814
|
+
const auth = await createAuth({ sessionKeySeed: args.sessionKeySeed });
|
|
2815
|
+
const bootstrapLog = resolveServiceLogger(args.server?.log);
|
|
2816
|
+
const bootstrap = await fetchServiceBootstrapInfo({
|
|
2817
|
+
trellisUrl: args.trellisUrl,
|
|
2818
|
+
serviceName: args.name,
|
|
2819
|
+
contractId: args.contract.CONTRACT_ID,
|
|
2820
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2821
|
+
contract: args.contract.CONTRACT,
|
|
2822
|
+
auth,
|
|
2823
|
+
log: bootstrapLog,
|
|
2824
|
+
});
|
|
2825
|
+
const { authenticator: authTokenAuthenticator, inboxPrefix } = await auth
|
|
2826
|
+
.natsConnectOptions({
|
|
2827
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2830
|
+
let nc: NatsConnection;
|
|
2831
|
+
try {
|
|
2832
|
+
nc = await runtimeDeps.connect({
|
|
2833
|
+
servers: selectRuntimeTransportServers(
|
|
2834
|
+
bootstrap.connectInfo.transports,
|
|
2835
|
+
),
|
|
2836
|
+
maxReconnectAttempts: DEFAULT_RUNTIME_MAX_RECONNECT_ATTEMPTS,
|
|
2837
|
+
waitOnFirstConnect: DEFAULT_SERVICE_RUNTIME_WAIT_ON_FIRST_CONNECT,
|
|
2838
|
+
inboxPrefix,
|
|
2839
|
+
authenticator: [
|
|
2840
|
+
authTokenAuthenticator,
|
|
2841
|
+
jwtAuthenticator(
|
|
2842
|
+
bootstrap.connectInfo.transport.sentinel.jwt,
|
|
2843
|
+
new TextEncoder().encode(
|
|
2844
|
+
bootstrap.connectInfo.transport.sentinel.seed,
|
|
2845
|
+
),
|
|
2846
|
+
),
|
|
2847
|
+
],
|
|
2848
|
+
});
|
|
2849
|
+
} catch (cause) {
|
|
2850
|
+
throw new TransportError({
|
|
2851
|
+
code: "trellis.runtime.connect_failed",
|
|
2852
|
+
message: "Trellis could not open the service runtime connection.",
|
|
2853
|
+
hint:
|
|
2854
|
+
"Retry the connection. If it keeps failing, check Trellis transport availability.",
|
|
2855
|
+
cause,
|
|
2856
|
+
context: {
|
|
2857
|
+
trellisUrl: args.trellisUrl,
|
|
2858
|
+
contractId: args.contract.CONTRACT_ID,
|
|
2859
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2860
|
+
},
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
try {
|
|
2865
|
+
const server = args.contract.API.trellis
|
|
2866
|
+
? {
|
|
2867
|
+
...(args.server ?? {}),
|
|
2868
|
+
api: args.contract.API.owned,
|
|
2869
|
+
trellisApi: args.contract.API.trellis as TTrellisApi,
|
|
2870
|
+
}
|
|
2871
|
+
: {
|
|
2872
|
+
...(args.server ?? {}),
|
|
2873
|
+
api: args.contract.API.owned,
|
|
2874
|
+
};
|
|
2875
|
+
|
|
2876
|
+
return Result.ok(
|
|
2877
|
+
await createConnectedService<
|
|
2878
|
+
TOwnedApi,
|
|
2879
|
+
TTrellisApi,
|
|
2880
|
+
ContractJobsOf<TContract>,
|
|
2881
|
+
ContractKvOf<TContract>
|
|
2882
|
+
>({
|
|
2883
|
+
name: args.name,
|
|
2884
|
+
auth,
|
|
2885
|
+
nc,
|
|
2886
|
+
contractId: args.contract.CONTRACT_ID,
|
|
2887
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2888
|
+
contractJobs:
|
|
2889
|
+
(args.contract[CONTRACT_JOBS_METADATA] ?? {}) as ContractJobsOf<
|
|
2890
|
+
TContract
|
|
2891
|
+
>,
|
|
2892
|
+
contractKv:
|
|
2893
|
+
(args.contract[CONTRACT_KV_METADATA] ?? {}) as ContractKvOf<
|
|
2894
|
+
TContract
|
|
2895
|
+
>,
|
|
2896
|
+
contractEventConsumers: args.contract.CONTRACT.eventConsumers,
|
|
2897
|
+
server,
|
|
2898
|
+
bindings: bootstrap.binding.resources,
|
|
2899
|
+
}),
|
|
2900
|
+
);
|
|
2901
|
+
} catch (cause) {
|
|
2902
|
+
await closeFailedServiceBootstrapConnection(nc);
|
|
2903
|
+
throw cause;
|
|
2904
|
+
}
|
|
2905
|
+
} catch (cause) {
|
|
2906
|
+
return Result.err(
|
|
2907
|
+
cause instanceof TransportError ? cause : toUnexpectedError(cause),
|
|
2908
|
+
);
|
|
2909
|
+
}
|
|
2910
|
+
})());
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2760
2913
|
export class TrellisService<
|
|
2761
2914
|
TOwnedApi extends TrellisAPI = TrellisAPI,
|
|
2762
2915
|
TTrellisApi extends TrellisAPI = TOwnedApi,
|
|
@@ -2765,8 +2918,8 @@ export class TrellisService<
|
|
|
2765
2918
|
> {
|
|
2766
2919
|
readonly name: string;
|
|
2767
2920
|
readonly auth: SessionAuth;
|
|
2768
|
-
readonly nc: NatsConnection;
|
|
2769
2921
|
readonly #server: TrellisServiceRuntimeFor<TOwnedApi & TTrellisApi>;
|
|
2922
|
+
readonly #nc: NatsConnection;
|
|
2770
2923
|
readonly #handlerTrellis: Trellis<TTrellisApi, TKv, TJobs>;
|
|
2771
2924
|
/** Event lifecycle surface for service startup listeners and publishers. */
|
|
2772
2925
|
readonly event: ActiveEventFacade<TTrellisApi>;
|
|
@@ -2783,7 +2936,7 @@ export class TrellisService<
|
|
|
2783
2936
|
#waitPromise?: Promise<void>;
|
|
2784
2937
|
#stopPromise?: Promise<void>;
|
|
2785
2938
|
|
|
2786
|
-
constructor(
|
|
2939
|
+
private constructor(
|
|
2787
2940
|
name: string,
|
|
2788
2941
|
auth: SessionAuth,
|
|
2789
2942
|
nc: NatsConnection,
|
|
@@ -2806,7 +2959,7 @@ export class TrellisService<
|
|
|
2806
2959
|
|
|
2807
2960
|
this.name = name;
|
|
2808
2961
|
this.auth = auth;
|
|
2809
|
-
this
|
|
2962
|
+
this.#nc = nc;
|
|
2810
2963
|
this.#server = server;
|
|
2811
2964
|
Object.defineProperty(this, "server", {
|
|
2812
2965
|
value: server,
|
|
@@ -2818,7 +2971,10 @@ export class TrellisService<
|
|
|
2818
2971
|
this.store = Object.fromEntries(
|
|
2819
2972
|
Object.entries(storeBindings).map((
|
|
2820
2973
|
[alias, binding],
|
|
2821
|
-
) => [
|
|
2974
|
+
) => [
|
|
2975
|
+
alias,
|
|
2976
|
+
new InternalStoreHandle(nc, binding, storeHandleConstructorToken),
|
|
2977
|
+
]),
|
|
2822
2978
|
);
|
|
2823
2979
|
this.#operationTransfer = operationTransfer;
|
|
2824
2980
|
const jobs = createJobsFacade<TJobs, TTrellisApi, TKv>({
|
|
@@ -2854,7 +3010,6 @@ export class TrellisService<
|
|
|
2854
3010
|
return {
|
|
2855
3011
|
name: this.name,
|
|
2856
3012
|
auth: this.auth,
|
|
2857
|
-
nc: this.nc,
|
|
2858
3013
|
event: this.#createBoundEventFacade(deps),
|
|
2859
3014
|
kv: this.kv,
|
|
2860
3015
|
store: this.store,
|
|
@@ -3147,7 +3302,6 @@ export class TrellisService<
|
|
|
3147
3302
|
>,
|
|
3148
3303
|
>(
|
|
3149
3304
|
args: TrellisServiceConnectArgs<TContract>,
|
|
3150
|
-
deps?: Partial<TrellisServiceRuntimeDeps>,
|
|
3151
3305
|
): AsyncResult<
|
|
3152
3306
|
TrellisService<
|
|
3153
3307
|
ContractOwnedApi<TContract>,
|
|
@@ -3157,123 +3311,14 @@ export class TrellisService<
|
|
|
3157
3311
|
>,
|
|
3158
3312
|
TransportError | UnexpectedError
|
|
3159
3313
|
> {
|
|
3160
|
-
return
|
|
3161
|
-
try {
|
|
3162
|
-
type TOwnedApi = ContractOwnedApi<TContract>;
|
|
3163
|
-
type TTrellisApi = ContractTrellisApi<TContract>;
|
|
3164
|
-
|
|
3165
|
-
const runtimeDeps = {
|
|
3166
|
-
...(await loadDefaultServiceRuntimeDeps()),
|
|
3167
|
-
...deps,
|
|
3168
|
-
} satisfies TrellisServiceRuntimeDeps;
|
|
3169
|
-
if (automaticTelemetryEnabled(args.telemetry)) {
|
|
3170
|
-
runtimeDeps.initTelemetry?.(args.name);
|
|
3171
|
-
}
|
|
3172
|
-
const auth = await createAuth({ sessionKeySeed: args.sessionKeySeed });
|
|
3173
|
-
const bootstrapLog = resolveServiceLogger(args.server?.log);
|
|
3174
|
-
const bootstrap = await fetchServiceBootstrapInfo({
|
|
3175
|
-
trellisUrl: args.trellisUrl,
|
|
3176
|
-
serviceName: args.name,
|
|
3177
|
-
contractId: args.contract.CONTRACT_ID,
|
|
3178
|
-
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
3179
|
-
contract: args.contract.CONTRACT,
|
|
3180
|
-
auth,
|
|
3181
|
-
log: bootstrapLog,
|
|
3182
|
-
});
|
|
3183
|
-
const { authenticator: authTokenAuthenticator, inboxPrefix } =
|
|
3184
|
-
await auth
|
|
3185
|
-
.natsConnectOptions({
|
|
3186
|
-
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
3187
|
-
});
|
|
3188
|
-
|
|
3189
|
-
let nc: NatsConnection;
|
|
3190
|
-
try {
|
|
3191
|
-
nc = await runtimeDeps.connect({
|
|
3192
|
-
servers: selectRuntimeTransportServers(
|
|
3193
|
-
bootstrap.connectInfo.transports,
|
|
3194
|
-
),
|
|
3195
|
-
maxReconnectAttempts: DEFAULT_RUNTIME_MAX_RECONNECT_ATTEMPTS,
|
|
3196
|
-
waitOnFirstConnect: DEFAULT_SERVICE_RUNTIME_WAIT_ON_FIRST_CONNECT,
|
|
3197
|
-
inboxPrefix,
|
|
3198
|
-
authenticator: [
|
|
3199
|
-
authTokenAuthenticator,
|
|
3200
|
-
jwtAuthenticator(
|
|
3201
|
-
bootstrap.connectInfo.transport.sentinel.jwt,
|
|
3202
|
-
new TextEncoder().encode(
|
|
3203
|
-
bootstrap.connectInfo.transport.sentinel.seed,
|
|
3204
|
-
),
|
|
3205
|
-
),
|
|
3206
|
-
],
|
|
3207
|
-
});
|
|
3208
|
-
} catch (cause) {
|
|
3209
|
-
throw new TransportError({
|
|
3210
|
-
code: "trellis.runtime.connect_failed",
|
|
3211
|
-
message: "Trellis could not open the service runtime connection.",
|
|
3212
|
-
hint:
|
|
3213
|
-
"Retry the connection. If it keeps failing, check Trellis transport availability.",
|
|
3214
|
-
cause,
|
|
3215
|
-
context: {
|
|
3216
|
-
trellisUrl: args.trellisUrl,
|
|
3217
|
-
contractId: args.contract.CONTRACT_ID,
|
|
3218
|
-
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
3219
|
-
},
|
|
3220
|
-
});
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
try {
|
|
3224
|
-
const server = args.contract.API.trellis
|
|
3225
|
-
? {
|
|
3226
|
-
...(args.server ?? {}),
|
|
3227
|
-
api: args.contract.API.owned,
|
|
3228
|
-
trellisApi: args.contract.API.trellis as TTrellisApi,
|
|
3229
|
-
}
|
|
3230
|
-
: {
|
|
3231
|
-
...(args.server ?? {}),
|
|
3232
|
-
api: args.contract.API.owned,
|
|
3233
|
-
};
|
|
3234
|
-
|
|
3235
|
-
return Result.ok(
|
|
3236
|
-
await createConnectedService<
|
|
3237
|
-
TOwnedApi,
|
|
3238
|
-
TTrellisApi,
|
|
3239
|
-
ContractJobsOf<TContract>,
|
|
3240
|
-
ContractKvOf<TContract>
|
|
3241
|
-
>({
|
|
3242
|
-
name: args.name,
|
|
3243
|
-
auth,
|
|
3244
|
-
nc,
|
|
3245
|
-
contractId: args.contract.CONTRACT_ID,
|
|
3246
|
-
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
3247
|
-
contractJobs:
|
|
3248
|
-
(args.contract[CONTRACT_JOBS_METADATA] ?? {}) as ContractJobsOf<
|
|
3249
|
-
TContract
|
|
3250
|
-
>,
|
|
3251
|
-
contractKv:
|
|
3252
|
-
(args.contract[CONTRACT_KV_METADATA] ?? {}) as ContractKvOf<
|
|
3253
|
-
TContract
|
|
3254
|
-
>,
|
|
3255
|
-
contractEventConsumers: args.contract.CONTRACT.eventConsumers,
|
|
3256
|
-
server,
|
|
3257
|
-
bindings: bootstrap.binding.resources,
|
|
3258
|
-
}),
|
|
3259
|
-
);
|
|
3260
|
-
} catch (cause) {
|
|
3261
|
-
await closeFailedServiceBootstrapConnection(nc);
|
|
3262
|
-
throw cause;
|
|
3263
|
-
}
|
|
3264
|
-
} catch (cause) {
|
|
3265
|
-
return Result.err(
|
|
3266
|
-
cause instanceof TransportError ? cause : toUnexpectedError(cause),
|
|
3267
|
-
);
|
|
3268
|
-
}
|
|
3269
|
-
})());
|
|
3314
|
+
return connectTrellisServiceWithRuntimeDeps(args, {});
|
|
3270
3315
|
}
|
|
3271
3316
|
|
|
3272
3317
|
async wait(): Promise<void> {
|
|
3273
3318
|
this.#waitPromise ??= (async () => {
|
|
3274
3319
|
try {
|
|
3275
3320
|
await this.#managedJobWorkers.start().orThrow();
|
|
3276
|
-
const closed = await this
|
|
3321
|
+
const closed = await this.#nc.closed();
|
|
3277
3322
|
if (closed instanceof Error) {
|
|
3278
3323
|
throw closed;
|
|
3279
3324
|
}
|
|
@@ -3299,6 +3344,12 @@ export class TrellisService<
|
|
|
3299
3344
|
await this.#operationTransfer.stop();
|
|
3300
3345
|
} finally {
|
|
3301
3346
|
await this.#server.stop();
|
|
3347
|
+
this.connection.setStatus({
|
|
3348
|
+
kind: this.connection.status.kind,
|
|
3349
|
+
phase: "closed",
|
|
3350
|
+
observedAt: new Date(),
|
|
3351
|
+
transport: { name: "nats" },
|
|
3352
|
+
});
|
|
3302
3353
|
}
|
|
3303
3354
|
}
|
|
3304
3355
|
}
|
package/src/server.ts
CHANGED
|
@@ -1799,12 +1799,12 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1799
1799
|
|
|
1800
1800
|
async stop(): Promise<void> {
|
|
1801
1801
|
this.#stopPromise ??= (async () => {
|
|
1802
|
-
if (this.
|
|
1802
|
+
if (this.nats.isClosed()) {
|
|
1803
1803
|
return;
|
|
1804
1804
|
}
|
|
1805
1805
|
|
|
1806
1806
|
try {
|
|
1807
|
-
await this.
|
|
1807
|
+
await this.nats.drain();
|
|
1808
1808
|
} catch (cause) {
|
|
1809
1809
|
if (
|
|
1810
1810
|
!(cause instanceof Error) ||
|
|
@@ -1813,7 +1813,7 @@ export class TrellisServiceRuntime extends Trellis<TrellisAPI, TrellisMode> {
|
|
|
1813
1813
|
throw cause;
|
|
1814
1814
|
}
|
|
1815
1815
|
|
|
1816
|
-
await this.
|
|
1816
|
+
await this.nats.closed().catch(() => undefined);
|
|
1817
1817
|
}
|
|
1818
1818
|
})();
|
|
1819
1819
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { NatsConnection } from "@nats-io/nats-core";
|
|
2
1
|
import { type AsyncResult, type BaseError, isErr } from "@qlever-llc/result";
|
|
3
2
|
import { type StaticDecode, Type } from "typebox";
|
|
4
3
|
import type { PreparedTrellisEvent, Trellis } from "../trellis.js";
|
|
@@ -387,19 +386,6 @@ export type KvOutboxRecord = StaticDecode<typeof KvOutboxRecordSchema>;
|
|
|
387
386
|
export class NatsKvOutboxRepository implements OutboxRepository {
|
|
388
387
|
constructor(readonly kv: OutboxKvStore) {}
|
|
389
388
|
|
|
390
|
-
/** Opens or creates the KV bucket used for durable outbox records. */
|
|
391
|
-
static async open(
|
|
392
|
-
nats: NatsConnection,
|
|
393
|
-
bucket = "trellis_outbox",
|
|
394
|
-
): Promise<NatsKvOutboxRepository> {
|
|
395
|
-
const opened = await TypedKV.open(nats, bucket, KvOutboxRecordSchema, {
|
|
396
|
-
history: 1,
|
|
397
|
-
});
|
|
398
|
-
const value = opened.take();
|
|
399
|
-
if (isErr(value)) throw value.error;
|
|
400
|
-
return new NatsKvOutboxRepository(value);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
389
|
async enqueue(event: PreparedTrellisEvent): Promise<OutboxMessage> {
|
|
404
390
|
const now = new Date().toISOString();
|
|
405
391
|
const record: KvOutboxRecord = {
|
|
@@ -500,20 +486,7 @@ export class NatsKvOutboxRepository implements OutboxRepository {
|
|
|
500
486
|
|
|
501
487
|
/** Durable NATS KV inbox repository for event-id duplicate suppression. */
|
|
502
488
|
export class NatsKvInboxRepository implements InboxRepository {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
/** Opens or creates the KV bucket used for durable inbox records. */
|
|
506
|
-
static async open(
|
|
507
|
-
nats: NatsConnection,
|
|
508
|
-
bucket = "trellis_inbox",
|
|
509
|
-
): Promise<NatsKvInboxRepository> {
|
|
510
|
-
const opened = await TypedKV.open(nats, bucket, KvInboxRecordSchema, {
|
|
511
|
-
history: 1,
|
|
512
|
-
});
|
|
513
|
-
const value = opened.take();
|
|
514
|
-
if (isErr(value)) throw value.error;
|
|
515
|
-
return new NatsKvInboxRepository(value);
|
|
516
|
-
}
|
|
489
|
+
constructor(readonly kv: TypedKV<typeof KvInboxRecordSchema>) {}
|
|
517
490
|
|
|
518
491
|
async record(messageId: string, now: Date = new Date()): Promise<boolean> {
|
|
519
492
|
// Durable NATS KV dedupe is useful for event handlers without SQL state, but
|
package/src/telemetry/env.ts
CHANGED
|
@@ -9,19 +9,22 @@ type ProcessLike = {
|
|
|
9
9
|
env?: Record<string, string | undefined>;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
type EnvironmentGlobalThis = typeof dntShim.dntGlobalThis & {
|
|
13
|
+
Deno?: DenoLike;
|
|
14
|
+
process?: ProcessLike;
|
|
15
|
+
};
|
|
16
|
+
|
|
12
17
|
// Shared telemetry code needs environment access without assuming Deno or Node.
|
|
13
18
|
export function getEnv(key: string): string | undefined {
|
|
14
|
-
const
|
|
15
|
-
|
|
19
|
+
const load = new Function("return globalThis") as () => EnvironmentGlobalThis;
|
|
20
|
+
const environmentGlobal = load();
|
|
21
|
+
if (environmentGlobal.Deno?.env?.get) {
|
|
16
22
|
try {
|
|
17
|
-
return
|
|
23
|
+
return environmentGlobal.Deno.env.get(key);
|
|
18
24
|
} catch {
|
|
19
25
|
return undefined;
|
|
20
26
|
}
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
process?: ProcessLike;
|
|
25
|
-
};
|
|
26
|
-
return processGlobal.process?.env?.[key];
|
|
29
|
+
return environmentGlobal.process?.env?.[key];
|
|
27
30
|
}
|
package/src/trellis.ts
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
CONTRACT_JOBS_METADATA,
|
|
23
23
|
CONTRACT_KV_METADATA,
|
|
24
24
|
CONTRACT_STATE_METADATA,
|
|
25
|
-
type ContractEventConsumers,
|
|
26
25
|
type ContractJobsMetadata,
|
|
27
26
|
type ContractKvMetadata,
|
|
28
27
|
type EventConsumerResourceBinding,
|
|
@@ -1117,10 +1116,28 @@ function createEventListenerContext(args: {
|
|
|
1117
1116
|
}
|
|
1118
1117
|
|
|
1119
1118
|
type RuntimeEventConsumers = {
|
|
1120
|
-
metadata?:
|
|
1119
|
+
metadata?: RuntimeEventConsumerGroups;
|
|
1121
1120
|
bindings?: Record<string, EventConsumerResourceBinding>;
|
|
1122
1121
|
};
|
|
1123
1122
|
|
|
1123
|
+
type RuntimeEventConsumerGroup = {
|
|
1124
|
+
uses?: Readonly<Record<string, readonly string[]>>;
|
|
1125
|
+
self?: readonly string[];
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
type RuntimeEventConsumerGroups = Readonly<
|
|
1129
|
+
Record<string, RuntimeEventConsumerGroup>
|
|
1130
|
+
>;
|
|
1131
|
+
|
|
1132
|
+
function eventConsumerGroupEvents(group: RuntimeEventConsumerGroup): string[] {
|
|
1133
|
+
const events = new Set<string>();
|
|
1134
|
+
for (const groupEvents of Object.values(group.uses ?? {})) {
|
|
1135
|
+
for (const event of groupEvents) events.add(event);
|
|
1136
|
+
}
|
|
1137
|
+
for (const event of group.self ?? []) events.add(event);
|
|
1138
|
+
return [...events].sort();
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1124
1141
|
type TrellisInternalOpts<TA extends AnyTrellisAPI> = TrellisOpts<TA> & {
|
|
1125
1142
|
eventConsumers?: RuntimeEventConsumers;
|
|
1126
1143
|
};
|
|
@@ -1472,7 +1489,6 @@ export type ClientTrellis<
|
|
|
1472
1489
|
readonly api: TA;
|
|
1473
1490
|
readonly state: StateFacade<TState>;
|
|
1474
1491
|
readonly connection: TrellisConnection;
|
|
1475
|
-
readonly natsConnection: NatsConnection;
|
|
1476
1492
|
readonly rpc: ActiveRpcFacade<TA>;
|
|
1477
1493
|
readonly event: ActiveEventFacade<TA>;
|
|
1478
1494
|
readonly feed: ActiveFeedFacade<TA>;
|
|
@@ -2077,13 +2093,6 @@ export class Trellis<
|
|
|
2077
2093
|
this.operation = this.#createOperationFacade();
|
|
2078
2094
|
}
|
|
2079
2095
|
|
|
2080
|
-
/**
|
|
2081
|
-
* Returns the underlying NATS connection.
|
|
2082
|
-
*/
|
|
2083
|
-
get natsConnection(): NatsConnection {
|
|
2084
|
-
return this.nats;
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
2096
|
#createStateFacade(state: TState | undefined): StateFacade<TState> {
|
|
2088
2097
|
const stores = (state ?? {}) as RuntimeStateStores;
|
|
2089
2098
|
const facade = Object.fromEntries(
|
|
@@ -4215,7 +4224,7 @@ export class Trellis<
|
|
|
4215
4224
|
const bindings = this.#eventConsumers.bindings ?? {};
|
|
4216
4225
|
const groups = Object.entries(metadata ?? {})
|
|
4217
4226
|
.filter(([, group]) =>
|
|
4218
|
-
group.
|
|
4227
|
+
eventConsumerGroupEvents(group).includes(String(event))
|
|
4219
4228
|
)
|
|
4220
4229
|
.map(([group]) => group);
|
|
4221
4230
|
|
|
@@ -4339,9 +4348,9 @@ export class Trellis<
|
|
|
4339
4348
|
): boolean {
|
|
4340
4349
|
const metadata = this.#eventConsumers.metadata?.[group];
|
|
4341
4350
|
if (!metadata) return false;
|
|
4342
|
-
return metadata.
|
|
4351
|
+
return eventConsumerGroupEvents(metadata).every((event) =>
|
|
4343
4352
|
loop.registrations.some((registration) =>
|
|
4344
|
-
String(registration.event) ===
|
|
4353
|
+
String(registration.event) === event
|
|
4345
4354
|
)
|
|
4346
4355
|
);
|
|
4347
4356
|
}
|