@stacksjs/commerce 0.75.89 → 0.75.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/catalog.d.ts +43 -0
- package/dist/catalog.js +2 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +5 -5
- package/dist/register.d.ts +56 -0
- package/dist/register.js +2 -0
- package/dist/releases.d.ts +48 -0
- package/dist/releases.js +2 -0
- package/package.json +12 -12
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Units still on the shelf once the basket is taken off it; null when stock is not counted. */
|
|
2
|
+
export declare function shelfLeft(product: Product, basket: BasketLine[], fields?: RegisterFields): number | null;
|
|
3
|
+
/** Whether one more can be rung up. */
|
|
4
|
+
export declare function canAdd(product: Product, basket: BasketLine[], fields?: RegisterFields): boolean;
|
|
5
|
+
/** One more of a product, when there is one to sell; the basket unchanged otherwise. */
|
|
6
|
+
export declare function addToBasket(basket: BasketLine[], product: Product, fields?: RegisterFields): BasketLine[];
|
|
7
|
+
/** A line's quantity, held to the stock on hand; zero (or less) takes the line off. */
|
|
8
|
+
export declare function setQuantity(basket: BasketLine[], product: Product, quantity: number, fields?: RegisterFields): BasketLine[];
|
|
9
|
+
/** The basket's lines with their products, leaving out any no longer in the catalog. */
|
|
10
|
+
export declare function basketLines<T extends Product>(basket: BasketLine[], products: T[], fields?: RegisterFields): Array<BasketLine & { product: T }>;
|
|
11
|
+
/** The basket's total, in minor units. */
|
|
12
|
+
export declare function basketTotal(basket: BasketLine[], products: Product[], fields?: RegisterFields): number;
|
|
13
|
+
/** How many units are in the basket. */
|
|
14
|
+
export declare function basketCount(basket: BasketLine[]): number;
|
|
15
|
+
/**
|
|
16
|
+
* How a product's stock reads on the register: sold out, running low (at or
|
|
17
|
+
* under its own reorder level, `defaultLow` when it has none), or how many
|
|
18
|
+
* are left. Null when stock is not counted.
|
|
19
|
+
*/
|
|
20
|
+
export declare function stockLabel(product: Product, basket?: BasketLine[], fields?: RegisterFields, defaultLow?: number): StockLabel | null;
|
|
21
|
+
/** The distinct values of a grouping (a shelf, a category), in the order the products come. */
|
|
22
|
+
export declare function groupsOf<T>(products: T[], groupOf: (product: T) => string): string[];
|
|
23
|
+
export declare const DEFAULT_FIELDS: RegisterFields;
|
|
24
|
+
/** Field names for a catalog stored with `price_cents` and `inventory_quantity`. */
|
|
25
|
+
export declare const CENTS_FIELDS: RegisterFields;
|
|
26
|
+
/**
|
|
27
|
+
* A point-of-sale register's basket: what is being rung up, held against what
|
|
28
|
+
* is on the shelf, before anything is written. Pure and dependency-free, so
|
|
29
|
+
* the browser bundles it (`@stacksjs/commerce/register`) and a server can
|
|
30
|
+
* check the same rules.
|
|
31
|
+
*
|
|
32
|
+
* A product is anything with an `id`, a `price` in minor units and, when its
|
|
33
|
+
* stock is counted, an `inventory` (null or absent: not counted, never runs
|
|
34
|
+
* out). Field names follow the caller: pass `fields` when a catalog stores
|
|
35
|
+
* them as, say, `price_cents` and `inventory_quantity`.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { addToBasket, basketTotal } from '@stacksjs/commerce/register'
|
|
39
|
+
*
|
|
40
|
+
* let basket = addToBasket([], gatorade)
|
|
41
|
+
* basket = addToBasket(basket, gatorade)
|
|
42
|
+
* basketTotal(basket, catalog) // 700
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare interface BasketLine {
|
|
46
|
+
productId: number
|
|
47
|
+
quantity: number
|
|
48
|
+
}
|
|
49
|
+
export declare interface RegisterFields {
|
|
50
|
+
id: string
|
|
51
|
+
price: string
|
|
52
|
+
inventory: string
|
|
53
|
+
lowStock: string
|
|
54
|
+
}
|
|
55
|
+
export declare interface StockLabel { text: string, tone: 'out' | 'low' | 'ok' }
|
|
56
|
+
declare type Product = Record<string, any>;
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var k=Object.defineProperty;var m=(t)=>t;function g(t,n){this[t]=m.bind(null,n)}var L=(t,n)=>{for(var e in n)k(t,e,{get:n[e],enumerable:!0,configurable:!0,set:g.bind(n,e)})};var T=(t,n,e)=>()=>{if(t)try{n=t(t=0)}catch(o){e=[o]}if(e)throw e[0];return n};var S={};L(S,{CENTS_FIELDS:()=>x,DEFAULT_FIELDS:()=>u,addToBasket:()=>y,basketCount:()=>B,basketLines:()=>f,basketTotal:()=>F,canAdd:()=>p,groupsOf:()=>R,setQuantity:()=>I,shelfLeft:()=>l,stockLabel:()=>b});var u={id:"id",price:"price",inventory:"inventory",lowStock:"low_stock_threshold"},x={id:"id",price:"price_cents",inventory:"inventory_quantity",lowStock:"low_stock_threshold"};function s(t,n){return Number(t[n.id])}function a(t,n){let e=t[n.inventory];return e===null||e===void 0||e===""?null:Number(e)}function l(t,n,e=u){let o=a(t,e);if(o===null)return null;let r=n.find((c)=>c.productId===s(t,e))?.quantity||0;return Math.max(0,o-r)}function p(t,n,e=u){let o=l(t,n,e);return o===null||o>0}function y(t,n,e=u){if(!p(n,t,e))return t;let o=s(n,e);return t.some((r)=>r.productId===o)?t.map((r)=>r.productId===o?{...r,quantity:r.quantity+1}:r):[...t,{productId:o,quantity:1}]}function I(t,n,e,o=u){let r=s(n,o),c=a(n,o),d=Math.min(Math.max(0,Math.floor(Number(e)||0)),c===null?Number.POSITIVE_INFINITY:c);if(d===0)return t.filter((i)=>i.productId!==r);return t.some((i)=>i.productId===r)?t.map((i)=>i.productId===r?{...i,quantity:d}:i):[...t,{productId:r,quantity:d}]}function f(t,n,e=u){return t.map((o)=>({...o,product:n.find((r)=>s(r,e)===o.productId)})).filter((o)=>!!o.product)}function F(t,n,e=u){return f(t,n,e).reduce((o,r)=>o+Number(r.product[e.price]||0)*r.quantity,0)}function B(t){return t.reduce((n,e)=>n+e.quantity,0)}function b(t,n=[],e=u,o=5){let r=l(t,n,e);if(r===null)return null;if(r===0)return{text:"Sold out",tone:"out"};let c=t[e.lowStock]===null||t[e.lowStock]===void 0?o:Number(t[e.lowStock]);return{text:`${r} left`,tone:r<=c?"low":"ok"}}function R(t,n){let e=[];for(let o of t){let r=n(o);if(!e.includes(r))e.push(r)}return e}export{x as CENTS_FIELDS,u as DEFAULT_FIELDS,y as addToBasket,B as basketCount,f as basketLines,F as basketTotal,p as canAdd,R as groupsOf,I as setQuantity,l as shelfLeft,b as stockLabel};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare function isReleaseMode(value: unknown): value is ReleaseMode;
|
|
2
|
+
/** The product row's columns as a config, with the defaults filled in. */
|
|
3
|
+
export declare function rolloutFromProduct(product: { release_mode?: unknown, release_interval_days?: unknown, release_initial_count?: unknown, release_starts_at?: unknown }): RolloutConfig;
|
|
4
|
+
/** When unit `index` unlocks for a buyer whose clock starts at `purchasedAt`. */
|
|
5
|
+
export declare function unlockAt(config: RolloutConfig, index: number, purchasedAt: Date, override?: UnitOverride): Date;
|
|
6
|
+
/** The release of every unit, in order, and whether it is open at `now`. */
|
|
7
|
+
export declare function releaseSchedule(config: RolloutConfig, count: number, purchasedAt: Date, now: Date, overrides?: UnitOverride[]): UnitRelease[];
|
|
8
|
+
/**
|
|
9
|
+
* How many leading units are open. A plan's weeks are written to the calendar
|
|
10
|
+
* in order, so an early week never waits behind a later one that opened first
|
|
11
|
+
* (possible with per-video overrides, which plans do not have).
|
|
12
|
+
*/
|
|
13
|
+
export declare function unlockedPrefix(schedule: UnitRelease[]): number;
|
|
14
|
+
/** A sentence for the product page: what a buyer gets, and when. */
|
|
15
|
+
export declare function describeRollout(config: RolloutConfig, count: number, unit: 'video' | 'week', now?: Date): string;
|
|
16
|
+
/**
|
|
17
|
+
* When each part of a digital product opens for a buyer: the videos of a
|
|
18
|
+
* series, the weeks of a training plan or a course. Pure and dependency-free,
|
|
19
|
+
* so the browser bundles it (`@stacksjs/commerce/releases`) and a server
|
|
20
|
+
* decides the same way.
|
|
21
|
+
*
|
|
22
|
+
* - `all_at_once`: every unit at the anchor.
|
|
23
|
+
* - `drip`: the first `initialCount` units at the anchor, then one more every
|
|
24
|
+
* `intervalDays`. A video's own `offsetDays` overrides its slot.
|
|
25
|
+
* - `scheduled`: the same dates for every buyer, from `startsAt`, stepping the
|
|
26
|
+
* same way. A video's own `releaseAt` overrides its slot. Someone buying
|
|
27
|
+
* late gets everything already released.
|
|
28
|
+
*
|
|
29
|
+
* The anchor is the purchase, but never before launch: a product whose
|
|
30
|
+
* `startsAt` is still ahead is a pre-sale, and nothing opens until then.
|
|
31
|
+
*/
|
|
32
|
+
export declare const RELEASE_MODES: readonly ['all_at_once', 'drip', 'scheduled'];
|
|
33
|
+
export declare interface RolloutConfig {
|
|
34
|
+
mode: ReleaseMode
|
|
35
|
+
intervalDays: number
|
|
36
|
+
initialCount: number
|
|
37
|
+
startsAt: string | null
|
|
38
|
+
}
|
|
39
|
+
export declare interface UnitOverride {
|
|
40
|
+
offsetDays?: number | null
|
|
41
|
+
releaseAt?: string | null
|
|
42
|
+
}
|
|
43
|
+
export declare interface UnitRelease {
|
|
44
|
+
index: number
|
|
45
|
+
unlock_at: string
|
|
46
|
+
unlocked: boolean
|
|
47
|
+
}
|
|
48
|
+
export type ReleaseMode = typeof RELEASE_MODES[number];
|
package/dist/releases.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var _=Object.defineProperty;var b=(e)=>e;function h(e,t){this[e]=b.bind(null,t)}var v=(e,t)=>{for(var n in t)_(e,n,{get:t[n],enumerable:!0,configurable:!0,set:h.bind(t,n)})};var A=(e,t,n)=>()=>{if(e)try{t=e(e=0)}catch(o){n=[o]}if(n)throw n[0];return t};var p={};v(p,{RELEASE_MODES:()=>D,describeRollout:()=>S,isReleaseMode:()=>f,releaseSchedule:()=>k,rolloutFromProduct:()=>w,unlockAt:()=>y,unlockedPrefix:()=>R});var D=["all_at_once","drip","scheduled"];function f(e){return typeof e==="string"&&D.includes(e)}function w(e){let t=Number(e.release_interval_days),n=Number(e.release_initial_count),o=s(e.release_starts_at);return{mode:f(e.release_mode)?e.release_mode:"all_at_once",intervalDays:Number.isInteger(t)&&t>0?t:7,initialCount:Number.isInteger(n)&&n>0?n:1,startsAt:o===null?null:new Date(o).toISOString()}}function s(e){if(e==null||e==="")return null;let t=new Date(String(e)).getTime();return Number.isFinite(t)?t:null}function d(e,t){return Math.max(0,t-(e.initialCount-1))}function y(e,t,n,o={}){let i=s(e.startsAt),a=Math.max(n.getTime(),i??0);if(e.mode==="scheduled"){let r=s(o.releaseAt);if(r!==null)return new Date(r);let l=i??n.getTime();return new Date(l+d(e,t)*e.intervalDays*86400000)}if(e.mode==="drip"){let r=o.offsetDays;if(r!=null&&Number.isFinite(Number(r))&&Number(r)>=0)return new Date(a+Number(r)*86400000);return new Date(a+d(e,t)*e.intervalDays*86400000)}return new Date(a)}function k(e,t,n,o,i=[]){let a=[];for(let r=0;r<t;r++){let l=y(e,r,n,i[r]||{});a.push({index:r,unlock_at:l.toISOString(),unlocked:l.getTime()<=o.getTime()})}return a}function R(e){let t=0;for(let n of e){if(!n.unlocked)break;t++}return t}function S(e,t,n,o=new Date){let i=(c)=>`${c} ${n}${c===1?"":"s"}`,a=s(e.startsAt),r=a!==null&&a>o.getTime()?` from ${new Date(a).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric",timeZone:"UTC"})}`:"";if(t<=0)return"";if(t===1)return`Available${r||" immediately"}.`;if(e.mode==="all_at_once"||t<=e.initialCount)return`All ${i(t)} available${r||" immediately"}.`;let l=e.intervalDays===1?"every day":e.intervalDays===7?"every week":`every ${e.intervalDays} days`,u=e.initialCount===1,m=u?`The first ${n}`:`The first ${i(e.initialCount)}`;if(e.mode==="drip")return`${m} ${u?"unlocks":"unlock"}${r||" on purchase"}, then one more ${l}.`;if(r)return`${m} ${u?"releases":"release"} on ${r.slice(6)}, then one more ${l}, on the same dates for everyone.`;return`Releasing on a shared schedule${a===null?"":` since ${new Date(a).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric",timeZone:"UTC"})}`}, one more ${n} ${l}. Join now and everything already out is yours.`}export{D as RELEASE_MODES,S as describeRollout,f as isReleaseMode,k as releaseSchedule,w as rolloutFromProduct,y as unlockAt,R as unlockedPrefix};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/commerce",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.75.
|
|
4
|
+
"version": "0.75.90",
|
|
5
5
|
"description": "Stacks commerce utilities.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"prepublishOnly": "bun run build"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@stacksjs/cache": "0.75.
|
|
61
|
-
"@stacksjs/config": "0.75.
|
|
62
|
-
"@stacksjs/database": "0.75.
|
|
63
|
-
"@stacksjs/env": "0.75.
|
|
64
|
-
"@stacksjs/error-handling": "0.75.
|
|
65
|
-
"@stacksjs/events": "0.75.
|
|
66
|
-
"@stacksjs/logging": "0.75.
|
|
67
|
-
"@stacksjs/orm": "0.75.
|
|
68
|
-
"@stacksjs/payments": "0.75.
|
|
69
|
-
"@stacksjs/realtime": "0.75.
|
|
70
|
-
"@stacksjs/strings": "0.75.
|
|
60
|
+
"@stacksjs/cache": "0.75.90",
|
|
61
|
+
"@stacksjs/config": "0.75.90",
|
|
62
|
+
"@stacksjs/database": "0.75.90",
|
|
63
|
+
"@stacksjs/env": "0.75.90",
|
|
64
|
+
"@stacksjs/error-handling": "0.75.90",
|
|
65
|
+
"@stacksjs/events": "0.75.90",
|
|
66
|
+
"@stacksjs/logging": "0.75.90",
|
|
67
|
+
"@stacksjs/orm": "0.75.90",
|
|
68
|
+
"@stacksjs/payments": "0.75.90",
|
|
69
|
+
"@stacksjs/realtime": "0.75.90",
|
|
70
|
+
"@stacksjs/strings": "0.75.90",
|
|
71
71
|
"ts-spreadsheets": "^0.2.0"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|