@byline/core 3.17.1 → 3.19.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/dist/@types/collection-types.d.ts +91 -6
- package/dist/@types/db-types.d.ts +26 -0
- package/dist/@types/site-config.d.ts +23 -0
- package/dist/@types/storage-types.d.ts +21 -0
- package/dist/auth/register-collection-abilities.d.ts +1 -1
- package/dist/config/config.js +4 -0
- package/dist/config/validate-collections.d.ts +2 -1
- package/dist/config/validate-collections.js +10 -1
- package/dist/config/validate-collections.test.node.js +22 -0
- package/dist/lib/errors.d.ts +11 -11
- package/dist/patches/apply-patches.js +1 -1
- package/dist/schemas/zod/builder.d.ts +20 -264
- package/dist/schemas/zod/cache.d.ts +2 -2
- package/dist/services/assign-counter-values.test.node.js +1 -0
- package/dist/services/collection-bootstrap.test.node.js +2 -0
- package/dist/services/discover-counter-groups.test.node.js +1 -0
- package/dist/services/document-lifecycle/audit.d.ts +4 -4
- package/dist/services/document-lifecycle.test.node.js +1 -0
- package/dist/services/field-upload.js +40 -8
- package/dist/services/field-upload.test.node.js +79 -0
- package/dist/services/populate.test.node.js +1 -0
- package/dist/validation/shared.d.ts +3 -3
- package/package.json +9 -9
|
@@ -132,6 +132,52 @@ export interface UploadConfig {
|
|
|
132
132
|
* @see UploadHooks
|
|
133
133
|
*/
|
|
134
134
|
hooks?: UploadHooks | UploadHooksLoader;
|
|
135
|
+
/**
|
|
136
|
+
* Form-value paths the admin upload executor resolves at submit time and
|
|
137
|
+
* posts alongside the file, so server-side `beforeStore` / `afterStore`
|
|
138
|
+
* hooks receive document context in `ctx.fields` (always strings —
|
|
139
|
+
* multipart form values arrive untyped).
|
|
140
|
+
*
|
|
141
|
+
* Paths use filesystem-style resolution relative to the upload field's
|
|
142
|
+
* containing scope. For a field at `files[2].filesGroup.publicationFile`:
|
|
143
|
+
*
|
|
144
|
+
* - `'language'` (bare / `./`) → sibling: `files[2].filesGroup.language`
|
|
145
|
+
* - `'../label'` → one level up: `files[2].label`
|
|
146
|
+
* - `'/serialNumber'` → document root: `serialNumber`
|
|
147
|
+
*
|
|
148
|
+
* Each resolved value is posted under the path's **leaf name** (`language`,
|
|
149
|
+
* `serialNumber`); when two context paths share a leaf name the later
|
|
150
|
+
* declaration wins. Serialisation: strings pass through; numbers/booleans
|
|
151
|
+
* are stringified; relation envelopes post their `targetDocumentId`
|
|
152
|
+
* (hasMany: comma-joined); `null`/`undefined` values are omitted; anything
|
|
153
|
+
* else is JSON-stringified.
|
|
154
|
+
*
|
|
155
|
+
* Independent of `context`, the executor always posts `documentId` (edit
|
|
156
|
+
* mode only — absent while the document is unsaved) and `fieldPath` (the
|
|
157
|
+
* full form path of the upload field, e.g.
|
|
158
|
+
* `files[2].filesGroup.publicationFile`).
|
|
159
|
+
*
|
|
160
|
+
* Hooks must treat all of these as **client-supplied claims** and re-verify
|
|
161
|
+
* anything security- or integrity-relevant server-side (e.g. fetch the
|
|
162
|
+
* document by `documentId` rather than trusting a posted serial).
|
|
163
|
+
*/
|
|
164
|
+
context?: string[];
|
|
165
|
+
/**
|
|
166
|
+
* When `true`, the admin renders a "save this document first" notice in
|
|
167
|
+
* place of the upload widget until the document has been persisted (i.e.
|
|
168
|
+
* the form is in edit mode with a document id). Existing stored values
|
|
169
|
+
* still render normally — only the *upload* affordance is gated.
|
|
170
|
+
*
|
|
171
|
+
* Use this when server-side upload hooks depend on state that exists only
|
|
172
|
+
* after the first save — allocator-assigned `counter` fields, the document
|
|
173
|
+
* id itself — e.g. a `beforeStore` hook that derives the storage key from
|
|
174
|
+
* the document's serial number.
|
|
175
|
+
*
|
|
176
|
+
* Admin-side UX only: API callers can still upload without a document.
|
|
177
|
+
* Enforce the invariant server-side in a `beforeStore` hook (reject with
|
|
178
|
+
* `{ error }` when the required context is missing).
|
|
179
|
+
*/
|
|
180
|
+
requireSavedDocument?: boolean;
|
|
135
181
|
}
|
|
136
182
|
/**
|
|
137
183
|
* The three status names that every workflow must contain.
|
|
@@ -141,9 +187,9 @@ export interface UploadConfig {
|
|
|
141
187
|
*
|
|
142
188
|
* `[draft, ...customStatuses, published, archived]`
|
|
143
189
|
*/
|
|
144
|
-
export declare const WORKFLOW_STATUS_DRAFT:
|
|
145
|
-
export declare const WORKFLOW_STATUS_PUBLISHED:
|
|
146
|
-
export declare const WORKFLOW_STATUS_ARCHIVED:
|
|
190
|
+
export declare const WORKFLOW_STATUS_DRAFT: 'draft';
|
|
191
|
+
export declare const WORKFLOW_STATUS_PUBLISHED: 'published';
|
|
192
|
+
export declare const WORKFLOW_STATUS_ARCHIVED: 'archived';
|
|
147
193
|
export declare const REQUIRED_WORKFLOW_STATUSES: readonly ["draft", "published", "archived"];
|
|
148
194
|
export type RequiredWorkflowStatusName = (typeof REQUIRED_WORKFLOW_STATUSES)[number];
|
|
149
195
|
/**
|
|
@@ -520,7 +566,19 @@ export interface TreeChangeContext {
|
|
|
520
566
|
*
|
|
521
567
|
* - Rename the file by returning a string or `{ filename }`. The
|
|
522
568
|
* override is threaded into `storage.upload(...)`, so generated
|
|
523
|
-
* image variants automatically inherit the new prefix.
|
|
569
|
+
* image variants automatically inherit the new prefix. The storage
|
|
570
|
+
* provider still derives the final key (e.g. local storage prefixes
|
|
571
|
+
* `<collection>/<uuid>-` for collision avoidance).
|
|
572
|
+
* - Take **full control of the storage key** by returning
|
|
573
|
+
* `{ storagePath }` — a fully-qualified, POSIX-style path (no
|
|
574
|
+
* leading slash) that is threaded into `storage.upload(...)` as
|
|
575
|
+
* `targetStoragePath` and written **verbatim**: no UUID prefix, no
|
|
576
|
+
* collection namespace, no provider rewriting. The hook assumes
|
|
577
|
+
* responsibility for sanitisation and collision avoidance (see
|
|
578
|
+
* `UploadFileOptions.targetStoragePath`). Generated image variants
|
|
579
|
+
* derive their sibling paths from the custom original. When
|
|
580
|
+
* `storagePath` is returned without `filename`, the stored
|
|
581
|
+
* `filename` defaults to the path's basename.
|
|
524
582
|
* - Reject the upload by returning `{ error }`. Surfaces as
|
|
525
583
|
* `ERR_VALIDATION` with the supplied message; no file is written,
|
|
526
584
|
* no variants are generated, no document is created, no later
|
|
@@ -529,7 +587,8 @@ export interface TreeChangeContext {
|
|
|
529
587
|
*
|
|
530
588
|
* When configured as an array, hooks fold: each function receives the
|
|
531
589
|
* filename returned by the previous function (or the original sanitised
|
|
532
|
-
* filename if the previous returned `void`).
|
|
590
|
+
* filename if the previous returned `void`), and `ctx.storagePath`
|
|
591
|
+
* carries the most recent explicit storage-path override (if any).
|
|
533
592
|
*/
|
|
534
593
|
export interface BeforeStoreContext {
|
|
535
594
|
/** Name of the image/file field receiving this upload. */
|
|
@@ -538,6 +597,12 @@ export interface BeforeStoreContext {
|
|
|
538
597
|
field: ImageField | FileField;
|
|
539
598
|
/** Sanitised default filename. Hooks may override. */
|
|
540
599
|
filename: string;
|
|
600
|
+
/**
|
|
601
|
+
* Explicit storage-path override set by an earlier hook in the chain
|
|
602
|
+
* (via `{ storagePath }`), if any. `undefined` means the storage
|
|
603
|
+
* provider will derive the key itself from `filename` + `collection`.
|
|
604
|
+
*/
|
|
605
|
+
storagePath?: string;
|
|
541
606
|
mimeType: string;
|
|
542
607
|
fileSize: number;
|
|
543
608
|
/**
|
|
@@ -550,22 +615,37 @@ export interface BeforeStoreContext {
|
|
|
550
615
|
collectionPath: string;
|
|
551
616
|
/** Authenticated request context. `actor.id`, `actor.tenantId`, etc. for prefixing. */
|
|
552
617
|
requestContext: RequestContext;
|
|
618
|
+
/**
|
|
619
|
+
* The storage provider this upload will be written to (the field's
|
|
620
|
+
* `upload.storage` or the site-wide default). Lets hooks feature-detect
|
|
621
|
+
* and use the optional provider capabilities — e.g. `storage.exists?.(key)`
|
|
622
|
+
* to collision-check an explicit `{ storagePath }` before claiming it.
|
|
623
|
+
*/
|
|
624
|
+
storage: IStorageProvider;
|
|
553
625
|
}
|
|
554
626
|
/**
|
|
555
627
|
* Result returned by a `beforeStore` hook.
|
|
556
628
|
*
|
|
557
629
|
* - `string` → override filename (shorthand).
|
|
558
|
-
* - `{ filename }` → override filename (object form).
|
|
630
|
+
* - `{ filename }` → override filename (object form). The storage
|
|
631
|
+
* provider still derives the final key.
|
|
632
|
+
* - `{ storagePath }` → take full control of the storage key: written
|
|
633
|
+
* verbatim via `UploadFileOptions.targetStoragePath`
|
|
634
|
+
* (no UUID prefix / provider rewriting). May be
|
|
635
|
+
* combined with `filename`; without it, the stored
|
|
636
|
+
* filename defaults to the path's basename.
|
|
559
637
|
* - `{ error }` → reject the upload; surfaces as
|
|
560
638
|
* `ERR_VALIDATION`. Short-circuits the chain.
|
|
561
639
|
* - `void` / undefined → keep current defaults.
|
|
562
640
|
*/
|
|
563
641
|
export type BeforeStoreResult = string | {
|
|
564
642
|
filename?: string;
|
|
643
|
+
storagePath?: string;
|
|
565
644
|
error?: undefined;
|
|
566
645
|
} | {
|
|
567
646
|
error: string;
|
|
568
647
|
filename?: undefined;
|
|
648
|
+
storagePath?: undefined;
|
|
569
649
|
} | void;
|
|
570
650
|
/**
|
|
571
651
|
* A `beforeStore` hook function. Async-capable.
|
|
@@ -595,6 +675,11 @@ export interface AfterStoreContext {
|
|
|
595
675
|
fields: Record<string, string>;
|
|
596
676
|
collectionPath: string;
|
|
597
677
|
requestContext: RequestContext;
|
|
678
|
+
/**
|
|
679
|
+
* The storage provider the file was written to. See
|
|
680
|
+
* {@link BeforeStoreContext.storage}.
|
|
681
|
+
*/
|
|
682
|
+
storage: IStorageProvider;
|
|
598
683
|
}
|
|
599
684
|
/** An `afterStore` hook function. Async-capable. */
|
|
600
685
|
export type AfterStoreHookFn = (ctx: AfterStoreContext) => void | Promise<void>;
|
|
@@ -424,6 +424,32 @@ export interface ICounterCommands {
|
|
|
424
424
|
* and deletes. The facet-URL use case does not require gapless IDs.
|
|
425
425
|
*/
|
|
426
426
|
nextCounterValue(groupName: string): Promise<number>;
|
|
427
|
+
/**
|
|
428
|
+
* Atomically allocate the next value from a **runtime-scoped** counter
|
|
429
|
+
* group, self-registering the group on first use.
|
|
430
|
+
*
|
|
431
|
+
* The schema-declared counter mechanism above is deliberately static:
|
|
432
|
+
* groups come from `CounterField.group` strings, are discovered and
|
|
433
|
+
* registered once at boot, and `nextCounterValue` throws for anything
|
|
434
|
+
* unregistered — that honesty catches configuration bugs. But some
|
|
435
|
+
* sequences are keyed on *data* that only exists at runtime — e.g. a
|
|
436
|
+
* per-document sub-sequence like "files attached to publication X",
|
|
437
|
+
* where each value must be unique and never reused within that scope
|
|
438
|
+
* (`publications:<documentId>:files` → 1, 2, 3, …). Those scopes cannot
|
|
439
|
+
* be known at boot, so this method performs an idempotent
|
|
440
|
+
* ensure-then-allocate instead of throwing on the first use.
|
|
441
|
+
*
|
|
442
|
+
* Semantics are otherwise identical to `nextCounterValue`: values are
|
|
443
|
+
* monotonic per scope and never reused (a Postgres SEQUENCE per scope
|
|
444
|
+
* in the Postgres adapter); gaps are expected on rolled-back or
|
|
445
|
+
* abandoned work.
|
|
446
|
+
*
|
|
447
|
+
* Callers own the scope-name namespace. Use a stable, collision-proof
|
|
448
|
+
* convention — `'<collectionPath>:<documentId>:<purpose>'` is
|
|
449
|
+
* recommended — and never derive scope names from mutable data
|
|
450
|
+
* (renaming a scope starts a fresh sequence at 1).
|
|
451
|
+
*/
|
|
452
|
+
nextScopedCounterValue(scopeName: string): Promise<number>;
|
|
427
453
|
}
|
|
428
454
|
export interface ICollectionCommands {
|
|
429
455
|
/**
|
|
@@ -144,6 +144,24 @@ export type TranslationBundleShape = Readonly<{
|
|
|
144
144
|
export interface ClientConfig extends BaseConfig {
|
|
145
145
|
/** Admin UI configuration for collections (client-side only). */
|
|
146
146
|
admin?: CollectionAdminConfig[];
|
|
147
|
+
/**
|
|
148
|
+
* Installation-wide slugifier — the **client-side** copy, used by the
|
|
149
|
+
* admin path-widget to render the live `path` preview (create-mode
|
|
150
|
+
* placeholder and the "Regenerate" target) as the editor types.
|
|
151
|
+
*
|
|
152
|
+
* Must be the *same* pure, synchronous function registered on
|
|
153
|
+
* `ServerConfig.slugifier`: the server derivation is authoritative, and a
|
|
154
|
+
* divergent client copy would show a preview that disagrees with what is
|
|
155
|
+
* persisted (and could let "Regenerate" overwrite a correct path). Falls
|
|
156
|
+
* back to the default `slugify` from `@byline/core` when not set — so
|
|
157
|
+
* installations that keep the default slugifier need not set this at all.
|
|
158
|
+
*
|
|
159
|
+
* Lives on `ClientConfig` rather than `BaseConfig` because it is a function
|
|
160
|
+
* (not serialisable), and `BaseConfig` is contractually serialisable.
|
|
161
|
+
*
|
|
162
|
+
* @see ServerConfig.slugifier
|
|
163
|
+
*/
|
|
164
|
+
slugifier?: SlugifierFn;
|
|
147
165
|
/**
|
|
148
166
|
* Site-wide field-level UI defaults. Currently surfaces the richtext
|
|
149
167
|
* editor adapter slot — additional field-level defaults (custom
|
|
@@ -214,6 +232,11 @@ export interface ServerConfig<TAdminStore = unknown> extends BaseConfig {
|
|
|
214
232
|
* Falls back to the default `slugify` from `@byline/core` when not set.
|
|
215
233
|
* Must be pure and synchronous — it runs server-side at write time and
|
|
216
234
|
* client-side for live form preview, and the two must agree on output.
|
|
235
|
+
*
|
|
236
|
+
* This is the **server-side** copy (the authoritative one — its output is
|
|
237
|
+
* what gets persisted). For the admin path-widget's live preview to match,
|
|
238
|
+
* register the *same* function on `ClientConfig.slugifier` via
|
|
239
|
+
* `defineClientConfig`. See {@link ClientConfig.slugifier}.
|
|
217
240
|
*/
|
|
218
241
|
slugifier?: SlugifierFn;
|
|
219
242
|
/**
|
|
@@ -97,4 +97,25 @@ export interface IStorageProvider {
|
|
|
97
97
|
* signing logic here.
|
|
98
98
|
*/
|
|
99
99
|
getUrl(storagePath: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* Move (re-key) a previously stored file from one `storage_path` to
|
|
102
|
+
* another, returning the new location metadata. The destination path is
|
|
103
|
+
* written verbatim — same contract as `UploadFileOptions.targetStoragePath`
|
|
104
|
+
* (POSIX-style, no leading slash; the caller owns sanitisation and
|
|
105
|
+
* collision avoidance). Throws if the source does not exist.
|
|
106
|
+
*
|
|
107
|
+
* **Optional capability** — both first-party providers (`storage-local`,
|
|
108
|
+
* `storage-s3`) implement it, but custom providers may not. Callers must
|
|
109
|
+
* feature-detect (`if (storage.move) …`) before relying on it.
|
|
110
|
+
*/
|
|
111
|
+
move?(fromPath: string, toPath: string): Promise<StoredFileLocation>;
|
|
112
|
+
/**
|
|
113
|
+
* Report whether a file exists at the given `storage_path`. Useful for
|
|
114
|
+
* collision checks when callers (e.g. `beforeStore` hooks assigning
|
|
115
|
+
* explicit storage keys via `{ storagePath }`) need to guarantee a key
|
|
116
|
+
* is free before claiming it.
|
|
117
|
+
*
|
|
118
|
+
* **Optional capability** — see `move`.
|
|
119
|
+
*/
|
|
120
|
+
exists?(storagePath: string): Promise<boolean>;
|
|
100
121
|
}
|
|
@@ -36,7 +36,7 @@ import type { CollectionDefinition } from '../@types/index.js';
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function registerCollectionAbilities(registry: AbilityRegistry, definition: CollectionDefinition): void;
|
|
38
38
|
/** The ability suffixes that every collection contributes. Exposed for contract tests. */
|
|
39
|
-
export declare const COLLECTION_ABILITY_VERBS: readonly [
|
|
39
|
+
export declare const COLLECTION_ABILITY_VERBS: readonly ['read', 'create', 'update', 'delete', 'publish', 'changeStatus', 'reindex'];
|
|
40
40
|
export type CollectionAbilityVerb = (typeof COLLECTION_ABILITY_VERBS)[number];
|
|
41
41
|
/** Compute the full ability key for a collection path and verb. */
|
|
42
42
|
export declare function collectionAbilityKey(path: string, verb: CollectionAbilityVerb): string;
|
package/dist/config/config.js
CHANGED
|
@@ -90,6 +90,10 @@ export function getClientConfig() {
|
|
|
90
90
|
routes: serverConfig.routes,
|
|
91
91
|
collections: serverConfig.collections,
|
|
92
92
|
admin: [],
|
|
93
|
+
// Carry the slugifier through the SSR fallback so a form rendered
|
|
94
|
+
// server-side derives the same path preview as the hydrated client
|
|
95
|
+
// (both configs are meant to register the same function).
|
|
96
|
+
slugifier: serverConfig.slugifier,
|
|
93
97
|
};
|
|
94
98
|
}
|
|
95
99
|
throw new Error('Byline has not been configured yet. Please call defineClientConfig in byline.config.ts first.');
|
|
@@ -23,7 +23,8 @@ export declare const RESERVED_FIELD_NAMES: ReadonlySet<string>;
|
|
|
23
23
|
* into derived paths via `useAsPath`.
|
|
24
24
|
* - When `useAsPath` is set, the referenced field must exist at the
|
|
25
25
|
* top level of the collection and be of a type the slugifier can
|
|
26
|
-
* sensibly consume (text-like
|
|
26
|
+
* sensibly consume (text-like, date-like, or a numeric identity
|
|
27
|
+
* field — `integer` / `counter`).
|
|
27
28
|
* - No field may be named `availableLocales`; collections opt into the
|
|
28
29
|
* editorial available-locales control via `advertiseLocales: true`.
|
|
29
30
|
* - When `advertiseLocales` is `true`, the collection must have at least
|
|
@@ -28,6 +28,14 @@ const USE_AS_PATH_SOURCE_TYPES = new Set([
|
|
|
28
28
|
'date',
|
|
29
29
|
'datetime',
|
|
30
30
|
'time',
|
|
31
|
+
// Numeric identity fields. `derivePath` stringifies the value before
|
|
32
|
+
// slugifying, so an integer or an allocator-assigned `counter` becomes a
|
|
33
|
+
// clean numeric slug (e.g. `1`, `42`). A replacement slugifier can branch
|
|
34
|
+
// on `collectionPath` to reshape it further — e.g. zero-padding a serial
|
|
35
|
+
// number to a fixed width. `float` / `decimal` are deliberately excluded:
|
|
36
|
+
// their string form carries a `.` which does not belong in a path segment.
|
|
37
|
+
'integer',
|
|
38
|
+
'counter',
|
|
31
39
|
]);
|
|
32
40
|
/**
|
|
33
41
|
* True when any field in the tree (at any nesting depth) is `localized`.
|
|
@@ -65,7 +73,8 @@ function walkFields(fields, visit) {
|
|
|
65
73
|
* into derived paths via `useAsPath`.
|
|
66
74
|
* - When `useAsPath` is set, the referenced field must exist at the
|
|
67
75
|
* top level of the collection and be of a type the slugifier can
|
|
68
|
-
* sensibly consume (text-like
|
|
76
|
+
* sensibly consume (text-like, date-like, or a numeric identity
|
|
77
|
+
* field — `integer` / `counter`).
|
|
69
78
|
* - No field may be named `availableLocales`; collections opt into the
|
|
70
79
|
* editorial available-locales control via `advertiseLocales: true`.
|
|
71
80
|
* - When `advertiseLocales` is `true`, the collection must have at least
|
|
@@ -87,6 +87,28 @@ describe('validateCollections', () => {
|
|
|
87
87
|
};
|
|
88
88
|
expect(() => validateCollections([collection])).not.toThrow();
|
|
89
89
|
});
|
|
90
|
+
it('accepts useAsPath pointing at a counter field', () => {
|
|
91
|
+
const collection = {
|
|
92
|
+
...baseCollection,
|
|
93
|
+
fields: [
|
|
94
|
+
{ name: 'title', label: 'Title', type: 'text' },
|
|
95
|
+
{ name: 'serialNumber', label: 'Serial Number', type: 'counter', group: 'serials' },
|
|
96
|
+
],
|
|
97
|
+
useAsPath: 'serialNumber',
|
|
98
|
+
};
|
|
99
|
+
expect(() => validateCollections([collection])).not.toThrow();
|
|
100
|
+
});
|
|
101
|
+
it('accepts useAsPath pointing at an integer field', () => {
|
|
102
|
+
const collection = {
|
|
103
|
+
...baseCollection,
|
|
104
|
+
fields: [
|
|
105
|
+
{ name: 'title', label: 'Title', type: 'text' },
|
|
106
|
+
{ name: 'issue', label: 'Issue', type: 'integer' },
|
|
107
|
+
],
|
|
108
|
+
useAsPath: 'issue',
|
|
109
|
+
};
|
|
110
|
+
expect(() => validateCollections([collection])).not.toThrow();
|
|
111
|
+
});
|
|
90
112
|
// useAsPath deliberately resolves against top-level fields only. A
|
|
91
113
|
// nested source (inside a group, array, or block) isn't addressable
|
|
92
114
|
// in the derivation cascade — path is a singular identity anchor, not
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -76,17 +76,17 @@ export declare class BylineError extends Error {
|
|
|
76
76
|
*/
|
|
77
77
|
export declare const createErrorType: (code: string, logLevel?: LogLevel) => (opts: BylineErrorOptions, errorConstructor?: any) => BylineError;
|
|
78
78
|
export declare const ErrorCodes: {
|
|
79
|
-
readonly UNHANDLED:
|
|
80
|
-
readonly NOT_FOUND:
|
|
81
|
-
readonly CONFLICT:
|
|
82
|
-
readonly VALIDATION:
|
|
83
|
-
readonly INVALID_TRANSITION:
|
|
84
|
-
readonly PATCH_FAILED:
|
|
85
|
-
readonly DATABASE:
|
|
86
|
-
readonly STORAGE:
|
|
87
|
-
readonly READ_BUDGET_EXCEEDED:
|
|
88
|
-
readonly PATH_CONFLICT:
|
|
89
|
-
readonly AUDIT_UNSUPPORTED:
|
|
79
|
+
readonly UNHANDLED: 'ERR_UNHANDLED';
|
|
80
|
+
readonly NOT_FOUND: 'ERR_NOT_FOUND';
|
|
81
|
+
readonly CONFLICT: 'ERR_CONFLICT';
|
|
82
|
+
readonly VALIDATION: 'ERR_VALIDATION';
|
|
83
|
+
readonly INVALID_TRANSITION: 'ERR_INVALID_TRANSITION';
|
|
84
|
+
readonly PATCH_FAILED: 'ERR_PATCH_FAILED';
|
|
85
|
+
readonly DATABASE: 'ERR_DATABASE';
|
|
86
|
+
readonly STORAGE: 'ERR_STORAGE';
|
|
87
|
+
readonly READ_BUDGET_EXCEEDED: 'ERR_READ_BUDGET_EXCEEDED';
|
|
88
|
+
readonly PATH_CONFLICT: 'ERR_PATH_CONFLICT';
|
|
89
|
+
readonly AUDIT_UNSUPPORTED: 'ERR_AUDIT_UNSUPPORTED';
|
|
90
90
|
};
|
|
91
91
|
export declare const ERR_UNHANDLED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError;
|
|
92
92
|
export declare const ERR_NOT_FOUND: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError;
|
|
@@ -46,7 +46,7 @@ export function resolveFieldForPath(definition, path) {
|
|
|
46
46
|
if (segments.length === 0)
|
|
47
47
|
return null;
|
|
48
48
|
const [first, ...rest] = segments;
|
|
49
|
-
if (
|
|
49
|
+
if (first?.kind !== 'field')
|
|
50
50
|
return null;
|
|
51
51
|
let current = definition.fields.find((f) => f.name === first.key);
|
|
52
52
|
if (!current)
|
|
@@ -53,9 +53,6 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
53
53
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
54
54
|
}, z.core.$strip>;
|
|
55
55
|
full: z.ZodObject<{
|
|
56
|
-
fields: z.ZodObject<{
|
|
57
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
58
|
-
}, z.core.$strip>;
|
|
59
56
|
id: z.ZodUUID;
|
|
60
57
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
61
58
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -68,12 +65,12 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
68
65
|
updatedAt: z.ZodISODateTime;
|
|
69
66
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
70
67
|
eventType: z.ZodOptional<z.ZodString>;
|
|
68
|
+
fields: z.ZodObject<{
|
|
69
|
+
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
70
|
+
}, z.core.$strip>;
|
|
71
71
|
}, z.core.$strip>;
|
|
72
72
|
list: z.ZodObject<{
|
|
73
73
|
docs: z.ZodArray<z.ZodObject<{
|
|
74
|
-
fields: z.ZodObject<{
|
|
75
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
76
|
-
}, z.core.$strip>;
|
|
77
74
|
id: z.ZodUUID;
|
|
78
75
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
79
76
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -86,6 +83,9 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
86
83
|
updatedAt: z.ZodISODateTime;
|
|
87
84
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
88
85
|
eventType: z.ZodOptional<z.ZodString>;
|
|
86
|
+
fields: z.ZodObject<{
|
|
87
|
+
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
89
|
}, z.core.$strip>>;
|
|
90
90
|
meta: z.ZodObject<{
|
|
91
91
|
page: z.ZodNumber;
|
|
@@ -108,9 +108,6 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
108
108
|
}, z.core.$strip>;
|
|
109
109
|
history: z.ZodObject<{
|
|
110
110
|
docs: z.ZodArray<z.ZodObject<{
|
|
111
|
-
fields: z.ZodObject<{
|
|
112
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
113
|
-
}, z.core.$strip>;
|
|
114
111
|
id: z.ZodUUID;
|
|
115
112
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
116
113
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -123,6 +120,9 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
123
120
|
updatedAt: z.ZodISODateTime;
|
|
124
121
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
125
122
|
eventType: z.ZodOptional<z.ZodString>;
|
|
123
|
+
fields: z.ZodObject<{
|
|
124
|
+
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
125
|
+
}, z.core.$strip>;
|
|
126
126
|
}, z.core.$strip>>;
|
|
127
127
|
meta: z.ZodObject<{
|
|
128
128
|
page: z.ZodNumber;
|
|
@@ -137,9 +137,6 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
137
137
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
138
138
|
}, z.core.$strip>;
|
|
139
139
|
get: z.ZodObject<{
|
|
140
|
-
fields: z.ZodObject<{
|
|
141
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
142
|
-
}, z.core.$strip>;
|
|
143
140
|
id: z.ZodUUID;
|
|
144
141
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
145
142
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -152,6 +149,9 @@ export declare const createCollectionSchemasForPath: (path: string) => {
|
|
|
152
149
|
updatedAt: z.ZodISODateTime;
|
|
153
150
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
154
151
|
eventType: z.ZodOptional<z.ZodString>;
|
|
152
|
+
fields: z.ZodObject<{
|
|
153
|
+
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
154
|
+
}, z.core.$strip>;
|
|
155
155
|
}, z.core.$strip>;
|
|
156
156
|
update: z.ZodObject<{
|
|
157
157
|
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
@@ -176,93 +176,6 @@ export declare const createCollectionSchemas: (collection: CollectionDefinition)
|
|
|
176
176
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
177
177
|
}, z.core.$strip>;
|
|
178
178
|
full: z.ZodObject<{
|
|
179
|
-
fields: z.ZodObject<{
|
|
180
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
181
|
-
}, z.core.$strip>;
|
|
182
|
-
id: z.ZodUUID;
|
|
183
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
184
|
-
path: z.ZodOptional<z.ZodString>;
|
|
185
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
186
|
-
status: z.ZodEnum<{
|
|
187
|
-
[x: string]: string;
|
|
188
|
-
}>;
|
|
189
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
190
|
-
createdAt: z.ZodISODateTime;
|
|
191
|
-
updatedAt: z.ZodISODateTime;
|
|
192
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
193
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
194
|
-
}, z.core.$strip>;
|
|
195
|
-
list: z.ZodObject<{
|
|
196
|
-
docs: z.ZodArray<z.ZodObject<{
|
|
197
|
-
fields: z.ZodObject<{
|
|
198
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
199
|
-
}, z.core.$strip>;
|
|
200
|
-
id: z.ZodUUID;
|
|
201
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
202
|
-
path: z.ZodOptional<z.ZodString>;
|
|
203
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
204
|
-
status: z.ZodEnum<{
|
|
205
|
-
[x: string]: string;
|
|
206
|
-
}>;
|
|
207
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
208
|
-
createdAt: z.ZodISODateTime;
|
|
209
|
-
updatedAt: z.ZodISODateTime;
|
|
210
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
211
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
212
|
-
}, z.core.$strip>>;
|
|
213
|
-
meta: z.ZodObject<{
|
|
214
|
-
page: z.ZodNumber;
|
|
215
|
-
pageSize: z.ZodNumber;
|
|
216
|
-
total: z.ZodNumber;
|
|
217
|
-
totalPages: z.ZodNumber;
|
|
218
|
-
order: z.ZodOptional<z.ZodString>;
|
|
219
|
-
desc: z.ZodOptional<z.ZodBoolean>;
|
|
220
|
-
}, z.core.$strip>;
|
|
221
|
-
included: z.ZodObject<{
|
|
222
|
-
collection: z.ZodObject<{
|
|
223
|
-
id: z.ZodString;
|
|
224
|
-
labels: z.ZodObject<{
|
|
225
|
-
singular: z.ZodString;
|
|
226
|
-
plural: z.ZodString;
|
|
227
|
-
}, z.core.$strip>;
|
|
228
|
-
path: z.ZodLiteral<string>;
|
|
229
|
-
}, z.core.$strip>;
|
|
230
|
-
}, z.core.$strip>;
|
|
231
|
-
}, z.core.$strip>;
|
|
232
|
-
history: z.ZodObject<{
|
|
233
|
-
docs: z.ZodArray<z.ZodObject<{
|
|
234
|
-
fields: z.ZodObject<{
|
|
235
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
236
|
-
}, z.core.$strip>;
|
|
237
|
-
id: z.ZodUUID;
|
|
238
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
239
|
-
path: z.ZodOptional<z.ZodString>;
|
|
240
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
241
|
-
status: z.ZodEnum<{
|
|
242
|
-
[x: string]: string;
|
|
243
|
-
}>;
|
|
244
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
245
|
-
createdAt: z.ZodISODateTime;
|
|
246
|
-
updatedAt: z.ZodISODateTime;
|
|
247
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
248
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
249
|
-
}, z.core.$strip>>;
|
|
250
|
-
meta: z.ZodObject<{
|
|
251
|
-
page: z.ZodNumber;
|
|
252
|
-
pageSize: z.ZodNumber;
|
|
253
|
-
total: z.ZodNumber;
|
|
254
|
-
totalPages: z.ZodNumber;
|
|
255
|
-
order: z.ZodOptional<z.ZodString>;
|
|
256
|
-
desc: z.ZodOptional<z.ZodBoolean>;
|
|
257
|
-
}, z.core.$strip>;
|
|
258
|
-
}, z.core.$strip>;
|
|
259
|
-
create: z.ZodObject<{
|
|
260
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
261
|
-
}, z.core.$strip>;
|
|
262
|
-
get: z.ZodObject<{
|
|
263
|
-
fields: z.ZodObject<{
|
|
264
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
265
|
-
}, z.core.$strip>;
|
|
266
179
|
id: z.ZodUUID;
|
|
267
180
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
268
181
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -275,88 +188,12 @@ export declare const createCollectionSchemas: (collection: CollectionDefinition)
|
|
|
275
188
|
updatedAt: z.ZodISODateTime;
|
|
276
189
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
277
190
|
eventType: z.ZodOptional<z.ZodString>;
|
|
278
|
-
}, z.core.$strip>;
|
|
279
|
-
update: z.ZodObject<{
|
|
280
|
-
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
281
|
-
}, z.core.$strip>;
|
|
282
|
-
};
|
|
283
|
-
export declare const createTypedCollectionSchemas: (collection: CollectionDefinition) => {
|
|
284
|
-
base: z.ZodObject<{
|
|
285
|
-
id: z.ZodUUID;
|
|
286
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
287
|
-
path: z.ZodOptional<z.ZodString>;
|
|
288
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
289
|
-
status: z.ZodEnum<{
|
|
290
|
-
[x: string]: string;
|
|
291
|
-
}>;
|
|
292
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
293
|
-
createdAt: z.ZodISODateTime;
|
|
294
|
-
updatedAt: z.ZodISODateTime;
|
|
295
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
296
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
297
|
-
}, z.core.$strip>;
|
|
298
|
-
fields: z.ZodObject<{
|
|
299
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
300
|
-
}, z.core.$strip>;
|
|
301
|
-
full: z.ZodObject<{
|
|
302
191
|
fields: z.ZodObject<{
|
|
303
192
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
304
193
|
}, z.core.$strip>;
|
|
305
|
-
id: z.ZodUUID;
|
|
306
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
307
|
-
path: z.ZodOptional<z.ZodString>;
|
|
308
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
309
|
-
status: z.ZodEnum<{
|
|
310
|
-
[x: string]: string;
|
|
311
|
-
}>;
|
|
312
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
313
|
-
createdAt: z.ZodISODateTime;
|
|
314
|
-
updatedAt: z.ZodISODateTime;
|
|
315
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
316
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
317
194
|
}, z.core.$strip>;
|
|
318
195
|
list: z.ZodObject<{
|
|
319
196
|
docs: z.ZodArray<z.ZodObject<{
|
|
320
|
-
fields: z.ZodObject<{
|
|
321
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
322
|
-
}, z.core.$strip>;
|
|
323
|
-
id: z.ZodUUID;
|
|
324
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
325
|
-
path: z.ZodOptional<z.ZodString>;
|
|
326
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
327
|
-
status: z.ZodEnum<{
|
|
328
|
-
[x: string]: string;
|
|
329
|
-
}>;
|
|
330
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
331
|
-
createdAt: z.ZodISODateTime;
|
|
332
|
-
updatedAt: z.ZodISODateTime;
|
|
333
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
334
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
335
|
-
}, z.core.$strip>>;
|
|
336
|
-
meta: z.ZodObject<{
|
|
337
|
-
page: z.ZodNumber;
|
|
338
|
-
pageSize: z.ZodNumber;
|
|
339
|
-
total: z.ZodNumber;
|
|
340
|
-
totalPages: z.ZodNumber;
|
|
341
|
-
order: z.ZodOptional<z.ZodString>;
|
|
342
|
-
desc: z.ZodOptional<z.ZodBoolean>;
|
|
343
|
-
}, z.core.$strip>;
|
|
344
|
-
included: z.ZodObject<{
|
|
345
|
-
collection: z.ZodObject<{
|
|
346
|
-
id: z.ZodString;
|
|
347
|
-
labels: z.ZodObject<{
|
|
348
|
-
singular: z.ZodString;
|
|
349
|
-
plural: z.ZodString;
|
|
350
|
-
}, z.core.$strip>;
|
|
351
|
-
path: z.ZodLiteral<string>;
|
|
352
|
-
}, z.core.$strip>;
|
|
353
|
-
}, z.core.$strip>;
|
|
354
|
-
}, z.core.$strip>;
|
|
355
|
-
history: z.ZodObject<{
|
|
356
|
-
docs: z.ZodArray<z.ZodObject<{
|
|
357
|
-
fields: z.ZodObject<{
|
|
358
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
359
|
-
}, z.core.$strip>;
|
|
360
197
|
id: z.ZodUUID;
|
|
361
198
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
362
199
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -369,92 +206,9 @@ export declare const createTypedCollectionSchemas: (collection: CollectionDefini
|
|
|
369
206
|
updatedAt: z.ZodISODateTime;
|
|
370
207
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
371
208
|
eventType: z.ZodOptional<z.ZodString>;
|
|
372
|
-
}, z.core.$strip>>;
|
|
373
|
-
meta: z.ZodObject<{
|
|
374
|
-
page: z.ZodNumber;
|
|
375
|
-
pageSize: z.ZodNumber;
|
|
376
|
-
total: z.ZodNumber;
|
|
377
|
-
totalPages: z.ZodNumber;
|
|
378
|
-
order: z.ZodOptional<z.ZodString>;
|
|
379
|
-
desc: z.ZodOptional<z.ZodBoolean>;
|
|
380
|
-
}, z.core.$strip>;
|
|
381
|
-
}, z.core.$strip>;
|
|
382
|
-
create: z.ZodObject<{
|
|
383
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
384
|
-
}, z.core.$strip>;
|
|
385
|
-
get: z.ZodObject<{
|
|
386
|
-
fields: z.ZodObject<{
|
|
387
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
388
|
-
}, z.core.$strip>;
|
|
389
|
-
id: z.ZodUUID;
|
|
390
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
391
|
-
path: z.ZodOptional<z.ZodString>;
|
|
392
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
393
|
-
status: z.ZodEnum<{
|
|
394
|
-
[x: string]: string;
|
|
395
|
-
}>;
|
|
396
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
397
|
-
createdAt: z.ZodISODateTime;
|
|
398
|
-
updatedAt: z.ZodISODateTime;
|
|
399
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
400
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
401
|
-
}, z.core.$strip>;
|
|
402
|
-
update: z.ZodObject<{
|
|
403
|
-
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
404
|
-
}, z.core.$strip>;
|
|
405
|
-
};
|
|
406
|
-
export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
407
|
-
base: z.ZodObject<{
|
|
408
|
-
id: z.ZodUUID;
|
|
409
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
410
|
-
path: z.ZodOptional<z.ZodString>;
|
|
411
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
412
|
-
status: z.ZodEnum<{
|
|
413
|
-
[x: string]: string;
|
|
414
|
-
}>;
|
|
415
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
416
|
-
createdAt: z.ZodISODateTime;
|
|
417
|
-
updatedAt: z.ZodISODateTime;
|
|
418
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
419
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
420
|
-
}, z.core.$strip>;
|
|
421
|
-
fields: z.ZodObject<{
|
|
422
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
423
|
-
}, z.core.$strip>;
|
|
424
|
-
full: z.ZodObject<{
|
|
425
|
-
fields: z.ZodObject<{
|
|
426
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
427
|
-
}, z.core.$strip>;
|
|
428
|
-
id: z.ZodUUID;
|
|
429
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
430
|
-
path: z.ZodOptional<z.ZodString>;
|
|
431
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
432
|
-
status: z.ZodEnum<{
|
|
433
|
-
[x: string]: string;
|
|
434
|
-
}>;
|
|
435
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
436
|
-
createdAt: z.ZodISODateTime;
|
|
437
|
-
updatedAt: z.ZodISODateTime;
|
|
438
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
439
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
440
|
-
}, z.core.$strip>;
|
|
441
|
-
list: z.ZodObject<{
|
|
442
|
-
docs: z.ZodArray<z.ZodObject<{
|
|
443
209
|
fields: z.ZodObject<{
|
|
444
210
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
445
211
|
}, z.core.$strip>;
|
|
446
|
-
id: z.ZodUUID;
|
|
447
|
-
versionId: z.ZodOptional<z.ZodUUID>;
|
|
448
|
-
path: z.ZodOptional<z.ZodString>;
|
|
449
|
-
sourceLocale: z.ZodOptional<z.ZodString>;
|
|
450
|
-
status: z.ZodEnum<{
|
|
451
|
-
[x: string]: string;
|
|
452
|
-
}>;
|
|
453
|
-
hasPublishedVersion: z.ZodOptional<z.ZodBoolean>;
|
|
454
|
-
createdAt: z.ZodISODateTime;
|
|
455
|
-
updatedAt: z.ZodISODateTime;
|
|
456
|
-
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
457
|
-
eventType: z.ZodOptional<z.ZodString>;
|
|
458
212
|
}, z.core.$strip>>;
|
|
459
213
|
meta: z.ZodObject<{
|
|
460
214
|
page: z.ZodNumber;
|
|
@@ -477,9 +231,6 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
477
231
|
}, z.core.$strip>;
|
|
478
232
|
history: z.ZodObject<{
|
|
479
233
|
docs: z.ZodArray<z.ZodObject<{
|
|
480
|
-
fields: z.ZodObject<{
|
|
481
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
482
|
-
}, z.core.$strip>;
|
|
483
234
|
id: z.ZodUUID;
|
|
484
235
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
485
236
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -492,6 +243,9 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
492
243
|
updatedAt: z.ZodISODateTime;
|
|
493
244
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
494
245
|
eventType: z.ZodOptional<z.ZodString>;
|
|
246
|
+
fields: z.ZodObject<{
|
|
247
|
+
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
248
|
+
}, z.core.$strip>;
|
|
495
249
|
}, z.core.$strip>>;
|
|
496
250
|
meta: z.ZodObject<{
|
|
497
251
|
page: z.ZodNumber;
|
|
@@ -506,9 +260,6 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
506
260
|
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
507
261
|
}, z.core.$strip>;
|
|
508
262
|
get: z.ZodObject<{
|
|
509
|
-
fields: z.ZodObject<{
|
|
510
|
-
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
511
|
-
}, z.core.$strip>;
|
|
512
263
|
id: z.ZodUUID;
|
|
513
264
|
versionId: z.ZodOptional<z.ZodUUID>;
|
|
514
265
|
path: z.ZodOptional<z.ZodString>;
|
|
@@ -521,8 +272,13 @@ export declare const createTypedCollectionSchemasForPath: (path: string) => {
|
|
|
521
272
|
updatedAt: z.ZodISODateTime;
|
|
522
273
|
createdBy: z.ZodOptional<z.ZodUUID>;
|
|
523
274
|
eventType: z.ZodOptional<z.ZodString>;
|
|
275
|
+
fields: z.ZodObject<{
|
|
276
|
+
[x: string]: z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>;
|
|
277
|
+
}, z.core.$strip>;
|
|
524
278
|
}, z.core.$strip>;
|
|
525
279
|
update: z.ZodObject<{
|
|
526
280
|
[x: string]: z.ZodOptional<z.ZodType<any, unknown, z.core.$ZodTypeInternals<any, unknown>>>;
|
|
527
281
|
}, z.core.$strip>;
|
|
528
282
|
};
|
|
283
|
+
export declare const createTypedCollectionSchemas: typeof createCollectionSchemas;
|
|
284
|
+
export declare const createTypedCollectionSchemasForPath: typeof createCollectionSchemasForPath;
|
|
@@ -3,8 +3,8 @@ import type { CollectionDefinition } from '../../@types/index.js';
|
|
|
3
3
|
type TypedSchemaSet = ReturnType<typeof createTypedCollectionSchemas>;
|
|
4
4
|
export declare const getCollectionSchemasForPath: (path: string) => TypedSchemaSet;
|
|
5
5
|
export declare const getCollectionSchemas: (collection: CollectionDefinition) => TypedSchemaSet;
|
|
6
|
-
export declare const getTypedCollectionSchemasForPath:
|
|
7
|
-
export declare const getTypedCollectionSchemas:
|
|
6
|
+
export declare const getTypedCollectionSchemasForPath: typeof getCollectionSchemasForPath;
|
|
7
|
+
export declare const getTypedCollectionSchemas: typeof getCollectionSchemas;
|
|
8
8
|
export declare const clearSchemaCache: (collectionPath?: string) => void;
|
|
9
9
|
export declare const getCacheStats: () => {
|
|
10
10
|
size: number;
|
|
@@ -52,6 +52,7 @@ function createMockDb(options) {
|
|
|
52
52
|
counters: {
|
|
53
53
|
ensureCounterGroup: vi.fn(fail),
|
|
54
54
|
nextCounterValue: vi.fn(fail),
|
|
55
|
+
nextScopedCounterValue: vi.fn(fail),
|
|
55
56
|
},
|
|
56
57
|
},
|
|
57
58
|
queries: {
|
|
@@ -203,6 +204,7 @@ describe('ensureCollections', () => {
|
|
|
203
204
|
counters: {
|
|
204
205
|
ensureCounterGroup: vi.fn(),
|
|
205
206
|
nextCounterValue: vi.fn(),
|
|
207
|
+
nextScopedCounterValue: vi.fn(),
|
|
206
208
|
},
|
|
207
209
|
},
|
|
208
210
|
queries: {
|
|
@@ -9,10 +9,10 @@ import type { AuditActorRealm, AuditLogAppendInput, IDbAdapter } from '../../@ty
|
|
|
9
9
|
import type { DocumentLifecycleContext } from './context.js';
|
|
10
10
|
/** Namespaced audit actions for document-grain changes. */
|
|
11
11
|
export declare const AUDIT_ACTIONS: {
|
|
12
|
-
readonly pathChanged:
|
|
13
|
-
readonly localesChanged:
|
|
14
|
-
readonly statusChanged:
|
|
15
|
-
readonly deleted:
|
|
12
|
+
readonly pathChanged: 'document.path.changed';
|
|
13
|
+
readonly localesChanged: 'document.locales.changed';
|
|
14
|
+
readonly statusChanged: 'document.status.changed';
|
|
15
|
+
readonly deleted: 'document.deleted';
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* The actor id + realm for an audit-log row. Mirrors `actorId()`: a real
|
|
@@ -69,18 +69,22 @@ function normalizeUploadHook(hook) {
|
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Run the `beforeStore` chain. Each function receives the previous
|
|
72
|
-
* function's filename
|
|
72
|
+
* function's filename / storage-path overrides (fold). A function may:
|
|
73
73
|
*
|
|
74
74
|
* - return a string or `{ filename }` to substitute a new filename;
|
|
75
|
+
* - return `{ storagePath }` to take full control of the storage key
|
|
76
|
+
* (bypasses provider key derivation — no UUID prefix). When set
|
|
77
|
+
* without `filename`, the filename defaults to the path's basename;
|
|
75
78
|
* - return `{ error }` to short-circuit with `ERR_VALIDATION`;
|
|
76
|
-
* - return `void` / `undefined` to leave
|
|
79
|
+
* - return `void` / `undefined` to leave current values unchanged.
|
|
77
80
|
*
|
|
78
|
-
* Returns the resolved filename.
|
|
81
|
+
* Returns the resolved filename and optional storage path.
|
|
79
82
|
*/
|
|
80
83
|
async function runBeforeStoreChain(hooks, ctx, logger) {
|
|
81
84
|
let effective = ctx.filename;
|
|
85
|
+
let effectiveStoragePath;
|
|
82
86
|
for (const fn of hooks) {
|
|
83
|
-
const result = await fn({ ...ctx, filename: effective });
|
|
87
|
+
const result = await fn({ ...ctx, filename: effective, storagePath: effectiveStoragePath });
|
|
84
88
|
if (result == null)
|
|
85
89
|
continue;
|
|
86
90
|
if (typeof result === 'string') {
|
|
@@ -96,14 +100,35 @@ async function runBeforeStoreChain(hooks, ctx, logger) {
|
|
|
96
100
|
details: { collectionPath: ctx.collectionPath, fieldName: ctx.fieldName },
|
|
97
101
|
}, runBeforeStoreChain).log(logger);
|
|
98
102
|
}
|
|
103
|
+
let filenameOverridden = false;
|
|
99
104
|
if ('filename' in result && typeof result.filename === 'string') {
|
|
100
105
|
const trimmed = result.filename.trim();
|
|
101
|
-
if (trimmed)
|
|
106
|
+
if (trimmed) {
|
|
102
107
|
effective = trimmed;
|
|
108
|
+
filenameOverridden = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if ('storagePath' in result && typeof result.storagePath === 'string') {
|
|
112
|
+
// Normalise: POSIX-style, no leading slash (matches the
|
|
113
|
+
// UploadFileOptions.targetStoragePath contract).
|
|
114
|
+
const trimmed = result.storagePath.trim().replace(/^\/+/, '');
|
|
115
|
+
if (trimmed) {
|
|
116
|
+
effectiveStoragePath = trimmed;
|
|
117
|
+
// Keep the stored filename in sync with the explicit key
|
|
118
|
+
// unless the hook also set filename itself.
|
|
119
|
+
if (!filenameOverridden) {
|
|
120
|
+
effective = posixBasename(trimmed);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
103
123
|
}
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
|
-
return effective;
|
|
126
|
+
return { filename: effective, storagePath: effectiveStoragePath };
|
|
127
|
+
}
|
|
128
|
+
/** Last path segment of a POSIX-style storage path. */
|
|
129
|
+
function posixBasename(storagePath) {
|
|
130
|
+
const idx = storagePath.lastIndexOf('/');
|
|
131
|
+
return idx === -1 ? storagePath : storagePath.slice(idx + 1);
|
|
107
132
|
}
|
|
108
133
|
export async function uploadField(ctx, params) {
|
|
109
134
|
return withLogContext({ domain: 'services', module: 'upload', function: 'uploadField' }, async () => {
|
|
@@ -163,7 +188,7 @@ export async function uploadField(ctx, params) {
|
|
|
163
188
|
// graphs out of the client bundle; the inline form returns as-is.
|
|
164
189
|
const uploadHooks = await resolveUploadHooks(upload.hooks);
|
|
165
190
|
const beforeStoreHooks = normalizeUploadHook(uploadHooks?.beforeStore);
|
|
166
|
-
const effectiveFilename = await runBeforeStoreChain(beforeStoreHooks, {
|
|
191
|
+
const { filename: effectiveFilename, storagePath: effectiveStoragePath } = await runBeforeStoreChain(beforeStoreHooks, {
|
|
167
192
|
fieldName,
|
|
168
193
|
field,
|
|
169
194
|
filename: sanitised,
|
|
@@ -176,9 +201,14 @@ export async function uploadField(ctx, params) {
|
|
|
176
201
|
requestId: '',
|
|
177
202
|
readMode: 'any',
|
|
178
203
|
},
|
|
204
|
+
storage,
|
|
179
205
|
}, logger);
|
|
180
206
|
// -- Storage write. Filename is the post-hook value, so generated
|
|
181
|
-
// variants automatically inherit the new prefix.
|
|
207
|
+
// variants automatically inherit the new prefix. An explicit
|
|
208
|
+
// `storagePath` from the chain is threaded through as
|
|
209
|
+
// `targetStoragePath`, which providers write verbatim (no UUID
|
|
210
|
+
// prefix / key derivation) — variants still derive sibling paths
|
|
211
|
+
// from the resulting `storedFile.storagePath`.
|
|
182
212
|
let storedFile;
|
|
183
213
|
try {
|
|
184
214
|
storedFile = await storage.upload(buffer, {
|
|
@@ -186,6 +216,7 @@ export async function uploadField(ctx, params) {
|
|
|
186
216
|
mimeType,
|
|
187
217
|
size: fileSize,
|
|
188
218
|
collection: collectionPath,
|
|
219
|
+
...(effectiveStoragePath !== undefined && { targetStoragePath: effectiveStoragePath }),
|
|
189
220
|
});
|
|
190
221
|
}
|
|
191
222
|
catch (err) {
|
|
@@ -261,6 +292,7 @@ export async function uploadField(ctx, params) {
|
|
|
261
292
|
requestId: '',
|
|
262
293
|
readMode: 'any',
|
|
263
294
|
},
|
|
295
|
+
storage,
|
|
264
296
|
};
|
|
265
297
|
await fn(afterCtx);
|
|
266
298
|
}
|
|
@@ -66,6 +66,7 @@ function createMockDb() {
|
|
|
66
66
|
counters: {
|
|
67
67
|
ensureCounterGroup: vi.fn(),
|
|
68
68
|
nextCounterValue: vi.fn(),
|
|
69
|
+
nextScopedCounterValue: vi.fn(),
|
|
69
70
|
},
|
|
70
71
|
},
|
|
71
72
|
queries: {
|
|
@@ -248,6 +249,84 @@ describe('uploadField service', () => {
|
|
|
248
249
|
});
|
|
249
250
|
expect(upload).toHaveBeenCalledWith(expect.any(Buffer), expect.objectContaining({ filename: 'PUB-42-tenant-hero.png' }));
|
|
250
251
|
});
|
|
252
|
+
it('beforeStore { storagePath } threads targetStoragePath verbatim and derives the filename from its basename', async () => {
|
|
253
|
+
const definition = withFieldUpload(uploadCollection, 'image', (f) => {
|
|
254
|
+
f.upload.hooks = {
|
|
255
|
+
beforeStore: () => ({ storagePath: 'publications/forru-0000447-0001-en.png' }),
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
const { ctx, upload } = buildCtx({ definition });
|
|
259
|
+
const result = await uploadField(ctx, {
|
|
260
|
+
buffer: Buffer.from('png'),
|
|
261
|
+
originalFilename: 'hero.png',
|
|
262
|
+
mimeType: 'image/png',
|
|
263
|
+
fileSize: 3,
|
|
264
|
+
shouldCreateDocument: false,
|
|
265
|
+
});
|
|
266
|
+
// Explicit key is handed to the provider verbatim — no UUID prefix,
|
|
267
|
+
// no collection-derived key.
|
|
268
|
+
expect(upload).toHaveBeenCalledWith(expect.any(Buffer), expect.objectContaining({
|
|
269
|
+
targetStoragePath: 'publications/forru-0000447-0001-en.png',
|
|
270
|
+
filename: 'forru-0000447-0001-en.png',
|
|
271
|
+
}));
|
|
272
|
+
// Stored filename follows the explicit key's basename.
|
|
273
|
+
expect(result.storedFile.filename).toBe('forru-0000447-0001-en.png');
|
|
274
|
+
});
|
|
275
|
+
it('beforeStore { storagePath, filename } honours both; a leading slash is stripped from the key', async () => {
|
|
276
|
+
const definition = withFieldUpload(uploadCollection, 'image', (f) => {
|
|
277
|
+
f.upload.hooks = {
|
|
278
|
+
beforeStore: () => ({
|
|
279
|
+
storagePath: '/publications/forru-0000447-0002-en.png',
|
|
280
|
+
filename: 'display-name.png',
|
|
281
|
+
}),
|
|
282
|
+
};
|
|
283
|
+
});
|
|
284
|
+
const { ctx, upload } = buildCtx({ definition });
|
|
285
|
+
const result = await uploadField(ctx, {
|
|
286
|
+
buffer: Buffer.from('png'),
|
|
287
|
+
originalFilename: 'hero.png',
|
|
288
|
+
mimeType: 'image/png',
|
|
289
|
+
fileSize: 3,
|
|
290
|
+
shouldCreateDocument: false,
|
|
291
|
+
});
|
|
292
|
+
expect(upload).toHaveBeenCalledWith(expect.any(Buffer), expect.objectContaining({
|
|
293
|
+
targetStoragePath: 'publications/forru-0000447-0002-en.png',
|
|
294
|
+
filename: 'display-name.png',
|
|
295
|
+
}));
|
|
296
|
+
expect(result.storedFile.filename).toBe('display-name.png');
|
|
297
|
+
});
|
|
298
|
+
it('folds storagePath through a chain — later hooks see ctx.storagePath and may override it', async () => {
|
|
299
|
+
const secondHook = vi.fn(({ storagePath }) => ({
|
|
300
|
+
storagePath: storagePath.replace('-draft', ''),
|
|
301
|
+
}));
|
|
302
|
+
const definition = withFieldUpload(uploadCollection, 'image', (f) => {
|
|
303
|
+
f.upload.hooks = {
|
|
304
|
+
beforeStore: [() => ({ storagePath: 'publications/report-draft.png' }), secondHook],
|
|
305
|
+
};
|
|
306
|
+
});
|
|
307
|
+
const { ctx, upload } = buildCtx({ definition });
|
|
308
|
+
await uploadField(ctx, {
|
|
309
|
+
buffer: Buffer.from('png'),
|
|
310
|
+
originalFilename: 'hero.png',
|
|
311
|
+
mimeType: 'image/png',
|
|
312
|
+
fileSize: 3,
|
|
313
|
+
shouldCreateDocument: false,
|
|
314
|
+
});
|
|
315
|
+
expect(secondHook).toHaveBeenCalledWith(expect.objectContaining({ storagePath: 'publications/report-draft.png' }));
|
|
316
|
+
expect(upload).toHaveBeenCalledWith(expect.any(Buffer), expect.objectContaining({ targetStoragePath: 'publications/report.png' }));
|
|
317
|
+
});
|
|
318
|
+
it('no storagePath override → storage.upload receives no targetStoragePath', async () => {
|
|
319
|
+
const { ctx, upload } = buildCtx();
|
|
320
|
+
await uploadField(ctx, {
|
|
321
|
+
buffer: Buffer.from('png'),
|
|
322
|
+
originalFilename: 'hero.png',
|
|
323
|
+
mimeType: 'image/png',
|
|
324
|
+
fileSize: 3,
|
|
325
|
+
shouldCreateDocument: false,
|
|
326
|
+
});
|
|
327
|
+
const options = upload.mock.calls[0]?.[1];
|
|
328
|
+
expect(options).not.toHaveProperty('targetStoragePath');
|
|
329
|
+
});
|
|
251
330
|
it('beforeStore { error } short-circuits — no storage write, no variants, no afterStore', async () => {
|
|
252
331
|
const afterStore = vi.fn();
|
|
253
332
|
const definition = withFieldUpload(uploadCollection, 'image', (f) => {
|
|
@@ -33,9 +33,9 @@ import { z } from 'zod';
|
|
|
33
33
|
* this file — emit codes here, translate in `@byline/admin`.
|
|
34
34
|
*/
|
|
35
35
|
export declare const PASSWORD_ERROR_CODES: {
|
|
36
|
-
readonly TOO_SHORT:
|
|
37
|
-
readonly TOO_LONG:
|
|
38
|
-
readonly COMPLEXITY:
|
|
36
|
+
readonly TOO_SHORT: 'password.tooShort';
|
|
37
|
+
readonly TOO_LONG: 'password.tooLong';
|
|
38
|
+
readonly COMPLEXITY: 'password.complexity';
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
41
41
|
* Standard password policy — 8 to 128 characters, must contain at least
|
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": "3.
|
|
5
|
+
"version": "3.19.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.9.0"
|
|
8
8
|
},
|
|
@@ -74,20 +74,20 @@
|
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"dotenv": "^17.4.2",
|
|
76
76
|
"pino": "^10.3.1",
|
|
77
|
-
"sharp": "^0.
|
|
77
|
+
"sharp": "^0.35.3",
|
|
78
78
|
"zod": "^4.4.3",
|
|
79
|
-
"@byline/auth": "3.
|
|
79
|
+
"@byline/auth": "3.19.0"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@biomejs/biome": "2.
|
|
83
|
-
"@types/node": "^
|
|
82
|
+
"@biomejs/biome": "2.5.2",
|
|
83
|
+
"@types/node": "^26.1.0",
|
|
84
84
|
"chokidar": "^5.0.0",
|
|
85
85
|
"chokidar-cli": "^3.0.0",
|
|
86
86
|
"npm-run-all": "^4.1.5",
|
|
87
|
-
"tsc-alias": "^1.
|
|
88
|
-
"tsx": "^4.
|
|
89
|
-
"typescript": "
|
|
90
|
-
"vitest": "^4.1.
|
|
87
|
+
"tsc-alias": "^1.9.0",
|
|
88
|
+
"tsx": "^4.23.0",
|
|
89
|
+
"typescript": "^7.0.2",
|
|
90
|
+
"vitest": "^4.1.10"
|
|
91
91
|
},
|
|
92
92
|
"publishConfig": {
|
|
93
93
|
"access": "public",
|