Skip to content

nullable properties are missing in model #1125

@thedomeffm

Description

@thedomeffm

Package version

21.6.1

Describe the bug

The nullable column "clientId" does not get initialized so the logical "null" check on the model is not executed as expected.

// The model
export default class CognitoUserPoolClient extends BaseModel {
    @column({ isPrimary: true })
    declare id: string;

    /* ... */

    @column()
    declare isDefault: boolean;

    @column()
    declare userPoolId: string;

    @column()
    declare clientName: string;

    @column()
    declare clientId: string | null; // <-----
}
// The code
const defaultCognitoUserPoolClient = await CognitoUserPoolClient.firstOrCreate(
    { isDefault: true },
    {
        userPoolId: 'foo',
        clientName: 'bar',
    },
);

console.log(defaultCognitoUserPoolClient); // log 1 <--
await defaultCognitoUserPoolClient.refresh();
console.log(defaultCognitoUserPoolClient); // log 2 <--

if (defaultCognitoUserPoolClient.clientId === null) {
    // consider null clientId as not created UserPoolClient.
    const userPoolClient = await cognitoIdentityProvider.createUserPoolClient(
        /* ... */
    );
    await defaultCognitoUserPoolClient.merge({ clientId: userPoolClient.ClientId }).save();
}

The Problem: the defaultCognitoUserPoolClient.clientId === null is always false when not calling await defaultCognitoUserPoolClient.refresh();! The clientId is just missing in the model.

see log 1 output (BEFORE refresh):

CognitoUserPoolClient {
  modelOptions: { connection: 'postgres' },
  modelTrx: undefined,
  transactionListener: [Function: bound listener],
  fillInvoked: false,
  cachedGetters: {},
  forceUpdate: false,
  '$columns': {},
  '$attributes': {
    isDefault: true,
    userPoolId: 'foo',
    clientName: 'bar',
    id: '019840bf-6aa3-75e6-bee8-7c931db59490',
    createdAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
    updatedAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US }
  },
  '$original': {
    isDefault: true,
    userPoolId: 'foo',
    clientName: 'bar',
    id: '019840bf-6aa3-75e6-bee8-7c931db59490',
    createdAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
    updatedAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US }
  },
  '$preloaded': {},
  '$extras': {},
  '$sideloaded': {},
  '$isPersisted': true,
  '$isDeleted': false,
  '$isLocal': true,
  isDefault: true,
  userPoolId: 'foo',
  clientName: 'bar',
  id: '019840bf-6aa3-75e6-bee8-7c931db59490',
  createdAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
  updatedAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US }
}

and log 2 output (AFTER refresh):

CognitoUserPoolClient {
  modelOptions: { connection: 'postgres' },
  modelTrx: undefined,
  transactionListener: [Function: bound listener],
  fillInvoked: true,
  cachedGetters: {},
  forceUpdate: false,
  '$columns': {},
  '$attributes': {
    id: '019840bf-6aa3-75e6-bee8-7c931db59490',
    createdAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
    updatedAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
    isDefault: true,
    userPoolId: 'foo',
    clientName: 'bar',
    clientId: null
  },
  '$original': {
    id: '019840bf-6aa3-75e6-bee8-7c931db59490',
    createdAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
    updatedAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
    isDefault: true,
    userPoolId: 'foo',
    clientName: 'bar',
    clientId: null
  },
  '$preloaded': {},
  '$extras': {},
  '$sideloaded': {},
  '$isPersisted': true,
  '$isDeleted': false,
  '$isLocal': true,
  isDefault: true,
  userPoolId: 'foo',
  clientName: 'bar',
  id: '019840bf-6aa3-75e6-bee8-7c931db59490',
  createdAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
  updatedAt: DateTime { ts: 2025-07-25T08:42:23.270+00:00, zone: UTC, locale: en-US },
  clientId: null // <-- Exists with correct null value as expected!
}

It is weird that the nullable clientId is just... not there in the model. Of course my IDE will say it is string | null and that the if-clause work as expected, but the truth is that the expected behavior of Lucid does not match the reality.

Reproduction repo

No response

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions