@logto/schemas 1.33.0 → 1.35.0
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/alterations/1.34.0-1762338508-add-organization-roles-type-index.ts +19 -0
- package/alterations/1.34.0-1764164658-add-applications-roles-include-indexes.ts +38 -0
- package/alterations/1.34.0-1764236036-remove-applications-roles-include-indexes.ts +38 -0
- package/alterations/1.35.0-1764580455-remove-daily-active-users-foreign-key.ts +69 -0
- package/alterations/1.35.0-1764580589-create-aggregated-daily-active-users-table.ts +51 -0
- package/alterations/1.35.0-1764653048-update-daily-token-usage-mau-support.ts +37 -0
- package/alterations/1.35.0-1765183934-add-logs-created-at-id-index.ts +39 -0
- package/alterations/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.ts +31 -0
- package/alterations/1.35.0-1765631949-drop-redundant-logs-id-index.ts +47 -0
- package/alterations/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.ts +39 -0
- package/alterations-js/1.34.0-1762338508-add-organization-roles-type-index.js +15 -0
- package/alterations-js/1.34.0-1764164658-add-applications-roles-include-indexes.js +30 -0
- package/alterations-js/1.34.0-1764236036-remove-applications-roles-include-indexes.js +30 -0
- package/alterations-js/1.35.0-1764580455-remove-daily-active-users-foreign-key.js +57 -0
- package/alterations-js/1.35.0-1764580589-create-aggregated-daily-active-users-table.js +40 -0
- package/alterations-js/1.35.0-1764653048-update-daily-token-usage-mau-support.js +31 -0
- package/alterations-js/1.35.0-1765183934-add-logs-created-at-id-index.js +35 -0
- package/alterations-js/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.js +25 -0
- package/alterations-js/1.35.0-1765631949-drop-redundant-logs-id-index.js +43 -0
- package/alterations-js/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.js +31 -0
- package/lib/consts/product-event.d.ts +0 -12
- package/lib/consts/product-event.js +0 -13
- package/lib/db-entries/aggregated-daily-active-user.d.ts +22 -0
- package/lib/db-entries/aggregated-daily-active-user.js +33 -0
- package/lib/db-entries/daily-token-usage.d.ts +5 -1
- package/lib/db-entries/daily-token-usage.js +8 -0
- package/lib/db-entries/index.d.ts +1 -0
- package/lib/db-entries/index.js +1 -0
- package/lib/db-entries/saml-application-session.js +2 -2
- package/lib/foundations/jsonb-types/captcha.d.ts +16 -0
- package/lib/foundations/jsonb-types/captcha.js +7 -0
- package/lib/foundations/jsonb-types/hooks.d.ts +6 -4
- package/lib/foundations/jsonb-types/hooks.js +3 -1
- package/lib/foundations/jsonb-types/oidc-module.js +1 -1
- package/lib/seeds/application.d.ts +6 -0
- package/lib/seeds/application.js +23 -4
- package/lib/types/alteration.d.ts +11 -1
- package/lib/types/domain.d.ts +4 -2
- package/lib/types/domain.js +2 -0
- package/lib/types/hook.d.ts +7 -4
- package/lib/types/interactions.d.ts +14 -5
- package/lib/types/interactions.js +10 -4
- package/lib/types/sign-in-experience.d.ts +13 -1
- package/lib/types/sign-in-experience.js +3 -1
- package/lib/types/user.d.ts +3 -0
- package/lib/types/user.js +1 -0
- package/package.json +4 -4
- package/tables/_after_all.sql +1 -1
- package/tables/aggregated_daily_active_users.sql +16 -0
- package/tables/daily_active_users.sql +9 -4
- package/tables/daily_token_usage.sql +3 -2
- package/tables/logs.sql +3 -3
- package/tables/organization_roles.sql +3 -0
- package/tables/saml_application_sessions.sql +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
create index organization_roles__type
|
|
9
|
+
on organization_roles (tenant_id, type);
|
|
10
|
+
`);
|
|
11
|
+
},
|
|
12
|
+
down: async (pool) => {
|
|
13
|
+
await pool.query(sql`
|
|
14
|
+
drop index organization_roles__type;
|
|
15
|
+
`);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default alteration;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
create index applications__include_type_is_third_party
|
|
9
|
+
on applications (tenant_id) include (type, is_third_party);
|
|
10
|
+
`);
|
|
11
|
+
|
|
12
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
13
|
+
await pool.query(sql`
|
|
14
|
+
analyze applications;
|
|
15
|
+
`);
|
|
16
|
+
|
|
17
|
+
await pool.query(sql`
|
|
18
|
+
create index roles__include_type_name
|
|
19
|
+
on roles (tenant_id) include (type, name);
|
|
20
|
+
`);
|
|
21
|
+
|
|
22
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
23
|
+
await pool.query(sql`
|
|
24
|
+
analyze roles;
|
|
25
|
+
`);
|
|
26
|
+
},
|
|
27
|
+
down: async (pool) => {
|
|
28
|
+
await pool.query(sql`
|
|
29
|
+
drop index if exists applications__include_type_is_third_party;
|
|
30
|
+
`);
|
|
31
|
+
|
|
32
|
+
await pool.query(sql`
|
|
33
|
+
drop index if exists roles__include_type_name;
|
|
34
|
+
`);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default alteration;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
await pool.query(sql`
|
|
8
|
+
drop index if exists applications__include_type_is_third_party;
|
|
9
|
+
`);
|
|
10
|
+
|
|
11
|
+
await pool.query(sql`
|
|
12
|
+
drop index if exists roles__include_type_name;
|
|
13
|
+
`);
|
|
14
|
+
},
|
|
15
|
+
down: async (pool) => {
|
|
16
|
+
await pool.query(sql`
|
|
17
|
+
create index applications__include_type_is_third_party
|
|
18
|
+
on applications (tenant_id) include (type, is_third_party);
|
|
19
|
+
`);
|
|
20
|
+
|
|
21
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
22
|
+
await pool.query(sql`
|
|
23
|
+
analyze applications;
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
await pool.query(sql`
|
|
27
|
+
create index roles__include_type_name
|
|
28
|
+
on roles (tenant_id) include (type, name);
|
|
29
|
+
`);
|
|
30
|
+
|
|
31
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
32
|
+
await pool.query(sql`
|
|
33
|
+
analyze roles;
|
|
34
|
+
`);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default alteration;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Remove foreign key constraint from daily_active_users table to allow
|
|
7
|
+
* historical billing data to persist even after tenant deletion.
|
|
8
|
+
* This supports the MAU-based billing system requirements.
|
|
9
|
+
*
|
|
10
|
+
* Index optimizations:
|
|
11
|
+
* 1. Removes redundant (tenant_id, id) index (id is already primary key)
|
|
12
|
+
* 2. Adds optimized index (tenant_id, date, user_id) for aggregation queries
|
|
13
|
+
* 3. Replaces problematic partial index with BRIN index
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const alteration: AlterationScript = {
|
|
17
|
+
up: async (pool) => {
|
|
18
|
+
// Drop the existing foreign key constraint
|
|
19
|
+
await pool.query(sql`
|
|
20
|
+
alter table daily_active_users
|
|
21
|
+
drop constraint if exists daily_active_users_tenant_id_fkey
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
// Remove the redundant (tenant_id, id) index since id is already primary key
|
|
25
|
+
await pool.query(sql`
|
|
26
|
+
drop index if exists daily_active_users__id
|
|
27
|
+
`);
|
|
28
|
+
|
|
29
|
+
// Add optimized index for aggregation queries with better write performance
|
|
30
|
+
await pool.query(sql`
|
|
31
|
+
create index daily_active_users__tenant_date_user
|
|
32
|
+
on daily_active_users (tenant_id, date, user_id)
|
|
33
|
+
`);
|
|
34
|
+
|
|
35
|
+
// Add BRIN index for time-series date range queries
|
|
36
|
+
// Optimized for sequential data insertion and range scans (date >= ?)
|
|
37
|
+
await pool.query(sql`
|
|
38
|
+
create index daily_active_users__date_brin
|
|
39
|
+
on daily_active_users using brin (date)
|
|
40
|
+
`);
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
down: async (pool) => {
|
|
44
|
+
// Drop the new indexes we created
|
|
45
|
+
await pool.query(sql`
|
|
46
|
+
drop index if exists daily_active_users__date_brin
|
|
47
|
+
`);
|
|
48
|
+
|
|
49
|
+
await pool.query(sql`
|
|
50
|
+
drop index if exists daily_active_users__tenant_date_user
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
// Recreate the original redundant index
|
|
54
|
+
await pool.query(sql`
|
|
55
|
+
create index daily_active_users__id
|
|
56
|
+
on daily_active_users (tenant_id, id)
|
|
57
|
+
`);
|
|
58
|
+
|
|
59
|
+
// Recreate the foreign key constraint
|
|
60
|
+
await pool.query(sql`
|
|
61
|
+
alter table daily_active_users
|
|
62
|
+
add constraint daily_active_users_tenant_id_fkey
|
|
63
|
+
foreign key (tenant_id) references tenants(id)
|
|
64
|
+
on update cascade on delete cascade
|
|
65
|
+
`);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default alteration;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create aggregated_daily_active_users table for MAU-based billing system.
|
|
9
|
+
* This table consolidates daily user activities for efficient billing calculations.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const alteration: AlterationScript = {
|
|
13
|
+
up: async (pool) => {
|
|
14
|
+
// Create the aggregated daily active users table
|
|
15
|
+
await pool.query(sql`
|
|
16
|
+
create table aggregated_daily_active_users (
|
|
17
|
+
tenant_id varchar(21) not null,
|
|
18
|
+
activity_date date not null,
|
|
19
|
+
user_id varchar(21) not null,
|
|
20
|
+
activity_count integer not null,
|
|
21
|
+
primary key (tenant_id, activity_date, user_id)
|
|
22
|
+
);
|
|
23
|
+
`);
|
|
24
|
+
|
|
25
|
+
// Index for billing cycle range queries
|
|
26
|
+
await pool.query(sql`
|
|
27
|
+
create index aggregated_daily_active_users__tenant_date
|
|
28
|
+
on aggregated_daily_active_users (tenant_id, activity_date);
|
|
29
|
+
`);
|
|
30
|
+
|
|
31
|
+
// Index for tenant-specific user activity queries
|
|
32
|
+
await pool.query(sql`
|
|
33
|
+
create index aggregated_daily_active_users__tenant_user_date
|
|
34
|
+
on aggregated_daily_active_users (tenant_id, user_id, activity_date desc);
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
await applyTableRls(pool, 'aggregated_daily_active_users');
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
down: async (pool) => {
|
|
41
|
+
// Drop RLS policies first
|
|
42
|
+
await dropTableRls(pool, 'aggregated_daily_active_users');
|
|
43
|
+
|
|
44
|
+
// Drop the table and all its indexes
|
|
45
|
+
await pool.query(sql`
|
|
46
|
+
drop table aggregated_daily_active_users;
|
|
47
|
+
`);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default alteration;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
up: async (pool) => {
|
|
7
|
+
// Add new columns for user and m2m token usage tracking
|
|
8
|
+
await pool.query(sql`
|
|
9
|
+
alter table daily_token_usage
|
|
10
|
+
add column user_token_usage bigint not null default 0,
|
|
11
|
+
add column m2m_token_usage bigint not null default 0;
|
|
12
|
+
`);
|
|
13
|
+
|
|
14
|
+
// Remove foreign key constraint to support enterprise tenant deletion requirements
|
|
15
|
+
await pool.query(sql`
|
|
16
|
+
alter table daily_token_usage
|
|
17
|
+
drop constraint if exists daily_token_usage_tenant_id_fkey;
|
|
18
|
+
`);
|
|
19
|
+
},
|
|
20
|
+
down: async (pool) => {
|
|
21
|
+
// Remove the new columns
|
|
22
|
+
await pool.query(sql`
|
|
23
|
+
alter table daily_token_usage
|
|
24
|
+
drop column if exists user_token_usage,
|
|
25
|
+
drop column if exists m2m_token_usage;
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
// Re-add the foreign key constraint
|
|
29
|
+
await pool.query(sql`
|
|
30
|
+
alter table daily_token_usage
|
|
31
|
+
add constraint daily_token_usage_tenant_id_fkey
|
|
32
|
+
foreign key (tenant_id) references tenants (id) on update cascade on delete cascade;
|
|
33
|
+
`);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default alteration;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const alteration: AlterationScript = {
|
|
6
|
+
beforeUp: async (pool) => {
|
|
7
|
+
/**
|
|
8
|
+
* Use 'if not exists' to ensure idempotency.
|
|
9
|
+
* If the subsequent transaction in 'up' fails, the migration can be safely re-run without failing on an already created index.
|
|
10
|
+
*/
|
|
11
|
+
await pool.query(sql`
|
|
12
|
+
create index concurrently if not exists logs__created_at_id
|
|
13
|
+
on logs (tenant_id, created_at, id);
|
|
14
|
+
`);
|
|
15
|
+
},
|
|
16
|
+
up: async () => {
|
|
17
|
+
/**
|
|
18
|
+
* The index on the logs table must be created outside of a transaction to avoid table locks.
|
|
19
|
+
* 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
|
|
20
|
+
*/
|
|
21
|
+
},
|
|
22
|
+
beforeDown: async (pool) => {
|
|
23
|
+
/**
|
|
24
|
+
* Use 'if exists' to ensure idempotency. If the subsequent transaction in 'down' fails,
|
|
25
|
+
* the rollback can be safely re-run without failing on a non-existent index.
|
|
26
|
+
*/
|
|
27
|
+
await pool.query(sql`
|
|
28
|
+
drop index concurrently if exists logs__created_at_id;
|
|
29
|
+
`);
|
|
30
|
+
},
|
|
31
|
+
down: async () => {
|
|
32
|
+
/**
|
|
33
|
+
* The index on the logs table must be dropped outside of a transaction to avoid table locks.
|
|
34
|
+
* 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
|
|
35
|
+
*/
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default alteration;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Update saml_application_sessions.relay_state column type from varchar(256) to text
|
|
7
|
+
* to support longer relay state values that may be used in SAML authentication flows.
|
|
8
|
+
*
|
|
9
|
+
* The relay state parameter in SAML can contain arbitrary data that needs to be
|
|
10
|
+
* preserved and returned to the service provider, and 256 characters may not be
|
|
11
|
+
* sufficient for all use cases.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const alteration: AlterationScript = {
|
|
15
|
+
up: async (pool) => {
|
|
16
|
+
await pool.query(sql`
|
|
17
|
+
alter table saml_application_sessions
|
|
18
|
+
alter column relay_state type varchar(512)
|
|
19
|
+
`);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
down: async (pool) => {
|
|
23
|
+
// The down migration may fail or cause data loss if any existing relay_state values exceed 256 characters. Consider adding a USING clause to safely truncate values during rollback, this ensures the rollback operation won't fail due to data that's too long for the varchar(256) constraint.
|
|
24
|
+
await pool.query(sql`
|
|
25
|
+
alter table saml_application_sessions
|
|
26
|
+
alter column relay_state type varchar(256) using left(relay_state, 256)
|
|
27
|
+
`);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default alteration;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `logs__id` index on (tenant_id, id) is redundant because:
|
|
7
|
+
* 1. `id` is already the primary key, which can efficiently lookup by id
|
|
8
|
+
* 2. `logs__created_at_id` index on (tenant_id, created_at, id) already covers tenant_id queries
|
|
9
|
+
*
|
|
10
|
+
* Removing this index reduces write IOPS during log deletion operations,
|
|
11
|
+
* as each DELETE no longer needs to update this redundant index.
|
|
12
|
+
*/
|
|
13
|
+
const alteration: AlterationScript = {
|
|
14
|
+
beforeUp: async (pool) => {
|
|
15
|
+
/**
|
|
16
|
+
* Use 'if exists' to ensure idempotency.
|
|
17
|
+
* 'concurrently' avoids table locks during index drop.
|
|
18
|
+
*/
|
|
19
|
+
await pool.query(sql`
|
|
20
|
+
drop index concurrently if exists logs__id;
|
|
21
|
+
`);
|
|
22
|
+
},
|
|
23
|
+
up: async () => {
|
|
24
|
+
/**
|
|
25
|
+
* The index must be dropped outside of a transaction to avoid table locks.
|
|
26
|
+
* 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
|
|
27
|
+
*/
|
|
28
|
+
},
|
|
29
|
+
beforeDown: async (pool) => {
|
|
30
|
+
/**
|
|
31
|
+
* Recreate the index if rolling back.
|
|
32
|
+
* Use 'if not exists' to ensure idempotency.
|
|
33
|
+
*/
|
|
34
|
+
await pool.query(sql`
|
|
35
|
+
create index concurrently if not exists logs__id
|
|
36
|
+
on logs (tenant_id, id);
|
|
37
|
+
`);
|
|
38
|
+
},
|
|
39
|
+
down: async () => {
|
|
40
|
+
/**
|
|
41
|
+
* The index must be created outside of a transaction to avoid table locks.
|
|
42
|
+
* 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
|
|
43
|
+
*/
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default alteration;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type CommonQueryMethods, sql } from '@silverhand/slonik';
|
|
2
|
+
|
|
3
|
+
import type { AlterationScript } from '../lib/types/alteration.js';
|
|
4
|
+
|
|
5
|
+
const getDatabaseName = async (pool: CommonQueryMethods) => {
|
|
6
|
+
const { currentDatabase } = await pool.one<{ currentDatabase: string }>(sql`
|
|
7
|
+
select current_database();
|
|
8
|
+
`);
|
|
9
|
+
|
|
10
|
+
return currentDatabase.replaceAll('-', '_');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Grant read permission to the tag column in the tenants table to the logto_tenant_<databaseName> role.
|
|
15
|
+
*/
|
|
16
|
+
const alteration: AlterationScript = {
|
|
17
|
+
up: async (pool) => {
|
|
18
|
+
const databaseName = await getDatabaseName(pool);
|
|
19
|
+
const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
|
|
20
|
+
|
|
21
|
+
await pool.query(sql`
|
|
22
|
+
grant select (tag)
|
|
23
|
+
on table tenants
|
|
24
|
+
to ${baseRoleId}
|
|
25
|
+
`);
|
|
26
|
+
},
|
|
27
|
+
down: async (pool) => {
|
|
28
|
+
const databaseName = await getDatabaseName(pool);
|
|
29
|
+
const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
|
|
30
|
+
|
|
31
|
+
await pool.query(sql`
|
|
32
|
+
revoke select (tag)
|
|
33
|
+
on table tenants
|
|
34
|
+
from ${baseRoleId}
|
|
35
|
+
`);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default alteration;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
create index organization_roles__type
|
|
6
|
+
on organization_roles (tenant_id, type);
|
|
7
|
+
`);
|
|
8
|
+
},
|
|
9
|
+
down: async (pool) => {
|
|
10
|
+
await pool.query(sql `
|
|
11
|
+
drop index organization_roles__type;
|
|
12
|
+
`);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
export default alteration;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
create index applications__include_type_is_third_party
|
|
6
|
+
on applications (tenant_id) include (type, is_third_party);
|
|
7
|
+
`);
|
|
8
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
9
|
+
await pool.query(sql `
|
|
10
|
+
analyze applications;
|
|
11
|
+
`);
|
|
12
|
+
await pool.query(sql `
|
|
13
|
+
create index roles__include_type_name
|
|
14
|
+
on roles (tenant_id) include (type, name);
|
|
15
|
+
`);
|
|
16
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
17
|
+
await pool.query(sql `
|
|
18
|
+
analyze roles;
|
|
19
|
+
`);
|
|
20
|
+
},
|
|
21
|
+
down: async (pool) => {
|
|
22
|
+
await pool.query(sql `
|
|
23
|
+
drop index if exists applications__include_type_is_third_party;
|
|
24
|
+
`);
|
|
25
|
+
await pool.query(sql `
|
|
26
|
+
drop index if exists roles__include_type_name;
|
|
27
|
+
`);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export default alteration;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
await pool.query(sql `
|
|
5
|
+
drop index if exists applications__include_type_is_third_party;
|
|
6
|
+
`);
|
|
7
|
+
await pool.query(sql `
|
|
8
|
+
drop index if exists roles__include_type_name;
|
|
9
|
+
`);
|
|
10
|
+
},
|
|
11
|
+
down: async (pool) => {
|
|
12
|
+
await pool.query(sql `
|
|
13
|
+
create index applications__include_type_is_third_party
|
|
14
|
+
on applications (tenant_id) include (type, is_third_party);
|
|
15
|
+
`);
|
|
16
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
17
|
+
await pool.query(sql `
|
|
18
|
+
analyze applications;
|
|
19
|
+
`);
|
|
20
|
+
await pool.query(sql `
|
|
21
|
+
create index roles__include_type_name
|
|
22
|
+
on roles (tenant_id) include (type, name);
|
|
23
|
+
`);
|
|
24
|
+
// Update table statistics to help query planner use the new index efficiently
|
|
25
|
+
await pool.query(sql `
|
|
26
|
+
analyze roles;
|
|
27
|
+
`);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export default alteration;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
/**
|
|
3
|
+
* Remove foreign key constraint from daily_active_users table to allow
|
|
4
|
+
* historical billing data to persist even after tenant deletion.
|
|
5
|
+
* This supports the MAU-based billing system requirements.
|
|
6
|
+
*
|
|
7
|
+
* Index optimizations:
|
|
8
|
+
* 1. Removes redundant (tenant_id, id) index (id is already primary key)
|
|
9
|
+
* 2. Adds optimized index (tenant_id, date, user_id) for aggregation queries
|
|
10
|
+
* 3. Replaces problematic partial index with BRIN index
|
|
11
|
+
*/
|
|
12
|
+
const alteration = {
|
|
13
|
+
up: async (pool) => {
|
|
14
|
+
// Drop the existing foreign key constraint
|
|
15
|
+
await pool.query(sql `
|
|
16
|
+
alter table daily_active_users
|
|
17
|
+
drop constraint if exists daily_active_users_tenant_id_fkey
|
|
18
|
+
`);
|
|
19
|
+
// Remove the redundant (tenant_id, id) index since id is already primary key
|
|
20
|
+
await pool.query(sql `
|
|
21
|
+
drop index if exists daily_active_users__id
|
|
22
|
+
`);
|
|
23
|
+
// Add optimized index for aggregation queries with better write performance
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
create index daily_active_users__tenant_date_user
|
|
26
|
+
on daily_active_users (tenant_id, date, user_id)
|
|
27
|
+
`);
|
|
28
|
+
// Add BRIN index for time-series date range queries
|
|
29
|
+
// Optimized for sequential data insertion and range scans (date >= ?)
|
|
30
|
+
await pool.query(sql `
|
|
31
|
+
create index daily_active_users__date_brin
|
|
32
|
+
on daily_active_users using brin (date)
|
|
33
|
+
`);
|
|
34
|
+
},
|
|
35
|
+
down: async (pool) => {
|
|
36
|
+
// Drop the new indexes we created
|
|
37
|
+
await pool.query(sql `
|
|
38
|
+
drop index if exists daily_active_users__date_brin
|
|
39
|
+
`);
|
|
40
|
+
await pool.query(sql `
|
|
41
|
+
drop index if exists daily_active_users__tenant_date_user
|
|
42
|
+
`);
|
|
43
|
+
// Recreate the original redundant index
|
|
44
|
+
await pool.query(sql `
|
|
45
|
+
create index daily_active_users__id
|
|
46
|
+
on daily_active_users (tenant_id, id)
|
|
47
|
+
`);
|
|
48
|
+
// Recreate the foreign key constraint
|
|
49
|
+
await pool.query(sql `
|
|
50
|
+
alter table daily_active_users
|
|
51
|
+
add constraint daily_active_users_tenant_id_fkey
|
|
52
|
+
foreign key (tenant_id) references tenants(id)
|
|
53
|
+
on update cascade on delete cascade
|
|
54
|
+
`);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export default alteration;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
import { applyTableRls, dropTableRls } from './utils/1704934999-tables.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create aggregated_daily_active_users table for MAU-based billing system.
|
|
5
|
+
* This table consolidates daily user activities for efficient billing calculations.
|
|
6
|
+
*/
|
|
7
|
+
const alteration = {
|
|
8
|
+
up: async (pool) => {
|
|
9
|
+
// Create the aggregated daily active users table
|
|
10
|
+
await pool.query(sql `
|
|
11
|
+
create table aggregated_daily_active_users (
|
|
12
|
+
tenant_id varchar(21) not null,
|
|
13
|
+
activity_date date not null,
|
|
14
|
+
user_id varchar(21) not null,
|
|
15
|
+
activity_count integer not null,
|
|
16
|
+
primary key (tenant_id, activity_date, user_id)
|
|
17
|
+
);
|
|
18
|
+
`);
|
|
19
|
+
// Index for billing cycle range queries
|
|
20
|
+
await pool.query(sql `
|
|
21
|
+
create index aggregated_daily_active_users__tenant_date
|
|
22
|
+
on aggregated_daily_active_users (tenant_id, activity_date);
|
|
23
|
+
`);
|
|
24
|
+
// Index for tenant-specific user activity queries
|
|
25
|
+
await pool.query(sql `
|
|
26
|
+
create index aggregated_daily_active_users__tenant_user_date
|
|
27
|
+
on aggregated_daily_active_users (tenant_id, user_id, activity_date desc);
|
|
28
|
+
`);
|
|
29
|
+
await applyTableRls(pool, 'aggregated_daily_active_users');
|
|
30
|
+
},
|
|
31
|
+
down: async (pool) => {
|
|
32
|
+
// Drop RLS policies first
|
|
33
|
+
await dropTableRls(pool, 'aggregated_daily_active_users');
|
|
34
|
+
// Drop the table and all its indexes
|
|
35
|
+
await pool.query(sql `
|
|
36
|
+
drop table aggregated_daily_active_users;
|
|
37
|
+
`);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export default alteration;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
up: async (pool) => {
|
|
4
|
+
// Add new columns for user and m2m token usage tracking
|
|
5
|
+
await pool.query(sql `
|
|
6
|
+
alter table daily_token_usage
|
|
7
|
+
add column user_token_usage bigint not null default 0,
|
|
8
|
+
add column m2m_token_usage bigint not null default 0;
|
|
9
|
+
`);
|
|
10
|
+
// Remove foreign key constraint to support enterprise tenant deletion requirements
|
|
11
|
+
await pool.query(sql `
|
|
12
|
+
alter table daily_token_usage
|
|
13
|
+
drop constraint if exists daily_token_usage_tenant_id_fkey;
|
|
14
|
+
`);
|
|
15
|
+
},
|
|
16
|
+
down: async (pool) => {
|
|
17
|
+
// Remove the new columns
|
|
18
|
+
await pool.query(sql `
|
|
19
|
+
alter table daily_token_usage
|
|
20
|
+
drop column if exists user_token_usage,
|
|
21
|
+
drop column if exists m2m_token_usage;
|
|
22
|
+
`);
|
|
23
|
+
// Re-add the foreign key constraint
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
alter table daily_token_usage
|
|
26
|
+
add constraint daily_token_usage_tenant_id_fkey
|
|
27
|
+
foreign key (tenant_id) references tenants (id) on update cascade on delete cascade;
|
|
28
|
+
`);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export default alteration;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { sql } from '@silverhand/slonik';
|
|
2
|
+
const alteration = {
|
|
3
|
+
beforeUp: async (pool) => {
|
|
4
|
+
/**
|
|
5
|
+
* Use 'if not exists' to ensure idempotency.
|
|
6
|
+
* If the subsequent transaction in 'up' fails, the migration can be safely re-run without failing on an already created index.
|
|
7
|
+
*/
|
|
8
|
+
await pool.query(sql `
|
|
9
|
+
create index concurrently if not exists logs__created_at_id
|
|
10
|
+
on logs (tenant_id, created_at, id);
|
|
11
|
+
`);
|
|
12
|
+
},
|
|
13
|
+
up: async () => {
|
|
14
|
+
/**
|
|
15
|
+
* The index on the logs table must be created outside of a transaction to avoid table locks.
|
|
16
|
+
* 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
|
|
17
|
+
*/
|
|
18
|
+
},
|
|
19
|
+
beforeDown: async (pool) => {
|
|
20
|
+
/**
|
|
21
|
+
* Use 'if exists' to ensure idempotency. If the subsequent transaction in 'down' fails,
|
|
22
|
+
* the rollback can be safely re-run without failing on a non-existent index.
|
|
23
|
+
*/
|
|
24
|
+
await pool.query(sql `
|
|
25
|
+
drop index concurrently if exists logs__created_at_id;
|
|
26
|
+
`);
|
|
27
|
+
},
|
|
28
|
+
down: async () => {
|
|
29
|
+
/**
|
|
30
|
+
* The index on the logs table must be dropped outside of a transaction to avoid table locks.
|
|
31
|
+
* 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
|
|
32
|
+
*/
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export default alteration;
|