@stacksjs/commerce 0.75.480 → 0.75.483
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors/fetch.d.ts +29 -0
- package/dist/errors/fetch.js +9 -1
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.js +1 -1
- package/package.json +14 -14
package/dist/errors/fetch.d.ts
CHANGED
|
@@ -11,6 +11,17 @@ export declare function fetchAll(): Promise<ErrorRecord[]>;
|
|
|
11
11
|
* Same errors are aggregated instead of creating duplicate entries
|
|
12
12
|
*/
|
|
13
13
|
export declare function fetchGrouped(): Promise<GroupedError[]>;
|
|
14
|
+
/**
|
|
15
|
+
* One page of error groups, most recently seen first, read by the database.
|
|
16
|
+
*
|
|
17
|
+
* `fetchGrouped` reads every error to group them, and the Errors page used to
|
|
18
|
+
* take that whole list to show ten (stacksjs/stacks#3195). This ranks each
|
|
19
|
+
* group's occurrences newest first in SQL, so the group's state, count and
|
|
20
|
+
* first and last seen come back with only the page's groups, and fetches just
|
|
21
|
+
* those groups' stacks. Window functions are in every database Stacks runs
|
|
22
|
+
* on: SQLite 3.25, MySQL 8 and PostgreSQL.
|
|
23
|
+
*/
|
|
24
|
+
export declare function fetchGroupedPage(query: ErrorGroupQuery): Promise<GroupedErrorPage>;
|
|
14
25
|
/**
|
|
15
26
|
* Fetch errors for a specific group (by type and message)
|
|
16
27
|
*/
|
|
@@ -59,5 +70,23 @@ export declare interface ErrorStats {
|
|
|
59
70
|
last_24h: number
|
|
60
71
|
trend: number
|
|
61
72
|
}
|
|
73
|
+
/** A page of groups asks for these. */
|
|
74
|
+
export declare interface ErrorGroupQuery {
|
|
75
|
+
page: number
|
|
76
|
+
perPage: number
|
|
77
|
+
search: string
|
|
78
|
+
status: ErrorResolution | 'all'
|
|
79
|
+
type: string
|
|
80
|
+
sort?: 'last_seen' | 'count' | 'type' | 'status'
|
|
81
|
+
direction?: 'asc' | 'desc'
|
|
82
|
+
}
|
|
83
|
+
export declare interface GroupedErrorPage {
|
|
84
|
+
groups: GroupedErrorSummary[]
|
|
85
|
+
total: number
|
|
86
|
+
page: number
|
|
87
|
+
types: string[]
|
|
88
|
+
}
|
|
62
89
|
export type ErrorResolution = 'unresolved' | 'resolved' | 'ignored';
|
|
90
|
+
/** One group on a page: `GroupedError` without every occurrence's id. */
|
|
91
|
+
export type GroupedErrorSummary = Omit<GroupedError, 'error_ids'>;
|
|
63
92
|
declare type GroupingRow = Pick<ErrorRecord, 'id' | 'type' | 'message' | 'resolution' | 'created_at'>;
|
package/dist/errors/fetch.js
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
import{db}from"@stacksjs/database/runtime";import{formatDate}from"@stacksjs/orm";export async function fetchById(id){return await db.selectFrom("errors").where("id","=",id).selectAll().executeTakeFirst()}export async function fetchAll(){return await db.selectFrom("errors").selectAll().orderBy("created_at","desc").execute()}export async function fetchGrouped(){const errors=await db.selectFrom("errors").select(["id","type","message","resolution","created_at"]).orderBy("created_at","desc").execute(),groupMap=new Map;for(const error of errors){const key=`${error.type}:${error.message}`;if(!groupMap.has(key))groupMap.set(key,{errors:[],firstSeen:new Date(error.created_at),lastSeen:new Date(error.created_at)});const group=groupMap.get(key);group.errors.push(error);const errorDate=new Date(error.created_at);if(errorDate<group.firstSeen)group.firstSeen=errorDate;if(errorDate>group.lastSeen)group.lastSeen=errorDate}const stacks=await fetchStacks([...groupMap.values()].flatMap((group)=>group.errors[0]?[group.errors[0].id]:[])),groupedErrors=[];for(const[_,group]of groupMap){const representative=group.errors[0];if(!representative)continue;const statusLabel=representative.resolution??"unresolved";groupedErrors.push({id:representative.id,type:representative.type,message:representative.message,stack:stacks.get(Number(representative.id))??null,status:statusLabel,count:group.errors.length,first_seen:group.firstSeen.toISOString(),last_seen:group.lastSeen.toISOString(),error_ids:group.errors.map((e)=>e.id)})}groupedErrors.sort((a,b)=>new Date(b.last_seen).getTime()-new Date(a.last_seen).getTime());return groupedErrors}
|
|
1
|
+
import{db,sqlHelpers}from"@stacksjs/database/runtime";import{env}from"@stacksjs/env";import{formatDate}from"@stacksjs/orm";export async function fetchById(id){return await db.selectFrom("errors").where("id","=",id).selectAll().executeTakeFirst()}export async function fetchAll(){return await db.selectFrom("errors").selectAll().orderBy("created_at","desc").execute()}export async function fetchGrouped(){const errors=await db.selectFrom("errors").select(["id","type","message","resolution","created_at"]).orderBy("created_at","desc").execute(),groupMap=new Map;for(const error of errors){const key=`${error.type}:${error.message}`;if(!groupMap.has(key))groupMap.set(key,{errors:[],firstSeen:new Date(error.created_at),lastSeen:new Date(error.created_at)});const group=groupMap.get(key);group.errors.push(error);const errorDate=new Date(error.created_at);if(errorDate<group.firstSeen)group.firstSeen=errorDate;if(errorDate>group.lastSeen)group.lastSeen=errorDate}const stacks=await fetchStacks([...groupMap.values()].flatMap((group)=>group.errors[0]?[group.errors[0].id]:[])),groupedErrors=[];for(const[_,group]of groupMap){const representative=group.errors[0];if(!representative)continue;const statusLabel=representative.resolution??"unresolved";groupedErrors.push({id:representative.id,type:representative.type,message:representative.message,stack:stacks.get(Number(representative.id))??null,status:statusLabel,count:group.errors.length,first_seen:group.firstSeen.toISOString(),last_seen:group.lastSeen.toISOString(),error_ids:group.errors.map((e)=>e.id)})}groupedErrors.sort((a,b)=>new Date(b.last_seen).getTime()-new Date(a.last_seen).getTime());return groupedErrors}function groupOrder(query){const direction=query.direction==="asc"?"ASC":"DESC";return`${{last_seen:`created_at ${direction}`,count:`occurrences ${direction}`,type:`LOWER(type) ${direction}`,status:`CASE WHEN resolution = 'resolved' THEN 1 WHEN resolution = 'ignored' THEN 2 ELSE 0 END ${direction}`}[query.sort??"last_seen"]}, created_at DESC, id DESC`}export async function fetchGroupedPage(query){const helpers=sqlHelpers(env.DB_CONNECTION||"sqlite"),values=[],bind=(value)=>{values.push(value);return helpers.param(values.length)},rowFilters=[];if(query.search){const term=`%${query.search}%`;rowFilters.push(`(LOWER(type) LIKE LOWER(${bind(term)}) OR LOWER(message) LIKE LOWER(${bind(term)}))`)}if(query.type)rowFilters.push(`type = ${bind(query.type)}`);const groupFilters=["group_rank = 1"];if(query.status==="unresolved")groupFilters.push(`(resolution = ${bind("unresolved")} OR resolution IS NULL)`);else if(query.status!=="all")groupFilters.push(`resolution = ${bind(query.status)}`);const ranked=`(
|
|
2
|
+
SELECT id, type, message, resolution, created_at,
|
|
3
|
+
ROW_NUMBER() OVER (PARTITION BY type, message ORDER BY created_at DESC, id DESC) AS group_rank,
|
|
4
|
+
COUNT(id) OVER (PARTITION BY type, message) AS occurrences,
|
|
5
|
+
MIN(created_at) OVER (PARTITION BY type, message) AS first_seen
|
|
6
|
+
FROM errors
|
|
7
|
+
${rowFilters.length>0?`WHERE ${rowFilters.join(" AND ")}`:""}
|
|
8
|
+
) ranked`,where=`WHERE ${groupFilters.join(" AND ")}`,[counted]=await db.unsafe(`SELECT COUNT(id) AS count FROM ${ranked} ${where}`,values).execute(),total=Number(counted?.count??0)||0,page=Math.min(query.page,Math.max(1,Math.ceil(total/query.perPage))),[rows,types]=await Promise.all([total===0?Promise.resolve([]):db.unsafe(`SELECT id, type, message, resolution, occurrences, first_seen, created_at AS last_seen FROM ${ranked} ${where}
|
|
9
|
+
ORDER BY ${groupOrder(query)} LIMIT ${Math.trunc(query.perPage)} OFFSET ${Math.trunc((page-1)*query.perPage)}`,values).execute(),db.unsafe("SELECT DISTINCT type FROM errors ORDER BY type").execute()]),stacks=await fetchStacks(rows.map((row)=>Number(row.id)));return{groups:rows.map((row)=>({id:Number(row.id),type:row.type,message:row.message,stack:stacks.get(Number(row.id))??null,status:row.resolution??"unresolved",count:Number(row.occurrences)||0,first_seen:new Date(row.first_seen).toISOString(),last_seen:new Date(row.last_seen).toISOString()})),total,page,types:types.map((row)=>String(row.type))}}const STACK_BATCH=500;async function fetchStacks(ids){const stacks=new Map;for(let start=0;start<ids.length;start+=STACK_BATCH){const rows=await db.selectFrom("errors").select(["id","stack"]).where("id","in",ids.slice(start,start+STACK_BATCH)).execute();for(const row of rows)stacks.set(Number(row.id),row.stack??null)}return stacks}export async function fetchByGroup(type,message){return await db.selectFrom("errors").where("type","=",type).where("message","=",message).selectAll().orderBy("created_at","desc").execute()}async function countSince(since){const row=await db.selectFrom("errors").where("created_at",">=",formatDate(since)).select(db.fn.count("id").as("count")).executeTakeFirst();return Number(row?.count??0)}export async function fetchStats(){const now=new Date,yesterday=new Date(now.getTime()-86400000),twoDaysAgo=new Date(now.getTime()-172800000),byResolution=await db.selectFrom("errors").select(["resolution",db.fn.count("id").as("count")]).groupBy("resolution").execute();let total=0,resolved=0,ignored=0;for(const row of byResolution){const count=Number(row.count);total+=count;if(row.resolution==="resolved")resolved+=count;else if(row.resolution==="ignored")ignored+=count}const last24h=await countSince(yesterday),previous24h=await countSince(twoDaysAgo)-last24h,trend=previous24h===0?last24h>0?100:0:Math.round((last24h-previous24h)/previous24h*100);return{total,unresolved:total-resolved-ignored,resolved,ignored,last_24h:last24h,trend}}export async function fetchTimeline(){const now=new Date,firstHour=new Date(now.getTime()-82800000);firstHour.setMinutes(0,0,0);const errors=await db.selectFrom("errors").select(["created_at"]).where("created_at",">=",formatDate(firstHour)).execute(),timeline=[];for(let i=23;i>=0;i--){const hourStart=new Date(now.getTime()-i*60*60*1000);hourStart.setMinutes(0,0,0);const hourEnd=new Date(hourStart.getTime()+3600000),count=errors.filter((e)=>{const date=new Date(e.created_at);return date>=hourStart&&date<hourEnd}).length;timeline.push({hour:hourStart.toISOString(),count})}return timeline}
|
package/dist/errors/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export type {
|
|
2
|
+
ErrorGroupQuery,
|
|
2
3
|
ErrorRecord,
|
|
3
4
|
ErrorStats,
|
|
4
5
|
GroupedError,
|
|
6
|
+
GroupedErrorPage,
|
|
7
|
+
GroupedErrorSummary,
|
|
5
8
|
} from './fetch';
|
|
6
9
|
export {
|
|
7
10
|
bulkDestroy,
|
|
@@ -13,6 +16,7 @@ export {
|
|
|
13
16
|
fetchByGroup,
|
|
14
17
|
fetchById,
|
|
15
18
|
fetchGrouped,
|
|
19
|
+
fetchGroupedPage,
|
|
16
20
|
fetchStats,
|
|
17
21
|
fetchTimeline,
|
|
18
22
|
} from './fetch';
|
package/dist/errors/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{bulkDestroy,destroy,destroyGroup}from"./destroy";export{fetchAll,fetchByGroup,fetchById,fetchGrouped,fetchStats,fetchTimeline}from"./fetch";export{bulkUpdateStatus,ignoreGroup,resolveGroup,unresolveGroup,update}from"./update";
|
|
1
|
+
export{bulkDestroy,destroy,destroyGroup}from"./destroy";export{fetchAll,fetchByGroup,fetchById,fetchGrouped,fetchGroupedPage,fetchStats,fetchTimeline}from"./fetch";export{bulkUpdateStatus,ignoreGroup,resolveGroup,unresolveGroup,update}from"./update";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/commerce",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.75.
|
|
4
|
+
"version": "0.75.483",
|
|
5
5
|
"description": "Stacks commerce utilities.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -57,19 +57,19 @@
|
|
|
57
57
|
"prepublishOnly": "bun run build"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@stacksjs/cache": "0.75.
|
|
61
|
-
"@stacksjs/config": "0.75.
|
|
62
|
-
"@stacksjs/database": "0.75.
|
|
63
|
-
"@stacksjs/env": "0.75.
|
|
64
|
-
"@stacksjs/error-handling": "0.75.
|
|
65
|
-
"@stacksjs/events": "0.75.
|
|
66
|
-
"@stacksjs/logging": "0.75.
|
|
67
|
-
"@stacksjs/orm": "0.75.
|
|
68
|
-
"@stacksjs/payments": "0.75.
|
|
69
|
-
"@stacksjs/realtime": "0.75.
|
|
70
|
-
"@stacksjs/slug": "0.75.
|
|
71
|
-
"@stacksjs/strings": "0.75.
|
|
72
|
-
"@stacksjs/validation": "0.75.
|
|
60
|
+
"@stacksjs/cache": "0.75.483",
|
|
61
|
+
"@stacksjs/config": "0.75.483",
|
|
62
|
+
"@stacksjs/database": "0.75.483",
|
|
63
|
+
"@stacksjs/env": "0.75.483",
|
|
64
|
+
"@stacksjs/error-handling": "0.75.483",
|
|
65
|
+
"@stacksjs/events": "0.75.483",
|
|
66
|
+
"@stacksjs/logging": "0.75.483",
|
|
67
|
+
"@stacksjs/orm": "0.75.483",
|
|
68
|
+
"@stacksjs/payments": "0.75.483",
|
|
69
|
+
"@stacksjs/realtime": "0.75.483",
|
|
70
|
+
"@stacksjs/slug": "0.75.483",
|
|
71
|
+
"@stacksjs/strings": "0.75.483",
|
|
72
|
+
"@stacksjs/validation": "0.75.483",
|
|
73
73
|
"ts-spreadsheets": "^0.2.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|