@btst/stack 2.11.2 → 2.11.4
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/packages/stack/src/plugins/cms/api/mutations.cjs +12 -1
- package/dist/packages/stack/src/plugins/cms/api/mutations.mjs +12 -1
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +11 -51
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +3 -43
- package/dist/packages/stack/src/plugins/cms/api/relations.cjs +76 -0
- package/dist/packages/stack/src/plugins/cms/api/relations.mjs +70 -0
- package/dist/plugins/blog/api/index.d.cts +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +2 -2
- package/dist/plugins/blog/client/index.d.mts +2 -2
- package/dist/plugins/blog/client/index.d.ts +2 -2
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/cms/api/index.d.cts +1 -1
- package/dist/plugins/cms/api/index.d.mts +1 -1
- package/dist/plugins/cms/api/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.d.cts +1 -1
- package/dist/plugins/cms/query-keys.d.mts +1 -1
- package/dist/plugins/cms/query-keys.d.ts +1 -1
- package/dist/shared/{stack.DOZ1EXjM.d.mts → stack.6mEHS2WH.d.mts} +3 -3
- package/dist/shared/{stack.DX-tQ93o.d.cts → stack.AJTXI7kw.d.cts} +3 -3
- package/dist/shared/{stack.E17kSK1W.d.mts → stack.C1jLzFZ6.d.mts} +5 -5
- package/dist/shared/{stack.D0p6oNme.d.ts → stack.DF1ewpVE.d.ts} +5 -5
- package/dist/shared/{stack.BX7MHi0J.d.mts → stack.DbnCLdNF.d.mts} +34 -7
- package/dist/shared/{stack.VF6FhyZw.d.ts → stack.QYn-Px94.d.ts} +3 -3
- package/dist/shared/{stack.DWipT53I.d.cts → stack.XulPeAD3.d.cts} +5 -5
- package/dist/shared/{stack.fQjVhw5a.d.ts → stack.fXlShnhv.d.ts} +34 -7
- package/dist/shared/{stack.C9zoS1TN.d.cts → stack.r6HohbyX.d.cts} +34 -7
- package/package.json +4 -4
- package/src/plugins/cms/api/index.ts +1 -0
- package/src/plugins/cms/api/mutations.ts +48 -3
- package/src/plugins/cms/api/plugin.ts +11 -101
- package/src/plugins/cms/api/relations.ts +174 -0
- package/dist/shared/{stack.BWp0hcm9.d.cts → stack.BQmuNl5p.d.cts} +3 -3
- package/dist/shared/{stack.BWp0hcm9.d.mts → stack.BQmuNl5p.d.mts} +3 -3
- package/dist/shared/{stack.BWp0hcm9.d.ts → stack.BQmuNl5p.d.ts} +3 -3
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { DBAdapter as Adapter } from "@btst/db";
|
|
2
|
+
import type {
|
|
3
|
+
ContentType,
|
|
4
|
+
ContentRelation,
|
|
5
|
+
RelationConfig,
|
|
6
|
+
RelationValue,
|
|
7
|
+
} from "../types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Shape of a property inside a content type's stored JSON Schema.
|
|
11
|
+
* Relation fields carry `fieldType: "relation"` and a `relation` descriptor.
|
|
12
|
+
*/
|
|
13
|
+
interface JsonSchemaProperty {
|
|
14
|
+
fieldType?: string;
|
|
15
|
+
relation?: RelationConfig;
|
|
16
|
+
type?: string;
|
|
17
|
+
items?: JsonSchemaProperty;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface JsonSchemaWithProperties {
|
|
22
|
+
properties?: Record<string, JsonSchemaProperty>;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract relation field configurations from a content type's JSON Schema.
|
|
28
|
+
*
|
|
29
|
+
* Returns a map of field name -> RelationConfig for every field declared
|
|
30
|
+
* with `fieldType: "relation"` and a populated `relation` descriptor.
|
|
31
|
+
*/
|
|
32
|
+
export function extractRelationFields(
|
|
33
|
+
contentType: ContentType,
|
|
34
|
+
): Record<string, RelationConfig> {
|
|
35
|
+
const jsonSchema = JSON.parse(
|
|
36
|
+
contentType.jsonSchema,
|
|
37
|
+
) as JsonSchemaWithProperties;
|
|
38
|
+
const properties = jsonSchema.properties || {};
|
|
39
|
+
const relationFields: Record<string, RelationConfig> = {};
|
|
40
|
+
|
|
41
|
+
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
42
|
+
if (fieldSchema.fieldType === "relation" && fieldSchema.relation) {
|
|
43
|
+
relationFields[fieldName] = fieldSchema.relation;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return relationFields;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Type guard: value is a `{ _new: true, data: ... }` descriptor used by the
|
|
52
|
+
* HTTP endpoints to request inline creation of a related item on save.
|
|
53
|
+
*/
|
|
54
|
+
export function isNewRelationValue(
|
|
55
|
+
value: unknown,
|
|
56
|
+
): value is { _new: true; data: Record<string, unknown> } {
|
|
57
|
+
return (
|
|
58
|
+
typeof value === "object" &&
|
|
59
|
+
value !== null &&
|
|
60
|
+
"_new" in value &&
|
|
61
|
+
(value as { _new: unknown })._new === true &&
|
|
62
|
+
"data" in value
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Type guard: value is an existing relation reference (`{ id: string }`).
|
|
68
|
+
*/
|
|
69
|
+
export function isExistingRelationValue(
|
|
70
|
+
value: unknown,
|
|
71
|
+
): value is { id: string } {
|
|
72
|
+
return (
|
|
73
|
+
typeof value === "object" &&
|
|
74
|
+
value !== null &&
|
|
75
|
+
"id" in value &&
|
|
76
|
+
typeof (value as { id: unknown }).id === "string"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Collect relation target IDs out of a content item's data payload,
|
|
82
|
+
* grouped by relation field name.
|
|
83
|
+
*
|
|
84
|
+
* Only processes fields that are:
|
|
85
|
+
* - present in `data`
|
|
86
|
+
* - declared as relations in `relationFields`
|
|
87
|
+
* - hold existing `{ id }` references (or arrays of them)
|
|
88
|
+
*
|
|
89
|
+
* Unlike `processRelationsInData` in the HTTP route, this does NOT create
|
|
90
|
+
* inline `_new` items — callers passing `_new` values will have them
|
|
91
|
+
* silently ignored. Programmatic callers (seeds, imports) are expected to
|
|
92
|
+
* supply pre-created IDs.
|
|
93
|
+
*
|
|
94
|
+
* Returns a map of `{ fieldName: targetIds[] }` ready to hand to
|
|
95
|
+
* {@link syncRelations}.
|
|
96
|
+
*/
|
|
97
|
+
export function collectExistingRelationIds(
|
|
98
|
+
data: Record<string, unknown>,
|
|
99
|
+
relationFields: Record<string, RelationConfig>,
|
|
100
|
+
): Record<string, string[]> {
|
|
101
|
+
const relationIds: Record<string, string[]> = {};
|
|
102
|
+
|
|
103
|
+
for (const [fieldName, relationConfig] of Object.entries(relationFields)) {
|
|
104
|
+
if (!(fieldName in data)) continue;
|
|
105
|
+
|
|
106
|
+
const fieldValue = data[fieldName];
|
|
107
|
+
if (!fieldValue) {
|
|
108
|
+
relationIds[fieldName] = [];
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const ids: string[] = [];
|
|
113
|
+
|
|
114
|
+
if (relationConfig.type === "belongsTo") {
|
|
115
|
+
const value = fieldValue as RelationValue;
|
|
116
|
+
if (isExistingRelationValue(value)) {
|
|
117
|
+
ids.push(value.id);
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
// hasMany / manyToMany — expect an array of { id }
|
|
121
|
+
const values = (
|
|
122
|
+
Array.isArray(fieldValue) ? fieldValue : []
|
|
123
|
+
) as RelationValue[];
|
|
124
|
+
|
|
125
|
+
for (const value of values) {
|
|
126
|
+
if (isExistingRelationValue(value)) {
|
|
127
|
+
ids.push(value.id);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
relationIds[fieldName] = ids;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return relationIds;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Sync relations in the junction table for a content item.
|
|
140
|
+
*
|
|
141
|
+
* Only updates relations for fields explicitly present in `relationIds`.
|
|
142
|
+
* Fields not included are left untouched — this preserves existing
|
|
143
|
+
* relations during partial updates.
|
|
144
|
+
*
|
|
145
|
+
* For each included field, existing junction rows (matching sourceId +
|
|
146
|
+
* fieldName) are deleted and replaced with the provided target IDs.
|
|
147
|
+
*/
|
|
148
|
+
export async function syncRelations(
|
|
149
|
+
adapter: Adapter,
|
|
150
|
+
sourceId: string,
|
|
151
|
+
relationIds: Record<string, string[]>,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
for (const [fieldName, targetIds] of Object.entries(relationIds)) {
|
|
154
|
+
await adapter.delete({
|
|
155
|
+
model: "contentRelation",
|
|
156
|
+
where: [
|
|
157
|
+
{ field: "sourceId", value: sourceId, operator: "eq" as const },
|
|
158
|
+
{ field: "fieldName", value: fieldName, operator: "eq" as const },
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
for (const targetId of targetIds) {
|
|
163
|
+
await adapter.create<ContentRelation>({
|
|
164
|
+
model: "contentRelation",
|
|
165
|
+
data: {
|
|
166
|
+
sourceId,
|
|
167
|
+
targetId,
|
|
168
|
+
fieldName,
|
|
169
|
+
createdAt: new Date(),
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -42,15 +42,15 @@ declare const createPostSchema: z.ZodObject<{
|
|
|
42
42
|
name: z.ZodString;
|
|
43
43
|
slug: z.ZodString;
|
|
44
44
|
}, z.core.$strip>]>>>>;
|
|
45
|
+
title: z.ZodString;
|
|
45
46
|
slug: z.ZodOptional<z.ZodString>;
|
|
46
|
-
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
47
|
-
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
48
47
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
49
48
|
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
50
|
-
|
|
49
|
+
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
51
50
|
content: z.ZodString;
|
|
52
51
|
excerpt: z.ZodString;
|
|
53
52
|
image: z.ZodOptional<z.ZodString>;
|
|
53
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
54
54
|
}, z.core.$strip>;
|
|
55
55
|
declare const updatePostSchema: z.ZodObject<{
|
|
56
56
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -42,15 +42,15 @@ declare const createPostSchema: z.ZodObject<{
|
|
|
42
42
|
name: z.ZodString;
|
|
43
43
|
slug: z.ZodString;
|
|
44
44
|
}, z.core.$strip>]>>>>;
|
|
45
|
+
title: z.ZodString;
|
|
45
46
|
slug: z.ZodOptional<z.ZodString>;
|
|
46
|
-
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
47
|
-
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
48
47
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
49
48
|
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
50
|
-
|
|
49
|
+
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
51
50
|
content: z.ZodString;
|
|
52
51
|
excerpt: z.ZodString;
|
|
53
52
|
image: z.ZodOptional<z.ZodString>;
|
|
53
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
54
54
|
}, z.core.$strip>;
|
|
55
55
|
declare const updatePostSchema: z.ZodObject<{
|
|
56
56
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
@@ -42,15 +42,15 @@ declare const createPostSchema: z.ZodObject<{
|
|
|
42
42
|
name: z.ZodString;
|
|
43
43
|
slug: z.ZodString;
|
|
44
44
|
}, z.core.$strip>]>>>>;
|
|
45
|
+
title: z.ZodString;
|
|
45
46
|
slug: z.ZodOptional<z.ZodString>;
|
|
46
|
-
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
47
|
-
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
48
47
|
createdAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
49
48
|
updatedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
50
|
-
|
|
49
|
+
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|
|
51
50
|
content: z.ZodString;
|
|
52
51
|
excerpt: z.ZodString;
|
|
53
52
|
image: z.ZodOptional<z.ZodString>;
|
|
53
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
54
54
|
}, z.core.$strip>;
|
|
55
55
|
declare const updatePostSchema: z.ZodObject<{
|
|
56
56
|
publishedAt: z.ZodOptional<z.ZodCoercedDate<unknown>>;
|