@shivaedev/sql 0.34.0 → 0.36.1

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 (124) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +3 -3
  3. package/dist/aggregateColumn.d.ts +3 -3
  4. package/dist/aggregateColumn.d.ts.map +1 -1
  5. package/dist/aggregateColumn.js +4 -2
  6. package/dist/aggregateColumn.js.map +1 -1
  7. package/dist/batchWrites.d.ts +4 -4
  8. package/dist/batchWrites.d.ts.map +1 -1
  9. package/dist/batchWrites.js +8 -5
  10. package/dist/batchWrites.js.map +1 -1
  11. package/dist/batchedDelete.js +1 -1
  12. package/dist/batchedDelete.js.map +1 -1
  13. package/dist/compositeRepository.d.ts +9 -5
  14. package/dist/compositeRepository.d.ts.map +1 -1
  15. package/dist/compositeRepository.js +12 -9
  16. package/dist/compositeRepository.js.map +1 -1
  17. package/dist/conditionalUpsert.d.ts +4 -4
  18. package/dist/conditionalUpsert.d.ts.map +1 -1
  19. package/dist/conditionalUpsert.js +7 -4
  20. package/dist/conditionalUpsert.js.map +1 -1
  21. package/dist/conflictWrites.d.ts +4 -4
  22. package/dist/conflictWrites.d.ts.map +1 -1
  23. package/dist/conflictWrites.js +11 -8
  24. package/dist/conflictWrites.js.map +1 -1
  25. package/dist/coordinateWorkerRelease.d.ts.map +1 -1
  26. package/dist/coordinateWorkerRelease.js +7 -1
  27. package/dist/coordinateWorkerRelease.js.map +1 -1
  28. package/dist/insertFromSelect.d.ts +3 -3
  29. package/dist/insertFromSelect.d.ts.map +1 -1
  30. package/dist/insertFromSelect.js +5 -3
  31. package/dist/insertFromSelect.js.map +1 -1
  32. package/dist/latestPerGroup.d.ts +2 -2
  33. package/dist/latestPerGroup.d.ts.map +1 -1
  34. package/dist/latestPerGroup.js +5 -4
  35. package/dist/latestPerGroup.js.map +1 -1
  36. package/dist/makeAggregate.d.ts +4 -3
  37. package/dist/makeAggregate.d.ts.map +1 -1
  38. package/dist/makeAggregate.js +35 -31
  39. package/dist/makeAggregate.js.map +1 -1
  40. package/dist/makeConflictClause.d.ts +3 -3
  41. package/dist/makeConflictClause.d.ts.map +1 -1
  42. package/dist/makeConflictClause.js +6 -4
  43. package/dist/makeConflictClause.js.map +1 -1
  44. package/dist/makeQueryClauses.d.ts +3 -3
  45. package/dist/makeQueryClauses.d.ts.map +1 -1
  46. package/dist/makeQueryClauses.js +5 -3
  47. package/dist/makeQueryClauses.js.map +1 -1
  48. package/dist/pinnedGroups.d.ts +3 -4
  49. package/dist/pinnedGroups.d.ts.map +1 -1
  50. package/dist/pinnedGroups.js +8 -10
  51. package/dist/pinnedGroups.js.map +1 -1
  52. package/dist/releaseWorkers.d.ts.map +1 -1
  53. package/dist/releaseWorkers.js +10 -4
  54. package/dist/releaseWorkers.js.map +1 -1
  55. package/dist/repository.d.ts +1 -1
  56. package/dist/repository.d.ts.map +1 -1
  57. package/dist/repository.js +7 -9
  58. package/dist/repository.js.map +1 -1
  59. package/dist/repositoryChanges.d.ts +2 -2
  60. package/dist/repositoryChanges.d.ts.map +1 -1
  61. package/dist/repositoryChanges.js +2 -1
  62. package/dist/repositoryChanges.js.map +1 -1
  63. package/dist/repositoryFilter.d.ts +4 -4
  64. package/dist/repositoryFilter.d.ts.map +1 -1
  65. package/dist/repositoryFilter.js +5 -3
  66. package/dist/repositoryFilter.js.map +1 -1
  67. package/dist/repositoryQueries.d.ts +3 -3
  68. package/dist/repositoryQueries.d.ts.map +1 -1
  69. package/dist/repositoryQueries.js +45 -41
  70. package/dist/repositoryQueries.js.map +1 -1
  71. package/dist/repositoryWrites.d.ts +9 -5
  72. package/dist/repositoryWrites.d.ts.map +1 -1
  73. package/dist/repositoryWrites.js +8 -7
  74. package/dist/repositoryWrites.js.map +1 -1
  75. package/dist/testing/WorkerRelease.d.ts +13 -0
  76. package/dist/testing/WorkerRelease.d.ts.map +1 -0
  77. package/dist/testing/WorkerRelease.js +4 -0
  78. package/dist/testing/WorkerRelease.js.map +1 -0
  79. package/dist/tupleIn.d.ts +1 -2
  80. package/dist/tupleIn.d.ts.map +1 -1
  81. package/dist/tupleIn.js +16 -17
  82. package/dist/tupleIn.js.map +1 -1
  83. package/dist/upsertResult.d.ts +2 -2
  84. package/dist/upsertResult.d.ts.map +1 -1
  85. package/dist/upsertResult.js +4 -2
  86. package/dist/upsertResult.js.map +1 -1
  87. package/dist/versionedRepository.d.ts +5 -5
  88. package/dist/versionedRepository.d.ts.map +1 -1
  89. package/dist/versionedRepository.js +5 -3
  90. package/dist/versionedRepository.js.map +1 -1
  91. package/dist/versionedStatements.d.ts +3 -3
  92. package/dist/versionedStatements.d.ts.map +1 -1
  93. package/dist/versionedStatements.js +12 -9
  94. package/dist/versionedStatements.js.map +1 -1
  95. package/package.json +8 -7
  96. package/src/aggregateColumn.ts +4 -3
  97. package/src/batchWrites.ts +10 -10
  98. package/src/batchedDelete.ts +1 -1
  99. package/src/compositeRepository.ts +15 -15
  100. package/src/conditionalUpsert.ts +7 -6
  101. package/src/conflictWrites.ts +14 -10
  102. package/src/coordinateWorkerRelease.ts +7 -1
  103. package/src/insertFromSelect.ts +6 -6
  104. package/src/latestPerGroup.ts +5 -4
  105. package/src/makeAggregate.ts +48 -47
  106. package/src/makeConflictClause.ts +6 -6
  107. package/src/makeQueryClauses.ts +6 -6
  108. package/src/pinnedGroups.ts +10 -14
  109. package/src/releaseWorkers.ts +16 -4
  110. package/src/repository.ts +8 -9
  111. package/src/repositoryChanges.ts +2 -6
  112. package/src/repositoryFilter.ts +5 -4
  113. package/src/repositoryQueries.ts +57 -55
  114. package/src/repositoryWrites.ts +9 -9
  115. package/src/testing/WorkerRelease.ts +13 -0
  116. package/src/tupleIn.ts +16 -18
  117. package/src/upsertResult.ts +4 -3
  118. package/src/versionedRepository.ts +6 -6
  119. package/src/versionedStatements.ts +15 -15
  120. package/dist/testing.d.ts +0 -27
  121. package/dist/testing.d.ts.map +0 -1
  122. package/dist/testing.js +0 -75
  123. package/dist/testing.js.map +0 -1
  124. package/src/testing.ts +0 -103
package/src/tupleIn.ts CHANGED
@@ -51,27 +51,25 @@ export function tupleIn<TModel extends Model.Any, const TFields extends readonly
51
51
  return { [tupleFilter]: { fields, model, types: fields.map((field: TFields[number]) => types[field]), values } };
52
52
  }
53
53
 
54
- export function tuplePredicate(
54
+ export const tuplePredicate = Effect.fnUntraced(function* (
55
55
  input: Partial<TupleFilter<Model.Any>>,
56
- sql: SqlClient.SqlClient,
57
56
  fieldAt: (key: string) => Effect.Effect<Schema.Constraint, Schema.SchemaError>,
58
57
  ) {
59
58
  const tuple = input[tupleFilter];
60
59
  if (tuple === undefined) {
61
- return Effect.succeed(undefined);
60
+ return undefined;
62
61
  }
63
- return Effect.gen(function* () {
64
- const fields = yield* Effect.forEach(tuple.fields, fieldAt);
65
- const encode = Schema.encodeUnknownEffect(Schema.Tuple(fields));
66
- const columns = sql.csv(tuple.fields.map((field) => sql`${sql(field)}`));
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}`))})`));
76
- });
77
- }
62
+ const sql = yield* SqlClient.SqlClient;
63
+ const fields = yield* Effect.forEach(tuple.fields, fieldAt);
64
+ const encode = Schema.encodeUnknownEffect(Schema.Tuple(fields));
65
+ const columns = sql.csv(tuple.fields.map((field) => sql`${sql(field)}`));
66
+ const rows: readonly (readonly unknown[])[] = yield* Effect.forEach(tuple.values, (value) => encode(value));
67
+ if (rows.length === 0) {
68
+ return sql`1 = 0`;
69
+ }
70
+ if (sql.onDialectOrElse({ orElse: () => false, pg: () => true })) {
71
+ const relation = yield* tupleUnnest(tuple.types, rows).pipe(Effect.provideService(SqlClient.SqlClient, sql));
72
+ return sql`(${columns}) in (select * from ${relation})`;
73
+ }
74
+ return sql.or(rows.map((row) => sql`(${columns}) = (${sql.csv(row.map((entry) => sql`${entry}`))})`));
75
+ });
@@ -1,15 +1,16 @@
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
4
 
5
5
  export interface UpsertRow<TRow> {
6
6
  readonly inserted: boolean;
7
7
  readonly row: TRow;
8
8
  }
9
9
 
10
- export function upsertReturning(sql: SqlClient.SqlClient) {
10
+ export const upsertReturning = Effect.fnUntraced(function* () {
11
+ const sql = yield* SqlClient.SqlClient;
11
12
  return sql`*, (xmax = 0) as ${sql("effectSqlInserted")}`;
12
- }
13
+ });
13
14
 
14
15
  export function makeUpsertDecoder(model: Model.Any) {
15
16
  const decode = Schema.decodeUnknownEffect(model);
@@ -1,12 +1,12 @@
1
1
  import { Effect, Option, Schema } from "effect";
2
2
  import type { Model } from "effect/schema";
3
- import type { SqlClient, SqlModel } from "effect/sql";
3
+ import { SqlClient, type SqlModel } from "effect/sql";
4
4
  import type { SqlError } from "effect/sql/SqlError";
5
5
  import type { PrimaryKey, Repository } from "#compositeRepository.ts";
6
6
  import type { OptimisticConcurrencyError } from "#repository.ts";
7
+ import type { RepositoryChanges } from "#repositoryChanges.ts";
7
8
  import type { UpdateInput } from "#repositoryUpdate.ts";
8
9
  import { type VersionedOptions, versionedStatements } from "#versionedStatements.ts";
9
- import type { RepositoryChanges } from "./repositoryChanges.ts";
10
10
 
11
11
  export type IdColumn<TModel extends Model.Any> = keyof TModel["Type"] & keyof TModel["update"]["Type"] & keyof TModel["fields"];
12
12
  export type VersionColumn<TModel extends Model.Any, TId extends PrimaryKey<TModel>> = {
@@ -43,13 +43,13 @@ export interface VersionedWrites<TModel extends Model.Any, TId extends PrimaryKe
43
43
  readonly updateVoid: (input: UpdateInput<TModel>) => Effect.Effect<void, WriteError, TModel["update"]["EncodingServices"]>;
44
44
  }
45
45
 
46
- export function makeVersionedWrites(
46
+ export const makeVersionedWrites = Effect.fnUntraced(function* (
47
47
  model: Model.Any,
48
- sql: SqlClient.SqlClient,
49
48
  options: VersionedOptions & { readonly changes: RepositoryChanges; readonly spanPrefix: string },
50
49
  ) {
50
+ const sql = yield* SqlClient.SqlClient;
51
51
  const { spanPrefix, changes } = options;
52
- const statements = versionedStatements(model, sql, options);
52
+ const statements = yield* versionedStatements(model, options);
53
53
  const decodeRow = Schema.decodeUnknownEffect(model);
54
54
  function insert(input: unknown) {
55
55
  return Effect.gen(function* () {
@@ -106,4 +106,4 @@ export function makeVersionedWrites(
106
106
  }).pipe(changes.write, Effect.withSpan(`${spanPrefix}.delete`));
107
107
  }
108
108
  return { delete: deleteRow, insert, insertVoid, update, updateVoid, ...(options.conflictColumns === undefined ? {} : { insertOrIgnore }) };
109
- }
109
+ });
@@ -1,6 +1,6 @@
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
4
  import { makeConflictClause } from "#makeConflictClause.ts";
5
5
  import type { OptimisticConcurrencyError } from "#repository.ts";
6
6
 
@@ -14,21 +14,21 @@ export interface VersionedOptions {
14
14
  readonly versionColumn: string;
15
15
  }
16
16
 
17
- export function versionedStatements(model: Model.Any, sql: SqlClient.SqlClient, options: VersionedOptions) {
17
+ export const versionedStatements = Effect.fnUntraced(function* (model: Model.Any, options: VersionedOptions) {
18
+ const sql = yield* SqlClient.SqlClient;
18
19
  const { tableName, idColumns, versionColumn, conflict } = options;
19
- const fields = Object.fromEntries(
20
- idColumns.map((column) => {
21
- const field = model.fields[column];
22
- if (field === undefined) {
23
- throw new Error(`Unknown ID column: ${column}`);
24
- }
25
- return [column, field];
26
- }),
27
- );
28
- const encodeKey = Schema.encodeEffect(Schema.Struct(fields));
20
+ const entries: [string, Schema.Constraint][] = [];
21
+ for (const column of idColumns) {
22
+ const field = model.fields[column];
23
+ if (field === undefined) {
24
+ return yield* Effect.die(new Error(`Unknown ID column: ${column}`));
25
+ }
26
+ entries.push([column, field]);
27
+ }
28
+ const encodeKey = Schema.encodeEffect(Schema.Struct(Object.fromEntries(entries)));
29
29
  const versionSchema = model.fields[versionColumn];
30
30
  if (versionSchema === undefined) {
31
- throw new Error(`Unknown version column: ${versionColumn}`);
31
+ return yield* Effect.die(new Error(`Unknown version column: ${versionColumn}`));
32
32
  }
33
33
  const encodeVersion = Schema.encodeEffect(versionSchema);
34
34
  const encodeInsert = Schema.encodeEffect(model.insert);
@@ -36,7 +36,7 @@ export function versionedStatements(model: Model.Any, sql: SqlClient.SqlClient,
36
36
  const record = Schema.decodeUnknownEffect(Schema.Record(Schema.String, Schema.Unknown));
37
37
  const validateVersion = Schema.decodeUnknownEffect(Version);
38
38
  const clause =
39
- options.conflictColumns === undefined ? undefined : makeConflictClause(model, sql, { ...options, conflictColumns: options.conflictColumns });
39
+ options.conflictColumns === undefined ? undefined : yield* makeConflictClause(model, { ...options, conflictColumns: options.conflictColumns });
40
40
  function key(input: unknown) {
41
41
  return idColumns.length === 1 ? { [idColumns[0] ?? ""]: input } : input;
42
42
  }
@@ -86,4 +86,4 @@ export function versionedStatements(model: Model.Any, sql: SqlClient.SqlClient,
86
86
  });
87
87
  }
88
88
  return { insert, remove, update };
89
- }
89
+ });
package/dist/testing.d.ts DELETED
@@ -1,27 +0,0 @@
1
- import { Effect, Schema } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- import { type BucketKey, type ItemKey } from "@shivaedev/changes/keys.ts";
4
- declare const UnannouncedWrites_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 & {
5
- readonly _tag: "UnannouncedWrites";
6
- } & Readonly<A>;
7
- export declare class UnannouncedWrites extends UnannouncedWrites_base<{
8
- readonly tables: readonly string[];
9
- }> {
10
- }
11
- export interface ChangeCoverage {
12
- readonly covers: Readonly<Record<string, readonly (ItemKey | BucketKey)[]>>;
13
- }
14
- export declare const withChangeCoverage: <A, E, R>(body: Effect.Effect<A, E, R>, options?: ChangeCoverage | undefined) => Effect.Effect<A, Schema.SchemaError | import("effect/sql/SqlError").SqlError | UnannouncedWrites | Exclude<E, import("effect/sql/SqlError").SqlError>, import("effect/reactivity/Reactivity").Reactivity | SqlClient.SqlClient | Exclude<Exclude<R, never>, never>>;
15
- declare const PublishedKeysMismatch_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 & {
16
- readonly _tag: "PublishedKeysMismatch";
17
- } & Readonly<A>;
18
- export declare class PublishedKeysMismatch extends PublishedKeysMismatch_base<{
19
- readonly expected: readonly unknown[];
20
- readonly actual: readonly unknown[];
21
- }> {
22
- get message(): string;
23
- }
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>;
26
- export {};
27
- //# sourceMappingURL=testing.d.ts.map
@@ -1 +0,0 @@
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 DELETED
@@ -1,75 +0,0 @@
1
- import { Data, Effect, Schema } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- import { invalidationKeys } from "@shivaedev/changes/keys.ts";
4
- import { ChangeDeclarations } from "#ChangeDeclarations.ts";
5
- import { ChangeObserver, transact } from "#transact.ts";
6
- export class UnannouncedWrites extends Data.TaggedError("UnannouncedWrites") {
7
- }
8
- export const withChangeCoverage = Effect.fn("Changes.withChangeCoverage")(function* (body, options) {
9
- const observer = yield* ChangeObserver;
10
- const sql = yield* SqlClient.SqlClient;
11
- const named = new Set();
12
- const declarations = new Map();
13
- const outer = yield* ChangeDeclarations;
14
- const written = sql `select relid, relname, (n_tup_ins + n_tup_upd + n_tup_del)::int as writes from pg_stat_xact_user_tables`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ relid: Schema.Int, relname: Schema.String, writes: Schema.Int })))));
15
- const checked = Effect.gen(function* () {
16
- const before = new Map((yield* written).map((row) => [row.relid, row.writes]));
17
- const result = yield* body;
18
- const missing = (yield* written).filter((row) => {
19
- const count = row.writes - (before.get(row.relid) ?? 0);
20
- if (count <= 0) {
21
- return false;
22
- }
23
- const automatic = declarations.get(row.relname);
24
- const explicit = options?.covers[row.relname] ?? [];
25
- const expected = [...(automatic?.keys ?? []), ...invalidationKeys(explicit)];
26
- return (count > (automatic?.rows ?? 0) && explicit.length === 0) || expected.some((key) => !named.has(key));
27
- });
28
- if (missing.length > 0) {
29
- return yield* Effect.fail(new UnannouncedWrites({ tables: missing.map((row) => row.relname).sort() }));
30
- }
31
- return result;
32
- });
33
- return yield* checked.pipe(transact({ onSqlError: (error) => error }), Effect.provideService(ChangeDeclarations, {
34
- declare: (table, keys, rows) => Effect.andThen(Effect.sync(() => {
35
- const entry = declarations.get(table) ?? { keys: new Set(), rows: 0 };
36
- for (const key of keys) {
37
- entry.keys.add(key);
38
- }
39
- entry.rows += rows;
40
- declarations.set(table, entry);
41
- }), outer.declare(table, keys, rows)),
42
- }), Effect.provideService(ChangeObserver, (event) => {
43
- if (event._tag === "Recorded") {
44
- for (const key of event.changes) {
45
- named.add(key);
46
- }
47
- }
48
- return observer(event);
49
- }));
50
- });
51
- export class PublishedKeysMismatch extends Data.TaggedError("PublishedKeysMismatch") {
52
- get message() {
53
- return `Expected published keys ${JSON.stringify(this.expected)}; actual published keys ${JSON.stringify(this.actual)}`;
54
- }
55
- }
56
- export function expectPublished(keys) {
57
- return Effect.fn("Changes.expectPublished")(function* (body) {
58
- const observer = yield* ChangeObserver;
59
- const actual = new Set();
60
- const result = yield* body.pipe(Effect.provideService(ChangeObserver, (event) => {
61
- if (event._tag === "Published") {
62
- for (const key of event.changes) {
63
- actual.add(key);
64
- }
65
- }
66
- return observer(event);
67
- }));
68
- const expected = new Set(typeof keys === "function" ? keys(result) : keys);
69
- if (actual.size !== expected.size || [...expected].some((key) => !actual.has(key))) {
70
- return yield* Effect.fail(new PublishedKeysMismatch({ actual: [...actual], expected: [...expected] }));
71
- }
72
- return result;
73
- });
74
- }
75
- //# sourceMappingURL=testing.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}
package/src/testing.ts DELETED
@@ -1,103 +0,0 @@
1
- import { Data, Effect, Schema } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- import { type BucketKey, type ItemKey, invalidationKeys } from "@shivaedev/changes/keys.ts";
4
- import { ChangeDeclarations } from "#ChangeDeclarations.ts";
5
- import { ChangeObserver, transact } from "#transact.ts";
6
-
7
- export class UnannouncedWrites extends Data.TaggedError("UnannouncedWrites")<{ readonly tables: readonly string[] }> {}
8
-
9
- export interface ChangeCoverage {
10
- readonly covers: Readonly<Record<string, readonly (ItemKey | BucketKey)[]>>;
11
- }
12
-
13
- export const withChangeCoverage = Effect.fn("Changes.withChangeCoverage")(function* <A, E, R>(
14
- body: Effect.Effect<A, E, R>,
15
- options?: ChangeCoverage,
16
- ) {
17
- const observer = yield* ChangeObserver;
18
- const sql = yield* SqlClient.SqlClient;
19
- const named = new Set<unknown>();
20
- const declarations = new Map<string, { keys: Set<string>; rows: number }>();
21
- const outer = yield* ChangeDeclarations;
22
- const written = sql`select relid, relname, (n_tup_ins + n_tup_upd + n_tup_del)::int as writes from pg_stat_xact_user_tables`.pipe(
23
- Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ relid: Schema.Int, relname: Schema.String, writes: Schema.Int })))),
24
- );
25
- const checked = Effect.gen(function* () {
26
- const before = new Map((yield* written).map((row) => [row.relid, row.writes]));
27
- const result = yield* body;
28
- const missing = (yield* written).filter((row) => {
29
- const count = row.writes - (before.get(row.relid) ?? 0);
30
- if (count <= 0) {
31
- return false;
32
- }
33
- const automatic = declarations.get(row.relname);
34
- const explicit = options?.covers[row.relname] ?? [];
35
- const expected = [...(automatic?.keys ?? []), ...invalidationKeys(explicit)];
36
- return (count > (automatic?.rows ?? 0) && explicit.length === 0) || expected.some((key) => !named.has(key));
37
- });
38
- if (missing.length > 0) {
39
- return yield* Effect.fail(new UnannouncedWrites({ tables: missing.map((row) => row.relname).sort() }));
40
- }
41
- return result;
42
- });
43
- return yield* checked.pipe(
44
- transact({ onSqlError: (error) => error }),
45
- Effect.provideService(ChangeDeclarations, {
46
- declare: (table, keys, rows) =>
47
- Effect.andThen(
48
- Effect.sync(() => {
49
- const entry = declarations.get(table) ?? { keys: new Set<string>(), rows: 0 };
50
- for (const key of keys) {
51
- entry.keys.add(key);
52
- }
53
- entry.rows += rows;
54
- declarations.set(table, entry);
55
- }),
56
- outer.declare(table, keys, rows),
57
- ),
58
- }),
59
- Effect.provideService(ChangeObserver, (event) => {
60
- if (event._tag === "Recorded") {
61
- for (const key of event.changes) {
62
- named.add(key);
63
- }
64
- }
65
- return observer(event);
66
- }),
67
- );
68
- });
69
-
70
- export class PublishedKeysMismatch extends Data.TaggedError("PublishedKeysMismatch")<{
71
- readonly expected: readonly unknown[];
72
- readonly actual: readonly unknown[];
73
- }> {
74
- override get message() {
75
- return `Expected published keys ${JSON.stringify(this.expected)}; actual published keys ${JSON.stringify(this.actual)}`;
76
- }
77
- }
78
-
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>) {
85
- const observer = yield* ChangeObserver;
86
- const actual = new Set<unknown>();
87
- const result = yield* body.pipe(
88
- Effect.provideService(ChangeObserver, (event) => {
89
- if (event._tag === "Published") {
90
- for (const key of event.changes) {
91
- actual.add(key);
92
- }
93
- }
94
- return observer(event);
95
- }),
96
- );
97
- const expected = new Set(typeof keys === "function" ? keys(result) : keys);
98
- if (actual.size !== expected.size || [...expected].some((key) => !actual.has(key))) {
99
- return yield* Effect.fail(new PublishedKeysMismatch({ actual: [...actual], expected: [...expected] }));
100
- }
101
- return result;
102
- });
103
- }