zerobucket 0.4.0 → 0.5.0
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/README.md +53 -20
- package/dist/bin.js +2506 -0
- package/dist/{client-WZ4VRCPi.d.cts → client-C7xs1dlr.d.cts} +49 -1
- package/dist/{client-WZ4VRCPi.d.ts → client-C7xs1dlr.d.ts} +49 -1
- package/dist/http.d.cts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/index.cjs +191 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
|
@@ -125,6 +125,50 @@ interface StreamOptions$1 extends CallOptions {
|
|
|
125
125
|
end: number;
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
/** Filters for maintenance listings (CLI `verify` / `tier`). All optional; combined with AND. */
|
|
129
|
+
interface ImageListOptions {
|
|
130
|
+
/** Return a random sample of this many images instead of all of them. */
|
|
131
|
+
sample?: number;
|
|
132
|
+
limit?: number;
|
|
133
|
+
/** Only images with size_bytes >= this. */
|
|
134
|
+
minSize?: number;
|
|
135
|
+
/** Only images created at least this many days ago. */
|
|
136
|
+
olderThanDays?: number;
|
|
137
|
+
/** Only images whose bytes are still in the database (not tiered). */
|
|
138
|
+
onlyUntiered?: boolean;
|
|
139
|
+
}
|
|
140
|
+
interface ImageListEntry {
|
|
141
|
+
id: string;
|
|
142
|
+
/** Bytes live in object storage, not in the database. */
|
|
143
|
+
tiered: boolean;
|
|
144
|
+
}
|
|
145
|
+
/** Storage statistics for `zerobucket info`. */
|
|
146
|
+
interface StorageInfo {
|
|
147
|
+
mode: "classic" | "dedup";
|
|
148
|
+
/** Number of images (references, in dedup mode). */
|
|
149
|
+
count: number;
|
|
150
|
+
/** Sum of every image's size_bytes as recorded by the application (logical size). */
|
|
151
|
+
totalBytes: number;
|
|
152
|
+
oldest: string | null;
|
|
153
|
+
newest: string | null;
|
|
154
|
+
/** Human-readable on-disk size, if the database can tell. */
|
|
155
|
+
onDisk: string | null;
|
|
156
|
+
byFormat: {
|
|
157
|
+
mimeType: string;
|
|
158
|
+
count: number;
|
|
159
|
+
bytes: number;
|
|
160
|
+
}[];
|
|
161
|
+
/** Classic mode: images currently living in object storage. */
|
|
162
|
+
tiered?: {
|
|
163
|
+
count: number;
|
|
164
|
+
bytes: number;
|
|
165
|
+
};
|
|
166
|
+
/** Dedup mode: distinct stored blobs and the bytes they physically occupy. */
|
|
167
|
+
dedup?: {
|
|
168
|
+
blobs: number;
|
|
169
|
+
storedBytes: number;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
128
172
|
interface StorageBackend {
|
|
129
173
|
/** Resolve once the backend is connected and migrated. Safe to call repeatedly. */
|
|
130
174
|
ready(): Promise<void>;
|
|
@@ -153,6 +197,10 @@ interface StorageBackend {
|
|
|
153
197
|
duplicateReferencesFound: number;
|
|
154
198
|
alreadyMigrated: number;
|
|
155
199
|
}>;
|
|
200
|
+
/** Maintenance: storage statistics, or null if the tables do not exist yet. */
|
|
201
|
+
getInfo?(): Promise<StorageInfo | null>;
|
|
202
|
+
/** Maintenance: list ids matching the filters (oldest first, or random when sampling). */
|
|
203
|
+
listImages?(opts?: ImageListOptions): Promise<ImageListEntry[]>;
|
|
156
204
|
close(): Promise<void>;
|
|
157
205
|
}
|
|
158
206
|
|
|
@@ -422,4 +470,4 @@ declare function readInput(image: ImageInput, filename: string | undefined, maxB
|
|
|
422
470
|
filename: string | undefined;
|
|
423
471
|
}>;
|
|
424
472
|
|
|
425
|
-
export { type BatchDeleteResult as B, type CallOptions as C, DEFAULT_MAX_BYTES as D, type GetOptions as G, type
|
|
473
|
+
export { type BatchDeleteResult as B, type CallOptions as C, DEFAULT_MAX_BYTES as D, type GetOptions as G, type ImageListOptions as I, type OperationEvent as O, type PreparedRow as P, type Queryable as Q, type StorageBackend as S, type ValidatedContent as V, type WritableLike as W, ZeroBucket as Z, type ObjectStorageLike as a, type StoredRecord as b, type StoredRecordMetadata as c, type StreamOptions$1 as d, type StorageInfo as e, type ImageListEntry as f, type ContentValidator as g, type BatchGetResult as h, type BatchPutResult as i, type BeforeGetHook as j, type BeforePutHook as k, type Connection as l, type Image as m, type ImageInput as n, type ImageMetadata as o, ObjectStorage as p, type ObjectStorageOptions as q, type OptimizationResult as r, type OptimizeOptions as s, type PutManyOptions as t, type PutOptions as u, type SqliteDatabaseLike as v, type StreamOptions as w, type ZeroBucketOptions as x, optimizeImage as y, readInput as z };
|
|
@@ -125,6 +125,50 @@ interface StreamOptions$1 extends CallOptions {
|
|
|
125
125
|
end: number;
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
/** Filters for maintenance listings (CLI `verify` / `tier`). All optional; combined with AND. */
|
|
129
|
+
interface ImageListOptions {
|
|
130
|
+
/** Return a random sample of this many images instead of all of them. */
|
|
131
|
+
sample?: number;
|
|
132
|
+
limit?: number;
|
|
133
|
+
/** Only images with size_bytes >= this. */
|
|
134
|
+
minSize?: number;
|
|
135
|
+
/** Only images created at least this many days ago. */
|
|
136
|
+
olderThanDays?: number;
|
|
137
|
+
/** Only images whose bytes are still in the database (not tiered). */
|
|
138
|
+
onlyUntiered?: boolean;
|
|
139
|
+
}
|
|
140
|
+
interface ImageListEntry {
|
|
141
|
+
id: string;
|
|
142
|
+
/** Bytes live in object storage, not in the database. */
|
|
143
|
+
tiered: boolean;
|
|
144
|
+
}
|
|
145
|
+
/** Storage statistics for `zerobucket info`. */
|
|
146
|
+
interface StorageInfo {
|
|
147
|
+
mode: "classic" | "dedup";
|
|
148
|
+
/** Number of images (references, in dedup mode). */
|
|
149
|
+
count: number;
|
|
150
|
+
/** Sum of every image's size_bytes as recorded by the application (logical size). */
|
|
151
|
+
totalBytes: number;
|
|
152
|
+
oldest: string | null;
|
|
153
|
+
newest: string | null;
|
|
154
|
+
/** Human-readable on-disk size, if the database can tell. */
|
|
155
|
+
onDisk: string | null;
|
|
156
|
+
byFormat: {
|
|
157
|
+
mimeType: string;
|
|
158
|
+
count: number;
|
|
159
|
+
bytes: number;
|
|
160
|
+
}[];
|
|
161
|
+
/** Classic mode: images currently living in object storage. */
|
|
162
|
+
tiered?: {
|
|
163
|
+
count: number;
|
|
164
|
+
bytes: number;
|
|
165
|
+
};
|
|
166
|
+
/** Dedup mode: distinct stored blobs and the bytes they physically occupy. */
|
|
167
|
+
dedup?: {
|
|
168
|
+
blobs: number;
|
|
169
|
+
storedBytes: number;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
128
172
|
interface StorageBackend {
|
|
129
173
|
/** Resolve once the backend is connected and migrated. Safe to call repeatedly. */
|
|
130
174
|
ready(): Promise<void>;
|
|
@@ -153,6 +197,10 @@ interface StorageBackend {
|
|
|
153
197
|
duplicateReferencesFound: number;
|
|
154
198
|
alreadyMigrated: number;
|
|
155
199
|
}>;
|
|
200
|
+
/** Maintenance: storage statistics, or null if the tables do not exist yet. */
|
|
201
|
+
getInfo?(): Promise<StorageInfo | null>;
|
|
202
|
+
/** Maintenance: list ids matching the filters (oldest first, or random when sampling). */
|
|
203
|
+
listImages?(opts?: ImageListOptions): Promise<ImageListEntry[]>;
|
|
156
204
|
close(): Promise<void>;
|
|
157
205
|
}
|
|
158
206
|
|
|
@@ -422,4 +470,4 @@ declare function readInput(image: ImageInput, filename: string | undefined, maxB
|
|
|
422
470
|
filename: string | undefined;
|
|
423
471
|
}>;
|
|
424
472
|
|
|
425
|
-
export { type BatchDeleteResult as B, type CallOptions as C, DEFAULT_MAX_BYTES as D, type GetOptions as G, type
|
|
473
|
+
export { type BatchDeleteResult as B, type CallOptions as C, DEFAULT_MAX_BYTES as D, type GetOptions as G, type ImageListOptions as I, type OperationEvent as O, type PreparedRow as P, type Queryable as Q, type StorageBackend as S, type ValidatedContent as V, type WritableLike as W, ZeroBucket as Z, type ObjectStorageLike as a, type StoredRecord as b, type StoredRecordMetadata as c, type StreamOptions$1 as d, type StorageInfo as e, type ImageListEntry as f, type ContentValidator as g, type BatchGetResult as h, type BatchPutResult as i, type BeforeGetHook as j, type BeforePutHook as k, type Connection as l, type Image as m, type ImageInput as n, type ImageMetadata as o, ObjectStorage as p, type ObjectStorageOptions as q, type OptimizationResult as r, type OptimizeOptions as s, type PutManyOptions as t, type PutOptions as u, type SqliteDatabaseLike as v, type StreamOptions as w, type ZeroBucketOptions as x, optimizeImage as y, readInput as z };
|
package/dist/http.d.cts
CHANGED
package/dist/http.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -187,6 +187,17 @@ async function* streamFromObjectStorage(os, id, key, start, end, chunkSize) {
|
|
|
187
187
|
offset = last + 1;
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
+
function prettySize(bytes) {
|
|
191
|
+
if (bytes < 1024) return `${bytes} bytes`;
|
|
192
|
+
const units = ["kB", "MB", "GB", "TB"];
|
|
193
|
+
let v = bytes / 1024;
|
|
194
|
+
let i = 0;
|
|
195
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
196
|
+
v /= 1024;
|
|
197
|
+
i += 1;
|
|
198
|
+
}
|
|
199
|
+
return `${v.toFixed(1)} ${units[i]}`;
|
|
200
|
+
}
|
|
190
201
|
|
|
191
202
|
// src/adapters/postgres.ts
|
|
192
203
|
var SCHEMA = `
|
|
@@ -821,6 +832,95 @@ var PostgresBackend = class {
|
|
|
821
832
|
throw wrap("Failed to check image existence", exc);
|
|
822
833
|
}
|
|
823
834
|
}
|
|
835
|
+
// ---- maintenance (CLI) -----------------------------------------------
|
|
836
|
+
async getInfo() {
|
|
837
|
+
try {
|
|
838
|
+
return await this.run("info", void 0, async (q) => {
|
|
839
|
+
const imgTable = this.dedup ? "zerobucket_image_refs" : "zerobucket_images";
|
|
840
|
+
const present = await q.query(
|
|
841
|
+
"SELECT to_regclass($1) AS a, to_regclass('zerobucket_blobs') AS b",
|
|
842
|
+
[imgTable]
|
|
843
|
+
);
|
|
844
|
+
if (!present.rows[0].a || this.dedup && !present.rows[0].b)
|
|
845
|
+
return null;
|
|
846
|
+
const j = this.dedup ? "FROM zerobucket_image_refs r JOIN zerobucket_blobs b ON r.checksum_sha256 = b.checksum_sha256" : "FROM zerobucket_images";
|
|
847
|
+
const size = this.dedup ? "b.size_bytes" : "size_bytes";
|
|
848
|
+
const created = this.dedup ? "r.created_at" : "created_at";
|
|
849
|
+
const mime = this.dedup ? "b.mime_type" : "mime_type";
|
|
850
|
+
const tot = (await q.query(
|
|
851
|
+
`SELECT count(*)::int AS n, coalesce(sum(${size}), 0)::text AS bytes, min(${created})::text AS oldest, max(${created})::text AS newest ${j}`
|
|
852
|
+
)).rows[0];
|
|
853
|
+
const disk = (await q.query(
|
|
854
|
+
this.dedup ? "SELECT pg_size_pretty(pg_total_relation_size('zerobucket_blobs') + pg_total_relation_size('zerobucket_image_refs')) AS s" : "SELECT pg_size_pretty(pg_total_relation_size('zerobucket_images')) AS s"
|
|
855
|
+
)).rows[0].s;
|
|
856
|
+
const fmt = (await q.query(
|
|
857
|
+
`SELECT ${mime} AS m, count(*)::int AS n, coalesce(sum(${size}), 0)::text AS bytes ${j} GROUP BY ${mime} ORDER BY count(*) DESC, ${mime}`
|
|
858
|
+
)).rows;
|
|
859
|
+
const info = {
|
|
860
|
+
mode: this.dedup ? "dedup" : "classic",
|
|
861
|
+
count: tot.n,
|
|
862
|
+
totalBytes: Number(tot.bytes),
|
|
863
|
+
// Postgres prints the offset as "+00"; Python's CLI (and ISO-8601) print "+00:00". Normalise so output is identical.
|
|
864
|
+
oldest: tot.oldest && tot.oldest.replace(/([+-]\d\d)$/, "$1:00"),
|
|
865
|
+
newest: tot.newest && tot.newest.replace(/([+-]\d\d)$/, "$1:00"),
|
|
866
|
+
onDisk: disk,
|
|
867
|
+
byFormat: fmt.map((r) => ({
|
|
868
|
+
mimeType: r.m,
|
|
869
|
+
count: r.n,
|
|
870
|
+
bytes: Number(r.bytes)
|
|
871
|
+
}))
|
|
872
|
+
};
|
|
873
|
+
if (this.dedup) {
|
|
874
|
+
const b = (await q.query(
|
|
875
|
+
"SELECT count(*)::int AS n, coalesce(sum(size_bytes), 0)::text AS bytes FROM zerobucket_blobs"
|
|
876
|
+
)).rows[0];
|
|
877
|
+
info.dedup = { blobs: b.n, storedBytes: Number(b.bytes) };
|
|
878
|
+
} else {
|
|
879
|
+
const t = (await q.query(
|
|
880
|
+
"SELECT count(*)::int AS n, coalesce(sum(size_bytes), 0)::text AS bytes FROM zerobucket_images WHERE storage_backend = 'object_storage'"
|
|
881
|
+
)).rows[0];
|
|
882
|
+
info.tiered = { count: t.n, bytes: Number(t.bytes) };
|
|
883
|
+
}
|
|
884
|
+
return info;
|
|
885
|
+
});
|
|
886
|
+
} catch (exc) {
|
|
887
|
+
throw wrap("Failed to read storage info", exc);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async listImages(o = {}) {
|
|
891
|
+
const where = [];
|
|
892
|
+
const params = [];
|
|
893
|
+
const add = (sql2, v) => (params.push(v), where.push(sql2.replace("$?", `$${params.length}`)));
|
|
894
|
+
if (o.minSize !== void 0)
|
|
895
|
+
add(`${this.dedup ? "b.size_bytes" : "size_bytes"} >= $?`, o.minSize);
|
|
896
|
+
if (o.olderThanDays !== void 0)
|
|
897
|
+
add(
|
|
898
|
+
`${this.dedup ? "r.created_at" : "created_at"} <= now() - make_interval(days => $?::int)`,
|
|
899
|
+
o.olderThanDays
|
|
900
|
+
);
|
|
901
|
+
if (o.onlyUntiered && !this.dedup)
|
|
902
|
+
where.push("storage_backend = 'postgres'");
|
|
903
|
+
const from = this.dedup ? "FROM zerobucket_image_refs r JOIN zerobucket_blobs b ON r.checksum_sha256 = b.checksum_sha256" : "FROM zerobucket_images";
|
|
904
|
+
const idCol = this.dedup ? "r.id" : "id";
|
|
905
|
+
const tierCol = this.dedup ? "false AS tiered" : "(storage_backend = 'object_storage') AS tiered";
|
|
906
|
+
const order = o.sample !== void 0 ? "ORDER BY random()" : `ORDER BY ${this.dedup ? "r.created_at" : "created_at"}, ${idCol}`;
|
|
907
|
+
const lim = o.sample ?? o.limit;
|
|
908
|
+
let sql = `SELECT ${idCol} AS id, ${tierCol} ${from}${where.length ? " WHERE " + where.join(" AND ") : ""} ${order}`;
|
|
909
|
+
if (lim !== void 0) {
|
|
910
|
+
params.push(lim);
|
|
911
|
+
sql += ` LIMIT $${params.length}`;
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
const rows = await this.run(
|
|
915
|
+
"list_images",
|
|
916
|
+
void 0,
|
|
917
|
+
async (q) => (await q.query(sql, params)).rows
|
|
918
|
+
);
|
|
919
|
+
return rows.map((r) => ({ id: String(r.id), tiered: Boolean(r.tiered) }));
|
|
920
|
+
} catch (exc) {
|
|
921
|
+
throw wrap("Failed to list images", exc);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
824
924
|
// ---- dedup deletes / migration ---------------------------------------
|
|
825
925
|
async deleteDedup(id, connection) {
|
|
826
926
|
try {
|
|
@@ -1102,6 +1202,7 @@ function isQueryable(c) {
|
|
|
1102
1202
|
|
|
1103
1203
|
// src/adapters/sqlite.ts
|
|
1104
1204
|
var import_node_crypto = require("crypto");
|
|
1205
|
+
var import_node_fs = require("fs");
|
|
1105
1206
|
var SCHEMA2 = `
|
|
1106
1207
|
CREATE TABLE IF NOT EXISTS zerobucket_images (
|
|
1107
1208
|
id TEXT PRIMARY KEY,
|
|
@@ -1688,6 +1789,96 @@ var SQLiteBackend = class {
|
|
|
1688
1789
|
throw wrap("Failed to check image existence", exc);
|
|
1689
1790
|
}
|
|
1690
1791
|
}
|
|
1792
|
+
// ---- maintenance (CLI) -----------------------------------------------
|
|
1793
|
+
async getInfo() {
|
|
1794
|
+
try {
|
|
1795
|
+
return await this.execute("info", void 0, "read", (db) => {
|
|
1796
|
+
const has = (t) => db.prepare(
|
|
1797
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?"
|
|
1798
|
+
).get(t) !== void 0;
|
|
1799
|
+
if (!has(this.dedup ? "zerobucket_image_refs" : "zerobucket_images") || this.dedup && !has("zerobucket_blobs"))
|
|
1800
|
+
return null;
|
|
1801
|
+
const j = this.dedup ? D_FROM2 : "FROM zerobucket_images";
|
|
1802
|
+
const size = this.dedup ? "b.size_bytes" : "size_bytes";
|
|
1803
|
+
const created = this.dedup ? "r.created_at" : "created_at";
|
|
1804
|
+
const mime = this.dedup ? "b.mime_type" : "mime_type";
|
|
1805
|
+
const tot = db.prepare(
|
|
1806
|
+
`SELECT count(*) AS n, coalesce(sum(${size}), 0) AS bytes, min(${created}) AS oldest, max(${created}) AS newest ${j}`
|
|
1807
|
+
).get();
|
|
1808
|
+
const fmt = db.prepare(
|
|
1809
|
+
`SELECT ${mime} AS m, count(*) AS n, coalesce(sum(${size}), 0) AS bytes ${j} GROUP BY ${mime} ORDER BY count(*) DESC, ${mime}`
|
|
1810
|
+
).all();
|
|
1811
|
+
let onDisk = null;
|
|
1812
|
+
if (this.opts.path !== ":memory:") {
|
|
1813
|
+
let total = (0, import_node_fs.statSync)(this.opts.path).size;
|
|
1814
|
+
for (const suffix of ["-wal", "-shm"])
|
|
1815
|
+
if ((0, import_node_fs.existsSync)(this.opts.path + suffix))
|
|
1816
|
+
total += (0, import_node_fs.statSync)(this.opts.path + suffix).size;
|
|
1817
|
+
onDisk = prettySize(total);
|
|
1818
|
+
}
|
|
1819
|
+
const info = {
|
|
1820
|
+
mode: this.dedup ? "dedup" : "classic",
|
|
1821
|
+
count: tot.n,
|
|
1822
|
+
totalBytes: Number(tot.bytes),
|
|
1823
|
+
oldest: tot.oldest,
|
|
1824
|
+
newest: tot.newest,
|
|
1825
|
+
onDisk,
|
|
1826
|
+
byFormat: fmt.map((r) => ({
|
|
1827
|
+
mimeType: r.m,
|
|
1828
|
+
count: r.n,
|
|
1829
|
+
bytes: Number(r.bytes)
|
|
1830
|
+
}))
|
|
1831
|
+
};
|
|
1832
|
+
if (this.dedup) {
|
|
1833
|
+
const b = db.prepare(
|
|
1834
|
+
"SELECT count(*) AS n, coalesce(sum(size_bytes), 0) AS bytes FROM zerobucket_blobs"
|
|
1835
|
+
).get();
|
|
1836
|
+
info.dedup = { blobs: b.n, storedBytes: Number(b.bytes) };
|
|
1837
|
+
} else {
|
|
1838
|
+
const t = db.prepare(
|
|
1839
|
+
"SELECT count(*) AS n, coalesce(sum(size_bytes), 0) AS bytes FROM zerobucket_images WHERE storage_backend = 'object_storage'"
|
|
1840
|
+
).get();
|
|
1841
|
+
info.tiered = { count: t.n, bytes: Number(t.bytes) };
|
|
1842
|
+
}
|
|
1843
|
+
return info;
|
|
1844
|
+
});
|
|
1845
|
+
} catch (exc) {
|
|
1846
|
+
throw wrap("Failed to read storage info", exc);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
async listImages(o = {}) {
|
|
1850
|
+
const where = [];
|
|
1851
|
+
const params = [];
|
|
1852
|
+
if (o.minSize !== void 0)
|
|
1853
|
+
where.push(`${this.dedup ? "b.size_bytes" : "size_bytes"} >= ?`), params.push(o.minSize);
|
|
1854
|
+
if (o.olderThanDays !== void 0) {
|
|
1855
|
+
const cutoff = new Date(Date.now() - o.olderThanDays * 864e5).toISOString().replace("Z", "000+00:00");
|
|
1856
|
+
where.push(`${this.dedup ? "r.created_at" : "created_at"} <= ?`);
|
|
1857
|
+
params.push(cutoff);
|
|
1858
|
+
}
|
|
1859
|
+
if (o.onlyUntiered && !this.dedup) where.push("storage_backend = 'sqlite'");
|
|
1860
|
+
const from = this.dedup ? D_FROM2 : "FROM zerobucket_images";
|
|
1861
|
+
const idCol = this.dedup ? "r.id" : "id";
|
|
1862
|
+
const tierCol = this.dedup ? "0 AS tiered" : "(storage_backend = 'object_storage') AS tiered";
|
|
1863
|
+
const order = o.sample !== void 0 ? "ORDER BY random()" : `ORDER BY ${this.dedup ? "r.created_at" : "created_at"}, ${idCol}`;
|
|
1864
|
+
const lim = o.sample ?? o.limit;
|
|
1865
|
+
let sql = `SELECT ${idCol} AS id, ${tierCol} ${from}${where.length ? " WHERE " + where.join(" AND ") : ""} ${order}`;
|
|
1866
|
+
if (lim !== void 0) sql += " LIMIT ?", params.push(lim);
|
|
1867
|
+
try {
|
|
1868
|
+
const rows = await this.execute(
|
|
1869
|
+
"list_images",
|
|
1870
|
+
void 0,
|
|
1871
|
+
"read",
|
|
1872
|
+
(db) => db.prepare(sql).all(...params)
|
|
1873
|
+
);
|
|
1874
|
+
return rows.map((r) => ({
|
|
1875
|
+
id: String(r.id),
|
|
1876
|
+
tiered: Boolean(r.tiered)
|
|
1877
|
+
}));
|
|
1878
|
+
} catch (exc) {
|
|
1879
|
+
throw wrap("Failed to list images", exc);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1691
1882
|
/**
|
|
1692
1883
|
* Remove tiered objects AFTER their rows are gone (a failed object delete leaves a harmless orphan; the
|
|
1693
1884
|
* reverse order could leave a row pointing at nothing). Skipped inside a caller-owned transaction, since the
|