@nextad/auction 0.1.3 → 0.1.4

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,50 @@
1
+ import { Bid, BidResponse } from 'iab-openrtb/v26';
2
+
3
+ interface CurrencyRates {
4
+ /** ISO 4217 Currency Code (Example: USD, JPY, EUR) */
5
+ [currency: string]: number;
6
+ }
7
+ interface ConversionRates {
8
+ /** convert rate of base currency */
9
+ [baseCurrency: string]: CurrencyRates;
10
+ }
11
+ interface CurrencyConversionData {
12
+ dataAsOf: string;
13
+ generatedAt: string;
14
+ conversions: ConversionRates;
15
+ }
16
+
17
+ type AuctionType = "open" | "closed";
18
+ type AuctionOptions = {
19
+ lossProcessing?: boolean;
20
+ currencyConversionData?: CurrencyConversionData;
21
+ };
22
+ declare class Auction {
23
+ private losingBids;
24
+ private bids;
25
+ private itemIds;
26
+ private status;
27
+ private options;
28
+ private bidInformation;
29
+ constructor(itemOrImpressionIds: string | string[], options?: AuctionOptions);
30
+ getStatus(): AuctionType;
31
+ getLosingBids(): {
32
+ v26: Bid[];
33
+ };
34
+ placeBidResponseV26(bidResponse: BidResponse): this;
35
+ end(): Bid;
36
+ private setLosingBids;
37
+ private handleLossBids;
38
+ private handleLossBid;
39
+ }
40
+
41
+ declare class BidNotFoundException {
42
+ name: string;
43
+ message: string;
44
+ }
45
+ declare class AlreadyEndedAuctionException {
46
+ name: string;
47
+ message: string;
48
+ }
49
+
50
+ export { AlreadyEndedAuctionException, Auction, BidNotFoundException };
@@ -0,0 +1,50 @@
1
+ import { Bid, BidResponse } from 'iab-openrtb/v26';
2
+
3
+ interface CurrencyRates {
4
+ /** ISO 4217 Currency Code (Example: USD, JPY, EUR) */
5
+ [currency: string]: number;
6
+ }
7
+ interface ConversionRates {
8
+ /** convert rate of base currency */
9
+ [baseCurrency: string]: CurrencyRates;
10
+ }
11
+ interface CurrencyConversionData {
12
+ dataAsOf: string;
13
+ generatedAt: string;
14
+ conversions: ConversionRates;
15
+ }
16
+
17
+ type AuctionType = "open" | "closed";
18
+ type AuctionOptions = {
19
+ lossProcessing?: boolean;
20
+ currencyConversionData?: CurrencyConversionData;
21
+ };
22
+ declare class Auction {
23
+ private losingBids;
24
+ private bids;
25
+ private itemIds;
26
+ private status;
27
+ private options;
28
+ private bidInformation;
29
+ constructor(itemOrImpressionIds: string | string[], options?: AuctionOptions);
30
+ getStatus(): AuctionType;
31
+ getLosingBids(): {
32
+ v26: Bid[];
33
+ };
34
+ placeBidResponseV26(bidResponse: BidResponse): this;
35
+ end(): Bid;
36
+ private setLosingBids;
37
+ private handleLossBids;
38
+ private handleLossBid;
39
+ }
40
+
41
+ declare class BidNotFoundException {
42
+ name: string;
43
+ message: string;
44
+ }
45
+ declare class AlreadyEndedAuctionException {
46
+ name: string;
47
+ message: string;
48
+ }
49
+
50
+ export { AlreadyEndedAuctionException, Auction, BidNotFoundException };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var u=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var l=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var f=(e,i)=>{for(var s in i)u(e,s,{get:i[s],enumerable:!0})},g=(e,i,s,r)=>{if(i&&typeof i=="object"||typeof i=="function")for(let t of l(i))!v.call(e,t)&&t!==s&&u(e,t,{get:()=>i[t],enumerable:!(r=B(i,t))||r.enumerable});return e};var m=e=>g(u({},"__esModule",{value:!0}),e);var y={};f(y,{AlreadyEndedAuctionException:()=>a,Auction:()=>h,BidNotFoundException:()=>n});module.exports=m(y);var d=class{static getHighestBidV26(i,s,r){return r?i.reduce((t,o)=>{let c=this.convertPrice(t,s,r);return this.convertPrice(o,s,r)>c?o:t}):i.reduce((t,o)=>{let c=t.price*100;return o.price*100>c?o:t})}static convertPrice(i,s,r){let t=i.price,o=r.conversions[s.get(i)?.currency||"USD"];if(!o)return t*100;let p=o["USD"];return t*(p||1)*100}};var n=class{name="BidNotFoundException";message="Bid is not found."},a=class{name="AlreadyEndedAuctionException";message="Auction is already ended."};var h=class{losingBids;bids;itemIds=[];status;options;bidInformation;constructor(i,s={}){this.options={lossProcessing:!0,...s},this.bids={v26:[]},this.losingBids={v26:[]},this.status="open",this.bidInformation=new WeakMap,typeof i=="string"?this.itemIds.push(i):this.itemIds.push(...i)}getStatus(){return this.status}getLosingBids(){return this.losingBids}placeBidResponseV26(i){if(i.seatbid){let s=i.seatbid.flatMap(r=>r.bid.map(t=>(this.bidInformation.set(t,{currency:i.cur,version:"2.6",seat:r.seat}),t)));this.bids.v26.push(...s)}return this}end(){if(this.status!=="open")throw new a;if(!this.bids.v26.length)throw new n;let i=d.getHighestBidV26(this.bids.v26,this.bidInformation,this.options.currencyConversionData);return this.setLosingBids(i),this.handleLossBids(),this.status="closed",i}setLosingBids(i){this.losingBids.v26.push(...this.bids.v26.filter(s=>s.id!==i.id))}handleLossBids(){this.losingBids.v26.forEach(i=>{this.handleLossBid(i)})}handleLossBid(i){this.options.lossProcessing&&i.lurl&&fetch(i.lurl,{keepalive:!0})}};0&&(module.exports={AlreadyEndedAuctionException,Auction,BidNotFoundException});
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var n=class{static getHighestBidV26(i,s,e){return e?i.reduce((t,r)=>{let o=this.convertPrice(t,s,e);return this.convertPrice(r,s,e)>o?r:t}):i.reduce((t,r)=>{let o=t.price*100;return r.price*100>o?r:t})}static convertPrice(i,s,e){let t=i.price,r=e.conversions[s.get(i)?.currency||"USD"];if(!r)return t*100;let p=r["USD"];return t*(p||1)*100}};var a=class{name="BidNotFoundException";message="Bid is not found."},c=class{name="AlreadyEndedAuctionException";message="Auction is already ended."};var u=class{losingBids;bids;itemIds=[];status;options;bidInformation;constructor(i,s={}){this.options={lossProcessing:!0,...s},this.bids={v26:[]},this.losingBids={v26:[]},this.status="open",this.bidInformation=new WeakMap,typeof i=="string"?this.itemIds.push(i):this.itemIds.push(...i)}getStatus(){return this.status}getLosingBids(){return this.losingBids}placeBidResponseV26(i){if(i.seatbid){let s=i.seatbid.flatMap(e=>e.bid.map(t=>(this.bidInformation.set(t,{currency:i.cur,version:"2.6",seat:e.seat}),t)));this.bids.v26.push(...s)}return this}end(){if(this.status!=="open")throw new c;if(!this.bids.v26.length)throw new a;let i=n.getHighestBidV26(this.bids.v26,this.bidInformation,this.options.currencyConversionData);return this.setLosingBids(i),this.handleLossBids(),this.status="closed",i}setLosingBids(i){this.losingBids.v26.push(...this.bids.v26.filter(s=>s.id!==i.id))}handleLossBids(){this.losingBids.v26.forEach(i=>{this.handleLossBid(i)})}handleLossBid(i){this.options.lossProcessing&&i.lurl&&fetch(i.lurl,{keepalive:!0})}};export{c as AlreadyEndedAuctionException,u as Auction,a as BidNotFoundException};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextad/auction",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "Kai Miyamoto",
@@ -25,6 +25,11 @@
25
25
  "import": "./dist/index.mjs",
26
26
  "require": "./dist/index.js"
27
27
  },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
28
33
  "scripts": {
29
34
  "test": "vitest",
30
35
  "build": "tsup"
package/.editorconfig DELETED
File without changes
package/src/Auction.ts DELETED
@@ -1,125 +0,0 @@
1
- import { BidComparator } from "./BidComparator";
2
- import {
3
- AlreadyEndedAuctionException,
4
- BidNotFoundException,
5
- } from "./exceptions";
6
- import type {
7
- BidResponse as BaseBidResponseV26,
8
- Bid as BidV26,
9
- } from "iab-openrtb/v26";
10
- import type { BidInformation, CurrencyConversionData } from "./types";
11
-
12
- type AuctionType = "open" | "closed";
13
-
14
- type AuctionOptions = {
15
- lossProcessing?: boolean;
16
- currencyConversionData?: CurrencyConversionData;
17
- };
18
-
19
- export class Auction {
20
- private losingBids: {
21
- v26: BidV26[];
22
- };
23
- private bids: {
24
- v26: BidV26[];
25
- };
26
-
27
- private itemIds: string[] = [];
28
- private status: AuctionType;
29
- private options: AuctionOptions;
30
- private bidInformation: WeakMap<BidV26, BidInformation>;
31
-
32
- public constructor(
33
- itemOrImpressionIds: string | string[],
34
- options: AuctionOptions = {}
35
- ) {
36
- this.options = {
37
- lossProcessing: true,
38
- ...options,
39
- };
40
-
41
- this.bids = {
42
- v26: [],
43
- };
44
- this.losingBids = {
45
- v26: [],
46
- };
47
- this.status = "open";
48
- this.bidInformation = new WeakMap();
49
-
50
- if (typeof itemOrImpressionIds === "string") {
51
- this.itemIds.push(itemOrImpressionIds);
52
- } else {
53
- this.itemIds.push(...itemOrImpressionIds);
54
- }
55
- }
56
-
57
- public getStatus(): AuctionType {
58
- return this.status;
59
- }
60
-
61
- public getLosingBids(): { v26: BidV26[] } {
62
- return this.losingBids;
63
- }
64
-
65
- public placeBidResponseV26(bidResponse: BaseBidResponseV26): this {
66
- if (bidResponse.seatbid) {
67
- const bids: BidV26[] = bidResponse.seatbid.flatMap((seatbid) => {
68
- return seatbid.bid.map((bid) => {
69
- this.bidInformation.set(bid, {
70
- currency: bidResponse.cur,
71
- version: "2.6",
72
- seat: seatbid.seat,
73
- });
74
- return bid;
75
- });
76
- });
77
-
78
- this.bids.v26.push(...bids);
79
- }
80
-
81
- return this;
82
- }
83
-
84
- public end(): BidV26 {
85
- if (this.status !== "open") {
86
- throw new AlreadyEndedAuctionException();
87
- }
88
-
89
- if (!this.bids.v26.length) {
90
- throw new BidNotFoundException();
91
- }
92
-
93
- const highestBid = BidComparator.getHighestBidV26(
94
- this.bids.v26,
95
- this.bidInformation,
96
- this.options.currencyConversionData
97
- );
98
-
99
- this.setLosingBids(highestBid);
100
- this.handleLossBids();
101
- this.status = "closed";
102
-
103
- return highestBid;
104
- }
105
-
106
- private setLosingBids(winningBid: BidV26): void {
107
- this.losingBids.v26.push(
108
- ...this.bids.v26.filter((bid) => bid.id !== winningBid.id)
109
- );
110
- }
111
-
112
- private handleLossBids(): void {
113
- this.losingBids.v26.forEach((bid) => {
114
- this.handleLossBid(bid);
115
- });
116
- }
117
-
118
- private handleLossBid(bid: BidV26): void {
119
- if (this.options.lossProcessing && bid.lurl) {
120
- fetch(bid.lurl, {
121
- keepalive: true,
122
- });
123
- }
124
- }
125
- }
@@ -1,50 +0,0 @@
1
- import type { BidInformation, CurrencyConversionData } from "./types";
2
- import type { Bid as BidV26 } from "iab-openrtb/v26";
3
-
4
- export class BidComparator {
5
- public static getHighestBidV26(
6
- bids: BidV26[],
7
- bidInformation: WeakMap<BidV26, BidInformation>,
8
- currencyConversionData?: CurrencyConversionData
9
- ): BidV26 {
10
- if (!currencyConversionData) {
11
- return bids.reduce((highest, current) => {
12
- const highestPrice = highest.price * 100;
13
- const currentPrice = current.price * 100;
14
- return currentPrice > highestPrice ? current : highest;
15
- });
16
- }
17
-
18
- return bids.reduce((highest, current) => {
19
- const highestPrice = this.convertPrice(
20
- highest,
21
- bidInformation,
22
- currencyConversionData
23
- );
24
- const currentPrice = this.convertPrice(
25
- current,
26
- bidInformation,
27
- currencyConversionData
28
- );
29
- return currentPrice > highestPrice ? current : highest;
30
- });
31
- }
32
-
33
- private static convertPrice(
34
- bid: BidV26,
35
- bidInformation: WeakMap<BidV26, BidInformation>,
36
- conversionData: CurrencyConversionData
37
- ): number {
38
- const price = bid.price;
39
- const rates =
40
- conversionData.conversions[bidInformation.get(bid)?.currency || "USD"];
41
-
42
- if (!rates) {
43
- return price * 100;
44
- }
45
-
46
- const targetCurrency = "USD";
47
- const rate = rates[targetCurrency];
48
- return price * (rate || 1) * 100;
49
- }
50
- }
@@ -1,9 +0,0 @@
1
- export class BidNotFoundException {
2
- name = "BidNotFoundException";
3
- message = "Bid is not found.";
4
- }
5
-
6
- export class AlreadyEndedAuctionException {
7
- name = "AlreadyEndedAuctionException";
8
- message = "Auction is already ended.";
9
- }
package/src/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export * from './Auction';
2
- export * from '@/exceptions';
@@ -1,22 +0,0 @@
1
- export type BidInformation = {
2
- currency?: string;
3
- seat?: string;
4
- version: string;
5
- };
6
-
7
-
8
- export interface CurrencyRates {
9
- /** ISO 4217 Currency Code (Example: USD, JPY, EUR) */
10
- [currency: string]: number;
11
- }
12
-
13
- interface ConversionRates {
14
- /** convert rate of base currency */
15
- [baseCurrency: string]: CurrencyRates;
16
- }
17
-
18
- export interface CurrencyConversionData {
19
- dataAsOf: string;
20
- generatedAt: string;
21
- conversions: ConversionRates;
22
- }
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../tsconfig.json",
3
- "compilerOptions": {
4
- "moduleResolution": "bundler",
5
- "types": ["vitest/globals"],
6
- "baseUrl": "..",
7
- "paths": {
8
- "@/*": ["src/*"]
9
- }
10
- },
11
- "include": ["./**/*.ts"]
12
- }
@@ -1,176 +0,0 @@
1
- import { openrtbFaker } from "@nextad/faker";
2
- import { Auction } from "@/Auction";
3
- import {
4
- AlreadyEndedAuctionException,
5
- BidNotFoundException,
6
- } from "@/exceptions";
7
-
8
- describe("Auction", () => {
9
- it("returns highest bid from bids with specified item(impression) when auction ends", () => {
10
- const sut = new Auction("impid-1");
11
- const bidResponse = openrtbFaker.v26.bidResponse
12
- .addBannerBid({ impid: "impid-1", price: 10 })
13
- .make();
14
- const bidResponse2 = openrtbFaker.v26.bidResponse
15
- .addBannerBid({ impid: "impid-1", price: 20 })
16
- .make();
17
- sut.placeBidResponseV26(bidResponse);
18
- sut.placeBidResponseV26(bidResponse2);
19
-
20
- const result = sut.end();
21
-
22
- expect(result.id).toBe(bidResponse2.seatbid![0].bid[0].id);
23
- });
24
-
25
- it("returns highest bid from bids with specified items(impressions) when auction ends", () => {
26
- const sut = new Auction(["impid-1", "impid-2"]);
27
- const bidResponse = openrtbFaker.v26.bidResponse
28
- .addBannerBid({ impid: "impid-1", price: 10 })
29
- .make();
30
- const bidResponse2 = openrtbFaker.v26.bidResponse
31
- .addBannerBid({ impid: "impid-1", price: 20 })
32
- .addVideoBid({ impid: "impid-2", price: 30 })
33
- .make();
34
- sut.placeBidResponseV26(bidResponse);
35
- sut.placeBidResponseV26(bidResponse2);
36
-
37
- const result = sut.end();
38
-
39
- expect(result.id).toBe(bidResponse2.seatbid![0].bid[1].id);
40
- });
41
-
42
- it("returns highest bid considering currency when auction ends", () => {
43
- const sut = new Auction("impid-1", {
44
- currencyConversionData: {
45
- dataAsOf: "2021-01-01",
46
- generatedAt: "2021-01-01",
47
- conversions: {
48
- JPY: {
49
- USD: 0.09,
50
- },
51
- },
52
- },
53
- });
54
- const bidResponse = openrtbFaker.v26.bidResponse
55
- .withCurrency("JPY")
56
- .addBannerBid({ impid: "impid-1", price: 10 })
57
- .make();
58
- const bidResponse2 = openrtbFaker.v26.bidResponse
59
- .withCurrency("USD")
60
- .addBannerBid({ impid: "impid-1", price: 5 })
61
- .make();
62
- sut.placeBidResponseV26(bidResponse);
63
- sut.placeBidResponseV26(bidResponse2);
64
-
65
- const result = sut.end();
66
-
67
- expect(result.id).toBe(bidResponse2.seatbid![0].bid[0].id);
68
- });
69
-
70
- it("throws error when attempting to end auction without bids", () => {
71
- const sut = new Auction("impid-1");
72
-
73
- expect(() => sut.end()).toThrow(BidNotFoundException);
74
- });
75
- it("returns losing bids when auction ends", () => {
76
- const sut = new Auction(["impid-1", "impid-2"]);
77
- const bidResponse = openrtbFaker.v26.bidResponse
78
- .addBannerBid({
79
- impid: "impid-1",
80
- price: 10,
81
- })
82
- .addBannerBid({
83
- impid: "impid-2",
84
- price: 20,
85
- })
86
- .make();
87
- sut.placeBidResponseV26(bidResponse);
88
-
89
- sut.end();
90
-
91
- const losingBids = sut.getLosingBids();
92
- expect(losingBids.v26.length).toBe(1);
93
- expect(losingBids.v26[0].id).toBe(bidResponse.seatbid![0].bid[0].id);
94
- });
95
-
96
- it("throws error when attempting to already ended auction", () => {
97
- const sut = new Auction("impid-1");
98
- const bidResponse = openrtbFaker.v26.bidResponse
99
- .addBannerBid({ impid: "impid-1", price: 10 })
100
- .make();
101
- sut.placeBidResponseV26(bidResponse);
102
- sut.end();
103
-
104
- expect(() => sut.end()).toThrow(AlreadyEndedAuctionException);
105
- });
106
-
107
- it("sends loss notification when bid contains loss notice url", () => {
108
- const fetchMock = vi.fn();
109
- vi.stubGlobal("fetch", fetchMock);
110
- const sut = new Auction(["impid-1", "impid-2"]);
111
- const bidResponse = openrtbFaker.v26.bidResponse
112
- .addBannerBid({
113
- impid: "impid-1",
114
- price: 10,
115
- lurl: "https://example.com/lurl",
116
- })
117
- .addBannerBid({
118
- impid: "impid-2",
119
- price: 20,
120
- })
121
- .make();
122
- sut.placeBidResponseV26(bidResponse);
123
-
124
- sut.end();
125
-
126
- expect(fetchMock).toHaveBeenCalledOnce();
127
- expect(fetchMock).toHaveBeenCalledWith("https://example.com/lurl", {
128
- keepalive: true,
129
- });
130
- });
131
-
132
- it("does not send loss notification when bid does not contain loss notice url", () => {
133
- const fetchMock = vi.fn();
134
- vi.stubGlobal("fetch", fetchMock);
135
- const sut = new Auction(["impid-1", "impid-2"]);
136
- const bidResponse = openrtbFaker.v26.bidResponse
137
- .addBannerBid({
138
- impid: "impid-1",
139
- price: 10,
140
- })
141
- .addBannerBid({
142
- impid: "impid-2",
143
- price: 20,
144
- })
145
- .make();
146
- sut.placeBidResponseV26(bidResponse);
147
-
148
- sut.end();
149
-
150
- expect(fetchMock).not.toHaveBeenCalled();
151
- });
152
-
153
- it("does not send loss notification when loss processing is specified as false", () => {
154
- const fetchMock = vi.fn();
155
- vi.stubGlobal("fetch", fetchMock);
156
- const sut = new Auction(["impid-1", "impid-2"], {
157
- lossProcessing: false,
158
- });
159
- const bidResponse = openrtbFaker.v26.bidResponse
160
- .addBannerBid({
161
- impid: "impid-1",
162
- price: 10,
163
- lurl: "https://example.com/lurl",
164
- })
165
- .addBannerBid({
166
- impid: "impid-2",
167
- price: 20,
168
- })
169
- .make();
170
- sut.placeBidResponseV26(bidResponse);
171
-
172
- sut.end();
173
-
174
- expect(fetchMock).not.toHaveBeenCalled();
175
- });
176
- });
package/tsconfig.json DELETED
@@ -1,29 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2022",
4
- "lib": ["es2022", "esnext.disposable", "dom"],
5
-
6
- "module": "ESNext",
7
- "moduleDetection": "force",
8
- "allowJs": true,
9
-
10
- "moduleResolution": "Bundler",
11
- "allowImportingTsExtensions": true,
12
- "verbatimModuleSyntax": true,
13
- "isolatedModules": true,
14
- "preserveConstEnums": true,
15
- "noEmit": true,
16
-
17
- "skipLibCheck": true,
18
- "strict": true,
19
- "noFallthroughCasesInSwitch": true,
20
- "forceConsistentCasingInFileNames": true,
21
-
22
- "baseUrl": ".",
23
- "paths": {
24
- "@/*": ["src/*"]
25
- }
26
- },
27
- "include": ["**/*.ts"],
28
- "exclude": ["node_modules"]
29
- }
package/tsup.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- format: ["esm", "cjs"],
5
- clean: true,
6
- minify: true,
7
- dts: true,
8
- entry: ["src/index.ts"],
9
- });
package/vitest.config.ts DELETED
@@ -1,14 +0,0 @@
1
- import { fileURLToPath } from "node:url";
2
- import path from "node:path";
3
- import { defineConfig } from "vitest/config";
4
-
5
- const __dirname = fileURLToPath(new URL(".", import.meta.url));
6
-
7
- export default defineConfig({
8
- test: {
9
- globals: true,
10
- alias: {
11
- "@": path.resolve(__dirname, "src"),
12
- },
13
- },
14
- });