@storic/cloudflare 0.1.0 → 0.2.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/persistence.d.ts +5 -2
- package/dist/persistence.d.ts.map +1 -1
- package/dist/persistence.js +5 -145
- package/dist/persistence.js.map +1 -1
- package/dist/storic-object.d.ts.map +1 -1
- package/dist/storic-object.js.map +1 -1
- package/package.json +1 -1
- package/src/persistence.ts +6 -244
- package/src/storic-object.ts +1 -1
package/dist/persistence.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as Layer from "effect/Layer";
|
|
2
|
-
import { Persistence } from "@storic/core";
|
|
2
|
+
import { Persistence, PersistenceError } from "@storic/core";
|
|
3
3
|
/**
|
|
4
4
|
* Persistence implementation backed by Cloudflare Durable Object's SqlStorage.
|
|
5
5
|
*
|
|
6
|
+
* This is a thin wrapper that provides the DO's `SqlStorage` as the `SqlClient`
|
|
7
|
+
* dependency for the shared SQL persistence implementation from `@storic/sql`.
|
|
8
|
+
*
|
|
6
9
|
* @example
|
|
7
10
|
* ```ts
|
|
8
11
|
* import { doStoragePersistence } from "@storic/cloudflare";
|
|
@@ -11,5 +14,5 @@ import { Persistence } from "@storic/core";
|
|
|
11
14
|
* const storeLayer = Store.layer(config).pipe(Layer.provide(persistenceLayer));
|
|
12
15
|
* ```
|
|
13
16
|
*/
|
|
14
|
-
export declare const doStoragePersistence: (storage: SqlStorage) => Layer.Layer<Persistence,
|
|
17
|
+
export declare const doStoragePersistence: (storage: SqlStorage) => Layer.Layer<Persistence, PersistenceError, never>;
|
|
15
18
|
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAI7D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,4EAGkC,CAAC"}
|
package/dist/persistence.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
1
|
import * as Layer from "effect/Layer";
|
|
3
|
-
import {
|
|
4
|
-
import { compileFilters } from "@storic/sql";
|
|
2
|
+
import { sqlPersistenceLayer } from "@storic/sql";
|
|
5
3
|
import { sqlStorageLayer } from "./sql-storage-client.js";
|
|
6
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
7
|
-
/**
|
|
8
|
-
* Parse a database row into a StoredRecord.
|
|
9
|
-
*/
|
|
10
|
-
function rowToStoredRecord(row) {
|
|
11
|
-
return {
|
|
12
|
-
id: row.id,
|
|
13
|
-
type: row.type,
|
|
14
|
-
data: JSON.parse(row.data),
|
|
15
|
-
created_at: row.created_at,
|
|
16
|
-
updated_at: row.updated_at,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
4
|
/**
|
|
20
5
|
* Persistence implementation backed by Cloudflare Durable Object's SqlStorage.
|
|
21
6
|
*
|
|
7
|
+
* This is a thin wrapper that provides the DO's `SqlStorage` as the `SqlClient`
|
|
8
|
+
* dependency for the shared SQL persistence implementation from `@storic/sql`.
|
|
9
|
+
*
|
|
22
10
|
* @example
|
|
23
11
|
* ```ts
|
|
24
12
|
* import { doStoragePersistence } from "@storic/cloudflare";
|
|
@@ -27,133 +15,5 @@ function rowToStoredRecord(row) {
|
|
|
27
15
|
* const storeLayer = Store.layer(config).pipe(Layer.provide(persistenceLayer));
|
|
28
16
|
* ```
|
|
29
17
|
*/
|
|
30
|
-
export const doStoragePersistence = (storage) =>
|
|
31
|
-
const sqlLayer = sqlStorageLayer(storage);
|
|
32
|
-
return Layer.effect(Persistence, Effect.gen(function* () {
|
|
33
|
-
const sql = yield* SqlClient;
|
|
34
|
-
const initialize = (spec) => Effect.gen(function* () {
|
|
35
|
-
yield* sql.unsafe(`
|
|
36
|
-
CREATE TABLE IF NOT EXISTS entities (
|
|
37
|
-
id TEXT PRIMARY KEY,
|
|
38
|
-
type TEXT NOT NULL,
|
|
39
|
-
data JSON NOT NULL,
|
|
40
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
41
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
42
|
-
)
|
|
43
|
-
`);
|
|
44
|
-
yield* sql.unsafe(`
|
|
45
|
-
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type)
|
|
46
|
-
`);
|
|
47
|
-
const expectedIndexes = new Set();
|
|
48
|
-
for (const idx of spec.indexes) {
|
|
49
|
-
expectedIndexes.add(idx.name);
|
|
50
|
-
yield* sql.unsafe(`CREATE INDEX IF NOT EXISTS "${idx.name}" ` +
|
|
51
|
-
`ON entities(json_extract(data, '$.${idx.fieldPath}')) ` +
|
|
52
|
-
`WHERE type = '${idx.typeDiscriminator}'`);
|
|
53
|
-
}
|
|
54
|
-
const existingIndexes = yield* sql `SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'entities' AND name LIKE 'idx_%'`;
|
|
55
|
-
for (const { name } of existingIndexes) {
|
|
56
|
-
if (name !== "idx_entities_type" && !expectedIndexes.has(name)) {
|
|
57
|
-
yield* sql.unsafe(`DROP INDEX "${name}"`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}).pipe(Effect.mapError((error) => new PersistenceError({
|
|
61
|
-
message: `Initialization failed: ${error}`,
|
|
62
|
-
cause: error,
|
|
63
|
-
})));
|
|
64
|
-
const put = (record) => Effect.gen(function* () {
|
|
65
|
-
yield* sql `
|
|
66
|
-
INSERT INTO entities (id, type, data)
|
|
67
|
-
VALUES (${record.id}, ${record.type}, ${JSON.stringify(record.data)})
|
|
68
|
-
`;
|
|
69
|
-
const rows = yield* sql `SELECT * FROM entities WHERE id = ${record.id}`;
|
|
70
|
-
return rowToStoredRecord(rows[0]);
|
|
71
|
-
}).pipe(Effect.mapError((error) => new PersistenceError({
|
|
72
|
-
message: `Put failed for ${record.id}: ${error}`,
|
|
73
|
-
cause: error,
|
|
74
|
-
})));
|
|
75
|
-
const get = (id) => Effect.gen(function* () {
|
|
76
|
-
const rows = yield* sql `SELECT * FROM entities WHERE id = ${id}`;
|
|
77
|
-
if (rows.length === 0)
|
|
78
|
-
return null;
|
|
79
|
-
return rowToStoredRecord(rows[0]);
|
|
80
|
-
}).pipe(Effect.mapError((error) => new PersistenceError({
|
|
81
|
-
message: `Get failed for ${id}: ${error}`,
|
|
82
|
-
cause: error,
|
|
83
|
-
})));
|
|
84
|
-
const query = (params) => Effect.gen(function* () {
|
|
85
|
-
const compiled = compileFilters(params.filters);
|
|
86
|
-
let whereClause = `type IN (${params.types.map(() => "?").join(", ")})`;
|
|
87
|
-
const bindValues = [...params.types];
|
|
88
|
-
if (compiled) {
|
|
89
|
-
whereClause += ` AND ${compiled.sql}`;
|
|
90
|
-
bindValues.push(...compiled.values);
|
|
91
|
-
}
|
|
92
|
-
let stmt = `SELECT * FROM entities WHERE ${whereClause} ORDER BY created_at DESC`;
|
|
93
|
-
if (params.limit != null) {
|
|
94
|
-
stmt += ` LIMIT ${params.limit}`;
|
|
95
|
-
}
|
|
96
|
-
if (params.offset != null) {
|
|
97
|
-
stmt += ` OFFSET ${params.offset}`;
|
|
98
|
-
}
|
|
99
|
-
const rows = yield* sql.unsafe(stmt, bindValues);
|
|
100
|
-
return rows.map(rowToStoredRecord);
|
|
101
|
-
}).pipe(Effect.mapError((error) => new PersistenceError({
|
|
102
|
-
message: `Query failed: ${error}`,
|
|
103
|
-
cause: error,
|
|
104
|
-
})));
|
|
105
|
-
const update = (id, record) => Effect.gen(function* () {
|
|
106
|
-
yield* sql `
|
|
107
|
-
UPDATE entities
|
|
108
|
-
SET type = ${record.type},
|
|
109
|
-
data = ${JSON.stringify(record.data)},
|
|
110
|
-
updated_at = unixepoch()
|
|
111
|
-
WHERE id = ${id}
|
|
112
|
-
`;
|
|
113
|
-
const rows = yield* sql `SELECT * FROM entities WHERE id = ${id}`;
|
|
114
|
-
return rowToStoredRecord(rows[0]);
|
|
115
|
-
}).pipe(Effect.mapError((error) => new PersistenceError({
|
|
116
|
-
message: `Update failed for ${id}: ${error}`,
|
|
117
|
-
cause: error,
|
|
118
|
-
})));
|
|
119
|
-
const patch = (params) => Effect.gen(function* () {
|
|
120
|
-
if (params.patches.length === 0)
|
|
121
|
-
return 0;
|
|
122
|
-
let totalAffected = 0;
|
|
123
|
-
for (const entry of params.patches) {
|
|
124
|
-
const compiled = compileFilters(entry.filters);
|
|
125
|
-
let whereClause = `type = ?`;
|
|
126
|
-
const bindValues = [entry.type];
|
|
127
|
-
if (compiled) {
|
|
128
|
-
whereClause += ` AND ${compiled.sql}`;
|
|
129
|
-
bindValues.push(...compiled.values);
|
|
130
|
-
}
|
|
131
|
-
const result = yield* sql.unsafe(`UPDATE entities ` +
|
|
132
|
-
`SET data = json_patch(data, ?), updated_at = unixepoch() ` +
|
|
133
|
-
`WHERE ${whereClause} ` +
|
|
134
|
-
`RETURNING id`, [JSON.stringify(entry.patch), ...bindValues]);
|
|
135
|
-
totalAffected += result.length;
|
|
136
|
-
}
|
|
137
|
-
return totalAffected;
|
|
138
|
-
}).pipe(Effect.mapError((error) => new PersistenceError({
|
|
139
|
-
message: `Patch failed: ${error}`,
|
|
140
|
-
cause: error,
|
|
141
|
-
})));
|
|
142
|
-
const remove = (id) => Effect.gen(function* () {
|
|
143
|
-
yield* sql `DELETE FROM entities WHERE id = ${id}`;
|
|
144
|
-
}).pipe(Effect.mapError((error) => new PersistenceError({
|
|
145
|
-
message: `Remove failed for ${id}: ${error}`,
|
|
146
|
-
cause: error,
|
|
147
|
-
})));
|
|
148
|
-
return Persistence.of({
|
|
149
|
-
initialize,
|
|
150
|
-
put,
|
|
151
|
-
get,
|
|
152
|
-
query,
|
|
153
|
-
update,
|
|
154
|
-
patch,
|
|
155
|
-
remove,
|
|
156
|
-
});
|
|
157
|
-
})).pipe(Layer.provide(sqlLayer));
|
|
158
|
-
};
|
|
18
|
+
export const doStoragePersistence = (storage) => sqlPersistenceLayer.pipe(Layer.provide(sqlStorageLayer(storage)));
|
|
159
19
|
//# sourceMappingURL=persistence.js.map
|
package/dist/persistence.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,OAAmB,EACyB,EAAE,CAC9C,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storic-object.d.ts","sourceRoot":"","sources":["../src/storic-object.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"storic-object.d.ts","sourceRoot":"","sources":["../src/storic-object.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,8BAAsB,QAAQ,CAC5B,GAAG,GAAG,OAAO,EACb,KAAK,GAAG,EAAE,CACV,SAAQ,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC;IACjC;;;OAGG;IACH,QAAQ,KAAK,MAAM,IAAI,WAAW,CAAC;IAEnC,OAAO,CAAC,QAAQ,CAA+C;IAE/D,YAAY,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,EAkB5C;IAED;;OAEG;IACH,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAElE;CACF;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,iBAAW,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storic-object.js","sourceRoot":"","sources":["../src/storic-object.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"storic-object.js","sourceRoot":"","sources":["../src/storic-object.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,cAAc,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAErC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,OAAgB,QAGpB,SAAQ,aAAyB;IAOzB,QAAQ,CAA+C;IAE/D,YAAY,GAAuB,EAAE,GAAQ;QAC3C,KAAK,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAEhB,IAAI,CAAC,GAAG,CAAC,qBAAqB,CAAC,KAAK,IAAI,EAAE;YACxC,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAEpE,8DAA8D;YAC9D,gDAAgD;YAChD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAC9C,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAC/B,KAAK,CAAC,KAAK,CACZ,CAAC;YAEF,IAAI,CAAC,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAEhD,oDAAoD;YACpD,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACO,GAAG,CAAO,MAAkC;QACpD,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC"}
|
package/package.json
CHANGED
package/src/persistence.ts
CHANGED
|
@@ -1,33 +1,14 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
1
|
import * as Layer from "effect/Layer";
|
|
3
2
|
import { Persistence, PersistenceError } from "@storic/core";
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
6
|
-
InitSpec,
|
|
7
|
-
PatchParams,
|
|
8
|
-
PersistenceRecord,
|
|
9
|
-
QueryParams,
|
|
10
|
-
StoredRecord,
|
|
11
|
-
} from "@storic/core";
|
|
3
|
+
import { sqlPersistenceLayer } from "@storic/sql";
|
|
12
4
|
import { sqlStorageLayer } from "./sql-storage-client.ts";
|
|
13
|
-
import { SqlClient } from "effect/unstable/sql/SqlClient";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Parse a database row into a StoredRecord.
|
|
17
|
-
*/
|
|
18
|
-
function rowToStoredRecord(row: Record<string, unknown>): StoredRecord {
|
|
19
|
-
return {
|
|
20
|
-
id: row.id as string,
|
|
21
|
-
type: row.type as string,
|
|
22
|
-
data: JSON.parse(row.data as string) as Record<string, unknown>,
|
|
23
|
-
created_at: row.created_at as number,
|
|
24
|
-
updated_at: row.updated_at as number,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
5
|
|
|
28
6
|
/**
|
|
29
7
|
* Persistence implementation backed by Cloudflare Durable Object's SqlStorage.
|
|
30
8
|
*
|
|
9
|
+
* This is a thin wrapper that provides the DO's `SqlStorage` as the `SqlClient`
|
|
10
|
+
* dependency for the shared SQL persistence implementation from `@storic/sql`.
|
|
11
|
+
*
|
|
31
12
|
* @example
|
|
32
13
|
* ```ts
|
|
33
14
|
* import { doStoragePersistence } from "@storic/cloudflare";
|
|
@@ -38,224 +19,5 @@ function rowToStoredRecord(row: Record<string, unknown>): StoredRecord {
|
|
|
38
19
|
*/
|
|
39
20
|
export const doStoragePersistence = (
|
|
40
21
|
storage: SqlStorage,
|
|
41
|
-
): Layer.Layer<Persistence> =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return Layer.effect(
|
|
45
|
-
Persistence,
|
|
46
|
-
Effect.gen(function* () {
|
|
47
|
-
const sql = yield* SqlClient;
|
|
48
|
-
|
|
49
|
-
const initialize = (spec: InitSpec) =>
|
|
50
|
-
Effect.gen(function* () {
|
|
51
|
-
yield* sql.unsafe(`
|
|
52
|
-
CREATE TABLE IF NOT EXISTS entities (
|
|
53
|
-
id TEXT PRIMARY KEY,
|
|
54
|
-
type TEXT NOT NULL,
|
|
55
|
-
data JSON NOT NULL,
|
|
56
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
57
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
58
|
-
)
|
|
59
|
-
`);
|
|
60
|
-
yield* sql.unsafe(`
|
|
61
|
-
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type)
|
|
62
|
-
`);
|
|
63
|
-
|
|
64
|
-
const expectedIndexes = new Set<string>();
|
|
65
|
-
|
|
66
|
-
for (const idx of spec.indexes) {
|
|
67
|
-
expectedIndexes.add(idx.name);
|
|
68
|
-
yield* sql.unsafe(
|
|
69
|
-
`CREATE INDEX IF NOT EXISTS "${idx.name}" ` +
|
|
70
|
-
`ON entities(json_extract(data, '$.${idx.fieldPath}')) ` +
|
|
71
|
-
`WHERE type = '${idx.typeDiscriminator}'`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const existingIndexes = yield* sql<{
|
|
76
|
-
name: string;
|
|
77
|
-
}>`SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'entities' AND name LIKE 'idx_%'`;
|
|
78
|
-
|
|
79
|
-
for (const { name } of existingIndexes) {
|
|
80
|
-
if (name !== "idx_entities_type" && !expectedIndexes.has(name)) {
|
|
81
|
-
yield* sql.unsafe(`DROP INDEX "${name}"`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}).pipe(
|
|
85
|
-
Effect.mapError(
|
|
86
|
-
(error) =>
|
|
87
|
-
new PersistenceError({
|
|
88
|
-
message: `Initialization failed: ${error}`,
|
|
89
|
-
cause: error,
|
|
90
|
-
}),
|
|
91
|
-
),
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const put = (record: PersistenceRecord) =>
|
|
95
|
-
Effect.gen(function* () {
|
|
96
|
-
yield* sql`
|
|
97
|
-
INSERT INTO entities (id, type, data)
|
|
98
|
-
VALUES (${record.id}, ${record.type}, ${JSON.stringify(record.data)})
|
|
99
|
-
`;
|
|
100
|
-
|
|
101
|
-
const rows = yield* sql<
|
|
102
|
-
Record<string, unknown>
|
|
103
|
-
>`SELECT * FROM entities WHERE id = ${record.id}`;
|
|
104
|
-
|
|
105
|
-
return rowToStoredRecord(rows[0]);
|
|
106
|
-
}).pipe(
|
|
107
|
-
Effect.mapError(
|
|
108
|
-
(error) =>
|
|
109
|
-
new PersistenceError({
|
|
110
|
-
message: `Put failed for ${record.id}: ${error}`,
|
|
111
|
-
cause: error,
|
|
112
|
-
}),
|
|
113
|
-
),
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const get = (id: string) =>
|
|
117
|
-
Effect.gen(function* () {
|
|
118
|
-
const rows = yield* sql<
|
|
119
|
-
Record<string, unknown>
|
|
120
|
-
>`SELECT * FROM entities WHERE id = ${id}`;
|
|
121
|
-
|
|
122
|
-
if (rows.length === 0) return null;
|
|
123
|
-
return rowToStoredRecord(rows[0]);
|
|
124
|
-
}).pipe(
|
|
125
|
-
Effect.mapError(
|
|
126
|
-
(error) =>
|
|
127
|
-
new PersistenceError({
|
|
128
|
-
message: `Get failed for ${id}: ${error}`,
|
|
129
|
-
cause: error,
|
|
130
|
-
}),
|
|
131
|
-
),
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const query = (params: QueryParams) =>
|
|
135
|
-
Effect.gen(function* () {
|
|
136
|
-
const compiled = compileFilters(params.filters);
|
|
137
|
-
|
|
138
|
-
let whereClause = `type IN (${params.types.map(() => "?").join(", ")})`;
|
|
139
|
-
const bindValues: unknown[] = [...params.types];
|
|
140
|
-
|
|
141
|
-
if (compiled) {
|
|
142
|
-
whereClause += ` AND ${compiled.sql}`;
|
|
143
|
-
bindValues.push(...compiled.values);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let stmt = `SELECT * FROM entities WHERE ${whereClause} ORDER BY created_at DESC`;
|
|
147
|
-
|
|
148
|
-
if (params.limit != null) {
|
|
149
|
-
stmt += ` LIMIT ${params.limit}`;
|
|
150
|
-
}
|
|
151
|
-
if (params.offset != null) {
|
|
152
|
-
stmt += ` OFFSET ${params.offset}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const rows = yield* sql.unsafe<Record<string, unknown>>(
|
|
156
|
-
stmt,
|
|
157
|
-
bindValues,
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
return rows.map(rowToStoredRecord);
|
|
161
|
-
}).pipe(
|
|
162
|
-
Effect.mapError(
|
|
163
|
-
(error) =>
|
|
164
|
-
new PersistenceError({
|
|
165
|
-
message: `Query failed: ${error}`,
|
|
166
|
-
cause: error,
|
|
167
|
-
}),
|
|
168
|
-
),
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
const update = (
|
|
172
|
-
id: string,
|
|
173
|
-
record: { readonly type: string; readonly data: Record<string, unknown> },
|
|
174
|
-
) =>
|
|
175
|
-
Effect.gen(function* () {
|
|
176
|
-
yield* sql`
|
|
177
|
-
UPDATE entities
|
|
178
|
-
SET type = ${record.type},
|
|
179
|
-
data = ${JSON.stringify(record.data)},
|
|
180
|
-
updated_at = unixepoch()
|
|
181
|
-
WHERE id = ${id}
|
|
182
|
-
`;
|
|
183
|
-
|
|
184
|
-
const rows = yield* sql<
|
|
185
|
-
Record<string, unknown>
|
|
186
|
-
>`SELECT * FROM entities WHERE id = ${id}`;
|
|
187
|
-
|
|
188
|
-
return rowToStoredRecord(rows[0]);
|
|
189
|
-
}).pipe(
|
|
190
|
-
Effect.mapError(
|
|
191
|
-
(error) =>
|
|
192
|
-
new PersistenceError({
|
|
193
|
-
message: `Update failed for ${id}: ${error}`,
|
|
194
|
-
cause: error,
|
|
195
|
-
}),
|
|
196
|
-
),
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
const patch = (params: PatchParams) =>
|
|
200
|
-
Effect.gen(function* () {
|
|
201
|
-
if (params.patches.length === 0) return 0;
|
|
202
|
-
|
|
203
|
-
let totalAffected = 0;
|
|
204
|
-
|
|
205
|
-
for (const entry of params.patches) {
|
|
206
|
-
const compiled = compileFilters(entry.filters);
|
|
207
|
-
|
|
208
|
-
let whereClause = `type = ?`;
|
|
209
|
-
const bindValues: unknown[] = [entry.type];
|
|
210
|
-
|
|
211
|
-
if (compiled) {
|
|
212
|
-
whereClause += ` AND ${compiled.sql}`;
|
|
213
|
-
bindValues.push(...compiled.values);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const result = yield* sql.unsafe<Record<string, unknown>>(
|
|
217
|
-
`UPDATE entities ` +
|
|
218
|
-
`SET data = json_patch(data, ?), updated_at = unixepoch() ` +
|
|
219
|
-
`WHERE ${whereClause} ` +
|
|
220
|
-
`RETURNING id`,
|
|
221
|
-
[JSON.stringify(entry.patch), ...bindValues],
|
|
222
|
-
);
|
|
223
|
-
totalAffected += result.length;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return totalAffected;
|
|
227
|
-
}).pipe(
|
|
228
|
-
Effect.mapError(
|
|
229
|
-
(error) =>
|
|
230
|
-
new PersistenceError({
|
|
231
|
-
message: `Patch failed: ${error}`,
|
|
232
|
-
cause: error,
|
|
233
|
-
}),
|
|
234
|
-
),
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
const remove = (id: string) =>
|
|
238
|
-
Effect.gen(function* () {
|
|
239
|
-
yield* sql`DELETE FROM entities WHERE id = ${id}`;
|
|
240
|
-
}).pipe(
|
|
241
|
-
Effect.mapError(
|
|
242
|
-
(error) =>
|
|
243
|
-
new PersistenceError({
|
|
244
|
-
message: `Remove failed for ${id}: ${error}`,
|
|
245
|
-
cause: error,
|
|
246
|
-
}),
|
|
247
|
-
),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
return Persistence.of({
|
|
251
|
-
initialize,
|
|
252
|
-
put,
|
|
253
|
-
get,
|
|
254
|
-
query,
|
|
255
|
-
update,
|
|
256
|
-
patch,
|
|
257
|
-
remove,
|
|
258
|
-
});
|
|
259
|
-
}),
|
|
260
|
-
).pipe(Layer.provide(sqlLayer));
|
|
261
|
-
};
|
|
22
|
+
): Layer.Layer<Persistence, PersistenceError> =>
|
|
23
|
+
sqlPersistenceLayer.pipe(Layer.provide(sqlStorageLayer(storage)));
|
package/src/storic-object.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { DurableObject } from "cloudflare:workers";
|
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
4
|
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
5
|
-
import { Store
|
|
5
|
+
import { Store } from "@storic/core";
|
|
6
6
|
import type { StoreConfig } from "@storic/core";
|
|
7
7
|
import { doStoragePersistence } from "./persistence.ts";
|
|
8
8
|
|