@monoflake/sdk 0.0.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 (44) hide show
  1. package/LICENSE +22 -0
  2. package/dist/artifacts/src/anchors.d.ts +34 -0
  3. package/dist/artifacts/src/anchors.js +64 -0
  4. package/dist/artifacts/src/api.d.ts +14 -0
  5. package/dist/artifacts/src/api.js +20 -0
  6. package/dist/artifacts/src/batch.d.ts +105 -0
  7. package/dist/artifacts/src/batch.js +81 -0
  8. package/dist/artifacts/src/engagement.d.ts +61 -0
  9. package/dist/artifacts/src/engagement.js +67 -0
  10. package/dist/artifacts/src/feed.d.ts +42 -0
  11. package/dist/artifacts/src/feed.js +89 -0
  12. package/dist/artifacts/src/index.d.ts +4224 -0
  13. package/dist/artifacts/src/index.js +219 -0
  14. package/dist/artifacts/src/picture.d.ts +116 -0
  15. package/dist/artifacts/src/picture.js +161 -0
  16. package/dist/artifacts/src/resource.d.ts +1391 -0
  17. package/dist/artifacts/src/resource.js +477 -0
  18. package/dist/artifacts/src/schema.d.ts +5 -0
  19. package/dist/artifacts/src/schema.js +18 -0
  20. package/dist/artifacts/src/types.d.ts +396 -0
  21. package/dist/artifacts/src/types.js +0 -0
  22. package/dist/cache/src/index.d.ts +67 -0
  23. package/dist/cache/src/index.js +58 -0
  24. package/dist/imgsrc/src/index.d.ts +16 -0
  25. package/dist/imgsrc/src/index.js +89 -0
  26. package/dist/limits/src/bucket.d.ts +28 -0
  27. package/dist/limits/src/bucket.js +23 -0
  28. package/dist/limits/src/index.d.ts +29 -0
  29. package/dist/limits/src/index.js +62 -0
  30. package/dist/limits/src/key.d.ts +37 -0
  31. package/dist/limits/src/key.js +64 -0
  32. package/dist/robots/src/index.d.ts +98 -0
  33. package/dist/robots/src/index.js +196 -0
  34. package/dist/security/src/agents.d.ts +10 -0
  35. package/dist/security/src/agents.js +95 -0
  36. package/dist/security/src/index.d.ts +12 -0
  37. package/dist/security/src/index.js +37 -0
  38. package/dist/src/index.d.ts +218 -0
  39. package/dist/src/index.js +219 -0
  40. package/dist/store/src/index.d.ts +92 -0
  41. package/dist/store/src/index.js +264 -0
  42. package/dist/symlink/src/index.d.ts +24 -0
  43. package/dist/symlink/src/index.js +89 -0
  44. package/package.json +85 -0
@@ -0,0 +1,396 @@
1
+ import { LocaleCode } from "@canmi/me/locales";
2
+ //#region artifacts/src/types.d.ts
3
+ /** Which of the three things a text track is. WebVTT's own vocabulary, not ours. */
4
+ export type CaptionKind = 'captions' | 'subtitles' | 'descriptions';
5
+ export type VideoRung = {
6
+ src: string;
7
+ type: string;
8
+ width: number;
9
+ height: number;
10
+ };
11
+ /** One published text track, described by what the record says it is rather than by a label. */
12
+ export type VideoTrack = {
13
+ src: string;
14
+ kind: CaptionKind;
15
+ language: string;
16
+ };
17
+ export type ArticleMeta = {
18
+ title: string;
19
+ subtitle: string;
20
+ description: string;
21
+ /** The source view's public BCP-47 language tag. */
22
+ lang: string;
23
+ created: string;
24
+ /** When the article went public, which is the date a reader is shown. The author's to edit. */
25
+ published: string;
26
+ lastmod: string;
27
+ /**
28
+ * Written but not published. Absent means published, so an article says nothing to stay
29
+ * ordinary and one word to be held back.
30
+ *
31
+ * A production build drops these before compiling them, so a draft has no page, no sitemap
32
+ * entry, no feed item and no search record. Every other build keeps them, which is what makes
33
+ * a draft previewable. See web's spec/drafts.md.
34
+ */
35
+ draft?: boolean;
36
+ };
37
+ export type Block = {
38
+ type: 'prose';
39
+ html: string;
40
+ } | {
41
+ type: 'heading';
42
+ depth: number;
43
+ slug: string;
44
+ text: string;
45
+ /** Note numbers written into this heading. Absent from `text`, and so from the ToC. */
46
+ notes?: number[];
47
+ } | {
48
+ type: 'code';
49
+ lang: string;
50
+ label?: string;
51
+ title?: string;
52
+ collapsible?: boolean;
53
+ default_expanded?: boolean;
54
+ html: string;
55
+ code: string;
56
+ } | {
57
+ type: 'mermaid';
58
+ source: string;
59
+ ratio?: number;
60
+ description?: string;
61
+ } | {
62
+ type: 'quadrant';
63
+ title: string;
64
+ description?: string;
65
+ /**
66
+ * What the figure reads as, from `local diagram`. Absent until one has been run.
67
+ *
68
+ * Not `description`, which is one line the author wrote to sit under the title. This
69
+ * is the whole figure said in prose, and it is translated, which is what the
70
+ * assembled-from-labels fallback never was.
71
+ */
72
+ reading?: string;
73
+ axes: Record<QuadrantDirection, string>;
74
+ items: QuadrantItem[];
75
+ } | {
76
+ type: 'svgCanvas';
77
+ svg: string;
78
+ title: string;
79
+ /** What the drawing says, from `local diagram`. Absent until one has been run. */
80
+ description?: string;
81
+ } | {
82
+ type: 'tokei';
83
+ source: string;
84
+ title: string;
85
+ view: TokeiView;
86
+ } | {
87
+ type: 'cargo';
88
+ crate: CrateRecord;
89
+ view: CargoView;
90
+ } | {
91
+ type: 'twitter';
92
+ tweet: TweetRecord;
93
+ } | {
94
+ type: 'github';
95
+ repo: RepoRecord;
96
+ git_ref?: string;
97
+ title?: string;
98
+ align: CardAlign;
99
+ } | {
100
+ type: 'linkcard';
101
+ src: string;
102
+ url: string;
103
+ title: string;
104
+ /**
105
+ * The site's mark, by rid, and never an address.
106
+ *
107
+ * An icon belongs to somebody else's site and is redrawn on their schedule, so what
108
+ * this resource currently holds is a fact about the corpus at the moment somebody
109
+ * asks -- not something a compiled article may carry. The whole key is absent for a
110
+ * site nothing has collected a mark for, and the role is spelled out so a page reads
111
+ * one name on every block: see `namedResources`, and spec/architecture/resource.md.
112
+ */
113
+ resources?: {
114
+ icon: string;
115
+ };
116
+ /** Which of that resource's files to draw. Selected at render time, not here. */
117
+ tone?: 'light' | 'dark';
118
+ width?: number;
119
+ height?: number;
120
+ preview?: string;
121
+ srcset?: string;
122
+ /** The cover's crop, defaulted like `::image`'s. See the `image` variant below. */
123
+ crop?: string;
124
+ /** `object-position` for that crop. Absent means centred. */
125
+ align?: string;
126
+ /** What the cover shows. Offered as the link's description, never as its name. */
127
+ description?: string;
128
+ } | ({
129
+ type: 'article';
130
+ path: string;
131
+ } & ArticleReference) | {
132
+ type: 'footnotes';
133
+ notes: ArticleNote[];
134
+ } | {
135
+ type: 'placeholder';
136
+ kind: string;
137
+ meta: Record<string, string>;
138
+ /**
139
+ * An embed named but not yet fetched, as opposed to a stub the author wrote.
140
+ *
141
+ * The two had one shape and meant different things, which a consumer could not tell
142
+ * apart: `::cargo` whose record `local embed` has not filled in still says which crate
143
+ * the article meant, while `::placeholder` is the author asking for a gap. The feed is
144
+ * where it showed -- one of them belongs in a document a reader subscribes to and the
145
+ * other does not.
146
+ */
147
+ pending?: true;
148
+ } | {
149
+ type: 'image';
150
+ /**
151
+ * The picture, by rid, and nothing derived from what it currently holds.
152
+ *
153
+ * Its ladder, its placeholder and its intrinsic box all move when the picture is
154
+ * encoded again, on nobody's schedule but the corpus's, so they are resolved per
155
+ * render and this names what they are resolved from. What is left is what the
156
+ * article itself decided. See spec/architecture/resource.md, "A rid is resolved
157
+ * three times".
158
+ */
159
+ resources: {
160
+ picture: string;
161
+ };
162
+ /**
163
+ * What the picture is called here, which is the article's to say.
164
+ *
165
+ * Baked, unlike the ladder above, because it changes on **this** repository's
166
+ * schedule: `local alt` writes it into a file beside the article, per locale, and a
167
+ * view carries the one it is written in rather than nine it is not.
168
+ */
169
+ alt: string;
170
+ /**
171
+ * A ratio to crop the displayed image to, as `16 / 9` ready for CSS.
172
+ *
173
+ * Cropping is presentation, so it is done by the browser with `object-fit` rather
174
+ * than by producing another object. A stored variant per ratio and alignment would
175
+ * multiply the bucket and, worse, make a content id mean "this image as shown here"
176
+ * instead of "this image".
177
+ */
178
+ crop?: string;
179
+ /** `object-position` for that crop. Absent means centred. */
180
+ align?: string;
181
+ } | {
182
+ type: 'video';
183
+ /**
184
+ * The reference the article wrote, extension and all.
185
+ *
186
+ * Kept beside the resolved fields for the reason an image keeps it: an article can name
187
+ * a clip nothing has imported yet, and that should cost a fallback rather than a build.
188
+ */
189
+ src: string;
190
+ /** Every published rung, smallest first. Absent for a reference nothing resolved. */
191
+ rungs?: VideoRung[];
192
+ /** The original's dimensions, which reserve the box before anything is fetched. */
193
+ width?: number;
194
+ height?: number;
195
+ /** The poster image asset's own rendition, which is what `<video poster>` names. */
196
+ poster?: string;
197
+ /**
198
+ * What every sample of this clip is multiplied by, so two clips play at one level.
199
+ *
200
+ * Computed in the build from the loudness and true peak services/apps/local measured. `1` for
201
+ * a clip nothing has measured, which plays as it always did. See `assets.ts`.
202
+ */
203
+ gain?: number;
204
+ /** The poster's placeholder, painted under it while it arrives. */
205
+ preview?: string;
206
+ captions?: VideoTrack[];
207
+ /** What the clip shows. Offered as a description, never as the element's name. */
208
+ description?: string;
209
+ /** Where the clip came from, which is what the unsupported-format notice links to. */
210
+ source?: {
211
+ url: string;
212
+ label?: string;
213
+ };
214
+ };
215
+ /**
216
+ * What an `::article` card shows of the article it points at, in the view's own locale.
217
+ *
218
+ * The card reads these off the target rather than off the directive, so retitling an article
219
+ * retitles every card naming it and each translated view names it in its own language.
220
+ */
221
+ export type ArticleReference = {
222
+ title: string;
223
+ subtitle: string;
224
+ /**
225
+ * The date the card draws, which is when the target went public and not when it was written.
226
+ *
227
+ * Named for what it holds rather than kept as `created` beside a second key: this is what a
228
+ * card shows, not what an article is, and a card shows one date. A record carries every fact
229
+ * it knows -- see `ArticleMeta` -- and a projection carries the one that is drawn.
230
+ */
231
+ published: string;
232
+ /** What a phone card shows instead, where the row clips. Falls back to the full form for a
233
+ * view `local` has not written one for. See web's spec/i18n/prose.md. */
234
+ short_title: string;
235
+ short_subtitle: string;
236
+ };
237
+ /**
238
+ * One `:fn` note: the words it explains, the number it was given, and what it says.
239
+ *
240
+ * The phrase is carried so the collected note can name what it is about instead of asking a
241
+ * reader to hold the sentence they left in their head while they read it.
242
+ *
243
+ * Two notes with the same words are two notes. There is no label to say otherwise, and a reader
244
+ * who meets the same explanation twice was told it twice on purpose.
245
+ */
246
+ export type ArticleNote = {
247
+ number: number;
248
+ phrase: string;
249
+ text: string;
250
+ };
251
+ export type TocEntry = {
252
+ slug: string;
253
+ text: string;
254
+ depth: number;
255
+ };
256
+ export type CardAlign = 'left' | 'center' | 'right';
257
+ export type CargoView = 'treemap' | 'table';
258
+ export type TokeiView = 'treemap' | 'bar' | 'table';
259
+ export type QuadrantDirection = 'top' | 'right' | 'bottom' | 'left';
260
+ export type QuadrantPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
261
+ export type QuadrantItem = {
262
+ at: QuadrantPosition;
263
+ title: string;
264
+ note?: string;
265
+ };
266
+ export type Compiled = {
267
+ meta: ArticleMeta;
268
+ toc: TocEntry[];
269
+ blocks: Block[];
270
+ markdown: string;
271
+ text: string;
272
+ };
273
+ export type ArticleView = Pick<Compiled, 'meta' | 'toc' | 'blocks' | 'text'> & {
274
+ code: LocaleCode;
275
+ /**
276
+ * How long the article is in this view, in words.
277
+ *
278
+ * Body prose and what is inside it. Not `text`, which is every readable string on the page --
279
+ * a picture's description, a linkcard's title, a diagram's caption, an embedded post -- and
280
+ * which this used to be measured from, in characters, making a 9,102-character article read
281
+ * as 14,870. Those are components; a reader asking how long an article is does not mean them.
282
+ */
283
+ words: number;
284
+ language_tag: string;
285
+ canonical: string;
286
+ /** False when this locale is showing the complete source article as a safe fallback. */
287
+ translation_available: boolean;
288
+ /** The title and subtitle a phone card shows instead of `meta`'s, where the row clips. Falls
289
+ * back to the full form for a view `local` has not written one for. See web's spec/i18n/prose.md.
290
+ * */
291
+ short: {
292
+ title: string;
293
+ subtitle: string;
294
+ };
295
+ /**
296
+ * The title the article page shows on a phone: `meta.title` where it fits the column, and
297
+ * `short.title` where it does not. Decided here rather than in the browser -- the answer is a
298
+ * property of the string, so it cannot change between renders, and computing it at runtime
299
+ * would mean the first frame guessing. See web's spec/styling/phone.md.
300
+ */
301
+ phone_title: string;
302
+ /**
303
+ * What the article is about, withholding what it concludes. Written by `local summary` into a
304
+ * sidecar rather than into the article, so it is absent until that has been run.
305
+ */
306
+ summary?: ArticleSummary;
307
+ };
308
+ export type ArticleSummary = {
309
+ text: string;
310
+ provider: string;
311
+ };
312
+ export type CrateDep = {
313
+ name: string;
314
+ version: string;
315
+ kind: string;
316
+ optional: boolean;
317
+ target: string | null;
318
+ features: string[];
319
+ size: number | null;
320
+ depth: number;
321
+ };
322
+ export type CrateRecord = {
323
+ name: string;
324
+ version: string;
325
+ rust_version: string | null;
326
+ features: Record<string, string[]>;
327
+ deps: CrateDep[];
328
+ total_dep_size: number;
329
+ };
330
+ export type RepoRecord = {
331
+ full_name: string;
332
+ description: string | null;
333
+ language: string | null;
334
+ stars: number;
335
+ forks: number;
336
+ open_issues: number;
337
+ license: string | null;
338
+ pushed_at: string | null;
339
+ };
340
+ export type TweetRecord = {
341
+ id: string;
342
+ author: string;
343
+ text: string;
344
+ created: string;
345
+ likes: number;
346
+ reposts: number;
347
+ replies: number;
348
+ };
349
+ export type Alternate = {
350
+ code: Exclude<LocaleCode, 'mw'> | 'x-default';
351
+ language_tag: string;
352
+ href: string;
353
+ };
354
+ export type Article = Compiled & {
355
+ /** The identity: the last segment of the path, unique across the corpus. See build/slugs.ts. */
356
+ slug: string;
357
+ /** The address: the directory it currently sits in, plus that identity. */
358
+ path: string;
359
+ url: string;
360
+ views: Record<LocaleCode, ArticleView>;
361
+ canonical_urls: string[];
362
+ alternates: Alternate[];
363
+ };
364
+ export type InlineSegment = {
365
+ type: 'html';
366
+ html: string;
367
+ } | {
368
+ type: 'link';
369
+ icon?: 'twitter' | 'github' | 'email';
370
+ href: string;
371
+ label: string;
372
+ new_tab: boolean;
373
+ /** `wide` / `narrow` from the directive, as the classes that act on them. A link is the
374
+ * one run that cannot be wrapped in `:t` -- nested, it stops being a link -- so it
375
+ * carries its own width the way a `:t` run carries one. See web's spec/styling/phone.md. */
376
+ width?: string;
377
+ };
378
+ export type PageBlock = {
379
+ type: 'p';
380
+ segments: InlineSegment[];
381
+ } | {
382
+ type: 'html';
383
+ html: string;
384
+ };
385
+ export type CompiledPage = {
386
+ meta: Record<string, string>;
387
+ blocks: PageBlock[];
388
+ body: string;
389
+ };
390
+ export type PageView = Pick<CompiledPage, 'meta' | 'blocks'>;
391
+ export type Page = {
392
+ path: string;
393
+ markdown: string;
394
+ views: Record<LocaleCode, PageView>;
395
+ };
396
+ //#endregion
File without changes
@@ -0,0 +1,67 @@
1
+ //#region cache/src/index.d.ts
2
+ /**
3
+ * How long an answer from this site may be kept.
4
+ *
5
+ * Publication has one delay rather than a different one per resource, so the number saying
6
+ * what that delay is belongs in one place rather than in each worker that stamps it -- three
7
+ * of them wrote it out, and two that drifted apart would be two answers to one question. See
8
+ * spec/architecture/delivery.md and spec/architecture/artifacts.md, "The key says what may
9
+ * cache it".
10
+ *
11
+ * Only the values are here. Which answer earns which lifetime stays with the worker that knows,
12
+ * because the three do three different jobs, and one policy deciding for all of them would be
13
+ * one place holding three unrelated decisions.
14
+ */
15
+ /**
16
+ * Five minutes, in seconds. How far behind the current publication an answer may be.
17
+ *
18
+ * None of it while developing: a publication delay is a promise made to readers, and on a laptop
19
+ * it is only the distance between a rebuild and seeing it, which was minutes of waiting for a
20
+ * change nobody else was going to read.
21
+ */
22
+ export declare const PUBLICATION_DELAY: number;
23
+ /**
24
+ * Three hours, in seconds, for a caller that decides its answer is worth serving stale.
25
+ *
26
+ * A root that old names objects that are all still there and still immutable, so what it renders
27
+ * is a coherent older page rather than a broken one. Whether an answer may be served during an
28
+ * outage is a fact about what produced it, so that decision stays where it is made and only the
29
+ * window is shared.
30
+ */
31
+ export declare const WHILE_UNREACHABLE = 10800;
32
+ /**
33
+ * What an answer about what is published right now earns.
34
+ *
35
+ * Its key names rather than identifies, so the bytes behind it change when somebody publishes
36
+ * and it is held for exactly the delay publication has. Refusals included: a 404 about the
37
+ * corpus and a 400 about an address are both true until the next publication, and giving either
38
+ * a number of its own would be a second publication delay.
39
+ */
40
+ export declare const PUBLISHED: string;
41
+ /**
42
+ * What an answer keeps when its address names something rather than identifying it.
43
+ *
44
+ * Between the other two: what stands behind the name can change, but not on this site's
45
+ * publication clock -- a permanent shortcut whose target moves, somebody else's file fetched
46
+ * live. An hour is long enough to be worth an edge entry and short enough that a change lands
47
+ * the same day.
48
+ */
49
+ export declare const NAMED = "public, max-age=3600";
50
+ /**
51
+ * What a key whose bytes cannot change earns.
52
+ *
53
+ * Content addressing is the usual reason -- a hashed name cannot denote different bytes, so a
54
+ * cached copy is correct forever and needs no invalidation. It is not the only one: a Latin font
55
+ * subset keeps the year on a written promise that re-subsetting renames it, which is why this is
56
+ * named for the property rather than for the hash that normally carries it.
57
+ */
58
+ export declare const UNCHANGING = "public, max-age=31536000, immutable";
59
+ /**
60
+ * A name resolved to the object it stands for right now: the publication delay, and served stale
61
+ * through an outage, since the target is content-addressed and a stale one is still bytes.
62
+ *
63
+ * The alias layer stamps it on its redirect, and a page that follows that redirect for a browser
64
+ * stamps it on its own. See spec/architecture/delivery.md.
65
+ */
66
+ export declare const RESOLVED: string;
67
+ //#endregion
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Five minutes, in seconds. How far behind the current publication an answer may be.
3
+ *
4
+ * None of it while developing: a publication delay is a promise made to readers, and on a laptop
5
+ * it is only the distance between a rebuild and seeing it, which was minutes of waiting for a
6
+ * change nobody else was going to read.
7
+ */
8
+ const PUBLICATION_DELAY = import.meta.env?.MODE === "development" ? 0 : 300;
9
+ /** One year, in seconds: the longest a browser honours, and what `immutable` already implies. */
10
+ const UNCHANGING_LIFE = 31536e3;
11
+ /** One hour, in seconds. The middle number, for an address that names rather than identifies. */
12
+ const NAMED_LIFE = 3600;
13
+ /**
14
+ * Three hours, in seconds, for a caller that decides its answer is worth serving stale.
15
+ *
16
+ * A root that old names objects that are all still there and still immutable, so what it renders
17
+ * is a coherent older page rather than a broken one. Whether an answer may be served during an
18
+ * outage is a fact about what produced it, so that decision stays where it is made and only the
19
+ * window is shared.
20
+ */
21
+ const WHILE_UNREACHABLE = 10800;
22
+ /**
23
+ * What an answer about what is published right now earns.
24
+ *
25
+ * Its key names rather than identifies, so the bytes behind it change when somebody publishes
26
+ * and it is held for exactly the delay publication has. Refusals included: a 404 about the
27
+ * corpus and a 400 about an address are both true until the next publication, and giving either
28
+ * a number of its own would be a second publication delay.
29
+ */
30
+ const PUBLISHED = `public, max-age=${PUBLICATION_DELAY}`;
31
+ /**
32
+ * What an answer keeps when its address names something rather than identifying it.
33
+ *
34
+ * Between the other two: what stands behind the name can change, but not on this site's
35
+ * publication clock -- a permanent shortcut whose target moves, somebody else's file fetched
36
+ * live. An hour is long enough to be worth an edge entry and short enough that a change lands
37
+ * the same day.
38
+ */
39
+ const NAMED = `public, max-age=${NAMED_LIFE}`;
40
+ /**
41
+ * What a key whose bytes cannot change earns.
42
+ *
43
+ * Content addressing is the usual reason -- a hashed name cannot denote different bytes, so a
44
+ * cached copy is correct forever and needs no invalidation. It is not the only one: a Latin font
45
+ * subset keeps the year on a written promise that re-subsetting renames it, which is why this is
46
+ * named for the property rather than for the hash that normally carries it.
47
+ */
48
+ const UNCHANGING = `public, max-age=${UNCHANGING_LIFE}, immutable`;
49
+ /**
50
+ * A name resolved to the object it stands for right now: the publication delay, and served stale
51
+ * through an outage, since the target is content-addressed and a stale one is still bytes.
52
+ *
53
+ * The alias layer stamps it on its redirect, and a page that follows that redirect for a browser
54
+ * stamps it on its own. See spec/architecture/delivery.md.
55
+ */
56
+ const RESOLVED = `${PUBLISHED}, stale-if-error=${WHILE_UNREACHABLE}`;
57
+ //#endregion
58
+ export { NAMED, PUBLICATION_DELAY, PUBLISHED, RESOLVED, UNCHANGING, WHILE_UNREACHABLE };
@@ -0,0 +1,16 @@
1
+ //#region imgsrc/src/index.d.ts
2
+ export type Options = {
3
+ cdnUrl?: string;
4
+ /**
5
+ * Selects the development CDN. Defaults to false, so server and browser always agree.
6
+ *
7
+ * Do not infer this from `globalThis.location`: that global is absent during SSR and
8
+ * present in the browser, so the same image would resolve to the production CDN on the
9
+ * server and the local one on the client -- a silent hydration mismatch that only appears
10
+ * in development. The caller knows the answer, so the caller passes it. In SvelteKit that
11
+ * is `dev` from `$app/environment`.
12
+ */
13
+ isDev?: boolean;
14
+ };
15
+ export declare function imgsrc(input: string, opts?: Options): string;
16
+ //#endregion
@@ -0,0 +1,89 @@
1
+ import { URLS, pickUrls } from "../../src/index.js";
2
+ //#region imgsrc/src/index.ts
3
+ const GITHUB_AVATAR_SCHEME = "github:avatar:";
4
+ const GITHUB_SCHEME = "github:";
5
+ function imgsrc(input, opts = {}) {
6
+ const cdnUrl = opts.cdnUrl ?? pickUrls(opts.isDev ?? false).cdn;
7
+ if (input.startsWith("data:")) return input;
8
+ if (input.startsWith(GITHUB_AVATAR_SCHEME)) return resolveGithubAvatar(input, cdnUrl);
9
+ if (input.startsWith(GITHUB_SCHEME)) return resolveGithubScheme(input);
10
+ if (hasWebScheme(input)) return rewriteIfKnown(input, cdnUrl);
11
+ return `${cdnUrl}/object/${input}`;
12
+ }
13
+ function hostOf(url) {
14
+ return new URL(url).hostname;
15
+ }
16
+ function hasWebScheme(input) {
17
+ const schemeEnd = input.indexOf(":");
18
+ if (schemeEnd < 0) return false;
19
+ const scheme = input.slice(0, schemeEnd).toLowerCase();
20
+ return scheme === "http" || scheme === "https";
21
+ }
22
+ function resolveGithubAvatar(input, cdnUrl) {
23
+ const parsed = parseAvatarRef(input.slice(14));
24
+ if (!parsed) return input;
25
+ const query = parsed.size ? `?width=${parsed.size}` : "";
26
+ return `${cdnUrl}/proxy/github/avatar/${parsed.idOrName}${query}`;
27
+ }
28
+ function parseAvatarRef(rest) {
29
+ const lastAt = rest.lastIndexOf("@");
30
+ if (lastAt < 0) return rest ? {
31
+ idOrName: rest,
32
+ size: null
33
+ } : null;
34
+ if (lastAt === 0) {
35
+ const name = rest.slice(1);
36
+ return name ? {
37
+ idOrName: name,
38
+ size: null
39
+ } : null;
40
+ }
41
+ const before = rest.slice(0, lastAt);
42
+ const after = rest.slice(lastAt + 1);
43
+ if (!/^\d+$/.test(after)) return null;
44
+ const idOrName = before.startsWith("@") ? before.slice(1) : before;
45
+ return idOrName ? {
46
+ idOrName,
47
+ size: after
48
+ } : null;
49
+ }
50
+ function resolveGithubScheme(input) {
51
+ const rest = input.slice(7);
52
+ const atIdx = rest.lastIndexOf("@");
53
+ const pathPart = atIdx >= 0 ? rest.slice(0, atIdx) : rest;
54
+ const ref = atIdx >= 0 ? rest.slice(atIdx + 1) : null;
55
+ const [owner, repo, ...pathBits] = pathPart.split("/");
56
+ if (!owner || !repo || pathBits.length === 0) return input;
57
+ return toGithubCdn(owner, repo, ref, pathBits);
58
+ }
59
+ function rewriteIfKnown(input, cdnUrl) {
60
+ let url;
61
+ try {
62
+ url = new URL(input);
63
+ } catch {
64
+ return input;
65
+ }
66
+ if (url.hostname === hostOf(URLS.external.github.avatars)) {
67
+ const match = url.pathname.match(/^\/u\/(\d+)/);
68
+ if (match) {
69
+ const size = url.searchParams.get("s");
70
+ const query = size ? `?width=${size}` : "";
71
+ return `${cdnUrl}/proxy/github/avatar/${match[1]}${query}`;
72
+ }
73
+ }
74
+ if (url.hostname === hostOf(URLS.external.github.raw)) {
75
+ const [owner, repo, ref, ...pathBits] = url.pathname.split("/").filter(Boolean);
76
+ if (owner && repo && ref && pathBits.length > 0) return toGithubCdn(owner, repo, ref, pathBits);
77
+ }
78
+ if (url.hostname === hostOf(URLS.external.github.web)) {
79
+ const [owner, repo, kind, ref, ...pathBits] = url.pathname.split("/").filter(Boolean);
80
+ if (owner && repo && ref && pathBits.length > 0 && (kind === "raw" || kind === "blob")) return toGithubCdn(owner, repo, ref, pathBits);
81
+ }
82
+ return input;
83
+ }
84
+ function toGithubCdn(owner, repo, ref, pathBits) {
85
+ const refSuffix = ref ? `@${ref}` : "";
86
+ return `${URLS.external.github.cdn}/${owner}/${repo}${refSuffix}/${pathBits.join("/")}`;
87
+ }
88
+ //#endregion
89
+ export { imgsrc };
@@ -0,0 +1,28 @@
1
+ //#region limits/src/bucket.d.ts
2
+ /**
3
+ * A limit as a bucket: `burst` calls at once, and room coming back at `count` calls in `seconds`.
4
+ * Counted with GCRA, which admits exactly what a token bucket would while keeping one number per
5
+ * key -- the moment the next call is due. Pure, so every deployment of `quota` runs the same
6
+ * arithmetic. See spec/architecture/quota.md, "A limit is a bucket".
7
+ */
8
+ /** How fast room comes back, and how much of it there is. */
9
+ export interface Rate {
10
+ readonly count: number;
11
+ readonly seconds: number;
12
+ /** How many calls may come together; `count` when absent. */
13
+ readonly burst?: number;
14
+ }
15
+ export interface Taken {
16
+ readonly allowed: boolean;
17
+ /** Whole seconds until another call would be allowed; 0 when this one was. */
18
+ readonly retryAfter: number;
19
+ }
20
+ /**
21
+ * One call at `now`, in milliseconds, against a bucket whose next call was `due`, or never asked
22
+ * when undefined: whether it passes, and the `due` to keep after it. A refused call keeps the old
23
+ * `due`, so refusals spend nothing.
24
+ */
25
+ export declare function take(due: number | undefined, rate: Rate, now: number): Taken & {
26
+ readonly due: number | undefined;
27
+ };
28
+ //#endregion
@@ -0,0 +1,23 @@
1
+ //#region limits/src/bucket.ts
2
+ /**
3
+ * One call at `now`, in milliseconds, against a bucket whose next call was `due`, or never asked
4
+ * when undefined: whether it passes, and the `due` to keep after it. A refused call keeps the old
5
+ * `due`, so refusals spend nothing.
6
+ */
7
+ function take(due, rate, now) {
8
+ const interval = rate.seconds * 1e3 / rate.count;
9
+ const room = (rate.burst ?? rate.count) * interval;
10
+ const next = Math.max(due ?? now, now) + interval;
11
+ if (next - now <= room) return {
12
+ allowed: true,
13
+ retryAfter: 0,
14
+ due: next
15
+ };
16
+ return {
17
+ allowed: false,
18
+ retryAfter: Math.max(1, Math.ceil((next - now - room) / 1e3)),
19
+ due
20
+ };
21
+ }
22
+ //#endregion
23
+ export { take };