@volter/twin-linkedin 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 (39) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +39 -0
  3. package/dist/client/linkedin-mirror.bundle.js +323 -0
  4. package/dist/client/linkedin-mirror.d.ts +43 -0
  5. package/dist/client/linkedin-mirror.js +393 -0
  6. package/dist/src/cli.d.ts +2 -0
  7. package/dist/src/cli.js +30 -0
  8. package/dist/src/index.d.ts +10 -0
  9. package/dist/src/index.js +74 -0
  10. package/dist/src/linkedin-budget.d.ts +36 -0
  11. package/dist/src/linkedin-budget.js +87 -0
  12. package/dist/src/linkedin-capabilities.d.ts +4 -0
  13. package/dist/src/linkedin-capabilities.js +1027 -0
  14. package/dist/src/linkedin-conformance.d.ts +8 -0
  15. package/dist/src/linkedin-conformance.js +58 -0
  16. package/dist/src/linkedin-connector.d.ts +66 -0
  17. package/dist/src/linkedin-connector.js +326 -0
  18. package/dist/src/linkedin-errors.d.ts +21 -0
  19. package/dist/src/linkedin-errors.js +38 -0
  20. package/dist/src/linkedin-media.d.ts +125 -0
  21. package/dist/src/linkedin-media.js +331 -0
  22. package/dist/src/linkedin-mirror-ui.d.ts +59 -0
  23. package/dist/src/linkedin-mirror-ui.js +174 -0
  24. package/dist/src/linkedin-server.d.ts +10 -0
  25. package/dist/src/linkedin-server.js +154 -0
  26. package/dist/src/linkedin-twin.d.ts +23 -0
  27. package/dist/src/linkedin-twin.js +1220 -0
  28. package/package.json +58 -0
  29. package/src/cli.ts +28 -0
  30. package/src/index.ts +118 -0
  31. package/src/linkedin-budget.ts +108 -0
  32. package/src/linkedin-capabilities.ts +1057 -0
  33. package/src/linkedin-conformance.ts +73 -0
  34. package/src/linkedin-connector.ts +314 -0
  35. package/src/linkedin-errors.ts +58 -0
  36. package/src/linkedin-media.ts +363 -0
  37. package/src/linkedin-mirror-ui.ts +182 -0
  38. package/src/linkedin-server.ts +150 -0
  39. package/src/linkedin-twin.ts +1143 -0
@@ -0,0 +1,87 @@
1
+ // LinkedIn's CLIENT-SIDE RATE BUDGET — this pack's DECLARATION (the numbers) plus the thin typed
2
+ // bindings the perform and refresh adapters use. The MECHANISM — the durable token-keyed ledger, the
3
+ // rolling window, reserve-under-lock, the Retry-After / 429 cooldown, fail-CLOSED on a corrupt ledger
4
+ // — lives ONCE in the kernel (`@volter/world-core` → rateBudget.ts).
5
+ //
6
+ // ── HOW THE CEILING WAS CHOSEN ──────────────────────────────────────────────────────────────
7
+ // LinkedIn's published scheme, live-fetched for this build (learn.microsoft.com/linkedin/shared/
8
+ // api-guide/concepts/rate-limits, fetched 2026-09-26): "Rate limits specify the maximum number of
9
+ // API calls that can be made in a 24 hour period. These limits reset at midnight UTC every day",
10
+ // per application and per member, "Rate limited requests will receive a 429 response", and
11
+ // "Standard rate limits are not published in documentation" — each app reads its own in the
12
+ // Developer Portal. So there is NO first-party figure to transcribe, and the declaration stays at
13
+ // or under the kernel fallback: a one-hour window (the longest the kernel's rolling ledger keeps),
14
+ // 600 weighted units in it, and a burst of 30 units a minute — the fallback's own burst, so no
15
+ // VENDOR_BURST_ANCHOR is needed. A post or a delete (the org speaking in public) costs 5, the
16
+ // upload control calls 2, every read 1, anything unnamed 2.
17
+ //
18
+ // WHAT IS NOT CHARGED. The part uploads a video perform sends to the signed www.linkedin.com
19
+ // /dms-uploads URLs (presigned requests) are not API calls — the rate-limit page limits "API
20
+ // requests", and those URLs are the upload service, carrying their own signature — so the budgeted
21
+ // executor passes them through uncharged. Their number is bounded by construction instead: a
22
+ // perform refuses a video over the feed's 500 MB (at most 125 parts of 4 MB) before initializing.
23
+ import { declareRateBudget, rateBudgetPath, rateBudgetWeight, RateBudget, } from '@volter/world-core';
24
+ const VENDOR = 'linkedin';
25
+ /** Rolling window, in ms — the longest the kernel's ledger keeps (LinkedIn's own window is a day). */
26
+ export const LINKEDIN_BUDGET_WINDOW_MS = 60 * 60_000;
27
+ /** Weighted units allowed inside one window: conservative, not transcribed (no figure is published). */
28
+ export const LINKEDIN_BUDGET_CEILING = 600;
29
+ /** Weighted units in any 60 s: the kernel fallback's 30, so no burst anchor is required. */
30
+ export const LINKEDIN_BUDGET_BURST_CEILING = 30;
31
+ /** Seconds. A Retry-After above this means the member or app is out for the day — fail loudly. */
32
+ export const LINKEDIN_BUDGET_MAX_RETRY_AFTER_S = 3600;
33
+ export const LINKEDIN_CALL_WEIGHTS = {
34
+ /** A post or its deletion — the organization speaking, in public. */
35
+ write: 5,
36
+ /** initializeUpload / finalizeUpload. */
37
+ upload: 2,
38
+ /** A read: a video's status, a finder page, an organization, the ACLs. */
39
+ read: 1,
40
+ /** Everything else on the vendor: priced above a read. */
41
+ other: 2,
42
+ };
43
+ /** THE PACK'S DECLARATION — pure data, the only LinkedIn-specific thing in the whole budget. */
44
+ export const LINKEDIN_RATE_BUDGET = {
45
+ windowMs: LINKEDIN_BUDGET_WINDOW_MS,
46
+ ceiling: LINKEDIN_BUDGET_CEILING,
47
+ burstCeiling: LINKEDIN_BUDGET_BURST_CEILING,
48
+ defaultWeight: LINKEDIN_CALL_WEIGHTS.other,
49
+ maxRetryAfterSeconds: LINKEDIN_BUDGET_MAX_RETRY_AFTER_S,
50
+ rules: [
51
+ { match: '^POST /rest/posts$', weight: LINKEDIN_CALL_WEIGHTS.write },
52
+ { match: '^DELETE /rest/posts/', weight: LINKEDIN_CALL_WEIGHTS.write },
53
+ { match: '^POST /rest/(videos|images)$', weight: LINKEDIN_CALL_WEIGHTS.upload },
54
+ { match: '^GET /rest/', weight: LINKEDIN_CALL_WEIGHTS.read },
55
+ ],
56
+ reason: 'LinkedIn limits API calls per application and per member over a 24-hour period reset at midnight UTC, '
57
+ + 'answers an exceeded limit with 429, and states "Standard rate limits are not published in documentation" '
58
+ + '(learn.microsoft.com/linkedin/shared/api-guide/concepts/rate-limits, fetched 2026-09-26). With no published '
59
+ + 'figure, this declaration stays at or under the kernel fallback rather than dressing a guess as a vendor fact: '
60
+ + '600 weighted units per rolling hour (the longest window the ledger keeps) and a 30-unit burst per minute (the '
61
+ + "fallback's own). A post or a delete costs 5, an upload initialize/finalize 2, a read 1, anything unnamed 2. The "
62
+ + 'signed /dms-uploads part PUTs are not API requests and are not charged; a perform bounds them to 125 parts.',
63
+ };
64
+ declareRateBudget(VENDOR, LINKEDIN_RATE_BUDGET);
65
+ /** Price one call, keyed `"<METHOD> <path>"` with the query split off and the method upper-cased. */
66
+ export function linkedinCallWeight(method, path) {
67
+ const at = path.indexOf('?');
68
+ const query = {};
69
+ if (at !== -1)
70
+ for (const [k, v] of new URLSearchParams(path.slice(at + 1)))
71
+ query[k] = v;
72
+ const raw = at === -1 ? path : path.slice(0, at);
73
+ const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
74
+ return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
75
+ }
76
+ /** Where this vendor's ledger lives: token-keyed and cwd-independent by default. */
77
+ export function linkedinBudgetPath(opts = {}) {
78
+ const o = typeof opts === 'string' ? { root: opts } : opts;
79
+ return rateBudgetPath({ ...o, vendor: VENDOR });
80
+ }
81
+ /** This vendor's budget — the kernel guard bound to this declaration. */
82
+ export class LinkedinBudget extends RateBudget {
83
+ constructor(opts = {}) {
84
+ super({ ...opts, vendor: VENDOR });
85
+ }
86
+ }
87
+ export { RateBudgetError as LinkedinBudgetError } from '@volter/world-core';
@@ -0,0 +1,4 @@
1
+ import { type CapabilityReport, type CapabilitySpec } from '@volter/world-tooling';
2
+ export declare const LINKEDIN_AREAS: readonly ["versioning", "auth", "organizations", "posts", "images", "videos", "documents", "social", "statistics", "errors", "twin_control", "connector", "rate_limit", "mirror"];
3
+ export declare const LINKEDIN_CAPABILITIES: CapabilitySpec[];
4
+ export declare function linkedinCapabilities(): Promise<CapabilityReport>;