includio-cms 0.26.0 → 0.28.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 (128) hide show
  1. package/API.md +58 -2
  2. package/CHANGELOG.md +105 -0
  3. package/DOCS.md +1 -1
  4. package/ROADMAP.md +8 -0
  5. package/dist/admin/auth-client.d.ts +42 -42
  6. package/dist/admin/client/admin/admin-layout.svelte +12 -2
  7. package/dist/admin/client/admin/admin-layout.svelte.d.ts +2 -1
  8. package/dist/admin/client/collection/data-table.svelte +0 -39
  9. package/dist/admin/client/collection/data-table.svelte.d.ts +0 -2
  10. package/dist/admin/client/shop/coupon-schema.d.ts +1 -1
  11. package/dist/admin/client/shop/refund-dialog.svelte +37 -1
  12. package/dist/admin/client/shop/refund-dialog.svelte.d.ts +3 -0
  13. package/dist/admin/client/shop/shop-order-detail-page.svelte +192 -0
  14. package/dist/admin/components/fields/field-renderer.svelte +6 -1
  15. package/dist/admin/components/fields/icon-field.svelte +86 -0
  16. package/dist/admin/components/fields/icon-field.svelte.d.ts +8 -0
  17. package/dist/admin/components/fields/icon-picker-dialog.svelte +174 -0
  18. package/dist/admin/components/fields/icon-picker-dialog.svelte.d.ts +11 -0
  19. package/dist/admin/components/fields/object-field.svelte +27 -7
  20. package/dist/admin/components/fields/shop-field.svelte +210 -20
  21. package/dist/admin/components/layout/layout-tabs.svelte +1 -0
  22. package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte +109 -0
  23. package/dist/admin/components/variant-form/VariantAttributeRenderer.svelte.d.ts +9 -0
  24. package/dist/admin/helpers/build-icon-set-map.d.ts +8 -0
  25. package/dist/admin/helpers/build-icon-set-map.js +16 -0
  26. package/dist/admin/helpers/index.d.ts +2 -0
  27. package/dist/admin/helpers/index.js +2 -0
  28. package/dist/admin/remote/shop.remote.d.ts +116 -24
  29. package/dist/admin/remote/shop.remote.js +79 -6
  30. package/dist/admin/state/icon-sets.svelte.d.ts +9 -0
  31. package/dist/admin/state/icon-sets.svelte.js +20 -0
  32. package/dist/cli/scaffold/admin.js +2 -2
  33. package/dist/components/ui/checkbox/checkbox.svelte +3 -3
  34. package/dist/core/cms.d.ts +11 -2
  35. package/dist/core/cms.js +29 -0
  36. package/dist/core/fields/fieldSchemaToTs.js +7 -0
  37. package/dist/core/server/generator/fields.d.ts +2 -0
  38. package/dist/core/server/generator/fields.js +34 -1
  39. package/dist/core/server/generator/generator.js +2 -1
  40. package/dist/db-postgres/schema/shop/index.d.ts +1 -0
  41. package/dist/db-postgres/schema/shop/index.js +1 -0
  42. package/dist/db-postgres/schema/shop/invoice.d.ts +254 -0
  43. package/dist/db-postgres/schema/shop/invoice.js +27 -0
  44. package/dist/db-postgres/schema/shop/order.d.ts +107 -1
  45. package/dist/db-postgres/schema/shop/order.js +7 -1
  46. package/dist/db-postgres/schema/shop/payment.d.ts +20 -0
  47. package/dist/db-postgres/schema/shop/payment.js +4 -1
  48. package/dist/db-postgres/schema/shop/product.d.ts +20 -0
  49. package/dist/db-postgres/schema/shop/product.js +3 -1
  50. package/dist/db-postgres/schema/shop/productVariant.d.ts +12 -2
  51. package/dist/db-postgres/schema/shop/productVariant.js +22 -0
  52. package/dist/paraglide/messages/_index.d.ts +36 -3
  53. package/dist/paraglide/messages/_index.js +71 -3
  54. package/dist/paraglide/messages/en.d.ts +5 -0
  55. package/dist/paraglide/messages/en.js +14 -0
  56. package/dist/paraglide/messages/pl.d.ts +5 -0
  57. package/dist/paraglide/messages/pl.js +14 -0
  58. package/dist/shop/adapters/fakturownia/client.d.ts +28 -0
  59. package/dist/shop/adapters/fakturownia/client.js +67 -0
  60. package/dist/shop/adapters/fakturownia/index.d.ts +27 -0
  61. package/dist/shop/adapters/fakturownia/index.js +36 -0
  62. package/dist/shop/adapters/fakturownia/payload.d.ts +35 -0
  63. package/dist/shop/adapters/fakturownia/payload.js +45 -0
  64. package/dist/shop/cart/types.d.ts +1 -0
  65. package/dist/shop/client/index.d.ts +61 -0
  66. package/dist/shop/client/index.js +5 -1
  67. package/dist/shop/expiry.d.ts +35 -0
  68. package/dist/shop/expiry.js +68 -0
  69. package/dist/shop/http/balance-handler.d.ts +20 -0
  70. package/dist/shop/http/balance-handler.js +91 -0
  71. package/dist/shop/http/cart-handler.js +19 -0
  72. package/dist/shop/http/checkout-handler.js +30 -1
  73. package/dist/shop/http/index.d.ts +2 -0
  74. package/dist/shop/http/index.js +2 -0
  75. package/dist/shop/http/upcoming-handler.d.ts +16 -0
  76. package/dist/shop/http/upcoming-handler.js +65 -0
  77. package/dist/shop/http/webhook-handler.js +46 -9
  78. package/dist/shop/index.d.ts +7 -1
  79. package/dist/shop/index.js +10 -1
  80. package/dist/shop/nip.d.ts +12 -0
  81. package/dist/shop/nip.js +23 -0
  82. package/dist/shop/server/balance-payment.d.ts +40 -0
  83. package/dist/shop/server/balance-payment.js +140 -0
  84. package/dist/shop/server/cart-hydrate.js +2 -0
  85. package/dist/shop/server/init.d.ts +14 -0
  86. package/dist/shop/server/init.js +35 -0
  87. package/dist/shop/server/invoices.d.ts +64 -0
  88. package/dist/shop/server/invoices.js +237 -0
  89. package/dist/shop/server/orders.d.ts +38 -0
  90. package/dist/shop/server/orders.js +152 -2
  91. package/dist/shop/server/payment-policy.d.ts +35 -0
  92. package/dist/shop/server/payment-policy.js +55 -0
  93. package/dist/shop/server/payments.d.ts +29 -0
  94. package/dist/shop/server/payments.js +64 -0
  95. package/dist/shop/server/populate.d.ts +1 -1
  96. package/dist/shop/server/refund.d.ts +17 -12
  97. package/dist/shop/server/refund.js +96 -13
  98. package/dist/shop/server/shop-data.d.ts +4 -1
  99. package/dist/shop/server/shop-data.js +24 -2
  100. package/dist/shop/template.d.ts +13 -0
  101. package/dist/shop/template.js +98 -0
  102. package/dist/shop/types.d.ts +208 -1
  103. package/dist/shop/variant-attributes.d.ts +28 -0
  104. package/dist/shop/variant-attributes.js +69 -0
  105. package/dist/sveltekit/server/index.d.ts +1 -0
  106. package/dist/sveltekit/server/index.js +2 -0
  107. package/dist/types/cms.d.ts +4 -3
  108. package/dist/types/cms.schema.d.ts +1 -1
  109. package/dist/types/cms.schema.js +9 -0
  110. package/dist/types/fields.d.ts +21 -2
  111. package/dist/types/index.d.ts +1 -1
  112. package/dist/types/index.js +1 -1
  113. package/dist/types/plugins.d.ts +40 -0
  114. package/dist/types/plugins.js +4 -1
  115. package/dist/updates/0.26.1/index.d.ts +2 -0
  116. package/dist/updates/0.26.1/index.js +19 -0
  117. package/dist/updates/0.27.0/index.d.ts +2 -0
  118. package/dist/updates/0.27.0/index.js +50 -0
  119. package/dist/updates/0.28.0/index.d.ts +2 -0
  120. package/dist/updates/0.28.0/index.js +38 -0
  121. package/dist/updates/index.js +7 -1
  122. package/package.json +1 -1
  123. package/dist/paraglide/messages/hello_world.d.ts +0 -5
  124. package/dist/paraglide/messages/hello_world.js +0 -33
  125. package/dist/paraglide/messages/login_hello.d.ts +0 -16
  126. package/dist/paraglide/messages/login_hello.js +0 -34
  127. package/dist/paraglide/messages/login_please_login.d.ts +0 -16
  128. package/dist/paraglide/messages/login_please_login.js +0 -34
@@ -8,6 +8,9 @@ export declare const getShopConfig: import("@sveltejs/kit").RemoteQueryFunction<
8
8
  id: string;
9
9
  label: import("../../shop/types.js").I18nText;
10
10
  }[];
11
+ variantAttributes: Record<string, import("../../shop/types.js").VariantAttribute>;
12
+ variantLabel: import("../../shop/types.js").VariantLabelConfig | null;
13
+ variantExpiry: import("../../shop/types.js").VariantExpiryConfig | null;
11
14
  } | null>;
12
15
  export declare const listShopProductEntries: import("@sveltejs/kit").RemoteQueryFunction<void, import("../../shop/server/shop-data.js").ShopEntryListItem[]>;
13
16
  export declare const getShopDataForEntry: import("@sveltejs/kit").RemoteQueryFunction<string, import("../../shop/server/shop-data.js").ShopDataWithVariants | null>;
@@ -18,6 +21,18 @@ export declare const upsertShopDataForEntry: import("@sveltejs/kit").RemoteComma
18
21
  vatRate: number;
19
22
  isActive?: boolean | undefined;
20
23
  sortOrder?: number | null | undefined;
24
+ paymentPolicy?: {
25
+ type: "full";
26
+ } | {
27
+ type: "deposit";
28
+ depositAmount: {
29
+ type: "percent";
30
+ value: number;
31
+ } | {
32
+ type: "amount";
33
+ value: number;
34
+ };
35
+ } | null | undefined;
21
36
  };
22
37
  variants?: {
23
38
  id?: string | undefined;
@@ -25,7 +40,7 @@ export declare const upsertShopDataForEntry: import("@sveltejs/kit").RemoteComma
25
40
  name?: Record<string, string> | null | undefined;
26
41
  priceDelta?: number | undefined;
27
42
  stock?: number | null | undefined;
28
- attributes?: Record<string, string> | null | undefined;
43
+ attributes?: Record<string, unknown> | null | undefined;
29
44
  }[] | undefined;
30
45
  }, Promise<import("../../shop/server/shop-data.js").ShopDataWithVariants>>;
31
46
  export declare const deleteShopDataForEntry: import("@sveltejs/kit").RemoteCommand<string, Promise<{
@@ -84,17 +99,26 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
84
99
  } | undefined, {
85
100
  items: {
86
101
  number: string;
102
+ currency: string;
103
+ consents: {
104
+ id: string;
105
+ accepted: boolean;
106
+ label: string;
107
+ }[] | null;
87
108
  id: string;
88
109
  status: import("../../shop/types.js").OrderStatus;
89
110
  createdAt: Date;
90
111
  updatedAt: Date;
91
112
  language: string | null;
92
113
  carrierType: string | null;
93
- currency: string;
94
114
  customerEmail: string;
95
115
  customerName: string | null;
96
116
  customerPhone: string | null;
117
+ customerNip: string | null;
118
+ customerCompanyName: string | null;
97
119
  shippingAddress: Record<string, string> | null;
120
+ billingAddress: Record<string, string> | null;
121
+ invoiceRequested: boolean;
98
122
  totalNet: number;
99
123
  totalGross: number;
100
124
  vatAmount: number;
@@ -108,13 +132,10 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
108
132
  shipmentCreatedAt: Date | null;
109
133
  paymentMethod: string | null;
110
134
  paymentProviderRef: string | null;
111
- consents: {
112
- id: string;
113
- accepted: boolean;
114
- label: string;
115
- }[] | null;
116
135
  notes: string | null;
117
136
  accessToken: string;
137
+ partialPayment: import("../../shop/types.js").PartialPayment | null;
138
+ balanceOwed: boolean;
118
139
  }[];
119
140
  total: number;
120
141
  limit: number;
@@ -123,17 +144,26 @@ export declare const listOrdersAdmin: import("@sveltejs/kit").RemoteQueryFunctio
123
144
  export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
124
145
  order: {
125
146
  number: string;
147
+ currency: string;
148
+ consents: {
149
+ id: string;
150
+ accepted: boolean;
151
+ label: string;
152
+ }[] | null;
126
153
  id: string;
127
154
  status: import("../../shop/types.js").OrderStatus;
128
155
  createdAt: Date;
129
156
  updatedAt: Date;
130
157
  language: string | null;
131
158
  carrierType: string | null;
132
- currency: string;
133
159
  customerEmail: string;
134
160
  customerName: string | null;
135
161
  customerPhone: string | null;
162
+ customerNip: string | null;
163
+ customerCompanyName: string | null;
136
164
  shippingAddress: Record<string, string> | null;
165
+ billingAddress: Record<string, string> | null;
166
+ invoiceRequested: boolean;
137
167
  totalNet: number;
138
168
  totalGross: number;
139
169
  vatAmount: number;
@@ -147,13 +177,10 @@ export declare const getOrderForAdmin: import("@sveltejs/kit").RemoteQueryFuncti
147
177
  shipmentCreatedAt: Date | null;
148
178
  paymentMethod: string | null;
149
179
  paymentProviderRef: string | null;
150
- consents: {
151
- id: string;
152
- accepted: boolean;
153
- label: string;
154
- }[] | null;
155
180
  notes: string | null;
156
181
  accessToken: string;
182
+ partialPayment: import("../../shop/types.js").PartialPayment | null;
183
+ balanceOwed: boolean;
157
184
  };
158
185
  items: {
159
186
  id: string;
@@ -182,17 +209,26 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
182
209
  note?: string | undefined;
183
210
  }, Promise<{
184
211
  number: string;
212
+ currency: string;
213
+ consents: {
214
+ id: string;
215
+ accepted: boolean;
216
+ label: string;
217
+ }[] | null;
185
218
  id: string;
186
219
  status: import("../../shop/types.js").OrderStatus;
187
220
  createdAt: Date;
188
221
  updatedAt: Date;
189
222
  language: string | null;
190
223
  carrierType: string | null;
191
- currency: string;
192
224
  customerEmail: string;
193
225
  customerName: string | null;
194
226
  customerPhone: string | null;
227
+ customerNip: string | null;
228
+ customerCompanyName: string | null;
195
229
  shippingAddress: Record<string, string> | null;
230
+ billingAddress: Record<string, string> | null;
231
+ invoiceRequested: boolean;
196
232
  totalNet: number;
197
233
  totalGross: number;
198
234
  vatAmount: number;
@@ -206,13 +242,10 @@ export declare const updateOrderStatusCmd: import("@sveltejs/kit").RemoteCommand
206
242
  shipmentCreatedAt: Date | null;
207
243
  paymentMethod: string | null;
208
244
  paymentProviderRef: string | null;
209
- consents: {
210
- id: string;
211
- accepted: boolean;
212
- label: string;
213
- }[] | null;
214
245
  notes: string | null;
215
246
  accessToken: string;
247
+ partialPayment: import("../../shop/types.js").PartialPayment | null;
248
+ balanceOwed: boolean;
216
249
  }>>;
217
250
  export declare const resendOrderEmailCmd: import("@sveltejs/kit").RemoteCommand<{
218
251
  orderId: string;
@@ -253,15 +286,15 @@ export declare const listShopableCollections: import("@sveltejs/kit").RemoteQuer
253
286
  }[]>;
254
287
  export declare const getOrderRefundsAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
255
288
  refunds: {
289
+ amount: number;
290
+ currency: string;
256
291
  id: string;
257
292
  status: import("../../db-postgres/schema/shop/index.js").ShopRefundStatus;
258
293
  createdAt: Date;
259
294
  updatedAt: Date;
260
- currency: string;
261
295
  orderId: string;
262
296
  provider: string;
263
297
  providerRef: string | null;
264
- amount: number;
265
298
  paymentId: string | null;
266
299
  reason: string | null;
267
300
  createdBy: string | null;
@@ -277,6 +310,8 @@ export declare const refundOrderCmd: import("@sveltejs/kit").RemoteCommand<{
277
310
  orderId: string;
278
311
  amount?: number | undefined;
279
312
  reason?: string | undefined;
313
+ kind?: "full" | "deposit" | "balance" | undefined;
314
+ releaseStock?: boolean | undefined;
280
315
  }, Promise<{
281
316
  refund: import("../../shop/server/refund.js").ShopRefundRow;
282
317
  remainingRefundable: number;
@@ -286,9 +321,66 @@ export declare const refundOrderCmd: import("@sveltejs/kit").RemoteCommand<{
286
321
  error?: undefined;
287
322
  } | {
288
323
  success: false;
289
- code: "order_not_found" | "order_not_paid" | "no_provider_ref" | "unknown_provider" | "refund_unsupported" | "invalid_amount" | "amount_exceeds_remaining" | "provider_error";
324
+ code: "order_not_found" | "order_not_paid" | "no_provider_ref" | "unknown_provider" | "refund_unsupported" | "invalid_amount" | "amount_exceeds_remaining" | "provider_error" | "no_payment_kind";
290
325
  error: string;
291
326
  }>>;
327
+ export declare const getOrderInvoiceAdmin: import("@sveltejs/kit").RemoteQueryFunction<string, {
328
+ invoice: {
329
+ number: string | null;
330
+ raw: unknown;
331
+ id: string;
332
+ status: import("../../db-postgres/schema/shop/index.js").ShopInvoiceStatus;
333
+ createdAt: Date;
334
+ updatedAt: Date;
335
+ orderId: string;
336
+ provider: string;
337
+ kind: string;
338
+ externalId: string | null;
339
+ pdfUrl: string | null;
340
+ attempts: number;
341
+ nextRetryAt: Date | null;
342
+ lastError: string | null;
343
+ } | null;
344
+ invoicingEnabled: boolean;
345
+ }>;
346
+ export declare const issueInvoiceCmd: import("@sveltejs/kit").RemoteCommand<{
347
+ orderId: string;
348
+ force?: boolean | undefined;
349
+ }, Promise<{
350
+ success: true;
351
+ invoice: {
352
+ number: string | null;
353
+ raw: unknown;
354
+ id: string;
355
+ status: import("../../db-postgres/schema/shop/index.js").ShopInvoiceStatus;
356
+ createdAt: Date;
357
+ updatedAt: Date;
358
+ orderId: string;
359
+ provider: string;
360
+ kind: string;
361
+ externalId: string | null;
362
+ pdfUrl: string | null;
363
+ attempts: number;
364
+ nextRetryAt: Date | null;
365
+ lastError: string | null;
366
+ } | null;
367
+ error?: undefined;
368
+ } | {
369
+ success: false;
370
+ error: string;
371
+ invoice?: undefined;
372
+ }>>;
373
+ export declare const generateBalanceLinkForOrder: import("@sveltejs/kit").RemoteCommand<string, Promise<{
374
+ success: false;
375
+ error: string;
376
+ url?: undefined;
377
+ balanceAmount?: undefined;
378
+ } | {
379
+ success: true;
380
+ url: string;
381
+ balanceAmount: number;
382
+ error?: undefined;
383
+ }>>;
292
384
  export declare const listCouponsAdmin: import("@sveltejs/kit").RemoteQueryFunction<void, {
293
385
  id: string;
294
386
  code: string;
@@ -317,7 +409,7 @@ export declare const getCouponAdmin: import("@sveltejs/kit").RemoteQueryFunction
317
409
  }>;
318
410
  export declare const createCouponCmd: import("@sveltejs/kit").RemoteCommand<{
319
411
  code: string;
320
- type: "fixed" | "percent";
412
+ type: "percent" | "fixed";
321
413
  value: number;
322
414
  minOrderAmount?: number | null | undefined;
323
415
  maxUses?: number | null | undefined;
@@ -340,7 +432,7 @@ export declare const updateCouponCmd: import("@sveltejs/kit").RemoteCommand<{
340
432
  id: string;
341
433
  input: {
342
434
  code?: string | undefined;
343
- type?: "fixed" | "percent" | undefined;
435
+ type?: "percent" | "fixed" | undefined;
344
436
  value?: number | undefined;
345
437
  minOrderAmount?: number | null | undefined;
346
438
  maxUses?: number | null | undefined;
@@ -6,6 +6,7 @@ import { createShippingMethod, deleteShippingMethod, getShippingMethod, listShip
6
6
  import { countOrders, getOrderById, getOrderItems, getOrderStatusHistory, listOrders, updateOrderStatus } from '../../shop/server/orders.js';
7
7
  import { cancelShipmentForOrder, createShipmentForOrder } from '../../shop/server/shipments.js';
8
8
  import { sendOrderStatusEmail } from '../../shop/server/email.js';
9
+ import { getInvoiceByOrderId, issueInvoiceForOrder } from '../../shop/server/invoices.js';
9
10
  import { getRefundedAmount, listRefunds, refundOrder, RefundError } from '../../shop/server/refund.js';
10
11
  import { getShopDb } from '../../shop/server/db.js';
11
12
  import { shopCouponsTable } from '../../db-postgres/schema/shop/index.js';
@@ -24,7 +25,10 @@ export const getShopConfig = query(async () => {
24
25
  vatRates: shop.vatRates,
25
26
  features: shop.features,
26
27
  languages: getCMS().languages,
27
- paymentMethods: shop.payment.map((p) => ({ id: p.id, label: p.label }))
28
+ paymentMethods: shop.payment.map((p) => ({ id: p.id, label: p.label })),
29
+ variantAttributes: shop.variantAttributes,
30
+ variantLabel: shop.variantLabel,
31
+ variantExpiry: shop.variantExpiry
28
32
  };
29
33
  });
30
34
  export const listShopProductEntries = query(async () => {
@@ -35,11 +39,20 @@ export const getShopDataForEntry = query(z.string(), async (entryId) => {
35
39
  requireAuth();
36
40
  return getShopDataByEntry(entryId);
37
41
  });
42
+ const depositAmountSchema = z.discriminatedUnion('type', [
43
+ z.object({ type: z.literal('percent'), value: z.number().positive().max(100) }),
44
+ z.object({ type: z.literal('amount'), value: z.number().int().positive() })
45
+ ]);
46
+ const paymentPolicySchema = z.discriminatedUnion('type', [
47
+ z.object({ type: z.literal('full') }),
48
+ z.object({ type: z.literal('deposit'), depositAmount: depositAmountSchema })
49
+ ]);
38
50
  const shopDataInputSchema = z.object({
39
51
  basePrice: z.number().nonnegative().max(1e9), // PLN (≤6dp)
40
52
  vatRate: z.number().int().min(0).max(100),
41
53
  isActive: z.boolean().optional(),
42
- sortOrder: z.number().int().nullable().optional()
54
+ sortOrder: z.number().int().nullable().optional(),
55
+ paymentPolicy: paymentPolicySchema.nullable().optional()
43
56
  });
44
57
  const variantInputSchema = z.object({
45
58
  id: z.string().optional(),
@@ -47,7 +60,9 @@ const variantInputSchema = z.object({
47
60
  name: z.record(z.string(), z.string()).nullable().optional(),
48
61
  priceDelta: z.number().optional(), // PLN
49
62
  stock: z.number().int().nullable().optional(),
50
- attributes: z.record(z.string(), z.string()).nullable().optional()
63
+ // unknown — variantAttributes can be string|number|boolean|datetime;
64
+ // validateVariantAttributes (server) enforces the typed shape from shop config.
65
+ attributes: z.record(z.string(), z.unknown()).nullable().optional()
51
66
  });
52
67
  export const upsertShopDataForEntry = command(z.object({
53
68
  entryId: z.string(),
@@ -223,11 +238,20 @@ export const getOrderRefundsAdmin = query(z.string(), async (orderId) => {
223
238
  export const refundOrderCmd = command(z.object({
224
239
  orderId: z.string(),
225
240
  amount: z.number().int().positive().optional(),
226
- reason: z.string().max(500).optional()
227
- }), async ({ orderId, amount, reason }) => {
241
+ reason: z.string().max(500).optional(),
242
+ kind: z.enum(['full', 'deposit', 'balance']).optional(),
243
+ releaseStock: z.boolean().optional()
244
+ }), async ({ orderId, amount, reason, kind, releaseStock }) => {
228
245
  requireAuth();
229
246
  try {
230
- const result = await refundOrder({ orderId, amount, reason, createdBy: 'admin' });
247
+ const result = await refundOrder({
248
+ orderId,
249
+ amount,
250
+ reason,
251
+ kind,
252
+ releaseStock,
253
+ createdBy: 'admin'
254
+ });
231
255
  return { success: true, ...result };
232
256
  }
233
257
  catch (err) {
@@ -238,6 +262,55 @@ export const refundOrderCmd = command(z.object({
238
262
  return { success: false, code: 'provider_error', error: message };
239
263
  }
240
264
  });
265
+ export const getOrderInvoiceAdmin = query(z.string(), async (orderId) => {
266
+ requireAuth();
267
+ const invoice = await getInvoiceByOrderId(orderId);
268
+ const invoicingEnabled = !!getCMS().shopConfig?.invoicing;
269
+ return { invoice, invoicingEnabled };
270
+ });
271
+ export const issueInvoiceCmd = command(z.object({ orderId: z.string(), force: z.boolean().optional() }), async ({ orderId, force }) => {
272
+ requireAuth();
273
+ try {
274
+ const invoice = await issueInvoiceForOrder(orderId, { force });
275
+ return { success: true, invoice };
276
+ }
277
+ catch (err) {
278
+ const message = err instanceof Error ? err.message : 'Invoice issuance failed';
279
+ return { success: false, error: message };
280
+ }
281
+ });
282
+ export const generateBalanceLinkForOrder = command(z.string(), async (orderId) => {
283
+ requireAuth();
284
+ const { generateBalanceToken, requireBalanceTokenSecret } = await import('../../shop/server/balance-payment.js');
285
+ const { getOrderById } = await import('../../shop/server/orders.js');
286
+ const order = await getOrderById(orderId);
287
+ if (!order)
288
+ return { success: false, error: 'Order not found' };
289
+ if (!order.balanceOwed) {
290
+ return { success: false, error: 'Order has no outstanding balance' };
291
+ }
292
+ let secret;
293
+ try {
294
+ secret = requireBalanceTokenSecret();
295
+ }
296
+ catch (err) {
297
+ const message = err instanceof Error ? err.message : 'Token secret not configured';
298
+ return { success: false, error: message };
299
+ }
300
+ const token = generateBalanceToken(order.id, secret);
301
+ // Mirror orderViewUrl template — build the customer-facing URL by
302
+ // extending the configured base with the balance path + token.
303
+ const cms = (await import('../../core/cms.js')).getCMS();
304
+ const orderViewUrl = cms.shopConfig?.orderViewUrl ?? '/shop/order/{orderNumber}?token={accessToken}';
305
+ const base = orderViewUrl
306
+ .replace('{orderNumber}', encodeURIComponent(order.number))
307
+ .replace('{orderId}', encodeURIComponent(order.id))
308
+ .replace('{accessToken}', encodeURIComponent(order.accessToken))
309
+ .replace('{language}', encodeURIComponent(order.language ?? ''));
310
+ const sep = base.includes('?') ? '&' : '?';
311
+ const url = `${base}${sep}balance=1&balanceToken=${encodeURIComponent(token)}`;
312
+ return { success: true, url, balanceAmount: order.partialPayment?.balanceAmount ?? 0 };
313
+ });
241
314
  // Coupons ────────────────────────────────────────────────────────────────────
242
315
  const couponInputSchema = z.object({
243
316
  code: z.string().min(1).max(64),
@@ -0,0 +1,9 @@
1
+ import type { IconSetPlugin } from '../../types/plugins.js';
2
+ export declare function setIconSets(sets: Map<string, IconSetPlugin>): void;
3
+ export declare function getIconSets(): Map<string, IconSetPlugin>;
4
+ /**
5
+ * Resolve a single icon set: explicit `slug` (from {@link IconField.set}) takes
6
+ * precedence; otherwise returns the first registered set (predictable order
7
+ * matches Map insertion). Returns `null` when none are registered.
8
+ */
9
+ export declare function resolveIconSet(slug?: string): IconSetPlugin | null;
@@ -0,0 +1,20 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ const contextKey = Symbol('iconSets');
3
+ export function setIconSets(sets) {
4
+ setContext(contextKey, sets);
5
+ }
6
+ export function getIconSets() {
7
+ return getContext(contextKey) ?? new Map();
8
+ }
9
+ /**
10
+ * Resolve a single icon set: explicit `slug` (from {@link IconField.set}) takes
11
+ * precedence; otherwise returns the first registered set (predictable order
12
+ * matches Map insertion). Returns `null` when none are registered.
13
+ */
14
+ export function resolveIconSet(slug) {
15
+ const sets = getIconSets();
16
+ if (slug)
17
+ return sets.get(slug) ?? null;
18
+ const first = sets.values().next();
19
+ return first.done ? null : first.value;
20
+ }
@@ -329,7 +329,7 @@ export const { POST } = createRetryPaymentHandler();
329
329
  {
330
330
  path: 'admin/api/[...path]/+server.ts',
331
331
  content: `${GENERATED_COMMENT_TS}
332
- import { createAdminApiHandler } from 'includio-cms/admin/api/handler';
332
+ import { createAdminApiHandler } from 'includio-cms/sveltekit/server';
333
333
 
334
334
  export const { GET, POST, PATCH, PUT, DELETE } = createAdminApiHandler();
335
335
  `
@@ -337,7 +337,7 @@ export const { GET, POST, PATCH, PUT, DELETE } = createAdminApiHandler();
337
337
  {
338
338
  path: 'admin/api/rest/[...restPath]/+server.ts',
339
339
  content: `${GENERATED_COMMENT_TS}
340
- import { createRestApiHandler } from 'includio-cms/admin/api/rest/handler';
340
+ import { createRestApiHandler } from 'includio-cms/sveltekit/server';
341
341
 
342
342
  export const { GET, POST, PUT, DELETE } = createRestApiHandler();
343
343
  `
@@ -17,7 +17,7 @@
17
17
  bind:ref
18
18
  data-slot="checkbox"
19
19
  class={cn(
20
- "border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer flex size-6 shrink-0 items-center justify-center rounded-[4px] border outline-none transition-shadow focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
20
+ "border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-xs peer relative flex size-4 shrink-0 items-center justify-center rounded-[4px] border outline-none transition-shadow focus-visible:ring-[3px] after:absolute after:-inset-1 after:content-[''] disabled:cursor-not-allowed disabled:opacity-50",
21
21
  className
22
22
  )}
23
23
  bind:checked
@@ -27,9 +27,9 @@
27
27
  {#snippet children({ checked, indeterminate })}
28
28
  <div data-slot="checkbox-indicator" class="text-current transition-none">
29
29
  {#if checked}
30
- <CheckIcon class="size-4" />
30
+ <CheckIcon class="size-3.5" />
31
31
  {:else if indeterminate}
32
- <MinusIcon class="size-4" />
32
+ <MinusIcon class="size-3.5" />
33
33
  {/if}
34
34
  </div>
35
35
  {/snippet}
@@ -4,7 +4,7 @@ import type { ApiKeyConfig, AuthConfig, CMSConfig, ICMS, MediaConfig, Typography
4
4
  import type { CollectionConfigWithType } from '../types/collections.js';
5
5
  import type { Language } from '../types/languages.js';
6
6
  import type { SingleConfigWithType } from '../types/singles.js';
7
- import type { CustomFieldDefinition, PluginConfig } from '../types/plugins.js';
7
+ import type { CustomFieldDefinition, IconSetPlugin, Plugin } from '../types/plugins.js';
8
8
  import type { FormConfig } from '../types/forms.js';
9
9
  import type { AIAdapter } from '../types/adapters/ai.js';
10
10
  import type { EmailAdapter } from '../types/adapters/email.js';
@@ -28,8 +28,17 @@ export declare class CMS implements ICMS {
28
28
  sidebarHelp: boolean;
29
29
  shopConfig: ResolvedShopConfig | null;
30
30
  cmpConfig: ResolvedCmpConfig | null;
31
- plugins: PluginConfig[];
31
+ /**
32
+ * Resolves once the shop's variant-attribute GIN indexes have been applied
33
+ * by `initCMS()`. `null` when the CMS is configured without a shop. Tests
34
+ * can `await` this to gate on init completion; production callers don't
35
+ * need to — index application is idempotent and non-blocking.
36
+ * @internal
37
+ */
38
+ shopInitPromise: Promise<void> | null;
39
+ plugins: Plugin[];
32
40
  customFields: Map<string, CustomFieldDefinition>;
41
+ iconSets: Map<string, IconSetPlugin>;
33
42
  apiKeys: ApiKeyConfig[];
34
43
  constructor(config: CMSConfig);
35
44
  private validateFieldSlugs;
package/dist/core/cms.js CHANGED
@@ -1,9 +1,11 @@
1
+ import { isIconSetPlugin } from '../types/plugins.js';
1
2
  import { setSchemaGetCMS } from './fields/fieldSchemaToTs.js';
2
3
  import { betterAuth } from 'better-auth';
3
4
  import { drizzleAdapter } from 'better-auth/adapters/drizzle';
4
5
  import { admin } from 'better-auth/plugins';
5
6
  import { resetPasswordEmailTemplate } from '../admin/email/reset-password-template.js';
6
7
  import * as authSchema from '../server/db/schema/auth-schema.js';
8
+ import { applyVariantAttributeIndexes } from '../shop/server/init.js';
7
9
  export class CMS {
8
10
  config;
9
11
  databaseAdapter;
@@ -21,8 +23,17 @@ export class CMS {
21
23
  sidebarHelp;
22
24
  shopConfig;
23
25
  cmpConfig;
26
+ /**
27
+ * Resolves once the shop's variant-attribute GIN indexes have been applied
28
+ * by `initCMS()`. `null` when the CMS is configured without a shop. Tests
29
+ * can `await` this to gate on init completion; production callers don't
30
+ * need to — index application is idempotent and non-blocking.
31
+ * @internal
32
+ */
33
+ shopInitPromise = null;
24
34
  plugins = [];
25
35
  customFields = new Map();
36
+ iconSets = new Map();
26
37
  apiKeys = [];
27
38
  constructor(config) {
28
39
  this.config = config;
@@ -64,6 +75,13 @@ export class CMS {
64
75
  if (config.plugins) {
65
76
  this.plugins = config.plugins;
66
77
  for (const plugin of this.plugins) {
78
+ if (isIconSetPlugin(plugin)) {
79
+ if (this.iconSets.has(plugin.slug)) {
80
+ throw new Error(`Duplicate icon-set plugin slug: "${plugin.slug}"`);
81
+ }
82
+ this.iconSets.set(plugin.slug, plugin);
83
+ continue;
84
+ }
67
85
  for (const def of plugin.fields ?? []) {
68
86
  if (this.customFields.has(def.fieldType)) {
69
87
  throw new Error(`Duplicate custom field type: "${def.fieldType}" (plugin: "${plugin.slug}")`);
@@ -157,6 +175,17 @@ export function initCMS(config) {
157
175
  import('./server/media/operations/backgroundMaintenance.js')
158
176
  .then((m) => m.startBackgroundMaintenance())
159
177
  .catch((e) => console.warn('[cms] Failed to start background maintenance:', e));
178
+ // Apply shop variantAttribute GIN indexes (idempotent CREATE INDEX IF NOT EXISTS).
179
+ // Pass shop + drizzle explicitly so the dynamic import doesn't depend on a
180
+ // shared CMS singleton (vitest can give the dynamic module a fresh instance).
181
+ if (cms.shopConfig) {
182
+ const drizzle = cms.databaseAdapter._drizzle;
183
+ if (drizzle) {
184
+ cms.shopInitPromise = applyVariantAttributeIndexes(cms.shopConfig, drizzle).catch((e) => {
185
+ console.warn('[shop] Failed to apply variant attribute indexes:', e);
186
+ });
187
+ }
188
+ }
160
189
  return cms;
161
190
  }
162
191
  /**
@@ -444,6 +444,13 @@ export function generateZodSchemaFromField(field, languages, options = {
444
444
  localizedDefault.text = emptyLangMap;
445
445
  return localizedSchema.optional().default(localizedDefault);
446
446
  }
447
+ case 'icon': {
448
+ // Value is a plain string key referencing an icon registered by an
449
+ // IconSetPlugin. Validation matches `slug` field semantics.
450
+ if (field.required)
451
+ return z.string().min(1, { message: msg.required });
452
+ return z.string().optional().default('');
453
+ }
447
454
  case 'custom': {
448
455
  const customDef = getCustomFieldDef(field.fieldType);
449
456
  if (!customDef)
@@ -1,6 +1,8 @@
1
1
  import type { Field } from '../../../types/fields.js';
2
2
  import type { CustomFieldDefinition } from '../../../types/plugins.js';
3
+ import type { VariantAttribute } from '../../../shop/types.js';
3
4
  export declare function setGeneratorCustomFields(customFields: Map<string, CustomFieldDefinition>): void;
5
+ export declare function setGeneratorShopVariantAttributes(attrs: Record<string, VariantAttribute>): void;
4
6
  export declare function generateTsTypeFromFields(fields: Field[]): string;
5
7
  export declare function generateFlatTsTypeFromFields(fields: Field[]): string;
6
8
  export interface InlineBlockTypeDef {
@@ -1,12 +1,44 @@
1
1
  import { toPascalCase, quoteKey } from './utils.js';
2
2
  import { buildSeoTsType } from '../../fields/seoFieldDescriptor.js';
3
3
  let _customFields = new Map();
4
+ let _shopVariantAttributes = {};
4
5
  function isGuaranteed(f) {
5
6
  return !!(f.required || f.type === 'seo' || f.defaultValue !== undefined);
6
7
  }
7
8
  export function setGeneratorCustomFields(customFields) {
8
9
  _customFields = customFields;
9
10
  }
11
+ export function setGeneratorShopVariantAttributes(attrs) {
12
+ _shopVariantAttributes = attrs;
13
+ }
14
+ function variantAttributeTsType(attr) {
15
+ switch (attr.type) {
16
+ case 'text':
17
+ case 'datetime':
18
+ case 'image':
19
+ case 'entry':
20
+ case 'slug':
21
+ return 'string';
22
+ case 'number':
23
+ return 'number';
24
+ case 'boolean':
25
+ return 'boolean';
26
+ case 'select':
27
+ return attr.options.map((o) => `'${o.value}'`).join(' | ');
28
+ }
29
+ }
30
+ function buildShopAttributesType() {
31
+ const entries = Object.entries(_shopVariantAttributes);
32
+ if (entries.length === 0)
33
+ return 'Record<string, string> | null';
34
+ const fields = entries
35
+ .map(([key, attr]) => {
36
+ const opt = attr.required ? '' : '?';
37
+ return `${quoteKey(key)}${opt}: ${variantAttributeTsType(attr)}`;
38
+ })
39
+ .join('; ');
40
+ return `{ ${fields} }`;
41
+ }
10
42
  function getFieldTypeAsString(field) {
11
43
  switch (field.type) {
12
44
  case 'text':
@@ -68,6 +100,7 @@ function getFieldTypeAsString(field) {
68
100
  return 'UrlFieldData[]';
69
101
  }
70
102
  case 'slug':
103
+ case 'icon':
71
104
  return 'string';
72
105
  case 'seo':
73
106
  return buildSeoTsType();
@@ -82,7 +115,7 @@ function getFieldTypeAsString(field) {
82
115
  name: Record<string, string> | null;
83
116
  priceDelta: number;
84
117
  stock: number | null;
85
- attributes: Record<string, string> | null;
118
+ attributes: ${buildShopAttributesType()};
86
119
  }>;
87
120
  } | null`;
88
121
  case 'url': {