@byline/core 4.1.0 → 4.2.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 (36) hide show
  1. package/dist/@types/admin-types.d.ts +74 -2
  2. package/dist/@types/admin-types.js +29 -0
  3. package/dist/@types/collection-types.d.ts +29 -7
  4. package/dist/@types/db-types.d.ts +7 -7
  5. package/dist/@types/field-data-types.d.ts +1 -0
  6. package/dist/@types/field-types.d.ts +36 -1
  7. package/dist/@types/site-config.d.ts +28 -1
  8. package/dist/@types/storage-types.d.ts +14 -1
  9. package/dist/codegen/fixtures/all-fields.d.ts +23 -0
  10. package/dist/codegen/fixtures/all-fields.expected.d.ts +4 -0
  11. package/dist/codegen/fixtures/all-fields.js +2 -0
  12. package/dist/codegen/index.js +2 -0
  13. package/dist/config/config.js +2 -1
  14. package/dist/config/validate-admin-configs.d.ts +17 -1
  15. package/dist/config/validate-admin-configs.js +84 -11
  16. package/dist/config/validate-admin-configs.test.node.js +114 -1
  17. package/dist/config/validate-collections.js +43 -0
  18. package/dist/config/validate-collections.test.node.js +60 -0
  19. package/dist/index.d.ts +2 -1
  20. package/dist/index.js +2 -1
  21. package/dist/schemas/zod/builder.js +11 -0
  22. package/dist/services/build-search-document.js +5 -1
  23. package/dist/services/document-to-markdown.js +10 -0
  24. package/dist/services/field-upload.d.ts +7 -0
  25. package/dist/services/field-upload.js +15 -13
  26. package/dist/services/field-upload.test.node.js +1 -1
  27. package/dist/services/populate.d.ts +1 -1
  28. package/dist/services/populate.js +1 -1
  29. package/dist/storage/collection-fingerprint.js +9 -0
  30. package/dist/storage/field-store-map.js +1 -0
  31. package/dist/storage/field-store-map.test.node.js +1 -0
  32. package/dist/utils/slugify-filename.d.ts +50 -0
  33. package/dist/utils/slugify-filename.js +36 -0
  34. package/dist/utils/slugify-filename.test.node.d.ts +8 -0
  35. package/dist/utils/slugify-filename.test.node.js +50 -0
  36. package/package.json +2 -2
@@ -0,0 +1,50 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ /**
9
+ * Context passed to a `FilenameSlugifierFn`.
10
+ *
11
+ * `collectionPath` - The collection receiving the upload. Lets an
12
+ * installation-supplied slugifier branch by collection
13
+ * if filename policies differ.
14
+ * `fieldName` - The upload field's name within the collection.
15
+ * `mimeType` - The uploaded file's MIME type.
16
+ */
17
+ export type FilenameSlugifyContext = {
18
+ collectionPath: string;
19
+ fieldName: string;
20
+ mimeType: string;
21
+ };
22
+ /**
23
+ * A pure function that converts an uploaded file's **base name** (extension
24
+ * already split off) into a storage-safe slug. The extension is handled by
25
+ * the framework — lowercased and reattached after slugification — so
26
+ * implementations only see and return the base name.
27
+ *
28
+ * Registered installation-wide via `ServerConfig.uploads.filenameSlugifier`;
29
+ * the parallel of `ServerConfig.slugifier` for document paths. Must be
30
+ * synchronous and side-effect free. Return value is sanitised again by the
31
+ * storage provider as a safety net, but implementations should stay within
32
+ * `A–Z a–z 0–9 . _ -` to keep the slug authoritative.
33
+ */
34
+ export type FilenameSlugifierFn = (basename: string, ctx: FilenameSlugifyContext) => string;
35
+ /**
36
+ * Default filename slugifier — lowercases, replaces unsafe characters with
37
+ * hyphens, collapses runs, trims leading/trailing hyphens, and falls back
38
+ * to `'file'` for empty results. Mirrors the storage providers' own
39
+ * sanitisation rules so the default round-trips them unchanged.
40
+ */
41
+ export declare const slugifyFilename: FilenameSlugifierFn;
42
+ /**
43
+ * Resolve an uploaded file's stored filename: split the extension, apply the
44
+ * installation slugifier (or the default) to the base name, and reattach the
45
+ * lowercased extension. This runs in `uploadField` **before** the
46
+ * `beforeStore` hook chain, so hooks observe (and may override) the
47
+ * already-slugified name — the same ordering the previous fixed sanitiser
48
+ * had.
49
+ */
50
+ export declare function resolveUploadFilename(originalFilename: string, slugifier: FilenameSlugifierFn | undefined, ctx: FilenameSlugifyContext): string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ /**
9
+ * Default filename slugifier — lowercases, replaces unsafe characters with
10
+ * hyphens, collapses runs, trims leading/trailing hyphens, and falls back
11
+ * to `'file'` for empty results. Mirrors the storage providers' own
12
+ * sanitisation rules so the default round-trips them unchanged.
13
+ */
14
+ export const slugifyFilename = (basename) => {
15
+ const safe = basename
16
+ .toLowerCase()
17
+ .replace(/[^a-z0-9._-]/g, '-')
18
+ .replace(/-+/g, '-')
19
+ .replace(/^-|-$/g, '');
20
+ return safe || 'file';
21
+ };
22
+ /**
23
+ * Resolve an uploaded file's stored filename: split the extension, apply the
24
+ * installation slugifier (or the default) to the base name, and reattach the
25
+ * lowercased extension. This runs in `uploadField` **before** the
26
+ * `beforeStore` hook chain, so hooks observe (and may override) the
27
+ * already-slugified name — the same ordering the previous fixed sanitiser
28
+ * had.
29
+ */
30
+ export function resolveUploadFilename(originalFilename, slugifier, ctx) {
31
+ const lastDot = originalFilename.lastIndexOf('.');
32
+ const ext = lastDot > 0 ? originalFilename.slice(lastDot).toLowerCase() : '';
33
+ const base = lastDot > 0 ? originalFilename.slice(0, lastDot) : originalFilename;
34
+ const slug = (slugifier ?? slugifyFilename)(base, ctx);
35
+ return `${slug || 'file'}${ext}`;
36
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export {};
@@ -0,0 +1,50 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import { describe, expect, it } from 'vitest';
9
+ import { resolveUploadFilename, slugifyFilename } from './slugify-filename.js';
10
+ const ctx = { collectionPath: 'events', fieldName: 'attachment', mimeType: 'application/pdf' };
11
+ describe('slugifyFilename (default)', () => {
12
+ it('lowercases and hyphenates unsafe characters', () => {
13
+ expect(slugifyFilename('Meeting Agenda (Final)', ctx)).toBe('meeting-agenda-final');
14
+ });
15
+ it('collapses hyphen runs and trims leading/trailing hyphens', () => {
16
+ expect(slugifyFilename('--Board -- Notes--', ctx)).toBe('board-notes');
17
+ });
18
+ it('falls back to "file" for empty results', () => {
19
+ expect(slugifyFilename('***', ctx)).toBe('file');
20
+ });
21
+ });
22
+ describe('resolveUploadFilename', () => {
23
+ it('slugifies the base name and lowercases the extension', () => {
24
+ expect(resolveUploadFilename('Meeting Agenda.PDF', undefined, ctx)).toBe('meeting-agenda.pdf');
25
+ });
26
+ it('handles filenames without an extension', () => {
27
+ expect(resolveUploadFilename('README', undefined, ctx)).toBe('readme');
28
+ });
29
+ it('treats a leading dot as part of the base name, not an extension', () => {
30
+ expect(resolveUploadFilename('.gitignore', undefined, ctx)).toBe('.gitignore');
31
+ });
32
+ it('only splits at the last dot', () => {
33
+ expect(resolveUploadFilename('archive.tar.gz', undefined, ctx)).toBe('archive.tar.gz');
34
+ });
35
+ it('applies a custom slugifier to the base name only', () => {
36
+ const shouty = (base) => base.toUpperCase().replace(/[^A-Z0-9]/g, '_');
37
+ expect(resolveUploadFilename('Meeting Agenda.pdf', shouty, ctx)).toBe('MEETING_AGENDA.pdf');
38
+ });
39
+ it('passes the slugify context through to the custom slugifier', () => {
40
+ const seen = [];
41
+ resolveUploadFilename('photo.jpg', (base, c) => {
42
+ seen.push(c);
43
+ return base;
44
+ }, ctx);
45
+ expect(seen).toEqual([ctx]);
46
+ });
47
+ it('guards against a custom slugifier returning an empty string', () => {
48
+ expect(resolveUploadFilename('photo.jpg', () => '', ctx)).toBe('file.jpg');
49
+ });
50
+ });
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/core",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "4.1.0",
5
+ "version": "4.2.0",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -81,7 +81,7 @@
81
81
  "pino": "^10.3.1",
82
82
  "sharp": "^0.35.3",
83
83
  "zod": "^4.4.3",
84
- "@byline/auth": "4.1.0"
84
+ "@byline/auth": "4.2.0"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@biomejs/biome": "2.5.2",