@streamsy/serve 0.4.0 → 0.5.0
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/action/contract.d.ts +1 -3
- package/dist/action/contract.js +3 -3
- package/dist/action/errors.d.ts +3 -6
- package/dist/action/errors.js +1 -2
- package/dist/action/outbox.d.ts +0 -2
- package/dist/action/outbox.js +7 -8
- package/dist/action/runtime.d.ts +2 -4
- package/dist/action/runtime.js +6 -7
- package/dist/action/sql.d.ts +3 -5
- package/dist/action/sql.js +6 -7
- package/dist/alchemy.d.ts +4 -7
- package/dist/alchemy.js +3 -4
- package/dist/bun.d.ts +1 -3
- package/dist/bun.js +1 -2
- package/dist/cloudflare.d.ts +2 -6
- package/dist/cloudflare.js +3 -4
- package/dist/{contract-BUWJ-Sy2.d.ts → contract-CFoMXKuH.d.ts} +0 -2
- package/dist/contract.d.ts +1 -1
- package/dist/contract.js +16 -9
- package/dist/deno.d.ts +1 -3
- package/dist/deno.js +1 -2
- package/dist/index.d.ts +3 -7
- package/dist/index.js +10 -12
- package/dist/node.d.ts +1 -3
- package/dist/node.js +1 -2
- package/dist/{placement-BOM3E08P.d.ts → placement-Cj3sd02S.d.ts} +1 -5
- package/dist/{rolldown-runtime-D7D4PA-g.js → rolldown-runtime-D8jQpDG7.js} +1 -2
- package/dist/{router-DviIc1O7.js → router-C2p1T25K.js} +5 -9
- package/dist/{wakeup-BtnMEjLv.d.ts → wakeup--kIoqiIE.d.ts} +0 -4
- package/dist/{wakeup-CWKg8pts.js → wakeup-CcJE8k3i.js} +6 -8
- package/package.json +3 -3
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/action/contract.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* The action-sink declaration.
|
|
4
3
|
*
|
|
@@ -96,5 +95,4 @@ export interface CheckedActionSink<Payload, From extends ActionSinkRelation = Ac
|
|
|
96
95
|
export declare function defineActionSink<Payload, From extends ActionSinkRelation>(spec: ActionSinkSpec<Payload, From>): CheckedActionSink<Payload, From>;
|
|
97
96
|
/** The next backoff, in milliseconds, after `attempts` failed attempts. */
|
|
98
97
|
export declare function backoffAfter(policy: ActionSinkDeliveryPolicy, attempts: number): number;
|
|
99
|
-
export type PayloadOf<Sink> = Sink extends CheckedActionSink<infer Payload, infer _From> ? Payload : never;
|
|
100
|
-
//#endregion
|
|
98
|
+
export type PayloadOf<Sink> = Sink extends CheckedActionSink<infer Payload, infer _From> ? Payload : never;
|
package/dist/action/contract.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { contractFingerprint } from "../contract.js";
|
|
2
|
-
|
|
2
|
+
import { Array, Record } from "effect";
|
|
3
|
+
// src/action/contract.ts
|
|
3
4
|
function defineActionSink(spec) {
|
|
4
5
|
if (!Number.isInteger(spec.delivery.maxAttempts) || spec.delivery.maxAttempts < 1) throw new TypeError(`action sink ${spec.name} must declare at least one delivery attempt`);
|
|
5
6
|
if (spec.delivery.backoffFactor < 1) throw new TypeError(`action sink ${spec.name} must not shrink its backoff`);
|
|
@@ -9,7 +10,7 @@ function defineActionSink(spec) {
|
|
|
9
10
|
name: spec.name,
|
|
10
11
|
key,
|
|
11
12
|
handler: { ...spec.handler },
|
|
12
|
-
delivery:
|
|
13
|
+
delivery: Record.fromEntries(Array.map(Record.toEntries(spec.delivery), ([name, value]) => [name, Number.isFinite(value) ? value : String(value)]))
|
|
13
14
|
});
|
|
14
15
|
return Object.freeze({
|
|
15
16
|
...spec,
|
|
@@ -24,5 +25,4 @@ function backoffAfter(policy, attempts) {
|
|
|
24
25
|
const raw = policy.initialBackoffMs * policy.backoffFactor ** exponent;
|
|
25
26
|
return Math.min(policy.maxBackoffMs, Math.round(raw));
|
|
26
27
|
}
|
|
27
|
-
//#endregion
|
|
28
28
|
export { backoffAfter, defineActionSink };
|
package/dist/action/errors.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
2
|
-
import * as Cause$1 from "effect/Cause";
|
|
3
|
-
//#region src/action/errors.d.ts
|
|
1
|
+
import { Cause, Schema } from "effect";
|
|
4
2
|
export declare const ACTION_SINK_ERROR_TAGS: readonly ["ActionSinkDeliveryFailure", "OutboxUnavailable"];
|
|
5
3
|
export type ActionSinkErrorTag = (typeof ACTION_SINK_ERROR_TAGS)[number];
|
|
6
4
|
/** The durable outbox could not be read or written. Never a delivery outcome. */
|
|
@@ -8,7 +6,7 @@ type OutboxUnavailableSchema = Schema.TaggedStruct<"OutboxUnavailable", {
|
|
|
8
6
|
readonly operation: Schema.String;
|
|
9
7
|
readonly detail: Schema.String;
|
|
10
8
|
}>;
|
|
11
|
-
declare const OutboxUnavailableBase: Schema.Class<OutboxUnavailable, OutboxUnavailableSchema, Cause
|
|
9
|
+
declare const OutboxUnavailableBase: Schema.Class<OutboxUnavailable, OutboxUnavailableSchema, Cause.YieldableError>;
|
|
12
10
|
export declare class OutboxUnavailable extends OutboxUnavailableBase {}
|
|
13
11
|
/**
|
|
14
12
|
* A handler refused one delivery.
|
|
@@ -23,7 +21,7 @@ type ActionSinkDeliveryFailureSchema = Schema.TaggedStruct<"ActionSinkDeliveryFa
|
|
|
23
21
|
readonly detail: Schema.String;
|
|
24
22
|
readonly retryable: Schema.Boolean;
|
|
25
23
|
}>;
|
|
26
|
-
declare const ActionSinkDeliveryFailureBase: Schema.Class<ActionSinkDeliveryFailure, ActionSinkDeliveryFailureSchema, Cause
|
|
24
|
+
declare const ActionSinkDeliveryFailureBase: Schema.Class<ActionSinkDeliveryFailure, ActionSinkDeliveryFailureSchema, Cause.YieldableError>;
|
|
27
25
|
export declare class ActionSinkDeliveryFailure extends ActionSinkDeliveryFailureBase {
|
|
28
26
|
static retryable(handler: string, idempotencyKey: string, detail: string): ActionSinkDeliveryFailure;
|
|
29
27
|
static permanent(handler: string, idempotencyKey: string, detail: string): ActionSinkDeliveryFailure;
|
|
@@ -31,5 +29,4 @@ export declare class ActionSinkDeliveryFailure extends ActionSinkDeliveryFailure
|
|
|
31
29
|
/** Why one outbox entry stopped being retried. */
|
|
32
30
|
export declare const DEAD_LETTER_REASONS: readonly ["attempts-exhausted", "permanent", "payload-poison"];
|
|
33
31
|
export type DeadLetterReason = (typeof DEAD_LETTER_REASONS)[number];
|
|
34
|
-
//#endregion
|
|
35
32
|
export {};
|
package/dist/action/errors.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
|
-
|
|
2
|
+
// src/action/errors.ts
|
|
3
3
|
/**
|
|
4
4
|
* Typed action-sink failures.
|
|
5
5
|
*
|
|
@@ -47,5 +47,4 @@ const DEAD_LETTER_REASONS = [
|
|
|
47
47
|
"permanent",
|
|
48
48
|
"payload-poison"
|
|
49
49
|
];
|
|
50
|
-
//#endregion
|
|
51
50
|
export { ACTION_SINK_ERROR_TAGS, ActionSinkDeliveryFailure, DEAD_LETTER_REASONS, OutboxUnavailable };
|
package/dist/action/outbox.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { DeadLetterReason, OutboxUnavailable } from "./errors.js";
|
|
2
2
|
import { Context, Effect, Layer, Option } from "effect";
|
|
3
|
-
//#region src/action/outbox.d.ts
|
|
4
3
|
export type OutboxEntryState = "pending" | "delivered" | "dead";
|
|
5
4
|
export interface OutboxEntry {
|
|
6
5
|
/** Monotonic enqueue order. Delivery walks a lane in this order. */
|
|
@@ -63,5 +62,4 @@ export declare const outboxStoreLayer: (backing: OutboxBacking) => Layer.Layer<O
|
|
|
63
62
|
* host behaves the same way on the durable one.
|
|
64
63
|
*/
|
|
65
64
|
export declare function makeMemoryOutboxBacking(): OutboxBacking;
|
|
66
|
-
//#endregion
|
|
67
65
|
export {};
|
package/dist/action/outbox.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./errors.js";
|
|
2
|
-
import { Context, Effect, Layer, Option } from "effect";
|
|
3
|
-
|
|
2
|
+
import { Array, Context, Effect, Layer, Option, Order } from "effect";
|
|
3
|
+
// src/action/outbox.ts
|
|
4
4
|
/**
|
|
5
5
|
* The durable outbox.
|
|
6
6
|
*
|
|
@@ -35,7 +35,7 @@ function makeMemoryOutboxBacking() {
|
|
|
35
35
|
...patch
|
|
36
36
|
});
|
|
37
37
|
};
|
|
38
|
-
const ordered = () => [...entries.values()].
|
|
38
|
+
const ordered = () => Array.sort([...entries.values()], Order.mapInput(Order.Number, (entry) => entry.id));
|
|
39
39
|
return {
|
|
40
40
|
enqueue: (drafts) => Effect.sync(() => {
|
|
41
41
|
let enqueued = 0;
|
|
@@ -72,11 +72,11 @@ function makeMemoryOutboxBacking() {
|
|
|
72
72
|
};
|
|
73
73
|
}),
|
|
74
74
|
nextDue: (sink, partitionId, inOrder = false) => Effect.sync(() => {
|
|
75
|
-
const pending = ordered()
|
|
75
|
+
const pending = Array.filter(ordered(), (entry) => entry.sink === sink && entry.state === "pending" && (partitionId === void 0 || entry.partitionId === partitionId));
|
|
76
76
|
const first = pending[0];
|
|
77
|
-
return first === void 0 ? Option.none() : Option.some(inOrder ? first.nextAttemptAtMs :
|
|
77
|
+
return first === void 0 ? Option.none() : Option.some(inOrder ? first.nextAttemptAtMs : Array.reduce(pending, Infinity, (at, entry) => Math.min(at, entry.nextAttemptAtMs)));
|
|
78
78
|
}),
|
|
79
|
-
claimDue: (sink, partitionId, nowMs, limit, inOrder = false) => Effect.sync(() => ordered()
|
|
79
|
+
claimDue: (sink, partitionId, nowMs, limit, inOrder = false) => Effect.sync(() => Array.filter(ordered(), (entry) => entry.sink === sink && entry.state === "pending" && (inOrder || entry.nextAttemptAtMs <= nowMs) && (partitionId === void 0 || entry.partitionId === partitionId)).slice(0, limit)),
|
|
80
80
|
markDelivered: (id, attempts, atMs) => Effect.sync(() => {
|
|
81
81
|
replace(id, {
|
|
82
82
|
state: "delivered",
|
|
@@ -102,8 +102,7 @@ function makeMemoryOutboxBacking() {
|
|
|
102
102
|
settledAtMs: atMs
|
|
103
103
|
});
|
|
104
104
|
}),
|
|
105
|
-
list: (sink, partitionId) => Effect.sync(() => ordered()
|
|
105
|
+
list: (sink, partitionId) => Effect.sync(() => Array.filter(ordered(), (entry) => entry.sink === sink && (partitionId === void 0 || entry.partitionId === partitionId)))
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
//#endregion
|
|
109
108
|
export { OutboxStore, makeMemoryOutboxBacking, outboxStore, outboxStoreLayer };
|
package/dist/action/runtime.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { ActionSinkRelation, CheckedActionSink } from "./contract.js";
|
|
2
2
|
import { ActionSinkDeliveryFailure, OutboxUnavailable } from "./errors.js";
|
|
3
3
|
import { OutboxDraft, OutboxStore } from "./outbox.js";
|
|
4
|
-
import { t as Wakeup } from "../wakeup
|
|
4
|
+
import { t as Wakeup } from "../wakeup--kIoqiIE.js";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
|
-
//#region src/action/runtime.d.ts
|
|
7
6
|
/** One attempt at one delivery, as the handler sees it. */
|
|
8
7
|
export interface ActionSinkDelivery<Payload> {
|
|
9
8
|
readonly entryId: number;
|
|
@@ -76,5 +75,4 @@ export declare const drain: <Payload, From extends ActionSinkRelation, Requireme
|
|
|
76
75
|
*/
|
|
77
76
|
export declare const OutboxWakeup: {
|
|
78
77
|
make: <Payload, From extends ActionSinkRelation, R = never>(sink: CheckedActionSink<Payload, From>, handler: ActionSinkHandler<Payload, R>, options?: DrainOptions) => Wakeup<OutboxStore | R>;
|
|
79
|
-
};
|
|
80
|
-
//#endregion
|
|
78
|
+
};
|
package/dist/action/runtime.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { backoffAfter } from "./contract.js";
|
|
2
2
|
import { ActionSinkDeliveryFailure } from "./errors.js";
|
|
3
3
|
import { OutboxStore } from "./outbox.js";
|
|
4
|
-
import { n as Wakeup } from "../wakeup-
|
|
5
|
-
import { Cause, Clock, Effect, Exit, Semaphore } from "effect";
|
|
4
|
+
import { n as Wakeup } from "../wakeup-CcJE8k3i.js";
|
|
5
|
+
import { Array, Cause, Clock, Effect, Exit, Option, Semaphore } from "effect";
|
|
6
6
|
import { StorageFault } from "@streamsy/core";
|
|
7
|
-
|
|
7
|
+
// src/action/runtime.ts
|
|
8
8
|
/**
|
|
9
9
|
* The delivery runtime: one bounded, serialized pass over a sink's outbox.
|
|
10
10
|
*
|
|
@@ -41,7 +41,7 @@ function actionSinkHandler(sink, deliver) {
|
|
|
41
41
|
const DEFAULT_DRAIN_LIMIT = 100;
|
|
42
42
|
/** Lower declared payloads to outbox drafts. Pure, so a caller can write them transactionally. */
|
|
43
43
|
function draftsFor(sink, payloads, enqueuedAtMs) {
|
|
44
|
-
return
|
|
44
|
+
return Array.map(payloads, (payload) => ({
|
|
45
45
|
sink: sink.name,
|
|
46
46
|
partitionId: sink.partitionBy(payload),
|
|
47
47
|
idempotencyKey: sink.idempotencyKey(payload),
|
|
@@ -147,8 +147,8 @@ function decodePayload(sink, entry) {
|
|
|
147
147
|
}
|
|
148
148
|
/** Every non-interrupt cause becomes one retryable refusal; a thrown handler is still a handler. */
|
|
149
149
|
function failureOf(sink, entry, cause) {
|
|
150
|
-
const declared = cause.reasons
|
|
151
|
-
if (declared
|
|
150
|
+
const declared = Array.findFirst(cause.reasons, Cause.isFailReason);
|
|
151
|
+
if (Option.isSome(declared)) return declared.value.error;
|
|
152
152
|
return ActionSinkDeliveryFailure.retryable(sink.handler.name, entry.idempotencyKey, Cause.pretty(cause).slice(0, 500));
|
|
153
153
|
}
|
|
154
154
|
function deadLetterReason(sink, attempt, failure) {
|
|
@@ -178,5 +178,4 @@ function outboxWakeupName(sink, partitionId) {
|
|
|
178
178
|
const lane = partitionId === void 0 ? "" : `/${encodeURIComponent(partitionId)}`;
|
|
179
179
|
return `streamsy/outbox/${encodeURIComponent(sink)}${lane}`;
|
|
180
180
|
}
|
|
181
|
-
//#endregion
|
|
182
181
|
export { DEFAULT_DRAIN_LIMIT, OutboxWakeup, actionSinkHandler, draftsFor, drain };
|
package/dist/action/sql.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { OutboxUnavailable } from "./errors.js";
|
|
2
2
|
import { OutboxBacking } from "./outbox.js";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
|
-
import { SqlClient } from "effect/sql
|
|
5
|
-
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function createSqlOutboxBacking(sql: SqlClient): OutboxBacking;
|
|
8
|
-
//#endregion
|
|
4
|
+
import { SqlClient } from "effect/sql";
|
|
5
|
+
export declare function migrateOutbox(sql: SqlClient.SqlClient): Effect.Effect<void, OutboxUnavailable>;
|
|
6
|
+
export declare function createSqlOutboxBacking(sql: SqlClient.SqlClient): OutboxBacking;
|
package/dist/action/sql.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OutboxUnavailable } from "./errors.js";
|
|
2
|
-
import { Effect, Option, Predicate, Schema } from "effect";
|
|
3
|
-
|
|
2
|
+
import { Array, Effect, Option, Predicate, Schema } from "effect";
|
|
3
|
+
// src/action/sql.ts
|
|
4
4
|
const TABLE = "streamsy_effect_outbox";
|
|
5
5
|
/** MySQL key columns. Their sum fits the 3072-byte InnoDB index limit at 4 bytes a character. */
|
|
6
6
|
const MYSQL_SINK_LENGTH = 255;
|
|
@@ -80,7 +80,7 @@ const unsupported = (operation) => Effect.fail(new OutboxUnavailable({
|
|
|
80
80
|
}));
|
|
81
81
|
/** The names of the outbox indexes that exist in the current MySQL database. */
|
|
82
82
|
const mysqlIndexes = (sql) => sql`SELECT DISTINCT index_name AS name FROM information_schema.statistics
|
|
83
|
-
WHERE table_schema = DATABASE() AND table_name = ${TABLE}`.pipe(Effect.map((rows) => new Set(
|
|
83
|
+
WHERE table_schema = DATABASE() AND table_name = ${TABLE}`.pipe(Effect.map((rows) => new Set(Array.map(rows, ({ name }) => name))));
|
|
84
84
|
/** MySQL has no `CREATE INDEX IF NOT EXISTS`. A concurrent migration may win the create. */
|
|
85
85
|
const migrateMysql = (sql) => Effect.gen(function* () {
|
|
86
86
|
if (Option.isSome(yield* Effect.serviceOption(sql.transactionService))) return yield* new OutboxUnavailable({
|
|
@@ -94,7 +94,7 @@ const migrateMysql = (sql) => Effect.gen(function* () {
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
function migrateOutbox(sql) {
|
|
97
|
-
const statements = (dialect) => Effect.forEach([CREATE_TABLE[dialect], ...
|
|
97
|
+
const statements = (dialect) => Effect.forEach([CREATE_TABLE[dialect], ...Array.map(INDEXES, (index) => createIndex(index, true))], (statement) => sql.unsafe(statement), { discard: true });
|
|
98
98
|
return attempt("migrateOutbox", sql.onDialectOrElse({
|
|
99
99
|
sqlite: () => statements("sqlite"),
|
|
100
100
|
pg: () => statements("pg"),
|
|
@@ -106,7 +106,7 @@ function migrateOutbox(sql) {
|
|
|
106
106
|
* MySQL readiness only reads the catalog. The backing may run inside a caller's
|
|
107
107
|
* transaction, and DDL there would commit it early.
|
|
108
108
|
*/
|
|
109
|
-
const mysqlReady = (sql) => Effect.flatMap(mysqlIndexes(sql), (names) =>
|
|
109
|
+
const mysqlReady = (sql) => Effect.flatMap(mysqlIndexes(sql), (names) => Array.every(INDEXES, (index) => names.has(index.name)) ? Effect.void : Effect.fail(new OutboxUnavailable({
|
|
110
110
|
operation: "ready",
|
|
111
111
|
detail: `${TABLE} is not migrated; on MySQL, run migrateOutbox at placement start`
|
|
112
112
|
})));
|
|
@@ -144,7 +144,7 @@ function createSqlOutboxBacking(sql) {
|
|
|
144
144
|
}));
|
|
145
145
|
const run = (operation, statement) => attempt(operation, Effect.andThen(ready, statement));
|
|
146
146
|
const execute = (operation, statement) => run(operation, statement).pipe(Effect.asVoid);
|
|
147
|
-
const rows = (operation, statement) => run(operation, statement).pipe(Effect.map((found) =>
|
|
147
|
+
const rows = (operation, statement) => run(operation, statement).pipe(Effect.map((found) => Array.map(found, (row) => decodeRow(row))));
|
|
148
148
|
/** Whether the draft was new. A draft with a known sink and key changes nothing. */
|
|
149
149
|
const insert = (draft) => {
|
|
150
150
|
const row = sql.insert({
|
|
@@ -225,5 +225,4 @@ function decodeReason(value) {
|
|
|
225
225
|
if (value === "attempts-exhausted" || value === "permanent" || value === "payload-poison") return value;
|
|
226
226
|
throw new TypeError(`unknown outbox dead-letter reason ${value}`);
|
|
227
227
|
}
|
|
228
|
-
//#endregion
|
|
229
228
|
export { createSqlOutboxBacking, migrateOutbox };
|
package/dist/alchemy.d.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { a as alarmSlotLayer, i as AlarmSlot, n as WakeupProgress, r as Wakeups, t as Wakeup } from "./wakeup
|
|
2
|
-
import { i as ObjectServices, n as rule, r as ObjectApp, t as Placement } from "./placement-
|
|
1
|
+
import { a as alarmSlotLayer, i as AlarmSlot, n as WakeupProgress, r as Wakeups, t as Wakeup } from "./wakeup--kIoqiIE.js";
|
|
2
|
+
import { i as ObjectServices, n as rule, r as ObjectApp, t as Placement } from "./placement-Cj3sd02S.js";
|
|
3
3
|
import { Effect, Layer, Scope } from "effect";
|
|
4
|
-
import { HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
4
|
+
import { HttpServerError, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
5
5
|
import { Storage, StorageFault } from "@streamsy/core";
|
|
6
6
|
import { DurableObject } from "alchemy/Cloudflare";
|
|
7
|
-
import { HttpServerError } from "effect/http/HttpServerError";
|
|
8
|
-
//#region src/alchemy.d.ts
|
|
9
7
|
/** An Alchemy HttpEffect; authenticate before evaluating this router. */
|
|
10
8
|
export declare const router: (options: {
|
|
11
9
|
readonly objects: Pick<DurableObject, "getByName">;
|
|
12
10
|
readonly prefix?: `/${string}`;
|
|
13
11
|
readonly placement?: Placement;
|
|
14
|
-
}) => Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError, HttpServerRequest.HttpServerRequest>;
|
|
12
|
+
}) => Effect.Effect<HttpServerResponse.HttpServerResponse, HttpServerError.HttpServerError, HttpServerRequest.HttpServerRequest>;
|
|
15
13
|
export type ObjectHandlers = {
|
|
16
14
|
readonly fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest>;
|
|
17
15
|
readonly alarm: () => Effect.Effect<void>;
|
|
@@ -21,5 +19,4 @@ export declare function objectHandlers<R = never>(options: {
|
|
|
21
19
|
readonly layer: Layer.Layer<Storage | AlarmSlot | R, StorageFault>;
|
|
22
20
|
readonly wakeups?: ReadonlyArray<Wakeup<ObjectServices<R>>>;
|
|
23
21
|
}): Effect.Effect<ObjectHandlers, never, Scope.Scope>;
|
|
24
|
-
//#endregion
|
|
25
22
|
export { Placement, Wakeup, type WakeupProgress, Wakeups, alarmSlotLayer, rule };
|
package/dist/alchemy.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as alarmSlotLayer, n as Wakeup, r as Wakeups } from "./wakeup-
|
|
2
|
-
import { a as acquireObject, i as rule, o as providedApp, r as Placement, s as unavailable, t as resolvePlacement } from "./router-
|
|
1
|
+
import { a as alarmSlotLayer, n as Wakeup, r as Wakeups } from "./wakeup-CcJE8k3i.js";
|
|
2
|
+
import { a as acquireObject, i as rule, o as providedApp, r as Placement, s as unavailable, t as resolvePlacement } from "./router-C2p1T25K.js";
|
|
3
3
|
import { Effect, Option } from "effect";
|
|
4
4
|
import { HttpRouter, HttpServerRequest, HttpServerRespondable, HttpServerResponse } from "effect/http";
|
|
5
5
|
import { securityHeaders, streamPath } from "@streamsy/core/http";
|
|
6
|
-
|
|
6
|
+
// src/alchemy.ts
|
|
7
7
|
/** An Alchemy HttpEffect; authenticate before evaluating this router. */
|
|
8
8
|
const router = (options) => {
|
|
9
9
|
const path = streamPath(options.prefix);
|
|
@@ -35,5 +35,4 @@ function objectHandlers(options) {
|
|
|
35
35
|
};
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
//#endregion
|
|
39
38
|
export { Placement, Wakeup, Wakeups, alarmSlotLayer, objectHandlers, router, rule };
|
package/dist/bun.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Duration } from "effect";
|
|
2
|
-
//#region src/bun.d.ts
|
|
3
2
|
export declare const DEFAULT_PORT = 3000;
|
|
4
3
|
export declare const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
5
4
|
export interface ListenerOptions {
|
|
@@ -9,5 +8,4 @@ export interface ListenerOptions {
|
|
|
9
8
|
readonly gracefulShutdownTimeout?: Duration.Input;
|
|
10
9
|
}
|
|
11
10
|
/** The Bun listener that keeps a parked long poll alive. */
|
|
12
|
-
export declare const listener: (options?: ListenerOptions) => import("effect/Layer").Layer<import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | import("effect/http/HttpServer").HttpServer | import("@effect/platform-bun/BunServices").BunServices, import("effect/http/HttpServerError").ServeError, never>;
|
|
13
|
-
//#endregion
|
|
11
|
+
export declare const listener: (options?: ListenerOptions) => import("effect/Layer").Layer<import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | import("effect/http/HttpServer").HttpServer | import("@effect/platform-bun/BunServices").BunServices, import("effect/http/HttpServerError").ServeError, never>;
|
package/dist/bun.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BunHttpServer } from "@effect/platform-bun";
|
|
2
|
-
|
|
2
|
+
// src/bun.ts
|
|
3
3
|
const DEFAULT_PORT = 3e3;
|
|
4
4
|
const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
5
5
|
const listenerOptions = (options) => ({
|
|
@@ -10,5 +10,4 @@ const listenerOptions = (options) => ({
|
|
|
10
10
|
});
|
|
11
11
|
/** The Bun listener that keeps a parked long poll alive. */
|
|
12
12
|
const listener = (options = {}) => BunHttpServer.layer(listenerOptions(options));
|
|
13
|
-
//#endregion
|
|
14
13
|
export { DEFAULT_HOSTNAME, DEFAULT_PORT, listener };
|
package/dist/cloudflare.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { a as alarmSlotLayer, n as WakeupProgress, r as Wakeups, t as Wakeup } from "./wakeup
|
|
2
|
-
import { i as ObjectServices, n as rule, r as ObjectApp, t as Placement } from "./placement-
|
|
1
|
+
import { a as alarmSlotLayer, n as WakeupProgress, r as Wakeups, t as Wakeup } from "./wakeup--kIoqiIE.js";
|
|
2
|
+
import { i as ObjectServices, n as rule, r as ObjectApp, t as Placement } from "./placement-Cj3sd02S.js";
|
|
3
3
|
import { Layer } from "effect";
|
|
4
4
|
import { Storage, StorageFault } from "@streamsy/core";
|
|
5
5
|
import { DurableObject } from "cloudflare:workers";
|
|
6
6
|
import { AlarmInvocationInfo, DurableObjectNamespace, DurableObjectState, ExportedHandler } from "@cloudflare/workers-types";
|
|
7
|
-
//#region src/cloudflare/object.d.ts
|
|
8
7
|
interface ObjectConfiguration<Env, R = never> {
|
|
9
8
|
readonly app: ObjectApp<R>;
|
|
10
9
|
readonly wakeups?: ReadonlyArray<Wakeup<ObjectServices<R>>>;
|
|
@@ -23,13 +22,10 @@ declare class ObjectHost<Env, R> extends DurableObject<Env> {
|
|
|
23
22
|
export declare const StreamsyObject: {
|
|
24
23
|
make: <Env = unknown, R = never>(configuration: ObjectConfiguration<Env, R>) => new (state: DurableObjectState, env: Env) => ObjectHost<Env, R>;
|
|
25
24
|
};
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/cloudflare/router.d.ts
|
|
28
25
|
interface RouterOptions<Env> {
|
|
29
26
|
readonly namespace: (env: Env) => DurableObjectNamespace;
|
|
30
27
|
readonly placement?: Placement;
|
|
31
28
|
readonly prefix?: `/${string}`;
|
|
32
29
|
}
|
|
33
30
|
export declare const router: <Env>(options: RouterOptions<Env>) => ExportedHandler<Env>;
|
|
34
|
-
//#endregion
|
|
35
31
|
export { type ObjectConfiguration, type ObjectInstance, Placement, type RouterOptions, Wakeup, type WakeupProgress, Wakeups, alarmSlotLayer, rule };
|
package/dist/cloudflare.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as alarmSlotLayer, n as Wakeup, r as Wakeups } from "./wakeup-
|
|
2
|
-
import { a as acquireObject, i as rule, n as router, o as providedApp, r as Placement, s as unavailable } from "./router-
|
|
1
|
+
import { a as alarmSlotLayer, n as Wakeup, r as Wakeups } from "./wakeup-CcJE8k3i.js";
|
|
2
|
+
import { a as acquireObject, i as rule, n as router, o as providedApp, r as Placement, s as unavailable } from "./router-C2p1T25K.js";
|
|
3
3
|
import { Context, Effect, Layer, ManagedRuntime, Option } from "effect";
|
|
4
4
|
import { HttpRouter, HttpServerResponse } from "effect/http";
|
|
5
5
|
import { DurableObject } from "cloudflare:workers";
|
|
6
|
-
|
|
6
|
+
// src/cloudflare/object.ts
|
|
7
7
|
var ObjectOwner = class extends Context.Service()("streamsy/ObjectOwner") {};
|
|
8
8
|
var ObjectHost = class extends DurableObject {
|
|
9
9
|
#runtime;
|
|
@@ -36,5 +36,4 @@ const StreamsyObject = { make: (configuration) => class extends ObjectHost {
|
|
|
36
36
|
super(state, env, configuration);
|
|
37
37
|
}
|
|
38
38
|
} };
|
|
39
|
-
//#endregion
|
|
40
39
|
export { Placement, StreamsyObject, Wakeup, Wakeups, alarmSlotLayer, router, rule };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
//#region src/contract.d.ts
|
|
2
1
|
/**
|
|
3
2
|
* Canonical encoding and the change-detection digests served routes need.
|
|
4
3
|
*
|
|
@@ -63,5 +62,4 @@ declare const PUBLIC_ERROR_TAGS: readonly ["InvalidParams", "ProtocolVersionUnsu
|
|
|
63
62
|
type PublicErrorTag = PublicError["_tag"];
|
|
64
63
|
/** The browser's JSON error boundary; unknown fields are discarded. */
|
|
65
64
|
declare function decodePublicError(value: unknown): PublicError;
|
|
66
|
-
//#endregion
|
|
67
65
|
export { PublicErrorTag as a, STATE_CONTRACT_HEADER as c, STREAM_VERSION_HEADER as d, VALUE_CONTRACT_HEADER as f, valueEtag as g, decodePublicError as h, PublicError as i, STATE_VERSION_HEADER as l, contractFingerprint as m, DURABLE_STATE_PROTOCOL as n, ReplayRecovery as o, canonicalJson as p, PUBLIC_ERROR_TAGS as r, ResumeRejectedReason as s, CanonicalValue as t, STREAM_CONTRACT_HEADER as u };
|
package/dist/contract.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as PublicErrorTag, c as STATE_CONTRACT_HEADER, d as STREAM_VERSION_HEADER, f as VALUE_CONTRACT_HEADER, g as valueEtag, h as decodePublicError, i as PublicError, l as STATE_VERSION_HEADER, m as contractFingerprint, n as DURABLE_STATE_PROTOCOL, o as ReplayRecovery, p as canonicalJson, r as PUBLIC_ERROR_TAGS, s as ResumeRejectedReason, t as CanonicalValue, u as STREAM_CONTRACT_HEADER } from "./contract-
|
|
1
|
+
import { a as PublicErrorTag, c as STATE_CONTRACT_HEADER, d as STREAM_VERSION_HEADER, f as VALUE_CONTRACT_HEADER, g as valueEtag, h as decodePublicError, i as PublicError, l as STATE_VERSION_HEADER, m as contractFingerprint, n as DURABLE_STATE_PROTOCOL, o as ReplayRecovery, p as canonicalJson, r as PUBLIC_ERROR_TAGS, s as ResumeRejectedReason, t as CanonicalValue, u as STREAM_CONTRACT_HEADER } from "./contract-CFoMXKuH.js";
|
|
2
2
|
export { CanonicalValue, DURABLE_STATE_PROTOCOL, PUBLIC_ERROR_TAGS, PublicError, PublicErrorTag, ReplayRecovery, ResumeRejectedReason, STATE_CONTRACT_HEADER, STATE_VERSION_HEADER, STREAM_CONTRACT_HEADER, STREAM_VERSION_HEADER, VALUE_CONTRACT_HEADER, canonicalJson, contractFingerprint, decodePublicError, valueEtag };
|
package/dist/contract.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { Array, Order, Record } from "effect";
|
|
2
|
+
// src/contract.ts
|
|
3
|
+
/**
|
|
4
|
+
* Canonical encoding and the change-detection digests served routes need.
|
|
5
|
+
*
|
|
6
|
+
* A contract fingerprint and a value ETag answer the same question — "is
|
|
7
|
+
* this still the thing you were holding?" — so both are computed from one
|
|
8
|
+
* canonical encoding. Object keys are sorted and array order is preserved, so
|
|
9
|
+
* two values that differ only in property order encode identically and cannot
|
|
10
|
+
* invalidate a consumer's cache for no reason.
|
|
11
|
+
*
|
|
12
|
+
* These are change-detection identities, not security digests.
|
|
13
|
+
*/
|
|
2
14
|
/** Encode a value with object keys sorted, recursively. Array order is preserved. */
|
|
3
15
|
function canonicalJson(value) {
|
|
4
|
-
if (isCanonicalArray(value)) return `[${
|
|
16
|
+
if (isCanonicalArray(value)) return `[${Array.join(Array.map(value, (item) => canonicalJson(item)), ",")}]`;
|
|
5
17
|
if (value === null) return "null";
|
|
6
18
|
if (value instanceof Object) return canonicalObject(value);
|
|
7
19
|
if (typeof value === "number" && !Number.isFinite(value)) throw new TypeError("Expected finite JSON number");
|
|
@@ -16,16 +28,12 @@ function isCanonicalArray(value) {
|
|
|
16
28
|
}
|
|
17
29
|
function canonicalObject(value) {
|
|
18
30
|
const entries = [];
|
|
19
|
-
for (const key of
|
|
31
|
+
for (const key of Array.sort(Record.keys(value), Order.String)) {
|
|
20
32
|
const member = value[key];
|
|
21
33
|
if (member === void 0) continue;
|
|
22
34
|
entries.push(`${JSON.stringify(key)}:${canonicalJson(member)}`);
|
|
23
35
|
}
|
|
24
|
-
return `{${
|
|
25
|
-
}
|
|
26
|
-
function compareKeys(left, right) {
|
|
27
|
-
if (left === right) return 0;
|
|
28
|
-
return left < right ? -1 : 1;
|
|
36
|
+
return `{${Array.join(entries, ",")}}`;
|
|
29
37
|
}
|
|
30
38
|
/** Eight lowercase hex characters of FNV-1a over the canonical encoding's UTF-16 code units. */
|
|
31
39
|
function contractFingerprint(value) {
|
|
@@ -120,5 +128,4 @@ function decodePublicError(value) {
|
|
|
120
128
|
default: throw new TypeError("Unknown public error tag");
|
|
121
129
|
}
|
|
122
130
|
}
|
|
123
|
-
//#endregion
|
|
124
131
|
export { DURABLE_STATE_PROTOCOL, PUBLIC_ERROR_TAGS, STATE_CONTRACT_HEADER, STATE_VERSION_HEADER, STREAM_CONTRACT_HEADER, STREAM_VERSION_HEADER, VALUE_CONTRACT_HEADER, canonicalJson, contractFingerprint, decodePublicError, valueEtag };
|
package/dist/deno.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Duration, Effect } from "effect";
|
|
2
|
-
//#region src/deno.d.ts
|
|
3
2
|
export declare const DEFAULT_PORT = 3000;
|
|
4
3
|
export declare const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
5
4
|
export interface ListenerOptions {
|
|
@@ -23,5 +22,4 @@ export declare const listener: (options?: ListenerOptions) => import("effect/Lay
|
|
|
23
22
|
* );
|
|
24
23
|
* ```
|
|
25
24
|
*/
|
|
26
|
-
export declare const nodeSqliteCompat: Effect.Effect<void>;
|
|
27
|
-
//#endregion
|
|
25
|
+
export declare const nodeSqliteCompat: Effect.Effect<void>;
|
package/dist/deno.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { DenoHttpServer } from "@effect/platform-deno";
|
|
3
|
-
|
|
3
|
+
// src/deno.ts
|
|
4
4
|
const DEFAULT_PORT = 3e3;
|
|
5
5
|
const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
6
6
|
const listenerOptions = (options) => ({
|
|
@@ -28,5 +28,4 @@ const nodeSqliteCompat = Effect.promise(() => import("node:sqlite")).pipe(Effect
|
|
|
28
28
|
const prototype = DatabaseSync.prototype;
|
|
29
29
|
if (prototype.enableLoadExtension === void 0) prototype.enableLoadExtension = () => {};
|
|
30
30
|
})));
|
|
31
|
-
//#endregion
|
|
32
31
|
export { DEFAULT_HOSTNAME, DEFAULT_PORT, listener, nodeSqliteCompat };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { t as CanonicalValue } from "./contract-
|
|
1
|
+
import { t as CanonicalValue } from "./contract-CFoMXKuH.js";
|
|
2
2
|
import { Duration, Effect, Layer, Runtime, Schema } from "effect";
|
|
3
3
|
import { HttpRouter, HttpServerRequest, HttpServerRespondable, HttpServerResponse } from "effect/http";
|
|
4
4
|
import { Http, StreamAddress, Streamsy } from "@streamsy/core";
|
|
5
5
|
import { StreamRoute } from "@streamsy/core/protocol";
|
|
6
6
|
import { ShutdownSignal } from "@streamsy/core/http";
|
|
7
|
-
//#region src/errors.d.ts
|
|
8
7
|
/** The public error body. `cases` gives one validating constructor per tag. */
|
|
9
8
|
export declare const PublicErrorSchema: Schema.TaggedUnion<{
|
|
10
9
|
readonly ContractChanged: Schema.TaggedStruct<"ContractChanged", {
|
|
@@ -95,8 +94,6 @@ declare class WireEncodeFailed extends WireEncodeFailed_base {
|
|
|
95
94
|
[HttpServerRespondable.symbol](): Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
|
|
96
95
|
}
|
|
97
96
|
type ServeError = InvalidParams | ProtocolVersionUnsupported | ResumeRejected | ContractChanged | TransportUnavailable | WireEncodeFailed;
|
|
98
|
-
//#endregion
|
|
99
|
-
//#region src/host.d.ts
|
|
100
97
|
type DefaultServeRequires<R> = HttpRouter.Request.Only<"Requires", R> | HttpRouter.Request.Only<"GlobalRequires", R>;
|
|
101
98
|
/** The options of `HttpRouter.serve`, plus the drain bound. */
|
|
102
99
|
type LayerOptions<R = never, HE = never, HR = DefaultServeRequires<R>> = NonNullable<Parameters<typeof HttpRouter.serve<unknown, unknown, R, HE, HR>>[1]> & {
|
|
@@ -155,8 +152,8 @@ declare function mount<P, A, K, RD, RE, const Path extends `/${string}`>(declara
|
|
|
155
152
|
}, path: Path & ExactPath<Path, NoInfer<P>>, options?: ReadOptions): import("effect/Layer").Layer<never, never, HttpRouter.HttpRouter | HttpRouter.Request<"Error", InvalidParams> | HttpRouter.Request<"Error", ProtocolVersionUnsupported> | HttpRouter.Request<"Error", ResumeRejected> | HttpRouter.Request<"Error", TransportUnavailable> | HttpRouter.Request<"Error", WireEncodeFailed> | HttpRouter.Request.From<"Requires", Exclude<K, HttpRouter.Provided>>>;
|
|
156
153
|
/** Durable Streams response for one resolved address. */
|
|
157
154
|
declare function stream<A, K, RD, RE>(address: Streamsy.Address<A, K, RD, RE>, options?: ReadOptions): Effect.Effect<HttpServerResponse.HttpServerResponse, ProtocolVersionUnsupported | ResumeRejected | TransportUnavailable | WireEncodeFailed, K | HttpServerRequest.HttpServerRequest | HttpRouter.RouteContext>;
|
|
158
|
-
/** Durable State
|
|
159
|
-
declare function state<C extends StreamAddress.Collections, K, RD, RE>(address: StreamAddress.StateAddress<C, RD, RE> & Streamsy.Address<StreamAddress.
|
|
155
|
+
/** Durable State response with replay-from-start recovery. Stored controls pass through. */
|
|
156
|
+
declare function state<C extends StreamAddress.Collections, K, RD, RE>(address: StreamAddress.StateAddress<C, RD, RE> & Streamsy.Address<StreamAddress.CollectionsEvent<C>, K, RD, RE>, options?: ReadOptions): Effect.Effect<HttpServerResponse.HttpServerResponse, ProtocolVersionUnsupported | ResumeRejected | TransportUnavailable | WireEncodeFailed, K | HttpServerRequest.HttpServerRequest | HttpRouter.RouteContext>;
|
|
160
157
|
interface ValueOptions {
|
|
161
158
|
readonly cache?: {
|
|
162
159
|
readonly visibility: "private" | "public";
|
|
@@ -167,5 +164,4 @@ interface ValueOptions {
|
|
|
167
164
|
}
|
|
168
165
|
/** Canonical JSON response for any computation, including mapped projection state. */
|
|
169
166
|
declare const value: <A, I extends CanonicalValue, E, R, RE>(schema: Schema.Codec<A, I, never, RE>, compute: Effect.Effect<A, E, R>, options?: ValueOptions | undefined) => Effect.Effect<HttpServerResponse.HttpServerResponse, E | ContractChanged | WireEncodeFailed, R | RE | HttpServerRequest.HttpServerRequest | HttpRouter.RouteContext>;
|
|
170
|
-
//#endregion
|
|
171
167
|
export { serve_d_exports as Serve };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.js";
|
|
2
2
|
import { STATE_CONTRACT_HEADER, STATE_VERSION_HEADER, STREAM_CONTRACT_HEADER, STREAM_VERSION_HEADER, VALUE_CONTRACT_HEADER, canonicalJson, contractFingerprint, valueEtag } from "./contract.js";
|
|
3
|
-
import { Cause, Duration, Effect, Exit, Layer, Runtime, Schema } from "effect";
|
|
3
|
+
import { Array, Cause, Duration, Effect, Exit, Layer, Order, Record, Runtime, Schema } from "effect";
|
|
4
4
|
import { HttpRouter, HttpServerRequest, HttpServerRespondable, HttpServerResponse } from "effect/http";
|
|
5
5
|
import { Http, Offset } from "@streamsy/core";
|
|
6
6
|
import { StreamsReader } from "@streamsy/core/protocol";
|
|
7
7
|
import { ShutdownSignal, layerShutdownSignal } from "@streamsy/core/http";
|
|
8
|
-
|
|
8
|
+
// src/errors.ts
|
|
9
9
|
/** The public error body. `cases` gives one validating constructor per tag. */
|
|
10
10
|
const PublicErrorSchema = Schema.TaggedUnion({
|
|
11
11
|
InvalidParams: {
|
|
@@ -92,8 +92,7 @@ function errorResponse(error, status, headers) {
|
|
|
92
92
|
}
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
//#region src/host.ts
|
|
95
|
+
// src/host.ts
|
|
97
96
|
const drain = (timeout = Duration.seconds(2)) => (serve) => {
|
|
98
97
|
return Layer.effectDiscard(Effect.gen(function* () {
|
|
99
98
|
const signal = yield* ShutdownSignal;
|
|
@@ -131,8 +130,7 @@ const layer = (routes, options) => HttpRouter.serve(routes, options).pipe(drain(
|
|
|
131
130
|
* caused. In a `Layer.launch` main the fiber ends only on failure or a signal.
|
|
132
131
|
*/
|
|
133
132
|
const teardown = (exit, onExit) => Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause) ? onExit(0) : Runtime.defaultTeardown(exit, onExit);
|
|
134
|
-
|
|
135
|
-
//#region src/serve.ts
|
|
133
|
+
// src/serve.ts
|
|
136
134
|
var serve_exports = /* @__PURE__ */ __exportAll({
|
|
137
135
|
ContractChanged: () => ContractChanged,
|
|
138
136
|
InvalidParams: () => InvalidParams,
|
|
@@ -187,7 +185,7 @@ function mount(declaration, path, options) {
|
|
|
187
185
|
function stream(address, options = {}) {
|
|
188
186
|
return read(address, options, "stream", Effect.succeed(null));
|
|
189
187
|
}
|
|
190
|
-
/** Durable State
|
|
188
|
+
/** Durable State response with replay-from-start recovery. Stored controls pass through. */
|
|
191
189
|
function state(address, options = {}) {
|
|
192
190
|
return read(address, options, "state", collectionDescriptor(address.collections));
|
|
193
191
|
}
|
|
@@ -198,7 +196,7 @@ function collectionDescriptor(value) {
|
|
|
198
196
|
route: matchedRoute.path,
|
|
199
197
|
detail: "Invalid collection metadata"
|
|
200
198
|
})));
|
|
201
|
-
return
|
|
199
|
+
return Record.fromEntries(Array.map(Record.toEntries(collections), ([type, collection]) => [type, collection.key]));
|
|
202
200
|
});
|
|
203
201
|
}
|
|
204
202
|
const read = Effect.fn("Serve.read")(function* (address, options, kind, descriptor) {
|
|
@@ -215,7 +213,7 @@ const read = Effect.fn("Serve.read")(function* (address, options, kind, descript
|
|
|
215
213
|
source,
|
|
216
214
|
id: address.id,
|
|
217
215
|
collections,
|
|
218
|
-
params: path.split("/")
|
|
216
|
+
params: Array.sort(Array.map(Array.filter(path.split("/"), (segment) => segment.startsWith(":")), (segment) => segment.slice(1)), Order.String),
|
|
219
217
|
version: 1,
|
|
220
218
|
recovery: "replay-from-start",
|
|
221
219
|
contract: options.contract ?? null
|
|
@@ -324,7 +322,8 @@ const value = Effect.fn("Serve.value")(function* (schema, compute, options = {})
|
|
|
324
322
|
"cache-control": cacheControl,
|
|
325
323
|
etag
|
|
326
324
|
};
|
|
327
|
-
|
|
325
|
+
const ifNoneMatch = request.headers["if-none-match"];
|
|
326
|
+
if (ifNoneMatch !== void 0 && Array.some(ifNoneMatch.split(","), (tag) => tag.trim() === "*" || tag.trim().replace(/^W\//, "") === etag)) return HttpServerResponse.empty({
|
|
328
327
|
status: 304,
|
|
329
328
|
headers: responseHeaders
|
|
330
329
|
});
|
|
@@ -333,5 +332,4 @@ const value = Effect.fn("Serve.value")(function* (schema, compute, options = {})
|
|
|
333
332
|
"content-type": "application/json"
|
|
334
333
|
} });
|
|
335
334
|
});
|
|
336
|
-
//#endregion
|
|
337
335
|
export { PublicErrorSchema, serve_exports as Serve };
|
package/dist/node.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Duration, Layer } from "effect";
|
|
2
2
|
import { HttpServer } from "effect/http";
|
|
3
3
|
import { ServerOptions } from "node:http";
|
|
4
|
-
//#region src/node.d.ts
|
|
5
4
|
export declare const DEFAULT_PORT = 3000;
|
|
6
5
|
export declare const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
7
6
|
export interface ListenerOptions {
|
|
@@ -16,5 +15,4 @@ export interface ListenerOptions {
|
|
|
16
15
|
readonly gracefulShutdownTimeout?: Duration.Input;
|
|
17
16
|
}
|
|
18
17
|
/** The Node listener for a Streamsy host. */
|
|
19
|
-
export declare const listener: (options?: ListenerOptions) => Layer.Layer<import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | HttpServer.HttpServer | import("@effect/platform-node/NodeServices").NodeServices, import("effect/http/HttpServerError").ServeError, never>;
|
|
20
|
-
//#endregion
|
|
18
|
+
export declare const listener: (options?: ListenerOptions) => Layer.Layer<import("effect/http/Etag").Generator | import("effect/http/HttpPlatform").HttpPlatform | HttpServer.HttpServer | import("@effect/platform-node/NodeServices").NodeServices, import("effect/http/HttpServerError").ServeError, never>;
|
package/dist/node.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Effect, Layer } from "effect";
|
|
|
2
2
|
import { HttpServer, HttpServerResponse } from "effect/http";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { NodeHttpServer } from "@effect/platform-node";
|
|
5
|
-
|
|
5
|
+
// src/node.ts
|
|
6
6
|
const DEFAULT_PORT = 3e3;
|
|
7
7
|
const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
8
8
|
const listenOptions = (options) => ({
|
|
@@ -54,5 +54,4 @@ const createUnavailableAwareServer = (options) => {
|
|
|
54
54
|
};
|
|
55
55
|
/** The Node listener for a Streamsy host. */
|
|
56
56
|
const listener = (options = {}) => nodeServer.pipe(Layer.provideMerge(NodeHttpServer.layer(() => createUnavailableAwareServer(options.server ?? {}), listenOptions(options))));
|
|
57
|
-
//#endregion
|
|
58
57
|
export { DEFAULT_HOSTNAME, DEFAULT_PORT, listener };
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { r as Wakeups } from "./wakeup
|
|
1
|
+
import { r as Wakeups } from "./wakeup--kIoqiIE.js";
|
|
2
2
|
import { Context, Effect, Layer, Option, Scope } from "effect";
|
|
3
3
|
import { HttpRouter, HttpServerRespondable, HttpServerResponse } from "effect/http";
|
|
4
4
|
import { Storage, StorageFault } from "@streamsy/core";
|
|
5
|
-
//#region src/cloudflare/object-runtime.d.ts
|
|
6
5
|
type ObjectServices<R = never> = Storage | Wakeups | R;
|
|
7
6
|
type ObjectApp<R = never> = Layer.Layer<never, never, HttpRouter.HttpRouter | HttpRouter.Request.From<"Requires", ObjectServices<R>> | HttpRouter.Request.From<"Error", HttpServerRespondable.Respondable>>;
|
|
8
|
-
//#endregion
|
|
9
|
-
//#region src/cloudflare/placement.d.ts
|
|
10
7
|
interface Placement {
|
|
11
8
|
readonly name: (streamPath: string) => string;
|
|
12
9
|
}
|
|
@@ -56,5 +53,4 @@ declare const Placement: {
|
|
|
56
53
|
*/
|
|
57
54
|
readonly byRoute: (rules: ReadonlyArray<ErasedOwnerRule>) => Placement;
|
|
58
55
|
};
|
|
59
|
-
//#endregion
|
|
60
56
|
export { ObjectServices as i, rule as n, ObjectApp as r, Placement as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
// \0rolldown/runtime.js
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __exportAll = (all, no_symbols) => {
|
|
4
4
|
let target = {};
|
|
@@ -9,5 +9,4 @@ var __exportAll = (all, no_symbols) => {
|
|
|
9
9
|
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
10
|
return target;
|
|
11
11
|
};
|
|
12
|
-
//#endregion
|
|
13
12
|
export { __exportAll as t };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { i as makeWakeups, r as Wakeups, t as RequestTouch } from "./wakeup-
|
|
1
|
+
import { i as makeWakeups, r as Wakeups, t as RequestTouch } from "./wakeup-CcJE8k3i.js";
|
|
2
2
|
import { Context, Effect, Exit, Layer, Option, Scope } from "effect";
|
|
3
3
|
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
4
4
|
import { Http } from "@streamsy/core";
|
|
5
5
|
import { securityHeaders, streamPath } from "@streamsy/core/http";
|
|
6
|
-
|
|
6
|
+
// src/cloudflare/host-program.ts
|
|
7
7
|
/**
|
|
8
8
|
* Reconciliation belongs to the mutation's finalizer. A request interruption
|
|
9
9
|
* can be pending when the mutation's own uninterruptible region ends, so a
|
|
@@ -11,8 +11,7 @@ import { securityHeaders, streamPath } from "@streamsy/core/http";
|
|
|
11
11
|
* reconciliation is logged and does not replace the mutation's result.
|
|
12
12
|
*/
|
|
13
13
|
const withMutationReconciliation = (effect, reconcile) => effect.pipe(Effect.ensuring(reconcile.pipe(Effect.uninterruptible, Effect.catchCause((cause) => Effect.logError("Wakeup reconciliation failed", cause)))));
|
|
14
|
-
|
|
15
|
-
//#region src/cloudflare/object-runtime.ts
|
|
14
|
+
// src/cloudflare/object-runtime.ts
|
|
16
15
|
/** Both object hosts install this middleware, including around user routes. */
|
|
17
16
|
function objectApp(app, context) {
|
|
18
17
|
const middleware = HttpRouter.middleware((handler) => Effect.gen(function* () {
|
|
@@ -55,8 +54,7 @@ function acquireObject(layer, compile, wakeups) {
|
|
|
55
54
|
function providedApp(app, context) {
|
|
56
55
|
return objectApp(app, context).pipe(HttpRouter.provideRequest(Layer.succeedContext(context)));
|
|
57
56
|
}
|
|
58
|
-
|
|
59
|
-
//#region src/cloudflare/placement.ts
|
|
57
|
+
// src/cloudflare/placement.ts
|
|
60
58
|
/**
|
|
61
59
|
* Declare one placement rule with its stream's parameters in scope.
|
|
62
60
|
*
|
|
@@ -84,8 +82,7 @@ const Placement = {
|
|
|
84
82
|
return "";
|
|
85
83
|
} })
|
|
86
84
|
};
|
|
87
|
-
|
|
88
|
-
//#region src/cloudflare/router.ts
|
|
85
|
+
// src/cloudflare/router.ts
|
|
89
86
|
const badRequest = (message) => new Response(message, {
|
|
90
87
|
status: 400,
|
|
91
88
|
headers: securityHeaders
|
|
@@ -126,5 +123,4 @@ const router = (options) => {
|
|
|
126
123
|
return namespace.get(namespace.idFromName(child.name)).fetch(request);
|
|
127
124
|
} };
|
|
128
125
|
};
|
|
129
|
-
//#endregion
|
|
130
126
|
export { acquireObject as a, rule as i, router as n, providedApp as o, Placement as r, unavailable as s, resolvePlacement as t };
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Context, Duration, Effect, Layer, Option } from "effect";
|
|
2
2
|
import { Storage, StorageFault } from "@streamsy/core";
|
|
3
3
|
import { DurableObjectStorage } from "@cloudflare/workers-types";
|
|
4
|
-
//#region src/cloudflare/alarm.d.ts
|
|
5
4
|
declare const AlarmSlot_base: Context.ServiceClass<AlarmSlot, "@streamsy/serve/cloudflare/AlarmSlot", {
|
|
6
5
|
readonly current: Effect.Effect<Option.Option<number>>;
|
|
7
6
|
readonly arm: (at: number) => Effect.Effect<void>;
|
|
@@ -9,8 +8,6 @@ declare const AlarmSlot_base: Context.ServiceClass<AlarmSlot, "@streamsy/serve/c
|
|
|
9
8
|
}>;
|
|
10
9
|
declare class AlarmSlot extends AlarmSlot_base {}
|
|
11
10
|
declare const alarmSlotLayer: (storage: DurableObjectStorage) => Layer.Layer<AlarmSlot>;
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region src/cloudflare/wakeup.d.ts
|
|
14
11
|
/**
|
|
15
12
|
* A bounded run that stopped at its bound returns `{ more: true }`. The
|
|
16
13
|
* scheduler then re-arms at once and does not count the pass as a failure.
|
|
@@ -52,5 +49,4 @@ declare const Wakeups_base: Context.ServiceClass<Wakeups, "@streamsy/serve/cloud
|
|
|
52
49
|
}>>;
|
|
53
50
|
}>;
|
|
54
51
|
declare class Wakeups extends Wakeups_base {}
|
|
55
|
-
//#endregion
|
|
56
52
|
export { alarmSlotLayer as a, AlarmSlot as i, WakeupProgress as n, Wakeups as r, Wakeup as t };
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { Clock, Context, Duration, Effect, Exit, Layer, Option, Predicate, Semaphore } from "effect";
|
|
1
|
+
import { Array, Clock, Context, Duration, Effect, Exit, Layer, Option, Predicate, Semaphore } from "effect";
|
|
2
2
|
import { Storage } from "@streamsy/core";
|
|
3
3
|
import { Protocol } from "@streamsy/core/protocol";
|
|
4
|
-
|
|
4
|
+
// src/cloudflare/alarm.ts
|
|
5
5
|
var AlarmSlot = class extends Context.Service()("@streamsy/serve/cloudflare/AlarmSlot") {};
|
|
6
6
|
const alarmSlotLayer = (storage) => Layer.succeed(AlarmSlot, {
|
|
7
7
|
current: Effect.promise(() => storage.getAlarm()).pipe(Effect.map(Option.fromNullishOr)),
|
|
8
8
|
arm: (at) => Effect.promise(() => storage.setAlarm(at)),
|
|
9
9
|
clear: Effect.promise(() => storage.deleteAlarm())
|
|
10
10
|
});
|
|
11
|
-
|
|
12
|
-
//#region src/cloudflare/wakeup.ts
|
|
11
|
+
// src/cloudflare/wakeup.ts
|
|
13
12
|
const Permits = Context.Reference("@streamsy/serve/cloudflare/WakeupPermits", { defaultValue: () => void 0 });
|
|
14
13
|
const Guarded = Symbol.for("@streamsy/serve/cloudflare/WakeupPermit");
|
|
15
14
|
const reportsMore = (value) => Predicate.hasProperty(value, "more") && value.more;
|
|
@@ -35,16 +34,16 @@ const expiry = Wakeup.make({
|
|
|
35
34
|
});
|
|
36
35
|
/** Internal object owner. Its context closes over the same services as fetch. */
|
|
37
36
|
const makeWakeups = (context, definitions = []) => Effect.gen(function* () {
|
|
38
|
-
const wakeups = [expiry, ...definitions]
|
|
37
|
+
const wakeups = Array.map([expiry, ...definitions], (wakeup) => Wakeup.make(wakeup));
|
|
39
38
|
const names = /* @__PURE__ */ new Set();
|
|
40
39
|
for (const wakeup of wakeups) {
|
|
41
40
|
if (names.has(wakeup.name)) return yield* Effect.die(/* @__PURE__ */ new Error(`Duplicate wakeup name: ${wakeup.name}`));
|
|
42
41
|
names.add(wakeup.name);
|
|
43
42
|
}
|
|
44
|
-
const permits = new Map(
|
|
43
|
+
const permits = new Map(Array.map(wakeups, (wakeup) => [wakeup.name, Semaphore.makeUnsafe(1)]));
|
|
45
44
|
const reconcilePermit = yield* Semaphore.make(1);
|
|
46
45
|
const turnPermit = yield* Semaphore.make(1);
|
|
47
|
-
const states =
|
|
46
|
+
const states = Array.map(wakeups, (wakeup) => ({
|
|
48
47
|
wakeup,
|
|
49
48
|
failures: 0,
|
|
50
49
|
floor: 0,
|
|
@@ -121,5 +120,4 @@ const makeWakeups = (context, definitions = []) => Effect.gen(function* () {
|
|
|
121
120
|
alarm: alarm.pipe(Effect.provide(complete))
|
|
122
121
|
};
|
|
123
122
|
});
|
|
124
|
-
//#endregion
|
|
125
123
|
export { alarmSlotLayer as a, makeWakeups as i, Wakeup as n, Wakeups as r, RequestTouch as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamsy/serve",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Effect HttpRouter routes, Bun, Node, Deno and Cloudflare hosts, and durable action delivery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"test:dialects": "bun --bun vitest run test/action/outbox-dialects.test.ts"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@streamsy/core": "^0.
|
|
90
|
+
"@streamsy/core": "^0.5.0"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@cloudflare/workers-types": "4.20251228.0",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@effect/sql-pg": "4.0.5",
|
|
99
99
|
"@effect/sql-sqlite-do": "4.0.5",
|
|
100
100
|
"@effect/vitest": "4.0.5",
|
|
101
|
-
"@streamsy/storage": "0.
|
|
101
|
+
"@streamsy/storage": "0.5.0",
|
|
102
102
|
"@types/bun": "^1.4.3",
|
|
103
103
|
"alchemy": "2.0.0-beta.83",
|
|
104
104
|
"effect": "4.0.5",
|