ignotum 0.0.12 → 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 (52) hide show
  1. package/dist/cli/bin.mjs +825 -403
  2. package/dist/cli/bin.mjs.map +1 -1
  3. package/dist/runtime/{api-DzcR7spt.js → api-CAgKDij7.js} +38 -2
  4. package/dist/runtime/api-CAgKDij7.js.map +1 -0
  5. package/dist/runtime/{api-5XSrIeqW.d.ts → api-Cv3hMbzo.d.ts} +4 -4
  6. package/dist/runtime/client.d.ts +39 -9
  7. package/dist/runtime/client.js +339 -60
  8. package/dist/runtime/client.js.map +1 -1
  9. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +154 -0
  10. package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +1 -0
  11. package/dist/runtime/id-Bt9XWRGL.js +423 -0
  12. package/dist/runtime/id-Bt9XWRGL.js.map +1 -0
  13. package/dist/runtime/{id-Cs82tq9Q-CK-maMgN.d.ts → id-BzFHf3Wo-DGPCjgrf.d.ts} +2 -2
  14. package/dist/runtime/id-Dz0apuB3.d.ts +1 -0
  15. package/dist/runtime/{index-CrWg4Z0y.d.ts → index-D54flWtH.d.ts} +9 -5
  16. package/dist/runtime/internal/api.d.ts +1 -1
  17. package/dist/runtime/internal/api.js +1 -1
  18. package/dist/runtime/internal/host.d.ts +9 -6
  19. package/dist/runtime/internal/host.js +15 -6
  20. package/dist/runtime/internal/host.js.map +1 -1
  21. package/dist/runtime/internal/server.d.ts +1 -1
  22. package/dist/runtime/internal/server.js +1 -1
  23. package/dist/runtime/internal/types.d.ts +1 -1
  24. package/dist/runtime/internal/types.js +1 -1
  25. package/dist/runtime/{pagination-D-R9NR61-CpIoHFoI.d.ts → pagination-DnKg3dkI-r5ZUxBBx.d.ts} +31 -6
  26. package/dist/runtime/pagination-Dz0apuB3.d.ts +1 -0
  27. package/dist/runtime/result-DKAA4gpS.d.ts +1 -0
  28. package/dist/runtime/{schema-B9XxyRO8.js → schema-1Zs03-iS.js} +5 -3
  29. package/dist/runtime/schema-1Zs03-iS.js.map +1 -0
  30. package/dist/runtime/server.d.ts +6 -4
  31. package/dist/runtime/server.js +2 -2
  32. package/dist/runtime/server.js.map +1 -1
  33. package/dist/runtime/{sync-avN7NkcU.d.ts → sync-Bs8J3fIr.d.ts} +2 -2
  34. package/package.json +1 -1
  35. package/src/cli/agent-files.ts +6 -0
  36. package/src/client/hooks.ts +68 -0
  37. package/src/client/id.ts +259 -0
  38. package/src/client/index.ts +5 -2
  39. package/src/client/sync.ts +143 -10
  40. package/src/dev-runtime/functions.ts +111 -88
  41. package/src/dev-runtime/id.ts +175 -0
  42. package/src/dev-runtime/query-cache.ts +105 -0
  43. package/src/dev-runtime/sync.ts +149 -17
  44. package/src/server/index.ts +2 -0
  45. package/dist/runtime/api-DzcR7spt.js.map +0 -1
  46. package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js +0 -330
  47. package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js.map +0 -1
  48. package/dist/runtime/file-C1abuMgd.js +0 -173
  49. package/dist/runtime/file-C1abuMgd.js.map +0 -1
  50. package/dist/runtime/pagination-CX5IPbEi.d.ts +0 -1
  51. package/dist/runtime/result-DIjKM-p4.d.ts +0 -1
  52. package/dist/runtime/schema-B9XxyRO8.js.map +0 -1
@@ -0,0 +1,175 @@
1
+ import { Context, Effect, FileSystem, Layer, Path, Schema, Semaphore } from "effect";
2
+ import {
3
+ DevelopmentUsername,
4
+ developmentSelectionKey,
5
+ ProfileFields,
6
+ SessionRequest,
7
+ User,
8
+ UserId,
9
+ type SessionState,
10
+ } from "@ignotum/contracts/id";
11
+ import * as Versioned from "@ignotum/contracts/versioned";
12
+
13
+ const Profile = Schema.Struct({ user: User, share: ProfileFields, revision: Schema.Natural });
14
+ const ProfilesV1 = Schema.Struct({
15
+ formatVersion: Schema.Literal(1),
16
+ profiles: Schema.Record(Schema.String, Profile),
17
+ });
18
+ const Profiles = Versioned.initial(ProfilesV1);
19
+ const ProfilesJson = Schema.fromJsonString(Profiles);
20
+
21
+ export const localIdPath = "/_ignotum/v1/id";
22
+ export const LocalCompletion = Schema.Struct({
23
+ request: SessionRequest,
24
+ username: DevelopmentUsername,
25
+ name: Schema.String,
26
+ email: Schema.String,
27
+ image: Schema.String,
28
+ share: ProfileFields,
29
+ manage: Schema.optional(Schema.Boolean),
30
+ });
31
+
32
+ const projection = (user: User, share: ReadonlyArray<string>): User => ({
33
+ id: user.id,
34
+ name: share.includes("name") ? user.name : undefined,
35
+ email: share.includes("email") ? user.email : undefined,
36
+ image: share.includes("image") ? user.image : undefined,
37
+ });
38
+
39
+ export class LocalId extends Context.Service<
40
+ LocalId,
41
+ {
42
+ readonly profile: (username: string) => Effect.Effect<typeof Profile.Type | undefined>;
43
+ readonly session: (username: string | null, epoch: string) => Effect.Effect<SessionState>;
44
+ readonly complete: (input: typeof LocalCompletion.Type) => Effect.Effect<void>;
45
+ }
46
+ >()("ignotum/dev-runtime/id/LocalId") {
47
+ static readonly layer = (directory: string) =>
48
+ Layer.effect(
49
+ LocalId,
50
+ Effect.gen(function* () {
51
+ const fs = yield* FileSystem.FileSystem;
52
+ const path = yield* Path.Path;
53
+ const file = path.join(directory, ".ignotum", "id.json");
54
+ const lock = yield* Semaphore.make(1);
55
+ const load = Effect.fn("LocalId.load")(function* () {
56
+ if (!(yield* fs.exists(file))) return Profiles.make({ formatVersion: 1, profiles: {} });
57
+ return yield* fs
58
+ .readFileString(file)
59
+ .pipe(Effect.flatMap(Schema.decodeEffect(ProfilesJson)));
60
+ });
61
+ return LocalId.of({
62
+ profile: Effect.fn("LocalId.profile")(function* (username) {
63
+ return (yield* load().pipe(Effect.orDie)).profiles[username];
64
+ }),
65
+ session: Effect.fn("LocalId.session")(function* (username, epoch) {
66
+ if (username === null)
67
+ return {
68
+ sessionEpoch: epoch,
69
+ viewRevision: 0,
70
+ user: null,
71
+ validUntil: Number.MAX_SAFE_INTEGER,
72
+ };
73
+ const id = yield* Schema.decodeEffect(UserId)(username).pipe(Effect.orDie);
74
+ const profile = (yield* load().pipe(Effect.orDie)).profiles[id];
75
+ const user: User = projection(profile?.user ?? { id }, profile?.share ?? []);
76
+ return {
77
+ sessionEpoch: epoch,
78
+ viewRevision: profile?.revision ?? 0,
79
+ user,
80
+ validUntil: Number.MAX_SAFE_INTEGER,
81
+ };
82
+ }),
83
+ complete: Effect.fn("LocalId.complete")(function* (input) {
84
+ yield* lock
85
+ .withPermits(1)(
86
+ Effect.gen(function* () {
87
+ const stored = yield* load();
88
+ const previous = stored.profiles[input.username];
89
+ const user = User.make({
90
+ id: UserId.make(input.username),
91
+ name: input.name === "" ? undefined : input.name,
92
+ email: input.email === "" ? undefined : input.email,
93
+ image: input.image === "" ? undefined : input.image,
94
+ });
95
+ const share =
96
+ input.manage === true
97
+ ? input.share
98
+ : [
99
+ ...new Set([
100
+ ...(previous?.share ?? []),
101
+ ...input.share.filter((field) => input.request.profile.includes(field)),
102
+ ]),
103
+ ];
104
+ const updated = Profiles.make({
105
+ formatVersion: 1,
106
+ profiles: {
107
+ ...stored.profiles,
108
+ [input.username]: {
109
+ user,
110
+ share,
111
+ revision:
112
+ (previous?.revision ?? 0) +
113
+ Number(
114
+ JSON.stringify(
115
+ projection(previous?.user ?? { id: user.id }, previous?.share ?? []),
116
+ ) !== JSON.stringify(projection(user, share)),
117
+ ),
118
+ },
119
+ },
120
+ });
121
+ yield* fs.makeDirectory(path.dirname(file), { recursive: true });
122
+ yield* Effect.scoped(
123
+ Effect.gen(function* () {
124
+ const temporary = yield* fs.makeTempFileScoped({
125
+ directory: path.dirname(file),
126
+ prefix: ".id-",
127
+ });
128
+ yield* fs.writeFileString(
129
+ temporary,
130
+ yield* Schema.encodeEffect(ProfilesJson)(updated),
131
+ );
132
+ yield* fs.rename(temporary, file);
133
+ }),
134
+ );
135
+ }),
136
+ )
137
+ .pipe(Effect.orDie);
138
+ }),
139
+ });
140
+ }),
141
+ );
142
+ }
143
+
144
+ const escapeHtml = (text: string) =>
145
+ text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll('"', "&quot;");
146
+
147
+ export const localIdPage = (request: typeof SessionRequest.Type): string => {
148
+ const encoded = JSON.stringify(request).replaceAll("<", "\\u003c");
149
+ return `<!doctype html><html><head><meta charset="utf-8"><title>Ignotum ID · Development</title>
150
+ <meta name="viewport" content="width=device-width,initial-scale=1"><style>
151
+ body{font:16px system-ui;background:#f7f7f5;color:#20211f;max-width:420px;margin:8vh auto;padding:24px}
152
+ label{display:block;margin:16px 0}input:not([type=checkbox]){box-sizing:border-box;width:100%;padding:10px;border:1px solid #bbb;border-radius:6px}
153
+ button{padding:12px 18px;border:0;border-radius:6px;background:#222;color:white;cursor:pointer;margin-right:8px}
154
+ p{line-height:1.5}small{color:#666}</style></head><body><h1>Ignotum ID</h1><p>Choose a local username to try your app.</p>
155
+ <form id="form"><label>Username<input name="username" required pattern="[a-z][a-z0-9_-]{0,63}" placeholder="john"></label>
156
+ <label>Name<input name="name"></label><label>Email<input name="email" type="email"></label><label>Image URL<input name="image" type="url"></label>
157
+ ${["name", "email", "image"].map((field) => `<label><input type="checkbox" name="share" value="${escapeHtml(field)}"> Share ${escapeHtml(field)}</label>`).join("")}
158
+ <label><input type="checkbox" name="manage"> Replace existing sharing choices</label>
159
+ <p><small>Profile sharing is optional. No password or account is needed in development.</small></p>
160
+ <button>Continue</button><button type="button" id="cancel">Cancel</button><p id="error"></p></form>
161
+ <script type="module">
162
+ const request=${encoded};const form=document.querySelector('#form');
163
+ const previous=JSON.parse(sessionStorage.getItem('${developmentSelectionKey}')||'null');
164
+ if(previous?.username)form.elements.username.value=previous.username;
165
+ if(request.intent==='requestProfile')form.elements.username.readOnly=true;
166
+ let loading=false;const loadProfile=async()=>{loading=true;try{const response=await fetch('${localIdPath}/profile?username='+encodeURIComponent(form.elements.username.value),{headers:{'x-ignotum-request':'1'}});if(!response.ok)throw new Error('Could not load the local profile.');const profile=await response.json();for(const field of ['name','email','image']){form.elements[field].value=profile?.user[field]??'';form.querySelector('input[name=share][value='+field+']').checked=profile?.share.includes(field)??false;}}finally{loading=false;}};form.elements.username.onchange=loadProfile;if(previous?.username)await loadProfile();
167
+ document.querySelector('#cancel').onclick=()=>{sessionStorage.setItem('ignotum.id.outcome','Cancelled');location.replace(request.returnTo)};
168
+ form.onsubmit=async(e)=>{e.preventDefault();if(loading)return;const data=new FormData(form);
169
+ try{const response=await fetch('${localIdPath}',{method:'POST',headers:{'content-type':'application/json','x-ignotum-request':'1'},body:JSON.stringify({request,username:data.get('username'),name:data.get('name'),email:data.get('email'),image:data.get('image'),share:data.getAll('share'),manage:data.get('manage')==='on'})});
170
+ if(!response.ok)throw new Error('Could not update your local ID.');
171
+ sessionStorage.setItem('${developmentSelectionKey}',JSON.stringify({formatVersion:1,username:data.get('username'),epoch:request.intent==='requestProfile'?previous.epoch:'dev:'+crypto.randomUUID()}));
172
+ sessionStorage.setItem('ignotum.id.outcome',request.intent==='requestProfile'?'ProfileUpdated':'SignedIn');location.replace(request.returnTo);
173
+ }catch(error){document.querySelector('#error').textContent=error.message}}
174
+ </script></body></html>`;
175
+ };
@@ -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,
@@ -95,12 +105,14 @@ const reloadEnvironmentAndInvalidate = <Error, Requirements>(
95
105
  export const queryInvalidationLayer = Layer.effect(
96
106
  QueryInvalidation,
97
107
  Effect.gen(function* () {
108
+ const cache = yield* LocalQueryCache;
98
109
  const pubsub = yield* PubSub.unbounded<QueryInvalidationEvent>();
99
110
  const latestRevision = yield* Ref.make(AppStateRevision.make(0));
100
111
  return QueryInvalidation.of({
101
112
  latestRevision: Ref.get(latestRevision),
102
113
  publish: (event) =>
103
114
  Effect.gen(function* () {
115
+ cache.invalidate(event);
104
116
  if (event.type === "Dependencies") {
105
117
  yield* Ref.update(latestRevision, (current) =>
106
118
  AppStateRevision.make(Math.max(current, event.committedRevision)),
@@ -111,7 +123,7 @@ export const queryInvalidationLayer = Layer.effect(
111
123
  subscribe: PubSub.subscribe(pubsub),
112
124
  });
113
125
  }),
114
- );
126
+ ).pipe(Layer.provideMerge(LocalQueryCache.layer));
115
127
 
116
128
  class InvocationIdConflict extends Schema.TaggedError<InvocationIdConflict>()(
117
129
  "InvocationIdConflict",
@@ -198,10 +210,21 @@ const syncError = (code: ErrorCode, message: string, operation?: Operation): Ser
198
210
  return { type: "Error", code, message, operation };
199
211
  };
200
212
 
201
- 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
+ };
202
224
  const runtime = yield* FunctionRuntime;
203
225
  const invalidation = yield* QueryInvalidation;
204
226
  const mutationReplay = yield* MutationReplay;
227
+ const queryCache = yield* LocalQueryCache;
205
228
  const files = yield* LocalApplicationFiles;
206
229
  const subscriptions = yield* Ref.make(HashMap.empty<SubscriptionId, QuerySubscription>());
207
230
  const dependencyIndex = makeDependencyIndex<SubscriptionId>();
@@ -211,8 +234,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
211
234
  const write = yield* socket.writer;
212
235
 
213
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;
214
241
  yield* writeSemaphore.withPermits(1)(
215
- Schema.encodeEffect(ServerMessageJson)(message).pipe(Effect.flatMap(write)),
242
+ Schema.encodeEffect(ServerMessageJson)(bound).pipe(Effect.flatMap(write)),
216
243
  );
217
244
  });
218
245
 
@@ -261,7 +288,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
261
288
  while (yield* isActive(subscriptionId, subscription)) {
262
289
  if (prepared === undefined) {
263
290
  const resolved = yield* runtime
264
- .prepare(subscription.function, "Query", subscription.args)
291
+ .prepare(subscription.function, "Query", subscription.args, identity)
265
292
  .pipe(
266
293
  Effect.catchTags({
267
294
  FunctionUnavailable: (error) => sendResolutionError(operation, error, deliver),
@@ -274,7 +301,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
274
301
  prepared = resolved;
275
302
  }
276
303
 
277
- const result = yield* prepared.execute;
304
+ const result = yield* queryCache.execute(
305
+ subscription.queryKey,
306
+ identity,
307
+ yield* invalidation.latestRevision,
308
+ prepared.execute,
309
+ );
278
310
  prepared = undefined;
279
311
  if (result.type !== "Query") return;
280
312
  if (
@@ -291,6 +323,11 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
291
323
  id: subscriptionId,
292
324
  result: result.result,
293
325
  revision: result.observedRevision,
326
+ identity: {
327
+ sessionEpoch: identity.sessionEpoch,
328
+ viewRevision: identity.viewRevision,
329
+ dependsOnId: result.dependencies.some((dependency) => dependency.type === "Id"),
330
+ },
294
331
  } as const;
295
332
  yield* deliver(granted.length === 0 ? snapshot : { ...snapshot, files: granted });
296
333
  return;
@@ -315,6 +352,19 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
315
352
  };
316
353
 
317
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
+ }
318
368
  const current = yield* Ref.get(subscriptions);
319
369
  yield* Effect.forEach(HashMap.toEntries(current), ([subscriptionId, subscription]) =>
320
370
  scheduleQuery(subscriptionId, subscription),
@@ -359,7 +409,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
359
409
  return;
360
410
  }
361
411
 
362
- const prepared = yield* runtime.prepare(message.function, "Query", message.args).pipe(
412
+ const prepared = yield* runtime.prepare(message.function, "Query", message.args, identity).pipe(
363
413
  Effect.catchTags({
364
414
  FunctionUnavailable: (error) => sendResolutionError(operation, error),
365
415
  InvalidArguments: (error) => sendResolutionError(operation, error),
@@ -402,14 +452,16 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
402
452
  yield* files.releasePreparation(message.id);
403
453
  return;
404
454
  }
405
- const prepared = yield* runtime.prepare(message.function, "Mutation", message.args).pipe(
406
- Effect.catchTags({
407
- FunctionUnavailable: (error) => sendResolutionError(operation, error),
408
- InvalidArguments: (error) => sendResolutionError(operation, error),
409
- UnknownFunction: (error) => sendResolutionError(operation, error),
410
- WrongFunctionKind: (error) => sendResolutionError(operation, error),
411
- }),
412
- );
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
+ );
413
465
 
414
466
  if (prepared === undefined) {
415
467
  yield* files.releasePreparation(message.id);
@@ -421,6 +473,7 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
421
473
  "Mutation",
422
474
  message.function,
423
475
  message.args,
476
+ identity.sessionEpoch,
424
477
  );
425
478
  yield* mutationReplay
426
479
  .execute(
@@ -525,7 +578,12 @@ export const runSession = Effect.fn("SyncServer.runSession")(function* (socket:
525
578
 
526
579
  yield* socket
527
580
  .runString((text) => messageSemaphore.withPermits(1)(handleMessage(text)), {
528
- 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),
529
587
  })
530
588
  .pipe(
531
589
  Effect.ensuring(
@@ -583,6 +641,7 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
583
641
  queryInvalidationLayer,
584
642
  persistenceLayer,
585
643
  NodeServices.layer,
644
+ LocalId.layer(appDirectory).pipe(Layer.provide(NodeServices.layer)),
586
645
  localApplicationFilesLayer(appDirectory).pipe(
587
646
  Layer.provideMerge(DevelopmentDatabase.layer),
588
647
  Layer.provide(Layer.mergeAll(IdGenerator.layer, sqliteLayer, NodeServices.layer)),
@@ -592,6 +651,7 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
592
651
  return Layer.effect(
593
652
  SyncHandlers,
594
653
  Effect.gen(function* () {
654
+ const localId = yield* LocalId;
595
655
  const invalidation = yield* QueryInvalidation;
596
656
  const environment = yield* DevelopmentEnvironment;
597
657
  const fileSystem = yield* FileSystem.FileSystem;
@@ -609,7 +669,72 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
609
669
  );
610
670
  const httpApp = Effect.gen(function* () {
611
671
  const request = yield* HttpServerRequest.HttpServerRequest;
612
- 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
+ }
613
738
  if (pathname.startsWith(fileUploadUrlPrefix)) {
614
739
  if (request.method !== "PUT")
615
740
  return HttpServerResponse.text("Method Not Allowed", { status: 405 });
@@ -661,8 +786,15 @@ const makeHandlersLayer = (server: ViteDevServer, appDirectory: string, database
661
786
  });
662
787
  const socketApp = Effect.gen(function* () {
663
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;
664
796
  const socket = yield* request.upgrade;
665
- yield* runSession(socket);
797
+ yield* runSession(socket, identity, refresh);
666
798
  return HttpServerResponse.empty();
667
799
  });
668
800
  const http = yield* NodeHttpServer.makeHandler(httpApp, { scope });
@@ -23,3 +23,5 @@ export type {
23
23
  FileValue,
24
24
  SchemaAuthoring,
25
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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"}