@replit/river 0.15.7 → 0.16.1

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.
@@ -22,103 +22,244 @@ var router_exports = {};
22
22
  __export(router_exports, {
23
23
  Err: () => Err,
24
24
  Ok: () => Ok,
25
+ Procedure: () => Procedure,
25
26
  RiverUncaughtSchema: () => RiverUncaughtSchema,
26
- ServiceBuilder: () => ServiceBuilder,
27
+ ServiceSchema: () => ServiceSchema,
27
28
  UNCAUGHT_ERROR: () => UNCAUGHT_ERROR,
28
- buildServiceDefs: () => buildServiceDefs,
29
29
  createClient: () => createClient,
30
- createServer: () => createServer,
31
- serializeService: () => serializeService
30
+ createServer: () => createServer
32
31
  });
33
32
  module.exports = __toCommonJS(router_exports);
34
33
 
35
- // router/builder.ts
34
+ // router/services.ts
36
35
  var import_typebox = require("@sinclair/typebox");
37
- function serializeService(s) {
38
- return {
39
- name: s.name,
40
- state: s.state,
41
- procedures: Object.fromEntries(
42
- Object.entries(s.procedures).map(([procName, procDef]) => [
43
- procName,
44
- {
45
- input: import_typebox.Type.Strict(procDef.input),
46
- output: import_typebox.Type.Strict(procDef.output),
47
- // Only add the `errors` field if it is non-never.
48
- ..."errors" in procDef ? {
49
- errors: import_typebox.Type.Strict(procDef.errors)
50
- } : {},
51
- type: procDef.type,
52
- // Only add the `init` field if the type declares it.
53
- ..."init" in procDef ? {
54
- init: import_typebox.Type.Strict(procDef.init)
55
- } : {}
56
- }
57
- ])
58
- )
59
- };
60
- }
61
- var ServiceBuilder = class _ServiceBuilder {
62
- schema;
63
- constructor(schema) {
64
- this.schema = schema;
65
- }
36
+ var ServiceSchema = class _ServiceSchema {
66
37
  /**
67
- * Finalizes the schema for the service.
38
+ * Factory function for creating a fresh state.
68
39
  */
69
- finalize() {
70
- return Object.freeze(this.schema);
40
+ initializeState;
41
+ /**
42
+ * The procedures for this service.
43
+ */
44
+ procedures;
45
+ /**
46
+ * @param config - The configuration for this service.
47
+ * @param procedures - The procedures for this service.
48
+ */
49
+ constructor(config, procedures) {
50
+ this.initializeState = config.initializeState;
51
+ this.procedures = procedures;
71
52
  }
72
53
  /**
73
- * Sets the initial state for the service.
74
- * @template InitState The type of the initial state.
75
- * @param {InitState} state The initial state for the service.
76
- * @returns {ServiceBuilder<{ name: T['name']; state: InitState; procedures: T['procedures']; }>} A new ServiceBuilder instance with the updated schema.
54
+ * Creates a {@link ServiceScaffold}, which can be used to define procedures
55
+ * that can then be merged into a {@link ServiceSchema}, via the scaffold's
56
+ * `finalize` method.
57
+ *
58
+ * There are two patterns that work well with this method. The first is using
59
+ * it to separate the definition of procedures from the definition of the
60
+ * service's configuration:
61
+ * ```ts
62
+ * const MyServiceScaffold = ServiceSchema.scaffold({
63
+ * initializeState: () => ({ count: 0 }),
64
+ * });
65
+ *
66
+ * const incrementProcedures = MyServiceScaffold.procedures({
67
+ * increment: Procedure.rpc({
68
+ * input: Type.Object({ amount: Type.Number() }),
69
+ * output: Type.Object({ current: Type.Number() }),
70
+ * async handler(ctx, input) {
71
+ * ctx.state.count += input.amount;
72
+ * return Ok({ current: ctx.state.count });
73
+ * }
74
+ * }),
75
+ * })
76
+ *
77
+ * const MyService = MyServiceScaffold.finalize({
78
+ * ...incrementProcedures,
79
+ * // you can also directly define procedures here
80
+ * });
81
+ * ```
82
+ * This might be really handy if you have a very large service and you're
83
+ * wanting to split it over multiple files. You can define the scaffold
84
+ * in one file, and then import that scaffold in other files where you
85
+ * define procedures - and then finally import the scaffolds and your
86
+ * procedure objects in a final file where you finalize the scaffold into
87
+ * a service schema.
88
+ *
89
+ * The other way is to use it like in a builder pattern:
90
+ * ```ts
91
+ * const MyService = ServiceSchema
92
+ * .scaffold({ initializeState: () => ({ count: 0 }) })
93
+ * .finalize({
94
+ * increment: Procedure.rpc({
95
+ * input: Type.Object({ amount: Type.Number() }),
96
+ * output: Type.Object({ current: Type.Number() }),
97
+ * async handler(ctx, input) {
98
+ * ctx.state.count += input.amount;
99
+ * return Ok({ current: ctx.state.count });
100
+ * }
101
+ * }),
102
+ * })
103
+ * ```
104
+ * Depending on your preferences, this may be a more appealing way to define
105
+ * a schema versus using the {@link ServiceSchema.define} method.
77
106
  */
78
- initialState(state) {
79
- return new _ServiceBuilder({
80
- ...this.schema,
81
- state
82
- });
107
+ static scaffold(config) {
108
+ return new ServiceScaffold(config);
109
+ }
110
+ // actual implementation
111
+ static define(configOrProcedures, maybeProcedures) {
112
+ let config;
113
+ let procedures;
114
+ if ("initializeState" in configOrProcedures && typeof configOrProcedures.initializeState === "function") {
115
+ if (!maybeProcedures) {
116
+ throw new Error("Expected procedures to be defined");
117
+ }
118
+ config = configOrProcedures;
119
+ procedures = maybeProcedures;
120
+ } else {
121
+ config = { initializeState: () => ({}) };
122
+ procedures = configOrProcedures;
123
+ }
124
+ return new _ServiceSchema(config, procedures);
83
125
  }
84
126
  /**
85
- * Defines a new procedure for the service.
86
- * @param {ProcName} procName The name of the procedure.
87
- * @param {Procedure<T['state'], Ty, I, O, E, Init>} procDef The definition of the procedure.
88
- * @returns {ServiceBuilder<{ name: T['name']; state: T['state']; procedures: T['procedures'] & { [k in ProcName]: Procedure<T['state'], Ty, I, O, E, Init>; }; }>} A new ServiceBuilder instance with the updated schema.
127
+ * Serializes this schema's procedures into a plain object that is JSON compatible.
89
128
  */
90
- defineProcedure(procName, procDef) {
91
- const newProcedure = { [procName]: procDef };
92
- const procedures = {
93
- ...this.schema.procedures,
94
- ...newProcedure
129
+ serialize() {
130
+ return {
131
+ procedures: Object.fromEntries(
132
+ Object.entries(this.procedures).map(([procName, procDef]) => [
133
+ procName,
134
+ {
135
+ input: import_typebox.Type.Strict(procDef.input),
136
+ output: import_typebox.Type.Strict(procDef.output),
137
+ // Only add the `errors` field if it is non-never.
138
+ ..."errors" in procDef ? {
139
+ errors: import_typebox.Type.Strict(procDef.errors)
140
+ } : {},
141
+ type: procDef.type,
142
+ // Only add the `init` field if the type declares it.
143
+ ..."init" in procDef ? {
144
+ init: import_typebox.Type.Strict(procDef.init)
145
+ } : {}
146
+ }
147
+ ])
148
+ )
95
149
  };
96
- return new _ServiceBuilder({
97
- ...this.schema,
98
- procedures
99
- });
100
150
  }
101
151
  /**
102
- * Creates a new instance of ServiceBuilder.
103
- * @param {Name} name The name of the service.
104
- * @returns {ServiceBuilder<{ name: Name; state: {}; procedures: {}; }>} A new instance of ServiceBuilder.
152
+ * Instantiates this schema into a {@link Service} object.
153
+ *
154
+ * You probably don't need this, usually the River server will handle this
155
+ * for you.
105
156
  */
106
- static create(name) {
107
- return new _ServiceBuilder({
108
- name,
109
- state: {},
110
- procedures: {}
157
+ instantiate() {
158
+ return Object.freeze({
159
+ state: this.initializeState(),
160
+ procedures: this.procedures
111
161
  });
112
162
  }
113
163
  };
164
+ var ServiceScaffold = class {
165
+ /**
166
+ * The configuration for this service.
167
+ */
168
+ config;
169
+ /**
170
+ * @param config - The configuration for this service.
171
+ */
172
+ constructor(config) {
173
+ this.config = config;
174
+ }
175
+ /**
176
+ * Define procedures for this service. Use the {@link Procedure} constructors
177
+ * to create them. This returns the procedures object, which can then be
178
+ * passed to {@link ServiceSchema.finalize} to create a {@link ServiceSchema}.
179
+ *
180
+ * @example
181
+ * ```
182
+ * const myProcedures = MyServiceScaffold.procedures({
183
+ * myRPC: Procedure.rpc({
184
+ * // ...
185
+ * }),
186
+ * });
187
+ *
188
+ * const MyService = MyServiceScaffold.finalize({
189
+ * ...myProcedures,
190
+ * });
191
+ * ```
192
+ *
193
+ * @param procedures - The procedures for this service.
194
+ */
195
+ procedures(procedures) {
196
+ return procedures;
197
+ }
198
+ /**
199
+ * Finalizes the scaffold into a {@link ServiceSchema}. This is where you
200
+ * provide the service's procedures and get a {@link ServiceSchema} in return.
201
+ *
202
+ * You can directly define procedures here, or you can define them separately
203
+ * with the {@link ServiceScaffold.procedures} method, and then pass them here.
204
+ *
205
+ * @example
206
+ * ```
207
+ * const MyService = MyServiceScaffold.finalize({
208
+ * myRPC: Procedure.rpc({
209
+ * // ...
210
+ * }),
211
+ * // e.g. from the procedures method
212
+ * ...myOtherProcedures,
213
+ * });
214
+ * ```
215
+ */
216
+ finalize(procedures) {
217
+ return ServiceSchema.define(this.config, procedures);
218
+ }
219
+ };
114
220
 
115
- // router/defs.ts
116
- function buildServiceDefs(services) {
117
- return services.reduce((acc, service) => {
118
- acc[service.name] = service;
119
- return acc;
120
- }, {});
221
+ // router/procedures.ts
222
+ var import_typebox2 = require("@sinclair/typebox");
223
+ function rpc({
224
+ input,
225
+ output,
226
+ errors = import_typebox2.Type.Never(),
227
+ handler
228
+ }) {
229
+ return { type: "rpc", input, output, errors, handler };
230
+ }
231
+ function upload({
232
+ init,
233
+ input,
234
+ output,
235
+ errors = import_typebox2.Type.Never(),
236
+ handler
237
+ }) {
238
+ return init !== void 0 && init !== null ? { type: "upload", init, input, output, errors, handler } : { type: "upload", input, output, errors, handler };
121
239
  }
240
+ function subscription({
241
+ input,
242
+ output,
243
+ errors = import_typebox2.Type.Never(),
244
+ handler
245
+ }) {
246
+ return { type: "subscription", input, output, errors, handler };
247
+ }
248
+ function stream({
249
+ init,
250
+ input,
251
+ output,
252
+ errors = import_typebox2.Type.Never(),
253
+ handler
254
+ }) {
255
+ return init !== void 0 && init !== null ? { type: "stream", init, input, output, errors, handler } : { type: "stream", input, output, errors, handler };
256
+ }
257
+ var Procedure = {
258
+ rpc,
259
+ upload,
260
+ subscription,
261
+ stream
262
+ };
122
263
 
123
264
  // node_modules/p-defer/index.js
124
265
  function pDefer() {
@@ -403,52 +544,52 @@ function _pushable(getNext, options) {
403
544
  }
404
545
 
405
546
  // transport/message.ts
406
- var import_typebox2 = require("@sinclair/typebox");
547
+ var import_typebox3 = require("@sinclair/typebox");
407
548
  var import_nanoid = require("nanoid");
408
- var TransportMessageSchema = (t) => import_typebox2.Type.Object({
409
- id: import_typebox2.Type.String(),
410
- from: import_typebox2.Type.String(),
411
- to: import_typebox2.Type.String(),
412
- seq: import_typebox2.Type.Integer(),
413
- ack: import_typebox2.Type.Integer(),
414
- serviceName: import_typebox2.Type.Optional(import_typebox2.Type.String()),
415
- procedureName: import_typebox2.Type.Optional(import_typebox2.Type.String()),
416
- streamId: import_typebox2.Type.String(),
417
- controlFlags: import_typebox2.Type.Integer(),
549
+ var TransportMessageSchema = (t) => import_typebox3.Type.Object({
550
+ id: import_typebox3.Type.String(),
551
+ from: import_typebox3.Type.String(),
552
+ to: import_typebox3.Type.String(),
553
+ seq: import_typebox3.Type.Integer(),
554
+ ack: import_typebox3.Type.Integer(),
555
+ serviceName: import_typebox3.Type.Optional(import_typebox3.Type.String()),
556
+ procedureName: import_typebox3.Type.Optional(import_typebox3.Type.String()),
557
+ streamId: import_typebox3.Type.String(),
558
+ controlFlags: import_typebox3.Type.Integer(),
418
559
  payload: t
419
560
  });
420
- var ControlMessageAckSchema = import_typebox2.Type.Object({
421
- type: import_typebox2.Type.Literal("ACK")
561
+ var ControlMessageAckSchema = import_typebox3.Type.Object({
562
+ type: import_typebox3.Type.Literal("ACK")
422
563
  });
423
- var ControlMessageCloseSchema = import_typebox2.Type.Object({
424
- type: import_typebox2.Type.Literal("CLOSE")
564
+ var ControlMessageCloseSchema = import_typebox3.Type.Object({
565
+ type: import_typebox3.Type.Literal("CLOSE")
425
566
  });
426
- var ControlMessageHandshakeRequestSchema = import_typebox2.Type.Object({
427
- type: import_typebox2.Type.Literal("HANDSHAKE_REQ"),
428
- protocolVersion: import_typebox2.Type.String(),
429
- instanceId: import_typebox2.Type.String()
567
+ var ControlMessageHandshakeRequestSchema = import_typebox3.Type.Object({
568
+ type: import_typebox3.Type.Literal("HANDSHAKE_REQ"),
569
+ protocolVersion: import_typebox3.Type.String(),
570
+ instanceId: import_typebox3.Type.String()
430
571
  });
431
- var ControlMessageHandshakeResponseSchema = import_typebox2.Type.Object({
432
- type: import_typebox2.Type.Literal("HANDSHAKE_RESP"),
433
- status: import_typebox2.Type.Union([
434
- import_typebox2.Type.Object({
435
- ok: import_typebox2.Type.Literal(true),
436
- instanceId: import_typebox2.Type.String()
572
+ var ControlMessageHandshakeResponseSchema = import_typebox3.Type.Object({
573
+ type: import_typebox3.Type.Literal("HANDSHAKE_RESP"),
574
+ status: import_typebox3.Type.Union([
575
+ import_typebox3.Type.Object({
576
+ ok: import_typebox3.Type.Literal(true),
577
+ instanceId: import_typebox3.Type.String()
437
578
  }),
438
- import_typebox2.Type.Object({
439
- ok: import_typebox2.Type.Literal(false),
440
- reason: import_typebox2.Type.String()
579
+ import_typebox3.Type.Object({
580
+ ok: import_typebox3.Type.Literal(false),
581
+ reason: import_typebox3.Type.String()
441
582
  })
442
583
  ])
443
584
  });
444
- var ControlMessagePayloadSchema = import_typebox2.Type.Union([
585
+ var ControlMessagePayloadSchema = import_typebox3.Type.Union([
445
586
  ControlMessageCloseSchema,
446
587
  ControlMessageAckSchema,
447
588
  ControlMessageHandshakeRequestSchema,
448
589
  ControlMessageHandshakeResponseSchema
449
590
  ]);
450
591
  var OpaqueTransportMessageSchema = TransportMessageSchema(
451
- import_typebox2.Type.Unknown()
592
+ import_typebox3.Type.Unknown()
452
593
  );
453
594
  function isStreamOpen(controlFlag) {
454
595
  return (
@@ -467,15 +608,15 @@ function isStreamClose(controlFlag) {
467
608
  var import_nanoid2 = require("nanoid");
468
609
 
469
610
  // router/result.ts
470
- var import_typebox3 = require("@sinclair/typebox");
611
+ var import_typebox4 = require("@sinclair/typebox");
471
612
  var UNCAUGHT_ERROR = "UNCAUGHT_ERROR";
472
613
  var UNEXPECTED_DISCONNECT = "UNEXPECTED_DISCONNECT";
473
- var RiverUncaughtSchema = import_typebox3.Type.Object({
474
- code: import_typebox3.Type.Union([
475
- import_typebox3.Type.Literal(UNCAUGHT_ERROR),
476
- import_typebox3.Type.Literal(UNEXPECTED_DISCONNECT)
614
+ var RiverUncaughtSchema = import_typebox4.Type.Object({
615
+ code: import_typebox4.Type.Union([
616
+ import_typebox4.Type.Literal(UNCAUGHT_ERROR),
617
+ import_typebox4.Type.Literal(UNEXPECTED_DISCONNECT)
477
618
  ]),
478
- message: import_typebox3.Type.String()
619
+ message: import_typebox4.Type.String()
479
620
  });
480
621
  function Ok(payload) {
481
622
  return {
@@ -812,16 +953,19 @@ var RiverServer = class {
812
953
  clientStreams;
813
954
  disconnectedSessions;
814
955
  constructor(transport, services, extendedContext) {
815
- this.transport = transport;
816
- this.services = services;
956
+ const instances = {};
957
+ this.services = instances;
817
958
  this.contextMap = /* @__PURE__ */ new Map();
818
- this.disconnectedSessions = /* @__PURE__ */ new Set();
819
- for (const service of Object.values(services)) {
820
- this.contextMap.set(service, {
959
+ for (const [name, service] of Object.entries(services)) {
960
+ const instance = service.instantiate();
961
+ instances[name] = instance;
962
+ this.contextMap.set(instance, {
821
963
  ...extendedContext,
822
- state: service.state
964
+ state: instance.state
823
965
  });
824
966
  }
967
+ this.transport = transport;
968
+ this.disconnectedSessions = /* @__PURE__ */ new Set();
825
969
  this.streamMap = /* @__PURE__ */ new Map();
826
970
  this.clientStreams = /* @__PURE__ */ new Map();
827
971
  this.transport.addEventListener("message", this.onMessage);
@@ -890,7 +1034,7 @@ var RiverServer = class {
890
1034
  return;
891
1035
  }
892
1036
  const service = this.services[message.serviceName];
893
- const serviceContext = this.getContext(service);
1037
+ const serviceContext = this.getContext(service, message.serviceName);
894
1038
  if (!(message.procedureName in service.procedures)) {
895
1039
  log?.warn(
896
1040
  `${this.transport.clientId} -- couldn't find a matching procedure for ${message.serviceName}.${message.procedureName}`
@@ -1100,24 +1244,24 @@ var RiverServer = class {
1100
1244
  }
1101
1245
  }
1102
1246
  }
1103
- getContext(service) {
1247
+ getContext(service, name) {
1104
1248
  const context = this.contextMap.get(service);
1105
1249
  if (!context) {
1106
- const err = `${this.transport.clientId} -- no context found for ${service.name}`;
1250
+ const err = `${this.transport.clientId} -- no context found for ${name}`;
1107
1251
  log?.error(err);
1108
1252
  throw new Error(err);
1109
1253
  }
1110
1254
  return context;
1111
1255
  }
1112
1256
  cleanupStream = async (id) => {
1113
- const stream = this.streamMap.get(id);
1114
- if (!stream) {
1257
+ const stream2 = this.streamMap.get(id);
1258
+ if (!stream2) {
1115
1259
  return;
1116
1260
  }
1117
- stream.incoming.end();
1118
- await stream.promises.inputHandler;
1119
- stream.outgoing.end();
1120
- await stream.promises.outputHandler;
1261
+ stream2.incoming.end();
1262
+ await stream2.promises.inputHandler;
1263
+ stream2.outgoing.end();
1264
+ await stream2.promises.outputHandler;
1121
1265
  this.streamMap.delete(id);
1122
1266
  };
1123
1267
  };
@@ -1128,11 +1272,10 @@ function createServer(transport, services, extendedContext) {
1128
1272
  0 && (module.exports = {
1129
1273
  Err,
1130
1274
  Ok,
1275
+ Procedure,
1131
1276
  RiverUncaughtSchema,
1132
- ServiceBuilder,
1277
+ ServiceSchema,
1133
1278
  UNCAUGHT_ERROR,
1134
- buildServiceDefs,
1135
1279
  createClient,
1136
- createServer,
1137
- serializeService
1280
+ createServer
1138
1281
  });