+1
-2
.env.example
+1
-2
.env.example
+3
-1
apps/api/package.json
+3
-1
apps/api/package.json
···
4
"private": true,
5
"scripts": {
6
"build": "bun --bun run check-types && bun --bun run compile",
7
-
"dev": "bun run --hot src/index.ts",
8
"check-types": "tsc --noEmit",
9
"compile": "bun build src/index.ts --compile --minify --sourcemap --outfile=dist/api --target=bun",
10
"clean": "rimraf dist"
···
20
"@cookware/lexicons": "workspace:*",
21
"@libsql/client": "^0.14.0",
22
"drizzle-orm": "catalog:",
23
"pino": "^9.5.0"
24
},
25
"devDependencies": {
···
27
"@cookware/tsconfig": "workspace:*",
28
"@types/bun": "catalog:",
29
"drizzle-kit": "^0.29.0",
30
"rimraf": "^6.0.1"
31
}
32
}
···
4
"private": true,
5
"scripts": {
6
"build": "bun --bun run check-types && bun --bun run compile",
7
+
"dev": "bun run --hot src/index.ts | pino-pretty",
8
"check-types": "tsc --noEmit",
9
"compile": "bun build src/index.ts --compile --minify --sourcemap --outfile=dist/api --target=bun",
10
"clean": "rimraf dist"
···
20
"@cookware/lexicons": "workspace:*",
21
"@libsql/client": "^0.14.0",
22
"drizzle-orm": "catalog:",
23
+
"hono": "^4.10.7",
24
"pino": "^9.5.0"
25
},
26
"devDependencies": {
···
28
"@cookware/tsconfig": "workspace:*",
29
"@types/bun": "catalog:",
30
"drizzle-kit": "^0.29.0",
31
+
"pino-pretty": "^13.1.2",
32
"rimraf": "^6.0.1"
33
}
34
}
+15
-6
apps/api/src/index.ts
+15
-6
apps/api/src/index.ts
···
6
import pino from 'pino';
7
import { RedisClient } from 'bun';
8
import { registerGetProfile } from './xrpc/blue.recipes.actor.getProfile.js';
9
10
const logger = pino();
11
const redis = new RedisClient(Bun.env.REDIS_URL ?? "redis://127.0.0.1:6379/0");
12
13
-
const router = new XRPCRouter({
14
handleException: (err, _req) => {
15
if (err instanceof XRPCError) {
16
return err.toResponse();
···
34
});
35
36
// actor
37
-
registerGetProfile(router, logger, redis);
38
39
// feed
40
-
registerGetRecipes(router, logger, redis);
41
-
registerGetRecipe(router, logger, redis);
42
43
const server = Bun.serve({
44
port: process.env.PORT || 3000,
45
-
...router
46
});
47
48
-
console.log(`Server running on http://localhost:${server.port}`);
···
6
import pino from 'pino';
7
import { RedisClient } from 'bun';
8
import { registerGetProfile } from './xrpc/blue.recipes.actor.getProfile.js';
9
+
import { Hono } from 'hono';
10
+
import { mountXrpcRouter } from './util/hono.js';
11
12
const logger = pino();
13
const redis = new RedisClient(Bun.env.REDIS_URL ?? "redis://127.0.0.1:6379/0");
14
15
+
const xrpcRouter = new XRPCRouter({
16
handleException: (err, _req) => {
17
if (err instanceof XRPCError) {
18
return err.toResponse();
···
36
});
37
38
// actor
39
+
registerGetProfile(xrpcRouter, logger, redis);
40
41
// feed
42
+
registerGetRecipes(xrpcRouter, logger, redis);
43
+
registerGetRecipe(xrpcRouter, logger, redis);
44
+
45
+
const app = new Hono();
46
+
47
+
// mount xrpc router at /xrpc
48
+
const xrpcApp = new Hono();
49
+
mountXrpcRouter(xrpcApp, xrpcRouter);
50
+
app.route('/xrpc', xrpcApp);
51
52
const server = Bun.serve({
53
port: process.env.PORT || 3000,
54
+
fetch: app.fetch,
55
});
56
57
+
logger.info({ url: server.url.toString() }, `Recipes.blue API started up`);
+40
apps/api/src/util/hono.ts
+40
apps/api/src/util/hono.ts
···
···
1
+
import { XRPCRouter } from '@atcute/xrpc-server';
2
+
import type { Context, Hono } from 'hono';
3
+
4
+
export type ApiContext = {};
5
+
6
+
/**
7
+
* mounts an @atcute/xrpc-server router into hono as a nested route
8
+
*
9
+
* basically just bridges the two request handlers since both are
10
+
* web standard Request/Response. you can optionally pass hono context
11
+
* properties to xrpc handlers via the request object
12
+
*/
13
+
export const mountXrpcRouter = (
14
+
app: Hono,
15
+
router: XRPCRouter,
16
+
injectContext?: (c: Context) => ApiContext,
17
+
) => {
18
+
app.all('*', async (c) => {
19
+
let request = c.req.raw;
20
+
21
+
// if context injector provided, attach properties to request
22
+
if (injectContext) {
23
+
const contextData = injectContext(c);
24
+
request = Object.assign(request, contextData);
25
+
}
26
+
27
+
const response = await router.fetch(request);
28
+
return response;
29
+
});
30
+
};
31
+
32
+
/**
33
+
* helper to extract injected context from xrpc request
34
+
* use this in your xrpc handlers to access hono context data
35
+
*/
36
+
export const getInjectedContext = (
37
+
request: Request
38
+
): ApiContext => {
39
+
return request as any as ApiContext;
40
+
};
+21
-1
bun.lock
+21
-1
bun.lock
···
22
"@cookware/lexicons": "workspace:*",
23
"@libsql/client": "^0.14.0",
24
"drizzle-orm": "catalog:",
25
"pino": "^9.5.0",
26
},
27
"devDependencies": {
···
29
"@cookware/tsconfig": "workspace:*",
30
"@types/bun": "catalog:",
31
"drizzle-kit": "^0.29.0",
32
"rimraf": "^6.0.1",
33
},
34
},
···
126
"dependencies": {
127
"@libsql/client": "^0.15.15",
128
"drizzle-orm": "catalog:",
129
"zod": "^3.23.8",
130
},
131
"devDependencies": {
···
135
"@cookware/tsconfig": "workspace:*",
136
"@types/bun": "catalog:",
137
"@types/node": "^22.10.1",
138
"drizzle-kit": "^0.29.0",
139
"typescript": "^5.2.2",
140
},
···
817
818
"@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="],
819
820
-
"@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="],
821
822
"@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="],
823
···
1171
1172
"help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
1173
1174
"iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
1175
1176
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
···
1333
1334
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
1335
1336
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
1337
1338
"pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="],
1339
1340
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
1341
1342
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
1343
···
1715
1716
"@opentelemetry/instrumentation-pg/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="],
1717
1718
"@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1719
1720
"@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
···
1770
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1771
1772
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
1773
1774
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
1775
···
22
"@cookware/lexicons": "workspace:*",
23
"@libsql/client": "^0.14.0",
24
"drizzle-orm": "catalog:",
25
+
"hono": "^4.10.7",
26
"pino": "^9.5.0",
27
},
28
"devDependencies": {
···
30
"@cookware/tsconfig": "workspace:*",
31
"@types/bun": "catalog:",
32
"drizzle-kit": "^0.29.0",
33
+
"pino-pretty": "^13.1.2",
34
"rimraf": "^6.0.1",
35
},
36
},
···
128
"dependencies": {
129
"@libsql/client": "^0.15.15",
130
"drizzle-orm": "catalog:",
131
+
"pg": "^8.16.3",
132
"zod": "^3.23.8",
133
},
134
"devDependencies": {
···
138
"@cookware/tsconfig": "workspace:*",
139
"@types/bun": "catalog:",
140
"@types/node": "^22.10.1",
141
+
"@types/pg": "^8.15.6",
142
"drizzle-kit": "^0.29.0",
143
"typescript": "^5.2.2",
144
},
···
821
822
"@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="],
823
824
+
"@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="],
825
826
"@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="],
827
···
1175
1176
"help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
1177
1178
+
"hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
1179
+
1180
"iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
1181
1182
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
···
1339
1340
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
1341
1342
+
"pg": ["pg@8.16.3", "", { "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw=="],
1343
+
1344
+
"pg-cloudflare": ["pg-cloudflare@1.2.7", "", {}, "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg=="],
1345
+
1346
+
"pg-connection-string": ["pg-connection-string@2.9.1", "", {}, "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w=="],
1347
+
1348
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
1349
1350
+
"pg-pool": ["pg-pool@3.10.1", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg=="],
1351
+
1352
"pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="],
1353
1354
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
1355
+
1356
+
"pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
1357
1358
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
1359
···
1731
1732
"@opentelemetry/instrumentation-pg/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="],
1733
1734
+
"@opentelemetry/instrumentation-pg/@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="],
1735
+
1736
"@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1737
1738
"@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
···
1788
"@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1789
1790
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
1791
+
1792
+
"@types/pg-pool/@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="],
1793
1794
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
1795
+13
config/dev/db/compose.yaml
+13
config/dev/db/compose.yaml
-13
config/dev/libsql/compose.yaml
-13
config/dev/libsql/compose.yaml
+12
config/dev/redis/compose.yaml
+12
config/dev/redis/compose.yaml
+2
-32
docker-compose.yaml
+2
-32
docker-compose.yaml
···
1
---
2
include:
3
-
- path: config/dev/caddy/compose.yaml
4
-
- path: config/dev/libsql/compose.yaml
5
6
networks:
7
recipesblue:
8
-
9
-
services:
10
-
redis:
11
-
image: redis:8
12
-
ports: [6379:6379]
13
-
14
-
api:
15
-
build:
16
-
context: .
17
-
dockerfile: apps/api/Dockerfile
18
-
restart: unless-stopped
19
-
networks: [recipesblue]
20
-
ports:
21
-
- "3000:3000"
22
-
environment:
23
-
- DATABASE_URL=http://libsql:8080
24
-
- PORT=3000
25
-
depends_on:
26
-
- libsql
27
-
28
-
ingester:
29
-
build:
30
-
context: .
31
-
dockerfile: apps/ingester/Dockerfile
32
-
restart: unless-stopped
33
-
networks: [recipesblue]
34
-
environment:
35
-
- DATABASE_URL=http://libsql:8080
36
-
depends_on:
37
-
- libsql
+2
-3
libs/database/drizzle.config.ts
+2
-3
libs/database/drizzle.config.ts
+5
-6
libs/database/lib/index.ts
+5
-6
libs/database/lib/index.ts
···
1
-
import { drizzle } from 'drizzle-orm/libsql';
2
-
import { createClient } from '@libsql/client';
3
4
-
const client = createClient({
5
-
url: process.env.TURSO_CONNECTION_URL || 'http://localhost:4001',
6
-
authToken: process.env.TURSO_AUTH_TOKEN || '',
7
});
8
9
import * as schema from './schema.js';
10
-
export const db = drizzle(client, { schema });
11
12
// Re-export drizzle-orm functions to ensure single instance
13
export { eq, and, or, desc, asc, sql } from 'drizzle-orm';
···
1
+
import { drizzle } from 'drizzle-orm/node-postgres';
2
+
import { Pool } from 'pg';
3
4
+
const pool = new Pool({
5
+
connectionString: process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/postgres',
6
});
7
8
import * as schema from './schema.js';
9
+
export const db = drizzle(pool, { schema });
10
11
// Re-export drizzle-orm functions to ensure single instance
12
export { eq, and, or, desc, asc, sql } from 'drizzle-orm';
+14
-13
libs/database/lib/schema.ts
+14
-13
libs/database/lib/schema.ts
···
1
-
import { customType, index, int, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
2
-
import { BlueRecipesFeedRecipe, BlueRecipesActorProfile } from "@cookware/lexicons";
3
import { Cid, isCid, ResourceUri, type AtprotoDid } from "@atcute/lexicons/syntax";
4
import { Blob, LegacyBlob } from "@atcute/lexicons";
5
import { relations, sql, type SQL } from "drizzle-orm";
···
53
},
54
});
55
56
-
export const profilesTable = sqliteTable("profiles", {
57
uri: text('uri')
58
.generatedAlwaysAs((): SQL => sql`'at://' || ${profilesTable.did} || '/blue.recipes.actor.profile/self'`)
59
.$type<ResourceUri>(),
60
cid: text("cid").$type<Cid>().notNull(),
61
did: text("did").$type<AtprotoDid>().notNull().primaryKey(),
62
ingestedAt: dateIsoText("ingested_at").notNull().default(sql`CURRENT_TIMESTAMP`),
63
64
-
displayName: text('display_name', { length: 640 }).notNull(),
65
-
description: text('description', { length: 2500 }),
66
-
pronouns: text('pronouns', { length: 200 }),
67
website: text('website'),
68
avatarRef: atBlob('avatar'),
69
bannerRef: atBlob('banner'),
···
74
index('profiles_iat_idx').on(t.ingestedAt),
75
]));
76
77
-
export const recipeTable = sqliteTable("recipes", {
78
uri: text('uri')
79
.generatedAlwaysAs((): SQL => sql`'at://' || ${recipeTable.did} || '/blue.recipes.feed.recipe/' || ${recipeTable.rkey}`),
80
···
88
imageRef: atBlob('image'),
89
90
title: text('title').notNull(),
91
-
time: int('time').notNull().default(0),
92
-
serves: int('serves'),
93
description: text('description'),
94
95
-
ingredients: text('ingredients', { mode: 'json' }).$type<BlueRecipesFeedRecipe.Main['ingredients']>().notNull(),
96
-
ingredientsCount: int('ingredients_count').generatedAlwaysAs((): SQL => sql`json_array_length(${recipeTable.ingredients})`),
97
98
-
steps: text('steps', { mode: 'json' }).$type<BlueRecipesFeedRecipe.Main['steps']>().notNull(),
99
-
stepsCount: int('steps_count').generatedAlwaysAs((): SQL => sql`json_array_length(${recipeTable.steps})`),
100
101
createdAt: dateIsoText("created_at").notNull(),
102
ingestedAt: dateIsoText("ingested_at").notNull().default(sql`CURRENT_TIMESTAMP`),
···
1
+
import { customType, index, integer, primaryKey, pgTable, text, jsonb, varchar } from "drizzle-orm/pg-core";
2
+
import { BlueRecipesFeedRecipe } from "@cookware/lexicons";
3
import { Cid, isCid, ResourceUri, type AtprotoDid } from "@atcute/lexicons/syntax";
4
import { Blob, LegacyBlob } from "@atcute/lexicons";
5
import { relations, sql, type SQL } from "drizzle-orm";
···
53
},
54
});
55
56
+
export const profilesTable = pgTable("profiles", {
57
uri: text('uri')
58
.generatedAlwaysAs((): SQL => sql`'at://' || ${profilesTable.did} || '/blue.recipes.actor.profile/self'`)
59
.$type<ResourceUri>(),
60
+
61
cid: text("cid").$type<Cid>().notNull(),
62
did: text("did").$type<AtprotoDid>().notNull().primaryKey(),
63
ingestedAt: dateIsoText("ingested_at").notNull().default(sql`CURRENT_TIMESTAMP`),
64
65
+
displayName: varchar('display_name', { length: 640 }).notNull(),
66
+
description: varchar('description', { length: 2500 }),
67
+
pronouns: varchar('pronouns', { length: 200 }),
68
website: text('website'),
69
avatarRef: atBlob('avatar'),
70
bannerRef: atBlob('banner'),
···
75
index('profiles_iat_idx').on(t.ingestedAt),
76
]));
77
78
+
export const recipeTable = pgTable("recipes", {
79
uri: text('uri')
80
.generatedAlwaysAs((): SQL => sql`'at://' || ${recipeTable.did} || '/blue.recipes.feed.recipe/' || ${recipeTable.rkey}`),
81
···
89
imageRef: atBlob('image'),
90
91
title: text('title').notNull(),
92
+
time: integer('time').notNull().default(0),
93
+
serves: integer('serves'),
94
description: text('description'),
95
96
+
ingredients: jsonb('ingredients').$type<BlueRecipesFeedRecipe.Main['ingredients']>().notNull(),
97
+
ingredientsCount: integer('ingredients_count').generatedAlwaysAs((): SQL => sql`jsonb_array_length(${recipeTable.ingredients})`),
98
99
+
steps: jsonb('steps').$type<BlueRecipesFeedRecipe.Main['steps']>().notNull(),
100
+
stepsCount: integer('steps_count').generatedAlwaysAs((): SQL => sql`jsonb_array_length(${recipeTable.steps})`),
101
102
createdAt: dateIsoText("created_at").notNull(),
103
ingestedAt: dateIsoText("ingested_at").notNull().default(sql`CURRENT_TIMESTAMP`),
-16
libs/database/migrations/0000_kind_ultron.sql
-16
libs/database/migrations/0000_kind_ultron.sql
···
1
-
CREATE TABLE `recipes` (
2
-
`uri` text GENERATED ALWAYS AS ("author_did" || '/' || "rkey") VIRTUAL,
3
-
`author_did` text NOT NULL,
4
-
`rkey` text NOT NULL,
5
-
`image_ref` text,
6
-
`title` text NOT NULL,
7
-
`time` integer DEFAULT 0 NOT NULL,
8
-
`serves` integer,
9
-
`description` text,
10
-
`ingredients` text NOT NULL,
11
-
`ingredients_count` integer GENERATED ALWAYS AS (json_array_length("ingredients")) VIRTUAL,
12
-
`steps` text NOT NULL,
13
-
`steps_count` integer GENERATED ALWAYS AS (json_array_length("steps")) VIRTUAL,
14
-
`created_at` text NOT NULL,
15
-
PRIMARY KEY(`author_did`, `rkey`)
16
-
);
···
+46
libs/database/migrations/0000_young_hellcat.sql
+46
libs/database/migrations/0000_young_hellcat.sql
···
···
1
+
CREATE TABLE IF NOT EXISTS "profiles" (
2
+
"uri" text GENERATED ALWAYS AS ('at://' || "profiles"."did" || '/blue.recipes.actor.profile/self') STORED,
3
+
"cid" text NOT NULL,
4
+
"did" text PRIMARY KEY NOT NULL,
5
+
"ingested_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
6
+
"display_name" varchar(640) NOT NULL,
7
+
"description" varchar(2500),
8
+
"pronouns" varchar(200),
9
+
"website" text,
10
+
"avatar" text,
11
+
"banner" text,
12
+
"created_at" text NOT NULL
13
+
);
14
+
--> statement-breakpoint
15
+
CREATE TABLE IF NOT EXISTS "recipes" (
16
+
"uri" text GENERATED ALWAYS AS ('at://' || "recipes"."author_did" || '/blue.recipes.feed.recipe/' || "recipes"."rkey") STORED,
17
+
"cid" text NOT NULL,
18
+
"author_did" text NOT NULL,
19
+
"rkey" text NOT NULL,
20
+
"image" text,
21
+
"title" text NOT NULL,
22
+
"time" integer DEFAULT 0 NOT NULL,
23
+
"serves" integer,
24
+
"description" text,
25
+
"ingredients" jsonb NOT NULL,
26
+
"ingredients_count" integer GENERATED ALWAYS AS (jsonb_array_length("recipes"."ingredients")) STORED,
27
+
"steps" jsonb NOT NULL,
28
+
"steps_count" integer GENERATED ALWAYS AS (jsonb_array_length("recipes"."steps")) STORED,
29
+
"created_at" text NOT NULL,
30
+
"ingested_at" text DEFAULT CURRENT_TIMESTAMP NOT NULL,
31
+
CONSTRAINT "recipes_author_did_rkey_pk" PRIMARY KEY("author_did","rkey")
32
+
);
33
+
--> statement-breakpoint
34
+
DO $$ BEGIN
35
+
ALTER TABLE "recipes" ADD CONSTRAINT "recipes_author_did_profiles_did_fk" FOREIGN KEY ("author_did") REFERENCES "public"."profiles"("did") ON DELETE cascade ON UPDATE no action;
36
+
EXCEPTION
37
+
WHEN duplicate_object THEN null;
38
+
END $$;
39
+
--> statement-breakpoint
40
+
CREATE INDEX IF NOT EXISTS "profiles_cid_idx" ON "profiles" USING btree ("cid");--> statement-breakpoint
41
+
CREATE INDEX IF NOT EXISTS "profiles_cat_idx" ON "profiles" USING btree ("created_at");--> statement-breakpoint
42
+
CREATE INDEX IF NOT EXISTS "profiles_iat_idx" ON "profiles" USING btree ("ingested_at");--> statement-breakpoint
43
+
CREATE INDEX IF NOT EXISTS "recipes_title_idx" ON "recipes" USING btree ("title");--> statement-breakpoint
44
+
CREATE INDEX IF NOT EXISTS "recipes_cid_idx" ON "recipes" USING btree ("cid");--> statement-breakpoint
45
+
CREATE INDEX IF NOT EXISTS "recipes_cat_idx" ON "recipes" USING btree ("created_at");--> statement-breakpoint
46
+
CREATE INDEX IF NOT EXISTS "recipes_iat_idx" ON "recipes" USING btree ("ingested_at");
-23
libs/database/migrations/0001_past_umar.sql
-23
libs/database/migrations/0001_past_umar.sql
···
1
-
ALTER TABLE `recipes` RENAME COLUMN "image_ref" TO "image";--> statement-breakpoint
2
-
CREATE TABLE `profiles` (
3
-
`uri` text GENERATED ALWAYS AS ('at://' || "did" || '/?/self') VIRTUAL,
4
-
`did` text PRIMARY KEY NOT NULL,
5
-
`ingested_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
6
-
`display_name` text(640) NOT NULL,
7
-
`description` text(2500),
8
-
`pronouns` text(200),
9
-
`website` text,
10
-
`avatar` text,
11
-
`banner` text,
12
-
`created_at` text NOT NULL
13
-
);
14
-
--> statement-breakpoint
15
-
CREATE INDEX `profiles_cat_idx` ON `profiles` (`created_at`);--> statement-breakpoint
16
-
CREATE INDEX `profiles_iat_idx` ON `profiles` (`ingested_at`);--> statement-breakpoint
17
-
ALTER TABLE `recipes` DROP COLUMN `uri`;--> statement-breakpoint
18
-
ALTER TABLE `recipes` ADD `uri` text GENERATED ALWAYS AS ('at://' || "author_did" || '/?/' || "rkey") VIRTUAL;--> statement-breakpoint
19
-
ALTER TABLE `recipes` ADD `ingested_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL;--> statement-breakpoint
20
-
CREATE INDEX `recipes_title_idx` ON `recipes` (`title`);--> statement-breakpoint
21
-
CREATE INDEX `recipes_cat_idx` ON `recipes` (`created_at`);--> statement-breakpoint
22
-
CREATE INDEX `recipes_iat_idx` ON `recipes` (`ingested_at`);--> statement-breakpoint
23
-
ALTER TABLE `recipes` ALTER COLUMN "author_did" TO "author_did" text NOT NULL REFERENCES profiles(did) ON DELETE cascade ON UPDATE no action;
···
-4
libs/database/migrations/0002_cheerful_venom.sql
-4
libs/database/migrations/0002_cheerful_venom.sql
···
1
-
ALTER TABLE `profiles` DROP COLUMN `uri`;--> statement-breakpoint
2
-
ALTER TABLE `profiles` ADD `uri` text GENERATED ALWAYS AS ('at://' || "did" || '/blue.recipes.actor.profile/self') VIRTUAL;--> statement-breakpoint
3
-
ALTER TABLE `recipes` DROP COLUMN `uri`;--> statement-breakpoint
4
-
ALTER TABLE `recipes` ADD `uri` text GENERATED ALWAYS AS ('at://' || "author_did" || '/blue.recipes.feed.recipe/' || "rkey") VIRTUAL;
···
-4
libs/database/migrations/0003_long_blue_marvel.sql
-4
libs/database/migrations/0003_long_blue_marvel.sql
+255
-46
libs/database/migrations/meta/0000_snapshot.json
+255
-46
libs/database/migrations/meta/0000_snapshot.json
···
1
{
2
-
"version": "6",
3
-
"dialect": "sqlite",
4
-
"id": "7b2675f9-5d97-4fac-983e-978efd250faf",
5
"prevId": "00000000-0000-0000-0000-000000000000",
6
"tables": {
7
-
"recipes": {
8
"name": "recipes",
9
"columns": {
10
"uri": {
11
"name": "uri",
12
"type": "text",
13
"primaryKey": false,
14
"notNull": false,
15
-
"autoincrement": false,
16
"generated": {
17
-
"as": "(\"author_did\" || '/' || \"rkey\")",
18
-
"type": "virtual"
19
}
20
},
21
"author_did": {
22
"name": "author_did",
23
"type": "text",
24
"primaryKey": false,
25
-
"notNull": true,
26
-
"autoincrement": false
27
},
28
"rkey": {
29
"name": "rkey",
30
"type": "text",
31
"primaryKey": false,
32
-
"notNull": true,
33
-
"autoincrement": false
34
},
35
-
"image_ref": {
36
-
"name": "image_ref",
37
"type": "text",
38
"primaryKey": false,
39
-
"notNull": false,
40
-
"autoincrement": false
41
},
42
"title": {
43
"name": "title",
44
"type": "text",
45
"primaryKey": false,
46
-
"notNull": true,
47
-
"autoincrement": false
48
},
49
"time": {
50
"name": "time",
51
"type": "integer",
52
"primaryKey": false,
53
"notNull": true,
54
-
"autoincrement": false,
55
"default": 0
56
},
57
"serves": {
58
"name": "serves",
59
"type": "integer",
60
"primaryKey": false,
61
-
"notNull": false,
62
-
"autoincrement": false
63
},
64
"description": {
65
"name": "description",
66
"type": "text",
67
"primaryKey": false,
68
-
"notNull": false,
69
-
"autoincrement": false
70
},
71
"ingredients": {
72
"name": "ingredients",
73
-
"type": "text",
74
"primaryKey": false,
75
-
"notNull": true,
76
-
"autoincrement": false
77
},
78
"ingredients_count": {
79
"name": "ingredients_count",
80
"type": "integer",
81
"primaryKey": false,
82
"notNull": false,
83
-
"autoincrement": false,
84
"generated": {
85
-
"as": "(json_array_length(\"ingredients\"))",
86
-
"type": "virtual"
87
}
88
},
89
"steps": {
90
"name": "steps",
91
-
"type": "text",
92
"primaryKey": false,
93
-
"notNull": true,
94
-
"autoincrement": false
95
},
96
"steps_count": {
97
"name": "steps_count",
98
"type": "integer",
99
"primaryKey": false,
100
"notNull": false,
101
-
"autoincrement": false,
102
"generated": {
103
-
"as": "(json_array_length(\"steps\"))",
104
-
"type": "virtual"
105
}
106
},
107
"created_at": {
108
"name": "created_at",
109
"type": "text",
110
"primaryKey": false,
111
"notNull": true,
112
-
"autoincrement": false
113
}
114
},
115
-
"indexes": {},
116
-
"foreignKeys": {},
117
"compositePrimaryKeys": {
118
"recipes_author_did_rkey_pk": {
119
"columns": [
120
"author_did",
121
"rkey"
122
-
],
123
-
"name": "recipes_author_did_rkey_pk"
124
}
125
},
126
"uniqueConstraints": {},
127
-
"checkConstraints": {}
128
}
129
},
130
-
"views": {},
131
"enums": {},
132
"_meta": {
133
"schemas": {},
134
-
"tables": {},
135
-
"columns": {}
136
-
},
137
-
"internal": {
138
-
"indexes": {}
139
}
140
}
···
1
{
2
+
"id": "5d896b70-087b-421e-8d94-c100476cd926",
3
"prevId": "00000000-0000-0000-0000-000000000000",
4
+
"version": "7",
5
+
"dialect": "postgresql",
6
"tables": {
7
+
"public.profiles": {
8
+
"name": "profiles",
9
+
"schema": "",
10
+
"columns": {
11
+
"uri": {
12
+
"name": "uri",
13
+
"type": "text",
14
+
"primaryKey": false,
15
+
"notNull": false,
16
+
"generated": {
17
+
"as": "'at://' || \"profiles\".\"did\" || '/blue.recipes.actor.profile/self'",
18
+
"type": "stored"
19
+
}
20
+
},
21
+
"cid": {
22
+
"name": "cid",
23
+
"type": "text",
24
+
"primaryKey": false,
25
+
"notNull": true
26
+
},
27
+
"did": {
28
+
"name": "did",
29
+
"type": "text",
30
+
"primaryKey": true,
31
+
"notNull": true
32
+
},
33
+
"ingested_at": {
34
+
"name": "ingested_at",
35
+
"type": "text",
36
+
"primaryKey": false,
37
+
"notNull": true,
38
+
"default": "CURRENT_TIMESTAMP"
39
+
},
40
+
"display_name": {
41
+
"name": "display_name",
42
+
"type": "varchar(640)",
43
+
"primaryKey": false,
44
+
"notNull": true
45
+
},
46
+
"description": {
47
+
"name": "description",
48
+
"type": "varchar(2500)",
49
+
"primaryKey": false,
50
+
"notNull": false
51
+
},
52
+
"pronouns": {
53
+
"name": "pronouns",
54
+
"type": "varchar(200)",
55
+
"primaryKey": false,
56
+
"notNull": false
57
+
},
58
+
"website": {
59
+
"name": "website",
60
+
"type": "text",
61
+
"primaryKey": false,
62
+
"notNull": false
63
+
},
64
+
"avatar": {
65
+
"name": "avatar",
66
+
"type": "text",
67
+
"primaryKey": false,
68
+
"notNull": false
69
+
},
70
+
"banner": {
71
+
"name": "banner",
72
+
"type": "text",
73
+
"primaryKey": false,
74
+
"notNull": false
75
+
},
76
+
"created_at": {
77
+
"name": "created_at",
78
+
"type": "text",
79
+
"primaryKey": false,
80
+
"notNull": true
81
+
}
82
+
},
83
+
"indexes": {
84
+
"profiles_cid_idx": {
85
+
"name": "profiles_cid_idx",
86
+
"columns": [
87
+
{
88
+
"expression": "cid",
89
+
"isExpression": false,
90
+
"asc": true,
91
+
"nulls": "last"
92
+
}
93
+
],
94
+
"isUnique": false,
95
+
"concurrently": false,
96
+
"method": "btree",
97
+
"with": {}
98
+
},
99
+
"profiles_cat_idx": {
100
+
"name": "profiles_cat_idx",
101
+
"columns": [
102
+
{
103
+
"expression": "created_at",
104
+
"isExpression": false,
105
+
"asc": true,
106
+
"nulls": "last"
107
+
}
108
+
],
109
+
"isUnique": false,
110
+
"concurrently": false,
111
+
"method": "btree",
112
+
"with": {}
113
+
},
114
+
"profiles_iat_idx": {
115
+
"name": "profiles_iat_idx",
116
+
"columns": [
117
+
{
118
+
"expression": "ingested_at",
119
+
"isExpression": false,
120
+
"asc": true,
121
+
"nulls": "last"
122
+
}
123
+
],
124
+
"isUnique": false,
125
+
"concurrently": false,
126
+
"method": "btree",
127
+
"with": {}
128
+
}
129
+
},
130
+
"foreignKeys": {},
131
+
"compositePrimaryKeys": {},
132
+
"uniqueConstraints": {},
133
+
"policies": {},
134
+
"checkConstraints": {},
135
+
"isRLSEnabled": false
136
+
},
137
+
"public.recipes": {
138
"name": "recipes",
139
+
"schema": "",
140
"columns": {
141
"uri": {
142
"name": "uri",
143
"type": "text",
144
"primaryKey": false,
145
"notNull": false,
146
"generated": {
147
+
"as": "'at://' || \"recipes\".\"author_did\" || '/blue.recipes.feed.recipe/' || \"recipes\".\"rkey\"",
148
+
"type": "stored"
149
}
150
},
151
+
"cid": {
152
+
"name": "cid",
153
+
"type": "text",
154
+
"primaryKey": false,
155
+
"notNull": true
156
+
},
157
"author_did": {
158
"name": "author_did",
159
"type": "text",
160
"primaryKey": false,
161
+
"notNull": true
162
},
163
"rkey": {
164
"name": "rkey",
165
"type": "text",
166
"primaryKey": false,
167
+
"notNull": true
168
},
169
+
"image": {
170
+
"name": "image",
171
"type": "text",
172
"primaryKey": false,
173
+
"notNull": false
174
},
175
"title": {
176
"name": "title",
177
"type": "text",
178
"primaryKey": false,
179
+
"notNull": true
180
},
181
"time": {
182
"name": "time",
183
"type": "integer",
184
"primaryKey": false,
185
"notNull": true,
186
"default": 0
187
},
188
"serves": {
189
"name": "serves",
190
"type": "integer",
191
"primaryKey": false,
192
+
"notNull": false
193
},
194
"description": {
195
"name": "description",
196
"type": "text",
197
"primaryKey": false,
198
+
"notNull": false
199
},
200
"ingredients": {
201
"name": "ingredients",
202
+
"type": "jsonb",
203
"primaryKey": false,
204
+
"notNull": true
205
},
206
"ingredients_count": {
207
"name": "ingredients_count",
208
"type": "integer",
209
"primaryKey": false,
210
"notNull": false,
211
"generated": {
212
+
"as": "jsonb_array_length(\"recipes\".\"ingredients\")",
213
+
"type": "stored"
214
}
215
},
216
"steps": {
217
"name": "steps",
218
+
"type": "jsonb",
219
"primaryKey": false,
220
+
"notNull": true
221
},
222
"steps_count": {
223
"name": "steps_count",
224
"type": "integer",
225
"primaryKey": false,
226
"notNull": false,
227
"generated": {
228
+
"as": "jsonb_array_length(\"recipes\".\"steps\")",
229
+
"type": "stored"
230
}
231
},
232
"created_at": {
233
"name": "created_at",
234
+
"type": "text",
235
+
"primaryKey": false,
236
+
"notNull": true
237
+
},
238
+
"ingested_at": {
239
+
"name": "ingested_at",
240
"type": "text",
241
"primaryKey": false,
242
"notNull": true,
243
+
"default": "CURRENT_TIMESTAMP"
244
}
245
},
246
+
"indexes": {
247
+
"recipes_title_idx": {
248
+
"name": "recipes_title_idx",
249
+
"columns": [
250
+
{
251
+
"expression": "title",
252
+
"isExpression": false,
253
+
"asc": true,
254
+
"nulls": "last"
255
+
}
256
+
],
257
+
"isUnique": false,
258
+
"concurrently": false,
259
+
"method": "btree",
260
+
"with": {}
261
+
},
262
+
"recipes_cid_idx": {
263
+
"name": "recipes_cid_idx",
264
+
"columns": [
265
+
{
266
+
"expression": "cid",
267
+
"isExpression": false,
268
+
"asc": true,
269
+
"nulls": "last"
270
+
}
271
+
],
272
+
"isUnique": false,
273
+
"concurrently": false,
274
+
"method": "btree",
275
+
"with": {}
276
+
},
277
+
"recipes_cat_idx": {
278
+
"name": "recipes_cat_idx",
279
+
"columns": [
280
+
{
281
+
"expression": "created_at",
282
+
"isExpression": false,
283
+
"asc": true,
284
+
"nulls": "last"
285
+
}
286
+
],
287
+
"isUnique": false,
288
+
"concurrently": false,
289
+
"method": "btree",
290
+
"with": {}
291
+
},
292
+
"recipes_iat_idx": {
293
+
"name": "recipes_iat_idx",
294
+
"columns": [
295
+
{
296
+
"expression": "ingested_at",
297
+
"isExpression": false,
298
+
"asc": true,
299
+
"nulls": "last"
300
+
}
301
+
],
302
+
"isUnique": false,
303
+
"concurrently": false,
304
+
"method": "btree",
305
+
"with": {}
306
+
}
307
+
},
308
+
"foreignKeys": {
309
+
"recipes_author_did_profiles_did_fk": {
310
+
"name": "recipes_author_did_profiles_did_fk",
311
+
"tableFrom": "recipes",
312
+
"tableTo": "profiles",
313
+
"columnsFrom": [
314
+
"author_did"
315
+
],
316
+
"columnsTo": [
317
+
"did"
318
+
],
319
+
"onDelete": "cascade",
320
+
"onUpdate": "no action"
321
+
}
322
+
},
323
"compositePrimaryKeys": {
324
"recipes_author_did_rkey_pk": {
325
+
"name": "recipes_author_did_rkey_pk",
326
"columns": [
327
"author_did",
328
"rkey"
329
+
]
330
}
331
},
332
"uniqueConstraints": {},
333
+
"policies": {},
334
+
"checkConstraints": {},
335
+
"isRLSEnabled": false
336
}
337
},
338
"enums": {},
339
+
"schemas": {},
340
+
"sequences": {},
341
+
"roles": {},
342
+
"policies": {},
343
+
"views": {},
344
"_meta": {
345
+
"columns": {},
346
"schemas": {},
347
+
"tables": {}
348
}
349
}
-286
libs/database/migrations/meta/0001_snapshot.json
-286
libs/database/migrations/meta/0001_snapshot.json
···
1
-
{
2
-
"version": "6",
3
-
"dialect": "sqlite",
4
-
"id": "d6f06b7d-9822-43ee-b96c-3b980a5e4953",
5
-
"prevId": "7b2675f9-5d97-4fac-983e-978efd250faf",
6
-
"tables": {
7
-
"profiles": {
8
-
"name": "profiles",
9
-
"columns": {
10
-
"uri": {
11
-
"name": "uri",
12
-
"type": "text",
13
-
"primaryKey": false,
14
-
"notNull": false,
15
-
"autoincrement": false,
16
-
"generated": {
17
-
"as": "('at://' || \"did\" || '/?/self')",
18
-
"type": "virtual"
19
-
}
20
-
},
21
-
"did": {
22
-
"name": "did",
23
-
"type": "text",
24
-
"primaryKey": true,
25
-
"notNull": true,
26
-
"autoincrement": false
27
-
},
28
-
"ingested_at": {
29
-
"name": "ingested_at",
30
-
"type": "text",
31
-
"primaryKey": false,
32
-
"notNull": true,
33
-
"autoincrement": false,
34
-
"default": "CURRENT_TIMESTAMP"
35
-
},
36
-
"display_name": {
37
-
"name": "display_name",
38
-
"type": "text(640)",
39
-
"primaryKey": false,
40
-
"notNull": true,
41
-
"autoincrement": false
42
-
},
43
-
"description": {
44
-
"name": "description",
45
-
"type": "text(2500)",
46
-
"primaryKey": false,
47
-
"notNull": false,
48
-
"autoincrement": false
49
-
},
50
-
"pronouns": {
51
-
"name": "pronouns",
52
-
"type": "text(200)",
53
-
"primaryKey": false,
54
-
"notNull": false,
55
-
"autoincrement": false
56
-
},
57
-
"website": {
58
-
"name": "website",
59
-
"type": "text",
60
-
"primaryKey": false,
61
-
"notNull": false,
62
-
"autoincrement": false
63
-
},
64
-
"avatar": {
65
-
"name": "avatar",
66
-
"type": "text",
67
-
"primaryKey": false,
68
-
"notNull": false,
69
-
"autoincrement": false
70
-
},
71
-
"banner": {
72
-
"name": "banner",
73
-
"type": "text",
74
-
"primaryKey": false,
75
-
"notNull": false,
76
-
"autoincrement": false
77
-
},
78
-
"created_at": {
79
-
"name": "created_at",
80
-
"type": "text",
81
-
"primaryKey": false,
82
-
"notNull": true,
83
-
"autoincrement": false
84
-
}
85
-
},
86
-
"indexes": {
87
-
"profiles_cat_idx": {
88
-
"name": "profiles_cat_idx",
89
-
"columns": [
90
-
"created_at"
91
-
],
92
-
"isUnique": false
93
-
},
94
-
"profiles_iat_idx": {
95
-
"name": "profiles_iat_idx",
96
-
"columns": [
97
-
"ingested_at"
98
-
],
99
-
"isUnique": false
100
-
}
101
-
},
102
-
"foreignKeys": {},
103
-
"compositePrimaryKeys": {},
104
-
"uniqueConstraints": {},
105
-
"checkConstraints": {}
106
-
},
107
-
"recipes": {
108
-
"name": "recipes",
109
-
"columns": {
110
-
"uri": {
111
-
"name": "uri",
112
-
"type": "text",
113
-
"primaryKey": false,
114
-
"notNull": false,
115
-
"autoincrement": false,
116
-
"generated": {
117
-
"as": "('at://' || \"author_did\" || '/?/' || \"rkey\")",
118
-
"type": "virtual"
119
-
}
120
-
},
121
-
"author_did": {
122
-
"name": "author_did",
123
-
"type": "text",
124
-
"primaryKey": false,
125
-
"notNull": true,
126
-
"autoincrement": false
127
-
},
128
-
"rkey": {
129
-
"name": "rkey",
130
-
"type": "text",
131
-
"primaryKey": false,
132
-
"notNull": true,
133
-
"autoincrement": false
134
-
},
135
-
"image": {
136
-
"name": "image",
137
-
"type": "text",
138
-
"primaryKey": false,
139
-
"notNull": false,
140
-
"autoincrement": false
141
-
},
142
-
"title": {
143
-
"name": "title",
144
-
"type": "text",
145
-
"primaryKey": false,
146
-
"notNull": true,
147
-
"autoincrement": false
148
-
},
149
-
"time": {
150
-
"name": "time",
151
-
"type": "integer",
152
-
"primaryKey": false,
153
-
"notNull": true,
154
-
"autoincrement": false,
155
-
"default": 0
156
-
},
157
-
"serves": {
158
-
"name": "serves",
159
-
"type": "integer",
160
-
"primaryKey": false,
161
-
"notNull": false,
162
-
"autoincrement": false
163
-
},
164
-
"description": {
165
-
"name": "description",
166
-
"type": "text",
167
-
"primaryKey": false,
168
-
"notNull": false,
169
-
"autoincrement": false
170
-
},
171
-
"ingredients": {
172
-
"name": "ingredients",
173
-
"type": "text",
174
-
"primaryKey": false,
175
-
"notNull": true,
176
-
"autoincrement": false
177
-
},
178
-
"ingredients_count": {
179
-
"name": "ingredients_count",
180
-
"type": "integer",
181
-
"primaryKey": false,
182
-
"notNull": false,
183
-
"autoincrement": false,
184
-
"generated": {
185
-
"as": "(json_array_length(\"ingredients\"))",
186
-
"type": "virtual"
187
-
}
188
-
},
189
-
"steps": {
190
-
"name": "steps",
191
-
"type": "text",
192
-
"primaryKey": false,
193
-
"notNull": true,
194
-
"autoincrement": false
195
-
},
196
-
"steps_count": {
197
-
"name": "steps_count",
198
-
"type": "integer",
199
-
"primaryKey": false,
200
-
"notNull": false,
201
-
"autoincrement": false,
202
-
"generated": {
203
-
"as": "(json_array_length(\"steps\"))",
204
-
"type": "virtual"
205
-
}
206
-
},
207
-
"created_at": {
208
-
"name": "created_at",
209
-
"type": "text",
210
-
"primaryKey": false,
211
-
"notNull": true,
212
-
"autoincrement": false
213
-
},
214
-
"ingested_at": {
215
-
"name": "ingested_at",
216
-
"type": "text",
217
-
"primaryKey": false,
218
-
"notNull": true,
219
-
"autoincrement": false,
220
-
"default": "CURRENT_TIMESTAMP"
221
-
}
222
-
},
223
-
"indexes": {
224
-
"recipes_title_idx": {
225
-
"name": "recipes_title_idx",
226
-
"columns": [
227
-
"title"
228
-
],
229
-
"isUnique": false
230
-
},
231
-
"recipes_cat_idx": {
232
-
"name": "recipes_cat_idx",
233
-
"columns": [
234
-
"created_at"
235
-
],
236
-
"isUnique": false
237
-
},
238
-
"recipes_iat_idx": {
239
-
"name": "recipes_iat_idx",
240
-
"columns": [
241
-
"ingested_at"
242
-
],
243
-
"isUnique": false
244
-
}
245
-
},
246
-
"foreignKeys": {
247
-
"recipes_author_did_profiles_did_fk": {
248
-
"name": "recipes_author_did_profiles_did_fk",
249
-
"tableFrom": "recipes",
250
-
"tableTo": "profiles",
251
-
"columnsFrom": [
252
-
"author_did"
253
-
],
254
-
"columnsTo": [
255
-
"did"
256
-
],
257
-
"onDelete": "cascade",
258
-
"onUpdate": "no action"
259
-
}
260
-
},
261
-
"compositePrimaryKeys": {
262
-
"recipes_author_did_rkey_pk": {
263
-
"columns": [
264
-
"author_did",
265
-
"rkey"
266
-
],
267
-
"name": "recipes_author_did_rkey_pk"
268
-
}
269
-
},
270
-
"uniqueConstraints": {},
271
-
"checkConstraints": {}
272
-
}
273
-
},
274
-
"views": {},
275
-
"enums": {},
276
-
"_meta": {
277
-
"schemas": {},
278
-
"tables": {},
279
-
"columns": {
280
-
"\"recipes\".\"image_ref\"": "\"recipes\".\"image\""
281
-
}
282
-
},
283
-
"internal": {
284
-
"indexes": {}
285
-
}
286
-
}
···
-284
libs/database/migrations/meta/0002_snapshot.json
-284
libs/database/migrations/meta/0002_snapshot.json
···
1
-
{
2
-
"version": "6",
3
-
"dialect": "sqlite",
4
-
"id": "25f6fc02-0357-4a4a-a43c-6fc138a21401",
5
-
"prevId": "d6f06b7d-9822-43ee-b96c-3b980a5e4953",
6
-
"tables": {
7
-
"profiles": {
8
-
"name": "profiles",
9
-
"columns": {
10
-
"uri": {
11
-
"name": "uri",
12
-
"type": "text",
13
-
"primaryKey": false,
14
-
"notNull": false,
15
-
"autoincrement": false,
16
-
"generated": {
17
-
"as": "('at://' || \"did\" || '/blue.recipes.actor.profile/self')",
18
-
"type": "virtual"
19
-
}
20
-
},
21
-
"did": {
22
-
"name": "did",
23
-
"type": "text",
24
-
"primaryKey": true,
25
-
"notNull": true,
26
-
"autoincrement": false
27
-
},
28
-
"ingested_at": {
29
-
"name": "ingested_at",
30
-
"type": "text",
31
-
"primaryKey": false,
32
-
"notNull": true,
33
-
"autoincrement": false,
34
-
"default": "CURRENT_TIMESTAMP"
35
-
},
36
-
"display_name": {
37
-
"name": "display_name",
38
-
"type": "text(640)",
39
-
"primaryKey": false,
40
-
"notNull": true,
41
-
"autoincrement": false
42
-
},
43
-
"description": {
44
-
"name": "description",
45
-
"type": "text(2500)",
46
-
"primaryKey": false,
47
-
"notNull": false,
48
-
"autoincrement": false
49
-
},
50
-
"pronouns": {
51
-
"name": "pronouns",
52
-
"type": "text(200)",
53
-
"primaryKey": false,
54
-
"notNull": false,
55
-
"autoincrement": false
56
-
},
57
-
"website": {
58
-
"name": "website",
59
-
"type": "text",
60
-
"primaryKey": false,
61
-
"notNull": false,
62
-
"autoincrement": false
63
-
},
64
-
"avatar": {
65
-
"name": "avatar",
66
-
"type": "text",
67
-
"primaryKey": false,
68
-
"notNull": false,
69
-
"autoincrement": false
70
-
},
71
-
"banner": {
72
-
"name": "banner",
73
-
"type": "text",
74
-
"primaryKey": false,
75
-
"notNull": false,
76
-
"autoincrement": false
77
-
},
78
-
"created_at": {
79
-
"name": "created_at",
80
-
"type": "text",
81
-
"primaryKey": false,
82
-
"notNull": true,
83
-
"autoincrement": false
84
-
}
85
-
},
86
-
"indexes": {
87
-
"profiles_cat_idx": {
88
-
"name": "profiles_cat_idx",
89
-
"columns": [
90
-
"created_at"
91
-
],
92
-
"isUnique": false
93
-
},
94
-
"profiles_iat_idx": {
95
-
"name": "profiles_iat_idx",
96
-
"columns": [
97
-
"ingested_at"
98
-
],
99
-
"isUnique": false
100
-
}
101
-
},
102
-
"foreignKeys": {},
103
-
"compositePrimaryKeys": {},
104
-
"uniqueConstraints": {},
105
-
"checkConstraints": {}
106
-
},
107
-
"recipes": {
108
-
"name": "recipes",
109
-
"columns": {
110
-
"uri": {
111
-
"name": "uri",
112
-
"type": "text",
113
-
"primaryKey": false,
114
-
"notNull": false,
115
-
"autoincrement": false,
116
-
"generated": {
117
-
"as": "('at://' || \"author_did\" || '/blue.recipes.feed.recipe/' || \"rkey\")",
118
-
"type": "virtual"
119
-
}
120
-
},
121
-
"author_did": {
122
-
"name": "author_did",
123
-
"type": "text",
124
-
"primaryKey": false,
125
-
"notNull": true,
126
-
"autoincrement": false
127
-
},
128
-
"rkey": {
129
-
"name": "rkey",
130
-
"type": "text",
131
-
"primaryKey": false,
132
-
"notNull": true,
133
-
"autoincrement": false
134
-
},
135
-
"image": {
136
-
"name": "image",
137
-
"type": "text",
138
-
"primaryKey": false,
139
-
"notNull": false,
140
-
"autoincrement": false
141
-
},
142
-
"title": {
143
-
"name": "title",
144
-
"type": "text",
145
-
"primaryKey": false,
146
-
"notNull": true,
147
-
"autoincrement": false
148
-
},
149
-
"time": {
150
-
"name": "time",
151
-
"type": "integer",
152
-
"primaryKey": false,
153
-
"notNull": true,
154
-
"autoincrement": false,
155
-
"default": 0
156
-
},
157
-
"serves": {
158
-
"name": "serves",
159
-
"type": "integer",
160
-
"primaryKey": false,
161
-
"notNull": false,
162
-
"autoincrement": false
163
-
},
164
-
"description": {
165
-
"name": "description",
166
-
"type": "text",
167
-
"primaryKey": false,
168
-
"notNull": false,
169
-
"autoincrement": false
170
-
},
171
-
"ingredients": {
172
-
"name": "ingredients",
173
-
"type": "text",
174
-
"primaryKey": false,
175
-
"notNull": true,
176
-
"autoincrement": false
177
-
},
178
-
"ingredients_count": {
179
-
"name": "ingredients_count",
180
-
"type": "integer",
181
-
"primaryKey": false,
182
-
"notNull": false,
183
-
"autoincrement": false,
184
-
"generated": {
185
-
"as": "(json_array_length(\"ingredients\"))",
186
-
"type": "virtual"
187
-
}
188
-
},
189
-
"steps": {
190
-
"name": "steps",
191
-
"type": "text",
192
-
"primaryKey": false,
193
-
"notNull": true,
194
-
"autoincrement": false
195
-
},
196
-
"steps_count": {
197
-
"name": "steps_count",
198
-
"type": "integer",
199
-
"primaryKey": false,
200
-
"notNull": false,
201
-
"autoincrement": false,
202
-
"generated": {
203
-
"as": "(json_array_length(\"steps\"))",
204
-
"type": "virtual"
205
-
}
206
-
},
207
-
"created_at": {
208
-
"name": "created_at",
209
-
"type": "text",
210
-
"primaryKey": false,
211
-
"notNull": true,
212
-
"autoincrement": false
213
-
},
214
-
"ingested_at": {
215
-
"name": "ingested_at",
216
-
"type": "text",
217
-
"primaryKey": false,
218
-
"notNull": true,
219
-
"autoincrement": false,
220
-
"default": "CURRENT_TIMESTAMP"
221
-
}
222
-
},
223
-
"indexes": {
224
-
"recipes_title_idx": {
225
-
"name": "recipes_title_idx",
226
-
"columns": [
227
-
"title"
228
-
],
229
-
"isUnique": false
230
-
},
231
-
"recipes_cat_idx": {
232
-
"name": "recipes_cat_idx",
233
-
"columns": [
234
-
"created_at"
235
-
],
236
-
"isUnique": false
237
-
},
238
-
"recipes_iat_idx": {
239
-
"name": "recipes_iat_idx",
240
-
"columns": [
241
-
"ingested_at"
242
-
],
243
-
"isUnique": false
244
-
}
245
-
},
246
-
"foreignKeys": {
247
-
"recipes_author_did_profiles_did_fk": {
248
-
"name": "recipes_author_did_profiles_did_fk",
249
-
"tableFrom": "recipes",
250
-
"tableTo": "profiles",
251
-
"columnsFrom": [
252
-
"author_did"
253
-
],
254
-
"columnsTo": [
255
-
"did"
256
-
],
257
-
"onDelete": "cascade",
258
-
"onUpdate": "no action"
259
-
}
260
-
},
261
-
"compositePrimaryKeys": {
262
-
"recipes_author_did_rkey_pk": {
263
-
"columns": [
264
-
"author_did",
265
-
"rkey"
266
-
],
267
-
"name": "recipes_author_did_rkey_pk"
268
-
}
269
-
},
270
-
"uniqueConstraints": {},
271
-
"checkConstraints": {}
272
-
}
273
-
},
274
-
"views": {},
275
-
"enums": {},
276
-
"_meta": {
277
-
"schemas": {},
278
-
"tables": {},
279
-
"columns": {}
280
-
},
281
-
"internal": {
282
-
"indexes": {}
283
-
}
284
-
}
···
-312
libs/database/migrations/meta/0003_snapshot.json
-312
libs/database/migrations/meta/0003_snapshot.json
···
1
-
{
2
-
"version": "6",
3
-
"dialect": "sqlite",
4
-
"id": "ca3337d9-69a0-468d-8364-0f05e91a0233",
5
-
"prevId": "25f6fc02-0357-4a4a-a43c-6fc138a21401",
6
-
"tables": {
7
-
"profiles": {
8
-
"name": "profiles",
9
-
"columns": {
10
-
"uri": {
11
-
"name": "uri",
12
-
"type": "text",
13
-
"primaryKey": false,
14
-
"notNull": false,
15
-
"autoincrement": false,
16
-
"generated": {
17
-
"as": "('at://' || \"did\" || '/blue.recipes.actor.profile/self')",
18
-
"type": "virtual"
19
-
}
20
-
},
21
-
"cid": {
22
-
"name": "cid",
23
-
"type": "text",
24
-
"primaryKey": false,
25
-
"notNull": true,
26
-
"autoincrement": false
27
-
},
28
-
"did": {
29
-
"name": "did",
30
-
"type": "text",
31
-
"primaryKey": true,
32
-
"notNull": true,
33
-
"autoincrement": false
34
-
},
35
-
"ingested_at": {
36
-
"name": "ingested_at",
37
-
"type": "text",
38
-
"primaryKey": false,
39
-
"notNull": true,
40
-
"autoincrement": false,
41
-
"default": "CURRENT_TIMESTAMP"
42
-
},
43
-
"display_name": {
44
-
"name": "display_name",
45
-
"type": "text(640)",
46
-
"primaryKey": false,
47
-
"notNull": true,
48
-
"autoincrement": false
49
-
},
50
-
"description": {
51
-
"name": "description",
52
-
"type": "text(2500)",
53
-
"primaryKey": false,
54
-
"notNull": false,
55
-
"autoincrement": false
56
-
},
57
-
"pronouns": {
58
-
"name": "pronouns",
59
-
"type": "text(200)",
60
-
"primaryKey": false,
61
-
"notNull": false,
62
-
"autoincrement": false
63
-
},
64
-
"website": {
65
-
"name": "website",
66
-
"type": "text",
67
-
"primaryKey": false,
68
-
"notNull": false,
69
-
"autoincrement": false
70
-
},
71
-
"avatar": {
72
-
"name": "avatar",
73
-
"type": "text",
74
-
"primaryKey": false,
75
-
"notNull": false,
76
-
"autoincrement": false
77
-
},
78
-
"banner": {
79
-
"name": "banner",
80
-
"type": "text",
81
-
"primaryKey": false,
82
-
"notNull": false,
83
-
"autoincrement": false
84
-
},
85
-
"created_at": {
86
-
"name": "created_at",
87
-
"type": "text",
88
-
"primaryKey": false,
89
-
"notNull": true,
90
-
"autoincrement": false
91
-
}
92
-
},
93
-
"indexes": {
94
-
"profiles_cid_idx": {
95
-
"name": "profiles_cid_idx",
96
-
"columns": [
97
-
"cid"
98
-
],
99
-
"isUnique": false
100
-
},
101
-
"profiles_cat_idx": {
102
-
"name": "profiles_cat_idx",
103
-
"columns": [
104
-
"created_at"
105
-
],
106
-
"isUnique": false
107
-
},
108
-
"profiles_iat_idx": {
109
-
"name": "profiles_iat_idx",
110
-
"columns": [
111
-
"ingested_at"
112
-
],
113
-
"isUnique": false
114
-
}
115
-
},
116
-
"foreignKeys": {},
117
-
"compositePrimaryKeys": {},
118
-
"uniqueConstraints": {},
119
-
"checkConstraints": {}
120
-
},
121
-
"recipes": {
122
-
"name": "recipes",
123
-
"columns": {
124
-
"uri": {
125
-
"name": "uri",
126
-
"type": "text",
127
-
"primaryKey": false,
128
-
"notNull": false,
129
-
"autoincrement": false,
130
-
"generated": {
131
-
"as": "('at://' || \"author_did\" || '/blue.recipes.feed.recipe/' || \"rkey\")",
132
-
"type": "virtual"
133
-
}
134
-
},
135
-
"cid": {
136
-
"name": "cid",
137
-
"type": "text",
138
-
"primaryKey": false,
139
-
"notNull": true,
140
-
"autoincrement": false
141
-
},
142
-
"author_did": {
143
-
"name": "author_did",
144
-
"type": "text",
145
-
"primaryKey": false,
146
-
"notNull": true,
147
-
"autoincrement": false
148
-
},
149
-
"rkey": {
150
-
"name": "rkey",
151
-
"type": "text",
152
-
"primaryKey": false,
153
-
"notNull": true,
154
-
"autoincrement": false
155
-
},
156
-
"image": {
157
-
"name": "image",
158
-
"type": "text",
159
-
"primaryKey": false,
160
-
"notNull": false,
161
-
"autoincrement": false
162
-
},
163
-
"title": {
164
-
"name": "title",
165
-
"type": "text",
166
-
"primaryKey": false,
167
-
"notNull": true,
168
-
"autoincrement": false
169
-
},
170
-
"time": {
171
-
"name": "time",
172
-
"type": "integer",
173
-
"primaryKey": false,
174
-
"notNull": true,
175
-
"autoincrement": false,
176
-
"default": 0
177
-
},
178
-
"serves": {
179
-
"name": "serves",
180
-
"type": "integer",
181
-
"primaryKey": false,
182
-
"notNull": false,
183
-
"autoincrement": false
184
-
},
185
-
"description": {
186
-
"name": "description",
187
-
"type": "text",
188
-
"primaryKey": false,
189
-
"notNull": false,
190
-
"autoincrement": false
191
-
},
192
-
"ingredients": {
193
-
"name": "ingredients",
194
-
"type": "text",
195
-
"primaryKey": false,
196
-
"notNull": true,
197
-
"autoincrement": false
198
-
},
199
-
"ingredients_count": {
200
-
"name": "ingredients_count",
201
-
"type": "integer",
202
-
"primaryKey": false,
203
-
"notNull": false,
204
-
"autoincrement": false,
205
-
"generated": {
206
-
"as": "(json_array_length(\"ingredients\"))",
207
-
"type": "virtual"
208
-
}
209
-
},
210
-
"steps": {
211
-
"name": "steps",
212
-
"type": "text",
213
-
"primaryKey": false,
214
-
"notNull": true,
215
-
"autoincrement": false
216
-
},
217
-
"steps_count": {
218
-
"name": "steps_count",
219
-
"type": "integer",
220
-
"primaryKey": false,
221
-
"notNull": false,
222
-
"autoincrement": false,
223
-
"generated": {
224
-
"as": "(json_array_length(\"steps\"))",
225
-
"type": "virtual"
226
-
}
227
-
},
228
-
"created_at": {
229
-
"name": "created_at",
230
-
"type": "text",
231
-
"primaryKey": false,
232
-
"notNull": true,
233
-
"autoincrement": false
234
-
},
235
-
"ingested_at": {
236
-
"name": "ingested_at",
237
-
"type": "text",
238
-
"primaryKey": false,
239
-
"notNull": true,
240
-
"autoincrement": false,
241
-
"default": "CURRENT_TIMESTAMP"
242
-
}
243
-
},
244
-
"indexes": {
245
-
"recipes_title_idx": {
246
-
"name": "recipes_title_idx",
247
-
"columns": [
248
-
"title"
249
-
],
250
-
"isUnique": false
251
-
},
252
-
"recipes_cid_idx": {
253
-
"name": "recipes_cid_idx",
254
-
"columns": [
255
-
"cid"
256
-
],
257
-
"isUnique": false
258
-
},
259
-
"recipes_cat_idx": {
260
-
"name": "recipes_cat_idx",
261
-
"columns": [
262
-
"created_at"
263
-
],
264
-
"isUnique": false
265
-
},
266
-
"recipes_iat_idx": {
267
-
"name": "recipes_iat_idx",
268
-
"columns": [
269
-
"ingested_at"
270
-
],
271
-
"isUnique": false
272
-
}
273
-
},
274
-
"foreignKeys": {
275
-
"recipes_author_did_profiles_did_fk": {
276
-
"name": "recipes_author_did_profiles_did_fk",
277
-
"tableFrom": "recipes",
278
-
"tableTo": "profiles",
279
-
"columnsFrom": [
280
-
"author_did"
281
-
],
282
-
"columnsTo": [
283
-
"did"
284
-
],
285
-
"onDelete": "cascade",
286
-
"onUpdate": "no action"
287
-
}
288
-
},
289
-
"compositePrimaryKeys": {
290
-
"recipes_author_did_rkey_pk": {
291
-
"columns": [
292
-
"author_did",
293
-
"rkey"
294
-
],
295
-
"name": "recipes_author_did_rkey_pk"
296
-
}
297
-
},
298
-
"uniqueConstraints": {},
299
-
"checkConstraints": {}
300
-
}
301
-
},
302
-
"views": {},
303
-
"enums": {},
304
-
"_meta": {
305
-
"schemas": {},
306
-
"tables": {},
307
-
"columns": {}
308
-
},
309
-
"internal": {
310
-
"indexes": {}
311
-
}
312
-
}
···
+4
-25
libs/database/migrations/meta/_journal.json
+4
-25
libs/database/migrations/meta/_journal.json
···
1
{
2
"version": "7",
3
-
"dialect": "sqlite",
4
"entries": [
5
{
6
"idx": 0,
7
-
"version": "6",
8
-
"when": 1764024817179,
9
-
"tag": "0000_kind_ultron",
10
-
"breakpoints": true
11
-
},
12
-
{
13
-
"idx": 1,
14
-
"version": "6",
15
-
"when": 1764102063385,
16
-
"tag": "0001_past_umar",
17
-
"breakpoints": true
18
-
},
19
-
{
20
-
"idx": 2,
21
-
"version": "6",
22
-
"when": 1764113357363,
23
-
"tag": "0002_cheerful_venom",
24
-
"breakpoints": true
25
-
},
26
-
{
27
-
"idx": 3,
28
-
"version": "6",
29
-
"when": 1764113735823,
30
-
"tag": "0003_long_blue_marvel",
31
"breakpoints": true
32
}
33
]
+2
libs/database/package.json
+2
libs/database/package.json
···
27
"@cookware/tsconfig": "workspace:*",
28
"@types/bun": "catalog:",
29
"@types/node": "^22.10.1",
30
+
"@types/pg": "^8.15.6",
31
"drizzle-kit": "^0.29.0",
32
"typescript": "^5.2.2"
33
},
34
"dependencies": {
35
"@libsql/client": "^0.15.15",
36
"drizzle-orm": "catalog:",
37
+
"pg": "^8.16.3",
38
"zod": "^3.23.8"
39
}
40
}