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
@@ -1,9 +1,10 @@
1
- import { C as RuntimeRequestNonce, T as TableId, _ as AppId, a as RuntimeFileMetadata, b as IdAlphabet, c as fileFormats, d as fileLimits, f as fileMimeTypes, h as makeFileValue, l as fileGrantUrlOf, m as isFileValue, o as RuntimeFileOccurrence, p as grantFileValue, r as FileId, u as fileIdOf, v as DeploymentId, w as SubscriptionId, x as InvocationId, y as GeneratedId } from "./file-C1abuMgd.js";
2
- import { S as encodeTransportObject, c as initial, f as Result$2, g as pending, h as inspectResult, l as upgrade, m as failureFromWire, s as PaginationPageSize, t as ValueDescriptor, x as decodeTransportValue } from "./descriptor-XzDX2JDw-j3O6YHgW.js";
3
- import { a as ErrorCode, c as ServerMessageJson, d as WireSuccess, f as AppStateRevision, i as DeploymentCloseCode, l as WireFailure, m as InvocationKey, n as functionPathOf, o as FunctionAddress, p as DeploymentGeneration, r as ClientMessageJson, s as Operation, u as WireResult } from "./api-DzcR7spt.js";
4
- import { Array as Array$1, Cause, Context, Deferred, Duration, Effect, ErrorReporter, Fiber, HashMap, Layer, ManagedRuntime, Predicate, Schedule, Schema, String } from "effect";
1
+ import { A as RuntimeFileMetadata, B as makeFileValue, D as encodeTransportObject, E as decodeTransportValue, F as fileIdOf, G as IdAlphabet, H as AppId, I as fileLimits, J as SessionEpoch, K as InvocationId, L as fileMimeTypes, N as fileFormats, P as fileGrantUrlOf, R as grantFileValue, U as DeploymentId, W as GeneratedId, X as TableId, Y as SubscriptionId, _ as Result$2, a as SessionResponse, b as inspectResult, d as sessionPath, f as sessionRequestsPath, i as SessionRequest, j as RuntimeFileOccurrence, k as FileId, l as developmentSelectionKey, m as upgrade, n as SessionOutcome, o as SessionState, p as initial, q as RuntimeRequestNonce, r as SessionRedirect, t as DevelopmentSelection, x as pending, y as failureFromWire, z as isFileValue } from "./id-Bt9XWRGL.js";
2
+ import { a as ErrorCode, c as ServerMessageJson, d as WireSuccess, f as AppStateRevision, i as DeploymentCloseCode, l as WireFailure, m as InvocationKey, n as functionPathOf, o as FunctionAddress, p as DeploymentGeneration, r as ClientMessageJson, s as Operation, u as WireResult } from "./api-CAgKDij7.js";
3
+ import { s as PaginationPageSize, t as ValueDescriptor } from "./descriptor-Cyo9FP9n-C0SRVXNW.js";
4
+ import { Array as Array$1, Cause, Context, Deferred, Duration, Effect, ErrorReporter, Fiber, HashMap, Layer, ManagedRuntime, Option, Predicate, Schedule, Schema, Semaphore, String } from "effect";
5
5
  import { customAlphabet } from "nanoid";
6
- import { useCallback, useCallback as useCallback$1, useContext, useDebugValue, useDebugValue as useDebugValue$1, useEffect, useEffect as useEffect$1, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useReducer, useRef, useState, useState as useState$1 } from "preact/hooks";
6
+ import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
7
+ import { useCallback, useCallback as useCallback$1, useContext, useDebugValue, useDebugValue as useDebugValue$1, useEffect, useEffect as useEffect$1, useErrorBoundary, useId as useElementId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useReducer, useRef, useState, useState as useState$1 } from "preact/hooks";
7
8
  import * as Socket from "effect/unstable/socket/Socket";
8
9
  import * as BrowserCrypto from "@effect/platform-browser/BrowserCrypto";
9
10
  import { Component, Fragment, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray } from "preact";
@@ -119,6 +120,177 @@ const encodeJson = (value) => {
119
120
  };
120
121
  const encodeCanonicalJson = (value) => encodeJson(Schema.decodeSync(Schema.Json)(value));
121
122
  //#endregion
123
+ //#region src/client/id.ts
124
+ var IdRequestError = class extends Schema.TaggedError()("IdRequestError", { message: Schema.String }) {};
125
+ const outcomeKey = "ignotum.id.outcome";
126
+ const selectionJson = Schema.fromJsonString(DevelopmentSelection);
127
+ var BrowserSession = class BrowserSession extends Context.Service()("ignotum/client/id/BrowserSession") {
128
+ static layer = Layer.effect(BrowserSession, Effect.gen(function* () {
129
+ const client = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk);
130
+ const ids = yield* IdGenerator;
131
+ const bootstrapLock = yield* Semaphore.make(1);
132
+ const listeners = /* @__PURE__ */ new Set();
133
+ let state = { status: "pending" };
134
+ let current;
135
+ let generation = 0;
136
+ let deadline = Infinity;
137
+ let development = false;
138
+ let selection;
139
+ let expiry;
140
+ const notify = () => {
141
+ for (const listener of listeners) listener();
142
+ };
143
+ const suspend = (error) => {
144
+ generation += 1;
145
+ deadline = Infinity;
146
+ if (expiry !== void 0) clearTimeout(expiry);
147
+ current = void 0;
148
+ state = error === void 0 ? {
149
+ status: "pending",
150
+ outcome: state.outcome
151
+ } : {
152
+ status: "error",
153
+ error,
154
+ outcome: state.outcome
155
+ };
156
+ notify();
157
+ };
158
+ const accept = (session, serverTime) => {
159
+ generation += 1;
160
+ if (expiry !== void 0) clearTimeout(expiry);
161
+ current = session;
162
+ state = session.user === null ? {
163
+ status: "signedOut",
164
+ outcome: state.outcome
165
+ } : {
166
+ status: "signedIn",
167
+ user: session.user,
168
+ outcome: state.outcome
169
+ };
170
+ const delay = Math.max(0, session.validUntil - serverTime);
171
+ deadline = performance.now() + delay;
172
+ if (delay < 2147483647) expiry = setTimeout(() => suspend(), delay);
173
+ notify();
174
+ };
175
+ const checkExpiry = () => {
176
+ if (current !== void 0 && performance.now() >= deadline) suspend();
177
+ };
178
+ globalThis.addEventListener?.("focus", checkExpiry);
179
+ globalThis.document?.addEventListener("visibilitychange", checkExpiry);
180
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
181
+ if (expiry !== void 0) clearTimeout(expiry);
182
+ listeners.clear();
183
+ globalThis.removeEventListener?.("focus", checkExpiry);
184
+ globalThis.document?.removeEventListener("visibilitychange", checkExpiry);
185
+ }));
186
+ const readOutcome = () => {
187
+ const raw = globalThis.sessionStorage.getItem(outcomeKey);
188
+ if (raw !== null) {
189
+ const decoded = Schema.decodeUnknownOption(SessionOutcome)(raw);
190
+ if (Option.isSome(decoded)) state = {
191
+ ...state,
192
+ outcome: decoded.value
193
+ };
194
+ globalThis.sessionStorage.removeItem(outcomeKey);
195
+ }
196
+ };
197
+ const parameters = () => {
198
+ if (!development || selection === void 0) return "";
199
+ const params = new URLSearchParams({ epoch: selection.epoch });
200
+ if (selection.username !== null) params.set("username", selection.username);
201
+ return `?${params}`;
202
+ };
203
+ const readSession = Effect.fn("BrowserSession.read")(function* () {
204
+ return yield* client.get(`${sessionPath}${parameters()}`, { headers: { "x-ignotum-request": "1" } }).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(SessionResponse)));
205
+ });
206
+ let bootstrapGeneration = 0;
207
+ const bootstrap = bootstrapLock.withPermits(1)(Effect.gen(function* () {
208
+ const admittedGeneration = generation;
209
+ bootstrapGeneration = admittedGeneration;
210
+ const startedAt = performance.now();
211
+ let session = yield* readSession();
212
+ development = session.development === true;
213
+ if (development && selection === void 0) {
214
+ const saved = globalThis.sessionStorage.getItem(developmentSelectionKey);
215
+ const decoded = saved === null ? Option.none() : Schema.decodeOption(selectionJson)(saved);
216
+ selection = Option.getOrElse(decoded, () => ({
217
+ formatVersion: 1,
218
+ username: null,
219
+ epoch: ""
220
+ }));
221
+ if (selection.epoch === "") selection = {
222
+ ...selection,
223
+ epoch: yield* ids.generate(SessionEpoch)
224
+ };
225
+ globalThis.sessionStorage.setItem(developmentSelectionKey, Schema.encodeSync(selectionJson)(selection));
226
+ session = yield* readSession();
227
+ }
228
+ if (generation !== admittedGeneration) {
229
+ if (current !== void 0) return current;
230
+ return yield* IdRequestError.make({ message: "Your ID session changed while loading." });
231
+ }
232
+ readOutcome();
233
+ if (session.outcome !== void 0) state = {
234
+ ...state,
235
+ outcome: session.outcome
236
+ };
237
+ accept(session, session.serverTime + performance.now() - startedAt);
238
+ return session;
239
+ })).pipe(Effect.mapError(() => IdRequestError.make({ message: "Could not load your ID session." })), Effect.tapError((error) => Effect.sync(() => {
240
+ if (generation === bootstrapGeneration) suspend(error.message);
241
+ })));
242
+ const start = Effect.fn("BrowserSession.start")(function* (intent, profile, returnTo) {
243
+ const session = current ?? (yield* bootstrap);
244
+ if (intent === "requestProfile" && session.user === null) return yield* IdRequestError.make({ message: "Sign in before requesting profile information." });
245
+ const request = yield* Schema.decodeUnknownEffect(SessionRequest)({
246
+ intent,
247
+ profile,
248
+ sessionEpoch: session.sessionEpoch,
249
+ returnTo: returnTo ?? `${globalThis.location.pathname}${globalThis.location.search}${globalThis.location.hash}`
250
+ }).pipe(Effect.mapError(() => IdRequestError.make({ message: "The ID request is invalid." })));
251
+ const redirect = yield* HttpClientRequest.post(sessionRequestsPath).pipe(HttpClientRequest.setHeader("x-ignotum-request", "1"), HttpClientRequest.bodyJsonUnsafe(request), client.execute, Effect.flatMap(HttpClientResponse.schemaBodyJson(SessionRedirect)), Effect.mapError(() => IdRequestError.make({ message: "Could not start the ID request." })));
252
+ if (current?.sessionEpoch !== session.sessionEpoch) return yield* IdRequestError.make({ message: "Your ID session changed. Try again." });
253
+ globalThis.location.assign(redirect.redirectUrl);
254
+ });
255
+ const signOut = Effect.gen(function* () {
256
+ const session = current ?? (yield* bootstrap);
257
+ yield* HttpClientRequest.delete(sessionPath).pipe(HttpClientRequest.setHeader("x-ignotum-request", "1"), HttpClientRequest.bodyJsonUnsafe({ sessionEpoch: session.sessionEpoch }), client.execute, Effect.mapError(() => IdRequestError.make({ message: "Could not sign out." })));
258
+ suspend();
259
+ if (development) {
260
+ selection = {
261
+ formatVersion: 1,
262
+ username: null,
263
+ epoch: yield* ids.generate(SessionEpoch)
264
+ };
265
+ globalThis.sessionStorage.setItem(developmentSelectionKey, Schema.encodeSync(selectionJson)(selection));
266
+ }
267
+ globalThis.location.reload();
268
+ });
269
+ return BrowserSession.of({
270
+ snapshot: () => {
271
+ checkExpiry();
272
+ return state;
273
+ },
274
+ current: () => {
275
+ checkExpiry();
276
+ return current;
277
+ },
278
+ subscribe: (listener) => {
279
+ listeners.add(listener);
280
+ return () => {
281
+ listeners.delete(listener);
282
+ };
283
+ },
284
+ accept,
285
+ suspend,
286
+ bootstrap,
287
+ start,
288
+ signOut,
289
+ socketParameters: parameters
290
+ });
291
+ })).pipe(Layer.provide(FetchHttpClient.layer));
292
+ };
293
+ //#endregion
122
294
  //#region src/client/errors.ts
123
295
  /** @effect-diagnostics globalConsole:skip-file */
124
296
  var ConnectionUnavailable = class extends Schema.TaggedError()("ConnectionUnavailable", {
@@ -198,7 +370,9 @@ const IndexPointInvalidation = Schema.Struct({
198
370
  index: IndexIdentity,
199
371
  key: EncodedIndexKey
200
372
  });
373
+ const IdDependency = Schema.Struct({ type: Schema.Literal("Id") });
201
374
  const ReadDependency = Schema.Union([
375
+ IdDependency,
202
376
  TableDependency,
203
377
  DocumentDependency,
204
378
  IndexRangeDependency
@@ -263,7 +437,8 @@ const InvocationBase = {
263
437
  generation: DeploymentGeneration,
264
438
  invocationKey: InvocationKey,
265
439
  function: FunctionAddress,
266
- args: Schema.Json
440
+ args: Schema.Json,
441
+ identity: Schema.optional(SessionState)
267
442
  };
268
443
  const QueryInvocation = Schema.Struct({
269
444
  ...InvocationBase,
@@ -286,6 +461,7 @@ var RuntimeDeploymentUnavailable = class extends Schema.TaggedError()("RuntimeDe
286
461
  message: Schema.String
287
462
  }) {};
288
463
  const RuntimeInvocationRejectionCode = Schema.Literals([
464
+ "SessionExpired",
289
465
  "FunctionUnavailable",
290
466
  "InvalidArguments",
291
467
  "InvocationIdConflict",
@@ -554,55 +730,67 @@ const subscribeMessage = (entry) => ({
554
730
  const makeQueryCache = (allocateId, sendLifecycleMessage) => {
555
731
  const queries = /* @__PURE__ */ new Map();
556
732
  const queriesById = /* @__PURE__ */ new Map();
733
+ const observe = Effect.fn("SyncClient.QueryCache.observe")(function* (functionAddress, args) {
734
+ const key = queryKey(functionAddress, args);
735
+ let entry = queries.get(key);
736
+ if (entry === void 0) {
737
+ const created = {
738
+ args,
739
+ function: functionAddress,
740
+ generation: 0,
741
+ listeners: /* @__PURE__ */ new Set(),
742
+ revision: void 0,
743
+ references: 0,
744
+ result: pending(),
745
+ subscriptionId: yield* allocateId
746
+ };
747
+ queries.set(key, created);
748
+ queriesById.set(created.subscriptionId, created);
749
+ yield* sendLifecycleMessage(subscribeMessage(created));
750
+ entry = created;
751
+ }
752
+ const observed = entry;
753
+ observed.references += 1;
754
+ observed.generation += 1;
755
+ let released = false;
756
+ return {
757
+ getSnapshot: () => observed.result,
758
+ subscribe: (listener) => {
759
+ observed.listeners.add(listener);
760
+ return () => observed.listeners.delete(listener);
761
+ },
762
+ release: Effect.gen(function* () {
763
+ if (released) return;
764
+ released = true;
765
+ observed.references -= 1;
766
+ if (observed.references !== 0) return;
767
+ const generation = ++observed.generation;
768
+ yield* Effect.sleep("25 millis");
769
+ if (observed.references !== 0 || observed.generation !== generation || queries.get(key) !== observed) return;
770
+ queries.delete(key);
771
+ queriesById.delete(observed.subscriptionId);
772
+ yield* sendLifecycleMessage({
773
+ type: "Unsubscribe",
774
+ id: observed.subscriptionId
775
+ });
776
+ })
777
+ };
778
+ });
779
+ const clear = (entry) => {
780
+ entry.result = pending();
781
+ entry.revision = void 0;
782
+ for (const listener of entry.listeners) listener();
783
+ };
557
784
  return {
785
+ invalidate: (ids) => {
786
+ for (const entry of queries.values()) if (ids === void 0 || ids.includes(entry.subscriptionId)) clear(entry);
787
+ },
788
+ invalidatePrivate: () => {
789
+ for (const entry of queries.values()) if (entry.dependsOnId !== false) clear(entry);
790
+ },
558
791
  entries: () => queries.values(),
559
792
  getById: (id) => queriesById.get(id),
560
- observe: Effect.fn("SyncClient.QueryCache.observe")(function* (functionAddress, args) {
561
- const key = queryKey(functionAddress, args);
562
- let entry = queries.get(key);
563
- if (entry === void 0) {
564
- const created = {
565
- args,
566
- function: functionAddress,
567
- generation: 0,
568
- listeners: /* @__PURE__ */ new Set(),
569
- revision: void 0,
570
- references: 0,
571
- result: pending(),
572
- subscriptionId: yield* allocateId
573
- };
574
- queries.set(key, created);
575
- queriesById.set(created.subscriptionId, created);
576
- yield* sendLifecycleMessage(subscribeMessage(created));
577
- entry = created;
578
- }
579
- const observed = entry;
580
- observed.references += 1;
581
- observed.generation += 1;
582
- let released = false;
583
- return {
584
- getSnapshot: () => observed.result,
585
- subscribe: (listener) => {
586
- observed.listeners.add(listener);
587
- return () => observed.listeners.delete(listener);
588
- },
589
- release: Effect.gen(function* () {
590
- if (released) return;
591
- released = true;
592
- observed.references -= 1;
593
- if (observed.references !== 0) return;
594
- const generation = ++observed.generation;
595
- yield* Effect.sleep("25 millis");
596
- if (observed.references !== 0 || observed.generation !== generation || queries.get(key) !== observed) return;
597
- queries.delete(key);
598
- queriesById.delete(observed.subscriptionId);
599
- yield* sendLifecycleMessage({
600
- type: "Unsubscribe",
601
- id: observed.subscriptionId
602
- });
603
- })
604
- };
605
- }),
793
+ observe,
606
794
  setResult: (id, result, revision) => {
607
795
  const entry = queriesById.get(id);
608
796
  if (entry === void 0) return;
@@ -644,10 +832,12 @@ const socketUrl = () => {
644
832
  var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync/SyncClient") {
645
833
  static layer = Layer.effect(SyncClient, Effect.gen(function* () {
646
834
  const ids = yield* IdGenerator;
835
+ const session = yield* BrowserSession;
647
836
  let invocations = HashMap.empty();
648
837
  let rememberedHandshake;
649
838
  let reloadRequested = false;
650
839
  let writer;
840
+ let sessionSuspended;
651
841
  const encode = (message) => Schema.encodeEffect(ClientMessageJson)(message).pipe(Effect.mapError((cause) => InvalidClientMessage.make({
652
842
  cause,
653
843
  message: "Could not encode an Ignotum sync message."
@@ -661,17 +851,47 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
661
851
  ConnectionUnavailable: reportClientError,
662
852
  InvalidClientMessage: reportClientError
663
853
  })));
854
+ const unsubscribeSession = session.subscribe(() => {
855
+ if (session.current() === void 0) {
856
+ queryCache.invalidatePrivate();
857
+ writer = void 0;
858
+ if (sessionSuspended !== void 0) Deferred.doneUnsafe(sessionSuspended, Effect.void);
859
+ }
860
+ });
861
+ yield* Effect.addFinalizer(() => Effect.sync(unsubscribeSession));
664
862
  const rejectInvocation = (id, error) => {
665
- const pendingInvocation = HashMap.getUnsafe(invocations, id);
863
+ const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, id));
666
864
  if (pendingInvocation === void 0) return Effect.void;
667
865
  invocations = HashMap.remove(invocations, id);
668
866
  return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingInvocation.prepared, error)), Effect.andThen(Deferred.fail(pendingInvocation.deferred, error)), Effect.asVoid);
669
867
  };
670
868
  const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (message) {
671
869
  switch (message.type) {
870
+ case "Session":
871
+ if (message.event === "Updated") {
872
+ const previous = session.current();
873
+ if (previous !== void 0 && previous.sessionEpoch !== message.session.sessionEpoch) return;
874
+ if (previous?.viewRevision !== message.session.viewRevision) queryCache.invalidatePrivate();
875
+ session.accept(message.session, message.serverTime);
876
+ } else {
877
+ if (rememberedHandshake?.session?.sessionEpoch !== message.sessionEpoch) return;
878
+ session.suspend(message.event === "Unavailable" ? "Your ID session is temporarily unavailable." : void 0);
879
+ if (message.event !== "Unavailable") yield* Effect.forEach(HashMap.keys(invocations), (id) => rejectInvocation(id, ServerError.make({
880
+ code: "SessionChanged",
881
+ message: "The session changed. This operation may already have completed."
882
+ })));
883
+ }
884
+ return;
885
+ case "Invalidate":
886
+ if (message.sessionEpoch === session.current()?.sessionEpoch) queryCache.invalidate(message.ids);
887
+ return;
672
888
  case "Snapshot": {
889
+ const current = session.current();
890
+ if (current === void 0 || message.identity?.sessionEpoch !== current.sessionEpoch) return;
891
+ if (message.identity.dependsOnId && message.identity.viewRevision !== current.viewRevision) return;
673
892
  const entry = queryCache.getById(message.id);
674
893
  if (entry === void 0) return;
894
+ entry.dependsOnId = message.identity.dependsOnId;
675
895
  if (entry.revision !== void 0 && message.revision < entry.revision) return;
676
896
  yield* resultFromWire(message.result, message.files ?? []).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))), Effect.catchTags({ InvalidServerMessage: (error) => {
677
897
  queryCache.setResult(message.id, error, message.revision);
@@ -680,12 +900,14 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
680
900
  return;
681
901
  }
682
902
  case "Preparation": {
683
- const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
903
+ if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
904
+ const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
684
905
  if (pendingInvocation !== void 0) yield* Deferred.succeed(pendingInvocation.prepared, message.uploads);
685
906
  return;
686
907
  }
687
908
  case "Result": {
688
- const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
909
+ if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
910
+ const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
689
911
  if (pendingInvocation === void 0) return;
690
912
  yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => {
691
913
  invocations = HashMap.remove(invocations, message.id);
@@ -694,6 +916,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
694
916
  return;
695
917
  }
696
918
  case "Error": {
919
+ if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
697
920
  const error = message.operation === void 0 ? ServerError.make({
698
921
  code: message.code,
699
922
  message: message.message
@@ -722,7 +945,8 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
722
945
  });
723
946
  const connect = Effect.scoped(Effect.gen(function* () {
724
947
  if (reloadRequested) return yield* Effect.never;
725
- const socket = yield* Socket.makeWebSocket(socketUrl(), { closeCodeIsError: (code) => {
948
+ yield* session.bootstrap.pipe(Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message })));
949
+ const socket = yield* Socket.makeWebSocket(`${socketUrl()}${session.socketParameters()}`, { closeCodeIsError: (code) => {
726
950
  if (shouldReloadForCloseCode(code)) requestReload();
727
951
  return false;
728
952
  } });
@@ -736,6 +960,22 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
736
960
  return;
737
961
  }
738
962
  if (decision === "Ignore") return;
963
+ if (handshake.session === void 0 || handshake.serverTime === void 0) {
964
+ requestReload();
965
+ return;
966
+ }
967
+ if (rememberedHandshake?.session !== void 0 && rememberedHandshake.session.sessionEpoch !== handshake.session.sessionEpoch) {
968
+ queryCache.invalidatePrivate();
969
+ yield* Effect.forEach(HashMap.keys(invocations), (id) => rejectInvocation(id, ServerError.make({
970
+ code: "SessionChanged",
971
+ message: "The session changed. This operation may already have completed."
972
+ })));
973
+ } else if (rememberedHandshake?.session?.viewRevision !== handshake.session.viewRevision) queryCache.invalidatePrivate();
974
+ yield* Effect.forEach(HashMap.entries(invocations), ([id, pending]) => pending.sessionEpoch === handshake.session.sessionEpoch ? Effect.void : rejectInvocation(id, ServerError.make({
975
+ code: "SessionChanged",
976
+ message: "The session changed. This operation may already have completed."
977
+ })));
978
+ session.accept(handshake.session, handshake.serverTime);
739
979
  rememberedHandshake = handshake;
740
980
  handshakeAccepted = true;
741
981
  writer = write;
@@ -761,9 +1001,14 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
761
1001
  });
762
1002
  return yield* handleMessage(message);
763
1003
  });
1004
+ const suspended = yield* Deferred.make();
1005
+ sessionSuspended = suspended;
1006
+ yield* Effect.addFinalizer(() => Effect.sync(() => {
1007
+ if (sessionSuspended === suspended) sessionSuspended = void 0;
1008
+ }));
764
1009
  const run = yield* socket.runString(handleText).pipe(Effect.forkChild);
765
1010
  yield* Effect.raceFirst(Deferred.await(handshakeReceived).pipe(Effect.timeout("10 seconds")), Fiber.join(run));
766
- yield* Fiber.join(run);
1011
+ yield* Effect.raceFirst(Fiber.join(run), Deferred.await(suspended));
767
1012
  })).pipe(Effect.mapError((cause) => Schema.is(ClientInfrastructureError)(cause) ? cause : ConnectionUnavailable.make({
768
1013
  cause,
769
1014
  message: "The sync connection was lost."
@@ -774,6 +1019,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
774
1019
  return yield* queryCache.observe(functionAddress, args);
775
1020
  });
776
1021
  const mutate = Effect.fn("SyncClient.mutate")(function* (functionAddress, args) {
1022
+ const admittedSession = session.current() ?? rememberedHandshake?.session ?? (yield* session.bootstrap.pipe(Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message }))));
777
1023
  const invocationId = yield* ids.generate(InvocationId);
778
1024
  const preparedArguments = yield* prepareMutationArguments(args).pipe(Effect.provideService(IdGenerator, ids), Effect.mapError((cause) => InvalidMutationArguments.make({
779
1025
  cause,
@@ -806,6 +1052,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
806
1052
  files: preparedArguments.fileIds
807
1053
  };
808
1054
  invocations = HashMap.set(invocations, invocationId, {
1055
+ sessionEpoch: admittedSession.sessionEpoch,
809
1056
  deferred,
810
1057
  function: functionAddress,
811
1058
  message,
@@ -867,7 +1114,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
867
1114
  concurrency: 3,
868
1115
  discard: true
869
1116
  });
870
- const pendingInvocation = HashMap.getUnsafe(invocations, invocationId);
1117
+ const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, invocationId));
871
1118
  if (pendingInvocation === void 0) return yield* Deferred.await(deferred);
872
1119
  invocations = HashMap.set(invocations, invocationId, {
873
1120
  ...pendingInvocation,
@@ -889,7 +1136,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
889
1136
  });
890
1137
  })).pipe(Layer.provide(Layer.merge(Socket.layerWebSocketConstructorGlobal, BrowserCrypto.layer)));
891
1138
  };
892
- const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(clientErrorReporterLayer), Layer.provideMerge(IdGenerator.layer)));
1139
+ const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(BrowserSession.layer), Layer.provideMerge(clientErrorReporterLayer), Layer.provideMerge(IdGenerator.layer)));
893
1140
  const hot = import.meta.hot;
894
1141
  if (hot !== void 0) hot.dispose(() => void syncRuntime.dispose());
895
1142
  //#endregion
@@ -1135,6 +1382,38 @@ const useMutation = (reference) => useMemo$1(() => {
1135
1382
  };
1136
1383
  return mutate;
1137
1384
  }, [reference]);
1385
+ function useId() {
1386
+ const [state, setState] = useState$1({ status: "pending" });
1387
+ useEffect$1(() => {
1388
+ let active = true;
1389
+ let unsubscribe;
1390
+ syncRuntime.runPromise(BrowserSession).then((session) => {
1391
+ if (!active) return;
1392
+ const update = () => setState(session.snapshot());
1393
+ unsubscribe = session.subscribe(update);
1394
+ update();
1395
+ });
1396
+ return () => {
1397
+ active = false;
1398
+ unsubscribe?.();
1399
+ };
1400
+ }, []);
1401
+ const actions = useMemo$1(() => ({
1402
+ signIn: (options) => syncRuntime.runPromise(Effect.gen(function* () {
1403
+ return yield* (yield* BrowserSession).start("signIn", options?.profile ?? [], options?.returnTo);
1404
+ })),
1405
+ requestProfile: (fields, options) => syncRuntime.runPromise(Effect.gen(function* () {
1406
+ return yield* (yield* BrowserSession).start("requestProfile", fields, options?.returnTo);
1407
+ })),
1408
+ signOut: () => syncRuntime.runPromise(Effect.gen(function* () {
1409
+ return yield* (yield* BrowserSession).signOut;
1410
+ }))
1411
+ }), []);
1412
+ return {
1413
+ ...state,
1414
+ ...actions
1415
+ };
1416
+ }
1138
1417
  //#endregion
1139
1418
  //#region src/client/app.ts
1140
1419
  const app = (definition) => {
@@ -1145,6 +1424,6 @@ const app = (definition) => {
1145
1424
  //#region src/client/index.ts
1146
1425
  const Result = { match: Result$2.match };
1147
1426
  //#endregion
1148
- export { Component, Files, Fragment, Query, Result, app, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray, useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useMutation, usePaginatedQuery, useQuery, useReducer, useRef, useState };
1427
+ export { Component, Files, Fragment, Query, Result, app, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray, useCallback, useContext, useDebugValue, useEffect, useElementId, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useMutation, usePaginatedQuery, useQuery, useReducer, useRef, useState };
1149
1428
 
1150
1429
  //# sourceMappingURL=client.js.map