ugly-app 0.1.310 → 0.1.312

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 (50) hide show
  1. package/dist/cli/authCommands.js +1 -1
  2. package/dist/cli/authCommands.js.map +1 -1
  3. package/dist/cli/dev.d.ts.map +1 -1
  4. package/dist/cli/dev.js +0 -25
  5. package/dist/cli/dev.js.map +1 -1
  6. package/dist/cli/migrate.js +1 -1
  7. package/dist/cli/migrate.js.map +1 -1
  8. package/dist/cli/scaffold.d.ts.map +1 -1
  9. package/dist/cli/scaffold.js +4 -1
  10. package/dist/cli/scaffold.js.map +1 -1
  11. package/dist/cli/serverLogQuery.js +1 -1
  12. package/dist/cli/serverLogQuery.js.map +1 -1
  13. package/dist/cli/version.d.ts +1 -1
  14. package/dist/cli/version.js +1 -1
  15. package/dist/server/App.d.ts.map +1 -1
  16. package/dist/server/App.js +1 -5
  17. package/dist/server/App.js.map +1 -1
  18. package/dist/server/DataProxyClient.d.ts +1 -1
  19. package/dist/server/DataProxyClient.d.ts.map +1 -1
  20. package/dist/server/DataProxyClient.js +5 -10
  21. package/dist/server/DataProxyClient.js.map +1 -1
  22. package/dist/server/Logging.d.ts +1 -1
  23. package/dist/server/Logging.d.ts.map +1 -1
  24. package/dist/server/SchemaCheck.d.ts.map +1 -1
  25. package/dist/server/SchemaCheck.js +0 -1
  26. package/dist/server/SchemaCheck.js.map +1 -1
  27. package/dist/server/Socket.d.ts +1 -1
  28. package/dist/server/Socket.d.ts.map +1 -1
  29. package/eslint.config.js +1 -0
  30. package/package.json +1 -3
  31. package/src/cli/authCommands.ts +1 -1
  32. package/src/cli/dev.ts +0 -30
  33. package/src/cli/migrate.ts +1 -1
  34. package/src/cli/scaffold.ts +4 -1
  35. package/src/cli/serverLogQuery.ts +1 -1
  36. package/src/cli/version.ts +1 -1
  37. package/src/server/App.ts +1 -5
  38. package/src/server/DataProxyClient.ts +6 -12
  39. package/src/server/Logging.ts +1 -1
  40. package/src/server/SchemaCheck.ts +0 -1
  41. package/src/server/Socket.ts +1 -1
  42. package/src/server/Postgres.ts +0 -29
  43. package/src/server/PostgresDB.ts +0 -263
  44. package/src/server/PostgresFilter.ts +0 -162
  45. package/src/server/PostgresOperators.ts +0 -136
  46. package/src/server/PostgresPipeline.ts +0 -262
  47. package/src/server/PostgresSchema.ts +0 -51
  48. package/src/server/PostgresSearch.ts +0 -109
  49. package/src/server/Qdrant.ts +0 -110
  50. package/src/server/Storage.ts +0 -135
@@ -2,7 +2,7 @@ import type { IncomingMessage as HttpIncomingMessage, Server } from 'http';
2
2
  import { WebSocket, WebSocketServer } from 'ws';
3
3
  import type { CollectionRegistry, TypedDB, UserHelper } from '../shared/DB.js';
4
4
  import type { AppRouter } from './Router.js';
5
- import type { StorageClient } from './Storage.js';
5
+ import type { StorageClient } from './DataProxyClient.js';
6
6
  export declare function initWebSocketServer(server: Server, router: AppRouter, collections: CollectionRegistry, _storage: StorageClient | null, typedDb: TypedDB<any>, _userHelper: UserHelper<any>, wsPath?: string, onSocketMessageHandlers?: ((ws: WebSocket, userId: string, msg: unknown) => boolean)[],
7
7
  /** Pass an existing WebSocketServer to skip creating a new one and skip
8
8
  * registering the HTTP upgrade handler (the caller manages routing). */
@@ -1 +1 @@
1
- {"version":3,"file":"Socket.d.ts","sourceRoot":"","sources":["../../src/server/Socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,IAAI,mBAAmB,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA+ClD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,kBAAkB,EAC/B,QAAQ,EAAE,aAAa,GAAG,IAAI,EAE9B,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAErB,WAAW,EAAE,UAAU,CAAC,GAAG,CAAC,EAC5B,MAAM,SAAS,EACf,uBAAuB,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,EAAE;AACtF;yEACyE;AACzE,WAAW,CAAC,EAAE,eAAe;AAC7B;+EAC+E;AAC/E,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACzF,eAAe,CA2ZjB"}
1
+ {"version":3,"file":"Socket.d.ts","sourceRoot":"","sources":["../../src/server/Socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,IAAI,mBAAmB,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAC3E,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA+C1D,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,kBAAkB,EAC/B,QAAQ,EAAE,aAAa,GAAG,IAAI,EAE9B,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAErB,WAAW,EAAE,UAAU,CAAC,GAAG,CAAC,EAC5B,MAAM,SAAS,EACf,uBAAuB,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,EAAE;AACtF;yEACyE;AACzE,WAAW,CAAC,EAAE,eAAe;AAC7B;+EAC+E;AAC/E,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACzF,eAAe,CA2ZjB"}
package/eslint.config.js CHANGED
@@ -87,6 +87,7 @@ export default [
87
87
  '@typescript-eslint/no-extraneous-class': 'off',
88
88
  '@typescript-eslint/no-deprecated': 'off',
89
89
  '@typescript-eslint/no-unnecessary-type-parameters': 'off',
90
+ '@typescript-eslint/no-unnecessary-type-arguments': 'off',
90
91
  '@typescript-eslint/no-redundant-type-constituents': 'off',
91
92
  '@typescript-eslint/only-throw-error': 'off',
92
93
  '@typescript-eslint/no-useless-constructor': 'off',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-app",
3
- "version": "0.1.310",
3
+ "version": "0.1.312",
4
4
  "type": "module",
5
5
  "main": "./dist/server/index.js",
6
6
  "exports": {
@@ -40,7 +40,6 @@
40
40
  "dependencies": {
41
41
  "@aws-sdk/client-s3": "^3.700.0",
42
42
  "@aws-sdk/s3-request-presigner": "^3.700.0",
43
- "@qdrant/js-client-rest": "^1.17.0",
44
43
  "@tanstack/virtual-core": "^3.0.0",
45
44
  "@types/jsonwebtoken": "^9.0.10",
46
45
  "bcrypt": "^6.0.0",
@@ -59,7 +58,6 @@
59
58
  "nanoid": "^5.1.5",
60
59
  "nats": "^2.28.2",
61
60
  "onnxruntime-node": "^1.23.0",
62
- "pg": "^8.20.0",
63
61
  "react-datepicker": "^9.1.0",
64
62
  "ws": "^8.18.0",
65
63
  "zod": "^4.3.6"
@@ -40,7 +40,7 @@ export async function createAccount(
40
40
 
41
41
  return { userId, email };
42
42
  } finally {
43
- await stopDataProxy();
43
+ stopDataProxy();
44
44
  }
45
45
  }
46
46
 
package/src/cli/dev.ts CHANGED
@@ -2,8 +2,6 @@ import { exec, execSync, spawn } from 'child_process';
2
2
  import fs from 'fs';
3
3
  import net from 'net';
4
4
  import path from 'path';
5
- import pg from 'pg';
6
-
7
5
  function waitForPort(port: number, retries = 30, intervalMs = 1000): Promise<void> {
8
6
  return new Promise((resolve, reject) => {
9
7
  let attempts = 0;
@@ -26,30 +24,6 @@ import {
26
24
  validateLocalPort,
27
25
  } from './uglyappConfig.js';
28
26
 
29
- async function waitForPostgres(
30
- url: string,
31
- opts: { retries: number; intervalMs: number },
32
- ): Promise<void> {
33
- for (let i = 0; i < opts.retries; i++) {
34
- try {
35
- const client = new pg.Client({ connectionString: url, connectionTimeoutMillis: 2000 });
36
- await client.connect();
37
- await client.query('SELECT 1');
38
- await client.end();
39
- return;
40
- } catch {
41
- // fall through to retry
42
- }
43
- if (i === opts.retries - 1)
44
- throw new Error(
45
- `[ugly-app] PostgreSQL not ready after ${Math.round((opts.retries * opts.intervalMs) / 1000)}s — check ugly.bot connection`,
46
- );
47
- if (i % 5 === 4) process.stdout.write('.');
48
- await new Promise((r) => setTimeout(r, opts.intervalMs));
49
- }
50
- }
51
-
52
-
53
27
  export async function runDev(opts: { watch: boolean; verbose: boolean } = { watch: false, verbose: false }): Promise<void> {
54
28
  const config = readUglyAppConfig(process.cwd());
55
29
  if (config !== null) {
@@ -97,10 +71,6 @@ export async function runDev(opts: { watch: boolean; verbose: boolean } = { watc
97
71
  // ── 2. Wait for data proxy ─────────────────────────────────────────────────
98
72
  if (process.env['DATA_PROXY_URL']) {
99
73
  process.stdout.write('[DataProxy] Connected to data proxy\n');
100
- } else if (process.env['POSTGRES_URL']) {
101
- process.stdout.write('[ugly-app] Waiting for PostgreSQL');
102
- await waitForPostgres(process.env['POSTGRES_URL']!, { retries: 30, intervalMs: 1000 });
103
- process.stdout.write(' ✓\n');
104
74
  }
105
75
 
106
76
  // ── 3. Run any pending migrations ─────────────────────────────────────────
@@ -117,6 +117,6 @@ export async function runMigrations(
117
117
  }
118
118
  }
119
119
  } finally {
120
- await stopDataProxy();
120
+ stopDataProxy();
121
121
  }
122
122
  }
@@ -109,8 +109,11 @@ export async function scaffoldProject(projectName: string): Promise<void> {
109
109
  for (const [name, def] of Object.entries(BUILTIN_DEFS)) {
110
110
  bootstrapCollections[name] = { indexes: (def as { indexes?: { fields: Record<string, 1 | -1>; unique?: boolean; ttl?: number }[] }).indexes };
111
111
  }
112
- // Template ships with todo (no indexes). User data lives on ugly.bot.
112
+ // Template collections todo, conversation, message, collabDoc
113
113
  bootstrapCollections['todo'] = {};
114
+ bootstrapCollections['conversation'] = {};
115
+ bootstrapCollections['message'] = { indexes: [{ fields: { conversationId: 1 } }] };
116
+ bootstrapCollections['collabDoc'] = {};
114
117
 
115
118
  const migrationCode = generateBootstrapMigration(bootstrapCollections);
116
119
  await fs.writeFile(path.join(migrationsDir, '001_bootstrap.ts'), migrationCode, 'utf-8');
@@ -140,6 +140,6 @@ export async function queryServerLogs(
140
140
  }
141
141
  }
142
142
  } finally {
143
- await stopDataProxy();
143
+ stopDataProxy();
144
144
  }
145
145
  }
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.310";
2
+ export const CLI_VERSION = "0.1.312";
package/src/server/App.ts CHANGED
@@ -962,11 +962,7 @@ export function createApp<
962
962
  console.log('[App] SIGTERM received, shutting down gracefully');
963
963
  await new Promise<void>((resolve) => httpServer.close(() => resolve()));
964
964
  if (workerQueue) await workerQueue.stop();
965
- await stopDataProxy();
966
- if (process.env.QDRANT_URL) {
967
- const { stopQdrant } = await import('./Qdrant.js');
968
- stopQdrant();
969
- }
965
+ stopDataProxy();
970
966
  console.log('[App] Shutdown complete');
971
967
  process.exit(0);
972
968
  };
@@ -39,12 +39,6 @@ let _connected = false;
39
39
  // Queue of messages to send when connection is established
40
40
  const _sendQueue: string[] = [];
41
41
 
42
- function ensureConnected(): void {
43
- if (!_ws || _ws.readyState !== WebSocket.OPEN) {
44
- throw new Error('[DataProxy] Not connected to data proxy server');
45
- }
46
- }
47
-
48
42
  async function rpc(op: string, args: unknown[]): Promise<unknown> {
49
43
  // Wait for connection if still connecting
50
44
  if (_connectPromise) await _connectPromise;
@@ -57,7 +51,7 @@ async function rpc(op: string, args: unknown[]): Promise<unknown> {
57
51
  return new Promise<unknown>((resolve, reject) => {
58
52
  _pending.set(id, { resolve, reject });
59
53
 
60
- if (_ws && _ws.readyState === WebSocket.OPEN) {
54
+ if (_ws?.readyState === WebSocket.OPEN) {
61
55
  _ws.send(msg);
62
56
  } else {
63
57
  _sendQueue.push(msg);
@@ -145,8 +139,8 @@ export async function initDataProxy(url: string, token: string): Promise<void> {
145
139
  if (_url && _token) {
146
140
  setTimeout(() => {
147
141
  console.log('[DataProxy] Reconnecting...');
148
- initDataProxy(_url, _token).catch((err) => {
149
- console.error('[DataProxy] Reconnect failed:', err.message);
142
+ initDataProxy(_url, _token).catch((err: unknown) => {
143
+ console.error('[DataProxy] Reconnect failed:', err instanceof Error ? err.message : err);
150
144
  });
151
145
  }, 1000);
152
146
  }
@@ -161,15 +155,14 @@ export async function initDataProxy(url: string, token: string): Promise<void> {
161
155
  });
162
156
  });
163
157
 
164
- _connectPromise.finally(() => {
158
+ void _connectPromise.finally(() => {
165
159
  _connectPromise = null;
166
160
  });
167
161
 
168
162
  return _connectPromise;
169
163
  }
170
164
 
171
- export async function stopDataProxy(): Promise<void> {
172
- const url = _url;
165
+ export function stopDataProxy(): void {
173
166
  _url = '';
174
167
  _token = '';
175
168
  _connected = false;
@@ -299,6 +292,7 @@ export async function pgFind<T>(
299
292
  }
300
293
 
301
294
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
295
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
302
296
  export async function pgFindCount<T = unknown>(
303
297
  collection: string,
304
298
  filter?: any,
@@ -3,7 +3,7 @@ import fsSync from 'node:fs';
3
3
  import fs from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import type { TypedDB } from '../shared/DB.js';
6
- import type { StorageClient } from './Storage.js';
6
+ import type { StorageClient } from './DataProxyClient.js';
7
7
 
8
8
  // ─── App API forwarding ─────────────────────────────────────────────────────
9
9
 
@@ -1,6 +1,5 @@
1
1
  import type { z } from 'zod';
2
2
  import { pgQuery } from './DataProxyClient.js';
3
- // Compatibility shim for code that uses Postgres.query()
4
3
  const Postgres = { query: pgQuery };
5
4
  import type { CollectionDefRegistry, IndexDef } from '../shared/DB.js';
6
5
  import { serializeSchema, type SchemaDescriptor } from '../shared/SchemaSerializer.js';
@@ -5,7 +5,7 @@ import { verifyToken } from './Auth.js';
5
5
  import { subscribeCollection, subscribeDoc, subscribeDocKey } from './NatsStore.js';
6
6
  import { registerPushConnection } from './PushNotification.js';
7
7
  import type { AppRouter } from './Router.js';
8
- import type { StorageClient } from './Storage.js';
8
+ import type { StorageClient } from './DataProxyClient.js';
9
9
 
10
10
  interface Session {
11
11
  userId: string;
@@ -1,29 +0,0 @@
1
- import pg from 'pg';
2
-
3
- const { Pool } = pg;
4
-
5
- let _pool: pg.Pool | null = null;
6
-
7
- export async function initPostgres(connectionString: string): Promise<void> {
8
- _pool = new Pool({ connectionString });
9
- // Verify connection
10
- const client = await _pool.connect();
11
- client.release();
12
- }
13
-
14
- export function getPool(): pg.Pool {
15
- if (!_pool) throw new Error('[DB] PostgreSQL not initialized — call initPostgres first');
16
- return _pool;
17
- }
18
-
19
- export async function query<T extends pg.QueryResultRow = pg.QueryResultRow>(
20
- text: string,
21
- values?: unknown[],
22
- ): Promise<pg.QueryResult<T>> {
23
- return getPool().query<T>(text, values);
24
- }
25
-
26
- export async function stopPostgres(): Promise<void> {
27
- await _pool?.end();
28
- _pool = null;
29
- }
@@ -1,263 +0,0 @@
1
- import type { DBObject, DocFields, FindOptions, MongoUpdateOp, TypedFilter } from '../shared/DB.js';
2
- import { query } from './Postgres.js';
3
- import { translateFilter, translateSort } from './PostgresFilter.js';
4
- import { applyUpdateOp } from './PostgresOperators.js';
5
- import { translatePipeline } from './PostgresPipeline.js';
6
-
7
- /** Merge JSONB data with top-level DB columns into a document */
8
- function toDoc<T extends DBObject>(row: {
9
- _id: string;
10
- data: Record<string, unknown>;
11
- created: Date;
12
- updated: Date;
13
- version: number;
14
- }): T {
15
- return {
16
- ...row.data,
17
- _id: row._id,
18
- created: row.created,
19
- updated: row.updated,
20
- version: row.version,
21
- } as unknown as T;
22
- }
23
-
24
- /** Strip DB metadata fields from a doc to get the JSONB data payload */
25
- function toData(doc: DBObject): Record<string, unknown> {
26
- const { _id: _, version: _v, created: _c, updated: _u, ...data } = doc as unknown as Record<string, unknown>;
27
- return data;
28
- }
29
-
30
- interface FullRow { _id: string; data: Record<string, unknown>; created: Date; updated: Date; version: number }
31
-
32
- export async function pgGetDoc<T extends DBObject>(
33
- collection: string,
34
- id: string,
35
- ): Promise<T | null> {
36
- const result = await query<FullRow>(
37
- `SELECT _id, data, created, updated, version FROM "${collection}" WHERE _id = $1`,
38
- [id],
39
- );
40
- if (result.rows.length === 0) return null;
41
- return toDoc<T>(result.rows[0]);
42
- }
43
-
44
- export async function pgSetDoc<T extends DBObject>(
45
- collection: string,
46
- doc: T,
47
- options?: { skipIfExists?: boolean },
48
- ): Promise<boolean> {
49
- const data = toData(doc);
50
-
51
- if (options?.skipIfExists) {
52
- const result = await query(
53
- `INSERT INTO "${collection}" (_id, data, created, updated, version)
54
- VALUES ($1, $2, $3, $4, $5)
55
- ON CONFLICT (_id) DO NOTHING`,
56
- [doc._id, JSON.stringify(data), doc.created, doc.updated, doc.version],
57
- );
58
- return (result.rowCount ?? 0) > 0;
59
- }
60
-
61
- await query(
62
- `INSERT INTO "${collection}" (_id, data, created, updated, version)
63
- VALUES ($1, $2, $3, $4, $5)
64
- ON CONFLICT (_id) DO UPDATE SET
65
- data = EXCLUDED.data,
66
- updated = EXCLUDED.updated,
67
- version = EXCLUDED.version`,
68
- [doc._id, JSON.stringify(data), doc.created, doc.updated, doc.version],
69
- );
70
- return true;
71
- }
72
-
73
- export async function pgSetDocFields<T extends DBObject>(
74
- collection: string,
75
- id: string,
76
- fields: DocFields<T>,
77
- ): Promise<T> {
78
- const result = await applyUpdateOp(collection, id, { $set: fields as Record<string, unknown> });
79
- if (!result) throw new Error(`[DB] Document not found: ${collection}/${id}`);
80
- return result as unknown as T;
81
- }
82
-
83
- export async function pgSetDocFieldsOrIgnore<T extends DBObject>(
84
- collection: string,
85
- id: string,
86
- fields: DocFields<T>,
87
- ): Promise<T | null> {
88
- const result = await applyUpdateOp(collection, id, { $set: fields as Record<string, unknown> });
89
- return result as unknown as T | null;
90
- }
91
-
92
- export async function pgSetDocFieldsOrCreate<T extends DBObject>(
93
- collection: string,
94
- id: string,
95
- fields: DocFields<T>,
96
- obj: T,
97
- ): Promise<T> {
98
- const existing = await pgSetDocFieldsOrIgnore<T>(collection, id, fields);
99
- if (existing) return existing;
100
- await pgSetDoc(collection, obj);
101
- return obj;
102
- }
103
-
104
- export async function pgSetDocOp<T extends DBObject>(
105
- collection: string,
106
- id: string,
107
- op: MongoUpdateOp<T>,
108
- ): Promise<T> {
109
- const result = await applyUpdateOp(collection, id, op as Record<string, unknown>);
110
- if (!result) throw new Error(`[DB] Document not found: ${collection}/${id}`);
111
- return result as unknown as T;
112
- }
113
-
114
- export async function pgSetDocOpOrIgnore<T extends DBObject>(
115
- collection: string,
116
- id: string,
117
- op: MongoUpdateOp<T>,
118
- ): Promise<T | null> {
119
- const result = await applyUpdateOp(collection, id, op as Record<string, unknown>);
120
- return result as unknown as T | null;
121
- }
122
-
123
- export async function pgDeleteDoc(
124
- collection: string,
125
- id: string,
126
- ): Promise<void> {
127
- await query(`DELETE FROM "${collection}" WHERE _id = $1`, [id]);
128
- }
129
-
130
- export async function pgGetDocs<T extends DBObject>(
131
- collection: string,
132
- filter: Record<string, unknown> = {},
133
- options: {
134
- sort?: Record<string, 1 | -1>;
135
- limit?: number;
136
- skip?: number;
137
- } = {},
138
- ): Promise<T[]> {
139
- const { where, values } = translateFilter(filter);
140
- let sql = `SELECT _id, data, created, updated, version FROM "${collection}"`;
141
- if (where) sql += ` WHERE ${where}`;
142
- if (options.sort) sql += ` ORDER BY ${translateSort(options.sort)}`;
143
- if (options.limit) {
144
- values.push(options.limit);
145
- sql += ` LIMIT $${values.length}`;
146
- }
147
- if (options.skip) {
148
- values.push(options.skip);
149
- sql += ` OFFSET $${values.length}`;
150
- }
151
-
152
- const result = await query<FullRow>(sql, values);
153
- return result.rows.map((row) => toDoc<T>(row));
154
- }
155
-
156
- export async function pgGetQuery<T>(
157
- collection: string,
158
- pipeline: Record<string, unknown>[],
159
- options?: { skip?: number; limit?: number },
160
- ): Promise<T[]> {
161
- const { sql, values } = translatePipeline(collection, pipeline, options);
162
- const result = await query(sql, values);
163
- return result.rows.map((row) => {
164
- if (row.data && row._id !== undefined && row.created !== undefined) {
165
- return toDoc(row as FullRow) as unknown as T;
166
- }
167
- return row as unknown as T;
168
- });
169
- }
170
-
171
- export async function pgGetQueryCount(
172
- collection: string,
173
- pipeline: Record<string, unknown>[],
174
- ): Promise<number> {
175
- const { sql, values } = translatePipeline(collection, [...pipeline, { $count: 'count' }]);
176
- const result = await query<{ count: string }>(sql, values);
177
- return Number(result.rows[0]?.count ?? 0);
178
- }
179
-
180
- export async function pgGetQueryRaw<T>(
181
- collection: string,
182
- pipeline: Record<string, unknown>[],
183
- options?: { skip?: number; limit?: number },
184
- ): Promise<T[]> {
185
- const { sql, values } = translatePipeline(collection, pipeline, options);
186
- const result = await query(sql, values);
187
- return result.rows as unknown as T[];
188
- }
189
-
190
- export async function pgDeleteDocs(
191
- collection: string,
192
- filter: Record<string, unknown>,
193
- ): Promise<string[]> {
194
- const { where, values } = translateFilter(filter);
195
- let sql = `DELETE FROM "${collection}"`;
196
- if (where) sql += ` WHERE ${where}`;
197
- sql += ` RETURNING _id`;
198
-
199
- const result = await query<{ _id: string }>(sql, values);
200
- return result.rows.map((r) => r._id);
201
- }
202
-
203
- // ---------------------------------------------------------------------------
204
- // Typed SQL-native query functions
205
- // ---------------------------------------------------------------------------
206
-
207
- export async function pgFind<T extends DBObject>(
208
- collection: string,
209
- filter: TypedFilter<T> = {} as TypedFilter<T>,
210
- options: FindOptions<T> = {} as FindOptions<T>,
211
- ): Promise<T[]> {
212
- const { where, values } = translateFilter(filter as Record<string, unknown>);
213
- let sql = `SELECT _id, data, created, updated, version FROM "${collection}"`;
214
- if (where) sql += ` WHERE ${where}`;
215
- if (options.sort) sql += ` ORDER BY ${translateSort(options.sort as Record<string, 1 | -1>)}`;
216
- if (options.limit) {
217
- values.push(options.limit);
218
- sql += ` LIMIT $${values.length}`;
219
- }
220
- if (options.skip) {
221
- values.push(options.skip);
222
- sql += ` OFFSET $${values.length}`;
223
- }
224
- const result = await query<FullRow>(sql, values);
225
- return result.rows.map((row) => toDoc<T>(row));
226
- }
227
-
228
- export async function pgFindCount<T>(
229
- collection: string,
230
- filter: TypedFilter<T> = {} as TypedFilter<T>,
231
- ): Promise<number> {
232
- const { where, values } = translateFilter(filter as Record<string, unknown>);
233
- let sql = `SELECT COUNT(*)::int AS count FROM "${collection}"`;
234
- if (where) sql += ` WHERE ${where}`;
235
- const result = await query<{ count: number }>(sql, values);
236
- return result.rows[0]?.count ?? 0;
237
- }
238
-
239
- export async function pgFindRandom<T extends DBObject>(
240
- collection: string,
241
- filter: TypedFilter<T> = {} as TypedFilter<T>,
242
- limit = 100,
243
- ): Promise<T[]> {
244
- const { where, values } = translateFilter(filter as Record<string, unknown>);
245
- let sql = `SELECT _id, data, created, updated, version FROM "${collection}"`;
246
- if (where) sql += ` WHERE ${where}`;
247
- values.push(limit);
248
- sql += ` ORDER BY RANDOM() LIMIT $${values.length}`;
249
- const result = await query<FullRow>(sql, values);
250
- return result.rows.map((row) => toDoc<T>(row));
251
- }
252
-
253
- export async function pgDeleteWhere<T>(
254
- collection: string,
255
- filter: TypedFilter<T>,
256
- ): Promise<string[]> {
257
- const { where, values } = translateFilter(filter as Record<string, unknown>);
258
- let sql = `DELETE FROM "${collection}"`;
259
- if (where) sql += ` WHERE ${where}`;
260
- sql += ` RETURNING _id`;
261
- const result = await query<{ _id: string }>(sql, values);
262
- return result.rows.map((r) => r._id);
263
- }