@takaro/db 0.4.10 → 0.4.12
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/dist/migrations/sql/20251115071948-remove-deleted-domain-state.d.ts +4 -0
- package/dist/migrations/sql/20251115071948-remove-deleted-domain-state.d.ts.map +1 -0
- package/dist/migrations/sql/20251115071948-remove-deleted-domain-state.js +35 -0
- package/dist/migrations/sql/20251115071948-remove-deleted-domain-state.js.map +1 -0
- package/dist/migrations/sql/20251220000000-inventory-diff-storage.d.ts +4 -0
- package/dist/migrations/sql/20251220000000-inventory-diff-storage.d.ts.map +1 -0
- package/dist/migrations/sql/20251220000000-inventory-diff-storage.js +147 -0
- package/dist/migrations/sql/20251220000000-inventory-diff-storage.js.map +1 -0
- package/package.json +19 -8
- package/src/migrations/sql/20251115071948-remove-deleted-domain-state.ts +46 -0
- package/src/migrations/sql/20251220000000-inventory-diff-storage.ts +164 -0
- package/tsconfig.build.json +6 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20251115071948-remove-deleted-domain-state.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20251115071948-remove-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,CAuBlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBpD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
// First, migrate any existing DELETED domains to DISABLED state
|
|
3
|
+
await knex.raw(`UPDATE domains SET state = 'DISABLED' WHERE state = 'DELETED'`);
|
|
4
|
+
// PostgreSQL requires a multi-step process to update enum constraints
|
|
5
|
+
// 1. Add a temporary column with the new enum values (without DELETED)
|
|
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
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
10
|
+
table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE']).notNullable().defaultTo('ACTIVE');
|
|
11
|
+
});
|
|
12
|
+
await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
|
|
13
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
14
|
+
table.dropColumn('state');
|
|
15
|
+
});
|
|
16
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
17
|
+
table.renameColumn('state_new', 'state');
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export async function down(knex) {
|
|
21
|
+
// Drop the enum type if it exists to prevent "type already exists" errors
|
|
22
|
+
await knex.raw('DROP TYPE IF EXISTS state_new CASCADE');
|
|
23
|
+
// Revert to include DELETED in enum values
|
|
24
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
25
|
+
table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE', 'DELETED']).notNullable().defaultTo('ACTIVE');
|
|
26
|
+
});
|
|
27
|
+
await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
|
|
28
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
29
|
+
table.dropColumn('state');
|
|
30
|
+
});
|
|
31
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
32
|
+
table.renameColumn('state_new', 'state');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=20251115071948-remove-deleted-domain-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20251115071948-remove-deleted-domain-state.js","sourceRoot":"","sources":["../../../src/migrations/sql/20251115071948-remove-deleted-domain-state.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,gEAAgE;IAChE,MAAM,IAAI,CAAC,GAAG,CAAC,+DAA+D,CAAC,CAAC;IAEhF,sEAAsE;IACtE,uEAAuE;IACvE,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,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;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,0EAA0E;IAC1E,MAAM,IAAI,CAAC,GAAG,CAAC,uCAAuC,CAAC,CAAC;IAExD,2CAA2C;IAC3C,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20251220000000-inventory-diff-storage.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20251220000000-inventory-diff-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA0JlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpD"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
// Create playerInventoryBaseline table - full inventory snapshots every hour
|
|
3
|
+
await knex.raw(`
|
|
4
|
+
CREATE TABLE "playerInventoryBaseline" (
|
|
5
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
6
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
7
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
8
|
+
domain VARCHAR(255) NOT NULL,
|
|
9
|
+
"playerId" UUID NOT NULL,
|
|
10
|
+
"baselineId" UUID NOT NULL,
|
|
11
|
+
"itemId" UUID NOT NULL,
|
|
12
|
+
quantity INTEGER NOT NULL,
|
|
13
|
+
quality VARCHAR(255),
|
|
14
|
+
CONSTRAINT "playerInventoryBaseline_pkey" PRIMARY KEY ("createdAt", "baselineId", "itemId"),
|
|
15
|
+
CONSTRAINT "playerInventoryBaseline_domain_foreign" FOREIGN KEY (domain) REFERENCES domains(id) ON DELETE CASCADE,
|
|
16
|
+
CONSTRAINT "playerInventoryBaseline_playerId_foreign" FOREIGN KEY ("playerId") REFERENCES "playerOnGameServer"(id) ON DELETE CASCADE,
|
|
17
|
+
CONSTRAINT "playerInventoryBaseline_itemId_foreign" FOREIGN KEY ("itemId") REFERENCES items(id) ON DELETE CASCADE,
|
|
18
|
+
CONSTRAINT "playerInventoryBaseline_quantity_check" CHECK (quantity >= 0)
|
|
19
|
+
) PARTITION BY RANGE ("createdAt")
|
|
20
|
+
`);
|
|
21
|
+
// Create playerInventoryDiff table - individual item changes between baselines
|
|
22
|
+
await knex.raw(`
|
|
23
|
+
CREATE TABLE "playerInventoryDiff" (
|
|
24
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
25
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
26
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
27
|
+
domain VARCHAR(255) NOT NULL,
|
|
28
|
+
"playerId" UUID NOT NULL,
|
|
29
|
+
"itemId" UUID NOT NULL,
|
|
30
|
+
"changeType" VARCHAR(10) NOT NULL,
|
|
31
|
+
"previousQuantity" INTEGER,
|
|
32
|
+
"newQuantity" INTEGER,
|
|
33
|
+
"previousQuality" VARCHAR(255),
|
|
34
|
+
"newQuality" VARCHAR(255),
|
|
35
|
+
CONSTRAINT "playerInventoryDiff_pkey" PRIMARY KEY ("createdAt", "playerId", "itemId"),
|
|
36
|
+
CONSTRAINT "playerInventoryDiff_domain_foreign" FOREIGN KEY (domain) REFERENCES domains(id) ON DELETE CASCADE,
|
|
37
|
+
CONSTRAINT "playerInventoryDiff_playerId_foreign" FOREIGN KEY ("playerId") REFERENCES "playerOnGameServer"(id) ON DELETE CASCADE,
|
|
38
|
+
CONSTRAINT "playerInventoryDiff_itemId_foreign" FOREIGN KEY ("itemId") REFERENCES items(id) ON DELETE CASCADE,
|
|
39
|
+
CONSTRAINT "playerInventoryDiff_changeType_check" CHECK ("changeType" IN ('added', 'removed', 'changed'))
|
|
40
|
+
) PARTITION BY RANGE ("createdAt")
|
|
41
|
+
`);
|
|
42
|
+
// Create partition function for playerInventoryBaseline
|
|
43
|
+
await knex.raw(`
|
|
44
|
+
CREATE OR REPLACE FUNCTION ensure_player_inventory_baseline_partition(date_param VARCHAR DEFAULT NULL)
|
|
45
|
+
RETURNS VOID AS $$
|
|
46
|
+
DECLARE
|
|
47
|
+
current_day_start DATE;
|
|
48
|
+
next_day_start DATE;
|
|
49
|
+
partition_name TEXT;
|
|
50
|
+
partition_exists BOOLEAN;
|
|
51
|
+
target_date DATE;
|
|
52
|
+
BEGIN
|
|
53
|
+
IF date_param IS NOT NULL THEN
|
|
54
|
+
target_date := DATE(date_param::TIMESTAMP);
|
|
55
|
+
ELSE
|
|
56
|
+
target_date := CURRENT_DATE;
|
|
57
|
+
END IF;
|
|
58
|
+
|
|
59
|
+
current_day_start := DATE_TRUNC('day', target_date);
|
|
60
|
+
next_day_start := current_day_start + INTERVAL '1 day';
|
|
61
|
+
|
|
62
|
+
partition_name := 'playerInventoryBaseline_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
|
|
63
|
+
|
|
64
|
+
SELECT EXISTS (
|
|
65
|
+
SELECT 1 FROM pg_class c
|
|
66
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
67
|
+
WHERE c.relname = partition_name
|
|
68
|
+
AND n.nspname = 'public'
|
|
69
|
+
) INTO partition_exists;
|
|
70
|
+
|
|
71
|
+
IF NOT partition_exists THEN
|
|
72
|
+
EXECUTE format(
|
|
73
|
+
'CREATE TABLE %I PARTITION OF "playerInventoryBaseline"
|
|
74
|
+
FOR VALUES FROM (%L) TO (%L)',
|
|
75
|
+
partition_name,
|
|
76
|
+
current_day_start,
|
|
77
|
+
next_day_start
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
RAISE NOTICE 'Created partition: % for date: %',
|
|
81
|
+
partition_name, current_day_start;
|
|
82
|
+
END IF;
|
|
83
|
+
END;
|
|
84
|
+
$$ LANGUAGE plpgsql;
|
|
85
|
+
`);
|
|
86
|
+
// Create partition function for playerInventoryDiff
|
|
87
|
+
await knex.raw(`
|
|
88
|
+
CREATE OR REPLACE FUNCTION ensure_player_inventory_diff_partition(date_param VARCHAR DEFAULT NULL)
|
|
89
|
+
RETURNS VOID AS $$
|
|
90
|
+
DECLARE
|
|
91
|
+
current_day_start DATE;
|
|
92
|
+
next_day_start DATE;
|
|
93
|
+
partition_name TEXT;
|
|
94
|
+
partition_exists BOOLEAN;
|
|
95
|
+
target_date DATE;
|
|
96
|
+
BEGIN
|
|
97
|
+
IF date_param IS NOT NULL THEN
|
|
98
|
+
target_date := DATE(date_param::TIMESTAMP);
|
|
99
|
+
ELSE
|
|
100
|
+
target_date := CURRENT_DATE;
|
|
101
|
+
END IF;
|
|
102
|
+
|
|
103
|
+
current_day_start := DATE_TRUNC('day', target_date);
|
|
104
|
+
next_day_start := current_day_start + INTERVAL '1 day';
|
|
105
|
+
|
|
106
|
+
partition_name := 'playerInventoryDiff_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
|
|
107
|
+
|
|
108
|
+
SELECT EXISTS (
|
|
109
|
+
SELECT 1 FROM pg_class c
|
|
110
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
111
|
+
WHERE c.relname = partition_name
|
|
112
|
+
AND n.nspname = 'public'
|
|
113
|
+
) INTO partition_exists;
|
|
114
|
+
|
|
115
|
+
IF NOT partition_exists THEN
|
|
116
|
+
EXECUTE format(
|
|
117
|
+
'CREATE TABLE %I PARTITION OF "playerInventoryDiff"
|
|
118
|
+
FOR VALUES FROM (%L) TO (%L)',
|
|
119
|
+
partition_name,
|
|
120
|
+
current_day_start,
|
|
121
|
+
next_day_start
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
RAISE NOTICE 'Created partition: % for date: %',
|
|
125
|
+
partition_name, current_day_start;
|
|
126
|
+
END IF;
|
|
127
|
+
END;
|
|
128
|
+
$$ LANGUAGE plpgsql;
|
|
129
|
+
`);
|
|
130
|
+
// Create indexes for playerInventoryBaseline
|
|
131
|
+
await knex.raw(`CREATE INDEX "playerInventoryBaseline_domain_createdAt_idx" ON "playerInventoryBaseline" (domain, "createdAt")`);
|
|
132
|
+
await knex.raw(`CREATE INDEX "playerInventoryBaseline_playerId_idx" ON "playerInventoryBaseline" ("playerId")`);
|
|
133
|
+
await knex.raw(`CREATE INDEX "playerInventoryBaseline_playerId_createdAt_idx" ON "playerInventoryBaseline" ("playerId", "createdAt" DESC)`);
|
|
134
|
+
await knex.raw(`CREATE INDEX "playerInventoryBaseline_baselineId_idx" ON "playerInventoryBaseline" ("baselineId")`);
|
|
135
|
+
// Create indexes for playerInventoryDiff
|
|
136
|
+
await knex.raw(`CREATE INDEX "playerInventoryDiff_domain_createdAt_idx" ON "playerInventoryDiff" (domain, "createdAt")`);
|
|
137
|
+
await knex.raw(`CREATE INDEX "playerInventoryDiff_playerId_idx" ON "playerInventoryDiff" ("playerId")`);
|
|
138
|
+
await knex.raw(`CREATE INDEX "playerInventoryDiff_playerId_createdAt_idx" ON "playerInventoryDiff" ("playerId", "createdAt" DESC)`);
|
|
139
|
+
await knex.raw(`CREATE INDEX "playerInventoryDiff_itemId_createdAt_idx" ON "playerInventoryDiff" ("itemId", "createdAt" DESC)`);
|
|
140
|
+
}
|
|
141
|
+
export async function down(knex) {
|
|
142
|
+
await knex.raw('DROP FUNCTION IF EXISTS ensure_player_inventory_baseline_partition(VARCHAR)');
|
|
143
|
+
await knex.raw('DROP FUNCTION IF EXISTS ensure_player_inventory_diff_partition(VARCHAR)');
|
|
144
|
+
await knex.raw('DROP TABLE IF EXISTS "playerInventoryBaseline" CASCADE');
|
|
145
|
+
await knex.raw('DROP TABLE IF EXISTS "playerInventoryDiff" CASCADE');
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=20251220000000-inventory-diff-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20251220000000-inventory-diff-storage.js","sourceRoot":"","sources":["../../../src/migrations/sql/20251220000000-inventory-diff-storage.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,6EAA6E;IAC7E,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;GAiBd,CAAC,CAAC;IAEH,+EAA+E;IAC/E,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;GAmBd,CAAC,CAAC;IAEH,wDAAwD;IACxD,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0Cd,CAAC,CAAC;IAEH,oDAAoD;IACpD,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0Cd,CAAC,CAAC;IAEH,6CAA6C;IAC7C,MAAM,IAAI,CAAC,GAAG,CACZ,gHAAgH,CACjH,CAAC;IACF,MAAM,IAAI,CAAC,GAAG,CAAC,+FAA+F,CAAC,CAAC;IAChH,MAAM,IAAI,CAAC,GAAG,CACZ,2HAA2H,CAC5H,CAAC;IACF,MAAM,IAAI,CAAC,GAAG,CAAC,mGAAmG,CAAC,CAAC;IAEpH,yCAAyC;IACzC,MAAM,IAAI,CAAC,GAAG,CACZ,wGAAwG,CACzG,CAAC;IACF,MAAM,IAAI,CAAC,GAAG,CAAC,uFAAuF,CAAC,CAAC;IACxG,MAAM,IAAI,CAAC,GAAG,CACZ,mHAAmH,CACpH,CAAC;IACF,MAAM,IAAI,CAAC,GAAG,CACZ,+GAA+G,CAChH,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,GAAG,CAAC,6EAA6E,CAAC,CAAC;IAC9F,MAAM,IAAI,CAAC,GAAG,CAAC,yEAAyE,CAAC,CAAC;IAC1F,MAAM,IAAI,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IACzE,MAAM,IAAI,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;AACvE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takaro/db",
|
|
3
|
-
"version": "0.4.10",
|
|
4
3
|
"description": "An opinionated data layer",
|
|
4
|
+
"version": "0.4.12",
|
|
5
|
+
"author": "",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"class-validator": "0.14.3",
|
|
8
|
+
"knex": "3.1.0",
|
|
9
|
+
"ms": "2.1.3",
|
|
10
|
+
"objection": "3.1.5",
|
|
11
|
+
"redis": "4.7.0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"license": "ISC",
|
|
5
15
|
"main": "dist/main.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@takaro/config": "*",
|
|
18
|
+
"@takaro/util": "*"
|
|
19
|
+
},
|
|
8
20
|
"scripts": {
|
|
9
|
-
"start:dev": "tsc --watch --preserveWatchOutput -p ./tsconfig.build.json",
|
|
10
21
|
"build": "tsc -p ./tsconfig.build.json",
|
|
11
|
-
"migrate:create": "run(){ touch ./src/migrations/sql/$(date -u +\"%Y%m%d%H%M%S\")-$1.ts; }; run"
|
|
22
|
+
"migrate:create": "run(){ touch ./src/migrations/sql/$(date -u +\"%Y%m%d%H%M%S\")-$1.ts; }; run",
|
|
23
|
+
"start:dev": ":"
|
|
12
24
|
},
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"license": "ISC"
|
|
25
|
+
"type": "module",
|
|
26
|
+
"types": "dist/main.d.ts"
|
|
16
27
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Knex } from 'knex';
|
|
2
|
+
|
|
3
|
+
export async function up(knex: Knex): Promise<void> {
|
|
4
|
+
// First, migrate any existing DELETED domains to DISABLED state
|
|
5
|
+
await knex.raw(`UPDATE domains SET state = 'DISABLED' WHERE state = 'DELETED'`);
|
|
6
|
+
|
|
7
|
+
// PostgreSQL requires a multi-step process to update enum constraints
|
|
8
|
+
// 1. Add a temporary column with the new enum values (without DELETED)
|
|
9
|
+
// 2. Copy data from old column to new column
|
|
10
|
+
// 3. Drop old column
|
|
11
|
+
// 4. Rename new column to old column name
|
|
12
|
+
|
|
13
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
14
|
+
table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE']).notNullable().defaultTo('ACTIVE');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
|
|
18
|
+
|
|
19
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
20
|
+
table.dropColumn('state');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
24
|
+
table.renameColumn('state_new', 'state');
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function down(knex: Knex): Promise<void> {
|
|
29
|
+
// Drop the enum type if it exists to prevent "type already exists" errors
|
|
30
|
+
await knex.raw('DROP TYPE IF EXISTS state_new CASCADE');
|
|
31
|
+
|
|
32
|
+
// Revert to include DELETED in enum values
|
|
33
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
34
|
+
table.enum('state_new', ['ACTIVE', 'DISABLED', 'MAINTENANCE', 'DELETED']).notNullable().defaultTo('ACTIVE');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await knex.raw(`UPDATE domains SET state_new = state::text::varchar`);
|
|
38
|
+
|
|
39
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
40
|
+
table.dropColumn('state');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await knex.schema.alterTable('domains', (table) => {
|
|
44
|
+
table.renameColumn('state_new', 'state');
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Knex } from 'knex';
|
|
2
|
+
|
|
3
|
+
export async function up(knex: Knex): Promise<void> {
|
|
4
|
+
// Create playerInventoryBaseline table - full inventory snapshots every hour
|
|
5
|
+
await knex.raw(`
|
|
6
|
+
CREATE TABLE "playerInventoryBaseline" (
|
|
7
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
8
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
9
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
10
|
+
domain VARCHAR(255) NOT NULL,
|
|
11
|
+
"playerId" UUID NOT NULL,
|
|
12
|
+
"baselineId" UUID NOT NULL,
|
|
13
|
+
"itemId" UUID NOT NULL,
|
|
14
|
+
quantity INTEGER NOT NULL,
|
|
15
|
+
quality VARCHAR(255),
|
|
16
|
+
CONSTRAINT "playerInventoryBaseline_pkey" PRIMARY KEY ("createdAt", "baselineId", "itemId"),
|
|
17
|
+
CONSTRAINT "playerInventoryBaseline_domain_foreign" FOREIGN KEY (domain) REFERENCES domains(id) ON DELETE CASCADE,
|
|
18
|
+
CONSTRAINT "playerInventoryBaseline_playerId_foreign" FOREIGN KEY ("playerId") REFERENCES "playerOnGameServer"(id) ON DELETE CASCADE,
|
|
19
|
+
CONSTRAINT "playerInventoryBaseline_itemId_foreign" FOREIGN KEY ("itemId") REFERENCES items(id) ON DELETE CASCADE,
|
|
20
|
+
CONSTRAINT "playerInventoryBaseline_quantity_check" CHECK (quantity >= 0)
|
|
21
|
+
) PARTITION BY RANGE ("createdAt")
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
// Create playerInventoryDiff table - individual item changes between baselines
|
|
25
|
+
await knex.raw(`
|
|
26
|
+
CREATE TABLE "playerInventoryDiff" (
|
|
27
|
+
id UUID DEFAULT gen_random_uuid() NOT NULL,
|
|
28
|
+
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
29
|
+
"updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
30
|
+
domain VARCHAR(255) NOT NULL,
|
|
31
|
+
"playerId" UUID NOT NULL,
|
|
32
|
+
"itemId" UUID NOT NULL,
|
|
33
|
+
"changeType" VARCHAR(10) NOT NULL,
|
|
34
|
+
"previousQuantity" INTEGER,
|
|
35
|
+
"newQuantity" INTEGER,
|
|
36
|
+
"previousQuality" VARCHAR(255),
|
|
37
|
+
"newQuality" VARCHAR(255),
|
|
38
|
+
CONSTRAINT "playerInventoryDiff_pkey" PRIMARY KEY ("createdAt", "playerId", "itemId"),
|
|
39
|
+
CONSTRAINT "playerInventoryDiff_domain_foreign" FOREIGN KEY (domain) REFERENCES domains(id) ON DELETE CASCADE,
|
|
40
|
+
CONSTRAINT "playerInventoryDiff_playerId_foreign" FOREIGN KEY ("playerId") REFERENCES "playerOnGameServer"(id) ON DELETE CASCADE,
|
|
41
|
+
CONSTRAINT "playerInventoryDiff_itemId_foreign" FOREIGN KEY ("itemId") REFERENCES items(id) ON DELETE CASCADE,
|
|
42
|
+
CONSTRAINT "playerInventoryDiff_changeType_check" CHECK ("changeType" IN ('added', 'removed', 'changed'))
|
|
43
|
+
) PARTITION BY RANGE ("createdAt")
|
|
44
|
+
`);
|
|
45
|
+
|
|
46
|
+
// Create partition function for playerInventoryBaseline
|
|
47
|
+
await knex.raw(`
|
|
48
|
+
CREATE OR REPLACE FUNCTION ensure_player_inventory_baseline_partition(date_param VARCHAR DEFAULT NULL)
|
|
49
|
+
RETURNS VOID AS $$
|
|
50
|
+
DECLARE
|
|
51
|
+
current_day_start DATE;
|
|
52
|
+
next_day_start DATE;
|
|
53
|
+
partition_name TEXT;
|
|
54
|
+
partition_exists BOOLEAN;
|
|
55
|
+
target_date DATE;
|
|
56
|
+
BEGIN
|
|
57
|
+
IF date_param IS NOT NULL THEN
|
|
58
|
+
target_date := DATE(date_param::TIMESTAMP);
|
|
59
|
+
ELSE
|
|
60
|
+
target_date := CURRENT_DATE;
|
|
61
|
+
END IF;
|
|
62
|
+
|
|
63
|
+
current_day_start := DATE_TRUNC('day', target_date);
|
|
64
|
+
next_day_start := current_day_start + INTERVAL '1 day';
|
|
65
|
+
|
|
66
|
+
partition_name := 'playerInventoryBaseline_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
|
|
67
|
+
|
|
68
|
+
SELECT EXISTS (
|
|
69
|
+
SELECT 1 FROM pg_class c
|
|
70
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
71
|
+
WHERE c.relname = partition_name
|
|
72
|
+
AND n.nspname = 'public'
|
|
73
|
+
) INTO partition_exists;
|
|
74
|
+
|
|
75
|
+
IF NOT partition_exists THEN
|
|
76
|
+
EXECUTE format(
|
|
77
|
+
'CREATE TABLE %I PARTITION OF "playerInventoryBaseline"
|
|
78
|
+
FOR VALUES FROM (%L) TO (%L)',
|
|
79
|
+
partition_name,
|
|
80
|
+
current_day_start,
|
|
81
|
+
next_day_start
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
RAISE NOTICE 'Created partition: % for date: %',
|
|
85
|
+
partition_name, current_day_start;
|
|
86
|
+
END IF;
|
|
87
|
+
END;
|
|
88
|
+
$$ LANGUAGE plpgsql;
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
// Create partition function for playerInventoryDiff
|
|
92
|
+
await knex.raw(`
|
|
93
|
+
CREATE OR REPLACE FUNCTION ensure_player_inventory_diff_partition(date_param VARCHAR DEFAULT NULL)
|
|
94
|
+
RETURNS VOID AS $$
|
|
95
|
+
DECLARE
|
|
96
|
+
current_day_start DATE;
|
|
97
|
+
next_day_start DATE;
|
|
98
|
+
partition_name TEXT;
|
|
99
|
+
partition_exists BOOLEAN;
|
|
100
|
+
target_date DATE;
|
|
101
|
+
BEGIN
|
|
102
|
+
IF date_param IS NOT NULL THEN
|
|
103
|
+
target_date := DATE(date_param::TIMESTAMP);
|
|
104
|
+
ELSE
|
|
105
|
+
target_date := CURRENT_DATE;
|
|
106
|
+
END IF;
|
|
107
|
+
|
|
108
|
+
current_day_start := DATE_TRUNC('day', target_date);
|
|
109
|
+
next_day_start := current_day_start + INTERVAL '1 day';
|
|
110
|
+
|
|
111
|
+
partition_name := 'playerInventoryDiff_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
|
|
112
|
+
|
|
113
|
+
SELECT EXISTS (
|
|
114
|
+
SELECT 1 FROM pg_class c
|
|
115
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
116
|
+
WHERE c.relname = partition_name
|
|
117
|
+
AND n.nspname = 'public'
|
|
118
|
+
) INTO partition_exists;
|
|
119
|
+
|
|
120
|
+
IF NOT partition_exists THEN
|
|
121
|
+
EXECUTE format(
|
|
122
|
+
'CREATE TABLE %I PARTITION OF "playerInventoryDiff"
|
|
123
|
+
FOR VALUES FROM (%L) TO (%L)',
|
|
124
|
+
partition_name,
|
|
125
|
+
current_day_start,
|
|
126
|
+
next_day_start
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
RAISE NOTICE 'Created partition: % for date: %',
|
|
130
|
+
partition_name, current_day_start;
|
|
131
|
+
END IF;
|
|
132
|
+
END;
|
|
133
|
+
$$ LANGUAGE plpgsql;
|
|
134
|
+
`);
|
|
135
|
+
|
|
136
|
+
// Create indexes for playerInventoryBaseline
|
|
137
|
+
await knex.raw(
|
|
138
|
+
`CREATE INDEX "playerInventoryBaseline_domain_createdAt_idx" ON "playerInventoryBaseline" (domain, "createdAt")`,
|
|
139
|
+
);
|
|
140
|
+
await knex.raw(`CREATE INDEX "playerInventoryBaseline_playerId_idx" ON "playerInventoryBaseline" ("playerId")`);
|
|
141
|
+
await knex.raw(
|
|
142
|
+
`CREATE INDEX "playerInventoryBaseline_playerId_createdAt_idx" ON "playerInventoryBaseline" ("playerId", "createdAt" DESC)`,
|
|
143
|
+
);
|
|
144
|
+
await knex.raw(`CREATE INDEX "playerInventoryBaseline_baselineId_idx" ON "playerInventoryBaseline" ("baselineId")`);
|
|
145
|
+
|
|
146
|
+
// Create indexes for playerInventoryDiff
|
|
147
|
+
await knex.raw(
|
|
148
|
+
`CREATE INDEX "playerInventoryDiff_domain_createdAt_idx" ON "playerInventoryDiff" (domain, "createdAt")`,
|
|
149
|
+
);
|
|
150
|
+
await knex.raw(`CREATE INDEX "playerInventoryDiff_playerId_idx" ON "playerInventoryDiff" ("playerId")`);
|
|
151
|
+
await knex.raw(
|
|
152
|
+
`CREATE INDEX "playerInventoryDiff_playerId_createdAt_idx" ON "playerInventoryDiff" ("playerId", "createdAt" DESC)`,
|
|
153
|
+
);
|
|
154
|
+
await knex.raw(
|
|
155
|
+
`CREATE INDEX "playerInventoryDiff_itemId_createdAt_idx" ON "playerInventoryDiff" ("itemId", "createdAt" DESC)`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function down(knex: Knex): Promise<void> {
|
|
160
|
+
await knex.raw('DROP FUNCTION IF EXISTS ensure_player_inventory_baseline_partition(VARCHAR)');
|
|
161
|
+
await knex.raw('DROP FUNCTION IF EXISTS ensure_player_inventory_diff_partition(VARCHAR)');
|
|
162
|
+
await knex.raw('DROP TABLE IF EXISTS "playerInventoryBaseline" CASCADE');
|
|
163
|
+
await knex.raw('DROP TABLE IF EXISTS "playerInventoryDiff" CASCADE');
|
|
164
|
+
}
|
package/tsconfig.build.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "./tsconfig.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
+
"composite": true,
|
|
4
5
|
"rootDir": "./src",
|
|
5
6
|
"outDir": "dist"
|
|
6
7
|
},
|
|
7
8
|
"include": ["src/**/*"],
|
|
8
|
-
"exclude": ["src/**/*.test.ts"]
|
|
9
|
+
"exclude": ["src/**/*.test.ts"],
|
|
10
|
+
"references": [
|
|
11
|
+
{ "path": "../lib-config/tsconfig.build.json" },
|
|
12
|
+
{ "path": "../lib-util/tsconfig.build.json" }
|
|
13
|
+
]
|
|
9
14
|
}
|