@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.
Files changed (101) hide show
  1. package/esm/auth/browser.js +0 -1
  2. package/esm/auth.js +0 -1
  3. package/esm/browser.d.ts +29 -2
  4. package/esm/browser.d.ts.map +1 -1
  5. package/esm/browser.js +14 -3
  6. package/esm/client_connect.d.ts.map +1 -1
  7. package/esm/client_connect.js +7 -3
  8. package/esm/contract_support/mod.d.ts +6 -10
  9. package/esm/contract_support/mod.d.ts.map +1 -1
  10. package/esm/contract_support/mod.js +42 -31
  11. package/esm/contract_support/protocol.d.ts +8 -13
  12. package/esm/contract_support/protocol.d.ts.map +1 -1
  13. package/esm/contract_support/protocol.js +4 -5
  14. package/esm/contract_support/schema_pointers.d.ts +6 -0
  15. package/esm/contract_support/schema_pointers.d.ts.map +1 -1
  16. package/esm/contract_support/schema_pointers.js +59 -7
  17. package/esm/contracts.js +0 -1
  18. package/esm/device.d.ts +0 -29
  19. package/esm/device.d.ts.map +1 -1
  20. package/esm/device.js +10 -1
  21. package/esm/errors/index.js +0 -1
  22. package/esm/index.d.ts +3 -4
  23. package/esm/index.d.ts.map +1 -1
  24. package/esm/index.js +3 -4
  25. package/esm/runtime_transport.d.ts.map +1 -1
  26. package/esm/runtime_transport.js +4 -2
  27. package/esm/server/health_rpc.d.ts +4 -2
  28. package/esm/server/health_rpc.d.ts.map +1 -1
  29. package/esm/server/health_rpc.js +6 -1
  30. package/esm/server/service.d.ts +12 -37
  31. package/esm/server/service.d.ts.map +1 -1
  32. package/esm/server/service.js +138 -103
  33. package/esm/server.js +3 -3
  34. package/esm/service/outbox_inbox.d.ts +1 -6
  35. package/esm/service/outbox_inbox.d.ts.map +1 -1
  36. package/esm/service/outbox_inbox.js +0 -21
  37. package/esm/telemetry/env.d.ts.map +1 -1
  38. package/esm/telemetry/env.js +5 -6
  39. package/esm/telemetry/metrics.d.ts +0 -7
  40. package/esm/telemetry/metrics.d.ts.map +1 -1
  41. package/esm/trellis.d.ts +1 -19
  42. package/esm/trellis.d.ts.map +1 -1
  43. package/esm/trellis.js +12 -8
  44. package/package.json +2 -2
  45. package/script/auth/browser.js +0 -1
  46. package/script/auth.js +0 -1
  47. package/script/browser.d.ts +29 -2
  48. package/script/browser.d.ts.map +1 -1
  49. package/script/browser.js +75 -17
  50. package/script/client_connect.d.ts.map +1 -1
  51. package/script/client_connect.js +7 -36
  52. package/script/contract_support/mod.d.ts +6 -10
  53. package/script/contract_support/mod.d.ts.map +1 -1
  54. package/script/contract_support/mod.js +46 -32
  55. package/script/contract_support/protocol.d.ts +8 -13
  56. package/script/contract_support/protocol.d.ts.map +1 -1
  57. package/script/contract_support/protocol.js +5 -6
  58. package/script/contract_support/schema_pointers.d.ts +6 -0
  59. package/script/contract_support/schema_pointers.d.ts.map +1 -1
  60. package/script/contract_support/schema_pointers.js +59 -7
  61. package/script/contracts.js +0 -1
  62. package/script/device.d.ts +0 -29
  63. package/script/device.d.ts.map +1 -1
  64. package/script/device.js +10 -1
  65. package/script/errors/index.js +0 -1
  66. package/script/index.d.ts +3 -4
  67. package/script/index.d.ts.map +1 -1
  68. package/script/index.js +1 -6
  69. package/script/runtime_transport.d.ts.map +1 -1
  70. package/script/runtime_transport.js +4 -2
  71. package/script/server/health_rpc.d.ts +4 -2
  72. package/script/server/health_rpc.d.ts.map +1 -1
  73. package/script/server/health_rpc.js +6 -1
  74. package/script/server/service.d.ts +12 -37
  75. package/script/server/service.d.ts.map +1 -1
  76. package/script/server/service.js +148 -112
  77. package/script/server.js +3 -3
  78. package/script/service/outbox_inbox.d.ts +1 -6
  79. package/script/service/outbox_inbox.d.ts.map +1 -1
  80. package/script/service/outbox_inbox.js +0 -21
  81. package/script/telemetry/env.d.ts.map +1 -1
  82. package/script/telemetry/env.js +5 -39
  83. package/script/telemetry/metrics.d.ts +0 -7
  84. package/script/telemetry/metrics.d.ts.map +1 -1
  85. package/script/trellis.d.ts +1 -19
  86. package/script/trellis.d.ts.map +1 -1
  87. package/script/trellis.js +12 -8
  88. package/src/browser.ts +200 -2
  89. package/src/client_connect.ts +10 -2
  90. package/src/contract_support/mod.ts +73 -45
  91. package/src/contract_support/protocol.ts +16 -7
  92. package/src/contract_support/schema_pointers.ts +80 -7
  93. package/src/device.ts +10 -2
  94. package/src/index.ts +3 -4
  95. package/src/runtime_transport.ts +6 -1
  96. package/src/server/health_rpc.ts +7 -2
  97. package/src/server/service.ts +177 -126
  98. package/src/server.ts +3 -3
  99. package/src/service/outbox_inbox.ts +1 -28
  100. package/src/telemetry/env.ts +10 -7
  101. package/src/trellis.ts +22 -13
@@ -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 "../index.js";
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 StoreHandle(args.nc, binding, storeHandleConstructorToken),
2010
+ new InternalStoreHandle(args.nc, binding, storeHandleConstructorToken),
1995
2011
  ]),
1996
2012
  ),
1997
2013
  });
1998
2014
 
1999
- const service = new TrellisService<TOwnedApi, TTrellisApi, TJobs, TKv>(
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.nc = nc;
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
- ) => [alias, new StoreHandle(nc, binding, storeHandleConstructorToken)]),
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 AsyncResult.from((async () => {
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.nc.closed();
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.natsConnection.isClosed()) {
1802
+ if (this.nats.isClosed()) {
1803
1803
  return;
1804
1804
  }
1805
1805
 
1806
1806
  try {
1807
- await this.natsConnection.drain();
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.natsConnection.closed().catch(() => undefined);
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
- private constructor(readonly kv: TypedKV<typeof KvInboxRecordSchema>) {}
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
@@ -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 deno = dntShim.dntGlobalThis as typeof dntShim.dntGlobalThis & { Deno?: DenoLike };
15
- if (deno.Deno?.env?.get) {
19
+ const load = new Function("return globalThis") as () => EnvironmentGlobalThis;
20
+ const environmentGlobal = load();
21
+ if (environmentGlobal.Deno?.env?.get) {
16
22
  try {
17
- return deno.Deno.env.get(key);
23
+ return environmentGlobal.Deno.env.get(key);
18
24
  } catch {
19
25
  return undefined;
20
26
  }
21
27
  }
22
28
 
23
- const processGlobal = dntShim.dntGlobalThis as typeof dntShim.dntGlobalThis & {
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?: ContractEventConsumers;
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.events.some((entry) => entry.event === String(event))
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.events.every((entry) =>
4351
+ return eventConsumerGroupEvents(metadata).every((event) =>
4343
4352
  loop.registrations.some((registration) =>
4344
- String(registration.event) === entry.event
4353
+ String(registration.event) === event
4345
4354
  )
4346
4355
  );
4347
4356
  }