Skip to content

Commit 889275c

Browse files
authored
Merge branch 'playfulprogramming:main' into main
2 parents c8450a1 + e3674b7 commit 889275c

18 files changed

+629
-35
lines changed

Diff for: content/crutchcorn/posts/uuid-v6-8/UUIDv6.svg

+150
Loading

Diff for: content/crutchcorn/posts/uuid-v6-8/UUIDv7.svg

+151
Loading

Diff for: content/crutchcorn/posts/uuid-v6-8/UUIDv8.svg

+148
Loading

Diff for: content/crutchcorn/posts/uuid-v6-8/index.md

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
{
3+
title: "UUIDv6, UUIDv7, and UUIDv8; what are they?",
4+
description: "There's new UUIDs on the block! Let's explore what's new with UUIDv6, v7, and v8.",
5+
published: '2024-11-20T21:52:59.284Z',
6+
tags: ['computer science'],
7+
license: 'cc-by-4',
8+
collection: "Explaining UUIDs",
9+
order: 3
10+
}
11+
---
12+
13+
[In our first article explaining what UUIDs are](/posts/what-are-uuids), we explored a few different variants of UUID:
14+
15+
- [UUIDv1](/posts/what-are-uuids#UUIDv1)
16+
- A machine's network card information + a timestamp
17+
- [UUIDv2](/posts/what-are-uuids#UUIDv2)
18+
- [It's a long story.](/posts/what-happened-to-uuid-v2)
19+
- [UUIDv3](/posts/what-are-uuids#UUIDv3and5)
20+
- Encode a string using MD5
21+
- [UUIDv4](/posts/what-are-uuids#UUIDv4)
22+
- Random UUID with effectively zero chance of producing the same number twice
23+
- [UUIDv5](/posts/what-are-uuids#UUIDv3and5)
24+
- UUIDv3 but more secure (uses SHA-1)
25+
26+
Since that time, however, there [was an RFC that was accepted](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html) which added 3 new standard UUID types:
27+
28+
- [UUIDv6](#UUIDv6)
29+
- UUIDv1 but better for database indexes
30+
- [UUIDv7](#UUIDv7)
31+
- UUIDv1 without network card information and with a more standard timestamp
32+
- [UUIDv8](#UUIDv8)
33+
- An intentionally broad UUID spec for all non-standard UUIDs - make up your own UUIDs
34+
35+
Let's explore each of these UUID systems individually to understand them better.
36+
37+
38+
39+
# Create a Database Index with UUIDv6 {#UUIDv6}
40+
41+
If you've worked much with UUIDv1 UUIDs, you'll see a pattern in the new UUIDv6 mechanism. If we look at the UUIDv1 example:
42+
43+
![A UUID broken down into "Low Time", a dash, "Mid Time", a dash, "Version", "High Time", a dash, "Variant", "Clock", a dash, and finally a "MAC Address". An example UUIDv1 might be "4e2b4d4c-92e8-11ed-86a8-3fdb0085247e"](../what-are-uuids/UUIDv1.svg)
44+
45+
And compare it to the UUIDv6 example:
46+
47+
![A UUID broken down into "Time High", a dash, "Time Mid", a dash, "Version", "Time Low", a dash, "Variant", "Clock", a dash, and finally a "MAC Address". An example UUIDv6 might be "1ed92e84-e2b4-6d4c-86a8-3fdb0085247e"](./UUIDv6.svg)
48+
49+
We can see how the order of the UUIDv6 is "just" a rearrangement of the bytes presented in a UUIDv1.
50+
51+
Why is this?
52+
53+
Well, this reorder allows multiple UUIDs to be sorted without having to parse them more in-depth. This helps makes databases of UUIDv6s easier to index as opposed to UUIDv1s while retaining the same base information.
54+
55+
> [Read more about the specifics of UUIDv6's sorting improvements in their draft RFC.](https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#section-6.10)
56+
57+
**Why UUIDv6?**
58+
59+
This is useful if your projects are already using UUIDv1 and you need a drop-in replacement with better database indexing.
60+
61+
**Why not UUIDv6?**
62+
63+
[According to the RFC that introduces v6, v7, and v8](https://ietf-wg-uuidrev.github.io/rfc4122bis/draft-00/draft-ietf-uuidrev-rfc4122bis.html#name-uuid-version-6):
64+
65+
> Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
66+
67+
Let's explore why that is by taking a look at UUIDv7 next.
68+
69+
# More Standard Timestamps with UUIDv7 {#UUIDv7}
70+
71+
If we look at the byte order of UUIDv7 without any additional context, it might start to look familiar to UUIDv1 and v6:
72+
73+
![A UUID broken down into "Time High", a dash, "Time Mid", a dash, "Version", "Random", a dash, "Variant", "Random", a dash, and finally "Random". An example UUIDv7 might be "0185aa25-112e-7cc3-98c4-dc0c0c07398f"](./UUIDv7.svg)
74+
75+
However, if we note the `input data` section of this graph, the distinction becomes clear.
76+
77+
While UUIDv1, v6, and v7 all track time; v1 and v6 use "a count of 100- nanosecond intervals since 00:00:00.00, 15 October 1582" as their time-keeping mechanism.
78+
79+
Compare and contrast that to UUIDv7 utilizing the far more standard ["Epoch time", or, "Unix time"](https://en.wikipedia.org/wiki/Unix_time).
80+
81+
See, Epoch time is broadly used in nearly every computer system that needs to keep track of time. This makes v7 the superior choice over v6 and v1 for most usages, since encoding and decoding date logic becomes far more standardized.
82+
83+
# Make your own UUID rules with UUIDv8 {#UUIDv8}
84+
85+
Now comes the fun one of the bunch of new UUID formats: UUIDv8.
86+
87+
![A UUID broken down into "Custom", a dash, "Custom", a dash, "Version", "Custom, a dash, "Variant", "Custom", a dash, and finally "Custom". An example UUIDv8 might be "01234567-89ab-8cde-8f01-23456789abcd"](./UUIDv8.svg)
88+
89+
You'll notice that outside of the encoded variant and version that UUIDv8 allows you to encode any data you'd like.
90+
91+
This is mostly useful for future-proofing UUIDs for the foreseeable future. By allowing whatever format you want as a formalized UUID standard,
92+
vendors (like APIs, databases, and more) can make their own flavors of UUIDs that extend the v8 format for their own needs.

Diff for: package.json

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"@types/hast": "^3.0.4",
6464
"@types/html-escaper": "^3.0.0",
6565
"@types/json5": "^2.2.0",
66+
"@types/lru-cache": "^7.10.10",
6667
"@types/mdast": "^4.0.3",
6768
"@types/node": "^20.5.0",
6869
"@types/uuid": "^10.0.0",
@@ -96,6 +97,7 @@
9697
"junk": "^4.0.1",
9798
"lint-staged": "^15.2.7",
9899
"live-server": "^1.2.2",
100+
"lru-cache": "^11.0.2",
99101
"msw": "^2.3.5",
100102
"npm-run-all": "^4.1.5",
101103
"octokit": "^4.0.2",

Diff for: pnpm-lock.yaml

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: public/icons/website.svg

+3
Loading

Diff for: public/link.png

-1.91 KB
Binary file not shown.

Diff for: src/utils/fetch-page-html.ts

+24-11
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
import { Element, Root } from "hast";
22
import { fromHtml } from "hast-util-from-html";
33
import { find } from "unist-util-find";
4+
import { LRUCache } from "lru-cache";
5+
6+
export async function fetchAsBrowser(input: string | URL, init?: RequestInit) {
7+
const response = await fetch(input, {
8+
...init,
9+
headers: {
10+
"User-Agent":
11+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
12+
"Accept-Language": "en",
13+
...init?.headers,
14+
},
15+
});
16+
const isSuccess = `${response.status}`.startsWith("2");
17+
if (!isSuccess)
18+
throw new Error(`Request ${input} returned an error: ${response.status}`);
19+
return response;
20+
}
421

5-
const pageHtmlMap = new Map<string, Promise<Root | null>>();
22+
const pageHtmlCache = new LRUCache<string, Promise<Root | null>>({
23+
max: 50,
24+
});
625

726
export function fetchPageHtml(src: string): Promise<Root | null> {
8-
if (pageHtmlMap.has(src)) return pageHtmlMap.get(src)!;
27+
if (pageHtmlCache.has(src)) return pageHtmlCache.get(src)!;
928

1029
const promise = (async () => {
11-
const srcHTML = await fetch(src, {
12-
headers: {
13-
"User-Agent":
14-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
15-
"Accept-Language": "en",
16-
},
17-
})
18-
.then((r) => (r.status === 200 ? r.text() : undefined))
30+
const srcHTML = await fetchAsBrowser(src)
31+
.then(async (r) => await r.text())
1932
.catch(() => null);
2033

2134
// if fetch fails...
@@ -26,7 +39,7 @@ export function fetchPageHtml(src: string): Promise<Root | null> {
2639
return srcHast;
2740
})();
2841

29-
pageHtmlMap.set(src, promise);
42+
pageHtmlCache.set(src, promise);
3043
return promise;
3144
}
3245

Diff for: src/utils/markdown/astro-integration-copy-generated.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as path from "node:path";
44
import { fileURLToPath } from "node:url";
55

66
const copyFiles = async (srcDir: string, destDir: string) => {
7+
await fs.promises.mkdir(destDir, { recursive: true });
78
const readDir = await fs.promises.readdir(srcDir);
89

910
await Promise.all(

Diff for: src/utils/markdown/iframes/iframe-placeholder.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface IFramePlaceholderProps {
1313
propsToPreserve: string;
1414
pageTitle: string;
1515
pageIcon: string;
16+
pageIconFallback: string;
1617
}
1718

1819
/** @jsxImportSource hastscript */
@@ -35,6 +36,9 @@ export function IFramePlaceholder({
3536
decoding="async"
3637
data-nozoom="true"
3738
data-dont-round="true"
39+
{...{
40+
onerror: `this.src='${props.pageIconFallback}';this.onerror='';`,
41+
}}
3842
/>
3943
</div>
4044
<div class="embed__header__info">

Diff for: src/utils/markdown/iframes/rehype-transform.ts

+20-10
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { Plugin } from "unified";
55
import { visit } from "unist-util-visit";
66

77
import { EMBED_MIN_HEIGHT, EMBED_SIZE } from "../constants";
8-
import { fromHtml } from "hast-util-from-html";
98
import { find } from "unist-util-find";
109
import { getLargestManifestIcon } from "../../get-largest-manifest-icon";
1110
import { IFramePlaceholder } from "./iframe-placeholder";
@@ -15,36 +14,43 @@ import * as stream from "stream";
1514
import sharp from "sharp";
1615
import * as svgo from "svgo";
1716
import { fetchPageHtml, getPageTitle } from "utils/fetch-page-html";
17+
import { LRUCache } from "lru-cache";
1818

1919
interface RehypeUnicornIFrameClickToRunProps {
2020
srcReplacements?: Array<(val: string, root: VFile) => string>;
2121
}
2222

2323
// default icon, used if a frame's favicon cannot be resolved
24-
const defaultPageIcon = "/link.png";
24+
const defaultPageIcon = "/icons/website.svg";
2525

2626
function getIconPath(src: URL) {
2727
return `generated/${src.hostname}.favicon`;
2828
}
2929

3030
// Cache the fetch *promises* - so that only one request per manifest/icon is processed,
3131
// and multiple fetchPageInfo() calls can await the same icon
32-
const pageIconMap = new Map<string, Promise<string>>();
32+
const pageIconCache = new LRUCache<string, Promise<string>>({
33+
max: 50,
34+
});
35+
3336
function fetchPageIcon(src: URL, srcHast: Root): Promise<string> {
34-
if (pageIconMap.has(src.hostname)) return pageIconMap.get(src.hostname)!;
37+
if (pageIconCache.has(src.hostname)) return pageIconCache.get(src.hostname)!;
3538

3639
const promise = (async () => {
3740
const iconPath = getIconPath(src);
38-
const iconDir = await fs.promises
39-
.readdir(path.dirname(iconPath))
41+
const iconDir = path.dirname("public/" + iconPath);
42+
await fs.promises.mkdir(iconDir, { recursive: true });
43+
44+
const existingIconFiles = await fs.promises
45+
.readdir(iconDir)
4046
.catch(() => []);
4147

4248
// If an icon has already been downloaded for the origin (in a previous build)
43-
const existingIconFile = iconDir.find((file) =>
49+
const existingIconFile = existingIconFiles.find((file) =>
4450
file.startsWith(path.basename(iconPath)),
4551
);
4652
if (existingIconFile) {
47-
return path.join(path.dirname(iconPath), existingIconFile);
53+
return iconDir.replace(/^public/, "") + "/" + existingIconFile;
4854
}
4955

5056
// <link rel="manifest" href="/manifest.json">
@@ -132,10 +138,13 @@ function fetchPageIcon(src: URL, srcHast: Root): Promise<string> {
132138
return "/" + iconPath + iconExt;
133139
})()
134140
// if an error is thrown, or response is null, use the default page icon
135-
.catch(() => null)
141+
.catch((e) => {
142+
console.error("[rehypeIFrameClickToRun]", e);
143+
return null;
144+
})
136145
.then((p) => p || defaultPageIcon);
137146

138-
pageIconMap.set(src.hostname, promise);
147+
pageIconCache.set(src.hostname, promise);
139148
return promise;
140149
}
141150

@@ -206,6 +215,7 @@ export const rehypeUnicornIFrameClickToRun: Plugin<
206215
src: String(src),
207216
pageTitle: String(dataFrameTitle ?? "") || info.title || "",
208217
pageIcon: info.iconFile,
218+
pageIconFallback: defaultPageIcon,
209219
propsToPreserve: JSON.stringify(propsToPreserve),
210220
});
211221

Diff for: src/views/blog-post/blog-post-layout/blog-post-layout.module.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
max-width: var(--max-width_xl);
88
margin: 0 auto;
99

10-
&:where(:not([data-hide-left-sidebar])) {
10+
&:where(:not([data-hide-left-sidebar="true"])) {
1111
@include from($tabletLarge) {
1212
grid-template-columns: 25% 1fr;
1313
}
1414
}
1515

1616
// in tablet view, when the sidebar is hidden, the "content" column needs to be centered
17-
&[data-hide-left-sidebar] {
17+
&[data-hide-left-sidebar="true"] {
1818
@include until($desktopSmall) {
1919
max-width: var(--max-width_m);
2020
}

Diff for: src/views/blog-post/series/series-toc.astro

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const showMoreText = translate(
101101
>
102102
const showMore = document.querySelector("#toggleAllButton");
103103
const showHideChapters = Array.from(
104-
document.querySelectorAll("[data-dont-show-initially]"),
104+
document.querySelectorAll("[data-dont-show-initially='true']"),
105105
);
106106

107107
let expanded = false;

0 commit comments

Comments
 (0)