@coffer-org/server 2.5.2 → 2.6.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.
@@ -2,6 +2,7 @@ import { serialize } from '@mikro-orm/core';
2
2
  import { collectionGroups, fieldEntries, storageColumns } from '@coffer-org/sdk/shelf';
3
3
  import { isGroup, isEmbeddedGroup, isJsonStored } from '@coffer-org/sdk/fields';
4
4
  import { chunk } from "./batch.js";
5
+ import { selectRows } from "./read-rows.js";
5
6
  function flattenLayout(fields) {
6
7
  const out = [];
7
8
  for (const f of fields) {
@@ -68,7 +69,7 @@ export async function readAt(em, prefix, fields, parentId) {
68
69
  for (const c of collectionGroups(fields)) {
69
70
  const table = `${prefix}__${c.key}`;
70
71
  const childFields = c.group.fields;
71
- const rows = serialize(await em.find(table, { parent_id: parentId }, { orderBy: { position: 'asc' } }));
72
+ const rows = (await selectRows(em, table, { parent_id: parentId }, { orderBy: { position: 'asc' } }));
72
73
  const acc = [];
73
74
  for (const row of rows) {
74
75
  const { id, parent_id: _pid, position: _pos, ...rest } = row;
@@ -90,9 +91,7 @@ export async function readAtMany(em, prefix, fields, parentIds) {
90
91
  const childFields = c.group.fields;
91
92
  const rows = [];
92
93
  for (const ids of chunk(parentIds)) {
93
- rows.push(...serialize(await em.find(table, { parent_id: { $in: ids } }, {
94
- orderBy: { position: 'asc' },
95
- })));
94
+ rows.push(...(await selectRows(em, table, { parent_id: { $in: ids } }, { orderBy: { position: 'asc' } })));
96
95
  }
97
96
  const nestedByChild = await readAtMany(em, table, childFields, rows.map((r) => r.id));
98
97
  for (const id of parentIds)
@@ -1,17 +1,16 @@
1
- import { serialize } from '@mikro-orm/core';
2
1
  import { getEm } from "./db.js";
3
2
  import { splitAt, writeAt, readAt, readAtMany, deleteAt } from "./collection-io.js";
4
3
  import { chunk } from "./batch.js";
4
+ import { selectRows } from "./read-rows.js";
5
5
  export function extendEntityName(e) {
6
6
  return `extend__${e.id}`;
7
7
  }
8
8
  export async function getExtendRecord(e, baseId, em) {
9
9
  const fork = em ?? getEm().fork();
10
10
  const name = extendEntityName(e);
11
- const row = await fork.findOne(name, { base_id: baseId });
12
- if (!row)
11
+ const [flat] = (await selectRows(fork, name, { base_id: baseId }, { limit: 1 }));
12
+ if (!flat)
13
13
  return undefined;
14
- const flat = serialize(row);
15
14
  const collections = await readAt(fork, name, e.fields, baseId);
16
15
  return { ...flat, ...collections };
17
16
  }
@@ -23,7 +22,7 @@ export async function getExtendRecords(e, baseIds, em) {
23
22
  const name = extendEntityName(e);
24
23
  const flats = [];
25
24
  for (const ids of chunk(baseIds)) {
26
- flats.push(...serialize(await fork.find(name, { base_id: { $in: ids } })));
25
+ flats.push(...(await selectRows(fork, name, { base_id: { $in: ids } })));
27
26
  }
28
27
  if (flats.length === 0)
29
28
  return out;
@@ -1,10 +1,10 @@
1
1
  import { textSearchKeys, titleKey, recordTitle, storageColumnsFor } from '@coffer-org/sdk/shelf';
2
- import { serialize } from '@mikro-orm/core';
3
2
  import { tokenize } from '@coffer-org/core/search';
4
3
  import { getLogger } from '@coffer-org/sdk/logger';
5
4
  import { getEm } from "./db.js";
6
5
  import { rowMatch } from "./records-api.js";
7
6
  import { ftsAvailable, ftsCandidates, MIN_FTS_QUERY_LENGTH } from "./search-index.js";
7
+ import { selectRows } from "./read-rows.js";
8
8
  const log = getLogger('search');
9
9
  export const SEARCH_SCAN_LIMIT = 1000;
10
10
  function matchCols(m) {
@@ -44,10 +44,7 @@ export async function globalSearchByScan(shelves, q, limit) {
44
44
  continue;
45
45
  if (!textSearchKeys(shelf.def).length)
46
46
  continue;
47
- const rows = (await fork.find(shelf.table, {}, {
48
- limit: SEARCH_SCAN_LIMIT,
49
- fields: matchCols(shelf.def),
50
- })).map((r) => serialize(r));
47
+ const rows = await selectRows(fork, shelf.table, {}, { limit: SEARCH_SCAN_LIMIT, fields: matchCols(shelf.def) });
51
48
  if (rows.length === SEARCH_SCAN_LIMIT) {
52
49
  log.warn('scan cap reached', { shelf: shelf.key, limit: SEARCH_SCAN_LIMIT });
53
50
  }
@@ -77,9 +74,7 @@ export async function globalSearch(shelves, q, limit) {
77
74
  continue;
78
75
  if (!textSearchKeys(shelf.def).length)
79
76
  continue;
80
- const rows = (await fork.find(shelf.table, { id: { $in: ids } }, {
81
- fields: matchCols(shelf.def),
82
- })).map((r) => serialize(r));
77
+ const rows = await selectRows(fork, shelf.table, { id: { $in: ids } }, { fields: matchCols(shelf.def) });
83
78
  scored.push(...scoreRows(shelf, rows, tokens));
84
79
  }
85
80
  scored.sort((a, b) => b.score - a.score || tieKey(a.result).localeCompare(tieKey(b.result)));
package/dist/index.js CHANGED
@@ -365,6 +365,11 @@ function maskRecordRow(library, shelf, row) {
365
365
  }
366
366
  return out;
367
367
  }
368
+ function listRow(library, shelf, row) {
369
+ const out = maskRecordRow(library, shelf, row);
370
+ const activity = out._activity;
371
+ return activity ? { ...out, _activity: { favorite: activity.favorite === true } } : out;
372
+ }
368
373
  app.get('/api/trash', async (_req, reply) => {
369
374
  const rows = await recordTrashList();
370
375
  return rows.map((entry) => ({ ...entry, record: maskRecordRow(entry.library, entry.shelf, entry.record) }));
@@ -415,7 +420,7 @@ app.get('/api/:library/:shelf', async (req, reply) => {
415
420
  let rows = await recordList(library, shelf, query, opts);
416
421
  if (req.user && tracksRecordActivity(req.headers))
417
422
  rows = await applySmartRanking(req.user.id, library, shelf, rows);
418
- return rows.map((r) => maskRecordRow(library, shelf, r));
423
+ return rows.map((r) => listRow(library, shelf, r));
419
424
  }
420
425
  const pageOpts = {
421
426
  ...opts,
@@ -431,7 +436,7 @@ app.get('/api/:library/:shelf', async (req, reply) => {
431
436
  const pageOffset = Math.max(0, Number.isFinite(pageOpts.offset) ? Math.trunc(pageOpts.offset) : 0);
432
437
  page = { rows: ranked.slice(pageOffset, pageOffset + pageLimit), total: ranked.length };
433
438
  }
434
- return { rows: page.rows.map((r) => maskRecordRow(library, shelf, r)), total: page.total };
439
+ return { rows: page.rows.map((r) => listRow(library, shelf, r)), total: page.total };
435
440
  }
436
441
  catch (e) {
437
442
  if (e instanceof UnknownShelfError)
package/dist/mutate.js CHANGED
@@ -3,6 +3,7 @@ import { buildZodObject, buildZodObjectPartial, fieldEntries } from '@coffer-org
3
3
  import { isJsonStored, decodeVmsg } from '@coffer-org/sdk/fields';
4
4
  import { resolveFlag } from '@coffer-org/sdk/condition';
5
5
  import { getEm } from "./db.js";
6
+ import { selectRows } from "./read-rows.js";
6
7
  import { normalizeFileFields, dropUnchangedFileFields, touchesFileFields } from "./file-fields.js";
7
8
  import { notifyRecordsChanged } from "./index-signal.js";
8
9
  import { encodeTemporal, decodeTemporal } from "./temporal.js";
@@ -54,18 +55,14 @@ export async function listRecords(entityName, opts = {}) {
54
55
  const fork = getEm().fork();
55
56
  const deleted = opts.deleted ?? 'active';
56
57
  const where = deleted === 'active' ? { deleted_at: null } : deleted === 'deleted' ? { deleted_at: { $ne: null } } : {};
57
- const rows = await fork.findAll(entityName, { where, orderBy: { id: 'asc' } });
58
- return serialize(rows);
58
+ return selectRows(fork, entityName, where, { orderBy: { id: 'asc' } });
59
59
  }
60
60
  export async function getRecord(m, entityName, id, opts = {}) {
61
61
  const fork = getEm().fork();
62
- const row = await fork.findOne(entityName, {
63
- id,
64
- ...(opts.includeDeleted ? {} : { deleted_at: null }),
65
- });
62
+ const [row] = await selectRows(fork, entityName, { id, ...(opts.includeDeleted ? {} : { deleted_at: null }) }, { limit: 1 });
66
63
  if (!row)
67
64
  return undefined;
68
- const flat = decodeTemporal(m, serialize(row));
65
+ const flat = decodeTemporal(m, row);
69
66
  const nested = nestEmbedded(m, flat);
70
67
  const collections = await readCollections(fork, m, id);
71
68
  return { ...nested, ...collections };
@@ -1,4 +1,3 @@
1
- import { serialize } from '@mikro-orm/core';
2
1
  import { composeRegistry } from '@coffer-org/core/compose';
3
2
  import { getLogger } from '@coffer-org/sdk/logger';
4
3
  import { initDb, getOrm, getEm } from "./db.js";
@@ -6,6 +5,7 @@ import { syncSchema } from "./schema-sync.js";
6
5
  import { systemEntities, buildPluginEntities, shelfTableName } from "./entity-schema.js";
7
6
  import { pluginHooks, pluginCtx, HttpError } from "./plugin-hooks.js";
8
7
  import { discoverPlugins, loadServerHooks } from "./plugin-discovery.js";
8
+ import { selectRows } from "./read-rows.js";
9
9
  import { runMigrations, assertSafeRequired, renameSystemShelfKey } from "./migrations.js";
10
10
  import { runSeeds } from "./seeds.js";
11
11
  import { setActiveRegistry } from "./registry-context.js";
@@ -36,12 +36,10 @@ export function getDisabled() {
36
36
  export async function getPluginSettings(pluginId) {
37
37
  try {
38
38
  const fork = getEm().fork();
39
- const row = await fork.findOne(`_settings__${pluginId}`, {
40
- plugin_id: pluginId,
41
- });
39
+ const [row] = await selectRows(fork, `_settings__${pluginId}`, { plugin_id: pluginId }, { limit: 1 });
42
40
  if (!row)
43
41
  return {};
44
- return serialize(row);
42
+ return row;
45
43
  }
46
44
  catch {
47
45
  return {};
@@ -0,0 +1,8 @@
1
+ import type { EntityManager } from '@mikro-orm/core';
2
+ export type SelectOpts = {
3
+ fields?: string[];
4
+ orderBy?: Record<string, 'ASC' | 'DESC' | 'asc' | 'desc'>;
5
+ limit?: number;
6
+ offset?: number;
7
+ };
8
+ export declare function selectRows(em: EntityManager, entityName: string, where?: Record<string, unknown>, opts?: SelectOpts): Promise<Record<string, unknown>[]>;
@@ -0,0 +1,11 @@
1
+ export async function selectRows(em, entityName, where = {}, opts = {}) {
2
+ const qb = em.createQueryBuilder(entityName);
3
+ qb.select(opts.fields ?? '*').where(where);
4
+ if (opts.orderBy)
5
+ qb.orderBy(opts.orderBy);
6
+ if (opts.limit !== undefined)
7
+ qb.limit(opts.limit, opts.offset);
8
+ else if (opts.offset !== undefined)
9
+ qb.offset(opts.offset);
10
+ return (await qb.execute('all', true));
11
+ }
@@ -1,7 +1,7 @@
1
1
  import { fieldMap, textSearchKeys, titleKey, recordTitle, listKeys, storageColumnsFor } from '@coffer-org/sdk/shelf';
2
2
  import { tokenize, matchScoreFolded, foldText } from '@coffer-org/core/search';
3
- import { serialize } from '@mikro-orm/core';
4
3
  import { getActiveRegistry, getShelf, getExtendsFor } from "./registry-context.js";
4
+ import { selectRows } from "./read-rows.js";
5
5
  import { shelfTableName } from "./entity-schema.js";
6
6
  import { getExtendRecord, getExtendRecords } from "./extend-table.js";
7
7
  import { getEm } from "./db.js";
@@ -15,7 +15,7 @@ export const MAX_PAGE = 500;
15
15
  function pickCols(row, cols) {
16
16
  const out = {};
17
17
  for (const c of cols)
18
- if (c in row)
18
+ if (c in row && row[c] !== null)
19
19
  out[c] = row[c];
20
20
  return out;
21
21
  }
@@ -137,7 +137,7 @@ export async function recordList(library, shelf, query = {}, opts = {}) {
137
137
  ...(opts.orderBy ? { orderBy: opts.orderBy } : {}),
138
138
  ...(sqlSlice ? { limit: opts.limit, offset: Math.max(0, opts.offset ?? 0) } : {}),
139
139
  };
140
- let rows = (await fork.find(ename, where, (Object.keys(findOpts).length ? findOpts : undefined))).map((r) => serialize(r));
140
+ let rows = await selectRows(fork, ename, where, findOpts);
141
141
  if (tokens.length > 0)
142
142
  rows = rows.filter((row) => rowMatch(m, row, tokens) !== null);
143
143
  if (projected)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/server",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"