@streamsy/storage 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/dist/bun.d.ts +1 -2
- package/dist/bun.js +6 -8
- package/dist/durable-object.d.ts +1 -2
- package/dist/durable-object.js +3 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +3 -4
- package/dist/{rolldown-runtime-D7D4PA-g.js → rolldown-runtime-D8jQpDG7.js} +1 -2
- package/dist/{storage-CrMmTyyG.d.ts → storage-Cb_kOO_g.d.ts} +4 -11
- package/dist/{storage-CpnFxwO5.js → storage-DFCZf1UK.js} +33 -33
- package/package.json +3 -3
package/dist/bun.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as SqlStorageOptions } from "./storage-
|
|
1
|
+
import { r as SqlStorageOptions } from "./storage-Cb_kOO_g.js";
|
|
2
2
|
import { Layer } from "effect";
|
|
3
3
|
import { StorageFault, Streamsy } from "@streamsy/core";
|
|
4
4
|
import { Protocol } from "@streamsy/core/protocol";
|
|
@@ -16,5 +16,4 @@ declare const storageLayer: (options: BunStorageOptions) => Layer.Layer<import("
|
|
|
16
16
|
/** Complete persistent protocol Layer for an HttpRouter application. */
|
|
17
17
|
declare const layerProtocol: (options: Options) => Layer.Layer<import("@streamsy/core").CommitBoundary | import("@streamsy/core/internal").OwnerRecords | import("effect/reactivity/Reactivity").Reactivity | import("effect/sql/SqlClient").SqlClient | import("@streamsy/core").Storage | import("@streamsy/core/protocol").StreamsReader | import("@streamsy/core/protocol").StreamsWriter, StorageFault, never>;
|
|
18
18
|
declare function layer<const Keys extends ReadonlyArray<Streamsy.Any>>(group: Streamsy.Group<Keys>, options: Options): Layer.Layer<Layer.Success<ReturnType<typeof storageLayer>> | Streamsy.Host<Streamsy.Group<Keys>>, Layer.Error<ReturnType<typeof storageLayer>>>;
|
|
19
|
-
//#endregion
|
|
20
19
|
export { bun_storage_d_exports as BunStorage };
|
package/dist/bun.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
2
|
-
import { a as storageLayer$1, d as retryWithPolicy, f as transactionRetryPolicy, o as inspectFormat, s as newerVersionFault, u as sharedSqlClientLayer } from "./storage-
|
|
3
|
-
import { Cause, Effect, FileSystem, Layer, Option, Path, Result, Schema } from "effect";
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.js";
|
|
2
|
+
import { a as storageLayer$1, d as retryWithPolicy, f as transactionRetryPolicy, o as inspectFormat, s as newerVersionFault, u as sharedSqlClientLayer } from "./storage-DFCZf1UK.js";
|
|
3
|
+
import { Array, Cause, Effect, FileSystem, Layer, Option, Path, Result, Schema } from "effect";
|
|
4
4
|
import { StorageFault } from "@streamsy/core";
|
|
5
5
|
import { Protocol } from "@streamsy/core/protocol";
|
|
6
6
|
import * as BunFileSystem from "@effect/platform-bun/BunFileSystem";
|
|
7
7
|
import * as BunPath from "@effect/platform-bun/BunPath";
|
|
8
8
|
import { SqliteClient } from "@effect/sql-sqlite-bun";
|
|
9
9
|
import { Database } from "bun:sqlite";
|
|
10
|
-
|
|
10
|
+
// src/sqlite-file.ts
|
|
11
11
|
const openFault$1 = (message) => new StorageFault({
|
|
12
12
|
operation: "migration.open",
|
|
13
13
|
message,
|
|
@@ -16,7 +16,7 @@ const openFault$1 = (message) => new StorageFault({
|
|
|
16
16
|
const decodeTableNames = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })));
|
|
17
17
|
const decodeVersions = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ version: Schema.NullOr(Schema.Finite) })));
|
|
18
18
|
const readFormat = (reader) => {
|
|
19
|
-
const names = new Set(decodeTableNames(reader.all("SELECT name FROM sqlite_master WHERE type='table'"))
|
|
19
|
+
const names = new Set(Array.map(decodeTableNames(reader.all("SELECT name FROM sqlite_master WHERE type='table'")), ({ name }) => name));
|
|
20
20
|
return {
|
|
21
21
|
names,
|
|
22
22
|
version: names.has("streamsy_schema") ? decodeVersions(reader.all(`SELECT MAX(version) version FROM streamsy_schema`)).at(0)?.version ?? 0 : 0
|
|
@@ -54,8 +54,7 @@ const prepare = (driver, options, retry) => Effect.gen(function* () {
|
|
|
54
54
|
create: options.create ?? true
|
|
55
55
|
}, ["PRAGMA busy_timeout=0", "PRAGMA journal_mode=WAL"]));
|
|
56
56
|
});
|
|
57
|
-
|
|
58
|
-
//#region src/bun-storage.ts
|
|
57
|
+
// src/bun-storage.ts
|
|
59
58
|
var bun_storage_exports = /* @__PURE__ */ __exportAll({
|
|
60
59
|
layer: () => layer,
|
|
61
60
|
layerProtocol: () => layerProtocol
|
|
@@ -105,5 +104,4 @@ function layer(group, options) {
|
|
|
105
104
|
const protocol = Protocol.layer(options).pipe(Layer.provideMerge(storage));
|
|
106
105
|
return Protocol.bind(protocol, group).pipe(Layer.provideMerge(storage));
|
|
107
106
|
}
|
|
108
|
-
//#endregion
|
|
109
107
|
export { bun_storage_exports as BunStorage };
|
package/dist/durable-object.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as SqlStorageOptions } from "./storage-
|
|
1
|
+
import { r as SqlStorageOptions } from "./storage-Cb_kOO_g.js";
|
|
2
2
|
import { Layer } from "effect";
|
|
3
3
|
import { Streamsy } from "@streamsy/core";
|
|
4
4
|
import { Protocol } from "@streamsy/core/protocol";
|
|
@@ -20,5 +20,4 @@ declare const storageLayer: (options: DurableObjectStorageOptions) => Layer.Laye
|
|
|
20
20
|
/** Protocol over the official Durable Object SQLite layer. */
|
|
21
21
|
declare const layerProtocol: (options: Options) => Layer.Layer<import("@streamsy/core").CommitBoundary | import("@streamsy/core/internal").OwnerRecords | import("effect/reactivity/Reactivity").Reactivity | import("effect/sql/SqlClient").SqlClient | import("@streamsy/core").Storage | import("@streamsy/core/protocol").StreamsReader | import("@streamsy/core/protocol").StreamsWriter, import("@streamsy/core").StorageFault, never>;
|
|
22
22
|
declare function layer<const Keys extends ReadonlyArray<Streamsy.Any>>(group: Streamsy.Group<Keys>, options: Options): Layer.Layer<Layer.Success<ReturnType<typeof storageLayer>> | Streamsy.Host<Streamsy.Group<Keys>>, Layer.Error<ReturnType<typeof storageLayer>>>;
|
|
23
|
-
//#endregion
|
|
24
23
|
export { durable_object_storage_d_exports as DurableObjectStorage };
|
package/dist/durable-object.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
2
|
-
import { i as layerWithDurableObjectTransactions, u as sharedSqlClientLayer } from "./storage-
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.js";
|
|
2
|
+
import { i as layerWithDurableObjectTransactions, u as sharedSqlClientLayer } from "./storage-DFCZf1UK.js";
|
|
3
3
|
import { Layer } from "effect";
|
|
4
4
|
import "@streamsy/core";
|
|
5
5
|
import { Protocol } from "@streamsy/core/protocol";
|
|
6
6
|
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
7
|
-
|
|
7
|
+
// src/durable-object-storage.ts
|
|
8
8
|
var durable_object_storage_exports = /* @__PURE__ */ __exportAll({
|
|
9
9
|
DEFAULT_LONG_POLL_TIMEOUT_MS: () => DEFAULT_LONG_POLL_TIMEOUT_MS,
|
|
10
10
|
layer: () => layer,
|
|
@@ -29,5 +29,4 @@ function layer(group, options) {
|
|
|
29
29
|
}).pipe(Layer.provideMerge(storage));
|
|
30
30
|
return Protocol.bind(protocol, group).pipe(Layer.provideMerge(storage));
|
|
31
31
|
}
|
|
32
|
-
//#endregion
|
|
33
32
|
export { durable_object_storage_exports as DurableObjectStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { a as layerProtocol, c as DEFAULT_TRANSACTION_RETRY_DELAY_MS, i as layer, n as Options, o as clientLayer, s as DEFAULT_TRANSACTION_RETRY_ATTEMPTS, t as DEFAULT_REPAIR_INTERVAL_MS } from "./storage-
|
|
1
|
+
import { a as layerProtocol, c as DEFAULT_TRANSACTION_RETRY_DELAY_MS, i as layer, n as Options, o as clientLayer, s as DEFAULT_TRANSACTION_RETRY_ATTEMPTS, t as DEFAULT_REPAIR_INTERVAL_MS } from "./storage-Cb_kOO_g.js";
|
|
2
2
|
declare namespace sql_storage_d_exports {
|
|
3
3
|
export { DEFAULT_REPAIR_INTERVAL_MS, DEFAULT_TRANSACTION_RETRY_ATTEMPTS, DEFAULT_TRANSACTION_RETRY_DELAY_MS, Options, clientLayer, layer, layerProtocol };
|
|
4
4
|
}
|
|
5
|
-
//#endregion
|
|
6
5
|
export { sql_storage_d_exports as SqlStorage };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
2
|
-
import { l as clientLayer, n as layer, r as layerProtocol, t as DEFAULT_REPAIR_INTERVAL_MS } from "./storage-
|
|
3
|
-
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D8jQpDG7.js";
|
|
2
|
+
import { l as clientLayer, n as layer, r as layerProtocol, t as DEFAULT_REPAIR_INTERVAL_MS } from "./storage-DFCZf1UK.js";
|
|
3
|
+
// src/sql-storage.ts
|
|
4
4
|
var sql_storage_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
DEFAULT_REPAIR_INTERVAL_MS: () => DEFAULT_REPAIR_INTERVAL_MS,
|
|
6
6
|
DEFAULT_TRANSACTION_RETRY_ATTEMPTS: () => 16,
|
|
@@ -9,5 +9,4 @@ var sql_storage_exports = /* @__PURE__ */ __exportAll({
|
|
|
9
9
|
layer: () => layer,
|
|
10
10
|
layerProtocol: () => layerProtocol
|
|
11
11
|
});
|
|
12
|
-
//#endregion
|
|
13
12
|
export { sql_storage_exports as SqlStorage };
|
|
@@ -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,15 +1,11 @@
|
|
|
1
|
-
import { Context, Effect, Layer, Queue, Scope, Stream } from "effect";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import "effect/sql/SqlError";
|
|
1
|
+
import { Context, Effect, Layer, PubSub, Queue, Scope, Stream } from "effect";
|
|
2
|
+
import { Reactivity } from "effect/reactivity";
|
|
3
|
+
import { SqlClient, SqlError } from "effect/sql";
|
|
5
4
|
import { OwnerRecords } from "@streamsy/core/internal";
|
|
6
|
-
import { CommitBoundary, Storage, StorageFault, Streamsy } from "@streamsy/core";
|
|
5
|
+
import { CommitBoundary, Storage, StorageFault, StreamId, Streamsy } from "@streamsy/core";
|
|
7
6
|
import { Protocol } from "@streamsy/core/protocol";
|
|
8
|
-
//#region src/transaction-retry.d.ts
|
|
9
7
|
declare const DEFAULT_TRANSACTION_RETRY_ATTEMPTS = 16;
|
|
10
8
|
declare const DEFAULT_TRANSACTION_RETRY_DELAY_MS = 25;
|
|
11
|
-
//#endregion
|
|
12
|
-
//#region src/boundary.d.ts
|
|
13
9
|
/**
|
|
14
10
|
* Build a `SqlClient` and provide it with the `Reactivity` it captured, so the
|
|
15
11
|
* client and the storage boundary share one change graph.
|
|
@@ -21,8 +17,6 @@ declare const DEFAULT_TRANSACTION_RETRY_DELAY_MS = 25;
|
|
|
21
17
|
* ```
|
|
22
18
|
*/
|
|
23
19
|
declare const clientLayer: <A extends SqlClient.SqlClient, E>(makeClient: Effect.Effect<A, E, Scope.Scope | Reactivity.Reactivity>) => Layer.Layer<SqlClient.SqlClient | Reactivity.Reactivity, E>;
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/storage.d.ts
|
|
26
20
|
declare const DEFAULT_REPAIR_INTERVAL_MS = 1000;
|
|
27
21
|
interface SqlStorageOptions {
|
|
28
22
|
/** Cross-process and post-commit-interruption staleness bound. */
|
|
@@ -39,5 +33,4 @@ interface Options extends SqlStorageOptions, Protocol.ProtocolOptions {}
|
|
|
39
33
|
/** Protocol over SQL storage. */
|
|
40
34
|
declare const layerProtocol: (options?: Options) => Layer.Layer<CommitBoundary | OwnerRecords | Storage | import("@streamsy/core/protocol").StreamsReader | import("@streamsy/core/protocol").StreamsWriter, StorageFault, Reactivity.Reactivity | SqlClient.SqlClient>;
|
|
41
35
|
declare function layer<const Keys extends ReadonlyArray<Streamsy.Any>>(group: Streamsy.Group<Keys>, options?: Options): Layer.Layer<Layer.Success<ReturnType<typeof storageLayer>> | Streamsy.Host<Streamsy.Group<Keys>>, Layer.Error<ReturnType<typeof storageLayer>>, Layer.Services<ReturnType<typeof storageLayer>>>;
|
|
42
|
-
//#endregion
|
|
43
36
|
export { layerProtocol as a, DEFAULT_TRANSACTION_RETRY_DELAY_MS as c, layer as i, Options as n, clientLayer as o, SqlStorageOptions as r, DEFAULT_TRANSACTION_RETRY_ATTEMPTS as s, DEFAULT_REPAIR_INTERVAL_MS as t };
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Clock, Context, Deferred, Effect, Fiber, Layer, Option, Predicate, Queue, Schedule, Schema, Stream } from "effect";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import * as SqlClient from "effect/sql/SqlClient";
|
|
5
|
-
import { isSqlError } from "effect/sql/SqlError";
|
|
1
|
+
import { Array, Clock, Context, Deferred, Effect, Fiber, Layer, Option, Order, Predicate, PubSub, Queue, Schedule, Scheduler, Schema, Stream } from "effect";
|
|
2
|
+
import { Reactivity } from "effect/reactivity";
|
|
3
|
+
import { SqlClient, SqlError } from "effect/sql";
|
|
6
4
|
import { OwnerRecords, attachOwner, ownerOf } from "@streamsy/core/internal";
|
|
7
5
|
import { CommitBoundary, MutationRejected, Mutations, Offset, OperationResults, Storage, StorageFault, StreamId, ZERO_OFFSET } from "@streamsy/core";
|
|
8
6
|
import { Protocol } from "@streamsy/core/protocol";
|
|
@@ -22,8 +20,7 @@ const retryWithPolicy = (transaction, policy, isRetryable) => transaction.pipe(E
|
|
|
22
20
|
schedule: Schedule.spaced(policy.delayMs),
|
|
23
21
|
while: isRetryable
|
|
24
22
|
}));
|
|
25
|
-
|
|
26
|
-
//#region src/boundary.ts
|
|
23
|
+
// src/boundary.ts
|
|
27
24
|
var PendingInvalidations = class extends Context.Service()("@streamsy/storage/PendingInvalidations") {};
|
|
28
25
|
const noop = () => {};
|
|
29
26
|
var BoundaryRuntimeService = class extends Context.Service()("@streamsy/storage/BoundaryRuntime") {};
|
|
@@ -44,6 +41,7 @@ const makeBoundary = (repairIntervalMs, transactionRetry, probe, transactionMaxO
|
|
|
44
41
|
const sql = yield* SqlClient.SqlClient;
|
|
45
42
|
const reactivity = yield* Reactivity.Reactivity;
|
|
46
43
|
const subscriptions = /* @__PURE__ */ new Set();
|
|
44
|
+
const feed = yield* Effect.acquireRelease(PubSub.unbounded(), PubSub.shutdown);
|
|
47
45
|
let closed = false;
|
|
48
46
|
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
|
49
47
|
const active = yield* Effect.sync(() => {
|
|
@@ -55,17 +53,20 @@ const makeBoundary = (repairIntervalMs, transactionRetry, probe, transactionMaxO
|
|
|
55
53
|
}));
|
|
56
54
|
const ownedTransaction = (body) => Effect.suspend(() => {
|
|
57
55
|
if (probe !== void 0) probe.transactionAttempts += 1;
|
|
58
|
-
const pending =
|
|
56
|
+
const pending = {
|
|
57
|
+
keys: /* @__PURE__ */ new Set(),
|
|
58
|
+
ids: /* @__PURE__ */ new Set()
|
|
59
|
+
};
|
|
59
60
|
let bodyCompleted = false;
|
|
60
61
|
const transactionBody = body.pipe(Effect.provideService(PendingInvalidations, pending), Effect.tap(() => Effect.sync(() => {
|
|
61
62
|
bodyCompleted = true;
|
|
62
63
|
})));
|
|
63
|
-
const transaction = sql.withTransaction(transactionBody).pipe(Effect.catchIf((error) => bodyCompleted && isSqlError(error), (error) => Effect.die(error)));
|
|
64
|
+
const transaction = sql.withTransaction(transactionBody).pipe(Effect.catchIf((error) => bodyCompleted && SqlError.isSqlError(error), (error) => Effect.die(error)));
|
|
64
65
|
return (transactionMaxOpsBeforeYield === void 0 ? transaction : transaction.pipe(Effect.provideService(Scheduler.MaxOpsBeforeYield, transactionMaxOpsBeforeYield))).pipe(Effect.tap(() => {
|
|
65
|
-
if (pending.size === 0) return Effect.void;
|
|
66
|
+
if (pending.keys.size === 0) return Effect.void;
|
|
66
67
|
if (probe !== void 0) probe.invalidations += 1;
|
|
67
|
-
return reactivity.invalidate([...pending]);
|
|
68
|
-
}));
|
|
68
|
+
return reactivity.invalidate([...pending.keys]);
|
|
69
|
+
}), Effect.tap(() => pending.ids.size === 0 ? Effect.void : PubSub.publish(feed, [...pending.ids])));
|
|
69
70
|
});
|
|
70
71
|
const withTransaction = (body) => Effect.flatMap(Effect.serviceOption(PendingInvalidations), (owned) => {
|
|
71
72
|
if (Option.isSome(owned)) return body;
|
|
@@ -74,15 +75,16 @@ const makeBoundary = (repairIntervalMs, transactionRetry, probe, transactionMaxO
|
|
|
74
75
|
return ownedTransaction(body);
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
|
-
const mutation = ({ keys, effect, retryable }) => Effect.flatMap(Effect.serviceOption(sql.transactionService), (ambient) => Effect.flatMap(Effect.serviceOption(PendingInvalidations), (pendingBefore) => {
|
|
78
|
+
const mutation = ({ keys, ids, effect, retryable }) => Effect.flatMap(Effect.serviceOption(sql.transactionService), (ambient) => Effect.flatMap(Effect.serviceOption(PendingInvalidations), (pendingBefore) => {
|
|
78
79
|
if (Option.isSome(ambient) && Option.isNone(pendingBefore)) return Effect.die(/* @__PURE__ */ new Error("Ambient SQL transaction is not owned by CommitBoundary"));
|
|
79
80
|
const run = Effect.tap(effect, () => Effect.flatMap(Effect.serviceOption(PendingInvalidations), (pending) => {
|
|
80
81
|
if (Option.isNone(pending)) return Effect.die(/* @__PURE__ */ new Error("CommitBoundary owner context disappeared"));
|
|
81
82
|
return Effect.sync(() => {
|
|
82
|
-
for (const key of keys) pending.value.add(key);
|
|
83
|
+
for (const key of keys) pending.value.keys.add(key);
|
|
84
|
+
for (const id of ids) pending.value.ids.add(id);
|
|
83
85
|
});
|
|
84
86
|
}));
|
|
85
|
-
return Option.isSome(ambient) ? run : retryWithPolicy(ownedTransaction(run), transactionRetry, (error) => isSqlError(error) ? error.isRetryable : retryable(error));
|
|
87
|
+
return Option.isSome(ambient) ? run : retryWithPolicy(ownedTransaction(run), transactionRetry, (error) => SqlError.isSqlError(error) ? error.isRetryable : retryable(error));
|
|
86
88
|
})).pipe(Effect.onExit(() => Effect.sync(() => {
|
|
87
89
|
if (probe !== void 0) probe.completedMutations += 1;
|
|
88
90
|
})));
|
|
@@ -137,12 +139,12 @@ const makeBoundary = (repairIntervalMs, transactionRetry, probe, transactionMaxO
|
|
|
137
139
|
return {
|
|
138
140
|
withTransaction,
|
|
139
141
|
mutation,
|
|
140
|
-
changes
|
|
142
|
+
changes,
|
|
143
|
+
committed: PubSub.subscribe(feed)
|
|
141
144
|
};
|
|
142
145
|
});
|
|
143
146
|
const boundaryLayer = (repairIntervalMs, transactionRetry, probe, transactionMaxOpsBeforeYield) => Layer.effect(BoundaryRuntimeService, makeBoundary(repairIntervalMs, transactionRetry, probe, transactionMaxOpsBeforeYield));
|
|
144
|
-
|
|
145
|
-
//#region src/dialect.ts
|
|
147
|
+
// src/dialect.ts
|
|
146
148
|
/** The client's dialect. Other dialects fail with a non-retryable fault. */
|
|
147
149
|
const storageDialect = (sql) => sql.onDialectOrElse({
|
|
148
150
|
sqlite: () => Effect.succeed("sqlite"),
|
|
@@ -154,8 +156,7 @@ const storageDialect = (sql) => sql.onDialectOrElse({
|
|
|
154
156
|
retryable: false
|
|
155
157
|
}))
|
|
156
158
|
});
|
|
157
|
-
|
|
158
|
-
//#region src/schema.ts
|
|
159
|
+
// src/schema.ts
|
|
159
160
|
const STORAGE_VERSION_TABLE = "streamsy_schema";
|
|
160
161
|
/** Version tables of earlier formats. A database that holds one must not be migrated. */
|
|
161
162
|
const LEGACY_VERSION_TABLES = ["streamsy_schema_version", "streamsy_storage_schema_version"];
|
|
@@ -258,8 +259,7 @@ const STORAGE_MIGRATIONS = [{
|
|
|
258
259
|
) ENGINE=InnoDB`
|
|
259
260
|
]
|
|
260
261
|
}];
|
|
261
|
-
|
|
262
|
-
//#region src/migrations.ts
|
|
262
|
+
// src/migrations.ts
|
|
263
263
|
/** Fixed `pg_advisory_xact_lock` key: "streamsy" in ASCII, read as a 64-bit integer. */
|
|
264
264
|
const PG_MIGRATION_LOCK_KEY = "8319400191411975033";
|
|
265
265
|
/** Named lock that serialises MySQL migrations, whose DDL cannot share one transaction. */
|
|
@@ -275,7 +275,7 @@ const sqlAttempt = (operation, effect) => effect.pipe(Effect.mapError((error) =>
|
|
|
275
275
|
/** Return whether the database already holds this format, or fail on an older format. */
|
|
276
276
|
const inspectFormat = (tables) => {
|
|
277
277
|
const ownsFormat = tables.has(STORAGE_VERSION_TABLE);
|
|
278
|
-
return
|
|
278
|
+
return Array.some(LEGACY_VERSION_TABLES, (name) => tables.has(name)) || !ownsFormat && Array.some(STORAGE_TABLES, (name) => tables.has(name)) ? Effect.fail(new StorageFault({
|
|
279
279
|
operation: "migration.open",
|
|
280
280
|
message: "Unsupported older Streamsy storage format; use a fresh database path (the database was not modified)",
|
|
281
281
|
retryable: false
|
|
@@ -292,7 +292,7 @@ const listTables = (sql, dialect) => sqlAttempt("migration.inspect", {
|
|
|
292
292
|
WHERE table_schema = current_schema()`,
|
|
293
293
|
mysql: () => sql`SELECT table_name AS name FROM information_schema.tables
|
|
294
294
|
WHERE table_schema = DATABASE()`
|
|
295
|
-
}[dialect]()).pipe(Effect.map((rows) => new Set(
|
|
295
|
+
}[dialect]()).pipe(Effect.map((rows) => new Set(Array.map(rows, ({ name }) => name))));
|
|
296
296
|
const migrateVersions = (sql, dialect, hooks) => Effect.gen(function* () {
|
|
297
297
|
if (!(yield* inspectFormat(yield* listTables(sql, dialect)))) yield* sqlAttempt("migration.create-version-table", sql.unsafe(STORAGE_VERSION_TABLE_DDL[dialect]).pipe(Effect.asVoid));
|
|
298
298
|
const current = yield* sqlAttempt("migration.read-version", sql`SELECT MAX(version) AS version FROM ${sql(STORAGE_VERSION_TABLE)}`.pipe(Effect.map((rows) => Number(rows[0]?.version ?? 0))));
|
|
@@ -332,9 +332,8 @@ const migrate = (sql, hooks = {}, transactionRetry = transactionRetryPolicy({}))
|
|
|
332
332
|
hooks.onTransactionAttempt?.();
|
|
333
333
|
if (dialect === "mysql") return migrateMysql(sql, hooks);
|
|
334
334
|
return sql.withTransaction(dialect === "pg" ? sqlAttempt("migration.lock", sql`SELECT pg_advisory_xact_lock(${sql.literal(PG_MIGRATION_LOCK_KEY)})`).pipe(Effect.andThen(migrateVersions(sql, dialect, hooks))) : migrateVersions(sql, dialect, hooks));
|
|
335
|
-
}), transactionRetry, (error) => isSqlError(error) ? error.isRetryable : error.retryable).pipe(Effect.catchIf(isSqlError, (error) => Effect.fail(fault("migration.transaction", error)))));
|
|
336
|
-
|
|
337
|
-
//#region src/storage.ts
|
|
335
|
+
}), transactionRetry, (error) => SqlError.isSqlError(error) ? error.isRetryable : error.retryable).pipe(Effect.catchIf(SqlError.isSqlError, (error) => Effect.fail(fault("migration.transaction", error)))));
|
|
336
|
+
// src/storage.ts
|
|
338
337
|
const DEFAULT_REPAIR_INTERVAL_MS = 1e3;
|
|
339
338
|
const STORAGE_KEY = "streamsy:storage";
|
|
340
339
|
const RecordRow = Schema.Struct({
|
|
@@ -467,7 +466,7 @@ const oversizedKey = (mutation) => {
|
|
|
467
466
|
* Sorted ids give every writer one lock order. SQLite already has one writer.
|
|
468
467
|
*/
|
|
469
468
|
const lockStreams = (sql, ids) => {
|
|
470
|
-
const sorted =
|
|
469
|
+
const sorted = Array.sort(new Set(ids), Order.String);
|
|
471
470
|
return sql.onDialectOrElse({
|
|
472
471
|
sqlite: () => Effect.void,
|
|
473
472
|
orElse: () => execute("mutate.lock", sql`SELECT stream_id FROM streamsy_streams WHERE stream_id IN ${sql.in(sorted)}
|
|
@@ -493,7 +492,7 @@ const lockingRead = (sql, lock) => sql.onDialectOrElse({
|
|
|
493
492
|
* mutation. Outside a transaction the read stays plain and takes no lock.
|
|
494
493
|
*/
|
|
495
494
|
const planningLock = (sql) => Effect.map(Effect.serviceOption(sql.transactionService), (open) => Option.isSome(open) ? "update" : void 0);
|
|
496
|
-
const lockIds = (mutation) => mutation.operations
|
|
495
|
+
const lockIds = (mutation) => Array.flatMap(mutation.operations, (operation) => Predicate.isTagged(operation, "Create") ? operation.forkSource === void 0 ? [operation.record.id] : [operation.record.id, operation.forkSource.id] : [operation.streamId]);
|
|
497
496
|
const recordColumns = (sql) => sql.literal("stream_id,content_type,ttl_seconds,expires_at,created_at,current_offset,last_seq,closed,closed_at,forked_from,fork_offset,fork_sub_offset,soft_deleted,expires_at_ms");
|
|
498
497
|
const readRecord = (sql, id, lock) => attempt("record", sql`SELECT ${recordColumns(sql)} FROM streamsy_streams WHERE stream_id = ${id}${lockingRead(sql, lock)}`).pipe(Effect.flatMap((rows) => {
|
|
499
498
|
const row = rows[0];
|
|
@@ -689,7 +688,7 @@ const readMessages = (sql, id, window) => Effect.gen(function* () {
|
|
|
689
688
|
until = record.value.lifecycle.forkOffset;
|
|
690
689
|
}
|
|
691
690
|
const messages = [];
|
|
692
|
-
for (const segment of
|
|
691
|
+
for (const segment of Array.reverse(chain)) {
|
|
693
692
|
if (remaining === 0) break;
|
|
694
693
|
const upper = segment.until === void 0 ? window.until : window.until === void 0 || segment.until < window.until ? segment.until : window.until;
|
|
695
694
|
const predicates = [sql`stream_id = ${segment.id}`];
|
|
@@ -724,20 +723,22 @@ const makeStorage = (sql, boundary) => Storage.of({
|
|
|
724
723
|
messages: Effect.fn("SqlStorage.messages")((id, window) => readMessages(sql, id, window)),
|
|
725
724
|
producer: Effect.fn("SqlStorage.producer")((id, producerId) => Effect.flatMap(planningLock(sql), (lock) => readProducer(sql, "producer", id, producerId, lock))),
|
|
726
725
|
mutate: Effect.fn("SqlStorage.mutate")((mutation) => {
|
|
727
|
-
const ids = mutation.operations
|
|
726
|
+
const ids = Array.map(mutation.operations, (operation) => Predicate.isTagged(operation, "Create") ? operation.record.id : operation.streamId);
|
|
728
727
|
if (ids.length === 0 || new Set(ids).size !== ids.length) return Effect.die(/* @__PURE__ */ new Error("Mutation requires distinct streams within atomicScope"));
|
|
729
728
|
const oversized = oversizedKey(mutation);
|
|
730
729
|
if (Option.isSome(oversized)) return Effect.fail(oversized.value);
|
|
731
730
|
return boundary.mutation({
|
|
732
731
|
keys: [STORAGE_KEY],
|
|
732
|
+
ids,
|
|
733
733
|
effect: applyMutation(sql, mutation),
|
|
734
734
|
retryable: (error) => Predicate.isTagged(error, "StorageFault") && error.retryable
|
|
735
|
-
}).pipe(Effect.catchIf(isSqlError, (error) => Effect.fail(sqlFault("mutate.transaction", error))));
|
|
735
|
+
}).pipe(Effect.catchIf(SqlError.isSqlError, (error) => Effect.fail(sqlFault("mutate.transaction", error))));
|
|
736
736
|
}),
|
|
737
737
|
changes: (id) => boundary.changes({
|
|
738
738
|
keys: [STORAGE_KEY],
|
|
739
739
|
read: snapshot(sql, id)
|
|
740
740
|
}),
|
|
741
|
+
committed: boundary.committed,
|
|
741
742
|
nextExpiry: attempt("nextExpiry", sql`SELECT expires_at_ms AS at, stream_id FROM streamsy_streams
|
|
742
743
|
WHERE expires_at_ms IS NOT NULL AND soft_deleted = 0
|
|
743
744
|
ORDER BY expires_at_ms, stream_id LIMIT 1`).pipe(Effect.flatMap((rows) => rows[0] === void 0 ? Effect.succeed(Option.none()) : decodeExpiryRow(rows[0]).pipe(Effect.map(({ at, stream_id: streamId }) => Option.some({
|
|
@@ -834,5 +835,4 @@ function layer(group, options = {}) {
|
|
|
834
835
|
const protocol = Protocol.layer(options).pipe(Layer.provideMerge(storage));
|
|
835
836
|
return Protocol.bind(protocol, group).pipe(Layer.provideMerge(storage));
|
|
836
837
|
}
|
|
837
|
-
//#endregion
|
|
838
838
|
export { storageLayer as a, STORAGE_VERSION_TABLE as c, retryWithPolicy as d, transactionRetryPolicy as f, layerWithDurableObjectTransactions as i, clientLayer as l, layer as n, inspectFormat as o, layerProtocol as r, newerVersionFault as s, DEFAULT_REPAIR_INTERVAL_MS as t, sharedSqlClientLayer as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@streamsy/storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Effect SQL storage for Streamsy on SQLite, Postgres and MySQL, with Bun and Durable Object SQLite host layers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"test:dialects": "bun --bun vitest run test/dialects"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@streamsy/core": "^0.
|
|
45
|
+
"@streamsy/core": "^0.6.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@cloudflare/workers-types": "4.20251228.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@effect/sql-sqlite-bun": "4.0.5",
|
|
53
53
|
"@effect/sql-sqlite-do": "4.0.5",
|
|
54
54
|
"@effect/vitest": "4.0.5",
|
|
55
|
-
"@streamsy/serve": "0.
|
|
55
|
+
"@streamsy/serve": "0.6.0",
|
|
56
56
|
"@types/bun": "^1.4.3",
|
|
57
57
|
"effect": "4.0.5",
|
|
58
58
|
"miniflare": "4.20260730.0",
|