@qlever-llc/trellis 0.10.13 → 0.10.15
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 +5 -10
- package/esm/contract_support/mod.d.ts.map +1 -1
- package/esm/contract_support/mod.js +41 -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/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 +14 -37
- package/esm/server/service.d.ts.map +1 -1
- package/esm/server/service.js +143 -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 +5 -10
- package/script/contract_support/mod.d.ts.map +1 -1
- package/script/contract_support/mod.js +43 -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/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 +14 -37
- package/script/server/service.d.ts.map +1 -1
- package/script/server/service.js +153 -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 +69 -45
- package/src/contract_support/protocol.ts +16 -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 +186 -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,12 +1467,12 @@ 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"
|
|
1460
1473
|
| "createTransfer"
|
|
1461
1474
|
| "completeOperation"
|
|
1475
|
+
| "publishPrepared"
|
|
1462
1476
|
| "wait"
|
|
1463
1477
|
| "stop"
|
|
1464
1478
|
>
|
|
@@ -1797,6 +1811,9 @@ export type TrellisServiceInternalConnectArgs<
|
|
|
1797
1811
|
contractKv?: TKv;
|
|
1798
1812
|
};
|
|
1799
1813
|
|
|
1814
|
+
/**
|
|
1815
|
+
* @internal Shared by Trellis-owned service bootstrap paths.
|
|
1816
|
+
*/
|
|
1800
1817
|
export async function createConnectedService<
|
|
1801
1818
|
TOwnedApi extends TrellisAPI,
|
|
1802
1819
|
TTrellisApi extends TrellisAPI,
|
|
@@ -1991,12 +2008,12 @@ export async function createConnectedService<
|
|
|
1991
2008
|
stores: Object.fromEntries(
|
|
1992
2009
|
Object.entries(args.bindings.store ?? {}).map(([alias, binding]) => [
|
|
1993
2010
|
alias,
|
|
1994
|
-
new
|
|
2011
|
+
new InternalStoreHandle(args.nc, binding, storeHandleConstructorToken),
|
|
1995
2012
|
]),
|
|
1996
2013
|
),
|
|
1997
2014
|
});
|
|
1998
2015
|
|
|
1999
|
-
const service =
|
|
2016
|
+
const service = Reflect.construct(TrellisService, [
|
|
2000
2017
|
args.name,
|
|
2001
2018
|
args.auth,
|
|
2002
2019
|
args.nc,
|
|
@@ -2011,7 +2028,7 @@ export async function createConnectedService<
|
|
|
2011
2028
|
stopHealthPublishing,
|
|
2012
2029
|
connection,
|
|
2013
2030
|
trellisServiceConstructorToken,
|
|
2014
|
-
)
|
|
2031
|
+
]) as TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>;
|
|
2015
2032
|
handlerResources = {
|
|
2016
2033
|
kv: service.kv,
|
|
2017
2034
|
store: service.store,
|
|
@@ -2757,6 +2774,143 @@ function createBoundJobsFacade<
|
|
|
2757
2774
|
return boundJobs as BoundJobsFacadeOf<TJobs, TTrellisApi, TKv, TDeps>;
|
|
2758
2775
|
}
|
|
2759
2776
|
|
|
2777
|
+
/**
|
|
2778
|
+
* Connects a service with caller-supplied runtime dependencies for tests and
|
|
2779
|
+
* Trellis-owned internals. This helper is intentionally not re-exported from
|
|
2780
|
+
* public package subpaths.
|
|
2781
|
+
*
|
|
2782
|
+
* @internal
|
|
2783
|
+
*/
|
|
2784
|
+
export function connectTrellisServiceWithRuntimeDeps<
|
|
2785
|
+
const TContract extends ServiceContract<
|
|
2786
|
+
TrellisAPI,
|
|
2787
|
+
TrellisAPI | undefined,
|
|
2788
|
+
ContractJobsMetadata,
|
|
2789
|
+
ContractKvMetadata
|
|
2790
|
+
>,
|
|
2791
|
+
>(
|
|
2792
|
+
args: TrellisServiceConnectArgs<TContract>,
|
|
2793
|
+
deps: Partial<TrellisServiceRuntimeDeps>,
|
|
2794
|
+
): AsyncResult<
|
|
2795
|
+
TrellisService<
|
|
2796
|
+
ContractOwnedApi<TContract>,
|
|
2797
|
+
ContractTrellisApi<TContract>,
|
|
2798
|
+
ContractJobsOf<TContract>,
|
|
2799
|
+
ContractKvOf<TContract>
|
|
2800
|
+
>,
|
|
2801
|
+
TransportError | UnexpectedError
|
|
2802
|
+
> {
|
|
2803
|
+
return AsyncResult.from((async () => {
|
|
2804
|
+
try {
|
|
2805
|
+
type TOwnedApi = ContractOwnedApi<TContract>;
|
|
2806
|
+
type TTrellisApi = ContractTrellisApi<TContract>;
|
|
2807
|
+
|
|
2808
|
+
const runtimeDeps = {
|
|
2809
|
+
...(await loadDefaultServiceRuntimeDeps()),
|
|
2810
|
+
...deps,
|
|
2811
|
+
} satisfies TrellisServiceRuntimeDeps;
|
|
2812
|
+
if (automaticTelemetryEnabled(args.telemetry)) {
|
|
2813
|
+
runtimeDeps.initTelemetry?.(args.name);
|
|
2814
|
+
}
|
|
2815
|
+
const auth = await createAuth({ sessionKeySeed: args.sessionKeySeed });
|
|
2816
|
+
const bootstrapLog = resolveServiceLogger(args.server?.log);
|
|
2817
|
+
const bootstrap = await fetchServiceBootstrapInfo({
|
|
2818
|
+
trellisUrl: args.trellisUrl,
|
|
2819
|
+
serviceName: args.name,
|
|
2820
|
+
contractId: args.contract.CONTRACT_ID,
|
|
2821
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2822
|
+
contract: args.contract.CONTRACT,
|
|
2823
|
+
auth,
|
|
2824
|
+
log: bootstrapLog,
|
|
2825
|
+
});
|
|
2826
|
+
const { authenticator: authTokenAuthenticator, inboxPrefix } = await auth
|
|
2827
|
+
.natsConnectOptions({
|
|
2828
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2829
|
+
});
|
|
2830
|
+
|
|
2831
|
+
let nc: NatsConnection;
|
|
2832
|
+
try {
|
|
2833
|
+
nc = await runtimeDeps.connect({
|
|
2834
|
+
servers: selectRuntimeTransportServers(
|
|
2835
|
+
bootstrap.connectInfo.transports,
|
|
2836
|
+
),
|
|
2837
|
+
maxReconnectAttempts: DEFAULT_RUNTIME_MAX_RECONNECT_ATTEMPTS,
|
|
2838
|
+
waitOnFirstConnect: DEFAULT_SERVICE_RUNTIME_WAIT_ON_FIRST_CONNECT,
|
|
2839
|
+
inboxPrefix,
|
|
2840
|
+
authenticator: [
|
|
2841
|
+
authTokenAuthenticator,
|
|
2842
|
+
jwtAuthenticator(
|
|
2843
|
+
bootstrap.connectInfo.transport.sentinel.jwt,
|
|
2844
|
+
new TextEncoder().encode(
|
|
2845
|
+
bootstrap.connectInfo.transport.sentinel.seed,
|
|
2846
|
+
),
|
|
2847
|
+
),
|
|
2848
|
+
],
|
|
2849
|
+
});
|
|
2850
|
+
} catch (cause) {
|
|
2851
|
+
throw new TransportError({
|
|
2852
|
+
code: "trellis.runtime.connect_failed",
|
|
2853
|
+
message: "Trellis could not open the service runtime connection.",
|
|
2854
|
+
hint:
|
|
2855
|
+
"Retry the connection. If it keeps failing, check Trellis transport availability.",
|
|
2856
|
+
cause,
|
|
2857
|
+
context: {
|
|
2858
|
+
trellisUrl: args.trellisUrl,
|
|
2859
|
+
contractId: args.contract.CONTRACT_ID,
|
|
2860
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2861
|
+
},
|
|
2862
|
+
});
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
try {
|
|
2866
|
+
const server = args.contract.API.trellis
|
|
2867
|
+
? {
|
|
2868
|
+
...(args.server ?? {}),
|
|
2869
|
+
api: args.contract.API.owned,
|
|
2870
|
+
trellisApi: args.contract.API.trellis as TTrellisApi,
|
|
2871
|
+
}
|
|
2872
|
+
: {
|
|
2873
|
+
...(args.server ?? {}),
|
|
2874
|
+
api: args.contract.API.owned,
|
|
2875
|
+
};
|
|
2876
|
+
|
|
2877
|
+
return Result.ok(
|
|
2878
|
+
await createConnectedService<
|
|
2879
|
+
TOwnedApi,
|
|
2880
|
+
TTrellisApi,
|
|
2881
|
+
ContractJobsOf<TContract>,
|
|
2882
|
+
ContractKvOf<TContract>
|
|
2883
|
+
>({
|
|
2884
|
+
name: args.name,
|
|
2885
|
+
auth,
|
|
2886
|
+
nc,
|
|
2887
|
+
contractId: args.contract.CONTRACT_ID,
|
|
2888
|
+
contractDigest: args.contract.CONTRACT_DIGEST,
|
|
2889
|
+
contractJobs:
|
|
2890
|
+
(args.contract[CONTRACT_JOBS_METADATA] ?? {}) as ContractJobsOf<
|
|
2891
|
+
TContract
|
|
2892
|
+
>,
|
|
2893
|
+
contractKv:
|
|
2894
|
+
(args.contract[CONTRACT_KV_METADATA] ?? {}) as ContractKvOf<
|
|
2895
|
+
TContract
|
|
2896
|
+
>,
|
|
2897
|
+
contractEventConsumers: args.contract.CONTRACT.eventConsumers,
|
|
2898
|
+
server,
|
|
2899
|
+
bindings: bootstrap.binding.resources,
|
|
2900
|
+
}),
|
|
2901
|
+
);
|
|
2902
|
+
} catch (cause) {
|
|
2903
|
+
await closeFailedServiceBootstrapConnection(nc);
|
|
2904
|
+
throw cause;
|
|
2905
|
+
}
|
|
2906
|
+
} catch (cause) {
|
|
2907
|
+
return Result.err(
|
|
2908
|
+
cause instanceof TransportError ? cause : toUnexpectedError(cause),
|
|
2909
|
+
);
|
|
2910
|
+
}
|
|
2911
|
+
})());
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2760
2914
|
export class TrellisService<
|
|
2761
2915
|
TOwnedApi extends TrellisAPI = TrellisAPI,
|
|
2762
2916
|
TTrellisApi extends TrellisAPI = TOwnedApi,
|
|
@@ -2765,8 +2919,8 @@ export class TrellisService<
|
|
|
2765
2919
|
> {
|
|
2766
2920
|
readonly name: string;
|
|
2767
2921
|
readonly auth: SessionAuth;
|
|
2768
|
-
readonly nc: NatsConnection;
|
|
2769
2922
|
readonly #server: TrellisServiceRuntimeFor<TOwnedApi & TTrellisApi>;
|
|
2923
|
+
readonly #nc: NatsConnection;
|
|
2770
2924
|
readonly #handlerTrellis: Trellis<TTrellisApi, TKv, TJobs>;
|
|
2771
2925
|
/** Event lifecycle surface for service startup listeners and publishers. */
|
|
2772
2926
|
readonly event: ActiveEventFacade<TTrellisApi>;
|
|
@@ -2783,7 +2937,7 @@ export class TrellisService<
|
|
|
2783
2937
|
#waitPromise?: Promise<void>;
|
|
2784
2938
|
#stopPromise?: Promise<void>;
|
|
2785
2939
|
|
|
2786
|
-
constructor(
|
|
2940
|
+
private constructor(
|
|
2787
2941
|
name: string,
|
|
2788
2942
|
auth: SessionAuth,
|
|
2789
2943
|
nc: NatsConnection,
|
|
@@ -2806,7 +2960,7 @@ export class TrellisService<
|
|
|
2806
2960
|
|
|
2807
2961
|
this.name = name;
|
|
2808
2962
|
this.auth = auth;
|
|
2809
|
-
this
|
|
2963
|
+
this.#nc = nc;
|
|
2810
2964
|
this.#server = server;
|
|
2811
2965
|
Object.defineProperty(this, "server", {
|
|
2812
2966
|
value: server,
|
|
@@ -2818,7 +2972,10 @@ export class TrellisService<
|
|
|
2818
2972
|
this.store = Object.fromEntries(
|
|
2819
2973
|
Object.entries(storeBindings).map((
|
|
2820
2974
|
[alias, binding],
|
|
2821
|
-
) => [
|
|
2975
|
+
) => [
|
|
2976
|
+
alias,
|
|
2977
|
+
new InternalStoreHandle(nc, binding, storeHandleConstructorToken),
|
|
2978
|
+
]),
|
|
2822
2979
|
);
|
|
2823
2980
|
this.#operationTransfer = operationTransfer;
|
|
2824
2981
|
const jobs = createJobsFacade<TJobs, TTrellisApi, TKv>({
|
|
@@ -2854,7 +3011,6 @@ export class TrellisService<
|
|
|
2854
3011
|
return {
|
|
2855
3012
|
name: this.name,
|
|
2856
3013
|
auth: this.auth,
|
|
2857
|
-
nc: this.nc,
|
|
2858
3014
|
event: this.#createBoundEventFacade(deps),
|
|
2859
3015
|
kv: this.kv,
|
|
2860
3016
|
store: this.store,
|
|
@@ -2865,12 +3021,20 @@ export class TrellisService<
|
|
|
2865
3021
|
createTransfer: (args) => this.createTransfer(args),
|
|
2866
3022
|
completeOperation: (operationId, output) =>
|
|
2867
3023
|
this.completeOperation(operationId, output),
|
|
3024
|
+
publishPrepared: (event) => this.publishPrepared(event),
|
|
2868
3025
|
wait: () => this.wait(),
|
|
2869
3026
|
stop: () => this.stop(),
|
|
2870
3027
|
with: (nextDeps) => this.with(nextDeps),
|
|
2871
3028
|
};
|
|
2872
3029
|
}
|
|
2873
3030
|
|
|
3031
|
+
/** Publishes a prepared event through the service runtime connection. */
|
|
3032
|
+
publishPrepared(
|
|
3033
|
+
event: PreparedTrellisEvent,
|
|
3034
|
+
): AsyncResult<void, UnexpectedError> {
|
|
3035
|
+
return this.#handlerTrellis.publishPrepared(event);
|
|
3036
|
+
}
|
|
3037
|
+
|
|
2874
3038
|
#createBoundHealth<TDeps>(deps: TDeps): BoundServiceHealth<TDeps> {
|
|
2875
3039
|
const health = this.health;
|
|
2876
3040
|
return {
|
|
@@ -3147,7 +3311,6 @@ export class TrellisService<
|
|
|
3147
3311
|
>,
|
|
3148
3312
|
>(
|
|
3149
3313
|
args: TrellisServiceConnectArgs<TContract>,
|
|
3150
|
-
deps?: Partial<TrellisServiceRuntimeDeps>,
|
|
3151
3314
|
): AsyncResult<
|
|
3152
3315
|
TrellisService<
|
|
3153
3316
|
ContractOwnedApi<TContract>,
|
|
@@ -3157,123 +3320,14 @@ export class TrellisService<
|
|
|
3157
3320
|
>,
|
|
3158
3321
|
TransportError | UnexpectedError
|
|
3159
3322
|
> {
|
|
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
|
-
})());
|
|
3323
|
+
return connectTrellisServiceWithRuntimeDeps(args, {});
|
|
3270
3324
|
}
|
|
3271
3325
|
|
|
3272
3326
|
async wait(): Promise<void> {
|
|
3273
3327
|
this.#waitPromise ??= (async () => {
|
|
3274
3328
|
try {
|
|
3275
3329
|
await this.#managedJobWorkers.start().orThrow();
|
|
3276
|
-
const closed = await this
|
|
3330
|
+
const closed = await this.#nc.closed();
|
|
3277
3331
|
if (closed instanceof Error) {
|
|
3278
3332
|
throw closed;
|
|
3279
3333
|
}
|
|
@@ -3299,6 +3353,12 @@ export class TrellisService<
|
|
|
3299
3353
|
await this.#operationTransfer.stop();
|
|
3300
3354
|
} finally {
|
|
3301
3355
|
await this.#server.stop();
|
|
3356
|
+
this.connection.setStatus({
|
|
3357
|
+
kind: this.connection.status.kind,
|
|
3358
|
+
phase: "closed",
|
|
3359
|
+
observedAt: new Date(),
|
|
3360
|
+
transport: { name: "nats" },
|
|
3361
|
+
});
|
|
3302
3362
|
}
|
|
3303
3363
|
}
|
|
3304
3364
|
}
|
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
|
}
|