@logto/schemas 1.34.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 (39) hide show
  1. package/alterations/1.35.0-1764580455-remove-daily-active-users-foreign-key.ts +69 -0
  2. package/alterations/1.35.0-1764580589-create-aggregated-daily-active-users-table.ts +51 -0
  3. package/alterations/1.35.0-1764653048-update-daily-token-usage-mau-support.ts +37 -0
  4. package/alterations/1.35.0-1765183934-add-logs-created-at-id-index.ts +39 -0
  5. package/alterations/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.ts +31 -0
  6. package/alterations/1.35.0-1765631949-drop-redundant-logs-id-index.ts +47 -0
  7. package/alterations/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.ts +39 -0
  8. package/alterations-js/1.35.0-1764580455-remove-daily-active-users-foreign-key.js +57 -0
  9. package/alterations-js/1.35.0-1764580589-create-aggregated-daily-active-users-table.js +40 -0
  10. package/alterations-js/1.35.0-1764653048-update-daily-token-usage-mau-support.js +31 -0
  11. package/alterations-js/1.35.0-1765183934-add-logs-created-at-id-index.js +35 -0
  12. package/alterations-js/1.35.0-1765255453-update-saml-session-relay-state-to-varchar-512.js +25 -0
  13. package/alterations-js/1.35.0-1765631949-drop-redundant-logs-id-index.js +43 -0
  14. package/alterations-js/1.35.0-1766028646-grant-tenants-table-tag-column-read-permission.js +31 -0
  15. package/lib/consts/product-event.d.ts +0 -12
  16. package/lib/consts/product-event.js +0 -13
  17. package/lib/db-entries/aggregated-daily-active-user.d.ts +22 -0
  18. package/lib/db-entries/aggregated-daily-active-user.js +33 -0
  19. package/lib/db-entries/daily-token-usage.d.ts +5 -1
  20. package/lib/db-entries/daily-token-usage.js +8 -0
  21. package/lib/db-entries/index.d.ts +1 -0
  22. package/lib/db-entries/index.js +1 -0
  23. package/lib/db-entries/saml-application-session.js +2 -2
  24. package/lib/foundations/jsonb-types/captcha.d.ts +16 -0
  25. package/lib/foundations/jsonb-types/captcha.js +7 -0
  26. package/lib/types/alteration.d.ts +11 -1
  27. package/lib/types/interactions.d.ts +14 -5
  28. package/lib/types/interactions.js +10 -4
  29. package/lib/types/sign-in-experience.d.ts +13 -1
  30. package/lib/types/sign-in-experience.js +3 -1
  31. package/lib/types/user.d.ts +3 -0
  32. package/lib/types/user.js +1 -0
  33. package/package.json +4 -4
  34. package/tables/_after_all.sql +1 -1
  35. package/tables/aggregated_daily_active_users.sql +16 -0
  36. package/tables/daily_active_users.sql +9 -4
  37. package/tables/daily_token_usage.sql +3 -2
  38. package/tables/logs.sql +3 -3
  39. package/tables/saml_application_sessions.sql +1 -1
@@ -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,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;
@@ -0,0 +1,25 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ /**
3
+ * Update saml_application_sessions.relay_state column type from varchar(256) to text
4
+ * to support longer relay state values that may be used in SAML authentication flows.
5
+ *
6
+ * The relay state parameter in SAML can contain arbitrary data that needs to be
7
+ * preserved and returned to the service provider, and 256 characters may not be
8
+ * sufficient for all use cases.
9
+ */
10
+ const alteration = {
11
+ up: async (pool) => {
12
+ await pool.query(sql `
13
+ alter table saml_application_sessions
14
+ alter column relay_state type varchar(512)
15
+ `);
16
+ },
17
+ down: async (pool) => {
18
+ // 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.
19
+ await pool.query(sql `
20
+ alter table saml_application_sessions
21
+ alter column relay_state type varchar(256) using left(relay_state, 256)
22
+ `);
23
+ },
24
+ };
25
+ export default alteration;
@@ -0,0 +1,43 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ /**
3
+ * The `logs__id` index on (tenant_id, id) is redundant because:
4
+ * 1. `id` is already the primary key, which can efficiently lookup by id
5
+ * 2. `logs__created_at_id` index on (tenant_id, created_at, id) already covers tenant_id queries
6
+ *
7
+ * Removing this index reduces write IOPS during log deletion operations,
8
+ * as each DELETE no longer needs to update this redundant index.
9
+ */
10
+ const alteration = {
11
+ beforeUp: async (pool) => {
12
+ /**
13
+ * Use 'if exists' to ensure idempotency.
14
+ * 'concurrently' avoids table locks during index drop.
15
+ */
16
+ await pool.query(sql `
17
+ drop index concurrently if exists logs__id;
18
+ `);
19
+ },
20
+ up: async () => {
21
+ /**
22
+ * The index must be dropped outside of a transaction to avoid table locks.
23
+ * 'concurrently' cannot be used inside a transaction, so this up is intentionally left empty.
24
+ */
25
+ },
26
+ beforeDown: async (pool) => {
27
+ /**
28
+ * Recreate the index if rolling back.
29
+ * Use 'if not exists' to ensure idempotency.
30
+ */
31
+ await pool.query(sql `
32
+ create index concurrently if not exists logs__id
33
+ on logs (tenant_id, id);
34
+ `);
35
+ },
36
+ down: async () => {
37
+ /**
38
+ * The index must be created outside of a transaction to avoid table locks.
39
+ * 'concurrently' cannot be used inside a transaction, so this down is intentionally left empty.
40
+ */
41
+ },
42
+ };
43
+ export default alteration;
@@ -0,0 +1,31 @@
1
+ import { sql } from '@silverhand/slonik';
2
+ const getDatabaseName = async (pool) => {
3
+ const { currentDatabase } = await pool.one(sql `
4
+ select current_database();
5
+ `);
6
+ return currentDatabase.replaceAll('-', '_');
7
+ };
8
+ /**
9
+ * Grant read permission to the tag column in the tenants table to the logto_tenant_<databaseName> role.
10
+ */
11
+ const alteration = {
12
+ up: async (pool) => {
13
+ const databaseName = await getDatabaseName(pool);
14
+ const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
15
+ await pool.query(sql `
16
+ grant select (tag)
17
+ on table tenants
18
+ to ${baseRoleId}
19
+ `);
20
+ },
21
+ down: async (pool) => {
22
+ const databaseName = await getDatabaseName(pool);
23
+ const baseRoleId = sql.identifier([`logto_tenant_${databaseName}`]);
24
+ await pool.query(sql `
25
+ revoke select (tag)
26
+ on table tenants
27
+ from ${baseRoleId}
28
+ `);
29
+ },
30
+ };
31
+ export default alteration;
@@ -46,7 +46,6 @@ export declare enum ProductEvent {
46
46
  DeveloperCreated = "developer created",
47
47
  /** A user has been deleted in the admin tenant. */
48
48
  DeveloperDeleted = "developer deleted",
49
- AccessTokenIssued = "access token issued",
50
49
  AppCreated = "app created",
51
50
  AppDeleted = "app deleted",
52
51
  RoleCreated = "role created",
@@ -86,14 +85,3 @@ export declare const tenantEventDistinctId = "TENANT_EVENT";
86
85
  * identifying the user who initiated the Management API request proxied by the cloud service.
87
86
  */
88
87
  export declare const cloudUserIdHeader = "logto-cloud-user-id";
89
- /**
90
- * The types of access tokens issued by Logto.
91
- *
92
- * Note that this is for internal use only and is different from other technical definitions of
93
- * token types.
94
- */
95
- export declare enum ProductAccessTokenType {
96
- Unknown = "unknown",
97
- User = "user",
98
- ClientCredentials = "client_credentials"
99
- }
@@ -47,7 +47,6 @@ export var ProductEvent;
47
47
  ProductEvent["DeveloperCreated"] = "developer created";
48
48
  /** A user has been deleted in the admin tenant. */
49
49
  ProductEvent["DeveloperDeleted"] = "developer deleted";
50
- ProductEvent["AccessTokenIssued"] = "access token issued";
51
50
  ProductEvent["AppCreated"] = "app created";
52
51
  ProductEvent["AppDeleted"] = "app deleted";
53
52
  ProductEvent["RoleCreated"] = "role created";
@@ -88,15 +87,3 @@ export const tenantEventDistinctId = 'TENANT_EVENT';
88
87
  * identifying the user who initiated the Management API request proxied by the cloud service.
89
88
  */
90
89
  export const cloudUserIdHeader = 'logto-cloud-user-id';
91
- /**
92
- * The types of access tokens issued by Logto.
93
- *
94
- * Note that this is for internal use only and is different from other technical definitions of
95
- * token types.
96
- */
97
- export var ProductAccessTokenType;
98
- (function (ProductAccessTokenType) {
99
- ProductAccessTokenType["Unknown"] = "unknown";
100
- ProductAccessTokenType["User"] = "user";
101
- ProductAccessTokenType["ClientCredentials"] = "client_credentials";
102
- })(ProductAccessTokenType || (ProductAccessTokenType = {}));
@@ -0,0 +1,22 @@
1
+ import { GeneratedSchema } from './../foundations/index.js';
2
+ /**
3
+ * This table is used to store aggregated data of daily active users for each tenant. A daily job summarizes data from the daily active users table and inserts it into this table, or removes expired data. Therefore, we should not directly manipulate this table, except for "read" operations.
4
+ *
5
+ * @remarks This is a type for database creation.
6
+ * @see {@link AggregatedDailyActiveUser} for the original type.
7
+ */
8
+ export type CreateAggregatedDailyActiveUser = {
9
+ tenantId?: string;
10
+ activityDate: number;
11
+ userId: string;
12
+ activityCount: number;
13
+ };
14
+ /** This table is used to store aggregated data of daily active users for each tenant. A daily job summarizes data from the daily active users table and inserts it into this table, or removes expired data. Therefore, we should not directly manipulate this table, except for "read" operations. */
15
+ export type AggregatedDailyActiveUser = {
16
+ tenantId: string;
17
+ activityDate: number;
18
+ userId: string;
19
+ activityCount: number;
20
+ };
21
+ export type AggregatedDailyActiveUserKeys = 'tenantId' | 'activityDate' | 'userId' | 'activityCount';
22
+ export declare const AggregatedDailyActiveUsers: GeneratedSchema<AggregatedDailyActiveUserKeys, CreateAggregatedDailyActiveUser, AggregatedDailyActiveUser, 'aggregated_daily_active_users', 'aggregated_daily_active_user'>;
@@ -0,0 +1,33 @@
1
+ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
+ import { z } from 'zod';
3
+ const createGuard = z.object({
4
+ tenantId: z.string().max(21).optional(),
5
+ activityDate: z.number(),
6
+ userId: z.string().min(1).max(21),
7
+ activityCount: z.number(),
8
+ });
9
+ const guard = z.object({
10
+ tenantId: z.string().max(21),
11
+ activityDate: z.number(),
12
+ userId: z.string().min(1).max(21),
13
+ activityCount: z.number(),
14
+ });
15
+ export const AggregatedDailyActiveUsers = Object.freeze({
16
+ table: 'aggregated_daily_active_users',
17
+ tableSingular: 'aggregated_daily_active_user',
18
+ fields: {
19
+ tenantId: 'tenant_id',
20
+ activityDate: 'activity_date',
21
+ userId: 'user_id',
22
+ activityCount: 'activity_count',
23
+ },
24
+ fieldKeys: [
25
+ 'tenantId',
26
+ 'activityDate',
27
+ 'userId',
28
+ 'activityCount',
29
+ ],
30
+ createGuard,
31
+ guard,
32
+ updateGuard: guard.partial(),
33
+ });
@@ -8,13 +8,17 @@ export type CreateDailyTokenUsage = {
8
8
  id: string;
9
9
  tenantId?: string;
10
10
  usage?: number;
11
+ userTokenUsage?: number;
12
+ m2mTokenUsage?: number;
11
13
  date: number;
12
14
  };
13
15
  export type DailyTokenUsage = {
14
16
  id: string;
15
17
  tenantId: string;
16
18
  usage: number;
19
+ userTokenUsage: number;
20
+ m2mTokenUsage: number;
17
21
  date: number;
18
22
  };
19
- export type DailyTokenUsageKeys = 'id' | 'tenantId' | 'usage' | 'date';
23
+ export type DailyTokenUsageKeys = 'id' | 'tenantId' | 'usage' | 'userTokenUsage' | 'm2mTokenUsage' | 'date';
20
24
  export declare const DailyTokenUsage: GeneratedSchema<DailyTokenUsageKeys, CreateDailyTokenUsage, DailyTokenUsage, 'daily_token_usage', 'daily_token_usage'>;
@@ -4,12 +4,16 @@ const createGuard = z.object({
4
4
  id: z.string().min(1).max(21),
5
5
  tenantId: z.string().max(21).optional(),
6
6
  usage: z.number().optional(),
7
+ userTokenUsage: z.number().optional(),
8
+ m2mTokenUsage: z.number().optional(),
7
9
  date: z.number(),
8
10
  });
9
11
  const guard = z.object({
10
12
  id: z.string().min(1).max(21),
11
13
  tenantId: z.string().max(21),
12
14
  usage: z.number(),
15
+ userTokenUsage: z.number(),
16
+ m2mTokenUsage: z.number(),
13
17
  date: z.number(),
14
18
  });
15
19
  export const DailyTokenUsage = Object.freeze({
@@ -19,12 +23,16 @@ export const DailyTokenUsage = Object.freeze({
19
23
  id: 'id',
20
24
  tenantId: 'tenant_id',
21
25
  usage: 'usage',
26
+ userTokenUsage: 'user_token_usage',
27
+ m2mTokenUsage: 'm2m_token_usage',
22
28
  date: 'date',
23
29
  },
24
30
  fieldKeys: [
25
31
  'id',
26
32
  'tenantId',
27
33
  'usage',
34
+ 'userTokenUsage',
35
+ 'm2mTokenUsage',
28
36
  'date',
29
37
  ],
30
38
  createGuard,
@@ -4,6 +4,7 @@ export * from './-after-each.js';
4
4
  export * from './-before-all.js';
5
5
  export * from './-function.js';
6
6
  export * from './account-center.js';
7
+ export * from './aggregated-daily-active-user.js';
7
8
  export * from './application-secret.js';
8
9
  export * from './application-sign-in-experience.js';
9
10
  export * from './application-user-consent-organization-resource-scope.js';
@@ -5,6 +5,7 @@ export * from './-after-each.js';
5
5
  export * from './-before-all.js';
6
6
  export * from './-function.js';
7
7
  export * from './account-center.js';
8
+ export * from './aggregated-daily-active-user.js';
8
9
  export * from './application-secret.js';
9
10
  export * from './application-sign-in-experience.js';
10
11
  export * from './application-user-consent-organization-resource-scope.js';
@@ -6,7 +6,7 @@ const createGuard = z.object({
6
6
  applicationId: z.string().min(1).max(21),
7
7
  samlRequestId: z.string().min(1).max(128),
8
8
  oidcState: z.string().max(32).nullable().optional(),
9
- relayState: z.string().max(256).nullable().optional(),
9
+ relayState: z.string().max(512).nullable().optional(),
10
10
  rawAuthRequest: z.string().min(1),
11
11
  createdAt: z.number().optional(),
12
12
  expiresAt: z.number(),
@@ -17,7 +17,7 @@ const guard = z.object({
17
17
  applicationId: z.string().min(1).max(21),
18
18
  samlRequestId: z.string().min(1).max(128),
19
19
  oidcState: z.string().max(32).nullable(),
20
- relayState: z.string().max(256).nullable(),
20
+ relayState: z.string().max(512).nullable(),
21
21
  rawAuthRequest: z.string().min(1),
22
22
  createdAt: z.number(),
23
23
  expiresAt: z.number(),
@@ -3,6 +3,10 @@ export declare enum CaptchaType {
3
3
  RecaptchaEnterprise = "RecaptchaEnterprise",
4
4
  Turnstile = "Turnstile"
5
5
  }
6
+ export declare enum RecaptchaEnterpriseMode {
7
+ Invisible = "invisible",
8
+ Checkbox = "checkbox"
9
+ }
6
10
  export declare const turnstileConfigGuard: z.ZodObject<{
7
11
  type: z.ZodLiteral<CaptchaType.Turnstile>;
8
12
  siteKey: z.ZodString;
@@ -22,16 +26,22 @@ export declare const recaptchaEnterpriseConfigGuard: z.ZodObject<{
22
26
  siteKey: z.ZodString;
23
27
  secretKey: z.ZodString;
24
28
  projectId: z.ZodString;
29
+ domain: z.ZodOptional<z.ZodString>;
30
+ mode: z.ZodOptional<z.ZodNativeEnum<typeof RecaptchaEnterpriseMode>>;
25
31
  }, "strip", z.ZodTypeAny, {
26
32
  type: CaptchaType.RecaptchaEnterprise;
27
33
  siteKey: string;
28
34
  secretKey: string;
29
35
  projectId: string;
36
+ domain?: string | undefined;
37
+ mode?: RecaptchaEnterpriseMode | undefined;
30
38
  }, {
31
39
  type: CaptchaType.RecaptchaEnterprise;
32
40
  siteKey: string;
33
41
  secretKey: string;
34
42
  projectId: string;
43
+ domain?: string | undefined;
44
+ mode?: RecaptchaEnterpriseMode | undefined;
35
45
  }>;
36
46
  export type RecaptchaEnterpriseConfig = z.infer<typeof recaptchaEnterpriseConfigGuard>;
37
47
  export declare const captchaConfigGuard: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
@@ -51,15 +61,21 @@ export declare const captchaConfigGuard: z.ZodDiscriminatedUnion<"type", [z.ZodO
51
61
  siteKey: z.ZodString;
52
62
  secretKey: z.ZodString;
53
63
  projectId: z.ZodString;
64
+ domain: z.ZodOptional<z.ZodString>;
65
+ mode: z.ZodOptional<z.ZodNativeEnum<typeof RecaptchaEnterpriseMode>>;
54
66
  }, "strip", z.ZodTypeAny, {
55
67
  type: CaptchaType.RecaptchaEnterprise;
56
68
  siteKey: string;
57
69
  secretKey: string;
58
70
  projectId: string;
71
+ domain?: string | undefined;
72
+ mode?: RecaptchaEnterpriseMode | undefined;
59
73
  }, {
60
74
  type: CaptchaType.RecaptchaEnterprise;
61
75
  siteKey: string;
62
76
  secretKey: string;
63
77
  projectId: string;
78
+ domain?: string | undefined;
79
+ mode?: RecaptchaEnterpriseMode | undefined;
64
80
  }>]>;
65
81
  export type CaptchaConfig = z.infer<typeof captchaConfigGuard>;
@@ -4,6 +4,11 @@ export var CaptchaType;
4
4
  CaptchaType["RecaptchaEnterprise"] = "RecaptchaEnterprise";
5
5
  CaptchaType["Turnstile"] = "Turnstile";
6
6
  })(CaptchaType || (CaptchaType = {}));
7
+ export var RecaptchaEnterpriseMode;
8
+ (function (RecaptchaEnterpriseMode) {
9
+ RecaptchaEnterpriseMode["Invisible"] = "invisible";
10
+ RecaptchaEnterpriseMode["Checkbox"] = "checkbox";
11
+ })(RecaptchaEnterpriseMode || (RecaptchaEnterpriseMode = {}));
7
12
  export const turnstileConfigGuard = z.object({
8
13
  type: z.literal(CaptchaType.Turnstile),
9
14
  siteKey: z.string(),
@@ -14,6 +19,8 @@ export const recaptchaEnterpriseConfigGuard = z.object({
14
19
  siteKey: z.string(),
15
20
  secretKey: z.string(),
16
21
  projectId: z.string(),
22
+ domain: z.string().optional(),
23
+ mode: z.nativeEnum(RecaptchaEnterpriseMode).optional(),
17
24
  });
18
25
  export const captchaConfigGuard = z.discriminatedUnion('type', [
19
26
  turnstileConfigGuard,
@@ -1,5 +1,15 @@
1
- import type { DatabaseTransactionConnection } from '@silverhand/slonik';
1
+ import type { CommonQueryMethods, DatabaseTransactionConnection } from '@silverhand/slonik';
2
2
  export type AlterationScript = {
3
+ /**
4
+ * Optional hook that runs before `up` outside of a transaction.
5
+ * Use for operations that cannot be wrapped in a transaction (e.g., CREATE INDEX CONCURRENTLY).
6
+ */
7
+ beforeUp?: (connection: CommonQueryMethods) => Promise<void>;
8
+ /**
9
+ * Optional hook that runs before `down` outside of a transaction.
10
+ * Use for operations that cannot be wrapped in a transaction (e.g., DROP INDEX CONCURRENTLY).
11
+ */
12
+ beforeDown?: (connection: CommonQueryMethods) => Promise<void>;
3
13
  up: (connection: DatabaseTransactionConnection) => Promise<void>;
4
14
  down: (connection: DatabaseTransactionConnection) => Promise<void>;
5
15
  };
@@ -45,16 +45,25 @@ export type VerificationCodeIdentifier<T extends VerificationCodeSignInIdentifie
45
45
  type: T;
46
46
  value: string;
47
47
  };
48
- export declare const verificationCodeIdentifierGuard: z.ZodObject<{
49
- type: z.ZodEnum<[SignInIdentifier.Email, SignInIdentifier.Phone]>;
48
+ export declare const verificationCodeIdentifierGuard: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
49
+ type: z.ZodLiteral<SignInIdentifier.Email>;
50
50
  value: z.ZodString;
51
51
  }, "strip", z.ZodTypeAny, {
52
52
  value: string;
53
- type: SignInIdentifier.Email | SignInIdentifier.Phone;
53
+ type: SignInIdentifier.Email;
54
54
  }, {
55
55
  value: string;
56
- type: SignInIdentifier.Email | SignInIdentifier.Phone;
57
- }>;
56
+ type: SignInIdentifier.Email;
57
+ }>, z.ZodObject<{
58
+ type: z.ZodLiteral<SignInIdentifier.Phone>;
59
+ value: z.ZodString;
60
+ }, "strip", z.ZodTypeAny, {
61
+ value: string;
62
+ type: SignInIdentifier.Phone;
63
+ }, {
64
+ value: string;
65
+ type: SignInIdentifier.Phone;
66
+ }>]>;
58
67
  /** Payload type for `POST /api/experience/verification/{social|sso}/:connectorId/authorization-uri`. */
59
68
  export type SocialAuthorizationUrlPayload = {
60
69
  state: string;
@@ -21,10 +21,16 @@ export const interactionIdentifierGuard = z.object({
21
21
  type: z.nativeEnum(SignInIdentifier),
22
22
  value: z.string(),
23
23
  });
24
- export const verificationCodeIdentifierGuard = z.object({
25
- type: z.enum([SignInIdentifier.Email, SignInIdentifier.Phone]),
26
- value: z.string(),
27
- });
24
+ export const verificationCodeIdentifierGuard = z.discriminatedUnion('type', [
25
+ z.object({
26
+ type: z.literal(SignInIdentifier.Email),
27
+ value: z.string().regex(emailRegEx),
28
+ }),
29
+ z.object({
30
+ type: z.literal(SignInIdentifier.Phone),
31
+ value: z.string().regex(phoneRegEx),
32
+ }),
33
+ ]);
28
34
  export const socialAuthorizationUrlPayloadGuard = z.object({
29
35
  state: z.string(),
30
36
  redirectUri: z.string(),
@@ -1,7 +1,7 @@
1
1
  import { type ConnectorMetadata, type GoogleOneTapConfig } from '@logto/connector-kit';
2
2
  import { z } from 'zod';
3
3
  import { type CustomProfileField, type SignInExperience } from '../db-entries/index.js';
4
- import { CaptchaType } from '../foundations/jsonb-types/index.js';
4
+ import { CaptchaType, RecaptchaEnterpriseMode } from '../foundations/jsonb-types/index.js';
5
5
  import { type SsoConnectorMetadata } from './sso-connector.js';
6
6
  type ForgotPassword = {
7
7
  phone: boolean;
@@ -32,6 +32,8 @@ export type FullSignInExperience = Omit<SignInExperience, 'forgotPasswordMethods
32
32
  captchaConfig?: {
33
33
  type: CaptchaType;
34
34
  siteKey: string;
35
+ domain?: string;
36
+ mode?: RecaptchaEnterpriseMode;
35
37
  };
36
38
  customProfileFields?: Readonly<CustomProfileField[]>;
37
39
  };
@@ -674,12 +676,18 @@ export declare const fullSignInExperienceGuard: z.ZodObject<Omit<{
674
676
  captchaConfig: z.ZodOptional<z.ZodObject<{
675
677
  type: z.ZodNativeEnum<typeof CaptchaType>;
676
678
  siteKey: z.ZodString;
679
+ domain: z.ZodOptional<z.ZodString>;
680
+ mode: z.ZodOptional<z.ZodNativeEnum<typeof RecaptchaEnterpriseMode>>;
677
681
  }, "strip", z.ZodTypeAny, {
678
682
  type: CaptchaType;
679
683
  siteKey: string;
684
+ domain?: string | undefined;
685
+ mode?: RecaptchaEnterpriseMode | undefined;
680
686
  }, {
681
687
  type: CaptchaType;
682
688
  siteKey: string;
689
+ domain?: string | undefined;
690
+ mode?: RecaptchaEnterpriseMode | undefined;
683
691
  }>>;
684
692
  customProfileFields: z.ZodArray<import("../index.js").Guard<CustomProfileField>, "many">;
685
693
  }, "strip", z.ZodTypeAny, {
@@ -873,6 +881,8 @@ export declare const fullSignInExperienceGuard: z.ZodObject<Omit<{
873
881
  captchaConfig?: {
874
882
  type: CaptchaType;
875
883
  siteKey: string;
884
+ domain?: string | undefined;
885
+ mode?: RecaptchaEnterpriseMode | undefined;
876
886
  } | undefined;
877
887
  }, {
878
888
  id: string;
@@ -1065,6 +1075,8 @@ export declare const fullSignInExperienceGuard: z.ZodObject<Omit<{
1065
1075
  captchaConfig?: {
1066
1076
  type: CaptchaType;
1067
1077
  siteKey: string;
1078
+ domain?: string | undefined;
1079
+ mode?: RecaptchaEnterpriseMode | undefined;
1068
1080
  } | undefined;
1069
1081
  }>;
1070
1082
  export {};
@@ -1,7 +1,7 @@
1
1
  import { connectorMetadataGuard, googleOneTapConfigGuard, } from '@logto/connector-kit';
2
2
  import { z } from 'zod';
3
3
  import { CustomProfileFields, SignInExperiences, } from '../db-entries/index.js';
4
- import { CaptchaType } from '../foundations/jsonb-types/index.js';
4
+ import { CaptchaType, RecaptchaEnterpriseMode } from '../foundations/jsonb-types/index.js';
5
5
  import { ssoConnectorMetadataGuard } from './sso-connector.js';
6
6
  export const fullSignInExperienceGuard = SignInExperiences.guard
7
7
  .omit({ forgotPasswordMethods: true })
@@ -25,6 +25,8 @@ export const fullSignInExperienceGuard = SignInExperiences.guard
25
25
  .object({
26
26
  type: z.nativeEnum(CaptchaType),
27
27
  siteKey: z.string(),
28
+ domain: z.string().optional(),
29
+ mode: z.nativeEnum(RecaptchaEnterpriseMode).optional(),
28
30
  })
29
31
  .optional(),
30
32
  customProfileFields: CustomProfileFields.guard.array(),
@@ -316,6 +316,7 @@ export type UserProfileResponse = z.infer<typeof userProfileResponseGuard>;
316
316
  export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
317
317
  id: z.ZodString;
318
318
  createdAt: z.ZodString;
319
+ lastUsedAt: z.ZodOptional<z.ZodString>;
319
320
  type: z.ZodNativeEnum<typeof MfaFactor>;
320
321
  agent: z.ZodOptional<z.ZodString>;
321
322
  name: z.ZodOptional<z.ZodString>;
@@ -325,6 +326,7 @@ export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
325
326
  id: string;
326
327
  createdAt: string;
327
328
  name?: string | undefined;
329
+ lastUsedAt?: string | undefined;
328
330
  agent?: string | undefined;
329
331
  remainCodes?: number | undefined;
330
332
  }, {
@@ -332,6 +334,7 @@ export declare const userMfaVerificationResponseGuard: z.ZodArray<z.ZodObject<{
332
334
  id: string;
333
335
  createdAt: string;
334
336
  name?: string | undefined;
337
+ lastUsedAt?: string | undefined;
335
338
  agent?: string | undefined;
336
339
  remainCodes?: number | undefined;
337
340
  }>, "many">;
package/lib/types/user.js CHANGED
@@ -29,6 +29,7 @@ export const userMfaVerificationResponseGuard = z
29
29
  .object({
30
30
  id: z.string(),
31
31
  createdAt: z.string(),
32
+ lastUsedAt: z.string().optional(),
32
33
  type: z.nativeEnum(MfaFactor),
33
34
  agent: z.string().optional(),
34
35
  name: z.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logto/schemas",
3
- "version": "1.34.0",
3
+ "version": "1.35.0",
4
4
  "author": "Silverhand Inc. <contact@silverhand.io>",
5
5
  "license": "MPL-2.0",
6
6
  "type": "module",
@@ -65,11 +65,11 @@
65
65
  "dependencies": {
66
66
  "@withtyped/server": "^0.14.0",
67
67
  "nanoid": "^5.0.9",
68
- "@logto/connector-kit": "^4.6.0",
68
+ "@logto/connector-kit": "^4.7.0",
69
69
  "@logto/core-kit": "^2.6.1",
70
- "@logto/language-kit": "^1.2.0",
71
- "@logto/phrases": "^1.23.0",
70
+ "@logto/phrases": "^1.24.0",
72
71
  "@logto/phrases-experience": "^1.12.0",
72
+ "@logto/language-kit": "^1.2.0",
73
73
  "@logto/shared": "^3.3.0"
74
74
  },
75
75
  "peerDependencies": {
@@ -13,7 +13,7 @@ revoke all privileges
13
13
  from logto_tenant_${database};
14
14
 
15
15
  -- Allow limited select to perform the RLS policy query in `after_each` (using select ... from tenants ...)
16
- grant select (id, db_user, is_suspended)
16
+ grant select (id, db_user, is_suspended, tag)
17
17
  on table tenants
18
18
  to logto_tenant_${database};
19
19
 
@@ -0,0 +1,16 @@
1
+ /** This table is used to store aggregated data of daily active users for each tenant. A daily job summarizes data from the daily active users table and inserts it into this table, or removes expired data. Therefore, we should not directly manipulate this table, except for "read" operations. */
2
+ create table aggregated_daily_active_users (
3
+ tenant_id varchar(21) not null,
4
+ activity_date date not null,
5
+ user_id varchar(21) not null,
6
+ activity_count integer not null,
7
+ primary key (tenant_id, activity_date, user_id)
8
+ );
9
+
10
+ -- Index for billing cycle range queries
11
+ create index aggregated_daily_active_users__tenant_date
12
+ on aggregated_daily_active_users (tenant_id, activity_date);
13
+
14
+ -- Index for tenant-specific user activity queries
15
+ create index aggregated_daily_active_users__tenant_user_date
16
+ on aggregated_daily_active_users (tenant_id, user_id, activity_date desc);
@@ -1,7 +1,6 @@
1
1
  create table daily_active_users (
2
2
  id varchar(21) not null,
3
- tenant_id varchar(21) not null
4
- references tenants (id) on update cascade on delete cascade,
3
+ tenant_id varchar(21) not null,
5
4
  user_id varchar(21) not null,
6
5
  date timestamptz not null default (now()),
7
6
  primary key (id),
@@ -9,8 +8,14 @@ create table daily_active_users (
9
8
  unique (user_id, date)
10
9
  );
11
10
 
12
- create index daily_active_users__id
13
- on daily_active_users (tenant_id, id);
11
+ -- Optimized index for aggregation queries with better write performance
12
+ create index daily_active_users__tenant_date_user
13
+ on daily_active_users (tenant_id, date, user_id);
14
+
15
+ -- BRIN index for time-series date range queries
16
+ -- Optimized for sequential data insertion and range scans (date >= ?)
17
+ create index daily_active_users__date_brin
18
+ on daily_active_users using brin (date);
14
19
 
15
20
  create index daily_active_users__date
16
21
  on daily_active_users (tenant_id, date);
@@ -1,8 +1,9 @@
1
1
  create table daily_token_usage (
2
2
  id varchar(21) not null,
3
- tenant_id varchar(21) not null
4
- references tenants (id) on update cascade on delete cascade,
3
+ tenant_id varchar(21) not null,
5
4
  usage bigint not null default(0),
5
+ user_token_usage bigint not null default(0),
6
+ m2m_token_usage bigint not null default(0),
6
7
  date timestamptz not null,
7
8
  primary key (id)
8
9
  );
package/tables/logs.sql CHANGED
@@ -10,9 +10,6 @@ create table logs (
10
10
  primary key (id)
11
11
  );
12
12
 
13
- create index logs__id
14
- on logs (tenant_id, id);
15
-
16
13
  create index logs__key
17
14
  on logs (tenant_id, key);
18
15
 
@@ -24,3 +21,6 @@ create index logs__application_id
24
21
 
25
22
  create index logs__hook_id
26
23
  on logs (tenant_id, (payload->>'hookId'));
24
+
25
+ create index logs__created_at_id
26
+ on logs (tenant_id, created_at, id);
@@ -12,7 +12,7 @@ create table saml_application_sessions (
12
12
  /** The identifier of the OIDC auth request state. */
13
13
  oidc_state varchar(32),
14
14
  /** The relay state of the SAML auth request. */
15
- relay_state varchar(256),
15
+ relay_state varchar(512),
16
16
  /** The raw request of the SAML auth request. */
17
17
  raw_auth_request text not null,
18
18
  created_at timestamptz not null default(now()),