Saving changes to the Journals and API CRUD V3 fixes.

This commit is contained in:
Scott Idem
2026-01-02 19:30:33 -05:00
parent 33d42ed85b
commit 6eb601f56d
6 changed files with 123 additions and 107 deletions

View File

@@ -9,86 +9,48 @@ This guide explains how to update or create frontend functions to interact with
| 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) |
| **List Suffix** | Uses `/list` | **No suffix** (e.g., `/v3/crud/journal/`) |
| **Nested Path** | Not supported in URL | **Supported** (e.g., `/v3/crud/journal/{id}/journal_entry/`) |
| **View Selection**| `tbl_alt`, `mdl_alt` | **`view` parameter** (e.g., `?view=enriched`) |
| **Complex Search**| Limited to GET `jp` | **POST `/search`** (Unlimited size + Hybrid params) |
| **Full-Text Search**| Manual column names | **Reserved `q` property** in SearchQuery |
---
## 2. Implementing V3 CRUD Functions
### A. List Top-Level Objects (GET)
Use this for simple lists or filtering by a parent via query parameters.
### A. List & Single Object (GET)
Support for view selection allows fetching richer data models when needed.
```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 });
// Example: Get enriched journal details
// GET /v3/crud/journal/{id}?view=enriched
export async function get_ae_obj_v3({ api_cfg, obj_type, obj_id, view = 'default' }) {
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
return await get_object({ api_cfg, endpoint, params: { view } });
}
```
### 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.
### B. Advanced & Hybrid Search (POST)
The `/search` endpoint combines the power of complex logical bodies with the simplicity of query parameters.
```ts
export async function search_ae_obj_v3({
api_cfg,
obj_type,
search_query, // Complex SearchQuery object
order_by_li = null
search_query, // { q: "search term", and: [...] }
enabled = 'enabled',
view = 'default',
for_obj_type,
for_obj_id
}) {
const endpoint = `/v3/crud/${obj_type}/search`;
const params: any = {};
if (order_by_li) params.order_by_li = JSON.stringify(order_by_li);
// Standard filters can be passed as query params
const params: any = { enabled, view };
if (for_obj_type) params.for_obj_type = for_obj_type;
if (for_obj_id) params.for_obj_id = for_obj_id;
// Note: search_query is sent in the BODY via POST
return await post_object({
api_cfg,
endpoint,
@@ -96,36 +58,26 @@ export async function search_ae_obj_v3({
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 }
* ]
* }
* ]
* }
*/
### C. Standardized Global Search
Use the `q` property in your search body for a general keyword search across indexed columns.
```json
// POST /v3/crud/journal/search
{
"q": "Annual Meeting",
"and": [
{ "field": "enable", "op": "eq", "value": true }
]
}
```
---
## 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.
1. **Use `view` for Rich Data**: Instead of manually joining data in separate calls, use `?view=enriched` or `?view=detail` if defined in the backend.
2. **Hybrid Search**: Use query parameters for simple toggles (enabled/hidden) and the POST body for complex logic.
3. **Global Search**: Always prefer the `q` property for text search instead of manually targeting `default_qry_str`.
4. **Singular Nouns**: Always use singular names for `obj_type` (e.g., `journal`).

View File

@@ -1,6 +1,41 @@
import type { key_val } from '$lib/stores/ae_stores';
import { get_object } from './api_get_object';
interface GetAeObjV3Params {
api_cfg: any;
obj_type: string;
obj_id: string;
view?: string;
log_lvl?: number;
}
/**
* Get a single object by ID (V3)
*/
export async function get_ae_obj_v3({
api_cfg,
obj_type,
obj_id,
view = 'default',
log_lvl = 0
}: GetAeObjV3Params) {
const endpoint = `/v3/crud/${obj_type}/${obj_id}`;
const params: key_val = { view };
if (log_lvl) {
console.log('*** get_ae_obj_v3 ***');
console.log('Endpoint:', endpoint);
console.log('Params:', params);
}
return await get_object({
api_cfg,
endpoint,
params,
log_lvl
});
}
interface GetAeObjLiV3Params {
api_cfg: any;
obj_type: string;
@@ -8,6 +43,7 @@ interface GetAeObjLiV3Params {
for_obj_id?: string;
enabled?: 'all' | 'enabled' | 'not_enabled';
hidden?: 'all' | 'hidden' | 'not_hidden';
view?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | null;
@@ -22,19 +58,21 @@ export async function get_ae_obj_li_v3({
for_obj_id,
enabled = 'enabled',
hidden = 'not_hidden',
view = 'default',
limit = 100,
offset = 0,
order_by_li = null,
delay_ms = 0,
log_lvl = 0
}: GetAeObjLiV3Params) {
// 1. Build V3 Endpoint (Note: No /list suffix)
// 1. Build V3 Endpoint
const endpoint = `/v3/crud/${obj_type}/`;
// 2. Build Query Params
const params: key_val = {
enabled,
hidden,
view,
limit,
offset
};
@@ -63,10 +101,11 @@ interface GetNestedObjLiV3Params {
parent_type: string;
parent_id: string;
child_type: string;
limit?: number;
offset?: number;
enabled?: 'all' | 'enabled' | 'not_enabled';
hidden?: 'all' | 'hidden' | 'not_hidden';
view?: string;
limit?: number;
offset?: number;
order_by_li?: Record<string, 'ASC' | 'DESC'> | null;
delay_ms?: number;
log_lvl?: number;
@@ -77,10 +116,11 @@ export async function get_nested_obj_li_v3({
parent_type,
parent_id,
child_type,
limit = 100,
offset = 0,
enabled = 'enabled',
hidden = 'not_hidden',
view = 'default',
limit = 100,
offset = 0,
order_by_li = null,
delay_ms = 0,
log_lvl = 0
@@ -90,6 +130,7 @@ export async function get_nested_obj_li_v3({
const params: key_val = {
enabled,
hidden,
view,
limit,
offset
};
@@ -109,4 +150,4 @@ export async function get_nested_obj_li_v3({
params,
log_lvl
});
}
}

View File

@@ -4,7 +4,12 @@ import { post_object } from './api_post_object';
interface SearchAeObjV3Params {
api_cfg: any;
obj_type: string;
search_query: any; // Complex SearchQuery object
search_query: any; // Complex SearchQuery object: { q: string, and: [], or: [] }
enabled?: 'all' | 'enabled' | 'not_enabled';
hidden?: 'all' | 'hidden' | 'not_hidden';
view?: string;
for_obj_type?: string;
for_obj_id?: string;
order_by_li?: Record<string, 'ASC' | 'DESC'> | null;
limit?: number;
offset?: number;
@@ -16,6 +21,11 @@ export async function search_ae_obj_v3({
api_cfg,
obj_type,
search_query,
enabled = 'enabled',
hidden = 'not_hidden',
view = 'default',
for_obj_type,
for_obj_id,
order_by_li = null,
limit = 100,
offset = 0,
@@ -24,11 +34,17 @@ export async function search_ae_obj_v3({
}: SearchAeObjV3Params) {
const endpoint = `/v3/crud/${obj_type}/search`;
// Hybrid search: Standard filters passed as query params
const params: key_val = {
enabled,
hidden,
view,
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;
@@ -47,4 +63,4 @@ export async function search_ae_obj_v3({
data: search_query,
log_lvl
});
}
}

View File

@@ -157,6 +157,7 @@ export async function load_ae_obj_li__journal_entry({
// Process the results first
const processed_obj_li = await process_ae_obj__journal_entry_props({
obj_li: journal_entry_obj_li_get_result,
journal_id: for_obj_type === 'journal' ? for_obj_id : undefined,
log_lvl: log_lvl
});
if (log_lvl) {
@@ -240,6 +241,7 @@ export async function create_ae_obj__journal_entry({
// Process the results first
const processed_obj_li = await process_ae_obj__journal_entry_props({
obj_li: [journal_entry_obj_create_result],
journal_id,
log_lvl: log_lvl
});
if (log_lvl) {
@@ -950,9 +952,11 @@ async function _process_generic_props<T extends Record<string, any>>({
// Updated 2025-05-09
export async function process_ae_obj__journal_entry_props({
obj_li,
journal_id,
log_lvl = 0
}: {
obj_li: any[];
journal_id?: string;
log_lvl?: number;
}) {
return _process_generic_props({
@@ -960,6 +964,12 @@ export async function process_ae_obj__journal_entry_props({
obj_type: 'journal_entry',
log_lvl,
specific_processor: async (obj) => {
// Inject journal_id if provided and missing
if (journal_id) {
if (!obj.journal_id) obj.journal_id = journal_id;
if (!obj.journal_id_random) obj.journal_id_random = journal_id;
}
// Content processing
let content = obj.content ?? '';
let content_cleaned: null | string = null;

View File

@@ -12,7 +12,7 @@ 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 { get_ae_obj_v3, 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!!!
@@ -916,6 +916,7 @@ const obj = {
patch_object: patch_object,
post_object: post_object,
get_ae_obj_id_crud: get_ae_obj_id_crud,
get_ae_obj_v3: get_ae_obj_v3,
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,

View File

@@ -373,24 +373,20 @@
if ($journals_loc.qry__category_code) {
data_kv.category_code = $journals_loc.qry__category_code;
}
$effect(() => {
if (log_lvl) {
console.log('Creating new journal entry with data_kv:', data_kv);
}
});
if (log_lvl) {
console.log('Creating new journal entry with data_kv:', data_kv);
}
journals_func
.create_ae_obj__journal_entry({
api_cfg: $ae_api,
journal_id: $lq__journal_obj.journal_id,
journal_id: $lq__journal_obj?.journal_id,
data_kv: data_kv,
log_lvl: log_lvl
})
.then((results) => {
$effect(() => {
if (log_lvl) {
console.log('New journal entry created:', results);
}
});
if (log_lvl) {
console.log('New journal entry created:', results);
}
$journals_slct.journal_entry_id = results?.journal_entry_id_random;
// $journals_loc.entry.edit = true;
$journals_loc.entry.edit_kv[$journals_slct.journal_entry_id] =