ignotum 0.0.11 → 0.0.13

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 (61) hide show
  1. package/README.md +8 -3
  2. package/dist/cli/bin.mjs +1372 -492
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/runtime/{api-DzcR7spt.js → api-CAgKDij7.js} +38 -2
  5. package/dist/runtime/api-CAgKDij7.js.map +1 -0
  6. package/dist/runtime/{api-DR_8vfKg.d.ts → api-Cv3hMbzo.d.ts} +4 -3
  7. package/dist/runtime/client.d.ts +39 -8
  8. package/dist/runtime/client.js +505 -131
  9. package/dist/runtime/client.js.map +1 -1
  10. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +154 -0
  11. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +1 -0
  12. package/dist/runtime/id-Bt9XWRGL.js +423 -0
  13. package/dist/runtime/id-Bt9XWRGL.js.map +1 -0
  14. package/dist/runtime/{id-Cs82tq9Q-hmYFyqTa.d.ts → id-BzFHf3Wo-DGPCjgrf.d.ts} +13 -13
  15. package/dist/runtime/id-Dz0apuB3.d.ts +1 -0
  16. package/dist/runtime/{index-2q9FwJud.d.ts → index-D54flWtH.d.ts} +136 -76
  17. package/dist/runtime/internal/api.d.ts +1 -1
  18. package/dist/runtime/internal/api.js +1 -1
  19. package/dist/runtime/internal/host.d.ts +21 -17
  20. package/dist/runtime/internal/host.js +22 -8
  21. package/dist/runtime/internal/host.js.map +1 -1
  22. package/dist/runtime/internal/server.d.ts +1 -1
  23. package/dist/runtime/internal/server.js +1 -1
  24. package/dist/runtime/internal/types.d.ts +1 -1
  25. package/dist/runtime/internal/types.js +1 -1
  26. package/dist/runtime/{pagination-Bt3l7QaC-BYEGmLBE.d.ts → pagination-DnKg3dkI-r5ZUxBBx.d.ts} +34 -6
  27. package/dist/runtime/pagination-Dz0apuB3.d.ts +1 -0
  28. package/dist/runtime/result-DKAA4gpS.d.ts +1 -0
  29. package/dist/runtime/{schema-ERFjT8-m.js → schema-1Zs03-iS.js} +135 -17
  30. package/dist/runtime/schema-1Zs03-iS.js.map +1 -0
  31. package/dist/runtime/server.d.ts +9 -3
  32. package/dist/runtime/server.js +5 -3
  33. package/dist/runtime/server.js.map +1 -1
  34. package/dist/runtime/{sync-a2EdGSdY.d.ts → sync-Bs8J3fIr.d.ts} +2 -2
  35. package/package.json +4 -4
  36. package/src/cli/agent-files.ts +8 -0
  37. package/src/cli/auth-client.ts +1 -1
  38. package/src/cli/bin.ts +13 -3
  39. package/src/cli/build/server.ts +102 -13
  40. package/src/cli/codegen.ts +7 -4
  41. package/src/cli/control-client.ts +30 -8
  42. package/src/cli/deploy.ts +7 -2
  43. package/src/cli/environment.ts +133 -0
  44. package/src/client/files.ts +43 -27
  45. package/src/client/hooks.ts +94 -17
  46. package/src/client/id.ts +259 -0
  47. package/src/client/index.ts +5 -2
  48. package/src/client/page-observers.ts +77 -0
  49. package/src/client/sync.ts +214 -69
  50. package/src/dev-runtime/functions.ts +232 -86
  51. package/src/dev-runtime/id.ts +174 -2
  52. package/src/dev-runtime/query-cache.ts +105 -0
  53. package/src/dev-runtime/sync.ts +187 -26
  54. package/src/server/index.ts +13 -1
  55. package/dist/runtime/api-DzcR7spt.js.map +0 -1
  56. package/dist/runtime/descriptor-C5VA9qRl-DuQsowaQ.js +0 -311
  57. package/dist/runtime/descriptor-C5VA9qRl-DuQsowaQ.js.map +0 -1
  58. package/dist/runtime/file-C1abuMgd.js +0 -173
  59. package/dist/runtime/file-C1abuMgd.js.map +0 -1
  60. package/dist/runtime/pagination-CFJ3xlAt.d.ts +0 -1
  61. package/dist/runtime/schema-ERFjT8-m.js.map +0 -1
@@ -0,0 +1,105 @@
1
+ import { Context, Deferred, Effect, Exit, Layer, Semaphore } from "effect";
2
+ import { idViewKey, type SessionState } from "@ignotum/contracts/id";
3
+ import type {
4
+ RuntimeInvocationResult,
5
+ RuntimeQueryResult,
6
+ } from "@ignotum/contracts/runtime/hosted";
7
+ import { makeDependencyIndex, type QueryInvalidationEvent } from "@ignotum/runtime/sync";
8
+
9
+ export class LocalQueryCache extends Context.Service<
10
+ LocalQueryCache,
11
+ {
12
+ readonly execute: (
13
+ base: string,
14
+ identity: SessionState,
15
+ minimumRevision: number,
16
+ execute: Effect.Effect<RuntimeInvocationResult>,
17
+ ) => Effect.Effect<RuntimeInvocationResult>;
18
+ readonly invalidate: (event: QueryInvalidationEvent) => void;
19
+ }
20
+ >()("ignotum/dev-runtime/query-cache/LocalQueryCache") {
21
+ static readonly layer = Layer.effect(
22
+ LocalQueryCache,
23
+ Effect.sync(() => {
24
+ const results = new Map<string, RuntimeQueryResult>();
25
+ const dependencies = makeDependencyIndex<string>();
26
+ const flights = new Map<string, Deferred.Deferred<RuntimeInvocationResult>>();
27
+ const publicQueries = new Set<string>();
28
+ const locks = new Map<string, Semaphore.Semaphore>();
29
+ let generation = 0;
30
+ const save = (key: string, result: RuntimeQueryResult) => {
31
+ results.delete(key);
32
+ results.set(key, result);
33
+ dependencies.record(key, result.dependencies);
34
+ while (results.size > 128) {
35
+ const oldest = results.keys().next().value;
36
+ if (oldest === undefined) break;
37
+ results.delete(oldest);
38
+ dependencies.remove(oldest);
39
+ publicQueries.delete(oldest);
40
+ }
41
+ };
42
+ return LocalQueryCache.of({
43
+ invalidate: (event) => {
44
+ generation++;
45
+ const keys =
46
+ event.type === "All"
47
+ ? [...results.keys()]
48
+ : [...dependencies.affected(event.invalidations)];
49
+ for (const key of keys) {
50
+ results.delete(key);
51
+ dependencies.remove(key);
52
+ }
53
+ if (event.type === "All") publicQueries.clear();
54
+ },
55
+ execute: Effect.fn("LocalQueryCache.execute")(
56
+ function* (base, identity, minimumRevision, execute) {
57
+ const admitted = generation;
58
+ const scoped = `${base}:id:${idViewKey(identity)}`;
59
+ const cached = results.get(base) ?? results.get(scoped);
60
+ if (cached !== undefined && cached.observedRevision >= minimumRevision) return cached;
61
+ const key = `${scoped}:${admitted}`;
62
+ const pending = flights.get(key);
63
+ if (pending !== undefined) return yield* Deferred.await(pending);
64
+ const done = yield* Deferred.make<RuntimeInvocationResult>();
65
+ flights.set(key, done);
66
+ const evaluate = Effect.gen(function* () {
67
+ const shared = results.get(base);
68
+ if (shared !== undefined && shared.observedRevision >= minimumRevision) return shared;
69
+ const result = yield* execute;
70
+ if (result.type === "Query" && generation === admitted) {
71
+ const readsId = result.dependencies.some((dependency) => dependency.type === "Id");
72
+ save(scoped, result);
73
+ if (!readsId) {
74
+ publicQueries.add(base);
75
+ save(base, result);
76
+ }
77
+ }
78
+ return result;
79
+ });
80
+ const lockKey = `${base}:${admitted}`;
81
+ let lock = locks.get(lockKey);
82
+ if (publicQueries.has(base) && lock === undefined) {
83
+ lock = Semaphore.makeUnsafe(1);
84
+ locks.set(lockKey, lock);
85
+ }
86
+ return yield* Effect.uninterruptibleMask((restore) =>
87
+ Effect.gen(function* () {
88
+ const exit = yield* restore(
89
+ lock === undefined ? evaluate : lock.withPermits(1)(evaluate),
90
+ ).pipe(Effect.exit);
91
+ Deferred.doneUnsafe(
92
+ done,
93
+ Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause),
94
+ );
95
+ flights.delete(key);
96
+ locks.delete(lockKey);
97
+ return yield* exit;
98
+ }),
99
+ );
100
+ },
101
+ ),
102
+ });
103
+ }),
104
+ );
105
+ }
@@ -1,3 +1,12 @@
1
+ import { LocalQueryCache } from "./query-cache.js";
2
+ import {
3
+ SessionRequest,
4
+ idViewKey,
5
+ sessionPath,
6
+ sessionRequestsPath,
7
+ type SessionState,
8
+ } from "@ignotum/contracts/id";
9
+ import { LocalId, LocalCompletion, localIdPage, localIdPath } from "./id.js";
1
10
  // @effect-diagnostics-next-line nodeBuiltinImport:off Vite exposes its HTTP server through Node's adapter types.
2
11
  import type { IncomingMessage, ServerResponse } from "node:http";
3
12
 
@@ -25,6 +34,7 @@ import {
25
34
  } from "@ignotum/contracts/runtime/sync";
26
35
  import {
27
36
  Effect,
37
+ Clock,
28
38
  Context,
29
39
  FileSystem,
30
40
  Function,
@@ -62,9 +72,14 @@ import {
62
72
  import { generate } from "../cli/codegen.js";
63
73
  import { isIgnotumPath, syncPath } from "../internal/http-paths.js";
64
74
  import { LocalDatabase } from "./database.js";
65
- import { idGeneratorLayer } from "./id.js";
75
+ import { IdGenerator } from "@ignotum/shared/id";
66
76
  import { DevelopmentDatabase } from "./migrations.js";
67
- import { FunctionExecutor, FunctionRegistry, functionRuntimeLayer } from "./functions.js";
77
+ import {
78
+ DevelopmentEnvironment,
79
+ FunctionExecutor,
80
+ FunctionRegistry,
81
+ functionRuntimeLayer,
82
+ } from "./functions.js";
68
83
  import { LocalApplicationFiles, localApplicationFilesLayer } from "./files.js";
69
84
 
70
85
  interface QuerySubscription {
@@ -82,15 +97,22 @@ export const localSyncIdentity = {
82
97
 
83
98
  export { QueryInvalidation };
84
99
 
100
+ const reloadEnvironmentAndInvalidate = <Error, Requirements>(
101
+ reload: Effect.Effect<void, Error, Requirements>,
102
+ publish: Effect.Effect<void>,
103
+ ) => reload.pipe(Effect.ensuring(publish));
104
+
85
105
  export const queryInvalidationLayer = Layer.effect(
86
106
  QueryInvalidation,
87
107
  Effect.gen(function* () {
108
+ const cache = yield* LocalQueryCache;
88
109
  const pubsub = yield* PubSub.unbounded<QueryInvalidationEvent>();
89
110
  const latestRevision = yield* Ref.make(AppStateRevision.make(0));
90
111
  return QueryInvalidation.of({
91
112
  latestRevision: Ref.get(latestRevision),
92
113
  publish: (event) =>
93
114
  Effect.gen(function* () {
115
+ cache.invalidate(event);
94
116
  if (event.type === "Dependencies") {
95
117
  yield* Ref.update(latestRevision, (current) =>
96
118
  AppStateRevision.make(Math.max(current, event.committedRevision)),
@@ -101,7 +123,7 @@ export const queryInvalidationLayer = Layer.effect(
101
123
  subscribe: PubSub.subscribe(pubsub),
102
124
  });
103
125
  }),
104
- );
126
+ ).pipe(Layer.provideMerge(LocalQueryCache.layer));
105
127
 
106
128
  class InvocationIdConflict extends Schema.TaggedError<InvocationIdConflict>()(
107
129
  "InvocationIdConflict",
@@ -188,10 +210,21 @@ const syncError = (code: ErrorCode, message: string, operation?: Operation): Ser
188
210
  return { type: "Error", code, message, operation };
189
211
  };
190
212
 
191
- export const runSession = Effect.fn("SyncServer.runSession")(function* (socket: Socket.Socket) {
213
+ export const runSession = Effect.fn("SyncServer.runSession")(function* (
214
+ socket: Socket.Socket,
215
+ initialIdentity?: SessionState,
216
+ refreshIdentity?: Effect.Effect<SessionState>,
217
+ ) {
218
+ let identity = initialIdentity ?? {
219
+ sessionEpoch: "dev:anonymous",
220
+ user: null,
221
+ viewRevision: 0,
222
+ validUntil: Number.MAX_SAFE_INTEGER,
223
+ };
192
224
  const runtime = yield* FunctionRuntime;
193
225
  const invalidation = yield* QueryInvalidation;
194
226
  const mutationReplay = yield* MutationReplay;
227
+ const queryCache = yield* LocalQueryCache;
195
228
  const files = yield* LocalApplicationFiles;
196
229
  const subscriptions = yield* Ref.make(HashMap.empty<SubscriptionId, QuerySubscription>());
197
230
  const dependencyIndex = makeDependencyIndex<SubscriptionId>();
@@ -201,8 +234,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
201
234
  const write = yield* socket.writer;
202
235
 
203
236
  const send = Effect.fn("SyncServer.send")(function* (message: ServerMessage) {
237
+ const bound =
238
+ message.type === "Result" || message.type === "Preparation" || message.type === "Error"
239
+ ? { ...message, sessionEpoch: identity.sessionEpoch }
240
+ : message;
204
241
  yield* writeSemaphore.withPermits(1)(
205
- Schema.encodeEffect(ServerMessageJson)(message).pipe(Effect.flatMap(write)),
242
+ Schema.encodeEffect(ServerMessageJson)(bound).pipe(Effect.flatMap(write)),
206
243
  );
207
244
  });
208
245
 
@@ -251,7 +288,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
251
288
  while (yield* isActive(subscriptionId, subscription)) {
252
289
  if (prepared === undefined) {
253
290
  const resolved = yield* runtime
254
- .prepare(subscription.function, "Query", subscription.args)
291
+ .prepare(subscription.function, "Query", subscription.args, identity)
255
292
  .pipe(
256
293
  Effect.catchTags({
257
294
  FunctionUnavailable: (error) => sendResolutionError(operation, error, deliver),
@@ -264,7 +301,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
264
301
  prepared = resolved;
265
302
  }
266
303
 
267
- const result = yield* prepared.execute;
304
+ const result = yield* queryCache.execute(
305
+ subscription.queryKey,
306
+ identity,
307
+ yield* invalidation.latestRevision,
308
+ prepared.execute,
309
+ );
268
310
  prepared = undefined;
269
311
  if (result.type !== "Query") return;
270
312
  if (
@@ -281,6 +323,11 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
281
323
  id: subscriptionId,
282
324
  result: result.result,
283
325
  revision: result.observedRevision,
326
+ identity: {
327
+ sessionEpoch: identity.sessionEpoch,
328
+ viewRevision: identity.viewRevision,
329
+ dependsOnId: result.dependencies.some((dependency) => dependency.type === "Id"),
330
+ },
284
331
  } as const;
285
332
  yield* deliver(granted.length === 0 ? snapshot : { ...snapshot, files: granted });
286
333
  return;
@@ -305,6 +352,19 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
305
352
  };
306
353
 
307
354
  const refreshAll = Effect.fn("SyncServer.refreshAll")(function* () {
355
+ if (refreshIdentity !== undefined) {
356
+ const next = yield* refreshIdentity;
357
+ const changed =
358
+ idViewKey(identity) !== idViewKey(next) || identity.validUntil !== next.validUntil;
359
+ identity = next;
360
+ if (changed)
361
+ yield* send({
362
+ type: "Session",
363
+ event: "Updated",
364
+ session: identity,
365
+ serverTime: yield* Clock.currentTimeMillis,
366
+ });
367
+ }
308
368
  const current = yield* Ref.get(subscriptions);
309
369
  yield* Effect.forEach(HashMap.toEntries(current), ([subscriptionId, subscription]) =>
310
370
  scheduleQuery(subscriptionId, subscription),
@@ -349,7 +409,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
349
409
  return;
350
410
  }
351
411
 
352
- const prepared = yield* runtime.prepare(message.function, "Query", message.args).pipe(
412
+ const prepared = yield* runtime.prepare(message.function, "Query", message.args, identity).pipe(
353
413
  Effect.catchTags({
354
414
  FunctionUnavailable: (error) => sendResolutionError(operation, error),
355
415
  InvalidArguments: (error) => sendResolutionError(operation, error),
@@ -392,14 +452,16 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
392
452
  yield* files.releasePreparation(message.id);
393
453
  return;
394
454
  }
395
- const prepared = yield* runtime.prepare(message.function, "Mutation", message.args).pipe(
396
- Effect.catchTags({
397
- FunctionUnavailable: (error) => sendResolutionError(operation, error),
398
- InvalidArguments: (error) => sendResolutionError(operation, error),
399
- UnknownFunction: (error) => sendResolutionError(operation, error),
400
- WrongFunctionKind: (error) => sendResolutionError(operation, error),
401
- }),
402
- );
455
+ const prepared = yield* runtime
456
+ .prepare(message.function, "Mutation", message.args, identity)
457
+ .pipe(
458
+ Effect.catchTags({
459
+ FunctionUnavailable: (error) => sendResolutionError(operation, error),
460
+ InvalidArguments: (error) => sendResolutionError(operation, error),
461
+ UnknownFunction: (error) => sendResolutionError(operation, error),
462
+ WrongFunctionKind: (error) => sendResolutionError(operation, error),
463
+ }),
464
+ );
403
465
 
404
466
  if (prepared === undefined) {
405
467
  yield* files.releasePreparation(message.id);
@@ -411,6 +473,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
411
473
  "Mutation",
412
474
  message.function,
413
475
  message.args,
476
+ identity.sessionEpoch,
414
477
  );
415
478
  yield* mutationReplay
416
479
  .execute(
@@ -515,7 +578,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
515
578
 
516
579
  yield* socket
517
580
  .runString((text) => messageSemaphore.withPermits(1)(handleMessage(text)), {
518
- onOpen: send({ type: "Handshake", ...localSyncIdentity }).pipe(Effect.orDie),
581
+ onOpen: send({
582
+ type: "Handshake",
583
+ ...localSyncIdentity,
584
+ session: identity,
585
+ serverTime: yield* Clock.currentTimeMillis,
586
+ }).pipe(Effect.orDie),
519
587
  })
520
588
  .pipe(
521
589
  Effect.ensuring(
@@ -547,35 +615,45 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
547
615
  const sqliteLayer = SqliteClient.layer({ filename: databasePath });
548
616
  const persistenceLayer = Layer.merge(LocalDatabase.layer, mutationReplayLayer).pipe(
549
617
  Layer.provideMerge(DevelopmentDatabase.layer),
550
- Layer.provide(Layer.merge(idGeneratorLayer, sqliteLayer)),
618
+ Layer.provide(Layer.merge(IdGenerator.layer, sqliteLayer)),
551
619
  );
552
620
  const executorLayer = FunctionExecutor.layer.pipe(
553
621
  Layer.provide(persistenceLayer),
554
- Layer.provide(idGeneratorLayer),
622
+ Layer.provide(IdGenerator.layer),
623
+ );
624
+ const environmentLayer = DevelopmentEnvironment.layer(server, appDirectory).pipe(
625
+ Layer.provide(NodeServices.layer),
626
+ Layer.orDie,
555
627
  );
556
628
  const devFunctionRuntimeLayer = functionRuntimeLayer.pipe(
557
629
  Layer.provide(
558
630
  Layer.merge(
559
- FunctionRegistry.layer(server, appDirectory).pipe(Layer.provide(NodeServices.layer)),
631
+ FunctionRegistry.layer(server, appDirectory).pipe(
632
+ Layer.provide(Layer.merge(NodeServices.layer, environmentLayer)),
633
+ ),
560
634
  executorLayer,
561
635
  ),
562
636
  ),
563
637
  );
564
638
  const dependencies = Layer.mergeAll(
565
639
  devFunctionRuntimeLayer,
640
+ environmentLayer,
566
641
  queryInvalidationLayer,
567
642
  persistenceLayer,
568
643
  NodeServices.layer,
644
+ LocalId.layer(appDirectory).pipe(Layer.provide(NodeServices.layer)),
569
645
  localApplicationFilesLayer(appDirectory).pipe(
570
646
  Layer.provideMerge(DevelopmentDatabase.layer),
571
- Layer.provide(Layer.mergeAll(idGeneratorLayer, sqliteLayer, NodeServices.layer)),
647
+ Layer.provide(Layer.mergeAll(IdGenerator.layer, sqliteLayer, NodeServices.layer)),
572
648
  ),
573
649
  );
574
650
 
575
651
  return Layer.effect(
576
652
  SyncHandlers,
577
653
  Effect.gen(function* () {
654
+ const localId = yield* LocalId;
578
655
  const invalidation = yield* QueryInvalidation;
656
+ const environment = yield* DevelopmentEnvironment;
579
657
  const fileSystem = yield* FileSystem.FileSystem;
580
658
  const path = yield* Path.Path;
581
659
  const scope = yield* Effect.scope;
@@ -591,7 +669,72 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
591
669
  );
592
670
  const httpApp = Effect.gen(function* () {
593
671
  const request = yield* HttpServerRequest.HttpServerRequest;
594
- const pathname = new URL(request.url, "http://ignotum.local").pathname;
672
+ const url = new URL(request.url, `http://${request.headers.host ?? "ignotum.local"}`);
673
+ const pathname = url.pathname;
674
+ if (pathname === sessionPath && request.method === "GET") {
675
+ const session = yield* localId.session(
676
+ url.searchParams.get("username"),
677
+ url.searchParams.get("epoch") ?? "dev:anonymous",
678
+ );
679
+ return yield* HttpServerResponse.json(
680
+ {
681
+ ...session,
682
+ serverTime: yield* Clock.currentTimeMillis,
683
+ expiresAt: session.validUntil,
684
+ development: true,
685
+ },
686
+ { headers: { "cache-control": "no-store" } },
687
+ );
688
+ }
689
+ if (pathname === sessionRequestsPath && request.method === "POST") {
690
+ if (request.headers.origin !== url.origin || request.headers["x-ignotum-request"] !== "1")
691
+ return HttpServerResponse.empty({ status: 403 });
692
+ const input = yield* request.json.pipe(
693
+ Effect.flatMap(Schema.decodeUnknownEffect(SessionRequest)),
694
+ Effect.option,
695
+ );
696
+ if (Option.isNone(input)) return HttpServerResponse.empty({ status: 400 });
697
+ return yield* HttpServerResponse.json(
698
+ {
699
+ redirectUrl: `${localIdPath}?request=${encodeURIComponent(JSON.stringify(input.value))}`,
700
+ expiresAt: (yield* Clock.currentTimeMillis) + 600_000,
701
+ },
702
+ { status: 201, headers: { "cache-control": "no-store" } },
703
+ );
704
+ }
705
+ if (pathname === `${localIdPath}/profile` && request.method === "GET") {
706
+ if (request.headers["x-ignotum-request"] !== "1")
707
+ return HttpServerResponse.empty({ status: 403 });
708
+ return yield* HttpServerResponse.json(
709
+ (yield* localId.profile(url.searchParams.get("username") ?? "")) ?? null,
710
+ { headers: { "cache-control": "no-store" } },
711
+ );
712
+ }
713
+ if (pathname === localIdPath && request.method === "GET") {
714
+ const input = yield* Schema.decodeEffect(Schema.fromJsonString(SessionRequest))(
715
+ url.searchParams.get("request") ?? "",
716
+ ).pipe(Effect.option);
717
+ return Option.isNone(input)
718
+ ? HttpServerResponse.empty({ status: 400 })
719
+ : HttpServerResponse.html(localIdPage(input.value));
720
+ }
721
+ if (pathname === localIdPath && request.method === "POST") {
722
+ if (request.headers.origin !== url.origin || request.headers["x-ignotum-request"] !== "1")
723
+ return HttpServerResponse.empty({ status: 403 });
724
+ const input = yield* request.json.pipe(
725
+ Effect.flatMap(Schema.decodeUnknownEffect(LocalCompletion)),
726
+ Effect.option,
727
+ );
728
+ if (Option.isNone(input)) return HttpServerResponse.empty({ status: 400 });
729
+ yield* localId.complete(input.value);
730
+ yield* invalidation.publish({ type: "All" });
731
+ return HttpServerResponse.empty({ status: 204 });
732
+ }
733
+ if (pathname === sessionPath && request.method === "DELETE") {
734
+ if (request.headers.origin !== url.origin || request.headers["x-ignotum-request"] !== "1")
735
+ return HttpServerResponse.empty({ status: 403 });
736
+ return HttpServerResponse.empty({ status: 204 });
737
+ }
595
738
  if (pathname.startsWith(fileUploadUrlPrefix)) {
596
739
  if (request.method !== "PUT")
597
740
  return HttpServerResponse.text("Method Not Allowed", { status: 405 });
@@ -643,8 +786,15 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
643
786
  });
644
787
  const socketApp = Effect.gen(function* () {
645
788
  const request = yield* HttpServerRequest.HttpServerRequest;
789
+ const url = new URL(request.url, `http://${request.headers.host ?? "ignotum.local"}`);
790
+ if (request.headers.origin !== url.origin) return HttpServerResponse.empty({ status: 403 });
791
+ const refresh = localId.session(
792
+ url.searchParams.get("username"),
793
+ url.searchParams.get("epoch") ?? "dev:anonymous",
794
+ );
795
+ const identity = yield* refresh;
646
796
  const socket = yield* request.upgrade;
647
- yield* runSession(socket);
797
+ yield* runSession(socket, identity, refresh);
648
798
  return HttpServerResponse.empty();
649
799
  });
650
800
  const http = yield* NodeHttpServer.makeHandler(httpApp, { scope });
@@ -654,21 +804,32 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
654
804
  { scope },
655
805
  );
656
806
  const serverDirectory = path.join(appDirectory, "server");
807
+ const environmentPath = path.join(appDirectory, ".env.ignotum");
657
808
  const reloadUnsafe = Effect.fn("SyncServer.reload")(function* (
658
809
  file: string,
659
810
  event: "add" | "change" | "unlink",
660
811
  ) {
812
+ const isEnvironmentValues = path.resolve(file) === path.resolve(environmentPath);
661
813
  const relative = path.relative(serverDirectory, file);
662
- if (relative.startsWith("..") || path.isAbsolute(relative) || !file.endsWith(".ts")) {
814
+ const isServerTypeScript =
815
+ !relative.startsWith("..") && !path.isAbsolute(relative) && file.endsWith(".ts");
816
+ if (!isEnvironmentValues && !isServerTypeScript) {
663
817
  return;
664
818
  }
665
819
 
666
- if (event !== "change") {
820
+ if (isServerTypeScript && event !== "change") {
667
821
  yield* generate(appDirectory).pipe(
668
822
  Effect.provideService(FileSystem.FileSystem, fileSystem),
669
823
  Effect.provideService(Path.Path, path),
670
824
  );
671
825
  }
826
+ if (isEnvironmentValues || relative === "env.ts") {
827
+ yield* reloadEnvironmentAndInvalidate(
828
+ environment.reload,
829
+ invalidation.publish({ type: "All" }),
830
+ );
831
+ return;
832
+ }
672
833
  yield* invalidation.publish({ type: "All" });
673
834
  });
674
835
  const reload = (file: string, event: "add" | "change" | "unlink") =>
@@ -707,7 +868,7 @@ const rejectUpgrade = (socket: import("node:stream").Duplex): void => {
707
868
  );
708
869
  };
709
870
 
710
- export const syncPluginInternals = { isAppOrigin };
871
+ export const syncPluginInternals = { isAppOrigin, reloadEnvironmentAndInvalidate };
711
872
 
712
873
  export const ignotumSyncPlugin: {
713
874
  (databasePath: string): (appDirectory: string) => Plugin;
@@ -1,15 +1,27 @@
1
1
  import { Result as contractResult } from "@ignotum/contracts/runtime/result";
2
2
  import type { ErrorValue, Result as ContractResult } from "@ignotum/contracts/runtime/result";
3
- import { defineSchema as defineContractSchema } from "@ignotum/contracts/schema";
3
+ import {
4
+ defineEnv as defineContractEnv,
5
+ defineSchema as defineContractSchema,
6
+ Secret as contractSecret,
7
+ } from "@ignotum/contracts/schema";
8
+ import type { Secret as ContractSecret } from "@ignotum/contracts/schema";
4
9
 
5
10
  export const Result: typeof contractResult = contractResult;
6
11
  export type Result<Success, Failure extends ErrorValue> = ContractResult<Success, Failure>;
12
+ export const defineEnv: typeof defineContractEnv = defineContractEnv;
7
13
  export const defineSchema: typeof defineContractSchema = defineContractSchema;
14
+ export const Secret: typeof contractSecret = contractSecret;
15
+ export type Secret<Value> = ContractSecret<Value>;
8
16
  export type {
17
+ DefinedEnv,
9
18
  DefinedSchema,
19
+ EnvironmentAuthoring,
10
20
  FileFormat,
11
21
  FileMetadata,
12
22
  FileMimeType,
13
23
  FileValue,
14
24
  SchemaAuthoring,
15
25
  } from "@ignotum/contracts/schema";
26
+
27
+ export type { User, UserId } from "@ignotum/contracts/id";
@@ -1 +0,0 @@
1
- {"version":3,"file":"api-DzcR7spt.js","names":[],"sources":["../../../contracts/dist/runtime/identity.js","../../../contracts/dist/runtime/sync.js","../../src/internal/api.ts"],"sourcesContent":["import { AppId, AuthAccountId, AuthDeviceCodeId, AuthInternalId, AuthInvitationId, AuthMemberId, AuthSessionId, AuthVerificationId, ConnectionId, DeploymentId, DevDatabaseLockId, InvocationId, PlatformPrincipalId, RequestId, RuntimeRequestNonce, SubscriptionId, TableId, TeamId } from \"./id.js\";\nimport { Schema } from \"effect\";\n//#region src/runtime/identity.ts\nconst InvocationKey = Schema.String.pipe(Schema.brand(\"ignotum/hosted/InvocationKey\"));\nconst DeploymentGeneration = Schema.Natural.pipe(Schema.brand(\"ignotum/hosted/DeploymentGeneration\"));\nconst AppStateRevision = Schema.Natural.pipe(Schema.brand(\"ignotum/hosted/AppStateRevision\"));\n//#endregion\nexport { AppId, AppStateRevision, AuthAccountId, AuthDeviceCodeId, AuthInternalId, AuthInvitationId, AuthMemberId, AuthSessionId, AuthVerificationId, ConnectionId, DeploymentGeneration, DeploymentId, DevDatabaseLockId, InvocationId, InvocationKey, PlatformPrincipalId, RequestId, RuntimeRequestNonce, SubscriptionId, TableId, TeamId };\n\n//# sourceMappingURL=identity.js.map","import { AppId, DeploymentId, InvocationId, SubscriptionId } from \"./id.js\";\nimport { AppStateRevision, DeploymentGeneration } from \"./identity.js\";\nimport { FileId } from \"../schema/file.js\";\nimport { TransportValueSchema, datePathsOf, datePathsOfObject, decodeTransportObject, decodeTransportValue, encodeTransportObject, encodeTransportValue } from \"./value.js\";\nimport { Schema } from \"effect\";\n//#region src/runtime/sync.ts\nconst FunctionNamePart = Schema.String.check(Schema.isPattern(/^[A-Za-z_$][A-Za-z0-9_$]*$/));\nconst ApiFunctionAddressParts = Schema.TemplateLiteralParser([\n\t\"api.\",\n\tFunctionNamePart,\n\t\".\",\n\tFunctionNamePart\n]);\nconst FunctionAddress = Schema.TemplateLiteral([\n\t\"api.\",\n\tFunctionNamePart,\n\t\".\",\n\tFunctionNamePart\n]);\nconst apiFunctionParts = (address) => {\n\tconst [, moduleName, , functionName] = Schema.decodeSync(ApiFunctionAddressParts)(address);\n\treturn {\n\t\tfunctionName,\n\t\tmoduleName\n\t};\n};\nconst Subscribe = Schema.Struct({\n\ttype: Schema.Literal(\"Subscribe\"),\n\tid: SubscriptionId,\n\tfunction: FunctionAddress,\n\targs: Schema.Json\n});\nconst Unsubscribe = Schema.Struct({\n\ttype: Schema.Literal(\"Unsubscribe\"),\n\tid: SubscriptionId\n});\nconst Invoke = Schema.Struct({\n\ttype: Schema.Literal(\"Invoke\"),\n\tid: InvocationId,\n\tkind: Schema.Literal(\"Mutation\"),\n\tfunction: FunctionAddress,\n\targs: Schema.Json\n});\nconst Prepare = Schema.Struct({\n\ttype: Schema.Literal(\"Prepare\"),\n\tid: InvocationId,\n\tkind: Schema.Literal(\"Mutation\"),\n\tfunction: FunctionAddress,\n\targs: Schema.Json,\n\tfiles: Schema.Array(FileId)\n});\nconst ClientMessage = Schema.Union([\n\tSubscribe,\n\tUnsubscribe,\n\tPrepare,\n\tInvoke\n]);\nconst SubscriptionOperation = Schema.Struct({\n\ttype: Schema.Literal(\"Subscription\"),\n\tid: SubscriptionId\n});\nconst InvocationOperation = Schema.Struct({\n\ttype: Schema.Literal(\"Invocation\"),\n\tid: InvocationId\n});\nconst Operation = Schema.Union([SubscriptionOperation, InvocationOperation]);\nconst DatePath = Schema.Array(Schema.Union([Schema.String, Schema.Natural]));\nconst DatePaths = Schema.Array(DatePath);\nconst WireSuccess = Schema.Struct({\n\ttype: Schema.Literal(\"Success\"),\n\tvalue: Schema.optional(Schema.Json),\n\tdates: Schema.optional(DatePaths)\n});\nconst WireFailure = Schema.Struct({\n\ttype: Schema.Literal(\"Failure\"),\n\terror: Schema.Json,\n\tdates: Schema.optional(DatePaths)\n});\nconst WireResult = Schema.Union([WireSuccess, WireFailure]);\nconst SyncHandshake = Schema.Struct({\n\ttype: Schema.Literal(\"Handshake\"),\n\tappId: AppId,\n\tdeploymentId: DeploymentId,\n\tgeneration: DeploymentGeneration\n});\nconst Snapshot = Schema.Struct({\n\ttype: Schema.Literal(\"Snapshot\"),\n\tid: SubscriptionId,\n\tresult: WireResult,\n\trevision: AppStateRevision,\n\tfiles: Schema.optional(Schema.Array(Schema.Struct({\n\t\tid: FileId,\n\t\turl: Schema.String\n\t})))\n});\nconst Preparation = Schema.Struct({\n\ttype: Schema.Literal(\"Preparation\"),\n\tid: InvocationId,\n\tkind: Schema.Literal(\"Mutation\"),\n\tuploads: Schema.Array(Schema.Struct({\n\t\tid: FileId,\n\t\turl: Schema.optional(Schema.String)\n\t}))\n});\nconst SyncResultSuccess = Schema.Struct({\n\ttype: Schema.Literal(\"Result\"),\n\tid: InvocationId,\n\tresult: WireSuccess,\n\tcommittedRevision: AppStateRevision\n});\nconst SyncResultFailure = Schema.Struct({\n\ttype: Schema.Literal(\"Result\"),\n\tid: InvocationId,\n\tresult: WireFailure\n});\nconst ErrorCode = Schema.Literals([\n\t\"DuplicateOperationId\",\n\t\"FunctionUnavailable\",\n\t\"InvalidArguments\",\n\t\"InvalidMessage\",\n\t\"InvocationIdConflict\",\n\t\"ResourceLimitExceeded\",\n\t\"UnknownFunction\",\n\t\"WrongFunctionKind\"\n]);\nconst SyncError = Schema.Struct({\n\ttype: Schema.Literal(\"Error\"),\n\toperation: Schema.optional(Operation),\n\tcode: ErrorCode,\n\tmessage: Schema.String\n});\nconst Deployment = Schema.Struct({\n\ttype: Schema.Literal(\"Deployment\"),\n\tdeploymentId: DeploymentId,\n\tgeneration: DeploymentGeneration\n});\nconst DeploymentCloseCode = 4409;\nconst ServerMessage = Schema.Union([\n\tSyncHandshake,\n\tSnapshot,\n\tPreparation,\n\tSyncResultSuccess,\n\tSyncResultFailure,\n\tSyncError,\n\tDeployment\n]);\nconst ClientMessageJson = Schema.fromJsonString(ClientMessage);\nconst ServerMessageJson = Schema.fromJsonString(ServerMessage);\n//#endregion\nexport { ClientMessage, ClientMessageJson, DatePath, Deployment, DeploymentCloseCode, ErrorCode, FunctionAddress, FunctionNamePart, InvocationId, Operation, ServerMessage, ServerMessageJson, SubscriptionId, SyncHandshake, TransportValueSchema, WireFailure, WireResult, WireSuccess, apiFunctionParts, datePathsOf, datePathsOfObject, decodeTransportObject, decodeTransportValue, encodeTransportObject, encodeTransportValue };\n\n//# sourceMappingURL=sync.js.map","import { Predicate } from \"effect\";\nimport type { Effect } from \"effect\";\n\nimport type { ErrorValue, InternalServerError } from \"@ignotum/contracts/runtime/result\";\nimport { FunctionAddress } from \"@ignotum/contracts/runtime/sync\";\nimport type { FileValue } from \"@ignotum/contracts/schema/file\";\n\nconst FunctionReferenceTypeId: unique symbol = Symbol.for(\"ignotum/internal/api/FunctionReference\");\ndeclare const FunctionReferenceTypesTypeId: unique symbol;\n\ntype FunctionKind = \"Mutation\" | \"Query\";\n\nexport type MutationInput<Value> = Value extends FileValue\n ? Value | File\n : Value extends Date\n ? Value\n : Value extends ReadonlyArray<infer Item>\n ? ReadonlyArray<MutationInput<Item>>\n : Value extends object\n ? { readonly [Key in keyof Value]: MutationInput<Value[Key]> }\n : Value;\n\nexport interface FunctionReference<\n Kind extends FunctionKind,\n Args,\n Success,\n Failure extends ErrorValue,\n> {\n readonly [FunctionReferenceTypeId]: FunctionAddress;\n readonly [FunctionReferenceTypesTypeId]?: {\n readonly kind: Kind;\n readonly args: Args;\n readonly value: Success;\n readonly error: Failure;\n };\n}\n\ntype ReferenceTypes<Reference> = Reference extends {\n readonly [FunctionReferenceTypesTypeId]?: infer Types;\n}\n ? Exclude<Types, undefined>\n : never;\n\nexport declare namespace FunctionReference {\n type Args<Reference> =\n ReferenceTypes<Reference> extends { readonly args: infer Args } ? Args : never;\n type Failure<Reference> =\n ReferenceTypes<Reference> extends {\n readonly error: infer Failure;\n }\n ? Failure\n : never;\n type Kind<Reference> =\n ReferenceTypes<Reference> extends { readonly kind: infer Kind } ? Kind : never;\n type Success<Reference> =\n ReferenceTypes<Reference> extends {\n readonly value: infer Success;\n }\n ? Success\n : never;\n}\n\ntype ReferenceOf<Definition> = Definition extends {\n readonly _tag: infer Kind extends FunctionKind;\n readonly handler: (\n ...arguments_: infer HandlerArguments\n ) => Generator<infer Yielded, infer Success, never>;\n}\n ? HandlerArguments extends readonly [infer _Context, ...infer Rest]\n ? FunctionReference<\n Kind,\n Rest extends readonly [infer Args, ...ReadonlyArray<unknown>]\n ? Kind extends \"Mutation\"\n ? MutationInput<Args>\n : Args\n : void,\n Success,\n | (Yielded extends Effect.Effect<unknown, infer Failure extends ErrorValue, never>\n ? Failure\n : never)\n | InternalServerError\n >\n : never\n : never;\n\ntype ApiModule<Module> = {\n readonly [FunctionName in keyof Module as FunctionName extends string\n ? ReferenceOf<Module[FunctionName]> extends never\n ? never\n : FunctionName\n : never]: ReferenceOf<Module[FunctionName]>;\n};\n\nexport type Api<Modules> = {\n readonly [ModuleName in keyof Modules]: ApiModule<Modules[ModuleName]>;\n};\n\nexport const functionPathOf = <\n Kind extends FunctionKind,\n Args,\n Success,\n Failure extends ErrorValue,\n>(\n reference: FunctionReference<Kind, Args, Success, Failure>,\n) => reference[FunctionReferenceTypeId];\n\nconst makeModuleReference = (moduleName: string) => {\n const references = new Map<string, object>();\n\n return new Proxy(\n {},\n {\n get: (_target, functionName) => {\n if (!Predicate.isString(functionName)) {\n return undefined;\n }\n\n const existing = references.get(functionName);\n if (existing !== undefined) {\n return existing;\n }\n\n const reference = {\n [FunctionReferenceTypeId]: FunctionAddress.make(`api.${moduleName}.${functionName}`),\n };\n references.set(functionName, reference);\n return reference;\n },\n },\n );\n};\n\nexport function createApi<Modules>(): Api<Modules>;\nexport function createApi() {\n const modules = new Map<string, object>();\n\n return new Proxy(\n {},\n {\n get: (_target, moduleName) => {\n if (!Predicate.isString(moduleName)) {\n return undefined;\n }\n\n const existing = modules.get(moduleName);\n if (existing !== undefined) {\n return existing;\n }\n\n const moduleReference = makeModuleReference(moduleName);\n modules.set(moduleName, moduleReference);\n return moduleReference;\n },\n },\n );\n}\n"],"mappings":"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"}