That actually _can_ be how it works!
Fundamentally, Godot is Object Oriented, but there is _no_ reason that has to extend beyond the base Scene node. This is because you can just define properties on your classes that represent all of those things and they can be primitives and other objects as well. An inventory absolutely _can_ just be an array.
Where I think you're getting mixed up is that you're thinking of an ECS-style implementation where raw data gets slung around to various servers which do their things to that data, and then seeing that Godot doesn't really support that out of the box, thinking it must be the exact opposite and Godot Demands that Everything Is A Node.
Here's the trick: the only things that actually need to be nodes are the things that get represented in screen space! You can (and should) use Resource classes to represent your data! Using your inventory example -- you can specify a Player class that has an inventory which is an array of Items, e.g. @export var inventory: Array[Item] = [], and Item then extends Resource instead -- it's just a collection of @export fields defining what an Item's properties are. And you can, of course, extend the Item class and add them to the array. Critically, those properties can be things like models and textures and other data used for your code to know how to render the items dynamically.
Or, if you want, you can still use a Scene to represent each item, and the inventory property will still work for that Player to hold references to those Items.
Another spot you might be getting tripped up is "okay, a player has an inventory of items which are data, and I have a bunch of items, how do I get the Player instance to add an Item to their inventory?" Which goes back to Globals and Signals.
Globals are singleton classes which are accessible anywhere. This means you can break object oriented design completely by relying on them. You can make a single giant GameManager Global and every Node and Resource you create can just call methods on the game manager. I don't recommend it because when you get the hang of Signals it just feels inelegant.
Signals are just special functions that allow one Object's script to reach out to another Object's script. This is mostly done in the UI to "wire" behaviors, and tutorials on this are clear. Since you're thinking code-first, and using the inventory and assuming you have a Globall StageManager that holds a reference to the Player:
Let's say you have an Item class which extends Resource, and an InventoryItem class which extends Node3D and is built as a typical node tree -- it has children which represent its model and its boundaries, and it has an @export var item: Item , so you can set the item in the UI to a presaved item representation or create the representation of the data right there.
You can then go to the Area3D representing the object boundaries, click on the signals pane, find the input_event signal, and hook it up by double-clicking. That connects input events of all kind to the function that gets created. Then in there you could do something like, StageManager.player.inventory.append((get_parent().item) . Et voila!
But to connect a signal in code is just "object.its_signal.connect(other_object, 'func_on_that_object')". To emit a signal in code is "object.its_signal.emit()". If you're familiar with the Pub/Sub pattern, it's literally that. So another way you could do the above:
Add func add_item(item: Item): inventory.append(item) to Player.
Add a "signal item_clicked(item: Item)" to InventoryItem.
Add an on_tree_entered to your InventoryItem that has a signal_item_clicked.connect(StageManager.current_player, "add_item", item)
Then in the input_event handler function on your InventoryItem, after verifying whatever, you call "item_clicked.emit(item)"
Honestly I really recommend reading the GDScript reference. It's actually enjoyable (or it was to me anyways).