@porulle/core 0.10.6 → 0.11.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/config/types.d.ts +3 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/hooks/checkout.d.ts +2 -0
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +2 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +4 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +37 -2
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +7 -1
- package/dist/interfaces/rest/routes/payments.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/payments.js +48 -30
- package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/search.js +12 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
- package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +86 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +1 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.js +1 -0
- package/dist/kernel/error-mapper.d.ts.map +1 -1
- package/dist/kernel/error-mapper.js +3 -0
- package/dist/modules/catalog/entity-service.d.ts +5 -3
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +73 -22
- package/dist/modules/catalog/module.d.ts +6 -1
- package/dist/modules/catalog/module.d.ts.map +1 -1
- package/dist/modules/catalog/module.js +6 -1
- package/dist/modules/catalog/ownership.d.ts +5 -0
- package/dist/modules/catalog/ownership.d.ts.map +1 -0
- package/dist/modules/catalog/ownership.js +11 -0
- package/dist/modules/catalog/repository/index.d.ts +52 -1
- package/dist/modules/catalog/repository/index.d.ts.map +1 -1
- package/dist/modules/catalog/repository/index.js +321 -2
- package/dist/modules/catalog/revision.d.ts +4 -0
- package/dist/modules/catalog/revision.d.ts.map +1 -0
- package/dist/modules/catalog/revision.js +33 -0
- package/dist/modules/catalog/schema.d.ts +979 -110
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +112 -2
- package/dist/modules/catalog/schemas.d.ts +2 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +2 -0
- package/dist/modules/catalog/service.d.ts +66 -3
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +512 -11
- package/dist/modules/channels/adapter.d.ts +85 -0
- package/dist/modules/channels/adapter.d.ts.map +1 -1
- package/dist/modules/channels/adapter.js +9 -1
- package/dist/modules/media/module.d.ts +68 -0
- package/dist/modules/media/module.d.ts.map +1 -1
- package/dist/modules/media/module.js +2 -0
- package/dist/modules/media/schema.d.ts +70 -0
- package/dist/modules/media/schema.d.ts.map +1 -1
- package/dist/modules/media/schema.js +20 -2
- package/dist/modules/media/service.d.ts +8 -1
- package/dist/modules/media/service.d.ts.map +1 -1
- package/dist/modules/media/service.js +66 -18
- package/dist/modules/orders/service.d.ts +2 -0
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +1 -0
- package/dist/modules/pricing/schema.d.ts +17 -0
- package/dist/modules/pricing/schema.d.ts.map +1 -1
- package/dist/modules/pricing/schema.js +1 -0
- package/dist/modules/pricing/schemas.d.ts +1 -0
- package/dist/modules/pricing/schemas.d.ts.map +1 -1
- package/dist/modules/pricing/schemas.js +1 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +6 -1
- package/dist/modules/search/adapter.d.ts +2 -0
- package/dist/modules/search/adapter.d.ts.map +1 -1
- package/dist/modules/search/module.d.ts.map +1 -1
- package/dist/modules/search/module.js +2 -0
- package/dist/modules/search/service.d.ts +4 -0
- package/dist/modules/search/service.d.ts.map +1 -1
- package/dist/modules/search/service.js +74 -5
- package/dist/runtime/kernel-modules.d.ts +73 -0
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/utils/id.d.ts +1 -0
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +8 -0
- package/package.json +1 -1
- package/src/config/types.ts +3 -0
- package/src/hooks/checkout.ts +4 -0
- package/src/index.ts +14 -0
- package/src/interfaces/rest/index.ts +4 -0
- package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
- package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
- package/src/interfaces/rest/routes/catalog.ts +85 -0
- package/src/interfaces/rest/routes/checkout.ts +7 -1
- package/src/interfaces/rest/routes/payments.ts +55 -35
- package/src/interfaces/rest/routes/search.ts +16 -0
- package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
- package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
- package/src/interfaces/rest/schemas/catalog.ts +92 -0
- package/src/interfaces/rest/schemas/search.ts +2 -0
- package/src/kernel/error-mapper.ts +3 -0
- package/src/modules/catalog/entity-service.ts +73 -20
- package/src/modules/catalog/module.ts +15 -0
- package/src/modules/catalog/ownership.ts +17 -0
- package/src/modules/catalog/repository/index.ts +547 -1
- package/src/modules/catalog/revision.ts +35 -0
- package/src/modules/catalog/schema.ts +169 -1
- package/src/modules/catalog/schemas.ts +2 -0
- package/src/modules/catalog/service.ts +738 -13
- package/src/modules/channels/adapter.ts +107 -1
- package/src/modules/media/module.ts +2 -0
- package/src/modules/media/schema.ts +23 -2
- package/src/modules/media/service.ts +84 -23
- package/src/modules/orders/service.ts +3 -0
- package/src/modules/pricing/schema.ts +1 -0
- package/src/modules/pricing/schemas.ts +1 -0
- package/src/modules/pricing/service.ts +7 -2
- package/src/modules/search/adapter.ts +2 -0
- package/src/modules/search/module.ts +2 -0
- package/src/modules/search/service.ts +87 -6
- package/src/utils/id.ts +11 -0
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
CreateOptionTypeInput,
|
|
37
37
|
CreateOptionValueInput,
|
|
38
38
|
CreateVariantInput,
|
|
39
|
+
EntityFieldDefinitionResolver,
|
|
39
40
|
} from "./service.js";
|
|
40
41
|
import type {
|
|
41
42
|
SellableEntity,
|
|
@@ -69,6 +70,7 @@ function getCustomFieldValue(field: SellableCustomField): unknown {
|
|
|
69
70
|
switch (field.fieldType) {
|
|
70
71
|
case "text":
|
|
71
72
|
case "relation":
|
|
73
|
+
case "select":
|
|
72
74
|
return field.textValue;
|
|
73
75
|
case "number":
|
|
74
76
|
return field.numberValue;
|
|
@@ -83,8 +85,14 @@ function getCustomFieldValue(field: SellableCustomField): unknown {
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
type CustomFieldData = Omit<SellableCustomFieldInsert, "entityId" | "fieldName" | "locale">;
|
|
89
|
+
type ValidatedCustomField = { fieldName: string; locale: string; data: CustomFieldData | null };
|
|
90
|
+
|
|
86
91
|
export class EntityService {
|
|
87
|
-
constructor(
|
|
92
|
+
constructor(
|
|
93
|
+
private readonly deps: CatalogServiceDeps,
|
|
94
|
+
private readonly resolveEntityFieldDefinitions: EntityFieldDefinitionResolver,
|
|
95
|
+
) {}
|
|
88
96
|
|
|
89
97
|
private get repo() {
|
|
90
98
|
return this.deps.repository;
|
|
@@ -102,22 +110,38 @@ export class EntityService {
|
|
|
102
110
|
if (entity.sourceStoreId != null) assertPermission(actor, "catalog:sync");
|
|
103
111
|
}
|
|
104
112
|
|
|
105
|
-
private async
|
|
106
|
-
if (!customFields) return Ok(
|
|
113
|
+
private async validateCustomFields(entityType: string, customFields: Record<string, unknown> | undefined, actor: Actor | null, ctx?: TxContext): Promise<Result<ValidatedCustomField[]>> {
|
|
114
|
+
if (!customFields) return Ok([]);
|
|
107
115
|
const entityConfig = this.deps.config.entities?.[entityType];
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
const definitions = await this.resolveEntityFieldDefinitions(entityType, actor ?? ctx?.actor ?? null, ctx);
|
|
117
|
+
if (!entityConfig && definitions.length === 0) return Ok([]);
|
|
118
|
+
const definitionMap = new Map(definitions.map((f) => [f.name, f]));
|
|
119
|
+
const validated: ValidatedCustomField[] = [];
|
|
110
120
|
for (const [name, value] of Object.entries(customFields)) {
|
|
111
121
|
const def = definitionMap.get(name);
|
|
112
122
|
if (!def) return Err(new CommerceValidationError(`Unknown custom field: ${name}`));
|
|
123
|
+
if (value === null) {
|
|
124
|
+
validated.push({ fieldName: name, locale: "en", data: null });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
113
127
|
const type = def.type;
|
|
128
|
+
let normalized = value;
|
|
114
129
|
let valid = false;
|
|
115
130
|
switch (type) {
|
|
116
131
|
case "text":
|
|
117
132
|
case "relation":
|
|
118
|
-
case "select":
|
|
119
133
|
valid = typeof value === "string";
|
|
120
134
|
break;
|
|
135
|
+
case "select": {
|
|
136
|
+
if (typeof value !== "string") break;
|
|
137
|
+
const trimmed = value.trim();
|
|
138
|
+
if (def.options && !def.options.includes(trimmed)) {
|
|
139
|
+
return Err(new CommerceValidationError(`Value "${trimmed}" is not permitted for field ${name}. Allowed: ${def.options.join(", ")}`));
|
|
140
|
+
}
|
|
141
|
+
normalized = trimmed;
|
|
142
|
+
valid = true;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
121
145
|
case "number":
|
|
122
146
|
valid = typeof value === "number";
|
|
123
147
|
break;
|
|
@@ -134,29 +158,50 @@ export class EntityService {
|
|
|
134
158
|
valid = false;
|
|
135
159
|
}
|
|
136
160
|
if (!valid) return Err(new CommerceValidationError(`Custom field ${name} expected type ${type}.`));
|
|
137
|
-
const fieldType =
|
|
138
|
-
const insertData:
|
|
161
|
+
const fieldType = type as SellableCustomField["fieldType"];
|
|
162
|
+
const insertData: CustomFieldData = {
|
|
163
|
+
fieldType,
|
|
164
|
+
source: "merchant",
|
|
165
|
+
status: "approved",
|
|
166
|
+
confidence: null,
|
|
167
|
+
evidence: null,
|
|
168
|
+
approvedAt: null,
|
|
169
|
+
approvedBy: null,
|
|
170
|
+
};
|
|
139
171
|
switch (fieldType) {
|
|
140
172
|
case "text":
|
|
141
173
|
case "relation":
|
|
142
|
-
|
|
174
|
+
case "select":
|
|
175
|
+
insertData.textValue = normalized as string;
|
|
143
176
|
break;
|
|
144
177
|
case "number":
|
|
145
|
-
insertData.numberValue =
|
|
178
|
+
insertData.numberValue = normalized as number;
|
|
146
179
|
break;
|
|
147
180
|
case "boolean":
|
|
148
|
-
insertData.booleanValue =
|
|
181
|
+
insertData.booleanValue = normalized as boolean;
|
|
149
182
|
break;
|
|
150
183
|
case "date":
|
|
151
|
-
insertData.dateValue =
|
|
184
|
+
insertData.dateValue = normalized instanceof Date ? normalized : new Date(normalized as string);
|
|
152
185
|
break;
|
|
153
186
|
case "json":
|
|
154
|
-
insertData.jsonValue =
|
|
187
|
+
insertData.jsonValue = normalized;
|
|
155
188
|
break;
|
|
156
189
|
}
|
|
157
|
-
|
|
190
|
+
validated.push({ fieldName: name, locale: "en", data: insertData });
|
|
191
|
+
}
|
|
192
|
+
return Ok(validated);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async writeCustomFields(entityId: string, customFields: ValidatedCustomField[], ctx?: TxContext, upsert = false): Promise<void> {
|
|
196
|
+
for (const customField of customFields) {
|
|
197
|
+
if (customField.data === null) {
|
|
198
|
+
await this.repo.deleteCustomField(entityId, customField.fieldName, customField.locale, ctx);
|
|
199
|
+
} else if (upsert) {
|
|
200
|
+
await this.repo.upsertCustomField(entityId, customField.fieldName, customField.locale, customField.data, ctx);
|
|
201
|
+
} else {
|
|
202
|
+
await this.repo.createCustomField({ ...customField.data, entityId, fieldName: customField.fieldName, locale: customField.locale }, ctx);
|
|
203
|
+
}
|
|
158
204
|
}
|
|
159
|
-
return Ok(undefined);
|
|
160
205
|
}
|
|
161
206
|
|
|
162
207
|
private async hydrateEntity(entity: SellableEntity, options?: GetOptions, ctx?: TxContext): Promise<CatalogEntityHydrated> {
|
|
@@ -185,14 +230,16 @@ export class EntityService {
|
|
|
185
230
|
}
|
|
186
231
|
if (options?.includeCategories) hydrated.categories = await this.repo.findEntityCategories(entity.id, ctx);
|
|
187
232
|
if (options?.includeBrands) hydrated.brands = await this.repo.findEntityBrands(entity.id, ctx);
|
|
233
|
+
if (options?.includeCustomFields) hydrated.customFields = await this.repo.findCustomFieldsByEntityId(entity.id, ctx);
|
|
188
234
|
if (options?.includeMedia) {
|
|
189
235
|
const mediaService = this.deps.services.media as {
|
|
190
236
|
listEntityMedia?: (
|
|
191
237
|
entityId: string,
|
|
192
238
|
opts?: { orgId?: string },
|
|
239
|
+
ctx?: TxContext,
|
|
193
240
|
) => Promise<{ ok: boolean; value?: CatalogEntityHydrated["media"] }>;
|
|
194
241
|
} | undefined;
|
|
195
|
-
const mediaResult = await mediaService?.listEntityMedia?.(entity.id, { orgId: entity.organizationId });
|
|
242
|
+
const mediaResult = await mediaService?.listEntityMedia?.(entity.id, { orgId: entity.organizationId }, ctx);
|
|
196
243
|
hydrated.media = mediaResult?.ok && mediaResult.value ? mediaResult.value : [];
|
|
197
244
|
}
|
|
198
245
|
if (options?.includePricing) {
|
|
@@ -217,12 +264,15 @@ export class EntityService {
|
|
|
217
264
|
const context: HookContext = createHookContext({ actor, tx: ctx?.tx ?? null, logger: createLogger("catalog.create"), services: this.deps.services, context: { moduleName: "catalog" }, ...hookDatabaseArg(this.deps.database) });
|
|
218
265
|
const processedInput = await runBeforeHooks(beforeHooks, input, "create", context);
|
|
219
266
|
if (processedInput.sourceStoreId != null) assertPermission(actor, "catalog:sync");
|
|
220
|
-
const
|
|
267
|
+
const customFieldsResult = await this.validateCustomFields(processedInput.type, processedInput.customFields, actor, ctx);
|
|
268
|
+
if (!customFieldsResult.ok) return customFieldsResult;
|
|
269
|
+
const statuses = ["draft", "active", "archived", "discontinued"] as const;
|
|
270
|
+
const status = statuses.find((value) => value === processedInput.status) ?? "draft";
|
|
271
|
+
const entity = await this.repo.createEntity({ organizationId: orgId, type: processedInput.type, slug: processedInput.slug, status, isVisible: status === "active", ...(processedInput.sourceStoreId !== undefined ? { sourceStoreId: processedInput.sourceStoreId } : {}), ...(processedInput.taxClass !== undefined ? { taxClass: processedInput.taxClass } : {}), metadata: processedInput.metadata ?? {} }, ctx);
|
|
221
272
|
if (processedInput.attributes) {
|
|
222
273
|
await this.repo.createAttribute({ entityId: entity.id, locale: processedInput.attributes.locale ?? "en", title: processedInput.attributes.title, subtitle: processedInput.attributes.subtitle, description: processedInput.attributes.description, richDescription: processedInput.attributes.richDescription, seoTitle: processedInput.attributes.seoTitle, seoDescription: processedInput.attributes.seoDescription }, ctx);
|
|
223
274
|
}
|
|
224
|
-
|
|
225
|
-
if (!customFieldsResult.ok) return customFieldsResult;
|
|
275
|
+
await this.writeCustomFields(entity.id, customFieldsResult.value, ctx);
|
|
226
276
|
const hookReport = await runAfterHooks(afterHooks, null, entity, "create", context);
|
|
227
277
|
const hydrated = await this.hydrateEntity(entity, undefined, ctx);
|
|
228
278
|
return Ok(hydrated, hookReport.hasErrors ? { hookErrors: hookReport.errors } : undefined);
|
|
@@ -238,8 +288,11 @@ export class EntityService {
|
|
|
238
288
|
const afterHooks = this.deps.hooks.resolve("catalog.afterUpdate") as CatalogUpdateAfterHook[];
|
|
239
289
|
const context: HookContext = createHookContext({ actor, tx: ctx?.tx ?? null, logger: createLogger("catalog.update"), services: this.deps.services, context: { moduleName: "catalog" }, ...hookDatabaseArg(this.deps.database) });
|
|
240
290
|
const processed = await runBeforeHooks(beforeHooks, input, "update", context);
|
|
291
|
+
const customFieldsResult = await this.validateCustomFields(existing.type, processed.customFields, actor, ctx);
|
|
292
|
+
if (!customFieldsResult.ok) return customFieldsResult;
|
|
241
293
|
const updated = await this.repo.updateEntity(id, { ...(processed.slug !== undefined ? { slug: processed.slug } : {}), ...(processed.status !== undefined ? { status: processed.status as SellableEntity["status"] } : {}), ...(processed.taxClass !== undefined ? { taxClass: processed.taxClass } : {}), ...(processed.metadata !== undefined ? { metadata: processed.metadata } : {}), ...(processed.isVisible !== undefined ? { isVisible: processed.isVisible } : {}) }, ctx);
|
|
242
294
|
if (!updated) return Err(new CommerceNotFoundError("Entity not found."));
|
|
295
|
+
await this.writeCustomFields(existing.id, customFieldsResult.value, ctx, true);
|
|
243
296
|
const hookReport = await runAfterHooks(afterHooks, existing, updated, "update", context);
|
|
244
297
|
const hydrated = await this.hydrateEntity(updated, undefined, ctx);
|
|
245
298
|
return Ok(hydrated, hookReport.hasErrors ? { hookErrors: hookReport.errors } : undefined);
|
|
@@ -467,7 +520,7 @@ export class EntityService {
|
|
|
467
520
|
if (!ov) return Err(new CommerceValidationError(`Option value "${optVal}" does not exist for option type "${optName}".`));
|
|
468
521
|
optionValueIds.push(ov.id);
|
|
469
522
|
}
|
|
470
|
-
const variant = await this.repo.createVariant({ entityId: input.entityId, status: "active", sortOrder: 0, metadata: {}, ...(input.sku !== undefined ? { sku: input.sku } : {}), ...(input.taxClass !== undefined ? { taxClass: input.taxClass } : {}) }, ctx);
|
|
523
|
+
const variant = await this.repo.createVariant({ entityId: input.entityId, status: "active", sortOrder: 0, metadata: {}, ...(input.sku !== undefined ? { sku: input.sku } : {}), ...(input.barcode !== undefined ? { barcode: input.barcode } : {}), ...(input.taxClass !== undefined ? { taxClass: input.taxClass } : {}) }, ctx);
|
|
471
524
|
await this.repo.createVariantOptionValues(optionValueIds.map((optionValueId) => ({ variantId: variant.id, optionValueId })), ctx);
|
|
472
525
|
return Ok(variant);
|
|
473
526
|
}
|
|
@@ -6,15 +6,20 @@ import { CatalogRepository } from "./repository/index.js";
|
|
|
6
6
|
import {
|
|
7
7
|
brands,
|
|
8
8
|
categories,
|
|
9
|
+
tags,
|
|
10
|
+
entityTags,
|
|
9
11
|
entityBrands,
|
|
10
12
|
entityCategories,
|
|
11
13
|
optionTypes,
|
|
12
14
|
optionValues,
|
|
13
15
|
sellableAttributes,
|
|
14
16
|
sellableCustomFields,
|
|
17
|
+
entityFieldDefinitions,
|
|
18
|
+
sellableEntityRevisions,
|
|
15
19
|
sellableEntities,
|
|
16
20
|
variantOptionValues,
|
|
17
21
|
variants,
|
|
22
|
+
catalogFieldOwnership,
|
|
18
23
|
} from "./schema.js";
|
|
19
24
|
import { CatalogServiceImpl } from "./service.js";
|
|
20
25
|
|
|
@@ -27,14 +32,19 @@ export const catalogModule = defineModule<
|
|
|
27
32
|
sellableEntities: typeof sellableEntities;
|
|
28
33
|
sellableAttributes: typeof sellableAttributes;
|
|
29
34
|
sellableCustomFields: typeof sellableCustomFields;
|
|
35
|
+
entityFieldDefinitions: typeof entityFieldDefinitions;
|
|
36
|
+
sellableEntityRevisions: typeof sellableEntityRevisions;
|
|
30
37
|
categories: typeof categories;
|
|
31
38
|
entityCategories: typeof entityCategories;
|
|
32
39
|
brands: typeof brands;
|
|
33
40
|
entityBrands: typeof entityBrands;
|
|
41
|
+
tags: typeof tags;
|
|
42
|
+
entityTags: typeof entityTags;
|
|
34
43
|
optionTypes: typeof optionTypes;
|
|
35
44
|
optionValues: typeof optionValues;
|
|
36
45
|
variants: typeof variants;
|
|
37
46
|
variantOptionValues: typeof variantOptionValues;
|
|
47
|
+
catalogFieldOwnership: typeof catalogFieldOwnership;
|
|
38
48
|
},
|
|
39
49
|
CatalogServiceImpl,
|
|
40
50
|
CatalogModuleDeps
|
|
@@ -45,14 +55,19 @@ export const catalogModule = defineModule<
|
|
|
45
55
|
sellableEntities,
|
|
46
56
|
sellableAttributes,
|
|
47
57
|
sellableCustomFields,
|
|
58
|
+
entityFieldDefinitions,
|
|
59
|
+
sellableEntityRevisions,
|
|
48
60
|
categories,
|
|
49
61
|
entityCategories,
|
|
50
62
|
brands,
|
|
51
63
|
entityBrands,
|
|
64
|
+
tags,
|
|
65
|
+
entityTags,
|
|
52
66
|
optionTypes,
|
|
53
67
|
optionValues,
|
|
54
68
|
variants,
|
|
55
69
|
variantOptionValues,
|
|
70
|
+
catalogFieldOwnership,
|
|
56
71
|
}),
|
|
57
72
|
service: (deps) =>
|
|
58
73
|
new CatalogServiceImpl({
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CommerceValidationError } from "../../kernel/errors.js";
|
|
2
|
+
|
|
3
|
+
export type FieldPath = string;
|
|
4
|
+
export type FieldOwner = "platform" | "store" | "shared";
|
|
5
|
+
|
|
6
|
+
const fieldPathPattern = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
|
|
7
|
+
|
|
8
|
+
export function isValidFieldPath(value: string): value is FieldPath {
|
|
9
|
+
return fieldPathPattern.test(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function validateFieldPath(value: string): FieldPath {
|
|
13
|
+
if (!isValidFieldPath(value)) {
|
|
14
|
+
throw new CommerceValidationError("Field path must contain dot-separated alphanumeric, underscore, or hyphen segments.");
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|