@jant/core 0.3.43 → 0.3.45
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/{app-GbfwoeDJ.js → app-C-L7wL6o.js} +485 -452
- package/dist/app-Hvqe7Ks_.js +5 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/client-DDs6NzB3.css +2 -0
- package/dist/client/_assets/{client-auth-CXILhW1b.js → client-auth-Dcon89Av.js} +30 -11
- package/dist/client/_assets/{client-D95FNDg5.js → client-dSfWfMe9.js} +7 -7
- package/dist/{github-sync-7y_nTXx1.js → github-sync-CQ1x271f.js} +3 -0
- package/dist/index.js +4 -87
- package/dist/node.js +3 -3
- package/package.json +1 -1
- package/src/client/components/jant-compose-dialog.ts +87 -9
- package/src/client/components/jant-compose-editor.ts +5 -1
- package/src/client/components/jant-post-menu.ts +23 -5
- package/src/client/compose-bridge.ts +2 -1
- package/src/client/toast.ts +29 -2
- package/src/client/upload-session.ts +1 -1
- package/src/db/migrations/0019_bored_magus.sql +2 -0
- package/src/db/migrations/0020_free_zaladane.sql +1 -0
- package/src/db/migrations/meta/0019_snapshot.json +2238 -0
- package/src/db/migrations/meta/0020_snapshot.json +2129 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/migrations/pg/0017_bright_beyonder.sql +2 -0
- package/src/db/migrations/pg/0018_red_warlock.sql +1 -0
- package/src/db/migrations/pg/meta/0017_snapshot.json +2862 -0
- package/src/db/migrations/pg/meta/0018_snapshot.json +2739 -0
- package/src/db/migrations/pg/meta/_journal.json +14 -0
- package/src/db/pg/schema.ts +4 -30
- package/src/db/schema.ts +4 -39
- package/src/i18n/locales/public/en.po +10 -5
- package/src/i18n/locales/public/en.ts +1 -1
- package/src/i18n/locales/public/zh-Hans.po +10 -5
- package/src/i18n/locales/public/zh-Hans.ts +1 -1
- package/src/i18n/locales/public/zh-Hant.po +10 -5
- package/src/i18n/locales/public/zh-Hant.ts +1 -1
- package/src/index.ts +0 -3
- package/src/lib/__tests__/resolve-config.test.ts +4 -4
- package/src/lib/__tests__/startup-config.test.ts +27 -2
- package/src/lib/constants.ts +1 -0
- package/src/lib/github-sync-trigger.ts +7 -51
- package/src/lib/icons.ts +37 -0
- package/src/lib/startup-config.ts +53 -6
- package/src/routes/api/github-sync.tsx +36 -14
- package/src/routes/api/internal/sites.ts +1 -0
- package/src/routes/pages/home.tsx +2 -0
- package/src/routes/pages/latest.tsx +2 -0
- package/src/runtime/__tests__/readiness.test.ts +34 -0
- package/src/runtime/readiness.ts +8 -4
- package/src/services/__tests__/collection.test.ts +13 -11
- package/src/services/__tests__/site-admin.test.ts +85 -0
- package/src/services/github-sync.ts +6 -0
- package/src/services/site-admin.ts +66 -1
- package/src/styles/components.css +14 -0
- package/src/styles/ui.css +109 -0
- package/src/types/bindings.ts +0 -2
- package/src/types/config.ts +1 -1
- package/src/types/props.ts +2 -0
- package/src/ui/__tests__/font-themes.test.ts +2 -2
- package/src/ui/dash/settings/SettingsRootContent.tsx +17 -17
- package/src/ui/feed/LinkCard.tsx +3 -20
- package/src/ui/feed/LinkPreview.tsx +5 -19
- package/src/ui/feed/PostStatusBadges.tsx +4 -38
- package/src/ui/font-themes.ts +17 -17
- package/src/ui/layouts/BaseLayout.tsx +14 -29
- package/src/ui/pages/HomePage.tsx +21 -5
- package/src/ui/shared/DecorativeQuoteMark.tsx +2 -13
- package/src/ui/shared/Icon.tsx +60 -0
- package/src/ui/shared/IconSprite.tsx +57 -0
- package/src/ui/shared/PostFooter.tsx +6 -62
- package/src/ui/shared/custom-icons.ts +132 -0
- package/src/ui/shared/icon-collector.ts +37 -0
- package/dist/app-Ctl0T0zO.js +0 -5
- package/dist/client/_assets/client-C_kImWZj.css +0 -2
- package/src/lib/github-sync-queue-handler.ts +0 -69
- package/src/lib/github-sync-worker.ts +0 -72
- package/src/lib/job-queue-cf.ts +0 -18
- package/src/lib/job-queue-db.ts +0 -149
- package/src/lib/job-queue.ts +0 -35
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare Queue batch handler for GitHub Sync jobs.
|
|
3
|
-
*
|
|
4
|
-
* This module bridges the CF Queue consumer interface with the
|
|
5
|
-
* generic sync worker. It creates a runtime per-batch to access
|
|
6
|
-
* services and configuration.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createRequestRuntime } from "../runtime/index.js";
|
|
10
|
-
import { processGitHubSyncJob } from "./github-sync-worker.js";
|
|
11
|
-
import { getGitHubAppConfig } from "./env.js";
|
|
12
|
-
import type { JobPayload } from "./job-queue.js";
|
|
13
|
-
import type { Bindings } from "../types/bindings.js";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Handle a batch of messages from a Cloudflare Queue.
|
|
17
|
-
*
|
|
18
|
-
* Each message is expected to be a `JobPayload` object.
|
|
19
|
-
*/
|
|
20
|
-
export async function handleQueueBatch(
|
|
21
|
-
batch: MessageBatch<unknown>,
|
|
22
|
-
env: Record<string, unknown>,
|
|
23
|
-
): Promise<void> {
|
|
24
|
-
// We need a runtime to access services. Use a synthetic URL since
|
|
25
|
-
// queue handlers don't have an incoming request.
|
|
26
|
-
const siteOrigin =
|
|
27
|
-
typeof env.SITE_ORIGIN === "string" && env.SITE_ORIGIN
|
|
28
|
-
? env.SITE_ORIGIN
|
|
29
|
-
: "http://localhost";
|
|
30
|
-
|
|
31
|
-
const runtime = await createRequestRuntime(env as Bindings, siteOrigin);
|
|
32
|
-
|
|
33
|
-
for (const message of batch.messages) {
|
|
34
|
-
const payload = message.body as JobPayload;
|
|
35
|
-
try {
|
|
36
|
-
await processGitHubSyncJob(
|
|
37
|
-
payload,
|
|
38
|
-
runtime.services,
|
|
39
|
-
payload.siteId,
|
|
40
|
-
{
|
|
41
|
-
siteName: "",
|
|
42
|
-
siteUrl: siteOrigin,
|
|
43
|
-
siteDescription: "",
|
|
44
|
-
siteLanguage: "",
|
|
45
|
-
showJantBrandingOnHome: false,
|
|
46
|
-
homeDefaultView: "",
|
|
47
|
-
mainRssFeed: "",
|
|
48
|
-
siteFooter: "",
|
|
49
|
-
showHeaderAvatar: false,
|
|
50
|
-
siteAvatarUrl: "",
|
|
51
|
-
themeId: "",
|
|
52
|
-
defaultThemeId: "",
|
|
53
|
-
fontThemeId: "",
|
|
54
|
-
themeMode: "",
|
|
55
|
-
noindex: false,
|
|
56
|
-
navItems: [],
|
|
57
|
-
pageSize: 50,
|
|
58
|
-
archivePageSize: 50,
|
|
59
|
-
rssFeedLimit: 50,
|
|
60
|
-
},
|
|
61
|
-
runtime.storage,
|
|
62
|
-
getGitHubAppConfig(env),
|
|
63
|
-
);
|
|
64
|
-
message.ack();
|
|
65
|
-
} catch {
|
|
66
|
-
message.retry();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub Sync Worker
|
|
3
|
-
*
|
|
4
|
-
* Processes queued sync jobs. Push jobs always run a full sync.
|
|
5
|
-
* After completing, re-checks the pending flag to catch changes
|
|
6
|
-
* that arrived during execution.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createGitHubSyncService } from "../services/github-sync.js";
|
|
10
|
-
import type { SiteConfig } from "../services/export.js";
|
|
11
|
-
import type { Services } from "../services/index.js";
|
|
12
|
-
import type { StorageDriver } from "../lib/storage.js";
|
|
13
|
-
import type { GitHubAppEnvConfig } from "./env.js";
|
|
14
|
-
import type { JobPayload } from "./job-queue.js";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Process a single GitHub Sync job.
|
|
18
|
-
*/
|
|
19
|
-
export async function processGitHubSyncJob(
|
|
20
|
-
payload: JobPayload,
|
|
21
|
-
services: Services,
|
|
22
|
-
siteId: string,
|
|
23
|
-
siteConfig: SiteConfig,
|
|
24
|
-
storage?: StorageDriver | null,
|
|
25
|
-
githubApp?: GitHubAppEnvConfig | null,
|
|
26
|
-
): Promise<void> {
|
|
27
|
-
const syncService = createGitHubSyncService(
|
|
28
|
-
{
|
|
29
|
-
posts: services.posts,
|
|
30
|
-
paths: services.paths,
|
|
31
|
-
collections: services.collections,
|
|
32
|
-
media: services.media,
|
|
33
|
-
settings: services.settings,
|
|
34
|
-
},
|
|
35
|
-
siteId,
|
|
36
|
-
siteConfig,
|
|
37
|
-
{ storage, githubApp },
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
if (payload.kind === "github-sync-push") {
|
|
41
|
-
// Clear pending flag before running so new triggers during
|
|
42
|
-
// execution will set it again.
|
|
43
|
-
await services.settings.set("GITHUB_SYNC_PENDING", "");
|
|
44
|
-
|
|
45
|
-
await syncService.pushFullSync();
|
|
46
|
-
|
|
47
|
-
// If the flag was re-set during execution, run once more
|
|
48
|
-
const stillPending = await services.settings.get("GITHUB_SYNC_PENDING");
|
|
49
|
-
if (stillPending === "true") {
|
|
50
|
-
await services.settings.set("GITHUB_SYNC_PENDING", "");
|
|
51
|
-
await syncService.pushFullSync();
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (payload.kind === "github-sync-pull") {
|
|
57
|
-
const webhookPayload = payload.data as {
|
|
58
|
-
ref: string;
|
|
59
|
-
before: string;
|
|
60
|
-
after: string;
|
|
61
|
-
commits: Array<{
|
|
62
|
-
id: string;
|
|
63
|
-
message: string;
|
|
64
|
-
added: string[];
|
|
65
|
-
modified: string[];
|
|
66
|
-
removed: string[];
|
|
67
|
-
}>;
|
|
68
|
-
};
|
|
69
|
-
await syncService.handleWebhookPush(webhookPayload);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
}
|
package/src/lib/job-queue-cf.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare Queue adapter for the job queue.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { JobPayload, JobQueue } from "./job-queue.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Create a job queue backed by a Cloudflare Queue binding.
|
|
9
|
-
*
|
|
10
|
-
* @param queue - The CF Queue binding (e.g. `env.GITHUB_SYNC_QUEUE`)
|
|
11
|
-
*/
|
|
12
|
-
export function createCfJobQueue(queue: Queue): JobQueue {
|
|
13
|
-
return {
|
|
14
|
-
async enqueue(payload: JobPayload) {
|
|
15
|
-
await queue.send(payload);
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
}
|
package/src/lib/job-queue-db.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database-backed job queue.
|
|
3
|
-
*
|
|
4
|
-
* Used as a fallback when Cloudflare Queues are not available (e.g. Node/Postgres
|
|
5
|
-
* or self-hosted deployments). Jobs are stored in the `sync_job` table and
|
|
6
|
-
* processed via periodic polling.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { eq, and, lte, sql } from "drizzle-orm";
|
|
10
|
-
import type { Database } from "../db/index.js";
|
|
11
|
-
import type { DatabaseSchema } from "../db/schema-bundle.js";
|
|
12
|
-
import type { JobPayload, JobQueue } from "./job-queue.js";
|
|
13
|
-
import { typeidUnboxed } from "typeid-js";
|
|
14
|
-
import { now as unixNow } from "./time.js";
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Queue (producer)
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Create a job queue backed by a database table.
|
|
22
|
-
*
|
|
23
|
-
* @param db - Drizzle database instance
|
|
24
|
-
* @param schema - Database schema bundle
|
|
25
|
-
*/
|
|
26
|
-
export function createDbJobQueue(
|
|
27
|
-
db: Database,
|
|
28
|
-
schema: DatabaseSchema,
|
|
29
|
-
): JobQueue {
|
|
30
|
-
return {
|
|
31
|
-
async enqueue(payload: JobPayload) {
|
|
32
|
-
const now = unixNow();
|
|
33
|
-
await db.insert(schema.syncJobs).values({
|
|
34
|
-
id: typeidUnboxed("job"),
|
|
35
|
-
siteId: payload.siteId,
|
|
36
|
-
kind: payload.kind,
|
|
37
|
-
payload: JSON.stringify(payload.data),
|
|
38
|
-
status: "pending",
|
|
39
|
-
attempts: 0,
|
|
40
|
-
maxAttempts: 3,
|
|
41
|
-
createdAt: now,
|
|
42
|
-
updatedAt: now,
|
|
43
|
-
lockedUntil: null,
|
|
44
|
-
});
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Worker (consumer) — poll-based dequeue
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
|
|
53
|
-
export interface DbJobWorkerOptions {
|
|
54
|
-
db: Database;
|
|
55
|
-
schema: DatabaseSchema;
|
|
56
|
-
/** Called to process each dequeued job. */
|
|
57
|
-
handler: (payload: JobPayload) => Promise<void>;
|
|
58
|
-
/** Lock duration in seconds. Defaults to 60. */
|
|
59
|
-
lockDurationSec?: number;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Dequeue and process a single pending job.
|
|
64
|
-
*
|
|
65
|
-
* @returns `true` if a job was processed, `false` if the queue was empty.
|
|
66
|
-
*/
|
|
67
|
-
export async function dequeueAndProcess(
|
|
68
|
-
opts: DbJobWorkerOptions,
|
|
69
|
-
): Promise<boolean> {
|
|
70
|
-
const { db, schema, handler, lockDurationSec = 60 } = opts;
|
|
71
|
-
const now = unixNow();
|
|
72
|
-
|
|
73
|
-
// Find one eligible job (pending or timed-out lock)
|
|
74
|
-
const [job] = await db
|
|
75
|
-
.select()
|
|
76
|
-
.from(schema.syncJobs)
|
|
77
|
-
.where(
|
|
78
|
-
and(
|
|
79
|
-
eq(schema.syncJobs.status, "pending"),
|
|
80
|
-
sql`(${schema.syncJobs.lockedUntil} IS NULL OR ${schema.syncJobs.lockedUntil} <= ${now})`,
|
|
81
|
-
),
|
|
82
|
-
)
|
|
83
|
-
.orderBy(schema.syncJobs.createdAt)
|
|
84
|
-
.limit(1);
|
|
85
|
-
|
|
86
|
-
if (!job) return false;
|
|
87
|
-
|
|
88
|
-
// Lock the job
|
|
89
|
-
await db
|
|
90
|
-
.update(schema.syncJobs)
|
|
91
|
-
.set({
|
|
92
|
-
status: "processing",
|
|
93
|
-
lockedUntil: now + lockDurationSec,
|
|
94
|
-
attempts: job.attempts + 1,
|
|
95
|
-
updatedAt: now,
|
|
96
|
-
})
|
|
97
|
-
.where(eq(schema.syncJobs.id, job.id));
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const payload: JobPayload = {
|
|
101
|
-
kind: job.kind as JobPayload["kind"],
|
|
102
|
-
siteId: job.siteId,
|
|
103
|
-
data: JSON.parse(job.payload) as Record<string, unknown>,
|
|
104
|
-
};
|
|
105
|
-
await handler(payload);
|
|
106
|
-
|
|
107
|
-
// Mark completed
|
|
108
|
-
await db
|
|
109
|
-
.update(schema.syncJobs)
|
|
110
|
-
.set({ status: "completed", updatedAt: unixNow() })
|
|
111
|
-
.where(eq(schema.syncJobs.id, job.id));
|
|
112
|
-
} catch {
|
|
113
|
-
const updatedAttempts = job.attempts + 1;
|
|
114
|
-
const maxAttempts = job.maxAttempts;
|
|
115
|
-
|
|
116
|
-
await db
|
|
117
|
-
.update(schema.syncJobs)
|
|
118
|
-
.set({
|
|
119
|
-
status: updatedAttempts >= maxAttempts ? "failed" : "pending",
|
|
120
|
-
lockedUntil: null,
|
|
121
|
-
updatedAt: unixNow(),
|
|
122
|
-
})
|
|
123
|
-
.where(eq(schema.syncJobs.id, job.id));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Clean up completed and failed jobs older than the given age.
|
|
131
|
-
*
|
|
132
|
-
* @param maxAgeSec - Maximum age in seconds. Defaults to 7 days.
|
|
133
|
-
*/
|
|
134
|
-
export async function cleanupOldJobs(
|
|
135
|
-
db: Database,
|
|
136
|
-
schema: DatabaseSchema,
|
|
137
|
-
maxAgeSec = 7 * 24 * 3600,
|
|
138
|
-
): Promise<number> {
|
|
139
|
-
const cutoff = unixNow() - maxAgeSec;
|
|
140
|
-
const result = await db
|
|
141
|
-
.delete(schema.syncJobs)
|
|
142
|
-
.where(
|
|
143
|
-
and(
|
|
144
|
-
sql`${schema.syncJobs.status} IN ('completed', 'failed')`,
|
|
145
|
-
lte(schema.syncJobs.updatedAt, cutoff),
|
|
146
|
-
),
|
|
147
|
-
);
|
|
148
|
-
return (result as { rowsAffected?: number }).rowsAffected ?? 0;
|
|
149
|
-
}
|
package/src/lib/job-queue.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Job Queue abstraction.
|
|
3
|
-
*
|
|
4
|
-
* Provides a unified interface for enqueueing background jobs across
|
|
5
|
-
* Cloudflare Workers (CF Queues) and Node/Postgres (DB-backed polling).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
// Types
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
export type JobKind = "github-sync-push" | "github-sync-pull";
|
|
13
|
-
|
|
14
|
-
export interface JobPayload {
|
|
15
|
-
kind: JobKind;
|
|
16
|
-
siteId: string;
|
|
17
|
-
data: Record<string, unknown>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface JobQueue {
|
|
21
|
-
/** Enqueue a job for async background processing. */
|
|
22
|
-
enqueue(payload: JobPayload): Promise<void>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Noop Queue (used when no queue backend is available)
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A no-op queue that silently drops jobs.
|
|
31
|
-
* Used as a fallback when neither CF Queue nor DB queue is configured.
|
|
32
|
-
*/
|
|
33
|
-
export const noopQueue: JobQueue = {
|
|
34
|
-
async enqueue() {},
|
|
35
|
-
};
|