Skip to content

Commit 7a4652f

Browse files
committed
fix(metadata-protocol): type the caller read's options to the engine contract
Claude-Session: https://claude.ai/code/session_01YNquHRZKDpiKsgmHXSd9Jx Co-authored-by: Claude <noreply@anthropic.com>
1 parent ea530cf commit 7a4652f

2 files changed

Lines changed: 11 additions & 7 deletions

File tree

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14978,7 +14978,7 @@ export class ObjectStackProtocolImplementation implements
1497814978
id: string,
1497914979
current: any,
1498014980
expectedVersion: string | undefined,
14981-
context: unknown,
14981+
context: EngineQueryOptionsParsed['context'],
1498214982
): Promise<void> {
1498314983
assertVersionTokenNotMalformed(expectedVersion);
1498414984
const expected = normaliseVersionToken(expectedVersion);
@@ -15028,7 +15028,7 @@ export class ObjectStackProtocolImplementation implements
1502815028
object: string,
1502915029
id: string,
1503015030
expectedVersion: string | undefined,
15031-
context: unknown,
15031+
context: EngineQueryOptionsParsed['context'],
1503215032
): Promise<void> {
1503315033
assertVersionTokenNotMalformed(expectedVersion);
1503415034
if (!normaliseVersionToken(expectedVersion)) return;
@@ -15042,8 +15042,8 @@ export class ObjectStackProtocolImplementation implements
1504215042
* spelling as the write it precedes: passed only when the request carried
1504315043
* one, so a caller with no context reads as its write would run.
1504415044
*/
15045-
private async readAsCaller(object: string, id: string, context: unknown): Promise<any> {
15046-
const opts: any = { where: { id } };
15045+
private async readAsCaller(object: string, id: string, context: EngineQueryOptionsParsed['context']): Promise<any> {
15046+
const opts: EngineQueryOptionsParsed = { where: { id } };
1504715047
if (context !== undefined) opts.context = context;
1504815048
return this.engine.findOne(object, opts);
1504915049
}

‎packages/runtime/src/domains/data-conflict-caller-visibility.test.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
4444
import { PermissionSetSchema } from '@objectstack/spec/security';
45+
import type { EngineQueryOptions } from '@objectstack/spec/data';
4546
import { ObjectQL } from '@objectstack/objectql';
4647
import { SqlDriver } from '@objectstack/driver-sql';
4748
import { SecurityPlugin } from '@objectstack/plugin-security';
@@ -69,7 +70,7 @@ const MEMBER_SET = PermissionSetSchema.parse({
6970
rowLevelSecurity: [{ name: 'region_scope', object: SCOPED, operation: 'all', using: "record.region != 'HIDDEN'" }],
7071
});
7172

72-
const MEMBER_CTX = { userId: 'usr_member', positions: [], permissions: [MEMBER_SET.name], posture: 'MEMBER' };
73+
const MEMBER_CTX = { userId: 'usr_member', positions: [] as string[], permissions: [MEMBER_SET.name], posture: 'MEMBER' as const };
7374

7475
/** A well-formed instant no row here carries: a token read before a write that moved it. */
7576
const STALE = '2000-01-01T00:00:00.000Z';
@@ -81,6 +82,9 @@ const MASKED_ID = 'v1';
8182
/** The positive control's row. */
8283
const PLAIN_ID = 'p1';
8384

85+
/** A by-id read's options, typed to the engine's contract. */
86+
const byId = (id: string, context: EngineQueryOptions['context']): EngineQueryOptions => ({ where: { id }, context });
87+
8488
const clone = <T>(v: T): T => (v === undefined ? v : JSON.parse(JSON.stringify(v)));
8589

8690
interface Answer {
@@ -179,7 +183,7 @@ describe('[#23586] a 409 CONCURRENT_UPDATE reports only what the caller may read
179183

180184
/** The row as the system reads it now (`null` once deleted). */
181185
const systemRow = async (object: string, id: string) =>
182-
(await engine.findOne(object, { where: { id }, context: SYS_CTX } as any)) as any;
186+
(await engine.findOne(object, byId(id, SYS_CTX))) as any;
183187
/** The row's current version token, as the system reads it. */
184188
const versionOf = async (object: string, id: string): Promise<string> => {
185189
const row = await systemRow(object, id);
@@ -316,7 +320,7 @@ describe('[#23586] a 409 CONCURRENT_UPDATE reports only what the caller may read
316320
const record: any = doorIndex === 0 ? conflictState(stale.body).currentRecord : thrownRecord;
317321
expect(record).toMatchObject({ id: MASKED_ID, title: 'visible', region: 'NA' });
318322
expect(record).not.toHaveProperty('secret');
319-
const callerRead: any = await engine.findOne(SCOPED, { where: { id: MASKED_ID }, context: clone(MEMBER_CTX) } as any);
323+
const callerRead: any = await engine.findOne(SCOPED, byId(MASKED_ID, clone(MEMBER_CTX)));
320324
expect(callerRead).not.toHaveProperty('secret');
321325
expect(record).toEqual(callerRead);
322326

0 commit comments

Comments
 (0)