@shivaedev/sql 0.27.0 → 0.29.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 +25 -0
- package/README.md +25 -10
- package/dist/adapters/makePostgresClient.d.ts.map +1 -1
- package/dist/adapters/makePostgresClient.js +8 -1
- package/dist/adapters/makePostgresClient.js.map +1 -1
- package/dist/latestPerGroup.d.ts +1 -1
- package/dist/latestPerGroup.d.ts.map +1 -1
- package/dist/latestPerGroup.js +37 -13
- package/dist/latestPerGroup.js.map +1 -1
- package/dist/listenForChanges.d.ts.map +1 -1
- package/dist/listenForChanges.js +11 -5
- package/dist/listenForChanges.js.map +1 -1
- package/dist/pinnedGroups.d.ts +9 -1
- package/dist/pinnedGroups.d.ts.map +1 -1
- package/dist/pinnedGroups.js +2 -2
- package/dist/pinnedGroups.js.map +1 -1
- package/dist/postgresChanges.d.ts +1 -0
- package/dist/postgresChanges.d.ts.map +1 -1
- package/dist/postgresChanges.js.map +1 -1
- package/dist/postgresLayer.d.ts +3 -0
- package/dist/postgresLayer.d.ts.map +1 -1
- package/dist/postgresLayer.js.map +1 -1
- package/dist/repository.d.ts +5 -9
- package/dist/repository.d.ts.map +1 -1
- package/dist/repository.js +2 -0
- package/dist/repository.js.map +1 -1
- package/dist/repositoryCollection.d.ts +28 -0
- package/dist/repositoryCollection.d.ts.map +1 -0
- package/dist/repositoryCollection.js +21 -0
- package/dist/repositoryCollection.js.map +1 -0
- package/dist/testing.d.ts +2 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/tupleIn.d.ts +4 -2
- package/dist/tupleIn.d.ts.map +1 -1
- package/dist/tupleIn.js +13 -5
- package/dist/tupleIn.js.map +1 -1
- package/dist/tupleUnnest.d.ts +5 -0
- package/dist/tupleUnnest.d.ts.map +1 -0
- package/dist/tupleUnnest.js +26 -0
- package/dist/tupleUnnest.js.map +1 -0
- package/package.json +4 -4
- package/src/adapters/makePostgresClient.ts +9 -1
- package/src/latestPerGroup.ts +41 -14
- package/src/listenForChanges.ts +13 -10
- package/src/pinnedGroups.ts +5 -3
- package/src/postgresChanges.ts +1 -0
- package/src/postgresLayer.ts +3 -0
- package/src/repository.ts +11 -11
- package/src/repositoryCollection.ts +46 -0
- package/src/testing.ts +7 -3
- package/src/tupleIn.ts +40 -7
- package/src/tupleUnnest.ts +25 -0
package/dist/repository.js
CHANGED
|
@@ -5,12 +5,14 @@ import { makeCompositeCrud } from "#compositeRepository.ts";
|
|
|
5
5
|
import { makeConflictWrites } from "#conflictWrites.ts";
|
|
6
6
|
import { makeInsertFromSelect } from "#insertFromSelect.ts";
|
|
7
7
|
import { repositoryChanges } from "#repositoryChanges.ts";
|
|
8
|
+
import { validateRepositoryCollection } from "#repositoryCollection.ts";
|
|
8
9
|
import { makeQueries } from "#repositoryQueries.ts";
|
|
9
10
|
import { makeVersionedWrites } from "#versionedRepository.ts";
|
|
10
11
|
export class OptimisticConcurrencyError extends Data.TaggedError("OptimisticConcurrencyError") {
|
|
11
12
|
}
|
|
12
13
|
export function makeRepository(model, options) {
|
|
13
14
|
return Effect.fnUntraced(function* () {
|
|
15
|
+
yield* validateRepositoryCollection(model.fields, options);
|
|
14
16
|
const sql = yield* SqlClient.SqlClient;
|
|
15
17
|
const composite = typeof options.idColumn === "object";
|
|
16
18
|
const idColumns = (composite ? options.idColumn : [options.idColumn]).map(String);
|
package/dist/repository.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAoB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAoC,MAAM,yBAAyB,CAAC;AAC9F,OAAO,EAA6C,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACnG,OAAO,EAAE,oBAAoB,EAAqB,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Effect, type Schema } from "effect";
|
|
2
|
+
export interface RepositoryCollection {
|
|
3
|
+
readonly bucket: Readonly<Record<string, unknown>>;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RepositoryOptions<TId> {
|
|
7
|
+
readonly collection?: RepositoryCollection;
|
|
8
|
+
readonly idColumn: TId;
|
|
9
|
+
readonly spanPrefix: string;
|
|
10
|
+
readonly tableName: string;
|
|
11
|
+
}
|
|
12
|
+
declare const BucketRegistrationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
13
|
+
readonly _tag: "BucketRegistrationError";
|
|
14
|
+
} & Readonly<A>;
|
|
15
|
+
export declare class BucketRegistrationError extends BucketRegistrationError_base<{
|
|
16
|
+
readonly tableName: string;
|
|
17
|
+
readonly collectionName: string | undefined;
|
|
18
|
+
readonly modelFields: readonly string[];
|
|
19
|
+
readonly collectionFields: readonly string[];
|
|
20
|
+
}> {
|
|
21
|
+
get message(): string;
|
|
22
|
+
}
|
|
23
|
+
export declare const validateRepositoryCollection: (fields: Schema.Struct.Fields, options: {
|
|
24
|
+
readonly tableName: string;
|
|
25
|
+
readonly collection?: RepositoryCollection;
|
|
26
|
+
}) => Effect.Effect<undefined, BucketRegistrationError, never>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=repositoryCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repositoryCollection.d.ts","sourceRoot":"","sources":["../src/repositoryCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAS,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAG1D,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB,CAAC,GAAG;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;;;;AAED,qBAAa,uBAAwB,SAAQ,6BAA4C;IACxF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C,CAAC;IACD,IAAa,OAAO,WAEnB;CACD;AAED,eAAO,MAAM,4BAA4B;wBAET,MAAM;0BAAwB,oBAAoB;8DAiBhF,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Data, Effect, Order } from "effect";
|
|
2
|
+
import { declaredBuckets } from "@shivaedev/changes/buckets.ts";
|
|
3
|
+
export class BucketRegistrationError extends Data.TaggedError("BucketRegistrationError") {
|
|
4
|
+
get message() {
|
|
5
|
+
return `Bucket registration for ${this.tableName}: model fields ${JSON.stringify(this.modelFields)} disagree with collection ${this.collectionName ?? "(missing)"} fields ${JSON.stringify(this.collectionFields)}`;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export const validateRepositoryCollection = Effect.fnUntraced(function* (fields, options) {
|
|
9
|
+
const modelFields = declaredBuckets(fields)
|
|
10
|
+
.map(({ field }) => field)
|
|
11
|
+
.sort(Order.String);
|
|
12
|
+
const collectionFields = Object.keys(options.collection?.bucket ?? {}).sort(Order.String);
|
|
13
|
+
if ((options.collection === undefined && modelFields.length === 0)
|
|
14
|
+
|| (options.collection?.name === options.tableName
|
|
15
|
+
&& modelFields.length === collectionFields.length
|
|
16
|
+
&& modelFields.every((field, index) => field === collectionFields[index]))) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
return yield* Effect.fail(new BucketRegistrationError({ collectionFields, collectionName: options.collection?.name, modelFields, tableName: options.tableName }));
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=repositoryCollection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repositoryCollection.js","sourceRoot":"","sources":["../src/repositoryCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAe,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAchE,MAAM,OAAO,uBAAwB,SAAQ,IAAI,CAAC,WAAW,CAAC,yBAAyB,CAKrF;IACD,IAAa,OAAO;QACnB,OAAO,2BAA2B,IAAI,CAAC,SAAS,kBAAkB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,6BAA6B,IAAI,CAAC,cAAc,IAAI,WAAW,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;IACrN,CAAC;CACD;AAED,MAAM,CAAC,MAAM,4BAA4B,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EACtE,MAA4B,EAC5B,OAAmF;IAEnF,MAAM,WAAW,GAAG,eAAe,CAAC,MAAM,CAAC;SACzC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC;SACzB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1F,IACC,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC;WAC3D,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,KAAK,OAAO,CAAC,SAAS;eAC9C,WAAW,CAAC,MAAM,KAAK,gBAAgB,CAAC,MAAM;eAC9C,WAAW,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,EAC1E,CAAC;QACF,OAAO;IACR,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACxB,IAAI,uBAAuB,CAAC,EAAE,gBAAgB,EAAE,cAAc,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CACtI,CAAC;AACH,CAAC,CAAC,CAAC"}
|
package/dist/testing.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class PublishedKeysMismatch extends PublishedKeysMismatch_base<{
|
|
|
21
21
|
}> {
|
|
22
22
|
get message(): string;
|
|
23
23
|
}
|
|
24
|
-
export declare function expectPublished(keys: readonly unknown[]): <A, E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E | PublishedKeysMismatch,
|
|
24
|
+
export declare function expectPublished(keys: readonly unknown[]): <A, E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E | PublishedKeysMismatch, R>;
|
|
25
|
+
export declare function expectPublished<A>(keys: (result: A) => readonly unknown[]): <E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E | PublishedKeysMismatch, R>;
|
|
25
26
|
export {};
|
|
26
27
|
//# sourceMappingURL=testing.d.ts.map
|
package/dist/testing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAoB,MAAM,4BAA4B,CAAC;;;;AAI5F,qBAAa,iBAAkB,SAAQ,uBAAsC;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;CAAG;AAEvH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5E;AAED,eAAO,MAAM,kBAAkB,GAAsD,CAAC,EAAE,CAAC,EAAE,CAAC,4UAuD1F,CAAC;;;;AAEH,qBAAa,qBAAsB,SAAQ,2BAA0C;IACpF,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CACpC,CAAC;IACD,IAAa,OAAO,WAEnB;CACD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAAoB,MAAM,4BAA4B,CAAC;;;;AAI5F,qBAAa,iBAAkB,SAAQ,uBAAsC;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,CAAC;CAAG;AAEvH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5E;AAED,eAAO,MAAM,kBAAkB,GAAsD,CAAC,EAAE,CAAC,EAAE,CAAC,4UAuD1F,CAAC;;;;AAEH,qBAAa,qBAAsB,SAAQ,2BAA0C;IACpF,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;CACpC,CAAC;IACD,IAAa,OAAO,WAEnB;CACD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,qBAAqB,EAAE,CAAC,CAAC,CAAC;AACrJ,wBAAgB,eAAe,CAAC,CAAC,EAChC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,SAAS,OAAO,EAAE,GACrC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,qBAAqB,EAAE,CAAC,CAAC,CAAC"}
|
package/dist/testing.js
CHANGED
|
@@ -65,7 +65,7 @@ export function expectPublished(keys) {
|
|
|
65
65
|
}
|
|
66
66
|
return observer(event);
|
|
67
67
|
}));
|
|
68
|
-
const expected = new Set(keys);
|
|
68
|
+
const expected = new Set(typeof keys === "function" ? keys(result) : keys);
|
|
69
69
|
if (actual.size !== expected.size || [...expected].some((key) => !actual.has(key))) {
|
|
70
70
|
return yield* Effect.fail(new PublishedKeysMismatch({ actual: [...actual], expected: [...expected] }));
|
|
71
71
|
}
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
|
package/dist/tupleIn.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import type { Model } from "effect/schema";
|
|
3
|
-
import
|
|
3
|
+
import { SqlClient } from "effect/sql";
|
|
4
|
+
export type TupleColumnType = "bool" | "bpchar" | "bytea" | "cidr" | "date" | "float4" | "float8" | "inet" | "int2" | "int4" | "int8" | "interval" | "jsonb" | "name" | "numeric" | "oid" | "regclass" | "text" | "time" | "timestamp" | "timestamptz" | "timetz" | "uuid" | "varchar";
|
|
4
5
|
export declare const tupleFilter: unique symbol;
|
|
5
6
|
type Row<TModel extends Model.Any> = Schema.Struct.Type<TModel["fields"]>;
|
|
6
7
|
type Key<TModel extends Model.Any> = Extract<keyof Row<TModel>, string>;
|
|
@@ -9,11 +10,12 @@ export interface TupleFilter<TModel extends Model.Any> {
|
|
|
9
10
|
readonly model: TModel;
|
|
10
11
|
readonly fields: readonly Key<TModel>[];
|
|
11
12
|
readonly values: readonly (readonly unknown[])[];
|
|
13
|
+
readonly types: readonly TupleColumnType[];
|
|
12
14
|
};
|
|
13
15
|
}
|
|
14
16
|
export declare function tupleIn<TModel extends Model.Any, const TFields extends readonly [Key<TModel>, Key<TModel>, ...Key<TModel>[]]>(model: TModel, fields: TFields, values: readonly {
|
|
15
17
|
readonly [TIndex in keyof TFields]: Row<TModel>[TFields[TIndex]];
|
|
16
|
-
}[]): TupleFilter<TModel>;
|
|
18
|
+
}[], types: Readonly<Record<TFields[number], TupleColumnType>>): TupleFilter<TModel>;
|
|
17
19
|
export declare function tuplePredicate(input: Partial<TupleFilter<Model.Any>>, sql: SqlClient.SqlClient, fieldAt: (key: string) => Effect.Effect<Schema.Constraint, Schema.SchemaError>): Effect.Effect<undefined, never, never> | Effect.Effect<import("effect/sql/Statement").Fragment, Schema.SchemaError, unknown>;
|
|
18
20
|
export {};
|
|
19
21
|
//# sourceMappingURL=tupleIn.d.ts.map
|
package/dist/tupleIn.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tupleIn.d.ts","sourceRoot":"","sources":["../src/tupleIn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,
|
|
1
|
+
{"version":3,"file":"tupleIn.d.ts","sourceRoot":"","sources":["../src/tupleIn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,MAAM,eAAe,GACxB,MAAM,GACN,QAAQ,GACR,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,MAAM,GACN,MAAM,GACN,MAAM,GACN,UAAU,GACV,OAAO,GACP,MAAM,GACN,SAAS,GACT,KAAK,GACL,UAAU,GACV,MAAM,GACN,MAAM,GACN,WAAW,GACX,aAAa,GACb,QAAQ,GACR,MAAM,GACN,SAAS,CAAC;AAEb,eAAO,MAAM,WAAW,eAAwB,CAAC;AACjD,KAAK,GAAG,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC1E,KAAK,GAAG,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;AAExE,MAAM,WAAW,WAAW,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG;IACpD,QAAQ,CAAC,CAAC,WAAW,CAAC,EAAE;QACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;QACjD,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;KAC3C,CAAC;CACF;AAED,wBAAgB,OAAO,CAAC,MAAM,SAAS,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,SAAS,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAC5H,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,SAAS;IAAE,QAAQ,EAAE,MAAM,IAAI,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;CAAE,EAAE,EACvF,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC,CAAC,GACvD,WAAW,CAAC,MAAM,CAAC,CAErB;AAED,wBAAgB,cAAc,CAC7B,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EACtC,GAAG,EAAE,SAAS,CAAC,SAAS,EACxB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,gIAoB9E"}
|
package/dist/tupleIn.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import { tupleUnnest } from "#tupleUnnest.ts";
|
|
2
4
|
export const tupleFilter = Symbol("sql/tupleIn");
|
|
3
|
-
export function tupleIn(model, fields, values) {
|
|
4
|
-
return { [tupleFilter]: { fields, model, values } };
|
|
5
|
+
export function tupleIn(model, fields, values, types) {
|
|
6
|
+
return { [tupleFilter]: { fields, model, types: fields.map((field) => types[field]), values } };
|
|
5
7
|
}
|
|
6
8
|
export function tuplePredicate(input, sql, fieldAt) {
|
|
7
9
|
const tuple = input[tupleFilter];
|
|
@@ -12,9 +14,15 @@ export function tuplePredicate(input, sql, fieldAt) {
|
|
|
12
14
|
const fields = yield* Effect.forEach(tuple.fields, fieldAt);
|
|
13
15
|
const encode = Schema.encodeUnknownEffect(Schema.Tuple(fields));
|
|
14
16
|
const columns = sql.csv(tuple.fields.map((field) => sql `${sql(field)}`));
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const rows = yield* Effect.forEach(tuple.values, (value) => encode(value));
|
|
18
|
+
if (rows.length === 0) {
|
|
19
|
+
return sql `1 = 0`;
|
|
20
|
+
}
|
|
21
|
+
if (sql.onDialectOrElse({ orElse: () => false, pg: () => true })) {
|
|
22
|
+
const relation = yield* tupleUnnest(tuple.types, rows).pipe(Effect.provideService(SqlClient.SqlClient, sql));
|
|
23
|
+
return sql `(${columns}) in (select * from ${relation})`;
|
|
24
|
+
}
|
|
25
|
+
return sql.or(rows.map((row) => sql `(${columns}) = (${sql.csv(row.map((entry) => sql `${entry}`))})`));
|
|
18
26
|
});
|
|
19
27
|
}
|
|
20
28
|
//# sourceMappingURL=tupleIn.js.map
|
package/dist/tupleIn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tupleIn.js","sourceRoot":"","sources":["../src/tupleIn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"tupleIn.js","sourceRoot":"","sources":["../src/tupleIn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA4B9C,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;AAajD,MAAM,UAAU,OAAO,CACtB,KAAa,EACb,MAAe,EACf,MAAuF,EACvF,KAAyD;IAEzD,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAsB,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC;AAClH,CAAC;AAED,MAAM,UAAU,cAAc,CAC7B,KAAsC,EACtC,GAAwB,EACxB,OAA8E;IAE9E,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IACjC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAA,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACzE,MAAM,IAAI,GAAoC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5G,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,GAAG,CAAA,OAAO,CAAC;QACnB,CAAC;QACD,IAAI,GAAG,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;YAC7G,OAAO,GAAG,CAAA,IAAI,OAAO,uBAAuB,QAAQ,GAAG,CAAC;QACzD,CAAC;QACD,OAAO,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAA,IAAI,OAAO,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAA,GAAG,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
import type { TupleColumnType } from "#tupleIn.ts";
|
|
4
|
+
export declare const tupleUnnest: (types: readonly TupleColumnType[], rows: readonly (readonly unknown[])[]) => Effect.Effect<import("effect/sql/Statement").Statement<import("effect/sql/SqlConnection").Row>, never, SqlClient.SqlClient>;
|
|
5
|
+
//# sourceMappingURL=tupleUnnest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tupleUnnest.d.ts","sourceRoot":"","sources":["../src/tupleUnnest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,WAAW,2MAoBtB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Effect, Result } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
export const tupleUnnest = Effect.fnUntraced(function* (types, rows) {
|
|
4
|
+
const sql = yield* SqlClient.SqlClient;
|
|
5
|
+
const pg = yield* Effect.promise(() => import("@effect/sql-pg/PgTypes"));
|
|
6
|
+
const arrays = types.map((type, index) => {
|
|
7
|
+
const entries = rows.map((row) => row[index]);
|
|
8
|
+
const values = entries.map((value) => (pg.isParameter(value) ? value.value : value));
|
|
9
|
+
// String-encoded fields rely on PostgreSQL input casts, including numerics and dates.
|
|
10
|
+
const first = entries.find((value) => value !== null && value !== undefined);
|
|
11
|
+
let oid = pg.OID[type];
|
|
12
|
+
if (pg.isParameter(first)) {
|
|
13
|
+
oid = first.oid;
|
|
14
|
+
}
|
|
15
|
+
else if (first instanceof Date) {
|
|
16
|
+
oid = pg.OID.timestamptz;
|
|
17
|
+
}
|
|
18
|
+
else if (values.every((value) => value === null || typeof value === "string")) {
|
|
19
|
+
oid = pg.OID.text;
|
|
20
|
+
}
|
|
21
|
+
const parameter = Result.getOrThrow(pg.array(values, oid));
|
|
22
|
+
return sql `${parameter}::${sql.literal(type)}[]`;
|
|
23
|
+
});
|
|
24
|
+
return sql `unnest(${sql.csv(arrays)})`;
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=tupleUnnest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tupleUnnest.js","sourceRoot":"","sources":["../src/tupleUnnest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAiC,EAAE,IAAqC;IAC/H,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACrF,sFAAsF;QACtF,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC,CAAC;QAC7E,IAAI,GAAG,GAAW,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACjB,CAAC;aAAM,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;YAClC,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC;QAC1B,CAAC;aAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;YACjF,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC;QACnB,CAAC;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3D,OAAO,GAAG,CAAA,GAAG,SAAS,KAAK,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;IAClD,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,CAAA,UAAU,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;AACxC,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/sql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Schema-derived repositories over native Effect SQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -50,9 +50,9 @@
|
|
|
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.15.
|
|
55
|
-
"@shivaedev/test-story": "0.
|
|
53
|
+
"@shivaedev/quality": "0.52.0",
|
|
54
|
+
"@shivaedev/test-sql": "0.15.2",
|
|
55
|
+
"@shivaedev/test-story": "0.31.2",
|
|
56
56
|
"@types/node": "24.19.2",
|
|
57
57
|
"effect": "4.0.6",
|
|
58
58
|
"typescript": "7.0.2",
|
|
@@ -31,7 +31,15 @@ export const makePostgresClient = Effect.fn("Sql.makePostgresClient")(function*
|
|
|
31
31
|
[PgClient.TypeId]: PgClient.TypeId,
|
|
32
32
|
config,
|
|
33
33
|
json: (value: unknown) => Statement.fragment([json(value)]),
|
|
34
|
-
listen: (channel: string) =>
|
|
34
|
+
listen: (channel: string) =>
|
|
35
|
+
Effect.gen(function* () {
|
|
36
|
+
// TTL checkout retains expired leases until its scope closes; LISTEN must not pin those old backends for another lifetime.
|
|
37
|
+
if (pool.config.connectionTTL !== undefined) {
|
|
38
|
+
yield* pool.use(() => Effect.void);
|
|
39
|
+
}
|
|
40
|
+
const connection = yield* pool.reserve;
|
|
41
|
+
return yield* connection.listen(channel);
|
|
42
|
+
}),
|
|
35
43
|
notify: (channel: string, payload: string) =>
|
|
36
44
|
pool.use((connection) => connection.query("SELECT pg_notify($1, $2)", [channel, payload])).pipe(Effect.asVoid),
|
|
37
45
|
}) satisfies PgClient.PgClient;
|
package/src/latestPerGroup.ts
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
2
|
import type { Model } from "effect/schema";
|
|
3
|
-
import
|
|
3
|
+
import { SqlClient } from "effect/sql";
|
|
4
4
|
import type { Fragment } from "effect/sql/Statement";
|
|
5
5
|
import { pinnedGroups } from "#pinnedGroups.ts";
|
|
6
6
|
import type { LatestPerGroup } from "#repository.ts";
|
|
7
7
|
import { makeFilter } from "#repositoryFilter.ts";
|
|
8
|
+
import { tupleUnnest } from "#tupleUnnest.ts";
|
|
9
|
+
|
|
10
|
+
function groupMasks(input: readonly (readonly unknown[])[]) {
|
|
11
|
+
const masks = new Map<string, (readonly unknown[])[]>();
|
|
12
|
+
for (const group of input) {
|
|
13
|
+
const mask = group.map((value) => (value === null ? "0" : "1")).join("");
|
|
14
|
+
const groups = masks.get(mask) ?? [];
|
|
15
|
+
groups.push(group);
|
|
16
|
+
masks.set(mask, groups);
|
|
17
|
+
}
|
|
18
|
+
return [...masks.values()];
|
|
19
|
+
}
|
|
8
20
|
|
|
9
21
|
export function latestPerGroup(model: Model.Any, sql: SqlClient.SqlClient, tableName: string) {
|
|
10
22
|
const { fieldAt, where } = makeFilter(model, sql);
|
|
23
|
+
function groupSource(groups: readonly (readonly unknown[])[], partitionBy: readonly string[], fromTuple: boolean) {
|
|
24
|
+
const partitions = sql.csv(partitionBy.map((key) => sql`${sql(key)}`));
|
|
25
|
+
if (fromTuple) {
|
|
26
|
+
const mask = partitionBy.map((key, index) => (groups[0]?.[index] === null ? sql`${sql(key)} is null` : sql`${sql(key)} is not null`));
|
|
27
|
+
return sql`select ${partitions} from ${sql("__effect_sql_tuple_groups")} where ${sql.and(mask)}`;
|
|
28
|
+
}
|
|
29
|
+
const values = sql.join(" union ", false)(groups.map((group) => sql`select ${sql.csv(group.map((value) => sql`${value}`))}`));
|
|
30
|
+
// The empty projection gives scalar pins their stored column types.
|
|
31
|
+
return sql`select ${partitions} from ${sql(tableName)} where false union ${values}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
return Effect.fnUntraced(function* (query: LatestPerGroup<Model.Any>, order: Fragment, rowLimit: number) {
|
|
12
35
|
yield* Effect.forEach(query.partitionBy, fieldAt);
|
|
13
36
|
const predicate = yield* where(query.where, true);
|
|
@@ -20,28 +43,32 @@ export function latestPerGroup(model: Model.Any, sql: SqlClient.SqlClient, table
|
|
|
20
43
|
}
|
|
21
44
|
if (pinned !== undefined && sql.onDialectOrElse({ orElse: () => false, pg: () => true })) {
|
|
22
45
|
const remaining = yield* where(pinned.remaining, true);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
const tuple = pinned.tuple;
|
|
47
|
+
const tupleRows = pinned.groups.map((group) => tuple?.fields.map((key) => group[query.partitionBy.indexOf(key)]) ?? group);
|
|
48
|
+
const tupleSource =
|
|
49
|
+
tuple === undefined
|
|
50
|
+
? sql``
|
|
51
|
+
: sql`with ${sql("__effect_sql_tuple_groups")} as materialized (
|
|
52
|
+
select distinct * from ${yield* tupleUnnest(tuple.types, tupleRows).pipe(Effect.provideService(SqlClient.SqlClient, sql))}
|
|
53
|
+
as ${sql("__effect_sql_tuples")} (${sql.csv(tuple.fields.map((key) => sql`${sql(key)}`))})
|
|
54
|
+
), ${sql("__effect_sql_tuple_limit")} as (select ${rowLimit}::bigint as ${sql("limit")})`;
|
|
55
|
+
const limit = tuple === undefined ? sql`${rowLimit}` : sql`(select ${sql("limit")} from ${sql("__effect_sql_tuple_limit")})`;
|
|
56
|
+
|
|
57
|
+
const branches = groupMasks(pinned.groups).map((groups) => {
|
|
58
|
+
const source = groupSource(groups, query.partitionBy, tuple !== undefined);
|
|
32
59
|
const matches = query.partitionBy.map((key, index) =>
|
|
33
60
|
groups[0]?.[index] === null ? sql`${sql(key)} is null` : sql`${sql(key)} = ${sql("__effect_sql_groups")}.${sql(key)}`,
|
|
34
61
|
);
|
|
35
|
-
//
|
|
62
|
+
// Null masks keep ordinary equality available to each non-null index column.
|
|
36
63
|
return sql`select ${sql("__effect_sql_latest")}.* from (
|
|
37
|
-
|
|
64
|
+
${source}
|
|
38
65
|
) as ${sql("__effect_sql_groups")} cross join lateral (
|
|
39
|
-
select ${columns} from ${sql(tableName)} where ${sql.and([...matches, remaining])} ${order} limit ${
|
|
66
|
+
select ${columns} from ${sql(tableName)} where ${sql.and([...matches, remaining])} ${order} limit ${limit}
|
|
40
67
|
) as ${sql("__effect_sql_latest")}`;
|
|
41
68
|
});
|
|
42
69
|
const ordering = sql.csv(query.orderBy.map(({ field, direction }) => sql`${sql(field)} ${sql.literal(direction === "desc" ? "desc" : "asc")}`));
|
|
43
70
|
const rows =
|
|
44
|
-
yield* sql
|
|
71
|
+
yield* sql`${tupleSource} select ${columns} from (${sql.join(" union all ")(branches)}) as ${sql("__effect_sql_pinned")} order by ${partitions}, ${ordering}`;
|
|
45
72
|
return yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct(model.fields)))(rows);
|
|
46
73
|
}
|
|
47
74
|
let rankColumn = "__effect_sql_rank";
|
package/src/listenForChanges.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Clock, Deferred,
|
|
1
|
+
import { Clock, Deferred, Duration, Effect, Queue, Random, Scope } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import type { SqlError } from "effect/sql/SqlError";
|
|
4
4
|
import { decodeKeys, resyncPayload } from "./notifyPayload.ts";
|
|
@@ -30,6 +30,9 @@ export const listenForChanges = Effect.fn("PostgresChanges.listenForChanges")(fu
|
|
|
30
30
|
Effect.gen(function* () {
|
|
31
31
|
const scope = yield* listenerScope(timeout);
|
|
32
32
|
const notifications = yield* (options.listenerClient ?? client).listen(channel).pipe(Scope.provide(scope), deadline);
|
|
33
|
+
const interval = Duration.toMillis(Duration.fromInputUnsafe(options.heartbeatInterval ?? "5 seconds"));
|
|
34
|
+
const lifetime = Duration.toMillis(Duration.fromInputUnsafe(options.connectionTTL ?? Duration.infinity));
|
|
35
|
+
const renewAt = (yield* Clock.currentTimeMillis) + (lifetime === 0 ? interval : lifetime);
|
|
33
36
|
const token = JSON.stringify({ heartbeat: `${yield* Random.nextInt}:${yield* Random.nextInt}` });
|
|
34
37
|
let ack = yield* Deferred.make<void>();
|
|
35
38
|
function receive(payload: string) {
|
|
@@ -64,13 +67,14 @@ export const listenForChanges = Effect.fn("PostgresChanges.listenForChanges")(fu
|
|
|
64
67
|
lostAt = undefined;
|
|
65
68
|
}
|
|
66
69
|
yield* Deferred.succeed(ready, undefined);
|
|
67
|
-
|
|
68
|
-
Effect.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
while (true) {
|
|
71
|
+
yield* Effect.sleep(Math.min(interval, Math.max(0, renewAt - (yield* Clock.currentTimeMillis))));
|
|
72
|
+
if ((yield* Clock.currentTimeMillis) >= renewAt) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
ack = yield* Deferred.make<void>();
|
|
76
|
+
yield* Effect.andThen(send, Deferred.await(ack)).pipe(deadline);
|
|
77
|
+
}
|
|
74
78
|
});
|
|
75
79
|
yield* Effect.raceFirst(listening, monitor);
|
|
76
80
|
}).pipe(Effect.onExit(() => resync)),
|
|
@@ -90,9 +94,8 @@ export const listenForChanges = Effect.fn("PostgresChanges.listenForChanges")(fu
|
|
|
90
94
|
}
|
|
91
95
|
lostAt = yield* Clock.currentTimeMillis;
|
|
92
96
|
yield* Effect.logWarning("PostgreSQL change listener lost; resynchronizing live streams", cause);
|
|
93
|
-
}),
|
|
97
|
+
}).pipe(Effect.andThen(Effect.sleep(options.retryDelay ?? "1 second"))),
|
|
94
98
|
),
|
|
95
|
-
Effect.andThen(Effect.sleep(options.retryDelay ?? "1 second")),
|
|
96
99
|
),
|
|
97
100
|
).pipe(Effect.forkScoped);
|
|
98
101
|
yield* Deferred.await(ready);
|
package/src/pinnedGroups.ts
CHANGED
|
@@ -18,7 +18,7 @@ export function pinnedGroups(
|
|
|
18
18
|
(value) => typeof value === "object" && value !== null && "in" in value && Array.isArray(value.in) && value.in.length === 0,
|
|
19
19
|
)
|
|
20
20
|
) {
|
|
21
|
-
return Effect.succeed({ groups: [], remaining: input });
|
|
21
|
+
return Effect.succeed({ groups: [], remaining: input, tuple: undefined });
|
|
22
22
|
}
|
|
23
23
|
const exactTuple = tuple !== undefined && tuple.fields.length === partitionBy.length && partitionBy.every((key) => tuple.fields.includes(key));
|
|
24
24
|
let groups: readonly (readonly unknown[])[] = [[]];
|
|
@@ -40,7 +40,9 @@ export function pinnedGroups(
|
|
|
40
40
|
const { fieldAt } = makeFilter(model, sql);
|
|
41
41
|
return Effect.gen(function* () {
|
|
42
42
|
const fields = yield* Effect.forEach(partitionBy, fieldAt);
|
|
43
|
-
const groupsEncoded = yield* Effect.forEach(groups, (group) =>
|
|
44
|
-
|
|
43
|
+
const groupsEncoded: readonly (readonly unknown[])[] = yield* Effect.forEach(groups, (group) =>
|
|
44
|
+
Schema.encodeUnknownEffect(Schema.Tuple(fields))(group),
|
|
45
|
+
);
|
|
46
|
+
return { groups: groupsEncoded, remaining, tuple: exactTuple ? tuple : undefined };
|
|
45
47
|
});
|
|
46
48
|
}
|
package/src/postgresChanges.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface PostgresChangesOptions {
|
|
|
19
19
|
readonly channel: string;
|
|
20
20
|
readonly client: PostgresChangeClient;
|
|
21
21
|
readonly connectionSetting?: string;
|
|
22
|
+
readonly "connectionTTL"?: Duration.Input | undefined;
|
|
22
23
|
readonly heartbeatInterval?: Duration.Input;
|
|
23
24
|
readonly heartbeatTimeout?: Duration.Input;
|
|
24
25
|
readonly listenerClient?: Pick<PostgresChangeClient, "listen">;
|
package/src/postgresLayer.ts
CHANGED
|
@@ -10,7 +10,10 @@ import { databaseAddress } from "#databaseAddress.ts";
|
|
|
10
10
|
|
|
11
11
|
export interface PostgresOptions extends SqlNameTransforms {
|
|
12
12
|
readonly applicationName?: string | undefined;
|
|
13
|
+
readonly "connectionTTL"?: PgPool.Config["connectionTTL"];
|
|
14
|
+
readonly idleTimeout?: PgPool.Config["idleTimeout"];
|
|
13
15
|
readonly maxConnections?: number | undefined;
|
|
16
|
+
readonly minConnections?: PgPool.Config["minConnections"];
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
export function postgresLayer(url: Redacted.Redacted<string>, options?: PostgresOptions) {
|
package/src/repository.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { makeCompositeCrud, type PrimaryKey, type Repository } from "#compositeR
|
|
|
6
6
|
import { type ConflictColumns, type ConflictWrites, makeConflictWrites } from "#conflictWrites.ts";
|
|
7
7
|
import { makeInsertFromSelect, type SelectWrites } from "#insertFromSelect.ts";
|
|
8
8
|
import { repositoryChanges } from "#repositoryChanges.ts";
|
|
9
|
+
import { type BucketRegistrationError, type RepositoryOptions, validateRepositoryCollection } from "#repositoryCollection.ts";
|
|
9
10
|
import type { FindManyFilter, RepositoryFilter, RepositoryListFilter } from "#repositoryFilter.ts";
|
|
10
11
|
import { makeQueries, type Queries } from "#repositoryQueries.ts";
|
|
11
12
|
import { type IdColumn, makeVersionedWrites, type VersionColumn, type VersionedWrites } from "#versionedRepository.ts";
|
|
@@ -39,12 +40,6 @@ export class OptimisticConcurrencyError extends Data.TaggedError("OptimisticConc
|
|
|
39
40
|
readonly reason: "missing" | "stale";
|
|
40
41
|
}> {}
|
|
41
42
|
|
|
42
|
-
interface RepositoryOptions<TId> {
|
|
43
|
-
readonly idColumn: TId;
|
|
44
|
-
readonly spanPrefix: string;
|
|
45
|
-
readonly tableName: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
43
|
type VersionedRepository<TModel extends Model.Any, TId extends PrimaryKey<TModel>> = Omit<
|
|
49
44
|
Repository<TModel, TId>,
|
|
50
45
|
keyof VersionedWrites<TModel, TId>
|
|
@@ -67,7 +62,7 @@ export function makeRepository<
|
|
|
67
62
|
): Effect.Effect<
|
|
68
63
|
VersionedRepository<TModel, TId> &
|
|
69
64
|
Pick<ConflictWrites<TModel, never, TModel["fields"][Extract<TWhere, keyof TModel["fields"]>]["EncodingServices"]>, "insertOrIgnore">,
|
|
70
|
-
|
|
65
|
+
BucketRegistrationError,
|
|
71
66
|
SqlClient.SqlClient
|
|
72
67
|
>;
|
|
73
68
|
export function makeRepository<TModel extends Model.Any, const TId extends PrimaryKey<TModel>>(
|
|
@@ -77,7 +72,7 @@ export function makeRepository<TModel extends Model.Any, const TId extends Prima
|
|
|
77
72
|
readonly conflictColumns?: never;
|
|
78
73
|
readonly conflictWhere?: never;
|
|
79
74
|
},
|
|
80
|
-
): Effect.Effect<VersionedRepository<TModel, TId>,
|
|
75
|
+
): Effect.Effect<VersionedRepository<TModel, TId>, BucketRegistrationError, SqlClient.SqlClient>;
|
|
81
76
|
export function makeRepository<
|
|
82
77
|
TModel extends Model.Any,
|
|
83
78
|
const TId extends PrimaryKey<TModel>,
|
|
@@ -100,13 +95,17 @@ export function makeRepository<
|
|
|
100
95
|
TConflict[number] | (TId extends readonly PropertyKey[] ? TId[number] : TId),
|
|
101
96
|
TModel["fields"][Extract<TWhere, keyof TModel["fields"]>]["EncodingServices"]
|
|
102
97
|
>,
|
|
103
|
-
|
|
98
|
+
BucketRegistrationError,
|
|
104
99
|
SqlClient.SqlClient
|
|
105
100
|
>;
|
|
106
101
|
export function makeRepository<TModel extends Model.Any, const TId extends PrimaryKey<TModel>>(
|
|
107
102
|
model: TModel,
|
|
108
103
|
options: RepositoryOptions<TId> & { readonly versionColumn?: never; readonly conflictColumns?: never; readonly conflictWhere?: never },
|
|
109
|
-
): Effect.Effect<
|
|
104
|
+
): Effect.Effect<
|
|
105
|
+
Repository<TModel, TId> & BatchWrites<TModel> & SelectWrites<TModel> & Queries<TModel>,
|
|
106
|
+
BucketRegistrationError,
|
|
107
|
+
SqlClient.SqlClient
|
|
108
|
+
>;
|
|
110
109
|
export function makeRepository<TModel extends Model.Any, TId extends IdColumn<TModel>>(
|
|
111
110
|
model: TModel,
|
|
112
111
|
options: RepositoryOptions<TId | readonly [TId, TId, ...TId[]]> & {
|
|
@@ -114,8 +113,9 @@ export function makeRepository<TModel extends Model.Any, TId extends IdColumn<TM
|
|
|
114
113
|
readonly conflictColumns?: readonly string[];
|
|
115
114
|
readonly conflictWhere?: Readonly<Record<string, unknown>>;
|
|
116
115
|
},
|
|
117
|
-
): Effect.Effect<unknown,
|
|
116
|
+
): Effect.Effect<unknown, BucketRegistrationError, SqlClient.SqlClient> {
|
|
118
117
|
return Effect.fnUntraced(function* () {
|
|
118
|
+
yield* validateRepositoryCollection(model.fields, options);
|
|
119
119
|
const sql = yield* SqlClient.SqlClient;
|
|
120
120
|
const composite = typeof options.idColumn === "object";
|
|
121
121
|
const idColumns = (composite ? options.idColumn : [options.idColumn]).map(String);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Data, Effect, Order, type Schema } from "effect";
|
|
2
|
+
import { declaredBuckets } from "@shivaedev/changes/buckets.ts";
|
|
3
|
+
|
|
4
|
+
export interface RepositoryCollection {
|
|
5
|
+
readonly bucket: Readonly<Record<string, unknown>>;
|
|
6
|
+
readonly name: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RepositoryOptions<TId> {
|
|
10
|
+
readonly collection?: RepositoryCollection;
|
|
11
|
+
readonly idColumn: TId;
|
|
12
|
+
readonly spanPrefix: string;
|
|
13
|
+
readonly tableName: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class BucketRegistrationError extends Data.TaggedError("BucketRegistrationError")<{
|
|
17
|
+
readonly tableName: string;
|
|
18
|
+
readonly collectionName: string | undefined;
|
|
19
|
+
readonly modelFields: readonly string[];
|
|
20
|
+
readonly collectionFields: readonly string[];
|
|
21
|
+
}> {
|
|
22
|
+
override get message() {
|
|
23
|
+
return `Bucket registration for ${this.tableName}: model fields ${JSON.stringify(this.modelFields)} disagree with collection ${this.collectionName ?? "(missing)"} fields ${JSON.stringify(this.collectionFields)}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const validateRepositoryCollection = Effect.fnUntraced(function* (
|
|
28
|
+
fields: Schema.Struct.Fields,
|
|
29
|
+
options: { readonly tableName: string; readonly collection?: RepositoryCollection },
|
|
30
|
+
) {
|
|
31
|
+
const modelFields = declaredBuckets(fields)
|
|
32
|
+
.map(({ field }) => field)
|
|
33
|
+
.sort(Order.String);
|
|
34
|
+
const collectionFields = Object.keys(options.collection?.bucket ?? {}).sort(Order.String);
|
|
35
|
+
if (
|
|
36
|
+
(options.collection === undefined && modelFields.length === 0)
|
|
37
|
+
|| (options.collection?.name === options.tableName
|
|
38
|
+
&& modelFields.length === collectionFields.length
|
|
39
|
+
&& modelFields.every((field, index) => field === collectionFields[index]))
|
|
40
|
+
) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
return yield* Effect.fail(
|
|
44
|
+
new BucketRegistrationError({ collectionFields, collectionName: options.collection?.name, modelFields, tableName: options.tableName }),
|
|
45
|
+
);
|
|
46
|
+
});
|