@maravilla-labs/platform 0.14.0 → 0.16.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/CHANGELOG.md +37 -0
- package/dist/config.d.ts +42 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +16 -7
- package/dist/index.js +80 -1
- package/dist/index.js.map +1 -1
- package/dist/{transforms-BHWez32E.d.ts → transforms-DkAlUPu6.d.ts} +196 -1
- package/package.json +1 -1
- package/src/config.ts +40 -0
- package/src/remote-client.ts +62 -7
- package/src/transforms.ts +249 -0
- package/src/types.ts +21 -12
- package/tests/focal-css.test.ts +106 -0
|
@@ -133,12 +133,109 @@ interface ResizeOpts {
|
|
|
133
133
|
format: ImageFormat;
|
|
134
134
|
quality?: number;
|
|
135
135
|
strip_metadata?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Crop strategy when BOTH `width` and `height` are given (the output is
|
|
138
|
+
* cropped to fill the exact box rather than letterboxed):
|
|
139
|
+
* - `'center'` — crop around the geometric center.
|
|
140
|
+
* - `'focal'` — crop around the source's focal point. The server injects
|
|
141
|
+
* the focal point from the persisted {@link FocalPointRecord} (set via
|
|
142
|
+
* `detectFaces` or `setFocalPoint`); if none exists it falls back to
|
|
143
|
+
* center.
|
|
144
|
+
* Omit for the default aspect-preserving fit (no crop).
|
|
145
|
+
*/
|
|
146
|
+
crop?: 'focal' | 'center';
|
|
147
|
+
/**
|
|
148
|
+
* Explicit focal point for `crop: 'focal'`, normalized `0..=1`. Server-
|
|
149
|
+
* injected — callers normally omit this and let the platform resolve the
|
|
150
|
+
* stored focal point for `src_key`. Present here so the wire type mirrors
|
|
151
|
+
* the Rust `ResizeOpts` exactly.
|
|
152
|
+
*/
|
|
153
|
+
focal?: FocalPoint;
|
|
154
|
+
/**
|
|
155
|
+
* Face-box-relative crop zoom for `crop: 'focal'`. A scale factor (`>= 1.0`)
|
|
156
|
+
* on the smallest output-aspect frame that fully contains the source's
|
|
157
|
+
* detected face(s): `1.0` frames tightly around the subject; larger values
|
|
158
|
+
* pull back for more headroom/context; a value large enough to exceed the
|
|
159
|
+
* image saturates to the plain scale-to-cover crop. Only meaningful with
|
|
160
|
+
* `crop: 'focal'` on a source that has stored face data (from `detectFaces`);
|
|
161
|
+
* ignored otherwise. Omit for the legacy point-only crop — when unset it does
|
|
162
|
+
* not affect the derived key, so existing crops are unchanged.
|
|
163
|
+
*/
|
|
164
|
+
zoom?: number;
|
|
165
|
+
/**
|
|
166
|
+
* Union bounding box of the source's detected faces, normalized `0..=1`.
|
|
167
|
+
* Server-injected (like {@link ResizeOpts.focal}) and only when `zoom` is set
|
|
168
|
+
* — callers never pass this. Present so the wire type mirrors the Rust
|
|
169
|
+
* `ResizeOpts` exactly.
|
|
170
|
+
*/
|
|
171
|
+
subject_box?: SubjectBox;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Union bounding box of a source's detected faces, normalized `0..=1`
|
|
175
|
+
* (top-left `x`/`y` + `w`/`h`). Sizes the `crop: 'focal'` {@link
|
|
176
|
+
* ResizeOpts.zoom} window; server-injected only.
|
|
177
|
+
*/
|
|
178
|
+
interface SubjectBox {
|
|
179
|
+
x: number;
|
|
180
|
+
y: number;
|
|
181
|
+
w: number;
|
|
182
|
+
h: number;
|
|
136
183
|
}
|
|
137
184
|
/** Options for `transforms.ocr`. */
|
|
138
185
|
interface OcrOpts {
|
|
139
186
|
/** Tesseract language code(s). Defaults to `"eng"` server-side when omitted. */
|
|
140
187
|
lang?: string;
|
|
141
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Options for `transforms.detectFaces` — run face detection over an image and
|
|
191
|
+
* persist the resulting focal point (the center of the highest-scoring face,
|
|
192
|
+
* or the centroid of all faces) as a {@link FocalPointRecord} keyed on the
|
|
193
|
+
* source. The derived output is a JSON artifact listing the detected boxes.
|
|
194
|
+
*/
|
|
195
|
+
interface DetectFacesOpts {
|
|
196
|
+
/** Minimum detector confidence to keep a face, `0..=1`. Defaults to `0.6` server-side. */
|
|
197
|
+
min_score?: number;
|
|
198
|
+
/** Cap on the number of faces retained (highest-scoring first). Defaults to `32` server-side. */
|
|
199
|
+
max_faces?: number;
|
|
200
|
+
/** Trade accuracy for latency (smaller detector input). Defaults to `false` server-side. */
|
|
201
|
+
fast?: boolean;
|
|
202
|
+
}
|
|
203
|
+
/** A focal point in normalized image coordinates, each component `0..=1`. */
|
|
204
|
+
interface FocalPoint {
|
|
205
|
+
x: number;
|
|
206
|
+
y: number;
|
|
207
|
+
}
|
|
208
|
+
/** A single detected face box, normalized to `0..1` of the image dimensions. */
|
|
209
|
+
interface FaceBox {
|
|
210
|
+
x: number;
|
|
211
|
+
y: number;
|
|
212
|
+
w: number;
|
|
213
|
+
h: number;
|
|
214
|
+
/** Detector confidence, `0..1`. */
|
|
215
|
+
score: number;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Persisted focal-point record for a source image — the result of
|
|
219
|
+
* `detectFaces` or a manual `setFocalPoint`. Returned by `getFocalPoint`.
|
|
220
|
+
*/
|
|
221
|
+
interface FocalPointRecord {
|
|
222
|
+
/** Storage key of the source image this focal point belongs to. */
|
|
223
|
+
src_key: string;
|
|
224
|
+
/** The resolved focal point, normalized `0..=1`. */
|
|
225
|
+
focal: FocalPoint;
|
|
226
|
+
/** Whether the point came from face detection or a manual override. */
|
|
227
|
+
source: 'faces' | 'manual';
|
|
228
|
+
/** Number of faces detected (`null` until a detection has run for this key). */
|
|
229
|
+
face_count: number | null;
|
|
230
|
+
/** The detected face boxes, when `source === 'faces'`. */
|
|
231
|
+
faces?: FaceBox[];
|
|
232
|
+
/** Content hash of the source bytes the detection ran against. */
|
|
233
|
+
src_hash?: string;
|
|
234
|
+
/** Version tag of the detector that produced this record. */
|
|
235
|
+
detector_version?: string;
|
|
236
|
+
/** Unix-epoch milliseconds the record was last written. */
|
|
237
|
+
updated_at: number;
|
|
238
|
+
}
|
|
142
239
|
/**
|
|
143
240
|
* LibreOffice-supported output container for `doc_convert`. Each value
|
|
144
241
|
* maps to a `--convert-to` filter and a derived-key extension.
|
|
@@ -263,6 +360,8 @@ type TransformSpec = ({
|
|
|
263
360
|
} & ExtractAudioOpts) | ({
|
|
264
361
|
kind: 'resize';
|
|
265
362
|
} & ResizeOpts) | ({
|
|
363
|
+
kind: 'detect_faces';
|
|
364
|
+
} & DetectFacesOpts) | ({
|
|
266
365
|
kind: 'ocr';
|
|
267
366
|
} & OcrOpts) | ({
|
|
268
367
|
kind: 'doc_to_pdf';
|
|
@@ -320,6 +419,30 @@ interface TransformsService {
|
|
|
320
419
|
resize(srcKey: string, opts: ResizeOpts): Promise<JobHandle>;
|
|
321
420
|
probe(srcKey: string): Promise<MediaInfo>;
|
|
322
421
|
ocr(srcKey: string, opts?: OcrOpts | null): Promise<JobHandle>;
|
|
422
|
+
/**
|
|
423
|
+
* Run face detection over an image and persist the resulting focal point as
|
|
424
|
+
* a {@link FocalPointRecord}. Resolves to a `JobHandle` whose `output_key`
|
|
425
|
+
* holds a JSON artifact of the detected {@link FaceBox | boxes} once
|
|
426
|
+
* complete. Subsequent `resize` calls with `crop: 'focal'` on the same
|
|
427
|
+
* source pick up the stored focal point automatically.
|
|
428
|
+
*/
|
|
429
|
+
detectFaces(srcKey: string, opts?: DetectFacesOpts | null): Promise<JobHandle>;
|
|
430
|
+
/**
|
|
431
|
+
* Manually set (or override) the focal point for a source image, normalized
|
|
432
|
+
* `0..=1`. Persists a {@link FocalPointRecord} with `source: 'manual'`.
|
|
433
|
+
* Resolves to `null`.
|
|
434
|
+
*/
|
|
435
|
+
setFocalPoint(srcKey: string, point: FocalPoint): Promise<null>;
|
|
436
|
+
/**
|
|
437
|
+
* Read the persisted {@link FocalPointRecord} for a source, or `null` when
|
|
438
|
+
* none has been set (neither `detectFaces` nor `setFocalPoint` has run).
|
|
439
|
+
*/
|
|
440
|
+
getFocalPoint(srcKey: string): Promise<FocalPointRecord | null>;
|
|
441
|
+
/**
|
|
442
|
+
* Remove the persisted focal point for a source. Resolves to `true` when a
|
|
443
|
+
* record was deleted, `false` when there was nothing to clear.
|
|
444
|
+
*/
|
|
445
|
+
clearFocalPoint(srcKey: string): Promise<boolean>;
|
|
323
446
|
/** Convert a LibreOffice-supported document to PDF. */
|
|
324
447
|
docToPdf(srcKey: string, opts?: DocToPdfOpts | null): Promise<JobHandle>;
|
|
325
448
|
/** Render a single page of a document as a raster thumbnail. */
|
|
@@ -349,9 +472,71 @@ interface TransformsService {
|
|
|
349
472
|
* `crates/platform/tests/derive_key_vectors.json`.
|
|
350
473
|
*/
|
|
351
474
|
declare function keyFor(srcKey: string, spec: TransformSpec): string;
|
|
475
|
+
/** Inputs for {@link focalCssFrame}. */
|
|
476
|
+
interface FocalCssFrameInput {
|
|
477
|
+
/** The source's focal point (from `detectFaces`/`getFocalPoint` or manual). */
|
|
478
|
+
focal: FocalPoint;
|
|
479
|
+
/** Detected face boxes (from the same record). Empty/absent → plain cover. */
|
|
480
|
+
faces?: FaceBox[] | null;
|
|
481
|
+
/**
|
|
482
|
+
* Source image intrinsic pixel dimensions — only their ratio is used. In the
|
|
483
|
+
* browser these are free from a loaded `<img>` (`naturalWidth`/`Height`);
|
|
484
|
+
* `detectFaces`' output artifact also carries them.
|
|
485
|
+
*/
|
|
486
|
+
imgWidth: number;
|
|
487
|
+
imgHeight: number;
|
|
488
|
+
/** Target container aspect ratio, `width / height`. */
|
|
489
|
+
containerAspect: number;
|
|
490
|
+
/**
|
|
491
|
+
* Frame zoom, clamped to `>= 1`. `1` frames tightly around the face(s); larger
|
|
492
|
+
* pulls back toward the plain cover view. Defaults to `1`. Mirrors the
|
|
493
|
+
* server-side `resize({ crop: 'focal', zoom })` semantics.
|
|
494
|
+
*/
|
|
495
|
+
zoom?: number;
|
|
496
|
+
}
|
|
497
|
+
/** CSS values produced by {@link focalCssFrame}. */
|
|
498
|
+
interface FocalCssFrame {
|
|
499
|
+
/** `object-position` value, e.g. `"44.0% 27.0%"`. */
|
|
500
|
+
objectPosition: string;
|
|
501
|
+
/** `transform: scale(...)` factor to apply on top of `object-fit: cover`. */
|
|
502
|
+
scale: number;
|
|
503
|
+
/** `transform-origin` — same anchor as `objectPosition`. */
|
|
504
|
+
transformOrigin: string;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Union bounding box of detected faces, clamped to `0..1`; `null` for an empty
|
|
508
|
+
* list or a degenerate (zero-area) union. Sizes the {@link focalCssFrame} zoom
|
|
509
|
+
* window; mirrors the server-side union used by `resize({ crop:'focal', zoom })`.
|
|
510
|
+
*/
|
|
511
|
+
declare function unionFaceBox(faces?: FaceBox[] | null): SubjectBox | null;
|
|
512
|
+
/**
|
|
513
|
+
* Compute CSS values that frame a source image on its detected face(s) for a
|
|
514
|
+
* container of ANY aspect ratio — the browser-side counterpart to the server's
|
|
515
|
+
* `resize({ crop: 'focal', zoom })`. Instead of a pre-rendered crop per shape,
|
|
516
|
+
* apply the result to a single `<img>`:
|
|
517
|
+
*
|
|
518
|
+
* ```html
|
|
519
|
+
* <div style="aspect-ratio: 3/1; overflow: hidden">
|
|
520
|
+
* <img
|
|
521
|
+
* style="width:100%; height:100%; object-fit:cover;
|
|
522
|
+
* object-position: {objectPosition}; transform-origin: {transformOrigin};
|
|
523
|
+
* transform: scale({scale})" />
|
|
524
|
+
* </div>
|
|
525
|
+
* ```
|
|
526
|
+
*
|
|
527
|
+
* `object-position` pans the cover image onto the focal point; the extra
|
|
528
|
+
* `transform: scale` tightens the frame around the face(s) per `zoom`, scaling
|
|
529
|
+
* about the same anchor so the face stays put. Framework-agnostic: it returns
|
|
530
|
+
* plain CSS values — apply them however your framework applies styles.
|
|
531
|
+
*
|
|
532
|
+
* With no detected faces it degrades to a plain focal-point cover (`scale: 1`).
|
|
533
|
+
*/
|
|
534
|
+
declare function focalCssFrame(input: FocalCssFrameInput): FocalCssFrame;
|
|
352
535
|
/** Convenience namespace mirroring the Rust `transforms::keyFor` re-export. */
|
|
353
536
|
declare const transforms: {
|
|
354
537
|
keyFor: typeof keyFor;
|
|
538
|
+
focalCssFrame: typeof focalCssFrame;
|
|
539
|
+
unionFaceBox: typeof unionFaceBox;
|
|
355
540
|
};
|
|
356
541
|
/**
|
|
357
542
|
* Per-pattern transforms declaration used inside the `transforms` block of
|
|
@@ -367,6 +552,16 @@ interface TransformsPatternSpec {
|
|
|
367
552
|
/** Sugar for `resize` arrays — same shape, same semantics. */
|
|
368
553
|
variants?: ResizeOpts[];
|
|
369
554
|
ocr?: OcrOpts | OcrOpts[];
|
|
555
|
+
/**
|
|
556
|
+
* Run face detection on matching uploads and persist a focal point so
|
|
557
|
+
* `resize`/`variants` entries with `crop: 'focal'` crop around faces. Pass
|
|
558
|
+
* `true` for detector defaults, or an object to tune the detection.
|
|
559
|
+
*/
|
|
560
|
+
detectFaces?: boolean | {
|
|
561
|
+
min_score?: number;
|
|
562
|
+
max_faces?: number;
|
|
563
|
+
fast?: boolean;
|
|
564
|
+
};
|
|
370
565
|
}
|
|
371
566
|
/**
|
|
372
567
|
* Top-level `transforms` block. Keys are glob path patterns matched against
|
|
@@ -396,4 +591,4 @@ interface TransformsPatternSpec {
|
|
|
396
591
|
*/
|
|
397
592
|
type TransformsConfig = Record<string, TransformsPatternSpec>;
|
|
398
593
|
|
|
399
|
-
export { type DetachAudioOpts as D, type ExtractFramesOpts as E, type FramesetManifest as F, type ImageFormat as I, type JobStatus as J, type MediaInfo as M, type OcrOpts as O, type QrCodeSpec as Q, type ResizeOpts as R, type TransformsConfig as T, type VideoFormat as V, type TransformsPatternSpec as a, type TranscodeOpts as b, type ThumbnailOpts as c, type ExtractAudioOpts as d, type
|
|
594
|
+
export { unionFaceBox as A, focalCssFrame as B, transforms as C, type DetachAudioOpts as D, type ExtractFramesOpts as E, type FramesetManifest as F, type ImageFormat as I, type JobStatus as J, type MediaInfo as M, type OcrOpts as O, type QrCodeSpec as Q, type ResizeOpts as R, type SubjectBox as S, type TransformsConfig as T, type VideoFormat as V, type TransformsPatternSpec as a, type TranscodeOpts as b, type ThumbnailOpts as c, type ExtractAudioOpts as d, type DetectFacesOpts as e, type FocalPoint as f, type FaceBox as g, type FocalPointRecord as h, type DocFormat as i, type DocToPdfOpts as j, type DocThumbnailOpts as k, type DocConvertOpts as l, type DocToMarkdownOpts as m, type DocToHtmlOpts as n, type ImageRef as o, type DocReplaceImagesOpts as p, type DocInsertQrCodeOpts as q, type QrPayload as r, type DocTemplateMergeOpts as s, type TransformSpec as t, type JobHandle as u, type JobStatusResponse as v, type TransformsService as w, keyFor as x, type FocalCssFrameInput as y, type FocalCssFrame as z };
|
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -393,6 +393,46 @@ export interface SessionConfigDefinition {
|
|
|
393
393
|
export interface SecurityConfig {
|
|
394
394
|
password_policy?: PasswordPolicyDefinition;
|
|
395
395
|
session?: SessionConfigDefinition;
|
|
396
|
+
signup?: SignupProtectionConfig;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Bot protection for the registration endpoints, applied to both the hosted
|
|
401
|
+
* `/_auth/register` form and the `POST /api/platform/auth/register` JSON API.
|
|
402
|
+
*
|
|
403
|
+
* These layers are not equally strong:
|
|
404
|
+
*
|
|
405
|
+
* - `honeypot` (and its submit-timing check) stops untargeted form spam — bots
|
|
406
|
+
* that scrape the HTML and fill every field. Anyone who looks at the page
|
|
407
|
+
* defeats it. It is free, so it is on by default, but it is a filter, not a
|
|
408
|
+
* gate.
|
|
409
|
+
* - `rate_limit` bounds how fast anyone can register. On by default.
|
|
410
|
+
* - `challenge` is the only layer that imposes a real per-attempt cost. `"pow"`
|
|
411
|
+
* makes the client burn CPU; `"turnstile"` / `"hcaptcha"` outsource the
|
|
412
|
+
* judgement to a third party. Off by default.
|
|
413
|
+
*
|
|
414
|
+
* A server-to-server caller cannot solve a browser challenge, so it presents
|
|
415
|
+
* `signup_secret` in the `x-maravilla-signup-secret` header instead. Without
|
|
416
|
+
* that header the JSON endpoint enforces exactly the same challenge as the
|
|
417
|
+
* form — otherwise a bot would simply POST the JSON endpoint to escape.
|
|
418
|
+
*/
|
|
419
|
+
export interface SignupProtectionConfig {
|
|
420
|
+
/** Default `"none"`. */
|
|
421
|
+
challenge?: 'none' | 'pow' | 'turnstile' | 'hcaptcha';
|
|
422
|
+
/** Leading zero bits a proof-of-work solution must have. Default 18. */
|
|
423
|
+
pow_difficulty?: number;
|
|
424
|
+
/** Default `true`. */
|
|
425
|
+
honeypot?: boolean;
|
|
426
|
+
/** Default `true`. */
|
|
427
|
+
rate_limit?: boolean;
|
|
428
|
+
/** Prefer `{ env: "VAR_NAME" }` or `"${env.VAR_NAME}"`. */
|
|
429
|
+
signup_secret?: SecretRef;
|
|
430
|
+
/** Required when `challenge` is `"turnstile"`. */
|
|
431
|
+
turnstile_secret?: SecretRef;
|
|
432
|
+
/** Required when `challenge` is `"hcaptcha"`. */
|
|
433
|
+
hcaptcha_secret?: SecretRef;
|
|
434
|
+
/** Public widget key, rendered into the page. Not a secret. */
|
|
435
|
+
captcha_site_key?: string;
|
|
396
436
|
}
|
|
397
437
|
|
|
398
438
|
// ── Branding ──
|
package/src/remote-client.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { KvNamespace, KvListResult, Database, DbDocument, DbFindOptions, Storage, RealtimeService, PresenceService, AuthService, AuthCaller, AuthUser, AuthSession, AuthField, RegisterOptions, LoginOptions, CreateManagedUserOptions, UserListFilter, UserListResponse, UpdateUserOptions, Relation, AddRelationOptions, ListRelationsOptions, PolicyExplain, CanCheck, ConnectedClient, ApiKeyInfo, LoginSession, PolicyService, VectorIndexSpec, VectorIndexDescriptor, VectorQueryWithFilter, VectorSearchHit, IndexSpec, IndexDescriptor, Workflows, WorkflowHandle, WorkflowRun, WorkflowStepRecord, BrowserClient, FramesClient, BrowserJobHandle, ScreenshotRequest, PdfRequest, FramesRenderRequest } from './types.js';
|
|
2
|
-
import type { TransformsService, TranscodeOpts, ExtractFramesOpts, ExtractAudioOpts, ThumbnailOpts, ResizeOpts, OcrOpts, DocToPdfOpts, DocThumbnailOpts, DocConvertOpts, DocToMarkdownOpts, DocToHtmlOpts, DocReplaceImagesOpts, DocInsertQrCodeOpts, DocTemplateMergeOpts, JobHandle, JobStatusResponse, MediaInfo } from './transforms.js';
|
|
2
|
+
import type { TransformsService, TranscodeOpts, ExtractFramesOpts, ExtractAudioOpts, ThumbnailOpts, ResizeOpts, OcrOpts, DetectFacesOpts, FocalPoint, FocalPointRecord, DocToPdfOpts, DocThumbnailOpts, DocConvertOpts, DocToMarkdownOpts, DocToHtmlOpts, DocReplaceImagesOpts, DocInsertQrCodeOpts, DocTemplateMergeOpts, JobHandle, JobStatusResponse, MediaInfo } from './transforms.js';
|
|
3
3
|
import { RemoteMediaService } from './media.js';
|
|
4
4
|
import { getRequestAuthHeader } from './request-scope.js';
|
|
5
5
|
|
|
@@ -133,20 +133,32 @@ class RemoteDatabase implements Database {
|
|
|
133
133
|
return result.id;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
async updateOne(
|
|
136
|
+
async updateOne(
|
|
137
|
+
collection: string,
|
|
138
|
+
filter: any,
|
|
139
|
+
update: any,
|
|
140
|
+
): Promise<{ modified: number; matched: number; matchedCount: number; modifiedCount: number }> {
|
|
137
141
|
const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/update`, {
|
|
138
142
|
method: 'POST',
|
|
139
143
|
body: JSON.stringify({ filter, update }),
|
|
140
144
|
});
|
|
141
|
-
return response.json() as Promise<{
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
return response.json() as Promise<{
|
|
146
|
+
modified: number;
|
|
147
|
+
matched: number;
|
|
148
|
+
matchedCount: number;
|
|
149
|
+
modifiedCount: number;
|
|
150
|
+
}>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async deleteOne(
|
|
154
|
+
collection: string,
|
|
155
|
+
filter: any,
|
|
156
|
+
): Promise<{ deleted: number; deletedCount: number }> {
|
|
145
157
|
const response = await this.fetch(`${this.baseUrl}/api/db/${collection}/delete`, {
|
|
146
158
|
method: 'DELETE',
|
|
147
159
|
body: JSON.stringify(filter),
|
|
148
160
|
});
|
|
149
|
-
return response.json() as Promise<{ deleted: number }>;
|
|
161
|
+
return response.json() as Promise<{ deleted: number; deletedCount: number }>;
|
|
150
162
|
}
|
|
151
163
|
|
|
152
164
|
async deleteMany(collection: string, filter: any): Promise<{ deleted: number }> {
|
|
@@ -1300,6 +1312,49 @@ class RemoteTransformsService implements TransformsService {
|
|
|
1300
1312
|
return this.post<JobHandle>('/ocr', { srcKey, opts: opts ?? {} });
|
|
1301
1313
|
}
|
|
1302
1314
|
|
|
1315
|
+
detectFaces(srcKey: string, opts?: DetectFacesOpts | null): Promise<JobHandle> {
|
|
1316
|
+
return this.post<JobHandle>('/detect-faces', { srcKey, opts: opts ?? {} });
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
async setFocalPoint(srcKey: string, point: FocalPoint): Promise<null> {
|
|
1320
|
+
const res = await fetch(`${this.baseUrl}/api/media/transforms/focal-point`, {
|
|
1321
|
+
method: 'PUT',
|
|
1322
|
+
headers: { 'Content-Type': 'application/json', ...this.headers, ...getRequestAuthHeader() },
|
|
1323
|
+
body: JSON.stringify({ srcKey, x: point.x, y: point.y }),
|
|
1324
|
+
});
|
|
1325
|
+
if (!res.ok) {
|
|
1326
|
+
const text = await res.text();
|
|
1327
|
+
throw new Error(`Focal-point set error (${res.status}): ${text}`);
|
|
1328
|
+
}
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
async getFocalPoint(srcKey: string): Promise<FocalPointRecord | null> {
|
|
1333
|
+
const res = await fetch(
|
|
1334
|
+
`${this.baseUrl}/api/media/transforms/focal-point?srcKey=${encodeURIComponent(srcKey)}`,
|
|
1335
|
+
{ method: 'GET', headers: { ...this.headers, ...getRequestAuthHeader() } },
|
|
1336
|
+
);
|
|
1337
|
+
if (res.status === 404) return null;
|
|
1338
|
+
if (!res.ok) {
|
|
1339
|
+
const text = await res.text();
|
|
1340
|
+
throw new Error(`Focal-point lookup error (${res.status}): ${text}`);
|
|
1341
|
+
}
|
|
1342
|
+
return res.json() as Promise<FocalPointRecord>;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
async clearFocalPoint(srcKey: string): Promise<boolean> {
|
|
1346
|
+
const res = await fetch(
|
|
1347
|
+
`${this.baseUrl}/api/media/transforms/focal-point?srcKey=${encodeURIComponent(srcKey)}`,
|
|
1348
|
+
{ method: 'DELETE', headers: { ...this.headers, ...getRequestAuthHeader() } },
|
|
1349
|
+
);
|
|
1350
|
+
if (!res.ok) {
|
|
1351
|
+
const text = await res.text();
|
|
1352
|
+
throw new Error(`Focal-point clear error (${res.status}): ${text}`);
|
|
1353
|
+
}
|
|
1354
|
+
const data = (await res.json()) as { cleared: boolean };
|
|
1355
|
+
return data.cleared;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1303
1358
|
docToPdf(srcKey: string, opts?: DocToPdfOpts | null): Promise<JobHandle> {
|
|
1304
1359
|
return this.post<JobHandle>('/doc_to_pdf', { srcKey, opts: opts ?? {} });
|
|
1305
1360
|
}
|