ignotum 0.0.3 → 0.0.5

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 +26 -7
  2. package/dist/cli/bin.mjs +27353 -1490
  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 +352 -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-C6KO3ZdI.d.ts → index-Dl_VQGmA.d.ts} +90 -29
  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-wiFI7rgx.js → schema-B-jMZEbs.js} +95 -35
  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 +13 -3
  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 +111 -0
  36. package/src/cli/build/public.ts +182 -0
  37. package/src/cli/build/server.ts +527 -0
  38. package/src/cli/client-config.ts +148 -0
  39. package/src/cli/client-entry.ts +152 -0
  40. package/src/cli/client-plugin.ts +86 -16
  41. package/src/cli/codegen.ts +5 -5
  42. package/src/cli/command.ts +41 -9
  43. package/src/cli/control-client.ts +343 -0
  44. package/src/cli/deploy.ts +217 -0
  45. package/src/cli/dev.ts +43 -108
  46. package/src/cli/module-boundaries.ts +183 -0
  47. package/src/cli/{new-project.ts → new-app.ts} +86 -86
  48. package/src/cli/package-manager.ts +9 -9
  49. package/src/client/app.ts +15 -0
  50. package/src/client/errors.ts +1 -17
  51. package/src/client/index.ts +2 -0
  52. package/src/client/sync.ts +172 -120
  53. package/src/dev-runtime/database.ts +253 -96
  54. package/src/dev-runtime/dev-database.ts +14 -14
  55. package/src/dev-runtime/functions.ts +57 -88
  56. package/src/dev-runtime/id.ts +2 -10
  57. package/src/dev-runtime/migrations.ts +68 -0
  58. package/src/dev-runtime/sync.ts +266 -83
  59. package/src/internal/function-definition.ts +77 -0
  60. package/src/internal/host.ts +1 -0
  61. package/src/internal/http-paths.ts +3 -1
  62. package/dist/runtime/api-BXXIZc_Q.js.map +0 -1
  63. package/dist/runtime/result-B2W-z2wG.js.map +0 -1
  64. package/dist/runtime/schema-wiFI7rgx.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,248 @@ 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 ClientPath = Schema.String.check(Schema.isPattern(/^\/(?!_ignotum(?:\/|$))(?:(?:[A-Za-z0-9._~-]+\/)*[A-Za-z0-9._~-]+\/?)?$/)).pipe(Schema.brand("ignotum/deployment/ClientPath"));
195
+ const Sha256 = Schema.String.check(Schema.isPattern(/^[0-9a-f]{64}$/)).pipe(Schema.brand("ignotum/deployment/Sha256"));
196
+ const ArtifactKind = Schema.Literals([
197
+ "ClientAsset",
198
+ "ClientDocument",
199
+ "ClientPublicFile",
200
+ "ClientShell",
201
+ "FunctionBundle",
202
+ "ServerManifest",
203
+ "SourceMap",
204
+ "SchemaSnapshot"
205
+ ]);
206
+ const ArtifactReference = Schema.Struct({
207
+ path: ArtifactPath,
208
+ size: Schema.Natural,
209
+ sha256: Sha256
210
+ });
211
+ const ArtifactFile = Schema.Struct({
212
+ path: ArtifactPath,
213
+ size: Schema.Natural,
214
+ sha256: Sha256,
215
+ kind: ArtifactKind,
216
+ contentType: Schema.String,
217
+ contentEncoding: Schema.optional(Schema.String)
218
+ });
219
+ const ClientRoute = Schema.Struct({
220
+ pathname: ClientPath,
221
+ artifact: ArtifactPath
222
+ });
223
+ Schema.Struct({
224
+ formatVersion: Schema.Literal(1),
225
+ client: Schema.Struct({ routes: Schema.Array(ClientRoute) }),
226
+ files: Schema.Array(ArtifactFile)
227
+ });
228
+ const SchemaSnapshotField = Schema.Struct({
229
+ name: Schema.String,
230
+ value: ValueDescriptor
231
+ });
232
+ const SchemaSnapshotTable = Schema.Struct({
233
+ name: Schema.String,
234
+ fields: Schema.Array(SchemaSnapshotField)
235
+ });
236
+ Schema.Struct({
237
+ formatVersion: Schema.Literal(1),
238
+ tables: Schema.Array(SchemaSnapshotTable)
239
+ });
240
+ const ServerFunctionArtifact = Schema.Struct({
241
+ address: FunctionAddress,
242
+ kind: FunctionKind,
243
+ bundle: ArtifactReference,
244
+ sourceMap: ArtifactReference
245
+ });
246
+ Schema.Struct({
247
+ formatVersion: Schema.Literal(1),
248
+ schema: ArtifactReference,
249
+ functions: Schema.Array(ServerFunctionArtifact)
250
+ });
251
+ ArtifactPath.make("inventory.json");
252
+ ArtifactPath.make("client/_shell.html");
253
+ `${ArtifactPath.make("client/_ignotum/assets")}`;
254
+ ArtifactPath.make("server/manifest.json");
255
+ ArtifactPath.make("server/schema.json");
256
+ //#endregion
257
+ //#region ../contracts/dist/runtime/transport.js
258
+ const runtimeInvocationPath = "/v1/invoke";
259
+ const runtimeRevisionPath = "/v1/revision";
260
+ const appSyncPath = `/_ignotum/v1/sync`;
261
+ const RuntimeRequestPath = Schema.Literals([runtimeInvocationPath, runtimeRevisionPath]);
262
+ const RuntimeRequestTimestamp = Schema.FiniteFromString.pipe(Schema.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)), Schema.brand("ignotum/runtime/RequestTimestamp"));
263
+ const RuntimeRequestSignature = Sha256.pipe(Schema.brand("ignotum/runtime/RequestSignature"));
264
+ Schema.Struct({
265
+ appId: AppId,
266
+ deploymentId: Schema.optional(DeploymentId),
267
+ nonce: RuntimeRequestNonce,
268
+ timestamp: RuntimeRequestTimestamp,
269
+ signature: RuntimeRequestSignature
270
+ });
271
+ Schema.Struct({
272
+ method: Schema.Literal("POST"),
273
+ path: RuntimeRequestPath,
274
+ timestamp: RuntimeRequestTimestamp,
275
+ nonce: RuntimeRequestNonce,
276
+ appId: AppId,
277
+ deploymentId: Schema.optional(DeploymentId),
278
+ bodySha256: Sha256
279
+ });
280
+ const RuntimeErrorCode = Schema.Literals([
281
+ "InvalidRequest",
282
+ "InvalidSignature",
283
+ "RequestExpired",
284
+ "RequestReplay",
285
+ "InvocationRejected",
286
+ "RuntimeUnavailable"
287
+ ]);
288
+ Schema.Struct({
289
+ code: RuntimeErrorCode,
290
+ message: Schema.String,
291
+ rejectionCode: Schema.optional(RuntimeInvocationRejectionCode)
292
+ });
293
+ //#endregion
294
+ //#region src/internal/http-paths.ts
295
+ const syncPath = appSyncPath;
296
+ //#endregion
297
+ //#region src/client/sync.ts
298
+ const queryKey = (functionAddress, args) => encodeCanonicalJson([functionAddress, args]);
70
299
  const subscribeMessage = (entry) => ({
71
300
  type: "Subscribe",
72
301
  id: entry.subscriptionId,
@@ -91,9 +320,10 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
91
320
  function: functionAddress,
92
321
  generation: 0,
93
322
  listeners: /* @__PURE__ */ new Set(),
323
+ revision: void 0,
94
324
  references: 0,
95
325
  result: pending(),
96
- subscriptionId: allocateId()
326
+ subscriptionId: yield* allocateId
97
327
  };
98
328
  queries.set(key, created);
99
329
  queryKeysById.set(created.subscriptionId, key);
@@ -134,20 +364,31 @@ const makeQueryCache = (allocateId, sendLifecycleMessage) => {
134
364
  entry.result = error;
135
365
  for (const listener of entry.listeners) listener();
136
366
  },
137
- setResult: (id, result) => {
367
+ setResult: (id, result, revision) => {
138
368
  const key = queryKeysById.get(id);
139
369
  const entry = key === void 0 ? void 0 : queries.get(key);
140
370
  if (entry === void 0) return;
371
+ if (entry.revision !== void 0 && revision < entry.revision) return;
372
+ entry.revision = revision;
141
373
  entry.result = result;
142
374
  for (const listener of entry.listeners) listener();
143
375
  }
144
376
  };
145
377
  };
146
378
  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)));
379
+ const shouldReloadForCloseCode = (code) => code === DeploymentChangedCloseCode;
380
+ const handshakesMatch = (first, next) => first.appId === next.appId && first.deploymentId === next.deploymentId && first.generation === next.generation;
381
+ const handshakeDecision = (remembered, acceptedOnConnection, next) => {
382
+ if (remembered !== void 0 && !handshakesMatch(remembered, next)) return "Reload";
383
+ return acceptedOnConnection ? "Ignore" : "Accept";
384
+ };
147
385
  const syncClientInternals = {
148
386
  makeQueryCache,
387
+ handshakeDecision,
149
388
  queryIdentity: queryKey,
150
- resultFromWire
389
+ resultFromWire,
390
+ handshakesMatch,
391
+ shouldReloadForCloseCode
151
392
  };
152
393
  const socketUrl = () => {
153
394
  const url = new URL(syncPath, globalThis.location.href);
@@ -156,8 +397,10 @@ const socketUrl = () => {
156
397
  };
157
398
  var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync/SyncClient") {
158
399
  static layer = Layer.effect(SyncClient, Effect.gen(function* () {
159
- let nextOperationId = 0;
160
- let mutations = HashMap.empty();
400
+ const ids = yield* IdGenerator;
401
+ let invocations = HashMap.empty();
402
+ let rememberedHandshake;
403
+ let reloadRequested = false;
161
404
  let writer;
162
405
  const encode = (message) => Schema.encodeEffect(ClientMessageJson)(message).pipe(Effect.mapError((cause) => InvalidClientMessage.make({
163
406
  cause,
@@ -168,31 +411,27 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
168
411
  message: "The sync connection was lost."
169
412
  })));
170
413
  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({
414
+ const queryCache = makeQueryCache(ids.generate(SubscriptionId), (message) => writer === void 0 ? Effect.void : send(message).pipe(Effect.catchTags({
172
415
  ConnectionUnavailable: reportClientError,
173
416
  InvalidClientMessage: reportClientError
174
417
  })));
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);
418
+ const rejectInvocation = (id, error) => {
419
+ const pendingInvocation = HashMap.getUnsafe(invocations, id);
420
+ if (pendingInvocation === void 0) return Effect.void;
421
+ invocations = HashMap.remove(invocations, id);
422
+ return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingInvocation.deferred, error)), Effect.asVoid);
180
423
  };
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
- })));
424
+ const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (message) {
186
425
  switch (message.type) {
187
- case "QuerySnapshot":
426
+ case "Snapshot":
188
427
  if (queryCache.getById(message.id) === void 0) return;
189
- yield* resultFromWire(message.result).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result))));
428
+ yield* resultFromWire(message.result).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))));
190
429
  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)));
430
+ case "Result": {
431
+ const pendingInvocation = HashMap.getUnsafe(invocations, message.id);
432
+ if (pendingInvocation === void 0) return;
433
+ invocations = HashMap.remove(invocations, message.id);
434
+ yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => Deferred.succeed(pendingInvocation.deferred, result)));
196
435
  return;
197
436
  }
198
437
  case "ProtocolError": {
@@ -204,8 +443,8 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
204
443
  message: message.message,
205
444
  operation: message.operation
206
445
  });
207
- if (message.operation?.type === "Mutate") yield* rejectMutation(message.operation.id, error);
208
- else if (message.operation?.type === "Query") {
446
+ if (message.operation?.type === "Invocation") yield* rejectInvocation(message.operation.id, error);
447
+ else if (message.operation?.type === "Subscription") {
209
448
  queryCache.setError(message.operation.id, error);
210
449
  yield* reportClientError(error);
211
450
  } else yield* reportClientError(error);
@@ -213,27 +452,59 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
213
452
  }
214
453
  }
215
454
  });
216
- const disconnect = Effect.gen(function* () {
455
+ const requestReload = () => {
456
+ if (reloadRequested) return;
457
+ reloadRequested = true;
458
+ writer = void 0;
459
+ globalThis.location.reload();
460
+ };
461
+ const disconnect = Effect.sync(() => {
217
462
  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
463
  });
229
464
  const connect = Effect.scoped(Effect.gen(function* () {
230
- const socket = yield* Socket.makeWebSocket(socketUrl(), { closeCodeIsError: () => false });
465
+ if (reloadRequested) return yield* Effect.never;
466
+ const socket = yield* Socket.makeWebSocket(socketUrl(), { closeCodeIsError: (code) => {
467
+ if (shouldReloadForCloseCode(code)) requestReload();
468
+ return false;
469
+ } });
231
470
  const write = yield* socket.writer;
232
- const onOpen = Effect.gen(function* () {
471
+ const handshakeReceived = yield* Deferred.make();
472
+ let handshakeAccepted = false;
473
+ const acceptHandshake = Effect.fn("SyncClient.acceptHandshake")(function* (handshake) {
474
+ const decision = handshakeDecision(rememberedHandshake, handshakeAccepted, handshake);
475
+ if (decision === "Reload") {
476
+ requestReload();
477
+ return;
478
+ }
479
+ if (decision === "Ignore") return;
480
+ rememberedHandshake = handshake;
481
+ handshakeAccepted = true;
233
482
  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 });
483
+ yield* Deferred.succeed(handshakeReceived, void 0);
484
+ yield* Effect.gen(function* () {
485
+ yield* Effect.forEach(queryCache.entries(), (entry) => sendWith(write, subscribeMessage(entry)));
486
+ yield* Effect.forEach(HashMap.values(invocations), (pendingInvocation) => sendWith(write, pendingInvocation.message));
487
+ }).pipe(Effect.tapError(reportClientError), Effect.ignore);
488
+ });
489
+ const handleText = Effect.fn("SyncClient.handleText")(function* (text) {
490
+ const message = yield* Schema.decodeEffect(ServerMessageJson)(text).pipe(Effect.mapError((cause) => InvalidServerMessage.make({
491
+ cause,
492
+ message: "The server returned an invalid sync message."
493
+ })));
494
+ if (message.type === "Handshake") return yield* acceptHandshake(message);
495
+ if (message.type === "DeploymentChanged") {
496
+ requestReload();
497
+ return;
498
+ }
499
+ if (!handshakeAccepted) return yield* InvalidServerMessage.make({
500
+ cause: /* @__PURE__ */ new Error("The server sent a sync message before the handshake."),
501
+ message: "The server returned a sync message before the handshake."
502
+ });
503
+ return yield* handleMessage(message);
504
+ });
505
+ const run = yield* socket.runString(handleText).pipe(Effect.forkChild);
506
+ yield* Effect.raceFirst(Deferred.await(handshakeReceived).pipe(Effect.timeout("10 seconds")), Fiber.join(run));
507
+ yield* Fiber.join(run);
237
508
  })).pipe(Effect.mapError((cause) => Schema.is(ClientInfrastructureError)(cause) ? cause : ConnectionUnavailable.make({
238
509
  cause,
239
510
  message: "The sync connection was lost."
@@ -244,37 +515,33 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
244
515
  return yield* queryCache.observe(functionAddress, args);
245
516
  });
246
517
  const mutate = Effect.fn("SyncClient.mutate")(function* (functionAddress, args) {
247
- const requestId = RequestId.make(nextOperationId++);
518
+ const invocationId = yield* ids.generate(InvocationId);
248
519
  const deferred = yield* Deferred.make();
249
- mutations = HashMap.set(mutations, requestId, {
250
- deferred,
251
- function: functionAddress
252
- });
253
520
  const message = {
254
- type: "Mutate",
255
- id: requestId,
521
+ type: "Invoke",
522
+ id: invocationId,
523
+ kind: "Mutation",
256
524
  function: functionAddress,
257
525
  args
258
526
  };
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))));
527
+ invocations = HashMap.set(invocations, invocationId, {
528
+ deferred,
529
+ function: functionAddress,
530
+ message
531
+ });
532
+ if (writer !== void 0) yield* sendWith(writer, message).pipe(Effect.catchTags({
533
+ ConnectionUnavailable: reportClientError,
534
+ InvalidClientMessage: (error) => rejectInvocation(invocationId, error)
535
+ }));
269
536
  return yield* Deferred.await(deferred);
270
537
  });
271
538
  return SyncClient.of({
272
539
  mutate,
273
540
  observe
274
541
  });
275
- })).pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal));
542
+ })).pipe(Layer.provide(Layer.merge(Socket.layerWebSocketConstructorGlobal, BrowserCrypto.layer)));
276
543
  };
277
- const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(clientErrorReporterLayer)));
544
+ const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(clientErrorReporterLayer), Layer.provideMerge(IdGenerator.layer)));
278
545
  const hot = import.meta.hot;
279
546
  if (hot !== void 0) hot.dispose(() => void syncRuntime.dispose());
280
547
  //#endregion
@@ -356,9 +623,15 @@ const useMutation = (reference) => useMemo$1(() => {
356
623
  return mutate;
357
624
  }, [reference]);
358
625
  //#endregion
626
+ //#region src/client/app.ts
627
+ const app = (definition) => {
628
+ if (definition.title.trim().length === 0) throw new Error("The app title must contain a non-whitespace character.");
629
+ return definition;
630
+ };
631
+ //#endregion
359
632
  //#region src/client/index.ts
360
633
  const Result = { match: Result$1.match };
361
634
  //#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 };
635
+ 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
636
 
364
637
  //# sourceMappingURL=client.js.map