jowork 0.2.4 → 0.3.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.
- package/dist/{chunk-ROIINI33.js → chunk-4PIT2GZ4.js} +13 -1
- package/dist/{chunk-XLYRHKG6.js → chunk-54SD5GBF.js} +1 -1
- package/dist/chunk-63AMINQC.js +156 -0
- package/dist/{chunk-XAEGXSEO.js → chunk-74AHY7X6.js} +4 -0
- package/dist/{chunk-7U3SXINY.js → chunk-ATAUWJYD.js} +320 -50
- package/dist/chunk-DQW74UCN.js +671 -0
- package/dist/chunk-EYP6WMFF.js +153 -0
- package/dist/{chunk-JSTXMDXI.js → chunk-FCFZCZHR.js} +1 -1
- package/dist/chunk-FX6Z3QHV.js +34 -0
- package/dist/chunk-HENAABEL.js +419 -0
- package/dist/chunk-OXWWOKC7.js +201 -0
- package/dist/chunk-QGHJ45PL.js +661 -0
- package/dist/chunk-RO3KK5RC.js +132 -0
- package/dist/{chunk-JE6TOU7W.js → chunk-TFMF3EXE.js} +2 -7
- package/dist/{chunk-TN327MDF.js → chunk-VX662YLA.js} +3 -3
- package/dist/cli.js +338 -149
- package/dist/{config-AI6UIJJN.js → config-FH2XLN7A.js} +2 -2
- package/dist/content-reader-VPGTR2SF.js +10 -0
- package/dist/context-ZNI3WOB7.js +10 -0
- package/dist/{credential-store-ZRZCSRPC.js → credential-store-OS5ZY4OW.js} +2 -2
- package/dist/{feishu-A6YVFKEN.js → feishu-XW5T6ER2.js} +8 -3
- package/dist/{git-manager-N35XSG4Y.js → git-manager-RVWV2GSV.js} +2 -1
- package/dist/github-PQKAYTLO.js +11 -0
- package/dist/{paths-JXOMBYIT.js → paths-FFRET6F7.js} +7 -3
- package/dist/{server-5GVWN2NB.js → server-WEADPUST.js} +59 -66
- package/dist/{setup-IDQDPCEJ.js → setup-S2S2CHB2.js} +91 -32
- package/dist/sync-SRLFR5NA.js +21 -0
- package/dist/transport.js +6 -4
- package/package.json +1 -1
- package/src/dashboard/public/app.js +34 -8
- package/src/dashboard/public/style.css +14 -0
- package/dist/chunk-AIXKXEYS.js +0 -547
- package/dist/chunk-L5ZR7TSK.js +0 -82
- package/dist/chunk-LS2AJM5A.js +0 -163
- package/dist/chunk-QMOFQX7X.js +0 -612
- package/dist/chunk-YJWTKFWX.js +0 -451
- package/dist/github-SHWUFNYB.js +0 -10
- package/dist/sync-7V54N62M.js +0 -18
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createId
|
|
3
|
+
} from "./chunk-TFMF3EXE.js";
|
|
4
|
+
|
|
5
|
+
// src/sync/context.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
function contentHash(str) {
|
|
8
|
+
return createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
9
|
+
}
|
|
10
|
+
var SyncContext = class {
|
|
11
|
+
sqlite;
|
|
12
|
+
fileWriter;
|
|
13
|
+
logger;
|
|
14
|
+
// Prepared statements — created once, reused across all upsert calls
|
|
15
|
+
stmts;
|
|
16
|
+
constructor(sqlite, logger, fileWriter) {
|
|
17
|
+
this.sqlite = sqlite;
|
|
18
|
+
this.fileWriter = fileWriter;
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.stmts = {
|
|
21
|
+
checkExists: sqlite.prepare("SELECT id, content_hash FROM objects WHERE uri = ?"),
|
|
22
|
+
getByUri: sqlite.prepare("SELECT id, content_hash, title, summary, tags FROM objects WHERE uri = ?"),
|
|
23
|
+
insertObj: sqlite.prepare(`
|
|
24
|
+
INSERT INTO objects (id, source, source_type, uri, title, summary, tags, content_hash, file_path, last_synced_at, created_at)
|
|
25
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
26
|
+
`),
|
|
27
|
+
updateObj: sqlite.prepare(`
|
|
28
|
+
UPDATE objects SET title = ?, summary = ?, tags = ?, content_hash = ?, file_path = ?, last_synced_at = ?
|
|
29
|
+
WHERE id = ?
|
|
30
|
+
`),
|
|
31
|
+
insertFts: sqlite.prepare(`
|
|
32
|
+
INSERT INTO objects_fts(rowid, title, summary, tags, source, source_type, body_excerpt)
|
|
33
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
34
|
+
`),
|
|
35
|
+
deleteFts: sqlite.prepare("DELETE FROM objects_fts WHERE rowid = ?"),
|
|
36
|
+
getRowid: sqlite.prepare("SELECT rowid FROM objects WHERE id = ?"),
|
|
37
|
+
getCursor: sqlite.prepare("SELECT cursor FROM sync_cursors WHERE connector_id = ?"),
|
|
38
|
+
saveCursor: sqlite.prepare(
|
|
39
|
+
"INSERT OR REPLACE INTO sync_cursors (connector_id, cursor, last_synced_at) VALUES (?, ?, ?)"
|
|
40
|
+
)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* UPSERT an object: INSERT if new, UPDATE if content_hash changed, SKIP if unchanged.
|
|
45
|
+
* Handles SQLite objects + FTS5 + FileWriter in one call.
|
|
46
|
+
*/
|
|
47
|
+
upsertObject(input) {
|
|
48
|
+
const { source, sourceType, uri, title, content } = input;
|
|
49
|
+
const summary = input.summary ?? (content.length > 200 ? content.slice(0, 200) + "..." : content);
|
|
50
|
+
const tags = input.tags ? JSON.stringify(input.tags) : JSON.stringify([source, sourceType]);
|
|
51
|
+
const hash = contentHash(content);
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const createdAt = input.createdAt ?? now;
|
|
54
|
+
const existing = this.stmts.getByUri.get(uri);
|
|
55
|
+
if (existing) {
|
|
56
|
+
if (existing.content_hash === hash) {
|
|
57
|
+
return { id: existing.id, action: "skipped" };
|
|
58
|
+
}
|
|
59
|
+
let filePath2;
|
|
60
|
+
if (this.fileWriter) {
|
|
61
|
+
try {
|
|
62
|
+
filePath2 = this.fileWriter.writeObject(source, sourceType, {
|
|
63
|
+
id: existing.id,
|
|
64
|
+
title,
|
|
65
|
+
...input.fileMeta
|
|
66
|
+
}, input.fileContent ?? content);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.stmts.updateObj.run(title, summary, tags, hash, filePath2 ?? null, now, existing.id);
|
|
71
|
+
this.rebuildFts(existing.id, title, summary, tags, source, sourceType, content);
|
|
72
|
+
return { id: existing.id, action: "updated", filePath: filePath2 };
|
|
73
|
+
}
|
|
74
|
+
const id = createId("obj");
|
|
75
|
+
let filePath;
|
|
76
|
+
if (this.fileWriter) {
|
|
77
|
+
try {
|
|
78
|
+
filePath = this.fileWriter.writeObject(source, sourceType, {
|
|
79
|
+
id,
|
|
80
|
+
title,
|
|
81
|
+
...input.fileMeta
|
|
82
|
+
}, input.fileContent ?? content);
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
this.stmts.insertObj.run(
|
|
88
|
+
id,
|
|
89
|
+
source,
|
|
90
|
+
sourceType,
|
|
91
|
+
uri,
|
|
92
|
+
title,
|
|
93
|
+
summary,
|
|
94
|
+
tags,
|
|
95
|
+
hash,
|
|
96
|
+
filePath ?? null,
|
|
97
|
+
now,
|
|
98
|
+
createdAt
|
|
99
|
+
);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (String(err).includes("UNIQUE constraint")) {
|
|
102
|
+
return this.upsertObject(input);
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const rowid = this.stmts.getRowid.get(id);
|
|
108
|
+
if (rowid) {
|
|
109
|
+
const excerpt = content.length > 500 ? content.slice(0, 500) : content;
|
|
110
|
+
this.stmts.insertFts.run(rowid.rowid, title ?? "", summary ?? "", tags, source, sourceType, excerpt);
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
return { id, action: "inserted", filePath };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Batch upsert within a transaction. Returns counts.
|
|
118
|
+
*/
|
|
119
|
+
batchUpsert(items) {
|
|
120
|
+
let inserted = 0, updated = 0, skipped = 0;
|
|
121
|
+
const run = this.sqlite.transaction((batch) => {
|
|
122
|
+
for (const item of batch) {
|
|
123
|
+
const result = this.upsertObject(item);
|
|
124
|
+
switch (result.action) {
|
|
125
|
+
case "inserted":
|
|
126
|
+
inserted++;
|
|
127
|
+
break;
|
|
128
|
+
case "updated":
|
|
129
|
+
updated++;
|
|
130
|
+
break;
|
|
131
|
+
case "skipped":
|
|
132
|
+
skipped++;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
for (let i = 0; i < items.length; i += 100) {
|
|
138
|
+
run(items.slice(i, i + 100));
|
|
139
|
+
}
|
|
140
|
+
return { inserted, updated, skipped };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get cursor for a specific connector/entity.
|
|
144
|
+
* Key format: "source:entity:subkey" (e.g., "feishu:messages:chatId123")
|
|
145
|
+
*/
|
|
146
|
+
getCursor(cursorKey) {
|
|
147
|
+
const row = this.stmts.getCursor.get(cursorKey);
|
|
148
|
+
if (!row?.cursor) return null;
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(row.cursor);
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Save cursor for a connector/entity. */
|
|
156
|
+
saveCursor(cursorKey, cursor) {
|
|
157
|
+
this.stmts.saveCursor.run(cursorKey, JSON.stringify(cursor), Date.now());
|
|
158
|
+
}
|
|
159
|
+
/** Save a simple timestamp cursor (convenience for sources that only need updatedSince). */
|
|
160
|
+
saveTimestampCursor(cursorKey) {
|
|
161
|
+
this.saveCursor(cursorKey, { updatedSince: (/* @__PURE__ */ new Date()).toISOString() });
|
|
162
|
+
}
|
|
163
|
+
/** Get the updatedSince value from a cursor, or null if not set. */
|
|
164
|
+
getUpdatedSince(cursorKey) {
|
|
165
|
+
const cursor = this.getCursor(cursorKey);
|
|
166
|
+
return cursor?.updatedSince ?? null;
|
|
167
|
+
}
|
|
168
|
+
/** Check if a URI already exists (quick check without full upsert). */
|
|
169
|
+
exists(uri) {
|
|
170
|
+
return !!this.stmts.checkExists.get(uri);
|
|
171
|
+
}
|
|
172
|
+
/** Get the underlying SQLite instance (for source-specific queries). */
|
|
173
|
+
getSqlite() {
|
|
174
|
+
return this.sqlite;
|
|
175
|
+
}
|
|
176
|
+
get writer() {
|
|
177
|
+
return this.fileWriter;
|
|
178
|
+
}
|
|
179
|
+
get log() {
|
|
180
|
+
return this.logger;
|
|
181
|
+
}
|
|
182
|
+
/** Rebuild FTS entry for an updated object. */
|
|
183
|
+
rebuildFts(id, title, summary, tags, source, sourceType, content) {
|
|
184
|
+
try {
|
|
185
|
+
const rowid = this.stmts.getRowid.get(id);
|
|
186
|
+
if (!rowid) return;
|
|
187
|
+
try {
|
|
188
|
+
this.stmts.deleteFts.run(rowid.rowid);
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
const excerpt = content.length > 500 ? content.slice(0, 500) : content;
|
|
192
|
+
this.stmts.insertFts.run(rowid.rowid, title ?? "", summary ?? "", tags, source, sourceType, excerpt);
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export {
|
|
199
|
+
contentHash,
|
|
200
|
+
SyncContext
|
|
201
|
+
};
|