MemberJunction / MemberJunction/MJ
No way to mark a NOT NULL column as server-assigned, so minted identifiers (order/invoice/contract numbers) cannot be created through the UI without a workaround
- Dominant language
- TSQL
- Stars
- 29
- Forks
- 6
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 323
Description
## Summary
MJ has no way to say **"this column is NOT NULL, but the server assigns it on insert."** Nullability is the only signal, so a server-assigned column is indistinguishable from one the user must fill: the client-side validator refuses the create before the server can mint a value, and the form renders the field as required-and-empty.
The result is that a perfectly ordinary pattern — a sequence-backed document number minted in `Save()` — cannot be created through the UI at all without a workaround.
## Where it comes from
`EntityField.Validate()` (`packages/MJCore/src/generic/baseEntity.ts`, ~line 315):
```ts
if (!ef.ReadOnly && !ef.SkipValidation) {
if (!ef.AllowsNull && (this.Value === null || this.Value === undefined)) {
if (ef.DefaultValue is empty) -> ERROR
else if (this._OldValue != null) -> ERROR // existing record only
}
}
```
and the form's required styling (`form-field.component.ts`):
```ts
get IsRequired(): boolean { return this.FieldInfo?.AllowsNull === false; }
```
So there are exactly two escapes, and each is a side effect rather than an expression of intent:
1. **A `DefaultValue`.** MJ already treats a defaulted NOT NULL column as legal-when-null on a *new* record — which is precisely the semantic wanted. But a real DB default whose definition is an *expression* breaks CodeGen (it string-quotes it into `spCreate`, which then fails to compile and is left dropped — see the companion issue), so this escape is unavailable for anything but a literal.
2. **`AllowUpdateAPI = false`**, which makes the field `ReadOnly`, which makes `SkipValidation` true, which skips the check. This works, and it is what we shipped — but it is a blunt instrument: it also permanently forecloses *deliberate* manual entry, and it says "not writable through the API" when the truth is "written by the server, not by the user."
Neither says what is actually true, and both are discovered by reading MJ's internals rather than by reading the field's metadata.
## What we'd like
An explicit flag on `EntityField` — `IsServerAssigned` (or `AssignedOnInsert`) — meaning: *the value is supplied during insert by server-side logic; do not demand it from the user and do not treat its absence as invalid on a new record.*
Behaviour it should drive:
- `EntityField.Validate()` skips the not-null check **on a new record only** (an existing record with a null value is still wrong).
- `form-field`'s `IsRequired` returns false, so no red required styling and no "cannot be null" on save.
- The field stays fully writable, so an explicit value supplied by the caller is still honoured — which is the part `AllowUpdateAPI = false` gives up.
- Ideally the generated form renders it read-only-with-explanation on create rather than as an empty required input.
## Why it is worth a first-class flag
This is not a niche case — it is every minted identifier: order numbers, invoice numbers, contract numbers, ticket numbers, any `NEXT VALUE FOR`-backed sequence. Each one currently has to rediscover the same two workarounds and pick the least-bad. A flag makes the intent legible in metadata, keeps validation honest for existing records, and removes the incentive to reach for a DB default (which is currently actively dangerous per the CodeGen issue).
## Context
Found in `bizapps-contracts`, MJ `6.1.0-edge.2/3`: `Contract.ContractNumber` is NOT NULL and minted by `ContractEntityServer.Save()` from `seq_ContractNumber` via `spAssignNextContractNumber`. Until `AllowUpdateAPI = false` was applied, every browser create failed with "Contract Number cannot be null" and users hand-typed numbers instead.
Contributor guide
Research direction
Start with EntityField.Validate() in packages/MJCore/src/generic/baseEntity.ts and IsRequired in form-field.component.ts. Trace how EntityField metadata reaches validation and generated forms, then define the new flag's behavior for new versus existing records. Done means server-assigned NOT NULL fields can be created without a value, remain writable for explicit values, and are not shown as required.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- full-stack
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 65/100