ignotum 0.0.7 → 0.0.9

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 (58) hide show
  1. package/README.md +89 -101
  2. package/dist/cli/bin.mjs +1119 -195
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/runtime/{api-DtX8qPrq.js → api-DzcR7spt.js} +36 -13
  5. package/dist/runtime/api-DzcR7spt.js.map +1 -0
  6. package/dist/runtime/{api-Dp4J-xt-.d.ts → api-jCl8Hzry.d.ts} +7 -9
  7. package/dist/runtime/client.d.ts +12 -5
  8. package/dist/runtime/client.js +285 -60
  9. package/dist/runtime/client.js.map +1 -1
  10. package/dist/runtime/{descriptor-t6BOEGw9-C1rwYIlx.js → descriptor-C5VA9qRl-BKbdenuU.js} +38 -7
  11. package/dist/runtime/descriptor-C5VA9qRl-BKbdenuU.js.map +1 -0
  12. package/dist/runtime/file-C1abuMgd.js +173 -0
  13. package/dist/runtime/file-C1abuMgd.js.map +1 -0
  14. package/dist/runtime/{id-Btwac71X-DhnKYsjY.d.ts → id-Cs82tq9Q-Caqfx54f.d.ts} +51 -5
  15. package/dist/runtime/{index-B5KSOjGN.d.ts → index-D6VLTbDB.d.ts} +40 -21
  16. package/dist/runtime/internal/api.d.ts +2 -2
  17. package/dist/runtime/internal/api.js +1 -1
  18. package/dist/runtime/internal/host.d.ts +8 -7
  19. package/dist/runtime/internal/host.js +21 -10
  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-B1BzNkh8-BUSTbeSg.d.ts → pagination-Bt3l7QaC-D_zI5zsu.d.ts} +8 -4
  26. package/dist/runtime/pagination-DcIkTOFs.d.ts +1 -0
  27. package/dist/runtime/{schema-D9RmboaS.js → schema-B6PK_ZwV.js} +17 -3
  28. package/dist/runtime/schema-B6PK_ZwV.js.map +1 -0
  29. package/dist/runtime/server.d.ts +3 -3
  30. package/dist/runtime/server.js +2 -2
  31. package/dist/runtime/server.js.map +1 -1
  32. package/dist/runtime/sync-bMQq9tXx.d.ts +8 -0
  33. package/package.json +7 -5
  34. package/src/cli/agent-files.ts +52 -13
  35. package/src/cli/app-configuration.ts +4 -1
  36. package/src/cli/auth-client.ts +286 -0
  37. package/src/cli/build/server.ts +83 -6
  38. package/src/cli/command.ts +65 -2
  39. package/src/cli/control-client.ts +19 -6
  40. package/src/cli/new-app.ts +16 -0
  41. package/src/client/errors.ts +7 -10
  42. package/src/client/files.ts +153 -0
  43. package/src/client/hooks.ts +2 -15
  44. package/src/client/index.ts +7 -0
  45. package/src/client/sync.ts +158 -30
  46. package/src/dev-runtime/database.ts +69 -57
  47. package/src/dev-runtime/files.ts +337 -0
  48. package/src/dev-runtime/functions.ts +14 -6
  49. package/src/dev-runtime/migrations.ts +14 -0
  50. package/src/dev-runtime/sync.ts +134 -22
  51. package/src/internal/api.ts +16 -1
  52. package/src/server/index.ts +8 -1
  53. package/dist/runtime/api-DtX8qPrq.js.map +0 -1
  54. package/dist/runtime/descriptor-t6BOEGw9-C1rwYIlx.js.map +0 -1
  55. package/dist/runtime/id-D570vudg.js +0 -26
  56. package/dist/runtime/id-D570vudg.js.map +0 -1
  57. package/dist/runtime/pagination-BKPko9Hm.d.ts +0 -1
  58. package/dist/runtime/schema-D9RmboaS.js.map +0 -1
@@ -1,12 +1,109 @@
1
- import { a as InvocationId, c as SubscriptionId, i as IdAlphabet, l as TableId, n as DeploymentId, r as GeneratedId, s as RuntimeRequestNonce, t as AppId } from "./id-D570vudg.js";
2
- import { d as failureFromWire, f as inspectResult, l as Result$1, o as PaginationPageSize, p as pending, t as ValueDescriptor, v as decodeTransportValue, y as encodeTransportObject } from "./descriptor-t6BOEGw9-C1rwYIlx.js";
3
- import { a as FunctionAddress, c as ServerMessageJson, d as WireSuccess, f as AppStateRevision, i as DeploymentChangedCloseCode, l as WireFailure, m as InvocationKey, n as functionPathOf, o as Operation, p as DeploymentGeneration, r as ClientMessageJson, s as ProtocolErrorCode, u as WireResult } from "./api-DtX8qPrq.js";
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-BKbdenuU.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
4
  import { Array as Array$1, Cause, Context, Deferred, Duration, Effect, ErrorReporter, Fiber, HashMap, Layer, ManagedRuntime, Predicate, Schedule, Schema, String } from "effect";
5
+ import { customAlphabet } from "nanoid";
5
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
7
  import * as Socket from "effect/unstable/socket/Socket";
7
8
  import * as BrowserCrypto from "@effect/platform-browser/BrowserCrypto";
8
- import { customAlphabet } from "nanoid";
9
9
  import { Component, Fragment, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray } from "preact";
10
+ //#region ../shared/dist/id.js
11
+ const decodeGenerated = (definition, payload) => Schema.decodeSync(definition)(definition.idPrefix === void 0 ? payload : `${definition.idPrefix}_${payload}`);
12
+ var IdGenerator = class IdGenerator extends Context.Service()("@ignotum/shared/id/IdGenerator") {
13
+ static layer = Layer.sync(IdGenerator, () => {
14
+ const generatePayload = customAlphabet(IdAlphabet, 24);
15
+ return IdGenerator.of({ generate: (definition) => Effect.sync(() => decodeGenerated(definition, generatePayload())) });
16
+ });
17
+ static deterministic = (startAt = 1) => Layer.sync(IdGenerator, () => {
18
+ const counters = /* @__PURE__ */ new Map();
19
+ return IdGenerator.of({ generate: (definition) => Effect.sync(() => {
20
+ const counter = (counters.get(definition.idPrefix) ?? startAt - 1) + 1;
21
+ counters.set(definition.idPrefix, counter);
22
+ return decodeGenerated(definition, counter.toString(36).padStart(24, "0"));
23
+ }) });
24
+ });
25
+ };
26
+ //#endregion
27
+ //#region src/client/files.ts
28
+ const Files = { url: (file) => {
29
+ const url = fileGrantUrlOf(file);
30
+ if (url === void 0) throw new Error("This FileValue has no active query grant.");
31
+ return url;
32
+ } };
33
+ const formatByMime = {};
34
+ for (const format of fileFormats) formatByMime[fileMimeTypes[format]] = format;
35
+ const collectNativeFiles = (value, files) => {
36
+ if (value instanceof File) {
37
+ files.push(value);
38
+ return;
39
+ }
40
+ if (isFileValue(value) || Predicate.isDate(value)) return;
41
+ if (globalThis.Array.isArray(value)) {
42
+ for (const child of value) collectNativeFiles(child, files);
43
+ return;
44
+ }
45
+ if (!Predicate.isObject(value)) return;
46
+ for (const child of Object.values(value)) collectNativeFiles(child, files);
47
+ };
48
+ const replaceNativeFiles = (value, replacements) => {
49
+ if (value instanceof File) return replacements.get(value);
50
+ if (isFileValue(value) || Predicate.isDate(value)) return value;
51
+ if (globalThis.Array.isArray(value)) return value.map((child) => replaceNativeFiles(child, replacements));
52
+ if (!Predicate.isObject(value)) return value;
53
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, replaceNativeFiles(child, replacements)]));
54
+ };
55
+ const prepareMutationArguments = Effect.fn("SyncClient.prepareMutationArguments")(function* (input) {
56
+ 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.`);
62
+ const replacements = /* @__PURE__ */ new Map();
63
+ const nativeFiles = /* @__PURE__ */ new Map();
64
+ 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.`);
69
+ const id = yield* ids.generate(FileId);
70
+ replacements.set(file, makeFileValue(id, {
71
+ format,
72
+ name: file.name,
73
+ size: file.size
74
+ }));
75
+ nativeFiles.set(id, file);
76
+ }
77
+ return {
78
+ value: replaceNativeFiles(input, replacements),
79
+ nativeFiles,
80
+ fileIds: Array.from(nativeFiles.keys())
81
+ };
82
+ });
83
+ const applyFileGrants = (value, grants) => {
84
+ const byId = /* @__PURE__ */ new Map();
85
+ for (const grant of grants) {
86
+ if (byId.has(grant.id)) throw new Error("A query file grant ID is duplicated.");
87
+ byId.set(grant.id, grant.url);
88
+ }
89
+ const matched = /* @__PURE__ */ new Set();
90
+ const visit = (current) => {
91
+ if (isFileValue(current)) {
92
+ const id = fileIdOf(current);
93
+ const url = byId.get(id);
94
+ if (url === void 0) return current;
95
+ matched.add(id);
96
+ return grantFileValue(current, url);
97
+ }
98
+ if (globalThis.Array.isArray(current)) return current.map(visit);
99
+ if (!Predicate.isObject(current) || Predicate.isDate(current)) return current;
100
+ return Object.fromEntries(Object.entries(current).map(([key, child]) => [key, visit(child)]));
101
+ };
102
+ const granted = visit(value);
103
+ if (matched.size !== byId.size) throw new Error("A query file grant has no result value.");
104
+ return granted;
105
+ };
106
+ //#endregion
10
107
  //#region ../contracts/dist/json.js
11
108
  const encodeScalar = (value) => Schema.decodeSync(Schema.String)(JSON.stringify(value));
12
109
  const encodeCanonicalJson = (value) => {
@@ -37,8 +134,8 @@ var InvalidServerMessage = class extends Schema.TaggedError()("InvalidServerMess
37
134
  cause: Schema.Defect(),
38
135
  message: Schema.String
39
136
  }) {};
40
- var ServerProtocolError = class extends Schema.TaggedError()("ServerProtocolError", {
41
- code: ProtocolErrorCode,
137
+ var ServerError = class extends Schema.TaggedError()("ServerError", {
138
+ code: ErrorCode,
42
139
  message: Schema.String,
43
140
  operation: Schema.optional(Operation)
44
141
  }) {};
@@ -47,7 +144,7 @@ const ClientInfrastructureError = Schema.Union([
47
144
  InvalidClientMessage,
48
145
  InvalidMutationArguments,
49
146
  InvalidServerMessage,
50
- ServerProtocolError
147
+ ServerError
51
148
  ]);
52
149
  const browserErrorReporter = ErrorReporter.make(({ attributes, error }) => {
53
150
  globalThis.console.error("Ignotum infrastructure error", error, attributes);
@@ -114,13 +211,41 @@ const RuntimeQueryResult = Schema.Struct({
114
211
  type: Schema.Literal("Query"),
115
212
  result: WireResult,
116
213
  dependencies: DependencySet,
117
- observedRevision: AppStateRevision
214
+ observedRevision: AppStateRevision,
215
+ files: Schema.optional(Schema.Array(RuntimeFileOccurrence))
216
+ });
217
+ const FileReferenceAdded = Schema.Struct({
218
+ type: Schema.Literal("AddReference"),
219
+ file: RuntimeFileMetadata,
220
+ tableId: TableId,
221
+ documentId: GeneratedId
222
+ });
223
+ const FileReferenceRemoved = Schema.Struct({
224
+ type: Schema.Literal("RemoveReference"),
225
+ fileId: FileId,
226
+ tableId: TableId,
227
+ documentId: GeneratedId
228
+ });
229
+ const FileReferenceEffect = Schema.Union([FileReferenceAdded, FileReferenceRemoved]);
230
+ const FileEffectBatch = Schema.Struct({
231
+ id: InvocationId,
232
+ committedRevision: AppStateRevision,
233
+ effects: Schema.Array(FileReferenceEffect)
234
+ });
235
+ Schema.Struct({ appId: AppId });
236
+ Schema.Struct({ batches: Schema.Array(FileEffectBatch) });
237
+ Schema.Struct({
238
+ appId: AppId,
239
+ invocationId: InvocationId,
240
+ acknowledgedAt: Schema.Int
118
241
  });
242
+ Schema.Struct({ completed: Schema.Literal(true) });
119
243
  const RuntimeMutationSuccess = Schema.Struct({
120
244
  type: Schema.Literal("Mutation"),
121
245
  result: WireSuccess,
122
246
  invalidations: InvalidationSet,
123
- committedRevision: AppStateRevision
247
+ committedRevision: AppStateRevision,
248
+ fileEffects: Schema.optional(FileEffectBatch)
124
249
  });
125
250
  const RuntimeMutationFailure = Schema.Struct({
126
251
  type: Schema.Literal("Mutation"),
@@ -143,7 +268,8 @@ const QueryInvocation = Schema.Struct({
143
268
  const MutationInvocation = Schema.Struct({
144
269
  ...InvocationBase,
145
270
  type: Schema.Literal("Mutation"),
146
- invocationId: InvocationId
271
+ invocationId: InvocationId,
272
+ files: Schema.optional(Schema.Array(RuntimeFileOccurrence))
147
273
  });
148
274
  Schema.Union([QueryInvocation, MutationInvocation]);
149
275
  Schema.Struct({
@@ -182,23 +308,6 @@ Schema.Union([
182
308
  RuntimeInvocationUnavailable
183
309
  ]);
184
310
  //#endregion
185
- //#region ../shared/dist/id.js
186
- const decodeGenerated = (definition, payload) => Schema.decodeSync(definition)(definition.idPrefix === void 0 ? payload : `${definition.idPrefix}_${payload}`);
187
- var IdGenerator = class IdGenerator extends Context.Service()("@ignotum/shared/id/IdGenerator") {
188
- static layer = Layer.sync(IdGenerator, () => {
189
- const generatePayload = customAlphabet(IdAlphabet, 24);
190
- return IdGenerator.of({ generate: (definition) => Effect.sync(() => decodeGenerated(definition, generatePayload())) });
191
- });
192
- static deterministic = (startAt = 1) => Layer.sync(IdGenerator, () => {
193
- const counters = /* @__PURE__ */ new Map();
194
- return IdGenerator.of({ generate: (definition) => Effect.sync(() => {
195
- const counter = (counters.get(definition.idPrefix) ?? startAt - 1) + 1;
196
- counters.set(definition.idPrefix, counter);
197
- return decodeGenerated(definition, counter.toString(36).padStart(24, "0"));
198
- }) });
199
- });
200
- };
201
- //#endregion
202
311
  //#region ../contracts/dist/runtime/functions.js
203
312
  const FunctionKind = Schema.Literals(["Mutation", "Query"]);
204
313
  Schema.TaggedError()("UnknownFunction", {
@@ -253,24 +362,27 @@ const ClientRoute = Schema.Struct({
253
362
  pathname: ClientPath,
254
363
  artifact: ArtifactReference
255
364
  });
256
- Schema.Struct({
365
+ const DeploymentInventoryV1 = Schema.Struct({
257
366
  formatVersion: Schema.Literal(1),
258
367
  files: Schema.Array(ArtifactFile)
259
368
  });
260
- Schema.Struct({
369
+ initial(DeploymentInventoryV1);
370
+ const ClientManifestV1 = Schema.Struct({
261
371
  formatVersion: Schema.Literal(1),
262
372
  shell: ArtifactReference,
263
373
  routes: Schema.Array(ClientRoute)
264
374
  });
375
+ initial(ClientManifestV1);
265
376
  const ClientRoutingRoute = Schema.Struct({
266
377
  pathname: ClientPath,
267
378
  artifact: ArtifactPath
268
379
  });
269
- Schema.Struct({
380
+ const ClientRoutingV1 = Schema.Struct({
270
381
  formatVersion: Schema.Literal(1),
271
382
  shell: ArtifactPath,
272
383
  routes: Schema.Array(ClientRoutingRoute)
273
384
  });
385
+ initial(ClientRoutingV1);
274
386
  const SchemaSnapshotField = Schema.Struct({
275
387
  name: Schema.String,
276
388
  value: ValueDescriptor
@@ -279,26 +391,60 @@ const SchemaSnapshotIndex = Schema.Struct({
279
391
  name: Schema.String,
280
392
  fields: Schema.Array(Schema.String)
281
393
  });
394
+ const SchemaSnapshotTableV1 = Schema.Struct({
395
+ name: Schema.String,
396
+ fields: Schema.Array(SchemaSnapshotField)
397
+ });
398
+ const SchemaSnapshotV1 = Schema.Struct({
399
+ formatVersion: Schema.Literal(1),
400
+ tables: Schema.Array(SchemaSnapshotTableV1)
401
+ });
282
402
  const SchemaSnapshotTable = Schema.Struct({
283
403
  name: Schema.String,
284
404
  fields: Schema.Array(SchemaSnapshotField),
285
405
  indexes: Schema.Array(SchemaSnapshotIndex)
286
406
  });
287
- Schema.Struct({
407
+ const SchemaSnapshotV2 = Schema.Struct({
288
408
  formatVersion: Schema.Literal(2),
289
409
  tables: Schema.Array(SchemaSnapshotTable)
290
410
  });
411
+ upgrade(initial(SchemaSnapshotV1), SchemaSnapshotV2, (snapshot) => ({
412
+ formatVersion: 2,
413
+ tables: snapshot.tables.map((table) => ({
414
+ ...table,
415
+ indexes: []
416
+ }))
417
+ }));
418
+ const ServerFunctionArtifactV1 = Schema.Struct({
419
+ address: FunctionAddress,
420
+ kind: FunctionKind,
421
+ bundle: ArtifactReference,
422
+ sourceMap: ArtifactReference
423
+ });
291
424
  const ServerFunctionArtifact = Schema.Struct({
292
425
  address: FunctionAddress,
293
426
  kind: FunctionKind,
427
+ args: Schema.optional(ValueDescriptor),
428
+ returns: Schema.optional(ValueDescriptor),
429
+ errors: Schema.optional(ValueDescriptor),
294
430
  bundle: ArtifactReference,
295
431
  sourceMap: ArtifactReference
296
432
  });
297
- Schema.Struct({
433
+ const ServerBuildManifestV1 = Schema.Struct({
298
434
  formatVersion: Schema.Literal(1),
299
435
  schema: ArtifactReference,
436
+ functions: Schema.Array(ServerFunctionArtifactV1)
437
+ });
438
+ const ServerBuildManifestV2 = Schema.Struct({
439
+ formatVersion: Schema.Literal(2),
440
+ schema: ArtifactReference,
300
441
  functions: Schema.Array(ServerFunctionArtifact)
301
442
  });
443
+ upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
444
+ formatVersion: 2,
445
+ schema: manifest.schema,
446
+ functions: manifest.functions
447
+ }));
302
448
  ArtifactPath.make("inventory.json");
303
449
  ArtifactPath.make("client/manifest.json");
304
450
  ArtifactPath.make("client/shell.html");
@@ -312,12 +458,16 @@ const runtimeInvocationPath = "/v1/invoke";
312
458
  const runtimeRevisionPath = "/v1/revision";
313
459
  const runtimeIndexPreparePath = "/v1/indexes/prepare";
314
460
  const runtimeIndexCommitPath = "/v1/indexes/commit";
461
+ const runtimeFileEffectsPath = "/v1/files/effects";
462
+ const runtimeFileEffectsAcknowledgePath = "/v1/files/effects/acknowledge";
315
463
  const appSyncPath = `/_ignotum/v1/sync`;
316
464
  const RuntimeRequestPath = Schema.Literals([
317
465
  runtimeInvocationPath,
318
466
  runtimeRevisionPath,
319
467
  runtimeIndexPreparePath,
320
- runtimeIndexCommitPath
468
+ runtimeIndexCommitPath,
469
+ runtimeFileEffectsPath,
470
+ runtimeFileEffectsAcknowledgePath
321
471
  ]);
322
472
  const RuntimeRequestTimestamp = Schema.FiniteFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ignotum/runtime/RequestTimestamp"));
323
473
  const RuntimeRequestSignature = Sha256.pipe(Schema.brand("ignotum/runtime/RequestSignature"));
@@ -440,8 +590,8 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
440
590
  }
441
591
  };
442
592
  };
443
- const resultFromWire = Effect.fn("SyncClient.resultFromWire")((wire) => Effect.succeed(wire.type === "Success" ? Result$1.succeed(wire.value === void 0 ? void 0 : decodeTransportValue(wire.value, wire.dates ?? [])) : failureFromWire(wire.error, wire.dates)));
444
- const shouldReloadForCloseCode = (code) => code === DeploymentChangedCloseCode;
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)));
594
+ const shouldReloadForCloseCode = (code) => code === DeploymentCloseCode;
445
595
  const handshakesMatch = (first, next) => first.appId === next.appId && first.deploymentId === next.deploymentId && first.generation === next.generation;
446
596
  const handshakeDecision = (remembered, acceptedOnConnection, next) => {
447
597
  if (remembered !== void 0 && !handshakesMatch(remembered, next)) return "Reload";
@@ -484,14 +634,19 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
484
634
  const pendingInvocation = HashMap.getUnsafe(invocations, id);
485
635
  if (pendingInvocation === void 0) return Effect.void;
486
636
  invocations = HashMap.remove(invocations, id);
487
- return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingInvocation.deferred, error)), Effect.asVoid);
637
+ return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingInvocation.prepared, error)), Effect.andThen(Deferred.fail(pendingInvocation.deferred, error)), Effect.asVoid);
488
638
  };
489
639
  const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (message) {
490
640
  switch (message.type) {
491
641
  case "Snapshot":
492
642
  if (queryCache.getById(message.id) === void 0) return;
493
- yield* resultFromWire(message.result).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))));
643
+ yield* resultFromWire(message.result, message.files ?? []).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))));
494
644
  return;
645
+ case "Preparation": {
646
+ const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
647
+ if (pendingInvocation !== void 0) yield* Deferred.succeed(pendingInvocation.prepared, message.uploads);
648
+ return;
649
+ }
495
650
  case "Result": {
496
651
  const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
497
652
  if (pendingInvocation === void 0) return;
@@ -499,11 +654,11 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
499
654
  yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => Deferred.succeed(pendingInvocation.deferred, result)));
500
655
  return;
501
656
  }
502
- case "ProtocolError": {
503
- const error = message.operation === void 0 ? ServerProtocolError.make({
657
+ case "Error": {
658
+ const error = message.operation === void 0 ? ServerError.make({
504
659
  code: message.code,
505
660
  message: message.message
506
- }) : ServerProtocolError.make({
661
+ }) : ServerError.make({
507
662
  code: message.code,
508
663
  message: message.message,
509
664
  operation: message.operation
@@ -557,7 +712,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
557
712
  message: "The server returned an invalid sync message."
558
713
  })));
559
714
  if (message.type === "Handshake") return yield* acceptHandshake(message);
560
- if (message.type === "DeploymentChanged") {
715
+ if (message.type === "Deployment") {
561
716
  requestReload();
562
717
  return;
563
718
  }
@@ -581,24 +736,103 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
581
736
  });
582
737
  const mutate = Effect.fn("SyncClient.mutate")(function* (functionAddress, args) {
583
738
  const invocationId = yield* ids.generate(InvocationId);
739
+ const preparedArguments = yield* prepareMutationArguments(args).pipe(Effect.provideService(IdGenerator, ids), Effect.mapError((cause) => InvalidMutationArguments.make({
740
+ cause,
741
+ function: functionAddress,
742
+ message: `Mutation arguments for ${functionAddress} contain an unsupported file or value.`
743
+ })), Effect.tapError(reportClientError));
744
+ const jsonArgs = yield* Effect.try({
745
+ try: () => encodeTransportObject(preparedArguments.value),
746
+ catch: (cause) => InvalidMutationArguments.make({
747
+ cause,
748
+ function: functionAddress,
749
+ message: `Mutation arguments for ${functionAddress} contain an unsupported value.`
750
+ })
751
+ }).pipe(Effect.tapError(reportClientError));
584
752
  const deferred = yield* Deferred.make();
585
- const message = {
753
+ const prepared = yield* Deferred.make();
754
+ const invokeMessage = {
586
755
  type: "Invoke",
587
756
  id: invocationId,
588
757
  kind: "Mutation",
589
758
  function: functionAddress,
590
- args
759
+ args: jsonArgs
760
+ };
761
+ const message = preparedArguments.fileIds.length === 0 ? invokeMessage : {
762
+ type: "Prepare",
763
+ id: invocationId,
764
+ kind: "Mutation",
765
+ function: functionAddress,
766
+ args: jsonArgs,
767
+ files: preparedArguments.fileIds
591
768
  };
592
769
  invocations = HashMap.set(invocations, invocationId, {
593
770
  deferred,
594
771
  function: functionAddress,
595
- message
772
+ message,
773
+ prepared
596
774
  });
597
- if (writer !== void 0) yield* sendWith(writer, message).pipe(Effect.catchTags({
598
- ConnectionUnavailable: reportClientError,
599
- InvalidClientMessage: (error) => rejectInvocation(invocationId, error)
600
- }));
601
- return yield* Deferred.await(deferred);
775
+ return yield* Effect.gen(function* () {
776
+ if (writer !== void 0) yield* sendWith(writer, message).pipe(Effect.catchTags({
777
+ ConnectionUnavailable: reportClientError,
778
+ InvalidClientMessage: (error) => rejectInvocation(invocationId, error)
779
+ }));
780
+ if (message.type === "Prepare") {
781
+ const uploads = yield* Deferred.await(prepared);
782
+ const expected = new Set(message.files);
783
+ const received = /* @__PURE__ */ new Set();
784
+ for (const upload of uploads) {
785
+ if (!expected.has(upload.id) || received.has(upload.id)) return yield* InvalidServerMessage.make({
786
+ cause: /* @__PURE__ */ new Error(`Unexpected prepared file '${upload.id}'.`),
787
+ message: "The server returned invalid application file preparation data."
788
+ });
789
+ received.add(upload.id);
790
+ const file = preparedArguments.nativeFiles.get(upload.id);
791
+ if (file === void 0) return yield* InvalidServerMessage.make({
792
+ cause: /* @__PURE__ */ new Error(`Missing native file '${upload.id}'.`),
793
+ message: "The server prepared an unknown application file."
794
+ });
795
+ if (upload.url === void 0) continue;
796
+ const uploadUrl = upload.url;
797
+ const response = yield* Effect.tryPromise({
798
+ try: () => fetch(uploadUrl, {
799
+ method: "PUT",
800
+ body: file,
801
+ headers: { "content-type": file.type }
802
+ }),
803
+ catch: (cause) => ConnectionUnavailable.make({
804
+ cause,
805
+ message: `File '${file.name}' could not be uploaded.`
806
+ })
807
+ });
808
+ if (!response.ok) {
809
+ const detail = yield* Effect.promise(() => response.text());
810
+ return yield* InvalidMutationArguments.make({
811
+ cause: new Error(detail),
812
+ function: functionAddress,
813
+ message: `File '${file.name}' was rejected by application storage.`
814
+ });
815
+ }
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."
820
+ });
821
+ const pendingInvocation = HashMap.getUnsafe(invocations, invocationId);
822
+ if (pendingInvocation === void 0) return yield* Deferred.await(deferred);
823
+ invocations = HashMap.set(invocations, invocationId, {
824
+ ...pendingInvocation,
825
+ message: invokeMessage
826
+ });
827
+ if (writer !== void 0) yield* sendWith(writer, invokeMessage).pipe(Effect.catchTags({
828
+ ConnectionUnavailable: reportClientError,
829
+ InvalidClientMessage: (error) => rejectInvocation(invocationId, error)
830
+ }));
831
+ }
832
+ return yield* Deferred.await(deferred);
833
+ }).pipe(Effect.tapError(() => Effect.sync(() => {
834
+ invocations = HashMap.remove(invocations, invocationId);
835
+ })));
602
836
  });
603
837
  return SyncClient.of({
604
838
  mutate,
@@ -801,16 +1035,7 @@ const useMutation = (reference) => useMemo$1(() => {
801
1035
  const functionPath = functionPathOf(reference);
802
1036
  const input = args === void 0 ? {} : args;
803
1037
  return syncRuntime.runPromise(Effect.gen(function* () {
804
- const client = yield* SyncClient;
805
- const jsonArgs = yield* Effect.try({
806
- try: () => encodeTransportObject(input),
807
- catch: (cause) => InvalidMutationArguments.make({
808
- cause,
809
- function: functionPath,
810
- message: `Mutation arguments for ${functionPath} contain an unsupported value.`
811
- })
812
- }).pipe(Effect.tapError(reportClientError));
813
- return yield* client.mutate(functionPath, jsonArgs);
1038
+ return yield* (yield* SyncClient).mutate(functionPath, input);
814
1039
  }));
815
1040
  };
816
1041
  return mutate;
@@ -825,6 +1050,6 @@ const app = (definition) => {
825
1050
  //#region src/client/index.ts
826
1051
  const Result = { match: Result$1.match };
827
1052
  //#endregion
828
- export { Component, 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 };
1053
+ 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 };
829
1054
 
830
1055
  //# sourceMappingURL=client.js.map