@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.
Files changed (54) hide show
  1. package/alterations/1.34.0-1762338508-add-organization-roles-type-index.ts +19 -0
  2. package/alterations/1.34.0-1764164658-add-applications-roles-include-indexes.ts +38 -0
  3. package/alterations/1.34.0-1764236036-remove-applications-roles-include-indexes.ts +38 -0
  4. package/alterations/1.35.0-1764580455-remove-daily-active-users-foreign-key.ts +69 -0
  5. package/alterations/1.35.0-1764580589-create-aggregated-daily-active-users-table.ts +51 -0
  6. package/alterations/1.35.0-1764653048-update-daily-token-usage-mau-support.ts +37 -0
  7. package/alterations/1.35.0-1765183934-add-logs-created-at-id-index.ts +39 -0
  8. package/alterations/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.ts +31 -0
  9. package/alterations/1.35.0-1765631949-drop-redundant-logs-id-index.ts +47 -0
  10. package/alterations/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.ts +39 -0
  11. package/alterations-js/1.34.0-1762338508-add-organization-roles-type-index.js +15 -0
  12. package/alterations-js/1.34.0-1764164658-add-applications-roles-include-indexes.js +30 -0
  13. package/alterations-js/1.34.0-1764236036-remove-applications-roles-include-indexes.js +30 -0
  14. package/alterations-js/1.35.0-1764580455-remove-daily-active-users-foreign-key.js +57 -0
  15. package/alterations-js/1.35.0-1764580589-create-aggregated-daily-active-users-table.js +40 -0
  16. package/alterations-js/1.35.0-1764653048-update-daily-token-usage-mau-support.js +31 -0
  17. package/alterations-js/1.35.0-1765183934-add-logs-created-at-id-index.js +35 -0
  18. package/alterations-js/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.js +25 -0
  19. package/alterations-js/1.35.0-1765631949-drop-redundant-logs-id-index.js +43 -0
  20. package/alterations-js/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.js +31 -0
  21. package/lib/consts/product-event.d.ts +0 -12
  22. package/lib/consts/product-event.js +0 -13
  23. package/lib/db-entries/aggregated-daily-active-user.d.ts +22 -0
  24. package/lib/db-entries/aggregated-daily-active-user.js +33 -0
  25. package/lib/db-entries/daily-token-usage.d.ts +5 -1
  26. package/lib/db-entries/daily-token-usage.js +8 -0
  27. package/lib/db-entries/index.d.ts +1 -0
  28. package/lib/db-entries/index.js +1 -0
  29. package/lib/db-entries/saml-application-session.js +2 -2
  30. package/lib/foundations/jsonb-types/captcha.d.ts +16 -0
  31. package/lib/foundations/jsonb-types/captcha.js +7 -0
  32. package/lib/foundations/jsonb-types/hooks.d.ts +6 -4
  33. package/lib/foundations/jsonb-types/hooks.js +3 -1
  34. package/lib/foundations/jsonb-types/oidc-module.js +1 -1
  35. package/lib/seeds/application.d.ts +6 -0
  36. package/lib/seeds/application.js +23 -4
  37. package/lib/types/alteration.d.ts +11 -1
  38. package/lib/types/domain.d.ts +4 -2
  39. package/lib/types/domain.js +2 -0
  40. package/lib/types/hook.d.ts +7 -4
  41. package/lib/types/interactions.d.ts +14 -5
  42. package/lib/types/interactions.js +10 -4
  43. package/lib/types/sign-in-experience.d.ts +13 -1
  44. package/lib/types/sign-in-experience.js +3 -1
  45. package/lib/types/user.d.ts +3 -0
  46. package/lib/types/user.js +1 -0
  47. package/package.json +4 -4
  48. package/tables/_after_all.sql +1 -1
  49. package/tables/aggregated_daily_active_users.sql +16 -0
  50. package/tables/daily_active_users.sql +9 -4
  51. package/tables/daily_token_usage.sql +3 -2
  52. package/tables/logs.sql +3 -3
  53. package/tables/organization_roles.sql +3 -0
  54. 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;