@rikalabs/akter-cli 0.0.0-stage → 0.1.0-alpha.3

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/dist/main.js ADDED
@@ -0,0 +1,3257 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.ts
4
+ import { Context as Context2, Effect as Effect23, Layer as Layer5, Stdio as Stdio2 } from "effect";
5
+ import { FetchHttpClient } from "effect/http";
6
+
7
+ // src/cli.ts
8
+ import { Effect as Effect22, Runtime as Runtime2, Schema as Schema29 } from "effect";
9
+ import { CliError, Command as Command19 } from "effect/cli";
10
+ // package.json
11
+ var version = "0.1.0-alpha.3";
12
+
13
+ // src/commands/adopt/run.ts
14
+ import {
15
+ adoptionStatus,
16
+ adoptionWriters,
17
+ backfillAdoption,
18
+ enforceAdoption,
19
+ formatAdoptionPlan,
20
+ formatAdoptionStatus,
21
+ formatBackfill,
22
+ formatEnforce,
23
+ formatObservedWriter,
24
+ observeAdoption,
25
+ planAdoption,
26
+ releaseAdoption
27
+ } from "@rikalabs/akter/runtime";
28
+ import { Clock, Console as Console3, Effect as Effect3, Layer as Layer2, Option } from "effect";
29
+ import { Argument, Command as Command2, Flag as Flag2 } from "effect/cli";
30
+ import { Database as Database2 } from "@rikalabs/akter/runtime";
31
+
32
+ // src/failure.ts
33
+ import { Console, Effect, Runtime, Schema } from "effect";
34
+
35
+ class UsageError extends Schema.TaggedError()("UsageError", {
36
+ message: Schema.String
37
+ }) {
38
+ }
39
+
40
+ class CommandFailed extends Schema.TaggedError()("CommandFailed", {
41
+ exitCode: Schema.Int,
42
+ reason: Schema.String
43
+ }) {
44
+ [Runtime.errorReported] = false;
45
+ get [Runtime.errorExitCode]() {
46
+ return this.exitCode;
47
+ }
48
+ }
49
+ var fail = (failure) => Console.error(failure.message).pipe(Effect.andThen(Effect.fail(CommandFailed.make({ exitCode: failure.exitCode ?? 2, reason: failure.reason }))));
50
+
51
+ // src/platform.ts
52
+ import { createServer } from "node:http";
53
+ var platform = typeof Bun === "undefined" ? await import("@effect/platform-node").then((node) => ({
54
+ crypto: node.NodeCrypto.layer,
55
+ services: node.NodeServices.layer,
56
+ runMain: node.NodeRuntime.runMain,
57
+ httpServer: (options) => node.NodeHttpServer.layer(createServer, { port: options.port, host: options.hostname })
58
+ })) : await import("@effect/platform-bun").then((bun) => ({
59
+ crypto: bun.BunCrypto.layer,
60
+ services: bun.BunServices.layer,
61
+ runMain: bun.BunRuntime.runMain,
62
+ httpServer: (options) => bun.BunHttpServer.layer(options)
63
+ }));
64
+ var PlatformCrypto = { layer: platform.crypto };
65
+ var PlatformServices = { layer: platform.services };
66
+ var PlatformRuntime = { runMain: platform.runMain };
67
+ var PlatformHttpServer = { layer: platform.httpServer };
68
+
69
+ // src/commands/workflows/check.ts
70
+ import { Database, checkWorkflows, formatIncompatibility } from "@rikalabs/akter/runtime";
71
+ import { Console as Console2, Effect as Effect2, Layer, Schema as Schema2 } from "effect";
72
+ import { Command, Flag } from "effect/cli";
73
+ import { pathToFileURL } from "node:url";
74
+ var entryFlags = {
75
+ entry: Flag.File("entry", { mustExist: true }).pipe(Flag.withDescription("The entry module; it exports an `actors` array of actor definitions")),
76
+ databaseUrl: Flag.Redacted("database-url").pipe(Flag.withDescription("The application's Postgres URL")),
77
+ json: Flag.Boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print the report as JSON"))
78
+ };
79
+ var entryCommand = ({
80
+ options,
81
+ reading,
82
+ run
83
+ }) => Effect2.gen(function* () {
84
+ const module = yield* loadEntry(options.entry);
85
+ const actors = yield* actorsOf({ module, entry: options.entry });
86
+ const services = yield* Layer.build(Database.postgres({ url: options.databaseUrl }).pipe(Layer.provideMerge(PlatformCrypto.layer)));
87
+ const { output, exitCode } = yield* run(actors).pipe(Effect2.provideContext(services));
88
+ yield* Console2.log(output);
89
+ if (exitCode !== 0)
90
+ return yield* CommandFailed.make({ exitCode, reason: "Refused" });
91
+ }).pipe(Effect2.scoped, Effect2.catchTags({
92
+ SqlError: (error) => fail({ reason: error._tag, message: `Cannot read ${reading}: ${error.message}` }),
93
+ UsageError: (error) => fail({ reason: error._tag, message: error.message })
94
+ }));
95
+ var Entry = Schema2.Struct({
96
+ actors: Schema2.Array(Schema2.Struct({ name: Schema2.String, api: Schema2.Record(Schema2.String, Schema2.Unknown) }))
97
+ });
98
+ var decodeEntry = Schema2.decodeUnknownEffect(Entry);
99
+ var AnySchema = Schema2.declare(Schema2.isSchema);
100
+ var WorkflowDefinition = Schema2.Struct({
101
+ kind: Schema2.Literal("workflow"),
102
+ tag: Schema2.String,
103
+ payload: AnySchema,
104
+ success: AnySchema,
105
+ error: AnySchema,
106
+ versions: Schema2.Record(Schema2.String, Schema2.Struct({ current: Schema2.Finite, min: Schema2.Finite })),
107
+ registry: Schema2.Struct({
108
+ steps: Schema2.declare((u) => u instanceof Map)
109
+ })
110
+ });
111
+ var isWorkflowDefinition = Schema2.is(WorkflowDefinition);
112
+ var isWorkflowKind = Schema2.is(Schema2.Struct({ kind: Schema2.Literal("workflow") }));
113
+ var loadEntry = (entry) => Effect2.tryPromise({
114
+ try: () => import(pathToFileURL(entry).href),
115
+ catch: (cause) => UsageError.make({
116
+ message: `Cannot load ${entry}: ${cause instanceof Error ? cause.message : String(cause)}`
117
+ })
118
+ });
119
+ var actorsOf = ({ module, entry }) => decodeEntry(module).pipe(Effect2.mapError(() => UsageError.make({ message: `${entry} must export an \`actors\` array of actor definitions` })), Effect2.flatMap(({ actors }) => {
120
+ for (const actor of actors)
121
+ for (const [name, member] of Object.entries(actor.api))
122
+ if (isWorkflowKind(member) && !isWorkflowDefinition(member))
123
+ return Effect2.fail(UsageError.make({
124
+ message: `${entry}: ${actor.name}.${name} is not an Actor.workflow definition`
125
+ }));
126
+ return Effect2.succeed(actors);
127
+ }));
128
+ var report = ({
129
+ incompatibilities,
130
+ json
131
+ }) => ({
132
+ incompatibilities,
133
+ output: json ? JSON.stringify({ compatible: incompatibilities.length === 0, incompatibilities }, null, 2) : incompatibilities.length === 0 ? "Workflows are compatible with every open execution" : [
134
+ ...incompatibilities.map(formatIncompatibility),
135
+ `${incompatibilities.length} incompatibilit${incompatibilities.length === 1 ? "y" : "ies"}; deploy refused (exit 1)`
136
+ ].join(`
137
+ `),
138
+ exitCode: incompatibilities.length === 0 ? 0 : 1
139
+ });
140
+ var check = ({
141
+ actors,
142
+ json
143
+ }) => checkWorkflows(actors).pipe(Effect2.map((incompatibilities) => report({ incompatibilities, json })));
144
+ var checkCommand = Command.make("check", entryFlags, (options) => entryCommand({
145
+ options,
146
+ reading: "workflow state",
147
+ run: (actors) => check({ actors, json: options.json })
148
+ })).pipe(Command.withDescription("Compare the entry's workflows with every open execution, read-only; exit 1 when a deploy would be refused"));
149
+
150
+ // src/commands/adopt/run.ts
151
+ var UNITS = { m: 60000, h: 3600000, d: 86400000 };
152
+ var WINDOW = /^([1-9][0-9]*)([mhd])$/;
153
+ var parseWindow = (text) => {
154
+ const match = WINDOW.exec(text);
155
+ return match === null ? Option.none() : Option.some(Number(match[1]) * UNITS[match[2]]);
156
+ };
157
+ var printJson = (value) => JSON.stringify(value, null, 2);
158
+ var adopt = ({
159
+ options,
160
+ actors
161
+ }) => Effect3.gen(function* () {
162
+ if (options.command === "status") {
163
+ const statuses = yield* adoptionStatus;
164
+ return {
165
+ output: options.json ? printJson({ tables: statuses }) : statuses.length === 0 ? "No table is adopted" : statuses.map(formatAdoptionStatus).join(`
166
+ `),
167
+ exitCode: 0
168
+ };
169
+ }
170
+ if (options.command === "enforce") {
171
+ const enforced = yield* enforceAdoption(actors, {
172
+ only: options.table,
173
+ writerRole: options.writerRole,
174
+ allowedRoles: options.allow,
175
+ quietMs: options.quietMs,
176
+ nowMs: yield* Clock.currentTimeMillis
177
+ });
178
+ return {
179
+ output: options.json ? printJson({ enforced }) : enforced.map(formatEnforce).join(`
180
+ `),
181
+ exitCode: 0
182
+ };
183
+ }
184
+ if (options.command === "release") {
185
+ const released = yield* releaseAdoption(actors, { only: options.table });
186
+ return {
187
+ output: options.json ? printJson({ observing: released }) : released.map((table) => `${table} is observing again`).join(`
188
+ `),
189
+ exitCode: 0
190
+ };
191
+ }
192
+ if (options.command === "plan") {
193
+ const plans = yield* planAdoption(actors, options.table);
194
+ return {
195
+ output: options.json ? printJson({ tables: plans }) : plans.map(formatAdoptionPlan).join(`
196
+
197
+ `),
198
+ exitCode: plans.some((plan) => plan.problems.length > 0) ? 1 : 0
199
+ };
200
+ }
201
+ if (options.command === "backfill") {
202
+ const results = yield* backfillAdoption(actors, {
203
+ only: options.table,
204
+ batch: options.batch
205
+ });
206
+ return {
207
+ output: options.json ? printJson({ tables: results }) : results.map(formatBackfill).join(`
208
+ `),
209
+ exitCode: 0
210
+ };
211
+ }
212
+ if (options.report) {
213
+ const writers = yield* adoptionWriters(actors, {
214
+ only: options.table,
215
+ sinceMs: options.sinceMs,
216
+ clear: options.clear
217
+ });
218
+ return {
219
+ output: options.json ? printJson({ writers }) : writers.length === 0 ? "No write was recorded" : writers.map(formatObservedWriter).join(`
220
+ `),
221
+ exitCode: 0
222
+ };
223
+ }
224
+ const observed = yield* observeAdoption(actors, options.table);
225
+ return {
226
+ output: options.json ? printJson({ observing: observed }) : observed.map((table) => `${table} is observing`).join(`
227
+ `),
228
+ exitCode: 0
229
+ };
230
+ }).pipe(Effect3.catchTag("AdoptionRefused", (refused) => Effect3.succeed({ output: refused.message, exitCode: 1 })));
231
+ var run = (options) => Effect3.gen(function* () {
232
+ const actors = options.entry === undefined ? [] : yield* actorsOf({ module: yield* loadEntry(options.entry), entry: options.entry });
233
+ const services = yield* Layer2.build(Database2.postgres({ url: options.databaseUrl }).pipe(Layer2.provideMerge(PlatformCrypto.layer)));
234
+ const { output, exitCode } = yield* adopt({ options, actors }).pipe(Effect3.provideContext(services));
235
+ yield* Console3.log(output);
236
+ if (exitCode !== 0)
237
+ return yield* CommandFailed.make({ exitCode, reason: "Refused" });
238
+ }).pipe(Effect3.scoped, Effect3.catchTags({
239
+ SqlError: (error) => fail({
240
+ reason: error._tag,
241
+ message: `Cannot read adoption state (is the database migrated to 0024_adoption?): ${error.message}`
242
+ }),
243
+ UsageError: (error) => fail({ reason: error._tag, message: error.message })
244
+ }));
245
+ var defaults = {
246
+ entry: undefined,
247
+ table: undefined,
248
+ report: false,
249
+ clear: false,
250
+ sinceMs: undefined,
251
+ batch: undefined,
252
+ writerRole: undefined,
253
+ allow: [],
254
+ quietMs: undefined
255
+ };
256
+ var table = (verb) => Argument.String("table").pipe(Argument.withDescription(`The adopted table to ${verb}`));
257
+ var planCommand = Command2.make("plan", {
258
+ ...entryFlags,
259
+ table: Flag2.String("table").pipe(Flag2.optional, Flag2.withDescription("Plan only this adopted table"))
260
+ }, (options) => run({
261
+ ...defaults,
262
+ ...options,
263
+ command: "plan",
264
+ table: Option.getOrUndefined(options.table)
265
+ })).pipe(Command2.withDescription("Plan adopting the entry's existing tables, with the SQL each needs; exit 1 while a table has a problem"));
266
+ var observeCommand = Command2.make("observe", {
267
+ ...entryFlags,
268
+ table: table("observe"),
269
+ report: Flag2.Boolean("report").pipe(Flag2.withDefault(false), Flag2.withDescription("Report the writes recorded so far instead of starting to observe")),
270
+ since: Flag2.String("since").pipe(Flag2.filterMap(parseWindow, () => "a window such as 30m, 12h, or 7d"), Flag2.optional, Flag2.withDescription("With --report, only writes within this window, such as 7d")),
271
+ clear: Flag2.Boolean("clear").pipe(Flag2.withDefault(false), Flag2.withDescription("With --report, clear the reported writes"))
272
+ }, (options) => Effect3.gen(function* () {
273
+ if ((Option.isSome(options.since) || options.clear) && !options.report)
274
+ return yield* fail({
275
+ reason: "UsageError",
276
+ message: "--since and --clear belong to observe --report"
277
+ });
278
+ const nowMs = yield* Clock.currentTimeMillis;
279
+ return yield* run({
280
+ ...defaults,
281
+ ...options,
282
+ command: "observe",
283
+ sinceMs: Option.getOrUndefined(Option.map(options.since, (window) => nowMs - window))
284
+ });
285
+ })).pipe(Command2.withDescription("Record which writers still write an adopted table, or report them with --report"));
286
+ var backfillCommand = Command2.make("backfill", {
287
+ ...entryFlags,
288
+ table: table("backfill"),
289
+ batch: Flag2.Int("batch").pipe(Flag2.filter((batch) => batch >= 1, () => "a positive integer"), Flag2.optional, Flag2.withDescription("Rows per pass (default 1000)"))
290
+ }, (options) => run({
291
+ ...defaults,
292
+ ...options,
293
+ command: "backfill",
294
+ batch: Option.getOrUndefined(options.batch)
295
+ })).pipe(Command2.withDescription("Fill routing_key on an observed table's rows, in batches"));
296
+ var enforceCommand = Command2.make("enforce", {
297
+ ...entryFlags,
298
+ table: table("enforce"),
299
+ writerRole: Flag2.String("writer-role").pipe(Flag2.withDescription("The database role the runtime writes the table as")),
300
+ allow: Flag2.String("allow").pipe(Flag2.atLeast(0), Flag2.withDescription("Another role still allowed to write the table; repeat for several")),
301
+ quiet: Flag2.String("quiet").pipe(Flag2.filterMap(parseWindow, () => "a window such as 30m, 12h, or 7d"), Flag2.optional, Flag2.withDescription("How long no legacy write may have been recorded before enforcing, such as 7d"))
302
+ }, (options) => run({
303
+ ...defaults,
304
+ ...options,
305
+ command: "enforce",
306
+ quietMs: Option.getOrUndefined(options.quiet)
307
+ })).pipe(Command2.withDescription("Enforce an adopted table: only the runtime's writer role and --allow roles may write it"));
308
+ var releaseCommand = Command2.make("release", {
309
+ ...entryFlags,
310
+ table: table("release"),
311
+ to: Flag2.Literals("to", ["observe"]).pipe(Flag2.withDescription("The mode to return the table to; only observe"))
312
+ }, (options) => run({ ...defaults, ...options, command: "release" })).pipe(Command2.withDescription("Return an enforced table to observing"));
313
+ var statusCommand = Command2.make("status", { databaseUrl: entryFlags.databaseUrl, json: entryFlags.json }, (options) => run({ ...defaults, ...options, command: "status" })).pipe(Command2.withDescription("Show each adopted table's mode and the rows left to backfill"));
314
+
315
+ // src/commands/dead-letters/repair.ts
316
+ import { Argument as Argument3, Command as Command3, Flag as Flag4 } from "effect/cli";
317
+
318
+ // src/commands/operator/request.ts
319
+ import { Config, Console as Console4, Effect as Effect4, Option as Option2, Redacted, Schema as Schema3 } from "effect";
320
+ import { Argument as Argument2, Flag as Flag3 } from "effect/cli";
321
+ import { HttpClient, HttpClientRequest } from "effect/http";
322
+ var TOKEN_ENV = "AKTER_OPERATOR_TOKEN";
323
+
324
+ class RunnerUnreachable extends Schema3.TaggedError()("RunnerUnreachable", { url: Schema3.String, message: Schema3.String }) {
325
+ }
326
+
327
+ class OperatorRefused extends Schema3.TaggedError()("OperatorRefused", {
328
+ status: Schema3.Finite,
329
+ body: Schema3.String
330
+ }) {
331
+ }
332
+ var urls = Flag3.String("url").pipe(Flag3.atLeast(1), Flag3.map((values) => values.map((value) => value.replace(/\/+$/, ""))), Flag3.withDescription("A runner's base URL; repeat to name several, a single-actor command uses the first"));
333
+ var tenant = Flag3.String("tenant").pipe(Flag3.withDescription("The tenant the request acts in"));
334
+ var operatorFlags = {
335
+ urls,
336
+ tokenEnv: Flag3.String("token-env").pipe(Flag3.withDefault(TOKEN_ENV), Flag3.withDescription(`The environment variable holding the operator bearer token (default ${TOKEN_ENV})`)),
337
+ json: Flag3.Boolean("json").pipe(Flag3.withDefault(false), Flag3.withDescription("Print the runner's answer as JSON"))
338
+ };
339
+ var splitActor = (value) => {
340
+ const slash = value.indexOf("/");
341
+ return slash <= 0 || slash === value.length - 1 ? Option2.none() : Option2.some({ actorType: value.slice(0, slash), actorId: value.slice(slash + 1) });
342
+ };
343
+ var notAnActor = () => "an actor named as <Type>/<id>";
344
+ var actorArgument = Argument2.String("actor").pipe(Argument2.filterMap(splitActor, notAnActor), Argument2.withDescription("The actor, as <Type>/<id>"));
345
+ var actorFlag = (flag) => Flag3.String(flag.name).pipe(Flag3.filterMap(splitActor, notAnActor), Flag3.withDescription(flag.description));
346
+ var reason = Flag3.String("reason").pipe(Flag3.filter((value) => value.length > 0 && value.length <= 500, () => "a reason of 1 to 500 characters"), Flag3.withDescription("Why, recorded in the operator audit log (up to 500 characters)"));
347
+ var operatorToken = (name) => Effect4.map(Config.option(Config.Redacted(name)), (token) => Option2.match(token, { onNone: () => {
348
+ return;
349
+ }, onSome: (value) => Redacted.value(value) }));
350
+ var encodeJson = (answer) => Schema3.encodeEffect(Schema3.fromJsonString(Schema3.Json))(answer).pipe(Effect4.orDie);
351
+ var operatorCommand = ({
352
+ options,
353
+ request,
354
+ format
355
+ }) => Effect4.gen(function* () {
356
+ const token = yield* operatorToken(options.tokenEnv);
357
+ const answer = yield* request({ options, token });
358
+ yield* Console4.log(options.json ? yield* encodeJson(answer) : yield* format(answer));
359
+ }).pipe(Effect4.catchTags({
360
+ RunnerUnreachable: (error) => fail({ reason: error._tag, message: `Cannot reach ${error.url}: ${error.message}` }),
361
+ OperatorRefused: (error) => fail({
362
+ reason: error._tag,
363
+ message: `Refused (${error.status}): ${error.body}`,
364
+ exitCode: 1
365
+ }),
366
+ ConfigError: (error) => fail({ reason: error._tag, message: `Cannot read the operator token: ${error.message}` }),
367
+ SchemaError: (error) => fail({ reason: error._tag, message: `Unexpected answer: ${error.message}` }),
368
+ PlatformError: (error) => fail({ reason: error._tag, message: `Cannot write the file: ${error.message}` })
369
+ }));
370
+ var decodeJson = Schema3.decodeUnknownEffect(Schema3.fromJsonString(Schema3.Json));
371
+ var operatorRequest = Effect4.fnUntraced(function* ({
372
+ url,
373
+ path,
374
+ token,
375
+ body
376
+ }) {
377
+ const client = yield* HttpClient.HttpClient;
378
+ const base = body === undefined ? HttpClientRequest.get(`${url}${path}`) : HttpClientRequest.post(`${url}${path}`);
379
+ const request = (body === undefined ? base : HttpClientRequest.bodyJsonUnsafe(base, body)).pipe(token === undefined ? (same) => same : HttpClientRequest.bearerToken(token));
380
+ const response = yield* client.execute(request).pipe(Effect4.mapError((error) => RunnerUnreachable.make({ url, message: error.message })));
381
+ const text = yield* response.text.pipe(Effect4.mapError((error) => RunnerUnreachable.make({ url, message: error.message })));
382
+ if (response.status >= 400)
383
+ return yield* OperatorRefused.make({ status: response.status, body: text });
384
+ return yield* decodeJson(text).pipe(Effect4.mapError(() => RunnerUnreachable.make({ url, message: "the answer is not JSON" })));
385
+ });
386
+
387
+ // src/commands/dead-letters/repair.ts
388
+ var flags = {
389
+ jobId: Argument3.String("jobId").pipe(Argument3.withDescription("The dead-lettered job's id")),
390
+ actor: actorFlag({
391
+ name: "actor",
392
+ description: "The actor that enqueued the job, as <Type>/<id>"
393
+ }),
394
+ tenant,
395
+ reason,
396
+ ...operatorFlags
397
+ };
398
+ var repair = ({
399
+ options,
400
+ token
401
+ }) => operatorRequest({
402
+ url: options.urls[0],
403
+ path: `/operator/dead-letters/${encodeURIComponent(options.jobId)}/${options.action}`,
404
+ token,
405
+ body: options.action === "retry" ? {
406
+ tenant: options.tenant,
407
+ actorType: options.actor.actorType,
408
+ actorId: options.actor.actorId,
409
+ reason: options.reason,
410
+ providerChecked: options.providerChecked
411
+ } : {
412
+ tenant: options.tenant,
413
+ actorType: options.actor.actorType,
414
+ actorId: options.actor.actorId,
415
+ reason: options.reason
416
+ }
417
+ });
418
+ var retryCommand = Command3.make("retry", {
419
+ ...flags,
420
+ providerChecked: Flag4.Boolean("provider-checked").pipe(Flag4.withDefault(false), Flag4.withDescription("Confirm the provider never applied an ambiguous attempt, so running it again is safe"))
421
+ }, (options) => operatorCommand({
422
+ options: { ...options, action: "retry" },
423
+ request: repair,
424
+ format: encodeJson
425
+ })).pipe(Command3.withDescription("Run a dead-lettered job again"));
426
+ var discardCommand = Command3.make("discard", flags, (options) => operatorCommand({
427
+ options: { ...options, action: "discard", providerChecked: false },
428
+ request: repair,
429
+ format: encodeJson
430
+ })).pipe(Command3.withDescription("Settle a dead-lettered job without running it"));
431
+
432
+ // src/commands/defects/list.ts
433
+ import { DefectRecords } from "@rikalabs/akter/runtime";
434
+ import { Clock as Clock2, Console as Console5, DateTime, Duration, Effect as Effect5, Option as Option3, Schema as Schema4 } from "effect";
435
+ import { Command as Command4, Flag as Flag5 } from "effect/cli";
436
+ var units = { s: "seconds", m: "minutes", h: "hours", d: "days" };
437
+ var parseSince = (value) => {
438
+ const compact = /^(\d+)([smhd])$/.exec(value);
439
+ return Duration.fromInput(compact === null ? value : `${compact[1]} ${units[compact[2]]}`);
440
+ };
441
+ var decodeDefects = Schema4.decodeUnknownEffect(DefectRecords);
442
+ var flags2 = {
443
+ ...operatorFlags,
444
+ tenant: Flag5.String("tenant").pipe(Flag5.withDefault("*"), Flag5.withDescription("The tenant to read, or * for every tenant the operator's grant covers (default *)")),
445
+ actor: Flag5.String("actor").pipe(Flag5.optional, Flag5.withDescription("Only defects of this actor type")),
446
+ since: Flag5.String("since").pipe(Flag5.filterMap(parseSince, () => 'a duration such as 1h, 30m, 2d, 45s or "90 minutes"'), Flag5.optional, Flag5.withDescription("Only defects this recent: 1h, 30m, 2d, 45s, or any duration")),
447
+ limit: Flag5.Int("limit").pipe(Flag5.filter((limit) => limit >= 1 && limit <= 1000, () => "an integer from 1 to 1000"), Flag5.optional, Flag5.withDescription("At most this many of the newest defects, 1 to 1000"))
448
+ };
449
+ var listDefects = Effect5.fnUntraced(function* (options, token) {
450
+ const read = (url) => {
451
+ const query = new URLSearchParams({ tenant: options.tenant });
452
+ if (options.actor !== undefined)
453
+ query.set("actor", options.actor);
454
+ if (options.sinceMs !== undefined)
455
+ query.set("sinceMs", String(Math.floor(options.sinceMs)));
456
+ if (options.limit !== undefined)
457
+ query.set("limit", String(options.limit));
458
+ return operatorRequest({ url, path: `/operator/defects?${query}`, token }).pipe(Effect5.flatMap(decodeDefects));
459
+ };
460
+ const each = yield* Effect5.forEach(options.urls, read, { concurrency: "unbounded" });
461
+ const merged = each.flat().toSorted((a, b) => a.atMs - b.atMs);
462
+ return options.limit === undefined ? merged : merged.slice(-options.limit);
463
+ });
464
+ var formatDefects = ({
465
+ defects,
466
+ json
467
+ }) => {
468
+ if (json)
469
+ return JSON.stringify(defects, null, 2);
470
+ if (defects.length === 0)
471
+ return "No defects.";
472
+ return defects.map((defect) => [
473
+ DateTime.formatIso(DateTime.makeUnsafe(defect.atMs)),
474
+ `${defect.actorType}/${defect.actorId}`,
475
+ `${defect.command} ${defect.commandId}`,
476
+ `tenant=${defect.tenant}`,
477
+ `trace=${defect.traceId}`,
478
+ defect.cause.split(`
479
+ `)[0] ?? ""
480
+ ].join(" ")).join(`
481
+ `);
482
+ };
483
+ var listCommand = Command4.make("list", flags2, (options) => Effect5.gen(function* () {
484
+ const nowMs = yield* Clock2.currentTimeMillis;
485
+ const token = yield* operatorToken(options.tokenEnv);
486
+ const defects = yield* listDefects({
487
+ urls: options.urls,
488
+ tenant: options.tenant,
489
+ actor: Option3.getOrUndefined(options.actor),
490
+ sinceMs: Option3.getOrUndefined(Option3.map(options.since, (since) => nowMs - Duration.toMillis(since))),
491
+ limit: Option3.getOrUndefined(options.limit)
492
+ }, token);
493
+ yield* Console5.log(formatDefects({ defects, json: options.json }));
494
+ }).pipe(Effect5.catchTags({
495
+ RunnerUnreachable: (error) => fail({
496
+ reason: error._tag,
497
+ message: `Cannot read defects from ${error.url}: ${error.message}`
498
+ }),
499
+ OperatorRefused: (error) => fail({
500
+ reason: error._tag,
501
+ message: `Refused (${error.status}): ${error.body}`,
502
+ exitCode: 1
503
+ }),
504
+ SchemaError: (error) => fail({ reason: error._tag, message: `Unexpected answer: ${error.message}` }),
505
+ ConfigError: (error) => fail({ reason: error._tag, message: `Cannot read the operator token: ${error.message}` })
506
+ }))).pipe(Command4.withDescription("List recent defects from each runner named; each keeps only its own recent defect spans"));
507
+
508
+ // src/commands/dev/run.ts
509
+ import { User } from "@rikalabs/akter";
510
+ import { Auth, Database as Database3, Inspector } from "@rikalabs/akter/runtime";
511
+ import { Console as Console6, Effect as Effect7, Layer as Layer3, Option as Option4, Schema as Schema5 } from "effect";
512
+ import { Command as Command5, Flag as Flag6 } from "effect/cli";
513
+ import { HttpRouter as HttpRouter2, HttpServer } from "effect/http";
514
+
515
+ // src/commands/dev/inspector/page.ts
516
+ import { Effect as Effect6, FileSystem } from "effect";
517
+ import { HttpRouter, HttpServerResponse } from "effect/http";
518
+ import { fileURLToPath } from "node:url";
519
+ var STYLES = `
520
+ :root {
521
+ color-scheme: light dark;
522
+ --bg: #f6f7f9; --panel: #ffffff; --ink: #16181d; --muted: #6b7280; --line: #e3e6ea;
523
+ --accent: #3b5bdb; --good: #2b8a3e; --bad: #c92a2a; --warn: #b7791f; --info: #1c7ed6;
524
+ --code: #f1f3f5;
525
+ }
526
+ @media (prefers-color-scheme: dark) {
527
+ :root {
528
+ --bg: #0f1115; --panel: #171a21; --ink: #e8eaed; --muted: #9aa0a6; --line: #2a2f39;
529
+ --accent: #7c95ff; --good: #51cf66; --bad: #ff6b6b; --warn: #fcc419; --info: #4dabf7;
530
+ --code: #1f232b;
531
+ }
532
+ }
533
+ * { box-sizing: border-box; }
534
+ body { margin: 0; font: 14px/1.45 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; background: var(--bg); color: var(--ink); }
535
+ a { color: var(--accent); text-decoration: none; }
536
+ a:hover { text-decoration: underline; }
537
+ code, .mono, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; }
538
+ header.top { display: flex; align-items: center; gap: 16px; padding: 12px 24px; background: var(--panel); border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 1; }
539
+ header.top .brand { font-weight: 650; letter-spacing: -0.01em; }
540
+ header.top .brand small { color: var(--muted); font-weight: 400; margin-left: 6px; }
541
+ header.top .spacer { flex: 1; }
542
+ header.top label, header.top .updated { color: var(--muted); font-size: 12.5px; }
543
+ header.top button { font: inherit; padding: 4px 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); color: var(--ink); cursor: pointer; }
544
+ .layout { display: grid; grid-template-columns: 200px 1fr; min-height: calc(100vh - 53px); }
545
+ nav.sections { padding: 16px 12px; border-right: 1px solid var(--line); display: flex; flex-direction: column; gap: 2px; }
546
+ nav.sections a { padding: 7px 10px; border-radius: 6px; color: var(--ink); }
547
+ nav.sections a.active { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); font-weight: 600; }
548
+ nav.sections .note { margin-top: 16px; padding: 0 10px; color: var(--muted); font-size: 12px; }
549
+ main { padding: 20px 24px 48px; min-width: 0; }
550
+ .tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 10px; margin-bottom: 18px; }
551
+ .tile { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; display: flex; flex-direction: column; color: var(--ink); }
552
+ .tile strong { font-size: 20px; font-variant-numeric: tabular-nums; }
553
+ .tile span { color: var(--muted); font-size: 12px; }
554
+ .tile.alert { border-color: var(--bad); }
555
+ .tile.alert strong { color: var(--bad); }
556
+ .stack { display: flex; flex-direction: column; gap: 16px; }
557
+ .card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 16px 18px; overflow-x: auto; }
558
+ .card.error { border-color: var(--bad); }
559
+ .card h2 { margin: 0 0 12px; font-size: 15px; display: flex; align-items: center; gap: 8px; }
560
+ .card h2 .count { color: var(--muted); font-weight: 500; font-size: 12.5px; background: var(--code); border-radius: 999px; padding: 1px 8px; }
561
+ .hero h1 { margin: 2px 0 10px; font-size: 20px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
562
+ .crumbs { margin: 0; color: var(--muted); font-size: 12.5px; }
563
+ .facts { display: flex; gap: 6px; flex-wrap: wrap; }
564
+ .hint { color: var(--muted); margin: -4px 0 10px; font-size: 12.5px; }
565
+ .empty { color: var(--muted); margin: 4px 0; }
566
+ .muted { color: var(--muted); }
567
+ table { width: 100%; border-collapse: collapse; }
568
+ th { text-align: left; font-weight: 600; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; padding: 6px 10px; border-bottom: 1px solid var(--line); white-space: nowrap; }
569
+ td { padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
570
+ tr:last-child td { border-bottom: 0; }
571
+ td.num { font-variant-numeric: tabular-nums; text-align: right; }
572
+ td.cause { max-width: 420px; word-break: break-word; }
573
+ td.cause pre.trace { margin: 6px 0 0; font-size: 11.5px; color: var(--muted); white-space: pre-wrap; max-height: 220px; overflow: auto; }
574
+ td pre.json { min-width: 200px; }
575
+ td .due time { white-space: nowrap; }
576
+ .small { font-size: 11.5px; color: var(--muted); word-break: break-all; }
577
+ .badge { display: inline-block; max-width: 280px; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; padding: 1px 8px; border-radius: 999px; font-size: 12px; font-weight: 550; background: var(--code); color: var(--ink); white-space: nowrap; }
578
+ .badge.good { background: color-mix(in srgb, var(--good) 16%, transparent); color: var(--good); }
579
+ .badge.bad { background: color-mix(in srgb, var(--bad) 16%, transparent); color: var(--bad); }
580
+ .badge.warn { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
581
+ .badge.info { background: color-mix(in srgb, var(--info) 16%, transparent); color: var(--info); }
582
+ .chip { display: inline-block; padding: 2px 9px; margin: 0 4px 4px 0; border: 1px solid var(--line); border-radius: 999px; font-size: 12px; color: var(--ink); background: var(--panel); cursor: pointer; font-family: inherit; }
583
+ .chip.active { border-color: var(--accent); color: var(--accent); }
584
+ .filters { margin-bottom: 10px; }
585
+ pre.json { margin: 0; padding: 8px 10px; background: var(--code); border-radius: 6px; max-height: 260px; overflow: auto; white-space: pre-wrap; word-break: break-word; }
586
+ details summary { cursor: pointer; color: var(--accent); font-size: 12.5px; }
587
+ .due small { color: var(--muted); }
588
+ .due.overdue small { color: var(--warn); }
589
+ .state { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
590
+ .state-entry h3 { margin: 0 0 6px; font-size: 13px; }
591
+ ol.timeline { list-style: none; margin: 0; padding: 0 0 0 14px; border-left: 2px solid var(--line); display: flex; flex-direction: column; gap: 10px; }
592
+ li.event { position: relative; }
593
+ li.event::before { content: ""; position: absolute; left: -20px; top: 6px; width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }
594
+ li.event.flash pre.json { outline: 2px solid var(--accent); }
595
+ .event-head { display: flex; gap: 10px; align-items: baseline; margin-bottom: 4px; flex-wrap: wrap; }
596
+ .event-head .seq { font-weight: 650; font-variant-numeric: tabular-nums; }
597
+ article.workflow { border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; margin-bottom: 12px; overflow-x: auto; }
598
+ article.workflow header { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
599
+ article.workflow h3 { margin: 0; font-size: 14px; }
600
+ article.workflow h4 { margin: 12px 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
601
+ article.workflow dl { display: grid; grid-template-columns: max-content 1fr; gap: 2px 12px; margin: 10px 0 0; }
602
+ article.workflow dt { color: var(--muted); }
603
+ article.workflow dd { margin: 0; }
604
+ .split { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
605
+ @media (max-width: 800px) { .layout { grid-template-columns: 1fr; } nav.sections { flex-direction: row; flex-wrap: wrap; border-right: 0; border-bottom: 1px solid var(--line); } .split { grid-template-columns: 1fr; } }
606
+ `;
607
+ var SECTIONS = [
608
+ ["#/actors", "/actor", "Actors"],
609
+ ["#/outbox", "/outbox", "Outbox and timers"],
610
+ ["#/jobs", "/jobs", "Jobs"],
611
+ ["#/dead-letters", "/dead-letters", "Dead letters"],
612
+ ["#/workflows", "/workflows", "Workflows"]
613
+ ];
614
+ var escape = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
615
+ var page = ({
616
+ api,
617
+ script
618
+ }) => `<!doctype html>
619
+ <html lang="en">
620
+ <head>
621
+ <meta charset="utf-8">
622
+ <meta name="viewport" content="width=device-width, initial-scale=1">
623
+ <title>akter dev inspector</title>
624
+ <style>${STYLES}</style>
625
+ </head>
626
+ <body data-api="${escape(api)}">
627
+ <header class="top">
628
+ <span class="brand">akter dev<small>inspector</small></span>
629
+ <span class="badge info">tenant <strong id="tenant">…</strong></span>
630
+ <span class="badge">read-only</span>
631
+ <span class="spacer"></span>
632
+ <span class="updated" id="updated"></span>
633
+ <label><input type="checkbox" id="live"> live</label>
634
+ <button type="button" id="refresh">Refresh</button>
635
+ </header>
636
+ <div class="layout">
637
+ <nav class="sections">
638
+ ${SECTIONS.map(([href, section, label]) => `<a href="${href}" data-section="${section}">${label}</a>`).join(`
639
+ `)}
640
+ <p class="note">Reads the durable inspection views. Nothing here writes.</p>
641
+ </nav>
642
+ <main>
643
+ <div id="tiles"></div>
644
+ <div id="app"><p class="empty">Loading…</p></div>
645
+ </main>
646
+ </div>
647
+ <script type="module" src="${escape(script)}"></script>
648
+ </body>
649
+ </html>`;
650
+ var bundle = Effect6.gen(function* () {
651
+ const fs = yield* FileSystem.FileSystem;
652
+ return yield* fs.readFileString(fileURLToPath(new URL(import.meta.url.endsWith(".ts") ? "../../../../.cache/inspector-client.js" : "./inspector-client.js", import.meta.url)));
653
+ });
654
+ var pageRoutes = (path) => HttpRouter.use(Effect6.fnUntraced(function* (router) {
655
+ const script = yield* bundle;
656
+ const html = page({ api: `${path}/api`, script: `${path}/client.js` });
657
+ yield* router.add("GET", path, () => Effect6.succeed(HttpServerResponse.text(html, {
658
+ contentType: "text/html; charset=utf-8",
659
+ headers: { "cache-control": "no-store" }
660
+ })));
661
+ yield* router.add("GET", `${path}/client.js`, () => Effect6.succeed(HttpServerResponse.text(script, {
662
+ contentType: "text/javascript; charset=utf-8",
663
+ headers: { "cache-control": "no-store" }
664
+ })));
665
+ }));
666
+
667
+ // src/commands/dev/run.ts
668
+ var flags3 = {
669
+ entry: Flag6.File("entry", { mustExist: true }).pipe(Flag6.withDescription("The entry module; it exports `app`, a Layer of its routes")),
670
+ databaseUrl: Flag6.Redacted("database-url").pipe(Flag6.optional, Flag6.withDescription("Run on this Postgres instead of PGlite")),
671
+ dataDir: Flag6.Directory("data-dir").pipe(Flag6.optional, Flag6.withDescription("Where PGlite keeps its files (default in memory)")),
672
+ port: Flag6.Int("port").pipe(Flag6.filter((port) => port >= 0 && port <= 65535, () => "an integer 0-65535"), Flag6.withDefault(3000), Flag6.withDescription("The port to listen on; 0 picks a free one (default 3000)")),
673
+ hostname: Flag6.String("hostname").pipe(Flag6.withDefault("127.0.0.1"), Flag6.withDescription("The address to listen on (default 127.0.0.1)")),
674
+ tenant: Flag6.String("tenant").pipe(Flag6.withDefault("default"), Flag6.withDescription("The one tenant the inspector reads (default default)"))
675
+ };
676
+ var isDevApp = (value) => Layer3.isLayer(value);
677
+ var Entry2 = Schema5.Struct({ app: Schema5.declare(isDevApp) });
678
+ var decodeEntry2 = Schema5.decodeUnknownEffect(Entry2);
679
+ var appOf = ({ module, entry }) => decodeEntry2(module).pipe(Effect7.map(({ app }) => app), Effect7.mapError(() => UsageError.make({
680
+ message: `${entry} must export \`app\`: a Layer of its routes that needs only the database`
681
+ })));
682
+ var INSPECTOR_PATH = "/_akter/inspector";
683
+ var localOperator = (tenant) => Auth.make(() => Effect7.succeed({ tenant, caller: User.make({ subject: "durable-dev" }) }));
684
+ var devRoutes = ({ app, tenant }) => Layer3.mergeAll(app, Inspector.serve({ auth: localOperator(tenant), basePath: `${INSPECTOR_PATH}/api` }), pageRoutes(INSPECTOR_PATH));
685
+ var devCommand = Command5.make("dev", flags3, (options) => Effect7.gen(function* () {
686
+ if (Option4.isSome(options.databaseUrl) && Option4.isSome(options.dataDir))
687
+ return yield* UsageError.make({
688
+ message: "--data-dir is for PGlite; drop it or --database-url"
689
+ });
690
+ const module = yield* loadEntry(options.entry);
691
+ const app = yield* appOf({ module, entry: options.entry });
692
+ const database = Option4.match(options.databaseUrl, {
693
+ onNone: () => Database3.pglite(Option4.match(options.dataDir, { onNone: () => ({}), onSome: (dataDir) => ({ dataDir }) })),
694
+ onSome: (url) => Database3.postgres({ url })
695
+ });
696
+ const storage = Option4.isSome(options.databaseUrl) ? "Postgres" : `PGlite (${Option4.getOrElse(options.dataDir, () => "in memory")})`;
697
+ const banner = Layer3.effectDiscard(HttpServer.addressFormattedWith((origin) => Console6.log([
698
+ `akter dev: ${options.entry} on ${storage}`,
699
+ ` app ${origin}`,
700
+ ` inspector ${origin}${INSPECTOR_PATH} (tenant ${options.tenant})`
701
+ ].join(`
702
+ `))));
703
+ return banner.pipe(Layer3.provideMerge(HttpRouter2.serve(devRoutes({ app, tenant: options.tenant }), { disableListenLog: true })), Layer3.provide(PlatformHttpServer.layer({ port: options.port, hostname: options.hostname })), Layer3.provide(database), Layer3.provide(PlatformCrypto.layer));
704
+ }).pipe(Effect7.flatMap(Layer3.launch), Effect7.catchTag("UsageError", (error) => fail({ reason: error._tag, message: error.message })))).pipe(Command5.withDescription("Run the entry's app locally with a read-only inspector at /_akter/inspector"));
705
+
706
+ // src/commands/export/run.ts
707
+ import { Seed, SeedJson } from "@rikalabs/akter/runtime";
708
+ import { Effect as Effect8, FileSystem as FileSystem2, Schema as Schema6 } from "effect";
709
+ import { Command as Command6, Flag as Flag7 } from "effect/cli";
710
+ var flags4 = {
711
+ actor: actorArgument,
712
+ tenant,
713
+ output: Flag7.File("output").pipe(Flag7.withDescription("The seed file to create; an existing file is never replaced")),
714
+ ...operatorFlags
715
+ };
716
+ var ExportAnswer = Schema6.Struct({
717
+ output: Schema6.String,
718
+ actor: Schema6.String,
719
+ state: Schema6.Finite,
720
+ intents: Schema6.Finite,
721
+ jobs: Schema6.Finite,
722
+ omitted: Schema6.Struct({
723
+ receipts: Schema6.Finite,
724
+ events: Schema6.Finite,
725
+ workflows: Schema6.Finite,
726
+ deadLetters: Schema6.Finite,
727
+ tableRows: Schema6.Finite,
728
+ blobs: Schema6.Finite
729
+ })
730
+ });
731
+ var exportSeed = Effect8.fnUntraced(function* ({
732
+ options,
733
+ token
734
+ }) {
735
+ const answer = yield* operatorRequest({
736
+ url: options.urls[0],
737
+ path: `/operator/actors/${encodeURIComponent(options.actor.actorType)}/${encodeURIComponent(options.actor.actorId)}/export?${new URLSearchParams({ tenant: options.tenant })}`,
738
+ token
739
+ });
740
+ const seed = yield* Schema6.decodeUnknownEffect(Seed)(answer);
741
+ const fs = yield* FileSystem2.FileSystem;
742
+ yield* fs.writeFileString(options.output, `${yield* Schema6.encodeEffect(SeedJson)(seed).pipe(Effect8.orDie)}
743
+ `, { flag: "wx", mode: 384 });
744
+ return {
745
+ output: options.output,
746
+ actor: `${seed.actor.type}/${seed.actor.id}`,
747
+ state: Object.keys(seed.state).length,
748
+ intents: seed.intents.length,
749
+ jobs: seed.jobs.length,
750
+ omitted: seed.omitted
751
+ };
752
+ });
753
+ var formatExport = (answer) => Effect8.map(Schema6.decodeUnknownEffect(ExportAnswer)(answer), (exported) => [
754
+ `Exported ${exported.actor} to ${exported.output}`,
755
+ `carries ${exported.state} state keys, ${exported.intents} pending intents, ${exported.jobs} pending jobs`,
756
+ `omits ${exported.omitted.receipts} receipts, ${exported.omitted.events} events, ${exported.omitted.workflows} workflows, ${exported.omitted.deadLetters} dead letters, ${exported.omitted.tableRows} owned-table rows, ${exported.omitted.blobs} blob entries`
757
+ ].join(`
758
+ `));
759
+ var exportCommand = Command6.make("export", flags4, (options) => operatorCommand({ options, request: exportSeed, format: formatExport })).pipe(Command6.withDescription("Write one actor's state and pending intents and jobs to a new seed file, through the first runner named"));
760
+
761
+ // src/commands/fleet/run.ts
762
+ import { Database as Database4, rebuildFleetView, setupFleet } from "@rikalabs/akter/runtime";
763
+ import { Console as Console7, Effect as Effect9, Layer as Layer4, Predicate } from "effect";
764
+ import { Argument as Argument4, Command as Command7, Flag as Flag8 } from "effect/cli";
765
+ var isView = (value) => Predicate.hasProperty(value, "definitionHash") && Predicate.hasProperty(value, "groupColumns") && Predicate.hasProperty(value, "table");
766
+ var viewsOf = ({ module, entry }) => {
767
+ const fleet = module.fleet;
768
+ return Array.isArray(fleet) && fleet.length > 0 && fleet.every(isView) ? Effect9.succeed(fleet) : Effect9.fail(UsageError.make({ message: `${entry} must export a \`fleet\` array of Fleet.view values` }));
769
+ };
770
+ var setup = (views) => setupFleet(views).pipe(Effect9.map((result) => [
771
+ `Full replica identity and publication durable_fleet: ${result.sources.join(", ")}`,
772
+ `Replication slot durable_fleet: ${result.slot}`
773
+ ].join(`
774
+ `)));
775
+ var rebuild = (view) => rebuildFleetView(view).pipe(Effect9.map((found) => found ? { output: `${view} is building; the maintainer rebuilds it from its source`, exitCode: 0 } : { output: `No fleet view ${view} is registered on this database`, exitCode: 1 }));
776
+ var onDatabase = (databaseUrl, run) => Effect9.gen(function* () {
777
+ const services = yield* Layer4.build(Database4.postgres({ url: databaseUrl }).pipe(Layer4.provideMerge(PlatformCrypto.layer)));
778
+ const { output, exitCode } = yield* run.pipe(Effect9.provideContext(services));
779
+ yield* Console7.log(output);
780
+ if (exitCode !== 0)
781
+ return yield* CommandFailed.make({ exitCode, reason: "Refused" });
782
+ }).pipe(Effect9.scoped);
783
+ var databaseUrl = Flag8.Redacted("database-url").pipe(Flag8.withDescription("The application's Postgres URL"));
784
+ var setupCommand = Command7.make("setup", {
785
+ entry: Flag8.File("entry", { mustExist: true }).pipe(Flag8.withDescription("The entry module; it exports a `fleet` array of Fleet.view values")),
786
+ databaseUrl
787
+ }, (options) => Effect9.gen(function* () {
788
+ const views = yield* viewsOf({
789
+ module: yield* loadEntry(options.entry),
790
+ entry: options.entry
791
+ });
792
+ return yield* onDatabase(options.databaseUrl, setup(views).pipe(Effect9.map((output) => ({ output, exitCode: 0 }))));
793
+ }).pipe(Effect9.catchTags({
794
+ SqlError: (error) => fail({ reason: error._tag, message: `Cannot set up fleet views: ${error.message}` }),
795
+ FleetSetupRefused: (error) => fail({ reason: error._tag, message: error.message }),
796
+ UsageError: (error) => fail({ reason: error._tag, message: error.message })
797
+ }))).pipe(Command7.withDescription("Give the entry's fleet view sources full replica identity, publish them, and create the logical slot; needs wal_level=logical"));
798
+ var rebuildCommand = Command7.make("rebuild", {
799
+ view: Argument4.String("view").pipe(Argument4.withDescription("The fleet view to rebuild")),
800
+ databaseUrl
801
+ }, (options) => onDatabase(options.databaseUrl, rebuild(options.view)).pipe(Effect9.catchTags({
802
+ SqlError: (error) => fail({ reason: error._tag, message: `Cannot rebuild ${options.view}: ${error.message}` })
803
+ }))).pipe(Command7.withDescription("Rebuild a fleet view from its source, clearing its error; exit 1 when no runtime registered it"));
804
+
805
+ // src/commands/inspect/show.ts
806
+ import { Inspection } from "@rikalabs/akter/client";
807
+ import { Effect as Effect10, Schema as Schema7, Struct } from "effect";
808
+ import { Command as Command8, Flag as Flag9 } from "effect/cli";
809
+ var flags5 = {
810
+ actor: actorArgument,
811
+ tenant,
812
+ limit: Flag9.Int("receipts").pipe(Flag9.filter((count) => count >= 1 && count <= 1000, () => "an integer from 1 to 1000"), Flag9.withDefault(20), Flag9.withDescription("How many of the newest receipts to show, 1 to 1000 (default 20)")),
813
+ ...operatorFlags
814
+ };
815
+ var ActorPage = Schema7.Struct({
816
+ actor: Inspection.ActorRow.mapFields(Struct.pick(["actorType", "actorId", "generation", "lastEventSequence"])),
817
+ state: Schema7.Array(Inspection.ActorDetail.fields.state.value.mapFields(Struct.pick(["key", "value"]))),
818
+ receipts: Schema7.Array(Inspection.OperatorActorDetail.fields.receipts.value.mapFields(Struct.pick(["commandId", "command", "outcomeTag", "outcome"]))),
819
+ deadLetters: Schema7.Array(Inspection.DeadLetterRow.mapFields(Struct.pick(["jobId", "job", "attempts", "ambiguous", "cause"]))),
820
+ totals: Inspection.ActorDetail.fields.totals
821
+ });
822
+ var decodeActorPage = Schema7.decodeUnknownEffect(ActorPage);
823
+ var shown = (value) => value === undefined ? "(outcome needs receipts.read)" : value === null ? "null" : ("json" in value) ? JSON.stringify(value.json) : `(undecodable: ${value.undecodable})`;
824
+ var formatActor = (page) => [
825
+ `${page.actor.actorType}/${page.actor.actorId} generation ${page.actor.generation} events through ${page.actor.lastEventSequence}`,
826
+ `totals receipts ${page.totals.receipts} events ${page.totals.events} outbox ${page.totals.outbox} jobs ${page.totals.jobs} dead letters ${page.totals.deadLetters} workflows ${page.totals.workflows}`,
827
+ "state",
828
+ ...page.state.map(({ key, value }) => ` ${key} = ${shown(value)}`),
829
+ "receipts (newest first)",
830
+ ...page.receipts.map((receipt) => ` ${receipt.command} ${receipt.commandId} ${receipt.outcomeTag ?? "?"} ${shown(receipt.outcome)}`),
831
+ ...page.deadLetters.length === 0 ? [] : [
832
+ "dead letters",
833
+ ...page.deadLetters.map((letter) => ` ${letter.jobId} ${letter.job} attempts ${letter.attempts}${letter.ambiguous ? " ambiguous" : ""} ${letter.cause.split(`
834
+ `)[0] ?? ""}`)
835
+ ]
836
+ ].join(`
837
+ `);
838
+ var inspect = ({
839
+ options,
840
+ token
841
+ }) => operatorRequest({
842
+ url: options.urls[0],
843
+ path: `/operator/actors/${encodeURIComponent(options.actor.actorType)}/${encodeURIComponent(options.actor.actorId)}?${new URLSearchParams({ tenant: options.tenant, limit: String(options.limit) })}`,
844
+ token
845
+ });
846
+ var formatInspection = (answer) => Effect10.map(decodeActorPage(answer), formatActor);
847
+ var inspectCommand = Command8.make("inspect", flags5, (options) => operatorCommand({ options, request: inspect, format: formatInspection })).pipe(Command8.withDescription("Read one actor's state, newest receipts, and dead letters through the first runner named"));
848
+
849
+ // src/commands/payloads/run.ts
850
+ import { checkPayloads, clearPayloads, formatPayloadProblem } from "@rikalabs/akter/runtime";
851
+ import { Effect as Effect11 } from "effect";
852
+ import { Command as Command9 } from "effect/cli";
853
+ var checkReport = ({
854
+ problems,
855
+ json
856
+ }) => ({
857
+ output: json ? JSON.stringify({ compatible: problems.length === 0, problems }, null, 2) : problems.length === 0 ? "Every stored event and job payload version decodes" : [
858
+ ...problems.map(formatPayloadProblem),
859
+ `${problems.length} problem${problems.length === 1 ? "" : "s"}; deploy refused (exit 1)`
860
+ ].join(`
861
+ `),
862
+ exitCode: problems.length === 0 ? 0 : 1
863
+ });
864
+ var reasons = {
865
+ cleared: "cleared",
866
+ writer: "not cleared: a runtime wrote it within its writer window and command timeout",
867
+ stored: "not cleared: events of this version are still stored"
868
+ };
869
+ var clearReport = ({
870
+ results,
871
+ json
872
+ }) => ({
873
+ output: json ? JSON.stringify({ results }, null, 2) : results.length === 0 ? "No superseded event version is past its retention horizon" : results.map((result) => `${result.actorType}/${result.tag} version ${result.version} ${reasons[result.outcome]}`).join(`
874
+ `),
875
+ exitCode: results.every((result) => result.outcome === "cleared") ? 0 : 1
876
+ });
877
+ var payloads = ({
878
+ command,
879
+ actors,
880
+ json
881
+ }) => command === "check" ? checkPayloads(actors).pipe(Effect11.map((problems) => checkReport({ problems, json }))) : clearPayloads(actors).pipe(Effect11.map((results) => clearReport({ results, json })));
882
+ var checkCommand2 = Command9.make("check", entryFlags, (options) => entryCommand({
883
+ options,
884
+ reading: "payload versions",
885
+ run: (actors) => payloads({ command: "check", actors, json: options.json })
886
+ })).pipe(Command9.withDescription("Check that every stored event and job payload version still decodes, read-only; exit 1 when a deploy would be refused"));
887
+ var clearCommand = Command9.make("clear", entryFlags, (options) => entryCommand({
888
+ options,
889
+ reading: "payload versions",
890
+ run: (actors) => payloads({ command: "clear", actors, json: options.json })
891
+ })).pipe(Command9.withDescription("Mark superseded event versions past their retention horizon cleared; exit 1 when one stays uncleared"));
892
+
893
+ // src/commands/receipts/show.ts
894
+ import { Command as Command10, Argument as Argument5 } from "effect/cli";
895
+ var flags6 = {
896
+ actor: actorArgument,
897
+ commandId: Argument5.String("commandId").pipe(Argument5.withDescription("The command id whose stored outcome to show")),
898
+ tenant,
899
+ ...operatorFlags
900
+ };
901
+ var showReceipt = ({
902
+ options,
903
+ token
904
+ }) => operatorRequest({
905
+ url: options.urls[0],
906
+ path: `/operator/receipts/${encodeURIComponent(options.actor.actorType)}/${encodeURIComponent(options.actor.actorId)}/${encodeURIComponent(options.commandId)}?${new URLSearchParams({ tenant: options.tenant })}`,
907
+ token
908
+ });
909
+ var showCommand = Command10.make("show", flags6, (options) => operatorCommand({ options, request: showReceipt, format: encodeJson })).pipe(Command10.withDescription("Print one receipt's stored outcome as JSON; the runner never runs the command to answer"));
910
+
911
+ // src/commands/subscriptions/list.ts
912
+ import { Effect as Effect12, Option as Option5, Schema as Schema8 } from "effect";
913
+ import { Command as Command11, Flag as Flag10 } from "effect/cli";
914
+ var count = (name, maximum, description) => Flag10.Int(name).pipe(Flag10.filter((value) => value >= 1 && value <= maximum, () => `an integer from 1 to ${maximum}`), Flag10.optional, Flag10.withDescription(description));
915
+ var flags7 = {
916
+ lagging: Flag10.Boolean("lagging").pipe(Flag10.withDescription("List the rows whose deliveries keep failing; the only listing, so required")),
917
+ tenant,
918
+ minAttempts: count("min-attempts", 1e6, "Only rows with at least this many failed attempts"),
919
+ limit: count("limit", 1000, "At most this many rows, 1 to 1000"),
920
+ ...operatorFlags
921
+ };
922
+ var list = ({
923
+ options,
924
+ token
925
+ }) => {
926
+ const params = new URLSearchParams({ tenant: options.tenant });
927
+ if (Option5.isSome(options.minAttempts))
928
+ params.set("minAttempts", String(options.minAttempts.value));
929
+ if (Option5.isSome(options.limit))
930
+ params.set("limit", String(options.limit.value));
931
+ return operatorRequest({
932
+ url: options.urls[0],
933
+ path: `/operator/subscriptions/lagging?${params}`,
934
+ token
935
+ });
936
+ };
937
+ var Row = Schema8.Struct({
938
+ sourceType: Schema8.String,
939
+ sourceId: Schema8.String,
940
+ subscriberType: Schema8.String,
941
+ subscription: Schema8.String,
942
+ subscriberId: Schema8.String,
943
+ delivered: Schema8.String,
944
+ head: Schema8.String,
945
+ lag: Schema8.String,
946
+ attempts: Schema8.Finite,
947
+ lastError: Schema8.String
948
+ });
949
+ var decodeRows = Schema8.decodeUnknownEffect(Schema8.Array(Row));
950
+ var formatLagging = (answer) => Effect12.map(decodeRows(answer), (rows) => rows.length === 0 ? "no lagging subscriptions" : rows.flatMap((row) => [
951
+ `${row.sourceType}/${row.sourceId} -> ${row.subscriberType}.${row.subscription}/${row.subscriberId} delivered ${row.delivered} of ${row.head} lag ${row.lag} attempts ${row.attempts}`,
952
+ ` ${row.lastError.split(`
953
+ `)[0] ?? ""}`
954
+ ]).join(`
955
+ `));
956
+ var listCommand2 = Command11.make("list", flags7, (options) => operatorCommand({ options, request: list, format: formatLagging })).pipe(Command11.withDescription("List subscription rows whose deliveries keep failing, with their lag and last error"));
957
+
958
+ // src/commands/subscriptions/skip.ts
959
+ import { Command as Command12, Flag as Flag11 } from "effect/cli";
960
+ var flags8 = {
961
+ source: actorFlag({
962
+ name: "source",
963
+ description: "The source actor whose events the row delivers, as <Type>/<id>"
964
+ }),
965
+ subscriber: actorFlag({
966
+ name: "subscriber",
967
+ description: "The subscribing actor, as <Type>/<id>"
968
+ }),
969
+ subscription: Flag11.String("subscription").pipe(Flag11.filter((name) => name.length > 0, () => "a subscription name"), Flag11.withDescription("The subscription's name on the subscriber")),
970
+ through: Flag11.String("through").pipe(Flag11.filter((cursor) => /^[1-9][0-9]{0,17}$/.test(cursor), () => "a positive event cursor"), Flag11.withDescription("Skip every event up to and including this source event cursor")),
971
+ tenant,
972
+ reason,
973
+ ...operatorFlags
974
+ };
975
+ var skip = ({
976
+ options,
977
+ token
978
+ }) => operatorRequest({
979
+ url: options.urls[0],
980
+ path: "/operator/subscriptions/skip",
981
+ token,
982
+ body: {
983
+ tenant: options.tenant,
984
+ sourceType: options.source.actorType,
985
+ sourceId: options.source.actorId,
986
+ subscriberType: options.subscriber.actorType,
987
+ subscription: options.subscription,
988
+ subscriberId: options.subscriber.actorId,
989
+ through: options.through,
990
+ reason: options.reason
991
+ }
992
+ });
993
+ var skipCommand = Command12.make("skip", flags8, (options) => operatorCommand({ options, request: skip, format: encodeJson })).pipe(Command12.withDescription("Skip a stuck subscription row's events through a cursor; the runner audits the skip"));
994
+
995
+ // ../../packages/cloud-api/src/errors.ts
996
+ import { Schema as Schema9 } from "effect";
997
+
998
+ class Unauthorized extends Schema9.TaggedError()("Unauthorized", {
999
+ code: Schema9.Literals(["missing_credentials", "invalid_credentials", "expired"]),
1000
+ message: Schema9.String
1001
+ }, { httpApiStatus: 401 }) {
1002
+ }
1003
+
1004
+ class Forbidden extends Schema9.TaggedError()("Forbidden", { message: Schema9.String }, { httpApiStatus: 403 }) {
1005
+ }
1006
+ var NotFoundResource = Schema9.Literals([
1007
+ "actor",
1008
+ "actor-type",
1009
+ "api-key",
1010
+ "command",
1011
+ "cursor",
1012
+ "deployment",
1013
+ "domain",
1014
+ "environment",
1015
+ "invitation",
1016
+ "live deployment",
1017
+ "member",
1018
+ "organization",
1019
+ "project",
1020
+ "source"
1021
+ ]);
1022
+
1023
+ class NotFound extends Schema9.TaggedError()("NotFound", { resource: NotFoundResource, id: Schema9.String }, { httpApiStatus: 404 }) {
1024
+ }
1025
+
1026
+ class Conflict extends Schema9.TaggedError()("Conflict", { message: Schema9.String }, { httpApiStatus: 409 }) {
1027
+ }
1028
+
1029
+ class NotImplemented extends Schema9.TaggedError()("NotImplemented", { operation: Schema9.String }, { httpApiStatus: 501 }) {
1030
+ }
1031
+ var UnavailableReason = Schema9.Literals(["unknownPlan"]);
1032
+
1033
+ class Unavailable extends Schema9.TaggedError()("Unavailable", {
1034
+ message: Schema9.String,
1035
+ retryAfterSeconds: Schema9.Int.check(Schema9.isGreaterThanOrEqualTo(1)),
1036
+ reason: Schema9.optionalKey(UnavailableReason)
1037
+ }, { httpApiStatus: 503 }) {
1038
+ }
1039
+
1040
+ class PayloadTooLarge extends Schema9.TaggedError()("PayloadTooLarge", { limitBytes: Schema9.Int }, { httpApiStatus: 413 }) {
1041
+ }
1042
+ var ReadErrors = [Forbidden, NotFound, NotImplemented, Unavailable];
1043
+ var WriteErrors = [Forbidden, NotFound, Conflict, NotImplemented, Unavailable];
1044
+ var SelfErrors = [Forbidden, NotImplemented];
1045
+ // ../../packages/cloud-api/src/auth.ts
1046
+ import { Context, Schema as Schema11 } from "effect";
1047
+ import { HttpApiMiddleware, HttpApiSecurity } from "effect/http-api";
1048
+
1049
+ // ../../packages/cloud-api/src/primitives.ts
1050
+ import { Schema as Schema10 } from "effect";
1051
+ var identifier = Schema10.String.pipe(Schema10.check(Schema10.isMinLength(1), Schema10.isMaxLength(128)));
1052
+ var UserId = identifier.pipe(Schema10.brand("UserId"));
1053
+ var OrganizationId = identifier.pipe(Schema10.brand("OrganizationId"));
1054
+ var MemberId = identifier.pipe(Schema10.brand("MemberId"));
1055
+ var InvitationId = identifier.pipe(Schema10.brand("InvitationId"));
1056
+ var ApiKeyId = identifier.pipe(Schema10.brand("ApiKeyId"));
1057
+ var ProjectId = identifier.pipe(Schema10.brand("ProjectId"));
1058
+ var DeploymentId = identifier.pipe(Schema10.brand("DeploymentId"));
1059
+ var DomainId = identifier.pipe(Schema10.brand("DomainId"));
1060
+ var DeadLetterId = identifier.pipe(Schema10.brand("DeadLetterId"));
1061
+ var InvoiceId = identifier.pipe(Schema10.brand("InvoiceId"));
1062
+ var EnvironmentId = identifier.pipe(Schema10.brand("EnvironmentId"));
1063
+ var Slug = Schema10.String.pipe(Schema10.check(Schema10.isPattern(/^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/)), Schema10.brand("Slug"));
1064
+ var Name = Schema10.String.pipe(Schema10.check(Schema10.isPattern(/^\S(?:.{0,98}\S)?$/)), Schema10.brand("Name"));
1065
+ var Email = Schema10.String.pipe(Schema10.check(Schema10.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/), Schema10.isMaxLength(254)), Schema10.brand("Email"));
1066
+ var NonNegativeInt = Schema10.Int.pipe(Schema10.check(Schema10.isGreaterThanOrEqualTo(0)));
1067
+ var NonNegative = Schema10.Finite.pipe(Schema10.check(Schema10.isGreaterThanOrEqualTo(0)));
1068
+ var Timestamp = Schema10.DateTimeUtc;
1069
+ var CalendarDay = Schema10.String.pipe(Schema10.check(Schema10.isPattern(/^\d{4}-\d{2}-\d{2}$/)));
1070
+ var BillingPeriod = Schema10.String.pipe(Schema10.check(Schema10.isPattern(/^\d{4}-(?:0[1-9]|1[0-2])$/)));
1071
+ var Role = Schema10.Literals(["owner", "admin", "member", "viewer"]);
1072
+ var InviteRole = Schema10.Literals(["admin", "member", "viewer"]);
1073
+ var EnvironmentName = Schema10.Literals(["production", "staging", "dev"]);
1074
+ var RegionId = Schema10.Literals(["us-east-1", "us-west-2"]);
1075
+ var CommitSha = Schema10.String.pipe(Schema10.check(Schema10.isPattern(/^[0-9a-f]{7,40}$/)));
1076
+ var ActorAddress = Schema10.String.pipe(Schema10.check(Schema10.isMinLength(3), Schema10.isMaxLength(1024), Schema10.isPattern(/^[^/]+\/.+$/)));
1077
+ var ActorReference = Schema10.Struct({
1078
+ kind: Schema10.Literals(["user", "api-key"]),
1079
+ id: Schema10.String,
1080
+ name: Schema10.NullOr(Schema10.String)
1081
+ });
1082
+ var SeriesPoint = Schema10.Struct({ at: Timestamp, value: Schema10.Finite });
1083
+ var pageQuery = {
1084
+ limit: Schema10.optional(Schema10.Int.pipe(Schema10.check(Schema10.isBetween({ minimum: 1, maximum: 100 })))),
1085
+ cursor: Schema10.optional(Schema10.String)
1086
+ };
1087
+ var Page = (item) => Schema10.Struct({ items: Schema10.Array(item), nextCursor: Schema10.NullOr(Schema10.String) });
1088
+
1089
+ // ../../packages/cloud-api/src/auth.ts
1090
+ var sessionCookieName = "better-auth.session_token";
1091
+ var secureSessionCookieName = "__Secure-better-auth.session_token";
1092
+ var apiKeyHeaderName = "x-api-key";
1093
+ var SessionIdentity = Schema11.TaggedStruct("session", {
1094
+ userId: UserId,
1095
+ sessionId: Schema11.String,
1096
+ activeOrganizationId: Schema11.NullOr(OrganizationId)
1097
+ });
1098
+ var ApiKeyIdentity = Schema11.TaggedStruct("api-key", {
1099
+ keyId: ApiKeyId,
1100
+ organizationId: OrganizationId,
1101
+ permission: Schema11.Literals(["read", "write", "admin"])
1102
+ });
1103
+ var Identity = Schema11.Union([SessionIdentity, ApiKeyIdentity]);
1104
+
1105
+ class CurrentIdentity extends Context.Service()("@akter/cloud-api/auth/CurrentIdentity") {
1106
+ }
1107
+
1108
+ class Authentication extends HttpApiMiddleware.Service()("@akter/cloud-api/Authentication", {
1109
+ security: {
1110
+ session: HttpApiSecurity.apiKey({ key: sessionCookieName, in: "cookie" }),
1111
+ secureSession: HttpApiSecurity.apiKey({ key: secureSessionCookieName, in: "cookie" }),
1112
+ apiKey: HttpApiSecurity.apiKey({ key: apiKeyHeaderName, in: "header" }),
1113
+ bearer: HttpApiSecurity.bearer
1114
+ },
1115
+ error: [Unauthorized, Forbidden]
1116
+ }) {
1117
+ }
1118
+ // ../../packages/cloud-api/src/identity.ts
1119
+ import { Schema as Schema12 } from "effect";
1120
+ var PlanId = Schema12.Literals(["free", "pro", "team", "enterprise"]);
1121
+ var UnboundPlan = Schema12.TaggedStruct("unbound", {});
1122
+ var KnownPlan = Schema12.TaggedStruct("known", { id: PlanId });
1123
+ var UnknownPlan = Schema12.TaggedStruct("unknown", { id: Schema12.String });
1124
+ var OrganizationPlan = Schema12.Union([UnboundPlan, KnownPlan, UnknownPlan]);
1125
+ var User2 = Schema12.Struct({
1126
+ id: UserId,
1127
+ name: Schema12.String,
1128
+ email: Email,
1129
+ emailVerified: Schema12.Boolean,
1130
+ image: Schema12.NullOr(Schema12.String)
1131
+ });
1132
+ var Organization = Schema12.Struct({
1133
+ id: OrganizationId,
1134
+ name: Schema12.String,
1135
+ slug: Slug,
1136
+ plan: OrganizationPlan,
1137
+ createdAt: Timestamp
1138
+ });
1139
+ var OrganizationMembership = Schema12.Struct({
1140
+ organization: Organization,
1141
+ role: Role
1142
+ });
1143
+ var Me = Schema12.Struct({
1144
+ user: Schema12.NullOr(User2),
1145
+ identityKind: Schema12.Literals(["session", "api-key"]),
1146
+ activeOrganizationId: Schema12.NullOr(OrganizationId),
1147
+ organizations: Schema12.Array(OrganizationMembership)
1148
+ });
1149
+ var UpdateProfile = Schema12.Struct({
1150
+ name: Schema12.optional(Name),
1151
+ image: Schema12.optional(Schema12.NullOr(Schema12.String))
1152
+ });
1153
+ var DeleteAccount = Schema12.Struct({ confirmation: Email });
1154
+ var PersonalDataExport = Schema12.Struct({
1155
+ userId: UserId,
1156
+ exportedAt: Timestamp,
1157
+ tables: Schema12.Record(Schema12.String, Schema12.Array(Schema12.Json))
1158
+ });
1159
+ var OrganizationDeletion = Schema12.Struct({
1160
+ organizationId: OrganizationId,
1161
+ phase: Schema12.Literals(["billing", "runners", "databases", "records", "completed"]),
1162
+ blocked: Schema12.Boolean
1163
+ });
1164
+ var SetActiveOrganization = Schema12.Struct({ organizationId: OrganizationId });
1165
+ var CreateOrganization = Schema12.Struct({ name: Name, slug: Slug });
1166
+ var UpdateOrganization = Schema12.Struct({
1167
+ name: Schema12.optional(Name),
1168
+ slug: Schema12.optional(Slug)
1169
+ });
1170
+ var MemberUser = Schema12.Struct({
1171
+ id: UserId,
1172
+ name: Schema12.String,
1173
+ email: Email,
1174
+ image: Schema12.NullOr(Schema12.String)
1175
+ });
1176
+ var Member = Schema12.Struct({
1177
+ id: MemberId,
1178
+ user: MemberUser,
1179
+ role: Role,
1180
+ createdAt: Timestamp,
1181
+ lastActiveAt: Schema12.NullOr(Timestamp)
1182
+ });
1183
+ var UpdateMemberRole = Schema12.Struct({ role: Role });
1184
+ var InvitationStatus = Schema12.Literals([
1185
+ "pending",
1186
+ "accepted",
1187
+ "declined",
1188
+ "canceled",
1189
+ "expired"
1190
+ ]);
1191
+ var Invitation = Schema12.Struct({
1192
+ id: InvitationId,
1193
+ organizationId: OrganizationId,
1194
+ email: Email,
1195
+ role: InviteRole,
1196
+ status: InvitationStatus,
1197
+ invitedBy: Schema12.Struct({ id: UserId, name: Schema12.String }),
1198
+ createdAt: Timestamp,
1199
+ expiresAt: Timestamp
1200
+ });
1201
+ var CreateInvitation = Schema12.Struct({ email: Email, role: InviteRole });
1202
+ var InvitationPreview = Schema12.Struct({
1203
+ id: InvitationId,
1204
+ email: Email,
1205
+ role: InviteRole,
1206
+ status: InvitationStatus,
1207
+ expiresAt: Timestamp,
1208
+ inviterName: Schema12.String,
1209
+ organization: Schema12.Struct({
1210
+ id: OrganizationId,
1211
+ name: Schema12.String,
1212
+ slug: Slug,
1213
+ plan: OrganizationPlan,
1214
+ memberCount: NonNegativeInt
1215
+ })
1216
+ });
1217
+ var ApiKeyPermission = Schema12.Literals(["read", "write", "admin"]);
1218
+ var ApiKey = Schema12.Struct({
1219
+ id: ApiKeyId,
1220
+ organizationId: OrganizationId,
1221
+ name: Schema12.String,
1222
+ prefix: Schema12.String,
1223
+ lastFour: Schema12.String,
1224
+ permission: ApiKeyPermission,
1225
+ projectId: Schema12.NullOr(ProjectId),
1226
+ createdAt: Timestamp,
1227
+ createdBy: ActorReference,
1228
+ lastUsedAt: Schema12.NullOr(Timestamp),
1229
+ expiresAt: Schema12.NullOr(Timestamp),
1230
+ revokedAt: Schema12.NullOr(Timestamp)
1231
+ });
1232
+ var CreateApiKey = Schema12.Struct({
1233
+ name: Name,
1234
+ permission: ApiKeyPermission,
1235
+ projectId: Schema12.optional(ProjectId),
1236
+ expiresAt: Schema12.optional(Timestamp)
1237
+ });
1238
+ var CreatedApiKey = Schema12.Struct({
1239
+ key: ApiKey,
1240
+ secret: Schema12.String
1241
+ });
1242
+ var Theme = Schema12.Literals(["light", "dark", "system"]);
1243
+ var Preferences = Schema12.Struct({
1244
+ defaultEnvironment: EnvironmentName,
1245
+ openActorLinksInNewTab: Schema12.Boolean,
1246
+ timeZone: Schema12.String,
1247
+ pauseLiveTailOnScroll: Schema12.Boolean,
1248
+ showReplayedCommands: Schema12.Boolean,
1249
+ theme: Theme
1250
+ });
1251
+ var UpdatePreferences = Schema12.Struct({
1252
+ defaultEnvironment: Schema12.optional(EnvironmentName),
1253
+ openActorLinksInNewTab: Schema12.optional(Schema12.Boolean),
1254
+ timeZone: Schema12.optional(Schema12.String),
1255
+ pauseLiveTailOnScroll: Schema12.optional(Schema12.Boolean),
1256
+ showReplayedCommands: Schema12.optional(Schema12.Boolean),
1257
+ theme: Schema12.optional(Theme)
1258
+ });
1259
+ var NotificationEvent = Schema12.Literals([
1260
+ "deploy_failed",
1261
+ "dead_letter",
1262
+ "spend_threshold"
1263
+ ]);
1264
+ var NotificationPreference = Schema12.Struct({
1265
+ event: NotificationEvent,
1266
+ email: Schema12.Boolean,
1267
+ slack: Schema12.Boolean
1268
+ });
1269
+ var NotificationSettings = Schema12.Struct({
1270
+ preferences: Schema12.Array(NotificationPreference)
1271
+ });
1272
+ var PinnedActor = Schema12.Struct({
1273
+ projectId: ProjectId,
1274
+ environment: EnvironmentName,
1275
+ address: Schema12.String,
1276
+ status: Schema12.Literals(["awake", "idle", "unknown"]),
1277
+ lastActivityAt: Schema12.NullOr(Timestamp)
1278
+ });
1279
+ var PinActor = Schema12.Struct({
1280
+ projectId: ProjectId,
1281
+ environment: EnvironmentName,
1282
+ address: Schema12.String
1283
+ });
1284
+ // ../../packages/cloud-api/src/projects.ts
1285
+ import { Schema as Schema13 } from "effect";
1286
+ var ProjectStatus = Schema13.Literals(["empty", "live", "deploying", "failed"]);
1287
+ var Region = Schema13.Struct({ id: RegionId, city: Schema13.String });
1288
+ var Project = Schema13.Struct({
1289
+ id: ProjectId,
1290
+ organizationId: OrganizationId,
1291
+ name: Schema13.String,
1292
+ slug: Slug,
1293
+ status: ProjectStatus,
1294
+ homeRegion: RegionId,
1295
+ createdAt: Timestamp
1296
+ });
1297
+ var CreateProject = Schema13.Struct({ name: Name, slug: Slug, homeRegion: RegionId });
1298
+ var UpdateProject = Schema13.Struct({
1299
+ name: Schema13.optional(Name),
1300
+ slug: Schema13.optional(Slug)
1301
+ });
1302
+ var Environment = Schema13.Struct({
1303
+ name: EnvironmentName,
1304
+ projectId: ProjectId,
1305
+ currentDeploymentId: Schema13.NullOr(DeploymentId),
1306
+ database: Schema13.optionalKey(Schema13.Struct({
1307
+ source: Schema13.Literals(["managed", "customer"]),
1308
+ state: Schema13.Literals(["provisioning", "ready", "read-only", "failed"])
1309
+ }))
1310
+ });
1311
+ var CreateEnvironment = Schema13.Struct({ name: EnvironmentName });
1312
+ var EnvVariable = Schema13.Struct({
1313
+ name: Schema13.String,
1314
+ usedBy: Schema13.Array(Schema13.String),
1315
+ updatedAt: Timestamp,
1316
+ updatedBy: Schema13.NullOr(ActorReference)
1317
+ });
1318
+ var EnvVariableName = Schema13.String.pipe(Schema13.check(Schema13.isPattern(/^[A-Za-z_][A-Za-z0-9_]{0,255}$/)));
1319
+ var SetEnvVariable = Schema13.Struct({
1320
+ value: Schema13.String.pipe(Schema13.check(Schema13.isMaxLength(65536)))
1321
+ });
1322
+ var ImportEnvVariables = Schema13.Struct({
1323
+ content: Schema13.String.pipe(Schema13.check(Schema13.isMaxLength(1048576)))
1324
+ });
1325
+ var ImportEnvVariablesResult = Schema13.Struct({
1326
+ created: Schema13.Array(Schema13.String),
1327
+ updated: Schema13.Array(Schema13.String)
1328
+ });
1329
+ var DomainStatus = Schema13.Literals(["pending", "verifying", "active"]);
1330
+ var DnsRecord = Schema13.Struct({
1331
+ type: Schema13.Literals(["A", "AAAA", "CNAME", "TXT"]),
1332
+ name: Schema13.String,
1333
+ value: Schema13.String
1334
+ });
1335
+ var Hostname = Schema13.String.pipe(Schema13.check(Schema13.isMaxLength(253), Schema13.isPattern(/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/)));
1336
+ var Domain = Schema13.Struct({
1337
+ id: DomainId,
1338
+ hostname: Hostname,
1339
+ environment: EnvironmentName,
1340
+ status: DomainStatus,
1341
+ dnsRecords: Schema13.Array(DnsRecord),
1342
+ createdAt: Timestamp
1343
+ });
1344
+ var AddDomain = Schema13.Struct({ hostname: Hostname, environment: EnvironmentName });
1345
+ var OwnedTable = Schema13.Struct({
1346
+ name: Schema13.String,
1347
+ actor: Schema13.String,
1348
+ rows: NonNegativeInt,
1349
+ sizeBytes: NonNegativeInt,
1350
+ region: RegionId
1351
+ });
1352
+ var ProjectRegion = Schema13.Struct({
1353
+ region: Region,
1354
+ home: Schema13.Boolean,
1355
+ tenantCount: NonNegativeInt,
1356
+ database: Schema13.Struct({ version: Schema13.String, sizeBytes: NonNegativeInt }),
1357
+ storage: Schema13.Struct({ usedBytes: NonNegativeInt, limitBytes: NonNegativeInt }),
1358
+ cpuPercent: NonNegative,
1359
+ connections: Schema13.Struct({ used: NonNegativeInt, limit: NonNegativeInt }),
1360
+ runners: NonNegativeInt,
1361
+ largestTables: Schema13.Array(OwnedTable)
1362
+ });
1363
+ var AddRegion = Schema13.Struct({ region: RegionId });
1364
+ var SetHomeRegion = Schema13.Struct({ region: RegionId });
1365
+ var ProjectEndpoints = Schema13.Struct({
1366
+ httpBaseUrl: Schema13.String,
1367
+ webSocketUrl: Schema13.String,
1368
+ openApiPath: Schema13.String,
1369
+ mcpPath: Schema13.String
1370
+ });
1371
+ var IntegrationKind = Schema13.Literals([
1372
+ "github",
1373
+ "slack",
1374
+ "datadog",
1375
+ "opentelemetry",
1376
+ "pagerduty"
1377
+ ]);
1378
+ var Integration = Schema13.Struct({
1379
+ kind: IntegrationKind,
1380
+ status: Schema13.Literals(["connected", "disconnected", "error"]),
1381
+ label: Schema13.NullOr(Schema13.String),
1382
+ connectedAt: Schema13.NullOr(Timestamp)
1383
+ });
1384
+ var ConnectIntegration = Schema13.Struct({
1385
+ settings: Schema13.optional(Schema13.Record(Schema13.String, Schema13.String))
1386
+ });
1387
+ var IntegrationConnection = Schema13.Struct({
1388
+ redirectUrl: Schema13.NullOr(Schema13.String),
1389
+ integration: Integration
1390
+ });
1391
+ // ../../packages/cloud-api/src/deployments.ts
1392
+ import { Schema as Schema14 } from "effect";
1393
+ var DeploymentStatus = Schema14.Literals([
1394
+ "in-progress",
1395
+ "live",
1396
+ "drained",
1397
+ "rolled-back",
1398
+ "failed"
1399
+ ]);
1400
+ var RolloutStepName = Schema14.Literals([
1401
+ "build",
1402
+ "migrate",
1403
+ "start-runners",
1404
+ "drain-previous"
1405
+ ]);
1406
+ var RolloutStep = Schema14.Struct({
1407
+ name: RolloutStepName,
1408
+ status: Schema14.Literals(["pending", "running", "succeeded", "failed", "skipped"]),
1409
+ durationMs: Schema14.NullOr(NonNegativeInt),
1410
+ detail: Schema14.NullOr(Schema14.String)
1411
+ });
1412
+ var DeploymentRunner = Schema14.Struct({
1413
+ id: Schema14.String,
1414
+ region: RegionId,
1415
+ actorCount: Schema14.NullOr(NonNegativeInt),
1416
+ cpuPercent: Schema14.NullOr(NonNegative),
1417
+ health: Schema14.Literals(["healthy", "unhealthy", "starting", "draining"])
1418
+ });
1419
+ var DeploymentAuthor = Schema14.Struct({
1420
+ name: Schema14.String,
1421
+ image: Schema14.NullOr(Schema14.String)
1422
+ });
1423
+ var DeploymentSummary = Schema14.Struct({
1424
+ id: DeploymentId,
1425
+ projectId: ProjectId,
1426
+ environment: EnvironmentName,
1427
+ environmentHost: Schema14.optional(Schema14.String),
1428
+ commitSha: CommitSha,
1429
+ message: Schema14.String,
1430
+ author: DeploymentAuthor,
1431
+ regions: Schema14.Array(RegionId),
1432
+ runnerCount: NonNegativeInt,
1433
+ durationMs: Schema14.NullOr(NonNegativeInt),
1434
+ status: DeploymentStatus,
1435
+ rolledBackFrom: Schema14.NullOr(DeploymentId),
1436
+ createdAt: Timestamp
1437
+ });
1438
+ var DeploymentDetail = Schema14.Struct({
1439
+ ...DeploymentSummary.fields,
1440
+ steps: Schema14.Array(RolloutStep),
1441
+ runners: Schema14.Array(DeploymentRunner)
1442
+ });
1443
+ var SourceDigest = Schema14.String.pipe(Schema14.check(Schema14.isPattern(/^sha256:[a-f0-9]{64}$/u)));
1444
+ var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
1445
+ var SourceArchive = Schema14.Struct({
1446
+ digest: SourceDigest,
1447
+ sizeBytes: NonNegativeInt
1448
+ });
1449
+ var SOURCE_ENTRY = "src/app.ts";
1450
+ var DeploymentSource = Schema14.Struct({
1451
+ digest: SourceDigest,
1452
+ dockerfile: Schema14.optionalKey(Schema14.Never.annotate({
1453
+ message: `source.dockerfile is not accepted: the platform builds every source from ${SOURCE_ENTRY}`
1454
+ }))
1455
+ });
1456
+ var CreateDeployment = Schema14.Struct({
1457
+ environment: EnvironmentName,
1458
+ commitSha: CommitSha,
1459
+ message: Schema14.optional(Schema14.String),
1460
+ regions: Schema14.optional(Schema14.Array(RegionId)),
1461
+ source: Schema14.optional(DeploymentSource)
1462
+ });
1463
+ var ImageDigest = Schema14.String.pipe(Schema14.check(Schema14.isPattern(/^(?:[^\s@]+@)?sha256:[a-f0-9]{64}$/u)));
1464
+ var RecordBuild = Schema14.Struct({
1465
+ image: ImageDigest,
1466
+ commitSha: CommitSha,
1467
+ environmentSnapshot: Schema14.Record(Schema14.String, Schema14.String).check(Schema14.makeFilter((snapshot) => Object.keys(snapshot).every((key) => /^[A-Za-z_][A-Za-z0-9_]{0,254}$/u.test(key)) || "Environment variable names must be valid identifiers"))
1468
+ });
1469
+ var FailBuild = Schema14.Struct({
1470
+ reason: Schema14.String.check(Schema14.isMinLength(1), Schema14.isMaxLength(4096))
1471
+ });
1472
+ var BuildLogLine = Schema14.Struct({
1473
+ index: NonNegativeInt,
1474
+ at: Timestamp,
1475
+ stream: Schema14.Literals(["stdout", "stderr"]),
1476
+ text: Schema14.String
1477
+ });
1478
+ var BuildLog = Schema14.Struct({
1479
+ lines: Schema14.Array(BuildLogLine),
1480
+ complete: Schema14.Boolean
1481
+ });
1482
+ // ../../packages/cloud-api/src/runtime.ts
1483
+ import * as Framework from "@rikalabs/akter/client";
1484
+ import { Schema as Schema15, Struct as Struct2 } from "effect";
1485
+ var SidebarCounts = Schema15.Struct({
1486
+ actorTypes: NonNegativeInt,
1487
+ openDeadLetters: NonNegativeInt
1488
+ });
1489
+ var Overview = Schema15.Struct({
1490
+ commands: Schema15.NullOr(Schema15.Struct({
1491
+ perSecond: NonNegative,
1492
+ series24h: Schema15.Array(SeriesPoint),
1493
+ p50Ms: Schema15.NullOr(NonNegative),
1494
+ p99Ms: Schema15.NullOr(NonNegative)
1495
+ })),
1496
+ actors: Schema15.Struct({ awake: Schema15.NullOr(NonNegativeInt), total: NonNegativeInt }),
1497
+ jobs: Schema15.Struct({ inFlight: NonNegativeInt, donePerHour: Schema15.NullOr(NonNegativeInt) }),
1498
+ deadLettersByJobType: Schema15.Array(Schema15.Struct({ jobName: Schema15.String, count: NonNegativeInt })),
1499
+ throughput: Schema15.NullOr(Schema15.Array(SeriesPoint)),
1500
+ p99: Schema15.NullOr(Schema15.Array(SeriesPoint)),
1501
+ health: Schema15.Struct({
1502
+ runners: Schema15.NullOr(Schema15.Struct({ healthy: NonNegativeInt, total: NonNegativeInt })),
1503
+ databaseCpuPercent: Schema15.NullOr(NonNegative),
1504
+ maxMailbox: Schema15.NullOr(Schema15.Struct({ depth: NonNegativeInt, actor: Schema15.NullOr(ActorAddress) })),
1505
+ parkedSockets: Schema15.NullOr(NonNegativeInt),
1506
+ outboxLagP99Ms: Schema15.NullOr(NonNegative),
1507
+ lastDeployAt: Schema15.NullOr(Timestamp)
1508
+ }),
1509
+ recentDeployments: Schema15.NullOr(Schema15.Array(DeploymentSummary))
1510
+ });
1511
+ var ActorTypeSummary = Schema15.Struct({
1512
+ name: Schema15.String,
1513
+ commands: Schema15.NullOr(Schema15.Array(Schema15.String)),
1514
+ instances: NonNegativeInt,
1515
+ awake: Schema15.NullOr(NonNegativeInt),
1516
+ commandsPerSecond: Schema15.NullOr(NonNegative),
1517
+ p99Ms: Schema15.NullOr(NonNegative),
1518
+ maxMailbox: Schema15.NullOr(NonNegativeInt)
1519
+ });
1520
+ var SeriesWindow = Schema15.Literals(["1h", "24h", "7d"]);
1521
+ var CommandVolume = Schema15.Struct({
1522
+ command: Schema15.String,
1523
+ count: NonNegativeInt,
1524
+ perSecond: NonNegative
1525
+ });
1526
+ var ActorTypeActivity = Schema15.Struct({
1527
+ window: SeriesWindow,
1528
+ since: Timestamp,
1529
+ series: Schema15.Array(SeriesPoint),
1530
+ commands: Schema15.Array(CommandVolume)
1531
+ });
1532
+ var LatencyBucket = Schema15.Struct({
1533
+ upToMs: Schema15.NullOr(NonNegative),
1534
+ count: NonNegativeInt
1535
+ });
1536
+ var TurnLatency = Schema15.Struct({
1537
+ window: SeriesWindow,
1538
+ since: Timestamp,
1539
+ buckets: Schema15.Array(LatencyBucket),
1540
+ p50Ms: Schema15.NullOr(NonNegative),
1541
+ p95Ms: Schema15.NullOr(NonNegative),
1542
+ p99Ms: Schema15.NullOr(NonNegative)
1543
+ });
1544
+ var ActorInstance = Schema15.Struct({
1545
+ key: Schema15.String,
1546
+ status: Schema15.NullOr(Schema15.Literals(["awake", "idle"])),
1547
+ lastCommand: Schema15.NullOr(Schema15.String),
1548
+ lastActivityAt: Schema15.NullOr(Timestamp),
1549
+ generation: NonNegativeInt
1550
+ });
1551
+ var OwnedTableRows = Schema15.Struct({
1552
+ table: Schema15.String,
1553
+ columns: Schema15.Array(Schema15.String),
1554
+ rows: Schema15.Array(Schema15.Array(Schema15.Json))
1555
+ });
1556
+ var CommandCaller = Schema15.Struct({
1557
+ kind: Schema15.Literals(["user", "anonymous", "system"]),
1558
+ subject: Schema15.NullOr(Schema15.String),
1559
+ source: Schema15.NullOr(Schema15.String)
1560
+ });
1561
+ var Receipt = Schema15.Struct({
1562
+ commandId: Schema15.String,
1563
+ command: Schema15.String,
1564
+ result: Schema15.NullOr(Schema15.String),
1565
+ caller: Schema15.NullOr(CommandCaller),
1566
+ at: Schema15.NullOr(Timestamp),
1567
+ expiresAt: Timestamp,
1568
+ replayed: Schema15.Boolean
1569
+ });
1570
+ var ActorEvent = Schema15.Struct({
1571
+ name: Schema15.String,
1572
+ cursor: Schema15.String,
1573
+ emittedAt: Timestamp,
1574
+ subscribers: Schema15.NullOr(NonNegativeInt)
1575
+ });
1576
+ var JobStatus = Schema15.Literals(["queued", "running", "retrying", "done", "dead"]);
1577
+ var ActorJob = Schema15.Struct({
1578
+ name: Schema15.String,
1579
+ id: Schema15.String,
1580
+ attempts: NonNegativeInt,
1581
+ status: JobStatus
1582
+ });
1583
+ var ActorTimelineEntry = Schema15.Struct({
1584
+ at: Timestamp,
1585
+ kind: Schema15.Literals(["command", "event", "job"]),
1586
+ label: Schema15.String,
1587
+ detail: Schema15.NullOr(Schema15.String),
1588
+ caller: Schema15.NullOr(CommandCaller)
1589
+ });
1590
+ var ActorInspector = Schema15.Struct({
1591
+ address: ActorAddress,
1592
+ state: Schema15.Json,
1593
+ turn: Schema15.NullOr(NonNegativeInt),
1594
+ tables: Schema15.NullOr(Schema15.Array(OwnedTableRows)),
1595
+ receipts: Schema15.Array(Receipt),
1596
+ events: Schema15.Array(ActorEvent),
1597
+ jobs: Schema15.Array(ActorJob),
1598
+ connections: Schema15.Struct({
1599
+ sockets: Schema15.NullOr(NonNegativeInt),
1600
+ feedCursor: Schema15.NullOr(Schema15.String)
1601
+ }),
1602
+ properties: Schema15.Struct({
1603
+ status: Schema15.NullOr(Schema15.Literals(["awake", "idle"])),
1604
+ type: Schema15.String,
1605
+ generation: NonNegativeInt,
1606
+ runner: Schema15.NullOr(Schema15.String),
1607
+ region: Schema15.NullOr(Schema15.String),
1608
+ tenant: Schema15.String,
1609
+ mailboxDepth: Schema15.NullOr(NonNegativeInt)
1610
+ }),
1611
+ timeline: Schema15.NullOr(Schema15.Array(ActorTimelineEntry))
1612
+ });
1613
+ var CommandOutcome = Schema15.Literals(["ok", "error", "replayed"]);
1614
+ var CommandLogEntry = Schema15.Struct({
1615
+ commandId: Schema15.String,
1616
+ at: Schema15.NullOr(Timestamp),
1617
+ durationMs: Schema15.NullOr(NonNegative),
1618
+ address: ActorAddress,
1619
+ command: Schema15.String,
1620
+ caller: Schema15.NullOr(CommandCaller),
1621
+ payloadPreview: Schema15.NullOr(Schema15.String),
1622
+ outcome: CommandOutcome,
1623
+ errorTag: Schema15.NullOr(Schema15.String)
1624
+ });
1625
+ var commandName = Schema15.String.pipe(Schema15.check(Schema15.isMinLength(1), Schema15.isMaxLength(128)));
1626
+ var SendCommand = Schema15.Struct({
1627
+ address: ActorAddress,
1628
+ command: commandName,
1629
+ payload: Schema15.Json,
1630
+ commandId: Schema15.optional(commandName)
1631
+ });
1632
+ var CommandSent = Schema15.Struct({
1633
+ commandId: Schema15.String,
1634
+ result: Schema15.Json,
1635
+ replayed: Schema15.Boolean
1636
+ });
1637
+
1638
+ class CommandExpired extends Schema15.TaggedError()("CommandExpired", { commandId: Schema15.String }, { httpApiStatus: 410 }) {
1639
+ }
1640
+
1641
+ class CommandStreamGap extends Schema15.TaggedError()("CommandStreamGap", {}) {
1642
+ }
1643
+
1644
+ class RunnerDefect extends Schema15.TaggedError()("RunnerDefect", {}, { httpApiStatus: 502 }) {
1645
+ }
1646
+
1647
+ class CommandRefused extends Schema15.TaggedError()("CommandRefused", {
1648
+ commandId: Schema15.String,
1649
+ reasonTag: Schema15.String,
1650
+ reason: Framework.ActorError.fields.reason
1651
+ }, { httpApiStatus: 422 }) {
1652
+ }
1653
+
1654
+ class CommandFailed2 extends Schema15.TaggedError()("CommandFailed", {
1655
+ commandId: Schema15.String,
1656
+ errorTag: Schema15.String,
1657
+ error: Schema15.Json,
1658
+ replayed: Schema15.Boolean
1659
+ }, { httpApiStatus: 422 }) {
1660
+ }
1661
+
1662
+ class QuotaExceeded2 extends Schema15.TaggedError()("QuotaExceeded", Struct2.omit(Framework.QuotaExceeded.fields, ["_tag"]), { httpApiStatus: 429 }) {
1663
+ }
1664
+
1665
+ class QuotaUnbound extends Schema15.TaggedError()("QuotaUnbound", {
1666
+ deployment: Schema15.String,
1667
+ tenant: Schema15.String,
1668
+ reason: Schema15.Literals(["tenant", "account", "plan"])
1669
+ }, { httpApiStatus: 402 }) {
1670
+ }
1671
+ var QuotaErrors = [QuotaExceeded2, QuotaUnbound];
1672
+ var JobTypeStats = Schema15.Struct({
1673
+ jobName: Schema15.String,
1674
+ done: Schema15.NullOr(NonNegativeInt),
1675
+ retried: NonNegativeInt,
1676
+ dead: NonNegativeInt,
1677
+ p99Ms: Schema15.NullOr(NonNegative)
1678
+ });
1679
+ var JobsSummary = Schema15.Struct({
1680
+ queued: NonNegativeInt,
1681
+ running: Schema15.NullOr(NonNegativeInt),
1682
+ retrying: NonNegativeInt,
1683
+ dead: NonNegativeInt,
1684
+ byType: Schema15.Array(JobTypeStats),
1685
+ throughput: Schema15.NullOr(Schema15.Array(SeriesPoint))
1686
+ });
1687
+ var DeadLetter = Schema15.Struct({
1688
+ id: DeadLetterId,
1689
+ jobName: Schema15.String,
1690
+ jobId: Schema15.String,
1691
+ actor: ActorAddress,
1692
+ attempts: NonNegativeInt,
1693
+ lastError: Schema15.String,
1694
+ since: Timestamp
1695
+ });
1696
+ var Workflow = Schema15.Struct({
1697
+ id: Schema15.String,
1698
+ name: Schema15.String,
1699
+ actor: ActorAddress,
1700
+ step: Schema15.NullOr(Schema15.Struct({
1701
+ index: Schema15.Int.pipe(Schema15.check(Schema15.isGreaterThanOrEqualTo(1))),
1702
+ total: Schema15.NullOr(Schema15.Int.pipe(Schema15.check(Schema15.isGreaterThanOrEqualTo(1)))),
1703
+ name: Schema15.String
1704
+ }).pipe(Schema15.check(Schema15.makeFilter((step) => step.total === null || step.index <= step.total || "step.index must not exceed step.total")))),
1705
+ waitingFor: Schema15.NullOr(Schema15.Struct({ kind: Schema15.Literals(["event", "timer"]), name: Schema15.String })),
1706
+ startedAt: Timestamp,
1707
+ status: Schema15.NullOr(Schema15.Literals(["running", "waiting", "completed", "failed"]))
1708
+ });
1709
+ var TimersSummary = Schema15.Struct({
1710
+ pending: NonNegativeInt,
1711
+ nextFireAt: Schema15.NullOr(Timestamp)
1712
+ });
1713
+ var Schedule = Schema15.Struct({
1714
+ name: Schema15.String,
1715
+ actorPattern: Schema15.String,
1716
+ cron: Schema15.String,
1717
+ lastRun: Schema15.NullOr(Schema15.Struct({
1718
+ at: Timestamp,
1719
+ outcome: Schema15.Literals(["ok", "error"]),
1720
+ durationMs: Schema15.NullOr(NonNegative)
1721
+ })),
1722
+ nextRunAt: Schema15.NullOr(Timestamp)
1723
+ });
1724
+ var ConnectionsSummary = Schema15.Struct({
1725
+ open: NonNegativeInt,
1726
+ parked: Schema15.NullOr(NonNegativeInt),
1727
+ sseStreams: NonNegativeInt,
1728
+ feedSubscribers: NonNegativeInt,
1729
+ replayGaps: Schema15.NullOr(NonNegativeInt),
1730
+ openVersusParked: Schema15.NullOr(Schema15.Array(Schema15.Struct({ at: Timestamp, open: NonNegativeInt, parked: NonNegativeInt }))),
1731
+ byActorType: Schema15.Array(Schema15.Struct({
1732
+ actorType: Schema15.String,
1733
+ open: NonNegativeInt,
1734
+ parked: Schema15.NullOr(NonNegativeInt),
1735
+ sse: NonNegativeInt
1736
+ }))
1737
+ });
1738
+ var SearchResult = Schema15.Struct({
1739
+ kind: Schema15.Literals(["actor-type", "actor", "deployment"]),
1740
+ id: Schema15.String,
1741
+ title: Schema15.String,
1742
+ subtitle: Schema15.NullOr(Schema15.String)
1743
+ });
1744
+ // ../../packages/cloud-api/src/billing.ts
1745
+ import { Schema as Schema16 } from "effect";
1746
+ var Plan = Schema16.TaggedStruct("known", {
1747
+ id: PlanId,
1748
+ name: Schema16.String,
1749
+ basePriceCents: NonNegativeInt,
1750
+ currency: Schema16.Literal("usd"),
1751
+ renewsAt: Schema16.NullOr(Timestamp),
1752
+ monthToDateEstimateCents: NonNegativeInt,
1753
+ provisional: Schema16.optionalKey(Schema16.Boolean),
1754
+ subscribedId: Schema16.optionalKey(PlanId),
1755
+ paymentStatus: Schema16.optionalKey(Schema16.Literals(["free", "active", "past_due", "unpaid", "canceled", "incomplete"]))
1756
+ });
1757
+ var CardPaymentMethod = Schema16.TaggedStruct("card", {
1758
+ brand: Schema16.String,
1759
+ lastFour: Schema16.String,
1760
+ expiryMonth: Schema16.Int.pipe(Schema16.check(Schema16.isBetween({ minimum: 1, maximum: 12 }))),
1761
+ expiryYear: NonNegativeInt
1762
+ });
1763
+ var LinkPaymentMethod = Schema16.TaggedStruct("link", {
1764
+ email: Schema16.NullOr(Schema16.String)
1765
+ });
1766
+ var PaymentMethod = Schema16.Union([CardPaymentMethod, LinkPaymentMethod]);
1767
+ var SpendLimit = Schema16.Struct({
1768
+ limitCents: Schema16.NullOr(NonNegativeInt),
1769
+ currentSpendCents: NonNegativeInt
1770
+ });
1771
+ var capFields = {
1772
+ limit: Schema16.NullOr(NonNegative),
1773
+ used: NonNegative,
1774
+ atCap: Schema16.Boolean,
1775
+ refusing: Schema16.Boolean,
1776
+ reason: Schema16.optionalKey(Schema16.Literal("unbound"))
1777
+ };
1778
+ var CapState = Schema16.Struct({
1779
+ cap: Schema16.Literals(["spend", "connections", "compute", "storage"]),
1780
+ ...capFields
1781
+ });
1782
+ var BillingSummary = Schema16.Struct({
1783
+ plan: Schema16.Union([Plan, UnboundPlan]),
1784
+ paymentMethod: Schema16.NullOr(PaymentMethod),
1785
+ billingEmail: Schema16.NullOr(Email),
1786
+ spendLimit: SpendLimit,
1787
+ caps: Schema16.optionalKey(Schema16.Array(CapState))
1788
+ });
1789
+ var SetSpendLimit = Schema16.Struct({ limitCents: Schema16.NullOr(NonNegativeInt) });
1790
+ var Invoice = Schema16.Struct({
1791
+ id: InvoiceId,
1792
+ number: Schema16.String,
1793
+ periodStart: Timestamp,
1794
+ periodEnd: Timestamp,
1795
+ amountCents: NonNegativeInt,
1796
+ currency: Schema16.Literal("usd"),
1797
+ status: Schema16.Literals(["draft", "open", "paid", "void", "uncollectible"]),
1798
+ pdfUrl: Schema16.NullOr(Schema16.String)
1799
+ });
1800
+ var StartCheckout = Schema16.Struct({ plan: Schema16.Literals(["pro", "team", "enterprise"]) });
1801
+ var PlanChange = Schema16.Struct({
1802
+ requestId: Schema16.String,
1803
+ status: Schema16.Literals(["pending", "completed", "failed"])
1804
+ });
1805
+ var HostedSession = Schema16.Struct({ url: Schema16.String });
1806
+ var machineSizeFields = {
1807
+ cpuKind: Schema16.Literals(["shared", "performance"]),
1808
+ cpus: Schema16.Int.check(Schema16.isGreaterThanOrEqualTo(1)),
1809
+ memoryMb: Schema16.Int.check(Schema16.isGreaterThanOrEqualTo(1))
1810
+ };
1811
+ var PlanFeature = Schema16.Literals([
1812
+ "compute-overage",
1813
+ "compute-cap",
1814
+ "storage-overage",
1815
+ "storage-cap",
1816
+ "byo-database",
1817
+ "dedicated-database",
1818
+ "checkout"
1819
+ ]);
1820
+ var CatalogPlan = Schema16.Struct({
1821
+ id: PlanId,
1822
+ name: Schema16.String,
1823
+ basePriceCents: NonNegativeInt,
1824
+ currency: Schema16.Literal("usd"),
1825
+ allowances: Schema16.Struct({
1826
+ computeUnitHours: NonNegative,
1827
+ computeUnitHourCap: Schema16.NullOr(NonNegative),
1828
+ storageGb: NonNegative,
1829
+ storageGbCap: Schema16.NullOr(NonNegative),
1830
+ concurrentConnections: NonNegativeInt
1831
+ }),
1832
+ overage: Schema16.Struct({
1833
+ computeCentsPerUnitHour: NonNegative,
1834
+ storageCentsPerGbMonth: NonNegative
1835
+ }),
1836
+ features: Schema16.Array(PlanFeature),
1837
+ provisional: Schema16.Boolean
1838
+ });
1839
+ var ComputeSize = Schema16.Struct({
1840
+ ...machineSizeFields,
1841
+ unitsPerHour: Schema16.Finite.check(Schema16.isGreaterThan(0))
1842
+ });
1843
+ var PlanCatalog = Schema16.Struct({
1844
+ plans: Schema16.Array(CatalogPlan),
1845
+ computeSizes: Schema16.Array(ComputeSize),
1846
+ provisional: Schema16.Boolean
1847
+ });
1848
+ var UsageMeterName = Schema16.Literals(["runnerHours", "storageGb"]);
1849
+ var UsageMeter = Schema16.Struct({
1850
+ meter: UsageMeterName,
1851
+ used: NonNegative,
1852
+ included: NonNegative,
1853
+ overage: NonNegative,
1854
+ overageCostCents: NonNegative
1855
+ });
1856
+ var UsagePricing = Schema16.Struct({
1857
+ computeCentsPerUnitHour: NonNegative,
1858
+ storageCentsPerGbMonth: NonNegative,
1859
+ provisional: Schema16.optionalKey(Schema16.Boolean)
1860
+ });
1861
+ var ComputeUsage = Schema16.Struct({
1862
+ environmentId: EnvironmentId,
1863
+ ...machineSizeFields,
1864
+ machineHours: NonNegative,
1865
+ computeUnitHours: NonNegative
1866
+ });
1867
+ var StorageSample = Schema16.Struct({ bytes: NonNegative, sampledAt: Timestamp });
1868
+ var Usage = Schema16.Struct({
1869
+ period: BillingPeriod,
1870
+ meters: Schema16.Array(UsageMeter),
1871
+ latestStorageSample: Schema16.NullOr(StorageSample),
1872
+ caps: Schema16.Array(CapState),
1873
+ byProject: Schema16.Array(Schema16.Struct({
1874
+ projectId: ProjectId,
1875
+ name: Schema16.String,
1876
+ computeUnitHours: NonNegative,
1877
+ compute: Schema16.Array(ComputeUsage),
1878
+ storageGbMonths: NonNegative,
1879
+ estimatedCostCents: NonNegative
1880
+ })),
1881
+ pricing: UsagePricing
1882
+ });
1883
+ var AuditEntry = Schema16.Struct({
1884
+ id: Schema16.String,
1885
+ at: Timestamp,
1886
+ actor: ActorReference,
1887
+ action: Schema16.String,
1888
+ target: Schema16.Struct({
1889
+ type: Schema16.String,
1890
+ id: Schema16.NullOr(Schema16.String),
1891
+ name: Schema16.NullOr(Schema16.String)
1892
+ }),
1893
+ ipAddress: Schema16.NullOr(Schema16.String)
1894
+ });
1895
+ // ../../packages/cloud-api/src/contract.ts
1896
+ import { HttpApi, OpenApi as OpenApi3 } from "effect/http-api";
1897
+
1898
+ // ../../packages/cloud-api/src/groups/account.ts
1899
+ import { Schema as Schema17 } from "effect";
1900
+ import { HttpApiEndpoint, HttpApiGroup } from "effect/http-api";
1901
+ var organizationParams = { organizationId: OrganizationId };
1902
+
1903
+ class AccountGroup extends HttpApiGroup.make("account").add(HttpApiEndpoint.get("me", "/me", { success: Me, error: SelfErrors }), HttpApiEndpoint.get("exportData", "/me/export", {
1904
+ success: PersonalDataExport,
1905
+ error: SelfErrors
1906
+ }), HttpApiEndpoint.delete("deleteAccount", "/me", {
1907
+ payload: DeleteAccount,
1908
+ error: WriteErrors
1909
+ }), HttpApiEndpoint.patch("updateProfile", "/me", {
1910
+ payload: UpdateProfile,
1911
+ success: User2,
1912
+ error: SelfErrors
1913
+ }), HttpApiEndpoint.put("setActiveOrganization", "/me/active-organization", {
1914
+ payload: SetActiveOrganization,
1915
+ success: OrganizationMembership,
1916
+ error: WriteErrors
1917
+ }), HttpApiEndpoint.get("getPreferences", "/me/preferences", {
1918
+ success: Preferences,
1919
+ error: SelfErrors
1920
+ }), HttpApiEndpoint.patch("updatePreferences", "/me/preferences", {
1921
+ payload: UpdatePreferences,
1922
+ success: Preferences,
1923
+ error: SelfErrors
1924
+ }), HttpApiEndpoint.get("getNotifications", "/me/notifications", {
1925
+ success: NotificationSettings,
1926
+ error: SelfErrors
1927
+ }), HttpApiEndpoint.put("setNotifications", "/me/notifications", {
1928
+ payload: NotificationSettings,
1929
+ success: NotificationSettings,
1930
+ error: SelfErrors
1931
+ }), HttpApiEndpoint.get("listPinnedActors", "/me/pinned-actors", {
1932
+ query: { projectId: ProjectId, environment: EnvironmentName },
1933
+ success: Schema17.Array(PinnedActor),
1934
+ error: ReadErrors
1935
+ }), HttpApiEndpoint.post("pinActor", "/me/pinned-actors", {
1936
+ payload: PinActor,
1937
+ success: PinnedActor,
1938
+ error: WriteErrors
1939
+ }), HttpApiEndpoint.delete("unpinActor", "/me/pinned-actors", {
1940
+ query: { projectId: ProjectId, environment: EnvironmentName, address: Schema17.String },
1941
+ error: WriteErrors
1942
+ })) {
1943
+ }
1944
+
1945
+ class OrganizationsGroup extends HttpApiGroup.make("organizations").add(HttpApiEndpoint.get("list", "/organizations", {
1946
+ success: Schema17.Array(OrganizationMembership),
1947
+ error: SelfErrors
1948
+ }), HttpApiEndpoint.post("create", "/organizations", {
1949
+ payload: CreateOrganization,
1950
+ success: OrganizationMembership,
1951
+ error: WriteErrors
1952
+ }), HttpApiEndpoint.get("get", "/organizations/:organizationId", {
1953
+ params: organizationParams,
1954
+ success: OrganizationMembership,
1955
+ error: ReadErrors
1956
+ }), HttpApiEndpoint.patch("update", "/organizations/:organizationId", {
1957
+ params: organizationParams,
1958
+ payload: UpdateOrganization,
1959
+ success: Organization,
1960
+ error: WriteErrors
1961
+ }), HttpApiEndpoint.delete("delete", "/organizations/:organizationId", {
1962
+ params: organizationParams,
1963
+ error: WriteErrors
1964
+ }), HttpApiEndpoint.get("getDeletion", "/organizations/:organizationId/deletion", {
1965
+ params: organizationParams,
1966
+ success: OrganizationDeletion,
1967
+ error: ReadErrors
1968
+ })) {
1969
+ }
1970
+
1971
+ class MembersGroup extends HttpApiGroup.make("members").add(HttpApiEndpoint.get("list", "/organizations/:organizationId/members", {
1972
+ params: organizationParams,
1973
+ success: Schema17.Array(Member),
1974
+ error: ReadErrors
1975
+ }), HttpApiEndpoint.patch("updateRole", "/organizations/:organizationId/members/:memberId", {
1976
+ params: { ...organizationParams, memberId: MemberId },
1977
+ payload: UpdateMemberRole,
1978
+ success: Member,
1979
+ error: WriteErrors
1980
+ }), HttpApiEndpoint.delete("remove", "/organizations/:organizationId/members/:memberId", {
1981
+ params: { ...organizationParams, memberId: MemberId },
1982
+ error: WriteErrors
1983
+ })) {
1984
+ }
1985
+
1986
+ class InvitationsGroup extends HttpApiGroup.make("invitations").add(HttpApiEndpoint.get("list", "/organizations/:organizationId/invitations", {
1987
+ params: organizationParams,
1988
+ success: Schema17.Array(Invitation),
1989
+ error: ReadErrors
1990
+ }), HttpApiEndpoint.post("create", "/organizations/:organizationId/invitations", {
1991
+ params: organizationParams,
1992
+ payload: CreateInvitation,
1993
+ success: Invitation,
1994
+ error: WriteErrors
1995
+ }), HttpApiEndpoint.post("resend", "/organizations/:organizationId/invitations/:invitationId/resend", {
1996
+ params: { ...organizationParams, invitationId: InvitationId },
1997
+ success: Invitation,
1998
+ error: WriteErrors
1999
+ }), HttpApiEndpoint.delete("cancel", "/organizations/:organizationId/invitations/:invitationId", {
2000
+ params: { ...organizationParams, invitationId: InvitationId },
2001
+ error: WriteErrors
2002
+ }), HttpApiEndpoint.get("preview", "/invitations/:invitationId", {
2003
+ params: { invitationId: InvitationId },
2004
+ success: InvitationPreview,
2005
+ error: ReadErrors
2006
+ }), HttpApiEndpoint.post("accept", "/invitations/:invitationId/accept", {
2007
+ params: { invitationId: InvitationId },
2008
+ success: OrganizationMembership,
2009
+ error: WriteErrors
2010
+ }), HttpApiEndpoint.post("decline", "/invitations/:invitationId/decline", {
2011
+ params: { invitationId: InvitationId },
2012
+ error: WriteErrors
2013
+ })) {
2014
+ }
2015
+
2016
+ class ApiKeysGroup extends HttpApiGroup.make("apiKeys").add(HttpApiEndpoint.get("list", "/organizations/:organizationId/api-keys", {
2017
+ params: organizationParams,
2018
+ query: { projectId: Schema17.optional(ProjectId) },
2019
+ success: Schema17.Array(ApiKey),
2020
+ error: ReadErrors
2021
+ }), HttpApiEndpoint.post("create", "/organizations/:organizationId/api-keys", {
2022
+ params: organizationParams,
2023
+ payload: CreateApiKey,
2024
+ success: CreatedApiKey,
2025
+ error: WriteErrors
2026
+ }), HttpApiEndpoint.delete("revoke", "/organizations/:organizationId/api-keys/:keyId", {
2027
+ params: { ...organizationParams, keyId: ApiKeyId },
2028
+ error: WriteErrors
2029
+ })) {
2030
+ }
2031
+
2032
+ // ../../packages/cloud-api/src/groups/billing.ts
2033
+ import { Schema as Schema18 } from "effect";
2034
+ import { HttpApiEndpoint as HttpApiEndpoint2, HttpApiGroup as HttpApiGroup2 } from "effect/http-api";
2035
+ var organizationParams2 = { organizationId: OrganizationId };
2036
+
2037
+ class BillingGroup extends HttpApiGroup2.make("billing").add(HttpApiEndpoint2.get("listPlans", "/billing/plans", {
2038
+ success: PlanCatalog,
2039
+ error: SelfErrors
2040
+ }), HttpApiEndpoint2.get("get", "/organizations/:organizationId/billing", {
2041
+ params: organizationParams2,
2042
+ success: BillingSummary,
2043
+ error: ReadErrors
2044
+ }), HttpApiEndpoint2.get("listInvoices", "/organizations/:organizationId/billing/invoices", {
2045
+ params: organizationParams2,
2046
+ success: Schema18.Array(Invoice),
2047
+ error: ReadErrors
2048
+ }), HttpApiEndpoint2.put("setSpendLimit", "/organizations/:organizationId/billing/spend-limit", {
2049
+ params: organizationParams2,
2050
+ payload: SetSpendLimit,
2051
+ success: SpendLimit,
2052
+ error: WriteErrors
2053
+ }), HttpApiEndpoint2.post("startCheckout", "/organizations/:organizationId/billing/checkout", {
2054
+ params: organizationParams2,
2055
+ payload: StartCheckout,
2056
+ success: HostedSession,
2057
+ error: WriteErrors
2058
+ }), HttpApiEndpoint2.post("changePlan", "/organizations/:organizationId/billing/plan", {
2059
+ params: organizationParams2,
2060
+ payload: StartCheckout,
2061
+ success: PlanChange,
2062
+ error: WriteErrors
2063
+ }), HttpApiEndpoint2.post("openPortal", "/organizations/:organizationId/billing/portal", {
2064
+ params: organizationParams2,
2065
+ success: HostedSession,
2066
+ error: WriteErrors
2067
+ })) {
2068
+ }
2069
+
2070
+ class UsageGroup extends HttpApiGroup2.make("usage").add(HttpApiEndpoint2.get("get", "/organizations/:organizationId/usage", {
2071
+ params: organizationParams2,
2072
+ query: { period: Schema18.optional(BillingPeriod) },
2073
+ success: Usage,
2074
+ error: ReadErrors
2075
+ })) {
2076
+ }
2077
+
2078
+ class AuditGroup extends HttpApiGroup2.make("audit").add(HttpApiEndpoint2.get("list", "/organizations/:organizationId/audit-log", {
2079
+ params: organizationParams2,
2080
+ query: {
2081
+ ...pageQuery,
2082
+ action: Schema18.optional(Schema18.String),
2083
+ actorId: Schema18.optional(Schema18.String)
2084
+ },
2085
+ success: Page(AuditEntry),
2086
+ error: ReadErrors
2087
+ })) {
2088
+ }
2089
+
2090
+ // ../../packages/cloud-api/src/groups/deployments.ts
2091
+ import { Schema as Schema20 } from "effect";
2092
+ import { HttpApiEndpoint as HttpApiEndpoint3, HttpApiGroup as HttpApiGroup3, HttpApiSchema, OpenApi } from "effect/http-api";
2093
+
2094
+ // ../../packages/cloud-api/src/logs.ts
2095
+ import { Schema as Schema19 } from "effect";
2096
+ var MAX_LOG_LINES = 200;
2097
+ var MAX_LOG_TEXT_BYTES = 4096;
2098
+ var MAX_LOG_WINDOW_SECONDS = 3600;
2099
+ var MAX_LOG_WAIT_SECONDS = 20;
2100
+ var LogCursor = Schema19.String.check(Schema19.isMinLength(1), Schema19.isMaxLength(2048));
2101
+ var LogLimit = Schema19.Int.check(Schema19.isBetween({ minimum: 1, maximum: MAX_LOG_LINES }));
2102
+ var LogWait = Schema19.Int.check(Schema19.isBetween({ minimum: 0, maximum: MAX_LOG_WAIT_SECONDS }));
2103
+ var logQuery = {
2104
+ since: Schema19.optional(Timestamp),
2105
+ limit: Schema19.optional(LogLimit),
2106
+ cursor: Schema19.optional(LogCursor),
2107
+ wait: Schema19.optional(LogWait)
2108
+ };
2109
+ var RunnerLogLine = Schema19.Struct({
2110
+ id: Schema19.String.check(Schema19.isMinLength(1), Schema19.isMaxLength(512)),
2111
+ deploymentId: DeploymentId,
2112
+ runnerId: Schema19.String.check(Schema19.isMinLength(1), Schema19.isMaxLength(256)),
2113
+ at: Timestamp,
2114
+ stream: Schema19.Literals(["stdout", "stderr", "unknown"]),
2115
+ text: Schema19.String.check(Schema19.makeFilter((text) => new TextEncoder().encode(text).byteLength <= MAX_LOG_TEXT_BYTES || "Log text exceeds the byte limit")),
2116
+ clipped: Schema19.Boolean
2117
+ });
2118
+ var RunnerLogPage = Schema19.Struct({
2119
+ lines: Schema19.Array(RunnerLogLine).check(Schema19.isMaxLength(MAX_LOG_LINES)),
2120
+ cursor: LogCursor,
2121
+ more: Schema19.Boolean
2122
+ });
2123
+
2124
+ // ../../packages/cloud-api/src/groups/deployments.ts
2125
+ var projectParams = { projectId: ProjectId };
2126
+ var deploymentParams = { ...projectParams, deploymentId: DeploymentId };
2127
+
2128
+ class DeploymentsGroup extends HttpApiGroup3.make("deployments").add(HttpApiEndpoint3.get("getEnvironmentLogs", "/projects/:projectId/environments/:environment/logs", {
2129
+ params: { ...projectParams, environment: EnvironmentName },
2130
+ query: logQuery,
2131
+ success: RunnerLogPage,
2132
+ error: ReadErrors
2133
+ }).annotate(OpenApi.Description, "Reads recent stdout/stderr from the environment's current deployment. Authorization is checked on every read, including resumed polls, and suspended organizations remain readable. Defaults: since five minutes ago, limit 100, wait 0. The server clamps since to the past hour through now, so network delay and client clock skew do not reject a window boundary; limit is 1–200 and wait is 0–20 seconds. A cursor, at most 2048 characters, resumes the same authorized resource and takes precedence over since. More means another page is immediately available: continue with the cursor and wait 0 until more is false, then follow with wait 20. A current-deployment change switches environment follow to the new deployment and resets its provider position, retaining the original bounded window. Provider retention is recent and best-effort, not an archive. No current deployment returns an empty page. Text is clipped at 4096 UTF-8 bytes; each line's clipped flag reports that independently of more."), HttpApiEndpoint3.get("getLogs", "/projects/:projectId/deployments/:deploymentId/logs", {
2134
+ params: deploymentParams,
2135
+ query: logQuery,
2136
+ success: RunnerLogPage,
2137
+ error: ReadErrors
2138
+ }).annotate(OpenApi.Description, "Reads only the authorized deployment's customer runner stdout/stderr, with the same bounds and cursor protocol as environment logs. A deployment from another project or organization is not visible. Runner identifiers come only from the deployment's stored runner records, never from the cursor or caller input."), HttpApiEndpoint3.get("list", "/projects/:projectId/deployments", {
2139
+ params: projectParams,
2140
+ query: {
2141
+ ...pageQuery,
2142
+ environment: Schema20.optional(EnvironmentName),
2143
+ status: Schema20.optional(DeploymentStatus)
2144
+ },
2145
+ success: Page(DeploymentSummary),
2146
+ error: ReadErrors
2147
+ }), HttpApiEndpoint3.post("create", "/projects/:projectId/deployments", {
2148
+ params: projectParams,
2149
+ payload: CreateDeployment,
2150
+ success: DeploymentDetail,
2151
+ error: WriteErrors
2152
+ }), HttpApiEndpoint3.post("uploadSource", "/projects/:projectId/sources", {
2153
+ params: projectParams,
2154
+ payload: Schema20.Uint8Array.pipe(HttpApiSchema.asUint8Array({ contentType: "application/gzip" })),
2155
+ success: SourceArchive,
2156
+ error: [...WriteErrors, PayloadTooLarge]
2157
+ }).annotate(OpenApi.Description, "Stores a gzip-compressed tar of a build context for the project and answers its digest, the SHA-256 of the bytes sent. Sending the same bytes again answers the same digest. A deployment created with `source` naming that digest is built from it by the control plane's builder. Access and the builder are checked before the body is read: a caller without write access answers 403 and a control plane without a builder 501 `NotImplemented`. A body over 64 MiB answers 413 `PayloadTooLarge`, before any byte is read when its `content-length` says so, and as soon as it passes the limit otherwise."), HttpApiEndpoint3.get("get", "/projects/:projectId/deployments/:deploymentId", {
2158
+ params: deploymentParams,
2159
+ success: DeploymentDetail,
2160
+ error: ReadErrors
2161
+ }), HttpApiEndpoint3.post("recordBuild", "/projects/:projectId/deployments/:deploymentId/build", {
2162
+ params: deploymentParams,
2163
+ payload: RecordBuild,
2164
+ success: DeploymentDetail,
2165
+ error: WriteErrors
2166
+ }), HttpApiEndpoint3.post("failBuild", "/projects/:projectId/deployments/:deploymentId/build-failure", {
2167
+ params: deploymentParams,
2168
+ payload: FailBuild,
2169
+ success: DeploymentDetail,
2170
+ error: WriteErrors
2171
+ }), HttpApiEndpoint3.get("getBuildLog", "/projects/:projectId/deployments/:deploymentId/build-log", {
2172
+ params: deploymentParams,
2173
+ query: { after: Schema20.optional(NonNegativeInt) },
2174
+ success: BuildLog,
2175
+ error: ReadErrors
2176
+ }), HttpApiEndpoint3.post("rollback", "/projects/:projectId/deployments/:deploymentId/rollback", {
2177
+ params: deploymentParams,
2178
+ success: DeploymentDetail,
2179
+ error: WriteErrors
2180
+ }).annotate(OpenApi.Description, "Rolls the environment back to the earlier deployment named by `deploymentId`. That deployment must have reached `live` before (status `live`, `drained` or `rolled-back`) and must not be the one live now; otherwise the answer is 409. The build is not repeated: a new deployment in the same environment redeploys that deployment's image and environment-variable snapshot, its `rolledBackFrom` is `deploymentId`, and its build and migrate steps are `skipped`. The new deployment starts `in-progress` and becomes `live` or `failed`. When it becomes `live`, the deployment that was live ends `rolled-back`; if it fails, that deployment stays `live`. The target keeps its own status. A second rollout in the environment while one is `in-progress` answers 409. The response is the new deployment."), HttpApiEndpoint3.post("redeploy", "/projects/:projectId/deployments/:deploymentId/redeploy", {
2181
+ params: deploymentParams,
2182
+ success: DeploymentDetail,
2183
+ error: WriteErrors
2184
+ })) {
2185
+ }
2186
+
2187
+ // ../../packages/cloud-api/src/groups/projects.ts
2188
+ import { Schema as Schema21 } from "effect";
2189
+ import { HttpApiEndpoint as HttpApiEndpoint4, HttpApiGroup as HttpApiGroup4 } from "effect/http-api";
2190
+ var projectParams2 = { projectId: ProjectId };
2191
+ var environmentParams = { ...projectParams2, environment: EnvironmentName };
2192
+
2193
+ class ProjectsGroup extends HttpApiGroup4.make("projects").add(HttpApiEndpoint4.get("list", "/organizations/:organizationId/projects", {
2194
+ params: { organizationId: OrganizationId },
2195
+ success: Schema21.Array(Project),
2196
+ error: ReadErrors
2197
+ }), HttpApiEndpoint4.post("create", "/organizations/:organizationId/projects", {
2198
+ params: { organizationId: OrganizationId },
2199
+ payload: CreateProject,
2200
+ success: Project,
2201
+ error: WriteErrors
2202
+ }), HttpApiEndpoint4.get("get", "/projects/:projectId", {
2203
+ params: projectParams2,
2204
+ success: Project,
2205
+ error: ReadErrors
2206
+ }), HttpApiEndpoint4.patch("update", "/projects/:projectId", {
2207
+ params: projectParams2,
2208
+ payload: UpdateProject,
2209
+ success: Project,
2210
+ error: WriteErrors
2211
+ }), HttpApiEndpoint4.delete("delete", "/projects/:projectId", {
2212
+ params: projectParams2,
2213
+ error: WriteErrors
2214
+ }), HttpApiEndpoint4.get("listEnvironments", "/projects/:projectId/environments", {
2215
+ params: projectParams2,
2216
+ success: Schema21.Array(Environment),
2217
+ error: ReadErrors
2218
+ }), HttpApiEndpoint4.post("createEnvironment", "/projects/:projectId/environments", {
2219
+ params: projectParams2,
2220
+ payload: CreateEnvironment,
2221
+ success: Environment,
2222
+ error: WriteErrors
2223
+ }), HttpApiEndpoint4.get("getEnvironment", "/projects/:projectId/environments/:environment", {
2224
+ params: environmentParams,
2225
+ success: Environment,
2226
+ error: ReadErrors
2227
+ }), HttpApiEndpoint4.delete("deleteEnvironment", "/projects/:projectId/environments/:environment", {
2228
+ params: environmentParams,
2229
+ error: WriteErrors
2230
+ }), HttpApiEndpoint4.get("getEndpoints", "/projects/:projectId/environments/:environment/endpoints", {
2231
+ params: environmentParams,
2232
+ success: ProjectEndpoints,
2233
+ error: ReadErrors
2234
+ })) {
2235
+ }
2236
+
2237
+ class EnvironmentVariablesGroup extends HttpApiGroup4.make("environmentVariables").add(HttpApiEndpoint4.get("list", "/projects/:projectId/environments/:environment/variables", {
2238
+ params: environmentParams,
2239
+ success: Schema21.Array(EnvVariable),
2240
+ error: ReadErrors
2241
+ }), HttpApiEndpoint4.put("set", "/projects/:projectId/environments/:environment/variables/:name", {
2242
+ params: { ...environmentParams, name: EnvVariableName },
2243
+ payload: SetEnvVariable,
2244
+ success: EnvVariable,
2245
+ error: WriteErrors
2246
+ }), HttpApiEndpoint4.delete("delete", "/projects/:projectId/environments/:environment/variables/:name", {
2247
+ params: { ...environmentParams, name: EnvVariableName },
2248
+ error: WriteErrors
2249
+ }), HttpApiEndpoint4.post("import", "/projects/:projectId/environments/:environment/variables/import", {
2250
+ params: environmentParams,
2251
+ payload: ImportEnvVariables,
2252
+ success: ImportEnvVariablesResult,
2253
+ error: WriteErrors
2254
+ })) {
2255
+ }
2256
+
2257
+ class DomainsGroup extends HttpApiGroup4.make("domains").add(HttpApiEndpoint4.get("list", "/projects/:projectId/domains", {
2258
+ params: projectParams2,
2259
+ success: Schema21.Array(Domain),
2260
+ error: ReadErrors
2261
+ }), HttpApiEndpoint4.post("add", "/projects/:projectId/domains", {
2262
+ params: projectParams2,
2263
+ payload: AddDomain,
2264
+ success: Domain,
2265
+ error: WriteErrors
2266
+ }), HttpApiEndpoint4.post("verify", "/projects/:projectId/domains/:domainId/verify", {
2267
+ params: { ...projectParams2, domainId: DomainId },
2268
+ success: Domain,
2269
+ error: WriteErrors
2270
+ }), HttpApiEndpoint4.delete("remove", "/projects/:projectId/domains/:domainId", {
2271
+ params: { ...projectParams2, domainId: DomainId },
2272
+ error: WriteErrors
2273
+ })) {
2274
+ }
2275
+
2276
+ class RegionsGroup extends HttpApiGroup4.make("regions").add(HttpApiEndpoint4.get("catalog", "/regions", {
2277
+ success: Schema21.Array(Region),
2278
+ error: ReadErrors
2279
+ }), HttpApiEndpoint4.get("list", "/projects/:projectId/environments/:environment/regions", {
2280
+ params: environmentParams,
2281
+ success: Schema21.Array(ProjectRegion),
2282
+ error: ReadErrors
2283
+ }), HttpApiEndpoint4.post("add", "/projects/:projectId/regions", {
2284
+ params: projectParams2,
2285
+ payload: AddRegion,
2286
+ success: Region,
2287
+ error: WriteErrors
2288
+ }), HttpApiEndpoint4.delete("remove", "/projects/:projectId/regions/:region", {
2289
+ params: { ...projectParams2, region: RegionId },
2290
+ error: WriteErrors
2291
+ }), HttpApiEndpoint4.put("setHome", "/projects/:projectId/home-region", {
2292
+ params: projectParams2,
2293
+ payload: SetHomeRegion,
2294
+ success: Project,
2295
+ error: WriteErrors
2296
+ })) {
2297
+ }
2298
+
2299
+ class IntegrationsGroup extends HttpApiGroup4.make("integrations").add(HttpApiEndpoint4.get("list", "/projects/:projectId/integrations", {
2300
+ params: projectParams2,
2301
+ success: Schema21.Array(Integration),
2302
+ error: ReadErrors
2303
+ }), HttpApiEndpoint4.post("connect", "/projects/:projectId/integrations/:kind", {
2304
+ params: { ...projectParams2, kind: IntegrationKind },
2305
+ payload: ConnectIntegration,
2306
+ success: IntegrationConnection,
2307
+ error: WriteErrors
2308
+ }), HttpApiEndpoint4.delete("disconnect", "/projects/:projectId/integrations/:kind", {
2309
+ params: { ...projectParams2, kind: IntegrationKind },
2310
+ error: WriteErrors
2311
+ })) {
2312
+ }
2313
+
2314
+ // ../../packages/cloud-api/src/groups/runtime.ts
2315
+ import { Schema as Schema22 } from "effect";
2316
+ import { HttpApiEndpoint as HttpApiEndpoint5, HttpApiGroup as HttpApiGroup5, HttpApiSchema as HttpApiSchema2, OpenApi as OpenApi2 } from "effect/http-api";
2317
+ var environmentParams2 = { projectId: ProjectId, environment: EnvironmentName };
2318
+ var actorTypeParams = { ...environmentParams2, actorType: Schema22.String };
2319
+ var actorParams = { ...actorTypeParams, key: Schema22.String };
2320
+ var InspectErrors = [...ReadErrors, RunnerDefect];
2321
+
2322
+ class RuntimeGroup extends HttpApiGroup5.make("runtime").add(HttpApiEndpoint5.get("getOverview", "/projects/:projectId/environments/:environment/runtime/overview", {
2323
+ params: environmentParams2,
2324
+ success: Overview,
2325
+ error: InspectErrors
2326
+ }), HttpApiEndpoint5.get("getSidebarCounts", "/projects/:projectId/environments/:environment/runtime/sidebar-counts", { params: environmentParams2, success: SidebarCounts, error: InspectErrors }), HttpApiEndpoint5.get("search", "/projects/:projectId/environments/:environment/runtime/search", {
2327
+ params: environmentParams2,
2328
+ query: { q: Schema22.String.pipe(Schema22.check(Schema22.isMinLength(1), Schema22.isMaxLength(256))) },
2329
+ success: Schema22.Array(SearchResult),
2330
+ error: InspectErrors
2331
+ }), HttpApiEndpoint5.get("listActorTypes", "/projects/:projectId/environments/:environment/runtime/actor-types", {
2332
+ params: environmentParams2,
2333
+ success: Schema22.Array(ActorTypeSummary),
2334
+ error: InspectErrors
2335
+ }), HttpApiEndpoint5.get("getActorType", "/projects/:projectId/environments/:environment/runtime/actor-types/:actorType", { params: actorTypeParams, success: ActorTypeSummary, error: InspectErrors }), HttpApiEndpoint5.get("getActorTypeActivity", "/projects/:projectId/environments/:environment/runtime/actor-types/:actorType/activity", {
2336
+ params: actorTypeParams,
2337
+ query: { window: Schema22.optional(SeriesWindow) },
2338
+ success: ActorTypeActivity,
2339
+ error: InspectErrors
2340
+ }).annotate(OpenApi2.Description, "Commands per second over the window (default 24h) and the volume of each command, for one actor type, as the serving runner counted them since `since`. `NotImplemented` while more than one runner serves the environment."), HttpApiEndpoint5.get("getActorTypeLatency", "/projects/:projectId/environments/:environment/runtime/actor-types/:actorType/latency", {
2341
+ params: actorTypeParams,
2342
+ query: { window: Schema22.optional(SeriesWindow) },
2343
+ success: TurnLatency,
2344
+ error: InspectErrors
2345
+ }).annotate(OpenApi2.Description, "Turn-latency histogram over the window (default 24h) with p50, p95 and p99, for one actor type, as the serving runner measured them since `since`. `NotImplemented` while more than one runner serves the environment."), HttpApiEndpoint5.get("listActorInstances", "/projects/:projectId/environments/:environment/runtime/actor-types/:actorType/instances", {
2346
+ params: actorTypeParams,
2347
+ query: { ...pageQuery, status: Schema22.optional(Schema22.Literals(["awake", "idle"])) },
2348
+ success: Page(ActorInstance),
2349
+ error: InspectErrors
2350
+ }), HttpApiEndpoint5.get("inspectActor", "/projects/:projectId/environments/:environment/runtime/actors/:actorType/:key", { params: actorParams, success: ActorInspector, error: InspectErrors }), HttpApiEndpoint5.get("listActorTables", "/projects/:projectId/environments/:environment/runtime/actors/:actorType/:key/tables", { params: actorParams, success: Schema22.Array(OwnedTableRows), error: ReadErrors }), HttpApiEndpoint5.get("listActorReceipts", "/projects/:projectId/environments/:environment/runtime/actors/:actorType/:key/receipts", { params: actorParams, query: pageQuery, success: Page(Receipt), error: InspectErrors }), HttpApiEndpoint5.get("listActorEvents", "/projects/:projectId/environments/:environment/runtime/actors/:actorType/:key/events", { params: actorParams, success: Schema22.Array(ActorEvent), error: InspectErrors }), HttpApiEndpoint5.get("listActorJobs", "/projects/:projectId/environments/:environment/runtime/actors/:actorType/:key/jobs", { params: actorParams, success: Schema22.Array(ActorJob), error: InspectErrors }), HttpApiEndpoint5.get("listActorTimeline", "/projects/:projectId/environments/:environment/runtime/actors/:actorType/:key/timeline", {
2351
+ params: actorParams,
2352
+ query: pageQuery,
2353
+ success: Page(ActorTimelineEntry),
2354
+ error: InspectErrors
2355
+ }), HttpApiEndpoint5.get("listCommands", "/projects/:projectId/environments/:environment/runtime/commands", {
2356
+ params: environmentParams2,
2357
+ query: {
2358
+ ...pageQuery,
2359
+ actorType: Schema22.optional(Schema22.String),
2360
+ outcome: Schema22.optional(CommandOutcome)
2361
+ },
2362
+ success: Page(CommandLogEntry),
2363
+ error: InspectErrors
2364
+ }), HttpApiEndpoint5.post("sendCommand", "/projects/:projectId/environments/:environment/runtime/commands", {
2365
+ params: environmentParams2,
2366
+ payload: SendCommand,
2367
+ success: CommandSent,
2368
+ error: [
2369
+ ...WriteErrors,
2370
+ CommandFailed2,
2371
+ CommandRefused,
2372
+ CommandExpired,
2373
+ RunnerDefect,
2374
+ ...QuotaErrors
2375
+ ]
2376
+ }).annotate(OpenApi2.Description, "Sends to an actor address, including one not yet created, without requiring an inspector read. Requires project write permission. `commandId` is an optional client idempotency key, not a runner command id. Its scope is organization/project/environment/actor address/command, independent of deployment. The control plane durably assigns a runner-minted id and stores only a canonical payload hash; the same key and JSON input reuse it and replay the receipt with `replayed: true` within the runner's retry window. Different input returns 409 Conflict. After expiry, the key is retained as a tombstone for 30 days and returns 410 `CommandExpired`; reusing it after that starts a new command. Declared actor errors are 422 CommandFailed; admission refusals are typed 4xx, including 422 CommandRefused. Mailbox backpressure remains 503 Unavailable. Remote defects are opaque, non-retryable 502 RunnerDefect errors. The edge's usage refusals keep the framework's tags and payloads: a full Free command quota is a 429 `QuotaExceeded`, a passed spend limit a 402 `SpendLimitExceeded`, a full connection allowance a 429 `ConnectionLimitExceeded` and a Free tenant at its storage cap a 429 `StorageQuotaExceeded`. A command the edge cannot bill, because the tenant has no organization, the organization no billing account, or its plan is not in the pricing configuration, is a 402 `QuotaUnbound` carrying that reason, never a generic 503."), HttpApiEndpoint5.get("streamCommands", "/projects/:projectId/environments/:environment/runtime/commands/stream", {
2377
+ params: environmentParams2,
2378
+ query: {
2379
+ actorType: Schema22.optional(Schema22.String),
2380
+ outcome: Schema22.optional(CommandOutcome)
2381
+ },
2382
+ success: HttpApiSchema2.StreamSse({
2383
+ data: Schema22.toCodecJson(CommandLogEntry),
2384
+ error: CommandStreamGap
2385
+ }),
2386
+ error: InspectErrors
2387
+ }).annotate(OpenApi2.Description, "Each command the serving runner commits from now on, filtered by actor type and outcome (`replayed` is always empty, since a replay commits no turn), with a payload preview of at most 256 characters that the runner cut and redacted. The control plane resumes across the runner's credential expiry; the stream fails with `CommandStreamGap` when the runner can no longer resume it without missing commands, and ends when the runner goes away; a client reconnects. `NotImplemented` while more than one runner serves the environment."), HttpApiEndpoint5.get("getJobs", "/projects/:projectId/environments/:environment/runtime/jobs", {
2388
+ params: environmentParams2,
2389
+ success: JobsSummary,
2390
+ error: InspectErrors
2391
+ }), HttpApiEndpoint5.get("listDeadLetters", "/projects/:projectId/environments/:environment/runtime/dead-letters", {
2392
+ params: environmentParams2,
2393
+ query: pageQuery,
2394
+ success: Page(DeadLetter),
2395
+ error: InspectErrors
2396
+ }), HttpApiEndpoint5.post("retryDeadLetter", "/projects/:projectId/environments/:environment/runtime/dead-letters/:deadLetterId/retry", {
2397
+ params: { ...environmentParams2, deadLetterId: DeadLetterId },
2398
+ error: WriteErrors
2399
+ }), HttpApiEndpoint5.post("discardDeadLetter", "/projects/:projectId/environments/:environment/runtime/dead-letters/:deadLetterId/discard", {
2400
+ params: { ...environmentParams2, deadLetterId: DeadLetterId },
2401
+ error: WriteErrors
2402
+ }), HttpApiEndpoint5.get("listWorkflows", "/projects/:projectId/environments/:environment/runtime/workflows", {
2403
+ params: environmentParams2,
2404
+ query: {
2405
+ ...pageQuery,
2406
+ status: Schema22.optional(Schema22.Literals(["running", "waiting", "completed", "failed"]))
2407
+ },
2408
+ success: Page(Workflow),
2409
+ error: InspectErrors
2410
+ }), HttpApiEndpoint5.get("getTimers", "/projects/:projectId/environments/:environment/runtime/timers", {
2411
+ params: environmentParams2,
2412
+ success: TimersSummary,
2413
+ error: InspectErrors
2414
+ }), HttpApiEndpoint5.get("listSchedules", "/projects/:projectId/environments/:environment/runtime/schedules", {
2415
+ params: environmentParams2,
2416
+ success: Schema22.Array(Schedule),
2417
+ error: InspectErrors
2418
+ }), HttpApiEndpoint5.get("getConnections", "/projects/:projectId/environments/:environment/runtime/connections", { params: environmentParams2, success: ConnectionsSummary, error: InspectErrors })) {
2419
+ }
2420
+
2421
+ // ../../packages/cloud-api/src/contract.ts
2422
+ class CloudApi extends HttpApi.make("akter-cloud").add(AccountGroup, OrganizationsGroup, MembersGroup, InvitationsGroup, ApiKeysGroup, ProjectsGroup, EnvironmentVariablesGroup, DomainsGroup, RegionsGroup, IntegrationsGroup, DeploymentsGroup, RuntimeGroup, BillingGroup, UsageGroup, AuditGroup).middleware(Authentication).prefix("/api").annotateMerge(OpenApi3.annotations({ title: "Akter Cloud API" })) {
2423
+ }
2424
+ // src/commands/cloud/deploy.ts
2425
+ import { Clock as Clock3, Config as Config3, Console as Console8, Duration as Duration2, Effect as Effect16, Option as Option8, Schema as Schema26, Stream } from "effect";
2426
+ import { Command as Command13, Flag as Flag12 } from "effect/cli";
2427
+ import { ChildProcess, ChildProcessSpawner } from "effect/process";
2428
+
2429
+ // src/commands/cloud/archive.ts
2430
+ import { Effect as Effect13, FileSystem as FileSystem3, Option as Option6, Schema as Schema23 } from "effect";
2431
+ import { gzipSync } from "node:zlib";
2432
+
2433
+ class ContextInvalid extends Schema23.TaggedError()("ContextInvalid", {
2434
+ message: Schema23.String
2435
+ }) {
2436
+ }
2437
+ var compile = (pattern) => {
2438
+ let source = "^";
2439
+ for (let index = 0;index < pattern.length; index++) {
2440
+ const char = pattern[index];
2441
+ if (char === "*" && pattern[index + 1] === "*") {
2442
+ index++;
2443
+ if (pattern[index + 1] === "/")
2444
+ index++;
2445
+ source += index + 1 >= pattern.length ? ".*" : "(.*/)?";
2446
+ } else if (char === "*")
2447
+ source += "[^/]*";
2448
+ else if (char === "?")
2449
+ source += "[^/]";
2450
+ else if (char === "\\" && index + 1 < pattern.length) {
2451
+ index++;
2452
+ source += `\\${pattern[index]}`;
2453
+ } else if (".+()|{}^$".includes(char))
2454
+ source += `\\${char}`;
2455
+ else
2456
+ source += char;
2457
+ }
2458
+ return new RegExp(`${source}$`, "u");
2459
+ };
2460
+ var ignoreRules = (text) => text.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#")).flatMap((line) => {
2461
+ const exception = line.startsWith("!");
2462
+ const body = exception ? line.slice(1) : line;
2463
+ const directory = body.endsWith("/");
2464
+ const trimmed = body.replace(/\/+$/u, "");
2465
+ const pattern = trimmed.replace(/^\//u, "");
2466
+ if (pattern === "")
2467
+ return [];
2468
+ return [
2469
+ {
2470
+ exception,
2471
+ directory,
2472
+ pattern: compile(trimmed.includes("/") ? pattern : `**/${pattern}`)
2473
+ }
2474
+ ];
2475
+ });
2476
+ var isIgnored = (rules) => (path, directory) => {
2477
+ const segments = path.split("/");
2478
+ return segments.some((_, index) => {
2479
+ const prefix = segments.slice(0, index + 1).join("/");
2480
+ const isDirectory = index < segments.length - 1 || directory;
2481
+ return rules.reduce((ignored, rule) => (!rule.directory || isDirectory) && rule.pattern.test(prefix) ? !rule.exception : ignored, false);
2482
+ });
2483
+ };
2484
+ var BLOCK = 512;
2485
+ var encoder = new TextEncoder;
2486
+ var octal = (value, width) => `${value.toString(8).padStart(width - 1, "0")}\x00`;
2487
+ var headers = (entry) => {
2488
+ const fits = (value) => encoder.encode(value).byteLength <= 100 && /^[\x20-\x7e]*$/u.test(value);
2489
+ const extended = entry.type !== "x" && (!fits(entry.path) || !fits(entry.target)) ? pax([
2490
+ ["path", entry.path],
2491
+ ["linkpath", entry.target]
2492
+ ].filter(([, value]) => value !== "")) : [];
2493
+ const header = new Uint8Array(BLOCK);
2494
+ const put = (offset, value) => header.set(encoder.encode(value).subarray(0, value.length), offset);
2495
+ put(0, extended.length > 0 ? entry.path.slice(0, 99).replace(/[^\x20-\x7e]/gu, "_") : entry.path);
2496
+ put(100, octal(entry.mode, 8));
2497
+ put(108, octal(0, 8));
2498
+ put(116, octal(0, 8));
2499
+ put(124, octal(entry.size, 12));
2500
+ put(136, octal(0, 12));
2501
+ put(148, " ");
2502
+ put(156, entry.type);
2503
+ put(157, extended.length > 0 ? "" : entry.target);
2504
+ put(257, "ustar\x00");
2505
+ put(263, "00");
2506
+ put(148, `${header.reduce((sum, byte) => sum + byte, 0).toString(8).padStart(6, "0")}\x00 `);
2507
+ return [...extended, header];
2508
+ };
2509
+ var pax = (records) => {
2510
+ const body = encoder.encode(records.map(([key, value]) => {
2511
+ const line = ` ${key}=${value}
2512
+ `;
2513
+ const size = encoder.encode(line).byteLength;
2514
+ let digits = 1;
2515
+ while (String(size + digits).length !== digits)
2516
+ digits += 1;
2517
+ return `${size + digits}${line}`;
2518
+ }).join(""));
2519
+ return [
2520
+ ...headers({
2521
+ path: "././@PaxHeader",
2522
+ mode: 420,
2523
+ size: body.byteLength,
2524
+ type: "x",
2525
+ target: ""
2526
+ }),
2527
+ padded(body)
2528
+ ];
2529
+ };
2530
+ var padded = (bytes) => {
2531
+ const block = new Uint8Array(Math.ceil(bytes.byteLength / BLOCK) * BLOCK);
2532
+ block.set(bytes);
2533
+ return block;
2534
+ };
2535
+ var tar = (entries) => {
2536
+ const blocks = entries.flatMap((entry) => ("target" in entry) ? headers({ path: entry.path, mode: 511, size: 0, type: "2", target: entry.target }) : [
2537
+ ...headers({
2538
+ path: entry.path,
2539
+ mode: entry.mode,
2540
+ size: entry.bytes.byteLength,
2541
+ type: "0",
2542
+ target: ""
2543
+ }),
2544
+ padded(entry.bytes)
2545
+ ]);
2546
+ const bytes = new Uint8Array(blocks.reduce((size, block) => size + block.byteLength, 0) + 2 * BLOCK);
2547
+ let offset = 0;
2548
+ for (const block of blocks) {
2549
+ bytes.set(block, offset);
2550
+ offset += block.byteLength;
2551
+ }
2552
+ return gzipSync(bytes);
2553
+ };
2554
+ var GIT_DIRECTORY = ignoreRules(".git");
2555
+ var packContext = (input) => Effect13.gen(function* () {
2556
+ const fs = yield* FileSystem3.FileSystem;
2557
+ const root = input.context.replace(/[\\/]+$/u, "");
2558
+ let rules = [];
2559
+ for (const candidate of [".akterignore", ".gitignore"])
2560
+ if (yield* fs.exists(`${root}/${candidate}`)) {
2561
+ rules = ignoreRules(yield* fs.readFileString(`${root}/${candidate}`));
2562
+ break;
2563
+ }
2564
+ const ignored = isIgnored([...rules, ...GIT_DIRECTORY]);
2565
+ const entries = [];
2566
+ const walk = (directory) => Effect13.gen(function* () {
2567
+ const names = (yield* fs.readDirectory(directory === "" ? root : `${root}/${directory}`)).toSorted();
2568
+ for (const name of names) {
2569
+ const path = directory === "" ? name : `${directory}/${name}`;
2570
+ const link = yield* Effect13.option(fs.readLink(`${root}/${path}`));
2571
+ if (Option6.isSome(link)) {
2572
+ if (!ignored(path, false))
2573
+ entries.push({ path, target: link.value });
2574
+ continue;
2575
+ }
2576
+ const info = yield* fs.stat(`${root}/${path}`);
2577
+ if (info.type === "Directory") {
2578
+ if (!ignored(path, true))
2579
+ yield* walk(path);
2580
+ } else if (info.type === "File" && !ignored(path, false))
2581
+ entries.push({
2582
+ path,
2583
+ mode: info.mode & 511,
2584
+ bytes: yield* fs.readFile(`${root}/${path}`)
2585
+ });
2586
+ }
2587
+ }).pipe(Effect13.catchTag("PlatformError", (error) => Effect13.fail(ContextInvalid.make({ message: `Cannot read ${root}/${directory}: ${error.message}` }))));
2588
+ yield* walk("");
2589
+ return { archive: tar(entries), files: entries.map((entry) => entry.path) };
2590
+ });
2591
+
2592
+ // src/commands/cloud/client.ts
2593
+ import { Effect as Effect15, Match, Schema as Schema25 } from "effect";
2594
+ import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "effect/http";
2595
+ import { HttpApiClient } from "effect/http-api";
2596
+
2597
+ // src/commands/cloud/credentials.ts
2598
+ import { homedir } from "node:os";
2599
+ import { Config as Config2, Crypto, Effect as Effect14, FileSystem as FileSystem4, Option as Option7, Schema as Schema24 } from "effect";
2600
+ var isLoopback = (hostname) => hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/u.test(hostname);
2601
+ var controlPlaneUrl = (input) => {
2602
+ const url = URL.parse(input);
2603
+ if (url === null || url.username !== "" || url.password !== "" || url.search !== "")
2604
+ return Option7.none();
2605
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback(url.hostname)))
2606
+ return Option7.none();
2607
+ return Option7.some(`${url.origin}${url.pathname}`.replace(/\/+$/u, ""));
2608
+ };
2609
+ var Credentials = Schema24.Struct({
2610
+ apiUrl: Schema24.String.check(Schema24.makeFilter((url) => Option7.isSome(controlPlaneUrl(url)) || "An https URL, or http on a loopback host")),
2611
+ token: Schema24.String,
2612
+ email: Schema24.String
2613
+ });
2614
+
2615
+ class NotLoggedIn extends Schema24.TaggedError()("NotLoggedIn", {
2616
+ path: Schema24.String
2617
+ }) {
2618
+ }
2619
+
2620
+ class CredentialsExposed extends Schema24.TaggedError()("CredentialsExposed", { path: Schema24.String, mode: Schema24.Int }) {
2621
+ }
2622
+
2623
+ class CredentialsUnreadable extends Schema24.TaggedError()("CredentialsUnreadable", { path: Schema24.String }) {
2624
+ }
2625
+ var configDirectory = (location) => {
2626
+ if (location.override !== undefined && location.override !== "")
2627
+ return location.override;
2628
+ if (location.platform === "darwin")
2629
+ return `${location.home}/Library/Application Support/akter`;
2630
+ if (location.platform === "win32")
2631
+ return `${location.appData ?? `${location.home}\\AppData\\Roaming`}\\akter`;
2632
+ return `${location.xdgConfigHome === undefined || location.xdgConfigHome === "" ? `${location.home}/.config` : location.xdgConfigHome}/akter`;
2633
+ };
2634
+ var optional = (name) => Config2.option(Config2.String(name)).pipe(Effect14.map(Option7.getOrUndefined));
2635
+ var credentialsPath = Effect14.gen(function* () {
2636
+ const platform = process.platform;
2637
+ const directory = configDirectory({
2638
+ platform,
2639
+ home: homedir(),
2640
+ override: yield* optional("AKTER_CONFIG_DIR"),
2641
+ xdgConfigHome: yield* optional("XDG_CONFIG_HOME"),
2642
+ appData: yield* optional("APPDATA")
2643
+ });
2644
+ return { directory, file: `${directory}${platform === "win32" ? "\\" : "/"}credentials.json` };
2645
+ });
2646
+ var encode = Schema24.encodeEffect(Schema24.fromJsonString(Credentials));
2647
+ var decode = Schema24.decodeUnknownEffect(Schema24.fromJsonString(Credentials));
2648
+ var saveCredentials = (credentials) => Effect14.gen(function* () {
2649
+ const fs = yield* FileSystem4.FileSystem;
2650
+ const { directory, file } = yield* credentialsPath;
2651
+ const suffix = yield* (yield* Crypto.Crypto).randomUUIDv4.pipe(Effect14.orDie);
2652
+ const pending = `${file}.${suffix}.tmp`;
2653
+ yield* fs.makeDirectory(directory, { recursive: true, mode: 448 });
2654
+ yield* fs.chmod(directory, 448);
2655
+ yield* Effect14.gen(function* () {
2656
+ yield* fs.writeFileString(pending, yield* encode(credentials).pipe(Effect14.orDie), {
2657
+ flag: "wx",
2658
+ mode: 384
2659
+ });
2660
+ yield* fs.chmod(pending, 384);
2661
+ yield* fs.rename(pending, file);
2662
+ }).pipe(Effect14.onError(() => fs.remove(pending, { force: true }).pipe(Effect14.ignore)));
2663
+ return file;
2664
+ });
2665
+ var readCredentials = Effect14.gen(function* () {
2666
+ const fs = yield* FileSystem4.FileSystem;
2667
+ const { file } = yield* credentialsPath;
2668
+ if (!(yield* fs.exists(file)))
2669
+ return yield* NotLoggedIn.make({ path: file });
2670
+ const mode = (yield* fs.stat(file)).mode & 511;
2671
+ const credentials = yield* fs.readFileString(file).pipe(Effect14.flatMap(decode), Effect14.mapError(() => CredentialsUnreadable.make({ path: file })));
2672
+ return { credentials, file, mode };
2673
+ });
2674
+ var loadCredentials = Effect14.gen(function* () {
2675
+ const { credentials, file, mode } = yield* readCredentials;
2676
+ if (process.platform !== "win32" && (mode & 63) !== 0)
2677
+ return yield* CredentialsExposed.make({ path: file, mode });
2678
+ return credentials;
2679
+ });
2680
+ var loadCredentialsToRevoke = Effect14.map(readCredentials, (read) => read.credentials);
2681
+ var removeCredentials = Effect14.gen(function* () {
2682
+ const fs = yield* FileSystem4.FileSystem;
2683
+ const { file } = yield* credentialsPath;
2684
+ if (!(yield* fs.exists(file)))
2685
+ return false;
2686
+ yield* fs.remove(file);
2687
+ return true;
2688
+ });
2689
+
2690
+ // src/commands/cloud/client.ts
2691
+ var DEFAULT_API_URL = "https://api.akter.dev";
2692
+ var cloudClient = (credentials) => HttpApiClient.make(CloudApi, {
2693
+ baseUrl: credentials.apiUrl,
2694
+ transformClient: HttpClient2.mapRequest(HttpClientRequest2.bearerToken(credentials.token))
2695
+ });
2696
+ var signedIn = Effect15.gen(function* () {
2697
+ const credentials = yield* loadCredentials;
2698
+ return { credentials, client: yield* cloudClient(credentials) };
2699
+ });
2700
+ var octal2 = (mode) => mode.toString(8).padStart(3, "0");
2701
+ var describe = Match.type().pipe(Match.tagsExhaustive({
2702
+ NotLoggedIn: () => ({
2703
+ reason: "NotLoggedIn",
2704
+ message: "Not logged in. Run `akter login` first.",
2705
+ exitCode: 2
2706
+ }),
2707
+ CredentialsExposed: (error) => ({
2708
+ reason: "CredentialsExposed",
2709
+ message: `${error.path} can be read by other users (mode ${octal2(error.mode)}). Run \`chmod 600\` on it or \`akter login\` again.`,
2710
+ exitCode: 2
2711
+ }),
2712
+ CredentialsUnreadable: (error) => ({
2713
+ reason: "CredentialsUnreadable",
2714
+ message: `${error.path} does not hold akter credentials. Run \`akter login\` again.`,
2715
+ exitCode: 2
2716
+ }),
2717
+ Unauthorized: () => ({
2718
+ reason: "Unauthorized",
2719
+ message: "Your session has expired or was revoked. Run `akter login` to sign in again.",
2720
+ exitCode: 1
2721
+ }),
2722
+ Forbidden: (error) => ({
2723
+ reason: "Forbidden",
2724
+ message: `Refused: ${error.message}`,
2725
+ exitCode: 1
2726
+ }),
2727
+ NotFound: (error) => ({
2728
+ reason: "NotFound",
2729
+ message: `No ${error.resource} ${error.id} is visible to you.`,
2730
+ exitCode: 1
2731
+ }),
2732
+ Conflict: (error) => ({
2733
+ reason: "Conflict",
2734
+ message: `Refused: ${error.message}`,
2735
+ exitCode: 1
2736
+ }),
2737
+ NotImplemented: (error) => ({
2738
+ reason: "NotImplemented",
2739
+ message: `This control plane does not support ${error.operation}.`,
2740
+ exitCode: 1
2741
+ }),
2742
+ PayloadTooLarge: (error) => ({
2743
+ reason: "PayloadTooLarge",
2744
+ message: `The control plane accepts at most ${error.limitBytes} bytes; exclude more files in .dockerignore.`,
2745
+ exitCode: 1
2746
+ }),
2747
+ Unavailable: () => ({
2748
+ reason: "Unavailable",
2749
+ message: "The control plane is temporarily unavailable. Try again shortly.",
2750
+ exitCode: 1
2751
+ }),
2752
+ HttpClientError: (error) => ({
2753
+ reason: "Unreachable",
2754
+ message: `Cannot reach the control plane: ${error.message}`,
2755
+ exitCode: 2
2756
+ }),
2757
+ SchemaError: (error) => ({
2758
+ reason: "SchemaError",
2759
+ message: `The control plane's answer was not understood: ${error.message}`,
2760
+ exitCode: 2
2761
+ }),
2762
+ PlatformError: (error) => ({
2763
+ reason: "PlatformError",
2764
+ message: `Cannot use the file system: ${error.message}`,
2765
+ exitCode: 2
2766
+ }),
2767
+ ConfigError: (error) => ({
2768
+ reason: "ConfigError",
2769
+ message: `Cannot read the environment: ${error.message}`,
2770
+ exitCode: 2
2771
+ })
2772
+ }));
2773
+ var reportFailures = (effect) => Effect15.catchIf(effect, (error) => !Schema25.is(CommandFailed)(error), (error) => fail(describe(error)));
2774
+
2775
+ // src/commands/cloud/deploy.ts
2776
+ class DeploymentFailed extends Schema26.TaggedError()("DeploymentFailed", {
2777
+ deploymentId: Schema26.String,
2778
+ step: Schema26.String,
2779
+ detail: Schema26.String
2780
+ }) {
2781
+ }
2782
+
2783
+ class DeploymentTimedOut extends Schema26.TaggedError()("DeploymentTimedOut", { deploymentId: Schema26.String, seconds: Schema26.Int }) {
2784
+ }
2785
+ var flags9 = {
2786
+ project: Flag12.String("project").pipe(Flag12.withFallbackConfig(Config3.String("AKTER_PROJECT")), Flag12.withSchema(ProjectId), Flag12.withDescription("The project to deploy to (default AKTER_PROJECT)")),
2787
+ environment: Flag12.Literals("env", EnvironmentName.literals).pipe(Flag12.withDefault("production"), Flag12.withDescription("The environment to deploy to (default production)")),
2788
+ context: Flag12.Directory("context", { mustExist: true }).pipe(Flag12.withDefault("."), Flag12.withDescription(`The app directory to upload, holding ${SOURCE_ENTRY} (default the current directory)`)),
2789
+ commit: Flag12.String("commit").pipe(Flag12.optional, Flag12.withDescription("The commit SHA the deployment is labeled with (default the context's git HEAD)")),
2790
+ message: Flag12.String("message").pipe(Flag12.optional, Flag12.withDescription("The deployment's message (default the commit's subject)")),
2791
+ timeout: Flag12.Int("timeout").pipe(Flag12.withDefault(900), Flag12.withDescription("Seconds to follow the rollout before giving up on it (default 900)"))
2792
+ };
2793
+ var git = (directory, args) => Effect16.gen(function* () {
2794
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
2795
+ const handle = yield* spawner.spawn(ChildProcess.make("git", ["-C", directory, ...args]));
2796
+ const [out] = yield* Effect16.all([handle.stdout.pipe(Stream.decodeText, Stream.mkString), handle.stderr.pipe(Stream.runDrain)], { concurrency: 2 });
2797
+ return Number(yield* handle.exitCode) === 0 ? out.trim() : undefined;
2798
+ }).pipe(Effect16.scoped, Effect16.orElseSucceed(() => {
2799
+ return;
2800
+ }));
2801
+ var label = Effect16.fnUntraced(function* (input) {
2802
+ const head = yield* git(input.context, ["rev-parse", "HEAD"]);
2803
+ const commitSha = input.commit ?? head ?? input.digest.slice("sha256:".length, 47);
2804
+ if (input.message !== undefined)
2805
+ return { commitSha, message: input.message };
2806
+ if (input.commit !== undefined || head === undefined)
2807
+ return { commitSha, message: "" };
2808
+ const subject = (yield* git(input.context, ["log", "-1", "--format=%s"])) ?? "";
2809
+ const dirty = ((yield* git(input.context, ["status", "--porcelain"])) ?? "") !== "";
2810
+ return { commitSha, message: dirty ? `${subject} (with uncommitted changes)` : subject };
2811
+ });
2812
+ var seconds = (durationMs) => durationMs === null ? "" : ` in ${(durationMs / 1000).toFixed(1)}s`;
2813
+ var follow = Effect16.fnUntraced(function* (input) {
2814
+ const params = { projectId: input.projectId, deploymentId: input.deployment.id };
2815
+ const deadline = (yield* Clock3.currentTimeMillis) + input.timeoutSeconds * 1000;
2816
+ const printed = new Map;
2817
+ let detail = input.deployment;
2818
+ let cursor = 0;
2819
+ let following = false;
2820
+ let logComplete = false;
2821
+ const printLog = Effect16.gen(function* () {
2822
+ const log = yield* input.client.deployments.getBuildLog({ params, query: { after: cursor } });
2823
+ const last = log.lines.at(-1);
2824
+ for (const line of log.lines)
2825
+ yield* Console8.log(` ${line.text}`);
2826
+ if (last !== undefined)
2827
+ cursor = last.index + 1;
2828
+ following = true;
2829
+ logComplete = log.complete;
2830
+ });
2831
+ while (true) {
2832
+ for (const step of detail.steps) {
2833
+ if (step.status === "pending" || printed.get(step.name) === step.status)
2834
+ continue;
2835
+ if (step.name === "build" && step.status === "succeeded" && following && !logComplete)
2836
+ yield* printLog;
2837
+ printed.set(step.name, step.status);
2838
+ yield* Console8.log(` ${step.name} ${step.status}${step.status === "running" ? "" : seconds(step.durationMs)}`);
2839
+ }
2840
+ if (!logComplete && detail.steps.some((step) => step.name === "build" && step.status === "running"))
2841
+ yield* printLog;
2842
+ if (detail.status === "live")
2843
+ return detail;
2844
+ if (detail.status === "failed") {
2845
+ const failed = detail.steps.find((step) => step.status === "failed");
2846
+ if (failed?.name === "build") {
2847
+ const log = yield* input.client.deployments.getBuildLog({ params, query: {} });
2848
+ for (const line of log.lines.slice(-20))
2849
+ yield* Console8.error(` ${line.text}`);
2850
+ }
2851
+ return yield* DeploymentFailed.make({
2852
+ deploymentId: detail.id,
2853
+ step: failed?.name ?? "rollout",
2854
+ detail: failed?.detail ?? "no reason was recorded"
2855
+ });
2856
+ }
2857
+ if ((yield* Clock3.currentTimeMillis) >= deadline)
2858
+ return yield* DeploymentTimedOut.make({
2859
+ deploymentId: detail.id,
2860
+ seconds: input.timeoutSeconds
2861
+ });
2862
+ yield* Effect16.sleep(Duration2.seconds(1));
2863
+ detail = yield* input.client.deployments.get({ params });
2864
+ }
2865
+ });
2866
+ var deployCommand = Command13.make("deploy", flags9, (options) => Effect16.gen(function* () {
2867
+ const { client, credentials } = yield* signedIn;
2868
+ const packed = yield* packContext({ context: options.context });
2869
+ if (!packed.files.includes(SOURCE_ENTRY))
2870
+ return yield* ContextInvalid.make({
2871
+ message: `No ${SOURCE_ENTRY} in ${options.context}, or its .akterignore or .gitignore leaves it out; the platform serves the App that file exports by default`
2872
+ });
2873
+ if (packed.archive.byteLength > MAX_SOURCE_BYTES)
2874
+ return yield* fail({
2875
+ reason: "ContextTooLarge",
2876
+ message: `The app directory is ${packed.archive.byteLength} bytes compressed; the control plane accepts at most ${MAX_SOURCE_BYTES}. Exclude more files in .akterignore.`
2877
+ });
2878
+ yield* Console8.log(`Uploading ${packed.files.length} files (${packed.archive.byteLength} bytes) from ${options.context} to ${credentials.apiUrl}`);
2879
+ const source = yield* client.deployments.uploadSource({
2880
+ params: { projectId: options.project },
2881
+ payload: packed.archive
2882
+ });
2883
+ const { commitSha, message } = yield* label({
2884
+ context: options.context,
2885
+ digest: source.digest,
2886
+ commit: Option8.getOrUndefined(options.commit),
2887
+ message: Option8.getOrUndefined(options.message)
2888
+ });
2889
+ const deployment = yield* client.deployments.create({
2890
+ params: { projectId: options.project },
2891
+ payload: {
2892
+ environment: options.environment,
2893
+ commitSha,
2894
+ message,
2895
+ source: { digest: source.digest }
2896
+ }
2897
+ });
2898
+ yield* Console8.log(`Deployment ${deployment.id} of ${commitSha.slice(0, 7)} to ${options.environment} started`);
2899
+ const live = yield* follow({
2900
+ client,
2901
+ projectId: options.project,
2902
+ deployment,
2903
+ timeoutSeconds: options.timeout
2904
+ });
2905
+ yield* Console8.log(`Deployment ${live.id} is live in ${options.environment}`);
2906
+ }).pipe(Effect16.catchTags({
2907
+ ContextInvalid: (error) => fail({ reason: "ContextInvalid", message: error.message }),
2908
+ DeploymentFailed: (error) => fail({
2909
+ reason: "DeploymentFailed",
2910
+ message: `Deployment ${error.deploymentId} failed at ${error.step}: ${error.detail}. The previous deployment, if any, is still serving.`,
2911
+ exitCode: 1
2912
+ }),
2913
+ DeploymentTimedOut: (error) => fail({
2914
+ reason: "DeploymentTimedOut",
2915
+ message: `Deployment ${error.deploymentId} was still rolling out after ${error.seconds}s; it continues on the control plane.`,
2916
+ exitCode: 1
2917
+ })
2918
+ }), reportFailures)).pipe(Command13.withDescription("Upload the app directory, build and roll it out on Akter Cloud, and follow it until it is live"));
2919
+
2920
+ // src/commands/cloud/login.ts
2921
+ import { Clock as Clock4, Config as Config4, Console as Console9, Data, Duration as Duration3, Effect as Effect17, Schema as Schema27 } from "effect";
2922
+ import { Command as Command14, Flag as Flag13 } from "effect/cli";
2923
+ import { HttpClient as HttpClient3, HttpClientRequest as HttpClientRequest3, HttpClientResponse } from "effect/http";
2924
+ var CLIENT_ID = "akter-cli";
2925
+ var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
2926
+ var DeviceCode = Schema27.Struct({
2927
+ device_code: Schema27.String,
2928
+ user_code: Schema27.String,
2929
+ verification_uri: Schema27.String,
2930
+ expires_in: Schema27.Finite,
2931
+ interval: Schema27.Finite
2932
+ });
2933
+ var DeviceToken = Schema27.Struct({ access_token: Schema27.String });
2934
+ var DeviceError = Schema27.Struct({ error: Schema27.String });
2935
+ var displayCode = (code) => code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;
2936
+
2937
+ class LoginDenied extends Schema27.TaggedError()("LoginDenied", {}) {
2938
+ }
2939
+
2940
+ class LoginExpired extends Schema27.TaggedError()("LoginExpired", {}) {
2941
+ }
2942
+
2943
+ class LoginRefused extends Schema27.TaggedError()("LoginRefused", {
2944
+ status: Schema27.Int,
2945
+ error: Schema27.String
2946
+ }) {
2947
+ }
2948
+ var Poll = Data.taggedEnum();
2949
+ var flags10 = {
2950
+ apiUrl: Flag13.String("api-url").pipe(Flag13.withFallbackConfig(Config4.String("AKTER_API_URL")), Flag13.withDefault(DEFAULT_API_URL), Flag13.filterMap(controlPlaneUrl, () => "an https URL, or http on a loopback host"), Flag13.withDescription(`The control plane to sign in to (default AKTER_API_URL, then ${DEFAULT_API_URL})`))
2951
+ };
2952
+ var post = (url, body) => Effect17.flatMap(HttpClient3.HttpClient, (client) => client.execute(HttpClientRequest3.bodyJsonUnsafe(HttpClientRequest3.post(url), body)));
2953
+ var refusal = (response) => HttpClientResponse.schemaBodyJson(DeviceError)(response).pipe(Effect17.orElseSucceed(() => ({ error: `HTTP ${response.status}` })));
2954
+ var deviceLogin = (apiUrl) => Effect17.gen(function* () {
2955
+ const started = yield* post(`${apiUrl}/auth/device/code`, { client_id: CLIENT_ID });
2956
+ if (started.status !== 200) {
2957
+ const { error } = yield* refusal(started);
2958
+ return yield* LoginRefused.make({ status: started.status, error });
2959
+ }
2960
+ const code = yield* HttpClientResponse.schemaBodyJson(DeviceCode)(started);
2961
+ yield* Console9.log(`To sign in, open ${code.verification_uri}
2962
+ and enter the code ${displayCode(code.user_code)}. Waiting for approval…`);
2963
+ const poll = Effect17.gen(function* () {
2964
+ const response = yield* post(`${apiUrl}/auth/device/token`, {
2965
+ grant_type: DEVICE_GRANT,
2966
+ device_code: code.device_code,
2967
+ client_id: CLIENT_ID
2968
+ });
2969
+ if (response.status === 200)
2970
+ return Poll.Granted({
2971
+ token: (yield* HttpClientResponse.schemaBodyJson(DeviceToken)(response)).access_token
2972
+ });
2973
+ const { error } = yield* refusal(response);
2974
+ if (error === "authorization_pending")
2975
+ return Poll.Pending();
2976
+ if (error === "slow_down")
2977
+ return Poll.SlowDown();
2978
+ if (error === "access_denied")
2979
+ return yield* LoginDenied.make({});
2980
+ if (error === "expired_token")
2981
+ return yield* LoginExpired.make({});
2982
+ return yield* LoginRefused.make({ status: response.status, error });
2983
+ });
2984
+ const deadline = (yield* Clock4.currentTimeMillis) + code.expires_in * 1000;
2985
+ let interval = Duration3.seconds(code.interval);
2986
+ while ((yield* Clock4.currentTimeMillis) < deadline) {
2987
+ yield* Effect17.sleep(interval);
2988
+ const answer = yield* poll;
2989
+ if (Poll.$is("Granted")(answer))
2990
+ return answer.token;
2991
+ if (Poll.$is("SlowDown")(answer))
2992
+ interval = Duration3.sum(interval, Duration3.seconds(5));
2993
+ }
2994
+ return yield* LoginExpired.make({});
2995
+ });
2996
+ var revoke = (apiUrl, token) => HttpClient3.HttpClient.pipe(Effect17.flatMap((client) => client.execute(HttpClientRequest3.post(`${apiUrl}/auth/sign-out`).pipe(HttpClientRequest3.bearerToken(token), HttpClientRequest3.bodyJsonUnsafe({})))), Effect17.ignore);
2997
+ var loginCommand = Command14.make("login", flags10, ({ apiUrl }) => Effect17.gen(function* () {
2998
+ const token = yield* deviceLogin(apiUrl);
2999
+ const client = yield* cloudClient({ apiUrl, token, email: "" });
3000
+ const me = yield* client.account.me().pipe(Effect17.tapError(() => revoke(apiUrl, token)));
3001
+ const email = me.user?.email ?? "";
3002
+ const file = yield* saveCredentials({ apiUrl, token, email });
3003
+ yield* Console9.log(`Logged in to ${apiUrl} as ${email}. Credentials saved to ${file}.`);
3004
+ }).pipe(Effect17.catchTags({
3005
+ LoginDenied: () => fail({
3006
+ reason: "LoginDenied",
3007
+ message: "The sign-in was denied in the browser. Nothing was saved.",
3008
+ exitCode: 1
3009
+ }),
3010
+ LoginExpired: () => fail({
3011
+ reason: "LoginExpired",
3012
+ message: "The sign-in code expired before it was approved. Run `akter login` again.",
3013
+ exitCode: 1
3014
+ }),
3015
+ LoginRefused: (error) => fail({
3016
+ reason: "LoginRefused",
3017
+ message: `The control plane refused the sign-in: ${error.error} (${error.status})`,
3018
+ exitCode: 1
3019
+ })
3020
+ }), reportFailures)).pipe(Command14.withDescription("Sign in to Akter Cloud through the browser and store the session for deploy"));
3021
+
3022
+ // src/commands/cloud/logout.ts
3023
+ import { Console as Console10, Effect as Effect18, Option as Option9 } from "effect";
3024
+ import { Command as Command15 } from "effect/cli";
3025
+ import { HttpClient as HttpClient4, HttpClientRequest as HttpClientRequest4 } from "effect/http";
3026
+ var logout = Effect18.gen(function* () {
3027
+ const credentials = yield* loadCredentialsToRevoke.pipe(Effect18.asSome, Effect18.catchTag("CredentialsUnreadable", () => Effect18.succeedNone), Effect18.map(Option9.getOrUndefined));
3028
+ const revoked = credentials === undefined ? false : yield* HttpClient4.HttpClient.pipe(Effect18.flatMap((client) => client.execute(HttpClientRequest4.post(`${credentials.apiUrl}/auth/sign-out`).pipe(HttpClientRequest4.bearerToken(credentials.token), HttpClientRequest4.bodyJsonUnsafe({})))), Effect18.map((response) => response.status === 200), Effect18.orElseSucceed(() => false));
3029
+ yield* removeCredentials;
3030
+ return { credentials, revoked };
3031
+ });
3032
+ var logoutCommand = Command15.make("logout", {}, () => Effect18.gen(function* () {
3033
+ const { credentials, revoked } = yield* logout;
3034
+ if (credentials === undefined)
3035
+ return yield* Console10.log("Removed the stored credentials.");
3036
+ yield* Console10.log(revoked ? `Logged out of ${credentials.apiUrl}.` : `Removed the stored credentials. ${credentials.apiUrl} could not revoke the session, which ends when it expires.`);
3037
+ }).pipe(Effect18.catchTag("NotLoggedIn", () => Console10.log("Not logged in.")), reportFailures)).pipe(Command15.withDescription("Sign out of Akter Cloud and delete the stored session"));
3038
+
3039
+ // src/commands/cloud/whoami.ts
3040
+ import { Console as Console11, Effect as Effect19 } from "effect";
3041
+ import { Command as Command16 } from "effect/cli";
3042
+ var whoamiCommand = Command16.make("whoami", {}, () => Effect19.gen(function* () {
3043
+ const { credentials, client } = yield* signedIn;
3044
+ const me = yield* client.account.me();
3045
+ yield* Console11.log([
3046
+ `${me.user?.email ?? credentials.email} at ${credentials.apiUrl}`,
3047
+ ...me.organizations.map((membership) => ` ${membership.organization.slug} ${membership.role} ${membership.organization.id}`)
3048
+ ].join(`
3049
+ `));
3050
+ }).pipe(reportFailures)).pipe(Command16.withDescription("Show who the stored Akter Cloud session signs in as"));
3051
+
3052
+ // src/commands/cloud/env.ts
3053
+ import { Config as Config5, Console as Console12, DateTime as DateTime2, Effect as Effect20, FileSystem as FileSystem5, Option as Option10, Stdio, Stream as Stream2 } from "effect";
3054
+ import { Argument as Argument6, Command as Command17, Flag as Flag14 } from "effect/cli";
3055
+ var scope = {
3056
+ project: Flag14.String("project").pipe(Flag14.withFallbackConfig(Config5.String("AKTER_PROJECT")), Flag14.withSchema(ProjectId)),
3057
+ environment: Flag14.Literals("env", EnvironmentName.literals).pipe(Flag14.withDefault("production"))
3058
+ };
3059
+ var name = Argument6.String("name").pipe(Argument6.withSchema(EnvVariableName));
3060
+ var input = (path, limit) => Effect20.gen(function* () {
3061
+ const tooLarge = () => fail({
3062
+ reason: "EnvironmentInputTooLarge",
3063
+ message: `Environment input exceeds ${limit} bytes.`,
3064
+ exitCode: 2
3065
+ });
3066
+ if (path !== undefined && path !== "-") {
3067
+ const fs = yield* FileSystem5.FileSystem;
3068
+ const stat = yield* fs.stat(path);
3069
+ if (Number(stat.size) > limit)
3070
+ return yield* tooLarge();
3071
+ const value = yield* fs.readFileString(path);
3072
+ if (new TextEncoder().encode(value).byteLength > limit)
3073
+ return yield* tooLarge();
3074
+ return value;
3075
+ }
3076
+ const stdio = yield* Stdio.Stdio;
3077
+ if (yield* stdio.stdinIsTerminal)
3078
+ return yield* fail({
3079
+ reason: "EnvironmentInputRequired",
3080
+ message: "Pipe the value on stdin or supply --file. Values are never accepted as command arguments.",
3081
+ exitCode: 2
3082
+ });
3083
+ const collected = yield* stdio.stdin.pipe(Stream2.runFoldEffect(() => ({ size: 0, chunks: new Array }), (state, chunk) => {
3084
+ if (state.size + chunk.length > limit)
3085
+ return tooLarge();
3086
+ state.chunks.push(chunk);
3087
+ state.size += chunk.length;
3088
+ return Effect20.succeed(state);
3089
+ }));
3090
+ const bytes = new Uint8Array(collected.size);
3091
+ let offset = 0;
3092
+ for (const chunk of collected.chunks) {
3093
+ bytes.set(chunk, offset);
3094
+ offset += chunk.length;
3095
+ }
3096
+ return new TextDecoder().decode(bytes);
3097
+ });
3098
+ var list2 = Command17.make("list", scope, ({ project, environment }) => Effect20.gen(function* () {
3099
+ const { client } = yield* signedIn;
3100
+ const variables = yield* client.environmentVariables.list({
3101
+ params: { projectId: project, environment }
3102
+ });
3103
+ for (const variable of variables)
3104
+ yield* Console12.log(`${variable.name} ${DateTime2.formatIso(variable.updatedAt)}${variable.usedBy.includes("platform") ? "\tmanaged" : ""}`);
3105
+ }).pipe(reportFailures)).pipe(Command17.withDescription("List variable names and update times; values cannot be read back"));
3106
+ var set = Command17.make("set", { ...scope, name, file: Flag14.File("file", { mustExist: true }).pipe(Flag14.optional) }, (options) => Effect20.gen(function* () {
3107
+ const { client } = yield* signedIn;
3108
+ const value = yield* input(Option10.getOrUndefined(options.file), 65536);
3109
+ yield* client.environmentVariables.set({
3110
+ params: {
3111
+ projectId: options.project,
3112
+ environment: options.environment,
3113
+ name: options.name
3114
+ },
3115
+ payload: { value }
3116
+ });
3117
+ yield* Console12.log(`Set ${options.name} in ${options.environment}. The value takes effect on the next deployment.`);
3118
+ }).pipe(reportFailures)).pipe(Command17.withDescription("Set a write-only value from piped stdin or --file"));
3119
+ var unset = Command17.make("unset", { ...scope, name }, ({ project, environment, name }) => Effect20.gen(function* () {
3120
+ const { client } = yield* signedIn;
3121
+ yield* client.environmentVariables.delete({ params: { projectId: project, environment, name } });
3122
+ yield* Console12.log(`Unset ${name} in ${environment}. The change takes effect on the next deployment.`);
3123
+ }).pipe(reportFailures)).pipe(Command17.withDescription("Remove a customer-managed variable"));
3124
+ var importVariables = Command17.make("import", { ...scope, file: Argument6.String("file").pipe(Argument6.withDefault("-")) }, ({ project, environment, file }) => Effect20.gen(function* () {
3125
+ const { client } = yield* signedIn;
3126
+ const content = yield* input(file, 1048576);
3127
+ const result = yield* client.environmentVariables.import({
3128
+ params: { projectId: project, environment },
3129
+ payload: { content }
3130
+ });
3131
+ yield* Console12.log(`Imported ${result.created.length} new and ${result.updated.length} updated variables in ${environment}.`);
3132
+ }).pipe(reportFailures)).pipe(Command17.withDescription("Import a dotenv file atomically; use - or no file for stdin"));
3133
+ var envCommand = Command17.make("env").pipe(Command17.withDescription("Manage encrypted, write-only Akter Cloud environment variables"), Command17.withSubcommands([list2, set, unset, importVariables]));
3134
+
3135
+ // src/commands/cloud/logs.ts
3136
+ import { Config as Config6, Console as Console13, DateTime as DateTime3, Effect as Effect21, Option as Option11, Schema as Schema28 } from "effect";
3137
+ import { Command as Command18, Flag as Flag15 } from "effect/cli";
3138
+ var colourSequence = new RegExp(`${String.fromCodePoint(27)}\\[[0-?]*[ -/]*[@-~]`, "gu");
3139
+ var logsCommand = Command18.make("logs", {
3140
+ project: Flag15.String("project").pipe(Flag15.withFallbackConfig(Config6.String("AKTER_PROJECT")), Flag15.withSchema(ProjectId), Flag15.withDescription("The project (default AKTER_PROJECT)")),
3141
+ environment: Flag15.Literals("env", EnvironmentName.literals).pipe(Flag15.withDefault("production"), Flag15.withDescription("The environment (default production)")),
3142
+ deployment: Flag15.String("deployment").pipe(Flag15.withSchema(DeploymentId), Flag15.optional, Flag15.withDescription("Read a deployment instead of the environment's current deployment")),
3143
+ since: Flag15.Int("since").pipe(Flag15.withSchema(Schema28.Int.check(Schema28.isBetween({ minimum: 1, maximum: MAX_LOG_WINDOW_SECONDS }))), Flag15.withDefault(300), Flag15.withDescription("Seconds of recent output, 1–3600 (default 300)")),
3144
+ limit: Flag15.Int("limit").pipe(Flag15.withSchema(LogLimit), Flag15.withDefault(100), Flag15.withDescription("Maximum lines per response, 1–200 (default 100)")),
3145
+ follow: Flag15.Boolean("follow").pipe(Flag15.withDefault(false), Flag15.withDescription("Resume long polls until interrupted"))
3146
+ }, (options) => Effect21.gen(function* () {
3147
+ const { client } = yield* signedIn;
3148
+ const since = DateTime3.subtract(yield* DateTime3.now, { seconds: options.since });
3149
+ let cursor;
3150
+ let more = false;
3151
+ let failures = 0;
3152
+ const retryRead = (error) => Effect21.gen(function* () {
3153
+ if (!options.follow || failures >= 5)
3154
+ return yield* error;
3155
+ failures += 1;
3156
+ yield* Console13.error("Log read unavailable; reconnecting from the last cursor.");
3157
+ yield* Effect21.sleep(`${Math.min(2 ** (failures - 1), 8)} seconds`);
3158
+ return;
3159
+ });
3160
+ while (true) {
3161
+ const query = {
3162
+ since,
3163
+ limit: options.limit,
3164
+ cursor,
3165
+ wait: options.follow && cursor !== undefined && !more ? MAX_LOG_WAIT_SECONDS : 0
3166
+ };
3167
+ const request = Option11.isSome(options.deployment) ? client.deployments.getLogs({
3168
+ params: { projectId: options.project, deploymentId: options.deployment.value },
3169
+ query
3170
+ }) : client.deployments.getEnvironmentLogs({
3171
+ params: { projectId: options.project, environment: options.environment },
3172
+ query
3173
+ });
3174
+ const page = yield* request.pipe(Effect21.catchTags({ HttpClientError: retryRead, Unavailable: retryRead }));
3175
+ if (page === undefined)
3176
+ continue;
3177
+ failures = 0;
3178
+ for (const line of page.lines)
3179
+ yield* Console13.log(`${DateTime3.formatIso(line.at)} ${line.runnerId} ${line.stream} ${line.text.replace(colourSequence, "").replace(/\p{Cc}|[\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/gu, "�")}${line.clipped ? " …" : ""}`);
3180
+ cursor = page.cursor;
3181
+ more = page.more;
3182
+ if (!options.follow && !more)
3183
+ return;
3184
+ if (page.lines.length === 0 && !more)
3185
+ yield* Effect21.sleep("1 second");
3186
+ }
3187
+ }).pipe(reportFailures)).pipe(Command18.withDescription("Read recent customer runner logs, or follow with resumable long polls"));
3188
+
3189
+ // src/cli.ts
3190
+ var group = (name, description, subcommands) => Command19.make(name).pipe(Command19.withDescription(description), Command19.withSubcommands(subcommands));
3191
+ var akter = Command19.make("akter").pipe(Command19.withDescription("Run actors locally, check a deploy against stored data, adopt existing tables, inspect and repair a running deployment, and deploy to Akter Cloud"), Command19.withSubcommands([
3192
+ {
3193
+ group: "Develop and check",
3194
+ commands: [
3195
+ devCommand,
3196
+ group("workflows", "Check workflow changes against open executions", [
3197
+ checkCommand
3198
+ ]),
3199
+ group("payloads", "Check and clear stored event and job payload versions", [
3200
+ checkCommand2,
3201
+ clearCommand
3202
+ ]),
3203
+ group("adopt", "Adopt existing tables: plan, observe legacy writers, backfill, enforce, and check status", [
3204
+ planCommand,
3205
+ observeCommand,
3206
+ backfillCommand,
3207
+ enforceCommand,
3208
+ statusCommand,
3209
+ releaseCommand
3210
+ ]),
3211
+ group("fleet", "Set up fleet views' change feed and rebuild a view from its source", [
3212
+ setupCommand,
3213
+ rebuildCommand
3214
+ ])
3215
+ ]
3216
+ },
3217
+ {
3218
+ group: "Operate a running deployment",
3219
+ commands: [
3220
+ group("defects", "Read recent defects from runners", [listCommand]),
3221
+ inspectCommand,
3222
+ exportCommand,
3223
+ group("receipts", "Read stored command outcomes", [showCommand]),
3224
+ group("dead-letters", "Repair dead-lettered jobs; the runner audits each repair", [
3225
+ retryCommand,
3226
+ discardCommand
3227
+ ]),
3228
+ group("subscriptions", "List and skip stuck subscription rows", [
3229
+ listCommand2,
3230
+ skipCommand
3231
+ ])
3232
+ ]
3233
+ },
3234
+ {
3235
+ group: "Akter Cloud",
3236
+ commands: [
3237
+ loginCommand,
3238
+ logoutCommand,
3239
+ whoamiCommand,
3240
+ deployCommand,
3241
+ envCommand,
3242
+ logsCommand
3243
+ ]
3244
+ }
3245
+ ]));
3246
+ var isShowHelp = Schema29.is(CliError.ShowHelp);
3247
+ var run2 = (args) => Command19.runWith(akter, { version })(args).pipe(Effect22.catchIf((error) => CliError.isCliError(error) && Runtime2.getErrorExitCode(error) !== 0, (error) => Effect22.fail(CommandFailed.make({
3248
+ exitCode: 2,
3249
+ reason: isShowHelp(error) ? error.errors[0]?._tag ?? "ShowHelp" : error._tag
3250
+ }))));
3251
+
3252
+ // src/main.ts
3253
+ Effect23.gen(function* () {
3254
+ const services = yield* Layer5.build(Layer5.mergeAll(PlatformServices.layer, FetchHttpClient.layer));
3255
+ const args = yield* Context2.get(services, Stdio2.Stdio).args;
3256
+ return yield* run2(args).pipe(Effect23.provideContext(services));
3257
+ }).pipe(Effect23.scoped, PlatformRuntime.runMain);