@streamsy/serve 0.4.0 → 0.6.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/README.md +36 -3
- 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 +16 -12
- package/dist/alchemy.js +17 -6
- package/dist/bun.d.ts +1 -3
- package/dist/bun.js +1 -2
- package/dist/cloudflare.d.ts +16 -14
- package/dist/cloudflare.js +5 -6
- 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/object-link-BbxO01Z5.d.ts +118 -0
- package/dist/object-link-HD-tFY4o.js +230 -0
- package/dist/{rolldown-runtime-D7D4PA-g.js → rolldown-runtime-D8jQpDG7.js} +1 -2
- package/dist/{wakeup-BtnMEjLv.d.ts → wakeup-Cy6pvhmx.d.ts} +6 -10
- package/dist/{wakeup-CWKg8pts.js → wakeup-eIs0xIUh.js} +38 -28
- package/package.json +3 -3
- package/dist/placement-BOM3E08P.d.ts +0 -60
- package/dist/router-DviIc1O7.js +0 -130
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Effect HttpRouter routes for streams, Durable State and JSON values, plus Bun,
|
|
4
4
|
Node, Deno and Cloudflare host glue and durable action delivery. Requires Effect
|
|
5
|
-
`4.0.
|
|
5
|
+
`4.0.5`.
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
8
|
import { Layer, Schema } from "effect";
|
|
@@ -85,8 +85,8 @@ The Durable Object host does not get a stop signal and uses neither.
|
|
|
85
85
|
| `/bun` | `listener`, its options and defaults |
|
|
86
86
|
| `/node` | `listener`, its options and defaults |
|
|
87
87
|
| `/deno` | `listener`, its options and defaults; `nodeSqliteCompat` |
|
|
88
|
-
| `/cloudflare` | `StreamsyObject`, placement router/rules, alarm service
|
|
89
|
-
| `/alchemy` | `objectHandlers`, placement router/rules, alarm service
|
|
88
|
+
| `/cloudflare` | `StreamsyObject`, placement router/rules, alarm service, `ObjectLink`, `ObjectStreams` |
|
|
89
|
+
| `/alchemy` | `objectHandlers`, placement router/rules, alarm service, `ObjectLink`, `objectFetch` |
|
|
90
90
|
| `/action` | Unchanged action declarations |
|
|
91
91
|
| `/action/errors`, `/action/outbox`, `/action/runtime`, `/action/sql` | Action delivery and backing APIs; `createSqlOutboxBacking` runs on SQLite, Postgres and MySQL |
|
|
92
92
|
|
|
@@ -106,3 +106,36 @@ Guides: [Serve routes](https://streamsy.dev/docs/http/serve),
|
|
|
106
106
|
[Cloudflare](https://streamsy.dev/docs/hosting/cloudflare),
|
|
107
107
|
[Host contract](https://streamsy.dev/docs/hosting/host-contract),
|
|
108
108
|
[Alchemy](https://streamsy.dev/docs/hosting/cloudflare#alchemy).
|
|
109
|
+
|
|
110
|
+
## Object links and projection wakeups
|
|
111
|
+
|
|
112
|
+
`ObjectLink.make({ group, prefix, placement, access })` from `/cloudflare` or
|
|
113
|
+
`/alchemy` declares an internal protocol mount and its client together. Add
|
|
114
|
+
`link.routes` to the owner app and the client to the caller's `links`.
|
|
115
|
+
The prefix must start with `/__`. Both public placement routers return 404
|
|
116
|
+
for paths whose first segment starts with `__`, without calling an object.
|
|
117
|
+
The owner still needs the explicit internal route.
|
|
118
|
+
|
|
119
|
+
Both hosts use one `ObjectLink` and one `LinkClient`. A native Cloudflare
|
|
120
|
+
object takes `links: (env) => [link.client(env.OWNERS)]`. Alchemy
|
|
121
|
+
`objectHandlers` takes `links: [link.client(objectFetch(owners))]`. A linked
|
|
122
|
+
declaration must not also be provided by local storage; the host rejects that
|
|
123
|
+
clash during acquisition.
|
|
124
|
+
|
|
125
|
+
`ObjectStreams.layer(group, { objects, placement, prefix })` builds the same
|
|
126
|
+
remote stream services for an existing protocol mount. `objects` is a native
|
|
127
|
+
namespace or an `ObjectFetch`: `(name, request) => Effect<Response,
|
|
128
|
+
HttpClientError>`. The client uses `HttpClient.make`, retaining caller context,
|
|
129
|
+
tracing and interruption. Alchemy's `objectFetch` adapts its stub Effects.
|
|
130
|
+
|
|
131
|
+
Access is mount policy for every stream. `Streamsy.wake` is a State preset
|
|
132
|
+
with the normal writer service. Use `read` for public projection outputs,
|
|
133
|
+
`full` when an internal producer creates its output, or `append` when the
|
|
134
|
+
owner creates it first. `Serve.mount` accepts GET and HEAD.
|
|
135
|
+
|
|
136
|
+
Object hosts acquire `ProjectionWakeup.attach(projection)` from
|
|
137
|
+
`@streamsy/projection` in their scope through `wakeups: [...]`. Local
|
|
138
|
+
`Storage.committed` events populate its in-memory pending set. Alarms drain
|
|
139
|
+
bounded passes. Retry options take an Effect `Schedule`; finite schedules stop
|
|
140
|
+
on exhaustion. A crash can leave work pending until the next source commit.
|
|
141
|
+
See the [projection host reference](https://streamsy.dev/docs/hosting/cloudflare#projections).
|
|
@@ -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-Cy6pvhmx.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-eIs0xIUh.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,25 +1,29 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { i as
|
|
1
|
+
import { a as AlarmSlot, i as Wakeups, n as WakeupProgress, o as alarmSlotLayer, r as WakeupSource, t as Wakeup } from "./wakeup-Cy6pvhmx.js";
|
|
2
|
+
import { a as ObjectLinkOptions, c as ObjectStreamsOptions, d as ObjectApp, i as ObjectLink, l as Placement, n as LinkClient, o as ObjectFetch, r as LinkRoutes, s as ObjectStreams, t as LinkAccess, u as rule } from "./object-link-BbxO01Z5.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>;
|
|
13
|
+
/**
|
|
14
|
+
* An `ObjectFetch` over an Alchemy Durable Object binding, for
|
|
15
|
+
* `ObjectStreams.layer` and `ObjectLink.client`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const objectFetch: (objects: Pick<DurableObject, "getByName">) => ObjectFetch;
|
|
15
18
|
export type ObjectHandlers = {
|
|
16
19
|
readonly fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, never, HttpServerRequest.HttpServerRequest>;
|
|
17
20
|
readonly alarm: () => Effect.Effect<void>;
|
|
18
21
|
};
|
|
19
|
-
export declare function objectHandlers<R = never>(options: {
|
|
20
|
-
readonly app: ObjectApp<R>;
|
|
21
|
-
readonly layer: Layer.Layer<Storage | AlarmSlot | R, StorageFault>;
|
|
22
|
-
readonly wakeups?: ReadonlyArray<
|
|
22
|
+
export declare function objectHandlers<R = never, L = never>(options: {
|
|
23
|
+
readonly app: ObjectApp<R | L>;
|
|
24
|
+
readonly layer: Layer.Layer<Storage | AlarmSlot | R, StorageFault, L>;
|
|
25
|
+
readonly wakeups?: ReadonlyArray<WakeupSource<Storage | R | L>>;
|
|
26
|
+
/** Streams that live on other objects, through `link.client(objectFetch(binding))`. */
|
|
27
|
+
readonly links?: ReadonlyArray<LinkClient<L>>;
|
|
23
28
|
}): Effect.Effect<ObjectHandlers, never, Scope.Scope>;
|
|
24
|
-
|
|
25
|
-
export { Placement, Wakeup, type WakeupProgress, Wakeups, alarmSlotLayer, rule };
|
|
29
|
+
export { type LinkAccess, type LinkClient, type LinkRoutes, type ObjectFetch, ObjectLink, type ObjectLinkOptions, ObjectStreams, type ObjectStreamsOptions, Placement, Wakeup, type WakeupProgress, type WakeupSource, 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
|
|
1
|
+
import { a as alarmSlotLayer, n as Wakeup, r as Wakeups } from "./wakeup-eIs0xIUh.js";
|
|
2
|
+
import { a as resolvePlacement, c as rule, d as unavailable, f as withLinks, i as isReserved, l as acquireObject, n as ObjectStreams, r as objectTransportError, s as Placement, t as ObjectLink, u as providedApp } from "./object-link-HD-tFY4o.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);
|
|
@@ -11,6 +11,10 @@ const router = (options) => {
|
|
|
11
11
|
return Effect.gen(function* () {
|
|
12
12
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
13
13
|
const pathname = new URL(request.url, "https://streamsy.internal").pathname;
|
|
14
|
+
if (isReserved(pathname)) return HttpServerResponse.empty({
|
|
15
|
+
status: 404,
|
|
16
|
+
headers: securityHeaders
|
|
17
|
+
});
|
|
14
18
|
const streamPath = path.strip(pathname);
|
|
15
19
|
if (!streamPath || streamPath === pathname) return HttpServerResponse.text(`Stream path required: ${path.requiredPathPattern()}`, {
|
|
16
20
|
status: 400,
|
|
@@ -21,9 +25,17 @@ const router = (options) => {
|
|
|
21
25
|
return yield* options.objects.getByName(child.name).fetch(request);
|
|
22
26
|
});
|
|
23
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* An `ObjectFetch` over an Alchemy Durable Object binding, for
|
|
30
|
+
* `ObjectStreams.layer` and `ObjectLink.client`.
|
|
31
|
+
*/
|
|
32
|
+
const objectFetch = (objects) => Effect.fn("Alchemy.objectFetch")(function* (name, request) {
|
|
33
|
+
const response = yield* objects.getByName(name).fetch(HttpServerRequest.fromWeb(request)).pipe(Effect.mapError((cause) => objectTransportError(request, cause)));
|
|
34
|
+
return HttpServerResponse.toWeb(response);
|
|
35
|
+
});
|
|
24
36
|
function objectHandlers(options) {
|
|
25
37
|
return Effect.gen(function* () {
|
|
26
|
-
const get = yield* acquireObject(options.layer, (context, alarm) => Effect.gen(function* () {
|
|
38
|
+
const get = yield* acquireObject(withLinks(options.layer, options.links), (context, alarm) => Effect.gen(function* () {
|
|
27
39
|
return {
|
|
28
40
|
fetch: (yield* HttpRouter.toHttpEffect(providedApp(options.app, context))).pipe(Effect.scoped, Effect.catch(HttpServerRespondable.toResponse)),
|
|
29
41
|
alarm: alarm.pipe(Effect.provide(context))
|
|
@@ -35,5 +47,4 @@ function objectHandlers(options) {
|
|
|
35
47
|
};
|
|
36
48
|
});
|
|
37
49
|
}
|
|
38
|
-
|
|
39
|
-
export { Placement, Wakeup, Wakeups, alarmSlotLayer, objectHandlers, router, rule };
|
|
50
|
+
export { ObjectLink, ObjectStreams, Placement, Wakeup, Wakeups, alarmSlotLayer, objectFetch, 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,35 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as
|
|
1
|
+
import { i as Wakeups, n as WakeupProgress, o as alarmSlotLayer, r as WakeupSource, t as Wakeup } from "./wakeup-Cy6pvhmx.js";
|
|
2
|
+
import { a as ObjectLinkOptions, c as ObjectStreamsOptions, d as ObjectApp, i as ObjectLink, l as Placement, n as LinkClient, o as ObjectFetch, r as LinkRoutes, s as ObjectStreams, t as LinkAccess, u as rule } from "./object-link-BbxO01Z5.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
|
-
|
|
8
|
-
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
|
|
7
|
+
interface ObjectConfiguration<Env, R = never, L = never> {
|
|
8
|
+
readonly app: ObjectApp<R | L>;
|
|
9
|
+
readonly wakeups?: ReadonlyArray<WakeupSource<Storage | R | L>>;
|
|
10
|
+
readonly layer: (state: DurableObjectState, env: Env) => Layer.Layer<Storage | R, StorageFault, L>;
|
|
11
|
+
/**
|
|
12
|
+
* Streams that live on other objects. Their services come from these links
|
|
13
|
+
* and never from `layer`. An object whose `layer` also provides a linked
|
|
14
|
+
* stream fails to start.
|
|
15
|
+
*/
|
|
16
|
+
readonly links?: (env: Env) => ReadonlyArray<LinkClient<L>>;
|
|
12
17
|
}
|
|
13
18
|
interface ObjectInstance {
|
|
14
19
|
fetch(request: Request): Promise<Response>;
|
|
15
20
|
alarm(info?: AlarmInvocationInfo): Promise<void>;
|
|
16
21
|
}
|
|
17
|
-
declare class ObjectHost<Env, R> extends DurableObject<Env> {
|
|
22
|
+
declare class ObjectHost<Env, R, L> extends DurableObject<Env> {
|
|
18
23
|
#private;
|
|
19
|
-
constructor(state: DurableObjectState, env: Env, configuration: ObjectConfiguration<Env, R>);
|
|
24
|
+
constructor(state: DurableObjectState, env: Env, configuration: ObjectConfiguration<Env, R, L>);
|
|
20
25
|
fetch(request: Request): Promise<Response>;
|
|
21
26
|
alarm(_info?: AlarmInvocationInfo): Promise<void>;
|
|
22
27
|
}
|
|
23
28
|
export declare const StreamsyObject: {
|
|
24
|
-
make: <Env = unknown, R = never>(configuration: ObjectConfiguration<Env, R>) => new (state: DurableObjectState, env: Env) => ObjectHost<Env, R>;
|
|
29
|
+
make: <Env = unknown, R = never, L = never>(configuration: ObjectConfiguration<Env, R, L>) => new (state: DurableObjectState, env: Env) => ObjectHost<Env, R, L>;
|
|
25
30
|
};
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/cloudflare/router.d.ts
|
|
28
31
|
interface RouterOptions<Env> {
|
|
29
32
|
readonly namespace: (env: Env) => DurableObjectNamespace;
|
|
30
33
|
readonly placement?: Placement;
|
|
31
34
|
readonly prefix?: `/${string}`;
|
|
32
35
|
}
|
|
33
36
|
export declare const router: <Env>(options: RouterOptions<Env>) => ExportedHandler<Env>;
|
|
34
|
-
|
|
35
|
-
export { type ObjectConfiguration, type ObjectInstance, Placement, type RouterOptions, Wakeup, type WakeupProgress, Wakeups, alarmSlotLayer, rule };
|
|
37
|
+
export { type LinkAccess, type LinkClient, type LinkRoutes, type ObjectConfiguration, type ObjectFetch, type ObjectInstance, ObjectLink, type ObjectLinkOptions, ObjectStreams, type ObjectStreamsOptions, Placement, type RouterOptions, Wakeup, type WakeupProgress, type WakeupSource, Wakeups, alarmSlotLayer, rule };
|
package/dist/cloudflare.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { a as alarmSlotLayer, n as Wakeup, r as Wakeups } from "./wakeup-
|
|
2
|
-
import {
|
|
1
|
+
import { a as alarmSlotLayer, n as Wakeup, r as Wakeups } from "./wakeup-eIs0xIUh.js";
|
|
2
|
+
import { c as rule, d as unavailable, f as withLinks, l as acquireObject, n as ObjectStreams, o as router, s as Placement, t as ObjectLink, u as providedApp } from "./object-link-HD-tFY4o.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;
|
|
10
10
|
constructor(state, env, configuration) {
|
|
11
11
|
super(state, env);
|
|
12
|
-
this.#runtime = ManagedRuntime.make(Layer.effect(ObjectOwner, Effect.map(acquireObject(Layer.suspend(() => Layer.merge(configuration.layer(state, env), alarmSlotLayer(state.storage))), (context, alarm) => Effect.gen(function* () {
|
|
12
|
+
this.#runtime = ManagedRuntime.make(Layer.effect(ObjectOwner, Effect.map(acquireObject(Layer.suspend(() => Layer.merge(withLinks(configuration.layer(state, env), configuration.links?.(env)), alarmSlotLayer(state.storage))), (context, alarm) => Effect.gen(function* () {
|
|
13
13
|
const web = HttpRouter.toWebHandler(providedApp(configuration.app, context), { disableLogger: true });
|
|
14
14
|
yield* Effect.addFinalizer(() => Effect.promise(web.dispose));
|
|
15
15
|
return {
|
|
@@ -36,5 +36,4 @@ const StreamsyObject = { make: (configuration) => class extends ObjectHost {
|
|
|
36
36
|
super(state, env, configuration);
|
|
37
37
|
}
|
|
38
38
|
} };
|
|
39
|
-
|
|
40
|
-
export { Placement, StreamsyObject, Wakeup, Wakeups, alarmSlotLayer, router, rule };
|
|
39
|
+
export { ObjectLink, ObjectStreams, 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 };
|