@shivaedev/sql 0.32.0 → 0.34.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 (120) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +153 -23
  3. package/dist/MigrationStartedAt.d.ts.map +1 -0
  4. package/dist/{internal/MigrationStartedAt.js → MigrationStartedAt.js} +1 -1
  5. package/dist/MigrationStartedAt.js.map +1 -0
  6. package/dist/batchWrites.d.ts +1 -1
  7. package/dist/batchWrites.d.ts.map +1 -1
  8. package/dist/batchWrites.js.map +1 -1
  9. package/dist/batchedDelete.d.ts +9 -2
  10. package/dist/batchedDelete.d.ts.map +1 -1
  11. package/dist/batchedDelete.js +34 -35
  12. package/dist/batchedDelete.js.map +1 -1
  13. package/dist/changes.d.ts +8 -0
  14. package/dist/changes.d.ts.map +1 -0
  15. package/dist/changes.js +7 -0
  16. package/dist/changes.js.map +1 -0
  17. package/dist/conditionalUpsert.d.ts +4 -3
  18. package/dist/conditionalUpsert.d.ts.map +1 -1
  19. package/dist/conditionalUpsert.js +1 -1
  20. package/dist/conditionalUpsert.js.map +1 -1
  21. package/dist/conflictWrites.d.ts +6 -5
  22. package/dist/conflictWrites.d.ts.map +1 -1
  23. package/dist/conflictWrites.js +1 -1
  24. package/dist/conflictWrites.js.map +1 -1
  25. package/dist/deleteGroupsBatched.d.ts +13 -0
  26. package/dist/deleteGroupsBatched.d.ts.map +1 -0
  27. package/dist/deleteGroupsBatched.js +23 -0
  28. package/dist/deleteGroupsBatched.js.map +1 -0
  29. package/dist/inValues.d.ts +5 -0
  30. package/dist/inValues.d.ts.map +1 -0
  31. package/dist/inValues.js +10 -0
  32. package/dist/inValues.js.map +1 -0
  33. package/dist/insertFromSelect.d.ts +1 -1
  34. package/dist/insertFromSelect.d.ts.map +1 -1
  35. package/dist/insertFromSelect.js.map +1 -1
  36. package/dist/int4Array.d.ts +5 -0
  37. package/dist/int4Array.d.ts.map +1 -0
  38. package/dist/int4Array.js +10 -0
  39. package/dist/int4Array.js.map +1 -0
  40. package/dist/int8.d.ts +5 -0
  41. package/dist/int8.d.ts.map +1 -0
  42. package/dist/int8.js +9 -0
  43. package/dist/int8.js.map +1 -0
  44. package/dist/invalidationKeys.d.ts +8 -0
  45. package/dist/invalidationKeys.d.ts.map +1 -0
  46. package/dist/invalidationKeys.js +15 -0
  47. package/dist/invalidationKeys.js.map +1 -0
  48. package/dist/keysetPage.d.ts +26 -0
  49. package/dist/keysetPage.d.ts.map +1 -0
  50. package/dist/keysetPage.js +23 -0
  51. package/dist/keysetPage.js.map +1 -0
  52. package/dist/listenForChanges.js +2 -2
  53. package/dist/listenForChanges.js.map +1 -1
  54. package/dist/makeConflictClause.d.ts +2 -1
  55. package/dist/makeConflictClause.d.ts.map +1 -1
  56. package/dist/makeConflictClause.js +36 -6
  57. package/dist/makeConflictClause.js.map +1 -1
  58. package/dist/migrationLock.d.ts.map +1 -0
  59. package/dist/migrationLock.js.map +1 -0
  60. package/dist/migrations.d.ts +1 -1
  61. package/dist/migrations.d.ts.map +1 -1
  62. package/dist/migrations.js +18 -9
  63. package/dist/migrations.js.map +1 -1
  64. package/dist/postgresChanges.d.ts.map +1 -1
  65. package/dist/postgresChanges.js +8 -4
  66. package/dist/postgresChanges.js.map +1 -1
  67. package/dist/postgresLayer.d.ts +2 -1
  68. package/dist/postgresLayer.d.ts.map +1 -1
  69. package/dist/postgresLayer.js +1 -1
  70. package/dist/postgresLayer.js.map +1 -1
  71. package/dist/postgresRelease.js +1 -1
  72. package/dist/postgresRelease.js.map +1 -1
  73. package/dist/repositoryChanges.d.ts.map +1 -1
  74. package/dist/repositoryChanges.js +4 -3
  75. package/dist/repositoryChanges.js.map +1 -1
  76. package/dist/transact.d.ts +13 -5
  77. package/dist/transact.d.ts.map +1 -1
  78. package/dist/transact.js +45 -19
  79. package/dist/transact.js.map +1 -1
  80. package/dist/upsertColumns.d.ts +16 -0
  81. package/dist/upsertColumns.d.ts.map +1 -0
  82. package/dist/upsertColumns.js +2 -0
  83. package/dist/upsertColumns.js.map +1 -0
  84. package/dist/versionedStatements.d.ts +1 -1
  85. package/package.json +7 -6
  86. package/src/{internal/MigrationStartedAt.ts → MigrationStartedAt.ts} +1 -1
  87. package/src/batchWrites.ts +2 -1
  88. package/src/batchedDelete.ts +53 -40
  89. package/src/changes.ts +12 -0
  90. package/src/conditionalUpsert.ts +4 -3
  91. package/src/conflictWrites.ts +11 -7
  92. package/src/deleteGroupsBatched.ts +41 -0
  93. package/src/inValues.ts +11 -0
  94. package/src/insertFromSelect.ts +1 -1
  95. package/src/int4Array.ts +14 -0
  96. package/src/int8.ts +14 -0
  97. package/src/invalidationKeys.ts +24 -0
  98. package/src/keysetPage.ts +54 -0
  99. package/src/listenForChanges.ts +2 -2
  100. package/src/makeConflictClause.ts +53 -8
  101. package/src/migrations.ts +21 -10
  102. package/src/postgresChanges.ts +13 -4
  103. package/src/postgresLayer.ts +1 -1
  104. package/src/postgresRelease.ts +1 -1
  105. package/src/repositoryChanges.ts +4 -3
  106. package/src/transact.ts +61 -24
  107. package/src/upsertColumns.ts +16 -0
  108. package/dist/internal/CommandRecovery.d.ts +0 -3
  109. package/dist/internal/CommandRecovery.d.ts.map +0 -1
  110. package/dist/internal/CommandRecovery.js +0 -5
  111. package/dist/internal/CommandRecovery.js.map +0 -1
  112. package/dist/internal/MigrationStartedAt.d.ts.map +0 -1
  113. package/dist/internal/MigrationStartedAt.js.map +0 -1
  114. package/dist/internal/migrationLock.d.ts.map +0 -1
  115. package/dist/internal/migrationLock.js.map +0 -1
  116. package/src/internal/CommandRecovery.ts +0 -5
  117. /package/dist/{internal/MigrationStartedAt.d.ts → MigrationStartedAt.d.ts} +0 -0
  118. /package/dist/{internal/migrationLock.d.ts → migrationLock.d.ts} +0 -0
  119. /package/dist/{internal/migrationLock.js → migrationLock.js} +0 -0
  120. /package/src/{internal/migrationLock.ts → migrationLock.ts} +0 -0
package/src/transact.ts CHANGED
@@ -1,45 +1,78 @@
1
- import { Effect } from "effect";
1
+ import { Context, Effect } from "effect";
2
2
  import { dual } from "effect/Function";
3
3
  import type { ReadonlyRecord } from "effect/Record";
4
4
  import * as Reactivity from "effect/reactivity/Reactivity";
5
5
  import { SqlClient } from "effect/sql";
6
6
  import { isSqlError, type SqlError } from "effect/sql/SqlError";
7
- import { makeChannel } from "@shivaedev/changes/channel.ts";
8
- import { recordKey } from "@shivaedev/changes/keys.ts";
7
+ import { type BeforeCommit as CommitHook, makeChannel } from "@shivaedev/changes/channel.ts";
8
+ import { type Observation, type Observer, unobserved } from "@shivaedev/changes/observe.ts";
9
+ import type { Publish } from "@shivaedev/changes/publish.ts";
10
+ import { flatten, type Invalidation, keysOf } from "#invalidationKeys.ts";
9
11
 
10
12
  export type InvalidationKeys = readonly unknown[] | ReadonlyRecord<string, readonly unknown[]>;
11
13
 
12
- function isList(keys: InvalidationKeys): keys is readonly unknown[] {
13
- return Array.isArray(keys);
14
+ export interface AnnouncementOptions {
15
+ readonly publication?: "local" | "shared";
14
16
  }
15
17
 
16
- function flatten(keys: InvalidationKeys): readonly unknown[] {
17
- return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) => recordKey(name, id))]);
18
+ type Services = SqlClient.SqlClient | Reactivity.Reactivity;
19
+
20
+ export const ChangeObserver = Context.Reference<Observer<unknown>>("@shivaedev/sql/ChangeObserver", { defaultValue: () => unobserved });
21
+ export const BeforeCommit = Context.Reference<CommitHook<unknown, SqlError, Services>>("@shivaedev/sql/BeforeCommit", {
22
+ defaultValue: () => () => Effect.void,
23
+ });
24
+ export const CommittedSink = Context.Reference<Publish<unknown, Services> | undefined>("@shivaedev/sql/CommittedSink", {
25
+ defaultValue: () => undefined,
26
+ });
27
+
28
+ function observe(event: Observation<Invalidation>) {
29
+ return Effect.flatMap(ChangeObserver, (observer) => observer({ ...event, changes: keysOf(event.changes) }));
30
+ }
31
+
32
+ function invalidate(keys: readonly unknown[]) {
33
+ return Reactivity.Reactivity.use((reactivity) => Effect.sync(() => reactivity.invalidateUnsafe(keys)));
18
34
  }
19
35
 
20
- const guard = Effect.fn("Transact.guard")(function* () {
21
- const sql = yield* SqlClient.SqlClient;
22
- const native = yield* Effect.serviceOption(sql.transactionService);
23
- if (native._tag === "Some") {
24
- return yield* Effect.die(
25
- new Error("transact cannot announce changes after a transaction it does not own; begin the outer transaction with transact"),
26
- );
36
+ const committed = Effect.fnUntraced(function* (changes: readonly Invalidation[]) {
37
+ const sink = yield* CommittedSink;
38
+ if (sink === undefined) {
39
+ return yield* invalidate(keysOf(changes));
40
+ }
41
+ const shared = keysOf(changes.filter((change) => change.publication === "shared"));
42
+ const sharedKeys = new Set(shared);
43
+ const local = keysOf(changes.filter((change) => change.publication === "local" && !sharedKeys.has(change.key)));
44
+ if (local.length > 0) {
45
+ yield* invalidate(local);
46
+ }
47
+ if (shared.length > 0) {
48
+ yield* sink(shared);
27
49
  }
28
50
  });
29
51
 
30
- const invalidations = makeChannel<unknown, SqlClient.SqlClient | Reactivity.Reactivity, SqlError>({
52
+ const invalidations = makeChannel<Invalidation, Services, SqlError>({
53
+ beforeCommit: (changes) => {
54
+ const shared = keysOf(changes.filter((change) => change.publication === "shared"));
55
+ return shared.length === 0 ? Effect.void : Effect.flatMap(BeforeCommit, (hook) => hook(shared));
56
+ },
57
+ key: (change) => (change.publication === "shared" ? change.key : change),
31
58
  name: "@shivaedev/sql/transact",
32
59
  owner: Effect.map(SqlClient.SqlClient, (sql) => sql.transactionService),
33
- publish: (keys) => Reactivity.Reactivity.use((reactivity) => Effect.sync(() => reactivity.invalidateUnsafe(keys))),
34
- unowned: guard(),
60
+ publish: (changes) => invalidate(keysOf(changes)),
61
+ unowned: Effect.gen(function* () {
62
+ const sql = yield* SqlClient.SqlClient;
63
+ const native = yield* Effect.serviceOption(sql.transactionService);
64
+ if (native._tag === "Some") {
65
+ return yield* Effect.die(
66
+ new Error("transact cannot announce changes after a transaction it does not own; begin the outer transaction with transact"),
67
+ );
68
+ }
69
+ }),
35
70
  });
36
71
 
37
- export const ChangeObserver = invalidations.Observer;
38
- export const BeforeCommit = invalidations.BeforeCommit;
39
- export const CommittedSink = invalidations.CommittedSink;
40
-
41
- export const invalidateOnCommit = Effect.fn("Transact.invalidateOnCommit")(function* (keys: InvalidationKeys) {
42
- yield* invalidations.record(flatten(keys));
72
+ export const invalidateOnCommit = Effect.fn("Transact.invalidateOnCommit")(function* (keys: InvalidationKeys, options?: AnnouncementOptions) {
73
+ yield* invalidations
74
+ .record(flatten(keys).map((key) => ({ key, publication: options?.publication ?? "shared" })))
75
+ .pipe(Effect.provideService(invalidations.Observer, observe));
43
76
  });
44
77
 
45
78
  export interface TransactOptions<TError> {
@@ -65,6 +98,10 @@ export const transact: {
65
98
  ? sql.withTransaction(body)
66
99
  : boundary.restore(sql.withTransaction(Effect.tap(body, () => sql.afterCommit(boundary.committed)))),
67
100
  )(effect)
68
- .pipe(Effect.catchIf(isSqlError, (error) => Effect.fail(options.onSqlError(error))));
101
+ .pipe(
102
+ Effect.provideService(invalidations.Observer, observe),
103
+ Effect.provideService(invalidations.CommittedSink, committed),
104
+ Effect.catchIf(isSqlError, (error) => Effect.fail(options.onSqlError(error))),
105
+ );
69
106
  }),
70
107
  );
@@ -0,0 +1,16 @@
1
+ import type { BigDecimal } from "effect";
2
+ import type { Model } from "effect/schema";
3
+
4
+ export type UpsertColumn<TModel extends Model.Any, TColumn extends keyof TModel["fields"]> =
5
+ | TColumn
6
+ | {
7
+ readonly column: TColumn;
8
+ readonly mode: "keepOnNull";
9
+ }
10
+ | {
11
+ [TKey in TColumn]: NonNullable<TModel["fields"][TKey]["Type"]> extends number | bigint | BigDecimal.BigDecimal
12
+ ? { readonly column: TKey; readonly mode: "add" }
13
+ : never;
14
+ }[TColumn];
15
+
16
+ export type UpsertAssignment = string | { readonly column: string; readonly mode: "add" | "keepOnNull" };
@@ -1,3 +0,0 @@
1
- import { Context, Effect } from "effect";
2
- export declare const CommandRecovery: Context.Reference<Effect.Effect<void, never, never>>;
3
- //# sourceMappingURL=CommandRecovery.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CommandRecovery.d.ts","sourceRoot":"","sources":["../../src/internal/CommandRecovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzC,eAAO,MAAM,eAAe,sDAE1B,CAAC"}
@@ -1,5 +0,0 @@
1
- import { Context, Effect } from "effect";
2
- export const CommandRecovery = Context.Reference("@shivaedev/sql/CommandRecovery", {
3
- defaultValue: () => Effect.void,
4
- });
5
- //# sourceMappingURL=CommandRecovery.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CommandRecovery.js","sourceRoot":"","sources":["../../src/internal/CommandRecovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEzC,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,SAAS,CAAsB,gCAAgC,EAAE;IACvG,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;CAC/B,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MigrationStartedAt.d.ts","sourceRoot":"","sources":["../../src/internal/MigrationStartedAt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,eAAO,MAAM,kBAAkB,uCAE7B,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MigrationStartedAt.js","sourceRoot":"","sources":["../../src/internal/MigrationStartedAt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,SAAS,CAAqB,4CAA4C,EAAE;IACrH,YAAY,EAAE,GAAG,EAAE,CAAC,SAAS;CAC7B,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"migrationLock.d.ts","sourceRoot":"","sources":["../../src/internal/migrationLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,eAAO,MAAM,aAAa,qJAexB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"migrationLock.js","sourceRoot":"","sources":["../../src/internal/migrationLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,KAAa,EAAE,OAAe;IACvF,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;IACtC,SAAS,OAAO,CAAC,SAAuD;QACvE,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC;QAC/C,OAAO,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC;IACD,MAAM,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAA,mDAAmD,CAAC,CAAC;IAC1F,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAA,qCAAqC,OAAO,UAAU,CAAC,EAAE,GAAG,EAAE,CACrG,OAAO,CAAC,GAAG,CAAA,qCAAqC,QAAQ,EAAE,OAAO,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAC/F,CAAC;IACF,MAAM,GAAG,GAAG,yBAAyB,KAAK,EAAE,CAAC;IAC7C,yGAAyG;IACzG,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAA,8CAA8C,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1H,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAA,4CAA4C,GAAG,OAAO,CAAC,CAAC;AAC3E,CAAC,CAAC,CAAC"}
@@ -1,5 +0,0 @@
1
- import { Context, Effect } from "effect";
2
-
3
- export const CommandRecovery = Context.Reference<Effect.Effect<void>>("@shivaedev/sql/CommandRecovery", {
4
- defaultValue: () => Effect.void,
5
- });