diff --git a/documentation/Aether_Svelte_API_TS_code_samples.md b/documentation/Aether_Svelte_API_TS_code_samples.md new file mode 100644 index 0000000..52809ad --- /dev/null +++ b/documentation/Aether_Svelte_API_TS_code_samples.md @@ -0,0 +1,297 @@ +```ts +export async function get_ae_obj_id_crud({ + api_cfg, + no_account_id = false, + obj_type, + obj_id, + use_alt_table = false, + use_alt_base = false, + inc = {}, + enabled = 'enabled', + hidden = 'not_hidden', + limit = 999999, + offset = 0, + data = {}, + // key, + // jwt = null, + headers = {}, + params = {}, + timeout = 25000, + return_meta = false, + log_lvl = 0 +}: { + api_cfg: any; + no_account_id?: boolean; + obj_type: string; + obj_id: string; + use_alt_table?: boolean; + use_alt_base?: boolean; + inc?: any; + enabled?: 'enabled' | 'all' | 'not_enabled' | undefined; + hidden?: 'hidden' | 'all' | 'not_hidden' | undefined; + limit?: number; + offset?: number; + data?: any; + // key: string, + // jwt?: string, + headers?: any; + params?: key_val; + timeout?: number; + return_meta?: boolean; + log_lvl?: number; +}) { +``` + +```ts +// Updated 2024-05-23 +export const patch_object = async function patch_object({ + api_cfg = null, + endpoint = '', + params = {}, + data = {}, + return_meta = false, + log_lvl = 0, + retry_count = 5 // Number of retry attempts +}: { + api_cfg: any; + endpoint: string; + params?: any; + data?: any; + return_meta?: boolean; + log_lvl?: number; + retry_count?: number; +}) { +``` + + +```ts +// Updated 2024-05-23 +export const post_object = async function post_object({ + api_cfg = null, + endpoint = '', + params = {}, + data = {}, + form_data = null, + return_meta = false, + return_blob = false, + filename = '', + auto_download = false, + // The task_id value should be a random string that is unique to the task. This is used to identify the task in the message event. + task_id = crypto.randomUUID(), + log_lvl = 0, + retry_count = 5 +}: { + api_cfg: any; + endpoint: string; + params?: any; + data?: any; + form_data?: any; + return_meta?: boolean; + return_blob?: boolean; + filename?: string; + auto_download?: boolean; + task_id?: string; + log_lvl?: number; + retry_count?: number; +}) { +``` + +```ts +// Updated 2024-05-23 +export const delete_object = async function delete_object({ + api_cfg = null, + endpoint = '', + params = {}, + data = {}, + return_meta = false, + log_lvl = 0, + retry_count = 5 // Number of retry attempts +}: { + api_cfg: any; + endpoint: string; + params?: any; + data?: any; + return_meta?: boolean; + log_lvl?: number; + retry_count?: number; +}) { +``` + + +```ts +import type { key_val } from '$lib/stores/ae_stores'; +import { get_object } from './api_get_object'; + +// Refactored 2025-11-13 to use a lookup map for endpoints. +const objTypeToEndpointMap: Record = { + account: '/crud/account/list', + address: '/crud/address/list', + archive: '/crud/archive/list', + archive_content: '/crud/archive/content/list', + activity_log: '/crud/activity_log/list', + contact: '/crud/contact/list', + data_store: '/crud/data_store/list', + event: '/crud/event/list', + event_abstract: '/crud/event/abstract/list', + event_badge: '/crud/event/badge/list', + event_badge_template: '/crud/event/badge/template/list', + event_device: '/crud/event/device/list', + event_exhibit: '/crud/event/exhibit/list', + event_exhibit_tracking: '/crud/event/exhibit/tracking/list', + event_file: '/crud/event/file/list', + event_location: '/crud/event/location/list', + event_person: '/crud/event/person/list', + event_presentation: '/crud/event/presentation/list', + event_presenter: '/crud/event/presenter/list', + event_session: '/crud/event/session/list', + event_track: '/crud/event/track/list', + grant: '/crud/grant/list', + hosted_file: '/crud/hosted_file/list', + journal: '/crud/journal/list', + journal_entry: '/crud/journal/entry/list', + order: '/crud/order/list', + order_line: '/crud/order/line/list', + page: '/crud/page/list', + person: '/crud/person/list', + post: '/crud/post/list', + post_comment: '/crud/post/comment/list', + site: '/crud/site/list', + sponsorship_cfg: '/crud/sponsorship/cfg/list', + sponsorship: '/crud/sponsorship/list', + // user: '/crud/user/list', + 'lu-country_subdivision': '/crud/lu/country_subdivision/list', + 'lu-country': '/crud/lu/country/list', + 'lu-time_zone': '/crud/lu/time_zone/list' +}; + +function getEndpointForObjType(obj_type: string, for_obj_type?: string): string { + if (obj_type === 'lu' && for_obj_type) { + const key = `lu-${for_obj_type}`; + const endpoint = objTypeToEndpointMap[key]; + if (endpoint) return endpoint; + } + + const endpoint = objTypeToEndpointMap[obj_type]; + if (endpoint) return endpoint; + + throw new Error(`Unknown object type: ${obj_type}`); +} + +type OrderBy = { [key: string]: 'ASC' | 'DESC' }; + +interface GetAeObjLiForObjIdCrudV2Params { + api_cfg: any; // Consider defining a specific type for api_cfg + obj_type: string; + for_obj_type: string; + for_obj_id?: string; + use_alt_tbl?: boolean | string; + use_alt_mdl?: boolean | string; + use_alt_exp?: boolean | string; + inc?: key_val; + enabled?: 'all' | 'enabled' | 'not_enabled'; + hidden?: 'all' | 'hidden' | 'not_hidden'; + order_by_li?: OrderBy[] | null; + limit?: number; + offset?: number; + headers?: Record; + params_json?: any; + params?: key_val; + log_lvl?: number; +} + +export async function get_ae_obj_li_for_obj_id_crud_v2({ + api_cfg, + obj_type, + for_obj_type, + for_obj_id, + use_alt_tbl = false, + use_alt_mdl = false, + use_alt_exp = false, + enabled = 'enabled', + hidden = 'not_hidden', + order_by_li = null, + limit = 999999, + offset = 0, + headers = {}, + params_json = null, + params = {}, + log_lvl = 0 +}: GetAeObjLiForObjIdCrudV2Params) { + if (log_lvl) { + console.log('*** get_ae_obj_li_for_obj_id_crud_v2() ***'); + } + + try { + const endpoint = `/v2${getEndpointForObjType(obj_type, for_obj_type)}`; + if (log_lvl) { + console.log('Endpoint:', endpoint); + } + + // We need to remove a few parameters from the params object that are not allowed. + delete params['qry__enabled']; + delete params['qry__hidden']; + delete params['qry__limit']; + delete params['qry__offset']; + + if (for_obj_type) params['for_obj_type'] = for_obj_type; + if (for_obj_id) params['for_obj_id'] = for_obj_id; + + if (use_alt_tbl === true) params['tbl_alt'] = 'alt'; + if (use_alt_mdl === true) params['mdl_alt'] = 'alt'; + if (use_alt_exp === true) params['exp_alt'] = 'alt'; + + const allowed_enabled_list = ['all', 'enabled', 'not_enabled']; + if (allowed_enabled_list.includes(enabled)) { + params['enabled'] = enabled; + } + + const allowed_hidden_list = ['all', 'hidden', 'not_hidden']; + if (allowed_hidden_list.includes(hidden)) { + params['hidden'] = hidden; + } + + // NOTE: The order_by_li variable is in the "headers" because URL GET params do not handle complex objects very well. + if (order_by_li) { + headers['order_by_li'] = JSON.stringify(order_by_li); + } + + if (limit > 0) params['limit'] = limit; + if (offset > 0) params['offset'] = offset; + + if (params_json) { + // NOTE: "jp" stands for "JSON Params". This is a JSON object that needs to be safely converted to a string for the params. + // Max characters for a GET request is ~2000. This is a limitation of the browser. + const json_params_str = encodeURIComponent(JSON.stringify(params_json)); + if (json_params_str.length > 2083) { + // Using console.error instead of throwing an error to avoid crashing the app for a known limitation. + console.error( + `The JSON object is too large to be used as a GET parameter. Max length is 2083 characters. Length = ${json_params_str.length}` + ); + return false; + } + params['jp'] = json_params_str; + } + + if (log_lvl) { + console.log('Params:', params); + } + + const object_li_get_promise = await get_object({ + api_cfg: api_cfg, + endpoint: endpoint, + headers: headers, + params: params, + log_lvl: log_lvl + }); + + if (log_lvl > 1) { + console.log(object_li_get_promise); + } + + return object_li_get_promise; + } catch (error) { + console.error('Error in get_ae_obj_li_for_obj_id_crud_v2:', error); + return false; // Or handle the error as appropriate + } +} +``` diff --git a/documentation/V3_FRONTEND_API_GUIDE.md b/documentation/V3_FRONTEND_API_GUIDE.md new file mode 100644 index 0000000..54c75a8 --- /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.