<https://docs.linkeddatahub.com/tutorial/app-as-repository/#content>
        a       <https://w3id.org/atomgraph/linkeddatahub#XHTML>;
        <http://www.w3.org/1999/02/22-rdf-syntax-ns#value>
                "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n    <p class=\"ldh-body-lg\">Make the app reproducible: a git repository that installs into any instance</p>\n    <p>Every stage so far produced files — document descriptions, mapping queries, CSV data, media. Kept in a repository with a small install script, they <em>are</em> the dataspace: versionable,\n        diffable, reviewable, and replayable into any LinkedDataHub instance with <samp>ldh push</samp>. This is the layout the <a href=\"https://github.com/AtomGraph/LinkedDataHub-Apps/tree/master/demo/northwind-traders\" target=\"_blank\">Northwind Traders repository</a> uses:</p>\n    <pre>northwind-traders/\n├── root.ttl              # the target document itself\n├── categories.ttl        # one RDF file per document...\n├── categories/           # ...and a folder for the files it holds\n│   ├── categories.csv\n│   ├── categories.rq\n│   └── *.jpg\n├── admin/model/\n│   ├── ns.ttl            # the namespace ontology\n│   └── patch-ontology.ru # resets it before re-import\n├── imports.csv           # the CSV import manifest\n├── install.sh            # replays everything against $LDH_BASE\n├── .ldhignore            # files that are neither documents nor uploads\n└── Makefile</pre>\n    <p class=\"exhibit-links\">Source: <a href=\"https://github.com/AtomGraph/LinkedDataHub-Apps/tree/master/demo/northwind-traders\" target=\"_blank\">demo/northwind-traders</a> · Live: <a href=\"https://northwind-traders.demo.linkeddatahub.com/\" target=\"_blank\">northwind-traders.demo.linkeddatahub.com</a></p>\n    <p>To follow along, clone the apps repository — <samp>git clone https://github.com/AtomGraph/LinkedDataHub-Apps</samp>, the demo lives in <samp>demo/northwind-traders</samp> — or assemble the\n        files from the previous stages into a folder of the same shape.</p>\n    <div>\n        <h2 id=\"convention\">The convention</h2>\n        <p>The document URL is derived from the file path: <samp>categories.ttl</samp> installs to <samp>${base}categories/</samp>, and <samp>root.ttl</samp> at the top of the tree is the\n            target document <samp>${base}</samp> itself. <a href=\"../../reference/command-line-interface/#push\" target=\"_blank\"><samp>ldh push</samp></a> walks the tree applying that rule: an RDF\n            file is <code>PUT</code> to the document its path spells, with its relative URIs resolved against that URL; every other file is uploaded into its folder's document; a subfolder\n            recurses under the child document the RDF file beside it describes. Anything matched by <samp>.ldhignore</samp> is skipped:</p>\n        <pre>admin/\nMakefile\nimports.csv\nscreenshot.gif\nunesco-mappings.ttl\n*.csv\n*.sh</pre>\n        <p>The patterns apply to the whole subtree below the ignore file, gitignore-style. A name pattern matches at any depth, so entries need not appear in the tree above:\n            <samp>unesco-mappings.ttl</samp> excludes <samp>categories/unesco-mappings.ttl</samp>, which is <a href=\"../../user-guide/import-data/import-rdf-data/\" target=\"_blank\">import material</a>\n            rather than a document, and <samp>screenshot.gif</samp> excludes the repository's own screenshot, which is not app media. A trailing slash restricts a pattern to directories, so\n            <samp>admin/</samp> keeps the ontology sources out of the document tree. Nothing is excluded by default beyond hidden entries, so <samp>*.sh</samp> keeps the scripts from being\n            uploaded, and <samp>*.csv</samp> leaves the data files to the <a href=\"#install\">CSV imports</a>, which upload each one into the document it is imported into.</p>\n        <pre>ldh push --dry-run \"$LDH_BASE\"   # print the plan, send nothing\nldh push \"$LDH_BASE\"</pre>\n    </div>\n    <div>\n        <h2 id=\"idempotency\">Idempotency</h2>\n        <p>Re-running the install converges on the same documents instead of duplicating data. <code>PUT</code> replaces each document's description outright, and the one place that appends rather than replaces — the\n            namespace ontology — is first reset by <samp>patch-ontology.ru</samp>, a SPARQL update that clears everything except the ontology document's own description. (The steps that create rather than replace —\n            <samp>make-public</samp> and each CSV import — are <code>POST</code>s, so a re-run records another authorization and another import; the documents the imports write are the same.)</p>\n        <pre>PREFIX foaf: &lt;http://xmlns.com/foaf/0.1/&gt;\n\nDELETE {\n  ?s ?p ?o\n}\nWHERE {\n  ?s ?p ?o\n  # Exclude triples about the main resource itself\n  MINUS {\n    &lt;&gt; ?p ?o\n    BIND(&lt;&gt; AS ?s)\n  }\n  # Exclude triples about the foaf:primaryTopic resource\n  MINUS {\n    &lt;&gt; foaf:primaryTopic ?primaryTopic .\n    ?primaryTopic ?p ?o\n    BIND(?primaryTopic AS ?s)\n  }\n}</pre>\n        <p class=\"exhibit-links\">Source: <a href=\"https://github.com/AtomGraph/LinkedDataHub-Apps/blob/master/demo/northwind-traders/admin/model/patch-ontology.ru\" target=\"_blank\">admin/model/patch-ontology.ru</a></p>\n    </div>\n    <div>\n        <h2 id=\"install\">Installing</h2>\n        <pre>make install</pre>\n        <p><samp>make install</samp> prompts for the base URI, certificate path, certificate password and an optional proxy URL, exports them as the <samp>LDH_BASE</samp>, <samp>LDH_CERT_FILE</samp>,\n            <samp>LDH_CERT_PASSWORD</samp> and <samp>LDH_PROXY</samp> variables the CLI reads, then runs <samp>install.sh</samp>: <samp>ldh admin make-public</samp>, the namespace ontology\n            install, <samp>ldh push</samp>, and one <samp>ldh import csv</samp> per row of the manifest. The entire tutorial, replayed in one command — against localhost today, against production tomorrow.\n            Export the variables yourself and run <samp>./install.sh</samp> to install without the prompts.</p>\n    </div>\n    <div>\n        <h2 id=\"result\">What you now see</h2>\n        <p>The install prints a banner per step — <samp>### Creating authorization to make the app public</samp>, <samp>### Importing namespace ontology</samp>,\n            <samp>### Pushing documents and files</samp> (with one <samp>PUT</samp> or <samp>POST</samp> line per document or upload), <samp>### Importing CSV data</samp>. When it finishes, open the base\n            URI: the finished app renders, with no certificate required — the first step made it public.</p>\n    </div>\n    <div>\n        <h2 id=\"how-this-works\">How this works</h2>\n        <ul>\n            <li><a href=\"../../reference/command-line-interface/\" target=\"_blank\">Command line interface</a> — one command per HTTP API operation, scriptable and replayable</li>\n            <li><a href=\"../../reference/http-api/\" target=\"_blank\">HTTP API</a> — the <code>PUT</code> semantics that make the replay idempotent</li>\n        </ul>\n    </div>\n    <p class=\"ldh-body-lg\">Next: <a href=\"../beyond-low-code/\">Beyond low-code</a></p>\n</div>"^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral> .

<https://docs.linkeddatahub.com/tutorial/app-as-repository/>
        a       <https://w3id.org/atomgraph/linkeddatahub/document-hierarchy#Item>;
        <http://www.w3.org/1999/02/22-rdf-syntax-ns#_1>
                <https://docs.linkeddatahub.com/tutorial/app-as-repository/#content>;
        <http://purl.org/dc/terms/created>
                "2026-09-29T09:52:40.39Z"^^<http://www.w3.org/2001/XMLSchema#dateTime>;
        <http://purl.org/dc/terms/creator>
                <https://admin.linkeddatahub.com/acl/agents/5ba81960-d511-4a3b-8b9a-cb5bb278dd95/#this>;
        <http://purl.org/dc/terms/description>
                "Make the app reproducible: a git repository that installs into any instance";
        <http://purl.org/dc/terms/title>
                "App as a repository";
        <http://rdfs.org/sioc/ns#has_container>
                <https://docs.linkeddatahub.com/tutorial/>;
        <http://www.w3.org/ns/auth/acl#owner>
                <https://admin.linkeddatahub.com/acl/agents/5ba81960-d511-4a3b-8b9a-cb5bb278dd95/#this> .
