@simpleplatform/sdk 1.0.4 → 1.2.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/dist/ai.d.ts CHANGED
@@ -157,6 +157,21 @@ export interface AITranscribeOptions extends Omit<AICommonOptions, 'prompt'> {
157
157
  */
158
158
  summarize?: boolean;
159
159
  }
160
+ /**
161
+ * Options for searching faces within the Face Engine.
162
+ */
163
+ export interface AIFaceSearchOptions {
164
+ /**
165
+ * Maximum number of matches to return.
166
+ * Defaults to 5.
167
+ */
168
+ maxFaces?: number;
169
+ /**
170
+ * Minimum similarity percentage (0-100) required for a match.
171
+ * Defaults to 85.0.
172
+ */
173
+ similarityThreshold?: number;
174
+ }
160
175
  /**
161
176
  * The response structure from a successful AI `extract` or `summarize` operation.
162
177
  */
@@ -236,4 +251,38 @@ export declare function summarize(input: DocumentHandle | object | string, optio
236
251
  * // Returns: { language: "en", summary: "...", participants: ["Participant 1", "Participant 2"] }
237
252
  */
238
253
  export declare function transcribe(input: DocumentHandle, options: AITranscribeOptions, context: Context): Promise<AIExecutionResult>;
254
+ /**
255
+ * Enrolls a face for a subject. The face is associated with the given `subjectId`
256
+ * within the tenant's secure collection.
257
+ *
258
+ * @param subjectId A unique identifier for the subject (e.g., employee ID, user ID).
259
+ * @param image The image containing the face to enroll. Can be a base64 string or a DocumentHandle.
260
+ * @param context The execution context provided by the host.
261
+ * @returns A promise that resolves to an object containing the enrolled `face_id`.
262
+ * @throws Will throw an error if the operation fails or inputs are invalid.
263
+ */
264
+ export declare function enrollFace(subjectId: string, image: DocumentHandle | string, context: Context): Promise<{
265
+ face_id: string;
266
+ }>;
267
+ /**
268
+ * Searches for faces matching the provided image within the tenant's collection.
269
+ *
270
+ * @param image The image to search for. Can be a base64 string or a DocumentHandle.
271
+ * @param options Configuration for the search operation (e.g., maxFaces, similarityThreshold).
272
+ * @param context The execution context provided by the host.
273
+ * @returns A promise that resolves to a list of matching faces and their confidence scores.
274
+ * @throws Will throw an error if the operation fails or inputs are invalid.
275
+ */
276
+ export declare function searchFace(image: DocumentHandle | string, options: AIFaceSearchOptions | undefined, context: Context): Promise<any>;
277
+ /**
278
+ * Deletes one or more faces from the tenant's collection by their face IDs.
279
+ *
280
+ * @param faceIds An array of face IDs to delete.
281
+ * @param context The execution context provided by the host.
282
+ * @returns A promise that resolves to an object indicating the IDs of successfully deleted faces.
283
+ * @throws Will throw an error if the operation fails.
284
+ */
285
+ export declare function deleteFace(faceIds: string[], context: Context): Promise<{
286
+ deleted: string[];
287
+ }>;
239
288
  export {};
package/dist/ai.js CHANGED
@@ -263,3 +263,109 @@ export async function transcribe(input, options, context) {
263
263
  schema,
264
264
  }, context);
265
265
  }
266
+ // ============================================================================
267
+ // Face Recognition API
268
+ // ============================================================================
269
+ let _collectionEnsured = false;
270
+ /**
271
+ * Internal helper to ensure the face collection exists before making face operations.
272
+ * Caches the result per Javascript isolate execution to minimize RPC overhead over
273
+ * sequential calls in the same logic.
274
+ */
275
+ async function _ensureFaceCollection(context) {
276
+ if (_collectionEnsured)
277
+ return;
278
+ const response = await hostExecute('action:ai/face/ensure-collection', {}, context);
279
+ if (!response.ok) {
280
+ throw new Error(response.error?.message || 'Failed to ensure face collection.');
281
+ }
282
+ _collectionEnsured = true;
283
+ }
284
+ /**
285
+ * Enrolls a face for a subject. The face is associated with the given `subjectId`
286
+ * within the tenant's secure collection.
287
+ *
288
+ * @param subjectId A unique identifier for the subject (e.g., employee ID, user ID).
289
+ * @param image The image containing the face to enroll. Can be a base64 string or a DocumentHandle.
290
+ * @param context The execution context provided by the host.
291
+ * @returns A promise that resolves to an object containing the enrolled `face_id`.
292
+ * @throws Will throw an error if the operation fails or inputs are invalid.
293
+ */
294
+ export async function enrollFace(subjectId, image, context) {
295
+ if (!subjectId) {
296
+ throw new Error('The `subjectId` parameter is required for `enrollFace`.');
297
+ }
298
+ if (!image) {
299
+ throw new Error('The `image` parameter is required for `enrollFace`.');
300
+ }
301
+ // Ensure the tenant's collection is ready before enrolling
302
+ await _ensureFaceCollection(context);
303
+ // Upload pending documents if given handles
304
+ const processedImage = await _uploadPendingFiles(image, context);
305
+ const payload = {
306
+ image: processedImage,
307
+ subject_id: subjectId,
308
+ };
309
+ const response = await hostExecute('action:ai/face/enroll', payload, context);
310
+ if (!response.ok) {
311
+ throw new Error(response.error?.message || 'Face enrollment failed.');
312
+ }
313
+ return response.data;
314
+ }
315
+ /**
316
+ * Searches for faces matching the provided image within the tenant's collection.
317
+ *
318
+ * @param image The image to search for. Can be a base64 string or a DocumentHandle.
319
+ * @param options Configuration for the search operation (e.g., maxFaces, similarityThreshold).
320
+ * @param context The execution context provided by the host.
321
+ * @returns A promise that resolves to a list of matching faces and their confidence scores.
322
+ * @throws Will throw an error if the operation fails or inputs are invalid.
323
+ */
324
+ export async function searchFace(image, options = {}, context) {
325
+ if (!image) {
326
+ throw new Error('The `image` parameter is required for `searchFace`.');
327
+ }
328
+ // Ensure collection exists before searching
329
+ await _ensureFaceCollection(context);
330
+ const processedImage = await _uploadPendingFiles(image, context);
331
+ // Map JS property names to Elixir convention if needed, though they are passed as options
332
+ // and parsed correctly in simple_logic if expected.
333
+ // The simple_ai FaceEngine expects snake_case for options like max_faces or similarity_threshold.
334
+ const mappedOptions = {};
335
+ if (options.maxFaces !== undefined)
336
+ mappedOptions.max_faces = options.maxFaces;
337
+ if (options.similarityThreshold !== undefined)
338
+ mappedOptions.similarity_threshold = options.similarityThreshold;
339
+ const payload = {
340
+ image: processedImage,
341
+ options: mappedOptions,
342
+ };
343
+ const response = await hostExecute('action:ai/face/search', payload, context);
344
+ if (!response.ok) {
345
+ throw new Error(response.error?.message || 'Face search failed.');
346
+ }
347
+ return response.data;
348
+ }
349
+ /**
350
+ * Deletes one or more faces from the tenant's collection by their face IDs.
351
+ *
352
+ * @param faceIds An array of face IDs to delete.
353
+ * @param context The execution context provided by the host.
354
+ * @returns A promise that resolves to an object indicating the IDs of successfully deleted faces.
355
+ * @throws Will throw an error if the operation fails.
356
+ */
357
+ export async function deleteFace(faceIds, context) {
358
+ if (!faceIds || !Array.isArray(faceIds) || faceIds.length === 0) {
359
+ throw new Error('The `faceIds` parameter must be a non-empty array for `deleteFace`.');
360
+ }
361
+ await _ensureFaceCollection(context);
362
+ const payload = {
363
+ face_ids: faceIds,
364
+ };
365
+ const response = await hostExecute('action:ai/face/delete', payload, context);
366
+ if (!response.ok) {
367
+ throw new Error(response.error?.message || 'Face deletion failed.');
368
+ }
369
+ // Ensure consistent return format
370
+ return { deleted: Array.isArray(response.data) ? response.data : response.data?.deleted || [] };
371
+ }
package/dist/storage.d.ts CHANGED
@@ -1,4 +1,30 @@
1
1
  import type { Context, DocumentHandle, ExternalFileSource, StorageTarget } from './types';
2
+ /**
3
+ * Uploads an in-memory binary buffer as a document to the platform's storage system.
4
+ *
5
+ * Accepts any binary content (images, PDFs, etc.) as an `ArrayBuffer` or `Uint8Array`.
6
+ * The bytes are base64-encoded once here at the JSON boundary, decoded on the backend,
7
+ * and then stored via the same pipeline as `uploadExternal`.
8
+ *
9
+ * @param buffer The binary content to upload.
10
+ * @param filename The filename to assign to the stored document.
11
+ * @param mimeType The MIME type of the content (e.g. `'application/pdf'`, `'image/png'`).
12
+ * @param target The target location where the file should be stored.
13
+ * @param context The execution context for the request.
14
+ * @returns A promise that resolves with a DocumentHandle containing file metadata.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const handle = await uploadBuffer(
19
+ * pdfBytes,
20
+ * 'report.pdf',
21
+ * 'application/pdf',
22
+ * { app_id: 'dev.simple.system', table_name: 'documents', field_name: 'attachment' },
23
+ * context
24
+ * )
25
+ * ```
26
+ */
27
+ export declare function uploadBuffer(buffer: ArrayBuffer | Uint8Array, filename: string, mimeType: string, target: StorageTarget, context: Context): Promise<DocumentHandle>;
2
28
  /**
3
29
  * Uploads a file from an external URL to the platform's storage system.
4
30
  *
package/dist/storage.js CHANGED
@@ -1,4 +1,49 @@
1
1
  import { execute as hostExecute } from './host';
2
+ /**
3
+ * Uploads an in-memory binary buffer as a document to the platform's storage system.
4
+ *
5
+ * Accepts any binary content (images, PDFs, etc.) as an `ArrayBuffer` or `Uint8Array`.
6
+ * The bytes are base64-encoded once here at the JSON boundary, decoded on the backend,
7
+ * and then stored via the same pipeline as `uploadExternal`.
8
+ *
9
+ * @param buffer The binary content to upload.
10
+ * @param filename The filename to assign to the stored document.
11
+ * @param mimeType The MIME type of the content (e.g. `'application/pdf'`, `'image/png'`).
12
+ * @param target The target location where the file should be stored.
13
+ * @param context The execution context for the request.
14
+ * @returns A promise that resolves with a DocumentHandle containing file metadata.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const handle = await uploadBuffer(
19
+ * pdfBytes,
20
+ * 'report.pdf',
21
+ * 'application/pdf',
22
+ * { app_id: 'dev.simple.system', table_name: 'documents', field_name: 'attachment' },
23
+ * context
24
+ * )
25
+ * ```
26
+ */
27
+ export async function uploadBuffer(buffer, filename, mimeType, target, context) {
28
+ if (!filename || filename.trim() === '')
29
+ throw new Error('filename is required');
30
+ if (!mimeType || mimeType.trim() === '')
31
+ throw new Error('mimeType is required');
32
+ if (!target.app_id || target.app_id.trim() === '')
33
+ throw new Error('Target app_id is required and cannot be empty');
34
+ if (!target.table_name || target.table_name.trim() === '')
35
+ throw new Error('Target table_name is required and cannot be empty');
36
+ if (!target.field_name || target.field_name.trim() === '')
37
+ throw new Error('Target field_name is required and cannot be empty');
38
+ const bytes = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;
39
+ // Base64-encode at the JSON boundary (JSON.stringify cannot carry raw binary)
40
+ const base64 = btoa(String.fromCharCode(...bytes));
41
+ const source = { bytes: base64, filename, mime_type: mimeType };
42
+ const response = await hostExecute('action:storage/upload-external', { source, target }, context);
43
+ if (!response.ok)
44
+ throw new Error(response.error?.message ?? 'Buffer upload failed');
45
+ return response.data;
46
+ }
2
47
  /**
3
48
  * Uploads a file from an external URL to the platform's storage system.
4
49
  *
@@ -32,9 +77,9 @@ import { execute as hostExecute } from './host';
32
77
  * ```
33
78
  */
34
79
  export async function uploadExternal(source, target, context) {
35
- // Validate source
36
- if (!source.url || source.url.trim() === '') {
37
- throw new Error('Source URL is required and cannot be empty');
80
+ // Validate source: must have either url or bytes
81
+ if ((!source.url || source.url.trim() === '') && !source.bytes) {
82
+ throw new Error('Either source URL or bytes must be provided');
38
83
  }
39
84
  // Validate target
40
85
  if (!target.app_id || target.app_id.trim() === '') {
package/dist/types.d.ts CHANGED
@@ -20,9 +20,9 @@ export interface DocumentHandle {
20
20
  /** The storage path where the file is stored. */
21
21
  storage_path: string;
22
22
  }
23
- /** Represents the source configuration for downloading an external file. */
23
+ /** Represents the source configuration for an external file upload (URL or in-memory bytes). */
24
24
  export interface ExternalFileSource {
25
- /** Optional authentication configuration for accessing the external file. */
25
+ /** Optional authentication configuration for accessing the external file (URL path only). */
26
26
  auth?: {
27
27
  /** Bearer token for bearer authentication. */
28
28
  bearer_token?: string;
@@ -33,8 +33,17 @@ export interface ExternalFileSource {
33
33
  /** Username for basic authentication. */
34
34
  username?: string;
35
35
  };
36
- /** The URL of the external file to download. */
37
- url: string;
36
+ /**
37
+ * Base64-encoded binary content for in-memory uploads.
38
+ * Use this with `filename` and `mime_type` instead of `url`.
39
+ */
40
+ bytes?: string;
41
+ /** Original filename — required when using `bytes`. */
42
+ filename?: string;
43
+ /** MIME type of the file — required when using `bytes`. */
44
+ mime_type?: string;
45
+ /** The URL of the external file to download. Mutually exclusive with `bytes`. */
46
+ url?: string;
38
47
  }
39
48
  /** Contains metadata about the specific logic execution. */
40
49
  export interface Logic {
@@ -11,22 +11,21 @@ const pendingHostRequests = new Map()
11
11
 
12
12
  // eslint-disable-next-line no-restricted-globals
13
13
  self.addEventListener('message', (event) => {
14
- const message = event && event.data
15
- if (
16
- typeof message !== 'object'
17
- || message === null
18
- || message.type !== 'host_response'
19
- ) {
14
+ const message = event.data
15
+ if (message.type !== 'host_response')
20
16
  return
21
- }
22
17
 
23
18
  const pending = pendingHostRequests.get(message.requestId)
24
19
  if (!pending)
25
20
  return
26
21
 
22
+ const errorMsg = typeof message.response.error === 'object' && message.response.error?.message
23
+ ? message.response.error.message
24
+ : message.response.error ?? 'Unknown host error'
25
+
27
26
  message.response.ok
28
27
  ? pending.resolve(message.response)
29
- : pending.reject(new Error(message.response.error ?? 'Unknown host error'))
28
+ : pending.reject(new Error(errorMsg))
30
29
 
31
30
  pendingHostRequests.delete(message.requestId)
32
31
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simpleplatform/sdk",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "description": "Simple Platform Typescript SDK",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://docs.simple.dev",
@@ -61,10 +61,10 @@
61
61
  "dist"
62
62
  ],
63
63
  "dependencies": {
64
- "esbuild": "0.27.2"
64
+ "esbuild": "0.27.4"
65
65
  },
66
66
  "devDependencies": {
67
- "@types/node": "25.2.0",
67
+ "@types/node": "25.5.0",
68
68
  "typescript": "5.9.3"
69
69
  },
70
70
  "scripts": {