@orthacms/content-server 0.4.3 → 0.5.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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/lib/content.module.d.ts.map +1 -1
- package/dist/lib/content.module.js +8 -0
- package/dist/lib/copilot/entry-proposal.applier.d.ts.map +1 -1
- package/dist/lib/copilot/entry-proposal.applier.js +5 -4
- package/dist/lib/docs/describe-content-api.d.ts +7 -0
- package/dist/lib/docs/describe-content-api.d.ts.map +1 -1
- package/dist/lib/docs/describe-content-api.js +177 -53
- package/dist/lib/docs/describe-views-api.d.ts +19 -0
- package/dist/lib/docs/describe-views-api.d.ts.map +1 -0
- package/dist/lib/docs/describe-views-api.js +76 -0
- package/dist/lib/docs/field-schema.d.ts.map +1 -1
- package/dist/lib/docs/field-schema.js +36 -2
- package/dist/lib/docs/openapi-writer.d.ts +49 -0
- package/dist/lib/docs/openapi-writer.d.ts.map +1 -0
- package/dist/lib/docs/openapi-writer.js +76 -0
- package/dist/lib/docs/public-api-schemas.d.ts +45 -0
- package/dist/lib/docs/public-api-schemas.d.ts.map +1 -0
- package/dist/lib/docs/public-api-schemas.js +377 -0
- package/dist/lib/docs/views-schemas.d.ts +22 -0
- package/dist/lib/docs/views-schemas.d.ts.map +1 -0
- package/dist/lib/docs/views-schemas.js +156 -0
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts +8 -2
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.js +19 -4
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts +2 -2
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.js +14 -4
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts +6 -8
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.js +10 -4
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts +2 -5
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts.map +1 -1
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.js +6 -1
- package/dist/lib/entries/domain/entry.d.ts +8 -1
- package/dist/lib/entries/domain/entry.d.ts.map +1 -1
- package/dist/lib/entries/domain/entry.js +12 -12
- package/dist/lib/entries/domain/events/entry-events.d.ts +47 -5
- package/dist/lib/entries/domain/events/entry-events.d.ts.map +1 -1
- package/dist/lib/entries/domain/events/entry-events.js +26 -10
- package/dist/lib/entries/entries.constants.d.ts +10 -2
- package/dist/lib/entries/entries.constants.d.ts.map +1 -1
- package/dist/lib/entries/entries.constants.js +11 -2
- package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts +2 -2
- package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts.map +1 -1
- package/dist/lib/entries/http/controllers/bulk-entries.controller.js +8 -6
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts +22 -6
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts.map +1 -1
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.js +71 -17
- package/dist/lib/insights/docs/describe-content-insights-api.d.ts +23 -0
- package/dist/lib/insights/docs/describe-content-insights-api.d.ts.map +1 -0
- package/dist/lib/insights/docs/describe-content-insights-api.js +329 -0
- package/dist/lib/mcp/content-tools.provider.d.ts.map +1 -1
- package/dist/lib/mcp/content-tools.provider.js +10 -9
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts +14 -13
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts.map +1 -1
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.js +62 -46
- package/dist/lib/public-api/http/guards/api-token-workspace.guard.d.ts.map +1 -1
- package/dist/lib/public-api/http/guards/api-token-workspace.guard.js +8 -0
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts +10 -9
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts.map +1 -1
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.js +23 -24
- package/dist/lib/public-api/infrastructure/token-actor.d.ts +37 -0
- package/dist/lib/public-api/infrastructure/token-actor.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/token-actor.js +58 -0
- package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts.map +1 -1
- package/dist/lib/revisions/application/use-cases/restore-revision.use-case.js +12 -1
- package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.d.ts +28 -0
- package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.d.ts.map +1 -0
- package/dist/lib/revisions/infrastructure/purge/revisions-workspace.purger.js +52 -0
- package/dist/lib/utils/content-plugin.d.ts.map +1 -1
- package/dist/lib/utils/content-plugin.js +10 -1
- package/dist/lib/utils/content-views-plugin.d.ts.map +1 -1
- package/dist/lib/utils/content-views-plugin.js +8 -0
- package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts +4 -1
- package/dist/lib/views/application/use-cases/create-saved-view.use-case.d.ts.map +1 -1
- package/dist/lib/views/application/use-cases/create-saved-view.use-case.js +33 -9
- package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts +4 -1
- package/dist/lib/views/application/use-cases/delete-saved-view.use-case.d.ts.map +1 -1
- package/dist/lib/views/application/use-cases/delete-saved-view.use-case.js +24 -3
- package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts +4 -1
- package/dist/lib/views/application/use-cases/update-saved-view.use-case.d.ts.map +1 -1
- package/dist/lib/views/application/use-cases/update-saved-view.use-case.js +32 -3
- package/dist/lib/views/domain/events/saved-view-events.d.ts +28 -0
- package/dist/lib/views/domain/events/saved-view-events.d.ts.map +1 -0
- package/dist/lib/views/domain/events/saved-view-events.js +38 -0
- package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts +12 -3
- package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.d.ts.map +1 -1
- package/dist/lib/views/infrastructure/persistence/drizzle-saved-view.repository.js +15 -5
- package/package.json +11 -11
- package/dist/lib/entries/controllers/bulk-entries.controller.d.ts +0 -26
- package/dist/lib/entries/controllers/bulk-entries.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/create-entry.controller.d.ts +0 -20
- package/dist/lib/entries/controllers/create-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/delete-entry.controller.d.ts +0 -24
- package/dist/lib/entries/controllers/delete-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/get-entry.controller.d.ts +0 -34
- package/dist/lib/entries/controllers/get-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/list-entries.controller.d.ts +0 -20
- package/dist/lib/entries/controllers/list-entries.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/publish-entry.controller.d.ts +0 -19
- package/dist/lib/entries/controllers/publish-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/controllers/resolve-type.d.ts +0 -9
- package/dist/lib/entries/controllers/resolve-type.d.ts.map +0 -1
- package/dist/lib/entries/controllers/update-entry.controller.d.ts +0 -18
- package/dist/lib/entries/controllers/update-entry.controller.d.ts.map +0 -1
- package/dist/lib/entries/dto/bulk-ids.dto.d.ts +0 -11
- package/dist/lib/entries/dto/bulk-ids.dto.d.ts.map +0 -1
- package/dist/lib/entries/dto/list-entries-query.dto.d.ts +0 -46
- package/dist/lib/entries/dto/list-entries-query.dto.d.ts.map +0 -1
- package/dist/lib/entries/dto/relation-delta-map.validator.d.ts +0 -19
- package/dist/lib/entries/dto/relation-delta-map.validator.d.ts.map +0 -1
- package/dist/lib/entries/dto/save-entry.dto.d.ts +0 -46
- package/dist/lib/entries/dto/save-entry.dto.d.ts.map +0 -1
- package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts +0 -24
- package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts.map +0 -1
- package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts +0 -40
- package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts.map +0 -1
- package/dist/lib/entries/services/entries.service.d.ts +0 -59
- package/dist/lib/entries/services/entries.service.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-counter.service.d.ts +0 -31
- package/dist/lib/entries/services/entry-counter.service.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-filter-schema.d.ts +0 -32
- package/dist/lib/entries/services/entry-filter-schema.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-row.d.ts +0 -56
- package/dist/lib/entries/services/entry-row.d.ts.map +0 -1
- package/dist/lib/entries/services/entry-writer.service.d.ts +0 -193
- package/dist/lib/entries/services/entry-writer.service.d.ts.map +0 -1
- package/dist/lib/entries/services/relation-link.service.d.ts +0 -115
- package/dist/lib/entries/services/relation-link.service.d.ts.map +0 -1
- package/dist/lib/extension/entry-access-source.d.ts +0 -112
- package/dist/lib/extension/entry-access-source.d.ts.map +0 -1
- package/dist/lib/extension/entry-write-hook.d.ts +0 -98
- package/dist/lib/extension/entry-write-hook.d.ts.map +0 -1
- package/dist/lib/extension/per-locale-relation.d.ts +0 -18
- package/dist/lib/extension/per-locale-relation.d.ts.map +0 -1
- package/dist/lib/public/controllers/get-public-entry.controller.d.ts +0 -26
- package/dist/lib/public/controllers/get-public-entry.controller.d.ts.map +0 -1
- package/dist/lib/public/controllers/list-public-entries.controller.d.ts +0 -24
- package/dist/lib/public/controllers/list-public-entries.controller.d.ts.map +0 -1
- package/dist/lib/public/controllers/public-schema.controller.d.ts +0 -18
- package/dist/lib/public/controllers/public-schema.controller.d.ts.map +0 -1
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure row ↔ entry mappers shared by the read ({@link EntriesService}) and write
|
|
3
|
-
* ({@link EntryWriterService}) paths, so the wire shape and the column projection
|
|
4
|
-
* have one definition each. No DB access, no NestJS — just shape translation.
|
|
5
|
-
*/
|
|
6
|
-
import type { AnyContentType } from '../../types/content-type';
|
|
7
|
-
import type { EntryRecord } from '../types/entry-list-view';
|
|
8
|
-
/** A generated content table row seen as a bag of values by property name. */
|
|
9
|
-
type Row = Record<string, unknown>;
|
|
10
|
-
/**
|
|
11
|
-
* Map a raw DB row to the admin {@link EntryRecord}: envelope fields plus a
|
|
12
|
-
* `values` bag keyed by field name. `status` is emitted only for publishable
|
|
13
|
-
* types. Relations that own no column — many-relations (their links live in join
|
|
14
|
-
* tables) and inverse/back-references (they reuse the owning side's storage) —
|
|
15
|
-
* aren't selected here; an owning single relation passes through as its FK uuid.
|
|
16
|
-
*/
|
|
17
|
-
export declare function toRecord(type: AnyContentType, row: Row): EntryRecord;
|
|
18
|
-
/**
|
|
19
|
-
* Normalize the admin's (string-shaped) `values` bag to the proper JS types the
|
|
20
|
-
* validator and storage expect — numbers parsed, JSON text parsed, empties
|
|
21
|
-
* collapsed to null — keeping every field (so a many-relation array still
|
|
22
|
-
* validates). The single coercion point: both {@link toColumns} (storage) and
|
|
23
|
-
* the writer's validation run against the result, so they can't disagree on a
|
|
24
|
-
* value's type. Datetime/date stay ISO strings (the validator accepts them;
|
|
25
|
-
* {@link toColumns} converts datetime to `Date` for `timestamptz`).
|
|
26
|
-
*/
|
|
27
|
-
export declare function coerceValues(type: AnyContentType, values: Record<string, unknown>): Record<string, unknown>;
|
|
28
|
-
/**
|
|
29
|
-
* Project a (coerced) `values` bag onto the columns of a generated table for an
|
|
30
|
-
* insert/update. Includes only keys declared on the type (reserved envelope
|
|
31
|
-
* columns — `status`/`published_at`/`deleted_at`/timestamps — are owned by the
|
|
32
|
-
* service, never the client), skips relations that own no column (many-relations,
|
|
33
|
-
* whose links live in join tables, and inverse/back-references, which reuse the
|
|
34
|
-
* owning side's storage), collapses empties to `null`, converts a `datetime` to a `Date` for
|
|
35
|
-
* the `timestamptz` column (guarding an invalid date to `null` rather than
|
|
36
|
-
* letting `pg` throw), and drops a `NaN` number to `null`. A field absent from
|
|
37
|
-
* `values` is written as `null` — the editor submits the full bag, so a write
|
|
38
|
-
* replaces the whole document.
|
|
39
|
-
*/
|
|
40
|
-
export declare function toColumns(type: AnyContentType, values: Record<string, unknown>): Record<string, unknown>;
|
|
41
|
-
/**
|
|
42
|
-
* A human display label for an entry — the first non-empty title-eligible field's
|
|
43
|
-
* value (see {@link TITLE_FIELD_TYPES}), falling back to the id. Used to label
|
|
44
|
-
* rows in the bulk-publish preview.
|
|
45
|
-
*/
|
|
46
|
-
export declare function entryTitle(type: AnyContentType, row: Row): string;
|
|
47
|
-
/**
|
|
48
|
-
* The slug handle for an entry — the value of its **slug field**: the first
|
|
49
|
-
* field flagged `admin.widget === 'slug'`, else a field literally named `slug`.
|
|
50
|
-
* Returns the trimmed value when present and non-empty, otherwise `undefined`
|
|
51
|
-
* (the type has no slug field, or the row's slug is blank). Used to give a
|
|
52
|
-
* linked relation record a stable `/handle` in the admin.
|
|
53
|
-
*/
|
|
54
|
-
export declare function entrySlug(type: AnyContentType, row: Row): string | undefined;
|
|
55
|
-
export {};
|
|
56
|
-
//# sourceMappingURL=entry-row.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entry-row.d.ts","sourceRoot":"","sources":["../../../../src/lib/entries/services/entry-row.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D,8EAA8E;AAC9E,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,WAAW,CAwBpE;AAKD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CACxB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACrB,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0BzB;AAWD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,CAOjE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAS5E"}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { type Database } from '@ortha-cms/database';
|
|
2
|
-
import { type ContentEntryExtension } from '../../extension/entry-extension';
|
|
3
|
-
import type { AnyContentType } from '../../types/content-type';
|
|
4
|
-
import { EntryValidationService } from '../../validation/services/entry-validation.service';
|
|
5
|
-
import type { EntryRecord, RelationDelta, RelationFieldView } from '../types/entry-list-view';
|
|
6
|
-
import { type BulkActionResult, type BulkPublishPreview, type BulkPublishResult } from '../types/bulk-publish';
|
|
7
|
-
import { RelationLinkService } from './relation-link.service';
|
|
8
|
-
/**
|
|
9
|
-
* The write half of the entries pipeline — create / read-one / update / publish
|
|
10
|
-
* / unpublish / delete / restore / purge, plus their bulk variants. Generic over
|
|
11
|
-
* the content type (like {@link EntriesService}): the physical table and its
|
|
12
|
-
* envelope columns are derived from `type` at request time, so one service backs
|
|
13
|
-
* every collection. {@link EntryValidationService} is the gate — nothing is
|
|
14
|
-
* written or published without passing it.
|
|
15
|
-
*/
|
|
16
|
-
export declare class EntryWriterService {
|
|
17
|
-
private readonly db;
|
|
18
|
-
private readonly validation;
|
|
19
|
-
private readonly relations;
|
|
20
|
-
private readonly extension?;
|
|
21
|
-
constructor(db: Database, validation: EntryValidationService, relations: RelationLinkService, extension?: ContentEntryExtension | undefined);
|
|
22
|
-
/**
|
|
23
|
-
* Create an entry **owned by `workspaceId`** (stamped onto the row so the
|
|
24
|
-
* reader's workspace filter and every later scoped write see it). A
|
|
25
|
-
* publishable type starts as a **draft** and may be incomplete — validation
|
|
26
|
-
* is deferred to publish. A non-publishable type is always live, so its
|
|
27
|
-
* values must validate now.
|
|
28
|
-
*
|
|
29
|
-
* The insert runs in a transaction that first takes the workspace's *shared*
|
|
30
|
-
* content lock ({@link lockWorkspaceShared}): it coordinates with the
|
|
31
|
-
* workspace delete / content-revoke guards (which take the lock exclusively),
|
|
32
|
-
* so a new entry can't land between their emptiness check and their mutation
|
|
33
|
-
* and be orphaned. Shared mode keeps concurrent creates non-blocking.
|
|
34
|
-
*
|
|
35
|
-
* `locale` / `localeGroupId` are opaque, extension-owned params: the bound
|
|
36
|
-
* extension validates them and stamps the envelope columns (a
|
|
37
|
-
* `localeGroupId` makes the row a **sibling** in that translation group). A
|
|
38
|
-
* duplicate `(group, locale)` surfaces as a **409** via {@link uniqueGuarded}.
|
|
39
|
-
*/
|
|
40
|
-
create(type: AnyContentType, values: Record<string, unknown>, workspaceId: string, relations?: Record<string, RelationDelta>, locale?: string, localeGroupId?: string): Promise<EntryRecord>;
|
|
41
|
-
/**
|
|
42
|
-
* First page (+ total) of every relation field's links for one live entry,
|
|
43
|
-
* keyed by field name — what the editor loads on open. Each field is
|
|
44
|
-
* paginated, so a relation with many links contributes only its first page.
|
|
45
|
-
* 404 if the entry is missing (or soft-deleted) in this workspace.
|
|
46
|
-
*/
|
|
47
|
-
getRelations(type: AnyContentType, id: string, workspaceId: string): Promise<Record<string, RelationFieldView>>;
|
|
48
|
-
/**
|
|
49
|
-
* One page of a single relation field's links (infinite-scroll for a
|
|
50
|
-
* many/inverse relation). 404 if the entry is missing; 400 if `field` isn't a
|
|
51
|
-
* relation on the type.
|
|
52
|
-
*/
|
|
53
|
-
getRelationField(type: AnyContentType, id: string, field: string, page: number, pageSize: number, workspaceId: string): Promise<RelationFieldView>;
|
|
54
|
-
/**
|
|
55
|
-
* Apply the staged per-field relation deltas of a save, inside the entry's
|
|
56
|
-
* transaction — so the row and every link change commit or roll back as one.
|
|
57
|
-
* Each field must be a **many/inverse** relation (a single relation is set
|
|
58
|
-
* through `values`); a single-relation or unknown key is a 400. `applyDelta`
|
|
59
|
-
* validates the linked ids against the workspace (422 on a bad target).
|
|
60
|
-
*/
|
|
61
|
-
private applyRelationDeltas;
|
|
62
|
-
/** Resolve a relation field spec on the type, or 400 if it isn't one. */
|
|
63
|
-
private relationSpec;
|
|
64
|
-
/** Read one live entry in the workspace, or 404. */
|
|
65
|
-
getOne(type: AnyContentType, id: string, workspaceId: string): Promise<EntryRecord>;
|
|
66
|
-
/**
|
|
67
|
-
* Replace a live entry's values, or 404. As with {@link create}, a
|
|
68
|
-
* publishable type's values aren't validated here (a draft may be saved
|
|
69
|
-
* incomplete; publish enforces the rules) — a non-publishable, always-live
|
|
70
|
-
* type validates now.
|
|
71
|
-
*/
|
|
72
|
-
update(type: AnyContentType, id: string, values: Record<string, unknown>, workspaceId: string, relations?: Record<string, RelationDelta>): Promise<EntryRecord>;
|
|
73
|
-
/** Validate the stored row, then mark it published (publishable types only). */
|
|
74
|
-
publish(type: AnyContentType, id: string, workspaceId: string): Promise<EntryRecord>;
|
|
75
|
-
/** Revert a live entry to draft (publishable types only). */
|
|
76
|
-
unpublish(type: AnyContentType, id: string, workspaceId: string): Promise<EntryRecord>;
|
|
77
|
-
/**
|
|
78
|
-
* Delete an entry: soft (stamp `deleted_at`) for a paranoid type, hard
|
|
79
|
-
* `DELETE` otherwise. 404 if there's no live row to remove **in this
|
|
80
|
-
* workspace** — an id from another workspace is indistinguishable from a
|
|
81
|
-
* missing one.
|
|
82
|
-
*/
|
|
83
|
-
remove(type: AnyContentType, id: string, workspaceId: string): Promise<void>;
|
|
84
|
-
/**
|
|
85
|
-
* Clear a soft-deleted entry's tombstone (paranoid types only), or 404.
|
|
86
|
-
* On an i18n type the restore can collide with a row created in the same
|
|
87
|
-
* (locale group, locale) slot after the soft delete — the partial unique
|
|
88
|
-
* index rejects it, surfaced as a 409 rather than a 500.
|
|
89
|
-
*/
|
|
90
|
-
restore(type: AnyContentType, id: string, workspaceId: string): Promise<EntryRecord>;
|
|
91
|
-
/** Permanently delete a tombstoned entry (paranoid types only), or 404. */
|
|
92
|
-
purge(type: AnyContentType, id: string, workspaceId: string): Promise<void>;
|
|
93
|
-
/**
|
|
94
|
-
* Dry-run a publish over a set of ids: validate each live row and report a
|
|
95
|
-
* verdict (will-publish / already-published / blocked / not-found) in
|
|
96
|
-
* request order. Writes nothing.
|
|
97
|
-
*/
|
|
98
|
-
previewBulkPublish(type: AnyContentType, ids: string[], workspaceId: string): Promise<BulkPublishPreview>;
|
|
99
|
-
/**
|
|
100
|
-
* Commit a bulk publish. The validation gate is **locked**: the candidate
|
|
101
|
-
* rows are selected `FOR UPDATE` and re-validated inside one transaction, so
|
|
102
|
-
* a concurrent edit can't invalidate a row between the dry run and the write
|
|
103
|
-
* (the single-statement preview-then-update split had that TOCTOU window).
|
|
104
|
-
* Publishes only the `publishable` ids, reporting which were skipped and why.
|
|
105
|
-
*/
|
|
106
|
-
bulkPublish(type: AnyContentType, ids: string[], workspaceId: string): Promise<BulkPublishResult>;
|
|
107
|
-
/**
|
|
108
|
-
* Compute the per-entry publish verdict (will-publish / already-published /
|
|
109
|
-
* blocked / not-found) for `ids` in request order, given the live rows keyed
|
|
110
|
-
* by id. Pure — the dry run and the committed {@link bulkPublish} share it so
|
|
111
|
-
* they can't disagree on what's publishable.
|
|
112
|
-
*/
|
|
113
|
-
private verdictsFor;
|
|
114
|
-
/**
|
|
115
|
-
* The per-field publish-gate checklist for one record: every required field
|
|
116
|
-
* plus any field that has an issue, each marked pass/fail. Mirrors the
|
|
117
|
-
* editor's Publish Gate so a row can show its passed fields, not just the
|
|
118
|
-
* failures.
|
|
119
|
-
*/
|
|
120
|
-
private buildChecks;
|
|
121
|
-
/** Revert a set of live entries to draft. */
|
|
122
|
-
bulkUnpublish(type: AnyContentType, ids: string[], workspaceId: string): Promise<BulkActionResult>;
|
|
123
|
-
/** Delete a set of entries: soft for paranoid types, hard otherwise. */
|
|
124
|
-
bulkRemove(type: AnyContentType, ids: string[], workspaceId: string): Promise<BulkActionResult>;
|
|
125
|
-
/** Permanently delete a set of tombstoned entries (paranoid types only). */
|
|
126
|
-
bulkPurge(type: AnyContentType, ids: string[], workspaceId: string): Promise<BulkActionResult>;
|
|
127
|
-
/**
|
|
128
|
-
* Restore a set of soft-deleted entries (paranoid types only). As with
|
|
129
|
-
* {@link restore}, a restored row colliding with a live sibling in its
|
|
130
|
-
* (locale group, locale) slot is a 409 — the whole batch rolls back
|
|
131
|
-
* (single statement), so the caller can retry without the conflicting id.
|
|
132
|
-
*/
|
|
133
|
-
bulkRestore(type: AnyContentType, ids: string[], workspaceId: string): Promise<BulkActionResult>;
|
|
134
|
-
/**
|
|
135
|
-
* Run a write, translating a Postgres unique violation (23505) into a 409.
|
|
136
|
-
* Only i18n types carry a restore-relevant unique index — the partial
|
|
137
|
-
* `(locale_group_id, locale)` one — so the mapping is scoped to them and
|
|
138
|
-
* any other type's violation still surfaces as the bug it is.
|
|
139
|
-
*/
|
|
140
|
-
private uniqueGuarded;
|
|
141
|
-
/** The type's generated table seen as a column bag (envelope + fields). */
|
|
142
|
-
private columns;
|
|
143
|
-
/**
|
|
144
|
-
* `workspace_id = :workspaceId` — the ownership predicate every read and
|
|
145
|
-
* write AND-s in, so a request scoped to one workspace can neither see nor
|
|
146
|
-
* mutate another's rows.
|
|
147
|
-
*/
|
|
148
|
-
private scope;
|
|
149
|
-
/**
|
|
150
|
-
* `id = :id` AND the workspace scope AND (for paranoid types)
|
|
151
|
-
* `deleted_at IS NULL`.
|
|
152
|
-
*/
|
|
153
|
-
private liveWhere;
|
|
154
|
-
/** Fetch one live row by id in the workspace, or undefined. */
|
|
155
|
-
private findLive;
|
|
156
|
-
/** Fetch the workspace's live rows for a set of ids, keyed by id. */
|
|
157
|
-
private loadLiveByIds;
|
|
158
|
-
/**
|
|
159
|
-
* Verify every referenced relation target exists **in the same workspace** —
|
|
160
|
-
* single FKs, many-to-many links, and the inverse (back-reference) side of a
|
|
161
|
-
* two-way relation. Neither the FK column nor the join table has a workspace
|
|
162
|
-
* constraint of its own (the target table is workspace-scoped only at the app
|
|
163
|
-
* layer), so without this a caller could reference — or probe the existence
|
|
164
|
-
* of — an entry in another workspace. Batched one existence query per
|
|
165
|
-
* referenced target type; a missing or cross-workspace target surfaces as a
|
|
166
|
-
* uniform 422 validation issue (indistinguishable from a plain "invalid id",
|
|
167
|
-
* so no not-found-vs-forbidden enumeration signal). Runs on every
|
|
168
|
-
* create/update, draft or not, because links are written eagerly either way.
|
|
169
|
-
* An inverse-of-single (one-to-many) field owns no writable link from this
|
|
170
|
-
* side, so it's skipped.
|
|
171
|
-
*/
|
|
172
|
-
private assertRelationTargets;
|
|
173
|
-
/**
|
|
174
|
-
* Enforce required **link-managed** relations (an owning many-to-many, or the
|
|
175
|
-
* inverse of one) against the entry's actual link set — the check
|
|
176
|
-
* {@link EntryValidationService} can't make, because those links never travel
|
|
177
|
-
* in the `values` bag it sees. A required such relation with zero links is a
|
|
178
|
-
* 422 `is required`, matching how a required scalar field reads. A single FK
|
|
179
|
-
* relation is validated in `values` already; an inverse-of-single owns no
|
|
180
|
-
* writable link from this side, so it's skipped (it can't be satisfied here).
|
|
181
|
-
* `exec` is the write transaction on create/update (so it counts the
|
|
182
|
-
* just-written, uncommitted rows) or the root client on publish (committed).
|
|
183
|
-
*/
|
|
184
|
-
private assertRequiredRelations;
|
|
185
|
-
/** Throw 422 with the issue list when the values fail validation. */
|
|
186
|
-
private assertValid;
|
|
187
|
-
/** Reject publish/unpublish on a type with no publish workflow. */
|
|
188
|
-
private assertPublishable;
|
|
189
|
-
/** Reject restore/purge on a type without soft delete. */
|
|
190
|
-
private assertParanoid;
|
|
191
|
-
private notFound;
|
|
192
|
-
}
|
|
193
|
-
//# sourceMappingURL=entry-writer.service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entry-writer.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/entries/services/entry-writer.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { type Database } from '@ortha-cms/database';
|
|
2
|
-
import type { AnyContentType } from '../../types/content-type';
|
|
3
|
-
import { type AnyFieldSpec } from '../../types/fields';
|
|
4
|
-
import type { RelationDelta, RelationFieldView } from '../types/entry-list-view';
|
|
5
|
-
/** A generated content row seen as a bag of values by property name. */
|
|
6
|
-
type Row = Record<string, unknown>;
|
|
7
|
-
/** Default links per page when the caller doesn't specify one. */
|
|
8
|
-
export declare const RELATION_PAGE_SIZE = 20;
|
|
9
|
-
/**
|
|
10
|
-
* The transaction handle Drizzle hands the `db.transaction(tx => …)` callback —
|
|
11
|
-
* derived from {@link Database} so link writes share the exact query surface of
|
|
12
|
-
* the client without hard-coding the dialect's transaction type.
|
|
13
|
-
*/
|
|
14
|
-
export type DbTransaction = Parameters<Parameters<Database['transaction']>[0]>[0];
|
|
15
|
-
/**
|
|
16
|
-
* Reads and writes the relation **links** an entry owns beyond its own columns:
|
|
17
|
-
* many-to-many join rows and the inverse (back-reference) side of a two-way
|
|
18
|
-
* relation. Owning **single** relations are plain `<field>_id` FK columns —
|
|
19
|
-
* written by `toColumns`, read off the row — so they need no join plumbing.
|
|
20
|
-
*
|
|
21
|
-
* Everything is **paginated**: a relation can hold thousands of links, so reads
|
|
22
|
-
* return one ordered page + a `total` (never every id), and writes apply an
|
|
23
|
-
* incremental {@link RelationDelta} (link / unlink / reorder) inside a
|
|
24
|
-
* transaction — so a huge relation never has to be sent or held in full.
|
|
25
|
-
* Order is persisted in the join table's `position` column, owned by the
|
|
26
|
-
* **source** side; the inverse reads by it (stable) but can't reorder it.
|
|
27
|
-
*/
|
|
28
|
-
export declare class RelationLinkService {
|
|
29
|
-
private readonly db;
|
|
30
|
-
constructor(db: Database);
|
|
31
|
-
/**
|
|
32
|
-
* First page (+ total) of **every** relation field of `type` for one entry,
|
|
33
|
-
* keyed by field name — what the editor loads on open. Each field is read
|
|
34
|
-
* independently (paginated), so a relation with many links contributes only
|
|
35
|
-
* its first page, not every id.
|
|
36
|
-
*/
|
|
37
|
-
readAll(type: AnyContentType, row: Row, workspaceId: string, pageSize?: number): Promise<Record<string, RelationFieldView>>;
|
|
38
|
-
/**
|
|
39
|
-
* One page of a single relation field's links, ordered by `position`
|
|
40
|
-
* (owning) and resolved to display-ready refs (id + title). `row` is the
|
|
41
|
-
* editing entry (its FK columns back a single relation); pass it to avoid a
|
|
42
|
-
* re-read. Returns `{ items, total }`.
|
|
43
|
-
*/
|
|
44
|
-
readField(type: AnyContentType, row: Row, field: string, spec: AnyFieldSpec, page: number, pageSize: number, workspaceId: string): Promise<RelationFieldView>;
|
|
45
|
-
/**
|
|
46
|
-
* Apply an incremental {@link RelationDelta} to one many/inverse relation
|
|
47
|
-
* field inside `tx`: validate the linked targets are in the workspace, unlink
|
|
48
|
-
* the removed pairs, append the new ones (`position = max+1` for the source,
|
|
49
|
-
* `ON CONFLICT DO NOTHING`), then renumber to `order` (owning side only).
|
|
50
|
-
* A single relation or an inverse-of-single owns no delta-writable link, so
|
|
51
|
-
* it's a no-op. Returns nothing — read the field back for the new state.
|
|
52
|
-
*/
|
|
53
|
-
applyDelta(tx: DbTransaction, type: AnyContentType, sourceId: string, field: string, delta: RelationDelta, workspaceId: string): Promise<void>;
|
|
54
|
-
/**
|
|
55
|
-
* Replace a many-relation's links with exactly `values[field]` (array),
|
|
56
|
-
* position = array index — the **whole-set** write used only when a caller
|
|
57
|
-
* genuinely submits a relation **array** in the entry body (legacy / bulk).
|
|
58
|
-
* The editor uses {@link applyDelta} instead. Runs inside the entry's
|
|
59
|
-
* transaction. **Only an array counts**: a field that's absent or `null`
|
|
60
|
-
* (`coerceValues` stamps `null` onto every unset field) is left untouched, so
|
|
61
|
-
* a save that manages a relation purely through deltas can never have its
|
|
62
|
-
* links cleared here.
|
|
63
|
-
*/
|
|
64
|
-
writeLinks(tx: DbTransaction, type: AnyContentType, sourceId: string, values: Record<string, unknown>, workspaceId: string): Promise<void>;
|
|
65
|
-
/**
|
|
66
|
-
* Count the links of one join-backed relation field for an entry — the
|
|
67
|
-
* `total` a read would report, without fetching or resolving any page. Runs
|
|
68
|
-
* on the passed executor (the write transaction when validating a just-saved
|
|
69
|
-
* entry, so it sees the delta's uncommitted rows). Returns 0 for a relation
|
|
70
|
-
* that owns no writable links from this side (single FK / inverse-of-single).
|
|
71
|
-
*/
|
|
72
|
-
countLinks(exec: Database | DbTransaction, type: AnyContentType, row: Row, field: string, spec: AnyFieldSpec, workspaceId: string): Promise<number>;
|
|
73
|
-
/**
|
|
74
|
-
* Take the transaction-scoped advisory lock for one physical source list
|
|
75
|
-
* (`<join table>:<sourceId>`), serializing every appender to that list so no
|
|
76
|
-
* two concurrent writes read the same `max(position)`. Auto-releases at
|
|
77
|
-
* commit/rollback. Keyed on the table name too, so distinct join tables that
|
|
78
|
-
* happen to share a source id don't falsely contend.
|
|
79
|
-
*/
|
|
80
|
-
private lockSource;
|
|
81
|
-
/** Next append position for a source: `max(position) + 1`, else 0. */
|
|
82
|
-
private nextPosition;
|
|
83
|
-
/**
|
|
84
|
-
* Resolve `ids` (in order) to display-ready refs via one lookup on the target
|
|
85
|
-
* type, scoped to the workspace. Ids with no **live** row drop to an id-only
|
|
86
|
-
* ref — a soft-deleted (paranoid) target is excluded here just as it is on
|
|
87
|
-
* the inverse read side, so a trashed record never surfaces its title on the
|
|
88
|
-
* owning side while it's hidden on the other.
|
|
89
|
-
*/
|
|
90
|
-
private refsFor;
|
|
91
|
-
/** Build a display ref from a full target row. */
|
|
92
|
-
private rowToRef;
|
|
93
|
-
/**
|
|
94
|
-
* Verify every id to be linked exists **in the same workspace** (the join FK
|
|
95
|
-
* has no workspace constraint of its own). A missing or cross-workspace id is
|
|
96
|
-
* a uniform 422 — indistinguishable from an invalid id, so no enumeration
|
|
97
|
-
* signal.
|
|
98
|
-
*/
|
|
99
|
-
private assertTargets;
|
|
100
|
-
/**
|
|
101
|
-
* The join-table plan for a relation field, or `null` when it owns no join
|
|
102
|
-
* table (an owning single relation, or an inverse-of-single). An owning
|
|
103
|
-
* many-relation uses its own join table (`source→target`); an inverse of a
|
|
104
|
-
* many-to-many reuses the owning side's join table with the roles swapped.
|
|
105
|
-
*/
|
|
106
|
-
private joinPlanFor;
|
|
107
|
-
/**
|
|
108
|
-
* The inverse-of-single (one-to-many) plan for a relation field, or `null`:
|
|
109
|
-
* it reads the owner rows whose FK points back at the editing record, and
|
|
110
|
-
* owns no writable link from this side.
|
|
111
|
-
*/
|
|
112
|
-
private inversePlanFor;
|
|
113
|
-
}
|
|
114
|
-
export {};
|
|
115
|
-
//# sourceMappingURL=relation-link.service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relation-link.service.d.ts","sourceRoot":"","sources":["../../../../src/lib/entries/services/relation-link.service.ts"],"names":[],"mappings":"AAeA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,KAAK,EACR,aAAa,EACb,iBAAiB,EAEpB,MAAM,0BAA0B,CAAC;AAMlC,wEAAwE;AACxE,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAUrC;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,CAClC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CACzC,CAAC,CAAC,CAAC,CAAC;AA0BL;;;;;;;;;;;;GAYG;AACH,qBACa,mBAAmB;IACE,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ;IAI3D;;;;;OAKG;IACG,OAAO,CACT,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,MAAM,EACnB,QAAQ,SAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAoB7C;;;;;OAKG;IACG,SAAS,CACX,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,iBAAiB,CAAC;IA0E7B;;;;;;;OAOG;IACG,UAAU,CACZ,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,aAAa,EACpB,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC;IA+FhB;;;;;;;;;OASG;IACG,UAAU,CACZ,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC;IAwChB;;;;;;OAMG;IACG,UAAU,CACZ,IAAI,EAAE,QAAQ,GAAG,aAAa,EAC9B,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC;IA8BlB;;;;;;OAMG;YACW,UAAU;IAWxB,sEAAsE;YACxD,YAAY;IAc1B;;;;;;OAMG;YACW,OAAO;IAuBrB,kDAAkD;IAClD,OAAO,CAAC,QAAQ;IAWhB;;;;;OAKG;YACW,aAAa;IAmC3B;;;;;OAKG;IACH,OAAO,CAAC,WAAW;IA8BnB;;;;OAIG;IACH,OAAO,CAAC,cAAc;CAYzB"}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The **entry-access description port** — the seam a downstream plugin uses to
|
|
3
|
-
* tell a protocol adapter *that* an entry it just served is restricted, without
|
|
4
|
-
* this package knowing what a restriction is.
|
|
5
|
-
*
|
|
6
|
-
* It is the read scope's opposite number and deliberately not the same thing.
|
|
7
|
-
* `CONTENT_READ_SCOPE` decides which rows a reader gets; this describes the rows
|
|
8
|
-
* they got. A scope can only subtract, so by the time an annotation is asked
|
|
9
|
-
* for, the entry has already been admitted — which is why nothing here can
|
|
10
|
-
* refuse anything, and why an implementation that returned nothing would be a
|
|
11
|
-
* missing label rather than a leak.
|
|
12
|
-
*
|
|
13
|
-
* ## What it is for
|
|
14
|
-
*
|
|
15
|
-
* One thing, and it is worth being blunt about it: **a restricted entry must not
|
|
16
|
-
* be cached as public.** A CDN, a shared HTTP cache or a client store that keeps
|
|
17
|
-
* one reader's copy and hands it to the next turns a working entitlement into a
|
|
18
|
-
* data leak, and nothing else in the response says the entry was reader-scoped —
|
|
19
|
-
* it looks exactly like an open one. Everything else an implementation could
|
|
20
|
-
* report (who exactly may read it, which segments) is a question for the admin
|
|
21
|
-
* API, where the caller is a member of the workspace rather than a consumer of
|
|
22
|
-
* its content.
|
|
23
|
-
*
|
|
24
|
-
* ## Why a registry rather than a DI token
|
|
25
|
-
*
|
|
26
|
-
* The same reason the read scope uses one: Nest has **no multi-provider**, so
|
|
27
|
-
* two dynamic modules binding one token do not merge — the second silently
|
|
28
|
-
* replaces the first. Here that would mean an entry that one plugin knows is
|
|
29
|
-
* restricted reported as open because a second plugin was registered after it,
|
|
30
|
-
* which is precisely the mislabelling this port exists to prevent. Registration
|
|
31
|
-
* is a runtime `register(...)` call, the same shape as
|
|
32
|
-
* {@link contentReadScopeRegistrar}.
|
|
33
|
-
*
|
|
34
|
-
* ## The batching constraint
|
|
35
|
-
*
|
|
36
|
-
* `describe` takes the whole page of entry ids, not one. A protocol adapter
|
|
37
|
-
* resolves a list and then labels it; asking per entry would be a query per row
|
|
38
|
-
* on a path whose cost is otherwise one page read. An implementation that cannot
|
|
39
|
-
* answer for an id simply leaves it out of the map, and the caller reads a
|
|
40
|
-
* missing key as unrestricted — the same default absence carries everywhere
|
|
41
|
-
* else in this feature.
|
|
42
|
-
*/
|
|
43
|
-
import { type Provider, type Type } from '@nestjs/common';
|
|
44
|
-
/** What one entry's restriction looks like from outside the plugin that owns it. */
|
|
45
|
-
export interface EntryAccessDescription {
|
|
46
|
-
/**
|
|
47
|
-
* Whether anything narrowed who may read this entry.
|
|
48
|
-
*
|
|
49
|
-
* `true` means the response is reader-specific and must not be stored in a
|
|
50
|
-
* shared cache without varying on whatever identifies the reader.
|
|
51
|
-
*/
|
|
52
|
-
readonly restricted: boolean;
|
|
53
|
-
/**
|
|
54
|
-
* The axes that took part, as opaque keys (`org`, `plan`). A hint for a
|
|
55
|
-
* client deciding what to vary its own cache on — never the segments
|
|
56
|
-
* themselves, which would tell a consumer who *else* may read the entry.
|
|
57
|
-
*/
|
|
58
|
-
readonly dimensions: readonly string[];
|
|
59
|
-
}
|
|
60
|
-
/** What a source is told about the page it is describing. */
|
|
61
|
-
export interface EntryAccessQuery {
|
|
62
|
-
/** The workspace the entries were read in. */
|
|
63
|
-
readonly workspaceId: string;
|
|
64
|
-
/** The ids to describe. Never empty — the caller skips an empty page. */
|
|
65
|
-
readonly entryIds: readonly string[];
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* One contributed description of what was served.
|
|
69
|
-
*
|
|
70
|
-
* Returning an empty map means "nothing to say about this page", which is what
|
|
71
|
-
* an installed-but-unconfigured plugin returns and what every entry defaults to.
|
|
72
|
-
*/
|
|
73
|
-
export interface EntryAccessSource {
|
|
74
|
-
/** Describes the given entries, keyed by entry id. Ids may be omitted. */
|
|
75
|
-
describe(query: EntryAccessQuery): Promise<ReadonlyMap<string, EntryAccessDescription>>;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Every registered source, merged.
|
|
79
|
-
*
|
|
80
|
-
* The merge is a **union that can only tighten**: an entry is restricted if any
|
|
81
|
-
* source says so, and the dimensions are the union of what each reported. Two
|
|
82
|
-
* plugins narrowing reads for different reasons both need to be heard, and the
|
|
83
|
-
* safe direction for a disagreement is the one that keeps a shared cache from
|
|
84
|
-
* storing the entry.
|
|
85
|
-
*/
|
|
86
|
-
export declare class EntryAccessSourceRegistry {
|
|
87
|
-
private readonly sources;
|
|
88
|
-
/** Adds a source. Registering the same instance twice is a no-op. */
|
|
89
|
-
register(source: EntryAccessSource): void;
|
|
90
|
-
/** Whether anything is registered — the cheap check before building a page. */
|
|
91
|
-
get active(): boolean;
|
|
92
|
-
/**
|
|
93
|
-
* Describes one page of entries.
|
|
94
|
-
*
|
|
95
|
-
* Returns an empty map when nothing is registered or the page is empty, so
|
|
96
|
-
* a caller on an installation with no scoping plugin does no work and every
|
|
97
|
-
* entry reads as unrestricted.
|
|
98
|
-
*/
|
|
99
|
-
describe(query: EntryAccessQuery): Promise<ReadonlyMap<string, EntryAccessDescription>>;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Builds the DI provider that registers `sources` with content's access-source
|
|
103
|
-
* registry at bootstrap.
|
|
104
|
-
*
|
|
105
|
-
* A factory with an explicit `inject` list rather than a class with reflected
|
|
106
|
-
* parameters, for the reason `contentReadScopeRegistrar` documents: an optional
|
|
107
|
-
* dependency typed as `Foo | null` emits `Object` for `design:paramtypes`, Nest
|
|
108
|
-
* injects `undefined` with no error, and the plugin ends up registering
|
|
109
|
-
* nothing.
|
|
110
|
-
*/
|
|
111
|
-
export declare function entryAccessSourceRegistrar(label: string, ...sources: Type<EntryAccessSource>[]): Provider;
|
|
112
|
-
//# sourceMappingURL=entry-access-source.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entry-access-source.d.ts","sourceRoot":"","sources":["../../../src/lib/extension/entry-access-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,OAAO,EAIH,KAAK,QAAQ,EACb,KAAK,IAAI,EACZ,MAAM,gBAAgB,CAAC;AAExB,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAC9B,0EAA0E;IAC1E,QAAQ,CACJ,KAAK,EAAE,gBAAgB,GACxB,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC;CAC3D;AAED;;;;;;;;GAQG;AACH,qBACa,yBAAyB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IAEnD,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAMzC,+EAA+E;IAC/E,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED;;;;;;OAMG;IACG,QAAQ,CACV,KAAK,EAAE,gBAAgB,GACxB,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;CAgC1D;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACtC,KAAK,EAAE,MAAM,EACb,GAAG,OAAO,EAAE,IAAI,CAAC,iBAAiB,CAAC,EAAE,GACtC,QAAQ,CAYV"}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The **entry write hook** — the seam a downstream plugin uses to do work
|
|
3
|
-
* *inside* an entry's write transaction.
|
|
4
|
-
*
|
|
5
|
-
* The read-scope port's sibling, and it exists for the same reason the read one
|
|
6
|
-
* does: a plugin that derives something from an entry has to derive it where the
|
|
7
|
-
* entry is written, or the two can disagree. `@orthacms/segments-server` uses it
|
|
8
|
-
* to write an entry's access projection, and the window between "the entry is
|
|
9
|
-
* live" and "the rule that hides it is recorded" is exactly the window in which
|
|
10
|
-
* restricted content is public.
|
|
11
|
-
*
|
|
12
|
-
* ## Why not `CONTENT_ENTRY_EXTENSION.afterUpdate`
|
|
13
|
-
*
|
|
14
|
-
* That hook does precisely this job and would need no new code — except that the
|
|
15
|
-
* port is documented as a **single binding**, one provider per app, and i18n
|
|
16
|
-
* holds it. Its own note suggests a composite for a second consumer, which is
|
|
17
|
-
* fine when the two are peers; here they are not. i18n's implementation returns
|
|
18
|
-
* the sibling rows it rewrote, and the writer snapshots a revision for each; a
|
|
19
|
-
* composite would have to merge that return value with a second implementation
|
|
20
|
-
* that has no opinion about it, and a mistake there writes the wrong revision
|
|
21
|
-
* history. This port returns nothing and cannot participate in that decision.
|
|
22
|
-
*
|
|
23
|
-
* ## The contract
|
|
24
|
-
*
|
|
25
|
-
* **Runs in the write's own transaction.** The executor handed to the hook is
|
|
26
|
-
* the transaction the row was written in, and a throw rolls the write back with
|
|
27
|
-
* it. That is the guarantee the hook exists for: an entry and whatever a plugin
|
|
28
|
-
* derives from it commit together or not at all.
|
|
29
|
-
*
|
|
30
|
-
* **Runs after the row and its relations are written**, so the hook sees the
|
|
31
|
-
* entry in its final state — including any columns the entries extension
|
|
32
|
-
* stamped or rewrote.
|
|
33
|
-
*
|
|
34
|
-
* **Must be cheap.** It is on the write path of every create and update, and it
|
|
35
|
-
* holds the entry's advisory lock while it runs. Work that can wait for a
|
|
36
|
-
* commit does not belong here; work that must not be observed separately from
|
|
37
|
-
* the write does.
|
|
38
|
-
*
|
|
39
|
-
* **Deletes are not hooked.** A hard-deleted entry's derived rows are
|
|
40
|
-
* unreachable — nothing joins to an id that no longer exists — so the cost of
|
|
41
|
-
* leaving them is disk rather than correctness, and paying for a hook on every
|
|
42
|
-
* delete to reclaim it is the wrong trade. A plugin that cares reclaims them on
|
|
43
|
-
* its own schedule.
|
|
44
|
-
*/
|
|
45
|
-
import { type Provider, type Type } from '@nestjs/common';
|
|
46
|
-
import type { Database } from '@orthacms/database';
|
|
47
|
-
import type { AnyContentType } from '../types/content-type';
|
|
48
|
-
/** The executor the hook must use — the write's own transaction. */
|
|
49
|
-
export type EntryWriteExecutor = Parameters<Parameters<Database['transaction']>[0]>[0];
|
|
50
|
-
/** What a hook is told about the write it is running inside. */
|
|
51
|
-
export interface ContentEntryWriteContext {
|
|
52
|
-
/** The write's transaction. Use this, never the shared connection. */
|
|
53
|
-
readonly tx: EntryWriteExecutor;
|
|
54
|
-
/** The content type written. */
|
|
55
|
-
readonly type: AnyContentType;
|
|
56
|
-
/** The entry's id. */
|
|
57
|
-
readonly entryId: string;
|
|
58
|
-
/** The workspace the entry belongs to. */
|
|
59
|
-
readonly workspaceId: string;
|
|
60
|
-
/** True when the row was just INSERTed; false on an update. */
|
|
61
|
-
readonly created: boolean;
|
|
62
|
-
}
|
|
63
|
-
/** One plugin's participation in an entry write. */
|
|
64
|
-
export interface ContentEntryWriteHook {
|
|
65
|
-
/**
|
|
66
|
-
* Runs inside the write transaction, after the row and its relations.
|
|
67
|
-
*
|
|
68
|
-
* A throw rolls the entry write back. That is deliberate: a hook whose
|
|
69
|
-
* failure is swallowed would let the entry commit without whatever the hook
|
|
70
|
-
* was meant to derive from it.
|
|
71
|
-
*/
|
|
72
|
-
afterWrite(context: ContentEntryWriteContext): Promise<void>;
|
|
73
|
-
}
|
|
74
|
-
/** Every registered write hook, in registration order. */
|
|
75
|
-
export declare class ContentEntryWriteHookRegistry {
|
|
76
|
-
private readonly hooks;
|
|
77
|
-
/** Adds a hook. Registering the same instance twice is a no-op. */
|
|
78
|
-
register(hook: ContentEntryWriteHook): void;
|
|
79
|
-
/** Every registered hook. Empty on an installation with none. */
|
|
80
|
-
all(): readonly ContentEntryWriteHook[];
|
|
81
|
-
/**
|
|
82
|
-
* Runs every hook, in order, inside the caller's transaction.
|
|
83
|
-
*
|
|
84
|
-
* Sequential rather than concurrent: the hooks share one transaction, and a
|
|
85
|
-
* Postgres connection carries one statement at a time — firing them at once
|
|
86
|
-
* would only interleave their awaits onto the same connection.
|
|
87
|
-
*/
|
|
88
|
-
run(context: ContentEntryWriteContext): Promise<void>;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Builds the DI provider that registers `hooks` with content's write-hook
|
|
92
|
-
* registry at bootstrap. The sibling of `contentReadScopeRegistrar`, and a
|
|
93
|
-
* factory with an explicit `inject` list for the same reason: a reflected
|
|
94
|
-
* `Foo | null` parameter type emits `Object`, Nest injects `undefined`, and the
|
|
95
|
-
* plugin silently registers nothing.
|
|
96
|
-
*/
|
|
97
|
-
export declare function contentEntryWriteHookRegistrar(label: string, ...hooks: Type<ContentEntryWriteHook>[]): Provider;
|
|
98
|
-
//# sourceMappingURL=entry-write-hook.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"entry-write-hook.d.ts","sourceRoot":"","sources":["../../../src/lib/extension/entry-write-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAIH,KAAK,QAAQ,EACb,KAAK,IAAI,EACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAAG,UAAU,CACvC,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CACzC,CAAC,CAAC,CAAC,CAAC;AAEL,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACrC,sEAAsE;IACtE,QAAQ,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAChC,gCAAgC;IAChC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,sBAAsB;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC7B;AAED,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IAClC;;;;;;OAMG;IACH,UAAU,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAED,0DAA0D;AAC1D,qBACa,6BAA6B;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IAErD,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,GAAG,IAAI;IAM3C,iEAAiE;IACjE,GAAG,IAAI,SAAS,qBAAqB,EAAE;IAIvC;;;;;;OAMG;IACG,GAAG,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAK9D;AAyBD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC1C,KAAK,EAAE,MAAM,EACb,GAAG,KAAK,EAAE,IAAI,CAAC,qBAAqB,CAAC,EAAE,GACxC,QAAQ,CAYV"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { type AnyFieldSpec } from '../types/fields';
|
|
2
|
-
import type { AnyContentType } from '../types/content-type';
|
|
3
|
-
/**
|
|
4
|
-
* Whether a field is a **per-locale relation** — a *single owning* relation
|
|
5
|
-
* (`!many`, `!inverse`) on an **i18n** owner whose **target type is also i18n**.
|
|
6
|
-
*
|
|
7
|
-
* Such a relation can't be shared across a translation group: a shared FK would
|
|
8
|
-
* point at one specific locale's row, i.e. a cross-locale link. So the platform
|
|
9
|
-
* treats it as `localized` — not synced onto siblings, not copied into a
|
|
10
|
-
* translation draft, and the picker offers only same-locale candidates. Both the
|
|
11
|
-
* schema serializer (which stamps `localized` for the admin) and the i18n
|
|
12
|
-
* sibling-sync consult this, so the rule lives in one place.
|
|
13
|
-
*
|
|
14
|
-
* The target's i18n-ness is read via the resolved thunk `spec.relation.to()` —
|
|
15
|
-
* safe because the registry resolves every relation target at boot.
|
|
16
|
-
*/
|
|
17
|
-
export declare function isPerLocaleRelation(owner: AnyContentType, spec: AnyFieldSpec): boolean;
|
|
18
|
-
//# sourceMappingURL=per-locale-relation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"per-locale-relation.d.ts","sourceRoot":"","sources":["../../../src/lib/extension/per-locale-relation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,YAAY,GACnB,OAAO,CAQT"}
|