@lacspace/commission 1.0.0 → 1.0.2
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 +4 -4
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,9 +91,9 @@ split(100, [
|
|
|
91
91
|
|
|
92
92
|
## Licensing
|
|
93
93
|
|
|
94
|
-
This package is **free** under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
|
|
94
|
+
This package is **free** under the **[Lacspace Free Licence](https://developer.lacspace.com/licenses/lacspace-free-1.0)** — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice.
|
|
95
95
|
|
|
96
|
-
Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
|
|
96
|
+
Not every Lacspace package is free. We also offer **Commercial** (paid), **Client-specific**, and **Private** (proprietary) packages under separate terms. See the full **[Lacspace Licence Centre](https://developer.lacspace.com/licenses)**.
|
|
97
97
|
|
|
98
98
|
<!-- LACSPACE-DEV-PLATFORM -->
|
|
99
99
|
|
|
@@ -101,7 +101,7 @@ Not every Lacspace package is free. We also offer **Commercial** (paid), **Clien
|
|
|
101
101
|
|
|
102
102
|
## The Lacspace Developer Platform
|
|
103
103
|
|
|
104
|
-
`@lacspace/commission` is part of **
|
|
104
|
+
`@lacspace/commission` is part of **80+ zero-dependency, isomorphic TypeScript packages**. Explore the ecosystem:
|
|
105
105
|
|
|
106
106
|
- 🗂️ **All packages** — https://developer.lacspace.com/packages
|
|
107
107
|
- 🧭 **Developer handbook** — https://developer.lacspace.com/handbook
|
|
@@ -109,4 +109,4 @@ Not every Lacspace package is free. We also offer **Commercial** (paid), **Clien
|
|
|
109
109
|
- 🖥️ **Finished app templates** — https://templates.lacspace.com
|
|
110
110
|
- 🚀 **Scaffold a full app** — `npm create lacspace-app@latest`
|
|
111
111
|
|
|
112
|
-
Free under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — a permissive, free-to-use licence.
|
|
112
|
+
Free under the **[Lacspace Free Licence](https://developer.lacspace.com/licenses/lacspace-free-1.0)** — a permissive, free-to-use licence.
|
package/dist/index.cjs
CHANGED
|
@@ -41,7 +41,9 @@ function split(amount, shares) {
|
|
|
41
41
|
if (total <= 0) {
|
|
42
42
|
const base = Math.trunc(amt / n);
|
|
43
43
|
const rem = amt - base * n;
|
|
44
|
-
|
|
44
|
+
const step = rem >= 0 ? 1 : -1;
|
|
45
|
+
const count = Math.abs(rem);
|
|
46
|
+
return shares.map((s, i) => ({ party: s.party, amount: base + (i < count ? step : 0) }));
|
|
45
47
|
}
|
|
46
48
|
const ideals = shares.map((s) => amt * s.rate / total);
|
|
47
49
|
const parts = shares.map((s, i) => {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAiEO,SAAS,UAAA,CAAW,MAAsB,MAAA,EAAkC;AACjF,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAE7B,EAAA,IAAI,GAAA;AACJ,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,MAAA;AACH,MAAA,GAAA,GAAM,IAAA,CAAK,MAAA;AACX,MAAA;AAAA,IACF,KAAK,SAAA;AACH,MAAA,GAAA,GAAM,MAAM,IAAA,CAAK,IAAA;AACjB,MAAA;AAAA,IACF,KAAK,QAAA,EAAU;AACb,MAAA,IAAI,GAAA,GAAM,CAAA;AACV,MAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,MAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,QAAA,MAAM,KAAA,GAAQ,KAAK,IAAA,KAAS,IAAA,GAAO,MAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,GAAG,CAAA;AAChE,QAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,UAAA,GAAA,IAAA,CAAQ,KAAA,GAAQ,SAAS,IAAA,CAAK,IAAA;AAC9B,UAAA,KAAA,GAAQ,KAAA;AAAA,QACV;AACA,QAAA,IAAI,IAAA,CAAK,IAAA,KAAS,IAAA,IAAQ,GAAA,IAAO,KAAK,IAAA,EAAM;AAAA,MAC9C;AACA,MAAA,GAAA,GAAM,GAAA;AACN,MAAA;AAAA,IACF;AAAA;AAGF,EAAA,IAAI,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACtB,EAAA,IAAI,KAAK,GAAA,KAAQ,MAAA,IAAa,IAAI,IAAA,CAAK,GAAA,MAAS,IAAA,CAAK,GAAA;AACrD,EAAA,IAAI,KAAK,GAAA,KAAQ,MAAA,IAAa,IAAI,IAAA,CAAK,GAAA,MAAS,IAAA,CAAK,GAAA;AAErD,EAAA,MAAM,MAAM,GAAA,GAAM,CAAA;AAClB,EAAA,MAAM,aAAA,GAAgB,GAAA,KAAQ,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,GAAA;AAC1C,EAAA,OAAO,EAAE,UAAA,EAAY,CAAA,EAAG,GAAA,EAAK,aAAA,EAAc;AAC7C;AASO,SAAS,KAAA,CAAM,QAAgB,MAAA,EAA8B;AAClE,EAAA,MAAM,IAAI,MAAA,CAAO,MAAA;AACjB,EAAA,IAAI,CAAA,KAAM,CAAA,EAAG,OAAO,EAAC;AAErB,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,OAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,IAAA,EAAM,CAAC,CAAA;AAGnD,EAAA,IAAI,SAAS,CAAA,EAAG;AACd,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,CAAC,CAAA;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lacspace/commission — commission & payout calculation engine.\n *\n * All money is expressed in **integer minor units** (e.g. cents, paisa).\n * There are no floating-point money values anywhere in this package.\n *\n * Isomorphic, dependency-free, side-effect-free.\n */\n\n/** Optional floor / cap applied to a computed commission (minor units). */\nexport interface CommissionBounds {\n /** Minimum commission (floor), in minor units. */\n min?: number;\n /** Maximum commission (cap), in minor units. */\n max?: number;\n}\n\n/** A single marginal bracket in a tiered rule. */\nexport interface Tier {\n /**\n * Upper boundary of this bracket, in minor units, or `null` for infinity.\n * The last tier in a list should use `upTo: null`.\n */\n upTo: number | null;\n /** Marginal rate applied to the portion of `amount` inside this bracket (0..1). */\n rate: number;\n}\n\n/** A commission rule: flat fee, percentage, or marginal tiers. */\nexport type CommissionRule =\n | ({ type: \"flat\"; amount: number } & CommissionBounds)\n | ({ type: \"percent\"; rate: number } & CommissionBounds)\n | ({ type: \"tiered\"; tiers: Tier[] } & CommissionBounds);\n\n/** Result of a commission computation (all money in minor units). */\nexport interface CommissionResult {\n /** Commission charged, in minor units. */\n commission: number;\n /** Amount left after commission (`amount - commission`), in minor units. */\n net: number;\n /** Effective rate = `commission / amount` (0..1); `0` when amount is `0`. */\n effectiveRate: number;\n}\n\n/** A payout share used by {@link split}. */\nexport interface Share {\n party: string;\n /** Relative weight of this share. Shares need not sum to 1. */\n rate: number;\n}\n\n/** One allocated slice returned by {@link split}. */\nexport interface SplitPart {\n party: string;\n /** Allocated amount, in minor units. */\n amount: number;\n}\n\n/**\n * Compute the commission for `amount` under `rule`.\n *\n * `amount` is truncated to an integer number of minor units. The raw\n * commission is rounded to the nearest minor unit, then clamped into\n * `[min, max]` when those bounds are present.\n */\nexport function commission(rule: CommissionRule, amount: number): CommissionResult {\n const amt = Math.trunc(amount);\n\n let raw: number;\n switch (rule.type) {\n case \"flat\":\n raw = rule.amount;\n break;\n case \"percent\":\n raw = amt * rule.rate;\n break;\n case \"tiered\": {\n let acc = 0;\n let lower = 0;\n for (const tier of rule.tiers) {\n const upper = tier.upTo === null ? amt : Math.min(tier.upTo, amt);\n if (upper > lower) {\n acc += (upper - lower) * tier.rate;\n lower = upper;\n }\n if (tier.upTo !== null && amt <= tier.upTo) break;\n }\n raw = acc;\n break;\n }\n }\n\n let c = Math.round(raw);\n if (rule.max !== undefined && c > rule.max) c = rule.max;\n if (rule.min !== undefined && c < rule.min) c = rule.min;\n\n const net = amt - c;\n const effectiveRate = amt === 0 ? 0 : c / amt;\n return { commission: c, net, effectiveRate };\n}\n\n/**\n * Split `amount` (minor units) proportionally across `shares`.\n *\n * Uses the largest-remainder method so the returned amounts always sum to\n * `amount` **exactly** — no minor units are lost or invented. When the shares'\n * rates sum to zero, `amount` is distributed as evenly as possible instead.\n */\nexport function split(amount: number, shares: Share[]): SplitPart[] {\n const n = shares.length;\n if (n === 0) return [];\n\n const amt = Math.trunc(amount);\n const total = shares.reduce((s, x) => s + x.rate, 0);\n\n // Degenerate weights: fall back to an even distribution.\n if (total <= 0) {\n const base = Math.trunc(amt / n);\n // `rem` may be negative when `amt` is negative; hand out the leftover units\n // one at a time in the matching direction so the parts still sum to `amt`.\n const rem = amt - base * n;\n const step = rem >= 0 ? 1 : -1;\n const count = Math.abs(rem);\n return shares.map((s, i) => ({ party: s.party, amount: base + (i < count ? step : 0) }));\n }\n\n const ideals = shares.map((s) => (amt * s.rate) / total);\n const parts: SplitPart[] = shares.map((s, i) => {\n const ideal = ideals[i] ?? 0;\n return { party: s.party, amount: Math.floor(ideal) };\n });\n\n const allocated = parts.reduce((a, p) => a + p.amount, 0);\n let remainder = amt - allocated;\n\n // Hand out the leftover minor units to the largest fractional parts first.\n const order = ideals\n .map((ideal, i) => ({ i, frac: ideal - Math.floor(ideal) }))\n .sort((a, b) => b.frac - a.frac);\n\n let k = 0;\n while (remainder > 0 && order.length > 0) {\n const target = order[k % order.length];\n if (target) {\n const part = parts[target.i];\n if (part) part.amount += 1;\n }\n remainder--;\n k++;\n }\n\n return parts;\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,9 @@ function split(amount, shares) {
|
|
|
39
39
|
if (total <= 0) {
|
|
40
40
|
const base = Math.trunc(amt / n);
|
|
41
41
|
const rem = amt - base * n;
|
|
42
|
-
|
|
42
|
+
const step = rem >= 0 ? 1 : -1;
|
|
43
|
+
const count = Math.abs(rem);
|
|
44
|
+
return shares.map((s, i) => ({ party: s.party, amount: base + (i < count ? step : 0) }));
|
|
43
45
|
}
|
|
44
46
|
const ideals = shares.map((s) => amt * s.rate / total);
|
|
45
47
|
const parts = shares.map((s, i) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";AAiEO,SAAS,UAAA,CAAW,MAAsB,MAAA,EAAkC;AACjF,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAE7B,EAAA,IAAI,GAAA;AACJ,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,MAAA;AACH,MAAA,GAAA,GAAM,IAAA,CAAK,MAAA;AACX,MAAA;AAAA,IACF,KAAK,SAAA;AACH,MAAA,GAAA,GAAM,MAAM,IAAA,CAAK,IAAA;AACjB,MAAA;AAAA,IACF,KAAK,QAAA,EAAU;AACb,MAAA,IAAI,GAAA,GAAM,CAAA;AACV,MAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,MAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,QAAA,MAAM,KAAA,GAAQ,KAAK,IAAA,KAAS,IAAA,GAAO,MAAM,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,GAAG,CAAA;AAChE,QAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,UAAA,GAAA,IAAA,CAAQ,KAAA,GAAQ,SAAS,IAAA,CAAK,IAAA;AAC9B,UAAA,KAAA,GAAQ,KAAA;AAAA,QACV;AACA,QAAA,IAAI,IAAA,CAAK,IAAA,KAAS,IAAA,IAAQ,GAAA,IAAO,KAAK,IAAA,EAAM;AAAA,MAC9C;AACA,MAAA,GAAA,GAAM,GAAA;AACN,MAAA;AAAA,IACF;AAAA;AAGF,EAAA,IAAI,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AACtB,EAAA,IAAI,KAAK,GAAA,KAAQ,MAAA,IAAa,IAAI,IAAA,CAAK,GAAA,MAAS,IAAA,CAAK,GAAA;AACrD,EAAA,IAAI,KAAK,GAAA,KAAQ,MAAA,IAAa,IAAI,IAAA,CAAK,GAAA,MAAS,IAAA,CAAK,GAAA;AAErD,EAAA,MAAM,MAAM,GAAA,GAAM,CAAA;AAClB,EAAA,MAAM,aAAA,GAAgB,GAAA,KAAQ,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,GAAA;AAC1C,EAAA,OAAO,EAAE,UAAA,EAAY,CAAA,EAAG,GAAA,EAAK,aAAA,EAAc;AAC7C;AASO,SAAS,KAAA,CAAM,QAAgB,MAAA,EAA8B;AAClE,EAAA,MAAM,IAAI,MAAA,CAAO,MAAA;AACjB,EAAA,IAAI,CAAA,KAAM,CAAA,EAAG,OAAO,EAAC;AAErB,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAC7B,EAAA,MAAM,KAAA,GAAQ,OAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,CAAA,CAAE,IAAA,EAAM,CAAC,CAAA;AAGnD,EAAA,IAAI,SAAS,CAAA,EAAG;AACd,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,CAAC,CAAA;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @lacspace/commission — commission & payout calculation engine.\n *\n * All money is expressed in **integer minor units** (e.g. cents, paisa).\n * There are no floating-point money values anywhere in this package.\n *\n * Isomorphic, dependency-free, side-effect-free.\n */\n\n/** Optional floor / cap applied to a computed commission (minor units). */\nexport interface CommissionBounds {\n /** Minimum commission (floor), in minor units. */\n min?: number;\n /** Maximum commission (cap), in minor units. */\n max?: number;\n}\n\n/** A single marginal bracket in a tiered rule. */\nexport interface Tier {\n /**\n * Upper boundary of this bracket, in minor units, or `null` for infinity.\n * The last tier in a list should use `upTo: null`.\n */\n upTo: number | null;\n /** Marginal rate applied to the portion of `amount` inside this bracket (0..1). */\n rate: number;\n}\n\n/** A commission rule: flat fee, percentage, or marginal tiers. */\nexport type CommissionRule =\n | ({ type: \"flat\"; amount: number } & CommissionBounds)\n | ({ type: \"percent\"; rate: number } & CommissionBounds)\n | ({ type: \"tiered\"; tiers: Tier[] } & CommissionBounds);\n\n/** Result of a commission computation (all money in minor units). */\nexport interface CommissionResult {\n /** Commission charged, in minor units. */\n commission: number;\n /** Amount left after commission (`amount - commission`), in minor units. */\n net: number;\n /** Effective rate = `commission / amount` (0..1); `0` when amount is `0`. */\n effectiveRate: number;\n}\n\n/** A payout share used by {@link split}. */\nexport interface Share {\n party: string;\n /** Relative weight of this share. Shares need not sum to 1. */\n rate: number;\n}\n\n/** One allocated slice returned by {@link split}. */\nexport interface SplitPart {\n party: string;\n /** Allocated amount, in minor units. */\n amount: number;\n}\n\n/**\n * Compute the commission for `amount` under `rule`.\n *\n * `amount` is truncated to an integer number of minor units. The raw\n * commission is rounded to the nearest minor unit, then clamped into\n * `[min, max]` when those bounds are present.\n */\nexport function commission(rule: CommissionRule, amount: number): CommissionResult {\n const amt = Math.trunc(amount);\n\n let raw: number;\n switch (rule.type) {\n case \"flat\":\n raw = rule.amount;\n break;\n case \"percent\":\n raw = amt * rule.rate;\n break;\n case \"tiered\": {\n let acc = 0;\n let lower = 0;\n for (const tier of rule.tiers) {\n const upper = tier.upTo === null ? amt : Math.min(tier.upTo, amt);\n if (upper > lower) {\n acc += (upper - lower) * tier.rate;\n lower = upper;\n }\n if (tier.upTo !== null && amt <= tier.upTo) break;\n }\n raw = acc;\n break;\n }\n }\n\n let c = Math.round(raw);\n if (rule.max !== undefined && c > rule.max) c = rule.max;\n if (rule.min !== undefined && c < rule.min) c = rule.min;\n\n const net = amt - c;\n const effectiveRate = amt === 0 ? 0 : c / amt;\n return { commission: c, net, effectiveRate };\n}\n\n/**\n * Split `amount` (minor units) proportionally across `shares`.\n *\n * Uses the largest-remainder method so the returned amounts always sum to\n * `amount` **exactly** — no minor units are lost or invented. When the shares'\n * rates sum to zero, `amount` is distributed as evenly as possible instead.\n */\nexport function split(amount: number, shares: Share[]): SplitPart[] {\n const n = shares.length;\n if (n === 0) return [];\n\n const amt = Math.trunc(amount);\n const total = shares.reduce((s, x) => s + x.rate, 0);\n\n // Degenerate weights: fall back to an even distribution.\n if (total <= 0) {\n const base = Math.trunc(amt / n);\n // `rem` may be negative when `amt` is negative; hand out the leftover units\n // one at a time in the matching direction so the parts still sum to `amt`.\n const rem = amt - base * n;\n const step = rem >= 0 ? 1 : -1;\n const count = Math.abs(rem);\n return shares.map((s, i) => ({ party: s.party, amount: base + (i < count ? step : 0) }));\n }\n\n const ideals = shares.map((s) => (amt * s.rate) / total);\n const parts: SplitPart[] = shares.map((s, i) => {\n const ideal = ideals[i] ?? 0;\n return { party: s.party, amount: Math.floor(ideal) };\n });\n\n const allocated = parts.reduce((a, p) => a + p.amount, 0);\n let remainder = amt - allocated;\n\n // Hand out the leftover minor units to the largest fractional parts first.\n const order = ideals\n .map((ideal, i) => ({ i, frac: ideal - Math.floor(ideal) }))\n .sort((a, b) => b.frac - a.frac);\n\n let k = 0;\n while (remainder > 0 && order.length > 0) {\n const target = order[k % order.length];\n if (target) {\n const part = parts[target.i];\n if (part) part.amount += 1;\n }\n remainder--;\n k++;\n }\n\n return parts;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lacspace/commission",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Commission & payout calculation engine — flat, percentage and marginal-tiered rules with min/cap, plus exact proportional split with remainder distribution. Integer minor units, zero floats. Isomorphic (Node, edge, browser).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|