@proseql/rpc 0.1.0

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.
@@ -0,0 +1,247 @@
1
+ /**
2
+ * RPC Handler Layer implementation.
3
+ *
4
+ * Creates an Effect Layer that provides handlers for all RPC procedures
5
+ * derived from a DatabaseConfig. The layer internally creates an EffectDatabase
6
+ * and wires each handler to the appropriate collection method.
7
+ */
8
+ import { type DatabaseConfig, type DatasetFor, type GenerateDatabase, type GenerateDatabaseWithPersistence, type MigrationError, type PluginError } from "@proseql/core";
9
+ import { Context, Effect, Layer, Stream } from "effect";
10
+ /**
11
+ * Service tag for providing the database instance to handlers.
12
+ * This allows handlers to access the database without creating it themselves.
13
+ */
14
+ export interface DatabaseContext<Config extends DatabaseConfig> {
15
+ readonly db: GenerateDatabase<Config>;
16
+ }
17
+ /**
18
+ * Create a Context.Tag for a specific database configuration.
19
+ * Each config type gets its own unique service identifier.
20
+ */
21
+ export declare const makeDatabaseContextTag: <Config extends DatabaseConfig>() => Context.Tag<DatabaseContext<Config>, DatabaseContext<Config>>;
22
+ /**
23
+ * Internal function to create handlers for a single collection.
24
+ * Returns an object with handler functions for each RPC operation.
25
+ */
26
+ declare const createCollectionHandlers: <Config extends DatabaseConfig>(collectionName: keyof Config, db: GenerateDatabase<Config>) => {
27
+ findById: ({ id }: {
28
+ readonly id: string;
29
+ }) => any;
30
+ query: (config: {
31
+ readonly where?: Record<string, unknown>;
32
+ readonly populate?: Record<string, unknown>;
33
+ readonly sort?: Record<string, "asc" | "desc">;
34
+ readonly select?: Record<string, unknown> | ReadonlyArray<string>;
35
+ readonly limit?: number;
36
+ readonly offset?: number;
37
+ }) => Effect.Effect<readonly Record<string, unknown>[], unknown, never>;
38
+ queryStream: (config: {
39
+ readonly where?: Record<string, unknown>;
40
+ readonly populate?: Record<string, unknown>;
41
+ readonly sort?: Record<string, "asc" | "desc">;
42
+ readonly select?: Record<string, unknown> | ReadonlyArray<string>;
43
+ readonly limit?: number;
44
+ readonly offset?: number;
45
+ readonly streamingOptions?: {
46
+ readonly chunkSize?: number;
47
+ readonly bufferSize?: number;
48
+ };
49
+ }) => Stream.Stream<Record<string, unknown>, unknown, never>;
50
+ create: ({ data }: {
51
+ readonly data: Record<string, unknown>;
52
+ }) => any;
53
+ createMany: ({ data, options, }: {
54
+ readonly data: ReadonlyArray<Record<string, unknown>>;
55
+ readonly options?: {
56
+ readonly skipDuplicates?: boolean;
57
+ readonly validateRelationships?: boolean;
58
+ };
59
+ }) => any;
60
+ update: ({ id, updates, }: {
61
+ readonly id: string;
62
+ readonly updates: Record<string, unknown>;
63
+ }) => any;
64
+ updateMany: ({ where, updates, }: {
65
+ readonly where: Record<string, unknown>;
66
+ readonly updates: Record<string, unknown>;
67
+ }) => any;
68
+ delete: ({ id }: {
69
+ readonly id: string;
70
+ }) => any;
71
+ deleteMany: ({ where, options, }: {
72
+ readonly where: Record<string, unknown>;
73
+ readonly options?: {
74
+ readonly limit?: number;
75
+ };
76
+ }) => any;
77
+ aggregate: (config: {
78
+ readonly count?: boolean;
79
+ readonly sum?: string | ReadonlyArray<string>;
80
+ readonly avg?: string | ReadonlyArray<string>;
81
+ readonly min?: string | ReadonlyArray<string>;
82
+ readonly max?: string | ReadonlyArray<string>;
83
+ readonly groupBy?: string | ReadonlyArray<string>;
84
+ readonly where?: Record<string, unknown>;
85
+ }) => any;
86
+ upsert: ({ where, create: createData, update: updateData, }: {
87
+ readonly where: Record<string, unknown>;
88
+ readonly create: Record<string, unknown>;
89
+ readonly update: Record<string, unknown>;
90
+ }) => any;
91
+ upsertMany: ({ data, }: {
92
+ readonly data: ReadonlyArray<{
93
+ readonly where: Record<string, unknown>;
94
+ readonly create: Record<string, unknown>;
95
+ readonly update: Record<string, unknown>;
96
+ }>;
97
+ }) => any;
98
+ };
99
+ /**
100
+ * Handler type for the RPC layer.
101
+ * This is the shape of handlers that need to be provided to the RpcGroup.
102
+ */
103
+ export type RpcHandlers<Config extends DatabaseConfig> = {
104
+ readonly [K in keyof Config & string]: ReturnType<typeof createCollectionHandlers<Config>>;
105
+ };
106
+ /**
107
+ * Create an Effect Layer that provides handlers for all RPC procedures.
108
+ *
109
+ * The layer:
110
+ * 1. Creates an in-memory EffectDatabase from the config and optional initial data
111
+ * 2. For each collection, wires handlers to the appropriate database methods
112
+ * 3. Returns a Layer that provides the handler implementations
113
+ *
114
+ * @param config - The database configuration defining collections and schemas
115
+ * @param initialData - Optional initial data to seed the database
116
+ * @returns An Effect that produces the handler implementations for use with RpcGroup.toLayer
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * import { Layer } from "effect"
121
+ * import { makeRpcHandlers, makeRpcGroup } from "@proseql/rpc"
122
+ *
123
+ * const config = {
124
+ * books: { schema: BookSchema, relationships: {} },
125
+ * } as const
126
+ *
127
+ * const rpcs = makeRpcGroup(config)
128
+ *
129
+ * // Create handler implementations
130
+ * const handlerEffect = makeRpcHandlers(config, {
131
+ * books: [{ id: "1", title: "Dune" }],
132
+ * })
133
+ *
134
+ * // Use with RpcGroup.toLayer for the complete RPC server layer
135
+ * ```
136
+ */
137
+ export declare const makeRpcHandlers: <Config extends DatabaseConfig>(config: Config, initialData?: Partial<DatasetFor<Config>>) => Effect.Effect<RpcHandlers<Config>, MigrationError | PluginError>;
138
+ /**
139
+ * Create an Effect Layer that provides RPC handlers for all collections.
140
+ *
141
+ * This is a convenience wrapper that combines makeRpcHandlers with makeRpcGroup
142
+ * to produce a complete Layer ready for use with Effect RPC server.
143
+ *
144
+ * @param config - The database configuration
145
+ * @param initialData - Optional initial data to seed the database
146
+ * @returns A Layer providing all RPC handlers
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * import { Effect } from "effect"
151
+ * import { makeRpcHandlersLayer } from "@proseql/rpc"
152
+ *
153
+ * const config = {
154
+ * books: { schema: BookSchema, relationships: {} },
155
+ * } as const
156
+ *
157
+ * // Create handler layer (can be composed with other layers)
158
+ * const handlersLayer = makeRpcHandlersLayer(config, {
159
+ * books: [{ id: "1", title: "Dune" }],
160
+ * })
161
+ * ```
162
+ */
163
+ export declare const makeRpcHandlersLayer: <Config extends DatabaseConfig>(config: Config, initialData?: Partial<DatasetFor<Config>>) => Layer.Layer<DatabaseContext<Config>, MigrationError | PluginError>;
164
+ /**
165
+ * Create RPC handlers from an existing database instance.
166
+ *
167
+ * This function accepts any EffectDatabase or EffectDatabaseWithPersistence
168
+ * and wires handlers to delegate to the collection methods. When the database
169
+ * is a persistent database (created via createPersistentEffectDatabase),
170
+ * mutations automatically trigger persistence as normal.
171
+ *
172
+ * This is the recommended approach for production use cases where you need:
173
+ * - File-based persistence with debounced writes
174
+ * - Control over the database lifecycle
175
+ * - Multiple transports (RPC, REST) sharing the same database instance
176
+ *
177
+ * @param config - The database configuration (used to enumerate collections)
178
+ * @param db - An existing EffectDatabase or EffectDatabaseWithPersistence instance
179
+ * @returns The RPC handler implementations
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * import { Effect, Layer } from "effect"
184
+ * import { createPersistentEffectDatabase, NodeStorageLayer, makeSerializerLayer, jsonCodec } from "@proseql/node"
185
+ * import { makeRpcHandlersFromDatabase } from "@proseql/rpc"
186
+ *
187
+ * const config = {
188
+ * books: {
189
+ * schema: BookSchema,
190
+ * file: "./data/books.json", // persistence enabled
191
+ * relationships: {},
192
+ * },
193
+ * } as const
194
+ *
195
+ * const program = Effect.gen(function* () {
196
+ * // Create persistent database
197
+ * const db = yield* createPersistentEffectDatabase(config, { books: [] })
198
+ *
199
+ * // Wire RPC handlers to the persistent database
200
+ * const handlers = makeRpcHandlersFromDatabase(config, db)
201
+ *
202
+ * // Mutations through RPC now trigger persistence automatically
203
+ * yield* handlers.books.create({ data: { id: "1", title: "Dune" } })
204
+ *
205
+ * // Flush to ensure data is written
206
+ * await db.flush()
207
+ * })
208
+ *
209
+ * const PersistenceLayer = Layer.merge(
210
+ * NodeStorageLayer,
211
+ * makeSerializerLayer([jsonCodec()]),
212
+ * )
213
+ *
214
+ * await Effect.runPromise(
215
+ * program.pipe(Effect.provide(PersistenceLayer), Effect.scoped),
216
+ * )
217
+ * ```
218
+ */
219
+ export declare const makeRpcHandlersFromDatabase: <Config extends DatabaseConfig>(config: Config, db: GenerateDatabase<Config> | GenerateDatabaseWithPersistence<Config>) => RpcHandlers<Config>;
220
+ /**
221
+ * Create an Effect Layer that provides RPC handlers from an existing database.
222
+ *
223
+ * Similar to makeRpcHandlersLayer, but accepts an existing database instance
224
+ * instead of creating one internally. This allows you to use a persistent
225
+ * database with the RPC layer.
226
+ *
227
+ * @param db - An existing EffectDatabase or EffectDatabaseWithPersistence instance
228
+ * @returns A Layer providing all RPC handlers via DatabaseContext
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * import { Effect, Layer } from "effect"
233
+ * import { createPersistentEffectDatabase } from "@proseql/node"
234
+ * import { makeRpcHandlersLayerFromDatabase, makeDatabaseContextTag } from "@proseql/rpc"
235
+ *
236
+ * const program = Effect.gen(function* () {
237
+ * const db = yield* createPersistentEffectDatabase(config, initialData)
238
+ * const handlerLayer = makeRpcHandlersLayerFromDatabase(db)
239
+ *
240
+ * // Use the layer with your RPC server
241
+ * // ...
242
+ * })
243
+ * ```
244
+ */
245
+ export declare const makeRpcHandlersLayerFromDatabase: <Config extends DatabaseConfig>(db: GenerateDatabase<Config> | GenerateDatabaseWithPersistence<Config>) => Layer.Layer<DatabaseContext<Config>>;
246
+ export {};
247
+ //# sourceMappingURL=rpc-handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../src/rpc-handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,266 @@
1
+ /**
2
+ * RPC Handler Layer implementation.
3
+ *
4
+ * Creates an Effect Layer that provides handlers for all RPC procedures
5
+ * derived from a DatabaseConfig. The layer internally creates an EffectDatabase
6
+ * and wires each handler to the appropriate collection method.
7
+ */
8
+ import { createEffectDatabase, } from "@proseql/core";
9
+ import { Chunk, Context, Effect, Layer, Stream } from "effect";
10
+ /**
11
+ * Create a Context.Tag for a specific database configuration.
12
+ * Each config type gets its own unique service identifier.
13
+ */
14
+ export const makeDatabaseContextTag = () => Context.GenericTag("@proseql/rpc/DatabaseContext");
15
+ // ============================================================================
16
+ // Handler Implementations
17
+ // ============================================================================
18
+ /**
19
+ * Internal function to create handlers for a single collection.
20
+ * Returns an object with handler functions for each RPC operation.
21
+ */
22
+ const createCollectionHandlers = (collectionName, db) => {
23
+ // biome-ignore lint/suspicious/noExplicitAny: Collection type is dynamic based on config
24
+ const collection = db[collectionName];
25
+ return {
26
+ findById: ({ id }) => collection.findById(id),
27
+ query: (config) => {
28
+ // Query returns a RunnableStream; collect it to an array for RPC response
29
+ const stream = collection.query(config);
30
+ // The stream is a Stream.Stream at runtime (RunnableStream wrapper)
31
+ return Stream.runCollect(stream).pipe(Effect.map(Chunk.toReadonlyArray));
32
+ },
33
+ queryStream: (config) => {
34
+ // Return the stream directly for incremental delivery over RPC transport
35
+ // The RPC layer will serialize stream items as they are emitted
36
+ const baseStream = collection.query(config);
37
+ // Apply rechunking if streamingOptions.chunkSize is specified
38
+ // This batches items into chunks of the specified size before they are
39
+ // sent over the RPC transport, reducing overhead at the cost of increased
40
+ // latency to first item. The RPC layer transmits items in chunks, so this
41
+ // ensures each transmission contains up to `chunkSize` items.
42
+ //
43
+ // Note: bufferSize is a client-side hint that should be passed to the
44
+ // RPC client's streamBufferSize option when making the call.
45
+ const chunkSize = config.streamingOptions?.chunkSize;
46
+ if (chunkSize && chunkSize > 1) {
47
+ return Stream.rechunk(baseStream, chunkSize);
48
+ }
49
+ return baseStream;
50
+ },
51
+ create: ({ data }) =>
52
+ // biome-ignore lint/suspicious/noExplicitAny: Data type is dynamic based on schema
53
+ collection.create(data),
54
+ createMany: ({ data, options, }) =>
55
+ // biome-ignore lint/suspicious/noExplicitAny: Data type is dynamic based on schema
56
+ collection.createMany(data, options),
57
+ update: ({ id, updates, }) =>
58
+ // biome-ignore lint/suspicious/noExplicitAny: Updates type is dynamic based on schema
59
+ collection.update(id, updates),
60
+ updateMany: ({ where, updates, }) => collection.updateMany(
61
+ // For RPC we receive where clause, convert to predicate that matches records
62
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic predicate - entity type unknown at runtime
63
+ (entity) => {
64
+ for (const [key, value] of Object.entries(where)) {
65
+ if (entity[key] !== value)
66
+ return false;
67
+ }
68
+ return true;
69
+ },
70
+ // biome-ignore lint/suspicious/noExplicitAny: Updates type is dynamic based on collection schema
71
+ updates),
72
+ delete: ({ id }) => collection.delete(id),
73
+ deleteMany: ({ where, options, }) =>
74
+ // biome-ignore lint/suspicious/noExplicitAny: Predicate is dynamic
75
+ collection.deleteMany((entity) => {
76
+ for (const [key, value] of Object.entries(where)) {
77
+ if (entity[key] !== value)
78
+ return false;
79
+ }
80
+ return true;
81
+ }, options),
82
+ aggregate: (config) =>
83
+ // biome-ignore lint/suspicious/noExplicitAny: Aggregate config is dynamic
84
+ collection.aggregate(config),
85
+ upsert: ({ where, create: createData, update: updateData, }) => collection.upsert({
86
+ where,
87
+ create: createData,
88
+ update: updateData,
89
+ // biome-ignore lint/suspicious/noExplicitAny: Upsert data is dynamic - types unknown at runtime
90
+ }),
91
+ upsertMany: ({ data, }) =>
92
+ // biome-ignore lint/suspicious/noExplicitAny: Upsert data is dynamic
93
+ collection.upsertMany(data),
94
+ };
95
+ };
96
+ /**
97
+ * Create an Effect Layer that provides handlers for all RPC procedures.
98
+ *
99
+ * The layer:
100
+ * 1. Creates an in-memory EffectDatabase from the config and optional initial data
101
+ * 2. For each collection, wires handlers to the appropriate database methods
102
+ * 3. Returns a Layer that provides the handler implementations
103
+ *
104
+ * @param config - The database configuration defining collections and schemas
105
+ * @param initialData - Optional initial data to seed the database
106
+ * @returns An Effect that produces the handler implementations for use with RpcGroup.toLayer
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { Layer } from "effect"
111
+ * import { makeRpcHandlers, makeRpcGroup } from "@proseql/rpc"
112
+ *
113
+ * const config = {
114
+ * books: { schema: BookSchema, relationships: {} },
115
+ * } as const
116
+ *
117
+ * const rpcs = makeRpcGroup(config)
118
+ *
119
+ * // Create handler implementations
120
+ * const handlerEffect = makeRpcHandlers(config, {
121
+ * books: [{ id: "1", title: "Dune" }],
122
+ * })
123
+ *
124
+ * // Use with RpcGroup.toLayer for the complete RPC server layer
125
+ * ```
126
+ */
127
+ export const makeRpcHandlers = (config, initialData) => Effect.gen(function* () {
128
+ // Create the in-memory database with optional initial data
129
+ // biome-ignore lint/suspicious/noExplicitAny: DatasetFor type is complex and requires casting
130
+ const db = yield* createEffectDatabase(config, initialData);
131
+ // Build handlers for each collection
132
+ const handlers = {};
133
+ for (const collectionName of Object.keys(config)) {
134
+ handlers[collectionName] = createCollectionHandlers(collectionName, db);
135
+ }
136
+ return handlers;
137
+ });
138
+ /**
139
+ * Create an Effect Layer that provides RPC handlers for all collections.
140
+ *
141
+ * This is a convenience wrapper that combines makeRpcHandlers with makeRpcGroup
142
+ * to produce a complete Layer ready for use with Effect RPC server.
143
+ *
144
+ * @param config - The database configuration
145
+ * @param initialData - Optional initial data to seed the database
146
+ * @returns A Layer providing all RPC handlers
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * import { Effect } from "effect"
151
+ * import { makeRpcHandlersLayer } from "@proseql/rpc"
152
+ *
153
+ * const config = {
154
+ * books: { schema: BookSchema, relationships: {} },
155
+ * } as const
156
+ *
157
+ * // Create handler layer (can be composed with other layers)
158
+ * const handlersLayer = makeRpcHandlersLayer(config, {
159
+ * books: [{ id: "1", title: "Dune" }],
160
+ * })
161
+ * ```
162
+ */
163
+ export const makeRpcHandlersLayer = (config, initialData) => {
164
+ const DatabaseContextTag = makeDatabaseContextTag();
165
+ return Layer.effect(DatabaseContextTag, Effect.gen(function* () {
166
+ // biome-ignore lint/suspicious/noExplicitAny: DatasetFor type is complex and requires casting
167
+ const db = yield* createEffectDatabase(config, initialData);
168
+ return { db };
169
+ }));
170
+ };
171
+ // ============================================================================
172
+ // Database-First Handler Factory
173
+ // ============================================================================
174
+ /**
175
+ * Create RPC handlers from an existing database instance.
176
+ *
177
+ * This function accepts any EffectDatabase or EffectDatabaseWithPersistence
178
+ * and wires handlers to delegate to the collection methods. When the database
179
+ * is a persistent database (created via createPersistentEffectDatabase),
180
+ * mutations automatically trigger persistence as normal.
181
+ *
182
+ * This is the recommended approach for production use cases where you need:
183
+ * - File-based persistence with debounced writes
184
+ * - Control over the database lifecycle
185
+ * - Multiple transports (RPC, REST) sharing the same database instance
186
+ *
187
+ * @param config - The database configuration (used to enumerate collections)
188
+ * @param db - An existing EffectDatabase or EffectDatabaseWithPersistence instance
189
+ * @returns The RPC handler implementations
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * import { Effect, Layer } from "effect"
194
+ * import { createPersistentEffectDatabase, NodeStorageLayer, makeSerializerLayer, jsonCodec } from "@proseql/node"
195
+ * import { makeRpcHandlersFromDatabase } from "@proseql/rpc"
196
+ *
197
+ * const config = {
198
+ * books: {
199
+ * schema: BookSchema,
200
+ * file: "./data/books.json", // persistence enabled
201
+ * relationships: {},
202
+ * },
203
+ * } as const
204
+ *
205
+ * const program = Effect.gen(function* () {
206
+ * // Create persistent database
207
+ * const db = yield* createPersistentEffectDatabase(config, { books: [] })
208
+ *
209
+ * // Wire RPC handlers to the persistent database
210
+ * const handlers = makeRpcHandlersFromDatabase(config, db)
211
+ *
212
+ * // Mutations through RPC now trigger persistence automatically
213
+ * yield* handlers.books.create({ data: { id: "1", title: "Dune" } })
214
+ *
215
+ * // Flush to ensure data is written
216
+ * await db.flush()
217
+ * })
218
+ *
219
+ * const PersistenceLayer = Layer.merge(
220
+ * NodeStorageLayer,
221
+ * makeSerializerLayer([jsonCodec()]),
222
+ * )
223
+ *
224
+ * await Effect.runPromise(
225
+ * program.pipe(Effect.provide(PersistenceLayer), Effect.scoped),
226
+ * )
227
+ * ```
228
+ */
229
+ export const makeRpcHandlersFromDatabase = (config, db) => {
230
+ // Build handlers for each collection, delegating to the provided database
231
+ const handlers = {};
232
+ for (const collectionName of Object.keys(config)) {
233
+ handlers[collectionName] = createCollectionHandlers(collectionName, db);
234
+ }
235
+ return handlers;
236
+ };
237
+ /**
238
+ * Create an Effect Layer that provides RPC handlers from an existing database.
239
+ *
240
+ * Similar to makeRpcHandlersLayer, but accepts an existing database instance
241
+ * instead of creating one internally. This allows you to use a persistent
242
+ * database with the RPC layer.
243
+ *
244
+ * @param db - An existing EffectDatabase or EffectDatabaseWithPersistence instance
245
+ * @returns A Layer providing all RPC handlers via DatabaseContext
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * import { Effect, Layer } from "effect"
250
+ * import { createPersistentEffectDatabase } from "@proseql/node"
251
+ * import { makeRpcHandlersLayerFromDatabase, makeDatabaseContextTag } from "@proseql/rpc"
252
+ *
253
+ * const program = Effect.gen(function* () {
254
+ * const db = yield* createPersistentEffectDatabase(config, initialData)
255
+ * const handlerLayer = makeRpcHandlersLayerFromDatabase(db)
256
+ *
257
+ * // Use the layer with your RPC server
258
+ * // ...
259
+ * })
260
+ * ```
261
+ */
262
+ export const makeRpcHandlersLayerFromDatabase = (db) => {
263
+ const DatabaseContextTag = makeDatabaseContextTag();
264
+ return Layer.succeed(DatabaseContextTag, { db });
265
+ };
266
+ //# sourceMappingURL=rpc-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-handlers.js","sourceRoot":"","sources":["../src/rpc-handlers.ts"],"names":[],"mappings":"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"}