ignotum 0.0.10 → 0.0.12

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 (48) hide show
  1. package/README.md +8 -3
  2. package/dist/cli/bin.mjs +563 -104
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/runtime/{api-jCl8Hzry.d.ts → api-5XSrIeqW.d.ts} +4 -3
  5. package/dist/runtime/client.d.ts +6 -5
  6. package/dist/runtime/client.js +170 -75
  7. package/dist/runtime/client.js.map +1 -1
  8. package/dist/runtime/{descriptor-C5VA9qRl-BhD-WQeO.js → descriptor-XzDX2JDw-j3O6YHgW.js} +26 -6
  9. package/dist/runtime/descriptor-XzDX2JDw-j3O6YHgW.js.map +1 -0
  10. package/dist/runtime/{id-Cs82tq9Q-Caqfx54f.d.ts → id-Cs82tq9Q-CK-maMgN.d.ts} +13 -13
  11. package/dist/runtime/{index-D6VLTbDB.d.ts → index-CrWg4Z0y.d.ts} +132 -76
  12. package/dist/runtime/internal/api.d.ts +1 -1
  13. package/dist/runtime/internal/host.d.ts +18 -17
  14. package/dist/runtime/internal/host.js +11 -6
  15. package/dist/runtime/internal/host.js.map +1 -1
  16. package/dist/runtime/internal/server.d.ts +1 -1
  17. package/dist/runtime/internal/server.js +1 -1
  18. package/dist/runtime/internal/types.d.ts +1 -1
  19. package/dist/runtime/internal/types.js +1 -1
  20. package/dist/runtime/pagination-CX5IPbEi.d.ts +1 -0
  21. package/dist/runtime/{pagination-Bt3l7QaC-D_zI5zsu.d.ts → pagination-D-R9NR61-CpIoHFoI.d.ts} +8 -5
  22. package/dist/runtime/result-DIjKM-p4.d.ts +1 -0
  23. package/dist/runtime/{schema-C00wobuy.js → schema-B9XxyRO8.js} +132 -16
  24. package/dist/runtime/schema-B9XxyRO8.js.map +1 -0
  25. package/dist/runtime/server.d.ts +7 -3
  26. package/dist/runtime/server.js +5 -3
  27. package/dist/runtime/server.js.map +1 -1
  28. package/dist/runtime/{sync-bMQq9tXx.d.ts → sync-avN7NkcU.d.ts} +2 -2
  29. package/package.json +1 -1
  30. package/src/cli/agent-files.ts +2 -0
  31. package/src/cli/auth-client.ts +1 -1
  32. package/src/cli/bin.ts +13 -3
  33. package/src/cli/build/server.ts +102 -13
  34. package/src/cli/codegen.ts +7 -4
  35. package/src/cli/control-client.ts +30 -8
  36. package/src/cli/deploy.ts +7 -2
  37. package/src/cli/environment.ts +133 -0
  38. package/src/client/files.ts +43 -27
  39. package/src/client/hooks.ts +26 -17
  40. package/src/client/page-observers.ts +77 -0
  41. package/src/client/sync.ts +71 -59
  42. package/src/dev-runtime/functions.ts +127 -4
  43. package/src/dev-runtime/sync.ts +38 -9
  44. package/src/server/index.ts +11 -1
  45. package/dist/runtime/descriptor-C5VA9qRl-BhD-WQeO.js.map +0 -1
  46. package/dist/runtime/pagination-DcIkTOFs.d.ts +0 -1
  47. package/dist/runtime/schema-C00wobuy.js.map +0 -1
  48. package/src/dev-runtime/id.ts +0 -3
@@ -1,5 +1,6 @@
1
- import { a as InternalServerError, g as FileValue, i as ErrorValue } from "./id-Cs82tq9Q-Caqfx54f.js";
2
- import { t as FunctionAddress } from "./sync-bMQq9tXx.js";
1
+ import { a as InternalServerError, g as FileValue, i as ErrorValue } from "./id-Cs82tq9Q-CK-maMgN.js";
2
+ import "./result-DIjKM-p4.js";
3
+ import { t as FunctionAddress } from "./sync-avN7NkcU.js";
3
4
  import { Effect } from "effect";
4
5
  //#region src/internal/api.d.ts
5
6
  declare const FunctionReferenceTypeId: unique symbol;
@@ -42,4 +43,4 @@ declare const functionPathOf: <Kind extends FunctionKind, Args, Success, Failure
42
43
  declare function createApi<Modules>(): Api<Modules>;
43
44
  //#endregion
44
45
  export { functionPathOf as a, createApi as i, FunctionReference as n, MutationInput as r, Api as t };
45
- //# sourceMappingURL=api-jCl8Hzry.d.ts.map
46
+ //# sourceMappingURL=api-5XSrIeqW.d.ts.map
@@ -1,8 +1,9 @@
1
- import { a as InternalServerError, g as FileValue, h as FileMimeType, i as ErrorValue, l as SettledResult, m as FileMetadata, o as Match, p as FileFormat, s as QueryResult$1 } from "./id-Cs82tq9Q-Caqfx54f.js";
2
- import "./sync-bMQq9tXx.js";
3
- import { n as FunctionReference } from "./api-jCl8Hzry.js";
4
- import { n as PaginationPage, t as PaginationOptions } from "./pagination-Bt3l7QaC-D_zI5zsu.js";
5
- import "./pagination-DcIkTOFs.js";
1
+ import { a as InternalServerError, g as FileValue, h as FileMimeType, i as ErrorValue, l as SettledResult, m as FileMetadata, o as Match, p as FileFormat, s as QueryResult$1 } from "./id-Cs82tq9Q-CK-maMgN.js";
2
+ import "./result-DIjKM-p4.js";
3
+ import "./sync-avN7NkcU.js";
4
+ import { n as FunctionReference } from "./api-5XSrIeqW.js";
5
+ import { n as PaginationPage, t as PaginationOptions } from "./pagination-D-R9NR61-CpIoHFoI.js";
6
+ import "./pagination-CX5IPbEi.js";
6
7
  import { Context as Context$1, Effect, Layer } from "effect";
7
8
  import { Dispatch, Reducer, StateUpdater, useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "preact/hooks";
8
9
  import { AnyComponent, Attributes, ClassAttributes, Component, ComponentChild, ComponentChildren, ComponentClass, ComponentConstructor, ComponentFactory, ComponentProps, ComponentType, ComponentType as ComponentType$1, Consumer, Context, ContextType, ErrorInfo, Fragment, FunctionComponent, FunctionalComponent, JSX, Key, PreactConsumer, PreactContext, PreactDOMAttributes, PreactProvider, Provider, Ref, RefCallback, RefObject, RenderableProps, TargetedAnimationEvent, TargetedClipboardEvent, TargetedCommandEvent, TargetedCompositionEvent, TargetedDragEvent, TargetedEvent, TargetedFocusEvent, TargetedInputEvent, TargetedKeyboardEvent, TargetedMouseEvent, TargetedPictureInPictureEvent, TargetedPointerEvent, TargetedSnapEvent, TargetedSubmitEvent, TargetedToggleEvent, TargetedTouchEvent, TargetedTransitionEvent, TargetedUIEvent, TargetedWheelEvent, VNode, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray } from "preact";
@@ -1,5 +1,5 @@
1
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 { b as decodeTransportValue, c as upgrade, d as Result$1, h as pending, m as inspectResult, o as PaginationPageSize, p as failureFromWire, s as initial, t as ValueDescriptor, x as encodeTransportObject } from "./descriptor-C5VA9qRl-BhD-WQeO.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
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
4
  import { Array as Array$1, Cause, Context, Deferred, Duration, Effect, ErrorReporter, Fiber, HashMap, Layer, ManagedRuntime, Predicate, Schedule, Schema, String } from "effect";
5
5
  import { customAlphabet } from "nanoid";
@@ -32,9 +32,10 @@ const Files = { url: (file) => {
32
32
  } };
33
33
  const formatByMime = {};
34
34
  for (const format of fileFormats) formatByMime[fileMimeTypes[format]] = format;
35
+ const MutationObject = Schema.ObjectKeyword.check(Schema.makeFilter((value) => Object.prototype.toString.call(value) === "[object Object]", { message: "Mutation objects must contain ordinary fields; built-in collections are unsupported." }));
35
36
  const collectNativeFiles = (value, files) => {
36
37
  if (value instanceof File) {
37
- files.push(value);
38
+ files.add(value);
38
39
  return;
39
40
  }
40
41
  if (isFileValue(value) || Predicate.isDate(value)) return;
@@ -43,7 +44,7 @@ const collectNativeFiles = (value, files) => {
43
44
  return;
44
45
  }
45
46
  if (!Predicate.isObject(value)) return;
46
- for (const child of Object.values(value)) collectNativeFiles(child, files);
47
+ for (const child of Object.values(Schema.decodeSync(MutationObject)(value))) collectNativeFiles(child, files);
47
48
  };
48
49
  const replaceNativeFiles = (value, replacements) => {
49
50
  if (value instanceof File) return replacements.get(value);
@@ -54,18 +55,23 @@ const replaceNativeFiles = (value, replacements) => {
54
55
  };
55
56
  const prepareMutationArguments = Effect.fn("SyncClient.prepareMutationArguments")(function* (input) {
56
57
  const ids = yield* IdGenerator;
57
- const native = [];
58
- collectNativeFiles(input, native);
59
- const unique = new Set(native);
60
- if (unique.size > fileLimits.filesPerMutation) throw new Error(`A mutation may upload at most ${fileLimits.filesPerMutation} files.`);
61
- if ([...unique].reduce((total, file) => total + file.size, 0) > fileLimits.mutationBytes) throw new Error(`Mutation uploads may total at most ${fileLimits.mutationBytes} bytes.`);
58
+ const unique = yield* Effect.try(() => {
59
+ const files = /* @__PURE__ */ new Set();
60
+ collectNativeFiles(input, files);
61
+ if (files.size > fileLimits.filesPerMutation) throw new Error(`A mutation may upload at most ${fileLimits.filesPerMutation} files.`);
62
+ if ([...files].reduce((total, file) => total + file.size, 0) > fileLimits.mutationBytes) throw new Error(`Mutation uploads may total at most ${fileLimits.mutationBytes} bytes.`);
63
+ return files;
64
+ });
62
65
  const replacements = /* @__PURE__ */ new Map();
63
66
  const nativeFiles = /* @__PURE__ */ new Map();
64
67
  for (const file of unique) {
65
- const format = formatByMime[file.type.toLowerCase()];
66
- if (format === void 0) throw new Error(`File '${file.name}' has unsupported media type '${file.type || "unknown"}'.`);
67
- if (file.size === 0 || file.size > fileLimits.fileBytes) throw new Error(`File '${file.name}' must be between 1 and ${fileLimits.fileBytes} bytes.`);
68
- if (new TextEncoder().encode(file.name).byteLength > fileLimits.filenameBytes) throw new Error(`File '${file.name}' has a filename that is too long.`);
68
+ const format = yield* Effect.try(() => {
69
+ const format = formatByMime[file.type.toLowerCase()];
70
+ if (format === void 0) throw new Error(`File '${file.name}' has unsupported media type '${file.type || "unknown"}'.`);
71
+ if (file.size === 0 || file.size > fileLimits.fileBytes) throw new Error(`File '${file.name}' must be between 1 and ${fileLimits.fileBytes} bytes.`);
72
+ if (new TextEncoder().encode(file.name).byteLength > fileLimits.filenameBytes) throw new Error(`File '${file.name}' has a filename that is too long.`);
73
+ return format;
74
+ });
69
75
  const id = yield* ids.generate(FileId);
70
76
  replacements.set(file, makeFileValue(id, {
71
77
  format,
@@ -75,12 +81,13 @@ const prepareMutationArguments = Effect.fn("SyncClient.prepareMutationArguments"
75
81
  nativeFiles.set(id, file);
76
82
  }
77
83
  return {
78
- value: replaceNativeFiles(input, replacements),
84
+ value: unique.size === 0 ? input : yield* Effect.try(() => replaceNativeFiles(input, replacements)),
79
85
  nativeFiles,
80
86
  fileIds: Array.from(nativeFiles.keys())
81
87
  };
82
88
  });
83
89
  const applyFileGrants = (value, grants) => {
90
+ if (grants.length === 0) return value;
84
91
  const byId = /* @__PURE__ */ new Map();
85
92
  for (const grant of grants) {
86
93
  if (byId.has(grant.id)) throw new Error("A query file grant ID is duplicated.");
@@ -105,15 +112,12 @@ const applyFileGrants = (value, grants) => {
105
112
  };
106
113
  //#endregion
107
114
  //#region ../contracts/dist/json.js
108
- const encodeScalar = (value) => Schema.decodeSync(Schema.String)(JSON.stringify(value));
109
- const encodeCanonicalJson = (value) => {
110
- if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return encodeScalar(value);
111
- if (Predicate.isObject(value)) return `{${Array$1.map(Array$1.sort(String.Order)(Object.keys(value)), (key) => {
112
- const field = Schema.decodeUnknownSync(Schema.Json)(value[key]);
113
- return `${encodeScalar(key)}:${encodeCanonicalJson(field)}`;
114
- }).join(",")}}`;
115
- return `[${Schema.decodeUnknownSync(Schema.Array(Schema.Json))(value).map(encodeCanonicalJson).join(",")}]`;
115
+ const encodeJson = (value) => {
116
+ if (Array$1.isArray(value)) return `[${value.map(encodeJson).join(",")}]`;
117
+ if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return JSON.stringify(value);
118
+ return `{${Array$1.sortWith(Object.entries(value), ([key]) => key, String.Order).map(([key, field]) => `${JSON.stringify(key)}:${encodeJson(field)}`).join(",")}}`;
116
119
  };
120
+ const encodeCanonicalJson = (value) => encodeJson(Schema.decodeSync(Schema.Json)(value));
117
121
  //#endregion
118
122
  //#region src/client/errors.ts
119
123
  /** @effect-diagnostics globalConsole:skip-file */
@@ -342,6 +346,7 @@ const ArtifactKind = Schema.Literals([
342
346
  "ClientShell",
343
347
  "FunctionBundle",
344
348
  "ServerManifest",
349
+ "ServerEnvironment",
345
350
  "SourceMap",
346
351
  "SchemaSnapshot"
347
352
  ]);
@@ -366,7 +371,14 @@ const DeploymentInventoryV1 = Schema.Struct({
366
371
  formatVersion: Schema.Literal(1),
367
372
  files: Schema.Array(ArtifactFile)
368
373
  });
369
- initial(DeploymentInventoryV1);
374
+ const DeploymentInventoryV2 = Schema.Struct({
375
+ formatVersion: Schema.Literal(2),
376
+ files: Schema.Array(ArtifactFile)
377
+ });
378
+ upgrade(initial(DeploymentInventoryV1), DeploymentInventoryV2, (inventory) => ({
379
+ formatVersion: 2,
380
+ files: inventory.files
381
+ }));
370
382
  const ClientManifestV1 = Schema.Struct({
371
383
  formatVersion: Schema.Literal(1),
372
384
  shell: ArtifactReference,
@@ -387,6 +399,16 @@ const SchemaSnapshotField = Schema.Struct({
387
399
  name: Schema.String,
388
400
  value: ValueDescriptor
389
401
  });
402
+ const EnvironmentArtifactV1 = Schema.Struct({
403
+ formatVersion: Schema.Literal(1),
404
+ nonce: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/)),
405
+ values: Schema.Record(Schema.String, Schema.String)
406
+ });
407
+ initial(EnvironmentArtifactV1);
408
+ const ServerEnvironmentArtifact = Schema.Struct({
409
+ artifact: ArtifactReference,
410
+ fields: Schema.Array(SchemaSnapshotField)
411
+ });
390
412
  const SchemaSnapshotIndex = Schema.Struct({
391
413
  name: Schema.String,
392
414
  fields: Schema.Array(Schema.String)
@@ -440,11 +462,22 @@ const ServerBuildManifestV2 = Schema.Struct({
440
462
  schema: ArtifactReference,
441
463
  functions: Schema.Array(ServerFunctionArtifact)
442
464
  });
443
- upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
465
+ const ServerBuildManifestThroughV2 = upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
444
466
  formatVersion: 2,
445
467
  schema: manifest.schema,
446
468
  functions: manifest.functions
447
469
  }));
470
+ const ServerBuildManifestV3 = Schema.Struct({
471
+ formatVersion: Schema.Literal(3),
472
+ schema: ArtifactReference,
473
+ environment: Schema.optional(ServerEnvironmentArtifact),
474
+ functions: Schema.Array(ServerFunctionArtifact)
475
+ });
476
+ upgrade(ServerBuildManifestThroughV2, ServerBuildManifestV3, (manifest) => ({
477
+ formatVersion: 3,
478
+ schema: manifest.schema,
479
+ functions: manifest.functions
480
+ }));
448
481
  ArtifactPath.make("inventory.json");
449
482
  ArtifactPath.make("client/manifest.json");
450
483
  ArtifactPath.make("client/shell.html");
@@ -452,6 +485,7 @@ ArtifactPath.make("client/shell.html");
452
485
  `${ArtifactPath.make("client/routes")}`;
453
486
  ArtifactPath.make("server/manifest.json");
454
487
  ArtifactPath.make("server/schema.json");
488
+ ArtifactPath.make("server/environment.json");
455
489
  //#endregion
456
490
  //#region ../contracts/dist/runtime/transport.js
457
491
  const runtimeInvocationPath = "/v1/invoke";
@@ -519,13 +553,10 @@ const subscribeMessage = (entry) => ({
519
553
  });
520
554
  const makeQueryCache = (allocateId, sendLifecycleMessage) => {
521
555
  const queries = /* @__PURE__ */ new Map();
522
- const queryKeysById = /* @__PURE__ */ new Map();
556
+ const queriesById = /* @__PURE__ */ new Map();
523
557
  return {
524
558
  entries: () => queries.values(),
525
- getById: (id) => {
526
- const key = queryKeysById.get(id);
527
- return key === void 0 ? void 0 : queries.get(key);
528
- },
559
+ getById: (id) => queriesById.get(id),
529
560
  observe: Effect.fn("SyncClient.QueryCache.observe")(function* (functionAddress, args) {
530
561
  const key = queryKey(functionAddress, args);
531
562
  let entry = queries.get(key);
@@ -541,7 +572,7 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
541
572
  subscriptionId: yield* allocateId
542
573
  };
543
574
  queries.set(key, created);
544
- queryKeysById.set(created.subscriptionId, key);
575
+ queriesById.set(created.subscriptionId, created);
545
576
  yield* sendLifecycleMessage(subscribeMessage(created));
546
577
  entry = created;
547
578
  }
@@ -564,7 +595,7 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
564
595
  yield* Effect.sleep("25 millis");
565
596
  if (observed.references !== 0 || observed.generation !== generation || queries.get(key) !== observed) return;
566
597
  queries.delete(key);
567
- queryKeysById.delete(observed.subscriptionId);
598
+ queriesById.delete(observed.subscriptionId);
568
599
  yield* sendLifecycleMessage({
569
600
  type: "Unsubscribe",
570
601
  id: observed.subscriptionId
@@ -572,25 +603,25 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
572
603
  })
573
604
  };
574
605
  }),
575
- setError: (id, error) => {
576
- const key = queryKeysById.get(id);
577
- const entry = key === void 0 ? void 0 : queries.get(key);
578
- if (entry === void 0) return;
579
- entry.result = error;
580
- for (const listener of entry.listeners) listener();
581
- },
582
606
  setResult: (id, result, revision) => {
583
- const key = queryKeysById.get(id);
584
- const entry = key === void 0 ? void 0 : queries.get(key);
607
+ const entry = queriesById.get(id);
585
608
  if (entry === void 0) return;
586
- if (entry.revision !== void 0 && revision < entry.revision) return;
587
- entry.revision = revision;
609
+ if (revision !== void 0) {
610
+ if (entry.revision !== void 0 && revision < entry.revision) return;
611
+ entry.revision = revision;
612
+ }
588
613
  entry.result = result;
589
614
  for (const listener of entry.listeners) listener();
590
615
  }
591
616
  };
592
617
  };
593
- const resultFromWire = Effect.fn("SyncClient.resultFromWire")((wire, files = []) => Effect.succeed(wire.type === "Success" ? Result$1.succeed(wire.value === void 0 ? void 0 : applyFileGrants(decodeTransportValue(wire.value, wire.dates ?? []), files)) : failureFromWire(wire.error, wire.dates)));
618
+ const resultFromWire = Effect.fn("SyncClient.resultFromWire")((wire, files = []) => Effect.try({
619
+ try: () => wire.type === "Success" ? Result$2.succeed(wire.value === void 0 ? void 0 : applyFileGrants(decodeTransportValue(wire.value, wire.dates ?? []), files)) : failureFromWire(wire.error, wire.dates),
620
+ catch: (cause) => InvalidServerMessage.make({
621
+ cause,
622
+ message: "The server returned an invalid function result."
623
+ })
624
+ }));
594
625
  const shouldReloadForCloseCode = (code) => code === DeploymentCloseCode;
595
626
  const handshakesMatch = (first, next) => first.appId === next.appId && first.deploymentId === next.deploymentId && first.generation === next.generation;
596
627
  const handshakeDecision = (remembered, acceptedOnConnection, next) => {
@@ -638,10 +669,16 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
638
669
  };
639
670
  const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (message) {
640
671
  switch (message.type) {
641
- case "Snapshot":
642
- if (queryCache.getById(message.id) === void 0) return;
643
- yield* resultFromWire(message.result, message.files ?? []).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))));
672
+ case "Snapshot": {
673
+ const entry = queryCache.getById(message.id);
674
+ if (entry === void 0) return;
675
+ if (entry.revision !== void 0 && message.revision < entry.revision) return;
676
+ yield* resultFromWire(message.result, message.files ?? []).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))), Effect.catchTags({ InvalidServerMessage: (error) => {
677
+ queryCache.setResult(message.id, error, message.revision);
678
+ return reportClientError(error);
679
+ } }));
644
680
  return;
681
+ }
645
682
  case "Preparation": {
646
683
  const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
647
684
  if (pendingInvocation !== void 0) yield* Deferred.succeed(pendingInvocation.prepared, message.uploads);
@@ -650,8 +687,10 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
650
687
  case "Result": {
651
688
  const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
652
689
  if (pendingInvocation === void 0) return;
653
- invocations = HashMap.remove(invocations, message.id);
654
- yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => Deferred.succeed(pendingInvocation.deferred, result)));
690
+ yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => {
691
+ invocations = HashMap.remove(invocations, message.id);
692
+ return Deferred.succeed(pendingInvocation.deferred, result);
693
+ }), Effect.catchTags({ InvalidServerMessage: (error) => rejectInvocation(message.id, error) }));
655
694
  return;
656
695
  }
657
696
  case "Error": {
@@ -665,7 +704,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
665
704
  });
666
705
  if (message.operation?.type === "Invocation") yield* rejectInvocation(message.operation.id, error);
667
706
  else if (message.operation?.type === "Subscription") {
668
- queryCache.setError(message.operation.id, error);
707
+ queryCache.setResult(message.operation.id, error);
669
708
  yield* reportClientError(error);
670
709
  } else yield* reportClientError(error);
671
710
  return;
@@ -781,6 +820,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
781
820
  const uploads = yield* Deferred.await(prepared);
782
821
  const expected = new Set(message.files);
783
822
  const received = /* @__PURE__ */ new Set();
823
+ const transfers = [];
784
824
  for (const upload of uploads) {
785
825
  if (!expected.has(upload.id) || received.has(upload.id)) return yield* InvalidServerMessage.make({
786
826
  cause: /* @__PURE__ */ new Error(`Unexpected prepared file '${upload.id}'.`),
@@ -793,12 +833,22 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
793
833
  message: "The server prepared an unknown application file."
794
834
  });
795
835
  if (upload.url === void 0) continue;
796
- const uploadUrl = upload.url;
836
+ transfers.push({
837
+ file,
838
+ url: upload.url
839
+ });
840
+ }
841
+ if (received.size !== expected.size) return yield* InvalidServerMessage.make({
842
+ cause: /* @__PURE__ */ new Error("The server omitted a prepared file."),
843
+ message: "The server returned incomplete application file preparation data."
844
+ });
845
+ yield* Effect.forEach(transfers, Effect.fn("SyncClient.uploadFile")(function* ({ file, url }) {
797
846
  const response = yield* Effect.tryPromise({
798
- try: () => fetch(uploadUrl, {
847
+ try: (signal) => fetch(url, {
799
848
  method: "PUT",
800
849
  body: file,
801
- headers: { "content-type": file.type }
850
+ headers: { "content-type": file.type },
851
+ signal
802
852
  }),
803
853
  catch: (cause) => ConnectionUnavailable.make({
804
854
  cause,
@@ -813,10 +863,9 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
813
863
  message: `File '${file.name}' was rejected by application storage.`
814
864
  });
815
865
  }
816
- }
817
- if (received.size !== expected.size) return yield* InvalidServerMessage.make({
818
- cause: /* @__PURE__ */ new Error("The server omitted a prepared file."),
819
- message: "The server returned incomplete application file preparation data."
866
+ }), {
867
+ concurrency: 3,
868
+ discard: true
820
869
  });
821
870
  const pendingInvocation = HashMap.getUnsafe(invocations, invocationId);
822
871
  if (pendingInvocation === void 0) return yield* Deferred.await(deferred);
@@ -844,6 +893,56 @@ const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge
844
893
  const hot = import.meta.hot;
845
894
  if (hot !== void 0) hot.dispose(() => void syncRuntime.dispose());
846
895
  //#endregion
896
+ //#region src/client/page-observers.ts
897
+ const makePageObservers = (onChange, releaseObserver) => {
898
+ const pages = /* @__PURE__ */ new Map();
899
+ let disposed = false;
900
+ const releaseEntry = (entry) => {
901
+ entry.released = true;
902
+ entry.release?.();
903
+ };
904
+ return {
905
+ observe: Effect.fn("PageObservers.observe")(function* (functionAddress, args) {
906
+ if (disposed) return;
907
+ const key = syncClientInternals.queryIdentity(functionAddress, args);
908
+ const existing = pages.get(key);
909
+ if (existing !== void 0) return yield* existing.acquire;
910
+ const entry = {
911
+ acquire: yield* Effect.cached(Effect.gen(function* () {
912
+ const observer = yield* (yield* SyncClient).observe(functionAddress, args);
913
+ if (entry.released) {
914
+ releaseObserver(observer.release);
915
+ return;
916
+ }
917
+ const unsubscribe = observer.subscribe(onChange);
918
+ entry.release = () => {
919
+ unsubscribe();
920
+ releaseObserver(observer.release);
921
+ };
922
+ return observer;
923
+ })),
924
+ release: void 0,
925
+ released: false
926
+ };
927
+ pages.set(key, entry);
928
+ return yield* entry.acquire;
929
+ }),
930
+ retain: (keys) => {
931
+ for (const [key, entry] of pages) {
932
+ if (keys.has(key)) continue;
933
+ pages.delete(key);
934
+ releaseEntry(entry);
935
+ }
936
+ },
937
+ release: () => {
938
+ if (disposed) return;
939
+ disposed = true;
940
+ for (const entry of pages.values()) releaseEntry(entry);
941
+ pages.clear();
942
+ }
943
+ };
944
+ };
945
+ //#endregion
847
946
  //#region src/client/hooks.ts
848
947
  function useQuery(reference, args) {
849
948
  const functionPath = functionPathOf(reference);
@@ -916,6 +1015,10 @@ function usePaginatedQuery(reference, args, options = {}) {
916
1015
  result: pending()
917
1016
  }));
918
1017
  const loadMore = useCallback$1(() => setRequestedPages((count) => count + 1), []);
1018
+ const pages = useMemo$1(() => makePageObservers(() => setRefresh((version) => version + 1), (release) => {
1019
+ syncRuntime.runFork(release);
1020
+ }), [identity, pageSize]);
1021
+ useEffect$1(() => () => pages.release(), [pages]);
919
1022
  useDebugValue$1({
920
1023
  args: input,
921
1024
  function: functionPath,
@@ -931,9 +1034,8 @@ function usePaginatedQuery(reference, args, options = {}) {
931
1034
  useEffect$1(() => {
932
1035
  if (skipped) return;
933
1036
  let active = true;
934
- const releases = [];
1037
+ const usedPages = /* @__PURE__ */ new Set();
935
1038
  syncRuntime.runPromise(Effect.gen(function* () {
936
- const client = yield* SyncClient;
937
1039
  const items = [];
938
1040
  let cursor = null;
939
1041
  for (let pageNumber = 0; pageNumber < requestedPages; pageNumber += 1) {
@@ -944,18 +1046,9 @@ function usePaginatedQuery(reference, args, options = {}) {
944
1046
  pageSize
945
1047
  }
946
1048
  });
947
- const observer = yield* client.observe(functionPath, pageArgs);
948
- if (!active) {
949
- yield* observer.release;
950
- return;
951
- }
952
- const releaseSubscription = observer.subscribe(() => {
953
- if (active) setRefresh((version) => version + 1);
954
- });
955
- releases.push(() => {
956
- releaseSubscription();
957
- syncRuntime.runFork(observer.release);
958
- });
1049
+ usedPages.add(syncClientInternals.queryIdentity(functionPath, pageArgs));
1050
+ const observer = yield* pages.observe(functionPath, pageArgs);
1051
+ if (!active || observer === void 0) return;
959
1052
  const snapshot = observer.getSnapshot();
960
1053
  if (Schema.is(ClientInfrastructureError)(snapshot)) {
961
1054
  setState({
@@ -968,7 +1061,7 @@ function usePaginatedQuery(reference, args, options = {}) {
968
1061
  if (inspected === void 0) {
969
1062
  setState({
970
1063
  identity,
971
- result: items.length === 0 ? pending() : Result$1.succeed({
1064
+ result: items.length === 0 ? pending() : Result$2.succeed({
972
1065
  items,
973
1066
  loadMore,
974
1067
  status: "LoadingMore"
@@ -989,7 +1082,7 @@ function usePaginatedQuery(reference, args, options = {}) {
989
1082
  if (cursor === null) {
990
1083
  setState({
991
1084
  identity,
992
- result: Result$1.succeed({
1085
+ result: Result$2.succeed({
993
1086
  items,
994
1087
  loadMore,
995
1088
  status: "Exhausted"
@@ -1000,13 +1093,15 @@ function usePaginatedQuery(reference, args, options = {}) {
1000
1093
  }
1001
1094
  setState({
1002
1095
  identity,
1003
- result: Result$1.succeed({
1096
+ result: Result$2.succeed({
1004
1097
  items,
1005
1098
  loadMore,
1006
1099
  status: "CanLoadMore"
1007
1100
  })
1008
1101
  });
1009
- })).catch((error) => {
1102
+ }).pipe(Effect.ensuring(Effect.sync(() => {
1103
+ if (active) pages.retain(usedPages);
1104
+ })))).catch((error) => {
1010
1105
  if (active && Schema.is(ClientInfrastructureError)(error)) setState({
1011
1106
  identity,
1012
1107
  result: error
@@ -1014,13 +1109,13 @@ function usePaginatedQuery(reference, args, options = {}) {
1014
1109
  });
1015
1110
  return () => {
1016
1111
  active = false;
1017
- for (const release of releases) release();
1018
1112
  };
1019
1113
  }, [
1020
1114
  functionPath,
1021
1115
  identity,
1022
1116
  loadMore,
1023
1117
  pageSize,
1118
+ pages,
1024
1119
  refresh,
1025
1120
  requestedPages,
1026
1121
  skipped
@@ -1048,7 +1143,7 @@ const app = (definition) => {
1048
1143
  };
1049
1144
  //#endregion
1050
1145
  //#region src/client/index.ts
1051
- const Result = { match: Result$1.match };
1146
+ const Result = { match: Result$2.match };
1052
1147
  //#endregion
1053
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 };
1054
1149