@shivaedev/sql 0.26.1 → 0.28.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 +8 -8
- 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/repositoryChanges.d.ts.map +1 -1
- package/dist/repositoryChanges.js +4 -14
- package/dist/repositoryChanges.js.map +1 -1
- package/dist/transact.d.ts.map +1 -1
- package/dist/transact.js +3 -7
- package/dist/transact.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 +5 -5
- 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/repositoryChanges.ts +4 -20
- package/src/transact.ts +3 -8
- package/src/tupleIn.ts +40 -7
- package/src/tupleUnnest.ts +25 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.0 - 2026-10-31
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Expose native PostgreSQL `connectionTTL`, `idleTimeout` and `minConnections` through the SQL layer and application host, and `idleTimeoutMillis` and `maxLifetimeSeconds` through the jobs layer.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Application and jobs pools recycle connections after ten minutes and close idle connections after one minute by default, returning backend memory after heavy queries. Application change listeners renew their reserved connection and resynchronize subscribers across renewal.
|
|
12
|
+
- PostgreSQL tuple filters bind one array per column and match through `unnest`, keeping parameter counts constant for large reads, prepared selects, deletes and tuple-pinned grouped lookups. Field codecs, empty lists, null equality and duplicate tuple results retain their semantics.
|
|
13
|
+
|
|
14
|
+
### Breaking
|
|
15
|
+
|
|
16
|
+
- `tupleIn(model, fields, tuples, types)` now requires a PostgreSQL scalar type map as its fourth argument. Migrate `tupleIn(Row, ["name", "attempt"], tuples)` to `tupleIn(Row, ["name", "attempt"], tuples, { name: "text", attempt: "int4" })`, choosing the actual stored column types, including `text` for numbers stored as text. Use the scalar names in `PgTypes.OID` (for example, `int4` for integer and `int8` for bigint); array-valued and custom PostgreSQL types use explicit native SQL.
|
|
17
|
+
|
|
18
|
+
## 0.27.0 - 2026-10-30
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `changes/keys.ts` exports `recordKey` for raw record dependencies and `compositeId` for composite identities, including bigint components encoded as decimal strings.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- SQL repository writes and manual invalidations build record keys through `changes/keys.ts`, sharing the format used by subscriptions. Existing key strings remain unchanged.
|
|
27
|
+
|
|
3
28
|
## 0.26.1 - 2026-10-30
|
|
4
29
|
|
|
5
30
|
### Fixed
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ The work has three parts: describe the model and provide its database once, choo
|
|
|
31
31
|
|
|
32
32
|
### One PostgreSQL pool per application
|
|
33
33
|
|
|
34
|
-
`postgresLayer(url, options)` from `@shivaedev/sql/postgresLayer.ts` provides the native `PgPool` service from `@effect/sql-pg/PgPool`, `PgClient` and `SqlClient` together. `options` accepts `applicationName`, `maxConnections`, `transformQueryNames` and `transformResultNames`. Queries, transactions and raw connections reserved with `yield* pool.reserve` share the same `maxConnections` budget. A reservation holds its connection until its scope closes; release it before waiting for work that needs that slot.
|
|
34
|
+
`postgresLayer(url, options)` from `@shivaedev/sql/postgresLayer.ts` provides the native `PgPool` service from `@effect/sql-pg/PgPool`, `PgClient` and `SqlClient` together. `options` accepts `applicationName`, `maxConnections`, `minConnections`, `connectionTTL`, `idleTimeout`, `transformQueryNames` and `transformResultNames`. Queries, transactions and raw connections reserved with `yield* pool.reserve` share the same `maxConnections` budget. A reservation holds its connection until its scope closes; release it before waiting for work that needs that slot.
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
37
|
import * as PgPool from "@effect/sql-pg/PgPool";
|
|
@@ -46,7 +46,7 @@ const backend = Effect.gen(function* () {
|
|
|
46
46
|
}).pipe(Effect.scoped, Effect.provide(database));
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
The Layer owns the pool and waits for every socket to close during shutdown. A reserved connection sees committed writes from client transactions in the same database; it does not implicitly join a client's open transaction. `appDatabase` and database-backed `appHost` from `@shivaedev/server/appHost.ts` compose this Layer and expose the pool to application services.
|
|
49
|
+
The Layer owns the pool and waits for every socket to close during shutdown. A reserved connection sees committed writes from client transactions in the same database; it does not implicitly join a client's open transaction. `appDatabase` from `@shivaedev/server/appDatabase.ts` and database-backed `appHost` from `@shivaedev/server/appHost.ts` compose this Layer and expose the pool to application services.
|
|
50
50
|
|
|
51
51
|
### SQL names: configure the client once
|
|
52
52
|
|
|
@@ -64,7 +64,7 @@ const database = PgClient.layer(
|
|
|
64
64
|
|
|
65
65
|
`clientOptions` enables Effect SQL's `transformQueryNames` with Effect's `String.camelToSnake` and `transformResultNames` with `String.snakeToCamel` by default. A model field `createdAt` writes the migration-created `created_at` column, and returned rows contain `createdAt`. Inserts, selects, filters, ordering, updates and raw SQL share the client's transforms; column aliases are unnecessary. Write literal migration SQL with snake_case columns. In tagged SQL, interpolated identifiers such as `sql("createdAt")` are transformed; literal SQL text stays as written.
|
|
66
66
|
|
|
67
|
-
To opt out for an existing database with quoted camelCase columns or models with snake_case fields, use `PgClient.layer(clientOptions(options, { snakeCase: false }))`. Existing native clients are unchanged until configured with this helper. Native `transformQueryNames` and `transformResultNames` supplied in `options` take precedence. For `appHost` or `appDatabase` from `@shivaedev/server/
|
|
67
|
+
To opt out for an existing database with quoted camelCase columns or models with snake_case fields, use `PgClient.layer(clientOptions(options, { snakeCase: false }))`. Existing native clients are unchanged until configured with this helper. Native `transformQueryNames` and `transformResultNames` supplied in `options` take precedence. For `appHost` from `@shivaedev/server/appHost.ts` or `appDatabase` from `@shivaedev/server/appDatabase.ts`, use `clientOptions` on their `config` object; the host forwards those native transforms to its client.
|
|
68
68
|
|
|
69
69
|
### 1. Once per model: fields and writable variants
|
|
70
70
|
|
|
@@ -200,14 +200,14 @@ import { tupleIn } from "@shivaedev/sql/tupleIn.ts";
|
|
|
200
200
|
|
|
201
201
|
yield* lines.findMany({
|
|
202
202
|
where: {
|
|
203
|
-
...tupleIn(InvoiceLine, ["name", "amount"], [["Consulting", 52], ["Travel", 20]]),
|
|
203
|
+
...tupleIn(InvoiceLine, ["name", "amount"], [["Consulting", 52], ["Travel", 20]], { name: "text", amount: "text" }),
|
|
204
204
|
note: null,
|
|
205
205
|
},
|
|
206
206
|
select: ["name", "amount"],
|
|
207
207
|
});
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
Each tuple matches all its fields together, so this filter does not match `("Consulting", 20)`. The field list has at least two model fields, and each tuple's length and decoded types follow that list. Every value passes through its field codec. Spread the filter beside other entries to combine them with `AND`, or pass it directly as `where`. The same filter works in `select` and unversioned `delete`. An empty tuple list matches nothing, including deleting no rows.
|
|
210
|
+
Each tuple matches all its fields together, so this filter does not match `("Consulting", 20)`. The field list has at least two model fields, and each tuple's length and decoded types follow that list. Every value passes through its field codec. Spread the filter beside other entries to combine them with `AND`, or pass it directly as `where`. The same filter works in `select` and unversioned `delete`. An empty tuple list matches nothing, including deleting no rows. On PostgreSQL, the fourth argument names each stored column’s scalar type using the names in `PgTypes.OID`, such as `text`, `int4` (integer), `int8` (bigint), `uuid`, `numeric` and `timestamptz`. Choose the database type, independently of the model’s decoded type: this example stores amounts as text. The helper binds one array per column, casts it to the declared type and matches through `unnest`; duplicate tuples do not duplicate results, and null tuple entries retain ordinary equality semantics. Array-valued and custom PostgreSQL columns use explicit native SQL.
|
|
211
211
|
|
|
212
212
|
Use `gt`, `gte`, `lt` and `lte` for exclusive or inclusive bounds, and `like` for a text pattern:
|
|
213
213
|
|
|
@@ -313,7 +313,7 @@ const ingest = Effect.gen(function* () {
|
|
|
313
313
|
});
|
|
314
314
|
```
|
|
315
315
|
|
|
316
|
-
The application creates `readings` with `PRIMARY KEY (source, sequence)`; `sequence` and `amount` are text columns for these codecs. Composite keys contain at least two model fields. `findById` and `delete` take an object containing every key field and encode each through its field schema. `update` and `updateVoid` use the full key from the model's update input and leave the key columns unchanged. Missing key fields fail with `SchemaError` before SQL runs. A scalar `idColumn` keeps native CRUD signatures. Writes announce a composite row as the table name, a colon and a JSON array of the returned key values in `idColumn` order, such as `readings:["meter","1"]`; a PostgreSQL `bigint` key value appears as its decimal string.
|
|
316
|
+
The application creates `readings` with `PRIMARY KEY (source, sequence)`; `sequence` and `amount` are text columns for these codecs. Composite keys contain at least two model fields. `findById` and `delete` take an object containing every key field and encode each through its field schema. `update` and `updateVoid` use the full key from the model's update input and leave the key columns unchanged. Missing key fields fail with `SchemaError` before SQL runs. A scalar `idColumn` keeps native CRUD signatures. Writes announce a composite row as the table name, a colon and a JSON array of the returned key values in `idColumn` order, such as `readings:["meter","1"]`; a PostgreSQL `bigint` key value appears as its decimal string. Build matching subscription ids with `compositeId` from `@shivaedev/changes/keys.ts`, using stored values in that same column order: `collection("readings", Schema.String).item(compositeId(["meter", "1"]))`. Manual invalidations use the same id: `invalidateOnCommit({ readings: [compositeId(["meter", "1"])] })`.
|
|
317
317
|
|
|
318
318
|
`conflictColumns` is a nonempty tuple of inserted model fields backed by a database unique constraint or primary key. It can differ from the primary key, including a unique external key beside a generated ID. Declaring it adds these methods:
|
|
319
319
|
|
|
@@ -544,7 +544,7 @@ For a structured readiness readout, use `pendingPostgresMigrations({ loader, tab
|
|
|
544
544
|
|
|
545
545
|
Pass an optional `release` string to `migratePostgres` to record the owning release after all ledger migrations and schema components succeed. The final `postgresRelease(release, table?)` schema component owns a one-row `<ledger>_release` table with typed `release` and UTC `recorded_at timestamptz` columns, plus a `<ledger>_workers` table with typed process identity, release and UTC heartbeat columns. `readPostgresRelease(table?)` from `postgresRelease.ts` returns the recorded release and `recordedAt` Date, or `undefined` for a missing marker. `pendingPostgresMigrations({ loader, release, table? })` reports a missing or different release as a pending component. Without `release`, migration and readiness calls neither write nor read these tables.
|
|
546
546
|
|
|
547
|
-
`appDatabase` from `@shivaedev/server/
|
|
547
|
+
`appDatabase` from `@shivaedev/server/appDatabase.ts` supplies the optional `WorkerRelease` Context service in migration mode `"wait"` with a release. Startup waits for that marker within `waitTimeout`; subsequent checks run every `pollInterval`. Each worker database Layer claims a process row only when its release owns the marker and no live row of another release remains. Heartbeats continue every poll while jobs drain, including after a connection blip. Each failed heartbeat or ownership poll logs its cause and retries three times, one quarter of a polling interval apart; exhausted retries close the worker scope with the failure cause. Completed drain and clean shutdown remove the row. Rows older than five polling intervals count as gone, bounding takeover after a crash (five seconds at the default one-second poll). Reserve SQL pool capacity for heartbeats alongside active jobs and the change listener. Release equality decides ownership, so rolling back the marker resumes the earlier worker. `@shivaedev/jobs` reads the service automatically; applications do not connect a signal themselves. Web's `"check"` mode ignores the marker.
|
|
548
548
|
|
|
549
549
|
### PostgreSQL delivery between processes
|
|
550
550
|
|
|
@@ -570,7 +570,7 @@ Import the module that defines the name; there is no root entry.
|
|
|
570
570
|
- `@shivaedev/sql/clientOptions.ts`: `clientOptions(options, { snakeCase? })`, `SqlNameTransforms` — Add default native name transforms to driver or app host options; opt out with `snakeCase: false`.
|
|
571
571
|
- `@shivaedev/sql/repository.ts`: `makeRepository(model, options)` — `tableName`, scalar or composite `idColumn`, `spanPrefix`, optional `versionColumn`, `conflictColumns` and `conflictWhere`; requires native `SqlClient`.
|
|
572
572
|
- `@shivaedev/sql/repository.ts`: `OptimisticConcurrencyError` — Typed stale or missing-row failure for versioned writes, with `tableName`, `id`, `expectedVersion`, `reason: "missing" | "stale"`.
|
|
573
|
-
- `@shivaedev/sql/tupleIn.ts`: `tupleIn(model, fields, tuples)` — Filter entry matching two or more fields together against a list of tuples, for `where` in reads and unversioned deletes.
|
|
573
|
+
- `@shivaedev/sql/tupleIn.ts`: `tupleIn(model, fields, tuples, types)` — Filter entry matching two or more fields together against a list of tuples, for `where` in reads and unversioned deletes.
|
|
574
574
|
- `@shivaedev/sql/upsertResult.ts`: `UpsertRow` — The `{ row, inserted }` result of an unconditional upsert.
|
|
575
575
|
- `@shivaedev/sql/conditionalUpsert.ts`: `UpsertFilter`, `UpsertResult` — The `updateWhere` filter shape and the tagged `Inserted`, `Updated` or `Unchanged` result of a conditional upsert.
|
|
576
576
|
- `@shivaedev/sql/mapSqlError.ts`: `mapSqlError({ constraints, fallback })` — Mapper from named constraint violations to application errors, with a fallback for every other `SqlError`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makePostgresClient.d.ts","sourceRoot":"","sources":["../../src/adapters/makePostgresClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,KAAK,YAAY,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAqB,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,SAAS,EAAsB,SAAS,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAgB,MAAM,qBAAqB,CAAC;AAI7D,eAAO,MAAM,kBAAkB;;;kBAuBf,OAAO;sBACH,MAAM;
|
|
1
|
+
{"version":3,"file":"makePostgresClient.d.ts","sourceRoot":"","sources":["../../src/adapters/makePostgresClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,KAAK,YAAY,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAqB,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,SAAS,EAAsB,SAAS,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAgB,MAAM,qBAAqB,CAAC;AAI7D,eAAO,MAAM,kBAAkB;;;kBAuBf,OAAO;sBACH,MAAM;sBASN,MAAM,WAAW,MAAM;4EAGzC,CAAC"}
|
|
@@ -28,7 +28,14 @@ export const makePostgresClient = Effect.fn("Sql.makePostgresClient")(function*
|
|
|
28
28
|
[PgClient.TypeId]: PgClient.TypeId,
|
|
29
29
|
config,
|
|
30
30
|
json: (value) => Statement.fragment([json(value)]),
|
|
31
|
-
listen: (channel) => Effect.
|
|
31
|
+
listen: (channel) => Effect.gen(function* () {
|
|
32
|
+
// TTL checkout retains expired leases until its scope closes; LISTEN must not pin those old backends for another lifetime.
|
|
33
|
+
if (pool.config.connectionTTL !== undefined) {
|
|
34
|
+
yield* pool.use(() => Effect.void);
|
|
35
|
+
}
|
|
36
|
+
const connection = yield* pool.reserve;
|
|
37
|
+
return yield* connection.listen(channel);
|
|
38
|
+
}),
|
|
32
39
|
notify: (channel, payload) => pool.use((connection) => connection.query("SELECT pg_notify($1, $2)", [channel, payload])).pipe(Effect.asVoid),
|
|
33
40
|
});
|
|
34
41
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makePostgresClient.js","sourceRoot":"","sources":["../../src/adapters/makePostgresClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,SAAS,EAAsB,SAAS,EAAE,MAAM,YAAY,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7D,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAoB,QAAQ,CAAC,CAAC;AAE3D,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EAAE,MAA+B;IAC/G,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;QACpC,QAAQ;QACR,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC;QAChF,MAAM;QACN,QAAQ,EAAE,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,mBAAmB,CAAC;QAC3D,MAAM,EAAE,IAAI;QACZ,0BAA0B,EAAE,IAAI;QAChC,gBAAgB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,qBAAqB,IAAI,EAAE;QACvD,cAAc,EAAE;YACf,CAAC,gBAAgB,EAAE,YAAY,CAAC;YAChC,CAAC,cAAc,EAAE,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,IAAI,UAAU,CAAC;YAClE,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC;YAC9C,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC;SACpC;QACD,mBAAmB,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC;QACjE,aAAa,EAAE,MAAM,CAAC,oBAAoB,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,KAAK;KACrI,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;QAC5B,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM;QAClC,MAAM;QACN,IAAI,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3D,MAAM,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"makePostgresClient.js","sourceRoot":"","sources":["../../src/adapters/makePostgresClient.ts"],"names":[],"mappings":"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"}
|
package/dist/latestPerGroup.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 type { LatestPerGroup } from "#repository.ts";
|
|
6
6
|
export declare function latestPerGroup(model: Model.Any, sql: SqlClient.SqlClient, tableName: string): (query: LatestPerGroup<Model.Any>, order: Fragment, rowLimit: number) => Effect.Effect<readonly {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"latestPerGroup.d.ts","sourceRoot":"","sources":["../src/latestPerGroup.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":"latestPerGroup.d.ts","sourceRoot":"","sources":["../src/latestPerGroup.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;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAerD,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM;;;;2EAgE3F"}
|
package/dist/latestPerGroup.js
CHANGED
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import { Effect, Schema } from "effect";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
2
3
|
import { pinnedGroups } from "#pinnedGroups.ts";
|
|
3
4
|
import { makeFilter } from "#repositoryFilter.ts";
|
|
5
|
+
import { tupleUnnest } from "#tupleUnnest.ts";
|
|
6
|
+
function groupMasks(input) {
|
|
7
|
+
const masks = new Map();
|
|
8
|
+
for (const group of input) {
|
|
9
|
+
const mask = group.map((value) => (value === null ? "0" : "1")).join("");
|
|
10
|
+
const groups = masks.get(mask) ?? [];
|
|
11
|
+
groups.push(group);
|
|
12
|
+
masks.set(mask, groups);
|
|
13
|
+
}
|
|
14
|
+
return [...masks.values()];
|
|
15
|
+
}
|
|
4
16
|
export function latestPerGroup(model, sql, tableName) {
|
|
5
17
|
const { fieldAt, where } = makeFilter(model, sql);
|
|
18
|
+
function groupSource(groups, partitionBy, fromTuple) {
|
|
19
|
+
const partitions = sql.csv(partitionBy.map((key) => sql `${sql(key)}`));
|
|
20
|
+
if (fromTuple) {
|
|
21
|
+
const mask = partitionBy.map((key, index) => (groups[0]?.[index] === null ? sql `${sql(key)} is null` : sql `${sql(key)} is not null`));
|
|
22
|
+
return sql `select ${partitions} from ${sql("__effect_sql_tuple_groups")} where ${sql.and(mask)}`;
|
|
23
|
+
}
|
|
24
|
+
const values = sql.join(" union ", false)(groups.map((group) => sql `select ${sql.csv(group.map((value) => sql `${value}`))}`));
|
|
25
|
+
// The empty projection gives scalar pins their stored column types.
|
|
26
|
+
return sql `select ${partitions} from ${sql(tableName)} where false union ${values}`;
|
|
27
|
+
}
|
|
6
28
|
return Effect.fnUntraced(function* (query, order, rowLimit) {
|
|
7
29
|
yield* Effect.forEach(query.partitionBy, fieldAt);
|
|
8
30
|
const predicate = yield* where(query.where, true);
|
|
@@ -15,25 +37,27 @@ export function latestPerGroup(model, sql, tableName) {
|
|
|
15
37
|
}
|
|
16
38
|
if (pinned !== undefined && sql.onDialectOrElse({ orElse: () => false, pg: () => true })) {
|
|
17
39
|
const remaining = yield* where(pinned.remaining, true);
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
const tuple = pinned.tuple;
|
|
41
|
+
const tupleRows = pinned.groups.map((group) => tuple?.fields.map((key) => group[query.partitionBy.indexOf(key)]) ?? group);
|
|
42
|
+
const tupleSource = tuple === undefined
|
|
43
|
+
? sql ``
|
|
44
|
+
: sql `with ${sql("__effect_sql_tuple_groups")} as materialized (
|
|
45
|
+
select distinct * from ${yield* tupleUnnest(tuple.types, tupleRows).pipe(Effect.provideService(SqlClient.SqlClient, sql))}
|
|
46
|
+
as ${sql("__effect_sql_tuples")} (${sql.csv(tuple.fields.map((key) => sql `${sql(key)}`))})
|
|
47
|
+
), ${sql("__effect_sql_tuple_limit")} as (select ${rowLimit}::bigint as ${sql("limit")})`;
|
|
48
|
+
const limit = tuple === undefined ? sql `${rowLimit}` : sql `(select ${sql("limit")} from ${sql("__effect_sql_tuple_limit")})`;
|
|
49
|
+
const branches = groupMasks(pinned.groups).map((groups) => {
|
|
50
|
+
const source = groupSource(groups, query.partitionBy, tuple !== undefined);
|
|
27
51
|
const matches = query.partitionBy.map((key, index) => groups[0]?.[index] === null ? sql `${sql(key)} is null` : sql `${sql(key)} = ${sql("__effect_sql_groups")}.${sql(key)}`);
|
|
28
|
-
//
|
|
52
|
+
// Null masks keep ordinary equality available to each non-null index column.
|
|
29
53
|
return sql `select ${sql("__effect_sql_latest")}.* from (
|
|
30
|
-
|
|
54
|
+
${source}
|
|
31
55
|
) as ${sql("__effect_sql_groups")} cross join lateral (
|
|
32
|
-
select ${columns} from ${sql(tableName)} where ${sql.and([...matches, remaining])} ${order} limit ${
|
|
56
|
+
select ${columns} from ${sql(tableName)} where ${sql.and([...matches, remaining])} ${order} limit ${limit}
|
|
33
57
|
) as ${sql("__effect_sql_latest")}`;
|
|
34
58
|
});
|
|
35
59
|
const ordering = sql.csv(query.orderBy.map(({ field, direction }) => sql `${sql(field)} ${sql.literal(direction === "desc" ? "desc" : "asc")}`));
|
|
36
|
-
const rows = yield* sql
|
|
60
|
+
const rows = yield* sql `${tupleSource} select ${columns} from (${sql.join(" union all ")(branches)}) as ${sql("__effect_sql_pinned")} order by ${partitions}, ${ordering}`;
|
|
37
61
|
return yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct(model.fields)))(rows);
|
|
38
62
|
}
|
|
39
63
|
let rankColumn = "__effect_sql_rank";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"latestPerGroup.js","sourceRoot":"","sources":["../src/latestPerGroup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"latestPerGroup.js","sourceRoot":"","sources":["../src/latestPerGroup.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listenForChanges.d.ts","sourceRoot":"","sources":["../src/listenForChanges.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"listenForChanges.d.ts","sourceRoot":"","sources":["../src/listenForChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,MAAM,EAAiB,KAAK,EAAE,MAAM,QAAQ,CAAC;AACjF,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AASnE,eAAO,MAAM,gBAAgB,iIAuF3B,CAAC"}
|
package/dist/listenForChanges.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Clock, Deferred, Effect, Queue, Random, Scope } from "effect";
|
|
1
|
+
import { Clock, Deferred, Duration, Effect, Queue, Random, Scope } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { decodeKeys, resyncPayload } from "./notifyPayload.js";
|
|
4
4
|
import { PostgresListenerError } from "./PostgresListenerError.js";
|
|
@@ -21,6 +21,9 @@ export const listenForChanges = Effect.fn("PostgresChanges.listenForChanges")(fu
|
|
|
21
21
|
const attempt = Effect.scoped(Effect.gen(function* () {
|
|
22
22
|
const scope = yield* listenerScope(timeout);
|
|
23
23
|
const notifications = yield* (options.listenerClient ?? client).listen(channel).pipe(Scope.provide(scope), deadline);
|
|
24
|
+
const interval = Duration.toMillis(Duration.fromInputUnsafe(options.heartbeatInterval ?? "5 seconds"));
|
|
25
|
+
const lifetime = Duration.toMillis(Duration.fromInputUnsafe(options.connectionTTL ?? Duration.infinity));
|
|
26
|
+
const renewAt = (yield* Clock.currentTimeMillis) + (lifetime === 0 ? interval : lifetime);
|
|
24
27
|
const token = JSON.stringify({ heartbeat: `${yield* Random.nextInt}:${yield* Random.nextInt}` });
|
|
25
28
|
let ack = yield* Deferred.make();
|
|
26
29
|
function receive(payload) {
|
|
@@ -46,11 +49,14 @@ export const listenForChanges = Effect.fn("PostgresChanges.listenForChanges")(fu
|
|
|
46
49
|
lostAt = undefined;
|
|
47
50
|
}
|
|
48
51
|
yield* Deferred.succeed(ready, undefined);
|
|
49
|
-
|
|
50
|
-
yield* Effect.sleep(
|
|
52
|
+
while (true) {
|
|
53
|
+
yield* Effect.sleep(Math.min(interval, Math.max(0, renewAt - (yield* Clock.currentTimeMillis))));
|
|
54
|
+
if ((yield* Clock.currentTimeMillis) >= renewAt) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
51
57
|
ack = yield* Deferred.make();
|
|
52
58
|
yield* Effect.andThen(send, Deferred.await(ack)).pipe(deadline);
|
|
53
|
-
}
|
|
59
|
+
}
|
|
54
60
|
});
|
|
55
61
|
yield* Effect.raceFirst(listening, monitor);
|
|
56
62
|
}).pipe(Effect.onExit(() => resync)));
|
|
@@ -66,7 +72,7 @@ export const listenForChanges = Effect.fn("PostgresChanges.listenForChanges")(fu
|
|
|
66
72
|
}
|
|
67
73
|
lostAt = yield* Clock.currentTimeMillis;
|
|
68
74
|
yield* Effect.logWarning("PostgreSQL change listener lost; resynchronizing live streams", cause);
|
|
69
|
-
})
|
|
75
|
+
}).pipe(Effect.andThen(Effect.sleep(options.retryDelay ?? "1 second")))))).pipe(Effect.forkScoped);
|
|
70
76
|
yield* Deferred.await(ready);
|
|
71
77
|
});
|
|
72
78
|
//# sourceMappingURL=listenForChanges.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listenForChanges.js","sourceRoot":"","sources":["../src/listenForChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"listenForChanges.js","sourceRoot":"","sources":["../src/listenForChanges.ts"],"names":[],"mappings":"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"}
|
package/dist/pinnedGroups.d.ts
CHANGED
|
@@ -6,8 +6,9 @@ import { tupleFilter } from "#tupleIn.ts";
|
|
|
6
6
|
export declare function pinnedGroups(model: Model.Any, sql: SqlClient.SqlClient, partitionBy: readonly string[], input?: RepositoryListFilter<Model.Any>): Effect.Effect<undefined, never, never> | Effect.Effect<{
|
|
7
7
|
groups: never[];
|
|
8
8
|
remaining: RepositoryListFilter<Model.Any>;
|
|
9
|
+
tuple: undefined;
|
|
9
10
|
}, never, never> | Effect.Effect<{
|
|
10
|
-
groups: (readonly [])[];
|
|
11
|
+
groups: readonly (readonly unknown[])[];
|
|
11
12
|
remaining: {
|
|
12
13
|
[x: string]: unknown;
|
|
13
14
|
[x: number]: unknown;
|
|
@@ -16,7 +17,14 @@ export declare function pinnedGroups(model: Model.Any, sql: SqlClient.SqlClient,
|
|
|
16
17
|
readonly model: Model.Any;
|
|
17
18
|
readonly fields: readonly string[];
|
|
18
19
|
readonly values: readonly (readonly unknown[])[];
|
|
20
|
+
readonly types: readonly import("#tupleIn.ts").TupleColumnType[];
|
|
19
21
|
};
|
|
20
22
|
};
|
|
23
|
+
tuple: {
|
|
24
|
+
readonly model: Model.Any;
|
|
25
|
+
readonly fields: readonly string[];
|
|
26
|
+
readonly values: readonly (readonly unknown[])[];
|
|
27
|
+
readonly types: readonly import("#tupleIn.ts").TupleColumnType[];
|
|
28
|
+
} | undefined;
|
|
21
29
|
}, Schema.SchemaError, unknown>;
|
|
22
30
|
//# sourceMappingURL=pinnedGroups.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinnedGroups.d.ts","sourceRoot":"","sources":["../src/pinnedGroups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,wBAAgB,YAAY,CAC3B,KAAK,EAAE,KAAK,CAAC,GAAG,EAChB,GAAG,EAAE,SAAS,CAAC,SAAS,EACxB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,KAAK,GAAE,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAM
|
|
1
|
+
{"version":3,"file":"pinnedGroups.d.ts","sourceRoot":"","sources":["../src/pinnedGroups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,wBAAgB,YAAY,CAC3B,KAAK,EAAE,KAAK,CAAC,GAAG,EAChB,GAAG,EAAE,SAAS,CAAC,SAAS,EACxB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,KAAK,GAAE,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAM;;;;;;;;;;;;;;;;;;;;;;;gCAoC3C"}
|
package/dist/pinnedGroups.js
CHANGED
|
@@ -5,7 +5,7 @@ export function pinnedGroups(model, sql, partitionBy, input = {}) {
|
|
|
5
5
|
const tuple = input[tupleFilter];
|
|
6
6
|
if (tuple?.values.length === 0
|
|
7
7
|
|| Object.values(input).some((value) => typeof value === "object" && value !== null && "in" in value && Array.isArray(value.in) && value.in.length === 0)) {
|
|
8
|
-
return Effect.succeed({ groups: [], remaining: input });
|
|
8
|
+
return Effect.succeed({ groups: [], remaining: input, tuple: undefined });
|
|
9
9
|
}
|
|
10
10
|
const exactTuple = tuple !== undefined && tuple.fields.length === partitionBy.length && partitionBy.every((key) => tuple.fields.includes(key));
|
|
11
11
|
let groups = [[]];
|
|
@@ -29,7 +29,7 @@ export function pinnedGroups(model, sql, partitionBy, input = {}) {
|
|
|
29
29
|
return Effect.gen(function* () {
|
|
30
30
|
const fields = yield* Effect.forEach(partitionBy, fieldAt);
|
|
31
31
|
const groupsEncoded = yield* Effect.forEach(groups, (group) => Schema.encodeUnknownEffect(Schema.Tuple(fields))(group));
|
|
32
|
-
return { groups: groupsEncoded, remaining };
|
|
32
|
+
return { groups: groupsEncoded, remaining, tuple: exactTuple ? tuple : undefined };
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
//# sourceMappingURL=pinnedGroups.js.map
|
package/dist/pinnedGroups.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinnedGroups.js","sourceRoot":"","sources":["../src/pinnedGroups.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIxC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,UAAU,YAAY,CAC3B,KAAgB,EAChB,GAAwB,EACxB,WAA8B,EAC9B,KAAK,GAAoC,EAAE;IAE3C,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IACjC,IACC,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC;WACvB,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAC3B,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,CAC3H,EACA,CAAC;QACF,OAAO,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"pinnedGroups.js","sourceRoot":"","sources":["../src/pinnedGroups.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,7 @@ export interface PostgresChangesOptions {
|
|
|
12
12
|
readonly channel: string;
|
|
13
13
|
readonly client: PostgresChangeClient;
|
|
14
14
|
readonly connectionSetting?: string;
|
|
15
|
+
readonly "connectionTTL"?: Duration.Input | undefined;
|
|
15
16
|
readonly heartbeatInterval?: Duration.Input;
|
|
16
17
|
readonly heartbeatTimeout?: Duration.Input;
|
|
17
18
|
readonly listenerClient?: Pick<PostgresChangeClient, "listen">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresChanges.d.ts","sourceRoot":"","sources":["../src/postgresChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvF,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAMpD,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAqB,SAAQ,SAAS,CAAC,SAAS;IAChE,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;CAC1H;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC3C,QAAQ,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;CACrC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,oHA2B9D"}
|
|
1
|
+
{"version":3,"file":"postgresChanges.d.ts","sourceRoot":"","sources":["../src/postgresChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvF,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAMpD,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAqB,SAAQ,SAAS,CAAC,SAAS;IAChE,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;CAC1H;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;IAC3C,QAAQ,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,CAAC;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;CACrC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,oHA2B9D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresChanges.js","sourceRoot":"","sources":["../src/postgresChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,EAAE,KAAK,EAA0B,MAAM,QAAQ,CAAC;AACvF,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"postgresChanges.js","sourceRoot":"","sources":["../src/postgresChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,EAAE,KAAK,EAA0B,MAAM,QAAQ,CAAC;AACvF,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAsB5D,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC9D,OAAO,KAAK,CAAC,aAAa,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,KAAK,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EAAE,IAAwB;YACrF,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;YACvC,IAAI,GAAG,CAAC,kBAAkB,KAAK,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;gBAClE,OAAO;YACR,CAAC;YACD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACzC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAChC,KAAK,CAAC,CAAC,GAAG,CAAA,oBAAoB,OAAO,CAAC,OAAO,KAAK,OAAO,GAAG,CAAC;YAC9D,CAAC;QACF,CAAC,CAAC,CAAC;QACH,SAAS,SAAS,CAAC,IAAwB;YAC1C,OAAO,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAClD,GAAG,CAAC,kBAAkB,KAAK,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CACxG,CAAC;QACH,CAAC;QACD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAClC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAA,oBAAoB,OAAO,CAAC,OAAO,KAAK,aAAa,GAAG,CAAC,EACtF,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,yEAAyE,EAAE,KAAK,CAAC,CAAC,EAC/H,MAAM,CAAC,MAAM,CACb,CAAC;QACF,OAAO,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9H,CAAC,CAAC,CACF,CAAC;AACH,CAAC"}
|
package/dist/postgresLayer.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ import { SqlClient } from "effect/sql";
|
|
|
5
5
|
import type { SqlNameTransforms } from "#clientOptions.ts";
|
|
6
6
|
export interface PostgresOptions extends SqlNameTransforms {
|
|
7
7
|
readonly applicationName?: string | undefined;
|
|
8
|
+
readonly "connectionTTL"?: PgPool.Config["connectionTTL"];
|
|
9
|
+
readonly idleTimeout?: PgPool.Config["idleTimeout"];
|
|
8
10
|
readonly maxConnections?: number | undefined;
|
|
11
|
+
readonly minConnections?: PgPool.Config["minConnections"];
|
|
9
12
|
}
|
|
10
13
|
export declare function postgresLayer(url: Redacted.Redacted<string>, options?: PostgresOptions): Layer.Layer<PgClient.PgClient | PgPool.PgPool | SqlClient.SqlClient, import("effect/sql/SqlError").SqlError, never>;
|
|
11
14
|
//# sourceMappingURL=postgresLayer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresLayer.d.ts","sourceRoot":"","sources":["../src/postgresLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAmB,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACzD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"postgresLayer.d.ts","sourceRoot":"","sources":["../src/postgresLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAmB,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACzD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAC1D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACpD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;CAC1D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,uHAWtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgresLayer.js","sourceRoot":"","sources":["../src/postgresLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAiB,MAAM,QAAQ,CAAC;AAC/D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"postgresLayer.js","sourceRoot":"","sources":["../src/postgresLayer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,MAAM,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAiB,MAAM,QAAQ,CAAC;AAC/D,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAUtD,MAAM,UAAU,aAAa,CAAC,GAA8B,EAAE,OAAyB;IACtF,OAAO,KAAK,CAAC,aAAa,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,cAAc,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;QAClG,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;IACjI,CAAC,CAAC,CACF,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repositoryChanges.d.ts","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"repositoryChanges.d.ts","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAMvC,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC;AAErF,eAAO,MAAM,iBAAiB;;;YAuDd,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBAIrD,CAAC"}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import { declaredBuckets } from "@shivaedev/changes/buckets.ts";
|
|
5
|
-
import { invalidationKeys } from "@shivaedev/changes/keys.ts";
|
|
5
|
+
import { compositeId, invalidationKeys, recordKey } from "@shivaedev/changes/keys.ts";
|
|
6
6
|
import { ChangeDeclarations } from "#ChangeDeclarations.ts";
|
|
7
7
|
import { invalidateOnCommit, transact } from "#transact.ts";
|
|
8
|
-
function encodeKeyValue(_key, value) {
|
|
9
|
-
return typeof value === "bigint" ? String(value) : value;
|
|
10
|
-
}
|
|
11
|
-
function recordId(value) {
|
|
12
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
13
|
-
? String(value)
|
|
14
|
-
: Hash.hash(value);
|
|
15
|
-
}
|
|
16
8
|
export const repositoryChanges = Effect.fnUntraced(function* (sql, table, columns, fields = {}) {
|
|
17
9
|
const buckets = declaredBuckets(fields);
|
|
18
10
|
const bucketKeys = Effect.fnUntraced(function* (row) {
|
|
@@ -45,10 +37,8 @@ export const repositoryChanges = Effect.fnUntraced(function* (sql, table, column
|
|
|
45
37
|
}
|
|
46
38
|
const keys = new Set([table]);
|
|
47
39
|
for (const row of rows) {
|
|
48
|
-
const id = columns.length === 1
|
|
49
|
-
|
|
50
|
-
: JSON.stringify(columns.map((column) => row[column]), encodeKeyValue);
|
|
51
|
-
keys.add(`${table}:${recordId(id)}`);
|
|
40
|
+
const id = columns.length === 1 ? row[columns[0] ?? ""] : compositeId(columns.map((column) => row[column]));
|
|
41
|
+
keys.add(recordKey(table, id));
|
|
52
42
|
for (const key of yield* bucketKeys(row)) {
|
|
53
43
|
keys.add(key);
|
|
54
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repositoryChanges.js","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"repositoryChanges.js","sourceRoot":"","sources":["../src/repositoryChanges.ts"],"names":[],"mappings":"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"}
|
package/dist/transact.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transact.d.ts","sourceRoot":"","sources":["../src/transact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"transact.d.ts","sourceRoot":"","sources":["../src/transact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,UAAU,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIhE,MAAM,MAAM,gBAAgB,GAAG,SAAS,OAAO,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC;AA2B/F,eAAO,MAAM,cAAc,+FAAyB,CAAC;AACrD,eAAO,MAAM,YAAY,0JAA6B,CAAC;AACvD,eAAO,MAAM,aAAa,uJAA8B,CAAC;AAEzD,eAAO,MAAM,kBAAkB,qGAE7B,CAAC;AAEH,MAAM,WAAW,eAAe,CAAC,MAAM;IACtC,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,CAAC;CACjD;AAED,eAAO,MAAM,QAAQ,EAAE;IACtB,CAAC,MAAM,EACN,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,GAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACjJ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAC9B,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC,SAAS,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;CASpG,CAAC"}
|
package/dist/transact.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
import { Effect
|
|
1
|
+
import { Effect } from "effect";
|
|
2
2
|
import { dual } from "effect/Function";
|
|
3
3
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
4
4
|
import { SqlClient } from "effect/sql";
|
|
5
5
|
import { isSqlError } from "effect/sql/SqlError";
|
|
6
6
|
import { makeChannel } from "@shivaedev/changes/channel.ts";
|
|
7
|
-
|
|
8
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
9
|
-
? String(value)
|
|
10
|
-
: Hash.hash(value);
|
|
11
|
-
}
|
|
7
|
+
import { recordKey } from "@shivaedev/changes/keys.ts";
|
|
12
8
|
function isList(keys) {
|
|
13
9
|
return Array.isArray(keys);
|
|
14
10
|
}
|
|
15
11
|
function flatten(keys) {
|
|
16
|
-
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) =>
|
|
12
|
+
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) => recordKey(name, id))]);
|
|
17
13
|
}
|
|
18
14
|
const guard = Effect.fn("Transact.guard")(function* () {
|
|
19
15
|
const sql = yield* SqlClient.SqlClient;
|
package/dist/transact.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transact.js","sourceRoot":"","sources":["../src/transact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"transact.js","sourceRoot":"","sources":["../src/transact.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.28.0",
|
|
4
4
|
"description": "Schema-derived repositories over native Effect SQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect"
|
|
@@ -43,16 +43,16 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@shivaedev/changes": "0.
|
|
46
|
+
"@shivaedev/changes": "0.6.0",
|
|
47
47
|
"@shivaedev/service": "0.6.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
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.
|
|
55
|
-
"@shivaedev/test-story": "0.
|
|
53
|
+
"@shivaedev/quality": "0.51.0",
|
|
54
|
+
"@shivaedev/test-sql": "0.15.1",
|
|
55
|
+
"@shivaedev/test-story": "0.31.0",
|
|
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/repositoryChanges.ts
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Option, type Schema } from "effect";
|
|
2
2
|
import * as Reactivity from "effect/reactivity/Reactivity";
|
|
3
3
|
import { SqlClient } from "effect/sql";
|
|
4
4
|
import { declaredBuckets } from "@shivaedev/changes/buckets.ts";
|
|
5
|
-
import { invalidationKeys, type Key } from "@shivaedev/changes/keys.ts";
|
|
5
|
+
import { compositeId, invalidationKeys, type Key, recordKey } from "@shivaedev/changes/keys.ts";
|
|
6
6
|
import { ChangeDeclarations } from "#ChangeDeclarations.ts";
|
|
7
7
|
import { invalidateOnCommit, transact } from "#transact.ts";
|
|
8
8
|
|
|
9
|
-
function encodeKeyValue(_key: string, value: unknown) {
|
|
10
|
-
return typeof value === "bigint" ? String(value) : value;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function recordId(value: unknown) {
|
|
14
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
15
|
-
? String(value)
|
|
16
|
-
: Hash.hash(value);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
9
|
export type RepositoryChanges = Effect.Success<ReturnType<typeof repositoryChanges>>;
|
|
20
10
|
|
|
21
11
|
export const repositoryChanges = Effect.fnUntraced(function* (
|
|
@@ -63,14 +53,8 @@ export const repositoryChanges = Effect.fnUntraced(function* (
|
|
|
63
53
|
}
|
|
64
54
|
const keys = new Set<string>([table]);
|
|
65
55
|
for (const row of rows) {
|
|
66
|
-
const id =
|
|
67
|
-
|
|
68
|
-
? row[columns[0] ?? ""]
|
|
69
|
-
: JSON.stringify(
|
|
70
|
-
columns.map((column) => row[column]),
|
|
71
|
-
encodeKeyValue,
|
|
72
|
-
);
|
|
73
|
-
keys.add(`${table}:${recordId(id)}`);
|
|
56
|
+
const id = columns.length === 1 ? row[columns[0] ?? ""] : compositeId(columns.map((column) => row[column]));
|
|
57
|
+
keys.add(recordKey(table, id));
|
|
74
58
|
for (const key of yield* bucketKeys(row)) {
|
|
75
59
|
keys.add(key);
|
|
76
60
|
}
|
package/src/transact.ts
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
import { Effect
|
|
1
|
+
import { 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
7
|
import { makeChannel } from "@shivaedev/changes/channel.ts";
|
|
8
|
+
import { recordKey } from "@shivaedev/changes/keys.ts";
|
|
8
9
|
|
|
9
10
|
export type InvalidationKeys = readonly unknown[] | ReadonlyRecord<string, readonly unknown[]>;
|
|
10
11
|
|
|
11
|
-
function hashOf(value: unknown): unknown {
|
|
12
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean"
|
|
13
|
-
? String(value)
|
|
14
|
-
: Hash.hash(value);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
function isList(keys: InvalidationKeys): keys is readonly unknown[] {
|
|
18
13
|
return Array.isArray(keys);
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
function flatten(keys: InvalidationKeys): readonly unknown[] {
|
|
22
|
-
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) =>
|
|
17
|
+
return isList(keys) ? keys : Object.entries(keys).flatMap(([name, ids]) => [name, ...ids.map((id) => recordKey(name, id))]);
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
const guard = Effect.fn("Transact.guard")(function* () {
|
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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
});
|