@cogenta/core 0.4.0 → 0.7.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +3532 -0
  2. package/dist/config/env.d.ts +21 -0
  3. package/dist/config/env.d.ts.map +1 -1
  4. package/dist/config/env.js +72 -0
  5. package/dist/config/env.js.map +1 -1
  6. package/dist/config/index.d.ts +2 -0
  7. package/dist/config/index.d.ts.map +1 -1
  8. package/dist/config/index.js +1 -0
  9. package/dist/config/index.js.map +1 -1
  10. package/dist/config/load-config.d.ts +9 -0
  11. package/dist/config/load-config.d.ts.map +1 -1
  12. package/dist/config/load-config.js +39 -15
  13. package/dist/config/load-config.js.map +1 -1
  14. package/dist/config/resolve-config.d.ts.map +1 -1
  15. package/dist/config/resolve-config.js +39 -0
  16. package/dist/config/resolve-config.js.map +1 -1
  17. package/dist/config/schema.d.ts +49 -0
  18. package/dist/config/schema.d.ts.map +1 -1
  19. package/dist/config/schema.js +149 -1
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/config/secret-hygiene.d.ts +55 -0
  22. package/dist/config/secret-hygiene.d.ts.map +1 -0
  23. package/dist/config/secret-hygiene.js +94 -0
  24. package/dist/config/secret-hygiene.js.map +1 -0
  25. package/dist/config/types.d.ts +166 -0
  26. package/dist/config/types.d.ts.map +1 -1
  27. package/dist/config/types.js +4 -0
  28. package/dist/config/types.js.map +1 -1
  29. package/dist/drivers/index.d.ts +1 -1
  30. package/dist/drivers/index.d.ts.map +1 -1
  31. package/dist/drivers/index.js.map +1 -1
  32. package/dist/drivers/registry.d.ts.map +1 -1
  33. package/dist/drivers/registry.js +15 -8
  34. package/dist/drivers/registry.js.map +1 -1
  35. package/dist/drivers/types.d.ts +23 -0
  36. package/dist/drivers/types.d.ts.map +1 -1
  37. package/dist/errors/codes.d.ts +1 -1
  38. package/dist/errors/codes.d.ts.map +1 -1
  39. package/dist/errors/codes.js +244 -0
  40. package/dist/errors/codes.js.map +1 -1
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +2 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/logger/error-log.d.ts +48 -0
  46. package/dist/logger/error-log.d.ts.map +1 -0
  47. package/dist/logger/error-log.js +57 -0
  48. package/dist/logger/error-log.js.map +1 -0
  49. package/dist/logger/index.d.ts +2 -0
  50. package/dist/logger/index.d.ts.map +1 -1
  51. package/dist/logger/index.js +1 -0
  52. package/dist/logger/index.js.map +1 -1
  53. package/dist/media/exif.d.ts +46 -0
  54. package/dist/media/exif.d.ts.map +1 -0
  55. package/dist/media/exif.js +291 -0
  56. package/dist/media/exif.js.map +1 -0
  57. package/dist/media/folder-path.d.ts +16 -0
  58. package/dist/media/folder-path.d.ts.map +1 -0
  59. package/dist/media/folder-path.js +51 -0
  60. package/dist/media/folder-path.js.map +1 -0
  61. package/dist/media/folder-store.d.ts +26 -0
  62. package/dist/media/folder-store.d.ts.map +1 -0
  63. package/dist/media/folder-store.js +361 -0
  64. package/dist/media/folder-store.js.map +1 -0
  65. package/dist/media/index.d.ts +5 -2
  66. package/dist/media/index.d.ts.map +1 -1
  67. package/dist/media/index.js +4 -1
  68. package/dist/media/index.js.map +1 -1
  69. package/dist/media/sql-fragments.d.ts +19 -0
  70. package/dist/media/sql-fragments.d.ts.map +1 -0
  71. package/dist/media/sql-fragments.js +29 -0
  72. package/dist/media/sql-fragments.js.map +1 -0
  73. package/dist/media/store.d.ts +1 -0
  74. package/dist/media/store.d.ts.map +1 -1
  75. package/dist/media/store.js +175 -20
  76. package/dist/media/store.js.map +1 -1
  77. package/dist/media/types.d.ts +148 -0
  78. package/dist/media/types.d.ts.map +1 -1
  79. package/dist/queue/bullmq.d.ts +4 -0
  80. package/dist/queue/bullmq.d.ts.map +1 -1
  81. package/dist/queue/bullmq.js +53 -10
  82. package/dist/queue/bullmq.js.map +1 -1
  83. package/dist/queue/database.d.ts.map +1 -1
  84. package/dist/queue/database.js +32 -11
  85. package/dist/queue/database.js.map +1 -1
  86. package/dist/queue/types.d.ts +19 -0
  87. package/dist/queue/types.d.ts.map +1 -1
  88. package/dist/rate-limit/index.d.ts +21 -0
  89. package/dist/rate-limit/index.d.ts.map +1 -0
  90. package/dist/rate-limit/index.js +25 -0
  91. package/dist/rate-limit/index.js.map +1 -0
  92. package/dist/rate-limit/memory.d.ts +17 -0
  93. package/dist/rate-limit/memory.d.ts.map +1 -0
  94. package/dist/rate-limit/memory.js +61 -0
  95. package/dist/rate-limit/memory.js.map +1 -0
  96. package/dist/rate-limit/redis.d.ts +55 -0
  97. package/dist/rate-limit/redis.d.ts.map +1 -0
  98. package/dist/rate-limit/redis.js +134 -0
  99. package/dist/rate-limit/redis.js.map +1 -0
  100. package/dist/rate-limit/types.d.ts +52 -0
  101. package/dist/rate-limit/types.d.ts.map +1 -0
  102. package/dist/rate-limit/types.js +2 -0
  103. package/dist/rate-limit/types.js.map +1 -0
  104. package/dist/version.d.ts +39 -0
  105. package/dist/version.d.ts.map +1 -0
  106. package/dist/version.js +84 -0
  107. package/dist/version.js.map +1 -0
  108. package/package.json +3 -2
@@ -0,0 +1,291 @@
1
+ /**
2
+ * A minimal, zero-dependency EXIF reader and GPS scrubber for JPEG originals
3
+ * (fiche 11 task 6).
4
+ *
5
+ * Only what the media library's detail screen and its privacy notice need:
6
+ * camera make/model, the date a photo was taken, its stored orientation, and
7
+ * whether it carries GPS coordinates — never the full EXIF tag set. R9/R10
8
+ * both apply here exactly as they did to `document.extract_text` (L19 task
9
+ * 1): a JPEG's EXIF block is a TIFF structure with a documented, stable
10
+ * layout, small enough to walk by hand without a dependency that would pull
11
+ * in a general-purpose (and much larger) metadata library for four tags.
12
+ *
13
+ * **Why GPS gets stripped, not just hidden.** `docs/lots/11-mediatheque.md`
14
+ * calls out EXIF GPS coordinates as personal data a public image can leak.
15
+ * Unlinking the `GPSInfo` pointer from IFD0 is what every EXIF-aware reader
16
+ * actually follows, but leaves the original bytes recoverable by anyone
17
+ * scanning the file directly — so `stripGpsFromJpeg` also zeroes the GPS
18
+ * IFD's own entries and any external data block a `RATIONAL` value pointed
19
+ * at (latitude/longitude do not fit in the 4 inline bytes of a directory
20
+ * entry, so they live elsewhere in the same TIFF blob).
21
+ */
22
+ const JPEG_SOI = 0xffd8;
23
+ const APP1_MARKER = 0xffe1;
24
+ const EXIF_HEADER = 'Exif\0\0';
25
+ const TAG_MAKE = 0x010f;
26
+ const TAG_MODEL = 0x0110;
27
+ const TAG_ORIENTATION = 0x0112;
28
+ const TAG_DATETIME = 0x0132;
29
+ const TAG_EXIF_IFD = 0x8769;
30
+ const TAG_GPS_IFD = 0x8825;
31
+ const TAG_DATETIME_ORIGINAL = 0x9003;
32
+ const TAG_GPS_LAT_REF = 0x0001;
33
+ const TAG_GPS_LAT = 0x0002;
34
+ const TAG_GPS_LON_REF = 0x0003;
35
+ const TAG_GPS_LON = 0x0004;
36
+ /** Bytes one value of this TIFF type occupies. Types this reader never emits (7, 11, 12) are included only so a stray tag does not miscompute an offset. */
37
+ const TYPE_SIZE = {
38
+ 1: 1,
39
+ 2: 1,
40
+ 3: 2,
41
+ 4: 4,
42
+ 5: 8,
43
+ 6: 1,
44
+ 7: 1,
45
+ 8: 2,
46
+ 9: 4,
47
+ 10: 8,
48
+ 11: 4,
49
+ 12: 8,
50
+ };
51
+ /** Finds the first APP1/Exif segment, or null when this is not a JPEG or carries none. */
52
+ function findExifTiff(bytes) {
53
+ if (bytes.length < 4)
54
+ return null;
55
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
56
+ if (view.getUint16(0) !== JPEG_SOI)
57
+ return null;
58
+ let offset = 2;
59
+ while (offset + 4 <= bytes.length) {
60
+ const marker = view.getUint16(offset);
61
+ // Anything outside 0xFFE0–0xFFEF/0xFFFE is past the metadata segments a
62
+ // JPEG puts before its image data — SOS (0xFFDA) above all — so there is
63
+ // nothing further to find.
64
+ if (marker === 0xffda || (marker & 0xff00) !== 0xff00)
65
+ return null;
66
+ const length = view.getUint16(offset + 2);
67
+ if (marker === APP1_MARKER && offset + 4 + 6 <= bytes.length) {
68
+ const header = Buffer.from(bytes.slice(offset + 4, offset + 4 + 6)).toString('latin1');
69
+ if (header === EXIF_HEADER) {
70
+ const tiffStart = offset + 4 + 6;
71
+ if (tiffStart + 8 > bytes.length)
72
+ return null;
73
+ const byteOrder = view.getUint16(tiffStart);
74
+ if (byteOrder !== 0x4949 && byteOrder !== 0x4d4d)
75
+ return null;
76
+ return { view, little: byteOrder === 0x4949, base: tiffStart };
77
+ }
78
+ }
79
+ offset += 2 + length;
80
+ }
81
+ return null;
82
+ }
83
+ function readIfdEntries(tiff, ifdOffset) {
84
+ const { view, little, base } = tiff;
85
+ const absolute = base + ifdOffset;
86
+ if (absolute + 2 > view.byteLength)
87
+ return [];
88
+ const count = view.getUint16(absolute, little);
89
+ const entries = [];
90
+ for (let index = 0; index < count; index += 1) {
91
+ const entryOffset = absolute + 2 + index * 12;
92
+ if (entryOffset + 12 > view.byteLength)
93
+ break;
94
+ entries.push({
95
+ tag: view.getUint16(entryOffset, little),
96
+ type: view.getUint16(entryOffset + 2, little),
97
+ count: view.getUint32(entryOffset + 4, little),
98
+ valueFieldOffset: entryOffset + 8,
99
+ });
100
+ }
101
+ return entries;
102
+ }
103
+ /** The byte offset (relative to the TIFF base) an entry's data actually lives at — inline for ≤4 bytes, elsewhere in the blob otherwise. */
104
+ function dataOffsetOf(tiff, entry) {
105
+ const size = (TYPE_SIZE[entry.type] ?? 1) * entry.count;
106
+ return size > 4
107
+ ? tiff.view.getUint32(entry.valueFieldOffset, tiff.little)
108
+ : entry.valueFieldOffset - tiff.base;
109
+ }
110
+ function readAscii(tiff, entry) {
111
+ if (entry.type !== 2 || entry.count === 0)
112
+ return null;
113
+ const start = tiff.base + dataOffsetOf(tiff, entry);
114
+ if (start + entry.count > tiff.view.byteLength)
115
+ return null;
116
+ const bytes = new Uint8Array(tiff.view.buffer, tiff.view.byteOffset + start, entry.count);
117
+ // EXIF ASCII fields are NUL-terminated; trailing NULs (and anything after
118
+ // a stray embedded one) are not part of the human string.
119
+ const nul = bytes.indexOf(0);
120
+ return Buffer.from(bytes.slice(0, nul === -1 ? bytes.length : nul)).toString('latin1');
121
+ }
122
+ function readShort(tiff, entry) {
123
+ if (entry.type !== 3)
124
+ return null;
125
+ return tiff.view.getUint16(entry.valueFieldOffset, tiff.little);
126
+ }
127
+ /**
128
+ * A single `LONG` value, read directly out of the entry's inline 4-byte
129
+ * field — never through `dataOffsetOf`, which answers "where does this
130
+ * entry's *byte sequence* live" (inline or external) and is right for text
131
+ * and rational arrays. A lone `LONG` always fits in those same 4 bytes, but
132
+ * its *value* is the thing itself (a sub-IFD pointer, for both tags this
133
+ * reader ever looks at one for), not a byte sequence to locate and read.
134
+ */
135
+ function readLong(tiff, entry) {
136
+ if (entry.type !== 4 || entry.count !== 1)
137
+ return null;
138
+ return tiff.view.getUint32(entry.valueFieldOffset, tiff.little);
139
+ }
140
+ function readRational(tiff, offset) {
141
+ const numerator = tiff.view.getUint32(offset, tiff.little);
142
+ const denominator = tiff.view.getUint32(offset + 4, tiff.little);
143
+ return denominator === 0 ? 0 : numerator / denominator;
144
+ }
145
+ /** Degrees/minutes/seconds, as three `RATIONAL`s, to a signed decimal degree. */
146
+ function readGpsCoordinate(tiff, entry, negative) {
147
+ if (entry.type !== 5 || entry.count !== 3)
148
+ return null;
149
+ const start = tiff.base + dataOffsetOf(tiff, entry);
150
+ if (start + 24 > tiff.view.byteLength)
151
+ return null;
152
+ const degrees = readRational(tiff, start);
153
+ const minutes = readRational(tiff, start + 8);
154
+ const seconds = readRational(tiff, start + 16);
155
+ const value = degrees + minutes / 60 + seconds / 3600;
156
+ return negative ? -value : value;
157
+ }
158
+ /** `YYYY:MM:DD HH:MM:SS` (the one timestamp format EXIF ever uses) to ISO 8601, local time — EXIF carries no time zone, so none is invented. */
159
+ function exifDateToIso(value) {
160
+ const match = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/u.exec(value.trim());
161
+ if (match === null)
162
+ return null;
163
+ const [, year, month, day, hour, minute, second] = match;
164
+ return `${year}-${month}-${day}T${hour}:${minute}:${second}`;
165
+ }
166
+ export function readExif(bytes) {
167
+ const tiff = findExifTiff(bytes);
168
+ if (tiff === null)
169
+ return null;
170
+ const ifd0Offset = tiff.view.getUint32(tiff.base + 4, tiff.little);
171
+ const ifd0 = readIfdEntries(tiff, ifd0Offset);
172
+ let make = null;
173
+ let model = null;
174
+ let orientation = null;
175
+ let dateTime = null;
176
+ let exifIfdOffset = null;
177
+ let gpsIfdOffset = null;
178
+ for (const entry of ifd0) {
179
+ if (entry.tag === TAG_MAKE)
180
+ make = readAscii(tiff, entry);
181
+ else if (entry.tag === TAG_MODEL)
182
+ model = readAscii(tiff, entry);
183
+ else if (entry.tag === TAG_ORIENTATION)
184
+ orientation = readShort(tiff, entry);
185
+ else if (entry.tag === TAG_DATETIME)
186
+ dateTime = readAscii(tiff, entry);
187
+ else if (entry.tag === TAG_EXIF_IFD)
188
+ exifIfdOffset = readLong(tiff, entry);
189
+ else if (entry.tag === TAG_GPS_IFD)
190
+ gpsIfdOffset = readLong(tiff, entry);
191
+ }
192
+ let takenAt = null;
193
+ if (exifIfdOffset !== null) {
194
+ for (const entry of readIfdEntries(tiff, exifIfdOffset)) {
195
+ if (entry.tag === TAG_DATETIME_ORIGINAL) {
196
+ const raw = readAscii(tiff, entry);
197
+ if (raw !== null)
198
+ takenAt = exifDateToIso(raw);
199
+ }
200
+ }
201
+ }
202
+ if (takenAt === null && dateTime !== null)
203
+ takenAt = exifDateToIso(dateTime);
204
+ let gps = null;
205
+ if (gpsIfdOffset !== null) {
206
+ const gpsEntries = readIfdEntries(tiff, gpsIfdOffset);
207
+ let latRef = null;
208
+ let lonRef = null;
209
+ let latEntry = null;
210
+ let lonEntry = null;
211
+ for (const entry of gpsEntries) {
212
+ if (entry.tag === TAG_GPS_LAT_REF)
213
+ latRef = readAscii(tiff, entry);
214
+ else if (entry.tag === TAG_GPS_LON_REF)
215
+ lonRef = readAscii(tiff, entry);
216
+ else if (entry.tag === TAG_GPS_LAT)
217
+ latEntry = entry;
218
+ else if (entry.tag === TAG_GPS_LON)
219
+ lonEntry = entry;
220
+ }
221
+ if (latEntry !== null && lonEntry !== null) {
222
+ const latitude = readGpsCoordinate(tiff, latEntry, latRef === 'S');
223
+ const longitude = readGpsCoordinate(tiff, lonEntry, lonRef === 'W');
224
+ if (latitude !== null && longitude !== null)
225
+ gps = { latitude, longitude };
226
+ }
227
+ }
228
+ if (make === null && model === null && takenAt === null && orientation === null && gps === null) {
229
+ return null;
230
+ }
231
+ return { make, model, takenAt, orientation, gps };
232
+ }
233
+ /**
234
+ * Removes GPS coordinates from a JPEG's EXIF block, in place semantics
235
+ * aside: returns a new buffer, the original is never mutated.
236
+ *
237
+ * Every other EXIF tag (camera, orientation, capture date) is left exactly
238
+ * as it was — this is a location scrub, not a metadata wipe.
239
+ */
240
+ export function stripGpsFromJpeg(bytes) {
241
+ const tiff = findExifTiff(bytes);
242
+ if (tiff === null)
243
+ return bytes;
244
+ const ifd0Offset = tiff.view.getUint32(tiff.base + 4, tiff.little);
245
+ const ifd0Absolute = tiff.base + ifd0Offset;
246
+ const entryCount = tiff.view.getUint16(ifd0Absolute, tiff.little);
247
+ let gpsEntryAbsolute = null;
248
+ let gpsIfdOffset = null;
249
+ for (let index = 0; index < entryCount; index += 1) {
250
+ const entryOffset = ifd0Absolute + 2 + index * 12;
251
+ if (tiff.view.getUint16(entryOffset, tiff.little) === TAG_GPS_IFD) {
252
+ gpsEntryAbsolute = entryOffset;
253
+ gpsIfdOffset = tiff.view.getUint32(entryOffset + 8, tiff.little);
254
+ break;
255
+ }
256
+ }
257
+ if (gpsEntryAbsolute === null || gpsIfdOffset === null)
258
+ return bytes;
259
+ const out = Buffer.from(bytes);
260
+ const zero = (start, length) => {
261
+ out.fill(0, start, Math.min(start + length, out.length));
262
+ };
263
+ // Unlink the pointer: tag id 0x0000 is not a defined EXIF/TIFF tag, so a
264
+ // compliant reader treats the entry as unrecognised and never follows its
265
+ // (now meaningless) offset into the GPS IFD. Zero is the same two bytes in
266
+ // either byte order, so no endianness branch is needed here.
267
+ zero(gpsEntryAbsolute, 2);
268
+ // Zero the GPS IFD's own directory (count + entries + "next IFD" pointer)
269
+ // and every external data block a `RATIONAL` (or otherwise >4-byte) GPS
270
+ // entry pointed at — the coordinates themselves live there, not inline.
271
+ const gpsIfdAbsolute = tiff.base + gpsIfdOffset;
272
+ const gpsCount = tiff.view.getUint16(gpsIfdAbsolute, tiff.little);
273
+ for (let index = 0; index < gpsCount; index += 1) {
274
+ const entryOffset = gpsIfdAbsolute + 2 + index * 12;
275
+ const type = tiff.view.getUint16(entryOffset + 2, tiff.little);
276
+ const count = tiff.view.getUint32(entryOffset + 4, tiff.little);
277
+ const size = (TYPE_SIZE[type] ?? 1) * count;
278
+ if (size > 4) {
279
+ const dataOffset = tiff.base + tiff.view.getUint32(entryOffset + 8, tiff.little);
280
+ zero(dataOffset, size);
281
+ }
282
+ }
283
+ zero(gpsIfdAbsolute, 2 + gpsCount * 12 + 4);
284
+ return out;
285
+ }
286
+ /** Whether a JPEG's EXIF block carries GPS coordinates — the yes/no a privacy notice needs, without the caller having to unpack the whole `ExifData` shape. */
287
+ export function hasGpsData(bytes) {
288
+ const exif = readExif(bytes);
289
+ return exif !== null && exif.gps !== null;
290
+ }
291
+ //# sourceMappingURL=exif.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exif.js","sourceRoot":"","sources":["../../src/media/exif.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /** Deep enough for any folder tree a human maintains; matches the taxonomy tree's own bound. */
2
+ export declare const MAX_MEDIA_FOLDER_DEPTH = 12;
3
+ /** `varchar(n)` of the path column: one leading slash plus the segments. */
4
+ export declare const MEDIA_FOLDER_PATH_LENGTH: number;
5
+ /** The path of a folder, given its parent's path (`''` at the root). */
6
+ export declare function childFolderPath(parentPath: string, id: string): string;
7
+ /** 0 at the root. */
8
+ export declare function folderDepthOf(path: string): number;
9
+ /** True when `path` is the folder itself or anything beneath it. */
10
+ export declare function isWithinFolder(path: string, ancestorPath: string): boolean;
11
+ /** True when `path` is strictly beneath `ancestorPath`. */
12
+ export declare function isBelowFolder(path: string, ancestorPath: string): boolean;
13
+ /** The path a moved subtree gets once it hangs under a new parent. */
14
+ export declare function rebasedFolderPath(path: string, fromPrefix: string, toPrefix: string): string;
15
+ export declare function assertMediaFolderDepth(path: string): void;
16
+ //# sourceMappingURL=folder-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-path.d.ts","sourceRoot":"","sources":["../../src/media/folder-path.ts"],"names":[],"mappings":"AAkBA,gGAAgG;AAChG,eAAO,MAAM,sBAAsB,KAAK,CAAA;AAExC,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB,QAA8C,CAAA;AAEnF,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,qBAAqB;AACrB,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAE1E;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5F;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAUzD"}
@@ -0,0 +1,51 @@
1
+ import { CogentaError } from '../errors/index.js';
2
+ /**
3
+ * The materialised path of a media folder tree (fiche 46).
4
+ *
5
+ * Deliberately the same shape as `@cogenta/schema`'s `store/taxonomy-path.ts`
6
+ * (ADR-0022) — a subtree is one `like`, a rename rewrites nothing, only a
7
+ * move pays. It is a *separate, local* copy rather than an import: this
8
+ * package is `@cogenta/core`, the foundation every other package (including
9
+ * `@cogenta/schema`) depends on, and a media folder is core's own concept (a
10
+ * `MediaAsset` already lives here). Importing from `@cogenta/schema` here
11
+ * would invert that dependency direction. The arithmetic is ~15 lines of
12
+ * pure string manipulation — duplicating it is cheaper and safer than
13
+ * bending the package graph for it.
14
+ */
15
+ const SEGMENT_LENGTH = 37;
16
+ /** Deep enough for any folder tree a human maintains; matches the taxonomy tree's own bound. */
17
+ export const MAX_MEDIA_FOLDER_DEPTH = 12;
18
+ /** `varchar(n)` of the path column: one leading slash plus the segments. */
19
+ export const MEDIA_FOLDER_PATH_LENGTH = SEGMENT_LENGTH * MAX_MEDIA_FOLDER_DEPTH + 1;
20
+ /** The path of a folder, given its parent's path (`''` at the root). */
21
+ export function childFolderPath(parentPath, id) {
22
+ return parentPath === '' ? `/${id}/` : `${parentPath}${id}/`;
23
+ }
24
+ /** 0 at the root. */
25
+ export function folderDepthOf(path) {
26
+ return path.split('/').filter((segment) => segment !== '').length - 1;
27
+ }
28
+ /** True when `path` is the folder itself or anything beneath it. */
29
+ export function isWithinFolder(path, ancestorPath) {
30
+ return path.startsWith(ancestorPath);
31
+ }
32
+ /** True when `path` is strictly beneath `ancestorPath`. */
33
+ export function isBelowFolder(path, ancestorPath) {
34
+ return path !== ancestorPath && path.startsWith(ancestorPath);
35
+ }
36
+ /** The path a moved subtree gets once it hangs under a new parent. */
37
+ export function rebasedFolderPath(path, fromPrefix, toPrefix) {
38
+ return `${toPrefix}${path.slice(fromPrefix.length)}`;
39
+ }
40
+ export function assertMediaFolderDepth(path) {
41
+ const depth = folderDepthOf(path);
42
+ if (depth < MAX_MEDIA_FOLDER_DEPTH)
43
+ return;
44
+ throw new CogentaError({
45
+ code: 'MEDIA_FOLDER_TOO_DEEP',
46
+ message: `A media folder cannot be nested more than ${MAX_MEDIA_FOLDER_DEPTH} levels deep.`,
47
+ hint: 'The tree is stored as a materialised path, whose column is bounded. Flatten this branch.',
48
+ details: { depth, max: MAX_MEDIA_FOLDER_DEPTH },
49
+ });
50
+ }
51
+ //# sourceMappingURL=folder-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-path.js","sourceRoot":"","sources":["../../src/media/folder-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD;;;;;;;;;;;;GAYG;AAEH,MAAM,cAAc,GAAG,EAAE,CAAA;AAEzB,gGAAgG;AAChG,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAA;AAExC,4EAA4E;AAC5E,MAAM,CAAC,MAAM,wBAAwB,GAAG,cAAc,GAAG,sBAAsB,GAAG,CAAC,CAAA;AAEnF,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,UAAkB,EAAE,EAAU;IAC5D,OAAO,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,EAAE,GAAG,CAAA;AAC9D,CAAC;AAED,qBAAqB;AACrB,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;AACvE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,YAAoB;IAC/D,OAAO,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;AACtC,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,YAAoB;IAC9D,OAAO,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;AAC/D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,UAAkB,EAAE,QAAgB;IAClF,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAA;AACtD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,KAAK,GAAG,sBAAsB;QAAE,OAAM;IAE1C,MAAM,IAAI,YAAY,CAAC;QACrB,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,6CAA6C,sBAAsB,eAAe;QAC3F,IAAI,EAAE,0FAA0F;QAChG,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,sBAAsB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type DatabaseHandle } from '../db/index.js';
2
+ import { folderDepthOf } from './folder-path.js';
3
+ import type { MediaFolderStore } from './types.js';
4
+ export declare const MEDIA_FOLDER_TABLE = "cogenta_media_folders";
5
+ export interface DatabaseMediaFolderStoreOptions {
6
+ readonly db: DatabaseHandle;
7
+ /** The media table checked for "does this folder still hold assets" on delete. Defaults to `MEDIA_TABLE`. */
8
+ readonly mediaTable?: string;
9
+ /** Injectable so tests can pin time; nothing else should pass it. */
10
+ readonly now?: () => Date;
11
+ readonly newId?: () => string;
12
+ }
13
+ /**
14
+ * The persistence layer of the media library's folder tree (fiche 46).
15
+ *
16
+ * Same materialised-path shape as `@cogenta/schema`'s `TaxonomyStore`
17
+ * (ADR-0022): reads are one `like`, writes pay for a move. A folder name is
18
+ * unique among its own siblings (case/whitespace-insensitive) rather than
19
+ * globally — the same rule a real filesystem enforces, and one the tree
20
+ * shape itself (a path is ids, never names) never actually required, so
21
+ * enforcing it is a deliberate UX choice for this fiche, not something the
22
+ * storage model demanded.
23
+ */
24
+ export declare function createDatabaseMediaFolderStore(options: DatabaseMediaFolderStoreOptions): MediaFolderStore;
25
+ export { folderDepthOf as mediaFolderDepthOf };
26
+ //# sourceMappingURL=folder-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folder-store.d.ts","sourceRoot":"","sources":["../../src/media/folder-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,cAAc,EAAgD,MAAM,gBAAgB,CAAA;AAElG,OAAO,EAGL,aAAa,EAId,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAIV,gBAAgB,EAEjB,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;AAEzD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;IAC3B,6GAA6G;IAC7G,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;CAC9B;AAoFD;;;;;;;;;;GAUG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,gBAAgB,CA8UlB;AAID,OAAO,EAAE,aAAa,IAAI,kBAAkB,EAAE,CAAA"}