@objectstack/objectql 4.0.1 → 4.0.2
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +9 -0
- package/dist/index.d.mts +22 -62
- package/dist/index.d.ts +22 -62
- package/dist/index.js +45 -53
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/plugin.integration.test.ts +75 -13
- package/src/plugin.ts +25 -45
- package/src/protocol.ts +11 -7
- package/src/registry.test.ts +16 -5
- package/src/registry.ts +30 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/objectql",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Isomorphic ObjectQL Engine for ObjectStack",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@objectstack/core": "4.0.
|
|
17
|
-
"@objectstack/spec": "4.0.
|
|
18
|
-
"@objectstack/types": "4.0.
|
|
16
|
+
"@objectstack/core": "4.0.2",
|
|
17
|
+
"@objectstack/spec": "4.0.2",
|
|
18
|
+
"@objectstack/types": "4.0.2"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"typescript": "^6.0.2",
|
|
@@ -15,7 +15,7 @@ describe('ObjectQLPlugin - Metadata Service Integration', () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
describe('Simple Mode (ObjectQL-only)', () => {
|
|
18
|
-
it('should register
|
|
18
|
+
it('should register objectql, data, and protocol services', async () => {
|
|
19
19
|
// Arrange
|
|
20
20
|
const plugin = new ObjectQLPlugin();
|
|
21
21
|
await kernel.use(plugin);
|
|
@@ -23,16 +23,15 @@ describe('ObjectQLPlugin - Metadata Service Integration', () => {
|
|
|
23
23
|
// Act
|
|
24
24
|
await kernel.bootstrap();
|
|
25
25
|
|
|
26
|
-
// Assert
|
|
27
|
-
const metadataService = kernel.getService('metadata');
|
|
28
|
-
expect(metadataService).toBeDefined();
|
|
29
|
-
|
|
30
|
-
// ObjectQL registers a MetadataFacade as the metadata service;
|
|
31
|
-
// it is separate from (but backed by the same registry as) the objectql service.
|
|
26
|
+
// Assert — ObjectQL no longer registers metadata (kernel provides fallback)
|
|
32
27
|
const objectql = kernel.getService('objectql');
|
|
33
28
|
expect(objectql).toBeDefined();
|
|
34
|
-
|
|
35
|
-
expect(
|
|
29
|
+
expect(kernel.getService('data')).toBeDefined();
|
|
30
|
+
expect(kernel.getService('protocol')).toBeDefined();
|
|
31
|
+
// metadata is provided by kernel's core fallback, not ObjectQL
|
|
32
|
+
const metadataService = kernel.getService('metadata');
|
|
33
|
+
expect(metadataService).toBeDefined();
|
|
34
|
+
expect((metadataService as any)._fallback).toBe(true);
|
|
36
35
|
});
|
|
37
36
|
|
|
38
37
|
it('should serve in-memory metadata definitions', async () => {
|
|
@@ -65,7 +64,7 @@ describe('ObjectQLPlugin - Metadata Service Integration', () => {
|
|
|
65
64
|
});
|
|
66
65
|
|
|
67
66
|
describe('Service Registration', () => {
|
|
68
|
-
it('should register
|
|
67
|
+
it('should register manifest service', async () => {
|
|
69
68
|
// Arrange
|
|
70
69
|
const plugin = new ObjectQLPlugin();
|
|
71
70
|
await kernel.use(plugin);
|
|
@@ -77,6 +76,7 @@ describe('ObjectQLPlugin - Metadata Service Integration', () => {
|
|
|
77
76
|
expect(kernel.getService('objectql')).toBeDefined();
|
|
78
77
|
expect(kernel.getService('data')).toBeDefined();
|
|
79
78
|
expect(kernel.getService('protocol')).toBeDefined();
|
|
79
|
+
expect(kernel.getService('manifest')).toBeDefined();
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
it('should respect existing metadata service', async () => {
|
|
@@ -146,8 +146,71 @@ describe('ObjectQLPlugin - Metadata Service Integration', () => {
|
|
|
146
146
|
expect(objectql.drivers?.has('mock-driver')).toBe(true);
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
it('should
|
|
149
|
+
it('should register apps via manifest service', async () => {
|
|
150
150
|
// Arrange
|
|
151
|
+
const plugin = new ObjectQLPlugin();
|
|
152
|
+
await kernel.use(plugin);
|
|
153
|
+
|
|
154
|
+
// Plugin that uses the manifest service directly
|
|
155
|
+
await kernel.use({
|
|
156
|
+
name: 'mock-app-plugin',
|
|
157
|
+
type: 'app',
|
|
158
|
+
version: '1.0.0',
|
|
159
|
+
dependencies: ['com.objectstack.engine.objectql'],
|
|
160
|
+
init: async (ctx) => {
|
|
161
|
+
ctx.getService<{ register(m: any): void }>('manifest').register({
|
|
162
|
+
id: 'test-app',
|
|
163
|
+
name: 'test_app',
|
|
164
|
+
version: '1.0.0',
|
|
165
|
+
type: 'app',
|
|
166
|
+
apps: [{ name: 'Test App' }],
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Act
|
|
172
|
+
await kernel.bootstrap();
|
|
173
|
+
|
|
174
|
+
// Assert
|
|
175
|
+
const objectql = kernel.getService('objectql') as any;
|
|
176
|
+
expect(objectql.registry).toBeDefined();
|
|
177
|
+
const apps = objectql.registry.getAllApps();
|
|
178
|
+
expect(apps.some((a: any) => a.name === 'Test App')).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should register manifests from start() phase via manifest service', async () => {
|
|
182
|
+
// Arrange — simulates SetupPlugin's pattern (registers in start, not init)
|
|
183
|
+
const plugin = new ObjectQLPlugin();
|
|
184
|
+
await kernel.use(plugin);
|
|
185
|
+
|
|
186
|
+
await kernel.use({
|
|
187
|
+
name: 'late-registerer',
|
|
188
|
+
type: 'standard',
|
|
189
|
+
version: '1.0.0',
|
|
190
|
+
dependencies: ['com.objectstack.engine.objectql'],
|
|
191
|
+
init: async () => {},
|
|
192
|
+
start: async (ctx) => {
|
|
193
|
+
ctx.getService<{ register(m: any): void }>('manifest').register({
|
|
194
|
+
id: 'late-app',
|
|
195
|
+
name: 'late_app',
|
|
196
|
+
version: '1.0.0',
|
|
197
|
+
type: 'plugin',
|
|
198
|
+
apps: [{ name: 'Late App' }],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Act
|
|
204
|
+
await kernel.bootstrap();
|
|
205
|
+
|
|
206
|
+
// Assert
|
|
207
|
+
const objectql = kernel.getService('objectql') as any;
|
|
208
|
+
const apps = objectql.registry.getAllApps();
|
|
209
|
+
expect(apps.some((a: any) => a.name === 'Late App')).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should still discover apps registered via legacy app.* convention', async () => {
|
|
213
|
+
// Arrange — legacy pattern for backward compatibility
|
|
151
214
|
const mockApp = {
|
|
152
215
|
manifest: {
|
|
153
216
|
id: 'test-app',
|
|
@@ -172,9 +235,8 @@ describe('ObjectQLPlugin - Metadata Service Integration', () => {
|
|
|
172
235
|
// Act
|
|
173
236
|
await kernel.bootstrap();
|
|
174
237
|
|
|
175
|
-
// Assert
|
|
238
|
+
// Assert — legacy pattern still works
|
|
176
239
|
const objectql = kernel.getService('objectql') as any;
|
|
177
|
-
// App should be registered (check via registry or apps list)
|
|
178
240
|
expect(objectql.registry).toBeDefined();
|
|
179
241
|
});
|
|
180
242
|
});
|
package/src/plugin.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
2
|
|
|
3
3
|
import { ObjectQL } from './engine.js';
|
|
4
|
-
import { MetadataFacade } from './metadata-facade.js';
|
|
5
4
|
import { ObjectStackProtocolImplementation } from './protocol.js';
|
|
6
5
|
import { Plugin, PluginContext } from '@objectstack/core';
|
|
7
6
|
|
|
@@ -33,45 +32,24 @@ export class ObjectQLPlugin implements Plugin {
|
|
|
33
32
|
|
|
34
33
|
// Register as provider for Core Kernel Services
|
|
35
34
|
ctx.registerService('objectql', this.ql);
|
|
36
|
-
|
|
37
|
-
// Register MetadataFacade as metadata service (unless external service exists)
|
|
38
|
-
let hasMetadata = false;
|
|
39
|
-
let metadataProvider = 'objectql';
|
|
40
|
-
try {
|
|
41
|
-
if (ctx.getService('metadata')) {
|
|
42
|
-
hasMetadata = true;
|
|
43
|
-
metadataProvider = 'external';
|
|
44
|
-
}
|
|
45
|
-
} catch (e: any) {
|
|
46
|
-
// Ignore errors during check (e.g. "Service is async")
|
|
47
|
-
}
|
|
48
35
|
|
|
49
|
-
if (!hasMetadata) {
|
|
50
|
-
try {
|
|
51
|
-
const metadataFacade = new MetadataFacade();
|
|
52
|
-
ctx.registerService('metadata', metadataFacade);
|
|
53
|
-
ctx.logger.info('MetadataFacade registered as metadata service', {
|
|
54
|
-
mode: 'in-memory',
|
|
55
|
-
features: ['registry', 'fast-lookup']
|
|
56
|
-
});
|
|
57
|
-
} catch (e: any) {
|
|
58
|
-
// Ignore if already registered (race condition or async mis-detection)
|
|
59
|
-
if (!e.message?.includes('already registered')) {
|
|
60
|
-
throw e;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
} else {
|
|
64
|
-
ctx.logger.info('External metadata service detected', {
|
|
65
|
-
provider: metadataProvider,
|
|
66
|
-
mode: 'will-sync-in-start-phase'
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
36
|
ctx.registerService('data', this.ql); // ObjectQL implements IDataEngine
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
37
|
+
|
|
38
|
+
// Register manifest service for direct app/package registration.
|
|
39
|
+
// Plugins call ctx.getService('manifest').register(manifestData)
|
|
40
|
+
// instead of the legacy ctx.registerService('app.<id>', manifestData) convention.
|
|
41
|
+
const ql = this.ql;
|
|
42
|
+
ctx.registerService('manifest', {
|
|
43
|
+
register: (manifest: any) => {
|
|
44
|
+
ql.registerApp(manifest);
|
|
45
|
+
ctx.logger.debug('Manifest registered via manifest service', {
|
|
46
|
+
id: manifest.id || manifest.name
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
ctx.logger.info('ObjectQL engine registered', {
|
|
52
|
+
services: ['objectql', 'data', 'manifest'],
|
|
75
53
|
});
|
|
76
54
|
|
|
77
55
|
// Register Protocol Implementation
|
|
@@ -86,16 +64,14 @@ export class ObjectQLPlugin implements Plugin {
|
|
|
86
64
|
|
|
87
65
|
start = async (ctx: PluginContext) => {
|
|
88
66
|
ctx.logger.info('ObjectQL engine starting...');
|
|
89
|
-
|
|
90
|
-
//
|
|
67
|
+
|
|
68
|
+
// Sync from external metadata service (e.g. MetadataPlugin) if available
|
|
91
69
|
try {
|
|
92
70
|
const metadataService = ctx.getService('metadata') as any;
|
|
93
|
-
|
|
94
|
-
if (metadataService && !(metadataService instanceof MetadataFacade) && this.ql) {
|
|
71
|
+
if (metadataService && typeof metadataService.loadMany === 'function' && this.ql) {
|
|
95
72
|
await this.loadMetadataFromService(metadataService, ctx);
|
|
96
73
|
}
|
|
97
74
|
} catch (e: any) {
|
|
98
|
-
// No external metadata service or error accessing it
|
|
99
75
|
ctx.logger.debug('No external metadata service to sync from');
|
|
100
76
|
}
|
|
101
77
|
|
|
@@ -109,9 +85,13 @@ export class ObjectQLPlugin implements Plugin {
|
|
|
109
85
|
ctx.logger.debug('Discovered and registered driver service', { serviceName: name });
|
|
110
86
|
}
|
|
111
87
|
if (name.startsWith('app.')) {
|
|
112
|
-
//
|
|
88
|
+
// Legacy fallback: discover app.* services (DEPRECATED)
|
|
89
|
+
ctx.logger.warn(
|
|
90
|
+
`[DEPRECATED] Service "${name}" uses legacy app.* convention. ` +
|
|
91
|
+
`Migrate to ctx.getService('manifest').register(data).`
|
|
92
|
+
);
|
|
113
93
|
this.ql.registerApp(service); // service is Manifest
|
|
114
|
-
ctx.logger.debug('Discovered and registered app service', { serviceName: name });
|
|
94
|
+
ctx.logger.debug('Discovered and registered app service (legacy)', { serviceName: name });
|
|
115
95
|
}
|
|
116
96
|
}
|
|
117
97
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -185,19 +185,22 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
async getMetaItems(request: { type: string }) {
|
|
189
|
-
|
|
188
|
+
async getMetaItems(request: { type: string; packageId?: string }) {
|
|
189
|
+
const { packageId } = request;
|
|
190
|
+
let items = SchemaRegistry.listItems(request.type, packageId);
|
|
190
191
|
// Normalize singular/plural: REST uses singular ('app') but registry may store as plural ('apps')
|
|
191
192
|
if (items.length === 0) {
|
|
192
193
|
const alt = request.type.endsWith('s') ? request.type.slice(0, -1) : request.type + 's';
|
|
193
|
-
items = SchemaRegistry.listItems(alt);
|
|
194
|
+
items = SchemaRegistry.listItems(alt, packageId);
|
|
194
195
|
}
|
|
195
196
|
|
|
196
197
|
// Fallback to database if registry is empty for this type
|
|
197
198
|
if (items.length === 0) {
|
|
198
199
|
try {
|
|
200
|
+
const whereClause: any = { type: request.type, state: 'active' };
|
|
201
|
+
if (packageId) whereClause._packageId = packageId;
|
|
199
202
|
const allRecords = await this.engine.find('sys_metadata', {
|
|
200
|
-
where:
|
|
203
|
+
where: whereClause
|
|
201
204
|
});
|
|
202
205
|
if (allRecords && allRecords.length > 0) {
|
|
203
206
|
items = allRecords.map((record: any) => {
|
|
@@ -235,7 +238,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
235
238
|
};
|
|
236
239
|
}
|
|
237
240
|
|
|
238
|
-
async getMetaItem(request: { type: string, name: string }) {
|
|
241
|
+
async getMetaItem(request: { type: string, name: string, packageId?: string }) {
|
|
239
242
|
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
240
243
|
// Normalize singular/plural
|
|
241
244
|
if (item === undefined) {
|
|
@@ -481,10 +484,11 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
|
|
|
481
484
|
}
|
|
482
485
|
|
|
483
486
|
const records = await this.engine.find(request.object, options);
|
|
487
|
+
// Spec: FindDataResponseSchema — only `records` is returned.
|
|
488
|
+
// OData `value` adaptation (if needed) is handled in the HTTP dispatch layer.
|
|
484
489
|
return {
|
|
485
490
|
object: request.object,
|
|
486
|
-
|
|
487
|
-
records, // Legacy
|
|
491
|
+
records,
|
|
488
492
|
total: records.length,
|
|
489
493
|
hasMore: false
|
|
490
494
|
};
|
package/src/registry.test.ts
CHANGED
|
@@ -53,11 +53,15 @@ describe('SchemaRegistry', () => {
|
|
|
53
53
|
}).not.toThrow();
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it('should
|
|
57
|
-
SchemaRegistry.registerNamespace('
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
it('should allow multiple packages to share a namespace', () => {
|
|
57
|
+
SchemaRegistry.registerNamespace('sys', 'com.objectstack.auth');
|
|
58
|
+
SchemaRegistry.registerNamespace('sys', 'com.objectstack.security');
|
|
59
|
+
// First registered package returned for backwards compat
|
|
60
|
+
expect(SchemaRegistry.getNamespaceOwner('sys')).toBe('com.objectstack.auth');
|
|
61
|
+
expect(SchemaRegistry.getNamespaceOwners('sys')).toEqual([
|
|
62
|
+
'com.objectstack.auth',
|
|
63
|
+
'com.objectstack.security',
|
|
64
|
+
]);
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
it('should unregister namespace', () => {
|
|
@@ -65,6 +69,13 @@ describe('SchemaRegistry', () => {
|
|
|
65
69
|
SchemaRegistry.unregisterNamespace('crm', 'com.example.crm');
|
|
66
70
|
expect(SchemaRegistry.getNamespaceOwner('crm')).toBeUndefined();
|
|
67
71
|
});
|
|
72
|
+
|
|
73
|
+
it('should keep namespace when one of multiple packages unregisters', () => {
|
|
74
|
+
SchemaRegistry.registerNamespace('sys', 'com.objectstack.auth');
|
|
75
|
+
SchemaRegistry.registerNamespace('sys', 'com.objectstack.setup');
|
|
76
|
+
SchemaRegistry.unregisterNamespace('sys', 'com.objectstack.setup');
|
|
77
|
+
expect(SchemaRegistry.getNamespaceOwner('sys')).toBe('com.objectstack.auth');
|
|
78
|
+
});
|
|
68
79
|
});
|
|
69
80
|
|
|
70
81
|
// ==========================================
|
package/src/registry.ts
CHANGED
|
@@ -144,8 +144,8 @@ export class SchemaRegistry {
|
|
|
144
144
|
/** FQN → Merged ServiceObject (cached, invalidated on changes) */
|
|
145
145
|
private static mergedObjectCache = new Map<string, ServiceObject>();
|
|
146
146
|
|
|
147
|
-
/** Namespace → PackageId (
|
|
148
|
-
private static namespaceRegistry = new Map<string, string
|
|
147
|
+
/** Namespace → Set<PackageId> (multiple packages can share a namespace) */
|
|
148
|
+
private static namespaceRegistry = new Map<string, Set<string>>();
|
|
149
149
|
|
|
150
150
|
// ==========================================
|
|
151
151
|
// Generic metadata storage (non-object types)
|
|
@@ -160,22 +160,17 @@ export class SchemaRegistry {
|
|
|
160
160
|
|
|
161
161
|
/**
|
|
162
162
|
* Register a namespace for a package.
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* @throws Error if namespace is already registered to a different package
|
|
163
|
+
* Multiple packages can share the same namespace (e.g. 'sys').
|
|
166
164
|
*/
|
|
167
165
|
static registerNamespace(namespace: string, packageId: string): void {
|
|
168
166
|
if (!namespace) return;
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
`Package "${packageId}" cannot use the same namespace.`
|
|
175
|
-
);
|
|
167
|
+
|
|
168
|
+
let owners = this.namespaceRegistry.get(namespace);
|
|
169
|
+
if (!owners) {
|
|
170
|
+
owners = new Set();
|
|
171
|
+
this.namespaceRegistry.set(namespace, owners);
|
|
176
172
|
}
|
|
177
|
-
|
|
178
|
-
this.namespaceRegistry.set(namespace, packageId);
|
|
173
|
+
owners.add(packageId);
|
|
179
174
|
this.log(`[Registry] Registered namespace: ${namespace} → ${packageId}`);
|
|
180
175
|
}
|
|
181
176
|
|
|
@@ -183,18 +178,32 @@ export class SchemaRegistry {
|
|
|
183
178
|
* Unregister a namespace when a package is uninstalled.
|
|
184
179
|
*/
|
|
185
180
|
static unregisterNamespace(namespace: string, packageId: string): void {
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
182
|
+
if (owners) {
|
|
183
|
+
owners.delete(packageId);
|
|
184
|
+
if (owners.size === 0) {
|
|
185
|
+
this.namespaceRegistry.delete(namespace);
|
|
186
|
+
}
|
|
187
|
+
this.log(`[Registry] Unregistered namespace: ${namespace} ← ${packageId}`);
|
|
190
188
|
}
|
|
191
189
|
}
|
|
192
190
|
|
|
193
191
|
/**
|
|
194
|
-
* Get the
|
|
192
|
+
* Get the packages that use a namespace.
|
|
195
193
|
*/
|
|
196
194
|
static getNamespaceOwner(namespace: string): string | undefined {
|
|
197
|
-
|
|
195
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
196
|
+
if (!owners || owners.size === 0) return undefined;
|
|
197
|
+
// Return the first registered package for backwards compatibility
|
|
198
|
+
return owners.values().next().value;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get all packages that share a namespace.
|
|
203
|
+
*/
|
|
204
|
+
static getNamespaceOwners(namespace: string): string[] {
|
|
205
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
206
|
+
return owners ? Array.from(owners) : [];
|
|
198
207
|
}
|
|
199
208
|
|
|
200
209
|
// ==========================================
|
|
@@ -541,7 +550,7 @@ export class SchemaRegistry {
|
|
|
541
550
|
if (type === 'object' || type === 'objects') {
|
|
542
551
|
return this.getAllObjects(packageId) as unknown as T[];
|
|
543
552
|
}
|
|
544
|
-
|
|
553
|
+
|
|
545
554
|
const items = Array.from(this.metadata.get(type)?.values() || []) as T[];
|
|
546
555
|
if (packageId) {
|
|
547
556
|
return items.filter((item: any) => item._packageId === packageId);
|