From 2c7ed476af62da3b60b1ee66300d7971cd3c3b7d Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 2 Jan 2026 18:11:07 -0500 Subject: [PATCH] Adding new API CRUD v3 functionality. --- documentation/V3_FRONTEND_API_GUIDE.md | 131 +++++++++++++++++++++ src/lib/ae_api/api_get__crud_obj_li_v3.ts | 112 ++++++++++++++++++ src/lib/ae_api/api_post__crud_search_v3.ts | 50 ++++++++ src/lib/api/api.ts | 5 + 4 files changed, 298 insertions(+) create mode 100644 documentation/V3_FRONTEND_API_GUIDE.md create mode 100644 src/lib/ae_api/api_get__crud_obj_li_v3.ts create mode 100644 src/lib/ae_api/api_post__crud_search_v3.ts diff --git a/documentation/V3_FRONTEND_API_GUIDE.md b/documentation/V3_FRONTEND_API_GUIDE.md new file mode 100644 index 00000000..54c75a86 --- /dev/null +++ b/documentation/V3_FRONTEND_API_GUIDE.md @@ -0,0 +1,131 @@ +# Aether API V3 Frontend Integration Guide (Svelte/TypeScript) + +This guide explains how to update or create frontend functions to interact with the new **Aether API V3 CRUD** endpoints. V3 introduces a nested URL structure, a powerful POST-based search, and improved performance. + +--- + +## 1. Key Differences (V2 vs V3) + +| Feature | CRUD V2 (Legacy) | CRUD V3 (Modern) | +| --- | --- | --- | +| **Base Prefix** | `/v2/crud` | `/v3/crud` | +| **List Suffix** | Uses `/list` (e.g., `/v2/crud/journal/list`) | **No suffix** (e.g., `/v3/crud/journal/`) | +| **Nested Path** | Not supported in URL path | **Supported** (e.g., `/v3/crud/journal/{id}/journal_entry/`) | +| **Order By** | Passed in **Headers** (`order_by_li`) | Passed as **Query Parameter** (`order_by_li`) | +| **Complex Search**| Limited to GET `jp` (~2KB limit) | **POST `/search`** (Unlimited size) | +| **Latency Simulation**| Not standardized | `X-Delay-ms` (Header) or `delay_ms` (Query) | + +--- + +## 2. Implementing V3 CRUD Functions + +### A. List Top-Level Objects (GET) +Use this for simple lists or filtering by a parent via query parameters. + +```ts +export async function get_ae_obj_li_v3({ + api_cfg, + obj_type, + for_obj_type, + for_obj_id, + enabled = 'enabled', + hidden = 'not_hidden', + limit = 100, + offset = 0, + order_by_li = null, // e.g., {"created_on": "DESC"} + delay_ms = 0 +}) { + // 1. Build V3 Endpoint (Note: No /list suffix) + const endpoint = `/v3/crud/${obj_type}/`; + + // 2. Build Query Params + const params: any = { + enabled, + hidden, + limit, + offset + }; + + if (for_obj_type) params.for_obj_type = for_obj_type; + if (for_obj_id) params.for_obj_id = for_obj_id; + if (order_by_li) params.order_by_li = JSON.stringify(order_by_li); + if (delay_ms) params.delay_ms = delay_ms; + + return await get_object({ api_cfg, endpoint, params }); +} +``` + +### B. Access Nested Objects (GET) +V3 allows you to enforce parent-child relationships directly in the URL. + +```ts +// Example: Get all entries for a specific journal +// GET /v3/crud/journal/{journal_id}/journal_entry/ +export async function get_nested_obj_li_v3({ + api_cfg, + parent_type, + parent_id, + child_type, + limit = 100 +}) { + const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`; + return await get_object({ api_cfg, endpoint, params: { limit } }); +} +``` + +### C. Advanced Search (POST) +Use the new `/search` endpoint for complex logic (AND/OR, LIKE, IN) that would exceed URL length limits. + +```ts +export async function search_ae_obj_v3({ + api_cfg, + obj_type, + search_query, // Complex SearchQuery object + order_by_li = null +}) { + const endpoint = `/v3/crud/${obj_type}/search`; + + const params: any = {}; + if (order_by_li) params.order_by_li = JSON.stringify(order_by_li); + + // Note: search_query is sent in the BODY via POST + return await post_object({ + api_cfg, + endpoint, + params, + data: search_query + }); +} + +/** + * Example search_query usage: + * { + * "and": [ + * { "field": "enable", "op": "eq", "value": true }, + * { + * "or": [ + * { "field": "name", "op": "like", "value": "%Meeting%" }, + * { "field": "priority", "op": "gt", "value": 5 } + * ] + * } + * ] + * } + */ +``` + +--- + +## 3. Best Practices for V3 + +1. **Stop using Headers for Sorting**: In V2, we used `headers['order_by_li']`. In V3, always use `params['order_by_li']`. +2. **Prefer `/search` for Filters**: If your query involves more than a simple `for_obj_id`, use the `POST .../search` endpoint. It is safer, more readable, and bypasses the 2083-character URL limit. +3. **Non-Blocking UI**: Use the `delay_ms` parameter during development to test how your Svelte components handle loading states and skeleton screens. +4. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`, not `journals`), following the backend's `obj_type_kv_li` registry. + +--- + +## 4. Special Case: Account Context +V3 strictly enforces account context. Ensure your `api_cfg` logic correctly sets the `X-Account-ID` header. + +* If a request doesn't need an account (e.g., public site data), set the `X-No-Account-ID: bypass` header. +* If using a temporary token, use the `x_no_account_id_token` query parameter. diff --git a/src/lib/ae_api/api_get__crud_obj_li_v3.ts b/src/lib/ae_api/api_get__crud_obj_li_v3.ts new file mode 100644 index 00000000..1e31317e --- /dev/null +++ b/src/lib/ae_api/api_get__crud_obj_li_v3.ts @@ -0,0 +1,112 @@ +import type { key_val } from '$lib/stores/ae_stores'; +import { get_object } from './api_get_object'; + +interface GetAeObjLiV3Params { + api_cfg: any; + obj_type: string; + for_obj_type?: string; + for_obj_id?: string; + enabled?: 'all' | 'enabled' | 'not_enabled'; + hidden?: 'all' | 'hidden' | 'not_hidden'; + limit?: number; + offset?: number; + order_by_li?: Record | null; + delay_ms?: number; + log_lvl?: number; +} + +export async function get_ae_obj_li_v3({ + api_cfg, + obj_type, + for_obj_type, + for_obj_id, + enabled = 'enabled', + hidden = 'not_hidden', + limit = 100, + offset = 0, + order_by_li = null, + delay_ms = 0, + log_lvl = 0 +}: GetAeObjLiV3Params) { + // 1. Build V3 Endpoint (Note: No /list suffix) + const endpoint = `/v3/crud/${obj_type}/`; + + // 2. Build Query Params + const params: key_val = { + enabled, + hidden, + limit, + offset + }; + + if (for_obj_type) params['for_obj_type'] = for_obj_type; + if (for_obj_id) params['for_obj_id'] = for_obj_id; + if (order_by_li) params['order_by_li'] = JSON.stringify(order_by_li); + if (delay_ms > 0) params['delay_ms'] = delay_ms; + + if (log_lvl) { + console.log('*** get_ae_obj_li_v3 ***'); + console.log('Endpoint:', endpoint); + console.log('Params:', params); + } + + return await get_object({ + api_cfg, + endpoint, + params, + log_lvl + }); +} + +interface GetNestedObjLiV3Params { + api_cfg: any; + parent_type: string; + parent_id: string; + child_type: string; + limit?: number; + offset?: number; + enabled?: 'all' | 'enabled' | 'not_enabled'; + hidden?: 'all' | 'hidden' | 'not_hidden'; + order_by_li?: Record | null; + delay_ms?: number; + log_lvl?: number; +} + +export async function get_nested_obj_li_v3({ + api_cfg, + parent_type, + parent_id, + child_type, + limit = 100, + offset = 0, + enabled = 'enabled', + hidden = 'not_hidden', + order_by_li = null, + delay_ms = 0, + log_lvl = 0 +}: GetNestedObjLiV3Params) { + const endpoint = `/v3/crud/${parent_type}/${parent_id}/${child_type}/`; + + const params: key_val = { + enabled, + hidden, + limit, + offset + }; + + if (order_by_li) params['order_by_li'] = JSON.stringify(order_by_li); + if (delay_ms > 0) params['delay_ms'] = delay_ms; + + if (log_lvl) { + console.log('*** get_nested_obj_li_v3 ***'); + console.log('Endpoint:', endpoint); + console.log('Params:', params); + } + + return await get_object({ + api_cfg, + endpoint, + params, + log_lvl + }); +} diff --git a/src/lib/ae_api/api_post__crud_search_v3.ts b/src/lib/ae_api/api_post__crud_search_v3.ts new file mode 100644 index 00000000..898ebe40 --- /dev/null +++ b/src/lib/ae_api/api_post__crud_search_v3.ts @@ -0,0 +1,50 @@ +import type { key_val } from '$lib/stores/ae_stores'; +import { post_object } from './api_post_object'; + +interface SearchAeObjV3Params { + api_cfg: any; + obj_type: string; + search_query: any; // Complex SearchQuery object + order_by_li?: Record | null; + limit?: number; + offset?: number; + delay_ms?: number; + log_lvl?: number; +} + +export async function search_ae_obj_v3({ + api_cfg, + obj_type, + search_query, + order_by_li = null, + limit = 100, + offset = 0, + delay_ms = 0, + log_lvl = 0 +}: SearchAeObjV3Params) { + const endpoint = `/v3/crud/${obj_type}/search`; + + const params: key_val = { + limit, + offset + }; + + if (order_by_li) params['order_by_li'] = JSON.stringify(order_by_li); + if (delay_ms > 0) params['delay_ms'] = delay_ms; + + if (log_lvl) { + console.log('*** search_ae_obj_v3 ***'); + console.log('Endpoint:', endpoint); + console.log('Params:', params); + console.log('Search Query:', search_query); + } + + // Note: search_query is sent in the BODY via POST + return await post_object({ + api_cfg, + endpoint, + params, + data: search_query, + log_lvl + }); +} diff --git a/src/lib/api/api.ts b/src/lib/api/api.ts index 959af263..27a68171 100644 --- a/src/lib/api/api.ts +++ b/src/lib/api/api.ts @@ -12,6 +12,8 @@ import { post_object } from '$lib/ae_api/api_post_object'; // Exported at the en import { get_ae_obj_id_crud } from '$lib/ae_api/api_get__crud_obj_id'; import { get_ae_obj_li_for_obj_id_crud } from '$lib/ae_api/api_get__crud_obj_li_v1'; import { get_ae_obj_li_for_obj_id_crud_v2 } from '$lib/ae_api/api_get__crud_obj_li_v2'; +import { get_ae_obj_li_v3, get_nested_obj_li_v3 } from '$lib/ae_api/api_get__crud_obj_li_v3'; +import { search_ae_obj_v3 } from '$lib/ae_api/api_post__crud_search_v3'; // This new function has not been tested yet!!! // Updated 2024-08-07 @@ -916,6 +918,9 @@ const obj = { get_ae_obj_id_crud: get_ae_obj_id_crud, get_ae_obj_li_for_obj_id_crud: get_ae_obj_li_for_obj_id_crud, get_ae_obj_li_for_obj_id_crud_v2: get_ae_obj_li_for_obj_id_crud_v2, + get_ae_obj_li_v3: get_ae_obj_li_v3, + get_nested_obj_li_v3: get_nested_obj_li_v3, + search_ae_obj_v3: search_ae_obj_v3, create_ae_obj_crud: create_ae_obj_crud, update_ae_obj_id_crud: update_ae_obj_id_crud, delete_ae_obj_id_crud: delete_ae_obj_id_crud,