@createdTime directive produces null for some records
Summary
@createdTime produces null for some records in a table while working correctly for others. All records are created via the same code path (tables.MyTable.post(record)). @updatedTime works correctly on the same affected records (via patch()).
Schema
type MyTable @table {
id: ID @primaryKey
name: String @indexed
normalizedName: String @indexed
externalId: String @indexed
tier: String
lastSyncedAt: Date
createdAt: Date @createdTime
updatedAt: Date @updatedTime
}
Write code
All records are created by the same function:
const existing = await findByExternalId(externalId); // uses search()
const record = {
name: org.name,
normalizedName: normalize(org.name),
externalId,
tier,
lastSyncedAt: new Date().toISOString(),
};
if (existing) {
await tables.MyTable.patch(existing.id, record);
} else {
await tables.MyTable.post(record);
}
This runs on a 12-hour setInterval scheduler. On the first run (application startup), ~24 records were created via post(). On a later run, 1 additional record was created via post() when a new entry appeared in the external system. That record has createdAt: null.
Observed behavior
1. Operations API: search_by_value with get_attributes: ["*"]
Working record (created during initial startup):
[
{
"name": "Acme Corp",
"normalizedName": "acme corp",
"externalId": "12345",
"tier": "premium",
"lastSyncedAt": "2026-04-08T13:42:29.704Z",
"updatedAt": "2026-04-08T13:42:29.704Z",
"id": "f0729b11-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"createdAt": "2026-03-25T00:08:54.524Z"
}
]
Broken record (created on a later scheduler cycle):
[
{
"name": "Globex Inc",
"normalizedName": "globex inc",
"externalId": "67890",
"tier": "premium",
"lastSyncedAt": "2026-04-08T13:42:29.803Z",
"id": "7f823526-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"updatedAt": "2026-04-08T13:42:29.803Z"
}
]
createdAt is omitted entirely from the wildcard response for the broken record.
2. Operations API: search_by_value with explicit get_attributes
Working record:
[
{
"name": "Acme Corp",
"createdAt": "2026-03-25T00:08:54.524Z",
"updatedAt": "2026-04-08T13:42:29.704Z",
"id": "f0729b11-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
Broken record:
[
{
"name": "Globex Inc",
"createdAt": null,
"updatedAt": "2026-04-08T13:42:29.803Z"
}
]
When explicitly requested, createdAt is returned as null for the broken record.
3. Programmatic search() vs get() — Object.keys() difference
Additionally, get() and search() return records with different enumerability for all records (not just broken ones):
// search() — keys are enumerable
for await (const r of tables.MyTable.search({ conditions: [...], limit: 1 })) {
console.log(Object.keys(r));
// ["name","normalizedName","externalId","tier","lastSyncedAt","updatedAt","id","createdAt"]
}
// get() — keys are NOT enumerable
const g = await tables.MyTable.get(id);
console.log(Object.keys(g));
// [] (empty for ALL records, working and broken)
// Direct property access works: g.createdAt returns Date or null
// JSON.stringify(g) includes all fields
This is a separate observation from the null issue but may be related — get() returns an LMDB-backed object where no properties appear in Object.keys().
4. Scope
This is not limited to one table:
| Table |
Schema |
Write method |
Records affected |
Table A (@table) |
@createdTime + @updatedTime |
post() |
1 of 25 records has createdAt: null |
Table B (@table(expiration: 7776000)) |
@createdTime only |
post() |
All records have createdAt: null |
Table C (@table) |
@createdTime + @updatedTime |
put() |
Some records have both timestamps null |
Table D (@table(expiration: 7776000)) |
@createdTime + @updatedTime |
put() |
1 record has createdAt: null |
Expected behavior
@createdTime should populate createdAt with a Date timestamp on every post() and first put(), regardless of when the write occurs during the application lifecycle.
Environment
- Harper Version: 4.7.24
- Running on Harper Fabric
- Schemas defined via
.graphql files
- TypeScript resources with Node.js type stripping
- Records written programmatically via
tables.TableName.post() and tables.TableName.put()
@createdTimedirective producesnullfor some recordsSummary
@createdTimeproducesnullfor some records in a table while working correctly for others. All records are created via the same code path (tables.MyTable.post(record)).@updatedTimeworks correctly on the same affected records (viapatch()).Schema
Write code
All records are created by the same function:
This runs on a 12-hour
setIntervalscheduler. On the first run (application startup), ~24 records were created viapost(). On a later run, 1 additional record was created viapost()when a new entry appeared in the external system. That record hascreatedAt: null.Observed behavior
1. Operations API:
search_by_valuewithget_attributes: ["*"]Working record (created during initial startup):
[ { "name": "Acme Corp", "normalizedName": "acme corp", "externalId": "12345", "tier": "premium", "lastSyncedAt": "2026-04-08T13:42:29.704Z", "updatedAt": "2026-04-08T13:42:29.704Z", "id": "f0729b11-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "createdAt": "2026-03-25T00:08:54.524Z" } ]Broken record (created on a later scheduler cycle):
[ { "name": "Globex Inc", "normalizedName": "globex inc", "externalId": "67890", "tier": "premium", "lastSyncedAt": "2026-04-08T13:42:29.803Z", "id": "7f823526-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "updatedAt": "2026-04-08T13:42:29.803Z" } ]createdAtis omitted entirely from the wildcard response for the broken record.2. Operations API:
search_by_valuewith explicitget_attributesWorking record:
[ { "name": "Acme Corp", "createdAt": "2026-03-25T00:08:54.524Z", "updatedAt": "2026-04-08T13:42:29.704Z", "id": "f0729b11-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } ]Broken record:
[ { "name": "Globex Inc", "createdAt": null, "updatedAt": "2026-04-08T13:42:29.803Z" } ]When explicitly requested,
createdAtis returned asnullfor the broken record.3. Programmatic
search()vsget()—Object.keys()differenceAdditionally,
get()andsearch()return records with different enumerability for all records (not just broken ones):This is a separate observation from the
nullissue but may be related —get()returns an LMDB-backed object where no properties appear inObject.keys().4. Scope
This is not limited to one table:
@table)@createdTime+@updatedTimepost()createdAt: null@table(expiration: 7776000))@createdTimeonlypost()createdAt: null@table)@createdTime+@updatedTimeput()@table(expiration: 7776000))@createdTime+@updatedTimeput()createdAt: nullExpected behavior
@createdTimeshould populatecreatedAtwith aDatetimestamp on everypost()and firstput(), regardless of when the write occurs during the application lifecycle.Environment
.graphqlfilestables.TableName.post()andtables.TableName.put()