@takaro/db 0.4.3 → 0.4.6

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,4 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
4
+ //# sourceMappingURL=20251014120000-shop-order-auto-cancel-on-listing-delete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20251014120000-shop-order-auto-cancel-on-listing-delete.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20251014120000-shop-order-auto-cancel-on-listing-delete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAYpD"}
@@ -0,0 +1,70 @@
1
+ export async function up(knex) {
2
+ // Step 1: Create trigger function that auto-cancels orders when listing is deleted
3
+ await knex.raw(`
4
+ CREATE OR REPLACE FUNCTION cancel_orders_on_listing_delete()
5
+ RETURNS TRIGGER AS $$
6
+ BEGIN
7
+ -- Only proceed if deletedAt changed from NULL to a value (soft-delete)
8
+ IF OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL THEN
9
+ -- Cancel all PAID orders for this listing and refund currency
10
+ -- Aggregate refunds by player to handle multiple orders correctly
11
+ WITH orders_to_cancel AS (
12
+ SELECT
13
+ so.id AS order_id,
14
+ so."playerId",
15
+ NEW."gameServerId",
16
+ NEW.domain AS domain,
17
+ NEW.price * so.amount AS refund_amount
18
+ FROM "shopOrder" so
19
+ WHERE so."listingId" = NEW.id
20
+ AND so.status = 'PAID'
21
+ ),
22
+ aggregated_refunds AS (
23
+ SELECT
24
+ "playerId",
25
+ "gameServerId",
26
+ domain,
27
+ SUM(refund_amount) AS total_refund
28
+ FROM orders_to_cancel
29
+ GROUP BY "playerId", "gameServerId", domain
30
+ )
31
+ -- Update playerOnGameServer currency with aggregated total
32
+ UPDATE "playerOnGameServer" pogs
33
+ SET currency = pogs.currency + ar.total_refund
34
+ FROM aggregated_refunds ar
35
+ WHERE pogs."playerId" = ar."playerId"
36
+ AND pogs."gameServerId" = ar."gameServerId"
37
+ AND pogs.domain = ar.domain;
38
+
39
+ -- Update order status to CANCELED
40
+ UPDATE "shopOrder"
41
+ SET status = 'CANCELED'
42
+ WHERE "listingId" = NEW.id
43
+ AND status = 'PAID';
44
+ END IF;
45
+
46
+ RETURN NEW;
47
+ END;
48
+ $$ LANGUAGE plpgsql;
49
+ `);
50
+ // Step 2: Create trigger on shopListing table
51
+ await knex.raw(`
52
+ CREATE TRIGGER auto_cancel_orders_on_listing_delete
53
+ AFTER UPDATE ON "shopListing"
54
+ FOR EACH ROW
55
+ WHEN (OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL)
56
+ EXECUTE FUNCTION cancel_orders_on_listing_delete();
57
+ `);
58
+ }
59
+ export async function down(knex) {
60
+ // Drop trigger
61
+ await knex.raw(`
62
+ DROP TRIGGER IF EXISTS auto_cancel_orders_on_listing_delete ON "shopListing";
63
+ `);
64
+ // Drop function
65
+ await knex.raw(`
66
+ DROP FUNCTION IF EXISTS cancel_orders_on_listing_delete();
67
+ `);
68
+ // Note: We don't reverse the data cleanup since those were genuinely bad orders
69
+ }
70
+ //# sourceMappingURL=20251014120000-shop-order-auto-cancel-on-listing-delete.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20251014120000-shop-order-auto-cancel-on-listing-delete.js","sourceRoot":"","sources":["../../../src/migrations/sql/20251014120000-shop-order-auto-cancel-on-listing-delete.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,mFAAmF;IACnF,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8Cd,CAAC,CAAC;IAEH,8CAA8C;IAC9C,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;GAMd,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,eAAe;IACf,MAAM,IAAI,CAAC,GAAG,CAAC;;GAEd,CAAC,CAAC;IAEH,gBAAgB;IAChB,MAAM,IAAI,CAAC,GAAG,CAAC;;GAEd,CAAC,CAAC;IAEH,gFAAgF;AAClF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
4
+ //# sourceMappingURL=20251101000000-add-deleted-domain-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20251101000000-add-deleted-domain-state.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20251101000000-add-deleted-domain-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBpD"}
@@ -0,0 +1,33 @@
1
+ export async function up(knex) {
2
+ // PostgreSQL requires a multi-step process to update enum constraints
3
+ // 1. Add a temporary column with the new enum values
4
+ // 2. Copy data from old column to new column
5
+ // 3. Drop old column
6
+ // 4. Rename new column to old column name
7
+ await knex.schema.alterTable('domains', (table) => {
8
+ table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE', 'DELETED']).notNullable().defaultTo('ACTIVE');
9
+ });
10
+ await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
11
+ await knex.schema.alterTable('domains', (table) => {
12
+ table.dropColumn('state');
13
+ });
14
+ await knex.schema.alterTable('domains', (table) => {
15
+ table.renameColumn('state_new', 'state');
16
+ });
17
+ }
18
+ export async function down(knex) {
19
+ // Revert to the original enum values (removing DELETED)
20
+ // First, ensure no domains are in DELETED state (or this will fail)
21
+ await knex.raw(`DELETE FROM domains WHERE state = 'DELETED'`);
22
+ await knex.schema.alterTable('domains', (table) => {
23
+ table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE']).notNullable().defaultTo('ACTIVE');
24
+ });
25
+ await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
26
+ await knex.schema.alterTable('domains', (table) => {
27
+ table.dropColumn('state');
28
+ });
29
+ await knex.schema.alterTable('domains', (table) => {
30
+ table.renameColumn('state_new', 'state');
31
+ });
32
+ }
33
+ //# sourceMappingURL=20251101000000-add-deleted-domain-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20251101000000-add-deleted-domain-state.js","sourceRoot":"","sources":["../../../src/migrations/sql/20251101000000-add-deleted-domain-state.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,sEAAsE;IACtE,qDAAqD;IACrD,6CAA6C;IAC7C,qBAAqB;IACrB,0CAA0C;IAE1C,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC9G,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IAEtE,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,wDAAwD;IACxD,oEAAoE;IACpE,MAAM,IAAI,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAE9D,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACnG,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IAEtE,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takaro/db",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
4
4
  "description": "An opinionated data layer",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -0,0 +1,75 @@
1
+ import { Knex } from 'knex';
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ // Step 1: Create trigger function that auto-cancels orders when listing is deleted
5
+ await knex.raw(`
6
+ CREATE OR REPLACE FUNCTION cancel_orders_on_listing_delete()
7
+ RETURNS TRIGGER AS $$
8
+ BEGIN
9
+ -- Only proceed if deletedAt changed from NULL to a value (soft-delete)
10
+ IF OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL THEN
11
+ -- Cancel all PAID orders for this listing and refund currency
12
+ -- Aggregate refunds by player to handle multiple orders correctly
13
+ WITH orders_to_cancel AS (
14
+ SELECT
15
+ so.id AS order_id,
16
+ so."playerId",
17
+ NEW."gameServerId",
18
+ NEW.domain AS domain,
19
+ NEW.price * so.amount AS refund_amount
20
+ FROM "shopOrder" so
21
+ WHERE so."listingId" = NEW.id
22
+ AND so.status = 'PAID'
23
+ ),
24
+ aggregated_refunds AS (
25
+ SELECT
26
+ "playerId",
27
+ "gameServerId",
28
+ domain,
29
+ SUM(refund_amount) AS total_refund
30
+ FROM orders_to_cancel
31
+ GROUP BY "playerId", "gameServerId", domain
32
+ )
33
+ -- Update playerOnGameServer currency with aggregated total
34
+ UPDATE "playerOnGameServer" pogs
35
+ SET currency = pogs.currency + ar.total_refund
36
+ FROM aggregated_refunds ar
37
+ WHERE pogs."playerId" = ar."playerId"
38
+ AND pogs."gameServerId" = ar."gameServerId"
39
+ AND pogs.domain = ar.domain;
40
+
41
+ -- Update order status to CANCELED
42
+ UPDATE "shopOrder"
43
+ SET status = 'CANCELED'
44
+ WHERE "listingId" = NEW.id
45
+ AND status = 'PAID';
46
+ END IF;
47
+
48
+ RETURN NEW;
49
+ END;
50
+ $$ LANGUAGE plpgsql;
51
+ `);
52
+
53
+ // Step 2: Create trigger on shopListing table
54
+ await knex.raw(`
55
+ CREATE TRIGGER auto_cancel_orders_on_listing_delete
56
+ AFTER UPDATE ON "shopListing"
57
+ FOR EACH ROW
58
+ WHEN (OLD."deletedAt" IS NULL AND NEW."deletedAt" IS NOT NULL)
59
+ EXECUTE FUNCTION cancel_orders_on_listing_delete();
60
+ `);
61
+ }
62
+
63
+ export async function down(knex: Knex): Promise<void> {
64
+ // Drop trigger
65
+ await knex.raw(`
66
+ DROP TRIGGER IF EXISTS auto_cancel_orders_on_listing_delete ON "shopListing";
67
+ `);
68
+
69
+ // Drop function
70
+ await knex.raw(`
71
+ DROP FUNCTION IF EXISTS cancel_orders_on_listing_delete();
72
+ `);
73
+
74
+ // Note: We don't reverse the data cleanup since those were genuinely bad orders
75
+ }
@@ -0,0 +1,43 @@
1
+ import { Knex } from 'knex';
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ // PostgreSQL requires a multi-step process to update enum constraints
5
+ // 1. Add a temporary column with the new enum values
6
+ // 2. Copy data from old column to new column
7
+ // 3. Drop old column
8
+ // 4. Rename new column to old column name
9
+
10
+ await knex.schema.alterTable('domains', (table) => {
11
+ table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE', 'DELETED']).notNullable().defaultTo('ACTIVE');
12
+ });
13
+
14
+ await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
15
+
16
+ await knex.schema.alterTable('domains', (table) => {
17
+ table.dropColumn('state');
18
+ });
19
+
20
+ await knex.schema.alterTable('domains', (table) => {
21
+ table.renameColumn('state_new', 'state');
22
+ });
23
+ }
24
+
25
+ export async function down(knex: Knex): Promise<void> {
26
+ // Revert to the original enum values (removing DELETED)
27
+ // First, ensure no domains are in DELETED state (or this will fail)
28
+ await knex.raw(`DELETE FROM domains WHERE state = 'DELETED'`);
29
+
30
+ await knex.schema.alterTable('domains', (table) => {
31
+ table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE']).notNullable().defaultTo('ACTIVE');
32
+ });
33
+
34
+ await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
35
+
36
+ await knex.schema.alterTable('domains', (table) => {
37
+ table.dropColumn('state');
38
+ });
39
+
40
+ await knex.schema.alterTable('domains', (table) => {
41
+ table.renameColumn('state_new', 'state');
42
+ });
43
+ }