supalite 0.5.5 โ 0.5.6
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 +5 -0
- package/README.md +13 -1
- package/dist/postgres-client.d.ts +1 -0
- package/dist/postgres-client.js +15 -0
- package/dist/query-builder.js +12 -1
- package/docs/changelog/2025-12-17-embed-many-to-one.md +30 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.6] - 2025-12-17
|
|
4
|
+
|
|
5
|
+
### ๐ Fixed
|
|
6
|
+
- `select()`์ PostgREST-style embed(`related_table(*)`)๊ฐ **์๋ฐฉํฅ FK**๋ฅผ ์ง์ํ๋๋ก ๊ฐ์ ํ์ต๋๋ค. ์ด์ 1:N ๊ด๊ณ๋ ๋ฐฐ์ด(`[]` ๊ธฐ๋ณธ๊ฐ), N:1 ๊ด๊ณ๋ ๊ฐ์ฒด(๋๋ `null`)๋ก ๋ฐํํฉ๋๋ค. (See [docs/changelog/2025-12-17-embed-many-to-one.md](docs/changelog/2025-12-17-embed-many-to-one.md))
|
|
7
|
+
|
|
3
8
|
## [0.5.5] - 2025-11-26
|
|
4
9
|
|
|
5
10
|
### โจ Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SupaLite
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/supalite)
|
|
4
4
|
|
|
5
5
|
๊ฐ๋ณ๊ณ ํจ์จ์ ์ธ PostgreSQL ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์
๋๋ค. Supabase์ ๋์ผํ API๋ฅผ ์ ๊ณตํ๋ฉด์๋ ๋ ๊ฐ๋ณ๊ณ ๋น ๋ฅธ ๊ตฌํ์ ์ ๊ณตํฉ๋๋ค.
|
|
6
6
|
|
|
@@ -213,6 +213,17 @@ const { data, error } = await client
|
|
|
213
213
|
.from('users')
|
|
214
214
|
.ilike('email', '%@example.com');
|
|
215
215
|
|
|
216
|
+
// ๊ด๊ณ ํ
์ด๋ธ ์กฐํ (PostgREST-style embed)
|
|
217
|
+
// 1:N ๊ด๊ณ๋ ๋ฐฐ์ด๋ก ๋ฐํ๋ฉ๋๋ค (๊ธฐ๋ณธ๊ฐ: [])
|
|
218
|
+
const { data: authors } = await client
|
|
219
|
+
.from('authors')
|
|
220
|
+
.select('*, books(*)');
|
|
221
|
+
|
|
222
|
+
// N:1 ๊ด๊ณ๋ ๊ฐ์ฒด๋ก ๋ฐํ๋ฉ๋๋ค (๋๋ null)
|
|
223
|
+
const { data: books } = await client
|
|
224
|
+
.from('books')
|
|
225
|
+
.select('*, authors(*)');
|
|
226
|
+
|
|
216
227
|
// ์ ํํ ์นด์ดํธ์ ํจ๊ป ์กฐํ
|
|
217
228
|
const { data, count, error } = await client
|
|
218
229
|
.from('users')
|
|
@@ -270,6 +281,7 @@ const { data, error } = await client
|
|
|
270
281
|
- `select(columns?: string, options?: { count?: 'exact' | 'planned' | 'estimated', head?: boolean })`: ์กฐํํ ์ปฌ๋ผ ์ง์
|
|
271
282
|
- `options.count`: `'exact'`๋ก ์ค์ ํ๋ฉด `limit`์ ์ํฅ์ ๋ฐ์ง ์๋ ์ ์ฒด ๊ฒฐ๊ณผ์ ๊ฐ์๋ฅผ `count` ์์ฑ์ผ๋ก ๋ฐํํฉ๋๋ค.
|
|
272
283
|
- `options.head`: `true`๋ก ์ค์ ํ๋ฉด ๋ฐ์ดํฐ ์์ด `count`๋ง ๊ฐ์ ธ์ต๋๋ค. `count` ์ต์
๊ณผ ํจ๊ป ์ฌ์ฉํ๋ฉด ํจ์จ์ ์ผ๋ก ์ ์ฒด ๊ฐ์๋ง ์กฐํํ ์ ์์ต๋๋ค.
|
|
284
|
+
- PostgREST-style embed: `select('*, related_table(*)')` ๋๋ `select('col, related_table(col1, col2)')`
|
|
273
285
|
- `insert(data: T['Tables'][K]['Insert'] | T['Tables'][K]['Insert'][])`: ๋จ์ผ ๋๋ ๋ค์ค ๋ ์ฝ๋ ์ฝ์
|
|
274
286
|
- `update(data: T['Tables'][K]['Update'])`: ๋ ์ฝ๋ ์
๋ฐ์ดํธ
|
|
275
287
|
- `delete()`: ๋ ์ฝ๋ ์ญ์
|
|
@@ -67,6 +67,7 @@ export declare class SupaLitePG<T extends {
|
|
|
67
67
|
getForeignKey(schema: string, table: string, foreignTable: string): Promise<{
|
|
68
68
|
column: string;
|
|
69
69
|
foreignColumn: string;
|
|
70
|
+
isArray: boolean;
|
|
70
71
|
} | null>;
|
|
71
72
|
rpc(procedureName: string, params?: Record<string, any>): RpcBuilder;
|
|
72
73
|
testConnection(): Promise<boolean>;
|
package/dist/postgres-client.js
CHANGED
|
@@ -329,11 +329,26 @@ class SupaLitePG {
|
|
|
329
329
|
`;
|
|
330
330
|
const activeClient = this.isTransaction && this.client ? this.client : await this.pool.connect();
|
|
331
331
|
try {
|
|
332
|
+
// 1) One-to-many: `foreignTable` has a foreign key referencing `table`
|
|
333
|
+
// e.g. authors <- books.author_id, so embedding books(*) on authors returns an array
|
|
332
334
|
const result = await activeClient.query(query, [schema, foreignTable, table]);
|
|
333
335
|
if (result.rows.length > 0) {
|
|
334
336
|
const relationship = {
|
|
335
337
|
column: result.rows[0].foreign_column_name,
|
|
336
338
|
foreignColumn: result.rows[0].column_name,
|
|
339
|
+
isArray: true,
|
|
340
|
+
};
|
|
341
|
+
this.foreignKeyCache.set(cacheKey, relationship);
|
|
342
|
+
return relationship;
|
|
343
|
+
}
|
|
344
|
+
// 2) Many-to-one: `table` has a foreign key referencing `foreignTable`
|
|
345
|
+
// e.g. books.author_id -> authors.id, so embedding authors(*) on books returns an object
|
|
346
|
+
const reverseResult = await activeClient.query(query, [schema, table, foreignTable]);
|
|
347
|
+
if (reverseResult.rows.length > 0) {
|
|
348
|
+
const relationship = {
|
|
349
|
+
column: reverseResult.rows[0].column_name,
|
|
350
|
+
foreignColumn: reverseResult.rows[0].foreign_column_name,
|
|
351
|
+
isArray: false,
|
|
337
352
|
};
|
|
338
353
|
this.foreignKeyCache.set(cacheKey, relationship);
|
|
339
354
|
return relationship;
|
package/dist/query-builder.js
CHANGED
|
@@ -280,12 +280,23 @@ class QueryBuilder {
|
|
|
280
280
|
return null;
|
|
281
281
|
}
|
|
282
282
|
const foreignSchemaTable = `"${String(this.schema)}"."${join.foreignTable}"`;
|
|
283
|
+
if (fk.isArray) {
|
|
284
|
+
return `(
|
|
285
|
+
SELECT COALESCE(json_agg(j), '[]'::json)
|
|
286
|
+
FROM (
|
|
287
|
+
SELECT ${join.columns}
|
|
288
|
+
FROM ${foreignSchemaTable}
|
|
289
|
+
WHERE "${fk.foreignColumn}" = ${schemaTable}."${fk.column}"
|
|
290
|
+
) as j
|
|
291
|
+
) as "${join.foreignTable}"`;
|
|
292
|
+
}
|
|
283
293
|
return `(
|
|
284
|
-
SELECT
|
|
294
|
+
SELECT row_to_json(j)
|
|
285
295
|
FROM (
|
|
286
296
|
SELECT ${join.columns}
|
|
287
297
|
FROM ${foreignSchemaTable}
|
|
288
298
|
WHERE "${fk.foreignColumn}" = ${schemaTable}."${fk.column}"
|
|
299
|
+
LIMIT 1
|
|
289
300
|
) as j
|
|
290
301
|
) as "${join.foreignTable}"`;
|
|
291
302
|
}));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# PostgREST-style Embed: Many-to-One Support
|
|
2
|
+
|
|
3
|
+
- **Date**: 2025-12-17
|
|
4
|
+
- **Author**: Codex
|
|
5
|
+
- **Status**: Completed
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
Fixed PostgREST-style embed syntax in `select()` (e.g. `related_table(*)`) so it works for both relationship directions:
|
|
10
|
+
|
|
11
|
+
- **1:N** (foreign table references the base table) returns an **array** (defaults to `[]`).
|
|
12
|
+
- **N:1** (base table references the foreign table) returns a **single object** (or `null`).
|
|
13
|
+
|
|
14
|
+
## Changes
|
|
15
|
+
|
|
16
|
+
1. **Bidirectional FK resolution**
|
|
17
|
+
- `SupaLitePG.getForeignKey()` now checks both directions between `table` and `foreignTable` and returns whether the embed should be an array or object.
|
|
18
|
+
|
|
19
|
+
2. **Correct JSON shape in SQL generation**
|
|
20
|
+
- `QueryBuilder` uses `json_agg` (with `COALESCE(..., '[]'::json)`) for 1:N embeds.
|
|
21
|
+
- `QueryBuilder` uses `row_to_json` (with `LIMIT 1`) for N:1 embeds.
|
|
22
|
+
|
|
23
|
+
3. **Unit tests**
|
|
24
|
+
- Added tests to cover N:1 embed behavior and nested column selection.
|
|
25
|
+
|
|
26
|
+
## Impact
|
|
27
|
+
|
|
28
|
+
- Queries like `from('menu_item_opts').select('*, menu_item_opts_schema(*)')` now embed `menu_item_opts_schema` without warnings, matching PostgREST expectations.
|
|
29
|
+
- Existing 1:N embed behavior remains compatible, with an improved empty-result shape (`[]` instead of `null`).
|
|
30
|
+
|