@superinterface/server 1.0.42 → 1.0.44
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_btwyo7 +1 -1
- package/.next/fallback-build-manifest.json +2 -2
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +1 -1
- package/.next/server/chunks/[root-of-the-server]__3a25f63d._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__3a25f63d._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__4d829477._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__4d829477._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__6169c901._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__6169c901._.js.map +1 -1
- package/.next/server/chunks/supercorp_superinterface_bebd2c96._.js +2 -2
- package/.next/server/chunks/supercorp_superinterface_bebd2c96._.js.map +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/trace +1 -1
- package/dist/lib/tasks/getScheduleOccurrences.d.ts +10 -0
- package/dist/lib/tasks/getScheduleOccurrences.d.ts.map +1 -0
- package/dist/lib/tasks/getScheduleOccurrences.js +301 -0
- package/dist/lib/tasks/getTaskScheduleConflict.d.ts.map +1 -1
- package/dist/lib/tasks/getTaskScheduleConflict.js +20 -27
- package/dist/lib/tools/tools/index.d.ts.map +1 -1
- package/dist/lib/tools/tools/index.js +2 -13
- package/package.json +1 -1
- package/scripts/cleanupTaskConflicts.ts +95 -31
- /package/.next/static/{lW_3DHiIf_IPb1ovDKNtb → DQromUo_Z941kWJSSM2pu}/_buildManifest.js +0 -0
- /package/.next/static/{lW_3DHiIf_IPb1ovDKNtb → DQromUo_Z941kWJSSM2pu}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{lW_3DHiIf_IPb1ovDKNtb → DQromUo_Z941kWJSSM2pu}/_ssgManifest.js +0 -0
|
@@ -3,6 +3,7 @@ import utc from 'dayjs/plugin/utc'
|
|
|
3
3
|
import { PrismaClient } from '@prisma/client'
|
|
4
4
|
import { qstash } from '@/lib/upstash/qstash'
|
|
5
5
|
import { FIFTEEN_MINUTES_IN_MS } from '@/lib/tasks/getTaskScheduleConflict'
|
|
6
|
+
import { getScheduleOccurrences } from '@/lib/tasks/getScheduleOccurrences'
|
|
6
7
|
import { TaskScheduleConflictError } from '@/lib/errors'
|
|
7
8
|
import { loadPrismaClient } from './utils/loadPrisma'
|
|
8
9
|
|
|
@@ -25,12 +26,8 @@ type TaskWithDates = Awaited<
|
|
|
25
26
|
ReturnType<PrismaClient['task']['findMany']>
|
|
26
27
|
>[number]
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
if (typeof start !== 'string') return null
|
|
31
|
-
const parsed = dayjs(start).utc()
|
|
32
|
-
return parsed.isValid() ? parsed : null
|
|
33
|
-
}
|
|
29
|
+
const LOOKAHEAD_DAYS = 365
|
|
30
|
+
const MAX_OCCURRENCES = 120
|
|
34
31
|
|
|
35
32
|
const getLastTouchedAt = (task: TaskWithDates) => {
|
|
36
33
|
const updated = dayjs(task.updatedAt).utc()
|
|
@@ -85,6 +82,15 @@ const main = async () => {
|
|
|
85
82
|
const options = parseCliOptions()
|
|
86
83
|
const dryRunLabel = options.dryRun ? '[DRY RUN]' : '[APPLY]'
|
|
87
84
|
|
|
85
|
+
if (!options.dryRun && !process.env.QSTASH_TOKEN) {
|
|
86
|
+
console.error(
|
|
87
|
+
`${dryRunLabel} Missing required environment variable QSTASH_TOKEN. Aborting.`,
|
|
88
|
+
)
|
|
89
|
+
await prisma.$disconnect()
|
|
90
|
+
process.exitCode = 1
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
console.log(
|
|
89
95
|
`${dryRunLabel} Starting duplicate task audit – ` +
|
|
90
96
|
`${TaskScheduleConflictError.defaultMessage}`,
|
|
@@ -95,6 +101,11 @@ const main = async () => {
|
|
|
95
101
|
orderBy: { createdAt: 'asc' },
|
|
96
102
|
})
|
|
97
103
|
|
|
104
|
+
if (!tasks.length) {
|
|
105
|
+
console.log(`${dryRunLabel} No tasks found – nothing to audit.`)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
const groups = new Map<string, TaskWithDates[]>()
|
|
99
110
|
for (const task of tasks) {
|
|
100
111
|
const key = `${task.threadId}::${task.key}`
|
|
@@ -103,35 +114,60 @@ const main = async () => {
|
|
|
103
114
|
groups.set(key, list)
|
|
104
115
|
}
|
|
105
116
|
|
|
117
|
+
console.log(
|
|
118
|
+
`${dryRunLabel} Loaded ${tasks.length} task(s) across ${groups.size} group(s).`,
|
|
119
|
+
)
|
|
120
|
+
|
|
106
121
|
let conflictGroupCount = 0
|
|
107
122
|
let candidateDeleteCount = 0
|
|
123
|
+
let processedTasks = 0
|
|
124
|
+
let processedGroups = 0
|
|
108
125
|
|
|
109
126
|
for (const [groupKey, taskGroup] of groups.entries()) {
|
|
127
|
+
processedGroups += 1
|
|
128
|
+
processedTasks += taskGroup.length
|
|
129
|
+
|
|
130
|
+
const percentComplete = Math.round((processedTasks / tasks.length) * 100)
|
|
131
|
+
if (
|
|
132
|
+
processedGroups === 1 ||
|
|
133
|
+
processedGroups === groups.size ||
|
|
134
|
+
processedGroups % 10 === 0
|
|
135
|
+
) {
|
|
136
|
+
console.log(
|
|
137
|
+
`${dryRunLabel} Progress: ${processedTasks}/${tasks.length} task(s) checked (${percentComplete}%).`,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
110
141
|
if (taskGroup.length < 2) continue
|
|
111
142
|
|
|
112
|
-
const
|
|
113
|
-
.map((task) => ({
|
|
114
|
-
task,
|
|
115
|
-
start: getScheduleStart(task),
|
|
116
|
-
}))
|
|
117
|
-
.sort((a, b) => {
|
|
118
|
-
if (!a.start && !b.start) return 0
|
|
119
|
-
if (!a.start) return 1
|
|
120
|
-
if (!b.start) return -1
|
|
121
|
-
return a.start.valueOf() - b.start.valueOf()
|
|
122
|
-
})
|
|
143
|
+
const occurrencesMap = new Map<string, dayjs.Dayjs[]>()
|
|
123
144
|
|
|
124
|
-
const
|
|
125
|
-
.map((
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
const taskEntries = taskGroup
|
|
146
|
+
.map((task) => {
|
|
147
|
+
const occurrences = getScheduleOccurrences(
|
|
148
|
+
task.schedule as PrismaJson.TaskSchedule,
|
|
149
|
+
{
|
|
150
|
+
lookAheadDays: LOOKAHEAD_DAYS,
|
|
151
|
+
maxOccurrences: MAX_OCCURRENCES,
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
occurrencesMap.set(task.id, occurrences)
|
|
155
|
+
return {
|
|
156
|
+
task,
|
|
157
|
+
occurrences,
|
|
132
158
|
}
|
|
133
|
-
return true
|
|
134
159
|
})
|
|
160
|
+
.map((entry, index) => ({ ...entry, index }))
|
|
161
|
+
|
|
162
|
+
const validEntries = taskEntries.filter((entry) => {
|
|
163
|
+
if (!entry.occurrences.length) {
|
|
164
|
+
console.log(
|
|
165
|
+
`${dryRunLabel} Skipping task without upcoming occurrences: ${entry.task.id}`,
|
|
166
|
+
)
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
return true
|
|
170
|
+
})
|
|
135
171
|
|
|
136
172
|
if (validEntries.length < 2) continue
|
|
137
173
|
|
|
@@ -148,13 +184,28 @@ const main = async () => {
|
|
|
148
184
|
parent[rootB] = rootA
|
|
149
185
|
}
|
|
150
186
|
|
|
187
|
+
const hasConflictWithinWindow = (a: dayjs.Dayjs[], b: dayjs.Dayjs[]) => {
|
|
188
|
+
let i = 0
|
|
189
|
+
let j = 0
|
|
190
|
+
while (i < a.length && j < b.length) {
|
|
191
|
+
const diff = a[i].valueOf() - b[j].valueOf()
|
|
192
|
+
if (Math.abs(diff) < FIFTEEN_MINUTES_IN_MS) return true
|
|
193
|
+
if (diff < 0) {
|
|
194
|
+
i += 1
|
|
195
|
+
} else {
|
|
196
|
+
j += 1
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return false
|
|
200
|
+
}
|
|
201
|
+
|
|
151
202
|
for (let i = 0; i < validEntries.length; i += 1) {
|
|
152
203
|
const current = validEntries[i]
|
|
153
204
|
for (let j = i + 1; j < validEntries.length; j += 1) {
|
|
154
205
|
const compare = validEntries[j]
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
206
|
+
if (
|
|
207
|
+
hasConflictWithinWindow(current.occurrences, compare.occurrences)
|
|
208
|
+
) {
|
|
158
209
|
union(i, j)
|
|
159
210
|
}
|
|
160
211
|
}
|
|
@@ -180,7 +231,7 @@ const main = async () => {
|
|
|
180
231
|
for (const cluster of overlapping) {
|
|
181
232
|
const evaluatedCluster = cluster.map((task) => ({
|
|
182
233
|
task,
|
|
183
|
-
|
|
234
|
+
occurrences: occurrencesMap.get(task.id) ?? [],
|
|
184
235
|
lastTouchedAt: getLastTouchedAt(task),
|
|
185
236
|
}))
|
|
186
237
|
|
|
@@ -203,9 +254,15 @@ const main = async () => {
|
|
|
203
254
|
)
|
|
204
255
|
console.log(' Conflicting tasks:')
|
|
205
256
|
for (const item of evaluatedCluster) {
|
|
257
|
+
const nextOccurrence = item.occurrences[0]?.toISOString() ?? 'n/a'
|
|
258
|
+
const preview =
|
|
259
|
+
item.occurrences
|
|
260
|
+
.slice(0, 3)
|
|
261
|
+
.map((occurrence) => occurrence.toISOString())
|
|
262
|
+
.join(', ') || 'n/a'
|
|
206
263
|
const statusSuffix = '⚠️ violates window'
|
|
207
264
|
console.log(
|
|
208
|
-
` • ${item.task.id} |
|
|
265
|
+
` • ${item.task.id} | next=${nextOccurrence} | upcoming=[${preview}] | ` +
|
|
209
266
|
`createdAt=${item.task.createdAt.toISOString()} | updatedAt=${item.task.updatedAt.toISOString()} ${statusSuffix}`,
|
|
210
267
|
)
|
|
211
268
|
}
|
|
@@ -218,6 +275,12 @@ const main = async () => {
|
|
|
218
275
|
dryRunLabel,
|
|
219
276
|
})
|
|
220
277
|
|
|
278
|
+
if (cancelResult.status === 'error') {
|
|
279
|
+
throw new Error(
|
|
280
|
+
'QStash cancellation failed; rerun after resolving token/availability issues.',
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
221
284
|
await prisma.task.delete({ where: { id: candidate.task.id } })
|
|
222
285
|
console.log(
|
|
223
286
|
` Deleted: ${candidate.task.id} (qstash: ${candidate.task.qstashMessageId ?? 'none'} | cancel=${cancelResult.status})`,
|
|
@@ -227,6 +290,7 @@ const main = async () => {
|
|
|
227
290
|
` Failed to delete ${candidate.task.id}:`,
|
|
228
291
|
(error as Error).message,
|
|
229
292
|
)
|
|
293
|
+
throw error
|
|
230
294
|
}
|
|
231
295
|
}
|
|
232
296
|
} else {
|
|
File without changes
|
/package/.next/static/{lW_3DHiIf_IPb1ovDKNtb → DQromUo_Z941kWJSSM2pu}/_clientMiddlewareManifest.json
RENAMED
|
File without changes
|
|
File without changes
|