@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.
Files changed (136) hide show
  1. package/dist/config/types.d.ts +3 -0
  2. package/dist/config/types.d.ts.map +1 -1
  3. package/dist/hooks/checkout.d.ts +2 -0
  4. package/dist/hooks/checkout.d.ts.map +1 -1
  5. package/dist/hooks/checkout.js +2 -0
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/interfaces/rest/index.d.ts.map +1 -1
  10. package/dist/interfaces/rest/index.js +4 -0
  11. package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts +5 -0
  12. package/dist/interfaces/rest/routes/admin/custom-field-proposals.d.ts.map +1 -0
  13. package/dist/interfaces/rest/routes/admin/custom-field-proposals.js +22 -0
  14. package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts +5 -0
  15. package/dist/interfaces/rest/routes/admin/entity-field-definitions.d.ts.map +1 -0
  16. package/dist/interfaces/rest/routes/admin/entity-field-definitions.js +39 -0
  17. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  18. package/dist/interfaces/rest/routes/catalog.js +37 -2
  19. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  20. package/dist/interfaces/rest/routes/checkout.js +7 -1
  21. package/dist/interfaces/rest/routes/payments.d.ts.map +1 -1
  22. package/dist/interfaces/rest/routes/payments.js +48 -30
  23. package/dist/interfaces/rest/routes/search.d.ts.map +1 -1
  24. package/dist/interfaces/rest/routes/search.js +12 -0
  25. package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts +134 -0
  26. package/dist/interfaces/rest/schemas/admin-custom-field-proposals.d.ts.map +1 -0
  27. package/dist/interfaces/rest/schemas/admin-custom-field-proposals.js +26 -0
  28. package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts +592 -0
  29. package/dist/interfaces/rest/schemas/admin-entity-field-definitions.d.ts.map +1 -0
  30. package/dist/interfaces/rest/schemas/admin-entity-field-definitions.js +109 -0
  31. package/dist/interfaces/rest/schemas/catalog.d.ts +1364 -401
  32. package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
  33. package/dist/interfaces/rest/schemas/catalog.js +86 -0
  34. package/dist/interfaces/rest/schemas/pricing.d.ts +1 -0
  35. package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
  36. package/dist/interfaces/rest/schemas/search.d.ts +1 -0
  37. package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
  38. package/dist/interfaces/rest/schemas/search.js +1 -0
  39. package/dist/kernel/error-mapper.d.ts.map +1 -1
  40. package/dist/kernel/error-mapper.js +3 -0
  41. package/dist/modules/catalog/entity-service.d.ts +5 -3
  42. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  43. package/dist/modules/catalog/entity-service.js +73 -22
  44. package/dist/modules/catalog/module.d.ts +6 -1
  45. package/dist/modules/catalog/module.d.ts.map +1 -1
  46. package/dist/modules/catalog/module.js +6 -1
  47. package/dist/modules/catalog/ownership.d.ts +5 -0
  48. package/dist/modules/catalog/ownership.d.ts.map +1 -0
  49. package/dist/modules/catalog/ownership.js +11 -0
  50. package/dist/modules/catalog/repository/index.d.ts +52 -1
  51. package/dist/modules/catalog/repository/index.d.ts.map +1 -1
  52. package/dist/modules/catalog/repository/index.js +321 -2
  53. package/dist/modules/catalog/revision.d.ts +4 -0
  54. package/dist/modules/catalog/revision.d.ts.map +1 -0
  55. package/dist/modules/catalog/revision.js +33 -0
  56. package/dist/modules/catalog/schema.d.ts +979 -110
  57. package/dist/modules/catalog/schema.d.ts.map +1 -1
  58. package/dist/modules/catalog/schema.js +112 -2
  59. package/dist/modules/catalog/schemas.d.ts +2 -0
  60. package/dist/modules/catalog/schemas.d.ts.map +1 -1
  61. package/dist/modules/catalog/schemas.js +2 -0
  62. package/dist/modules/catalog/service.d.ts +66 -3
  63. package/dist/modules/catalog/service.d.ts.map +1 -1
  64. package/dist/modules/catalog/service.js +512 -11
  65. package/dist/modules/channels/adapter.d.ts +85 -0
  66. package/dist/modules/channels/adapter.d.ts.map +1 -1
  67. package/dist/modules/channels/adapter.js +9 -1
  68. package/dist/modules/media/module.d.ts +68 -0
  69. package/dist/modules/media/module.d.ts.map +1 -1
  70. package/dist/modules/media/module.js +2 -0
  71. package/dist/modules/media/schema.d.ts +70 -0
  72. package/dist/modules/media/schema.d.ts.map +1 -1
  73. package/dist/modules/media/schema.js +20 -2
  74. package/dist/modules/media/service.d.ts +8 -1
  75. package/dist/modules/media/service.d.ts.map +1 -1
  76. package/dist/modules/media/service.js +66 -18
  77. package/dist/modules/orders/service.d.ts +2 -0
  78. package/dist/modules/orders/service.d.ts.map +1 -1
  79. package/dist/modules/orders/service.js +1 -0
  80. package/dist/modules/pricing/schema.d.ts +17 -0
  81. package/dist/modules/pricing/schema.d.ts.map +1 -1
  82. package/dist/modules/pricing/schema.js +1 -0
  83. package/dist/modules/pricing/schemas.d.ts +1 -0
  84. package/dist/modules/pricing/schemas.d.ts.map +1 -1
  85. package/dist/modules/pricing/schemas.js +1 -0
  86. package/dist/modules/pricing/service.d.ts.map +1 -1
  87. package/dist/modules/pricing/service.js +6 -1
  88. package/dist/modules/search/adapter.d.ts +2 -0
  89. package/dist/modules/search/adapter.d.ts.map +1 -1
  90. package/dist/modules/search/module.d.ts.map +1 -1
  91. package/dist/modules/search/module.js +2 -0
  92. package/dist/modules/search/service.d.ts +4 -0
  93. package/dist/modules/search/service.d.ts.map +1 -1
  94. package/dist/modules/search/service.js +74 -5
  95. package/dist/runtime/kernel-modules.d.ts +73 -0
  96. package/dist/runtime/kernel-modules.d.ts.map +1 -1
  97. package/dist/utils/id.d.ts +1 -0
  98. package/dist/utils/id.d.ts.map +1 -1
  99. package/dist/utils/id.js +8 -0
  100. package/package.json +1 -1
  101. package/src/config/types.ts +3 -0
  102. package/src/hooks/checkout.ts +4 -0
  103. package/src/index.ts +14 -0
  104. package/src/interfaces/rest/index.ts +4 -0
  105. package/src/interfaces/rest/routes/admin/custom-field-proposals.ts +37 -0
  106. package/src/interfaces/rest/routes/admin/entity-field-definitions.ts +89 -0
  107. package/src/interfaces/rest/routes/catalog.ts +85 -0
  108. package/src/interfaces/rest/routes/checkout.ts +7 -1
  109. package/src/interfaces/rest/routes/payments.ts +55 -35
  110. package/src/interfaces/rest/routes/search.ts +16 -0
  111. package/src/interfaces/rest/schemas/admin-custom-field-proposals.ts +29 -0
  112. package/src/interfaces/rest/schemas/admin-entity-field-definitions.ts +120 -0
  113. package/src/interfaces/rest/schemas/catalog.ts +92 -0
  114. package/src/interfaces/rest/schemas/search.ts +2 -0
  115. package/src/kernel/error-mapper.ts +3 -0
  116. package/src/modules/catalog/entity-service.ts +73 -20
  117. package/src/modules/catalog/module.ts +15 -0
  118. package/src/modules/catalog/ownership.ts +17 -0
  119. package/src/modules/catalog/repository/index.ts +547 -1
  120. package/src/modules/catalog/revision.ts +35 -0
  121. package/src/modules/catalog/schema.ts +169 -1
  122. package/src/modules/catalog/schemas.ts +2 -0
  123. package/src/modules/catalog/service.ts +738 -13
  124. package/src/modules/channels/adapter.ts +107 -1
  125. package/src/modules/media/module.ts +2 -0
  126. package/src/modules/media/schema.ts +23 -2
  127. package/src/modules/media/service.ts +84 -23
  128. package/src/modules/orders/service.ts +3 -0
  129. package/src/modules/pricing/schema.ts +1 -0
  130. package/src/modules/pricing/schemas.ts +1 -0
  131. package/src/modules/pricing/service.ts +7 -2
  132. package/src/modules/search/adapter.ts +2 -0
  133. package/src/modules/search/module.ts +2 -0
  134. package/src/modules/search/service.ts +87 -6
  135. package/src/utils/id.ts +11 -0
  136. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -5,9 +5,12 @@ import {
5
5
  index,
6
6
  integer,
7
7
  jsonb,
8
+ numeric,
9
+ pgEnum,
8
10
  pgTable,
9
11
  text,
10
12
  timestamp,
13
+ unique,
11
14
  uniqueIndex,
12
15
  uuid,
13
16
  } from "drizzle-orm/pg-core";
@@ -77,15 +80,35 @@ export const sellableCustomFields = pgTable(
77
80
  .notNull(),
78
81
  fieldName: text("field_name").notNull(),
79
82
  fieldType: text("field_type", {
80
- enum: ["text", "number", "boolean", "date", "json", "relation"],
83
+ enum: ["text", "number", "boolean", "date", "json", "relation", "select"],
81
84
  }).notNull(),
85
+ source: text("source", {
86
+ enum: ["merchant", "import", "enrichment", "rule"],
87
+ }).notNull().default("merchant"),
88
+ status: text("status", {
89
+ enum: ["proposed", "approved", "rejected"],
90
+ }).notNull().default("approved"),
91
+ confidence: numeric("confidence", { precision: 4, scale: 3 }),
92
+ evidence: jsonb("evidence").$type<Record<string, unknown>>(),
93
+ locale: text("locale").notNull().default("en"),
94
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
95
+ approvedBy: text("approved_by"),
82
96
  textValue: text("text_value"),
83
97
  numberValue: integer("number_value"),
84
98
  booleanValue: boolean("boolean_value"),
85
99
  dateValue: timestamp("date_value", { withTimezone: true }),
86
100
  jsonValue: jsonb("json_value"),
101
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
102
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
87
103
  },
88
104
  (table) => ({
105
+ liveUnique: uniqueIndex("sellable_custom_fields_live_unique")
106
+ .on(
107
+ table.entityId,
108
+ table.fieldName,
109
+ table.locale,
110
+ )
111
+ .where(sql`${table.status} = 'approved'`),
89
112
  entityFieldIdx: index("idx_custom_fields_entity_field").on(
90
113
  table.entityId,
91
114
  table.fieldName,
@@ -101,6 +124,41 @@ export const sellableCustomFields = pgTable(
101
124
  }),
102
125
  );
103
126
 
127
+ export const entityFieldDefinitions = pgTable(
128
+ "entity_field_definitions",
129
+ {
130
+ id: uuid("id").defaultRandom().primaryKey(),
131
+ organizationId: text("organization_id")
132
+ .notNull()
133
+ .references(() => organization.id, { onDelete: "cascade" }),
134
+ entityType: text("entity_type").notNull(),
135
+ name: text("name").notNull(),
136
+ type: text("type", {
137
+ enum: ["text", "number", "boolean", "date", "json", "relation", "select"],
138
+ }).notNull(),
139
+ unit: text("unit"),
140
+ options: jsonb("options").$type<string[]>(),
141
+ target: text("target"),
142
+ filterable: boolean("filterable").notNull().default(false),
143
+ localized: boolean("localized").notNull().default(false),
144
+ status: text("status", { enum: ["active", "archived"] })
145
+ .notNull()
146
+ .default("active"),
147
+ sortOrder: integer("sort_order").notNull().default(0),
148
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
149
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
150
+ },
151
+ (table) => ({
152
+ organizationIdx: index("idx_entity_field_definitions_organization").on(table.organizationId),
153
+ entityTypeIdx: index("idx_entity_field_definitions_entity_type").on(table.organizationId, table.entityType),
154
+ organizationEntityTypeNameUnique: uniqueIndex("entity_field_definitions_org_type_name_unique").on(
155
+ table.organizationId,
156
+ table.entityType,
157
+ table.name,
158
+ ),
159
+ }),
160
+ );
161
+
104
162
  export const categories = pgTable(
105
163
  "categories",
106
164
  {
@@ -161,6 +219,85 @@ export const entityBrands = pgTable("entity_brands", {
161
219
  entityBrandUnique: uniqueIndex("entity_brands_entity_brand_unique").on(table.entityId, table.brandId),
162
220
  }));
163
221
 
222
+ export const tags = pgTable(
223
+ "tags",
224
+ {
225
+ id: uuid("id").defaultRandom().primaryKey(),
226
+ organizationId: text("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
227
+ slug: text("slug").notNull(),
228
+ displayName: text("display_name").notNull(),
229
+ },
230
+ (table) => ({
231
+ orgIdx: index("idx_tags_org").on(table.organizationId),
232
+ orgSlugUnique: uniqueIndex("tags_org_slug_unique").on(table.organizationId, table.slug),
233
+ }),
234
+ );
235
+
236
+ export const entityTags = pgTable("entity_tags", {
237
+ entityId: uuid("entity_id")
238
+ .references(() => sellableEntities.id, { onDelete: "cascade" })
239
+ .notNull(),
240
+ tagId: uuid("tag_id")
241
+ .references(() => tags.id, { onDelete: "cascade" })
242
+ .notNull(),
243
+ }, (table) => ({
244
+ entityTagUnique: uniqueIndex("entity_tags_entity_tag_unique").on(table.entityId, table.tagId),
245
+ }));
246
+
247
+ export const sellableEntityRevisionReason = pgEnum("sellable_entity_revision_reason", [
248
+ "create",
249
+ "update",
250
+ "import",
251
+ "enrichment",
252
+ "push",
253
+ "restore",
254
+ ]);
255
+
256
+ export type SellableEntityRevisionReason = (typeof sellableEntityRevisionReason.enumValues)[number];
257
+
258
+ export type SellableEntityRevisionSnapshot = {
259
+ entity: Record<string, unknown>;
260
+ attributes: Array<Record<string, unknown>>;
261
+ customFields: Array<Record<string, unknown>>;
262
+ media: Array<Record<string, unknown>>;
263
+ categories: Array<Record<string, unknown>>;
264
+ brands: Array<Record<string, unknown>>;
265
+ // Optional: snapshots written before tags existed do not carry the key.
266
+ tags?: Array<Record<string, unknown>>;
267
+ };
268
+
269
+ export const sellableEntityRevisions = pgTable(
270
+ "sellable_entity_revisions",
271
+ {
272
+ id: uuid("id").defaultRandom().primaryKey(),
273
+ organizationId: text("organization_id")
274
+ .references(() => organization.id, { onDelete: "cascade" })
275
+ .notNull(),
276
+ entityId: uuid("entity_id")
277
+ .references(() => sellableEntities.id, { onDelete: "cascade" })
278
+ .notNull(),
279
+ revision: integer("revision").notNull(),
280
+ snapshot: jsonb("snapshot").$type<SellableEntityRevisionSnapshot>().notNull(),
281
+ reason: sellableEntityRevisionReason("reason").notNull(),
282
+ pinned: boolean("pinned").notNull().default(false),
283
+ actorId: text("actor_id"),
284
+ actorType: text("actor_type"),
285
+ requestId: text("request_id"),
286
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
287
+ },
288
+ (table) => ({
289
+ entityRevisionUnique: uniqueIndex("sellable_entity_revisions_entity_revision_unique").on(
290
+ table.entityId,
291
+ table.revision,
292
+ ),
293
+ entityCreatedIdx: index("idx_sellable_entity_revisions_entity_created").on(
294
+ table.entityId,
295
+ table.createdAt,
296
+ ),
297
+ organizationIdx: index("idx_sellable_entity_revisions_organization").on(table.organizationId),
298
+ }),
299
+ );
300
+
164
301
  export const optionTypes = pgTable("option_types", {
165
302
  id: uuid("id").defaultRandom().primaryKey(),
166
303
  entityId: uuid("entity_id")
@@ -223,3 +360,34 @@ export const variantOptionValues = pgTable("variant_option_values", {
223
360
  }, (table) => ({
224
361
  variantOptionValueUnique: uniqueIndex("variant_option_values_variant_option_unique").on(table.variantId, table.optionValueId),
225
362
  }));
363
+
364
+ export const catalogFieldOwner = pgEnum("catalog_field_owner", ["platform", "store", "shared"]);
365
+
366
+ export const catalogFieldOwnership = pgTable(
367
+ "catalog_field_ownership",
368
+ {
369
+ id: uuid("id").defaultRandom().primaryKey(),
370
+ organizationId: text("organization_id")
371
+ .notNull()
372
+ .references(() => organization.id, { onDelete: "cascade" }),
373
+ entityId: uuid("entity_id")
374
+ .notNull()
375
+ .references(() => sellableEntities.id, { onDelete: "cascade" }),
376
+ variantId: uuid("variant_id").references(() => variants.id, { onDelete: "cascade" }),
377
+ storeId: text("store_id"),
378
+ fieldPath: text("field_path").notNull(),
379
+ owner: catalogFieldOwner("owner").notNull(),
380
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
381
+ },
382
+ (table) => ({
383
+ ownershipUnique: unique("catalog_field_ownership_key")
384
+ .on(table.organizationId, table.entityId, table.variantId, table.storeId, table.fieldPath)
385
+ .nullsNotDistinct(),
386
+ entityStoreFieldIdx: index("idx_catalog_field_ownership_entity_store_field").on(
387
+ table.entityId,
388
+ table.storeId,
389
+ table.variantId,
390
+ table.fieldPath,
391
+ ),
392
+ }),
393
+ );
@@ -28,6 +28,7 @@ export const UpdateEntityBodySchema = z.object({
28
28
  taxClass: z.string().nullable().optional(),
29
29
  metadata: z.record(z.string(), z.unknown()).optional(),
30
30
  isVisible: z.boolean().optional(),
31
+ customFields: z.record(z.string(), z.unknown().nullable()).optional(),
31
32
  }).openapi("UpdateEntityBody");
32
33
 
33
34
  export const SetAttributesBodySchema = z.record(z.string(), z.unknown()).openapi("SetAttributesBody");
@@ -69,6 +70,7 @@ export const CreateOptionValueBodySchema = z.object({
69
70
 
70
71
  export const CreateVariantBodySchema = z.object({
71
72
  sku: z.string().optional().openapi({ example: "SKU-001" }),
73
+ barcode: z.string().optional().openapi({ example: "0123456789012" }),
72
74
  options: z.record(z.string(), z.string()).openapi({ example: { Color: "Red" } }),
73
75
  price: z.number().optional().openapi({ example: 34.99 }),
74
76
  taxClass: z.string().optional().openapi({ example: "zero", description: "Overrides the entity's tax class (issue #57)." }),