@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +25 -10
  3. package/dist/adapters/makePostgresClient.d.ts.map +1 -1
  4. package/dist/adapters/makePostgresClient.js +8 -1
  5. package/dist/adapters/makePostgresClient.js.map +1 -1
  6. package/dist/latestPerGroup.d.ts +1 -1
  7. package/dist/latestPerGroup.d.ts.map +1 -1
  8. package/dist/latestPerGroup.js +37 -13
  9. package/dist/latestPerGroup.js.map +1 -1
  10. package/dist/listenForChanges.d.ts.map +1 -1
  11. package/dist/listenForChanges.js +11 -5
  12. package/dist/listenForChanges.js.map +1 -1
  13. package/dist/pinnedGroups.d.ts +9 -1
  14. package/dist/pinnedGroups.d.ts.map +1 -1
  15. package/dist/pinnedGroups.js +2 -2
  16. package/dist/pinnedGroups.js.map +1 -1
  17. package/dist/postgresChanges.d.ts +1 -0
  18. package/dist/postgresChanges.d.ts.map +1 -1
  19. package/dist/postgresChanges.js.map +1 -1
  20. package/dist/postgresLayer.d.ts +3 -0
  21. package/dist/postgresLayer.d.ts.map +1 -1
  22. package/dist/postgresLayer.js.map +1 -1
  23. package/dist/repository.d.ts +5 -9
  24. package/dist/repository.d.ts.map +1 -1
  25. package/dist/repository.js +2 -0
  26. package/dist/repository.js.map +1 -1
  27. package/dist/repositoryCollection.d.ts +28 -0
  28. package/dist/repositoryCollection.d.ts.map +1 -0
  29. package/dist/repositoryCollection.js +21 -0
  30. package/dist/repositoryCollection.js.map +1 -0
  31. package/dist/testing.d.ts +2 -1
  32. package/dist/testing.d.ts.map +1 -1
  33. package/dist/testing.js +1 -1
  34. package/dist/testing.js.map +1 -1
  35. package/dist/tupleIn.d.ts +4 -2
  36. package/dist/tupleIn.d.ts.map +1 -1
  37. package/dist/tupleIn.js +13 -5
  38. package/dist/tupleIn.js.map +1 -1
  39. package/dist/tupleUnnest.d.ts +5 -0
  40. package/dist/tupleUnnest.d.ts.map +1 -0
  41. package/dist/tupleUnnest.js +26 -0
  42. package/dist/tupleUnnest.js.map +1 -0
  43. package/package.json +4 -4
  44. package/src/adapters/makePostgresClient.ts +9 -1
  45. package/src/latestPerGroup.ts +41 -14
  46. package/src/listenForChanges.ts +13 -10
  47. package/src/pinnedGroups.ts +5 -3
  48. package/src/postgresChanges.ts +1 -0
  49. package/src/postgresLayer.ts +3 -0
  50. package/src/repository.ts +11 -11
  51. package/src/repositoryCollection.ts +46 -0
  52. package/src/testing.ts +7 -3
  53. package/src/tupleIn.ts +40 -7
  54. package/src/tupleUnnest.ts +25 -0
package/src/testing.ts CHANGED
@@ -76,8 +76,12 @@ export class PublishedKeysMismatch extends Data.TaggedError("PublishedKeysMismat
76
76
  }
77
77
  }
78
78
 
79
- export function expectPublished(keys: readonly unknown[]) {
80
- return Effect.fn("Changes.expectPublished")(function* <A, E, R>(body: Effect.Effect<A, E, R>) {
79
+ export function expectPublished(keys: readonly unknown[]): <A, E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E | PublishedKeysMismatch, R>;
80
+ export function expectPublished<A>(
81
+ keys: (result: A) => readonly unknown[],
82
+ ): <E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E | PublishedKeysMismatch, R>;
83
+ export function expectPublished<A>(keys: readonly unknown[] | ((result: A) => readonly unknown[])) {
84
+ return Effect.fn("Changes.expectPublished")(function* <E, R>(body: Effect.Effect<A, E, R>) {
81
85
  const observer = yield* ChangeObserver;
82
86
  const actual = new Set<unknown>();
83
87
  const result = yield* body.pipe(
@@ -90,7 +94,7 @@ export function expectPublished(keys: readonly unknown[]) {
90
94
  return observer(event);
91
95
  }),
92
96
  );
93
- const expected = new Set(keys);
97
+ const expected = new Set(typeof keys === "function" ? keys(result) : keys);
94
98
  if (actual.size !== expected.size || [...expected].some((key) => !actual.has(key))) {
95
99
  return yield* Effect.fail(new PublishedKeysMismatch({ actual: [...actual], expected: [...expected] }));
96
100
  }
package/src/tupleIn.ts CHANGED
@@ -1,6 +1,33 @@
1
1
  import { Effect, Schema } from "effect";
2
2
  import type { Model } from "effect/schema";
3
- import type { SqlClient } from "effect/sql";
3
+ import { SqlClient } from "effect/sql";
4
+ import { tupleUnnest } from "#tupleUnnest.ts";
5
+
6
+ export type TupleColumnType =
7
+ | "bool"
8
+ | "bpchar"
9
+ | "bytea"
10
+ | "cidr"
11
+ | "date"
12
+ | "float4"
13
+ | "float8"
14
+ | "inet"
15
+ | "int2"
16
+ | "int4"
17
+ | "int8"
18
+ | "interval"
19
+ | "jsonb"
20
+ | "name"
21
+ | "numeric"
22
+ | "oid"
23
+ | "regclass"
24
+ | "text"
25
+ | "time"
26
+ | "timestamp"
27
+ | "timestamptz"
28
+ | "timetz"
29
+ | "uuid"
30
+ | "varchar";
4
31
 
5
32
  export const tupleFilter = Symbol("sql/tupleIn");
6
33
  type Row<TModel extends Model.Any> = Schema.Struct.Type<TModel["fields"]>;
@@ -11,6 +38,7 @@ export interface TupleFilter<TModel extends Model.Any> {
11
38
  readonly model: TModel;
12
39
  readonly fields: readonly Key<TModel>[];
13
40
  readonly values: readonly (readonly unknown[])[];
41
+ readonly types: readonly TupleColumnType[];
14
42
  };
15
43
  }
16
44
 
@@ -18,8 +46,9 @@ export function tupleIn<TModel extends Model.Any, const TFields extends readonly
18
46
  model: TModel,
19
47
  fields: TFields,
20
48
  values: readonly { readonly [TIndex in keyof TFields]: Row<TModel>[TFields[TIndex]] }[],
49
+ types: Readonly<Record<TFields[number], TupleColumnType>>,
21
50
  ): TupleFilter<TModel> {
22
- return { [tupleFilter]: { fields, model, values } };
51
+ return { [tupleFilter]: { fields, model, types: fields.map((field: TFields[number]) => types[field]), values } };
23
52
  }
24
53
 
25
54
  export function tuplePredicate(
@@ -35,10 +64,14 @@ export function tuplePredicate(
35
64
  const fields = yield* Effect.forEach(tuple.fields, fieldAt);
36
65
  const encode = Schema.encodeUnknownEffect(Schema.Tuple(fields));
37
66
  const columns = sql.csv(tuple.fields.map((field) => sql`${sql(field)}`));
38
- // Row comparisons retain column type inference for string-encoded numerics without PostgreSQL's deeply nested row-IN rewrite.
39
- const rows = yield* Effect.forEach(tuple.values, (value) =>
40
- Effect.map(encode(value), (encoded) => sql`(${columns}) = (${sql.csv(encoded.map((entry) => sql`${entry}`))})`),
41
- );
42
- return rows.length === 0 ? sql`1 = 0` : sql.or(rows);
67
+ const rows: readonly (readonly unknown[])[] = yield* Effect.forEach(tuple.values, (value) => encode(value));
68
+ if (rows.length === 0) {
69
+ return sql`1 = 0`;
70
+ }
71
+ if (sql.onDialectOrElse({ orElse: () => false, pg: () => true })) {
72
+ const relation = yield* tupleUnnest(tuple.types, rows).pipe(Effect.provideService(SqlClient.SqlClient, sql));
73
+ return sql`(${columns}) in (select * from ${relation})`;
74
+ }
75
+ return sql.or(rows.map((row) => sql`(${columns}) = (${sql.csv(row.map((entry) => sql`${entry}`))})`));
43
76
  });
44
77
  }
@@ -0,0 +1,25 @@
1
+ import { Effect, Result } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import type { TupleColumnType } from "#tupleIn.ts";
4
+
5
+ export const tupleUnnest = Effect.fnUntraced(function* (types: readonly TupleColumnType[], rows: readonly (readonly unknown[])[]) {
6
+ const sql = yield* SqlClient.SqlClient;
7
+ const pg = yield* Effect.promise(() => import("@effect/sql-pg/PgTypes"));
8
+ const arrays = types.map((type, index) => {
9
+ const entries = rows.map((row) => row[index]);
10
+ const values = entries.map((value) => (pg.isParameter(value) ? value.value : value));
11
+ // String-encoded fields rely on PostgreSQL input casts, including numerics and dates.
12
+ const first = entries.find((value) => value !== null && value !== undefined);
13
+ let oid: number = pg.OID[type];
14
+ if (pg.isParameter(first)) {
15
+ oid = first.oid;
16
+ } else if (first instanceof Date) {
17
+ oid = pg.OID.timestamptz;
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
+ });