@absolutejs/commerce 0.5.0-beta.0 → 0.6.0-beta.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.
@@ -0,0 +1,41 @@
1
+ export type AnalyticsOrderLine = {
2
+ product: string;
3
+ quantity: number;
4
+ amountTotal: number;
5
+ };
6
+ export type AnalyticsOrder = {
7
+ amount_total: number | null;
8
+ created_at: Date | string;
9
+ status: string;
10
+ customer_email?: string | null;
11
+ line_items?: AnalyticsOrderLine[] | null;
12
+ };
13
+ export type DayRevenue = {
14
+ date: string;
15
+ revenueCents: number;
16
+ orders: number;
17
+ };
18
+ export type ProductRevenue = {
19
+ product: string;
20
+ quantity: number;
21
+ revenueCents: number;
22
+ };
23
+ export type SalesSummary = {
24
+ paidOrders: number;
25
+ revenueCents: number;
26
+ averageOrderCents: number;
27
+ statusCounts: Record<string, number>;
28
+ revenueByDay: DayRevenue[];
29
+ topProducts: ProductRevenue[];
30
+ };
31
+ /** Aggregate revenue, AOV, status mix, revenue-by-day, and top products. */
32
+ export declare const salesSummary: (orders: AnalyticsOrder[]) => SalesSummary;
33
+ export type CustomerSummary = {
34
+ email: string;
35
+ orders: number;
36
+ totalSpentCents: number;
37
+ firstOrderAt: string;
38
+ lastOrderAt: string;
39
+ };
40
+ /** Per-customer rollup (realized-revenue orders), newest activity first. */
41
+ export declare const customerSummaries: (orders: AnalyticsOrder[]) => CustomerSummary[];
@@ -1148,6 +1148,102 @@ export declare const commerceAbandonedCarts: import("drizzle-orm/pg-core").PgTab
1148
1148
  };
1149
1149
  dialect: "pg";
1150
1150
  }>;
1151
+ export declare const commerceGiftCards: import("drizzle-orm/pg-core").PgTableWithColumns<{
1152
+ name: "gift_cards";
1153
+ schema: undefined;
1154
+ columns: {
1155
+ balance_cents: import("drizzle-orm/pg-core").PgColumn<{
1156
+ name: "balance_cents";
1157
+ tableName: "gift_cards";
1158
+ dataType: "number";
1159
+ columnType: "PgInteger";
1160
+ data: number;
1161
+ driverParam: string | number;
1162
+ notNull: true;
1163
+ hasDefault: false;
1164
+ isPrimaryKey: false;
1165
+ isAutoincrement: false;
1166
+ hasRuntimeDefault: false;
1167
+ enumValues: undefined;
1168
+ baseColumn: never;
1169
+ identity: undefined;
1170
+ generated: undefined;
1171
+ }, {}, {}>;
1172
+ code: import("drizzle-orm/pg-core").PgColumn<{
1173
+ name: "code";
1174
+ tableName: "gift_cards";
1175
+ dataType: "string";
1176
+ columnType: "PgVarchar";
1177
+ data: string;
1178
+ driverParam: string;
1179
+ notNull: true;
1180
+ hasDefault: false;
1181
+ isPrimaryKey: true;
1182
+ isAutoincrement: false;
1183
+ hasRuntimeDefault: false;
1184
+ enumValues: [string, ...string[]];
1185
+ baseColumn: never;
1186
+ identity: undefined;
1187
+ generated: undefined;
1188
+ }, {}, {
1189
+ length: 40;
1190
+ }>;
1191
+ created_at: import("drizzle-orm/pg-core").PgColumn<{
1192
+ name: "created_at";
1193
+ tableName: "gift_cards";
1194
+ dataType: "date";
1195
+ columnType: "PgTimestamp";
1196
+ data: Date;
1197
+ driverParam: string;
1198
+ notNull: true;
1199
+ hasDefault: true;
1200
+ isPrimaryKey: false;
1201
+ isAutoincrement: false;
1202
+ hasRuntimeDefault: false;
1203
+ enumValues: undefined;
1204
+ baseColumn: never;
1205
+ identity: undefined;
1206
+ generated: undefined;
1207
+ }, {}, {}>;
1208
+ initial_cents: import("drizzle-orm/pg-core").PgColumn<{
1209
+ name: "initial_cents";
1210
+ tableName: "gift_cards";
1211
+ dataType: "number";
1212
+ columnType: "PgInteger";
1213
+ data: number;
1214
+ driverParam: string | number;
1215
+ notNull: true;
1216
+ hasDefault: false;
1217
+ isPrimaryKey: false;
1218
+ isAutoincrement: false;
1219
+ hasRuntimeDefault: false;
1220
+ enumValues: undefined;
1221
+ baseColumn: never;
1222
+ identity: undefined;
1223
+ generated: undefined;
1224
+ }, {}, {}>;
1225
+ recipient_email: import("drizzle-orm/pg-core").PgColumn<{
1226
+ name: "recipient_email";
1227
+ tableName: "gift_cards";
1228
+ dataType: "string";
1229
+ columnType: "PgVarchar";
1230
+ data: string;
1231
+ driverParam: string;
1232
+ notNull: false;
1233
+ hasDefault: false;
1234
+ isPrimaryKey: false;
1235
+ isAutoincrement: false;
1236
+ hasRuntimeDefault: false;
1237
+ enumValues: [string, ...string[]];
1238
+ baseColumn: never;
1239
+ identity: undefined;
1240
+ generated: undefined;
1241
+ }, {}, {
1242
+ length: 320;
1243
+ }>;
1244
+ };
1245
+ dialect: "pg";
1246
+ }>;
1151
1247
  export declare const commerceFavorites: import("drizzle-orm/pg-core").PgTableWithColumns<{
1152
1248
  name: "favorites";
1153
1249
  schema: undefined;
@@ -2125,6 +2221,102 @@ export declare const commerceDrizzleSchema: {
2125
2221
  };
2126
2222
  dialect: "pg";
2127
2223
  }>;
2224
+ giftCards: import("drizzle-orm/pg-core").PgTableWithColumns<{
2225
+ name: "gift_cards";
2226
+ schema: undefined;
2227
+ columns: {
2228
+ balance_cents: import("drizzle-orm/pg-core").PgColumn<{
2229
+ name: "balance_cents";
2230
+ tableName: "gift_cards";
2231
+ dataType: "number";
2232
+ columnType: "PgInteger";
2233
+ data: number;
2234
+ driverParam: string | number;
2235
+ notNull: true;
2236
+ hasDefault: false;
2237
+ isPrimaryKey: false;
2238
+ isAutoincrement: false;
2239
+ hasRuntimeDefault: false;
2240
+ enumValues: undefined;
2241
+ baseColumn: never;
2242
+ identity: undefined;
2243
+ generated: undefined;
2244
+ }, {}, {}>;
2245
+ code: import("drizzle-orm/pg-core").PgColumn<{
2246
+ name: "code";
2247
+ tableName: "gift_cards";
2248
+ dataType: "string";
2249
+ columnType: "PgVarchar";
2250
+ data: string;
2251
+ driverParam: string;
2252
+ notNull: true;
2253
+ hasDefault: false;
2254
+ isPrimaryKey: true;
2255
+ isAutoincrement: false;
2256
+ hasRuntimeDefault: false;
2257
+ enumValues: [string, ...string[]];
2258
+ baseColumn: never;
2259
+ identity: undefined;
2260
+ generated: undefined;
2261
+ }, {}, {
2262
+ length: 40;
2263
+ }>;
2264
+ created_at: import("drizzle-orm/pg-core").PgColumn<{
2265
+ name: "created_at";
2266
+ tableName: "gift_cards";
2267
+ dataType: "date";
2268
+ columnType: "PgTimestamp";
2269
+ data: Date;
2270
+ driverParam: string;
2271
+ notNull: true;
2272
+ hasDefault: true;
2273
+ isPrimaryKey: false;
2274
+ isAutoincrement: false;
2275
+ hasRuntimeDefault: false;
2276
+ enumValues: undefined;
2277
+ baseColumn: never;
2278
+ identity: undefined;
2279
+ generated: undefined;
2280
+ }, {}, {}>;
2281
+ initial_cents: import("drizzle-orm/pg-core").PgColumn<{
2282
+ name: "initial_cents";
2283
+ tableName: "gift_cards";
2284
+ dataType: "number";
2285
+ columnType: "PgInteger";
2286
+ data: number;
2287
+ driverParam: string | number;
2288
+ notNull: true;
2289
+ hasDefault: false;
2290
+ isPrimaryKey: false;
2291
+ isAutoincrement: false;
2292
+ hasRuntimeDefault: false;
2293
+ enumValues: undefined;
2294
+ baseColumn: never;
2295
+ identity: undefined;
2296
+ generated: undefined;
2297
+ }, {}, {}>;
2298
+ recipient_email: import("drizzle-orm/pg-core").PgColumn<{
2299
+ name: "recipient_email";
2300
+ tableName: "gift_cards";
2301
+ dataType: "string";
2302
+ columnType: "PgVarchar";
2303
+ data: string;
2304
+ driverParam: string;
2305
+ notNull: false;
2306
+ hasDefault: false;
2307
+ isPrimaryKey: false;
2308
+ isAutoincrement: false;
2309
+ hasRuntimeDefault: false;
2310
+ enumValues: [string, ...string[]];
2311
+ baseColumn: never;
2312
+ identity: undefined;
2313
+ generated: undefined;
2314
+ }, {}, {
2315
+ length: 320;
2316
+ }>;
2317
+ };
2318
+ dialect: "pg";
2319
+ }>;
2128
2320
  orders: import("drizzle-orm/pg-core").PgTableWithColumns<{
2129
2321
  name: "orders";
2130
2322
  schema: undefined;
@@ -57,6 +57,29 @@ var saveDesign = async (db, design) => {
57
57
  const [created] = await db.insert(commerceSavedDesigns).values(design).returning();
58
58
  return created;
59
59
  };
60
+ var issueGiftCard = async (db, input) => {
61
+ const [created] = await db.insert(commerceGiftCards).values({
62
+ balance_cents: input.cents,
63
+ code: input.code.trim().toUpperCase(),
64
+ initial_cents: input.cents,
65
+ recipient_email: input.recipientEmail ?? null
66
+ }).returning();
67
+ return created;
68
+ };
69
+ var getGiftCard = async (db, code) => {
70
+ const [card] = await db.select().from(commerceGiftCards).where(eq(commerceGiftCards.code, code.trim().toUpperCase())).limit(1);
71
+ return card ?? null;
72
+ };
73
+ var redeemGiftCard = async (db, code, amountCents) => {
74
+ const card = await getGiftCard(db, code);
75
+ if (!card)
76
+ return null;
77
+ const applied = Math.max(0, Math.min(card.balance_cents, amountCents));
78
+ const balance = card.balance_cents - applied;
79
+ if (applied > 0)
80
+ await db.update(commerceGiftCards).set({ balance_cents: balance }).where(eq(commerceGiftCards.code, card.code));
81
+ return { appliedCents: applied, balanceCents: balance };
82
+ };
60
83
  var createReturnRequest = async (db, request) => {
61
84
  const [created] = await db.insert(commerceReturnRequests).values(request).returning();
62
85
  return created;
@@ -164,6 +187,13 @@ var commerceAbandonedCarts = pgTable("abandoned_carts", {
164
187
  recovered: boolean().notNull().default(false),
165
188
  reminded: boolean().notNull().default(false)
166
189
  });
190
+ var commerceGiftCards = pgTable("gift_cards", {
191
+ balance_cents: integer().notNull(),
192
+ code: varchar({ length: 40 }).primaryKey(),
193
+ created_at: timestamp().notNull().defaultNow(),
194
+ initial_cents: integer().notNull(),
195
+ recipient_email: varchar({ length: 320 })
196
+ });
167
197
  var commerceFavorites = pgTable("favorites", {
168
198
  created_at: timestamp().notNull().defaultNow(),
169
199
  customer_email: varchar({ length: 320 }).notNull(),
@@ -207,6 +237,7 @@ var commerceDrizzleSchema = {
207
237
  designs: commerceDesigns,
208
238
  discounts: commerceDiscounts,
209
239
  favorites: commerceFavorites,
240
+ giftCards: commerceGiftCards,
210
241
  orders: commerceOrders,
211
242
  quotes: commerceQuotes,
212
243
  returnRequests: commerceReturnRequests,
@@ -220,6 +251,7 @@ export {
220
251
  setReviewStatus,
221
252
  setReturnStatus,
222
253
  saveDesign,
254
+ redeemGiftCard,
223
255
  recordAbandonedCart,
224
256
  ratingSummaries,
225
257
  markReminded,
@@ -230,6 +262,8 @@ export {
230
262
  listFavorites,
231
263
  listApprovedReviews,
232
264
  listAllReviews,
265
+ issueGiftCard,
266
+ getGiftCard,
233
267
  findOrderForTracking,
234
268
  dueForReminder,
235
269
  deleteSavedDesign,
@@ -241,6 +275,7 @@ export {
241
275
  commerceReturnRequests,
242
276
  commerceQuotes,
243
277
  commerceOrders,
278
+ commerceGiftCards,
244
279
  commerceFavorites,
245
280
  commerceDrizzleSchema,
246
281
  commerceDiscounts,
@@ -1,5 +1,5 @@
1
1
  import type { PgDatabase } from 'drizzle-orm/pg-core';
2
- import { commerceFavorites, commerceReturnRequests, commerceReviews, commerceSavedDesigns } from './index';
2
+ import { commerceFavorites, commerceGiftCards, commerceReturnRequests, commerceReviews, commerceSavedDesigns } from './index';
3
3
  export type CommerceDb = PgDatabase<any, any, any>;
4
4
  export type Review = typeof commerceReviews.$inferSelect;
5
5
  export type NewReview = typeof commerceReviews.$inferInsert;
@@ -8,6 +8,11 @@ export type SavedDesign = typeof commerceSavedDesigns.$inferSelect;
8
8
  export type NewSavedDesign = typeof commerceSavedDesigns.$inferInsert;
9
9
  export type ReturnRequest = typeof commerceReturnRequests.$inferSelect;
10
10
  export type NewReturnRequest = typeof commerceReturnRequests.$inferInsert;
11
+ export type GiftCard = typeof commerceGiftCards.$inferSelect;
12
+ export type GiftCardRedemption = {
13
+ appliedCents: number;
14
+ balanceCents: number;
15
+ };
11
16
  export type RatingSummary = {
12
17
  productId: string;
13
18
  count: number;
@@ -962,6 +967,25 @@ export declare const saveDesign: (db: CommerceDb, design: NewSavedDesign) => Pro
962
967
  label: string | null;
963
968
  snapshot: Record<string, unknown>;
964
969
  } | undefined>;
970
+ export declare const issueGiftCard: (db: CommerceDb, input: {
971
+ code: string;
972
+ cents: number;
973
+ recipientEmail?: string | null;
974
+ }) => Promise<{
975
+ created_at: Date;
976
+ balance_cents: number;
977
+ code: string;
978
+ initial_cents: number;
979
+ recipient_email: string | null;
980
+ } | undefined>;
981
+ export declare const getGiftCard: (db: CommerceDb, code: string) => Promise<{
982
+ balance_cents: number;
983
+ code: string;
984
+ created_at: Date;
985
+ initial_cents: number;
986
+ recipient_email: string | null;
987
+ } | null>;
988
+ export declare const redeemGiftCard: (db: CommerceDb, code: string, amountCents: number) => Promise<GiftCardRedemption | null>;
965
989
  export declare const createReturnRequest: (db: CommerceDb, request: NewReturnRequest) => Promise<{
966
990
  status: string;
967
991
  created_at: Date;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './core/analytics';
1
2
  export * from './core/cart';
2
3
  export * from './core/discounts';
3
4
  export * from './core/email';
package/dist/index.js CHANGED
@@ -1,4 +1,72 @@
1
1
  // @bun
2
+ // src/core/analytics.ts
3
+ var REVENUE_STATES = new Set(["paid", "shipped"]);
4
+ var isoDay = (value) => {
5
+ const text = typeof value === "string" ? value : value.toISOString();
6
+ return text.slice(0, 10);
7
+ };
8
+ var salesSummary = (orders) => {
9
+ const statusCounts = {};
10
+ const byDay = new Map;
11
+ const byProduct = new Map;
12
+ let revenueCents = 0;
13
+ let paidOrders = 0;
14
+ for (const order of orders) {
15
+ statusCounts[order.status] = (statusCounts[order.status] ?? 0) + 1;
16
+ if (!REVENUE_STATES.has(order.status))
17
+ continue;
18
+ const amount = order.amount_total ?? 0;
19
+ revenueCents += amount;
20
+ paidOrders += 1;
21
+ const day = isoDay(order.created_at);
22
+ const existing = byDay.get(day) ?? { date: day, orders: 0, revenueCents: 0 };
23
+ existing.orders += 1;
24
+ existing.revenueCents += amount;
25
+ byDay.set(day, existing);
26
+ for (const line of order.line_items ?? []) {
27
+ const row = byProduct.get(line.product) ?? {
28
+ product: line.product,
29
+ quantity: 0,
30
+ revenueCents: 0
31
+ };
32
+ row.quantity += line.quantity;
33
+ row.revenueCents += line.amountTotal;
34
+ byProduct.set(line.product, row);
35
+ }
36
+ }
37
+ return {
38
+ averageOrderCents: paidOrders ? Math.round(revenueCents / paidOrders) : 0,
39
+ paidOrders,
40
+ revenueByDay: [...byDay.values()].sort((left, right) => left.date < right.date ? -1 : 1),
41
+ revenueCents,
42
+ statusCounts,
43
+ topProducts: [...byProduct.values()].sort((left, right) => right.revenueCents - left.revenueCents).slice(0, 8)
44
+ };
45
+ };
46
+ var customerSummaries = (orders) => {
47
+ const byEmail = new Map;
48
+ for (const order of orders) {
49
+ if (!order.customer_email || !REVENUE_STATES.has(order.status))
50
+ continue;
51
+ const email = order.customer_email.toLowerCase();
52
+ const at = isoDay(order.created_at);
53
+ const existing = byEmail.get(email) ?? {
54
+ email,
55
+ firstOrderAt: at,
56
+ lastOrderAt: at,
57
+ orders: 0,
58
+ totalSpentCents: 0
59
+ };
60
+ existing.orders += 1;
61
+ existing.totalSpentCents += order.amount_total ?? 0;
62
+ if (at < existing.firstOrderAt)
63
+ existing.firstOrderAt = at;
64
+ if (at > existing.lastOrderAt)
65
+ existing.lastOrderAt = at;
66
+ byEmail.set(email, existing);
67
+ }
68
+ return [...byEmail.values()].sort((left, right) => left.lastOrderAt < right.lastOrderAt ? 1 : -1);
69
+ };
2
70
  // src/core/cart.ts
3
71
  var cartCount = (lines) => lines.reduce((sum, line) => sum + line.quantity, 0);
4
72
  var lineTotal = (line) => line.setupFee + line.unitPrice * line.quantity;
@@ -127,6 +195,7 @@ var DEFAULT_APPAREL_PARCEL = {
127
195
  export {
128
196
  toProductionStage,
129
197
  toCents,
198
+ salesSummary,
130
199
  roundMoney,
131
200
  renderEmail,
132
201
  quantityDiscount,
@@ -143,6 +212,7 @@ export {
143
212
  emailButton,
144
213
  discountAmountCents,
145
214
  depositCents,
215
+ customerSummaries,
146
216
  cartSubtotal,
147
217
  cartSetupTotal,
148
218
  cartCount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/commerce",
3
- "version": "0.5.0-beta.0",
3
+ "version": "0.6.0-beta.0",
4
4
  "description": "Provider-agnostic commerce primitives (cart, orders, fulfillment, shipping) for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",