@usesocial/cli 0.7.1 → 0.8.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/CHANGELOG.md +17 -0
- package/README.md +53 -31
- package/dist/index.mjs +1372 -1026
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -129,855 +129,1014 @@ var CancelledError = class extends Error {
|
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
131
|
//#endregion
|
|
132
|
-
//#region ../../packages/sync-engine/src/
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
"sql": "CREATE TABLE `li_profiles` (\n `provider_id` text PRIMARY KEY NOT NULL,\n `handle` text,\n `display_name` text NOT NULL,\n `avatar_url` text,\n `headline` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `li_profiles_handle_idx` ON `li_profiles` (`handle`);--> statement-breakpoint\nCREATE TABLE `x_messages` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `dm_conversation_id` text,\n `event_type` text,\n `sender_id` text,\n `text` text,\n `participant_ids` text,\n `attachments` text,\n `entities` text,\n `referenced_tweets` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_messages_created_at_idx` ON `x_messages` (`created_at`);--> statement-breakpoint\nCREATE INDEX `x_messages_dm_conversation_id_idx` ON `x_messages` (`dm_conversation_id`);--> statement-breakpoint\nCREATE TABLE `x_profiles` (\n `provider_id` text PRIMARY KEY NOT NULL,\n `handle` text,\n `display_name` text NOT NULL,\n `avatar_url` text,\n `headline` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_profiles_handle_idx` ON `x_profiles` (`handle`);"
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
"tag": "0002_previous_spitfire",
|
|
144
|
-
"sql": "ALTER TABLE `li_connections` ADD `user_headline` text;--> statement-breakpoint\nALTER TABLE `li_requests` ADD `user_headline` text;--> statement-breakpoint\nALTER TABLE `x_bookmarks` DROP COLUMN `edit_controls_editable_until`;--> statement-breakpoint\nALTER TABLE `x_bookmarks` DROP COLUMN `edit_controls_edits_remaining`;--> statement-breakpoint\nALTER TABLE `x_bookmarks` DROP COLUMN `edit_controls_is_edit_eligible`;--> statement-breakpoint\nALTER TABLE `x_tweets` DROP COLUMN `edit_controls_editable_until`;--> statement-breakpoint\nALTER TABLE `x_tweets` DROP COLUMN `edit_controls_edits_remaining`;--> statement-breakpoint\nALTER TABLE `x_tweets` DROP COLUMN `edit_controls_is_edit_eligible`;"
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
"tag": "0003_wonderful_makkari",
|
|
148
|
-
"sql": "ALTER TABLE `li_conversations` RENAME COLUMN \"headline\" TO \"user_headline\";--> statement-breakpoint\nCREATE INDEX `li_conversations_user_id_last_message_timestamp_idx` ON `li_conversations` (`user_id`,`last_message_timestamp`);--> statement-breakpoint\nCREATE INDEX `li_conversations_user_public_identifier_last_message_timestamp_idx` ON `li_conversations` (`user_public_identifier`,`last_message_timestamp`);--> statement-breakpoint\nCREATE INDEX `li_conversations_user_display_name_idx` ON `li_conversations` (`user_display_name`);--> statement-breakpoint\nCREATE INDEX `li_conversations_is_archived_last_message_timestamp_idx` ON `li_conversations` (`is_archived`,`last_message_timestamp`);--> statement-breakpoint\nDROP INDEX `li_messages_chat_id_idx`;--> statement-breakpoint\nCREATE INDEX `li_messages_chat_id_timestamp_idx` ON `li_messages` (`chat_id`,`timestamp`);--> statement-breakpoint\nCREATE INDEX `li_messages_sender_id_timestamp_idx` ON `li_messages` (`sender_id`,`timestamp`);--> statement-breakpoint\nDROP INDEX `x_messages_dm_conversation_id_idx`;--> statement-breakpoint\nCREATE INDEX `x_messages_dm_conversation_id_created_at_idx` ON `x_messages` (`dm_conversation_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_messages_sender_id_created_at_idx` ON `x_messages` (`sender_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `li_connections_synced_at_idx` ON `li_connections` (`synced_at`);--> statement-breakpoint\nCREATE INDEX `li_connections_user_id_idx` ON `li_connections` (`user_id`);--> statement-breakpoint\nCREATE INDEX `li_connections_user_public_identifier_idx` ON `li_connections` (`user_public_identifier`);--> statement-breakpoint\nCREATE INDEX `li_connections_user_display_name_idx` ON `li_connections` (`user_display_name`);--> statement-breakpoint\nCREATE INDEX `li_posts_author_id_created_at_idx` ON `li_posts` (`author_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `li_posts_author_public_identifier_created_at_idx` ON `li_posts` (`author_public_identifier`,`created_at`);--> statement-breakpoint\nCREATE INDEX `li_requests_type_synced_at_idx` ON `li_requests` (`type`,`synced_at`);--> statement-breakpoint\nCREATE INDEX `li_requests_user_id_idx` ON `li_requests` (`user_id`);--> statement-breakpoint\nCREATE INDEX `li_requests_user_public_identifier_idx` ON `li_requests` (`user_public_identifier`);--> statement-breakpoint\nCREATE INDEX `li_requests_user_display_name_idx` ON `li_requests` (`user_display_name`);--> statement-breakpoint\nCREATE INDEX `x_bookmarks_author_id_created_at_idx` ON `x_bookmarks` (`author_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_bookmarks_conversation_id_created_at_idx` ON `x_bookmarks` (`conversation_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_followers_synced_at_idx` ON `x_followers` (`synced_at`);--> statement-breakpoint\nCREATE INDEX `x_followers_username_idx` ON `x_followers` (`username`);--> statement-breakpoint\nCREATE INDEX `x_followers_verified_idx` ON `x_followers` (`verified`);--> statement-breakpoint\nCREATE INDEX `x_followers_verified_type_idx` ON `x_followers` (`verified_type`);--> statement-breakpoint\nCREATE INDEX `x_following_synced_at_idx` ON `x_following` (`synced_at`);--> statement-breakpoint\nCREATE INDEX `x_following_username_idx` ON `x_following` (`username`);--> statement-breakpoint\nCREATE INDEX `x_following_verified_idx` ON `x_following` (`verified`);--> statement-breakpoint\nCREATE INDEX `x_following_verified_type_idx` ON `x_following` (`verified_type`);--> statement-breakpoint\nCREATE INDEX `x_tweets_author_id_created_at_idx` ON `x_tweets` (`author_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_tweets_conversation_id_created_at_idx` ON `x_tweets` (`conversation_id`,`created_at`);"
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"tag": "0004_quiet_prism",
|
|
152
|
-
"sql": "ALTER TABLE `x_messages` RENAME TO `x_raw_messages`;--> statement-breakpoint\nCREATE VIEW `x_messages` AS\nSELECT m.*, p.handle AS sender_handle, p.display_name AS sender_name,\n p.avatar_url AS sender_avatar_url, p.headline AS sender_headline\nFROM `x_raw_messages` m LEFT JOIN `x_profiles` p ON m.sender_id = p.provider_id;\n"
|
|
153
|
-
}
|
|
154
|
-
];
|
|
155
|
-
//#endregion
|
|
156
|
-
//#region ../../packages/sync-engine/src/migrate.ts
|
|
157
|
-
const readUserVersion = (db) => {
|
|
158
|
-
const version = db.prepare("PRAGMA user_version").get()?.user_version;
|
|
159
|
-
return typeof version === "number" ? version : Number(version ?? 0);
|
|
160
|
-
};
|
|
161
|
-
const setUserVersion = (db, version) => {
|
|
162
|
-
db.exec(`PRAGMA user_version = ${version}`);
|
|
163
|
-
};
|
|
164
|
-
const migrate = (db) => {
|
|
165
|
-
const currentVersion = readUserVersion(db);
|
|
166
|
-
if (currentVersion >= MIGRATIONS.length) return {
|
|
167
|
-
fromVersion: currentVersion,
|
|
168
|
-
toVersion: currentVersion,
|
|
169
|
-
applied: 0
|
|
170
|
-
};
|
|
171
|
-
for (let index = currentVersion; index < MIGRATIONS.length; index += 1) {
|
|
172
|
-
const migration = MIGRATIONS[index];
|
|
173
|
-
if (!migration) throw new Error(`Missing migration at index ${index}`);
|
|
174
|
-
db.transaction(() => {
|
|
175
|
-
db.exec(migration.sql);
|
|
176
|
-
setUserVersion(db, index + 1);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
return {
|
|
180
|
-
fromVersion: currentVersion,
|
|
181
|
-
toVersion: MIGRATIONS.length,
|
|
182
|
-
applied: MIGRATIONS.length - currentVersion
|
|
183
|
-
};
|
|
184
|
-
};
|
|
185
|
-
const assertAtLatest = (db) => {
|
|
186
|
-
const currentVersion = readUserVersion(db);
|
|
187
|
-
if (currentVersion < MIGRATIONS.length) throw new Error(`Local SQLite cache schema is at version ${currentVersion}; expected ${MIGRATIONS.length}`);
|
|
132
|
+
//#region ../../packages/sync-engine/src/collections/linkedin.ts
|
|
133
|
+
const linkedinCursorPagination = {
|
|
134
|
+
style: "cursor",
|
|
135
|
+
limitParam: "limit",
|
|
136
|
+
cursorParam: "cursor",
|
|
137
|
+
cursorPath: "next_cursor",
|
|
138
|
+
totalCountPath: "total_count",
|
|
139
|
+
itemsPath: "data"
|
|
188
140
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
196
|
-
return value;
|
|
141
|
+
const linkedinRelationsCursorPagination = {
|
|
142
|
+
style: "cursor",
|
|
143
|
+
limitParam: "limit",
|
|
144
|
+
cursorParam: "cursor",
|
|
145
|
+
cursorPath: "next_cursor",
|
|
146
|
+
itemsPath: "data"
|
|
197
147
|
};
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
148
|
+
const asRecord$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
149
|
+
const read$1 = (value) => value === null || value === void 0 ? null : value;
|
|
150
|
+
const readReaction = (value) => typeof value === "string" ? value : null;
|
|
151
|
+
const trimmed$1 = (value) => typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
152
|
+
const userHeadline = (user) => {
|
|
153
|
+
const specifics = asRecord$1(user.specifics);
|
|
154
|
+
return trimmed$1(user.description) ?? trimmed$1(user.headline) ?? trimmed$1(specifics.description) ?? trimmed$1(specifics.headline) ?? trimmed$1(specifics.occupation) ?? null;
|
|
201
155
|
};
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
156
|
+
const isoToEpochMs$1 = (value) => {
|
|
157
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
158
|
+
if (typeof value !== "string") return null;
|
|
159
|
+
const epochMs = Date.parse(value);
|
|
160
|
+
return Number.isNaN(epochMs) ? null : epochMs;
|
|
205
161
|
};
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
all: (params) => invokeWithParams(statement.all.bind(statement), params)
|
|
210
|
-
});
|
|
211
|
-
const openRuntimeDatabase = (path, opts) => {
|
|
212
|
-
if (isBunRuntime()) {
|
|
213
|
-
const { Database } = loadModule("bun:sqlite");
|
|
214
|
-
return new Database(path, {
|
|
215
|
-
readonly: opts?.readonly,
|
|
216
|
-
strict: true
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
const { DatabaseSync } = loadModule("node:sqlite");
|
|
220
|
-
return opts?.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
162
|
+
const toBool$1 = (value) => {
|
|
163
|
+
if (typeof value !== "boolean") return null;
|
|
164
|
+
return value ? 1 : 0;
|
|
221
165
|
};
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const exec = (sql) => {
|
|
225
|
-
database.exec(sql);
|
|
226
|
-
};
|
|
166
|
+
const liftConnection = (item) => {
|
|
167
|
+
const user = asRecord$1(item.user);
|
|
227
168
|
return {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
169
|
+
id: read$1(item.id),
|
|
170
|
+
created_at: isoToEpochMs$1(item.created_at),
|
|
171
|
+
object: read$1(item.object),
|
|
172
|
+
url: read$1(item.url),
|
|
173
|
+
user_id: read$1(user.id),
|
|
174
|
+
user_object: read$1(user.object),
|
|
175
|
+
user_type: read$1(user.type),
|
|
176
|
+
user_display_name: read$1(user.display_name),
|
|
177
|
+
user_public_identifier: read$1(user.public_identifier),
|
|
178
|
+
user_profile_url: read$1(user.profile_url),
|
|
179
|
+
user_public_picture_url: read$1(user.public_picture_url),
|
|
180
|
+
user_description: read$1(user.description),
|
|
181
|
+
user_headline: userHeadline(user),
|
|
182
|
+
raw: JSON.stringify(item)
|
|
243
183
|
};
|
|
244
184
|
};
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
185
|
+
const liftConversation = (item) => {
|
|
186
|
+
const user = asRecord$1(item.user);
|
|
187
|
+
const userSpecifics = asRecord$1(user.specifics);
|
|
188
|
+
return {
|
|
189
|
+
id: read$1(item.id),
|
|
190
|
+
created_at: isoToEpochMs$1(item.created_at),
|
|
191
|
+
last_message_timestamp: isoToEpochMs$1(item.last_message_timestamp),
|
|
192
|
+
muted_until: isoToEpochMs$1(item.muted_until),
|
|
193
|
+
creator_id: read$1(item.creator_id),
|
|
194
|
+
user_headline: userHeadline(user),
|
|
195
|
+
image_url: read$1(item.image_url),
|
|
196
|
+
name: read$1(item.name),
|
|
197
|
+
object: read$1(item.object),
|
|
198
|
+
provider: read$1(item.provider),
|
|
199
|
+
type: read$1(item.type),
|
|
200
|
+
user_id: read$1(item.user_id ?? user.id),
|
|
201
|
+
is_1to1: toBool$1(item.is_1to1),
|
|
202
|
+
is_archived: toBool$1(item.is_archived),
|
|
203
|
+
is_channel: toBool$1(item.is_channel),
|
|
204
|
+
is_group: toBool$1(item.is_group),
|
|
205
|
+
is_pinned: toBool$1(item.is_pinned),
|
|
206
|
+
is_readonly: toBool$1(item.is_readonly),
|
|
207
|
+
unread_count: read$1(item.unread_count),
|
|
208
|
+
user_object: read$1(user.object),
|
|
209
|
+
user_type: read$1(user.type),
|
|
210
|
+
user_display_name: read$1(user.display_name),
|
|
211
|
+
user_public_identifier: read$1(user.public_identifier),
|
|
212
|
+
user_profile_url: read$1(user.profile_url),
|
|
213
|
+
user_public_picture_url: read$1(user.public_picture_url),
|
|
214
|
+
user_description: read$1(user.description),
|
|
215
|
+
user_specifics_network_distance: read$1(userSpecifics.network_distance),
|
|
216
|
+
raw: JSON.stringify(item)
|
|
217
|
+
};
|
|
276
218
|
};
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
219
|
+
const liftMessage = (item) => {
|
|
220
|
+
const sender = asRecord$1(item.sender);
|
|
221
|
+
const senderSpecifics = asRecord$1(sender.specifics);
|
|
222
|
+
return {
|
|
223
|
+
id: read$1(item.id),
|
|
224
|
+
timestamp: isoToEpochMs$1(item.timestamp),
|
|
225
|
+
chat_id: read$1(item.chat_id ?? null),
|
|
226
|
+
object: read$1(item.object),
|
|
227
|
+
provider: read$1(item.provider),
|
|
228
|
+
text: read$1(item.text),
|
|
229
|
+
sender_id: read$1(item.sender_id),
|
|
230
|
+
is_deleted: toBool$1(item.is_deleted),
|
|
231
|
+
is_delivered: toBool$1(item.is_delivered),
|
|
232
|
+
is_edited: toBool$1(item.is_edited),
|
|
233
|
+
is_event: toBool$1(item.is_event),
|
|
234
|
+
is_hidden: toBool$1(item.is_hidden),
|
|
235
|
+
is_mentionned: toBool$1(item.is_mentionned),
|
|
236
|
+
is_pinned: toBool$1(item.is_pinned),
|
|
237
|
+
is_seen: toBool$1(item.is_seen),
|
|
238
|
+
is_sender: toBool$1(item.is_sender),
|
|
239
|
+
sender_object: read$1(sender.object),
|
|
240
|
+
sender_type: read$1(sender.type),
|
|
241
|
+
sender_display_name: read$1(sender.display_name),
|
|
242
|
+
sender_public_identifier: read$1(sender.public_identifier),
|
|
243
|
+
sender_profile_url: read$1(sender.profile_url),
|
|
244
|
+
sender_public_picture_url: read$1(sender.public_picture_url),
|
|
245
|
+
sender_description: read$1(sender.description),
|
|
246
|
+
sender_specifics_network_distance: read$1(senderSpecifics.network_distance),
|
|
247
|
+
raw: JSON.stringify(item)
|
|
248
|
+
};
|
|
284
249
|
};
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
250
|
+
const liftPost = (item) => {
|
|
251
|
+
const analytics = asRecord$1(item.analytics);
|
|
252
|
+
const author = asRecord$1(item.author);
|
|
253
|
+
const authorSpecifics = asRecord$1(author.specifics);
|
|
254
|
+
const permissions = asRecord$1(item.permissions);
|
|
255
|
+
const poll = asRecord$1(item.poll);
|
|
256
|
+
const quotedPost = asRecord$1(item.quoted_post);
|
|
257
|
+
const quotedPostPermissions = asRecord$1(quotedPost.permissions);
|
|
258
|
+
const quotedPostAuthor = asRecord$1(quotedPost.author);
|
|
259
|
+
const quotedPostAuthorSpecifics = asRecord$1(quotedPostAuthor.specifics);
|
|
260
|
+
const repostedBy = asRecord$1(item.reposted_by);
|
|
261
|
+
return {
|
|
262
|
+
id: read$1(item.id),
|
|
263
|
+
created_at: isoToEpochMs$1(item.created_at),
|
|
264
|
+
object: read$1(item.object),
|
|
265
|
+
share_url: read$1(item.share_url),
|
|
266
|
+
text: read$1(item.text),
|
|
267
|
+
url: read$1(item.url),
|
|
268
|
+
is_repost: toBool$1(item.is_repost),
|
|
269
|
+
user_reacted: readReaction(item.user_reacted),
|
|
270
|
+
comments_counter: read$1(item.comments_counter),
|
|
271
|
+
reposts_counter: read$1(item.reposts_counter),
|
|
272
|
+
analytics_impressions_counter: read$1(analytics.impressions_counter),
|
|
273
|
+
author_id: read$1(author.id),
|
|
274
|
+
author_object: read$1(author.object),
|
|
275
|
+
author_type: read$1(author.type),
|
|
276
|
+
author_display_name: read$1(author.display_name),
|
|
277
|
+
author_public_identifier: read$1(author.public_identifier),
|
|
278
|
+
author_profile_url: read$1(author.profile_url),
|
|
279
|
+
author_public_picture_url: read$1(author.public_picture_url),
|
|
280
|
+
author_description: read$1(author.description),
|
|
281
|
+
author_specifics_network_distance: read$1(authorSpecifics.network_distance),
|
|
282
|
+
permissions_can_post_comments: toBool$1(permissions.can_post_comments),
|
|
283
|
+
permissions_can_react: toBool$1(permissions.can_react),
|
|
284
|
+
permissions_can_share: toBool$1(permissions.can_share),
|
|
285
|
+
poll_id: read$1(poll.id),
|
|
286
|
+
poll_question: read$1(poll.question),
|
|
287
|
+
poll_is_open: toBool$1(poll.is_open),
|
|
288
|
+
poll_total_votes_count: read$1(poll.total_votes_count),
|
|
289
|
+
quoted_post_id: read$1(quotedPost.id),
|
|
290
|
+
quoted_post_object: read$1(quotedPost.object),
|
|
291
|
+
quoted_post_created_at: isoToEpochMs$1(quotedPost.created_at),
|
|
292
|
+
quoted_post_text: read$1(quotedPost.text),
|
|
293
|
+
quoted_post_share_url: read$1(quotedPost.share_url),
|
|
294
|
+
quoted_post_user_reacted: readReaction(quotedPost.user_reacted),
|
|
295
|
+
quoted_post_comments_counter: read$1(quotedPost.comments_counter),
|
|
296
|
+
quoted_post_reposts_counter: read$1(quotedPost.reposts_counter),
|
|
297
|
+
quoted_post_permissions_can_post_comments: toBool$1(quotedPostPermissions.can_post_comments),
|
|
298
|
+
quoted_post_permissions_can_react: toBool$1(quotedPostPermissions.can_react),
|
|
299
|
+
quoted_post_permissions_can_share: toBool$1(quotedPostPermissions.can_share),
|
|
300
|
+
quoted_post_author_id: read$1(quotedPostAuthor.id),
|
|
301
|
+
quoted_post_author_object: read$1(quotedPostAuthor.object),
|
|
302
|
+
quoted_post_author_type: read$1(quotedPostAuthor.type),
|
|
303
|
+
quoted_post_author_display_name: read$1(quotedPostAuthor.display_name),
|
|
304
|
+
quoted_post_author_public_identifier: read$1(quotedPostAuthor.public_identifier),
|
|
305
|
+
quoted_post_author_profile_url: read$1(quotedPostAuthor.profile_url),
|
|
306
|
+
quoted_post_author_public_picture_url: read$1(quotedPostAuthor.public_picture_url),
|
|
307
|
+
quoted_post_author_description: read$1(quotedPostAuthor.description),
|
|
308
|
+
quoted_post_author_specifics_network_distance: read$1(quotedPostAuthorSpecifics.network_distance),
|
|
309
|
+
reposted_by_id: read$1(repostedBy.id),
|
|
310
|
+
reposted_by_object: read$1(repostedBy.object),
|
|
311
|
+
reposted_by_type: read$1(repostedBy.type),
|
|
312
|
+
reposted_by_display_name: read$1(repostedBy.display_name),
|
|
313
|
+
reposted_by_public_identifier: read$1(repostedBy.public_identifier),
|
|
314
|
+
reposted_by_profile_url: read$1(repostedBy.profile_url),
|
|
315
|
+
reposted_by_public_picture_url: read$1(repostedBy.public_picture_url),
|
|
316
|
+
raw: JSON.stringify(item)
|
|
317
|
+
};
|
|
303
318
|
};
|
|
304
|
-
const
|
|
305
|
-
const
|
|
306
|
-
return
|
|
319
|
+
const liftRequest = (item) => {
|
|
320
|
+
const user = asRecord$1(item.user);
|
|
321
|
+
return {
|
|
322
|
+
id: read$1(item.id),
|
|
323
|
+
created_at: isoToEpochMs$1(item.created_at),
|
|
324
|
+
object: read$1(item.object),
|
|
325
|
+
type: read$1(item.type ?? null),
|
|
326
|
+
user_id: read$1(user.id),
|
|
327
|
+
user_object: read$1(user.object),
|
|
328
|
+
user_type: read$1(user.type),
|
|
329
|
+
user_display_name: read$1(user.display_name),
|
|
330
|
+
user_public_identifier: read$1(user.public_identifier),
|
|
331
|
+
user_profile_url: read$1(user.profile_url),
|
|
332
|
+
user_public_picture_url: read$1(user.public_picture_url),
|
|
333
|
+
user_description: read$1(user.description),
|
|
334
|
+
user_headline: userHeadline(user),
|
|
335
|
+
raw: JSON.stringify(item)
|
|
336
|
+
};
|
|
307
337
|
};
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
338
|
+
const LINKEDIN_COLLECTIONS = [
|
|
339
|
+
{
|
|
340
|
+
key: "li_conversations",
|
|
341
|
+
table: "li_conversations_raw",
|
|
342
|
+
platform: "linkedin",
|
|
343
|
+
requests: [{ path: "/chats" }],
|
|
344
|
+
pagination: linkedinCursorPagination,
|
|
345
|
+
pageSize: 20,
|
|
346
|
+
sinceField: "last_message_timestamp",
|
|
347
|
+
lift: liftConversation
|
|
314
348
|
},
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
349
|
+
{
|
|
350
|
+
key: "li_messages",
|
|
351
|
+
table: "li_messages_raw",
|
|
352
|
+
platform: "linkedin",
|
|
353
|
+
requests: [{ path: "/chats/:chat_id/messages" }],
|
|
354
|
+
parentCollection: "li_conversations",
|
|
355
|
+
parentIdToken: ":chat_id",
|
|
356
|
+
pagination: linkedinCursorPagination,
|
|
357
|
+
pageSize: 250,
|
|
358
|
+
sinceField: "timestamp",
|
|
359
|
+
lift: liftMessage
|
|
319
360
|
},
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
361
|
+
{
|
|
362
|
+
key: "li_connections",
|
|
363
|
+
table: "li_connections_raw",
|
|
364
|
+
platform: "linkedin",
|
|
365
|
+
requests: [{ path: "/users/connections" }],
|
|
366
|
+
requiresFullInitialSync: true,
|
|
367
|
+
pagination: linkedinRelationsCursorPagination,
|
|
368
|
+
pageSize: 100,
|
|
369
|
+
sinceField: "created_at",
|
|
370
|
+
lift: liftConnection
|
|
323
371
|
},
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
372
|
+
{
|
|
373
|
+
key: "li_posts",
|
|
374
|
+
table: "li_posts_raw",
|
|
375
|
+
platform: "linkedin",
|
|
376
|
+
requests: [{ path: "/users/:identifier/posts" }],
|
|
377
|
+
ownIdToken: ":identifier",
|
|
378
|
+
pagination: linkedinCursorPagination,
|
|
379
|
+
pageSize: 50,
|
|
380
|
+
sinceField: "created_at",
|
|
381
|
+
lift: liftPost
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
key: "li_requests",
|
|
385
|
+
table: "li_requests_raw",
|
|
386
|
+
platform: "linkedin",
|
|
387
|
+
requests: [{
|
|
388
|
+
path: "/users/me/relation-requests/received",
|
|
389
|
+
bakedQuery: { type: "received" }
|
|
390
|
+
}, {
|
|
391
|
+
path: "/users/me/relation-requests/sent",
|
|
392
|
+
bakedQuery: { type: "sent" }
|
|
393
|
+
}],
|
|
394
|
+
pagination: linkedinRelationsCursorPagination,
|
|
395
|
+
pageSize: 100,
|
|
396
|
+
lift: liftRequest
|
|
397
|
+
}
|
|
398
|
+
];
|
|
399
|
+
const userTypeValues = ["individual", "organization"];
|
|
400
|
+
const LINKEDIN_CACHE_METADATA = {
|
|
401
|
+
joins: [
|
|
402
|
+
"li_*.user_id = li_profiles.provider_id",
|
|
403
|
+
"li_messages.conversation_id = li_conversations.id",
|
|
404
|
+
"li_messages.sender_id = li_profiles.provider_id",
|
|
405
|
+
"li_posts.author_id = li_profiles.provider_id"
|
|
406
|
+
],
|
|
407
|
+
enums: {
|
|
408
|
+
"li_connections.user_type": userTypeValues,
|
|
409
|
+
"li_conversations.type": ["1to1", "group"],
|
|
410
|
+
"li_conversations.user_type": userTypeValues,
|
|
411
|
+
"li_messages.sender_type": userTypeValues,
|
|
412
|
+
"li_posts.author_type": userTypeValues,
|
|
413
|
+
"li_requests.type": ["received", "sent"],
|
|
414
|
+
"li_requests.user_type": userTypeValues
|
|
415
|
+
},
|
|
416
|
+
indexed: {
|
|
417
|
+
li_connections: [
|
|
418
|
+
"created_at",
|
|
419
|
+
"user_id",
|
|
420
|
+
"user_username",
|
|
421
|
+
"user_name"
|
|
422
|
+
],
|
|
423
|
+
li_conversations: [
|
|
424
|
+
"created_at",
|
|
425
|
+
"last_message_at",
|
|
426
|
+
"user_id",
|
|
427
|
+
"user_username",
|
|
428
|
+
"user_name",
|
|
429
|
+
"is_archived"
|
|
430
|
+
],
|
|
431
|
+
li_messages: [
|
|
432
|
+
"created_at",
|
|
433
|
+
"conversation_id",
|
|
434
|
+
"sender_id"
|
|
435
|
+
],
|
|
436
|
+
li_posts: [
|
|
437
|
+
"created_at",
|
|
438
|
+
"author_id",
|
|
439
|
+
"author_username"
|
|
440
|
+
],
|
|
441
|
+
li_profiles: ["username"],
|
|
442
|
+
li_requests: [
|
|
443
|
+
"created_at",
|
|
444
|
+
"type",
|
|
445
|
+
"user_id",
|
|
446
|
+
"user_username",
|
|
447
|
+
"user_name"
|
|
448
|
+
]
|
|
327
449
|
}
|
|
328
450
|
};
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region ../../packages/sync-engine/src/collections/x.ts
|
|
453
|
+
const xPagination = {
|
|
454
|
+
style: "cursor",
|
|
455
|
+
limitParam: "max_results",
|
|
456
|
+
cursorParam: "pagination_token",
|
|
457
|
+
cursorPath: "meta.next_token",
|
|
458
|
+
itemsPath: "data"
|
|
332
459
|
};
|
|
333
|
-
const
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
460
|
+
const defaultDMEventFields = "attachments,created_at,dm_conversation_id,entities,event_type,id,participant_ids,referenced_tweets,sender_id,text";
|
|
461
|
+
const defaultDMExpansions = "attachments.media_keys,participant_ids,referenced_tweets.id,sender_id";
|
|
462
|
+
const defaultDMUserFields = "affiliation,connection_status,created_at,description,entities,id,location,most_recent_tweet_id,name,pinned_tweet_id,profile_banner_url,profile_image_url,protected,public_metrics,receives_your_dm,subscription_type,url,username,verified,verified_type,withheld";
|
|
463
|
+
const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
464
|
+
const read = (value) => value === null || value === void 0 ? null : value;
|
|
465
|
+
const isoToEpochMs = (value) => {
|
|
466
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
467
|
+
if (typeof value !== "string") return null;
|
|
468
|
+
const epochMs = Date.parse(value);
|
|
469
|
+
return Number.isNaN(epochMs) ? null : epochMs;
|
|
337
470
|
};
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
342
|
-
return `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
|
|
471
|
+
const toBool = (value) => {
|
|
472
|
+
if (typeof value !== "boolean") return null;
|
|
473
|
+
return value ? 1 : 0;
|
|
343
474
|
};
|
|
344
|
-
const
|
|
345
|
-
|
|
475
|
+
const stringifyJSON$1 = (value) => value === null || value === void 0 ? null : JSON.stringify(value);
|
|
476
|
+
const liftTweetColumns = (item) => {
|
|
477
|
+
const publicMetrics = asRecord(item.public_metrics);
|
|
478
|
+
return {
|
|
479
|
+
id: read(item.id),
|
|
480
|
+
created_at: isoToEpochMs(item.created_at),
|
|
481
|
+
author_id: read(item.author_id),
|
|
482
|
+
conversation_id: read(item.conversation_id),
|
|
483
|
+
in_reply_to_user_id: read(item.in_reply_to_user_id),
|
|
484
|
+
lang: read(item.lang),
|
|
485
|
+
reply_settings: read(item.reply_settings),
|
|
486
|
+
text: read(item.text),
|
|
487
|
+
url: read(item.url),
|
|
488
|
+
paid_partnership: toBool(item.paid_partnership),
|
|
489
|
+
possibly_sensitive: toBool(item.possibly_sensitive),
|
|
490
|
+
like_count: read(publicMetrics.like_count),
|
|
491
|
+
retweet_count: read(publicMetrics.retweet_count),
|
|
492
|
+
reply_count: read(publicMetrics.reply_count),
|
|
493
|
+
quote_count: read(publicMetrics.quote_count),
|
|
494
|
+
bookmark_count: read(publicMetrics.bookmark_count),
|
|
495
|
+
impression_count: read(publicMetrics.impression_count)
|
|
496
|
+
};
|
|
346
497
|
};
|
|
347
|
-
const
|
|
348
|
-
|
|
498
|
+
const liftTweet = (item) => ({
|
|
499
|
+
...liftTweetColumns(item),
|
|
500
|
+
raw: JSON.stringify(item)
|
|
501
|
+
});
|
|
502
|
+
const liftBookmark = (item) => {
|
|
503
|
+
const article = asRecord(item.article);
|
|
504
|
+
const noteTweet = asRecord(item.note_tweet);
|
|
505
|
+
return {
|
|
506
|
+
...liftTweetColumns(item),
|
|
507
|
+
article_cover_media: read(article.cover_media),
|
|
508
|
+
article_plain_text: read(article.plain_text),
|
|
509
|
+
article_preview_text: read(article.preview_text),
|
|
510
|
+
article_title: read(article.title),
|
|
511
|
+
note_tweet_text: read(noteTweet.text),
|
|
512
|
+
raw: JSON.stringify(item)
|
|
513
|
+
};
|
|
349
514
|
};
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
515
|
+
const liftDMEvent = (item) => ({
|
|
516
|
+
id: read(item.id),
|
|
517
|
+
created_at: isoToEpochMs(item.created_at),
|
|
518
|
+
dm_conversation_id: read(item.dm_conversation_id),
|
|
519
|
+
event_type: read(item.event_type),
|
|
520
|
+
sender_id: read(item.sender_id),
|
|
521
|
+
text: read(item.text),
|
|
522
|
+
participant_ids: stringifyJSON$1(item.participant_ids),
|
|
523
|
+
attachments: stringifyJSON$1(item.attachments),
|
|
524
|
+
entities: stringifyJSON$1(item.entities),
|
|
525
|
+
referenced_tweets: stringifyJSON$1(item.referenced_tweets),
|
|
526
|
+
raw: JSON.stringify(item)
|
|
527
|
+
});
|
|
528
|
+
const liftXUser = (item) => {
|
|
529
|
+
const affiliation = asRecord(item.affiliation);
|
|
530
|
+
const publicMetrics = asRecord(item.public_metrics);
|
|
531
|
+
const subscription = asRecord(item.subscription);
|
|
532
|
+
return {
|
|
533
|
+
id: read(item.id),
|
|
534
|
+
created_at: isoToEpochMs(item.created_at),
|
|
535
|
+
username: read(item.username),
|
|
536
|
+
name: read(item.name),
|
|
537
|
+
description: read(item.description),
|
|
538
|
+
location: read(item.location),
|
|
539
|
+
url: read(item.url),
|
|
540
|
+
is_identity_verified: toBool(item.is_identity_verified),
|
|
541
|
+
protected: toBool(item.protected),
|
|
542
|
+
verified: toBool(item.verified),
|
|
543
|
+
verified_type: read(item.verified_type),
|
|
544
|
+
subscription_type: read(item.subscription_type),
|
|
545
|
+
parody: toBool(item.parody),
|
|
546
|
+
most_recent_tweet_id: read(item.most_recent_tweet_id),
|
|
547
|
+
pinned_tweet_id: read(item.pinned_tweet_id),
|
|
548
|
+
profile_banner_url: read(item.profile_banner_url),
|
|
549
|
+
profile_image_url: read(item.profile_image_url),
|
|
550
|
+
affiliation_badge_url: read(affiliation.badge_url),
|
|
551
|
+
affiliation_description: read(affiliation.description),
|
|
552
|
+
affiliation_url: read(affiliation.url),
|
|
553
|
+
subscription_subscribes_to_you: toBool(subscription.subscribes_to_you),
|
|
554
|
+
followers_count: read(publicMetrics.followers_count),
|
|
555
|
+
following_count: read(publicMetrics.following_count),
|
|
556
|
+
tweet_count: read(publicMetrics.tweet_count),
|
|
557
|
+
listed_count: read(publicMetrics.listed_count),
|
|
558
|
+
like_count: read(publicMetrics.like_count),
|
|
559
|
+
media_count: read(publicMetrics.media_count),
|
|
560
|
+
raw: JSON.stringify(item)
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
const X_COLLECTIONS = [
|
|
564
|
+
{
|
|
565
|
+
key: "x_tweets",
|
|
566
|
+
table: "x_tweets_raw",
|
|
567
|
+
platform: "x",
|
|
568
|
+
requests: [{ path: "/users/:id/tweets" }],
|
|
569
|
+
ownIdToken: ":id",
|
|
570
|
+
pagination: xPagination,
|
|
571
|
+
pageSize: 100,
|
|
572
|
+
sinceField: "created_at",
|
|
573
|
+
lift: liftTweet
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
key: "x_followers",
|
|
577
|
+
table: "x_followers_raw",
|
|
578
|
+
platform: "x",
|
|
579
|
+
requests: [{ path: "/users/:id/followers" }],
|
|
580
|
+
ownIdToken: ":id",
|
|
581
|
+
requiresFullInitialSync: true,
|
|
582
|
+
pagination: xPagination,
|
|
583
|
+
pageSize: 1e3,
|
|
584
|
+
lift: liftXUser
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
key: "x_following",
|
|
588
|
+
table: "x_following_raw",
|
|
589
|
+
platform: "x",
|
|
590
|
+
requests: [{ path: "/users/:id/following" }],
|
|
591
|
+
ownIdToken: ":id",
|
|
592
|
+
requiresFullInitialSync: true,
|
|
593
|
+
pagination: xPagination,
|
|
594
|
+
pageSize: 1e3,
|
|
595
|
+
lift: liftXUser
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
key: "x_bookmarks",
|
|
599
|
+
table: "x_bookmarks_raw",
|
|
600
|
+
platform: "x",
|
|
601
|
+
requests: [{ path: "/users/:id/bookmarks" }],
|
|
602
|
+
ownIdToken: ":id",
|
|
603
|
+
pagination: xPagination,
|
|
604
|
+
pageSize: 100,
|
|
605
|
+
lift: liftBookmark
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
key: "x_messages",
|
|
609
|
+
table: "x_messages_raw",
|
|
610
|
+
platform: "x",
|
|
611
|
+
requests: [{
|
|
612
|
+
path: "/dm_events",
|
|
613
|
+
bakedQuery: {
|
|
614
|
+
"dm_event.fields": defaultDMEventFields,
|
|
615
|
+
expansions: defaultDMExpansions,
|
|
616
|
+
"user.fields": defaultDMUserFields
|
|
617
|
+
}
|
|
618
|
+
}],
|
|
619
|
+
pagination: xPagination,
|
|
620
|
+
pageSize: 100,
|
|
621
|
+
sinceField: "created_at",
|
|
622
|
+
lift: liftDMEvent
|
|
623
|
+
}
|
|
624
|
+
];
|
|
625
|
+
const X_CACHE_METADATA = {
|
|
626
|
+
joins: ["x_messages.sender_id = x_profiles.provider_id", "x_followers.id = x_profiles.provider_id"],
|
|
627
|
+
enums: {
|
|
628
|
+
"x_followers.verified_type": [
|
|
629
|
+
"blue",
|
|
630
|
+
"business",
|
|
631
|
+
"government",
|
|
632
|
+
"none"
|
|
633
|
+
],
|
|
634
|
+
"x_following.verified_type": [
|
|
635
|
+
"blue",
|
|
636
|
+
"business",
|
|
637
|
+
"government",
|
|
638
|
+
"none"
|
|
639
|
+
]
|
|
640
|
+
},
|
|
641
|
+
indexed: {
|
|
642
|
+
x_bookmarks: [
|
|
643
|
+
"created_at",
|
|
644
|
+
"author_id",
|
|
645
|
+
"conversation_id"
|
|
646
|
+
],
|
|
647
|
+
x_followers: [
|
|
648
|
+
"created_at",
|
|
649
|
+
"username",
|
|
650
|
+
"verified",
|
|
651
|
+
"verified_type"
|
|
652
|
+
],
|
|
653
|
+
x_following: [
|
|
654
|
+
"created_at",
|
|
655
|
+
"username",
|
|
656
|
+
"verified",
|
|
657
|
+
"verified_type"
|
|
658
|
+
],
|
|
659
|
+
x_messages: [
|
|
660
|
+
"created_at",
|
|
661
|
+
"conversation_id",
|
|
662
|
+
"sender_id"
|
|
663
|
+
],
|
|
664
|
+
x_profiles: ["username"],
|
|
665
|
+
x_tweets: [
|
|
666
|
+
"created_at",
|
|
667
|
+
"author_id",
|
|
668
|
+
"conversation_id"
|
|
669
|
+
]
|
|
364
670
|
}
|
|
365
|
-
return missing;
|
|
366
671
|
};
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region ../../packages/sync-engine/src/collections/index.ts
|
|
674
|
+
const COLLECTIONS = [...X_COLLECTIONS, ...LINKEDIN_COLLECTIONS];
|
|
675
|
+
const CACHE_METADATA_BY_PLATFORM = {
|
|
676
|
+
linkedin: LINKEDIN_CACHE_METADATA,
|
|
677
|
+
x: X_CACHE_METADATA
|
|
371
678
|
};
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
679
|
+
const collectionByKey = (key) => COLLECTIONS.find((collection) => collection.key === key);
|
|
680
|
+
const collectionsFor = (platform) => COLLECTIONS.filter((collection) => collection.platform === platform);
|
|
681
|
+
const syncableCollectionsFor = (platform) => {
|
|
682
|
+
const parentKeys = new Set(COLLECTIONS.map((collection) => collection.parentCollection).filter(Boolean));
|
|
683
|
+
return collectionsFor(platform).filter((collection) => !parentKeys.has(collection.key));
|
|
375
684
|
};
|
|
376
|
-
const
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
assertAtLatest(db);
|
|
382
|
-
return createCache(dbPath, db);
|
|
383
|
-
}
|
|
384
|
-
if (dbPath !== ":memory:") mkdirSync(dirname(dbPath), { recursive: true });
|
|
385
|
-
const db = openDatabase(dbPath);
|
|
386
|
-
const result = migrate(db);
|
|
387
|
-
if (result.applied > 0) opts?.onMigrate?.(result);
|
|
388
|
-
return createCache(dbPath, db);
|
|
685
|
+
const physicalTableFor = (collection) => collection.table ?? collection.key;
|
|
686
|
+
const resetTablesFor = (collection) => {
|
|
687
|
+
if (!collection.parentCollection) return [collection];
|
|
688
|
+
const parent = collectionByKey(collection.parentCollection);
|
|
689
|
+
return parent ? [collection, parent] : [collection];
|
|
389
690
|
};
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
assertKnownTable(table);
|
|
398
|
-
const columns = [...Object.keys(rows[0] ?? {}).filter((column) => column !== "synced_at"), "synced_at"];
|
|
399
|
-
const updateColumns = columns.filter((column) => column !== "id");
|
|
400
|
-
const statement = db.prepare(`INSERT INTO ${quoteIdentifier(table)} (${columns.map(quoteIdentifier).join(", ")}) VALUES (${columns.map((column) => `:${column}`).join(", ")}) ON CONFLICT(id) DO UPDATE SET ${updateColumns.map((column) => `${quoteIdentifier(column)}=excluded.${quoteIdentifier(column)}`).join(", ")}`);
|
|
401
|
-
db.transaction(() => {
|
|
402
|
-
for (const row of rows) statement.run({
|
|
403
|
-
...row,
|
|
404
|
-
synced_at: syncedAt
|
|
405
|
-
});
|
|
406
|
-
});
|
|
691
|
+
const resetPhysicalTablesFor = (collection) => resetTablesFor(collection).map(physicalTableFor);
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region ../../packages/sync-engine/src/migrations.generated.ts
|
|
694
|
+
const MIGRATIONS = [
|
|
695
|
+
{
|
|
696
|
+
"tag": "0000_modern_stephen_strange",
|
|
697
|
+
"sql": "CREATE TABLE `li_connections` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `object` text,\n `url` text,\n `user_id` text,\n `user_object` text,\n `user_type` text,\n `user_display_name` text,\n `user_public_identifier` text,\n `user_profile_url` text,\n `user_public_picture_url` text,\n `user_description` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `li_connections_created_at_idx` ON `li_connections` (`created_at`);--> statement-breakpoint\nCREATE TABLE `li_conversations` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `last_message_timestamp` integer,\n `muted_until` integer,\n `creator_id` text,\n `headline` text,\n `image_url` text,\n `name` text,\n `object` text,\n `provider` text,\n `type` text,\n `user_id` text,\n `is_1to1` integer,\n `is_archived` integer,\n `is_channel` integer,\n `is_group` integer,\n `is_pinned` integer,\n `is_readonly` integer,\n `unread_count` integer,\n `user_object` text,\n `user_type` text,\n `user_display_name` text,\n `user_public_identifier` text,\n `user_profile_url` text,\n `user_public_picture_url` text,\n `user_description` text,\n `user_specifics_network_distance` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `li_conversations_created_at_idx` ON `li_conversations` (`created_at`);--> statement-breakpoint\nCREATE INDEX `li_conversations_last_message_timestamp_idx` ON `li_conversations` (`last_message_timestamp`);--> statement-breakpoint\nCREATE TABLE `li_messages` (\n `id` text PRIMARY KEY NOT NULL,\n `timestamp` integer,\n `chat_id` text,\n `object` text,\n `provider` text,\n `text` text,\n `sender_id` text,\n `is_deleted` integer,\n `is_delivered` integer,\n `is_edited` integer,\n `is_event` integer,\n `is_hidden` integer,\n `is_mentionned` integer,\n `is_pinned` integer,\n `is_seen` integer,\n `is_sender` integer,\n `sender_object` text,\n `sender_type` text,\n `sender_display_name` text,\n `sender_public_identifier` text,\n `sender_profile_url` text,\n `sender_public_picture_url` text,\n `sender_description` text,\n `sender_specifics_network_distance` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `li_messages_timestamp_idx` ON `li_messages` (`timestamp`);--> statement-breakpoint\nCREATE INDEX `li_messages_chat_id_idx` ON `li_messages` (`chat_id`);--> statement-breakpoint\nCREATE TABLE `li_posts` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `object` text,\n `share_url` text,\n `text` text,\n `url` text,\n `is_repost` integer,\n `user_reacted` text,\n `comments_counter` integer,\n `reposts_counter` integer,\n `analytics_impressions_counter` integer,\n `author_id` text,\n `author_object` text,\n `author_type` text,\n `author_display_name` text,\n `author_public_identifier` text,\n `author_profile_url` text,\n `author_public_picture_url` text,\n `author_description` text,\n `author_specifics_network_distance` text,\n `permissions_can_post_comments` integer,\n `permissions_can_react` integer,\n `permissions_can_share` integer,\n `poll_id` text,\n `poll_question` text,\n `poll_is_open` integer,\n `poll_total_votes_count` integer,\n `quoted_post_id` text,\n `quoted_post_object` text,\n `quoted_post_created_at` integer,\n `quoted_post_text` text,\n `quoted_post_share_url` text,\n `quoted_post_user_reacted` text,\n `quoted_post_comments_counter` integer,\n `quoted_post_reposts_counter` integer,\n `quoted_post_permissions_can_post_comments` integer,\n `quoted_post_permissions_can_react` integer,\n `quoted_post_permissions_can_share` integer,\n `quoted_post_author_id` text,\n `quoted_post_author_object` text,\n `quoted_post_author_type` text,\n `quoted_post_author_display_name` text,\n `quoted_post_author_public_identifier` text,\n `quoted_post_author_profile_url` text,\n `quoted_post_author_public_picture_url` text,\n `quoted_post_author_description` text,\n `quoted_post_author_specifics_network_distance` text,\n `reposted_by_id` text,\n `reposted_by_object` text,\n `reposted_by_type` text,\n `reposted_by_display_name` text,\n `reposted_by_public_identifier` text,\n `reposted_by_profile_url` text,\n `reposted_by_public_picture_url` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `li_posts_created_at_idx` ON `li_posts` (`created_at`);--> statement-breakpoint\nCREATE TABLE `li_requests` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `object` text,\n `type` text,\n `user_id` text,\n `user_object` text,\n `user_type` text,\n `user_display_name` text,\n `user_public_identifier` text,\n `user_profile_url` text,\n `user_public_picture_url` text,\n `user_description` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `li_requests_created_at_idx` ON `li_requests` (`created_at`);--> statement-breakpoint\nCREATE TABLE `sync_state` (\n `collection` text PRIMARY KEY NOT NULL,\n `cursor` text,\n `last_synced` integer,\n `object_count` integer\n);\n--> statement-breakpoint\nCREATE TABLE `x_bookmarks` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `author_id` text,\n `conversation_id` text,\n `in_reply_to_user_id` text,\n `lang` text,\n `reply_settings` text,\n `text` text,\n `url` text,\n `paid_partnership` integer,\n `possibly_sensitive` integer,\n `edit_controls_editable_until` integer,\n `edit_controls_edits_remaining` integer,\n `edit_controls_is_edit_eligible` integer,\n `like_count` integer,\n `retweet_count` integer,\n `reply_count` integer,\n `quote_count` integer,\n `bookmark_count` integer,\n `impression_count` integer,\n `article_cover_media` text,\n `article_plain_text` text,\n `article_preview_text` text,\n `article_title` text,\n `note_tweet_text` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_bookmarks_created_at_idx` ON `x_bookmarks` (`created_at`);--> statement-breakpoint\nCREATE TABLE `x_followers` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `username` text,\n `name` text,\n `description` text,\n `location` text,\n `url` text,\n `is_identity_verified` integer,\n `protected` integer,\n `verified` integer,\n `verified_type` text,\n `subscription_type` text,\n `parody` integer,\n `most_recent_tweet_id` text,\n `pinned_tweet_id` text,\n `profile_banner_url` text,\n `profile_image_url` text,\n `affiliation_badge_url` text,\n `affiliation_description` text,\n `affiliation_url` text,\n `subscription_subscribes_to_you` integer,\n `followers_count` integer,\n `following_count` integer,\n `tweet_count` integer,\n `listed_count` integer,\n `like_count` integer,\n `media_count` integer,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_followers_created_at_idx` ON `x_followers` (`created_at`);--> statement-breakpoint\nCREATE TABLE `x_following` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `username` text,\n `name` text,\n `description` text,\n `location` text,\n `url` text,\n `is_identity_verified` integer,\n `protected` integer,\n `verified` integer,\n `verified_type` text,\n `subscription_type` text,\n `parody` integer,\n `most_recent_tweet_id` text,\n `pinned_tweet_id` text,\n `profile_banner_url` text,\n `profile_image_url` text,\n `affiliation_badge_url` text,\n `affiliation_description` text,\n `affiliation_url` text,\n `subscription_subscribes_to_you` integer,\n `followers_count` integer,\n `following_count` integer,\n `tweet_count` integer,\n `listed_count` integer,\n `like_count` integer,\n `media_count` integer,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_following_created_at_idx` ON `x_following` (`created_at`);--> statement-breakpoint\nCREATE TABLE `x_tweets` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `author_id` text,\n `conversation_id` text,\n `in_reply_to_user_id` text,\n `lang` text,\n `reply_settings` text,\n `text` text,\n `url` text,\n `paid_partnership` integer,\n `possibly_sensitive` integer,\n `edit_controls_editable_until` integer,\n `edit_controls_edits_remaining` integer,\n `edit_controls_is_edit_eligible` integer,\n `like_count` integer,\n `retweet_count` integer,\n `reply_count` integer,\n `quote_count` integer,\n `bookmark_count` integer,\n `impression_count` integer,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_tweets_created_at_idx` ON `x_tweets` (`created_at`);"
|
|
407
698
|
},
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const rowsByProviderId = /* @__PURE__ */ new Map();
|
|
412
|
-
for (const row of rows) rowsByProviderId.set(row.providerId, row);
|
|
413
|
-
const columns = [
|
|
414
|
-
"provider_id",
|
|
415
|
-
"handle",
|
|
416
|
-
"display_name",
|
|
417
|
-
"avatar_url",
|
|
418
|
-
"headline",
|
|
419
|
-
"raw",
|
|
420
|
-
"synced_at"
|
|
421
|
-
];
|
|
422
|
-
const quotedTable = quoteIdentifier(table);
|
|
423
|
-
const nullableColumns = [
|
|
424
|
-
"handle",
|
|
425
|
-
"avatar_url",
|
|
426
|
-
"headline"
|
|
427
|
-
];
|
|
428
|
-
const lastWriteColumns = [
|
|
429
|
-
"display_name",
|
|
430
|
-
"raw",
|
|
431
|
-
"synced_at"
|
|
432
|
-
];
|
|
433
|
-
const updateAssignments = [...nullableColumns.map((column) => `${quoteIdentifier(column)}=COALESCE(excluded.${quoteIdentifier(column)}, ${quotedTable}.${quoteIdentifier(column)})`), ...lastWriteColumns.map((column) => `${quoteIdentifier(column)}=excluded.${quoteIdentifier(column)}`)].join(", ");
|
|
434
|
-
const statement = db.prepare(`INSERT INTO ${quotedTable} (${columns.map(quoteIdentifier).join(", ")}) VALUES (${columns.map((column) => `:${column}`).join(", ")}) ON CONFLICT(provider_id) DO UPDATE SET ${updateAssignments}`);
|
|
435
|
-
const syncedAt = Date.now();
|
|
436
|
-
db.transaction(() => {
|
|
437
|
-
for (const row of rowsByProviderId.values()) statement.run({
|
|
438
|
-
provider_id: row.providerId,
|
|
439
|
-
handle: row.handle,
|
|
440
|
-
display_name: row.displayName,
|
|
441
|
-
avatar_url: row.avatarURL,
|
|
442
|
-
headline: row.headline,
|
|
443
|
-
raw: JSON.stringify(row.raw),
|
|
444
|
-
synced_at: syncedAt
|
|
445
|
-
});
|
|
446
|
-
});
|
|
699
|
+
{
|
|
700
|
+
"tag": "0001_far_beast",
|
|
701
|
+
"sql": "CREATE TABLE `li_profiles` (\n `provider_id` text PRIMARY KEY NOT NULL,\n `handle` text,\n `display_name` text NOT NULL,\n `avatar_url` text,\n `headline` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `li_profiles_handle_idx` ON `li_profiles` (`handle`);--> statement-breakpoint\nCREATE TABLE `x_messages` (\n `id` text PRIMARY KEY NOT NULL,\n `created_at` integer,\n `dm_conversation_id` text,\n `event_type` text,\n `sender_id` text,\n `text` text,\n `participant_ids` text,\n `attachments` text,\n `entities` text,\n `referenced_tweets` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_messages_created_at_idx` ON `x_messages` (`created_at`);--> statement-breakpoint\nCREATE INDEX `x_messages_dm_conversation_id_idx` ON `x_messages` (`dm_conversation_id`);--> statement-breakpoint\nCREATE TABLE `x_profiles` (\n `provider_id` text PRIMARY KEY NOT NULL,\n `handle` text,\n `display_name` text NOT NULL,\n `avatar_url` text,\n `headline` text,\n `raw` text NOT NULL,\n `synced_at` integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX `x_profiles_handle_idx` ON `x_profiles` (`handle`);"
|
|
447
702
|
},
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return {
|
|
452
|
-
cursor: stringOrNull(row.cursor),
|
|
453
|
-
lastSynced: numberOrNull(row.last_synced),
|
|
454
|
-
objectCount: numberOrNull(row.object_count)
|
|
455
|
-
};
|
|
703
|
+
{
|
|
704
|
+
"tag": "0002_previous_spitfire",
|
|
705
|
+
"sql": "ALTER TABLE `li_connections` ADD `user_headline` text;--> statement-breakpoint\nALTER TABLE `li_requests` ADD `user_headline` text;--> statement-breakpoint\nALTER TABLE `x_bookmarks` DROP COLUMN `edit_controls_editable_until`;--> statement-breakpoint\nALTER TABLE `x_bookmarks` DROP COLUMN `edit_controls_edits_remaining`;--> statement-breakpoint\nALTER TABLE `x_bookmarks` DROP COLUMN `edit_controls_is_edit_eligible`;--> statement-breakpoint\nALTER TABLE `x_tweets` DROP COLUMN `edit_controls_editable_until`;--> statement-breakpoint\nALTER TABLE `x_tweets` DROP COLUMN `edit_controls_edits_remaining`;--> statement-breakpoint\nALTER TABLE `x_tweets` DROP COLUMN `edit_controls_is_edit_eligible`;"
|
|
456
706
|
},
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
cursor,
|
|
461
|
-
last_synced: lastSynced,
|
|
462
|
-
object_count: objectCount
|
|
463
|
-
});
|
|
707
|
+
{
|
|
708
|
+
"tag": "0003_wonderful_makkari",
|
|
709
|
+
"sql": "ALTER TABLE `li_conversations` RENAME COLUMN \"headline\" TO \"user_headline\";--> statement-breakpoint\nCREATE INDEX `li_conversations_user_id_last_message_timestamp_idx` ON `li_conversations` (`user_id`,`last_message_timestamp`);--> statement-breakpoint\nCREATE INDEX `li_conversations_user_public_identifier_last_message_timestamp_idx` ON `li_conversations` (`user_public_identifier`,`last_message_timestamp`);--> statement-breakpoint\nCREATE INDEX `li_conversations_user_display_name_idx` ON `li_conversations` (`user_display_name`);--> statement-breakpoint\nCREATE INDEX `li_conversations_is_archived_last_message_timestamp_idx` ON `li_conversations` (`is_archived`,`last_message_timestamp`);--> statement-breakpoint\nDROP INDEX `li_messages_chat_id_idx`;--> statement-breakpoint\nCREATE INDEX `li_messages_chat_id_timestamp_idx` ON `li_messages` (`chat_id`,`timestamp`);--> statement-breakpoint\nCREATE INDEX `li_messages_sender_id_timestamp_idx` ON `li_messages` (`sender_id`,`timestamp`);--> statement-breakpoint\nDROP INDEX `x_messages_dm_conversation_id_idx`;--> statement-breakpoint\nCREATE INDEX `x_messages_dm_conversation_id_created_at_idx` ON `x_messages` (`dm_conversation_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_messages_sender_id_created_at_idx` ON `x_messages` (`sender_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `li_connections_synced_at_idx` ON `li_connections` (`synced_at`);--> statement-breakpoint\nCREATE INDEX `li_connections_user_id_idx` ON `li_connections` (`user_id`);--> statement-breakpoint\nCREATE INDEX `li_connections_user_public_identifier_idx` ON `li_connections` (`user_public_identifier`);--> statement-breakpoint\nCREATE INDEX `li_connections_user_display_name_idx` ON `li_connections` (`user_display_name`);--> statement-breakpoint\nCREATE INDEX `li_posts_author_id_created_at_idx` ON `li_posts` (`author_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `li_posts_author_public_identifier_created_at_idx` ON `li_posts` (`author_public_identifier`,`created_at`);--> statement-breakpoint\nCREATE INDEX `li_requests_type_synced_at_idx` ON `li_requests` (`type`,`synced_at`);--> statement-breakpoint\nCREATE INDEX `li_requests_user_id_idx` ON `li_requests` (`user_id`);--> statement-breakpoint\nCREATE INDEX `li_requests_user_public_identifier_idx` ON `li_requests` (`user_public_identifier`);--> statement-breakpoint\nCREATE INDEX `li_requests_user_display_name_idx` ON `li_requests` (`user_display_name`);--> statement-breakpoint\nCREATE INDEX `x_bookmarks_author_id_created_at_idx` ON `x_bookmarks` (`author_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_bookmarks_conversation_id_created_at_idx` ON `x_bookmarks` (`conversation_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_followers_synced_at_idx` ON `x_followers` (`synced_at`);--> statement-breakpoint\nCREATE INDEX `x_followers_username_idx` ON `x_followers` (`username`);--> statement-breakpoint\nCREATE INDEX `x_followers_verified_idx` ON `x_followers` (`verified`);--> statement-breakpoint\nCREATE INDEX `x_followers_verified_type_idx` ON `x_followers` (`verified_type`);--> statement-breakpoint\nCREATE INDEX `x_following_synced_at_idx` ON `x_following` (`synced_at`);--> statement-breakpoint\nCREATE INDEX `x_following_username_idx` ON `x_following` (`username`);--> statement-breakpoint\nCREATE INDEX `x_following_verified_idx` ON `x_following` (`verified`);--> statement-breakpoint\nCREATE INDEX `x_following_verified_type_idx` ON `x_following` (`verified_type`);--> statement-breakpoint\nCREATE INDEX `x_tweets_author_id_created_at_idx` ON `x_tweets` (`author_id`,`created_at`);--> statement-breakpoint\nCREATE INDEX `x_tweets_conversation_id_created_at_idx` ON `x_tweets` (`conversation_id`,`created_at`);"
|
|
464
710
|
},
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
db.transaction(() => {
|
|
469
|
-
for (const table of tables) {
|
|
470
|
-
const result = db.prepare(`DELETE FROM ${quoteIdentifier(table)}`).run();
|
|
471
|
-
results.push({
|
|
472
|
-
table,
|
|
473
|
-
rowsDeleted: result?.changes ?? 0
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
const deleteState = db.prepare("DELETE FROM sync_state WHERE collection = :collection");
|
|
477
|
-
for (const collection of collections) deleteState.run({ collection });
|
|
478
|
-
});
|
|
479
|
-
return results;
|
|
711
|
+
{
|
|
712
|
+
"tag": "0004_quiet_prism",
|
|
713
|
+
"sql": "ALTER TABLE `x_messages` RENAME TO `x_raw_messages`;--> statement-breakpoint\nCREATE VIEW `x_messages` AS\nSELECT m.*, p.handle AS sender_handle, p.display_name AS sender_name,\n p.avatar_url AS sender_avatar_url, p.headline AS sender_headline\nFROM `x_raw_messages` m LEFT JOIN `x_profiles` p ON m.sender_id = p.provider_id;\n"
|
|
480
714
|
},
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
715
|
+
{
|
|
716
|
+
"tag": "0005_raw_table_views",
|
|
717
|
+
"sql": "DROP VIEW IF EXISTS \"x_messages\";\nALTER TABLE \"x_tweets\" RENAME TO \"x_tweets_raw\";\nALTER TABLE \"x_bookmarks\" RENAME TO \"x_bookmarks_raw\";\nALTER TABLE \"x_followers\" RENAME TO \"x_followers_raw\";\nALTER TABLE \"x_following\" RENAME TO \"x_following_raw\";\nALTER TABLE \"x_profiles\" RENAME TO \"x_profiles_raw\";\nALTER TABLE \"x_raw_messages\" RENAME TO \"x_messages_raw\";\nALTER TABLE \"li_connections\" RENAME TO \"li_connections_raw\";\nALTER TABLE \"li_profiles\" RENAME TO \"li_profiles_raw\";\nALTER TABLE \"li_conversations\" RENAME TO \"li_conversations_raw\";\nALTER TABLE \"li_messages\" RENAME TO \"li_messages_raw\";\nALTER TABLE \"li_posts\" RENAME TO \"li_posts_raw\";\nALTER TABLE \"li_requests\" RENAME TO \"li_requests_raw\";\nCREATE VIEW \"x_tweets\" AS\nSELECT \"id\", \"created_at\", \"author_id\", \"conversation_id\", \"in_reply_to_user_id\", \"lang\",\n \"reply_settings\", \"text\", \"url\", \"paid_partnership\", \"possibly_sensitive\",\n \"like_count\", \"retweet_count\", \"reply_count\", \"quote_count\", \"bookmark_count\",\n \"impression_count\"\nFROM \"x_tweets_raw\";\nCREATE VIEW \"x_bookmarks\" AS\nSELECT \"id\", \"created_at\", \"author_id\", \"conversation_id\", \"in_reply_to_user_id\", \"lang\",\n \"reply_settings\", \"text\", \"url\", \"paid_partnership\", \"possibly_sensitive\",\n \"like_count\", \"retweet_count\", \"reply_count\", \"quote_count\", \"bookmark_count\",\n \"impression_count\", \"article_cover_media\", \"article_plain_text\",\n \"article_preview_text\", \"article_title\", \"note_tweet_text\"\nFROM \"x_bookmarks_raw\";\nCREATE VIEW \"x_followers\" AS\nSELECT \"id\", \"created_at\", \"username\", \"name\", \"description\", \"location\", \"url\",\n \"verified\", \"verified_type\", \"is_identity_verified\", \"protected\",\n \"subscription_type\", \"parody\", \"followers_count\", \"following_count\",\n \"tweet_count\", \"listed_count\", \"like_count\", \"media_count\", \"most_recent_tweet_id\",\n \"pinned_tweet_id\", \"profile_image_url\" AS \"avatar_url\", \"profile_banner_url\",\n \"affiliation_description\", \"affiliation_url\", \"affiliation_badge_url\",\n \"subscription_subscribes_to_you\"\nFROM \"x_followers_raw\";\nCREATE VIEW \"x_following\" AS\nSELECT \"id\", \"created_at\", \"username\", \"name\", \"description\", \"location\", \"url\",\n \"verified\", \"verified_type\", \"is_identity_verified\", \"protected\",\n \"subscription_type\", \"parody\", \"followers_count\", \"following_count\",\n \"tweet_count\", \"listed_count\", \"like_count\", \"media_count\", \"most_recent_tweet_id\",\n \"pinned_tweet_id\", \"profile_image_url\" AS \"avatar_url\", \"profile_banner_url\",\n \"affiliation_description\", \"affiliation_url\", \"affiliation_badge_url\",\n \"subscription_subscribes_to_you\"\nFROM \"x_following_raw\";\nCREATE VIEW \"x_messages\" AS\nSELECT m.\"id\", m.\"created_at\", m.\"dm_conversation_id\" AS \"conversation_id\",\n m.\"event_type\", m.\"sender_id\", m.\"text\", m.\"participant_ids\", m.\"attachments\",\n m.\"entities\", m.\"referenced_tweets\", p.\"handle\" AS \"sender_username\",\n p.\"display_name\" AS \"sender_name\", p.\"avatar_url\" AS \"sender_avatar_url\",\n p.\"headline\" AS \"sender_headline\"\nFROM \"x_messages_raw\" m\nLEFT JOIN \"x_profiles_raw\" p ON m.\"sender_id\" = p.\"provider_id\";\nCREATE VIEW \"x_profiles\" AS\nSELECT \"provider_id\", \"handle\" AS \"username\", \"display_name\" AS \"name\", \"avatar_url\",\n \"headline\", json_extract(\"raw\", '$.receives_your_dm') AS \"receives_your_dm\"\nFROM \"x_profiles_raw\";\nCREATE VIEW \"li_profiles\" AS\nSELECT \"provider_id\", \"handle\" AS \"username\", \"display_name\" AS \"name\", \"avatar_url\",\n \"headline\"\nFROM \"li_profiles_raw\";\nCREATE VIEW \"li_connections\" AS\nSELECT \"id\", \"created_at\", \"url\", \"user_id\", \"user_public_identifier\" AS \"user_username\",\n \"user_display_name\" AS \"user_name\", \"user_headline\", \"user_description\",\n \"user_profile_url\", \"user_public_picture_url\" AS \"user_avatar_url\", \"user_type\"\nFROM \"li_connections_raw\";\nCREATE VIEW \"li_requests\" AS\nSELECT \"id\", \"created_at\", \"type\", json_extract(\"raw\", '$.message') AS \"message\",\n \"user_id\", \"user_public_identifier\" AS \"user_username\",\n \"user_display_name\" AS \"user_name\", \"user_headline\", \"user_description\",\n \"user_profile_url\", \"user_public_picture_url\" AS \"user_avatar_url\", \"user_type\"\nFROM \"li_requests_raw\";\nCREATE VIEW \"li_conversations\" AS\nSELECT \"id\", \"created_at\", \"last_message_timestamp\" AS \"last_message_at\", \"name\",\n CASE\n WHEN \"is_1to1\" = 1 THEN '1to1'\n WHEN \"is_group\" = 1 THEN 'group'\n ELSE \"type\"\n END AS \"type\",\n \"unread_count\", \"is_archived\", \"is_pinned\", \"is_readonly\", \"is_channel\",\n \"muted_until\", \"image_url\", \"creator_id\", \"user_id\",\n \"user_public_identifier\" AS \"user_username\", \"user_display_name\" AS \"user_name\",\n \"user_headline\", \"user_description\", \"user_profile_url\",\n \"user_public_picture_url\" AS \"user_avatar_url\", \"user_type\",\n \"user_specifics_network_distance\" AS \"user_distance\"\nFROM \"li_conversations_raw\";\nCREATE VIEW \"li_messages\" AS\nSELECT \"id\", \"timestamp\" AS \"created_at\", \"chat_id\" AS \"conversation_id\", \"text\",\n \"sender_id\", \"is_deleted\", \"is_delivered\", \"is_edited\", \"is_event\", \"is_hidden\",\n \"is_mentionned\" AS \"is_mentioned\", \"is_pinned\", \"is_seen\", \"is_sender\",\n \"sender_public_identifier\" AS \"sender_username\",\n \"sender_display_name\" AS \"sender_name\", \"sender_description\" AS \"sender_headline\",\n \"sender_public_picture_url\" AS \"sender_avatar_url\", \"sender_profile_url\",\n \"sender_type\", \"sender_specifics_network_distance\" AS \"sender_distance\"\nFROM \"li_messages_raw\";\nCREATE VIEW \"li_posts\" AS\nSELECT \"id\", \"created_at\", \"text\", \"url\", \"share_url\", \"is_repost\", \"user_reacted\",\n \"comments_counter\", \"reposts_counter\",\n \"analytics_impressions_counter\" AS \"impressions_counter\", \"author_id\",\n \"author_public_identifier\" AS \"author_username\",\n \"author_display_name\" AS \"author_name\", \"author_description\", \"author_profile_url\",\n \"author_public_picture_url\" AS \"author_avatar_url\", \"author_type\",\n \"author_specifics_network_distance\" AS \"author_distance\", \"poll_id\", \"poll_question\",\n \"poll_is_open\", \"poll_total_votes_count\", \"quoted_post_id\", \"quoted_post_created_at\",\n \"quoted_post_text\", \"quoted_post_author_public_identifier\" AS \"quoted_post_author_username\",\n \"quoted_post_author_display_name\" AS \"quoted_post_author_name\", \"reposted_by_id\",\n \"reposted_by_public_identifier\" AS \"reposted_by_username\",\n \"reposted_by_display_name\" AS \"reposted_by_name\"\nFROM \"li_posts_raw\";\n"
|
|
484
718
|
},
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const policy = freshnessPolicyForTable(name);
|
|
489
|
-
return {
|
|
490
|
-
name,
|
|
491
|
-
missingName: missingNameForPolicy(name, policy),
|
|
492
|
-
command: syncCommandForPolicy(opts.platform, policy),
|
|
493
|
-
lastSynced: lastSyncedForPolicy(db, policy)
|
|
494
|
-
};
|
|
495
|
-
});
|
|
496
|
-
const missingFreshness = missingFreshnessForTables(tables);
|
|
497
|
-
if (missingFreshness.length > 0) throw new NeverSyncedError(missingFreshness.map((item) => item.name), neverSyncedMessage(missingFreshness));
|
|
498
|
-
return {
|
|
499
|
-
rows: db.prepare(sql).all(),
|
|
500
|
-
tables: tables.map(({ name, lastSynced }) => ({
|
|
501
|
-
name,
|
|
502
|
-
lastSynced
|
|
503
|
-
}))
|
|
504
|
-
};
|
|
505
|
-
},
|
|
506
|
-
describe: (opts) => {
|
|
507
|
-
const allowedTables = opts?.platform ? tablesForPlatform(opts.platform) : void 0;
|
|
508
|
-
const tables = db.prepare("SELECT name, type, sql FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name").all().filter((table) => String(table.name) !== "x_raw_messages").filter((table) => allowedTables === void 0 || allowedTables.has(String(table.name)));
|
|
509
|
-
const lastSyncedForTable = (name) => {
|
|
510
|
-
if (name === "sync_state") return null;
|
|
511
|
-
return lastSyncedForPolicy(db, freshnessPolicyForTable(name));
|
|
512
|
-
};
|
|
513
|
-
return {
|
|
514
|
-
path,
|
|
515
|
-
tables: tables.map((table) => {
|
|
516
|
-
const name = String(table.name);
|
|
517
|
-
const type = table.type === "view" ? "view" : "table";
|
|
518
|
-
const rowCount = db.prepare(`SELECT count(*) AS rows FROM ${quoteIdentifier(name)}`).get();
|
|
519
|
-
return {
|
|
520
|
-
name,
|
|
521
|
-
type,
|
|
522
|
-
sql: String(table.sql),
|
|
523
|
-
rows: Number(rowCount?.rows ?? 0),
|
|
524
|
-
last_synced: lastSyncedForTable(name)
|
|
525
|
-
};
|
|
526
|
-
})
|
|
527
|
-
};
|
|
719
|
+
{
|
|
720
|
+
"tag": "0006_username_profiles",
|
|
721
|
+
"sql": "DROP VIEW IF EXISTS \"x_messages\";\nDROP VIEW IF EXISTS \"x_profiles\";\nDROP VIEW IF EXISTS \"li_profiles\";\nALTER TABLE \"x_profiles_raw\" RENAME COLUMN \"handle\" TO \"username\";\nALTER TABLE \"li_profiles_raw\" RENAME COLUMN \"handle\" TO \"username\";\nDROP INDEX IF EXISTS \"x_profiles_handle_idx\";\nDROP INDEX IF EXISTS \"li_profiles_handle_idx\";\nCREATE INDEX \"x_profiles_username_idx\" ON \"x_profiles_raw\" (\"username\");\nCREATE INDEX \"li_profiles_username_idx\" ON \"li_profiles_raw\" (\"username\");\nCREATE VIEW \"x_messages\" AS\nSELECT m.\"id\", m.\"created_at\", m.\"dm_conversation_id\" AS \"conversation_id\",\n m.\"event_type\", m.\"sender_id\", m.\"text\", m.\"participant_ids\", m.\"attachments\",\n m.\"entities\", m.\"referenced_tweets\", p.\"username\" AS \"sender_username\",\n p.\"display_name\" AS \"sender_name\", p.\"avatar_url\" AS \"sender_avatar_url\",\n p.\"headline\" AS \"sender_headline\"\nFROM \"x_messages_raw\" m\nLEFT JOIN \"x_profiles_raw\" p ON m.\"sender_id\" = p.\"provider_id\";\nCREATE VIEW \"x_profiles\" AS\nSELECT \"provider_id\", \"username\", \"display_name\" AS \"name\", \"avatar_url\",\n \"headline\", json_extract(\"raw\", '$.receives_your_dm') AS \"receives_your_dm\"\nFROM \"x_profiles_raw\";\nCREATE VIEW \"li_profiles\" AS\nSELECT \"provider_id\", \"username\", \"display_name\" AS \"name\", \"avatar_url\", \"headline\"\nFROM \"li_profiles_raw\";\n"
|
|
528
722
|
},
|
|
529
|
-
|
|
530
|
-
|
|
723
|
+
{
|
|
724
|
+
"tag": "0007_connection_headline_coalesce",
|
|
725
|
+
"sql": "DROP VIEW IF EXISTS \"li_connections\";\nDROP VIEW IF EXISTS \"li_requests\";\nDROP VIEW IF EXISTS \"li_conversations\";\nCREATE VIEW \"li_connections\" AS\nSELECT \"id\", \"created_at\", \"url\", \"user_id\", \"user_public_identifier\" AS \"user_username\",\n \"user_display_name\" AS \"user_name\",\n COALESCE(\"user_headline\", \"user_description\") AS \"user_headline\", \"user_description\",\n \"user_profile_url\", \"user_public_picture_url\" AS \"user_avatar_url\", \"user_type\"\nFROM \"li_connections_raw\";\nCREATE VIEW \"li_requests\" AS\nSELECT \"id\", \"created_at\", \"type\", json_extract(\"raw\", '$.message') AS \"message\",\n \"user_id\", \"user_public_identifier\" AS \"user_username\",\n \"user_display_name\" AS \"user_name\",\n COALESCE(\"user_headline\", \"user_description\") AS \"user_headline\", \"user_description\",\n \"user_profile_url\", \"user_public_picture_url\" AS \"user_avatar_url\", \"user_type\"\nFROM \"li_requests_raw\";\nCREATE VIEW \"li_conversations\" AS\nSELECT \"id\", \"created_at\", \"last_message_timestamp\" AS \"last_message_at\", \"name\",\n CASE\n WHEN \"is_1to1\" = 1 THEN '1to1'\n WHEN \"is_group\" = 1 THEN 'group'\n ELSE \"type\"\n END AS \"type\",\n \"unread_count\", \"is_archived\", \"is_pinned\", \"is_readonly\", \"is_channel\",\n \"muted_until\", \"image_url\", \"creator_id\", \"user_id\",\n \"user_public_identifier\" AS \"user_username\", \"user_display_name\" AS \"user_name\",\n COALESCE(\"user_headline\", \"user_description\") AS \"user_headline\", \"user_description\",\n \"user_profile_url\", \"user_public_picture_url\" AS \"user_avatar_url\", \"user_type\",\n \"user_specifics_network_distance\" AS \"user_distance\"\nFROM \"li_conversations_raw\";\n"
|
|
531
726
|
}
|
|
532
|
-
|
|
727
|
+
];
|
|
533
728
|
//#endregion
|
|
534
|
-
//#region ../../packages/sync-engine/src/
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
cursorParam: "cursor",
|
|
539
|
-
cursorPath: "next_cursor",
|
|
540
|
-
totalCountPath: "total_count",
|
|
541
|
-
itemsPath: "data"
|
|
729
|
+
//#region ../../packages/sync-engine/src/migrate.ts
|
|
730
|
+
const readUserVersion = (db) => {
|
|
731
|
+
const version = db.prepare("PRAGMA user_version").get()?.user_version;
|
|
732
|
+
return typeof version === "number" ? version : Number(version ?? 0);
|
|
542
733
|
};
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
limitParam: "limit",
|
|
546
|
-
cursorParam: "cursor",
|
|
547
|
-
cursorPath: "next_cursor",
|
|
548
|
-
itemsPath: "data"
|
|
734
|
+
const setUserVersion = (db, version) => {
|
|
735
|
+
db.exec(`PRAGMA user_version = ${version}`);
|
|
549
736
|
};
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
737
|
+
const migrate = (db) => {
|
|
738
|
+
const currentVersion = readUserVersion(db);
|
|
739
|
+
if (currentVersion >= MIGRATIONS.length) return {
|
|
740
|
+
fromVersion: currentVersion,
|
|
741
|
+
toVersion: currentVersion,
|
|
742
|
+
applied: 0
|
|
743
|
+
};
|
|
744
|
+
for (let index = currentVersion; index < MIGRATIONS.length; index += 1) {
|
|
745
|
+
const migration = MIGRATIONS[index];
|
|
746
|
+
if (!migration) throw new Error(`Missing migration at index ${index}`);
|
|
747
|
+
db.transaction(() => {
|
|
748
|
+
db.exec(migration.sql);
|
|
749
|
+
setUserVersion(db, index + 1);
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return {
|
|
753
|
+
fromVersion: currentVersion,
|
|
754
|
+
toVersion: MIGRATIONS.length,
|
|
755
|
+
applied: MIGRATIONS.length - currentVersion
|
|
756
|
+
};
|
|
557
757
|
};
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
if (
|
|
561
|
-
const epochMs = Date.parse(value);
|
|
562
|
-
return Number.isNaN(epochMs) ? null : epochMs;
|
|
758
|
+
const assertAtLatest = (db) => {
|
|
759
|
+
const currentVersion = readUserVersion(db);
|
|
760
|
+
if (currentVersion < MIGRATIONS.length) throw new Error(`Local SQLite cache schema is at version ${currentVersion}; expected ${MIGRATIONS.length}`);
|
|
563
761
|
};
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
762
|
+
//#endregion
|
|
763
|
+
//#region ../../packages/sync-engine/src/sqlite.ts
|
|
764
|
+
const loadModule = createRequire(import.meta.url);
|
|
765
|
+
const isBunRuntime = () => typeof Bun !== "undefined";
|
|
766
|
+
const normalizeValue = (value) => {
|
|
767
|
+
if (value === null || value === void 0) return null;
|
|
768
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
769
|
+
return value;
|
|
567
770
|
};
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
return
|
|
571
|
-
id: read$1(item.id),
|
|
572
|
-
created_at: isoToEpochMs$1(item.created_at),
|
|
573
|
-
object: read$1(item.object),
|
|
574
|
-
url: read$1(item.url),
|
|
575
|
-
user_id: read$1(user.id),
|
|
576
|
-
user_object: read$1(user.object),
|
|
577
|
-
user_type: read$1(user.type),
|
|
578
|
-
user_display_name: read$1(user.display_name),
|
|
579
|
-
user_public_identifier: read$1(user.public_identifier),
|
|
580
|
-
user_profile_url: read$1(user.profile_url),
|
|
581
|
-
user_public_picture_url: read$1(user.public_picture_url),
|
|
582
|
-
user_description: read$1(user.description),
|
|
583
|
-
user_headline: userHeadline(user),
|
|
584
|
-
raw: JSON.stringify(item)
|
|
585
|
-
};
|
|
771
|
+
const normalizeParams$1 = (params) => {
|
|
772
|
+
if (!params) return;
|
|
773
|
+
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, normalizeValue(value)]));
|
|
586
774
|
};
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
return {
|
|
591
|
-
id: read$1(item.id),
|
|
592
|
-
created_at: isoToEpochMs$1(item.created_at),
|
|
593
|
-
last_message_timestamp: isoToEpochMs$1(item.last_message_timestamp),
|
|
594
|
-
muted_until: isoToEpochMs$1(item.muted_until),
|
|
595
|
-
creator_id: read$1(item.creator_id),
|
|
596
|
-
user_headline: userHeadline(user),
|
|
597
|
-
image_url: read$1(item.image_url),
|
|
598
|
-
name: read$1(item.name),
|
|
599
|
-
object: read$1(item.object),
|
|
600
|
-
provider: read$1(item.provider),
|
|
601
|
-
type: read$1(item.type),
|
|
602
|
-
user_id: read$1(item.user_id ?? user.id),
|
|
603
|
-
is_1to1: toBool$1(item.is_1to1),
|
|
604
|
-
is_archived: toBool$1(item.is_archived),
|
|
605
|
-
is_channel: toBool$1(item.is_channel),
|
|
606
|
-
is_group: toBool$1(item.is_group),
|
|
607
|
-
is_pinned: toBool$1(item.is_pinned),
|
|
608
|
-
is_readonly: toBool$1(item.is_readonly),
|
|
609
|
-
unread_count: read$1(item.unread_count),
|
|
610
|
-
user_object: read$1(user.object),
|
|
611
|
-
user_type: read$1(user.type),
|
|
612
|
-
user_display_name: read$1(user.display_name),
|
|
613
|
-
user_public_identifier: read$1(user.public_identifier),
|
|
614
|
-
user_profile_url: read$1(user.profile_url),
|
|
615
|
-
user_public_picture_url: read$1(user.public_picture_url),
|
|
616
|
-
user_description: read$1(user.description),
|
|
617
|
-
user_specifics_network_distance: read$1(userSpecifics.network_distance),
|
|
618
|
-
raw: JSON.stringify(item)
|
|
619
|
-
};
|
|
775
|
+
const invokeWithParams = (fn, params) => {
|
|
776
|
+
const normalizedParams = normalizeParams$1(params);
|
|
777
|
+
return normalizedParams ? fn(normalizedParams) : fn();
|
|
620
778
|
};
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
is_hidden: toBool$1(item.is_hidden),
|
|
637
|
-
is_mentionned: toBool$1(item.is_mentionned),
|
|
638
|
-
is_pinned: toBool$1(item.is_pinned),
|
|
639
|
-
is_seen: toBool$1(item.is_seen),
|
|
640
|
-
is_sender: toBool$1(item.is_sender),
|
|
641
|
-
sender_object: read$1(sender.object),
|
|
642
|
-
sender_type: read$1(sender.type),
|
|
643
|
-
sender_display_name: read$1(sender.display_name),
|
|
644
|
-
sender_public_identifier: read$1(sender.public_identifier),
|
|
645
|
-
sender_profile_url: read$1(sender.profile_url),
|
|
646
|
-
sender_public_picture_url: read$1(sender.public_picture_url),
|
|
647
|
-
sender_description: read$1(sender.description),
|
|
648
|
-
sender_specifics_network_distance: read$1(senderSpecifics.network_distance),
|
|
649
|
-
raw: JSON.stringify(item)
|
|
650
|
-
};
|
|
779
|
+
const wrapStatement = (statement) => ({
|
|
780
|
+
run: (params) => invokeWithParams(statement.run.bind(statement), params),
|
|
781
|
+
get: (params) => invokeWithParams(statement.get.bind(statement), params),
|
|
782
|
+
all: (params) => invokeWithParams(statement.all.bind(statement), params)
|
|
783
|
+
});
|
|
784
|
+
const openRuntimeDatabase = (path, opts) => {
|
|
785
|
+
if (isBunRuntime()) {
|
|
786
|
+
const { Database } = loadModule("bun:sqlite");
|
|
787
|
+
return new Database(path, {
|
|
788
|
+
readonly: opts?.readonly,
|
|
789
|
+
strict: true
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const { DatabaseSync } = loadModule("node:sqlite");
|
|
793
|
+
return opts?.readonly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
|
|
651
794
|
};
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
const permissions = asRecord$1(item.permissions);
|
|
657
|
-
const poll = asRecord$1(item.poll);
|
|
658
|
-
const quotedPost = asRecord$1(item.quoted_post);
|
|
659
|
-
const quotedPostPermissions = asRecord$1(quotedPost.permissions);
|
|
660
|
-
const quotedPostAuthor = asRecord$1(quotedPost.author);
|
|
661
|
-
const quotedPostAuthorSpecifics = asRecord$1(quotedPostAuthor.specifics);
|
|
662
|
-
const repostedBy = asRecord$1(item.reposted_by);
|
|
663
|
-
return {
|
|
664
|
-
id: read$1(item.id),
|
|
665
|
-
created_at: isoToEpochMs$1(item.created_at),
|
|
666
|
-
object: read$1(item.object),
|
|
667
|
-
share_url: read$1(item.share_url),
|
|
668
|
-
text: read$1(item.text),
|
|
669
|
-
url: read$1(item.url),
|
|
670
|
-
is_repost: toBool$1(item.is_repost),
|
|
671
|
-
user_reacted: readReaction(item.user_reacted),
|
|
672
|
-
comments_counter: read$1(item.comments_counter),
|
|
673
|
-
reposts_counter: read$1(item.reposts_counter),
|
|
674
|
-
analytics_impressions_counter: read$1(analytics.impressions_counter),
|
|
675
|
-
author_id: read$1(author.id),
|
|
676
|
-
author_object: read$1(author.object),
|
|
677
|
-
author_type: read$1(author.type),
|
|
678
|
-
author_display_name: read$1(author.display_name),
|
|
679
|
-
author_public_identifier: read$1(author.public_identifier),
|
|
680
|
-
author_profile_url: read$1(author.profile_url),
|
|
681
|
-
author_public_picture_url: read$1(author.public_picture_url),
|
|
682
|
-
author_description: read$1(author.description),
|
|
683
|
-
author_specifics_network_distance: read$1(authorSpecifics.network_distance),
|
|
684
|
-
permissions_can_post_comments: toBool$1(permissions.can_post_comments),
|
|
685
|
-
permissions_can_react: toBool$1(permissions.can_react),
|
|
686
|
-
permissions_can_share: toBool$1(permissions.can_share),
|
|
687
|
-
poll_id: read$1(poll.id),
|
|
688
|
-
poll_question: read$1(poll.question),
|
|
689
|
-
poll_is_open: toBool$1(poll.is_open),
|
|
690
|
-
poll_total_votes_count: read$1(poll.total_votes_count),
|
|
691
|
-
quoted_post_id: read$1(quotedPost.id),
|
|
692
|
-
quoted_post_object: read$1(quotedPost.object),
|
|
693
|
-
quoted_post_created_at: isoToEpochMs$1(quotedPost.created_at),
|
|
694
|
-
quoted_post_text: read$1(quotedPost.text),
|
|
695
|
-
quoted_post_share_url: read$1(quotedPost.share_url),
|
|
696
|
-
quoted_post_user_reacted: readReaction(quotedPost.user_reacted),
|
|
697
|
-
quoted_post_comments_counter: read$1(quotedPost.comments_counter),
|
|
698
|
-
quoted_post_reposts_counter: read$1(quotedPost.reposts_counter),
|
|
699
|
-
quoted_post_permissions_can_post_comments: toBool$1(quotedPostPermissions.can_post_comments),
|
|
700
|
-
quoted_post_permissions_can_react: toBool$1(quotedPostPermissions.can_react),
|
|
701
|
-
quoted_post_permissions_can_share: toBool$1(quotedPostPermissions.can_share),
|
|
702
|
-
quoted_post_author_id: read$1(quotedPostAuthor.id),
|
|
703
|
-
quoted_post_author_object: read$1(quotedPostAuthor.object),
|
|
704
|
-
quoted_post_author_type: read$1(quotedPostAuthor.type),
|
|
705
|
-
quoted_post_author_display_name: read$1(quotedPostAuthor.display_name),
|
|
706
|
-
quoted_post_author_public_identifier: read$1(quotedPostAuthor.public_identifier),
|
|
707
|
-
quoted_post_author_profile_url: read$1(quotedPostAuthor.profile_url),
|
|
708
|
-
quoted_post_author_public_picture_url: read$1(quotedPostAuthor.public_picture_url),
|
|
709
|
-
quoted_post_author_description: read$1(quotedPostAuthor.description),
|
|
710
|
-
quoted_post_author_specifics_network_distance: read$1(quotedPostAuthorSpecifics.network_distance),
|
|
711
|
-
reposted_by_id: read$1(repostedBy.id),
|
|
712
|
-
reposted_by_object: read$1(repostedBy.object),
|
|
713
|
-
reposted_by_type: read$1(repostedBy.type),
|
|
714
|
-
reposted_by_display_name: read$1(repostedBy.display_name),
|
|
715
|
-
reposted_by_public_identifier: read$1(repostedBy.public_identifier),
|
|
716
|
-
reposted_by_profile_url: read$1(repostedBy.profile_url),
|
|
717
|
-
reposted_by_public_picture_url: read$1(repostedBy.public_picture_url),
|
|
718
|
-
raw: JSON.stringify(item)
|
|
795
|
+
const openDatabase = (path, opts) => {
|
|
796
|
+
const database = openRuntimeDatabase(path, opts);
|
|
797
|
+
const exec = (sql) => {
|
|
798
|
+
database.exec(sql);
|
|
719
799
|
};
|
|
720
|
-
};
|
|
721
|
-
const liftRequest = (item) => {
|
|
722
|
-
const user = asRecord$1(item.user);
|
|
723
800
|
return {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
801
|
+
exec,
|
|
802
|
+
prepare: (sql) => wrapStatement(database.prepare(sql)),
|
|
803
|
+
transaction: (fn) => {
|
|
804
|
+
exec("BEGIN");
|
|
805
|
+
try {
|
|
806
|
+
fn();
|
|
807
|
+
exec("COMMIT");
|
|
808
|
+
} catch (error) {
|
|
809
|
+
exec("ROLLBACK");
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
close: () => {
|
|
814
|
+
database.close();
|
|
815
|
+
}
|
|
738
816
|
};
|
|
739
817
|
};
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region ../../packages/sync-engine/src/cache.ts
|
|
820
|
+
var NeverSyncedError = class extends Error {
|
|
821
|
+
name = "NeverSyncedError";
|
|
822
|
+
tables;
|
|
823
|
+
constructor(tables, message) {
|
|
824
|
+
super(message);
|
|
825
|
+
this.tables = tables;
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
const publicCacheTables = [
|
|
829
|
+
"x_tweets",
|
|
830
|
+
"x_followers",
|
|
831
|
+
"x_following",
|
|
832
|
+
"x_bookmarks",
|
|
833
|
+
"x_profiles",
|
|
834
|
+
"x_messages",
|
|
835
|
+
"li_conversations",
|
|
836
|
+
"li_messages",
|
|
837
|
+
"li_connections",
|
|
838
|
+
"li_posts",
|
|
839
|
+
"li_profiles",
|
|
840
|
+
"li_requests",
|
|
841
|
+
"sync_state"
|
|
842
|
+
];
|
|
843
|
+
const rawCacheTables = publicCacheTables.filter((table) => table !== "sync_state").map((table) => `${table}_raw`);
|
|
844
|
+
const cacheTables = new Set([...publicCacheTables, ...rawCacheTables]);
|
|
845
|
+
const profileTables = new Set(["x_profiles_raw", "li_profiles_raw"]);
|
|
846
|
+
const sharedTables = new Set(["sync_state"]);
|
|
847
|
+
const platformTables = {
|
|
848
|
+
linkedin: new Set([...cacheTables].filter((table) => table.startsWith("li_"))),
|
|
849
|
+
x: new Set([...cacheTables].filter((table) => table.startsWith("x_")))
|
|
850
|
+
};
|
|
851
|
+
const sanitizeAccountId = (accountId) => accountId.replaceAll(/[^A-Za-z0-9._-]/g, "_");
|
|
852
|
+
const quoteIdentifier = (identifier) => `\`${identifier.replaceAll("`", "``")}\``;
|
|
853
|
+
const resolveCachePath = (path) => path === ":memory:" ? path : resolve(path);
|
|
854
|
+
const rawTableFor = (table) => table === "sync_state" ? table : `${table}_raw`;
|
|
855
|
+
const collectionForReadTable = (table) => table.endsWith("_raw") ? table.slice(0, -4) : table;
|
|
856
|
+
const physicalTableForCacheTable = (table) => table === "sync_state" || table.endsWith("_raw") ? table : rawTableFor(table);
|
|
857
|
+
const stringOrNull = (value) => typeof value === "string" ? value : null;
|
|
858
|
+
const numberOrNull = (value) => typeof value === "number" ? value : null;
|
|
859
|
+
const assertKnownTable = (table) => {
|
|
860
|
+
if (!cacheTables.has(table)) throw new Error(`Unknown cache table: ${table}`);
|
|
861
|
+
};
|
|
862
|
+
const tablesForPlatform = (platform) => new Set([...platformTables[platform], ...sharedTables]);
|
|
863
|
+
const knownTableTokens = (sql) => {
|
|
864
|
+
const tokens = sql.match(/\b[A-Za-z_][A-Za-z0-9_]*\b/g) ?? [];
|
|
865
|
+
const tables = [];
|
|
866
|
+
const seen = /* @__PURE__ */ new Set();
|
|
867
|
+
for (const token of tokens) {
|
|
868
|
+
const table = token.toLowerCase();
|
|
869
|
+
if (cacheTables.has(table) && !seen.has(table)) {
|
|
870
|
+
seen.add(table);
|
|
871
|
+
tables.push(table);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return tables;
|
|
875
|
+
};
|
|
876
|
+
const assertPlatformQuery = (sql, platform) => {
|
|
877
|
+
const allowedTables = tablesForPlatform(platform);
|
|
878
|
+
const rejectedTables = new Set(knownTableTokens(sql).filter((table) => !allowedTables.has(table)));
|
|
879
|
+
if (rejectedTables.size > 0) throw new Error(`Cache SQL for ${platform} cannot read ${[...rejectedTables].sort().join(", ")}.`);
|
|
880
|
+
};
|
|
881
|
+
const referencedTablesForRead = (sql, platform) => {
|
|
882
|
+
const allowedTables = platformTables[platform];
|
|
883
|
+
return knownTableTokens(sql).filter((table) => allowedTables.has(table));
|
|
884
|
+
};
|
|
885
|
+
const collectionShortName = (collection) => collection.replace(/^(x|li)_/, "");
|
|
886
|
+
const tableFreshnessPolicies = {
|
|
887
|
+
x_messages_raw: {
|
|
888
|
+
kind: "collection",
|
|
889
|
+
collection: "x_messages"
|
|
890
|
+
},
|
|
891
|
+
li_conversations: {
|
|
892
|
+
kind: "collection",
|
|
893
|
+
collection: "li_conversations",
|
|
894
|
+
commandCollection: "li_messages"
|
|
749
895
|
},
|
|
750
|
-
{
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
parentCollection: "li_conversations",
|
|
755
|
-
parentIdToken: ":chat_id",
|
|
756
|
-
pagination: linkedinCursorPagination,
|
|
757
|
-
pageSize: 250,
|
|
758
|
-
sinceField: "timestamp",
|
|
759
|
-
lift: liftMessage
|
|
896
|
+
li_conversations_raw: {
|
|
897
|
+
kind: "collection",
|
|
898
|
+
collection: "li_conversations",
|
|
899
|
+
commandCollection: "li_messages"
|
|
760
900
|
},
|
|
761
|
-
{
|
|
762
|
-
|
|
763
|
-
platform: "linkedin"
|
|
764
|
-
requests: [{ path: "/users/connections" }],
|
|
765
|
-
requiresFullInitialSync: true,
|
|
766
|
-
pagination: linkedinRelationsCursorPagination,
|
|
767
|
-
pageSize: 100,
|
|
768
|
-
sinceField: "created_at",
|
|
769
|
-
lift: liftConnection
|
|
901
|
+
li_profiles: {
|
|
902
|
+
kind: "platform",
|
|
903
|
+
platform: "linkedin"
|
|
770
904
|
},
|
|
771
|
-
{
|
|
772
|
-
|
|
773
|
-
platform: "linkedin"
|
|
774
|
-
requests: [{ path: "/users/:identifier/posts" }],
|
|
775
|
-
ownIdToken: ":identifier",
|
|
776
|
-
pagination: linkedinCursorPagination,
|
|
777
|
-
pageSize: 50,
|
|
778
|
-
sinceField: "created_at",
|
|
779
|
-
lift: liftPost
|
|
905
|
+
li_profiles_raw: {
|
|
906
|
+
kind: "platform",
|
|
907
|
+
platform: "linkedin"
|
|
780
908
|
},
|
|
781
|
-
{
|
|
782
|
-
|
|
783
|
-
platform: "
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
path: "/users/me/relation-requests/sent",
|
|
789
|
-
bakedQuery: { type: "sent" }
|
|
790
|
-
}],
|
|
791
|
-
pagination: linkedinRelationsCursorPagination,
|
|
792
|
-
pageSize: 100,
|
|
793
|
-
lift: liftRequest
|
|
909
|
+
x_profiles: {
|
|
910
|
+
kind: "platform",
|
|
911
|
+
platform: "x"
|
|
912
|
+
},
|
|
913
|
+
x_profiles_raw: {
|
|
914
|
+
kind: "platform",
|
|
915
|
+
platform: "x"
|
|
794
916
|
}
|
|
795
|
-
];
|
|
796
|
-
//#endregion
|
|
797
|
-
//#region ../../packages/sync-engine/src/collections/x.ts
|
|
798
|
-
const xPagination = {
|
|
799
|
-
style: "cursor",
|
|
800
|
-
limitParam: "max_results",
|
|
801
|
-
cursorParam: "pagination_token",
|
|
802
|
-
cursorPath: "meta.next_token",
|
|
803
|
-
itemsPath: "data"
|
|
804
917
|
};
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
809
|
-
const read = (value) => value === null || value === void 0 ? null : value;
|
|
810
|
-
const isoToEpochMs = (value) => {
|
|
811
|
-
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
812
|
-
if (typeof value !== "string") return null;
|
|
813
|
-
const epochMs = Date.parse(value);
|
|
814
|
-
return Number.isNaN(epochMs) ? null : epochMs;
|
|
918
|
+
const freshnessPolicyForTable = (table) => tableFreshnessPolicies[table] ?? {
|
|
919
|
+
kind: "collection",
|
|
920
|
+
collection: collectionForReadTable(table)
|
|
815
921
|
};
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
922
|
+
const platformCollectionPrefix = (platform) => platform === "linkedin" ? "li_" : "x_";
|
|
923
|
+
const syncCommandForPolicy = (platform, policy) => {
|
|
924
|
+
if (policy.kind === "platform") return `social ${policy.platform} sync`;
|
|
925
|
+
return `social ${platform} sync ${collectionShortName(policy.commandCollection ?? policy.collection)}`;
|
|
819
926
|
};
|
|
820
|
-
const
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
return {
|
|
824
|
-
|
|
825
|
-
created_at: isoToEpochMs(item.created_at),
|
|
826
|
-
author_id: read(item.author_id),
|
|
827
|
-
conversation_id: read(item.conversation_id),
|
|
828
|
-
in_reply_to_user_id: read(item.in_reply_to_user_id),
|
|
829
|
-
lang: read(item.lang),
|
|
830
|
-
reply_settings: read(item.reply_settings),
|
|
831
|
-
text: read(item.text),
|
|
832
|
-
url: read(item.url),
|
|
833
|
-
paid_partnership: toBool(item.paid_partnership),
|
|
834
|
-
possibly_sensitive: toBool(item.possibly_sensitive),
|
|
835
|
-
like_count: read(publicMetrics.like_count),
|
|
836
|
-
retweet_count: read(publicMetrics.retweet_count),
|
|
837
|
-
reply_count: read(publicMetrics.reply_count),
|
|
838
|
-
quote_count: read(publicMetrics.quote_count),
|
|
839
|
-
bookmark_count: read(publicMetrics.bookmark_count),
|
|
840
|
-
impression_count: read(publicMetrics.impression_count)
|
|
841
|
-
};
|
|
927
|
+
const missingNameForPolicy = (table, policy) => policy.kind === "platform" ? table : policy.collection;
|
|
928
|
+
const formatList = (items) => {
|
|
929
|
+
if (items.length <= 1) return items[0] ?? "";
|
|
930
|
+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
931
|
+
return `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
|
|
842
932
|
};
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
raw: JSON.stringify(item)
|
|
846
|
-
});
|
|
847
|
-
const liftBookmark = (item) => {
|
|
848
|
-
const article = asRecord(item.article);
|
|
849
|
-
const noteTweet = asRecord(item.note_tweet);
|
|
850
|
-
return {
|
|
851
|
-
...liftTweetColumns(item),
|
|
852
|
-
article_cover_media: read(article.cover_media),
|
|
853
|
-
article_plain_text: read(article.plain_text),
|
|
854
|
-
article_preview_text: read(article.preview_text),
|
|
855
|
-
article_title: read(article.title),
|
|
856
|
-
note_tweet_text: read(noteTweet.text),
|
|
857
|
-
raw: JSON.stringify(item)
|
|
858
|
-
};
|
|
933
|
+
const collectionLastSynced = (db, collection) => {
|
|
934
|
+
return numberOrNull(db.prepare("SELECT last_synced FROM sync_state WHERE collection = :collection").get({ collection })?.last_synced);
|
|
859
935
|
};
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
936
|
+
const platformLastSynced = (db, platform) => {
|
|
937
|
+
return numberOrNull(db.prepare("SELECT MAX(last_synced) AS last_synced FROM sync_state WHERE collection GLOB :glob").get({ glob: `${platformCollectionPrefix(platform)}*` })?.last_synced);
|
|
938
|
+
};
|
|
939
|
+
const lastSyncedForPolicy = (db, policy) => policy.kind === "platform" ? platformLastSynced(db, policy.platform) : collectionLastSynced(db, policy.collection);
|
|
940
|
+
const missingFreshnessForTables = (tables) => {
|
|
941
|
+
const missing = [];
|
|
942
|
+
const seen = /* @__PURE__ */ new Set();
|
|
943
|
+
for (const table of tables) {
|
|
944
|
+
if (table.lastSynced != null) continue;
|
|
945
|
+
const key = `${table.missingName}\0${table.command}`;
|
|
946
|
+
if (!seen.has(key)) {
|
|
947
|
+
seen.add(key);
|
|
948
|
+
missing.push({
|
|
949
|
+
name: table.missingName,
|
|
950
|
+
command: table.command
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return missing;
|
|
955
|
+
};
|
|
956
|
+
const neverSyncedMessage = (missing) => {
|
|
957
|
+
const names = missing.map((item) => item.name);
|
|
958
|
+
const commands = missing.map((item) => `\`${item.command}\``);
|
|
959
|
+
return `No synced ${formatList(names)} yet — run ${formatList(commands)} first.`;
|
|
960
|
+
};
|
|
961
|
+
const assertKnownProfileTable = (table) => {
|
|
962
|
+
assertKnownTable(table);
|
|
963
|
+
if (!profileTables.has(physicalTableForCacheTable(table))) throw new Error(`Unknown profile table: ${table}`);
|
|
964
|
+
};
|
|
965
|
+
const cacheDescriptionNotes = ["time columns (*_at, muted_until, synced_at) are epoch ms; booleans are 0/1", "each view has a <name>_raw twin holding the upstream JSON: json_extract(raw,'$.field')"];
|
|
966
|
+
const metadataForPlatform = (platform) => {
|
|
967
|
+
const platforms = platform ? [platform] : ["linkedin", "x"];
|
|
877
968
|
return {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
name: read(item.name),
|
|
882
|
-
description: read(item.description),
|
|
883
|
-
location: read(item.location),
|
|
884
|
-
url: read(item.url),
|
|
885
|
-
is_identity_verified: toBool(item.is_identity_verified),
|
|
886
|
-
protected: toBool(item.protected),
|
|
887
|
-
verified: toBool(item.verified),
|
|
888
|
-
verified_type: read(item.verified_type),
|
|
889
|
-
subscription_type: read(item.subscription_type),
|
|
890
|
-
parody: toBool(item.parody),
|
|
891
|
-
most_recent_tweet_id: read(item.most_recent_tweet_id),
|
|
892
|
-
pinned_tweet_id: read(item.pinned_tweet_id),
|
|
893
|
-
profile_banner_url: read(item.profile_banner_url),
|
|
894
|
-
profile_image_url: read(item.profile_image_url),
|
|
895
|
-
affiliation_badge_url: read(affiliation.badge_url),
|
|
896
|
-
affiliation_description: read(affiliation.description),
|
|
897
|
-
affiliation_url: read(affiliation.url),
|
|
898
|
-
subscription_subscribes_to_you: toBool(subscription.subscribes_to_you),
|
|
899
|
-
followers_count: read(publicMetrics.followers_count),
|
|
900
|
-
following_count: read(publicMetrics.following_count),
|
|
901
|
-
tweet_count: read(publicMetrics.tweet_count),
|
|
902
|
-
listed_count: read(publicMetrics.listed_count),
|
|
903
|
-
like_count: read(publicMetrics.like_count),
|
|
904
|
-
media_count: read(publicMetrics.media_count),
|
|
905
|
-
raw: JSON.stringify(item)
|
|
969
|
+
joins: platforms.flatMap((item) => CACHE_METADATA_BY_PLATFORM[item].joins),
|
|
970
|
+
enums: Object.assign({}, ...platforms.map((item) => CACHE_METADATA_BY_PLATFORM[item].enums)),
|
|
971
|
+
indexed: Object.assign({}, ...platforms.map((item) => CACHE_METADATA_BY_PLATFORM[item].indexed))
|
|
906
972
|
};
|
|
907
973
|
};
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const
|
|
911
|
-
{
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
974
|
+
const cachePath = (args) => join(homedir(), ".social", "cache", `${args.platform}-${sanitizeAccountId(args.accountId)}.db`);
|
|
975
|
+
const openCacheAt = (path, opts) => {
|
|
976
|
+
const dbPath = resolveCachePath(path);
|
|
977
|
+
if (opts?.readonly) {
|
|
978
|
+
const db = openDatabase(dbPath, { readonly: true });
|
|
979
|
+
assertAtLatest(db);
|
|
980
|
+
return createCache(dbPath, db);
|
|
981
|
+
}
|
|
982
|
+
if (dbPath !== ":memory:") mkdirSync(dirname(dbPath), { recursive: true });
|
|
983
|
+
const db = openDatabase(dbPath);
|
|
984
|
+
const result = migrate(db);
|
|
985
|
+
if (result.applied > 0) opts?.onMigrate?.(result);
|
|
986
|
+
return createCache(dbPath, db);
|
|
987
|
+
};
|
|
988
|
+
const openCache = (args) => openCacheAt(cachePath(args), {
|
|
989
|
+
readonly: args.readonly,
|
|
990
|
+
onMigrate: args.onMigrate
|
|
991
|
+
});
|
|
992
|
+
const createCache = (path, db) => ({
|
|
993
|
+
upsert: (table, rows, syncedAt = Date.now()) => {
|
|
994
|
+
if (rows.length === 0) return;
|
|
995
|
+
assertKnownTable(table);
|
|
996
|
+
const physicalTable = physicalTableForCacheTable(table);
|
|
997
|
+
const columns = [...Object.keys(rows[0] ?? {}).filter((column) => column !== "synced_at"), "synced_at"];
|
|
998
|
+
const updateColumns = columns.filter((column) => column !== "id");
|
|
999
|
+
const statement = db.prepare(`INSERT INTO ${quoteIdentifier(physicalTable)} (${columns.map(quoteIdentifier).join(", ")}) VALUES (${columns.map((column) => `:${column}`).join(", ")}) ON CONFLICT(id) DO UPDATE SET ${updateColumns.map((column) => `${quoteIdentifier(column)}=excluded.${quoteIdentifier(column)}`).join(", ")}`);
|
|
1000
|
+
db.transaction(() => {
|
|
1001
|
+
for (const row of rows) statement.run({
|
|
1002
|
+
...row,
|
|
1003
|
+
synced_at: syncedAt
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
920
1006
|
},
|
|
921
|
-
{
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1007
|
+
upsertProfiles: (table, rows) => {
|
|
1008
|
+
if (rows.length === 0) return;
|
|
1009
|
+
assertKnownProfileTable(table);
|
|
1010
|
+
const physicalTable = physicalTableForCacheTable(table);
|
|
1011
|
+
const rowsByProviderId = /* @__PURE__ */ new Map();
|
|
1012
|
+
for (const row of rows) rowsByProviderId.set(row.providerId, row);
|
|
1013
|
+
const columns = [
|
|
1014
|
+
"provider_id",
|
|
1015
|
+
"username",
|
|
1016
|
+
"display_name",
|
|
1017
|
+
"avatar_url",
|
|
1018
|
+
"headline",
|
|
1019
|
+
"raw",
|
|
1020
|
+
"synced_at"
|
|
1021
|
+
];
|
|
1022
|
+
const quotedTable = quoteIdentifier(physicalTable);
|
|
1023
|
+
const nullableColumns = [
|
|
1024
|
+
"username",
|
|
1025
|
+
"avatar_url",
|
|
1026
|
+
"headline"
|
|
1027
|
+
];
|
|
1028
|
+
const lastWriteColumns = [
|
|
1029
|
+
"display_name",
|
|
1030
|
+
"raw",
|
|
1031
|
+
"synced_at"
|
|
1032
|
+
];
|
|
1033
|
+
const updateAssignments = [...nullableColumns.map((column) => `${quoteIdentifier(column)}=COALESCE(excluded.${quoteIdentifier(column)}, ${quotedTable}.${quoteIdentifier(column)})`), ...lastWriteColumns.map((column) => `${quoteIdentifier(column)}=excluded.${quoteIdentifier(column)}`)].join(", ");
|
|
1034
|
+
const statement = db.prepare(`INSERT INTO ${quotedTable} (${columns.map(quoteIdentifier).join(", ")}) VALUES (${columns.map((column) => `:${column}`).join(", ")}) ON CONFLICT(provider_id) DO UPDATE SET ${updateAssignments}`);
|
|
1035
|
+
const syncedAt = Date.now();
|
|
1036
|
+
db.transaction(() => {
|
|
1037
|
+
for (const row of rowsByProviderId.values()) statement.run({
|
|
1038
|
+
provider_id: row.providerId,
|
|
1039
|
+
username: row.username,
|
|
1040
|
+
display_name: row.displayName,
|
|
1041
|
+
avatar_url: row.avatarURL,
|
|
1042
|
+
headline: row.headline,
|
|
1043
|
+
raw: JSON.stringify(row.raw),
|
|
1044
|
+
synced_at: syncedAt
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
930
1047
|
},
|
|
931
|
-
{
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
lift: liftXUser
|
|
1048
|
+
getCursor: (collection) => {
|
|
1049
|
+
const row = db.prepare("SELECT cursor, last_synced, object_count FROM sync_state WHERE collection = :collection").get({ collection });
|
|
1050
|
+
if (!row) return;
|
|
1051
|
+
return {
|
|
1052
|
+
cursor: stringOrNull(row.cursor),
|
|
1053
|
+
lastSynced: numberOrNull(row.last_synced),
|
|
1054
|
+
objectCount: numberOrNull(row.object_count)
|
|
1055
|
+
};
|
|
940
1056
|
},
|
|
941
|
-
{
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
lift: liftBookmark
|
|
1057
|
+
setCursor: (collection, cursor, objectCount, lastSynced = Date.now()) => {
|
|
1058
|
+
db.prepare("INSERT INTO sync_state (collection, cursor, last_synced, object_count) VALUES (:collection, :cursor, :last_synced, :object_count) ON CONFLICT(collection) DO UPDATE SET cursor=excluded.cursor, last_synced=excluded.last_synced, object_count=excluded.object_count").run({
|
|
1059
|
+
collection,
|
|
1060
|
+
cursor,
|
|
1061
|
+
last_synced: lastSynced,
|
|
1062
|
+
object_count: objectCount
|
|
1063
|
+
});
|
|
949
1064
|
},
|
|
950
|
-
{
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1065
|
+
reset: ({ tables, collections }) => {
|
|
1066
|
+
for (const table of tables) assertKnownTable(table);
|
|
1067
|
+
const results = [];
|
|
1068
|
+
db.transaction(() => {
|
|
1069
|
+
for (const table of tables) {
|
|
1070
|
+
const physicalTable = physicalTableForCacheTable(table);
|
|
1071
|
+
const result = db.prepare(`DELETE FROM ${quoteIdentifier(physicalTable)}`).run();
|
|
1072
|
+
results.push({
|
|
1073
|
+
table: physicalTable,
|
|
1074
|
+
rowsDeleted: result?.changes ?? 0
|
|
1075
|
+
});
|
|
960
1076
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1077
|
+
const deleteState = db.prepare("DELETE FROM sync_state WHERE collection = :collection");
|
|
1078
|
+
for (const collection of collections) deleteState.run({ collection });
|
|
1079
|
+
});
|
|
1080
|
+
return results;
|
|
1081
|
+
},
|
|
1082
|
+
query: (sql, opts) => {
|
|
1083
|
+
if (opts?.platform) assertPlatformQuery(sql, opts.platform);
|
|
1084
|
+
return db.prepare(sql).all();
|
|
1085
|
+
},
|
|
1086
|
+
read: (sql, opts) => {
|
|
1087
|
+
assertPlatformQuery(sql, opts.platform);
|
|
1088
|
+
const tables = referencedTablesForRead(sql, opts.platform).map((name) => {
|
|
1089
|
+
const policy = freshnessPolicyForTable(name);
|
|
1090
|
+
return {
|
|
1091
|
+
name,
|
|
1092
|
+
missingName: missingNameForPolicy(name, policy),
|
|
1093
|
+
command: syncCommandForPolicy(opts.platform, policy),
|
|
1094
|
+
lastSynced: lastSyncedForPolicy(db, policy)
|
|
1095
|
+
};
|
|
1096
|
+
});
|
|
1097
|
+
const missingFreshness = missingFreshnessForTables(tables);
|
|
1098
|
+
if (missingFreshness.length > 0) throw new NeverSyncedError(missingFreshness.map((item) => item.name), neverSyncedMessage(missingFreshness));
|
|
1099
|
+
return {
|
|
1100
|
+
rows: db.prepare(sql).all(),
|
|
1101
|
+
tables: tables.map(({ name, lastSynced }) => ({
|
|
1102
|
+
name,
|
|
1103
|
+
lastSynced
|
|
1104
|
+
}))
|
|
1105
|
+
};
|
|
1106
|
+
},
|
|
1107
|
+
describe: (opts) => {
|
|
1108
|
+
const allowedTables = opts?.platform ? tablesForPlatform(opts.platform) : void 0;
|
|
1109
|
+
const metadata = metadataForPlatform(opts?.platform);
|
|
1110
|
+
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name").all().filter((table) => !String(table.name).endsWith("_raw")).filter((table) => allowedTables === void 0 || allowedTables.has(String(table.name)));
|
|
1111
|
+
const lastSyncedForTable = (name) => {
|
|
1112
|
+
if (name === "sync_state") return null;
|
|
1113
|
+
return lastSyncedForPolicy(db, freshnessPolicyForTable(name));
|
|
1114
|
+
};
|
|
1115
|
+
const columnsForTable = (name) => db.prepare(`PRAGMA table_info(${quoteIdentifier(name)})`).all().map((row) => String(row.name)).join(", ");
|
|
1116
|
+
return {
|
|
1117
|
+
path,
|
|
1118
|
+
notes: cacheDescriptionNotes,
|
|
1119
|
+
joins: metadata.joins,
|
|
1120
|
+
enums: metadata.enums,
|
|
1121
|
+
tables: tables.map((table) => {
|
|
1122
|
+
const name = String(table.name);
|
|
1123
|
+
const rowCount = db.prepare(`SELECT count(*) AS rows FROM ${quoteIdentifier(name)}`).get();
|
|
1124
|
+
const syncedAt = lastSyncedForTable(name);
|
|
1125
|
+
return {
|
|
1126
|
+
name,
|
|
1127
|
+
rows: Number(rowCount?.rows ?? 0),
|
|
1128
|
+
synced_at: syncedAt,
|
|
1129
|
+
query_ready: syncedAt != null,
|
|
1130
|
+
columns: columnsForTable(name),
|
|
1131
|
+
indexed: metadata.indexed[name] ?? []
|
|
1132
|
+
};
|
|
1133
|
+
})
|
|
1134
|
+
};
|
|
1135
|
+
},
|
|
1136
|
+
close: () => {
|
|
1137
|
+
db.close();
|
|
966
1138
|
}
|
|
967
|
-
|
|
968
|
-
const collectionByKey = (key) => COLLECTIONS.find((collection) => collection.key === key);
|
|
969
|
-
const collectionsFor = (platform) => COLLECTIONS.filter((collection) => collection.platform === platform);
|
|
970
|
-
const syncableCollectionsFor = (platform) => {
|
|
971
|
-
const parentKeys = new Set(COLLECTIONS.map((collection) => collection.parentCollection).filter(Boolean));
|
|
972
|
-
return collectionsFor(platform).filter((collection) => !parentKeys.has(collection.key));
|
|
973
|
-
};
|
|
974
|
-
const physicalTableFor = (collection) => collection.table ?? collection.key;
|
|
975
|
-
const resetTablesFor = (collection) => {
|
|
976
|
-
if (!collection.parentCollection) return [collection];
|
|
977
|
-
const parent = collectionByKey(collection.parentCollection);
|
|
978
|
-
return parent ? [collection, parent] : [collection];
|
|
979
|
-
};
|
|
980
|
-
const resetPhysicalTablesFor = (collection) => resetTablesFor(collection).map(physicalTableFor);
|
|
1139
|
+
});
|
|
981
1140
|
//#endregion
|
|
982
1141
|
//#region ../../packages/sync-engine/src/cost.ts
|
|
983
1142
|
const creditsFromHeader = (headers) => {
|
|
@@ -987,7 +1146,7 @@ const creditsFromHeader = (headers) => {
|
|
|
987
1146
|
};
|
|
988
1147
|
//#endregion
|
|
989
1148
|
//#region ../../packages/sync-engine/src/profiles.ts
|
|
990
|
-
const profileTableFor = (platform) => platform === "x" ? "
|
|
1149
|
+
const profileTableFor = (platform) => platform === "x" ? "x_profiles_raw" : "li_profiles_raw";
|
|
991
1150
|
//#endregion
|
|
992
1151
|
//#region ../../node_modules/.bun/drizzle-orm@0.43.1+ae5d55cf414a93be/node_modules/drizzle-orm/entity.js
|
|
993
1152
|
const entityKind = Symbol.for("drizzle:entityKind");
|
|
@@ -3748,7 +3907,7 @@ const rawColumns = () => ({
|
|
|
3748
3907
|
raw: text$1("raw").notNull(),
|
|
3749
3908
|
syncedAt: integer$2("synced_at").notNull()
|
|
3750
3909
|
});
|
|
3751
|
-
sqliteTable("
|
|
3910
|
+
sqliteTable("x_tweets_raw", {
|
|
3752
3911
|
id: idColumn(),
|
|
3753
3912
|
createdAt: integer$2("created_at"),
|
|
3754
3913
|
authorId: text$1("author_id"),
|
|
@@ -3772,7 +3931,7 @@ sqliteTable("x_tweets", {
|
|
|
3772
3931
|
index$1("x_tweets_author_id_created_at_idx").on(table.authorId, table.createdAt),
|
|
3773
3932
|
index$1("x_tweets_conversation_id_created_at_idx").on(table.conversationId, table.createdAt)
|
|
3774
3933
|
]);
|
|
3775
|
-
sqliteTable("
|
|
3934
|
+
sqliteTable("x_bookmarks_raw", {
|
|
3776
3935
|
id: idColumn(),
|
|
3777
3936
|
createdAt: integer$2("created_at"),
|
|
3778
3937
|
authorId: text$1("author_id"),
|
|
@@ -3803,14 +3962,14 @@ sqliteTable("x_bookmarks", {
|
|
|
3803
3962
|
]);
|
|
3804
3963
|
const profileColumns = () => ({
|
|
3805
3964
|
providerId: text$1("provider_id").primaryKey(),
|
|
3806
|
-
|
|
3965
|
+
username: text$1("username"),
|
|
3807
3966
|
displayName: text$1("display_name").notNull(),
|
|
3808
3967
|
avatarURL: text$1("avatar_url"),
|
|
3809
3968
|
headline: text$1("headline"),
|
|
3810
3969
|
...rawColumns()
|
|
3811
3970
|
});
|
|
3812
|
-
sqliteTable("
|
|
3813
|
-
sqliteTable("
|
|
3971
|
+
sqliteTable("x_profiles_raw", profileColumns(), (table) => [index$1("x_profiles_username_idx").on(table.username)]);
|
|
3972
|
+
sqliteTable("x_messages_raw", {
|
|
3814
3973
|
id: idColumn(),
|
|
3815
3974
|
createdAt: integer$2("created_at"),
|
|
3816
3975
|
dmConversationId: text$1("dm_conversation_id"),
|
|
@@ -3857,21 +4016,21 @@ const xUserColumns = () => ({
|
|
|
3857
4016
|
mediaCount: integer$2("media_count"),
|
|
3858
4017
|
...rawColumns()
|
|
3859
4018
|
});
|
|
3860
|
-
sqliteTable("
|
|
4019
|
+
sqliteTable("x_followers_raw", xUserColumns(), (table) => [
|
|
3861
4020
|
index$1("x_followers_created_at_idx").on(table.createdAt),
|
|
3862
4021
|
index$1("x_followers_synced_at_idx").on(table.syncedAt),
|
|
3863
4022
|
index$1("x_followers_username_idx").on(table.username),
|
|
3864
4023
|
index$1("x_followers_verified_idx").on(table.verified),
|
|
3865
4024
|
index$1("x_followers_verified_type_idx").on(table.verifiedType)
|
|
3866
4025
|
]);
|
|
3867
|
-
sqliteTable("
|
|
4026
|
+
sqliteTable("x_following_raw", xUserColumns(), (table) => [
|
|
3868
4027
|
index$1("x_following_created_at_idx").on(table.createdAt),
|
|
3869
4028
|
index$1("x_following_synced_at_idx").on(table.syncedAt),
|
|
3870
4029
|
index$1("x_following_username_idx").on(table.username),
|
|
3871
4030
|
index$1("x_following_verified_idx").on(table.verified),
|
|
3872
4031
|
index$1("x_following_verified_type_idx").on(table.verifiedType)
|
|
3873
4032
|
]);
|
|
3874
|
-
sqliteTable("
|
|
4033
|
+
sqliteTable("li_connections_raw", {
|
|
3875
4034
|
id: idColumn(),
|
|
3876
4035
|
createdAt: integer$2("created_at"),
|
|
3877
4036
|
object: text$1("object"),
|
|
@@ -3893,8 +4052,8 @@ sqliteTable("li_connections", {
|
|
|
3893
4052
|
index$1("li_connections_user_public_identifier_idx").on(table.userPublicIdentifier),
|
|
3894
4053
|
index$1("li_connections_user_display_name_idx").on(table.userDisplayName)
|
|
3895
4054
|
]);
|
|
3896
|
-
sqliteTable("
|
|
3897
|
-
sqliteTable("
|
|
4055
|
+
sqliteTable("li_profiles_raw", profileColumns(), (table) => [index$1("li_profiles_username_idx").on(table.username)]);
|
|
4056
|
+
sqliteTable("li_conversations_raw", {
|
|
3898
4057
|
id: idColumn(),
|
|
3899
4058
|
createdAt: integer$2("created_at"),
|
|
3900
4059
|
lastMessageTimestamp: integer$2("last_message_timestamp"),
|
|
@@ -3931,7 +4090,7 @@ sqliteTable("li_conversations", {
|
|
|
3931
4090
|
index$1("li_conversations_user_display_name_idx").on(table.userDisplayName),
|
|
3932
4091
|
index$1("li_conversations_is_archived_last_message_timestamp_idx").on(table.isArchived, table.lastMessageTimestamp)
|
|
3933
4092
|
]);
|
|
3934
|
-
sqliteTable("
|
|
4093
|
+
sqliteTable("li_messages_raw", {
|
|
3935
4094
|
id: idColumn(),
|
|
3936
4095
|
timestamp: integer$2("timestamp"),
|
|
3937
4096
|
chatId: text$1("chat_id"),
|
|
@@ -3962,7 +4121,7 @@ sqliteTable("li_messages", {
|
|
|
3962
4121
|
index$1("li_messages_chat_id_timestamp_idx").on(table.chatId, table.timestamp),
|
|
3963
4122
|
index$1("li_messages_sender_id_timestamp_idx").on(table.senderId, table.timestamp)
|
|
3964
4123
|
]);
|
|
3965
|
-
sqliteTable("
|
|
4124
|
+
sqliteTable("li_posts_raw", {
|
|
3966
4125
|
id: idColumn(),
|
|
3967
4126
|
createdAt: integer$2("created_at"),
|
|
3968
4127
|
object: text$1("object"),
|
|
@@ -4023,7 +4182,7 @@ sqliteTable("li_posts", {
|
|
|
4023
4182
|
index$1("li_posts_author_id_created_at_idx").on(table.authorId, table.createdAt),
|
|
4024
4183
|
index$1("li_posts_author_public_identifier_created_at_idx").on(table.authorPublicIdentifier, table.createdAt)
|
|
4025
4184
|
]);
|
|
4026
|
-
sqliteTable("
|
|
4185
|
+
sqliteTable("li_requests_raw", {
|
|
4027
4186
|
id: idColumn(),
|
|
4028
4187
|
createdAt: integer$2("created_at"),
|
|
4029
4188
|
object: text$1("object"),
|
|
@@ -4053,19 +4212,31 @@ sqliteTable("sync_state", {
|
|
|
4053
4212
|
});
|
|
4054
4213
|
//#endregion
|
|
4055
4214
|
//#region ../../packages/sync-engine/src/since.ts
|
|
4056
|
-
const
|
|
4057
|
-
const
|
|
4058
|
-
const
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4215
|
+
const isoDatePattern = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
|
|
4216
|
+
const isoDateTimePattern = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d{1,3})?)?(?:Z|[+-]([01]\d|2[0-3]):[0-5]\d)?$/;
|
|
4217
|
+
const timezonePattern = /(Z|[+-]\d{2}:\d{2})$/;
|
|
4218
|
+
const invalidSinceMessage = "Invalid --since value. Use an ISO 8601 date like 2026-05-04 or datetime like 2026-05-04T00:00:00Z.";
|
|
4219
|
+
const daysInMonth = (year, month) => {
|
|
4220
|
+
if (month === 2) return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
|
|
4221
|
+
return [
|
|
4222
|
+
4,
|
|
4223
|
+
6,
|
|
4224
|
+
9,
|
|
4225
|
+
11
|
|
4226
|
+
].includes(month) ? 30 : 31;
|
|
4227
|
+
};
|
|
4228
|
+
const hasValidDate = (value) => {
|
|
4229
|
+
const year = Number.parseInt(value.slice(0, 4), 10);
|
|
4230
|
+
const month = Number.parseInt(value.slice(5, 7), 10);
|
|
4231
|
+
return Number.parseInt(value.slice(8, 10), 10) <= daysInMonth(year, month);
|
|
4232
|
+
};
|
|
4233
|
+
const parseSince = (input) => {
|
|
4064
4234
|
const value = input.trim();
|
|
4065
|
-
const
|
|
4066
|
-
|
|
4067
|
-
const
|
|
4068
|
-
|
|
4235
|
+
const isISODate = isoDatePattern.test(value);
|
|
4236
|
+
const isISODateTime = isoDateTimePattern.test(value);
|
|
4237
|
+
const normalized = isISODateTime && !timezonePattern.test(value) ? `${value}Z` : value;
|
|
4238
|
+
const parsed = (isISODate || isISODateTime) && hasValidDate(value) ? Date.parse(normalized) : NaN;
|
|
4239
|
+
if (Number.isNaN(parsed)) throw new Error(invalidSinceMessage);
|
|
4069
4240
|
return parsed;
|
|
4070
4241
|
};
|
|
4071
4242
|
//#endregion
|
|
@@ -8722,6 +8893,7 @@ var SyncPageError = class extends Error {
|
|
|
8722
8893
|
pagesCompleted;
|
|
8723
8894
|
objectsUpserted;
|
|
8724
8895
|
creditsSpent;
|
|
8896
|
+
cursorPersisted;
|
|
8725
8897
|
cause;
|
|
8726
8898
|
constructor(context, cause) {
|
|
8727
8899
|
super(`Failed to sync ${context.collectionKey} page at ${context.path}`);
|
|
@@ -8737,13 +8909,31 @@ var SyncPageError = class extends Error {
|
|
|
8737
8909
|
this.pagesCompleted = context.pagesCompleted;
|
|
8738
8910
|
this.objectsUpserted = context.objectsUpserted;
|
|
8739
8911
|
this.creditsSpent = context.creditsSpent;
|
|
8912
|
+
this.cursorPersisted = context.cursorPersisted;
|
|
8740
8913
|
this.cause = cause;
|
|
8741
8914
|
}
|
|
8742
8915
|
};
|
|
8916
|
+
const PARTIAL_SYNC_CURSOR_PREFIX = "partial-sync-v1:";
|
|
8743
8917
|
const itemsSchema = array$1(record$1(string$1(), unknown$1()));
|
|
8744
8918
|
const cursorSchema = union$1([string$1(), _null$1()]);
|
|
8745
8919
|
const totalCountSchema = number$1().int().nonnegative();
|
|
8746
8920
|
const isRecord$5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8921
|
+
const validPartialSyncCursor = (value) => value.version === 1 && Number.isInteger(value.requestIndex) && typeof value.requestIndex === "number" && value.requestIndex >= 0 && typeof value.cursor === "string" && (typeof value.checkpoint === "string" || value.checkpoint === null) && (typeof value.newestId === "string" || value.newestId === null) && (typeof value.since === "number" || value.since === null);
|
|
8922
|
+
const decodePartialSyncCursor = (value) => {
|
|
8923
|
+
if (!value?.startsWith(PARTIAL_SYNC_CURSOR_PREFIX)) return;
|
|
8924
|
+
try {
|
|
8925
|
+
const parsed = JSON.parse(value.slice(16));
|
|
8926
|
+
return isRecord$5(parsed) && validPartialSyncCursor(parsed) ? parsed : void 0;
|
|
8927
|
+
} catch {
|
|
8928
|
+
return;
|
|
8929
|
+
}
|
|
8930
|
+
};
|
|
8931
|
+
const encodePartialSyncCursor = (cursor) => `${PARTIAL_SYNC_CURSOR_PREFIX}${JSON.stringify(cursor)}`;
|
|
8932
|
+
const isRateLimitError = (error) => isRecord$5(error) && error.status === 429;
|
|
8933
|
+
const retryAfterSecondsFromRateLimitError = (error) => {
|
|
8934
|
+
if (!(isRateLimitError(error) && isRecord$5(error))) return;
|
|
8935
|
+
return typeof error.retryAfterSeconds === "number" && Number.isFinite(error.retryAfterSeconds) && error.retryAfterSeconds >= 0 ? error.retryAfterSeconds : void 0;
|
|
8936
|
+
};
|
|
8747
8937
|
const getByPath = (obj, path) => {
|
|
8748
8938
|
if (path === void 0) return;
|
|
8749
8939
|
let current = obj;
|
|
@@ -8877,18 +9067,62 @@ const mergeStoppedReason = (current, next) => {
|
|
|
8877
9067
|
return "complete";
|
|
8878
9068
|
};
|
|
8879
9069
|
const walkRequest = async (args) => {
|
|
8880
|
-
const
|
|
9070
|
+
const storedCursor = args.deps.cache.getCursor(args.collection.key)?.cursor ?? null;
|
|
9071
|
+
const storedPartialCursor = decodePartialSyncCursor(storedCursor);
|
|
9072
|
+
const checkpoint = args.resumeCursor?.checkpoint ?? (args.useCheckpoint ? storedPartialCursor?.checkpoint ?? storedCursor : null);
|
|
8881
9073
|
const ids = [];
|
|
8882
9074
|
let state = {
|
|
8883
9075
|
checkpoint,
|
|
8884
|
-
cursor: null,
|
|
9076
|
+
cursor: args.resumeCursor?.cursor ?? null,
|
|
8885
9077
|
offset: 0,
|
|
8886
|
-
newestId: null,
|
|
8887
|
-
firstPage:
|
|
9078
|
+
newestId: args.resumeCursor?.newestId ?? null,
|
|
9079
|
+
firstPage: args.resumeCursor === void 0
|
|
8888
9080
|
};
|
|
8889
9081
|
let pages = 0;
|
|
8890
9082
|
let upserted = 0;
|
|
8891
9083
|
let stoppedReason = "complete";
|
|
9084
|
+
const rateLimitNow = args.rateLimit?.now ?? Date.now;
|
|
9085
|
+
const rateLimitSleep = args.rateLimit?.sleep ?? sleep;
|
|
9086
|
+
const rateLimitMaxRetries = args.rateLimit?.maxRetries ?? 25;
|
|
9087
|
+
const pageRequestOptions = {
|
|
9088
|
+
headers: args.deps.accountHeaders,
|
|
9089
|
+
...args.rateLimit?.timeoutSeconds === void 0 ? {} : { retry: { limit: 0 } }
|
|
9090
|
+
};
|
|
9091
|
+
const waitForRateLimit = async (error) => {
|
|
9092
|
+
if (!(args.rateLimit && args.rateLimitState?.deadlineMs !== void 0)) return false;
|
|
9093
|
+
const retryCount = args.rateLimitState.retryCount + 1;
|
|
9094
|
+
const retryAfterSeconds = args.rateLimit.retryAfterSecondsFor(error, retryCount);
|
|
9095
|
+
if (!(args.rateLimit.isRateLimitError?.(error) ?? retryAfterSeconds !== void 0)) return false;
|
|
9096
|
+
const delaySeconds = retryAfterSeconds ?? args.rateLimit.fallbackDelaySecondsForRetry?.(retryCount);
|
|
9097
|
+
if (delaySeconds === void 0 || retryCount > rateLimitMaxRetries) return false;
|
|
9098
|
+
const remainingMs = args.rateLimitState.deadlineMs - rateLimitNow();
|
|
9099
|
+
if (delaySeconds * 1e3 > remainingMs) return false;
|
|
9100
|
+
args.rateLimitState.retryCount = retryCount;
|
|
9101
|
+
await args.rateLimit.onRetry?.({
|
|
9102
|
+
delaySeconds,
|
|
9103
|
+
retryCount
|
|
9104
|
+
});
|
|
9105
|
+
await rateLimitSleep(delaySeconds * 1e3);
|
|
9106
|
+
return true;
|
|
9107
|
+
};
|
|
9108
|
+
const persistPartialCursor = (error) => {
|
|
9109
|
+
const isRateLimit = args.rateLimit?.isRateLimitError?.(error) ?? args.rateLimit?.retryAfterSecondsFor(error, 1) !== void 0;
|
|
9110
|
+
if (!(args.allowPartialResume && args.rateLimit && isRateLimit) || args.collection.pagination.style !== "cursor" || !state.cursor) return false;
|
|
9111
|
+
args.writePendingCursor({
|
|
9112
|
+
collection: args.collection.key,
|
|
9113
|
+
cursor: encodePartialSyncCursor({
|
|
9114
|
+
version: 1,
|
|
9115
|
+
requestIndex: args.requestIndex,
|
|
9116
|
+
cursor: state.cursor,
|
|
9117
|
+
checkpoint,
|
|
9118
|
+
newestId: state.newestId,
|
|
9119
|
+
since: args.since ?? null
|
|
9120
|
+
}),
|
|
9121
|
+
objectCount: upserted,
|
|
9122
|
+
lastSynced: null
|
|
9123
|
+
});
|
|
9124
|
+
return true;
|
|
9125
|
+
};
|
|
8892
9126
|
while (true) {
|
|
8893
9127
|
if (args.pageDelayState.pagesCompleted > 0) {
|
|
8894
9128
|
const delay = pageDelayFor(args.deps.pageDelayMs, args.pageDelayState.pagesCompleted);
|
|
@@ -8915,13 +9149,17 @@ const walkRequest = async (args) => {
|
|
|
8915
9149
|
objectsUpserted: upserted,
|
|
8916
9150
|
creditsSpent: args.credits.spent
|
|
8917
9151
|
};
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
9152
|
+
let response;
|
|
9153
|
+
while (true) try {
|
|
9154
|
+
response = await args.deps.api.get(path, pageRequestOptions);
|
|
9155
|
+
break;
|
|
9156
|
+
} catch (error) {
|
|
9157
|
+
if (await waitForRateLimit(error)) continue;
|
|
9158
|
+
return wrapPageError({
|
|
9159
|
+
...pageContext,
|
|
9160
|
+
cursorPersisted: persistPartialCursor(error) || void 0
|
|
9161
|
+
}, error);
|
|
9162
|
+
}
|
|
8925
9163
|
const credits = creditsFromHeader(response.headers);
|
|
8926
9164
|
args.credits.spent += credits;
|
|
8927
9165
|
let json;
|
|
@@ -8989,6 +9227,11 @@ const runSync = async (deps, opts) => {
|
|
|
8989
9227
|
let stoppedReason = "complete";
|
|
8990
9228
|
const pendingCursors = [];
|
|
8991
9229
|
const pageDelayState = { pagesCompleted: 0 };
|
|
9230
|
+
const rateLimitNow = opts.rateLimit?.now ?? Date.now;
|
|
9231
|
+
const rateLimitState = opts.rateLimit ? {
|
|
9232
|
+
deadlineMs: opts.rateLimit.timeoutSeconds === void 0 ? void 0 : rateLimitNow() + opts.rateLimit.timeoutSeconds * 1e3,
|
|
9233
|
+
retryCount: 0
|
|
9234
|
+
} : void 0;
|
|
8992
9235
|
const addResult = (result) => {
|
|
8993
9236
|
pages += result.pages;
|
|
8994
9237
|
upserted += result.upserted;
|
|
@@ -9003,16 +9246,21 @@ const runSync = async (deps, opts) => {
|
|
|
9003
9246
|
const parentIds = [];
|
|
9004
9247
|
const parentPersistsCheckpoint = parentCollection.requests.length === 1;
|
|
9005
9248
|
let parentCheckpoint;
|
|
9006
|
-
for (const request of parentCollection.requests) {
|
|
9249
|
+
for (const [requestIndex, request] of parentCollection.requests.entries()) {
|
|
9007
9250
|
const result = await walkRequest({
|
|
9008
9251
|
deps,
|
|
9009
9252
|
collection: parentCollection,
|
|
9010
9253
|
request,
|
|
9254
|
+
requestIndex,
|
|
9011
9255
|
since: opts.since,
|
|
9012
9256
|
useCheckpoint: false,
|
|
9013
9257
|
collectIds: true,
|
|
9258
|
+
allowPartialResume: false,
|
|
9014
9259
|
credits,
|
|
9015
9260
|
pageDelayState,
|
|
9261
|
+
rateLimit: opts.rateLimit,
|
|
9262
|
+
rateLimitState,
|
|
9263
|
+
writePendingCursor,
|
|
9016
9264
|
onPage: opts.onPage,
|
|
9017
9265
|
onProgress: opts.onProgress
|
|
9018
9266
|
});
|
|
@@ -9030,16 +9278,21 @@ const runSync = async (deps, opts) => {
|
|
|
9030
9278
|
lastSynced: Date.now()
|
|
9031
9279
|
};
|
|
9032
9280
|
if (parentCheckpoint) writePendingCursor(parentCheckpoint);
|
|
9033
|
-
for (const parentId of parentIds) for (const request of collection.requests) addResult(await walkRequest({
|
|
9281
|
+
for (const parentId of parentIds) for (const [requestIndex, request] of collection.requests.entries()) addResult(await walkRequest({
|
|
9034
9282
|
deps,
|
|
9035
9283
|
collection,
|
|
9036
9284
|
request,
|
|
9285
|
+
requestIndex,
|
|
9037
9286
|
since: opts.since,
|
|
9038
9287
|
useCheckpoint: false,
|
|
9288
|
+
allowPartialResume: false,
|
|
9039
9289
|
parentId,
|
|
9040
9290
|
parentIdToken: collection.parentIdToken,
|
|
9041
9291
|
credits,
|
|
9042
9292
|
pageDelayState,
|
|
9293
|
+
rateLimit: opts.rateLimit,
|
|
9294
|
+
rateLimitState,
|
|
9295
|
+
writePendingCursor,
|
|
9043
9296
|
onPage: opts.onPage,
|
|
9044
9297
|
onProgress: opts.onProgress
|
|
9045
9298
|
}));
|
|
@@ -9051,15 +9304,24 @@ const runSync = async (deps, opts) => {
|
|
|
9051
9304
|
});
|
|
9052
9305
|
} else {
|
|
9053
9306
|
const useCheckpoint = collection.requests.length === 1;
|
|
9054
|
-
|
|
9307
|
+
const partialCursor = decodePartialSyncCursor(deps.cache.getCursor(collection.key)?.cursor ?? null);
|
|
9308
|
+
const activePartialCursor = partialCursor && partialCursor.since === (opts.since ?? null) && partialCursor.requestIndex < collection.requests.length ? partialCursor : void 0;
|
|
9309
|
+
for (const [requestIndex, request] of collection.requests.entries()) {
|
|
9310
|
+
if (activePartialCursor && requestIndex < activePartialCursor.requestIndex) continue;
|
|
9055
9311
|
const result = await walkRequest({
|
|
9056
9312
|
deps,
|
|
9057
9313
|
collection,
|
|
9058
9314
|
request,
|
|
9315
|
+
requestIndex,
|
|
9059
9316
|
since: opts.since,
|
|
9060
9317
|
useCheckpoint,
|
|
9318
|
+
resumeCursor: activePartialCursor?.requestIndex === requestIndex ? activePartialCursor : void 0,
|
|
9319
|
+
allowPartialResume: true,
|
|
9061
9320
|
credits,
|
|
9062
9321
|
pageDelayState,
|
|
9322
|
+
rateLimit: opts.rateLimit,
|
|
9323
|
+
rateLimitState,
|
|
9324
|
+
writePendingCursor,
|
|
9063
9325
|
onPage: opts.onPage,
|
|
9064
9326
|
onProgress: opts.onProgress
|
|
9065
9327
|
});
|
|
@@ -9870,7 +10132,7 @@ pgTable("profiles", {
|
|
|
9870
10132
|
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
9871
10133
|
platform: text("platform", { enum: ["linkedin", "x"] }).notNull(),
|
|
9872
10134
|
providerId: text("provider_id").notNull(),
|
|
9873
|
-
|
|
10135
|
+
username: text("username"),
|
|
9874
10136
|
displayName: text("display_name").notNull(),
|
|
9875
10137
|
avatarURL: text("avatar_url"),
|
|
9876
10138
|
headline: text("headline"),
|
|
@@ -9879,7 +10141,7 @@ pgTable("profiles", {
|
|
|
9879
10141
|
updatedAt: timestamptz("updated_at").notNull().defaultNow()
|
|
9880
10142
|
}, (table) => [
|
|
9881
10143
|
uniqueIndex("profiles_platform_provider_id_pkey").on(table.platform, table.providerId),
|
|
9882
|
-
index("
|
|
10144
|
+
index("profiles_platform_username_idx").on(table.platform, table.username),
|
|
9883
10145
|
check("profiles_platform_check", sql`${table.platform} in ('linkedin', 'x')`)
|
|
9884
10146
|
]);
|
|
9885
10147
|
pgTable("feedback_submissions", {
|
|
@@ -9902,7 +10164,7 @@ pgTable("wrapped_runs", {
|
|
|
9902
10164
|
userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
|
|
9903
10165
|
connectedAccountId: text("connected_account_id").references(() => connectedAccounts.id, { onDelete: "set null" }),
|
|
9904
10166
|
platform: text("platform", { enum: ["x"] }).notNull().default("x"),
|
|
9905
|
-
|
|
10167
|
+
username: text("username").notNull(),
|
|
9906
10168
|
platformProfileId: text("platform_profile_id"),
|
|
9907
10169
|
status: text("status", { enum: [
|
|
9908
10170
|
"pending",
|
|
@@ -9922,7 +10184,7 @@ pgTable("wrapped_runs", {
|
|
|
9922
10184
|
}, (table) => [
|
|
9923
10185
|
uniqueIndex("wrapped_runs_share_slug_key").on(table.shareSlug),
|
|
9924
10186
|
index("wrapped_runs_user_created_idx").on(table.userId, table.createdAt.desc()),
|
|
9925
|
-
index("
|
|
10187
|
+
index("wrapped_runs_username_done_idx").on(table.username, table.createdAt.desc()).where(sql`${table.status} = 'done'`),
|
|
9926
10188
|
check("wrapped_runs_platform_check", sql`${table.platform} in ('x')`),
|
|
9927
10189
|
check("wrapped_runs_status_check", sql`${table.status} in ('pending', 'running', 'done', 'failed')`)
|
|
9928
10190
|
]);
|
|
@@ -9987,16 +10249,16 @@ const createURL = (baseURL, path) => {
|
|
|
9987
10249
|
const createAbsoluteURL = (baseURL, path) => createURL(baseURL, path).toString();
|
|
9988
10250
|
//#endregion
|
|
9989
10251
|
//#region ../../packages/linkedin/src/profiles.ts
|
|
9990
|
-
const displayNameFrom = (firstName, lastName,
|
|
10252
|
+
const displayNameFrom = (firstName, lastName, username) => [firstName, lastName].filter(Boolean).join(" ").trim() || username || "";
|
|
9991
10253
|
const rowFrom$1 = (fields) => {
|
|
9992
10254
|
const providerId = fields.providerId?.trim();
|
|
9993
10255
|
if (!providerId) return null;
|
|
9994
|
-
const
|
|
9995
|
-
const displayName = fields.displayName?.trim() || displayNameFrom(fields.firstName, fields.lastName,
|
|
10256
|
+
const username = fields.username?.trim() || null;
|
|
10257
|
+
const displayName = fields.displayName?.trim() || displayNameFrom(fields.firstName, fields.lastName, username ?? void 0);
|
|
9996
10258
|
if (!displayName) return null;
|
|
9997
10259
|
return {
|
|
9998
10260
|
providerId,
|
|
9999
|
-
|
|
10261
|
+
username,
|
|
10000
10262
|
displayName,
|
|
10001
10263
|
avatarURL: fields.avatarURL?.trim() || null,
|
|
10002
10264
|
headline: fields.headline?.trim() || null,
|
|
@@ -10028,7 +10290,7 @@ const rowFromUser = ({ user, providerId, raw }) => rowFrom$1({
|
|
|
10028
10290
|
displayName: user.display_name,
|
|
10029
10291
|
firstName: user.first_name,
|
|
10030
10292
|
lastName: user.last_name,
|
|
10031
|
-
|
|
10293
|
+
username: user.public_identifier,
|
|
10032
10294
|
avatarURL: user.public_picture_url,
|
|
10033
10295
|
headline: headlineFromUser(user),
|
|
10034
10296
|
raw
|
|
@@ -10042,7 +10304,7 @@ const extractProfile$1 = (body) => {
|
|
|
10042
10304
|
displayName: parsed.data.display_name,
|
|
10043
10305
|
firstName: parsed.data.first_name,
|
|
10044
10306
|
lastName: parsed.data.last_name,
|
|
10045
|
-
|
|
10307
|
+
username: parsed.data.public_identifier,
|
|
10046
10308
|
avatarURL: parsed.data.public_picture_url,
|
|
10047
10309
|
headline: headlineFromUser(parsed.data),
|
|
10048
10310
|
raw: parsed.data
|
|
@@ -13227,7 +13489,7 @@ const buildRuntimeArgs = (adapter, op) => {
|
|
|
13227
13489
|
};
|
|
13228
13490
|
args.account = {
|
|
13229
13491
|
type: "string",
|
|
13230
|
-
description: "Account selector: @
|
|
13492
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
13231
13493
|
};
|
|
13232
13494
|
if (adapter.isCacheable(op.endpoint)) args.header = {
|
|
13233
13495
|
type: "string",
|
|
@@ -14150,7 +14412,7 @@ const linkedinRuntimeAdapter = {
|
|
|
14150
14412
|
accountFromOwnAccount: (ownAccount) => ownAccount.account,
|
|
14151
14413
|
ownAccountTargetId: (ownAccount) => ownAccount.providerId,
|
|
14152
14414
|
ownAccountHeader: (ownAccount, selectedAccount) => ownAccount?.accountIdHeader ?? selectedAccount.selector,
|
|
14153
|
-
accountRecordInput: (ownAccount) => ownAccount.account.
|
|
14415
|
+
accountRecordInput: (ownAccount) => ownAccount.account.username ?? ownAccount.providerId,
|
|
14154
14416
|
argAliases: argNameAliasesFrom,
|
|
14155
14417
|
bodyParamArg,
|
|
14156
14418
|
bodyValidationError: ({ bodyFromJSON, bodyParamEntries, bodyParamValues, bodyText, bodyTextArg, displayTextArg, op }) => {
|
|
@@ -14254,6 +14516,8 @@ const DM_INTERACTION_ENDPOINT_BILLING = {
|
|
|
14254
14516
|
billingCategory: DM_INTERACTION_BILLING_CATEGORY,
|
|
14255
14517
|
costBPS: costBPSForCategory$1(DM_INTERACTION_BILLING_CATEGORY)
|
|
14256
14518
|
};
|
|
14519
|
+
const LINKEDIN_RATE_LIMIT_RETRY_LIMIT$1 = 25;
|
|
14520
|
+
const LINKEDIN_RATE_LIMIT_MAX_FALLBACK_SECONDS$1 = 60;
|
|
14257
14521
|
const linkedinSyncPageDelayMs = (pagesCompleted) => {
|
|
14258
14522
|
if (pagesCompleted < 10) return 1e3;
|
|
14259
14523
|
if (pagesCompleted < 20) return 2500;
|
|
@@ -14346,6 +14610,60 @@ const createSyncProgressReporter = (deps, collectionKey) => {
|
|
|
14346
14610
|
stop: () => void 0
|
|
14347
14611
|
};
|
|
14348
14612
|
};
|
|
14613
|
+
const writeRateLimitRetryNotice$1 = (args) => {
|
|
14614
|
+
process.stderr.write(`LinkedIn rate limited; waiting ${args.delaySeconds}s before retry ${args.retryCount}/${LINKEDIN_RATE_LIMIT_RETRY_LIMIT$1}.\n`);
|
|
14615
|
+
};
|
|
14616
|
+
const linkedInRateLimitFallbackDelaySeconds$1 = (retryCount) => Math.min(2 ** Math.max(0, retryCount - 1), LINKEDIN_RATE_LIMIT_MAX_FALLBACK_SECONDS$1);
|
|
14617
|
+
const shellQuote$1 = (value) => /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
14618
|
+
const syncRetryCommand$1 = (platform, args) => {
|
|
14619
|
+
const parts = [
|
|
14620
|
+
"social",
|
|
14621
|
+
platform,
|
|
14622
|
+
"sync"
|
|
14623
|
+
];
|
|
14624
|
+
if (args.collection) parts.push(String(args.collection));
|
|
14625
|
+
if (typeof args.account === "string") parts.push("--account", args.account);
|
|
14626
|
+
if (typeof args.since === "string") parts.push("--since", args.since);
|
|
14627
|
+
if (args.timeout != null) parts.push("--timeout", String(args.timeout));
|
|
14628
|
+
return parts.map(shellQuote$1).join(" ");
|
|
14629
|
+
};
|
|
14630
|
+
const rateLimitCauseFor$1 = (error) => {
|
|
14631
|
+
if (isRateLimitError(error)) return error;
|
|
14632
|
+
if (error instanceof SyncPageError && isRateLimitError(error.cause)) return error.cause;
|
|
14633
|
+
};
|
|
14634
|
+
const enrichRateLimitSyncError$1 = (args) => {
|
|
14635
|
+
const cause = rateLimitCauseFor$1(args.error);
|
|
14636
|
+
if (!cause || typeof cause !== "object") return;
|
|
14637
|
+
const retryAfterSeconds = retryAfterSecondsFromRateLimitError(cause);
|
|
14638
|
+
const resumeAt = retryAfterSeconds === void 0 ? void 0 : new Date(Date.now() + retryAfterSeconds * 1e3).toISOString();
|
|
14639
|
+
const retryCommand = syncRetryCommand$1(args.platform, args.commandArgs);
|
|
14640
|
+
const cursorPersisted = args.error instanceof SyncPageError && args.error.cursorPersisted === true;
|
|
14641
|
+
const savedPages = args.error instanceof SyncPageError && args.error.pagesCompleted > 0;
|
|
14642
|
+
const retryAt = resumeAt ? ` at or after ${resumeAt}` : "";
|
|
14643
|
+
Object.assign(cause, {
|
|
14644
|
+
resumeAt,
|
|
14645
|
+
retryCommand,
|
|
14646
|
+
hint: `Rate limited. Wait${retryAt}, then run \`${retryCommand}\`. ${cursorPersisted ? "Already-synced pages are saved; this sync will resume from the saved cursor." : savedPages ? "Already-synced rows are saved, but this collection cannot safely resume the partial page walk; the re-run may re-fetch unfinished pages." : "No sync page completed before the abort."}`,
|
|
14647
|
+
syncResume: {
|
|
14648
|
+
collection: args.collectionKey,
|
|
14649
|
+
command: retryCommand,
|
|
14650
|
+
cursorPersisted,
|
|
14651
|
+
savedPages,
|
|
14652
|
+
behavior: cursorPersisted ? "resume" : "rerun"
|
|
14653
|
+
}
|
|
14654
|
+
});
|
|
14655
|
+
return cause;
|
|
14656
|
+
};
|
|
14657
|
+
const syncRateLimitOptions$1 = (timeoutSeconds) => ({
|
|
14658
|
+
retryAfterSecondsFor: retryAfterSecondsFromRateLimitError,
|
|
14659
|
+
fallbackDelaySecondsForRetry: linkedInRateLimitFallbackDelaySeconds$1,
|
|
14660
|
+
isRateLimitError,
|
|
14661
|
+
...timeoutSeconds === void 0 ? {} : {
|
|
14662
|
+
timeoutSeconds,
|
|
14663
|
+
maxRetries: LINKEDIN_RATE_LIMIT_RETRY_LIMIT$1,
|
|
14664
|
+
onRetry: writeRateLimitRetryNotice$1
|
|
14665
|
+
}
|
|
14666
|
+
});
|
|
14349
14667
|
const buildSync$1 = (deps, platform) => defineCommand({
|
|
14350
14668
|
meta: commandMeta$2({
|
|
14351
14669
|
name: "sync",
|
|
@@ -14401,7 +14719,7 @@ const buildSync$1 = (deps, platform) => defineCommand({
|
|
|
14401
14719
|
},
|
|
14402
14720
|
since: {
|
|
14403
14721
|
type: "string",
|
|
14404
|
-
description: "ISO date
|
|
14722
|
+
description: "ISO date (2026-05-04) or datetime (2026-05-04T00:00:00Z)"
|
|
14405
14723
|
},
|
|
14406
14724
|
reset: {
|
|
14407
14725
|
type: "boolean",
|
|
@@ -14410,6 +14728,10 @@ const buildSync$1 = (deps, platform) => defineCommand({
|
|
|
14410
14728
|
account: {
|
|
14411
14729
|
type: "string",
|
|
14412
14730
|
description: "Account selector; defaults to the first connected account"
|
|
14731
|
+
},
|
|
14732
|
+
timeout: {
|
|
14733
|
+
type: "string",
|
|
14734
|
+
description: "Optional LinkedIn rate-limit wait budget in seconds"
|
|
14413
14735
|
}
|
|
14414
14736
|
},
|
|
14415
14737
|
run: async ({ args }) => {
|
|
@@ -14421,6 +14743,7 @@ const buildSync$1 = (deps, platform) => defineCommand({
|
|
|
14421
14743
|
const accountSelector = requireAccountSelector$1(res.account.selector);
|
|
14422
14744
|
const accountHeaders = { "x-social-account-id": accountSelector };
|
|
14423
14745
|
if (args.credits != null) throw new UsageError("Unsupported option --credits for linkedin sync.");
|
|
14746
|
+
const timeoutSeconds = args.timeout == null ? void 0 : deps.parseIntegerString("--timeout", args.timeout, { min: 1 });
|
|
14424
14747
|
if (!args.collection) {
|
|
14425
14748
|
if (args.reset === true) throw new UsageError("--reset requires a collection");
|
|
14426
14749
|
const cache = openLinkedinCache({
|
|
@@ -14493,12 +14816,20 @@ const buildSync$1 = (deps, platform) => defineCommand({
|
|
|
14493
14816
|
collection,
|
|
14494
14817
|
since,
|
|
14495
14818
|
onPage: progress.onPage,
|
|
14496
|
-
onProgress
|
|
14819
|
+
onProgress,
|
|
14820
|
+
rateLimit: syncRateLimitOptions$1(timeoutSeconds)
|
|
14497
14821
|
});
|
|
14498
14822
|
progress.stop(summary);
|
|
14499
14823
|
deps.printData(summary);
|
|
14500
14824
|
} catch (error) {
|
|
14501
14825
|
progress.error();
|
|
14826
|
+
const rateLimitError = enrichRateLimitSyncError$1({
|
|
14827
|
+
error,
|
|
14828
|
+
platform,
|
|
14829
|
+
commandArgs: args,
|
|
14830
|
+
collectionKey: collection.key
|
|
14831
|
+
});
|
|
14832
|
+
if (rateLimitError) throw rateLimitError;
|
|
14502
14833
|
throw error;
|
|
14503
14834
|
} finally {
|
|
14504
14835
|
cache.close();
|
|
@@ -14532,7 +14863,7 @@ const buildSql$1 = (deps, platform) => defineCommand({
|
|
|
14532
14863
|
formats: ["json"],
|
|
14533
14864
|
projection: false
|
|
14534
14865
|
},
|
|
14535
|
-
examples: [{ command: "social linkedin sql \"SELECT
|
|
14866
|
+
examples: [{ command: "social linkedin sql \"SELECT user_username, user_name, user_headline FROM li_connections WHERE user_headline LIKE '%founder%' LIMIT 20\"" }, { command: "social linkedin sql \"SELECT sender_name, text FROM li_messages ORDER BY created_at DESC LIMIT 20\"" }],
|
|
14536
14867
|
cost: {
|
|
14537
14868
|
metered: false,
|
|
14538
14869
|
unit: "local_cache",
|
|
@@ -14659,7 +14990,7 @@ const buildMark = (deps) => {
|
|
|
14659
14990
|
},
|
|
14660
14991
|
account: {
|
|
14661
14992
|
type: "string",
|
|
14662
|
-
description: "Account selector: @
|
|
14993
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
14663
14994
|
}
|
|
14664
14995
|
},
|
|
14665
14996
|
run: async ({ args, cmd }) => {
|
|
@@ -14770,7 +15101,7 @@ const buildMessageDelete = (deps) => {
|
|
|
14770
15101
|
},
|
|
14771
15102
|
account: {
|
|
14772
15103
|
type: "string",
|
|
14773
|
-
description: "Account selector: @
|
|
15104
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
14774
15105
|
}
|
|
14775
15106
|
},
|
|
14776
15107
|
run: async ({ args, cmd }) => {
|
|
@@ -14861,7 +15192,7 @@ const buildMessageEdit = (deps) => {
|
|
|
14861
15192
|
},
|
|
14862
15193
|
account: {
|
|
14863
15194
|
type: "string",
|
|
14864
|
-
description: "Account selector: @
|
|
15195
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
14865
15196
|
}
|
|
14866
15197
|
},
|
|
14867
15198
|
run: async ({ args, cmd }) => {
|
|
@@ -14925,12 +15256,12 @@ const buildMessage$1 = (deps, sendCommand) => {
|
|
|
14925
15256
|
target: {
|
|
14926
15257
|
type: "positional",
|
|
14927
15258
|
required: true,
|
|
14928
|
-
description: "LinkedIn person or conversation target: @
|
|
15259
|
+
description: "LinkedIn person or conversation target: @username, profile URL, profile_id:<id>, chat_id:<id>, or a messaging thread URL",
|
|
14929
15260
|
valueHint: "target"
|
|
14930
15261
|
},
|
|
14931
15262
|
account: {
|
|
14932
15263
|
type: "string",
|
|
14933
|
-
description: "Account selector: @
|
|
15264
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
14934
15265
|
}
|
|
14935
15266
|
},
|
|
14936
15267
|
run: async ({ args }) => {
|
|
@@ -15014,7 +15345,7 @@ const buildPosts = (liveCommand) => defineCommand({
|
|
|
15014
15345
|
});
|
|
15015
15346
|
}
|
|
15016
15347
|
});
|
|
15017
|
-
const messagesTeachingUsage = "LinkedIn messages live in your local mirror. Read them with: social linkedin sql \"SELECT
|
|
15348
|
+
const messagesTeachingUsage = "LinkedIn messages live in your local mirror. Read them with: social linkedin sql \"SELECT sender_name, text FROM li_messages ORDER BY created_at DESC LIMIT 20\" (after `social linkedin sync messages`). To change read state: social linkedin messages chat_id:<id> mark read|unread";
|
|
15018
15349
|
const buildMessages = (deps) => {
|
|
15019
15350
|
const markCommand = buildMark(deps);
|
|
15020
15351
|
const schemaContract = markCommand.meta.schemaContract;
|
|
@@ -15037,7 +15368,7 @@ const buildMessages = (deps) => {
|
|
|
15037
15368
|
},
|
|
15038
15369
|
account: {
|
|
15039
15370
|
type: "string",
|
|
15040
|
-
description: "Account selector: @
|
|
15371
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
15041
15372
|
}
|
|
15042
15373
|
},
|
|
15043
15374
|
run: async ({ args }) => {
|
|
@@ -15374,12 +15705,12 @@ const XUser = looseObject({
|
|
|
15374
15705
|
const rowFrom = (user) => {
|
|
15375
15706
|
const providerId = user.id.trim();
|
|
15376
15707
|
if (!providerId) return null;
|
|
15377
|
-
const
|
|
15378
|
-
const displayName = user.name?.trim() ||
|
|
15708
|
+
const username = user.username?.trim() || null;
|
|
15709
|
+
const displayName = user.name?.trim() || username || "";
|
|
15379
15710
|
if (!displayName) return null;
|
|
15380
15711
|
return {
|
|
15381
15712
|
providerId,
|
|
15382
|
-
|
|
15713
|
+
username,
|
|
15383
15714
|
displayName,
|
|
15384
15715
|
avatarURL: user.profile_image_url?.trim() || null,
|
|
15385
15716
|
headline: user.description?.trim() || null,
|
|
@@ -15607,7 +15938,7 @@ const X_OPERATIONS = [
|
|
|
15607
15938
|
required: true,
|
|
15608
15939
|
kind: "string",
|
|
15609
15940
|
valueHint: "target",
|
|
15610
|
-
description: "Target profile: @
|
|
15941
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>"
|
|
15611
15942
|
}]
|
|
15612
15943
|
},
|
|
15613
15944
|
{
|
|
@@ -16159,7 +16490,7 @@ const X_OPERATIONS = [
|
|
|
16159
16490
|
required: true,
|
|
16160
16491
|
kind: "string",
|
|
16161
16492
|
valueHint: "target",
|
|
16162
|
-
description: "Target profile: @
|
|
16493
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>. Defaults to the selected account when omitted."
|
|
16163
16494
|
},
|
|
16164
16495
|
{
|
|
16165
16496
|
name: "user.fields",
|
|
@@ -16335,7 +16666,7 @@ const X_OPERATIONS = [
|
|
|
16335
16666
|
required: true,
|
|
16336
16667
|
kind: "string",
|
|
16337
16668
|
valueHint: "target",
|
|
16338
|
-
description: "Target profile: @
|
|
16669
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>. Defaults to the selected account when omitted."
|
|
16339
16670
|
},
|
|
16340
16671
|
{
|
|
16341
16672
|
name: "max_results",
|
|
@@ -16419,7 +16750,7 @@ const X_OPERATIONS = [
|
|
|
16419
16750
|
required: true,
|
|
16420
16751
|
kind: "string",
|
|
16421
16752
|
valueHint: "target",
|
|
16422
|
-
description: "Target profile: @
|
|
16753
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>. Defaults to the selected account when omitted."
|
|
16423
16754
|
},
|
|
16424
16755
|
{
|
|
16425
16756
|
name: "max_results",
|
|
@@ -16510,7 +16841,7 @@ const X_OPERATIONS = [
|
|
|
16510
16841
|
required: true,
|
|
16511
16842
|
kind: "string",
|
|
16512
16843
|
valueHint: "target",
|
|
16513
|
-
description: "Target profile: @
|
|
16844
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>. Defaults to the selected account when omitted."
|
|
16514
16845
|
},
|
|
16515
16846
|
{
|
|
16516
16847
|
name: "max_results",
|
|
@@ -16622,7 +16953,7 @@ const X_OPERATIONS = [
|
|
|
16622
16953
|
required: true,
|
|
16623
16954
|
kind: "string",
|
|
16624
16955
|
valueHint: "target",
|
|
16625
|
-
description: "Target profile: @
|
|
16956
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>. Defaults to the selected account when omitted."
|
|
16626
16957
|
},
|
|
16627
16958
|
{
|
|
16628
16959
|
name: "since_id",
|
|
@@ -16905,7 +17236,7 @@ const X_OPERATIONS = [
|
|
|
16905
17236
|
required: true,
|
|
16906
17237
|
kind: "string",
|
|
16907
17238
|
valueHint: "target",
|
|
16908
|
-
description: "Target profile: @
|
|
17239
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>. Defaults to the selected account when omitted."
|
|
16909
17240
|
},
|
|
16910
17241
|
{
|
|
16911
17242
|
name: "since_id",
|
|
@@ -17909,13 +18240,13 @@ const targetDescriptionFor = (targetKind) => {
|
|
|
17909
18240
|
const kinds = kindsOf(targetKind);
|
|
17910
18241
|
if (kinds.length === 1) {
|
|
17911
18242
|
const [kind] = kinds;
|
|
17912
|
-
if (kind === "profile") return "Target profile: @
|
|
18243
|
+
if (kind === "profile") return "Target profile: @username, profile URL, or profile_id:<id>";
|
|
17913
18244
|
if (kind === "post") return "Target post: a post URL or post_id:<id>";
|
|
17914
18245
|
if (kind === "list") return "Target list: a list URL or list_id:<id>";
|
|
17915
18246
|
if (kind === "chat") return "Target conversation: a chat URL or chat_id:<id>";
|
|
17916
18247
|
return "Target company: a company URL or company_id:<id>";
|
|
17917
18248
|
}
|
|
17918
|
-
return "Target: @
|
|
18249
|
+
return "Target: @username, URL, or a typed ID such as profile_id:<id>.";
|
|
17919
18250
|
};
|
|
17920
18251
|
const commandParameters = (op) => op.parameters.filter((p) => !(p.in === "path" && ownMode(op, p.name) === "implicit"));
|
|
17921
18252
|
const parameterHelpRank = (op, param, index) => {
|
|
@@ -18162,7 +18493,7 @@ const xRuntimeAdapter = {
|
|
|
18162
18493
|
accountFromOwnAccount: (ownAccount) => ownAccount.account,
|
|
18163
18494
|
ownAccountTargetId: (ownAccount) => ownAccount.xUserId,
|
|
18164
18495
|
ownAccountHeader: (_ownAccount, selectedAccount) => selectedAccount.selector,
|
|
18165
|
-
accountRecordInput: (ownAccount) => ownAccount.account.
|
|
18496
|
+
accountRecordInput: (ownAccount) => ownAccount.account.username ?? ownAccount.xUserId,
|
|
18166
18497
|
argAliases,
|
|
18167
18498
|
bodyTextResolvesTarget: resolvesTargetArg,
|
|
18168
18499
|
bodyTextTargetRequired: resolvesTargetArg,
|
|
@@ -18238,6 +18569,50 @@ const syncListRowFor = (collection, shortName, state) => {
|
|
|
18238
18569
|
objectCount: lastSynced === null ? null : state?.objectCount ?? 0
|
|
18239
18570
|
};
|
|
18240
18571
|
};
|
|
18572
|
+
const shellQuote = (value) => /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
18573
|
+
const syncRetryCommand = (platform, args) => {
|
|
18574
|
+
const parts = [
|
|
18575
|
+
"social",
|
|
18576
|
+
platform,
|
|
18577
|
+
"sync"
|
|
18578
|
+
];
|
|
18579
|
+
if (args.collection) parts.push(String(args.collection));
|
|
18580
|
+
if (typeof args.account === "string") parts.push("--account", args.account);
|
|
18581
|
+
if (typeof args.since === "string") parts.push("--since", args.since);
|
|
18582
|
+
if (args.timeout != null) parts.push("--timeout", String(args.timeout));
|
|
18583
|
+
return parts.map(shellQuote).join(" ");
|
|
18584
|
+
};
|
|
18585
|
+
const rateLimitCauseFor = (error) => {
|
|
18586
|
+
if (isRateLimitError(error)) return error;
|
|
18587
|
+
if (error instanceof SyncPageError && isRateLimitError(error.cause)) return error.cause;
|
|
18588
|
+
};
|
|
18589
|
+
const enrichRateLimitSyncError = (args) => {
|
|
18590
|
+
const cause = rateLimitCauseFor(args.error);
|
|
18591
|
+
if (!cause || typeof cause !== "object") return;
|
|
18592
|
+
const retryAfterSeconds = retryAfterSecondsFromRateLimitError(cause);
|
|
18593
|
+
const resumeAt = retryAfterSeconds === void 0 ? void 0 : new Date(Date.now() + retryAfterSeconds * 1e3).toISOString();
|
|
18594
|
+
const retryCommand = syncRetryCommand(args.platform, args.commandArgs);
|
|
18595
|
+
const cursorPersisted = args.error instanceof SyncPageError && args.error.cursorPersisted === true;
|
|
18596
|
+
const savedPages = args.error instanceof SyncPageError && args.error.pagesCompleted > 0;
|
|
18597
|
+
const retryAt = resumeAt ? ` at or after ${resumeAt}` : "";
|
|
18598
|
+
Object.assign(cause, {
|
|
18599
|
+
resumeAt,
|
|
18600
|
+
retryCommand,
|
|
18601
|
+
hint: `Rate limited. Wait${retryAt}, then run \`${retryCommand}\`. ${cursorPersisted ? "Already-synced pages are saved; this sync will resume from the saved cursor." : savedPages ? "Already-synced rows are saved, but this collection cannot safely resume the partial page walk; the re-run may re-fetch unfinished pages." : "No sync page completed before the abort."}`,
|
|
18602
|
+
syncResume: {
|
|
18603
|
+
collection: args.collectionKey,
|
|
18604
|
+
command: retryCommand,
|
|
18605
|
+
cursorPersisted,
|
|
18606
|
+
savedPages,
|
|
18607
|
+
behavior: cursorPersisted ? "resume" : "rerun"
|
|
18608
|
+
}
|
|
18609
|
+
});
|
|
18610
|
+
return cause;
|
|
18611
|
+
};
|
|
18612
|
+
const syncRateLimitOptions = () => ({
|
|
18613
|
+
retryAfterSecondsFor: retryAfterSecondsFromRateLimitError,
|
|
18614
|
+
isRateLimitError
|
|
18615
|
+
});
|
|
18241
18616
|
const buildSync = (deps, platform) => defineCommand({
|
|
18242
18617
|
meta: commandMeta$1({
|
|
18243
18618
|
name: "sync",
|
|
@@ -18288,7 +18663,7 @@ const buildSync = (deps, platform) => defineCommand({
|
|
|
18288
18663
|
},
|
|
18289
18664
|
since: {
|
|
18290
18665
|
type: "string",
|
|
18291
|
-
description: "ISO date
|
|
18666
|
+
description: "ISO date (2026-05-04) or datetime (2026-05-04T00:00:00Z)"
|
|
18292
18667
|
},
|
|
18293
18668
|
reset: {
|
|
18294
18669
|
type: "boolean",
|
|
@@ -18297,6 +18672,10 @@ const buildSync = (deps, platform) => defineCommand({
|
|
|
18297
18672
|
account: {
|
|
18298
18673
|
type: "string",
|
|
18299
18674
|
description: "Account selector; defaults to the first connected account"
|
|
18675
|
+
},
|
|
18676
|
+
timeout: {
|
|
18677
|
+
type: "string",
|
|
18678
|
+
description: "Optional rate-limit wait budget in seconds"
|
|
18300
18679
|
}
|
|
18301
18680
|
},
|
|
18302
18681
|
run: async ({ args }) => {
|
|
@@ -18308,6 +18687,7 @@ const buildSync = (deps, platform) => defineCommand({
|
|
|
18308
18687
|
const accountSelector = requireAccountSelector(res.account.selector);
|
|
18309
18688
|
const accountHeaders = { "x-social-account-id": accountSelector };
|
|
18310
18689
|
if (args.credits != null) throw new UsageError("Unsupported option --credits for x sync.");
|
|
18690
|
+
if (args.timeout != null) deps.parseIntegerString("--timeout", args.timeout, { min: 1 });
|
|
18311
18691
|
if (!args.collection) {
|
|
18312
18692
|
if (args.reset === true) throw new UsageError("--reset requires a collection");
|
|
18313
18693
|
const cache = openXCache({
|
|
@@ -18366,9 +18746,19 @@ const buildSync = (deps, platform) => defineCommand({
|
|
|
18366
18746
|
}, {
|
|
18367
18747
|
collection,
|
|
18368
18748
|
since,
|
|
18369
|
-
onProgress
|
|
18749
|
+
onProgress,
|
|
18750
|
+
rateLimit: syncRateLimitOptions()
|
|
18370
18751
|
});
|
|
18371
18752
|
deps.printData(summary);
|
|
18753
|
+
} catch (error) {
|
|
18754
|
+
const rateLimitError = enrichRateLimitSyncError({
|
|
18755
|
+
error,
|
|
18756
|
+
platform,
|
|
18757
|
+
commandArgs: args,
|
|
18758
|
+
collectionKey: collection.key
|
|
18759
|
+
});
|
|
18760
|
+
if (rateLimitError) throw rateLimitError;
|
|
18761
|
+
throw error;
|
|
18372
18762
|
} finally {
|
|
18373
18763
|
cache.close();
|
|
18374
18764
|
}
|
|
@@ -18403,7 +18793,7 @@ const buildSql = (deps, platform) => {
|
|
|
18403
18793
|
formats: ["json"],
|
|
18404
18794
|
projection: false
|
|
18405
18795
|
},
|
|
18406
|
-
examples: [{ command: "social x sql \"SELECT
|
|
18796
|
+
examples: [{ command: "social x sql \"SELECT username, name, followers_count FROM x_followers ORDER BY followers_count DESC LIMIT 20\"" }, { command: "social x sql \"SELECT username, name FROM x_following WHERE verified = 1 ORDER BY followers_count DESC LIMIT 20\"" }],
|
|
18407
18797
|
cost: {
|
|
18408
18798
|
metered: false,
|
|
18409
18799
|
unit: "local_cache",
|
|
@@ -18509,7 +18899,7 @@ const buildTweets = (deps) => {
|
|
|
18509
18899
|
if (args.target !== void 0) args.target = {
|
|
18510
18900
|
...args.target,
|
|
18511
18901
|
required: true,
|
|
18512
|
-
description: "Target profile: @
|
|
18902
|
+
description: "Target profile: @username, profile URL, or profile_id:<id>"
|
|
18513
18903
|
};
|
|
18514
18904
|
return defineCommand({
|
|
18515
18905
|
meta: commandMeta$1({
|
|
@@ -18543,12 +18933,12 @@ const buildMessage = (deps) => {
|
|
|
18543
18933
|
recipients: {
|
|
18544
18934
|
type: "positional",
|
|
18545
18935
|
required: true,
|
|
18546
|
-
description: "Recipient(s): chat URL, chat_id:<id>, @
|
|
18936
|
+
description: "Recipient(s): chat URL, chat_id:<id>, @username, profile URL, or profile_id:<id>; comma-separate profiles for a group",
|
|
18547
18937
|
valueHint: "recipients"
|
|
18548
18938
|
},
|
|
18549
18939
|
account: {
|
|
18550
18940
|
type: "string",
|
|
18551
|
-
description: "Account selector: @
|
|
18941
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
18552
18942
|
}
|
|
18553
18943
|
},
|
|
18554
18944
|
run: async ({ args }) => {
|
|
@@ -18664,7 +19054,7 @@ const buildReplies = (deps) => {
|
|
|
18664
19054
|
},
|
|
18665
19055
|
account: {
|
|
18666
19056
|
type: "string",
|
|
18667
|
-
description: "Account selector: @
|
|
19057
|
+
description: "Account selector: @username or profile_id:<id>; overrides config default"
|
|
18668
19058
|
}
|
|
18669
19059
|
},
|
|
18670
19060
|
run: async ({ args }) => {
|
|
@@ -18834,14 +19224,14 @@ const prepareAccountConnect$1 = async (deps) => {
|
|
|
18834
19224
|
await pollForSeat$2(deps);
|
|
18835
19225
|
};
|
|
18836
19226
|
const normalizeIdentifier$1 = (value) => value.trim().replace(LEADING_AT_PATTERN$1, "").toLowerCase();
|
|
18837
|
-
const
|
|
19227
|
+
const accountUsername = (account) => account.username.replace(LEADING_AT_PATTERN$1, "");
|
|
18838
19228
|
const linkedinReconnectTargetFrom = (value) => {
|
|
18839
19229
|
const trimmed = value.trim();
|
|
18840
19230
|
if (trimmed.startsWith("@")) {
|
|
18841
|
-
const
|
|
18842
|
-
if (
|
|
18843
|
-
type: "
|
|
18844
|
-
value:
|
|
19231
|
+
const username = normalizeIdentifier$1(trimmed);
|
|
19232
|
+
if (username) return {
|
|
19233
|
+
type: "username",
|
|
19234
|
+
value: username
|
|
18845
19235
|
};
|
|
18846
19236
|
}
|
|
18847
19237
|
if (trimmed.startsWith("profile_id:")) {
|
|
@@ -18856,20 +19246,20 @@ const linkedinReconnectTargetFrom = (value) => {
|
|
|
18856
19246
|
if ((url.protocol === "https:" || url.protocol === "http:") && LINKEDIN_PROFILE_HOST_PATTERN.test(url.hostname.replace(/^www\./i, ""))) {
|
|
18857
19247
|
const [, section, publicIdentifier] = url.pathname.split("/");
|
|
18858
19248
|
if (section === "in" && publicIdentifier) return {
|
|
18859
|
-
type: "
|
|
19249
|
+
type: "username",
|
|
18860
19250
|
value: decodeURIComponent(publicIdentifier).toLowerCase()
|
|
18861
19251
|
};
|
|
18862
19252
|
}
|
|
18863
19253
|
} catch {}
|
|
18864
|
-
throw new Error("LinkedIn reconnect account must be @
|
|
19254
|
+
throw new Error("LinkedIn reconnect account must be @username, a LinkedIn profile URL, or profile_id:<id>.");
|
|
18865
19255
|
};
|
|
18866
19256
|
const accountMatchesReconnectTarget = (account, target) => {
|
|
18867
19257
|
if (target.type === "profileId") return account.profileId.toLowerCase() === target.value;
|
|
18868
|
-
return normalizeIdentifier$1(account.
|
|
19258
|
+
return normalizeIdentifier$1(account.username) === target.value;
|
|
18869
19259
|
};
|
|
18870
19260
|
const reconnectCommandTargetFor = (account) => {
|
|
18871
|
-
const
|
|
18872
|
-
return normalizeIdentifier$1(
|
|
19261
|
+
const username = accountUsername(account);
|
|
19262
|
+
return normalizeIdentifier$1(username) === normalizeIdentifier$1(account.profileId) ? `profile_id:${account.profileId}` : `@${username}`;
|
|
18873
19263
|
};
|
|
18874
19264
|
const listLinkedinLifecycleAccounts = (deps, includeDisconnected = true) => deps.client.listAccounts({
|
|
18875
19265
|
platform: LINKEDIN_PLATFORM,
|
|
@@ -18929,7 +19319,7 @@ const reconnectLinkedinAccount = async (deps, identifier) => {
|
|
|
18929
19319
|
return {
|
|
18930
19320
|
platform: LINKEDIN_PLATFORM,
|
|
18931
19321
|
status: "connected",
|
|
18932
|
-
account: await pollForAccount$1(deps, (candidate) => candidate.profileId === account.profileId && candidate.status === CONNECTED_STATUS, `Timed out waiting for LinkedIn account
|
|
19322
|
+
account: await pollForAccount$1(deps, (candidate) => candidate.profileId === account.profileId && candidate.status === CONNECTED_STATUS, `Timed out waiting for LinkedIn account @${accountUsername(account)} to reconnect.`)
|
|
18933
19323
|
};
|
|
18934
19324
|
};
|
|
18935
19325
|
const disconnectLinkedinAccount = async (deps, identifier) => {
|
|
@@ -19004,7 +19394,7 @@ const prepareAccountConnect = async (deps) => {
|
|
|
19004
19394
|
};
|
|
19005
19395
|
const accountKey = (account) => account.profileId;
|
|
19006
19396
|
const normalizeIdentifier = (value) => value.trim().replace(/^@+/, "").toLowerCase();
|
|
19007
|
-
const accountSelectorFor$1 = (account) => account.
|
|
19397
|
+
const accountSelectorFor$1 = (account) => account.username ? `@${account.username.replace(/^@+/, "")}` : `profile_id:${account.profileId}`;
|
|
19008
19398
|
const assertXAccountSelector = (target) => {
|
|
19009
19399
|
const trimmed = target.trim();
|
|
19010
19400
|
if (trimmed.startsWith("profile_id:")) {
|
|
@@ -19012,7 +19402,7 @@ const assertXAccountSelector = (target) => {
|
|
|
19012
19402
|
if (profileId && !/[/?#%]/.test(profileId)) return;
|
|
19013
19403
|
}
|
|
19014
19404
|
if (trimmed.startsWith("@") && !/[/?#%]/.test(trimmed)) return;
|
|
19015
|
-
throw new Error("account must be an @
|
|
19405
|
+
throw new Error("account must be an @username or profile_id:<id>.");
|
|
19016
19406
|
};
|
|
19017
19407
|
const findAccount = (accounts, target) => {
|
|
19018
19408
|
const trimmed = target.trim();
|
|
@@ -19020,7 +19410,7 @@ const findAccount = (accounts, target) => {
|
|
|
19020
19410
|
const profileId = trimmed.slice(11).trim().toLowerCase();
|
|
19021
19411
|
return accounts.find((account) => account.profileId.toLowerCase() === profileId);
|
|
19022
19412
|
}
|
|
19023
|
-
if (trimmed.startsWith("@")) return accounts.find((account) => normalizeIdentifier(account.
|
|
19413
|
+
if (trimmed.startsWith("@")) return accounts.find((account) => normalizeIdentifier(account.username) === normalizeIdentifier(target));
|
|
19024
19414
|
};
|
|
19025
19415
|
const listXLifecycleAccounts = (deps, includeDisconnected) => deps.client.accounts.list({
|
|
19026
19416
|
platform: "x",
|
|
@@ -21629,7 +22019,7 @@ const createInstance = (defaults) => {
|
|
|
21629
22019
|
const ky = createInstance();
|
|
21630
22020
|
//#endregion
|
|
21631
22021
|
//#region package.json
|
|
21632
|
-
var version$1 = "0.
|
|
22022
|
+
var version$1 = "0.8.0";
|
|
21633
22023
|
//#endregion
|
|
21634
22024
|
//#region src/lib/env.ts
|
|
21635
22025
|
const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
|
|
@@ -21762,12 +22152,20 @@ var CLIAPIError = class extends Error {
|
|
|
21762
22152
|
status;
|
|
21763
22153
|
body;
|
|
21764
22154
|
retryAfterSeconds;
|
|
21765
|
-
|
|
22155
|
+
hint;
|
|
22156
|
+
resumeAt;
|
|
22157
|
+
retryCommand;
|
|
22158
|
+
syncResume;
|
|
22159
|
+
constructor(message, status, body, retryAfterSeconds, details = {}) {
|
|
21766
22160
|
super(message);
|
|
21767
22161
|
this.name = "CLIAPIError";
|
|
21768
22162
|
this.status = status;
|
|
21769
22163
|
this.body = body;
|
|
21770
22164
|
this.retryAfterSeconds = retryAfterSeconds;
|
|
22165
|
+
this.hint = details.hint;
|
|
22166
|
+
this.resumeAt = details.resumeAt;
|
|
22167
|
+
this.retryCommand = details.retryCommand;
|
|
22168
|
+
this.syncResume = details.syncResume;
|
|
21771
22169
|
}
|
|
21772
22170
|
};
|
|
21773
22171
|
const readBearerToken = async () => {
|
|
@@ -21777,8 +22175,12 @@ const readBearerToken = async () => {
|
|
|
21777
22175
|
};
|
|
21778
22176
|
const linkedInRateLimitFallbackDelaySeconds = (retryCount) => Math.min(2 ** Math.max(0, retryCount - 1), LINKEDIN_RATE_LIMIT_MAX_FALLBACK_SECONDS);
|
|
21779
22177
|
const linkedInRateLimitRetryDelaySeconds = (retryCount, retryAfterSeconds) => retryAfterSeconds ?? linkedInRateLimitFallbackDelaySeconds(retryCount);
|
|
22178
|
+
const linkedInRateLimitWaitBudgetCovers = (args) => args.delaySeconds <= (args.waitBudgetSeconds ?? LINKEDIN_RATE_LIMIT_MAX_FALLBACK_SECONDS);
|
|
21780
22179
|
const isLinkedInProxyRequest = (request) => new URL(request.url).pathname.includes("/linkedin/");
|
|
21781
|
-
const shouldRetryLinkedInRateLimit = (args) => args.error instanceof HTTPError && args.error.response.status === 429 && isLinkedInProxyRequest(args.error.request) && args.retryCount <= LINKEDIN_RATE_LIMIT_RETRY_LIMIT
|
|
22180
|
+
const shouldRetryLinkedInRateLimit = (args) => args.error instanceof HTTPError && args.error.response.status === 429 && isLinkedInProxyRequest(args.error.request) && args.retryCount <= LINKEDIN_RATE_LIMIT_RETRY_LIMIT && linkedInRateLimitWaitBudgetCovers({
|
|
22181
|
+
delaySeconds: linkedInRateLimitRetryDelaySeconds(args.retryCount, retryAfterSecondsFrom(args.error.response.headers.get("retry-after"))),
|
|
22182
|
+
waitBudgetSeconds: args.waitBudgetSeconds
|
|
22183
|
+
});
|
|
21782
22184
|
const writeRateLimitRetryNotice = (args) => {
|
|
21783
22185
|
process.stderr.write(`LinkedIn rate limited; waiting ${args.delaySeconds}s before retry ${args.retryCount}/${LINKEDIN_RATE_LIMIT_RETRY_LIMIT}.\n`);
|
|
21784
22186
|
};
|
|
@@ -21970,26 +22372,9 @@ const authWhoamiOutput = async (client, apiURL = env.SOCIAL_API_URL) => {
|
|
|
21970
22372
|
};
|
|
21971
22373
|
}
|
|
21972
22374
|
};
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
const
|
|
21976
|
-
live: 900,
|
|
21977
|
-
analytical: 1440 * 60,
|
|
21978
|
-
historical: 10080 * 60
|
|
21979
|
-
};
|
|
21980
|
-
const CACHE_MODES = [
|
|
21981
|
-
"live",
|
|
21982
|
-
"analytical",
|
|
21983
|
-
"historical"
|
|
21984
|
-
];
|
|
21985
|
-
const MAX_CACHE_TTL_SECONDS = CACHE_MODE_TTL_SECONDS.historical;
|
|
21986
|
-
const CacheMode = _enum$1([...CACHE_MODES, "custom"]);
|
|
21987
|
-
const ConfigurableCacheMode = _enum$1(CACHE_MODES);
|
|
21988
|
-
const CLIConfig = object$1({ cache: object$1({
|
|
21989
|
-
mode: CacheMode.default("live"),
|
|
21990
|
-
cacheTTLSeconds: number$1().int().min(0).max(MAX_CACHE_TTL_SECONDS).optional()
|
|
21991
|
-
}).default({ mode: "live" }) });
|
|
21992
|
-
const defaultCLIConfig = () => ({ cache: { mode: "live" } });
|
|
22375
|
+
const MAX_CACHE_TTL_SECONDS = 10080 * 60;
|
|
22376
|
+
const CLIConfig = object$1({ cache: object$1({ cacheTTLSeconds: number$1().int().min(0).max(MAX_CACHE_TTL_SECONDS).default(900) }).default({ cacheTTLSeconds: 900 }) });
|
|
22377
|
+
const defaultCLIConfig = () => ({ cache: { cacheTTLSeconds: 900 } });
|
|
21993
22378
|
const configPath = () => join(process.env.HOME ?? homedir(), ".social", "config.json");
|
|
21994
22379
|
const parseCLIConfig = (serialized) => CLIConfig.parse(JSON.parse(serialized));
|
|
21995
22380
|
const readCLIConfig = async () => {
|
|
@@ -22007,27 +22392,15 @@ const writeCLIConfig = async (config) => {
|
|
|
22007
22392
|
await writeFile(path, `${JSON.stringify(parsed, null, 2)}\n`, { mode: 384 });
|
|
22008
22393
|
await chmod(path, 384);
|
|
22009
22394
|
};
|
|
22010
|
-
const cacheTTLSecondsFrom = (config) => config.cache.mode === "custom" ? config.cache.cacheTTLSeconds ?? CACHE_MODE_TTL_SECONDS.live : CACHE_MODE_TTL_SECONDS[config.cache.mode];
|
|
22011
|
-
const setCacheMode = async (mode) => {
|
|
22012
|
-
const next = {
|
|
22013
|
-
...await readCLIConfig(),
|
|
22014
|
-
cache: { mode }
|
|
22015
|
-
};
|
|
22016
|
-
await writeCLIConfig(next);
|
|
22017
|
-
return next;
|
|
22018
|
-
};
|
|
22019
22395
|
const setCacheTTLSeconds = async (cacheTTLSeconds) => {
|
|
22020
22396
|
const next = {
|
|
22021
22397
|
...await readCLIConfig(),
|
|
22022
|
-
cache: {
|
|
22023
|
-
mode: "custom",
|
|
22024
|
-
cacheTTLSeconds
|
|
22025
|
-
}
|
|
22398
|
+
cache: { cacheTTLSeconds }
|
|
22026
22399
|
};
|
|
22027
22400
|
await writeCLIConfig(next);
|
|
22028
22401
|
return next;
|
|
22029
22402
|
};
|
|
22030
|
-
const cacheControlHeaderFromConfig = async () => `max-age=${
|
|
22403
|
+
const cacheControlHeaderFromConfig = async () => `max-age=${(await readCLIConfig()).cache.cacheTTLSeconds}`;
|
|
22031
22404
|
//#endregion
|
|
22032
22405
|
//#region src/lib/output.ts
|
|
22033
22406
|
const isRecord$3 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -22216,7 +22589,7 @@ const resultEnvelope = ({ account, contract, data, local, resolved = [], respons
|
|
|
22216
22589
|
return {
|
|
22217
22590
|
account: {
|
|
22218
22591
|
platform: account.platform,
|
|
22219
|
-
|
|
22592
|
+
username: account.username
|
|
22220
22593
|
},
|
|
22221
22594
|
...payload,
|
|
22222
22595
|
meta: {
|
|
@@ -22246,7 +22619,11 @@ const errorPayload = (error) => {
|
|
|
22246
22619
|
type: error.name,
|
|
22247
22620
|
status: error.status,
|
|
22248
22621
|
body: error.body,
|
|
22249
|
-
retryAfterSeconds: error.retryAfterSeconds
|
|
22622
|
+
retryAfterSeconds: error.retryAfterSeconds,
|
|
22623
|
+
resumeAt: error.resumeAt,
|
|
22624
|
+
retryCommand: error.retryCommand,
|
|
22625
|
+
hint: error.hint,
|
|
22626
|
+
syncResume: error.syncResume
|
|
22250
22627
|
};
|
|
22251
22628
|
if (error instanceof Error) return {
|
|
22252
22629
|
error: error.message,
|
|
@@ -22270,7 +22647,11 @@ const apiErrorCause = (error) => ({
|
|
|
22270
22647
|
error: error.message,
|
|
22271
22648
|
status: error.status,
|
|
22272
22649
|
body: error.body,
|
|
22273
|
-
retryAfterSeconds: error.retryAfterSeconds
|
|
22650
|
+
retryAfterSeconds: error.retryAfterSeconds,
|
|
22651
|
+
resumeAt: error.resumeAt,
|
|
22652
|
+
retryCommand: error.retryCommand,
|
|
22653
|
+
hint: error.hint,
|
|
22654
|
+
syncResume: error.syncResume
|
|
22274
22655
|
});
|
|
22275
22656
|
const errorCause = (error) => {
|
|
22276
22657
|
if (!("cause" in error)) return {};
|
|
@@ -22344,7 +22725,7 @@ const TARGET_PREFIXES = {
|
|
|
22344
22725
|
request_id: "request"
|
|
22345
22726
|
};
|
|
22346
22727
|
const KIND_HINTS = {
|
|
22347
|
-
profile: "a profile (@
|
|
22728
|
+
profile: "a profile (@username, a profile URL, or profile_id:…)",
|
|
22348
22729
|
post: "a post (a post URL or post_id:…)",
|
|
22349
22730
|
list: "a list (a list URL or list_id:…)",
|
|
22350
22731
|
chat: "a conversation (a chat URL or chat_id:…)",
|
|
@@ -22456,8 +22837,8 @@ const resolveTarget = async (name, value, options) => {
|
|
|
22456
22837
|
const list = url.pathname.match(/^\/i\/lists\/(\d+)/)?.[1];
|
|
22457
22838
|
if (list) return accept("list", list, { url: url.toString() });
|
|
22458
22839
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
22459
|
-
const
|
|
22460
|
-
if (segments.length === 1 &&
|
|
22840
|
+
const username = segments[0];
|
|
22841
|
+
if (segments.length === 1 && username && !X_RESERVED_SEGMENTS.has(username.toLowerCase())) return await lookupProfile(username, url.toString());
|
|
22461
22842
|
}
|
|
22462
22843
|
if (platform === "linkedin" && isLinkedinHost(url)) {
|
|
22463
22844
|
const post = linkedinPostURN(input, url);
|
|
@@ -22478,10 +22859,10 @@ const resolveTarget = async (name, value, options) => {
|
|
|
22478
22859
|
if (colon > 0) {
|
|
22479
22860
|
const prefix = input.slice(0, colon);
|
|
22480
22861
|
const kind = TARGET_PREFIXES[prefix];
|
|
22481
|
-
if (!kind) throw validationError([name], `${name}: unknown prefix "${prefix}:". Use ${Object.keys(TARGET_PREFIXES).map((targetPrefix) => `${targetPrefix}:<id>`).join(", ")}, @
|
|
22862
|
+
if (!kind) throw validationError([name], `${name}: unknown prefix "${prefix}:". Use ${Object.keys(TARGET_PREFIXES).map((targetPrefix) => `${targetPrefix}:<id>`).join(", ")}, @username, or a URL.`);
|
|
22482
22863
|
return accept(kind, parseResourceId(name, input.slice(colon + 1)));
|
|
22483
22864
|
}
|
|
22484
|
-
throw validationError([name], `${name} must be @
|
|
22865
|
+
throw validationError([name], `${name} must be @username, a URL, or a typed ID (${Object.keys(TARGET_PREFIXES).map((targetPrefix) => `${targetPrefix}:<id>`).join(", ")}). Bare IDs are not accepted.`);
|
|
22485
22866
|
};
|
|
22486
22867
|
const plainResourceRecord = (platform, kind, id, options = {}) => resolutionRecord({
|
|
22487
22868
|
input: options.input ?? id,
|
|
@@ -22550,14 +22931,8 @@ const commandMeta = (meta) => {
|
|
|
22550
22931
|
contract
|
|
22551
22932
|
};
|
|
22552
22933
|
};
|
|
22553
|
-
const CacheStateOutput = object$1({
|
|
22554
|
-
|
|
22555
|
-
cacheTTLSeconds: number$1().int().nonnegative()
|
|
22556
|
-
});
|
|
22557
|
-
const cacheStateFrom = (config) => ({
|
|
22558
|
-
mode: config.cache.mode,
|
|
22559
|
-
cacheTTLSeconds: cacheTTLSecondsFrom(config)
|
|
22560
|
-
});
|
|
22934
|
+
const CacheStateOutput = object$1({ cacheTTLSeconds: number$1().int().nonnegative() });
|
|
22935
|
+
const cacheStateFrom = (config) => ({ cacheTTLSeconds: config.cache.cacheTTLSeconds });
|
|
22561
22936
|
const printCacheState = (config) => {
|
|
22562
22937
|
printData(cacheStateFrom(config));
|
|
22563
22938
|
};
|
|
@@ -22571,79 +22946,43 @@ const configCommand = defineCommand({
|
|
|
22571
22946
|
name: "cache",
|
|
22572
22947
|
description: "Read or configure proxy cache defaults."
|
|
22573
22948
|
},
|
|
22574
|
-
subCommands: {
|
|
22575
|
-
|
|
22576
|
-
|
|
22577
|
-
|
|
22578
|
-
|
|
22579
|
-
|
|
22580
|
-
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
22585
|
-
|
|
22586
|
-
|
|
22587
|
-
|
|
22588
|
-
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
}]
|
|
22594
|
-
}
|
|
22595
|
-
}),
|
|
22596
|
-
args: { mode: {
|
|
22597
|
-
type: "positional",
|
|
22598
|
-
required: false,
|
|
22599
|
-
description: "Cache mode: live, analytical, or historical."
|
|
22600
|
-
} },
|
|
22601
|
-
run: async ({ args }) => {
|
|
22602
|
-
if (args.mode === void 0) {
|
|
22603
|
-
printCacheState(await readCLIConfig());
|
|
22604
|
-
return;
|
|
22605
|
-
}
|
|
22606
|
-
printCacheState(await setCacheMode(ConfigurableCacheMode.parse(String(args.mode))));
|
|
22949
|
+
subCommands: { ttl: defineCommand({
|
|
22950
|
+
meta: commandMeta({
|
|
22951
|
+
name: "ttl",
|
|
22952
|
+
description: "Read or set the default cache TTL in seconds.",
|
|
22953
|
+
contract: {
|
|
22954
|
+
capability: "none",
|
|
22955
|
+
auth: {
|
|
22956
|
+
local: true,
|
|
22957
|
+
required: false
|
|
22958
|
+
},
|
|
22959
|
+
mutates: true,
|
|
22960
|
+
outputSchema: CacheStateOutput,
|
|
22961
|
+
idempotency: "idempotent",
|
|
22962
|
+
confirmation: false,
|
|
22963
|
+
responseShaping: { formats: ["json"] },
|
|
22964
|
+
hazards: [{
|
|
22965
|
+
code: "local_config_write",
|
|
22966
|
+
message: "Writes local CLI configuration when ttl_seconds is provided."
|
|
22967
|
+
}]
|
|
22607
22968
|
}
|
|
22608
22969
|
}),
|
|
22609
|
-
|
|
22610
|
-
|
|
22611
|
-
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22617
|
-
|
|
22618
|
-
},
|
|
22619
|
-
mutates: true,
|
|
22620
|
-
outputSchema: CacheStateOutput,
|
|
22621
|
-
idempotency: "idempotent",
|
|
22622
|
-
confirmation: false,
|
|
22623
|
-
responseShaping: { formats: ["json"] },
|
|
22624
|
-
hazards: [{
|
|
22625
|
-
code: "local_config_write",
|
|
22626
|
-
message: "Writes local CLI configuration when ttl_seconds is provided."
|
|
22627
|
-
}]
|
|
22628
|
-
}
|
|
22629
|
-
}),
|
|
22630
|
-
args: { ttl_seconds: {
|
|
22631
|
-
type: "positional",
|
|
22632
|
-
required: false,
|
|
22633
|
-
description: "Cache TTL in seconds. Setting this switches cache mode to custom."
|
|
22634
|
-
} },
|
|
22635
|
-
run: async ({ args }) => {
|
|
22636
|
-
if (args.ttl_seconds === void 0) {
|
|
22637
|
-
printCacheState(await readCLIConfig());
|
|
22638
|
-
return;
|
|
22639
|
-
}
|
|
22640
|
-
printCacheState(await setCacheTTLSeconds(parseIntegerString("--ttl-seconds", args.ttl_seconds, {
|
|
22641
|
-
min: 0,
|
|
22642
|
-
max: MAX_CACHE_TTL_SECONDS
|
|
22643
|
-
})));
|
|
22970
|
+
args: { ttl_seconds: {
|
|
22971
|
+
type: "positional",
|
|
22972
|
+
required: false,
|
|
22973
|
+
description: "Cache TTL in seconds."
|
|
22974
|
+
} },
|
|
22975
|
+
run: async ({ args }) => {
|
|
22976
|
+
if (args.ttl_seconds === void 0) {
|
|
22977
|
+
printCacheState(await readCLIConfig());
|
|
22978
|
+
return;
|
|
22644
22979
|
}
|
|
22645
|
-
|
|
22646
|
-
|
|
22980
|
+
printCacheState(await setCacheTTLSeconds(parseIntegerString("--ttl-seconds", args.ttl_seconds, {
|
|
22981
|
+
min: 0,
|
|
22982
|
+
max: MAX_CACHE_TTL_SECONDS
|
|
22983
|
+
})));
|
|
22984
|
+
}
|
|
22985
|
+
}) }
|
|
22647
22986
|
}) }
|
|
22648
22987
|
});
|
|
22649
22988
|
//#endregion
|
|
@@ -28928,7 +29267,7 @@ const writeAuthContract = {
|
|
|
28928
29267
|
};
|
|
28929
29268
|
const LifecycleAccountOutput = object$1({
|
|
28930
29269
|
platform: string$1(),
|
|
28931
|
-
|
|
29270
|
+
username: string$1(),
|
|
28932
29271
|
profileId: string$1(),
|
|
28933
29272
|
url: string$1(),
|
|
28934
29273
|
displayName: string$1(),
|
|
@@ -28994,17 +29333,17 @@ const AccountOverviewOutput = object$1({
|
|
|
28994
29333
|
const accountArg = {
|
|
28995
29334
|
type: "positional",
|
|
28996
29335
|
required: true,
|
|
28997
|
-
description: "Account @
|
|
29336
|
+
description: "Account @username, profile URL, or profile_id:<id>."
|
|
28998
29337
|
};
|
|
28999
29338
|
const xAccountArg = {
|
|
29000
29339
|
type: "positional",
|
|
29001
29340
|
required: true,
|
|
29002
|
-
description: "X account @
|
|
29341
|
+
description: "X account @username or profile_id:<id>."
|
|
29003
29342
|
};
|
|
29004
29343
|
const linkedinReconnectAccountArg = {
|
|
29005
29344
|
type: "positional",
|
|
29006
29345
|
required: true,
|
|
29007
|
-
description: "LinkedIn account @
|
|
29346
|
+
description: "LinkedIn account @username, profile URL, or profile_id:<id>."
|
|
29008
29347
|
};
|
|
29009
29348
|
const writeOutput = async (deps, value) => {
|
|
29010
29349
|
if (deps.writeOutput) {
|
|
@@ -29030,7 +29369,7 @@ const lifecycleConnectContract = {
|
|
|
29030
29369
|
idempotency: "non-idempotent",
|
|
29031
29370
|
confirmation: false
|
|
29032
29371
|
};
|
|
29033
|
-
const
|
|
29372
|
+
const usernameLabelFor = (account) => account.username.startsWith("@") ? account.username : `@${account.username}`;
|
|
29034
29373
|
const authHandshakeMessage = "Handshaking auth...";
|
|
29035
29374
|
const lifecycleDepsWithTextLogs = (deps) => ({
|
|
29036
29375
|
...deps,
|
|
@@ -29055,7 +29394,7 @@ const lifecycleDepsWithTextLogs = (deps) => ({
|
|
|
29055
29394
|
}
|
|
29056
29395
|
});
|
|
29057
29396
|
const runConnectOutput = async (deps, connect) => {
|
|
29058
|
-
writeText(deps, `${
|
|
29397
|
+
writeText(deps, `${usernameLabelFor((await connect(lifecycleDepsWithTextLogs(deps))).account)} connected!`);
|
|
29059
29398
|
};
|
|
29060
29399
|
const createConnectPlatformCommand = (platform, run) => defineCommand({
|
|
29061
29400
|
meta: commandMeta({
|
|
@@ -29263,11 +29602,11 @@ const createFeedbackCommand = (deps) => defineCommand({
|
|
|
29263
29602
|
//#region src/lib/account.ts
|
|
29264
29603
|
const LEADING_AT_PATTERN = /^@+/;
|
|
29265
29604
|
const normalizeAccountIdentifier = (value) => value.trim().replace(LEADING_AT_PATTERN, "").toLowerCase();
|
|
29266
|
-
const accountSelectorFor = (account) => account.
|
|
29605
|
+
const accountSelectorFor = (account) => account.username ? `@${account.username.replace(LEADING_AT_PATTERN, "")}` : `profile_id:${account.profileId}`;
|
|
29267
29606
|
const accountMatchesIdentifier = (account, identifier) => {
|
|
29268
29607
|
const trimmed = identifier.trim();
|
|
29269
29608
|
if (trimmed.startsWith("profile_id:")) return account.profileId.toLowerCase() === trimmed.slice(11).trim().toLowerCase();
|
|
29270
|
-
return trimmed.startsWith("@") && normalizeAccountIdentifier(account.
|
|
29609
|
+
return trimmed.startsWith("@") && normalizeAccountIdentifier(account.username) === normalizeAccountIdentifier(trimmed);
|
|
29271
29610
|
};
|
|
29272
29611
|
const resolveAccountId = ({ account }) => {
|
|
29273
29612
|
if (!account) return;
|
|
@@ -29363,12 +29702,12 @@ const createCLIDeps = () => {
|
|
|
29363
29702
|
if (account && !selected) throw new NotFoundError("account_not_found");
|
|
29364
29703
|
return {
|
|
29365
29704
|
platform,
|
|
29366
|
-
|
|
29705
|
+
username: selected?.username ?? (account?.startsWith("@") ? account.slice(1) : account ?? "default"),
|
|
29367
29706
|
selector: selected ? accountSelectorFor(selected) : account
|
|
29368
29707
|
};
|
|
29369
29708
|
};
|
|
29370
29709
|
const lookupXHandle = async (input, account) => {
|
|
29371
|
-
const
|
|
29710
|
+
const inputUsername = input.replace(/^@+/, "");
|
|
29372
29711
|
const selectedAccount = await resolveAccount({
|
|
29373
29712
|
account,
|
|
29374
29713
|
platform: "x"
|
|
@@ -29377,13 +29716,13 @@ const createCLIDeps = () => {
|
|
|
29377
29716
|
"Cache-Control": await cacheControlHeaderFromConfig(),
|
|
29378
29717
|
...selectedAccount.selector === void 0 ? {} : { "x-social-account-id": selectedAccount.selector }
|
|
29379
29718
|
};
|
|
29380
|
-
const data = await api.get(`x/users/by/username/${encodeURIComponent(
|
|
29381
|
-
if (!data || typeof data !== "object" || !("data" in data)) throw new Error(`Could not resolve X
|
|
29719
|
+
const data = await api.get(`x/users/by/username/${encodeURIComponent(inputUsername)}`, { headers }).json();
|
|
29720
|
+
if (!data || typeof data !== "object" || !("data" in data)) throw new Error(`Could not resolve X username ${input}.`);
|
|
29382
29721
|
const user = data.data;
|
|
29383
|
-
if (!user || typeof user !== "object" || !("id" in user)) throw new Error(`Could not resolve X
|
|
29722
|
+
if (!user || typeof user !== "object" || !("id" in user)) throw new Error(`Could not resolve X username ${input}.`);
|
|
29384
29723
|
return {
|
|
29385
29724
|
id: String(user.id),
|
|
29386
|
-
url: `https://x.com/${"username" in user && typeof user.username === "string" ? user.username :
|
|
29725
|
+
url: `https://x.com/${"username" in user && typeof user.username === "string" ? user.username : inputUsername}`
|
|
29387
29726
|
};
|
|
29388
29727
|
};
|
|
29389
29728
|
const lookupLinkedinProfile = async (input, account, selectedAccount) => {
|
|
@@ -29474,7 +29813,7 @@ const createCLIDeps = () => {
|
|
|
29474
29813
|
return {
|
|
29475
29814
|
account: {
|
|
29476
29815
|
platform: "x",
|
|
29477
|
-
|
|
29816
|
+
username: selected.username,
|
|
29478
29817
|
selector: accountSelectorFor(selected)
|
|
29479
29818
|
},
|
|
29480
29819
|
accountIdHeader: accountSelectorFor(selected),
|
|
@@ -29491,13 +29830,13 @@ const createCLIDeps = () => {
|
|
|
29491
29830
|
if (!selected) throw account ? new NotFoundError("linkedin_account_not_found") : /* @__PURE__ */ new Error("linkedin_account_not_connected");
|
|
29492
29831
|
const profile = await lookupLinkedinProfile("me", account, {
|
|
29493
29832
|
platform: "linkedin",
|
|
29494
|
-
|
|
29833
|
+
username: selected.username,
|
|
29495
29834
|
selector: accountSelectorFor(selected)
|
|
29496
29835
|
});
|
|
29497
29836
|
return {
|
|
29498
29837
|
account: {
|
|
29499
29838
|
platform: "linkedin",
|
|
29500
|
-
|
|
29839
|
+
username: selected.username,
|
|
29501
29840
|
selector: accountSelectorFor(selected)
|
|
29502
29841
|
},
|
|
29503
29842
|
accountIdHeader: accountSelectorFor(selected),
|
|
@@ -29704,7 +30043,6 @@ const compactSchemaForCommand = (command, path) => {
|
|
|
29704
30043
|
const label = commandLabel(command, path);
|
|
29705
30044
|
const { method, capability, mutates } = declaredMetadataFor(command, contractFor(command, label), label, { require: false });
|
|
29706
30045
|
return {
|
|
29707
|
-
schemaVersion: "1.0",
|
|
29708
30046
|
name: path.join(".") || commandName(command, "social"),
|
|
29709
30047
|
description: commandDescription(command),
|
|
29710
30048
|
...method ? { method } : {},
|
|
@@ -29724,7 +30062,6 @@ const compactLeafSchemaForCommand = (command, path) => {
|
|
|
29724
30062
|
if (capability === void 0 || mutates === void 0) throw new Error(`Missing CLI contract metadata for ${label}.`);
|
|
29725
30063
|
const pagination = metadataValueFor(contract.pagination ?? command.meta?.pagination, `${label}.pagination`);
|
|
29726
30064
|
return {
|
|
29727
|
-
schemaVersion: "1.0",
|
|
29728
30065
|
path: path.join(" "),
|
|
29729
30066
|
name: path.join(".") || commandName(command, "social"),
|
|
29730
30067
|
description: commandDescription(command),
|
|
@@ -29751,14 +30088,12 @@ const schemaForCommand = (command, path) => {
|
|
|
29751
30088
|
const pagination = metadataValueFor(contract.pagination ?? command.meta?.pagination, `${label}.pagination`);
|
|
29752
30089
|
if (pagination === void 0) throw new Error(`Missing CLI contract pagination for ${label}.`);
|
|
29753
30090
|
return {
|
|
29754
|
-
schemaVersion: "1.0",
|
|
29755
30091
|
name: path.join(".") || commandName(command, "social"),
|
|
29756
30092
|
description: commandDescription(command),
|
|
29757
30093
|
...method ? { method } : {},
|
|
29758
30094
|
...inputSourceFor(command),
|
|
29759
30095
|
args: metadataValueFor(contract.args, `${label}.args`) ?? positionalArgs,
|
|
29760
30096
|
flags: metadataValueFor(contract.flags, `${label}.flags`) ?? flags,
|
|
29761
|
-
params: metadataValueFor(contract.params, `${label}.params`) ?? args,
|
|
29762
30097
|
inputSchema: inputSchemaFor(command, contract, label, args),
|
|
29763
30098
|
outputSchema,
|
|
29764
30099
|
auth: authFor(contract.auth ?? command.meta?.auth, capability),
|
|
@@ -29776,9 +30111,11 @@ const schemaForCommand = (command, path) => {
|
|
|
29776
30111
|
};
|
|
29777
30112
|
const treeForCommand = (command, path = []) => {
|
|
29778
30113
|
const subCommands = childCommandsFor(command);
|
|
30114
|
+
const entries = Object.entries(subCommands).filter(([, subCommand]) => !isHiddenCommand(subCommand));
|
|
30115
|
+
const visibleSubCommands = Object.fromEntries(entries.map(([name, subCommand]) => [name, treeForCommand(subCommand, [...path, commandName(subCommand, name)])]));
|
|
29779
30116
|
return {
|
|
29780
30117
|
...compactSchemaForCommand(command, path),
|
|
29781
|
-
|
|
30118
|
+
...entries.length > 0 ? { subCommands: visibleSubCommands } : {}
|
|
29782
30119
|
};
|
|
29783
30120
|
};
|
|
29784
30121
|
const leafSchemasForCommand = (command, path = []) => {
|
|
@@ -29864,11 +30201,20 @@ const schemaCommand = defineCommand({
|
|
|
29864
30201
|
return;
|
|
29865
30202
|
}
|
|
29866
30203
|
if (parts.length === 0) {
|
|
29867
|
-
printData(
|
|
30204
|
+
printData({
|
|
30205
|
+
schemaVersion: "1.0",
|
|
30206
|
+
...treeForCommand(rootCommand)
|
|
30207
|
+
});
|
|
29868
30208
|
return;
|
|
29869
30209
|
}
|
|
29870
30210
|
const { command, path } = commandAtPath(rootCommand, parts);
|
|
29871
|
-
printData(command.subCommands && typeof command.run !== "function" ?
|
|
30211
|
+
printData(command.subCommands && typeof command.run !== "function" ? {
|
|
30212
|
+
schemaVersion: "1.0",
|
|
30213
|
+
...treeForCommand(command, path)
|
|
30214
|
+
} : {
|
|
30215
|
+
schemaVersion: "1.0",
|
|
30216
|
+
...schemaForCommand(command, path)
|
|
30217
|
+
});
|
|
29872
30218
|
}
|
|
29873
30219
|
});
|
|
29874
30220
|
//#endregion
|
|
@@ -29975,7 +30321,7 @@ const unknownSubCommandFor = async (command, rawArgs) => {
|
|
|
29975
30321
|
* `commandForUsage` walk but collects names.
|
|
29976
30322
|
*
|
|
29977
30323
|
* PRIVACY: only tokens that resolve to a real subcommand are appended. The first
|
|
29978
|
-
* arg that does not resolve (a positional value: tweet ID,
|
|
30324
|
+
* arg that does not resolve (a positional value: tweet ID, username, query text,
|
|
29979
30325
|
* flag value) breaks the walk and is NEVER captured. Never returns rawArgs or
|
|
29980
30326
|
* flag values.
|
|
29981
30327
|
*/
|