[#7396] added nullable JSVM type helpers
This commit is contained in:
@@ -431,6 +431,32 @@ func baseBinds(vm *goja.Runtime) {
|
||||
return instanceValue
|
||||
})
|
||||
|
||||
// nullable helpers usually used as DynamicModel shape values
|
||||
vm.Set("nullString", func() *string {
|
||||
var v string
|
||||
return &v
|
||||
})
|
||||
vm.Set("nullFloat", func() *float64 {
|
||||
var v float64
|
||||
return &v
|
||||
})
|
||||
vm.Set("nullInt", func() *int64 {
|
||||
var v int64
|
||||
return &v
|
||||
})
|
||||
vm.Set("nullBool", func() *bool {
|
||||
var v bool
|
||||
return &v
|
||||
})
|
||||
vm.Set("nullArray", func() *types.JSONArray[any] {
|
||||
var v types.JSONArray[any]
|
||||
return &v
|
||||
})
|
||||
vm.Set("nullObject", func() *types.JSONMap[any] {
|
||||
var v types.JSONMap[any]
|
||||
return &v
|
||||
})
|
||||
|
||||
vm.Set("Record", func(call goja.ConstructorCall) *goja.Object {
|
||||
var instance *core.Record
|
||||
|
||||
@@ -1100,12 +1126,19 @@ var cachedDynamicModelStructs = store.New[string, reflect.Type](nil)
|
||||
// on the specified "shape".
|
||||
//
|
||||
// The "shape" values are used as defaults and could be of type:
|
||||
// - int (ex. 0)
|
||||
// - float (ex. -0)
|
||||
// - string (ex. "")
|
||||
// - bool (ex. false)
|
||||
// - slice (ex. [])
|
||||
// - map (ex. map[string]any{})
|
||||
//
|
||||
// - int64 (ex.: 0)
|
||||
// - *int64 (ex.: nullInt())
|
||||
// - float64 (ex.: -0)
|
||||
// - *float64 (ex.: nullFloat())
|
||||
// - string (ex.: "")
|
||||
// - *string (ex.: nullString())
|
||||
// - bool (ex.: false)
|
||||
// - *bool (ex.: nullBool())
|
||||
// - slice/arr (ex.: [])
|
||||
// - *slice/arr (ex.: nullArray())
|
||||
// - map (ex.: {})
|
||||
// - *map (ex.: nullObject())
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
@@ -1141,6 +1174,9 @@ func newDynamicModel(shape map[string]any) any {
|
||||
newV.Scan(raw)
|
||||
v = newV
|
||||
vt = reflect.TypeOf(newV)
|
||||
case reflect.Pointer:
|
||||
// for pointers always fallback to nil as their default value
|
||||
v = nil
|
||||
}
|
||||
|
||||
hash.WriteString(k)
|
||||
@@ -1169,6 +1205,9 @@ func newDynamicModel(shape map[string]any) any {
|
||||
|
||||
// load default values into the new model
|
||||
for i, item := range info {
|
||||
if item.value == nil {
|
||||
continue
|
||||
}
|
||||
elem.Field(i).Set(reflect.ValueOf(item.value))
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestBaseBindsCount(t *testing.T) {
|
||||
vm := goja.New()
|
||||
baseBinds(vm)
|
||||
|
||||
testBindsCount(vm, "this", 35, t)
|
||||
testBindsCount(vm, "this", 41, t)
|
||||
}
|
||||
|
||||
func TestBaseBindsSleep(t *testing.T) {
|
||||
@@ -1162,44 +1162,89 @@ func TestLoadingDynamicModel(t *testing.T) {
|
||||
|
||||
_, err := vm.RunString(`
|
||||
let result = new DynamicModel({
|
||||
text: "",
|
||||
bool: false,
|
||||
number: 0,
|
||||
select_many: [],
|
||||
json: [],
|
||||
// custom map-like field
|
||||
obj: {},
|
||||
string: "",
|
||||
nullString: nullString(),
|
||||
nullStringEmpty: nullString(),
|
||||
|
||||
bool: false,
|
||||
nullBool: nullBool(),
|
||||
nullBoolEmpty: nullBool(),
|
||||
|
||||
int: 0,
|
||||
nullInt: nullInt(),
|
||||
nullIntEmpty: nullInt(),
|
||||
|
||||
float: -0,
|
||||
nullFloat: nullFloat(),
|
||||
nullFloatEmpty: nullFloat(),
|
||||
|
||||
array: [],
|
||||
nullArray: nullArray(),
|
||||
nullArrayEmpty: nullArray(),
|
||||
|
||||
object: {},
|
||||
nullObject: nullObject(),
|
||||
nullObjectEmpty: nullObject(),
|
||||
})
|
||||
|
||||
const expectations = {
|
||||
"string": "a",
|
||||
"nullString": "b",
|
||||
"nullStringEmpty": null,
|
||||
|
||||
"bool": false,
|
||||
"nullBool": true,
|
||||
"nullBoolEmpty": null,
|
||||
|
||||
"int": 1,
|
||||
"nullInt": 2,
|
||||
"nullIntEmpty": null,
|
||||
|
||||
"float": 1.1,
|
||||
"nullFloat": 1.2,
|
||||
"nullFloatEmpty": null,
|
||||
|
||||
"array": [1,2],
|
||||
"nullArray": [3,4],
|
||||
"nullArrayEmpty": null,
|
||||
|
||||
"object": {a:1},
|
||||
"nullObject": {a:2},
|
||||
"nullObjectEmpty": null,
|
||||
};
|
||||
|
||||
// constuct dummy SELECT column value literals based on the expectations
|
||||
const selectColumns = [];
|
||||
for (const col in expectations) {
|
||||
const val = expectations[col]
|
||||
|
||||
if (val === null) {
|
||||
selectColumns.push("null as [[" + col + "]]")
|
||||
} else if (typeof val === "string") {
|
||||
selectColumns.push("'" + val + "' as [[" + col + "]]")
|
||||
} else if (typeof val === "object") {
|
||||
selectColumns.push("'" + JSON.stringify(val) + "' as [[" + col + "]]")
|
||||
} else {
|
||||
selectColumns.push(val + " as [[" + col + "]]")
|
||||
}
|
||||
}
|
||||
|
||||
$app.db()
|
||||
.select("text", "bool", "number", "select_many", "json", "('{\"test\": 1}') as obj")
|
||||
.from("demo1")
|
||||
.where($dbx.hashExp({"id": "84nmscqy84lsi1t"}))
|
||||
.limit(1)
|
||||
.newQuery("SELECT " + selectColumns.join(", "))
|
||||
.one(result)
|
||||
|
||||
if (result.text != "test") {
|
||||
throw new Error('Expected text "test", got ' + result.text);
|
||||
}
|
||||
for (const col in expectations) {
|
||||
let expVal = expectations[col];
|
||||
let resVal = result[col];
|
||||
|
||||
if (result.bool != true) {
|
||||
throw new Error('Expected bool true, got ' + result.bool);
|
||||
}
|
||||
if (expVal !== null && typeof expVal === "object") {
|
||||
expVal = JSON.stringify(expVal)
|
||||
resVal = JSON.stringify(resVal)
|
||||
}
|
||||
|
||||
if (result.number != 123456) {
|
||||
throw new Error('Expected number 123456, got ' + result.number);
|
||||
}
|
||||
|
||||
if (result.select_many.length != 2 || result.select_many[0] != "optionB" || result.select_many[1] != "optionC") {
|
||||
throw new Error('Expected select_many ["optionB", "optionC"], got ' + result.select_many);
|
||||
}
|
||||
|
||||
if (result.json.length != 3 || result.json[0] != 1 || result.json[1] != 2 || result.json[2] != 3) {
|
||||
throw new Error('Expected json [1, 2, 3], got ' + result.json);
|
||||
}
|
||||
|
||||
if (result.obj.get("test") != 1) {
|
||||
throw new Error('Expected obj.get("test") 1, got ' + JSON.stringify(result.obj));
|
||||
if (expVal != resVal) {
|
||||
throw new Error("Expected '" + col + "' value " + expVal + ", got " + resVal);
|
||||
}
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
|
||||
5949
plugins/jsvm/internal/types/generated/types.d.ts
vendored
5949
plugins/jsvm/internal/types/generated/types.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -259,27 +259,78 @@ declare function arrayOf<T>(model: T): Array<T>;
|
||||
*
|
||||
* Caveats:
|
||||
* - In order to use 0 as double/float initialization number you have to negate it (` + "`-0`" + `).
|
||||
* - You need to use lowerCamelCase when accessing the model fields (e.g. ` + "`model.roles`" + ` and not ` + "`model.Roles`" + `).
|
||||
* - You need to use lowerCamelCase when accessing the model fields (e.g. ` + "`model.roles`" + ` and not ` + "`model.Roles`" + ` even if in the model shape and in the DB table the column is capitalized).
|
||||
* - Objects are loaded into types.JSONMap, meaning that they need to be accessed with ` + "`get(key)`" + ` (e.g. ` + "`model.meta.get('something')`" + `).
|
||||
* - For describing nullable types you can use the ` + "`null*()`" + ` helpers - ` + "`nullString()`" + `, ` + "`nullInt()`" + `, ` + "`nullFloat()`" + `, ` + "`nullBool()`" + `, ` + "`nullArray()`" + `, ` + "`nullObject()`" + `.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const model = new DynamicModel({
|
||||
* name: ""
|
||||
* age: 0, // int64
|
||||
* totalSpent: -0, // float64
|
||||
* active: false,
|
||||
* Roles: [], // maps to "Roles" in the DB/JSON but the prop would be accessible via "model.roles"
|
||||
* meta: {}
|
||||
* name: "" // or nullString() if nullable
|
||||
* age: 0, // or nullInt() if nullable
|
||||
* totalSpent: -0, // or nullFloat() if nullable
|
||||
* active: false, // or nullBool() if nullable
|
||||
* Roles: [], // or nullArray() if nullable; maps to "Roles" in the DB/JSON but the prop would be accessible via "model.roles"
|
||||
* meta: {}, // or nullObject() if nullable
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class DynamicModel {
|
||||
[key: string]: any;
|
||||
constructor(shape?: { [key:string]: any })
|
||||
}
|
||||
|
||||
/**
|
||||
* nullString creates an empty Go string pointer usually used for
|
||||
* describing a **nullable** ` + "`DynamicModel`" + ` string value.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function nullString(): string;
|
||||
|
||||
/**
|
||||
* nullInt creates an empty Go int64 pointer usually used for
|
||||
* describing a **nullable** ` + "`DynamicModel`" + ` int value.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function nullInt(): number;
|
||||
|
||||
/**
|
||||
* nullFloat creates an empty Go float64 pointer usually used for
|
||||
* describing a **nullable** ` + "`DynamicModel`" + ` float value.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function nullFloat(): number;
|
||||
|
||||
/**
|
||||
* nullBool creates an empty Go bool pointer usually used for
|
||||
* describing a **nullable** ` + "`DynamicModel`" + ` bool value.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function nullBool(): boolean;
|
||||
|
||||
/**
|
||||
* nullArray creates an empty Go types.JSONArray pointer usually used for
|
||||
* describing a **nullable** ` + "`DynamicModel`" + ` JSON array value.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function nullArray(): Array<any>;
|
||||
|
||||
/**
|
||||
* nullObject creates an empty Go types.JSONMap pointer usually used for
|
||||
* describing a **nullable** ` + "`DynamicModel`" + ` JSON object value.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function nullObject(): { get(key:string):any; set(key:string,value:any):void };
|
||||
|
||||
interface Context extends context.Context{} // merge
|
||||
/**
|
||||
* Context creates a new empty Go context.Context.
|
||||
|
||||
Reference in New Issue
Block a user