@tulip-systems/drive-local 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +662 -0
- package/dist/client.d.mts +12 -0
- package/dist/client.mjs +12 -0
- package/dist/database/drizzle/lib/adapter.d.mts +310 -0
- package/dist/database/drizzle/lib/adapter.mjs +520 -0
- package/dist/database/drizzle/lib/compilers.d.mts +395 -0
- package/dist/database/drizzle/lib/compilers.mjs +21 -0
- package/dist/database/drizzle/lib/schema.d.mts +61 -0
- package/dist/drizzle.d.mts +4 -0
- package/dist/drizzle.mjs +3 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +8 -0
- package/dist/root/components/command-file-update.d.mts +11 -0
- package/dist/root/components/command-file-update.mjs +44 -0
- package/dist/root/components/command-file-upload.d.mts +5 -0
- package/dist/root/components/command-file-upload.mjs +31 -0
- package/dist/root/components/command-folder-create.d.mts +11 -0
- package/dist/root/components/command-folder-create.mjs +50 -0
- package/dist/root/components/command-folder-update.d.mts +11 -0
- package/dist/root/components/command-folder-update.mjs +44 -0
- package/dist/root/components/content.client.d.mts +5 -0
- package/dist/root/components/content.client.mjs +5 -0
- package/dist/root/components/navigation.client.d.mts +11 -0
- package/dist/root/components/navigation.client.mjs +12 -0
- package/dist/root/components/provider.client.d.mts +16 -0
- package/dist/root/components/provider.client.mjs +65 -0
- package/dist/root/components/upload-zone-context.client.d.mts +36 -0
- package/dist/root/components/upload-zone-context.client.mjs +19 -0
- package/dist/root/components/upload-zone.client.d.mts +16 -0
- package/dist/root/components/upload-zone.client.mjs +147 -0
- package/dist/root/components/view.client.d.mts +20 -0
- package/dist/root/components/view.client.mjs +86 -0
- package/dist/root/config/columns-data.d.mts +6 -0
- package/dist/root/config/columns-data.mjs +62 -0
- package/dist/root/config/filters.d.mts +404 -0
- package/dist/root/config/filters.mjs +122 -0
- package/dist/root/config/sorting.d.mts +38 -0
- package/dist/root/config/sorting.mjs +26 -0
- package/dist/root/lib/constants.d.mts +12 -0
- package/dist/root/lib/constants.mjs +27 -0
- package/dist/root/lib/database/contracts.d.mts +112 -0
- package/dist/root/lib/database/filters.d.mts +28 -0
- package/dist/root/lib/database/filters.mjs +73 -0
- package/dist/root/lib/helpers.d.mts +33 -0
- package/dist/root/lib/helpers.mjs +78 -0
- package/dist/root/lib/route-handler.server.d.mts +36 -0
- package/dist/root/lib/route-handler.server.mjs +111 -0
- package/dist/root/lib/router.server.d.mts +819 -0
- package/dist/root/lib/router.server.mjs +87 -0
- package/dist/root/lib/search-params.d.mts +15 -0
- package/dist/root/lib/search-params.mjs +6 -0
- package/dist/root/lib/service.server.d.mts +522 -0
- package/dist/root/lib/service.server.mjs +613 -0
- package/dist/root/lib/upload.client.d.mts +60 -0
- package/dist/root/lib/upload.client.mjs +97 -0
- package/dist/root/lib/validators.d.mts +710 -0
- package/dist/root/lib/validators.mjs +116 -0
- package/dist/server.d.mts +5 -0
- package/dist/server.mjs +4 -0
- package/package.json +83 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import { localDriveFiltersParser, localDrivePresignedUrlFiltersParser, localDriveVariantFiltersParser } from "../../../root/config/filters.mjs";
|
|
2
|
+
import { localDriveTableSortsParser } from "../../../root/config/sorting.mjs";
|
|
3
|
+
import { localDriveDatabaseNodeFiltersParser } from "../../../root/lib/database/filters.mjs";
|
|
4
|
+
import { compileLocalDriveDatabaseNodeFiltersToDrizzle, compileLocalDriveFiltersToDrizzle, compileLocalDrivePresignedUrlFiltersToDrizzle, compileLocalDriveSortsToDrizzle, compileLocalDriveVariantFiltersToDrizzle } from "./compilers.mjs";
|
|
5
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
6
|
+
//#region src/database/drizzle/lib/adapter.ts
|
|
7
|
+
/**
|
|
8
|
+
* Creates the Drizzle database adapter for the local drive package.
|
|
9
|
+
*
|
|
10
|
+
* The local drive package owns the drive behavior, but the consumer app owns the
|
|
11
|
+
* Drizzle schema and migrations. Pass the app's `db` instance and a schema map
|
|
12
|
+
* with the local drive tables under the expected contract keys.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import { localDriveDrizzleAdapter } from "@tulip-systems/drive-local/drizzle";
|
|
16
|
+
* import { LocalDrive } from "@tulip-systems/drive-local/server";
|
|
17
|
+
* import * as driveSchema from "@/server/drive-local/schema";
|
|
18
|
+
* import * as storageSchema from "@/server/storage/schema";
|
|
19
|
+
*
|
|
20
|
+
* export const drive = new LocalDrive({
|
|
21
|
+
* database: localDriveDrizzleAdapter({
|
|
22
|
+
* db,
|
|
23
|
+
* schema: { ...driveSchema, storageAssets: storageSchema.storageAssets },
|
|
24
|
+
* }),
|
|
25
|
+
* storage,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // You can also remap app-owned table names to the local drive contract keys.
|
|
30
|
+
* localDriveDrizzleAdapter({
|
|
31
|
+
* db,
|
|
32
|
+
* schema: {
|
|
33
|
+
* nodes: driveNodes,
|
|
34
|
+
* nodeVariants: driveNodeVariants,
|
|
35
|
+
* nodePresignedUrls: driveNodePresignedUrls,
|
|
36
|
+
* storageAssets: assets,
|
|
37
|
+
* },
|
|
38
|
+
* });
|
|
39
|
+
*/
|
|
40
|
+
function localDriveDrizzleAdapter({ db, schema }) {
|
|
41
|
+
const { nodePresignedUrls, nodeVariants, nodes, storageAssets } = schema;
|
|
42
|
+
function parseNodeSorts(sorts) {
|
|
43
|
+
const parsedSorts = localDriveTableSortsParser.parse(sorts);
|
|
44
|
+
return compileLocalDriveSortsToDrizzle(nodes).parse(parsedSorts);
|
|
45
|
+
}
|
|
46
|
+
function parseNodeFilters(filters) {
|
|
47
|
+
const parsedFilters = localDriveFiltersParser.parse(filters);
|
|
48
|
+
return compileLocalDriveFiltersToDrizzle(nodes).parse(parsedFilters);
|
|
49
|
+
}
|
|
50
|
+
function parseDatabaseNodeFilters(filters) {
|
|
51
|
+
const parsedFilters = localDriveDatabaseNodeFiltersParser.parse(filters);
|
|
52
|
+
return compileLocalDriveDatabaseNodeFiltersToDrizzle(nodes).parse(parsedFilters);
|
|
53
|
+
}
|
|
54
|
+
function parseVariantFilters(filters) {
|
|
55
|
+
const parsedFilters = localDriveVariantFiltersParser.parse(filters);
|
|
56
|
+
return compileLocalDriveVariantFiltersToDrizzle(nodeVariants).parse(parsedFilters);
|
|
57
|
+
}
|
|
58
|
+
function parsePresignedUrlFilters(filters) {
|
|
59
|
+
const parsedFilters = localDrivePresignedUrlFiltersParser.parse(filters);
|
|
60
|
+
return compileLocalDrivePresignedUrlFiltersToDrizzle(nodePresignedUrls).parse(parsedFilters);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
db,
|
|
64
|
+
schema,
|
|
65
|
+
/**
|
|
66
|
+
* Finds a local drive node by its primary id.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const node = await database.findNodeById("019d...");
|
|
70
|
+
*/
|
|
71
|
+
findNodeById: async (id) => {
|
|
72
|
+
const [node] = await db.select({
|
|
73
|
+
id: nodes.id,
|
|
74
|
+
createdAt: nodes.createdAt,
|
|
75
|
+
updatedAt: nodes.updatedAt,
|
|
76
|
+
name: nodes.name,
|
|
77
|
+
namespace: nodes.namespace,
|
|
78
|
+
type: nodes.type,
|
|
79
|
+
subtype: nodes.subtype,
|
|
80
|
+
size: nodes.size,
|
|
81
|
+
contentType: nodes.contentType,
|
|
82
|
+
readonly: nodes.readonly,
|
|
83
|
+
hidden: nodes.hidden,
|
|
84
|
+
archivedAt: nodes.archivedAt,
|
|
85
|
+
parentId: nodes.parentId,
|
|
86
|
+
assetId: nodes.assetId
|
|
87
|
+
}).from(nodes).where(eq(nodes.id, id)).limit(1);
|
|
88
|
+
return node ?? null;
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* Finds the first local drive node matching the provided database filters.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const node = await database.findFirstNode({ filters: { assetIds: [asset.id] } });
|
|
95
|
+
*/
|
|
96
|
+
findFirstNode: async (input) => {
|
|
97
|
+
let query = db.select({
|
|
98
|
+
id: nodes.id,
|
|
99
|
+
createdAt: nodes.createdAt,
|
|
100
|
+
updatedAt: nodes.updatedAt,
|
|
101
|
+
name: nodes.name,
|
|
102
|
+
namespace: nodes.namespace,
|
|
103
|
+
type: nodes.type,
|
|
104
|
+
subtype: nodes.subtype,
|
|
105
|
+
size: nodes.size,
|
|
106
|
+
contentType: nodes.contentType,
|
|
107
|
+
readonly: nodes.readonly,
|
|
108
|
+
hidden: nodes.hidden,
|
|
109
|
+
archivedAt: nodes.archivedAt,
|
|
110
|
+
parentId: nodes.parentId,
|
|
111
|
+
assetId: nodes.assetId
|
|
112
|
+
}).from(nodes).$dynamic();
|
|
113
|
+
if (input?.filters) {
|
|
114
|
+
const where = parseDatabaseNodeFilters(input.filters);
|
|
115
|
+
query = query.where(where);
|
|
116
|
+
}
|
|
117
|
+
const [node] = await query.limit(1);
|
|
118
|
+
return node ?? null;
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* Finds local drive nodes matching the provided filters, sorts, and pagination limits.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* const nodes = await database.findNodes({
|
|
125
|
+
* filters: { namespace: "products", parentId: null },
|
|
126
|
+
* sorts: [{ key: "name", direction: "asc" }],
|
|
127
|
+
* });
|
|
128
|
+
*/
|
|
129
|
+
findNodes: async (input = {}) => {
|
|
130
|
+
let query = db.select({
|
|
131
|
+
id: nodes.id,
|
|
132
|
+
createdAt: nodes.createdAt,
|
|
133
|
+
updatedAt: nodes.updatedAt,
|
|
134
|
+
name: nodes.name,
|
|
135
|
+
namespace: nodes.namespace,
|
|
136
|
+
type: nodes.type,
|
|
137
|
+
subtype: nodes.subtype,
|
|
138
|
+
size: nodes.size,
|
|
139
|
+
contentType: nodes.contentType,
|
|
140
|
+
readonly: nodes.readonly,
|
|
141
|
+
hidden: nodes.hidden,
|
|
142
|
+
archivedAt: nodes.archivedAt,
|
|
143
|
+
parentId: nodes.parentId,
|
|
144
|
+
assetId: nodes.assetId
|
|
145
|
+
}).from(nodes).$dynamic();
|
|
146
|
+
if (input.filters) {
|
|
147
|
+
const where = parseDatabaseNodeFilters(input.filters);
|
|
148
|
+
query = query.where(where);
|
|
149
|
+
}
|
|
150
|
+
if (input.sorts) {
|
|
151
|
+
const orderBy = parseNodeSorts(input.sorts);
|
|
152
|
+
query = query.orderBy(...orderBy);
|
|
153
|
+
}
|
|
154
|
+
if (input.limit !== void 0) query = query.limit(input.limit);
|
|
155
|
+
if (input.offset !== void 0) query = query.offset(input.offset);
|
|
156
|
+
return await query;
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* Lists paginated nodes with their linked storage asset.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* const result = await database.listNodesWithAsset({
|
|
163
|
+
* limit: 20,
|
|
164
|
+
* offset: 0,
|
|
165
|
+
* filters: { namespace: "products", parentId: null },
|
|
166
|
+
* sorts: [],
|
|
167
|
+
* });
|
|
168
|
+
*/
|
|
169
|
+
listNodesWithAsset: async ({ filters, limit, offset, sorts }) => {
|
|
170
|
+
const [data, total] = await db.transaction(async (tx) => {
|
|
171
|
+
const where = parseNodeFilters(filters);
|
|
172
|
+
let query = tx.select({
|
|
173
|
+
id: nodes.id,
|
|
174
|
+
createdAt: nodes.createdAt,
|
|
175
|
+
updatedAt: nodes.updatedAt,
|
|
176
|
+
name: nodes.name,
|
|
177
|
+
namespace: nodes.namespace,
|
|
178
|
+
type: nodes.type,
|
|
179
|
+
subtype: nodes.subtype,
|
|
180
|
+
size: nodes.size,
|
|
181
|
+
contentType: nodes.contentType,
|
|
182
|
+
readonly: nodes.readonly,
|
|
183
|
+
hidden: nodes.hidden,
|
|
184
|
+
archivedAt: nodes.archivedAt,
|
|
185
|
+
parentId: nodes.parentId,
|
|
186
|
+
assetId: nodes.assetId,
|
|
187
|
+
asset: {
|
|
188
|
+
id: storageAssets.id,
|
|
189
|
+
createdAt: storageAssets.createdAt,
|
|
190
|
+
updatedAt: storageAssets.updatedAt,
|
|
191
|
+
provider: storageAssets.provider,
|
|
192
|
+
bucket: storageAssets.bucket,
|
|
193
|
+
key: storageAssets.key,
|
|
194
|
+
status: storageAssets.status,
|
|
195
|
+
visibility: storageAssets.visibility,
|
|
196
|
+
size: storageAssets.size,
|
|
197
|
+
contentType: storageAssets.contentType,
|
|
198
|
+
uploadId: storageAssets.uploadId,
|
|
199
|
+
name: storageAssets.name,
|
|
200
|
+
metadata: storageAssets.metadata,
|
|
201
|
+
etag: storageAssets.etag,
|
|
202
|
+
uploadedAt: storageAssets.uploadedAt,
|
|
203
|
+
deletedAt: storageAssets.deletedAt
|
|
204
|
+
}
|
|
205
|
+
}).from(nodes).leftJoin(storageAssets, eq(nodes.assetId, storageAssets.id)).$dynamic();
|
|
206
|
+
if (where) query = query.where(where);
|
|
207
|
+
if (sorts && sorts.length > 0) {
|
|
208
|
+
const orderBy = parseNodeSorts(sorts);
|
|
209
|
+
query = query.orderBy(...orderBy);
|
|
210
|
+
}
|
|
211
|
+
return [await query.limit(limit).offset(offset), await tx.$count(nodes, where)];
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
data,
|
|
215
|
+
total
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
/**
|
|
219
|
+
* Resolves the ancestor chain for a folder or file within a namespace.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* const parents = await database.getNodeParents({ id: node.id, namespace: "products" });
|
|
223
|
+
*/
|
|
224
|
+
getNodeParents: async (input) => {
|
|
225
|
+
if (!input.id) return [];
|
|
226
|
+
const startNodeQuery = db.select({
|
|
227
|
+
id: nodes.id,
|
|
228
|
+
name: nodes.name,
|
|
229
|
+
parentId: nodes.parentId,
|
|
230
|
+
depth: sql`0`.as("depth")
|
|
231
|
+
}).from(nodes).where(and(eq(nodes.id, input.id), eq(nodes.namespace, input.namespace)));
|
|
232
|
+
const alias = "parent_nodes";
|
|
233
|
+
const parentNodesQueryAlias = startNodeQuery.as(alias);
|
|
234
|
+
const recursiveQueryName = sql.raw(`"${alias}"`);
|
|
235
|
+
const recursiveQuery = startNodeQuery.unionAll(db.select({
|
|
236
|
+
id: nodes.id,
|
|
237
|
+
name: nodes.name,
|
|
238
|
+
parentId: nodes.parentId,
|
|
239
|
+
depth: sql`${parentNodesQueryAlias.depth} + 1`
|
|
240
|
+
}).from(nodes).innerJoin(recursiveQueryName, eq(nodes.id, parentNodesQueryAlias.parentId)));
|
|
241
|
+
return (await db.execute(sql`WITH RECURSIVE ${recursiveQueryName} AS ${recursiveQuery}
|
|
242
|
+
SELECT
|
|
243
|
+
${parentNodesQueryAlias.id} as id,
|
|
244
|
+
${parentNodesQueryAlias.name} as name,
|
|
245
|
+
${parentNodesQueryAlias.parentId} as "parentId",
|
|
246
|
+
${parentNodesQueryAlias.depth}::int as depth
|
|
247
|
+
FROM ${recursiveQueryName}`)).rows ?? [];
|
|
248
|
+
},
|
|
249
|
+
/**
|
|
250
|
+
* Resolves all recursive descendants for one or more nodes.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* const descendants = await database.getNodeChildren([folder.id]);
|
|
254
|
+
*/
|
|
255
|
+
getNodeChildren: async (ids) => {
|
|
256
|
+
if (ids.length === 0) return [];
|
|
257
|
+
const startNodeQuery = db.select({
|
|
258
|
+
id: nodes.id,
|
|
259
|
+
createdAt: nodes.createdAt,
|
|
260
|
+
updatedAt: nodes.updatedAt,
|
|
261
|
+
name: nodes.name,
|
|
262
|
+
namespace: nodes.namespace,
|
|
263
|
+
type: nodes.type,
|
|
264
|
+
subtype: nodes.subtype,
|
|
265
|
+
size: nodes.size,
|
|
266
|
+
contentType: nodes.contentType,
|
|
267
|
+
readonly: nodes.readonly,
|
|
268
|
+
hidden: nodes.hidden,
|
|
269
|
+
archivedAt: nodes.archivedAt,
|
|
270
|
+
parentId: nodes.parentId,
|
|
271
|
+
assetId: nodes.assetId,
|
|
272
|
+
depth: sql`0`.as("depth")
|
|
273
|
+
}).from(nodes).where(inArray(nodes.id, ids));
|
|
274
|
+
const alias = "child_nodes";
|
|
275
|
+
const childNodesQueryAlias = startNodeQuery.as(alias);
|
|
276
|
+
const recursiveQueryName = sql.raw(`"${alias}"`);
|
|
277
|
+
const recursiveQuery = startNodeQuery.unionAll(db.select({
|
|
278
|
+
id: nodes.id,
|
|
279
|
+
createdAt: nodes.createdAt,
|
|
280
|
+
updatedAt: nodes.updatedAt,
|
|
281
|
+
name: nodes.name,
|
|
282
|
+
namespace: nodes.namespace,
|
|
283
|
+
type: nodes.type,
|
|
284
|
+
subtype: nodes.subtype,
|
|
285
|
+
size: nodes.size,
|
|
286
|
+
contentType: nodes.contentType,
|
|
287
|
+
readonly: nodes.readonly,
|
|
288
|
+
hidden: nodes.hidden,
|
|
289
|
+
archivedAt: nodes.archivedAt,
|
|
290
|
+
parentId: nodes.parentId,
|
|
291
|
+
assetId: nodes.assetId,
|
|
292
|
+
depth: sql`${childNodesQueryAlias.depth} + 1`
|
|
293
|
+
}).from(nodes).innerJoin(recursiveQueryName, eq(nodes.parentId, childNodesQueryAlias.id)));
|
|
294
|
+
return (await db.execute(sql`WITH RECURSIVE ${recursiveQueryName} AS ${recursiveQuery}
|
|
295
|
+
SELECT
|
|
296
|
+
${childNodesQueryAlias.id} as id,
|
|
297
|
+
${childNodesQueryAlias.createdAt} as "createdAt",
|
|
298
|
+
${childNodesQueryAlias.updatedAt} as "updatedAt",
|
|
299
|
+
${childNodesQueryAlias.name} as name,
|
|
300
|
+
${childNodesQueryAlias.namespace} as namespace,
|
|
301
|
+
${childNodesQueryAlias.type} as type,
|
|
302
|
+
${childNodesQueryAlias.subtype} as subtype,
|
|
303
|
+
${childNodesQueryAlias.size} as size,
|
|
304
|
+
${childNodesQueryAlias.contentType} as "contentType",
|
|
305
|
+
${childNodesQueryAlias.readonly} as readonly,
|
|
306
|
+
${childNodesQueryAlias.hidden} as hidden,
|
|
307
|
+
${childNodesQueryAlias.archivedAt} as "archivedAt",
|
|
308
|
+
${childNodesQueryAlias.parentId} as "parentId",
|
|
309
|
+
${childNodesQueryAlias.assetId} as "assetId",
|
|
310
|
+
MIN(${childNodesQueryAlias.depth})::int as depth
|
|
311
|
+
FROM ${recursiveQueryName}
|
|
312
|
+
GROUP BY
|
|
313
|
+
${childNodesQueryAlias.id},
|
|
314
|
+
${childNodesQueryAlias.createdAt},
|
|
315
|
+
${childNodesQueryAlias.updatedAt},
|
|
316
|
+
${childNodesQueryAlias.name},
|
|
317
|
+
${childNodesQueryAlias.namespace},
|
|
318
|
+
${childNodesQueryAlias.type},
|
|
319
|
+
${childNodesQueryAlias.subtype},
|
|
320
|
+
${childNodesQueryAlias.size},
|
|
321
|
+
${childNodesQueryAlias.contentType},
|
|
322
|
+
${childNodesQueryAlias.readonly},
|
|
323
|
+
${childNodesQueryAlias.hidden},
|
|
324
|
+
${childNodesQueryAlias.archivedAt},
|
|
325
|
+
${childNodesQueryAlias.parentId},
|
|
326
|
+
${childNodesQueryAlias.assetId}
|
|
327
|
+
`)).rows ?? [];
|
|
328
|
+
},
|
|
329
|
+
/**
|
|
330
|
+
* Creates a node row.
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* const node = await database.createNode({
|
|
334
|
+
* type: "file",
|
|
335
|
+
* name: "manual.pdf",
|
|
336
|
+
* namespace: "products",
|
|
337
|
+
* subtype: "document",
|
|
338
|
+
* assetId: asset.id,
|
|
339
|
+
* });
|
|
340
|
+
*/
|
|
341
|
+
createNode: async (input) => {
|
|
342
|
+
const [node] = await db.insert(nodes).values(input).returning({
|
|
343
|
+
id: nodes.id,
|
|
344
|
+
createdAt: nodes.createdAt,
|
|
345
|
+
updatedAt: nodes.updatedAt,
|
|
346
|
+
name: nodes.name,
|
|
347
|
+
namespace: nodes.namespace,
|
|
348
|
+
type: nodes.type,
|
|
349
|
+
subtype: nodes.subtype,
|
|
350
|
+
size: nodes.size,
|
|
351
|
+
contentType: nodes.contentType,
|
|
352
|
+
readonly: nodes.readonly,
|
|
353
|
+
hidden: nodes.hidden,
|
|
354
|
+
archivedAt: nodes.archivedAt,
|
|
355
|
+
parentId: nodes.parentId,
|
|
356
|
+
assetId: nodes.assetId
|
|
357
|
+
});
|
|
358
|
+
return node ?? null;
|
|
359
|
+
},
|
|
360
|
+
/**
|
|
361
|
+
* Updates a node row by primary id.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* const updated = await database.updateNodeById(node.id, { name: "Updated name" });
|
|
365
|
+
*/
|
|
366
|
+
updateNodeById: async (id, data) => {
|
|
367
|
+
const [node] = await db.update(nodes).set(data).where(eq(nodes.id, id)).returning({
|
|
368
|
+
id: nodes.id,
|
|
369
|
+
createdAt: nodes.createdAt,
|
|
370
|
+
updatedAt: nodes.updatedAt,
|
|
371
|
+
name: nodes.name,
|
|
372
|
+
namespace: nodes.namespace,
|
|
373
|
+
type: nodes.type,
|
|
374
|
+
subtype: nodes.subtype,
|
|
375
|
+
size: nodes.size,
|
|
376
|
+
contentType: nodes.contentType,
|
|
377
|
+
readonly: nodes.readonly,
|
|
378
|
+
hidden: nodes.hidden,
|
|
379
|
+
archivedAt: nodes.archivedAt,
|
|
380
|
+
parentId: nodes.parentId,
|
|
381
|
+
assetId: nodes.assetId
|
|
382
|
+
});
|
|
383
|
+
return node ?? null;
|
|
384
|
+
},
|
|
385
|
+
/**
|
|
386
|
+
* Updates nodes matching the provided filters.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* const [updated] = await database.updateNodes({
|
|
390
|
+
* filters: { nodeIds: [node.id] },
|
|
391
|
+
* data: { name: "Updated name" },
|
|
392
|
+
* });
|
|
393
|
+
*/
|
|
394
|
+
updateNodes: async ({ data, filters }) => {
|
|
395
|
+
const where = parseDatabaseNodeFilters(filters);
|
|
396
|
+
if (!where) return [];
|
|
397
|
+
return await db.update(nodes).set(data).where(where).returning({
|
|
398
|
+
id: nodes.id,
|
|
399
|
+
createdAt: nodes.createdAt,
|
|
400
|
+
updatedAt: nodes.updatedAt,
|
|
401
|
+
name: nodes.name,
|
|
402
|
+
namespace: nodes.namespace,
|
|
403
|
+
type: nodes.type,
|
|
404
|
+
subtype: nodes.subtype,
|
|
405
|
+
size: nodes.size,
|
|
406
|
+
contentType: nodes.contentType,
|
|
407
|
+
readonly: nodes.readonly,
|
|
408
|
+
hidden: nodes.hidden,
|
|
409
|
+
archivedAt: nodes.archivedAt,
|
|
410
|
+
parentId: nodes.parentId,
|
|
411
|
+
assetId: nodes.assetId
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
/**
|
|
415
|
+
* Deletes a node by primary id and relies on database cascades for dependent rows.
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* await database.deleteNodeById(node.id);
|
|
419
|
+
*/
|
|
420
|
+
deleteNodeById: async (id) => {
|
|
421
|
+
await db.delete(nodes).where(eq(nodes.id, id));
|
|
422
|
+
},
|
|
423
|
+
/**
|
|
424
|
+
* Deletes nodes matching the provided filters.
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* await database.deleteNodes({ filters: { nodeIds: [node.id] } });
|
|
428
|
+
*/
|
|
429
|
+
deleteNodes: async ({ filters }) => {
|
|
430
|
+
const where = parseDatabaseNodeFilters(filters);
|
|
431
|
+
if (!where) return;
|
|
432
|
+
await db.delete(nodes).where(where);
|
|
433
|
+
},
|
|
434
|
+
/**
|
|
435
|
+
* Finds generated variants matching the provided filters.
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* const variants = await database.findVariants({ filters: { nodeIds: [node.id] } });
|
|
439
|
+
*/
|
|
440
|
+
findVariants: async (input = {}) => {
|
|
441
|
+
let query = db.select({
|
|
442
|
+
id: nodeVariants.id,
|
|
443
|
+
createdAt: nodeVariants.createdAt,
|
|
444
|
+
updatedAt: nodeVariants.updatedAt,
|
|
445
|
+
nodeId: nodeVariants.nodeId,
|
|
446
|
+
assetId: nodeVariants.assetId,
|
|
447
|
+
variant: nodeVariants.variant,
|
|
448
|
+
width: nodeVariants.width
|
|
449
|
+
}).from(nodeVariants).$dynamic();
|
|
450
|
+
if (input.filters) {
|
|
451
|
+
const where = parseVariantFilters(input.filters);
|
|
452
|
+
if (where) query = query.where(where);
|
|
453
|
+
}
|
|
454
|
+
if (input.limit !== void 0) query = query.limit(input.limit);
|
|
455
|
+
if (input.offset !== void 0) query = query.offset(input.offset);
|
|
456
|
+
return await query;
|
|
457
|
+
},
|
|
458
|
+
/**
|
|
459
|
+
* Records a generated image variant and its storage asset.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* await database.createVariant({
|
|
463
|
+
* nodeId: node.id,
|
|
464
|
+
* assetId: previewAsset.id,
|
|
465
|
+
* variant: "preview-640",
|
|
466
|
+
* width: 640,
|
|
467
|
+
* });
|
|
468
|
+
*/
|
|
469
|
+
createVariant: async (input) => {
|
|
470
|
+
await db.insert(nodeVariants).values(input);
|
|
471
|
+
},
|
|
472
|
+
/**
|
|
473
|
+
* Finds the first cached presigned URL matching the provided filters.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* const cached = await database.findFirstPresignedUrl({
|
|
477
|
+
* filters: { nodeId: node.id, variant: "main", disposition: "inline" },
|
|
478
|
+
* });
|
|
479
|
+
*/
|
|
480
|
+
findFirstPresignedUrl: async (input) => {
|
|
481
|
+
let query = db.select({
|
|
482
|
+
url: nodePresignedUrls.url,
|
|
483
|
+
expiresAt: nodePresignedUrls.expiresAt
|
|
484
|
+
}).from(nodePresignedUrls).$dynamic();
|
|
485
|
+
if (input?.filters) {
|
|
486
|
+
const where = parsePresignedUrlFilters(input.filters);
|
|
487
|
+
if (where) query = query.where(where);
|
|
488
|
+
}
|
|
489
|
+
const [presignedUrl] = await query.limit(1);
|
|
490
|
+
return presignedUrl ?? null;
|
|
491
|
+
},
|
|
492
|
+
/**
|
|
493
|
+
* Creates or refreshes the cached presigned URL for a node variant.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* await database.upsertPresignedUrl({
|
|
497
|
+
* nodeId: node.id,
|
|
498
|
+
* variant: "main",
|
|
499
|
+
* disposition: "attachment",
|
|
500
|
+
* url,
|
|
501
|
+
* expiresAt,
|
|
502
|
+
* });
|
|
503
|
+
*/
|
|
504
|
+
upsertPresignedUrl: async (input) => {
|
|
505
|
+
await db.insert(nodePresignedUrls).values(input).onConflictDoUpdate({
|
|
506
|
+
target: [
|
|
507
|
+
nodePresignedUrls.nodeId,
|
|
508
|
+
nodePresignedUrls.variant,
|
|
509
|
+
nodePresignedUrls.disposition
|
|
510
|
+
],
|
|
511
|
+
set: {
|
|
512
|
+
url: input.url,
|
|
513
|
+
expiresAt: input.expiresAt
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
//#endregion
|
|
520
|
+
export { localDriveDrizzleAdapter };
|