@nest-my-admin/core 0.2.1 → 0.3.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/README.md +75 -0
- package/dist/api/admin-api.service.d.ts +20 -0
- package/dist/api/admin-api.service.js +132 -21
- package/dist/api/admin-api.service.js.map +1 -1
- package/dist/api/write-participant.d.ts +5 -0
- package/dist/audit/audit-participant.js +4 -2
- package/dist/audit/audit-participant.js.map +1 -1
- package/dist/audit/audit-reader.d.ts +2 -0
- package/dist/audit/audit-reader.js +9 -1
- package/dist/audit/audit-reader.js.map +1 -1
- package/dist/audit/entities.d.ts +5 -0
- package/dist/audit/entities.js.map +1 -1
- package/dist/contract.d.ts +76 -4
- package/dist/crud/validate-write.d.ts +2 -0
- package/dist/crud/validate-write.js +2 -0
- package/dist/crud/validate-write.js.map +1 -1
- package/dist/files/admin-files.service.d.ts +19 -1
- package/dist/files/admin-files.service.js +20 -0
- package/dist/files/admin-files.service.js.map +1 -1
- package/dist/files/files.service.d.ts +42 -1
- package/dist/files/files.service.js +127 -0
- package/dist/files/files.service.js.map +1 -1
- package/dist/files/transaction-files.d.ts +25 -0
- package/dist/files/transaction-files.js +63 -0
- package/dist/files/transaction-files.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js.map +1 -1
- package/dist/inlines/inline-config.d.ts +36 -0
- package/dist/inlines/inline-config.js +130 -0
- package/dist/inlines/inline-config.js.map +1 -0
- package/dist/inlines/inline-reader.d.ts +38 -0
- package/dist/inlines/inline-reader.js +84 -0
- package/dist/inlines/inline-reader.js.map +1 -0
- package/dist/inlines/inline-writer.d.ts +102 -0
- package/dist/inlines/inline-writer.js +545 -0
- package/dist/inlines/inline-writer.js.map +1 -0
- package/dist/policy/admin-policy.service.d.ts +12 -0
- package/dist/policy/admin-policy.service.js +44 -5
- package/dist/policy/admin-policy.service.js.map +1 -1
- package/dist/registry/resource-registry.d.ts +3 -0
- package/dist/registry/resource-registry.js +10 -2
- package/dist/registry/resource-registry.js.map +1 -1
- package/dist/schema/build-resource-schema.js +1 -1
- package/dist/schema/build-resource-schema.js.map +1 -1
- package/dist/schema/field-config.d.ts +52 -6
- package/dist/schema/field-config.js +89 -11
- package/dist/schema/field-config.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AdminRecord } from '../contract.js';
|
|
2
|
+
import type { AdminPolicy } from '../policy/admin-policy.service.js';
|
|
3
|
+
import type { RegisteredResource } from '../registry/resource-registry.js';
|
|
4
|
+
import type { AdminContext } from '../resource/admin-context.js';
|
|
5
|
+
export interface InlineReaderDeps {
|
|
6
|
+
policy: AdminPolicy;
|
|
7
|
+
/** Serializes the child's entities exactly as its own record endpoint does (fields cut, `_perm`, `_files`…). */
|
|
8
|
+
serialize: (entry: RegisteredResource, entities: object[], ctx: AdminContext) => Promise<AdminRecord[]>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* `_inlines` of a record: for each inline the user may see (`AdminPolicy.canSeeInline`: the child's `view` and its link
|
|
12
|
+
* field), the children of this record read through the CHILD resource's own `findMany` (so its `query()`, the user's
|
|
13
|
+
* scopes there and any host override apply, as on the child's list), filtered by the link and sorted by `order` then
|
|
14
|
+
* key, and serialized like the child's records.
|
|
15
|
+
*/
|
|
16
|
+
export declare class InlineReader {
|
|
17
|
+
private readonly deps;
|
|
18
|
+
constructor(deps: InlineReaderDeps);
|
|
19
|
+
/** Sets `record._inlines`, unless the parent has no inline this user may see (then the key stays absent). */
|
|
20
|
+
readInto(record: AdminRecord, parent: RegisteredResource, entity: object, ctx: AdminContext): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* The default `findMany` gets one page of `max` rows, uncounted (it reads `max + 1` rows to know whether more exist),
|
|
23
|
+
* so an inline costs one query for its rows plus the serializer's lookups (one per relation field and one for files,
|
|
24
|
+
* whatever the number of rows); a truncated inline adds one count query. A child that overrides `findMany` runs its
|
|
25
|
+
* own queries, and its rows are re-checked in one more query (see `overridden`).
|
|
26
|
+
*/
|
|
27
|
+
private rows;
|
|
28
|
+
/**
|
|
29
|
+
* A `findMany` override may ignore the link filter and the scopes: its rows are kept only when they are children of
|
|
30
|
+
* THIS parent inside the user's view scopes and the child's `query()` (one query, `children()` with their keys), in
|
|
31
|
+
* the override's order. `truncated` and `total` come after that re-check: the override's own `hasMore`/`total` count
|
|
32
|
+
* rows it may not have filtered, so when it says there are more (or more than `max` remain), the children are
|
|
33
|
+
* counted with `children()` instead of trusting it.
|
|
34
|
+
*/
|
|
35
|
+
private overridden;
|
|
36
|
+
/** This parent's children as the user may view them: the child's `query()`, the user's view scopes, the link. */
|
|
37
|
+
private children;
|
|
38
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { recordIdOf } from '../crud/record-id.js';
|
|
2
|
+
import { AdminResourceBase } from '../resource/admin-resource-base.js';
|
|
3
|
+
/**
|
|
4
|
+
* `_inlines` of a record: for each inline the user may see (`AdminPolicy.canSeeInline`: the child's `view` and its link
|
|
5
|
+
* field), the children of this record read through the CHILD resource's own `findMany` (so its `query()`, the user's
|
|
6
|
+
* scopes there and any host override apply, as on the child's list), filtered by the link and sorted by `order` then
|
|
7
|
+
* key, and serialized like the child's records.
|
|
8
|
+
*/
|
|
9
|
+
export class InlineReader {
|
|
10
|
+
deps;
|
|
11
|
+
constructor(deps) {
|
|
12
|
+
this.deps = deps;
|
|
13
|
+
}
|
|
14
|
+
/** Sets `record._inlines`, unless the parent has no inline this user may see (then the key stays absent). */
|
|
15
|
+
async readInto(record, parent, entity, ctx) {
|
|
16
|
+
const out = {};
|
|
17
|
+
for (const inline of parent.inlines) {
|
|
18
|
+
if (this.deps.policy.canSeeInline(inline.child, inline.link, ctx))
|
|
19
|
+
out[inline.name] = await this.rows(inline, entity, ctx);
|
|
20
|
+
}
|
|
21
|
+
if (Object.keys(out).length > 0)
|
|
22
|
+
record._inlines = out;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The default `findMany` gets one page of `max` rows, uncounted (it reads `max + 1` rows to know whether more exist),
|
|
26
|
+
* so an inline costs one query for its rows plus the serializer's lookups (one per relation field and one for files,
|
|
27
|
+
* whatever the number of rows); a truncated inline adds one count query. A child that overrides `findMany` runs its
|
|
28
|
+
* own queries, and its rows are re-checked in one more query (see `overridden`).
|
|
29
|
+
*/
|
|
30
|
+
async rows(inline, parent, ctx) {
|
|
31
|
+
const { child } = inline;
|
|
32
|
+
const { policy } = this.deps;
|
|
33
|
+
const value = parent[inline.referenced];
|
|
34
|
+
if (value === null || value === undefined)
|
|
35
|
+
return { rows: [], total: 0, truncated: false };
|
|
36
|
+
// Sorting by a field the user cannot see would reveal its values (the child's list refuses it too): by key then.
|
|
37
|
+
const order = inline.order !== undefined && (policy.levels(child, ctx).get(inline.order) ?? 'hidden') !== 'hidden' ? inline.order : undefined;
|
|
38
|
+
const params = {
|
|
39
|
+
page: 1,
|
|
40
|
+
pageSize: inline.max,
|
|
41
|
+
sort: { field: order ?? child.schema.primaryKeys[0], direction: 'asc' },
|
|
42
|
+
filters: [{ field: inline.link, operator: 'eq', value: value }],
|
|
43
|
+
count: 'none',
|
|
44
|
+
pagination: 'offset',
|
|
45
|
+
};
|
|
46
|
+
const found = await child.resource.findMany(params, ctx);
|
|
47
|
+
if (child.resource.findMany !== AdminResourceBase.prototype.findMany)
|
|
48
|
+
return this.overridden(inline, value, found, ctx);
|
|
49
|
+
const truncated = found.hasMore === true || found.items.length > inline.max;
|
|
50
|
+
const items = found.items.slice(0, inline.max);
|
|
51
|
+
const total = truncated ? await this.children(inline, value, ctx).getCount() : items.length;
|
|
52
|
+
return { rows: await this.deps.serialize(child, items, ctx), total, truncated };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A `findMany` override may ignore the link filter and the scopes: its rows are kept only when they are children of
|
|
56
|
+
* THIS parent inside the user's view scopes and the child's `query()` (one query, `children()` with their keys), in
|
|
57
|
+
* the override's order. `truncated` and `total` come after that re-check: the override's own `hasMore`/`total` count
|
|
58
|
+
* rows it may not have filtered, so when it says there are more (or more than `max` remain), the children are
|
|
59
|
+
* counted with `children()` instead of trusting it.
|
|
60
|
+
*/
|
|
61
|
+
async overridden(inline, value, found, ctx) {
|
|
62
|
+
const { child } = inline;
|
|
63
|
+
const keys = child.schema.primaryKeys;
|
|
64
|
+
const idOf = (item) => (keys.length === 1 ? item[keys[0]] : Object.fromEntries(keys.map((key) => [key, item[key]])));
|
|
65
|
+
let items = found.items;
|
|
66
|
+
if (items.length > 0) {
|
|
67
|
+
const kept = await this.children(inline, value, ctx).andWhereInIds(items.map(idOf)).select(keys.map((key) => `nma_inline.${key}`)).getMany();
|
|
68
|
+
const allowed = new Set(kept.map((row) => recordIdOf(row, keys)));
|
|
69
|
+
items = items.filter((item) => allowed.has(recordIdOf(item, keys)));
|
|
70
|
+
}
|
|
71
|
+
const more = found.hasMore === true || (found.total ?? 0) > found.items.length;
|
|
72
|
+
const total = more || items.length > inline.max ? await this.children(inline, value, ctx).getCount() : items.length;
|
|
73
|
+
const rows = items.slice(0, inline.max);
|
|
74
|
+
return { rows: await this.deps.serialize(child, rows, ctx), total, truncated: total > rows.length };
|
|
75
|
+
}
|
|
76
|
+
/** This parent's children as the user may view them: the child's `query()`, the user's view scopes, the link. */
|
|
77
|
+
children(inline, value, ctx) {
|
|
78
|
+
const { child } = inline;
|
|
79
|
+
const manager = ctx.manager ?? child.dataSource.manager;
|
|
80
|
+
const qb = manager.getRepository(child.metadata.target).createQueryBuilder('nma_inline');
|
|
81
|
+
return this.deps.policy.applyScopes(child.resource.query(qb, ctx), child, 'view', ctx).andWhere(`nma_inline.${inline.link} = :nmaInlineParent`, { nmaInlineParent: value });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=inline-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-reader.js","sourceRoot":"","sources":["../../src/inlines/inline-reader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type RelationId } from '../crud/relation-writes.js';
|
|
2
|
+
import type { ErrorMapper } from '../options.js';
|
|
3
|
+
import type { AdminPolicy, ResourceView } from '../policy/admin-policy.service.js';
|
|
4
|
+
import type { RegisteredResource } from '../registry/resource-registry.js';
|
|
5
|
+
import type { AdminContext } from '../resource/admin-context.js';
|
|
6
|
+
import type { RecordId } from '../resource/admin-resource-base.js';
|
|
7
|
+
import type { WriteChange } from '../api/write-participant.js';
|
|
8
|
+
import type { RegisteredInline } from './inline-config.js';
|
|
9
|
+
/** What the writer needs of `AdminApiService` (its write participants, checks and serializer). */
|
|
10
|
+
export interface InlineWriterDeps {
|
|
11
|
+
policy: AdminPolicy;
|
|
12
|
+
view: (entry: RegisteredResource, ctx: AdminContext) => ResourceView;
|
|
13
|
+
sanitizeHtml: (html: string) => string;
|
|
14
|
+
/** One change inside the parent's write, with every participant (files, audit) around the method. */
|
|
15
|
+
change: <T>(change: WriteChange, tx: AdminContext, method: () => Promise<T>) => Promise<T>;
|
|
16
|
+
checkRelationsExist: (entry: RegisteredResource, ids: Map<string, RelationId[]>, ctx: AdminContext, values: Record<string, unknown>) => Promise<void>;
|
|
17
|
+
checkVersion: (entry: RegisteredResource, id: RecordId, expected: number | undefined, ctx: AdminContext) => Promise<void>;
|
|
18
|
+
lockedFields: (entry: RegisteredResource, entity: object) => string[];
|
|
19
|
+
/** A record's values as `relationOptions()` sees them (as the user sees the record, relations as ids). */
|
|
20
|
+
values: (entry: RegisteredResource, entity: object, ctx: AdminContext) => Promise<Record<string, unknown>>;
|
|
21
|
+
reloadIfEmpty: (entry: RegisteredResource, result: unknown, id: RecordId, ctx: AdminContext) => Promise<object>;
|
|
22
|
+
errorMapper?: ErrorMapper;
|
|
23
|
+
}
|
|
24
|
+
interface PlannedCreate {
|
|
25
|
+
key: string;
|
|
26
|
+
index: number;
|
|
27
|
+
dto: Record<string, unknown>;
|
|
28
|
+
relationIds: Map<string, RelationId[]>;
|
|
29
|
+
}
|
|
30
|
+
interface PlannedUpdate {
|
|
31
|
+
/** The `_id` as sent (error paths) and as the canonical encoding (matching). */
|
|
32
|
+
raw: string;
|
|
33
|
+
canonical: string;
|
|
34
|
+
id: RecordId;
|
|
35
|
+
version?: number;
|
|
36
|
+
dto: Record<string, unknown>;
|
|
37
|
+
relationIds: Map<string, RelationId[]>;
|
|
38
|
+
}
|
|
39
|
+
interface PlannedDelete {
|
|
40
|
+
raw: string;
|
|
41
|
+
canonical: string;
|
|
42
|
+
id: RecordId;
|
|
43
|
+
}
|
|
44
|
+
/** One inline's changes, parsed, permission-checked and validated: nothing in it has touched the database. */
|
|
45
|
+
interface PlannedInline {
|
|
46
|
+
inline: RegisteredInline;
|
|
47
|
+
creates: PlannedCreate[];
|
|
48
|
+
updates: PlannedUpdate[];
|
|
49
|
+
deletes: PlannedDelete[];
|
|
50
|
+
order?: string[];
|
|
51
|
+
}
|
|
52
|
+
/** The inline changes of one save (`prepare`), checked against the database (`check`) before anything is written. */
|
|
53
|
+
export interface InlinePlan {
|
|
54
|
+
parent: RegisteredResource;
|
|
55
|
+
creating: boolean;
|
|
56
|
+
inlines: PlannedInline[];
|
|
57
|
+
}
|
|
58
|
+
/** What `check` read: the rows the save touches, and the order values it writes. */
|
|
59
|
+
interface CheckedInline {
|
|
60
|
+
planned: PlannedInline;
|
|
61
|
+
/** Canonical `_id` → the stored child (updates, deletes and rows only moved). */
|
|
62
|
+
rows: Map<string, object>;
|
|
63
|
+
/** Canonical `_id` → new position, for existing rows whose position changes. */
|
|
64
|
+
moves: Map<string, number>;
|
|
65
|
+
/** `_key` → position. */
|
|
66
|
+
positions: Map<string, number>;
|
|
67
|
+
}
|
|
68
|
+
export type CheckedPlan = CheckedInline[];
|
|
69
|
+
/**
|
|
70
|
+
* Saves the `_inlines` of a parent's POST or PATCH inside the parent's write (spec §5.2 inlines), in three steps:
|
|
71
|
+
*
|
|
72
|
+
* 1. `prepare` (before the transaction): the shape, the child's permissions (403), its field rules (403
|
|
73
|
+
* FORBIDDEN_FIELDS) and each entry's validation by the child's DTO, as its own endpoints would (422);
|
|
74
|
+
* 2. `check` (in the transaction, before the parent is written): every `_id` is a child of THIS parent inside the
|
|
75
|
+
* user's view scope and the operation's scope (else 404), `_version`s (409), `@AdminCan` rules (403), `readonlyIf`
|
|
76
|
+
* (422), the order list and the resulting count (`min`/`max`, 422). A refused save has written nothing;
|
|
77
|
+
* 3. `apply` (after the parent): deletes, updates, then creates, each through the CHILD resource's own method inside
|
|
78
|
+
* `change()`, so host overrides, hooks, files and the audit log (with `meta.parent`) apply as on its endpoints. A
|
|
79
|
+
* failure there rolls back the transaction, parent included (inlines need `transactions`, checked at boot).
|
|
80
|
+
*/
|
|
81
|
+
export declare class InlineWriter {
|
|
82
|
+
private readonly deps;
|
|
83
|
+
constructor(deps: InlineWriterDeps);
|
|
84
|
+
/** Step 1, before the transaction. `raw` is the body's `_inlines` (undefined when it has none). */
|
|
85
|
+
prepare(parent: RegisteredResource, raw: unknown, ctx: AdminContext, creating: boolean): Promise<InlinePlan>;
|
|
86
|
+
/**
|
|
87
|
+
* Inside the transaction, before the parent is written: reads what the save touches and decides every 404, 409,
|
|
88
|
+
* 403 and 422 left, so that a refused save writes nothing. `parentEntity` is the stored parent (undefined when it
|
|
89
|
+
* is being created: it has no children yet, so any `_id` is not one of them).
|
|
90
|
+
*/
|
|
91
|
+
check(plan: InlinePlan, parentEntity: object | undefined, tx: AdminContext): Promise<CheckedPlan>;
|
|
92
|
+
private checkInline;
|
|
93
|
+
/** `order` lists every row left (`remaining` ids, then the new rows' keys) exactly once; positions by index. */
|
|
94
|
+
private checkOrder;
|
|
95
|
+
private checkCount;
|
|
96
|
+
/**
|
|
97
|
+
* After the parent: every change through the child resource's own method, inside the parent's write. Deletes, then
|
|
98
|
+
* updates (with their new positions), rows that only move, then creates (link and position set by the server).
|
|
99
|
+
*/
|
|
100
|
+
apply(plan: InlinePlan, checked: CheckedPlan, parentEntity: object, tx: AdminContext): Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
export {};
|