Script slow processing search result

I am trying to query DT using AppleScript or JXA.

This query (written in AppleScriptused to run quickly (sub-second response) but recently it has become slow. I don’t know exactly when it became slow. It is possible that this is with the introduction of 4.0 but I simply don’t know. I am currently using 4.2.

So I have rewritten it in JXA with the same performance issues.

The query itself is fast. It searches in a database group that contains bookmarks. I am searching for url’s that match a given search keyword:

  let searchString = `kind:bookmark {any: tags: ${searchKeyword} name:~ ${searchKeyword} url:~ ${searchKeyword}`;
  candidates = dt.search(searchString, { 'in': theGroup });

If I run this query, it is fast. In an example query I get 203 records back back in 83 milliseconds.

So far so good. The next step is to convert returned records to JSON. For that I first convert the record to objects:

const urls = candidates.map((x) => {
  return {
    "the_title": x.name(),
    "the_url": x.url(),
    "the_tags": x.tags()
  }
}
);

This is where the problem starts. Converting the 203 records that match the search keyword takes 12 seconds. This is such a simple operation that it doesn’t make sense.

Is is possible that the records are not fully fetched where I do the search operation? Did something change in DT in this area.

Are you running macOS 26 Tahoe?

Yes. Just updated to 26.3. No change in behavior. Note that the example above is not running AppleScript.

You’re misunderstanding what the script is doing. There is no fetching records here. It’s returning a list of references to the records. Then you have to walk the list, getting properties of each.

And no, it doesn’t matter if this is AppleScript versus JXA.

Querying properties of database contents works almost immediately, though you will need to process the results.

And an example per your output…

tell application id "DNtp"
	-- This returns 3 independent lists, one for each property
	set {recNames, recURLs, recTags} to {name without extension, URL, tags} of (contents of (current database) whose (name contains "manual") and (kind is "PDF+Text")) -- Kind is NOT recommended.
	
	if recNames is not {} then -- A record may not have tags or a URL but it will ALWAYS have a name.
		set od to AppleScript's text item delimiters
		set jsonData to {}
		repeat with incr from 1 to recNames's length -- All the returned lists will be the same length.
			set AppleScript's text item delimiters to "; " -- Multiple tags in a list need to be separated for coercion to a string.
			set theTags to (item incr of recTags) as string
			copy {"{" & linefeed & "\"the_title\": \"" & (item incr of recNames) & "\"" & linefeed & ¬
				"\"the_url\": \"" & (item incr of recURLs) & "\"" & linefeed & ¬
				"\"the_tags\": \"" & (item incr of recTags) & "\"" & linefeed & "}" & linefeed} to end of jsonData
			set AppleScript's text item delimiters to od
		end repeat
		set the clipboard to (jsonData as string)
	end if
end tell
111
1 Like

Thanks. I will look into this approach.

In general whose is blazingly fast. As is search for that matter. I’m still on macOS 15, so I haven’t experienced any speed bumps with scripting.

AFAIK, there’s no equivalent to AppleScript’s set {a, b, c} to {prop1, prop2, prop3} of expression. So, if @BLUEFROG 's code is really faster than yours in Tahoe, you should use that instead of JXA.

Thank you for the support. Everything is working now as expected.

I am sharing the final result, because it may be useful for somebody else as well.

The below script is called by and Alfred workflow to search for URL’s in DEVONthink. It will search in a defined database and within that database in a defined group. Both values are configured in Alfred.

The workflow is activated by a hot key and when you start typing it will search in the bookmarks as found in DEVONthink and do a match on either the name, the URL or a tag.

I cannot upload the Alfred workflow here but if there is interest, I can make a couple of screenshots to get you going.

--
--	Created by: Fred Appelman
--	Created on: 12/02/2026
--
--

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

on run argv
	if (count of argv) > 0 then
		set searchKeyword to item 1 of argv
	else
		set searchKeyword to ""
	end if
	
	-- Get the name of the database and the name of the group from Alfred
	set databaseName to (system attribute "database_name")
	set groupName to (system attribute "group_name")
	
	tell application id "DNtp"
		-- Try to set the database
		try
			set db to database databaseName
		on error errmsg
			return my error_json("Error: No such database '" & databaseName & "'")
		end try
		
		-- Try to set the group
		set theGroup to get record at groupName in db
		if theGroup is missing value then
			return my error_json("Error: No such group '" & groupName & "'")
		end if
		
		-- Retrieve all bookmarks from the specified group and database
		set {recNames, recURLs, recTags} ¬
			to {name without extension, URL, tags} ¬
			of ((children of theGroup) ¬
			whose (kind is "Web internet location"))
		
		-- Filter them on the searchkeyword
		set {filteredNames, filteredTags, filteredURLs} to my filterOnKeyword(recNames, recTags, recURLs, searchKeyword)
		
		-- Convert to JSON
		set objects to my convertToObjectList(filteredNames, filteredTags, filteredURLs)
		return my to_json(objects)
		
	end tell
	
end run

-- Convert the 3 separate lists to a combined array of objects
on convertToObjectList(filteredNames, filteredTags, filteredURLs)
	set objects to {}
	repeat with incr from 1 to filteredNames's length
		set urlTitle to item incr of filteredNames
		set urlAsText to item incr of filteredURLs
		set theTags to item incr of filteredTags
		copy {the_title:urlTitle, the_url:urlAsText, the_tags:theTags} to the end of objects
	end repeat
	return objects
end convertToObjectList

-- Filter the the given lists using the keyword into filtered lists
on filterOnKeyword(recNames, recTags, recURLs, searchKeyword)
	if searchKeyword is "" then
		-- If not searching, return the lists unchanged
		return {recNames, recTags, recURLs}
	end if
	set filteredNames to {}
	set filteredTags to {}
	set filteredURLs to {}
	-- Loop over all the lists. The lists have the same length
	repeat with incr from 1 to recNames's length
		set theTags to (item incr of recTags)
		if (item incr of recNames) contains searchKeyword ¬
			or (item incr of recURLs) contains searchKeyword ¬
			or my hasMatchingTag(theTags, searchKeyword) then
			copy (item incr of recNames) to end of filteredNames
			copy (item incr of recTags) to end of filteredTags
			copy (item incr of recURLs) to end of filteredURLs
		end if
	end repeat
	return {filteredNames, filteredTags, filteredURLs}
end filterOnKeyword

-- Check if the tags match the search keyword
on hasMatchingTag(theTags, searchKeyword)
	repeat with incr from 1 to theTags's length
		set tag to (item incr of theTags)
		if (tag contains searchKeyword) then
			return true
		end if
	end repeat
	return false
end hasMatchingTag

-- Logic to conver to JSON understood by JSON
on to_alfred_json(the_url)
	set theTitle to (get the_title of the_url)
	set theURL to (get the_url of the_url)
	return "{
\"title\": \"" & theURL & "\",
\"subtitle\": \"" & theTitle & "\",
\"arg\": \"" & theURL & "\",
\"quicklookurl\": \"" & theURL & "\"
}"
end to_alfred_json

on empty_json()
	return "{
	\"title\": \"No such URL\"
}"
end empty_json


on error_json(message)
	set saveTID to text item delimiters
	set payload to "{
	\"title\": \"" & message & "\"
}"
	set json to "{\"items\": [" & payload & "]}"
	
	set text item delimiters to saveTID
	return json
end error_json


on convert_list(objects)
	set json_payload to {}
	repeat with the_url in objects
		set converted to to_alfred_json(the_url)
		copy converted to the end of json_payload
	end repeat
	return json_payload
end convert_list

on to_json(objects)
	set saveTID to text item delimiters
	set text item delimiters to ","
	set json_payload to convert_list(objects)
	if (count of json_payload) = 0 then
		set json_payload to empty_json()
	end if
	set json to "{\"items\": [" & json_payload & "]}"
	set text item delimiters to saveTID
	return json
end to_json