@shivaedev/sql 0.37.0 → 0.39.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 +24 -0
- package/README.md +117 -2
- package/dist/Model.d.ts +9 -0
- package/dist/Model.d.ts.map +1 -0
- package/dist/Model.js +6 -0
- package/dist/Model.js.map +1 -0
- package/dist/VersionedRepositoryShape.d.ts +6 -0
- package/dist/VersionedRepositoryShape.d.ts.map +1 -0
- package/dist/VersionedRepositoryShape.js +2 -0
- package/dist/VersionedRepositoryShape.js.map +1 -0
- package/dist/WorkerRelease.d.ts +1 -8
- package/dist/WorkerRelease.d.ts.map +1 -1
- package/dist/WorkerRelease.js +4 -29
- package/dist/WorkerRelease.js.map +1 -1
- package/dist/adapters/groupColumns.d.ts +14 -0
- package/dist/adapters/groupColumns.d.ts.map +1 -0
- package/dist/adapters/groupColumns.js +54 -0
- package/dist/adapters/groupColumns.js.map +1 -0
- package/dist/adapters/storageSchema.d.ts +4 -0
- package/dist/adapters/storageSchema.d.ts.map +1 -0
- package/dist/adapters/storageSchema.js +7 -0
- package/dist/adapters/storageSchema.js.map +1 -0
- package/dist/backfillPostgres.d.ts +18 -0
- package/dist/backfillPostgres.d.ts.map +1 -0
- package/dist/backfillPostgres.js +35 -0
- package/dist/backfillPostgres.js.map +1 -0
- package/dist/batchWrites.d.ts.map +1 -1
- package/dist/batchWrites.js +6 -3
- package/dist/batchWrites.js.map +1 -1
- package/dist/compositeRecordKey.d.ts +8 -0
- package/dist/compositeRecordKey.d.ts.map +1 -0
- package/dist/compositeRecordKey.js +15 -0
- package/dist/compositeRecordKey.js.map +1 -0
- package/dist/compositeRepository.d.ts.map +1 -1
- package/dist/compositeRepository.js +4 -1
- package/dist/compositeRepository.js.map +1 -1
- package/dist/conditionalUpsert.d.ts.map +1 -1
- package/dist/conditionalUpsert.js +5 -2
- package/dist/conditionalUpsert.js.map +1 -1
- package/dist/conflictWrites.d.ts.map +1 -1
- package/dist/conflictWrites.js +6 -3
- package/dist/conflictWrites.js.map +1 -1
- package/dist/coordinateWorkerRelease.js +1 -1
- package/dist/coordinateWorkerRelease.js.map +1 -1
- package/dist/initializeWorkerRelease.d.ts +7 -0
- package/dist/initializeWorkerRelease.d.ts.map +1 -0
- package/dist/initializeWorkerRelease.js +7 -0
- package/dist/initializeWorkerRelease.js.map +1 -0
- package/dist/insertFromSelect.d.ts.map +1 -1
- package/dist/insertFromSelect.js +4 -1
- package/dist/insertFromSelect.js.map +1 -1
- package/dist/latestPerGroup.d.ts.map +1 -1
- package/dist/latestPerGroup.js +7 -3
- package/dist/latestPerGroup.js.map +1 -1
- package/dist/makeOrderedChildren.d.ts +26 -0
- package/dist/makeOrderedChildren.d.ts.map +1 -0
- package/dist/makeOrderedChildren.js +38 -0
- package/dist/makeOrderedChildren.js.map +1 -0
- package/dist/makeWorkerRelease.d.ts +8 -0
- package/dist/makeWorkerRelease.d.ts.map +1 -0
- package/dist/makeWorkerRelease.js +12 -0
- package/dist/makeWorkerRelease.js.map +1 -0
- package/dist/orderedChildStatements.d.ts +30 -0
- package/dist/orderedChildStatements.d.ts.map +1 -0
- package/dist/orderedChildStatements.js +61 -0
- package/dist/orderedChildStatements.js.map +1 -0
- package/dist/registerWorkerRelease.d.ts +4 -0
- package/dist/registerWorkerRelease.d.ts.map +1 -0
- package/dist/registerWorkerRelease.js +14 -0
- package/dist/registerWorkerRelease.js.map +1 -0
- package/dist/repository.d.ts +9 -8
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js.map +1 -1
- package/dist/repositoryQueries.d.ts +3 -2
- package/dist/repositoryQueries.d.ts.map +1 -1
- package/dist/repositoryQueries.js +9 -4
- package/dist/repositoryQueries.js.map +1 -1
- package/dist/repositoryUpdate.d.ts +3 -3
- package/dist/repositoryUpdate.d.ts.map +1 -1
- package/dist/repositoryWrites.d.ts.map +1 -1
- package/dist/repositoryWrites.js +21 -6
- package/dist/repositoryWrites.js.map +1 -1
- package/dist/setWorkerReleaseActive.d.ts +4 -0
- package/dist/setWorkerReleaseActive.d.ts.map +1 -0
- package/dist/setWorkerReleaseActive.js +13 -0
- package/dist/setWorkerReleaseActive.js.map +1 -0
- package/dist/sqlNameTransforms.d.ts +5 -0
- package/dist/sqlNameTransforms.d.ts.map +1 -0
- package/dist/sqlNameTransforms.js +8 -0
- package/dist/sqlNameTransforms.js.map +1 -0
- package/dist/upsertResult.d.ts +2 -1
- package/dist/upsertResult.d.ts.map +1 -1
- package/dist/upsertResult.js +3 -2
- package/dist/upsertResult.js.map +1 -1
- package/dist/versionedRepository.d.ts +15 -6
- package/dist/versionedRepository.d.ts.map +1 -1
- package/dist/versionedRepository.js +8 -5
- package/dist/versionedRepository.js.map +1 -1
- package/dist/versionedStatements.d.ts +1 -1
- package/dist/versionedStatements.d.ts.map +1 -1
- package/dist/versionedStatements.js +10 -4
- package/dist/versionedStatements.js.map +1 -1
- package/package.json +15 -15
- package/src/Model.ts +7 -0
- package/src/VersionedRepositoryShape.ts +11 -0
- package/src/WorkerRelease.ts +4 -33
- package/src/adapters/groupColumns.ts +63 -0
- package/src/adapters/storageSchema.ts +10 -0
- package/src/backfillPostgres.ts +47 -0
- package/src/batchWrites.ts +6 -3
- package/src/compositeRecordKey.ts +26 -0
- package/src/compositeRepository.ts +4 -1
- package/src/conditionalUpsert.ts +5 -2
- package/src/conflictWrites.ts +6 -3
- package/src/coordinateWorkerRelease.ts +1 -1
- package/src/initializeWorkerRelease.ts +7 -0
- package/src/insertFromSelect.ts +4 -1
- package/src/latestPerGroup.ts +7 -3
- package/src/makeOrderedChildren.ts +78 -0
- package/src/makeWorkerRelease.ts +12 -0
- package/src/orderedChildStatements.ts +76 -0
- package/src/registerWorkerRelease.ts +15 -0
- package/src/repository.ts +14 -13
- package/src/repositoryQueries.ts +18 -5
- package/src/repositoryUpdate.ts +3 -3
- package/src/repositoryWrites.ts +21 -6
- package/src/setWorkerReleaseActive.ts +17 -0
- package/src/sqlNameTransforms.ts +9 -0
- package/src/upsertResult.ts +4 -2
- package/src/versionedRepository.ts +31 -11
- package/src/versionedStatements.ts +14 -4
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
3
4
|
import { makeConflictClause } from "#makeConflictClause.ts";
|
|
5
|
+
import { selectedUpdate } from "#repositoryUpdate.ts";
|
|
6
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
4
7
|
const Version = Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0));
|
|
5
8
|
export const versionedStatements = Effect.fnUntraced(function* (model, options) {
|
|
6
9
|
const sql = yield* SqlClient.SqlClient;
|
|
10
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
7
11
|
const { tableName, idColumns, versionColumn, conflict } = options;
|
|
8
12
|
const entries = [];
|
|
9
13
|
for (const column of idColumns) {
|
|
@@ -19,8 +23,8 @@ export const versionedStatements = Effect.fnUntraced(function* (model, options)
|
|
|
19
23
|
return yield* Effect.die(new Error(`Unknown version column: ${versionColumn}`));
|
|
20
24
|
}
|
|
21
25
|
const encodeVersion = Schema.encodeEffect(versionSchema);
|
|
22
|
-
const encodeInsert = Schema.encodeEffect(model.insert);
|
|
23
|
-
const encodeUpdate = Schema.encodeEffect(model.update);
|
|
26
|
+
const encodeInsert = Schema.encodeEffect(storageSchema(model.insert, model.fields, nameTransforms));
|
|
27
|
+
const encodeUpdate = Schema.encodeEffect(storageSchema(model.update, model.fields, nameTransforms));
|
|
24
28
|
const record = Schema.decodeUnknownEffect(Schema.Record(Schema.String, Schema.Unknown));
|
|
25
29
|
const validateVersion = Schema.decodeUnknownEffect(Version);
|
|
26
30
|
const clause = options.conflictColumns === undefined ? undefined : yield* makeConflictClause(model, { ...options, conflictColumns: options.conflictColumns });
|
|
@@ -46,9 +50,11 @@ export const versionedStatements = Effect.fnUntraced(function* (model, options)
|
|
|
46
50
|
return sql `insert into ${sql(tableName)} ${sql.insert(encoded)} ${suffix}`;
|
|
47
51
|
});
|
|
48
52
|
}
|
|
49
|
-
function update(input) {
|
|
53
|
+
function update(input, columns) {
|
|
50
54
|
return Effect.gen(function* () {
|
|
51
|
-
const encoded = yield* record(yield*
|
|
55
|
+
const encoded = yield* record(yield* columns === undefined
|
|
56
|
+
? encodeUpdate(input)
|
|
57
|
+
: Effect.flatMap(selectedUpdate(model, columns, [...idColumns, versionColumn], input), (schema) => Schema.encodeEffect(storageSchema(schema, model.fields, nameTransforms))(input)));
|
|
52
58
|
const expectedVersion = yield* validateVersion(encoded[versionColumn]);
|
|
53
59
|
const assignments = Object.entries(encoded).filter(([column]) => !idColumns.includes(column) && column !== versionColumn);
|
|
54
60
|
const set = sql.csv([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"versionedStatements.js","sourceRoot":"","sources":["../src/versionedStatements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"versionedStatements.js","sourceRoot":"","sources":["../src/versionedStatements.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Schema-derived repositories over native Effect SQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -43,27 +43,27 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shivaedev/changes": "0.10.
|
|
47
|
-
"@shivaedev/service": "0.7.
|
|
46
|
+
"@shivaedev/changes": "0.10.1",
|
|
47
|
+
"@shivaedev/service": "0.7.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@effect/platform-node": "4.0.
|
|
51
|
-
"@effect/sql-pg": "4.0.
|
|
52
|
-
"@effect/sql-sqlite-node": "4.0.
|
|
53
|
-
"@shivaedev/contract": "0.36.
|
|
54
|
-
"@shivaedev/jobs": "0.
|
|
55
|
-
"@shivaedev/quality": "0.
|
|
56
|
-
"@shivaedev/test-kits": "0.
|
|
57
|
-
"@shivaedev/test-sql": "0.
|
|
58
|
-
"@shivaedev/test-story": "0.
|
|
50
|
+
"@effect/platform-node": "4.0.7",
|
|
51
|
+
"@effect/sql-pg": "4.0.7",
|
|
52
|
+
"@effect/sql-sqlite-node": "4.0.7",
|
|
53
|
+
"@shivaedev/contract": "0.36.1",
|
|
54
|
+
"@shivaedev/jobs": "0.36.0",
|
|
55
|
+
"@shivaedev/quality": "0.61.1",
|
|
56
|
+
"@shivaedev/test-kits": "0.4.0",
|
|
57
|
+
"@shivaedev/test-sql": "0.21.1",
|
|
58
|
+
"@shivaedev/test-story": "0.39.1",
|
|
59
59
|
"@types/node": "24.19.2",
|
|
60
|
-
"effect": "4.0.
|
|
60
|
+
"effect": "4.0.7",
|
|
61
61
|
"typescript": "7.0.2",
|
|
62
62
|
"vitest": "5.0.5"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@effect/sql-pg": "4.0.
|
|
66
|
-
"effect": "4.0.
|
|
65
|
+
"@effect/sql-pg": "4.0.7",
|
|
66
|
+
"effect": "4.0.7"
|
|
67
67
|
},
|
|
68
68
|
"peerDependenciesMeta": {
|
|
69
69
|
"@effect/sql-pg": {
|
package/src/Model.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
function Group<TFields extends Schema.Struct.Fields>(fields: TFields, options: { readonly prefix: string }) {
|
|
4
|
+
return Schema.Struct(fields).annotate({ "@shivaedev/sql/groupPrefix": options.prefix });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const Model = { Group };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Model } from "effect/schema";
|
|
2
|
+
import type { PrimaryKey, Repository } from "#compositeRepository.ts";
|
|
3
|
+
import type { Queries } from "#repositoryQueries.ts";
|
|
4
|
+
import type { VersionColumn, VersionedWrites } from "#versionedRepository.ts";
|
|
5
|
+
|
|
6
|
+
export type VersionedRepositoryShape<TModel extends Model.Any, TId extends PrimaryKey<TModel>, TVersion extends VersionColumn<TModel, TId>> = Omit<
|
|
7
|
+
Repository<TModel, TId>,
|
|
8
|
+
keyof VersionedWrites<TModel, TId, TVersion>
|
|
9
|
+
> &
|
|
10
|
+
VersionedWrites<TModel, TId, TVersion> &
|
|
11
|
+
Queries<TModel>;
|
package/src/WorkerRelease.ts
CHANGED
|
@@ -1,40 +1,11 @@
|
|
|
1
|
-
import { Effect, Semaphore } from "effect";
|
|
2
1
|
import { defineService } from "@shivaedev/service/defineService.ts";
|
|
3
2
|
import { genericMethod } from "@shivaedev/service/genericMethod.ts";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const mutex = Semaphore.makeUnsafe(1);
|
|
7
|
-
const controls = new Set<(active: boolean) => Effect.Effect<void, unknown>>();
|
|
8
|
-
let active = false;
|
|
9
|
-
const locked = mutex.withPermits(1);
|
|
10
|
-
function register<E>(control: (active: boolean) => Effect.Effect<void, E>) {
|
|
11
|
-
const attach = Effect.gen(function* () {
|
|
12
|
-
controls.add(control);
|
|
13
|
-
yield* control(active);
|
|
14
|
-
});
|
|
15
|
-
const detach = Effect.sync(() => {
|
|
16
|
-
controls.delete(control);
|
|
17
|
-
});
|
|
18
|
-
return Effect.acquireRelease(locked(attach), () => locked(detach));
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
service: { register },
|
|
22
|
-
setActive: (next: boolean) =>
|
|
23
|
-
locked(
|
|
24
|
-
Effect.gen(function* () {
|
|
25
|
-
if (next === active) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
active = next;
|
|
29
|
-
yield* Effect.forEach(controls, (control) => control(active), { concurrency: "unbounded", discard: true });
|
|
30
|
-
}),
|
|
31
|
-
).pipe(Effect.uninterruptible),
|
|
32
|
-
};
|
|
33
|
-
});
|
|
3
|
+
import { initializeWorkerRelease } from "./initializeWorkerRelease.ts";
|
|
4
|
+
import { registerWorkerRelease } from "./registerWorkerRelease.ts";
|
|
34
5
|
|
|
35
6
|
export const WorkerRelease = defineService({
|
|
36
7
|
id: "@shivaedev/sql/WorkerRelease",
|
|
37
|
-
initialize:
|
|
38
|
-
methods: (
|
|
8
|
+
initialize: initializeWorkerRelease,
|
|
9
|
+
methods: (state) => ({ register: genericMethod(registerWorkerRelease(state)) }),
|
|
39
10
|
requires: [],
|
|
40
11
|
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Schema } from "effect";
|
|
2
|
+
import { Predicate, SchemaAST } from "effect";
|
|
3
|
+
import type { SqlNameTransforms } from "#clientOptions.ts";
|
|
4
|
+
|
|
5
|
+
interface Column {
|
|
6
|
+
readonly column: string;
|
|
7
|
+
readonly key: string;
|
|
8
|
+
readonly member: PropertyKey | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function groupColumns(fields: Schema.Struct.Fields, keys: readonly string[] = Object.keys(fields)): Column[] {
|
|
12
|
+
return keys.flatMap<Column>((key) => {
|
|
13
|
+
const ast = fields[key]?.ast;
|
|
14
|
+
const prefix = ast?.annotations?.["@shivaedev/sql/groupPrefix"];
|
|
15
|
+
return typeof prefix === "string" && ast !== undefined && SchemaAST.isObjects(ast)
|
|
16
|
+
? ast.propertySignatures.map((field) => ({ column: `${prefix}${String(field.name)}`, key, member: field.name }))
|
|
17
|
+
: [{ column: key, key, member: undefined }];
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function rowMapping(fields: Schema.Struct.Fields, names?: SqlNameTransforms) {
|
|
22
|
+
const columns = groupColumns(fields);
|
|
23
|
+
function returnedName(column: string) {
|
|
24
|
+
return names?.transformResultNames?.(names.transformQueryNames?.(column) ?? column) ?? column;
|
|
25
|
+
}
|
|
26
|
+
const seen = new Set<string>();
|
|
27
|
+
for (const { column } of columns) {
|
|
28
|
+
if (seen.has(column)) {
|
|
29
|
+
throw new Error(`Duplicate grouped column: ${column}`);
|
|
30
|
+
}
|
|
31
|
+
seen.add(column);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
expand: (row: Readonly<Record<string, unknown>>) => {
|
|
35
|
+
const expanded: Record<string, unknown> = { ...row };
|
|
36
|
+
for (const { key, member, column } of columns) {
|
|
37
|
+
if (member !== undefined && Object.hasOwn(row, returnedName(column))) {
|
|
38
|
+
const held = expanded[key];
|
|
39
|
+
expanded[key] = { ...(typeof held === "object" && held !== null ? held : {}), [member]: row[returnedName(column)] };
|
|
40
|
+
delete expanded[returnedName(column)];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return expanded;
|
|
44
|
+
},
|
|
45
|
+
flatten: (row: unknown) => {
|
|
46
|
+
if (!Predicate.isObject(row)) {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
const flat: Record<string, unknown> = { ...row };
|
|
50
|
+
for (const { key, member, column } of columns) {
|
|
51
|
+
if (member === undefined) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
delete flat[key];
|
|
55
|
+
const value = row[key];
|
|
56
|
+
if (Predicate.isObject(value) && Object.hasOwn(value, member)) {
|
|
57
|
+
flat[column] = Reflect.get(value, member);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return flat;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Schema, SchemaGetter } from "effect";
|
|
2
|
+
import { rowMapping } from "#adapters/groupColumns.ts";
|
|
3
|
+
import type { SqlNameTransforms } from "#clientOptions.ts";
|
|
4
|
+
|
|
5
|
+
export function storageSchema<TSchema extends Schema.Constraint>(schema: TSchema, fields: Schema.Struct.Fields, names?: SqlNameTransforms) {
|
|
6
|
+
const mapping = rowMapping(fields, names);
|
|
7
|
+
return Schema.Record(Schema.String, Schema.Unknown).pipe(
|
|
8
|
+
Schema.decodeTo(schema, { decode: SchemaGetter.transform(mapping.expand), encode: SchemaGetter.transform(mapping.flatten) }),
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Data, Effect, Option, Schema } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { advisoryXactLock } from "#advisoryXactLock.ts";
|
|
4
|
+
import { transact } from "#transact.ts";
|
|
5
|
+
|
|
6
|
+
export class BackfillInsideTransaction extends Data.TaggedError("BackfillInsideTransaction") {}
|
|
7
|
+
|
|
8
|
+
export interface BackfillOptions<TRow extends object, TProgress, E, R> {
|
|
9
|
+
readonly batchSize: number;
|
|
10
|
+
readonly loadProgress: Effect.Effect<TProgress, E, R>;
|
|
11
|
+
readonly lock: bigint | string;
|
|
12
|
+
readonly process: (row: TRow) => Effect.Effect<unknown, E, R>;
|
|
13
|
+
readonly saveProgress: (last: TRow, previous: TProgress) => Effect.Effect<unknown, E, R>;
|
|
14
|
+
readonly select: (progress: TProgress, limit: number) => Effect.Effect<readonly TRow[], E, R>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const BatchSize = Schema.Number.check(Schema.isInt(), Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(Number.MAX_SAFE_INTEGER));
|
|
18
|
+
|
|
19
|
+
export const backfillPostgres = Effect.fn("Sql.backfillPostgres")(function* <TRow extends object, TProgress, E, R>(
|
|
20
|
+
options: BackfillOptions<TRow, TProgress, E, R>,
|
|
21
|
+
) {
|
|
22
|
+
const sql = yield* SqlClient.SqlClient;
|
|
23
|
+
if (Option.isSome(yield* Effect.serviceOption(sql.transactionService))) {
|
|
24
|
+
return yield* new BackfillInsideTransaction();
|
|
25
|
+
}
|
|
26
|
+
const batchSize = yield* Schema.decodeUnknownEffect(BatchSize)(options.batchSize);
|
|
27
|
+
let processed = 0;
|
|
28
|
+
while (true) {
|
|
29
|
+
const count = yield* Effect.gen(function* () {
|
|
30
|
+
yield* advisoryXactLock(options.lock).pipe(Effect.catchTag("AdvisoryLockOutsideTransaction", Effect.die));
|
|
31
|
+
const progress = yield* options.loadProgress;
|
|
32
|
+
const rows = (yield* options.select(progress, batchSize)).slice(0, batchSize);
|
|
33
|
+
for (const row of rows) {
|
|
34
|
+
yield* options.process(row);
|
|
35
|
+
}
|
|
36
|
+
const last = rows.at(-1);
|
|
37
|
+
if (last !== undefined) {
|
|
38
|
+
yield* options.saveProgress(last, progress);
|
|
39
|
+
}
|
|
40
|
+
return rows.length;
|
|
41
|
+
}).pipe(transact({ onSqlError: (error) => error }));
|
|
42
|
+
processed += count;
|
|
43
|
+
if (count === 0) {
|
|
44
|
+
return processed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
package/src/batchWrites.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { Model } from "effect/schema";
|
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import type { SqlError } from "effect/sql/SqlError";
|
|
5
5
|
import type { Fragment } from "effect/sql/Statement";
|
|
6
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
6
7
|
import type { RepositoryChanges } from "#repositoryChanges.ts";
|
|
8
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
7
9
|
import { makeUpsertDecoder, upsertReturning } from "#upsertResult.ts";
|
|
8
10
|
|
|
9
11
|
export type InsertMany<TModel extends Model.Any, TServices = never> = (
|
|
@@ -22,11 +24,12 @@ export const makeInsertMany = Effect.fnUntraced(function* <TServices = never>(
|
|
|
22
24
|
upsert = false,
|
|
23
25
|
) {
|
|
24
26
|
const sql = yield* SqlClient.SqlClient;
|
|
27
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
25
28
|
const prepareRow = prepare ?? (() => Effect.succeed(sql``));
|
|
26
|
-
const encode = Schema.encodeEffect(model.insert);
|
|
29
|
+
const encode = Schema.encodeEffect(storageSchema(model.insert, model.fields, nameTransforms));
|
|
27
30
|
const record = Schema.decodeUnknownEffect(Schema.Record(Schema.String, Schema.Unknown));
|
|
28
|
-
const decode = Schema.decodeUnknownEffect(Schema.Array(model));
|
|
29
|
-
const decodeUpsert = makeUpsertDecoder(model);
|
|
31
|
+
const decode = Schema.decodeUnknownEffect(Schema.Array(storageSchema(model, model.fields, nameTransforms)));
|
|
32
|
+
const decodeUpsert = makeUpsertDecoder(model, nameTransforms);
|
|
30
33
|
const returning = options.changes.returning(upsert ? yield* upsertReturning() : sql`*`);
|
|
31
34
|
return (inputs: readonly unknown[]) =>
|
|
32
35
|
Effect.gen(function* () {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as PgTypes from "@effect/sql-pg/PgTypes";
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
import type { Model } from "effect/schema";
|
|
4
|
+
import { compositeId, recordKey } from "@shivaedev/changes/keys.ts";
|
|
5
|
+
import type { CompositeKey } from "#compositeRepository.ts";
|
|
6
|
+
|
|
7
|
+
export function compositeRecordKey<TModel extends Model.Any, const TColumns extends CompositeKey<TModel>>(
|
|
8
|
+
model: TModel,
|
|
9
|
+
options: { readonly tableName: string; readonly idColumn: TColumns },
|
|
10
|
+
value: Pick<Schema.Struct.Type<TModel["fields"]>, TColumns[number]>,
|
|
11
|
+
): Effect.Effect<string, Schema.SchemaError, TModel["fields"][TColumns[number]]["EncodingServices"]>;
|
|
12
|
+
export function compositeRecordKey(
|
|
13
|
+
model: Model.Any,
|
|
14
|
+
options: { readonly tableName: string; readonly idColumn: readonly string[] },
|
|
15
|
+
value: Readonly<Record<string, unknown>>,
|
|
16
|
+
): Effect.Effect<string, Schema.SchemaError, unknown> {
|
|
17
|
+
return Effect.fn("Sql.compositeRecordKey")(function* () {
|
|
18
|
+
const fields = Object.fromEntries(Object.entries(model.fields).filter(([column]) => options.idColumn.includes(column)));
|
|
19
|
+
const encoded = yield* Schema.encodeUnknownEffect(Schema.Struct(fields))(value);
|
|
20
|
+
const values = options.idColumn.map((column) => {
|
|
21
|
+
const stored = encoded[column];
|
|
22
|
+
return PgTypes.isParameter(stored) ? stored.value : stored;
|
|
23
|
+
});
|
|
24
|
+
return recordKey(options.tableName, compositeId(values));
|
|
25
|
+
})();
|
|
26
|
+
}
|
|
@@ -3,11 +3,13 @@ import { Effect, Schema } from "effect";
|
|
|
3
3
|
import type { Model } from "effect/schema";
|
|
4
4
|
import { SqlClient, SqlSchema } from "effect/sql";
|
|
5
5
|
import type { SqlError } from "effect/sql/SqlError";
|
|
6
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
6
7
|
import type { BatchedDelete } from "#batchedDelete.ts";
|
|
7
8
|
import type { RepositoryChanges } from "#repositoryChanges.ts";
|
|
8
9
|
import type { RepositoryListFilter } from "#repositoryFilter.ts";
|
|
9
10
|
import type { Update, UpdateVoid } from "#repositoryUpdate.ts";
|
|
10
11
|
import { repositoryWrites } from "#repositoryWrites.ts";
|
|
12
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
11
13
|
import type { IdColumn, NativeRepository } from "#versionedRepository.ts";
|
|
12
14
|
|
|
13
15
|
export type CompositeKey<TModel extends Model.Any> = readonly [IdColumn<TModel>, IdColumn<TModel>, ...IdColumn<TModel>[]];
|
|
@@ -51,6 +53,7 @@ export const makeCompositeCrud = Effect.fnUntraced(function* (
|
|
|
51
53
|
changes: RepositoryChanges,
|
|
52
54
|
) {
|
|
53
55
|
const sql = yield* SqlClient.SqlClient;
|
|
56
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
54
57
|
const { columns, tableName, spanPrefix } = options;
|
|
55
58
|
const entries: [string, Schema.Constraint][] = [];
|
|
56
59
|
for (const column of columns) {
|
|
@@ -76,7 +79,7 @@ export const makeCompositeCrud = Effect.fnUntraced(function* (
|
|
|
76
79
|
(key) => sql`select * from ${sql(tableName)} where ${predicate(key)}`,
|
|
77
80
|
),
|
|
78
81
|
Request: Key,
|
|
79
|
-
Result: model,
|
|
82
|
+
Result: storageSchema(model, model.fields, nameTransforms),
|
|
80
83
|
});
|
|
81
84
|
|
|
82
85
|
return {
|
package/src/conditionalUpsert.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import type { Model } from "effect/schema";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import type { SqlError } from "effect/sql/SqlError";
|
|
5
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
5
6
|
import { makeConflictClause } from "#makeConflictClause.ts";
|
|
6
7
|
import type { RepositoryChanges } from "#repositoryChanges.ts";
|
|
7
8
|
import type { Comparisons, FindManyFilter } from "#repositoryFilter.ts";
|
|
9
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
8
10
|
import type { UpsertAssignment } from "#upsertColumns.ts";
|
|
9
11
|
import { makeUpsertDecoder, type UpsertRow, upsertReturning } from "#upsertResult.ts";
|
|
10
12
|
|
|
@@ -65,10 +67,11 @@ export const makeConditionalUpsert = Effect.fnUntraced(function* (
|
|
|
65
67
|
},
|
|
66
68
|
) {
|
|
67
69
|
const sql = yield* SqlClient.SqlClient;
|
|
70
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
68
71
|
const returning = yield* upsertReturning();
|
|
69
|
-
const encode = Schema.encodeEffect(model.insert);
|
|
72
|
+
const encode = Schema.encodeEffect(storageSchema(model.insert, model.fields, nameTransforms));
|
|
70
73
|
const record = Schema.decodeUnknownEffect(Schema.Record(Schema.String, Schema.Unknown));
|
|
71
|
-
const decode = makeUpsertDecoder(model);
|
|
74
|
+
const decode = makeUpsertDecoder(model, nameTransforms);
|
|
72
75
|
const clause = yield* makeConflictClause(model, options);
|
|
73
76
|
return (
|
|
74
77
|
inputs: readonly unknown[],
|
package/src/conflictWrites.ts
CHANGED
|
@@ -2,11 +2,13 @@ import { Effect, Option, Schema } from "effect";
|
|
|
2
2
|
import type { Model } from "effect/schema";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import type { SqlError } from "effect/sql/SqlError";
|
|
5
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
5
6
|
import { type InsertMany, makeInsertMany } from "#batchWrites.ts";
|
|
6
7
|
import { type ConditionalUpsert, type ConditionalUpsertMany, makeConditionalUpsert } from "#conditionalUpsert.ts";
|
|
7
8
|
import { type InsertFromSelect, makeInsertFromSelect } from "#insertFromSelect.ts";
|
|
8
9
|
import { makeConflictClause } from "#makeConflictClause.ts";
|
|
9
10
|
import type { RepositoryChanges } from "#repositoryChanges.ts";
|
|
11
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
10
12
|
import type { UpsertAssignment, UpsertColumn } from "#upsertColumns.ts";
|
|
11
13
|
import { makeUpsertDecoder, upsertReturning } from "#upsertResult.ts";
|
|
12
14
|
|
|
@@ -52,11 +54,12 @@ export const makeConflictWrites = Effect.fnUntraced(function* (
|
|
|
52
54
|
},
|
|
53
55
|
) {
|
|
54
56
|
const sql = yield* SqlClient.SqlClient;
|
|
57
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
55
58
|
const { tableName, spanPrefix } = options;
|
|
56
59
|
const returning = yield* upsertReturning();
|
|
57
|
-
const encode = Schema.encodeEffect(model.insert);
|
|
58
|
-
const decode = Schema.decodeUnknownEffect(model);
|
|
59
|
-
const decodeUpsert = makeUpsertDecoder(model);
|
|
60
|
+
const encode = Schema.encodeEffect(storageSchema(model.insert, model.fields, nameTransforms));
|
|
61
|
+
const decode = Schema.decodeUnknownEffect(storageSchema(model, model.fields, nameTransforms));
|
|
62
|
+
const decodeUpsert = makeUpsertDecoder(model, nameTransforms);
|
|
60
63
|
const record = Schema.decodeUnknownEffect(Schema.Record(Schema.String, Schema.Unknown));
|
|
61
64
|
const conditional = yield* makeConditionalUpsert(model, options);
|
|
62
65
|
const clause = yield* makeConflictClause(model, options);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Duration, Effect, Exit, Option, Predicate, Schedule, Scope } from "effect";
|
|
2
2
|
import { isSqlError } from "effect/sql/SqlError";
|
|
3
3
|
import { WorkerReleaseTesting } from "#testing/WorkerRelease.ts";
|
|
4
|
+
import { makeWorkerRelease } from "./makeWorkerRelease.ts";
|
|
4
5
|
import { readPostgresRelease } from "./postgresRelease.ts";
|
|
5
6
|
import { releaseWorkers } from "./releaseWorkers.ts";
|
|
6
|
-
import { makeWorkerRelease } from "./WorkerRelease.ts";
|
|
7
7
|
|
|
8
8
|
function isCloseable(scope: Scope.Scope): scope is Scope.Closeable {
|
|
9
9
|
return Predicate.hasProperty(scope, "~effect/Scope/Closeable") && scope["~effect/Scope/Closeable"] === "~effect/Scope/Closeable";
|
package/src/insertFromSelect.ts
CHANGED
|
@@ -3,8 +3,10 @@ import type { Model } from "effect/schema";
|
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import type { SqlError } from "effect/sql/SqlError";
|
|
5
5
|
import type { Fragment } from "effect/sql/Statement";
|
|
6
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
6
7
|
import type { RepositoryChanges } from "#repositoryChanges.ts";
|
|
7
8
|
import type { RepositorySelect } from "#repositoryQueries.ts";
|
|
9
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
8
10
|
|
|
9
11
|
type CompatibleColumn<TType, TEncoded, TFields extends Schema.Struct.Fields> = {
|
|
10
12
|
[TKey in keyof TFields]: TFields[TKey]["Type"] extends TType ? (TFields[TKey]["Encoded"] extends TEncoded ? TKey : never) : never;
|
|
@@ -31,7 +33,8 @@ export const makeInsertFromSelect = Effect.fnUntraced(function* <TServices = nev
|
|
|
31
33
|
prepare?: (columns: readonly string[]) => Effect.Effect<Fragment, SqlError | Schema.SchemaError, TServices>,
|
|
32
34
|
) {
|
|
33
35
|
const sql = yield* SqlClient.SqlClient;
|
|
34
|
-
const
|
|
36
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
37
|
+
const decode = Schema.decodeUnknownEffect(Schema.Array(storageSchema(model, model.fields, nameTransforms)));
|
|
35
38
|
return (source: RepositorySelect<Schema.Struct.Fields, unknown>, columns: Readonly<Record<string, string>>) =>
|
|
36
39
|
Effect.gen(function* () {
|
|
37
40
|
const names = Object.keys(columns);
|
package/src/latestPerGroup.ts
CHANGED
|
@@ -2,9 +2,12 @@ import { Effect, Schema } from "effect";
|
|
|
2
2
|
import type { Model } from "effect/schema";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import type { Fragment } from "effect/sql/Statement";
|
|
5
|
+
import { groupColumns } from "#adapters/groupColumns.ts";
|
|
6
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
5
7
|
import { pinnedGroups } from "#pinnedGroups.ts";
|
|
6
8
|
import type { LatestPerGroup } from "#repository.ts";
|
|
7
9
|
import { makeFilter } from "#repositoryFilter.ts";
|
|
10
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
8
11
|
import { tupleUnnest } from "#tupleUnnest.ts";
|
|
9
12
|
|
|
10
13
|
function groupMasks(input: readonly (readonly unknown[])[]) {
|
|
@@ -20,6 +23,7 @@ function groupMasks(input: readonly (readonly unknown[])[]) {
|
|
|
20
23
|
|
|
21
24
|
export const latestPerGroup = Effect.fnUntraced(function* (model: Model.Any, tableName: string) {
|
|
22
25
|
const sql = yield* SqlClient.SqlClient;
|
|
26
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
23
27
|
const { fieldAt, where } = yield* makeFilter(model);
|
|
24
28
|
function groupSource(groups: readonly (readonly unknown[])[], partitionBy: readonly string[], fromTuple: boolean) {
|
|
25
29
|
const partitions = sql.csv(partitionBy.map((key) => sql`${sql(key)}`));
|
|
@@ -37,7 +41,7 @@ export const latestPerGroup = Effect.fnUntraced(function* (model: Model.Any, tab
|
|
|
37
41
|
const predicate = yield* where(query.where, true);
|
|
38
42
|
const partitions = sql.csv(query.partitionBy.map((key) => sql`${sql(key)}`));
|
|
39
43
|
const keys = Object.keys(model.fields);
|
|
40
|
-
const columns = sql.csv(keys.map((
|
|
44
|
+
const columns = sql.csv(groupColumns(model.fields, keys).map(({ column }) => sql`${sql(column)}`));
|
|
41
45
|
const pinned = yield* pinnedGroups(model, query.partitionBy, query.where).pipe(Effect.provideService(SqlClient.SqlClient, sql));
|
|
42
46
|
if (pinned !== undefined && pinned.groups.length === 0) {
|
|
43
47
|
return [];
|
|
@@ -70,7 +74,7 @@ export const latestPerGroup = Effect.fnUntraced(function* (model: Model.Any, tab
|
|
|
70
74
|
const ordering = sql.csv(query.orderBy.map(({ field, direction }) => sql`${sql(field)} ${sql.literal(direction === "desc" ? "desc" : "asc")}`));
|
|
71
75
|
const rows =
|
|
72
76
|
yield* sql`${tupleSource} select ${columns} from (${sql.join(" union all ")(branches)}) as ${sql("__effect_sql_pinned")} order by ${partitions}, ${ordering}`;
|
|
73
|
-
return yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct(model.fields)))(rows);
|
|
77
|
+
return yield* Schema.decodeUnknownEffect(Schema.Array(storageSchema(Schema.Struct(model.fields), model.fields, nameTransforms)))(rows);
|
|
74
78
|
}
|
|
75
79
|
let rankColumn = "__effect_sql_rank";
|
|
76
80
|
while (Object.hasOwn(model.fields, rankColumn)) {
|
|
@@ -81,6 +85,6 @@ export const latestPerGroup = Effect.fnUntraced(function* (model: Model.Any, tab
|
|
|
81
85
|
from ${sql(tableName)} where ${predicate}
|
|
82
86
|
) as ${sql("__effect_sql_ranked")} where ${sql(rankColumn)} <= ${rowLimit}
|
|
83
87
|
order by ${partitions}, ${sql(rankColumn)}`;
|
|
84
|
-
return yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct(model.fields)))(rows);
|
|
88
|
+
return yield* Schema.decodeUnknownEffect(Schema.Array(storageSchema(Schema.Struct(model.fields), model.fields, nameTransforms)))(rows);
|
|
85
89
|
});
|
|
86
90
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type Cause, Effect, Schema } from "effect";
|
|
2
|
+
import type { Model } from "effect/schema";
|
|
3
|
+
import { SqlClient, SqlSchema } from "effect/sql";
|
|
4
|
+
import type { SqlError } from "effect/sql/SqlError";
|
|
5
|
+
import { storageSchema } from "#adapters/storageSchema.ts";
|
|
6
|
+
import { type ChildOrderError, orderedChildStatements } from "#orderedChildStatements.ts";
|
|
7
|
+
import { repositoryChanges } from "#repositoryChanges.ts";
|
|
8
|
+
import { sqlNameTransforms } from "#sqlNameTransforms.ts";
|
|
9
|
+
import type { IdColumn, VersionColumn } from "#versionedRepository.ts";
|
|
10
|
+
|
|
11
|
+
type Column<TModel extends Model.Any> = Extract<keyof TModel["fields"] & keyof TModel["insert"]["Type"], string>;
|
|
12
|
+
type Position<TModel extends Model.Any> = {
|
|
13
|
+
[TKey in Column<TModel>]: TModel["fields"][TKey] extends Schema.ConstraintCodec<number, number, never, never> ? TKey : never;
|
|
14
|
+
}[Column<TModel>];
|
|
15
|
+
|
|
16
|
+
export const makeOrderedChildren = Effect.fn("Sql.makeOrderedChildren")(function* <
|
|
17
|
+
TModel extends Model.Any,
|
|
18
|
+
const TId extends Extract<IdColumn<TModel>, string>,
|
|
19
|
+
const TParent extends Column<TModel>,
|
|
20
|
+
const TPosition extends Position<TModel>,
|
|
21
|
+
>(
|
|
22
|
+
model: TModel,
|
|
23
|
+
options: {
|
|
24
|
+
readonly idColumn: TId;
|
|
25
|
+
readonly parentColumn: TParent;
|
|
26
|
+
readonly parentTable: string;
|
|
27
|
+
readonly parentIdColumn: string;
|
|
28
|
+
readonly positionColumn: TPosition;
|
|
29
|
+
readonly spanPrefix: string;
|
|
30
|
+
readonly tableName: string;
|
|
31
|
+
readonly versionColumn?: VersionColumn<TModel, TId>;
|
|
32
|
+
},
|
|
33
|
+
) {
|
|
34
|
+
const sql = yield* SqlClient.SqlClient;
|
|
35
|
+
const nameTransforms = yield* sqlNameTransforms;
|
|
36
|
+
const changes = yield* repositoryChanges(options.tableName, [options.idColumn], model.fields);
|
|
37
|
+
const statements = yield* orderedChildStatements(model, options, changes);
|
|
38
|
+
const encode = Schema.encodeEffect(storageSchema(model.insert, model.fields, nameTransforms));
|
|
39
|
+
const decode = Schema.decodeUnknownEffect(storageSchema(model, model.fields, nameTransforms));
|
|
40
|
+
const { parentColumn, positionColumn, versionColumn, tableName, spanPrefix } = options;
|
|
41
|
+
function append(
|
|
42
|
+
parent: TModel["fields"][TParent]["Type"],
|
|
43
|
+
input: Omit<TModel["insert"]["Type"], TParent | TPosition>,
|
|
44
|
+
): Effect.Effect<
|
|
45
|
+
TModel["Type"],
|
|
46
|
+
SqlError | Schema.SchemaError | Cause.NoSuchElementError,
|
|
47
|
+
TModel["insert"]["EncodingServices"] | TModel["DecodingServices"]
|
|
48
|
+
> {
|
|
49
|
+
return Effect.gen(function* () {
|
|
50
|
+
const encodedParent = yield* statements.lock(parent);
|
|
51
|
+
const last = yield* SqlSchema.findOne({
|
|
52
|
+
execute: (value) =>
|
|
53
|
+
sql`select coalesce(max(${sql(positionColumn)}) + 1, 0)::integer as next from ${sql(tableName)} where ${sql(parentColumn)} = ${value}`,
|
|
54
|
+
Request: Schema.Unknown,
|
|
55
|
+
Result: Schema.Struct({ next: Schema.Int }),
|
|
56
|
+
})(encodedParent);
|
|
57
|
+
const encoded = yield* encode({ ...input, [parentColumn]: parent, [positionColumn]: last.next });
|
|
58
|
+
const row = versionColumn === undefined ? encoded : { ...encoded, [versionColumn]: 0 };
|
|
59
|
+
const rows = yield* sql`insert into ${sql(tableName)} ${sql.insert(row)} returning ${changes.returning()}`;
|
|
60
|
+
yield* changes.record(rows);
|
|
61
|
+
return yield* decode(rows[0]);
|
|
62
|
+
}).pipe(changes.write, Effect.withSpan(`${spanPrefix}.append`));
|
|
63
|
+
}
|
|
64
|
+
type OrderResult = Effect.Effect<
|
|
65
|
+
void,
|
|
66
|
+
SqlError | Schema.SchemaError | Cause.NoSuchElementError | ChildOrderError,
|
|
67
|
+
TModel["fields"][TParent | TId]["EncodingServices"] | TModel["fields"][TId]["DecodingServices"]
|
|
68
|
+
>;
|
|
69
|
+
function move(parent: TModel["fields"][TParent]["Type"], id: TModel["fields"][TId]["Type"], position: number): OrderResult;
|
|
70
|
+
function move(parent: unknown, id: unknown, position: number) {
|
|
71
|
+
return statements.move(parent, id, position).pipe(changes.write, Effect.withSpan(`${spanPrefix}.move`));
|
|
72
|
+
}
|
|
73
|
+
function reorder(parent: TModel["fields"][TParent]["Type"], ids: readonly TModel["fields"][TId]["Type"][]): OrderResult;
|
|
74
|
+
function reorder(parent: unknown, ids: readonly unknown[]) {
|
|
75
|
+
return statements.reorder(parent, ids).pipe(changes.write, Effect.withSpan(`${spanPrefix}.reorder`));
|
|
76
|
+
}
|
|
77
|
+
return { append, move, reorder };
|
|
78
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { initializeWorkerRelease } from "./initializeWorkerRelease.ts";
|
|
3
|
+
import { registerWorkerRelease } from "./registerWorkerRelease.ts";
|
|
4
|
+
import { setWorkerReleaseActive } from "./setWorkerReleaseActive.ts";
|
|
5
|
+
|
|
6
|
+
export const makeWorkerRelease = Effect.fn("EffectSql.makeWorkerRelease")(function* () {
|
|
7
|
+
const state = yield* initializeWorkerRelease;
|
|
8
|
+
return {
|
|
9
|
+
service: { register: registerWorkerRelease(state) },
|
|
10
|
+
setActive: setWorkerReleaseActive(state),
|
|
11
|
+
};
|
|
12
|
+
});
|