@volter/twin-tiktok 0.1.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.
Files changed (76) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +310 -0
  3. package/client/tiktok-consent.tsx +154 -0
  4. package/client/tiktok-mirror.css +137 -0
  5. package/client/tiktok-mirror.tsx +492 -0
  6. package/dist/client/tiktok-consent.bundle.js +18 -0
  7. package/dist/client/tiktok-consent.d.ts +47 -0
  8. package/dist/client/tiktok-consent.js +20 -0
  9. package/dist/client/tiktok-consent.tsx +154 -0
  10. package/dist/client/tiktok-mirror.bundle.js +487 -0
  11. package/dist/client/tiktok-mirror.css +137 -0
  12. package/dist/client/tiktok-mirror.d.ts +42 -0
  13. package/dist/client/tiktok-mirror.js +315 -0
  14. package/dist/client/tiktok-mirror.tsx +492 -0
  15. package/dist/src/cli.d.ts +2 -0
  16. package/dist/src/cli.js +44 -0
  17. package/dist/src/index.d.ts +22 -0
  18. package/dist/src/index.js +167 -0
  19. package/dist/src/tiktok-blobs.d.ts +66 -0
  20. package/dist/src/tiktok-blobs.js +161 -0
  21. package/dist/src/tiktok-budget.d.ts +56 -0
  22. package/dist/src/tiktok-budget.js +136 -0
  23. package/dist/src/tiktok-capabilities.d.ts +7 -0
  24. package/dist/src/tiktok-capabilities.js +1855 -0
  25. package/dist/src/tiktok-conformance.d.ts +11 -0
  26. package/dist/src/tiktok-conformance.js +498 -0
  27. package/dist/src/tiktok-connector.d.ts +158 -0
  28. package/dist/src/tiktok-connector.js +600 -0
  29. package/dist/src/tiktok-consent-ui.d.ts +19 -0
  30. package/dist/src/tiktok-consent-ui.js +127 -0
  31. package/dist/src/tiktok-errors.d.ts +78 -0
  32. package/dist/src/tiktok-errors.js +175 -0
  33. package/dist/src/tiktok-ids.d.ts +16 -0
  34. package/dist/src/tiktok-ids.js +48 -0
  35. package/dist/src/tiktok-media.d.ts +7 -0
  36. package/dist/src/tiktok-media.js +86 -0
  37. package/dist/src/tiktok-mirror-ui.d.ts +49 -0
  38. package/dist/src/tiktok-mirror-ui.js +159 -0
  39. package/dist/src/tiktok-pkce.d.ts +25 -0
  40. package/dist/src/tiktok-pkce.js +56 -0
  41. package/dist/src/tiktok-posting.d.ts +100 -0
  42. package/dist/src/tiktok-posting.js +599 -0
  43. package/dist/src/tiktok-sample-mp4.d.ts +10 -0
  44. package/dist/src/tiktok-sample-mp4.js +55 -0
  45. package/dist/src/tiktok-scopes.d.ts +29 -0
  46. package/dist/src/tiktok-scopes.js +106 -0
  47. package/dist/src/tiktok-server.d.ts +28 -0
  48. package/dist/src/tiktok-server.js +89 -0
  49. package/dist/src/tiktok-store.d.ts +164 -0
  50. package/dist/src/tiktok-store.js +451 -0
  51. package/dist/src/tiktok-twin.d.ts +70 -0
  52. package/dist/src/tiktok-twin.js +1197 -0
  53. package/dist/src/tiktok-user.d.ts +28 -0
  54. package/dist/src/tiktok-user.js +174 -0
  55. package/package.json +74 -0
  56. package/src/cli.ts +43 -0
  57. package/src/index.ts +270 -0
  58. package/src/tiktok-blobs.ts +217 -0
  59. package/src/tiktok-budget.ts +163 -0
  60. package/src/tiktok-capabilities.ts +2022 -0
  61. package/src/tiktok-conformance.ts +526 -0
  62. package/src/tiktok-connector.ts +637 -0
  63. package/src/tiktok-consent-ui.ts +146 -0
  64. package/src/tiktok-errors.ts +197 -0
  65. package/src/tiktok-ids.ts +51 -0
  66. package/src/tiktok-journey.uitest.ts +305 -0
  67. package/src/tiktok-media.ts +89 -0
  68. package/src/tiktok-mirror-ui.ts +167 -0
  69. package/src/tiktok-pkce.ts +61 -0
  70. package/src/tiktok-posting.ts +617 -0
  71. package/src/tiktok-sample-mp4.ts +54 -0
  72. package/src/tiktok-scopes.ts +122 -0
  73. package/src/tiktok-server.ts +100 -0
  74. package/src/tiktok-store.ts +543 -0
  75. package/src/tiktok-twin.ts +1361 -0
  76. package/src/tiktok-user.ts +137 -0
@@ -0,0 +1,158 @@
1
+ import type { PerformContext, PushOutcome, RemoteExecute, SyncResource, TwinAction } from '@volter/world-core';
2
+ import { TikTokBudget, type TikTokBudgetOptions } from './tiktok-budget.js';
3
+ /** Every modelled user field a pull requests by default — narrow it for a narrower token. */
4
+ export declare const PULL_USER_FIELDS: readonly string[];
5
+ /** Every modelled video field a pull requests when `videos` is enabled. */
6
+ export declare const PULL_VIDEO_FIELDS: readonly string[];
7
+ /**
8
+ * The injected real-TikTok boundary. `execute` issues ONE request and returns the parsed JSON
9
+ * body. A real client (a raw fetch wrapper) is structurally assignable; tests pass a fake.
10
+ */
11
+ export type TikTokExecute = (method: 'GET' | 'POST', path: string, init?: {
12
+ headers?: Record<string, string>;
13
+ body?: string;
14
+ }) => Promise<Record<string, any>>;
15
+ export type LiveTikTokOptions = {
16
+ /** Injected `fetch`, so a test can COUNT the requests the guard did or did not let through. */
17
+ fetchImpl?: typeof fetch;
18
+ /** An existing budget to share across executors. Omit and one is constructed. Cannot be null. */
19
+ budget?: TikTokBudget;
20
+ /** Construction options for the default budget (ledger path, clock). Cannot loosen it. */
21
+ budgetOptions?: TikTokBudgetOptions;
22
+ };
23
+ /**
24
+ * A live executor against the real TikTok open API, holding the operator's OWN user access token.
25
+ *
26
+ * THIS IS THE ONE PLACE this pack issues a live TikTok request, and therefore the one place the
27
+ * rate budget has to be enforced. EVERY call is guarded: the budget is charged BEFORE the request
28
+ * goes out (`checkBudget`, which THROWS instead of returning when the ceiling or a cooldown says
29
+ * stop) and the response is fed back (`recordCall`) so a 429 / `Retry-After` becomes a PERSISTED
30
+ * cooldown that makes every later call fail fast WITHOUT touching TikTok. There is deliberately no
31
+ * option to disable the guard and no value of `budget` that yields an unguarded client
32
+ * (`assertBudgetGuardIntact`).
33
+ */
34
+ export declare function liveTikTokExecute(accessToken: string, opts?: LiveTikTokOptions): TikTokExecute;
35
+ /**
36
+ * Map a real `GET /v2/user/info/` response -> the account (persona) SyncResource.
37
+ *
38
+ * NOTHING IS INVENTED: a field the response omits records NOTHING rather than a placeholder —
39
+ * including username and display_name, because a scope-narrowed token legitimately omits them and
40
+ * a `?? null` there would fold null over a previously pulled persona's real values.
41
+ *
42
+ * The subject id is `union_id`, the vendor's own cross-app user key — NOT `open_id`, which is
43
+ * per-app and would give one human as many twin accounts as they have authorized apps.
44
+ */
45
+ export declare function mapUserInfoAccount(body: Record<string, any>): SyncResource;
46
+ /** Map one real Video object -> the video SyncResource, owned by the pulled account. */
47
+ export declare function mapVideo(video: Record<string, any>, ownerUnionId: string): SyncResource;
48
+ export type PullOptions = {
49
+ root?: string;
50
+ occurredAt?: string;
51
+ /** Narrow the `user.fields` a pull asks for — required when the token holds fewer scopes than
52
+ * the three `user.info.*` ones (an unauthorized field is a refusal, not an empty value). */
53
+ userFields?: readonly string[];
54
+ /** Also pull the account's videos. OFF by default: the Display API video reads need the separate
55
+ * `video.list` scope, and a refused read must never fold an empty list over observed state. */
56
+ videos?: boolean;
57
+ /** How many videos to ask for in one page (TikTok's own maximum is 20). */
58
+ videoCount?: number;
59
+ };
60
+ /** PULL the operator's own identity (and optionally their videos) into the observed log. */
61
+ export declare function pullTikTok(execute: TikTokExecute, opts?: PullOptions): Promise<number>;
62
+ /**
63
+ * D7 consumer-facing pull entry point: pull everything readable from the real TikTok surface and
64
+ * fold it into the twin in ONE observation, returning the standard `{ observed, deltasAppended }`.
65
+ * Idempotent — a re-pull of identical state appends nothing.
66
+ */
67
+ export declare function syncTikTokFromReal(execute: TikTokExecute, opts?: PullOptions): Promise<{
68
+ observed: number;
69
+ deltasAppended: number;
70
+ }>;
71
+ /**
72
+ * PUSH without a kernel executor — reported, never faked. TikTok has no API that creates a
73
+ * developer app, seeds a user, grants a scope, or edits a published video: those are developer-portal
74
+ * and in-app actions. A World's new posts DO have a door (the Content Posting API), and they cross
75
+ * through the kernel's deploy (`performTikTokAction`), which holds the sealed credential this
76
+ * function never does. This never confirms an action and never pretends to have pushed one; it
77
+ * returns the pending count so a caller can see exactly how much local state is waiting.
78
+ */
79
+ export declare function pushPendingTikTokActions(root?: string): {
80
+ pushed: 0;
81
+ unpushable: number;
82
+ };
83
+ /** A `TikTokExecute` over the kernel's executor. At a REAL boundary the kernel sets the sealed
84
+ * credential over these headers (executor.ts); at the twin's own wire any credential is one. */
85
+ export declare function tiktokExecuteOver(execute: RemoteExecute): TikTokExecute;
86
+ /**
87
+ * The refresh adapter (protocol 2): `GET /v2/user/info/` is a real read of the real account, so the
88
+ * operator's own identity comes back from TikTok itself. It is also nearly the only thing this
89
+ * vendor lets a client read about its own identity surface — apps, grants and scopes are portal
90
+ * state.
91
+ *
92
+ * ── WHY THIS ONE DOES NOT THROW, AND THE PULL ABOVE STILL DOES ──────────────────────────────────
93
+ * Two entry points, two callers, two correct behaviours — and the difference is deliberate rather
94
+ * than a softened refusal:
95
+ *
96
+ * • `pullTikTok` / `syncTikTokFromReal` are the OPERATOR's pull over an executor they built. The
97
+ * credential is theirs and it is meant to work, so any refusal is a fault they must see: those
98
+ * THROW on every refusal shape, and `tiktok.connector.refused_pull_throws` holds that.
99
+ * • THIS adapter is driven by the kernel against whatever credential the root holds — including
100
+ * none. A reply that discloses no account is then not "the vendor failed", it is "this wire
101
+ * has nothing to tell us", and the honest answer is ZERO OBSERVATIONS. It is the googleoauth
102
+ * remote adapter's own shape (`collectGoogleOAuthIdentity` pushes a resource only when the
103
+ * reply carries one), transcribed.
104
+ *
105
+ * WHAT IS NOT WEAKENED, because this is the line the doctrine actually draws: nothing empty is ever
106
+ * FOLDED. `observeResources` is not called at all on this path, so no prior observation is
107
+ * overwritten, no subject is tombstoned, and a previously pulled persona survives byte for byte —
108
+ * which is the whole content of "a refused pull is not an empty account". The count comes back 0,
109
+ * and a caller that reads the count sees exactly that nothing was observed.
110
+ * `tiktok.connector.remote_refresh_folds_nothing_when_unreadable` pins both halves at once.
111
+ */
112
+ export declare function syncTikTokFromRemote(execute: RemoteExecute, opts?: {
113
+ root?: string;
114
+ origin?: string;
115
+ occurredAt?: string;
116
+ }): Promise<{
117
+ observed: number;
118
+ deltasAppended: number;
119
+ }>;
120
+ /** How often the perform reads status/fetch while TikTok processes (see THE BOUND above). */
121
+ export declare const STATUS_POLL_MS = 3000;
122
+ /** How long, in total, a perform waits on TikTok's processing before it fails the entry retryably. */
123
+ export declare const MAX_PUBLISH_WAIT_MS = 120000;
124
+ /** The chunk the perform sends: TikTok's 5 MB minimum (read as MiB), so a 4 GB video is 800 chunks. */
125
+ export declare const PERFORM_CHUNK_BYTES: number;
126
+ /** TikTok answered "later" (429, a 5xx) — the entry stays deployable and the next deploy tries again. */
127
+ export declare class TikTokRetryableError extends Error {
128
+ readonly retryable = true;
129
+ constructor(message: string);
130
+ }
131
+ /** TikTok has the video but has not finished processing it inside the bound: retry later. */
132
+ export declare class TikTokStillProcessingError extends TikTokRetryableError {
133
+ readonly publishId: string;
134
+ constructor(publishId: string, waitedMs: number, reads: number);
135
+ }
136
+ /**
137
+ * The kernel executor, charged to this pack's TikTokBudget: the check RESERVES before the call and
138
+ * throws (TikTokBudgetError) instead of calling when the ceiling or a cooldown says stop; the answer
139
+ * settles the reservation and arms a cooldown on 429 / Retry-After. The vendor has ANSWERED by then:
140
+ * if recording throws (a back-off past the cap, persisted first), the answer still goes back and the
141
+ * next call meets the cooldown — dropping it would record a post TikTok accepted as failed.
142
+ */
143
+ export declare function budgetedTikTokExecute(execute: RemoteExecute, budget?: TikTokBudget): RemoteExecute;
144
+ /** The chunk plan TikTok's rules require for `size` bytes at the perform's chunk size. */
145
+ export declare function performChunkPlan(size: number): {
146
+ chunkSize: number;
147
+ totalChunkCount: number;
148
+ };
149
+ /**
150
+ * The perform adapter (protocol 2).
151
+ *
152
+ * A World's post crosses (`video.publish`, `video.inbox_upload`: see PERFORM above). Everything else a
153
+ * World writes here has no upstream home, and saying so is the point: TikTok publishes NO API that
154
+ * creates a developer app, seeds a user, grants a scope or issues a token — those are developer-portal
155
+ * and in-app settings actions a person takes in a browser — and an upload whose file failed TikTok's
156
+ * checks here (`publish.fail`) has nothing to send.
157
+ */
158
+ export declare function performTikTokAction(execute: RemoteExecute, action: TwinAction, ctx: PerformContext, budget?: TikTokBudget): Promise<PushOutcome>;