@things-factory/meta-ui 10.1.37 → 10.1.39

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": "@things-factory/meta-ui",
3
- "version": "10.1.37",
3
+ "version": "10.1.39",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -41,24 +41,24 @@
41
41
  "@operato/styles": "^10.0.0",
42
42
  "@operato/time-calculator": "^10.0.0",
43
43
  "@operato/utils": "^10.0.0",
44
- "@things-factory/auth-base": "^10.1.37",
45
- "@things-factory/code-base": "^10.1.37",
44
+ "@things-factory/auth-base": "^10.1.39",
45
+ "@things-factory/code-base": "^10.1.39",
46
46
  "@things-factory/env": "^10.1.20",
47
- "@things-factory/export-base": "^10.1.37",
47
+ "@things-factory/export-base": "^10.1.39",
48
48
  "@things-factory/form-ui": "^10.1.29",
49
- "@things-factory/id-rule-base": "^10.1.37",
50
- "@things-factory/import-base": "^10.1.37",
51
- "@things-factory/import-ui": "^10.1.37",
52
- "@things-factory/integration-base": "^10.1.37",
53
- "@things-factory/lite-menu": "^10.1.37",
54
- "@things-factory/menu-base": "^10.1.37",
55
- "@things-factory/more-base": "^10.1.37",
56
- "@things-factory/personalization": "^10.1.37",
57
- "@things-factory/resource-base": "^10.1.37",
58
- "@things-factory/shell": "^10.1.37",
49
+ "@things-factory/id-rule-base": "^10.1.39",
50
+ "@things-factory/import-base": "^10.1.39",
51
+ "@things-factory/import-ui": "^10.1.39",
52
+ "@things-factory/integration-base": "^10.1.39",
53
+ "@things-factory/lite-menu": "^10.1.39",
54
+ "@things-factory/menu-base": "^10.1.39",
55
+ "@things-factory/more-base": "^10.1.39",
56
+ "@things-factory/personalization": "^10.1.39",
57
+ "@things-factory/resource-base": "^10.1.39",
58
+ "@things-factory/shell": "^10.1.39",
59
59
  "@things-factory/utils": "^10.1.20",
60
- "@things-factory/worklist": "^10.1.37",
60
+ "@things-factory/worklist": "^10.1.39",
61
61
  "moment-timezone": "^0.5.45"
62
62
  },
63
- "gitHead": "d8a69e5c8803a4b9d3289df0cf38e161a207db61"
63
+ "gitHead": "fe5257ad8995fe7e79275e287f36e95b5fd32d1c"
64
64
  }
@@ -0,0 +1,49 @@
1
+ import { join } from 'path'
2
+
3
+ import { buildSchema } from 'type-graphql'
4
+
5
+ /**
6
+ * deleteMetaActivities deletes activity definitions in bulk. Its neighbour updateMultipleMetaActivity
7
+ * asked the activity privilege and it did not; closed 2026-09-24 (architect ruling).
8
+ *
9
+ * Measured from the schema meta-ui boots from, walked by auth-base's privilege directive - the
10
+ * compiled output, since the entities need emitDecoratorMetadata (auth-base tests/compiled.ts).
11
+ */
12
+
13
+ const { schema: declared } = require(join(__dirname, '..', 'dist-server', 'service', 'index.js'))
14
+ const { privilegeDirectiveResolver, privilegeOfField } = require(
15
+ join(__dirname, '..', '..', 'auth-base', 'dist-server', 'service', 'privilege', 'privilege-directive.js')
16
+ )
17
+
18
+ beforeAll(async () => {
19
+ const schema = await buildSchema({
20
+ resolvers: declared.resolverClasses,
21
+ validate: false,
22
+ pubSub: { publish() {}, subscribe() {} } as any
23
+ })
24
+
25
+ privilegeDirectiveResolver(schema)
26
+ })
27
+
28
+ it('asks the same privilege as the other activity definition writes', () => {
29
+ expect(privilegeOfField('Mutation', 'deleteMetaActivities')).toEqual({
30
+ known: true,
31
+ gate: { category: 'activity', privilege: 'mutation', owner: true, super: false }
32
+ })
33
+ expect(privilegeOfField('Mutation', 'updateMultipleMetaActivity')).toEqual(privilegeOfField('Mutation', 'deleteMetaActivities'))
34
+ })
35
+
36
+ it('looks each activity up in the caller\'s domain only', () => {
37
+ /*
38
+ * Unnarrowed, an id from another tenant removed that tenant's template from the process-wide
39
+ * installations. Read off the compiled body: the lookup runs inside a transaction this test cannot
40
+ * stand up without a database.
41
+ */
42
+ const body = require('fs').readFileSync(
43
+ join(__dirname, '..', 'dist-server', 'service', 'meta-activity', 'meta-activity-mutation.js'),
44
+ 'utf8'
45
+ )
46
+ const deleteBody = body.slice(body.indexOf('async deleteMetaActivities('), body.indexOf('async installActivityTemplate('))
47
+
48
+ expect(deleteBody).toMatch(/findOne\(\{\s*where: \{ domain: \{ id: domain\.id \}, id: ids\[i\] \}/)
49
+ })
@@ -0,0 +1,122 @@
1
+ import { join } from 'path'
2
+
3
+ import { buildSchema } from 'type-graphql'
4
+
5
+ /**
6
+ * meta-ui's activity writes stay in the caller's domain. Ruled 2026-09-24.
7
+ *
8
+ * installActivityTemplate asks the activity privilege, as the other writes do - measured from the
9
+ * schema. And an id from another tenant, for an activity or for the menu it embeds, is not reached:
10
+ * the compiled resolver is called with a transaction whose repositories answer only rows of the
11
+ * domain they are asked for.
12
+ */
13
+
14
+ const DIST = join(__dirname, '..', 'dist-server')
15
+ const { schema: declared } = require(join(DIST, 'service', 'index.js'))
16
+ const { MetaActivityMutation } = require(join(DIST, 'service', 'meta-activity', 'meta-activity-mutation.js'))
17
+ const { privilegeDirectiveResolver, privilegeOfField } = require(
18
+ join(__dirname, '..', '..', 'auth-base', 'dist-server', 'service', 'privilege', 'privilege-directive.js')
19
+ )
20
+
21
+ beforeAll(async () => {
22
+ const schema = await buildSchema({
23
+ resolvers: declared.resolverClasses,
24
+ validate: false,
25
+ pubSub: { publish() {}, subscribe() {} } as any
26
+ })
27
+
28
+ privilegeDirectiveResolver(schema)
29
+ })
30
+
31
+ it('installActivityTemplate asks the same privilege as the other activity writes', () => {
32
+ expect(privilegeOfField('Mutation', 'installActivityTemplate')).toEqual({
33
+ known: true,
34
+ gate: { category: 'activity', privilege: 'mutation', owner: true, super: false }
35
+ })
36
+ })
37
+
38
+ /* Rows by entity name; a lookup that names no domain gets nothing, so an unnarrowed query shows. */
39
+ function transaction(rows: Record<string, any[]>, saved: any[], asked: any[]) {
40
+ return {
41
+ getRepository: (entity: any) => ({
42
+ findOne: async ({ where }: any) => {
43
+ asked.push({ entity: entity.name, where })
44
+ return (rows[entity.name] || []).find(one => one.id === where?.id && one.domainId === where?.domain?.id) || null
45
+ },
46
+ find: async ({ where }: any) => {
47
+ asked.push({ entity: entity.name, where })
48
+ return (rows[entity.name] || []).filter(one => one.domainId === where?.domain?.id)
49
+ },
50
+ save: async (row: any) => (saved.push(row), row)
51
+ })
52
+ }
53
+ }
54
+
55
+ const context = (tx: any) =>
56
+ ({
57
+ state: { domain: { id: 'd-here' }, user: { id: 'u-me' }, tx },
58
+ t: (key: string) => key
59
+ }) as any
60
+
61
+ const rows = {
62
+ Activity: [
63
+ { id: 'act-here', domainId: 'd-here', name: 'Mine' },
64
+ { id: 'act-other', domainId: 'd-other', name: 'Theirs' }
65
+ ],
66
+ Menu: [
67
+ { id: 'menu-here', domainId: 'd-here', name: 'mine', routing: 'mine' },
68
+ { id: 'menu-other', domainId: 'd-other', name: 'theirs', routing: 'theirs' }
69
+ ]
70
+ }
71
+
72
+ describe('updateMultipleMetaActivity', () => {
73
+ it("another tenant's activity id is refused by name, and nothing is saved", async () => {
74
+ const saved: any[] = []
75
+ const tx = transaction(rows, saved, [])
76
+
77
+ await expect(
78
+ new MetaActivityMutation().updateMultipleMetaActivity([{ id: 'act-other', cuFlag: 'M' }], context(tx))
79
+ ).rejects.toThrow('error.meta-activity not in this domain')
80
+ expect(saved).toEqual([])
81
+ })
82
+
83
+ it("this domain's activity is updated", async () => {
84
+ const saved: any[] = []
85
+ const tx = transaction(rows, saved, [])
86
+
87
+ await new MetaActivityMutation().updateMultipleMetaActivity([{ id: 'act-here', cuFlag: 'M', name: 'Mine2' }], context(tx))
88
+ expect(saved.map(one => one.id)).toEqual(['act-here'])
89
+ })
90
+
91
+ it("another tenant's menu is not embedded in the model", async () => {
92
+ const saved: any[] = []
93
+ const tx = transaction(rows, saved, [])
94
+
95
+ await new MetaActivityMutation().updateMultipleMetaActivity(
96
+ [{ id: 'act-here', cuFlag: 'M', menu: { id: 'menu-other' } }],
97
+ context(tx)
98
+ )
99
+ expect(JSON.stringify(saved[0].model)).not.toContain('theirs')
100
+ })
101
+
102
+ it("this domain's menu is embedded", async () => {
103
+ const saved: any[] = []
104
+ const tx = transaction(rows, saved, [])
105
+
106
+ await new MetaActivityMutation().updateMultipleMetaActivity(
107
+ [{ id: 'act-here', cuFlag: 'M', menu: { id: 'menu-here' } }],
108
+ context(tx)
109
+ )
110
+ expect(JSON.stringify(saved[0].model)).toContain('mine')
111
+ })
112
+ })
113
+
114
+ describe('installActivityTemplate', () => {
115
+ it("installs from this domain's released activities only", async () => {
116
+ const asked: any[] = []
117
+ const tx = transaction(rows, [], asked)
118
+
119
+ await new MetaActivityMutation().installActivityTemplate(context(tx))
120
+ expect(asked.filter(one => one.entity === 'Activity').every(one => one.where?.domain?.id === 'd-here')).toBe(true)
121
+ })
122
+ })
@@ -1,4 +1,5 @@
1
1
  {
2
+ "error.meta-activity not in this domain": "activity {id} is not in this domain.",
2
3
  "menu.my_job": "My Job",
3
4
  "menu.todo_list": "My Todo List",
4
5
  "menu.approval_pending_list": "My Approval Pending List",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "error.meta-activity not in this domain": "업무 {id} 는 이 사업장의 것이 아닙니다.",
2
3
  "menu.my_job": "나의 업무",
3
4
  "menu.todo_list": "내 업무함",
4
5
  "menu.approval_pending_list": "내 결재함",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "error.meta-activity not in this domain": "tugas {id} bukan milik domain ini.",
2
3
  "menu.my_job": "My Job",
3
4
  "menu.todo_list": "My Todo List",
4
5
  "menu.approval_pending_list": "My Approval Pending List",
@@ -1,4 +1,5 @@
1
1
  {
2
+ "error.meta-activity not in this domain": "业务 {id} 不属于此站点。",
2
3
  "menu.my_job": "My Job",
3
4
  "menu.todo_list": "My Todo List",
4
5
  "menu.approval_pending_list": "My Approval Pending List",