Minimalistic JavaScript fails with addition assignment in DT 3.6.1

Consider this JavaScript:

let app = Application("DEVONthink 3");
let mdfile = app.createRecordWith({name: "MyMDFile", 'type' : "markdown"});
mdfile.name +=  "";

It fails miserably on the last line:

app = Application("DEVONthink 3")
app.createRecordWith({"name":"MyMDFile", "type":"markdown"})
	--> app.databases.byId(1).contents.byId(105800)
app.databases.byId(1).contents.byId(105800).name.Symbol.toPrimitive()
	--> Error -1700: Typen können nicht konvertiert werden. 

However

mdfile.name = mdfile.name() + ""; 

works ok. I’m not sure if this is a bug in DT or one of the idiosyncrasies of JXA.

Apparently, it’s the latter: Apple’s TextEditor fails similarly with an addition assignment to its document’s text property.

I’m so glad you answered that one yourself, it was giving me a headache :face_with_head_bandage:

1 Like

name.Symbol

Tell us more … why are you expecting the name reference to have a .Symbol property there ?

typeof app.databases.byId(1).contents.byId(105800).name

//--> 'function'

You can, of course, write things like this:

(() => {
    'use strict';

    const app = Application("DEVONthink 3")
    
    // Assuming the existence of a record with id 40291
    // in the first database.

    // String
    return app.databases.at(0).records.byId(40291).name();
})();

( but I’m not quite sure what you’re after )

I apologise if I was too terse in my post.
The original problem was that an addition assignment like this failed;

r.plainText += " append"

I wanted to use this to append to the plaintext property of a record.
The simple assignment r.plainText = "text" works, so I’d expect the addition assignment += to work, too.
Which it doesn’t. I’m aware that r.name(), r.plainText() etc return the current value of the property. Which indicates that these properties are in fact functions. But if I assign a value to these properties, I do obviously not change a function but set the value its next call returns.
So on the left side of an assignment, name, plainText etc behave like a scalar. As long as one doesn’t try to use a particular assignment operator.

That’s right – the writable property and the get method share a name.

( Incidentally my own experience is that the amount of time lost in debugging shrinks suddenly as soon as we abandon mutable variables and work only with constants – so I personally never reach for mutation operators like +=, but that is another story : -)

in DT 3.6.1

And, of course, as you suggest above:

The pattern:

  • Setter .name
  • Getter .name()

is the architecture of the Automation interface itself – its doesn’t vary between applications (or between application builds).

See Getting and Setting Properties, in:

JavaScript for Automation Release Notes

For an outline map of that interface, from which you can explore the details, you can run the following code:

JS Source
(() => {
    'use strict';

    // Rob Trew 2020

    // Indented outline of macOS Automation library keys
    // (copied to clipboard)

    ObjC.import('AppKit');

    // main :: IO ()
    const main = () => (
        copyText(
            showOutline(
                keyTree([
                    'Automation', 'name', 'prototype',
                    '__private__', '0', 'length'
                ])('Automation')(Automation)
            )
        ),
        'Outline copied to clipboard.'
    );


    // ------------------ NEST OF KEYS -------------------

    // keyTree :: [String] -> String -> 
    // Object -> Tree String
    const keyTree = except =>
        name => obj => {
            const go = seen => o =>
                Object.getOwnPropertyNames(o)
                .sort()
                .flatMap(
                    k => !seen.includes(k) ? [
                        Node(k)(
                            go(seen.concat(k))(
                                o[k]
                            )
                        )
                    ] : []
                );
            return Node(name)(
                go(except)(obj)
            );
        };


    // showOutline :: Tree String -> String
    const showOutline = tree => {
        const go = indent => x =>
            unlines(
                [indent + x.root]
                .concat(
                    x.nest.flatMap(
                        go('    ' + indent)
                    )
                )
            );
        return go('')(tree);
    };

    // -------------------- CLIPBOARD --------------------

    // copyText :: String -> IO String
    const copyText = s => {
        const pb = $.NSPasteboard.generalPasteboard;
        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };

    // ---------------- GENERIC FUNCTIONS ----------------
    // https://github.com/RobTrew/prelude-jxa

    // Node :: a -> [Tree a] -> Tree a
    const Node = v =>
        // Constructor for a Tree node which connects a
        // value of some kind to a list of zero or
        // more child trees.
        xs => ({
            type: 'Node',
            root: v,
            nest: xs || []
        });


    // unlines :: [String] -> String
    const unlines = xs =>
        xs.join('\n');

    // MAIN ---
    return main();
})();

Which interrogates the interface and copies the following outline to the clipboard:

Automation
    Application
        currentApplication
    Library
    ObjC
        $
        Ref
            equals
        bindFunction
        block
        castObjectToRef
        castRefToObject
        deepUnwrap
        dict
        import
        interactWithUser
        registerSubclass
        super
        unwrap
        wrap
    ObjectSpecifier
    Path
    Progress
    delay
    getDisplayString
    initializeGlobalObject
    log