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.
- package/dist/cli/authCommands.js +1 -1
- package/dist/cli/authCommands.js.map +1 -1
- package/dist/cli/dev.d.ts.map +1 -1
- package/dist/cli/dev.js +0 -25
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/migrate.js +1 -1
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/scaffold.d.ts.map +1 -1
- package/dist/cli/scaffold.js +4 -1
- package/dist/cli/scaffold.js.map +1 -1
- package/dist/cli/serverLogQuery.js +1 -1
- package/dist/cli/serverLogQuery.js.map +1 -1
- package/dist/cli/version.d.ts +1 -1
- package/dist/cli/version.js +1 -1
- package/dist/server/App.d.ts.map +1 -1
- package/dist/server/App.js +1 -5
- package/dist/server/App.js.map +1 -1
- package/dist/server/DataProxyClient.d.ts +1 -1
- package/dist/server/DataProxyClient.d.ts.map +1 -1
- package/dist/server/DataProxyClient.js +5 -10
- package/dist/server/DataProxyClient.js.map +1 -1
- package/dist/server/Logging.d.ts +1 -1
- package/dist/server/Logging.d.ts.map +1 -1
- package/dist/server/SchemaCheck.d.ts.map +1 -1
- package/dist/server/SchemaCheck.js +0 -1
- package/dist/server/SchemaCheck.js.map +1 -1
- package/dist/server/Socket.d.ts +1 -1
- package/dist/server/Socket.d.ts.map +1 -1
- package/eslint.config.js +1 -0
- package/package.json +1 -3
- package/src/cli/authCommands.ts +1 -1
- package/src/cli/dev.ts +0 -30
- package/src/cli/migrate.ts +1 -1
- package/src/cli/scaffold.ts +4 -1
- package/src/cli/serverLogQuery.ts +1 -1
- package/src/cli/version.ts +1 -1
- package/src/server/App.ts +1 -5
- package/src/server/DataProxyClient.ts +6 -12
- package/src/server/Logging.ts +1 -1
- package/src/server/SchemaCheck.ts +0 -1
- package/src/server/Socket.ts +1 -1
- package/src/server/Postgres.ts +0 -29
- package/src/server/PostgresDB.ts +0 -263
- package/src/server/PostgresFilter.ts +0 -162
- package/src/server/PostgresOperators.ts +0 -136
- package/src/server/PostgresPipeline.ts +0 -262
- package/src/server/PostgresSchema.ts +0 -51
- package/src/server/PostgresSearch.ts +0 -109
- package/src/server/Qdrant.ts +0 -110
- package/src/server/Storage.ts +0 -135
package/dist/server/Socket.d.ts
CHANGED
|
@@ -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 './
|
|
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,
|
|
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.
|
|
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"
|
package/src/cli/authCommands.ts
CHANGED
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 ─────────────────────────────────────────
|
package/src/cli/migrate.ts
CHANGED
package/src/cli/scaffold.ts
CHANGED
|
@@ -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
|
|
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');
|
package/src/cli/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by prebuild — do not edit manually
|
|
2
|
-
export const CLI_VERSION = "0.1.
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
package/src/server/Logging.ts
CHANGED
|
@@ -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 './
|
|
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';
|
package/src/server/Socket.ts
CHANGED
|
@@ -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 './
|
|
8
|
+
import type { StorageClient } from './DataProxyClient.js';
|
|
9
9
|
|
|
10
10
|
interface Session {
|
|
11
11
|
userId: string;
|
package/src/server/Postgres.ts
DELETED
|
@@ -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
|
-
}
|
package/src/server/PostgresDB.ts
DELETED
|
@@ -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
|
-
}
|