ignotum 0.0.0 → 0.0.2

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 (57) hide show
  1. package/README.md +161 -0
  2. package/dist/cli/bin.d.mts +1 -0
  3. package/dist/cli/bin.mjs +1696 -0
  4. package/dist/cli/bin.mjs.map +1 -0
  5. package/dist/runtime/api-BXXIZc_Q.js +119 -0
  6. package/dist/runtime/api-BXXIZc_Q.js.map +1 -0
  7. package/dist/runtime/api-Cpx57mk3.d.ts +47 -0
  8. package/dist/runtime/client/jsx-dev-runtime.d.ts +2 -0
  9. package/dist/runtime/client/jsx-dev-runtime.js +2 -0
  10. package/dist/runtime/client/jsx-runtime.d.ts +2 -0
  11. package/dist/runtime/client/jsx-runtime.js +2 -0
  12. package/dist/runtime/client.d.ts +29 -0
  13. package/dist/runtime/client.js +364 -0
  14. package/dist/runtime/client.js.map +1 -0
  15. package/dist/runtime/index-BgWROoyk.d.ts +249 -0
  16. package/dist/runtime/internal/api.d.ts +2 -0
  17. package/dist/runtime/internal/api.js +2 -0
  18. package/dist/runtime/internal/server.d.ts +6 -0
  19. package/dist/runtime/internal/server.js +7 -0
  20. package/dist/runtime/internal/server.js.map +1 -0
  21. package/dist/runtime/internal/types.d.ts +2 -0
  22. package/dist/runtime/internal/types.js +2 -0
  23. package/dist/runtime/result-B2W-z2wG.js +136 -0
  24. package/dist/runtime/result-B2W-z2wG.js.map +1 -0
  25. package/dist/runtime/result-C1ZdsM6Y.d.ts +106 -0
  26. package/dist/runtime/schema-CNEVLF7D.js +116 -0
  27. package/dist/runtime/schema-CNEVLF7D.js.map +1 -0
  28. package/dist/runtime/server.d.ts +9 -0
  29. package/dist/runtime/server.js +9 -0
  30. package/dist/runtime/server.js.map +1 -0
  31. package/package.json +81 -2
  32. package/src/cli/agent-files.ts +35 -0
  33. package/src/cli/bin.ts +5 -0
  34. package/src/cli/client-plugin.ts +66 -0
  35. package/src/cli/codegen.ts +203 -0
  36. package/src/cli/command.ts +155 -0
  37. package/src/cli/dev.ts +206 -0
  38. package/src/cli/new-project.ts +377 -0
  39. package/src/cli/package-manager.ts +55 -0
  40. package/src/client/errors.ts +83 -0
  41. package/src/client/hooks.ts +141 -0
  42. package/src/client/index.ts +90 -0
  43. package/src/client/jsx-dev-runtime.ts +2 -0
  44. package/src/client/jsx-runtime.ts +2 -0
  45. package/src/client/query.ts +17 -0
  46. package/src/client/sync.ts +487 -0
  47. package/src/dev-runtime/database.ts +374 -0
  48. package/src/dev-runtime/dev-database.ts +199 -0
  49. package/src/dev-runtime/functions.ts +293 -0
  50. package/src/dev-runtime/id.ts +11 -0
  51. package/src/dev-runtime/sync.ts +473 -0
  52. package/src/internal/api.ts +141 -0
  53. package/src/internal/http-paths.ts +5 -0
  54. package/src/internal/server.ts +3 -0
  55. package/src/internal/types.ts +2 -0
  56. package/src/raw.d.ts +4 -0
  57. package/src/server/index.ts +8 -0
@@ -0,0 +1,473 @@
1
+ // @effect-diagnostics-next-line nodeBuiltinImport:off Vite exposes its HTTP server through Node's adapter types.
2
+ import type { IncomingMessage, ServerResponse } from "node:http";
3
+
4
+ import { NodeHttpServer, NodeServices, NodeSocket } from "@effect/platform-node";
5
+ import { SqliteClient } from "@effect/sql-sqlite-node";
6
+ import {
7
+ ClientMessageJson,
8
+ ServerMessageJson,
9
+ type ClientMessage,
10
+ type FunctionAddress,
11
+ type Operation,
12
+ type ProtocolErrorCode,
13
+ type ServerMessage,
14
+ type SubscriptionId,
15
+ type WireResult,
16
+ } from "@ignotum/contracts/runtime/sync";
17
+ import {
18
+ Effect,
19
+ Context,
20
+ FileSystem,
21
+ Function,
22
+ FiberSet,
23
+ HashMap,
24
+ Layer,
25
+ ManagedRuntime,
26
+ Option,
27
+ Path,
28
+ PartitionedSemaphore,
29
+ PubSub,
30
+ Ref,
31
+ Schema,
32
+ Semaphore,
33
+ } from "effect";
34
+ import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
35
+ import type * as Socket from "effect/unstable/socket/Socket";
36
+ import type { Plugin, ViteDevServer } from "vite";
37
+ import { FunctionRuntime, type PreparedFunction } from "@ignotum/runtime/functions";
38
+ import { QueryInvalidation } from "@ignotum/runtime/sync";
39
+
40
+ import { generate } from "../cli/codegen.js";
41
+ import { isIgnotumPath, syncPath } from "../internal/http-paths.js";
42
+ import { LocalDatabase } from "./database.js";
43
+ import { idGeneratorLayer } from "./id.js";
44
+ import { FunctionExecutor, FunctionRegistry, functionRuntimeLayer } from "./functions.js";
45
+
46
+ interface QuerySubscription {
47
+ readonly args: Schema.Json;
48
+ readonly function: FunctionAddress;
49
+ }
50
+
51
+ export { QueryInvalidation };
52
+
53
+ export const queryInvalidationLayer = Layer.effect(
54
+ QueryInvalidation,
55
+ Effect.gen(function* () {
56
+ const pubsub = yield* PubSub.unbounded<void>();
57
+ return QueryInvalidation.of({
58
+ publish: PubSub.publish(pubsub, undefined),
59
+ subscribe: PubSub.subscribe(pubsub),
60
+ });
61
+ }),
62
+ );
63
+
64
+ const operationForQuery = (subscriptionId: SubscriptionId): Operation => ({
65
+ type: "Query",
66
+ id: subscriptionId,
67
+ });
68
+
69
+ const protocolError = (
70
+ code: ProtocolErrorCode,
71
+ message: string,
72
+ operation?: Operation,
73
+ ): ServerMessage => {
74
+ if (operation === undefined) {
75
+ return { type: "ProtocolError", code, message };
76
+ }
77
+ return { type: "ProtocolError", code, message, operation };
78
+ };
79
+
80
+ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket: Socket.Socket) {
81
+ const runtime = yield* FunctionRuntime;
82
+ const invalidation = yield* QueryInvalidation;
83
+ const subscriptions = yield* Ref.make(HashMap.empty<SubscriptionId, QuerySubscription>());
84
+ const invocationFibers = yield* FiberSet.make();
85
+ const querySemaphore = yield* PartitionedSemaphore.make<SubscriptionId>({ permits: 1 });
86
+ const messageSemaphore = yield* Semaphore.make(1);
87
+ const writeSemaphore = yield* Semaphore.make(1);
88
+ const write = yield* socket.writer;
89
+
90
+ const send = Effect.fn("SyncServer.send")(function* (message: ServerMessage) {
91
+ yield* writeSemaphore.withPermits(1)(
92
+ Schema.encodeEffect(ServerMessageJson)(message).pipe(Effect.flatMap(write)),
93
+ );
94
+ });
95
+
96
+ const sendResolutionError = (
97
+ operation: Operation,
98
+ error:
99
+ | { readonly _tag: "UnknownFunction"; readonly message: string }
100
+ | { readonly _tag: "WrongFunctionKind"; readonly message: string }
101
+ | { readonly _tag: "InvalidArguments"; readonly message: string }
102
+ | { readonly _tag: "FunctionUnavailable"; readonly message: string },
103
+ deliver: (
104
+ message: ServerMessage,
105
+ ) => Effect.Effect<void, Schema.SchemaError | Socket.SocketError> = send,
106
+ ) => {
107
+ const code: ProtocolErrorCode = error._tag;
108
+ return deliver(protocolError(code, error.message, operation));
109
+ };
110
+
111
+ const isActive = (subscriptionId: SubscriptionId, subscription: QuerySubscription) =>
112
+ Ref.get(subscriptions).pipe(
113
+ Effect.map(
114
+ (current) => Option.getOrUndefined(HashMap.get(current, subscriptionId)) === subscription,
115
+ ),
116
+ );
117
+
118
+ const sendIfActive = Effect.fn("SyncServer.sendIfActive")(function* (
119
+ subscriptionId: SubscriptionId,
120
+ subscription: QuerySubscription,
121
+ message: ServerMessage,
122
+ ) {
123
+ if (yield* isActive(subscriptionId, subscription)) {
124
+ yield* send(message);
125
+ }
126
+ });
127
+
128
+ const executeQuery = Effect.fn("SyncServer.executeQuery")(function* (
129
+ subscriptionId: SubscriptionId,
130
+ subscription: QuerySubscription,
131
+ previouslyPrepared?: PreparedFunction,
132
+ ) {
133
+ if (!(yield* isActive(subscriptionId, subscription))) return;
134
+
135
+ const operation = operationForQuery(subscriptionId);
136
+ const deliver = (message: ServerMessage) => sendIfActive(subscriptionId, subscription, message);
137
+ const prepared =
138
+ previouslyPrepared ??
139
+ (yield* runtime.prepare(subscription.function, "Query", subscription.args).pipe(
140
+ Effect.catchTags({
141
+ FunctionUnavailable: (error) => sendResolutionError(operation, error, deliver),
142
+ InvalidArguments: (error) => sendResolutionError(operation, error, deliver),
143
+ UnknownFunction: (error) => sendResolutionError(operation, error, deliver),
144
+ WrongFunctionKind: (error) => sendResolutionError(operation, error, deliver),
145
+ }),
146
+ ));
147
+ if (prepared === undefined) return;
148
+
149
+ const result = yield* prepared.execute;
150
+ yield* deliver({
151
+ type: "QuerySnapshot",
152
+ id: subscriptionId,
153
+ result,
154
+ });
155
+ });
156
+
157
+ const scheduleQuery = (
158
+ subscriptionId: SubscriptionId,
159
+ subscription: QuerySubscription,
160
+ previouslyPrepared?: PreparedFunction,
161
+ ) =>
162
+ querySemaphore
163
+ .withPermits(
164
+ subscriptionId,
165
+ 1,
166
+ )(executeQuery(subscriptionId, subscription, previouslyPrepared))
167
+ .pipe(
168
+ Effect.catchCause((cause) =>
169
+ Effect.logError("A query refresh fiber failed.").pipe(
170
+ Effect.annotateLogs({ cause, function: subscription.function, subscriptionId }),
171
+ ),
172
+ ),
173
+ FiberSet.run(invocationFibers),
174
+ Effect.asVoid,
175
+ );
176
+
177
+ const refresh = Effect.fn("SyncServer.refresh")(function* () {
178
+ const current = yield* Ref.get(subscriptions);
179
+ yield* Effect.forEach(HashMap.toEntries(current), ([subscriptionId, subscription]) =>
180
+ scheduleQuery(subscriptionId, subscription),
181
+ );
182
+ });
183
+
184
+ const invalidations = yield* invalidation.subscribe;
185
+ yield* PubSub.take(invalidations).pipe(
186
+ Effect.flatMap(refresh),
187
+ Effect.forever,
188
+ Effect.forkScoped,
189
+ );
190
+
191
+ const handleSubscribe = Effect.fn("SyncServer.handleSubscribe")(function* (
192
+ message: Extract<ClientMessage, { readonly type: "Subscribe" }>,
193
+ ) {
194
+ const operation = operationForQuery(message.id);
195
+ const current = yield* Ref.get(subscriptions);
196
+
197
+ if (HashMap.has(current, message.id)) {
198
+ yield* send(
199
+ protocolError(
200
+ "DuplicateOperationId",
201
+ `Subscription ${message.id} already exists.`,
202
+ operation,
203
+ ),
204
+ );
205
+ return;
206
+ }
207
+
208
+ const prepared = yield* runtime.prepare(message.function, "Query", message.args).pipe(
209
+ Effect.catchTags({
210
+ FunctionUnavailable: (error) => sendResolutionError(operation, error),
211
+ InvalidArguments: (error) => sendResolutionError(operation, error),
212
+ UnknownFunction: (error) => sendResolutionError(operation, error),
213
+ WrongFunctionKind: (error) => sendResolutionError(operation, error),
214
+ }),
215
+ );
216
+
217
+ if (prepared === undefined) {
218
+ return;
219
+ }
220
+
221
+ const subscription = { args: message.args, function: message.function };
222
+ yield* Ref.update(subscriptions, HashMap.set(message.id, subscription));
223
+ yield* scheduleQuery(message.id, subscription, prepared);
224
+ });
225
+
226
+ const handleMutate = Effect.fn("SyncServer.handleMutate")(function* (
227
+ message: Extract<ClientMessage, { readonly type: "Mutate" }>,
228
+ ) {
229
+ const operation: Operation = { type: "Mutate", id: message.id };
230
+ const prepared = yield* runtime.prepare(message.function, "Mutation", message.args).pipe(
231
+ Effect.catchTags({
232
+ FunctionUnavailable: (error) => sendResolutionError(operation, error),
233
+ InvalidArguments: (error) => sendResolutionError(operation, error),
234
+ UnknownFunction: (error) => sendResolutionError(operation, error),
235
+ WrongFunctionKind: (error) => sendResolutionError(operation, error),
236
+ }),
237
+ );
238
+
239
+ if (prepared === undefined) {
240
+ return;
241
+ }
242
+
243
+ yield* prepared.execute.pipe(
244
+ Effect.flatMap((result: WireResult) => {
245
+ const response = send({ type: "MutationResult", id: message.id, result });
246
+ return result.type === "Success"
247
+ ? response.pipe(Effect.ensuring(invalidation.publish))
248
+ : response;
249
+ }),
250
+ Effect.catchCause((cause) =>
251
+ Effect.logError("A mutation invocation fiber failed.").pipe(
252
+ Effect.annotateLogs({ cause, function: message.function, requestId: message.id }),
253
+ ),
254
+ ),
255
+ FiberSet.run(invocationFibers),
256
+ Effect.asVoid,
257
+ );
258
+ });
259
+
260
+ const handleMessage = Effect.fn("SyncServer.handleMessage")(function* (text: string) {
261
+ const message = yield* Schema.decodeEffect(ClientMessageJson)(text).pipe(
262
+ Effect.catch(() =>
263
+ send(protocolError("InvalidMessage", "The WebSocket frame is not valid Ignotum JSON.")),
264
+ ),
265
+ );
266
+
267
+ if (message === undefined) {
268
+ return;
269
+ }
270
+
271
+ switch (message.type) {
272
+ case "Subscribe":
273
+ yield* handleSubscribe(message);
274
+ return;
275
+ case "Unsubscribe":
276
+ yield* Ref.update(subscriptions, HashMap.remove(message.id));
277
+ return;
278
+ case "Mutate":
279
+ yield* handleMutate(message);
280
+ return;
281
+ }
282
+ });
283
+
284
+ yield* socket.runString((text) => messageSemaphore.withPermits(1)(handleMessage(text)));
285
+ });
286
+
287
+ interface SyncHandlersService {
288
+ readonly http: (request: IncomingMessage, response: ServerResponse) => void;
289
+ readonly upgrade: NodeJS.EventEmitter extends infer _EventEmitter
290
+ ? (request: IncomingMessage, socket: import("node:stream").Duplex, head: Buffer) => void
291
+ : never;
292
+ readonly reload: (file: string, event: "add" | "change" | "unlink") => Effect.Effect<void>;
293
+ }
294
+
295
+ class SyncHandlers extends Context.Service<SyncHandlers, SyncHandlersService>()(
296
+ "ignotum/dev-runtime/sync/SyncHandlers",
297
+ ) {}
298
+
299
+ const makeHandlersLayer = (
300
+ server: ViteDevServer,
301
+ projectDirectory: string,
302
+ databasePath: string,
303
+ ) => {
304
+ const databaseLayer = LocalDatabase.layer.pipe(
305
+ Layer.provide(Layer.merge(idGeneratorLayer, SqliteClient.layer({ filename: databasePath }))),
306
+ );
307
+ const executorLayer = FunctionExecutor.layer.pipe(Layer.provide(databaseLayer));
308
+ const devFunctionRuntimeLayer = functionRuntimeLayer.pipe(
309
+ Layer.provide(
310
+ Layer.merge(
311
+ FunctionRegistry.layer(server, projectDirectory).pipe(Layer.provide(NodeServices.layer)),
312
+ executorLayer,
313
+ ),
314
+ ),
315
+ );
316
+ const dependencies = Layer.mergeAll(
317
+ devFunctionRuntimeLayer,
318
+ queryInvalidationLayer,
319
+ NodeServices.layer,
320
+ );
321
+
322
+ return Layer.effect(
323
+ SyncHandlers,
324
+ Effect.gen(function* () {
325
+ const invalidation = yield* QueryInvalidation;
326
+ const fileSystem = yield* FileSystem.FileSystem;
327
+ const path = yield* Path.Path;
328
+ const scope = yield* Effect.scope;
329
+ const reloadSemaphore = yield* Semaphore.make(1);
330
+ const webSocketServer = yield* Effect.acquireRelease(
331
+ Effect.sync(
332
+ () => new NodeSocket.NodeWS.WebSocketServer({ maxPayload: 1024 * 1024, noServer: true }),
333
+ ),
334
+ (instance) =>
335
+ Effect.callback<void>((resume) => {
336
+ instance.close(() => resume(Effect.void));
337
+ }),
338
+ );
339
+ const httpApp = Effect.gen(function* () {
340
+ const request = yield* HttpServerRequest.HttpServerRequest;
341
+ const pathname = new URL(request.url, "http://ignotum.local").pathname;
342
+ return yield* HttpServerResponse.json(
343
+ pathname === syncPath
344
+ ? { code: "UpgradeRequired", message: `Connect to ${syncPath} with WebSocket.` }
345
+ : { code: "NotFound", message: "Unknown Ignotum API route." },
346
+ { status: pathname === syncPath ? 426 : 404 },
347
+ );
348
+ });
349
+ const socketApp = Effect.gen(function* () {
350
+ const request = yield* HttpServerRequest.HttpServerRequest;
351
+ const socket = yield* request.upgrade;
352
+ yield* runSession(socket);
353
+ return HttpServerResponse.empty();
354
+ });
355
+ const http = yield* NodeHttpServer.makeHandler(httpApp, { scope });
356
+ const upgrade = yield* NodeHttpServer.makeUpgradeHandler(
357
+ Effect.succeed(webSocketServer),
358
+ socketApp,
359
+ { scope },
360
+ );
361
+ const serverDirectory = path.join(projectDirectory, "server");
362
+ const reloadUnsafe = Effect.fn("SyncServer.reload")(function* (
363
+ file: string,
364
+ event: "add" | "change" | "unlink",
365
+ ) {
366
+ const relative = path.relative(serverDirectory, file);
367
+ if (relative.startsWith("..") || path.isAbsolute(relative) || !file.endsWith(".ts")) {
368
+ return;
369
+ }
370
+
371
+ if (event !== "change") {
372
+ yield* generate(projectDirectory).pipe(
373
+ Effect.provideService(FileSystem.FileSystem, fileSystem),
374
+ Effect.provideService(Path.Path, path),
375
+ );
376
+ }
377
+ yield* invalidation.publish;
378
+ });
379
+ const reload = (file: string, event: "add" | "change" | "unlink") =>
380
+ reloadSemaphore
381
+ .withPermits(1)(reloadUnsafe(file, event))
382
+ .pipe(
383
+ Effect.catchCause((cause) =>
384
+ Effect.logError("Could not reload Ignotum server functions.").pipe(
385
+ Effect.annotateLogs({ cause, event, file }),
386
+ ),
387
+ ),
388
+ );
389
+
390
+ return SyncHandlers.of({ http, reload, upgrade });
391
+ }),
392
+ ).pipe(Layer.provide(dependencies));
393
+ };
394
+
395
+ const isApiPath = (request: IncomingMessage): boolean => {
396
+ const pathname = new URL(request.url ?? "/", "http://ignotum.local").pathname;
397
+ return isIgnotumPath(pathname);
398
+ };
399
+
400
+ const isAppOrigin = (host: string | undefined, origin: string | undefined): boolean => {
401
+ if (host === undefined || origin === undefined) return false;
402
+
403
+ return Option.match(Schema.decodeOption(Schema.URLFromString)(origin), {
404
+ onNone: () => false,
405
+ onSome: (url) => url.origin === origin && url.host === host,
406
+ });
407
+ };
408
+
409
+ const rejectUpgrade = (socket: import("node:stream").Duplex): void => {
410
+ socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", () =>
411
+ socket.destroy(),
412
+ );
413
+ };
414
+
415
+ export const syncPluginInternals = { isAppOrigin };
416
+
417
+ export const ignotumSyncPlugin: {
418
+ (databasePath: string): (projectDirectory: string) => Plugin;
419
+ (projectDirectory: string, databasePath: string): Plugin;
420
+ } = Function.dual(2, (projectDirectory: string, databasePath: string): Plugin => ({
421
+ name: "ignotum:sync",
422
+ configureServer: (server) => {
423
+ if (server.httpServer === null) {
424
+ throw new Error("Ignotum sync requires Vite's Node HTTP server.");
425
+ }
426
+
427
+ const runtime = ManagedRuntime.make(makeHandlersLayer(server, projectDirectory, databasePath));
428
+ const httpServer = server.httpServer;
429
+ return runtime.runPromise(SyncHandlers).then(
430
+ (handlers) => {
431
+ const onUpgrade = (
432
+ request: IncomingMessage,
433
+ socket: import("node:stream").Duplex,
434
+ head: Buffer,
435
+ ) => {
436
+ if (new URL(request.url ?? "/", "http://ignotum.local").pathname !== syncPath) return;
437
+ if (!isAppOrigin(request.headers.host, request.headers.origin)) {
438
+ rejectUpgrade(socket);
439
+ return;
440
+ }
441
+ handlers.upgrade(request, socket, head);
442
+ };
443
+ const onAdd = (file: string) => runtime.runFork(handlers.reload(file, "add"));
444
+ const onChange = (file: string) => runtime.runFork(handlers.reload(file, "change"));
445
+ const onUnlink = (file: string) => runtime.runFork(handlers.reload(file, "unlink"));
446
+ const cleanup = () => {
447
+ httpServer.off("upgrade", onUpgrade);
448
+ server.watcher.off("add", onAdd);
449
+ server.watcher.off("change", onChange);
450
+ server.watcher.off("unlink", onUnlink);
451
+ void runtime.dispose();
452
+ };
453
+
454
+ httpServer.on("upgrade", onUpgrade);
455
+ httpServer.once("close", cleanup);
456
+ server.watcher.on("add", onAdd);
457
+ server.watcher.on("change", onChange);
458
+ server.watcher.on("unlink", onUnlink);
459
+ server.middlewares.use((request, response, next) => {
460
+ if (!isApiPath(request)) {
461
+ next();
462
+ return;
463
+ }
464
+ handlers.http(request, response);
465
+ });
466
+ },
467
+ (error) => {
468
+ void runtime.dispose();
469
+ throw error;
470
+ },
471
+ );
472
+ },
473
+ }));
@@ -0,0 +1,141 @@
1
+ import { Predicate } from "effect";
2
+ import type { Effect } from "effect";
3
+
4
+ import type { ErrorValue, InternalServerError } from "@ignotum/contracts/runtime/result";
5
+ import { FunctionAddress } from "@ignotum/contracts/runtime/sync";
6
+
7
+ const FunctionReferenceTypeId: unique symbol = Symbol.for("ignotum/internal/api/FunctionReference");
8
+ declare const FunctionReferenceTypesTypeId: unique symbol;
9
+
10
+ type FunctionKind = "Mutation" | "Query";
11
+
12
+ export interface FunctionReference<
13
+ Kind extends FunctionKind,
14
+ Args,
15
+ Success,
16
+ Failure extends ErrorValue,
17
+ > {
18
+ readonly [FunctionReferenceTypeId]: FunctionAddress;
19
+ readonly [FunctionReferenceTypesTypeId]?: {
20
+ readonly kind: Kind;
21
+ readonly args: Args;
22
+ readonly value: Success;
23
+ readonly error: Failure;
24
+ };
25
+ }
26
+
27
+ type ReferenceTypes<Reference> = Reference extends {
28
+ readonly [FunctionReferenceTypesTypeId]?: infer Types;
29
+ }
30
+ ? Exclude<Types, undefined>
31
+ : never;
32
+
33
+ export declare namespace FunctionReference {
34
+ type Args<Reference> =
35
+ ReferenceTypes<Reference> extends { readonly args: infer Args } ? Args : never;
36
+ type Failure<Reference> =
37
+ ReferenceTypes<Reference> extends {
38
+ readonly error: infer Failure;
39
+ }
40
+ ? Failure
41
+ : never;
42
+ type Kind<Reference> =
43
+ ReferenceTypes<Reference> extends { readonly kind: infer Kind } ? Kind : never;
44
+ type Success<Reference> =
45
+ ReferenceTypes<Reference> extends {
46
+ readonly value: infer Success;
47
+ }
48
+ ? Success
49
+ : never;
50
+ }
51
+
52
+ type ReferenceOf<Definition> = Definition extends {
53
+ readonly _tag: infer Kind extends FunctionKind;
54
+ readonly handler: (
55
+ ...arguments_: infer HandlerArguments
56
+ ) => Generator<infer Yielded, infer Success, never>;
57
+ }
58
+ ? HandlerArguments extends readonly [infer _Context, ...infer Rest]
59
+ ? FunctionReference<
60
+ Kind,
61
+ Rest extends readonly [infer Args, ...ReadonlyArray<unknown>] ? Args : void,
62
+ Success,
63
+ | (Yielded extends Effect.Effect<unknown, infer Failure extends ErrorValue, never>
64
+ ? Failure
65
+ : never)
66
+ | InternalServerError
67
+ >
68
+ : never
69
+ : never;
70
+
71
+ type ApiModule<Module> = {
72
+ readonly [FunctionName in keyof Module as FunctionName extends string
73
+ ? ReferenceOf<Module[FunctionName]> extends never
74
+ ? never
75
+ : FunctionName
76
+ : never]: ReferenceOf<Module[FunctionName]>;
77
+ };
78
+
79
+ export type Api<Modules> = {
80
+ readonly [ModuleName in keyof Modules]: ApiModule<Modules[ModuleName]>;
81
+ };
82
+
83
+ export const functionPathOf = <
84
+ Kind extends FunctionKind,
85
+ Args,
86
+ Success,
87
+ Failure extends ErrorValue,
88
+ >(
89
+ reference: FunctionReference<Kind, Args, Success, Failure>,
90
+ ) => reference[FunctionReferenceTypeId];
91
+
92
+ const makeModuleReference = (moduleName: string) => {
93
+ const references = new Map<string, object>();
94
+
95
+ return new Proxy(
96
+ {},
97
+ {
98
+ get: (_target, functionName) => {
99
+ if (!Predicate.isString(functionName)) {
100
+ return undefined;
101
+ }
102
+
103
+ const existing = references.get(functionName);
104
+ if (existing !== undefined) {
105
+ return existing;
106
+ }
107
+
108
+ const reference = {
109
+ [FunctionReferenceTypeId]: FunctionAddress.make(`api.${moduleName}.${functionName}`),
110
+ };
111
+ references.set(functionName, reference);
112
+ return reference;
113
+ },
114
+ },
115
+ );
116
+ };
117
+
118
+ export function createApi<Modules>(): Api<Modules>;
119
+ export function createApi() {
120
+ const modules = new Map<string, object>();
121
+
122
+ return new Proxy(
123
+ {},
124
+ {
125
+ get: (_target, moduleName) => {
126
+ if (!Predicate.isString(moduleName)) {
127
+ return undefined;
128
+ }
129
+
130
+ const existing = modules.get(moduleName);
131
+ if (existing !== undefined) {
132
+ return existing;
133
+ }
134
+
135
+ const moduleReference = makeModuleReference(moduleName);
136
+ modules.set(moduleName, moduleReference);
137
+ return moduleReference;
138
+ },
139
+ },
140
+ );
141
+ }
@@ -0,0 +1,5 @@
1
+ export const ignotumPathPrefix = "/_ignotum";
2
+ export const syncPath = `${ignotumPathPrefix}/sync`;
3
+
4
+ export const isIgnotumPath = (pathname: string): boolean =>
5
+ pathname === ignotumPathPrefix || pathname.startsWith(`${ignotumPathPrefix}/`);
@@ -0,0 +1,3 @@
1
+ import { bindSchema as bindContractSchema } from "@ignotum/contracts/schema";
2
+
3
+ export const bindSchema: typeof bindContractSchema = bindContractSchema;
@@ -0,0 +1,2 @@
1
+ export { SchemaDefinitionTypeId } from "@ignotum/contracts/schema";
2
+ export type { Document, Id, SchemaDefinitionOf } from "@ignotum/contracts/schema";
package/src/raw.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module "*?raw" {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -0,0 +1,8 @@
1
+ import { Result as contractResult } from "@ignotum/contracts/runtime/result";
2
+ import type { ErrorValue, Result as ContractResult } from "@ignotum/contracts/runtime/result";
3
+ import { defineSchema as defineContractSchema } from "@ignotum/contracts/schema";
4
+
5
+ export const Result: typeof contractResult = contractResult;
6
+ export type Result<Success, Failure extends ErrorValue> = ContractResult<Success, Failure>;
7
+ export const defineSchema: typeof defineContractSchema = defineContractSchema;
8
+ export type { DefinedSchema, SchemaAuthoring } from "@ignotum/contracts/schema";