@cogenta/core 0.4.0 → 0.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.
- package/CHANGELOG.md +3479 -0
- package/dist/config/env.d.ts +21 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +72 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/load-config.d.ts +9 -0
- package/dist/config/load-config.d.ts.map +1 -1
- package/dist/config/load-config.js +39 -15
- package/dist/config/load-config.js.map +1 -1
- package/dist/config/resolve-config.d.ts.map +1 -1
- package/dist/config/resolve-config.js +39 -0
- package/dist/config/resolve-config.js.map +1 -1
- package/dist/config/schema.d.ts +49 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +149 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/secret-hygiene.d.ts +55 -0
- package/dist/config/secret-hygiene.d.ts.map +1 -0
- package/dist/config/secret-hygiene.js +94 -0
- package/dist/config/secret-hygiene.js.map +1 -0
- package/dist/config/types.d.ts +166 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +4 -0
- package/dist/config/types.js.map +1 -1
- package/dist/drivers/index.d.ts +1 -1
- package/dist/drivers/index.d.ts.map +1 -1
- package/dist/drivers/index.js.map +1 -1
- package/dist/drivers/registry.d.ts.map +1 -1
- package/dist/drivers/registry.js +15 -8
- package/dist/drivers/registry.js.map +1 -1
- package/dist/drivers/types.d.ts +23 -0
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/errors/codes.d.ts +1 -1
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +234 -0
- package/dist/errors/codes.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger/error-log.d.ts +48 -0
- package/dist/logger/error-log.d.ts.map +1 -0
- package/dist/logger/error-log.js +57 -0
- package/dist/logger/error-log.js.map +1 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js +1 -0
- package/dist/logger/index.js.map +1 -1
- package/dist/media/exif.d.ts +46 -0
- package/dist/media/exif.d.ts.map +1 -0
- package/dist/media/exif.js +291 -0
- package/dist/media/exif.js.map +1 -0
- package/dist/media/folder-path.d.ts +16 -0
- package/dist/media/folder-path.d.ts.map +1 -0
- package/dist/media/folder-path.js +51 -0
- package/dist/media/folder-path.js.map +1 -0
- package/dist/media/folder-store.d.ts +26 -0
- package/dist/media/folder-store.d.ts.map +1 -0
- package/dist/media/folder-store.js +361 -0
- package/dist/media/folder-store.js.map +1 -0
- package/dist/media/index.d.ts +5 -2
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +4 -1
- package/dist/media/index.js.map +1 -1
- package/dist/media/sql-fragments.d.ts +19 -0
- package/dist/media/sql-fragments.d.ts.map +1 -0
- package/dist/media/sql-fragments.js +29 -0
- package/dist/media/sql-fragments.js.map +1 -0
- package/dist/media/store.d.ts +1 -0
- package/dist/media/store.d.ts.map +1 -1
- package/dist/media/store.js +175 -20
- package/dist/media/store.js.map +1 -1
- package/dist/media/types.d.ts +148 -0
- package/dist/media/types.d.ts.map +1 -1
- package/dist/queue/bullmq.d.ts +4 -0
- package/dist/queue/bullmq.d.ts.map +1 -1
- package/dist/queue/bullmq.js +53 -10
- package/dist/queue/bullmq.js.map +1 -1
- package/dist/queue/database.d.ts.map +1 -1
- package/dist/queue/database.js +32 -11
- package/dist/queue/database.js.map +1 -1
- package/dist/queue/types.d.ts +19 -0
- package/dist/queue/types.d.ts.map +1 -1
- package/dist/rate-limit/index.d.ts +21 -0
- package/dist/rate-limit/index.d.ts.map +1 -0
- package/dist/rate-limit/index.js +25 -0
- package/dist/rate-limit/index.js.map +1 -0
- package/dist/rate-limit/memory.d.ts +17 -0
- package/dist/rate-limit/memory.d.ts.map +1 -0
- package/dist/rate-limit/memory.js +61 -0
- package/dist/rate-limit/memory.js.map +1 -0
- package/dist/rate-limit/redis.d.ts +55 -0
- package/dist/rate-limit/redis.d.ts.map +1 -0
- package/dist/rate-limit/redis.js +134 -0
- package/dist/rate-limit/redis.js.map +1 -0
- package/dist/rate-limit/types.d.ts +52 -0
- package/dist/rate-limit/types.d.ts.map +1 -0
- package/dist/rate-limit/types.js +2 -0
- package/dist/rate-limit/types.js.map +1 -0
- package/dist/version.d.ts +39 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +84 -0
- package/dist/version.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { identifier, sql, unsafeRaw } from '../db/index.js';
|
|
3
|
+
import { CogentaError } from '../errors/index.js';
|
|
4
|
+
import { assertMediaFolderDepth, childFolderPath, folderDepthOf, isWithinFolder, MEDIA_FOLDER_PATH_LENGTH, rebasedFolderPath, } from './folder-path.js';
|
|
5
|
+
import { MEDIA_TABLE } from './store.js';
|
|
6
|
+
export const MEDIA_FOLDER_TABLE = 'cogenta_media_folders';
|
|
7
|
+
function rowToFolder(row) {
|
|
8
|
+
return {
|
|
9
|
+
id: row.id,
|
|
10
|
+
parentId: row.parent_id,
|
|
11
|
+
name: row.name,
|
|
12
|
+
path: row.path,
|
|
13
|
+
position: Number(row.position),
|
|
14
|
+
createdAt: row.created_at,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function folderNotFound(id) {
|
|
18
|
+
return new CogentaError({
|
|
19
|
+
code: 'MEDIA_FOLDER_NOT_FOUND',
|
|
20
|
+
message: `No media folder with id "${id}".`,
|
|
21
|
+
hint: 'List the folder tree to find a valid id, or the folder may already have been deleted.',
|
|
22
|
+
details: { id },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function invalidName() {
|
|
26
|
+
return new CogentaError({
|
|
27
|
+
code: 'MEDIA_FOLDER_INVALID',
|
|
28
|
+
message: 'A media folder needs a non-empty name.',
|
|
29
|
+
hint: 'Give the folder a name before creating or renaming it.',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A stable id for `ensureRoot(name)` — same normalised name, same id, every
|
|
34
|
+
* time, on every replica. That determinism is what lets the `on
|
|
35
|
+
* conflict`/`on duplicate key` upsert in `ensureRoot` resolve two concurrent
|
|
36
|
+
* bootstraps atomically in the database engine itself, rather than this
|
|
37
|
+
* layer having to guess whether a select-then-insert raced. Never used for
|
|
38
|
+
* any other folder — everything else keeps a real random id.
|
|
39
|
+
*/
|
|
40
|
+
function deterministicRootId(name) {
|
|
41
|
+
const digest = createHash('sha256').update(name.trim().toLowerCase()).digest('hex');
|
|
42
|
+
return `root-${digest.slice(0, 32)}`;
|
|
43
|
+
}
|
|
44
|
+
/** Orders two siblings by `position`, then by `id` only to break an exact tie. */
|
|
45
|
+
function siblingCompare(a, b) {
|
|
46
|
+
const byPosition = Number(a.position) - Number(b.position);
|
|
47
|
+
if (byPosition !== 0)
|
|
48
|
+
return byPosition;
|
|
49
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
50
|
+
}
|
|
51
|
+
function groupByParent(rows) {
|
|
52
|
+
const byParent = new Map();
|
|
53
|
+
for (const row of rows) {
|
|
54
|
+
const list = byParent.get(row.parent_id) ?? [];
|
|
55
|
+
list.push(row);
|
|
56
|
+
byParent.set(row.parent_id, list);
|
|
57
|
+
}
|
|
58
|
+
for (const list of byParent.values())
|
|
59
|
+
list.sort(siblingCompare);
|
|
60
|
+
return byParent;
|
|
61
|
+
}
|
|
62
|
+
/** The whole tree, flattened depth-first from the roots, siblings by `position`. */
|
|
63
|
+
function flattenTree(rows) {
|
|
64
|
+
const byParent = groupByParent(rows);
|
|
65
|
+
const ordered = [];
|
|
66
|
+
const visit = (parent) => {
|
|
67
|
+
for (const row of byParent.get(parent) ?? []) {
|
|
68
|
+
ordered.push(row);
|
|
69
|
+
visit(row.id);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
visit(null);
|
|
73
|
+
return ordered;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The persistence layer of the media library's folder tree (fiche 46).
|
|
77
|
+
*
|
|
78
|
+
* Same materialised-path shape as `@cogenta/schema`'s `TaxonomyStore`
|
|
79
|
+
* (ADR-0022): reads are one `like`, writes pay for a move. A folder name is
|
|
80
|
+
* unique among its own siblings (case/whitespace-insensitive) rather than
|
|
81
|
+
* globally — the same rule a real filesystem enforces, and one the tree
|
|
82
|
+
* shape itself (a path is ids, never names) never actually required, so
|
|
83
|
+
* enforcing it is a deliberate UX choice for this fiche, not something the
|
|
84
|
+
* storage model demanded.
|
|
85
|
+
*/
|
|
86
|
+
export function createDatabaseMediaFolderStore(options) {
|
|
87
|
+
const { db } = options;
|
|
88
|
+
const dialect = db.dialect;
|
|
89
|
+
const now = options.now ?? (() => new Date());
|
|
90
|
+
const newId = options.newId ?? randomUUID;
|
|
91
|
+
const mediaTableName = options.mediaTable ?? MEDIA_TABLE;
|
|
92
|
+
const table = identifier(MEDIA_FOLDER_TABLE, dialect);
|
|
93
|
+
const mediaTable = identifier(mediaTableName, dialect);
|
|
94
|
+
let ready = false;
|
|
95
|
+
async function ensureTable() {
|
|
96
|
+
if (ready)
|
|
97
|
+
return;
|
|
98
|
+
await db.query(sql `
|
|
99
|
+
create table if not exists ${table} (
|
|
100
|
+
id varchar(64) not null primary key,
|
|
101
|
+
parent_id varchar(64),
|
|
102
|
+
name varchar(255) not null,
|
|
103
|
+
path varchar(${unsafeRaw(String(MEDIA_FOLDER_PATH_LENGTH))}) not null,
|
|
104
|
+
position integer not null,
|
|
105
|
+
created_at varchar(32) not null
|
|
106
|
+
)`);
|
|
107
|
+
await db
|
|
108
|
+
.query(sql `create index ${identifier('cogenta_media_folders_path', dialect)} on ${table} (path)`)
|
|
109
|
+
.catch(() => undefined);
|
|
110
|
+
await db
|
|
111
|
+
.query(sql `create index ${identifier('cogenta_media_folders_parent', dialect)}
|
|
112
|
+
on ${table} (parent_id)`)
|
|
113
|
+
.catch(() => undefined);
|
|
114
|
+
ready = true;
|
|
115
|
+
}
|
|
116
|
+
async function rowOf(tx, id) {
|
|
117
|
+
const found = await tx.query(sql `select * from ${table} where id = ${id}`);
|
|
118
|
+
return found.rows[0] ?? null;
|
|
119
|
+
}
|
|
120
|
+
async function siblingsOf(tx, parentId) {
|
|
121
|
+
const found = await tx.query(parentId === null
|
|
122
|
+
? sql `select * from ${table} where parent_id is null`
|
|
123
|
+
: sql `select * from ${table} where parent_id = ${parentId}`);
|
|
124
|
+
return found.rows;
|
|
125
|
+
}
|
|
126
|
+
function normaliseName(name) {
|
|
127
|
+
return name.trim().toLowerCase();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A select-then-check, not a database constraint — a real (if narrow)
|
|
131
|
+
* gap on Postgres/MySQL, unlike `ensureRoot`'s own race. `{ immediate:
|
|
132
|
+
* true }` only serialises two connections on SQLite (a file write lock);
|
|
133
|
+
* Postgres and MySQL both discard it and run under their own default
|
|
134
|
+
* isolation, so two concurrent creates of the same sibling name can both
|
|
135
|
+
* pass this check before either commits. A unique index would need a
|
|
136
|
+
* non-`null` sentinel for the root `parent_id` to be portable across all
|
|
137
|
+
* three dialects (`taxonomy-store.ts` never had this problem — a slug is
|
|
138
|
+
* unique *globally*, not per sibling) — a migration, not a query change.
|
|
139
|
+
* Tracked in `BLOCKERS.md` § fiche 46 rather than fixed here: the worst
|
|
140
|
+
* outcome is a silent duplicate name, never data loss or an escalation.
|
|
141
|
+
*/
|
|
142
|
+
async function assertNameFree(tx, parentId, name, exceptId) {
|
|
143
|
+
const siblings = await siblingsOf(tx, parentId);
|
|
144
|
+
const needle = normaliseName(name);
|
|
145
|
+
const clash = siblings.find((row) => row.id !== exceptId && normaliseName(row.name) === needle);
|
|
146
|
+
if (clash === undefined)
|
|
147
|
+
return;
|
|
148
|
+
throw new CogentaError({
|
|
149
|
+
code: 'MEDIA_FOLDER_NAME_TAKEN',
|
|
150
|
+
message: `A folder named "${name.trim()}" already exists here.`,
|
|
151
|
+
hint: 'Folder names are unique among siblings — rename one of the two, or move this folder elsewhere.',
|
|
152
|
+
details: { parentId, name: name.trim() },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async function parentPathOf(tx, parentId) {
|
|
156
|
+
if (parentId === null)
|
|
157
|
+
return '';
|
|
158
|
+
const row = await rowOf(tx, parentId);
|
|
159
|
+
if (row === null)
|
|
160
|
+
throw folderNotFound(parentId);
|
|
161
|
+
return row.path;
|
|
162
|
+
}
|
|
163
|
+
async function nextPosition(tx, parentId) {
|
|
164
|
+
const siblings = await siblingsOf(tx, parentId);
|
|
165
|
+
return siblings.reduce((highest, row) => Math.max(highest, Number(row.position) + 1), 0);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
create: async (input) => {
|
|
169
|
+
await ensureTable();
|
|
170
|
+
const name = input.name.trim();
|
|
171
|
+
if (name.length === 0)
|
|
172
|
+
throw invalidName();
|
|
173
|
+
return db.transaction(async (tx) => {
|
|
174
|
+
const id = input.id ?? newId();
|
|
175
|
+
const parentId = input.parentId ?? null;
|
|
176
|
+
const parentPath = await parentPathOf(tx, parentId);
|
|
177
|
+
const path = childFolderPath(parentPath, id);
|
|
178
|
+
assertMediaFolderDepth(path);
|
|
179
|
+
await assertNameFree(tx, parentId, name);
|
|
180
|
+
const position = input.position ?? (await nextPosition(tx, parentId));
|
|
181
|
+
const createdAt = now().toISOString();
|
|
182
|
+
await tx.query(sql `
|
|
183
|
+
insert into ${table} (id, parent_id, name, path, position, created_at)
|
|
184
|
+
values (${id}, ${parentId}, ${name}, ${path}, ${position}, ${createdAt})`);
|
|
185
|
+
const row = await rowOf(tx, id);
|
|
186
|
+
if (row === null)
|
|
187
|
+
throw folderNotFound(id);
|
|
188
|
+
return rowToFolder(row);
|
|
189
|
+
}, { immediate: true });
|
|
190
|
+
},
|
|
191
|
+
read: async (id) => {
|
|
192
|
+
await ensureTable();
|
|
193
|
+
const row = await rowOf(db, id);
|
|
194
|
+
return row === null ? null : rowToFolder(row);
|
|
195
|
+
},
|
|
196
|
+
update: async (id, input) => {
|
|
197
|
+
await ensureTable();
|
|
198
|
+
return db.transaction(async (tx) => {
|
|
199
|
+
const row = await rowOf(tx, id);
|
|
200
|
+
if (row === null)
|
|
201
|
+
throw folderNotFound(id);
|
|
202
|
+
let name = row.name;
|
|
203
|
+
if (input.name !== undefined) {
|
|
204
|
+
name = input.name.trim();
|
|
205
|
+
if (name.length === 0)
|
|
206
|
+
throw invalidName();
|
|
207
|
+
await assertNameFree(tx, row.parent_id, name, id);
|
|
208
|
+
}
|
|
209
|
+
const position = input.position ?? row.position;
|
|
210
|
+
await tx.query(sql `update ${table} set name = ${name}, position = ${position} where id = ${id}`);
|
|
211
|
+
const after = await rowOf(tx, id);
|
|
212
|
+
if (after === null)
|
|
213
|
+
throw folderNotFound(id);
|
|
214
|
+
return rowToFolder(after);
|
|
215
|
+
}, { immediate: true });
|
|
216
|
+
},
|
|
217
|
+
move: async (id, parentId) => {
|
|
218
|
+
await ensureTable();
|
|
219
|
+
return db.transaction(async (tx) => {
|
|
220
|
+
const row = await rowOf(tx, id);
|
|
221
|
+
if (row === null)
|
|
222
|
+
throw folderNotFound(id);
|
|
223
|
+
if (parentId === id) {
|
|
224
|
+
throw new CogentaError({
|
|
225
|
+
code: 'MEDIA_FOLDER_CYCLE',
|
|
226
|
+
message: `A folder cannot be moved into itself.`,
|
|
227
|
+
hint: 'Choose a different destination.',
|
|
228
|
+
details: { id },
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const from = row.path;
|
|
232
|
+
const parentPath = await parentPathOf(tx, parentId);
|
|
233
|
+
// A materialised path makes "would this create a cycle" a string
|
|
234
|
+
// test rather than a walk: the impossible move is exactly the one
|
|
235
|
+
// whose new parent already lives inside the subtree being moved —
|
|
236
|
+
// same guard as `TaxonomyStore.move`.
|
|
237
|
+
if (parentId !== null && isWithinFolder(parentPath, from)) {
|
|
238
|
+
throw new CogentaError({
|
|
239
|
+
code: 'MEDIA_FOLDER_CYCLE',
|
|
240
|
+
message: `Moving this folder under "${parentId}" would make it its own ancestor.`,
|
|
241
|
+
hint: 'Move the destination out of this subtree first.',
|
|
242
|
+
details: { id, parentId },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const to = childFolderPath(parentPath, id);
|
|
246
|
+
if (to === from && parentId === row.parent_id)
|
|
247
|
+
return rowToFolder(row);
|
|
248
|
+
await assertNameFree(tx, parentId, row.name, id);
|
|
249
|
+
const subtree = await tx.query(sql `select * from ${table} where path like ${`${from}%`}`);
|
|
250
|
+
for (const member of subtree.rows) {
|
|
251
|
+
assertMediaFolderDepth(rebasedFolderPath(member.path, from, to));
|
|
252
|
+
}
|
|
253
|
+
for (const member of subtree.rows) {
|
|
254
|
+
const rebased = rebasedFolderPath(member.path, from, to);
|
|
255
|
+
await tx.query(sql `update ${table} set path = ${rebased} where id = ${member.id}`);
|
|
256
|
+
}
|
|
257
|
+
const position = await nextPosition(tx, parentId);
|
|
258
|
+
await tx.query(sql `update ${table} set parent_id = ${parentId}, position = ${position} where id = ${id}`);
|
|
259
|
+
const after = await rowOf(tx, id);
|
|
260
|
+
if (after === null)
|
|
261
|
+
throw folderNotFound(id);
|
|
262
|
+
return rowToFolder(after);
|
|
263
|
+
}, { immediate: true });
|
|
264
|
+
},
|
|
265
|
+
delete: async (id) => {
|
|
266
|
+
await ensureTable();
|
|
267
|
+
return db.transaction(async (tx) => {
|
|
268
|
+
const row = await rowOf(tx, id);
|
|
269
|
+
if (row === null)
|
|
270
|
+
return false;
|
|
271
|
+
const children = await tx.query(sql `select id from ${table} where parent_id = ${id}`);
|
|
272
|
+
if (children.rows.length > 0) {
|
|
273
|
+
throw new CogentaError({
|
|
274
|
+
code: 'MEDIA_FOLDER_NOT_EMPTY',
|
|
275
|
+
message: `This folder still has ${children.rows.length} subfolder(s).`,
|
|
276
|
+
hint: 'Move or delete the subfolders first. Nothing was changed.',
|
|
277
|
+
details: { id, subfolders: children.rows.length },
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// Whether any media asset still files under this folder. Wrapped
|
|
281
|
+
// so a media table that has never run `createDatabaseMediaStore`'s
|
|
282
|
+
// own `ensureTable()` (and so has no `folder_id` column yet) reads
|
|
283
|
+
// as "no assets" rather than crashing a folder-only test suite —
|
|
284
|
+
// any real server always constructs both stores together.
|
|
285
|
+
const assetCount = await tx
|
|
286
|
+
.query(sql `select count(*) as c from ${mediaTable} where folder_id = ${id}`)
|
|
287
|
+
.then((result) => Number(result.rows[0]?.c ?? 0))
|
|
288
|
+
.catch(() => 0);
|
|
289
|
+
if (assetCount > 0) {
|
|
290
|
+
throw new CogentaError({
|
|
291
|
+
code: 'MEDIA_FOLDER_NOT_EMPTY',
|
|
292
|
+
message: `This folder still holds ${assetCount} media asset(s).`,
|
|
293
|
+
hint: 'Move or delete the assets first. Nothing was changed.',
|
|
294
|
+
details: { id, assets: assetCount },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
const removed = await tx.query(sql `delete from ${table} where id = ${id}`);
|
|
298
|
+
return removed.rowsAffected > 0;
|
|
299
|
+
}, { immediate: true });
|
|
300
|
+
},
|
|
301
|
+
list: async (listOptions = {}) => {
|
|
302
|
+
await ensureTable();
|
|
303
|
+
if (listOptions.parentId !== undefined) {
|
|
304
|
+
const rows = await siblingsOf(db, listOptions.parentId);
|
|
305
|
+
return [...rows].sort(siblingCompare).map(rowToFolder);
|
|
306
|
+
}
|
|
307
|
+
const found = await db.query(sql `select * from ${table}`);
|
|
308
|
+
return flattenTree(found.rows).map(rowToFolder);
|
|
309
|
+
},
|
|
310
|
+
subtreeIds: async (id) => {
|
|
311
|
+
await ensureTable();
|
|
312
|
+
const row = await rowOf(db, id);
|
|
313
|
+
if (row === null)
|
|
314
|
+
throw folderNotFound(id);
|
|
315
|
+
const found = await db.query(sql `select id from ${table} where path like ${`${row.path}%`}`);
|
|
316
|
+
return found.rows.map((member) => member.id);
|
|
317
|
+
},
|
|
318
|
+
// Idempotent bootstrap, called on *every* `cogenta serve` startup — the
|
|
319
|
+
// one case here guaranteed to race for real, not a hypothetical: two
|
|
320
|
+
// replicas starting at once on Postgres or MySQL/MariaDB. `{ immediate:
|
|
321
|
+
// true }` only takes a real write lock on SQLite (`BEGIN IMMEDIATE`);
|
|
322
|
+
// both other drivers discard it and run under their default isolation,
|
|
323
|
+
// so a plain "read the roots, insert if absent" (what this used to do)
|
|
324
|
+
// lets both replicas see no `contents` folder and both insert one — two
|
|
325
|
+
// silent root folders with the same name, no error. Same race, same fix,
|
|
326
|
+
// as `@cogenta/schema`'s `not-found-log.ts`: a deterministic id (so both
|
|
327
|
+
// replicas target the *same* row) plus a real `on conflict`/`on
|
|
328
|
+
// duplicate key` upsert, which every dialect resolves atomically in the
|
|
329
|
+
// engine itself — never a select-then-insert this layer has to guess is
|
|
330
|
+
// safe.
|
|
331
|
+
ensureRoot: async (name) => {
|
|
332
|
+
await ensureTable();
|
|
333
|
+
const trimmed = name.trim();
|
|
334
|
+
const id = deterministicRootId(trimmed);
|
|
335
|
+
const path = childFolderPath('', id);
|
|
336
|
+
const createdAt = now().toISOString();
|
|
337
|
+
await db.transaction(async (tx) => {
|
|
338
|
+
if (dialect === 'mysql') {
|
|
339
|
+
await tx.query(sql `
|
|
340
|
+
insert into ${table} (id, parent_id, name, path, position, created_at)
|
|
341
|
+
values (${id}, ${null}, ${trimmed}, ${path}, 0, ${createdAt})
|
|
342
|
+
on duplicate key update id = id`);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
await tx.query(sql `
|
|
346
|
+
insert into ${table} (id, parent_id, name, path, position, created_at)
|
|
347
|
+
values (${id}, ${null}, ${trimmed}, ${path}, 0, ${createdAt})
|
|
348
|
+
on conflict (id) do nothing`);
|
|
349
|
+
}
|
|
350
|
+
}, { immediate: true });
|
|
351
|
+
const row = await rowOf(db, id);
|
|
352
|
+
if (row === null)
|
|
353
|
+
throw folderNotFound(id);
|
|
354
|
+
return rowToFolder(row);
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
// Re-exported for callers that only need the depth bound (the admin's own
|
|
359
|
+
// validation, for instance) without pulling in the whole path module.
|
|
360
|
+
export { folderDepthOf as mediaFolderDepthOf };
|
|
361
|
+
//# sourceMappingURL=folder-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folder-store.js","sourceRoot":"","sources":["../../src/media/folder-store.ts"],"names":[],"mappings":"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"}
|
package/dist/media/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
export { type ExifData, type ExifGps, hasGpsData, readExif, stripGpsFromJpeg } from './exif.js';
|
|
2
|
+
export { assertMediaFolderDepth, childFolderPath, folderDepthOf, isBelowFolder, isWithinFolder, MAX_MEDIA_FOLDER_DEPTH, MEDIA_FOLDER_PATH_LENGTH, rebasedFolderPath, } from './folder-path.js';
|
|
3
|
+
export { createDatabaseMediaFolderStore, type DatabaseMediaFolderStoreOptions, MEDIA_FOLDER_TABLE, } from './folder-store.js';
|
|
1
4
|
export { describeContainer, type SniffedImageFormat, sniffImageFormat } from './format-sniff.js';
|
|
2
|
-
export { createDatabaseMediaStore, type DatabaseMediaStoreOptions } from './store.js';
|
|
3
|
-
export type { CreateMediaInput, FocalPoint, ListMediaOptions, MediaAsset, MediaKind, MediaPage, MediaStore, UpdateMediaInput, } from './types.js';
|
|
5
|
+
export { createDatabaseMediaStore, type DatabaseMediaStoreOptions, MEDIA_TABLE } from './store.js';
|
|
6
|
+
export type { CreateMediaFolderInput, CreateMediaInput, FocalPoint, ListMediaFoldersOptions, ListMediaOptions, MediaAsset, MediaFolder, MediaFolderStore, MediaKind, MediaPage, MediaSortField, MediaStore, ReplaceMediaInput, UpdateMediaFolderInput, UpdateMediaInput, } from './types.js';
|
|
4
7
|
export { MEDIA_KINDS } from './types.js';
|
|
5
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,wBAAwB,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC/F,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,8BAA8B,EAC9B,KAAK,+BAA+B,EACpC,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,wBAAwB,EAAE,KAAK,yBAAyB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAClG,YAAY,EACV,sBAAsB,EACtB,gBAAgB,EAChB,UAAU,EACV,uBAAuB,EACvB,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,SAAS,EACT,SAAS,EACT,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/media/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
export { hasGpsData, readExif, stripGpsFromJpeg } from './exif.js';
|
|
2
|
+
export { assertMediaFolderDepth, childFolderPath, folderDepthOf, isBelowFolder, isWithinFolder, MAX_MEDIA_FOLDER_DEPTH, MEDIA_FOLDER_PATH_LENGTH, rebasedFolderPath, } from './folder-path.js';
|
|
3
|
+
export { createDatabaseMediaFolderStore, MEDIA_FOLDER_TABLE, } from './folder-store.js';
|
|
1
4
|
export { describeContainer, sniffImageFormat } from './format-sniff.js';
|
|
2
|
-
export { createDatabaseMediaStore } from './store.js';
|
|
5
|
+
export { createDatabaseMediaStore, MEDIA_TABLE } from './store.js';
|
|
3
6
|
export { MEDIA_KINDS } from './types.js';
|
|
4
7
|
//# sourceMappingURL=index.js.map
|
package/dist/media/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAA2B,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,wBAAwB,EAAkC,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,UAAU,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC/F,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,wBAAwB,EACxB,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,8BAA8B,EAE9B,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,iBAAiB,EAA2B,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,EAAE,wBAAwB,EAAkC,WAAW,EAAE,MAAM,YAAY,CAAA;AAkBlG,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SqlFragment } from '../db/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* A comma-separated, bound `SqlFragment` for an `in (…)` clause.
|
|
4
|
+
*
|
|
5
|
+
* `@cogenta/core`'s `sql` tagged template splices a nested fragment's parts
|
|
6
|
+
* and values in place (`dialect.ts`), so folding the list this way produces
|
|
7
|
+
* one bound placeholder per value on every dialect — never a string built by
|
|
8
|
+
* hand, which is exactly the kind of injection surface `sql` exists to
|
|
9
|
+
* remove. Kept local to the media subsystem rather than exported from
|
|
10
|
+
* `../db/index.js`: nothing else in this package needs an `in` list yet, and
|
|
11
|
+
* `AGENTS.md` asks for three real uses before a helper becomes generic.
|
|
12
|
+
*
|
|
13
|
+
* Refuses an empty list rather than quietly building `in ()`, a syntax error
|
|
14
|
+
* on every dialect — the one caller today (`store.ts`'s `folderIds` filter)
|
|
15
|
+
* already guards against this, but a helper that hands a future caller a
|
|
16
|
+
* malformed fragment instead of a named error is the wrong failure mode.
|
|
17
|
+
*/
|
|
18
|
+
export declare function valueList(values: readonly string[]): SqlFragment;
|
|
19
|
+
//# sourceMappingURL=sql-fragments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-fragments.d.ts","sourceRoot":"","sources":["../../src/media/sql-fragments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAO,MAAM,gBAAgB,CAAA;AAGtD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAYhE"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { sql } from '../db/index.js';
|
|
2
|
+
import { CogentaError } from '../errors/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* A comma-separated, bound `SqlFragment` for an `in (…)` clause.
|
|
5
|
+
*
|
|
6
|
+
* `@cogenta/core`'s `sql` tagged template splices a nested fragment's parts
|
|
7
|
+
* and values in place (`dialect.ts`), so folding the list this way produces
|
|
8
|
+
* one bound placeholder per value on every dialect — never a string built by
|
|
9
|
+
* hand, which is exactly the kind of injection surface `sql` exists to
|
|
10
|
+
* remove. Kept local to the media subsystem rather than exported from
|
|
11
|
+
* `../db/index.js`: nothing else in this package needs an `in` list yet, and
|
|
12
|
+
* `AGENTS.md` asks for three real uses before a helper becomes generic.
|
|
13
|
+
*
|
|
14
|
+
* Refuses an empty list rather than quietly building `in ()`, a syntax error
|
|
15
|
+
* on every dialect — the one caller today (`store.ts`'s `folderIds` filter)
|
|
16
|
+
* already guards against this, but a helper that hands a future caller a
|
|
17
|
+
* malformed fragment instead of a named error is the wrong failure mode.
|
|
18
|
+
*/
|
|
19
|
+
export function valueList(values) {
|
|
20
|
+
if (values.length === 0) {
|
|
21
|
+
throw new CogentaError({
|
|
22
|
+
code: 'INTERNAL',
|
|
23
|
+
message: 'valueList() was called with an empty list.',
|
|
24
|
+
hint: 'Guard the empty case before building an "in (...)" clause — this helper never builds one for zero values.',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return values.reduce((acc, value, index) => (index === 0 ? sql `${value}` : sql `${acc}, ${value}`), sql ``);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=sql-fragments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-fragments.js","sourceRoot":"","sources":["../../src/media/sql-fragments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,SAAS,CAAC,MAAyB;IACjD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,4CAA4C;YACrD,IAAI,EAAE,2GAA2G;SAClH,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAClB,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,EAC5E,GAAG,CAAA,EAAE,CACN,CAAA;AACH,CAAC"}
|
package/dist/media/store.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/media/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,gBAAgB,CAAA;
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/media/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAA0B,MAAM,gBAAgB,CAAA;AAG5E,OAAO,KAAK,EAOV,UAAU,EAGX,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,WAAW,kBAAkB,CAAA;AAgB1C,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;CAC5B;AAqID;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,UAAU,CAqSvF"}
|