Blaze style additionalProperties for Objects?
In my effort to convert my Blaze rules into Bolt rules I'm trying to figure out exactly how a Bolt Object subclass of my own can support my specified properties, as well as additional ones I do not specify. In Blaze this was done by setting additionalProperties=true. Is there an equivalent in Bolt? or what is the strategy if I want an open object with only some of the properties defined/checked?
Or at least allow the rule for $other to be specified.. maybe add an other() { } to a type? or allow 'path /somepath/{other} { validate() { true }}' even when a type is used at /somepath (currently not supported, gets ignored)
Using Bolt types, a node in a Firebase datastore is either a collection (no specifically named property keys), or an Object (doesn't now allow additional un-named properties).
The escape hatch is to use path statements instead of types. So if you wanted:
// This example is NOT supported in Bolt
type User {
name: String;
age: Number;
$other: String;
}
path /users/{uid} is User;
you could do this:
path /users/{uid} {
/name is String;
/age is Number;
/{other} is String;
}
Another way to approach this is to put all your additional properties in a sub-collection:
type User {
name: String;
age: Number;
other: String[];
}
path /users/{uid} is User;
This changes the structure of your data - but isolates your unstructured data into a single collection.
Would you please consider a solution that supports open types? Throwing away the type system doesn't seem like a reasonable solution to this problem.
Seems reasonable for types to have an option to specify how other, non-specified properties, should be handled. This would give everyone the benefits of the Type system in Bolt, as well as the capabilities we are accustomed to, without making us bend over backwards to work around this deficiency and resort to non-reusable path statements everywhere.
As an example. If the customer pain is not obvious enough yet. Lets say my User type has 15 properties and allows for open ended properties (currently specified using Blaze just fine). Now assume that User type is appropriate at a half-dozen paths in the database. I either have to change how my data is defined because of Bolt (which seems like an anti-goal), or I have to abandon the re-usable type notation and specify the full User spec at every path in my rule system, which also seems like an anti-goal to having the Type system in the first place.
Is forcing types to be closed a really important design feature for Bolt? What does it get us that I'm missing?
I think an open type system is probably a bad idea in most cases - any such system is very difficult to update over time unless you're careful to restrict the class of names that can be used for the "extensible" properties in your types (if you add a named property to an existing type - you may already have old data in the extensible portion that will now overlap unexpectedly). Using a sub-collection for extensible proprties would be preferable, IMO.
So, we have a goal to dissuade people from using this as a common design pattern. That said, I take your point that if the developer REALLY WANTS to do this - we should not actively get in the way to make things extra hard.
Maybe something like:
type User {
name: String;
age: Number;
{other}: String;
}
(and maybe emitting a warning at compile time that the User type is open ended).
It might have less to do with a developer really wanting to do something, and more that data and schemas already exist that do this. So maybe Bolt needs a warning that it is not intended as a general-purpose rule syntax for Firebase. There are schemas that are either not-possible, or not-practical to define in Bolt because of some of the design decisions. Bolt then has a highly opinionated way of defining schemas and if your data does not conform, it will be difficult/impossible to use Bolt.
I went into this assuming this was a replacement for Blaze so we can move to a more attractive format instead of YAML. With my current app and data, Bolt won't work. I'll need to change my schema or wait for Bolt to be more flexible before I can use the nicer notation. It is unfortunate, Bolt has a lot of advantages over Blaze.
As a work-around to our limitation on types, you mentioned the path-only design is not tenable since you may have the same type represented in across different locations in the database.
Do you think it would work to use functions instead of types to capture the re-usable portion of your type definition?
isUser(ref) { ref.name.isString() && ref.age.isNumber() }
type UserLike {
validate() { isUser(this) }
$other: String;
}
path /x is UserLike;
path /y is UserLike;
path /z is UserLike {
/karma is Number;
}
which generates:
{
"rules": {
"x": {
".validate": "newData.hasChildren() && (newData.child('name').isString() && newData.child('age').isNumber())",
"$other": {
".validate": "newData.isString()"
}
},
"y": {
".validate": "newData.hasChildren() && (newData.child('name').isString() && newData.child('age').isNumber())",
"$other": {
".validate": "newData.isString()"
}
},
"z": {
"karma": {
".validate": "newData.isNumber()"
},
".validate": "newData.hasChildren() && (newData.child('name').isString() && newData.child('age').isNumber())",
"$other": {
".validate": "newData.isString()"
}
}
}
}
First of all, thanks for the quick responses!
Are suggesting this as a temporary workaround? or as a fundamental adjustment given the design of Bolt? To be candid, I consider both of these workarounds as anti-patterns and if I have to I would rather go through the painful process of changing my schema to fit Bolt than to use these. It would seem to me that part of the goal of Bolt is to be clear and straightforward so it is easy to interpret rules. Given that goal, I would expect that the Type System (the center of that communication) to be a servant of the developer and assist us in communicating clear intent to the Firebase System, as well as the other developers on the team. Communicating what to do with $other properties found on an object seems to me to be a reasonable expectation of this system.
I appreciate your effort in proposing two workarounds to the current version of Bolt. However, I want to be clear so I understand if they are permanent solutions to the issue or not, because I see some serious flaws. They make it difficult and counterintuitive to understand the rules being introduced. Aren't we still early enough with Bolt that we should be evaluating it and verifying it meets the needs of the developers?
From my perspective, the conversation should be about how and when Bolt can be improved to accommodate reasonable use-cases. Or a discussion about the reasonableness of this use-case if that is in question.
Yes, I was mainly suggesting as a temporary work around; but also wanted your feedback (as you've given) on whether they are good enough for you to move forward - or if you really need an update to types sooner rather than later.
I have been viewing Bolt as a test-bed for getting feedback on the design possibilities. For example, the type system (as opposed to path statements) could be viewed as something that belongs in a separate language as a companion to the access-control rules of the path-statements.
They already have some overlap - so it's occurred to me that the type language might be considered "optional" from a completeness point of view; but I see from your feedback that we don't have the same powerful yet simple constructs w/o types that we do with them.
Thanks for hashing it out with me!