@open-mercato/shared 0.4.11-develop.1536.bce594656c → 0.4.11-develop.1537.8724d715df
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/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.11-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.4.11-develop.1537.8724d715df'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/core'
|
|
2
|
+
import { buildScopedWhere, extractScopeFromAuth, findOneScoped, softDelete } from '../crud'
|
|
3
|
+
|
|
4
|
+
class ExampleEntity {
|
|
5
|
+
id = ''
|
|
6
|
+
organizationId?: string | null
|
|
7
|
+
tenantId?: string | null
|
|
8
|
+
companyId?: string | null
|
|
9
|
+
workspaceId?: string | null
|
|
10
|
+
deletedAt?: Date | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('buildScopedWhere', () => {
|
|
14
|
+
it('adds organization, tenant, and soft-delete filters without mutating the base object', () => {
|
|
15
|
+
const base = { id: 'entity-1' }
|
|
16
|
+
|
|
17
|
+
const where = buildScopedWhere(base, {
|
|
18
|
+
organizationId: 'org-1',
|
|
19
|
+
tenantId: 'tenant-1',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
expect(where).toEqual({
|
|
23
|
+
id: 'entity-1',
|
|
24
|
+
organizationId: 'org-1',
|
|
25
|
+
tenantId: 'tenant-1',
|
|
26
|
+
deletedAt: null,
|
|
27
|
+
})
|
|
28
|
+
expect(where).not.toBe(base)
|
|
29
|
+
expect(base).toEqual({ id: 'entity-1' })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('prefers organizationIds and collapses them to scalar or $in filters after sanitizing empty values', () => {
|
|
33
|
+
expect(
|
|
34
|
+
buildScopedWhere(
|
|
35
|
+
{ id: 'entity-1' },
|
|
36
|
+
{ organizationId: 'org-ignored', organizationIds: ['org-1'], tenantId: 'tenant-1' }
|
|
37
|
+
)
|
|
38
|
+
).toEqual({
|
|
39
|
+
id: 'entity-1',
|
|
40
|
+
organizationId: 'org-1',
|
|
41
|
+
tenantId: 'tenant-1',
|
|
42
|
+
deletedAt: null,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
expect(
|
|
46
|
+
buildScopedWhere(
|
|
47
|
+
{ id: 'entity-1' },
|
|
48
|
+
{ organizationIds: ['org-1', '', 'org-2'], tenantId: 'tenant-1' }
|
|
49
|
+
)
|
|
50
|
+
).toEqual({
|
|
51
|
+
id: 'entity-1',
|
|
52
|
+
organizationId: { $in: ['org-1', 'org-2'] },
|
|
53
|
+
tenantId: 'tenant-1',
|
|
54
|
+
deletedAt: null,
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('fails closed when organizationIds is explicitly empty', () => {
|
|
59
|
+
expect(
|
|
60
|
+
buildScopedWhere(
|
|
61
|
+
{ id: 'entity-1' },
|
|
62
|
+
{ organizationIds: [], tenantId: 'tenant-1' }
|
|
63
|
+
)
|
|
64
|
+
).toEqual({
|
|
65
|
+
id: 'entity-1',
|
|
66
|
+
organizationId: { $in: [] },
|
|
67
|
+
tenantId: 'tenant-1',
|
|
68
|
+
deletedAt: null,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
expect(
|
|
72
|
+
buildScopedWhere(
|
|
73
|
+
{ id: 'entity-1' },
|
|
74
|
+
{ organizationIds: null, tenantId: 'tenant-1' }
|
|
75
|
+
)
|
|
76
|
+
).toEqual({
|
|
77
|
+
id: 'entity-1',
|
|
78
|
+
organizationId: { $in: [] },
|
|
79
|
+
tenantId: 'tenant-1',
|
|
80
|
+
deletedAt: null,
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('supports custom scope fields and disabling implicit scope clauses', () => {
|
|
85
|
+
expect(
|
|
86
|
+
buildScopedWhere(
|
|
87
|
+
{ id: 'entity-1' },
|
|
88
|
+
{
|
|
89
|
+
organizationId: 'org-1',
|
|
90
|
+
tenantId: 'tenant-1',
|
|
91
|
+
orgField: 'companyId',
|
|
92
|
+
tenantField: 'workspaceId',
|
|
93
|
+
softDeleteField: 'archivedAt',
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
).toEqual({
|
|
97
|
+
id: 'entity-1',
|
|
98
|
+
companyId: 'org-1',
|
|
99
|
+
workspaceId: 'tenant-1',
|
|
100
|
+
archivedAt: null,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
expect(
|
|
104
|
+
buildScopedWhere(
|
|
105
|
+
{ id: 'entity-1' },
|
|
106
|
+
{
|
|
107
|
+
organizationId: 'org-1',
|
|
108
|
+
tenantId: 'tenant-1',
|
|
109
|
+
orgField: null,
|
|
110
|
+
tenantField: null,
|
|
111
|
+
softDeleteField: null,
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
).toEqual({ id: 'entity-1' })
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('preserves an explicit null organization scope to keep queries fail-closed', () => {
|
|
118
|
+
expect(
|
|
119
|
+
buildScopedWhere(
|
|
120
|
+
{ id: 'entity-1' },
|
|
121
|
+
{ organizationId: null, tenantId: 'tenant-1' }
|
|
122
|
+
)
|
|
123
|
+
).toEqual({
|
|
124
|
+
id: 'entity-1',
|
|
125
|
+
organizationId: null,
|
|
126
|
+
tenantId: 'tenant-1',
|
|
127
|
+
deletedAt: null,
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('extractScopeFromAuth', () => {
|
|
133
|
+
it('returns an empty scope when auth is missing', () => {
|
|
134
|
+
expect(extractScopeFromAuth(null)).toEqual({})
|
|
135
|
+
expect(extractScopeFromAuth(undefined)).toEqual({})
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('maps auth fields and normalizes missing values to null', () => {
|
|
139
|
+
expect(extractScopeFromAuth({ orgId: 'org-1', tenantId: 'tenant-1' })).toEqual({
|
|
140
|
+
organizationId: 'org-1',
|
|
141
|
+
tenantId: 'tenant-1',
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
expect(extractScopeFromAuth({})).toEqual({
|
|
145
|
+
organizationId: null,
|
|
146
|
+
tenantId: null,
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe('findOneScoped', () => {
|
|
152
|
+
it('queries by id with the default organization and tenant fields when scope values are present', async () => {
|
|
153
|
+
const entity = new ExampleEntity()
|
|
154
|
+
entity.id = 'entity-1'
|
|
155
|
+
const findOne = jest.fn(async () => entity)
|
|
156
|
+
const getRepository = jest.fn(() => ({ findOne }))
|
|
157
|
+
const em = { getRepository } as unknown as EntityManager
|
|
158
|
+
|
|
159
|
+
const result = await findOneScoped(em, ExampleEntity, 'entity-1', {
|
|
160
|
+
organizationId: 'org-1',
|
|
161
|
+
tenantId: 'tenant-1',
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
expect(getRepository).toHaveBeenCalledWith(ExampleEntity)
|
|
165
|
+
expect(findOne).toHaveBeenCalledWith({
|
|
166
|
+
id: 'entity-1',
|
|
167
|
+
organizationId: 'org-1',
|
|
168
|
+
tenantId: 'tenant-1',
|
|
169
|
+
})
|
|
170
|
+
expect(result).toBe(entity)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('omits nullable scope values and honors custom field names', async () => {
|
|
174
|
+
const findOne = jest.fn(async () => null)
|
|
175
|
+
const em = {
|
|
176
|
+
getRepository: jest.fn(() => ({ findOne })),
|
|
177
|
+
} as unknown as EntityManager
|
|
178
|
+
|
|
179
|
+
await findOneScoped(em, ExampleEntity, 'entity-2', {
|
|
180
|
+
organizationId: null,
|
|
181
|
+
tenantId: undefined,
|
|
182
|
+
orgField: 'companyId',
|
|
183
|
+
tenantField: 'workspaceId',
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
expect(findOne).toHaveBeenCalledWith({ id: 'entity-2' })
|
|
187
|
+
|
|
188
|
+
await findOneScoped(em, ExampleEntity, 'entity-3', {
|
|
189
|
+
organizationId: 'org-3',
|
|
190
|
+
tenantId: 'tenant-3',
|
|
191
|
+
orgField: 'companyId',
|
|
192
|
+
tenantField: 'workspaceId',
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
expect(findOne).toHaveBeenLastCalledWith({
|
|
196
|
+
id: 'entity-3',
|
|
197
|
+
companyId: 'org-3',
|
|
198
|
+
workspaceId: 'tenant-3',
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('softDelete', () => {
|
|
204
|
+
it('sets deletedAt and persists the updated entity', async () => {
|
|
205
|
+
const entity = new ExampleEntity()
|
|
206
|
+
const persistAndFlush = jest.fn(async () => undefined)
|
|
207
|
+
const em = { persistAndFlush } as unknown as EntityManager
|
|
208
|
+
const before = Date.now()
|
|
209
|
+
|
|
210
|
+
await softDelete(em, entity)
|
|
211
|
+
|
|
212
|
+
expect(entity.deletedAt).toBeInstanceOf(Date)
|
|
213
|
+
expect((entity.deletedAt as Date).getTime()).toBeGreaterThanOrEqual(before)
|
|
214
|
+
expect(persistAndFlush).toHaveBeenCalledWith(entity)
|
|
215
|
+
})
|
|
216
|
+
})
|