ignotum 0.0.18 → 0.0.19
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 +73 -62
- package/dist/cli/Undici-C6Asbalt.mjs +30 -0
- package/dist/cli/Undici-C6Asbalt.mjs.map +1 -0
- package/dist/cli/bin.mjs +732 -24022
- package/dist/cli/bin.mjs.map +1 -1
- package/dist/platform/_headers +1 -1
- package/dist/platform/_ignotum/v1.1/client.js +12 -0
- package/dist/runtime/{api-BnUKx9cj-D49Tf0rX.d.ts → api-BnUKx9cj-K-F-SOLs.d.ts} +4 -4
- package/dist/runtime/{api-C-tltz6s.js → api-CrrSjYL1.js} +2 -2
- package/dist/runtime/{api-C-tltz6s.js.map → api-CrrSjYL1.js.map} +1 -1
- package/dist/runtime/{bootstrap-DlNIfH-r.js → bootstrap-CLVnD5vS.js} +4 -4
- package/dist/runtime/bootstrap-CLVnD5vS.js.map +1 -0
- package/dist/runtime/{bootstrap-DjTwGJao-DBx5zdFI.d.ts → bootstrap-DjTwGJao-DPi768T4.d.ts} +3 -3
- package/dist/runtime/client.d.ts +9 -9
- package/dist/runtime/client.js +4 -4
- package/dist/runtime/id-BPFV3D3V.d.ts +1 -0
- package/dist/runtime/{id-7an3nxTu-DZJSM5xh.d.ts → id-D87q-n2D-CDMwTSUR.d.ts} +2 -2
- package/dist/runtime/{id-BrEnxY-r.js → id-rzjaCxeF.js} +11 -2
- package/dist/runtime/{id-BrEnxY-r.js.map → id-rzjaCxeF.js.map} +1 -1
- package/dist/runtime/{index-Biaw5vdF.d.ts → index-BWHInzfU.d.ts} +3 -3
- package/dist/runtime/internal/api.d.ts +1 -1
- package/dist/runtime/internal/api.js +1 -1
- package/dist/runtime/internal/bootstrap.d.ts +1 -1
- package/dist/runtime/internal/bootstrap.js +1 -1
- package/dist/runtime/internal/client.d.ts +1 -1
- package/dist/runtime/internal/client.js +4 -4
- package/dist/runtime/internal/client.js.map +1 -1
- package/dist/runtime/internal/host.d.ts +6 -6
- package/dist/runtime/internal/host.js +3 -3
- package/dist/runtime/internal/routes.d.ts +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 +2 -2
- package/dist/runtime/internal/types.js +1 -1
- package/dist/runtime/pagination-BPFV3D3V.d.ts +1 -0
- package/dist/runtime/{pagination--f4kMKf6.js → pagination-CfEleffG.js} +3 -2
- package/dist/runtime/pagination-CfEleffG.js.map +1 -0
- package/dist/runtime/result-Bq32FA7g.d.ts +1 -0
- package/dist/runtime/{router-C1eC9yy5.js → router-CfNPtoVO.js} +109 -60
- package/dist/runtime/router-CfNPtoVO.js.map +1 -0
- package/dist/runtime/{schema-BSjepoNF.js → schema-JtFb4BbG.js} +3 -3
- package/dist/runtime/{schema-BSjepoNF.js.map → schema-JtFb4BbG.js.map} +1 -1
- package/dist/runtime/server.d.ts +5 -5
- package/dist/runtime/server.js +2 -2
- package/dist/runtime/{sync-By2hX5ev.d.ts → sync-BMmV8oYt.d.ts} +2 -2
- package/dist/runtime/{sync-BwgS19Xy.js → sync-DUfp7S5R.js} +2 -2
- package/dist/runtime/{sync-BwgS19Xy.js.map → sync-DUfp7S5R.js.map} +1 -1
- package/dist/runtime/{types-DeCCyqLU-CHx7KFtx.d.ts → types-BeT6F0Pg-DMvRDN0c.d.ts} +5 -5
- package/package.json +9 -8
- package/src/cli/app-configuration.ts +44 -5
- package/src/cli/auth-client.ts +3 -3
- package/src/cli/build/managed-server.ts +3 -2
- package/src/cli/build/server.ts +2 -2
- package/src/cli/command.ts +18 -11
- package/src/cli/control-client.ts +2 -2
- package/src/cli/deploy.ts +27 -2
- package/src/cli/dev.ts +1 -1
- package/src/cli/managed-dev-platform.ts +13 -4
- package/src/cli/module-boundaries.ts +1 -1
- package/src/cli/new-app.ts +35 -6
- package/src/cli/package-manager.ts +1 -1
- package/src/cli/route-static.ts +7 -2
- package/src/cli/typecheck.ts +66 -0
- package/src/cli/upload-journal.ts +3 -3
- package/src/dev-runtime/database.ts +1 -1
- package/src/dev-runtime/files.ts +1 -1
- package/src/dev-runtime/managed-functions.ts +3 -2
- package/src/dev-runtime/managed-sync.ts +114 -105
- package/src/dev-runtime/managed-websocket.ts +11 -7
- package/src/dev-runtime/migrations.ts +2 -2
- package/src/dev-runtime/sync.ts +3 -3
- package/dist/platform/_ignotum/v1.0/client.js +0 -12
- package/dist/runtime/bootstrap-DlNIfH-r.js.map +0 -1
- package/dist/runtime/id-BI08JkmR.d.ts +0 -1
- package/dist/runtime/pagination--f4kMKf6.js.map +0 -1
- package/dist/runtime/pagination-BI08JkmR.d.ts +0 -1
- package/dist/runtime/result-BkziOG1L.d.ts +0 -1
- package/dist/runtime/router-C1eC9yy5.js.map +0 -1
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
import { idViewKey, type SessionState } from "@ignotum/contracts/id";
|
|
11
11
|
import type { ViteDevServer } from "vite";
|
|
12
12
|
import { Clock, Deferred, Effect, PubSub, Schema, Semaphore } from "effect";
|
|
13
|
-
import { SqlClient } from "effect/
|
|
14
|
-
import * as Socket from "effect/
|
|
13
|
+
import { SqlClient } from "effect/sql";
|
|
14
|
+
import * as Socket from "effect/socket/Socket";
|
|
15
15
|
import { LocalApplicationFiles } from "./files.js";
|
|
16
16
|
import { MutationReplay, QueryInvalidation, createLocalSession } from "./sync.js";
|
|
17
17
|
|
|
@@ -36,7 +36,7 @@ export const runManagedSession = Effect.fn("ManagedLocalSync.runSession")(functi
|
|
|
36
36
|
const shutdown = yield* Deferred.make<void>();
|
|
37
37
|
const replays = yield* MutationReplay;
|
|
38
38
|
const invalidation = yield* QueryInvalidation;
|
|
39
|
-
const write = yield* socket.writer;
|
|
39
|
+
const { write } = yield* socket.writer;
|
|
40
40
|
const writes = yield* Semaphore.make(1);
|
|
41
41
|
const messages = yield* Semaphore.make(1);
|
|
42
42
|
let selection: ServingSelection | undefined;
|
|
@@ -152,112 +152,121 @@ export const runManagedSession = Effect.fn("ManagedLocalSync.runSession")(functi
|
|
|
152
152
|
yield* Effect.addFinalizer(() =>
|
|
153
153
|
Effect.forEach(preparations, (id) => files.releasePreparation(id)),
|
|
154
154
|
);
|
|
155
|
-
yield* socket
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
Effect.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return yield* error("InvalidMessage", "Connect has already been accepted.");
|
|
170
|
-
const current = options.selection();
|
|
171
|
-
if (message.protocolVersion !== 1 || !sameSelection(message.selection, current)) {
|
|
172
|
-
yield* send({ type: "UpgradeRequired", selection: current });
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
if (!(yield* checkIdentity())) return;
|
|
176
|
-
selection = current;
|
|
177
|
-
clientSessionId = message.clientSessionId;
|
|
178
|
-
const versionId =
|
|
179
|
-
options.versionId ?? VersionId.make(`dep_${current.appId.slice("app_".length)}`);
|
|
180
|
-
session = yield* createLocalSession(current, versionId, identity, deliver);
|
|
181
|
-
yield* send({
|
|
182
|
-
type: "Handshake",
|
|
183
|
-
protocolVersion: 1,
|
|
184
|
-
selection,
|
|
185
|
-
session: identity,
|
|
186
|
-
serverTime: yield* Clock.currentTimeMillis,
|
|
187
|
-
});
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (selection === undefined || session === undefined)
|
|
191
|
-
return yield* error("InvalidMessage", "Connect must be accepted first.");
|
|
192
|
-
if (expired || !(yield* checkSelection())) return;
|
|
193
|
-
if (!(yield* checkIdentity())) return;
|
|
194
|
-
if (message.type === "RecoverMutation")
|
|
195
|
-
return yield* outcome(message.id, message.identity);
|
|
196
|
-
if (message.type === "Invoke" || message.type === "Prepare") {
|
|
197
|
-
const input = canonicalInvocationInput(
|
|
198
|
-
options.versionId ?? VersionId.make(`dep_${selection.appId.slice("app_".length)}`),
|
|
199
|
-
"Mutation",
|
|
200
|
-
message.function,
|
|
201
|
-
message.args,
|
|
202
|
-
identity.sessionEpoch,
|
|
203
|
-
);
|
|
204
|
-
const rows = yield* ownerRows(message.invocationId).pipe(Effect.orDie);
|
|
205
|
-
if (rows[0] !== undefined && (!owns(rows[0], selection) || rows[0].input !== input))
|
|
206
|
-
return yield* error(
|
|
207
|
-
"InvocationIdConflict",
|
|
208
|
-
"This invocation belongs to another input, selection or session.",
|
|
209
|
-
message.invocationId,
|
|
155
|
+
const pull = yield* Socket.readerString(socket);
|
|
156
|
+
yield* pull.pipe(
|
|
157
|
+
Effect.flatMap((batch) =>
|
|
158
|
+
Effect.forEach(
|
|
159
|
+
batch,
|
|
160
|
+
(text) =>
|
|
161
|
+
messages.withPermits(1)(
|
|
162
|
+
Effect.gen(function* () {
|
|
163
|
+
const message = yield* Schema.decodeEffect(
|
|
164
|
+
Schema.fromJsonString(ManagedClientMessage),
|
|
165
|
+
)(text).pipe(
|
|
166
|
+
Effect.catch(() =>
|
|
167
|
+
error("InvalidMessage", "The frame is not valid managed sync JSON."),
|
|
168
|
+
),
|
|
210
169
|
);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
)
|
|
233
|
-
return yield* error(
|
|
234
|
-
"InvocationIdConflict",
|
|
235
|
-
"This invocation belongs to another session.",
|
|
236
|
-
message.invocationId,
|
|
237
|
-
);
|
|
238
|
-
if (inserted.length === 0) {
|
|
239
|
-
if (admitted[0].prepared === 0)
|
|
240
|
-
return yield* outcome(message.invocationId, {
|
|
241
|
-
invocationId: message.invocationId,
|
|
242
|
-
originalSelection: selection,
|
|
170
|
+
if (message === undefined) return;
|
|
171
|
+
if (message.type === "Connect") {
|
|
172
|
+
if (selection !== undefined)
|
|
173
|
+
return yield* error("InvalidMessage", "Connect has already been accepted.");
|
|
174
|
+
const current = options.selection();
|
|
175
|
+
if (message.protocolVersion !== 1 || !sameSelection(message.selection, current)) {
|
|
176
|
+
yield* send({ type: "UpgradeRequired", selection: current });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!(yield* checkIdentity())) return;
|
|
180
|
+
selection = current;
|
|
181
|
+
clientSessionId = message.clientSessionId;
|
|
182
|
+
const versionId =
|
|
183
|
+
options.versionId ?? VersionId.make(`dep_${current.appId.slice("app_".length)}`);
|
|
184
|
+
session = yield* createLocalSession(current, versionId, identity, deliver);
|
|
185
|
+
yield* send({
|
|
186
|
+
type: "Handshake",
|
|
187
|
+
protocolVersion: 1,
|
|
188
|
+
selection,
|
|
189
|
+
session: identity,
|
|
190
|
+
serverTime: yield* Clock.currentTimeMillis,
|
|
243
191
|
});
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (selection === undefined || session === undefined)
|
|
195
|
+
return yield* error("InvalidMessage", "Connect must be accepted first.");
|
|
196
|
+
if (expired || !(yield* checkSelection())) return;
|
|
197
|
+
if (!(yield* checkIdentity())) return;
|
|
198
|
+
if (message.type === "RecoverMutation")
|
|
199
|
+
return yield* outcome(message.id, message.identity);
|
|
200
|
+
if (message.type === "Invoke" || message.type === "Prepare") {
|
|
201
|
+
const input = canonicalInvocationInput(
|
|
202
|
+
options.versionId ??
|
|
203
|
+
VersionId.make(`dep_${selection.appId.slice("app_".length)}`),
|
|
204
|
+
"Mutation",
|
|
205
|
+
message.function,
|
|
206
|
+
message.args,
|
|
207
|
+
identity.sessionEpoch,
|
|
208
|
+
);
|
|
209
|
+
const rows = yield* ownerRows(message.invocationId).pipe(Effect.orDie);
|
|
210
|
+
if (rows[0] !== undefined && (!owns(rows[0], selection) || rows[0].input !== input))
|
|
211
|
+
return yield* error(
|
|
212
|
+
"InvocationIdConflict",
|
|
213
|
+
"This invocation belongs to another input, selection or session.",
|
|
214
|
+
message.invocationId,
|
|
215
|
+
);
|
|
216
|
+
if (
|
|
217
|
+
rows[0] === undefined &&
|
|
218
|
+
(yield* replays.lookup(message.invocationId).pipe(Effect.orDie)) !== undefined
|
|
219
|
+
)
|
|
220
|
+
return yield* error(
|
|
221
|
+
"InvocationIdConflict",
|
|
222
|
+
"This invocation already exists outside this managed session.",
|
|
223
|
+
message.invocationId,
|
|
224
|
+
);
|
|
225
|
+
const encodedSelection = yield* Schema.encodeEffect(
|
|
226
|
+
Schema.fromJsonString(ServingSelection),
|
|
227
|
+
)(selection).pipe(Effect.orDie);
|
|
228
|
+
const inserted =
|
|
229
|
+
yield* sql`INSERT OR IGNORE INTO managed_invocation_owners (id, input, prepared, clientSessionId, sessionEpoch, selection) VALUES (${message.invocationId}, ${input}, ${message.type === "Prepare" ? 1 : 0}, ${clientSessionId!}, ${identity.sessionEpoch}, ${encodedSelection}) RETURNING id`.pipe(
|
|
247
230
|
Effect.orDie,
|
|
248
231
|
);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
232
|
+
const admitted = yield* ownerRows(message.invocationId).pipe(Effect.orDie);
|
|
233
|
+
if (
|
|
234
|
+
admitted[0] === undefined ||
|
|
235
|
+
admitted[0].input !== input ||
|
|
236
|
+
!owns(admitted[0], selection)
|
|
237
|
+
)
|
|
238
|
+
return yield* error(
|
|
239
|
+
"InvocationIdConflict",
|
|
240
|
+
"This invocation belongs to another session.",
|
|
241
|
+
message.invocationId,
|
|
242
|
+
);
|
|
243
|
+
if (inserted.length === 0) {
|
|
244
|
+
if (admitted[0].prepared === 0)
|
|
245
|
+
return yield* outcome(message.invocationId, {
|
|
246
|
+
invocationId: message.invocationId,
|
|
247
|
+
originalSelection: selection,
|
|
248
|
+
});
|
|
249
|
+
if (message.type === "Invoke") {
|
|
250
|
+
const claimed =
|
|
251
|
+
yield* sql`UPDATE managed_invocation_owners SET prepared = 0 WHERE id = ${message.invocationId} AND prepared = 1 RETURNING id`.pipe(
|
|
252
|
+
Effect.orDie,
|
|
253
|
+
);
|
|
254
|
+
if (claimed.length === 0)
|
|
255
|
+
return yield* outcome(message.invocationId, {
|
|
256
|
+
invocationId: message.invocationId,
|
|
257
|
+
originalSelection: selection,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
preparations.add(message.invocationId);
|
|
254
262
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}),
|
|
263
|
+
yield* session.handle(message);
|
|
264
|
+
}),
|
|
265
|
+
),
|
|
266
|
+
{ discard: true },
|
|
260
267
|
),
|
|
261
|
-
)
|
|
262
|
-
|
|
268
|
+
),
|
|
269
|
+
Effect.forever,
|
|
270
|
+
Effect.raceFirst(Deferred.await(shutdown)),
|
|
271
|
+
);
|
|
263
272
|
}, Effect.scoped);
|
|
@@ -3,8 +3,8 @@ import type { IncomingMessage } from "node:http";
|
|
|
3
3
|
// @effect-diagnostics-next-line nodeBuiltinImport:off Vite supplies the raw Node upgrade socket.
|
|
4
4
|
import type { Duplex } from "node:stream";
|
|
5
5
|
import { NodeSocket } from "@effect/platform-node";
|
|
6
|
-
import { Cause, Context, Effect, FiberSet, Function, Layer } from "effect";
|
|
7
|
-
import * as Socket from "effect/
|
|
6
|
+
import { Cause, Context, Effect, FiberSet, Function, Layer, Option } from "effect";
|
|
7
|
+
import * as Socket from "effect/socket/Socket";
|
|
8
8
|
|
|
9
9
|
/** Owns upgraded sessions without sending an HTTP response when a session ends. */
|
|
10
10
|
const makeManagedWebSocketUpgradeHandler = Effect.fn("ManagedWebSocket.makeUpgradeHandler")(
|
|
@@ -26,18 +26,22 @@ const makeManagedWebSocketUpgradeHandler = Effect.fn("ManagedWebSocket.makeUpgra
|
|
|
26
26
|
}),
|
|
27
27
|
(webSocket) => Effect.sync(() => webSocket.close()),
|
|
28
28
|
),
|
|
29
|
-
{ closeCodeIsError: Function.constFalse },
|
|
30
29
|
);
|
|
31
30
|
yield* handle(socket, request);
|
|
32
31
|
}).pipe(
|
|
33
32
|
Effect.scoped,
|
|
34
|
-
Effect.catchCause((cause) =>
|
|
35
|
-
Cause.
|
|
33
|
+
Effect.catchCause((cause) => {
|
|
34
|
+
const failure = Cause.findErrorOption(cause);
|
|
35
|
+
return Cause.hasInterruptsOnly(cause) ||
|
|
36
|
+
(Option.isSome(failure) &&
|
|
37
|
+
Socket.isSocketError(failure.value) &&
|
|
38
|
+
failure.value.reason._tag === "SocketCloseError" &&
|
|
39
|
+
(failure.value.reason.code === 1000 || failure.value.reason.code === 1001))
|
|
36
40
|
? Effect.void
|
|
37
41
|
: Effect.logError("A managed local WebSocket session failed.").pipe(
|
|
38
42
|
Effect.annotateLogs({ cause }),
|
|
39
|
-
)
|
|
40
|
-
),
|
|
43
|
+
);
|
|
44
|
+
}),
|
|
41
45
|
);
|
|
42
46
|
const fiber = run(session);
|
|
43
47
|
const onClose = () => fiber.interruptUnsafe();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Context, Effect, Layer } from "effect";
|
|
2
|
-
import * as Migrator from "effect/
|
|
3
|
-
import * as SqlClient from "effect/
|
|
2
|
+
import * as Migrator from "effect/sql/Migrator";
|
|
3
|
+
import * as SqlClient from "effect/sql/SqlClient";
|
|
4
4
|
|
|
5
5
|
const initialDevelopmentSchema = Effect.gen(function* () {
|
|
6
6
|
const sql = yield* SqlClient.SqlClient;
|
package/src/dev-runtime/sync.ts
CHANGED
|
@@ -44,9 +44,9 @@ import {
|
|
|
44
44
|
Schema,
|
|
45
45
|
Semaphore,
|
|
46
46
|
} from "effect";
|
|
47
|
-
import { HttpServerRequest, HttpServerResponse } from "effect/
|
|
48
|
-
import { SqlClient, SqlError, SqlSchema } from "effect/
|
|
49
|
-
import type * as Socket from "effect/
|
|
47
|
+
import { HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
48
|
+
import { SqlClient, SqlError, SqlSchema } from "effect/sql";
|
|
49
|
+
import type * as Socket from "effect/socket/Socket";
|
|
50
50
|
import type { Plugin, ViteDevServer } from "vite";
|
|
51
51
|
import { FunctionRuntime, type PreparedFunction } from "@ignotum/runtime/functions";
|
|
52
52
|
import {
|