@objectstack/plugin-sharing 9.2.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/plugin-sharing",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Record-level sharing for ObjectStack — sys_record_share + middleware that enforces sharingModel + ISharingService.",
6
6
  "main": "dist/index.js",
@@ -13,10 +13,10 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "@objectstack/core": "9.2.0",
17
- "@objectstack/objectql": "9.2.0",
18
- "@objectstack/platform-objects": "9.2.0",
19
- "@objectstack/spec": "9.2.0"
16
+ "@objectstack/core": "9.3.0",
17
+ "@objectstack/objectql": "9.3.0",
18
+ "@objectstack/platform-objects": "9.3.0",
19
+ "@objectstack/spec": "9.3.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^25.9.2",
@@ -249,7 +249,7 @@ export function registerShareLinkRoutes(
249
249
  const SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;
250
250
  const rows = await engine.find('ai_messages', {
251
251
  where: { conversation_id: resolved.link.record_id },
252
- sort: [{ field: 'created_at', direction: 'asc' }],
252
+ sort: [{ field: 'created_at', order: 'asc' }],
253
253
  limit: 500,
254
254
  context: SYSTEM_CTX,
255
255
  } as any);
@@ -149,7 +149,7 @@ export class SharingRuleService implements ISharingRuleService {
149
149
  if (orgId) where.organization_id = orgId;
150
150
  const rows = await this.engine.find('sys_sharing_rule', {
151
151
  filter: where,
152
- orderBy: [{ field: 'name', direction: 'asc' }],
152
+ orderBy: [{ field: 'name', order: 'asc' }],
153
153
  limit: 1000,
154
154
  context: SYSTEM_CTX,
155
155
  });
@@ -42,7 +42,20 @@ function makeFakeEngine(schemas: Record<string, any>) {
42
42
  async find(object: string, options?: any) {
43
43
  const table = ensure(object);
44
44
  const filter = options?.filter ?? options?.where;
45
- return table.filter(r => matches(r, filter)).slice(0, options?.limit ?? 1000);
45
+ let out = table.filter(r => matches(r, filter));
46
+ if (options?.orderBy?.[0]) {
47
+ // Canonical SortNode key only (spec/data/query.zod.ts): the real
48
+ // engine strips an unknown `direction:` key and defaults to asc, so
49
+ // the mock must too — honoring both keys masks wrong-key sorts.
50
+ const { field, order } = options.orderBy[0];
51
+ out = [...out].sort((a, b) => {
52
+ const av = a[field]; const bv = b[field];
53
+ if (av === bv) return 0;
54
+ const cmp = av > bv ? 1 : -1;
55
+ return order === 'desc' ? -cmp : cmp;
56
+ });
57
+ }
58
+ return out.slice(0, options?.limit ?? 1000);
46
59
  },
47
60
  async insert(object: string, data: any) {
48
61
  const row = { ...data };
@@ -236,6 +249,17 @@ describe('SharingService.grant / listShares / revoke', () => {
236
249
  expect(rows.length).toBe(2);
237
250
  });
238
251
 
252
+ it('listShares returns the newest grant first', async () => {
253
+ // Regression: the query sorted with the non-canonical `direction: 'desc'`
254
+ // key, which SortNode strips — so it sorted ascending (oldest first).
255
+ engine._tables.sys_record_share = [
256
+ { id: 'shr_old', object_name: 'account', record_id: 'a1', recipient_id: 'bob', created_at: '2026-01-01T00:00:00Z' },
257
+ { id: 'shr_new', object_name: 'account', record_id: 'a1', recipient_id: 'carol', created_at: '2026-02-01T00:00:00Z' },
258
+ ];
259
+ const rows = await svc.listShares('account', 'a1', { userId: 'admin' });
260
+ expect(rows.map(r => r.id)).toEqual(['shr_new', 'shr_old']);
261
+ });
262
+
239
263
  it('revoke removes the row', async () => {
240
264
  const r = await svc.grant({ object: 'account', recordId: 'a1', recipientId: 'bob' }, { userId: 'admin' });
241
265
  await svc.revoke(r.id, { userId: 'admin' });
@@ -266,7 +266,7 @@ export class SharingService implements ISharingService {
266
266
  ): Promise<RecordShare[]> {
267
267
  const rows = await this.engine.find('sys_record_share', {
268
268
  filter: { object_name: object, record_id: recordId },
269
- orderBy: [{ field: 'created_at', direction: 'desc' }],
269
+ orderBy: [{ field: 'created_at', order: 'desc' }],
270
270
  limit: 500,
271
271
  context: SYSTEM_CTX,
272
272
  });