src
modules
atproto
cards
domain
infrastructure
tests
shared
···
7
7
type Options as XrpcOptions,
8
8
type AuthVerifier,
9
9
type StreamAuthVerifier,
10
10
-
} from '@atproto/xrpc-server'
11
11
-
import { schemas } from './lexicons.js'
10
10
+
} from '@atproto/xrpc-server';
11
11
+
import { schemas } from './lexicons.js';
12
12
13
13
export function createServer(options?: XrpcOptions): Server {
14
14
-
return new Server(options)
14
14
+
return new Server(options);
15
15
}
16
16
17
17
export class Server {
18
18
-
xrpc: XrpcServer
19
19
-
network: NetworkNS
20
20
-
com: ComNS
18
18
+
xrpc: XrpcServer;
19
19
+
network: NetworkNS;
20
20
+
com: ComNS;
21
21
22
22
constructor(options?: XrpcOptions) {
23
23
-
this.xrpc = createXrpcServer(schemas, options)
24
24
-
this.network = new NetworkNS(this)
25
25
-
this.com = new ComNS(this)
23
23
+
this.xrpc = createXrpcServer(schemas, options);
24
24
+
this.network = new NetworkNS(this);
25
25
+
this.com = new ComNS(this);
26
26
}
27
27
}
28
28
29
29
export class NetworkNS {
30
30
-
_server: Server
31
31
-
cosmik: NetworkCosmikNS
30
30
+
_server: Server;
31
31
+
cosmik: NetworkCosmikNS;
32
32
33
33
constructor(server: Server) {
34
34
-
this._server = server
35
35
-
this.cosmik = new NetworkCosmikNS(server)
34
34
+
this._server = server;
35
35
+
this.cosmik = new NetworkCosmikNS(server);
36
36
}
37
37
}
38
38
39
39
export class NetworkCosmikNS {
40
40
-
_server: Server
40
40
+
_server: Server;
41
41
42
42
constructor(server: Server) {
43
43
-
this._server = server
43
43
+
this._server = server;
44
44
}
45
45
}
46
46
47
47
export class ComNS {
48
48
-
_server: Server
49
49
-
atproto: ComAtprotoNS
48
48
+
_server: Server;
49
49
+
atproto: ComAtprotoNS;
50
50
51
51
constructor(server: Server) {
52
52
-
this._server = server
53
53
-
this.atproto = new ComAtprotoNS(server)
52
52
+
this._server = server;
53
53
+
this.atproto = new ComAtprotoNS(server);
54
54
}
55
55
}
56
56
57
57
export class ComAtprotoNS {
58
58
-
_server: Server
59
59
-
repo: ComAtprotoRepoNS
58
58
+
_server: Server;
59
59
+
repo: ComAtprotoRepoNS;
60
60
61
61
constructor(server: Server) {
62
62
-
this._server = server
63
63
-
this.repo = new ComAtprotoRepoNS(server)
62
62
+
this._server = server;
63
63
+
this.repo = new ComAtprotoRepoNS(server);
64
64
}
65
65
}
66
66
67
67
export class ComAtprotoRepoNS {
68
68
-
_server: Server
68
68
+
_server: Server;
69
69
70
70
constructor(server: Server) {
71
71
-
this._server = server
71
71
+
this._server = server;
72
72
}
73
73
}
74
74
75
75
type SharedRateLimitOpts<T> = {
76
76
-
name: string
77
77
-
calcKey?: (ctx: T) => string | null
78
78
-
calcPoints?: (ctx: T) => number
79
79
-
}
76
76
+
name: string;
77
77
+
calcKey?: (ctx: T) => string | null;
78
78
+
calcPoints?: (ctx: T) => number;
79
79
+
};
80
80
type RouteRateLimitOpts<T> = {
81
81
-
durationMs: number
82
82
-
points: number
83
83
-
calcKey?: (ctx: T) => string | null
84
84
-
calcPoints?: (ctx: T) => number
85
85
-
}
86
86
-
type HandlerOpts = { blobLimit?: number }
87
87
-
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>
81
81
+
durationMs: number;
82
82
+
points: number;
83
83
+
calcKey?: (ctx: T) => string | null;
84
84
+
calcPoints?: (ctx: T) => number;
85
85
+
};
86
86
+
type HandlerOpts = { blobLimit?: number };
87
87
+
type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T>;
88
88
type ConfigOf<Auth, Handler, ReqCtx> =
89
89
| Handler
90
90
| {
91
91
-
auth?: Auth
92
92
-
opts?: HandlerOpts
93
93
-
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[]
94
94
-
handler: Handler
95
95
-
}
91
91
+
auth?: Auth;
92
92
+
opts?: HandlerOpts;
93
93
+
rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[];
94
94
+
handler: Handler;
95
95
+
};
96
96
type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract<
97
97
Awaited<ReturnType<AV>>,
98
98
{ credentials: unknown }
99
99
-
>
99
99
+
>;
···
6
6
Lexicons,
7
7
ValidationError,
8
8
type ValidationResult,
9
9
-
} from '@atproto/lexicon'
10
10
-
import { type $Typed, is$typed, maybe$typed } from './util.js'
9
9
+
} from '@atproto/lexicon';
10
10
+
import { type $Typed, is$typed, maybe$typed } from './util.js';
11
11
12
12
export const schemaDict = {
13
13
NetworkCosmikCard: {
···
286
286
},
287
287
},
288
288
},
289
289
-
} as const satisfies Record<string, LexiconDoc>
290
290
-
export const schemas = Object.values(schemaDict) satisfies LexiconDoc[]
291
291
-
export const lexicons: Lexicons = new Lexicons(schemas)
289
289
+
} as const satisfies Record<string, LexiconDoc>;
290
290
+
export const schemas = Object.values(schemaDict) satisfies LexiconDoc[];
291
291
+
export const lexicons: Lexicons = new Lexicons(schemas);
292
292
293
293
export function validate<T extends { $type: string }>(
294
294
v: unknown,
295
295
id: string,
296
296
hash: string,
297
297
requiredType: true,
298
298
-
): ValidationResult<T>
298
298
+
): ValidationResult<T>;
299
299
export function validate<T extends { $type?: string }>(
300
300
v: unknown,
301
301
id: string,
302
302
hash: string,
303
303
requiredType?: false,
304
304
-
): ValidationResult<T>
304
304
+
): ValidationResult<T>;
305
305
export function validate(
306
306
v: unknown,
307
307
id: string,
···
315
315
error: new ValidationError(
316
316
`Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`,
317
317
),
318
318
-
}
318
318
+
};
319
319
}
320
320
321
321
export const ids = {
···
324
324
NetworkCosmikCollectionLink: 'network.cosmik.collectionLink',
325
325
NetworkCosmikDefs: 'network.cosmik.defs',
326
326
ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef',
327
327
-
} as const
327
327
+
} as const;
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../../lexicons'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../../lexicons';
7
7
import {
8
8
type $Typed,
9
9
is$typed as _is$typed,
10
10
type OmitKey,
11
11
-
} from '../../../../util'
11
11
+
} from '../../../../util';
12
12
13
13
const is$typed = _is$typed,
14
14
-
validate = _validate
15
15
-
const id = 'com.atproto.repo.strongRef'
14
14
+
validate = _validate;
15
15
+
const id = 'com.atproto.repo.strongRef';
16
16
17
17
export interface Main {
18
18
-
$type?: 'com.atproto.repo.strongRef'
19
19
-
cid: string
20
20
-
uri: string
18
18
+
$type?: 'com.atproto.repo.strongRef';
19
19
+
cid: string;
20
20
+
uri: string;
21
21
}
22
22
23
23
-
const hashMain = 'main'
23
23
+
const hashMain = 'main';
24
24
25
25
export function isMain<V>(v: V) {
26
26
-
return is$typed(v, id, hashMain)
26
26
+
return is$typed(v, id, hashMain);
27
27
}
28
28
29
29
export function validateMain<V>(v: V) {
30
30
-
return validate<Main & V>(v, id, hashMain)
30
30
+
return validate<Main & V>(v, id, hashMain);
31
31
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
8
-
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
12
12
+
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js';
9
13
10
14
const is$typed = _is$typed,
11
11
-
validate = _validate
12
12
-
const id = 'network.cosmik.card'
15
15
+
validate = _validate;
16
16
+
const id = 'network.cosmik.card';
13
17
14
18
export interface Record {
15
15
-
$type: 'network.cosmik.card'
19
19
+
$type: 'network.cosmik.card';
16
20
/** The type of card */
17
17
-
type: 'URL' | 'NOTE' | (string & {})
18
18
-
content: $Typed<UrlContent> | $Typed<NoteContent> | { $type: string }
21
21
+
type: 'URL' | 'NOTE' | (string & {});
22
22
+
content: $Typed<UrlContent> | $Typed<NoteContent> | { $type: string };
19
23
/** Optional URL associated with the card. Required for URL cards, optional for NOTE cards. */
20
20
-
url?: string
21
21
-
parentCard?: ComAtprotoRepoStrongRef.Main
24
24
+
url?: string;
25
25
+
parentCard?: ComAtprotoRepoStrongRef.Main;
22
26
/** Timestamp when this card was created (usually set by PDS). */
23
23
-
createdAt?: string
24
24
-
originalCard?: ComAtprotoRepoStrongRef.Main
25
25
-
[k: string]: unknown
27
27
+
createdAt?: string;
28
28
+
originalCard?: ComAtprotoRepoStrongRef.Main;
29
29
+
[k: string]: unknown;
26
30
}
27
31
28
28
-
const hashRecord = 'main'
32
32
+
const hashRecord = 'main';
29
33
30
34
export function isRecord<V>(v: V) {
31
31
-
return is$typed(v, id, hashRecord)
35
35
+
return is$typed(v, id, hashRecord);
32
36
}
33
37
34
38
export function validateRecord<V>(v: V) {
35
35
-
return validate<Record & V>(v, id, hashRecord, true)
39
39
+
return validate<Record & V>(v, id, hashRecord, true);
36
40
}
37
41
38
42
/** Content structure for a URL card. */
39
43
export interface UrlContent {
40
40
-
$type?: 'network.cosmik.card#urlContent'
44
44
+
$type?: 'network.cosmik.card#urlContent';
41
45
/** The URL being saved */
42
42
-
url: string
43
43
-
metadata?: UrlMetadata
46
46
+
url: string;
47
47
+
metadata?: UrlMetadata;
44
48
}
45
49
46
46
-
const hashUrlContent = 'urlContent'
50
50
+
const hashUrlContent = 'urlContent';
47
51
48
52
export function isUrlContent<V>(v: V) {
49
49
-
return is$typed(v, id, hashUrlContent)
53
53
+
return is$typed(v, id, hashUrlContent);
50
54
}
51
55
52
56
export function validateUrlContent<V>(v: V) {
53
53
-
return validate<UrlContent & V>(v, id, hashUrlContent)
57
57
+
return validate<UrlContent & V>(v, id, hashUrlContent);
54
58
}
55
59
56
60
/** Content structure for a note card. */
57
61
export interface NoteContent {
58
58
-
$type?: 'network.cosmik.card#noteContent'
62
62
+
$type?: 'network.cosmik.card#noteContent';
59
63
/** The note text content */
60
60
-
text: string
64
64
+
text: string;
61
65
}
62
66
63
63
-
const hashNoteContent = 'noteContent'
67
67
+
const hashNoteContent = 'noteContent';
64
68
65
69
export function isNoteContent<V>(v: V) {
66
66
-
return is$typed(v, id, hashNoteContent)
70
70
+
return is$typed(v, id, hashNoteContent);
67
71
}
68
72
69
73
export function validateNoteContent<V>(v: V) {
70
70
-
return validate<NoteContent & V>(v, id, hashNoteContent)
74
74
+
return validate<NoteContent & V>(v, id, hashNoteContent);
71
75
}
72
76
73
77
/** Metadata about a URL. */
74
78
export interface UrlMetadata {
75
75
-
$type?: 'network.cosmik.card#urlMetadata'
79
79
+
$type?: 'network.cosmik.card#urlMetadata';
76
80
/** Title of the page */
77
77
-
title?: string
81
81
+
title?: string;
78
82
/** Description of the page */
79
79
-
description?: string
83
83
+
description?: string;
80
84
/** Author of the content */
81
81
-
author?: string
85
85
+
author?: string;
82
86
/** When the content was published */
83
83
-
publishedDate?: string
87
87
+
publishedDate?: string;
84
88
/** Name of the site */
85
85
-
siteName?: string
89
89
+
siteName?: string;
86
90
/** URL of a representative image */
87
87
-
imageUrl?: string
91
91
+
imageUrl?: string;
88
92
/** Type of content (e.g., 'video', 'article') */
89
89
-
type?: string
93
93
+
type?: string;
90
94
/** When the metadata was retrieved */
91
91
-
retrievedAt?: string
95
95
+
retrievedAt?: string;
92
96
}
93
97
94
94
-
const hashUrlMetadata = 'urlMetadata'
98
98
+
const hashUrlMetadata = 'urlMetadata';
95
99
96
100
export function isUrlMetadata<V>(v: V) {
97
97
-
return is$typed(v, id, hashUrlMetadata)
101
101
+
return is$typed(v, id, hashUrlMetadata);
98
102
}
99
103
100
104
export function validateUrlMetadata<V>(v: V) {
101
101
-
return validate<UrlMetadata & V>(v, id, hashUrlMetadata)
105
105
+
return validate<UrlMetadata & V>(v, id, hashUrlMetadata);
102
106
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
8
12
9
13
const is$typed = _is$typed,
10
10
-
validate = _validate
11
11
-
const id = 'network.cosmik.collection'
14
14
+
validate = _validate;
15
15
+
const id = 'network.cosmik.collection';
12
16
13
17
export interface Record {
14
14
-
$type: 'network.cosmik.collection'
18
18
+
$type: 'network.cosmik.collection';
15
19
/** Name of the collection */
16
16
-
name: string
20
20
+
name: string;
17
21
/** Description of the collection */
18
18
-
description?: string
22
22
+
description?: string;
19
23
/** Access control for the collection */
20
20
-
accessType: 'OPEN' | 'CLOSED' | (string & {})
24
24
+
accessType: 'OPEN' | 'CLOSED' | (string & {});
21
25
/** List of collaborator DIDs who can add cards to closed collections */
22
22
-
collaborators?: string[]
26
26
+
collaborators?: string[];
23
27
/** Timestamp when this collection was created (usually set by PDS). */
24
24
-
createdAt?: string
28
28
+
createdAt?: string;
25
29
/** Timestamp when this collection was last updated. */
26
26
-
updatedAt?: string
27
27
-
[k: string]: unknown
30
30
+
updatedAt?: string;
31
31
+
[k: string]: unknown;
28
32
}
29
33
30
30
-
const hashRecord = 'main'
34
34
+
const hashRecord = 'main';
31
35
32
36
export function isRecord<V>(v: V) {
33
33
-
return is$typed(v, id, hashRecord)
37
37
+
return is$typed(v, id, hashRecord);
34
38
}
35
39
36
40
export function validateRecord<V>(v: V) {
37
37
-
return validate<Record & V>(v, id, hashRecord, true)
41
41
+
return validate<Record & V>(v, id, hashRecord, true);
38
42
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
8
8
-
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
12
12
+
import type * as ComAtprotoRepoStrongRef from '../../com/atproto/repo/strongRef.js';
9
13
10
14
const is$typed = _is$typed,
11
11
-
validate = _validate
12
12
-
const id = 'network.cosmik.collectionLink'
15
15
+
validate = _validate;
16
16
+
const id = 'network.cosmik.collectionLink';
13
17
14
18
export interface Record {
15
15
-
$type: 'network.cosmik.collectionLink'
16
16
-
collection: ComAtprotoRepoStrongRef.Main
17
17
-
card: ComAtprotoRepoStrongRef.Main
18
18
-
originalCard?: ComAtprotoRepoStrongRef.Main
19
19
+
$type: 'network.cosmik.collectionLink';
20
20
+
collection: ComAtprotoRepoStrongRef.Main;
21
21
+
card: ComAtprotoRepoStrongRef.Main;
22
22
+
originalCard?: ComAtprotoRepoStrongRef.Main;
19
23
/** DID of the user who added the card to the collection */
20
20
-
addedBy: string
24
24
+
addedBy: string;
21
25
/** Timestamp when the card was added to the collection. */
22
22
-
addedAt: string
26
26
+
addedAt: string;
23
27
/** Timestamp when this link record was created (usually set by PDS). */
24
24
-
createdAt?: string
25
25
-
[k: string]: unknown
28
28
+
createdAt?: string;
29
29
+
[k: string]: unknown;
26
30
}
27
31
28
28
-
const hashRecord = 'main'
32
32
+
const hashRecord = 'main';
29
33
30
34
export function isRecord<V>(v: V) {
31
31
-
return is$typed(v, id, hashRecord)
35
35
+
return is$typed(v, id, hashRecord);
32
36
}
33
37
34
38
export function validateRecord<V>(v: V) {
35
35
-
return validate<Record & V>(v, id, hashRecord, true)
39
39
+
return validate<Record & V>(v, id, hashRecord, true);
36
40
}
···
1
1
/**
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
-
import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
5
-
import { CID } from 'multiformats/cid'
6
6
-
import { validate as _validate } from '../../../lexicons'
7
7
-
import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
4
4
+
import { type ValidationResult, BlobRef } from '@atproto/lexicon';
5
5
+
import { CID } from 'multiformats/cid';
6
6
+
import { validate as _validate } from '../../../lexicons';
7
7
+
import {
8
8
+
type $Typed,
9
9
+
is$typed as _is$typed,
10
10
+
type OmitKey,
11
11
+
} from '../../../util';
8
12
9
13
const is$typed = _is$typed,
10
10
-
validate = _validate
11
11
-
const id = 'network.cosmik.defs'
14
14
+
validate = _validate;
15
15
+
const id = 'network.cosmik.defs';
12
16
13
17
/** Represents an identifier with a type and value. */
14
18
export interface Identifier {
15
15
-
$type?: 'network.cosmik.defs#identifier'
19
19
+
$type?: 'network.cosmik.defs#identifier';
16
20
/** The type of identifier (e.g., 'doi', 'at-uri', 'isbn'). */
17
17
-
type: string
21
21
+
type: string;
18
22
/** The identifier value. */
19
19
-
value: string
23
23
+
value: string;
20
24
}
21
25
22
22
-
const hashIdentifier = 'identifier'
26
26
+
const hashIdentifier = 'identifier';
23
27
24
28
export function isIdentifier<V>(v: V) {
25
25
-
return is$typed(v, id, hashIdentifier)
29
29
+
return is$typed(v, id, hashIdentifier);
26
30
}
27
31
28
32
export function validateIdentifier<V>(v: V) {
29
29
-
return validate<Identifier & V>(v, id, hashIdentifier)
33
33
+
return validate<Identifier & V>(v, id, hashIdentifier);
30
34
}
···
2
2
* GENERATED CODE - DO NOT MODIFY
3
3
*/
4
4
5
5
-
import { type ValidationResult } from '@atproto/lexicon'
5
5
+
import { type ValidationResult } from '@atproto/lexicon';
6
6
7
7
export type OmitKey<T, K extends keyof T> = {
8
8
-
[K2 in keyof T as K2 extends K ? never : K2]: T[K2]
9
9
-
}
8
8
+
[K2 in keyof T as K2 extends K ? never : K2]: T[K2];
9
9
+
};
10
10
11
11
-
export type $Typed<V, T extends string = string> = V & { $type: T }
12
12
-
export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'>
11
11
+
export type $Typed<V, T extends string = string> = V & { $type: T };
12
12
+
export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'>;
13
13
14
14
export type $Type<Id extends string, Hash extends string> = Hash extends 'main'
15
15
? Id
16
16
-
: `${Id}#${Hash}`
16
16
+
: `${Id}#${Hash}`;
17
17
18
18
function isObject<V>(v: V): v is V & object {
19
19
-
return v != null && typeof v === 'object'
19
19
+
return v != null && typeof v === 'object';
20
20
}
21
21
22
22
function is$type<Id extends string, Hash extends string>(
···
31
31
$type.length === id.length + 1 + hash.length &&
32
32
$type.charCodeAt(id.length) === 35 /* '#' */ &&
33
33
$type.startsWith(id) &&
34
34
-
$type.endsWith(hash)
34
34
+
$type.endsWith(hash);
35
35
}
36
36
37
37
export type $TypedObject<
···
39
39
Id extends string,
40
40
Hash extends string,
41
41
> = V extends {
42
42
-
$type: $Type<Id, Hash>
42
42
+
$type: $Type<Id, Hash>;
43
43
}
44
44
? V
45
45
: V extends { $type?: string }
46
46
? V extends { $type?: infer T extends $Type<Id, Hash> }
47
47
? V & { $type: T }
48
48
: never
49
49
-
: V & { $type: $Type<Id, Hash> }
49
49
+
: V & { $type: $Type<Id, Hash> };
50
50
51
51
export function is$typed<V, Id extends string, Hash extends string>(
52
52
v: V,
53
53
id: Id,
54
54
hash: Hash,
55
55
): v is $TypedObject<V, Id, Hash> {
56
56
-
return isObject(v) && '$type' in v && is$type(v.$type, id, hash)
56
56
+
return isObject(v) && '$type' in v && is$type(v.$type, id, hash);
57
57
}
58
58
59
59
export function maybe$typed<V, Id extends string, Hash extends string>(
···
64
64
return (
65
65
isObject(v) &&
66
66
('$type' in v ? v.$type === undefined || is$type(v.$type, id, hash) : true)
67
67
-
)
67
67
+
);
68
68
}
69
69
70
70
-
export type Validator<R = unknown> = (v: unknown) => ValidationResult<R>
70
70
+
export type Validator<R = unknown> = (v: unknown) => ValidationResult<R>;
71
71
export type ValidatorParam<V extends Validator> =
72
72
-
V extends Validator<infer R> ? R : never
72
72
+
V extends Validator<infer R> ? R : never;
73
73
74
74
/**
75
75
* Utility function that allows to convert a "validate*" utility function into a
···
77
77
*/
78
78
export function asPredicate<V extends Validator>(validate: V) {
79
79
return function <T>(v: T): v is T & ValidatorParam<V> {
80
80
-
return validate(v).success
81
81
-
}
80
80
+
return validate(v).success;
81
81
+
};
82
82
}
···
14
14
URL_CARD_CANNOT_HAVE_PARENT: 'URL cards cannot have parent cards',
15
15
URL_CARD_MUST_HAVE_URL: 'URL cards must have a url property',
16
16
URL_CARD_SINGLE_LIBRARY_ONLY: 'URL cards can only be in one library',
17
17
-
URL_CARD_CREATOR_LIBRARY_ONLY: 'URL cards can only be in the library of the creator',
18
18
-
LIBRARY_COUNT_MISMATCH: 'Library count does not match library memberships length',
17
17
+
URL_CARD_CREATOR_LIBRARY_ONLY:
18
18
+
'URL cards can only be in the library of the creator',
19
19
+
LIBRARY_COUNT_MISMATCH:
20
20
+
'Library count does not match library memberships length',
19
21
CANNOT_CHANGE_CONTENT_TYPE: 'Cannot change card content to different type',
20
22
ALREADY_IN_LIBRARY: "Card is already in user's library",
21
23
NOT_IN_LIBRARY: "Card is not in user's library",
···
123
125
): Result<Card, CardValidationError> {
124
126
// Validate content type matches card type
125
127
if (props.type.value !== props.content.type) {
126
126
-
return err(new CardValidationError(CARD_ERROR_MESSAGES.CARD_TYPE_CONTENT_MISMATCH));
128
128
+
return err(
129
129
+
new CardValidationError(CARD_ERROR_MESSAGES.CARD_TYPE_CONTENT_MISMATCH),
130
130
+
);
127
131
}
128
132
129
133
// Validate parent/source card relationships
···
157
161
): Result<void, CardValidationError> {
158
162
// URL cards should not have parent cards
159
163
if (props.type.value === CardTypeEnum.URL && props.parentCardId) {
160
160
-
return err(new CardValidationError(CARD_ERROR_MESSAGES.URL_CARD_CANNOT_HAVE_PARENT));
164
164
+
return err(
165
165
+
new CardValidationError(
166
166
+
CARD_ERROR_MESSAGES.URL_CARD_CANNOT_HAVE_PARENT,
167
167
+
),
168
168
+
);
161
169
}
162
170
163
171
// URL cards must have a URL property
164
172
if (props.type.value === CardTypeEnum.URL && !props.url) {
165
165
-
return err(new CardValidationError(CARD_ERROR_MESSAGES.URL_CARD_MUST_HAVE_URL));
173
173
+
return err(
174
174
+
new CardValidationError(CARD_ERROR_MESSAGES.URL_CARD_MUST_HAVE_URL),
175
175
+
);
166
176
}
167
177
168
178
// URL cards can only be in one library
···
172
182
libraryMemberships.length > 1
173
183
) {
174
184
return err(
175
175
-
new CardValidationError(CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY),
185
185
+
new CardValidationError(
186
186
+
CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY,
187
187
+
),
176
188
);
177
189
}
178
190
···
186
198
);
187
199
if (hasNonCreatorMembership) {
188
200
return err(
189
189
-
new CardValidationError(CARD_ERROR_MESSAGES.URL_CARD_CREATOR_LIBRARY_ONLY),
201
201
+
new CardValidationError(
202
202
+
CARD_ERROR_MESSAGES.URL_CARD_CREATOR_LIBRARY_ONLY,
203
203
+
),
190
204
);
191
205
}
192
206
}
···
227
241
link.curatorId.equals(userId),
228
242
)
229
243
) {
230
230
-
return err(new CardValidationError(CARD_ERROR_MESSAGES.ALREADY_IN_LIBRARY));
244
244
+
return err(
245
245
+
new CardValidationError(CARD_ERROR_MESSAGES.ALREADY_IN_LIBRARY),
246
246
+
);
231
247
}
232
248
233
249
// URL cards can only be in one library
234
250
if (this.isUrlCard && this.props.libraryMemberships.length > 0) {
235
251
return err(
236
236
-
new CardValidationError(CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY),
252
252
+
new CardValidationError(
253
253
+
CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY,
254
254
+
),
237
255
);
238
256
}
239
257
···
49
49
curatorId: string,
50
50
options: CollectionQueryOptions,
51
51
): Promise<PaginatedQueryResult<CollectionQueryResultDTO>>;
52
52
-
52
52
+
53
53
getCollectionsContainingCardForUser(
54
54
cardId: string,
55
55
curatorId: string,
···
42
42
);
43
43
44
44
// GET /api/cards/library/status - Get URL status for my library
45
45
-
router.get('/library/status', authMiddleware.ensureAuthenticated(), (req, res) =>
46
46
-
getUrlStatusForMyLibraryController.execute(req, res),
45
45
+
router.get(
46
46
+
'/library/status',
47
47
+
authMiddleware.ensureAuthenticated(),
48
48
+
(req, res) => getUrlStatusForMyLibraryController.execute(req, res),
47
49
);
48
50
49
51
// GET /api/cards/user/:identifier - Get user's URL cards by identifier
···
10
10
} from 'drizzle-orm/pg-core';
11
11
import { publishedRecords } from './publishedRecord.sql';
12
12
13
13
-
export const cards: PgTableWithColumns<any> = pgTable('cards', {
14
14
-
id: uuid('id').primaryKey(),
15
15
-
authorId: text('author_id').notNull(),
16
16
-
type: text('type').notNull(), // URL, NOTE, HIGHLIGHT
17
17
-
contentData: jsonb('content_data').notNull(),
18
18
-
url: text('url'), // Optional URL field for all card types
19
19
-
parentCardId: uuid('parent_card_id').references(() => cards.id),
20
20
-
publishedRecordId: uuid('published_record_id').references(
21
21
-
() => publishedRecords.id,
22
22
-
),
23
23
-
libraryCount: integer('library_count').notNull().default(0),
24
24
-
createdAt: timestamp('created_at').notNull().defaultNow(),
25
25
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
26
26
-
}, (table) => {
27
27
-
return {
28
28
-
// Critical for findUsersUrlCardByUrl queries
29
29
-
authorUrlIdx: index('cards_author_url_idx').on(table.authorId, table.url),
30
30
-
31
31
-
// For general card queries by author
32
32
-
authorIdIdx: index('cards_author_id_idx').on(table.authorId),
33
33
-
};
34
34
-
});
13
13
+
export const cards: PgTableWithColumns<any> = pgTable(
14
14
+
'cards',
15
15
+
{
16
16
+
id: uuid('id').primaryKey(),
17
17
+
authorId: text('author_id').notNull(),
18
18
+
type: text('type').notNull(), // URL, NOTE, HIGHLIGHT
19
19
+
contentData: jsonb('content_data').notNull(),
20
20
+
url: text('url'), // Optional URL field for all card types
21
21
+
parentCardId: uuid('parent_card_id').references(() => cards.id),
22
22
+
publishedRecordId: uuid('published_record_id').references(
23
23
+
() => publishedRecords.id,
24
24
+
),
25
25
+
libraryCount: integer('library_count').notNull().default(0),
26
26
+
createdAt: timestamp('created_at').notNull().defaultNow(),
27
27
+
updatedAt: timestamp('updated_at').notNull().defaultNow(),
28
28
+
},
29
29
+
(table) => {
30
30
+
return {
31
31
+
// Critical for findUsersUrlCardByUrl queries
32
32
+
authorUrlIdx: index('cards_author_url_idx').on(table.authorId, table.url),
33
33
+
34
34
+
// For general card queries by author
35
35
+
authorIdIdx: index('cards_author_id_idx').on(table.authorId),
36
36
+
};
37
37
+
},
38
38
+
);
···
10
10
import { publishedRecords } from './publishedRecord.sql';
11
11
import { cards } from './card.sql';
12
12
13
13
-
export const collections = pgTable('collections', {
14
14
-
id: uuid('id').primaryKey(),
15
15
-
authorId: text('author_id').notNull(),
16
16
-
name: text('name').notNull(),
17
17
-
description: text('description'),
18
18
-
accessType: text('access_type').notNull(), // OPEN, CLOSED
19
19
-
cardCount: integer('card_count').notNull().default(0),
20
20
-
createdAt: timestamp('created_at').notNull().defaultNow(),
21
21
-
updatedAt: timestamp('updated_at').notNull().defaultNow(),
22
22
-
publishedRecordId: uuid('published_record_id').references(
23
23
-
() => publishedRecords.id,
24
24
-
),
25
25
-
}, (table) => {
26
26
-
return {
27
27
-
// Critical for all collection queries by user
28
28
-
authorIdIdx: index('collections_author_id_idx').on(table.authorId),
29
29
-
30
30
-
// For paginated collection listings (most common sort)
31
31
-
authorUpdatedAtIdx: index('collections_author_updated_at_idx').on(table.authorId, table.updatedAt),
32
32
-
};
33
33
-
});
13
13
+
export const collections = pgTable(
14
14
+
'collections',
15
15
+
{
16
16
+
id: uuid('id').primaryKey(),
17
17
+
authorId: text('author_id').notNull(),
18
18
+
name: text('name').notNull(),
19
19
+
description: text('description'),
20
20
+
accessType: text('access_type').notNull(), // OPEN, CLOSED
21
21
+
cardCount: integer('card_count').notNull().default(0),
22
22
+
createdAt: timestamp('created_at').notNull().defaultNow(),
23
23
+
updatedAt: timestamp('updated_at').notNull().defaultNow(),
24
24
+
publishedRecordId: uuid('published_record_id').references(
25
25
+
() => publishedRecords.id,
26
26
+
),
27
27
+
},
28
28
+
(table) => {
29
29
+
return {
30
30
+
// Critical for all collection queries by user
31
31
+
authorIdIdx: index('collections_author_id_idx').on(table.authorId),
32
32
+
33
33
+
// For paginated collection listings (most common sort)
34
34
+
authorUpdatedAtIdx: index('collections_author_updated_at_idx').on(
35
35
+
table.authorId,
36
36
+
table.updatedAt,
37
37
+
),
38
38
+
};
39
39
+
},
40
40
+
);
34
41
35
42
// Join table for collection collaborators
36
43
export const collectionCollaborators = pgTable('collection_collaborators', {
···
42
49
});
43
50
44
51
// Join table for cards in collections
45
45
-
export const collectionCards = pgTable('collection_cards', {
46
46
-
id: uuid('id').primaryKey(),
47
47
-
collectionId: uuid('collection_id')
48
48
-
.notNull()
49
49
-
.references(() => collections.id, { onDelete: 'cascade' }),
50
50
-
cardId: uuid('card_id')
51
51
-
.notNull()
52
52
-
.references(() => cards.id, { onDelete: 'cascade' }),
53
53
-
addedBy: text('added_by').notNull(),
54
54
-
addedAt: timestamp('added_at').notNull().defaultNow(),
55
55
-
publishedRecordId: uuid('published_record_id').references(
56
56
-
() => publishedRecords.id,
57
57
-
),
58
58
-
}, (table) => {
59
59
-
return {
60
60
-
// Critical for getCollectionsContainingCardForUser queries
61
61
-
cardIdIdx: index('collection_cards_card_id_idx').on(table.cardId),
62
62
-
collectionIdIdx: index('collection_cards_collection_id_idx').on(table.collectionId),
63
63
-
};
64
64
-
});
52
52
+
export const collectionCards = pgTable(
53
53
+
'collection_cards',
54
54
+
{
55
55
+
id: uuid('id').primaryKey(),
56
56
+
collectionId: uuid('collection_id')
57
57
+
.notNull()
58
58
+
.references(() => collections.id, { onDelete: 'cascade' }),
59
59
+
cardId: uuid('card_id')
60
60
+
.notNull()
61
61
+
.references(() => cards.id, { onDelete: 'cascade' }),
62
62
+
addedBy: text('added_by').notNull(),
63
63
+
addedAt: timestamp('added_at').notNull().defaultNow(),
64
64
+
publishedRecordId: uuid('published_record_id').references(
65
65
+
() => publishedRecords.id,
66
66
+
),
67
67
+
},
68
68
+
(table) => {
69
69
+
return {
70
70
+
// Critical for getCollectionsContainingCardForUser queries
71
71
+
cardIdIdx: index('collection_cards_card_id_idx').on(table.cardId),
72
72
+
collectionIdIdx: index('collection_cards_collection_id_idx').on(
73
73
+
table.collectionId,
74
74
+
),
75
75
+
};
76
76
+
},
77
77
+
);
···
51
51
cardPublisher.clear();
52
52
});
53
53
54
54
-
const createCard = async (type: CardTypeEnum = CardTypeEnum.URL, creatorId: CuratorId = curatorId) => {
55
55
-
const card = new CardBuilder().withType(type).withCuratorId(creatorId.value).build();
54
54
+
const createCard = async (
55
55
+
type: CardTypeEnum = CardTypeEnum.URL,
56
56
+
creatorId: CuratorId = curatorId,
57
57
+
) => {
58
58
+
const card = new CardBuilder()
59
59
+
.withType(type)
60
60
+
.withCuratorId(creatorId.value)
61
61
+
.build();
56
62
57
63
if (card instanceof Error) {
58
64
throw new Error(`Failed to create card: ${card.message}`);
···
65
71
const addCardToLibrary = async (card: any, curatorId: CuratorId) => {
66
72
// For URL cards, can only add to creator's library
67
73
if (card.isUrlCard && !card.curatorId.equals(curatorId)) {
68
68
-
throw new Error('URL cards can only be added to creator\'s library');
74
74
+
throw new Error("URL cards can only be added to creator's library");
69
75
}
70
70
-
76
76
+
71
77
const addResult = await cardLibraryService.addCardToLibrary(
72
78
card,
73
79
curatorId,
···
169
175
it('should handle different card types', async () => {
170
176
// Create URL card with curatorId as creator
171
177
const urlCard = await createCard(CardTypeEnum.URL, curatorId);
172
172
-
// Create note card with curatorId as creator
178
178
+
// Create note card with curatorId as creator
173
179
const noteCard = await createCard(CardTypeEnum.NOTE, curatorId);
174
180
175
181
await addCardToLibrary(urlCard, curatorId);
···
520
526
it('should not delete card when curator is not the owner', async () => {
521
527
// Create card with different owner
522
528
const card = await createCard(CardTypeEnum.NOTE, otherCuratorId);
523
523
-
529
529
+
524
530
// Add to other curator's library first
525
531
await addCardToLibrary(card, otherCuratorId);
526
526
-
532
532
+
527
533
// Add to current curator's library (note cards can be in multiple libraries)
528
534
await addCardToLibrary(card, curatorId);
529
535
···
548
554
it('should not delete card when it still has other library memberships', async () => {
549
555
// Create note card (can be in multiple libraries)
550
556
const card = await createCard(CardTypeEnum.NOTE, curatorId);
551
551
-
557
557
+
552
558
// Add to both curator's libraries
553
559
await addCardToLibrary(card, curatorId);
554
560
await addCardToLibrary(card, otherCuratorId);
···
151
151
throw new Error('Expected creation to fail but it succeeded');
152
152
}
153
153
expect(result.isErr()).toBe(true);
154
154
-
expect(result.error.message).toBe(CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY);
154
154
+
expect(result.error.message).toBe(
155
155
+
CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY,
156
156
+
);
155
157
});
156
158
157
159
it('should allow NOTE cards to have multiple library memberships', () => {
···
256
258
throw new Error('Expected creation to fail but it succeeded');
257
259
}
258
260
expect(result.isErr()).toBe(true);
259
259
-
expect(result.error.message).toBe(CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY);
261
261
+
expect(result.error.message).toBe(
262
262
+
CARD_ERROR_MESSAGES.URL_CARD_SINGLE_LIBRARY_ONLY,
263
263
+
);
260
264
expect(card.libraryMembershipCount).toBe(1);
261
265
expect(card.isInLibrary(curatorId)).toBe(true);
262
266
expect(card.isInLibrary(otherUserId)).toBe(false);
···
69
69
process.env.ATPROTO_SERVICE_ENDPOINT || 'https://bsky.social',
70
70
baseUrl: process.env.BASE_URL || 'http://127.0.0.1:3000',
71
71
collections: {
72
72
-
card: environment === 'prod' ? 'network.cosmik.card' : 'network.cosmik.dev.card',
73
73
-
collection: environment === 'prod' ? 'network.cosmik.collection' : 'network.cosmik.dev.collection',
74
74
-
collectionLink: environment === 'prod' ? 'network.cosmik.collectionLink' : 'network.cosmik.dev.collectionLink',
72
72
+
card:
73
73
+
environment === 'prod'
74
74
+
? 'network.cosmik.card'
75
75
+
: 'network.cosmik.dev.card',
76
76
+
collection:
77
77
+
environment === 'prod'
78
78
+
? 'network.cosmik.collection'
79
79
+
: 'network.cosmik.dev.collection',
80
80
+
collectionLink:
81
81
+
environment === 'prod'
82
82
+
? 'network.cosmik.collectionLink'
83
83
+
: 'network.cosmik.dev.collectionLink',
75
84
},
76
85
},
77
86
server: {
···
123
123
"name": "cards_parent_card_id_cards_id_fk",
124
124
"tableFrom": "cards",
125
125
"tableTo": "cards",
126
126
-
"columnsFrom": [
127
127
-
"parent_card_id"
128
128
-
],
129
129
-
"columnsTo": [
130
130
-
"id"
131
131
-
],
126
126
+
"columnsFrom": ["parent_card_id"],
127
127
+
"columnsTo": ["id"],
132
128
"onDelete": "no action",
133
129
"onUpdate": "no action"
134
130
},
···
136
132
"name": "cards_published_record_id_published_records_id_fk",
137
133
"tableFrom": "cards",
138
134
"tableTo": "published_records",
139
139
-
"columnsFrom": [
140
140
-
"published_record_id"
141
141
-
],
142
142
-
"columnsTo": [
143
143
-
"id"
144
144
-
],
135
135
+
"columnsFrom": ["published_record_id"],
136
136
+
"columnsTo": ["id"],
145
137
"onDelete": "no action",
146
138
"onUpdate": "no action"
147
139
}
···
200
192
"name": "collection_cards_collection_id_collections_id_fk",
201
193
"tableFrom": "collection_cards",
202
194
"tableTo": "collections",
203
203
-
"columnsFrom": [
204
204
-
"collection_id"
205
205
-
],
206
206
-
"columnsTo": [
207
207
-
"id"
208
208
-
],
195
195
+
"columnsFrom": ["collection_id"],
196
196
+
"columnsTo": ["id"],
209
197
"onDelete": "cascade",
210
198
"onUpdate": "no action"
211
199
},
···
213
201
"name": "collection_cards_card_id_cards_id_fk",
214
202
"tableFrom": "collection_cards",
215
203
"tableTo": "cards",
216
216
-
"columnsFrom": [
217
217
-
"card_id"
218
218
-
],
219
219
-
"columnsTo": [
220
220
-
"id"
221
221
-
],
204
204
+
"columnsFrom": ["card_id"],
205
205
+
"columnsTo": ["id"],
222
206
"onDelete": "cascade",
223
207
"onUpdate": "no action"
224
208
},
···
226
210
"name": "collection_cards_published_record_id_published_records_id_fk",
227
211
"tableFrom": "collection_cards",
228
212
"tableTo": "published_records",
229
229
-
"columnsFrom": [
230
230
-
"published_record_id"
231
231
-
],
232
232
-
"columnsTo": [
233
233
-
"id"
234
234
-
],
213
213
+
"columnsFrom": ["published_record_id"],
214
214
+
"columnsTo": ["id"],
235
215
"onDelete": "no action",
236
216
"onUpdate": "no action"
237
217
}
···
271
251
"name": "collection_collaborators_collection_id_collections_id_fk",
272
252
"tableFrom": "collection_collaborators",
273
253
"tableTo": "collections",
274
274
-
"columnsFrom": [
275
275
-
"collection_id"
276
276
-
],
277
277
-
"columnsTo": [
278
278
-
"id"
279
279
-
],
254
254
+
"columnsFrom": ["collection_id"],
255
255
+
"columnsTo": ["id"],
280
256
"onDelete": "cascade",
281
257
"onUpdate": "no action"
282
258
}
···
355
331
"name": "collections_published_record_id_published_records_id_fk",
356
332
"tableFrom": "collections",
357
333
"tableTo": "published_records",
358
358
-
"columnsFrom": [
359
359
-
"published_record_id"
360
360
-
],
361
361
-
"columnsTo": [
362
362
-
"id"
363
363
-
],
334
334
+
"columnsFrom": ["published_record_id"],
335
335
+
"columnsTo": ["id"],
364
336
"onDelete": "no action",
365
337
"onUpdate": "no action"
366
338
}
···
438
410
"name": "library_memberships_card_id_cards_id_fk",
439
411
"tableFrom": "library_memberships",
440
412
"tableTo": "cards",
441
441
-
"columnsFrom": [
442
442
-
"card_id"
443
443
-
],
444
444
-
"columnsTo": [
445
445
-
"id"
446
446
-
],
413
413
+
"columnsFrom": ["card_id"],
414
414
+
"columnsTo": ["id"],
447
415
"onDelete": "cascade",
448
416
"onUpdate": "no action"
449
417
},
···
451
419
"name": "library_memberships_published_record_id_published_records_id_fk",
452
420
"tableFrom": "library_memberships",
453
421
"tableTo": "published_records",
454
454
-
"columnsFrom": [
455
455
-
"published_record_id"
456
456
-
],
457
457
-
"columnsTo": [
458
458
-
"id"
459
459
-
],
422
422
+
"columnsFrom": ["published_record_id"],
423
423
+
"columnsTo": ["id"],
460
424
"onDelete": "no action",
461
425
"onUpdate": "no action"
462
426
}
···
464
428
"compositePrimaryKeys": {
465
429
"library_memberships_card_id_user_id_pk": {
466
430
"name": "library_memberships_card_id_user_id_pk",
467
467
-
"columns": [
468
468
-
"card_id",
469
469
-
"user_id"
470
470
-
]
431
431
+
"columns": ["card_id", "user_id"]
471
432
}
472
433
},
473
434
"uniqueConstraints": {},
···
699
660
"name": "auth_refresh_tokens_user_did_users_id_fk",
700
661
"tableFrom": "auth_refresh_tokens",
701
662
"tableTo": "users",
702
702
-
"columnsFrom": [
703
703
-
"user_did"
704
704
-
],
705
705
-
"columnsTo": [
706
706
-
"id"
707
707
-
],
663
663
+
"columnsFrom": ["user_did"],
664
664
+
"columnsTo": ["id"],
708
665
"onDelete": "no action",
709
666
"onUpdate": "no action"
710
667
}
···
764
721
"schemas": {},
765
722
"tables": {}
766
723
}
767
767
-
}
724
724
+
}
···
122
122
"cards_parent_card_id_cards_id_fk": {
123
123
"name": "cards_parent_card_id_cards_id_fk",
124
124
"tableFrom": "cards",
125
125
-
"columnsFrom": [
126
126
-
"parent_card_id"
127
127
-
],
125
125
+
"columnsFrom": ["parent_card_id"],
128
126
"tableTo": "cards",
129
129
-
"columnsTo": [
130
130
-
"id"
131
131
-
],
127
127
+
"columnsTo": ["id"],
132
128
"onUpdate": "no action",
133
129
"onDelete": "no action"
134
130
},
135
131
"cards_published_record_id_published_records_id_fk": {
136
132
"name": "cards_published_record_id_published_records_id_fk",
137
133
"tableFrom": "cards",
138
138
-
"columnsFrom": [
139
139
-
"published_record_id"
140
140
-
],
134
134
+
"columnsFrom": ["published_record_id"],
141
135
"tableTo": "published_records",
142
142
-
"columnsTo": [
143
143
-
"id"
144
144
-
],
136
136
+
"columnsTo": ["id"],
145
137
"onUpdate": "no action",
146
138
"onDelete": "no action"
147
139
}
···
199
191
"collection_cards_collection_id_collections_id_fk": {
200
192
"name": "collection_cards_collection_id_collections_id_fk",
201
193
"tableFrom": "collection_cards",
202
202
-
"columnsFrom": [
203
203
-
"collection_id"
204
204
-
],
194
194
+
"columnsFrom": ["collection_id"],
205
195
"tableTo": "collections",
206
206
-
"columnsTo": [
207
207
-
"id"
208
208
-
],
196
196
+
"columnsTo": ["id"],
209
197
"onUpdate": "no action",
210
198
"onDelete": "cascade"
211
199
},
212
200
"collection_cards_card_id_cards_id_fk": {
213
201
"name": "collection_cards_card_id_cards_id_fk",
214
202
"tableFrom": "collection_cards",
215
215
-
"columnsFrom": [
216
216
-
"card_id"
217
217
-
],
203
203
+
"columnsFrom": ["card_id"],
218
204
"tableTo": "cards",
219
219
-
"columnsTo": [
220
220
-
"id"
221
221
-
],
205
205
+
"columnsTo": ["id"],
222
206
"onUpdate": "no action",
223
207
"onDelete": "cascade"
224
208
},
225
209
"collection_cards_published_record_id_published_records_id_fk": {
226
210
"name": "collection_cards_published_record_id_published_records_id_fk",
227
211
"tableFrom": "collection_cards",
228
228
-
"columnsFrom": [
229
229
-
"published_record_id"
230
230
-
],
212
212
+
"columnsFrom": ["published_record_id"],
231
213
"tableTo": "published_records",
232
232
-
"columnsTo": [
233
233
-
"id"
234
234
-
],
214
214
+
"columnsTo": ["id"],
235
215
"onUpdate": "no action",
236
216
"onDelete": "no action"
237
217
}
···
270
250
"collection_collaborators_collection_id_collections_id_fk": {
271
251
"name": "collection_collaborators_collection_id_collections_id_fk",
272
252
"tableFrom": "collection_collaborators",
273
273
-
"columnsFrom": [
274
274
-
"collection_id"
275
275
-
],
253
253
+
"columnsFrom": ["collection_id"],
276
254
"tableTo": "collections",
277
277
-
"columnsTo": [
278
278
-
"id"
279
279
-
],
255
255
+
"columnsTo": ["id"],
280
256
"onUpdate": "no action",
281
257
"onDelete": "cascade"
282
258
}
···
354
330
"collections_published_record_id_published_records_id_fk": {
355
331
"name": "collections_published_record_id_published_records_id_fk",
356
332
"tableFrom": "collections",
357
357
-
"columnsFrom": [
358
358
-
"published_record_id"
359
359
-
],
333
333
+
"columnsFrom": ["published_record_id"],
360
334
"tableTo": "published_records",
361
361
-
"columnsTo": [
362
362
-
"id"
363
363
-
],
335
335
+
"columnsTo": ["id"],
364
336
"onUpdate": "no action",
365
337
"onDelete": "no action"
366
338
}
···
437
409
"library_memberships_card_id_cards_id_fk": {
438
410
"name": "library_memberships_card_id_cards_id_fk",
439
411
"tableFrom": "library_memberships",
440
440
-
"columnsFrom": [
441
441
-
"card_id"
442
442
-
],
412
412
+
"columnsFrom": ["card_id"],
443
413
"tableTo": "cards",
444
444
-
"columnsTo": [
445
445
-
"id"
446
446
-
],
414
414
+
"columnsTo": ["id"],
447
415
"onUpdate": "no action",
448
416
"onDelete": "cascade"
449
417
},
450
418
"library_memberships_published_record_id_published_records_id_fk": {
451
419
"name": "library_memberships_published_record_id_published_records_id_fk",
452
420
"tableFrom": "library_memberships",
453
453
-
"columnsFrom": [
454
454
-
"published_record_id"
455
455
-
],
421
421
+
"columnsFrom": ["published_record_id"],
456
422
"tableTo": "published_records",
457
457
-
"columnsTo": [
458
458
-
"id"
459
459
-
],
423
423
+
"columnsTo": ["id"],
460
424
"onUpdate": "no action",
461
425
"onDelete": "no action"
462
426
}
···
464
428
"compositePrimaryKeys": {
465
429
"library_memberships_card_id_user_id_pk": {
466
430
"name": "library_memberships_card_id_user_id_pk",
467
467
-
"columns": [
468
468
-
"card_id",
469
469
-
"user_id"
470
470
-
]
431
431
+
"columns": ["card_id", "user_id"]
471
432
}
472
433
},
473
434
"uniqueConstraints": {},
···
698
659
"auth_refresh_tokens_user_did_users_id_fk": {
699
660
"name": "auth_refresh_tokens_user_did_users_id_fk",
700
661
"tableFrom": "auth_refresh_tokens",
701
701
-
"columnsFrom": [
702
702
-
"user_did"
703
703
-
],
662
662
+
"columnsFrom": ["user_did"],
704
663
"tableTo": "users",
705
705
-
"columnsTo": [
706
706
-
"id"
707
707
-
],
664
664
+
"columnsTo": ["id"],
708
665
"onUpdate": "no action",
709
666
"onDelete": "no action"
710
667
}
···
764
721
"schemas": {},
765
722
"tables": {}
766
723
}
767
767
-
}
724
724
+
}
···
160
160
"name": "cards_parent_card_id_cards_id_fk",
161
161
"tableFrom": "cards",
162
162
"tableTo": "cards",
163
163
-
"columnsFrom": [
164
164
-
"parent_card_id"
165
165
-
],
166
166
-
"columnsTo": [
167
167
-
"id"
168
168
-
],
163
163
+
"columnsFrom": ["parent_card_id"],
164
164
+
"columnsTo": ["id"],
169
165
"onDelete": "no action",
170
166
"onUpdate": "no action"
171
167
},
···
173
169
"name": "cards_published_record_id_published_records_id_fk",
174
170
"tableFrom": "cards",
175
171
"tableTo": "published_records",
176
176
-
"columnsFrom": [
177
177
-
"published_record_id"
178
178
-
],
179
179
-
"columnsTo": [
180
180
-
"id"
181
181
-
],
172
172
+
"columnsFrom": ["published_record_id"],
173
173
+
"columnsTo": ["id"],
182
174
"onDelete": "no action",
183
175
"onUpdate": "no action"
184
176
}
···
268
260
"name": "collection_cards_collection_id_collections_id_fk",
269
261
"tableFrom": "collection_cards",
270
262
"tableTo": "collections",
271
271
-
"columnsFrom": [
272
272
-
"collection_id"
273
273
-
],
274
274
-
"columnsTo": [
275
275
-
"id"
276
276
-
],
263
263
+
"columnsFrom": ["collection_id"],
264
264
+
"columnsTo": ["id"],
277
265
"onDelete": "cascade",
278
266
"onUpdate": "no action"
279
267
},
···
281
269
"name": "collection_cards_card_id_cards_id_fk",
282
270
"tableFrom": "collection_cards",
283
271
"tableTo": "cards",
284
284
-
"columnsFrom": [
285
285
-
"card_id"
286
286
-
],
287
287
-
"columnsTo": [
288
288
-
"id"
289
289
-
],
272
272
+
"columnsFrom": ["card_id"],
273
273
+
"columnsTo": ["id"],
290
274
"onDelete": "cascade",
291
275
"onUpdate": "no action"
292
276
},
···
294
278
"name": "collection_cards_published_record_id_published_records_id_fk",
295
279
"tableFrom": "collection_cards",
296
280
"tableTo": "published_records",
297
297
-
"columnsFrom": [
298
298
-
"published_record_id"
299
299
-
],
300
300
-
"columnsTo": [
301
301
-
"id"
302
302
-
],
281
281
+
"columnsFrom": ["published_record_id"],
282
282
+
"columnsTo": ["id"],
303
283
"onDelete": "no action",
304
284
"onUpdate": "no action"
305
285
}
···
339
319
"name": "collection_collaborators_collection_id_collections_id_fk",
340
320
"tableFrom": "collection_collaborators",
341
321
"tableTo": "collections",
342
342
-
"columnsFrom": [
343
343
-
"collection_id"
344
344
-
],
345
345
-
"columnsTo": [
346
346
-
"id"
347
347
-
],
322
322
+
"columnsFrom": ["collection_id"],
323
323
+
"columnsTo": ["id"],
348
324
"onDelete": "cascade",
349
325
"onUpdate": "no action"
350
326
}
···
460
436
"name": "collections_published_record_id_published_records_id_fk",
461
437
"tableFrom": "collections",
462
438
"tableTo": "published_records",
463
463
-
"columnsFrom": [
464
464
-
"published_record_id"
465
465
-
],
466
466
-
"columnsTo": [
467
467
-
"id"
468
468
-
],
439
439
+
"columnsFrom": ["published_record_id"],
440
440
+
"columnsTo": ["id"],
469
441
"onDelete": "no action",
470
442
"onUpdate": "no action"
471
443
}
···
543
515
"name": "library_memberships_card_id_cards_id_fk",
544
516
"tableFrom": "library_memberships",
545
517
"tableTo": "cards",
546
546
-
"columnsFrom": [
547
547
-
"card_id"
548
548
-
],
549
549
-
"columnsTo": [
550
550
-
"id"
551
551
-
],
518
518
+
"columnsFrom": ["card_id"],
519
519
+
"columnsTo": ["id"],
552
520
"onDelete": "cascade",
553
521
"onUpdate": "no action"
554
522
},
···
556
524
"name": "library_memberships_published_record_id_published_records_id_fk",
557
525
"tableFrom": "library_memberships",
558
526
"tableTo": "published_records",
559
559
-
"columnsFrom": [
560
560
-
"published_record_id"
561
561
-
],
562
562
-
"columnsTo": [
563
563
-
"id"
564
564
-
],
527
527
+
"columnsFrom": ["published_record_id"],
528
528
+
"columnsTo": ["id"],
565
529
"onDelete": "no action",
566
530
"onUpdate": "no action"
567
531
}
···
569
533
"compositePrimaryKeys": {
570
534
"library_memberships_card_id_user_id_pk": {
571
535
"name": "library_memberships_card_id_user_id_pk",
572
572
-
"columns": [
573
573
-
"card_id",
574
574
-
"user_id"
575
575
-
]
536
536
+
"columns": ["card_id", "user_id"]
576
537
}
577
538
},
578
539
"uniqueConstraints": {},
···
804
765
"name": "auth_refresh_tokens_user_did_users_id_fk",
805
766
"tableFrom": "auth_refresh_tokens",
806
767
"tableTo": "users",
807
807
-
"columnsFrom": [
808
808
-
"user_did"
809
809
-
],
810
810
-
"columnsTo": [
811
811
-
"id"
812
812
-
],
768
768
+
"columnsFrom": ["user_did"],
769
769
+
"columnsTo": ["id"],
813
770
"onDelete": "no action",
814
771
"onUpdate": "no action"
815
772
}
···
869
826
"schemas": {},
870
827
"tables": {}
871
828
}
872
872
-
}
829
829
+
}