levrops-contracts 1.1.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 +149 -0
- package/contracts/content/README.md +108 -0
- package/contracts/content/example.ts +142 -0
- package/contracts/content/heirloom/product.ts +98 -0
- package/contracts/content/index.ts +70 -0
- package/contracts/content/shared/index.ts +13 -0
- package/contracts/content/studioops/artist.ts +102 -0
- package/contracts/content/types.ts +180 -0
- package/contracts/content/utils.ts +148 -0
- package/index.ts +20 -0
- package/package.json +51 -0
- package/sanity/generated/schema-heirloom.ts +223 -0
- package/sanity/generated/schema-studioops.ts +213 -0
- package/sanity/generated/schema.ts +293 -0
- package/sanity/utils.ts +99 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { defineType, defineField, string, text, number, boolean, date, datetime, url, email, slug, image, file, array, object, reference } from "sanity";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generated Sanity Schema
|
|
5
|
+
*
|
|
6
|
+
* This file is auto-generated from content contracts in contracts/content/.
|
|
7
|
+
* DO NOT EDIT THIS FILE DIRECTLY.
|
|
8
|
+
*
|
|
9
|
+
* Content contracts for studioops tenant
|
|
10
|
+
*
|
|
11
|
+
* To update the schema:
|
|
12
|
+
* 1. Update or add contracts in contracts/content/
|
|
13
|
+
* 2. Run: npm run sanity:codegen
|
|
14
|
+
* (or: npm run sanity:codegen -- --tenant=<tenant> --property=<property>)
|
|
15
|
+
*
|
|
16
|
+
* Generated: 2025-11-14T19:30:21.496Z
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Blog Post
|
|
20
|
+
// A blog post article
|
|
21
|
+
export const BlogPost = defineType({
|
|
22
|
+
name: "blog-post",
|
|
23
|
+
title: "Blog Post",
|
|
24
|
+
type: "document",
|
|
25
|
+
description: "A blog post article",
|
|
26
|
+
fields: [
|
|
27
|
+
defineField({
|
|
28
|
+
name: "title",
|
|
29
|
+
type: string(),
|
|
30
|
+
title: "Title",
|
|
31
|
+
validation: (Rule) => Rule.required().and(Rule.max(200)),
|
|
32
|
+
|
|
33
|
+
}),
|
|
34
|
+
defineField({
|
|
35
|
+
name: "slug",
|
|
36
|
+
type: slug({ source: "title" }),
|
|
37
|
+
title: "Slug",
|
|
38
|
+
validation: (Rule) => Rule.required(),
|
|
39
|
+
|
|
40
|
+
}),
|
|
41
|
+
defineField({
|
|
42
|
+
name: "author",
|
|
43
|
+
type: reference({ to: [{ type: "author" }] }),
|
|
44
|
+
title: "Author",
|
|
45
|
+
validation: (Rule) => Rule.required(),
|
|
46
|
+
|
|
47
|
+
}),
|
|
48
|
+
defineField({
|
|
49
|
+
name: "publishedAt",
|
|
50
|
+
type: datetime(),
|
|
51
|
+
title: "Published At",
|
|
52
|
+
validation: (Rule) => Rule.required(),
|
|
53
|
+
|
|
54
|
+
}),
|
|
55
|
+
defineField({
|
|
56
|
+
name: "excerpt",
|
|
57
|
+
type: text(),
|
|
58
|
+
title: "Excerpt",
|
|
59
|
+
description: "Short summary for previews",
|
|
60
|
+
|
|
61
|
+
}),
|
|
62
|
+
defineField({
|
|
63
|
+
name: "content",
|
|
64
|
+
type: array({
|
|
65
|
+
of: [{ type: "block" }],
|
|
66
|
+
}),
|
|
67
|
+
title: "Content",
|
|
68
|
+
validation: (Rule) => Rule.required(),
|
|
69
|
+
|
|
70
|
+
}),
|
|
71
|
+
defineField({
|
|
72
|
+
name: "tags",
|
|
73
|
+
type: array({
|
|
74
|
+
of: [string()],
|
|
75
|
+
}),
|
|
76
|
+
title: "Tags",
|
|
77
|
+
|
|
78
|
+
}),
|
|
79
|
+
defineField({
|
|
80
|
+
name: "featuredImage",
|
|
81
|
+
type: image(),
|
|
82
|
+
title: "Featured Image",
|
|
83
|
+
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
preview: {
|
|
87
|
+
select: {
|
|
88
|
+
"title": "title",
|
|
89
|
+
"author": "author.name",
|
|
90
|
+
"publishedAt": "publishedAt"
|
|
91
|
+
},
|
|
92
|
+
// prepare: (selection) => { ... } // Custom prepare function
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Hero Section
|
|
97
|
+
// Hero banner section for landing pages
|
|
98
|
+
export const HeroSection = defineType({
|
|
99
|
+
name: "hero-section",
|
|
100
|
+
title: "Hero Section",
|
|
101
|
+
type: "object",
|
|
102
|
+
description: "Hero banner section for landing pages",
|
|
103
|
+
fields: [
|
|
104
|
+
defineField({
|
|
105
|
+
name: "headline",
|
|
106
|
+
type: string(),
|
|
107
|
+
title: "Headline",
|
|
108
|
+
validation: (Rule) => Rule.required().and(Rule.max(100)),
|
|
109
|
+
|
|
110
|
+
}),
|
|
111
|
+
defineField({
|
|
112
|
+
name: "subheadline",
|
|
113
|
+
type: text(),
|
|
114
|
+
title: "Subheadline",
|
|
115
|
+
|
|
116
|
+
}),
|
|
117
|
+
defineField({
|
|
118
|
+
name: "image",
|
|
119
|
+
type: image(),
|
|
120
|
+
title: "Background Image",
|
|
121
|
+
validation: (Rule) => Rule.required(),
|
|
122
|
+
|
|
123
|
+
}),
|
|
124
|
+
defineField({
|
|
125
|
+
name: "ctaText",
|
|
126
|
+
type: string(),
|
|
127
|
+
title: "CTA Button Text",
|
|
128
|
+
|
|
129
|
+
}),
|
|
130
|
+
defineField({
|
|
131
|
+
name: "ctaLink",
|
|
132
|
+
type: url(),
|
|
133
|
+
title: "CTA Link",
|
|
134
|
+
|
|
135
|
+
}),
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Artist
|
|
140
|
+
// Artist profile for StudioOps
|
|
141
|
+
export const Artist = defineType({
|
|
142
|
+
name: "artist",
|
|
143
|
+
title: "Artist",
|
|
144
|
+
type: "document",
|
|
145
|
+
description: "Artist profile for StudioOps",
|
|
146
|
+
fields: [
|
|
147
|
+
defineField({
|
|
148
|
+
name: "name",
|
|
149
|
+
type: string(),
|
|
150
|
+
title: "Artist Name",
|
|
151
|
+
validation: (Rule) => Rule.required().and(Rule.max(200)),
|
|
152
|
+
|
|
153
|
+
}),
|
|
154
|
+
defineField({
|
|
155
|
+
name: "slug",
|
|
156
|
+
type: slug({ source: "name" }),
|
|
157
|
+
title: "Slug",
|
|
158
|
+
validation: (Rule) => Rule.required(),
|
|
159
|
+
|
|
160
|
+
}),
|
|
161
|
+
defineField({
|
|
162
|
+
name: "bio",
|
|
163
|
+
type: array({
|
|
164
|
+
of: [{ type: "block" }],
|
|
165
|
+
}),
|
|
166
|
+
title: "Biography",
|
|
167
|
+
description: "Artist biography and background",
|
|
168
|
+
|
|
169
|
+
}),
|
|
170
|
+
defineField({
|
|
171
|
+
name: "photo",
|
|
172
|
+
type: image(),
|
|
173
|
+
title: "Artist Photo",
|
|
174
|
+
|
|
175
|
+
}),
|
|
176
|
+
defineField({
|
|
177
|
+
name: "genre",
|
|
178
|
+
type: string(),
|
|
179
|
+
title: "Genre",
|
|
180
|
+
validation: (Rule) => Rule.custom((value) => !value || ["rock", "pop", "jazz", "classical", "electronic", "hip-hop", "country"].includes(value) || "Invalid option"),
|
|
181
|
+
|
|
182
|
+
}),
|
|
183
|
+
defineField({
|
|
184
|
+
name: "albums",
|
|
185
|
+
type: array({
|
|
186
|
+
of: [reference({ to: [{ type: "album" }] })],
|
|
187
|
+
}),
|
|
188
|
+
title: "Albums",
|
|
189
|
+
|
|
190
|
+
}),
|
|
191
|
+
defineField({
|
|
192
|
+
name: "socialLinks",
|
|
193
|
+
type: object({ fields: socialLinksFields }),
|
|
194
|
+
title: "Social Links",
|
|
195
|
+
|
|
196
|
+
}),
|
|
197
|
+
],
|
|
198
|
+
preview: {
|
|
199
|
+
select: {
|
|
200
|
+
"title": "name",
|
|
201
|
+
"genre": "genre",
|
|
202
|
+
"photo": "photo"
|
|
203
|
+
},
|
|
204
|
+
// prepare: (selection) => { ... } // Custom prepare function
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Export all schemas
|
|
209
|
+
export const allSchemas = [
|
|
210
|
+
BlogPost,
|
|
211
|
+
HeroSection,
|
|
212
|
+
Artist,
|
|
213
|
+
];
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { defineType, defineField, string, text, number, boolean, date, datetime, url, email, slug, image, file, array, object, reference } from "sanity";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generated Sanity Schema
|
|
5
|
+
*
|
|
6
|
+
* This file is auto-generated from content contracts in contracts/content/.
|
|
7
|
+
* DO NOT EDIT THIS FILE DIRECTLY.
|
|
8
|
+
*
|
|
9
|
+
* All content contracts
|
|
10
|
+
*
|
|
11
|
+
* To update the schema:
|
|
12
|
+
* 1. Update or add contracts in contracts/content/
|
|
13
|
+
* 2. Run: npm run sanity:codegen
|
|
14
|
+
* (or: npm run sanity:codegen -- --tenant=<tenant> --property=<property>)
|
|
15
|
+
*
|
|
16
|
+
* Generated: 2025-11-14T19:30:27.357Z
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Blog Post
|
|
20
|
+
// A blog post article
|
|
21
|
+
export const BlogPost = defineType({
|
|
22
|
+
name: "blog-post",
|
|
23
|
+
title: "Blog Post",
|
|
24
|
+
type: "document",
|
|
25
|
+
description: "A blog post article",
|
|
26
|
+
fields: [
|
|
27
|
+
defineField({
|
|
28
|
+
name: "title",
|
|
29
|
+
type: string(),
|
|
30
|
+
title: "Title",
|
|
31
|
+
validation: (Rule) => Rule.required().and(Rule.max(200)),
|
|
32
|
+
|
|
33
|
+
}),
|
|
34
|
+
defineField({
|
|
35
|
+
name: "slug",
|
|
36
|
+
type: slug({ source: "title" }),
|
|
37
|
+
title: "Slug",
|
|
38
|
+
validation: (Rule) => Rule.required(),
|
|
39
|
+
|
|
40
|
+
}),
|
|
41
|
+
defineField({
|
|
42
|
+
name: "author",
|
|
43
|
+
type: reference({ to: [{ type: "author" }] }),
|
|
44
|
+
title: "Author",
|
|
45
|
+
validation: (Rule) => Rule.required(),
|
|
46
|
+
|
|
47
|
+
}),
|
|
48
|
+
defineField({
|
|
49
|
+
name: "publishedAt",
|
|
50
|
+
type: datetime(),
|
|
51
|
+
title: "Published At",
|
|
52
|
+
validation: (Rule) => Rule.required(),
|
|
53
|
+
|
|
54
|
+
}),
|
|
55
|
+
defineField({
|
|
56
|
+
name: "excerpt",
|
|
57
|
+
type: text(),
|
|
58
|
+
title: "Excerpt",
|
|
59
|
+
description: "Short summary for previews",
|
|
60
|
+
|
|
61
|
+
}),
|
|
62
|
+
defineField({
|
|
63
|
+
name: "content",
|
|
64
|
+
type: array({
|
|
65
|
+
of: [{ type: "block" }],
|
|
66
|
+
}),
|
|
67
|
+
title: "Content",
|
|
68
|
+
validation: (Rule) => Rule.required(),
|
|
69
|
+
|
|
70
|
+
}),
|
|
71
|
+
defineField({
|
|
72
|
+
name: "tags",
|
|
73
|
+
type: array({
|
|
74
|
+
of: [string()],
|
|
75
|
+
}),
|
|
76
|
+
title: "Tags",
|
|
77
|
+
|
|
78
|
+
}),
|
|
79
|
+
defineField({
|
|
80
|
+
name: "featuredImage",
|
|
81
|
+
type: image(),
|
|
82
|
+
title: "Featured Image",
|
|
83
|
+
|
|
84
|
+
}),
|
|
85
|
+
],
|
|
86
|
+
preview: {
|
|
87
|
+
select: {
|
|
88
|
+
"title": "title",
|
|
89
|
+
"author": "author.name",
|
|
90
|
+
"publishedAt": "publishedAt"
|
|
91
|
+
},
|
|
92
|
+
// prepare: (selection) => { ... } // Custom prepare function
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Hero Section
|
|
97
|
+
// Hero banner section for landing pages
|
|
98
|
+
export const HeroSection = defineType({
|
|
99
|
+
name: "hero-section",
|
|
100
|
+
title: "Hero Section",
|
|
101
|
+
type: "object",
|
|
102
|
+
description: "Hero banner section for landing pages",
|
|
103
|
+
fields: [
|
|
104
|
+
defineField({
|
|
105
|
+
name: "headline",
|
|
106
|
+
type: string(),
|
|
107
|
+
title: "Headline",
|
|
108
|
+
validation: (Rule) => Rule.required().and(Rule.max(100)),
|
|
109
|
+
|
|
110
|
+
}),
|
|
111
|
+
defineField({
|
|
112
|
+
name: "subheadline",
|
|
113
|
+
type: text(),
|
|
114
|
+
title: "Subheadline",
|
|
115
|
+
|
|
116
|
+
}),
|
|
117
|
+
defineField({
|
|
118
|
+
name: "image",
|
|
119
|
+
type: image(),
|
|
120
|
+
title: "Background Image",
|
|
121
|
+
validation: (Rule) => Rule.required(),
|
|
122
|
+
|
|
123
|
+
}),
|
|
124
|
+
defineField({
|
|
125
|
+
name: "ctaText",
|
|
126
|
+
type: string(),
|
|
127
|
+
title: "CTA Button Text",
|
|
128
|
+
|
|
129
|
+
}),
|
|
130
|
+
defineField({
|
|
131
|
+
name: "ctaLink",
|
|
132
|
+
type: url(),
|
|
133
|
+
title: "CTA Link",
|
|
134
|
+
|
|
135
|
+
}),
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Product
|
|
140
|
+
// Product for Heirloom Supply catalog
|
|
141
|
+
export const Product = defineType({
|
|
142
|
+
name: "product",
|
|
143
|
+
title: "Product",
|
|
144
|
+
type: "document",
|
|
145
|
+
description: "Product for Heirloom Supply catalog",
|
|
146
|
+
fields: [
|
|
147
|
+
defineField({
|
|
148
|
+
name: "title",
|
|
149
|
+
type: string(),
|
|
150
|
+
title: "Product Name",
|
|
151
|
+
validation: (Rule) => Rule.required().and(Rule.max(200)),
|
|
152
|
+
|
|
153
|
+
}),
|
|
154
|
+
defineField({
|
|
155
|
+
name: "slug",
|
|
156
|
+
type: slug({ source: "title" }),
|
|
157
|
+
title: "Slug",
|
|
158
|
+
validation: (Rule) => Rule.required(),
|
|
159
|
+
|
|
160
|
+
}),
|
|
161
|
+
defineField({
|
|
162
|
+
name: "description",
|
|
163
|
+
type: array({
|
|
164
|
+
of: [{ type: "block" }],
|
|
165
|
+
}),
|
|
166
|
+
title: "Description",
|
|
167
|
+
validation: (Rule) => Rule.required(),
|
|
168
|
+
|
|
169
|
+
}),
|
|
170
|
+
defineField({
|
|
171
|
+
name: "price",
|
|
172
|
+
type: number(),
|
|
173
|
+
title: "Price",
|
|
174
|
+
validation: (Rule) => Rule.required().and(Rule.min(0)),
|
|
175
|
+
|
|
176
|
+
}),
|
|
177
|
+
defineField({
|
|
178
|
+
name: "images",
|
|
179
|
+
type: array({
|
|
180
|
+
of: [image()],
|
|
181
|
+
validation: (Rule) => Rule.min(1),
|
|
182
|
+
}),
|
|
183
|
+
title: "Product Images",
|
|
184
|
+
validation: (Rule) => Rule.required(),
|
|
185
|
+
|
|
186
|
+
}),
|
|
187
|
+
defineField({
|
|
188
|
+
name: "category",
|
|
189
|
+
type: reference({ to: [{ type: "category" }] }),
|
|
190
|
+
title: "Category",
|
|
191
|
+
|
|
192
|
+
}),
|
|
193
|
+
defineField({
|
|
194
|
+
name: "tags",
|
|
195
|
+
type: array({
|
|
196
|
+
of: [string()],
|
|
197
|
+
}),
|
|
198
|
+
title: "Tags",
|
|
199
|
+
|
|
200
|
+
}),
|
|
201
|
+
defineField({
|
|
202
|
+
name: "inStock",
|
|
203
|
+
type: boolean(),
|
|
204
|
+
title: "In Stock",
|
|
205
|
+
|
|
206
|
+
}),
|
|
207
|
+
],
|
|
208
|
+
preview: {
|
|
209
|
+
select: {
|
|
210
|
+
"title": "title",
|
|
211
|
+
"price": "price",
|
|
212
|
+
"inStock": "inStock"
|
|
213
|
+
},
|
|
214
|
+
// prepare: (selection) => { ... } // Custom prepare function
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Artist
|
|
219
|
+
// Artist profile for StudioOps
|
|
220
|
+
export const Artist = defineType({
|
|
221
|
+
name: "artist",
|
|
222
|
+
title: "Artist",
|
|
223
|
+
type: "document",
|
|
224
|
+
description: "Artist profile for StudioOps",
|
|
225
|
+
fields: [
|
|
226
|
+
defineField({
|
|
227
|
+
name: "name",
|
|
228
|
+
type: string(),
|
|
229
|
+
title: "Artist Name",
|
|
230
|
+
validation: (Rule) => Rule.required().and(Rule.max(200)),
|
|
231
|
+
|
|
232
|
+
}),
|
|
233
|
+
defineField({
|
|
234
|
+
name: "slug",
|
|
235
|
+
type: slug({ source: "name" }),
|
|
236
|
+
title: "Slug",
|
|
237
|
+
validation: (Rule) => Rule.required(),
|
|
238
|
+
|
|
239
|
+
}),
|
|
240
|
+
defineField({
|
|
241
|
+
name: "bio",
|
|
242
|
+
type: array({
|
|
243
|
+
of: [{ type: "block" }],
|
|
244
|
+
}),
|
|
245
|
+
title: "Biography",
|
|
246
|
+
description: "Artist biography and background",
|
|
247
|
+
|
|
248
|
+
}),
|
|
249
|
+
defineField({
|
|
250
|
+
name: "photo",
|
|
251
|
+
type: image(),
|
|
252
|
+
title: "Artist Photo",
|
|
253
|
+
|
|
254
|
+
}),
|
|
255
|
+
defineField({
|
|
256
|
+
name: "genre",
|
|
257
|
+
type: string(),
|
|
258
|
+
title: "Genre",
|
|
259
|
+
validation: (Rule) => Rule.custom((value) => !value || ["rock", "pop", "jazz", "classical", "electronic", "hip-hop", "country"].includes(value) || "Invalid option"),
|
|
260
|
+
|
|
261
|
+
}),
|
|
262
|
+
defineField({
|
|
263
|
+
name: "albums",
|
|
264
|
+
type: array({
|
|
265
|
+
of: [reference({ to: [{ type: "album" }] })],
|
|
266
|
+
}),
|
|
267
|
+
title: "Albums",
|
|
268
|
+
|
|
269
|
+
}),
|
|
270
|
+
defineField({
|
|
271
|
+
name: "socialLinks",
|
|
272
|
+
type: object({ fields: socialLinksFields }),
|
|
273
|
+
title: "Social Links",
|
|
274
|
+
|
|
275
|
+
}),
|
|
276
|
+
],
|
|
277
|
+
preview: {
|
|
278
|
+
select: {
|
|
279
|
+
"title": "name",
|
|
280
|
+
"genre": "genre",
|
|
281
|
+
"photo": "photo"
|
|
282
|
+
},
|
|
283
|
+
// prepare: (selection) => { ... } // Custom prepare function
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Export all schemas
|
|
288
|
+
export const allSchemas = [
|
|
289
|
+
BlogPost,
|
|
290
|
+
HeroSection,
|
|
291
|
+
Product,
|
|
292
|
+
Artist,
|
|
293
|
+
];
|
package/sanity/utils.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanity Schema Utilities
|
|
3
|
+
*
|
|
4
|
+
* Runtime utilities for filtering Sanity schemas by tenant and property.
|
|
5
|
+
* These utilities work with generated schemas from content contracts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Filter schemas by tenant at runtime.
|
|
10
|
+
*
|
|
11
|
+
* @param schemas - Array of Sanity schema definitions (from generated schema)
|
|
12
|
+
* @param tenant - Tenant slug to filter by
|
|
13
|
+
* @param allContracts - All content contracts to check scope
|
|
14
|
+
* @returns Filtered schemas available to the specified tenant
|
|
15
|
+
*/
|
|
16
|
+
export function filterSchemasByTenant<T>(
|
|
17
|
+
schemas: T[],
|
|
18
|
+
tenant: string,
|
|
19
|
+
allContracts: Array<{ id: string; scope?: { tenants?: string[] } }>
|
|
20
|
+
): T[] {
|
|
21
|
+
// Create a map of contract IDs to their tenant scope
|
|
22
|
+
const contractScopeMap = new Map<string, string[] | undefined>();
|
|
23
|
+
allContracts.forEach((contract) => {
|
|
24
|
+
contractScopeMap.set(contract.id, contract.scope?.tenants);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Filter schemas based on tenant scope
|
|
28
|
+
return schemas.filter((schema: any) => {
|
|
29
|
+
const contractId = schema.name || schema.id;
|
|
30
|
+
const allowedTenants = contractScopeMap.get(contractId);
|
|
31
|
+
|
|
32
|
+
// If no scope or empty scope, available to all tenants
|
|
33
|
+
if (!allowedTenants || allowedTenants.length === 0) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if tenant is in the allowed list
|
|
38
|
+
return allowedTenants.includes(tenant);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Filter schemas by property at runtime.
|
|
44
|
+
*
|
|
45
|
+
* @param schemas - Array of Sanity schema definitions (from generated schema)
|
|
46
|
+
* @param property - Property slug to filter by
|
|
47
|
+
* @param allContracts - All content contracts to check scope
|
|
48
|
+
* @returns Filtered schemas available to the specified property
|
|
49
|
+
*/
|
|
50
|
+
export function filterSchemasByProperty<T>(
|
|
51
|
+
schemas: T[],
|
|
52
|
+
property: string,
|
|
53
|
+
allContracts: Array<{ id: string; scope?: { properties?: string[] } }>
|
|
54
|
+
): T[] {
|
|
55
|
+
// Create a map of contract IDs to their property scope
|
|
56
|
+
const contractScopeMap = new Map<string, string[] | undefined>();
|
|
57
|
+
allContracts.forEach((contract) => {
|
|
58
|
+
contractScopeMap.set(contract.id, contract.scope?.properties);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Filter schemas based on property scope
|
|
62
|
+
return schemas.filter((schema: any) => {
|
|
63
|
+
const contractId = schema.name || schema.id;
|
|
64
|
+
const allowedProperties = contractScopeMap.get(contractId);
|
|
65
|
+
|
|
66
|
+
// If no scope or empty scope, available to all properties
|
|
67
|
+
if (!allowedProperties || allowedProperties.length === 0) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Check if property is in the allowed list
|
|
72
|
+
return allowedProperties.includes(property);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Filter schemas by tenant and property at runtime.
|
|
78
|
+
*
|
|
79
|
+
* @param schemas - Array of Sanity schema definitions (from generated schema)
|
|
80
|
+
* @param tenant - Tenant slug to filter by
|
|
81
|
+
* @param property - Optional property slug to filter by
|
|
82
|
+
* @param allContracts - All content contracts to check scope
|
|
83
|
+
* @returns Filtered schemas available to the specified tenant (and property if provided)
|
|
84
|
+
*/
|
|
85
|
+
export function filterSchemas<T>(
|
|
86
|
+
schemas: T[],
|
|
87
|
+
tenant: string,
|
|
88
|
+
allContracts: Array<{ id: string; scope?: { tenants?: string[]; properties?: string[] } }>,
|
|
89
|
+
property?: string
|
|
90
|
+
): T[] {
|
|
91
|
+
let filtered = filterSchemasByTenant(schemas, tenant, allContracts);
|
|
92
|
+
|
|
93
|
+
if (property) {
|
|
94
|
+
filtered = filterSchemasByProperty(filtered, property, allContracts);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return filtered;
|
|
98
|
+
}
|
|
99
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"allowSyntheticDefaultImports": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["contracts/**/*", "tools/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "clients/**/*", "cli/**/*"]
|
|
17
|
+
}
|
|
18
|
+
|