a tool for shared writing and social publishing
1import { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2import * as driz from "drizzle-orm";
3import type { Fact } from ".";
4import { replicache_clients } from "drizzle/schema";
5import type { Attribute, FilterAttributes } from "./attributes";
6import { ReadTransaction, WriteTransaction } from "replicache";
7import { PgTransaction } from "drizzle-orm/pg-core";
8
9export function FactWithIndexes(f: Fact<Attribute>) {
10 let indexes: {
11 eav: string;
12 vae?: string;
13 } = {
14 eav: `${f.entity}-${f.attribute}-${f.id}`,
15 };
16 if (
17 f.data.type === "reference" ||
18 f.data.type === "ordered-reference" ||
19 f.data.type === "spatial-reference"
20 )
21 indexes.vae = `${f.data.value}-${f.attribute}`;
22 return { ...f, indexes };
23}
24
25export async function getClientGroup(
26 db: PgTransaction<any, any, any>,
27 clientGroupID: string,
28): Promise<{ [clientID: string]: number }> {
29 let data = await db
30 .select()
31 .from(replicache_clients)
32 .where(driz.eq(replicache_clients.client_group, clientGroupID));
33 if (!data) return {};
34 return data.reduce(
35 (acc, clientRecord) => {
36 acc[clientRecord.client_id] = clientRecord.last_mutation;
37 return acc;
38 },
39 {} as { [clientID: string]: number },
40 );
41}
42
43export const scanIndex = (tx: ReadTransaction) => ({
44 async eav<A extends Attribute>(entity: string, attribute: A | "") {
45 return (
46 (
47 await tx
48 .scan<Fact<A>>({ indexName: "eav", prefix: `${entity}-${attribute}` })
49 // Hack rn because of the rich bluesky-post type
50 .toArray()
51 ).filter((f) => attribute === "" || f.attribute === attribute)
52 );
53 },
54 async vae<
55 A extends keyof FilterAttributes<{
56 type: "reference" | "ordered-reference" | "spatial-reference";
57 }>,
58 >(entity: string, attribute: A) {
59 return (
60 await tx
61 .scan<Fact<A>>({ indexName: "vae", prefix: `${entity}-${attribute}` })
62 .toArray()
63 ).filter((f) => f.attribute === attribute);
64 },
65});
66
67export const scanIndexLocal = (initialFacts: Fact<any>[]) => ({
68 eav<A extends Attribute>(entity: string, attribute: A) {
69 return initialFacts.filter(
70 (f) => f.entity === entity && f.attribute === attribute,
71 ) as SafeArray<Fact<A>>;
72 },
73});
74
75// Base utility type for making types compatible with ReadonlyJSONObject
76export type AsReadonlyJSONObject<T> = T & {
77 [key: string]: undefined;
78};
79
80// Recursive utility type for nested objects
81export type DeepAsReadonlyJSONValue<T> = T extends object
82 ? T extends Array<infer U>
83 ? ReadonlyArray<DeepAsReadonlyJSONValue<U>> // Handle arrays
84 : AsReadonlyJSONObject<{
85 [K in keyof T]: DeepAsReadonlyJSONValue<T[K]>;
86 }>
87 : T extends string | number | boolean | null
88 ? T // Primitive types that already match ReadonlyJSONValue
89 : never; // For other types that can't be converted