a tool for shared writing and social publishing

use uuidv7 for facts and entities instead of v4

Should probably use them everywhere eventually but for now this works!

+31 -19
+2 -1
app/page.tsx
··· 9 9 import postgres from "postgres"; 10 10 import { Doc } from "./[doc_id]/Doc"; 11 11 import { UpdateURL } from "components/UpdateURL"; 12 + import { v7 } from "uuid"; 12 13 const client = postgres(process.env.DB_URL as string); 13 14 const db = drizzle(client); 14 15 ··· 26 27 let [entity] = await tx 27 28 .insert(entities) 28 29 // And add it to that permission set 29 - .values({ set: entity_set.id }) 30 + .values({ set: entity_set.id, id: v7() }) 30 31 .returning(); 31 32 //Create a new permission token 32 33 let [permissionToken] = await tx
+4 -3
components/BlockOptions.tsx
··· 14 14 import { Separator } from "./Layout"; 15 15 import { addLinkBlock } from "src/utils/addLinkBlock"; 16 16 import { useEntitySetContext } from "./EntitySetProvider"; 17 + import { v7 } from "uuid"; 17 18 18 19 type Props = { 19 20 parent: string; ··· 53 54 await rep.mutate.retractFact({ factID: props.factID }); 54 55 let entity = props.entityID; 55 56 if (!entity) { 56 - entity = crypto.randomUUID(); 57 + entity = v7(); 57 58 await rep?.mutate.addBlock({ 58 59 parent: props.parent, 59 60 permission_set: entity_set.set, ··· 85 86 onMouseDown={(e) => e.preventDefault()} 86 87 onClick={async () => { 87 88 if (!props.entityID) { 88 - let entity = crypto.randomUUID(); 89 + let entity = v7(); 89 90 90 91 await rep?.mutate.addBlock({ 91 92 permission_set: entity_set.set, ··· 125 126 let submit = async () => { 126 127 let entity = props.entityID; 127 128 if (!entity) { 128 - entity = crypto.randomUUID(); 129 + entity = v7(); 129 130 130 131 await rep?.mutate.addBlock({ 131 132 permission_set: entity_set.set,
+6 -4
components/Blocks.tsx
··· 15 15 import { setEditorState, useEditorStates } from "src/state/useEditorState"; 16 16 import { useEntitySetContext } from "./EntitySetProvider"; 17 17 import { scanIndex } from "src/replicache/utils"; 18 + import { v7 } from "uuid"; 18 19 19 20 export type Block = { 20 21 parent: string; ··· 38 39 !lastBlock || 39 40 (lastBlock.type !== "text" && lastBlock.type !== "heading") 40 41 ) { 41 - let newEntityID = crypto.randomUUID(); 42 + let newEntityID = v7(); 42 43 await rep.rep?.mutate.addBlock({ 43 44 parent: props.entityID, 44 45 permission_set: entity_set.set, ··· 75 76 <div 76 77 className="shrink-0 h-[50vh]" 77 78 onClick={() => { 78 - let newEntityID = crypto.randomUUID(); 79 + let newEntityID = v7(); 79 80 80 81 if (lastBlock && lastBlock.type === "text") { 81 82 focusBlock({ ...lastBlock, type: "text" }, { type: "end" }); ··· 117 118 <div 118 119 className="h-6 hover:cursor-text italic text-tertiary" 119 120 onMouseDown={async () => { 120 - let newEntityID = crypto.randomUUID(); 121 + let newEntityID = v7(); 121 122 await rep?.mutate.addBlock({ 122 123 parent: props.entityID, 123 124 type: "text", ··· 204 205 if (block) focusBlock(block, { type: "end" }); 205 206 } 206 207 if (e.key === "Enter") { 207 - let newEntityID = crypto.randomUUID(); 208 + let newEntityID = v7(); 208 209 r.mutate.addBlock({ 209 210 permission_set: entity_set.set, 210 211 newEntityID, ··· 239 240 <div 240 241 data-entityid={props.entityID} 241 242 onMouseDown={(e) => { 243 + useSelectingMouse.setState({ start: props.entityID }); 242 244 if (e.shiftKey) { 243 245 e.preventDefault(); 244 246 useUIState.getState().addBlockToSelection(props);
+2 -1
components/TextBlock/keymap.ts
··· 11 11 import { useUIState } from "src/useUIState"; 12 12 import { setEditorState, useEditorStates } from "src/state/useEditorState"; 13 13 import { focusCard } from "components/Cards"; 14 + import { v7 } from "uuid"; 14 15 15 16 export const TextBlockKeymap = ( 16 17 propsRef: MutableRefObject<BlockProps & { entity_set: { set: string } }>, ··· 250 251 let newContent = tr.doc.slice(state.selection.anchor); 251 252 tr.delete(state.selection.anchor, state.doc.content.size); 252 253 dispatch?.(tr); 253 - let newEntityID = crypto.randomUUID(); 254 + let newEntityID = v7(); 254 255 let position = generateKeyBetween( 255 256 propsRef.current.position, 256 257 propsRef.current.nextPosition,
+4 -3
components/TextBlock/useHandlePaste.ts
··· 8 8 import { addImage } from "src/utils/addImage"; 9 9 import { BlockProps, focusBlock } from "components/Blocks"; 10 10 import { useEntitySetContext } from "components/EntitySetProvider"; 11 + import { v7 } from "uuid"; 11 12 12 13 export const useHandlePaste = ( 13 14 entityID: string, ··· 67 68 if (index === 0 && type === propsRef.current.type) 68 69 entityID = propsRef.current.entityID; 69 70 else { 70 - entityID = crypto.randomUUID(); 71 + entityID = v7(); 71 72 currentPosition = generateKeyBetween( 72 73 currentPosition, 73 74 propsRef.current.nextPosition, ··· 121 122 .filter((f) => !!f); 122 123 let currentPosition = propsRef.current.position; 123 124 for (let p of paragraphs) { 124 - let newEntityID = crypto.randomUUID(); 125 + let newEntityID = v7(); 125 126 currentPosition = generateKeyBetween( 126 127 currentPosition, 127 128 propsRef.current.nextPosition, ··· 161 162 }); 162 163 if (factID) rep.mutate.retractFact({ factID: factID }); 163 164 } else { 164 - entity = crypto.randomUUID(); 165 + entity = v7(); 165 166 rep.mutate.addBlock({ 166 167 permission_set: entity_set.set, 167 168 type: "image",
+2 -2
drizzle/schema.ts
··· 21 21 }); 22 22 23 23 export const entities = pgTable("entities", { 24 - id: uuid("id").defaultRandom().primaryKey().notNull(), 24 + id: uuid("id").primaryKey().notNull(), 25 25 created_at: timestamp("created_at", { withTimezone: true, mode: 'string' }).defaultNow().notNull(), 26 26 set: uuid("set").notNull().references(() => entity_sets.id, { onDelete: "cascade", onUpdate: "cascade" } ), 27 27 }); ··· 37 37 }); 38 38 39 39 export const facts = pgTable("facts", { 40 - id: uuid("id").defaultRandom().primaryKey().notNull(), 40 + id: uuid("id").primaryKey().notNull(), 41 41 entity: uuid("entity").notNull().references(() => entities.id, { onDelete: "cascade", onUpdate: "restrict" } ), 42 42 attribute: text("attribute").notNull(), 43 43 data: jsonb("data").notNull(),
+2 -1
src/replicache/clientMutationContext.ts
··· 6 6 import { Fact } from "."; 7 7 import { MutationContext } from "./mutations"; 8 8 import { supabaseBrowserClient } from "supabase/browserClient"; 9 + import { v7 } from "uuid"; 9 10 10 11 export function clientMutationContext(tx: WriteTransaction) { 11 12 let ctx: MutationContext = { ··· 32 33 async assertFact(f) { 33 34 let attribute = Attributes[f.attribute as keyof typeof Attributes]; 34 35 if (!attribute) return; 35 - let id = f.id || crypto.randomUUID(); 36 + let id = f.id || v7(); 36 37 let data = { ...f.data }; 37 38 if (attribute.cardinality === "one") { 38 39 let existingFact = await tx
+2 -1
src/replicache/serverMutationContext.ts
··· 9 9 import { DeepReadonly } from "replicache"; 10 10 import { createClient } from "@supabase/supabase-js"; 11 11 import { Database } from "supabase/database.types"; 12 + import { v7 } from "uuid"; 12 13 export function serverMutationContext( 13 14 tx: PgTransaction<any, any, any>, 14 15 token_rights: PermissionToken["permission_token_rights"], ··· 77 78 async assertFact(f) { 78 79 let attribute = Attributes[f.attribute as keyof typeof Attributes]; 79 80 if (!attribute) return; 80 - let id = f.id || crypto.randomUUID(); 81 + let id = f.id || v7(); 81 82 let data = { ...f.data }; 82 83 let [permission_set] = await tx 83 84 .select({ entity_set: entities.set })
+2 -1
src/utils/addImage.ts
··· 3 3 import { supabaseBrowserClient } from "supabase/browserClient"; 4 4 import { FilterAttributes } from "src/replicache/attributes"; 5 5 import { rgbaToDataURL, rgbaToThumbHash, thumbHashToDataURL } from "thumbhash"; 6 + import { v7 } from "uuid"; 6 7 7 8 export async function addImage( 8 9 file: File, ··· 14 15 ) { 15 16 let client = supabaseBrowserClient(); 16 17 let cache = await caches.open("minilink-user-assets"); 17 - let fileID = crypto.randomUUID(); 18 + let fileID = v7(); 18 19 let url = client.storage.from("minilink-user-assets").getPublicUrl(fileID) 19 20 .data.publicUrl; 20 21 let dimensions = await getImageDimensions(file);
+2 -2
supabase/database.types.ts
··· 42 42 } 43 43 Insert: { 44 44 created_at?: string 45 - id?: string 45 + id: string 46 46 set: string 47 47 } 48 48 Update: { ··· 90 90 created_at?: string 91 91 data: Json 92 92 entity: string 93 - id?: string 93 + id: string 94 94 updated_at?: string | null 95 95 version?: number 96 96 }
+3
supabase/migrations/20240707221949_make_ids_mandatory_for_entities_and_facts.sql
··· 1 + alter table "public"."entities" alter column "id" drop default; 2 + 3 + alter table "public"."facts" alter column "id" drop default;