From 1538075fd1a53e80f7a729af981b05ba19b39d28 Mon Sep 17 00:00:00 2001 From: Agus Putra Dana Date: Thu, 12 Feb 2026 15:40:22 +0800 Subject: [PATCH 1/5] Add test: Flex field with object value --- tests/unit/mutations/refFields.ts | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/unit/mutations/refFields.ts b/tests/unit/mutations/refFields.ts index d01ad3a..d2f93c8 100644 --- a/tests/unit/mutations/refFields.ts +++ b/tests/unit/mutations/refFields.ts @@ -1135,4 +1135,39 @@ export const testRefFieldsMutations = createTest('Mutation: RefFields', (ctx) => flexReferences: ['hello ? yes : no', 'User:abc:xyz', 'things it can do: jumping', 'User: hey', 'User:hey '], }); }); + + it('fl8:[flex, object] Should accept objects in flexReferences', async () => { + const flexWithObject = { + id: 'fl8-flex-with-object', + flexReferences: [{ msg: 'Hello, world!' }], + }; + await ctx.mutate( + [ + { + ...flexWithObject, + $thing: 'FlexRefRel', + }, + ], + { noMetadata: true }, + ); + + const res = await ctx.query( + { + $relation: 'FlexRefRel', + $id: 'fl8-flex-with-object', + $fields: ['id', 'flexReferences'], + }, + { noMetadata: true }, + ); + + //clean + await ctx.mutate({ + $thing: 'FlexRefRel', + $op: 'delete', + $id: 'fl8-flex-with-object', + space: { $op: 'delete' }, + }); + + expect(res).toEqual(flexWithObject); + }); }); From 42672b51590a52ccb829713ef3dded6d88289187 Mon Sep 17 00:00:00 2001 From: Agus Putra Dana Date: Fri, 13 Feb 2026 15:48:59 +0800 Subject: [PATCH 2/5] Fix: plain object in flex field mutation threw an error --- src/adapters/surrealDB/parsing/parseFlexVal.ts | 3 +++ src/stateMachine/mutation/bql/enrich.ts | 7 ++++++- .../mutation/bql/enrichSteps/enrichChildren.ts | 5 ++++- src/types/symbols/index.ts | 3 +++ tests/unit/mutations/refFields.ts | 2 ++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/adapters/surrealDB/parsing/parseFlexVal.ts b/src/adapters/surrealDB/parsing/parseFlexVal.ts index e6c88de..76bdf23 100644 --- a/src/adapters/surrealDB/parsing/parseFlexVal.ts +++ b/src/adapters/surrealDB/parsing/parseFlexVal.ts @@ -18,5 +18,8 @@ export const parseFlexValSurrealDB = (v: unknown) => { if (['number', 'boolean'].includes(typeof v)) { return v; } + if (typeof v === 'object' && v !== null) { + return JSON.stringify(v); + } throw new Error(`Unsupported type ${typeof v}`); }; diff --git a/src/stateMachine/mutation/bql/enrich.ts b/src/stateMachine/mutation/bql/enrich.ts index e5d1644..1fe6a7a 100644 --- a/src/stateMachine/mutation/bql/enrich.ts +++ b/src/stateMachine/mutation/bql/enrich.ts @@ -5,7 +5,7 @@ import { traverse } from 'object-traversal'; import { isArray, isObject } from 'radash'; import { getCurrentFields, getCurrentSchema, getFieldSchema } from '../../../helpers'; import type { BormConfig, BQLMutationBlock, EnrichedBormSchema, EnrichedBQLMutationBlock } from '../../../types'; -import { SharedMetadata } from '../../../types/symbols'; +import { FlexDataValue, SharedMetadata } from '../../../types/symbols'; import { enrichFilter } from '../../query/bql/enrich'; import { computeFields } from './enrichSteps/computeFields'; import { enrichChildren } from './enrichSteps/enrichChildren'; @@ -71,6 +71,11 @@ export const enrichBQLMutation = ( return; } + if (FlexDataValue in value) { + //plain data objects in FLEX ref fields, not mutation nodes + return; + } + if ('$root' in value) { // This is hte $root object, we will split the real root if needed in this iteration } else if (!('$thing' in value || '$entity' in value || '$relation' in value)) { diff --git a/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts b/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts index 22c81b3..8ab9311 100644 --- a/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts +++ b/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts @@ -8,7 +8,7 @@ import type { EnrichedRefField, EnrichedRoleField, } from '../../../../types'; -import { EdgeSchema, SharedMetadata } from '../../../../types/symbols'; +import { EdgeSchema, FlexDataValue, SharedMetadata } from '../../../../types/symbols'; import { get$bzId } from '../shared/get$bzId'; import { getOp } from '../shared/getOp'; import { getOppositePlayers } from '../shared/getOppositePlayers'; @@ -38,6 +38,9 @@ export const enrichChildren = ( } if (!subNode.$thing) { + if (refSchema.contentType === 'FLEX') { + return { ...subNode, [FlexDataValue]: true }; + } throw new Error('[Wrong format] The field $thing is required in refFields'); } return { ...subNode, $op, $bzId }; diff --git a/src/types/symbols/index.ts b/src/types/symbols/index.ts index 2f994f6..f0dc519 100644 --- a/src/types/symbols/index.ts +++ b/src/types/symbols/index.ts @@ -21,6 +21,9 @@ export const FieldSchema = Symbol.for('fieldSchema'); /// Shared schema metadata export const SharedMetadata = Symbol.for('sharedMetadata'); +/// Marks plain objects stored as data values in FLEX ref fields +export const FlexDataValue = Symbol.for('flexDataValue'); + /// SurrealDB schema metadata export const SuqlMetadata = Symbol.for('suqlMetadata'); diff --git a/tests/unit/mutations/refFields.ts b/tests/unit/mutations/refFields.ts index d2f93c8..a2c3e10 100644 --- a/tests/unit/mutations/refFields.ts +++ b/tests/unit/mutations/refFields.ts @@ -1146,6 +1146,8 @@ export const testRefFieldsMutations = createTest('Mutation: RefFields', (ctx) => { ...flexWithObject, $thing: 'FlexRefRel', + // We need to link something when creating a relation to avoid "[Wrong format] Can't create a relation without any player". + space: { id: 'fl8-space', name: 'fl8-space' }, }, ], { noMetadata: true }, From d31938f2fe351604e6d351b97a80855c61d8c5f9 Mon Sep 17 00:00:00 2001 From: Agus Putra Dana Date: Fri, 13 Feb 2026 23:30:48 +0800 Subject: [PATCH 3/5] Fix: Array in FLEX field mutation threw an error --- src/adapters/surrealDB/parsing/values.ts | 6 +-- .../bql/enrichSteps/enrichChildren.ts | 16 ++++++++ src/stateMachine/mutation/surql/build.ts | 14 ++++++- tests/unit/mutations/refFields.ts | 37 +++++++++++++++++++ 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/adapters/surrealDB/parsing/values.ts b/src/adapters/surrealDB/parsing/values.ts index 03fb649..56a36f1 100644 --- a/src/adapters/surrealDB/parsing/values.ts +++ b/src/adapters/surrealDB/parsing/values.ts @@ -1,4 +1,4 @@ -import { isArray, isDate } from 'radash'; +import { isDate } from 'radash'; import { parseFlexValSurrealDB } from './parseFlexVal'; export const surrealDBtypeMap: Record = { @@ -54,9 +54,7 @@ export const parseValueSurrealDB = (value: unknown, ct?: string): any => { } return `"${value}"`; //let surrealDB try to do the conversion case 'FLEX': { - // array elements go throw the parsing - const parsedVal = isArray(value) ? value.map((v) => parseFlexValSurrealDB(v)) : parseFlexValSurrealDB(value); - return `${isArray(parsedVal) ? parsedVal.map((v) => v) : parsedVal}`; + return parseFlexValSurrealDB(value); } default: throw new Error(`Unsupported data field type ${ct}.`); diff --git a/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts b/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts index 8ab9311..8904369 100644 --- a/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts +++ b/src/stateMachine/mutation/bql/enrichSteps/enrichChildren.ts @@ -13,6 +13,16 @@ import { get$bzId } from '../shared/get$bzId'; import { getOp } from '../shared/getOp'; import { getOppositePlayers } from '../shared/getOppositePlayers'; +const markFlexDataValues = (value: unknown): unknown => { + if (isArray(value)) { + return value.map(markFlexDataValues); + } + if (isObject(value)) { + return { ...(value as Record), [FlexDataValue]: true }; + } + return value; +}; + export const enrichChildren = ( node: BQLMutationBlock, field: string, @@ -32,6 +42,12 @@ export const enrichChildren = ( if (!isObject(subNode)) { if (refSchema.contentType === 'FLEX') { + if (isArray(subNode)) { + // Wrap in array to prevent flatMap from flattening the inner array, + // and recursively mark nested objects with FlexDataValue so the + // traverser in enrich.ts skips them. + return [markFlexDataValues(subNode)]; + } return subNode; } throw new Error(`[Wrong format] The refField ${field} must receive an object`); diff --git a/src/stateMachine/mutation/surql/build.ts b/src/stateMachine/mutation/surql/build.ts index ec7c5c4..ca4b538 100644 --- a/src/stateMachine/mutation/surql/build.ts +++ b/src/stateMachine/mutation/surql/build.ts @@ -317,7 +317,19 @@ export const buildSURQLMutation = async (flat: FlatBqlMutation, schema: Enriched if (contentType === 'FLEX') { //todo: card one check len 1 //todo: add/remove etc - return `${rf} = ${cardinality === 'ONE' ? `array::flatten([${block[rf]}])[0]` : `array::flatten([${block[rf]}])`}`; + if (cardinality === 'ONE') { + return `${rf} = array::flatten([${block[rf]}])[0]`; + } + // For MANY: flatten only variable references (entity matches) individually, + // keeping data values (including nested arrays/objects) as-is. + const elements = isArray(block[rf]) ? block[rf] : [block[rf]]; + const processedElements = elements.map((el: unknown) => { + if (typeof el === 'string' && (el as string).startsWith('$')) { + return `array::flatten([${el}])[0]`; + } + return el; + }); + return `${rf} = [${processedElements.join(', ')}]`; } throw new Error(`Unsupported contentType ${contentType}`); diff --git a/tests/unit/mutations/refFields.ts b/tests/unit/mutations/refFields.ts index a2c3e10..516e803 100644 --- a/tests/unit/mutations/refFields.ts +++ b/tests/unit/mutations/refFields.ts @@ -1172,4 +1172,41 @@ export const testRefFieldsMutations = createTest('Mutation: RefFields', (ctx) => expect(res).toEqual(flexWithObject); }); + + it('fl9:[flex, object] Should accept an array of objects in flexReferences', async () => { + const flexWithObject = { + id: 'fl8-flex-with-object', + flexReferences: [[{ msg: 'Hello, world!' }]], + }; + await ctx.mutate( + [ + { + ...flexWithObject, + $thing: 'FlexRefRel', + // We need to link something when creating a relation to avoid "[Wrong format] Can't create a relation without any player". + space: { id: 'fl8-space', name: 'fl8-space' }, + }, + ], + { noMetadata: true }, + ); + + const res = await ctx.query( + { + $relation: 'FlexRefRel', + $id: 'fl8-flex-with-object', + $fields: ['id', 'flexReferences'], + }, + { noMetadata: true }, + ); + + //clean + await ctx.mutate({ + $thing: 'FlexRefRel', + $op: 'delete', + $id: 'fl8-flex-with-object', + space: { $op: 'delete' }, + }); + + expect(res).toEqual(flexWithObject); + }); }); From 3278f28b220913d7291e8182516ec1204a56dd0c Mon Sep 17 00:00:00 2001 From: Loic Veillard Date: Tue, 17 Feb 2026 22:11:05 +0100 Subject: [PATCH 4/5] chore: bump version to 0.15.4, update changelog, and upgrade pnpm to 9.15.4 Co-authored-by: Cursor --- changelog.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 6e5c260..5fe66e1 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,12 @@ 📝 following beta format X.Y.Z where Y = breaking change and Z = feature and fix. Later => FAIL.FEATURE.FIX +## 0.15.4(2026-02-17) + +- Fix: Plain object in flex field mutation threw an error +- Fix: Array in flex field mutation threw an error +- Tests: Added tests for flex fields with object values + ## 0.15.3(2026-02-12) - Fix: Upgrade SurrealDB SDK to 2.0.0-alpha.18 diff --git a/package.json b/package.json index 4a5f5dc..bac2c13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blitznocode/blitz-orm", - "version": "0.15.3", + "version": "0.15.4", "author": "blitznocode.com", "description": "Blitz-orm is an Object Relational Mapper (ORM) for graph databases that uses a JSON query language called Blitz Query Language (BQL). BQL is similar to GraphQL but uses JSON instead of strings. This makes it easier to build dynamic queries.", "main": "dist/index.mjs", @@ -106,5 +106,5 @@ "directories": { "test": "tests" }, - "packageManager": "pnpm@8.10.2+sha512.0782093d5ba6c7ad9462081bc1ef0775016a4b4109eca1e1fedcea6f110143af5f50993db36c427d4fa8c62be3920a3224db12da719d246ca19dd9f18048c33c" + "packageManager": "pnpm@9.15.4" } From 1289b862e9eec69ce97950dd9368cac8826e92bc Mon Sep 17 00:00:00 2001 From: Loic Veillard Date: Tue, 17 Feb 2026 22:18:40 +0100 Subject: [PATCH 5/5] fix: package pnpm --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bac2c13..b227416 100644 --- a/package.json +++ b/package.json @@ -106,5 +106,5 @@ "directories": { "test": "tests" }, - "packageManager": "pnpm@9.15.4" + "packageManager": "pnpm@10.30.0" }