@shivaedev/sql 0.26.0 → 0.27.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/CHANGELOG.md +16 -0
- package/README.md +2 -2
- package/dist/mapSqlError.js +17 -3
- package/dist/mapSqlError.js.map +1 -1
- package/dist/repositoryChanges.d.ts.map +1 -1
- package/dist/repositoryChanges.js +4 -14
- package/dist/repositoryChanges.js.map +1 -1
- package/dist/transact.d.ts.map +1 -1
- package/dist/transact.js +3 -7
- package/dist/transact.js.map +1 -1
- package/package.json +5 -5
- package/src/mapSqlError.ts +19 -3
- package/src/repositoryChanges.ts +4 -20
- package/src/transact.ts +3 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.27.0 - 2026-10-30
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `changes/keys.ts` exports `recordKey` for raw record dependencies and `compositeId` for composite identities, including bigint components encoded as decimal strings.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- SQL repository writes and manual invalidations build record keys through `changes/keys.ts`, sharing the format used by subscriptions. Existing key strings remain unchanged.
|
|
12
|
+
|
|
13
|
+
## 0.26.1 - 2026-10-30
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- `mapSqlError` maps constraints whose declared names exceed PostgreSQL's 63-byte identifier limit. It matches the name PostgreSQL stores, clipped to 63 bytes at a character boundary, instead of sending those violations to the fallback.
|
|
18
|
+
|
|
3
19
|
## 0.26.0 - 2026-10-30
|
|
4
20
|
|
|
5
21
|
### Added
|
package/README.md
CHANGED
|
@@ -313,7 +313,7 @@ const ingest = Effect.gen(function* () {
|
|
|
313
313
|
});
|
|
314
314
|
```
|
|
315
315
|
|
|
316
|
-
The application creates `readings` with `PRIMARY KEY (source, sequence)`; `sequence` and `amount` are text columns for these codecs. Composite keys contain at least two model fields. `findById` and `delete` take an object containing every key field and encode each through its field schema. `update` and `updateVoid` use the full key from the model's update input and leave the key columns unchanged. Missing key fields fail with `SchemaError` before SQL runs. A scalar `idColumn` keeps native CRUD signatures. Writes announce a composite row as the table name, a colon and a JSON array of the returned key values in `idColumn` order, such as `readings:["meter","1"]`; a PostgreSQL `bigint` key value appears as its decimal string.
|
|
316
|
+
The application creates `readings` with `PRIMARY KEY (source, sequence)`; `sequence` and `amount` are text columns for these codecs. Composite keys contain at least two model fields. `findById` and `delete` take an object containing every key field and encode each through its field schema. `update` and `updateVoid` use the full key from the model's update input and leave the key columns unchanged. Missing key fields fail with `SchemaError` before SQL runs. A scalar `idColumn` keeps native CRUD signatures. Writes announce a composite row as the table name, a colon and a JSON array of the returned key values in `idColumn` order, such as `readings:["meter","1"]`; a PostgreSQL `bigint` key value appears as its decimal string. Build matching subscription ids with `compositeId` from `@shivaedev/changes/keys.ts`, using stored values in that same column order: `collection("readings", Schema.String).item(compositeId(["meter", "1"]))`. Manual invalidations use the same id: `invalidateOnCommit({ readings: [compositeId(["meter", "1"])] })`.
|
|
317
317
|
|
|
318
318
|
`conflictColumns` is a nonempty tuple of inserted model fields backed by a database unique constraint or primary key. It can differ from the primary key, including a unique external key beside a generated ID. Declaring it adds these methods:
|
|
319
319
|
|
|
@@ -634,7 +634,7 @@ const onSqlError = mapSqlError({
|
|
|
634
634
|
const insertNamedLine = lines.insert({ amount: 52, name: "Consulting", note: null }).pipe(transact({ onSqlError }));
|
|
635
635
|
```
|
|
636
636
|
|
|
637
|
-
The mapper handles named unique violations and PostgreSQL foreign-key and check violations. Every other `SqlError`, including unknown constraints, goes to `fallback`. Each callback receives the original error. The inferred error type contains exactly the mapped errors and the fallback; `transact` also preserves other typed failures from the operation. Constraint names belong to the application's migration; error types and presentation belong to the application.
|
|
637
|
+
The mapper handles named unique violations and PostgreSQL foreign-key and check violations. Every other `SqlError`, including unknown constraints, goes to `fallback`. A constraint name longer than PostgreSQL's 63-byte identifier limit matches the clipped name PostgreSQL stores and reports. Each callback receives the original error. The inferred error type contains exactly the mapped errors and the fallback; `transact` also preserves other typed failures from the operation. Constraint names belong to the application's migration; error types and presentation belong to the application.
|
|
638
638
|
|
|
639
639
|
Recover outside `transact` so earlier writes roll back before handling a mapped domain error. A losing unique-key insert can then catch `NameTaken` and read the winner's row without a private transaction error. See the [constraint-failures guide](https://github.com/ShivaeDev/platform/blob/main/docs/framework/constraint-errors.md).
|
|
640
640
|
|
package/dist/mapSqlError.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export function mapSqlError(options) {
|
|
2
|
+
const handlers = new Map(Object.entries(options.constraints).map(([name, handler]) => [storedIdentifier(name), handler]));
|
|
2
3
|
return (error) => {
|
|
3
4
|
const reason = error.reason;
|
|
4
5
|
let constraint;
|
|
@@ -14,9 +15,22 @@ export function mapSqlError(options) {
|
|
|
14
15
|
&& typeof reason.cause.constraint === "string") {
|
|
15
16
|
constraint = reason.cause.constraint;
|
|
16
17
|
}
|
|
17
|
-
return constraint
|
|
18
|
-
? (options.constraints[constraint] ?? options.fallback)(error)
|
|
19
|
-
: options.fallback(error);
|
|
18
|
+
return ((constraint === undefined ? undefined : handlers.get(constraint)) ?? options.fallback)(error);
|
|
20
19
|
};
|
|
21
20
|
}
|
|
21
|
+
const IDENTIFIER_BYTES = 63;
|
|
22
|
+
const encoder = new TextEncoder();
|
|
23
|
+
// PostgreSQL stores identifiers clipped to 63 bytes at a character boundary, and reports that clipped name.
|
|
24
|
+
function storedIdentifier(name) {
|
|
25
|
+
let bytes = 0;
|
|
26
|
+
let stored = "";
|
|
27
|
+
for (const character of name) {
|
|
28
|
+
bytes += encoder.encode(character).length;
|
|
29
|
+
if (bytes > IDENTIFIER_BYTES) {
|
|
30
|
+
return stored;
|
|
31
|
+
}
|
|
32
|
+
stored += character;
|
|
33
|
+
}
|
|
34
|
+
return stored;
|
|
35
|
+
}
|
|
22
36
|
//# sourceMappingURL=mapSqlError.js.map
|
package/dist/mapSqlError.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mapSqlError.js","sourceRoot":"","sources":["../src/mapSqlError.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,WAAW,CAAC,OAG3B;IACA,OAAO,CAAC,KAAe,EAAE,EAAE;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5B,IAAI,UAA8B,CAAC;QACnC,IAAI,MAAM,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACvC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QAChC,CAAC;aAAM,IACN,MAAM,CAAC,IAAI,KAAK,iBAAiB;eAC9B,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;eAChC,MAAM,CAAC,KAAK,KAAK,IAAI;eACrB,MAAM,IAAI,MAAM,CAAC,KAAK;eACtB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;eAChE,YAAY,IAAI,MAAM,CAAC,KAAK;eAC5B,OAAO,MAAM,CAAC,KAAK,CAAC,UAAU,KAAK,QAAQ,EAC7C,CAAC;YACF,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC;QACtC,CAAC;QACD,OAAO,UAAU,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"mapSqlError.js","sourceRoot":"","sources":["../src/mapSqlError.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,WAAW,CAAC,OAG3B;IACA,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1H,OAAO,CAAC,KAAe,EAAE,EAAE;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5B,IAAI,UAA8B,CAAC;QACnC,IAAI,MAAM,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACvC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QAChC,CAAC;aAAM,IACN,MAAM,CAAC,IAAI,KAAK,iBAAiB;eAC9B,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;eAChC,MAAM,CAAC,KAAK,KAAK,IAAI;eACrB,MAAM,IAAI,MAAM,CAAC,KAAK;eACtB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;eAChE,YAAY,IAAI,MAAM,CAAC,KAAK;eAC5B,OAAO,MAAM,CAAC,KAAK,CAAC,UAAU,KAAK,QAAQ,EAC7C,CAAC;YACF,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC;QACtC,CAAC;QACD,OAAO,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC;IACvG,CAAC,CAAC;AACH,CAAC;AAED,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAC5B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,4GAA4G;AAC5G,SAAS,gBAAgB,CAAC,IAAY;IACrC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,SAAS,IAAI,IAAI,EAAE,CAAC;QAC9B,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAC1C,IAAI,KAAK,GAAG,gBAAgB,EAAE,CAAC;YAC9B,OAAO,MAAM,CAAC;QACf,CAAC;QACD,MAAM,IAAI,SAAS,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repositoryChanges.d.ts","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"repositoryChanges.d.ts","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAMvC,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC;AAErF,eAAO,MAAM,iBAAiB;;;YAuDd,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBAIrD,CAAC"}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import { declaredBuckets } from "@shivaedev/changes/buckets.ts";
|
|
5
|
-
import { invalidationKeys } from "@shivaedev/changes/keys.ts";
|
|
5
|
+
import { compositeId, invalidationKeys, recordKey } from "@shivaedev/changes/keys.ts";
|
|
6
6
|
import { ChangeDeclarations } from "#ChangeDeclarations.ts";
|
|
7
7
|
import { invalidateOnCommit, transact } from "#transact.ts";
|
|
8
|
-
function encodeKeyValue(_key, value) {
|
|
9
|
-
return typeof value === "bigint" ? String(value) : value;
|
|
10
|
-
}
|
|
11
|
-
function recordId(value) {
|
|
12
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
13
|
-
? String(value)
|
|
14
|
-
: Hash.hash(value);
|
|
15
|
-
}
|
|
16
8
|
export const repositoryChanges = Effect.fnUntraced(function* (sql, table, columns, fields = {}) {
|
|
17
9
|
const buckets = declaredBuckets(fields);
|
|
18
10
|
const bucketKeys = Effect.fnUntraced(function* (row) {
|
|
@@ -45,10 +37,8 @@ export const repositoryChanges = Effect.fnUntraced(function* (sql, table, column
|
|
|
45
37
|
}
|
|
46
38
|
const keys = new Set([table]);
|
|
47
39
|
for (const row of rows) {
|
|
48
|
-
const id = columns.length === 1
|
|
49
|
-
|
|
50
|
-
: JSON.stringify(columns.map((column) => row[column]), encodeKeyValue);
|
|
51
|
-
keys.add(`${table}:${recordId(id)}`);
|
|
40
|
+
const id = columns.length === 1 ? row[columns[0] ?? ""] : compositeId(columns.map((column) => row[column]));
|
|
41
|
+
keys.add(recordKey(table, id));
|
|
52
42
|
for (const key of yield* bucketKeys(row)) {
|
|
53
43
|
keys.add(key);
|
|
54
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repositoryChanges.js","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"repositoryChanges.js","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"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"}
|
package/dist/transact.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transact.d.ts","sourceRoot":"","sources":["../src/transact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"transact.d.ts","sourceRoot":"","sources":["../src/transact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,MAAM,gBAAgB,GAAG,SAAS,OAAO,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC;AA2B/F,eAAO,MAAM,cAAc,+FAAyB,CAAC;AACrD,eAAO,MAAM,YAAY,0JAA6B,CAAC;AACvD,eAAO,MAAM,aAAa,uJAA8B,CAAC;AAEzD,eAAO,MAAM,kBAAkB,qGAE7B,CAAC;AAEH,MAAM,WAAW,eAAe,CAAC,MAAM;IACtC,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,CAAC;CACjD;AAED,eAAO,MAAM,QAAQ,EAAE;IACtB,CAAC,MAAM,EACN,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,GAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACjJ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC9B,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;CASpG,CAAC"}
|
package/dist/transact.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
import { Effect
|
|
1
|
+
import { Effect } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
3
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
4
4
|
import { SqlClient } from "effect/sql";
|
|
5
5
|
import { isSqlError } from "effect/sql/SqlError";
|
|
6
6
|
import { makeChannel } from "@shivaedev/changes/channel.ts";
|
|
7
|
-
|
|
8
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
9
|
-
? String(value)
|
|
10
|
-
: Hash.hash(value);
|
|
11
|
-
}
|
|
7
|
+
import { recordKey } from "@shivaedev/changes/keys.ts";
|
|
12
8
|
function isList(keys) {
|
|
13
9
|
return Array.isArray(keys);
|
|
14
10
|
}
|
|
15
11
|
function flatten(keys) {
|
|
16
|
-
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) =>
|
|
12
|
+
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) => recordKey(name, id))]);
|
|
17
13
|
}
|
|
18
14
|
const guard = Effect.fn("Transact.guard")(function* () {
|
|
19
15
|
const sql = yield* SqlClient.SqlClient;
|
package/dist/transact.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transact.js","sourceRoot":"","sources":["../src/transact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"transact.js","sourceRoot":"","sources":["../src/transact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEvC,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,UAAU,EAAiB,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAIvD,SAAS,MAAM,CAAC,IAAsB;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,OAAO,CAAC,IAAsB;IACtC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7H,CAAC;AAED,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC;IAClD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CACvB,IAAI,KAAK,CAAC,iHAAiH,CAAC,CAC5H,CAAC;IACH,CAAC;AACF,CAAC,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,WAAW,CAAiE;IACjG,IAAI,EAAE,yBAAyB;IAC/B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACvE,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;IAClH,OAAO,EAAE,KAAK,EAAE;CAChB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC,QAAQ,CAAC;AACrD,MAAM,CAAC,MAAM,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;AACvD,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC,aAAa,CAAC;AAEzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,EAAE,CAAC,6BAA6B,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAsB;IAC3G,KAAK,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5C,CAAC,CAAC,CAAC;AAMH,MAAM,CAAC,MAAM,QAAQ,GAQjB,IAAI,CACP,CAAC,EACD,MAAM,CAAC,EAAE,CAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,EAAmB,MAA8B,EAAE,OAAgC;IAC1H,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,OAAO,KAAK,CAAC,CAAC,aAAa;SACzB,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC;SACnC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACvF,CAAC,CAAC,CACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Schema-derived repositories over native Effect SQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -43,16 +43,16 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shivaedev/changes": "0.
|
|
46
|
+
"@shivaedev/changes": "0.6.0",
|
|
47
47
|
"@shivaedev/service": "0.6.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@effect/platform-node": "4.0.6",
|
|
51
51
|
"@effect/sql-pg": "4.0.6",
|
|
52
52
|
"@effect/sql-sqlite-node": "4.0.6",
|
|
53
|
-
"@shivaedev/quality": "0.
|
|
54
|
-
"@shivaedev/test-sql": "0.
|
|
55
|
-
"@shivaedev/test-story": "0.
|
|
53
|
+
"@shivaedev/quality": "0.48.0",
|
|
54
|
+
"@shivaedev/test-sql": "0.15.0",
|
|
55
|
+
"@shivaedev/test-story": "0.26.2",
|
|
56
56
|
"@types/node": "24.19.2",
|
|
57
57
|
"effect": "4.0.6",
|
|
58
58
|
"typescript": "7.0.2",
|
package/src/mapSqlError.ts
CHANGED
|
@@ -8,6 +8,7 @@ export function mapSqlError(options: {
|
|
|
8
8
|
readonly constraints: Readonly<Record<string, (error: SqlError) => unknown>>;
|
|
9
9
|
readonly fallback: (error: SqlError) => unknown;
|
|
10
10
|
}) {
|
|
11
|
+
const handlers = new Map(Object.entries(options.constraints).map(([name, handler]) => [storedIdentifier(name), handler]));
|
|
11
12
|
return (error: SqlError) => {
|
|
12
13
|
const reason = error.reason;
|
|
13
14
|
let constraint: string | undefined;
|
|
@@ -24,8 +25,23 @@ export function mapSqlError(options: {
|
|
|
24
25
|
) {
|
|
25
26
|
constraint = reason.cause.constraint;
|
|
26
27
|
}
|
|
27
|
-
return constraint
|
|
28
|
-
? (options.constraints[constraint] ?? options.fallback)(error)
|
|
29
|
-
: options.fallback(error);
|
|
28
|
+
return ((constraint === undefined ? undefined : handlers.get(constraint)) ?? options.fallback)(error);
|
|
30
29
|
};
|
|
31
30
|
}
|
|
31
|
+
|
|
32
|
+
const IDENTIFIER_BYTES = 63;
|
|
33
|
+
const encoder = new TextEncoder();
|
|
34
|
+
|
|
35
|
+
// PostgreSQL stores identifiers clipped to 63 bytes at a character boundary, and reports that clipped name.
|
|
36
|
+
function storedIdentifier(name: string) {
|
|
37
|
+
let bytes = 0;
|
|
38
|
+
let stored = "";
|
|
39
|
+
for (const character of name) {
|
|
40
|
+
bytes += encoder.encode(character).length;
|
|
41
|
+
if (bytes > IDENTIFIER_BYTES) {
|
|
42
|
+
return stored;
|
|
43
|
+
}
|
|
44
|
+
stored += character;
|
|
45
|
+
}
|
|
46
|
+
return stored;
|
|
47
|
+
}
|
package/src/repositoryChanges.ts
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Option, type Schema } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import { declaredBuckets } from "@shivaedev/changes/buckets.ts";
|
|
5
|
-
import { invalidationKeys, type Key } from "@shivaedev/changes/keys.ts";
|
|
5
|
+
import { compositeId, invalidationKeys, type Key, recordKey } from "@shivaedev/changes/keys.ts";
|
|
6
6
|
import { ChangeDeclarations } from "#ChangeDeclarations.ts";
|
|
7
7
|
import { invalidateOnCommit, transact } from "#transact.ts";
|
|
8
8
|
|
|
9
|
-
function encodeKeyValue(_key: string, value: unknown) {
|
|
10
|
-
return typeof value === "bigint" ? String(value) : value;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function recordId(value: unknown) {
|
|
14
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
15
|
-
? String(value)
|
|
16
|
-
: Hash.hash(value);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
9
|
export type RepositoryChanges = Effect.Success<ReturnType<typeof repositoryChanges>>;
|
|
20
10
|
|
|
21
11
|
export const repositoryChanges = Effect.fnUntraced(function* (
|
|
@@ -63,14 +53,8 @@ export const repositoryChanges = Effect.fnUntraced(function* (
|
|
|
63
53
|
}
|
|
64
54
|
const keys = new Set<string>([table]);
|
|
65
55
|
for (const row of rows) {
|
|
66
|
-
const id =
|
|
67
|
-
|
|
68
|
-
? row[columns[0] ?? ""]
|
|
69
|
-
: JSON.stringify(
|
|
70
|
-
columns.map((column) => row[column]),
|
|
71
|
-
encodeKeyValue,
|
|
72
|
-
);
|
|
73
|
-
keys.add(`${table}:${recordId(id)}`);
|
|
56
|
+
const id = columns.length === 1 ? row[columns[0] ?? ""] : compositeId(columns.map((column) => row[column]));
|
|
57
|
+
keys.add(recordKey(table, id));
|
|
74
58
|
for (const key of yield* bucketKeys(row)) {
|
|
75
59
|
keys.add(key);
|
|
76
60
|
}
|
package/src/transact.ts
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
import { Effect
|
|
1
|
+
import { Effect } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
3
|
import type { ReadonlyRecord } from "effect/Record";
|
|
4
4
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
5
5
|
import { SqlClient } from "effect/sql";
|
|
6
6
|
import { isSqlError, type SqlError } from "effect/sql/SqlError";
|
|
7
7
|
import { makeChannel } from "@shivaedev/changes/channel.ts";
|
|
8
|
+
import { recordKey } from "@shivaedev/changes/keys.ts";
|
|
8
9
|
|
|
9
10
|
export type InvalidationKeys = readonly unknown[] | ReadonlyRecord<string, readonly unknown[]>;
|
|
10
11
|
|
|
11
|
-
function hashOf(value: unknown): unknown {
|
|
12
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
13
|
-
? String(value)
|
|
14
|
-
: Hash.hash(value);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
function isList(keys: InvalidationKeys): keys is readonly unknown[] {
|
|
18
13
|
return Array.isArray(keys);
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
function flatten(keys: InvalidationKeys): readonly unknown[] {
|
|
22
|
-
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) =>
|
|
17
|
+
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) => recordKey(name, id))]);
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
const guard = Effect.fn("Transact.guard")(function* () {
|