ignotum 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/cli/bin.mjs +1372 -492
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/runtime/{api-DzcR7spt.js → api-CAgKDij7.js} +38 -2
- package/dist/runtime/api-CAgKDij7.js.map +1 -0
- package/dist/runtime/{api-DR_8vfKg.d.ts → api-Cv3hMbzo.d.ts} +4 -3
- package/dist/runtime/client.d.ts +39 -8
- package/dist/runtime/client.js +505 -131
- package/dist/runtime/client.js.map +1 -1
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js +154 -0
- package/dist/runtime/descriptor-Cyo9FP9n-C0SRVXNW.js.map +1 -0
- package/dist/runtime/id-Bt9XWRGL.js +423 -0
- package/dist/runtime/id-Bt9XWRGL.js.map +1 -0
- package/dist/runtime/{id-Cs82tq9Q-hmYFyqTa.d.ts → id-BzFHf3Wo-DGPCjgrf.d.ts} +13 -13
- package/dist/runtime/id-Dz0apuB3.d.ts +1 -0
- package/dist/runtime/{index-2q9FwJud.d.ts → index-D54flWtH.d.ts} +136 -76
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +1 -1
- package/dist/runtime/internal/host.d.ts +21 -17
- package/dist/runtime/internal/host.js +22 -8
- package/dist/runtime/internal/host.js.map +1 -1
- package/dist/runtime/internal/server.d.ts +1 -1
- package/dist/runtime/internal/server.js +1 -1
- package/dist/runtime/internal/types.d.ts +1 -1
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/{pagination-Bt3l7QaC-BYEGmLBE.d.ts → pagination-DnKg3dkI-r5ZUxBBx.d.ts} +34 -6
- package/dist/runtime/pagination-Dz0apuB3.d.ts +1 -0
- package/dist/runtime/result-DKAA4gpS.d.ts +1 -0
- package/dist/runtime/{schema-ERFjT8-m.js → schema-1Zs03-iS.js} +135 -17
- package/dist/runtime/schema-1Zs03-iS.js.map +1 -0
- package/dist/runtime/server.d.ts +9 -3
- package/dist/runtime/server.js +5 -3
- package/dist/runtime/server.js.map +1 -1
- package/dist/runtime/{sync-a2EdGSdY.d.ts → sync-Bs8J3fIr.d.ts} +2 -2
- package/package.json +4 -4
- package/src/cli/agent-files.ts +8 -0
- package/src/cli/auth-client.ts +1 -1
- package/src/cli/bin.ts +13 -3
- package/src/cli/build/server.ts +102 -13
- package/src/cli/codegen.ts +7 -4
- package/src/cli/control-client.ts +30 -8
- package/src/cli/deploy.ts +7 -2
- package/src/cli/environment.ts +133 -0
- package/src/client/files.ts +43 -27
- package/src/client/hooks.ts +94 -17
- package/src/client/id.ts +259 -0
- package/src/client/index.ts +5 -2
- package/src/client/page-observers.ts +77 -0
- package/src/client/sync.ts +214 -69
- package/src/dev-runtime/functions.ts +232 -86
- package/src/dev-runtime/id.ts +174 -2
- package/src/dev-runtime/query-cache.ts +105 -0
- package/src/dev-runtime/sync.ts +187 -26
- package/src/server/index.ts +13 -1
- package/dist/runtime/api-DzcR7spt.js.map +0 -1
- package/dist/runtime/descriptor-C5VA9qRl-DuQsowaQ.js +0 -311
- package/dist/runtime/descriptor-C5VA9qRl-DuQsowaQ.js.map +0 -1
- package/dist/runtime/file-C1abuMgd.js +0 -173
- package/dist/runtime/file-C1abuMgd.js.map +0 -1
- package/dist/runtime/pagination-CFJ3xlAt.d.ts +0 -1
- package/dist/runtime/schema-ERFjT8-m.js.map +0 -1
package/dist/runtime/client.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Array as Array$1, Cause, Context, Deferred, Duration, Effect, ErrorReporter, Fiber, HashMap, Layer, ManagedRuntime, Predicate, Schedule, Schema, String } from "effect";
|
|
1
|
+
import { A as RuntimeFileMetadata, B as makeFileValue, D as encodeTransportObject, E as decodeTransportValue, F as fileIdOf, G as IdAlphabet, H as AppId, I as fileLimits, J as SessionEpoch, K as InvocationId, L as fileMimeTypes, N as fileFormats, P as fileGrantUrlOf, R as grantFileValue, U as DeploymentId, W as GeneratedId, X as TableId, Y as SubscriptionId, _ as Result$2, a as SessionResponse, b as inspectResult, d as sessionPath, f as sessionRequestsPath, i as SessionRequest, j as RuntimeFileOccurrence, k as FileId, l as developmentSelectionKey, m as upgrade, n as SessionOutcome, o as SessionState, p as initial, q as RuntimeRequestNonce, r as SessionRedirect, t as DevelopmentSelection, x as pending, y as failureFromWire, z as isFileValue } from "./id-Bt9XWRGL.js";
|
|
2
|
+
import { a as ErrorCode, c as ServerMessageJson, d as WireSuccess, f as AppStateRevision, i as DeploymentCloseCode, l as WireFailure, m as InvocationKey, n as functionPathOf, o as FunctionAddress, p as DeploymentGeneration, r as ClientMessageJson, s as Operation, u as WireResult } from "./api-CAgKDij7.js";
|
|
3
|
+
import { s as PaginationPageSize, t as ValueDescriptor } from "./descriptor-Cyo9FP9n-C0SRVXNW.js";
|
|
4
|
+
import { Array as Array$1, Cause, Context, Deferred, Duration, Effect, ErrorReporter, Fiber, HashMap, Layer, ManagedRuntime, Option, Predicate, Schedule, Schema, Semaphore, String } from "effect";
|
|
5
5
|
import { customAlphabet } from "nanoid";
|
|
6
|
-
import {
|
|
6
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
|
|
7
|
+
import { useCallback, useCallback as useCallback$1, useContext, useDebugValue, useDebugValue as useDebugValue$1, useEffect, useEffect as useEffect$1, useErrorBoundary, useId as useElementId, useImperativeHandle, useLayoutEffect, useMemo, useMemo as useMemo$1, useReducer, useRef, useState, useState as useState$1 } from "preact/hooks";
|
|
7
8
|
import * as Socket from "effect/unstable/socket/Socket";
|
|
8
9
|
import * as BrowserCrypto from "@effect/platform-browser/BrowserCrypto";
|
|
9
10
|
import { Component, Fragment, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray } from "preact";
|
|
@@ -32,9 +33,10 @@ const Files = { url: (file) => {
|
|
|
32
33
|
} };
|
|
33
34
|
const formatByMime = {};
|
|
34
35
|
for (const format of fileFormats) formatByMime[fileMimeTypes[format]] = format;
|
|
36
|
+
const MutationObject = Schema.ObjectKeyword.check(Schema.makeFilter((value) => Object.prototype.toString.call(value) === "[object Object]", { message: "Mutation objects must contain ordinary fields; built-in collections are unsupported." }));
|
|
35
37
|
const collectNativeFiles = (value, files) => {
|
|
36
38
|
if (value instanceof File) {
|
|
37
|
-
files.
|
|
39
|
+
files.add(value);
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
if (isFileValue(value) || Predicate.isDate(value)) return;
|
|
@@ -43,7 +45,7 @@ const collectNativeFiles = (value, files) => {
|
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
47
|
if (!Predicate.isObject(value)) return;
|
|
46
|
-
for (const child of Object.values(value)) collectNativeFiles(child, files);
|
|
48
|
+
for (const child of Object.values(Schema.decodeSync(MutationObject)(value))) collectNativeFiles(child, files);
|
|
47
49
|
};
|
|
48
50
|
const replaceNativeFiles = (value, replacements) => {
|
|
49
51
|
if (value instanceof File) return replacements.get(value);
|
|
@@ -54,18 +56,23 @@ const replaceNativeFiles = (value, replacements) => {
|
|
|
54
56
|
};
|
|
55
57
|
const prepareMutationArguments = Effect.fn("SyncClient.prepareMutationArguments")(function* (input) {
|
|
56
58
|
const ids = yield* IdGenerator;
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const unique = yield* Effect.try(() => {
|
|
60
|
+
const files = /* @__PURE__ */ new Set();
|
|
61
|
+
collectNativeFiles(input, files);
|
|
62
|
+
if (files.size > fileLimits.filesPerMutation) throw new Error(`A mutation may upload at most ${fileLimits.filesPerMutation} files.`);
|
|
63
|
+
if ([...files].reduce((total, file) => total + file.size, 0) > fileLimits.mutationBytes) throw new Error(`Mutation uploads may total at most ${fileLimits.mutationBytes} bytes.`);
|
|
64
|
+
return files;
|
|
65
|
+
});
|
|
62
66
|
const replacements = /* @__PURE__ */ new Map();
|
|
63
67
|
const nativeFiles = /* @__PURE__ */ new Map();
|
|
64
68
|
for (const file of unique) {
|
|
65
|
-
const format =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
const format = yield* Effect.try(() => {
|
|
70
|
+
const format = formatByMime[file.type.toLowerCase()];
|
|
71
|
+
if (format === void 0) throw new Error(`File '${file.name}' has unsupported media type '${file.type || "unknown"}'.`);
|
|
72
|
+
if (file.size === 0 || file.size > fileLimits.fileBytes) throw new Error(`File '${file.name}' must be between 1 and ${fileLimits.fileBytes} bytes.`);
|
|
73
|
+
if (new TextEncoder().encode(file.name).byteLength > fileLimits.filenameBytes) throw new Error(`File '${file.name}' has a filename that is too long.`);
|
|
74
|
+
return format;
|
|
75
|
+
});
|
|
69
76
|
const id = yield* ids.generate(FileId);
|
|
70
77
|
replacements.set(file, makeFileValue(id, {
|
|
71
78
|
format,
|
|
@@ -75,12 +82,13 @@ const prepareMutationArguments = Effect.fn("SyncClient.prepareMutationArguments"
|
|
|
75
82
|
nativeFiles.set(id, file);
|
|
76
83
|
}
|
|
77
84
|
return {
|
|
78
|
-
value: replaceNativeFiles(input, replacements),
|
|
85
|
+
value: unique.size === 0 ? input : yield* Effect.try(() => replaceNativeFiles(input, replacements)),
|
|
79
86
|
nativeFiles,
|
|
80
87
|
fileIds: Array.from(nativeFiles.keys())
|
|
81
88
|
};
|
|
82
89
|
});
|
|
83
90
|
const applyFileGrants = (value, grants) => {
|
|
91
|
+
if (grants.length === 0) return value;
|
|
84
92
|
const byId = /* @__PURE__ */ new Map();
|
|
85
93
|
for (const grant of grants) {
|
|
86
94
|
if (byId.has(grant.id)) throw new Error("A query file grant ID is duplicated.");
|
|
@@ -105,14 +113,182 @@ const applyFileGrants = (value, grants) => {
|
|
|
105
113
|
};
|
|
106
114
|
//#endregion
|
|
107
115
|
//#region ../contracts/dist/json.js
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
const encodeJson = (value) => {
|
|
117
|
+
if (Array$1.isArray(value)) return `[${value.map(encodeJson).join(",")}]`;
|
|
118
|
+
if (value === null || Predicate.isString(value) || Predicate.isNumber(value) || Predicate.isBoolean(value)) return JSON.stringify(value);
|
|
119
|
+
return `{${Array$1.sortWith(Object.entries(value), ([key]) => key, String.Order).map(([key, field]) => `${JSON.stringify(key)}:${encodeJson(field)}`).join(",")}}`;
|
|
120
|
+
};
|
|
121
|
+
const encodeCanonicalJson = (value) => encodeJson(Schema.decodeSync(Schema.Json)(value));
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/client/id.ts
|
|
124
|
+
var IdRequestError = class extends Schema.TaggedError()("IdRequestError", { message: Schema.String }) {};
|
|
125
|
+
const outcomeKey = "ignotum.id.outcome";
|
|
126
|
+
const selectionJson = Schema.fromJsonString(DevelopmentSelection);
|
|
127
|
+
var BrowserSession = class BrowserSession extends Context.Service()("ignotum/client/id/BrowserSession") {
|
|
128
|
+
static layer = Layer.effect(BrowserSession, Effect.gen(function* () {
|
|
129
|
+
const client = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk);
|
|
130
|
+
const ids = yield* IdGenerator;
|
|
131
|
+
const bootstrapLock = yield* Semaphore.make(1);
|
|
132
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
133
|
+
let state = { status: "pending" };
|
|
134
|
+
let current;
|
|
135
|
+
let generation = 0;
|
|
136
|
+
let deadline = Infinity;
|
|
137
|
+
let development = false;
|
|
138
|
+
let selection;
|
|
139
|
+
let expiry;
|
|
140
|
+
const notify = () => {
|
|
141
|
+
for (const listener of listeners) listener();
|
|
142
|
+
};
|
|
143
|
+
const suspend = (error) => {
|
|
144
|
+
generation += 1;
|
|
145
|
+
deadline = Infinity;
|
|
146
|
+
if (expiry !== void 0) clearTimeout(expiry);
|
|
147
|
+
current = void 0;
|
|
148
|
+
state = error === void 0 ? {
|
|
149
|
+
status: "pending",
|
|
150
|
+
outcome: state.outcome
|
|
151
|
+
} : {
|
|
152
|
+
status: "error",
|
|
153
|
+
error,
|
|
154
|
+
outcome: state.outcome
|
|
155
|
+
};
|
|
156
|
+
notify();
|
|
157
|
+
};
|
|
158
|
+
const accept = (session, serverTime) => {
|
|
159
|
+
generation += 1;
|
|
160
|
+
if (expiry !== void 0) clearTimeout(expiry);
|
|
161
|
+
current = session;
|
|
162
|
+
state = session.user === null ? {
|
|
163
|
+
status: "signedOut",
|
|
164
|
+
outcome: state.outcome
|
|
165
|
+
} : {
|
|
166
|
+
status: "signedIn",
|
|
167
|
+
user: session.user,
|
|
168
|
+
outcome: state.outcome
|
|
169
|
+
};
|
|
170
|
+
const delay = Math.max(0, session.validUntil - serverTime);
|
|
171
|
+
deadline = performance.now() + delay;
|
|
172
|
+
if (delay < 2147483647) expiry = setTimeout(() => suspend(), delay);
|
|
173
|
+
notify();
|
|
174
|
+
};
|
|
175
|
+
const checkExpiry = () => {
|
|
176
|
+
if (current !== void 0 && performance.now() >= deadline) suspend();
|
|
177
|
+
};
|
|
178
|
+
globalThis.addEventListener?.("focus", checkExpiry);
|
|
179
|
+
globalThis.document?.addEventListener("visibilitychange", checkExpiry);
|
|
180
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
181
|
+
if (expiry !== void 0) clearTimeout(expiry);
|
|
182
|
+
listeners.clear();
|
|
183
|
+
globalThis.removeEventListener?.("focus", checkExpiry);
|
|
184
|
+
globalThis.document?.removeEventListener("visibilitychange", checkExpiry);
|
|
185
|
+
}));
|
|
186
|
+
const readOutcome = () => {
|
|
187
|
+
const raw = globalThis.sessionStorage.getItem(outcomeKey);
|
|
188
|
+
if (raw !== null) {
|
|
189
|
+
const decoded = Schema.decodeUnknownOption(SessionOutcome)(raw);
|
|
190
|
+
if (Option.isSome(decoded)) state = {
|
|
191
|
+
...state,
|
|
192
|
+
outcome: decoded.value
|
|
193
|
+
};
|
|
194
|
+
globalThis.sessionStorage.removeItem(outcomeKey);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const parameters = () => {
|
|
198
|
+
if (!development || selection === void 0) return "";
|
|
199
|
+
const params = new URLSearchParams({ epoch: selection.epoch });
|
|
200
|
+
if (selection.username !== null) params.set("username", selection.username);
|
|
201
|
+
return `?${params}`;
|
|
202
|
+
};
|
|
203
|
+
const readSession = Effect.fn("BrowserSession.read")(function* () {
|
|
204
|
+
return yield* client.get(`${sessionPath}${parameters()}`, { headers: { "x-ignotum-request": "1" } }).pipe(Effect.flatMap(HttpClientResponse.schemaBodyJson(SessionResponse)));
|
|
205
|
+
});
|
|
206
|
+
let bootstrapGeneration = 0;
|
|
207
|
+
const bootstrap = bootstrapLock.withPermits(1)(Effect.gen(function* () {
|
|
208
|
+
const admittedGeneration = generation;
|
|
209
|
+
bootstrapGeneration = admittedGeneration;
|
|
210
|
+
const startedAt = performance.now();
|
|
211
|
+
let session = yield* readSession();
|
|
212
|
+
development = session.development === true;
|
|
213
|
+
if (development && selection === void 0) {
|
|
214
|
+
const saved = globalThis.sessionStorage.getItem(developmentSelectionKey);
|
|
215
|
+
const decoded = saved === null ? Option.none() : Schema.decodeOption(selectionJson)(saved);
|
|
216
|
+
selection = Option.getOrElse(decoded, () => ({
|
|
217
|
+
formatVersion: 1,
|
|
218
|
+
username: null,
|
|
219
|
+
epoch: ""
|
|
220
|
+
}));
|
|
221
|
+
if (selection.epoch === "") selection = {
|
|
222
|
+
...selection,
|
|
223
|
+
epoch: yield* ids.generate(SessionEpoch)
|
|
224
|
+
};
|
|
225
|
+
globalThis.sessionStorage.setItem(developmentSelectionKey, Schema.encodeSync(selectionJson)(selection));
|
|
226
|
+
session = yield* readSession();
|
|
227
|
+
}
|
|
228
|
+
if (generation !== admittedGeneration) {
|
|
229
|
+
if (current !== void 0) return current;
|
|
230
|
+
return yield* IdRequestError.make({ message: "Your ID session changed while loading." });
|
|
231
|
+
}
|
|
232
|
+
readOutcome();
|
|
233
|
+
if (session.outcome !== void 0) state = {
|
|
234
|
+
...state,
|
|
235
|
+
outcome: session.outcome
|
|
236
|
+
};
|
|
237
|
+
accept(session, session.serverTime + performance.now() - startedAt);
|
|
238
|
+
return session;
|
|
239
|
+
})).pipe(Effect.mapError(() => IdRequestError.make({ message: "Could not load your ID session." })), Effect.tapError((error) => Effect.sync(() => {
|
|
240
|
+
if (generation === bootstrapGeneration) suspend(error.message);
|
|
241
|
+
})));
|
|
242
|
+
const start = Effect.fn("BrowserSession.start")(function* (intent, profile, returnTo) {
|
|
243
|
+
const session = current ?? (yield* bootstrap);
|
|
244
|
+
if (intent === "requestProfile" && session.user === null) return yield* IdRequestError.make({ message: "Sign in before requesting profile information." });
|
|
245
|
+
const request = yield* Schema.decodeUnknownEffect(SessionRequest)({
|
|
246
|
+
intent,
|
|
247
|
+
profile,
|
|
248
|
+
sessionEpoch: session.sessionEpoch,
|
|
249
|
+
returnTo: returnTo ?? `${globalThis.location.pathname}${globalThis.location.search}${globalThis.location.hash}`
|
|
250
|
+
}).pipe(Effect.mapError(() => IdRequestError.make({ message: "The ID request is invalid." })));
|
|
251
|
+
const redirect = yield* HttpClientRequest.post(sessionRequestsPath).pipe(HttpClientRequest.setHeader("x-ignotum-request", "1"), HttpClientRequest.bodyJsonUnsafe(request), client.execute, Effect.flatMap(HttpClientResponse.schemaBodyJson(SessionRedirect)), Effect.mapError(() => IdRequestError.make({ message: "Could not start the ID request." })));
|
|
252
|
+
if (current?.sessionEpoch !== session.sessionEpoch) return yield* IdRequestError.make({ message: "Your ID session changed. Try again." });
|
|
253
|
+
globalThis.location.assign(redirect.redirectUrl);
|
|
254
|
+
});
|
|
255
|
+
const signOut = Effect.gen(function* () {
|
|
256
|
+
const session = current ?? (yield* bootstrap);
|
|
257
|
+
yield* HttpClientRequest.delete(sessionPath).pipe(HttpClientRequest.setHeader("x-ignotum-request", "1"), HttpClientRequest.bodyJsonUnsafe({ sessionEpoch: session.sessionEpoch }), client.execute, Effect.mapError(() => IdRequestError.make({ message: "Could not sign out." })));
|
|
258
|
+
suspend();
|
|
259
|
+
if (development) {
|
|
260
|
+
selection = {
|
|
261
|
+
formatVersion: 1,
|
|
262
|
+
username: null,
|
|
263
|
+
epoch: yield* ids.generate(SessionEpoch)
|
|
264
|
+
};
|
|
265
|
+
globalThis.sessionStorage.setItem(developmentSelectionKey, Schema.encodeSync(selectionJson)(selection));
|
|
266
|
+
}
|
|
267
|
+
globalThis.location.reload();
|
|
268
|
+
});
|
|
269
|
+
return BrowserSession.of({
|
|
270
|
+
snapshot: () => {
|
|
271
|
+
checkExpiry();
|
|
272
|
+
return state;
|
|
273
|
+
},
|
|
274
|
+
current: () => {
|
|
275
|
+
checkExpiry();
|
|
276
|
+
return current;
|
|
277
|
+
},
|
|
278
|
+
subscribe: (listener) => {
|
|
279
|
+
listeners.add(listener);
|
|
280
|
+
return () => {
|
|
281
|
+
listeners.delete(listener);
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
accept,
|
|
285
|
+
suspend,
|
|
286
|
+
bootstrap,
|
|
287
|
+
start,
|
|
288
|
+
signOut,
|
|
289
|
+
socketParameters: parameters
|
|
290
|
+
});
|
|
291
|
+
})).pipe(Layer.provide(FetchHttpClient.layer));
|
|
116
292
|
};
|
|
117
293
|
//#endregion
|
|
118
294
|
//#region src/client/errors.ts
|
|
@@ -194,7 +370,9 @@ const IndexPointInvalidation = Schema.Struct({
|
|
|
194
370
|
index: IndexIdentity,
|
|
195
371
|
key: EncodedIndexKey
|
|
196
372
|
});
|
|
373
|
+
const IdDependency = Schema.Struct({ type: Schema.Literal("Id") });
|
|
197
374
|
const ReadDependency = Schema.Union([
|
|
375
|
+
IdDependency,
|
|
198
376
|
TableDependency,
|
|
199
377
|
DocumentDependency,
|
|
200
378
|
IndexRangeDependency
|
|
@@ -259,7 +437,8 @@ const InvocationBase = {
|
|
|
259
437
|
generation: DeploymentGeneration,
|
|
260
438
|
invocationKey: InvocationKey,
|
|
261
439
|
function: FunctionAddress,
|
|
262
|
-
args: Schema.Json
|
|
440
|
+
args: Schema.Json,
|
|
441
|
+
identity: Schema.optional(SessionState)
|
|
263
442
|
};
|
|
264
443
|
const QueryInvocation = Schema.Struct({
|
|
265
444
|
...InvocationBase,
|
|
@@ -282,6 +461,7 @@ var RuntimeDeploymentUnavailable = class extends Schema.TaggedError()("RuntimeDe
|
|
|
282
461
|
message: Schema.String
|
|
283
462
|
}) {};
|
|
284
463
|
const RuntimeInvocationRejectionCode = Schema.Literals([
|
|
464
|
+
"SessionExpired",
|
|
285
465
|
"FunctionUnavailable",
|
|
286
466
|
"InvalidArguments",
|
|
287
467
|
"InvocationIdConflict",
|
|
@@ -342,6 +522,7 @@ const ArtifactKind = Schema.Literals([
|
|
|
342
522
|
"ClientShell",
|
|
343
523
|
"FunctionBundle",
|
|
344
524
|
"ServerManifest",
|
|
525
|
+
"ServerEnvironment",
|
|
345
526
|
"SourceMap",
|
|
346
527
|
"SchemaSnapshot"
|
|
347
528
|
]);
|
|
@@ -366,7 +547,14 @@ const DeploymentInventoryV1 = Schema.Struct({
|
|
|
366
547
|
formatVersion: Schema.Literal(1),
|
|
367
548
|
files: Schema.Array(ArtifactFile)
|
|
368
549
|
});
|
|
369
|
-
|
|
550
|
+
const DeploymentInventoryV2 = Schema.Struct({
|
|
551
|
+
formatVersion: Schema.Literal(2),
|
|
552
|
+
files: Schema.Array(ArtifactFile)
|
|
553
|
+
});
|
|
554
|
+
upgrade(initial(DeploymentInventoryV1), DeploymentInventoryV2, (inventory) => ({
|
|
555
|
+
formatVersion: 2,
|
|
556
|
+
files: inventory.files
|
|
557
|
+
}));
|
|
370
558
|
const ClientManifestV1 = Schema.Struct({
|
|
371
559
|
formatVersion: Schema.Literal(1),
|
|
372
560
|
shell: ArtifactReference,
|
|
@@ -387,6 +575,16 @@ const SchemaSnapshotField = Schema.Struct({
|
|
|
387
575
|
name: Schema.String,
|
|
388
576
|
value: ValueDescriptor
|
|
389
577
|
});
|
|
578
|
+
const EnvironmentArtifactV1 = Schema.Struct({
|
|
579
|
+
formatVersion: Schema.Literal(1),
|
|
580
|
+
nonce: Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/)),
|
|
581
|
+
values: Schema.Record(Schema.String, Schema.String)
|
|
582
|
+
});
|
|
583
|
+
initial(EnvironmentArtifactV1);
|
|
584
|
+
const ServerEnvironmentArtifact = Schema.Struct({
|
|
585
|
+
artifact: ArtifactReference,
|
|
586
|
+
fields: Schema.Array(SchemaSnapshotField)
|
|
587
|
+
});
|
|
390
588
|
const SchemaSnapshotIndex = Schema.Struct({
|
|
391
589
|
name: Schema.String,
|
|
392
590
|
fields: Schema.Array(Schema.String)
|
|
@@ -440,11 +638,22 @@ const ServerBuildManifestV2 = Schema.Struct({
|
|
|
440
638
|
schema: ArtifactReference,
|
|
441
639
|
functions: Schema.Array(ServerFunctionArtifact)
|
|
442
640
|
});
|
|
443
|
-
upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
|
|
641
|
+
const ServerBuildManifestThroughV2 = upgrade(initial(ServerBuildManifestV1), ServerBuildManifestV2, (manifest) => ({
|
|
444
642
|
formatVersion: 2,
|
|
445
643
|
schema: manifest.schema,
|
|
446
644
|
functions: manifest.functions
|
|
447
645
|
}));
|
|
646
|
+
const ServerBuildManifestV3 = Schema.Struct({
|
|
647
|
+
formatVersion: Schema.Literal(3),
|
|
648
|
+
schema: ArtifactReference,
|
|
649
|
+
environment: Schema.optional(ServerEnvironmentArtifact),
|
|
650
|
+
functions: Schema.Array(ServerFunctionArtifact)
|
|
651
|
+
});
|
|
652
|
+
upgrade(ServerBuildManifestThroughV2, ServerBuildManifestV3, (manifest) => ({
|
|
653
|
+
formatVersion: 3,
|
|
654
|
+
schema: manifest.schema,
|
|
655
|
+
functions: manifest.functions
|
|
656
|
+
}));
|
|
448
657
|
ArtifactPath.make("inventory.json");
|
|
449
658
|
ArtifactPath.make("client/manifest.json");
|
|
450
659
|
ArtifactPath.make("client/shell.html");
|
|
@@ -452,6 +661,7 @@ ArtifactPath.make("client/shell.html");
|
|
|
452
661
|
`${ArtifactPath.make("client/routes")}`;
|
|
453
662
|
ArtifactPath.make("server/manifest.json");
|
|
454
663
|
ArtifactPath.make("server/schema.json");
|
|
664
|
+
ArtifactPath.make("server/environment.json");
|
|
455
665
|
//#endregion
|
|
456
666
|
//#region ../contracts/dist/runtime/transport.js
|
|
457
667
|
const runtimeInvocationPath = "/v1/invoke";
|
|
@@ -519,78 +729,87 @@ const subscribeMessage = (entry) => ({
|
|
|
519
729
|
});
|
|
520
730
|
const makeQueryCache = (allocateId, sendLifecycleMessage) => {
|
|
521
731
|
const queries = /* @__PURE__ */ new Map();
|
|
522
|
-
const
|
|
732
|
+
const queriesById = /* @__PURE__ */ new Map();
|
|
733
|
+
const observe = Effect.fn("SyncClient.QueryCache.observe")(function* (functionAddress, args) {
|
|
734
|
+
const key = queryKey(functionAddress, args);
|
|
735
|
+
let entry = queries.get(key);
|
|
736
|
+
if (entry === void 0) {
|
|
737
|
+
const created = {
|
|
738
|
+
args,
|
|
739
|
+
function: functionAddress,
|
|
740
|
+
generation: 0,
|
|
741
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
742
|
+
revision: void 0,
|
|
743
|
+
references: 0,
|
|
744
|
+
result: pending(),
|
|
745
|
+
subscriptionId: yield* allocateId
|
|
746
|
+
};
|
|
747
|
+
queries.set(key, created);
|
|
748
|
+
queriesById.set(created.subscriptionId, created);
|
|
749
|
+
yield* sendLifecycleMessage(subscribeMessage(created));
|
|
750
|
+
entry = created;
|
|
751
|
+
}
|
|
752
|
+
const observed = entry;
|
|
753
|
+
observed.references += 1;
|
|
754
|
+
observed.generation += 1;
|
|
755
|
+
let released = false;
|
|
756
|
+
return {
|
|
757
|
+
getSnapshot: () => observed.result,
|
|
758
|
+
subscribe: (listener) => {
|
|
759
|
+
observed.listeners.add(listener);
|
|
760
|
+
return () => observed.listeners.delete(listener);
|
|
761
|
+
},
|
|
762
|
+
release: Effect.gen(function* () {
|
|
763
|
+
if (released) return;
|
|
764
|
+
released = true;
|
|
765
|
+
observed.references -= 1;
|
|
766
|
+
if (observed.references !== 0) return;
|
|
767
|
+
const generation = ++observed.generation;
|
|
768
|
+
yield* Effect.sleep("25 millis");
|
|
769
|
+
if (observed.references !== 0 || observed.generation !== generation || queries.get(key) !== observed) return;
|
|
770
|
+
queries.delete(key);
|
|
771
|
+
queriesById.delete(observed.subscriptionId);
|
|
772
|
+
yield* sendLifecycleMessage({
|
|
773
|
+
type: "Unsubscribe",
|
|
774
|
+
id: observed.subscriptionId
|
|
775
|
+
});
|
|
776
|
+
})
|
|
777
|
+
};
|
|
778
|
+
});
|
|
779
|
+
const clear = (entry) => {
|
|
780
|
+
entry.result = pending();
|
|
781
|
+
entry.revision = void 0;
|
|
782
|
+
for (const listener of entry.listeners) listener();
|
|
783
|
+
};
|
|
523
784
|
return {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const key = queryKeysById.get(id);
|
|
527
|
-
return key === void 0 ? void 0 : queries.get(key);
|
|
785
|
+
invalidate: (ids) => {
|
|
786
|
+
for (const entry of queries.values()) if (ids === void 0 || ids.includes(entry.subscriptionId)) clear(entry);
|
|
528
787
|
},
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
let entry = queries.get(key);
|
|
532
|
-
if (entry === void 0) {
|
|
533
|
-
const created = {
|
|
534
|
-
args,
|
|
535
|
-
function: functionAddress,
|
|
536
|
-
generation: 0,
|
|
537
|
-
listeners: /* @__PURE__ */ new Set(),
|
|
538
|
-
revision: void 0,
|
|
539
|
-
references: 0,
|
|
540
|
-
result: pending(),
|
|
541
|
-
subscriptionId: yield* allocateId
|
|
542
|
-
};
|
|
543
|
-
queries.set(key, created);
|
|
544
|
-
queryKeysById.set(created.subscriptionId, key);
|
|
545
|
-
yield* sendLifecycleMessage(subscribeMessage(created));
|
|
546
|
-
entry = created;
|
|
547
|
-
}
|
|
548
|
-
const observed = entry;
|
|
549
|
-
observed.references += 1;
|
|
550
|
-
observed.generation += 1;
|
|
551
|
-
let released = false;
|
|
552
|
-
return {
|
|
553
|
-
getSnapshot: () => observed.result,
|
|
554
|
-
subscribe: (listener) => {
|
|
555
|
-
observed.listeners.add(listener);
|
|
556
|
-
return () => observed.listeners.delete(listener);
|
|
557
|
-
},
|
|
558
|
-
release: Effect.gen(function* () {
|
|
559
|
-
if (released) return;
|
|
560
|
-
released = true;
|
|
561
|
-
observed.references -= 1;
|
|
562
|
-
if (observed.references !== 0) return;
|
|
563
|
-
const generation = ++observed.generation;
|
|
564
|
-
yield* Effect.sleep("25 millis");
|
|
565
|
-
if (observed.references !== 0 || observed.generation !== generation || queries.get(key) !== observed) return;
|
|
566
|
-
queries.delete(key);
|
|
567
|
-
queryKeysById.delete(observed.subscriptionId);
|
|
568
|
-
yield* sendLifecycleMessage({
|
|
569
|
-
type: "Unsubscribe",
|
|
570
|
-
id: observed.subscriptionId
|
|
571
|
-
});
|
|
572
|
-
})
|
|
573
|
-
};
|
|
574
|
-
}),
|
|
575
|
-
setError: (id, error) => {
|
|
576
|
-
const key = queryKeysById.get(id);
|
|
577
|
-
const entry = key === void 0 ? void 0 : queries.get(key);
|
|
578
|
-
if (entry === void 0) return;
|
|
579
|
-
entry.result = error;
|
|
580
|
-
for (const listener of entry.listeners) listener();
|
|
788
|
+
invalidatePrivate: () => {
|
|
789
|
+
for (const entry of queries.values()) if (entry.dependsOnId !== false) clear(entry);
|
|
581
790
|
},
|
|
791
|
+
entries: () => queries.values(),
|
|
792
|
+
getById: (id) => queriesById.get(id),
|
|
793
|
+
observe,
|
|
582
794
|
setResult: (id, result, revision) => {
|
|
583
|
-
const
|
|
584
|
-
const entry = key === void 0 ? void 0 : queries.get(key);
|
|
795
|
+
const entry = queriesById.get(id);
|
|
585
796
|
if (entry === void 0) return;
|
|
586
|
-
if (
|
|
587
|
-
|
|
797
|
+
if (revision !== void 0) {
|
|
798
|
+
if (entry.revision !== void 0 && revision < entry.revision) return;
|
|
799
|
+
entry.revision = revision;
|
|
800
|
+
}
|
|
588
801
|
entry.result = result;
|
|
589
802
|
for (const listener of entry.listeners) listener();
|
|
590
803
|
}
|
|
591
804
|
};
|
|
592
805
|
};
|
|
593
|
-
const resultFromWire = Effect.fn("SyncClient.resultFromWire")((wire, files = []) => Effect.
|
|
806
|
+
const resultFromWire = Effect.fn("SyncClient.resultFromWire")((wire, files = []) => Effect.try({
|
|
807
|
+
try: () => wire.type === "Success" ? Result$2.succeed(wire.value === void 0 ? void 0 : applyFileGrants(decodeTransportValue(wire.value, wire.dates ?? []), files)) : failureFromWire(wire.error, wire.dates),
|
|
808
|
+
catch: (cause) => InvalidServerMessage.make({
|
|
809
|
+
cause,
|
|
810
|
+
message: "The server returned an invalid function result."
|
|
811
|
+
})
|
|
812
|
+
}));
|
|
594
813
|
const shouldReloadForCloseCode = (code) => code === DeploymentCloseCode;
|
|
595
814
|
const handshakesMatch = (first, next) => first.appId === next.appId && first.deploymentId === next.deploymentId && first.generation === next.generation;
|
|
596
815
|
const handshakeDecision = (remembered, acceptedOnConnection, next) => {
|
|
@@ -613,10 +832,12 @@ const socketUrl = () => {
|
|
|
613
832
|
var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync/SyncClient") {
|
|
614
833
|
static layer = Layer.effect(SyncClient, Effect.gen(function* () {
|
|
615
834
|
const ids = yield* IdGenerator;
|
|
835
|
+
const session = yield* BrowserSession;
|
|
616
836
|
let invocations = HashMap.empty();
|
|
617
837
|
let rememberedHandshake;
|
|
618
838
|
let reloadRequested = false;
|
|
619
839
|
let writer;
|
|
840
|
+
let sessionSuspended;
|
|
620
841
|
const encode = (message) => Schema.encodeEffect(ClientMessageJson)(message).pipe(Effect.mapError((cause) => InvalidClientMessage.make({
|
|
621
842
|
cause,
|
|
622
843
|
message: "Could not encode an Ignotum sync message."
|
|
@@ -630,31 +851,72 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
630
851
|
ConnectionUnavailable: reportClientError,
|
|
631
852
|
InvalidClientMessage: reportClientError
|
|
632
853
|
})));
|
|
854
|
+
const unsubscribeSession = session.subscribe(() => {
|
|
855
|
+
if (session.current() === void 0) {
|
|
856
|
+
queryCache.invalidatePrivate();
|
|
857
|
+
writer = void 0;
|
|
858
|
+
if (sessionSuspended !== void 0) Deferred.doneUnsafe(sessionSuspended, Effect.void);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
yield* Effect.addFinalizer(() => Effect.sync(unsubscribeSession));
|
|
633
862
|
const rejectInvocation = (id, error) => {
|
|
634
|
-
const pendingInvocation = HashMap.
|
|
863
|
+
const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, id));
|
|
635
864
|
if (pendingInvocation === void 0) return Effect.void;
|
|
636
865
|
invocations = HashMap.remove(invocations, id);
|
|
637
866
|
return reportClientError(error).pipe(Effect.andThen(Deferred.fail(pendingInvocation.prepared, error)), Effect.andThen(Deferred.fail(pendingInvocation.deferred, error)), Effect.asVoid);
|
|
638
867
|
};
|
|
639
868
|
const handleMessage = Effect.fn("SyncClient.handleMessage")(function* (message) {
|
|
640
869
|
switch (message.type) {
|
|
641
|
-
case "
|
|
642
|
-
if (
|
|
643
|
-
|
|
870
|
+
case "Session":
|
|
871
|
+
if (message.event === "Updated") {
|
|
872
|
+
const previous = session.current();
|
|
873
|
+
if (previous !== void 0 && previous.sessionEpoch !== message.session.sessionEpoch) return;
|
|
874
|
+
if (previous?.viewRevision !== message.session.viewRevision) queryCache.invalidatePrivate();
|
|
875
|
+
session.accept(message.session, message.serverTime);
|
|
876
|
+
} else {
|
|
877
|
+
if (rememberedHandshake?.session?.sessionEpoch !== message.sessionEpoch) return;
|
|
878
|
+
session.suspend(message.event === "Unavailable" ? "Your ID session is temporarily unavailable." : void 0);
|
|
879
|
+
if (message.event !== "Unavailable") yield* Effect.forEach(HashMap.keys(invocations), (id) => rejectInvocation(id, ServerError.make({
|
|
880
|
+
code: "SessionChanged",
|
|
881
|
+
message: "The session changed. This operation may already have completed."
|
|
882
|
+
})));
|
|
883
|
+
}
|
|
884
|
+
return;
|
|
885
|
+
case "Invalidate":
|
|
886
|
+
if (message.sessionEpoch === session.current()?.sessionEpoch) queryCache.invalidate(message.ids);
|
|
644
887
|
return;
|
|
888
|
+
case "Snapshot": {
|
|
889
|
+
const current = session.current();
|
|
890
|
+
if (current === void 0 || message.identity?.sessionEpoch !== current.sessionEpoch) return;
|
|
891
|
+
if (message.identity.dependsOnId && message.identity.viewRevision !== current.viewRevision) return;
|
|
892
|
+
const entry = queryCache.getById(message.id);
|
|
893
|
+
if (entry === void 0) return;
|
|
894
|
+
entry.dependsOnId = message.identity.dependsOnId;
|
|
895
|
+
if (entry.revision !== void 0 && message.revision < entry.revision) return;
|
|
896
|
+
yield* resultFromWire(message.result, message.files ?? []).pipe(Effect.tap((result) => Effect.sync(() => queryCache.setResult(message.id, result, message.revision))), Effect.catchTags({ InvalidServerMessage: (error) => {
|
|
897
|
+
queryCache.setResult(message.id, error, message.revision);
|
|
898
|
+
return reportClientError(error);
|
|
899
|
+
} }));
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
645
902
|
case "Preparation": {
|
|
646
|
-
|
|
903
|
+
if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
|
|
904
|
+
const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
|
|
647
905
|
if (pendingInvocation !== void 0) yield* Deferred.succeed(pendingInvocation.prepared, message.uploads);
|
|
648
906
|
return;
|
|
649
907
|
}
|
|
650
908
|
case "Result": {
|
|
651
|
-
|
|
909
|
+
if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
|
|
910
|
+
const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, message.id));
|
|
652
911
|
if (pendingInvocation === void 0) return;
|
|
653
|
-
|
|
654
|
-
|
|
912
|
+
yield* resultFromWire(message.result).pipe(Effect.flatMap((result) => {
|
|
913
|
+
invocations = HashMap.remove(invocations, message.id);
|
|
914
|
+
return Deferred.succeed(pendingInvocation.deferred, result);
|
|
915
|
+
}), Effect.catchTags({ InvalidServerMessage: (error) => rejectInvocation(message.id, error) }));
|
|
655
916
|
return;
|
|
656
917
|
}
|
|
657
918
|
case "Error": {
|
|
919
|
+
if (message.sessionEpoch !== session.current()?.sessionEpoch) return;
|
|
658
920
|
const error = message.operation === void 0 ? ServerError.make({
|
|
659
921
|
code: message.code,
|
|
660
922
|
message: message.message
|
|
@@ -665,7 +927,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
665
927
|
});
|
|
666
928
|
if (message.operation?.type === "Invocation") yield* rejectInvocation(message.operation.id, error);
|
|
667
929
|
else if (message.operation?.type === "Subscription") {
|
|
668
|
-
queryCache.
|
|
930
|
+
queryCache.setResult(message.operation.id, error);
|
|
669
931
|
yield* reportClientError(error);
|
|
670
932
|
} else yield* reportClientError(error);
|
|
671
933
|
return;
|
|
@@ -683,7 +945,8 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
683
945
|
});
|
|
684
946
|
const connect = Effect.scoped(Effect.gen(function* () {
|
|
685
947
|
if (reloadRequested) return yield* Effect.never;
|
|
686
|
-
|
|
948
|
+
yield* session.bootstrap.pipe(Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message })));
|
|
949
|
+
const socket = yield* Socket.makeWebSocket(`${socketUrl()}${session.socketParameters()}`, { closeCodeIsError: (code) => {
|
|
687
950
|
if (shouldReloadForCloseCode(code)) requestReload();
|
|
688
951
|
return false;
|
|
689
952
|
} });
|
|
@@ -697,6 +960,22 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
697
960
|
return;
|
|
698
961
|
}
|
|
699
962
|
if (decision === "Ignore") return;
|
|
963
|
+
if (handshake.session === void 0 || handshake.serverTime === void 0) {
|
|
964
|
+
requestReload();
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (rememberedHandshake?.session !== void 0 && rememberedHandshake.session.sessionEpoch !== handshake.session.sessionEpoch) {
|
|
968
|
+
queryCache.invalidatePrivate();
|
|
969
|
+
yield* Effect.forEach(HashMap.keys(invocations), (id) => rejectInvocation(id, ServerError.make({
|
|
970
|
+
code: "SessionChanged",
|
|
971
|
+
message: "The session changed. This operation may already have completed."
|
|
972
|
+
})));
|
|
973
|
+
} else if (rememberedHandshake?.session?.viewRevision !== handshake.session.viewRevision) queryCache.invalidatePrivate();
|
|
974
|
+
yield* Effect.forEach(HashMap.entries(invocations), ([id, pending]) => pending.sessionEpoch === handshake.session.sessionEpoch ? Effect.void : rejectInvocation(id, ServerError.make({
|
|
975
|
+
code: "SessionChanged",
|
|
976
|
+
message: "The session changed. This operation may already have completed."
|
|
977
|
+
})));
|
|
978
|
+
session.accept(handshake.session, handshake.serverTime);
|
|
700
979
|
rememberedHandshake = handshake;
|
|
701
980
|
handshakeAccepted = true;
|
|
702
981
|
writer = write;
|
|
@@ -722,9 +1001,14 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
722
1001
|
});
|
|
723
1002
|
return yield* handleMessage(message);
|
|
724
1003
|
});
|
|
1004
|
+
const suspended = yield* Deferred.make();
|
|
1005
|
+
sessionSuspended = suspended;
|
|
1006
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
1007
|
+
if (sessionSuspended === suspended) sessionSuspended = void 0;
|
|
1008
|
+
}));
|
|
725
1009
|
const run = yield* socket.runString(handleText).pipe(Effect.forkChild);
|
|
726
1010
|
yield* Effect.raceFirst(Deferred.await(handshakeReceived).pipe(Effect.timeout("10 seconds")), Fiber.join(run));
|
|
727
|
-
yield* Fiber.join(run);
|
|
1011
|
+
yield* Effect.raceFirst(Fiber.join(run), Deferred.await(suspended));
|
|
728
1012
|
})).pipe(Effect.mapError((cause) => Schema.is(ClientInfrastructureError)(cause) ? cause : ConnectionUnavailable.make({
|
|
729
1013
|
cause,
|
|
730
1014
|
message: "The sync connection was lost."
|
|
@@ -735,6 +1019,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
735
1019
|
return yield* queryCache.observe(functionAddress, args);
|
|
736
1020
|
});
|
|
737
1021
|
const mutate = Effect.fn("SyncClient.mutate")(function* (functionAddress, args) {
|
|
1022
|
+
const admittedSession = session.current() ?? rememberedHandshake?.session ?? (yield* session.bootstrap.pipe(Effect.mapError((error) => ConnectionUnavailable.make({ message: error.message }))));
|
|
738
1023
|
const invocationId = yield* ids.generate(InvocationId);
|
|
739
1024
|
const preparedArguments = yield* prepareMutationArguments(args).pipe(Effect.provideService(IdGenerator, ids), Effect.mapError((cause) => InvalidMutationArguments.make({
|
|
740
1025
|
cause,
|
|
@@ -767,6 +1052,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
767
1052
|
files: preparedArguments.fileIds
|
|
768
1053
|
};
|
|
769
1054
|
invocations = HashMap.set(invocations, invocationId, {
|
|
1055
|
+
sessionEpoch: admittedSession.sessionEpoch,
|
|
770
1056
|
deferred,
|
|
771
1057
|
function: functionAddress,
|
|
772
1058
|
message,
|
|
@@ -781,6 +1067,7 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
781
1067
|
const uploads = yield* Deferred.await(prepared);
|
|
782
1068
|
const expected = new Set(message.files);
|
|
783
1069
|
const received = /* @__PURE__ */ new Set();
|
|
1070
|
+
const transfers = [];
|
|
784
1071
|
for (const upload of uploads) {
|
|
785
1072
|
if (!expected.has(upload.id) || received.has(upload.id)) return yield* InvalidServerMessage.make({
|
|
786
1073
|
cause: /* @__PURE__ */ new Error(`Unexpected prepared file '${upload.id}'.`),
|
|
@@ -793,12 +1080,22 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
793
1080
|
message: "The server prepared an unknown application file."
|
|
794
1081
|
});
|
|
795
1082
|
if (upload.url === void 0) continue;
|
|
796
|
-
|
|
1083
|
+
transfers.push({
|
|
1084
|
+
file,
|
|
1085
|
+
url: upload.url
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
if (received.size !== expected.size) return yield* InvalidServerMessage.make({
|
|
1089
|
+
cause: /* @__PURE__ */ new Error("The server omitted a prepared file."),
|
|
1090
|
+
message: "The server returned incomplete application file preparation data."
|
|
1091
|
+
});
|
|
1092
|
+
yield* Effect.forEach(transfers, Effect.fn("SyncClient.uploadFile")(function* ({ file, url }) {
|
|
797
1093
|
const response = yield* Effect.tryPromise({
|
|
798
|
-
try: () => fetch(
|
|
1094
|
+
try: (signal) => fetch(url, {
|
|
799
1095
|
method: "PUT",
|
|
800
1096
|
body: file,
|
|
801
|
-
headers: { "content-type": file.type }
|
|
1097
|
+
headers: { "content-type": file.type },
|
|
1098
|
+
signal
|
|
802
1099
|
}),
|
|
803
1100
|
catch: (cause) => ConnectionUnavailable.make({
|
|
804
1101
|
cause,
|
|
@@ -813,12 +1110,11 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
813
1110
|
message: `File '${file.name}' was rejected by application storage.`
|
|
814
1111
|
});
|
|
815
1112
|
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
message: "The server returned incomplete application file preparation data."
|
|
1113
|
+
}), {
|
|
1114
|
+
concurrency: 3,
|
|
1115
|
+
discard: true
|
|
820
1116
|
});
|
|
821
|
-
const pendingInvocation = HashMap.
|
|
1117
|
+
const pendingInvocation = Option.getOrUndefined(HashMap.get(invocations, invocationId));
|
|
822
1118
|
if (pendingInvocation === void 0) return yield* Deferred.await(deferred);
|
|
823
1119
|
invocations = HashMap.set(invocations, invocationId, {
|
|
824
1120
|
...pendingInvocation,
|
|
@@ -840,10 +1136,60 @@ var SyncClient = class SyncClient extends Context.Service()("ignotum/client/sync
|
|
|
840
1136
|
});
|
|
841
1137
|
})).pipe(Layer.provide(Layer.merge(Socket.layerWebSocketConstructorGlobal, BrowserCrypto.layer)));
|
|
842
1138
|
};
|
|
843
|
-
const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(clientErrorReporterLayer), Layer.provideMerge(IdGenerator.layer)));
|
|
1139
|
+
const syncRuntime = ManagedRuntime.make(SyncClient.layer.pipe(Layer.provideMerge(BrowserSession.layer), Layer.provideMerge(clientErrorReporterLayer), Layer.provideMerge(IdGenerator.layer)));
|
|
844
1140
|
const hot = import.meta.hot;
|
|
845
1141
|
if (hot !== void 0) hot.dispose(() => void syncRuntime.dispose());
|
|
846
1142
|
//#endregion
|
|
1143
|
+
//#region src/client/page-observers.ts
|
|
1144
|
+
const makePageObservers = (onChange, releaseObserver) => {
|
|
1145
|
+
const pages = /* @__PURE__ */ new Map();
|
|
1146
|
+
let disposed = false;
|
|
1147
|
+
const releaseEntry = (entry) => {
|
|
1148
|
+
entry.released = true;
|
|
1149
|
+
entry.release?.();
|
|
1150
|
+
};
|
|
1151
|
+
return {
|
|
1152
|
+
observe: Effect.fn("PageObservers.observe")(function* (functionAddress, args) {
|
|
1153
|
+
if (disposed) return;
|
|
1154
|
+
const key = syncClientInternals.queryIdentity(functionAddress, args);
|
|
1155
|
+
const existing = pages.get(key);
|
|
1156
|
+
if (existing !== void 0) return yield* existing.acquire;
|
|
1157
|
+
const entry = {
|
|
1158
|
+
acquire: yield* Effect.cached(Effect.gen(function* () {
|
|
1159
|
+
const observer = yield* (yield* SyncClient).observe(functionAddress, args);
|
|
1160
|
+
if (entry.released) {
|
|
1161
|
+
releaseObserver(observer.release);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const unsubscribe = observer.subscribe(onChange);
|
|
1165
|
+
entry.release = () => {
|
|
1166
|
+
unsubscribe();
|
|
1167
|
+
releaseObserver(observer.release);
|
|
1168
|
+
};
|
|
1169
|
+
return observer;
|
|
1170
|
+
})),
|
|
1171
|
+
release: void 0,
|
|
1172
|
+
released: false
|
|
1173
|
+
};
|
|
1174
|
+
pages.set(key, entry);
|
|
1175
|
+
return yield* entry.acquire;
|
|
1176
|
+
}),
|
|
1177
|
+
retain: (keys) => {
|
|
1178
|
+
for (const [key, entry] of pages) {
|
|
1179
|
+
if (keys.has(key)) continue;
|
|
1180
|
+
pages.delete(key);
|
|
1181
|
+
releaseEntry(entry);
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
release: () => {
|
|
1185
|
+
if (disposed) return;
|
|
1186
|
+
disposed = true;
|
|
1187
|
+
for (const entry of pages.values()) releaseEntry(entry);
|
|
1188
|
+
pages.clear();
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
};
|
|
1192
|
+
//#endregion
|
|
847
1193
|
//#region src/client/hooks.ts
|
|
848
1194
|
function useQuery(reference, args) {
|
|
849
1195
|
const functionPath = functionPathOf(reference);
|
|
@@ -916,6 +1262,10 @@ function usePaginatedQuery(reference, args, options = {}) {
|
|
|
916
1262
|
result: pending()
|
|
917
1263
|
}));
|
|
918
1264
|
const loadMore = useCallback$1(() => setRequestedPages((count) => count + 1), []);
|
|
1265
|
+
const pages = useMemo$1(() => makePageObservers(() => setRefresh((version) => version + 1), (release) => {
|
|
1266
|
+
syncRuntime.runFork(release);
|
|
1267
|
+
}), [identity, pageSize]);
|
|
1268
|
+
useEffect$1(() => () => pages.release(), [pages]);
|
|
919
1269
|
useDebugValue$1({
|
|
920
1270
|
args: input,
|
|
921
1271
|
function: functionPath,
|
|
@@ -931,9 +1281,8 @@ function usePaginatedQuery(reference, args, options = {}) {
|
|
|
931
1281
|
useEffect$1(() => {
|
|
932
1282
|
if (skipped) return;
|
|
933
1283
|
let active = true;
|
|
934
|
-
const
|
|
1284
|
+
const usedPages = /* @__PURE__ */ new Set();
|
|
935
1285
|
syncRuntime.runPromise(Effect.gen(function* () {
|
|
936
|
-
const client = yield* SyncClient;
|
|
937
1286
|
const items = [];
|
|
938
1287
|
let cursor = null;
|
|
939
1288
|
for (let pageNumber = 0; pageNumber < requestedPages; pageNumber += 1) {
|
|
@@ -944,18 +1293,9 @@ function usePaginatedQuery(reference, args, options = {}) {
|
|
|
944
1293
|
pageSize
|
|
945
1294
|
}
|
|
946
1295
|
});
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
const releaseSubscription = observer.subscribe(() => {
|
|
953
|
-
if (active) setRefresh((version) => version + 1);
|
|
954
|
-
});
|
|
955
|
-
releases.push(() => {
|
|
956
|
-
releaseSubscription();
|
|
957
|
-
syncRuntime.runFork(observer.release);
|
|
958
|
-
});
|
|
1296
|
+
usedPages.add(syncClientInternals.queryIdentity(functionPath, pageArgs));
|
|
1297
|
+
const observer = yield* pages.observe(functionPath, pageArgs);
|
|
1298
|
+
if (!active || observer === void 0) return;
|
|
959
1299
|
const snapshot = observer.getSnapshot();
|
|
960
1300
|
if (Schema.is(ClientInfrastructureError)(snapshot)) {
|
|
961
1301
|
setState({
|
|
@@ -968,7 +1308,7 @@ function usePaginatedQuery(reference, args, options = {}) {
|
|
|
968
1308
|
if (inspected === void 0) {
|
|
969
1309
|
setState({
|
|
970
1310
|
identity,
|
|
971
|
-
result: items.length === 0 ? pending() : Result$
|
|
1311
|
+
result: items.length === 0 ? pending() : Result$2.succeed({
|
|
972
1312
|
items,
|
|
973
1313
|
loadMore,
|
|
974
1314
|
status: "LoadingMore"
|
|
@@ -989,7 +1329,7 @@ function usePaginatedQuery(reference, args, options = {}) {
|
|
|
989
1329
|
if (cursor === null) {
|
|
990
1330
|
setState({
|
|
991
1331
|
identity,
|
|
992
|
-
result: Result$
|
|
1332
|
+
result: Result$2.succeed({
|
|
993
1333
|
items,
|
|
994
1334
|
loadMore,
|
|
995
1335
|
status: "Exhausted"
|
|
@@ -1000,13 +1340,15 @@ function usePaginatedQuery(reference, args, options = {}) {
|
|
|
1000
1340
|
}
|
|
1001
1341
|
setState({
|
|
1002
1342
|
identity,
|
|
1003
|
-
result: Result$
|
|
1343
|
+
result: Result$2.succeed({
|
|
1004
1344
|
items,
|
|
1005
1345
|
loadMore,
|
|
1006
1346
|
status: "CanLoadMore"
|
|
1007
1347
|
})
|
|
1008
1348
|
});
|
|
1009
|
-
})
|
|
1349
|
+
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
1350
|
+
if (active) pages.retain(usedPages);
|
|
1351
|
+
})))).catch((error) => {
|
|
1010
1352
|
if (active && Schema.is(ClientInfrastructureError)(error)) setState({
|
|
1011
1353
|
identity,
|
|
1012
1354
|
result: error
|
|
@@ -1014,13 +1356,13 @@ function usePaginatedQuery(reference, args, options = {}) {
|
|
|
1014
1356
|
});
|
|
1015
1357
|
return () => {
|
|
1016
1358
|
active = false;
|
|
1017
|
-
for (const release of releases) release();
|
|
1018
1359
|
};
|
|
1019
1360
|
}, [
|
|
1020
1361
|
functionPath,
|
|
1021
1362
|
identity,
|
|
1022
1363
|
loadMore,
|
|
1023
1364
|
pageSize,
|
|
1365
|
+
pages,
|
|
1024
1366
|
refresh,
|
|
1025
1367
|
requestedPages,
|
|
1026
1368
|
skipped
|
|
@@ -1040,6 +1382,38 @@ const useMutation = (reference) => useMemo$1(() => {
|
|
|
1040
1382
|
};
|
|
1041
1383
|
return mutate;
|
|
1042
1384
|
}, [reference]);
|
|
1385
|
+
function useId() {
|
|
1386
|
+
const [state, setState] = useState$1({ status: "pending" });
|
|
1387
|
+
useEffect$1(() => {
|
|
1388
|
+
let active = true;
|
|
1389
|
+
let unsubscribe;
|
|
1390
|
+
syncRuntime.runPromise(BrowserSession).then((session) => {
|
|
1391
|
+
if (!active) return;
|
|
1392
|
+
const update = () => setState(session.snapshot());
|
|
1393
|
+
unsubscribe = session.subscribe(update);
|
|
1394
|
+
update();
|
|
1395
|
+
});
|
|
1396
|
+
return () => {
|
|
1397
|
+
active = false;
|
|
1398
|
+
unsubscribe?.();
|
|
1399
|
+
};
|
|
1400
|
+
}, []);
|
|
1401
|
+
const actions = useMemo$1(() => ({
|
|
1402
|
+
signIn: (options) => syncRuntime.runPromise(Effect.gen(function* () {
|
|
1403
|
+
return yield* (yield* BrowserSession).start("signIn", options?.profile ?? [], options?.returnTo);
|
|
1404
|
+
})),
|
|
1405
|
+
requestProfile: (fields, options) => syncRuntime.runPromise(Effect.gen(function* () {
|
|
1406
|
+
return yield* (yield* BrowserSession).start("requestProfile", fields, options?.returnTo);
|
|
1407
|
+
})),
|
|
1408
|
+
signOut: () => syncRuntime.runPromise(Effect.gen(function* () {
|
|
1409
|
+
return yield* (yield* BrowserSession).signOut;
|
|
1410
|
+
}))
|
|
1411
|
+
}), []);
|
|
1412
|
+
return {
|
|
1413
|
+
...state,
|
|
1414
|
+
...actions
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1043
1417
|
//#endregion
|
|
1044
1418
|
//#region src/client/app.ts
|
|
1045
1419
|
const app = (definition) => {
|
|
@@ -1048,8 +1422,8 @@ const app = (definition) => {
|
|
|
1048
1422
|
};
|
|
1049
1423
|
//#endregion
|
|
1050
1424
|
//#region src/client/index.ts
|
|
1051
|
-
const Result = { match: Result$
|
|
1425
|
+
const Result = { match: Result$2.match };
|
|
1052
1426
|
//#endregion
|
|
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 };
|
|
1427
|
+
export { Component, Files, Fragment, Query, Result, app, cloneElement, createContext, createElement, createRef, h, isValidElement, toChildArray, useCallback, useContext, useDebugValue, useEffect, useElementId, useErrorBoundary, useId, useImperativeHandle, useLayoutEffect, useMemo, useMutation, usePaginatedQuery, useQuery, useReducer, useRef, useState };
|
|
1054
1428
|
|
|
1055
1429
|
//# sourceMappingURL=client.js.map
|