ignotum 0.0.2 → 0.0.4

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 (64) hide show
  1. package/README.md +23 -7
  2. package/dist/cli/bin.mjs +32313 -751
  3. package/dist/cli/bin.mjs.map +1 -1
  4. package/dist/runtime/{api-Cpx57mk3.d.ts → api-D2bsAz4T.d.ts} +3 -2
  5. package/dist/runtime/{api-BXXIZc_Q.js → api-DtX8qPrq.js} +58 -27
  6. package/dist/runtime/api-DtX8qPrq.js.map +1 -0
  7. package/dist/runtime/client.d.ts +11 -4
  8. package/dist/runtime/client.js +343 -79
  9. package/dist/runtime/client.js.map +1 -1
  10. package/dist/runtime/{result-B2W-z2wG.js → descriptor-t6BOEGw9-BTHR-ZMv.js} +119 -4
  11. package/dist/runtime/descriptor-t6BOEGw9-BTHR-ZMv.js.map +1 -0
  12. package/dist/runtime/id-Btwac71X-B9OeBzvq.d.ts +9 -0
  13. package/dist/runtime/id-D570vudg.js +26 -0
  14. package/dist/runtime/id-D570vudg.js.map +1 -0
  15. package/dist/runtime/{index-BgWROoyk.d.ts → index-Dl_VQGmA.d.ts} +114 -31
  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 +44 -0
  19. package/dist/runtime/internal/host.js +213 -0
  20. package/dist/runtime/internal/host.js.map +1 -0
  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/{result-C1ZdsM6Y.d.ts → result-BgcHn25t.d.ts} +21 -6
  26. package/dist/runtime/schema-B-jMZEbs.js +227 -0
  27. package/dist/runtime/schema-B-jMZEbs.js.map +1 -0
  28. package/dist/runtime/server.d.ts +2 -2
  29. package/dist/runtime/server.js +2 -2
  30. package/package.json +15 -5
  31. package/src/cli/agent-files.ts +6 -2
  32. package/src/cli/app-configuration.ts +135 -0
  33. package/src/cli/bin.ts +24 -1
  34. package/src/cli/build/artifact.ts +95 -0
  35. package/src/cli/build/client.ts +103 -0
  36. package/src/cli/build/server.ts +526 -0
  37. package/src/cli/client-config.ts +141 -0
  38. package/src/cli/client-entry.ts +152 -0
  39. package/src/cli/client-plugin.ts +86 -16
  40. package/src/cli/codegen.ts +5 -5
  41. package/src/cli/command.ts +41 -9
  42. package/src/cli/control-client.ts +343 -0
  43. package/src/cli/deploy.ts +217 -0
  44. package/src/cli/dev.ts +43 -108
  45. package/src/cli/module-boundaries.ts +183 -0
  46. package/src/cli/{new-project.ts → new-app.ts} +86 -86
  47. package/src/cli/package-manager.ts +9 -9
  48. package/src/client/app.ts +15 -0
  49. package/src/client/errors.ts +1 -17
  50. package/src/client/index.ts +2 -0
  51. package/src/client/sync.ts +172 -120
  52. package/src/dev-runtime/database.ts +253 -96
  53. package/src/dev-runtime/dev-database.ts +14 -14
  54. package/src/dev-runtime/functions.ts +57 -88
  55. package/src/dev-runtime/id.ts +2 -10
  56. package/src/dev-runtime/migrations.ts +68 -0
  57. package/src/dev-runtime/sync.ts +266 -83
  58. package/src/internal/function-definition.ts +77 -0
  59. package/src/internal/host.ts +1 -0
  60. package/src/internal/http-paths.ts +3 -1
  61. package/dist/runtime/api-BXXIZc_Q.js.map +0 -1
  62. package/dist/runtime/result-B2W-z2wG.js.map +0 -1
  63. package/dist/runtime/schema-CNEVLF7D.js +0 -116
  64. package/dist/runtime/schema-CNEVLF7D.js.map +0 -1
@@ -1,8 +1,11 @@
1
- import { a as decodeTransportValue, i as pending, n as Result$1, o as encodeTransportObject, r as failureFromWire } from "./result-B2W-z2wG.js";
2
- import { a as Operation, c as ServerMessageJson, i as FunctionAddress, l as SubscriptionId, n as functionPathOf, o as ProtocolErrorCode, r as ClientMessageJson, s as RequestId } from "./api-BXXIZc_Q.js";
3
- import { Cause, Context, Deferred, Duration, Effect, ErrorReporter, HashMap, Layer, ManagedRuntime, Predicate, Schedule, Schema } from "effect";
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 { g as encodeTransportObject, h as decodeTransportValue, l as failureFromWire, s as Result$1, t as ValueDescriptor, u as pending } from "./descriptor-t6BOEGw9-BTHR-ZMv.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";
4
+ import { Array, Cause, Context, Deferred, Duration, Effect, ErrorReporter, Fiber, HashMap, Layer, ManagedRuntime, Predicate, Schedule, Schema, String } from "effect";
4
5
  import { useCallback, 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";
5
6
  import * as Socket from "effect/unstable/socket/Socket";
7
+ import * as BrowserCrypto from "@effect/platform-browser/BrowserCrypto";
8
+ import { customAlphabet } from "nanoid";
6
9
  import { Component, Fragment, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray } from "preact";
7
10
  //#region src/client/errors.ts
8
11
  /** @effect-diagnostics globalConsole:skip-file */
@@ -28,19 +31,12 @@ var ServerProtocolError = class extends Schema.TaggedError()("ServerProtocolErro
28
31
  message: Schema.String,
29
32
  operation: Schema.optional(Operation)
30
33
  }) {};
31
- var MutationOutcomeUnknown = class extends Schema.TaggedError()("MutationOutcomeUnknown", {
32
- cause: Schema.optional(Schema.Defect()),
33
- function: FunctionAddress,
34
- id: RequestId,
35
- message: Schema.String
36
- }) {};
37
34
  const ClientInfrastructureError = Schema.Union([
38
35
  ConnectionUnavailable,
39
36
  InvalidClientMessage,
40
37
  InvalidMutationArguments,
41
38
  InvalidServerMessage,
42
- ServerProtocolError,
43
- MutationOutcomeUnknown
39
+ ServerProtocolError
44
40
  ]);
45
41
  const browserErrorReporter = ErrorReporter.make(({ attributes, error }) => {
46
42
  globalThis.console.error("Ignotum infrastructure error", error, attributes);
@@ -58,15 +54,239 @@ var QuerySkipToken = class {
58
54
  const skip = Object.freeze(new QuerySkipToken());
59
55
  const Query = { skip };
60
56
  const isQuerySkip = (value) => value === skip;
61
- const syncPath = `/_ignotum/sync`;
62
57
  //#endregion
63
- //#region src/client/sync.ts
64
- const canonicalJson = (value) => {
65
- if (Predicate.isNull(value) || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return JSON.stringify(value);
66
- if (globalThis.Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
67
- return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => `${JSON.stringify(key)}:${canonicalJson(child)}`).join(",")}}`;
58
+ //#region ../contracts/dist/json.js
59
+ const encodeScalar = (value) => Schema.decodeSync(Schema.String)(JSON.stringify(value));
60
+ const encodeCanonicalJson = (value) => {
61
+ if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return encodeScalar(value);
62
+ if (Predicate.isObject(value)) return `{${Array.map(Array.sort(String.Order)(Object.keys(value)), (key) => {
63
+ const field = Schema.decodeUnknownSync(Schema.Json)(value[key]);
64
+ return `${encodeScalar(key)}:${encodeCanonicalJson(field)}`;
65
+ }).join(",")}}`;
66
+ return `[${Schema.decodeUnknownSync(Schema.Array(Schema.Json))(value).map(encodeCanonicalJson).join(",")}]`;
67
+ };
68
+ Schema.String.pipe(Schema.brand("ignotum/hosted/QueryKey"));
69
+ const TableDependency = Schema.Struct({
70
+ type: Schema.Literal("Table"),
71
+ tableId: TableId
72
+ });
73
+ const DocumentDependency = Schema.Struct({
74
+ type: Schema.Literal("Document"),
75
+ tableId: TableId,
76
+ id: GeneratedId
77
+ });
78
+ const DependencyKey = Schema.Union([TableDependency, DocumentDependency]);
79
+ const DependencySet = Schema.Array(DependencyKey);
80
+ const InvalidationSet = Schema.Array(DependencyKey);
81
+ const RuntimeQueryResult = Schema.Struct({
82
+ type: Schema.Literal("Query"),
83
+ result: WireResult,
84
+ dependencies: DependencySet,
85
+ observedRevision: AppStateRevision
86
+ });
87
+ const RuntimeMutationSuccess = Schema.Struct({
88
+ type: Schema.Literal("Mutation"),
89
+ result: WireSuccess,
90
+ invalidations: InvalidationSet,
91
+ committedRevision: AppStateRevision
92
+ });
93
+ const RuntimeMutationFailure = Schema.Struct({
94
+ type: Schema.Literal("Mutation"),
95
+ result: WireFailure
96
+ });
97
+ const RuntimeMutationResult = Schema.Union([RuntimeMutationSuccess, RuntimeMutationFailure]);
98
+ Schema.Union([RuntimeQueryResult, RuntimeMutationResult]);
99
+ const InvocationBase = {
100
+ appId: AppId,
101
+ deploymentId: DeploymentId,
102
+ generation: DeploymentGeneration,
103
+ invocationKey: InvocationKey,
104
+ function: FunctionAddress,
105
+ args: Schema.Json
68
106
  };
69
- const queryKey = (functionAddress, args) => `${functionAddress}:${canonicalJson(args)}`;
107
+ const QueryInvocation = Schema.Struct({
108
+ ...InvocationBase,
109
+ type: Schema.Literal("Query")
110
+ });
111
+ const MutationInvocation = Schema.Struct({
112
+ ...InvocationBase,
113
+ type: Schema.Literal("Mutation"),
114
+ invocationId: InvocationId
115
+ });
116
+ Schema.Union([QueryInvocation, MutationInvocation]);
117
+ Schema.Struct({
118
+ appId: AppId,
119
+ expectedRevision: AppStateRevision
120
+ });
121
+ Schema.Struct({ currentRevision: AppStateRevision });
122
+ var RuntimeDeploymentUnavailable = class extends Schema.TaggedError()("RuntimeDeploymentUnavailable", {
123
+ deploymentId: DeploymentId,
124
+ message: Schema.String
125
+ }) {};
126
+ const RuntimeInvocationRejectionCode = Schema.Literals([
127
+ "FunctionUnavailable",
128
+ "InvalidArguments",
129
+ "InvocationIdConflict",
130
+ "ResourceLimitExceeded",
131
+ "UnknownFunction",
132
+ "WrongFunctionKind"
133
+ ]);
134
+ var RuntimeInvocationRejected = class extends Schema.TaggedError()("RuntimeInvocationRejected", {
135
+ code: RuntimeInvocationRejectionCode,
136
+ invocationKey: InvocationKey,
137
+ message: Schema.String
138
+ }) {};
139
+ var RuntimeInvocationUnavailable = class extends Schema.TaggedError()("RuntimeInvocationUnavailable", {
140
+ invocationKey: InvocationKey,
141
+ message: Schema.String
142
+ }) {};
143
+ Schema.TaggedError()("RuntimeRevisionRejected", {
144
+ appId: AppId,
145
+ message: Schema.String
146
+ });
147
+ Schema.Union([
148
+ RuntimeDeploymentUnavailable,
149
+ RuntimeInvocationRejected,
150
+ RuntimeInvocationUnavailable
151
+ ]);
152
+ //#endregion
153
+ //#region ../shared/dist/id.js
154
+ const decodeGenerated = (definition, payload) => Schema.decodeSync(definition)(definition.idPrefix === void 0 ? payload : `${definition.idPrefix}_${payload}`);
155
+ var IdGenerator = class IdGenerator extends Context.Service()("@ignotum/shared/id/IdGenerator") {
156
+ static layer = Layer.sync(IdGenerator, () => {
157
+ const generatePayload = customAlphabet(IdAlphabet, 24);
158
+ return IdGenerator.of({ generate: (definition) => Effect.sync(() => decodeGenerated(definition, generatePayload())) });
159
+ });
160
+ static deterministic = (startAt = 1) => Layer.sync(IdGenerator, () => {
161
+ const counters = /* @__PURE__ */ new Map();
162
+ return IdGenerator.of({ generate: (definition) => Effect.sync(() => {
163
+ const counter = (counters.get(definition.idPrefix) ?? startAt - 1) + 1;
164
+ counters.set(definition.idPrefix, counter);
165
+ return decodeGenerated(definition, counter.toString(36).padStart(24, "0"));
166
+ }) });
167
+ });
168
+ };
169
+ //#endregion
170
+ //#region ../contracts/dist/runtime/functions.js
171
+ const FunctionKind = Schema.Literals(["Mutation", "Query"]);
172
+ Schema.TaggedError()("UnknownFunction", {
173
+ function: FunctionAddress,
174
+ message: Schema.String
175
+ });
176
+ Schema.TaggedError()("WrongFunctionKind", {
177
+ actual: FunctionKind,
178
+ expected: FunctionKind,
179
+ function: FunctionAddress,
180
+ message: Schema.String
181
+ });
182
+ Schema.TaggedError()("InvalidArguments", {
183
+ function: FunctionAddress,
184
+ message: Schema.String
185
+ });
186
+ Schema.TaggedError()("FunctionUnavailable", {
187
+ cause: Schema.Defect(),
188
+ function: FunctionAddress,
189
+ message: Schema.String
190
+ });
191
+ //#endregion
192
+ //#region ../contracts/dist/deployment.js
193
+ const ArtifactPath = Schema.String.check(Schema.isPattern(/^(?!\/)(?![A-Za-z]:\/)(?!.*(?:^|\/)\.\.(?:\/|$))(?!.*(?:^|\/)\.(?:\/|$))(?!.*\/\/)[^\\\0]+$/)).pipe(Schema.brand("ignotum/deployment/ArtifactPath"));
194
+ const Sha256 = Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/)).pipe(Schema.brand("ignotum/deployment/Sha256"));
195
+ const ArtifactKind = Schema.Literals([
196
+ "ClientAsset",
197
+ "ClientShell",
198
+ "FunctionBundle",
199
+ "ServerManifest",
200
+ "SourceMap",
201
+ "SchemaSnapshot"
202
+ ]);
203
+ const ArtifactReference = Schema.Struct({
204
+ path: ArtifactPath,
205
+ size: Schema.Natural,
206
+ sha256: Sha256
207
+ });
208
+ const ArtifactFile = Schema.Struct({
209
+ path: ArtifactPath,
210
+ size: Schema.Natural,
211
+ sha256: Sha256,
212
+ kind: ArtifactKind,
213
+ contentType: Schema.String,
214
+ contentEncoding: Schema.optional(Schema.String)
215
+ });
216
+ Schema.Struct({
217
+ formatVersion: Schema.Literal(1),
218
+ files: Schema.Array(ArtifactFile)
219
+ });
220
+ const SchemaSnapshotField = Schema.Struct({
221
+ name: Schema.String,
222
+ value: ValueDescriptor
223
+ });
224
+ const SchemaSnapshotTable = Schema.Struct({
225
+ name: Schema.String,
226
+ fields: Schema.Array(SchemaSnapshotField)
227
+ });
228
+ Schema.Struct({
229
+ formatVersion: Schema.Literal(1),
230
+ tables: Schema.Array(SchemaSnapshotTable)
231
+ });
232
+ const ServerFunctionArtifact = Schema.Struct({
233
+ address: FunctionAddress,
234
+ kind: FunctionKind,
235
+ bundle: ArtifactReference,
236
+ sourceMap: ArtifactReference
237
+ });
238
+ Schema.Struct({
239
+ formatVersion: Schema.Literal(1),
240
+ schema: ArtifactReference,
241
+ functions: Schema.Array(ServerFunctionArtifact)
242
+ });
243
+ ArtifactPath.make("inventory.json");
244
+ ArtifactPath.make("client/_shell.html");
245
+ ArtifactPath.make("server/manifest.json");
246
+ ArtifactPath.make("server/schema.json");
247
+ //#endregion
248
+ //#region ../contracts/dist/runtime/transport.js
249
+ const runtimeInvocationPath = "/v1/invoke";
250
+ const runtimeRevisionPath = "/v1/revision";
251
+ const appSyncPath = "/_ignotum/v1/sync";
252
+ const RuntimeRequestPath = Schema.Literals([runtimeInvocationPath, runtimeRevisionPath]);
253
+ const RuntimeRequestTimestamp = Schema.FiniteFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ignotum/runtime/RequestTimestamp"));
254
+ const RuntimeRequestSignature = Sha256.pipe(Schema.brand("ignotum/runtime/RequestSignature"));
255
+ Schema.Struct({
256
+ appId: AppId,
257
+ deploymentId: Schema.optional(DeploymentId),
258
+ nonce: RuntimeRequestNonce,
259
+ timestamp: RuntimeRequestTimestamp,
260
+ signature: RuntimeRequestSignature
261
+ });
262
+ Schema.Struct({
263
+ method: Schema.Literal("POST"),
264
+ path: RuntimeRequestPath,
265
+ timestamp: RuntimeRequestTimestamp,
266
+ nonce: RuntimeRequestNonce,
267
+ appId: AppId,
268
+ deploymentId: Schema.optional(DeploymentId),
269
+ bodySha256: Sha256
270
+ });
271
+ const RuntimeErrorCode = Schema.Literals([
272
+ "InvalidRequest",
273
+ "InvalidSignature",
274
+ "RequestExpired",
275
+ "RequestReplay",
276
+ "InvocationRejected",
277
+ "RuntimeUnavailable"
278
+ ]);
279
+ Schema.Struct({
280
+ code: RuntimeErrorCode,
281
+ message: Schema.String,
282
+ rejectionCode: Schema.optional(RuntimeInvocationRejectionCode)
283
+ });
284
+ //#endregion
285
+ //#region src/internal/http-paths.ts
286
+ const syncPath = appSyncPath;
287
+ //#endregion
288
+ //#region src/client/sync.ts
289
+ const queryKey = (functionAddress, args) => encodeCanonicalJson([functionAddress, args]);
70
290
  const subscribeMessage = (entry) => ({
71
291
  type: "Subscribe",
72
292
  id: entry.subscriptionId,
@@ -91,9 +311,10 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
91
311
  function: functionAddress,
92
312
  generation: 0,
93
313
  listeners: /* @__PURE__ */ new Set(),
314
+ revision: void 0,
94
315
  references: 0,
95
316
  result: pending(),
96
- subscriptionId: allocateId()
317
+ subscriptionId: yield* allocateId
97
318
  };
98
319
  queries.set(key, created);
99
320
  queryKeysById.set(created.subscriptionId, key);
@@ -134,20 +355,31 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
134
355
  entry.result = error;
135
356
  for (const listener of entry.listeners) listener();
136
357
  },
137
- setResult: (id, result) => {
358
+ setResult: (id, result, revision) => {
138
359
  const key = queryKeysById.get(id);
139
360
  const entry = key === void 0 ? void 0 : queries.get(key);
140
361
  if (entry === void 0) return;
362
+ if (entry.revision !== void 0 && revision < entry.revision) return;
363
+ entry.revision = revision;
141
364
  entry.result = result;
142
365
  for (const listener of entry.listeners) listener();
143
366
  }
144
367
  };
145
368
  };
146
369
  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)));
370
+ const shouldReloadForCloseCode = (code) => code === DeploymentChangedCloseCode;
371
+ const handshakesMatch = (first, next) => first.appId === next.appId && first.deploymentId === next.deploymentId && first.generation === next.generation;
372
+ const handshakeDecision = (remembered, acceptedOnConnection, next) => {
373
+ if (remembered !== void 0 && !handshakesMatch(remembered, next)) return "Reload";
374
+ return acceptedOnConnection ? "Ignore" : "Accept";
375
+ };
147
376
  const syncClientInternals = {
148
377
  makeQueryCache,
378
+ handshakeDecision,
149
379
  queryIdentity: queryKey,
150
- resultFromWire
380
+ resultFromWire,
381
+ handshakesMatch,
382
+ shouldReloadForCloseCode
151
383
  };
152
384
  const socketUrl = () => {
153
385
  const url = new URL(syncPath, globalThis.location.href);
@@ -156,8 +388,10 @@ const socketUrl = () => {
156
388
  };
157
389
  var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync/SyncClient") {
158
390
  static layer = Layer.effect(SyncClient, Effect.gen(function* () {
159
- let nextOperationId = 0;
160
- let mutations = HashMap.empty();
391
+ const ids = yield* IdGenerator;
392
+ let invocations = HashMap.empty();
393
+ let rememberedHandshake;
394
+ let reloadRequested = false;
161
395
  let writer;
162
396
  const encode = (message) => Schema.encodeEffect(ClientMessageJson)(message).pipe(Effect.mapError((cause) => InvalidClientMessage.make({
163
397
  cause,
@@ -168,31 +402,27 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
168
402
  message: "The sync connection was lost."
169
403
  })));
170
404
  const send = (message) => writer === void 0 ? Effect.fail(ConnectionUnavailable.make({ message: "The sync connection is not ready." })) : sendWith(writer, message);
171
- const queryCache = makeQueryCache(() => SubscriptionId.make(nextOperationId++), (message) => writer === void 0 ? Effect.void : send(message).pipe(Effect.catchTags({
405
+ const queryCache = makeQueryCache(ids.generate(SubscriptionId), (message) => writer === void 0 ? Effect.void : send(message).pipe(Effect.catchTags({
172
406
  ConnectionUnavailable: reportClientError,
173
407
  InvalidClientMessage: reportClientError
174
408
  })));
175
- const rejectMutation = (requestId, error) => {
176
- const pendingMutation = HashMap.getUnsafe(mutations, requestId);
177
- if (pendingMutation === void 0) return Effect.void;
178
- mutations = HashMap.remove(mutations, requestId);
179
- return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingMutation.deferred, error)), Effect.asVoid);
409
+ const rejectInvocation = (id, error) => {
410
+ const pendingInvocation = HashMap.getUnsafe(invocations, id);
411
+ if (pendingInvocation === void 0) return Effect.void;
412
+ invocations = HashMap.remove(invocations, id);
413
+ return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingInvocation.deferred, error)), Effect.asVoid);
180
414
  };
181
- const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (text) {
182
- const message = yield* Schema.decodeEffect(ServerMessageJson)(text).pipe(Effect.mapError((cause) => InvalidServerMessage.make({
183
- cause,
184
- message: "The server returned an invalid sync message."
185
- })));
415
+ const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (message) {
186
416
  switch (message.type) {
187
- case "QuerySnapshot":
417
+ case "Snapshot":
188
418
  if (queryCache.getById(message.id) === void 0) return;
189
- yield* resultFromWire(message.result).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result))));
419
+ yield* resultFromWire(message.result).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))));
190
420
  return;
191
- case "MutationResult": {
192
- const pendingMutation = HashMap.getUnsafe(mutations, message.id);
193
- if (pendingMutation === void 0) return;
194
- mutations = HashMap.remove(mutations, message.id);
195
- yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => Deferred.succeed(pendingMutation.deferred, result)));
421
+ case "Result": {
422
+ const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
423
+ if (pendingInvocation === void 0) return;
424
+ invocations = HashMap.remove(invocations, message.id);
425
+ yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => Deferred.succeed(pendingInvocation.deferred, result)));
196
426
  return;
197
427
  }
198
428
  case "ProtocolError": {
@@ -204,8 +434,8 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
204
434
  message: message.message,
205
435
  operation: message.operation
206
436
  });
207
- if (message.operation?.type === "Mutate") yield* rejectMutation(message.operation.id, error);
208
- else if (message.operation?.type === "Query") {
437
+ if (message.operation?.type === "Invocation") yield* rejectInvocation(message.operation.id, error);
438
+ else if (message.operation?.type === "Subscription") {
209
439
  queryCache.setError(message.operation.id, error);
210
440
  yield* reportClientError(error);
211
441
  } else yield* reportClientError(error);
@@ -213,27 +443,59 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
213
443
  }
214
444
  }
215
445
  });
216
- const disconnect = Effect.gen(function* () {
446
+ const requestReload = () => {
447
+ if (reloadRequested) return;
448
+ reloadRequested = true;
449
+ writer = void 0;
450
+ globalThis.location.reload();
451
+ };
452
+ const disconnect = Effect.sync(() => {
217
453
  writer = void 0;
218
- const current = mutations;
219
- mutations = HashMap.empty();
220
- yield* Effect.forEach(HashMap.entries(current), ([id, pendingMutation]) => {
221
- const error = MutationOutcomeUnknown.make({
222
- function: pendingMutation.function,
223
- id,
224
- message: "The connection closed before the mutation outcome was known."
225
- });
226
- return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingMutation.deferred, error)));
227
- });
228
454
  });
229
455
  const connect = Effect.scoped(Effect.gen(function* () {
230
- const socket = yield* Socket.makeWebSocket(socketUrl(), { closeCodeIsError: () => false });
456
+ if (reloadRequested) return yield* Effect.never;
457
+ const socket = yield* Socket.makeWebSocket(socketUrl(), { closeCodeIsError: (code) => {
458
+ if (shouldReloadForCloseCode(code)) requestReload();
459
+ return false;
460
+ } });
231
461
  const write = yield* socket.writer;
232
- const onOpen = Effect.gen(function* () {
462
+ const handshakeReceived = yield* Deferred.make();
463
+ let handshakeAccepted = false;
464
+ const acceptHandshake = Effect.fn("SyncClient.acceptHandshake")(function* (handshake) {
465
+ const decision = handshakeDecision(rememberedHandshake, handshakeAccepted, handshake);
466
+ if (decision === "Reload") {
467
+ requestReload();
468
+ return;
469
+ }
470
+ if (decision === "Ignore") return;
471
+ rememberedHandshake = handshake;
472
+ handshakeAccepted = true;
233
473
  writer = write;
234
- yield* Effect.forEach(queryCache.entries(), (entry) => sendWith(write, subscribeMessage(entry)));
235
- }).pipe(Effect.tapError(reportClientError), Effect.ignore);
236
- yield* socket.runString(handleMessage, { onOpen });
474
+ yield* Deferred.succeed(handshakeReceived, void 0);
475
+ yield* Effect.gen(function* () {
476
+ yield* Effect.forEach(queryCache.entries(), (entry) => sendWith(write, subscribeMessage(entry)));
477
+ yield* Effect.forEach(HashMap.values(invocations), (pendingInvocation) => sendWith(write, pendingInvocation.message));
478
+ }).pipe(Effect.tapError(reportClientError), Effect.ignore);
479
+ });
480
+ const handleText = Effect.fn("SyncClient.handleText")(function* (text) {
481
+ const message = yield* Schema.decodeEffect(ServerMessageJson)(text).pipe(Effect.mapError((cause) => InvalidServerMessage.make({
482
+ cause,
483
+ message: "The server returned an invalid sync message."
484
+ })));
485
+ if (message.type === "Handshake") return yield* acceptHandshake(message);
486
+ if (message.type === "DeploymentChanged") {
487
+ requestReload();
488
+ return;
489
+ }
490
+ if (!handshakeAccepted) return yield* InvalidServerMessage.make({
491
+ cause: /* @__PURE__ */ new Error("The server sent a sync message before the handshake."),
492
+ message: "The server returned a sync message before the handshake."
493
+ });
494
+ return yield* handleMessage(message);
495
+ });
496
+ const run = yield* socket.runString(handleText).pipe(Effect.forkChild);
497
+ yield* Effect.raceFirst(Deferred.await(handshakeReceived).pipe(Effect.timeout("10 seconds")), Fiber.join(run));
498
+ yield* Fiber.join(run);
237
499
  })).pipe(Effect.mapError((cause) => Schema.is(ClientInfrastructureError)(cause) ? cause : ConnectionUnavailable.make({
238
500
  cause,
239
501
  message: "The sync connection was lost."
@@ -244,37 +506,33 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
244
506
  return yield* queryCache.observe(functionAddress, args);
245
507
  });
246
508
  const mutate = Effect.fn("SyncClient.mutate")(function* (functionAddress, args) {
247
- const requestId = RequestId.make(nextOperationId++);
509
+ const invocationId = yield* ids.generate(InvocationId);
248
510
  const deferred = yield* Deferred.make();
249
- mutations = HashMap.set(mutations, requestId, {
250
- deferred,
251
- function: functionAddress
252
- });
253
511
  const message = {
254
- type: "Mutate",
255
- id: requestId,
512
+ type: "Invoke",
513
+ id: invocationId,
514
+ kind: "Mutation",
256
515
  function: functionAddress,
257
516
  args
258
517
  };
259
- yield* Effect.gen(function* () {
260
- const mutationWriter = writer;
261
- if (mutationWriter === void 0) return yield* ConnectionUnavailable.make({ message: "The sync connection is not ready." });
262
- return yield* sendWith(mutationWriter, message).pipe(Effect.mapError((error) => Schema.is(InvalidClientMessage)(error) ? error : MutationOutcomeUnknown.make({
263
- cause: error,
264
- function: functionAddress,
265
- id: requestId,
266
- message: "The connection failed while sending the mutation."
267
- })));
268
- }).pipe(Effect.tapError(reportClientError), Effect.tapError(() => Effect.sync(() => mutations = HashMap.remove(mutations, requestId))));
518
+ invocations = HashMap.set(invocations, invocationId, {
519
+ deferred,
520
+ function: functionAddress,
521
+ message
522
+ });
523
+ if (writer !== void 0) yield* sendWith(writer, message).pipe(Effect.catchTags({
524
+ ConnectionUnavailable: reportClientError,
525
+ InvalidClientMessage: (error) => rejectInvocation(invocationId, error)
526
+ }));
269
527
  return yield* Deferred.await(deferred);
270
528
  });
271
529
  return SyncClient.of({
272
530
  mutate,
273
531
  observe
274
532
  });
275
- })).pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal));
533
+ })).pipe(Layer.provide(Layer.merge(Socket.layerWebSocketConstructorGlobal, BrowserCrypto.layer)));
276
534
  };
277
- const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(clientErrorReporterLayer)));
535
+ const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(clientErrorReporterLayer), Layer.provideMerge(IdGenerator.layer)));
278
536
  const hot = import.meta.hot;
279
537
  if (hot !== void 0) hot.dispose(() => void syncRuntime.dispose());
280
538
  //#endregion
@@ -356,9 +614,15 @@ const useMutation = (reference) => useMemo$1(() => {
356
614
  return mutate;
357
615
  }, [reference]);
358
616
  //#endregion
617
+ //#region src/client/app.ts
618
+ const app = (definition) => {
619
+ if (definition.title.trim().length === 0) throw new Error("The app title must contain a non-whitespace character.");
620
+ return definition;
621
+ };
622
+ //#endregion
359
623
  //#region src/client/index.ts
360
624
  const Result = { match: Result$1.match };
361
625
  //#endregion
362
- export { Component, Fragment, Query, Result, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray, useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useMutation, useQuery, useReducer, useRef, useState };
626
+ export { Component, Fragment, Query, Result, app, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray, useCallback, useContext, useDebugValue, useEffect, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useMutation, useQuery, useReducer, useRef, useState };
363
627
 
364
628
  //# sourceMappingURL=client.js.map